Merge pull request #2099 from ZcashFoundation/redpallas-sig-stub

Flesh out redpallas, direct port of redjubjub
This commit is contained in:
Deirdre Connolly 2021-05-06 10:56:00 -04:00 committed by GitHub
parent c2706f448a
commit 3901dc9adc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 465 additions and 34 deletions

3
Cargo.lock generated
View File

@ -4427,6 +4427,8 @@ dependencies = [
"primitive-types",
"proptest",
"proptest-derive",
"rand 0.8.1",
"rand_chacha 0.3.0",
"rand_core 0.6.2",
"redjubjub",
"ripemd160",
@ -4457,6 +4459,7 @@ dependencies = [
"displaydoc",
"futures 0.3.14",
"futures-util",
"halo2",
"jubjub",
"lazy_static",
"metrics",

View File

@ -59,12 +59,14 @@ zebra-test = { path = "../zebra-test/", optional = true }
bincode = "1"
color-eyre = "0.5.11"
criterion = { version = "0.3", features = ["html_reports"] }
itertools = "0.10.0"
spandoc = "0.2"
tracing = "0.1.26"
proptest = "0.10"
proptest-derive = "0.3"
itertools = "0.10.0"
rand = "0.8"
rand_chacha = "0.3"
zebra-test = { path = "../zebra-test/" }

View File

@ -1,19 +1,35 @@
// XXX: Extracted from redjubjub for now.
// -*- 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>
use group::GroupEncoding;
use halo2::pasta::pallas;
// pub mod batch;
mod constants;
mod error;
// TODO: full redpallas implementation https://github.com/ZcashFoundation/zebra/issues/2044
mod hash;
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`].
///
@ -25,7 +41,7 @@ pub use verification_key::{VerificationKey, VerificationKeyBytes};
/// To handle this, we encode the parameter choice as a genuine type
/// parameter.
///
/// [concretereddsa]: https://zips.z.cash/protocol/protocol.pdf#concretereddsa
/// [concretereddsa]: https://zips.z.cash/protocol/nu5.pdf#concretereddsa
pub trait SigType: private::Sealed {}
/// A type variable corresponding to Zcash's `BindingSig`.

View File

@ -1,6 +1,6 @@
// -*- mode: rust; -*-
//
// This file is part of redjubjub.
// This file is part of redpallas.
// Copyright (c) 2019-2021 Zcash Foundation
// See LICENSE for licensing information.
//
@ -10,13 +10,15 @@
/// 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] = [
215, 148, 162, 4, 167, 65, 231, 17, 216, 7, 4, 206, 68, 161, 32, 20, 67, 192, 174, 143, 131,
35, 240, 117, 113, 113, 7, 198, 56, 190, 133, 53,
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] = [
48, 181, 242, 170, 173, 50, 86, 48, 188, 221, 219, 206, 77, 103, 101, 109, 5, 253, 28, 194,
208, 55, 187, 83, 117, 182, 233, 109, 158, 1, 161, 215,
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,
];

View File

@ -0,0 +1,40 @@
// -*- 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())
}
}

View File

@ -0,0 +1,45 @@
// -*- 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::marker::PhantomData;
use super::SigType;
/// A RedPallas signature.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Signature<T: SigType> {
pub(crate) r_bytes: [u8; 32],
pub(crate) s_bytes: [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,
s_bytes,
_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
}
}

View File

@ -1,9 +1,11 @@
use std::convert::TryFrom;
use std::convert::{TryFrom, TryInto};
use std::marker::PhantomData;
use halo2::arithmetic::FieldExt;
use halo2::pasta::pallas;
use group::GroupEncoding;
use halo2::{arithmetic::FieldExt, pasta::pallas};
use rand_core::{CryptoRng, RngCore};
use super::{Error, SigType, VerificationKey};
use super::{Error, SigType, Signature, SpendAuth, VerificationKey};
/// A RedPallas signing key.
#[derive(Copy, Clone, Debug)]
@ -22,6 +24,12 @@ impl<'a, T: SigType> From<&'a SigningKey<T>> for VerificationKey<T> {
}
}
impl<T: SigType> From<SigningKey<T>> for [u8; 32] {
fn from(sk: SigningKey<T>) -> [u8; 32] {
sk.sk.to_bytes()
}
}
impl<T: SigType> TryFrom<[u8; 32]> for SigningKey<T> {
type Error = Error;
@ -30,10 +38,88 @@ impl<T: SigType> TryFrom<[u8; 32]> for SigningKey<T> {
if maybe_sk.is_some().into() {
let sk = maybe_sk.unwrap();
let pk = VerificationKey::from(&sk);
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_bytes();
Signature {
r_bytes,
s_bytes,
_marker: PhantomData,
}
}
}

View File

@ -0,0 +1,2 @@
mod basepoints;
mod prop;

View File

@ -0,0 +1,22 @@
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
);
}

View File

@ -0,0 +1,151 @@
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>,
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();
Self {
msg,
sig,
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 signature validation has the expected result.
self.is_valid
== VerificationKey::try_from(pk_bytes)
.and_then(|pk| pk.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.
let mut bytes: [u8; 32] = self.pk_bytes.into();
let j = (bytes[2] & 31) as usize;
bytes[2] ^= 0x23;
bytes[2] |= 0x99;
bytes[j] ^= bytes[2];
self.pk_bytes = bytes.into();
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-jubjub: 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);
}
}

View File

@ -1,13 +1,9 @@
use std::{
convert::TryFrom,
// hash::{Hash, Hasher},
marker::PhantomData,
};
use std::{convert::TryFrom, marker::PhantomData};
use group::{cofactor::CofactorGroup, GroupEncoding};
use halo2::pasta::pallas;
use halo2::{arithmetic::FieldExt, pasta::pallas};
use super::{Error, SigType};
use super::*;
/// A refinement type for `[u8; 32]` indicating that the bytes represent
/// an encoding of a RedPallas verification key.
@ -73,18 +69,6 @@ impl<T: SigType> From<VerificationKey<T>> for [u8; 32] {
}
}
impl<T: SigType> From<&pallas::Scalar> for VerificationKey<T> {
fn from(s: &pallas::Scalar) -> VerificationKey<T> {
let point = T::basepoint() * s;
let bytes = VerificationKeyBytes {
bytes: pallas::Affine::from(&point).to_bytes(),
_marker: PhantomData,
};
Self { point, bytes }
}
}
impl<T: SigType> TryFrom<VerificationKeyBytes<T>> for VerificationKey<T> {
type Error = Error;
@ -115,3 +99,81 @@ impl<T: SigType> TryFrom<[u8; 32]> for VerificationKey<T> {
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(),
_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(),
_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_bytes(&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)
}
}
}