From 878dd1351b27c92446df6b22b4d87c4983017528 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 1 Mar 2021 15:29:07 +0000 Subject: [PATCH] Introduce SpendAuth: SigType and Binding: SigType traits The prior `SpendAuth` and `Binding` enums have been renamed to `sapling::{SpendAuth, Binding}`. These might subsequently be removed from the crate entirely (moving into a wrapping `redjubjub` crate). The code assumes that scalar and point representations are [u8; 32], which will be the case for all curves we instantiate RedDSA with for Zcash. --- Cargo.toml | 1 + README.md | 11 +- benches/bench.rs | 16 +- src/batch.rs | 117 ++++++------ src/error.rs | 2 +- src/frost.rs | 294 +++++++++++++++--------------- src/hash.rs | 27 ++- src/lib.rs | 61 +++++-- src/messages.rs | 43 +++-- src/messages/tests/integration.rs | 36 ++-- src/sapling.rs | 27 +++ src/scalar_mul.rs | 15 +- src/signature.rs | 4 +- src/signing_key.rs | 37 ++-- src/verification_key.rs | 51 +++--- tests/batch.rs | 32 ++-- tests/bincode.rs | 16 +- tests/frost.rs | 10 +- tests/librustzcash_vectors.rs | 10 +- tests/proptests.rs | 10 +- tests/smallorder.rs | 8 +- 21 files changed, 471 insertions(+), 357 deletions(-) create mode 100644 src/sapling.rs diff --git a/Cargo.toml b/Cargo.toml index c7f1217..fa69534 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ features = ["nightly"] blake2b_simd = "0.5" byteorder = "1.4" digest = "0.9" +group = "0.11" jubjub = "0.8" rand_core = "0.6" serde = { version = "1", optional = true, features = ["derive"] } diff --git a/README.md b/README.md index a0b68f1..472df0d 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,18 @@ A minimal [RedDSA][reddsa] implementation for use in Zcash. -Two parameterizations of RedJubjub are used in Zcash, one for +Two specializations of RedDSA are used in Zcash: RedJubjub and +RedPallas. For each of these, two parameterizations are used, one for `BindingSig` and one for `SpendAuthSig`. This library distinguishes these in the type system, using the [sealed] `SigType` trait as a type-level enum. In addition to the `Signature`, `SigningKey`, `VerificationKey` types, the library also provides `VerificationKeyBytes`, a [refinement] of a -`[u8; 32]` indicating that bytes represent an encoding of a RedJubjub +`[u8; 32]` indicating that bytes represent an encoding of a RedDSA verification key. This allows the `VerificationKey` type to cache verification checks related to the verification key encoding. +For all specializations of RedDSA used in Zcash, encodings of signing +and verification keys are 32 bytes. ## Examples @@ -24,7 +27,7 @@ use reddsa::*; let msg = b"Hello!"; // Generate a secret key and sign the message -let sk = SigningKey::::new(thread_rng()); +let sk = SigningKey::::new(thread_rng()); let sig = sk.sign(thread_rng(), msg); // Types can be converted to raw byte arrays using From/Into @@ -32,7 +35,7 @@ let sig_bytes: [u8; 64] = sig.into(); let pk_bytes: [u8; 32] = VerificationKey::from(&sk).into(); // Deserialize and verify the signature. -let sig: Signature = sig_bytes.into(); +let sig: Signature = sig_bytes.into(); assert!( VerificationKey::try_from(pk_bytes) .and_then(|pk| pk.verify(msg, &sig)) diff --git a/benches/bench.rs b/benches/bench.rs index b040237..c530889 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -6,12 +6,12 @@ use std::convert::TryFrom; enum Item { SpendAuth { - vk_bytes: VerificationKeyBytes, - sig: Signature, + vk_bytes: VerificationKeyBytes, + sig: Signature, }, Binding { - vk_bytes: VerificationKeyBytes, - sig: Signature, + vk_bytes: VerificationKeyBytes, + sig: Signature, }, } @@ -21,13 +21,13 @@ fn sigs_with_distinct_keys() -> impl Iterator { let msg = b"Bench"; match rng.gen::() % 2 { 0 => { - let sk = SigningKey::::new(thread_rng()); + let sk = SigningKey::::new(thread_rng()); let vk_bytes = VerificationKey::from(&sk).into(); let sig = sk.sign(thread_rng(), &msg[..]); Item::SpendAuth { vk_bytes, sig } } 1 => { - let sk = SigningKey::::new(thread_rng()); + let sk = SigningKey::::new(thread_rng()); let vk_bytes = VerificationKey::from(&sk).into(); let sig = sk.sign(thread_rng(), &msg[..]); Item::Binding { vk_bytes, sig } @@ -76,10 +76,10 @@ fn bench_batch_verify(c: &mut Criterion) { let msg = b"Bench"; match item { Item::SpendAuth { vk_bytes, sig } => { - batch.queue((*vk_bytes, *sig, msg)); + batch.queue(batch::Item::from_spendauth(*vk_bytes, *sig, msg)); } Item::Binding { vk_bytes, sig } => { - batch.queue((*vk_bytes, *sig, msg)); + batch.queue(batch::Item::from_binding(*vk_bytes, *sig, msg)); } } } diff --git a/src/batch.rs b/src/batch.rs index b82873b..e092d21 100644 --- a/src/batch.rs +++ b/src/batch.rs @@ -8,7 +8,7 @@ // - Deirdre Connolly // - Henry de Valence -//! Performs batch RedJubjub signature verification. +//! Performs batch RedDSA signature verification. //! //! Batch verification asks whether *all* signatures in some set are valid, //! rather than asking whether *each* of them is valid. This allows sharing @@ -20,10 +20,14 @@ use std::convert::TryFrom; -use jubjub::*; +use group::{ + cofactor::CofactorGroup, + ff::{Field, PrimeField}, + GroupEncoding, +}; use rand_core::{CryptoRng, RngCore}; -use crate::{private::Sealed, scalar_mul::VartimeMultiscalarMul, *}; +use crate::{private::SealedScalar, scalar_mul::VartimeMultiscalarMul, *}; // Shim to generate a random 128bit value in a [u64; 4], without // importing `rand`. @@ -35,16 +39,16 @@ fn gen_128_bits(mut rng: R) -> [u64; 4] { } #[derive(Clone, Debug)] -enum Inner { +enum Inner> { SpendAuth { - vk_bytes: VerificationKeyBytes, - sig: Signature, - c: Scalar, + vk_bytes: VerificationKeyBytes, + sig: Signature, + c: S::Scalar, }, Binding { - vk_bytes: VerificationKeyBytes, - sig: Signature, - c: Scalar, + vk_bytes: VerificationKeyBytes, + sig: Signature, + c: B::Scalar, }, } @@ -54,26 +58,19 @@ enum Inner { /// lifetime of the message. This is useful when using the batch verification API /// in an async context. #[derive(Clone, Debug)] -pub struct Item { - inner: Inner, +pub struct Item> { + inner: Inner, } -impl<'msg, M: AsRef<[u8]>> - From<( - VerificationKeyBytes, - Signature, - &'msg M, - )> for Item -{ - fn from( - (vk_bytes, sig, msg): ( - VerificationKeyBytes, - Signature, - &'msg M, - ), +impl> Item { + /// Create a batch item from a `SpendAuth` signature. + pub fn from_spendauth<'msg, M: AsRef<[u8]>>( + vk_bytes: VerificationKeyBytes, + sig: Signature, + msg: &'msg M, ) -> Self { // Compute c now to avoid dependency on the msg lifetime. - let c = HStar::default() + let c = HStar::::default() .update(&sig.r_bytes[..]) .update(&vk_bytes.bytes[..]) .update(msg) @@ -82,16 +79,15 @@ impl<'msg, M: AsRef<[u8]>> inner: Inner::SpendAuth { vk_bytes, sig, c }, } } -} -impl<'msg, M: AsRef<[u8]>> From<(VerificationKeyBytes, Signature, &'msg M)> - for Item -{ - fn from( - (vk_bytes, sig, msg): (VerificationKeyBytes, Signature, &'msg M), + /// Create a batch item from a `Binding` signature. + pub fn from_binding<'msg, M: AsRef<[u8]>>( + vk_bytes: VerificationKeyBytes, + sig: Signature, + msg: &'msg M, ) -> Self { // Compute c now to avoid dependency on the msg lifetime. - let c = HStar::default() + let c = HStar::::default() .update(&sig.r_bytes[..]) .update(&vk_bytes.bytes[..]) .update(msg) @@ -100,9 +96,7 @@ impl<'msg, M: AsRef<[u8]>> From<(VerificationKeyBytes, Signature Result<(), Error> { match self.inner { - Inner::Binding { vk_bytes, sig, c } => VerificationKey::::try_from(vk_bytes) - .and_then(|vk| vk.verify_prehashed(&sig, c)), + Inner::Binding { vk_bytes, sig, c } => { + VerificationKey::::try_from(vk_bytes).and_then(|vk| vk.verify_prehashed(&sig, c)) + } Inner::SpendAuth { vk_bytes, sig, c } => { - VerificationKey::::try_from(vk_bytes) - .and_then(|vk| vk.verify_prehashed(&sig, c)) + VerificationKey::::try_from(vk_bytes).and_then(|vk| vk.verify_prehashed(&sig, c)) } } } } -#[derive(Default)] /// A batch verification context. -pub struct Verifier { +pub struct Verifier> { /// Signature data queued for verification. - signatures: Vec, + signatures: Vec>, } -impl Verifier { +impl> Default for Verifier { + fn default() -> Self { + Verifier { signatures: vec![] } + } +} + +impl> Verifier { /// Construct a new batch verifier. - pub fn new() -> Verifier { + pub fn new() -> Verifier { Verifier::default() } /// Queue an Item for verification. - pub fn queue>(&mut self, item: I) { + pub fn queue>>(&mut self, item: I) { self.signatures.push(item.into()); } @@ -163,7 +162,7 @@ impl Verifier { /// - h_G is the cofactor of the group; /// - P_G is the generator of the subgroup; /// - /// Since RedJubjub uses different subgroups for different types + /// Since RedDSA uses different subgroups for different types /// of signatures, SpendAuth's and Binding's, we need to have yet /// another point and associated scalar accumulator for all the /// signatures of each type in our batch, but we can still @@ -185,8 +184,8 @@ impl Verifier { let mut VKs = Vec::with_capacity(n); let mut R_coeffs = Vec::with_capacity(self.signatures.len()); let mut Rs = Vec::with_capacity(self.signatures.len()); - let mut P_spendauth_coeff = Scalar::zero(); - let mut P_binding_coeff = Scalar::zero(); + let mut P_spendauth_coeff = S::Scalar::zero(); + let mut P_binding_coeff = B::Scalar::zero(); for item in self.signatures.iter() { let (s_bytes, r_bytes, c) = match item.inner { @@ -196,7 +195,9 @@ impl Verifier { let s = { // XXX-jubjub: should not use CtOption here - let maybe_scalar = Scalar::from_bytes(&s_bytes); + let mut repr = ::Repr::default(); + repr.as_mut().copy_from_slice(&s_bytes); + let maybe_scalar = S::Scalar::from_repr(repr); if maybe_scalar.is_some().into() { maybe_scalar.unwrap() } else { @@ -207,9 +208,11 @@ impl Verifier { let R = { // XXX-jubjub: should not use CtOption here // XXX-jubjub: inconsistent ownership in from_bytes - let maybe_point = AffinePoint::from_bytes(r_bytes); + let mut repr = ::Repr::default(); + repr.as_mut().copy_from_slice(&r_bytes); + let maybe_point = S::Point::from_bytes(&repr); if maybe_point.is_some().into() { - jubjub::ExtendedPoint::from(maybe_point.unwrap()) + maybe_point.unwrap() } else { return Err(Error::InvalidSignature); } @@ -217,14 +220,14 @@ impl Verifier { let VK = match item.inner { Inner::SpendAuth { vk_bytes, .. } => { - VerificationKey::::try_from(vk_bytes.bytes)?.point + VerificationKey::::try_from(vk_bytes.bytes)?.point } Inner::Binding { vk_bytes, .. } => { - VerificationKey::::try_from(vk_bytes.bytes)?.point + VerificationKey::::try_from(vk_bytes.bytes)?.point } }; - let z = Scalar::from_raw(gen_128_bits(&mut rng)); + let z = S::Scalar::from_raw(gen_128_bits(&mut rng)); let P_coeff = z * s; match item.inner { @@ -239,7 +242,7 @@ impl Verifier { R_coeffs.push(z); Rs.push(R); - VK_coeffs.push(Scalar::zero() + (z * c)); + VK_coeffs.push(S::Scalar::zero() + (z * c)); VKs.push(VK); } @@ -250,10 +253,10 @@ impl Verifier { .chain(VK_coeffs.iter()) .chain(R_coeffs.iter()); - let basepoints = [SpendAuth::basepoint(), Binding::basepoint()]; + let basepoints = [S::basepoint(), B::basepoint()]; let points = basepoints.iter().chain(VKs.iter()).chain(Rs.iter()); - let check = ExtendedPoint::vartime_multiscalar_mul(scalars, points); + let check = S::Point::vartime_multiscalar_mul(scalars, points); if check.is_small_order().into() { Ok(()) diff --git a/src/error.rs b/src/error.rs index 3868de5..9d2da1a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -10,7 +10,7 @@ use thiserror::Error; -/// An error related to RedJubJub signatures. +/// An error related to RedDSA signatures. #[derive(Error, Debug, Copy, Clone, Eq, PartialEq)] pub enum Error { /// The encoding of a signing key was malformed. diff --git a/src/frost.rs b/src/frost.rs index befec93..f0e5a04 100644 --- a/src/frost.rs +++ b/src/frost.rs @@ -23,36 +23,43 @@ //! Internally, keygen_with_dealer generates keys using Verifiable Secret //! Sharing, where shares are generated using Shamir Secret Sharing. -use std::{collections::HashMap, convert::TryFrom, marker::PhantomData}; +use std::{ + collections::HashMap, + convert::{TryFrom, TryInto}, + marker::PhantomData, +}; -use jubjub::Scalar; +use group::{ + cofactor::CofactorCurve, + ff::{Field, PrimeField}, + Curve, Group, GroupEncoding, +}; use rand_core::{CryptoRng, RngCore}; use zeroize::DefaultIsZeroes; -use crate::private::Sealed; -use crate::{HStar, Signature, SpendAuth, VerificationKey}; +use crate::{private::SealedScalar, sapling, HStar, Signature, SpendAuth, VerificationKey}; /// A secret scalar value representing a single signer's secret key. #[derive(Clone, Copy, Default, PartialEq)] -pub struct Secret(pub(crate) Scalar); +pub struct Secret(pub(crate) S::Scalar); // Zeroizes `Secret` to be the `Default` value on drop (when it goes out of // scope). Luckily the derived `Default` includes the `Default` impl of // jubjub::Fr/Scalar, which is four 0u64's under the hood. -impl DefaultIsZeroes for Secret {} +impl DefaultIsZeroes for Secret {} -impl From for Secret { - fn from(source: Scalar) -> Secret { +impl From for Secret { + fn from(source: jubjub::Scalar) -> Secret { Secret(source) } } /// A public group element that represents a single signer's public key. #[derive(Copy, Clone, Debug, PartialEq)] -pub struct Public(jubjub::ExtendedPoint); +pub struct Public(S::Point); -impl From for Public { - fn from(source: jubjub::ExtendedPoint) -> Public { +impl From for Public { + fn from(source: jubjub::ExtendedPoint) -> Public { Public(source) } } @@ -61,12 +68,12 @@ impl From for Public { /// n is the total number of shares and t is the threshold required to /// reconstruct the secret; in this case we use Shamir's secret sharing. #[derive(Clone)] -pub struct Share { +pub struct Share { receiver_index: u64, /// Secret Key. - pub(crate) value: Secret, + pub(crate) value: Secret, /// The commitments to be distributed among signers. - pub(crate) commitment: ShareCommitment, + pub(crate) commitment: ShareCommitment, } /// A Jubjub point that is a commitment to one coefficient of our secret @@ -75,7 +82,7 @@ pub struct Share { /// This is a (public) commitment to one coefficient of a secret polynomial used /// for performing verifiable secret sharing for a Shamir secret share. #[derive(Clone, PartialEq)] -pub(crate) struct Commitment(pub(crate) jubjub::AffinePoint); +pub(crate) struct Commitment(pub(crate) ::Affine); /// Contains the commitments to the coefficients for our secret polynomial _f_, /// used to generate participants' key shares. @@ -90,30 +97,30 @@ pub(crate) struct Commitment(pub(crate) jubjub::AffinePoint); /// some agreed-upon public location for publication, where each participant can /// ensure that they received the correct (and same) value. #[derive(Clone)] -pub struct ShareCommitment(pub(crate) Vec); +pub struct ShareCommitment(pub(crate) Vec>); /// The product of all signers' individual commitments, published as part of the /// final signature. #[derive(PartialEq)] -pub struct GroupCommitment(pub(crate) jubjub::AffinePoint); +pub struct GroupCommitment(pub(crate) ::Affine); /// Secret and public key material generated by a dealer performing /// [`keygen_with_dealer`]. /// /// To derive a FROST keypair, the receiver of the [`SharePackage`] *must* call /// .into(), which under the hood also performs validation. -pub struct SharePackage { +pub struct SharePackage { /// The public signing key that represents the entire group. - pub(crate) group_public: VerificationKey, + pub(crate) group_public: VerificationKey, /// Denotes the participant index each share is owned by. pub index: u64, /// This participant's public key. - pub(crate) public: Public, + pub(crate) public: Public, /// This participant's share. - pub(crate) share: Share, + pub(crate) share: Share, } -impl TryFrom for KeyPackage { +impl TryFrom> for KeyPackage { type Error = &'static str; /// Tries to verify a share and construct a [`KeyPackage`] from it. @@ -124,7 +131,7 @@ impl TryFrom for KeyPackage { /// every participant has the same view of the commitment issued by the /// dealer, but implementations *MUST* make sure that all participants have /// a consistent view of this commitment in practice. - fn try_from(sharepackage: SharePackage) -> Result { + fn try_from(sharepackage: SharePackage) -> Result { verify_share(&sharepackage.share)?; Ok(KeyPackage { @@ -143,25 +150,25 @@ impl TryFrom for KeyPackage { /// participants, who then perform verification, before deriving /// [`KeyPackage`]s, which they store to later use during signing. #[allow(dead_code)] -pub struct KeyPackage { +pub struct KeyPackage { index: u64, - secret_share: Secret, - public: Public, - group_public: VerificationKey, + secret_share: Secret, + public: Public, + group_public: VerificationKey, } /// Public data that contains all the signer's public keys as well as the /// group public key. /// /// Used for verification purposes before publishing a signature. -pub struct PublicKeyPackage { +pub struct PublicKeyPackage { /// When performing signing, the coordinator must ensure that they have the /// correct view of participant's public keys to perform verification before /// publishing a signature. signer_pubkeys represents all signers for a /// signing operation. - pub(crate) signer_pubkeys: HashMap, + pub(crate) signer_pubkeys: HashMap>, /// group_public represents the joint public key for the entire group. - pub group_public: VerificationKey, + pub group_public: VerificationKey, } /// Allows all participants' keys to be generated using a central, trusted @@ -172,22 +179,22 @@ pub struct PublicKeyPackage { /// key. The output from this function is a set of shares along with one single /// commitment that participants use to verify the integrity of the share. The /// number of signers is limited to 255. -pub fn keygen_with_dealer( +pub fn keygen_with_dealer( num_signers: u8, threshold: u8, mut rng: R, -) -> Result<(Vec, PublicKeyPackage), &'static str> { +) -> Result<(Vec>, PublicKeyPackage), &'static str> { let mut bytes = [0; 64]; rng.fill_bytes(&mut bytes); - let secret = Secret(Scalar::from_bytes_wide(&bytes)); + let secret = Secret(S::Scalar::from_bytes_wide(&bytes)); let group_public = VerificationKey::from(&secret.0); let shares = generate_shares(&secret, num_signers, threshold, rng)?; - let mut sharepackages: Vec = Vec::with_capacity(num_signers as usize); - let mut signer_pubkeys: HashMap = HashMap::with_capacity(num_signers as usize); + let mut sharepackages: Vec> = Vec::with_capacity(num_signers as usize); + let mut signer_pubkeys: HashMap> = HashMap::with_capacity(num_signers as usize); for share in shares { - let signer_public = Public(SpendAuth::basepoint() * share.value.0); + let signer_public = Public(S::basepoint() * share.value.0); sharepackages.push(SharePackage { index: share.receiver_index, share: share.clone(), @@ -213,13 +220,13 @@ pub fn keygen_with_dealer( /// mechanism as all other signing participants. Note that participants *MUST* /// ensure that they have the same view as all other participants of the /// commitment! -fn verify_share(share: &Share) -> Result<(), &'static str> { - let f_result = SpendAuth::basepoint() * share.value.0; +fn verify_share(share: &Share) -> Result<(), &'static str> { + let f_result = S::basepoint() * share.value.0; - let x = Scalar::from(share.receiver_index as u64); + let x = S::Scalar::from(share.receiver_index as u64); let (_, result) = share.commitment.0.iter().fold( - (Scalar::one(), jubjub::ExtendedPoint::identity()), + (S::Scalar::one(), S::Point::identity()), |(x_to_the_i, sum_so_far), comm_i| (x_to_the_i * x, sum_so_far + comm_i.0 * x_to_the_i), ); @@ -245,12 +252,12 @@ fn verify_share(share: &Share) -> Result<(), &'static str> { /// polynomial f /// - For each participant i, their secret share is f(i) /// - The commitment to the secret polynomial f is [g^a, g^b, g^c] -fn generate_shares( - secret: &Secret, +fn generate_shares( + secret: &Secret, numshares: u8, threshold: u8, mut rng: R, -) -> Result, &'static str> { +) -> Result>, &'static str> { if threshold < 1 { return Err("Threshold cannot be 0"); } @@ -265,36 +272,37 @@ fn generate_shares( let numcoeffs = threshold - 1; - let mut coefficients: Vec = Vec::with_capacity(threshold as usize); + let mut coefficients: Vec = Vec::with_capacity(threshold as usize); - let mut shares: Vec = Vec::with_capacity(numshares as usize); + let mut shares: Vec> = Vec::with_capacity(numshares as usize); - let mut commitment: ShareCommitment = ShareCommitment(Vec::with_capacity(threshold as usize)); + let mut commitment: ShareCommitment = + ShareCommitment(Vec::with_capacity(threshold as usize)); for _ in 0..numcoeffs { let mut bytes = [0; 64]; rng.fill_bytes(&mut bytes); - coefficients.push(Scalar::from_bytes_wide(&bytes)); + coefficients.push(S::Scalar::from_bytes_wide(&bytes)); } // Verifiable secret sharing, to make sure that participants can ensure their secret is consistent // with every other participant's. - commitment.0.push(Commitment(jubjub::AffinePoint::from( - SpendAuth::basepoint() * secret.0, - ))); + commitment + .0 + .push(Commitment((S::basepoint() * secret.0).to_affine())); for c in &coefficients { - commitment.0.push(Commitment(jubjub::AffinePoint::from( - SpendAuth::basepoint() * c, - ))); + commitment + .0 + .push(Commitment((S::basepoint() * c).to_affine())); } // Evaluate the polynomial with `secret` as the constant term // and `coeffs` as the other coefficients at the point x=share_index, // using Horner's method. for index in 1..numshares + 1 { - let scalar_index = Scalar::from(index as u64); - let mut value = Scalar::zero(); + let scalar_index = S::Scalar::from(index as u64); + let mut value = S::Scalar::zero(); // Polynomial evaluation, for this index for i in (0..numcoeffs).rev() { @@ -319,17 +327,17 @@ fn generate_shares( /// operation; re-using nonces will result in leakage of a signer's long-lived /// signing key. #[derive(Clone, Copy, Default)] -pub struct SigningNonces { - hiding: Scalar, - binding: Scalar, +pub struct SigningNonces { + hiding: S::Scalar, + binding: S::Scalar, } // Zeroizes `SigningNonces` to be the `Default` value on drop (when it goes out // of scope). Luckily the derived `Default` includes the `Default` impl of the // `jubjub::Fr/Scalar`'s, which is four 0u64's under the hood. -impl DefaultIsZeroes for SigningNonces {} +impl DefaultIsZeroes for SigningNonces {} -impl SigningNonces { +impl SigningNonces { /// Generates a new signing nonce. /// /// Each participant generates signing nonces before performing a signing @@ -353,8 +361,8 @@ impl SigningNonces { // The values of 'hiding' and 'binding' must be non-zero so that commitments are not the // identity. - let hiding = Scalar::from_bytes_wide(&random_nonzero_bytes(rng)); - let binding = Scalar::from_bytes_wide(&random_nonzero_bytes(rng)); + let hiding = S::Scalar::from_bytes_wide(&random_nonzero_bytes(rng)); + let binding = S::Scalar::from_bytes_wide(&random_nonzero_bytes(rng)); Self { hiding, binding } } @@ -365,32 +373,32 @@ impl SigningNonces { /// This step can be batched if desired by the implementation. Each /// SigningCommitment can be used for exactly *one* signature. #[derive(Copy, Clone)] -pub struct SigningCommitments { +pub struct SigningCommitments { /// The participant index pub(crate) index: u64, /// The hiding point. - pub(crate) hiding: jubjub::ExtendedPoint, + pub(crate) hiding: S::Point, /// The binding point. - pub(crate) binding: jubjub::ExtendedPoint, + pub(crate) binding: S::Point, } -impl From<(u64, &SigningNonces)> for SigningCommitments { - /// For SpendAuth signatures only, not Binding signatures, in RedJubjub/Zcash. - fn from((index, nonces): (u64, &SigningNonces)) -> Self { +impl From<(u64, &SigningNonces)> for SigningCommitments { + /// For SpendAuth signatures only, not Binding signatures, in RedDSA/Zcash. + fn from((index, nonces): (u64, &SigningNonces)) -> Self { Self { index, - hiding: SpendAuth::basepoint() * nonces.hiding, - binding: SpendAuth::basepoint() * nonces.binding, + hiding: S::basepoint() * nonces.hiding, + binding: S::basepoint() * nonces.binding, } } } /// Generated by the coordinator of the signing operation and distributed to /// each signing party. -pub struct SigningPackage { +pub struct SigningPackage { /// The set of commitments participants published in the first round of the /// protocol. - pub signing_commitments: Vec, + pub signing_commitments: Vec>, /// Message which each participant will sign. /// /// Each signer should perform protocol-specific verification on the message. @@ -399,37 +407,35 @@ pub struct SigningPackage { /// A representation of a single signature used in FROST structures and messages. #[derive(Clone, Copy, Default, PartialEq)] -pub struct SignatureResponse(pub(crate) Scalar); +pub struct SignatureResponse(pub(crate) S::Scalar); /// A participant's signature share, which the coordinator will use to aggregate /// with all other signer's shares into the joint signature. #[derive(Clone, Copy, Default)] -pub struct SignatureShare { +pub struct SignatureShare { /// Represents the participant index. pub(crate) index: u64, /// This participant's signature over the message. - pub(crate) signature: SignatureResponse, + pub(crate) signature: SignatureResponse, } // Zeroizes `SignatureShare` to be the `Default` value on drop (when it goes out // of scope). Luckily the derived `Default` includes the `Default` impl of // jubjub::Fr/Scalar, which is four 0u64's under the hood, and u32, which is // 0u32. -impl DefaultIsZeroes for SignatureShare {} +impl DefaultIsZeroes for SignatureShare {} -impl SignatureShare { +impl SignatureShare { /// Tests if a signature share issued by a participant is valid before /// aggregating it into a final joint signature to publish. pub fn check_is_valid( &self, - pubkey: &Public, - lambda_i: Scalar, - commitment: jubjub::ExtendedPoint, - challenge: Scalar, + pubkey: &Public, + lambda_i: S::Scalar, + commitment: S::Point, + challenge: S::Scalar, ) -> Result<(), &'static str> { - if (SpendAuth::basepoint() * self.signature.0) - != (commitment + pubkey.0 * challenge * lambda_i) - { + if (S::basepoint() * self.signature.0) != (commitment + pubkey.0 * challenge * lambda_i) { return Err("Invalid signature share"); } Ok(()) @@ -448,16 +454,18 @@ impl SignatureShare { /// turns out to be too conservative. // TODO: Make sure the above is a correct statement, fix if needed in: // https://github.com/ZcashFoundation/redjubjub/issues/111 -pub fn preprocess( +pub fn preprocess( num_nonces: u8, participant_index: u64, rng: &mut R, -) -> (Vec, Vec) +) -> (Vec>, Vec>) where R: CryptoRng + RngCore, + S: SpendAuth, { - let mut signing_nonces: Vec = Vec::with_capacity(num_nonces as usize); - let mut signing_commitments: Vec = Vec::with_capacity(num_nonces as usize); + let mut signing_nonces: Vec> = Vec::with_capacity(num_nonces as usize); + let mut signing_commitments: Vec> = + Vec::with_capacity(num_nonces as usize); for _ in 0..num_nonces { let nonces = SigningNonces::new(rng); @@ -470,28 +478,28 @@ where /// Generates the binding factor that ensures each signature share is strongly /// bound to a signing set, specific set of commitments, and a specific message. -fn gen_rho_i(index: u64, signing_package: &SigningPackage) -> Scalar { +fn gen_rho_i(index: u64, signing_package: &SigningPackage) -> S::Scalar { // Hash signature message with HStar before deriving the binding factor. // // To avoid a collision with other inputs to the hash that generates the // binding factor, we should hash our input message first. Our 'standard' // hash is HStar, which uses a domain separator already, and is the same one // that generates the binding factor. - let message_hash = HStar::default() + let message_hash = HStar::::default() .update(signing_package.message.as_slice()) .finalize(); - let mut hasher = HStar::default(); + let mut hasher = HStar::::default(); hasher .update("FROST_rho".as_bytes()) .update(index.to_be_bytes()) - .update(message_hash.to_bytes()); + .update(message_hash.to_repr()); for item in signing_package.signing_commitments.iter() { hasher.update(item.index.to_be_bytes()); - let hiding_bytes = jubjub::AffinePoint::from(item.hiding).to_bytes(); + let hiding_bytes = item.hiding.to_bytes(); hasher.update(hiding_bytes); - let binding_bytes = jubjub::AffinePoint::from(item.binding).to_bytes(); + let binding_bytes = item.binding.to_bytes(); hasher.update(binding_bytes); } @@ -500,11 +508,11 @@ fn gen_rho_i(index: u64, signing_package: &SigningPackage) -> Scalar { /// Generates the group commitment which is published as part of the joint /// Schnorr signature. -fn gen_group_commitment( - signing_package: &SigningPackage, - bindings: &HashMap, -) -> Result { - let identity = jubjub::ExtendedPoint::identity(); +fn gen_group_commitment( + signing_package: &SigningPackage, + bindings: &HashMap, +) -> Result, &'static str> { + let identity = S::Point::identity(); let mut accumulator = identity; for commitment in signing_package.signing_commitments.iter() { @@ -520,18 +528,18 @@ fn gen_group_commitment( accumulator += commitment.hiding + (commitment.binding * rho_i) } - Ok(GroupCommitment(jubjub::AffinePoint::from(accumulator))) + Ok(GroupCommitment(accumulator.to_affine())) } /// Generates the challenge as is required for Schnorr signatures. -fn gen_challenge( - signing_package: &SigningPackage, - group_commitment: &GroupCommitment, - group_public: &VerificationKey, -) -> Scalar { - let group_commitment_bytes = jubjub::AffinePoint::from(group_commitment.0).to_bytes(); +fn gen_challenge( + signing_package: &SigningPackage, + group_commitment: &GroupCommitment, + group_public: &VerificationKey, +) -> S::Scalar { + let group_commitment_bytes = group_commitment.0.to_bytes(); - HStar::default() + HStar::::default() .update(group_commitment_bytes) .update(group_public.bytes.bytes) .update(signing_package.message.as_slice()) @@ -539,21 +547,21 @@ fn gen_challenge( } /// Generates the lagrange coefficient for the i'th participant. -fn gen_lagrange_coeff( +fn gen_lagrange_coeff( signer_index: u64, - signing_package: &SigningPackage, -) -> Result { - let mut num = Scalar::one(); - let mut den = Scalar::one(); + signing_package: &SigningPackage, +) -> Result { + let mut num = S::Scalar::one(); + let mut den = S::Scalar::one(); for commitment in signing_package.signing_commitments.iter() { if commitment.index == signer_index { continue; } - num *= Scalar::from(commitment.index as u64); - den *= Scalar::from(commitment.index as u64) - Scalar::from(signer_index as u64); + num *= S::Scalar::from(commitment.index as u64); + den *= S::Scalar::from(commitment.index as u64) - S::Scalar::from(signer_index as u64); } - if den == Scalar::zero() { + if den == S::Scalar::zero() { return Err("Duplicate shares provided"); } @@ -571,12 +579,12 @@ fn gen_lagrange_coeff( /// /// Assumes the participant has already determined which nonce corresponds with /// the commitment that was assigned by the coordinator in the SigningPackage. -pub fn sign( - signing_package: &SigningPackage, - participant_nonces: SigningNonces, - share_package: &SharePackage, -) -> Result { - let mut bindings: HashMap = +pub fn sign( + signing_package: &SigningPackage, + participant_nonces: SigningNonces, + share_package: &SharePackage, +) -> Result, &'static str> { + let mut bindings: HashMap = HashMap::with_capacity(signing_package.signing_commitments.len()); for comm in signing_package.signing_commitments.iter() { @@ -599,7 +607,7 @@ pub fn sign( .ok_or("No matching binding!")?; // The Schnorr signature share - let signature: Scalar = participant_nonces.hiding + let signature: S::Scalar = participant_nonces.hiding + (participant_nonces.binding * participant_rho_i) + (lambda_i * share_package.share.value.0 * challenge); @@ -624,12 +632,12 @@ pub fn sign( /// signature, if the coordinator themselves is a signer and misbehaves, they /// can avoid that step. However, at worst, this results in a denial of /// service attack due to publishing an invalid signature. -pub fn aggregate( - signing_package: &SigningPackage, - signing_shares: &[SignatureShare], - pubkeys: &PublicKeyPackage, -) -> Result, &'static str> { - let mut bindings: HashMap = +pub fn aggregate( + signing_package: &SigningPackage, + signing_shares: &[SignatureShare], + pubkeys: &PublicKeyPackage, +) -> Result, &'static str> { + let mut bindings: HashMap = HashMap::with_capacity(signing_package.signing_commitments.len()); for comm in signing_package.signing_commitments.iter() { @@ -658,14 +666,14 @@ pub fn aggregate( // The aggregation of the signature shares by summing them up, resulting in // a plain Schnorr signature. - let mut z = Scalar::zero(); + let mut z = S::Scalar::zero(); for signature_share in signing_shares { z += signature_share.signature.0; } Ok(Signature { - r_bytes: jubjub::AffinePoint::from(group_commitment.0).to_bytes(), - s_bytes: z.to_bytes(), + r_bytes: group_commitment.0.to_bytes().as_ref().try_into().unwrap(), + s_bytes: z.to_repr().as_ref().try_into().unwrap(), _marker: PhantomData, }) } @@ -673,35 +681,37 @@ pub fn aggregate( #[cfg(test)] mod tests { use super::*; + use crate::{private::Sealed, sapling}; + use jubjub::Scalar; use rand::thread_rng; - fn reconstruct_secret(shares: Vec) -> Result { + fn reconstruct_secret(shares: Vec>) -> Result { let numshares = shares.len(); if numshares < 1 { return Err("No shares provided"); } - let mut lagrange_coeffs: Vec = Vec::with_capacity(numshares as usize); + let mut lagrange_coeffs: Vec = Vec::with_capacity(numshares as usize); for i in 0..numshares { - let mut num = Scalar::one(); - let mut den = Scalar::one(); + let mut num = S::Scalar::one(); + let mut den = S::Scalar::one(); for j in 0..numshares { if j == i { continue; } - num *= Scalar::from(shares[j].receiver_index as u64); - den *= Scalar::from(shares[j].receiver_index as u64) - - Scalar::from(shares[i].receiver_index as u64); + num *= S::Scalar::from(shares[j].receiver_index as u64); + den *= S::Scalar::from(shares[j].receiver_index as u64) + - S::Scalar::from(shares[i].receiver_index as u64); } - if den == Scalar::zero() { + if den == S::Scalar::zero() { return Err("Duplicate shares provided"); } lagrange_coeffs.push(num * den.invert().unwrap()); } - let mut secret = Scalar::zero(); + let mut secret = S::Scalar::zero(); for i in 0..numshares { secret += lagrange_coeffs[i] * shares[i].value.0; @@ -720,9 +730,9 @@ mod tests { rng.fill_bytes(&mut bytes); let secret = Secret(Scalar::from_bytes_wide(&bytes)); - let _ = SpendAuth::basepoint() * secret.0; + let _ = sapling::SpendAuth::basepoint() * secret.0; - let shares = generate_shares(&secret, 5, 3, rng).unwrap(); + let shares = generate_shares::<_, sapling::SpendAuth>(&secret, 5, 3, rng).unwrap(); for share in shares.iter() { assert_eq!(verify_share(&share), Ok(())); diff --git a/src/hash.rs b/src/hash.rs index a7ca75e..d73f822 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -8,25 +8,32 @@ // - Deirdre Connolly // - Henry de Valence -use blake2b_simd::{Params, State}; -use jubjub::Scalar; +use std::marker::PhantomData; -/// Provides H^star, the hash-to-scalar function used by RedJubjub. -pub struct HStar { +use blake2b_simd::{Params, State}; + +use crate::{private::SealedScalar, SigType}; + +/// Provides H^star, the hash-to-scalar function used by RedDSA. +pub struct HStar { state: State, + _marker: PhantomData, } -impl Default for HStar { +impl Default for HStar { fn default() -> Self { let state = Params::new() .hash_length(64) - .personal(b"Zcash_RedJubjubH") + .personal(T::H_STAR_PERSONALIZATION) .to_state(); - Self { state } + Self { + state, + _marker: PhantomData::default(), + } } } -impl HStar { +impl HStar { /// Add `data` to the hash, and return `Self` for chaining. pub fn update(&mut self, data: impl AsRef<[u8]>) -> &mut Self { self.state.update(data.as_ref()); @@ -34,7 +41,7 @@ impl HStar { } /// Consume `self` to compute the hash output. - pub fn finalize(&self) -> Scalar { - Scalar::from_bytes_wide(self.state.finalize().as_array()) + pub fn finalize(&self) -> T::Scalar { + T::Scalar::from_bytes_wide(self.state.finalize().as_array()) } } diff --git a/src/lib.rs b/src/lib.rs index 88e5405..3cb4f1f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,13 +17,14 @@ mod error; pub mod frost; mod hash; mod messages; +pub mod sapling; mod scalar_mul; pub(crate) mod signature; mod signing_key; mod verification_key; -/// An element of the JubJub scalar field used for randomization of public and secret keys. -pub type Randomizer = jubjub::Scalar; +/// An element of the protocol's scalar field used for randomization of public and secret keys. +pub type Randomizer = >::Scalar; use hash::HStar; @@ -32,11 +33,11 @@ pub use signature::Signature; pub use signing_key::SigningKey; pub use verification_key::{VerificationKey, VerificationKeyBytes}; -/// Abstracts over different RedJubJub parameter choices, [`Binding`] +/// Abstracts over different RedDSA parameter choices, [`Binding`] /// and [`SpendAuth`]. /// /// As described [at the end of ยง5.4.6][concretereddsa] of the Zcash -/// protocol specification, the generator used in RedJubjub is left as +/// protocol specification, the generator used in RedDSA is left as /// an unspecified parameter, chosen differently for each of /// `BindingSig` and `SpendAuthSig`. /// @@ -44,31 +45,57 @@ pub use verification_key::{VerificationKey, VerificationKeyBytes}; /// parameter. /// /// [concretereddsa]: https://zips.z.cash/protocol/protocol.pdf#concretereddsa -pub trait SigType: private::Sealed {} +pub trait SigType: private::Sealed {} -/// A type variable corresponding to Zcash's `BindingSig`. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum Binding {} -impl SigType for Binding {} +/// A trait corresponding to `BindingSig` in Zcash protocols. +pub trait Binding: SigType {} -/// A type variable corresponding to Zcash's `SpendAuthSig`. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum SpendAuth {} -impl SigType for SpendAuth {} +/// A trait corresponding to `SpendAuthSig` in Zcash protocols. +pub trait SpendAuth: SigType {} pub(crate) mod private { use super::*; - pub trait Sealed: Copy + Clone + Eq + PartialEq + std::fmt::Debug { - fn basepoint() -> jubjub::ExtendedPoint; + + pub trait SealedScalar { + fn from_bytes_wide(bytes: &[u8; 64]) -> Self; + fn from_raw(val: [u64; 4]) -> Self; } - impl Sealed for Binding { + + impl SealedScalar for jubjub::Scalar { + fn from_bytes_wide(bytes: &[u8; 64]) -> Self { + jubjub::Scalar::from_bytes_wide(bytes) + } + fn from_raw(val: [u64; 4]) -> Self { + jubjub::Scalar::from_raw(val) + } + } + + pub trait Sealed: + Copy + Clone + Default + Eq + PartialEq + std::fmt::Debug + { + const H_STAR_PERSONALIZATION: &'static [u8; 16]; + type Scalar: group::ff::PrimeField + SealedScalar; + type Point: group::cofactor::CofactorCurve + + scalar_mul::VartimeMultiscalarMul; + + fn basepoint() -> T::Point; + } + impl Sealed for sapling::Binding { + const H_STAR_PERSONALIZATION: &'static [u8; 16] = b"Zcash_RedJubjubH"; + type Point = jubjub::ExtendedPoint; + type Scalar = jubjub::Scalar; + fn basepoint() -> jubjub::ExtendedPoint { jubjub::AffinePoint::from_bytes(constants::BINDINGSIG_BASEPOINT_BYTES) .unwrap() .into() } } - impl Sealed for SpendAuth { + impl Sealed for sapling::SpendAuth { + const H_STAR_PERSONALIZATION: &'static [u8; 16] = b"Zcash_RedJubjubH"; + type Point = jubjub::ExtendedPoint; + type Scalar = jubjub::Scalar; + fn basepoint() -> jubjub::ExtendedPoint { jubjub::AffinePoint::from_bytes(constants::SPENDAUTHSIG_BASEPOINT_BYTES) .unwrap() diff --git a/src/messages.rs b/src/messages.rs index 1b335e6..170d924 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -3,9 +3,10 @@ //! [RFC-001]: https://github.com/ZcashFoundation/redjubjub/blob/main/rfcs/0001-messages.md use crate::{frost, signature, verification_key, SpendAuth}; +use group::GroupEncoding; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; +use std::{collections::BTreeMap, convert::TryInto}; #[cfg(test)] use proptest_derive::Arbitrary; @@ -34,9 +35,12 @@ pub struct Secret([u8; 32]); #[cfg_attr(test, derive(Arbitrary))] pub struct Commitment([u8; 32]); -impl From for Commitment { - fn from(value: frost::Commitment) -> Commitment { - Commitment(jubjub::AffinePoint::from(value.0).to_bytes()) +impl From> for Commitment { + fn from(value: frost::Commitment) -> Commitment { + // TODO(str4d): We need to either enforce somewhere that these messages are only + // used with curves that have 32-byte encodings, or make the curve a parameter of + // the encoding. This will be easier once const_evaluatable_checked stabilises. + Commitment(value.0.to_bytes().as_ref().try_into().unwrap()) } } @@ -56,14 +60,14 @@ pub struct GroupCommitment([u8; 32]); #[cfg_attr(test, derive(Arbitrary))] pub struct SignatureResponse([u8; 32]); -impl From> for SignatureResponse { - fn from(value: signature::Signature) -> SignatureResponse { +impl From> for SignatureResponse { + fn from(value: signature::Signature) -> SignatureResponse { SignatureResponse(value.s_bytes) } } -impl From> for GroupCommitment { - fn from(value: signature::Signature) -> GroupCommitment { +impl From> for GroupCommitment { + fn from(value: signature::Signature) -> GroupCommitment { GroupCommitment(value.r_bytes) } } @@ -76,8 +80,8 @@ impl From> for GroupCommitment { #[cfg_attr(test, derive(Arbitrary))] pub struct VerificationKey([u8; 32]); -impl From> for VerificationKey { - fn from(value: verification_key::VerificationKey) -> VerificationKey { +impl From> for VerificationKey { + fn from(value: verification_key::VerificationKey) -> VerificationKey { VerificationKey(<[u8; 32]>::from(value)) } } @@ -220,19 +224,22 @@ pub struct SigningPackage { message: Vec, } -impl From for frost::SigningPackage { - fn from(value: SigningPackage) -> frost::SigningPackage { +impl From for frost::SigningPackage { + fn from(value: SigningPackage) -> frost::SigningPackage { let mut signing_commitments = Vec::new(); for (participant_id, commitment) in &value.signing_commitments { + // TODO(str4d): This will be so much nicer once const_evaluatable_checked + // stabilises, and `GroupEncoding::from_bytes` can take the array directly. + let mut hiding_repr = ::Repr::default(); + let mut binding_repr = ::Repr::default(); + hiding_repr.as_mut().copy_from_slice(&commitment.hiding.0); + binding_repr.as_mut().copy_from_slice(&commitment.binding.0); + let s = frost::SigningCommitments { index: u64::from(*participant_id), // TODO: The `from_bytes()` response is a `CtOption` so we have to `unwrap()` - hiding: jubjub::ExtendedPoint::from( - jubjub::AffinePoint::from_bytes(commitment.hiding.0).unwrap(), - ), - binding: jubjub::ExtendedPoint::from( - jubjub::AffinePoint::from_bytes(commitment.binding.0).unwrap(), - ), + hiding: S::Point::from_bytes(&hiding_repr).unwrap(), + binding: S::Point::from_bytes(&binding_repr).unwrap(), }; signing_commitments.push(s); } diff --git a/src/messages/tests/integration.rs b/src/messages/tests/integration.rs index e571669..50e6cd5 100644 --- a/src/messages/tests/integration.rs +++ b/src/messages/tests/integration.rs @@ -4,7 +4,7 @@ use crate::{ validate::{MsgErr, Validate}, *, }, - verification_key, + sapling, verification_key, }; use rand::thread_rng; use serde_json; @@ -53,8 +53,12 @@ fn validate_sender_receiver() { #[test] fn validate_sharepackage() { let setup = basic_setup(); - let (mut shares, _pubkeys) = - frost::keygen_with_dealer(setup.num_signers, setup.threshold, setup.rng.clone()).unwrap(); + let (mut shares, _pubkeys) = frost::keygen_with_dealer::<_, sapling::SpendAuth>( + setup.num_signers, + setup.threshold, + setup.rng.clone(), + ) + .unwrap(); let header = create_valid_header(setup.signer1, setup.signer2); @@ -130,8 +134,12 @@ fn validate_sharepackage() { fn serialize_sharepackage() { let setup = basic_setup(); - let (mut shares, _pubkeys) = - frost::keygen_with_dealer(setup.num_signers, setup.threshold, setup.rng.clone()).unwrap(); + let (mut shares, _pubkeys) = frost::keygen_with_dealer::<_, sapling::SpendAuth>( + setup.num_signers, + setup.threshold, + setup.rng.clone(), + ) + .unwrap(); let header = create_valid_header(setup.dealer, setup.signer1); @@ -196,7 +204,8 @@ fn serialize_sharepackage() { fn validate_signingcommitments() { let mut setup = basic_setup(); - let (_nonce, commitment) = frost::preprocess(1, u64::from(setup.signer1), &mut setup.rng); + let (_nonce, commitment) = + frost::preprocess::<_, sapling::SpendAuth>(1, u64::from(setup.signer1), &mut setup.rng); let header = create_valid_header(setup.aggregator, setup.signer2); @@ -236,7 +245,8 @@ fn validate_signingcommitments() { fn serialize_signingcommitments() { let mut setup = basic_setup(); - let (_nonce, commitment) = frost::preprocess(1, u64::from(setup.signer1), &mut setup.rng); + let (_nonce, commitment) = + frost::preprocess::<_, sapling::SpendAuth>(1, u64::from(setup.signer1), &mut setup.rng); let header = create_valid_header(setup.aggregator, setup.signer1); @@ -724,16 +734,16 @@ fn basic_setup() -> Setup { } } -fn full_setup() -> (Setup, signature::Signature) { +fn full_setup() -> (Setup, signature::Signature) { let mut setup = basic_setup(); // aggregator creates the shares and pubkeys for this round let (shares, pubkeys) = frost::keygen_with_dealer(setup.num_signers, setup.threshold, setup.rng.clone()).unwrap(); - let mut nonces: std::collections::HashMap> = + let mut nonces: std::collections::HashMap>> = std::collections::HashMap::with_capacity(setup.threshold as usize); - let mut commitments: Vec = + let mut commitments: Vec> = Vec::with_capacity(setup.threshold as usize); // aggregator generates nonces and signing commitments for each participant. @@ -744,7 +754,7 @@ fn full_setup() -> (Setup, signature::Signature) { } // aggregator generates a signing package - let mut signature_shares: Vec = + let mut signature_shares: Vec> = Vec::with_capacity(setup.threshold as usize); let message = "message to sign".as_bytes().to_vec(); let signing_package = frost::SigningPackage { @@ -770,7 +780,7 @@ fn full_setup() -> (Setup, signature::Signature) { } fn generate_share_commitment( - shares: &Vec, + shares: &Vec>, participants: Vec, ) -> BTreeMap { assert_eq!(shares.len(), participants.len()); @@ -787,7 +797,7 @@ fn generate_share_commitment( } fn create_signing_commitments( - commitments: Vec, + commitments: Vec>, participants: Vec, ) -> BTreeMap { assert_eq!(commitments.len(), participants.len()); diff --git a/src/sapling.rs b/src/sapling.rs new file mode 100644 index 0000000..84b89ea --- /dev/null +++ b/src/sapling.rs @@ -0,0 +1,27 @@ +//! Signature types for the Sapling protocol. + +use super::SigType; + +/// A type variable corresponding to Zcash's Sapling `SpendAuthSig`. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum SpendAuth {} +// This should not exist, but is necessary to use zeroize::DefaultIsZeroes. +impl Default for SpendAuth { + fn default() -> Self { + unimplemented!() + } +} +impl SigType for SpendAuth {} +impl super::SpendAuth for SpendAuth {} + +/// A type variable corresponding to Zcash's Sapling `BindingSig`. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum Binding {} +// This should not exist, but is necessary to use zeroize::DefaultIsZeroes. +impl Default for Binding { + fn default() -> Self { + unimplemented!() + } +} +impl SigType for Binding {} +impl super::Binding for Binding {} diff --git a/src/scalar_mul.rs b/src/scalar_mul.rs index 89b808c..a62248c 100644 --- a/src/scalar_mul.rs +++ b/src/scalar_mul.rs @@ -12,7 +12,7 @@ use std::{borrow::Borrow, fmt::Debug}; -use jubjub::*; +use jubjub::{ExtendedNielsPoint, ExtendedPoint}; pub trait NonAdjacentForm { fn non_adjacent_form(&self, w: usize) -> [i8; 256]; @@ -20,7 +20,9 @@ pub trait NonAdjacentForm { /// A trait for variable-time multiscalar multiplication without precomputation. pub trait VartimeMultiscalarMul { - /// The type of point being multiplied, e.g., `AffinePoint`. + /// The type of scalar being multiplied, e.g., `jubjub::Scalar`. + type Scalar; + /// The type of point being multiplied, e.g., `jubjub::AffinePoint`. type Point; /// Given an iterator of public scalars and an iterator of @@ -32,7 +34,7 @@ pub trait VartimeMultiscalarMul { fn optional_multiscalar_mul(scalars: I, points: J) -> Option where I: IntoIterator, - I::Item: Borrow, + I::Item: Borrow, J: IntoIterator>; /// Given an iterator of public scalars and an iterator of @@ -46,7 +48,7 @@ pub trait VartimeMultiscalarMul { fn vartime_multiscalar_mul(scalars: I, points: J) -> Self::Point where I: IntoIterator, - I::Item: Borrow, + I::Item: Borrow, J: IntoIterator, J::Item: Borrow, Self::Point: Clone, @@ -59,7 +61,7 @@ pub trait VartimeMultiscalarMul { } } -impl NonAdjacentForm for Scalar { +impl NonAdjacentForm for jubjub::Scalar { /// Compute a width-\\(w\\) "Non-Adjacent Form" of this scalar. /// /// Thanks to curve25519-dalek @@ -155,13 +157,14 @@ impl<'a> From<&'a ExtendedPoint> for LookupTable5 { } impl VartimeMultiscalarMul for ExtendedPoint { + type Scalar = jubjub::Scalar; type Point = ExtendedPoint; #[allow(non_snake_case)] fn optional_multiscalar_mul(scalars: I, points: J) -> Option where I: IntoIterator, - I::Item: Borrow, + I::Item: Borrow, J: IntoIterator>, { let nafs: Vec<_> = scalars diff --git a/src/signature.rs b/src/signature.rs index 4e42db1..93ac7b0 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -7,12 +7,12 @@ // Authors: // - Henry de Valence -//! Redjubjub Signatures +//! RedDSA Signatures use std::marker::PhantomData; use crate::SigType; -/// A RedJubJub signature. +/// A RedDSA signature. #[derive(Copy, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Signature { diff --git a/src/signing_key.rs b/src/signing_key.rs index 76a2444..2fe88ab 100644 --- a/src/signing_key.rs +++ b/src/signing_key.rs @@ -13,19 +13,21 @@ use std::{ marker::PhantomData, }; -use crate::{Error, Randomizer, SigType, Signature, SpendAuth, VerificationKey}; +use crate::{ + private::SealedScalar, Error, Randomizer, SigType, Signature, SpendAuth, VerificationKey, +}; -use jubjub::Scalar; +use group::{ff::PrimeField, GroupEncoding}; use rand_core::{CryptoRng, RngCore}; -/// A RedJubJub signing key. +/// A RedDSA signing key. #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(try_from = "SerdeHelper"))] #[cfg_attr(feature = "serde", serde(into = "SerdeHelper"))] #[cfg_attr(feature = "serde", serde(bound = "T: SigType"))] pub struct SigningKey { - sk: Scalar, + sk: T::Scalar, pk: VerificationKey, } @@ -37,7 +39,7 @@ impl<'a, T: SigType> From<&'a SigningKey> for VerificationKey { impl From> for [u8; 32] { fn from(sk: SigningKey) -> [u8; 32] { - sk.sk.to_bytes() + sk.sk.to_repr().as_ref().try_into().unwrap() } } @@ -46,7 +48,9 @@ impl TryFrom<[u8; 32]> for SigningKey { fn try_from(bytes: [u8; 32]) -> Result { // XXX-jubjub: this should not use CtOption - let maybe_sk = Scalar::from_bytes(&bytes); + let mut repr = ::Repr::default(); + repr.as_mut().copy_from_slice(&bytes); + let maybe_sk = T::Scalar::from_repr(repr); if maybe_sk.is_some().into() { let sk = maybe_sk.unwrap(); let pk = VerificationKey::from(&sk); @@ -74,10 +78,10 @@ impl From> for SerdeHelper { } } -impl SigningKey { +impl SigningKey { /// Randomize this public key with the given `randomizer`. - pub fn randomize(&self, randomizer: &Randomizer) -> SigningKey { - let sk = &self.sk + randomizer; + pub fn randomize(&self, randomizer: &Randomizer) -> SigningKey { + let sk = self.sk + randomizer; let pk = VerificationKey::from(&sk); SigningKey { sk, pk } } @@ -89,7 +93,7 @@ impl SigningKey { let sk = { let mut bytes = [0; 64]; rng.fill_bytes(&mut bytes); - Scalar::from_bytes_wide(&bytes) + T::Scalar::from_bytes_wide(&bytes) }; let pk = VerificationKey::from(&sk); SigningKey { sk, pk } @@ -101,28 +105,31 @@ impl SigningKey { use crate::HStar; // Choose a byte sequence uniformly at random of length - // (\ell_H + 128)/8 bytes. For RedJubjub this is (512 + 128)/8 = 80. + // (\ell_H + 128)/8 bytes. For RedJubjub and RedPallas this is + // (512 + 128)/8 = 80. let random_bytes = { let mut bytes = [0; 80]; rng.fill_bytes(&mut bytes); bytes }; - let nonce = HStar::default() + let nonce = HStar::::default() .update(&random_bytes[..]) .update(&self.pk.bytes.bytes[..]) // XXX ugly .update(msg) .finalize(); - let r_bytes = jubjub::AffinePoint::from(&T::basepoint() * &nonce).to_bytes(); + let r: T::Point = T::basepoint() * nonce; + let r_bytes: [u8; 32] = r.to_bytes().as_ref().try_into().unwrap(); - let c = HStar::default() + let c = HStar::::default() .update(&r_bytes[..]) .update(&self.pk.bytes.bytes[..]) // XXX ugly .update(msg) .finalize(); - let s_bytes = (&nonce + &(&c * &self.sk)).to_bytes(); + let s = nonce + (c * self.sk); + let s_bytes = s.to_repr().as_ref().try_into().unwrap(); Signature { r_bytes, diff --git a/src/verification_key.rs b/src/verification_key.rs index 09c190b..02966d9 100644 --- a/src/verification_key.rs +++ b/src/verification_key.rs @@ -9,17 +9,17 @@ // - Henry de Valence use std::{ - convert::TryFrom, + convert::{TryFrom, TryInto}, hash::{Hash, Hasher}, marker::PhantomData, }; -use jubjub::Scalar; +use group::{cofactor::CofactorGroup, ff::PrimeField, GroupEncoding}; use crate::{Error, Randomizer, SigType, Signature, SpendAuth}; /// A refinement type for `[u8; 32]` indicating that the bytes represent -/// an encoding of a RedJubJub verification key. +/// an encoding of a RedDSA verification key. /// /// This is useful for representing a compressed verification key; the /// [`VerificationKey`] type in this library holds other decompressed state @@ -53,7 +53,7 @@ impl Hash for VerificationKeyBytes { } } -/// A valid RedJubJub verification key. +/// A valid RedDSA verification key. /// /// This type holds decompressed state used in signature verification; if the /// verification key may not be used immediately, it is probably better to use @@ -72,8 +72,7 @@ impl Hash for VerificationKeyBytes { #[cfg_attr(feature = "serde", serde(into = "VerificationKeyBytes"))] #[cfg_attr(feature = "serde", serde(bound = "T: SigType"))] pub struct VerificationKey { - // XXX-jubjub: this should just be Point - pub(crate) point: jubjub::ExtendedPoint, + pub(crate) point: T::Point, pub(crate) bytes: VerificationKeyBytes, } @@ -96,9 +95,11 @@ impl TryFrom> for VerificationKey { // XXX-jubjub: this should not use CtOption // XXX-jubjub: this takes ownership of bytes, while Fr doesn't. // This checks that the encoding is canonical... - let maybe_point = jubjub::AffinePoint::from_bytes(bytes.bytes); + let mut repr = ::Repr::default(); + repr.as_mut().copy_from_slice(&bytes.bytes); + let maybe_point = T::Point::from_bytes(&repr); if maybe_point.is_some().into() { - let point: jubjub::ExtendedPoint = maybe_point.unwrap().into(); + let point = maybe_point.unwrap(); // Note that small-order verification keys (including the identity) are not // rejected here. Previously they were rejected, but this was a bug as the // RedDSA specification allows them. Zcash Sapling rejects small-order points @@ -116,20 +117,18 @@ impl TryFrom<[u8; 32]> for VerificationKey { type Error = Error; fn try_from(bytes: [u8; 32]) -> Result { - use std::convert::TryInto; VerificationKeyBytes::from(bytes).try_into() } } -impl VerificationKey { +impl VerificationKey { /// Randomize this verification key with the given `randomizer`. /// /// Randomization is only supported for `SpendAuth` keys. - pub fn randomize(&self, randomizer: &Randomizer) -> VerificationKey { - use crate::private::Sealed; - let point = &self.point + &(&SpendAuth::basepoint() * randomizer); + pub fn randomize(&self, randomizer: &Randomizer) -> VerificationKey { + let point = self.point + (T::basepoint() * randomizer); let bytes = VerificationKeyBytes { - bytes: jubjub::AffinePoint::from(&point).to_bytes(), + bytes: point.to_bytes().as_ref().try_into().unwrap(), _marker: PhantomData, }; VerificationKey { bytes, point } @@ -137,10 +136,10 @@ impl VerificationKey { } impl VerificationKey { - pub(crate) fn from(s: &Scalar) -> VerificationKey { - let point = &T::basepoint() * s; + pub(crate) fn from(s: &T::Scalar) -> VerificationKey { + let point = T::basepoint() * s; let bytes = VerificationKeyBytes { - bytes: jubjub::AffinePoint::from(&point).to_bytes(), + bytes: point.to_bytes().as_ref().try_into().unwrap(), _marker: PhantomData, }; VerificationKey { bytes, point } @@ -150,7 +149,7 @@ impl VerificationKey { // This is similar to impl signature::Verifier but without boxed errors pub fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), Error> { use crate::HStar; - let c = HStar::default() + let c = HStar::::default() .update(&signature.r_bytes[..]) .update(&self.bytes.bytes[..]) // XXX ugly .update(msg) @@ -163,14 +162,16 @@ impl VerificationKey { pub(crate) fn verify_prehashed( &self, signature: &Signature, - c: Scalar, + c: T::Scalar, ) -> Result<(), Error> { let r = { // XXX-jubjub: should not use CtOption here // XXX-jubjub: inconsistent ownership in from_bytes - let maybe_point = jubjub::AffinePoint::from_bytes(signature.r_bytes); + let mut repr = ::Repr::default(); + repr.as_mut().copy_from_slice(&signature.r_bytes); + let maybe_point = T::Point::from_bytes(&repr); if maybe_point.is_some().into() { - jubjub::ExtendedPoint::from(maybe_point.unwrap()) + maybe_point.unwrap() } else { return Err(Error::InvalidSignature); } @@ -178,7 +179,9 @@ impl VerificationKey { let s = { // XXX-jubjub: should not use CtOption here - let maybe_scalar = Scalar::from_bytes(&signature.s_bytes); + let mut repr = ::Repr::default(); + repr.as_mut().copy_from_slice(&signature.s_bytes); + let maybe_scalar = T::Scalar::from_repr(repr); if maybe_scalar.is_some().into() { maybe_scalar.unwrap() } else { @@ -189,8 +192,8 @@ impl VerificationKey { // XXX rewrite as normal double scalar mul // Verify check is h * ( - s * B + R + c * A) == 0 // h * ( s * B - c * A - R) == 0 - let sB = &T::basepoint() * &s; - let cA = &self.point * &c; + let sB = T::basepoint() * s; + let cA = self.point * c; let check = sB - cA - r; if check.is_small_order().into() { diff --git a/tests/batch.rs b/tests/batch.rs index 27cc6e7..b7d62bc 100644 --- a/tests/batch.rs +++ b/tests/batch.rs @@ -5,13 +5,13 @@ use reddsa::*; #[test] fn spendauth_batch_verify() { let mut rng = thread_rng(); - let mut batch = batch::Verifier::new(); + let mut batch = batch::Verifier::<_, sapling::Binding>::new(); for _ in 0..32 { - let sk = SigningKey::::new(&mut rng); + let sk = SigningKey::::new(&mut rng); let vk = VerificationKey::from(&sk); let msg = b"BatchVerifyTest"; let sig = sk.sign(&mut rng, &msg[..]); - batch.queue((vk.into(), sig, msg)); + batch.queue(batch::Item::from_spendauth(vk.into(), sig, msg)); } assert!(batch.verify(rng).is_ok()); } @@ -19,13 +19,13 @@ fn spendauth_batch_verify() { #[test] fn binding_batch_verify() { let mut rng = thread_rng(); - let mut batch = batch::Verifier::new(); + let mut batch = batch::Verifier::::new(); for _ in 0..32 { - let sk = SigningKey::::new(&mut rng); + let sk = SigningKey::::new(&mut rng); let vk = VerificationKey::from(&sk); let msg = b"BatchVerifyTest"; let sig = sk.sign(&mut rng, &msg[..]); - batch.queue((vk.into(), sig, msg)); + batch.queue(batch::Item::from_binding(vk.into(), sig, msg)); } assert!(batch.verify(rng).is_ok()); } @@ -35,20 +35,20 @@ fn alternating_batch_verify() { let mut rng = thread_rng(); let mut batch = batch::Verifier::new(); for i in 0..32 { - let item: batch::Item = match i % 2 { + let item = match i % 2 { 0 => { - let sk = SigningKey::::new(&mut rng); + let sk = SigningKey::::new(&mut rng); let vk = VerificationKey::from(&sk); let msg = b"BatchVerifyTest"; let sig = sk.sign(&mut rng, &msg[..]); - (vk.into(), sig, msg).into() + batch::Item::from_spendauth(vk.into(), sig, msg) } 1 => { - let sk = SigningKey::::new(&mut rng); + let sk = SigningKey::::new(&mut rng); let vk = VerificationKey::from(&sk); let msg = b"BatchVerifyTest"; let sig = sk.sign(&mut rng, &msg[..]); - (vk.into(), sig, msg).into() + batch::Item::from_binding(vk.into(), sig, msg) } _ => unreachable!(), }; @@ -64,9 +64,9 @@ fn bad_batch_verify() { let mut batch = batch::Verifier::new(); let mut items = Vec::new(); for i in 0..32 { - let item: batch::Item = match i % 2 { + let item = match i % 2 { 0 => { - let sk = SigningKey::::new(&mut rng); + let sk = SigningKey::::new(&mut rng); let vk = VerificationKey::from(&sk); let msg = b"BatchVerifyTest"; let sig = if i != bad_index { @@ -74,14 +74,14 @@ fn bad_batch_verify() { } else { sk.sign(&mut rng, b"bad") }; - (vk.into(), sig, msg).into() + batch::Item::from_spendauth(vk.into(), sig, msg) } 1 => { - let sk = SigningKey::::new(&mut rng); + let sk = SigningKey::::new(&mut rng); let vk = VerificationKey::from(&sk); let msg = b"BatchVerifyTest"; let sig = sk.sign(&mut rng, &msg[..]); - (vk.into(), sig, msg).into() + batch::Item::from_binding(vk.into(), sig, msg) } _ => unreachable!(), }; diff --git a/tests/bincode.rs b/tests/bincode.rs index e0816a3..e482130 100644 --- a/tests/bincode.rs +++ b/tests/bincode.rs @@ -9,8 +9,8 @@ proptest! { fn secretkey_serialization( bytes in prop::array::uniform32(any::()), ) { - let sk_result_from = SigningKey::::try_from(bytes); - let sk_result_bincode: Result, _> + let sk_result_from = SigningKey::::try_from(bytes); + let sk_result_bincode: Result, _> = bincode::deserialize(&bytes[..]); // Check 1: both decoding methods should agree @@ -39,8 +39,8 @@ proptest! { fn publickeybytes_serialization( bytes in prop::array::uniform32(any::()), ) { - let pk_bytes_from = VerificationKeyBytes::::from(bytes); - let pk_bytes_bincode: VerificationKeyBytes:: + let pk_bytes_from = VerificationKeyBytes::::from(bytes); + let pk_bytes_bincode: VerificationKeyBytes:: = bincode::deserialize(&bytes[..]).unwrap(); // Check 1: both decoding methods should have the same result. @@ -59,8 +59,8 @@ proptest! { fn publickey_serialization( bytes in prop::array::uniform32(any::()), ) { - let pk_result_try_from = VerificationKey::::try_from(bytes); - let pk_result_bincode: Result, _> + let pk_result_try_from = VerificationKey::::try_from(bytes); + let pk_result_bincode: Result, _> = bincode::deserialize(&bytes[..]); // Check 1: both decoding methods should have the same result @@ -93,8 +93,8 @@ proptest! { bytes }; - let sig_bytes_from = Signature::::from(bytes); - let sig_bytes_bincode: Signature:: + let sig_bytes_from = Signature::::from(bytes); + let sig_bytes_bincode: Signature:: = bincode::deserialize(&bytes[..]).unwrap(); // Check 1: both decoding methods should have the same result. diff --git a/tests/frost.rs b/tests/frost.rs index 9d6a943..e4be0a4 100644 --- a/tests/frost.rs +++ b/tests/frost.rs @@ -1,7 +1,7 @@ use rand::thread_rng; use std::collections::HashMap; -use reddsa::frost; +use reddsa::{frost, sapling}; #[test] fn check_sign_with_dealer() { @@ -10,9 +10,10 @@ fn check_sign_with_dealer() { let threshold = 3; let (shares, pubkeys) = frost::keygen_with_dealer(numsigners, threshold, &mut rng).unwrap(); - let mut nonces: HashMap> = + let mut nonces: HashMap>> = HashMap::with_capacity(threshold as usize); - let mut commitments: Vec = Vec::with_capacity(threshold as usize); + let mut commitments: Vec> = + Vec::with_capacity(threshold as usize); // Round 1, generating nonces and signing commitments for each participant. for participant_index in 1..(threshold + 1) { @@ -26,7 +27,8 @@ fn check_sign_with_dealer() { // This is what the signature aggregator / coordinator needs to do: // - decide what message to sign // - take one (unused) commitment per signing participant - let mut signature_shares: Vec = Vec::with_capacity(threshold as usize); + let mut signature_shares: Vec> = + Vec::with_capacity(threshold as usize); let message = "message to sign".as_bytes(); let signing_package = frost::SigningPackage { message: message.to_vec(), diff --git a/tests/librustzcash_vectors.rs b/tests/librustzcash_vectors.rs index c516327..6dc2d04 100644 --- a/tests/librustzcash_vectors.rs +++ b/tests/librustzcash_vectors.rs @@ -26,8 +26,8 @@ fn verify_librustzcash_binding() { lazy_static! { static ref LIBRUSTZCASH_SPENDAUTH_SIGS: [( Vec, - Signature, - VerificationKeyBytes + Signature, + VerificationKeyBytes ); 32] = [ ( [ @@ -638,7 +638,11 @@ lazy_static! { .into(), ), ]; - static ref LIBRUSTZCASH_BINDING_SIGS: [(Vec, Signature, VerificationKeyBytes); 32] = [ + static ref LIBRUSTZCASH_BINDING_SIGS: [( + Vec, + Signature, + VerificationKeyBytes + ); 32] = [ ( [ 16, 28, 190, 75, 156, 66, 96, 79, 4, 199, 3, 195, 150, 247, 136, 198, 203, 45, 109, diff --git a/tests/proptests.rs b/tests/proptests.rs index 7b22c36..9ffe0ee 100644 --- a/tests/proptests.rs +++ b/tests/proptests.rs @@ -64,7 +64,7 @@ impl SignatureCase { VerificationKeyBytes::::from(bytes) }; - // Check that the verification key is a valid RedJubjub verification key. + // Check that the verification key is a valid RedDSA verification key. let pub_key = VerificationKey::try_from(pk_bytes) .expect("The test verification key to be well-formed."); @@ -114,8 +114,8 @@ proptest! { // Create a test case for each signature type. let msg = b"test message for proptests"; - let mut binding = SignatureCase::::new(&mut rng, msg.to_vec()); - let mut spendauth = SignatureCase::::new(&mut rng, msg.to_vec()); + let mut binding = SignatureCase::::new(&mut rng, msg.to_vec()); + let mut spendauth = SignatureCase::::new(&mut rng, msg.to_vec()); // Apply tweaks to each case. for t in &tweaks { @@ -136,10 +136,10 @@ proptest! { // XXX-jubjub: better API for this let mut bytes = [0; 64]; rng.fill_bytes(&mut bytes[..]); - Randomizer::from_bytes_wide(&bytes) + jubjub::Scalar::from_bytes_wide(&bytes) }; - let sk = SigningKey::::new(&mut rng); + let sk = SigningKey::::new(&mut rng); let pk = VerificationKey::from(&sk); let sk_r = sk.randomize(&r); diff --git a/tests/smallorder.rs b/tests/smallorder.rs index f5db77e..7efbdf7 100644 --- a/tests/smallorder.rs +++ b/tests/smallorder.rs @@ -9,8 +9,8 @@ fn identity_publickey_passes() { let identity = AffinePoint::identity(); assert_eq!(::from(identity.is_small_order()), true); let bytes = identity.to_bytes(); - let pk_bytes = VerificationKeyBytes::::from(bytes); - assert!(VerificationKey::::try_from(pk_bytes).is_ok()); + let pk_bytes = VerificationKeyBytes::::from(bytes); + assert!(VerificationKey::::try_from(pk_bytes).is_ok()); } #[test] @@ -19,6 +19,6 @@ fn smallorder_publickey_passes() { let order4 = AffinePoint::from_raw_unchecked(Fq::one(), Fq::zero()); assert_eq!(::from(order4.is_small_order()), true); let bytes = order4.to_bytes(); - let pk_bytes = VerificationKeyBytes::::from(bytes); - assert!(VerificationKey::::try_from(pk_bytes).is_ok()); + let pk_bytes = VerificationKeyBytes::::from(bytes); + assert!(VerificationKey::::try_from(pk_bytes).is_ok()); }