mirror of https://github.com/zcash/orchard.git
Merge pull request #414 from zcash/zip32-0.1
This commit is contained in:
commit
c5dea4e33b
45
CHANGELOG.md
45
CHANGELOG.md
|
@ -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`
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
55
src/keys.rs
55
src/keys.rs
|
@ -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())
|
||||
}
|
||||
|
|
102
src/zip32.rs
102
src/zip32.rs
|
@ -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)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue