mirror of https://github.com/zcash/halo2.git
Structs representing note encryption key material
This commit is contained in:
parent
6823272cfe
commit
8a7ff1b28a
137
src/keys.rs
137
src/keys.rs
|
@ -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() {
|
||||
|
|
34
src/spec.rs
34
src/spec.rs
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue