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 <durumcrustulum@gmail.com>
This commit is contained in:
parent
b3ee94bc7b
commit
26c86cc088
|
@ -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",
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<SpendAuth>,
|
||||
pub rk: reddsa::VerificationKeyBytes<SpendAuth>,
|
||||
/// 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.
|
||||
|
|
|
@ -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::<note::Nullifier>(),
|
||||
any::<VerificationKeyBytes<SpendAuth>>(),
|
||||
any::<SpendAuthVerificationKeyBytes>(),
|
||||
any::<note::EncryptedNote>(),
|
||||
any::<note::WrappedNoteKey>(),
|
||||
)
|
||||
.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::<Action>(), any::<Signature<SpendAuth>>())
|
||||
(any::<Action>(), any::<SpendAuthSignature>())
|
||||
.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<Self>;
|
||||
}
|
||||
|
||||
impl Arbitrary for Signature<SpendAuth> {
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
struct SpendAuthSignature(pub(crate) Signature<SpendAuth>);
|
||||
|
||||
impl Arbitrary for SpendAuthSignature {
|
||||
type Parameters = ();
|
||||
|
||||
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
|
||||
(array::uniform32(any::<u8>()), array::uniform32(any::<u8>()))
|
||||
.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::<SpendAuth>::from(bytes))
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
|
@ -84,16 +86,25 @@ impl Arbitrary for Signature<SpendAuth> {
|
|||
type Strategy = BoxedStrategy<Self>;
|
||||
}
|
||||
|
||||
impl Arbitrary for VerificationKeyBytes<SpendAuth> {
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
struct SpendAuthVerificationKeyBytes(pub(crate) VerificationKeyBytes<SpendAuth>);
|
||||
|
||||
impl Arbitrary for SpendAuthVerificationKeyBytes {
|
||||
type Parameters = ();
|
||||
|
||||
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
|
||||
// Generate a random signing key from a "seed".
|
||||
(vec(any::<u8>(), 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::<SpendAuth>::from(&sk);
|
||||
SpendAuthVerificationKeyBytes(pk.into())
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
/// <https://zips.z.cash/protocol/protocol.pdf#orchardbalance>
|
||||
pub fn binding_verification_key(&self) -> redpallas::VerificationKeyBytes<Binding> {
|
||||
pub fn binding_verification_key(&self) -> reddsa::VerificationKeyBytes<Binding> {
|
||||
let cv: ValueCommitment = self.actions().map(|action| action.cv).sum();
|
||||
let cv_balance: ValueCommitment =
|
||||
ValueCommitment::new(pallas::Scalar::zero(), self.value_balance);
|
||||
|
|
|
@ -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},
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 <deirdre@zfnd.org>
|
||||
// - Henry de Valence <hdevalence@hdevalence.ca>
|
||||
|
||||
//! RedPallas digital signatures.
|
||||
//!
|
||||
//! RedDSA (a Schnorr-based signature scheme) signatures over the [Pallas][pallas] curve.
|
||||
//!
|
||||
//! <https://zips.z.cash/protocol/protocol.pdf#concretereddsa>
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 <deirdre@zfnd.org>
|
||||
// - Henry de Valence <hdevalence@hdevalence.ca>
|
||||
|
||||
//! 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<R: RngCore + CryptoRng>(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.
|
||||
///
|
||||
/// <https://zips.z.cash/protocol/protocol.pdf#spendauthsig>
|
||||
SpendAuth {
|
||||
vk_bytes: VerificationKeyBytes<SpendAuth>,
|
||||
sig: Signature<SpendAuth>,
|
||||
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`.
|
||||
///
|
||||
/// <https://zips.z.cash/protocol/protocol.pdf#orchardbalance>
|
||||
Binding {
|
||||
vk_bytes: VerificationKeyBytes<Binding>,
|
||||
sig: Signature<Binding>,
|
||||
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<SpendAuth>,
|
||||
Signature<SpendAuth>,
|
||||
&'msg M,
|
||||
)> for Item
|
||||
{
|
||||
fn from(
|
||||
(vk_bytes, sig, msg): (
|
||||
VerificationKeyBytes<SpendAuth>,
|
||||
Signature<SpendAuth>,
|
||||
&'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<Binding>, Signature<Binding>, &'msg M)>
|
||||
for Item
|
||||
{
|
||||
fn from(
|
||||
(vk_bytes, sig, msg): (VerificationKeyBytes<Binding>, Signature<Binding>, &'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::<Binding>::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::<SpendAuth>::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<Item>,
|
||||
}
|
||||
|
||||
impl Verifier {
|
||||
/// Construct a new batch verifier.
|
||||
pub fn new() -> Verifier {
|
||||
Verifier::default()
|
||||
}
|
||||
|
||||
/// Queue an Item for verification.
|
||||
pub fn queue<I: Into<Item>>(&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<R: RngCore + CryptoRng>(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::<SpendAuth>::try_from(*vk_bytes.bytes)?.point
|
||||
}
|
||||
Inner::Binding { vk_bytes, .. } => {
|
||||
VerificationKey::<Binding>::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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 <deirdre@zfnd.org>
|
||||
|
||||
/// 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,
|
||||
];
|
|
@ -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,
|
||||
}
|
|
@ -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 <deirdre@zfnd.org>
|
||||
// - Henry de Valence <hdevalence@hdevalence.ca>
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
|
@ -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 <isis@patternsinthevoid.net>
|
||||
// - Henry de Valence <hdevalence@hdevalence.ca>
|
||||
// - Deirdre Connolly <deirdre@zfnd.org>
|
||||
|
||||
//! 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<I, J>(scalars: I, points: J) -> Option<Self::Point>
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: Borrow<pallas::Scalar>,
|
||||
J: IntoIterator<Item = Option<Self::Point>>;
|
||||
|
||||
/// 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<I, J>(scalars: I, points: J) -> Self::Point
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: Borrow<pallas::Scalar>,
|
||||
J: IntoIterator,
|
||||
J::Item: Borrow<Self::Point>,
|
||||
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<T>(pub(crate) [T; 8]);
|
||||
|
||||
impl<T: Copy> LookupTable5<T> {
|
||||
/// 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<T: Debug> Debug for LookupTable5<T> {
|
||||
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
|
||||
write!(f, "LookupTable5({:?})", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a pallas::Point> for LookupTable5<pallas::Point> {
|
||||
#[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<I, J>(scalars: I, points: J) -> Option<pallas::Point>
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: Borrow<pallas::Scalar>,
|
||||
J: IntoIterator<Item = Option<pallas::Point>>,
|
||||
{
|
||||
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::<pallas::Point>::from(&P)))
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -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 <hdevalence@hdevalence.ca>
|
||||
// - Deirdre Connolly <deirdre@zfnd.org>
|
||||
|
||||
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<T: SigType> {
|
||||
pub(crate) r_bytes: HexDebug<[u8; 32]>,
|
||||
pub(crate) s_bytes: HexDebug<[u8; 32]>,
|
||||
pub(crate) _marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: SigType> From<[u8; 64]> for Signature<T> {
|
||||
fn from(bytes: [u8; 64]) -> Signature<T> {
|
||||
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<T: SigType> From<Signature<T>> for [u8; 64] {
|
||||
fn from(sig: Signature<T>) -> [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<T: SigType> ZcashSerialize for Signature<T> {
|
||||
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||
writer.write_all(&<[u8; 64]>::from(*self)[..])?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: SigType> ZcashDeserialize for Signature<T> {
|
||||
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
||||
Ok(reader.read_64_bytes()?.into())
|
||||
}
|
||||
}
|
|
@ -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<T: SigType> {
|
||||
sk: pallas::Scalar,
|
||||
pk: VerificationKey<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: SigType> From<&'a SigningKey<T>> for VerificationKey<T> {
|
||||
fn from(sk: &'a SigningKey<T>) -> VerificationKey<T> {
|
||||
sk.pk
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: SigType> From<SigningKey<T>> for [u8; 32] {
|
||||
fn from(sk: SigningKey<T>) -> [u8; 32] {
|
||||
sk.sk.to_repr()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: SigType> TryFrom<[u8; 32]> for SigningKey<T> {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
|
||||
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<T: SigType> TryFrom<SerdeHelper> for SigningKey<T> {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(helper: SerdeHelper) -> Result<Self, Self::Error> {
|
||||
helper.0.try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: SigType> From<SigningKey<T>> for SerdeHelper {
|
||||
fn from(sk: SigningKey<T>) -> Self {
|
||||
Self(sk.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl SigningKey<SpendAuth> {
|
||||
/// Randomize this public key with the given `randomizer`.
|
||||
pub fn randomize(&self, randomizer: &pallas::Scalar) -> SigningKey<SpendAuth> {
|
||||
let sk = self.sk + randomizer;
|
||||
let pk = VerificationKey::from_scalar(&sk);
|
||||
SigningKey { sk, pk }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: SigType> SigningKey<T> {
|
||||
/// Generate a new signing key.
|
||||
pub fn new<R: RngCore + CryptoRng>(mut rng: R) -> SigningKey<T> {
|
||||
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`.
|
||||
///
|
||||
/// <https://zips.z.cash/protocol/nu5.pdf#concretereddsa>
|
||||
// Similar to signature::Signer but without boxed errors.
|
||||
pub fn sign<R: RngCore + CryptoRng>(&self, mut rng: R, msg: &[u8]) -> Signature<T> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
mod basepoints;
|
||||
mod batch;
|
||||
mod prop;
|
|
@ -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
|
||||
);
|
||||
}
|
|
@ -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::<SpendAuth>::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::<Binding>::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::<SpendAuth>::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::<Binding>::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::<SpendAuth>::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::<Binding>::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::<SpendAuth>::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::<Binding>::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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<T: SigType> {
|
||||
msg: Vec<u8>,
|
||||
sig: Signature<T>,
|
||||
pk_bytes: VerificationKeyBytes<T>,
|
||||
invalid_pk_bytes: VerificationKeyBytes<T>,
|
||||
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<T: SigType> SignatureCase<T> {
|
||||
fn new<R: RngCore + CryptoRng>(mut rng: R, msg: Vec<u8>) -> 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::<T>::from(bytes)
|
||||
};
|
||||
let pk_bytes = {
|
||||
let bytes: [u8; 32] = self.pk_bytes.into();
|
||||
VerificationKeyBytes::<T>::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<Value = Tweak> {
|
||||
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::<u8>()),
|
||||
) {
|
||||
// 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::<Binding>::new(&mut rng, msg.to_vec());
|
||||
let mut spendauth = SignatureCase::<SpendAuth>::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::<u8>())) {
|
||||
// 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::<SpendAuth>::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);
|
||||
}
|
||||
}
|
|
@ -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<T: SigType> {
|
||||
pub(crate) bytes: HexDebug<[u8; 32]>,
|
||||
pub(crate) _marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: SigType> From<[u8; 32]> for VerificationKeyBytes<T> {
|
||||
fn from(bytes: [u8; 32]) -> VerificationKeyBytes<T> {
|
||||
VerificationKeyBytes {
|
||||
bytes: bytes.into(),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: SigType> From<VerificationKeyBytes<T>> for [u8; 32] {
|
||||
fn from(refined: VerificationKeyBytes<T>) -> [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<VerificationKeyBytes>` 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<T>"))]
|
||||
#[cfg_attr(feature = "serde", serde(into = "VerificationKeyBytes<T>"))]
|
||||
#[cfg_attr(feature = "serde", serde(bound = "T: SigType"))]
|
||||
pub struct VerificationKey<T: SigType> {
|
||||
pub(crate) point: pallas::Point,
|
||||
pub(crate) bytes: VerificationKeyBytes<T>,
|
||||
}
|
||||
|
||||
impl<T: SigType> From<VerificationKey<T>> for VerificationKeyBytes<T> {
|
||||
fn from(pk: VerificationKey<T>) -> VerificationKeyBytes<T> {
|
||||
pk.bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: SigType> From<VerificationKey<T>> for [u8; 32] {
|
||||
fn from(pk: VerificationKey<T>) -> [u8; 32] {
|
||||
*pk.bytes.bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: SigType> TryFrom<VerificationKeyBytes<T>> for VerificationKey<T> {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(bytes: VerificationKeyBytes<T>) -> Result<Self, Self::Error> {
|
||||
// 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 !<bool>::from(point.is_small_order()) {
|
||||
Ok(VerificationKey { point, bytes })
|
||||
} else {
|
||||
Err(Error::MalformedVerificationKey)
|
||||
}
|
||||
} else {
|
||||
Err(Error::MalformedVerificationKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: SigType> TryFrom<[u8; 32]> for VerificationKey<T> {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
|
||||
VerificationKeyBytes::from(bytes).try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl VerificationKey<SpendAuth> {
|
||||
/// Randomize this verification key with the given `randomizer`.
|
||||
///
|
||||
/// Randomization is only supported for `SpendAuth` keys.
|
||||
pub fn randomize(&self, randomizer: &Randomizer) -> VerificationKey<SpendAuth> {
|
||||
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<T: SigType> VerificationKey<T> {
|
||||
pub(crate) fn from_scalar(s: &pallas::Scalar) -> VerificationKey<T> {
|
||||
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<T>) -> 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<T>,
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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::<orchard::shielded_data::AuthorizedAction>(),
|
||||
1..MAX_ARBITRARY_ITEMS,
|
||||
),
|
||||
any::<Signature<Binding>>(),
|
||||
any::<BindingSignature>(),
|
||||
)
|
||||
.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<Self>;
|
||||
}
|
||||
|
||||
impl Arbitrary for Signature<Binding> {
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
struct BindingSignature(pub(crate) Signature<Binding>);
|
||||
|
||||
impl Arbitrary for BindingSignature {
|
||||
type Parameters = ();
|
||||
|
||||
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
|
||||
|
@ -729,7 +730,7 @@ impl Arbitrary for Signature<Binding> {
|
|||
if b == [0u8; 64] {
|
||||
return None;
|
||||
}
|
||||
Some(Signature::<Binding>::from(b))
|
||||
Some(BindingSignature(Signature::<Binding>::from(b)))
|
||||
},
|
||||
)
|
||||
.boxed()
|
||||
|
|
|
@ -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<orchard::ShieldedData> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: reddsa::SigType> ZcashSerialize for reddsa::Signature<T> {
|
||||
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||
writer.write_all(&<[u8; 64]>::from(*self)[..])?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: reddsa::SigType> ZcashDeserialize for reddsa::Signature<T> {
|
||||
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
||||
Ok(reader.read_64_bytes()?.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl ZcashSerialize for Transaction {
|
||||
#[allow(clippy::unwrap_in_result)]
|
||||
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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<orchard::SpendAuth, orchard::Binding>;
|
||||
|
||||
/// The type of verification results.
|
||||
type VerifyResult = Result<(), Error>;
|
||||
|
@ -33,7 +33,7 @@ type Sender = watch::Sender<Option<VerifyResult>>;
|
|||
|
||||
/// The type of the batch item.
|
||||
/// This is a `RedPallasItem`.
|
||||
pub type Item = batch::Item;
|
||||
pub type Item = batch::Item<orchard::SpendAuth, orchard::Binding>;
|
||||
|
||||
/// Global batch verification context for RedPallas signatures.
|
||||
///
|
||||
|
|
|
@ -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<V>(mut verifier: V, n: usize) -> Result<(), V::Error>
|
||||
where
|
||||
V: Service<Item, Response = ()>,
|
||||
|
@ -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::<Binding>::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!(),
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue