Downstream the eVRF libraries from FCMP++

Also adds no-std support to secq256k1 and embedwards25519.
This commit is contained in:
Luke Parker
2025-01-29 22:29:40 -05:00
parent 19422de231
commit 2bc880e372
35 changed files with 456 additions and 340 deletions

View File

@@ -3,19 +3,25 @@ name = "generalized-bulletproofs-circuit-abstraction"
version = "0.1.0"
description = "An abstraction for arithmetic circuits over Generalized Bulletproofs"
license = "MIT"
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/evrf/circuit-abstraction"
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/fcmps/circuit-abstraction"
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["bulletproofs", "circuit"]
edition = "2021"
rust-version = "1.80"
rust-version = "1.69"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
std-shims = { path = "../../../common/std-shims", version = "^0.1.1", default-features = false }
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
ciphersuite = { path = "../../ciphersuite", version = "0.4", default-features = false, features = ["std"] }
ciphersuite = { path = "../../ciphersuite", version = "0.4", default-features = false }
generalized-bulletproofs = { path = "../generalized-bulletproofs" }
generalized-bulletproofs = { path = "../generalized-bulletproofs", default-features = false }
[features]
std = ["std-shims/std", "zeroize/std", "ciphersuite/std", "generalized-bulletproofs/std"]
default = ["std"]

View File

@@ -1,14 +1,14 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#![cfg_attr(not(feature = "std"), no_std)]
#![deny(missing_docs)]
#![allow(non_snake_case)]
use std_shims::{vec, vec::Vec};
use zeroize::{Zeroize, ZeroizeOnDrop};
use ciphersuite::{
group::ff::{Field, PrimeField},
Ciphersuite,
};
use ciphersuite::{group::ff::Field, Ciphersuite};
use generalized_bulletproofs::{
ScalarVector, PedersenCommitment, PedersenVectorCommitment, ProofGenerators,
@@ -26,16 +26,28 @@ pub trait Transcript {
///
/// It is the caller's responsibility to have properly transcripted all variables prior to
/// sampling this challenge.
fn challenge<F: PrimeField>(&mut self) -> F;
fn challenge<C: Ciphersuite>(&mut self) -> C::F;
/// Sample a challenge as a byte array.
///
/// It is the caller's responsibility to have properly transcripted all variables prior to
/// sampling this challenge.
fn challenge_bytes(&mut self) -> [u8; 64];
}
impl Transcript for ProverTranscript {
fn challenge<F: PrimeField>(&mut self) -> F {
self.challenge()
fn challenge<C: Ciphersuite>(&mut self) -> C::F {
self.challenge::<C>()
}
fn challenge_bytes(&mut self) -> [u8; 64] {
self.challenge_bytes()
}
}
impl Transcript for VerifierTranscript<'_> {
fn challenge<F: PrimeField>(&mut self) -> F {
self.challenge()
fn challenge<C: Ciphersuite>(&mut self) -> C::F {
self.challenge::<C>()
}
fn challenge_bytes(&mut self) -> [u8; 64] {
self.challenge_bytes()
}
}
@@ -64,7 +76,6 @@ impl<C: Ciphersuite> Circuit<C> {
}
/// Create an instance to prove satisfaction of a circuit with.
// TODO: Take the transcript here
#[allow(clippy::type_complexity)]
pub fn prove(
vector_commitments: Vec<PedersenVectorCommitment<C>>,
@@ -78,14 +89,13 @@ impl<C: Ciphersuite> Circuit<C> {
}
/// Create an instance to verify a proof with.
// TODO: Take the transcript here
pub fn verify() -> Self {
Self { muls: 0, constraints: vec![], prover: None }
}
/// Evaluate a linear combination.
///
/// Yields WL aL + WR aR + WO aO + WCG CG + WCH CH + WV V + c.
/// Yields WL aL + WR aR + WO aO + WCG CG + WV V + c.
///
/// May panic if the linear combination references non-existent terms.
///
@@ -107,11 +117,6 @@ impl<C: Ciphersuite> Circuit<C> {
res += C.g_values[*j] * weight;
}
}
for (WCH, C) in lincomb.WCH().iter().zip(&prover.C) {
for (j, weight) in WCH {
res += C.h_values[*j] * weight;
}
}
for (index, weight) in lincomb.WV() {
res += prover.V[*index].value * weight;
}
@@ -176,13 +181,13 @@ impl<C: Ciphersuite> Circuit<C> {
// We can't deconstruct the witness as it implements Drop (per ZeroizeOnDrop)
// Accordingly, we take the values within it and move forward with those
let mut aL = vec![];
std::mem::swap(&mut prover.aL, &mut aL);
core::mem::swap(&mut prover.aL, &mut aL);
let mut aR = vec![];
std::mem::swap(&mut prover.aR, &mut aR);
core::mem::swap(&mut prover.aR, &mut aR);
let mut C = vec![];
std::mem::swap(&mut prover.C, &mut C);
core::mem::swap(&mut prover.C, &mut C);
let mut V = vec![];
std::mem::swap(&mut prover.V, &mut V);
core::mem::swap(&mut prover.V, &mut V);
ArithmeticCircuitWitness::new(ScalarVector::from(aL), ScalarVector::from(aR), C, V)
})
.transpose()?;

View File

@@ -3,35 +3,39 @@ name = "ec-divisors"
version = "0.1.0"
description = "A library for calculating elliptic curve divisors"
license = "MIT"
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/evrf/divisors"
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/divisors"
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["ciphersuite", "ff", "group"]
edition = "2021"
rust-version = "1.71"
rust-version = "1.69"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
rand_core = { version = "0.6", default-features = false }
zeroize = { version = "^1.5", default-features = false, features = ["std", "zeroize_derive"] }
std-shims = { path = "../../../common/std-shims", version = "^0.1.1", default-features = false }
subtle = { version = "2", default-features = false, features = ["std"] }
ff = { version = "0.13", default-features = false, features = ["std", "bits"] }
rand_core = { version = "0.6", default-features = false }
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
subtle = { version = "2", default-features = false }
ff = { version = "0.13", default-features = false, features = ["bits"] }
group = { version = "0.13", default-features = false }
hex = { version = "0.4", optional = true }
dalek-ff-group = { path = "../../dalek-ff-group", features = ["std"], optional = true }
pasta_curves = { version = "0.5", default-features = false, features = ["bits", "alloc"], optional = true }
hex = { version = "0.4", default-features = false, optional = true }
dalek-ff-group = { path = "../../dalek-ff-group", default-features = false, optional = true }
pasta_curves = { version = "0.5", git = "https://github.com/kayabaNerve/pasta_curves.git", rev = "a46b5be95cacbff54d06aad8d3bbcba42e05d616", default-features = false, features = ["bits", "alloc"], optional = true }
[dev-dependencies]
rand_core = { version = "0.6", features = ["getrandom"] }
hex = "0.4"
dalek-ff-group = { path = "../../dalek-ff-group", features = ["std"] }
pasta_curves = { version = "0.5", default-features = false, features = ["bits", "alloc"] }
pasta_curves = { version = "0.5", git = "https://github.com/kayabaNerve/pasta_curves.git", rev = "a46b5be95cacbff54d06aad8d3bbcba42e05d616", default-features = false, features = ["bits", "alloc"] }
[features]
ed25519 = ["hex", "dalek-ff-group"]
std = ["std-shims/std", "zeroize/std", "subtle/std", "ff/std", "dalek-ff-group?/std"]
ed25519 = ["hex/alloc", "dalek-ff-group"]
pasta = ["pasta_curves"]
default = ["std"]

View File

@@ -1,8 +1,11 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#![cfg_attr(not(feature = "std"), no_std)]
#![deny(missing_docs)]
#![allow(non_snake_case)]
use std_shims::{vec, vec::Vec};
use subtle::{Choice, ConstantTimeEq, ConstantTimeGreater, ConditionallySelectable};
use zeroize::{Zeroize, ZeroizeOnDrop};
@@ -18,7 +21,7 @@ pub use poly::Poly;
mod tests;
/// A curve usable with this library.
pub trait DivisorCurve: Group + ConstantTimeEq + ConditionallySelectable {
pub trait DivisorCurve: Group + ConstantTimeEq + ConditionallySelectable + Zeroize {
/// An element of the field this curve is defined over.
type FieldElement: Zeroize + PrimeField + ConditionallySelectable;
@@ -54,6 +57,8 @@ pub trait DivisorCurve: Group + ConstantTimeEq + ConditionallySelectable {
/// Convert a point to its x and y coordinates.
///
/// Returns None if passed the point at infinity.
///
/// This function may run in time variable to if the point is the identity.
fn to_xy(point: Self) -> Option<(Self::FieldElement, Self::FieldElement)>;
}
@@ -271,8 +276,16 @@ pub struct ScalarDecomposition<F: Zeroize + PrimeFieldBits> {
}
impl<F: Zeroize + PrimeFieldBits> ScalarDecomposition<F> {
/// Decompose a scalar.
pub fn new(scalar: F) -> Self {
/// Decompose a non-zero scalar.
///
/// Returns `None` if the scalar is zero.
///
/// This function is constant time if the scalar is non-zero.
pub fn new(scalar: F) -> Option<Self> {
if bool::from(scalar.is_zero()) {
None?;
}
/*
We need the sum of the coefficients to equal F::NUM_BITS. The scalar's bits will be less than
F::NUM_BITS. Accordingly, we need to increment the sum of the coefficients without
@@ -400,7 +413,12 @@ impl<F: Zeroize + PrimeFieldBits> ScalarDecomposition<F> {
}
debug_assert!(bool::from(decomposition.iter().sum::<u64>().ct_eq(&num_bits)));
ScalarDecomposition { scalar, decomposition }
Some(ScalarDecomposition { scalar, decomposition })
}
/// The scalar.
pub fn scalar(&self) -> &F {
&self.scalar
}
/// The decomposition of the scalar.
@@ -414,7 +432,7 @@ impl<F: Zeroize + PrimeFieldBits> ScalarDecomposition<F> {
///
/// This function executes in constant time with regards to the scalar.
///
/// This function MAY panic if this scalar is zero.
/// This function MAY panic if the generator is the point at infinity.
pub fn scalar_mul_divisor<C: Zeroize + DivisorCurve<Scalar = F>>(
&self,
mut generator: C,
@@ -430,37 +448,19 @@ impl<F: Zeroize + PrimeFieldBits> ScalarDecomposition<F> {
divisor_points[0] = -generator * self.scalar;
// Write the decomposition
let mut write_to: u32 = 1;
let mut write_above: u64 = 0;
for coefficient in &self.decomposition {
let mut coefficient = *coefficient;
// Iterate over the maximum amount of iters for this value to be constant time regardless of
// any branch prediction algorithms
for _ in 0 .. <C::Scalar as PrimeField>::NUM_BITS {
// Write the generator to the slot we're supposed to
/*
Without this loop, we'd increment this dependent on the distribution within the
decomposition. If the distribution is bottom-heavy, we won't access the tail of
`divisor_points` for a while, risking it being ejected out of the cache (causing a cache
miss which may not occur with a top-heavy distribution which quickly moves to the tail).
This is O(log2(NUM_BITS) ** 3) though, as this the third loop, which is horrific.
*/
for i in 1 ..= <C::Scalar as PrimeField>::NUM_BITS {
divisor_points[i as usize] =
<_>::conditional_select(&divisor_points[i as usize], &generator, i.ct_eq(&write_to));
}
// If the coefficient isn't zero, increment write_to (so we don't overwrite this generator
// when it should be there)
let coefficient_not_zero = !coefficient.ct_eq(&0);
write_to = <_>::conditional_select(&write_to, &(write_to + 1), coefficient_not_zero);
// Subtract one from the coefficient, if it's not zero and won't underflow
coefficient =
<_>::conditional_select(&coefficient, &coefficient.wrapping_sub(1), coefficient_not_zero);
// Write the generator to every slot except the slots we have already written to.
for i in 1 ..= (<C::Scalar as PrimeField>::NUM_BITS as u64) {
divisor_points[i as usize].conditional_assign(&generator, i.ct_gt(&write_above));
}
// Increase the next write start by the coefficient.
write_above += coefficient;
generator = generator.double();
}
// Create a divisor out of all points except the last point which is solely scratch
// Create a divisor out of the points
let res = new_divisor(&divisor_points).unwrap();
divisor_points.zeroize();
res
@@ -511,6 +511,7 @@ mod pasta {
#[cfg(any(test, feature = "ed25519"))]
mod ed25519 {
use subtle::{Choice, ConditionallySelectable};
use group::{
ff::{Field, PrimeField},
Group, GroupEncoding,
@@ -558,9 +559,13 @@ mod ed25519 {
((D * edwards_y_sq) + Self::FieldElement::ONE).invert().unwrap())
.sqrt()
.unwrap();
if u8::from(bool::from(edwards_x.is_odd())) != x_is_odd {
edwards_x = -edwards_x;
}
// Negate the x coordinate if the sign doesn't match
edwards_x = <_>::conditional_select(
&edwards_x,
&-edwards_x,
edwards_x.is_odd() ^ Choice::from(x_is_odd),
);
// Calculate the x and y coordinates for Wei25519
let edwards_y_plus_one = Self::FieldElement::ONE + edwards_y;

View File

@@ -1,4 +1,5 @@
use core::ops::{Add, Neg, Sub, Mul, Rem};
use std_shims::{vec, vec::Vec};
use subtle::{Choice, ConstantTimeEq, ConstantTimeGreater, ConditionallySelectable};
use zeroize::{Zeroize, ZeroizeOnDrop};
@@ -257,7 +258,7 @@ impl<F: From<u64> + Zeroize + PrimeField> Poly<F> {
self.zero_coefficient = F::ZERO;
// Move the x coefficients
std::mem::swap(&mut self.yx_coefficients[power_of_y - 1], &mut self.x_coefficients);
core::mem::swap(&mut self.yx_coefficients[power_of_y - 1], &mut self.x_coefficients);
self.x_coefficients = vec![];
self
@@ -564,7 +565,7 @@ impl<F: From<u64> + Zeroize + PrimeField> Poly<F> {
quotient = conditional_select_poly(
quotient,
// If the dividing coefficient was for y**0 x**0, we return the poly scaled by its inverse
self.clone() * denominator_dividing_coefficient_inv,
self * denominator_dividing_coefficient_inv,
denominator_dividing_coefficient.ct_eq(&CoefficientIndex { y_pow: 0, x_pow: 0 }),
);
remainder = conditional_select_poly(

View File

@@ -3,19 +3,25 @@ name = "generalized-bulletproofs-ec-gadgets"
version = "0.1.0"
description = "Gadgets for working with an embedded Elliptic Curve in a Generalized Bulletproofs circuit"
license = "MIT"
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/evrf/ec-gadgets"
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/fcmps/ec-gadgets"
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["bulletproofs", "circuit", "divisors"]
edition = "2021"
rust-version = "1.80"
rust-version = "1.69"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
std-shims = { path = "../../../common/std-shims", version = "^0.1.1", default-features = false }
generic-array = { version = "1", default-features = false, features = ["alloc"] }
ciphersuite = { path = "../../ciphersuite", version = "0.4", default-features = false, features = ["std"] }
ciphersuite = { path = "../../ciphersuite", version = "0.4", default-features = false }
generalized-bulletproofs-circuit-abstraction = { path = "../circuit-abstraction" }
generalized-bulletproofs-circuit-abstraction = { path = "../circuit-abstraction", default-features = false }
[features]
std = ["std-shims/std", "ciphersuite/std", "generalized-bulletproofs-circuit-abstraction/std"]
default = ["std"]

View File

@@ -1,4 +1,5 @@
use core::fmt;
use std_shims::{vec, vec::Vec};
use ciphersuite::{
group::ff::{Field, PrimeField, BatchInverter},
@@ -10,11 +11,6 @@ use generalized_bulletproofs_circuit_abstraction::*;
use crate::*;
/// Parameters for a discrete logarithm proof.
///
/// This isn't required to be implemented by the Field/Group/Ciphersuite, solely a struct, to
/// enable parameterization of discrete log proofs to the bitlength of the discrete logarithm.
/// While that may be F::NUM_BITS, a discrete log proof a for a full scalar, it could also be 64,
/// a discrete log proof for a u64 (such as if opening a Pedersen commitment in-circuit).
pub trait DiscreteLogParameters {
/// The amount of bits used to represent a scalar.
type ScalarBits: ArrayLength;
@@ -30,8 +26,8 @@ pub trait DiscreteLogParameters {
/// The amount of y x**i coefficients in a divisor.
///
/// This is the amount of points in a divisor (the amount of bits in a scalar, plus one) plus
/// one, divided by two, minus two.
/// This is the amount of points in a divisor (the amount of bits in a scalar, plus one) divided
/// by two, minus two.
type YxCoefficients: ArrayLength;
}
@@ -106,8 +102,6 @@ pub struct Divisor<Parameters: DiscreteLogParameters> {
/// exceeding trivial complexity.
pub y: Variable,
/// The coefficients for the `y**1 x**i` terms of the polynomial.
// This subtraction enforces the divisor to have at least 4 points which is acceptable.
// TODO: Double check these constants
pub yx: GenericArray<Variable, Parameters::YxCoefficients>,
/// The coefficients for the `x**i` terms of the polynomial, skipping x**1.
///
@@ -324,7 +318,7 @@ pub trait EcDlogGadgets<C: Ciphersuite> {
&self,
transcript: &mut T,
curve: &CurveSpec<C::F>,
generators: &[GeneratorTable<C::F, Parameters>],
generators: &[&GeneratorTable<C::F, Parameters>],
) -> (DiscreteLogChallenge<C::F, Parameters>, Vec<ChallengedGenerator<C::F, Parameters>>);
/// Prove this point has the specified discrete logarithm over the specified generator.
@@ -355,12 +349,14 @@ impl<C: Ciphersuite> EcDlogGadgets<C> for Circuit<C> {
&self,
transcript: &mut T,
curve: &CurveSpec<C::F>,
generators: &[GeneratorTable<C::F, Parameters>],
generators: &[&GeneratorTable<C::F, Parameters>],
) -> (DiscreteLogChallenge<C::F, Parameters>, Vec<ChallengedGenerator<C::F, Parameters>>) {
// Get the challenge points
// TODO: Implement a proper hash to curve
let sign_of_points = transcript.challenge_bytes();
let sign_of_point_0 = (sign_of_points[0] & 1) == 1;
let sign_of_point_1 = ((sign_of_points[0] >> 1) & 1) == 1;
let (c0_x, c0_y) = loop {
let c0_x: C::F = transcript.challenge();
let c0_x = transcript.challenge::<C>();
let Some(c0_y) =
Option::<C::F>::from(((c0_x.square() * c0_x) + (curve.a * c0_x) + curve.b).sqrt())
else {
@@ -368,17 +364,16 @@ impl<C: Ciphersuite> EcDlogGadgets<C> for Circuit<C> {
};
// Takes the even y coordinate as to not be dependent on whatever root the above sqrt
// happens to returns
// TODO: Randomly select which to take
break (c0_x, if bool::from(c0_y.is_odd()) { -c0_y } else { c0_y });
break (c0_x, if bool::from(c0_y.is_odd()) != sign_of_point_0 { -c0_y } else { c0_y });
};
let (c1_x, c1_y) = loop {
let c1_x: C::F = transcript.challenge();
let c1_x = transcript.challenge::<C>();
let Some(c1_y) =
Option::<C::F>::from(((c1_x.square() * c1_x) + (curve.a * c1_x) + curve.b).sqrt())
else {
continue;
};
break (c1_x, if bool::from(c1_y.is_odd()) { -c1_y } else { c1_y });
break (c1_x, if bool::from(c1_y.is_odd()) != sign_of_point_1 { -c1_y } else { c1_y });
};
// mmadd-1998-cmo
@@ -483,7 +478,7 @@ impl<C: Ciphersuite> EcDlogGadgets<C> for Circuit<C> {
let arg_iter = arg_iter.chain(dlog.iter());
for variable in arg_iter {
debug_assert!(
matches!(variable, Variable::CG { .. } | Variable::CH { .. } | Variable::V(_)),
matches!(variable, Variable::CG { .. } | Variable::V(_)),
"discrete log proofs requires all arguments belong to commitments",
);
}

View File

@@ -1,5 +1,6 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#![cfg_attr(not(feature = "std"), no_std)]
#![deny(missing_docs)]
#![allow(non_snake_case)]

View File

@@ -17,20 +17,22 @@ rustdoc-args = ["--cfg", "docsrs"]
rustversion = "1"
hex-literal = { version = "0.4", default-features = false }
rand_core = { version = "0.6", default-features = false, features = ["std"] }
std-shims = { version = "0.1", path = "../../../common/std-shims", default-features = false, optional = true }
zeroize = { version = "^1.5", default-features = false, features = ["std", "zeroize_derive"] }
subtle = { version = "^2.4", default-features = false, features = ["std"] }
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 }
generic-array = { version = "1", default-features = false }
crypto-bigint = { version = "0.5", default-features = false, features = ["zeroize"] }
dalek-ff-group = { path = "../../dalek-ff-group", version = "0.4", default-features = false }
blake2 = { version = "0.10", default-features = false, features = ["std"] }
ciphersuite = { path = "../../ciphersuite", version = "0.4", default-features = false, features = ["std"] }
ec-divisors = { path = "../divisors" }
generalized-bulletproofs-ec-gadgets = { path = "../ec-gadgets" }
blake2 = { version = "0.10", default-features = false }
ciphersuite = { path = "../../ciphersuite", version = "0.4", default-features = false }
ec-divisors = { path = "../divisors", default-features = false }
generalized-bulletproofs-ec-gadgets = { path = "../ec-gadgets", default-features = false }
[dev-dependencies]
hex = "0.4"
@@ -38,3 +40,8 @@ hex = "0.4"
rand_core = { version = "0.6", features = ["std"] }
ff-group-tests = { path = "../../ff-group-tests" }
[features]
alloc = ["std-shims", "zeroize/alloc", "ciphersuite/alloc"]
std = ["std-shims/std", "rand_core/std", "zeroize/std", "subtle/std", "blake2/std", "ciphersuite/std", "ec-divisors/std", "generalized-bulletproofs-ec-gadgets/std"]
default = ["std"]

View File

@@ -1,5 +1,9 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(any(feature = "alloc", feature = "std"))]
use std_shims::io::{self, Read};
use generic_array::typenum::{Sum, Diff, Quot, U, U1, U2};
use ciphersuite::group::{ff::PrimeField, Group};
@@ -33,10 +37,29 @@ impl ciphersuite::Ciphersuite for Embedwards25519 {
Point::generator()
}
fn reduce_512(scalar: [u8; 64]) -> Self::F {
Scalar::wide_reduce(scalar)
}
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
use blake2::Digest;
Scalar::wide_reduce(Self::H::digest([dst, data].concat()).as_slice().try_into().unwrap())
}
// We override the provided impl, which compares against the reserialization, because
// we already require canonicity
#[cfg(any(feature = "alloc", feature = "std"))]
#[allow(non_snake_case)]
fn read_G<R: Read>(reader: &mut R) -> io::Result<Self::G> {
use ciphersuite::group::GroupEncoding;
let mut encoding = <Self::G as GroupEncoding>::Repr::default();
reader.read_exact(encoding.as_mut())?;
let point = Option::<Self::G>::from(Self::G::from_bytes(&encoding))
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "invalid point"))?;
Ok(point)
}
}
impl generalized_bulletproofs_ec_gadgets::DiscreteLogParameters for Embedwards25519 {

View File

@@ -3,25 +3,27 @@ name = "generalized-bulletproofs"
version = "0.1.0"
description = "Generalized Bulletproofs"
license = "MIT"
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/evrf/generalized-bulletproofs"
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/generalized-bulletproofs"
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["ciphersuite", "ff", "group"]
edition = "2021"
rust-version = "1.80"
rust-version = "1.69"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
rand_core = { version = "0.6", default-features = false, features = ["std"] }
std-shims = { path = "../../../common/std-shims", version = "^0.1.1", default-features = false }
zeroize = { version = "^1.5", default-features = false, features = ["std", "zeroize_derive"] }
rand_core = { version = "0.6", default-features = false }
blake2 = { version = "0.10", default-features = false, features = ["std"] }
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
multiexp = { path = "../../multiexp", version = "0.4", default-features = false, features = ["std", "batch"] }
ciphersuite = { path = "../../ciphersuite", version = "0.4", default-features = false, features = ["std"] }
blake2 = { version = "0.10", default-features = false }
multiexp = { path = "../../multiexp", version = "0.4", default-features = false, features = ["batch"] }
ciphersuite = { path = "../../ciphersuite", version = "0.4", default-features = false }
[dev-dependencies]
rand_core = { version = "0.6", features = ["getrandom"] }
@@ -31,4 +33,6 @@ transcript = { package = "flexible-transcript", path = "../../transcript", featu
ciphersuite = { path = "../../ciphersuite", features = ["ristretto"] }
[features]
tests = []
std = ["std-shims/std", "rand_core/std", "zeroize/std", "blake2/std", "multiexp/std", "ciphersuite/std"]
tests = ["std"]
default = ["std"]

View File

@@ -1,3 +1,5 @@
use std_shims::{vec, vec::Vec};
use rand_core::{RngCore, CryptoRng};
use zeroize::{Zeroize, ZeroizeOnDrop};
@@ -20,10 +22,10 @@ pub use crate::lincomb::{Variable, LinComb};
/// `aL * aR = aO, WL * aL + WR * aR + WO * aO = WV * V + c`.
///
/// Generalized Bulletproofs modifies this to
/// `aL * aR = aO, WL * aL + WR * aR + WO * aO + WCG * C_G + WCH * C_H = WV * V + c`.
/// `aL * aR = aO, WL * aL + WR * aR + WO * aO + WCG * C_G = WV * V + c`.
///
/// We implement the latter, yet represented (for simplicity) as
/// `aL * aR = aO, WL * aL + WR * aR + WO * aO + WCG * C_G + WCH * C_H + WV * V + c = 0`.
/// `aL * aR = aO, WL * aL + WR * aR + WO * aO + WCG * C_G + WV * V + c = 0`.
#[derive(Clone, Debug)]
pub struct ArithmeticCircuitStatement<'a, C: Ciphersuite> {
generators: ProofGenerators<'a, C>,
@@ -202,16 +204,10 @@ impl<'a, C: Ciphersuite> ArithmeticCircuitStatement<'a, C> {
if c.g_values.len() > n {
Err(AcError::NotEnoughGenerators)?;
}
if c.h_values.len() > n {
Err(AcError::NotEnoughGenerators)?;
}
// The Pedersen vector commitments internally have n terms
while c.g_values.len() < n {
c.g_values.0.push(C::F::ZERO);
}
while c.h_values.len() < n {
c.h_values.0.push(C::F::ZERO);
}
}
// Check the witness's consistency with the statement
@@ -227,12 +223,7 @@ impl<'a, C: Ciphersuite> ArithmeticCircuitStatement<'a, C> {
}
}
for (commitment, opening) in self.C.0.iter().zip(witness.c.iter()) {
if Some(*commitment) !=
opening.commit(
self.generators.g_bold_slice(),
self.generators.h_bold_slice(),
self.generators.h(),
)
if Some(*commitment) != opening.commit(self.generators.g_bold_slice(), self.generators.h())
{
Err(AcError::InconsistentWitness)?;
}
@@ -250,11 +241,6 @@ impl<'a, C: Ciphersuite> ArithmeticCircuitStatement<'a, C> {
weights.iter().map(|(j, weight)| *weight * c.g_values[*j])
}),
)
.chain(
constraint.WCH.iter().zip(&witness.c).flat_map(|(weights, c)| {
weights.iter().map(|(j, weight)| *weight * c.h_values[*j])
}),
)
.chain(constraint.WV.iter().map(|(i, weight)| *weight * witness.v[*i].value))
.chain(core::iter::once(constraint.c))
.sum::<C::F>();
@@ -306,8 +292,8 @@ impl<'a, C: Ciphersuite> ArithmeticCircuitStatement<'a, C> {
transcript.push_point(AI);
transcript.push_point(AO);
transcript.push_point(S);
let y = transcript.challenge();
let z = transcript.challenge();
let y = transcript.challenge::<C>();
let z = transcript.challenge::<C>();
let YzChallenges { y_inv, z } = self.yz_challenges(y, z);
let y = ScalarVector::powers(y, n);
@@ -318,7 +304,7 @@ impl<'a, C: Ciphersuite> ArithmeticCircuitStatement<'a, C> {
// polynomial).
// ni = n'
let ni = 2 * (c + 1);
let ni = 2 + (2 * (c / 2));
// These indexes are from the Generalized Bulletproofs paper
#[rustfmt::skip]
let ilr = ni / 2; // 1 if c = 0
@@ -379,32 +365,25 @@ impl<'a, C: Ciphersuite> ArithmeticCircuitStatement<'a, C> {
// r decreasing from n' (skipping jlr)
let mut cg_weights = Vec::with_capacity(witness.c.len());
let mut ch_weights = Vec::with_capacity(witness.c.len());
for i in 0 .. witness.c.len() {
let mut cg = ScalarVector::new(n);
let mut ch = ScalarVector::new(n);
for (constraint, z) in self.constraints.iter().zip(&z.0) {
if let Some(WCG) = constraint.WCG.get(i) {
accumulate_vector(&mut cg, WCG, *z);
}
if let Some(WCH) = constraint.WCH.get(i) {
accumulate_vector(&mut ch, WCH, *z);
}
}
cg_weights.push(cg);
ch_weights.push(ch);
}
for (i, (c, (cg_weights, ch_weights))) in
witness.c.iter().zip(cg_weights.into_iter().zip(ch_weights)).enumerate()
{
let i = i + 1;
for (mut i, (c, cg_weights)) in witness.c.iter().zip(cg_weights).enumerate() {
if i >= ilr {
i += 1;
}
// Because i has skipped ilr, j will skip jlr
let j = ni - i;
l[i] = c.g_values.clone();
l[j] = ch_weights * &y_inv;
r[j] = cg_weights;
r[i] = (c.h_values.clone() * &y) + &r[i];
}
// Multiply them to obtain t
@@ -437,7 +416,7 @@ impl<'a, C: Ciphersuite> ArithmeticCircuitStatement<'a, C> {
transcript.push_point(multiexp(&[(*t, self.generators.g()), (*tau, self.generators.h())]));
}
let x: ScalarVector<C::F> = ScalarVector::powers(transcript.challenge(), t.len());
let x: ScalarVector<C::F> = ScalarVector::powers(transcript.challenge::<C>(), t.len());
let poly_eval = |poly: &[ScalarVector<C::F>], x: &ScalarVector<_>| -> ScalarVector<_> {
let mut res = ScalarVector::<C::F>::new(poly[0].0.len());
@@ -477,8 +456,11 @@ impl<'a, C: Ciphersuite> ArithmeticCircuitStatement<'a, C> {
let mut u = (alpha * x[ilr]) + (beta * x[io]) + (rho * x[is]);
// Incorporate the commitment masks multiplied by the associated power of x
for (i, commitment) in witness.c.iter().enumerate() {
let i = i + 1;
for (mut i, commitment) in witness.c.iter().enumerate() {
// If this index is ni / 2, skip it
if i >= (ni / 2) {
i += 1;
}
u += x[i] * commitment.mask;
}
u
@@ -498,7 +480,7 @@ impl<'a, C: Ciphersuite> ArithmeticCircuitStatement<'a, C> {
transcript.push_scalar(tau_x);
transcript.push_scalar(u);
transcript.push_scalar(t_caret);
let ip_x = transcript.challenge();
let ip_x = transcript.challenge::<C>();
P_terms.push((ip_x * t_caret, self.generators.g()));
IpStatement::new(
self.generators,
@@ -513,16 +495,27 @@ impl<'a, C: Ciphersuite> ArithmeticCircuitStatement<'a, C> {
}
/// Verify a proof for this statement.
///
/// This solely queues the statement for batch verification. The resulting BatchVerifier MUST
/// still be verified.
///
/// If this proof returns an error, the BatchVerifier MUST be assumed corrupted and discarded.
pub fn verify<R: RngCore + CryptoRng>(
self,
rng: &mut R,
verifier: &mut BatchVerifier<C>,
transcript: &mut VerifierTranscript,
) -> Result<(), AcError> {
if verifier.g_bold.len() < self.generators.len() {
verifier.g_bold.resize(self.generators.len(), C::F::ZERO);
verifier.h_bold.resize(self.generators.len(), C::F::ZERO);
verifier.h_sum.resize(self.generators.len(), C::F::ZERO);
}
let n = self.n();
let c = self.c();
let ni = 2 * (c + 1);
let ni = 2 + (2 * (c / 2));
let ilr = ni / 2;
let io = ni;
@@ -535,8 +528,8 @@ impl<'a, C: Ciphersuite> ArithmeticCircuitStatement<'a, C> {
let AI = transcript.read_point::<C>().map_err(|_| AcError::IncompleteProof)?;
let AO = transcript.read_point::<C>().map_err(|_| AcError::IncompleteProof)?;
let S = transcript.read_point::<C>().map_err(|_| AcError::IncompleteProof)?;
let y = transcript.challenge();
let z = transcript.challenge();
let y = transcript.challenge::<C>();
let z = transcript.challenge::<C>();
let YzChallenges { y_inv, z } = self.yz_challenges(y, z);
let mut l_weights = ScalarVector::new(n);
@@ -559,7 +552,7 @@ impl<'a, C: Ciphersuite> ArithmeticCircuitStatement<'a, C> {
for _ in 0 .. (t_poly_len - ni - 1) {
T_after_ni.push(transcript.read_point::<C>().map_err(|_| AcError::IncompleteProof)?);
}
let x: ScalarVector<C::F> = ScalarVector::powers(transcript.challenge(), t_poly_len);
let x: ScalarVector<C::F> = ScalarVector::powers(transcript.challenge::<C>(), t_poly_len);
let tau_x = transcript.read_scalar::<C>().map_err(|_| AcError::IncompleteProof)?;
let u = transcript.read_scalar::<C>().map_err(|_| AcError::IncompleteProof)?;
@@ -624,34 +617,25 @@ impl<'a, C: Ciphersuite> ArithmeticCircuitStatement<'a, C> {
h_bold_scalars = h_bold_scalars + &(o_weights * verifier_weight);
let mut cg_weights = Vec::with_capacity(self.C.len());
let mut ch_weights = Vec::with_capacity(self.C.len());
for i in 0 .. self.C.len() {
let mut cg = ScalarVector::new(n);
let mut ch = ScalarVector::new(n);
for (constraint, z) in self.constraints.iter().zip(&z.0) {
if let Some(WCG) = constraint.WCG.get(i) {
accumulate_vector(&mut cg, WCG, *z);
}
if let Some(WCH) = constraint.WCH.get(i) {
accumulate_vector(&mut ch, WCH, *z);
}
}
cg_weights.push(cg);
ch_weights.push(ch);
}
// Push the terms for C, which increment from 0, and the terms for WC, which decrement from
// n'
for (i, (C, (WCG, WCH))) in
self.C.0.into_iter().zip(cg_weights.into_iter().zip(ch_weights)).enumerate()
{
let i = i + 1;
for (mut i, (C, WCG)) in self.C.0.into_iter().zip(cg_weights).enumerate() {
if i >= (ni / 2) {
i += 1;
}
let j = ni - i;
verifier.additional.push((x[i], C));
h_bold_scalars = h_bold_scalars + &(WCG * x[j]);
for (i, scalar) in (WCH * &y_inv * x[j]).0.into_iter().enumerate() {
verifier.g_bold[i] += scalar;
}
}
// All terms for h_bold here have actually been for h_bold', h_bold * y_inv
@@ -666,7 +650,7 @@ impl<'a, C: Ciphersuite> ArithmeticCircuitStatement<'a, C> {
// Prove for lines 88, 92 with an Inner-Product statement
// This inlines Protocol 1, as our IpStatement implements Protocol 2
let ip_x = transcript.challenge();
let ip_x = transcript.challenge::<C>();
// P is amended with this additional term
verifier.g += verifier_weight * ip_x * t_caret;
IpStatement::new(self.generators, y_inv, ip_x, P::Verifier { verifier_weight })

View File

@@ -1,3 +1,5 @@
use std_shims::{vec, vec::Vec};
use multiexp::multiexp_vartime;
use ciphersuite::{group::ff::Field, Ciphersuite};
@@ -186,7 +188,7 @@ impl<'a, C: Ciphersuite> IpStatement<'a, C> {
// Now that we've calculate L, R, transcript them to receive x (26-27)
transcript.push_point(L);
transcript.push_point(R);
let x: C::F = transcript.challenge();
let x: C::F = transcript.challenge::<C>();
let x_inv = x.invert().unwrap();
// The prover and verifier now calculate the following (28-31)
@@ -269,11 +271,19 @@ impl<'a, C: Ciphersuite> IpStatement<'a, C> {
/// This will return Err if there is an error. This will return Ok if the proof was successfully
/// queued for batch verification. The caller is required to verify the batch in order to ensure
/// the proof is actually correct.
///
/// If this proof returns an error, the BatchVerifier MUST be assumed corrupted and discarded.
pub(crate) fn verify(
self,
verifier: &mut BatchVerifier<C>,
transcript: &mut VerifierTranscript,
) -> Result<(), IpError> {
if verifier.g_bold.len() < self.generators.len() {
verifier.g_bold.resize(self.generators.len(), C::F::ZERO);
verifier.h_bold.resize(self.generators.len(), C::F::ZERO);
verifier.h_sum.resize(self.generators.len(), C::F::ZERO);
}
let IpStatement { generators, h_bold_weights, u, P } = self;
// Calculate the discrete log w.r.t. 2 for the amount of generators present
@@ -296,7 +306,7 @@ impl<'a, C: Ciphersuite> IpStatement<'a, C> {
for _ in 0 .. lr_len {
L.push(transcript.read_point::<C>().map_err(|_| IpError::IncompleteProof)?);
R.push(transcript.read_point::<C>().map_err(|_| IpError::IncompleteProof)?);
xs.push(transcript.challenge());
xs.push(transcript.challenge::<C>());
}
// We calculate their inverse in batch

View File

@@ -1,10 +1,11 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#![cfg_attr(not(feature = "std"), no_std)]
#![deny(missing_docs)]
#![allow(non_snake_case)]
use core::fmt;
use std::collections::HashSet;
use std_shims::{vec, vec::Vec, collections::HashSet};
use zeroize::Zeroize;
@@ -70,14 +71,26 @@ pub struct Generators<C: Ciphersuite> {
#[must_use]
#[derive(Clone)]
pub struct BatchVerifier<C: Ciphersuite> {
g: C::F,
h: C::F,
/// The summed scalar for the G generator.
pub g: C::F,
/// The summed scalar for the G generator.
pub h: C::F,
g_bold: Vec<C::F>,
h_bold: Vec<C::F>,
h_sum: Vec<C::F>,
/// The summed scalars for the G_bold generators.
pub g_bold: Vec<C::F>,
/// The summed scalars for the H_bold generators.
pub h_bold: Vec<C::F>,
/// The summed scalars for the sums of all H generators prior to the index.
///
/// This is not populated with the full set of summed H generators. This is only populated with
/// the powers of 2. Accordingly, an index i specifies a scalar for the sum of all H generators
/// from H**2**0 ..= H**2**i.
pub h_sum: Vec<C::F>,
additional: Vec<(C::F, C::G)>,
/// Additional (non-fixed) points to include in the multiexp.
///
/// This is used for proof-specific elements.
pub additional: Vec<(C::F, C::G)>,
}
impl<C: Ciphersuite> fmt::Debug for Generators<C> {
@@ -171,15 +184,15 @@ impl<C: Ciphersuite> Generators<C> {
Ok(Generators { g, h, g_bold, h_bold, h_sum })
}
/// Create a BatchVerifier for proofs which use these generators.
pub fn batch_verifier(&self) -> BatchVerifier<C> {
/// Create a BatchVerifier for proofs which use a consistent set of generators.
pub fn batch_verifier() -> BatchVerifier<C> {
BatchVerifier {
g: C::F::ZERO,
h: C::F::ZERO,
g_bold: vec![C::F::ZERO; self.g_bold.len()],
h_bold: vec![C::F::ZERO; self.h_bold.len()],
h_sum: vec![C::F::ZERO; self.h_sum.len()],
g_bold: vec![],
h_bold: vec![],
h_sum: vec![],
additional: Vec::with_capacity(128),
}
@@ -298,8 +311,6 @@ impl<C: Ciphersuite> PedersenCommitment<C> {
pub struct PedersenVectorCommitment<C: Ciphersuite> {
/// The values committed to across the `g` (bold) generators.
pub g_values: ScalarVector<C::F>,
/// The values committed to across the `h` (bold) generators.
pub h_values: ScalarVector<C::F>,
/// The mask blinding the values committed to.
pub mask: C::F,
}
@@ -309,8 +320,8 @@ impl<C: Ciphersuite> PedersenVectorCommitment<C> {
///
/// This function returns None if the amount of generators is less than the amount of values
/// within the relevant vector.
pub fn commit(&self, g_bold: &[C::G], h_bold: &[C::G], h: C::G) -> Option<C::G> {
if (g_bold.len() < self.g_values.len()) || (h_bold.len() < self.h_values.len()) {
pub fn commit(&self, g_bold: &[C::G], h: C::G) -> Option<C::G> {
if g_bold.len() < self.g_values.len() {
None?;
};
@@ -318,9 +329,6 @@ impl<C: Ciphersuite> PedersenVectorCommitment<C> {
for pair in self.g_values.0.iter().cloned().zip(g_bold.iter().cloned()) {
terms.push(pair);
}
for pair in self.h_values.0.iter().cloned().zip(h_bold.iter().cloned()) {
terms.push(pair);
}
let res = multiexp(&terms);
terms.zeroize();
Some(res)

View File

@@ -1,4 +1,5 @@
use core::ops::{Add, Sub, Mul};
use std_shims::{vec, vec::Vec};
use zeroize::Zeroize;
@@ -23,13 +24,6 @@ pub enum Variable {
/// The index of the variable.
index: usize,
},
/// A variable within a Pedersen vector commitment, committed to with a generator from `h` (bold).
CH {
/// The commitment being indexed.
commitment: usize,
/// The index of the variable.
index: usize,
},
/// A variable within a Pedersen commitment.
V(usize),
}
@@ -41,7 +35,7 @@ impl Zeroize for Variable {
/// A linear combination.
///
/// Specifically, `WL aL + WR aR + WO aO + WCG C_G + WCH C_H + WV V + c`.
/// Specifically, `WL aL + WR aR + WO aO + WCG C_G + WV V + c`.
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
#[must_use]
pub struct LinComb<F: PrimeField> {
@@ -55,7 +49,6 @@ pub struct LinComb<F: PrimeField> {
pub(crate) WO: Vec<(usize, F)>,
// Sparse representation once within a commitment
pub(crate) WCG: Vec<Vec<(usize, F)>>,
pub(crate) WCH: Vec<Vec<(usize, F)>>,
// Sparse representation of WV
pub(crate) WV: Vec<(usize, F)>,
pub(crate) c: F,
@@ -81,15 +74,9 @@ impl<F: PrimeField> Add<&LinComb<F>> for LinComb<F> {
while self.WCG.len() < constraint.WCG.len() {
self.WCG.push(vec![]);
}
while self.WCH.len() < constraint.WCH.len() {
self.WCH.push(vec![]);
}
for (sWC, cWC) in self.WCG.iter_mut().zip(&constraint.WCG) {
sWC.extend(cWC);
}
for (sWC, cWC) in self.WCH.iter_mut().zip(&constraint.WCH) {
sWC.extend(cWC);
}
self.WV.extend(&constraint.WV);
self.c += constraint.c;
self
@@ -110,15 +97,9 @@ impl<F: PrimeField> Sub<&LinComb<F>> for LinComb<F> {
while self.WCG.len() < constraint.WCG.len() {
self.WCG.push(vec![]);
}
while self.WCH.len() < constraint.WCH.len() {
self.WCH.push(vec![]);
}
for (sWC, cWC) in self.WCG.iter_mut().zip(&constraint.WCG) {
sWC.extend(cWC.iter().map(|(i, weight)| (*i, -*weight)));
}
for (sWC, cWC) in self.WCH.iter_mut().zip(&constraint.WCH) {
sWC.extend(cWC.iter().map(|(i, weight)| (*i, -*weight)));
}
self.WV.extend(constraint.WV.iter().map(|(i, weight)| (*i, -*weight)));
self.c -= constraint.c;
self
@@ -143,11 +124,6 @@ impl<F: PrimeField> Mul<F> for LinComb<F> {
*weight *= scalar;
}
}
for WC in self.WCH.iter_mut() {
for (_, weight) in WC {
*weight *= scalar;
}
}
for (_, weight) in self.WV.iter_mut() {
*weight *= scalar;
}
@@ -167,7 +143,6 @@ impl<F: PrimeField> LinComb<F> {
WR: vec![],
WO: vec![],
WCG: vec![],
WCH: vec![],
WV: vec![],
c: F::ZERO,
}
@@ -196,14 +171,6 @@ impl<F: PrimeField> LinComb<F> {
}
self.WCG[i].push((j, scalar))
}
Variable::CH { commitment: i, index: j } => {
self.highest_c_index = self.highest_c_index.max(Some(i));
self.highest_a_index = self.highest_a_index.max(Some(j));
while self.WCH.len() <= i {
self.WCH.push(vec![]);
}
self.WCH[i].push((j, scalar))
}
Variable::V(i) => {
self.highest_v_index = self.highest_v_index.max(Some(i));
self.WV.push((i, scalar));
@@ -238,11 +205,6 @@ impl<F: PrimeField> LinComb<F> {
&self.WCG
}
/// View the current weights for CH.
pub fn WCH(&self) -> &[Vec<(usize, F)>] {
&self.WCH
}
/// View the current weights for V.
pub fn WV(&self) -> &[(usize, F)] {
&self.WV

View File

@@ -1,4 +1,5 @@
use core::ops::{Index, IndexMut};
use std_shims::vec::Vec;
use zeroize::Zeroize;

View File

@@ -1,4 +1,5 @@
use core::ops::{Index, IndexMut, Add, Sub, Mul};
use std_shims::{vec, vec::Vec};
use zeroize::Zeroize;

View File

@@ -3,7 +3,7 @@ use rand_core::{RngCore, OsRng};
use ciphersuite::{group::ff::Field, Ciphersuite, Ristretto};
use crate::{
ScalarVector, PedersenCommitment, PedersenVectorCommitment,
ScalarVector, PedersenCommitment, PedersenVectorCommitment, Generators,
transcript::*,
arithmetic_circuit_proof::{
Variable, LinComb, ArithmeticCircuitStatement, ArithmeticCircuitWitness,
@@ -43,7 +43,7 @@ fn test_zero_arithmetic_circuit() {
statement.clone().prove(&mut OsRng, &mut transcript, witness).unwrap();
transcript.complete()
};
let mut verifier = generators.batch_verifier();
let mut verifier = Generators::batch_verifier();
let mut transcript = VerifierTranscript::new([0; 32], &proof);
let verifier_commmitments = transcript.read_commitments(0, 1);
@@ -59,14 +59,8 @@ fn test_vector_commitment_arithmetic_circuit() {
let v1 = <Ristretto as Ciphersuite>::F::random(&mut OsRng);
let v2 = <Ristretto as Ciphersuite>::F::random(&mut OsRng);
let v3 = <Ristretto as Ciphersuite>::F::random(&mut OsRng);
let v4 = <Ristretto as Ciphersuite>::F::random(&mut OsRng);
let gamma = <Ristretto as Ciphersuite>::F::random(&mut OsRng);
let commitment = (reduced.g_bold(0) * v1) +
(reduced.g_bold(1) * v2) +
(reduced.h_bold(0) * v3) +
(reduced.h_bold(1) * v4) +
(generators.h() * gamma);
let commitment = (reduced.g_bold(0) * v1) + (reduced.g_bold(1) * v2) + (generators.h() * gamma);
let V = vec![];
let C = vec![commitment];
@@ -83,20 +77,14 @@ fn test_vector_commitment_arithmetic_circuit() {
vec![LinComb::empty()
.term(<Ristretto as Ciphersuite>::F::ONE, Variable::CG { commitment: 0, index: 0 })
.term(<Ristretto as Ciphersuite>::F::from(2u64), Variable::CG { commitment: 0, index: 1 })
.term(<Ristretto as Ciphersuite>::F::from(3u64), Variable::CH { commitment: 0, index: 0 })
.term(<Ristretto as Ciphersuite>::F::from(4u64), Variable::CH { commitment: 0, index: 1 })
.constant(-(v1 + (v2 + v2) + (v3 + v3 + v3) + (v4 + v4 + v4 + v4)))],
.constant(-(v1 + (v2 + v2)))],
commitments.clone(),
)
.unwrap();
let witness = ArithmeticCircuitWitness::<Ristretto>::new(
aL,
aR,
vec![PedersenVectorCommitment {
g_values: ScalarVector(vec![v1, v2]),
h_values: ScalarVector(vec![v3, v4]),
mask: gamma,
}],
vec![PedersenVectorCommitment { g_values: ScalarVector(vec![v1, v2]), mask: gamma }],
vec![],
)
.unwrap();
@@ -105,7 +93,7 @@ fn test_vector_commitment_arithmetic_circuit() {
statement.clone().prove(&mut OsRng, &mut transcript, witness).unwrap();
transcript.complete()
};
let mut verifier = generators.batch_verifier();
let mut verifier = Generators::batch_verifier();
let mut transcript = VerifierTranscript::new([0; 32], &proof);
let verifier_commmitments = transcript.read_commitments(1, 0);
@@ -139,13 +127,8 @@ fn fuzz_test_arithmetic_circuit() {
while g_values.0.len() < ((OsRng.next_u64() % 8) + 1).try_into().unwrap() {
g_values.0.push(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
}
let mut h_values = ScalarVector(vec![]);
while h_values.0.len() < ((OsRng.next_u64() % 8) + 1).try_into().unwrap() {
h_values.0.push(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
}
C.push(PedersenVectorCommitment {
g_values,
h_values,
mask: <Ristretto as Ciphersuite>::F::random(&mut OsRng),
});
}
@@ -193,13 +176,6 @@ fn fuzz_test_arithmetic_circuit() {
constraint = constraint.term(weight, Variable::CG { commitment, index });
eval += weight * C.g_values[index];
}
for _ in 0 .. (OsRng.next_u64() % 4) {
let index = usize::try_from(OsRng.next_u64()).unwrap() % C.h_values.len();
let weight = <Ristretto as Ciphersuite>::F::random(&mut OsRng);
constraint = constraint.term(weight, Variable::CH { commitment, index });
eval += weight * C.h_values[index];
}
}
if !V.is_empty() {
@@ -218,11 +194,7 @@ fn fuzz_test_arithmetic_circuit() {
let mut transcript = Transcript::new([0; 32]);
let commitments = transcript.write_commitments(
C.iter()
.map(|C| {
C.commit(generators.g_bold_slice(), generators.h_bold_slice(), generators.h()).unwrap()
})
.collect(),
C.iter().map(|C| C.commit(generators.g_bold_slice(), generators.h()).unwrap()).collect(),
V.iter().map(|V| V.commit(generators.g(), generators.h())).collect(),
);
@@ -239,7 +211,7 @@ fn fuzz_test_arithmetic_circuit() {
statement.clone().prove(&mut OsRng, &mut transcript, witness).unwrap();
transcript.complete()
};
let mut verifier = generators.batch_verifier();
let mut verifier = Generators::batch_verifier();
let mut transcript = VerifierTranscript::new([0; 32], &proof);
let verifier_commmitments = transcript.read_commitments(C.len(), V.len());

View File

@@ -8,7 +8,7 @@ use ciphersuite::{
};
use crate::{
ScalarVector, PointVector,
ScalarVector, PointVector, Generators,
transcript::*,
inner_product::{P, IpStatement, IpWitness},
tests::generators,
@@ -41,7 +41,7 @@ fn test_zero_inner_product() {
transcript.complete()
};
let mut verifier = generators.batch_verifier();
let mut verifier = Generators::batch_verifier();
IpStatement::<Ristretto>::new(
reduced,
ScalarVector(vec![<Ristretto as Ciphersuite>::F::ONE; 1]),
@@ -58,7 +58,7 @@ fn test_zero_inner_product() {
fn test_inner_product() {
// P = sum(g_bold * a, h_bold * b)
let generators = generators::<Ristretto>(32);
let mut verifier = generators.batch_verifier();
let mut verifier = Generators::batch_verifier();
for i in [1, 2, 4, 8, 16, 32] {
let generators = generators.reduce(i).unwrap();
let g = generators.g();

View File

@@ -1,9 +1,12 @@
use std::io;
use std_shims::{vec::Vec, io};
use blake2::{Digest, Blake2b512};
use ciphersuite::{
group::{ff::PrimeField, GroupEncoding},
group::{
ff::{Field, PrimeField},
GroupEncoding,
},
Ciphersuite,
};
@@ -13,27 +16,11 @@ const SCALAR: u8 = 0;
const POINT: u8 = 1;
const CHALLENGE: u8 = 2;
fn challenge<F: PrimeField>(digest: &mut Blake2b512) -> F {
// Panic if this is such a wide field, we won't successfully perform a reduction into an unbiased
// scalar
debug_assert!((F::NUM_BITS + 128) < 512);
fn challenge<C: Ciphersuite>(digest: &mut Blake2b512) -> C::F {
digest.update([CHALLENGE]);
let chl = digest.clone().finalize();
let chl = digest.clone().finalize().into();
let mut res = F::ZERO;
for (i, mut byte) in chl.iter().cloned().enumerate() {
for j in 0 .. 8 {
let lsb = byte & 1;
let mut bit = F::from(u64::from(lsb));
for _ in 0 .. ((i * 8) + j) {
bit = bit.double();
}
res += bit;
byte >>= 1;
}
}
let res = C::reduce_512(chl);
// Negligible probability
if bool::from(res.is_zero()) {
@@ -83,6 +70,8 @@ impl Transcript {
}
/// Push a scalar onto the transcript.
///
/// The order and layout of this must be constant to the context.
pub fn push_scalar(&mut self, scalar: impl PrimeField) {
self.digest.update([SCALAR]);
let bytes = scalar.to_repr();
@@ -91,6 +80,8 @@ impl Transcript {
}
/// Push a point onto the transcript.
///
/// The order and layout of this must be constant to the context.
pub fn push_point(&mut self, point: impl GroupEncoding) {
self.digest.update([POINT]);
let bytes = point.to_bytes();
@@ -104,9 +95,11 @@ impl Transcript {
C: Vec<C::G>,
V: Vec<C::G>,
) -> Commitments<C> {
self.digest.update(u32::try_from(C.len()).unwrap().to_le_bytes());
for C in &C {
self.push_point(*C);
}
self.digest.update(u32::try_from(V.len()).unwrap().to_le_bytes());
for V in &V {
self.push_point(*V);
}
@@ -114,8 +107,14 @@ impl Transcript {
}
/// Sample a challenge.
pub fn challenge<F: PrimeField>(&mut self) -> F {
challenge(&mut self.digest)
pub fn challenge<C: Ciphersuite>(&mut self) -> C::F {
challenge::<C>(&mut self.digest)
}
/// Sample a challenge as a byte array.
pub fn challenge_bytes(&mut self) -> [u8; 64] {
self.digest.update([CHALLENGE]);
self.digest.clone().finalize().into()
}
/// Complete a transcript, yielding the fully serialized proof.
@@ -139,20 +138,36 @@ impl<'a> VerifierTranscript<'a> {
}
/// Read a scalar from the transcript.
///
/// The order and layout of this must be constant to the context.
pub fn read_scalar<C: Ciphersuite>(&mut self) -> io::Result<C::F> {
let scalar = C::read_F(&mut self.transcript)?;
// Read the scalar onto the transcript using the serialization present in the transcript
self.digest.update([SCALAR]);
let bytes = scalar.to_repr();
self.digest.update(bytes);
let scalar_len = <C::F as PrimeField>::Repr::default().as_ref().len();
if self.transcript.len() < scalar_len {
Err(io::Error::new(io::ErrorKind::Other, "not enough bytes to read_scalar"))?;
}
self.digest.update(&self.transcript[.. scalar_len]);
// Read the actual scalar, where `read_F` ensures its canonically serialized
let scalar = C::read_F(&mut self.transcript)?;
Ok(scalar)
}
/// Read a point from the transcript.
///
/// The order and layout of this must be constant to the context.
pub fn read_point<C: Ciphersuite>(&mut self) -> io::Result<C::G> {
let point = C::read_G(&mut self.transcript)?;
// Read the point onto the transcript using the serialization present in the transcript
self.digest.update([POINT]);
let bytes = point.to_bytes();
self.digest.update(bytes);
let point_len = <C::G as GroupEncoding>::Repr::default().as_ref().len();
if self.transcript.len() < point_len {
Err(io::Error::new(io::ErrorKind::Other, "not enough bytes to read_point"))?;
}
self.digest.update(&self.transcript[.. point_len]);
// Read the actual point, where `read_G` ensures its canonically serialized
let point = C::read_G(&mut self.transcript)?;
Ok(point)
}
@@ -165,10 +180,12 @@ impl<'a> VerifierTranscript<'a> {
C: usize,
V: usize,
) -> io::Result<Commitments<C>> {
self.digest.update(u32::try_from(C).unwrap().to_le_bytes());
let mut C_vec = Vec::with_capacity(C);
for _ in 0 .. C {
C_vec.push(self.read_point::<C>()?);
}
self.digest.update(u32::try_from(V).unwrap().to_le_bytes());
let mut V_vec = Vec::with_capacity(V);
for _ in 0 .. V {
V_vec.push(self.read_point::<C>()?);
@@ -177,11 +194,17 @@ impl<'a> VerifierTranscript<'a> {
}
/// Sample a challenge.
pub fn challenge<F: PrimeField>(&mut self) -> F {
challenge(&mut self.digest)
pub fn challenge<C: Ciphersuite>(&mut self) -> C::F {
challenge::<C>(&mut self.digest)
}
/// Complete the transcript, returning the advanced slice.
/// Sample a challenge as a byte array.
pub fn challenge_bytes(&mut self) -> [u8; 64] {
self.digest.update([CHALLENGE]);
self.digest.clone().finalize().into()
}
/// Complete the transcript transcript, yielding what remains.
pub fn complete(self) -> &'a [u8] {
self.transcript
}

View File

@@ -17,20 +17,22 @@ rustdoc-args = ["--cfg", "docsrs"]
rustversion = "1"
hex-literal = { version = "0.4", default-features = false }
rand_core = { version = "0.6", default-features = false, features = ["std"] }
std-shims = { version = "0.1", path = "../../../common/std-shims", default-features = false, optional = true }
zeroize = { version = "^1.5", default-features = false, features = ["std", "zeroize_derive"] }
subtle = { version = "^2.4", default-features = false, features = ["std"] }
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 }
generic-array = { version = "0.14", default-features = false }
crypto-bigint = { version = "0.5", default-features = false, features = ["zeroize"] }
k256 = { version = "0.13", default-features = false, features = ["arithmetic"] }
blake2 = { version = "0.10", default-features = false, features = ["std"] }
ciphersuite = { path = "../../ciphersuite", version = "0.4", default-features = false, features = ["std"] }
ec-divisors = { path = "../divisors" }
generalized-bulletproofs-ec-gadgets = { path = "../ec-gadgets" }
blake2 = { version = "0.10", default-features = false }
ciphersuite = { path = "../../ciphersuite", version = "0.4", default-features = false }
ec-divisors = { path = "../divisors", default-features = false }
generalized-bulletproofs-ec-gadgets = { path = "../ec-gadgets", default-features = false }
[dev-dependencies]
hex = "0.4"
@@ -38,3 +40,8 @@ hex = "0.4"
rand_core = { version = "0.6", features = ["std"] }
ff-group-tests = { path = "../../ff-group-tests" }
[features]
alloc = ["std-shims", "zeroize/alloc", "ciphersuite/alloc"]
std = ["std-shims/std", "rand_core/std", "zeroize/std", "subtle/std", "blake2/std", "ciphersuite/std", "ec-divisors/std", "generalized-bulletproofs-ec-gadgets/std"]
default = ["std"]

View File

@@ -1,5 +1,9 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(any(feature = "alloc", feature = "std"))]
use std_shims::io::{self, Read};
use generic_array::typenum::{Sum, Diff, Quot, U, U1, U2};
use ciphersuite::group::{ff::PrimeField, Group};
@@ -33,10 +37,29 @@ impl ciphersuite::Ciphersuite for Secq256k1 {
Point::generator()
}
fn reduce_512(scalar: [u8; 64]) -> Self::F {
Scalar::wide_reduce(scalar)
}
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
use blake2::Digest;
Scalar::wide_reduce(Self::H::digest([dst, data].concat()).as_slice().try_into().unwrap())
}
// We override the provided impl, which compares against the reserialization, because
// we already require canonicity
#[cfg(any(feature = "alloc", feature = "std"))]
#[allow(non_snake_case)]
fn read_G<R: Read>(reader: &mut R) -> io::Result<Self::G> {
use ciphersuite::group::GroupEncoding;
let mut encoding = <Self::G as GroupEncoding>::Repr::default();
reader.read_exact(encoding.as_mut())?;
let point = Option::<Self::G>::from(Self::G::from_bytes(&encoding))
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "invalid point"))?;
Ok(point)
}
}
impl generalized_bulletproofs_ec_gadgets::DiscreteLogParameters for Secq256k1 {