monero_bulletproofs/plus/
aggregate_range_proof.rs
1use std_shims::{vec, vec::Vec};
2
3use rand_core::{RngCore, CryptoRng};
4use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
5
6use curve25519_dalek::{traits::Identity, scalar::Scalar, edwards::EdwardsPoint};
7use curve25519_dalek::edwards::CompressedEdwardsY;
8use monero_io::decompress_point;
9use monero_primitives::{INV_EIGHT, Commitment, keccak256_to_scalar};
10
11use crate::{
12 batch_verifier::BulletproofsPlusBatchVerifier,
13 core::{MAX_COMMITMENTS, COMMITMENT_BITS, multiexp, multiexp_vartime},
14 plus::{
15 ScalarVector, PointVector, GeneratorsList, BpPlusGenerators,
16 transcript::*,
17 weighted_inner_product::{WipStatement, WipWitness, WipProof},
18 padded_pow_of_2, u64_decompose,
19 },
20};
21
22#[derive(Clone, Debug)]
24pub(crate) struct AggregateRangeStatement<'a> {
25 generators: BpPlusGenerators,
26 V: &'a [EdwardsPoint],
27}
28
29#[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)]
30pub(crate) struct AggregateRangeWitness(Vec<Commitment>);
31
32impl AggregateRangeWitness {
33 pub(crate) fn new(commitments: Vec<Commitment>) -> Option<Self> {
34 if commitments.is_empty() || (commitments.len() > MAX_COMMITMENTS) {
35 return None;
36 }
37
38 Some(AggregateRangeWitness(commitments))
39 }
40}
41
42#[doc(hidden)]
44#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
45pub struct AggregateRangeProof {
46 pub(crate) A: CompressedEdwardsY,
47 pub(crate) wip: WipProof,
48}
49
50struct AHatComputation {
51 y: Scalar,
52 d_descending_y_plus_z: ScalarVector,
53 y_mn_plus_one: Scalar,
54 z: Scalar,
55 z_pow: ScalarVector,
56 A_hat: EdwardsPoint,
57}
58
59impl<'a> AggregateRangeStatement<'a> {
60 pub(crate) fn new(V: &'a [EdwardsPoint]) -> Option<Self> {
61 if V.is_empty() || (V.len() > MAX_COMMITMENTS) {
62 return None;
63 }
64
65 Some(Self { generators: BpPlusGenerators::new(), V })
66 }
67
68 fn transcript_A(transcript: &mut Scalar, A: EdwardsPoint) -> (Scalar, Scalar) {
69 let y = keccak256_to_scalar(
70 [transcript.to_bytes().as_ref(), A.compress().to_bytes().as_ref()].concat(),
71 );
72 let z = keccak256_to_scalar(y.to_bytes().as_ref());
73 *transcript = z;
74 (y, z)
75 }
76
77 fn d_j(j: usize, m: usize) -> ScalarVector {
78 let mut d_j = Vec::with_capacity(m * COMMITMENT_BITS);
79 for _ in 0 .. (j - 1) * COMMITMENT_BITS {
80 d_j.push(Scalar::ZERO);
81 }
82 d_j.append(&mut ScalarVector::powers(Scalar::from(2u8), COMMITMENT_BITS).0);
83 for _ in 0 .. (m - j) * COMMITMENT_BITS {
84 d_j.push(Scalar::ZERO);
85 }
86 ScalarVector(d_j)
87 }
88
89 fn compute_A_hat(
90 mut V: PointVector,
91 generators: &BpPlusGenerators,
92 transcript: &mut Scalar,
93 mut A: EdwardsPoint,
94 ) -> AHatComputation {
95 let (y, z) = Self::transcript_A(transcript, A);
96 A = A.mul_by_cofactor();
97
98 while V.len() < padded_pow_of_2(V.len()) {
99 V.0.push(EdwardsPoint::identity());
100 }
101 let mn = V.len() * COMMITMENT_BITS;
102
103 let mut z_pow = Vec::with_capacity(V.len());
105 z_pow.push(z * z);
107
108 let mut d = ScalarVector::new(mn);
109 for j in 1 ..= V.len() {
110 z_pow.push(*z_pow.last().unwrap() * z_pow[0]);
111 d = d + &(Self::d_j(j, V.len()) * (z_pow[j - 1]));
112 }
113
114 let mut ascending_y = ScalarVector(vec![y]);
115 for i in 1 .. d.len() {
116 ascending_y.0.push(ascending_y[i - 1] * y);
117 }
118 let y_pows = ascending_y.clone().sum();
119
120 let mut descending_y = ascending_y.clone();
121 descending_y.0.reverse();
122
123 let d_descending_y = d.clone() * &descending_y;
124 let d_descending_y_plus_z = d_descending_y + z;
125
126 let y_mn_plus_one = descending_y[0] * y;
127
128 let mut commitment_accum = EdwardsPoint::identity();
129 for (j, commitment) in V.0.iter().enumerate() {
130 commitment_accum += *commitment * z_pow[j];
131 }
132
133 let neg_z = -z;
134 let mut A_terms = Vec::with_capacity((generators.len() * 2) + 2);
135 for (i, d_y_z) in d_descending_y_plus_z.0.iter().enumerate() {
136 A_terms.push((neg_z, generators.generator(GeneratorsList::GBold, i)));
137 A_terms.push((*d_y_z, generators.generator(GeneratorsList::HBold, i)));
138 }
139 A_terms.push((y_mn_plus_one, commitment_accum));
140 A_terms.push((
141 ((y_pows * z) - (d.sum() * y_mn_plus_one * z) - (y_pows * (z * z))),
142 BpPlusGenerators::g(),
143 ));
144
145 AHatComputation {
146 y,
147 d_descending_y_plus_z,
148 y_mn_plus_one,
149 z,
150 z_pow: ScalarVector(z_pow),
151 A_hat: A + multiexp_vartime(&A_terms),
152 }
153 }
154
155 pub(crate) fn prove<R: RngCore + CryptoRng>(
156 self,
157 rng: &mut R,
158 witness: &AggregateRangeWitness,
159 ) -> Option<AggregateRangeProof> {
160 if self.V.len() != witness.0.len() {
162 return None;
163 }
164 for (commitment, witness) in self.V.iter().zip(witness.0.iter()) {
165 if witness.calculate() != *commitment {
166 return None;
167 }
168 }
169
170 let Self { generators, V } = self;
171 let V = V.iter().map(|V| V * INV_EIGHT()).collect::<Vec<_>>();
179 let mut transcript = initial_transcript(V.iter());
180 let mut V = V.iter().map(EdwardsPoint::mul_by_cofactor).collect::<Vec<_>>();
181
182 while V.len() < padded_pow_of_2(V.len()) {
184 V.push(EdwardsPoint::identity());
185 }
186
187 let generators = generators.reduce(V.len() * COMMITMENT_BITS);
188
189 let mut d_js = Vec::with_capacity(V.len());
190 let mut a_l = ScalarVector(Vec::with_capacity(V.len() * COMMITMENT_BITS));
191 for j in 1 ..= V.len() {
192 d_js.push(Self::d_j(j, V.len()));
193 #[allow(clippy::map_unwrap_or)]
194 a_l.0.append(
195 &mut u64_decompose(
196 *witness.0.get(j - 1).map(|commitment| &commitment.amount).unwrap_or(&0),
197 )
198 .0,
199 );
200 }
201
202 let a_r = a_l.clone() - Scalar::ONE;
203
204 let alpha = Scalar::random(&mut *rng);
205
206 let mut A_terms = Vec::with_capacity((generators.len() * 2) + 1);
207 for (i, a_l) in a_l.0.iter().enumerate() {
208 A_terms.push((*a_l, generators.generator(GeneratorsList::GBold, i)));
209 }
210 for (i, a_r) in a_r.0.iter().enumerate() {
211 A_terms.push((*a_r, generators.generator(GeneratorsList::HBold, i)));
212 }
213 A_terms.push((alpha, BpPlusGenerators::h()));
214 let mut A = multiexp(&A_terms);
215 A_terms.zeroize();
216
217 A *= INV_EIGHT();
219
220 let AHatComputation { y, d_descending_y_plus_z, y_mn_plus_one, z, z_pow, A_hat } =
221 Self::compute_A_hat(PointVector(V), &generators, &mut transcript, A);
222
223 let a_l = a_l - z;
224 let a_r = a_r + &d_descending_y_plus_z;
225 let mut alpha = alpha;
226 for j in 1 ..= witness.0.len() {
227 alpha += z_pow[j - 1] * witness.0[j - 1].mask * y_mn_plus_one;
228 }
229
230 Some(AggregateRangeProof {
231 A: A.compress(),
232 wip: WipStatement::new(generators, A_hat, y)
233 .prove(rng, transcript, &Zeroizing::new(WipWitness::new(a_l, a_r, alpha).unwrap()))
234 .unwrap(),
235 })
236 }
237
238 pub(crate) fn verify<R: RngCore + CryptoRng>(
239 self,
240 rng: &mut R,
241 verifier: &mut BulletproofsPlusBatchVerifier,
242 proof: AggregateRangeProof,
243 ) -> bool {
244 let Self { generators, V } = self;
245
246 let V = V.iter().map(|V| V * INV_EIGHT()).collect::<Vec<_>>();
247 let mut transcript = initial_transcript(V.iter());
248 let V = V.iter().map(EdwardsPoint::mul_by_cofactor).collect::<Vec<_>>();
249
250 let generators = generators.reduce(V.len() * COMMITMENT_BITS);
251
252 let Some(A) = decompress_point(proof.A) else { return false };
253
254 let AHatComputation { y, A_hat, .. } =
255 Self::compute_A_hat(PointVector(V), &generators, &mut transcript, A);
256 WipStatement::new(generators, A_hat, y).verify(rng, verifier, transcript, proof.wip)
257 }
258}