Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to zip32::hardened_only implementation #437

Merged
merged 3 commits into from
Feb 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/test_vectors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ pub(crate) mod commitment_tree;
pub(crate) mod keys;
pub(crate) mod merkle_path;
pub(crate) mod note_encryption;
pub(crate) mod zip32;
112 changes: 112 additions & 0 deletions src/test_vectors/zip32.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//! Test vectors for Orchard ZIP 32 key derivation.

pub(crate) struct TestVector {
pub(crate) sk: [u8; 32],
pub(crate) c: [u8; 32],
pub(crate) xsk: [u8; 73],
pub(crate) fp: [u8; 32],
}

// From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/orchard_zip32.py
pub(crate) const TEST_VECTORS: &[TestVector] = &[
TestVector {
sk: [
0x7e, 0xee, 0x3c, 0x10, 0x17, 0x87, 0x09, 0x90, 0xa3, 0xdd, 0x68, 0x91, 0xb8, 0x2f,
0x80, 0xbe, 0x89, 0x76, 0xc1, 0xe7, 0xdc, 0x20, 0xd6, 0x08, 0x17, 0xa5, 0xe8, 0x8e,
0x8b, 0x2c, 0xd4, 0xb8,
],
c: [
0xab, 0x8b, 0x7a, 0x00, 0x50, 0x9e, 0xf2, 0x0e, 0x46, 0x9b, 0x52, 0x92, 0xb6, 0x1d,
0x47, 0x4b, 0x7c, 0xff, 0xcb, 0x16, 0x57, 0x92, 0x4c, 0xda, 0x72, 0x02, 0x50, 0xae,
0x40, 0x52, 0x66, 0x77,
],
xsk: [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0x8b, 0x7a, 0x00, 0x50,
0x9e, 0xf2, 0x0e, 0x46, 0x9b, 0x52, 0x92, 0xb6, 0x1d, 0x47, 0x4b, 0x7c, 0xff, 0xcb,
0x16, 0x57, 0x92, 0x4c, 0xda, 0x72, 0x02, 0x50, 0xae, 0x40, 0x52, 0x66, 0x77, 0x7e,
0xee, 0x3c, 0x10, 0x17, 0x87, 0x09, 0x90, 0xa3, 0xdd, 0x68, 0x91, 0xb8, 0x2f, 0x80,
0xbe, 0x89, 0x76, 0xc1, 0xe7, 0xdc, 0x20, 0xd6, 0x08, 0x17, 0xa5, 0xe8, 0x8e, 0x8b,
0x2c, 0xd4, 0xb8,
],
fp: [
0xff, 0x4c, 0xda, 0x50, 0x02, 0xc8, 0xd1, 0x82, 0x05, 0x88, 0x07, 0xb8, 0x4e, 0x61,
0x6b, 0x6d, 0x33, 0x9e, 0x1b, 0xbe, 0xec, 0xea, 0x01, 0x65, 0x05, 0x68, 0xd8, 0x91,
0xa4, 0x38, 0xe7, 0x06,
],
},
TestVector {
sk: [
0x98, 0xd7, 0x03, 0xfc, 0xb4, 0x05, 0x04, 0xc9, 0x5b, 0x3b, 0x6e, 0xd1, 0x0e, 0xcd,
0x50, 0x08, 0x2c, 0xff, 0x97, 0xdf, 0xd1, 0xdd, 0x9a, 0xa0, 0x91, 0x3c, 0x78, 0xf9,
0x77, 0xc9, 0x62, 0xaf,
],
c: [
0x6a, 0x04, 0x1d, 0xfb, 0x9c, 0xfe, 0xbe, 0xe9, 0x7c, 0xb1, 0x85, 0x4f, 0xdc, 0x48,
0x1c, 0xc0, 0x4f, 0x02, 0xc9, 0x57, 0x7a, 0xa6, 0xf1, 0x3b, 0x2c, 0x44, 0x5b, 0x80,
0xa9, 0x66, 0x9a, 0x22,
],
xsk: [
0x01, 0xff, 0x4c, 0xda, 0x50, 0x01, 0x00, 0x00, 0x80, 0x6a, 0x04, 0x1d, 0xfb, 0x9c,
0xfe, 0xbe, 0xe9, 0x7c, 0xb1, 0x85, 0x4f, 0xdc, 0x48, 0x1c, 0xc0, 0x4f, 0x02, 0xc9,
0x57, 0x7a, 0xa6, 0xf1, 0x3b, 0x2c, 0x44, 0x5b, 0x80, 0xa9, 0x66, 0x9a, 0x22, 0x98,
0xd7, 0x03, 0xfc, 0xb4, 0x05, 0x04, 0xc9, 0x5b, 0x3b, 0x6e, 0xd1, 0x0e, 0xcd, 0x50,
0x08, 0x2c, 0xff, 0x97, 0xdf, 0xd1, 0xdd, 0x9a, 0xa0, 0x91, 0x3c, 0x78, 0xf9, 0x77,
0xc9, 0x62, 0xaf,
],
fp: [
0x32, 0xbb, 0xdc, 0x92, 0x1d, 0x06, 0x6f, 0x23, 0x5d, 0xc9, 0x3e, 0x91, 0x3b, 0x8f,
0xe1, 0xfd, 0x5b, 0x9f, 0x7f, 0x6a, 0x13, 0xd5, 0x6f, 0x18, 0xec, 0x0d, 0x36, 0x20,
0xd1, 0xf7, 0xb9, 0xa6,
],
},
TestVector {
sk: [
0x99, 0xaf, 0xd8, 0x89, 0x4b, 0xaa, 0xd5, 0x87, 0x84, 0xd0, 0xec, 0x08, 0xf5, 0x14,
0x8e, 0xe2, 0xc2, 0xa1, 0x7b, 0x2b, 0x29, 0x4b, 0x08, 0xef, 0x9e, 0x0a, 0x0c, 0xf1,
0x4b, 0xcc, 0x09, 0x20,
],
c: [
0x6d, 0xa8, 0xb5, 0x7a, 0x36, 0xc7, 0x7a, 0xd6, 0x41, 0x2a, 0x9d, 0xc0, 0x11, 0x5f,
0x12, 0xac, 0xed, 0x0e, 0xe0, 0x1c, 0x40, 0x2a, 0x0c, 0xf0, 0xa5, 0x07, 0xcb, 0x17,
0xfc, 0x7b, 0xbd, 0x1d,
],
xsk: [
0x02, 0x32, 0xbb, 0xdc, 0x92, 0x02, 0x00, 0x00, 0x80, 0x6d, 0xa8, 0xb5, 0x7a, 0x36,
0xc7, 0x7a, 0xd6, 0x41, 0x2a, 0x9d, 0xc0, 0x11, 0x5f, 0x12, 0xac, 0xed, 0x0e, 0xe0,
0x1c, 0x40, 0x2a, 0x0c, 0xf0, 0xa5, 0x07, 0xcb, 0x17, 0xfc, 0x7b, 0xbd, 0x1d, 0x99,
0xaf, 0xd8, 0x89, 0x4b, 0xaa, 0xd5, 0x87, 0x84, 0xd0, 0xec, 0x08, 0xf5, 0x14, 0x8e,
0xe2, 0xc2, 0xa1, 0x7b, 0x2b, 0x29, 0x4b, 0x08, 0xef, 0x9e, 0x0a, 0x0c, 0xf1, 0x4b,
0xcc, 0x09, 0x20,
],
fp: [
0x36, 0xa5, 0x7c, 0x4f, 0xc5, 0xb8, 0xb4, 0xa3, 0xd6, 0x2f, 0x22, 0xa5, 0x50, 0x08,
0x78, 0xf3, 0x93, 0x85, 0x6b, 0x7e, 0xcc, 0xe7, 0x71, 0xad, 0x59, 0x7c, 0xa9, 0x64,
0xb9, 0x86, 0x37, 0xd9,
],
},
TestVector {
sk: [
0x96, 0x43, 0x9e, 0xa3, 0x48, 0xa4, 0xb2, 0xce, 0x4e, 0xc7, 0xbe, 0xb4, 0x54, 0x3c,
0x70, 0x27, 0x4c, 0x8f, 0x76, 0x49, 0x5d, 0x60, 0xc5, 0xfa, 0x5f, 0x01, 0x8b, 0x68,
0xf3, 0xc3, 0x23, 0x67,
],
c: [
0xb1, 0x96, 0xe9, 0xb5, 0x80, 0x9d, 0x76, 0x57, 0x7a, 0x89, 0x44, 0xc3, 0xf8, 0xc8,
0xa8, 0x3f, 0x93, 0xf0, 0xc8, 0xf5, 0xac, 0xe6, 0xe7, 0xbc, 0x9c, 0xe4, 0x39, 0x6c,
0x03, 0x4d, 0x93, 0xfe,
],
xsk: [
0x03, 0x36, 0xa5, 0x7c, 0x4f, 0x03, 0x00, 0x00, 0x80, 0xb1, 0x96, 0xe9, 0xb5, 0x80,
0x9d, 0x76, 0x57, 0x7a, 0x89, 0x44, 0xc3, 0xf8, 0xc8, 0xa8, 0x3f, 0x93, 0xf0, 0xc8,
0xf5, 0xac, 0xe6, 0xe7, 0xbc, 0x9c, 0xe4, 0x39, 0x6c, 0x03, 0x4d, 0x93, 0xfe, 0x96,
0x43, 0x9e, 0xa3, 0x48, 0xa4, 0xb2, 0xce, 0x4e, 0xc7, 0xbe, 0xb4, 0x54, 0x3c, 0x70,
0x27, 0x4c, 0x8f, 0x76, 0x49, 0x5d, 0x60, 0xc5, 0xfa, 0x5f, 0x01, 0x8b, 0x68, 0xf3,
0xc3, 0x23, 0x67,
],
fp: [
0xbe, 0x1a, 0x1b, 0x66, 0x1d, 0x2c, 0xa3, 0x19, 0x82, 0x2a, 0x32, 0x55, 0x0d, 0x6d,
0xc4, 0x88, 0xb6, 0x57, 0x1e, 0x0c, 0xd7, 0x81, 0xd5, 0x07, 0x8b, 0x8f, 0x7b, 0xa3,
0x66, 0xdd, 0xd3, 0x68,
],
},
];
111 changes: 68 additions & 43 deletions src/zip32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ use core::fmt;

use blake2b_simd::Params as Blake2bParams;
use subtle::{Choice, ConstantTimeEq, CtOption};
use zip32::ChainCode;
use zcash_spec::VariableLengthSlice;
use zip32::{
hardened_only::{self, HardenedOnlyKey},
ChainCode,
};

use crate::{
keys::{FullViewingKey, SpendingKey},
Expand Down Expand Up @@ -116,6 +120,15 @@ impl KeyIndex {
}
}

#[derive(Clone, Copy, Debug)]
struct Orchard;

impl hardened_only::Context for Orchard {
const MKG_DOMAIN: [u8; 16] = *ZIP32_ORCHARD_PERSONALIZATION;
const CKD_DOMAIN: PrfExpand<([u8; 32], [u8; 4], [u8; 1], VariableLengthSlice)> =
PrfExpand::ORCHARD_ZIP32_CHILD;
}

/// An Orchard extended spending key.
///
/// Defined in [ZIP32: Orchard extended keys][orchardextendedkeys].
Expand All @@ -126,17 +139,15 @@ pub(crate) struct ExtendedSpendingKey {
depth: u8,
parent_fvk_tag: FvkTag,
child_index: KeyIndex,
chain_code: ChainCode,
sk: SpendingKey,
inner: HardenedOnlyKey<Orchard>,
}

impl ConstantTimeEq for ExtendedSpendingKey {
fn ct_eq(&self, rhs: &Self) -> Choice {
self.depth.ct_eq(&rhs.depth)
& self.parent_fvk_tag.0.ct_eq(&rhs.parent_fvk_tag.0)
& self.child_index.ct_eq(&rhs.child_index)
& self.chain_code.ct_eq(&rhs.chain_code)
& self.sk.ct_eq(&rhs.sk)
& self.inner.ct_eq(&rhs.inner)
}
}

Expand Down Expand Up @@ -166,33 +177,19 @@ impl ExtendedSpendingKey {
///
/// Panics if the seed is shorter than 32 bytes or longer than 252 bytes.
fn master(seed: &[u8]) -> Result<Self, Error> {
assert!(seed.len() >= 32 && seed.len() <= 252);
// I := BLAKE2b-512("ZcashIP32Orchard", seed)
let I: [u8; 64] = {
let mut I = Blake2bParams::new()
.hash_length(64)
.personal(ZIP32_ORCHARD_PERSONALIZATION)
.to_state();
I.update(seed);
I.finalize().as_bytes().try_into().unwrap()
};
// I_L is used as the master spending key sk_m.
let sk_m = SpendingKey::from_bytes(I[..32].try_into().unwrap());
if sk_m.is_none().into() {
let m_orchard = HardenedOnlyKey::master(&[seed]);

let sk = SpendingKey::from_bytes(*m_orchard.parts().0);
if sk.is_none().into() {
return Err(Error::InvalidSpendingKey);
}
let sk_m = sk_m.unwrap();

// I_R is used as the master chain code c_m.
let c_m = ChainCode::new(I[32..].try_into().unwrap());

// For the master extended spending key, depth is 0, parent_fvk_tag is 4 zero bytes, and i is 0.
Ok(Self {
depth: 0,
parent_fvk_tag: FvkTag([0; 4]),
child_index: KeyIndex::master(),
chain_code: c_m,
sk: sk_m,
inner: m_orchard,
})
}

Expand All @@ -204,39 +201,31 @@ impl ExtendedSpendingKey {
///
/// Discards index if it results in an invalid sk
fn derive_child(&self, index: ChildIndex) -> Result<Self, Error> {
// I := PRF^Expand(c_par, [0x81] || sk_par || I2LEOSP(i))
let I: [u8; 64] = PrfExpand::ORCHARD_ZIP32_CHILD.with(
self.chain_code.as_bytes(),
self.sk.to_bytes(),
&index.index().to_le_bytes(),
&[0],
&[],
);

// I_L is used as the child spending key sk_i.
let sk_i = SpendingKey::from_bytes(I[..32].try_into().unwrap());
if sk_i.is_none().into() {
let child_i = self.inner.derive_child(index);

let sk = SpendingKey::from_bytes(*child_i.parts().0);
if sk.is_none().into() {
return Err(Error::InvalidSpendingKey);
}
let sk_i = sk_i.unwrap();

// I_R is used as the child chain code c_i.
let c_i = ChainCode::new(I[32..].try_into().unwrap());

let fvk: FullViewingKey = self.into();

Ok(Self {
depth: self.depth + 1,
parent_fvk_tag: FvkFingerprint::from(&fvk).tag(),
child_index: KeyIndex::child(index),
chain_code: c_i,
sk: sk_i,
inner: child_i,
})
}

/// Returns sk of this ExtendedSpendingKey.
pub fn sk(&self) -> SpendingKey {
self.sk
SpendingKey::from_bytes(*self.inner.parts().0).expect("checked during derivation")
}

/// Returns the chain code for this ExtendedSpendingKey.
fn chain_code(&self) -> &ChainCode {
self.inner.parts().1
}
}

Expand Down Expand Up @@ -277,4 +266,40 @@ mod tests {
.ct_eq(&xsk_5h_7)
));
}

#[test]
fn test_vectors() {
let test_vectors = crate::test_vectors::zip32::TEST_VECTORS;

let seed = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31,
];

let i1h = ChildIndex::hardened(1);
let i2h = ChildIndex::hardened(2);
let i3h = ChildIndex::hardened(3);

let m = ExtendedSpendingKey::master(&seed).unwrap();
let m_1h = m.derive_child(i1h).unwrap();
let m_1h_2h = ExtendedSpendingKey::from_path(&seed, &[i1h, i2h]).unwrap();
let m_1h_2h_3h = m_1h_2h.derive_child(i3h).unwrap();

let xsks = [m, m_1h, m_1h_2h, m_1h_2h_3h];
assert_eq!(test_vectors.len(), xsks.len());

for (xsk, tv) in xsks.iter().zip(test_vectors.iter()) {
assert_eq!(xsk.sk().to_bytes(), &tv.sk);
assert_eq!(xsk.chain_code().as_bytes(), &tv.c);

assert_eq!(xsk.depth, tv.xsk[0]);
assert_eq!(&xsk.parent_fvk_tag.0, &tv.xsk[1..5]);
assert_eq!(&xsk.child_index.index().to_le_bytes(), &tv.xsk[5..9]);
assert_eq!(xsk.chain_code().as_bytes(), &tv.xsk[9..9 + 32]);
assert_eq!(xsk.sk().to_bytes(), &tv.xsk[9 + 32..]);

let fvk: FullViewingKey = (&xsk.sk()).into();
assert_eq!(FvkFingerprint::from(&fvk).0, tv.fp);
}
}
}
Loading