Migrate to `zip32 0.1`

Closes zcash/orchard#410.
This commit is contained in:
Jack Grigg 2024-01-10 22:41:16 +00:00
parent 21cff6bb9d
commit 68290a1a58
5 changed files with 102 additions and 73 deletions

View File

@ -24,6 +24,10 @@ and this project adheres to Rust's notion of
- `orchard::tree::Anchor::empty_tree`
### Changed
- Migrated to the `zip32` crate. The following types have been replaced by the
equivalent ones in that crate are now re-exported from there:
- `orchard::keys::DiversifierIndex`
- `orchard::zip32::ChildIndex`
- `orchard::builder`:
- `Builder::new` now takes the bundle type to be used in bundle construction,
instead of taking the flags and anchor separately.
@ -40,6 +44,7 @@ and this project adheres to Rust's notion of
- `AnchorMismatch`
- `SpendInfo::new` now returns a `Result<SpendInfo, SpendError>` instead of an
`Option`.
- `orchard::keys::SpendingKey::from_zip32_seed` now takes a `zip32::AccountId`.
### Removed
- `orchard::bundle::Flags::from_parts`

12
Cargo.lock generated
View File

@ -1442,6 +1442,7 @@ dependencies = [
"tracing",
"zcash_note_encryption",
"zcash_spec",
"zip32",
]
[[package]]
@ -2523,6 +2524,17 @@ dependencies = [
"syn 2.0.31",
]
[[package]]
name = "zip32"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d724a63be4dfb50b7f3617e542984e22e4b4a5b8ca5de91f55613152885e6b22"
dependencies = [
"blake2b_simd",
"memuse",
"subtle",
]
[[package]]
name = "zune-inflate"
version = "0.2.54"

View File

@ -44,6 +44,7 @@ subtle = "2.3"
zcash_note_encryption = "0.4"
incrementalmerkletree = "0.5"
zcash_spec = "0.1"
zip32 = "0.1"
# Logging
tracing = "0.1"

View File

@ -1,8 +1,8 @@
//! Key structures for Orchard.
use core::mem;
use std::io::{self, Read, Write};
use ::zip32::{AccountId, ChildIndex};
use aes::Aes256;
use blake2b_simd::{Hash as Blake2bHash, Params};
use fpe::ff1::{BinaryNumeralString, FF1};
@ -24,9 +24,11 @@ use crate::{
to_scalar, NonIdentityPallasPoint, NonZeroPallasBase, NonZeroPallasScalar,
PreparedNonIdentityBase, PreparedNonZeroScalar, PrfExpand,
},
zip32::{self, ChildIndex, ExtendedSpendingKey},
zip32::{self, ExtendedSpendingKey},
};
pub use ::zip32::DiversifierIndex;
const KDF_ORCHARD_PERSONALIZATION: &[u8; 16] = b"Zcash_OrchardKDF";
const ZIP32_PURPOSE: u32 = 32;
@ -91,13 +93,17 @@ impl SpendingKey {
pub fn from_zip32_seed(
seed: &[u8],
coin_type: u32,
account: u32,
account: AccountId,
) -> Result<Self, zip32::Error> {
if coin_type >= (1 << 31) {
return Err(zip32::Error::InvalidChildIndex(coin_type));
}
// Call zip32 logic
let path = &[
ChildIndex::try_from(ZIP32_PURPOSE)?,
ChildIndex::try_from(coin_type)?,
ChildIndex::try_from(account)?,
ChildIndex::hardened(ZIP32_PURPOSE),
ChildIndex::hardened(coin_type),
ChildIndex::hardened(account.into()),
];
ExtendedSpendingKey::from_path(seed, path).map(|esk| esk.sk())
}
@ -481,44 +487,15 @@ impl FullViewingKey {
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct DiversifierKey([u8; 32]);
/// The index for a particular diversifier.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DiversifierIndex([u8; 11]);
macro_rules! di_from {
($n:ident) => {
impl From<$n> for DiversifierIndex {
fn from(j: $n) -> Self {
let mut j_bytes = [0; 11];
j_bytes[..mem::size_of::<$n>()].copy_from_slice(&j.to_le_bytes());
DiversifierIndex(j_bytes)
}
}
};
}
di_from!(u32);
di_from!(u64);
di_from!(usize);
impl From<[u8; 11]> for DiversifierIndex {
fn from(j_bytes: [u8; 11]) -> Self {
DiversifierIndex(j_bytes)
}
}
impl DiversifierIndex {
/// Returns the raw bytes of the diversifier index.
pub fn to_bytes(&self) -> &[u8; 11] {
&self.0
}
}
impl DiversifierKey {
/// Returns the diversifier at the given index.
pub fn get(&self, j: impl Into<DiversifierIndex>) -> Diversifier {
let ff = FF1::<Aes256>::new(&self.0, 2).expect("valid radix");
let enc = ff
.encrypt(&[], &BinaryNumeralString::from_bytes_le(&j.into().0[..]))
.encrypt(
&[],
&BinaryNumeralString::from_bytes_le(j.into().as_bytes()),
)
.unwrap();
Diversifier(enc.to_bytes_le().try_into().unwrap())
}

View File

@ -3,13 +3,16 @@
use core::fmt;
use blake2b_simd::Params as Blake2bParams;
use subtle::{Choice, ConstantTimeEq};
use subtle::{Choice, ConstantTimeEq, CtOption};
use zip32::ChainCode;
use crate::{
keys::{FullViewingKey, SpendingKey},
spec::PrfExpand,
};
pub use zip32::ChildIndex;
const ZIP32_ORCHARD_PERSONALIZATION: &[u8; 16] = b"ZcashIP32Orchard";
const ZIP32_ORCHARD_FVFP_PERSONALIZATION: &[u8; 16] = b"ZcashOrchardFVFP";
@ -64,26 +67,54 @@ impl FvkTag {
}
}
/// A hardened child index for a derived key.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ChildIndex(u32);
/// The derivation index associated with a key.
///
/// Master keys are never derived via the ZIP 32 child derivation process, but they have
/// an index in their encoding. This type allows the encoding to be represented, while
/// also enabling the derivation methods to only accept [`ChildIndex`].
#[derive(Clone, Copy, Debug)]
struct KeyIndex(CtOption<ChildIndex>);
impl TryFrom<u32> for ChildIndex {
type Error = Error;
/// `index` must be less than 2^31
fn try_from(index: u32) -> Result<Self, Self::Error> {
if index < (1 << 31) {
Ok(Self(index + (1 << 31)))
} else {
Err(Error::InvalidChildIndex(32))
}
impl ConstantTimeEq for KeyIndex {
fn ct_eq(&self, other: &Self) -> Choice {
// We use a `CtOption` above instead of an enum so that we can implement this.
self.0.ct_eq(&other.0)
}
}
/// The chain code forming the second half of an Orchard extended key.
#[derive(Debug, Copy, Clone, PartialEq)]
struct ChainCode([u8; 32]);
impl PartialEq for KeyIndex {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl Eq for KeyIndex {}
impl KeyIndex {
fn master() -> Self {
Self(CtOption::new(ChildIndex::hardened(0), 0.into()))
}
fn child(i: ChildIndex) -> Self {
Self(CtOption::new(i, 1.into()))
}
fn new(depth: u8, i: u32) -> Option<Self> {
match (depth == 0, i) {
(true, 0) => Some(KeyIndex::master()),
(false, _) => ChildIndex::from_index(i).map(KeyIndex::child),
_ => None,
}
}
fn index(&self) -> u32 {
if self.0.is_some().into() {
self.0.unwrap().index()
} else {
0
}
}
}
/// An Orchard extended spending key.
///
@ -94,7 +125,7 @@ struct ChainCode([u8; 32]);
pub(crate) struct ExtendedSpendingKey {
depth: u8,
parent_fvk_tag: FvkTag,
child_index: ChildIndex,
child_index: KeyIndex,
chain_code: ChainCode,
sk: SpendingKey,
}
@ -103,8 +134,8 @@ 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.0.ct_eq(&rhs.child_index.0)
& self.chain_code.0.ct_eq(&rhs.chain_code.0)
& self.child_index.ct_eq(&rhs.child_index)
& self.chain_code.ct_eq(&rhs.chain_code)
& self.sk.ct_eq(&rhs.sk)
}
}
@ -153,13 +184,13 @@ impl ExtendedSpendingKey {
let sk_m = sk_m.unwrap();
// I_R is used as the master chain code c_m.
let c_m = ChainCode(I[32..].try_into().unwrap());
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: ChildIndex(0),
child_index: KeyIndex::master(),
chain_code: c_m,
sk: sk_m,
})
@ -175,9 +206,9 @@ impl ExtendedSpendingKey {
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.0,
self.chain_code.as_bytes(),
self.sk.to_bytes(),
&index.0.to_le_bytes(),
&index.index().to_le_bytes(),
);
// I_L is used as the child spending key sk_i.
@ -188,14 +219,14 @@ impl ExtendedSpendingKey {
let sk_i = sk_i.unwrap();
// I_R is used as the child chain code c_i.
let c_i = ChainCode(I[32..].try_into().unwrap());
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: index,
child_index: KeyIndex::child(index),
chain_code: c_i,
sk: sk_i,
})
@ -216,8 +247,8 @@ mod tests {
let seed = [0; 32];
let xsk_m = ExtendedSpendingKey::master(&seed).unwrap();
let i_5 = 5;
let xsk_5 = xsk_m.derive_child(i_5.try_into().unwrap());
let i_5 = ChildIndex::hardened(5);
let xsk_5 = xsk_m.derive_child(i_5);
assert!(xsk_5.is_ok());
}
@ -227,18 +258,21 @@ mod tests {
let seed = [0; 32];
let xsk_m = ExtendedSpendingKey::master(&seed).unwrap();
let xsk_5h = xsk_m.derive_child(5.try_into().unwrap()).unwrap();
let xsk_5h = xsk_m.derive_child(ChildIndex::hardened(5)).unwrap();
assert!(bool::from(
ExtendedSpendingKey::from_path(&seed, &[5.try_into().unwrap()])
ExtendedSpendingKey::from_path(&seed, &[ChildIndex::hardened(5)])
.unwrap()
.ct_eq(&xsk_5h)
));
let xsk_5h_7 = xsk_5h.derive_child(7.try_into().unwrap()).unwrap();
let xsk_5h_7 = xsk_5h.derive_child(ChildIndex::hardened(7)).unwrap();
assert!(bool::from(
ExtendedSpendingKey::from_path(&seed, &[5.try_into().unwrap(), 7.try_into().unwrap()])
.unwrap()
.ct_eq(&xsk_5h_7)
ExtendedSpendingKey::from_path(
&seed,
&[ChildIndex::hardened(5), ChildIndex::hardened(7)]
)
.unwrap()
.ct_eq(&xsk_5h_7)
));
}
}