From 1d454d2cd7fe6c921d571cb17f4283ddf431061a Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 13 Aug 2022 20:51:48 -0400 Subject: [PATCH 1/8] Add Curve as syntactic sugar around ff/group --- Cargo.lock | 13 ++- Cargo.toml | 2 + crypto/curve/Cargo.toml | 15 +++ crypto/curve/src/lib.rs | 92 ++++++++++++++++ crypto/dleq/Cargo.toml | 3 +- crypto/dleq/src/cross_group/aos.rs | 109 +++++++++---------- crypto/dleq/src/cross_group/bits.rs | 57 ++++------ crypto/dleq/src/cross_group/mod.rs | 94 +++++++--------- crypto/dleq/src/cross_group/scalar.rs | 4 +- crypto/dleq/src/cross_group/schnorr.rs | 47 ++++---- crypto/dleq/src/lib.rs | 63 +++++------ crypto/dleq/src/tests/cross_group/aos.rs | 2 +- crypto/dleq/src/tests/cross_group/mod.rs | 28 +++-- crypto/dleq/src/tests/cross_group/scalar.rs | 2 +- crypto/dleq/src/tests/cross_group/schnorr.rs | 6 +- crypto/dleq/src/tests/mod.rs | 6 +- 16 files changed, 311 insertions(+), 232 deletions(-) create mode 100644 crypto/curve/Cargo.toml create mode 100644 crypto/curve/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 501aac7d2..4473b5680 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1000,6 +1000,16 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "composable-curve" +version = "0.12.0" +dependencies = [ + "ff", + "group", + "thiserror", + "zeroize", +] + [[package]] name = "concurrent-queue" version = "1.2.2" @@ -1529,11 +1539,10 @@ name = "dleq" version = "0.1.0" dependencies = [ "blake2", + "composable-curve", "dalek-ff-group", "digest 0.10.3", - "ff", "flexible-transcript", - "group", "hex-literal", "k256", "multiexp", diff --git a/Cargo.toml b/Cargo.toml index 3b7eb7d09..1c91daeb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,9 @@ members = [ "crypto/transcript", + "crypto/curve", "crypto/dalek-ff-group", + "crypto/multiexp", "crypto/dleq", diff --git a/crypto/curve/Cargo.toml b/crypto/curve/Cargo.toml new file mode 100644 index 000000000..a8e879f7f --- /dev/null +++ b/crypto/curve/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "composable-curve" +version = "0.12.0" +description = "Sugar around ff/group" +license = "MIT" +authors = ["Luke Parker "] +edition = "2021" + +[dependencies] +thiserror = "1" + +zeroize = "1.3" + +ff = { version = "0.12", features = ["bits"] } +group = "0.12" diff --git a/crypto/curve/src/lib.rs b/crypto/curve/src/lib.rs new file mode 100644 index 000000000..4ecf70281 --- /dev/null +++ b/crypto/curve/src/lib.rs @@ -0,0 +1,92 @@ +use core::fmt::Debug; +use std::io::Read; + +use thiserror::Error; + +use zeroize::Zeroize; + +pub use group; +pub use group::ff; +use group::{ff::{PrimeField, PrimeFieldBits}, Group, GroupOps, GroupEncoding, prime::PrimeGroup}; + +/// Set of errors for curve-related operations, namely encoding and decoding +#[derive(Clone, Error, Debug)] +pub enum CurveError { + #[error("invalid scalar")] + InvalidScalar, + #[error("invalid point")] + InvalidPoint, +} + +/// Curve trait ensuring access to a variety of ff/group APIs +pub trait Curve: Clone + Copy + PartialEq + Eq + Debug { + /// Scalar field element type + type F: Zeroize + PrimeField + PrimeFieldBits; + /// Group element type + type G: Zeroize + Group + GroupOps + PrimeGroup; + + /// Generator for the group + // While group does provide this in its API, multiple schemes frequently require a + // different/variable one + fn generator() -> Self::G; + + /// Length of a serialized Scalar field element + #[allow(non_snake_case)] + fn F_len() -> usize { + ::Repr::default().as_ref().len() + } + + /// Length of a serialized group element + #[allow(non_snake_case)] + fn G_len() -> usize { + ::Repr::default().as_ref().len() + } + + /// Read a canonical Scalar field element from a Reader + #[allow(non_snake_case)] + fn read_F(r: &mut R) -> Result { + let mut encoding = ::Repr::default(); + r.read_exact(encoding.as_mut()).map_err(|_| CurveError::InvalidScalar)?; + + // ff mandates this is canonical + let res = + Option::::from(Self::F::from_repr(encoding)).ok_or(CurveError::InvalidScalar); + for b in encoding.as_mut() { + b.zeroize(); + } + res + } + + /// Read a canonical, non-identity, group element from a Reader + #[allow(non_snake_case)] + fn read_G(r: &mut R) -> Result { + let mut encoding = ::Repr::default(); + r.read_exact(encoding.as_mut()).map_err(|_| CurveError::InvalidPoint)?; + + let point = + Option::::from(Self::G::from_bytes(&encoding)).ok_or(CurveError::InvalidPoint)?; + + if (point.is_identity().into()) || (point.to_bytes().as_ref() != encoding.as_ref()) { + Err(CurveError::InvalidPoint)?; + } + + Ok(point) + } +} + +impl Curve for G where G::Scalar: Zeroize + PrimeField + PrimeFieldBits { + type F = G::Scalar; + type G = G; + + fn generator() -> G { + G::generator() + } +} + +/// Curve implementing hash to curve for its field/group +pub trait HashToCurve: Curve { + #[allow(non_snake_case)] + fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F; + #[allow(non_snake_case)] + fn hash_to_G(dst: &[u8], msg: &[u8]) -> Self::G; +} diff --git a/crypto/dleq/Cargo.toml b/crypto/dleq/Cargo.toml index e544b9964..dc373d299 100644 --- a/crypto/dleq/Cargo.toml +++ b/crypto/dleq/Cargo.toml @@ -16,8 +16,7 @@ digest = "0.10" transcript = { package = "flexible-transcript", path = "../transcript", version = "0.1" } -ff = "0.12" -group = "0.12" +curve = { package = "composable-curve", path = "../curve", version = "0.12" } multiexp = { path = "../multiexp", version = "0.2", features = ["batch"], optional = true } diff --git a/crypto/dleq/src/cross_group/aos.rs b/crypto/dleq/src/cross_group/aos.rs index 0c52d501f..3ab8b6879 100644 --- a/crypto/dleq/src/cross_group/aos.rs +++ b/crypto/dleq/src/cross_group/aos.rs @@ -4,11 +4,11 @@ use zeroize::Zeroize; use transcript::Transcript; -use group::{ - ff::{Field, PrimeFieldBits}, - prime::PrimeGroup, +use curve::{ + ff::Field, + group::{Group, GroupEncoding}, + Curve, CurveError, }; - use multiexp::BatchVerifier; use crate::cross_group::{ @@ -19,48 +19,41 @@ use crate::cross_group::{ #[cfg(feature = "serialize")] use std::io::{Read, Write}; #[cfg(feature = "serialize")] -use ff::PrimeField; -#[cfg(feature = "serialize")] -use crate::{read_scalar, cross_group::read_point}; +use curve::ff::PrimeField; #[allow(non_camel_case_types)] #[derive(Clone, PartialEq, Eq, Debug)] -pub(crate) enum Re { - R(G0, G1), +pub(crate) enum Re { + R(C0::G, C1::G), // Merged challenges have a slight security reduction, yet one already applied to the scalar // being proven for, and this saves ~8kb. Alternatively, challenges could be redefined as a seed, // present here, which is then hashed for each of the two challenges, remaining unbiased/unique // while maintaining the bandwidth savings, yet also while adding 252 hashes for // Secp256k1/Ed25519 - e(G0::Scalar), + e(C0::F), } -impl Re { +impl Re { #[allow(non_snake_case)] - pub(crate) fn R_default() -> Re { - Re::R(G0::identity(), G1::identity()) + pub(crate) fn R_default() -> Re { + Re::R(C0::G::identity(), C1::G::identity()) } - pub(crate) fn e_default() -> Re { - Re::e(G0::Scalar::zero()) + pub(crate) fn e_default() -> Re { + Re::e(C0::F::zero()) } } #[allow(non_snake_case)] #[derive(Clone, PartialEq, Eq, Debug)] -pub(crate) struct Aos { - Re_0: Re, - s: [(G0::Scalar, G1::Scalar); RING_LEN], +pub(crate) struct Aos { + Re_0: Re, + s: [(C0::F, C1::F); RING_LEN], } -impl - Aos -where - G0::Scalar: PrimeFieldBits + Zeroize, - G1::Scalar: PrimeFieldBits + Zeroize, -{ +impl Aos { #[allow(non_snake_case)] - fn nonces(mut transcript: T, nonces: (G0, G1)) -> (G0::Scalar, G1::Scalar) { + fn nonces(mut transcript: T, nonces: (C0::G, C1::G)) -> (C0::F, C1::F) { transcript.domain_separate(b"aos_membership_proof"); transcript.append_message(b"ring_len", &u8::try_from(RING_LEN).unwrap().to_le_bytes()); transcript.append_message(b"nonce_0", nonces.0.to_bytes().as_ref()); @@ -70,32 +63,32 @@ where #[allow(non_snake_case)] fn R( - generators: (Generators, Generators), - s: (G0::Scalar, G1::Scalar), - A: (G0, G1), - e: (G0::Scalar, G1::Scalar), - ) -> (G0, G1) { + generators: (Generators, Generators), + s: (C0::F, C1::F), + A: (C0::G, C1::G), + e: (C0::F, C1::F), + ) -> (C0::G, C1::G) { (((generators.0.alt * s.0) - (A.0 * e.0)), ((generators.1.alt * s.1) - (A.1 * e.1))) } #[allow(non_snake_case)] fn R_batch( - generators: (Generators, Generators), - s: (G0::Scalar, G1::Scalar), - A: (G0, G1), - e: (G0::Scalar, G1::Scalar), - ) -> (Vec<(G0::Scalar, G0)>, Vec<(G1::Scalar, G1)>) { + generators: (Generators, Generators), + s: (C0::F, C1::F), + A: (C0::G, C1::G), + e: (C0::F, C1::F), + ) -> (Vec<(C0::F, C0::G)>, Vec<(C1::F, C1::G)>) { (vec![(-s.0, generators.0.alt), (e.0, A.0)], vec![(-s.1, generators.1.alt), (e.1, A.1)]) } #[allow(non_snake_case)] fn R_nonces( transcript: T, - generators: (Generators, Generators), - s: (G0::Scalar, G1::Scalar), - A: (G0, G1), - e: (G0::Scalar, G1::Scalar), - ) -> (G0::Scalar, G1::Scalar) { + generators: (Generators, Generators), + s: (C0::F, C1::F), + A: (C0::G, C1::G), + e: (C0::F, C1::F), + ) -> (C0::F, C1::F) { Self::nonces(transcript, Self::R(generators, s, A, e)) } @@ -103,20 +96,20 @@ where pub(crate) fn prove( rng: &mut R, transcript: T, - generators: (Generators, Generators), - ring: &[(G0, G1)], + generators: (Generators, Generators), + ring: &[(C0::G, C1::G)], mut actual: usize, - blinding_key: &mut (G0::Scalar, G1::Scalar), - mut Re_0: Re, + blinding_key: &mut (C0::F, C1::F), + mut Re_0: Re, ) -> Self { // While it is possible to use larger values, it's not efficient to do so // 2 + 2 == 2^2, yet 2 + 2 + 2 < 2^3 debug_assert!((RING_LEN == 2) || (RING_LEN == 4)); debug_assert_eq!(RING_LEN, ring.len()); - let mut s = [(G0::Scalar::zero(), G1::Scalar::zero()); RING_LEN]; + let mut s = [(C0::F::zero(), C1::F::zero()); RING_LEN]; - let mut r = (G0::Scalar::random(&mut *rng), G1::Scalar::random(&mut *rng)); + let mut r = (C0::F::random(&mut *rng), C1::F::random(&mut *rng)); #[allow(non_snake_case)] let original_R = (generators.0.alt * r.0, generators.1.alt * r.1); #[allow(non_snake_case)] @@ -146,7 +139,7 @@ where break; // Generate a decoy response } else { - s[i] = (G0::Scalar::random(&mut *rng), G1::Scalar::random(&mut *rng)); + s[i] = (C0::F::random(&mut *rng), C1::F::random(&mut *rng)); } R = Self::R(generators, s[i], ring[i], e); @@ -160,9 +153,9 @@ where &self, rng: &mut R, transcript: T, - generators: (Generators, Generators), - batch: &mut (BatchVerifier<(), G0>, BatchVerifier<(), G1>), - ring: &[(G0, G1)], + generators: (Generators, Generators), + batch: &mut (BatchVerifier<(), C0::G>, BatchVerifier<(), C1::G>), + ring: &[(C0::G, C1::G)], ) -> Result<(), DLEqError> { debug_assert!((RING_LEN == 2) || (RING_LEN == 4)); debug_assert_eq!(RING_LEN, ring.len()); @@ -178,8 +171,8 @@ where let mut statements = Self::R_batch(generators, *self.s.last().unwrap(), *ring.last().unwrap(), e); - statements.0.push((G0::Scalar::one(), R0_0)); - statements.1.push((G1::Scalar::one(), R1_0)); + statements.0.push((C0::F::one(), R0_0)); + statements.1.push((C1::F::one(), R1_0)); batch.0.queue(&mut *rng, (), statements.0); batch.1.queue(&mut *rng, (), statements.1); } @@ -230,18 +223,18 @@ where #[allow(non_snake_case)] #[cfg(feature = "serialize")] - pub(crate) fn deserialize(r: &mut R, mut Re_0: Re) -> std::io::Result { + pub(crate) fn deserialize(r: &mut R, mut Re_0: Re) -> Result { match Re_0 { Re::R(ref mut R0, ref mut R1) => { - *R0 = read_point(r)?; - *R1 = read_point(r)? + *R0 = C0::read_G(r)?; + *R1 = C1::read_G(r)? } - Re::e(ref mut e) => *e = read_scalar(r)?, + Re::e(ref mut e) => *e = C0::read_F(r)?, } - let mut s = [(G0::Scalar::zero(), G1::Scalar::zero()); RING_LEN]; + let mut s = [(C0::F::zero(), C1::F::zero()); RING_LEN]; for s in s.iter_mut() { - *s = (read_scalar(r)?, read_scalar(r)?); + *s = (C0::read_F(r)?, C1::read_F(r)?); } Ok(Aos { Re_0, s }) diff --git a/crypto/dleq/src/cross_group/bits.rs b/crypto/dleq/src/cross_group/bits.rs index 54774cdd4..349bcde30 100644 --- a/crypto/dleq/src/cross_group/bits.rs +++ b/crypto/dleq/src/cross_group/bits.rs @@ -4,7 +4,10 @@ use zeroize::Zeroize; use transcript::Transcript; -use group::{ff::PrimeFieldBits, prime::PrimeGroup}; +use curve::{ + group::{Group, GroupEncoding}, + Curve, CurveError, +}; use multiexp::BatchVerifier; use crate::cross_group::{ @@ -14,8 +17,6 @@ use crate::cross_group::{ #[cfg(feature = "serialize")] use std::io::{Read, Write}; -#[cfg(feature = "serialize")] -use crate::cross_group::read_point; #[allow(clippy::enum_variant_names)] pub(crate) enum BitSignature { @@ -58,7 +59,7 @@ impl BitSignature { 2_usize.pow(self.bits() as u32) } - fn aos_form(&self) -> Re { + fn aos_form(&self) -> Re { match self { BitSignature::ClassicLinear => Re::e_default(), BitSignature::ConciseLinear => Re::e_default(), @@ -69,34 +70,22 @@ impl BitSignature { } #[derive(Clone, PartialEq, Eq, Debug)] -pub(crate) struct Bits< - G0: PrimeGroup + Zeroize, - G1: PrimeGroup + Zeroize, - const SIGNATURE: u8, - const RING_LEN: usize, -> { - pub(crate) commitments: (G0, G1), - signature: Aos, +pub(crate) struct Bits { + pub(crate) commitments: (C0::G, C1::G), + signature: Aos, } -impl< - G0: PrimeGroup + Zeroize, - G1: PrimeGroup + Zeroize, - const SIGNATURE: u8, - const RING_LEN: usize, - > Bits -where - G0::Scalar: PrimeFieldBits + Zeroize, - G1::Scalar: PrimeFieldBits + Zeroize, +impl + Bits { - fn transcript(transcript: &mut T, i: usize, commitments: (G0, G1)) { + fn transcript(transcript: &mut T, i: usize, commitments: (C0::G, C1::G)) { transcript.domain_separate(b"bits"); transcript.append_message(b"group", &u16::try_from(i).unwrap().to_le_bytes()); transcript.append_message(b"commitment_0", commitments.0.to_bytes().as_ref()); transcript.append_message(b"commitment_1", commitments.1.to_bytes().as_ref()); } - fn ring(pow_2: (G0, G1), commitments: (G0, G1)) -> Vec<(G0, G1)> { + fn ring(pow_2: (C0::G, C1::G), commitments: (C0::G, C1::G)) -> Vec<(C0::G, C1::G)> { let mut res = vec![commitments; RING_LEN]; for i in 1 .. RING_LEN { res[i] = (res[i - 1].0 - pow_2.0, res[i - 1].1 - pow_2.1); @@ -104,7 +93,7 @@ where res } - fn shift(pow_2: &mut (G0, G1)) { + fn shift(pow_2: &mut (C0::G, C1::G)) { for _ in 0 .. BitSignature::from(SIGNATURE).bits() { pow_2.0 = pow_2.0.double(); pow_2.1 = pow_2.1.double(); @@ -114,16 +103,16 @@ where pub(crate) fn prove( rng: &mut R, transcript: &mut T, - generators: (Generators, Generators), + generators: (Generators, Generators), i: usize, - pow_2: &mut (G0, G1), + pow_2: &mut (C0::G, C1::G), mut bits: u8, - blinding_key: &mut (G0::Scalar, G1::Scalar), + blinding_key: &mut (C0::F, C1::F), ) -> Self { let mut commitments = ((generators.0.alt * blinding_key.0), (generators.1.alt * blinding_key.1)); - commitments.0 += pow_2.0 * G0::Scalar::from(bits.into()); - commitments.1 += pow_2.1 * G1::Scalar::from(bits.into()); + commitments.0 += pow_2.0 * C0::F::from(bits.into()); + commitments.1 += pow_2.1 * C1::F::from(bits.into()); Self::transcript(transcript, i, commitments); @@ -146,10 +135,10 @@ where &self, rng: &mut R, transcript: &mut T, - generators: (Generators, Generators), - batch: &mut (BatchVerifier<(), G0>, BatchVerifier<(), G1>), + generators: (Generators, Generators), + batch: &mut (BatchVerifier<(), C0::G>, BatchVerifier<(), C1::G>), i: usize, - pow_2: &mut (G0, G1), + pow_2: &mut (C0::G, C1::G), ) -> Result<(), DLEqError> { Self::transcript(transcript, i, self.commitments); @@ -173,9 +162,9 @@ where } #[cfg(feature = "serialize")] - pub(crate) fn deserialize(r: &mut R) -> std::io::Result { + pub(crate) fn deserialize(r: &mut R) -> Result { Ok(Bits { - commitments: (read_point(r)?, read_point(r)?), + commitments: (C0::G::read_G(r)?, C1::G::read_G(r)?), signature: Aos::deserialize(r, BitSignature::from(SIGNATURE).aos_form())?, }) } diff --git a/crypto/dleq/src/cross_group/mod.rs b/crypto/dleq/src/cross_group/mod.rs index 7d2501e22..f41fca65b 100644 --- a/crypto/dleq/src/cross_group/mod.rs +++ b/crypto/dleq/src/cross_group/mod.rs @@ -8,9 +8,10 @@ use digest::Digest; use transcript::Transcript; -use group::{ +use curve::{ ff::{Field, PrimeField, PrimeFieldBits}, - prime::PrimeGroup, + group::{GroupEncoding, prime::PrimeGroup}, + Curve, CurveError, }; use multiexp::BatchVerifier; @@ -28,17 +29,6 @@ use bits::{BitSignature, Bits}; #[cfg(feature = "serialize")] use std::io::{Read, Write}; -#[cfg(feature = "serialize")] -pub(crate) fn read_point(r: &mut R) -> std::io::Result { - let mut repr = G::Repr::default(); - r.read_exact(repr.as_mut())?; - let point = G::from_bytes(&repr); - if point.is_none().into() { - Err(std::io::Error::new(std::io::ErrorKind::Other, "invalid point"))?; - } - Ok(point.unwrap()) -} - #[derive(Clone, Copy, PartialEq, Eq)] pub struct Generators { pub primary: G, @@ -76,25 +66,22 @@ pub enum DLEqError { // anyone who wants it #[derive(Clone, PartialEq, Eq, Debug)] pub struct __DLEqProof< - G0: PrimeGroup + Zeroize, - G1: PrimeGroup + Zeroize, + C0: Curve, + C1: Curve, const SIGNATURE: u8, const RING_LEN: usize, const REMAINDER_RING_LEN: usize, -> where - G0::Scalar: PrimeFieldBits, - G1::Scalar: PrimeFieldBits, -{ - bits: Vec>, - remainder: Option>, - poks: (SchnorrPoK, SchnorrPoK), +> { + bits: Vec>, + remainder: Option>, + poks: (SchnorrPoK, SchnorrPoK), } macro_rules! dleq { ($name: ident, $signature: expr, $remainder: literal) => { - pub type $name = __DLEqProof< - G0, - G1, + pub type $name = __DLEqProof< + C0, + C1, { $signature.to_u8() }, { $signature.ring_len() }, // There may not be a remainder, yet if there is one, it'll be just one bit @@ -134,20 +121,17 @@ dleq!(EfficientLinearDLEq, BitSignature::EfficientLinear, false); dleq!(CompromiseLinearDLEq, BitSignature::CompromiseLinear, true); impl< - G0: PrimeGroup + Zeroize, - G1: PrimeGroup + Zeroize, + C0: Curve, + C1: Curve, const SIGNATURE: u8, const RING_LEN: usize, const REMAINDER_RING_LEN: usize, - > __DLEqProof -where - G0::Scalar: PrimeFieldBits + Zeroize, - G1::Scalar: PrimeFieldBits + Zeroize, + > __DLEqProof { pub(crate) fn transcript( transcript: &mut T, - generators: (Generators, Generators), - keys: (G0, G1), + generators: (Generators, Generators), + keys: (C0::G, C1::G), ) { transcript.domain_separate(b"cross_group_dleq"); generators.0.transcript(transcript); @@ -167,10 +151,10 @@ where blinding_key } - fn reconstruct_keys(&self) -> (G0, G1) { + fn reconstruct_keys(&self) -> (C0::G, C1::G) { let mut res = ( - self.bits.iter().map(|bit| bit.commitments.0).sum::(), - self.bits.iter().map(|bit| bit.commitments.1).sum::(), + self.bits.iter().map(|bit| bit.commitments.0).sum::(), + self.bits.iter().map(|bit| bit.commitments.1).sum::(), ); if let Some(bit) = &self.remainder { @@ -184,9 +168,9 @@ where fn prove_internal( rng: &mut R, transcript: &mut T, - generators: (Generators, Generators), - f: (G0::Scalar, G1::Scalar), - ) -> (Self, (G0::Scalar, G1::Scalar)) { + generators: (Generators, Generators), + f: (C0::F, C1::F), + ) -> (Self, (C0::F, C1::F)) { Self::transcript( transcript, generators, @@ -194,24 +178,24 @@ where ); let poks = ( - SchnorrPoK::::prove(rng, transcript, generators.0.primary, f.0), - SchnorrPoK::::prove(rng, transcript, generators.1.primary, f.1), + SchnorrPoK::::prove(rng, transcript, generators.0.primary, f.0), + SchnorrPoK::::prove(rng, transcript, generators.1.primary, f.1), ); - let mut blinding_key_total = (G0::Scalar::zero(), G1::Scalar::zero()); + let mut blinding_key_total = (C0::F::zero(), C1::F::zero()); let mut blinding_key = |rng: &mut R, last| { let blinding_key = ( Self::blinding_key(&mut *rng, &mut blinding_key_total.0, last), Self::blinding_key(&mut *rng, &mut blinding_key_total.1, last), ); if last { - debug_assert_eq!(blinding_key_total.0, G0::Scalar::zero()); - debug_assert_eq!(blinding_key_total.1, G1::Scalar::zero()); + debug_assert_eq!(blinding_key_total.0, C0::F::zero()); + debug_assert_eq!(blinding_key_total.1, C1::F::zero()); } blinding_key }; - let capacity = usize::try_from(G0::Scalar::CAPACITY.min(G1::Scalar::CAPACITY)).unwrap(); + let capacity = usize::try_from(C0::F::CAPACITY.min(C1::F::CAPACITY)).unwrap(); let bits_per_group = BitSignature::from(SIGNATURE).bits(); let mut pow_2 = (generators.0.primary, generators.1.primary); @@ -285,9 +269,9 @@ where pub fn prove( rng: &mut R, transcript: &mut T, - generators: (Generators, Generators), + generators: (Generators, Generators), digest: D, - ) -> (Self, (G0::Scalar, G1::Scalar)) { + ) -> (Self, (C0::F, C1::F)) { Self::prove_internal( rng, transcript, @@ -302,9 +286,9 @@ where pub fn prove_without_bias( rng: &mut R, transcript: &mut T, - generators: (Generators, Generators), - f0: G0::Scalar, - ) -> Option<(Self, (G0::Scalar, G1::Scalar))> { + generators: (Generators, Generators), + f0: C0::F, + ) -> Option<(Self, (C0::F, C1::F))> { scalar_convert(f0).map(|f1| Self::prove_internal(rng, transcript, generators, (f0, f1))) } @@ -313,9 +297,9 @@ where &self, rng: &mut R, transcript: &mut T, - generators: (Generators, Generators), - ) -> Result<(G0, G1), DLEqError> { - let capacity = usize::try_from(G0::Scalar::CAPACITY.min(G1::Scalar::CAPACITY)).unwrap(); + generators: (Generators, Generators), + ) -> Result<(C0::G, C1::G), DLEqError> { + let capacity = usize::try_from(C0::F::CAPACITY.min(C1::F::CAPACITY)).unwrap(); let bits_per_group = BitSignature::from(SIGNATURE).bits(); let has_remainder = (capacity % bits_per_group) != 0; @@ -370,8 +354,8 @@ where } #[cfg(feature = "serialize")] - pub fn deserialize(r: &mut R) -> std::io::Result { - let capacity = usize::try_from(G0::Scalar::CAPACITY.min(G1::Scalar::CAPACITY)).unwrap(); + pub fn deserialize(r: &mut R) -> Result { + let capacity = usize::try_from(C0::F::CAPACITY.min(C1::F::CAPACITY)).unwrap(); let bits_per_group = BitSignature::from(SIGNATURE).bits(); let mut bits = Vec::with_capacity(capacity / bits_per_group); diff --git a/crypto/dleq/src/cross_group/scalar.rs b/crypto/dleq/src/cross_group/scalar.rs index 5000c223c..6cc6b8b3b 100644 --- a/crypto/dleq/src/cross_group/scalar.rs +++ b/crypto/dleq/src/cross_group/scalar.rs @@ -1,7 +1,7 @@ -use ff::PrimeFieldBits; - use zeroize::Zeroize; +use curve::ff::PrimeFieldBits; + /// Convert a uniform scalar into one usable on both fields, clearing the top bits as needed pub fn scalar_normalize( mut scalar: F0, diff --git a/crypto/dleq/src/cross_group/schnorr.rs b/crypto/dleq/src/cross_group/schnorr.rs index 564d868eb..993b3791c 100644 --- a/crypto/dleq/src/cross_group/schnorr.rs +++ b/crypto/dleq/src/cross_group/schnorr.rs @@ -4,10 +4,7 @@ use zeroize::Zeroize; use transcript::Transcript; -use group::{ - ff::{Field, PrimeFieldBits}, - prime::PrimeGroup, -}; +use curve::{ff::Field, group::GroupEncoding, Curve, CurveError}; use multiexp::BatchVerifier; use crate::challenge; @@ -15,43 +12,39 @@ use crate::challenge; #[cfg(feature = "serialize")] use std::io::{Read, Write}; #[cfg(feature = "serialize")] -use ff::PrimeField; -#[cfg(feature = "serialize")] -use crate::{read_scalar, cross_group::read_point}; +use curve::ff::PrimeField; #[allow(non_snake_case)] #[derive(Clone, PartialEq, Eq, Debug)] -pub(crate) struct SchnorrPoK { - R: G, - s: G::Scalar, +pub(crate) struct SchnorrPoK { + R: C::G, + s: C::F, } -impl SchnorrPoK -where - G::Scalar: PrimeFieldBits + Zeroize, -{ +impl SchnorrPoK { // Not hram due to the lack of m #[allow(non_snake_case)] - fn hra(transcript: &mut T, generator: G, R: G, A: G) -> G::Scalar { + fn hra(transcript: &mut T, generator: C::G, R: C::G, A: C::G) -> C::F { transcript.domain_separate(b"schnorr_proof_of_knowledge"); transcript.append_message(b"generator", generator.to_bytes().as_ref()); transcript.append_message(b"nonce", R.to_bytes().as_ref()); transcript.append_message(b"public_key", A.to_bytes().as_ref()); - challenge(transcript) + challenge::<_, C>(transcript) } pub(crate) fn prove( rng: &mut R, transcript: &mut T, - generator: G, - mut private_key: G::Scalar, - ) -> SchnorrPoK { - let mut nonce = G::Scalar::random(rng); + generator: C::G, + mut private_key: C::F, + ) -> SchnorrPoK { + let mut nonce = C::F::random(rng); #[allow(non_snake_case)] let R = generator * nonce; let res = SchnorrPoK { R, - s: nonce + (private_key * SchnorrPoK::hra(transcript, generator, R, generator * private_key)), + s: nonce + + (private_key * SchnorrPoK::::hra(transcript, generator, R, generator * private_key)), }; private_key.zeroize(); nonce.zeroize(); @@ -62,16 +55,16 @@ where &self, rng: &mut R, transcript: &mut T, - generator: G, - public_key: G, - batch: &mut BatchVerifier<(), G>, + generator: C::G, + public_key: C::G, + batch: &mut BatchVerifier<(), C::G>, ) { batch.queue( rng, (), [ (-self.s, generator), - (G::Scalar::one(), self.R), + (C::F::one(), self.R), (Self::hra(transcript, generator, self.R, public_key), public_key), ], ); @@ -84,7 +77,7 @@ where } #[cfg(feature = "serialize")] - pub fn deserialize(r: &mut R) -> std::io::Result> { - Ok(SchnorrPoK { R: read_point(r)?, s: read_scalar(r)? }) + pub fn deserialize(r: &mut R) -> Result, CurveError> { + Ok(SchnorrPoK { R: C::read_G(r)?, s: C::read_F(r)? }) } } diff --git a/crypto/dleq/src/lib.rs b/crypto/dleq/src/lib.rs index 3492bba59..1bb14718a 100644 --- a/crypto/dleq/src/lib.rs +++ b/crypto/dleq/src/lib.rs @@ -6,11 +6,16 @@ use zeroize::Zeroize; use transcript::Transcript; -use ff::{Field, PrimeField}; -use group::prime::PrimeGroup; +use curve::{ + ff::{Field, PrimeField}, + group::GroupEncoding, + Curve, +}; #[cfg(feature = "serialize")] -use std::io::{self, ErrorKind, Error, Read, Write}; +use std::io::{self, Read, Write}; +#[cfg(feature = "serialize")] +use curve::CurveError; #[cfg(feature = "experimental")] pub mod cross_group; @@ -18,7 +23,7 @@ pub mod cross_group; #[cfg(test)] mod tests; -pub(crate) fn challenge(transcript: &mut T) -> F { +pub(crate) fn challenge(transcript: &mut T) -> C::F { // From here, there are three ways to get a scalar under the ff/group API // 1: Scalar::random(ChaCha12Rng::from_seed(self.transcript.rng_seed(b"challenge"))) // 2: Grabbing a UInt library to perform reduction by the modulus, then determining endianess @@ -26,7 +31,7 @@ pub(crate) fn challenge(transcript: &mut T) -> F { // 3: Iterating over each byte and manually doubling/adding. This is simplest // Get a wide amount of bytes to safely reduce without bias - let target = ((usize::try_from(F::NUM_BITS).unwrap() + 7) / 8) * 2; + let target = ((usize::try_from(C::F::NUM_BITS).unwrap() + 7) / 8) * 2; let mut challenge_bytes = transcript.challenge(b"challenge").as_ref().to_vec(); while challenge_bytes.len() < target { // Secure given transcripts updating on challenge @@ -34,41 +39,30 @@ pub(crate) fn challenge(transcript: &mut T) -> F { } challenge_bytes.truncate(target); - let mut challenge = F::zero(); + let mut challenge = C::F::zero(); for b in challenge_bytes { for _ in 0 .. 8 { challenge = challenge.double(); } - challenge += F::from(u64::from(b)); + challenge += C::F::from(u64::from(b)); } challenge } -#[cfg(feature = "serialize")] -fn read_scalar(r: &mut R) -> io::Result { - let mut repr = F::Repr::default(); - r.read_exact(repr.as_mut())?; - let scalar = F::from_repr(repr); - if scalar.is_none().into() { - Err(Error::new(ErrorKind::Other, "invalid scalar"))?; - } - Ok(scalar.unwrap()) -} - #[derive(Debug)] pub enum DLEqError { InvalidProof, } #[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct DLEqProof { - c: G::Scalar, - s: G::Scalar, +pub struct DLEqProof { + c: C::F, + s: C::F, } #[allow(non_snake_case)] -impl DLEqProof { - fn transcript(transcript: &mut T, generator: G, nonce: G, point: G) { +impl DLEqProof { + fn transcript(transcript: &mut T, generator: C::G, nonce: C::G, point: C::G) { transcript.append_message(b"generator", generator.to_bytes().as_ref()); transcript.append_message(b"nonce", nonce.to_bytes().as_ref()); transcript.append_message(b"point", point.to_bytes().as_ref()); @@ -77,20 +71,17 @@ impl DLEqProof { pub fn prove( rng: &mut R, transcript: &mut T, - generators: &[G], - mut scalar: G::Scalar, - ) -> DLEqProof - where - G::Scalar: Zeroize, - { - let mut r = G::Scalar::random(rng); + generators: &[C::G], + mut scalar: C::F, + ) -> DLEqProof { + let mut r = C::F::random(rng); transcript.domain_separate(b"dleq"); for generator in generators { Self::transcript(transcript, *generator, *generator * r, *generator * scalar); } - let c = challenge(transcript); + let c = challenge::<_, C>(transcript); let s = r + (c * scalar); scalar.zeroize(); @@ -102,8 +93,8 @@ impl DLEqProof { pub fn verify( &self, transcript: &mut T, - generators: &[G], - points: &[G], + generators: &[C::G], + points: &[C::G], ) -> Result<(), DLEqError> { if generators.len() != points.len() { Err(DLEqError::InvalidProof)?; @@ -114,7 +105,7 @@ impl DLEqProof { Self::transcript(transcript, *generator, (*generator * self.s) - (*point * self.c), *point); } - if self.c != challenge(transcript) { + if self.c != challenge::<_, C>(transcript) { Err(DLEqError::InvalidProof)?; } @@ -128,7 +119,7 @@ impl DLEqProof { } #[cfg(feature = "serialize")] - pub fn deserialize(r: &mut R) -> io::Result> { - Ok(DLEqProof { c: read_scalar(r)?, s: read_scalar(r)? }) + pub fn deserialize(r: &mut R) -> Result, CurveError> { + Ok(DLEqProof { c: C::read_F(r)?, s: C::read_F(r)? }) } } diff --git a/crypto/dleq/src/tests/cross_group/aos.rs b/crypto/dleq/src/tests/cross_group/aos.rs index 4fb43ca19..f7298aa9f 100644 --- a/crypto/dleq/src/tests/cross_group/aos.rs +++ b/crypto/dleq/src/tests/cross_group/aos.rs @@ -1,6 +1,6 @@ use rand_core::OsRng; -use group::{ff::Field, Group}; +use curve::{ff::Field, group::Group}; use multiexp::BatchVerifier; diff --git a/crypto/dleq/src/tests/cross_group/mod.rs b/crypto/dleq/src/tests/cross_group/mod.rs index 84dfcb2a2..04f57d3e3 100644 --- a/crypto/dleq/src/tests/cross_group/mod.rs +++ b/crypto/dleq/src/tests/cross_group/mod.rs @@ -1,8 +1,10 @@ use hex_literal::hex; use rand_core::{RngCore, OsRng}; -use ff::{Field, PrimeField}; -use group::{Group, GroupEncoding}; +use curve::{ + ff::{Field, PrimeField}, + group::{Group, GroupEncoding}, +}; use blake2::{Digest, Blake2b512}; @@ -80,7 +82,8 @@ macro_rules! test_dleq { let mut proofs = Vec::with_capacity(usize::try_from(runs).unwrap()); let time = std::time::Instant::now(); for _ in 0 .. runs { - proofs.push($type::prove(&mut OsRng, &mut transcript(), generators, key.clone()).0); + proofs + .push($type::::prove(&mut OsRng, &mut transcript(), generators, key.clone()).0); } println!("{} had a average prove time of {}ms", $str, time.elapsed().as_millis() / runs); @@ -156,8 +159,13 @@ fn test_rejection_sampling() { assert!( // Either would work - EfficientLinearDLEq::prove_without_bias(&mut OsRng, &mut transcript(), generators(), pow_2) - .is_none() + EfficientLinearDLEq::::prove_without_bias( + &mut OsRng, + &mut transcript(), + generators(), + pow_2 + ) + .is_none() ); } @@ -171,9 +179,13 @@ fn test_remainder() { assert_eq!(keys.0 + Scalar::one(), Scalar::from(2u64).pow_vartime(&[255])); assert_eq!(keys.0, keys.1); - let (proof, res) = - ConciseLinearDLEq::prove_without_bias(&mut OsRng, &mut transcript(), generators, keys.0) - .unwrap(); + let (proof, res) = ConciseLinearDLEq::::prove_without_bias( + &mut OsRng, + &mut transcript(), + generators, + keys.0, + ) + .unwrap(); assert_eq!(keys, res); verify_and_deserialize!( diff --git a/crypto/dleq/src/tests/cross_group/scalar.rs b/crypto/dleq/src/tests/cross_group/scalar.rs index 30495bb33..324e3bee2 100644 --- a/crypto/dleq/src/tests/cross_group/scalar.rs +++ b/crypto/dleq/src/tests/cross_group/scalar.rs @@ -1,6 +1,6 @@ use rand_core::OsRng; -use ff::{Field, PrimeField}; +use curve::ff::{Field, PrimeField}; use k256::Scalar as K256Scalar; use dalek_ff_group::Scalar as DalekScalar; diff --git a/crypto/dleq/src/tests/cross_group/schnorr.rs b/crypto/dleq/src/tests/cross_group/schnorr.rs index 18dd6f1a5..bd3b7323d 100644 --- a/crypto/dleq/src/tests/cross_group/schnorr.rs +++ b/crypto/dleq/src/tests/cross_group/schnorr.rs @@ -2,9 +2,9 @@ use rand_core::OsRng; use zeroize::Zeroize; -use group::{ +use curve::{ ff::{Field, PrimeFieldBits}, - prime::PrimeGroup, + group::prime::PrimeGroup, }; use multiexp::BatchVerifier; @@ -21,7 +21,7 @@ where let mut batch = BatchVerifier::new(10); for _ in 0 .. 10 { let private = G::Scalar::random(&mut OsRng); - SchnorrPoK::prove(&mut OsRng, &mut transcript.clone(), G::generator(), private).verify( + SchnorrPoK::::prove(&mut OsRng, &mut transcript.clone(), G::generator(), private).verify( &mut OsRng, &mut transcript.clone(), G::generator(), diff --git a/crypto/dleq/src/tests/mod.rs b/crypto/dleq/src/tests/mod.rs index 115a6fde2..a17a5accb 100644 --- a/crypto/dleq/src/tests/mod.rs +++ b/crypto/dleq/src/tests/mod.rs @@ -4,8 +4,7 @@ mod cross_group; use hex_literal::hex; use rand_core::OsRng; -use ff::Field; -use group::GroupEncoding; +use curve::{ff::Field, group::GroupEncoding}; use k256::{Scalar, ProjectivePoint}; @@ -40,7 +39,8 @@ fn test_dleq() { for i in 0 .. 5 { let key = Scalar::random(&mut OsRng); - let proof = DLEqProof::prove(&mut OsRng, &mut transcript(), &generators[.. i], key); + let proof = + DLEqProof::::prove(&mut OsRng, &mut transcript(), &generators[.. i], key); let mut keys = [ProjectivePoint::GENERATOR; 5]; for k in 0 .. 5 { From db27dde788a1be22862445f92f5a886701d93a90 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 13 Aug 2022 23:26:10 -0400 Subject: [PATCH 2/8] Update FROST to composable-curve --- Cargo.lock | 3 +- crypto/curve/src/lib.rs | 2 +- crypto/frost/Cargo.toml | 3 +- crypto/frost/src/curve/dalek.rs | 39 +++---------- crypto/frost/src/curve/kp256.rs | 22 ++----- crypto/frost/src/curve/mod.rs | 76 +++---------------------- crypto/frost/src/key_gen.rs | 2 +- crypto/frost/src/lib.rs | 4 +- crypto/frost/src/promote.rs | 2 +- crypto/frost/src/schnorr.rs | 2 +- crypto/frost/src/sign.rs | 6 +- crypto/frost/src/tests/curve.rs | 2 +- crypto/frost/src/tests/literal/dalek.rs | 4 +- crypto/frost/src/tests/literal/kp256.rs | 8 +-- crypto/frost/src/tests/mod.rs | 2 +- crypto/frost/src/tests/promote.rs | 22 +++---- crypto/frost/src/tests/schnorr.rs | 2 +- crypto/frost/src/tests/vectors.rs | 2 +- 18 files changed, 53 insertions(+), 150 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4473b5680..7279b1435 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4532,12 +4532,11 @@ dependencies = [ name = "modular-frost" version = "0.2.0" dependencies = [ + "composable-curve", "dalek-ff-group", "dleq", "elliptic-curve", - "ff", "flexible-transcript", - "group", "hex", "k256", "multiexp", diff --git a/crypto/curve/src/lib.rs b/crypto/curve/src/lib.rs index 4ecf70281..a61152e27 100644 --- a/crypto/curve/src/lib.rs +++ b/crypto/curve/src/lib.rs @@ -19,7 +19,7 @@ pub enum CurveError { } /// Curve trait ensuring access to a variety of ff/group APIs -pub trait Curve: Clone + Copy + PartialEq + Eq + Debug { +pub trait Curve: Zeroize + Clone + Copy + PartialEq + Eq + Debug { /// Scalar field element type type F: Zeroize + PrimeField + PrimeFieldBits; /// Group element type diff --git a/crypto/frost/Cargo.toml b/crypto/frost/Cargo.toml index ae6706f79..91b1c862f 100644 --- a/crypto/frost/Cargo.toml +++ b/crypto/frost/Cargo.toml @@ -19,8 +19,7 @@ hex = "0.4" sha2 = { version = "0.10", optional = true } -ff = "0.12" -group = "0.12" +curve = { package = "composable-curve", path = "../curve", version = "0.12" } elliptic-curve = { version = "0.12", features = ["hash2curve"], optional = true } p256 = { version = "0.11", features = ["arithmetic", "bits", "hash2curve"], optional = true } diff --git a/crypto/frost/src/curve/dalek.rs b/crypto/frost/src/curve/dalek.rs index 334e978a1..b7cd8198e 100644 --- a/crypto/frost/src/curve/dalek.rs +++ b/crypto/frost/src/curve/dalek.rs @@ -1,5 +1,3 @@ -use zeroize::Zeroize; - use sha2::{Digest, Sha512}; use dalek_ff_group::Scalar; @@ -8,31 +6,19 @@ use crate::{curve::Curve, algorithm::Hram}; macro_rules! dalek_curve { ( - $Curve: ident, - $Hram: ident, $Point: ident, - - $POINT: ident, + $Hram: ident, $ID: literal, $CONTEXT: literal, $chal: literal, $digest: literal, ) => { - use dalek_ff_group::{$Point, $POINT}; - - #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] - pub struct $Curve; - impl Curve for $Curve { - type F = Scalar; - type G = $Point; + use dalek_ff_group::$Point; + impl Curve for $Point { const ID: &'static [u8] = $ID; - fn generator() -> Self::G { - $POINT - } - fn hash_msg(msg: &[u8]) -> Vec { Sha512::new() .chain_update($CONTEXT) @@ -53,10 +39,10 @@ macro_rules! dalek_curve { #[derive(Copy, Clone)] pub struct $Hram; - impl Hram<$Curve> for $Hram { + impl Hram<$Point> for $Hram { #[allow(non_snake_case)] fn hram(R: &$Point, A: &$Point, m: &[u8]) -> Scalar { - $Curve::hash_to_F($chal, &[&R.compress().to_bytes(), &A.compress().to_bytes(), m].concat()) + $Point::hash_to_F($chal, &[&R.compress().to_bytes(), &A.compress().to_bytes(), m].concat()) } } }; @@ -64,10 +50,8 @@ macro_rules! dalek_curve { #[cfg(any(test, feature = "ristretto"))] dalek_curve!( - Ristretto, - IetfRistrettoHram, RistrettoPoint, - RISTRETTO_BASEPOINT_POINT, + IetfRistrettoHram, b"ristretto", b"FROST-RISTRETTO255-SHA512-v5", b"chal", @@ -75,13 +59,4 @@ dalek_curve!( ); #[cfg(feature = "ed25519")] -dalek_curve!( - Ed25519, - IetfEd25519Hram, - EdwardsPoint, - ED25519_BASEPOINT_POINT, - b"edwards25519", - b"", - b"", - b"", -); +dalek_curve!(EdwardsPoint, IetfEd25519Hram, b"edwards25519", b"", b"", b"",); diff --git a/crypto/frost/src/curve/kp256.rs b/crypto/frost/src/curve/kp256.rs index 1bc427f6d..1a0d6231a 100644 --- a/crypto/frost/src/curve/kp256.rs +++ b/crypto/frost/src/curve/kp256.rs @@ -2,7 +2,7 @@ use zeroize::Zeroize; use sha2::{digest::Update, Digest, Sha256}; -use group::{ +use ::curve::group::{ ff::{Field, PrimeField}, GroupEncoding, }; @@ -18,24 +18,14 @@ use crate::{curve::Curve, algorithm::Hram}; macro_rules! kp_curve { ( $lib: ident, - $Curve: ident, $Hram: ident, $ID: literal, $CONTEXT: literal ) => { - #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] - pub struct $Curve; - impl Curve for $Curve { - type F = $lib::Scalar; - type G = $lib::ProjectivePoint; - + impl Curve for $lib::ProjectivePoint { const ID: &'static [u8] = $ID; - fn generator() -> Self::G { - $lib::ProjectivePoint::GENERATOR - } - fn hash_msg(msg: &[u8]) -> Vec { (&Sha256::new().chain($CONTEXT).chain(b"digest").chain(msg).finalize()).to_vec() } @@ -76,10 +66,10 @@ macro_rules! kp_curve { #[derive(Clone)] pub struct $Hram; - impl Hram<$Curve> for $Hram { + impl Hram<$lib::ProjectivePoint> for $Hram { #[allow(non_snake_case)] fn hram(R: &$lib::ProjectivePoint, A: &$lib::ProjectivePoint, m: &[u8]) -> $lib::Scalar { - $Curve::hash_to_F( + $lib::ProjectivePoint::hash_to_F( &[$CONTEXT as &[u8], b"chal"].concat(), &[R.to_bytes().as_ref(), A.to_bytes().as_ref(), m].concat(), ) @@ -89,7 +79,7 @@ macro_rules! kp_curve { } #[cfg(feature = "p256")] -kp_curve!(p256, P256, IetfP256Hram, b"P-256", b"FROST-P256-SHA256-v5"); +kp_curve!(p256, IetfP256Hram, b"P-256", b"FROST-P256-SHA256-v5"); #[cfg(feature = "secp256k1")] -kp_curve!(k256, Secp256k1, NonIetfSecp256k1Hram, b"secp256k1", b"FROST-secp256k1-SHA256-v7"); +kp_curve!(k256, IetfSecp256k1Hram, b"secp256k1", b"FROST-secp256k1-SHA256-v7"); diff --git a/crypto/frost/src/curve/mod.rs b/crypto/frost/src/curve/mod.rs index 4c10d1381..a971b32e8 100644 --- a/crypto/frost/src/curve/mod.rs +++ b/crypto/frost/src/curve/mod.rs @@ -1,37 +1,23 @@ -use core::fmt::Debug; -use std::io::Read; - -use thiserror::Error; - use rand_core::{RngCore, CryptoRng}; use zeroize::Zeroize; -use ff::{PrimeField, PrimeFieldBits}; -use group::{Group, GroupOps, GroupEncoding, prime::PrimeGroup}; +use ::curve::ff::PrimeField; +pub use ::curve::CurveError; #[cfg(any(test, feature = "dalek"))] mod dalek; #[cfg(any(test, feature = "ristretto"))] -pub use dalek::{Ristretto, IetfRistrettoHram}; +pub use dalek::IetfRistrettoHram; #[cfg(feature = "ed25519")] -pub use dalek::{Ed25519, IetfEd25519Hram}; +pub use dalek::IetfEd25519Hram; #[cfg(feature = "kp256")] mod kp256; #[cfg(feature = "secp256k1")] -pub use kp256::{Secp256k1, NonIetfSecp256k1Hram}; +pub use kp256::IetfSecp256k1Hram; #[cfg(feature = "p256")] -pub use kp256::{P256, IetfP256Hram}; - -/// Set of errors for curve-related operations, namely encoding and decoding -#[derive(Clone, Error, Debug)] -pub enum CurveError { - #[error("invalid scalar")] - InvalidScalar, - #[error("invalid point")] - InvalidPoint, -} +pub use kp256::IetfP256Hram; /// Unified trait to manage a field/group // This should be moved into its own crate if the need for generic cryptography over ff/group @@ -39,20 +25,10 @@ pub enum CurveError { // elliptic-curve exists, yet it doesn't really serve the same role, nor does it use &[u8]/Vec // It uses GenericArray which will hopefully be deprecated as Rust evolves and doesn't offer enough // advantages in the modern day to be worth the hassle -- Kayaba -pub trait Curve: Clone + Copy + PartialEq + Eq + Debug + Zeroize { - /// Scalar field element type - // This is available via G::Scalar yet `C::G::Scalar` is ambiguous, forcing horrific accesses - type F: PrimeField + PrimeFieldBits + Zeroize; - /// Group element type - type G: Group + GroupOps + PrimeGroup + Zeroize; - +pub trait Curve: ::curve::Curve { /// ID for this curve const ID: &'static [u8]; - /// Generator for the group - // While group does provide this in its API, privacy coins may want to use a custom basepoint - fn generator() -> Self::G; - /// Hash the message for the binding factor. H3 from the IETF draft // This doesn't actually need to be part of Curve as it does nothing with the curve // This also solely relates to FROST and with a proper Algorithm/HRAM, all projects using @@ -90,42 +66,4 @@ pub trait Curve: Clone + Copy + PartialEq + Eq + Debug + Zeroize { // hash_msg and hash_binding_factor #[allow(non_snake_case)] fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F; - - #[allow(non_snake_case)] - fn F_len() -> usize { - ::Repr::default().as_ref().len() - } - - #[allow(non_snake_case)] - fn G_len() -> usize { - ::Repr::default().as_ref().len() - } - - #[allow(non_snake_case)] - fn read_F(r: &mut R) -> Result { - let mut encoding = ::Repr::default(); - r.read_exact(encoding.as_mut()).map_err(|_| CurveError::InvalidScalar)?; - - // ff mandates this is canonical - let res = - Option::::from(Self::F::from_repr(encoding)).ok_or(CurveError::InvalidScalar); - for b in encoding.as_mut() { - b.zeroize(); - } - res - } - - #[allow(non_snake_case)] - fn read_G(r: &mut R) -> Result { - let mut encoding = ::Repr::default(); - r.read_exact(encoding.as_mut()).map_err(|_| CurveError::InvalidPoint)?; - - let point = - Option::::from(Self::G::from_bytes(&encoding)).ok_or(CurveError::InvalidPoint)?; - // Ban the identity, per the FROST spec, and non-canonical points - if (point.is_identity().into()) || (point.to_bytes().as_ref() != encoding.as_ref()) { - Err(CurveError::InvalidPoint)?; - } - Ok(point) - } } diff --git a/crypto/frost/src/key_gen.rs b/crypto/frost/src/key_gen.rs index a2789ab7d..825d38089 100644 --- a/crypto/frost/src/key_gen.rs +++ b/crypto/frost/src/key_gen.rs @@ -8,7 +8,7 @@ use rand_core::{RngCore, CryptoRng}; use zeroize::Zeroize; -use group::{ +use ::curve::group::{ ff::{Field, PrimeField}, GroupEncoding, }; diff --git a/crypto/frost/src/lib.rs b/crypto/frost/src/lib.rs index d41600ced..7ec74dc73 100644 --- a/crypto/frost/src/lib.rs +++ b/crypto/frost/src/lib.rs @@ -5,7 +5,7 @@ use thiserror::Error; use zeroize::Zeroize; -use group::{ +use ::curve::group::{ ff::{Field, PrimeField}, GroupEncoding, }; @@ -13,7 +13,7 @@ use group::{ mod schnorr; pub mod curve; -use curve::Curve; +use crate::curve::Curve; pub mod key_gen; pub mod promote; diff --git a/crypto/frost/src/promote.rs b/crypto/frost/src/promote.rs index eba79ca31..19820a585 100644 --- a/crypto/frost/src/promote.rs +++ b/crypto/frost/src/promote.rs @@ -7,7 +7,7 @@ use std::{ use rand_core::{RngCore, CryptoRng}; -use group::GroupEncoding; +use ::curve::group::GroupEncoding; use transcript::{Transcript, RecommendedTranscript}; use dleq::DLEqProof; diff --git a/crypto/frost/src/schnorr.rs b/crypto/frost/src/schnorr.rs index b47b43703..cd09c4a00 100644 --- a/crypto/frost/src/schnorr.rs +++ b/crypto/frost/src/schnorr.rs @@ -2,7 +2,7 @@ use rand_core::{RngCore, CryptoRng}; use zeroize::Zeroize; -use group::{ +use ::curve::group::{ ff::{Field, PrimeField}, GroupEncoding, }; diff --git a/crypto/frost/src/sign.rs b/crypto/frost/src/sign.rs index 7faf3d4c4..f2d80a6af 100644 --- a/crypto/frost/src/sign.rs +++ b/crypto/frost/src/sign.rs @@ -10,7 +10,7 @@ use zeroize::Zeroize; use transcript::Transcript; -use group::{ +use ::curve::group::{ ff::{Field, PrimeField}, Group, GroupEncoding, }; @@ -134,7 +134,7 @@ fn preprocess>( // This could be further optimized with a multi-nonce proof. // See https://github.com/serai-dex/serai/issues/38 for mut nonce in nonces { - DLEqProof::prove(&mut *rng, &mut transcript, generators, nonce) + DLEqProof::::prove(&mut *rng, &mut transcript, generators, nonce) .serialize(&mut serialized) .unwrap(); nonce.zeroize(); @@ -229,7 +229,7 @@ fn sign_with_share>( if nonce_generators.len() >= 2 { let mut transcript = nonce_transcript::(); for de in 0 .. 2 { - DLEqProof::deserialize(&mut cursor) + DLEqProof::::deserialize(&mut cursor) .map_err(|_| FrostError::InvalidCommitment(*l))? .verify( &mut transcript, diff --git a/crypto/frost/src/tests/curve.rs b/crypto/frost/src/tests/curve.rs index c57b0568d..154a6af94 100644 --- a/crypto/frost/src/tests/curve.rs +++ b/crypto/frost/src/tests/curve.rs @@ -2,7 +2,7 @@ use std::io::Cursor; use rand_core::{RngCore, CryptoRng}; -use group::{ff::Field, Group}; +use ::curve::group::{ff::Field, Group}; use crate::{Curve, FrostCore, tests::core_gen}; diff --git a/crypto/frost/src/tests/literal/dalek.rs b/crypto/frost/src/tests/literal/dalek.rs index 835f2141d..d030006c7 100644 --- a/crypto/frost/src/tests/literal/dalek.rs +++ b/crypto/frost/src/tests/literal/dalek.rs @@ -8,7 +8,7 @@ use crate::{ #[cfg(any(test, feature = "ristretto"))] #[test] fn ristretto_vectors() { - test_with_vectors::<_, curve::Ristretto, curve::IetfRistrettoHram>( + test_with_vectors::<_, dalek_ff_group::RistrettoPoint, curve::IetfRistrettoHram>( &mut OsRng, Vectors { threshold: 2, @@ -45,7 +45,7 @@ fn ristretto_vectors() { #[cfg(feature = "ed25519")] #[test] fn ed25519_vectors() { - test_with_vectors::<_, curve::Ed25519, curve::IetfEd25519Hram>( + test_with_vectors::<_, dalek_ff_group::EdwardsPoint, curve::IetfEd25519Hram>( &mut OsRng, Vectors { threshold: 2, diff --git a/crypto/frost/src/tests/literal/kp256.rs b/crypto/frost/src/tests/literal/kp256.rs index 24d5dc738..7f97d5672 100644 --- a/crypto/frost/src/tests/literal/kp256.rs +++ b/crypto/frost/src/tests/literal/kp256.rs @@ -4,15 +4,15 @@ use rand_core::OsRng; use crate::tests::vectors::{Vectors, test_with_vectors}; #[cfg(feature = "secp256k1")] -use crate::curve::{Secp256k1, NonIetfSecp256k1Hram}; +use crate::curve::IetfSecp256k1Hram; #[cfg(feature = "p256")] -use crate::curve::{P256, IetfP256Hram}; +use crate::curve::IetfP256Hram; #[cfg(feature = "secp256k1")] #[test] fn secp256k1_non_ietf() { - test_with_vectors::<_, Secp256k1, NonIetfSecp256k1Hram>( + test_with_vectors::<_, k256::ProjectivePoint, IetfSecp256k1Hram>( &mut OsRng, Vectors { threshold: 2, @@ -49,7 +49,7 @@ fn secp256k1_non_ietf() { #[cfg(feature = "p256")] #[test] fn p256_vectors() { - test_with_vectors::<_, P256, IetfP256Hram>( + test_with_vectors::<_, p256::ProjectivePoint, IetfP256Hram>( &mut OsRng, Vectors { threshold: 2, diff --git a/crypto/frost/src/tests/mod.rs b/crypto/frost/src/tests/mod.rs index 8f25aab9b..ebb79b2e0 100644 --- a/crypto/frost/src/tests/mod.rs +++ b/crypto/frost/src/tests/mod.rs @@ -2,7 +2,7 @@ use std::{io::Cursor, collections::HashMap}; use rand_core::{RngCore, CryptoRng}; -use group::ff::Field; +use ::curve::group::ff::Field; use crate::{ Curve, FrostParams, FrostCore, FrostKeys, lagrange, diff --git a/crypto/frost/src/tests/promote.rs b/crypto/frost/src/tests/promote.rs index 5d184892d..db8b38eac 100644 --- a/crypto/frost/src/tests/promote.rs +++ b/crypto/frost/src/tests/promote.rs @@ -4,11 +4,9 @@ use rand_core::{RngCore, CryptoRng}; use zeroize::Zeroize; -use group::Group; - use crate::{ - Curve, // FrostKeys, - promote::{GeneratorPromotion /* CurvePromote */}, + Curve, + promote::GeneratorPromotion, tests::{clone_without, key_gen, schnorr::sign_core}, }; @@ -18,15 +16,17 @@ struct AltFunctions { _curve: PhantomData, } -impl Curve for AltFunctions { +impl ::curve::Curve for AltFunctions { type F = C::F; type G = C::G; - const ID: &'static [u8] = b"alt_functions"; - fn generator() -> Self::G { C::generator() } +} + +impl Curve for AltFunctions { + const ID: &'static [u8] = b"alt_functions"; fn hash_msg(msg: &[u8]) -> Vec { C::hash_msg(&[msg, b"alt"].concat()) @@ -60,15 +60,17 @@ struct AltGenerator { _curve: PhantomData, } -impl Curve for AltGenerator { +impl ::curve::Curve for AltGenerator { type F = C::F; type G = C::G; - const ID: &'static [u8] = b"alt_generator"; - fn generator() -> Self::G { C::G::generator() * C::hash_to_F(b"FROST_tests", b"generator") } +} + +impl Curve for AltGenerator { + const ID: &'static [u8] = b"alt_generator"; fn hash_msg(msg: &[u8]) -> Vec { C::hash_msg(msg) diff --git a/crypto/frost/src/tests/schnorr.rs b/crypto/frost/src/tests/schnorr.rs index 8c12b07dd..bea9b3db7 100644 --- a/crypto/frost/src/tests/schnorr.rs +++ b/crypto/frost/src/tests/schnorr.rs @@ -2,7 +2,7 @@ use std::{marker::PhantomData, collections::HashMap}; use rand_core::{RngCore, CryptoRng}; -use group::{ff::Field, Group, GroupEncoding}; +use ::curve::group::{ff::Field, Group, GroupEncoding}; use crate::{ Curve, FrostKeys, diff --git a/crypto/frost/src/tests/vectors.rs b/crypto/frost/src/tests/vectors.rs index ca2cf2784..ae79cab7b 100644 --- a/crypto/frost/src/tests/vectors.rs +++ b/crypto/frost/src/tests/vectors.rs @@ -2,7 +2,7 @@ use std::{io::Cursor, collections::HashMap}; use rand_core::{RngCore, CryptoRng}; -use group::{ff::PrimeField, GroupEncoding}; +use ::curve::group::{ff::PrimeField, GroupEncoding}; use crate::{ curve::Curve, From 89679487e55af59e9d316949f9dfb043c0c3d018 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 13 Aug 2022 23:50:48 -0400 Subject: [PATCH 3/8] Update surrounding libraries --- Cargo.lock | 1 + coins/ethereum/src/crypto.rs | 4 ++-- coins/ethereum/tests/contract.rs | 7 +++---- coins/ethereum/tests/crypto.rs | 12 ++++++------ coins/monero/src/frost.rs | 2 +- coins/monero/src/ringct/clsag/multisig.rs | 10 +++++----- coins/monero/src/tests/clsag.rs | 4 ++-- coins/monero/src/wallet/send/multisig.rs | 11 ++++++----- coins/monero/tests/send.rs | 4 ++-- crypto/curve/src/lib.rs | 11 +++++++++-- processor/Cargo.toml | 1 + processor/src/coin/mod.rs | 4 ++-- processor/src/coin/monero.rs | 8 ++++---- processor/src/lib.rs | 2 +- 14 files changed, 45 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7279b1435..63cf62c42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7534,6 +7534,7 @@ version = "0.1.0" dependencies = [ "async-trait", "blake2", + "composable-curve", "curve25519-dalek 3.2.0", "dalek-ff-group", "flexible-transcript", diff --git a/coins/ethereum/src/crypto.rs b/coins/ethereum/src/crypto.rs index b661fdc09..8158d27a1 100644 --- a/coins/ethereum/src/crypto.rs +++ b/coins/ethereum/src/crypto.rs @@ -6,7 +6,7 @@ use k256::{ AffinePoint, ProjectivePoint, Scalar, U256, }; -use frost::{algorithm::Hram, curve::Secp256k1}; +use frost::algorithm::Hram; pub fn keccak256(data: &[u8]) -> [u8; 32] { Keccak256::digest(data).try_into().unwrap() @@ -47,7 +47,7 @@ pub fn ecrecover(message: Scalar, v: u8, r: Scalar, s: Scalar) -> Option<[u8; 20 #[derive(Clone, Default)] pub struct EthereumHram {} -impl Hram for EthereumHram { +impl Hram for EthereumHram { #[allow(non_snake_case)] fn hram(R: &ProjectivePoint, A: &ProjectivePoint, m: &[u8]) -> Scalar { let a_encoded_point = A.to_encoded_point(true); diff --git a/coins/ethereum/tests/contract.rs b/coins/ethereum/tests/contract.rs index 581bb2cd0..3022aaf61 100644 --- a/coins/ethereum/tests/contract.rs +++ b/coins/ethereum/tests/contract.rs @@ -19,11 +19,10 @@ async fn test_ecrecover_hack() { use ethers::utils::keccak256; use frost::{ algorithm::Schnorr, - curve::Secp256k1, tests::{algorithm_machines, key_gen, sign}, }; use k256::elliptic_curve::bigint::ArrayEncoding; - use k256::{Scalar, U256}; + use k256::{U256, Scalar, ProjectivePoint}; use rand_core::OsRng; let anvil = Anvil::new().spawn(); @@ -33,7 +32,7 @@ async fn test_ecrecover_hack() { let chain_id = provider.get_chainid().await.unwrap(); let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let keys = key_gen::<_, Secp256k1>(&mut OsRng); + let keys = key_gen::<_, ProjectivePoint>(&mut OsRng); let group_key = keys[&1].group_key(); const MESSAGE: &'static [u8] = b"Hello, World!"; @@ -44,7 +43,7 @@ async fn test_ecrecover_hack() { let sig = sign( &mut OsRng, - algorithm_machines(&mut OsRng, Schnorr::::new(), &keys), + algorithm_machines(&mut OsRng, Schnorr::::new(), &keys), full_message, ); let mut processed_sig = diff --git a/coins/ethereum/tests/crypto.rs b/coins/ethereum/tests/crypto.rs index 01584355b..f3820c1d7 100644 --- a/coins/ethereum/tests/crypto.rs +++ b/coins/ethereum/tests/crypto.rs @@ -1,10 +1,10 @@ -use ethereum_serai::crypto::*; -use frost::curve::Secp256k1; use k256::{ elliptic_curve::{bigint::ArrayEncoding, ops::Reduce, sec1::ToEncodedPoint}, ProjectivePoint, Scalar, U256, }; +use ethereum_serai::crypto::*; + #[test] fn test_ecrecover() { use k256::ecdsa::{ @@ -35,14 +35,14 @@ fn test_signing() { }; use rand_core::OsRng; - let keys = key_gen::<_, Secp256k1>(&mut OsRng); + let keys = key_gen::<_, ProjectivePoint>(&mut OsRng); let _group_key = keys[&1].group_key(); const MESSAGE: &'static [u8] = b"Hello, World!"; let _sig = sign( &mut OsRng, - algorithm_machines(&mut OsRng, Schnorr::::new(), &keys), + algorithm_machines(&mut OsRng, Schnorr::::new(), &keys), MESSAGE, ); } @@ -55,7 +55,7 @@ fn test_ecrecover_hack() { }; use rand_core::OsRng; - let keys = key_gen::<_, Secp256k1>(&mut OsRng); + let keys = key_gen::<_, ProjectivePoint>(&mut OsRng); let group_key = keys[&1].group_key(); let group_key_encoded = group_key.to_encoded_point(true); let group_key_compressed = group_key_encoded.as_ref(); @@ -69,7 +69,7 @@ fn test_ecrecover_hack() { let sig = sign( &mut OsRng, - algorithm_machines(&mut OsRng, Schnorr::::new(), &keys), + algorithm_machines(&mut OsRng, Schnorr::::new(), &keys), full_message, ); diff --git a/coins/monero/src/frost.rs b/coins/monero/src/frost.rs index 0453e28c6..6fa447f82 100644 --- a/coins/monero/src/frost.rs +++ b/coins/monero/src/frost.rs @@ -34,7 +34,7 @@ pub(crate) fn write_dleq( mut x: Scalar, ) -> Vec { let mut res = Vec::with_capacity(64); - DLEqProof::prove( + DLEqProof::::prove( rng, // Doesn't take in a larger transcript object due to the usage of this // Every prover would immediately write their own DLEq proof, when they can only do so in diff --git a/coins/monero/src/ringct/clsag/multisig.rs b/coins/monero/src/ringct/clsag/multisig.rs index e5e7f9cb6..2dfff0e3a 100644 --- a/coins/monero/src/ringct/clsag/multisig.rs +++ b/coins/monero/src/ringct/clsag/multisig.rs @@ -19,7 +19,7 @@ use curve25519_dalek::{ use group::Group; use transcript::{Transcript, RecommendedTranscript}; -use frost::{curve::Ed25519, FrostError, FrostView, algorithm::Algorithm}; +use frost::{FrostError, FrostView, algorithm::Algorithm}; use dalek_ff_group as dfg; use crate::{ @@ -124,7 +124,7 @@ impl ClsagMultisig { } } -impl Algorithm for ClsagMultisig { +impl Algorithm for ClsagMultisig { type Transcript = RecommendedTranscript; type Signature = (Clsag, EdwardsPoint); @@ -135,7 +135,7 @@ impl Algorithm for ClsagMultisig { fn preprocess_addendum( &mut self, rng: &mut R, - view: &FrostView, + view: &FrostView, ) -> Vec { let mut serialized = Vec::with_capacity(Self::serialized_len()); serialized.extend((view.secret_share().0 * self.H).compress().to_bytes()); @@ -145,7 +145,7 @@ impl Algorithm for ClsagMultisig { fn process_addendum( &mut self, - view: &FrostView, + view: &FrostView, l: u16, serialized: &mut Re, ) -> Result<(), FrostError> { @@ -171,7 +171,7 @@ impl Algorithm for ClsagMultisig { fn sign_share( &mut self, - view: &FrostView, + view: &FrostView, nonce_sums: &[Vec], nonces: &[dfg::Scalar], msg: &[u8], diff --git a/coins/monero/src/tests/clsag.rs b/coins/monero/src/tests/clsag.rs index d51f7c91d..d46908d5f 100644 --- a/coins/monero/src/tests/clsag.rs +++ b/coins/monero/src/tests/clsag.rs @@ -8,7 +8,7 @@ use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar}; #[cfg(feature = "multisig")] use transcript::{Transcript, RecommendedTranscript}; #[cfg(feature = "multisig")] -use frost::curve::Ed25519; +use dalek_ff_group::EdwardsPoint; use crate::{ Commitment, random_scalar, @@ -80,7 +80,7 @@ fn clsag() { #[cfg(feature = "multisig")] #[test] fn clsag_multisig() -> Result<(), MultisigError> { - let keys = key_gen::<_, Ed25519>(&mut OsRng); + let keys = key_gen::<_, EdwardsPoint>(&mut OsRng); let randomness = random_scalar(&mut OsRng); let mut ring = vec![]; diff --git a/coins/monero/src/wallet/send/multisig.rs b/coins/monero/src/wallet/send/multisig.rs index c8bd5e70c..6af7ae4d1 100644 --- a/coins/monero/src/wallet/send/multisig.rs +++ b/coins/monero/src/wallet/send/multisig.rs @@ -13,9 +13,10 @@ use curve25519_dalek::{ edwards::{EdwardsPoint, CompressedEdwardsY}, }; +use dalek_ff_group as dfg; + use transcript::{Transcript, RecommendedTranscript}; use frost::{ - curve::Ed25519, FrostError, FrostKeys, sign::{ PreprocessMachine, SignMachine, SignatureMachine, AlgorithmMachine, AlgorithmSignMachine, @@ -43,7 +44,7 @@ pub struct TransactionMachine { decoys: Vec, inputs: Vec>>>, - clsags: Vec>, + clsags: Vec>, } pub struct TransactionSignMachine { @@ -55,21 +56,21 @@ pub struct TransactionSignMachine { decoys: Vec, inputs: Vec>>>, - clsags: Vec>, + clsags: Vec>, our_preprocess: Vec, } pub struct TransactionSignatureMachine { tx: Transaction, - clsags: Vec>, + clsags: Vec>, } impl SignableTransaction { pub async fn multisig( self, rpc: &Rpc, - keys: FrostKeys, + keys: FrostKeys, mut transcript: RecommendedTranscript, height: usize, mut included: Vec, diff --git a/coins/monero/tests/send.rs b/coins/monero/tests/send.rs index 98193baf7..1318fc5b1 100644 --- a/coins/monero/tests/send.rs +++ b/coins/monero/tests/send.rs @@ -17,7 +17,7 @@ use dalek_ff_group::Scalar; use transcript::{Transcript, RecommendedTranscript}; #[cfg(feature = "multisig")] use frost::{ - curve::Ed25519, + curve::dfg::EdwardsPoint, tests::{THRESHOLD, key_gen, sign}, }; @@ -66,7 +66,7 @@ async fn send_core(test: usize, multisig: bool) { let mut spend_pub = &spend * &ED25519_BASEPOINT_TABLE; #[cfg(feature = "multisig")] - let keys = key_gen::<_, Ed25519>(&mut OsRng); + let keys = key_gen::<_, dfg::EdwardsPoint>(&mut OsRng); if multisig { #[cfg(not(feature = "multisig"))] diff --git a/crypto/curve/src/lib.rs b/crypto/curve/src/lib.rs index a61152e27..ae96690c0 100644 --- a/crypto/curve/src/lib.rs +++ b/crypto/curve/src/lib.rs @@ -7,7 +7,11 @@ use zeroize::Zeroize; pub use group; pub use group::ff; -use group::{ff::{PrimeField, PrimeFieldBits}, Group, GroupOps, GroupEncoding, prime::PrimeGroup}; +use group::{ + ff::{PrimeField, PrimeFieldBits}, + Group, GroupOps, GroupEncoding, + prime::PrimeGroup, +}; /// Set of errors for curve-related operations, namely encoding and decoding #[derive(Clone, Error, Debug)] @@ -74,7 +78,10 @@ pub trait Curve: Zeroize + Clone + Copy + PartialEq + Eq + Debug { } } -impl Curve for G where G::Scalar: Zeroize + PrimeField + PrimeFieldBits { +impl Curve for G +where + G::Scalar: Zeroize + PrimeField + PrimeFieldBits, +{ type F = G::Scalar; type G = G; diff --git a/processor/Cargo.toml b/processor/Cargo.toml index db89b8d04..78885546e 100644 --- a/processor/Cargo.toml +++ b/processor/Cargo.toml @@ -26,6 +26,7 @@ k256 = { version = "0.11", features = ["arithmetic", "keccak256", "ecdsa"] } curve25519-dalek = { version = "3", features = ["std"] } transcript = { package = "flexible-transcript", path = "../crypto/transcript", features = ["recommended"] } +curve = { package = "composable-curve", path = "../crypto/curve" } dalek-ff-group = { path = "../crypto/dalek-ff-group" } frost = { package = "modular-frost", path = "../crypto/frost", features = ["secp256k1", "ed25519"] } diff --git a/processor/src/coin/mod.rs b/processor/src/coin/mod.rs index 7a8ce22b8..bde022950 100644 --- a/processor/src/coin/mod.rs +++ b/processor/src/coin/mod.rs @@ -45,14 +45,14 @@ pub trait Coin { const MAX_OUTPUTS: usize; // TODO: Decide if this includes change or not // Doesn't have to take self, enables some level of caching which is pleasant - fn address(&self, key: ::G) -> Self::Address; + fn address(&self, key: ::G) -> Self::Address; async fn get_height(&self) -> Result; async fn get_block(&self, height: usize) -> Result; async fn get_outputs( &self, block: &Self::Block, - key: ::G, + key: ::G, ) -> Vec; async fn prepare_send( diff --git a/processor/src/coin/monero.rs b/processor/src/coin/monero.rs index 2a40c572d..52a297d3c 100644 --- a/processor/src/coin/monero.rs +++ b/processor/src/coin/monero.rs @@ -4,7 +4,7 @@ use curve25519_dalek::scalar::Scalar; use dalek_ff_group as dfg; use transcript::RecommendedTranscript; -use frost::{curve::Ed25519, FrostKeys}; +use frost::FrostKeys; use monero_serai::{ ringct::bulletproofs::Bulletproofs, @@ -55,7 +55,7 @@ impl OutputTrait for Output { #[derive(Debug)] pub struct SignableTransaction( - FrostKeys, + FrostKeys, RecommendedTranscript, usize, MSignableTransaction, @@ -97,7 +97,7 @@ impl Monero { #[async_trait] impl Coin for Monero { - type Curve = Ed25519; + type Curve = dfg::EdwardsPoint; type Fee = Fee; type Transaction = Transaction; @@ -142,7 +142,7 @@ impl Coin for Monero { async fn prepare_send( &self, - keys: FrostKeys, + keys: FrostKeys, transcript: RecommendedTranscript, height: usize, mut inputs: Vec, diff --git a/processor/src/lib.rs b/processor/src/lib.rs index 58346e0eb..f61741ffd 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -33,6 +33,6 @@ pub enum SignError { // Generate a static view key for a given chain in a globally consistent manner // Doesn't consider the current group key to increase the simplicity of verifying Serai's status // Takes an index, k, for more modern privacy protocols which use multiple view keys -pub fn view_key(k: u64) -> ::F { +pub fn view_key(k: u64) -> ::F { C::Curve::hash_to_F(b"Serai DEX View Key", &[C::ID, &k.to_le_bytes()].concat()) } From 9610013cab789c7b6013b4c4d8b9bdb294d73353 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 14 Aug 2022 00:05:36 -0400 Subject: [PATCH 4/8] Slight correction to Monero test --- coins/monero/tests/send.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/coins/monero/tests/send.rs b/coins/monero/tests/send.rs index 1318fc5b1..d188bc692 100644 --- a/coins/monero/tests/send.rs +++ b/coins/monero/tests/send.rs @@ -12,14 +12,11 @@ use blake2::{digest::Update, Digest, Blake2b512}; use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE; #[cfg(feature = "multisig")] -use dalek_ff_group::Scalar; +use dalek_ff_group::{Scalar, EdwardsPoint}; #[cfg(feature = "multisig")] use transcript::{Transcript, RecommendedTranscript}; #[cfg(feature = "multisig")] -use frost::{ - curve::dfg::EdwardsPoint, - tests::{THRESHOLD, key_gen, sign}, -}; +use frost::tests::{THRESHOLD, key_gen, sign}; use monero_serai::{ random_scalar, @@ -66,7 +63,7 @@ async fn send_core(test: usize, multisig: bool) { let mut spend_pub = &spend * &ED25519_BASEPOINT_TABLE; #[cfg(feature = "multisig")] - let keys = key_gen::<_, dfg::EdwardsPoint>(&mut OsRng); + let keys = key_gen::<_, EdwardsPoint>(&mut OsRng); if multisig { #[cfg(not(feature = "multisig"))] From 8a79c415ab06ae7d584a184494f30a6cb120507d Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 16 Aug 2022 22:31:24 -0400 Subject: [PATCH 5/8] Schnorr signature lib --- Cargo.lock | 11 +++ Cargo.toml | 1 + crypto/schnorr/Cargo.toml | 23 ++++++ crypto/schnorr/src/lib.rs | 129 ++++++++++++++++++++++++++++++++++ crypto/schnorr/tests/tests.rs | 115 ++++++++++++++++++++++++++++++ 5 files changed, 279 insertions(+) create mode 100644 crypto/schnorr/Cargo.toml create mode 100644 crypto/schnorr/src/lib.rs create mode 100644 crypto/schnorr/tests/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 63cf62c42..ab8a3297c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4547,6 +4547,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "modular-schnorr" +version = "0.1.0" +dependencies = [ + "blake2", + "composable-curve", + "dalek-ff-group", + "multiexp", + "rand_core 0.6.3", +] + [[package]] name = "monero" version = "0.16.0" diff --git a/Cargo.toml b/Cargo.toml index 1c91daeb7..f6eeecb17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "crypto/multiexp", + "crypto/schnorr", "crypto/dleq", "crypto/frost", diff --git a/crypto/schnorr/Cargo.toml b/crypto/schnorr/Cargo.toml new file mode 100644 index 000000000..f0088fd55 --- /dev/null +++ b/crypto/schnorr/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "modular-schnorr" +version = "0.1.0" +description = "Implementation of Schnorr signatures for ff/group" +license = "MIT" +authors = ["Luke Parker "] +edition = "2021" + +[dependencies] +rand_core = "0.6" + +curve = { package = "composable-curve", path = "../curve", version = "0.12" } + +multiexp = { path = "../multiexp", version = "0.2", features = ["batch"], optional = true } + +[dev-dependencies] +blake2 = "0.10" +dalek-ff-group = { path = "../dalek-ff-group" } + +[features] +std = [] +serialize = ["std"] +batch = ["std", "multiexp"] diff --git a/crypto/schnorr/src/lib.rs b/crypto/schnorr/src/lib.rs new file mode 100644 index 000000000..5b3bf582d --- /dev/null +++ b/crypto/schnorr/src/lib.rs @@ -0,0 +1,129 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use core::marker::PhantomData; + +use rand_core::{RngCore, CryptoRng}; + +use curve::{ + group::{ff::Field, Group}, + Curve, +}; + +#[cfg(feature = "batch")] +use multiexp::BatchVerifier; + +pub trait Signature: Sized + Clone + Copy { + /// Sign a msg with the provided nonce. + fn sign_core(key: C::F, nonce: C::F, msg: &[u8]) -> Self; + + /// Sign a msg with a randomly generated nonce. + fn sign_random_nonce(rng: &mut R, key: C::F, msg: &[u8]) -> Self { + Self::sign_core(key, C::F::random(rng), msg) + } + + /* + For a properly selected hash function (no length extension attacks and so on), this would be + optimal. sign could then call sign_deterministic_nonce with SHA3. The issues are two-fold: + 1) Needing to perform multiple hashes for the needed bits/use SHAKE256 + 2) Needing to convert this to a Scalar for an arbitratry field (only possible with + double-and-add-style schemes) + The latter is the main objection at this time. + */ + /// Sign a msg with a deterministic nonce generated as H(x || m). + // pub fn sign_deterministic_nonce(key: C::F, msg: &[u8]) -> Self {} + + /// Alias to the preferred sign function, which is currently sign_random_nonce but may change to + /// a deterministic scheme in the future. + fn sign(rng: &mut R, key: C::F, msg: &[u8]) -> Self { + Self::sign_random_nonce(rng, key, msg) + } + + /// Verify a signature. + #[must_use] + fn verify(&self, key: C::G, msg: &[u8]) -> bool; +} + +/// A parameterizable HRAm to support a variety of signature specifications. +pub trait Hram: Sized + Clone + Copy { + /// HRAM function to generate a challenge + #[allow(non_snake_case)] + fn hram(R: C::G, A: C::G, m: &[u8]) -> C::F; +} + +#[allow(non_snake_case)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct Schnorr> { + pub R: C::G, + pub s: C::F, + _hram: PhantomData, +} + +#[allow(non_snake_case)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct ClassicalSchnorr> { + pub c: C::F, + pub s: C::F, + _hram: PhantomData, +} + +impl> Schnorr { + #[allow(non_snake_case)] + pub fn new(R: C::G, s: C::F) -> Self { + Self { R, s, _hram: PhantomData } + } + + fn verification_statements(&self, key: C::G, msg: &[u8]) -> [(C::F, C::G); 3] { + // R + cA == sG where s = r + cx + [(C::F::one(), self.R), (H::hram(self.R, key, msg), key), (-self.s, C::generator())] + } + + #[cfg(feature = "batch")] + pub fn queue_batch_verification( + &self, + rng: &mut R, + verifier: &mut BatchVerifier, + id: Id, + key: C::G, + msg: &[u8], + ) { + verifier.queue(rng, id, self.verification_statements(key, msg)); + } +} + +impl> Signature for Schnorr { + fn sign_core(key: C::F, nonce: C::F, msg: &[u8]) -> Self { + #[allow(non_snake_case)] + let R = C::generator() * nonce; + Self { R, s: nonce + (key * H::hram(R, C::generator() * key, msg)), _hram: PhantomData } + } + + /// Verify a signature. + #[must_use] + fn verify(&self, key: C::G, msg: &[u8]) -> bool { + let mut accum = C::G::identity(); + for statement in self.verification_statements(key, msg) { + accum += statement.1 * statement.0; + } + accum.is_identity().into() + } +} + +impl> ClassicalSchnorr { + pub fn new(c: C::F, s: C::F) -> Self { + Self { c, s, _hram: PhantomData } + } +} + +impl> Signature for ClassicalSchnorr { + fn sign_core(key: C::F, nonce: C::F, msg: &[u8]) -> Self { + let c = H::hram(C::generator() * nonce, C::generator() * key, msg); + Self { c, s: nonce - (key * c), _hram: PhantomData } + } + + /// Verify a signature. + #[must_use] + fn verify(&self, key: C::G, msg: &[u8]) -> bool { + self.c == H::hram((C::generator() * self.s) + (key * self.c), key, msg) + } +} + diff --git a/crypto/schnorr/tests/tests.rs b/crypto/schnorr/tests/tests.rs new file mode 100644 index 000000000..be0033441 --- /dev/null +++ b/crypto/schnorr/tests/tests.rs @@ -0,0 +1,115 @@ +use rand_core::OsRng; + +use blake2::{Digest, Blake2b512}; + +use curve::group::{ff::Field, Group, GroupEncoding}; +use dalek_ff_group::{Scalar, RistrettoPoint}; + +use multiexp::BatchVerifier; + +use modular_schnorr::{Hram, Signature, Schnorr, ClassicalSchnorr}; + +const MSG: &[u8] = b"Hello, World"; + +#[derive(Clone, Copy)] +struct SimpleHram; +impl Hram for SimpleHram { + #[allow(non_snake_case)] + fn hram(R: RistrettoPoint, A: RistrettoPoint, m: &[u8]) -> Scalar { + Scalar::from_hash(Blake2b512::new().chain_update(&[&R.to_bytes(), &A.to_bytes(), m].concat())) + } +} + +#[test] +fn sign_verify() { + let key = Scalar::random(&mut OsRng); + assert!(Schnorr::::sign(&mut OsRng, key, MSG) + .verify(RistrettoPoint::generator() * key, MSG)); + assert!(ClassicalSchnorr::::sign(&mut OsRng, key, MSG) + .verify(RistrettoPoint::generator() * key, MSG)); +} + +/* +#[test] +fn conversion() { + let key = Scalar::random(&mut OsRng); + let pub = RistrettoPoint::generator() * key; + let sig = Schnorr::::sign(&mut OsRng, key, MSG); + assert!(sig.verify(public_key, MSG)); + + let classical = ClassicalSchnorr::from(sig); + assert!(classical.verify(public_key, MSG)); + let back = Schnorr::from(classical); + assert!(back.verify(public_key, MSG)); + assert_eq!(sig, back); +} +*/ + +#[test] +fn zero() { + // True zero should pass for any message + assert!(Schnorr::::new(RistrettoPoint::identity(), Scalar::zero()) + .verify(RistrettoPoint::identity(), MSG)); + // ClassicalSchnorr doesn't have a "true zero" as c is a hash output + // While it's possible to craft a c for a 0 nonce, that's just checking a 0 nonce + + // But not for an actual key + assert!(!Schnorr::::new(RistrettoPoint::identity(), Scalar::zero()) + .verify(RistrettoPoint::random(&mut OsRng), MSG)); + assert!(!ClassicalSchnorr::::new(Scalar::zero(), Scalar::zero()) + .verify(RistrettoPoint::random(&mut OsRng), MSG)); +} + +#[test] +fn random_fails() { + assert!(!Schnorr::::new( + RistrettoPoint::random(&mut OsRng), + Scalar::random(&mut OsRng) + ) + .verify(RistrettoPoint::random(&mut OsRng), MSG)); + + assert!(!ClassicalSchnorr::::new( + Scalar::random(&mut OsRng), + Scalar::random(&mut OsRng) + ) + .verify(RistrettoPoint::random(&mut OsRng), MSG)); +} + +#[cfg(feature = "batch")] +#[test] +fn batch_verify() { + // Create 5 signatures + let mut keys = vec![]; + let mut sigs = vec![]; + let mut verifier = BatchVerifier::new(5); + for i in 0 .. 5 { + keys.push(Scalar::random(&mut OsRng)); + sigs.push(Schnorr::::sign( + &mut OsRng, + keys[i], + &[MSG, &i.to_le_bytes()].concat(), + )); + sigs[i].queue_batch_verification( + &mut OsRng, + &mut verifier, + i, + RistrettoPoint::generator() * keys[i], + &[MSG, &i.to_le_bytes()].concat(), + ); + } + assert!(verifier.verify_vartime()); + + // Test invalid signatures don't batch verify + Schnorr::::new( + RistrettoPoint::random(&mut OsRng), + Scalar::random(&mut OsRng), + ) + .queue_batch_verification( + &mut OsRng, + &mut verifier, + 5, + RistrettoPoint::random(&mut OsRng), + MSG, + ); + assert_eq!(verifier.verify_vartime_with_vartime_blame().unwrap_err(), 5); +} From d63093ca88695d3f1b1fa1f0808dde8dc9fe42f0 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 16 Aug 2022 23:11:54 -0400 Subject: [PATCH 6/8] Move BatchVerifier's rng from queue to verify Makes it not infest the callstack while simultaneously not notably affecting callers. --- coins/monero/src/ringct/bulletproofs/mod.rs | 9 +- .../src/ringct/bulletproofs/original.rs | 16 +- coins/monero/src/ringct/bulletproofs/plus.rs | 14 +- coins/monero/src/tests/bulletproofs.rs | 4 +- crypto/dleq/src/cross_group/aos.rs | 7 +- crypto/dleq/src/cross_group/bits.rs | 1 - crypto/dleq/src/cross_group/mod.rs | 10 +- crypto/dleq/src/cross_group/schnorr.rs | 4 +- crypto/frost/src/key_gen.rs | 4 +- crypto/frost/src/schnorr.rs | 4 +- crypto/multiexp/src/batch.rs | 165 ++++++++++-------- crypto/multiexp/src/tests/mod.rs | 11 ++ crypto/schnorr/src/lib.rs | 6 +- crypto/schnorr/tests/tests.rs | 13 +- 14 files changed, 140 insertions(+), 128 deletions(-) diff --git a/coins/monero/src/ringct/bulletproofs/mod.rs b/coins/monero/src/ringct/bulletproofs/mod.rs index e877175aa..89742eee8 100644 --- a/coins/monero/src/ringct/bulletproofs/mod.rs +++ b/coins/monero/src/ringct/bulletproofs/mod.rs @@ -2,8 +2,6 @@ use rand_core::{RngCore, CryptoRng}; -use zeroize::Zeroize; - use curve25519_dalek::edwards::EdwardsPoint; use multiexp::BatchVerifier; @@ -75,16 +73,15 @@ impl Bulletproofs { } #[must_use] - pub fn batch_verify( + pub fn batch_verify( &self, - rng: &mut R, verifier: &mut BatchVerifier, id: ID, commitments: &[EdwardsPoint], ) -> bool { match self { - Bulletproofs::Original(bp) => bp.batch_verify(rng, verifier, id, commitments), - Bulletproofs::Plus(bp) => bp.batch_verify(rng, verifier, id, commitments), + Bulletproofs::Original(bp) => bp.batch_verify(verifier, id, commitments), + Bulletproofs::Plus(bp) => bp.batch_verify(verifier, id, commitments), } } diff --git a/coins/monero/src/ringct/bulletproofs/original.rs b/coins/monero/src/ringct/bulletproofs/original.rs index bf235d21e..33fc25fb8 100644 --- a/coins/monero/src/ringct/bulletproofs/original.rs +++ b/coins/monero/src/ringct/bulletproofs/original.rs @@ -168,9 +168,8 @@ impl OriginalStruct { } #[must_use] - fn verify_core( + fn verify_core( &self, - rng: &mut R, verifier: &mut BatchVerifier, id: ID, commitments: &[DalekPoint], @@ -246,7 +245,7 @@ impl OriginalStruct { proof.push((x, T1)); proof.push((x * x, T2)); - verifier.queue(&mut *rng, id, proof); + verifier.queue(id, proof); proof = Vec::with_capacity(4 + (2 * (MN + logMN))); let z3 = (Scalar(self.t) - (Scalar(self.a) * Scalar(self.b))) * x_ip; @@ -277,7 +276,7 @@ impl OriginalStruct { proof.push((w[i] * w[i], L[i])); proof.push((winv[i] * winv[i], R[i])); } - verifier.queue(rng, id, proof); + verifier.queue(id, proof); true } @@ -289,21 +288,20 @@ impl OriginalStruct { commitments: &[DalekPoint], ) -> bool { let mut verifier = BatchVerifier::new(1); - if self.verify_core(rng, &mut verifier, (), commitments) { - verifier.verify_vartime() + if self.verify_core(&mut verifier, (), commitments) { + verifier.verify_vartime(rng) } else { false } } #[must_use] - pub(crate) fn batch_verify( + pub(crate) fn batch_verify( &self, - rng: &mut R, verifier: &mut BatchVerifier, id: ID, commitments: &[DalekPoint], ) -> bool { - self.verify_core(rng, verifier, id, commitments) + self.verify_core(verifier, id, commitments) } } diff --git a/coins/monero/src/ringct/bulletproofs/plus.rs b/coins/monero/src/ringct/bulletproofs/plus.rs index 4082e169f..ef1a2b181 100644 --- a/coins/monero/src/ringct/bulletproofs/plus.rs +++ b/coins/monero/src/ringct/bulletproofs/plus.rs @@ -177,9 +177,8 @@ impl PlusStruct { } #[must_use] - fn verify_core( + fn verify_core( &self, - rng: &mut R, verifier: &mut BatchVerifier, id: ID, commitments: &[DalekPoint], @@ -284,7 +283,7 @@ impl PlusStruct { proof.push((minus_esq * winv[i] * winv[i], R[i])); } - verifier.queue(rng, id, proof); + verifier.queue(id, proof); true } @@ -295,21 +294,20 @@ impl PlusStruct { commitments: &[DalekPoint], ) -> bool { let mut verifier = BatchVerifier::new(1); - if self.verify_core(rng, &mut verifier, (), commitments) { - verifier.verify_vartime() + if self.verify_core(&mut verifier, (), commitments) { + verifier.verify_vartime(rng) } else { false } } #[must_use] - pub(crate) fn batch_verify( + pub(crate) fn batch_verify( &self, - rng: &mut R, verifier: &mut BatchVerifier, id: ID, commitments: &[DalekPoint], ) -> bool { - self.verify_core(rng, verifier, id, commitments) + self.verify_core(verifier, id, commitments) } } diff --git a/coins/monero/src/tests/bulletproofs.rs b/coins/monero/src/tests/bulletproofs.rs index 3bb88921a..ec6b20977 100644 --- a/coins/monero/src/tests/bulletproofs.rs +++ b/coins/monero/src/tests/bulletproofs.rs @@ -76,9 +76,9 @@ macro_rules! bulletproofs_tests { let commitments = commitments.iter().map(Commitment::calculate).collect::>(); assert!(bp.verify(&mut OsRng, &commitments)); - assert!(bp.batch_verify(&mut OsRng, &mut verifier, i, &commitments)); + assert!(bp.batch_verify(&mut verifier, i, &commitments)); } - assert!(verifier.verify_vartime()); + assert!(verifier.verify_vartime(&mut OsRng)); } #[test] diff --git a/crypto/dleq/src/cross_group/aos.rs b/crypto/dleq/src/cross_group/aos.rs index 3ab8b6879..cadfe2574 100644 --- a/crypto/dleq/src/cross_group/aos.rs +++ b/crypto/dleq/src/cross_group/aos.rs @@ -149,9 +149,8 @@ impl Aos { } // Assumes the ring has already been transcripted in some form. Critically insecure if it hasn't - pub(crate) fn verify( + pub(crate) fn verify( &self, - rng: &mut R, transcript: T, generators: (Generators, Generators), batch: &mut (BatchVerifier<(), C0::G>, BatchVerifier<(), C1::G>), @@ -173,8 +172,8 @@ impl Aos { Self::R_batch(generators, *self.s.last().unwrap(), *ring.last().unwrap(), e); statements.0.push((C0::F::one(), R0_0)); statements.1.push((C1::F::one(), R1_0)); - batch.0.queue(&mut *rng, (), statements.0); - batch.1.queue(&mut *rng, (), statements.1); + batch.0.queue((), statements.0); + batch.1.queue((), statements.1); } Re::e(e_0) => { diff --git a/crypto/dleq/src/cross_group/bits.rs b/crypto/dleq/src/cross_group/bits.rs index 349bcde30..0a0c5c81f 100644 --- a/crypto/dleq/src/cross_group/bits.rs +++ b/crypto/dleq/src/cross_group/bits.rs @@ -143,7 +143,6 @@ impl Self::transcript(transcript, i, self.commitments); self.signature.verify( - rng, transcript.clone(), generators, batch, diff --git a/crypto/dleq/src/cross_group/mod.rs b/crypto/dleq/src/cross_group/mod.rs index f41fca65b..f224a3609 100644 --- a/crypto/dleq/src/cross_group/mod.rs +++ b/crypto/dleq/src/cross_group/mod.rs @@ -323,18 +323,18 @@ impl< }; let mut batch = (BatchVerifier::new(batch_capacity), BatchVerifier::new(batch_capacity)); - self.poks.0.verify(&mut *rng, transcript, generators.0.primary, keys.0, &mut batch.0); - self.poks.1.verify(&mut *rng, transcript, generators.1.primary, keys.1, &mut batch.1); + self.poks.0.verify(ranscript, generators.0.primary, keys.0, &mut batch.0); + self.poks.1.verify(transcript, generators.1.primary, keys.1, &mut batch.1); let mut pow_2 = (generators.0.primary, generators.1.primary); for (i, bits) in self.bits.iter().enumerate() { - bits.verify(&mut *rng, transcript, generators, &mut batch, i, &mut pow_2)?; + bits.verify(transcript, generators, &mut batch, i, &mut pow_2)?; } if let Some(bit) = &self.remainder { - bit.verify(&mut *rng, transcript, generators, &mut batch, self.bits.len(), &mut pow_2)?; + bit.verify(transcript, generators, &mut batch, self.bits.len(), &mut pow_2)?; } - if (!batch.0.verify_vartime()) || (!batch.1.verify_vartime()) { + if (!batch.0.verify_vartime(&mut *rng)) || (!batch.1.verify_vartime(rng)) { Err(DLEqError::InvalidProof)?; } diff --git a/crypto/dleq/src/cross_group/schnorr.rs b/crypto/dleq/src/cross_group/schnorr.rs index 993b3791c..b8c5d7037 100644 --- a/crypto/dleq/src/cross_group/schnorr.rs +++ b/crypto/dleq/src/cross_group/schnorr.rs @@ -51,16 +51,14 @@ impl SchnorrPoK { res } - pub(crate) fn verify( + pub(crate) fn verify( &self, - rng: &mut R, transcript: &mut T, generator: C::G, public_key: C::G, batch: &mut BatchVerifier<(), C::G>, ) { batch.queue( - rng, (), [ (-self.s, generator), diff --git a/crypto/frost/src/key_gen.rs b/crypto/frost/src/key_gen.rs index 825d38089..171989b01 100644 --- a/crypto/frost/src/key_gen.rs +++ b/crypto/frost/src/key_gen.rs @@ -227,9 +227,9 @@ fn complete_r2( values.push((-*share, C::generator())); share.zeroize(); - batch.queue(rng, *l, values); + batch.queue(*l, values); } - batch.verify_with_vartime_blame().map_err(FrostError::InvalidCommitment)?; + batch.verify_with_vartime_blame(rng).map_err(FrostError::InvalidCommitment)?; // Stripe commitments per t and sum them in advance. Calculating verification shares relies on // these sums so preprocessing them is a massive speedup diff --git a/crypto/frost/src/schnorr.rs b/crypto/frost/src/schnorr.rs index cd09c4a00..311cbd60d 100644 --- a/crypto/frost/src/schnorr.rs +++ b/crypto/frost/src/schnorr.rs @@ -65,8 +65,8 @@ pub(crate) fn batch_verify( // -sG values[2].0 = -triple.3.s; - batch.queue(rng, triple.0, values); + batch.queue(triple.0, values); } - batch.verify_vartime_with_vartime_blame() + batch.verify_vartime_with_vartime_blame(rng) } diff --git a/crypto/multiexp/src/batch.rs b/crypto/multiexp/src/batch.rs index 398b9b7d0..d77e8eef4 100644 --- a/crypto/multiexp/src/batch.rs +++ b/crypto/multiexp/src/batch.rs @@ -8,11 +8,67 @@ use group::Group; use crate::{multiexp, multiexp_vartime}; #[cfg(feature = "batch")] -#[derive(Clone, Zeroize)] -pub struct BatchVerifier(Vec<(Id, Vec<(G::Scalar, G)>)>); +#[derive(Clone)] +pub struct BatchVerifier(Vec<(Id, Vec<(G::Scalar, G)>)>); +impl Zeroize for BatchVerifier +where + G::Scalar: Zeroize, +{ + fn zeroize(&mut self) { + for (_, pairs) in self.0.iter_mut() { + for pair in pairs.iter_mut() { + pair.zeroize(); + } + } + } +} + +fn weight(rng: &mut R, i: usize) -> F { + if i == 0 { + F::one() + } else { + let mut weight; + while { + // Generate a random scalar + weight = F::random(&mut *rng); + + // Clears half the bits, maintaining security, to minimize scalar additions + // Is not practically faster for whatever reason + /* + // Generate a random scalar + let mut repr = G::Scalar::random(&mut *rng).to_repr(); + + // Calculate the amount of bytes to clear. We want to clear less than half + let repr_len = repr.as_ref().len(); + let unused_bits = (repr_len * 8) - usize::try_from(G::Scalar::CAPACITY).unwrap(); + // Don't clear any partial bytes + let to_clear = (repr_len / 2) - ((unused_bits + 7) / 8); + + // Clear a safe amount of bytes + for b in &mut repr.as_mut()[.. to_clear] { + *b = 0; + } + + // Ensure these bits are used as the low bits so low scalars multiplied by this don't + // become large scalars + weight = G::Scalar::from_repr(repr).unwrap(); + // Tests if any bit we supposedly just cleared is set, and if so, reverses it + // Not a security issue if this fails, just a minor performance hit at ~2^-120 odds + if weight.to_le_bits().iter().take(to_clear * 8).any(|bit| *bit) { + repr.as_mut().reverse(); + weight = G::Scalar::from_repr(repr).unwrap(); + } + */ + + // Ensure it's non-zero, as a zero scalar would cause this item to pass no matter what + weight.is_zero().into() + } {} + weight + } +} #[cfg(feature = "batch")] -impl BatchVerifier +impl BatchVerifier where ::Scalar: PrimeFieldBits + Zeroize, { @@ -20,89 +76,48 @@ where BatchVerifier(Vec::with_capacity(capacity)) } - pub fn queue>( - &mut self, - rng: &mut R, - id: Id, - pairs: I, - ) { - // Define a unique scalar factor for this set of variables so individual items can't overlap - let u = if self.0.is_empty() { - G::Scalar::one() - } else { - let mut weight; - while { - // Generate a random scalar - weight = G::Scalar::random(&mut *rng); - - // Clears half the bits, maintaining security, to minimize scalar additions - // Is not practically faster for whatever reason - /* - // Generate a random scalar - let mut repr = G::Scalar::random(&mut *rng).to_repr(); - - // Calculate the amount of bytes to clear. We want to clear less than half - let repr_len = repr.as_ref().len(); - let unused_bits = (repr_len * 8) - usize::try_from(G::Scalar::CAPACITY).unwrap(); - // Don't clear any partial bytes - let to_clear = (repr_len / 2) - ((unused_bits + 7) / 8); - - // Clear a safe amount of bytes - for b in &mut repr.as_mut()[.. to_clear] { - *b = 0; - } - - // Ensure these bits are used as the low bits so low scalars multiplied by this don't - // become large scalars - weight = G::Scalar::from_repr(repr).unwrap(); - // Tests if any bit we supposedly just cleared is set, and if so, reverses it - // Not a security issue if this fails, just a minor performance hit at ~2^-120 odds - if weight.to_le_bits().iter().take(to_clear * 8).any(|bit| *bit) { - repr.as_mut().reverse(); - weight = G::Scalar::from_repr(repr).unwrap(); - } - */ - - // Ensure it's non-zero, as a zero scalar would cause this item to pass no matter what - weight.is_zero().into() - } {} - weight - }; - self.0.push((id, pairs.into_iter().map(|(scalar, point)| (scalar * u, point)).collect())); + pub fn queue>(&mut self, id: Id, pairs: I) { + self.0.push((id, pairs.into_iter().collect())); + } + + fn flat(&self, rng: &mut R) -> Vec<(G::Scalar, G)> { + self + .0 + .iter() + .enumerate() + .flat_map(|(i, pairs)| { + let weight: G::Scalar = weight(rng, i); + pairs.1.iter().map(move |(f, g)| (*f * weight, *g)) + }) + .collect::>() } #[must_use] - pub fn verify_core(&self) -> bool { - let mut flat = self.0.iter().flat_map(|pairs| pairs.1.iter()).cloned().collect::>(); + pub fn verify_core(&self, rng: &mut R) -> bool { + let mut flat = self.flat(rng); let res = multiexp(&flat).is_identity().into(); flat.zeroize(); res } - pub fn verify(mut self) -> bool { - let res = self.verify_core(); + #[must_use] + pub fn verify(mut self, rng: &mut R) -> bool { + let res = self.verify_core(rng); self.zeroize(); res } #[must_use] - pub fn verify_vartime(&self) -> bool { - multiexp_vartime(&self.0.iter().flat_map(|pairs| pairs.1.iter()).cloned().collect::>()) - .is_identity() - .into() + pub fn verify_vartime(&self, rng: &mut R) -> bool { + multiexp_vartime(&self.flat(rng)).is_identity().into() } // A constant time variant may be beneficial for robust protocols - pub fn blame_vartime(&self) -> Option { + pub fn blame_vartime(&self, rng: &mut R) -> Option { let mut slice = self.0.as_slice(); while slice.len() > 1 { let split = slice.len() / 2; - if multiexp_vartime( - &slice[.. split].iter().flat_map(|pairs| pairs.1.iter()).cloned().collect::>(), - ) - .is_identity() - .into() - { + if BatchVerifier(slice[.. split].to_vec()).verify_vartime(rng) { slice = &slice[split ..]; } else { slice = &slice[.. split]; @@ -115,17 +130,23 @@ where .map(|(id, _)| *id) } - pub fn verify_with_vartime_blame(mut self) -> Result<(), Id> { - let res = if self.verify_core() { Ok(()) } else { Err(self.blame_vartime().unwrap()) }; + pub fn verify_with_vartime_blame( + mut self, + rng: &mut R, + ) -> Result<(), Id> { + let res = if self.verify_core(rng) { Ok(()) } else { Err(self.blame_vartime(rng).unwrap()) }; self.zeroize(); res } - pub fn verify_vartime_with_vartime_blame(&self) -> Result<(), Id> { - if self.verify_vartime() { + pub fn verify_vartime_with_vartime_blame( + &self, + rng: &mut R, + ) -> Result<(), Id> { + if self.verify_vartime(rng) { Ok(()) } else { - Err(self.blame_vartime().unwrap()) + Err(self.blame_vartime(rng).unwrap()) } } } diff --git a/crypto/multiexp/src/tests/mod.rs b/crypto/multiexp/src/tests/mod.rs index b587d3561..621d70c29 100644 --- a/crypto/multiexp/src/tests/mod.rs +++ b/crypto/multiexp/src/tests/mod.rs @@ -107,6 +107,17 @@ fn test_ed25519() { test_multiexp::(); } +#[cfg(feature = "batch")] +#[test] +fn test_weighting() { + // Queue statements which sum to 0 yet in different groups + let mut verifier = crate::BatchVerifier::new(2); + verifier.queue(0, [(k256::Scalar::one(), ProjectivePoint::GENERATOR)]); + verifier.queue(1, [(-k256::Scalar::one(), ProjectivePoint::GENERATOR)]); + // Should fail + assert!(!verifier.verify_vartime(&mut OsRng)); +} + #[ignore] #[test] fn benchmark() { diff --git a/crypto/schnorr/src/lib.rs b/crypto/schnorr/src/lib.rs index 5b3bf582d..18f824d76 100644 --- a/crypto/schnorr/src/lib.rs +++ b/crypto/schnorr/src/lib.rs @@ -78,15 +78,14 @@ impl> Schnorr { } #[cfg(feature = "batch")] - pub fn queue_batch_verification( + pub fn queue_batch_verification( &self, - rng: &mut R, verifier: &mut BatchVerifier, id: Id, key: C::G, msg: &[u8], ) { - verifier.queue(rng, id, self.verification_statements(key, msg)); + verifier.queue(id, self.verification_statements(key, msg)); } } @@ -126,4 +125,3 @@ impl> Signature for ClassicalSchnorr { self.c == H::hram((C::generator() * self.s) + (key * self.c), key, msg) } } - diff --git a/crypto/schnorr/tests/tests.rs b/crypto/schnorr/tests/tests.rs index be0033441..eeb5e00b4 100644 --- a/crypto/schnorr/tests/tests.rs +++ b/crypto/schnorr/tests/tests.rs @@ -90,26 +90,19 @@ fn batch_verify() { &[MSG, &i.to_le_bytes()].concat(), )); sigs[i].queue_batch_verification( - &mut OsRng, &mut verifier, i, RistrettoPoint::generator() * keys[i], &[MSG, &i.to_le_bytes()].concat(), ); } - assert!(verifier.verify_vartime()); + assert!(verifier.verify_vartime(&mut OsRng)); // Test invalid signatures don't batch verify Schnorr::::new( RistrettoPoint::random(&mut OsRng), Scalar::random(&mut OsRng), ) - .queue_batch_verification( - &mut OsRng, - &mut verifier, - 5, - RistrettoPoint::random(&mut OsRng), - MSG, - ); - assert_eq!(verifier.verify_vartime_with_vartime_blame().unwrap_err(), 5); + .queue_batch_verification(&mut verifier, 5, RistrettoPoint::random(&mut OsRng), MSG); + assert_eq!(verifier.verify_vartime_with_vartime_blame(&mut OsRng).unwrap_err(), 5); } From dbd5aa998dc17c8da6ac33686d8970b1db70b911 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 17 Aug 2022 00:11:27 -0400 Subject: [PATCH 7/8] Serialization Makes curve no_std --- crypto/curve/Cargo.toml | 6 ++++- crypto/curve/src/lib.rs | 13 ++++++++++- crypto/schnorr/Cargo.toml | 2 +- crypto/schnorr/src/lib.rs | 44 ++++++++++++++++++++++++++++++++--- crypto/schnorr/tests/tests.rs | 22 +++++++++++++++++- 5 files changed, 80 insertions(+), 7 deletions(-) diff --git a/crypto/curve/Cargo.toml b/crypto/curve/Cargo.toml index a8e879f7f..a1e63cbdb 100644 --- a/crypto/curve/Cargo.toml +++ b/crypto/curve/Cargo.toml @@ -7,9 +7,13 @@ authors = ["Luke Parker "] edition = "2021" [dependencies] -thiserror = "1" +thiserror = { version = "1", optional = true } zeroize = "1.3" ff = { version = "0.12", features = ["bits"] } group = "0.12" + +[features] +std = [] +serialize = ["std", "thiserror"] diff --git a/crypto/curve/src/lib.rs b/crypto/curve/src/lib.rs index ae96690c0..90cf274b9 100644 --- a/crypto/curve/src/lib.rs +++ b/crypto/curve/src/lib.rs @@ -1,6 +1,10 @@ +#![cfg_attr(not(feature = "std"), no_std)] use core::fmt::Debug; + +#[cfg(feature = "serialize")] use std::io::Read; +#[cfg(feature = "serialize")] use thiserror::Error; use zeroize::Zeroize; @@ -9,11 +13,14 @@ pub use group; pub use group::ff; use group::{ ff::{PrimeField, PrimeFieldBits}, - Group, GroupOps, GroupEncoding, + Group, GroupOps, prime::PrimeGroup, }; +#[cfg(feature = "serialize")] +use group::GroupEncoding; /// Set of errors for curve-related operations, namely encoding and decoding +#[cfg(feature = "serialize")] #[derive(Clone, Error, Debug)] pub enum CurveError { #[error("invalid scalar")] @@ -35,18 +42,21 @@ pub trait Curve: Zeroize + Clone + Copy + PartialEq + Eq + Debug { fn generator() -> Self::G; /// Length of a serialized Scalar field element + #[cfg(feature = "serialize")] #[allow(non_snake_case)] fn F_len() -> usize { ::Repr::default().as_ref().len() } /// Length of a serialized group element + #[cfg(feature = "serialize")] #[allow(non_snake_case)] fn G_len() -> usize { ::Repr::default().as_ref().len() } /// Read a canonical Scalar field element from a Reader + #[cfg(feature = "serialize")] #[allow(non_snake_case)] fn read_F(r: &mut R) -> Result { let mut encoding = ::Repr::default(); @@ -62,6 +72,7 @@ pub trait Curve: Zeroize + Clone + Copy + PartialEq + Eq + Debug { } /// Read a canonical, non-identity, group element from a Reader + #[cfg(feature = "serialize")] #[allow(non_snake_case)] fn read_G(r: &mut R) -> Result { let mut encoding = ::Repr::default(); diff --git a/crypto/schnorr/Cargo.toml b/crypto/schnorr/Cargo.toml index f0088fd55..c7cd02c0f 100644 --- a/crypto/schnorr/Cargo.toml +++ b/crypto/schnorr/Cargo.toml @@ -19,5 +19,5 @@ dalek-ff-group = { path = "../dalek-ff-group" } [features] std = [] -serialize = ["std"] +serialize = ["std", "curve/serialize"] batch = ["std", "multiexp"] diff --git a/crypto/schnorr/src/lib.rs b/crypto/schnorr/src/lib.rs index 18f824d76..e2c39c9ce 100644 --- a/crypto/schnorr/src/lib.rs +++ b/crypto/schnorr/src/lib.rs @@ -1,6 +1,8 @@ #![cfg_attr(not(feature = "std"), no_std)] use core::marker::PhantomData; +#[cfg(feature = "serialize")] +use std::io::{self, Read, Write}; use rand_core::{RngCore, CryptoRng}; @@ -8,15 +10,20 @@ use curve::{ group::{ff::Field, Group}, Curve, }; +#[cfg(feature = "serialize")] +use curve::{ + group::{ff::PrimeField, GroupEncoding}, + CurveError, +}; #[cfg(feature = "batch")] use multiexp::BatchVerifier; pub trait Signature: Sized + Clone + Copy { - /// Sign a msg with the provided nonce. + /// Sign a message with the provided nonce. fn sign_core(key: C::F, nonce: C::F, msg: &[u8]) -> Self; - /// Sign a msg with a randomly generated nonce. + /// Sign a message with a randomly generated nonce. fn sign_random_nonce(rng: &mut R, key: C::F, msg: &[u8]) -> Self { Self::sign_core(key, C::F::random(rng), msg) } @@ -29,7 +36,7 @@ pub trait Signature: Sized + Clone + Copy { double-and-add-style schemes) The latter is the main objection at this time. */ - /// Sign a msg with a deterministic nonce generated as H(x || m). + /// Sign a message with a deterministic nonce generated as H(x || m). // pub fn sign_deterministic_nonce(key: C::F, msg: &[u8]) -> Self {} /// Alias to the preferred sign function, which is currently sign_random_nonce but may change to @@ -41,6 +48,13 @@ pub trait Signature: Sized + Clone + Copy { /// Verify a signature. #[must_use] fn verify(&self, key: C::G, msg: &[u8]) -> bool; + + /// Serialize a signature. + #[cfg(feature = "serialize")] + fn serialize(&self, writer: &mut W) -> io::Result<()>; + /// Deserialize a signature. + #[cfg(feature = "serialize")] + fn deserialize(reader: &mut R) -> Result; } /// A parameterizable HRAm to support a variety of signature specifications. @@ -105,6 +119,18 @@ impl> Signature for Schnorr { } accum.is_identity().into() } + + #[cfg(feature = "serialize")] + fn serialize(&self, writer: &mut W) -> io::Result<()> { + writer.write_all(self.R.to_bytes().as_ref())?; + writer.write_all(self.s.to_repr().as_ref())?; + Ok(()) + } + + #[cfg(feature = "serialize")] + fn deserialize(reader: &mut R) -> Result { + Ok(Self::new(C::read_G(reader)?, C::read_F(reader)?)) + } } impl> ClassicalSchnorr { @@ -124,4 +150,16 @@ impl> Signature for ClassicalSchnorr { fn verify(&self, key: C::G, msg: &[u8]) -> bool { self.c == H::hram((C::generator() * self.s) + (key * self.c), key, msg) } + + #[cfg(feature = "serialize")] + fn serialize(&self, writer: &mut W) -> io::Result<()> { + writer.write_all(self.c.to_repr().as_ref())?; + writer.write_all(self.s.to_repr().as_ref())?; + Ok(()) + } + + #[cfg(feature = "serialize")] + fn deserialize(reader: &mut R) -> Result { + Ok(Self::new(C::read_F(reader)?, C::read_F(reader)?)) + } } diff --git a/crypto/schnorr/tests/tests.rs b/crypto/schnorr/tests/tests.rs index eeb5e00b4..a77940cbd 100644 --- a/crypto/schnorr/tests/tests.rs +++ b/crypto/schnorr/tests/tests.rs @@ -5,13 +5,14 @@ use blake2::{Digest, Blake2b512}; use curve::group::{ff::Field, Group, GroupEncoding}; use dalek_ff_group::{Scalar, RistrettoPoint}; +#[cfg(feature = "batch")] use multiexp::BatchVerifier; use modular_schnorr::{Hram, Signature, Schnorr, ClassicalSchnorr}; const MSG: &[u8] = b"Hello, World"; -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] struct SimpleHram; impl Hram for SimpleHram { #[allow(non_snake_case)] @@ -75,6 +76,25 @@ fn random_fails() { .verify(RistrettoPoint::random(&mut OsRng), MSG)); } +#[cfg(feature = "serialize")] +#[test] +fn serialize() { + let sig = + Schnorr::::sign(&mut OsRng, Scalar::random(&mut OsRng), MSG); + let mut serialized = vec![]; + sig.serialize(&mut serialized).unwrap(); + assert_eq!(sig, Schnorr::deserialize(&mut std::io::Cursor::new(serialized)).unwrap()); + + let sig = ClassicalSchnorr::::sign( + &mut OsRng, + Scalar::random(&mut OsRng), + MSG, + ); + let mut serialized = vec![]; + sig.serialize(&mut serialized).unwrap(); + assert_eq!(sig, ClassicalSchnorr::deserialize(&mut std::io::Cursor::new(serialized)).unwrap()); +} + #[cfg(feature = "batch")] #[test] fn batch_verify() { From 18a845a50fa4af40dab31bdecfd14e99c2cf0c65 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 17 Aug 2022 00:42:14 -0400 Subject: [PATCH 8/8] Correct dleq build errors --- crypto/dleq/Cargo.toml | 2 +- crypto/dleq/src/cross_group/bits.rs | 3 +-- crypto/dleq/src/cross_group/mod.rs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/crypto/dleq/Cargo.toml b/crypto/dleq/Cargo.toml index dc373d299..cc1d683f3 100644 --- a/crypto/dleq/Cargo.toml +++ b/crypto/dleq/Cargo.toml @@ -32,7 +32,7 @@ transcript = { package = "flexible-transcript", path = "../transcript", features [features] std = [] -serialize = ["std"] +serialize = ["std", "curve/serialize"] experimental = ["std", "multiexp"] secure_capacity_difference = [] diff --git a/crypto/dleq/src/cross_group/bits.rs b/crypto/dleq/src/cross_group/bits.rs index 0a0c5c81f..20dc1a54d 100644 --- a/crypto/dleq/src/cross_group/bits.rs +++ b/crypto/dleq/src/cross_group/bits.rs @@ -131,9 +131,8 @@ impl Bits { commitments, signature } } - pub(crate) fn verify( + pub(crate) fn verify( &self, - rng: &mut R, transcript: &mut T, generators: (Generators, Generators), batch: &mut (BatchVerifier<(), C0::G>, BatchVerifier<(), C1::G>), diff --git a/crypto/dleq/src/cross_group/mod.rs b/crypto/dleq/src/cross_group/mod.rs index f224a3609..56f723a1d 100644 --- a/crypto/dleq/src/cross_group/mod.rs +++ b/crypto/dleq/src/cross_group/mod.rs @@ -323,7 +323,7 @@ impl< }; let mut batch = (BatchVerifier::new(batch_capacity), BatchVerifier::new(batch_capacity)); - self.poks.0.verify(ranscript, generators.0.primary, keys.0, &mut batch.0); + self.poks.0.verify(transcript, generators.0.primary, keys.0, &mut batch.0); self.poks.1.verify(transcript, generators.1.primary, keys.1, &mut batch.1); let mut pow_2 = (generators.0.primary, generators.1.primary);