mirror of https://github.com/zcash/halo2.git
commit
bd2a7c2e79
|
@ -659,7 +659,7 @@ pub mod testing {
|
||||||
) -> ArbitraryBundleInputs<StdRng> {
|
) -> ArbitraryBundleInputs<StdRng> {
|
||||||
ArbitraryBundleInputs {
|
ArbitraryBundleInputs {
|
||||||
rng: StdRng::from_seed(rng_seed),
|
rng: StdRng::from_seed(rng_seed),
|
||||||
sk: sk.clone(),
|
sk,
|
||||||
anchor,
|
anchor,
|
||||||
notes: notes_and_auth_paths,
|
notes: notes_and_auth_paths,
|
||||||
recipient_amounts
|
recipient_amounts
|
||||||
|
|
62
src/keys.rs
62
src/keys.rs
|
@ -1,6 +1,7 @@
|
||||||
//! Key structures for Orchard.
|
//! Key structures for Orchard.
|
||||||
|
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::{TryFrom, TryInto};
|
||||||
|
use std::io::{self, Read, Write};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
use aes::Aes256;
|
use aes::Aes256;
|
||||||
|
@ -20,16 +21,18 @@ use crate::{
|
||||||
commit_ivk, diversify_hash, extract_p, ka_orchard, prf_nf, to_base, to_scalar,
|
commit_ivk, diversify_hash, extract_p, ka_orchard, prf_nf, to_base, to_scalar,
|
||||||
NonIdentityPallasPoint, NonZeroPallasBase, NonZeroPallasScalar, PrfExpand,
|
NonIdentityPallasPoint, NonZeroPallasBase, NonZeroPallasScalar, PrfExpand,
|
||||||
},
|
},
|
||||||
|
zip32::{self, ChildIndex, ExtendedSpendingKey},
|
||||||
};
|
};
|
||||||
|
|
||||||
const KDF_ORCHARD_PERSONALIZATION: &[u8; 16] = b"Zcash_OrchardKDF";
|
const KDF_ORCHARD_PERSONALIZATION: &[u8; 16] = b"Zcash_OrchardKDF";
|
||||||
|
const ZIP32_PURPOSE: u32 = 32;
|
||||||
|
|
||||||
/// A spending key, from which all key material is derived.
|
/// A spending key, from which all key material is derived.
|
||||||
///
|
///
|
||||||
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
|
||||||
///
|
///
|
||||||
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct SpendingKey([u8; 32]);
|
pub struct SpendingKey([u8; 32]);
|
||||||
|
|
||||||
impl SpendingKey {
|
impl SpendingKey {
|
||||||
|
@ -69,6 +72,21 @@ impl SpendingKey {
|
||||||
pub fn to_bytes(&self) -> &[u8; 32] {
|
pub fn to_bytes(&self) -> &[u8; 32] {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Derives the Orchard spending key for the given seed, coin type, and account.
|
||||||
|
pub fn from_zip32_seed(
|
||||||
|
seed: &[u8],
|
||||||
|
coin_type: u32,
|
||||||
|
account: u32,
|
||||||
|
) -> Result<Self, zip32::Error> {
|
||||||
|
// Call zip32 logic
|
||||||
|
let path = &[
|
||||||
|
ChildIndex::try_from(ZIP32_PURPOSE)?,
|
||||||
|
ChildIndex::try_from(coin_type)?,
|
||||||
|
ChildIndex::try_from(account)?,
|
||||||
|
];
|
||||||
|
ExtendedSpendingKey::from_path(seed, path).map(|esk| esk.sk())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A spend authorizing key, used to create spend authorization signatures.
|
/// A spend authorizing key, used to create spend authorization signatures.
|
||||||
|
@ -272,6 +290,12 @@ impl From<&SpendingKey> for FullViewingKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&ExtendedSpendingKey> for FullViewingKey {
|
||||||
|
fn from(extsk: &ExtendedSpendingKey) -> Self {
|
||||||
|
(&extsk.sk()).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<FullViewingKey> for SpendValidatingKey {
|
impl From<FullViewingKey> for SpendValidatingKey {
|
||||||
fn from(fvk: FullViewingKey) -> Self {
|
fn from(fvk: FullViewingKey) -> Self {
|
||||||
fvk.ak
|
fvk.ak
|
||||||
|
@ -319,18 +343,44 @@ impl FullViewingKey {
|
||||||
/// Serializes the full viewing key as specified in [Zcash Protocol Spec § 5.6.4.4: Orchard Raw Full Viewing Keys][orchardrawfullviewingkeys]
|
/// Serializes the full viewing key as specified in [Zcash Protocol Spec § 5.6.4.4: Orchard Raw Full Viewing Keys][orchardrawfullviewingkeys]
|
||||||
///
|
///
|
||||||
/// [orchardrawfullviewingkeys]: https://zips.z.cash/protocol/protocol.pdf#orchardfullviewingkeyencoding
|
/// [orchardrawfullviewingkeys]: https://zips.z.cash/protocol/protocol.pdf#orchardfullviewingkeyencoding
|
||||||
pub fn to_raw_fvk_bytes(&self) -> [u8; 96] {
|
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||||
|
let ak_raw: [u8; 32] = self.ak.0.clone().into();
|
||||||
|
writer.write_all(&ak_raw)?;
|
||||||
|
writer.write_all(&self.nk.0.to_bytes())?;
|
||||||
|
writer.write_all(&self.rivk.0.to_bytes())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses a full viewing key from its "raw" encoding as specified in [Zcash Protocol Spec § 5.6.4.4: Orchard Raw Full Viewing Keys][orchardrawfullviewingkeys]
|
||||||
|
///
|
||||||
|
/// [orchardrawfullviewingkeys]: https://zips.z.cash/protocol/protocol.pdf#orchardfullviewingkeyencoding
|
||||||
|
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
|
||||||
|
let mut data = [0u8; 96];
|
||||||
|
reader.read_exact(&mut data)?;
|
||||||
|
|
||||||
|
Self::from_bytes(&data).ok_or_else(|| {
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"Unable to deserialize a valid Orchard FullViewingKey from bytes".to_owned(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serializes the full viewing key as specified in [Zcash Protocol Spec § 5.6.4.4: Orchard Raw Full Viewing Keys][orchardrawfullviewingkeys]
|
||||||
|
///
|
||||||
|
/// [orchardrawfullviewingkeys]: https://zips.z.cash/protocol/protocol.pdf#orchardfullviewingkeyencoding
|
||||||
|
pub fn to_bytes(&self) -> [u8; 96] {
|
||||||
let mut result = [0u8; 96];
|
let mut result = [0u8; 96];
|
||||||
result[..32].copy_from_slice(&self.ak.to_bytes());
|
self.write(&mut result[..])
|
||||||
result[32..64].copy_from_slice(&self.nk.to_bytes());
|
.expect("should be able to serialize a FullViewingKey");
|
||||||
result[64..].copy_from_slice(&<[u8; 32]>::from(&self.rivk.0));
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses a full viewing key from its "raw" encoding as specified in [Zcash Protocol Spec § 5.6.4.4: Orchard Raw Full Viewing Keys][orchardrawfullviewingkeys]
|
/// Parses a full viewing key from its "raw" encoding as specified in [Zcash Protocol Spec § 5.6.4.4: Orchard Raw Full Viewing Keys][orchardrawfullviewingkeys]
|
||||||
///
|
///
|
||||||
/// [orchardrawfullviewingkeys]: https://zips.z.cash/protocol/protocol.pdf#orchardfullviewingkeyencoding
|
/// [orchardrawfullviewingkeys]: https://zips.z.cash/protocol/protocol.pdf#orchardfullviewingkeyencoding
|
||||||
pub fn from_raw_fvk_bytes(bytes: &[u8; 96]) -> Option<Self> {
|
pub fn from_bytes(bytes: &[u8; 96]) -> Option<Self> {
|
||||||
let ak = SpendValidatingKey::from_bytes(&bytes[..32])?;
|
let ak = SpendValidatingKey::from_bytes(&bytes[..32])?;
|
||||||
let nk = NullifierDerivingKey::from_bytes(&bytes[32..64])?;
|
let nk = NullifierDerivingKey::from_bytes(&bytes[32..64])?;
|
||||||
let rivk = CommitIvkRandomness::from_bytes(&bytes[64..])?;
|
let rivk = CommitIvkRandomness::from_bytes(&bytes[64..])?;
|
||||||
|
|
|
@ -28,6 +28,7 @@ pub mod primitives;
|
||||||
mod spec;
|
mod spec;
|
||||||
pub mod tree;
|
pub mod tree;
|
||||||
pub mod value;
|
pub mod value;
|
||||||
|
pub mod zip32;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_vectors;
|
mod test_vectors;
|
||||||
|
|
|
@ -10,6 +10,7 @@ pub(crate) enum PrfExpand {
|
||||||
OrchardNk,
|
OrchardNk,
|
||||||
OrchardRivk,
|
OrchardRivk,
|
||||||
Psi,
|
Psi,
|
||||||
|
OrchardZip32Child,
|
||||||
OrchardDkOvk,
|
OrchardDkOvk,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +23,7 @@ impl PrfExpand {
|
||||||
Self::OrchardNk => 0x07,
|
Self::OrchardNk => 0x07,
|
||||||
Self::OrchardRivk => 0x08,
|
Self::OrchardRivk => 0x08,
|
||||||
Self::Psi => 0x09,
|
Self::Psi => 0x09,
|
||||||
|
Self::OrchardZip32Child => 0x81,
|
||||||
Self::OrchardDkOvk => 0x82,
|
Self::OrchardDkOvk => 0x82,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,244 @@
|
||||||
|
//! Key structures for Orchard.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
convert::{TryFrom, TryInto},
|
||||||
|
fmt,
|
||||||
|
};
|
||||||
|
|
||||||
|
use blake2b_simd::Params as Blake2bParams;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
keys::{FullViewingKey, SpendingKey},
|
||||||
|
spec::PrfExpand,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ZIP32_ORCHARD_PERSONALIZATION: &[u8; 16] = b"ZcashIP32Orchard";
|
||||||
|
const ZIP32_ORCHARD_FVFP_PERSONALIZATION: &[u8; 16] = b"ZcashOrchardFVFP";
|
||||||
|
|
||||||
|
/// 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),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "Seed produced invalid spending key.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//impl std::error::Error for Error {}
|
||||||
|
|
||||||
|
/// 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])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A hardened child index for a derived key.
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
pub struct ChildIndex(u32);
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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].
|
||||||
|
///
|
||||||
|
/// [orchardextendedkeys]: https://zips.z.cash/zip-0032#orchard-extended-keys
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct ExtendedSpendingKey {
|
||||||
|
depth: u8,
|
||||||
|
parent_fvk_tag: FvkTag,
|
||||||
|
child_index: ChildIndex,
|
||||||
|
chain_code: ChainCode,
|
||||||
|
sk: SpendingKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::cmp::PartialEq for ExtendedSpendingKey {
|
||||||
|
fn eq(&self, rhs: &ExtendedSpendingKey) -> bool {
|
||||||
|
self.depth == rhs.depth
|
||||||
|
&& self.parent_fvk_tag == rhs.parent_fvk_tag
|
||||||
|
&& self.child_index == rhs.child_index
|
||||||
|
&& self.chain_code == rhs.chain_code
|
||||||
|
&& self.sk == rhs.sk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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.
|
||||||
|
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)?;
|
||||||
|
}
|
||||||
|
Ok(xsk)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
fn master(seed: &[u8]) -> Result<Self, Error> {
|
||||||
|
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() {
|
||||||
|
return Err(Error::InvalidSpendingKey);
|
||||||
|
}
|
||||||
|
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());
|
||||||
|
|
||||||
|
// 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),
|
||||||
|
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
|
||||||
|
fn derive_child(&self, index: ChildIndex) -> Result<Self, Error> {
|
||||||
|
// I := PRF^Expand(c_par, [0x81] || sk_par || I2LEOSP(i))
|
||||||
|
let I: [u8; 64] = PrfExpand::OrchardZip32Child.with_ad_slices(
|
||||||
|
&self.chain_code.0,
|
||||||
|
&[self.sk.to_bytes(), &index.0.to_le_bytes()],
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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() {
|
||||||
|
return Err(Error::InvalidSpendingKey);
|
||||||
|
}
|
||||||
|
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 fvk: FullViewingKey = self.into();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
depth: self.depth + 1,
|
||||||
|
parent_fvk_tag: FvkFingerprint::from(&fvk).tag(),
|
||||||
|
child_index: index,
|
||||||
|
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();
|
||||||
|
|
||||||
|
let i_5 = 5;
|
||||||
|
let xsk_5 = xsk_m.derive_child(i_5.try_into().unwrap());
|
||||||
|
|
||||||
|
assert!(xsk_5.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn path() {
|
||||||
|
let seed = [0; 32];
|
||||||
|
let xsk_m = ExtendedSpendingKey::master(&seed).unwrap();
|
||||||
|
|
||||||
|
let xsk_5h = xsk_m.derive_child(5.try_into().unwrap()).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
ExtendedSpendingKey::from_path(&seed, &[5.try_into().unwrap()]).unwrap(),
|
||||||
|
xsk_5h
|
||||||
|
);
|
||||||
|
|
||||||
|
let xsk_5h_7 = xsk_5h.derive_child(7.try_into().unwrap()).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
ExtendedSpendingKey::from_path(&seed, &[5.try_into().unwrap(), 7.try_into().unwrap()])
|
||||||
|
.unwrap(),
|
||||||
|
xsk_5h_7
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue