2021-07-15 00:18:01 -07:00
|
|
|
//! Key structures for Orchard.
|
|
|
|
|
2022-04-28 13:20:23 -07:00
|
|
|
use core::fmt;
|
2021-07-15 00:18:01 -07:00
|
|
|
|
|
|
|
use blake2b_simd::Params as Blake2bParams;
|
2024-01-10 14:41:16 -08:00
|
|
|
use subtle::{Choice, ConstantTimeEq, CtOption};
|
|
|
|
use zip32::ChainCode;
|
2021-07-15 00:18:01 -07:00
|
|
|
|
|
|
|
use crate::{
|
|
|
|
keys::{FullViewingKey, SpendingKey},
|
|
|
|
spec::PrfExpand,
|
|
|
|
};
|
|
|
|
|
2024-01-10 14:41:16 -08:00
|
|
|
pub use zip32::ChildIndex;
|
|
|
|
|
2021-07-15 00:18:01 -07:00
|
|
|
const ZIP32_ORCHARD_PERSONALIZATION: &[u8; 16] = b"ZcashIP32Orchard";
|
|
|
|
const ZIP32_ORCHARD_FVFP_PERSONALIZATION: &[u8; 16] = b"ZcashOrchardFVFP";
|
|
|
|
|
2021-08-31 15:49:58 -07:00
|
|
|
/// Errors produced in derivation of extended spending keys
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
|
|
pub enum Error {
|
|
|
|
/// A seed resulted in an invalid spending key
|
|
|
|
InvalidSpendingKey,
|
|
|
|
/// A child index in a derivation path exceeded 2^31
|
|
|
|
InvalidChildIndex(u32),
|
|
|
|
}
|
2021-07-15 00:18:01 -07:00
|
|
|
|
2021-08-31 15:49:58 -07:00
|
|
|
impl fmt::Display for Error {
|
2021-07-15 00:18:01 -07:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
write!(f, "Seed produced invalid spending key.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-31 15:49:58 -07:00
|
|
|
//impl std::error::Error for Error {}
|
2021-07-15 00:18:01 -07:00
|
|
|
|
|
|
|
/// An Orchard full viewing key fingerprint
|
|
|
|
struct FvkFingerprint([u8; 32]);
|
|
|
|
|
|
|
|
impl From<&FullViewingKey> for FvkFingerprint {
|
|
|
|
fn from(fvk: &FullViewingKey) -> Self {
|
|
|
|
let mut h = Blake2bParams::new()
|
|
|
|
.hash_length(32)
|
|
|
|
.personal(ZIP32_ORCHARD_FVFP_PERSONALIZATION)
|
|
|
|
.to_state();
|
|
|
|
h.update(&fvk.to_bytes());
|
|
|
|
let mut fvfp = [0u8; 32];
|
|
|
|
fvfp.copy_from_slice(h.finalize().as_bytes());
|
|
|
|
FvkFingerprint(fvfp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// An Orchard full viewing key tag
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
|
|
struct FvkTag([u8; 4]);
|
|
|
|
|
|
|
|
impl FvkFingerprint {
|
|
|
|
fn tag(&self) -> FvkTag {
|
|
|
|
let mut tag = [0u8; 4];
|
|
|
|
tag.copy_from_slice(&self.0[..4]);
|
|
|
|
FvkTag(tag)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FvkTag {
|
|
|
|
fn master() -> Self {
|
|
|
|
FvkTag([0u8; 4])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-10 14:41:16 -08:00
|
|
|
/// 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)
|
|
|
|
}
|
|
|
|
}
|
2021-08-31 15:49:58 -07:00
|
|
|
|
2024-01-10 14:41:16 -08:00
|
|
|
impl PartialEq for KeyIndex {
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
self.ct_eq(other).into()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Eq for KeyIndex {}
|
2021-07-15 00:18:01 -07:00
|
|
|
|
2024-01-10 14:41:16 -08:00
|
|
|
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()
|
2021-08-31 15:49:58 -07:00
|
|
|
} else {
|
2024-01-10 14:41:16 -08:00
|
|
|
0
|
2021-08-31 15:49:58 -07:00
|
|
|
}
|
2021-07-15 00:18:01 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// An Orchard extended spending key.
|
|
|
|
///
|
|
|
|
/// Defined in [ZIP32: Orchard extended keys][orchardextendedkeys].
|
|
|
|
///
|
|
|
|
/// [orchardextendedkeys]: https://zips.z.cash/zip-0032#orchard-extended-keys
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub(crate) struct ExtendedSpendingKey {
|
|
|
|
depth: u8,
|
|
|
|
parent_fvk_tag: FvkTag,
|
2024-01-10 14:41:16 -08:00
|
|
|
child_index: KeyIndex,
|
2021-07-15 00:18:01 -07:00
|
|
|
chain_code: ChainCode,
|
|
|
|
sk: SpendingKey,
|
|
|
|
}
|
|
|
|
|
2021-11-30 15:24:50 -08:00
|
|
|
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)
|
2024-01-10 14:41:16 -08:00
|
|
|
& self.child_index.ct_eq(&rhs.child_index)
|
|
|
|
& self.chain_code.ct_eq(&rhs.chain_code)
|
2021-11-30 15:24:50 -08:00
|
|
|
& self.sk.ct_eq(&rhs.sk)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-15 00:18:01 -07:00
|
|
|
#[allow(non_snake_case)]
|
|
|
|
impl ExtendedSpendingKey {
|
|
|
|
/// Returns the spending key of the child key corresponding to
|
|
|
|
/// the path derived from the master key
|
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
///
|
|
|
|
/// Panics if seed results in invalid spending key.
|
2021-08-31 15:49:58 -07:00
|
|
|
pub fn from_path(seed: &[u8], path: &[ChildIndex]) -> Result<Self, Error> {
|
|
|
|
let mut xsk = Self::master(seed)?;
|
|
|
|
for i in path {
|
|
|
|
xsk = xsk.derive_child(*i)?;
|
2021-07-15 00:18:01 -07:00
|
|
|
}
|
2021-08-31 15:49:58 -07:00
|
|
|
Ok(xsk)
|
2021-07-15 00:18:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Generates the master key of an Orchard extended spending key.
|
|
|
|
///
|
|
|
|
/// Defined in [ZIP32: Orchard master key generation][orchardmasterkey].
|
|
|
|
///
|
|
|
|
/// [orchardmasterkey]: https://zips.z.cash/zip-0032#orchard-master-key-generation
|
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
///
|
|
|
|
/// Panics if the seed is shorter than 32 bytes or longer than 252 bytes.
|
2021-08-31 15:49:58 -07:00
|
|
|
fn master(seed: &[u8]) -> Result<Self, Error> {
|
2021-07-15 00:18:01 -07:00
|
|
|
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() {
|
2021-08-31 15:49:58 -07:00
|
|
|
return Err(Error::InvalidSpendingKey);
|
2021-07-15 00:18:01 -07:00
|
|
|
}
|
|
|
|
let sk_m = sk_m.unwrap();
|
|
|
|
|
|
|
|
// I_R is used as the master chain code c_m.
|
2024-01-10 14:41:16 -08:00
|
|
|
let c_m = ChainCode::new(I[32..].try_into().unwrap());
|
2021-07-15 00:18:01 -07:00
|
|
|
|
|
|
|
// 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]),
|
2024-01-10 14:41:16 -08:00
|
|
|
child_index: KeyIndex::master(),
|
2021-07-15 00:18:01 -07:00
|
|
|
chain_code: c_m,
|
|
|
|
sk: sk_m,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Derives a child key from a parent key at a given index.
|
|
|
|
///
|
|
|
|
/// Defined in [ZIP32: Orchard child key derivation][orchardchildkey].
|
|
|
|
///
|
|
|
|
/// [orchardchildkey]: https://zips.z.cash/zip-0032#orchard-child-key-derivation
|
|
|
|
///
|
|
|
|
/// Discards index if it results in an invalid sk
|
2021-08-31 15:49:58 -07:00
|
|
|
fn derive_child(&self, index: ChildIndex) -> Result<Self, Error> {
|
2021-07-15 00:18:01 -07:00
|
|
|
// I := PRF^Expand(c_par, [0x81] || sk_par || I2LEOSP(i))
|
2024-01-10 13:50:10 -08:00
|
|
|
let I: [u8; 64] = PrfExpand::ORCHARD_ZIP32_CHILD.with(
|
2024-01-10 14:41:16 -08:00
|
|
|
self.chain_code.as_bytes(),
|
2024-01-10 13:50:10 -08:00
|
|
|
self.sk.to_bytes(),
|
2024-01-10 14:41:16 -08:00
|
|
|
&index.index().to_le_bytes(),
|
2021-07-15 00:18:01 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
// 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() {
|
2021-08-31 15:49:58 -07:00
|
|
|
return Err(Error::InvalidSpendingKey);
|
2021-07-15 00:18:01 -07:00
|
|
|
}
|
|
|
|
let sk_i = sk_i.unwrap();
|
|
|
|
|
|
|
|
// I_R is used as the child chain code c_i.
|
2024-01-10 14:41:16 -08:00
|
|
|
let c_i = ChainCode::new(I[32..].try_into().unwrap());
|
2021-07-15 00:18:01 -07:00
|
|
|
|
|
|
|
let fvk: FullViewingKey = self.into();
|
|
|
|
|
|
|
|
Ok(Self {
|
|
|
|
depth: self.depth + 1,
|
|
|
|
parent_fvk_tag: FvkFingerprint::from(&fvk).tag(),
|
2024-01-10 14:41:16 -08:00
|
|
|
child_index: KeyIndex::child(index),
|
2021-07-15 00:18:01 -07:00
|
|
|
chain_code: c_i,
|
|
|
|
sk: sk_i,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns sk of this ExtendedSpendingKey.
|
|
|
|
pub fn sk(&self) -> SpendingKey {
|
|
|
|
self.sk
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn derive_child() {
|
|
|
|
let seed = [0; 32];
|
|
|
|
let xsk_m = ExtendedSpendingKey::master(&seed).unwrap();
|
|
|
|
|
2024-01-10 14:41:16 -08:00
|
|
|
let i_5 = ChildIndex::hardened(5);
|
|
|
|
let xsk_5 = xsk_m.derive_child(i_5);
|
2021-07-15 00:18:01 -07:00
|
|
|
|
|
|
|
assert!(xsk_5.is_ok());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn path() {
|
|
|
|
let seed = [0; 32];
|
|
|
|
let xsk_m = ExtendedSpendingKey::master(&seed).unwrap();
|
|
|
|
|
2024-01-10 14:41:16 -08:00
|
|
|
let xsk_5h = xsk_m.derive_child(ChildIndex::hardened(5)).unwrap();
|
2021-11-30 15:25:35 -08:00
|
|
|
assert!(bool::from(
|
2024-01-10 14:41:16 -08:00
|
|
|
ExtendedSpendingKey::from_path(&seed, &[ChildIndex::hardened(5)])
|
2021-11-30 15:25:35 -08:00
|
|
|
.unwrap()
|
|
|
|
.ct_eq(&xsk_5h)
|
|
|
|
));
|
2021-07-15 00:18:01 -07:00
|
|
|
|
2024-01-10 14:41:16 -08:00
|
|
|
let xsk_5h_7 = xsk_5h.derive_child(ChildIndex::hardened(7)).unwrap();
|
2021-11-30 15:25:35 -08:00
|
|
|
assert!(bool::from(
|
2024-01-10 14:41:16 -08:00
|
|
|
ExtendedSpendingKey::from_path(
|
|
|
|
&seed,
|
|
|
|
&[ChildIndex::hardened(5), ChildIndex::hardened(7)]
|
|
|
|
)
|
|
|
|
.unwrap()
|
|
|
|
.ct_eq(&xsk_5h_7)
|
2021-11-30 15:25:35 -08:00
|
|
|
));
|
2021-07-15 00:18:01 -07:00
|
|
|
}
|
|
|
|
}
|