-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement ZIP 32 arbitrary key derivation
- Loading branch information
Showing
5 changed files
with
219 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
//! Arbitrary key derivation. | ||
//! | ||
//! Defined in [ZIP32: Arbitrary key derivation][arbkd]. | ||
//! | ||
//! [arbkd]: https://zips.z.cash/zip-0032#specification-arbitrary-key-derivation | ||
|
||
use zcash_spec::PrfExpand; | ||
|
||
use crate::{ | ||
hardened_only::{Context, HardenedOnlyKey}, | ||
ChildIndex, | ||
}; | ||
|
||
struct Arbitrary; | ||
|
||
impl Context for Arbitrary { | ||
const MKG_DOMAIN: [u8; 16] = *b"ZcashArbitraryKD"; | ||
const CKD_DOMAIN: PrfExpand<([u8; 32], [u8; 4])> = PrfExpand::ARBITRARY_ZIP32_CHILD; | ||
} | ||
|
||
/// An arbitrary extended secret key. | ||
/// | ||
/// Defined in [ZIP32: Arbitrary key derivation][arbkd]. | ||
/// | ||
/// [arbkd]: https://zips.z.cash/zip-0032#specification-arbitrary-key-derivation | ||
pub struct SecretKey { | ||
inner: HardenedOnlyKey<Arbitrary>, | ||
} | ||
|
||
impl SecretKey { | ||
/// Returns the the child key corresponding to the path derived from the master key | ||
/// for the given input key material. | ||
/// | ||
/// # Panics | ||
/// | ||
/// Panics if: | ||
/// - the context string is empty or longer than 65535 bytes. | ||
/// - the seed is shorter than 32 bytes or longer than 252 bytes. | ||
pub fn from_path(context_string: &[u8], seed: &[u8], path: &[ChildIndex]) -> Self { | ||
let mut xsk = Self::master(context_string, seed); | ||
for i in path { | ||
xsk = xsk.derive_child(*i); | ||
} | ||
xsk | ||
} | ||
|
||
/// Generates the master key of an Arbitrary extended secret key. | ||
/// | ||
/// Defined in [ZIP32: Arbitrary master key generation][mkgarb]. | ||
/// | ||
/// [mkgarb]: https://zips.z.cash/zip-0032#arbitrary-master-key-generation | ||
/// | ||
/// # Panics | ||
/// | ||
/// Panics if: | ||
/// - the context string is empty or longer than 65535 bytes. | ||
/// - the seed is shorter than 32 bytes or longer than 252 bytes. | ||
fn master(context_string: &[u8], seed: &[u8]) -> Self { | ||
assert!(!context_string.is_empty()); | ||
let context_len = u16::try_from(context_string.len()) | ||
.expect("context string should be at most 65535 bytes"); | ||
|
||
let seed_len = u8::try_from(seed.len()).expect("seed should be at most 252 bytes"); | ||
assert!((32..=252).contains(&seed_len)); | ||
|
||
let ikm = &[ | ||
&context_len.to_le_bytes(), | ||
context_string, | ||
&[seed_len], | ||
seed, | ||
]; | ||
|
||
Self { | ||
inner: HardenedOnlyKey::master(ikm), | ||
} | ||
} | ||
|
||
/// Derives a child key from a parent key at a given index. | ||
/// | ||
/// Defined in [ZIP32: Arbitrary-only child key derivation][ckdarb]. | ||
/// | ||
/// [ckdarb]: https://zips.z.cash/zip-0032#arbitrary-child-key-derivation | ||
fn derive_child(&self, index: ChildIndex) -> Self { | ||
Self { | ||
inner: self.inner.derive_child(index), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
//! Generic implementation of hardened-only key derivation. | ||
//! | ||
//! Defined in [ZIP32: Hardened-only key derivation][hkd]. | ||
//! | ||
//! Any usage of the types in this module needs to have a corresponding ZIP. If you just | ||
//! want to derive an arbitrary key in a ZIP 32-compatible manner without ecosystem-wide | ||
//! coordination, use [`arbitrary::SecretKey`]. | ||
//! | ||
//! [hkd]: https://zips.z.cash/zip-0032#specification-hardened-only-key-derivation | ||
//! [`arbitrary::SecretKey`]: crate::arbitrary::SecretKey | ||
|
||
use core::marker::PhantomData; | ||
|
||
use blake2b_simd::Params as Blake2bParams; | ||
use subtle::{Choice, ConstantTimeEq}; | ||
use zcash_spec::PrfExpand; | ||
|
||
use crate::{ChainCode, ChildIndex}; | ||
|
||
/// The context in which hardened-only key derivation is instantiated. | ||
pub trait Context { | ||
/// A 16-byte domain separator used during master key generation. | ||
/// | ||
/// It SHOULD be disjoint from other domain separators used with BLAKE2b in Zcash | ||
/// protocols. | ||
const MKG_DOMAIN: [u8; 16]; | ||
/// The `PrfExpand` domain used during child key derivation. | ||
const CKD_DOMAIN: PrfExpand<([u8; 32], [u8; 4])>; | ||
} | ||
|
||
/// An arbitrary extended secret key. | ||
/// | ||
/// Defined in [ZIP32: Hardened-only key derivation][hkd]. | ||
/// | ||
/// [hkd]: https://zips.z.cash/zip-0032#specification-hardened-only-key-derivation | ||
#[derive(Clone, Debug)] | ||
pub struct HardenedOnlyKey<C: Context> { | ||
sk: [u8; 32], | ||
chain_code: ChainCode, | ||
_context: PhantomData<C>, | ||
} | ||
|
||
impl<C: Context> ConstantTimeEq for HardenedOnlyKey<C> { | ||
fn ct_eq(&self, rhs: &Self) -> Choice { | ||
self.chain_code.ct_eq(&rhs.chain_code) & self.sk.ct_eq(&rhs.sk) | ||
} | ||
} | ||
|
||
#[allow(non_snake_case)] | ||
impl<C: Context> HardenedOnlyKey<C> { | ||
/// Exposes the parts of this key. | ||
pub fn parts(&self) -> (&[u8; 32], &ChainCode) { | ||
(&self.sk, &self.chain_code) | ||
} | ||
|
||
/// Generates the master key of a hardened-only extended secret key. | ||
/// | ||
/// Defined in [ZIP32: Hardened-only master key generation][mkgh]. | ||
/// | ||
/// [mkgh]: https://zips.z.cash/zip-0032#hardened-only-master-key-generation | ||
pub fn master(ikm: &[&[u8]]) -> Self { | ||
// I := BLAKE2b-512(Context.MKGDomain, IKM) | ||
let I: [u8; 64] = { | ||
let mut I = Blake2bParams::new() | ||
.hash_length(64) | ||
.personal(&C::MKG_DOMAIN) | ||
.to_state(); | ||
for input in ikm { | ||
I.update(input); | ||
} | ||
I.finalize().as_bytes().try_into().unwrap() | ||
}; | ||
|
||
let (I_L, I_R) = I.split_at(32); | ||
|
||
// I_L is used as the master secret key sk_m. | ||
let sk_m = I_L.try_into().unwrap(); | ||
|
||
// I_R is used as the master chain code c_m. | ||
let c_m = ChainCode::new(I_R.try_into().unwrap()); | ||
|
||
Self { | ||
sk: sk_m, | ||
chain_code: c_m, | ||
_context: PhantomData, | ||
} | ||
} | ||
|
||
/// Derives a child key from a parent key at a given index. | ||
/// | ||
/// Defined in [ZIP32: Hardened-only child key derivation][ckdh]. | ||
/// | ||
/// [ckdh]: https://zips.z.cash/zip-0032#hardened-only-child-key-derivation | ||
pub fn derive_child(&self, index: ChildIndex) -> Self { | ||
// I := PRF^Expand(c_par, [Context.CKDDomain] || sk_par || I2LEOSP(i)) | ||
let I: [u8; 64] = C::CKD_DOMAIN.with( | ||
self.chain_code.as_bytes(), | ||
&self.sk, | ||
&index.index().to_le_bytes(), | ||
); | ||
|
||
let (I_L, I_R) = I.split_at(32); | ||
|
||
// I_L is used as the child spending key sk_i. | ||
let sk_i = I_L.try_into().unwrap(); | ||
|
||
// I_R is used as the child chain code c_i. | ||
let c_i = ChainCode::new(I_R.try_into().unwrap()); | ||
|
||
Self { | ||
sk: sk_i, | ||
chain_code: c_i, | ||
_context: PhantomData, | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters