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,
9 vec::Vec,
10 io::{self, Read, Write},
11};
12
13use rand_core::{RngCore, CryptoRng};
14use zeroize::Zeroizing;
15
16use curve25519_dalek::edwards::CompressedEdwardsY;
17
18use monero_io::*;
19pub use monero_generators::MAX_COMMITMENTS;
20use monero_primitives::Commitment;
21
22pub(crate) mod scalar_vector;
23pub(crate) mod point_vector;
24
25pub(crate) mod core;
26use crate::core::LOG_COMMITMENT_BITS;
27
28pub(crate) mod batch_verifier;
29use batch_verifier::{BulletproofsBatchVerifier, BulletproofsPlusBatchVerifier};
30pub use batch_verifier::BatchVerifier;
31
32pub(crate) mod original;
33use crate::original::{
34 IpProof, AggregateRangeStatement as OriginalStatement, AggregateRangeWitness as OriginalWitness,
35 AggregateRangeProof as OriginalProof,
36};
37
38pub(crate) mod plus;
39use crate::plus::{
40 WipProof, AggregateRangeStatement as PlusStatement, AggregateRangeWitness as PlusWitness,
41 AggregateRangeProof as PlusProof,
42};
43
44#[cfg(test)]
45mod tests;
46
47#[derive(Clone, Copy, PartialEq, Eq, Debug)]
49#[cfg_attr(feature = "std", derive(thiserror::Error))]
50pub enum BulletproofError {
51 #[cfg_attr(feature = "std", error("no commitments to prove the range for"))]
53 NoCommitments,
54 #[cfg_attr(feature = "std", error("too many commitments to prove the range for"))]
56 TooManyCommitments,
57}
58
59#[allow(clippy::large_enum_variant)]
63#[derive(Clone, PartialEq, Eq, Debug)]
64pub enum Bulletproof {
65 Original(OriginalProof),
67 Plus(PlusProof),
69}
70
71impl Bulletproof {
72 fn bp_fields(plus: bool) -> usize {
73 if plus {
74 6
75 } else {
76 9
77 }
78 }
79
80 pub fn calculate_bp_clawback(plus: bool, n_outputs: usize) -> (usize, usize) {
88 #[allow(non_snake_case)]
89 let mut LR_len = 0;
90 let mut n_padded_outputs = 1;
91 while n_padded_outputs < n_outputs {
92 LR_len += 1;
93 n_padded_outputs = 1 << LR_len;
94 }
95 LR_len += LOG_COMMITMENT_BITS;
96
97 let mut bp_clawback = 0;
98 if n_padded_outputs > 2 {
99 let fields = Bulletproof::bp_fields(plus);
100 let base = ((fields + (2 * (LOG_COMMITMENT_BITS + 1))) * 32) / 2;
101 let size = (fields + (2 * LR_len)) * 32;
102 bp_clawback = ((base * n_padded_outputs) - size) * 4 / 5;
103 }
104
105 (bp_clawback, LR_len)
106 }
107
108 pub fn prove<R: RngCore + CryptoRng>(
110 rng: &mut R,
111 outputs: Vec<Commitment>,
112 ) -> Result<Bulletproof, BulletproofError> {
113 if outputs.is_empty() {
114 Err(BulletproofError::NoCommitments)?;
115 }
116 if outputs.len() > MAX_COMMITMENTS {
117 Err(BulletproofError::TooManyCommitments)?;
118 }
119 let commitments = outputs.iter().map(Commitment::calculate).collect::<Vec<_>>();
120 Ok(Bulletproof::Original(
121 OriginalStatement::new(&commitments)
122 .unwrap()
123 .prove(rng, OriginalWitness::new(outputs).unwrap())
124 .unwrap(),
125 ))
126 }
127
128 pub fn prove_plus<R: RngCore + CryptoRng>(
130 rng: &mut R,
131 outputs: Vec<Commitment>,
132 ) -> Result<Bulletproof, BulletproofError> {
133 if outputs.is_empty() {
134 Err(BulletproofError::NoCommitments)?;
135 }
136 if outputs.len() > MAX_COMMITMENTS {
137 Err(BulletproofError::TooManyCommitments)?;
138 }
139 let commitments = outputs.iter().map(Commitment::calculate).collect::<Vec<_>>();
140 Ok(Bulletproof::Plus(
141 PlusStatement::new(&commitments)
142 .unwrap()
143 .prove(rng, &Zeroizing::new(PlusWitness::new(outputs).unwrap()))
144 .unwrap(),
145 ))
146 }
147
148 #[must_use]
150 pub fn verify<R: RngCore + CryptoRng>(
151 &self,
152 rng: &mut R,
153 commitments: &[CompressedEdwardsY],
154 ) -> bool {
155 let Some(commitments) =
156 commitments.iter().map(|p| decompress_point(*p)).collect::<Option<Vec<_>>>()
157 else {
158 return false;
159 };
160
161 match self {
162 Bulletproof::Original(bp) => {
163 let mut verifier = BulletproofsBatchVerifier::default();
164 let Some(statement) = OriginalStatement::new(&commitments) else {
165 return false;
166 };
167 if !statement.verify(rng, &mut verifier, bp.clone()) {
168 return false;
169 }
170 verifier.verify()
171 }
172 Bulletproof::Plus(bp) => {
173 let mut verifier = BulletproofsPlusBatchVerifier::default();
174 let Some(statement) = PlusStatement::new(&commitments) else {
175 return false;
176 };
177 if !statement.verify(rng, &mut verifier, bp.clone()) {
178 return false;
179 }
180 verifier.verify()
181 }
182 }
183 }
184
185 #[must_use]
194 pub fn batch_verify<R: RngCore + CryptoRng>(
195 &self,
196 rng: &mut R,
197 verifier: &mut BatchVerifier,
198 commitments: &[CompressedEdwardsY],
199 ) -> bool {
200 let Some(commitments) =
201 commitments.iter().map(|p| decompress_point(*p)).collect::<Option<Vec<_>>>()
202 else {
203 return false;
204 };
205
206 match self {
207 Bulletproof::Original(bp) => {
208 let Some(statement) = OriginalStatement::new(&commitments) else {
209 return false;
210 };
211 statement.verify(rng, &mut verifier.original, bp.clone())
212 }
213 Bulletproof::Plus(bp) => {
214 let Some(statement) = PlusStatement::new(&commitments) else {
215 return false;
216 };
217 statement.verify(rng, &mut verifier.plus, bp.clone())
218 }
219 }
220 }
221
222 fn write_core<W: Write, F: Fn(&[CompressedEdwardsY], &mut W) -> io::Result<()>>(
223 &self,
224 w: &mut W,
225 specific_write_vec: F,
226 ) -> io::Result<()> {
227 match self {
228 Bulletproof::Original(bp) => {
229 write_compressed_point(&bp.A, w)?;
230 write_compressed_point(&bp.S, w)?;
231 write_compressed_point(&bp.T1, w)?;
232 write_compressed_point(&bp.T2, w)?;
233 write_scalar(&bp.tau_x, w)?;
234 write_scalar(&bp.mu, w)?;
235 specific_write_vec(&bp.ip.L, w)?;
236 specific_write_vec(&bp.ip.R, w)?;
237 write_scalar(&bp.ip.a, w)?;
238 write_scalar(&bp.ip.b, w)?;
239 write_scalar(&bp.t_hat, w)
240 }
241
242 Bulletproof::Plus(bp) => {
243 write_compressed_point(&bp.A, w)?;
244 write_compressed_point(&bp.wip.A, w)?;
245 write_compressed_point(&bp.wip.B, w)?;
246 write_scalar(&bp.wip.r_answer, w)?;
247 write_scalar(&bp.wip.s_answer, w)?;
248 write_scalar(&bp.wip.delta_answer, w)?;
249 specific_write_vec(&bp.wip.L, w)?;
250 specific_write_vec(&bp.wip.R, w)
251 }
252 }
253 }
254
255 pub fn signature_write<W: Write>(&self, w: &mut W) -> io::Result<()> {
259 self.write_core(w, |points, w| write_raw_vec(write_compressed_point, points, w))
260 }
261
262 pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
264 self.write_core(w, |points, w| write_vec(write_compressed_point, points, w))
265 }
266
267 pub fn serialize(&self) -> Vec<u8> {
269 let mut serialized = vec![];
270 self.write(&mut serialized).unwrap();
271 serialized
272 }
273
274 pub fn read<R: Read>(r: &mut R) -> io::Result<Bulletproof> {
276 Ok(Bulletproof::Original(OriginalProof {
277 A: read_compressed_point(r)?,
278 S: read_compressed_point(r)?,
279 T1: read_compressed_point(r)?,
280 T2: read_compressed_point(r)?,
281 tau_x: read_scalar(r)?,
282 mu: read_scalar(r)?,
283 ip: IpProof {
284 L: read_vec(read_compressed_point, r)?,
285 R: read_vec(read_compressed_point, r)?,
286 a: read_scalar(r)?,
287 b: read_scalar(r)?,
288 },
289 t_hat: read_scalar(r)?,
290 }))
291 }
292
293 pub fn read_plus<R: Read>(r: &mut R) -> io::Result<Bulletproof> {
295 Ok(Bulletproof::Plus(PlusProof {
296 A: read_compressed_point(r)?,
297 wip: WipProof {
298 A: read_compressed_point(r)?,
299 B: read_compressed_point(r)?,
300 r_answer: read_scalar(r)?,
301 s_answer: read_scalar(r)?,
302 delta_answer: read_scalar(r)?,
303 L: read_vec(read_compressed_point, r)?,
304 R: read_vec(read_compressed_point, r)?,
305 },
306 }))
307 }
308}