From a98010a6841659c1a7cabefabee123c4e583a9fe Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Thu, 6 Jan 2022 22:33:17 +0800 Subject: [PATCH 1/6] Derive Sapling internal spending key. Co-authored-by: Daira Hopwood --- zcash_primitives/src/zip32.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/zcash_primitives/src/zip32.rs b/zcash_primitives/src/zip32.rs index 406fb9271..3bf7d2328 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 @@ -409,6 +411,35 @@ impl ExtendedSpendingKey { pub fn default_address(&self) -> (DiversifierIndex, PaymentAddress) { ExtendedFullViewingKey::from(self).default_address() } + + pub fn derive_internal(&self) -> Self { + let i = { + let fvk = FullViewingKey::from_expanded_spending_key(&self.expsk); + Blake2bParams::new() + .hash_length(64) + .personal(crate::zip32::ZIP32_SAPLING_INT_PERSONALIZATION) + .hash(&fvk.to_bytes()) + }; + 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 { From f6f5096ae4a1199cd4b19534f955badc0e570994 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Thu, 6 Jan 2022 22:34:03 +0800 Subject: [PATCH 2/6] Derive Sapling internal full viewing key. Co-authored-by: Daira Hopwood --- zcash_primitives/src/zip32.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/zcash_primitives/src/zip32.rs b/zcash_primitives/src/zip32.rs index 3bf7d2328..9753f31e7 100644 --- a/zcash_primitives/src/zip32.rs +++ b/zcash_primitives/src/zip32.rs @@ -544,6 +544,35 @@ impl ExtendedFullViewingKey { pub fn default_address(&self) -> (DiversifierIndex, PaymentAddress) { sapling_default_address(&self.fvk, &self.dk) } + + pub fn derive_internal(&self) -> Self { + let i = Blake2bParams::new() + .hash_length(64) + .personal(crate::zip32::ZIP32_SAPLING_INT_PERSONALIZATION) + .hash(&self.fvk.to_bytes()); + 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 + self.fvk.vk.nk; + let dk_internal = DiversifierKey(r[..32].try_into().unwrap()); + let ovk_internal = OutgoingViewingKey(r[32..].try_into().unwrap()); + + ExtendedFullViewingKey { + depth: self.depth, + parent_fvk_tag: self.parent_fvk_tag, + child_index: self.child_index, + chain_code: self.chain_code, + fvk: FullViewingKey { + vk: ViewingKey { + ak: self.fvk.vk.ak, + nk: nk_internal, + }, + ovk: ovk_internal, + }, + dk: dk_internal, + } + } } #[cfg(test)] From da3833f906c490631d87273e862db6047a1ea905 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 19 Jan 2022 18:18:22 -0700 Subject: [PATCH 3/6] Fix missing use of `dk` in derivation of sapling internal FVK. Also, factor out sapling internal fvk derivation so that it only requires (fvk, dk) since we may not have the full extfvk. --- zcash_primitives/src/zip32.rs | 62 ++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/zcash_primitives/src/zip32.rs b/zcash_primitives/src/zip32.rs index 9753f31e7..2cdd597d1 100644 --- a/zcash_primitives/src/zip32.rs +++ b/zcash_primitives/src/zip32.rs @@ -235,6 +235,41 @@ 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 and dk +pub fn sapling_derive_internal_fvk( + fvk: &FullViewingKey, + dk: &DiversifierKey, +) -> (FullViewingKey, DiversifierKey) { + let i = { + let mut h = Blake2bParams::new() + .hash_length(64) + .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 { @@ -415,10 +450,13 @@ impl ExtendedSpendingKey { pub fn derive_internal(&self) -> Self { let i = { let fvk = FullViewingKey::from_expanded_spending_key(&self.expsk); - Blake2bParams::new() + let mut h = Blake2bParams::new() .hash_length(64) .personal(crate::zip32::ZIP32_SAPLING_INT_PERSONALIZATION) - .hash(&fvk.to_bytes()) + .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]); @@ -546,30 +584,14 @@ impl ExtendedFullViewingKey { } pub fn derive_internal(&self) -> Self { - let i = Blake2bParams::new() - .hash_length(64) - .personal(crate::zip32::ZIP32_SAPLING_INT_PERSONALIZATION) - .hash(&self.fvk.to_bytes()); - 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 + self.fvk.vk.nk; - let dk_internal = DiversifierKey(r[..32].try_into().unwrap()); - let ovk_internal = OutgoingViewingKey(r[32..].try_into().unwrap()); + 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: FullViewingKey { - vk: ViewingKey { - ak: self.fvk.vk.ak, - nk: nk_internal, - }, - ovk: ovk_internal, - }, + fvk: fvk_internal, dk: dk_internal, } } From 82c1d87dcdaf520fdab9fd0dc662f3d66a9694e3 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Thu, 20 Jan 2022 18:08:01 -0700 Subject: [PATCH 4/6] Fix incorrect length of blake2b hashes for internal key derivation. Co-authored-by: str4d --- zcash_primitives/src/zip32.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zcash_primitives/src/zip32.rs b/zcash_primitives/src/zip32.rs index 2cdd597d1..d70b2595c 100644 --- a/zcash_primitives/src/zip32.rs +++ b/zcash_primitives/src/zip32.rs @@ -243,7 +243,7 @@ pub fn sapling_derive_internal_fvk( ) -> (FullViewingKey, DiversifierKey) { let i = { let mut h = Blake2bParams::new() - .hash_length(64) + .hash_length(32) .personal(crate::zip32::ZIP32_SAPLING_INT_PERSONALIZATION) .to_state(); h.update(&fvk.to_bytes()); @@ -451,7 +451,7 @@ impl ExtendedSpendingKey { let i = { let fvk = FullViewingKey::from_expanded_spending_key(&self.expsk); let mut h = Blake2bParams::new() - .hash_length(64) + .hash_length(32) .personal(crate::zip32::ZIP32_SAPLING_INT_PERSONALIZATION) .to_state(); h.update(&fvk.to_bytes()); From eb80138cf9cb1fab1bdb1006cdd5013068d580f8 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Fri, 21 Jan 2022 10:49:17 +0800 Subject: [PATCH 5/6] Document new APIs for deriving internal keys. --- zcash_primitives/src/zip32.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/zcash_primitives/src/zip32.rs b/zcash_primitives/src/zip32.rs index d70b2595c..4a4b85ad8 100644 --- a/zcash_primitives/src/zip32.rs +++ b/zcash_primitives/src/zip32.rs @@ -236,7 +236,10 @@ pub fn sapling_default_address( } /// Returns the internal full viewing key and diversifier key -/// for the provided external fvk and dk +/// 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, @@ -447,6 +450,9 @@ impl ExtendedSpendingKey { 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); @@ -583,6 +589,12 @@ impl ExtendedFullViewingKey { sapling_default_address(&self.fvk, &self.dk) } + /// 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); From 54cca8081b60ab4d50481caaad1618d00ede9317 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Sat, 22 Jan 2022 19:35:01 -0700 Subject: [PATCH 6/6] Update zcash_primitives/CHANGELOG.md with change key derivation methods. --- zcash_primitives/CHANGELOG.md | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) 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.