mirror of https://github.com/zcash/zip32.git
Compare commits
14 Commits
Author | SHA1 | Date |
---|---|---|
|
bbd4e9c243 | |
|
372a4887f0 | |
|
92aec1f9f4 | |
|
b9787dfe2c | |
|
6632f2c1b5 | |
|
2c177cb81c | |
|
49c65fc46b | |
|
fa7805dd7d | |
|
bd9652155c | |
|
bcbfd01921 | |
|
6cb332b3a1 | |
|
e5b4ea2a43 | |
|
c7442d7d36 | |
|
9ed8d60b2e |
20
CHANGELOG.md
20
CHANGELOG.md
|
@ -7,6 +7,26 @@ and this library adheres to Rust's notion of
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- `zip32::AccountId::const_from_u32`
|
||||
|
||||
## [0.2.0] - 2025-02-20
|
||||
|
||||
### Added
|
||||
- `zip32::registered` module, implementing hardened-only key derivation for
|
||||
an application protocol specified in a ZIP.
|
||||
- `zip32::ChildIndex::PRIVATE_USE`
|
||||
- `zip32::hardened_only::HardenedOnlyKey::{from_parts, derive_child_with_tag}`
|
||||
|
||||
### Changed
|
||||
- The type of `zip32::hardened_only::Context::CKD_DOMAIN` has changed, in
|
||||
order to support child derivation with tags.
|
||||
|
||||
### Deprecated
|
||||
- `zip32::arbitrary::SecretKey::into_full_width_key`. This API is
|
||||
cryptographically unsafe because it depends on a restriction that cannot
|
||||
be enforced. Use `zip32::registered::cryptovalue_from_subpath` instead.
|
||||
|
||||
## [0.1.3] - 2024-12-13
|
||||
|
||||
### Fixed
|
||||
|
|
|
@ -51,16 +51,16 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
|||
|
||||
[[package]]
|
||||
name = "zcash_spec"
|
||||
version = "0.1.2"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9cede95491c2191d3e278cab76e097a44b17fde8d6ca0d4e3a22cf4807b2d857"
|
||||
checksum = "ded3f58b93486aa79b85acba1001f5298f27a46489859934954d262533ee2915"
|
||||
dependencies = [
|
||||
"blake2b_simd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip32"
|
||||
version = "0.1.3"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"blake2b_simd",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "zip32"
|
||||
version = "0.1.3"
|
||||
version = "0.2.0"
|
||||
authors = [
|
||||
"Jack Grigg <jack@electriccoin.co>",
|
||||
"Kris Nuttycombe <kris@electriccoin.co>",
|
||||
|
@ -17,7 +17,7 @@ rust-version = "1.60"
|
|||
blake2b_simd = { version = "1", default-features = false }
|
||||
memuse = { version = "0.2.2", default-features = false }
|
||||
subtle = { version = "2.2.3", default-features = false }
|
||||
zcash_spec = "0.1.2"
|
||||
zcash_spec = "0.2.1"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_matches = "1.5"
|
||||
|
|
169
src/arbitrary.rs
169
src/arbitrary.rs
|
@ -1,56 +1,53 @@
|
|||
//! Arbitrary key derivation.
|
||||
//! Ad-hoc ("arbitrary") key derivation.
|
||||
//!
|
||||
//! In some contexts there is a need for deriving arbitrary keys with the same derivation
|
||||
//! path as existing key material (for example, deriving an arbitrary account-level key),
|
||||
//! without the need for ecosystem-wide coordination. The following instantiation of the
|
||||
//! [hardened key generation framework] may be used for this purpose.
|
||||
//! For compatibility with existing deployments, we define a mechanism to generate
|
||||
//! ad-hoc key trees for private use by applications, without ecosystem coordination,
|
||||
//! using the [hardened key derivation framework].
|
||||
//!
|
||||
//! Defined in [ZIP32: Arbitrary key derivation][arbkd].
|
||||
//! This used to be called "arbitrary key derivation" in ZIP 32, but that term caused
|
||||
//! confusion as to the applicability of the mechanism and so has been renamed to
|
||||
//! "ad-hoc key derivation". The module name is still `arbitrary` for compatibility.
|
||||
//!
|
||||
//! [hardened key generation framework]: crate::hardened_only
|
||||
//! [arbkd]: https://zips.z.cash/zip-0032#specification-arbitrary-key-derivation
|
||||
//! Since there is no guarantee of non-collision between different application protocols,
|
||||
//! and no way to tie these key trees to well-defined specification or documentation
|
||||
//! processes, use of this mechanism is NOT RECOMMENDED for new protocols.
|
||||
//!
|
||||
//! The keys derived by the functions in this module will be unrelated to any keys
|
||||
//! derived by functions in the [`crate::registered`] module, even if the same context
|
||||
//! string and seed are used.
|
||||
//!
|
||||
//! Defined in [ZIP 32: Ad-hoc key derivation (deprecated)][adhockd].
|
||||
//!
|
||||
//! [hardened key derivation framework]: crate::hardened_only
|
||||
//! [adhockd]: https://zips.z.cash/zip-0032#specification-ad-hoc-key-derivation-deprecated
|
||||
|
||||
use zcash_spec::PrfExpand;
|
||||
|
||||
use crate::{
|
||||
hardened_only::{Context, HardenedOnlyKey},
|
||||
hardened_only::{Context, HardenedOnlyCkdDomain, HardenedOnlyKey},
|
||||
ChainCode, ChildIndex,
|
||||
};
|
||||
|
||||
struct Arbitrary;
|
||||
use super::with_ikm;
|
||||
|
||||
impl Context for Arbitrary {
|
||||
struct Adhoc;
|
||||
|
||||
impl Context for Adhoc {
|
||||
const MKG_DOMAIN: [u8; 16] = *b"ZcashArbitraryKD";
|
||||
const CKD_DOMAIN: PrfExpand<([u8; 32], [u8; 4])> = PrfExpand::ARBITRARY_ZIP32_CHILD;
|
||||
const CKD_DOMAIN: HardenedOnlyCkdDomain = PrfExpand::ADHOC_ZIP32_CHILD;
|
||||
}
|
||||
|
||||
/// An arbitrary extended secret key.
|
||||
/// An ad-hoc extended secret key.
|
||||
///
|
||||
/// Defined in [ZIP32: Arbitrary key derivation][arbkd].
|
||||
/// Defined in [ZIP 32: Ad-hoc key generation (deprecated)][adhockd].
|
||||
///
|
||||
/// [arbkd]: https://zips.z.cash/zip-0032#specification-arbitrary-key-derivation
|
||||
/// [adhockd]: https://zips.z.cash/zip-0032#specification-ad-hoc-key-derivation-deprecated
|
||||
pub struct SecretKey {
|
||||
inner: HardenedOnlyKey<Arbitrary>,
|
||||
}
|
||||
|
||||
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)
|
||||
inner: HardenedOnlyKey<Adhoc>,
|
||||
}
|
||||
|
||||
impl SecretKey {
|
||||
/// Derives an arbitrary key at the given path from the given seed.
|
||||
/// Derives an ad-hoc key at the given path from the given seed.
|
||||
///
|
||||
/// `context_string` is an identifier for the context in which this key will be used.
|
||||
/// It must be globally unique.
|
||||
|
@ -68,11 +65,11 @@ impl SecretKey {
|
|||
xsk
|
||||
}
|
||||
|
||||
/// Generates the master key of an Arbitrary extended secret key.
|
||||
/// Generates the master key of an ad-hoc extended secret key.
|
||||
///
|
||||
/// Defined in [ZIP32: Arbitrary master key generation][mkgarb].
|
||||
/// Defined in [ZIP 32: Ad-hoc master key generation (deprecated)][adhocmkg].
|
||||
///
|
||||
/// [mkgarb]: https://zips.z.cash/zip-0032#arbitrary-master-key-generation
|
||||
/// [adhocmkg]: https://zips.z.cash/zip-0032#ad-hoc-master-key-generation-deprecated
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
|
@ -87,21 +84,21 @@ impl SecretKey {
|
|||
|
||||
/// Derives a child key from a parent key at a given index.
|
||||
///
|
||||
/// Defined in [ZIP32: Arbitrary-only child key derivation][ckdarb].
|
||||
/// Defined in [ZIP 32: Ad-hoc child key derivation (deprecated)][adhocckd].
|
||||
///
|
||||
/// [ckdarb]: https://zips.z.cash/zip-0032#arbitrary-child-key-derivation
|
||||
/// [adhocckd]: https://zips.z.cash/zip-0032#ad-hoc-child-key-derivation-deprecated
|
||||
fn derive_child(&self, index: ChildIndex) -> Self {
|
||||
Self {
|
||||
inner: self.inner.derive_child(index),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the key material for this arbitrary key.
|
||||
/// Returns the key material for this key.
|
||||
pub fn data(&self) -> &[u8; 32] {
|
||||
self.inner.parts().0
|
||||
}
|
||||
|
||||
/// Returns the chain code for this arbitrary key.
|
||||
/// Returns the chain code for this key.
|
||||
pub fn chain_code(&self) -> &ChainCode {
|
||||
self.inner.parts().1
|
||||
}
|
||||
|
@ -114,7 +111,12 @@ impl SecretKey {
|
|||
///
|
||||
/// Child keys MUST NOT be derived from any key on which this method is called. For
|
||||
/// the current API, this means that [`SecretKey::from_path`] MUST NOT be called with
|
||||
/// a `path` for which this key's path is a prefix.
|
||||
/// a `path` for which this key's path is a prefix. This API is cryptographically
|
||||
/// unsafe because there is no way to enforce that restriction.
|
||||
#[deprecated(
|
||||
since = "0.1.4",
|
||||
note = "Use [`zip32::registered::cryptovalue_from_subpath`] instead."
|
||||
)]
|
||||
pub fn into_full_width_key(self) -> [u8; 64] {
|
||||
let (sk, c) = self.inner.into_parts();
|
||||
// Re-concatenate the key parts.
|
||||
|
@ -127,9 +129,7 @@ impl SecretKey {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{arbitrary::with_ikm, ChildIndex};
|
||||
|
||||
use super::SecretKey;
|
||||
use super::{with_ikm, ChildIndex, SecretKey};
|
||||
|
||||
struct TestVector {
|
||||
context_string: &'static [u8],
|
||||
|
@ -141,7 +141,7 @@ mod tests {
|
|||
}
|
||||
|
||||
// From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/zip_0032_arbitrary.py
|
||||
const TEST_VECTORS: &'static [TestVector] = &[
|
||||
const TEST_VECTORS: &[TestVector] = &[
|
||||
TestVector {
|
||||
context_string: &[
|
||||
0x5a, 0x63, 0x61, 0x73, 0x68, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x76, 0x65, 0x63,
|
||||
|
@ -239,26 +239,89 @@ mod tests {
|
|||
0xac, 0x19, 0x84, 0x29,
|
||||
],
|
||||
},
|
||||
TestVector {
|
||||
context_string: &[
|
||||
0x5a, 0x63, 0x61, 0x73, 0x68, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x76, 0x65, 0x63,
|
||||
0x74, 0x6f, 0x72, 0x73,
|
||||
],
|
||||
seed: [
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
|
||||
0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
|
||||
0x1c, 0x1d, 0x1e, 0x1f,
|
||||
],
|
||||
ikm: None,
|
||||
path: &[2147483680],
|
||||
sk: [
|
||||
0xc4, 0x30, 0xc4, 0xde, 0xfd, 0x03, 0xd7, 0x57, 0x8b, 0x2b, 0xb0, 0x9e, 0x58, 0x13,
|
||||
0x5c, 0xdd, 0x1d, 0x7b, 0x7c, 0x97, 0x5f, 0x01, 0xa8, 0x90, 0x84, 0x7e, 0xe0, 0xb5,
|
||||
0xc4, 0x68, 0xbc, 0x98,
|
||||
],
|
||||
c: [
|
||||
0x0f, 0x47, 0x37, 0x89, 0xfe, 0x7d, 0x55, 0x85, 0xb7, 0x9a, 0xd5, 0xf7, 0xe0, 0xa4,
|
||||
0x69, 0xd9, 0xa3, 0x01, 0x46, 0x64, 0x77, 0x64, 0x48, 0x51, 0x50, 0xdb, 0x78, 0xd7,
|
||||
0x20, 0x9d, 0xcb, 0x30,
|
||||
],
|
||||
},
|
||||
TestVector {
|
||||
context_string: &[
|
||||
0x5a, 0x63, 0x61, 0x73, 0x68, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x76, 0x65, 0x63,
|
||||
0x74, 0x6f, 0x72, 0x73,
|
||||
],
|
||||
seed: [
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
|
||||
0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
|
||||
0x1c, 0x1d, 0x1e, 0x1f,
|
||||
],
|
||||
ikm: None,
|
||||
path: &[2147483680, 2147483781],
|
||||
sk: [
|
||||
0x43, 0xe5, 0x48, 0x46, 0x79, 0xfd, 0xfa, 0x0f, 0x61, 0x76, 0xae, 0x86, 0x79, 0x5d,
|
||||
0x0d, 0x44, 0xc4, 0x0e, 0x14, 0x9e, 0xf4, 0xba, 0x1b, 0x0e, 0x2e, 0xbd, 0x88, 0x3c,
|
||||
0x71, 0xf4, 0x91, 0x87,
|
||||
],
|
||||
c: [
|
||||
0xdb, 0x42, 0xc3, 0xb7, 0x25, 0xf3, 0x24, 0x59, 0xb2, 0xcf, 0x82, 0x15, 0x41, 0x8b,
|
||||
0x8e, 0x8f, 0x8e, 0x7b, 0x1b, 0x3f, 0x4a, 0xba, 0x2f, 0x5b, 0x5e, 0x81, 0x29, 0xe6,
|
||||
0xf0, 0x57, 0x57, 0x84,
|
||||
],
|
||||
},
|
||||
TestVector {
|
||||
context_string: &[
|
||||
0x5a, 0x63, 0x61, 0x73, 0x68, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x76, 0x65, 0x63,
|
||||
0x74, 0x6f, 0x72, 0x73,
|
||||
],
|
||||
seed: [
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
|
||||
0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
|
||||
0x1c, 0x1d, 0x1e, 0x1f,
|
||||
],
|
||||
ikm: None,
|
||||
path: &[2147483680, 2147483781, 2147483648],
|
||||
sk: [
|
||||
0xbf, 0x60, 0x07, 0x83, 0x62, 0xa0, 0x92, 0x34, 0xfc, 0xbc, 0x6b, 0xf6, 0xc8, 0xa8,
|
||||
0x7b, 0xde, 0x9f, 0xc7, 0x37, 0x76, 0xbf, 0x93, 0xf3, 0x7a, 0xdb, 0xcc, 0x43, 0x9a,
|
||||
0x85, 0x57, 0x4a, 0x9a,
|
||||
],
|
||||
c: [
|
||||
0x2b, 0x65, 0x7e, 0x08, 0xf6, 0x7a, 0x57, 0x0c, 0x53, 0xb9, 0xed, 0x30, 0x61, 0x1e,
|
||||
0x6a, 0x2f, 0x82, 0x26, 0x62, 0xb4, 0x88, 0x7a, 0x8c, 0xfb, 0x46, 0x9e, 0x9d, 0x0d,
|
||||
0x98, 0x17, 0x01, 0x1a,
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn test_vectors() {
|
||||
let context_string = b"Zcash test vectors";
|
||||
let full_path = [
|
||||
ChildIndex::hardened(1),
|
||||
ChildIndex::hardened(2),
|
||||
ChildIndex::hardened(3),
|
||||
];
|
||||
|
||||
for (i, tv) in TEST_VECTORS.iter().enumerate() {
|
||||
for tv in TEST_VECTORS {
|
||||
assert_eq!(tv.context_string, context_string);
|
||||
|
||||
let path = tv
|
||||
.path
|
||||
.into_iter()
|
||||
.iter()
|
||||
.map(|i| ChildIndex::from_index(*i).expect("hardened"))
|
||||
.collect::<alloc::vec::Vec<_>>();
|
||||
assert_eq!(&full_path[..i], &path);
|
||||
|
||||
// The derived master key should be identical to the key at the empty path.
|
||||
if let Some(mut tv_ikm) = tv.ikm {
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
//! Generic framework for hardened-only key derivation.
|
||||
//!
|
||||
//! Defined in [ZIP32: Hardened-only key derivation][hkd].
|
||||
//! Defined in [ZIP 32: 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`].
|
||||
//! Any usage of the types in this module needs to have a corresponding ZIP (except via
|
||||
//! [`arbitrary::SecretKey`] but that is [NOT RECOMMENDED for new protocols][adhockd]).
|
||||
//!
|
||||
//! [hkd]: https://zips.z.cash/zip-0032#specification-hardened-only-key-derivation
|
||||
//! [adhockd]: https://zips.z.cash/zip-0032#specification-ad-hoc-key-derivation-deprecated
|
||||
//! [`arbitrary::SecretKey`]: crate::arbitrary::SecretKey
|
||||
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use blake2b_simd::Params as Blake2bParams;
|
||||
use subtle::{Choice, ConstantTimeEq};
|
||||
use zcash_spec::PrfExpand;
|
||||
use zcash_spec::{PrfExpand, VariableLengthSlice};
|
||||
|
||||
use crate::{ChainCode, ChildIndex};
|
||||
|
||||
pub(crate) type HardenedOnlyCkdDomain =
|
||||
PrfExpand<([u8; 32], [u8; 4], [u8; 1], VariableLengthSlice)>;
|
||||
|
||||
/// The context in which hardened-only key derivation is instantiated.
|
||||
pub trait Context {
|
||||
/// A 16-byte domain separator used during master key generation.
|
||||
|
@ -25,12 +28,12 @@ pub trait Context {
|
|||
/// protocols.
|
||||
const MKG_DOMAIN: [u8; 16];
|
||||
/// The `PrfExpand` domain used during child key derivation.
|
||||
const CKD_DOMAIN: PrfExpand<([u8; 32], [u8; 4])>;
|
||||
const CKD_DOMAIN: HardenedOnlyCkdDomain;
|
||||
}
|
||||
|
||||
/// An arbitrary extended secret key.
|
||||
/// An arbitrary or registered extended secret key.
|
||||
///
|
||||
/// Defined in [ZIP32: Hardened-only key derivation][hkd].
|
||||
/// Defined in [ZIP 32: Hardened-only key derivation][hkd].
|
||||
///
|
||||
/// [hkd]: https://zips.z.cash/zip-0032#specification-hardened-only-key-derivation
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -48,6 +51,17 @@ impl<C: Context> ConstantTimeEq for HardenedOnlyKey<C> {
|
|||
|
||||
#[allow(non_snake_case)]
|
||||
impl<C: Context> HardenedOnlyKey<C> {
|
||||
/// Constructs a hardened-only key from its parts.
|
||||
///
|
||||
/// Crate-internal because we want this to only be used within specific contexts.
|
||||
pub(crate) fn from_parts(sk: [u8; 32], chain_code: ChainCode) -> Self {
|
||||
Self {
|
||||
sk,
|
||||
chain_code,
|
||||
_context: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Exposes the parts of this key.
|
||||
pub fn parts(&self) -> (&[u8; 32], &ChainCode) {
|
||||
(&self.sk, &self.chain_code)
|
||||
|
@ -60,7 +74,7 @@ impl<C: Context> HardenedOnlyKey<C> {
|
|||
|
||||
/// Generates the master key of a hardened-only extended secret key.
|
||||
///
|
||||
/// Defined in [ZIP32: Hardened-only master key generation][mkgh].
|
||||
/// Defined in [ZIP 32: Hardened-only master key generation][mkgh].
|
||||
///
|
||||
/// [mkgh]: https://zips.z.cash/zip-0032#hardened-only-master-key-generation
|
||||
pub fn master(ikm: &[&[u8]]) -> Self {
|
||||
|
@ -73,48 +87,61 @@ impl<C: Context> HardenedOnlyKey<C> {
|
|||
for input in ikm {
|
||||
I.update(input);
|
||||
}
|
||||
I.finalize().as_bytes().try_into().unwrap()
|
||||
I.finalize().as_bytes().try_into().expect("64-byte output")
|
||||
};
|
||||
|
||||
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,
|
||||
}
|
||||
Self::from_bytes(&I)
|
||||
}
|
||||
|
||||
/// Derives a child key from a parent key at a given index.
|
||||
/// Derives a child key from a parent key at a given index and empty tag.
|
||||
///
|
||||
/// Defined in [ZIP32: Hardened-only child key derivation][ckdh].
|
||||
/// This is a convenience function equivalent to
|
||||
/// `self.derive_child_with_tag(index, &[])`.
|
||||
pub fn derive_child(&self, index: ChildIndex) -> Self {
|
||||
self.derive_child_with_tag(index, &[])
|
||||
}
|
||||
|
||||
/// Derives a child key from a parent key at a given index and (possibly empty)
|
||||
/// tag.
|
||||
///
|
||||
/// Defined in [ZIP 32: 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(
|
||||
pub fn derive_child_with_tag(&self, index: ChildIndex, tag: &[u8]) -> Self {
|
||||
Self::from_bytes(&self.ckdh_internal(index, 0, tag))
|
||||
}
|
||||
|
||||
/// Defined in [ZIP 32: Hardened-only child key derivation][ckdh].
|
||||
///
|
||||
/// This returns `I` rather than `(I_L, I_R)` so that we don't have
|
||||
/// to re-concatenate the pieces, e.g. when using it in
|
||||
/// [`crate::registered::SecretKey::derive_child_cryptovalue`].
|
||||
///
|
||||
/// [ckdh]: https://zips.z.cash/zip-0032#hardened-only-child-key-derivation
|
||||
pub(crate) fn ckdh_internal(&self, index: ChildIndex, lead: u8, tag: &[u8]) -> [u8; 64] {
|
||||
// One of these depending on lead and tag:
|
||||
// - I := PRF^Expand(c_par, [Context.CKDDomain] || sk_par || I2LEOSP(i))
|
||||
// - I := PRF^Expand(c_par, [Context.CKDDomain] || sk_par || I2LEOSP(i) || [lead] || tag)
|
||||
C::CKD_DOMAIN.with(
|
||||
self.chain_code.as_bytes(),
|
||||
&self.sk,
|
||||
&index.index().to_le_bytes(),
|
||||
);
|
||||
&[lead],
|
||||
tag,
|
||||
)
|
||||
}
|
||||
|
||||
fn from_bytes(I: &[u8; 64]) -> Self {
|
||||
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_L is used as the spending key sk.
|
||||
let sk = 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());
|
||||
// I_R is used as the chain code c.
|
||||
let chain_code = ChainCode::new(I_R.try_into().unwrap());
|
||||
|
||||
Self {
|
||||
sk: sk_i,
|
||||
chain_code: c_i,
|
||||
sk,
|
||||
chain_code,
|
||||
_context: PhantomData,
|
||||
}
|
||||
}
|
||||
|
|
36
src/lib.rs
36
src/lib.rs
|
@ -21,6 +21,7 @@ use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
|
|||
pub mod arbitrary;
|
||||
pub mod fingerprint;
|
||||
pub mod hardened_only;
|
||||
pub mod registered;
|
||||
|
||||
/// A type-safe wrapper for account identifiers.
|
||||
///
|
||||
|
@ -72,6 +73,18 @@ impl AccountId {
|
|||
pub fn next(&self) -> Option<Self> {
|
||||
Self::try_from(self.0 + 1).ok()
|
||||
}
|
||||
|
||||
/// Constant function to construct an account ID from a u32.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the provided value is >= 2^31
|
||||
pub const fn const_from_u32(value: u32) -> Self {
|
||||
if value < (1 << 31) {
|
||||
Self(value)
|
||||
} else {
|
||||
panic!("Account IDs must be in the range 0..2^31");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The error type returned when a checked integral type conversion fails.
|
||||
|
@ -87,6 +100,24 @@ impl core::fmt::Display for TryFromIntError {
|
|||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for TryFromIntError {}
|
||||
|
||||
// Helper function for arbitrary and registered master key generation.
|
||||
|
||||
pub(crate) 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)
|
||||
}
|
||||
|
||||
// ZIP 32 structures
|
||||
|
||||
/// A child index for a derived key.
|
||||
|
@ -127,6 +158,9 @@ impl ChildIndex {
|
|||
pub fn index(&self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// A `ChildIndex` sometimes employed for private-use subtrees.
|
||||
pub const PRIVATE_USE: Self = Self::hardened(0x7fff_ffff);
|
||||
}
|
||||
|
||||
/// A value that is needed, in addition to a spending key, in order to derive descendant
|
||||
|
@ -363,7 +397,7 @@ mod tests {
|
|||
let max_di = DiversifierIndex([
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
]);
|
||||
assert_eq!(u128::try_from(max_di), Ok(0x00ff_ffff_ffff_ffff_ffff_ffff));
|
||||
assert_eq!(u128::from(max_di), 0x00ff_ffff_ffff_ffff_ffff_ffff);
|
||||
assert_eq!(
|
||||
DiversifierIndex::try_from(0x00ff_ffff_ffff_ffff_ffff_ffff_u128).unwrap(),
|
||||
max_di,
|
||||
|
|
|
@ -0,0 +1,422 @@
|
|||
//! Registered key derivation.
|
||||
//!
|
||||
//! In the context of a particular application protocol defined by a ZIP, there is
|
||||
//! sometimes a need to define an HD subtree that will not collide with keys derived
|
||||
//! for other protocols, as far as that is possible to assure by following the
|
||||
//! [ZIP process].
|
||||
//!
|
||||
//! Within this subtree, the application protocol may use derivation paths related to
|
||||
//! those used for existing key material — for example, to derive an account-level key.
|
||||
//! The instantiation of the [hardened key derivation framework] in this module may be
|
||||
//! used for this purpose.
|
||||
//!
|
||||
//! It is strongly RECOMMENDED that implementors ensure that documentation of the
|
||||
//! usage and derivation paths of the application protocol's key tree in the
|
||||
//! corresponding ZIP is substantially complete, before public deployment of software
|
||||
//! or hardware using this mechanism. The ZIP process allows for subsequent updates
|
||||
//! and corrections.
|
||||
//!
|
||||
//! The functionality of this module is similar to that of the [`zip32::arbitrary`]
|
||||
//! module, with the following improvements:
|
||||
//!
|
||||
//! - The key tree is associated with the ZIP that should document it, and cannot
|
||||
//! collide with the tree for any other ZIP.
|
||||
//! - Child indices may include byte sequence tags.
|
||||
//! - A 64-bit cryptovalue can be derived at the same path as any node in the tree,
|
||||
//! without any cryptographic unsafety.
|
||||
//!
|
||||
//! The keys derived by the functions in this module will be unrelated to any keys
|
||||
//! derived by functions in the [`zip32::arbitrary`] module, even if the same context
|
||||
//! string and seed are used.
|
||||
//!
|
||||
//! Defined in [ZIP 32: Registered key derivation][regkd].
|
||||
//!
|
||||
//! [hardened key derivation framework]: crate::hardened_only
|
||||
//! [regkd]: https://zips.z.cash/zip-0032#specification-registered-key-derivation
|
||||
//! [ZIP process]: https://zips.z.cash/zip-0000
|
||||
//! [`zip32::arbitrary`]: `crate::arbitrary`
|
||||
|
||||
use core::fmt::Display;
|
||||
|
||||
use zcash_spec::PrfExpand;
|
||||
|
||||
use crate::{
|
||||
hardened_only::{Context, HardenedOnlyCkdDomain, HardenedOnlyKey},
|
||||
ChainCode, ChildIndex,
|
||||
};
|
||||
|
||||
use super::with_ikm;
|
||||
|
||||
struct Registered;
|
||||
|
||||
impl Context for Registered {
|
||||
const MKG_DOMAIN: [u8; 16] = *b"ZIPRegistered_KD";
|
||||
const CKD_DOMAIN: HardenedOnlyCkdDomain = PrfExpand::REGISTERED_ZIP32_CHILD;
|
||||
}
|
||||
|
||||
/// An error that occurred in cryptovalue derivation.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DerivationError {
|
||||
/// The provided seed data was invalid. A seed must be between 32 and 252 bytes in length,
|
||||
/// inclusive.
|
||||
SeedInvalid,
|
||||
/// The provided context string is invalid; context strings must be non-empty and no greater
|
||||
/// than 252 bytes in length.
|
||||
ContextStringInvalid,
|
||||
/// The provided subpath was empty. Empty subpaths are not permitted by this API, as the
|
||||
/// full-width cryptovalue at the empty subpath would be outside the allowed subtree
|
||||
/// rooted at `m_{context} / zip_number'`.
|
||||
SubpathEmpty,
|
||||
}
|
||||
|
||||
impl Display for DerivationError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
DerivationError::SeedInvalid => {
|
||||
write!(f, "Seed must be between 32 and 252 bytes, inclusive.")
|
||||
}
|
||||
DerivationError::ContextStringInvalid => write!(
|
||||
f,
|
||||
"Context string must be between 1 and 252 bytes, inclusive."
|
||||
),
|
||||
DerivationError::SubpathEmpty => write!(
|
||||
f,
|
||||
"ZIP 32 registered 64-byte cryptovalue subpaths must have at least one element."
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for DerivationError {}
|
||||
|
||||
/// A ZIP 32 registered key derivation path element, consisting of a child index and an
|
||||
/// optionally-empty tag value.
|
||||
pub struct PathElement<'a> {
|
||||
child_index: ChildIndex,
|
||||
tag: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> PathElement<'a> {
|
||||
/// Constructs a new [`PathElement`] from its constituent parts.
|
||||
pub fn new(child_index: ChildIndex, tag: &'a [u8]) -> Self {
|
||||
Self { child_index, tag }
|
||||
}
|
||||
|
||||
/// Returns the index at which the child key will be derived.
|
||||
pub fn child_index(&self) -> ChildIndex {
|
||||
self.child_index
|
||||
}
|
||||
|
||||
/// Returns the tag that will be used in derivation of the child key.
|
||||
pub fn tag(&self) -> &[u8] {
|
||||
self.tag
|
||||
}
|
||||
}
|
||||
|
||||
/// A registered extended secret key.
|
||||
///
|
||||
/// Defined in [ZIP 32: Registered key derivation][regkd].
|
||||
///
|
||||
/// [regkd]: https://zips.z.cash/zip-0032#specification-registered-key-derivation
|
||||
pub struct SecretKey {
|
||||
inner: HardenedOnlyKey<Registered>,
|
||||
}
|
||||
|
||||
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 (possibly empty) tag.
|
||||
///
|
||||
/// - `context_string`: an identifier for the context in which this key will be used. It must
|
||||
/// be globally unique, non-empty, and no more than 252 bytes in length.
|
||||
/// - `seed`: the root seed. Must be between 32 bytes and 252 bytes in length, inclusive.
|
||||
/// - `zip_number`: the number of the ZIP defining the application protocol. The corresponding
|
||||
/// hardened index (with empty tag) will be prepended to the `subpath` to obtain the ZIP 32
|
||||
/// path.
|
||||
/// - `subpath`: the path to the desired child element.
|
||||
pub fn from_subpath(
|
||||
context_string: &[u8],
|
||||
seed: &[u8],
|
||||
zip_number: u16,
|
||||
subpath: &[PathElement<'_>],
|
||||
) -> Result<Self, DerivationError> {
|
||||
if context_string.is_empty() || context_string.len() > 252 {
|
||||
return Err(DerivationError::ContextStringInvalid);
|
||||
}
|
||||
if seed.len() < 32 || seed.len() > 252 {
|
||||
return Err(DerivationError::SeedInvalid);
|
||||
}
|
||||
|
||||
let mut xsk = Self::master(context_string, seed)
|
||||
.derive_child(ChildIndex::hardened(u32::from(zip_number)));
|
||||
|
||||
for elem in subpath {
|
||||
xsk = xsk.derive_child_with_tag(elem.child_index, elem.tag);
|
||||
}
|
||||
Ok(xsk)
|
||||
}
|
||||
|
||||
/// Constructs a key for a registered application protocol from its constituent parts.
|
||||
///
|
||||
/// This is a low-level API. The constructor must only be called with parts that were
|
||||
/// obtained from previous calls to [`key.data()`][`Self::data`] and
|
||||
/// [`key.chain_code()`][`Self::chain_code`] for some `key: registered::SecretKey`.
|
||||
pub fn from_parts(sk: [u8; 32], chain_code: ChainCode) -> Self {
|
||||
Self {
|
||||
inner: HardenedOnlyKey::from_parts(sk, chain_code),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the master key of a registered extended secret key.
|
||||
/// This should not be exposed directly. It is defined as an intermediate
|
||||
/// value in [ZIP 32: Registered subtree root key generation][regroot].
|
||||
///
|
||||
/// [regroot]: https://zips.z.cash/zip-0032#registered-subtree-root-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 empty tag.
|
||||
///
|
||||
/// This is a convenience function equivalent to
|
||||
/// `self.derive_child_with_tag(index, &[])`.
|
||||
pub fn derive_child(&self, index: ChildIndex) -> Self {
|
||||
self.derive_child_with_tag(index, &[])
|
||||
}
|
||||
|
||||
/// Derives a child key from a parent key at a given index and (possibly empty) tag.
|
||||
///
|
||||
/// Defined in [ZIP 32: Registered child key derivation][regckd].
|
||||
///
|
||||
/// [regckd]: https://zips.z.cash/zip-0032#registered-child-key-derivation
|
||||
pub fn derive_child_with_tag(&self, index: ChildIndex, tag: &[u8]) -> Self {
|
||||
Self {
|
||||
inner: self.inner.derive_child_with_tag(index, tag),
|
||||
}
|
||||
}
|
||||
|
||||
/// Derives a 64-byte child cryptovalue from a parent key at a given index
|
||||
/// and (possibly empty) tag.
|
||||
///
|
||||
/// Defined in [ZIP 32: Full-width child cryptovalue derivation][fwccd].
|
||||
///
|
||||
/// [fwccd]: https://zips.z.cash/zip-0032#full-width-child-cryptovalue-derivation
|
||||
pub fn derive_child_cryptovalue(&self, index: ChildIndex, tag: &[u8]) -> [u8; 64] {
|
||||
self.inner.ckdh_internal(index, 1, tag)
|
||||
}
|
||||
|
||||
/// Returns the key material for this key.
|
||||
pub fn data(&self) -> &[u8; 32] {
|
||||
self.inner.parts().0
|
||||
}
|
||||
|
||||
/// Returns the chain code for this key.
|
||||
pub fn chain_code(&self) -> &ChainCode {
|
||||
self.inner.parts().1
|
||||
}
|
||||
}
|
||||
|
||||
/// Derives a 64-byte cryptovalue (for use as key material for example), for a registered
|
||||
/// application protocol at the given non-empty subpath from the given seed. Each subpath element
|
||||
/// may consist of an index and a (possibly empty) tag.
|
||||
///
|
||||
/// - `context_string`: an identifier for the context in which this key will be used. It must be
|
||||
/// globally unique, non-empty, and no more than 252 bytes in length.
|
||||
/// - `seed`: the root seed. Must be between 32 bytes and 252 bytes in length, inclusive.
|
||||
/// - `zip_number`: the number of the ZIP defining the application protocol. The corresponding
|
||||
/// hardened index (with empty tag) will be prepended to the `subpath` to obtain the ZIP 32 path.
|
||||
/// - `subpath`: the path to the desired child element. A non-empty path is required, in order
|
||||
/// to ensure that the resulting full-width cryptovalue is within the allowed subtree rooted
|
||||
/// at `m_{context} / zip_number'`.
|
||||
pub fn cryptovalue_from_subpath(
|
||||
context_string: &[u8],
|
||||
seed: &[u8],
|
||||
zip_number: u16,
|
||||
subpath: &[PathElement<'_>],
|
||||
) -> Result<[u8; 64], DerivationError> {
|
||||
if context_string.is_empty() || context_string.len() > 252 {
|
||||
return Err(DerivationError::ContextStringInvalid);
|
||||
}
|
||||
if seed.len() < 32 || seed.len() > 252 {
|
||||
return Err(DerivationError::SeedInvalid);
|
||||
}
|
||||
// We can't use NonEmpty because it requires allocation.
|
||||
if subpath.is_empty() {
|
||||
return Err(DerivationError::SubpathEmpty);
|
||||
}
|
||||
|
||||
let mut xsk = SecretKey::master(context_string, seed)
|
||||
.derive_child(ChildIndex::hardened(u32::from(zip_number)));
|
||||
|
||||
for elem in subpath.iter().take(subpath.len() - 1) {
|
||||
xsk = xsk.derive_child_with_tag(elem.child_index, elem.tag);
|
||||
}
|
||||
let elem = subpath.last().expect("nonempty");
|
||||
Ok(xsk.derive_child_cryptovalue(elem.child_index, elem.tag))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::registered::PathElement;
|
||||
|
||||
use super::{cryptovalue_from_subpath, ChildIndex, DerivationError, SecretKey};
|
||||
|
||||
#[test]
|
||||
fn test_cryptovalue_from_empty_subpath_errors() {
|
||||
assert_eq!(
|
||||
cryptovalue_from_subpath(&[0], &[0; 32], 32, &[]),
|
||||
Err(DerivationError::SubpathEmpty),
|
||||
);
|
||||
}
|
||||
|
||||
struct TestVector {
|
||||
context_string: &'static [u8],
|
||||
seed: [u8; 32],
|
||||
zip_number: u16,
|
||||
subpath: &'static [(u32, &'static [u8])],
|
||||
sk: [u8; 32],
|
||||
c: [u8; 32],
|
||||
full_width: Option<[u8; 64]>,
|
||||
}
|
||||
|
||||
// From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/zip_0032_registered.py
|
||||
const TEST_VECTORS: &[TestVector] = &[
|
||||
TestVector {
|
||||
context_string: &[
|
||||
0x5a, 0x63, 0x61, 0x73, 0x68, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x76, 0x65, 0x63,
|
||||
0x74, 0x6f, 0x72, 0x73,
|
||||
],
|
||||
seed: [
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
|
||||
0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
|
||||
0x1c, 0x1d, 0x1e, 0x1f,
|
||||
],
|
||||
zip_number: 1,
|
||||
subpath: &[],
|
||||
sk: [
|
||||
0x53, 0xa7, 0x15, 0x07, 0xe6, 0xdf, 0xda, 0x58, 0x8b, 0xc1, 0xe1, 0x38, 0xc2, 0x65,
|
||||
0x7c, 0x92, 0x69, 0xe5, 0x5f, 0x5d, 0x9b, 0x99, 0xe3, 0x88, 0x7c, 0x13, 0x40, 0x08,
|
||||
0x19, 0x3a, 0x2f, 0x47,
|
||||
],
|
||||
c: [
|
||||
0x08, 0xbb, 0x26, 0xaa, 0xe2, 0x1d, 0x4e, 0xfd, 0xc3, 0x24, 0x9b, 0x95, 0x57, 0xfc,
|
||||
0xd9, 0x13, 0x1e, 0x8b, 0x98, 0x27, 0x24, 0x1d, 0x9f, 0x61, 0xd0, 0xd7, 0x74, 0xbb,
|
||||
0x4f, 0xed, 0x3d, 0xe6,
|
||||
],
|
||||
full_width: None,
|
||||
},
|
||||
TestVector {
|
||||
context_string: &[
|
||||
0x5a, 0x63, 0x61, 0x73, 0x68, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x76, 0x65, 0x63,
|
||||
0x74, 0x6f, 0x72, 0x73,
|
||||
],
|
||||
seed: [
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
|
||||
0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
|
||||
0x1c, 0x1d, 0x1e, 0x1f,
|
||||
],
|
||||
zip_number: 1,
|
||||
subpath: &[(
|
||||
2147483650,
|
||||
&[
|
||||
0x74, 0x72, 0x61, 0x6e, 0x73, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x73, 0x20,
|
||||
0x61, 0x72, 0x65, 0x20, 0x68, 0x75, 0x6d, 0x61, 0x6e, 0x20, 0x72, 0x69, 0x67,
|
||||
0x68, 0x74, 0x73,
|
||||
],
|
||||
)],
|
||||
sk: [
|
||||
0x02, 0xdc, 0x25, 0xcc, 0x40, 0x31, 0x0e, 0xed, 0x08, 0xb0, 0x28, 0xe0, 0x7f, 0xae,
|
||||
0x9a, 0xdb, 0xee, 0x2f, 0xbe, 0x56, 0xa4, 0x69, 0x4d, 0xef, 0x04, 0x01, 0xe6, 0x56,
|
||||
0xdf, 0xae, 0x02, 0x11,
|
||||
],
|
||||
c: [
|
||||
0xd8, 0xf9, 0xd8, 0xa1, 0xf8, 0x1d, 0x1b, 0x5d, 0x55, 0x06, 0xb5, 0xff, 0x94, 0x2d,
|
||||
0x2f, 0xf3, 0xda, 0xe7, 0xa6, 0x3f, 0x57, 0xd6, 0xb8, 0xc7, 0xfb, 0xe5, 0x81, 0x49,
|
||||
0x82, 0x3c, 0xc6, 0xec,
|
||||
],
|
||||
full_width: Some([
|
||||
0x25, 0x5d, 0x75, 0xb5, 0xf9, 0x7d, 0xd8, 0x80, 0xa1, 0x44, 0x60, 0xab, 0x0a, 0x28,
|
||||
0x93, 0x8e, 0x7b, 0xa4, 0x97, 0xce, 0xb1, 0x45, 0x7f, 0xff, 0x29, 0x92, 0xe9, 0x01,
|
||||
0x5a, 0x84, 0x03, 0xf8, 0xc0, 0x81, 0x12, 0xb7, 0xa9, 0x4c, 0xf5, 0x39, 0xc2, 0x1c,
|
||||
0x9d, 0xa7, 0xee, 0x99, 0x89, 0x7b, 0xe9, 0x47, 0x6b, 0x68, 0x13, 0x53, 0x2e, 0xe2,
|
||||
0x2c, 0x89, 0x47, 0xd7, 0x53, 0xb7, 0x2b, 0xdf,
|
||||
]),
|
||||
},
|
||||
TestVector {
|
||||
context_string: &[
|
||||
0x5a, 0x63, 0x61, 0x73, 0x68, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x76, 0x65, 0x63,
|
||||
0x74, 0x6f, 0x72, 0x73,
|
||||
],
|
||||
seed: [
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
|
||||
0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
|
||||
0x1c, 0x1d, 0x1e, 0x1f,
|
||||
],
|
||||
zip_number: 1,
|
||||
subpath: &[
|
||||
(
|
||||
2147483650,
|
||||
&[
|
||||
0x74, 0x72, 0x61, 0x6e, 0x73, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x73,
|
||||
0x20, 0x61, 0x72, 0x65, 0x20, 0x68, 0x75, 0x6d, 0x61, 0x6e, 0x20, 0x72,
|
||||
0x69, 0x67, 0x68, 0x74, 0x73,
|
||||
],
|
||||
),
|
||||
(2147483651, &[]),
|
||||
],
|
||||
sk: [
|
||||
0xa1, 0x27, 0xdb, 0x66, 0x62, 0x8b, 0x25, 0x6e, 0x5b, 0x66, 0x4d, 0x54, 0x05, 0x0c,
|
||||
0x1e, 0x6b, 0x02, 0x89, 0x63, 0xae, 0xa2, 0x2b, 0x04, 0xd1, 0xbc, 0x6f, 0x48, 0x12,
|
||||
0x36, 0x74, 0xed, 0x82,
|
||||
],
|
||||
c: [
|
||||
0x34, 0x00, 0x84, 0x03, 0x36, 0x05, 0xed, 0xca, 0x11, 0x46, 0x3f, 0xfe, 0xc5, 0x6b,
|
||||
0xf0, 0xca, 0xc4, 0x25, 0xc4, 0x10, 0xe9, 0x53, 0x62, 0x86, 0x71, 0xce, 0xc6, 0xa6,
|
||||
0x51, 0x4c, 0x32, 0xa8,
|
||||
],
|
||||
full_width: Some([
|
||||
0x7f, 0x85, 0x3e, 0xef, 0x00, 0x1b, 0x1b, 0xc5, 0xa1, 0xa5, 0xe6, 0x7f, 0x5d, 0xfd,
|
||||
0x0e, 0x90, 0x42, 0x75, 0x96, 0xd4, 0x84, 0x2f, 0x5b, 0x10, 0xa1, 0x11, 0xe9, 0x7c,
|
||||
0x40, 0x73, 0x20, 0x3c, 0xed, 0xf6, 0xb8, 0x0a, 0x85, 0x14, 0x5e, 0x50, 0x61, 0xac,
|
||||
0xd2, 0x9b, 0xc5, 0xa4, 0xe3, 0x49, 0xb1, 0x4f, 0x85, 0x57, 0xa7, 0x03, 0x3e, 0x23,
|
||||
0xb0, 0x66, 0xb7, 0xce, 0x24, 0x09, 0xd9, 0x73,
|
||||
]),
|
||||
},
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn test_vectors() {
|
||||
for tv in TEST_VECTORS {
|
||||
let subpath = tv
|
||||
.subpath
|
||||
.iter()
|
||||
.map(|(i, tag)| {
|
||||
PathElement::new(ChildIndex::from_index(*i).expect("hardened"), tag)
|
||||
})
|
||||
.collect::<alloc::vec::Vec<_>>();
|
||||
|
||||
let sk = SecretKey::from_subpath(tv.context_string, &tv.seed, tv.zip_number, &subpath)
|
||||
.unwrap();
|
||||
assert_eq!(sk.data(), &tv.sk);
|
||||
assert_eq!(sk.chain_code().as_bytes(), &tv.c);
|
||||
|
||||
let fw = (!subpath.is_empty()).then(|| {
|
||||
cryptovalue_from_subpath(tv.context_string, &tv.seed, tv.zip_number, &subpath)
|
||||
.unwrap()
|
||||
});
|
||||
assert_eq!(&fw, &tv.full_width);
|
||||
if let Some(fw) = fw {
|
||||
assert_ne!(&fw[..32], &tv.sk);
|
||||
assert_ne!(&fw[32..], &tv.c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue