1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3#![cfg_attr(not(feature = "std"), no_std)]
4#![allow(non_snake_case)]
5
6use std_shims::{
7 prelude::*,
8 sync::LazyLock,
9 io::{self, Read, Write},
10};
11
12use rand_core::{RngCore, CryptoRng};
13use zeroize::Zeroizing;
14
15use curve25519_dalek::EdwardsPoint;
16
17use monero_io::*;
18use monero_ed25519::*;
19pub use monero_bulletproofs_generators::MAX_BULLETPROOF_COMMITMENTS as MAX_COMMITMENTS;
20use monero_bulletproofs_generators::COMMITMENT_BITS;
21
22pub(crate) mod scalar_vector;
23pub(crate) mod point_vector;
24
25pub(crate) mod core;
26
27pub(crate) mod batch_verifier;
28use batch_verifier::{BulletproofsBatchVerifier, BulletproofsPlusBatchVerifier};
29pub use batch_verifier::BatchVerifier;
30
31pub(crate) mod original;
32use crate::original::{
33 IpProof, AggregateRangeStatement as OriginalStatement, AggregateRangeWitness as OriginalWitness,
34 AggregateRangeProof as OriginalProof,
35};
36
37pub(crate) mod plus;
38use crate::plus::{
39 WipProof, AggregateRangeStatement as PlusStatement, AggregateRangeWitness as PlusWitness,
40 AggregateRangeProof as PlusProof,
41};
42
43#[cfg(test)]
44mod tests;
45
46#[allow(clippy::as_conversions)]
48const LOG_COMMITMENT_BITS: usize = COMMITMENT_BITS.ilog2() as usize;
49#[allow(clippy::as_conversions)]
51const MAX_LR: usize = (MAX_COMMITMENTS.ilog2() as usize) + LOG_COMMITMENT_BITS;
52
53static MONERO_H: LazyLock<EdwardsPoint> = LazyLock::new(|| {
55 CompressedPoint::H.decompress().expect("couldn't decompress `CompressedPoint::H`").into()
56});
57
58#[derive(Clone, Copy, PartialEq, Eq, Debug, thiserror::Error)]
60pub enum BulletproofError {
61 #[error("no commitments to prove the range for")]
63 NoCommitments,
64 #[error("too many commitments to prove the range for")]
66 TooManyCommitments,
67}
68
69#[allow(clippy::large_enum_variant)]
73#[derive(Clone, PartialEq, Eq, Debug)]
74pub enum Bulletproof {
75 Original(OriginalProof),
77 Plus(PlusProof),
79}
80
81impl Bulletproof {
82 fn bp_fields(plus: bool) -> usize {
83 if plus {
84 6
85 } else {
86 9
87 }
88 }
89
90 pub fn calculate_clawback(plus: bool, n_outputs: usize) -> (usize, usize) {
101 #[allow(non_snake_case)]
102 let mut LR_len = 0;
103 let mut n_padded_outputs = 1;
104 while n_padded_outputs < n_outputs.min(MAX_COMMITMENTS) {
105 LR_len += 1;
106 n_padded_outputs = 1 << LR_len;
107 }
108 LR_len += LOG_COMMITMENT_BITS;
109
110 let mut clawback = 0;
111 if n_padded_outputs > 2 {
112 let fields = Bulletproof::bp_fields(plus);
113 let base = ((fields + (2 * (LOG_COMMITMENT_BITS + 1))) * 32) / 2;
114 let size = (fields + (2 * LR_len)) * 32;
115 clawback = ((base * n_padded_outputs) - size) * 4 / 5;
116 }
117
118 (clawback, LR_len)
119 }
120
121 pub fn prove<R: RngCore + CryptoRng>(
125 rng: &mut R,
126 outputs: Vec<Commitment>,
127 ) -> Result<Bulletproof, BulletproofError> {
128 if outputs.is_empty() {
129 Err(BulletproofError::NoCommitments)?;
130 }
131 if outputs.len() > MAX_COMMITMENTS {
132 Err(BulletproofError::TooManyCommitments)?;
133 }
134 let commitments =
135 outputs.iter().map(|commitment| commitment.commit().into()).collect::<Vec<_>>();
136 Ok(Bulletproof::Original(
137 OriginalStatement::new(&commitments)
138 .expect("failed to create statement despite checking amount of commitments")
139 .prove(
140 rng,
141 OriginalWitness::new(outputs)
142 .expect("failed to create witness despite checking amount of commitments"),
143 )
144 .expect(
145 "failed to prove Bulletproof::Original despite ensuring statement/witness consistency",
146 ),
147 ))
148 }
149
150 pub fn prove_plus<R: RngCore + CryptoRng>(
154 rng: &mut R,
155 outputs: Vec<Commitment>,
156 ) -> Result<Bulletproof, BulletproofError> {
157 if outputs.is_empty() {
158 Err(BulletproofError::NoCommitments)?;
159 }
160 if outputs.len() > MAX_COMMITMENTS {
161 Err(BulletproofError::TooManyCommitments)?;
162 }
163 let commitments =
164 outputs.iter().map(|commitment| commitment.commit().into()).collect::<Vec<_>>();
165 Ok(Bulletproof::Plus(
166 PlusStatement::new(&commitments)
167 .expect("failed to create statement despite checking amount of commitments")
168 .prove(
169 rng,
170 &Zeroizing::new(
171 PlusWitness::new(outputs)
172 .expect("failed to create witness despite checking amount of commitments"),
173 ),
174 )
175 .expect("failed to prove Bulletproof::Plus despite ensuring statement/witness consistency"),
176 ))
177 }
178
179 #[must_use]
181 pub fn verify<R: RngCore + CryptoRng>(
182 &self,
183 rng: &mut R,
184 commitments: &[CompressedPoint],
185 ) -> bool {
186 let Some(commitments) = commitments
187 .iter()
188 .map(|point| point.decompress().map(Point::into))
189 .collect::<Option<Vec<_>>>()
190 else {
191 return false;
192 };
193
194 match self {
195 Bulletproof::Original(bp) => {
196 let mut verifier = BulletproofsBatchVerifier::default();
197 let Some(statement) = OriginalStatement::new(&commitments) else {
198 return false;
199 };
200 if !statement.verify(rng, &mut verifier, bp.clone()) {
201 return false;
202 }
203 verifier.verify()
204 }
205 Bulletproof::Plus(bp) => {
206 let mut verifier = BulletproofsPlusBatchVerifier::default();
207 let Some(statement) = PlusStatement::new(&commitments) else {
208 return false;
209 };
210 if !statement.verify(rng, &mut verifier, bp.clone()) {
211 return false;
212 }
213 verifier.verify()
214 }
215 }
216 }
217
218 #[must_use]
227 pub fn batch_verify<R: RngCore + CryptoRng>(
228 &self,
229 rng: &mut R,
230 verifier: &mut BatchVerifier,
231 commitments: &[CompressedPoint],
232 ) -> bool {
233 let Some(commitments) = commitments
234 .iter()
235 .map(|point| point.decompress().map(Point::into))
236 .collect::<Option<Vec<_>>>()
237 else {
238 return false;
239 };
240
241 match self {
242 Bulletproof::Original(bp) => {
243 let Some(statement) = OriginalStatement::new(&commitments) else {
244 return false;
245 };
246 statement.verify(rng, &mut verifier.original, bp.clone())
247 }
248 Bulletproof::Plus(bp) => {
249 let Some(statement) = PlusStatement::new(&commitments) else {
250 return false;
251 };
252 statement.verify(rng, &mut verifier.plus, bp.clone())
253 }
254 }
255 }
256
257 fn write_core<W: Write, F: Fn(&[CompressedPoint], &mut W) -> io::Result<()>>(
260 &self,
261 w: &mut W,
262 specific_write_vec: F,
263 ) -> io::Result<()> {
264 match self {
265 Bulletproof::Original(bp) => {
266 bp.A.write(w)?;
267 bp.S.write(w)?;
268 bp.T1.write(w)?;
269 bp.T2.write(w)?;
270 w.write_all(&bp.tau_x.to_bytes())?;
271 w.write_all(&bp.mu.to_bytes())?;
272 specific_write_vec(&bp.ip.L, w)?;
273 specific_write_vec(&bp.ip.R, w)?;
274 w.write_all(&bp.ip.a.to_bytes())?;
275 w.write_all(&bp.ip.b.to_bytes())?;
276 w.write_all(&bp.t_hat.to_bytes())
277 }
278
279 Bulletproof::Plus(bp) => {
280 bp.A.write(w)?;
281 bp.wip.A.write(w)?;
282 bp.wip.B.write(w)?;
283 w.write_all(&bp.wip.r_answer.to_bytes())?;
284 w.write_all(&bp.wip.s_answer.to_bytes())?;
285 w.write_all(&bp.wip.delta_answer.to_bytes())?;
286 specific_write_vec(&bp.wip.L, w)?;
287 specific_write_vec(&bp.wip.R, w)
288 }
289 }
290 }
291
292 pub fn signature_write<W: Write>(&self, w: &mut W) -> io::Result<()> {
296 self.write_core(w, |points, w| write_raw_vec(CompressedPoint::write, points, w))
297 }
298
299 pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
301 self.write_core(w, |points, w| write_vec(CompressedPoint::write, points, w))
302 }
303
304 pub fn serialize(&self) -> Vec<u8> {
306 let mut serialized = Vec::with_capacity(512);
307 self.write(&mut serialized).expect("write failed but <Vec as io::Write> doesn't fail");
308 serialized
309 }
310
311 pub fn read<R: Read>(r: &mut R) -> io::Result<Bulletproof> {
313 Ok(Bulletproof::Original(OriginalProof {
314 A: CompressedPoint::read(r)?,
315 S: CompressedPoint::read(r)?,
316 T1: CompressedPoint::read(r)?,
317 T2: CompressedPoint::read(r)?,
318 tau_x: Scalar::read(r)?.into(),
319 mu: Scalar::read(r)?.into(),
320 ip: IpProof {
321 L: read_vec(CompressedPoint::read, Some(MAX_LR), r)?,
322 R: read_vec(CompressedPoint::read, Some(MAX_LR), r)?,
323 a: Scalar::read(r)?.into(),
324 b: Scalar::read(r)?.into(),
325 },
326 t_hat: Scalar::read(r)?.into(),
327 }))
328 }
329
330 pub fn read_plus<R: Read>(r: &mut R) -> io::Result<Bulletproof> {
332 Ok(Bulletproof::Plus(PlusProof {
333 A: CompressedPoint::read(r)?,
334 wip: WipProof {
335 A: CompressedPoint::read(r)?,
336 B: CompressedPoint::read(r)?,
337 r_answer: Scalar::read(r)?.into(),
338 s_answer: Scalar::read(r)?.into(),
339 delta_answer: Scalar::read(r)?.into(),
340 L: read_vec(CompressedPoint::read, Some(MAX_LR), r)?,
341 R: read_vec(CompressedPoint::read, Some(MAX_LR), r)?,
342 },
343 }))
344 }
345}