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
No known key found for this signature in database
35 changed files with 456 additions and 340 deletions

12
Cargo.lock generated
View file

@ -2470,6 +2470,7 @@ dependencies = [
"hex",
"pasta_curves",
"rand_core",
"std-shims",
"subtle",
"zeroize",
]
@ -2570,6 +2571,7 @@ dependencies = [
"hex-literal",
"rand_core",
"rustversion",
"std-shims",
"subtle",
"zeroize",
]
@ -3293,6 +3295,7 @@ dependencies = [
"flexible-transcript",
"multiexp",
"rand_core",
"std-shims",
"zeroize",
]
@ -3302,6 +3305,7 @@ version = "0.1.0"
dependencies = [
"ciphersuite",
"generalized-bulletproofs",
"std-shims",
"zeroize",
]
@ -3312,6 +3316,7 @@ dependencies = [
"ciphersuite",
"generalized-bulletproofs-circuit-abstraction",
"generic-array 1.1.1",
"std-shims",
]
[[package]]
@ -8822,6 +8827,7 @@ dependencies = [
"k256",
"rand_core",
"rustversion",
"std-shims",
"subtle",
"zeroize",
]
@ -9432,11 +9438,17 @@ dependencies = [
"dalek-ff-group",
"dkg",
"dleq",
"ec-divisors",
"embedwards25519",
"flexible-transcript",
"generalized-bulletproofs",
"generalized-bulletproofs-circuit-abstraction",
"generalized-bulletproofs-ec-gadgets",
"minimal-ed448",
"monero-wallet-util",
"multiexp",
"schnorr-signatures",
"secq256k1",
]
[[package]]

View file

@ -28,6 +28,12 @@ macro_rules! dalek_curve {
$Point::generator()
}
fn reduce_512(mut scalar: [u8; 64]) -> Self::F {
let res = Scalar::from_bytes_mod_order_wide(&scalar);
scalar.zeroize();
res
}
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
Scalar::from_hash(Sha512::new_with_prefix(&[dst, data].concat()))
}

View file

@ -66,6 +66,12 @@ impl Ciphersuite for Ed448 {
Point::generator()
}
fn reduce_512(mut scalar: [u8; 64]) -> Self::F {
let res = Self::hash_to_F(b"Ciphersuite-reduce_512", &scalar);
scalar.zeroize();
res
}
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
Scalar::wide_reduce(Self::H::digest([dst, data].concat()).as_ref().try_into().unwrap())
}

View file

@ -6,7 +6,7 @@ use group::ff::PrimeField;
use elliptic_curve::{
generic_array::GenericArray,
bigint::{NonZero, CheckedAdd, Encoding, U384},
bigint::{NonZero, CheckedAdd, Encoding, U384, U512},
hash2curve::{Expander, ExpandMsg, ExpandMsgXmd},
};
@ -31,6 +31,22 @@ macro_rules! kp_curve {
$lib::ProjectivePoint::GENERATOR
}
fn reduce_512(scalar: [u8; 64]) -> Self::F {
let mut modulus = [0; 64];
modulus[32 ..].copy_from_slice(&(Self::F::ZERO - Self::F::ONE).to_bytes());
let modulus = U512::from_be_slice(&modulus).checked_add(&U512::ONE).unwrap();
let mut wide =
U512::from_be_bytes(scalar).rem(&NonZero::new(modulus).unwrap()).to_be_bytes();
let mut array = *GenericArray::from_slice(&wide[32 ..]);
let res = $lib::Scalar::from_repr(array).unwrap();
wide.zeroize();
array.zeroize();
res
}
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F {
// While one of these two libraries does support directly hashing to the Scalar field, the
// other doesn't. While that's probably an oversight, this is a universally working method

View file

@ -62,6 +62,12 @@ pub trait Ciphersuite:
// While group does provide this in its API, privacy coins may want to use a custom basepoint
fn generator() -> Self::G;
/// Reduce 512 bits into a uniform scalar.
///
/// If 512 bits is insufficient to perform a reduction into a uniform scalar, the ciphersuite
/// will perform a hash to sample the necessary bits.
fn reduce_512(scalar: [u8; 64]) -> Self::F;
/// Hash the provided domain-separation tag and message to a scalar. Ciphersuites MAY naively
/// prefix the tag to the message, enabling transpotion between the two. Accordingly, this
/// function should NOT be used in any scheme where one tag is a valid substring of another
@ -99,6 +105,9 @@ pub trait Ciphersuite:
}
/// Read a canonical point from something implementing std::io::Read.
///
/// The provided implementation is safe so long as `GroupEncoding::to_bytes` always returns a
/// canonical serialization.
#[cfg(any(feature = "alloc", feature = "std"))]
#[allow(non_snake_case)]
fn read_G<R: Read>(reader: &mut R) -> io::Result<Self::G> {

View file

@ -54,7 +54,7 @@ rand = { version = "0.8", default-features = false, features = ["std"] }
ciphersuite = { path = "../ciphersuite", default-features = false, features = ["ristretto"] }
generalized-bulletproofs = { path = "../evrf/generalized-bulletproofs", features = ["tests"] }
ec-divisors = { path = "../evrf/divisors", features = ["pasta"] }
pasta_curves = "0.5"
pasta_curves = { git = "https://github.com/kayabaNerve/pasta_curves", rev = "a46b5be95cacbff54d06aad8d3bbcba42e05d616" }
[features]
std = [

View file

@ -85,7 +85,7 @@ use ciphersuite::{
};
use multiexp::multiexp_vartime;
use generalized_bulletproofs::arithmetic_circuit_proof::*;
use generalized_bulletproofs::{Generators, arithmetic_circuit_proof::*};
use ec_divisors::DivisorCurve;
use crate::{Participant, ThresholdParams, Interpolation, ThresholdCore, ThresholdKeys};
@ -277,6 +277,7 @@ impl<C: EvrfCurve> EvrfDkg<C> {
if evrf_public_keys.iter().any(|key| bool::from(key.is_identity())) {
Err(EvrfError::PublicKeyWasIdentity)?;
};
// This also checks the private key is not 0
let evrf_public_key = <C::EmbeddedCurve as Ciphersuite>::generator() * evrf_private_key.deref();
if !evrf_public_keys.iter().any(|key| *key == evrf_public_key) {
Err(EvrfError::NotAParticipant)?;
@ -359,7 +360,7 @@ impl<C: EvrfCurve> EvrfDkg<C> {
let transcript = Self::initial_transcript(context, evrf_public_keys, t);
let mut evrf_verifier = generators.0.batch_verifier();
let mut evrf_verifier = Generators::batch_verifier();
for (i, participation) in participations {
let evrf_public_key = evrf_public_keys[usize::from(u16::from(*i)) - 1];
@ -395,7 +396,7 @@ impl<C: EvrfCurve> EvrfDkg<C> {
if faulty.contains(i) {
continue;
}
let mut evrf_verifier = generators.0.batch_verifier();
let mut evrf_verifier = Generators::batch_verifier();
Evrf::<C>::verify(
rng,
&generators.0,

View file

@ -129,15 +129,11 @@ impl<C: EvrfCurve> Evrf<C> {
/// Read a Variable from a theoretical vector commitment tape
fn read_one_from_tape(generators_to_use: usize, start: &mut usize) -> Variable {
// Each commitment has twice as many variables as generators in use
let commitment = *start / (2 * generators_to_use);
let commitment = *start / generators_to_use;
// The index will be less than the amount of generators in use, as half are left and half are
// right
let index = *start % generators_to_use;
let res = if (*start / generators_to_use) % 2 == 0 {
Variable::CG { commitment, index }
} else {
Variable::CH { commitment, index }
};
let res = Variable::CG { commitment, index };
*start += 1;
res
}
@ -202,8 +198,8 @@ impl<C: EvrfCurve> Evrf<C> {
padded_pow_of_2 <<= 1;
}
// This may as small as 16, which would create an excessive amount of vector commitments
// We set a floor of 1024 rows for bandwidth reasons
padded_pow_of_2.max(1024)
// We set a floor of 2048 rows for bandwidth reasons
padded_pow_of_2.max(2048)
};
(expected_muls, generators_to_use)
}
@ -213,7 +209,7 @@ impl<C: EvrfCurve> Evrf<C> {
evrf_public_key: (C::F, C::F),
coefficients: usize,
ecdh_commitments: &[[(C::F, C::F); 2]],
generator_tables: &[GeneratorTable<C::F, C::EmbeddedCurveParameters>],
generator_tables: &[&GeneratorTable<C::F, C::EmbeddedCurveParameters>],
circuit: &mut Circuit<C>,
transcript: &mut impl Transcript,
) {
@ -376,8 +372,10 @@ impl<C: EvrfCurve> Evrf<C> {
let evrf_public_key;
let mut actual_coefficients = Vec::with_capacity(coefficients);
{
// This is checked at a higher level
let dlog =
ScalarDecomposition::<<C::EmbeddedCurve as Ciphersuite>::F>::new(**evrf_private_key);
ScalarDecomposition::<<C::EmbeddedCurve as Ciphersuite>::F>::new(**evrf_private_key)
.expect("eVRF private key was zero");
let points = Self::transcript_to_points(transcript, coefficients);
// Start by pushing the discrete logarithm onto the tape
@ -431,7 +429,8 @@ impl<C: EvrfCurve> Evrf<C> {
}
}
let dlog =
ScalarDecomposition::<<C::EmbeddedCurve as Ciphersuite>::F>::new(ecdh_private_key);
ScalarDecomposition::<<C::EmbeddedCurve as Ciphersuite>::F>::new(ecdh_private_key)
.expect("ECDH private key was zero");
let ecdh_commitment = <C::EmbeddedCurve as Ciphersuite>::generator() * ecdh_private_key;
ecdh_commitments.push(ecdh_commitment);
ecdh_commitments_xy.last_mut().unwrap()[j] =
@ -471,15 +470,10 @@ impl<C: EvrfCurve> Evrf<C> {
Self::muls_and_generators_to_use(coefficients, ecdh_public_keys.len());
let mut vector_commitments =
Vec::with_capacity(vector_commitment_tape.len().div_ceil(2 * generators_to_use));
for chunk in vector_commitment_tape.chunks(2 * generators_to_use) {
Vec::with_capacity(vector_commitment_tape.len().div_ceil(generators_to_use));
for chunk in vector_commitment_tape.chunks(generators_to_use) {
let g_values = chunk[.. generators_to_use.min(chunk.len())].to_vec().into();
let h_values = chunk[generators_to_use.min(chunk.len()) ..].to_vec().into();
vector_commitments.push(PedersenVectorCommitment {
g_values,
h_values,
mask: C::F::random(&mut *rng),
});
vector_commitments.push(PedersenVectorCommitment { g_values, mask: C::F::random(&mut *rng) });
}
vector_commitment_tape.zeroize();
@ -499,7 +493,7 @@ impl<C: EvrfCurve> Evrf<C> {
.iter()
.map(|commitment| {
commitment
.commit(generators.g_bold_slice(), generators.h_bold_slice(), generators.h())
.commit(generators.g_bold_slice(), generators.h())
.ok_or(AcError::NotEnoughGenerators)
})
.collect::<Result<_, _>>()?,
@ -518,7 +512,7 @@ impl<C: EvrfCurve> Evrf<C> {
evrf_public_key,
coefficients,
&ecdh_commitments_xy,
&generator_tables,
&generator_tables.iter().collect::<Vec<_>>(),
&mut circuit,
&mut transcript,
);
@ -543,7 +537,7 @@ impl<C: EvrfCurve> Evrf<C> {
let mut agg_weights = Vec::with_capacity(commitments.len());
agg_weights.push(C::F::ONE);
while agg_weights.len() < commitments.len() {
agg_weights.push(transcript.challenge::<C::F>());
agg_weights.push(transcript.challenge::<C>());
}
let mut x = commitments
.iter()
@ -554,7 +548,7 @@ impl<C: EvrfCurve> Evrf<C> {
// Do a Schnorr PoK for the randomness of the aggregated Pedersen commitment
let mut r = C::F::random(&mut *rng);
transcript.push_point(generators.h() * r);
let c = transcript.challenge::<C::F>();
let c = transcript.challenge::<C>();
transcript.push_scalar(r + (c * x));
r.zeroize();
x.zeroize();
@ -615,7 +609,7 @@ impl<C: EvrfCurve> Evrf<C> {
let coeffs_vc_variables = dlog_len + ((1 + (2 * coefficients)) * dlog_proof_len);
let ecdhs_vc_variables = ((2 * ecdh_public_keys.len()) * dlog_len) +
((2 * 2 * ecdh_public_keys.len()) * dlog_proof_len);
let vcs = (coeffs_vc_variables + ecdhs_vc_variables).div_ceil(2 * generators_to_use);
let vcs = (coeffs_vc_variables + ecdhs_vc_variables).div_ceil(generators_to_use);
let all_commitments =
transcript.read_commitments(vcs, coefficients + ecdh_public_keys.len()).map_err(|_| ())?;
@ -642,7 +636,7 @@ impl<C: EvrfCurve> Evrf<C> {
<C::EmbeddedCurve as Ciphersuite>::G::to_xy(evrf_public_key).ok_or(())?,
coefficients,
&ecdh_keys_xy,
&generator_tables,
&generator_tables.iter().collect::<Vec<_>>(),
&mut circuit,
&mut transcript,
);
@ -665,7 +659,7 @@ impl<C: EvrfCurve> Evrf<C> {
let mut agg_weights = Vec::with_capacity(commitments.len());
agg_weights.push(C::F::ONE);
while agg_weights.len() < commitments.len() {
agg_weights.push(transcript.challenge::<C::F>());
agg_weights.push(transcript.challenge::<C>());
}
let sum_points =
@ -677,7 +671,7 @@ impl<C: EvrfCurve> Evrf<C> {
#[allow(non_snake_case)]
let R = transcript.read_point::<C>().map_err(|_| ())?;
let c = transcript.challenge::<C::F>();
let c = transcript.challenge::<C>();
let s = transcript.read_scalar::<C>().map_err(|_| ())?;
// Doesn't batch verify this as we can't access the internals of the GBP batch verifier

View file

@ -15,7 +15,7 @@ use ciphersuite::{
};
use pasta_curves::{Ep, Eq, Fp, Fq};
use generalized_bulletproofs::tests::generators;
use generalized_bulletproofs::{Generators, tests::generators};
use generalized_bulletproofs_ec_gadgets::DiscreteLogParameters;
use crate::evrf::proof::*;
@ -35,6 +35,9 @@ impl Ciphersuite for Pallas {
// This is solely test code so it's fine
Self::F::from_uniform_bytes(&Self::H::digest([dst, msg].concat()).into())
}
fn reduce_512(scalar: [u8; 64]) -> Self::F {
Self::F::from_uniform_bytes(&scalar)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
@ -52,6 +55,9 @@ impl Ciphersuite for Vesta {
// This is solely test code so it's fine
Self::F::from_uniform_bytes(&Self::H::digest([dst, msg].concat()).into())
}
fn reduce_512(scalar: [u8; 64]) -> Self::F {
Self::F::from_uniform_bytes(&scalar)
}
}
pub struct VestaParams;
@ -68,7 +74,7 @@ impl EvrfCurve for Pallas {
}
fn evrf_proof_test<C: EvrfCurve>() {
let generators = generators(1024);
let generators = generators(2048);
let vesta_private_key = Zeroizing::new(<C::EmbeddedCurve as Ciphersuite>::F::random(&mut OsRng));
let ecdh_public_keys = [
<C::EmbeddedCurve as Ciphersuite>::G::random(&mut OsRng),
@ -81,7 +87,7 @@ fn evrf_proof_test<C: EvrfCurve>() {
println!("Proving time: {:?}", time.elapsed());
let time = Instant::now();
let mut verifier = generators.batch_verifier();
let mut verifier = Generators::batch_verifier();
Evrf::<C>::verify(
&mut OsRng,
&generators,

View file

@ -28,6 +28,10 @@ impl<C: Ciphersuite> Ciphersuite for AltGenerator<C> {
C::G::generator() * <C as Ciphersuite>::hash_to_F(b"DKG Promotion Test", b"generator")
}
fn reduce_512(scalar: [u8; 64]) -> Self::F {
<C as Ciphersuite>::reduce_512(scalar)
}
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
<C as Ciphersuite>::hash_to_F(dst, data)
}

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 {

View file

@ -30,4 +30,4 @@ p256 = { version = "^0.13.1", default-features = false, features = ["std", "arit
bls12_381 = "0.8"
pasta_curves = "0.5"
pasta_curves = { git = "https://github.com/kayabaNerve/pasta_curves", rev = "a46b5be95cacbff54d06aad8d3bbcba42e05d616" }

View file

@ -29,6 +29,13 @@ multiexp = { path = "../../crypto/multiexp", default-features = false, features
dleq = { path = "../../crypto/dleq", default-features = false }
schnorr-signatures = { path = "../../crypto/schnorr", default-features = false }
secq256k1 = { path = "../../crypto/evrf/secq256k1", default-features = false }
embedwards25519 = { path = "../../crypto/evrf/embedwards25519", default-features = false }
generalized-bulletproofs = { path = "../../crypto/evrf/generalized-bulletproofs", default-features = false }
generalized-bulletproofs-circuit-abstraction = { path = "../../crypto/evrf/circuit-abstraction", default-features = false }
ec-divisors = { path = "../../crypto/evrf/divisors", default-features = false }
generalized-bulletproofs-ec-gadgets = { path = "../../crypto/evrf/ec-gadgets", default-features = false }
dkg = { path = "../../crypto/dkg", default-features = false }
# modular-frost = { path = "../../crypto/frost", default-features = false }
# frost-schnorrkel = { path = "../../crypto/schnorrkel", default-features = false }

View file

@ -12,6 +12,13 @@ pub use multiexp;
pub use dleq;
pub use schnorr_signatures;
pub use secq256k1;
pub use embedwards25519;
pub use generalized_bulletproofs;
pub use generalized_bulletproofs_circuit_abstraction;
pub use ec_divisors;
pub use generalized_bulletproofs_ec_gadgets;
pub use dkg;
/*
pub use modular_frost;