From c3e24794f07a533233309084787fe5ed1eeb9311 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Thu, 15 Jul 2021 15:18:01 +0800 Subject: [PATCH] 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); + } +}