diff --git a/Cargo.toml b/Cargo.toml index 16375d0..48cc9ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,16 @@ homepage = "https://github.com/zcash-hackworks/zip32" repository = "https://github.com/zcash-hackworks/zip32" [dependencies] +aes = "0.2" +byteorder = "1" +fpe = "0.1" +lazy_static = "1.0" +pairing = "0.14.2" + +[dependencies.blake2-rfc] +git = "https://github.com/gtank/blake2-rfc" +rev = "7a5b5fc99ae483a0043db7547fb79a6fa44b88a9" + +[dependencies.sapling-crypto] +git = "https://github.com/zcash-hackworks/sapling-crypto" +rev = "21084bde2019c04bd34208e63c3560fe2c02fb0e" diff --git a/src/lib.rs b/src/lib.rs index 31e1bb2..70173ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,1231 @@ -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); +extern crate aes; +extern crate blake2_rfc; +extern crate byteorder; +extern crate fpe; +#[macro_use] +extern crate lazy_static; +extern crate pairing; +extern crate sapling_crypto; + +use aes::Aes256; +use blake2_rfc::blake2b::{Blake2b, Blake2bResult}; +use byteorder::{ByteOrder, LittleEndian, ReadBytesExt, WriteBytesExt}; +use fpe::ff1::{BinaryNumeralString, FF1}; +use pairing::{bls12_381::Bls12, Field, PrimeField, PrimeFieldRepr}; +use sapling_crypto::{ + jubjub::{ + edwards, FixedGenerators, JubjubBls12, JubjubEngine, JubjubParams, ToUniform, Unknown, + }, + primitives::{Diversifier, PaymentAddress, ViewingKey}, +}; +use std::io::{self, Read, Write}; + +lazy_static! { + static ref JUBJUB: JubjubBls12 = { JubjubBls12::new() }; +} + +pub const PRF_EXPAND_PERSONALIZATION: &'static [u8; 16] = b"Zcash_ExpandSeed"; +pub const ZIP32_SAPLING_MASTER_PERSONALIZATION: &'static [u8; 16] = b"ZcashIP32Sapling"; +pub const ZIP32_SAPLING_FVFP_PERSONALIZATION: &'static [u8; 16] = b"ZcashSaplingFVFP"; + +// Sapling key components + +/// PRF^expand(sk, t) := BLAKE2b-512("Zcash_ExpandSeed", sk || t) +fn prf_expand(sk: &[u8], t: &[u8]) -> Blake2bResult { + prf_expand_vec(sk, &vec![t]) +} + +fn prf_expand_vec(sk: &[u8], ts: &[&[u8]]) -> Blake2bResult { + let mut h = Blake2b::with_params(64, &[], &[], PRF_EXPAND_PERSONALIZATION); + h.update(sk); + for t in ts { + h.update(t); + } + h.finalize() +} + +/// An outgoing viewing key +#[derive(Clone, Copy, PartialEq)] +struct OutgoingViewingKey([u8; 32]); + +impl OutgoingViewingKey { + fn derive_child(&self, i_l: &[u8]) -> Self { + let mut ovk = [0u8; 32]; + ovk.copy_from_slice(&prf_expand_vec(i_l, &[&[0x15], &self.0]).as_bytes()[..32]); + OutgoingViewingKey(ovk) + } +} + +/// A Sapling expanded spending key +#[derive(Clone)] +pub struct ExpandedSpendingKey { + ask: E::Fs, + nsk: E::Fs, + ovk: OutgoingViewingKey, +} + +/// A Sapling full viewing key +pub struct FullViewingKey { + vk: ViewingKey, + ovk: OutgoingViewingKey, +} + +impl ExpandedSpendingKey { + fn from_spending_key(sk: &[u8]) -> Self { + let ask = E::Fs::to_uniform(prf_expand(sk, &[0x00]).as_bytes()); + let nsk = E::Fs::to_uniform(prf_expand(sk, &[0x01]).as_bytes()); + let mut ovk = OutgoingViewingKey([0u8; 32]); + ovk.0 + .copy_from_slice(&prf_expand(sk, &[0x02]).as_bytes()[..32]); + ExpandedSpendingKey { ask, nsk, ovk } + } + + fn derive_child(&self, i_l: &[u8]) -> Self { + let mut ask = E::Fs::to_uniform(prf_expand(i_l, &[0x13]).as_bytes()); + let mut nsk = E::Fs::to_uniform(prf_expand(i_l, &[0x14]).as_bytes()); + ask.add_assign(&self.ask); + nsk.add_assign(&self.nsk); + let ovk = self.ovk.derive_child(i_l); + ExpandedSpendingKey { ask, nsk, ovk } + } + + pub fn read(mut reader: R) -> io::Result { + let mut ask_repr = ::Repr::default(); + ask_repr.read_le(&mut reader)?; + let ask = + E::Fs::from_repr(ask_repr).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + + let mut nsk_repr = ::Repr::default(); + nsk_repr.read_le(&mut reader)?; + let nsk = + E::Fs::from_repr(nsk_repr).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + + let mut ovk = [0; 32]; + reader.read_exact(&mut ovk)?; + + Ok(ExpandedSpendingKey { + ask, + nsk, + ovk: OutgoingViewingKey(ovk), + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + self.ask.into_repr().write_le(&mut writer)?; + self.nsk.into_repr().write_le(&mut writer)?; + writer.write_all(&self.ovk.0)?; + + Ok(()) + } + + fn to_bytes(&self) -> [u8; 96] { + let mut result = [0u8; 96]; + self.write(&mut result[..]) + .expect("should be able to serialize an ExpandedSpendingKey"); + result + } +} + +impl FullViewingKey { + fn from_expanded_spending_key(expsk: &ExpandedSpendingKey, params: &E::Params) -> Self { + FullViewingKey { + vk: ViewingKey { + ak: params + .generator(FixedGenerators::SpendingKeyGenerator) + .mul(expsk.ask, params), + nk: params + .generator(FixedGenerators::ProofGenerationKey) + .mul(expsk.nsk, params), + }, + ovk: expsk.ovk, + } + } + + fn derive_child(&self, i_l: &[u8], params: &E::Params) -> Self { + let i_ask = E::Fs::to_uniform(prf_expand(i_l, &[0x13]).as_bytes()); + let i_nsk = E::Fs::to_uniform(prf_expand(i_l, &[0x14]).as_bytes()); + let ak = params + .generator(FixedGenerators::SpendingKeyGenerator) + .mul(i_ask, params) + .add(&self.vk.ak, params); + let nk = params + .generator(FixedGenerators::ProofGenerationKey) + .mul(i_nsk, params) + .add(&self.vk.nk, params); + + FullViewingKey { + vk: ViewingKey { ak, nk }, + ovk: self.ovk.derive_child(i_l), + } + } + + pub fn read(mut reader: R, params: &E::Params) -> io::Result { + let ak = edwards::Point::::read(&mut reader, params)?; + let ak = match ak.as_prime_order(params) { + Some(p) => p, + None => { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "ak not of prime order", + )) + } + }; + + let nk = edwards::Point::::read(&mut reader, params)?; + let nk = match nk.as_prime_order(params) { + Some(p) => p, + None => { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "nk not of prime order", + )) + } + }; + + let mut ovk = [0; 32]; + reader.read_exact(&mut ovk)?; + + Ok(FullViewingKey { + vk: ViewingKey { ak, nk }, + ovk: OutgoingViewingKey(ovk), + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + self.vk.ak.write(&mut writer)?; + self.vk.nk.write(&mut writer)?; + writer.write_all(&self.ovk.0)?; + + Ok(()) + } + + fn to_bytes(&self) -> [u8; 96] { + let mut result = [0u8; 96]; + self.write(&mut result[..]) + .expect("should be able to serialize a FullViewingKey"); + result + } + + fn fingerprint(&self) -> FVKFingerprint { + let mut h = Blake2b::with_params(32, &[], &[], ZIP32_SAPLING_FVFP_PERSONALIZATION); + h.update(&self.to_bytes()); + let mut fvfp = [0u8; 32]; + fvfp.copy_from_slice(h.finalize().as_bytes()); + FVKFingerprint(fvfp) + } +} + +// ZIP 32 structures + +/// A Sapling full viewing key fingerprint +struct FVKFingerprint([u8; 32]); + +/// A Sapling 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 child index for a derived key +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum ChildIndex { + NonHardened(u32), + Hardened(u32), // Hardened(n) == n + (1 << 31) == n' in path notation +} + +impl ChildIndex { + pub fn from_index(i: u32) -> Self { + match i { + n if n >= (1 << 31) => ChildIndex::Hardened(n - (1 << 31)), + n => ChildIndex::NonHardened(n), + } + } + + fn master() -> Self { + ChildIndex::from_index(0) + } + + fn to_index(&self) -> u32 { + match self { + &ChildIndex::Hardened(i) => i + (1 << 31), + &ChildIndex::NonHardened(i) => i, + } + } +} + +/// A chain code +#[derive(Clone, Copy, Debug, PartialEq)] +struct ChainCode([u8; 32]); + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct DiversifierIndex(pub [u8; 11]); + +impl DiversifierIndex { + fn new() -> Self { + DiversifierIndex([0; 11]) + } + + pub fn increment(&mut self) -> Result<(), ()> { + for k in 0..11 { + self.0[k] = self.0[k].wrapping_add(1); + if self.0[k] != 0 { + // No overflow + return Ok(()); + } + } + // Overflow + Err(()) + } +} + +/// A key used to derive diversifiers for a particular child key +#[derive(Clone, Copy, Debug, PartialEq)] +struct DiversifierKey([u8; 32]); + +impl DiversifierKey { + fn master(sk_m: &[u8]) -> Self { + let mut dk_m = [0u8; 32]; + dk_m.copy_from_slice(&prf_expand(sk_m, &[0x10]).as_bytes()[..32]); + DiversifierKey(dk_m) + } + + fn derive_child(&self, i_l: &[u8]) -> Self { + let mut dk = [0u8; 32]; + dk.copy_from_slice(&prf_expand_vec(i_l, &[&[0x16], &self.0]).as_bytes()[..32]); + DiversifierKey(dk) + } + + /// Returns the first index starting from j that generates a valid + /// diversifier, along with the corresponding diversifier. Returns + /// an error if the diversifier space is exhausted. + fn diversifier(&self, mut j: DiversifierIndex) -> Result<(DiversifierIndex, Diversifier), ()> { + let ff = FF1::::new(&self.0, 2).unwrap(); + loop { + // Generate d_j + let enc = ff + .encrypt(&[], &BinaryNumeralString::from_bytes_le(&j.0[..])) + .unwrap(); + let mut d_j = [0; 11]; + d_j.copy_from_slice(&enc.to_bytes_le()); + let d_j = Diversifier(d_j); + + // Return (j, d_j) if valid, else increment j and try again + match d_j.g_d::(&JUBJUB) { + Some(_) => return Ok((j, d_j)), + None => if j.increment().is_err() { + return Err(()); + }, + } + } + } +} + +/// A Sapling extended spending key +#[derive(Clone)] +pub struct ExtendedSpendingKey { + depth: u8, + parent_fvk_tag: FVKTag, + child_index: ChildIndex, + chain_code: ChainCode, + pub expsk: ExpandedSpendingKey, + dk: DiversifierKey, +} + +// A Sapling extended full viewing key +pub struct ExtendedFullViewingKey { + depth: u8, + parent_fvk_tag: FVKTag, + child_index: ChildIndex, + chain_code: ChainCode, + pub fvk: FullViewingKey, + dk: DiversifierKey, +} + +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.expsk.ask == rhs.expsk.ask + && self.expsk.nsk == rhs.expsk.nsk + && self.expsk.ovk == rhs.expsk.ovk + && self.dk == rhs.dk + } +} + +impl std::fmt::Debug for ExtendedSpendingKey { + fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + write!( + f, + "ExtendedSpendingKey(d = {}, tag_p = {:?}, i = {:?})", + self.depth, self.parent_fvk_tag, self.child_index + ) + } +} + +impl std::cmp::PartialEq for ExtendedFullViewingKey { + fn eq(&self, rhs: &ExtendedFullViewingKey) -> 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.fvk.vk.ak == rhs.fvk.vk.ak + && self.fvk.vk.nk == rhs.fvk.vk.nk + && self.fvk.ovk == rhs.fvk.ovk + && self.dk == rhs.dk + } +} + +impl std::fmt::Debug for ExtendedFullViewingKey { + fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + write!( + f, + "ExtendedFullViewingKey(d = {}, tag_p = {:?}, i = {:?})", + self.depth, self.parent_fvk_tag, self.child_index + ) + } +} + +impl ExtendedSpendingKey { + pub fn master(seed: &[u8]) -> Self { + let mut h = Blake2b::with_params(64, &[], &[], ZIP32_SAPLING_MASTER_PERSONALIZATION); + h.update(seed); + let i = h.finalize(); + + let sk_m = &i.as_bytes()[..32]; + let mut c_m = [0u8; 32]; + c_m.copy_from_slice(&i.as_bytes()[32..]); + + ExtendedSpendingKey { + depth: 0, + parent_fvk_tag: FVKTag::master(), + child_index: ChildIndex::master(), + chain_code: ChainCode(c_m), + expsk: ExpandedSpendingKey::from_spending_key(sk_m), + dk: DiversifierKey::master(sk_m), + } + } + + pub fn read(mut reader: R) -> io::Result { + let depth = reader.read_u8()?; + let mut tag = [0; 4]; + reader.read_exact(&mut tag)?; + let i = reader.read_u32::()?; + let mut c = [0; 32]; + reader.read_exact(&mut c)?; + let expsk = ExpandedSpendingKey::read(&mut reader)?; + let mut dk = [0; 32]; + reader.read_exact(&mut dk)?; + + Ok(ExtendedSpendingKey { + depth, + parent_fvk_tag: FVKTag(tag), + child_index: ChildIndex::from_index(i), + chain_code: ChainCode(c), + expsk, + dk: DiversifierKey(dk), + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_u8(self.depth)?; + writer.write_all(&self.parent_fvk_tag.0)?; + writer.write_u32::(self.child_index.to_index())?; + writer.write_all(&self.chain_code.0)?; + writer.write_all(&self.expsk.to_bytes())?; + writer.write_all(&self.dk.0)?; + + Ok(()) + } + + /// Returns the child key corresponding to the path derived from the master key + pub fn from_path(master: &ExtendedSpendingKey, path: &[ChildIndex]) -> Self { + let mut xsk = master.clone(); + for &i in path.iter() { + xsk = xsk.derive_child(i); + } + xsk + } + + pub fn derive_child(&self, i: ChildIndex) -> Self { + let fvk = FullViewingKey::from_expanded_spending_key(&self.expsk, &JUBJUB); + let tmp = match i { + ChildIndex::Hardened(i) => { + let mut le_i = [0; 4]; + LittleEndian::write_u32(&mut le_i, i + (1 << 31)); + prf_expand_vec( + &self.chain_code.0, + &[&[0x11], &self.expsk.to_bytes(), &self.dk.0, &le_i], + ) + } + ChildIndex::NonHardened(i) => { + let mut le_i = [0; 4]; + LittleEndian::write_u32(&mut le_i, i); + prf_expand_vec( + &self.chain_code.0, + &[&[0x12], &fvk.to_bytes(), &self.dk.0, &le_i], + ) + } + }; + let i_l = &tmp.as_bytes()[..32]; + let mut c_i = [0u8; 32]; + c_i.copy_from_slice(&tmp.as_bytes()[32..]); + + ExtendedSpendingKey { + depth: self.depth + 1, + parent_fvk_tag: fvk.fingerprint().tag(), + child_index: i, + chain_code: ChainCode(c_i), + expsk: self.expsk.derive_child(i_l), + dk: self.dk.derive_child(i_l), + } + } + + pub fn default_address(&self) -> Result<(DiversifierIndex, PaymentAddress), ()> { + ExtendedFullViewingKey::from(self).default_address() + } +} + +impl<'a> From<&'a ExtendedSpendingKey> for ExtendedFullViewingKey { + fn from(xsk: &ExtendedSpendingKey) -> Self { + ExtendedFullViewingKey { + depth: xsk.depth, + parent_fvk_tag: xsk.parent_fvk_tag, + child_index: xsk.child_index, + chain_code: xsk.chain_code, + fvk: FullViewingKey::from_expanded_spending_key(&xsk.expsk, &JUBJUB), + dk: xsk.dk, + } + } +} + +impl ExtendedFullViewingKey { + pub fn read(mut reader: R) -> io::Result { + let depth = reader.read_u8()?; + let mut tag = [0; 4]; + reader.read_exact(&mut tag)?; + let i = reader.read_u32::()?; + let mut c = [0; 32]; + reader.read_exact(&mut c)?; + let fvk = FullViewingKey::read(&mut reader, &*JUBJUB)?; + let mut dk = [0; 32]; + reader.read_exact(&mut dk)?; + + Ok(ExtendedFullViewingKey { + depth, + parent_fvk_tag: FVKTag(tag), + child_index: ChildIndex::from_index(i), + chain_code: ChainCode(c), + fvk, + dk: DiversifierKey(dk), + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_u8(self.depth)?; + writer.write_all(&self.parent_fvk_tag.0)?; + writer.write_u32::(self.child_index.to_index())?; + writer.write_all(&self.chain_code.0)?; + writer.write_all(&self.fvk.to_bytes())?; + writer.write_all(&self.dk.0)?; + + Ok(()) + } + + pub fn derive_child(&self, i: ChildIndex) -> Result { + let tmp = match i { + ChildIndex::Hardened(_) => return Err(()), + ChildIndex::NonHardened(i) => { + let mut le_i = [0; 4]; + LittleEndian::write_u32(&mut le_i, i); + prf_expand_vec( + &self.chain_code.0, + &[&[0x12], &self.fvk.to_bytes(), &self.dk.0, &le_i], + ) + } + }; + let i_l = &tmp.as_bytes()[..32]; + let mut c_i = [0u8; 32]; + c_i.copy_from_slice(&tmp.as_bytes()[32..]); + + Ok(ExtendedFullViewingKey { + depth: self.depth + 1, + parent_fvk_tag: self.fvk.fingerprint().tag(), + child_index: i, + chain_code: ChainCode(c_i), + fvk: self.fvk.derive_child(i_l, &JUBJUB), + dk: self.dk.derive_child(i_l), + }) + } + + pub fn address( + &self, + j: DiversifierIndex, + ) -> Result<(DiversifierIndex, PaymentAddress), ()> { + let (j, d_j) = match self.dk.diversifier(j) { + Ok(ret) => ret, + Err(()) => return Err(()), + }; + match self.fvk.vk.into_payment_address(d_j, &JUBJUB) { + Some(addr) => Ok((j, addr)), + None => Err(()), + } + } + + pub fn default_address(&self) -> Result<(DiversifierIndex, PaymentAddress), ()> { + self.address(DiversifierIndex::new()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn derive_nonhardened_child() { + let seed = [0; 32]; + let xsk_m = ExtendedSpendingKey::master(&seed); + let xfvk_m = ExtendedFullViewingKey::from(&xsk_m); + + let i_5 = ChildIndex::NonHardened(5); + let xsk_5 = xsk_m.derive_child(i_5); + let xfvk_5 = xfvk_m.derive_child(i_5); + + assert!(xfvk_5.is_ok()); + assert_eq!(ExtendedFullViewingKey::from(&xsk_5), xfvk_5.unwrap()); + } + + #[test] + fn derive_hardened_child() { + let seed = [0; 32]; + let xsk_m = ExtendedSpendingKey::master(&seed); + let xfvk_m = ExtendedFullViewingKey::from(&xsk_m); + + let i_5h = ChildIndex::Hardened(5); + let xsk_5h = xsk_m.derive_child(i_5h); + let xfvk_5h = xfvk_m.derive_child(i_5h); + + // Cannot derive a hardened child from an ExtendedFullViewingKey + assert!(xfvk_5h.is_err()); + let xfvk_5h = ExtendedFullViewingKey::from(&xsk_5h); + + let i_7 = ChildIndex::NonHardened(7); + let xsk_5h_7 = xsk_5h.derive_child(i_7); + let xfvk_5h_7 = xfvk_5h.derive_child(i_7); + + // But we *can* derive a non-hardened child from a hardened parent + assert!(xfvk_5h_7.is_ok()); + assert_eq!(ExtendedFullViewingKey::from(&xsk_5h_7), xfvk_5h_7.unwrap()); + } + + #[test] + fn path() { + let seed = [0; 32]; + let xsk_m = ExtendedSpendingKey::master(&seed); + + let xsk_5h = xsk_m.derive_child(ChildIndex::Hardened(5)); + assert_eq!( + ExtendedSpendingKey::from_path(&xsk_m, &[ChildIndex::Hardened(5)]), + xsk_5h + ); + + let xsk_5h_7 = xsk_5h.derive_child(ChildIndex::NonHardened(7)); + assert_eq!( + ExtendedSpendingKey::from_path( + &xsk_m, + &[ChildIndex::Hardened(5), ChildIndex::NonHardened(7)] + ), + xsk_5h_7 + ); + } + + #[test] + fn diversifier() { + let dk = DiversifierKey([0; 32]); + let j_0 = DiversifierIndex::new(); + let j_1 = DiversifierIndex([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + let j_2 = DiversifierIndex([2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + let j_3 = DiversifierIndex([3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + // Computed using this Rust implementation + let d_0 = [220, 231, 126, 188, 236, 10, 38, 175, 214, 153, 140]; + let d_3 = [60, 253, 170, 8, 171, 147, 220, 31, 3, 144, 34]; + + // j = 0 + let (j, d_j) = dk.diversifier(j_0).unwrap(); + assert_eq!(j, j_0); + assert_eq!(d_j.0, d_0); + + // j = 1 + let (j, d_j) = dk.diversifier(j_1).unwrap(); + assert_eq!(j, j_3); + assert_eq!(d_j.0, d_3); + + // j = 2 + let (j, d_j) = dk.diversifier(j_2).unwrap(); + assert_eq!(j, j_3); + assert_eq!(d_j.0, d_3); + + // j = 3 + let (j, d_j) = dk.diversifier(j_3).unwrap(); + assert_eq!(j, j_3); + assert_eq!(d_j.0, d_3); + } + + #[test] + fn default_address() { + let seed = [0; 32]; + let xsk_m = ExtendedSpendingKey::master(&seed); + let (j_m, addr_m) = xsk_m.default_address().unwrap(); + assert_eq!(j_m.0, [0; 11]); + assert_eq!( + addr_m.diversifier.0, + // Computed using this Rust implementation + [59, 246, 250, 31, 131, 191, 69, 99, 200, 167, 19] + ); + } + + #[test] + fn read_write() { + let seed = [0; 32]; + let xsk = ExtendedSpendingKey::master(&seed); + let fvk = ExtendedFullViewingKey::from(&xsk); + + let mut ser = vec![]; + xsk.write(&mut ser).unwrap(); + let xsk2 = ExtendedSpendingKey::read(&ser[..]).unwrap(); + assert_eq!(xsk2, xsk); + + let mut ser = vec![]; + fvk.write(&mut ser).unwrap(); + let fvk2 = ExtendedFullViewingKey::read(&ser[..]).unwrap(); + assert_eq!(fvk2, fvk); + } + + #[test] + fn test_vectors() { + struct TestVector { + ask: Option<[u8; 32]>, + nsk: Option<[u8; 32]>, + ovk: [u8; 32], + dk: [u8; 32], + c: [u8; 32], + ak: [u8; 32], + nk: [u8; 32], + ivk: [u8; 32], + xsk: Option<[u8; 169]>, + xfvk: [u8; 169], + fp: [u8; 32], + d0: Option<[u8; 11]>, + d1: Option<[u8; 11]>, + d2: Option<[u8; 11]>, + dmax: Option<[u8; 11]>, + }; + + // From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/sapling_zip32.py + let test_vectors = vec![ + TestVector { + ask: Some([ + 0xb6, 0xc0, 0x0c, 0x93, 0xd3, 0x60, 0x32, 0xb9, 0xa2, 0x68, 0xe9, 0x9e, 0x86, + 0xa8, 0x60, 0x77, 0x65, 0x60, 0xbf, 0x0e, 0x83, 0xc1, 0xa1, 0x0b, 0x51, 0xf6, + 0x07, 0xc9, 0x54, 0x74, 0x25, 0x06, + ]), + nsk: Some([ + 0x82, 0x04, 0xed, 0xe8, 0x3b, 0x2f, 0x1f, 0xbd, 0x84, 0xf9, 0xb4, 0x5d, 0x7f, + 0x99, 0x6e, 0x2e, 0xbd, 0x0a, 0x03, 0x0a, 0xd2, 0x43, 0xb4, 0x8e, 0xd3, 0x9f, + 0x74, 0x8a, 0x88, 0x21, 0xea, 0x06, + ]), + ovk: [ + 0x39, 0x58, 0x84, 0x89, 0x03, 0x23, 0xb9, 0xd4, 0x93, 0x3c, 0x02, 0x1d, 0xb8, + 0x9b, 0xcf, 0x76, 0x7d, 0xf2, 0x19, 0x77, 0xb2, 0xff, 0x06, 0x83, 0x84, 0x83, + 0x21, 0xa4, 0xdf, 0x4a, 0xfb, 0x21, + ], + dk: [ + 0x77, 0xc1, 0x7c, 0xb7, 0x5b, 0x77, 0x96, 0xaf, 0xb3, 0x9f, 0x0f, 0x3e, 0x91, + 0xc9, 0x24, 0x60, 0x7d, 0xa5, 0x6f, 0xa9, 0xa2, 0x0e, 0x28, 0x35, 0x09, 0xbc, + 0x8a, 0x3e, 0xf9, 0x96, 0xa1, 0x72, + ], + c: [ + 0xd0, 0x94, 0x7c, 0x4b, 0x03, 0xbf, 0x72, 0xa3, 0x7a, 0xb4, 0x4f, 0x72, 0x27, + 0x6d, 0x1c, 0xf3, 0xfd, 0xcd, 0x7e, 0xbf, 0x3e, 0x73, 0x34, 0x8b, 0x7e, 0x55, + 0x0d, 0x75, 0x20, 0x18, 0x66, 0x8e, + ], + ak: [ + 0x93, 0x44, 0x2e, 0x5f, 0xef, 0xfb, 0xff, 0x16, 0xe7, 0x21, 0x72, 0x02, 0xdc, + 0x73, 0x06, 0x72, 0x9f, 0xff, 0xfe, 0x85, 0xaf, 0x56, 0x83, 0xbc, 0xe2, 0x64, + 0x2e, 0x3e, 0xeb, 0x5d, 0x38, 0x71, + ], + nk: [ + 0xdc, 0xe8, 0xe7, 0xed, 0xec, 0xe0, 0x4b, 0x89, 0x50, 0x41, 0x7f, 0x85, 0xba, + 0x57, 0x69, 0x1b, 0x78, 0x3c, 0x45, 0xb1, 0xa2, 0x74, 0x22, 0xdb, 0x16, 0x93, + 0xdc, 0xeb, 0x67, 0xb1, 0x01, 0x06, + ], + ivk: [ + 0x48, 0x47, 0xa1, 0x30, 0xe7, 0x99, 0xd3, 0xdb, 0xea, 0x36, 0xa1, 0xc1, 0x64, + 0x67, 0xd6, 0x21, 0xfb, 0x2d, 0x80, 0xe3, 0x0b, 0x3b, 0x1d, 0x1a, 0x42, 0x68, + 0x93, 0x41, 0x5d, 0xad, 0x66, 0x01, + ], + xsk: Some([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x94, 0x7c, 0x4b, + 0x03, 0xbf, 0x72, 0xa3, 0x7a, 0xb4, 0x4f, 0x72, 0x27, 0x6d, 0x1c, 0xf3, 0xfd, + 0xcd, 0x7e, 0xbf, 0x3e, 0x73, 0x34, 0x8b, 0x7e, 0x55, 0x0d, 0x75, 0x20, 0x18, + 0x66, 0x8e, 0xb6, 0xc0, 0x0c, 0x93, 0xd3, 0x60, 0x32, 0xb9, 0xa2, 0x68, 0xe9, + 0x9e, 0x86, 0xa8, 0x60, 0x77, 0x65, 0x60, 0xbf, 0x0e, 0x83, 0xc1, 0xa1, 0x0b, + 0x51, 0xf6, 0x07, 0xc9, 0x54, 0x74, 0x25, 0x06, 0x82, 0x04, 0xed, 0xe8, 0x3b, + 0x2f, 0x1f, 0xbd, 0x84, 0xf9, 0xb4, 0x5d, 0x7f, 0x99, 0x6e, 0x2e, 0xbd, 0x0a, + 0x03, 0x0a, 0xd2, 0x43, 0xb4, 0x8e, 0xd3, 0x9f, 0x74, 0x8a, 0x88, 0x21, 0xea, + 0x06, 0x39, 0x58, 0x84, 0x89, 0x03, 0x23, 0xb9, 0xd4, 0x93, 0x3c, 0x02, 0x1d, + 0xb8, 0x9b, 0xcf, 0x76, 0x7d, 0xf2, 0x19, 0x77, 0xb2, 0xff, 0x06, 0x83, 0x84, + 0x83, 0x21, 0xa4, 0xdf, 0x4a, 0xfb, 0x21, 0x77, 0xc1, 0x7c, 0xb7, 0x5b, 0x77, + 0x96, 0xaf, 0xb3, 0x9f, 0x0f, 0x3e, 0x91, 0xc9, 0x24, 0x60, 0x7d, 0xa5, 0x6f, + 0xa9, 0xa2, 0x0e, 0x28, 0x35, 0x09, 0xbc, 0x8a, 0x3e, 0xf9, 0x96, 0xa1, 0x72, + ]), + xfvk: [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x94, 0x7c, 0x4b, + 0x03, 0xbf, 0x72, 0xa3, 0x7a, 0xb4, 0x4f, 0x72, 0x27, 0x6d, 0x1c, 0xf3, 0xfd, + 0xcd, 0x7e, 0xbf, 0x3e, 0x73, 0x34, 0x8b, 0x7e, 0x55, 0x0d, 0x75, 0x20, 0x18, + 0x66, 0x8e, 0x93, 0x44, 0x2e, 0x5f, 0xef, 0xfb, 0xff, 0x16, 0xe7, 0x21, 0x72, + 0x02, 0xdc, 0x73, 0x06, 0x72, 0x9f, 0xff, 0xfe, 0x85, 0xaf, 0x56, 0x83, 0xbc, + 0xe2, 0x64, 0x2e, 0x3e, 0xeb, 0x5d, 0x38, 0x71, 0xdc, 0xe8, 0xe7, 0xed, 0xec, + 0xe0, 0x4b, 0x89, 0x50, 0x41, 0x7f, 0x85, 0xba, 0x57, 0x69, 0x1b, 0x78, 0x3c, + 0x45, 0xb1, 0xa2, 0x74, 0x22, 0xdb, 0x16, 0x93, 0xdc, 0xeb, 0x67, 0xb1, 0x01, + 0x06, 0x39, 0x58, 0x84, 0x89, 0x03, 0x23, 0xb9, 0xd4, 0x93, 0x3c, 0x02, 0x1d, + 0xb8, 0x9b, 0xcf, 0x76, 0x7d, 0xf2, 0x19, 0x77, 0xb2, 0xff, 0x06, 0x83, 0x84, + 0x83, 0x21, 0xa4, 0xdf, 0x4a, 0xfb, 0x21, 0x77, 0xc1, 0x7c, 0xb7, 0x5b, 0x77, + 0x96, 0xaf, 0xb3, 0x9f, 0x0f, 0x3e, 0x91, 0xc9, 0x24, 0x60, 0x7d, 0xa5, 0x6f, + 0xa9, 0xa2, 0x0e, 0x28, 0x35, 0x09, 0xbc, 0x8a, 0x3e, 0xf9, 0x96, 0xa1, 0x72, + ], + fp: [ + 0x14, 0xc2, 0x71, 0x3a, 0xdc, 0xe9, 0x3a, 0x83, 0x0e, 0xa8, 0x3a, 0x05, 0x19, + 0x08, 0xb7, 0x44, 0x77, 0x83, 0xf5, 0xd1, 0x06, 0xc0, 0x98, 0x5e, 0x02, 0x55, + 0x0e, 0x42, 0x6f, 0x27, 0x59, 0x7c, + ], + d0: Some([ + 0xd8, 0x62, 0x1b, 0x98, 0x1c, 0xf3, 0x00, 0xe9, 0xd4, 0xcc, 0x89, + ]), + d1: Some([ + 0x48, 0xea, 0x17, 0xa1, 0x99, 0xc8, 0x4b, 0xd1, 0xba, 0xa5, 0xd4, + ]), + d2: None, + dmax: None, + }, + TestVector { + ask: Some([ + 0x28, 0x2b, 0xc1, 0x97, 0xa5, 0x16, 0x28, 0x7c, 0x8e, 0xa8, 0xf6, 0x8c, 0x42, + 0x4a, 0xba, 0xd3, 0x02, 0xb4, 0x5c, 0xdf, 0x95, 0x40, 0x79, 0x61, 0xd7, 0xb8, + 0xb4, 0x55, 0x26, 0x7a, 0x35, 0x0c, + ]), + nsk: Some([ + 0xe7, 0xa3, 0x29, 0x88, 0xfd, 0xca, 0x1e, 0xfc, 0xd6, 0xd1, 0xc4, 0xc5, 0x62, + 0xe6, 0x29, 0xc2, 0xe9, 0x6b, 0x2c, 0x3f, 0x7e, 0xda, 0x04, 0xac, 0x4e, 0xfd, + 0x18, 0x10, 0xff, 0x6b, 0xba, 0x01, + ]), + ovk: [ + 0x5f, 0x13, 0x81, 0xfc, 0x88, 0x86, 0xda, 0x6a, 0x02, 0xdf, 0xfe, 0xef, 0xcf, + 0x50, 0x3c, 0x40, 0xfa, 0x8f, 0x5a, 0x36, 0xf7, 0xa7, 0x14, 0x2f, 0xd8, 0x1b, + 0x55, 0x18, 0xc5, 0xa4, 0x74, 0x74, + ], + dk: [ + 0xe0, 0x4d, 0xe8, 0x32, 0xa2, 0xd7, 0x91, 0xec, 0x12, 0x9a, 0xb9, 0x00, 0x2b, + 0x91, 0xc9, 0xe9, 0xcd, 0xee, 0xd7, 0x92, 0x41, 0xa7, 0xc4, 0x96, 0x0e, 0x51, + 0x78, 0xd8, 0x70, 0xc1, 0xb4, 0xdc, + ], + c: [ + 0x01, 0x47, 0x11, 0x0c, 0x69, 0x1a, 0x03, 0xb9, 0xd9, 0xf0, 0xba, 0x90, 0x05, + 0xc5, 0xe7, 0x90, 0xa5, 0x95, 0xb7, 0xf0, 0x4e, 0x33, 0x29, 0xd2, 0xfa, 0x43, + 0x8a, 0x67, 0x05, 0xda, 0xbc, 0xe6, + ], + ak: [ + 0xdc, 0x14, 0xb5, 0x14, 0xd3, 0xa9, 0x25, 0x94, 0xc2, 0x19, 0x25, 0xaf, 0x2f, + 0x77, 0x65, 0xa5, 0x47, 0xb3, 0x0e, 0x73, 0xfa, 0x7b, 0x70, 0x0e, 0xa1, 0xbf, + 0xf2, 0xe5, 0xef, 0xaa, 0xa8, 0x8b, + ], + nk: [ + 0x61, 0x52, 0xeb, 0x7f, 0xdb, 0x25, 0x27, 0x79, 0xdd, 0xcb, 0x95, 0xd2, 0x17, + 0xea, 0x4b, 0x6f, 0xd3, 0x40, 0x36, 0xe9, 0xad, 0xad, 0xb3, 0xb5, 0xc9, 0xcb, + 0xec, 0xeb, 0x41, 0xba, 0x45, 0x2a, + ], + ivk: [ + 0x15, 0x5a, 0x8e, 0xe2, 0x05, 0xd3, 0x87, 0x2d, 0x12, 0xf8, 0xa3, 0xe6, 0x39, + 0x91, 0x46, 0x33, 0xc2, 0x3c, 0xde, 0x1f, 0x30, 0xed, 0x50, 0x51, 0xe5, 0x21, + 0x30, 0xb1, 0xd0, 0x10, 0x4c, 0x06, + ], + xsk: Some([ + 0x01, 0x14, 0xc2, 0x71, 0x3a, 0x01, 0x00, 0x00, 0x00, 0x01, 0x47, 0x11, 0x0c, + 0x69, 0x1a, 0x03, 0xb9, 0xd9, 0xf0, 0xba, 0x90, 0x05, 0xc5, 0xe7, 0x90, 0xa5, + 0x95, 0xb7, 0xf0, 0x4e, 0x33, 0x29, 0xd2, 0xfa, 0x43, 0x8a, 0x67, 0x05, 0xda, + 0xbc, 0xe6, 0x28, 0x2b, 0xc1, 0x97, 0xa5, 0x16, 0x28, 0x7c, 0x8e, 0xa8, 0xf6, + 0x8c, 0x42, 0x4a, 0xba, 0xd3, 0x02, 0xb4, 0x5c, 0xdf, 0x95, 0x40, 0x79, 0x61, + 0xd7, 0xb8, 0xb4, 0x55, 0x26, 0x7a, 0x35, 0x0c, 0xe7, 0xa3, 0x29, 0x88, 0xfd, + 0xca, 0x1e, 0xfc, 0xd6, 0xd1, 0xc4, 0xc5, 0x62, 0xe6, 0x29, 0xc2, 0xe9, 0x6b, + 0x2c, 0x3f, 0x7e, 0xda, 0x04, 0xac, 0x4e, 0xfd, 0x18, 0x10, 0xff, 0x6b, 0xba, + 0x01, 0x5f, 0x13, 0x81, 0xfc, 0x88, 0x86, 0xda, 0x6a, 0x02, 0xdf, 0xfe, 0xef, + 0xcf, 0x50, 0x3c, 0x40, 0xfa, 0x8f, 0x5a, 0x36, 0xf7, 0xa7, 0x14, 0x2f, 0xd8, + 0x1b, 0x55, 0x18, 0xc5, 0xa4, 0x74, 0x74, 0xe0, 0x4d, 0xe8, 0x32, 0xa2, 0xd7, + 0x91, 0xec, 0x12, 0x9a, 0xb9, 0x00, 0x2b, 0x91, 0xc9, 0xe9, 0xcd, 0xee, 0xd7, + 0x92, 0x41, 0xa7, 0xc4, 0x96, 0x0e, 0x51, 0x78, 0xd8, 0x70, 0xc1, 0xb4, 0xdc, + ]), + xfvk: [ + 0x01, 0x14, 0xc2, 0x71, 0x3a, 0x01, 0x00, 0x00, 0x00, 0x01, 0x47, 0x11, 0x0c, + 0x69, 0x1a, 0x03, 0xb9, 0xd9, 0xf0, 0xba, 0x90, 0x05, 0xc5, 0xe7, 0x90, 0xa5, + 0x95, 0xb7, 0xf0, 0x4e, 0x33, 0x29, 0xd2, 0xfa, 0x43, 0x8a, 0x67, 0x05, 0xda, + 0xbc, 0xe6, 0xdc, 0x14, 0xb5, 0x14, 0xd3, 0xa9, 0x25, 0x94, 0xc2, 0x19, 0x25, + 0xaf, 0x2f, 0x77, 0x65, 0xa5, 0x47, 0xb3, 0x0e, 0x73, 0xfa, 0x7b, 0x70, 0x0e, + 0xa1, 0xbf, 0xf2, 0xe5, 0xef, 0xaa, 0xa8, 0x8b, 0x61, 0x52, 0xeb, 0x7f, 0xdb, + 0x25, 0x27, 0x79, 0xdd, 0xcb, 0x95, 0xd2, 0x17, 0xea, 0x4b, 0x6f, 0xd3, 0x40, + 0x36, 0xe9, 0xad, 0xad, 0xb3, 0xb5, 0xc9, 0xcb, 0xec, 0xeb, 0x41, 0xba, 0x45, + 0x2a, 0x5f, 0x13, 0x81, 0xfc, 0x88, 0x86, 0xda, 0x6a, 0x02, 0xdf, 0xfe, 0xef, + 0xcf, 0x50, 0x3c, 0x40, 0xfa, 0x8f, 0x5a, 0x36, 0xf7, 0xa7, 0x14, 0x2f, 0xd8, + 0x1b, 0x55, 0x18, 0xc5, 0xa4, 0x74, 0x74, 0xe0, 0x4d, 0xe8, 0x32, 0xa2, 0xd7, + 0x91, 0xec, 0x12, 0x9a, 0xb9, 0x00, 0x2b, 0x91, 0xc9, 0xe9, 0xcd, 0xee, 0xd7, + 0x92, 0x41, 0xa7, 0xc4, 0x96, 0x0e, 0x51, 0x78, 0xd8, 0x70, 0xc1, 0xb4, 0xdc, + ], + fp: [ + 0xdb, 0x99, 0x9e, 0x07, 0x1d, 0xcb, 0x58, 0xdd, 0x93, 0x02, 0x9a, 0xe6, 0x97, + 0x05, 0x3e, 0x90, 0xed, 0xb3, 0x59, 0xd1, 0xa1, 0xb7, 0xa1, 0x25, 0x16, 0x7e, + 0xfb, 0xe9, 0x28, 0x06, 0x84, 0x23, + ], + d0: Some([ + 0x8b, 0x41, 0x38, 0x32, 0x0d, 0xfa, 0xfd, 0x7b, 0x39, 0x97, 0x81, + ]), + d1: None, + d2: Some([ + 0x57, 0x49, 0xa1, 0x33, 0x52, 0xbc, 0x22, 0x3e, 0x30, 0x80, 0x78, + ]), + dmax: Some([ + 0x63, 0x89, 0x57, 0x4c, 0xde, 0x0f, 0xbb, 0xc6, 0x36, 0x81, 0x31, + ]), + }, + TestVector { + ask: Some([ + 0x8b, 0xe8, 0x11, 0x3c, 0xee, 0x34, 0x13, 0xa7, 0x1f, 0x82, 0xc4, 0x1f, 0xc8, + 0xda, 0x51, 0x7b, 0xe1, 0x34, 0x04, 0x98, 0x32, 0xe6, 0x82, 0x5c, 0x92, 0xda, + 0x6b, 0x84, 0xfe, 0xe4, 0xc6, 0x0d, + ]), + nsk: Some([ + 0x37, 0x78, 0x05, 0x9d, 0xc5, 0x69, 0xe7, 0xd0, 0xd3, 0x23, 0x91, 0x57, 0x3f, + 0x95, 0x1b, 0xbd, 0xe9, 0x2f, 0xc6, 0xb9, 0xcf, 0x61, 0x47, 0x73, 0x66, 0x1c, + 0x5c, 0x27, 0x3a, 0xa6, 0x99, 0x0c, + ]), + ovk: [ + 0xcf, 0x81, 0x18, 0x2e, 0x96, 0x22, 0x3c, 0x02, 0x8c, 0xe3, 0xd6, 0xeb, 0x47, + 0x94, 0xd3, 0x11, 0x3b, 0x95, 0x06, 0x9d, 0x14, 0xc5, 0x75, 0x88, 0xe1, 0x93, + 0xb6, 0x5e, 0xfc, 0x28, 0x13, 0xbc, + ], + dk: [ + 0xa3, 0xed, 0xa1, 0x9f, 0x9e, 0xff, 0x46, 0xca, 0x12, 0xdf, 0xa1, 0xbf, 0x10, + 0x37, 0x1b, 0x48, 0xd1, 0xb4, 0xa4, 0x0c, 0x4d, 0x05, 0xa0, 0xd8, 0xdc, 0xe0, + 0xe7, 0xdc, 0x62, 0xb0, 0x7b, 0x37, + ], + c: [ + 0x97, 0xce, 0x15, 0xf4, 0xed, 0x1b, 0x97, 0x39, 0xb0, 0x26, 0x2a, 0x46, 0x3b, + 0xcb, 0x3d, 0xc9, 0xb3, 0xbd, 0x23, 0x23, 0xa9, 0xba, 0xa4, 0x41, 0xca, 0x42, + 0x77, 0x73, 0x83, 0xa8, 0xd4, 0x35, + ], + ak: [ + 0xa6, 0xc5, 0x92, 0x5a, 0x0f, 0x85, 0xfa, 0x4f, 0x1e, 0x40, 0x5e, 0x3a, 0x49, + 0x70, 0xd0, 0xc4, 0xa4, 0xb4, 0x81, 0x44, 0x38, 0xf4, 0xe9, 0xd4, 0x52, 0x0e, + 0x20, 0xf7, 0xfd, 0xcf, 0x38, 0x41, + ], + nk: [ + 0x30, 0x4e, 0x30, 0x59, 0x16, 0x21, 0x6b, 0xeb, 0x7b, 0x65, 0x4d, 0x8a, 0xae, + 0x50, 0xec, 0xd1, 0x88, 0xfc, 0xb3, 0x84, 0xbc, 0x36, 0xc0, 0x0c, 0x66, 0x4f, + 0x30, 0x77, 0x25, 0xe2, 0xee, 0x11, + ], + ivk: [ + 0xa2, 0xa1, 0x3c, 0x1e, 0x38, 0xb4, 0x59, 0x84, 0x44, 0x58, 0x03, 0xe4, 0x30, + 0xa6, 0x83, 0xc9, 0x0b, 0xb2, 0xe1, 0x4d, 0x4c, 0x86, 0x92, 0xff, 0x25, 0x3a, + 0x64, 0x84, 0xdd, 0x9b, 0xb5, 0x04, + ], + xsk: Some([ + 0x02, 0xdb, 0x99, 0x9e, 0x07, 0x02, 0x00, 0x00, 0x80, 0x97, 0xce, 0x15, 0xf4, + 0xed, 0x1b, 0x97, 0x39, 0xb0, 0x26, 0x2a, 0x46, 0x3b, 0xcb, 0x3d, 0xc9, 0xb3, + 0xbd, 0x23, 0x23, 0xa9, 0xba, 0xa4, 0x41, 0xca, 0x42, 0x77, 0x73, 0x83, 0xa8, + 0xd4, 0x35, 0x8b, 0xe8, 0x11, 0x3c, 0xee, 0x34, 0x13, 0xa7, 0x1f, 0x82, 0xc4, + 0x1f, 0xc8, 0xda, 0x51, 0x7b, 0xe1, 0x34, 0x04, 0x98, 0x32, 0xe6, 0x82, 0x5c, + 0x92, 0xda, 0x6b, 0x84, 0xfe, 0xe4, 0xc6, 0x0d, 0x37, 0x78, 0x05, 0x9d, 0xc5, + 0x69, 0xe7, 0xd0, 0xd3, 0x23, 0x91, 0x57, 0x3f, 0x95, 0x1b, 0xbd, 0xe9, 0x2f, + 0xc6, 0xb9, 0xcf, 0x61, 0x47, 0x73, 0x66, 0x1c, 0x5c, 0x27, 0x3a, 0xa6, 0x99, + 0x0c, 0xcf, 0x81, 0x18, 0x2e, 0x96, 0x22, 0x3c, 0x02, 0x8c, 0xe3, 0xd6, 0xeb, + 0x47, 0x94, 0xd3, 0x11, 0x3b, 0x95, 0x06, 0x9d, 0x14, 0xc5, 0x75, 0x88, 0xe1, + 0x93, 0xb6, 0x5e, 0xfc, 0x28, 0x13, 0xbc, 0xa3, 0xed, 0xa1, 0x9f, 0x9e, 0xff, + 0x46, 0xca, 0x12, 0xdf, 0xa1, 0xbf, 0x10, 0x37, 0x1b, 0x48, 0xd1, 0xb4, 0xa4, + 0x0c, 0x4d, 0x05, 0xa0, 0xd8, 0xdc, 0xe0, 0xe7, 0xdc, 0x62, 0xb0, 0x7b, 0x37, + ]), + xfvk: [ + 0x02, 0xdb, 0x99, 0x9e, 0x07, 0x02, 0x00, 0x00, 0x80, 0x97, 0xce, 0x15, 0xf4, + 0xed, 0x1b, 0x97, 0x39, 0xb0, 0x26, 0x2a, 0x46, 0x3b, 0xcb, 0x3d, 0xc9, 0xb3, + 0xbd, 0x23, 0x23, 0xa9, 0xba, 0xa4, 0x41, 0xca, 0x42, 0x77, 0x73, 0x83, 0xa8, + 0xd4, 0x35, 0xa6, 0xc5, 0x92, 0x5a, 0x0f, 0x85, 0xfa, 0x4f, 0x1e, 0x40, 0x5e, + 0x3a, 0x49, 0x70, 0xd0, 0xc4, 0xa4, 0xb4, 0x81, 0x44, 0x38, 0xf4, 0xe9, 0xd4, + 0x52, 0x0e, 0x20, 0xf7, 0xfd, 0xcf, 0x38, 0x41, 0x30, 0x4e, 0x30, 0x59, 0x16, + 0x21, 0x6b, 0xeb, 0x7b, 0x65, 0x4d, 0x8a, 0xae, 0x50, 0xec, 0xd1, 0x88, 0xfc, + 0xb3, 0x84, 0xbc, 0x36, 0xc0, 0x0c, 0x66, 0x4f, 0x30, 0x77, 0x25, 0xe2, 0xee, + 0x11, 0xcf, 0x81, 0x18, 0x2e, 0x96, 0x22, 0x3c, 0x02, 0x8c, 0xe3, 0xd6, 0xeb, + 0x47, 0x94, 0xd3, 0x11, 0x3b, 0x95, 0x06, 0x9d, 0x14, 0xc5, 0x75, 0x88, 0xe1, + 0x93, 0xb6, 0x5e, 0xfc, 0x28, 0x13, 0xbc, 0xa3, 0xed, 0xa1, 0x9f, 0x9e, 0xff, + 0x46, 0xca, 0x12, 0xdf, 0xa1, 0xbf, 0x10, 0x37, 0x1b, 0x48, 0xd1, 0xb4, 0xa4, + 0x0c, 0x4d, 0x05, 0xa0, 0xd8, 0xdc, 0xe0, 0xe7, 0xdc, 0x62, 0xb0, 0x7b, 0x37, + ], + fp: [ + 0x48, 0xc1, 0x83, 0x75, 0x7b, 0x5d, 0xa6, 0x61, 0x2a, 0x81, 0xb3, 0x0e, 0x40, + 0xb4, 0xac, 0xaa, 0x2d, 0x9e, 0x73, 0x95, 0x12, 0xe1, 0xd2, 0xd0, 0x01, 0x0e, + 0x92, 0xa7, 0xf7, 0xf2, 0xfc, 0xdf, + ], + d0: Some([ + 0xe8, 0xd0, 0x37, 0x93, 0xcd, 0xd2, 0xba, 0xcc, 0x9c, 0x70, 0x41, + ]), + d1: Some([ + 0x02, 0x0a, 0x7a, 0x6b, 0x0b, 0xf8, 0x4d, 0x3e, 0x89, 0x9f, 0x68, + ]), + d2: None, + dmax: None, + }, + TestVector { + ask: None, + nsk: None, + ovk: [ + 0xcf, 0x81, 0x18, 0x2e, 0x96, 0x22, 0x3c, 0x02, 0x8c, 0xe3, 0xd6, 0xeb, 0x47, + 0x94, 0xd3, 0x11, 0x3b, 0x95, 0x06, 0x9d, 0x14, 0xc5, 0x75, 0x88, 0xe1, 0x93, + 0xb6, 0x5e, 0xfc, 0x28, 0x13, 0xbc, + ], + dk: [ + 0xa3, 0xed, 0xa1, 0x9f, 0x9e, 0xff, 0x46, 0xca, 0x12, 0xdf, 0xa1, 0xbf, 0x10, + 0x37, 0x1b, 0x48, 0xd1, 0xb4, 0xa4, 0x0c, 0x4d, 0x05, 0xa0, 0xd8, 0xdc, 0xe0, + 0xe7, 0xdc, 0x62, 0xb0, 0x7b, 0x37, + ], + c: [ + 0x97, 0xce, 0x15, 0xf4, 0xed, 0x1b, 0x97, 0x39, 0xb0, 0x26, 0x2a, 0x46, 0x3b, + 0xcb, 0x3d, 0xc9, 0xb3, 0xbd, 0x23, 0x23, 0xa9, 0xba, 0xa4, 0x41, 0xca, 0x42, + 0x77, 0x73, 0x83, 0xa8, 0xd4, 0x35, + ], + ak: [ + 0xa6, 0xc5, 0x92, 0x5a, 0x0f, 0x85, 0xfa, 0x4f, 0x1e, 0x40, 0x5e, 0x3a, 0x49, + 0x70, 0xd0, 0xc4, 0xa4, 0xb4, 0x81, 0x44, 0x38, 0xf4, 0xe9, 0xd4, 0x52, 0x0e, + 0x20, 0xf7, 0xfd, 0xcf, 0x38, 0x41, + ], + nk: [ + 0x30, 0x4e, 0x30, 0x59, 0x16, 0x21, 0x6b, 0xeb, 0x7b, 0x65, 0x4d, 0x8a, 0xae, + 0x50, 0xec, 0xd1, 0x88, 0xfc, 0xb3, 0x84, 0xbc, 0x36, 0xc0, 0x0c, 0x66, 0x4f, + 0x30, 0x77, 0x25, 0xe2, 0xee, 0x11, + ], + ivk: [ + 0xa2, 0xa1, 0x3c, 0x1e, 0x38, 0xb4, 0x59, 0x84, 0x44, 0x58, 0x03, 0xe4, 0x30, + 0xa6, 0x83, 0xc9, 0x0b, 0xb2, 0xe1, 0x4d, 0x4c, 0x86, 0x92, 0xff, 0x25, 0x3a, + 0x64, 0x84, 0xdd, 0x9b, 0xb5, 0x04, + ], + xsk: None, + xfvk: [ + 0x02, 0xdb, 0x99, 0x9e, 0x07, 0x02, 0x00, 0x00, 0x80, 0x97, 0xce, 0x15, 0xf4, + 0xed, 0x1b, 0x97, 0x39, 0xb0, 0x26, 0x2a, 0x46, 0x3b, 0xcb, 0x3d, 0xc9, 0xb3, + 0xbd, 0x23, 0x23, 0xa9, 0xba, 0xa4, 0x41, 0xca, 0x42, 0x77, 0x73, 0x83, 0xa8, + 0xd4, 0x35, 0xa6, 0xc5, 0x92, 0x5a, 0x0f, 0x85, 0xfa, 0x4f, 0x1e, 0x40, 0x5e, + 0x3a, 0x49, 0x70, 0xd0, 0xc4, 0xa4, 0xb4, 0x81, 0x44, 0x38, 0xf4, 0xe9, 0xd4, + 0x52, 0x0e, 0x20, 0xf7, 0xfd, 0xcf, 0x38, 0x41, 0x30, 0x4e, 0x30, 0x59, 0x16, + 0x21, 0x6b, 0xeb, 0x7b, 0x65, 0x4d, 0x8a, 0xae, 0x50, 0xec, 0xd1, 0x88, 0xfc, + 0xb3, 0x84, 0xbc, 0x36, 0xc0, 0x0c, 0x66, 0x4f, 0x30, 0x77, 0x25, 0xe2, 0xee, + 0x11, 0xcf, 0x81, 0x18, 0x2e, 0x96, 0x22, 0x3c, 0x02, 0x8c, 0xe3, 0xd6, 0xeb, + 0x47, 0x94, 0xd3, 0x11, 0x3b, 0x95, 0x06, 0x9d, 0x14, 0xc5, 0x75, 0x88, 0xe1, + 0x93, 0xb6, 0x5e, 0xfc, 0x28, 0x13, 0xbc, 0xa3, 0xed, 0xa1, 0x9f, 0x9e, 0xff, + 0x46, 0xca, 0x12, 0xdf, 0xa1, 0xbf, 0x10, 0x37, 0x1b, 0x48, 0xd1, 0xb4, 0xa4, + 0x0c, 0x4d, 0x05, 0xa0, 0xd8, 0xdc, 0xe0, 0xe7, 0xdc, 0x62, 0xb0, 0x7b, 0x37, + ], + fp: [ + 0x48, 0xc1, 0x83, 0x75, 0x7b, 0x5d, 0xa6, 0x61, 0x2a, 0x81, 0xb3, 0x0e, 0x40, + 0xb4, 0xac, 0xaa, 0x2d, 0x9e, 0x73, 0x95, 0x12, 0xe1, 0xd2, 0xd0, 0x01, 0x0e, + 0x92, 0xa7, 0xf7, 0xf2, 0xfc, 0xdf, + ], + d0: Some([ + 0xe8, 0xd0, 0x37, 0x93, 0xcd, 0xd2, 0xba, 0xcc, 0x9c, 0x70, 0x41, + ]), + d1: Some([ + 0x02, 0x0a, 0x7a, 0x6b, 0x0b, 0xf8, 0x4d, 0x3e, 0x89, 0x9f, 0x68, + ]), + d2: None, + dmax: None, + }, + TestVector { + ask: None, + nsk: None, + ovk: [ + 0x69, 0xb9, 0xe0, 0xfa, 0x1c, 0x4b, 0x3d, 0xeb, 0x91, 0xd5, 0x3b, 0xee, 0xe8, + 0x71, 0x15, 0x61, 0x21, 0x47, 0x4b, 0x8b, 0x62, 0xef, 0x24, 0x13, 0x44, 0x78, + 0xdc, 0x34, 0x99, 0x69, 0x1a, 0xf6, + ], + dk: [ + 0xbe, 0xcb, 0x50, 0xc3, 0x63, 0xbb, 0x2e, 0xd9, 0xda, 0x5c, 0x30, 0x43, 0xce, + 0xb0, 0xf1, 0xa0, 0x52, 0x7b, 0xf8, 0x36, 0xb2, 0x9a, 0x35, 0xf7, 0xc0, 0xc9, + 0xf2, 0x61, 0x12, 0x3b, 0xe5, 0x6e, + ], + c: [ + 0x8d, 0x93, 0x7b, 0xcf, 0x81, 0xba, 0x43, 0x0d, 0x5b, 0x49, 0xaf, 0xc0, 0xa4, + 0x03, 0x36, 0x7b, 0x1f, 0xd9, 0x98, 0x79, 0xec, 0xba, 0x41, 0xbe, 0x05, 0x1c, + 0x5a, 0x4a, 0xa7, 0xd6, 0xe7, 0xe8, + ], + ak: [ + 0xb1, 0x85, 0xc5, 0x7b, 0x50, 0x9c, 0x25, 0x36, 0xc4, 0xf2, 0xd3, 0x26, 0xd7, + 0x66, 0xc8, 0xfa, 0xb2, 0x54, 0x47, 0xde, 0x53, 0x75, 0xa9, 0x32, 0x8d, 0x64, + 0x9d, 0xda, 0xbd, 0x97, 0xa6, 0xa3, + ], + nk: [ + 0xdb, 0x88, 0x04, 0x9e, 0x02, 0xd2, 0x07, 0x56, 0x8a, 0xfc, 0x42, 0xe0, 0x7d, + 0xb2, 0xab, 0xed, 0x50, 0x0b, 0x27, 0x01, 0xc0, 0x1b, 0xbf, 0xf3, 0x63, 0x99, + 0x76, 0x4b, 0x81, 0xc0, 0x66, 0x4f, + ], + ivk: [ + 0xb0, 0xa5, 0xf3, 0x37, 0x23, 0x2f, 0x2c, 0x3d, 0xac, 0x70, 0xc2, 0xa4, 0x10, + 0xfa, 0x56, 0x1f, 0xc4, 0x5d, 0x8c, 0xc5, 0x9c, 0xda, 0x24, 0x6d, 0x31, 0xc8, + 0xb1, 0x71, 0x5a, 0x57, 0xd9, 0x00, + ], + xsk: None, + xfvk: [ + 0x03, 0x48, 0xc1, 0x83, 0x75, 0x03, 0x00, 0x00, 0x00, 0x8d, 0x93, 0x7b, 0xcf, + 0x81, 0xba, 0x43, 0x0d, 0x5b, 0x49, 0xaf, 0xc0, 0xa4, 0x03, 0x36, 0x7b, 0x1f, + 0xd9, 0x98, 0x79, 0xec, 0xba, 0x41, 0xbe, 0x05, 0x1c, 0x5a, 0x4a, 0xa7, 0xd6, + 0xe7, 0xe8, 0xb1, 0x85, 0xc5, 0x7b, 0x50, 0x9c, 0x25, 0x36, 0xc4, 0xf2, 0xd3, + 0x26, 0xd7, 0x66, 0xc8, 0xfa, 0xb2, 0x54, 0x47, 0xde, 0x53, 0x75, 0xa9, 0x32, + 0x8d, 0x64, 0x9d, 0xda, 0xbd, 0x97, 0xa6, 0xa3, 0xdb, 0x88, 0x04, 0x9e, 0x02, + 0xd2, 0x07, 0x56, 0x8a, 0xfc, 0x42, 0xe0, 0x7d, 0xb2, 0xab, 0xed, 0x50, 0x0b, + 0x27, 0x01, 0xc0, 0x1b, 0xbf, 0xf3, 0x63, 0x99, 0x76, 0x4b, 0x81, 0xc0, 0x66, + 0x4f, 0x69, 0xb9, 0xe0, 0xfa, 0x1c, 0x4b, 0x3d, 0xeb, 0x91, 0xd5, 0x3b, 0xee, + 0xe8, 0x71, 0x15, 0x61, 0x21, 0x47, 0x4b, 0x8b, 0x62, 0xef, 0x24, 0x13, 0x44, + 0x78, 0xdc, 0x34, 0x99, 0x69, 0x1a, 0xf6, 0xbe, 0xcb, 0x50, 0xc3, 0x63, 0xbb, + 0x2e, 0xd9, 0xda, 0x5c, 0x30, 0x43, 0xce, 0xb0, 0xf1, 0xa0, 0x52, 0x7b, 0xf8, + 0x36, 0xb2, 0x9a, 0x35, 0xf7, 0xc0, 0xc9, 0xf2, 0x61, 0x12, 0x3b, 0xe5, 0x6e, + ], + fp: [ + 0x2e, 0x08, 0x15, 0x6d, 0xf8, 0xdf, 0xa2, 0x5b, 0x50, 0x55, 0xfc, 0x06, 0x3c, + 0x67, 0x15, 0x35, 0xa6, 0xa6, 0x5a, 0x60, 0x43, 0x7d, 0x96, 0xe7, 0x93, 0x08, + 0x15, 0xd0, 0x90, 0xf6, 0x2d, 0x67, + ], + d0: None, + d1: Some([ + 0x03, 0x0f, 0xfb, 0x26, 0x3a, 0x93, 0x9e, 0x23, 0x0e, 0x96, 0xdd, + ]), + d2: Some([ + 0x7b, 0xbf, 0x63, 0x93, 0x4c, 0x7e, 0x92, 0x67, 0x0c, 0xdb, 0x55, + ]), + dmax: Some([ + 0x1a, 0x73, 0x0f, 0xeb, 0x00, 0x59, 0xcf, 0x1f, 0x5b, 0xde, 0xa8, + ]), + }, + ]; + + let seed = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + ]; + + let i1 = ChildIndex::NonHardened(1); + let i2h = ChildIndex::Hardened(2); + let i3 = ChildIndex::NonHardened(3); + + let m = ExtendedSpendingKey::master(&seed); + let m_1 = m.derive_child(i1); + let m_1_2h = ExtendedSpendingKey::from_path(&m, &[i1, i2h]); + let m_1_2hv = ExtendedFullViewingKey::from(&m_1_2h); + let m_1_2hv_3 = m_1_2hv.derive_child(i3).unwrap(); + + let xfvks = [ + ExtendedFullViewingKey::from(&m), + ExtendedFullViewingKey::from(&m_1), + ExtendedFullViewingKey::from(&m_1_2h), + m_1_2hv, // Appears twice so we can de-duplicate test code below + m_1_2hv_3, + ]; + assert_eq!(test_vectors.len(), xfvks.len()); + + let xsks = [m, m_1, m_1_2h]; + + for j in 0..xsks.len() { + let xsk = &xsks[j]; + let tv = &test_vectors[j]; + + let mut buf = [0; 32]; + xsk.expsk.ask.into_repr().write_le(&mut buf[..]).unwrap(); + assert_eq!(buf, tv.ask.unwrap()); + xsk.expsk.nsk.into_repr().write_le(&mut buf[..]).unwrap(); + assert_eq!(buf, tv.nsk.unwrap()); + + assert_eq!(xsk.expsk.ovk.0, tv.ovk); + assert_eq!(xsk.dk.0, tv.dk); + assert_eq!(xsk.chain_code.0, tv.c); + + let mut ser = vec![]; + xsk.write(&mut ser).unwrap(); + assert_eq!(&ser[..], &tv.xsk.unwrap()[..]); + } + + for j in 0..xfvks.len() { + let xfvk = &xfvks[j]; + let tv = &test_vectors[j]; + + let mut buf = [0; 32]; + xfvk.fvk.vk.ak.write(&mut buf[..]).unwrap(); + assert_eq!(buf, tv.ak); + xfvk.fvk.vk.nk.write(&mut buf[..]).unwrap(); + assert_eq!(buf, tv.nk); + + assert_eq!(xfvk.fvk.ovk.0, tv.ovk); + assert_eq!(xfvk.dk.0, tv.dk); + assert_eq!(xfvk.chain_code.0, tv.c); + + xfvk.fvk + .vk + .ivk() + .into_repr() + .write_le(&mut buf[..]) + .unwrap(); + assert_eq!(buf, tv.ivk); + + let mut ser = vec![]; + xfvk.write(&mut ser).unwrap(); + assert_eq!(&ser[..], &tv.xfvk[..]); + assert_eq!(xfvk.fvk.fingerprint().0, tv.fp); + + // d0 + let mut di = DiversifierIndex::new(); + match xfvk.dk.diversifier(di) { + Ok((l, d)) if l == di => assert_eq!(d.0, tv.d0.unwrap()), + Ok((_, _)) => assert!(tv.d0.is_none()), + Err(_) => panic!(), + } + + // d1 + di.increment().unwrap(); + match xfvk.dk.diversifier(di) { + Ok((l, d)) if l == di => assert_eq!(d.0, tv.d1.unwrap()), + Ok((_, _)) => assert!(tv.d1.is_none()), + Err(_) => panic!(), + } + + // d2 + di.increment().unwrap(); + match xfvk.dk.diversifier(di) { + Ok((l, d)) if l == di => assert_eq!(d.0, tv.d2.unwrap()), + Ok((_, _)) => assert!(tv.d2.is_none()), + Err(_) => panic!(), + } + + // dmax + let dmax = DiversifierIndex([0xff; 11]); + match xfvk.dk.diversifier(dmax) { + Ok((l, d)) if l == dmax => assert_eq!(d.0, tv.dmax.unwrap()), + Ok((_, _)) => panic!(), + Err(_) => assert!(tv.dmax.is_none()), + } + } } }