From c75dd13b17f6e9076081f72ca6cc97ef8448b40e Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 26 Jan 2024 03:00:18 +0000 Subject: [PATCH] WIP --- CHANGELOG.md | 4 +++ src/keys.rs | 92 +++++++++++++++++++++++++++------------------------- src/zip32.rs | 45 ++++++++++--------------- 3 files changed, 70 insertions(+), 71 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62caadf..0973bb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,10 @@ and this library adheres to Rust's notion of - `ExpandedSpendingKey.nsk` now has type `ProofAuthorizingKey`. - `ProofGenerationKey.nsk` now has type `ProofAuthorizingKey`. +### Removed +- `sapling_crypto::keys`: + - `ViewingKey` (use `FullViewingKey` instead). + ## [0.1.0] - 2024-01-26 The crate has been completely rewritten. See [`zcash/librustzcash`] for the history of this rewrite. diff --git a/src/keys.rs b/src/keys.rs index 8a75017..8a5a1e5 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -391,56 +391,38 @@ impl ProofGenerationKey { } } -#[derive(Debug, Clone)] -pub struct ViewingKey { - pub ak: SpendValidatingKey, - pub nk: NullifierDerivingKey, -} - -impl ViewingKey { - pub fn rk(&self, ar: jubjub::Fr) -> redjubjub::VerificationKey { - self.ak.randomize(&ar) - } - - pub fn ivk(&self) -> SaplingIvk { - SaplingIvk(crh_ivk(self.ak.to_bytes(), self.nk.0.to_bytes())) - } - - pub fn to_payment_address(&self, diversifier: Diversifier) -> Option { - self.ivk().to_payment_address(diversifier) - } -} - -/// A Sapling key that provides the capability to view incoming and outgoing transactions. -#[derive(Debug)] +/// A key that provides the capability to view incoming and outgoing transactions. +/// +/// Modern Zcash wallets use multiple viewing keys scoped to external and internal +/// operations. You should consider using [`DiversifiableFullViewingKey`] instead, which +/// handles these details and ensures that you have a full and consistent view of wallet +/// activity. +/// +/// Defined in [Zcash Protocol Spec § 3.1: Payment Addresses and Keys][addressesandkeys]. +/// +/// [`DiversifiableFullViewingKey`]: crate::zip32::DiversifiableFullViewingKey +/// [addressesandkeys]: https://zips.z.cash/protocol/protocol.pdf#addressesandkeys +// TODO: Rename to `ScopedFullViewingKey` or `ScopedViewingKey` for API clarity. +#[derive(Clone, Debug, PartialEq, Eq)] pub struct FullViewingKey { - pub vk: ViewingKey, - pub ovk: OutgoingViewingKey, -} - -impl Clone for FullViewingKey { - fn clone(&self) -> Self { - FullViewingKey { - vk: ViewingKey { - ak: self.vk.ak.clone(), - nk: self.vk.nk, - }, - ovk: self.ovk, - } - } + ak: SpendValidatingKey, + nk: NullifierDerivingKey, + ovk: OutgoingViewingKey, } impl FullViewingKey { pub fn from_expanded_spending_key(expsk: &ExpandedSpendingKey) -> Self { FullViewingKey { - vk: ViewingKey { - ak: (&expsk.ask).into(), - nk: (&expsk.nsk).into(), - }, + ak: (&expsk.ask).into(), + nk: (&expsk.nsk).into(), ovk: expsk.ovk, } } + /// Parses a full viewing key from its "raw" encoding as specified in + /// [Zcash Protocol Spec § 5.6.3.3: Sapling Full Viewing Keys][saplingfullviewingkeyencoding]. + /// + /// [saplingfullviewingkeyencoding]: https://zips.z.cash/protocol/protocol.pdf#saplingfullviewingkeyencoding pub fn read(mut reader: R) -> io::Result { let ak = { let mut buf = [0u8; 32]; @@ -471,25 +453,47 @@ impl FullViewingKey { reader.read_exact(&mut ovk)?; Ok(FullViewingKey { - vk: ViewingKey { ak, nk }, + ak, + nk, ovk: OutgoingViewingKey(ovk), }) } + /// Serializes the full viewing key as specified in + /// [Zcash Protocol Spec § 5.6.3.3: Sapling Full Viewing Keys][saplingfullviewingkeyencoding]. + /// + /// [saplingfullviewingkeyencoding]: https://zips.z.cash/protocol/protocol.pdf#saplingfullviewingkeyencoding pub fn write(&self, mut writer: W) -> io::Result<()> { - writer.write_all(&self.vk.ak.to_bytes())?; - writer.write_all(&self.vk.nk.0.to_bytes())?; + writer.write_all(&self.ak.to_bytes())?; + writer.write_all(&self.nk.0.to_bytes())?; writer.write_all(&self.ovk.0)?; Ok(()) } + /// Serializes the full viewing key as specified in + /// [Zcash Protocol Spec § 5.6.3.3: Sapling Full Viewing Keys][saplingfullviewingkeyencoding]. + /// + /// [saplingfullviewingkeyencoding]: https://zips.z.cash/protocol/protocol.pdf#saplingfullviewingkeyencoding pub fn to_bytes(&self) -> [u8; 96] { let mut result = [0u8; 96]; self.write(&mut result[..]) .expect("should be able to serialize a FullViewingKey"); result } + + pub fn rk(&self, ar: jubjub::Fr) -> redjubjub::VerificationKey { + self.ak.randomize(&ar) + } + + /// Derives an `IncomingViewingKey` for this full viewing key. + pub fn ivk(&self) -> SaplingIvk { + SaplingIvk(crh_ivk(self.ak.to_bytes(), self.nk.0.to_bytes())) + } + + pub fn to_payment_address(&self, diversifier: Diversifier) -> Option { + self.ivk().to_payment_address(diversifier) + } } #[derive(Debug, Clone)] @@ -750,7 +754,7 @@ pub mod testing { prop_compose! { pub fn arb_incoming_viewing_key()(fvk in arb_full_viewing_key()) -> SaplingIvk { - fvk.vk.ivk() + fvk.ivk() } } } diff --git a/src/zip32.rs b/src/zip32.rs index 7461578..5ff125d 100644 --- a/src/zip32.rs +++ b/src/zip32.rs @@ -14,7 +14,7 @@ use zip32::{ChainCode, ChildIndex, DiversifierIndex, Scope}; use std::io::{self, Read, Write}; use std::ops::AddAssign; -use super::{Diversifier, NullifierDerivingKey, PaymentAddress, ViewingKey}; +use super::{Diversifier, NullifierDerivingKey, PaymentAddress}; use crate::keys::ProofAuthorizingKey; use crate::{ constants::PROOF_GENERATION_KEY_GENERATOR, @@ -37,7 +37,7 @@ pub fn sapling_address( j: DiversifierIndex, ) -> Option { dk.diversifier(j) - .and_then(|d_j| fvk.vk.to_payment_address(d_j)) + .and_then(|d_j| fvk.to_payment_address(d_j)) } /// Search the diversifier space starting at diversifier index `j` for @@ -50,7 +50,7 @@ pub fn sapling_find_address( j: DiversifierIndex, ) -> Option<(DiversifierIndex, PaymentAddress)> { let (j, d_j) = dk.find_diversifier(j)?; - fvk.vk.to_payment_address(d_j).map(|addr| (j, addr)) + fvk.to_payment_address(d_j).map(|addr| (j, addr)) } /// Returns the payment address corresponding to the smallest valid diversifier @@ -93,16 +93,14 @@ pub fn sapling_derive_internal_fvk( jubjub::Fr::from_bytes_wide(&PrfExpand::SAPLING_ZIP32_INTERNAL_NSK.with(i.as_bytes())); let r = PrfExpand::SAPLING_ZIP32_INTERNAL_DK_OVK.with(i.as_bytes()); // PROOF_GENERATION_KEY_GENERATOR = \mathcal{H}^Sapling - let nk_internal = NullifierDerivingKey(PROOF_GENERATION_KEY_GENERATOR * i_nsk + fvk.vk.nk.0); + let nk_internal = NullifierDerivingKey(PROOF_GENERATION_KEY_GENERATOR * i_nsk + fvk.nk.0); 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.clone(), - nk: nk_internal, - }, + ak: fvk.ak.clone(), + nk: nk_internal, ovk: ovk_internal, }, dk_internal, @@ -534,9 +532,7 @@ impl std::cmp::PartialEq for ExtendedFullViewingKey { && self.parent_fvk_tag == rhs.parent_fvk_tag && self.child_index == rhs.child_index && self.chain_code == rhs.chain_code - && self.fvk.vk.ak == rhs.fvk.vk.ak - && self.fvk.vk.nk == rhs.fvk.vk.nk - && self.fvk.ovk == rhs.fvk.ovk + && self.fvk == rhs.fvk && self.dk == rhs.dk } } @@ -709,16 +705,16 @@ impl DiversifiableFullViewingKey { /// This API is provided so that nullifiers for change notes can be correctly computed. pub fn to_nk(&self, scope: Scope) -> NullifierDerivingKey { match scope { - Scope::External => self.fvk.vk.nk, - Scope::Internal => self.derive_internal().fvk.vk.nk, + Scope::External => self.fvk.nk, + Scope::Internal => self.derive_internal().fvk.nk, } } /// Derives an incoming viewing key corresponding to this full viewing key. pub fn to_ivk(&self, scope: Scope) -> SaplingIvk { match scope { - Scope::External => self.fvk.vk.ivk(), - Scope::Internal => self.derive_internal().fvk.vk.ivk(), + Scope::External => self.fvk.ivk(), + Scope::Internal => self.derive_internal().fvk.ivk(), } } @@ -937,9 +933,7 @@ mod tests { // Check value -> bytes -> parsed round trip. let dfvk_bytes = dfvk.to_bytes(); let dfvk_parsed = DiversifiableFullViewingKey::from_bytes(&dfvk_bytes).unwrap(); - assert_eq!(dfvk_parsed.fvk.vk.ak, dfvk.fvk.vk.ak); - assert_eq!(dfvk_parsed.fvk.vk.nk, dfvk.fvk.vk.nk); - assert_eq!(dfvk_parsed.fvk.ovk, dfvk.fvk.ovk); + assert_eq!(dfvk_parsed.fvk, dfvk.fvk); assert_eq!(dfvk_parsed.dk, dfvk.dk); // Check bytes -> parsed -> bytes round trip. @@ -1651,14 +1645,14 @@ mod tests { } for (xfvk, tv) in xfvks.iter().zip(test_vectors.iter()) { - assert_eq!(xfvk.fvk.vk.ak.to_bytes(), tv.ak); - assert_eq!(xfvk.fvk.vk.nk.0.to_bytes(), tv.nk); + assert_eq!(xfvk.fvk.ak.to_bytes(), tv.ak); + assert_eq!(xfvk.fvk.nk.0.to_bytes(), tv.nk); assert_eq!(xfvk.fvk.ovk.0, tv.ovk); assert_eq!(xfvk.dk.0, tv.dk); assert_eq!(xfvk.chain_code.as_bytes(), &tv.c); - assert_eq!(xfvk.fvk.vk.ivk().to_repr().as_ref(), tv.ivk); + assert_eq!(xfvk.fvk.ivk().to_repr().as_ref(), tv.ivk); let mut ser = vec![]; xfvk.write(&mut ser).unwrap(); @@ -1695,17 +1689,14 @@ mod tests { } let internal_xfvk = xfvk.derive_internal(); - assert_eq!(internal_xfvk.fvk.vk.ak.to_bytes(), tv.ak); - assert_eq!(internal_xfvk.fvk.vk.nk.0.to_bytes(), tv.internal_nk); + assert_eq!(internal_xfvk.fvk.ak.to_bytes(), tv.ak); + assert_eq!(internal_xfvk.fvk.nk.0.to_bytes(), tv.internal_nk); assert_eq!(internal_xfvk.fvk.ovk.0, tv.internal_ovk); assert_eq!(internal_xfvk.dk.0, tv.internal_dk); assert_eq!(internal_xfvk.chain_code.as_bytes(), &tv.c); - assert_eq!( - internal_xfvk.fvk.vk.ivk().to_repr().as_ref(), - tv.internal_ivk - ); + assert_eq!(internal_xfvk.fvk.ivk().to_repr().as_ref(), tv.internal_ivk); let mut ser = vec![]; internal_xfvk.write(&mut ser).unwrap();