use blstrs::Compress;
use crossbeam_channel::bounded;
use ff::{Field, PrimeField};
use group::{prime::PrimeCurveAffine, Curve, Group};
use log::{debug, info};
use pairing::{Engine, MultiMillerLoop};
use rayon::prelude::*;
use serde::Serialize;
use super::{
accumulator::PairingChecks,
inner_product,
prove::polynomial_evaluation_product_form_from_transcript,
structured_scalar_power,
transcript::{Challenge, Transcript},
AggregateProof, AggregateProofAndInstance, KZGOpening, VerifierSRS,
};
use crate::groth16::{
aggregate::AggregateVersion,
multiscalar::{par_multiscalar, MultiscalarPrecomp, ScalarList},
PreparedVerifyingKey,
};
use crate::SynthesisError;
use std::default::Default;
use std::ops::{AddAssign, MulAssign, SubAssign};
use std::time::Instant;
pub fn verify_aggregate_proof<E, R>(
ip_verifier_srs: &VerifierSRS<E>,
pvk: &PreparedVerifyingKey<E>,
rng: R,
public_inputs: &[Vec<E::Fr>],
proof: &AggregateProof<E>,
transcript_include: &[u8],
version: AggregateVersion,
) -> Result<bool, SynthesisError>
where
E: MultiMillerLoop + std::fmt::Debug,
E::Fr: Serialize,
<E as Engine>::Gt: Compress + Serialize,
E::G1: Serialize,
E::G1Affine: Serialize,
E::G2Affine: Serialize,
R: rand_core::RngCore + Send,
{
info!("verify_aggregate_proof");
proof.parsing_check()?;
for pub_input in public_inputs {
if (pub_input.len() + 1) != pvk.ic.len() {
return Err(SynthesisError::MalformedVerifyingKey);
}
}
if public_inputs.len() != proof.tmipp.gipa.nproofs as usize {
return Err(SynthesisError::MalformedProofs(
"public inputs length does not match nproofs".to_string(),
));
}
let hcom = Transcript::<E>::new("hcom")
.write(&proof.com_ab)
.write(&proof.com_c)
.into_challenge();
let r = Transcript::<E>::new("random-r")
.write(&hcom)
.write(&transcript_include)
.into_challenge();
let pairing_checks = PairingChecks::new(rng);
let pairing_checks_copy = &pairing_checks;
let now = Instant::now();
verify_tipp_mipp::<E, R>(
ip_verifier_srs,
proof,
&r, pairing_checks_copy,
&hcom,
version,
);
debug!("TIPP took {} ms", now.elapsed().as_millis(),);
info!("checking aggregate pairing");
let mut r_sum = r.pow_vartime(&[public_inputs.len() as u64]);
r_sum.sub_assign(&E::Fr::one());
let b = (*r - E::Fr::one()).invert().unwrap();
r_sum.mul_assign(&b);
let (r_vec_sender, r_vec_receiver) = bounded(1);
let now = Instant::now();
r_vec_sender
.send(structured_scalar_power(public_inputs.len(), &*r))
.unwrap();
let elapsed = now.elapsed().as_millis();
debug!("generation of r vector: {}ms", elapsed);
par! {
let left = {
let mut alpha_g1_r_sum = pvk.alpha_g1;
alpha_g1_r_sum.mul_assign(r_sum);
E::multi_miller_loop(&[(&alpha_g1_r_sum.to_affine(), &pvk.beta_g2)])
},
let right = {
E::multi_miller_loop(&[(
&proof.agg_c.to_affine(),
&pvk.delta_g2,
)])
},
let middle = {
let l = public_inputs[0].len();
let mut g_ic = pvk.ic_projective[0];
g_ic.mul_assign(r_sum);
let powers = r_vec_receiver.recv().unwrap();
let now = Instant::now();
let getter = |i: usize| -> <E::Fr as PrimeField>::Repr {
let mut c = public_inputs[0][i];
for j in 1..public_inputs.len() {
let mut ai = public_inputs[j][i];
ai.mul_assign(&powers[j]);
c.add_assign(&ai);
}
c.to_repr()
};
let totsi = par_multiscalar::<_, E::G1Affine>(
&ScalarList::Getter(getter, l),
&pvk.multiscalar.at_point(1),
std::mem::size_of::<<E::Fr as PrimeField>::Repr>() * 8,
);
g_ic.add_assign(&totsi);
let ml = E::multi_miller_loop(&[(&g_ic.to_affine(), &pvk.gamma_g2)]);
let elapsed = now.elapsed().as_millis();
debug!("table generation: {}ms", elapsed);
ml
}
};
pairing_checks_copy.merge_nonrandom(
vec![left, middle, right],
proof.ip_ab,
);
let res = pairing_checks.verify();
info!("aggregate verify done");
res
}
#[allow(clippy::too_many_arguments)]
pub fn verify_aggregate_proof_and_aggregate_instances<
E: Engine + std::fmt::Debug,
R: rand::RngCore + Send,
>(
ip_verifier_srs: &VerifierSRS<E>,
pvk: &PreparedVerifyingKey<E>,
rng: R,
public_inputs: &[E::Fr],
public_outputs: &[E::Fr],
aggregate_proof_and_instance: &AggregateProofAndInstance<E>,
transcript_include: &[u8],
version: AggregateVersion,
) -> Result<bool, SynthesisError>
where
E: MultiMillerLoop + std::fmt::Debug,
E::Fr: Serialize,
<E as Engine>::Gt: Compress + Serialize,
E::G1: Serialize,
E::G1Affine: Serialize,
E::G2Affine: Serialize,
R: rand_core::RngCore + Send,
{
info!("verify_aggregate_proof");
aggregate_proof_and_instance.parsing_check()?;
let proof = &aggregate_proof_and_instance.pi_agg;
if (public_inputs.len() + public_outputs.len() + 1) != pvk.ic.len() {
return Err(SynthesisError::MalformedVerifyingKey);
}
let transcript_new = Transcript::<E>::new("transcript-with-coms")
.write(&aggregate_proof_and_instance.com_f)
.write(&aggregate_proof_and_instance.com_w0)
.write(&aggregate_proof_and_instance.com_wd)
.write(&transcript_include)
.into_bytes();
let hcom = Transcript::<E>::new("hcom")
.write(&proof.com_ab)
.write(&proof.com_c)
.into_challenge();
let r = Transcript::<E>::new("random-r")
.write(&hcom)
.write(&transcript_new)
.into_challenge();
let r_f = (*r).pow_vartime(&[1u64]);
let pairing_checks: PairingChecks<E, R> = PairingChecks::new(rng);
let pairing_checks_copy = &pairing_checks;
for (i, public_input) in public_inputs.iter().enumerate() {
let d = (aggregate_proof_and_instance.com_f[i] - (ip_verifier_srs.g * public_input))
.to_affine();
pairing_checks_copy.merge_miller_inputs(
&[
(&d, &ip_verifier_srs.h.to_affine()),
(
&aggregate_proof_and_instance.com_w0[i].to_affine(),
&ip_verifier_srs.h_alpha.to_affine(),
),
],
&<E as Engine>::Gt::generator(),
);
pairing_checks_copy.merge_miller_inputs(
&[
(
&aggregate_proof_and_instance.com_f[i].to_affine(),
&ip_verifier_srs.h_alpha_d.to_affine(),
),
(
&aggregate_proof_and_instance.com_wd[i].to_affine(),
&ip_verifier_srs.h.to_affine(),
),
],
&<E as Engine>::Gt::generator(),
);
let d2 = (aggregate_proof_and_instance.com_f[i]
- (ip_verifier_srs.g * aggregate_proof_and_instance.f_eval[i]))
.to_affine();
let d2 = -d2;
let d3 = (ip_verifier_srs.h_alpha - (ip_verifier_srs.h * r_f)).to_affine();
pairing_checks_copy.merge_miller_inputs(
&[
(&d2, &ip_verifier_srs.h.to_affine()),
(
&aggregate_proof_and_instance.f_eval_proof[i].to_affine(),
&d3,
),
],
&<E as Engine>::Gt::generator(),
);
}
rayon::scope(move |_s| {
let now = Instant::now();
verify_tipp_mipp::<E, R>(
ip_verifier_srs,
proof,
&r, pairing_checks_copy,
&hcom,
version,
);
debug!("TIPP took {} ms", now.elapsed().as_millis(),);
info!("checking aggregate pairing");
let mut r_sum = r.pow_vartime(&[ip_verifier_srs.n as u64]);
r_sum.sub_assign(&E::Fr::one());
let b = (*r - E::Fr::one()).invert().unwrap();
r_sum.mul_assign(&b);
par! {
let left = {
let alpha_g1_r_sum = pvk.alpha_g1 * r_sum;
E::multi_miller_loop(&[(&alpha_g1_r_sum.to_affine(), &pvk.beta_g2)])
},
let middle = {
let mut g_ic = pvk.ic_projective[0] * r_sum;
for i in 0..public_inputs.len() {
g_ic += pvk.ic[1 + i] * aggregate_proof_and_instance.f_eval[i];
let mut d = aggregate_proof_and_instance.f_eval[i] - public_inputs[i];
d *= &r.invert().unwrap();
let n_neg_one = (ip_verifier_srs.n - 1) as u64;
d += public_outputs[i] * r.pow_vartime(&[n_neg_one]) ;
let pk_ic_in = pvk.ic[1 + i + public_inputs.len()] * d;
g_ic += pk_ic_in;
}
E::multi_miller_loop(&[(&g_ic.to_affine() , &pvk.gamma_g2)])
},
let right = {
E::multi_miller_loop(&[(
&proof.agg_c.to_affine(),
&pvk.delta_g2,
)])
}
};
pairing_checks_copy.merge_nonrandom(
vec![left, middle, right],
proof.ip_ab,
);
});
let res = pairing_checks.verify();
info!("aggregate verify done");
res
}
fn verify_tipp_mipp<E, R>(
v_srs: &VerifierSRS<E>,
proof: &AggregateProof<E>,
r_shift: &E::Fr,
pairing_checks: &PairingChecks<E, R>,
hcom: &Challenge<E>,
version: AggregateVersion,
) where
E: MultiMillerLoop,
E::Fr: Serialize,
<E as Engine>::Gt: Compress + Serialize,
E::G1: Serialize,
E::G1Affine: Serialize,
E::G2Affine: Serialize,
R: rand_core::RngCore + Send,
{
info!("verify with srs shift");
let now = Instant::now();
let (final_res, final_r, challenges, challenges_inv, extra_challenge) =
gipa_verify_tipp_mipp(proof, r_shift, hcom, version);
debug!(
"TIPP verify: gipa verify tipp {}ms",
now.elapsed().as_millis()
);
let fvkey = proof.tmipp.gipa.final_vkey;
let fwkey = proof.tmipp.gipa.final_wkey;
let final_a = &proof.tmipp.gipa.final_a;
let final_b = &proof.tmipp.gipa.final_b;
let final_c = &proof.tmipp.gipa.final_c;
let final_zab = &final_res.zab;
let final_tab = &final_res.tab;
let final_uab = &final_res.uab;
let final_tc = &final_res.tc;
let final_uc = &final_res.uc;
let c = match version {
AggregateVersion::V1 => Transcript::<E>::new("random-z")
.write(&challenges[0])
.write(&fvkey.0)
.write(&fvkey.1)
.write(&fwkey.0)
.write(&fwkey.1)
.into_challenge(),
AggregateVersion::V2 => Transcript::<E>::new("random-z")
.write(&extra_challenge)
.write(&fvkey.0)
.write(&fvkey.1)
.write(&fwkey.0)
.write(&fwkey.1)
.write(final_a)
.write(final_b)
.write(final_c)
.into_challenge(),
};
let now = Instant::now();
par! {
let _vtuple = verify_kzg_v(
v_srs,
&fvkey,
&proof.tmipp.vkey_opening,
&challenges_inv,
&c,
pairing_checks,
),
let _wtuple = verify_kzg_w(
v_srs,
&fwkey,
&proof.tmipp.wkey_opening,
&challenges,
&r_shift.invert().unwrap(),
&c,
pairing_checks,
),
let _check_z = pairing_checks.merge_miller_inputs(&[(final_a, final_b)], final_zab),
let _check_ab0 = pairing_checks.merge_miller_inputs(&[(final_a, &fvkey.0),(&fwkey.0, final_b)], final_tab),
let _check_ab1 = pairing_checks.merge_miller_inputs(&[(final_a, &fvkey.1),(&fwkey.1, final_b)], final_uab),
let final_z =
inner_product::multiexponentiation::<E::G1Affine>(&[*final_c],
&[final_r]),
let _check_t = pairing_checks.merge_miller_inputs(&[(final_c,&fvkey.0)], final_tc),
let _check_u = pairing_checks.merge_miller_inputs(&[(final_c,&fvkey.1)], final_uc)
};
match final_z {
Err(e) => pairing_checks.report_err(e),
Ok(z) => {
debug!(
"TIPP verify: parallel checks before merge: {}ms",
now.elapsed().as_millis(),
);
let b = z == final_res.zc;
if !b {
pairing_checks.invalidate();
}
}
}
}
#[allow(clippy::type_complexity)]
fn gipa_verify_tipp_mipp<E>(
proof: &AggregateProof<E>,
r_shift: &E::Fr,
hcom: &E::Fr,
version: AggregateVersion,
) -> (GipaTUZ<E>, E::Fr, Vec<E::Fr>, Vec<E::Fr>, E::Fr)
where
E: MultiMillerLoop,
E::Fr: Serialize,
<E as Engine>::Gt: Compress + Serialize,
E::G1: Serialize,
E::G1Affine: Serialize,
E::G2Affine: Serialize,
{
info!("gipa verify TIPP [version {}]", version);
let gipa = &proof.tmipp.gipa;
let comms_ab = &gipa.comms_ab;
let comms_c = &gipa.comms_c;
let zs_ab = &gipa.z_ab;
let zs_c = &gipa.z_c;
let now = Instant::now();
let mut challenges = Vec::new();
let mut challenges_inv = Vec::new();
let mut c_inv: E::Fr = *Transcript::<E>::new("gipa-0")
.write(hcom)
.write(&proof.ip_ab)
.write(&proof.agg_c)
.write(&r_shift)
.into_challenge();
let mut c = c_inv.invert().unwrap();
for (i, ((comm_ab, z_ab), (comm_c, z_c))) in comms_ab
.iter()
.zip(zs_ab.iter())
.zip(comms_c.iter().zip(zs_c.iter()))
.enumerate()
{
let (tab_l, tab_r) = comm_ab;
let (zab_l, zab_r) = z_ab;
let (tc_l, tc_r) = comm_c;
let (zc_l, zc_r) = z_c;
if i == 0 {
match version {
AggregateVersion::V1 => {
}
AggregateVersion::V2 => {
c_inv = *Transcript::<E>::new("gipa-0")
.write(&c_inv)
.write(&zab_l)
.write(&zab_r)
.write(&zc_l)
.write(&zc_r)
.write(&tab_l.0)
.write(&tab_l.1)
.write(&tab_r.0)
.write(&tab_r.1)
.write(&tc_l.0)
.write(&tc_l.1)
.write(&tc_r.0)
.write(&tc_r.1)
.into_challenge();
c = c_inv.invert().unwrap();
}
}
} else {
c_inv = *Transcript::<E>::new(&format!("gipa-{}", i))
.write(&c_inv)
.write(&zab_l)
.write(&zab_r)
.write(&zc_l)
.write(&zc_r)
.write(&tab_l.0)
.write(&tab_l.1)
.write(&tab_r.0)
.write(&tab_r.1)
.write(&tc_l.0)
.write(&tc_l.1)
.write(&tc_r.0)
.write(&tc_r.1)
.into_challenge();
c = c_inv.invert().unwrap();
}
challenges.push(c);
challenges_inv.push(c_inv);
info!("verify: challenge {} -> {:?}", i, c);
}
debug!(
"TIPP verify: gipa challenge gen took {}ms",
now.elapsed().as_millis()
);
let now = Instant::now();
let (t_ab, u_ab) = proof.com_ab;
let z_ab = proof.ip_ab; let (final_zab_l, final_zab_r) = proof.tmipp.gipa.z_ab.last().unwrap();
let (final_zc_l, final_zc_r) = proof.tmipp.gipa.z_c.last().unwrap();
let (final_tab_l, final_tab_r) = proof.tmipp.gipa.comms_ab.last().unwrap();
let (final_tuc_l, final_tuc_r) = proof.tmipp.gipa.comms_c.last().unwrap();
let (t_c, u_c) = proof.com_c;
let z_c = proof.agg_c;
let mut final_res = GipaTUZ {
tab: t_ab,
uab: u_ab,
zab: z_ab,
tc: t_c,
uc: u_c,
zc: z_c,
};
let extra_challenge = *Transcript::<E>::new("gipa-extra-link")
.write(&challenges.last().unwrap())
.write(&proof.tmipp.gipa.final_a)
.write(&proof.tmipp.gipa.final_b)
.write(&proof.tmipp.gipa.final_c)
.write(&final_zab_l)
.write(&final_zab_r)
.write(&final_zc_l)
.write(&final_zc_r)
.write(&final_tab_l.0)
.write(&final_tab_l.1)
.write(&final_tab_r.0)
.write(&final_tab_r.1)
.write(&final_tuc_l.0)
.write(&final_tuc_l.1)
.write(&final_tuc_r.0)
.write(&final_tuc_r.1)
.into_challenge();
debug!("verify: extra challenge {:?}", extra_challenge);
#[allow(clippy::upper_case_acronyms)]
enum Op<'a, E>
where
E: MultiMillerLoop,
{
TAB(&'a <E as Engine>::Gt, &'a E::Fr),
UAB(&'a <E as Engine>::Gt, &'a E::Fr),
ZAB(&'a <E as Engine>::Gt, &'a E::Fr),
TC(&'a <E as Engine>::Gt, &'a E::Fr),
UC(&'a <E as Engine>::Gt, &'a E::Fr),
ZC(&'a E::G1, &'a E::Fr),
}
let res = comms_ab
.par_iter()
.zip(zs_ab.par_iter())
.zip(comms_c.par_iter().zip(zs_c.par_iter()))
.zip(challenges.par_iter().zip(challenges_inv.par_iter()))
.flat_map(|(((comm_ab, z_ab), (comm_c, z_c)), (c, c_inv))| {
let ((tab_l, uab_l), (tab_r, uab_r)) = comm_ab;
let (zab_l, zab_r) = z_ab;
let ((tc_l, uc_l), (tc_r, uc_r)) = comm_c;
let (zc_l, zc_r) = z_c;
vec![
Op::TAB::<E>(tab_l, c),
Op::TAB(tab_r, c_inv),
Op::UAB(uab_l, c),
Op::UAB(uab_r, c_inv),
Op::ZAB(zab_l, c),
Op::ZAB(zab_r, c_inv),
Op::TC::<E>(tc_l, c),
Op::TC(tc_r, c_inv),
Op::UC(uc_l, c),
Op::UC(uc_r, c_inv),
Op::ZC(zc_l, c),
Op::ZC(zc_r, c_inv),
]
})
.fold(GipaTUZ::<E>::default, |mut res, op: Op<E>| {
match op {
Op::TAB(tx, c) => {
let tx = *tx * c;
res.tab += tx;
}
Op::UAB(ux, c) => {
let ux = *ux * c;
res.uab += ux;
}
Op::ZAB(zx, c) => {
let zx = *zx * c;
res.zab += zx;
}
Op::TC(tx, c) => {
let tx = *tx * c;
res.tc += tx;
}
Op::UC(ux, c) => {
let ux = *ux * c;
res.uc += ux;
}
Op::ZC(zx, c) => {
let zx = *zx * c;
res.zc += zx;
}
}
res
})
.reduce(GipaTUZ::default, |mut acc_res, res| {
acc_res.merge(&res);
acc_res
});
challenges.reverse();
challenges_inv.reverse();
let ref_final_res = &mut final_res;
let ref_challenges_inv = &challenges_inv;
ref_final_res.merge(&res);
let final_r = polynomial_evaluation_product_form_from_transcript(
ref_challenges_inv,
r_shift,
&E::Fr::one(),
);
debug!(
"TIPP verify: gipa prep and accumulate took {}ms",
now.elapsed().as_millis()
);
(
final_res,
final_r,
challenges,
challenges_inv,
extra_challenge,
)
}
pub fn verify_kzg_v<E, R>(
v_srs: &VerifierSRS<E>,
final_vkey: &(E::G2Affine, E::G2Affine),
vkey_opening: &KZGOpening<E::G2Affine>,
challenges: &[E::Fr],
kzg_challenge: &E::Fr,
pairing_checks: &PairingChecks<E, R>,
) where
E: MultiMillerLoop,
R: rand_core::RngCore + Send,
{
let vpoly_eval_z = polynomial_evaluation_product_form_from_transcript(
challenges,
kzg_challenge,
&E::Fr::one(),
);
let ng = (-v_srs.g).to_affine();
par! {
let _check1 = kzg_check_v::<E, R>(
v_srs,
ng,
*kzg_challenge,
vpoly_eval_z,
final_vkey.0.to_curve(),
v_srs.g_alpha,
vkey_opening.0,
pairing_checks,
),
let _check2 = kzg_check_v::<E, R>(
v_srs,
ng,
*kzg_challenge,
vpoly_eval_z,
final_vkey.1.to_curve(),
v_srs.g_beta,
vkey_opening.1,
pairing_checks,
)
};
}
#[allow(clippy::too_many_arguments)]
fn kzg_check_v<E, R>(
v_srs: &VerifierSRS<E>,
ng: E::G1Affine,
x: E::Fr,
y: E::Fr,
cf: E::G2,
vk: E::G1,
pi: E::G2Affine,
pairing_checks: &PairingChecks<E, R>,
) where
E: MultiMillerLoop,
R: rand_core::RngCore + Send,
{
let b = (cf - (v_srs.h * y)).to_affine();
let c = (vk - (v_srs.g * x)).to_affine();
pairing_checks.merge_miller_inputs(&[(&ng, &b), (&c, &pi)], &<E as Engine>::Gt::generator());
}
pub fn verify_kzg_w<E, R>(
v_srs: &VerifierSRS<E>,
final_wkey: &(E::G1Affine, E::G1Affine),
wkey_opening: &KZGOpening<E::G1Affine>,
challenges: &[E::Fr],
r_shift: &E::Fr,
kzg_challenge: &E::Fr,
pairing_checks: &PairingChecks<E, R>,
) where
E: MultiMillerLoop,
R: rand_core::RngCore + Send,
{
par! {
let fz = polynomial_evaluation_product_form_from_transcript(challenges, kzg_challenge, r_shift),
let zn = kzg_challenge.pow_vartime(&[v_srs.n as u64])
};
let mut fwz = fz;
fwz.mul_assign(&zn);
let nh = -v_srs.h;
let nh = nh.to_affine();
par! {
let _check1 = kzg_check_w::<E, R>(
v_srs,
nh,
*kzg_challenge,
fwz,
final_wkey.0.to_curve(),
v_srs.h_alpha,
wkey_opening.0,
pairing_checks,
),
let _check2 = kzg_check_w::<E, R>(
v_srs,
nh,
*kzg_challenge,
fwz,
final_wkey.1.to_curve(),
v_srs.h_beta,
wkey_opening.1,
pairing_checks,
)
};
}
#[allow(clippy::too_many_arguments)]
fn kzg_check_w<E, R>(
v_srs: &VerifierSRS<E>,
nh: E::G2Affine,
x: E::Fr,
y: E::Fr,
cf: E::G1,
wk: E::G2,
pi: E::G1Affine,
pairing_checks: &PairingChecks<E, R>,
) where
E: MultiMillerLoop,
R: rand_core::RngCore + Send,
{
let a = (cf - (v_srs.g * y)).to_affine();
let d = (wk - (v_srs.h * x)).to_affine();
pairing_checks.merge_miller_inputs(&[(&a, &nh), (&pi, &d)], &<E as Engine>::Gt::generator());
}
#[allow(clippy::upper_case_acronyms)]
struct GipaTUZ<E>
where
E: MultiMillerLoop,
{
pub tab: <E as Engine>::Gt,
pub uab: <E as Engine>::Gt,
pub zab: <E as Engine>::Gt,
pub tc: <E as Engine>::Gt,
pub uc: <E as Engine>::Gt,
pub zc: E::G1,
}
impl<E> Default for GipaTUZ<E>
where
E: MultiMillerLoop,
{
fn default() -> Self {
Self {
tab: <E as Engine>::Gt::identity(),
uab: <E as Engine>::Gt::identity(),
zab: <E as Engine>::Gt::identity(),
tc: <E as Engine>::Gt::identity(),
uc: <E as Engine>::Gt::identity(),
zc: E::G1::identity(),
}
}
}
impl<E> GipaTUZ<E>
where
E: MultiMillerLoop,
{
fn merge(&mut self, other: &Self) {
self.tab += &other.tab;
self.uab += &other.uab;
self.zab += &other.zab;
self.tc += &other.tc;
self.uc += &other.uc;
self.zc += &other.zc;
}
}