1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![doc = include_str!("../README.md")]
3#![deny(missing_docs)]
4#![cfg_attr(not(feature = "std"), no_std)]
5#![allow(non_snake_case)]
6
7use std_shims::{
8 vec::Vec,
9 io::{self, Read, Write},
10};
11
12use rand_core::{RngCore, CryptoRng};
13use zeroize::Zeroizing;
14
15use monero_io::*;
16pub use monero_generators::MAX_BULLETPROOF_COMMITMENTS as MAX_COMMITMENTS;
17use monero_generators::COMMITMENT_BITS;
18use monero_primitives::Commitment;
19
20pub(crate) mod scalar_vector;
21pub(crate) mod point_vector;
22
23pub(crate) mod core;
24
25pub(crate) mod batch_verifier;
26use batch_verifier::{BulletproofsBatchVerifier, BulletproofsPlusBatchVerifier};
27pub use batch_verifier::BatchVerifier;
28
29pub(crate) mod original;
30use crate::original::{
31 IpProof, AggregateRangeStatement as OriginalStatement, AggregateRangeWitness as OriginalWitness,
32 AggregateRangeProof as OriginalProof,
33};
34
35pub(crate) mod plus;
36use crate::plus::{
37 WipProof, AggregateRangeStatement as PlusStatement, AggregateRangeWitness as PlusWitness,
38 AggregateRangeProof as PlusProof,
39};
40
41#[cfg(test)]
42mod tests;
43
44const LOG_COMMITMENT_BITS: usize = COMMITMENT_BITS.ilog2() as usize;
46const MAX_LR: usize = (MAX_COMMITMENTS.ilog2() as usize) + LOG_COMMITMENT_BITS;
48
49#[derive(Clone, Copy, PartialEq, Eq, Debug, thiserror::Error)]
51pub enum BulletproofError {
52 #[error("no commitments to prove the range for")]
54 NoCommitments,
55 #[error("too many commitments to prove the range for")]
57 TooManyCommitments,
58}
59
60#[allow(clippy::large_enum_variant)]
64#[derive(Clone, PartialEq, Eq, Debug)]
65pub enum Bulletproof {
66 Original(OriginalProof),
68 Plus(PlusProof),
70}
71
72impl Bulletproof {
73 fn bp_fields(plus: bool) -> usize {
74 if plus {
75 6
76 } else {
77 9
78 }
79 }
80
81 pub fn calculate_clawback(plus: bool, n_outputs: usize) -> (usize, usize) {
92 #[allow(non_snake_case)]
93 let mut LR_len = 0;
94 let mut n_padded_outputs = 1;
95 while n_padded_outputs < n_outputs.min(MAX_COMMITMENTS) {
96 LR_len += 1;
97 n_padded_outputs = 1 << LR_len;
98 }
99 LR_len += LOG_COMMITMENT_BITS;
100
101 let mut clawback = 0;
102 if n_padded_outputs > 2 {
103 let fields = Bulletproof::bp_fields(plus);
104 let base = ((fields + (2 * (LOG_COMMITMENT_BITS + 1))) * 32) / 2;
105 let size = (fields + (2 * LR_len)) * 32;
106 clawback = ((base * n_padded_outputs) - size) * 4 / 5;
107 }
108
109 (clawback, LR_len)
110 }
111
112 pub fn prove<R: RngCore + CryptoRng>(
114 rng: &mut R,
115 outputs: Vec<Commitment>,
116 ) -> Result<Bulletproof, BulletproofError> {
117 if outputs.is_empty() {
118 Err(BulletproofError::NoCommitments)?;
119 }
120 if outputs.len() > MAX_COMMITMENTS {
121 Err(BulletproofError::TooManyCommitments)?;
122 }
123 let commitments = outputs.iter().map(Commitment::calculate).collect::<Vec<_>>();
124 Ok(Bulletproof::Original(
125 OriginalStatement::new(&commitments)
126 .expect("failed to create statement despite checking amount of commitments")
127 .prove(
128 rng,
129 OriginalWitness::new(outputs)
130 .expect("failed to create witness despite checking amount of commitments"),
131 )
132 .expect(
133 "failed to prove Bulletproof::Original despite ensuring statement/witness consistency",
134 ),
135 ))
136 }
137
138 pub fn prove_plus<R: RngCore + CryptoRng>(
140 rng: &mut R,
141 outputs: Vec<Commitment>,
142 ) -> Result<Bulletproof, BulletproofError> {
143 if outputs.is_empty() {
144 Err(BulletproofError::NoCommitments)?;
145 }
146 if outputs.len() > MAX_COMMITMENTS {
147 Err(BulletproofError::TooManyCommitments)?;
148 }
149 let commitments = outputs.iter().map(Commitment::calculate).collect::<Vec<_>>();
150 Ok(Bulletproof::Plus(
151 PlusStatement::new(&commitments)
152 .expect("failed to create statement despite checking amount of commitments")
153 .prove(
154 rng,
155 &Zeroizing::new(
156 PlusWitness::new(outputs)
157 .expect("failed to create witness despite checking amount of commitments"),
158 ),
159 )
160 .expect("failed to prove Bulletproof::Plus despite ensuring statement/witness consistency"),
161 ))
162 }
163
164 #[must_use]
166 pub fn verify<R: RngCore + CryptoRng>(
167 &self,
168 rng: &mut R,
169 commitments: &[CompressedPoint],
170 ) -> bool {
171 let Some(commitments) =
172 commitments.iter().map(CompressedPoint::decompress).collect::<Option<Vec<_>>>()
173 else {
174 return false;
175 };
176
177 match self {
178 Bulletproof::Original(bp) => {
179 let mut verifier = BulletproofsBatchVerifier::default();
180 let Some(statement) = OriginalStatement::new(&commitments) else {
181 return false;
182 };
183 if !statement.verify(rng, &mut verifier, bp.clone()) {
184 return false;
185 }
186 verifier.verify()
187 }
188 Bulletproof::Plus(bp) => {
189 let mut verifier = BulletproofsPlusBatchVerifier::default();
190 let Some(statement) = PlusStatement::new(&commitments) else {
191 return false;
192 };
193 if !statement.verify(rng, &mut verifier, bp.clone()) {
194 return false;
195 }
196 verifier.verify()
197 }
198 }
199 }
200
201 #[must_use]
210 pub fn batch_verify<R: RngCore + CryptoRng>(
211 &self,
212 rng: &mut R,
213 verifier: &mut BatchVerifier,
214 commitments: &[CompressedPoint],
215 ) -> bool {
216 let Some(commitments) =
217 commitments.iter().map(CompressedPoint::decompress).collect::<Option<Vec<_>>>()
218 else {
219 return false;
220 };
221
222 match self {
223 Bulletproof::Original(bp) => {
224 let Some(statement) = OriginalStatement::new(&commitments) else {
225 return false;
226 };
227 statement.verify(rng, &mut verifier.original, bp.clone())
228 }
229 Bulletproof::Plus(bp) => {
230 let Some(statement) = PlusStatement::new(&commitments) else {
231 return false;
232 };
233 statement.verify(rng, &mut verifier.plus, bp.clone())
234 }
235 }
236 }
237
238 fn write_core<W: Write, F: Fn(&[CompressedPoint], &mut W) -> io::Result<()>>(
239 &self,
240 w: &mut W,
241 specific_write_vec: F,
242 ) -> io::Result<()> {
243 match self {
244 Bulletproof::Original(bp) => {
245 CompressedPoint::write(&bp.A, w)?;
246 CompressedPoint::write(&bp.S, w)?;
247 CompressedPoint::write(&bp.T1, w)?;
248 CompressedPoint::write(&bp.T2, w)?;
249 write_scalar(&bp.tau_x, w)?;
250 write_scalar(&bp.mu, w)?;
251 specific_write_vec(&bp.ip.L, w)?;
252 specific_write_vec(&bp.ip.R, w)?;
253 write_scalar(&bp.ip.a, w)?;
254 write_scalar(&bp.ip.b, w)?;
255 write_scalar(&bp.t_hat, w)
256 }
257
258 Bulletproof::Plus(bp) => {
259 CompressedPoint::write(&bp.A, w)?;
260 CompressedPoint::write(&bp.wip.A, w)?;
261 CompressedPoint::write(&bp.wip.B, w)?;
262 write_scalar(&bp.wip.r_answer, w)?;
263 write_scalar(&bp.wip.s_answer, w)?;
264 write_scalar(&bp.wip.delta_answer, w)?;
265 specific_write_vec(&bp.wip.L, w)?;
266 specific_write_vec(&bp.wip.R, w)
267 }
268 }
269 }
270
271 pub fn signature_write<W: Write>(&self, w: &mut W) -> io::Result<()> {
275 self.write_core(w, |points, w| write_raw_vec(CompressedPoint::write, points, w))
276 }
277
278 pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
280 self.write_core(w, |points, w| write_vec(CompressedPoint::write, points, w))
281 }
282
283 pub fn serialize(&self) -> Vec<u8> {
285 let mut serialized = Vec::with_capacity(512);
286 self.write(&mut serialized).expect("write failed but <Vec as io::Write> doesn't fail");
287 serialized
288 }
289
290 pub fn read<R: Read>(r: &mut R) -> io::Result<Bulletproof> {
292 Ok(Bulletproof::Original(OriginalProof {
293 A: CompressedPoint::read(r)?,
294 S: CompressedPoint::read(r)?,
295 T1: CompressedPoint::read(r)?,
296 T2: CompressedPoint::read(r)?,
297 tau_x: read_scalar(r)?,
298 mu: read_scalar(r)?,
299 ip: IpProof {
300 L: read_vec(CompressedPoint::read, Some(MAX_LR), r)?,
301 R: read_vec(CompressedPoint::read, Some(MAX_LR), r)?,
302 a: read_scalar(r)?,
303 b: read_scalar(r)?,
304 },
305 t_hat: read_scalar(r)?,
306 }))
307 }
308
309 pub fn read_plus<R: Read>(r: &mut R) -> io::Result<Bulletproof> {
311 Ok(Bulletproof::Plus(PlusProof {
312 A: CompressedPoint::read(r)?,
313 wip: WipProof {
314 A: CompressedPoint::read(r)?,
315 B: CompressedPoint::read(r)?,
316 r_answer: read_scalar(r)?,
317 s_answer: read_scalar(r)?,
318 delta_answer: read_scalar(r)?,
319 L: read_vec(CompressedPoint::read, Some(MAX_LR), r)?,
320 R: read_vec(CompressedPoint::read, Some(MAX_LR), r)?,
321 },
322 }))
323 }
324}