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.
This commit is contained in:
Luke Parker
2024-06-15 17:00:11 -04:00
parent 798ffc9b28
commit f7c13fd1ca
32 changed files with 716 additions and 502 deletions

18
Cargo.lock generated
View File

@@ -4751,6 +4751,22 @@ dependencies = [
"zeroize", "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]] [[package]]
name = "monero-clsag" name = "monero-clsag"
version = "0.1.0" version = "0.1.0"
@@ -4819,6 +4835,7 @@ dependencies = [
"hex", "hex",
"hex-literal", "hex-literal",
"modular-frost", "modular-frost",
"monero-bulletproofs",
"monero-clsag", "monero-clsag",
"monero-generators", "monero-generators",
"monero-io", "monero-io",
@@ -8079,6 +8096,7 @@ dependencies = [
"dleq", "dleq",
"flexible-transcript", "flexible-transcript",
"minimal-ed448", "minimal-ed448",
"monero-bulletproofs",
"monero-clsag", "monero-clsag",
"monero-generators", "monero-generators",
"monero-io", "monero-io",

View File

@@ -47,6 +47,7 @@ members = [
"coins/monero/generators", "coins/monero/generators",
"coins/monero/primitives", "coins/monero/primitives",
"coins/monero/ringct/clsag", "coins/monero/ringct/clsag",
"coins/monero/ringct/bulletproofs",
"coins/monero", "coins/monero",
"message-queue", "message-queue",

View File

@@ -34,7 +34,7 @@ rand_distr = { version = "0.4", default-features = false }
sha3 = { version = "0.10", default-features = false } sha3 = { version = "0.10", default-features = false }
pbkdf2 = { version = "0.12", features = ["simple"], 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 # Used for the hash to curve, along with the more complicated proofs
group = { version = "0.13", default-features = false } 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-generators = { path = "generators", version = "0.4", default-features = false }
monero-primitives = { path = "primitives", version = "0.1", 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-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-literal = "0.4"
hex = { version = "0.4", default-features = false, features = ["alloc"] } hex = { version = "0.4", default-features = false, features = ["alloc"] }
@@ -96,6 +97,7 @@ std = [
"monero-generators/std", "monero-generators/std",
"monero-primitives/std", "monero-primitives/std",
"monero-clsag/std", "monero-clsag/std",
"monero-bulletproofs/std",
"hex/std", "hex/std",
"serde/std", "serde/std",
@@ -104,6 +106,7 @@ std = [
"base58-monero/std", "base58-monero/std",
] ]
compile-time-generators = ["curve25519-dalek/precomputed-tables", "monero-bulletproofs/compile-time-generators"]
http-rpc = ["digest_auth", "simple-request", "tokio"] http-rpc = ["digest_auth", "simple-request", "tokio"]
multisig = ["transcript", "frost", "monero-clsag/multisig", "std"] multisig = ["transcript", "frost", "monero-clsag/multisig", "std"]
binaries = ["tokio/rt-multi-thread", "tokio/macros", "http-rpc"] binaries = ["tokio/rt-multi-thread", "tokio/macros", "http-rpc"]

View File

@@ -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<Generators> = 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");
}

View File

@@ -37,7 +37,6 @@ std = [
"subtle/std", "subtle/std",
"sha3/std", "sha3/std",
"curve25519-dalek/precomputed-tables",
"group/alloc", "group/alloc",
"dalek-ff-group/std", "dalek-ff-group/std",

View File

@@ -6,10 +6,7 @@ use std_shims::{sync::OnceLock, vec::Vec};
use sha3::{Digest, Keccak256}; use sha3::{Digest, Keccak256};
use curve25519_dalek::edwards::{EdwardsPoint as DalekPoint}; use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, edwards::EdwardsPoint};
use group::{Group, GroupEncoding};
use dalek_ff_group::EdwardsPoint;
use monero_io::{write_varint, decompress_point}; use monero_io::{write_varint, decompress_point};
@@ -23,24 +20,26 @@ fn keccak256(data: &[u8]) -> [u8; 32] {
Keccak256::digest(data).into() Keccak256::digest(data).into()
} }
static H_CELL: OnceLock<DalekPoint> = OnceLock::new(); static H_CELL: OnceLock<EdwardsPoint> = OnceLock::new();
/// Monero's `H` generator. /// Monero's `H` generator.
/// ///
/// Contrary to convention (`G` for values, `H` for randomness), `H` is used by Monero for amounts /// Contrary to convention (`G` for values, `H` for randomness), `H` is used by Monero for amounts
/// within Pedersen commitments. /// within Pedersen commitments.
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn H() -> DalekPoint { pub fn H() -> EdwardsPoint {
*H_CELL.get_or_init(|| { *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. /// Monero's `H` generator, multiplied by 2**i for i in 1 ..= 64.
/// ///
/// This table is useful when working with amounts, which are u64s. /// This table is useful when working with amounts, which are u64s.
#[allow(non_snake_case)] #[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(|| { H_POW_2_CELL.get_or_init(|| {
let mut res = [H(); 64]; let mut res = [H(); 64];
for i in 1 .. 64 { for i in 1 .. 64 {
@@ -79,11 +78,11 @@ pub fn bulletproofs_generators(dst: &'static [u8]) -> Generators {
let mut even = preimage.clone(); let mut even = preimage.clone();
write_varint(&i, &mut even).unwrap(); 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(); let mut odd = preimage.clone();
write_varint(&(i + 1), &mut odd).unwrap(); 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 res
} }

View File

@@ -35,7 +35,6 @@ std = [
"zeroize/std", "zeroize/std",
"sha3/std", "sha3/std",
"curve25519-dalek/precomputed-tables",
"monero-generators/std", "monero-generators/std",
] ]

View File

@@ -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 <lukeparker5132@gmail.com>"]
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"]

View File

@@ -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.

View File

@@ -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.

View File

@@ -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<Generators> = 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<Generators> = 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");
}

View File

@@ -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<Scalar>,
pub(crate) h_bold: Vec<Scalar>,
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()
}
}

View File

@@ -1,42 +1,44 @@
use std_shims::{vec::Vec, sync::OnceLock}; use std_shims::{vec::Vec, sync::OnceLock};
use rand_core::{RngCore, CryptoRng}; use rand_core::{RngCore, CryptoRng};
use subtle::{Choice, ConditionallySelectable}; use subtle::{Choice, ConditionallySelectable};
use curve25519_dalek::edwards::EdwardsPoint as DalekPoint; use curve25519_dalek::{
constants::ED25519_BASEPOINT_TABLE,
use group::{ff::Field, Group}; traits::{MultiscalarMul, VartimeMultiscalarMul},
use dalek_ff_group::{Scalar, EdwardsPoint}; scalar::Scalar,
edwards::EdwardsPoint,
use multiexp::multiexp as multiexp_const; };
pub(crate) use monero_generators::Generators; 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::scalar_vector::*;
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))
}
// Components common between variants // Components common between variants
// TODO: Move to generators? primitives?
pub(crate) const MAX_M: usize = 16; pub(crate) const MAX_M: usize = 16;
pub(crate) const LOG_N: usize = 6; // 2 << 6 == N pub(crate) const LOG_N: usize = 6; // 2 << 6 == N
pub(crate) const N: usize = 64; pub(crate) const N: usize = 64;
pub(crate) fn prove_multiexp(pairs: &[(Scalar, EdwardsPoint)]) -> EdwardsPoint { pub(crate) fn multiexp(pairs: &[(Scalar, EdwardsPoint)]) -> EdwardsPoint {
multiexp_const(pairs) * INV_EIGHT() 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( pub(crate) fn vector_exponent(
@@ -52,7 +54,7 @@ pub(crate) fn hash_cache(cache: &mut Scalar, mash: &[[u8; 32]]) -> Scalar {
let slice = let slice =
&[cache.to_bytes().as_ref(), mash.iter().copied().flatten().collect::<Vec<_>>().as_ref()] &[cache.to_bytes().as_ref(), mash.iter().copied().flatten().collect::<Vec<_>>().as_ref()]
.concat(); .concat();
*cache = hash_to_scalar(slice); *cache = keccak256_to_scalar(slice);
*cache *cache
} }
@@ -88,11 +90,11 @@ pub(crate) fn bit_decompose(commitments: &[Commitment]) -> (ScalarVector, Scalar
(aL, aR) (aL, aR)
} }
pub(crate) fn hash_commitments<C: IntoIterator<Item = DalekPoint>>( pub(crate) fn hash_commitments<C: IntoIterator<Item = EdwardsPoint>>(
commitments: C, commitments: C,
) -> (Scalar, Vec<EdwardsPoint>) { ) -> (Scalar, Vec<EdwardsPoint>) {
let V = commitments.into_iter().map(|c| EdwardsPoint(c) * INV_EIGHT()).collect::<Vec<_>>(); let V = commitments.into_iter().map(|c| c * INV_EIGHT()).collect::<Vec<_>>();
(hash_to_scalar(&V.iter().flat_map(|V| V.compress().to_bytes()).collect::<Vec<_>>()), V) (keccak256_to_scalar(V.iter().flat_map(|V| V.compress().to_bytes()).collect::<Vec<_>>()), V)
} }
pub(crate) fn alpha_rho<R: RngCore + CryptoRng>( pub(crate) fn alpha_rho<R: RngCore + CryptoRng>(
@@ -102,7 +104,7 @@ pub(crate) fn alpha_rho<R: RngCore + CryptoRng>(
aR: &ScalarVector, aR: &ScalarVector,
) -> (Scalar, EdwardsPoint) { ) -> (Scalar, EdwardsPoint) {
let ar = Scalar::random(rng); 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( pub(crate) fn LR_statements(
@@ -144,7 +146,7 @@ pub(crate) fn challenge_products(w: &[Scalar], winv: &[Scalar]) -> Vec<Scalar> {
// Sanity check as if the above failed to populate, it'd be critical // Sanity check as if the above failed to populate, it'd be critical
for w in &products { for w in &products {
debug_assert!(!bool::from(w.is_zero())); debug_assert!(*w != Scalar::ZERO);
} }
products products

View File

@@ -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)] #![allow(non_snake_case)]
use std_shims::{ use std_shims::{
@@ -6,25 +9,39 @@ use std_shims::{
}; };
use rand_core::{RngCore, CryptoRng}; use rand_core::{RngCore, CryptoRng};
use zeroize::Zeroizing;
use zeroize::{Zeroize, Zeroizing};
use curve25519_dalek::edwards::EdwardsPoint; 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 scalar_vector;
pub(crate) mod core; 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; pub(crate) mod original;
use self::original::OriginalStruct; use crate::original::OriginalStruct;
pub(crate) mod plus; 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+. /// Bulletproof enum, encapsulating both Bulletproofs and Bulletproofs+.
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
@@ -45,7 +62,7 @@ impl Bulletproof {
// https://github.com/monero-project/monero/blob/94e67bf96bbc010241f29ada6abc89f49a81759c/ // https://github.com/monero-project/monero/blob/94e67bf96bbc010241f29ada6abc89f49a81759c/
// src/cryptonote_basic/cryptonote_format_utils.cpp#L106-L124 // 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)] #[allow(non_snake_case)]
let mut LR_len = 0; let mut LR_len = 0;
let mut n_padded_outputs = 1; let mut n_padded_outputs = 1;
@@ -66,7 +83,7 @@ impl Bulletproof {
(bp_clawback, LR_len) (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)] #[allow(non_snake_case)]
let (bp_clawback, LR_len) = Bulletproof::calculate_bp_clawback(plus, outputs); let (bp_clawback, LR_len) = Bulletproof::calculate_bp_clawback(plus, outputs);
32 * (Bulletproof::bp_fields(plus) + (2 * LR_len)) + 2 + bp_clawback 32 * (Bulletproof::bp_fields(plus) + (2 * LR_len)) + 2 + bp_clawback
@@ -76,12 +93,12 @@ impl Bulletproof {
pub fn prove<R: RngCore + CryptoRng>( pub fn prove<R: RngCore + CryptoRng>(
rng: &mut R, rng: &mut R,
outputs: &[Commitment], outputs: &[Commitment],
) -> Result<Bulletproof, TransactionError> { ) -> Result<Bulletproof, BulletproofError> {
if outputs.is_empty() { if outputs.is_empty() {
Err(TransactionError::NoOutputs)?; Err(BulletproofError::NoCommitments)?;
} }
if outputs.len() > MAX_OUTPUTS { if outputs.len() > MAX_COMMITMENTS {
Err(TransactionError::TooManyOutputs)?; Err(BulletproofError::TooManyCommitments)?;
} }
Ok(Bulletproof::Original(OriginalStruct::prove(rng, outputs))) Ok(Bulletproof::Original(OriginalStruct::prove(rng, outputs)))
} }
@@ -90,12 +107,12 @@ impl Bulletproof {
pub fn prove_plus<R: RngCore + CryptoRng>( pub fn prove_plus<R: RngCore + CryptoRng>(
rng: &mut R, rng: &mut R,
outputs: Vec<Commitment>, outputs: Vec<Commitment>,
) -> Result<Bulletproof, TransactionError> { ) -> Result<Bulletproof, BulletproofError> {
if outputs.is_empty() { if outputs.is_empty() {
Err(TransactionError::NoOutputs)?; Err(BulletproofError::NoCommitments)?;
} }
if outputs.len() > MAX_OUTPUTS { if outputs.len() > MAX_COMMITMENTS {
Err(TransactionError::TooManyOutputs)?; Err(BulletproofError::TooManyCommitments)?;
} }
Ok(Bulletproof::Plus( Ok(Bulletproof::Plus(
AggregateRangeStatement::new(outputs.iter().map(Commitment::calculate).collect()) AggregateRangeStatement::new(outputs.iter().map(Commitment::calculate).collect())
@@ -111,14 +128,14 @@ impl Bulletproof {
match self { match self {
Bulletproof::Original(bp) => bp.verify(rng, commitments), Bulletproof::Original(bp) => bp.verify(rng, commitments),
Bulletproof::Plus(bp) => { Bulletproof::Plus(bp) => {
let mut verifier = BatchVerifier::new(1); let mut verifier = BulletproofsPlusBatchVerifier(InternalBatchVerifier::new());
let Some(statement) = AggregateRangeStatement::new(commitments.to_vec()) else { let Some(statement) = AggregateRangeStatement::new(commitments.to_vec()) else {
return false; return false;
}; };
if !statement.verify(rng, &mut verifier, (), bp.clone()) { if !statement.verify(rng, &mut verifier, bp.clone()) {
return false; return false;
} }
verifier.verify_vartime() verifier.verify()
} }
} }
} }
@@ -129,20 +146,19 @@ impl Bulletproof {
/// state. /// state.
/// Returns true if the Bulletproof is sane, regardless of their validity. /// Returns true if the Bulletproof is sane, regardless of their validity.
#[must_use] #[must_use]
pub fn batch_verify<ID: Copy + Zeroize, R: RngCore + CryptoRng>( pub fn batch_verify<R: RngCore + CryptoRng>(
&self, &self,
rng: &mut R, rng: &mut R,
verifier: &mut BatchVerifier<ID, dalek_ff_group::EdwardsPoint>, verifier: &mut BatchVerifier,
id: ID,
commitments: &[EdwardsPoint], commitments: &[EdwardsPoint],
) -> bool { ) -> bool {
match self { 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) => { Bulletproof::Plus(bp) => {
let Some(statement) = AggregateRangeStatement::new(commitments.to_vec()) else { let Some(statement) = AggregateRangeStatement::new(commitments.to_vec()) else {
return false; return false;
}; };
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.S, w)?;
write_point(&bp.T1, w)?; write_point(&bp.T1, w)?;
write_point(&bp.T2, w)?; write_point(&bp.T2, w)?;
write_scalar(&bp.taux, w)?; write_scalar(&bp.tau_x, w)?;
write_scalar(&bp.mu, w)?; write_scalar(&bp.mu, w)?;
specific_write_vec(&bp.L, w)?; specific_write_vec(&bp.L, w)?;
specific_write_vec(&bp.R, w)?; specific_write_vec(&bp.R, w)?;
@@ -168,19 +184,19 @@ impl Bulletproof {
} }
Bulletproof::Plus(bp) => { Bulletproof::Plus(bp) => {
write_point(&bp.A.0, w)?; write_point(&bp.A, w)?;
write_point(&bp.wip.A.0, w)?; write_point(&bp.wip.A, w)?;
write_point(&bp.wip.B.0, w)?; write_point(&bp.wip.B, w)?;
write_scalar(&bp.wip.r_answer.0, w)?; write_scalar(&bp.wip.r_answer, w)?;
write_scalar(&bp.wip.s_answer.0, w)?; write_scalar(&bp.wip.s_answer, w)?;
write_scalar(&bp.wip.delta_answer.0, w)?; write_scalar(&bp.wip.delta_answer, w)?;
specific_write_vec(&bp.wip.L.iter().copied().map(|L| L.0).collect::<Vec<_>>(), w)?; specific_write_vec(&bp.wip.L, w)?;
specific_write_vec(&bp.wip.R.iter().copied().map(|R| R.0).collect::<Vec<_>>(), w) specific_write_vec(&bp.wip.R, w)
} }
} }
} }
pub(crate) fn signature_write<W: Write>(&self, w: &mut W) -> io::Result<()> { pub fn signature_write<W: Write>(&self, w: &mut W) -> io::Result<()> {
self.write_core(w, |points, w| write_raw_vec(write_point, points, w)) self.write_core(w, |points, w| write_raw_vec(write_point, points, w))
} }
@@ -203,7 +219,7 @@ impl Bulletproof {
S: read_point(r)?, S: read_point(r)?,
T1: read_point(r)?, T1: read_point(r)?,
T2: read_point(r)?, T2: read_point(r)?,
taux: read_scalar(r)?, tau_x: read_scalar(r)?,
mu: read_scalar(r)?, mu: read_scalar(r)?,
L: read_vec(read_point, r)?, L: read_vec(read_point, r)?,
R: read_vec(read_point, r)?, R: read_vec(read_point, r)?,
@@ -215,18 +231,16 @@ impl Bulletproof {
/// Read a Bulletproof+. /// Read a Bulletproof+.
pub fn read_plus<R: Read>(r: &mut R) -> io::Result<Bulletproof> { pub fn read_plus<R: Read>(r: &mut R) -> io::Result<Bulletproof> {
use dalek_ff_group::{Scalar as DfgScalar, EdwardsPoint as DfgPoint};
Ok(Bulletproof::Plus(AggregateRangeProof { Ok(Bulletproof::Plus(AggregateRangeProof {
A: DfgPoint(read_point(r)?), A: read_point(r)?,
wip: WipProof { wip: WipProof {
A: DfgPoint(read_point(r)?), A: read_point(r)?,
B: DfgPoint(read_point(r)?), B: read_point(r)?,
r_answer: DfgScalar(read_scalar(r)?), r_answer: read_scalar(r)?,
s_answer: DfgScalar(read_scalar(r)?), s_answer: read_scalar(r)?,
delta_answer: DfgScalar(read_scalar(r)?), delta_answer: read_scalar(r)?,
L: read_vec(read_point, r)?.into_iter().map(DfgPoint).collect(), L: read_vec(read_point, r)?.into_iter().collect(),
R: read_vec(read_point, r)?.into_iter().map(DfgPoint).collect(), R: read_vec(read_point, r)?.into_iter().collect(),
}, },
})) }))
} }

View File

@@ -1,17 +1,17 @@
use std_shims::{vec::Vec, sync::OnceLock}; use std_shims::{vec::Vec, sync::OnceLock};
use rand_core::{RngCore, CryptoRng}; use rand_core::{RngCore, CryptoRng};
use zeroize::Zeroize; 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 monero_generators::H;
use dalek_ff_group::{ED25519_BASEPOINT_POINT as G, Scalar, EdwardsPoint}; use monero_primitives::{INV_EIGHT, Commitment, keccak256_to_scalar};
use multiexp::{BatchVerifier, multiexp}; use crate::{
core::*,
use crate::{Commitment, ringct::bulletproofs::core::*}; batch_verifier::{InternalBatchVerifier, BulletproofsBatchVerifier},
};
include!(concat!(env!("OUT_DIR"), "/generators.rs")); include!(concat!(env!("OUT_DIR"), "/generators.rs"));
@@ -36,17 +36,17 @@ pub(crate) fn hadamard_fold(
/// Internal structure representing a Bulletproof. /// Internal structure representing a Bulletproof.
#[derive(Clone, PartialEq, Eq, Debug)] #[derive(Clone, PartialEq, Eq, Debug)]
pub struct OriginalStruct { pub struct OriginalStruct {
pub(crate) A: DalekPoint, pub(crate) A: EdwardsPoint,
pub(crate) S: DalekPoint, pub(crate) S: EdwardsPoint,
pub(crate) T1: DalekPoint, pub(crate) T1: EdwardsPoint,
pub(crate) T2: DalekPoint, pub(crate) T2: EdwardsPoint,
pub(crate) taux: DalekScalar, pub(crate) tau_x: Scalar,
pub(crate) mu: DalekScalar, pub(crate) mu: Scalar,
pub(crate) L: Vec<DalekPoint>, pub(crate) L: Vec<EdwardsPoint>,
pub(crate) R: Vec<DalekPoint>, pub(crate) R: Vec<EdwardsPoint>,
pub(crate) a: DalekScalar, pub(crate) a: Scalar,
pub(crate) b: DalekScalar, pub(crate) b: Scalar,
pub(crate) t: DalekScalar, pub(crate) t: Scalar,
} }
impl OriginalStruct { impl OriginalStruct {
@@ -68,7 +68,7 @@ impl OriginalStruct {
let (mut rho, S) = alpha_rho(&mut *rng, generators, &sL, &sR); 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 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 z = cache;
let l0 = aL - z; let l0 = aL - z;
@@ -86,32 +86,32 @@ impl OriginalStruct {
let r0 = ((aR + z) * &yMN) + &ScalarVector(zero_twos); let r0 = ((aR + z) * &yMN) + &ScalarVector(zero_twos);
let r1 = yMN * &sR; 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 t1 = l0.clone().inner_product(&r1) + r0.clone().inner_product(&l1);
let t2 = l1.clone().inner_product(&r1); let t2 = l1.clone().inner_product(&r1);
let mut tau1 = Scalar::random(&mut *rng); let mut tau1 = Scalar::random(&mut *rng);
let mut tau2 = Scalar::random(&mut *rng); let mut tau2 = Scalar::random(&mut *rng);
let T1 = prove_multiexp(&[(t1, H()), (tau1, EdwardsPoint::generator())]); let T1 = multiexp(&[(t1, H()), (tau1, ED25519_BASEPOINT_POINT)]) * INV_EIGHT();
let T2 = prove_multiexp(&[(t2, H()), (tau2, EdwardsPoint::generator())]); let T2 = multiexp(&[(t2, H()), (tau2, ED25519_BASEPOINT_POINT)]) * INV_EIGHT();
let x = let x =
hash_cache(&mut cache, &[z.to_bytes(), T1.compress().to_bytes(), T2.compress().to_bytes()]); 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(); tau1.zeroize();
tau2.zeroize(); tau2.zeroize();
(T1, T2, x, taux) (T1, T2, x, tau_x)
}; };
let mu = (x * rho) + alpha; let mu = (x * rho) + alpha;
alpha.zeroize(); alpha.zeroize();
rho.zeroize(); rho.zeroize();
for (i, gamma) in commitments.iter().map(|c| Scalar(c.mask)).enumerate() { for (i, gamma) in commitments.iter().map(|c| c.mask).enumerate() {
taux += zpow[i + 2] * gamma; tau_x += zpow[i + 2] * gamma;
} }
let l = l0 + &(l1 * x); let l = l0 + &(l1 * x);
@@ -120,12 +120,12 @@ impl OriginalStruct {
let t = l.clone().inner_product(&r); let t = l.clone().inner_product(&r);
let x_ip = 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 a = l;
let mut b = r; let mut b = r;
let yinv = y.invert().unwrap(); let yinv = y.invert();
let yinvpow = ScalarVector::powers(yinv, MN); let yinvpow = ScalarVector::powers(yinv, MN);
let mut G_proof = generators.G[.. a.len()].to_vec(); 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 (G_L, G_R) = G_proof.split_at(aL.len());
let (H_L, H_R) = H_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 L_i = multiexp(&LR_statements(&aL, G_R, &bR, H_L, cL, U)) * INV_EIGHT();
let R_i = prove_multiexp(&LR_statements(&aR, G_L, &bL, H_R, cR, U)); let R_i = multiexp(&LR_statements(&aR, G_L, &bL, H_R, cR, U)) * INV_EIGHT();
L.push(L_i); L.push(L_i);
R.push(R_i); R.push(R_i);
let w = hash_cache(&mut cache, &[L_i.compress().to_bytes(), R_i.compress().to_bytes()]); 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); a = (aL * w) + &(aR * winv);
b = (bL * winv) + &(bR * w); b = (bL * winv) + &(bR * w);
@@ -163,30 +163,17 @@ impl OriginalStruct {
} }
} }
let res = OriginalStruct { let res = OriginalStruct { A, S, T1, T2, tau_x, mu, L, R, a: a[0], b: b[0], t };
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,
};
debug_assert!(res.verify(rng, &commitments_points)); debug_assert!(res.verify(rng, &commitments_points));
res res
} }
#[must_use] #[must_use]
fn verify_core<ID: Copy + Zeroize, R: RngCore + CryptoRng>( fn verify_core<R: RngCore + CryptoRng>(
&self, &self,
rng: &mut R, rng: &mut R,
verifier: &mut BatchVerifier<ID, EdwardsPoint>, verifier: &mut BulletproofsBatchVerifier,
id: ID, commitments: &[EdwardsPoint],
commitments: &[DalekPoint],
) -> bool { ) -> bool {
// Verify commitments are valid // Verify commitments are valid
if commitments.is_empty() || (commitments.len() > MAX_M) { if commitments.is_empty() || (commitments.len() > MAX_M) {
@@ -207,7 +194,7 @@ impl OriginalStruct {
let (mut cache, commitments) = hash_commitments(commitments.iter().copied()); 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 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; cache = z;
let x = hash_cache( let x = hash_cache(
@@ -217,18 +204,18 @@ impl OriginalStruct {
let x_ip = hash_cache( let x_ip = hash_cache(
&mut 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 w = Vec::with_capacity(logMN);
let mut winv = Vec::with_capacity(logMN); let mut winv = Vec::with_capacity(logMN);
for (L, R) in self.L.iter().zip(&self.R) { for (L, R) in self.L.iter().zip(&self.R) {
w.push(hash_cache(&mut cache, &[L.compress().to_bytes(), R.compress().to_bytes()])); 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 // 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::<Vec<_>>(); let L = self.L.iter().map(normalize).collect::<Vec<_>>();
let R = self.R.iter().map(normalize).collect::<Vec<_>>(); let R = self.R.iter().map(normalize).collect::<Vec<_>>();
@@ -240,59 +227,70 @@ impl OriginalStruct {
let commitments = commitments.iter().map(EdwardsPoint::mul_by_cofactor).collect::<Vec<_>>(); let commitments = commitments.iter().map(EdwardsPoint::mul_by_cofactor).collect::<Vec<_>>();
// Verify it // Verify it
let mut proof = Vec::with_capacity(4 + commitments.len());
let zpow = ScalarVector::powers(z, M + 3); 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 verifier_weight = Scalar::random(rng);
let yinv = y.invert().unwrap();
let yinvpow = ScalarVector::powers(yinv, MN);
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(); verifier.0.g -= verifier_weight * self.tau_x;
for i in 0 .. MN {
let g = (Scalar(self.a) * w_cache[i]) + z;
proof.push((-g, generators.G[i]));
let mut h = Scalar(self.b) * yinvpow[i] * w_cache[(!i) & (MN - 1)]; for (j, commitment) in commitments.iter().enumerate() {
h -= ((zpow[(i / N) + 2] * TWO_N()[i % N]) + (z * ypow[i])) * yinvpow[i]; verifier.0.other.push((verifier_weight * zpow[j + 2], *commitment));
proof.push((-h, generators.H[i])); }
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 true
} }
@@ -300,24 +298,23 @@ impl OriginalStruct {
pub(crate) fn verify<R: RngCore + CryptoRng>( pub(crate) fn verify<R: RngCore + CryptoRng>(
&self, &self,
rng: &mut R, rng: &mut R,
commitments: &[DalekPoint], commitments: &[EdwardsPoint],
) -> bool { ) -> bool {
let mut verifier = BatchVerifier::new(1); let mut verifier = BulletproofsBatchVerifier(InternalBatchVerifier::new());
if self.verify_core(rng, &mut verifier, (), commitments) { if self.verify_core(rng, &mut verifier, commitments) {
verifier.verify_vartime() verifier.verify()
} else { } else {
false false
} }
} }
#[must_use] #[must_use]
pub(crate) fn batch_verify<ID: Copy + Zeroize, R: RngCore + CryptoRng>( pub(crate) fn batch_verify<R: RngCore + CryptoRng>(
&self, &self,
rng: &mut R, rng: &mut R,
verifier: &mut BatchVerifier<ID, EdwardsPoint>, verifier: &mut BulletproofsBatchVerifier,
id: ID, commitments: &[EdwardsPoint],
commitments: &[DalekPoint],
) -> bool { ) -> bool {
self.verify_core(rng, verifier, id, commitments) self.verify_core(rng, verifier, commitments)
} }
} }

View File

@@ -1,35 +1,28 @@
use std_shims::vec::Vec; use std_shims::vec::Vec;
use rand_core::{RngCore, CryptoRng}; use rand_core::{RngCore, CryptoRng};
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing}; use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
use multiexp::{multiexp, multiexp_vartime, BatchVerifier}; use curve25519_dalek::{traits::Identity, scalar::Scalar, edwards::EdwardsPoint};
use group::{
ff::{Field, PrimeField}, use monero_primitives::{INV_EIGHT, Commitment, keccak256_to_scalar};
Group, GroupEncoding,
};
use curve25519_dalek::EdwardsPoint as DalekPoint;
use dalek_ff_group::{Scalar, EdwardsPoint};
use crate::{ use crate::{
Commitment, batch_verifier::BulletproofsPlusBatchVerifier,
ringct::{ core::{MAX_M, N, multiexp, multiexp_vartime},
bulletproofs::core::{MAX_M, N}, plus::{
bulletproofs::plus::{ ScalarVector, PointVector, GeneratorsList, BpPlusGenerators,
ScalarVector, PointVector, GeneratorsList, Generators, transcript::*,
transcript::*, weighted_inner_product::{WipStatement, WipWitness, WipProof},
weighted_inner_product::{WipStatement, WipWitness, WipProof}, padded_pow_of_2, u64_decompose,
padded_pow_of_2, u64_decompose,
},
}, },
}; };
// Figure 3 of the Bulletproofs+ Paper // Figure 3 of the Bulletproofs+ Paper
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct AggregateRangeStatement { pub(crate) struct AggregateRangeStatement {
generators: Generators, generators: BpPlusGenerators,
V: Vec<DalekPoint>, V: Vec<EdwardsPoint>,
} }
impl Zeroize for AggregateRangeStatement { impl Zeroize for AggregateRangeStatement {
@@ -58,18 +51,29 @@ pub struct AggregateRangeProof {
pub(crate) wip: WipProof, 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 { impl AggregateRangeStatement {
pub(crate) fn new(V: Vec<DalekPoint>) -> Option<Self> { pub(crate) fn new(V: Vec<EdwardsPoint>) -> Option<Self> {
if V.is_empty() || (V.len() > MAX_M) { if V.is_empty() || (V.len() > MAX_M) {
return None; return None;
} }
Some(Self { generators: Generators::new(), V }) Some(Self { generators: BpPlusGenerators::new(), V })
} }
fn transcript_A(transcript: &mut Scalar, A: EdwardsPoint) -> (Scalar, Scalar) { 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 y = keccak256_to_scalar(
let z = hash_to_scalar(y.to_bytes().as_ref()); [transcript.to_bytes().as_ref(), A.compress().to_bytes().as_ref()].concat(),
);
let z = keccak256_to_scalar(y.to_bytes().as_ref());
*transcript = z; *transcript = z;
(y, z) (y, z)
} }
@@ -88,10 +92,10 @@ impl AggregateRangeStatement {
fn compute_A_hat( fn compute_A_hat(
mut V: PointVector, mut V: PointVector,
generators: &Generators, generators: &BpPlusGenerators,
transcript: &mut Scalar, transcript: &mut Scalar,
mut A: EdwardsPoint, mut A: EdwardsPoint,
) -> (Scalar, ScalarVector, Scalar, Scalar, ScalarVector, EdwardsPoint) { ) -> AHatComputation {
let (y, z) = Self::transcript_A(transcript, A); let (y, z) = Self::transcript_A(transcript, A);
A = A.mul_by_cofactor(); A = A.mul_by_cofactor();
@@ -133,23 +137,23 @@ impl AggregateRangeStatement {
let neg_z = -z; let neg_z = -z;
let mut A_terms = Vec::with_capacity((generators.len() * 2) + 2); 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() { 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((neg_z, generators.generator(GeneratorsList::GBold, i)));
A_terms.push((*d_y_z, generators.generator(GeneratorsList::HBold1, 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_mn_plus_one, commitment_accum));
A_terms.push(( A_terms.push((
((y_pows * z) - (d.sum() * y_mn_plus_one * z) - (y_pows * z.square())), ((y_pows * z) - (d.sum() * y_mn_plus_one * z) - (y_pows * (z * z))),
Generators::g(), BpPlusGenerators::g(),
)); ));
( AHatComputation {
y, y,
d_descending_y_plus_z, d_descending_y_plus_z,
y_mn_plus_one, y_mn_plus_one,
z, z,
ScalarVector(z_pow), z_pow: ScalarVector(z_pow),
A + multiexp_vartime(&A_terms), A_hat: A + multiexp_vartime(&A_terms),
) }
} }
pub(crate) fn prove<R: RngCore + CryptoRng>( pub(crate) fn prove<R: RngCore + CryptoRng>(
@@ -175,9 +179,9 @@ impl AggregateRangeStatement {
// Commitments aren't transmitted INV_EIGHT though, so this multiplies by INV_EIGHT to enable // Commitments aren't transmitted INV_EIGHT though, so this multiplies by INV_EIGHT to enable
// clearing its cofactor without mutating the value // clearing its cofactor without mutating the value
// For some reason, these values are transcripted * INV_EIGHT, not as transmitted // For some reason, these values are transcripted * INV_EIGHT, not as transmitted
let V = V.into_iter().map(|V| V * crate::INV_EIGHT()).collect::<Vec<_>>(); let V = V.into_iter().map(|V| V * INV_EIGHT()).collect::<Vec<_>>();
let mut transcript = initial_transcript(V.iter()); let mut transcript = initial_transcript(V.iter());
let mut V = V.into_iter().map(|V| EdwardsPoint(V.mul_by_cofactor())).collect::<Vec<_>>(); let mut V = V.iter().map(EdwardsPoint::mul_by_cofactor).collect::<Vec<_>>();
// Pad V // Pad V
while V.len() < padded_pow_of_2(V.len()) { 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); let mut A_terms = Vec::with_capacity((generators.len() * 2) + 1);
for (i, a_l) in a_l.0.iter().enumerate() { 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() { 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); let mut A = multiexp(&A_terms);
A_terms.zeroize(); A_terms.zeroize();
// Multiply by INV_EIGHT per earlier commentary // 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); Self::compute_A_hat(PointVector(V), &generators, &mut transcript, A);
let a_l = a_l - z; let a_l = a_l - z;
let a_r = a_r + &d_descending_y_plus_z; let a_r = a_r + &d_descending_y_plus_z;
let mut alpha = alpha; let mut alpha = alpha;
for j in 1 ..= witness.0.len() { 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 { Some(AggregateRangeProof {
@@ -235,25 +239,22 @@ impl AggregateRangeStatement {
}) })
} }
pub(crate) fn verify<Id: Copy + Zeroize, R: RngCore + CryptoRng>( pub(crate) fn verify<R: RngCore + CryptoRng>(
self, self,
rng: &mut R, rng: &mut R,
verifier: &mut BatchVerifier<Id, EdwardsPoint>, verifier: &mut BulletproofsPlusBatchVerifier,
id: Id,
proof: AggregateRangeProof, proof: AggregateRangeProof,
) -> bool { ) -> bool {
let Self { generators, V } = self; let Self { generators, V } = self;
let V = V.into_iter().map(|V| V * crate::INV_EIGHT()).collect::<Vec<_>>(); let V = V.into_iter().map(|V| V * INV_EIGHT()).collect::<Vec<_>>();
let mut transcript = initial_transcript(V.iter()); let mut transcript = initial_transcript(V.iter());
// With the torsion clear, wrap it into a EdwardsPoint from dalek-ff-group let V = V.iter().map(EdwardsPoint::mul_by_cofactor).collect::<Vec<_>>();
// (which is prime-order)
let V = V.into_iter().map(|V| EdwardsPoint(V.mul_by_cofactor())).collect::<Vec<_>>();
let generators = generators.reduce(V.len() * N); 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); 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)
} }
} }

View File

@@ -1,9 +1,13 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use group::Group; use std_shims::sync::OnceLock;
use dalek_ff_group::{Scalar, EdwardsPoint};
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; mod point_vector;
pub(crate) use point_vector::PointVector; 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)] #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub(crate) enum GeneratorsList { pub(crate) enum GeneratorsList {
GBold1, GBold,
HBold1, HBold,
} }
// TODO: Table these // TODO: Table these
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct Generators { pub(crate) struct BpPlusGenerators {
g_bold1: &'static [EdwardsPoint], g_bold: &'static [EdwardsPoint],
h_bold1: &'static [EdwardsPoint], h_bold: &'static [EdwardsPoint],
} }
mod generators { include!(concat!(env!("OUT_DIR"), "/generators_plus.rs"));
use std_shims::sync::OnceLock;
use monero_generators::Generators;
include!(concat!(env!("OUT_DIR"), "/generators_plus.rs"));
}
impl Generators { impl BpPlusGenerators {
#[allow(clippy::new_without_default)] #[allow(clippy::new_without_default)]
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
let gens = generators::GENERATORS(); let gens = GENERATORS();
Generators { g_bold1: &gens.G, h_bold1: &gens.H } BpPlusGenerators { g_bold: &gens.G, h_bold: &gens.H }
} }
pub(crate) fn len(&self) -> usize { pub(crate) fn len(&self) -> usize {
self.g_bold1.len() self.g_bold.len()
} }
pub(crate) fn g() -> EdwardsPoint { pub(crate) fn g() -> EdwardsPoint {
dalek_ff_group::EdwardsPoint(crate::H()) H()
} }
pub(crate) fn h() -> EdwardsPoint { pub(crate) fn h() -> EdwardsPoint {
EdwardsPoint::generator() ED25519_BASEPOINT_POINT
} }
pub(crate) fn generator(&self, list: GeneratorsList, i: usize) -> EdwardsPoint { pub(crate) fn generator(&self, list: GeneratorsList, i: usize) -> EdwardsPoint {
match list { match list {
GeneratorsList::GBold1 => self.g_bold1[i], GeneratorsList::GBold => self.g_bold[i],
GeneratorsList::HBold1 => self.h_bold1[i], GeneratorsList::HBold => self.h_bold[i],
} }
} }
pub(crate) fn reduce(&self, generators: usize) -> Self { pub(crate) fn reduce(&self, generators: usize) -> Self {
// Round to the nearest power of 2 // Round to the nearest power of 2
let generators = padded_pow_of_2(generators); 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] }
} }
} }

View File

@@ -3,12 +3,10 @@ use std_shims::vec::Vec;
use zeroize::{Zeroize, ZeroizeOnDrop}; use zeroize::{Zeroize, ZeroizeOnDrop};
use dalek_ff_group::EdwardsPoint; use curve25519_dalek::edwards::EdwardsPoint;
#[cfg(test)] #[cfg(test)]
use multiexp::multiexp; use crate::{core::multiexp, plus::ScalarVector};
#[cfg(test)]
use crate::ringct::bulletproofs::plus::ScalarVector;
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)] #[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
pub(crate) struct PointVector(pub(crate) Vec<EdwardsPoint>); pub(crate) struct PointVector(pub(crate) Vec<EdwardsPoint>);

View File

@@ -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::<Vec<_>>());
keccak256_to_scalar([TRANSCRIPT().as_ref(), &commitments_hash.to_bytes()].concat())
}

View File

@@ -1,24 +1,21 @@
use std_shims::vec::Vec; use std_shims::vec::Vec;
use rand_core::{RngCore, CryptoRng}; use rand_core::{RngCore, CryptoRng};
use zeroize::{Zeroize, ZeroizeOnDrop}; use zeroize::{Zeroize, ZeroizeOnDrop};
use multiexp::{BatchVerifier, multiexp, multiexp_vartime}; use curve25519_dalek::{scalar::Scalar, edwards::EdwardsPoint};
use group::{
ff::{Field, PrimeField},
GroupEncoding,
};
use dalek_ff_group::{Scalar, EdwardsPoint};
use crate::ringct::bulletproofs::plus::{ use monero_primitives::{INV_EIGHT, keccak256_to_scalar};
ScalarVector, PointVector, GeneratorsList, Generators, padded_pow_of_2, transcript::*, use crate::{
core::{multiexp, multiexp_vartime},
batch_verifier::BulletproofsPlusBatchVerifier,
plus::{ScalarVector, PointVector, GeneratorsList, BpPlusGenerators, padded_pow_of_2},
}; };
// Figure 1 of the Bulletproofs+ paper // Figure 1 of the Bulletproofs+ paper
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct WipStatement { pub(crate) struct WipStatement {
generators: Generators, generators: BpPlusGenerators,
P: EdwardsPoint, P: EdwardsPoint,
y: ScalarVector, y: ScalarVector,
} }
@@ -68,7 +65,7 @@ pub(crate) struct WipProof {
} }
impl WipStatement { 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())); debug_assert_eq!(generators.len(), padded_pow_of_2(generators.len()));
// y ** n // y ** n
@@ -82,16 +79,26 @@ impl WipStatement {
} }
fn transcript_L_R(transcript: &mut Scalar, L: EdwardsPoint, R: EdwardsPoint) -> Scalar { fn transcript_L_R(transcript: &mut Scalar, L: EdwardsPoint, R: EdwardsPoint) -> Scalar {
let e = hash_to_scalar( let e = keccak256_to_scalar(
&[transcript.to_repr().as_ref(), L.to_bytes().as_ref(), R.to_bytes().as_ref()].concat(), [
transcript.to_bytes().as_ref(),
L.compress().to_bytes().as_ref(),
R.compress().to_bytes().as_ref(),
]
.concat(),
); );
*transcript = e; *transcript = e;
e e
} }
fn transcript_A_B(transcript: &mut Scalar, A: EdwardsPoint, B: EdwardsPoint) -> Scalar { fn transcript_A_B(transcript: &mut Scalar, A: EdwardsPoint, B: EdwardsPoint) -> Scalar {
let e = hash_to_scalar( let e = keccak256_to_scalar(
&[transcript.to_repr().as_ref(), A.to_bytes().as_ref(), B.to_bytes().as_ref()].concat(), [
transcript.to_bytes().as_ref(),
A.compress().to_bytes().as_ref(),
B.compress().to_bytes().as_ref(),
]
.concat(),
); );
*transcript = e; *transcript = e;
e e
@@ -119,7 +126,7 @@ impl WipStatement {
debug_assert_eq!(g_bold1.len(), h_bold1.len()); debug_assert_eq!(g_bold1.len(), h_bold1.len());
let e = Self::transcript_L_R(transcript, L, R); 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 // This vartime is safe as all of these arguments are public
let mut new_g_bold = Vec::with_capacity(g_bold1.len()); 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)])); new_h_bold.push(multiexp_vartime(&[(e, h_bold.0), (inv_e, h_bold.1)]));
} }
let e_square = e.square(); let e_square = e * e;
let inv_e_square = inv_e.square(); let inv_e_square = inv_e * inv_e;
(e, inv_e, e_square, inv_e_square, PointVector(new_g_bold), PointVector(new_h_bold)) (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 // Sanity check since if the above failed to populate, it'd be critical
for product in &products { 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() { if generators.len() != witness.a.len() {
return None; return None;
} }
let (g, h) = (Generators::g(), Generators::h()); let (g, h) = (BpPlusGenerators::g(), BpPlusGenerators::h());
let mut g_bold = vec![]; let mut g_bold = vec![];
let mut h_bold = vec![]; let mut h_bold = vec![];
for i in 0 .. generators.len() { for i in 0 .. generators.len() {
g_bold.push(generators.generator(GeneratorsList::GBold1, i)); g_bold.push(generators.generator(GeneratorsList::GBold, i));
h_bold.push(generators.generator(GeneratorsList::HBold1, i)); h_bold.push(generators.generator(GeneratorsList::HBold, i));
} }
let mut g_bold = PointVector(g_bold); let mut g_bold = PointVector(g_bold);
let mut h_bold = PointVector(h_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); let c_r = (a2.clone() * y_n_hat).weighted_inner_product(&b1, &y);
// TODO: Calculate these with a batch inversion // 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) let mut L_terms = (a1.clone() * y_inv_n_hat)
.0 .0
@@ -271,7 +278,7 @@ impl WipStatement {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
L_terms.push((c_l, g)); L_terms.push((c_l, g));
L_terms.push((d_l, h)); 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_vec.push(L);
L_terms.zeroize(); L_terms.zeroize();
@@ -283,7 +290,7 @@ impl WipStatement {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
R_terms.push((c_r, g)); R_terms.push((c_r, g));
R_terms.push((d_r, h)); 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_vec.push(R);
R_terms.zeroize(); R_terms.zeroize();
@@ -316,33 +323,32 @@ impl WipStatement {
let mut A_terms = let mut A_terms =
vec![(r, g_bold[0]), (s, h_bold[0]), ((ry * b[0]) + (s * y[0] * a[0]), g), (delta, h)]; 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(); A_terms.zeroize();
let mut B_terms = vec![(ry * s, g), (eta, h)]; 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(); B_terms.zeroize();
let e = Self::transcript_A_B(&mut transcript, A, B); let e = Self::transcript_A_B(&mut transcript, A, B);
let r_answer = r + (a[0] * e); let r_answer = r + (a[0] * e);
let s_answer = s + (b[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 }) Some(WipProof { L: L_vec, R: R_vec, A, B, r_answer, s_answer, delta_answer })
} }
pub(crate) fn verify<Id: Copy + Zeroize, R: RngCore + CryptoRng>( pub(crate) fn verify<R: RngCore + CryptoRng>(
self, self,
rng: &mut R, rng: &mut R,
verifier: &mut BatchVerifier<Id, EdwardsPoint>, verifier: &mut BulletproofsPlusBatchVerifier,
id: Id,
mut transcript: Scalar, mut transcript: Scalar,
mut proof: WipProof, mut proof: WipProof,
) -> bool { ) -> 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 // Verify the L/R lengths
{ {
@@ -359,7 +365,7 @@ impl WipStatement {
} }
let inv_y = { let inv_y = {
let inv_y = y[0].invert().unwrap(); let inv_y = y[0].invert();
let mut res = Vec::with_capacity(y.len()); let mut res = Vec::with_capacity(y.len());
res.push(inv_y); res.push(inv_y);
while res.len() < y.len() { while res.len() < y.len() {
@@ -368,51 +374,49 @@ impl WipStatement {
res res
}; };
let mut P_terms = vec![(Scalar::ONE, P)]; let mut e_is = Vec::with_capacity(proof.L.len());
P_terms.reserve(6 + (2 * generators.len()) + 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 mut challenges = Vec::with_capacity(proof.L.len());
let product_cache = { let product_cache = {
let mut es = Vec::with_capacity(proof.L.len()); let mut inv_e_is = e_is.clone();
for (L, R) in proof.L.iter_mut().zip(proof.R.iter_mut()) { Scalar::batch_invert(&mut inv_e_is);
es.push(Self::transcript_L_R(&mut transcript, *L, *R));
*L = L.mul_by_cofactor();
*R = R.mul_by_cofactor();
}
let mut inv_es = es.clone(); debug_assert_eq!(e_is.len(), inv_e_is.len());
let mut scratch = vec![Scalar::ZERO; es.len()]; debug_assert_eq!(e_is.len(), proof.L.len());
group::ff::BatchInverter::invert_with_external_scratch(&mut inv_es, &mut scratch); debug_assert_eq!(e_is.len(), proof.R.len());
drop(scratch); 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!(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.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 e_i_square = e_i * e_i;
let inv_e_square = inv_e.square(); let inv_e_i_square = inv_e_i * inv_e_i;
P_terms.push((e_square, *L)); verifier.0.other.push((neg_e_square * e_i_square, *L));
P_terms.push((inv_e_square, *R)); verifier.0.other.push((neg_e_square * inv_e_i_square, *R));
} }
Self::challenge_products(&challenges) Self::challenge_products(&challenges)
}; };
let e = Self::transcript_A_B(&mut transcript, proof.A, proof.B); while verifier.0.g_bold.len() < generators.len() {
proof.A = proof.A.mul_by_cofactor(); verifier.0.g_bold.push(Scalar::ZERO);
proof.B = proof.B.mul_by_cofactor(); }
let neg_e_square = -e.square(); while verifier.0.h_bold.len() < generators.len() {
verifier.0.h_bold.push(Scalar::ZERO);
let mut multiexp = P_terms;
multiexp.reserve(4 + (2 * generators.len()));
for (scalar, _) in &mut multiexp {
*scalar *= neg_e_square;
} }
let re = proof.r_answer * e; let re = proof.r_answer * e;
@@ -421,23 +425,18 @@ impl WipStatement {
if i > 0 { if i > 0 {
scalar *= inv_y[i - 1]; 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; let se = proof.s_answer * e;
for i in 0 .. generators.len() { for i in 0 .. generators.len() {
multiexp.push(( verifier.0.h_bold[i] += verifier_weight * (se * product_cache[product_cache.len() - 1 - i]);
se * product_cache[product_cache.len() - 1 - i],
generators.generator(GeneratorsList::HBold1, i),
));
} }
multiexp.push((-e, proof.A)); verifier.0.other.push((verifier_weight * -e, proof.A));
multiexp.push((proof.r_answer * y[0] * proof.s_answer, g)); verifier.0.g += verifier_weight * (proof.r_answer * y[0] * proof.s_answer);
multiexp.push((proof.delta_answer, h)); verifier.0.h += verifier_weight * proof.delta_answer;
multiexp.push((-Scalar::ONE, proof.B)); verifier.0.other.push((-verifier_weight, proof.B));
verifier.queue(rng, id, multiexp);
true true
} }

View File

@@ -6,9 +6,9 @@ use std_shims::vec::Vec;
use zeroize::{Zeroize, ZeroizeOnDrop}; use zeroize::{Zeroize, ZeroizeOnDrop};
use group::ff::Field; use curve25519_dalek::{scalar::Scalar, edwards::EdwardsPoint};
use dalek_ff_group::{Scalar, EdwardsPoint};
use multiexp::multiexp; use crate::core::multiexp;
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)] #[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
pub(crate) struct ScalarVector(pub(crate) Vec<Scalar>); pub(crate) struct ScalarVector(pub(crate) Vec<Scalar>);

View File

@@ -2,14 +2,11 @@ use hex_literal::hex;
use rand_core::OsRng; use rand_core::OsRng;
use curve25519_dalek::scalar::Scalar; use curve25519_dalek::scalar::Scalar;
use monero_io::decompress_point;
use multiexp::BatchVerifier;
use crate::{ use monero_io::decompress_point;
Commitment, use monero_primitives::Commitment;
ringct::bulletproofs::{Bulletproof, original::OriginalStruct},
wallet::TransactionError, use crate::{batch_verifier::BatchVerifier, original::OriginalStruct, Bulletproof, BulletproofError};
};
mod plus; mod plus;
@@ -24,7 +21,7 @@ fn bulletproofs_vector() {
S: point(hex!("e1285960861783574ee2b689ae53622834eb0b035d6943103f960cd23e063fa0")), S: point(hex!("e1285960861783574ee2b689ae53622834eb0b035d6943103f960cd23e063fa0")),
T1: point(hex!("4ea07735f184ba159d0e0eb662bac8cde3eb7d39f31e567b0fbda3aa23fe5620")), T1: point(hex!("4ea07735f184ba159d0e0eb662bac8cde3eb7d39f31e567b0fbda3aa23fe5620")),
T2: point(hex!("b8390aa4b60b255630d40e592f55ec6b7ab5e3a96bfcdcd6f1cd1d2fc95f441e")), T2: point(hex!("b8390aa4b60b255630d40e592f55ec6b7ab5e3a96bfcdcd6f1cd1d2fc95f441e")),
taux: scalar(hex!("5957dba8ea9afb23d6e81cc048a92f2d502c10c749dc1b2bd148ae8d41ec7107")), tau_x: scalar(hex!("5957dba8ea9afb23d6e81cc048a92f2d502c10c749dc1b2bd148ae8d41ec7107")),
mu: scalar(hex!("923023b234c2e64774b820b4961f7181f6c1dc152c438643e5a25b0bf271bc02")), mu: scalar(hex!("923023b234c2e64774b820b4961f7181f6c1dc152c438643e5a25b0bf271bc02")),
L: vec![ L: vec![
point(hex!("c45f656316b9ebf9d357fb6a9f85b5f09e0b991dd50a6e0ae9b02de3946c9d99")), point(hex!("c45f656316b9ebf9d357fb6a9f85b5f09e0b991dd50a6e0ae9b02de3946c9d99")),
@@ -65,7 +62,7 @@ macro_rules! bulletproofs_tests {
#[test] #[test]
fn $name() { fn $name() {
// Create Bulletproofs for all possible output quantities // Create Bulletproofs for all possible output quantities
let mut verifier = BatchVerifier::new(16); let mut verifier = BatchVerifier::new();
for i in 1 ..= 16 { for i in 1 ..= 16 {
let commitments = (1 ..= i) let commitments = (1 ..= i)
.map(|i| Commitment::new(Scalar::random(&mut OsRng), u64::try_from(i).unwrap())) .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::<Vec<_>>(); let commitments = commitments.iter().map(Commitment::calculate).collect::<Vec<_>>();
assert!(bp.verify(&mut OsRng, &commitments)); 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] #[test]
@@ -98,7 +95,7 @@ macro_rules! bulletproofs_tests {
Bulletproof::prove(&mut OsRng, &commitments) Bulletproof::prove(&mut OsRng, &commitments)
}) })
.unwrap_err(), .unwrap_err(),
TransactionError::TooManyOutputs, BulletproofError::TooManyCommitments,
); );
} }
}; };

View File

@@ -1,30 +1,28 @@
use rand_core::{RngCore, OsRng}; use rand_core::{RngCore, OsRng};
use multiexp::BatchVerifier; use curve25519_dalek::Scalar;
use group::ff::Field;
use dalek_ff_group::Scalar; use monero_primitives::Commitment;
use crate::{ use crate::{
Commitment, batch_verifier::BulletproofsPlusBatchVerifier,
ringct::bulletproofs::plus::aggregate_range_proof::{ plus::aggregate_range_proof::{AggregateRangeStatement, AggregateRangeWitness},
AggregateRangeStatement, AggregateRangeWitness,
},
}; };
#[test] #[test]
fn test_aggregate_range_proof() { fn test_aggregate_range_proof() {
let mut verifier = BatchVerifier::new(16); let mut verifier = BulletproofsPlusBatchVerifier::default();
for m in 1 ..= 16 { for m in 1 ..= 16 {
let mut commitments = vec![]; let mut commitments = vec![];
for _ in 0 .. m { 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 commitment_points = commitments.iter().map(Commitment::calculate).collect();
let statement = AggregateRangeStatement::new(commitment_points).unwrap(); let statement = AggregateRangeStatement::new(commitment_points).unwrap();
let witness = AggregateRangeWitness::new(commitments).unwrap(); let witness = AggregateRangeWitness::new(commitments).unwrap();
let proof = statement.clone().prove(&mut OsRng, &witness).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());
} }

View File

@@ -2,13 +2,14 @@
use rand_core::OsRng; use rand_core::OsRng;
use multiexp::BatchVerifier; use curve25519_dalek::{traits::Identity, scalar::Scalar, edwards::EdwardsPoint};
use group::{ff::Field, Group};
use dalek_ff_group::{Scalar, EdwardsPoint};
use crate::ringct::bulletproofs::plus::{ use crate::{
ScalarVector, PointVector, GeneratorsList, Generators, batch_verifier::BulletproofsPlusBatchVerifier,
weighted_inner_product::{WipStatement, WipWitness}, plus::{
ScalarVector, PointVector, GeneratorsList, BpPlusGenerators,
weighted_inner_product::{WipStatement, WipWitness},
},
}; };
#[test] #[test]
@@ -17,33 +18,33 @@ fn test_zero_weighted_inner_product() {
let P = EdwardsPoint::identity(); let P = EdwardsPoint::identity();
let y = Scalar::random(&mut OsRng); 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 statement = WipStatement::new(generators, P, y);
let witness = WipWitness::new(ScalarVector::new(1), ScalarVector::new(1), Scalar::ZERO).unwrap(); let witness = WipWitness::new(ScalarVector::new(1), ScalarVector::new(1), Scalar::ZERO).unwrap();
let transcript = Scalar::random(&mut OsRng); let transcript = Scalar::random(&mut OsRng);
let proof = statement.clone().prove(&mut OsRng, transcript, &witness).unwrap(); let proof = statement.clone().prove(&mut OsRng, transcript, &witness).unwrap();
let mut verifier = BatchVerifier::new(1); let mut verifier = BulletproofsPlusBatchVerifier::default();
statement.verify(&mut OsRng, &mut verifier, (), transcript, proof); statement.verify(&mut OsRng, &mut verifier, transcript, proof);
assert!(verifier.verify_vartime()); assert!(verifier.verify());
} }
#[test] #[test]
fn test_weighted_inner_product() { fn test_weighted_inner_product() {
// P = sum(g_bold * a, h_bold * b, g * (a * y * b), h * alpha) // P = sum(g_bold * a, h_bold * b, g * (a * y * b), h * alpha)
let mut verifier = BatchVerifier::new(6); let mut verifier = BulletproofsPlusBatchVerifier::default();
let generators = Generators::new(); let generators = BpPlusGenerators::new();
for i in [1, 2, 4, 8, 16, 32] { for i in [1, 2, 4, 8, 16, 32] {
let generators = generators.reduce(i); let generators = generators.reduce(i);
let g = Generators::g(); let g = BpPlusGenerators::g();
let h = Generators::h(); let h = BpPlusGenerators::h();
assert_eq!(generators.len(), i); assert_eq!(generators.len(), i);
let mut g_bold = vec![]; let mut g_bold = vec![];
let mut h_bold = vec![]; let mut h_bold = vec![];
for i in 0 .. i { for i in 0 .. i {
g_bold.push(generators.generator(GeneratorsList::GBold1, i)); g_bold.push(generators.generator(GeneratorsList::GBold, i));
h_bold.push(generators.generator(GeneratorsList::HBold1, i)); h_bold.push(generators.generator(GeneratorsList::HBold, i));
} }
let g_bold = PointVector(g_bold); let g_bold = PointVector(g_bold);
let h_bold = PointVector(h_bold); let h_bold = PointVector(h_bold);
@@ -75,7 +76,7 @@ fn test_weighted_inner_product() {
let transcript = Scalar::random(&mut OsRng); let transcript = Scalar::random(&mut OsRng);
let proof = statement.clone().prove(&mut OsRng, transcript, &witness).unwrap(); 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());
} }

View File

@@ -52,8 +52,6 @@ std = [
"zeroize/std", "zeroize/std",
"subtle/std", "subtle/std",
"curve25519-dalek/precomputed-tables",
"rand_chacha?/std", "rand_chacha?/std",
"transcript?/std", "transcript?/std",
"group?/alloc", "group?/alloc",

View File

@@ -4,14 +4,12 @@ mod binaries {
pub(crate) use curve25519_dalek::{scalar::Scalar, edwards::EdwardsPoint}; pub(crate) use curve25519_dalek::{scalar::Scalar, edwards::EdwardsPoint};
pub(crate) use multiexp::BatchVerifier;
pub(crate) use serde::Deserialize; pub(crate) use serde::Deserialize;
pub(crate) use serde_json::json; pub(crate) use serde_json::json;
pub(crate) use monero_serai::{ pub(crate) use monero_serai::{
Commitment, Commitment,
ringct::RctPrunable, ringct::{RctPrunable, bulletproofs::batch_verifier::BatchVerifier},
transaction::{Input, Transaction}, transaction::{Input, Transaction},
block::Block, block::Block,
rpc::{RpcError, Rpc, HttpRpc}, rpc::{RpcError, Rpc, HttpRpc},
@@ -95,7 +93,7 @@ mod binaries {
all_txs.extend(txs.txs); 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) { for (tx_hash, tx_res) in block.txs.into_iter().zip(all_txs) {
assert_eq!( assert_eq!(
tx_res.tx_hash, tx_res.tx_hash,
@@ -135,7 +133,6 @@ mod binaries {
assert!(bulletproofs.batch_verify( assert!(bulletproofs.batch_verify(
&mut rand_core::OsRng, &mut rand_core::OsRng,
&mut batch, &mut batch,
(),
&tx.rct_signatures.base.commitments &tx.rct_signatures.base.commitments
)); ));
} }
@@ -143,7 +140,6 @@ mod binaries {
assert!(bulletproofs.batch_verify( assert!(bulletproofs.batch_verify(
&mut rand_core::OsRng, &mut rand_core::OsRng,
&mut batch, &mut batch,
(),
&tx.rct_signatures.base.commitments &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"); println!("Deserialized, hashed, and reserialized {block_i} with {txs_len} TXs");

View File

@@ -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::<Vec<_>>());
hash_to_scalar(&[TRANSCRIPT().as_ref(), &commitments_hash.to_bytes()].concat())
}

View File

@@ -18,7 +18,7 @@ pub use monero_clsag as clsag;
/// BorromeanRange struct, along with verifying functionality. /// BorromeanRange struct, along with verifying functionality.
pub mod borromean; pub mod borromean;
/// Bulletproofs(+) structs, along with proving and verifying functionality. /// Bulletproofs(+) structs, along with proving and verifying functionality.
pub mod bulletproofs; pub use monero_bulletproofs as bulletproofs;
use crate::{ use crate::{
Protocol, Protocol,

View File

@@ -1,5 +1,4 @@
mod unreduced_scalar; mod unreduced_scalar;
mod bulletproofs;
mod address; mod address;
mod seed; mod seed;
mod extra; mod extra;

View File

@@ -33,7 +33,7 @@ use crate::{
ringct::{ ringct::{
generate_key_image, generate_key_image,
clsag::{ClsagError, ClsagContext, Clsag}, clsag::{ClsagError, ClsagContext, Clsag},
bulletproofs::{MAX_OUTPUTS, Bulletproof}, bulletproofs::{MAX_COMMITMENTS, Bulletproof},
RctBase, RctPrunable, RctSignatures, RctBase, RctPrunable, RctSignatures,
}, },
transaction::{Input, Output, Timelock, TransactionPrefix, Transaction}, transaction::{Input, Output, Timelock, TransactionPrefix, Transaction},
@@ -504,7 +504,7 @@ impl SignableTransaction {
let out_amount = payments.iter().map(|payment| payment.1).sum::<u64>(); let out_amount = payments.iter().map(|payment| payment.1).sum::<u64>();
let outputs = payments.len() + usize::from(change.address.is_some()); let outputs = payments.len() + usize::from(change.address.is_some());
if outputs > MAX_OUTPUTS { if outputs > MAX_COMMITMENTS {
Err(TransactionError::TooManyOutputs)?; Err(TransactionError::TooManyOutputs)?;
} }
@@ -803,7 +803,7 @@ impl SignableTransaction {
let commitments = outputs.iter().map(|output| output.commitment.clone()).collect::<Vec<_>>(); let commitments = outputs.iter().map(|output| output.commitment.clone()).collect::<Vec<_>>();
let sum = commitments.iter().map(|commitment| commitment.mask).sum(); let sum = commitments.iter().map(|commitment| commitment.mask).sum();
// Safe due to the constructor checking MAX_OUTPUTS // Safe due to the constructor checking MAX_COMMITMENTS
let bp = if self.protocol.bp_plus() { let bp = if self.protocol.bp_plus() {
Bulletproof::prove_plus(rng, commitments.clone()).unwrap() Bulletproof::prove_plus(rng, commitments.clone()).unwrap()
} else { } else {

View File

@@ -39,4 +39,5 @@ monero-io = { path = "../../coins/monero/io", default-features = false }
monero-generators = { path = "../../coins/monero/generators", default-features = false } monero-generators = { path = "../../coins/monero/generators", default-features = false }
monero-primitives = { path = "../../coins/monero/primitives", default-features = false } monero-primitives = { path = "../../coins/monero/primitives", default-features = false }
monero-clsag = { path = "../../coins/monero/ringct/clsag", 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 } monero-serai = { path = "../../coins/monero", default-features = false }