zebra/zebra-chain/src/orchard/keys.rs

1093 lines
33 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 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 subtle::{Choice, ConstantTimeEq};
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::<Aes256>::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 <bool>::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)]
#[cfg_attr(
any(test, feature = "proptest-impl"),
derive(proptest_derive::Arbitrary)
)]
pub struct SpendingKey {
network: Network,
bytes: [u8; 32],
}
impl ConstantTimeEq for SpendingKey {
/// Check whether two `SpendingKey`s are equal, runtime independent of the
/// value of the secret.
///
/// # Note
///
/// This function short-circuits if the networks of the keys are different.
/// Otherwise, it should execute in time independent of the `bytes` value.
fn ct_eq(&self, other: &Self) -> Choice {
if self.network != other.network {
return Choice::from(0);
}
self.bytes.ct_eq(&other.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 From<SpendingKey> for [u8; 32] {
fn from(sk: SpendingKey) -> Self {
sk.bytes
}
}
impl Eq for SpendingKey {}
impl PartialEq for SpendingKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).unwrap_u8() == 1u8
}
}
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<T>(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 _Action
/// Description_ that spends notes, proving ownership of notes.
///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Copy, Clone)]
pub struct SpendAuthorizingKey(pub(crate) pallas::Scalar);
impl ConstantTimeEq for SpendAuthorizingKey {
/// Check whether two `SpendAuthorizingKey`s are equal, runtime independent
/// of the value of the secret.
fn ct_eq(&self, other: &Self) -> Choice {
self.0.to_bytes().ct_eq(&other.0.to_bytes())
}
}
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<SpendAuthorizingKey> for [u8; 32] {
fn from(sk: SpendAuthorizingKey) -> Self {
sk.0.to_bytes()
}
}
impl From<SpendingKey> 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 Eq for SpendAuthorizingKey {}
impl PartialEq for SpendAuthorizingKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).unwrap_u8() == 1u8
}
}
impl PartialEq<[u8; 32]> for SpendAuthorizingKey {
fn eq(&self, other: &[u8; 32]) -> bool {
self.0.to_bytes().ct_eq(other).unwrap_u8() == 1u8
}
}
/// 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(crate) redpallas::VerificationKey<SpendAuth>);
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<SpendValidatingKey> for [u8; 32] {
fn from(ak: SpendValidatingKey) -> [u8; 32] {
ak.0.into()
}
}
impl From<SpendAuthorizingKey> for SpendValidatingKey {
fn from(ask: SpendAuthorizingKey) -> Self {
let sk = redpallas::SigningKey::<SpendAuth>::try_from(<[u8; 32]>::from(ask)).unwrap();
Self(redpallas::VerificationKey::from(&sk))
}
}
impl PartialEq for SpendValidatingKey {
fn eq(&self, other: &Self) -> bool {
// XXX: These redpallas::VerificationKey(Bytes) fields are pub(crate)
self.0.bytes.bytes == other.0.bytes.bytes
}
}
impl PartialEq<[u8; 32]> for SpendValidatingKey {
fn eq(&self, other: &[u8; 32]) -> bool {
// XXX: These redpallas::VerificationKey(Bytes) fields are pub(crate)
self.0.bytes.bytes == *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)]
pub struct NullifierDerivingKey(pub(crate) pallas::Base);
impl ConstantTimeEq for NullifierDerivingKey {
/// Check whether two `NullifierDerivingKey`s are equal, runtime independent
/// of the value of the secret.
fn ct_eq(&self, other: &Self) -> Choice {
self.0.to_bytes().ct_eq(&other.0.to_bytes())
}
}
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 Eq for NullifierDerivingKey {}
impl From<NullifierDerivingKey> 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<NullifierDerivingKey> 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<SpendingKey> 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 PartialEq for NullifierDerivingKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).unwrap_u8() == 1u8
}
}
impl PartialEq<[u8; 32]> for NullifierDerivingKey {
fn eq(&self, other: &[u8; 32]) -> bool {
self.0.to_bytes().ct_eq(other).unwrap_u8() == 1u8
}
}
/// Commit^ivk randomness.
///
/// <https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents>
// XXX: Should this be replaced by commitment::CommitmentRandomness?
#[derive(Copy, Clone)]
pub struct IvkCommitRandomness(pub(crate) pallas::Scalar);
impl ConstantTimeEq for IvkCommitRandomness {
/// Check whether two `IvkCommitRandomness`s are equal, runtime independent
/// of the value of the secret.
fn ct_eq(&self, other: &Self) -> Choice {
self.0.to_bytes().ct_eq(&other.0.to_bytes())
}
}
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 Eq for IvkCommitRandomness {}
impl From<SpendingKey> 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<IvkCommitRandomness> for [u8; 32] {
fn from(rivk: IvkCommitRandomness) -> Self {
rivk.0.into()
}
}
impl From<IvkCommitRandomness> for pallas::Scalar {
fn from(rivk: IvkCommitRandomness) -> Self {
rivk.0
}
}
impl PartialEq for IvkCommitRandomness {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).unwrap_u8() == 1u8
}
}
impl PartialEq<[u8; 32]> for IvkCommitRandomness {
fn eq(&self, other: &[u8; 32]) -> bool {
self.0.to_bytes().ct_eq(other).unwrap_u8() == 1u8
}
}
impl TryFrom<[u8; 32]> for IvkCommitRandomness {
type Error = &'static str;
fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
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)]
pub struct IncomingViewingKey {
network: Network,
scalar: pallas::Scalar,
}
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(),
}
}
}
impl ConstantTimeEq for IncomingViewingKey {
/// Check whether two `IncomingViewingKey`s are equal, runtime independent
/// of the value of the secret.
///
/// # Note
///
/// This function short-circuits if the networks of the keys are different.
/// Otherwise, it should execute in time independent of the `bytes` value.
fn ct_eq(&self, other: &Self) -> Choice {
if self.network != other.network {
return Choice::from(0);
}
self.scalar.to_bytes().ct_eq(&other.scalar.to_bytes())
}
}
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 Eq for IncomingViewingKey {}
impl From<FullViewingKey> 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<Lsb0, u8> = BitVec::new();
M.extend(<[u8; 32]>::from(fvk.spend_validating_key));
M.extend(<[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 for IncomingViewingKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).unwrap_u8() == 1u8
}
}
impl PartialEq<[u8; 32]> for IncomingViewingKey {
fn eq(&self, other: &[u8; 32]) -> bool {
self.scalar.to_bytes().ct_eq(other).unwrap_u8() == 1u8
}
}
/// 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)]
pub struct FullViewingKey {
network: Network,
spend_validating_key: SpendValidatingKey,
nullifier_deriving_key: NullifierDerivingKey,
ivk_commit_randomness: IvkCommitRandomness,
}
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),
}
}
}
impl ConstantTimeEq for FullViewingKey {
/// Check whether two `FullViewingKey`s are equal, runtime independent of
/// the value of the secrets.
///
/// # Note
///
/// This function short-circuits if the networks or spend validating keys
/// are different. Otherwise, it should execute in time independent of the
/// secret component values.
fn ct_eq(&self, other: &Self) -> Choice {
if self.network != other.network || self.spend_validating_key != other.spend_validating_key
{
return Choice::from(0);
}
// Uses std::ops::BitAnd
self.nullifier_deriving_key
.ct_eq(&other.nullifier_deriving_key)
& self
.ivk_commit_randomness
.ct_eq(&other.ivk_commit_randomness)
}
}
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 PartialEq for FullViewingKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).unwrap_u8() == 1u8
}
}
/// 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)]
pub struct OutgoingViewingKey(pub(crate) [u8; 32]);
impl ConstantTimeEq for OutgoingViewingKey {
/// Check whether two `OutgoingViewingKey`s are equal, runtime independent
/// of the value of the secret.
fn ct_eq(&self, other: &Self) -> Choice {
self.0.ct_eq(&other.0)
}
}
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 Eq for OutgoingViewingKey {}
impl From<[u8; 32]> for OutgoingViewingKey {
/// Generate an `OutgoingViewingKey` from existing bytes.
fn from(bytes: [u8; 32]) -> Self {
Self(bytes)
}
}
impl From<OutgoingViewingKey> for [u8; 32] {
fn from(ovk: OutgoingViewingKey) -> [u8; 32] {
ovk.0
}
}
impl From<FullViewingKey> 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 for OutgoingViewingKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).unwrap_u8() == 1u8
}
}
impl PartialEq<[u8; 32]> for OutgoingViewingKey {
fn eq(&self, other: &[u8; 32]) -> bool {
self.0.ct_eq(other).unwrap_u8() == 1u8
}
}
/// 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)]
pub struct DiversifierKey([u8; 32]);
impl ConstantTimeEq for DiversifierKey {
/// Check whether two `DiversifierKey`s are equal, runtime independent of
/// the value of the secret.
fn ct_eq(&self, other: &Self) -> Choice {
self.0.ct_eq(&other.0)
}
}
impl Eq for DiversifierKey {}
impl From<FullViewingKey> 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<DiversifierKey> for [u8; 32] {
fn from(dk: DiversifierKey) -> [u8; 32] {
dk.0
}
}
impl PartialEq for DiversifierKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).unwrap_u8() == 1u8
}
}
impl PartialEq<[u8; 32]> for DiversifierKey {
fn eq(&self, other: &[u8; 32]) -> bool {
self.0.ct_eq(other).unwrap_u8() == 1u8
}
}
/// 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(crate) [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<DiversifierKey> 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<Diversifier> for [u8; 11] {
fn from(d: Diversifier) -> [u8; 11] {
d.0
}
}
impl From<Diversifier> 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<Diversifier> 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<Self, Self::Error> {
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<T>(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(crate) 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<Coordinates<pallas::Affine>> = 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<TransmissionKey> 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 {
fn eq(&self, other: &[u8; 32]) -> bool {
&self.0.to_bytes() == other
}
}
// TODO: implement EphemeralPrivateKey: #2192
/// 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(crate) 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<Coordinates<pallas::Affine>> = 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 {
fn eq(&self, other: &[u8; 32]) -> bool {
&self.0.to_bytes() == other
}
}
impl TryFrom<[u8; 32]> for EphemeralPublicKey {
type Error = &'static str;
fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
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<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
writer.write_all(&<[u8; 32]>::from(self)[..])?;
Ok(())
}
}
impl ZcashDeserialize for EphemeralPublicKey {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
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