From c3e24794f07a533233309084787fe5ed1eeb9311 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Thu, 15 Jul 2021 15:18:01 +0800 Subject: [PATCH 1/2] zip32.rs: master and child key derivation for ExtendedSpendingKey --- src/builder.rs | 2 +- src/keys.rs | 53 ++++++++-- src/lib.rs | 1 + src/spec/prf_expand.rs | 2 + src/zip32.rs | 226 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 277 insertions(+), 7 deletions(-) create mode 100644 src/zip32.rs diff --git a/src/builder.rs b/src/builder.rs index 7070b084..edc14fa8 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -659,7 +659,7 @@ pub mod testing { ) -> ArbitraryBundleInputs { ArbitraryBundleInputs { rng: StdRng::from_seed(rng_seed), - sk: sk.clone(), + sk, anchor, notes: notes_and_auth_paths, recipient_amounts diff --git a/src/keys.rs b/src/keys.rs index 477b175a..98acb7a1 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -1,6 +1,7 @@ //! Key structures for Orchard. use std::convert::{TryFrom, TryInto}; +use std::io::{self, Read, Write}; use std::mem; use aes::Aes256; @@ -20,16 +21,18 @@ use crate::{ commit_ivk, diversify_hash, extract_p, ka_orchard, prf_nf, to_base, to_scalar, NonIdentityPallasPoint, NonZeroPallasBase, NonZeroPallasScalar, PrfExpand, }, + zip32::ExtendedSpendingKey, }; const KDF_ORCHARD_PERSONALIZATION: &[u8; 16] = b"Zcash_OrchardKDF"; +const ZIP32_PURPOSE: u32 = 32; /// A spending key, from which all key material is derived. /// /// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents]. /// /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct SpendingKey([u8; 32]); impl SpendingKey { @@ -69,6 +72,12 @@ impl SpendingKey { pub fn to_bytes(&self) -> &[u8; 32] { &self.0 } + + /// Derives the Orchard spending key for the given seed, coin type, and account. + pub fn from_zip32_seed(seed: &[u8], coin_type: u32, account: u32) -> Self { + // Call zip32 logic + ExtendedSpendingKey::from_path(&seed, &[ZIP32_PURPOSE, coin_type, account]).sk() + } } /// A spend authorizing key, used to create spend authorization signatures. @@ -272,6 +281,12 @@ impl From<&SpendingKey> for FullViewingKey { } } +impl From<&ExtendedSpendingKey> for FullViewingKey { + fn from(extsk: &ExtendedSpendingKey) -> Self { + (&extsk.sk()).into() + } +} + impl From for SpendValidatingKey { fn from(fvk: FullViewingKey) -> Self { fvk.ak @@ -319,18 +334,44 @@ impl FullViewingKey { /// Serializes the full viewing key as specified in [Zcash Protocol Spec § 5.6.4.4: Orchard Raw Full Viewing Keys][orchardrawfullviewingkeys] /// /// [orchardrawfullviewingkeys]: https://zips.z.cash/protocol/protocol.pdf#orchardfullviewingkeyencoding - pub fn to_raw_fvk_bytes(&self) -> [u8; 96] { + pub fn write(&self, mut writer: W) -> io::Result<()> { + let ak_raw: [u8; 32] = self.ak.0.clone().into(); + writer.write_all(&ak_raw)?; + writer.write_all(&self.nk.0.to_bytes())?; + writer.write_all(&self.rivk.0.to_bytes())?; + + Ok(()) + } + + /// Parses a full viewing key from its "raw" encoding as specified in [Zcash Protocol Spec § 5.6.4.4: Orchard Raw Full Viewing Keys][orchardrawfullviewingkeys] + /// + /// [orchardrawfullviewingkeys]: https://zips.z.cash/protocol/protocol.pdf#orchardfullviewingkeyencoding + pub fn read(mut reader: R) -> io::Result { + let mut data = [0u8; 96]; + reader.read_exact(&mut data)?; + + Self::from_bytes(&data).ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + "Unable to deserialize a valid Orchard FullViewingKey from bytes".to_owned(), + ) + }) + } + + /// Serializes the full viewing key as specified in [Zcash Protocol Spec § 5.6.4.4: Orchard Raw Full Viewing Keys][orchardrawfullviewingkeys] + /// + /// [orchardrawfullviewingkeys]: https://zips.z.cash/protocol/protocol.pdf#orchardfullviewingkeyencoding + pub fn to_bytes(&self) -> [u8; 96] { let mut result = [0u8; 96]; - result[..32].copy_from_slice(&self.ak.to_bytes()); - result[32..64].copy_from_slice(&self.nk.to_bytes()); - result[64..].copy_from_slice(&<[u8; 32]>::from(&self.rivk.0)); + self.write(&mut result[..]) + .expect("should be able to serialize a FullViewingKey"); result } /// Parses a full viewing key from its "raw" encoding as specified in [Zcash Protocol Spec § 5.6.4.4: Orchard Raw Full Viewing Keys][orchardrawfullviewingkeys] /// /// [orchardrawfullviewingkeys]: https://zips.z.cash/protocol/protocol.pdf#orchardfullviewingkeyencoding - pub fn from_raw_fvk_bytes(bytes: &[u8; 96]) -> Option { + pub fn from_bytes(bytes: &[u8; 96]) -> Option { let ak = SpendValidatingKey::from_bytes(&bytes[..32])?; let nk = NullifierDerivingKey::from_bytes(&bytes[32..64])?; let rivk = CommitIvkRandomness::from_bytes(&bytes[64..])?; diff --git a/src/lib.rs b/src/lib.rs index 61a64aa3..d0078466 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,7 @@ pub mod primitives; mod spec; pub mod tree; pub mod value; +pub mod zip32; #[cfg(test)] mod test_vectors; diff --git a/src/spec/prf_expand.rs b/src/spec/prf_expand.rs index 950947ce..d00c5610 100644 --- a/src/spec/prf_expand.rs +++ b/src/spec/prf_expand.rs @@ -10,6 +10,7 @@ pub(crate) enum PrfExpand { OrchardNk, OrchardRivk, Psi, + OrchardZip32Child, OrchardDkOvk, } @@ -22,6 +23,7 @@ impl PrfExpand { Self::OrchardNk => 0x07, Self::OrchardRivk => 0x08, Self::Psi => 0x09, + Self::OrchardZip32Child => 0x81, Self::OrchardDkOvk => 0x82, } } diff --git a/src/zip32.rs b/src/zip32.rs new file mode 100644 index 00000000..0a342e29 --- /dev/null +++ b/src/zip32.rs @@ -0,0 +1,226 @@ +//! Key structures for Orchard. + +use std::{convert::TryInto, fmt}; + +use blake2b_simd::Params as Blake2bParams; + +use crate::{ + keys::{FullViewingKey, SpendingKey}, + spec::PrfExpand, +}; + +const ZIP32_ORCHARD_PERSONALIZATION: &[u8; 16] = b"ZcashIP32Orchard"; +const ZIP32_ORCHARD_FVFP_PERSONALIZATION: &[u8; 16] = b"ZcashOrchardFVFP"; + +/// A seed resulted in an invalid spending key +#[derive(Debug)] +pub struct InvalidSkError; + +impl fmt::Display for InvalidSkError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Seed produced invalid spending key.") + } +} + +impl std::error::Error for InvalidSkError {} + +/// An Orchard full viewing key fingerprint +struct FvkFingerprint([u8; 32]); + +impl From<&FullViewingKey> for FvkFingerprint { + fn from(fvk: &FullViewingKey) -> Self { + let mut h = Blake2bParams::new() + .hash_length(32) + .personal(ZIP32_ORCHARD_FVFP_PERSONALIZATION) + .to_state(); + h.update(&fvk.to_bytes()); + let mut fvfp = [0u8; 32]; + fvfp.copy_from_slice(h.finalize().as_bytes()); + FvkFingerprint(fvfp) + } +} + +/// An Orchard full viewing key tag +#[derive(Clone, Copy, Debug, PartialEq)] +struct FvkTag([u8; 4]); + +impl FvkFingerprint { + fn tag(&self) -> FvkTag { + let mut tag = [0u8; 4]; + tag.copy_from_slice(&self.0[..4]); + FvkTag(tag) + } +} + +impl FvkTag { + fn master() -> Self { + FvkTag([0u8; 4]) + } +} + +/// A hardened child index for a derived key. +#[derive(Clone, Debug, PartialEq)] +struct ChildIndex(u32); + +impl From for ChildIndex { + /// # Panics + /// + /// `index` must be less than 2^31 + fn from(index: u32) -> Self { + assert!(index < (1 << 31)); + Self(index + (1 << 31)) + } +} + +/// The chain code forming the second half of an Orchard extended key. +#[derive(Debug, Copy, Clone, PartialEq)] +struct ChainCode([u8; 32]); + +/// An Orchard extended spending key. +/// +/// Defined in [ZIP32: Orchard extended keys][orchardextendedkeys]. +/// +/// [orchardextendedkeys]: https://zips.z.cash/zip-0032#orchard-extended-keys +#[derive(Debug, Clone)] +pub(crate) struct ExtendedSpendingKey { + depth: u8, + parent_fvk_tag: FvkTag, + child_index: ChildIndex, + chain_code: ChainCode, + sk: SpendingKey, +} + +impl std::cmp::PartialEq for ExtendedSpendingKey { + fn eq(&self, rhs: &ExtendedSpendingKey) -> bool { + self.depth == rhs.depth + && self.parent_fvk_tag == rhs.parent_fvk_tag + && self.child_index == rhs.child_index + && self.chain_code == rhs.chain_code + && self.sk == rhs.sk + } +} + +#[allow(non_snake_case)] +impl ExtendedSpendingKey { + /// Returns the spending key of the child key corresponding to + /// the path derived from the master key + /// + /// # Panics + /// + /// Panics if seed results in invalid spending key. + pub fn from_path(seed: &[u8], path: &[u32]) -> Self { + let mut xsk = Self::master(seed).unwrap(); + for &i in path.iter() { + xsk = xsk.derive_child(i.into()).unwrap(); + } + xsk + } + + /// Generates the master key of an Orchard extended spending key. + /// + /// Defined in [ZIP32: Orchard master key generation][orchardmasterkey]. + /// + /// [orchardmasterkey]: https://zips.z.cash/zip-0032#orchard-master-key-generation + /// + /// # Panics + /// + /// Panics if the seed is shorter than 32 bytes or longer than 252 bytes. + fn master(seed: &[u8]) -> Result { + assert!(seed.len() >= 32 && seed.len() <= 252); + // I := BLAKE2b-512("ZcashIP32Orchard", seed) + let I: [u8; 64] = { + let mut I = Blake2bParams::new() + .hash_length(64) + .personal(ZIP32_ORCHARD_PERSONALIZATION) + .to_state(); + I.update(seed); + I.finalize().as_bytes().try_into().unwrap() + }; + // I_L is used as the master spending key sk_m. + let sk_m = SpendingKey::from_bytes(I[..32].try_into().unwrap()); + if sk_m.is_none().into() { + return Err(InvalidSkError); + } + let sk_m = sk_m.unwrap(); + + // I_R is used as the master chain code c_m. + let c_m = ChainCode(I[32..].try_into().unwrap()); + + // For the master extended spending key, depth is 0, parent_fvk_tag is 4 zero bytes, and i is 0. + Ok(Self { + depth: 0, + parent_fvk_tag: FvkTag([0; 4]), + child_index: ChildIndex(0), + chain_code: c_m, + sk: sk_m, + }) + } + + /// Derives a child key from a parent key at a given index. + /// + /// Defined in [ZIP32: Orchard child key derivation][orchardchildkey]. + /// + /// [orchardchildkey]: https://zips.z.cash/zip-0032#orchard-child-key-derivation + /// + /// Discards index if it results in an invalid sk + fn derive_child(&self, index: ChildIndex) -> Result { + // I := PRF^Expand(c_par, [0x81] || sk_par || I2LEOSP(i)) + let I: [u8; 64] = PrfExpand::OrchardZip32Child.with_ad_slices( + &self.chain_code.0, + &[self.sk.to_bytes(), &index.0.to_le_bytes()], + ); + + // I_L is used as the child spending key sk_i. + let sk_i = SpendingKey::from_bytes(I[..32].try_into().unwrap()); + if sk_i.is_none().into() { + return Err(InvalidSkError); + } + let sk_i = sk_i.unwrap(); + + // I_R is used as the child chain code c_i. + let c_i = ChainCode(I[32..].try_into().unwrap()); + + let fvk: FullViewingKey = self.into(); + + Ok(Self { + depth: self.depth + 1, + parent_fvk_tag: FvkFingerprint::from(&fvk).tag(), + child_index: index, + chain_code: c_i, + sk: sk_i, + }) + } + + /// Returns sk of this ExtendedSpendingKey. + pub fn sk(&self) -> SpendingKey { + self.sk + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn derive_child() { + let seed = [0; 32]; + let xsk_m = ExtendedSpendingKey::master(&seed).unwrap(); + + let i_5 = 5; + let xsk_5 = xsk_m.derive_child(i_5.into()); + + assert!(xsk_5.is_ok()); + } + + #[test] + fn path() { + let seed = [0; 32]; + let xsk_m = ExtendedSpendingKey::master(&seed).unwrap(); + + let xsk_5h = xsk_m.derive_child(5.into()).unwrap(); + assert_eq!(ExtendedSpendingKey::from_path(&seed, &[5]), xsk_5h); + + let xsk_5h_7 = xsk_5h.derive_child(7.into()).unwrap(); + assert_eq!(ExtendedSpendingKey::from_path(&seed, &[5, 7]), xsk_5h_7); + } +} From e4a54cdf610ebef65075ce13bde38c5c5e92954a Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 31 Aug 2021 16:49:58 -0600 Subject: [PATCH 2/2] Improve error handling in zip32 APIs. --- src/keys.rs | 15 ++++++++--- src/zip32.rs | 74 ++++++++++++++++++++++++++++++++-------------------- 2 files changed, 58 insertions(+), 31 deletions(-) diff --git a/src/keys.rs b/src/keys.rs index 98acb7a1..223368f1 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -21,7 +21,7 @@ use crate::{ commit_ivk, diversify_hash, extract_p, ka_orchard, prf_nf, to_base, to_scalar, NonIdentityPallasPoint, NonZeroPallasBase, NonZeroPallasScalar, PrfExpand, }, - zip32::ExtendedSpendingKey, + zip32::{self, ChildIndex, ExtendedSpendingKey}, }; const KDF_ORCHARD_PERSONALIZATION: &[u8; 16] = b"Zcash_OrchardKDF"; @@ -74,9 +74,18 @@ impl SpendingKey { } /// Derives the Orchard spending key for the given seed, coin type, and account. - pub fn from_zip32_seed(seed: &[u8], coin_type: u32, account: u32) -> Self { + pub fn from_zip32_seed( + seed: &[u8], + coin_type: u32, + account: u32, + ) -> Result { // Call zip32 logic - ExtendedSpendingKey::from_path(&seed, &[ZIP32_PURPOSE, coin_type, account]).sk() + let path = &[ + ChildIndex::try_from(ZIP32_PURPOSE)?, + ChildIndex::try_from(coin_type)?, + ChildIndex::try_from(account)?, + ]; + ExtendedSpendingKey::from_path(seed, path).map(|esk| esk.sk()) } } diff --git a/src/zip32.rs b/src/zip32.rs index 0a342e29..d6a6db8c 100644 --- a/src/zip32.rs +++ b/src/zip32.rs @@ -1,6 +1,9 @@ //! Key structures for Orchard. -use std::{convert::TryInto, fmt}; +use std::{ + convert::{TryFrom, TryInto}, + fmt, +}; use blake2b_simd::Params as Blake2bParams; @@ -12,17 +15,22 @@ use crate::{ const ZIP32_ORCHARD_PERSONALIZATION: &[u8; 16] = b"ZcashIP32Orchard"; const ZIP32_ORCHARD_FVFP_PERSONALIZATION: &[u8; 16] = b"ZcashOrchardFVFP"; -/// A seed resulted in an invalid spending key -#[derive(Debug)] -pub struct InvalidSkError; +/// Errors produced in derivation of extended spending keys +#[derive(Debug, PartialEq, Eq)] +pub enum Error { + /// A seed resulted in an invalid spending key + InvalidSpendingKey, + /// A child index in a derivation path exceeded 2^31 + InvalidChildIndex(u32), +} -impl fmt::Display for InvalidSkError { +impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Seed produced invalid spending key.") } } -impl std::error::Error for InvalidSkError {} +//impl std::error::Error for Error {} /// An Orchard full viewing key fingerprint struct FvkFingerprint([u8; 32]); @@ -59,16 +67,19 @@ impl FvkTag { } /// A hardened child index for a derived key. -#[derive(Clone, Debug, PartialEq)] -struct ChildIndex(u32); +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct ChildIndex(u32); + +impl TryFrom for ChildIndex { + type Error = Error; -impl From for ChildIndex { - /// # Panics - /// /// `index` must be less than 2^31 - fn from(index: u32) -> Self { - assert!(index < (1 << 31)); - Self(index + (1 << 31)) + fn try_from(index: u32) -> Result { + if index < (1 << 31) { + Ok(Self(index + (1 << 31))) + } else { + Err(Error::InvalidChildIndex(32)) + } } } @@ -108,12 +119,12 @@ impl ExtendedSpendingKey { /// # Panics /// /// Panics if seed results in invalid spending key. - pub fn from_path(seed: &[u8], path: &[u32]) -> Self { - let mut xsk = Self::master(seed).unwrap(); - for &i in path.iter() { - xsk = xsk.derive_child(i.into()).unwrap(); + pub fn from_path(seed: &[u8], path: &[ChildIndex]) -> Result { + let mut xsk = Self::master(seed)?; + for i in path { + xsk = xsk.derive_child(*i)?; } - xsk + Ok(xsk) } /// Generates the master key of an Orchard extended spending key. @@ -125,7 +136,7 @@ impl ExtendedSpendingKey { /// # Panics /// /// Panics if the seed is shorter than 32 bytes or longer than 252 bytes. - fn master(seed: &[u8]) -> Result { + fn master(seed: &[u8]) -> Result { assert!(seed.len() >= 32 && seed.len() <= 252); // I := BLAKE2b-512("ZcashIP32Orchard", seed) let I: [u8; 64] = { @@ -139,7 +150,7 @@ impl ExtendedSpendingKey { // I_L is used as the master spending key sk_m. let sk_m = SpendingKey::from_bytes(I[..32].try_into().unwrap()); if sk_m.is_none().into() { - return Err(InvalidSkError); + return Err(Error::InvalidSpendingKey); } let sk_m = sk_m.unwrap(); @@ -163,7 +174,7 @@ impl ExtendedSpendingKey { /// [orchardchildkey]: https://zips.z.cash/zip-0032#orchard-child-key-derivation /// /// Discards index if it results in an invalid sk - fn derive_child(&self, index: ChildIndex) -> Result { + fn derive_child(&self, index: ChildIndex) -> Result { // I := PRF^Expand(c_par, [0x81] || sk_par || I2LEOSP(i)) let I: [u8; 64] = PrfExpand::OrchardZip32Child.with_ad_slices( &self.chain_code.0, @@ -173,7 +184,7 @@ impl ExtendedSpendingKey { // I_L is used as the child spending key sk_i. let sk_i = SpendingKey::from_bytes(I[..32].try_into().unwrap()); if sk_i.is_none().into() { - return Err(InvalidSkError); + return Err(Error::InvalidSpendingKey); } let sk_i = sk_i.unwrap(); @@ -207,7 +218,7 @@ mod tests { let xsk_m = ExtendedSpendingKey::master(&seed).unwrap(); let i_5 = 5; - let xsk_5 = xsk_m.derive_child(i_5.into()); + let xsk_5 = xsk_m.derive_child(i_5.try_into().unwrap()); assert!(xsk_5.is_ok()); } @@ -217,10 +228,17 @@ mod tests { let seed = [0; 32]; let xsk_m = ExtendedSpendingKey::master(&seed).unwrap(); - let xsk_5h = xsk_m.derive_child(5.into()).unwrap(); - assert_eq!(ExtendedSpendingKey::from_path(&seed, &[5]), xsk_5h); + let xsk_5h = xsk_m.derive_child(5.try_into().unwrap()).unwrap(); + assert_eq!( + ExtendedSpendingKey::from_path(&seed, &[5.try_into().unwrap()]).unwrap(), + xsk_5h + ); - let xsk_5h_7 = xsk_5h.derive_child(7.into()).unwrap(); - assert_eq!(ExtendedSpendingKey::from_path(&seed, &[5, 7]), xsk_5h_7); + let xsk_5h_7 = xsk_5h.derive_child(7.try_into().unwrap()).unwrap(); + assert_eq!( + ExtendedSpendingKey::from_path(&seed, &[5.try_into().unwrap(), 7.try_into().unwrap()]) + .unwrap(), + xsk_5h_7 + ); } }