2021-01-20 10:54:00 -08:00
|
|
|
//! Key structures for Orchard.
|
|
|
|
|
2021-03-05 15:25:45 -08:00
|
|
|
use std::convert::TryInto;
|
|
|
|
use std::mem;
|
|
|
|
|
2021-03-05 15:36:38 -08:00
|
|
|
use aes::Aes256;
|
2021-06-02 14:31:18 -07:00
|
|
|
use blake2b_simd::{Hash as Blake2bHash, Params};
|
2021-03-05 15:36:38 -08:00
|
|
|
use fpe::ff1::{BinaryNumeralString, FF1};
|
2021-03-05 15:25:45 -08:00
|
|
|
use group::GroupEncoding;
|
2021-03-17 19:06:16 -07:00
|
|
|
use halo2::arithmetic::FieldExt;
|
|
|
|
use pasta_curves::pallas;
|
2021-06-05 14:35:52 -07:00
|
|
|
use rand::RngCore;
|
2021-06-02 14:31:18 -07:00
|
|
|
use subtle::ConstantTimeEq;
|
2021-03-17 12:15:55 -07:00
|
|
|
use subtle::CtOption;
|
2021-06-02 14:31:18 -07:00
|
|
|
use zcash_note_encryption::EphemeralKeyBytes;
|
2021-03-05 15:25:45 -08:00
|
|
|
|
|
|
|
use crate::{
|
|
|
|
address::Address,
|
2021-03-05 16:03:26 -08:00
|
|
|
primitives::redpallas::{self, SpendAuth},
|
2021-03-05 15:25:45 -08:00
|
|
|
spec::{
|
2021-05-28 05:11:54 -07:00
|
|
|
commit_ivk, diversify_hash, extract_p, ka_orchard, prf_nf, to_base, to_scalar,
|
|
|
|
NonIdentityPallasPoint, NonZeroPallasBase, NonZeroPallasScalar, PrfExpand,
|
2021-03-05 15:25:45 -08:00
|
|
|
},
|
|
|
|
};
|
2021-01-20 10:54:00 -08:00
|
|
|
|
2021-06-02 14:31:18 -07:00
|
|
|
const KDF_ORCHARD_PERSONALIZATION: &[u8; 16] = b"Zcash_OrchardKDF";
|
|
|
|
|
2021-01-20 10:54:00 -08:00
|
|
|
/// A spending key, from which all key material is derived.
|
|
|
|
///
|
2021-03-05 17:17:51 -08:00
|
|
|
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
2021-03-05 15:25:45 -08:00
|
|
|
///
|
2021-03-05 17:17:51 -08:00
|
|
|
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
2021-04-27 12:56:36 -07:00
|
|
|
#[derive(Debug, Clone)]
|
2021-03-05 15:25:45 -08:00
|
|
|
pub struct SpendingKey([u8; 32]);
|
|
|
|
|
|
|
|
impl SpendingKey {
|
2021-04-14 21:14:34 -07:00
|
|
|
/// Generates a random spending key.
|
|
|
|
///
|
|
|
|
/// This is only used when generating dummy notes. Real spending keys should be
|
|
|
|
/// derived according to [ZIP 32].
|
|
|
|
///
|
|
|
|
/// [ZIP 32]: https://zips.z.cash/zip-0032
|
|
|
|
pub(crate) fn random(rng: &mut impl RngCore) -> Self {
|
|
|
|
loop {
|
|
|
|
let mut bytes = [0; 32];
|
|
|
|
rng.fill_bytes(&mut bytes);
|
|
|
|
let sk = SpendingKey::from_bytes(bytes);
|
|
|
|
if sk.is_some().into() {
|
|
|
|
break sk.unwrap();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-05 15:25:45 -08:00
|
|
|
/// Constructs an Orchard spending key from uniformly-random bytes.
|
|
|
|
///
|
|
|
|
/// Returns `None` if the bytes do not correspond to a valid Orchard spending key.
|
|
|
|
pub fn from_bytes(sk: [u8; 32]) -> CtOption<Self> {
|
|
|
|
let sk = SpendingKey(sk);
|
2021-04-21 15:28:32 -07:00
|
|
|
// If ask = 0, discard this key. We call `derive_inner` rather than
|
|
|
|
// `SpendAuthorizingKey::from` here because we only need to know
|
|
|
|
// whether ask = 0; the adjustment to potentially negate ask is not
|
|
|
|
// needed. Also, `from` would panic on ask = 0.
|
2021-03-17 12:15:55 -07:00
|
|
|
let ask = SpendAuthorizingKey::derive_inner(&sk);
|
2021-04-19 15:05:56 -07:00
|
|
|
// If ivk = ⊥, discard this key.
|
2021-05-11 03:07:08 -07:00
|
|
|
let ivk = KeyAgreementPrivateKey::derive_inner(&(&sk).into());
|
2021-04-19 15:05:56 -07:00
|
|
|
CtOption::new(sk, !(ask.ct_is_zero() | ivk.is_none()))
|
2021-03-05 15:25:45 -08:00
|
|
|
}
|
|
|
|
}
|
2021-01-20 10:54:00 -08:00
|
|
|
|
2021-03-05 16:58:48 -08:00
|
|
|
/// A spend authorizing key, used to create spend authorization signatures.
|
2021-03-05 15:25:45 -08:00
|
|
|
///
|
2021-03-05 17:17:51 -08:00
|
|
|
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
2021-03-05 15:25:45 -08:00
|
|
|
///
|
2021-03-05 17:17:51 -08:00
|
|
|
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
2021-01-20 10:54:00 -08:00
|
|
|
#[derive(Debug)]
|
2021-04-14 21:14:34 -07:00
|
|
|
pub struct SpendAuthorizingKey(redpallas::SigningKey<SpendAuth>);
|
2021-03-05 15:25:45 -08:00
|
|
|
|
|
|
|
impl SpendAuthorizingKey {
|
|
|
|
/// Derives ask from sk. Internal use only, does not enforce all constraints.
|
|
|
|
fn derive_inner(sk: &SpendingKey) -> pallas::Scalar {
|
2021-05-28 05:11:54 -07:00
|
|
|
to_scalar(PrfExpand::OrchardAsk.expand(&sk.0))
|
2021-03-05 15:25:45 -08:00
|
|
|
}
|
2021-04-14 21:14:34 -07:00
|
|
|
|
2021-06-05 14:35:52 -07:00
|
|
|
/// Randomizes this spend authorizing key with the given `randomizer`.
|
|
|
|
///
|
|
|
|
/// The resulting key can be used to actually sign a spend.
|
|
|
|
pub fn randomize(&self, randomizer: &pallas::Scalar) -> redpallas::SigningKey<SpendAuth> {
|
|
|
|
self.0.randomize(randomizer)
|
2021-04-14 21:14:34 -07:00
|
|
|
}
|
2021-03-05 15:25:45 -08:00
|
|
|
}
|
2021-01-20 10:54:00 -08:00
|
|
|
|
2021-01-21 04:16:50 -08:00
|
|
|
impl From<&SpendingKey> for SpendAuthorizingKey {
|
2021-03-05 15:25:45 -08:00
|
|
|
fn from(sk: &SpendingKey) -> Self {
|
|
|
|
let ask = Self::derive_inner(sk);
|
|
|
|
// SpendingKey cannot be constructed such that this assertion would fail.
|
|
|
|
assert!(!bool::from(ask.ct_is_zero()));
|
|
|
|
// TODO: Add TryFrom<S::Scalar> for SpendAuthorizingKey.
|
|
|
|
let ret = SpendAuthorizingKey(ask.to_bytes().try_into().unwrap());
|
|
|
|
// If the last bit of repr_P(ak) is 1, negate ask.
|
2021-03-08 12:20:09 -08:00
|
|
|
if (<[u8; 32]>::from(SpendValidatingKey::from(&ret).0)[31] >> 7) == 1 {
|
2021-03-05 15:25:45 -08:00
|
|
|
SpendAuthorizingKey((-ask).to_bytes().try_into().unwrap())
|
|
|
|
} else {
|
|
|
|
ret
|
|
|
|
}
|
2021-01-20 10:54:00 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-08 12:20:09 -08:00
|
|
|
/// A key used to validate spend authorization signatures.
|
|
|
|
///
|
|
|
|
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
2021-03-15 13:33:07 -07:00
|
|
|
/// Note that this is $\mathsf{ak}^\mathbb{P}$, which by construction is equivalent to
|
|
|
|
/// $\mathsf{ak}$ but stored here as a RedPallas verification key.
|
2021-03-08 12:20:09 -08:00
|
|
|
///
|
|
|
|
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
2021-04-27 06:49:49 -07:00
|
|
|
#[derive(Debug, Clone)]
|
2021-04-14 21:14:34 -07:00
|
|
|
pub struct SpendValidatingKey(redpallas::VerificationKey<SpendAuth>);
|
2021-01-20 10:54:00 -08:00
|
|
|
|
2021-03-08 12:20:09 -08:00
|
|
|
impl From<&SpendAuthorizingKey> for SpendValidatingKey {
|
2021-03-05 15:25:45 -08:00
|
|
|
fn from(ask: &SpendAuthorizingKey) -> Self {
|
2021-03-08 12:20:09 -08:00
|
|
|
SpendValidatingKey((&ask.0).into())
|
2021-01-20 10:54:00 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-14 21:14:34 -07:00
|
|
|
impl PartialEq for SpendValidatingKey {
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
<[u8; 32]>::from(&self.0).eq(&<[u8; 32]>::from(&other.0))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-14 21:33:15 -07:00
|
|
|
impl SpendValidatingKey {
|
|
|
|
/// Randomizes this spend validating key with the given `randomizer`.
|
|
|
|
pub fn randomize(&self, randomizer: &pallas::Scalar) -> redpallas::VerificationKey<SpendAuth> {
|
|
|
|
self.0.randomize(randomizer)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-05 15:25:45 -08:00
|
|
|
/// A key used to derive [`Nullifier`]s from [`Note`]s.
|
|
|
|
///
|
2021-03-05 17:17:51 -08:00
|
|
|
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
2021-03-05 15:25:45 -08:00
|
|
|
///
|
2021-03-08 12:27:34 -08:00
|
|
|
/// [`Nullifier`]: crate::note::Nullifier
|
|
|
|
/// [`Note`]: crate::note::Note
|
2021-03-05 17:17:51 -08:00
|
|
|
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
2021-04-27 06:49:49 -07:00
|
|
|
#[derive(Debug, Clone)]
|
2021-03-05 15:25:45 -08:00
|
|
|
pub(crate) struct NullifierDerivingKey(pallas::Base);
|
2021-01-20 10:54:00 -08:00
|
|
|
|
2021-01-21 04:16:50 -08:00
|
|
|
impl From<&SpendingKey> for NullifierDerivingKey {
|
2021-03-05 15:25:45 -08:00
|
|
|
fn from(sk: &SpendingKey) -> Self {
|
2021-05-28 05:11:54 -07:00
|
|
|
NullifierDerivingKey(to_base(PrfExpand::OrchardNk.expand(&sk.0)))
|
2021-01-20 10:54:00 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-15 18:27:08 -07:00
|
|
|
impl NullifierDerivingKey {
|
|
|
|
pub(crate) fn prf_nf(&self, rho: pallas::Base) -> pallas::Base {
|
|
|
|
prf_nf(self.0, rho)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-05 17:24:45 -08:00
|
|
|
/// The randomness for $\mathsf{Commit}^\mathsf{ivk}$.
|
|
|
|
///
|
|
|
|
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
|
|
|
///
|
|
|
|
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
2021-04-27 06:49:49 -07:00
|
|
|
#[derive(Debug, Clone)]
|
2021-03-05 17:24:45 -08:00
|
|
|
struct CommitIvkRandomness(pallas::Scalar);
|
|
|
|
|
|
|
|
impl From<&SpendingKey> for CommitIvkRandomness {
|
|
|
|
fn from(sk: &SpendingKey) -> Self {
|
2021-05-28 05:11:54 -07:00
|
|
|
CommitIvkRandomness(to_scalar(PrfExpand::OrchardRivk.expand(&sk.0)))
|
2021-03-05 17:24:45 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-20 10:54:00 -08:00
|
|
|
/// A key that provides the capability to view incoming and outgoing transactions.
|
|
|
|
///
|
2021-02-03 06:19:29 -08:00
|
|
|
/// This key is useful anywhere you need to maintain accurate balance, but do not want the
|
|
|
|
/// ability to spend funds (such as a view-only wallet).
|
2021-03-08 12:22:38 -08:00
|
|
|
///
|
|
|
|
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
|
|
|
///
|
|
|
|
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
2021-04-27 06:49:49 -07:00
|
|
|
#[derive(Debug, Clone)]
|
2021-01-21 04:16:50 -08:00
|
|
|
pub struct FullViewingKey {
|
2021-03-08 12:20:09 -08:00
|
|
|
ak: SpendValidatingKey,
|
2021-01-21 04:16:50 -08:00
|
|
|
nk: NullifierDerivingKey,
|
2021-03-05 17:24:45 -08:00
|
|
|
rivk: CommitIvkRandomness,
|
2021-01-20 10:54:00 -08:00
|
|
|
}
|
|
|
|
|
2021-01-21 04:16:50 -08:00
|
|
|
impl From<&SpendingKey> for FullViewingKey {
|
2021-03-05 15:25:45 -08:00
|
|
|
fn from(sk: &SpendingKey) -> Self {
|
|
|
|
FullViewingKey {
|
|
|
|
ak: (&SpendAuthorizingKey::from(sk)).into(),
|
|
|
|
nk: sk.into(),
|
2021-03-05 17:24:45 -08:00
|
|
|
rivk: sk.into(),
|
2021-03-05 15:25:45 -08:00
|
|
|
}
|
2021-01-20 10:54:00 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-14 21:34:51 -07:00
|
|
|
impl From<FullViewingKey> for SpendValidatingKey {
|
|
|
|
fn from(fvk: FullViewingKey) -> Self {
|
|
|
|
fvk.ak
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-21 04:16:50 -08:00
|
|
|
impl FullViewingKey {
|
2021-03-15 18:27:08 -07:00
|
|
|
pub(crate) fn nk(&self) -> &NullifierDerivingKey {
|
|
|
|
&self.nk
|
|
|
|
}
|
|
|
|
|
2021-03-05 17:17:51 -08:00
|
|
|
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
2021-03-05 15:25:45 -08:00
|
|
|
///
|
2021-03-05 17:17:51 -08:00
|
|
|
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
2021-03-05 15:25:45 -08:00
|
|
|
fn derive_dk_ovk(&self) -> (DiversifierKey, OutgoingViewingKey) {
|
2021-03-05 17:24:45 -08:00
|
|
|
let k = self.rivk.0.to_bytes();
|
2021-03-05 16:03:26 -08:00
|
|
|
let b = [(&self.ak.0).into(), self.nk.0.to_bytes()];
|
2021-05-28 05:11:54 -07:00
|
|
|
let r = PrfExpand::OrchardDkOvk.with_ad_slices(&k, &[&b[0][..], &b[1][..]]);
|
2021-03-05 15:25:45 -08:00
|
|
|
(
|
2021-03-08 13:33:56 -08:00
|
|
|
DiversifierKey(r[..32].try_into().unwrap()),
|
|
|
|
OutgoingViewingKey(r[32..].try_into().unwrap()),
|
2021-03-05 15:25:45 -08:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the default payment address for this key.
|
|
|
|
pub fn default_address(&self) -> Address {
|
2021-05-11 03:07:08 -07:00
|
|
|
IncomingViewingKey::from(self).default_address()
|
2021-03-15 14:01:50 -07:00
|
|
|
}
|
|
|
|
|
2021-03-15 13:04:36 -07:00
|
|
|
/// Returns the payment address for this key at the given index.
|
2021-03-17 12:15:55 -07:00
|
|
|
pub fn address_at(&self, j: impl Into<DiversifierIndex>) -> Address {
|
2021-05-11 03:07:08 -07:00
|
|
|
IncomingViewingKey::from(self).address_at(j)
|
2021-03-15 13:04:36 -07:00
|
|
|
}
|
|
|
|
|
2021-01-20 10:54:00 -08:00
|
|
|
/// Returns the payment address for this key corresponding to the given diversifier.
|
2021-03-17 12:15:55 -07:00
|
|
|
pub fn address(&self, d: Diversifier) -> Address {
|
2021-05-11 03:07:08 -07:00
|
|
|
// Shortcut: we don't need to derive DiversifierKey.
|
|
|
|
KeyAgreementPrivateKey::from(self).address(d)
|
2021-01-20 10:54:00 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-05 15:25:45 -08:00
|
|
|
/// A key that provides the capability to derive a sequence of diversifiers.
|
2021-03-08 12:22:38 -08:00
|
|
|
///
|
|
|
|
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
|
|
|
///
|
|
|
|
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
2021-03-05 15:25:45 -08:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct DiversifierKey([u8; 32]);
|
|
|
|
|
|
|
|
impl From<&FullViewingKey> for DiversifierKey {
|
|
|
|
fn from(fvk: &FullViewingKey) -> Self {
|
|
|
|
fvk.derive_dk_ovk().0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The index for a particular diversifier.
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
|
|
pub struct DiversifierIndex([u8; 11]);
|
|
|
|
|
|
|
|
macro_rules! di_from {
|
|
|
|
($n:ident) => {
|
|
|
|
impl From<$n> for DiversifierIndex {
|
|
|
|
fn from(j: $n) -> Self {
|
|
|
|
let mut j_bytes = [0; 11];
|
|
|
|
j_bytes[..mem::size_of::<$n>()].copy_from_slice(&j.to_le_bytes());
|
|
|
|
DiversifierIndex(j_bytes)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
di_from!(u32);
|
|
|
|
di_from!(u64);
|
|
|
|
di_from!(usize);
|
|
|
|
|
|
|
|
impl DiversifierKey {
|
|
|
|
/// Returns the diversifier at index 0.
|
|
|
|
pub fn default_diversifier(&self) -> Diversifier {
|
2021-03-08 10:39:25 -08:00
|
|
|
self.get(0u32)
|
2021-03-05 15:25:45 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the diversifier at the given index.
|
2021-03-05 15:36:38 -08:00
|
|
|
pub fn get(&self, j: impl Into<DiversifierIndex>) -> Diversifier {
|
|
|
|
let ff = FF1::<Aes256>::new(&self.0, 2).expect("valid radix");
|
|
|
|
let enc = ff
|
|
|
|
.encrypt(&[], &BinaryNumeralString::from_bytes_le(&j.into().0[..]))
|
|
|
|
.unwrap();
|
|
|
|
Diversifier(enc.to_bytes_le().try_into().unwrap())
|
2021-03-05 15:25:45 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-20 10:54:00 -08:00
|
|
|
/// A diversifier that can be used to derive a specific [`Address`] from a
|
|
|
|
/// [`FullViewingKey`] or [`IncomingViewingKey`].
|
2021-03-08 12:22:38 -08:00
|
|
|
///
|
|
|
|
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
|
|
|
///
|
|
|
|
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
2021-06-10 11:19:08 -07:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
2021-02-03 06:16:58 -08:00
|
|
|
pub struct Diversifier([u8; 11]);
|
2021-01-20 10:54:00 -08:00
|
|
|
|
2021-03-12 16:04:13 -08:00
|
|
|
impl Diversifier {
|
2021-06-02 15:22:22 -07:00
|
|
|
pub(crate) fn from_bytes(d: [u8; 11]) -> Self {
|
|
|
|
Diversifier(d)
|
|
|
|
}
|
|
|
|
|
2021-03-12 16:04:13 -08:00
|
|
|
/// Returns the byte array corresponding to this diversifier.
|
|
|
|
pub fn as_array(&self) -> &[u8; 11] {
|
|
|
|
&self.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-11 03:07:08 -07:00
|
|
|
/// The private key $\mathsf{ivk}$ used in $KA^{Orchard}$, for decrypting incoming notes.
|
|
|
|
///
|
|
|
|
/// In Sapling this is what was encoded as an incoming viewing key. For Orchard, we store
|
|
|
|
/// both this and [`DiversifierKey`] inside [`IncomingViewingKey`] for usability (to
|
|
|
|
/// enable deriving the default address for an incoming viewing key), while this separate
|
|
|
|
/// type represents $\mathsf{ivk}$.
|
|
|
|
///
|
|
|
|
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
|
|
|
///
|
|
|
|
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
|
|
|
///
|
|
|
|
/// # Implementation notes
|
|
|
|
///
|
|
|
|
/// We store $\mathsf{ivk}$ in memory as a scalar instead of a base, so that we aren't
|
|
|
|
/// incurring an expensive serialize-and-parse step every time we use it (e.g. for trial
|
|
|
|
/// decryption of notes). When we actually want to serialize ivk, we're guaranteed to get
|
|
|
|
/// a valid base field element encoding, because we always construct ivk from an integer
|
|
|
|
/// in the correct range.
|
|
|
|
#[derive(Debug)]
|
|
|
|
struct KeyAgreementPrivateKey(NonZeroPallasScalar);
|
|
|
|
|
|
|
|
impl From<&FullViewingKey> for KeyAgreementPrivateKey {
|
|
|
|
fn from(fvk: &FullViewingKey) -> Self {
|
|
|
|
// KeyAgreementPrivateKey cannot be constructed such that this unwrap would fail.
|
|
|
|
let ivk = KeyAgreementPrivateKey::derive_inner(fvk).unwrap();
|
|
|
|
KeyAgreementPrivateKey(ivk.into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl KeyAgreementPrivateKey {
|
2021-05-21 13:24:08 -07:00
|
|
|
/// Derives ivk from fvk. Internal use only, does not enforce all constraints.
|
2021-05-11 03:07:08 -07:00
|
|
|
fn derive_inner(fvk: &FullViewingKey) -> CtOption<NonZeroPallasBase> {
|
|
|
|
let ak = extract_p(&pallas::Point::from_bytes(&(&fvk.ak.0).into()).unwrap());
|
|
|
|
commit_ivk(&ak, &fvk.nk.0, &fvk.rivk.0)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the payment address for this key corresponding to the given diversifier.
|
|
|
|
fn address(&self, d: Diversifier) -> Address {
|
2021-06-02 15:22:22 -07:00
|
|
|
let pk_d = DiversifiedTransmissionKey::derive_inner(self, &d);
|
2021-05-11 03:07:08 -07:00
|
|
|
Address::from_parts(d, pk_d)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-20 10:54:00 -08:00
|
|
|
/// A key that provides the capability to detect and decrypt incoming notes from the block
|
|
|
|
/// chain, without being able to spend the notes or detect when they are spent.
|
|
|
|
///
|
|
|
|
/// This key is useful in situations where you only need the capability to detect inbound
|
|
|
|
/// payments, such as merchant terminals.
|
|
|
|
///
|
2021-02-08 07:01:34 -08:00
|
|
|
/// This key is not suitable for use on its own in a wallet, as it cannot maintain
|
|
|
|
/// accurate balance. You should use a [`FullViewingKey`] instead.
|
2021-03-08 12:22:38 -08:00
|
|
|
///
|
2021-05-11 03:07:08 -07:00
|
|
|
/// Defined in [Zcash Protocol Spec § 5.6.4.3: Orchard Raw Incoming Viewing Keys][orchardinviewingkeyencoding].
|
2021-03-08 12:22:38 -08:00
|
|
|
///
|
2021-05-11 03:07:08 -07:00
|
|
|
/// [orchardinviewingkeyencoding]: https://zips.z.cash/protocol/nu5.pdf#orchardinviewingkeyencoding
|
2021-01-20 10:54:00 -08:00
|
|
|
#[derive(Debug)]
|
2021-05-11 03:07:08 -07:00
|
|
|
pub struct IncomingViewingKey {
|
|
|
|
dk: DiversifierKey,
|
|
|
|
ivk: KeyAgreementPrivateKey,
|
|
|
|
}
|
2021-01-20 10:54:00 -08:00
|
|
|
|
2021-01-21 04:16:50 -08:00
|
|
|
impl From<&FullViewingKey> for IncomingViewingKey {
|
2021-03-05 15:25:45 -08:00
|
|
|
fn from(fvk: &FullViewingKey) -> Self {
|
2021-05-11 03:07:08 -07:00
|
|
|
IncomingViewingKey {
|
|
|
|
dk: fvk.into(),
|
|
|
|
ivk: fvk.into(),
|
|
|
|
}
|
2021-01-20 10:54:00 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-21 04:16:50 -08:00
|
|
|
impl IncomingViewingKey {
|
2021-06-10 11:19:08 -07:00
|
|
|
/// Parses an Orchard incoming viewing key from its raw encoding.
|
|
|
|
pub fn from_bytes(bytes: &[u8; 64]) -> CtOption<Self> {
|
|
|
|
NonZeroPallasBase::from_bytes(bytes[32..].try_into().unwrap()).map(|ivk| {
|
|
|
|
IncomingViewingKey {
|
|
|
|
dk: DiversifierKey(bytes[..32].try_into().unwrap()),
|
|
|
|
ivk: KeyAgreementPrivateKey(ivk.into()),
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-05-11 03:07:08 -07:00
|
|
|
/// Returns the default payment address for this key.
|
|
|
|
pub fn default_address(&self) -> Address {
|
|
|
|
self.address(self.dk.default_diversifier())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the payment address for this key at the given index.
|
|
|
|
pub fn address_at(&self, j: impl Into<DiversifierIndex>) -> Address {
|
|
|
|
self.address(self.dk.get(j))
|
2021-04-19 15:05:56 -07:00
|
|
|
}
|
|
|
|
|
2021-01-20 10:54:00 -08:00
|
|
|
/// Returns the payment address for this key corresponding to the given diversifier.
|
2021-03-17 12:15:55 -07:00
|
|
|
pub fn address(&self, d: Diversifier) -> Address {
|
2021-05-11 03:07:08 -07:00
|
|
|
self.ivk.address(d)
|
2021-01-20 10:54:00 -08:00
|
|
|
}
|
|
|
|
}
|
2021-02-08 07:01:34 -08:00
|
|
|
|
|
|
|
/// A key that provides the capability to recover outgoing transaction information from
|
|
|
|
/// the block chain.
|
|
|
|
///
|
|
|
|
/// This key is not suitable for use on its own in a wallet, as it cannot maintain
|
|
|
|
/// accurate balance. You should use a [`FullViewingKey`] instead.
|
2021-03-08 12:22:38 -08:00
|
|
|
///
|
|
|
|
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
|
|
|
///
|
|
|
|
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
2021-04-27 06:49:49 -07:00
|
|
|
#[derive(Debug, Clone)]
|
2021-03-05 15:25:45 -08:00
|
|
|
pub struct OutgoingViewingKey([u8; 32]);
|
2021-02-08 07:01:34 -08:00
|
|
|
|
|
|
|
impl From<&FullViewingKey> for OutgoingViewingKey {
|
2021-03-05 15:25:45 -08:00
|
|
|
fn from(fvk: &FullViewingKey) -> Self {
|
|
|
|
fvk.derive_dk_ovk().1
|
2021-02-08 07:01:34 -08:00
|
|
|
}
|
|
|
|
}
|
2021-03-05 17:03:53 -08:00
|
|
|
|
2021-06-10 11:19:08 -07:00
|
|
|
impl From<[u8; 32]> for OutgoingViewingKey {
|
|
|
|
fn from(ovk: [u8; 32]) -> Self {
|
|
|
|
OutgoingViewingKey(ovk)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-02 15:22:22 -07:00
|
|
|
impl AsRef<[u8; 32]> for OutgoingViewingKey {
|
|
|
|
fn as_ref(&self) -> &[u8; 32] {
|
|
|
|
&self.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-05 17:03:53 -08:00
|
|
|
/// The diversified transmission key for a given payment address.
|
2021-03-08 12:22:38 -08:00
|
|
|
///
|
|
|
|
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
|
|
|
///
|
|
|
|
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
2021-06-10 11:19:08 -07:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
2021-06-02 15:22:22 -07:00
|
|
|
pub struct DiversifiedTransmissionKey(NonIdentityPallasPoint);
|
2021-03-05 17:03:53 -08:00
|
|
|
|
|
|
|
impl DiversifiedTransmissionKey {
|
2021-03-05 17:17:51 -08:00
|
|
|
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
2021-03-05 17:03:53 -08:00
|
|
|
///
|
2021-03-05 17:17:51 -08:00
|
|
|
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
2021-06-02 15:22:22 -07:00
|
|
|
pub(crate) fn derive(ivk: &IncomingViewingKey, d: &Diversifier) -> Self {
|
|
|
|
Self::derive_inner(&ivk.ivk, d)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn derive_inner(ivk: &KeyAgreementPrivateKey, d: &Diversifier) -> Self {
|
2021-06-21 10:01:35 -07:00
|
|
|
let g_d = diversify_hash(d.as_array());
|
2021-03-17 12:15:55 -07:00
|
|
|
DiversifiedTransmissionKey(ka_orchard(&ivk.0, &g_d))
|
2021-03-05 17:03:53 -08:00
|
|
|
}
|
2021-03-12 16:04:13 -08:00
|
|
|
|
2021-06-02 15:22:22 -07:00
|
|
|
/// $abst_P(bytes)$
|
|
|
|
pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
|
|
|
|
NonIdentityPallasPoint::from_bytes(bytes).map(DiversifiedTransmissionKey)
|
|
|
|
}
|
|
|
|
|
2021-03-12 16:04:13 -08:00
|
|
|
/// $repr_P(self)$
|
2021-05-28 04:11:22 -07:00
|
|
|
pub(crate) fn to_bytes(self) -> [u8; 32] {
|
2021-03-12 16:04:13 -08:00
|
|
|
self.0.to_bytes()
|
|
|
|
}
|
2021-03-05 17:03:53 -08:00
|
|
|
}
|
2021-04-27 06:49:49 -07:00
|
|
|
|
2021-06-02 14:31:18 -07:00
|
|
|
/// 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 {
|
2021-06-10 11:19:08 -07:00
|
|
|
/// For checking test vectors only.
|
|
|
|
#[cfg(test)]
|
|
|
|
pub(crate) fn to_bytes(&self) -> [u8; 32] {
|
|
|
|
self.0.to_bytes()
|
|
|
|
}
|
|
|
|
|
2021-06-02 14:31:18 -07:00
|
|
|
/// 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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-27 06:49:49 -07:00
|
|
|
/// Generators for property testing.
|
|
|
|
#[cfg(any(test, feature = "test-dependencies"))]
|
|
|
|
pub mod testing {
|
|
|
|
use proptest::prelude::*;
|
|
|
|
|
2021-06-02 14:31:18 -07:00
|
|
|
use super::{EphemeralSecretKey, SpendingKey};
|
2021-04-27 06:49:49 -07:00
|
|
|
|
|
|
|
prop_compose! {
|
|
|
|
/// Generate a uniformly distributed fake note commitment value.
|
|
|
|
pub fn arb_spending_key()(
|
2021-05-05 10:46:24 -07:00
|
|
|
key in prop::array::uniform32(prop::num::u8::ANY)
|
|
|
|
.prop_map(SpendingKey::from_bytes)
|
|
|
|
.prop_filter(
|
2021-04-27 06:49:49 -07:00
|
|
|
"Values must correspond to valid Orchard spending keys.",
|
|
|
|
|opt| bool::from(opt.is_some())
|
|
|
|
)
|
|
|
|
) -> SpendingKey {
|
|
|
|
key.unwrap()
|
|
|
|
}
|
|
|
|
}
|
2021-06-02 14:31:18 -07:00
|
|
|
|
|
|
|
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()
|
|
|
|
}
|
|
|
|
}
|
2021-04-27 06:49:49 -07:00
|
|
|
}
|
2021-05-28 03:57:21 -07:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use ff::PrimeField;
|
2021-06-02 14:31:18 -07:00
|
|
|
use proptest::prelude::*;
|
2021-05-28 03:57:21 -07:00
|
|
|
|
2021-06-02 14:31:18 -07:00
|
|
|
use super::{
|
|
|
|
testing::{arb_esk, arb_spending_key},
|
|
|
|
*,
|
|
|
|
};
|
2021-05-28 03:57:21 -07:00
|
|
|
use crate::{
|
2021-06-02 15:14:13 -07:00
|
|
|
note::{ExtractedNoteCommitment, Nullifier, RandomSeed},
|
2021-05-28 03:57:21 -07:00
|
|
|
value::NoteValue,
|
|
|
|
Note,
|
|
|
|
};
|
|
|
|
|
2021-06-02 14:31:18 -07:00
|
|
|
#[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)
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-28 03:57:21 -07:00
|
|
|
#[test]
|
|
|
|
fn test_vectors() {
|
|
|
|
for tv in crate::test_vectors::keys::test_vectors() {
|
|
|
|
let sk = SpendingKey::from_bytes(tv.sk).unwrap();
|
|
|
|
|
|
|
|
let ask: SpendAuthorizingKey = (&sk).into();
|
|
|
|
assert_eq!(<[u8; 32]>::from(&ask.0), tv.ask);
|
|
|
|
|
|
|
|
let ak: SpendValidatingKey = (&ask).into();
|
|
|
|
assert_eq!(<[u8; 32]>::from(ak.0), tv.ak);
|
|
|
|
|
|
|
|
let nk: NullifierDerivingKey = (&sk).into();
|
|
|
|
assert_eq!(nk.0.to_repr(), tv.nk);
|
|
|
|
|
|
|
|
let rivk: CommitIvkRandomness = (&sk).into();
|
|
|
|
assert_eq!(rivk.0.to_repr(), tv.rivk);
|
|
|
|
|
|
|
|
let fvk: FullViewingKey = (&sk).into();
|
|
|
|
assert_eq!(<[u8; 32]>::from(&fvk.ak.0), tv.ak);
|
|
|
|
assert_eq!(fvk.nk().0.to_repr(), tv.nk);
|
|
|
|
assert_eq!(fvk.rivk.0.to_repr(), tv.rivk);
|
|
|
|
|
|
|
|
let ivk: KeyAgreementPrivateKey = (&fvk).into();
|
|
|
|
assert_eq!(ivk.0.to_repr(), tv.ivk);
|
|
|
|
|
|
|
|
let diversifier = Diversifier(tv.default_d);
|
|
|
|
|
|
|
|
let addr = fvk.address(diversifier);
|
|
|
|
assert_eq!(&addr.pk_d().to_bytes(), &tv.default_pk_d);
|
|
|
|
|
|
|
|
let rho = Nullifier::from_bytes(&tv.note_rho).unwrap();
|
|
|
|
let note = Note::from_parts(
|
|
|
|
addr,
|
|
|
|
NoteValue::from_raw(tv.note_v),
|
|
|
|
rho,
|
2021-06-02 15:14:13 -07:00
|
|
|
RandomSeed::from_bytes(tv.note_rseed, &rho).unwrap(),
|
2021-05-28 03:57:21 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
let cmx: ExtractedNoteCommitment = note.commitment().into();
|
|
|
|
assert_eq!(cmx.to_bytes(), tv.note_cmx);
|
|
|
|
|
|
|
|
assert_eq!(note.nullifier(&fvk).to_bytes(), tv.note_nf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|