Skip to content

Commit

Permalink
Migrate to zip32::hardened_only implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
str4d committed Feb 21, 2025
1 parent fa08740 commit d38fdba
Showing 1 changed file with 37 additions and 48 deletions.
85 changes: 37 additions & 48 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 @@ -300,16 +289,16 @@ mod tests {
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.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..]);
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();
let fvk: FullViewingKey = (&xsk.sk()).into();
assert_eq!(FvkFingerprint::from(&fvk).0, tv.fp);
}
}
Expand Down

0 comments on commit d38fdba

Please sign in to comment.