Structs representing note encryption key material

This commit is contained in:
Jack Grigg 2021-06-02 22:31:18 +01:00
parent 6823272cfe
commit 8a7ff1b28a
2 changed files with 166 additions and 5 deletions

View File

@ -4,12 +4,15 @@ use std::convert::TryInto;
use std::mem;
use aes::Aes256;
use blake2b_simd::{Hash as Blake2bHash, Params};
use fpe::ff1::{BinaryNumeralString, FF1};
use group::GroupEncoding;
use halo2::arithmetic::FieldExt;
use pasta_curves::pallas;
use rand::RngCore;
use subtle::ConstantTimeEq;
use subtle::CtOption;
use zcash_note_encryption::EphemeralKeyBytes;
use crate::{
address::Address,
@ -20,6 +23,8 @@ use crate::{
},
};
const KDF_ORCHARD_PERSONALIZATION: &[u8; 16] = b"Zcash_OrchardKDF";
/// A spending key, from which all key material is derived.
///
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
@ -424,12 +429,95 @@ impl DiversifiedTransmissionKey {
}
}
/// An ephemeral secret key used to encrypt an output note on-chain.
///
/// `esk` is "ephemeral" in the sense that each secret key is only used once. In
/// practice, `esk` is derived deterministically from the note that it is encrypting.
///
/// $\mathsf{KA}^\mathsf{Orchard}.\mathsf{Private} := \mathbb{F}^{\ast}_{r_P}$
///
/// Defined in [section 5.4.5.5: Orchard Key Agreement][concreteorchardkeyagreement].
///
/// [concreteorchardkeyagreement]: https://zips.z.cash/protocol/nu5.pdf#concreteorchardkeyagreement
#[derive(Debug)]
pub struct EphemeralSecretKey(pub(crate) NonZeroPallasScalar);
impl ConstantTimeEq for EphemeralSecretKey {
fn ct_eq(&self, other: &Self) -> subtle::Choice {
self.0.ct_eq(&other.0)
}
}
impl EphemeralSecretKey {
pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
NonZeroPallasScalar::from_bytes(bytes).map(EphemeralSecretKey)
}
pub(crate) fn derive_public(&self, g_d: NonIdentityPallasPoint) -> EphemeralPublicKey {
EphemeralPublicKey(ka_orchard(&self.0, &g_d))
}
pub(crate) fn agree(&self, pk_d: &DiversifiedTransmissionKey) -> SharedSecret {
SharedSecret(ka_orchard(&self.0, &pk_d.0))
}
}
/// An ephemeral public key used to encrypt an output note on-chain.
///
/// `epk` is "ephemeral" in the sense that each public key is only used once. In practice,
/// `epk` is derived deterministically from the note that it is encrypting.
///
/// $\mathsf{KA}^\mathsf{Orchard}.\mathsf{Public} := \mathbb{P}^{\ast}$
///
/// Defined in [section 5.4.5.5: Orchard Key Agreement][concreteorchardkeyagreement].
///
/// [concreteorchardkeyagreement]: https://zips.z.cash/protocol/nu5.pdf#concreteorchardkeyagreement
#[derive(Debug)]
pub struct EphemeralPublicKey(NonIdentityPallasPoint);
impl EphemeralPublicKey {
pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
NonIdentityPallasPoint::from_bytes(bytes).map(EphemeralPublicKey)
}
pub(crate) fn to_bytes(&self) -> EphemeralKeyBytes {
EphemeralKeyBytes(self.0.to_bytes())
}
pub(crate) fn agree(&self, ivk: &IncomingViewingKey) -> SharedSecret {
SharedSecret(ka_orchard(&ivk.ivk.0, &self.0))
}
}
/// $\mathsf{KA}^\mathsf{Orchard}.\mathsf{SharedSecret} := \mathbb{P}^{\ast}$
///
/// Defined in [section 5.4.5.5: Orchard Key Agreement][concreteorchardkeyagreement].
///
/// [concreteorchardkeyagreement]: https://zips.z.cash/protocol/nu5.pdf#concreteorchardkeyagreement
#[derive(Debug)]
pub struct SharedSecret(NonIdentityPallasPoint);
impl SharedSecret {
/// Defined in [Zcash Protocol Spec § 5.4.5.6: Orchard Key Agreement][concreteorchardkdf].
///
/// [concreteorchardkdf]: https://zips.z.cash/protocol/nu5.pdf#concreteorchardkdf
pub(crate) fn kdf_orchard(self, ephemeral_key: &EphemeralKeyBytes) -> Blake2bHash {
Params::new()
.hash_length(32)
.personal(KDF_ORCHARD_PERSONALIZATION)
.to_state()
.update(&self.0.to_bytes())
.update(&ephemeral_key.0)
.finalize()
}
}
/// Generators for property testing.
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use proptest::prelude::*;
use super::SpendingKey;
use super::{EphemeralSecretKey, SpendingKey};
prop_compose! {
/// Generate a uniformly distributed fake note commitment value.
@ -444,19 +532,64 @@ pub mod testing {
key.unwrap()
}
}
prop_compose! {
/// Generate a uniformly distributed fake note commitment value.
pub fn arb_esk()(
esk in prop::array::uniform32(prop::num::u8::ANY)
.prop_map(|b| EphemeralSecretKey::from_bytes(&b))
.prop_filter(
"Values must correspond to valid Orchard ephemeral secret keys.",
|opt| bool::from(opt.is_some())
)
) -> EphemeralSecretKey {
esk.unwrap()
}
}
}
#[cfg(test)]
mod tests {
use ff::PrimeField;
use proptest::prelude::*;
use super::*;
use super::{
testing::{arb_esk, arb_spending_key},
*,
};
use crate::{
note::{ExtractedNoteCommitment, Nullifier},
value::NoteValue,
Note,
};
#[test]
fn parsers_reject_invalid() {
assert!(bool::from(
EphemeralSecretKey::from_bytes(&[0xff; 32]).is_none()
));
assert!(bool::from(
EphemeralPublicKey::from_bytes(&[0xff; 32]).is_none()
));
}
proptest! {
#[test]
fn key_agreement(
sk in arb_spending_key(),
esk in arb_esk(),
) {
let ivk = IncomingViewingKey::from(&(&sk).into());
let addr = ivk.default_address();
let epk = esk.derive_public(addr.g_d());
assert!(bool::from(
esk.agree(addr.pk_d()).0.ct_eq(&epk.agree(&ivk).0)
));
}
}
#[test]
fn test_vectors() {
for tv in crate::test_vectors::keys::test_vectors() {

View File

@ -4,10 +4,11 @@ use std::iter;
use std::ops::Deref;
use ff::{Field, PrimeField, PrimeFieldBits};
use group::GroupEncoding;
use group::{Curve, Group};
use halo2::arithmetic::{CurveAffine, CurveExt, FieldExt};
use pasta_curves::pallas;
use subtle::CtOption;
use subtle::{ConditionallySelectable, CtOption};
use crate::{
constants::L_ORCHARD_BASE,
@ -18,9 +19,22 @@ mod prf_expand;
pub(crate) use prf_expand::PrfExpand;
/// A Pallas point that is guaranteed to not be the identity.
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct NonIdentityPallasPoint(pallas::Point);
impl ConditionallySelectable for NonIdentityPallasPoint {
fn conditional_select(a: &Self, b: &Self, choice: subtle::Choice) -> Self {
NonIdentityPallasPoint(pallas::Point::conditional_select(&a.0, &b.0, choice))
}
}
impl NonIdentityPallasPoint {
pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
pallas::Point::from_bytes(bytes)
.and_then(|p| CtOption::new(NonIdentityPallasPoint(p), !p.is_identity()))
}
}
impl Deref for NonIdentityPallasPoint {
type Target = pallas::Point;
@ -45,7 +59,7 @@ impl NonZeroPallasBase {
}
/// An integer in [1..r_P].
#[derive(Debug)]
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct NonZeroPallasScalar(pallas::Scalar);
impl From<NonZeroPallasBase> for NonZeroPallasScalar {
@ -54,7 +68,21 @@ impl From<NonZeroPallasBase> for NonZeroPallasScalar {
}
}
impl ConditionallySelectable for NonZeroPallasScalar {
fn conditional_select(a: &Self, b: &Self, choice: subtle::Choice) -> Self {
NonZeroPallasScalar(pallas::Scalar::conditional_select(&a.0, &b.0, choice))
}
}
impl NonZeroPallasScalar {
pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
pallas::Scalar::from_bytes(bytes).and_then(NonZeroPallasScalar::from_scalar)
}
pub(crate) fn from_scalar(s: pallas::Scalar) -> CtOption<Self> {
CtOption::new(NonZeroPallasScalar(s), !s.ct_is_zero())
}
/// Constructs a wrapper for a scalar field element that is guaranteed to be non-zero.
///
/// # Panics