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 0.1 #414

Merged
merged 2 commits into from
Jan 11, 2024
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
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)
));
}
}
Loading