diff --git a/Cargo.toml b/Cargo.toml index 62d2706..a17c9c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,9 +5,13 @@ authors = ["Henry de Valence "] 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 = [] diff --git a/src/error.rs b/src/error.rs index 617773a..d1f2842 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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, } diff --git a/src/hash.rs b/src/hash.rs new file mode 100644 index 0000000..b3beee6 --- /dev/null +++ b/src/hash.rs @@ -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()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 7ca40cf..ca9fabd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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::::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(())); + } } diff --git a/src/public_key.rs b/src/public_key.rs index c6d773c..026c74d 100644 --- a/src/public_key.rs +++ b/src/public_key.rs @@ -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 { - bytes: [u8; 32], - _marker: PhantomData, + pub(crate) bytes: [u8; 32], + pub(crate) _marker: PhantomData, } impl From<[u8; 32]> for PublicKeyBytes { @@ -26,20 +30,16 @@ impl From> for [u8; 32] { } /// A RedJubJub public key. -// XXX PartialEq, Eq? #[derive(Copy, Clone, Debug)] pub struct PublicKey { // 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, + pub(crate) bytes: PublicKeyBytes, } impl From> for PublicKeyBytes { fn from(pk: PublicKey) -> PublicKeyBytes { - let PublicKey { bytes, _marker, .. } = pk; - PublicKeyBytes { bytes, _marker } + pk.bytes } } @@ -53,8 +53,7 @@ impl TryFrom> for PublicKey { 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 TryFrom> for PublicKey { } impl PublicKey { + pub(crate) fn from_secret(s: &Scalar) -> PublicKey { + 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 { unimplemented!(); } -} -impl PublicKey { - /// 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) -> Result<(), Error> { - // this lets us specialize the basepoint parameter, could call a verify_inner - unimplemented!(); - } -} + use crate::HStar; -impl PublicKey { - /// 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) -> 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) + } } } diff --git a/src/secret_key.rs b/src/secret_key.rs index 54a6fdd..ee4c3b6 100644 --- a/src/secret_key.rs +++ b/src/secret_key.rs @@ -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 { - bytes: [u8; 32], - _marker: PhantomData, -} - -impl From<[u8; 32]> for SecretKeyBytes { - fn from(bytes: [u8; 32]) -> SecretKeyBytes { - SecretKeyBytes { - bytes, - _marker: PhantomData, - } - } -} - -impl From> for [u8; 32] { - fn from(refined: SecretKeyBytes) -> [u8; 32] { - refined.bytes - } -} +use rand_core::{CryptoRng, RngCore}; /// A RedJubJub secret key. -// XXX PartialEq, Eq? #[derive(Copy, Clone, Debug)] pub struct SecretKey { sk: Scalar, - _marker: PhantomData, + pk: PublicKey, } -impl From> for SecretKeyBytes { - fn from(sk: SecretKey) -> SecretKeyBytes { - SecretKeyBytes { - bytes: sk.sk.to_bytes(), - _marker: PhantomData, - } +impl<'a, T: SigType> From<&'a SecretKey> for PublicKey { + fn from(sk: &'a SecretKey) -> PublicKey { + 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 TryFrom> for SecretKey { - type Error = Error; - - fn try_from(bytes: SecretKeyBytes) -> Result { - // 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 From> for [u8; 32] { + fn from(sk: SecretKey) -> [u8; 32] { + sk.sk.to_bytes() } } -impl<'a> From<&'a SecretKey> for PublicKey { - fn from(sk: &'a SecretKey) -> PublicKey { - // 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> for PublicKey { - fn from(sk: &'a SecretKey) -> PublicKey { - 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( - sk: &SecretKey, - basepoint: jubjub::ExtendedPoint, -) -> PublicKey { - let point = &basepoint * &sk.sk; - let bytes = jubjub::AffinePoint::from(&point).to_bytes(); - PublicKey { - point, - bytes, - _marker: PhantomData, +impl From<[u8; 32]> for SecretKey { + 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 SecretKey { + /// Generate a new secret key. + pub fn new(mut rng: R) -> SecretKey { + 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 { unimplemented!(); } -} -impl SecretKey { - /// 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 { - // could use sign_inner - unimplemented!(); - } -} - -impl SecretKey { - /// Create a Zcash `SpendAuthSig` on `msg` using this `SecretKey`. - // Similar to signature::Signer but without boxed errors. - pub fn sign(&self, msg: &[u8]) -> Signature { - // could use sign_inner - unimplemented!(); + pub fn sign(&self, mut rng: R, msg: &[u8]) -> Signature { + 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, + } } } diff --git a/src/signature.rs b/src/signature.rs index e902eb1..8035955 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -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 { - bytes: [u8; 64], - _marker: PhantomData, + pub(crate) r_bytes: [u8; 32], + pub(crate) s_bytes: [u8; 32], + pub(crate) _marker: PhantomData, } impl From<[u8; 64]> for Signature { fn from(bytes: [u8; 64]) -> Signature { + let mut r_bytes = [0; 32]; + r_bytes.copy_from_slice(&bytes[0..32]); + let mut s_bytes = [0; 32]; + s_bytes.copy_from_slice(&bytes[32..64]); Signature { - bytes, + r_bytes, + s_bytes, _marker: PhantomData, } } } impl From> for [u8; 64] { - fn from(s: Signature) -> [u8; 64] { - s.bytes - } -} - -// These impls all only exist because of array length restrictions. - -// XXX print the type variable -impl fmt::Debug for Signature { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - //f.debug_tuple("Signature").field(&self.0[..]).finish() - f.debug_tuple("Signature").finish() - } -} - -impl Copy for Signature {} - -impl Clone for Signature { - fn clone(&self) -> Self { + fn from(sig: Signature) -> [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 PartialEq for Signature { - fn eq(&self, other: &Self) -> bool { - self.bytes[..] == other.bytes[..] - } -} - -impl Eq for Signature {}