Merge pull request #11 from ZcashFoundation/signverify

Implement sign, verify operations.
This commit is contained in:
Henry de Valence 2019-12-04 11:51:35 -08:00 committed by GitHub
commit 24d856de2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 221 additions and 163 deletions

View File

@ -5,9 +5,13 @@ authors = ["Henry de Valence <hdevalence@hdevalence.ca>"]
edition = "2018"
[dependencies]
rand_core = "0.5"
thiserror = "1.0"
blake2b_simd = "0.5"
jubjub = { git = "https://github.com/zkcrypto/jubjub", rev = "e83f7d2bd136498a27f9d943fea635d8682bf2c6" }
[dev-dependencies]
rand = "0.7"
[features]
nightly = []

View File

@ -1,7 +1,7 @@
use thiserror::Error;
/// An error related to RedJubJub signatures.
#[derive(Error, Debug)]
#[derive(Error, Debug, Copy, Clone, Eq, PartialEq)]
pub enum Error {
/// The encoding of a secret key was malformed.
#[error("Malformed secret key encoding.")]
@ -9,4 +9,7 @@ pub enum Error {
/// The encoding of a public key was malformed.
#[error("Malformed public key encoding.")]
MalformedPublicKey,
/// Signature verification failed.
#[error("Invalid signature.")]
InvalidSignature,
}

31
src/hash.rs Normal file
View File

@ -0,0 +1,31 @@
use blake2b_simd::{Params, State};
use crate::Scalar;
/// Provides H^star, the hash-to-scalar function used by RedJubjub.
pub struct HStar {
state: State,
}
impl Default for HStar {
fn default() -> Self {
let state = Params::new()
.hash_length(64)
.personal(b"Zcash_RedJubjubH")
.to_state();
Self { state }
}
}
impl HStar {
/// Add `data` to the hash, and return `Self` for chaining.
pub fn update(mut self, data: &[u8]) -> Self {
self.state.update(data);
self
}
/// Consume `self` to compute the hash output.
pub fn finalize(mut self) -> Scalar {
Scalar::from_bytes_wide(self.state.finalize().as_array())
}
}

View File

@ -6,6 +6,7 @@
mod constants;
mod error;
mod hash;
mod public_key;
mod secret_key;
mod signature;
@ -14,11 +15,14 @@ mod signature;
pub type Randomizer = jubjub::Fr;
/// A better name than Fr.
// XXX-jubjub: upstream this name
type Scalar = jubjub::Fr;
use hash::HStar;
pub use error::Error;
pub use public_key::{PublicKey, PublicKeyBytes};
pub use secret_key::{SecretKey, SecretKeyBytes};
pub use secret_key::SecretKey;
pub use signature::Signature;
/// Abstracts over different RedJubJub parameter choices.
@ -35,16 +39,47 @@ pub use signature::Signature;
pub trait SigType: private::Sealed {}
/// A type variable corresponding to Zcash's `BindingSig`.
#[derive(Copy, Clone, Debug)]
pub struct Binding {}
impl SigType for Binding {}
/// A type variable corresponding to Zcash's `SpendAuthSig`.
#[derive(Copy, Clone, Debug)]
pub struct SpendAuth {}
impl SigType for SpendAuth {}
mod private {
pub(crate) mod private {
use super::*;
pub trait Sealed {}
impl Sealed for Binding {}
impl Sealed for SpendAuth {}
pub trait Sealed: Copy + Clone + std::fmt::Debug {
fn basepoint() -> jubjub::ExtendedPoint;
}
impl Sealed for Binding {
fn basepoint() -> jubjub::ExtendedPoint {
jubjub::AffinePoint::from_bytes(constants::BINDINGSIG_BASEPOINT_BYTES)
.unwrap()
.into()
}
}
impl Sealed for SpendAuth {
fn basepoint() -> jubjub::ExtendedPoint {
jubjub::AffinePoint::from_bytes(constants::SPENDAUTHSIG_BASEPOINT_BYTES)
.unwrap()
.into()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sign_and_verify() {
let sk = SecretKey::<Binding>::new(rand::thread_rng());
let msg = b"test";
let sig = sk.sign(rand::thread_rng(), msg);
let pk = PublicKey::from(&sk);
assert_eq!(pk.verify(msg, &sig), Ok(()));
}
}

View File

@ -1,13 +1,17 @@
use std::{convert::TryFrom, marker::PhantomData};
use crate::{Binding, Error, Randomizer, SigType, Signature, SpendAuth};
use crate::{Binding, Error, Randomizer, Scalar, SigType, Signature, SpendAuth};
/// A refinement type indicating that the inner `[u8; 32]` represents an
/// encoding of a RedJubJub public key.
/// A refinement type for `[u8; 32]` indicating that the bytes represent
/// an encoding of a RedJubJub public key.
///
/// This is useful for representing a compressed public key; the
/// [`PublicKey`] type in this library holds other decompressed state
/// used in signature verification.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct PublicKeyBytes<T: SigType> {
bytes: [u8; 32],
_marker: PhantomData<T>,
pub(crate) bytes: [u8; 32],
pub(crate) _marker: PhantomData<T>,
}
impl<T: SigType> From<[u8; 32]> for PublicKeyBytes<T> {
@ -26,20 +30,16 @@ impl<T: SigType> From<PublicKeyBytes<T>> for [u8; 32] {
}
/// A RedJubJub public key.
// XXX PartialEq, Eq?
#[derive(Copy, Clone, Debug)]
pub struct PublicKey<T: SigType> {
// XXX-jubjub: this should just be Point
pub(crate) point: jubjub::ExtendedPoint,
// XXX should this just store a PublicKeyBytes?
pub(crate) bytes: [u8; 32],
pub(crate) _marker: PhantomData<T>,
pub(crate) bytes: PublicKeyBytes<T>,
}
impl<T: SigType> From<PublicKey<T>> for PublicKeyBytes<T> {
fn from(pk: PublicKey<T>) -> PublicKeyBytes<T> {
let PublicKey { bytes, _marker, .. } = pk;
PublicKeyBytes { bytes, _marker }
pk.bytes
}
}
@ -53,8 +53,7 @@ impl<T: SigType> TryFrom<PublicKeyBytes<T>> for PublicKey<T> {
if maybe_point.is_some().into() {
Ok(PublicKey {
point: maybe_point.unwrap().into(),
bytes: bytes.bytes,
_marker: PhantomData,
bytes,
})
} else {
Err(Error::MalformedPublicKey)
@ -63,26 +62,63 @@ impl<T: SigType> TryFrom<PublicKeyBytes<T>> for PublicKey<T> {
}
impl<T: SigType> PublicKey<T> {
pub(crate) fn from_secret(s: &Scalar) -> PublicKey<T> {
let point = &T::basepoint() * s;
let bytes = PublicKeyBytes {
bytes: jubjub::AffinePoint::from(&point).to_bytes(),
_marker: PhantomData,
};
PublicKey { bytes, point }
}
/// Randomize this public key with the given `randomizer`.
pub fn randomize(&self, randomizer: Randomizer) -> PublicKey<T> {
unimplemented!();
}
}
impl PublicKey<Binding> {
/// Verify a Zcash `BindingSig` over `msg` made by this public key.
/// Verify a purported `signature` over `msg` made by this public key.
// This is similar to impl signature::Verifier but without boxed errors
pub fn verify(&self, msg: &[u8], signature: &Signature<Binding>) -> Result<(), Error> {
// this lets us specialize the basepoint parameter, could call a verify_inner
unimplemented!();
}
}
use crate::HStar;
impl PublicKey<SpendAuth> {
/// Verify a Zcash `SpendAuthSig` over `msg` made by this public key.
// This is similar to impl signature::Verifier but without boxed errors
pub fn verify(&self, msg: &[u8], signature: &Signature<SpendAuth>) -> Result<(), Error> {
// this lets us specialize the basepoint parameter, could call a verify_inner
unimplemented!();
let r = {
// XXX-jubjub: should not use CtOption here
// XXX-jubjub: inconsistent ownership in from_bytes
let maybe_point = jubjub::AffinePoint::from_bytes(signature.r_bytes);
if maybe_point.is_some().into() {
jubjub::ExtendedPoint::from(maybe_point.unwrap())
} else {
return Err(Error::InvalidSignature);
}
};
let s = {
// XXX-jubjub: should not use CtOption here
let maybe_scalar = Scalar::from_bytes(&signature.s_bytes);
if maybe_scalar.is_some().into() {
maybe_scalar.unwrap()
} else {
return Err(Error::InvalidSignature);
}
};
let c = HStar::default()
.update(&signature.r_bytes[..])
.update(&self.bytes.bytes[..]) // XXX ugly
.update(msg)
.finalize();
// 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)
}
}
}

View File

@ -1,124 +1,94 @@
use std::{convert::TryFrom, marker::PhantomData};
use crate::{Binding, Error, PublicKey, Randomizer, Scalar, SigType, Signature, SpendAuth};
use crate::{
Binding, Error, PublicKey, PublicKeyBytes, Randomizer, Scalar, SigType, Signature, SpendAuth,
};
/// A refinement type indicating that the inner `[u8; 32]` represents an
/// encoding of a RedJubJub secret key.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct SecretKeyBytes<T: SigType> {
bytes: [u8; 32],
_marker: PhantomData<T>,
}
impl<T: SigType> From<[u8; 32]> for SecretKeyBytes<T> {
fn from(bytes: [u8; 32]) -> SecretKeyBytes<T> {
SecretKeyBytes {
bytes,
_marker: PhantomData,
}
}
}
impl<T: SigType> From<SecretKeyBytes<T>> for [u8; 32] {
fn from(refined: SecretKeyBytes<T>) -> [u8; 32] {
refined.bytes
}
}
use rand_core::{CryptoRng, RngCore};
/// A RedJubJub secret key.
// XXX PartialEq, Eq?
#[derive(Copy, Clone, Debug)]
pub struct SecretKey<T: SigType> {
sk: Scalar,
_marker: PhantomData<T>,
pk: PublicKey<T>,
}
impl<T: SigType> From<SecretKey<T>> for SecretKeyBytes<T> {
fn from(sk: SecretKey<T>) -> SecretKeyBytes<T> {
SecretKeyBytes {
bytes: sk.sk.to_bytes(),
_marker: PhantomData,
}
impl<'a, T: SigType> From<&'a SecretKey<T>> for PublicKey<T> {
fn from(sk: &'a SecretKey<T>) -> PublicKey<T> {
sk.pk.clone()
}
}
// XXX could this be a From impl?
// not unless there's an infallible conversion from bytes to scalars,
// which is not currently present in jubjub
impl<T: SigType> TryFrom<SecretKeyBytes<T>> for SecretKey<T> {
type Error = Error;
fn try_from(bytes: SecretKeyBytes<T>) -> Result<Self, Self::Error> {
// XXX-jubjub: it does not make sense for this to be a CtOption...
// XXX-jubjub: this takes a borrow but point deser doesn't
let maybe_sk = Scalar::from_bytes(&bytes.bytes);
if maybe_sk.is_some().into() {
Ok(SecretKey {
sk: maybe_sk.unwrap(),
_marker: PhantomData,
})
} else {
Err(Error::MalformedSecretKey)
}
impl<T: SigType> From<SecretKey<T>> for [u8; 32] {
fn from(sk: SecretKey<T>) -> [u8; 32] {
sk.sk.to_bytes()
}
}
impl<'a> From<&'a SecretKey<SpendAuth>> for PublicKey<SpendAuth> {
fn from(sk: &'a SecretKey<SpendAuth>) -> PublicKey<SpendAuth> {
// XXX-jubjub: this is pretty baroque
// XXX-jubjub: provide basepoint tables for generators
let basepoint: jubjub::ExtendedPoint =
jubjub::AffinePoint::from_bytes(crate::constants::SPENDAUTHSIG_BASEPOINT_BYTES)
.unwrap()
.into();
pk_from_sk_inner(sk, basepoint)
}
}
impl<'a> From<&'a SecretKey<Binding>> for PublicKey<Binding> {
fn from(sk: &'a SecretKey<Binding>) -> PublicKey<Binding> {
let basepoint: jubjub::ExtendedPoint =
jubjub::AffinePoint::from_bytes(crate::constants::BINDINGSIG_BASEPOINT_BYTES)
.unwrap()
.into();
pk_from_sk_inner(sk, basepoint)
}
}
fn pk_from_sk_inner<T: SigType>(
sk: &SecretKey<T>,
basepoint: jubjub::ExtendedPoint,
) -> PublicKey<T> {
let point = &basepoint * &sk.sk;
let bytes = jubjub::AffinePoint::from(&point).to_bytes();
PublicKey {
point,
bytes,
_marker: PhantomData,
impl<T: SigType> From<[u8; 32]> for SecretKey<T> {
fn from(bytes: [u8; 32]) -> Self {
let sk = {
// XXX-jubjub: would be nice to unconditionally deser
// This incantation ensures deserialization is infallible.
let mut wide = [0; 64];
wide[0..32].copy_from_slice(&bytes);
Scalar::from_bytes_wide(&wide)
};
let pk = PublicKey::from_secret(&sk);
SecretKey { sk, pk }
}
}
impl<T: SigType> SecretKey<T> {
/// Generate a new secret key.
pub fn new<R: RngCore + CryptoRng>(mut rng: R) -> SecretKey<T> {
let sk = {
let mut bytes = [0; 64];
rng.fill_bytes(&mut bytes);
Scalar::from_bytes_wide(&bytes)
};
let pk = PublicKey::from_secret(&sk);
SecretKey { sk, pk }
}
/// Randomize this public key with the given `randomizer`.
pub fn randomize(&self, randomizer: Randomizer) -> PublicKey<T> {
unimplemented!();
}
}
impl SecretKey<Binding> {
/// Create a Zcash `BindingSig` on `msg` using this `SecretKey`.
/// Create a signature of type `T` on `msg` using this `SecretKey`.
// Similar to signature::Signer but without boxed errors.
pub fn sign(&self, msg: &[u8]) -> Signature<Binding> {
// could use sign_inner
unimplemented!();
}
}
impl SecretKey<SpendAuth> {
/// Create a Zcash `SpendAuthSig` on `msg` using this `SecretKey`.
// Similar to signature::Signer but without boxed errors.
pub fn sign(&self, msg: &[u8]) -> Signature<SpendAuth> {
// could use sign_inner
unimplemented!();
pub fn sign<R: RngCore + CryptoRng>(&self, mut rng: R, msg: &[u8]) -> Signature<T> {
use crate::HStar;
// Choose a byte sequence uniformly at random of length
// (\ell_H + 128)/8 bytes. For RedJubjub this is (512 + 128)/8 = 80.
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 = jubjub::AffinePoint::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

@ -3,53 +3,32 @@ use std::{convert, fmt, marker::PhantomData};
use crate::SigType;
/// A RedJubJub signature.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Signature<T: SigType> {
bytes: [u8; 64],
_marker: PhantomData<T>,
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 {
bytes,
r_bytes,
s_bytes,
_marker: PhantomData,
}
}
}
impl<T: SigType> From<Signature<T>> for [u8; 64] {
fn from(s: Signature<T>) -> [u8; 64] {
s.bytes
}
}
// These impls all only exist because of array length restrictions.
// XXX print the type variable
impl<T: SigType> fmt::Debug for Signature<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
//f.debug_tuple("Signature").field(&self.0[..]).finish()
f.debug_tuple("Signature").finish()
}
}
impl<T: SigType> Copy for Signature<T> {}
impl<T: SigType> Clone for Signature<T> {
fn clone(&self) -> Self {
fn from(sig: Signature<T>) -> [u8; 64] {
let mut bytes = [0; 64];
bytes[..].copy_from_slice(&self.bytes[..]);
Signature {
bytes,
_marker: PhantomData,
}
bytes[0..32].copy_from_slice(&sig.r_bytes[..]);
bytes[32..64].copy_from_slice(&sig.s_bytes[..]);
bytes
}
}
impl<T: SigType> PartialEq for Signature<T> {
fn eq(&self, other: &Self) -> bool {
self.bytes[..] == other.bytes[..]
}
}
impl<T: SigType> Eq for Signature<T> {}