From 26c86cc088fbf1060f95cc79c932d87c627c3cce Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Wed, 1 Feb 2023 20:27:28 -0300 Subject: [PATCH] use `reddsa` crate and remove duplicated RedPallas code (#6013) * use `reddsa` crate and remove duplicated RedPallas code * update old references to 'redpallas' crate * Use reddsa 0.4.0 * update Cargo.lock --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Deirdre Connolly --- Cargo.lock | 21 +- zebra-chain/Cargo.toml | 1 + zebra-chain/benches/redpallas.rs | 19 +- zebra-chain/src/orchard/action.rs | 12 +- zebra-chain/src/orchard/arbitrary.rs | 43 ++- zebra-chain/src/orchard/shielded_data.rs | 8 +- zebra-chain/src/orchard/tests/preallocate.rs | 3 +- zebra-chain/src/primitives.rs | 4 +- zebra-chain/src/primitives/redpallas.rs | 81 ----- zebra-chain/src/primitives/redpallas/batch.rs | 305 ------------------ .../src/primitives/redpallas/constants.rs | 24 -- zebra-chain/src/primitives/redpallas/error.rs | 11 - zebra-chain/src/primitives/redpallas/hash.rs | 40 --- .../src/primitives/redpallas/scalar_mul.rs | 212 ------------ .../src/primitives/redpallas/signature.rs | 62 ---- .../src/primitives/redpallas/signing_key.rs | 126 -------- zebra-chain/src/primitives/redpallas/tests.rs | 3 - .../primitives/redpallas/tests/basepoints.rs | 22 -- .../src/primitives/redpallas/tests/batch.rs | 141 -------- .../src/primitives/redpallas/tests/prop.rs | 150 --------- .../primitives/redpallas/verification_key.rs | 182 ----------- zebra-chain/src/transaction/arbitrary.rs | 17 +- zebra-chain/src/transaction/serialize.rs | 19 +- zebra-consensus/src/error.rs | 2 +- zebra-consensus/src/primitives/redpallas.rs | 6 +- .../src/primitives/redpallas/tests.rs | 12 +- zebra-consensus/src/transaction.rs | 28 +- 27 files changed, 128 insertions(+), 1426 deletions(-) delete mode 100644 zebra-chain/src/primitives/redpallas.rs delete mode 100644 zebra-chain/src/primitives/redpallas/batch.rs delete mode 100644 zebra-chain/src/primitives/redpallas/constants.rs delete mode 100644 zebra-chain/src/primitives/redpallas/error.rs delete mode 100644 zebra-chain/src/primitives/redpallas/hash.rs delete mode 100644 zebra-chain/src/primitives/redpallas/scalar_mul.rs delete mode 100644 zebra-chain/src/primitives/redpallas/signature.rs delete mode 100644 zebra-chain/src/primitives/redpallas/signing_key.rs delete mode 100644 zebra-chain/src/primitives/redpallas/tests.rs delete mode 100644 zebra-chain/src/primitives/redpallas/tests/basepoints.rs delete mode 100644 zebra-chain/src/primitives/redpallas/tests/batch.rs delete mode 100644 zebra-chain/src/primitives/redpallas/tests/prop.rs delete mode 100644 zebra-chain/src/primitives/redpallas/verification_key.rs diff --git a/Cargo.lock b/Cargo.lock index c0e373c1..b7f0ddb9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2654,7 +2654,7 @@ dependencies = [ "nonempty", "pasta_curves", "rand 0.8.5", - "reddsa", + "reddsa 0.3.0", "serde", "subtle", "tracing", @@ -3374,6 +3374,24 @@ dependencies = [ "zeroize", ] +[[package]] +name = "reddsa" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56c56c88c633d5ccc03ec3931a0d3991c2ae8d92c049ece060d41f6662a818c" +dependencies = [ + "blake2b_simd", + "byteorder", + "group", + "hex", + "jubjub", + "pasta_curves", + "rand_core 0.6.4", + "serde", + "thiserror", + "zeroize", +] + [[package]] name = "redjubjub" version = "0.5.0" @@ -5453,6 +5471,7 @@ dependencies = [ "rand_chacha 0.3.1", "rand_core 0.6.4", "rayon", + "reddsa 0.4.0", "redjubjub", "ripemd", "secp256k1", diff --git a/zebra-chain/Cargo.toml b/zebra-chain/Cargo.toml index 2814f73c..cc6472d5 100644 --- a/zebra-chain/Cargo.toml +++ b/zebra-chain/Cargo.toml @@ -87,6 +87,7 @@ rayon = "1.6.1" # ZF deps ed25519-zebra = "3.1.0" redjubjub = "0.5.0" +reddsa = "0.4.0" # Optional testing dependencies proptest = { version = "0.10.1", optional = true } diff --git a/zebra-chain/benches/redpallas.rs b/zebra-chain/benches/redpallas.rs index 49fbf522..15467658 100644 --- a/zebra-chain/benches/redpallas.rs +++ b/zebra-chain/benches/redpallas.rs @@ -5,8 +5,11 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; use rand::{thread_rng, Rng}; - -use zebra_chain::primitives::redpallas::*; +use reddsa::{ + batch, + orchard::{Binding, SpendAuth}, + Signature, SigningKey, VerificationKey, VerificationKeyBytes, +}; const MESSAGE_BYTES: &[u8; 0] = b""; @@ -95,10 +98,18 @@ fn bench_batch_verify(c: &mut Criterion) { for item in sigs.iter() { match item { Item::SpendAuth { vk_bytes, sig } => { - batch.queue((*vk_bytes, *sig, MESSAGE_BYTES)); + batch.queue(batch::Item::from_spendauth( + *vk_bytes, + *sig, + MESSAGE_BYTES, + )); } Item::Binding { vk_bytes, sig } => { - batch.queue((*vk_bytes, *sig, MESSAGE_BYTES)); + batch.queue(batch::Item::from_binding( + *vk_bytes, + *sig, + MESSAGE_BYTES, + )); } } } diff --git a/zebra-chain/src/orchard/action.rs b/zebra-chain/src/orchard/action.rs index 316ccfa1..f08b0464 100644 --- a/zebra-chain/src/orchard/action.rs +++ b/zebra-chain/src/orchard/action.rs @@ -1,12 +1,10 @@ use std::{convert::TryFrom, io}; use halo2::pasta::pallas; +use reddsa::orchard::SpendAuth; -use crate::{ - primitives::redpallas::{self, SpendAuth}, - serialization::{ - serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize, - }, +use crate::serialization::{ + serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize, }; use super::{ @@ -29,7 +27,7 @@ pub struct Action { /// The nullifier of the input note being spent. pub nullifier: note::Nullifier, /// The randomized validating key for spendAuthSig, - pub rk: redpallas::VerificationKeyBytes, + pub rk: reddsa::VerificationKeyBytes, /// The x-coordinate of the note commitment for the output note. #[serde(with = "serde_helpers::Base")] pub cm_x: pallas::Base, @@ -81,7 +79,7 @@ impl ZcashDeserialize for Action { // https://zips.z.cash/protocol/protocol.pdf#concretespendauthsig // https://zips.z.cash/protocol/protocol.pdf#concretereddsa // This only reads the 32-byte buffer. The type is enforced - // on signature verification; see [`redpallas::batch`] + // on signature verification; see [`reddsa::batch`] rk: reader.read_32_bytes()?.into(), // Type is `{0 .. ๐‘ž_โ„™ โˆ’ 1}`. Note that the second rule quoted above // is also enforced here and it is technically redundant with the first. diff --git a/zebra-chain/src/orchard/arbitrary.rs b/zebra-chain/src/orchard/arbitrary.rs index 28f1201f..c2152e0c 100644 --- a/zebra-chain/src/orchard/arbitrary.rs +++ b/zebra-chain/src/orchard/arbitrary.rs @@ -1,12 +1,10 @@ //! Randomised data generation for Orchard types. -use std::marker::PhantomData; - use group::{ff::PrimeField, prime::PrimeCurveAffine}; use halo2::{arithmetic::FieldExt, pasta::pallas}; use proptest::{arbitrary::any, array, collection::vec, prelude::*}; -use crate::primitives::redpallas::{Signature, SpendAuth, VerificationKey, VerificationKeyBytes}; +use reddsa::{orchard::SpendAuth, Signature, SigningKey, VerificationKey, VerificationKeyBytes}; use super::{ keys::*, note, tree, Action, AuthorizedAction, Flags, NoteCommitment, ValueCommitment, @@ -18,14 +16,14 @@ impl Arbitrary for Action { fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { ( any::(), - any::>(), + any::(), any::(), any::(), ) .prop_map(|(nullifier, rk, enc_ciphertext, out_ciphertext)| Self { cv: ValueCommitment(pallas::Affine::identity()), nullifier, - rk, + rk: rk.0, cm_x: NoteCommitment(pallas::Affine::identity()).extract_x(), ephemeral_key: EphemeralPublicKey(pallas::Affine::generator()), enc_ciphertext, @@ -57,10 +55,10 @@ impl Arbitrary for AuthorizedAction { type Parameters = (); fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - (any::(), any::>()) + (any::(), any::()) .prop_map(|(action, spend_auth_sig)| Self { action, - spend_auth_sig, + spend_auth_sig: spend_auth_sig.0, }) .boxed() } @@ -68,15 +66,19 @@ impl Arbitrary for AuthorizedAction { type Strategy = BoxedStrategy; } -impl Arbitrary for Signature { +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +struct SpendAuthSignature(pub(crate) Signature); + +impl Arbitrary for SpendAuthSignature { type Parameters = (); fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { (array::uniform32(any::()), array::uniform32(any::())) - .prop_map(|(r_bytes, s_bytes)| Self { - r_bytes: r_bytes.into(), - s_bytes: s_bytes.into(), - _marker: PhantomData, + .prop_map(|(r_bytes, s_bytes)| { + let mut bytes = [0; 64]; + bytes[0..32].copy_from_slice(&r_bytes[..]); + bytes[32..64].copy_from_slice(&s_bytes[..]); + SpendAuthSignature(Signature::::from(bytes)) }) .boxed() } @@ -84,16 +86,25 @@ impl Arbitrary for Signature { type Strategy = BoxedStrategy; } -impl Arbitrary for VerificationKeyBytes { +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +struct SpendAuthVerificationKeyBytes(pub(crate) VerificationKeyBytes); + +impl Arbitrary for SpendAuthVerificationKeyBytes { type Parameters = (); fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + // Generate a random signing key from a "seed". (vec(any::(), 64)) .prop_map(|bytes| { let bytes = bytes.try_into().expect("vec is the correct length"); - let sk = pallas::Scalar::from_bytes_wide(&bytes); - let pk = VerificationKey::from_scalar(&sk); - pk.into() + // Convert to a scalar + let sk_scalar = pallas::Scalar::from_bytes_wide(&bytes); + // Convert that back to a (canonical) encoding + let sk_bytes = sk_scalar.to_repr(); + // Decode it into a signing key + let sk = SigningKey::try_from(sk_bytes).unwrap(); + let pk = VerificationKey::::from(&sk); + SpendAuthVerificationKeyBytes(pk.into()) }) .boxed() } diff --git a/zebra-chain/src/orchard/shielded_data.rs b/zebra-chain/src/orchard/shielded_data.rs index 6b3d8a25..6b60a982 100644 --- a/zebra-chain/src/orchard/shielded_data.rs +++ b/zebra-chain/src/orchard/shielded_data.rs @@ -8,15 +8,13 @@ use std::{ use byteorder::{ReadBytesExt, WriteBytesExt}; use halo2::pasta::pallas; +use reddsa::{self, orchard::Binding, orchard::SpendAuth, Signature}; use crate::{ amount::{Amount, NegativeAllowed}, block::MAX_BLOCK_BYTES, orchard::{tree, Action, Nullifier, ValueCommitment}, - primitives::{ - redpallas::{self, Binding, Signature, SpendAuth}, - Halo2Proof, - }, + primitives::Halo2Proof, serialization::{ AtLeastOne, SerializationError, TrustedPreallocate, ZcashDeserialize, ZcashSerialize, }, @@ -98,7 +96,7 @@ impl ShieldedData { /// the balancing value. /// /// - pub fn binding_verification_key(&self) -> redpallas::VerificationKeyBytes { + pub fn binding_verification_key(&self) -> reddsa::VerificationKeyBytes { let cv: ValueCommitment = self.actions().map(|action| action.cv).sum(); let cv_balance: ValueCommitment = ValueCommitment::new(pallas::Scalar::zero(), self.value_balance); diff --git a/zebra-chain/src/orchard/tests/preallocate.rs b/zebra-chain/src/orchard/tests/preallocate.rs index d055e5fd..79f6a16e 100644 --- a/zebra-chain/src/orchard/tests/preallocate.rs +++ b/zebra-chain/src/orchard/tests/preallocate.rs @@ -1,12 +1,13 @@ //! Tests for trusted preallocation during deserialization. +use reddsa::{orchard::SpendAuth, Signature}; + use crate::{ block::MAX_BLOCK_BYTES, orchard::{ shielded_data::{ACTION_SIZE, AUTHORIZED_ACTION_SIZE}, Action, AuthorizedAction, }, - primitives::redpallas::{Signature, SpendAuth}, serialization::{arbitrary::max_allocation_is_big_enough, TrustedPreallocate, ZcashSerialize}, }; diff --git a/zebra-chain/src/primitives.rs b/zebra-chain/src/primitives.rs index 0a6ce4c2..1c7154eb 100644 --- a/zebra-chain/src/primitives.rs +++ b/zebra-chain/src/primitives.rs @@ -5,10 +5,8 @@ //! whose functionality is implemented elsewhere. mod proofs; -// TODO: re-export redpallas if needed, or reddsa if that gets merged https://github.com/ZcashFoundation/zebra/issues/2044 -pub mod redpallas; - pub use ed25519_zebra as ed25519; +pub use reddsa; pub use redjubjub; pub use x25519_dalek as x25519; diff --git a/zebra-chain/src/primitives/redpallas.rs b/zebra-chain/src/primitives/redpallas.rs deleted file mode 100644 index c680fbfd..00000000 --- a/zebra-chain/src/primitives/redpallas.rs +++ /dev/null @@ -1,81 +0,0 @@ -// -*- mode: rust; -*- -// -// This file is part of redjubjub. -// Copyright (c) 2019-2021 Zcash Foundation -// See LICENSE for licensing information. -// -// Authors: -// - Deirdre Connolly -// - Henry de Valence - -//! RedPallas digital signatures. -//! -//! RedDSA (a Schnorr-based signature scheme) signatures over the [Pallas][pallas] curve. -//! -//! - -use group::GroupEncoding; -use halo2::pasta::pallas; - -pub mod batch; -mod constants; -#[allow(missing_docs)] -mod error; -mod hash; -mod scalar_mul; -mod signature; -mod signing_key; -#[cfg(test)] -mod tests; -mod verification_key; - -pub use error::Error; -pub use hash::HStar; -pub use signature::Signature; -pub use signing_key::SigningKey; -pub use verification_key::{VerificationKey, VerificationKeyBytes}; - -/// An element of the Pallas scalar field used for randomization of verification -/// and signing keys. -pub type Randomizer = pallas::Scalar; - -/// Abstracts over different RedPallas parameter choices, [`Binding`] -/// and [`SpendAuth`]. -/// -/// As described [at the end of ยง5.4.6][concretereddsa] of the Zcash -/// protocol specification, the generator used in RedPallas is left as -/// an unspecified parameter, chosen differently for each of -/// `BindingSig` and `SpendAuthSig`. -/// -/// To handle this, we encode the parameter choice as a genuine type -/// parameter. -/// -/// [concretereddsa]: https://zips.z.cash/protocol/nu5.pdf#concretereddsa -pub trait SigType: private::Sealed {} - -/// A type variable corresponding to Zcash's `BindingSig`. -#[derive(Copy, Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] -pub enum Binding {} -impl SigType for Binding {} - -/// A type variable corresponding to Zcash's `SpendAuthSig`. -#[derive(Copy, Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] -pub enum SpendAuth {} -impl SigType for SpendAuth {} - -mod private { - use super::*; - pub trait Sealed: Copy + Clone + Eq + PartialEq + std::fmt::Debug { - fn basepoint() -> pallas::Point; - } - impl Sealed for Binding { - fn basepoint() -> pallas::Point { - pallas::Point::from_bytes(&constants::BINDINGSIG_BASEPOINT_BYTES).unwrap() - } - } - impl Sealed for SpendAuth { - fn basepoint() -> pallas::Point { - pallas::Point::from_bytes(&constants::SPENDAUTHSIG_BASEPOINT_BYTES).unwrap() - } - } -} diff --git a/zebra-chain/src/primitives/redpallas/batch.rs b/zebra-chain/src/primitives/redpallas/batch.rs deleted file mode 100644 index 95ecbcbf..00000000 --- a/zebra-chain/src/primitives/redpallas/batch.rs +++ /dev/null @@ -1,305 +0,0 @@ -// -*- mode: rust; -*- -// -// This file is part of redpallas. -// Copyright (c) 2019-2021 Zcash Foundation -// See LICENSE for licensing information. -// -// Authors: -// - Deirdre Connolly -// - Henry de Valence - -//! Performs batch RedPallas 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 -//! computations among all signature verifications, performing less work overall -//! at the cost of higher latency (the entire batch must complete), complexity of -//! caller code (which must assemble a batch of signatures across work-items), -//! and loss of the ability to easily pinpoint failing signatures. - -use std::convert::TryFrom; - -use group::{ff::PrimeField, Group, GroupEncoding}; -use rand_core::{CryptoRng, RngCore}; - -use super::{private::Sealed, scalar_mul::VartimeMultiscalarMul, *}; - -/// Shim to generate a random 128 bit value in a `[u64; 4]`, without -/// importing `rand`. -/// -/// The final 128 bits are zero. -fn gen_128_bits(mut rng: R) -> [u64; 4] { - let mut bytes = [0u64; 4]; - bytes[0] = rng.next_u64(); - bytes[1] = rng.next_u64(); - bytes -} - -/// Inner type of a batch verification item. -/// -/// This struct exists to allow batch processing to be decoupled from the -/// lifetime of the message. This is useful when using the batch verification -/// API in an async context -/// -/// The different enum variants are for the different signature types which use -/// different Pallas basepoints for computation: SpendAuth and Binding signatures. -#[derive(Clone, Debug)] -enum Inner { - /// A RedPallas signature using the SpendAuth generator group element. - /// - /// Used in Orchard to prove knowledge of the `spending key` authorizing - /// spending of an input note. There is a separate signature, vs just - /// verifying inside the proof, to allow resource-limited devices to - /// authorize a shielded transaction without needing to construct a proof - /// themselves. - /// - /// - SpendAuth { - vk_bytes: VerificationKeyBytes, - sig: Signature, - c: pallas::Scalar, - }, - /// A RedPallas signature using the Binding generator group element. - /// - /// Verifying this signature ensures that the Orchard Action transfers in - /// the transaction balance are valid, without their individual net values - /// being revealed. In addition, this proves that the signer, knowing the - /// sum of the Orchard value commitment randomnesses, authorized a - /// transaction with the given SIGHASH transaction hash by signing `SigHash`. - /// - /// - Binding { - vk_bytes: VerificationKeyBytes, - sig: Signature, - c: pallas::Scalar, - }, -} - -/// A batch verification item. -/// -/// This struct exists to allow batch processing to be decoupled from the -/// 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, -} - -impl<'msg, M: AsRef<[u8]>> - From<( - VerificationKeyBytes, - Signature, - &'msg M, - )> for Item -{ - fn from( - (vk_bytes, sig, msg): ( - VerificationKeyBytes, - Signature, - &'msg M, - ), - ) -> Self { - // Compute c now to avoid dependency on the msg lifetime. - let c = HStar::default() - .update(&sig.r_bytes[..]) - .update(&vk_bytes.bytes[..]) - .update(msg) - .finalize(); - Self { - 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), - ) -> Self { - // Compute c now to avoid dependency on the msg lifetime. - let c = HStar::default() - .update(&sig.r_bytes[..]) - .update(&vk_bytes.bytes[..]) - .update(msg) - .finalize(); - Self { - inner: Inner::Binding { vk_bytes, sig, c }, - } - } -} - -impl Item { - /// Perform non-batched verification of this `Item`. - /// - /// This is useful (in combination with `Item::clone`) for implementing - /// fallback logic when batch verification fails. In contrast to - /// [`VerificationKey::verify`], which requires borrowing the message data, - /// the `Item` type is unlinked from the lifetime of the message. - pub fn verify_single(self) -> 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::SpendAuth { vk_bytes, sig, c } => { - // # Consensus - // - // > Elements of an Action description MUST be canonical encodings of the types given above. - // - // https://zips.z.cash/protocol/protocol.pdf#actiondesc - // - // This validates the `rk` element, whose type is - // SpendAuthSig^{Orchard}.Public, i.e. โ„™. - VerificationKey::::try_from(vk_bytes) - .and_then(|vk| vk.verify_prehashed(&sig, c)) - } - } - } -} - -#[derive(Default)] -/// A batch verification context. -pub struct Verifier { - /// Signature data queued for verification. - signatures: Vec, -} - -impl Verifier { - /// Construct a new batch verifier. - pub fn new() -> Verifier { - Verifier::default() - } - - /// Queue an Item for verification. - pub fn queue>(&mut self, item: I) { - self.signatures.push(item.into()); - } - - /// Perform batch verification, returning `Ok(())` if all signatures were - /// valid and `Err` otherwise. - /// - /// The batch verification equation is: - /// - /// h_G * -[sum(z_i * s_i)]P_G + sum(\[z_i\]R_i + [z_i * c_i]VK_i) = 0_G - /// - /// which we split out into: - /// - /// h_G * -[sum(z_i * s_i)]P_G + sum(\[z_i\]R_i) + sum([z_i * c_i]VK_i) = 0_G - /// - /// so that we can use multiscalar multiplication speedups. - /// - /// where for each signature i, - /// - VK_i is the verification key; - /// - R_i is the signature's R value; - /// - s_i is the signature's s value; - /// - c_i is the hash of the message and other data; - /// - z_i is a random 128-bit Scalar; - /// - h_G is the cofactor of the group; - /// - P_G is the generator of the subgroup; - /// - /// Since RedPallas 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 - /// amortize computation nicely in one multiscalar multiplication: - /// - /// h_G * ( [-sum(z_i * s_i): i_type == SpendAuth]P_SpendAuth + [-sum(z_i * s_i): i_type == Binding]P_Binding + sum(\[z_i\]R_i) + sum([z_i * c_i]VK_i) ) = 0_G - /// - /// As follows elliptic curve scalar multiplication convention, - /// scalar variables are lowercase and group point variables - /// are uppercase. This does not exactly match the RedDSA - /// notation in the [protocol specification ยงB.1][ps]. - /// - /// [ps]: https://zips.z.cash/protocol/protocol.pdf#reddsabatchverify - #[allow(non_snake_case)] - pub fn verify(self, mut rng: R) -> Result<(), Error> { - let n = self.signatures.len(); - - let mut VK_coeffs = Vec::with_capacity(n); - 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 = pallas::Scalar::zero(); - let mut P_binding_coeff = pallas::Scalar::zero(); - - for item in self.signatures.iter() { - let (s_bytes, r_bytes, c) = match item.inner { - Inner::SpendAuth { sig, c, .. } => (sig.s_bytes, sig.r_bytes, c), - Inner::Binding { sig, c, .. } => (sig.s_bytes, sig.r_bytes, c), - }; - - let s = { - // XXX-pallas: should not use CtOption here - let maybe_scalar = pallas::Scalar::from_repr(*s_bytes); - if maybe_scalar.is_some().into() { - maybe_scalar.unwrap() - } else { - return Err(Error::InvalidSignature); - } - }; - - let R = { - // XXX-pallas: should not use CtOption here - // XXX-pallas: inconsistent ownership in from_bytes - let maybe_point = pallas::Affine::from_bytes(&r_bytes); - if maybe_point.is_some().into() { - pallas::Point::from(maybe_point.unwrap()) - } else { - return Err(Error::InvalidSignature); - } - }; - - let VK = match item.inner { - Inner::SpendAuth { vk_bytes, .. } => { - // # Consensus - // - // > Elements of an Action description MUST be canonical encodings of the types given above. - // - // https://zips.z.cash/protocol/protocol.pdf#actiondesc - // - // This validates the `rk` element, whose type is - // SpendAuthSig^{Orchard}.Public, i.e. โ„™. - VerificationKey::::try_from(*vk_bytes.bytes)?.point - } - Inner::Binding { vk_bytes, .. } => { - VerificationKey::::try_from(*vk_bytes.bytes)?.point - } - }; - - let z = pallas::Scalar::from_raw(gen_128_bits(&mut rng)); - - let P_coeff = z * s; - match item.inner { - Inner::SpendAuth { .. } => { - P_spendauth_coeff -= P_coeff; - } - Inner::Binding { .. } => { - P_binding_coeff -= P_coeff; - } - }; - - R_coeffs.push(z); - Rs.push(R); - - VK_coeffs.push(z * c); - VKs.push(VK); - } - - use std::iter::once; - - let scalars = once(&P_spendauth_coeff) - .chain(once(&P_binding_coeff)) - .chain(VK_coeffs.iter()) - .chain(R_coeffs.iter()); - - let basepoints = [SpendAuth::basepoint(), Binding::basepoint()]; - let points = basepoints.iter().chain(VKs.iter()).chain(Rs.iter()); - - let check = pallas::Point::vartime_multiscalar_mul(scalars, points); - - if check.is_identity().into() { - Ok(()) - } else { - Err(Error::InvalidSignature) - } - } -} diff --git a/zebra-chain/src/primitives/redpallas/constants.rs b/zebra-chain/src/primitives/redpallas/constants.rs deleted file mode 100644 index e3eefc07..00000000 --- a/zebra-chain/src/primitives/redpallas/constants.rs +++ /dev/null @@ -1,24 +0,0 @@ -// -*- mode: rust; -*- -// -// This file is part of redpallas. -// Copyright (c) 2019-2021 Zcash Foundation -// See LICENSE for licensing information. -// -// Authors: -// - Deirdre Connolly - -/// The byte-encoding of the basepoint for `SpendAuthSig` on the [Pallas curve][pallasandvesta]. -/// -/// [pallasandvesta]: https://zips.z.cash/protocol/nu5.pdf#pallasandvesta -// Reproducible by pallas::Point::hash_to_curve("z.cash:Orchard")(b"G").to_bytes() -pub const SPENDAUTHSIG_BASEPOINT_BYTES: [u8; 32] = [ - 99, 201, 117, 184, 132, 114, 26, 141, 12, 161, 112, 123, 227, 12, 127, 12, 95, 68, 95, 62, 124, - 24, 141, 59, 6, 214, 241, 40, 179, 35, 85, 183, -]; - -/// The byte-encoding of the basepoint for `BindingSig` on the Pallas curve. -// Reproducible by pallas::Point::hash_to_curve("z.cash:Orchard-cv")(b"r").to_bytes() -pub const BINDINGSIG_BASEPOINT_BYTES: [u8; 32] = [ - 145, 90, 60, 136, 104, 198, 195, 14, 47, 128, 144, 238, 69, 215, 110, 64, 72, 32, 141, 234, 91, - 35, 102, 79, 187, 9, 164, 15, 85, 68, 244, 7, -]; diff --git a/zebra-chain/src/primitives/redpallas/error.rs b/zebra-chain/src/primitives/redpallas/error.rs deleted file mode 100644 index 063925c6..00000000 --- a/zebra-chain/src/primitives/redpallas/error.rs +++ /dev/null @@ -1,11 +0,0 @@ -use thiserror::Error; - -#[derive(Error, Debug, Copy, Clone, Eq, PartialEq)] -pub enum Error { - #[error("Malformed signing key encoding.")] - MalformedSigningKey, - #[error("Malformed verification key encoding.")] - MalformedVerificationKey, - #[error("Invalid signature.")] - InvalidSignature, -} diff --git a/zebra-chain/src/primitives/redpallas/hash.rs b/zebra-chain/src/primitives/redpallas/hash.rs deleted file mode 100644 index 757acac5..00000000 --- a/zebra-chain/src/primitives/redpallas/hash.rs +++ /dev/null @@ -1,40 +0,0 @@ -// -*- mode: rust; -*- -// -// This file is part of redpallas. -// Copyright (c) 2019-2021 Zcash Foundation -// See LICENSE for licensing information. -// -// Authors: -// - Deirdre Connolly -// - Henry de Valence - -use blake2b_simd::{Params, State}; -use halo2::{arithmetic::FieldExt, pasta::pallas::Scalar}; - -/// Provides H^star, the hash-to-scalar function used by RedPallas. -pub struct HStar { - state: State, -} - -impl Default for HStar { - fn default() -> Self { - let state = Params::new() - .hash_length(64) - .personal(b"Zcash_RedPallasH") - .to_state(); - Self { state } - } -} - -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()); - self - } - - /// Consume `self` to compute the hash output. - pub fn finalize(&self) -> Scalar { - Scalar::from_bytes_wide(self.state.finalize().as_array()) - } -} diff --git a/zebra-chain/src/primitives/redpallas/scalar_mul.rs b/zebra-chain/src/primitives/redpallas/scalar_mul.rs deleted file mode 100644 index a7108fa2..00000000 --- a/zebra-chain/src/primitives/redpallas/scalar_mul.rs +++ /dev/null @@ -1,212 +0,0 @@ -// -*- mode: rust; -*- -// -// This file is part of redpallas. -// Copyright (c) 2019-2021 Zcash Foundation -// Copyright (c) 2017-2021 isis agora lovecruft, Henry de Valence -// See LICENSE for licensing information. -// -// Authors: -// - isis agora lovecruft -// - Henry de Valence -// - Deirdre Connolly - -//! Traits and types that support variable-time multiscalar multiplication with -//! the [Pallas][pallas] curve. - -use std::{borrow::Borrow, fmt::Debug}; - -use group::{ff::PrimeField, Group}; -use halo2::pasta::pallas; - -/// A trait to support getting the Non-Adjacent form of a scalar. -pub trait NonAdjacentForm { - fn non_adjacent_form(&self, w: usize) -> [i8; 256]; -} - -/// A trait for variable-time multiscalar multiplication without precomputation. -pub trait VartimeMultiscalarMul { - /// The type of point being multiplied, e.g., `AffinePoint`. - type Point; - - /// Given an iterator of public scalars and an iterator of - /// `Option`s of points, compute either `Some(Q)`, where - /// $$ - /// Q = c\_1 P\_1 + \cdots + c\_n P\_n, - /// $$ - /// if all points were `Some(P_i)`, or else return `None`. - fn optional_multiscalar_mul(scalars: I, points: J) -> Option - where - I: IntoIterator, - I::Item: Borrow, - J: IntoIterator>; - - /// Given an iterator of public scalars and an iterator of - /// public points, compute - /// $$ - /// Q = c\_1 P\_1 + \cdots + c\_n P\_n, - /// $$ - /// using variable-time operations. - /// - /// It is an error to call this function with two iterators of different lengths. - fn vartime_multiscalar_mul(scalars: I, points: J) -> Self::Point - where - I: IntoIterator, - I::Item: Borrow, - J: IntoIterator, - J::Item: Borrow, - Self::Point: Clone, - { - Self::optional_multiscalar_mul( - scalars, - points.into_iter().map(|p| Some(p.borrow().clone())), - ) - .unwrap() - } -} - -impl NonAdjacentForm for pallas::Scalar { - /// Compute a width-\\(w\\) "Non-Adjacent Form" of this scalar. - /// - /// Thanks to [`curve25519-dalek`]. - /// - /// [`curve25519-dalek`]: https://github.com/dalek-cryptography/curve25519-dalek/blob/3e189820da03cc034f5fa143fc7b2ccb21fffa5e/src/scalar.rs#L907 - fn non_adjacent_form(&self, w: usize) -> [i8; 256] { - // required by the NAF definition - debug_assert!(w >= 2); - // required so that the NAF digits fit in i8 - debug_assert!(w <= 8); - - use byteorder::{ByteOrder, LittleEndian}; - - let mut naf = [0i8; 256]; - - let mut x_u64 = [0u64; 5]; - LittleEndian::read_u64_into(&self.to_repr(), &mut x_u64[0..4]); - - let width = 1 << w; - let window_mask = width - 1; - - let mut pos = 0; - let mut carry = 0; - while pos < 256 { - // Construct a buffer of bits of the scalar, starting at bit `pos` - let u64_idx = pos / 64; - let bit_idx = pos % 64; - let bit_buf: u64 = if bit_idx < 64 - w { - // This window's bits are contained in a single u64 - x_u64[u64_idx] >> bit_idx - } else { - // Combine the current u64's bits with the bits from the next u64 - (x_u64[u64_idx] >> bit_idx) | (x_u64[1 + u64_idx] << (64 - bit_idx)) - }; - - // Add the carry into the current window - let window = carry + (bit_buf & window_mask); - - if window & 1 == 0 { - // If the window value is even, preserve the carry and continue. - // Why is the carry preserved? - // If carry == 0 and window & 1 == 0, then the next carry should be 0 - // If carry == 1 and window & 1 == 0, then bit_buf & 1 == 1 so the next carry should be 1 - pos += 1; - continue; - } - - if window < width / 2 { - carry = 0; - naf[pos] = window as i8; - } else { - carry = 1; - naf[pos] = (window as i8).wrapping_sub(width as i8); - } - - pos += w; - } - - naf - } -} - -/// Holds odd multiples 1A, 3A, ..., 15A of a point A. -#[derive(Copy, Clone)] -pub(crate) struct LookupTable5(pub(crate) [T; 8]); - -impl LookupTable5 { - /// Given public, odd \\( x \\) with \\( 0 < x < 2^4 \\), return \\(xA\\). - pub fn select(&self, x: usize) -> T { - debug_assert_eq!(x & 1, 1); - debug_assert!(x < 16); - - self.0[x / 2] - } -} - -impl Debug for LookupTable5 { - fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - write!(f, "LookupTable5({:?})", self.0) - } -} - -impl<'a> From<&'a pallas::Point> for LookupTable5 { - #[allow(non_snake_case)] - fn from(A: &'a pallas::Point) -> Self { - let mut Ai = [*A; 8]; - let A2 = A.double(); - for i in 0..7 { - Ai[i + 1] = A2 + Ai[i]; - } - // Now Ai = [A, 3A, 5A, 7A, 9A, 11A, 13A, 15A] - LookupTable5(Ai) - } -} - -impl VartimeMultiscalarMul for pallas::Point { - type Point = pallas::Point; - - /// Variable-time multiscalar multiplication using a non-adjacent form of - /// width (5). - /// - /// The non-adjacent form has signed, odd digits. Using only odd digits - /// halves the table size (since we only need odd multiples), or gives fewer - /// additions for the same table size. - /// - /// As the name implies, the runtime varies according to the values of the - /// inputs, thus is not safe for computing over secret data, but is great - /// for computing over public data, such as validating signatures. - #[allow(non_snake_case)] - #[allow(clippy::comparison_chain)] - fn optional_multiscalar_mul(scalars: I, points: J) -> Option - where - I: IntoIterator, - I::Item: Borrow, - J: IntoIterator>, - { - let nafs: Vec<_> = scalars - .into_iter() - .map(|c| c.borrow().non_adjacent_form(5)) - .collect(); - - let lookup_tables = points - .into_iter() - .map(|P_opt| P_opt.map(|P| LookupTable5::::from(&P))) - .collect::>>()?; - - let mut r = pallas::Point::identity(); - - for i in (0..256).rev() { - let mut t = r.double(); - - for (naf, lookup_table) in nafs.iter().zip(lookup_tables.iter()) { - if naf[i] > 0 { - t += lookup_table.select(naf[i] as usize); - } else if naf[i] < 0 { - t -= lookup_table.select(-naf[i] as usize); - } - } - - r = t; - } - - Some(r) - } -} diff --git a/zebra-chain/src/primitives/redpallas/signature.rs b/zebra-chain/src/primitives/redpallas/signature.rs deleted file mode 100644 index 8f8d3a07..00000000 --- a/zebra-chain/src/primitives/redpallas/signature.rs +++ /dev/null @@ -1,62 +0,0 @@ -// -*- mode: rust; -*- -// -// This file is part of redpallas. -// Copyright (c) 2019-2021 Zcash Foundation -// See LICENSE for licensing information. -// -// Authors: -// - Henry de Valence -// - Deirdre Connolly - -use std::{io, marker::PhantomData}; - -use super::SigType; - -use crate::{ - fmt::HexDebug, - serialization::{ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize}, -}; - -/// A RedPallas signature. -#[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] -pub struct Signature { - pub(crate) r_bytes: HexDebug<[u8; 32]>, - pub(crate) s_bytes: HexDebug<[u8; 32]>, - pub(crate) _marker: PhantomData, -} - -impl From<[u8; 64]> for Signature { - fn from(bytes: [u8; 64]) -> Signature { - let mut r_bytes = [0; 32]; - r_bytes.copy_from_slice(&bytes[0..32]); - let mut s_bytes = [0; 32]; - s_bytes.copy_from_slice(&bytes[32..64]); - Signature { - r_bytes: r_bytes.into(), - s_bytes: s_bytes.into(), - _marker: PhantomData, - } - } -} - -impl From> for [u8; 64] { - fn from(sig: Signature) -> [u8; 64] { - let mut bytes = [0; 64]; - bytes[0..32].copy_from_slice(&sig.r_bytes[..]); - bytes[32..64].copy_from_slice(&sig.s_bytes[..]); - bytes - } -} - -impl ZcashSerialize for Signature { - fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { - writer.write_all(&<[u8; 64]>::from(*self)[..])?; - Ok(()) - } -} - -impl ZcashDeserialize for Signature { - fn zcash_deserialize(mut reader: R) -> Result { - Ok(reader.read_64_bytes()?.into()) - } -} diff --git a/zebra-chain/src/primitives/redpallas/signing_key.rs b/zebra-chain/src/primitives/redpallas/signing_key.rs deleted file mode 100644 index 0570ee27..00000000 --- a/zebra-chain/src/primitives/redpallas/signing_key.rs +++ /dev/null @@ -1,126 +0,0 @@ -//! Redpallas signing keys for Zebra. - -use std::marker::PhantomData; - -use group::{ff::PrimeField, GroupEncoding}; -use halo2::{arithmetic::FieldExt, pasta::pallas}; -use rand_core::{CryptoRng, RngCore}; - -use super::{Error, SigType, Signature, SpendAuth, VerificationKey}; - -/// A RedPallas 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: pallas::Scalar, - pk: VerificationKey, -} - -impl<'a, T: SigType> From<&'a SigningKey> for VerificationKey { - fn from(sk: &'a SigningKey) -> VerificationKey { - sk.pk - } -} - -impl From> for [u8; 32] { - fn from(sk: SigningKey) -> [u8; 32] { - sk.sk.to_repr() - } -} - -impl TryFrom<[u8; 32]> for SigningKey { - type Error = Error; - - fn try_from(bytes: [u8; 32]) -> Result { - let maybe_sk = pallas::Scalar::from_repr(bytes); - - if maybe_sk.is_some().into() { - let sk = maybe_sk.unwrap(); - let pk = VerificationKey::from_scalar(&sk); - Ok(SigningKey { sk, pk }) - } else { - Err(Error::MalformedSigningKey) - } - } -} - -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -struct SerdeHelper([u8; 32]); - -impl TryFrom for SigningKey { - type Error = Error; - - fn try_from(helper: SerdeHelper) -> Result { - helper.0.try_into() - } -} - -impl From> for SerdeHelper { - fn from(sk: SigningKey) -> Self { - Self(sk.into()) - } -} - -impl SigningKey { - /// Randomize this public key with the given `randomizer`. - pub fn randomize(&self, randomizer: &pallas::Scalar) -> SigningKey { - let sk = self.sk + randomizer; - let pk = VerificationKey::from_scalar(&sk); - SigningKey { sk, pk } - } -} - -impl SigningKey { - /// Generate a new signing key. - pub fn new(mut rng: R) -> SigningKey { - let sk = { - let mut bytes = [0; 64]; - rng.fill_bytes(&mut bytes); - pallas::Scalar::from_bytes_wide(&bytes) - }; - let pk = VerificationKey::from_scalar(&sk); - SigningKey { sk, pk } - } - - /// Create a signature of type `T` on `msg` using this `SigningKey`. - /// - /// - // Similar to signature::Signer but without boxed errors. - pub fn sign(&self, mut rng: R, msg: &[u8]) -> Signature { - use super::HStar; - - // RedDSA.GenRandom:() โ†’ R RedDSA.Random - // Choose a byte sequence uniformly at random of length - // (\ell_H + 128)/8 bytes. For 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() - .update(&random_bytes[..]) - .update(&self.pk.bytes.bytes[..]) // XXX ugly - .update(msg) - .finalize(); - - let r_bytes = pallas::Affine::from(T::basepoint() * nonce).to_bytes(); - - 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_repr(); - - Signature { - r_bytes: r_bytes.into(), - s_bytes: s_bytes.into(), - _marker: PhantomData, - } - } -} diff --git a/zebra-chain/src/primitives/redpallas/tests.rs b/zebra-chain/src/primitives/redpallas/tests.rs deleted file mode 100644 index 148b5aa3..00000000 --- a/zebra-chain/src/primitives/redpallas/tests.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod basepoints; -mod batch; -mod prop; diff --git a/zebra-chain/src/primitives/redpallas/tests/basepoints.rs b/zebra-chain/src/primitives/redpallas/tests/basepoints.rs deleted file mode 100644 index 298990e9..00000000 --- a/zebra-chain/src/primitives/redpallas/tests/basepoints.rs +++ /dev/null @@ -1,22 +0,0 @@ -use group::GroupEncoding; -use halo2::pasta::{arithmetic::CurveExt, pallas}; - -use super::super::constants; - -#[test] -fn orchard_spendauth_basepoint() { - assert_eq!( - // An instance of _GroupHash^P_ - pallas::Point::hash_to_curve("z.cash:Orchard")(b"G").to_bytes(), - constants::SPENDAUTHSIG_BASEPOINT_BYTES - ); -} - -#[test] -fn orchard_binding_basepoint() { - assert_eq!( - // An instance of _GroupHash^P_ - pallas::Point::hash_to_curve("z.cash:Orchard-cv")(b"r").to_bytes(), - constants::BINDINGSIG_BASEPOINT_BYTES - ); -} diff --git a/zebra-chain/src/primitives/redpallas/tests/batch.rs b/zebra-chain/src/primitives/redpallas/tests/batch.rs deleted file mode 100644 index 33a7ea41..00000000 --- a/zebra-chain/src/primitives/redpallas/tests/batch.rs +++ /dev/null @@ -1,141 +0,0 @@ -use rand::thread_rng; - -use super::super::*; - -#[test] -fn spendauth_batch_verify() { - let mut rng = thread_rng(); - let mut batch = batch::Verifier::new(); - for _ in 0..32 { - 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)); - } - assert!(batch.verify(rng).is_ok()); -} - -#[test] -fn binding_batch_verify() { - let mut rng = thread_rng(); - let mut batch = batch::Verifier::new(); - for _ in 0..32 { - 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)); - } - assert!(batch.verify(rng).is_ok()); -} - -#[test] -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 { - 0 => { - 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() - } - 1 => { - 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() - } - _ => unreachable!(), - }; - batch.queue(item); - } - assert!(batch.verify(rng).is_ok()); -} - -#[test] -fn bad_spendauth_in_batch_verify() { - let mut rng = thread_rng(); - let bad_index = 4; // must be even - let mut batch = batch::Verifier::new(); - let mut items = Vec::new(); - for i in 0..32 { - let item: batch::Item = match i % 2 { - 0 => { - let sk = SigningKey::::new(&mut rng); - let vk = VerificationKey::from(&sk); - let msg = b"BatchVerifyTest"; - let sig = if i != bad_index { - sk.sign(&mut rng, &msg[..]) - } else { - sk.sign(&mut rng, b"bad") - }; - (vk.into(), sig, msg).into() - } - 1 => { - 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() - } - _ => unreachable!(), - }; - items.push(item.clone()); - batch.queue(item); - } - assert!(batch.verify(rng).is_err()); - for (i, item) in items.drain(..).enumerate() { - if i != bad_index { - assert!(item.verify_single().is_ok()); - } else { - assert!(item.verify_single().is_err()); - } - } -} - -#[test] -fn bad_binding_in_batch_verify() { - let mut rng = thread_rng(); - let bad_index = 3; // must be odd - let mut batch = batch::Verifier::new(); - let mut items = Vec::new(); - for i in 0..32 { - let item: batch::Item = match i % 2 { - 0 => { - 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() - } - 1 => { - let sk = SigningKey::::new(&mut rng); - let vk = VerificationKey::from(&sk); - let msg = b"BatchVerifyTest"; - - let sig = if i != bad_index { - sk.sign(&mut rng, &msg[..]) - } else { - sk.sign(&mut rng, b"bad") - }; - (vk.into(), sig, msg).into() - } - _ => unreachable!(), - }; - items.push(item.clone()); - batch.queue(item); - } - assert!(batch.verify(rng).is_err()); - for (i, item) in items.drain(..).enumerate() { - if i != bad_index { - assert!(item.verify_single().is_ok()); - } else { - assert!(item.verify_single().is_err()); - } - } -} diff --git a/zebra-chain/src/primitives/redpallas/tests/prop.rs b/zebra-chain/src/primitives/redpallas/tests/prop.rs deleted file mode 100644 index aff82ea7..00000000 --- a/zebra-chain/src/primitives/redpallas/tests/prop.rs +++ /dev/null @@ -1,150 +0,0 @@ -use std::convert::TryFrom; - -use halo2::arithmetic::FieldExt; -use proptest::prelude::*; -use rand_chacha::ChaChaRng; -use rand_core::{CryptoRng, RngCore, SeedableRng}; - -use super::super::SigningKey; -use super::super::*; - -/// A signature test-case, containing signature data and expected validity. -#[derive(Clone, Debug)] -struct SignatureCase { - msg: Vec, - sig: Signature, - pk_bytes: VerificationKeyBytes, - invalid_pk_bytes: VerificationKeyBytes, - is_valid: bool, -} - -/// A modification to a test-case. -#[derive(Copy, Clone, Debug)] -enum Tweak { - /// No-op, used to check that unchanged cases verify. - None, - /// Change the message the signature is defined for, invalidating the signature. - ChangeMessage, - /// Change the public key the signature is defined for, invalidating the signature. - ChangePubkey, - /* XXX implement this -- needs to regenerate a custom signature because the - nonce commitment is fed into the hash, so it has to have torsion at signing - time. - /// Change the case to have a torsion component in the signature's `r` value. - AddTorsion, - */ - /* XXX implement this -- needs custom handling of field arithmetic. - /// Change the signature's `s` scalar to be unreduced (mod L), invalidating the signature. - UnreducedScalar, - */ -} - -impl SignatureCase { - fn new(mut rng: R, msg: Vec) -> Self { - let sk = SigningKey::new(&mut rng); - let sig = sk.sign(&mut rng, &msg); - let pk_bytes = VerificationKey::from(&sk).into(); - let invalid_pk_bytes = VerificationKey::from(&SigningKey::new(&mut rng)).into(); - Self { - msg, - sig, - pk_bytes, - invalid_pk_bytes, - is_valid: true, - } - } - - // Check that signature verification succeeds or fails, as expected. - fn check(&self) -> bool { - // The signature data is stored in (refined) byte types, but do a round trip - // conversion to raw bytes to exercise those code paths. - let sig = { - let bytes: [u8; 64] = self.sig.into(); - Signature::::from(bytes) - }; - let pk_bytes = { - let bytes: [u8; 32] = self.pk_bytes.into(); - VerificationKeyBytes::::from(bytes) - }; - - // Check that the verification key is a valid RedPallas verification key. - let pub_key = VerificationKey::try_from(pk_bytes) - .expect("The test verification key to be well-formed."); - - // Check that signature validation has the expected result. - self.is_valid == pub_key.verify(&self.msg, &sig).is_ok() - } - - fn apply_tweak(&mut self, tweak: &Tweak) { - match tweak { - Tweak::None => {} - Tweak::ChangeMessage => { - // Changing the message makes the signature invalid. - self.msg.push(90); - self.is_valid = false; - } - Tweak::ChangePubkey => { - // Changing the public key makes the signature invalid. - self.pk_bytes = self.invalid_pk_bytes; - self.is_valid = false; - } - } - } -} - -fn tweak_strategy() -> impl Strategy { - prop_oneof![ - 10 => Just(Tweak::None), - 1 => Just(Tweak::ChangeMessage), - 1 => Just(Tweak::ChangePubkey), - ] -} - -proptest! { - #[test] - fn tweak_signature( - tweaks in prop::collection::vec(tweak_strategy(), (0,5)), - rng_seed in prop::array::uniform32(any::()), - ) { - // Use a deterministic RNG so that test failures can be reproduced. - let mut rng = ChaChaRng::from_seed(rng_seed); - - // 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()); - - // Apply tweaks to each case. - for t in &tweaks { - binding.apply_tweak(t); - spendauth.apply_tweak(t); - } - - assert!(binding.check()); - assert!(spendauth.check()); - } - - #[test] - fn randomization_commutes_with_pubkey_homomorphism(rng_seed in prop::array::uniform32(any::())) { - // Use a deterministic RNG so that test failures can be reproduced. - let mut rng = ChaChaRng::from_seed(rng_seed); - - let r = { - // XXX-pasta_curves: better API for this - let mut bytes = [0; 64]; - rng.fill_bytes(&mut bytes[..]); - Randomizer::from_bytes_wide(&bytes) - }; - - let sk = SigningKey::::new(&mut rng); - let pk = VerificationKey::from(&sk); - - let sk_r = sk.randomize(&r); - let pk_r = pk.randomize(&r); - - let pk_r_via_sk_rand: [u8; 32] = VerificationKeyBytes::from(VerificationKey::from(&sk_r)).into(); - let pk_r_via_pk_rand: [u8; 32] = VerificationKeyBytes::from(pk_r).into(); - - assert_eq!(pk_r_via_pk_rand, pk_r_via_sk_rand); - } -} diff --git a/zebra-chain/src/primitives/redpallas/verification_key.rs b/zebra-chain/src/primitives/redpallas/verification_key.rs deleted file mode 100644 index c523ab88..00000000 --- a/zebra-chain/src/primitives/redpallas/verification_key.rs +++ /dev/null @@ -1,182 +0,0 @@ -//! Redpallas verification keys for Zebra. - -use std::marker::PhantomData; - -use group::{cofactor::CofactorGroup, ff::PrimeField, GroupEncoding}; -use halo2::pasta::pallas; - -use crate::fmt::HexDebug; - -use super::*; - -/// A refinement type for `[u8; 32]` indicating that the bytes represent -/// an encoding of a RedPallas verification key. -/// -/// This is useful for representing a compressed verification key; the -/// [`VerificationKey`] type in this library holds other decompressed state -/// used in signature verification. -#[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] -pub struct VerificationKeyBytes { - pub(crate) bytes: HexDebug<[u8; 32]>, - pub(crate) _marker: PhantomData, -} - -impl From<[u8; 32]> for VerificationKeyBytes { - fn from(bytes: [u8; 32]) -> VerificationKeyBytes { - VerificationKeyBytes { - bytes: bytes.into(), - _marker: PhantomData, - } - } -} - -impl From> for [u8; 32] { - fn from(refined: VerificationKeyBytes) -> [u8; 32] { - *refined.bytes - } -} - -// TODO: impl Hash for VerificationKeyBytes, or import that impl: https://github.com/ZcashFoundation/zebra/issues/2044 - -/// A valid RedPallas 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 -/// [`VerificationKeyBytes`], which is a refinement type for `[u8; 32]`. -/// -/// ## Consensus properties -/// -/// The `TryFrom` conversion performs the following Zcash -/// consensus rule checks: -/// -/// 1. The check that the bytes are a canonical encoding of a verification key; -/// 2. The check that the verification key is not a point of small order. -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(try_from = "VerificationKeyBytes"))] -#[cfg_attr(feature = "serde", serde(into = "VerificationKeyBytes"))] -#[cfg_attr(feature = "serde", serde(bound = "T: SigType"))] -pub struct VerificationKey { - pub(crate) point: pallas::Point, - pub(crate) bytes: VerificationKeyBytes, -} - -impl From> for VerificationKeyBytes { - fn from(pk: VerificationKey) -> VerificationKeyBytes { - pk.bytes - } -} - -impl From> for [u8; 32] { - fn from(pk: VerificationKey) -> [u8; 32] { - *pk.bytes.bytes - } -} - -impl TryFrom> for VerificationKey { - type Error = Error; - - fn try_from(bytes: VerificationKeyBytes) -> Result { - // This checks that the encoding is canonical... - let maybe_point = pallas::Affine::from_bytes(&bytes.bytes); - - if maybe_point.is_some().into() { - let point: pallas::Point = maybe_point.unwrap().into(); - - // This checks that the verification key is not of small order. - if !::from(point.is_small_order()) { - Ok(VerificationKey { point, bytes }) - } else { - Err(Error::MalformedVerificationKey) - } - } else { - Err(Error::MalformedVerificationKey) - } - } -} - -impl TryFrom<[u8; 32]> for VerificationKey { - type Error = Error; - - fn try_from(bytes: [u8; 32]) -> Result { - VerificationKeyBytes::from(bytes).try_into() - } -} - -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 super::private::Sealed; - let point = self.point + (SpendAuth::basepoint() * randomizer); - let bytes = VerificationKeyBytes { - bytes: point.to_bytes().into(), - _marker: PhantomData, - }; - VerificationKey { point, bytes } - } -} - -impl VerificationKey { - pub(crate) fn from_scalar(s: &pallas::Scalar) -> VerificationKey { - let point = T::basepoint() * s; - let bytes = VerificationKeyBytes { - bytes: point.to_bytes().into(), - _marker: PhantomData, - }; - VerificationKey { point, bytes } - } - - /// Verify a purported `signature` over `msg` made by this verification key. - // This is similar to impl signature::Verifier but without boxed errors - pub fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), Error> { - let c = HStar::default() - .update(&signature.r_bytes[..]) - .update(&self.bytes.bytes[..]) // XXX ugly - .update(msg) - .finalize(); - self.verify_prehashed(signature, c) - } - - /// Verify a purported `signature` with a prehashed challenge. - #[allow(non_snake_case)] - pub(crate) fn verify_prehashed( - &self, - signature: &Signature, - c: pallas::Scalar, - ) -> Result<(), Error> { - let r = { - // XXX-pasta_curves: should not use CtOption here - let maybe_point = pallas::Affine::from_bytes(&signature.r_bytes); - if maybe_point.is_some().into() { - pallas::Point::from(maybe_point.unwrap()) - } else { - return Err(Error::InvalidSignature); - } - }; - - let s = { - // XXX-pasta_curves: should not use CtOption here - let maybe_scalar = pallas::Scalar::from_repr(*signature.s_bytes); - if maybe_scalar.is_some().into() { - maybe_scalar.unwrap() - } else { - return Err(Error::InvalidSignature); - } - }; - - // 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 check = sB - cA - r; - - if check.is_small_order().into() { - Ok(()) - } else { - Err(Error::InvalidSignature) - } - } -} diff --git a/zebra-chain/src/transaction/arbitrary.rs b/zebra-chain/src/transaction/arbitrary.rs index 57052223..c218ccb6 100644 --- a/zebra-chain/src/transaction/arbitrary.rs +++ b/zebra-chain/src/transaction/arbitrary.rs @@ -12,6 +12,7 @@ use chrono::{TimeZone, Utc}; use proptest::{ arbitrary::any, array, collection::vec, option, prelude::*, test_runner::TestRunner, }; +use reddsa::{orchard::Binding, Signature}; use crate::{ amount::{self, Amount, NegativeAllowed, NonNegative}, @@ -19,10 +20,7 @@ use crate::{ block::{self, arbitrary::MAX_PARTIAL_CHAIN_BLOCKS}, orchard, parameters::{Network, NetworkUpgrade}, - primitives::{ - redpallas::{Binding, Signature}, - Bctv14Proof, Groth16Proof, Halo2Proof, ZkSnarkProof, - }, + primitives::{Bctv14Proof, Groth16Proof, Halo2Proof, ZkSnarkProof}, sapling::{self, AnchorVariant, PerSpendAnchor, SharedAnchor}, serialization::ZcashDeserializeInto, sprout, transparent, @@ -696,7 +694,7 @@ impl Arbitrary for orchard::ShieldedData { any::(), 1..MAX_ARBITRARY_ITEMS, ), - any::>(), + any::(), ) .prop_map( |(flags, value_balance, shared_anchor, proof, actions, binding_sig)| Self { @@ -707,7 +705,7 @@ impl Arbitrary for orchard::ShieldedData { actions: actions .try_into() .expect("arbitrary vector size range produces at least one action"), - binding_sig, + binding_sig: binding_sig.0, }, ) .boxed() @@ -716,7 +714,10 @@ impl Arbitrary for orchard::ShieldedData { type Strategy = BoxedStrategy; } -impl Arbitrary for Signature { +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +struct BindingSignature(pub(crate) Signature); + +impl Arbitrary for BindingSignature { type Parameters = (); fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { @@ -729,7 +730,7 @@ impl Arbitrary for Signature { if b == [0u8; 64] { return None; } - Some(Signature::::from(b)) + Some(BindingSignature(Signature::::from(b))) }, ) .boxed() diff --git a/zebra-chain/src/transaction/serialize.rs b/zebra-chain/src/transaction/serialize.rs index 7e670226..eb4ba90f 100644 --- a/zebra-chain/src/transaction/serialize.rs +++ b/zebra-chain/src/transaction/serialize.rs @@ -6,15 +6,13 @@ use std::{borrow::Borrow, convert::TryInto, io, sync::Arc}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use halo2::pasta::{group::ff::PrimeField, pallas}; use hex::FromHex; +use reddsa::{orchard::Binding, orchard::SpendAuth, Signature}; use crate::{ amount, block::MAX_BLOCK_BYTES, parameters::{OVERWINTER_VERSION_GROUP_ID, SAPLING_VERSION_GROUP_ID, TX_V5_VERSION_GROUP_ID}, - primitives::{ - redpallas::{Binding, Signature, SpendAuth}, - Groth16Proof, Halo2Proof, ZkSnarkProof, - }, + primitives::{Groth16Proof, Halo2Proof, ZkSnarkProof}, serialization::{ zcash_deserialize_external_count, zcash_serialize_empty_list, zcash_serialize_external_count, AtLeastOne, ReadZcashExt, SerializationError, @@ -448,6 +446,19 @@ impl ZcashDeserialize for Option { } } +impl ZcashSerialize for reddsa::Signature { + fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { + writer.write_all(&<[u8; 64]>::from(*self)[..])?; + Ok(()) + } +} + +impl ZcashDeserialize for reddsa::Signature { + fn zcash_deserialize(mut reader: R) -> Result { + Ok(reader.read_64_bytes()?.into()) + } +} + impl ZcashSerialize for Transaction { #[allow(clippy::unwrap_in_result)] fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { diff --git a/zebra-consensus/src/error.rs b/zebra-consensus/src/error.rs index e3372375..aeb7e6b7 100644 --- a/zebra-consensus/src/error.rs +++ b/zebra-consensus/src/error.rs @@ -149,7 +149,7 @@ pub enum TransactionError { #[error("Orchard bindingSig MUST represent a valid signature under the transaction binding validating key bvk of SigHash")] #[cfg_attr(any(test, feature = "proptest-impl"), proptest(skip))] - RedPallas(zebra_chain::primitives::redpallas::Error), + RedPallas(zebra_chain::primitives::reddsa::Error), // temporary error type until #1186 is fixed #[error("Downcast from BoxError to redjubjub::Error failed")] diff --git a/zebra-consensus/src/primitives/redpallas.rs b/zebra-consensus/src/primitives/redpallas.rs index f13d1c05..26f40b61 100644 --- a/zebra-consensus/src/primitives/redpallas.rs +++ b/zebra-consensus/src/primitives/redpallas.rs @@ -17,13 +17,13 @@ use tower::{util::ServiceFn, Service}; use tower_batch::{Batch, BatchControl}; use tower_fallback::Fallback; -use zebra_chain::primitives::redpallas::{batch, *}; +use zebra_chain::primitives::reddsa::{batch, orchard, Error}; #[cfg(test)] mod tests; /// The type of the batch verifier. -type BatchVerifier = batch::Verifier; +type BatchVerifier = batch::Verifier; /// The type of verification results. type VerifyResult = Result<(), Error>; @@ -33,7 +33,7 @@ type Sender = watch::Sender>; /// The type of the batch item. /// This is a `RedPallasItem`. -pub type Item = batch::Item; +pub type Item = batch::Item; /// Global batch verification context for RedPallas signatures. /// diff --git a/zebra-consensus/src/primitives/redpallas/tests.rs b/zebra-consensus/src/primitives/redpallas/tests.rs index beacb3ff..2a49b9a1 100644 --- a/zebra-consensus/src/primitives/redpallas/tests.rs +++ b/zebra-consensus/src/primitives/redpallas/tests.rs @@ -9,6 +9,11 @@ use futures::stream::{FuturesUnordered, StreamExt}; use tower::ServiceExt; use tower_batch::Batch; +use zebra_chain::primitives::reddsa::{ + orchard::{Binding, SpendAuth}, + SigningKey, VerificationKey, +}; + async fn sign_and_verify(mut verifier: V, n: usize) -> Result<(), V::Error> where V: Service, @@ -25,14 +30,17 @@ where let vk = VerificationKey::from(&sk); let sig = sk.sign(&mut rng, &msg[..]); verifier.ready().await?; - results.push(span.in_scope(|| verifier.call((vk.into(), sig, msg).into()))) + results.push( + span.in_scope(|| verifier.call(Item::from_spendauth(vk.into(), sig, msg))), + ) } 1 => { let sk = SigningKey::::new(&mut rng); let vk = VerificationKey::from(&sk); let sig = sk.sign(&mut rng, &msg[..]); verifier.ready().await?; - results.push(span.in_scope(|| verifier.call((vk.into(), sig, msg).into()))) + results + .push(span.in_scope(|| verifier.call(Item::from_binding(vk.into(), sig, msg)))) } _ => panic!(), } diff --git a/zebra-consensus/src/transaction.rs b/zebra-consensus/src/transaction.rs index 7a9b1b55..30516887 100644 --- a/zebra-consensus/src/transaction.rs +++ b/zebra-consensus/src/transaction.rs @@ -1020,7 +1020,7 @@ where // // https://zips.z.cash/protocol/protocol.pdf#actiondesc // - // This is validated by the verifier, inside the [`primitives::redpallas`] module. + // This is validated by the verifier, inside the [`reddsa`] crate. // It calls [`pallas::Affine::from_bytes`] to parse R and // that enforces the canonical encoding. // @@ -1029,11 +1029,13 @@ where // description while adding the resulting future to // our collection of async checks that (at a // minimum) must pass for the transaction to verify. - async_checks.push( - primitives::redpallas::VERIFIER - .clone() - .oneshot((action.rk, spend_auth_sig, &shielded_sighash).into()), - ); + async_checks.push(primitives::redpallas::VERIFIER.clone().oneshot( + primitives::redpallas::Item::from_spendauth( + action.rk, + spend_auth_sig, + &shielded_sighash, + ), + )); } let bvk = orchard_shielded_data.binding_verification_key(); @@ -1062,15 +1064,17 @@ where // // https://zips.z.cash/protocol/protocol.pdf#txnconsensus // - // This is validated by the verifier, inside the `redpallas` crate. + // This is validated by the verifier, inside the `reddsa` crate. // It calls [`pallas::Affine::from_bytes`] to parse R and // that enforces the canonical encoding. - async_checks.push( - primitives::redpallas::VERIFIER - .clone() - .oneshot((bvk, orchard_shielded_data.binding_sig, &shielded_sighash).into()), - ); + async_checks.push(primitives::redpallas::VERIFIER.clone().oneshot( + primitives::redpallas::Item::from_binding( + bvk, + orchard_shielded_data.binding_sig, + &shielded_sighash, + ), + )); } Ok(async_checks)