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",
]
[[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",

View File

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

View File

@@ -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"]

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",
"sha3/std",
"curve25519-dalek/precomputed-tables",
"group/alloc",
"dalek-ff-group/std",

View File

@@ -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<DalekPoint> = OnceLock::new();
static H_CELL: OnceLock<EdwardsPoint> = 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
}

View File

@@ -35,7 +35,6 @@ std = [
"zeroize/std",
"sha3/std",
"curve25519-dalek/precomputed-tables",
"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 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::<Vec<_>>().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<C: IntoIterator<Item = DalekPoint>>(
pub(crate) fn hash_commitments<C: IntoIterator<Item = EdwardsPoint>>(
commitments: C,
) -> (Scalar, Vec<EdwardsPoint>) {
let V = commitments.into_iter().map(|c| EdwardsPoint(c) * INV_EIGHT()).collect::<Vec<_>>();
(hash_to_scalar(&V.iter().flat_map(|V| V.compress().to_bytes()).collect::<Vec<_>>()), V)
let V = commitments.into_iter().map(|c| c * INV_EIGHT()).collect::<Vec<_>>();
(keccak256_to_scalar(V.iter().flat_map(|V| V.compress().to_bytes()).collect::<Vec<_>>()), V)
}
pub(crate) fn alpha_rho<R: RngCore + CryptoRng>(
@@ -102,7 +104,7 @@ pub(crate) fn alpha_rho<R: RngCore + CryptoRng>(
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<Scalar> {
// 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

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)]
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<R: RngCore + CryptoRng>(
rng: &mut R,
outputs: &[Commitment],
) -> Result<Bulletproof, TransactionError> {
) -> Result<Bulletproof, BulletproofError> {
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<R: RngCore + CryptoRng>(
rng: &mut R,
outputs: Vec<Commitment>,
) -> Result<Bulletproof, TransactionError> {
) -> Result<Bulletproof, BulletproofError> {
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<ID: Copy + Zeroize, R: RngCore + CryptoRng>(
pub fn batch_verify<R: RngCore + CryptoRng>(
&self,
rng: &mut R,
verifier: &mut BatchVerifier<ID, dalek_ff_group::EdwardsPoint>,
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::<Vec<_>>(), w)?;
specific_write_vec(&bp.wip.R.iter().copied().map(|R| R.0).collect::<Vec<_>>(), 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<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))
}
@@ -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: Read>(r: &mut R) -> io::Result<Bulletproof> {
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(),
},
}))
}

View File

@@ -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<DalekPoint>,
pub(crate) R: Vec<DalekPoint>,
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<EdwardsPoint>,
pub(crate) R: Vec<EdwardsPoint>,
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<ID: Copy + Zeroize, R: RngCore + CryptoRng>(
fn verify_core<R: RngCore + CryptoRng>(
&self,
rng: &mut R,
verifier: &mut BatchVerifier<ID, EdwardsPoint>,
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::<Vec<_>>();
let R = self.R.iter().map(normalize).collect::<Vec<_>>();
@@ -240,58 +227,69 @@ impl OriginalStruct {
let commitments = commitments.iter().map(EdwardsPoint::mul_by_cofactor).collect::<Vec<_>>();
// Verify it
let mut proof = Vec::with_capacity(4 + commitments.len());
let zpow = ScalarVector::powers(z, M + 3);
// First multiexp
{
let verifier_weight = Scalar::random(rng);
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()));
let y1 = self.t - ((z * ip1y) + k);
verifier.0.h -= verifier_weight * y1;
proof.push((-Scalar(self.taux), G));
verifier.0.g -= verifier_weight * self.tau_x;
for (j, commitment) in commitments.iter().enumerate() {
proof.push((zpow[j + 2], *commitment));
verifier.0.other.push((verifier_weight * zpow[j + 2], *commitment));
}
proof.push((x, T1));
proof.push((x * x, T2));
verifier.queue(&mut *rng, id, proof);
verifier.0.other.push((verifier_weight * x, T1));
verifier.0.other.push((verifier_weight * (x * x), T2));
}
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));
// 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;
proof.push((Scalar::ONE, A));
proof.push((x, S));
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().unwrap();
let yinv = y.invert();
let yinvpow = ScalarVector::powers(yinv, MN);
let w_cache = challenge_products(&w, &winv);
let generators = GENERATORS();
for i in 0 .. MN {
let g = (Scalar(self.a) * w_cache[i]) + z;
proof.push((-g, generators.G[i]));
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);
}
let mut h = Scalar(self.b) * yinvpow[i] * w_cache[(!i) & (MN - 1)];
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];
proof.push((-h, generators.H[i]));
verifier.0.h_bold[i] -= verifier_weight * h;
}
}
for i in 0 .. logMN {
proof.push((w[i] * w[i], L[i]));
proof.push((winv[i] * winv[i], R[i]));
verifier.0.other.push((verifier_weight * (w[i] * w[i]), L[i]));
verifier.0.other.push((verifier_weight * (winv[i] * winv[i]), R[i]));
}
}
verifier.queue(rng, id, proof);
true
}
@@ -300,24 +298,23 @@ impl OriginalStruct {
pub(crate) fn verify<R: RngCore + CryptoRng>(
&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<ID: Copy + Zeroize, R: RngCore + CryptoRng>(
pub(crate) fn batch_verify<R: RngCore + CryptoRng>(
&self,
rng: &mut R,
verifier: &mut BatchVerifier<ID, EdwardsPoint>,
id: ID,
commitments: &[DalekPoint],
verifier: &mut BulletproofsBatchVerifier,
commitments: &[EdwardsPoint],
) -> 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 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,
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<DalekPoint>,
generators: BpPlusGenerators,
V: Vec<EdwardsPoint>,
}
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<DalekPoint>) -> Option<Self> {
pub(crate) fn new(V: Vec<EdwardsPoint>) -> Option<Self> {
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<R: RngCore + CryptoRng>(
@@ -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::<Vec<_>>();
let V = V.into_iter().map(|V| V * INV_EIGHT()).collect::<Vec<_>>();
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
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<Id: Copy + Zeroize, R: RngCore + CryptoRng>(
pub(crate) fn verify<R: RngCore + CryptoRng>(
self,
rng: &mut R,
verifier: &mut BatchVerifier<Id, EdwardsPoint>,
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::<Vec<_>>();
let V = V.into_iter().map(|V| V * INV_EIGHT()).collect::<Vec<_>>();
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::<Vec<_>>();
let V = V.iter().map(EdwardsPoint::mul_by_cofactor).collect::<Vec<_>>();
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)
}
}

View File

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

View File

@@ -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<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 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::<Vec<_>>();
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::<Vec<_>>();
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<Id: Copy + Zeroize, R: RngCore + CryptoRng>(
pub(crate) fn verify<R: RngCore + CryptoRng>(
self,
rng: &mut R,
verifier: &mut BatchVerifier<Id, EdwardsPoint>,
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 challenges = Vec::with_capacity(proof.L.len());
let product_cache = {
let mut es = Vec::with_capacity(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()) {
es.push(Self::transcript_L_R(&mut transcript, *L, *R));
e_is.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();
let mut scratch = vec![Scalar::ZERO; es.len()];
group::ff::BatchInverter::invert_with_external_scratch(&mut inv_es, &mut scratch);
drop(scratch);
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);
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()))
verifier.0.other.push((neg_e_square, P));
let mut challenges = Vec::with_capacity(proof.L.len());
let product_cache = {
let mut inv_e_is = e_is.clone();
Scalar::batch_invert(&mut inv_e_is);
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
}

View File

@@ -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<Scalar>);

View File

@@ -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::<Vec<_>>();
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,
);
}
};

View File

@@ -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());
}

View File

@@ -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,
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());
}

View File

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

View File

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

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.
pub mod borromean;
/// Bulletproofs(+) structs, along with proving and verifying functionality.
pub mod bulletproofs;
pub use monero_bulletproofs as bulletproofs;
use crate::{
Protocol,

View File

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

View File

@@ -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::<u64>();
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::<Vec<_>>();
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 {

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-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 }