From f7c13fd1ca444d014f27beed412e8f77aa23e805 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 15 Jun 2024 17:00:11 -0400 Subject: [PATCH] Smash out monero-bulletproofs Removes usage of dalek-ff-group/multiexp for curve25519-dalek. Makes compiling in the generators an optional feature. Adds a structured batch verifier which should be notably more performant. Documentation and clean up still necessary. --- Cargo.lock | 18 ++ Cargo.toml | 1 + coins/monero/Cargo.toml | 5 +- coins/monero/build.rs | 67 ------ coins/monero/generators/Cargo.toml | 1 - coins/monero/generators/src/lib.rs | 21 +- coins/monero/primitives/Cargo.toml | 1 - coins/monero/ringct/bulletproofs/Cargo.toml | 57 +++++ coins/monero/ringct/bulletproofs/LICENSE | 21 ++ coins/monero/ringct/bulletproofs/README.md | 6 + coins/monero/ringct/bulletproofs/build.rs | 88 ++++++++ .../ringct/bulletproofs/src/batch_verifier.rs | 94 ++++++++ .../bulletproofs/src}/core.rs | 64 +++--- .../mod.rs => ringct/bulletproofs/src/lib.rs} | 108 +++++---- .../bulletproofs/src}/original.rs | 207 +++++++++--------- .../src}/plus/aggregate_range_proof.rs | 101 ++++----- .../bulletproofs/src}/plus/mod.rs | 46 ++-- .../bulletproofs/src}/plus/point_vector.rs | 6 +- .../bulletproofs/src/plus/transcript.rs | 20 ++ .../src}/plus/weighted_inner_product.rs | 155 +++++++------ .../bulletproofs/src}/scalar_vector.rs | 6 +- .../bulletproofs/src/tests}/mod.rs | 21 +- .../src/tests}/plus/aggregate_range_proof.rs | 20 +- .../bulletproofs/src/tests}/plus/mod.rs | 0 .../src/tests}/plus/weighted_inner_product.rs | 37 ++-- coins/monero/ringct/clsag/Cargo.toml | 2 - coins/monero/src/bin/reserialize_chain.rs | 10 +- .../ringct/bulletproofs/plus/transcript.rs | 25 --- coins/monero/src/ringct/mod.rs | 2 +- coins/monero/src/tests/mod.rs | 1 - coins/monero/src/wallet/send/mod.rs | 6 +- tests/no-std/Cargo.toml | 1 + 32 files changed, 716 insertions(+), 502 deletions(-) delete mode 100644 coins/monero/build.rs create mode 100644 coins/monero/ringct/bulletproofs/Cargo.toml create mode 100644 coins/monero/ringct/bulletproofs/LICENSE create mode 100644 coins/monero/ringct/bulletproofs/README.md create mode 100644 coins/monero/ringct/bulletproofs/build.rs create mode 100644 coins/monero/ringct/bulletproofs/src/batch_verifier.rs rename coins/monero/{src/ringct/bulletproofs => ringct/bulletproofs/src}/core.rs (67%) rename coins/monero/{src/ringct/bulletproofs/mod.rs => ringct/bulletproofs/src/lib.rs} (66%) rename coins/monero/{src/ringct/bulletproofs => ringct/bulletproofs/src}/original.rs (56%) rename coins/monero/{src/ringct/bulletproofs => ringct/bulletproofs/src}/plus/aggregate_range_proof.rs (75%) rename coins/monero/{src/ringct/bulletproofs => ringct/bulletproofs/src}/plus/mod.rs (59%) rename coins/monero/{src/ringct/bulletproofs => ringct/bulletproofs/src}/plus/point_vector.rs (90%) create mode 100644 coins/monero/ringct/bulletproofs/src/plus/transcript.rs rename coins/monero/{src/ringct/bulletproofs => ringct/bulletproofs/src}/plus/weighted_inner_product.rs (75%) rename coins/monero/{src/ringct/bulletproofs => ringct/bulletproofs/src}/scalar_vector.rs (97%) rename coins/monero/{src/tests/bulletproofs => ringct/bulletproofs/src/tests}/mod.rs (89%) rename coins/monero/{src/tests/bulletproofs => ringct/bulletproofs/src/tests}/plus/aggregate_range_proof.rs (52%) rename coins/monero/{src/tests/bulletproofs => ringct/bulletproofs/src/tests}/plus/mod.rs (100%) rename coins/monero/{src/tests/bulletproofs => ringct/bulletproofs/src/tests}/plus/weighted_inner_product.rs (66%) delete mode 100644 coins/monero/src/ringct/bulletproofs/plus/transcript.rs diff --git a/Cargo.lock b/Cargo.lock index 6cda32f6..8d406fe1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4751,6 +4751,22 @@ dependencies = [ "zeroize", ] +[[package]] +name = "monero-bulletproofs" +version = "0.1.0" +dependencies = [ + "curve25519-dalek", + "hex-literal", + "monero-generators", + "monero-io", + "monero-primitives", + "rand_core", + "std-shims", + "subtle", + "thiserror", + "zeroize", +] + [[package]] name = "monero-clsag" version = "0.1.0" @@ -4819,6 +4835,7 @@ dependencies = [ "hex", "hex-literal", "modular-frost", + "monero-bulletproofs", "monero-clsag", "monero-generators", "monero-io", @@ -8079,6 +8096,7 @@ dependencies = [ "dleq", "flexible-transcript", "minimal-ed448", + "monero-bulletproofs", "monero-clsag", "monero-generators", "monero-io", diff --git a/Cargo.toml b/Cargo.toml index df76b22a..1e8da216 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ members = [ "coins/monero/generators", "coins/monero/primitives", "coins/monero/ringct/clsag", + "coins/monero/ringct/bulletproofs", "coins/monero", "message-queue", diff --git a/coins/monero/Cargo.toml b/coins/monero/Cargo.toml index fc87df3b..608963f2 100644 --- a/coins/monero/Cargo.toml +++ b/coins/monero/Cargo.toml @@ -34,7 +34,7 @@ rand_distr = { version = "0.4", default-features = false } sha3 = { version = "0.10", default-features = false } pbkdf2 = { version = "0.12", features = ["simple"], default-features = false } -curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize", "precomputed-tables"] } +curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize"] } # Used for the hash to curve, along with the more complicated proofs group = { version = "0.13", default-features = false } @@ -49,6 +49,7 @@ monero-io = { path = "io", version = "0.1", default-features = false } monero-generators = { path = "generators", version = "0.4", default-features = false } monero-primitives = { path = "primitives", version = "0.1", default-features = false } monero-clsag = { path = "ringct/clsag", version = "0.1", default-features = false } +monero-bulletproofs = { path = "ringct/bulletproofs", version = "0.1", default-features = false } hex-literal = "0.4" hex = { version = "0.4", default-features = false, features = ["alloc"] } @@ -96,6 +97,7 @@ std = [ "monero-generators/std", "monero-primitives/std", "monero-clsag/std", + "monero-bulletproofs/std", "hex/std", "serde/std", @@ -104,6 +106,7 @@ std = [ "base58-monero/std", ] +compile-time-generators = ["curve25519-dalek/precomputed-tables", "monero-bulletproofs/compile-time-generators"] http-rpc = ["digest_auth", "simple-request", "tokio"] multisig = ["transcript", "frost", "monero-clsag/multisig", "std"] binaries = ["tokio/rt-multi-thread", "tokio/macros", "http-rpc"] diff --git a/coins/monero/build.rs b/coins/monero/build.rs deleted file mode 100644 index b10e956a..00000000 --- a/coins/monero/build.rs +++ /dev/null @@ -1,67 +0,0 @@ -use std::{ - io::Write, - env, - path::Path, - fs::{File, remove_file}, -}; - -use dalek_ff_group::EdwardsPoint; - -use monero_generators::bulletproofs_generators; - -fn serialize(generators_string: &mut String, points: &[EdwardsPoint]) { - for generator in points { - generators_string.extend( - format!( - " - dalek_ff_group::EdwardsPoint( - curve25519_dalek::edwards::CompressedEdwardsY({:?}).decompress().unwrap() - ), - ", - generator.compress().to_bytes() - ) - .chars(), - ); - } -} - -fn generators(prefix: &'static str, path: &str) { - let generators = bulletproofs_generators(prefix.as_bytes()); - #[allow(non_snake_case)] - let mut G_str = String::new(); - serialize(&mut G_str, &generators.G); - #[allow(non_snake_case)] - let mut H_str = String::new(); - serialize(&mut H_str, &generators.H); - - let path = Path::new(&env::var("OUT_DIR").unwrap()).join(path); - let _ = remove_file(&path); - File::create(&path) - .unwrap() - .write_all( - format!( - " - pub(crate) static GENERATORS_CELL: OnceLock = OnceLock::new(); - pub fn GENERATORS() -> &'static Generators {{ - GENERATORS_CELL.get_or_init(|| Generators {{ - G: vec![ - {G_str} - ], - H: vec![ - {H_str} - ], - }}) - }} - ", - ) - .as_bytes(), - ) - .unwrap(); -} - -fn main() { - println!("cargo:rerun-if-changed=build.rs"); - - generators("bulletproof", "generators.rs"); - generators("bulletproof_plus", "generators_plus.rs"); -} diff --git a/coins/monero/generators/Cargo.toml b/coins/monero/generators/Cargo.toml index ec62879a..0796eb13 100644 --- a/coins/monero/generators/Cargo.toml +++ b/coins/monero/generators/Cargo.toml @@ -37,7 +37,6 @@ std = [ "subtle/std", "sha3/std", - "curve25519-dalek/precomputed-tables", "group/alloc", "dalek-ff-group/std", diff --git a/coins/monero/generators/src/lib.rs b/coins/monero/generators/src/lib.rs index cd9ee237..d334f472 100644 --- a/coins/monero/generators/src/lib.rs +++ b/coins/monero/generators/src/lib.rs @@ -6,10 +6,7 @@ use std_shims::{sync::OnceLock, vec::Vec}; use sha3::{Digest, Keccak256}; -use curve25519_dalek::edwards::{EdwardsPoint as DalekPoint}; - -use group::{Group, GroupEncoding}; -use dalek_ff_group::EdwardsPoint; +use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, edwards::EdwardsPoint}; use monero_io::{write_varint, decompress_point}; @@ -23,24 +20,26 @@ fn keccak256(data: &[u8]) -> [u8; 32] { Keccak256::digest(data).into() } -static H_CELL: OnceLock = OnceLock::new(); +static H_CELL: OnceLock = OnceLock::new(); /// Monero's `H` generator. /// /// Contrary to convention (`G` for values, `H` for randomness), `H` is used by Monero for amounts /// within Pedersen commitments. #[allow(non_snake_case)] -pub fn H() -> DalekPoint { +pub fn H() -> EdwardsPoint { *H_CELL.get_or_init(|| { - decompress_point(keccak256(&EdwardsPoint::generator().to_bytes())).unwrap().mul_by_cofactor() + decompress_point(keccak256(&ED25519_BASEPOINT_POINT.compress().to_bytes())) + .unwrap() + .mul_by_cofactor() }) } -static H_POW_2_CELL: OnceLock<[DalekPoint; 64]> = OnceLock::new(); +static H_POW_2_CELL: OnceLock<[EdwardsPoint; 64]> = OnceLock::new(); /// Monero's `H` generator, multiplied by 2**i for i in 1 ..= 64. /// /// This table is useful when working with amounts, which are u64s. #[allow(non_snake_case)] -pub fn H_pow_2() -> &'static [DalekPoint; 64] { +pub fn H_pow_2() -> &'static [EdwardsPoint; 64] { H_POW_2_CELL.get_or_init(|| { let mut res = [H(); 64]; for i in 1 .. 64 { @@ -79,11 +78,11 @@ pub fn bulletproofs_generators(dst: &'static [u8]) -> Generators { let mut even = preimage.clone(); write_varint(&i, &mut even).unwrap(); - res.H.push(EdwardsPoint(hash_to_point(keccak256(&even)))); + res.H.push(hash_to_point(keccak256(&even))); let mut odd = preimage.clone(); write_varint(&(i + 1), &mut odd).unwrap(); - res.G.push(EdwardsPoint(hash_to_point(keccak256(&odd)))); + res.G.push(hash_to_point(keccak256(&odd))); } res } diff --git a/coins/monero/primitives/Cargo.toml b/coins/monero/primitives/Cargo.toml index 05646f40..ec6d13e3 100644 --- a/coins/monero/primitives/Cargo.toml +++ b/coins/monero/primitives/Cargo.toml @@ -35,7 +35,6 @@ std = [ "zeroize/std", "sha3/std", - "curve25519-dalek/precomputed-tables", "monero-generators/std", ] diff --git a/coins/monero/ringct/bulletproofs/Cargo.toml b/coins/monero/ringct/bulletproofs/Cargo.toml new file mode 100644 index 00000000..65997baa --- /dev/null +++ b/coins/monero/ringct/bulletproofs/Cargo.toml @@ -0,0 +1,57 @@ +[package] +name = "monero-bulletproofs" +version = "0.1.0" +description = "Bulletproofs(+) range proofs, as defined by the Monero protocol" +license = "MIT" +repository = "https://github.com/serai-dex/serai/tree/develop/coins/monero/ringct/bulletproofs" +authors = ["Luke Parker "] +edition = "2021" +rust-version = "1.79" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[lints] +workspace = true + +[dependencies] +std-shims = { path = "../../../../common/std-shims", version = "^0.1.1", default-features = false } + +thiserror = { version = "1", default-features = false, optional = true } + +rand_core = { version = "0.6", default-features = false } +zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] } +subtle = { version = "^2.4", default-features = false } + +# Cryptographic dependencies +curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize"] } + +# Other Monero dependencies +monero-io = { path = "../../io", version = "0.1", default-features = false } +monero-generators = { path = "../../generators", version = "0.4", default-features = false } +monero-primitives = { path = "../../primitives", version = "0.1", default-features = false } + +[build-dependencies] +curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize"] } +monero-generators = { path = "../../generators", version = "0.4", default-features = false } + +[dev-dependencies] +hex-literal = "0.4" + +[features] +std = [ + "std-shims/std", + + "thiserror", + + "rand_core/std", + "zeroize/std", + "subtle/std", + + "monero-io/std", + "monero-generators/std", + "monero-primitives/std", +] +compile-time-generators = ["curve25519-dalek/precomputed-tables"] +default = ["std"] diff --git a/coins/monero/ringct/bulletproofs/LICENSE b/coins/monero/ringct/bulletproofs/LICENSE new file mode 100644 index 00000000..91d893c1 --- /dev/null +++ b/coins/monero/ringct/bulletproofs/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022-2024 Luke Parker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/coins/monero/ringct/bulletproofs/README.md b/coins/monero/ringct/bulletproofs/README.md new file mode 100644 index 00000000..9e712848 --- /dev/null +++ b/coins/monero/ringct/bulletproofs/README.md @@ -0,0 +1,6 @@ +# Monero Bulletproofs(+) + +Bulletproofs(+) range proofs, as defined by the Monero protocol. + +This library is usable under no-std when the `std` feature (on by default) is +disabled. diff --git a/coins/monero/ringct/bulletproofs/build.rs b/coins/monero/ringct/bulletproofs/build.rs new file mode 100644 index 00000000..4c3565c9 --- /dev/null +++ b/coins/monero/ringct/bulletproofs/build.rs @@ -0,0 +1,88 @@ +use std::{ + io::Write, + env, + path::Path, + fs::{File, remove_file}, +}; + +#[cfg(feature = "compile-time-generators")] +fn generators(prefix: &'static str, path: &str) { + use curve25519_dalek::EdwardsPoint; + + use monero_generators::bulletproofs_generators; + + fn serialize(generators_string: &mut String, points: &[EdwardsPoint]) { + for generator in points { + generators_string.extend( + format!( + " + curve25519_dalek::edwards::CompressedEdwardsY({:?}).decompress().unwrap(), + ", + generator.compress().to_bytes() + ) + .chars(), + ); + } + } + + let generators = bulletproofs_generators(prefix.as_bytes()); + #[allow(non_snake_case)] + let mut G_str = String::new(); + serialize(&mut G_str, &generators.G); + #[allow(non_snake_case)] + let mut H_str = String::new(); + serialize(&mut H_str, &generators.H); + + let path = Path::new(&env::var("OUT_DIR").unwrap()).join(path); + let _ = remove_file(&path); + File::create(&path) + .unwrap() + .write_all( + format!( + " + static GENERATORS_CELL: OnceLock = OnceLock::new(); + pub(crate) fn GENERATORS() -> &'static Generators {{ + GENERATORS_CELL.get_or_init(|| Generators {{ + G: vec![ + {G_str} + ], + H: vec![ + {H_str} + ], + }}) + }} + ", + ) + .as_bytes(), + ) + .unwrap(); +} + +#[cfg(not(feature = "compile-time-generators"))] +fn generators(prefix: &'static str, path: &str) { + let path = Path::new(&env::var("OUT_DIR").unwrap()).join(path); + let _ = remove_file(&path); + File::create(&path) + .unwrap() + .write_all( + format!( + r#" + static GENERATORS_CELL: OnceLock = OnceLock::new(); + pub(crate) fn GENERATORS() -> &'static Generators {{ + GENERATORS_CELL.get_or_init(|| {{ + monero_generators::bulletproofs_generators(b"{prefix}") + }}) + }} + "#, + ) + .as_bytes(), + ) + .unwrap(); +} + +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + + generators("bulletproof", "generators.rs"); + generators("bulletproof_plus", "generators_plus.rs"); +} diff --git a/coins/monero/ringct/bulletproofs/src/batch_verifier.rs b/coins/monero/ringct/bulletproofs/src/batch_verifier.rs new file mode 100644 index 00000000..3821411b --- /dev/null +++ b/coins/monero/ringct/bulletproofs/src/batch_verifier.rs @@ -0,0 +1,94 @@ +use curve25519_dalek::{ + constants::ED25519_BASEPOINT_POINT, + traits::{IsIdentity, VartimeMultiscalarMul}, + scalar::Scalar, + edwards::EdwardsPoint, +}; + +use monero_generators::{H, Generators}; + +use crate::{original, plus}; + +#[derive(Default)] +pub(crate) struct InternalBatchVerifier { + pub(crate) g: Scalar, + pub(crate) h: Scalar, + pub(crate) g_bold: Vec, + pub(crate) h_bold: Vec, + pub(crate) other: Vec<(Scalar, EdwardsPoint)>, +} + +impl InternalBatchVerifier { + pub fn new() -> Self { + Self { g: Scalar::ZERO, h: Scalar::ZERO, g_bold: vec![], h_bold: vec![], other: vec![] } + } + + #[must_use] + pub fn verify(self, G: EdwardsPoint, H: EdwardsPoint, generators: &Generators) -> bool { + let capacity = 2 + self.g_bold.len() + self.h_bold.len() + self.other.len(); + let mut scalars = Vec::with_capacity(capacity); + let mut points = Vec::with_capacity(capacity); + + scalars.push(self.g); + points.push(G); + + scalars.push(self.h); + points.push(H); + + for (i, g_bold) in self.g_bold.into_iter().enumerate() { + scalars.push(g_bold); + points.push(generators.G[i]); + } + + for (i, h_bold) in self.h_bold.into_iter().enumerate() { + scalars.push(h_bold); + points.push(generators.H[i]); + } + + for (scalar, point) in self.other { + scalars.push(scalar); + points.push(point); + } + + EdwardsPoint::vartime_multiscalar_mul(scalars, points).is_identity() + } +} + +#[derive(Default)] +pub(crate) struct BulletproofsBatchVerifier(pub(crate) InternalBatchVerifier); +impl BulletproofsBatchVerifier { + #[must_use] + pub fn verify(self) -> bool { + self.0.verify(ED25519_BASEPOINT_POINT, H(), original::GENERATORS()) + } +} + +#[derive(Default)] +pub(crate) struct BulletproofsPlusBatchVerifier(pub(crate) InternalBatchVerifier); +impl BulletproofsPlusBatchVerifier { + #[must_use] + pub fn verify(self) -> bool { + // Bulletproofs+ is written as per the paper, with G for the value and H for the mask + // Monero uses H for the value and G for the mask + self.0.verify(H(), ED25519_BASEPOINT_POINT, plus::GENERATORS()) + } +} + +#[derive(Default)] +pub struct BatchVerifier { + pub(crate) original: BulletproofsBatchVerifier, + pub(crate) plus: BulletproofsPlusBatchVerifier, +} +impl BatchVerifier { + pub fn new() -> Self { + Self { + original: BulletproofsBatchVerifier(InternalBatchVerifier::new()), + plus: BulletproofsPlusBatchVerifier(InternalBatchVerifier::new()), + } + } + + #[must_use] + pub fn verify(self) -> bool { + self.original.verify() && self.plus.verify() + } +} diff --git a/coins/monero/src/ringct/bulletproofs/core.rs b/coins/monero/ringct/bulletproofs/src/core.rs similarity index 67% rename from coins/monero/src/ringct/bulletproofs/core.rs rename to coins/monero/ringct/bulletproofs/src/core.rs index 6c264e00..4246a4bc 100644 --- a/coins/monero/src/ringct/bulletproofs/core.rs +++ b/coins/monero/ringct/bulletproofs/src/core.rs @@ -1,42 +1,44 @@ use std_shims::{vec::Vec, sync::OnceLock}; use rand_core::{RngCore, CryptoRng}; - use subtle::{Choice, ConditionallySelectable}; -use curve25519_dalek::edwards::EdwardsPoint as DalekPoint; - -use group::{ff::Field, Group}; -use dalek_ff_group::{Scalar, EdwardsPoint}; - -use multiexp::multiexp as multiexp_const; +use curve25519_dalek::{ + constants::ED25519_BASEPOINT_TABLE, + traits::{MultiscalarMul, VartimeMultiscalarMul}, + scalar::Scalar, + edwards::EdwardsPoint, +}; pub(crate) use monero_generators::Generators; +use monero_primitives::{INV_EIGHT, Commitment, keccak256_to_scalar}; -use crate::{INV_EIGHT as DALEK_INV_EIGHT, H as DALEK_H, Commitment, hash_to_scalar as dalek_hash}; -pub(crate) use crate::ringct::bulletproofs::scalar_vector::*; - -#[inline] -pub(crate) fn INV_EIGHT() -> Scalar { - Scalar(DALEK_INV_EIGHT()) -} - -#[inline] -pub(crate) fn H() -> EdwardsPoint { - EdwardsPoint(DALEK_H()) -} - -pub(crate) fn hash_to_scalar(data: &[u8]) -> Scalar { - Scalar(dalek_hash(data)) -} +pub(crate) use crate::scalar_vector::*; // Components common between variants +// TODO: Move to generators? primitives? pub(crate) const MAX_M: usize = 16; pub(crate) const LOG_N: usize = 6; // 2 << 6 == N pub(crate) const N: usize = 64; -pub(crate) fn prove_multiexp(pairs: &[(Scalar, EdwardsPoint)]) -> EdwardsPoint { - multiexp_const(pairs) * INV_EIGHT() +pub(crate) fn multiexp(pairs: &[(Scalar, EdwardsPoint)]) -> EdwardsPoint { + let mut buf_scalars = Vec::with_capacity(pairs.len()); + let mut buf_points = Vec::with_capacity(pairs.len()); + for (scalar, point) in pairs { + buf_scalars.push(scalar); + buf_points.push(point); + } + EdwardsPoint::multiscalar_mul(buf_scalars, buf_points) +} + +pub(crate) fn multiexp_vartime(pairs: &[(Scalar, EdwardsPoint)]) -> EdwardsPoint { + let mut buf_scalars = Vec::with_capacity(pairs.len()); + let mut buf_points = Vec::with_capacity(pairs.len()); + for (scalar, point) in pairs { + buf_scalars.push(scalar); + buf_points.push(point); + } + EdwardsPoint::vartime_multiscalar_mul(buf_scalars, buf_points) } pub(crate) fn vector_exponent( @@ -52,7 +54,7 @@ pub(crate) fn hash_cache(cache: &mut Scalar, mash: &[[u8; 32]]) -> Scalar { let slice = &[cache.to_bytes().as_ref(), mash.iter().copied().flatten().collect::>().as_ref()] .concat(); - *cache = hash_to_scalar(slice); + *cache = keccak256_to_scalar(slice); *cache } @@ -88,11 +90,11 @@ pub(crate) fn bit_decompose(commitments: &[Commitment]) -> (ScalarVector, Scalar (aL, aR) } -pub(crate) fn hash_commitments>( +pub(crate) fn hash_commitments>( commitments: C, ) -> (Scalar, Vec) { - let V = commitments.into_iter().map(|c| EdwardsPoint(c) * INV_EIGHT()).collect::>(); - (hash_to_scalar(&V.iter().flat_map(|V| V.compress().to_bytes()).collect::>()), V) + let V = commitments.into_iter().map(|c| c * INV_EIGHT()).collect::>(); + (keccak256_to_scalar(V.iter().flat_map(|V| V.compress().to_bytes()).collect::>()), V) } pub(crate) fn alpha_rho( @@ -102,7 +104,7 @@ pub(crate) fn alpha_rho( aR: &ScalarVector, ) -> (Scalar, EdwardsPoint) { let ar = Scalar::random(rng); - (ar, (vector_exponent(generators, aL, aR) + (EdwardsPoint::generator() * ar)) * INV_EIGHT()) + (ar, (vector_exponent(generators, aL, aR) + (ED25519_BASEPOINT_TABLE * &ar)) * INV_EIGHT()) } pub(crate) fn LR_statements( @@ -144,7 +146,7 @@ pub(crate) fn challenge_products(w: &[Scalar], winv: &[Scalar]) -> Vec { // Sanity check as if the above failed to populate, it'd be critical for w in &products { - debug_assert!(!bool::from(w.is_zero())); + debug_assert!(*w != Scalar::ZERO); } products diff --git a/coins/monero/src/ringct/bulletproofs/mod.rs b/coins/monero/ringct/bulletproofs/src/lib.rs similarity index 66% rename from coins/monero/src/ringct/bulletproofs/mod.rs rename to coins/monero/ringct/bulletproofs/src/lib.rs index 9c3d571e..1ab041b3 100644 --- a/coins/monero/src/ringct/bulletproofs/mod.rs +++ b/coins/monero/ringct/bulletproofs/src/lib.rs @@ -1,3 +1,6 @@ +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc = include_str!("../README.md")] +#![cfg_attr(not(feature = "std"), no_std)] #![allow(non_snake_case)] use std_shims::{ @@ -6,25 +9,39 @@ use std_shims::{ }; use rand_core::{RngCore, CryptoRng}; - -use zeroize::{Zeroize, Zeroizing}; +use zeroize::Zeroizing; use curve25519_dalek::edwards::EdwardsPoint; -use multiexp::BatchVerifier; -use crate::{Commitment, wallet::TransactionError, serialize::*}; +use monero_io::*; +use monero_primitives::Commitment; pub(crate) mod scalar_vector; pub(crate) mod core; -use self::core::LOG_N; +use crate::core::LOG_N; + +pub mod batch_verifier; +use batch_verifier::{InternalBatchVerifier, BulletproofsPlusBatchVerifier, BatchVerifier}; pub(crate) mod original; -use self::original::OriginalStruct; +use crate::original::OriginalStruct; pub(crate) mod plus; -use self::plus::*; +use crate::plus::*; -pub(crate) const MAX_OUTPUTS: usize = self::core::MAX_M; +#[cfg(test)] +mod tests; + +pub const MAX_COMMITMENTS: usize = crate::core::MAX_M; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "std", derive(thiserror::Error))] +pub enum BulletproofError { + #[cfg_attr(feature = "std", error("no commitments to prove the range for"))] + NoCommitments, + #[cfg_attr(feature = "std", error("too many commitments to prove the range for"))] + TooManyCommitments, +} /// Bulletproof enum, encapsulating both Bulletproofs and Bulletproofs+. #[allow(clippy::large_enum_variant)] @@ -45,7 +62,7 @@ impl Bulletproof { // https://github.com/monero-project/monero/blob/94e67bf96bbc010241f29ada6abc89f49a81759c/ // src/cryptonote_basic/cryptonote_format_utils.cpp#L106-L124 - pub(crate) fn calculate_bp_clawback(plus: bool, n_outputs: usize) -> (usize, usize) { + pub fn calculate_bp_clawback(plus: bool, n_outputs: usize) -> (usize, usize) { #[allow(non_snake_case)] let mut LR_len = 0; let mut n_padded_outputs = 1; @@ -66,7 +83,7 @@ impl Bulletproof { (bp_clawback, LR_len) } - pub(crate) fn fee_weight(plus: bool, outputs: usize) -> usize { + pub fn fee_weight(plus: bool, outputs: usize) -> usize { #[allow(non_snake_case)] let (bp_clawback, LR_len) = Bulletproof::calculate_bp_clawback(plus, outputs); 32 * (Bulletproof::bp_fields(plus) + (2 * LR_len)) + 2 + bp_clawback @@ -76,12 +93,12 @@ impl Bulletproof { pub fn prove( rng: &mut R, outputs: &[Commitment], - ) -> Result { + ) -> Result { if outputs.is_empty() { - Err(TransactionError::NoOutputs)?; + Err(BulletproofError::NoCommitments)?; } - if outputs.len() > MAX_OUTPUTS { - Err(TransactionError::TooManyOutputs)?; + if outputs.len() > MAX_COMMITMENTS { + Err(BulletproofError::TooManyCommitments)?; } Ok(Bulletproof::Original(OriginalStruct::prove(rng, outputs))) } @@ -90,12 +107,12 @@ impl Bulletproof { pub fn prove_plus( rng: &mut R, outputs: Vec, - ) -> Result { + ) -> Result { if outputs.is_empty() { - Err(TransactionError::NoOutputs)?; + Err(BulletproofError::NoCommitments)?; } - if outputs.len() > MAX_OUTPUTS { - Err(TransactionError::TooManyOutputs)?; + if outputs.len() > MAX_COMMITMENTS { + Err(BulletproofError::TooManyCommitments)?; } Ok(Bulletproof::Plus( AggregateRangeStatement::new(outputs.iter().map(Commitment::calculate).collect()) @@ -111,14 +128,14 @@ impl Bulletproof { match self { Bulletproof::Original(bp) => bp.verify(rng, commitments), Bulletproof::Plus(bp) => { - let mut verifier = BatchVerifier::new(1); + let mut verifier = BulletproofsPlusBatchVerifier(InternalBatchVerifier::new()); let Some(statement) = AggregateRangeStatement::new(commitments.to_vec()) else { return false; }; - if !statement.verify(rng, &mut verifier, (), bp.clone()) { + if !statement.verify(rng, &mut verifier, bp.clone()) { return false; } - verifier.verify_vartime() + verifier.verify() } } } @@ -129,20 +146,19 @@ impl Bulletproof { /// state. /// Returns true if the Bulletproof is sane, regardless of their validity. #[must_use] - pub fn batch_verify( + pub fn batch_verify( &self, rng: &mut R, - verifier: &mut BatchVerifier, - id: ID, + verifier: &mut BatchVerifier, commitments: &[EdwardsPoint], ) -> bool { match self { - Bulletproof::Original(bp) => bp.batch_verify(rng, verifier, id, commitments), + Bulletproof::Original(bp) => bp.batch_verify(rng, &mut verifier.original, commitments), Bulletproof::Plus(bp) => { let Some(statement) = AggregateRangeStatement::new(commitments.to_vec()) else { return false; }; - statement.verify(rng, verifier, id, bp.clone()) + statement.verify(rng, &mut verifier.plus, bp.clone()) } } } @@ -158,7 +174,7 @@ impl Bulletproof { write_point(&bp.S, w)?; write_point(&bp.T1, w)?; write_point(&bp.T2, w)?; - write_scalar(&bp.taux, w)?; + write_scalar(&bp.tau_x, w)?; write_scalar(&bp.mu, w)?; specific_write_vec(&bp.L, w)?; specific_write_vec(&bp.R, w)?; @@ -168,19 +184,19 @@ impl Bulletproof { } Bulletproof::Plus(bp) => { - write_point(&bp.A.0, w)?; - write_point(&bp.wip.A.0, w)?; - write_point(&bp.wip.B.0, w)?; - write_scalar(&bp.wip.r_answer.0, w)?; - write_scalar(&bp.wip.s_answer.0, w)?; - write_scalar(&bp.wip.delta_answer.0, w)?; - specific_write_vec(&bp.wip.L.iter().copied().map(|L| L.0).collect::>(), w)?; - specific_write_vec(&bp.wip.R.iter().copied().map(|R| R.0).collect::>(), w) + write_point(&bp.A, w)?; + write_point(&bp.wip.A, w)?; + write_point(&bp.wip.B, w)?; + write_scalar(&bp.wip.r_answer, w)?; + write_scalar(&bp.wip.s_answer, w)?; + write_scalar(&bp.wip.delta_answer, w)?; + specific_write_vec(&bp.wip.L, w)?; + specific_write_vec(&bp.wip.R, w) } } } - pub(crate) fn signature_write(&self, w: &mut W) -> io::Result<()> { + pub fn signature_write(&self, w: &mut W) -> io::Result<()> { self.write_core(w, |points, w| write_raw_vec(write_point, points, w)) } @@ -203,7 +219,7 @@ impl Bulletproof { S: read_point(r)?, T1: read_point(r)?, T2: read_point(r)?, - taux: read_scalar(r)?, + tau_x: read_scalar(r)?, mu: read_scalar(r)?, L: read_vec(read_point, r)?, R: read_vec(read_point, r)?, @@ -215,18 +231,16 @@ impl Bulletproof { /// Read a Bulletproof+. pub fn read_plus(r: &mut R) -> io::Result { - use dalek_ff_group::{Scalar as DfgScalar, EdwardsPoint as DfgPoint}; - Ok(Bulletproof::Plus(AggregateRangeProof { - A: DfgPoint(read_point(r)?), + A: read_point(r)?, wip: WipProof { - A: DfgPoint(read_point(r)?), - B: DfgPoint(read_point(r)?), - r_answer: DfgScalar(read_scalar(r)?), - s_answer: DfgScalar(read_scalar(r)?), - delta_answer: DfgScalar(read_scalar(r)?), - L: read_vec(read_point, r)?.into_iter().map(DfgPoint).collect(), - R: read_vec(read_point, r)?.into_iter().map(DfgPoint).collect(), + A: read_point(r)?, + B: read_point(r)?, + r_answer: read_scalar(r)?, + s_answer: read_scalar(r)?, + delta_answer: read_scalar(r)?, + L: read_vec(read_point, r)?.into_iter().collect(), + R: read_vec(read_point, r)?.into_iter().collect(), }, })) } diff --git a/coins/monero/src/ringct/bulletproofs/original.rs b/coins/monero/ringct/bulletproofs/src/original.rs similarity index 56% rename from coins/monero/src/ringct/bulletproofs/original.rs rename to coins/monero/ringct/bulletproofs/src/original.rs index a0a4a4d6..4958f1e4 100644 --- a/coins/monero/src/ringct/bulletproofs/original.rs +++ b/coins/monero/ringct/bulletproofs/src/original.rs @@ -1,17 +1,17 @@ use std_shims::{vec::Vec, sync::OnceLock}; use rand_core::{RngCore, CryptoRng}; - use zeroize::Zeroize; -use curve25519_dalek::{scalar::Scalar as DalekScalar, edwards::EdwardsPoint as DalekPoint}; +use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar, edwards::EdwardsPoint}; -use group::{ff::Field, Group}; -use dalek_ff_group::{ED25519_BASEPOINT_POINT as G, Scalar, EdwardsPoint}; +use monero_generators::H; +use monero_primitives::{INV_EIGHT, Commitment, keccak256_to_scalar}; -use multiexp::{BatchVerifier, multiexp}; - -use crate::{Commitment, ringct::bulletproofs::core::*}; +use crate::{ + core::*, + batch_verifier::{InternalBatchVerifier, BulletproofsBatchVerifier}, +}; include!(concat!(env!("OUT_DIR"), "/generators.rs")); @@ -36,17 +36,17 @@ pub(crate) fn hadamard_fold( /// Internal structure representing a Bulletproof. #[derive(Clone, PartialEq, Eq, Debug)] pub struct OriginalStruct { - pub(crate) A: DalekPoint, - pub(crate) S: DalekPoint, - pub(crate) T1: DalekPoint, - pub(crate) T2: DalekPoint, - pub(crate) taux: DalekScalar, - pub(crate) mu: DalekScalar, - pub(crate) L: Vec, - pub(crate) R: Vec, - pub(crate) a: DalekScalar, - pub(crate) b: DalekScalar, - pub(crate) t: DalekScalar, + pub(crate) A: EdwardsPoint, + pub(crate) S: EdwardsPoint, + pub(crate) T1: EdwardsPoint, + pub(crate) T2: EdwardsPoint, + pub(crate) tau_x: Scalar, + pub(crate) mu: Scalar, + pub(crate) L: Vec, + pub(crate) R: Vec, + pub(crate) a: Scalar, + pub(crate) b: Scalar, + pub(crate) t: Scalar, } impl OriginalStruct { @@ -68,7 +68,7 @@ impl OriginalStruct { let (mut rho, S) = alpha_rho(&mut *rng, generators, &sL, &sR); let y = hash_cache(&mut cache, &[A.compress().to_bytes(), S.compress().to_bytes()]); - let mut cache = hash_to_scalar(&y.to_bytes()); + let mut cache = keccak256_to_scalar(y.to_bytes()); let z = cache; let l0 = aL - z; @@ -86,32 +86,32 @@ impl OriginalStruct { let r0 = ((aR + z) * &yMN) + &ScalarVector(zero_twos); let r1 = yMN * &sR; - let (T1, T2, x, mut taux) = { + let (T1, T2, x, mut tau_x) = { let t1 = l0.clone().inner_product(&r1) + r0.clone().inner_product(&l1); let t2 = l1.clone().inner_product(&r1); let mut tau1 = Scalar::random(&mut *rng); let mut tau2 = Scalar::random(&mut *rng); - let T1 = prove_multiexp(&[(t1, H()), (tau1, EdwardsPoint::generator())]); - let T2 = prove_multiexp(&[(t2, H()), (tau2, EdwardsPoint::generator())]); + let T1 = multiexp(&[(t1, H()), (tau1, ED25519_BASEPOINT_POINT)]) * INV_EIGHT(); + let T2 = multiexp(&[(t2, H()), (tau2, ED25519_BASEPOINT_POINT)]) * INV_EIGHT(); let x = hash_cache(&mut cache, &[z.to_bytes(), T1.compress().to_bytes(), T2.compress().to_bytes()]); - let taux = (tau2 * (x * x)) + (tau1 * x); + let tau_x = (tau2 * (x * x)) + (tau1 * x); tau1.zeroize(); tau2.zeroize(); - (T1, T2, x, taux) + (T1, T2, x, tau_x) }; let mu = (x * rho) + alpha; alpha.zeroize(); rho.zeroize(); - for (i, gamma) in commitments.iter().map(|c| Scalar(c.mask)).enumerate() { - taux += zpow[i + 2] * gamma; + for (i, gamma) in commitments.iter().map(|c| c.mask).enumerate() { + tau_x += zpow[i + 2] * gamma; } let l = l0 + &(l1 * x); @@ -120,12 +120,12 @@ impl OriginalStruct { let t = l.clone().inner_product(&r); let x_ip = - hash_cache(&mut cache, &[x.to_bytes(), taux.to_bytes(), mu.to_bytes(), t.to_bytes()]); + hash_cache(&mut cache, &[x.to_bytes(), tau_x.to_bytes(), mu.to_bytes(), t.to_bytes()]); let mut a = l; let mut b = r; - let yinv = y.invert().unwrap(); + let yinv = y.invert(); let yinvpow = ScalarVector::powers(yinv, MN); let mut G_proof = generators.G[.. a.len()].to_vec(); @@ -146,13 +146,13 @@ impl OriginalStruct { let (G_L, G_R) = G_proof.split_at(aL.len()); let (H_L, H_R) = H_proof.split_at(aL.len()); - let L_i = prove_multiexp(&LR_statements(&aL, G_R, &bR, H_L, cL, U)); - let R_i = prove_multiexp(&LR_statements(&aR, G_L, &bL, H_R, cR, U)); + let L_i = multiexp(&LR_statements(&aL, G_R, &bR, H_L, cL, U)) * INV_EIGHT(); + let R_i = multiexp(&LR_statements(&aR, G_L, &bL, H_R, cR, U)) * INV_EIGHT(); L.push(L_i); R.push(R_i); let w = hash_cache(&mut cache, &[L_i.compress().to_bytes(), R_i.compress().to_bytes()]); - let winv = w.invert().unwrap(); + let winv = w.invert(); a = (aL * w) + &(aR * winv); b = (bL * winv) + &(bR * w); @@ -163,30 +163,17 @@ impl OriginalStruct { } } - let res = OriginalStruct { - A: *A, - S: *S, - T1: *T1, - T2: *T2, - taux: *taux, - mu: *mu, - L: L.drain(..).map(|L| *L).collect(), - R: R.drain(..).map(|R| *R).collect(), - a: *a[0], - b: *b[0], - t: *t, - }; + let res = OriginalStruct { A, S, T1, T2, tau_x, mu, L, R, a: a[0], b: b[0], t }; debug_assert!(res.verify(rng, &commitments_points)); res } #[must_use] - fn verify_core( + fn verify_core( &self, rng: &mut R, - verifier: &mut BatchVerifier, - id: ID, - commitments: &[DalekPoint], + verifier: &mut BulletproofsBatchVerifier, + commitments: &[EdwardsPoint], ) -> bool { // Verify commitments are valid if commitments.is_empty() || (commitments.len() > MAX_M) { @@ -207,7 +194,7 @@ impl OriginalStruct { let (mut cache, commitments) = hash_commitments(commitments.iter().copied()); let y = hash_cache(&mut cache, &[self.A.compress().to_bytes(), self.S.compress().to_bytes()]); - let z = hash_to_scalar(&y.to_bytes()); + let z = keccak256_to_scalar(y.to_bytes()); cache = z; let x = hash_cache( @@ -217,18 +204,18 @@ impl OriginalStruct { let x_ip = hash_cache( &mut cache, - &[x.to_bytes(), self.taux.to_bytes(), self.mu.to_bytes(), self.t.to_bytes()], + &[x.to_bytes(), self.tau_x.to_bytes(), self.mu.to_bytes(), self.t.to_bytes()], ); let mut w = Vec::with_capacity(logMN); let mut winv = Vec::with_capacity(logMN); for (L, R) in self.L.iter().zip(&self.R) { w.push(hash_cache(&mut cache, &[L.compress().to_bytes(), R.compress().to_bytes()])); - winv.push(cache.invert().unwrap()); + winv.push(cache.invert()); } // Convert the proof from * INV_EIGHT to its actual form - let normalize = |point: &DalekPoint| EdwardsPoint(point.mul_by_cofactor()); + let normalize = |point: &EdwardsPoint| point.mul_by_cofactor(); let L = self.L.iter().map(normalize).collect::>(); let R = self.R.iter().map(normalize).collect::>(); @@ -240,59 +227,70 @@ impl OriginalStruct { let commitments = commitments.iter().map(EdwardsPoint::mul_by_cofactor).collect::>(); // Verify it - let mut proof = Vec::with_capacity(4 + commitments.len()); - let zpow = ScalarVector::powers(z, M + 3); - let ip1y = ScalarVector::powers(y, M * N).sum(); - let mut k = -(zpow[2] * ip1y); - for j in 1 ..= M { - k -= zpow[j + 2] * IP12(); - } - let y1 = Scalar(self.t) - ((z * ip1y) + k); - proof.push((-y1, H())); - - proof.push((-Scalar(self.taux), G)); - - for (j, commitment) in commitments.iter().enumerate() { - proof.push((zpow[j + 2], *commitment)); - } - - proof.push((x, T1)); - proof.push((x * x, T2)); - verifier.queue(&mut *rng, id, proof); - - proof = Vec::with_capacity(4 + (2 * (MN + logMN))); - let z3 = (Scalar(self.t) - (Scalar(self.a) * Scalar(self.b))) * x_ip; - proof.push((z3, H())); - proof.push((-Scalar(self.mu), G)); - - proof.push((Scalar::ONE, A)); - proof.push((x, S)); + // First multiexp { - let ypow = ScalarVector::powers(y, MN); - let yinv = y.invert().unwrap(); - let yinvpow = ScalarVector::powers(yinv, MN); + let verifier_weight = Scalar::random(rng); - let w_cache = challenge_products(&w, &winv); + let ip1y = ScalarVector::powers(y, M * N).sum(); + let mut k = -(zpow[2] * ip1y); + for j in 1 ..= M { + k -= zpow[j + 2] * IP12(); + } + let y1 = self.t - ((z * ip1y) + k); + verifier.0.h -= verifier_weight * y1; - let generators = GENERATORS(); - for i in 0 .. MN { - let g = (Scalar(self.a) * w_cache[i]) + z; - proof.push((-g, generators.G[i])); + verifier.0.g -= verifier_weight * self.tau_x; - let mut h = Scalar(self.b) * yinvpow[i] * w_cache[(!i) & (MN - 1)]; - h -= ((zpow[(i / N) + 2] * TWO_N()[i % N]) + (z * ypow[i])) * yinvpow[i]; - proof.push((-h, generators.H[i])); + for (j, commitment) in commitments.iter().enumerate() { + verifier.0.other.push((verifier_weight * zpow[j + 2], *commitment)); + } + + verifier.0.other.push((verifier_weight * x, T1)); + verifier.0.other.push((verifier_weight * (x * x), T2)); + } + + // Second multiexp + { + let verifier_weight = Scalar::random(rng); + let z3 = (self.t - (self.a * self.b)) * x_ip; + verifier.0.h += verifier_weight * z3; + verifier.0.g -= verifier_weight * self.mu; + + verifier.0.other.push((verifier_weight, A)); + verifier.0.other.push((verifier_weight * x, S)); + + { + let ypow = ScalarVector::powers(y, MN); + let yinv = y.invert(); + let yinvpow = ScalarVector::powers(yinv, MN); + + let w_cache = challenge_products(&w, &winv); + + while verifier.0.g_bold.len() < MN { + verifier.0.g_bold.push(Scalar::ZERO); + } + while verifier.0.h_bold.len() < MN { + verifier.0.h_bold.push(Scalar::ZERO); + } + + for i in 0 .. MN { + let g = (self.a * w_cache[i]) + z; + verifier.0.g_bold[i] -= verifier_weight * g; + + let mut h = self.b * yinvpow[i] * w_cache[(!i) & (MN - 1)]; + h -= ((zpow[(i / N) + 2] * TWO_N()[i % N]) + (z * ypow[i])) * yinvpow[i]; + verifier.0.h_bold[i] -= verifier_weight * h; + } + } + + for i in 0 .. logMN { + verifier.0.other.push((verifier_weight * (w[i] * w[i]), L[i])); + verifier.0.other.push((verifier_weight * (winv[i] * winv[i]), R[i])); } } - for i in 0 .. logMN { - proof.push((w[i] * w[i], L[i])); - proof.push((winv[i] * winv[i], R[i])); - } - verifier.queue(rng, id, proof); - true } @@ -300,24 +298,23 @@ impl OriginalStruct { pub(crate) fn verify( &self, rng: &mut R, - commitments: &[DalekPoint], + commitments: &[EdwardsPoint], ) -> bool { - let mut verifier = BatchVerifier::new(1); - if self.verify_core(rng, &mut verifier, (), commitments) { - verifier.verify_vartime() + let mut verifier = BulletproofsBatchVerifier(InternalBatchVerifier::new()); + if self.verify_core(rng, &mut verifier, commitments) { + verifier.verify() } else { false } } #[must_use] - pub(crate) fn batch_verify( + pub(crate) fn batch_verify( &self, rng: &mut R, - verifier: &mut BatchVerifier, - id: ID, - commitments: &[DalekPoint], + verifier: &mut BulletproofsBatchVerifier, + commitments: &[EdwardsPoint], ) -> bool { - self.verify_core(rng, verifier, id, commitments) + self.verify_core(rng, verifier, commitments) } } diff --git a/coins/monero/src/ringct/bulletproofs/plus/aggregate_range_proof.rs b/coins/monero/ringct/bulletproofs/src/plus/aggregate_range_proof.rs similarity index 75% rename from coins/monero/src/ringct/bulletproofs/plus/aggregate_range_proof.rs rename to coins/monero/ringct/bulletproofs/src/plus/aggregate_range_proof.rs index 58524953..3dd21879 100644 --- a/coins/monero/src/ringct/bulletproofs/plus/aggregate_range_proof.rs +++ b/coins/monero/ringct/bulletproofs/src/plus/aggregate_range_proof.rs @@ -1,35 +1,28 @@ use std_shims::vec::Vec; use rand_core::{RngCore, CryptoRng}; - use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing}; -use multiexp::{multiexp, multiexp_vartime, BatchVerifier}; -use group::{ - ff::{Field, PrimeField}, - Group, GroupEncoding, -}; -use curve25519_dalek::EdwardsPoint as DalekPoint; -use dalek_ff_group::{Scalar, EdwardsPoint}; +use curve25519_dalek::{traits::Identity, scalar::Scalar, edwards::EdwardsPoint}; + +use monero_primitives::{INV_EIGHT, Commitment, keccak256_to_scalar}; use crate::{ - Commitment, - ringct::{ - bulletproofs::core::{MAX_M, N}, - bulletproofs::plus::{ - ScalarVector, PointVector, GeneratorsList, Generators, - transcript::*, - weighted_inner_product::{WipStatement, WipWitness, WipProof}, - padded_pow_of_2, u64_decompose, - }, + batch_verifier::BulletproofsPlusBatchVerifier, + core::{MAX_M, N, multiexp, multiexp_vartime}, + plus::{ + ScalarVector, PointVector, GeneratorsList, BpPlusGenerators, + transcript::*, + weighted_inner_product::{WipStatement, WipWitness, WipProof}, + padded_pow_of_2, u64_decompose, }, }; // Figure 3 of the Bulletproofs+ Paper #[derive(Clone, Debug)] pub(crate) struct AggregateRangeStatement { - generators: Generators, - V: Vec, + generators: BpPlusGenerators, + V: Vec, } impl Zeroize for AggregateRangeStatement { @@ -58,18 +51,29 @@ pub struct AggregateRangeProof { pub(crate) wip: WipProof, } +struct AHatComputation { + y: Scalar, + d_descending_y_plus_z: ScalarVector, + y_mn_plus_one: Scalar, + z: Scalar, + z_pow: ScalarVector, + A_hat: EdwardsPoint, +} + impl AggregateRangeStatement { - pub(crate) fn new(V: Vec) -> Option { + pub(crate) fn new(V: Vec) -> Option { if V.is_empty() || (V.len() > MAX_M) { return None; } - Some(Self { generators: Generators::new(), V }) + Some(Self { generators: BpPlusGenerators::new(), V }) } fn transcript_A(transcript: &mut Scalar, A: EdwardsPoint) -> (Scalar, Scalar) { - let y = hash_to_scalar(&[transcript.to_repr().as_ref(), A.to_bytes().as_ref()].concat()); - let z = hash_to_scalar(y.to_bytes().as_ref()); + let y = keccak256_to_scalar( + [transcript.to_bytes().as_ref(), A.compress().to_bytes().as_ref()].concat(), + ); + let z = keccak256_to_scalar(y.to_bytes().as_ref()); *transcript = z; (y, z) } @@ -88,10 +92,10 @@ impl AggregateRangeStatement { fn compute_A_hat( mut V: PointVector, - generators: &Generators, + generators: &BpPlusGenerators, transcript: &mut Scalar, mut A: EdwardsPoint, - ) -> (Scalar, ScalarVector, Scalar, Scalar, ScalarVector, EdwardsPoint) { + ) -> AHatComputation { let (y, z) = Self::transcript_A(transcript, A); A = A.mul_by_cofactor(); @@ -133,23 +137,23 @@ impl AggregateRangeStatement { let neg_z = -z; let mut A_terms = Vec::with_capacity((generators.len() * 2) + 2); for (i, d_y_z) in d_descending_y_plus_z.0.iter().enumerate() { - A_terms.push((neg_z, generators.generator(GeneratorsList::GBold1, i))); - A_terms.push((*d_y_z, generators.generator(GeneratorsList::HBold1, i))); + A_terms.push((neg_z, generators.generator(GeneratorsList::GBold, i))); + A_terms.push((*d_y_z, generators.generator(GeneratorsList::HBold, i))); } A_terms.push((y_mn_plus_one, commitment_accum)); A_terms.push(( - ((y_pows * z) - (d.sum() * y_mn_plus_one * z) - (y_pows * z.square())), - Generators::g(), + ((y_pows * z) - (d.sum() * y_mn_plus_one * z) - (y_pows * (z * z))), + BpPlusGenerators::g(), )); - ( + AHatComputation { y, d_descending_y_plus_z, y_mn_plus_one, z, - ScalarVector(z_pow), - A + multiexp_vartime(&A_terms), - ) + z_pow: ScalarVector(z_pow), + A_hat: A + multiexp_vartime(&A_terms), + } } pub(crate) fn prove( @@ -175,9 +179,9 @@ impl AggregateRangeStatement { // Commitments aren't transmitted INV_EIGHT though, so this multiplies by INV_EIGHT to enable // clearing its cofactor without mutating the value // For some reason, these values are transcripted * INV_EIGHT, not as transmitted - let V = V.into_iter().map(|V| V * crate::INV_EIGHT()).collect::>(); + let V = V.into_iter().map(|V| V * INV_EIGHT()).collect::>(); let mut transcript = initial_transcript(V.iter()); - let mut V = V.into_iter().map(|V| EdwardsPoint(V.mul_by_cofactor())).collect::>(); + let mut V = V.iter().map(EdwardsPoint::mul_by_cofactor).collect::>(); // Pad V while V.len() < padded_pow_of_2(V.len()) { @@ -205,26 +209,26 @@ impl AggregateRangeStatement { let mut A_terms = Vec::with_capacity((generators.len() * 2) + 1); for (i, a_l) in a_l.0.iter().enumerate() { - A_terms.push((*a_l, generators.generator(GeneratorsList::GBold1, i))); + A_terms.push((*a_l, generators.generator(GeneratorsList::GBold, i))); } for (i, a_r) in a_r.0.iter().enumerate() { - A_terms.push((*a_r, generators.generator(GeneratorsList::HBold1, i))); + A_terms.push((*a_r, generators.generator(GeneratorsList::HBold, i))); } - A_terms.push((alpha, Generators::h())); + A_terms.push((alpha, BpPlusGenerators::h())); let mut A = multiexp(&A_terms); A_terms.zeroize(); // Multiply by INV_EIGHT per earlier commentary - A.0 *= crate::INV_EIGHT(); + A *= INV_EIGHT(); - let (y, d_descending_y_plus_z, y_mn_plus_one, z, z_pow, A_hat) = + let AHatComputation { y, d_descending_y_plus_z, y_mn_plus_one, z, z_pow, A_hat } = Self::compute_A_hat(PointVector(V), &generators, &mut transcript, A); let a_l = a_l - z; let a_r = a_r + &d_descending_y_plus_z; let mut alpha = alpha; for j in 1 ..= witness.0.len() { - alpha += z_pow[j - 1] * Scalar(witness.0[j - 1].mask) * y_mn_plus_one; + alpha += z_pow[j - 1] * witness.0[j - 1].mask * y_mn_plus_one; } Some(AggregateRangeProof { @@ -235,25 +239,22 @@ impl AggregateRangeStatement { }) } - pub(crate) fn verify( + pub(crate) fn verify( self, rng: &mut R, - verifier: &mut BatchVerifier, - id: Id, + verifier: &mut BulletproofsPlusBatchVerifier, proof: AggregateRangeProof, ) -> bool { let Self { generators, V } = self; - let V = V.into_iter().map(|V| V * crate::INV_EIGHT()).collect::>(); + let V = V.into_iter().map(|V| V * INV_EIGHT()).collect::>(); let mut transcript = initial_transcript(V.iter()); - // With the torsion clear, wrap it into a EdwardsPoint from dalek-ff-group - // (which is prime-order) - let V = V.into_iter().map(|V| EdwardsPoint(V.mul_by_cofactor())).collect::>(); + let V = V.iter().map(EdwardsPoint::mul_by_cofactor).collect::>(); let generators = generators.reduce(V.len() * N); - let (y, _, _, _, _, A_hat) = + let AHatComputation { y, A_hat, .. } = Self::compute_A_hat(PointVector(V), &generators, &mut transcript, proof.A); - WipStatement::new(generators, A_hat, y).verify(rng, verifier, id, transcript, proof.wip) + WipStatement::new(generators, A_hat, y).verify(rng, verifier, transcript, proof.wip) } } diff --git a/coins/monero/src/ringct/bulletproofs/plus/mod.rs b/coins/monero/ringct/bulletproofs/src/plus/mod.rs similarity index 59% rename from coins/monero/src/ringct/bulletproofs/plus/mod.rs rename to coins/monero/ringct/bulletproofs/src/plus/mod.rs index 30417821..ec7ca6a7 100644 --- a/coins/monero/src/ringct/bulletproofs/plus/mod.rs +++ b/coins/monero/ringct/bulletproofs/src/plus/mod.rs @@ -1,9 +1,13 @@ #![allow(non_snake_case)] -use group::Group; -use dalek_ff_group::{Scalar, EdwardsPoint}; +use std_shims::sync::OnceLock; + +use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar, edwards::EdwardsPoint}; + +use monero_generators::{H, Generators}; + +pub(crate) use crate::scalar_vector::ScalarVector; -pub(crate) use crate::ringct::bulletproofs::scalar_vector::ScalarVector; mod point_vector; pub(crate) use point_vector::PointVector; @@ -23,55 +27,51 @@ pub(crate) fn padded_pow_of_2(i: usize) -> usize { #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub(crate) enum GeneratorsList { - GBold1, - HBold1, + GBold, + HBold, } // TODO: Table these #[derive(Clone, Debug)] -pub(crate) struct Generators { - g_bold1: &'static [EdwardsPoint], - h_bold1: &'static [EdwardsPoint], +pub(crate) struct BpPlusGenerators { + g_bold: &'static [EdwardsPoint], + h_bold: &'static [EdwardsPoint], } -mod generators { - use std_shims::sync::OnceLock; - use monero_generators::Generators; - include!(concat!(env!("OUT_DIR"), "/generators_plus.rs")); -} +include!(concat!(env!("OUT_DIR"), "/generators_plus.rs")); -impl Generators { +impl BpPlusGenerators { #[allow(clippy::new_without_default)] pub(crate) fn new() -> Self { - let gens = generators::GENERATORS(); - Generators { g_bold1: &gens.G, h_bold1: &gens.H } + let gens = GENERATORS(); + BpPlusGenerators { g_bold: &gens.G, h_bold: &gens.H } } pub(crate) fn len(&self) -> usize { - self.g_bold1.len() + self.g_bold.len() } pub(crate) fn g() -> EdwardsPoint { - dalek_ff_group::EdwardsPoint(crate::H()) + H() } pub(crate) fn h() -> EdwardsPoint { - EdwardsPoint::generator() + ED25519_BASEPOINT_POINT } pub(crate) fn generator(&self, list: GeneratorsList, i: usize) -> EdwardsPoint { match list { - GeneratorsList::GBold1 => self.g_bold1[i], - GeneratorsList::HBold1 => self.h_bold1[i], + GeneratorsList::GBold => self.g_bold[i], + GeneratorsList::HBold => self.h_bold[i], } } pub(crate) fn reduce(&self, generators: usize) -> Self { // Round to the nearest power of 2 let generators = padded_pow_of_2(generators); - assert!(generators <= self.g_bold1.len()); + assert!(generators <= self.g_bold.len()); - Generators { g_bold1: &self.g_bold1[.. generators], h_bold1: &self.h_bold1[.. generators] } + BpPlusGenerators { g_bold: &self.g_bold[.. generators], h_bold: &self.h_bold[.. generators] } } } diff --git a/coins/monero/src/ringct/bulletproofs/plus/point_vector.rs b/coins/monero/ringct/bulletproofs/src/plus/point_vector.rs similarity index 90% rename from coins/monero/src/ringct/bulletproofs/plus/point_vector.rs rename to coins/monero/ringct/bulletproofs/src/plus/point_vector.rs index ac753a01..f9b52a61 100644 --- a/coins/monero/src/ringct/bulletproofs/plus/point_vector.rs +++ b/coins/monero/ringct/bulletproofs/src/plus/point_vector.rs @@ -3,12 +3,10 @@ use std_shims::vec::Vec; use zeroize::{Zeroize, ZeroizeOnDrop}; -use dalek_ff_group::EdwardsPoint; +use curve25519_dalek::edwards::EdwardsPoint; #[cfg(test)] -use multiexp::multiexp; -#[cfg(test)] -use crate::ringct::bulletproofs::plus::ScalarVector; +use crate::{core::multiexp, plus::ScalarVector}; #[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)] pub(crate) struct PointVector(pub(crate) Vec); diff --git a/coins/monero/ringct/bulletproofs/src/plus/transcript.rs b/coins/monero/ringct/bulletproofs/src/plus/transcript.rs new file mode 100644 index 00000000..3e43a239 --- /dev/null +++ b/coins/monero/ringct/bulletproofs/src/plus/transcript.rs @@ -0,0 +1,20 @@ +use std_shims::{sync::OnceLock, vec::Vec}; + +use curve25519_dalek::{scalar::Scalar, edwards::EdwardsPoint}; + +use monero_generators::hash_to_point; +use monero_primitives::{keccak256, keccak256_to_scalar}; + +// Monero starts BP+ transcripts with the following constant. +static TRANSCRIPT_CELL: OnceLock<[u8; 32]> = OnceLock::new(); +pub(crate) fn TRANSCRIPT() -> [u8; 32] { + // Why this uses a hash_to_point is completely unknown. + *TRANSCRIPT_CELL + .get_or_init(|| hash_to_point(keccak256(b"bulletproof_plus_transcript")).compress().to_bytes()) +} + +pub(crate) fn initial_transcript(commitments: core::slice::Iter<'_, EdwardsPoint>) -> Scalar { + let commitments_hash = + keccak256_to_scalar(commitments.flat_map(|V| V.compress().to_bytes()).collect::>()); + keccak256_to_scalar([TRANSCRIPT().as_ref(), &commitments_hash.to_bytes()].concat()) +} diff --git a/coins/monero/src/ringct/bulletproofs/plus/weighted_inner_product.rs b/coins/monero/ringct/bulletproofs/src/plus/weighted_inner_product.rs similarity index 75% rename from coins/monero/src/ringct/bulletproofs/plus/weighted_inner_product.rs rename to coins/monero/ringct/bulletproofs/src/plus/weighted_inner_product.rs index 7cb9a4df..e446a79b 100644 --- a/coins/monero/src/ringct/bulletproofs/plus/weighted_inner_product.rs +++ b/coins/monero/ringct/bulletproofs/src/plus/weighted_inner_product.rs @@ -1,24 +1,21 @@ use std_shims::vec::Vec; use rand_core::{RngCore, CryptoRng}; - use zeroize::{Zeroize, ZeroizeOnDrop}; -use multiexp::{BatchVerifier, multiexp, multiexp_vartime}; -use group::{ - ff::{Field, PrimeField}, - GroupEncoding, -}; -use dalek_ff_group::{Scalar, EdwardsPoint}; +use curve25519_dalek::{scalar::Scalar, edwards::EdwardsPoint}; -use crate::ringct::bulletproofs::plus::{ - ScalarVector, PointVector, GeneratorsList, Generators, padded_pow_of_2, transcript::*, +use monero_primitives::{INV_EIGHT, keccak256_to_scalar}; +use crate::{ + core::{multiexp, multiexp_vartime}, + batch_verifier::BulletproofsPlusBatchVerifier, + plus::{ScalarVector, PointVector, GeneratorsList, BpPlusGenerators, padded_pow_of_2}, }; // Figure 1 of the Bulletproofs+ paper #[derive(Clone, Debug)] pub(crate) struct WipStatement { - generators: Generators, + generators: BpPlusGenerators, P: EdwardsPoint, y: ScalarVector, } @@ -68,7 +65,7 @@ pub(crate) struct WipProof { } impl WipStatement { - pub(crate) fn new(generators: Generators, P: EdwardsPoint, y: Scalar) -> Self { + pub(crate) fn new(generators: BpPlusGenerators, P: EdwardsPoint, y: Scalar) -> Self { debug_assert_eq!(generators.len(), padded_pow_of_2(generators.len())); // y ** n @@ -82,16 +79,26 @@ impl WipStatement { } fn transcript_L_R(transcript: &mut Scalar, L: EdwardsPoint, R: EdwardsPoint) -> Scalar { - let e = hash_to_scalar( - &[transcript.to_repr().as_ref(), L.to_bytes().as_ref(), R.to_bytes().as_ref()].concat(), + let e = keccak256_to_scalar( + [ + transcript.to_bytes().as_ref(), + L.compress().to_bytes().as_ref(), + R.compress().to_bytes().as_ref(), + ] + .concat(), ); *transcript = e; e } fn transcript_A_B(transcript: &mut Scalar, A: EdwardsPoint, B: EdwardsPoint) -> Scalar { - let e = hash_to_scalar( - &[transcript.to_repr().as_ref(), A.to_bytes().as_ref(), B.to_bytes().as_ref()].concat(), + let e = keccak256_to_scalar( + [ + transcript.to_bytes().as_ref(), + A.compress().to_bytes().as_ref(), + B.compress().to_bytes().as_ref(), + ] + .concat(), ); *transcript = e; e @@ -119,7 +126,7 @@ impl WipStatement { debug_assert_eq!(g_bold1.len(), h_bold1.len()); let e = Self::transcript_L_R(transcript, L, R); - let inv_e = e.invert().unwrap(); + let inv_e = e.invert(); // This vartime is safe as all of these arguments are public let mut new_g_bold = Vec::with_capacity(g_bold1.len()); @@ -133,8 +140,8 @@ impl WipStatement { new_h_bold.push(multiexp_vartime(&[(e, h_bold.0), (inv_e, h_bold.1)])); } - let e_square = e.square(); - let inv_e_square = inv_e.square(); + let e_square = e * e; + let inv_e_square = inv_e * inv_e; (e, inv_e, e_square, inv_e_square, PointVector(new_g_bold), PointVector(new_h_bold)) } @@ -177,7 +184,7 @@ impl WipStatement { // Sanity check since if the above failed to populate, it'd be critical for product in &products { - debug_assert!(!bool::from(product.is_zero())); + debug_assert!(*product != Scalar::ZERO); } } @@ -197,12 +204,12 @@ impl WipStatement { if generators.len() != witness.a.len() { return None; } - let (g, h) = (Generators::g(), Generators::h()); + let (g, h) = (BpPlusGenerators::g(), BpPlusGenerators::h()); let mut g_bold = vec![]; let mut h_bold = vec![]; for i in 0 .. generators.len() { - g_bold.push(generators.generator(GeneratorsList::GBold1, i)); - h_bold.push(generators.generator(GeneratorsList::HBold1, i)); + g_bold.push(generators.generator(GeneratorsList::GBold, i)); + h_bold.push(generators.generator(GeneratorsList::HBold, i)); } let mut g_bold = PointVector(g_bold); let mut h_bold = PointVector(h_bold); @@ -261,7 +268,7 @@ impl WipStatement { let c_r = (a2.clone() * y_n_hat).weighted_inner_product(&b1, &y); // TODO: Calculate these with a batch inversion - let y_inv_n_hat = y_n_hat.invert().unwrap(); + let y_inv_n_hat = y_n_hat.invert(); let mut L_terms = (a1.clone() * y_inv_n_hat) .0 @@ -271,7 +278,7 @@ impl WipStatement { .collect::>(); L_terms.push((c_l, g)); L_terms.push((d_l, h)); - let L = multiexp(&L_terms) * Scalar(crate::INV_EIGHT()); + let L = multiexp(&L_terms) * INV_EIGHT(); L_vec.push(L); L_terms.zeroize(); @@ -283,7 +290,7 @@ impl WipStatement { .collect::>(); R_terms.push((c_r, g)); R_terms.push((d_r, h)); - let R = multiexp(&R_terms) * Scalar(crate::INV_EIGHT()); + let R = multiexp(&R_terms) * INV_EIGHT(); R_vec.push(R); R_terms.zeroize(); @@ -316,33 +323,32 @@ impl WipStatement { let mut A_terms = vec![(r, g_bold[0]), (s, h_bold[0]), ((ry * b[0]) + (s * y[0] * a[0]), g), (delta, h)]; - let A = multiexp(&A_terms) * Scalar(crate::INV_EIGHT()); + let A = multiexp(&A_terms) * INV_EIGHT(); A_terms.zeroize(); let mut B_terms = vec![(ry * s, g), (eta, h)]; - let B = multiexp(&B_terms) * Scalar(crate::INV_EIGHT()); + let B = multiexp(&B_terms) * INV_EIGHT(); B_terms.zeroize(); let e = Self::transcript_A_B(&mut transcript, A, B); let r_answer = r + (a[0] * e); let s_answer = s + (b[0] * e); - let delta_answer = eta + (delta * e) + (alpha * e.square()); + let delta_answer = eta + (delta * e) + (alpha * (e * e)); Some(WipProof { L: L_vec, R: R_vec, A, B, r_answer, s_answer, delta_answer }) } - pub(crate) fn verify( + pub(crate) fn verify( self, rng: &mut R, - verifier: &mut BatchVerifier, - id: Id, + verifier: &mut BulletproofsPlusBatchVerifier, mut transcript: Scalar, mut proof: WipProof, ) -> bool { - let WipStatement { generators, P, y } = self; + let verifier_weight = Scalar::random(rng); - let (g, h) = (Generators::g(), Generators::h()); + let WipStatement { generators, P, y } = self; // Verify the L/R lengths { @@ -359,7 +365,7 @@ impl WipStatement { } let inv_y = { - let inv_y = y[0].invert().unwrap(); + let inv_y = y[0].invert(); let mut res = Vec::with_capacity(y.len()); res.push(inv_y); while res.len() < y.len() { @@ -368,51 +374,49 @@ impl WipStatement { res }; - let mut P_terms = vec![(Scalar::ONE, P)]; - P_terms.reserve(6 + (2 * generators.len()) + proof.L.len()); + let mut e_is = Vec::with_capacity(proof.L.len()); + for (L, R) in proof.L.iter_mut().zip(proof.R.iter_mut()) { + e_is.push(Self::transcript_L_R(&mut transcript, *L, *R)); + *L = L.mul_by_cofactor(); + *R = R.mul_by_cofactor(); + } + + let e = Self::transcript_A_B(&mut transcript, proof.A, proof.B); + proof.A = proof.A.mul_by_cofactor(); + proof.B = proof.B.mul_by_cofactor(); + let neg_e_square = verifier_weight * -(e * e); + + verifier.0.other.push((neg_e_square, P)); let mut challenges = Vec::with_capacity(proof.L.len()); let product_cache = { - let mut es = Vec::with_capacity(proof.L.len()); - for (L, R) in proof.L.iter_mut().zip(proof.R.iter_mut()) { - es.push(Self::transcript_L_R(&mut transcript, *L, *R)); - *L = L.mul_by_cofactor(); - *R = R.mul_by_cofactor(); - } + let mut inv_e_is = e_is.clone(); + Scalar::batch_invert(&mut inv_e_is); - let mut inv_es = es.clone(); - let mut scratch = vec![Scalar::ZERO; es.len()]; - group::ff::BatchInverter::invert_with_external_scratch(&mut inv_es, &mut scratch); - drop(scratch); - - debug_assert_eq!(es.len(), inv_es.len()); - debug_assert_eq!(es.len(), proof.L.len()); - debug_assert_eq!(es.len(), proof.R.len()); - for ((e, inv_e), (L, R)) in - es.drain(..).zip(inv_es.drain(..)).zip(proof.L.iter().zip(proof.R.iter())) + debug_assert_eq!(e_is.len(), inv_e_is.len()); + debug_assert_eq!(e_is.len(), proof.L.len()); + debug_assert_eq!(e_is.len(), proof.R.len()); + for ((e_i, inv_e_i), (L, R)) in + e_is.drain(..).zip(inv_e_is.drain(..)).zip(proof.L.iter().zip(proof.R.iter())) { - debug_assert_eq!(e.invert().unwrap(), inv_e); + debug_assert_eq!(e_i.invert(), inv_e_i); - challenges.push((e, inv_e)); + challenges.push((e_i, inv_e_i)); - let e_square = e.square(); - let inv_e_square = inv_e.square(); - P_terms.push((e_square, *L)); - P_terms.push((inv_e_square, *R)); + let e_i_square = e_i * e_i; + let inv_e_i_square = inv_e_i * inv_e_i; + verifier.0.other.push((neg_e_square * e_i_square, *L)); + verifier.0.other.push((neg_e_square * inv_e_i_square, *R)); } Self::challenge_products(&challenges) }; - let e = Self::transcript_A_B(&mut transcript, proof.A, proof.B); - proof.A = proof.A.mul_by_cofactor(); - proof.B = proof.B.mul_by_cofactor(); - let neg_e_square = -e.square(); - - let mut multiexp = P_terms; - multiexp.reserve(4 + (2 * generators.len())); - for (scalar, _) in &mut multiexp { - *scalar *= neg_e_square; + while verifier.0.g_bold.len() < generators.len() { + verifier.0.g_bold.push(Scalar::ZERO); + } + while verifier.0.h_bold.len() < generators.len() { + verifier.0.h_bold.push(Scalar::ZERO); } let re = proof.r_answer * e; @@ -421,23 +425,18 @@ impl WipStatement { if i > 0 { scalar *= inv_y[i - 1]; } - multiexp.push((scalar, generators.generator(GeneratorsList::GBold1, i))); + verifier.0.g_bold[i] += verifier_weight * scalar; } let se = proof.s_answer * e; for i in 0 .. generators.len() { - multiexp.push(( - se * product_cache[product_cache.len() - 1 - i], - generators.generator(GeneratorsList::HBold1, i), - )); + verifier.0.h_bold[i] += verifier_weight * (se * product_cache[product_cache.len() - 1 - i]); } - multiexp.push((-e, proof.A)); - multiexp.push((proof.r_answer * y[0] * proof.s_answer, g)); - multiexp.push((proof.delta_answer, h)); - multiexp.push((-Scalar::ONE, proof.B)); - - verifier.queue(rng, id, multiexp); + verifier.0.other.push((verifier_weight * -e, proof.A)); + verifier.0.g += verifier_weight * (proof.r_answer * y[0] * proof.s_answer); + verifier.0.h += verifier_weight * proof.delta_answer; + verifier.0.other.push((-verifier_weight, proof.B)); true } diff --git a/coins/monero/src/ringct/bulletproofs/scalar_vector.rs b/coins/monero/ringct/bulletproofs/src/scalar_vector.rs similarity index 97% rename from coins/monero/src/ringct/bulletproofs/scalar_vector.rs rename to coins/monero/ringct/bulletproofs/src/scalar_vector.rs index e6288367..a8354c4d 100644 --- a/coins/monero/src/ringct/bulletproofs/scalar_vector.rs +++ b/coins/monero/ringct/bulletproofs/src/scalar_vector.rs @@ -6,9 +6,9 @@ use std_shims::vec::Vec; use zeroize::{Zeroize, ZeroizeOnDrop}; -use group::ff::Field; -use dalek_ff_group::{Scalar, EdwardsPoint}; -use multiexp::multiexp; +use curve25519_dalek::{scalar::Scalar, edwards::EdwardsPoint}; + +use crate::core::multiexp; #[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)] pub(crate) struct ScalarVector(pub(crate) Vec); diff --git a/coins/monero/src/tests/bulletproofs/mod.rs b/coins/monero/ringct/bulletproofs/src/tests/mod.rs similarity index 89% rename from coins/monero/src/tests/bulletproofs/mod.rs rename to coins/monero/ringct/bulletproofs/src/tests/mod.rs index 51c02a11..45a04362 100644 --- a/coins/monero/src/tests/bulletproofs/mod.rs +++ b/coins/monero/ringct/bulletproofs/src/tests/mod.rs @@ -2,14 +2,11 @@ use hex_literal::hex; use rand_core::OsRng; use curve25519_dalek::scalar::Scalar; -use monero_io::decompress_point; -use multiexp::BatchVerifier; -use crate::{ - Commitment, - ringct::bulletproofs::{Bulletproof, original::OriginalStruct}, - wallet::TransactionError, -}; +use monero_io::decompress_point; +use monero_primitives::Commitment; + +use crate::{batch_verifier::BatchVerifier, original::OriginalStruct, Bulletproof, BulletproofError}; mod plus; @@ -24,7 +21,7 @@ fn bulletproofs_vector() { S: point(hex!("e1285960861783574ee2b689ae53622834eb0b035d6943103f960cd23e063fa0")), T1: point(hex!("4ea07735f184ba159d0e0eb662bac8cde3eb7d39f31e567b0fbda3aa23fe5620")), T2: point(hex!("b8390aa4b60b255630d40e592f55ec6b7ab5e3a96bfcdcd6f1cd1d2fc95f441e")), - taux: scalar(hex!("5957dba8ea9afb23d6e81cc048a92f2d502c10c749dc1b2bd148ae8d41ec7107")), + tau_x: scalar(hex!("5957dba8ea9afb23d6e81cc048a92f2d502c10c749dc1b2bd148ae8d41ec7107")), mu: scalar(hex!("923023b234c2e64774b820b4961f7181f6c1dc152c438643e5a25b0bf271bc02")), L: vec![ point(hex!("c45f656316b9ebf9d357fb6a9f85b5f09e0b991dd50a6e0ae9b02de3946c9d99")), @@ -65,7 +62,7 @@ macro_rules! bulletproofs_tests { #[test] fn $name() { // Create Bulletproofs for all possible output quantities - let mut verifier = BatchVerifier::new(16); + let mut verifier = BatchVerifier::new(); for i in 1 ..= 16 { let commitments = (1 ..= i) .map(|i| Commitment::new(Scalar::random(&mut OsRng), u64::try_from(i).unwrap())) @@ -79,9 +76,9 @@ macro_rules! bulletproofs_tests { let commitments = commitments.iter().map(Commitment::calculate).collect::>(); assert!(bp.verify(&mut OsRng, &commitments)); - assert!(bp.batch_verify(&mut OsRng, &mut verifier, i, &commitments)); + assert!(bp.batch_verify(&mut OsRng, &mut verifier, &commitments)); } - assert!(verifier.verify_vartime()); + assert!(verifier.verify()); } #[test] @@ -98,7 +95,7 @@ macro_rules! bulletproofs_tests { Bulletproof::prove(&mut OsRng, &commitments) }) .unwrap_err(), - TransactionError::TooManyOutputs, + BulletproofError::TooManyCommitments, ); } }; diff --git a/coins/monero/src/tests/bulletproofs/plus/aggregate_range_proof.rs b/coins/monero/ringct/bulletproofs/src/tests/plus/aggregate_range_proof.rs similarity index 52% rename from coins/monero/src/tests/bulletproofs/plus/aggregate_range_proof.rs rename to coins/monero/ringct/bulletproofs/src/tests/plus/aggregate_range_proof.rs index 1a0e654d..fc5d429e 100644 --- a/coins/monero/src/tests/bulletproofs/plus/aggregate_range_proof.rs +++ b/coins/monero/ringct/bulletproofs/src/tests/plus/aggregate_range_proof.rs @@ -1,30 +1,28 @@ use rand_core::{RngCore, OsRng}; -use multiexp::BatchVerifier; -use group::ff::Field; -use dalek_ff_group::Scalar; +use curve25519_dalek::Scalar; + +use monero_primitives::Commitment; use crate::{ - Commitment, - ringct::bulletproofs::plus::aggregate_range_proof::{ - AggregateRangeStatement, AggregateRangeWitness, - }, + batch_verifier::BulletproofsPlusBatchVerifier, + plus::aggregate_range_proof::{AggregateRangeStatement, AggregateRangeWitness}, }; #[test] fn test_aggregate_range_proof() { - let mut verifier = BatchVerifier::new(16); + let mut verifier = BulletproofsPlusBatchVerifier::default(); for m in 1 ..= 16 { let mut commitments = vec![]; for _ in 0 .. m { - commitments.push(Commitment::new(*Scalar::random(&mut OsRng), OsRng.next_u64())); + commitments.push(Commitment::new(Scalar::random(&mut OsRng), OsRng.next_u64())); } let commitment_points = commitments.iter().map(Commitment::calculate).collect(); let statement = AggregateRangeStatement::new(commitment_points).unwrap(); let witness = AggregateRangeWitness::new(commitments).unwrap(); let proof = statement.clone().prove(&mut OsRng, &witness).unwrap(); - statement.verify(&mut OsRng, &mut verifier, (), proof); + statement.verify(&mut OsRng, &mut verifier, proof); } - assert!(verifier.verify_vartime()); + assert!(verifier.verify()); } diff --git a/coins/monero/src/tests/bulletproofs/plus/mod.rs b/coins/monero/ringct/bulletproofs/src/tests/plus/mod.rs similarity index 100% rename from coins/monero/src/tests/bulletproofs/plus/mod.rs rename to coins/monero/ringct/bulletproofs/src/tests/plus/mod.rs diff --git a/coins/monero/src/tests/bulletproofs/plus/weighted_inner_product.rs b/coins/monero/ringct/bulletproofs/src/tests/plus/weighted_inner_product.rs similarity index 66% rename from coins/monero/src/tests/bulletproofs/plus/weighted_inner_product.rs rename to coins/monero/ringct/bulletproofs/src/tests/plus/weighted_inner_product.rs index b0890cf8..eaa00cd3 100644 --- a/coins/monero/src/tests/bulletproofs/plus/weighted_inner_product.rs +++ b/coins/monero/ringct/bulletproofs/src/tests/plus/weighted_inner_product.rs @@ -2,13 +2,14 @@ use rand_core::OsRng; -use multiexp::BatchVerifier; -use group::{ff::Field, Group}; -use dalek_ff_group::{Scalar, EdwardsPoint}; +use curve25519_dalek::{traits::Identity, scalar::Scalar, edwards::EdwardsPoint}; -use crate::ringct::bulletproofs::plus::{ - ScalarVector, PointVector, GeneratorsList, Generators, - weighted_inner_product::{WipStatement, WipWitness}, +use crate::{ + batch_verifier::BulletproofsPlusBatchVerifier, + plus::{ + ScalarVector, PointVector, GeneratorsList, BpPlusGenerators, + weighted_inner_product::{WipStatement, WipWitness}, + }, }; #[test] @@ -17,33 +18,33 @@ fn test_zero_weighted_inner_product() { let P = EdwardsPoint::identity(); let y = Scalar::random(&mut OsRng); - let generators = Generators::new().reduce(1); + let generators = BpPlusGenerators::new().reduce(1); let statement = WipStatement::new(generators, P, y); let witness = WipWitness::new(ScalarVector::new(1), ScalarVector::new(1), Scalar::ZERO).unwrap(); let transcript = Scalar::random(&mut OsRng); let proof = statement.clone().prove(&mut OsRng, transcript, &witness).unwrap(); - let mut verifier = BatchVerifier::new(1); - statement.verify(&mut OsRng, &mut verifier, (), transcript, proof); - assert!(verifier.verify_vartime()); + let mut verifier = BulletproofsPlusBatchVerifier::default(); + statement.verify(&mut OsRng, &mut verifier, transcript, proof); + assert!(verifier.verify()); } #[test] fn test_weighted_inner_product() { // P = sum(g_bold * a, h_bold * b, g * (a * y * b), h * alpha) - let mut verifier = BatchVerifier::new(6); - let generators = Generators::new(); + let mut verifier = BulletproofsPlusBatchVerifier::default(); + let generators = BpPlusGenerators::new(); for i in [1, 2, 4, 8, 16, 32] { let generators = generators.reduce(i); - let g = Generators::g(); - let h = Generators::h(); + let g = BpPlusGenerators::g(); + let h = BpPlusGenerators::h(); assert_eq!(generators.len(), i); let mut g_bold = vec![]; let mut h_bold = vec![]; for i in 0 .. i { - g_bold.push(generators.generator(GeneratorsList::GBold1, i)); - h_bold.push(generators.generator(GeneratorsList::HBold1, i)); + g_bold.push(generators.generator(GeneratorsList::GBold, i)); + h_bold.push(generators.generator(GeneratorsList::HBold, i)); } let g_bold = PointVector(g_bold); let h_bold = PointVector(h_bold); @@ -75,7 +76,7 @@ fn test_weighted_inner_product() { let transcript = Scalar::random(&mut OsRng); let proof = statement.clone().prove(&mut OsRng, transcript, &witness).unwrap(); - statement.verify(&mut OsRng, &mut verifier, (), transcript, proof); + statement.verify(&mut OsRng, &mut verifier, transcript, proof); } - assert!(verifier.verify_vartime()); + assert!(verifier.verify()); } diff --git a/coins/monero/ringct/clsag/Cargo.toml b/coins/monero/ringct/clsag/Cargo.toml index 4aa528c1..59aaff57 100644 --- a/coins/monero/ringct/clsag/Cargo.toml +++ b/coins/monero/ringct/clsag/Cargo.toml @@ -52,8 +52,6 @@ std = [ "zeroize/std", "subtle/std", - "curve25519-dalek/precomputed-tables", - "rand_chacha?/std", "transcript?/std", "group?/alloc", diff --git a/coins/monero/src/bin/reserialize_chain.rs b/coins/monero/src/bin/reserialize_chain.rs index 244e2e1f..b20ee0dd 100644 --- a/coins/monero/src/bin/reserialize_chain.rs +++ b/coins/monero/src/bin/reserialize_chain.rs @@ -4,14 +4,12 @@ mod binaries { pub(crate) use curve25519_dalek::{scalar::Scalar, edwards::EdwardsPoint}; - pub(crate) use multiexp::BatchVerifier; - pub(crate) use serde::Deserialize; pub(crate) use serde_json::json; pub(crate) use monero_serai::{ Commitment, - ringct::RctPrunable, + ringct::{RctPrunable, bulletproofs::batch_verifier::BatchVerifier}, transaction::{Input, Transaction}, block::Block, rpc::{RpcError, Rpc, HttpRpc}, @@ -95,7 +93,7 @@ mod binaries { all_txs.extend(txs.txs); } - let mut batch = BatchVerifier::new(block.txs.len()); + let mut batch = BatchVerifier::new(); for (tx_hash, tx_res) in block.txs.into_iter().zip(all_txs) { assert_eq!( tx_res.tx_hash, @@ -135,7 +133,6 @@ mod binaries { assert!(bulletproofs.batch_verify( &mut rand_core::OsRng, &mut batch, - (), &tx.rct_signatures.base.commitments )); } @@ -143,7 +140,6 @@ mod binaries { assert!(bulletproofs.batch_verify( &mut rand_core::OsRng, &mut batch, - (), &tx.rct_signatures.base.commitments )); @@ -234,7 +230,7 @@ mod binaries { } } } - assert!(batch.verify_vartime()); + assert!(batch.verify()); } println!("Deserialized, hashed, and reserialized {block_i} with {txs_len} TXs"); diff --git a/coins/monero/src/ringct/bulletproofs/plus/transcript.rs b/coins/monero/src/ringct/bulletproofs/plus/transcript.rs deleted file mode 100644 index 0e61fdc6..00000000 --- a/coins/monero/src/ringct/bulletproofs/plus/transcript.rs +++ /dev/null @@ -1,25 +0,0 @@ -use std_shims::{sync::OnceLock, vec::Vec}; - -use curve25519_dalek::EdwardsPoint; -use dalek_ff_group::Scalar; - -use monero_generators::{hash_to_point as raw_hash_to_point}; -use crate::{hash, hash_to_scalar as dalek_hash}; - -// Monero starts BP+ transcripts with the following constant. -static TRANSCRIPT_CELL: OnceLock<[u8; 32]> = OnceLock::new(); -pub(crate) fn TRANSCRIPT() -> [u8; 32] { - // Why this uses a hash_to_point is completely unknown. - *TRANSCRIPT_CELL - .get_or_init(|| raw_hash_to_point(hash(b"bulletproof_plus_transcript")).compress().to_bytes()) -} - -pub(crate) fn hash_to_scalar(data: &[u8]) -> Scalar { - Scalar(dalek_hash(data)) -} - -pub(crate) fn initial_transcript(commitments: core::slice::Iter<'_, EdwardsPoint>) -> Scalar { - let commitments_hash = - hash_to_scalar(&commitments.flat_map(|V| V.compress().to_bytes()).collect::>()); - hash_to_scalar(&[TRANSCRIPT().as_ref(), &commitments_hash.to_bytes()].concat()) -} diff --git a/coins/monero/src/ringct/mod.rs b/coins/monero/src/ringct/mod.rs index 9413d48c..8256d76f 100644 --- a/coins/monero/src/ringct/mod.rs +++ b/coins/monero/src/ringct/mod.rs @@ -18,7 +18,7 @@ pub use monero_clsag as clsag; /// BorromeanRange struct, along with verifying functionality. pub mod borromean; /// Bulletproofs(+) structs, along with proving and verifying functionality. -pub mod bulletproofs; +pub use monero_bulletproofs as bulletproofs; use crate::{ Protocol, diff --git a/coins/monero/src/tests/mod.rs b/coins/monero/src/tests/mod.rs index 8a5b7ab4..99da24ac 100644 --- a/coins/monero/src/tests/mod.rs +++ b/coins/monero/src/tests/mod.rs @@ -1,5 +1,4 @@ mod unreduced_scalar; -mod bulletproofs; mod address; mod seed; mod extra; diff --git a/coins/monero/src/wallet/send/mod.rs b/coins/monero/src/wallet/send/mod.rs index 50a126b3..4f0f7eae 100644 --- a/coins/monero/src/wallet/send/mod.rs +++ b/coins/monero/src/wallet/send/mod.rs @@ -33,7 +33,7 @@ use crate::{ ringct::{ generate_key_image, clsag::{ClsagError, ClsagContext, Clsag}, - bulletproofs::{MAX_OUTPUTS, Bulletproof}, + bulletproofs::{MAX_COMMITMENTS, Bulletproof}, RctBase, RctPrunable, RctSignatures, }, transaction::{Input, Output, Timelock, TransactionPrefix, Transaction}, @@ -504,7 +504,7 @@ impl SignableTransaction { let out_amount = payments.iter().map(|payment| payment.1).sum::(); let outputs = payments.len() + usize::from(change.address.is_some()); - if outputs > MAX_OUTPUTS { + if outputs > MAX_COMMITMENTS { Err(TransactionError::TooManyOutputs)?; } @@ -803,7 +803,7 @@ impl SignableTransaction { let commitments = outputs.iter().map(|output| output.commitment.clone()).collect::>(); let sum = commitments.iter().map(|commitment| commitment.mask).sum(); - // Safe due to the constructor checking MAX_OUTPUTS + // Safe due to the constructor checking MAX_COMMITMENTS let bp = if self.protocol.bp_plus() { Bulletproof::prove_plus(rng, commitments.clone()).unwrap() } else { diff --git a/tests/no-std/Cargo.toml b/tests/no-std/Cargo.toml index d224f1e1..daad3577 100644 --- a/tests/no-std/Cargo.toml +++ b/tests/no-std/Cargo.toml @@ -39,4 +39,5 @@ monero-io = { path = "../../coins/monero/io", default-features = false } monero-generators = { path = "../../coins/monero/generators", default-features = false } monero-primitives = { path = "../../coins/monero/primitives", default-features = false } monero-clsag = { path = "../../coins/monero/ringct/clsag", default-features = false } +monero-bulletproofs = { path = "../../coins/monero/ringct/bulletproofs", default-features = false } monero-serai = { path = "../../coins/monero", default-features = false }