Change terminology to signing, verification keys (#35)

Matches ed25519-zebra.

Resolves #33
This commit is contained in:
Deirdre Connolly 2020-06-25 14:56:29 -04:00 committed by GitHub
parent c933a8eb85
commit f27b9c3c77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 117 additions and 112 deletions

View File

@ -1,19 +1,20 @@
A minimal [RedJubjub][redjubjub] implementation for use in [Zebra][zebra].
Two parameterizations of RedJubjub are used in Zcash, one for `BindingSig`
and one for `SpendAuthSig`. This library distinguishes these in the type
system, using the [sealed] `SigType` trait as a type-level enum.
Two parameterizations of RedJubjub are used in Zcash, one for
`BindingSig` and one for `SpendAuthSig`. This library distinguishes
these in the type system, using the [sealed] `SigType` trait as a
type-level enum.
In addition to the usual `Signature`, `SecretKey`, `PublicKey` types, the
library also provides `PublicKeyBytes`, a [refinement] of a `[u8; 32]`
indicating that bytes represent an encoding of a RedJubjub public key. This
allows the `PublicKey` type to cache verification checks related to the
public key encoding.
In addition to the `Signature`, `SigningKey`, `VerificationKey` types,
the library also provides `VerificationKeyBytes`, a [refinement] of a
`[u8; 32]` indicating that bytes represent an encoding of a RedJubjub
verification key. This allows the `VerificationKey` type to cache
verification checks related to the verification key encoding.
## Examples
Creating a `BindingSig`, serializing and deserializing it, and verifying the
signature:
Creating a `BindingSig`, serializing and deserializing it, and
verifying the signature:
```
# use std::convert::TryFrom;
@ -23,17 +24,17 @@ use redjubjub::*;
let msg = b"Hello!";
// Generate a secret key and sign the message
let sk = SecretKey::<Binding>::new(thread_rng());
let sk = SigningKey::<Binding>::new(thread_rng());
let sig = sk.sign(thread_rng(), msg);
// Types can be converted to raw byte arrays using From/Into
let sig_bytes: [u8; 64] = sig.into();
let pk_bytes: [u8; 32] = PublicKey::from(&sk).into();
let pk_bytes: [u8; 32] = VerificationKey::from(&sk).into();
// Deserialize and verify the signature.
let sig: Signature<Binding> = sig_bytes.into();
assert!(
PublicKey::try_from(pk_bytes)
VerificationKey::try_from(pk_bytes)
.and_then(|pk| pk.verify(msg, &sig))
.is_ok()
);

View File

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

View File

@ -8,9 +8,9 @@
mod constants;
mod error;
mod hash;
mod public_key;
mod secret_key;
mod signature;
mod signing_key;
mod verification_key;
/// An element of the JubJub scalar field used for randomization of public and secret keys.
pub type Randomizer = jubjub::Fr;
@ -22,9 +22,9 @@ type Scalar = jubjub::Fr;
use hash::HStar;
pub use error::Error;
pub use public_key::{PublicKey, PublicKeyBytes};
pub use secret_key::SecretKey;
pub use signature::Signature;
pub use signing_key::SigningKey;
pub use verification_key::{VerificationKey, VerificationKeyBytes};
/// Abstracts over different RedJubJub parameter choices, [`Binding`]
/// and [`SpendAuth`].

View File

@ -3,34 +3,34 @@ use std::{
marker::PhantomData,
};
use crate::{Error, PublicKey, Randomizer, Scalar, SigType, Signature, SpendAuth};
use crate::{Error, Randomizer, Scalar, SigType, Signature, SpendAuth, VerificationKey};
use rand_core::{CryptoRng, RngCore};
/// A RedJubJub secret key.
/// A RedJubJub 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 SecretKey<T: SigType> {
pub struct SigningKey<T: SigType> {
sk: Scalar,
pk: PublicKey<T>,
pk: VerificationKey<T>,
}
impl<'a, T: SigType> From<&'a SecretKey<T>> for PublicKey<T> {
fn from(sk: &'a SecretKey<T>) -> PublicKey<T> {
impl<'a, T: SigType> From<&'a SigningKey<T>> for VerificationKey<T> {
fn from(sk: &'a SigningKey<T>) -> VerificationKey<T> {
sk.pk.clone()
}
}
impl<T: SigType> From<SecretKey<T>> for [u8; 32] {
fn from(sk: SecretKey<T>) -> [u8; 32] {
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 SecretKey<T> {
impl<T: SigType> TryFrom<[u8; 32]> for SigningKey<T> {
type Error = Error;
fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
@ -38,10 +38,10 @@ impl<T: SigType> TryFrom<[u8; 32]> for SecretKey<T> {
let maybe_sk = Scalar::from_bytes(&bytes);
if maybe_sk.is_some().into() {
let sk = maybe_sk.unwrap();
let pk = PublicKey::from_secret(&sk);
Ok(SecretKey { sk, pk })
let pk = VerificationKey::from(&sk);
Ok(SigningKey { sk, pk })
} else {
Err(Error::MalformedSecretKey)
Err(Error::MalformedSigningKey)
}
}
}
@ -49,7 +49,7 @@ impl<T: SigType> TryFrom<[u8; 32]> for SecretKey<T> {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct SerdeHelper([u8; 32]);
impl<T: SigType> TryFrom<SerdeHelper> for SecretKey<T> {
impl<T: SigType> TryFrom<SerdeHelper> for SigningKey<T> {
type Error = Error;
fn try_from(helper: SerdeHelper) -> Result<Self, Self::Error> {
@ -57,34 +57,34 @@ impl<T: SigType> TryFrom<SerdeHelper> for SecretKey<T> {
}
}
impl<T: SigType> From<SecretKey<T>> for SerdeHelper {
fn from(sk: SecretKey<T>) -> Self {
impl<T: SigType> From<SigningKey<T>> for SerdeHelper {
fn from(sk: SigningKey<T>) -> Self {
Self(sk.into())
}
}
impl SecretKey<SpendAuth> {
impl SigningKey<SpendAuth> {
/// Randomize this public key with the given `randomizer`.
pub fn randomize(&self, randomizer: &Randomizer) -> SecretKey<SpendAuth> {
pub fn randomize(&self, randomizer: &Randomizer) -> SigningKey<SpendAuth> {
let sk = &self.sk + randomizer;
let pk = PublicKey::from_secret(&sk);
SecretKey { sk, pk }
let pk = VerificationKey::from(&sk);
SigningKey { sk, pk }
}
}
impl<T: SigType> SecretKey<T> {
/// Generate a new secret key.
pub fn new<R: RngCore + CryptoRng>(mut rng: R) -> SecretKey<T> {
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);
Scalar::from_bytes_wide(&bytes)
};
let pk = PublicKey::from_secret(&sk);
SecretKey { sk, pk }
let pk = VerificationKey::from(&sk);
SigningKey { sk, pk }
}
/// Create a signature of type `T` on `msg` using this `SecretKey`.
/// Create a signature of type `T` on `msg` using this `SigningKey`.
// Similar to signature::Signer but without boxed errors.
pub fn sign<R: RngCore + CryptoRng>(&self, mut rng: R, msg: &[u8]) -> Signature<T> {
use crate::HStar;

View File

@ -3,126 +3,126 @@ use std::{convert::TryFrom, marker::PhantomData};
use crate::{Error, Randomizer, Scalar, SigType, Signature, SpendAuth};
/// A refinement type for `[u8; 32]` indicating that the bytes represent
/// an encoding of a RedJubJub public key.
/// an encoding of a RedJubJub verification key.
///
/// This is useful for representing a compressed public key; the
/// [`PublicKey`] type in this library holds other decompressed state
/// 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, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PublicKeyBytes<T: SigType> {
pub struct VerificationKeyBytes<T: SigType> {
pub(crate) bytes: [u8; 32],
pub(crate) _marker: PhantomData<T>,
}
impl<T: SigType> From<[u8; 32]> for PublicKeyBytes<T> {
fn from(bytes: [u8; 32]) -> PublicKeyBytes<T> {
PublicKeyBytes {
impl<T: SigType> From<[u8; 32]> for VerificationKeyBytes<T> {
fn from(bytes: [u8; 32]) -> VerificationKeyBytes<T> {
VerificationKeyBytes {
bytes,
_marker: PhantomData,
}
}
}
impl<T: SigType> From<PublicKeyBytes<T>> for [u8; 32] {
fn from(refined: PublicKeyBytes<T>) -> [u8; 32] {
impl<T: SigType> From<VerificationKeyBytes<T>> for [u8; 32] {
fn from(refined: VerificationKeyBytes<T>) -> [u8; 32] {
refined.bytes
}
}
/// A valid RedJubJub public key.
/// A valid RedJubJub verification key.
///
/// This type holds decompressed state used in signature verification; if the
/// public key may not be used immediately, it is probably better to use
/// [`PublicKeyBytes`], which is a refinement type for `[u8; 32]`.
/// 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<PublicKeyBytes>` conversion performs the following Zcash
/// The `TryFrom<VerificationKeyBytes>` conversion performs the following Zcash
/// consensus rule checks:
///
/// 1. The check that the bytes are a canonical encoding of a public key;
/// 2. The check that the public key is not a point of small order.
/// 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 = "PublicKeyBytes<T>"))]
#[cfg_attr(feature = "serde", serde(into = "PublicKeyBytes<T>"))]
#[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 PublicKey<T: SigType> {
pub struct VerificationKey<T: SigType> {
// XXX-jubjub: this should just be Point
pub(crate) point: jubjub::ExtendedPoint,
pub(crate) bytes: PublicKeyBytes<T>,
pub(crate) bytes: VerificationKeyBytes<T>,
}
impl<T: SigType> From<PublicKey<T>> for PublicKeyBytes<T> {
fn from(pk: PublicKey<T>) -> PublicKeyBytes<T> {
impl<T: SigType> From<VerificationKey<T>> for VerificationKeyBytes<T> {
fn from(pk: VerificationKey<T>) -> VerificationKeyBytes<T> {
pk.bytes
}
}
impl<T: SigType> From<PublicKey<T>> for [u8; 32] {
fn from(pk: PublicKey<T>) -> [u8; 32] {
impl<T: SigType> From<VerificationKey<T>> for [u8; 32] {
fn from(pk: VerificationKey<T>) -> [u8; 32] {
pk.bytes.bytes
}
}
impl<T: SigType> TryFrom<PublicKeyBytes<T>> for PublicKey<T> {
impl<T: SigType> TryFrom<VerificationKeyBytes<T>> for VerificationKey<T> {
type Error = Error;
fn try_from(bytes: PublicKeyBytes<T>) -> Result<Self, Self::Error> {
fn try_from(bytes: VerificationKeyBytes<T>) -> Result<Self, Self::Error> {
// XXX-jubjub: this should not use CtOption
// XXX-jubjub: this takes ownership of bytes, while Fr doesn't.
// This checks that the encoding is canonical...
let maybe_point = jubjub::AffinePoint::from_bytes(bytes.bytes);
if maybe_point.is_some().into() {
let point: jubjub::ExtendedPoint = maybe_point.unwrap().into();
// This checks that the public key is not of small order.
// This checks that the verification key is not of small order.
if <bool>::from(point.is_small_order()) == false {
Ok(PublicKey { point, bytes })
Ok(VerificationKey { point, bytes })
} else {
Err(Error::MalformedPublicKey)
Err(Error::MalformedVerificationKey)
}
} else {
Err(Error::MalformedPublicKey)
Err(Error::MalformedVerificationKey)
}
}
}
impl<T: SigType> TryFrom<[u8; 32]> for PublicKey<T> {
impl<T: SigType> TryFrom<[u8; 32]> for VerificationKey<T> {
type Error = Error;
fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
use std::convert::TryInto;
PublicKeyBytes::from(bytes).try_into()
VerificationKeyBytes::from(bytes).try_into()
}
}
impl PublicKey<SpendAuth> {
/// Randomize this public key with the given `randomizer`.
impl VerificationKey<SpendAuth> {
/// Randomize this verification key with the given `randomizer`.
///
/// Randomization is only supported for `SpendAuth` keys.
pub fn randomize(&self, randomizer: &Randomizer) -> PublicKey<SpendAuth> {
pub fn randomize(&self, randomizer: &Randomizer) -> VerificationKey<SpendAuth> {
use crate::private::Sealed;
let point = &self.point + &(&SpendAuth::basepoint() * randomizer);
let bytes = PublicKeyBytes {
let bytes = VerificationKeyBytes {
bytes: jubjub::AffinePoint::from(&point).to_bytes(),
_marker: PhantomData,
};
PublicKey { bytes, point }
VerificationKey { bytes, point }
}
}
impl<T: SigType> PublicKey<T> {
pub(crate) fn from_secret(s: &Scalar) -> PublicKey<T> {
impl<T: SigType> VerificationKey<T> {
pub(crate) fn from(s: &Scalar) -> VerificationKey<T> {
let point = &T::basepoint() * s;
let bytes = PublicKeyBytes {
let bytes = VerificationKeyBytes {
bytes: jubjub::AffinePoint::from(&point).to_bytes(),
_marker: PhantomData,
};
PublicKey { bytes, point }
VerificationKey { bytes, point }
}
/// Verify a purported `signature` over `msg` made by this public key.
/// 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> {
#![allow(non_snake_case)]

View File

@ -9,16 +9,16 @@ proptest! {
fn secretkey_serialization(
bytes in prop::array::uniform32(any::<u8>()),
) {
let sk_result_from = SecretKey::<SpendAuth>::try_from(bytes);
let sk_result_bincode: Result<SecretKey::<SpendAuth>, _>
let sk_result_from = SigningKey::<SpendAuth>::try_from(bytes);
let sk_result_bincode: Result<SigningKey::<SpendAuth>, _>
= bincode::deserialize(&bytes[..]);
// Check 1: both decoding methods should agree
match (sk_result_from, sk_result_bincode) {
// Both agree on success
(Ok(sk_from), Ok(sk_bincode)) => {
let pk_bytes_from = PublicKeyBytes::from(PublicKey::from(&sk_from));
let pk_bytes_bincode = PublicKeyBytes::from(PublicKey::from(&sk_bincode));
let pk_bytes_from = VerificationKeyBytes::from(VerificationKey::from(&sk_from));
let pk_bytes_bincode = VerificationKeyBytes::from(VerificationKey::from(&sk_bincode));
assert_eq!(pk_bytes_from, pk_bytes_bincode);
// Check 2: bincode encoding should match original bytes.
@ -39,8 +39,8 @@ proptest! {
fn publickeybytes_serialization(
bytes in prop::array::uniform32(any::<u8>()),
) {
let pk_bytes_from = PublicKeyBytes::<SpendAuth>::from(bytes);
let pk_bytes_bincode: PublicKeyBytes::<SpendAuth>
let pk_bytes_from = VerificationKeyBytes::<SpendAuth>::from(bytes);
let pk_bytes_bincode: VerificationKeyBytes::<SpendAuth>
= bincode::deserialize(&bytes[..]).unwrap();
// Check 1: both decoding methods should have the same result.
@ -59,8 +59,8 @@ proptest! {
fn publickey_serialization(
bytes in prop::array::uniform32(any::<u8>()),
) {
let pk_result_try_from = PublicKey::<SpendAuth>::try_from(bytes);
let pk_result_bincode: Result<PublicKey::<SpendAuth>, _>
let pk_result_try_from = VerificationKey::<SpendAuth>::try_from(bytes);
let pk_result_bincode: Result<VerificationKey::<SpendAuth>, _>
= bincode::deserialize(&bytes[..]);
// Check 1: both decoding methods should have the same result

View File

@ -8,7 +8,7 @@ use redjubjub::*;
#[test]
fn verify_librustzcash_spendauth() {
for (msg, sig, pk_bytes) in LIBRUSTZCASH_SPENDAUTH_SIGS.iter() {
assert!(PublicKey::try_from(*pk_bytes)
assert!(VerificationKey::try_from(*pk_bytes)
.and_then(|pk| pk.verify(&msg, &sig))
.is_ok());
}
@ -17,14 +17,18 @@ fn verify_librustzcash_spendauth() {
#[test]
fn verify_librustzcash_binding() {
for (msg, sig, pk_bytes) in LIBRUSTZCASH_BINDING_SIGS.iter() {
assert!(PublicKey::try_from(*pk_bytes)
assert!(VerificationKey::try_from(*pk_bytes)
.and_then(|pk| pk.verify(&msg, &sig))
.is_ok());
}
}
lazy_static! {
static ref LIBRUSTZCASH_SPENDAUTH_SIGS: [(Vec<u8>, Signature<SpendAuth>, PublicKeyBytes<SpendAuth>); 32] = [
static ref LIBRUSTZCASH_SPENDAUTH_SIGS: [(
Vec<u8>,
Signature<SpendAuth>,
VerificationKeyBytes<SpendAuth>
); 32] = [
(
[
16, 28, 190, 75, 156, 66, 96, 79, 4, 199, 3, 195, 150, 247, 136, 198, 203, 45, 109,
@ -634,7 +638,7 @@ lazy_static! {
.into(),
),
];
static ref LIBRUSTZCASH_BINDING_SIGS: [(Vec<u8>, Signature<Binding>, PublicKeyBytes<Binding>); 32] = [
static ref LIBRUSTZCASH_BINDING_SIGS: [(Vec<u8>, Signature<Binding>, VerificationKeyBytes<Binding>); 32] = [
(
[
16, 28, 190, 75, 156, 66, 96, 79, 4, 199, 3, 195, 150, 247, 136, 198, 203, 45, 109,

View File

@ -10,7 +10,7 @@ use redjubjub::*;
struct SignatureCase<T: SigType> {
msg: Vec<u8>,
sig: Signature<T>,
pk_bytes: PublicKeyBytes<T>,
pk_bytes: VerificationKeyBytes<T>,
is_valid: bool,
}
@ -37,9 +37,9 @@ enum Tweak {
impl<T: SigType> SignatureCase<T> {
fn new<R: RngCore + CryptoRng>(mut rng: R, msg: Vec<u8>) -> Self {
let sk = SecretKey::new(&mut rng);
let sk = SigningKey::new(&mut rng);
let sig = sk.sign(&mut rng, &msg);
let pk_bytes = PublicKey::from(&sk).into();
let pk_bytes = VerificationKey::from(&sk).into();
Self {
msg,
sig,
@ -58,12 +58,12 @@ impl<T: SigType> SignatureCase<T> {
};
let pk_bytes = {
let bytes: [u8; 32] = self.pk_bytes.into();
PublicKeyBytes::<T>::from(bytes)
VerificationKeyBytes::<T>::from(bytes)
};
// Check that signature validation has the expected result.
self.is_valid
== PublicKey::try_from(pk_bytes)
== VerificationKey::try_from(pk_bytes)
.and_then(|pk| pk.verify(&self.msg, &sig))
.is_ok()
}
@ -142,14 +142,14 @@ proptest! {
Randomizer::from_bytes_wide(&bytes)
};
let sk = SecretKey::<SpendAuth>::new(&mut rng);
let pk = PublicKey::from(&sk);
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] = PublicKeyBytes::from(PublicKey::from(&sk_r)).into();
let pk_r_via_pk_rand: [u8; 32] = PublicKeyBytes::from(pk_r).into();
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

@ -10,6 +10,6 @@ fn smallorder_publickey_fails() {
let order4 = AffinePoint::from_raw_unchecked(Fq::one(), Fq::zero());
assert_eq!(<bool>::from(order4.is_small_order()), true);
let bytes = order4.to_bytes();
let pk_bytes = PublicKeyBytes::<SpendAuth>::from(bytes);
assert!(PublicKey::<SpendAuth>::try_from(pk_bytes).is_err());
let pk_bytes = VerificationKeyBytes::<SpendAuth>::from(bytes);
assert!(VerificationKey::<SpendAuth>::try_from(pk_bytes).is_err());
}