From e87fb329f974fa135e3e99c050aebf76e3eeea62 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 11 Jul 2018 12:43:15 +0100 Subject: [PATCH] Child key derivation --- Cargo.toml | 1 + src/lib.rs | 186 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 178 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 764a2a0..93978c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ homepage = "https://github.com/zcash-hackworks/zip32" repository = "https://github.com/zcash-hackworks/zip32" [dependencies] +byteorder = "1" lazy_static = "1.0" pairing = "0.14.2" diff --git a/src/lib.rs b/src/lib.rs index 29599e3..f9b68d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,13 @@ extern crate blake2_rfc; +extern crate byteorder; #[macro_use] extern crate lazy_static; extern crate pairing; extern crate sapling_crypto; use blake2_rfc::blake2b::{Blake2b, Blake2bResult}; -use pairing::{bls12_381::Bls12, PrimeField, PrimeFieldRepr}; +use byteorder::{ByteOrder, LittleEndian}; +use pairing::{bls12_381::Bls12, Field, PrimeField, PrimeFieldRepr}; use sapling_crypto::{ jubjub::{FixedGenerators, JubjubBls12, JubjubEngine, JubjubParams, ToUniform}, primitives::ViewingKey, @@ -23,16 +25,30 @@ pub const ZIP32_SAPLING_FVFP_PERSONALIZATION: &'static [u8; 16] = b"ZcashSapling /// PRF^expand(sk, t) := BLAKE2b-512("Zcash_ExpandSeed", sk || t) fn prf_expand(sk: &[u8], t: &[u8]) -> Blake2bResult { + prf_expand_vec(sk, &vec![t]) +} + +fn prf_expand_vec(sk: &[u8], ts: &[&[u8]]) -> Blake2bResult { let mut h = Blake2b::with_params(64, &[], &[], PRF_EXPAND_PERSONALIZATION); h.update(sk); - h.update(t); + for t in ts { + h.update(t); + } h.finalize() } /// An outgoing viewing key -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq)] struct OutgoingViewingKey([u8; 32]); +impl OutgoingViewingKey { + fn derive_child(&self, i_l: &[u8]) -> Self { + let mut ovk = [0u8; 32]; + ovk.copy_from_slice(&prf_expand_vec(i_l, &[&[0x15], &self.0]).as_bytes()[..32]); + OutgoingViewingKey(ovk) + } +} + /// A Sapling expanded spending key struct ExpandedSpendingKey { ask: E::Fs, @@ -56,6 +72,15 @@ impl ExpandedSpendingKey { ExpandedSpendingKey { ask, nsk, ovk } } + fn derive_child(&self, i_l: &[u8]) -> Self { + let mut ask = E::Fs::to_uniform(prf_expand(i_l, &[0x13]).as_bytes()); + let mut nsk = E::Fs::to_uniform(prf_expand(i_l, &[0x14]).as_bytes()); + ask.add_assign(&self.ask); + nsk.add_assign(&self.nsk); + let ovk = self.ovk.derive_child(i_l); + ExpandedSpendingKey { ask, nsk, ovk } + } + fn to_bytes(&self) -> [u8; 96] { let mut result = [0u8; 96]; self.ask @@ -86,6 +111,24 @@ impl FullViewingKey { } } + fn derive_child(&self, i_l: &[u8], params: &E::Params) -> Self { + let i_ask = E::Fs::to_uniform(prf_expand(i_l, &[0x13]).as_bytes()); + let i_nsk = E::Fs::to_uniform(prf_expand(i_l, &[0x14]).as_bytes()); + let ak = params + .generator(FixedGenerators::SpendingKeyGenerator) + .mul(i_ask, params) + .add(&self.vk.ak, params); + let nk = params + .generator(FixedGenerators::ProofGenerationKey) + .mul(i_nsk, params) + .add(&self.vk.nk, params); + + FullViewingKey { + vk: ViewingKey { ak, nk }, + ovk: self.ovk.derive_child(i_l), + } + } + fn to_bytes(&self) -> [u8; 96] { let mut result = [0u8; 96]; self.vk @@ -117,7 +160,7 @@ impl<'a, E: JubjubEngine> From<&'a FullViewingKey> for FVKFingerprint { } /// A Sapling full viewing key tag -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug, PartialEq)] struct FVKTag([u8; 4]); impl<'a> From<&'a FVKFingerprint> for FVKTag { @@ -141,7 +184,7 @@ impl FVKTag { } /// A child index for a derived key -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum ChildIndex { NonHardened(u32), Hardened(u32), // Hardened(n) == n + (1 << 31) == n' in path notation @@ -161,11 +204,11 @@ impl ChildIndex { } /// A chain code -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug, PartialEq)] struct ChainCode([u8; 32]); /// A key used to derive diversifiers for a particular child key -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug, PartialEq)] struct DiversifierKey([u8; 32]); impl DiversifierKey { @@ -174,6 +217,12 @@ impl DiversifierKey { dk_m.copy_from_slice(&prf_expand(sk_m, &[0x10]).as_bytes()[..32]); DiversifierKey(dk_m) } + + fn derive_child(&self, i_l: &[u8]) -> Self { + let mut dk = [0u8; 32]; + dk.copy_from_slice(&prf_expand_vec(i_l, &[&[0x16], &self.0]).as_bytes()[..32]); + DiversifierKey(dk) + } } /// A Sapling extended spending key @@ -196,6 +245,29 @@ pub struct ExtendedFullViewingKey { dk: DiversifierKey, } +impl std::cmp::PartialEq for ExtendedFullViewingKey { + fn eq(&self, rhs: &ExtendedFullViewingKey) -> 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.fvk.vk.ak == rhs.fvk.vk.ak + && self.fvk.vk.nk == rhs.fvk.vk.nk + && self.fvk.ovk == rhs.fvk.ovk + && self.dk == rhs.dk + } +} + +impl std::fmt::Debug for ExtendedFullViewingKey { + fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + write!( + f, + "ExtendedFullViewingKey(d = {}, tag_p = {:?}, i = {:?})", + self.depth, self.parent_fvk_tag, self.child_index + ) + } +} + impl ExtendedSpendingKey { pub fn master(seed: &[u8]) -> Self { let mut h = Blake2b::with_params(64, &[], &[], ZIP32_SAPLING_MASTER_PERSONALIZATION); @@ -215,6 +287,40 @@ impl ExtendedSpendingKey { dk: DiversifierKey::master(sk_m), } } + + pub fn derive_child(&self, i: ChildIndex) -> Self { + let fvk = FullViewingKey::from_expanded_spending_key(&self.xsk, &JUBJUB); + let tmp = match i { + ChildIndex::Hardened(i) => { + let mut le_i = [0; 4]; + LittleEndian::write_u32(&mut le_i, i + (1 << 31)); + prf_expand_vec( + &self.chain_code.0, + &[&[0x11], &self.xsk.to_bytes(), &self.dk.0, &le_i], + ) + } + ChildIndex::NonHardened(i) => { + let mut le_i = [0; 4]; + LittleEndian::write_u32(&mut le_i, i); + prf_expand_vec( + &self.chain_code.0, + &[&[0x12], &fvk.to_bytes(), &self.dk.0, &le_i], + ) + } + }; + let i_l = &tmp.as_bytes()[..32]; + let mut c_i = [0u8; 32]; + c_i.copy_from_slice(&tmp.as_bytes()[32..]); + + ExtendedSpendingKey { + depth: self.depth + 1, + parent_fvk_tag: FVKFingerprint::from(&fvk).into(), + child_index: i, + chain_code: ChainCode(c_i), + xsk: self.xsk.derive_child(i_l), + dk: self.dk.derive_child(i_l), + } + } } impl<'a> From<&'a ExtendedSpendingKey> for ExtendedFullViewingKey { @@ -230,10 +336,72 @@ impl<'a> From<&'a ExtendedSpendingKey> for ExtendedFullViewingKey { } } +impl ExtendedFullViewingKey { + pub fn derive_child(&self, i: ChildIndex) -> Result { + let tmp = match i { + ChildIndex::Hardened(_) => return Err(()), + ChildIndex::NonHardened(i) => { + let mut le_i = [0; 4]; + LittleEndian::write_u32(&mut le_i, i); + prf_expand_vec( + &self.chain_code.0, + &[&[0x12], &self.fvk.to_bytes(), &self.dk.0, &le_i], + ) + } + }; + let i_l = &tmp.as_bytes()[..32]; + let mut c_i = [0u8; 32]; + c_i.copy_from_slice(&tmp.as_bytes()[32..]); + + Ok(ExtendedFullViewingKey { + depth: self.depth + 1, + parent_fvk_tag: FVKFingerprint::from(&self.fvk).into(), + child_index: i, + chain_code: ChainCode(c_i), + fvk: self.fvk.derive_child(i_l, &JUBJUB), + dk: self.dk.derive_child(i_l), + }) + } +} + #[cfg(test)] mod tests { + use super::*; + + #[test] + fn derive_nonhardened_child() { + let seed = [0; 32]; + let xsk_m = ExtendedSpendingKey::master(&seed); + let xfvk_m = ExtendedFullViewingKey::from(&xsk_m); + + let i_5 = ChildIndex::NonHardened(5); + let xsk_5 = xsk_m.derive_child(i_5); + let xfvk_5 = xfvk_m.derive_child(i_5); + + assert!(xfvk_5.is_ok()); + assert_eq!(ExtendedFullViewingKey::from(&xsk_5), xfvk_5.unwrap()); + } + #[test] - fn it_works() { - assert_eq!(2 + 2, 4); + fn derive_hardened_child() { + let seed = [0; 32]; + let xsk_m = ExtendedSpendingKey::master(&seed); + let xfvk_m = ExtendedFullViewingKey::from(&xsk_m); + + let i_5h = ChildIndex::Hardened(5); + let xsk_5h = xsk_m.derive_child(i_5h); + let xfvk_5h = xfvk_m.derive_child(i_5h); + + // Cannot derive a hardened child from an ExtendedFullViewingKey + assert!(xfvk_5h.is_err()); + let xfvk_5h = ExtendedFullViewingKey::from(&xsk_5h); + + let i_7 = ChildIndex::NonHardened(7); + let xsk_5h_7 = xsk_5h.derive_child(i_7); + let xfvk_5h_7 = xfvk_5h.derive_child(i_7); + + // But we *can* derive a non-hardened child from a hardened parent + assert!(xfvk_5h_7.is_ok()); + assert_eq!(ExtendedFullViewingKey::from(&xsk_5h_7), xfvk_5h_7.unwrap()); } }