From f133507ddf3a4c53b856e1575b0dc2b17d3a7269 Mon Sep 17 00:00:00 2001 From: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com> Date: Tue, 9 Jan 2024 09:53:38 -0600 Subject: [PATCH 1/3] Support `no-std` environments --- .github/workflows/test.yml | 15 ++++ Cargo.toml | 17 ++-- benches/range_proof.rs | 55 +++++++----- src/commitment_opening.rs | 2 + src/errors.rs | 2 + src/extended_mask.rs | 5 +- src/generators/aggregated_gens_iter.rs | 2 + src/generators/bulletproof_gens.rs | 6 +- src/generators/generators_chain.rs | 2 +- src/generators/mod.rs | 2 + src/generators/pedersen_gens.rs | 5 +- src/lib.rs | 2 + src/protocols/curve_point_protocol.rs | 2 +- src/protocols/scalar_protocol.rs | 6 +- src/protocols/transcript_protocol.rs | 2 + src/range_parameters.rs | 8 +- src/range_proof.rs | 114 +++++++++++++------------ src/range_statement.rs | 4 + src/range_witness.rs | 5 +- src/ristretto.rs | 7 +- src/utils/generic.rs | 23 +++-- tests/ristretto.rs | 29 ++++--- 22 files changed, 190 insertions(+), 125 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4692ca9..7bade34 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,6 +3,20 @@ on: [push, pull_request] name: Test jobs: + wasm: + name: cargo build (WASM) + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v4 + - name: toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + targets: wasm32-unknown-unknown + - name: build (WASM) + run: cargo build --target wasm32-unknown-unknown --no-default-features + test: name: cargo test runs-on: ubuntu-latest @@ -18,6 +32,7 @@ jobs: uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} + targets: wasm32-unknown-unknown - name: check (no features) run: cargo +${{ matrix.rust }} check --no-default-features - name: check (all features) diff --git a/Cargo.toml b/Cargo.toml index 7ef1800..3c4fc70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,26 +9,27 @@ description = "A smaller faster implementation of Bulletproofs" [dependencies] blake2 = { version = "0.10", default-features = false } byteorder = { version = "1", default-features = false } -curve25519-dalek = { package = "tari-curve25519-dalek", version = "4.0.3", features = ["serde", "rand_core"] } -digest = { version = "0.10", default-features = false } +curve25519-dalek = { package = "tari-curve25519-dalek", version = "4.0.3", default-features = false, features = ["alloc", "rand_core", "serde", "zeroize"] } +digest = { version = "0.10", default-features = false, features = ["alloc"] } itertools = { version = "0.12", default-features = false, features = ["use_alloc"] } merlin = { version = "3", default-features = false } -once_cell = { version = "1", default-features = false, features = ["critical-section"] } -rand = { version = "0.8", optional = true } +once_cell = { version = "1", default-features = false, features = ["alloc", "critical-section"] } +rand = { version = "0.8", optional = true, default-features = false } +rand_core = { version = "0.6", default-features = false, features = ["alloc"] } serde = { version = "1.0", default-features = false, features = ["alloc"] } sha3 = { version = "0.10", default-features = false } thiserror-no-std = { version = "2", default-features = false } zeroize = { version = "1", default-features = false, features = ["alloc", "derive"] } -rand_core = { version = "0.6", default-features = false, features = ["alloc"] } [dev-dependencies] bincode = "1" criterion = "0.5" -quickcheck = "1" +rand_chacha = "0.3.1" [features] -default = ["rand"] -rand = ["dep:rand"] +default = ["rand", "std"] +std = ["blake2/std", "byteorder/std", "digest/std", "itertools/use_std", "merlin/std", "once_cell/std", "rand?/std", "rand_core/std", "serde/std", "sha3/std", "zeroize/std"] +rand = ["dep:rand", "rand/alloc", "rand/getrandom"] [[bench]] name = "range_proof" diff --git a/benches/range_proof.rs b/benches/range_proof.rs index 45b6f01..06b3635 100644 --- a/benches/range_proof.rs +++ b/benches/range_proof.rs @@ -11,7 +11,8 @@ extern crate criterion; use criterion::{Criterion, SamplingMode}; use curve25519_dalek::scalar::Scalar; -use rand::{thread_rng, Rng}; +use rand_chacha::ChaCha12Rng; +use rand_core::{CryptoRngCore, SeedableRng}; use tari_bulletproofs_plus::{ commitment_opening::CommitmentOpening, generators::pedersen_gens::ExtensionDegree, @@ -41,16 +42,18 @@ fn create_aggregated_rangeproof_helper(bit_length: usize, extension_degree: Exte let mut group = c.benchmark_group("range_proof_creation"); group.sampling_mode(SamplingMode::Flat); + let mut rng = ChaCha12Rng::seed_from_u64(8675309); // for testing only! + let transcript_label: &'static str = "BatchedRangeProofTest"; #[allow(clippy::cast_possible_truncation)] - let (value_min, value_max) = (0u64, (1u128 << (bit_length - 1)) as u64); + let value_max = (1u128 << (bit_length - 1)) as u64; for aggregation_factor in AGGREGATION_SIZES { let label = format!( "Agg {}-bit BP+ create agg factor {} degree {:?}", bit_length, aggregation_factor, extension_degree ); - group.bench_function(&label, move |b| { + group.bench_function(&label, |b| { // 1. Generators let generators = RangeParameters::init( bit_length, @@ -63,9 +66,8 @@ fn create_aggregated_rangeproof_helper(bit_length: usize, extension_degree: Exte let mut commitments = vec![]; let mut minimum_values = vec![]; let mut openings = vec![]; - let mut rng = thread_rng(); for _ in 0..aggregation_factor { - let value = rng.gen_range(value_min..=value_max); + let value = rng.as_rngcore().next_u64() % value_max; // introduces bias, but that's fine for testing minimum_values.push(Some(value / 3)); let blindings = vec![Scalar::random_not_zero(&mut rng); extension_degree as usize]; commitments.push( @@ -90,7 +92,7 @@ fn create_aggregated_rangeproof_helper(bit_length: usize, extension_degree: Exte // Benchmark this code b.iter(|| { // 4. Create the aggregated proof - let _proof = RistrettoRangeProof::prove(transcript_label, &statement, &witness); + let _proof = RistrettoRangeProof::prove_with_rng(transcript_label, &statement, &witness, &mut rng); }) }); } @@ -113,9 +115,11 @@ fn verify_aggregated_rangeproof_helper(bit_length: usize, extension_degree: Exte let mut group = c.benchmark_group("range_proof_verification"); group.sampling_mode(SamplingMode::Flat); + let mut rng = ChaCha12Rng::seed_from_u64(8675309); // for testing only! + let transcript_label: &'static str = "BatchedRangeProofTest"; #[allow(clippy::cast_possible_truncation)] - let (value_min, value_max) = (0u64, (1u128 << (bit_length - 1)) as u64); + let value_max = (1u128 << (bit_length - 1)) as u64; for aggregation_factor in AGGREGATION_SIZES { let pederson_gens = ristretto::create_pedersen_gens_with_extension_degree(extension_degree); @@ -123,7 +127,7 @@ fn verify_aggregated_rangeproof_helper(bit_length: usize, extension_degree: Exte "Agg {}-bit BP+ verify agg factor {} degree {:?}", bit_length, aggregation_factor, extension_degree ); - group.bench_function(&label, move |b| { + group.bench_function(&label, |b| { // 0. Batch data let mut statements = vec![]; let mut proofs = vec![]; @@ -136,9 +140,8 @@ fn verify_aggregated_rangeproof_helper(bit_length: usize, extension_degree: Exte let mut commitments = vec![]; let mut minimum_values = vec![]; let mut openings = vec![]; - let mut rng = thread_rng(); for _ in 0..aggregation_factor { - let value = rng.gen_range(value_min..=value_max); + let value = rng.as_rngcore().next_u64() % value_max; // introduces bias, but that's fine for testing minimum_values.push(Some(value / 3)); let blindings = vec![Scalar::random_not_zero(&mut rng); extension_degree as usize]; commitments.push( @@ -163,15 +166,20 @@ fn verify_aggregated_rangeproof_helper(bit_length: usize, extension_degree: Exte transcript_labels.push(transcript_label); // 4. Create the proof - let proof = RistrettoRangeProof::prove(transcript_label, &statement, &witness).unwrap(); + let proof = RistrettoRangeProof::prove_with_rng(transcript_label, &statement, &witness, &mut rng).unwrap(); proofs.push(proof); // Benchmark this code b.iter(|| { // 5. Verify the aggregated proof - let _masks = - RangeProof::verify_batch(&transcript_labels, &statements, &proofs, VerifyAction::VerifyOnly) - .unwrap(); + let _masks = RangeProof::verify_batch_with_rng( + &transcript_labels, + &statements, + &proofs, + VerifyAction::VerifyOnly, + &mut rng, + ) + .unwrap(); }); }); } @@ -194,9 +202,11 @@ fn verify_batched_rangeproofs_helper(bit_length: usize, extension_degree: Extens let mut group = c.benchmark_group("batched_range_proof_verification"); group.sampling_mode(SamplingMode::Flat); + let mut rng = ChaCha12Rng::seed_from_u64(8675309); // for testing only! + let transcript_label: &'static str = "BatchedRangeProofTest"; #[allow(clippy::cast_possible_truncation)] - let (value_min, value_max) = (0u64, (1u128 << (bit_length - 1)) as u64); + let value_max = (1u128 << (bit_length - 1)) as u64; for extract_masks in EXTRACT_MASKS { for number_of_range_proofs in BATCHED_SIZES { @@ -209,9 +219,7 @@ fn verify_batched_rangeproofs_helper(bit_length: usize, extension_degree: Extens let pc_gens = ristretto::create_pedersen_gens_with_extension_degree(extension_degree); let generators = RangeParameters::init(bit_length, 1, pc_gens).unwrap(); - let mut rng = thread_rng(); - - group.bench_function(&label, move |b| { + group.bench_function(&label, |b| { // Batch data let mut statements = vec![]; let mut proofs = vec![]; @@ -220,7 +228,7 @@ fn verify_batched_rangeproofs_helper(bit_length: usize, extension_degree: Extens for _ in 0..number_of_range_proofs { // Witness data let mut openings = vec![]; - let value = rng.gen_range(value_min..=value_max); + let value = rng.as_rngcore().next_u64() % value_max; // introduces bias, but that's fine for testing let blindings = vec![Scalar::random_not_zero(&mut rng); extension_degree as usize]; openings.push(CommitmentOpening::new(value, blindings.clone())); let witness = RangeWitness::init(openings).unwrap(); @@ -241,7 +249,8 @@ fn verify_batched_rangeproofs_helper(bit_length: usize, extension_degree: Extens transcript_labels.push(transcript_label); // Proof - let proof = RistrettoRangeProof::prove(transcript_label, &statement, &witness).unwrap(); + let proof = + RistrettoRangeProof::prove_with_rng(transcript_label, &statement, &witness, &mut rng).unwrap(); proofs.push(proof); } @@ -250,20 +259,22 @@ fn verify_batched_rangeproofs_helper(bit_length: usize, extension_degree: Extens // Verify the entire batch of proofs match extract_masks { VerifyAction::VerifyOnly => { - let _masks = RangeProof::verify_batch( + let _masks = RangeProof::verify_batch_with_rng( &transcript_labels, &statements, &proofs, VerifyAction::VerifyOnly, + &mut rng, ) .unwrap(); }, VerifyAction::RecoverOnly => { - let _masks = RangeProof::verify_batch( + let _masks = RangeProof::verify_batch_with_rng( &transcript_labels, &statements, &proofs, VerifyAction::RecoverOnly, + &mut rng, ) .unwrap(); }, diff --git a/src/commitment_opening.rs b/src/commitment_opening.rs index 27f32ec..0b79085 100644 --- a/src/commitment_opening.rs +++ b/src/commitment_opening.rs @@ -3,6 +3,8 @@ //! Bulletproofs+ commitment opening struct +use alloc::{string::ToString, vec::Vec}; + use curve25519_dalek::scalar::Scalar; use zeroize::{Zeroize, ZeroizeOnDrop}; diff --git a/src/errors.rs b/src/errors.rs index 79c90ee..0b2052a 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -3,6 +3,8 @@ //! Bulletproofs+ error definitions +use alloc::string::String; + use thiserror_no_std::Error; /// Represents an error in proof creation, verification, or parsing. diff --git a/src/extended_mask.rs b/src/extended_mask.rs index fd23d90..0ee8500 100644 --- a/src/extended_mask.rs +++ b/src/extended_mask.rs @@ -3,6 +3,8 @@ //! Bulletproofs+ embedded extended mask +use alloc::{string::ToString, vec::Vec}; + use curve25519_dalek::scalar::Scalar; use zeroize::{Zeroize, ZeroizeOnDrop}; @@ -40,7 +42,8 @@ impl ExtendedMask { #[cfg(test)] mod test { - use std::convert::TryFrom; + use alloc::vec; + use core::convert::TryFrom; use super::*; diff --git a/src/generators/aggregated_gens_iter.rs b/src/generators/aggregated_gens_iter.rs index 10909b1..47bafa6 100644 --- a/src/generators/aggregated_gens_iter.rs +++ b/src/generators/aggregated_gens_iter.rs @@ -4,6 +4,8 @@ // Copyright (c) 2018 Chain, Inc. // SPDX-License-Identifier: MIT +use alloc::vec::Vec; + /// A convenience iterator struct for the generators pub struct AggregatedGensIter<'a, P> { pub(super) array: &'a Vec>, diff --git a/src/generators/bulletproof_gens.rs b/src/generators/bulletproof_gens.rs index 174b9c8..83ab4e6 100644 --- a/src/generators/bulletproof_gens.rs +++ b/src/generators/bulletproof_gens.rs @@ -4,10 +4,10 @@ // Copyright (c) 2018 Chain, Inc. // SPDX-License-Identifier: MIT -use std::{ +use alloc::{sync::Arc, vec::Vec}; +use core::{ convert::TryFrom, fmt::{Debug, Formatter}, - sync::Arc, }; use byteorder::{ByteOrder, LittleEndian}; @@ -139,7 +139,7 @@ where P: Compressable + Debug + Precomputable, P::Compressed: Debug, { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { f.debug_struct("RangeParameters") .field("gens_capacity", &self.gens_capacity) .field("party_capacity", &self.party_capacity) diff --git a/src/generators/generators_chain.rs b/src/generators/generators_chain.rs index e6ec399..4be1a0c 100644 --- a/src/generators/generators_chain.rs +++ b/src/generators/generators_chain.rs @@ -4,7 +4,7 @@ // Copyright (c) 2018 Chain, Inc. // SPDX-License-Identifier: MIT -use std::marker::PhantomData; +use core::marker::PhantomData; use digest::{core_api::XofReaderCoreWrapper, ExtendableOutput, Update, XofReader}; use sha3::{Shake256, Shake256ReaderCore}; diff --git a/src/generators/mod.rs b/src/generators/mod.rs index cb8ea79..53f35a2 100644 --- a/src/generators/mod.rs +++ b/src/generators/mod.rs @@ -17,6 +17,8 @@ pub mod pedersen_gens; #[cfg(test)] mod tests { + use alloc::vec::Vec; + use curve25519_dalek::ristretto::RistrettoPoint; use crate::generators::bulletproof_gens::BulletproofGens; diff --git a/src/generators/pedersen_gens.rs b/src/generators/pedersen_gens.rs index 6507e1f..3ff17b5 100644 --- a/src/generators/pedersen_gens.rs +++ b/src/generators/pedersen_gens.rs @@ -4,7 +4,8 @@ // Copyright (c) 2018 Chain, Inc. // SPDX-License-Identifier: MIT -use std::{borrow::Borrow, convert::TryFrom, iter::once}; +use alloc::{string::ToString, vec::Vec}; +use core::{borrow::Borrow, convert::TryFrom, iter::once}; use curve25519_dalek::{scalar::Scalar, traits::MultiscalarMul}; use zeroize::Zeroize; @@ -124,7 +125,7 @@ where P: Compressable + MultiscalarMul + Clone #[cfg(test)] mod test { - use std::convert::TryFrom; + use core::convert::TryFrom; use super::ExtensionDegree; diff --git a/src/lib.rs b/src/lib.rs index f9e6497..af3c25d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,8 @@ //! Bulletproofs+ +extern crate alloc; + /// Bulletproofs+ commitment opening pub mod commitment_opening; /// Bulletproofs+ error definitions diff --git a/src/protocols/curve_point_protocol.rs b/src/protocols/curve_point_protocol.rs index 080b74a..c7eb093 100644 --- a/src/protocols/curve_point_protocol.rs +++ b/src/protocols/curve_point_protocol.rs @@ -3,7 +3,7 @@ //! Bulletproofs+ `CurvePointProtocol` trait provides the required interface for curves using BP+. -use std::{ +use core::{ borrow::Borrow, ops::{Add, AddAssign}, }; diff --git a/src/protocols/scalar_protocol.rs b/src/protocols/scalar_protocol.rs index 65b6930..25fcdf3 100644 --- a/src/protocols/scalar_protocol.rs +++ b/src/protocols/scalar_protocol.rs @@ -39,12 +39,14 @@ impl ScalarProtocol for Scalar { #[cfg(test)] mod test { use curve25519_dalek::Scalar; - use rand::thread_rng; + use rand_chacha::ChaCha12Rng; + use rand_core::SeedableRng; use super::*; #[test] fn test_nonzero() { - assert_ne!(Scalar::random_not_zero(&mut thread_rng()), Scalar::ZERO); + let mut rng = ChaCha12Rng::seed_from_u64(8675309); // for testing only! + assert_ne!(Scalar::random_not_zero(&mut rng), Scalar::ZERO); } } diff --git a/src/protocols/transcript_protocol.rs b/src/protocols/transcript_protocol.rs index d25e356..767d5cb 100644 --- a/src/protocols/transcript_protocol.rs +++ b/src/protocols/transcript_protocol.rs @@ -6,6 +6,8 @@ //! Bulletproofs+ `TranscriptProtocol` trait for using a Transcript +use alloc::string::ToString; + use curve25519_dalek::{scalar::Scalar, traits::IsIdentity}; use merlin::Transcript; diff --git a/src/range_parameters.rs b/src/range_parameters.rs index b562957..3673f48 100644 --- a/src/range_parameters.rs +++ b/src/range_parameters.rs @@ -3,10 +3,8 @@ //! Bulletproofs+ range parameters (generators and base points) needed for a batch of range proofs -use std::{ - fmt::{Debug, Formatter}, - sync::Arc, -}; +use alloc::{format, string::ToString, sync::Arc}; +use core::fmt::{Debug, Formatter}; use crate::{ errors::ProofError, @@ -116,7 +114,7 @@ where P: Compressable + Debug + Precomputable, P::Compressed: Debug, { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { f.debug_struct("RangeParameters") .field("pc_gens", &self.pc_gens) .field("bp_gens", &self.bp_gens) diff --git a/src/range_proof.rs b/src/range_proof.rs index 7a098ae..85ba1a6 100644 --- a/src/range_proof.rs +++ b/src/range_proof.rs @@ -5,8 +5,10 @@ #![allow(clippy::too_many_lines)] -use std::{ +use alloc::{string::ToString, vec, vec::Vec}; +use core::{ convert::{TryFrom, TryInto}, + iter::once, marker::PhantomData, ops::{Add, Mul, Shr}, slice::ChunksExact, @@ -19,7 +21,7 @@ use curve25519_dalek::{ use itertools::{izip, Itertools}; use merlin::Transcript; #[cfg(feature = "rand")] -use rand::thread_rng; +use rand::rngs::OsRng; use rand_core::CryptoRngCore; use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; use zeroize::Zeroizing; @@ -86,7 +88,7 @@ const ENCODED_EXTENSION_SIZE: usize = 1; /// ``` /// use curve25519_dalek::scalar::Scalar; /// use merlin::Transcript; -/// use rand::Rng; +/// use rand::rngs::OsRng; /// # fn main() { /// use tari_bulletproofs_plus::{ /// commitment_opening::CommitmentOpening, @@ -101,7 +103,7 @@ const ENCODED_EXTENSION_SIZE: usize = 1; /// ristretto, /// ristretto::RistrettoRangeProof, /// }; -/// let mut rng = rand::thread_rng(); +/// let mut rng = OsRng; /// let transcript_label: &'static str = "BatchedRangeProofTest"; /// let bit_length = 64; // Other powers of two are permissible up to 2^6 = 64 /// @@ -217,7 +219,7 @@ where statement: &RangeStatement

, witness: &RangeWitness, ) -> Result { - Self::prove_with_rng(transcript_label, statement, witness, &mut thread_rng()) + Self::prove_with_rng(transcript_label, statement, witness, &mut OsRng) } /// Create a single or aggregated range proof for a single party that knows all the secrets @@ -440,24 +442,18 @@ where // Compute L and R by multi-scalar multiplication li.push(P::vartime_multiscalar_mul( - std::iter::once::<&Scalar>(&c_l) + once::<&Scalar>(&c_l) .chain(d_l.iter()) .chain(a_lo_offset.iter()) .chain(b_hi.iter()), - std::iter::once(h_base) - .chain(g_base.iter()) - .chain(gi_base_hi) - .chain(hi_base_lo), + once(h_base).chain(g_base.iter()).chain(gi_base_hi).chain(hi_base_lo), )); ri.push(P::vartime_multiscalar_mul( - std::iter::once::<&Scalar>(&c_r) + once::<&Scalar>(&c_r) .chain(d_r.iter()) .chain(a_hi_offset.iter()) .chain(b_lo.iter()), - std::iter::once(h_base) - .chain(g_base.iter()) - .chain(gi_base_lo) - .chain(hi_base_hi), + once(h_base).chain(g_base.iter()).chain(gi_base_lo).chain(hi_base_hi), )); // Get the round challenge and associated values @@ -671,7 +667,7 @@ where proofs: &[RangeProof

], action: VerifyAction, ) -> Result>, ProofError> { - Self::verify_batch_with_rng(transcript_labels, statements, proofs, action, &mut thread_rng()) + Self::verify_batch_with_rng(transcript_labels, statements, proofs, action, &mut OsRng) } /// Wrapper function for batch verification in different modes: mask recovery, verification, or both @@ -1242,10 +1238,11 @@ where #[cfg(test)] mod tests { - use std::convert::TryFrom; + use core::convert::TryFrom; use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint}; - use quickcheck::QuickCheck; + use rand_chacha::ChaCha12Rng; + use rand_core::SeedableRng; use super::*; use crate::{ @@ -1256,26 +1253,6 @@ mod tests { BulletproofGens, }; - #[test] - #[allow(clippy::needless_pass_by_value)] - fn test_deserialization() { - fn internal(bytes: Vec) -> bool { - // Deserialization should either fail or serialize canonically - match RistrettoRangeProof::from_bytes(&bytes) { - Err(_) => true, - Ok(proof) => proof.to_bytes() == bytes, - } - } - - const TESTS: u64 = 100_000; - - QuickCheck::new() - .min_tests_passed(TESTS) - .tests(TESTS) - .max_tests(TESTS) - .quickcheck(internal as fn(Vec) -> bool); - } - #[test] fn test_from_bytes() { assert!((RistrettoRangeProof::from_bytes(&[])).is_err()); @@ -1377,6 +1354,8 @@ mod tests { #[test] fn test_consistency_errors() { + let mut rng = ChaCha12Rng::seed_from_u64(8675309); // for testing only! + // Generate two proofs let params = RangeParameters::init( 4, @@ -1398,7 +1377,10 @@ mod tests { ) .unwrap(), ); - proofs.push(RangeProof::prove("test", statements.last().unwrap(), witnesses.last().unwrap()).unwrap()); + proofs.push( + RangeProof::prove_with_rng("test", statements.last().unwrap(), witnesses.last().unwrap(), &mut rng) + .unwrap(), + ); } // Empty vectors @@ -1552,6 +1534,8 @@ mod tests { #[test] fn test_getters() { + let mut rng = ChaCha12Rng::seed_from_u64(8675309); // for testing only! + // Generate a valid proof let params = RangeParameters::init( 4, @@ -1567,7 +1551,7 @@ mod tests { None, ) .unwrap(); - let mut proof = RangeProof::prove("test", &statement, &witness).unwrap(); + let mut proof = RangeProof::prove_with_rng("test", &statement, &witness, &mut rng).unwrap(); // Mutate proof elements let mut bytes = [0u8; 32]; @@ -1598,6 +1582,8 @@ mod tests { #[test] fn test_prover_consistency_errors() { + let mut rng = ChaCha12Rng::seed_from_u64(8675309); // for testing only! + // Create range parameters to use for all tests let params = RangeParameters::init( 4, @@ -1622,7 +1608,7 @@ mod tests { None, ) .unwrap(); - assert!(RangeProof::prove("test", &statement, &witness).is_err()); + assert!(RangeProof::prove_with_rng("test", &statement, &witness, &mut rng).is_err()); // Witness and statement extension degrees do not match let witness = RangeWitness::init(vec![CommitmentOpening::new(1u64, vec![Scalar::ONE, Scalar::ONE])]).unwrap(); @@ -1636,7 +1622,7 @@ mod tests { None, ) .unwrap(); - assert!(RangeProof::prove("test", &statement, &witness).is_err()); + assert!(RangeProof::prove_with_rng("test", &statement, &witness, &mut rng).is_err()); // Witness value overflows bit length let witness = RangeWitness::init(vec![CommitmentOpening::new(16u64, vec![Scalar::ONE])]).unwrap(); @@ -1650,7 +1636,7 @@ mod tests { None, ) .unwrap(); - assert!(RangeProof::prove("test", &statement, &witness).is_err()); + assert!(RangeProof::prove_with_rng("test", &statement, &witness, &mut rng).is_err()); // Witness opening is invalid for statement commitment let witness = RangeWitness::init(vec![CommitmentOpening::new(1u64, vec![Scalar::ONE])]).unwrap(); @@ -1664,7 +1650,7 @@ mod tests { None, ) .unwrap(); - assert!(RangeProof::prove("test", &statement, &witness).is_err()); + assert!(RangeProof::prove_with_rng("test", &statement, &witness, &mut rng).is_err()); // Witness value does not meet minimum value promise let witness = RangeWitness::init(vec![CommitmentOpening::new(1u64, vec![Scalar::ONE])]).unwrap(); @@ -1678,11 +1664,13 @@ mod tests { None, ) .unwrap(); - assert!(RangeProof::prove("test", &statement, &witness).is_err()); + assert!(RangeProof::prove_with_rng("test", &statement, &witness, &mut rng).is_err()); } #[test] fn test_verify_errors() { + let mut rng = ChaCha12Rng::seed_from_u64(8675309); // for testing only! + // Generate a valid proof let params = RangeParameters::init( 4, @@ -1698,28 +1686,47 @@ mod tests { None, ) .unwrap(); - let mut proof = RangeProof::prove("test", &statement, &witness).unwrap(); + let mut proof = RangeProof::prove_with_rng("test", &statement, &witness, &mut rng).unwrap(); // Empty statement and proof vectors - assert!(RangeProof::verify_batch(&[], &[], &[proof.clone()], VerifyAction::VerifyOnly).is_err()); - assert!(RangeProof::verify_batch(&["test"], &[statement.clone()], &[], VerifyAction::VerifyOnly).is_err()); + assert!( + RangeProof::verify_batch_with_rng(&[], &[], &[proof.clone()], VerifyAction::VerifyOnly, &mut rng).is_err() + ); + assert!(RangeProof::verify_batch_with_rng( + &["test"], + &[statement.clone()], + &[], + VerifyAction::VerifyOnly, + &mut rng + ) + .is_err()); // Proof vector mismatches proof.li.pop(); - assert!(RangeProof::verify_batch( + assert!(RangeProof::verify_batch_with_rng( &["test"], &[statement.clone()], &[proof.clone()], - VerifyAction::VerifyOnly + VerifyAction::VerifyOnly, + &mut rng, ) .is_err()); proof.ri.pop(); - assert!(RangeProof::verify_batch(&["test"], &[statement], &[proof], VerifyAction::VerifyOnly).is_err()); + assert!(RangeProof::verify_batch_with_rng( + &["test"], + &[statement], + &[proof], + VerifyAction::VerifyOnly, + &mut rng + ) + .is_err()); } #[test] fn test_aggregation_lower_than_generators() { + let mut rng = ChaCha12Rng::seed_from_u64(8675309); // for testing only! + // Create range parameters let params = RangeParameters::init( 4, @@ -1740,10 +1747,11 @@ mod tests { None, ) .unwrap(); - let proof = RangeProof::prove("test", &statement, &witness).unwrap(); + let proof = RangeProof::prove_with_rng("test", &statement, &witness, &mut rng).unwrap(); // The proof should verify - RangeProof::verify_batch(&["test"], &[statement], &[proof], VerifyAction::VerifyOnly).unwrap(); + RangeProof::verify_batch_with_rng(&["test"], &[statement], &[proof], VerifyAction::VerifyOnly, &mut rng) + .unwrap(); } #[test] diff --git a/src/range_statement.rs b/src/range_statement.rs index 71d426f..65faaff 100644 --- a/src/range_statement.rs +++ b/src/range_statement.rs @@ -4,6 +4,8 @@ //! Bulletproofs+ generators, vector of commitments, vector of optional minimum promised //! values and a vector of optional seed nonces for mask recovery +use alloc::{string::ToString, vec::Vec}; + use curve25519_dalek::scalar::Scalar; use zeroize::Zeroize; @@ -80,6 +82,8 @@ impl Drop for RangeStatement

{ #[cfg(test)] mod test { + use alloc::vec; + use curve25519_dalek::RistrettoPoint; use super::*; diff --git a/src/range_witness.rs b/src/range_witness.rs index 81e614b..e621787 100644 --- a/src/range_witness.rs +++ b/src/range_witness.rs @@ -3,7 +3,8 @@ //! Bulletproofs+ commitment openings for the aggregated case -use std::convert::TryInto; +use alloc::{string::ToString, vec::Vec}; +use core::convert::TryInto; use zeroize::{Zeroize, ZeroizeOnDrop}; @@ -41,6 +42,8 @@ impl RangeWitness { #[cfg(test)] mod test { + use alloc::vec; + use curve25519_dalek::Scalar; use super::*; diff --git a/src/ristretto.rs b/src/ristretto.rs index 9fe1716..633eedd 100644 --- a/src/ristretto.rs +++ b/src/ristretto.rs @@ -5,6 +5,8 @@ //! //! Implementation of BulletProofs for the Ristretto group for Curve25519. +use alloc::{borrow::ToOwned, string::ToString, vec::Vec}; + use curve25519_dalek::{ constants::{RISTRETTO_BASEPOINT_COMPRESSED, RISTRETTO_BASEPOINT_POINT}, ristretto::{CompressedRistretto, RistrettoPoint, VartimeRistrettoPrecomputation}, @@ -110,7 +112,8 @@ fn ristretto_compressed_masking_basepoints() -> &'static [CompressedRistretto; E #[cfg(test)] mod tests { use curve25519_dalek::scalar::Scalar; - use rand::thread_rng; + use rand_chacha::ChaCha12Rng; + use rand_core::SeedableRng; use super::*; use crate::protocols::scalar_protocol::ScalarProtocol; @@ -145,7 +148,7 @@ mod tests { #[test] fn test_commitments() { - let mut rng = thread_rng(); + let mut rng = ChaCha12Rng::seed_from_u64(8675309); // for testing only! let value = Scalar::random_not_zero(&mut rng); let blindings = [ Scalar::random_not_zero(&mut rng), diff --git a/src/utils/generic.rs b/src/utils/generic.rs index ccc2aac..2a9d257 100644 --- a/src/utils/generic.rs +++ b/src/utils/generic.rs @@ -6,14 +6,8 @@ //! Bulletproofs+ utilities -use core::{ - option::{Option, Option::Some}, - result::{ - Result, - Result::{Err, Ok}, - }, -}; -use std::convert::TryFrom; +use alloc::{string::ToString, vec::Vec}; +use core::convert::TryFrom; use blake2::Blake2bMac512; use curve25519_dalek::scalar::Scalar; @@ -76,8 +70,11 @@ pub fn split_at_checked(vec: &[T], n: usize) -> Result<(&[T], &[T]), ProofErr #[cfg(test)] mod tests { + use alloc::{vec, vec::Vec}; + use curve25519_dalek::scalar::Scalar; - use rand::{distributions::Alphanumeric, thread_rng, Rng}; + use rand_chacha::ChaCha12Rng; + use rand_core::SeedableRng; use crate::{ protocols::scalar_protocol::ScalarProtocol, @@ -89,7 +86,7 @@ mod tests { // Check valid splits let v = vec![0u8, 1u8, 2u8]; let (l, r) = split_at_checked(&v, 0).unwrap(); - assert_eq!(l, vec![]); + assert_eq!(l, Vec::::new()); assert_eq!(r, vec![0u8, 1u8, 2u8]); let (l, r) = split_at_checked(&v, 1).unwrap(); @@ -102,7 +99,7 @@ mod tests { let (l, r) = split_at_checked(&v, 3).unwrap(); assert_eq!(l, vec![0u8, 1u8, 2u8]); - assert_eq!(r, vec![]); + assert_eq!(r, Vec::::new()); // Check invalid split assert!(split_at_checked(&v, 4).is_err()); @@ -116,7 +113,7 @@ mod tests { #[test] fn test_nonce() { - let mut rng = thread_rng(); + let mut rng = ChaCha12Rng::seed_from_u64(8675309); // for testing only! let seed_nonce = Scalar::random_not_zero(&mut rng); // Create personalized nonces @@ -181,7 +178,7 @@ mod tests { // Verify no unhandled exceptions occur with varying label parameter lengths for i in 0..32 { - let label: String = (&mut rng).sample_iter(Alphanumeric).take(i).map(char::from).collect(); + let label = "a".repeat(i); match nonce( &Scalar::random(&mut rng), label.as_str(), diff --git a/tests/ristretto.rs b/tests/ristretto.rs index b7a1035..156ccde 100644 --- a/tests/ristretto.rs +++ b/tests/ristretto.rs @@ -4,7 +4,8 @@ #![allow(clippy::too_many_lines)] use curve25519_dalek::scalar::Scalar; -use rand::{thread_rng, Rng}; +use rand_chacha::ChaCha12Rng; +use rand_core::{CryptoRngCore, SeedableRng}; use tari_bulletproofs_plus::{ commitment_opening::CommitmentOpening, errors::ProofError, @@ -152,7 +153,7 @@ fn prove_and_verify( extension_degree: ExtensionDegree, promise_strategy: &ProofOfMinimumValueStrategy, ) { - let mut rng = thread_rng(); + let mut rng = ChaCha12Rng::seed_from_u64(8675309); // for testing only! let transcript_label: &'static str = "BatchedRangeProofTest"; for bit_length in bit_lengths { @@ -165,7 +166,7 @@ fn prove_and_verify( let mut transcript_labels = vec![]; #[allow(clippy::cast_possible_truncation)] - let (value_min, value_max) = (0u64, (1u128 << (bit_length - 1)) as u64); + let value_max = (1u128 << (bit_length - 1)) as u64; for aggregation_size in proof_batch { // 1. Generators let pc_gens = ristretto::create_pedersen_gens_with_extension_degree(extension_degree); @@ -176,7 +177,7 @@ fn prove_and_verify( let mut commitments = vec![]; let mut minimum_values = vec![]; for m in 0..*aggregation_size { - let value = rng.gen_range(value_min..=value_max); + let value = rng.as_rngcore().next_u64() % value_max; // introduces bias, but that's fine for this test let minimum_value = match promise_strategy { ProofOfMinimumValueStrategy::NoOffset => None, ProofOfMinimumValueStrategy::Intermediate => Some(value / 3), @@ -221,7 +222,7 @@ fn prove_and_verify( RangeStatement::init(generators.clone(), commitments, minimum_values.clone(), None).unwrap(); // 4. Create the proofs - let proof = RangeProof::prove(transcript_label, &private_statement.clone(), &witness); + let proof = RangeProof::prove_with_rng(transcript_label, &private_statement.clone(), &witness, &mut rng); match promise_strategy { ProofOfMinimumValueStrategy::LargerThanValue => match proof { Ok(_) => { @@ -246,39 +247,43 @@ fn prove_and_verify( if !proofs.is_empty() { // 5. Verify the entire batch as the commitment owner, i.e. the prover self // --- Only recover the masks - let recovered_private_masks = RangeProof::verify_batch( + let recovered_private_masks = RangeProof::verify_batch_with_rng( &transcript_labels, &statements_private.clone(), &proofs.clone(), VerifyAction::RecoverOnly, + &mut rng, ) .unwrap(); assert_eq!(private_masks, recovered_private_masks); // --- Recover the masks and verify the proofs - let recovered_private_masks = RangeProof::verify_batch( + let recovered_private_masks = RangeProof::verify_batch_with_rng( &transcript_labels, &statements_private.clone(), &proofs.clone(), VerifyAction::RecoverAndVerify, + &mut rng, ) .unwrap(); assert_eq!(private_masks, recovered_private_masks); // --- Verify the proofs but do not recover the masks - let recovered_private_masks = RangeProof::verify_batch( + let recovered_private_masks = RangeProof::verify_batch_with_rng( &transcript_labels, &statements_private.clone(), &proofs.clone(), VerifyAction::VerifyOnly, + &mut rng, ) .unwrap(); assert_eq!(public_masks, recovered_private_masks); // 6. Verify the entire batch as public entity - let recovered_public_masks = RangeProof::verify_batch( + let recovered_public_masks = RangeProof::verify_batch_with_rng( &transcript_labels, &statements_public, &proofs, VerifyAction::VerifyOnly, + &mut rng, ) .unwrap(); assert_eq!(public_masks, recovered_public_masks); @@ -302,11 +307,12 @@ fn prove_and_verify( seed_nonce: statement.seed_nonce.map(|seed_nonce| seed_nonce + Scalar::ONE), }); } - let recovered_private_masks_changed = RistrettoRangeProof::verify_batch( + let recovered_private_masks_changed = RistrettoRangeProof::verify_batch_with_rng( &transcript_labels, &statements_private_changed, &proofs.clone(), VerifyAction::RecoverAndVerify, + &mut rng, ) .unwrap(); assert_ne!(private_masks, recovered_private_masks_changed); @@ -333,11 +339,12 @@ fn prove_and_verify( seed_nonce: statement.seed_nonce, }); } - match RangeProof::verify_batch( + match RangeProof::verify_batch_with_rng( &transcript_labels, &statements_public_changed, &proofs, VerifyAction::VerifyOnly, + &mut rng, ) { Ok(_) => { panic!("Range proof should not verify") From 78daeeac536826b18f54974959a4517a8354aa01 Mon Sep 17 00:00:00 2001 From: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com> Date: Tue, 9 Jan 2024 11:29:37 -0600 Subject: [PATCH 2/3] Update documentation --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 133be66..7af539d 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,15 @@ As always, your mileage may vary. This library underwent a code audit by [Quarkslab](https://www.quarkslab.com/) at a [specific point](https://github.com/tari-project/bulletproofs-plus/releases/tag/pre-audit-commit) in the repository history. You can read the [report and issue responses](docs/quarkslab-audit/README.md) in this repository. +## Features + +The library is `#![no_std]`-friendly when default features are disabled. + +The (default) `rand` feature adds prover and verifier functionality using the `OsRng` random number generator. +If it is not enabled, you must supply your own cryptographically-secure random number generator. + +The (default) `std` feature enables corresponding functionality in dependencies. + ## Testing Unit tests are available via `cargo test`. Basic fuzz testing can be run (on a nightly toolchain) via `cargo fuzz`. From c9a03b1751bf42e5af483433289ee5b0e324936c Mon Sep 17 00:00:00 2001 From: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com> Date: Thu, 11 Jan 2024 10:33:25 -0600 Subject: [PATCH 3/3] Restore fuzzing and add CI tests --- .github/workflows/test.yml | 12 ++++++++---- Cargo.toml | 1 + src/range_proof.rs | 27 +++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7bade34..9d57f20 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,10 +12,10 @@ jobs: - name: toolchain uses: dtolnay/rust-toolchain@master with: - toolchain: stable + toolchain: nightly targets: wasm32-unknown-unknown - name: build (WASM) - run: cargo build --target wasm32-unknown-unknown --no-default-features + run: cargo +nightly build --target wasm32-unknown-unknown --no-default-features -Zavoid-dev-deps test: name: cargo test @@ -37,9 +37,13 @@ jobs: run: cargo +${{ matrix.rust }} check --no-default-features - name: check (all features) run: cargo +${{ matrix.rust }} check --all-features --all-targets - - name: test/debug + - name: test/debug (all features) run: cargo +${{ matrix.rust }} test --all-features - - name: test/release + - name: test/debug (no features) + run: cargo +${{ matrix.rust }} test --no-default-features + - name: test/release (all features) run: cargo +${{ matrix.rust }} test --release --all-features + - name: test/release (no features) + run: cargo +${{ matrix.rust }} test --release --no-default-features - name: Build documentation run: RUSTDOCFLAGS="-D warnings" cargo +${{ matrix.rust }} doc --no-deps diff --git a/Cargo.toml b/Cargo.toml index 3c4fc70..b731833 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ zeroize = { version = "1", default-features = false, features = ["alloc", "deriv [dev-dependencies] bincode = "1" criterion = "0.5" +quickcheck = "1" rand_chacha = "0.3.1" [features] diff --git a/src/range_proof.rs b/src/range_proof.rs index 85ba1a6..42ce126 100644 --- a/src/range_proof.rs +++ b/src/range_proof.rs @@ -88,8 +88,11 @@ const ENCODED_EXTENSION_SIZE: usize = 1; /// ``` /// use curve25519_dalek::scalar::Scalar; /// use merlin::Transcript; +/// #[cfg(feature = "rand")] /// use rand::rngs::OsRng; /// # fn main() { +/// #[cfg(feature = "rand")] +/// # { /// use tari_bulletproofs_plus::{ /// commitment_opening::CommitmentOpening, /// errors::ProofError, @@ -197,6 +200,7 @@ const ENCODED_EXTENSION_SIZE: usize = 1; /// assert_eq!(public_masks, recovered_public_masks); /// /// # } +/// # } /// ``` impl

RangeProof

@@ -1241,6 +1245,7 @@ mod tests { use core::convert::TryFrom; use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint}; + use quickcheck::QuickCheck; use rand_chacha::ChaCha12Rng; use rand_core::SeedableRng; @@ -1253,6 +1258,28 @@ mod tests { BulletproofGens, }; + #[test] + #[allow(clippy::needless_pass_by_value)] + fn test_deserialization_fuzzing() { + fn internal(bytes: Vec) -> bool { + // Deserialization should either fail or serialize canonically + match RistrettoRangeProof::from_bytes(&bytes) { + Err(_) => true, + Ok(proof) => proof.to_bytes() == bytes, + } + } + + // Number of fuzzing tests to run + const TESTS: u64 = 100_000; + + // Run fuzzing tests + QuickCheck::new() + .min_tests_passed(TESTS) + .tests(TESTS) + .max_tests(TESTS) + .quickcheck(internal as fn(Vec) -> bool); + } + #[test] fn test_from_bytes() { assert!((RistrettoRangeProof::from_bytes(&[])).is_err());