//! Orchard key types. //! //! https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents #![allow(clippy::unit_arg)] #![allow(dead_code)] #[cfg(test)] mod tests; use std::{ convert::{From, Into, TryFrom, TryInto}, fmt, io::{self, Write}, }; use aes::Aes256; use bech32::{self, ToBase32, Variant}; use bitvec::prelude::*; use fpe::ff1::{BinaryNumeralString, FF1}; use group::{Group, GroupEncoding}; use halo2::{ arithmetic::{Coordinates, CurveAffine, FieldExt}, pasta::pallas, }; use rand_core::{CryptoRng, RngCore}; use crate::{ parameters::Network, primitives::redpallas::{self, SpendAuth}, serialization::{ serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize, }, }; use super::sinsemilla::*; /// PRP^d_K(d) := FF1-AES256_K("", d) /// /// "Let FF1-AES256_K(tweak, x) be the FF1 format-preserving encryption /// algorithm using AES with a 256-bit key K, and parameters radix = 2, minlen = /// 88, maxlen = 88. It will be used only with the empty string "" as the /// tweak. x is a sequence of 88 bits, as is the output." /// /// https://zips.z.cash/protocol/nu5.pdf#concreteprps #[allow(non_snake_case)] fn prp_d(K: [u8; 32], d: [u8; 11]) -> [u8; 11] { let radix = 2; let tweak = b""; let ff = FF1::::new(&K, radix).expect("valid radix"); ff.encrypt(tweak, &BinaryNumeralString::from_bytes_le(&d)) .unwrap() .to_bytes_le() .try_into() .unwrap() } /// Invokes Blake2b-512 as PRF^expand with parameter t. /// /// PRF^expand(sk, t) := BLAKE2b-512("Zcash_ExpandSeed", sk || t) /// /// https://zips.z.cash/protocol/nu5.pdf#concreteprfs // TODO: This is basically a duplicate of the one in our sapling module, its // definition in the draft Nu5 spec is incomplete so I'm putting it here in case // it changes. pub fn prf_expand(sk: [u8; 32], t: Vec<&[u8]>) -> [u8; 64] { let mut state = blake2b_simd::Params::new() .hash_length(64) .personal(b"Zcash_ExpandSeed") .to_state(); state.update(&sk[..]); for t_i in t { state.update(t_i); } *state.finalize().as_array() } /// Used to derive the outgoing cipher key _ock_ used to encrypt an encrypted /// output note from an Action. /// /// PRF^ock(ovk, cv, cm_x, ephemeralKey) := BLAKE2b-256(“Zcash_Orchardock”, ovk || cv || cm_x || ephemeralKey) /// /// https://zips.z.cash/protocol/nu5.pdf#concreteprfs /// https://zips.z.cash/protocol/nu5.pdf#concretesym fn prf_ock(ovk: [u8; 32], cv: [u8; 32], cm_x: [u8; 32], ephemeral_key: [u8; 32]) -> [u8; 32] { let hash = blake2b_simd::Params::new() .hash_length(32) .personal(b"Zcash_Orchardock") .to_state() .update(&ovk) .update(&cv) .update(&cm_x) .update(&ephemeral_key) .finalize(); hash.as_bytes().try_into().expect("32 byte array") } /// Used to derive a diversified base point from a diversifier value. /// /// DiversifyHash^Orchard(d) := {︃ GroupHash^P("z.cash:Orchard-gd",""), if P = 0_P /// P, otherwise /// /// where P = GroupHash^P(("z.cash:Orchard-gd", LEBS2OSP_l_d(d))) /// /// https://zips.z.cash/protocol/nu5.pdf#concretediversifyhash fn diversify_hash(d: &[u8]) -> pallas::Point { let p = pallas_group_hash(b"z.cash:Orchard-gd", &d); if ::from(p.is_identity()) { pallas_group_hash(b"z.cash:Orchard-gd", b"") } else { p } } /// Magic human-readable strings used to identify what networks Orchard spending /// keys are associated with when encoded/decoded with bech32. /// /// [orchardspendingkeyencoding]: https://zips.z.cash/protocol/nu5.pdf#orchardspendingkeyencoding mod sk_hrp { pub const MAINNET: &str = "secret-orchard-sk-main"; pub const TESTNET: &str = "secret-orchard-sk-test"; } /// A spending key, as described in [protocol specification §4.2.3][ps]. /// /// Our root secret key of the Orchard key derivation tree. All other Orchard /// key types derive from the [`SpendingKey`] value. /// /// [ps]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents #[derive(Copy, Clone, Debug, Eq, PartialEq)] #[cfg_attr( any(test, feature = "proptest-impl"), derive(proptest_derive::Arbitrary) )] pub struct SpendingKey { network: Network, bytes: [u8; 32], } impl From for [u8; 32] { fn from(sk: SpendingKey) -> Self { sk.bytes } } impl fmt::Display for SpendingKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let hrp = match self.network { Network::Mainnet => sk_hrp::MAINNET, Network::Testnet => sk_hrp::TESTNET, }; bech32::encode_to_fmt(f, hrp, &self.bytes.to_base32(), Variant::Bech32).unwrap() } } impl SpendingKey { /// Generate a new `SpendingKey`. /// /// When generating, we check that the corresponding `SpendAuthorizingKey` /// is not zero, else fail. /// /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents pub fn new(csprng: &mut T, network: Network) -> Self where T: RngCore + CryptoRng, { loop { let mut bytes = [0u8; 32]; csprng.fill_bytes(&mut bytes); let sk = Self::from_bytes(bytes, network); // "if ask = 0, discard this key and repeat with a new sk" if SpendAuthorizingKey::from(sk).0 == pallas::Scalar::zero() { continue; } break sk; } } /// Generate a `SpendingKey` from existing bytes. fn from_bytes(bytes: [u8; 32], network: Network) -> Self { Self { network, bytes } } } /// A Spend authorizing key (_ask_), as described in [protocol specification /// §4.2.3][orchardkeycomponents]. /// /// Used to generate _spend authorization randomizers_ to sign each _Spend /// Description_, proving ownership of notes. /// /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents #[derive(Copy, Clone, Eq, PartialEq)] pub struct SpendAuthorizingKey(pub pallas::Scalar); impl fmt::Debug for SpendAuthorizingKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("SpendAuthorizingKey") .field(&hex::encode(<[u8; 32]>::from(*self))) .finish() } } impl From for [u8; 32] { fn from(sk: SpendAuthorizingKey) -> Self { sk.0.to_bytes() } } impl From for SpendAuthorizingKey { /// Invokes Blake2b-512 as _PRF^expand_, t=6, to derive a /// `SpendAuthorizingKey` from a `SpendingKey`. /// /// ask := ToScalar^Orchard(PRF^expand(sk, [6])) /// /// https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents /// https://zips.z.cash/protocol/nu5.pdf#concreteprfs fn from(spending_key: SpendingKey) -> SpendAuthorizingKey { let hash_bytes = prf_expand(spending_key.bytes, vec![&[6]]); // Handles ToScalar^Orchard Self(pallas::Scalar::from_bytes_wide(&hash_bytes)) } } impl PartialEq<[u8; 32]> for SpendAuthorizingKey { // TODO: do we want to use constant-time comparison here? #[allow(clippy::cmp_owned)] fn eq(&self, other: &[u8; 32]) -> bool { <[u8; 32]>::from(*self) == *other } } /// A Spend validating key, as described in [protocol specification /// §4.2.3][orchardkeycomponents]. /// /// Used to validate Orchard _Spend Authorization Signatures_, proving ownership /// of notes. /// /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents #[derive(Copy, Clone, Debug)] pub struct SpendValidatingKey(pub redpallas::VerificationKey); impl Eq for SpendValidatingKey {} impl From<[u8; 32]> for SpendValidatingKey { fn from(bytes: [u8; 32]) -> Self { Self(redpallas::VerificationKey::try_from(bytes).unwrap()) } } impl From for [u8; 32] { fn from(ak: SpendValidatingKey) -> [u8; 32] { ak.0.into() } } impl From for SpendValidatingKey { fn from(ask: SpendAuthorizingKey) -> Self { let sk = redpallas::SigningKey::::try_from(<[u8; 32]>::from(ask)).unwrap(); Self(redpallas::VerificationKey::from(&sk)) } } impl PartialEq for SpendValidatingKey { #[allow(clippy::cmp_owned)] fn eq(&self, other: &Self) -> bool { <[u8; 32]>::from(self.0) == <[u8; 32]>::from(other.0) } } impl PartialEq<[u8; 32]> for SpendValidatingKey { #[allow(clippy::cmp_owned)] fn eq(&self, other: &[u8; 32]) -> bool { <[u8; 32]>::from(self.0) == *other } } /// A Orchard nullifier deriving key, as described in [protocol specification /// §4.2.3][orchardkeycomponents]. /// /// Used to create a _Nullifier_ per note. /// /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents #[derive(Copy, Clone, PartialEq)] pub struct NullifierDerivingKey(pub pallas::Base); impl fmt::Debug for NullifierDerivingKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("NullifierDerivingKey") .field(&hex::encode(self.0.to_bytes())) .finish() } } impl From for [u8; 32] { fn from(nk: NullifierDerivingKey) -> [u8; 32] { nk.0.to_bytes() } } impl From<&NullifierDerivingKey> for [u8; 32] { fn from(nk: &NullifierDerivingKey) -> [u8; 32] { nk.0.to_bytes() } } impl From for pallas::Base { fn from(nk: NullifierDerivingKey) -> pallas::Base { nk.0 } } impl From<[u8; 32]> for NullifierDerivingKey { fn from(bytes: [u8; 32]) -> Self { Self(pallas::Base::from_bytes(&bytes).unwrap()) } } impl From for NullifierDerivingKey { /// nk = ToBase^Orchard(PRF^expand_sk ([7])) /// /// https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents fn from(sk: SpendingKey) -> Self { Self(pallas::Base::from_bytes_wide(&prf_expand( sk.into(), vec![&[7]], ))) } } impl Eq for NullifierDerivingKey {} impl PartialEq<[u8; 32]> for NullifierDerivingKey { #[allow(clippy::cmp_owned)] fn eq(&self, other: &[u8; 32]) -> bool { <[u8; 32]>::from(*self) == *other } } /// Commit^ivk randomness. /// /// https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents // XXX: Should this be replaced by commitment::CommitmentRandomness? #[derive(Copy, Clone, Eq, PartialEq)] pub struct IvkCommitRandomness(pallas::Scalar); impl fmt::Debug for IvkCommitRandomness { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("IvkCommitRandomness") .field(&hex::encode(self.0.to_bytes())) .finish() } } impl From for IvkCommitRandomness { /// rivk = ToScalar^Orchard(PRF^expand_sk ([8])) /// /// https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents fn from(sk: SpendingKey) -> Self { let scalar = pallas::Scalar::from_bytes_wide(&prf_expand(sk.into(), vec![&[8]])); Self(scalar) } } impl From for [u8; 32] { fn from(rivk: IvkCommitRandomness) -> Self { rivk.0.into() } } impl From for pallas::Scalar { fn from(rivk: IvkCommitRandomness) -> Self { rivk.0 } } impl TryFrom<[u8; 32]> for IvkCommitRandomness { type Error = &'static str; fn try_from(bytes: [u8; 32]) -> Result { let possible_scalar = pallas::Scalar::from_bytes(&bytes); if possible_scalar.is_some().into() { Ok(Self(possible_scalar.unwrap())) } else { Err("Invalid pallas::Scalar value") } } } /// Magic human-readable strings used to identify what networks Orchard incoming /// viewing keys are associated with when encoded/decoded with bech32. /// /// https://zips.z.cash/protocol/nu5.pdf#orchardinviewingkeyencoding mod ivk_hrp { pub const MAINNET: &str = "zivko"; pub const TESTNET: &str = "zivktestorchard"; } /// An incoming viewing key, as described in [protocol specification /// §4.2.3][ps]. /// /// Used to decrypt incoming notes without spending them. /// /// [ps]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents #[derive(Copy, Clone, Eq, PartialEq)] pub struct IncomingViewingKey { network: Network, scalar: pallas::Scalar, } impl fmt::Debug for IncomingViewingKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("IncomingViewingKey") .field(&hex::encode(self.scalar.to_bytes())) .finish() } } impl fmt::Display for IncomingViewingKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let hrp = match self.network { Network::Mainnet => ivk_hrp::MAINNET, Network::Testnet => ivk_hrp::TESTNET, }; bech32::encode_to_fmt(f, hrp, &self.scalar.to_bytes().to_base32(), Variant::Bech32).unwrap() } } impl From for IncomingViewingKey { /// Commit^ivk_rivk(ak, nk) := /// SinsemillaShortCommit_rcm (︁"z.cash:Orchard-CommitIvk", I2LEBSP_l(ak) || I2LEBSP_l(nk)︁) mod r_P /// /// https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents /// https://zips.z.cash/protocol/nu5.pdf#concreteprfs #[allow(non_snake_case)] fn from(fvk: FullViewingKey) -> Self { let mut M: BitVec = BitVec::new(); M.append(&mut BitVec::::from_slice( &<[u8; 32]>::from(fvk.spend_validating_key)[..], )); M.append(&mut BitVec::::from_slice( &<[u8; 32]>::from(fvk.nullifier_deriving_key)[..], )); // Commit^ivk_rivk let commit_x = sinsemilla_short_commit( fvk.ivk_commit_randomness.into(), b"z.cash:Orchard-CommitIvk", &M, ); Self { network: fvk.network, // mod r_P scalar: pallas::Scalar::from_bytes(&commit_x.into()).unwrap(), } } } impl PartialEq<[u8; 32]> for IncomingViewingKey { fn eq(&self, other: &[u8; 32]) -> bool { self.scalar.to_bytes() == *other } } impl IncomingViewingKey { /// Generate an _IncomingViewingKey_ from existing bytes and a network variant. fn from_bytes(bytes: [u8; 32], network: Network) -> Self { Self { network, scalar: pallas::Scalar::from_bytes(&bytes).unwrap(), } } } /// Magic human-readable strings used to identify what networks Orchard full /// viewing keys are associated with when encoded/decoded with bech32. /// /// https://zips.z.cash/protocol/nu5.pdf#orchardfullviewingkeyencoding mod fvk_hrp { pub const MAINNET: &str = "zviewo"; pub const TESTNET: &str = "zviewtestorchard"; } /// Full Viewing Keys /// /// Allows recognizing both incoming and outgoing notes without having /// spend authority. /// /// For incoming viewing keys on the production network, the /// Human-Readable Part is “zviewo”. For incoming viewing keys on the /// test network, the Human-Readable Part is “zviewtestorchard”. /// /// https://zips.z.cash/protocol/nu5.pdf#orchardfullviewingkeyencoding #[derive(Copy, Clone, Eq, PartialEq)] pub struct FullViewingKey { network: Network, spend_validating_key: SpendValidatingKey, nullifier_deriving_key: NullifierDerivingKey, ivk_commit_randomness: IvkCommitRandomness, } impl fmt::Debug for FullViewingKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("FullViewingKey") .field("network", &self.network) .field("spend_validating_key", &self.spend_validating_key) .field("nullifier_deriving_key", &self.nullifier_deriving_key) .field("ivk_commit_randomness", &self.ivk_commit_randomness) .finish() } } impl fmt::Display for FullViewingKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut bytes = io::Cursor::new(Vec::new()); let _ = bytes.write_all(&<[u8; 32]>::from(self.spend_validating_key)); let _ = bytes.write_all(&<[u8; 32]>::from(self.nullifier_deriving_key)); let _ = bytes.write_all(&<[u8; 32]>::from(self.ivk_commit_randomness)); let hrp = match self.network { Network::Mainnet => fvk_hrp::MAINNET, Network::Testnet => fvk_hrp::TESTNET, }; bech32::encode_to_fmt(f, hrp, bytes.get_ref().to_base32(), Variant::Bech32).unwrap() } } impl FullViewingKey { /// [4.2.3]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents #[allow(non_snake_case)] pub fn to_R(self) -> [u8; 64] { // let K = I2LEBSP_l_sk(rivk) let K: [u8; 32] = self.ivk_commit_randomness.into(); let mut t: Vec<&[u8]> = vec![&[0x82u8]]; let ak_bytes = <[u8; 32]>::from(self.spend_validating_key); t.push(&ak_bytes); let nk_bytes = <[u8; 32]>::from(self.nullifier_deriving_key); t.push(&nk_bytes); // let R = PRF^expand_K( [0x82] || I2LEOSP256(ak) || I2LEOSP256(nk) ) prf_expand(K, t) } /// Derive a full viewing key from a existing spending key and its network. /// /// https://zips.z.cash/protocol/nu5.pdf#addressesandkeys /// https://zips.z.cash/protocol/nu5.pdf#orchardfullviewingkeyencoding pub fn from_spending_key(sk: SpendingKey) -> FullViewingKey { let spend_authorizing_key = SpendAuthorizingKey::from(sk); Self { network: sk.network, spend_validating_key: SpendValidatingKey::from(spend_authorizing_key), nullifier_deriving_key: NullifierDerivingKey::from(sk), ivk_commit_randomness: IvkCommitRandomness::from(sk), } } } /// An outgoing viewing key, as described in [protocol specification /// §4.2.3][ps]. /// /// Used to decrypt outgoing notes without spending them. /// /// [ps]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents #[derive(Copy, Clone, Eq, PartialEq)] pub struct OutgoingViewingKey(pub [u8; 32]); impl fmt::Debug for OutgoingViewingKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("OutgoingViewingKey") .field(&hex::encode(&self.0)) .finish() } } impl From<[u8; 32]> for OutgoingViewingKey { /// Generate an `OutgoingViewingKey` from existing bytes. fn from(bytes: [u8; 32]) -> Self { Self(bytes) } } impl From for [u8; 32] { fn from(ovk: OutgoingViewingKey) -> [u8; 32] { ovk.0 } } impl From for OutgoingViewingKey { /// Derive an `OutgoingViewingKey` from a `FullViewingKey`. /// /// [4.2.3]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents #[allow(non_snake_case)] fn from(fvk: FullViewingKey) -> OutgoingViewingKey { let R = fvk.to_R(); // let ovk be the remaining [32] bytes of R [which is 64 bytes] let ovk_bytes: [u8; 32] = R[32..64].try_into().expect("32 byte array"); Self::from(ovk_bytes) } } impl PartialEq<[u8; 32]> for OutgoingViewingKey { fn eq(&self, other: &[u8; 32]) -> bool { self.0 == *other } } /// A _diversifier key_. /// /// "We define a mechanism for deterministically deriving a sequence of /// diversifiers, without leaking how many diversified addresses have already /// been generated for an account. Unlike Sapling, we do so by deriving a /// _diversifier key_ directly from the _full viewing key_, instead of as part /// of the _extended spending key_. This means that the _full viewing key_ /// provides the capability to determine the position of a _diversifier_ within /// the sequence, which matches the capabilities of a Sapling _extended full /// viewing key_ but simplifies the key structure." /// /// [4.2.3]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents /// [ZIP-32]: https://zips.z.cash/zip-0032#orchard-diversifier-derivation #[derive(Copy, Clone, PartialEq)] pub struct DiversifierKey([u8; 32]); impl From for DiversifierKey { /// Derives a _diversifier key_ from a `FullViewingKey`. /// /// 'For each spending key, there is also a default diversified /// payment address with a “random-looking” diversifier. This /// allows an implementation that does not expose diversified /// addresses as a user-visible feature, to use a default address /// that cannot be distinguished (without knowledge of the /// spending key) from one with a random diversifier...' /// /// Derived as specied in section [4.2.3] of the spec, and [ZIP-32]. /// /// [4.2.3]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents /// [ZIP-32]: https://zips.z.cash/zip-0032#orchard-diversifier-derivation #[allow(non_snake_case)] fn from(fvk: FullViewingKey) -> DiversifierKey { let R = fvk.to_R(); // "let dk be the first [32] bytes of R" Self(R[..32].try_into().expect("subslice of R is a valid array")) } } impl From for [u8; 32] { fn from(dk: DiversifierKey) -> [u8; 32] { dk.0 } } /// A _diversifier_, as described in [protocol specification §4.2.3][ps]. /// /// Combined with an `IncomingViewingKey`, produces a _diversified /// payment address_. /// /// [ps]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents #[derive(Copy, Clone, Eq, PartialEq)] #[cfg_attr( any(test, feature = "proptest-impl"), derive(proptest_derive::Arbitrary) )] pub struct Diversifier(pub [u8; 11]); impl fmt::Debug for Diversifier { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("Diversifier") .field(&hex::encode(&self.0)) .finish() } } impl From<[u8; 11]> for Diversifier { fn from(bytes: [u8; 11]) -> Self { Self(bytes) } } impl From for Diversifier { /// Generates the _default diversifier_, where the index into the /// `DiversifierKey` is 0. /// /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents fn from(dk: DiversifierKey) -> Self { Self(prp_d(dk.into(), [0u8; 11])) } } impl From for [u8; 11] { fn from(d: Diversifier) -> [u8; 11] { d.0 } } impl From for pallas::Point { /// Derive a _diversified base_ point. /// /// g_d := DiversifyHash^Orchard(d) /// /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents fn from(d: Diversifier) -> Self { diversify_hash(&d.0) } } impl PartialEq<[u8; 11]> for Diversifier { fn eq(&self, other: &[u8; 11]) -> bool { self.0 == *other } } impl TryFrom for pallas::Affine { type Error = &'static str; /// Get a diversified base point from a diversifier value in affine /// representation. fn try_from(d: Diversifier) -> Result { if let Ok(projective_point) = pallas::Point::try_from(d) { Ok(projective_point.into()) } else { Err("Invalid Diversifier -> pallas::Affine") } } } impl Diversifier { /// Generate a new `Diversifier`. /// /// https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents pub fn new(csprng: &mut T) -> Self where T: RngCore + CryptoRng, { let mut bytes = [0u8; 11]; csprng.fill_bytes(&mut bytes); Self::from(bytes) } } /// A (diversified) transmission Key /// /// In Orchard, secrets need to be transmitted to a recipient of funds in order /// for them to be later spent. To transmit these secrets securely to a /// recipient without requiring an out-of-band communication channel, the /// transmission key is used to encrypt them. /// /// Derived by multiplying a Pallas point [derived][concretediversifyhash] from /// a `Diversifier` by the `IncomingViewingKey` scalar. /// /// [concretediversifyhash]: https://zips.z.cash/protocol/nu5.pdf#concretediversifyhash /// https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents #[derive(Copy, Clone, PartialEq)] pub struct TransmissionKey(pub pallas::Affine); impl fmt::Debug for TransmissionKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut d = f.debug_struct("TransmissionKey"); let option: Option> = self.0.coordinates().into(); match option { Some(coordinates) => d .field("x", &hex::encode(coordinates.x().to_bytes())) .field("y", &hex::encode(coordinates.y().to_bytes())) .finish(), None => d .field("x", &hex::encode(pallas::Base::zero().to_bytes())) .field("y", &hex::encode(pallas::Base::zero().to_bytes())) .finish(), } } } impl Eq for TransmissionKey {} impl From<[u8; 32]> for TransmissionKey { /// Attempts to interpret a byte representation of an affine point, failing /// if the element is not on the curve or non-canonical. /// /// https://github.com/zkcrypto/jubjub/blob/master/src/lib.rs#L411 fn from(bytes: [u8; 32]) -> Self { Self(pallas::Affine::from_bytes(&bytes).unwrap()) } } impl From for [u8; 32] { fn from(pk_d: TransmissionKey) -> [u8; 32] { pk_d.0.to_bytes() } } impl From<(IncomingViewingKey, Diversifier)> for TransmissionKey { /// This includes _KA^Orchard.DerivePublic(ivk, G_d)_, which is just a /// scalar mult _\[ivk\]G_d_. /// /// KA^Orchard.DerivePublic(sk, B) := [sk] B /// /// https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents /// https://zips.z.cash/protocol/nu5.pdf#concreteorchardkeyagreement fn from((ivk, d): (IncomingViewingKey, Diversifier)) -> Self { let g_d = pallas::Point::from(d); Self(pallas::Affine::from(g_d * ivk.scalar)) } } impl PartialEq<[u8; 32]> for TransmissionKey { #[allow(clippy::cmp_owned)] fn eq(&self, other: &[u8; 32]) -> bool { <[u8; 32]>::from(*self) == *other } } /// An ephemeral public key for Orchard key agreement. /// /// https://zips.z.cash/protocol/nu5.pdf#concreteorchardkeyagreement /// https://zips.z.cash/protocol/nu5.pdf#saplingandorchardencrypt #[derive(Copy, Clone, Deserialize, PartialEq, Serialize)] pub struct EphemeralPublicKey(#[serde(with = "serde_helpers::Affine")] pub pallas::Affine); impl fmt::Debug for EphemeralPublicKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut d = f.debug_struct("EphemeralPublicKey"); let option: Option> = self.0.coordinates().into(); match option { Some(coordinates) => d .field("x", &hex::encode(coordinates.x().to_bytes())) .field("y", &hex::encode(coordinates.y().to_bytes())) .finish(), None => d .field("x", &hex::encode(pallas::Base::zero().to_bytes())) .field("y", &hex::encode(pallas::Base::zero().to_bytes())) .finish(), } } } impl Eq for EphemeralPublicKey {} impl From<&EphemeralPublicKey> for [u8; 32] { fn from(epk: &EphemeralPublicKey) -> [u8; 32] { epk.0.to_bytes() } } impl PartialEq<[u8; 32]> for EphemeralPublicKey { #[allow(clippy::cmp_owned)] fn eq(&self, other: &[u8; 32]) -> bool { <[u8; 32]>::from(self) == *other } } impl TryFrom<[u8; 32]> for EphemeralPublicKey { type Error = &'static str; fn try_from(bytes: [u8; 32]) -> Result { let possible_point = pallas::Affine::from_bytes(&bytes); if possible_point.is_some().into() { Ok(Self(possible_point.unwrap())) } else { Err("Invalid pallas::Affine value") } } } impl ZcashSerialize for EphemeralPublicKey { fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { writer.write_all(&<[u8; 32]>::from(self)[..])?; Ok(()) } } impl ZcashDeserialize for EphemeralPublicKey { fn zcash_deserialize(mut reader: R) -> Result { Self::try_from(reader.read_32_bytes()?).map_err(|e| SerializationError::Parse(e)) } } /// An _outgoing cipher key_ for Orchard note encryption/decryption. /// /// https://zips.z.cash/protocol/nu5.pdf#saplingandorchardencrypt #[derive(Copy, Clone, PartialEq)] pub struct OutgoingCipherKey([u8; 32]); impl fmt::Debug for OutgoingCipherKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("OutgoingCipherKey") .field(&hex::encode(self.0)) .finish() } } impl From<&OutgoingCipherKey> for [u8; 32] { fn from(ock: &OutgoingCipherKey) -> [u8; 32] { ock.0 } } // TODO: derive `OutgoingCipherKey`: https://github.com/ZcashFoundation/zebra/issues/2041