monero_bulletproofs/original/
mod.rs
1use std_shims::{sync::LazyLock, vec::Vec};
2
3use rand_core::{RngCore, CryptoRng};
4
5use zeroize::Zeroize;
6
7use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, Scalar, EdwardsPoint};
8use curve25519_dalek::edwards::CompressedEdwardsY;
9use monero_generators::{H as MONERO_H, Generators, MAX_COMMITMENTS, COMMITMENT_BITS};
10use monero_primitives::{Commitment, INV_EIGHT, keccak256_to_scalar};
11use crate::{core::multiexp, scalar_vector::ScalarVector, BulletproofsBatchVerifier};
12
13pub(crate) mod inner_product;
14use inner_product::*;
15pub(crate) use inner_product::IpProof;
16use monero_io::decompress_point;
17
18include!(concat!(env!("OUT_DIR"), "/generators.rs"));
19
20#[derive(Clone, Debug)]
21pub(crate) struct AggregateRangeStatement<'a> {
22 commitments: &'a [EdwardsPoint],
23}
24
25#[derive(Clone, Debug)]
26pub(crate) struct AggregateRangeWitness {
27 commitments: Vec<Commitment>,
28}
29
30#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
31pub struct AggregateRangeProof {
32 pub(crate) A: CompressedEdwardsY,
33 pub(crate) S: CompressedEdwardsY,
34 pub(crate) T1: CompressedEdwardsY,
35 pub(crate) T2: CompressedEdwardsY,
36 pub(crate) tau_x: Scalar,
37 pub(crate) mu: Scalar,
38 pub(crate) t_hat: Scalar,
39 pub(crate) ip: IpProof,
40}
41
42impl<'a> AggregateRangeStatement<'a> {
43 pub(crate) fn new(commitments: &'a [EdwardsPoint]) -> Option<Self> {
44 if commitments.is_empty() || (commitments.len() > MAX_COMMITMENTS) {
45 None?;
46 }
47 Some(Self { commitments })
48 }
49}
50
51impl AggregateRangeWitness {
52 pub(crate) fn new(commitments: Vec<Commitment>) -> Option<Self> {
53 if commitments.is_empty() || (commitments.len() > MAX_COMMITMENTS) {
54 None?;
55 }
56 Some(Self { commitments })
57 }
58}
59
60impl<'a> AggregateRangeStatement<'a> {
61 fn initial_transcript(&self) -> (Scalar, Vec<EdwardsPoint>) {
62 let V = self.commitments.iter().map(|c| c * INV_EIGHT()).collect::<Vec<_>>();
63 (keccak256_to_scalar(V.iter().flat_map(|V| V.compress().to_bytes()).collect::<Vec<_>>()), V)
64 }
65
66 fn transcript_A_S(
67 transcript: Scalar,
68 A: CompressedEdwardsY,
69 S: CompressedEdwardsY,
70 ) -> (Scalar, Scalar) {
71 let mut buf = Vec::with_capacity(96);
72 buf.extend(transcript.to_bytes());
73 buf.extend_from_slice(A.as_bytes());
74 buf.extend_from_slice(S.as_bytes());
75 let y = keccak256_to_scalar(buf);
76 let z = keccak256_to_scalar(y.to_bytes());
77 (y, z)
78 }
79
80 fn transcript_T12(transcript: Scalar, T1: CompressedEdwardsY, T2: CompressedEdwardsY) -> Scalar {
81 let mut buf = Vec::with_capacity(128);
82 buf.extend_from_slice(transcript.as_bytes());
83 buf.extend_from_slice(transcript.as_bytes());
84 buf.extend_from_slice(T1.as_bytes());
85 buf.extend_from_slice(T2.as_bytes());
86 keccak256_to_scalar(buf)
87 }
88
89 fn transcript_tau_x_mu_t_hat(
90 transcript: Scalar,
91 tau_x: Scalar,
92 mu: Scalar,
93 t_hat: Scalar,
94 ) -> Scalar {
95 let mut buf = Vec::with_capacity(128);
96 buf.extend(transcript.to_bytes());
97 buf.extend(transcript.to_bytes());
98 buf.extend(tau_x.to_bytes());
99 buf.extend(mu.to_bytes());
100 buf.extend(t_hat.to_bytes());
101 keccak256_to_scalar(buf)
102 }
103
104 #[allow(clippy::needless_pass_by_value)]
105 pub(crate) fn prove(
106 self,
107 rng: &mut (impl RngCore + CryptoRng),
108 witness: AggregateRangeWitness,
109 ) -> Option<AggregateRangeProof> {
110 if self.commitments != witness.commitments.iter().map(Commitment::calculate).collect::<Vec<_>>()
111 {
112 None?
113 };
114
115 let generators = &GENERATORS;
116
117 let (mut transcript, _) = self.initial_transcript();
118
119 let mut padded_pow_of_2 = 1;
121 while padded_pow_of_2 < witness.commitments.len() {
122 padded_pow_of_2 <<= 1;
123 }
124
125 let mut aL = ScalarVector::new(padded_pow_of_2 * COMMITMENT_BITS);
126 for (i, commitment) in witness.commitments.iter().enumerate() {
127 let mut amount = commitment.amount;
128 for j in 0 .. COMMITMENT_BITS {
129 aL[(i * COMMITMENT_BITS) + j] = Scalar::from(amount & 1);
130 amount >>= 1;
131 }
132 }
133 let aR = aL.clone() - Scalar::ONE;
134
135 let alpha = Scalar::random(&mut *rng);
136
137 let A = {
138 let mut terms = Vec::with_capacity(1 + (2 * aL.len()));
139 terms.push((alpha, ED25519_BASEPOINT_POINT));
140 for (aL, G) in aL.0.iter().zip(&generators.G) {
141 terms.push((*aL, *G));
142 }
143 for (aR, H) in aR.0.iter().zip(&generators.H) {
144 terms.push((*aR, *H));
145 }
146 let res = multiexp(&terms) * INV_EIGHT();
147 terms.zeroize();
148 res
149 }
150 .compress();
151
152 let mut sL = ScalarVector::new(padded_pow_of_2 * COMMITMENT_BITS);
153 let mut sR = ScalarVector::new(padded_pow_of_2 * COMMITMENT_BITS);
154 for i in 0 .. (padded_pow_of_2 * COMMITMENT_BITS) {
155 sL[i] = Scalar::random(&mut *rng);
156 sR[i] = Scalar::random(&mut *rng);
157 }
158 let rho = Scalar::random(&mut *rng);
159
160 let S = {
161 let mut terms = Vec::with_capacity(1 + (2 * sL.len()));
162 terms.push((rho, ED25519_BASEPOINT_POINT));
163 for (sL, G) in sL.0.iter().zip(&generators.G) {
164 terms.push((*sL, *G));
165 }
166 for (sR, H) in sR.0.iter().zip(&generators.H) {
167 terms.push((*sR, *H));
168 }
169 let res = multiexp(&terms) * INV_EIGHT();
170 terms.zeroize();
171 res
172 }
173 .compress();
174
175 let (y, z) = Self::transcript_A_S(transcript, A, S);
176 transcript = z;
177 let z = ScalarVector::powers(z, 3 + padded_pow_of_2);
178
179 let twos = ScalarVector::powers(Scalar::from(2u8), COMMITMENT_BITS);
180
181 let l = [aL - z[1], sL];
182 let y_pow_n = ScalarVector::powers(y, aR.len());
183 let mut r = [((aR + z[1]) * &y_pow_n), sR * &y_pow_n];
184 {
185 for j in 0 .. padded_pow_of_2 {
186 for i in 0 .. COMMITMENT_BITS {
187 r[0].0[(j * COMMITMENT_BITS) + i] += z[2 + j] * twos[i];
188 }
189 }
190 }
191 let t1 = (l[0].clone().inner_product(&r[1])) + (r[0].clone().inner_product(&l[1]));
192 let t2 = l[1].clone().inner_product(&r[1]);
193
194 let tau_1 = Scalar::random(&mut *rng);
195 let T1 = {
196 let mut T1_terms = [(t1, *MONERO_H), (tau_1, ED25519_BASEPOINT_POINT)];
197 for term in &mut T1_terms {
198 term.0 *= INV_EIGHT();
199 }
200 let T1 = multiexp(&T1_terms);
201 T1_terms.zeroize();
202 T1
203 }
204 .compress();
205 let tau_2 = Scalar::random(&mut *rng);
206 let T2 = {
207 let mut T2_terms = [(t2, *MONERO_H), (tau_2, ED25519_BASEPOINT_POINT)];
208 for term in &mut T2_terms {
209 term.0 *= INV_EIGHT();
210 }
211 let T2 = multiexp(&T2_terms);
212 T2_terms.zeroize();
213 T2
214 }
215 .compress();
216
217 transcript = Self::transcript_T12(transcript, T1, T2);
218 let x = transcript;
219
220 let [l0, l1] = l;
221 let l = l0 + &(l1 * x);
222 let [r0, r1] = r;
223 let r = r0 + &(r1 * x);
224 let t_hat = l.clone().inner_product(&r);
225 let mut tau_x = ((tau_2 * x) + tau_1) * x;
226 {
227 for (i, commitment) in witness.commitments.iter().enumerate() {
228 tau_x += z[2 + i] * commitment.mask;
229 }
230 }
231 let mu = alpha + (rho * x);
232
233 let y_inv_pow_n = ScalarVector::powers(y.invert(), l.len());
234
235 transcript = Self::transcript_tau_x_mu_t_hat(transcript, tau_x, mu, t_hat);
236 let x_ip = transcript;
237
238 let ip = IpStatement::new_without_P_transcript(y_inv_pow_n, x_ip)
239 .prove(transcript, IpWitness::new(l, r).unwrap())
240 .unwrap();
241
242 let res = AggregateRangeProof { A, S, T1, T2, tau_x, mu, t_hat, ip };
243 #[cfg(debug_assertions)]
244 {
245 let mut verifier = BulletproofsBatchVerifier::default();
246 debug_assert!(self.verify(rng, &mut verifier, res.clone()));
247 debug_assert!(verifier.verify());
248 }
249 Some(res)
250 }
251
252 #[must_use]
253 pub(crate) fn verify(
254 self,
255 rng: &mut (impl RngCore + CryptoRng),
256 verifier: &mut BulletproofsBatchVerifier,
257 proof: AggregateRangeProof,
258 ) -> bool {
259 let mut padded_pow_of_2 = 1;
260 while padded_pow_of_2 < self.commitments.len() {
261 padded_pow_of_2 <<= 1;
262 }
263 let ip_rows = padded_pow_of_2 * COMMITMENT_BITS;
264
265 while verifier.0.g_bold.len() < ip_rows {
266 verifier.0.g_bold.push(Scalar::ZERO);
267 verifier.0.h_bold.push(Scalar::ZERO);
268 }
269
270 let (mut transcript, mut commitments) = self.initial_transcript();
271 for commitment in &mut commitments {
272 *commitment = commitment.mul_by_cofactor();
273 }
274
275 let (y, z) = Self::transcript_A_S(transcript, proof.A, proof.S);
276 transcript = z;
277 let z = ScalarVector::powers(z, 3 + padded_pow_of_2);
278 transcript = Self::transcript_T12(transcript, proof.T1, proof.T2);
279 let x = transcript;
280 transcript = Self::transcript_tau_x_mu_t_hat(transcript, proof.tau_x, proof.mu, proof.t_hat);
281 let x_ip = transcript;
282
283 let Some(A) = decompress_point(proof.A).map(|p| EdwardsPoint::mul_by_cofactor(&p)) else {
284 return false;
285 };
286 let Some(S) = decompress_point(proof.S).map(|p| EdwardsPoint::mul_by_cofactor(&p)) else {
287 return false;
288 };
289 let Some(T1) = decompress_point(proof.T1).map(|p| EdwardsPoint::mul_by_cofactor(&p)) else {
290 return false;
291 };
292 let Some(T2) = decompress_point(proof.T2).map(|p| EdwardsPoint::mul_by_cofactor(&p)) else {
293 return false;
294 };
295
296 let y_pow_n = ScalarVector::powers(y, ip_rows);
297 let y_inv_pow_n = ScalarVector::powers(y.invert(), ip_rows);
298
299 let twos = ScalarVector::powers(Scalar::from(2u8), COMMITMENT_BITS);
300
301 {
303 let weight = Scalar::random(&mut *rng);
304 verifier.0.h += weight * proof.t_hat;
305 verifier.0.g += weight * proof.tau_x;
306
307 let weight = -weight;
310
311 verifier.0.h += weight * (z[1] - (z[2])) * y_pow_n.sum();
312
313 for (i, commitment) in commitments.iter().enumerate() {
314 verifier.0.other.push((weight * z[2 + i], *commitment));
315 }
316
317 for i in 0 .. padded_pow_of_2 {
318 verifier.0.h -= weight * z[3 + i] * twos.clone().sum();
319 }
320 verifier.0.other.push((weight * x, T1));
321 verifier.0.other.push((weight * (x * x), T2));
322 }
323
324 let ip_weight = Scalar::random(&mut *rng);
325
326 verifier.0.other.push((ip_weight, A));
328 verifier.0.other.push((ip_weight * x, S));
329 let ip_z = ip_weight * z[1];
333 for i in 0 .. ip_rows {
334 verifier.0.h_bold[i] += ip_z;
335 }
336 let neg_ip_z = -ip_z;
337 for i in 0 .. ip_rows {
338 verifier.0.g_bold[i] += neg_ip_z;
339 }
340 {
341 for j in 0 .. padded_pow_of_2 {
342 for i in 0 .. COMMITMENT_BITS {
343 let full_i = (j * COMMITMENT_BITS) + i;
344 verifier.0.h_bold[full_i] += ip_weight * y_inv_pow_n[full_i] * z[2 + j] * twos[i];
345 }
346 }
347 }
348 verifier.0.h += ip_weight * x_ip * proof.t_hat;
349
350 verifier.0.g += ip_weight * -proof.mu;
352 let res = IpStatement::new_without_P_transcript(y_inv_pow_n, x_ip)
353 .verify(verifier, ip_rows, transcript, ip_weight, proof.ip);
354 res.is_ok()
355 }
356}