Rename Bulletproofs to Bulletproof, since they are a single Bulletproof

Also bifurcates prove with prove_plus, and adds a few documentation items.
This commit is contained in:
Luke Parker
2024-04-22 00:00:14 -04:00
parent fccb1aea51
commit 372e29fe08
5 changed files with 80 additions and 52 deletions

View File

@@ -26,15 +26,15 @@ use self::plus::*;
pub(crate) const MAX_OUTPUTS: usize = self::core::MAX_M; pub(crate) const MAX_OUTPUTS: usize = self::core::MAX_M;
/// Bulletproofs enum, supporting the original and plus formulations. /// Bulletproof enum, encapsulating both Bulletproofs and Bulletproofs+.
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
#[derive(Clone, PartialEq, Eq, Debug)] #[derive(Clone, PartialEq, Eq, Debug)]
pub enum Bulletproofs { pub enum Bulletproof {
Original(OriginalStruct), Original(OriginalStruct),
Plus(AggregateRangeProof), Plus(AggregateRangeProof),
} }
impl Bulletproofs { impl Bulletproof {
fn bp_fields(plus: bool) -> usize { fn bp_fields(plus: bool) -> usize {
if plus { if plus {
6 6
@@ -57,7 +57,7 @@ impl Bulletproofs {
let mut bp_clawback = 0; let mut bp_clawback = 0;
if n_padded_outputs > 2 { if n_padded_outputs > 2 {
let fields = Bulletproofs::bp_fields(plus); let fields = Bulletproof::bp_fields(plus);
let base = ((fields + (2 * (LOG_N + 1))) * 32) / 2; let base = ((fields + (2 * (LOG_N + 1))) * 32) / 2;
let size = (fields + (2 * LR_len)) * 32; let size = (fields + (2 * LR_len)) * 32;
bp_clawback = ((base * n_padded_outputs) - size) * 4 / 5; bp_clawback = ((base * n_padded_outputs) - size) * 4 / 5;
@@ -68,40 +68,49 @@ impl Bulletproofs {
pub(crate) fn fee_weight(plus: bool, outputs: usize) -> usize { pub(crate) fn fee_weight(plus: bool, outputs: usize) -> usize {
#[allow(non_snake_case)] #[allow(non_snake_case)]
let (bp_clawback, LR_len) = Bulletproofs::calculate_bp_clawback(plus, outputs); let (bp_clawback, LR_len) = Bulletproof::calculate_bp_clawback(plus, outputs);
32 * (Bulletproofs::bp_fields(plus) + (2 * LR_len)) + 2 + bp_clawback 32 * (Bulletproof::bp_fields(plus) + (2 * LR_len)) + 2 + bp_clawback
} }
/// Prove the list of commitments are within [0 .. 2^64). /// Prove the list of commitments are within [0 .. 2^64) with an aggregate Bulletproof.
pub fn prove<R: RngCore + CryptoRng>( pub fn prove<R: RngCore + CryptoRng>(
rng: &mut R, rng: &mut R,
outputs: &[Commitment], outputs: &[Commitment],
plus: bool, ) -> Result<Bulletproof, TransactionError> {
) -> Result<Bulletproofs, TransactionError> {
if outputs.is_empty() { if outputs.is_empty() {
Err(TransactionError::NoOutputs)?; Err(TransactionError::NoOutputs)?;
} }
if outputs.len() > MAX_OUTPUTS { if outputs.len() > MAX_OUTPUTS {
Err(TransactionError::TooManyOutputs)?; Err(TransactionError::TooManyOutputs)?;
} }
Ok(if !plus { Ok(Bulletproof::Original(OriginalStruct::prove(rng, outputs)))
Bulletproofs::Original(OriginalStruct::prove(rng, outputs))
} else {
Bulletproofs::Plus(
AggregateRangeStatement::new(outputs.iter().map(Commitment::calculate).collect())
.unwrap()
.prove(rng, &Zeroizing::new(AggregateRangeWitness::new(outputs.to_vec()).unwrap()))
.unwrap(),
)
})
} }
/// Verify the given Bulletproofs. /// Prove the list of commitments are within [0 .. 2^64) with an aggregate Bulletproof+.
pub fn prove_plus<R: RngCore + CryptoRng>(
rng: &mut R,
outputs: Vec<Commitment>,
) -> Result<Bulletproof, TransactionError> {
if outputs.is_empty() {
Err(TransactionError::NoOutputs)?;
}
if outputs.len() > MAX_OUTPUTS {
Err(TransactionError::TooManyOutputs)?;
}
Ok(Bulletproof::Plus(
AggregateRangeStatement::new(outputs.iter().map(Commitment::calculate).collect())
.unwrap()
.prove(rng, &Zeroizing::new(AggregateRangeWitness::new(outputs).unwrap()))
.unwrap(),
))
}
/// Verify the given Bulletproof(+).
#[must_use] #[must_use]
pub fn verify<R: RngCore + CryptoRng>(&self, rng: &mut R, commitments: &[EdwardsPoint]) -> bool { pub fn verify<R: RngCore + CryptoRng>(&self, rng: &mut R, commitments: &[EdwardsPoint]) -> bool {
match self { match self {
Bulletproofs::Original(bp) => bp.verify(rng, commitments), Bulletproof::Original(bp) => bp.verify(rng, commitments),
Bulletproofs::Plus(bp) => { Bulletproof::Plus(bp) => {
let mut verifier = BatchVerifier::new(1); let mut verifier = BatchVerifier::new(1);
let Some(statement) = AggregateRangeStatement::new(commitments.to_vec()) else { let Some(statement) = AggregateRangeStatement::new(commitments.to_vec()) else {
return false; return false;
@@ -114,9 +123,11 @@ impl Bulletproofs {
} }
} }
/// Accumulate the verification for the given Bulletproofs into the specified BatchVerifier. /// Accumulate the verification for the given Bulletproof into the specified BatchVerifier.
/// Returns false if the Bulletproofs aren't sane, without mutating the BatchVerifier. ///
/// Returns true if the Bulletproofs are sane, regardless of their validity. /// Returns false if the Bulletproof isn't sane, leaving the BatchVerifier in an undefined
/// state.
/// Returns true if the Bulletproof is sane, regardless of their validity.
#[must_use] #[must_use]
pub fn batch_verify<ID: Copy + Zeroize, R: RngCore + CryptoRng>( pub fn batch_verify<ID: Copy + Zeroize, R: RngCore + CryptoRng>(
&self, &self,
@@ -126,8 +137,8 @@ impl Bulletproofs {
commitments: &[EdwardsPoint], commitments: &[EdwardsPoint],
) -> bool { ) -> bool {
match self { match self {
Bulletproofs::Original(bp) => bp.batch_verify(rng, verifier, id, commitments), Bulletproof::Original(bp) => bp.batch_verify(rng, verifier, id, commitments),
Bulletproofs::Plus(bp) => { Bulletproof::Plus(bp) => {
let Some(statement) = AggregateRangeStatement::new(commitments.to_vec()) else { let Some(statement) = AggregateRangeStatement::new(commitments.to_vec()) else {
return false; return false;
}; };
@@ -142,7 +153,7 @@ impl Bulletproofs {
specific_write_vec: F, specific_write_vec: F,
) -> io::Result<()> { ) -> io::Result<()> {
match self { match self {
Bulletproofs::Original(bp) => { Bulletproof::Original(bp) => {
write_point(&bp.A, w)?; write_point(&bp.A, w)?;
write_point(&bp.S, w)?; write_point(&bp.S, w)?;
write_point(&bp.T1, w)?; write_point(&bp.T1, w)?;
@@ -156,7 +167,7 @@ impl Bulletproofs {
write_scalar(&bp.t, w) write_scalar(&bp.t, w)
} }
Bulletproofs::Plus(bp) => { Bulletproof::Plus(bp) => {
write_point(&bp.A.0, w)?; write_point(&bp.A.0, w)?;
write_point(&bp.wip.A.0, w)?; write_point(&bp.wip.A.0, w)?;
write_point(&bp.wip.B.0, w)?; write_point(&bp.wip.B.0, w)?;
@@ -173,19 +184,21 @@ impl Bulletproofs {
self.write_core(w, |points, w| write_raw_vec(write_point, points, w)) self.write_core(w, |points, w| write_raw_vec(write_point, points, w))
} }
/// Write the Bulletproof(+) to a writer.
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> { pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
self.write_core(w, |points, w| write_vec(write_point, points, w)) self.write_core(w, |points, w| write_vec(write_point, points, w))
} }
/// Serialize the Bulletproof(+) to a Vec<u8>.
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
let mut serialized = vec![]; let mut serialized = vec![];
self.write(&mut serialized).unwrap(); self.write(&mut serialized).unwrap();
serialized serialized
} }
/// Read Bulletproofs. /// Read a Bulletproof.
pub fn read<R: Read>(r: &mut R) -> io::Result<Bulletproofs> { pub fn read<R: Read>(r: &mut R) -> io::Result<Bulletproof> {
Ok(Bulletproofs::Original(OriginalStruct { Ok(Bulletproof::Original(OriginalStruct {
A: read_point(r)?, A: read_point(r)?,
S: read_point(r)?, S: read_point(r)?,
T1: read_point(r)?, T1: read_point(r)?,
@@ -200,11 +213,11 @@ impl Bulletproofs {
})) }))
} }
/// Read Bulletproofs+. /// Read a Bulletproof+.
pub fn read_plus<R: Read>(r: &mut R) -> io::Result<Bulletproofs> { pub fn read_plus<R: Read>(r: &mut R) -> io::Result<Bulletproof> {
use dalek_ff_group::{Scalar as DfgScalar, EdwardsPoint as DfgPoint}; use dalek_ff_group::{Scalar as DfgScalar, EdwardsPoint as DfgPoint};
Ok(Bulletproofs::Plus(AggregateRangeProof { Ok(Bulletproof::Plus(AggregateRangeProof {
A: DfgPoint(read_point(r)?), A: DfgPoint(read_point(r)?),
wip: WipProof { wip: WipProof {
A: DfgPoint(read_point(r)?), A: DfgPoint(read_point(r)?),

View File

@@ -23,7 +23,7 @@ pub mod bulletproofs;
use crate::{ use crate::{
Protocol, Protocol,
serialize::*, serialize::*,
ringct::{mlsag::Mlsag, clsag::Clsag, borromean::BorromeanRange, bulletproofs::Bulletproofs}, ringct::{mlsag::Mlsag, clsag::Clsag, borromean::BorromeanRange, bulletproofs::Bulletproof},
}; };
/// Generate a key image for a given key. Defined as `x * hash_to_point(xG)`. /// Generate a key image for a given key. Defined as `x * hash_to_point(xG)`.
@@ -199,12 +199,12 @@ pub enum RctPrunable {
mlsags: Vec<Mlsag>, mlsags: Vec<Mlsag>,
}, },
MlsagBulletproofs { MlsagBulletproofs {
bulletproofs: Bulletproofs, bulletproofs: Bulletproof,
mlsags: Vec<Mlsag>, mlsags: Vec<Mlsag>,
pseudo_outs: Vec<EdwardsPoint>, pseudo_outs: Vec<EdwardsPoint>,
}, },
Clsag { Clsag {
bulletproofs: Bulletproofs, bulletproofs: Bulletproof,
clsags: Vec<Clsag>, clsags: Vec<Clsag>,
pseudo_outs: Vec<EdwardsPoint>, pseudo_outs: Vec<EdwardsPoint>,
}, },
@@ -213,7 +213,7 @@ pub enum RctPrunable {
impl RctPrunable { impl RctPrunable {
pub(crate) fn fee_weight(protocol: Protocol, inputs: usize, outputs: usize) -> usize { pub(crate) fn fee_weight(protocol: Protocol, inputs: usize, outputs: usize) -> usize {
// 1 byte for number of BPs (technically a VarInt, yet there's always just zero or one) // 1 byte for number of BPs (technically a VarInt, yet there's always just zero or one)
1 + Bulletproofs::fee_weight(protocol.bp_plus(), outputs) + 1 + Bulletproof::fee_weight(protocol.bp_plus(), outputs) +
(inputs * (Clsag::fee_weight(protocol.ring_len()) + 32)) (inputs * (Clsag::fee_weight(protocol.ring_len()) + 32))
} }
@@ -294,7 +294,7 @@ impl RctPrunable {
{ {
Err(io::Error::other("n bulletproofs instead of one"))?; Err(io::Error::other("n bulletproofs instead of one"))?;
} }
Bulletproofs::read(r)? Bulletproof::read(r)?
}, },
mlsags: (0 .. inputs) mlsags: (0 .. inputs)
.map(|_| Mlsag::read(ring_length, 2, r)) .map(|_| Mlsag::read(ring_length, 2, r))
@@ -307,9 +307,7 @@ impl RctPrunable {
if read_varint::<_, u64>(r)? != 1 { if read_varint::<_, u64>(r)? != 1 {
Err(io::Error::other("n bulletproofs instead of one"))?; Err(io::Error::other("n bulletproofs instead of one"))?;
} }
(if rct_type == RctType::Clsag { Bulletproofs::read } else { Bulletproofs::read_plus })( (if rct_type == RctType::Clsag { Bulletproof::read } else { Bulletproof::read_plus })(r)?
r,
)?
}, },
clsags: (0 .. inputs).map(|_| Clsag::read(ring_length, r)).collect::<Result<_, _>>()?, clsags: (0 .. inputs).map(|_| Clsag::read(ring_length, r)).collect::<Result<_, _>>()?,
pseudo_outs: read_raw_vec(read_point, inputs, r)?, pseudo_outs: read_raw_vec(read_point, inputs, r)?,
@@ -360,7 +358,7 @@ impl RctSignatures {
} }
} }
RctPrunable::Clsag { bulletproofs, .. } => { RctPrunable::Clsag { bulletproofs, .. } => {
if matches!(bulletproofs, Bulletproofs::Original { .. }) { if matches!(bulletproofs, Bulletproof::Original { .. }) {
RctType::Clsag RctType::Clsag
} else { } else {
RctType::BulletproofsPlus RctType::BulletproofsPlus

View File

@@ -7,7 +7,8 @@ use multiexp::BatchVerifier;
use crate::{ use crate::{
Commitment, random_scalar, Commitment, random_scalar,
ringct::bulletproofs::{Bulletproofs, original::OriginalStruct}, ringct::bulletproofs::{Bulletproof, original::OriginalStruct},
wallet::TransactionError,
}; };
mod plus; mod plus;
@@ -18,7 +19,7 @@ fn bulletproofs_vector() {
let point = |point| decompress_point(point).unwrap(); let point = |point| decompress_point(point).unwrap();
// Generated from Monero // Generated from Monero
assert!(Bulletproofs::Original(OriginalStruct { assert!(Bulletproof::Original(OriginalStruct {
A: point(hex!("ef32c0b9551b804decdcb107eb22aa715b7ce259bf3c5cac20e24dfa6b28ac71")), A: point(hex!("ef32c0b9551b804decdcb107eb22aa715b7ce259bf3c5cac20e24dfa6b28ac71")),
S: point(hex!("e1285960861783574ee2b689ae53622834eb0b035d6943103f960cd23e063fa0")), S: point(hex!("e1285960861783574ee2b689ae53622834eb0b035d6943103f960cd23e063fa0")),
T1: point(hex!("4ea07735f184ba159d0e0eb662bac8cde3eb7d39f31e567b0fbda3aa23fe5620")), T1: point(hex!("4ea07735f184ba159d0e0eb662bac8cde3eb7d39f31e567b0fbda3aa23fe5620")),
@@ -70,7 +71,11 @@ macro_rules! bulletproofs_tests {
.map(|i| Commitment::new(random_scalar(&mut OsRng), u64::try_from(i).unwrap())) .map(|i| Commitment::new(random_scalar(&mut OsRng), u64::try_from(i).unwrap()))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let bp = Bulletproofs::prove(&mut OsRng, &commitments, $plus).unwrap(); let bp = if $plus {
Bulletproof::prove_plus(&mut OsRng, commitments.clone()).unwrap()
} else {
Bulletproof::prove(&mut OsRng, &commitments).unwrap()
};
let commitments = commitments.iter().map(Commitment::calculate).collect::<Vec<_>>(); let commitments = commitments.iter().map(Commitment::calculate).collect::<Vec<_>>();
assert!(bp.verify(&mut OsRng, &commitments)); assert!(bp.verify(&mut OsRng, &commitments));
@@ -86,7 +91,15 @@ macro_rules! bulletproofs_tests {
for _ in 0 .. 17 { for _ in 0 .. 17 {
commitments.push(Commitment::new(Scalar::ZERO, 0)); commitments.push(Commitment::new(Scalar::ZERO, 0));
} }
assert!(Bulletproofs::prove(&mut OsRng, &commitments, $plus).is_err()); assert_eq!(
(if $plus {
Bulletproof::prove_plus(&mut OsRng, commitments)
} else {
Bulletproof::prove(&mut OsRng, &commitments)
})
.unwrap_err(),
TransactionError::TooManyOutputs,
);
} }
}; };
} }

View File

@@ -12,7 +12,7 @@ use crate::{
Protocol, hash, Protocol, hash,
serialize::*, serialize::*,
ring_signatures::RingSignature, ring_signatures::RingSignature,
ringct::{bulletproofs::Bulletproofs, RctType, RctBase, RctPrunable, RctSignatures}, ringct::{bulletproofs::Bulletproof, RctType, RctBase, RctPrunable, RctSignatures},
}; };
#[derive(Clone, PartialEq, Eq, Debug)] #[derive(Clone, PartialEq, Eq, Debug)]
@@ -426,7 +426,7 @@ impl Transaction {
if !(bp || bp_plus) { if !(bp || bp_plus) {
blob_size blob_size
} else { } else {
blob_size + Bulletproofs::calculate_bp_clawback(bp_plus, self.prefix.outputs.len()).0 blob_size + Bulletproof::calculate_bp_clawback(bp_plus, self.prefix.outputs.len()).0
} }
} }
} }

View File

@@ -31,7 +31,7 @@ use crate::{
ringct::{ ringct::{
generate_key_image, generate_key_image,
clsag::{ClsagError, ClsagInput, Clsag}, clsag::{ClsagError, ClsagInput, Clsag},
bulletproofs::{MAX_OUTPUTS, Bulletproofs}, bulletproofs::{MAX_OUTPUTS, Bulletproof},
RctBase, RctPrunable, RctSignatures, RctBase, RctPrunable, RctSignatures,
}, },
transaction::{Input, Output, Timelock, TransactionPrefix, Transaction}, transaction::{Input, Output, Timelock, TransactionPrefix, Transaction},
@@ -783,7 +783,11 @@ impl SignableTransaction {
let sum = commitments.iter().map(|commitment| commitment.mask).sum(); let sum = commitments.iter().map(|commitment| commitment.mask).sum();
// Safe due to the constructor checking MAX_OUTPUTS // Safe due to the constructor checking MAX_OUTPUTS
let bp = Bulletproofs::prove(rng, &commitments, self.protocol.bp_plus()).unwrap(); let bp = if self.protocol.bp_plus() {
Bulletproof::prove_plus(rng, commitments.clone()).unwrap()
} else {
Bulletproof::prove(rng, &commitments).unwrap()
};
// Create the TX extra // Create the TX extra
let extra = Self::extra( let extra = Self::extra(