From 9221dbf0489f9c98e24264eaa407efbd2768b74d Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 1 Aug 2022 23:30:24 -0400 Subject: [PATCH] Bulletproofs+ Verification --- coins/monero/src/ringct/bulletproofs/core.rs | 21 +++ .../src/ringct/bulletproofs/original.rs | 16 +- coins/monero/src/ringct/bulletproofs/plus.rs | 146 +++++++++++++++--- coins/monero/src/tests/bulletproofs.rs | 49 +++--- 4 files changed, 178 insertions(+), 54 deletions(-) diff --git a/coins/monero/src/ringct/bulletproofs/core.rs b/coins/monero/src/ringct/bulletproofs/core.rs index b2d5691f..9a6086b9 100644 --- a/coins/monero/src/ringct/bulletproofs/core.rs +++ b/coins/monero/src/ringct/bulletproofs/core.rs @@ -151,3 +151,24 @@ pub(crate) fn LR_statements( lazy_static! { pub(crate) static ref TWO_N: ScalarVector = ScalarVector::powers(Scalar::from(2u8), N); } + +pub(crate) fn challenge_products(w: &[Scalar], winv: &[Scalar]) -> Vec { + let mut products = vec![Scalar::zero(); 1 << w.len()]; + products[0] = winv[0]; + products[1] = w[0]; + for j in 1 .. w.len() { + let mut slots = (1 << (j + 1)) - 1; + while slots > 0 { + products[slots] = products[slots / 2] * w[j]; + products[slots - 1] = products[slots / 2] * winv[j]; + slots = slots.saturating_sub(2); + } + } + + // Sanity check as if the above failed to populate, it'd be critical + for w in &products { + debug_assert!(!bool::from(w.is_zero())); + } + + products +} diff --git a/coins/monero/src/ringct/bulletproofs/original.rs b/coins/monero/src/ringct/bulletproofs/original.rs index b94e1c32..d84eef46 100644 --- a/coins/monero/src/ringct/bulletproofs/original.rs +++ b/coins/monero/src/ringct/bulletproofs/original.rs @@ -241,21 +241,7 @@ impl OriginalStruct { let yinv = y.invert().unwrap(); let yinvpow = ScalarVector::powers(yinv, MN); - let mut w_cache = vec![Scalar::zero(); MN]; - w_cache[0] = winv[0]; - w_cache[1] = w[0]; - for j in 1 .. logMN { - let mut slots = (1 << (j + 1)) - 1; - while slots > 0 { - w_cache[slots] = w_cache[slots / 2] * w[j]; - w_cache[slots - 1] = w_cache[slots / 2] * winv[j]; - slots = slots.saturating_sub(2); - } - } - - for w in &w_cache { - debug_assert!(!bool::from(w.is_zero())); - } + let w_cache = challenge_products(&w, &winv); for i in 0 .. MN { let g = (Scalar(self.a) * w_cache[i]) + z; diff --git a/coins/monero/src/ringct/bulletproofs/plus.rs b/coins/monero/src/ringct/bulletproofs/plus.rs index e3b031c2..4c869999 100644 --- a/coins/monero/src/ringct/bulletproofs/plus.rs +++ b/coins/monero/src/ringct/bulletproofs/plus.rs @@ -20,8 +20,21 @@ lazy_static! { } // TRANSCRIPT isn't a Scalar, so we need this alternative for the first hash -fn hash_plus(mash: &[u8]) -> Scalar { - hash_to_scalar(&[&*TRANSCRIPT as &[u8], mash].concat()) +fn hash_plus>(commitments: C) -> (Scalar, Vec) { + let (cache, commitments) = hash_commitments(commitments); + (hash_to_scalar(&[&*TRANSCRIPT as &[u8], &cache.to_bytes()].concat()), commitments) +} + +// d[j*N+i] = z**(2*(j+1)) * 2**i +fn d(z: Scalar, M: usize, MN: usize) -> (ScalarVector, ScalarVector) { + let zpow = ScalarVector::even_powers(z, 2 * M); + let mut d = vec![Scalar::zero(); MN]; + for j in 0 .. M { + for i in 0 .. N { + d[(j * N) + i] = zpow[j] * TWO_N[i]; + } + } + (zpow, ScalarVector(d)) } #[derive(Clone, PartialEq, Eq, Debug)] @@ -44,29 +57,21 @@ impl PlusStruct { let (logMN, M, MN) = MN(commitments.len()); let (aL, aR) = bit_decompose(commitments); - let (mut cache, _) = hash_commitments(commitments.iter().map(Commitment::calculate)); - cache = hash_plus(&cache.to_bytes()); + let (mut cache, _) = hash_plus(commitments.iter().map(Commitment::calculate)); let (mut alpha1, A) = alpha_rho(&mut *rng, &GENERATORS, &aL, &aR); let y = hash_cache(&mut cache, &[A.compress().to_bytes()]); let mut cache = hash_to_scalar(&y.to_bytes()); let z = cache; - let zpow = ScalarVector::even_powers(z, 2 * M); - // d[j*N+i] = z**(2*(j+1)) * 2**i - let mut d = vec![Scalar::zero(); MN]; - for j in 0 .. M { - for i in 0 .. N { - d[(j * N) + i] = zpow[j] * TWO_N[i]; - } - } + let (zpow, d) = d(z, M, MN); let aL1 = aL - z; let ypow = ScalarVector::powers(y, MN + 2); let mut y_for_d = ScalarVector(ypow.0[1 ..= MN].to_vec()); y_for_d.0.reverse(); - let aR1 = (aR + z) + (y_for_d * ScalarVector(d)); + let aR1 = (aR + z) + (y_for_d * d); for (j, gamma) in commitments.iter().map(|c| Scalar(c.mask)).enumerate() { alpha1 += zpow[j] * ypow[MN + 1] * gamma; @@ -151,12 +156,117 @@ impl PlusStruct { #[must_use] fn verify_core( &self, - _rng: &mut R, - _verifier: &mut BatchVerifier, - _id: ID, - _commitments: &[DalekPoint], + rng: &mut R, + verifier: &mut BatchVerifier, + id: ID, + commitments: &[DalekPoint], ) -> bool { - unimplemented!("Bulletproofs+ verification isn't implemented") + // Verify commitments are valid + if commitments.is_empty() || (commitments.len() > MAX_M) { + return false; + } + + // Verify L and R are properly sized + if self.L.len() != self.R.len() { + return false; + } + + let (logMN, M, MN) = MN(commitments.len()); + if self.L.len() != logMN { + return false; + } + + // Rebuild all challenges + let (mut cache, commitments) = hash_plus(commitments.iter().cloned()); + let y = hash_cache(&mut cache, &[self.A.compress().to_bytes()]); + let yinv = y.invert().unwrap(); + let z = hash_to_scalar(&y.to_bytes()); + cache = z; + + let mut w = Vec::with_capacity(logMN); + let mut winv = Vec::with_capacity(logMN); + for (L, R) in self.L.iter().zip(&self.R) { + w.push(hash_cache(&mut cache, &[L.compress().to_bytes(), R.compress().to_bytes()])); + winv.push(cache.invert().unwrap()); + } + + let e = hash_cache(&mut cache, &[self.A1.compress().to_bytes(), self.B.compress().to_bytes()]); + + // Convert the proof from * INV_EIGHT to its actual form + let normalize = |point: &DalekPoint| EdwardsPoint(point.mul_by_cofactor()); + + let L = self.L.iter().map(normalize).collect::>(); + let R = self.R.iter().map(normalize).collect::>(); + let A = normalize(&self.A); + let A1 = normalize(&self.A1); + let B = normalize(&self.B); + + let mut commitments = commitments.iter().map(|c| c.mul_by_cofactor()).collect::>(); + + // Verify it + let mut proof = Vec::with_capacity(logMN + 5 + (2 * (MN + logMN))); + + let mut yMN = y; + for _ in 0 .. logMN { + yMN *= yMN; + } + let yMNy = yMN * y; + + let (zpow, d) = d(z, M, MN); + let zsq = zpow[0]; + assert_eq!(zsq, z * z); + + let esq = e * e; + let minus_esq = -esq; + let commitment_weight = minus_esq * yMNy; + for (i, commitment) in commitments.drain(..).enumerate() { + proof.push((commitment_weight * zpow[i], commitment)); + } + + proof.push((Scalar::one(), -B)); + proof.push((-e, A1)); + proof.push((minus_esq, A)); + proof.push((Scalar(self.d1), G)); + + let mut twoSixtyFourMinusOne = Scalar::from(2u8); + for _ in 0 .. 6 { + twoSixtyFourMinusOne *= twoSixtyFourMinusOne; + } + twoSixtyFourMinusOne -= Scalar::one(); + let d_sum = zpow.sum() * twoSixtyFourMinusOne; + + let y_sum = ScalarVector(ScalarVector::powers(y, MN + 1).0[1 ..].to_vec()).sum(); + proof.push(( + Scalar(self.r1 * y.0 * self.s1) + (esq * ((yMNy * z * d_sum) + ((zsq - z) * y_sum))), + *H, + )); + + let w_cache = challenge_products(&w, &winv); + + let mut e_r1_y = e * Scalar(self.r1); + let e_s1 = e * Scalar(self.s1); + let esq_z = esq * z; + let minus_esq_z = -esq_z; + let mut minus_esq_y = minus_esq * yMN; + + for i in 0 .. MN { + proof.push((e_r1_y * w_cache[i] + esq_z, GENERATORS.G[i])); + proof.push(( + (e_s1 * w_cache[(!i) & (MN - 1)]) + minus_esq_z + (minus_esq_y * d[i]), + GENERATORS.H[i], + )); + + e_r1_y *= yinv; + minus_esq_y *= yinv; + } + + for i in 0 .. logMN { + proof.push((minus_esq * w[i] * w[i], L[i])); + proof.push((minus_esq * winv[i] * winv[i], R[i])); + } + + verifier.queue(rng, id, proof); + true } #[must_use] diff --git a/coins/monero/src/tests/bulletproofs.rs b/coins/monero/src/tests/bulletproofs.rs index 1cfd6115..4ef3f5d2 100644 --- a/coins/monero/src/tests/bulletproofs.rs +++ b/coins/monero/src/tests/bulletproofs.rs @@ -56,28 +56,35 @@ fn bulletproofs_vector() { )); } -#[test] -fn bulletproofs() { - // Create Bulletproofs for all possible output quantities - let mut verifier = BatchVerifier::new(0); - for i in 1 .. 17 { - let commitments = (1 ..= i) - .map(|i| Commitment::new(random_scalar(&mut OsRng), u64::try_from(i).unwrap())) - .collect::>(); +macro_rules! bulletproofs_tests { + ($name: ident, $max: ident, $plus: literal) => { + #[test] + fn $name() { + // Create Bulletproofs for all possible output quantities + let mut verifier = BatchVerifier::new(16); + for i in 1 .. 17 { + let commitments = (1 ..= i) + .map(|i| Commitment::new(random_scalar(&mut OsRng), u64::try_from(i).unwrap())) + .collect::>(); - let bp = Bulletproofs::prove(&mut OsRng, &commitments, false).unwrap(); + let bp = Bulletproofs::prove(&mut OsRng, &commitments, $plus).unwrap(); - let commitments = commitments.iter().map(Commitment::calculate).collect::>(); - assert!(bp.verify(&mut OsRng, &commitments)); - assert!(bp.batch_verify(&mut OsRng, &mut verifier, i, &commitments)); - } - assert!(verifier.verify_vartime()); + let commitments = commitments.iter().map(Commitment::calculate).collect::>(); + assert!(bp.verify(&mut OsRng, &commitments)); + assert!(bp.batch_verify(&mut OsRng, &mut verifier, i, &commitments)); + } + assert!(verifier.verify_vartime()); + } + + #[test] + fn $max() { + // Check Bulletproofs errors if we try to prove for too many outputs + assert!( + Bulletproofs::prove(&mut OsRng, &[Commitment::new(Scalar::zero(), 0); 17], $plus).is_err() + ); + } + }; } -#[test] -fn bulletproofs_max() { - // Check Bulletproofs errors if we try to prove for too many outputs - assert!( - Bulletproofs::prove(&mut OsRng, &[Commitment::new(Scalar::zero(), 0); 17], false).is_err() - ); -} +bulletproofs_tests!(bulletproofs, bulletproofs_max, false); +bulletproofs_tests!(bulletproofs_plus, bulletproofs_plus_max, true);