Child key derivation

This commit is contained in:
Jack Grigg 2018-07-11 12:43:15 +01:00
parent 0e1d440287
commit e87fb329f9
No known key found for this signature in database
GPG Key ID: 1B8D649257DB0829
2 changed files with 181 additions and 12 deletions

View File

@ -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"

View File

@ -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<E: JubjubEngine> {
ask: E::Fs,
@ -56,6 +72,15 @@ impl<E: JubjubEngine> ExpandedSpendingKey<E> {
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<E: JubjubEngine> FullViewingKey<E> {
}
}
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<E>> 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 {
}
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
impl ExtendedFullViewingKey {
pub fn derive_child(&self, i: ChildIndex) -> Result<Self, ()> {
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 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());
}
}