Skip to content

Commit

Permalink
Merge pull request #414 from zcash/zip32-0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
nuttycom authored Jan 11, 2024
2 parents d5fa3e1 + 68290a1 commit c5dea4e
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 90 deletions.
45 changes: 26 additions & 19 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,36 @@ and this project adheres to Rust's notion of
with or without use of the license exception.

### Added
- `orchard::builder::bundle`
- `orchard::builder::BundleMetadata`
- `orchard::builder::BundleType`
- `orchard::builder::OutputInfo`
- `orchard::builder`:
- `bundle`
- `BundleMetadata`
- `BundleType`
- `OutputInfo`
- `orchard::bundle::Flags::{ENABLED, SPENDS_DISABLED, OUTPUTS_DISABLED}`
- `orchard::tree::Anchor::empty_tree`

### Changed
- `orchard::builder::Builder::new` now takes the bundle type to be used
in bundle construction, instead of taking the flags and anchor separately.
- `orchard::builder::Builder::add_recipient` has been renamed to `add_output`
in order to clarify than more than one output of a given transaction may be
sent to the same recipient.
- `orchard::builder::Builder::build` now takes an additional `BundleType` argument
that specifies how actions should be padded, instead of using hardcoded padding.
It also now returns a `Result<Option<(Bundle<...>, BundleMetadata)>, ...>` instead of a
`Result<Bundle<...>, ...>`.
- `orchard::builder::BuildError` has additional variants:
- `SpendsDisabled`
- `OutputsDisabled`
- `AnchorMismatch`
- `orchard::builder::SpendInfo::new` now returns a `Result<SpendInfo, SpendError>`
instead of an `Option`.
- 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.
- `Builder::add_recipient` has been renamed to `add_output` in order to
clarify than more than one output of a given transaction may be sent to the
same recipient.
- `Builder::build` now takes an additional `BundleType` argument that
specifies how actions should be padded, instead of using hardcoded padding.
It also now returns a `Result<Option<(Bundle<...>, BundleMetadata)>, ...>`
instead of a `Result<Bundle<...>, ...>`.
- `BuildError` has additional variants:
- `SpendsDisabled`
- `OutputsDisabled`
- `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`
Expand Down
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
55 changes: 16 additions & 39 deletions src/keys.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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;

Expand Down Expand Up @@ -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())
}
Expand Down Expand Up @@ -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())
}
Expand Down
98 changes: 66 additions & 32 deletions src/zip32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -64,27 +67,55 @@ 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 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)
}
}

impl TryFrom<u32> for ChildIndex {
type Error = Error;
impl PartialEq for KeyIndex {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}

impl Eq for KeyIndex {}

/// `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)))
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 {
Err(Error::InvalidChildIndex(32))
0
}
}
}

/// The chain code forming the second half of an Orchard extended key.
#[derive(Debug, Copy, Clone, PartialEq)]
struct ChainCode([u8; 32]);

/// An Orchard extended spending key.
///
/// Defined in [ZIP32: Orchard extended keys][orchardextendedkeys].
Expand All @@ -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,
}
Expand All @@ -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)
}
}
Expand Down Expand Up @@ -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,
})
Expand All @@ -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.
Expand All @@ -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,
})
Expand All @@ -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());
}
Expand All @@ -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)
));
}
}

0 comments on commit c5dea4e

Please sign in to comment.