diff --git a/zcash_primitives/CHANGELOG.md b/zcash_primitives/CHANGELOG.md index 9158f694e..57c35c4c9 100644 --- a/zcash_primitives/CHANGELOG.md +++ b/zcash_primitives/CHANGELOG.md @@ -31,8 +31,8 @@ and this library adheres to Rust's notion of Orchard). This type makes it possible to encode a type-safe state machine for the application of authorizing data to a transaction; implementations of this trait represent different states of the authorization process. -- New bundle types under the `zcash_primitives::transaction` submodules, one for - each Zcash sub-protocol. These are now used instead of bare fields +- New bundle types under the `zcash_primitives::transaction` submodules, one for + each Zcash sub-protocol. These are now used instead of bare fields within the `TransactionData` type. - `components::sapling::Bundle` bundle of Sapling transaction elements. This new struct is parameterized by a @@ -58,7 +58,7 @@ and this library adheres to Rust's notion of diversifier index space, whereas `sapling_diversifier` just attempts to use the provided diversifier index and returns `None` if it does not produce a valid diversifier. -- `zcash_primitives::zip32::DiversifierKey::diversifier` has been renamed to +- `zcash_primitives::zip32::DiversifierKey::diversifier` has been renamed to `find_diversifier` and the `diversifier` method has new semantics. `find_diversifier` searches the diversifier index space to find a diversifier index which produces a valid diversifier, whereas `diversifier` just attempts @@ -71,6 +71,23 @@ and this library adheres to Rust's notion of just attempts to create an address corresponding to the diversifier derived from the provided diversifier index and returns `None` if the provided index does not produce a valid diversifier. +- `zcash_primitives::zip32::ExtendedSpendingKey.derive_internal` has been + added to facilitate the derivation of an internal (change) spending key. + This spending key can be used to spend change sent to an internal address + corresponding to the associated full viewing key as specified in + [ZIP 316](https://zips.z.cash/zip-0316#encoding-of-unified-full-incoming-viewing-keys).. +- `zcash_primitives::zip32::ExtendedFullViewingKey.derive_internal` has been + added to facilitate the derivation of an internal (change) spending key. + This spending key can be used to spend change sent to an internal address + corresponding to the associated full viewing key as specified in + [ZIP 32](https://zips.z.cash/zip-0032#deriving-a-sapling-internal-spending-key). +- `zcash_primitives::zip32::sapling_derive_internal_fvk` provides the + internal implementation of `ExtendedFullViewingKey.derive_internal` + but does not require a complete extended full viewing key, just + the full viewing key and the diversifier key. In the future, this + function will likely be refactored to become a member function of + a new `DiversifiableFullViewingKey` type, which represents the ability + to derive IVKs, OVKs, and addresses, but not child viewing keys. ### Changed - MSRV is now 1.51.0. diff --git a/zcash_primitives/src/zip32.rs b/zcash_primitives/src/zip32.rs index 46bdd02fe..4602fb28f 100644 --- a/zcash_primitives/src/zip32.rs +++ b/zcash_primitives/src/zip32.rs @@ -6,6 +6,7 @@ use aes::Aes256; use blake2b_simd::Params as Blake2bParams; use byteorder::{ByteOrder, LittleEndian, ReadBytesExt, WriteBytesExt}; use fpe::ff1::{BinaryNumeralString, FF1}; +use std::convert::TryInto; use std::ops::AddAssign; use crate::{ @@ -20,6 +21,7 @@ use crate::sapling::keys::{ pub const ZIP32_SAPLING_MASTER_PERSONALIZATION: &[u8; 16] = b"ZcashIP32Sapling"; pub const ZIP32_SAPLING_FVFP_PERSONALIZATION: &[u8; 16] = b"ZcashSaplingFVFP"; +pub const ZIP32_SAPLING_INT_PERSONALIZATION: &[u8; 16] = b"Zcash_SaplingInt"; // Common helper functions @@ -233,6 +235,44 @@ pub fn sapling_default_address( sapling_find_address(fvk, dk, DiversifierIndex::new()).unwrap() } +/// Returns the internal full viewing key and diversifier key +/// for the provided external FVK = (ak, nk, ovk) and dk encoded +/// in a [Unified FVK]. +/// +/// [Unified FVK]: https://zips.z.cash/zip-0316#encoding-of-unified-full-incoming-viewing-keys +pub fn sapling_derive_internal_fvk( + fvk: &FullViewingKey, + dk: &DiversifierKey, +) -> (FullViewingKey, DiversifierKey) { + let i = { + let mut h = Blake2bParams::new() + .hash_length(32) + .personal(crate::zip32::ZIP32_SAPLING_INT_PERSONALIZATION) + .to_state(); + h.update(&fvk.to_bytes()); + h.update(&dk.0); + h.finalize() + }; + let i_nsk = jubjub::Fr::from_bytes_wide(prf_expand(i.as_bytes(), &[0x17]).as_array()); + let r = prf_expand(i.as_bytes(), &[0x18]); + let r = r.as_bytes(); + // PROOF_GENERATION_KEY_GENERATOR = \mathcal{H}^Sapling + let nk_internal = PROOF_GENERATION_KEY_GENERATOR * i_nsk + fvk.vk.nk; + let dk_internal = DiversifierKey(r[..32].try_into().unwrap()); + let ovk_internal = OutgoingViewingKey(r[32..].try_into().unwrap()); + + ( + FullViewingKey { + vk: ViewingKey { + ak: fvk.vk.ak, + nk: nk_internal, + }, + ovk: ovk_internal, + }, + dk_internal, + ) +} + /// A Sapling extended spending key #[derive(Clone)] pub struct ExtendedSpendingKey { @@ -409,6 +449,41 @@ impl ExtendedSpendingKey { pub fn default_address(&self) -> (DiversifierIndex, PaymentAddress) { ExtendedFullViewingKey::from(self).default_address() } + + /// Derives an internal spending key given an external spending key. + /// + /// Specified in [ZIP 32](https://zips.z.cash/zip-0032#deriving-a-sapling-internal-spending-key). + pub fn derive_internal(&self) -> Self { + let i = { + let fvk = FullViewingKey::from_expanded_spending_key(&self.expsk); + let mut h = Blake2bParams::new() + .hash_length(32) + .personal(crate::zip32::ZIP32_SAPLING_INT_PERSONALIZATION) + .to_state(); + h.update(&fvk.to_bytes()); + h.update(&self.dk.0); + h.finalize() + }; + let i_nsk = jubjub::Fr::from_bytes_wide(prf_expand(i.as_bytes(), &[0x17]).as_array()); + let r = prf_expand(i.as_bytes(), &[0x18]); + let r = r.as_bytes(); + let nsk_internal = i_nsk + self.expsk.nsk; + let dk_internal = DiversifierKey(r[..32].try_into().unwrap()); + let ovk_internal = OutgoingViewingKey(r[32..].try_into().unwrap()); + + ExtendedSpendingKey { + depth: self.depth, + parent_fvk_tag: self.parent_fvk_tag, + child_index: self.child_index, + chain_code: self.chain_code, + expsk: ExpandedSpendingKey { + ask: self.expsk.ask, + nsk: nsk_internal, + ovk: ovk_internal, + }, + dk: dk_internal, + } + } } impl<'a> From<&'a ExtendedSpendingKey> for ExtendedFullViewingKey { @@ -518,6 +593,25 @@ impl ExtendedFullViewingKey { pub fn chain_code(&self) -> ChainCode { self.chain_code } + + /// Derives an internal full viewing key used for internal operations such + /// as change and auto-shielding. The internal FVK has the same spend authority + /// (the private key corresponding to ak) as the original, but viewing authority + /// only for internal transfers. + /// + /// Specified in [ZIP 32](https://zips.z.cash/zip-0032#deriving-a-sapling-internal-full-viewing-key). + pub fn derive_internal(&self) -> Self { + let (fvk_internal, dk_internal) = sapling_derive_internal_fvk(&self.fvk, &self.dk); + + ExtendedFullViewingKey { + depth: self.depth, + parent_fvk_tag: self.parent_fvk_tag, + child_index: self.child_index, + chain_code: self.chain_code, + fvk: fvk_internal, + dk: dk_internal, + } + } } #[cfg(test)]