-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support tagged ZIP 32 child derivation for registered application pro…
…tocols. Signed-off-by: Daira-Emma Hopwood <daira@jacaranda.org>
- Loading branch information
Showing
7 changed files
with
262 additions
and
4 deletions.
There are no files selected for viewing
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
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
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
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,210 @@ | ||
//! Registered key derivation. | ||
//! | ||
//! In some contexts there is a need for deriving a key for a registered application | ||
//! protocol (defined by a ZIP) with a similar derivation path to existing key material | ||
//! (for example, deriving an arbitrary account-level key). The following instantiation | ||
//! of the [hardened key generation framework] may be used for this purpose. | ||
//! | ||
//! The keys derived by the functions in this module will be unrelated to any keys | ||
//! derived by functions in the [`crate::arbitrary`] module, even if the same context | ||
//! string and seed are used. | ||
//! | ||
//! Defined in [ZIP32: Registered key derivation][regkd]. | ||
//! | ||
//! [hardened key generation framework]: crate::hardened_only | ||
//! [regkd]: https://zips.z.cash/zip-0032#specification-registered-key-derivation | ||
use zcash_spec::PrfExpand; | ||
|
||
use crate::{ | ||
hardened_only::{Context, HardenedOnlyKey}, | ||
ChainCode, ChildIndex, | ||
}; | ||
|
||
struct Registered; | ||
|
||
impl Context for Registered { | ||
const MKG_DOMAIN: [u8; 16] = *b"ZIPRegistered_KD"; | ||
const CKD_DOMAIN: PrfExpand<([u8; 32], [u8; 4])> = PrfExpand::REGISTERED_ZIP32_CHILD; | ||
} | ||
|
||
/// A registered extended secret key. | ||
/// | ||
/// Defined in [ZIP32: Registered key derivation][arbkd]. | ||
/// | ||
/// [regkd]: https://zips.z.cash/zip-0032#specification-registered-key-derivation | ||
pub struct SecretKey { | ||
inner: HardenedOnlyKey<Registered>, | ||
} | ||
|
||
fn with_ikm<F, T>(context_string: &[u8], seed: &[u8], f: F) -> T | ||
where | ||
F: FnOnce(&[&[u8]]) -> T, | ||
{ | ||
let context_len = | ||
u8::try_from(context_string.len()).expect("context string should be at most 252 bytes"); | ||
assert!((1..=252).contains(&context_len)); | ||
|
||
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], context_string, &[seed_len], seed]; | ||
|
||
f(ikm) | ||
} | ||
|
||
impl SecretKey { | ||
/// Derives a key for a registered application protocol at the given path from the | ||
/// given seed. Each path element may consist of an index and optional tag. | ||
/// | ||
/// - `zip_number` is the number of the ZIP defining the application protocol. | ||
/// The corresponding hardened index (with no tag) will be prepended to the | ||
/// `path`. | ||
/// - `context_string` is an identifier for the context in which this key will be | ||
/// used. It must be globally unique. | ||
/// | ||
/// # Panics | ||
/// | ||
/// Panics if: | ||
/// - the context string is empty or longer than 252 bytes. | ||
/// - the seed is shorter than 32 bytes or longer than 252 bytes. | ||
pub fn from_path( | ||
zip_number: u16, | ||
context_string: &[u8], | ||
seed: &[u8], | ||
path: &[(ChildIndex, Option<&[u8]>)], | ||
) -> Self { | ||
let mut xsk = Self::master(context_string, seed).derive_child_with_tag( | ||
ChildIndex::hardened(u32::from(zip_number)), | ||
None, | ||
false, | ||
); | ||
|
||
for (i, tag) in path { | ||
xsk = xsk.derive_child_with_tag(*i, *tag, false); | ||
} | ||
xsk | ||
} | ||
|
||
/// Derives 64 bytes of key material for a registered application protocol at | ||
/// the given path from the given seed. Each path element may consist of an | ||
/// index and optional tag. | ||
/// | ||
/// - `zip_number` is the number of the ZIP defining the application protocol. | ||
/// The corresponding hardened index (with no tag) will be prepended to the | ||
/// `path`. | ||
/// - `context_string` is an identifier for the context in which this key will be | ||
/// used. It must be globally unique. | ||
/// | ||
/// # Panics | ||
/// | ||
/// Panics if: | ||
/// - the context string is empty or longer than 252 bytes. | ||
/// - the seed is shorter than 32 bytes or longer than 252 bytes. | ||
pub fn full_width_key_from_path( | ||
zip_number: u16, | ||
context_string: &[u8], | ||
seed: &[u8], | ||
path: &[(ChildIndex, Option<&[u8]>)], | ||
) -> [u8; 64] { | ||
let mut xsk = Self::master(context_string, seed); | ||
|
||
for (j, (i, tag)) in Some((ChildIndex::hardened(u32::from(zip_number)), None)) | ||
.iter() | ||
.chain(path) | ||
.enumerate() | ||
{ | ||
xsk = xsk.derive_child_with_tag(*i, *tag, j == path.len()); | ||
} | ||
|
||
// Concatenate the key data and chain code to obtain a full-width key. | ||
// This is safe because we only do it on a child key that has been | ||
// derived with `full_width_leaf: true`, which ensures the same key | ||
// cannot be obtained directly from any public API. | ||
|
||
let (sk, c) = xsk.inner.into_parts(); | ||
// Re-concatenate the key parts. | ||
let mut key = [0; 64]; | ||
key[..32].copy_from_slice(&sk); | ||
key[32..].copy_from_slice(&c.0); | ||
key | ||
} | ||
|
||
/// Generates the master key of a registered extended secret key. | ||
/// | ||
/// Defined in [ZIP32: Registered master key generation][mkgarb]. | ||
/// | ||
/// [regmkg]: https://zips.z.cash/zip-0032#arbitrary-master-key-generation | ||
/// | ||
/// # Panics | ||
/// | ||
/// Panics if: | ||
/// - the context string is empty or longer than 252 bytes. | ||
/// - the seed is shorter than 32 bytes or longer than 252 bytes. | ||
fn master(context_string: &[u8], seed: &[u8]) -> Self { | ||
with_ikm(context_string, seed, |ikm| Self { | ||
inner: HardenedOnlyKey::master(ikm), | ||
}) | ||
} | ||
|
||
/// Derives a child key from a parent key at a given index and optional tag. | ||
/// | ||
/// Defined in [ZIP32: Arbitrary and registered child key derivation][ckdarb]. | ||
/// | ||
/// [ckdarb]: https://zips.z.cash/zip-0032#arbitrary-and-registered-child-key-derivation | ||
fn derive_child_with_tag( | ||
&self, | ||
index: ChildIndex, | ||
tag: Option<&[u8]>, | ||
full_width_leaf: bool, | ||
) -> Self { | ||
Self { | ||
inner: self | ||
.inner | ||
.derive_child_with_tag(index, tag, full_width_leaf), | ||
} | ||
} | ||
|
||
/// Returns the key material for this arbitrary key. | ||
pub fn data(&self) -> &[u8; 32] { | ||
self.inner.parts().0 | ||
} | ||
|
||
/// Returns the chain code for this arbitrary key. | ||
pub fn chain_code(&self) -> &ChainCode { | ||
self.inner.parts().1 | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::SecretKey; | ||
use crate::ChildIndex; | ||
|
||
struct TestVector { | ||
context_string: &'static [u8], | ||
seed: [u8; 32], | ||
zip_number: u16, | ||
path: &'static [(u32, Option<&'static [u8]>)], | ||
sk: [u8; 32], | ||
c: [u8; 32], | ||
} | ||
|
||
// From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/zip_0032_registered.py | ||
const TEST_VECTORS: &'static [TestVector] = &[]; | ||
|
||
#[test] | ||
fn test_vectors() { | ||
for tv in TEST_VECTORS { | ||
let path = tv | ||
.path | ||
.into_iter() | ||
.map(|(i, tag)| (ChildIndex::from_index(*i).expect("hardened"), *tag)) | ||
.collect::<alloc::vec::Vec<_>>(); | ||
|
||
let sk = SecretKey::from_path(tv.zip_number, tv.context_string, &tv.seed, &path); | ||
assert_eq!(sk.data(), &tv.sk); | ||
assert_eq!(sk.chain_code().as_bytes(), &tv.c); | ||
} | ||
} | ||
} |