diff --git a/Cargo.lock b/Cargo.lock index 81fc21ff..b5c5fac7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1417,6 +1417,21 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" +[[package]] +name = "halo2" +version = "0.0.1" +source = "git+https://github.com/zcash/halo2.git?branch=main#dda60a363001373d564156ad0334e2022d85a5b4" +dependencies = [ + "blake2b_simd", + "crossbeam-utils 0.8.0", + "ff", + "funty", + "group", + "num_cpus", + "pasta_curves", + "rand 0.8.1", +] + [[package]] name = "hashbrown" version = "0.9.1" @@ -2221,6 +2236,21 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "pasta_curves" +version = "0.0.0" +source = "git+https://github.com/zcash/pasta_curves.git?rev=b55a6960dfafd7f767e2820ddf1adaa499322f98#b55a6960dfafd7f767e2820ddf1adaa499322f98" +dependencies = [ + "blake2b_simd", + "ff", + "funty", + "group", + "lazy_static", + "rand 0.8.1", + "static_assertions", + "subtle", +] + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -4304,7 +4334,9 @@ dependencies = [ "displaydoc", "ed25519-zebra", "equihash", + "funty", "futures 0.3.14", + "halo2", "hex", "itertools 0.10.0", "jubjub", diff --git a/zebra-chain/Cargo.toml b/zebra-chain/Cargo.toml index c281ada1..668a0435 100644 --- a/zebra-chain/Cargo.toml +++ b/zebra-chain/Cargo.toml @@ -25,6 +25,7 @@ chrono = { version = "0.4", features = ["serde"] } displaydoc = "0.2.1" equihash = "0.1" futures = "0.3" +halo2 = { git = "https://github.com/zcash/halo2.git", branch = "main" } hex = "0.4" jubjub = "0.6.0" lazy_static = "1.4.0" @@ -48,6 +49,9 @@ redjubjub = "0.4" zebra-test = { path = "../zebra-test/", optional = true } +# Temporary workaround for https://github.com/myrrlyn/funty/issues/3 +funty = "=1.1.0" + [dev-dependencies] bincode = "1" diff --git a/zebra-chain/src/lib.rs b/zebra-chain/src/lib.rs index 81ea3ae5..dca4e87f 100644 --- a/zebra-chain/src/lib.rs +++ b/zebra-chain/src/lib.rs @@ -1,22 +1,22 @@ //! Core Zcash data structures. 🦓 //! -//! This crate provides definitions of core datastructures for Zcash, such as +//! This crate provides definitions of core data structures for Zcash, such as //! blocks, transactions, addresses, etc. #![doc(html_favicon_url = "https://www.zfnd.org/images/zebra-favicon-128.png")] #![doc(html_logo_url = "https://www.zfnd.org/images/zebra-icon.png")] #![doc(html_root_url = "https://doc.zebra.zfnd.org/zebra_chain")] -// #![deny(missing_docs)] #![allow(clippy::try_err)] +// Use the old lint name to build without warnings on stable until 1.51 is stable +#![allow(clippy::unknown_clippy_lints)] +// The actual lints we want to disable +#![allow(clippy::unnecessary_wraps)] // Disable some broken or unwanted clippy nightly lints // Build without warnings on nightly 2021-01-17 and later and stable 1.51 and later #![allow(unknown_lints)] // Disable old lint warnings on nightly until 1.51 is stable #![allow(renamed_and_removed_lints)] -// Use the old lint name to build without warnings on stable until 1.51 is stable -#![allow(clippy::unknown_clippy_lints)] -// The actual lints we want to disable -#![allow(clippy::unnecessary_wraps)] +#![warn(missing_docs)] #[macro_use] extern crate serde; @@ -24,6 +24,7 @@ extern crate serde; pub mod amount; pub mod block; pub mod fmt; +pub mod orchard; pub mod parameters; pub mod primitives; pub mod sapling; diff --git a/zebra-chain/src/orchard.rs b/zebra-chain/src/orchard.rs new file mode 100644 index 00000000..82b3b07a --- /dev/null +++ b/zebra-chain/src/orchard.rs @@ -0,0 +1,20 @@ +//! Orchard-related functionality. + +mod action; +mod address; +#[cfg(any(test, feature = "proptest-impl"))] +mod arbitrary; +mod commitment; +mod note; + +#[cfg(test)] +mod tests; + +pub mod keys; +pub mod tree; + +pub use action::Action; +pub use address::Address; +pub use commitment::{CommitmentRandomness, NoteCommitment, ValueCommitment}; +pub use keys::Diversifier; +pub use note::{EncryptedNote, Note, Nullifier, WrappedNoteKey}; diff --git a/zebra-chain/src/orchard/action.rs b/zebra-chain/src/orchard/action.rs new file mode 100644 index 00000000..78ffefce --- /dev/null +++ b/zebra-chain/src/orchard/action.rs @@ -0,0 +1,68 @@ +use std::io; + +use halo2::pasta::pallas; + +use crate::{ + primitives::redpallas::{self, SpendAuth}, + serialization::{ + ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashSerialize, + }, +}; + +use super::{ + commitment::{self, ValueCommitment}, + keys, + note::{self, Nullifier}, +}; + +/// An Action description, as described in the [Zcash specification §7.3][actiondesc]. +/// +/// Action transfers can optionally perform a spend, and optionally perform an +/// output. Action descriptions are data included in a transaction that +/// describe Action transfers. +/// +/// [actiondesc]: https://zips.z.cash/protocol/nu5.pdf#actiondesc +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Action { + /// A value commitment to net value of the input note minus the output note + pub cv: commitment::ValueCommitment, + /// The nullifier of the input note being spent. + pub nullifer: note::Nullifier, + /// The randomized validating key for spendAuthSig, + pub rk: redpallas::VerificationKey, + /// The 𝑥-coordinate of the note commitment for the output note. + pub cm_x: pallas::Base, + /// An encoding of an ephemeral Pallas public key. + pub ephemeral_key: keys::EphemeralPublicKey, + /// A ciphertext component for the encrypted output note. + pub enc_ciphertext: note::EncryptedNote, + /// A ciphertext component for the encrypted output note. + pub out_ciphertext: note::WrappedNoteKey, +} + +impl ZcashSerialize for Action { + fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { + self.cv.zcash_serialize(&mut writer)?; + writer.write_32_bytes(self.nullifier.into())?; + writer.write_all(&<[u8; 32]>::from(self.rk)[..])?; + writer.write_all(self.cm_x.to_bytes())?; + self.ephemeral_key.zcash_serialize(&mut writer)?; + self.enc_ciphertext.zcash_serialize(&mut writer)?; + self.out_ciphertext.zcash_serialize(&mut writer)?; + Ok(()) + } +} + +impl ZcashDeserialize for Action { + fn zcash_deserialize(mut reader: R) -> Result { + Ok(Action { + cv: ValueCommitment::zcash_deserialize(&mut reader)?, + nullifier: Nullifier::from(reader.read_32_bytes()?), + rk: reader.read_32_bytes()?.into(), + cm_x: pallas::Base::zcash_deserialize(&mut reader)?, + ephemeral_key: keys::EphemeralPublicKey::zcash_deserialize(&mut reader)?, + enc_ciphertext: note::EncryptedNote::zcash_deserialize(&mut reader)?, + out_ciphertext: note::WrappedNoteKey::zcash_deserialize(&mut reader)?, + }) + } +} diff --git a/zebra-chain/src/orchard/address.rs b/zebra-chain/src/orchard/address.rs new file mode 100644 index 00000000..c08621df --- /dev/null +++ b/zebra-chain/src/orchard/address.rs @@ -0,0 +1,174 @@ +//! Orchard shielded payment addresses. + +use std::{ + fmt, + io::{self, Read, Write}, +}; + +use bech32::{self, FromBase32, ToBase32, Variant}; + +#[cfg(test)] +use proptest::prelude::*; + +use crate::{ + parameters::Network, + serialization::{ReadZcashExt, SerializationError}, +}; + +use super::keys; + +/// Human-Readable Parts for input to bech32 encoding. +mod human_readable_parts { + pub const MAINNET: &str = "zo"; + pub const TESTNET: &str = "ztestorchard"; +} + +/// A Orchard _shielded payment address_. +/// +/// Also known as a _diversified payment address_ for Orchard, as +/// defined in [§5.6.4.1 of the Zcash Specification][orchardpaymentaddrencoding]. +/// +/// [orchardpaymentaddrencoding]: https://zips.z.cash/protocol/nu5.pdf#orchardpaymentaddrencoding +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Address { + network: Network, + diversifier: keys::Diversifier, + transmission_key: keys::TransmissionKey, +} + +impl fmt::Debug for Address { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("OrchardAddress") + .field("network", &self.network) + .field("diversifier", &self.diversifier) + .field("transmission_key", &self.transmission_key) + .finish() + } +} + +impl fmt::Display for Address { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut bytes = io::Cursor::new(Vec::new()); + + let _ = bytes.write_all(&<[u8; 11]>::from(self.diversifier)); + let _ = bytes.write_all(&<[u8; 32]>::from(self.transmission_key)); + + let hrp = match self.network { + Network::Mainnet => human_readable_parts::MAINNET, + _ => human_readable_parts::TESTNET, + }; + + bech32::encode_to_fmt(f, hrp, bytes.get_ref().to_base32(), Variant::Bech32).unwrap() + } +} + +impl std::str::FromStr for Address { + type Err = SerializationError; + + fn from_str(s: &str) -> Result { + match bech32::decode(s) { + Ok((hrp, bytes, Variant::Bech32)) => { + let mut decoded_bytes = io::Cursor::new(Vec::::from_base32(&bytes).unwrap()); + + let mut diversifier_bytes = [0; 11]; + decoded_bytes.read_exact(&mut diversifier_bytes)?; + + let transmission_key_bytes = decoded_bytes.read_32_bytes()?; + + Ok(Address { + network: match hrp.as_str() { + human_readable_parts::MAINNET => Network::Mainnet, + _ => Network::Testnet, + }, + diversifier: keys::Diversifier::from(diversifier_bytes), + transmission_key: keys::TransmissionKey::from(transmission_key_bytes), + }) + } + _ => Err(SerializationError::Parse("bech32 decoding error")), + } + } +} + +#[cfg(test)] +impl Arbitrary for Address { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + ( + any::(), + any::(), + any::(), + ) + .prop_map(|(network, diversifier, transmission_key)| Self { + network, + diversifier, + transmission_key, + }) + .boxed() + } + + type Strategy = BoxedStrategy; +} + +#[cfg(test)] +mod tests { + + use rand_core::OsRng; + + use super::*; + + // #[test] + // fn from_str_display() { + // zebra_test::init(); + + // let zo_addr: Address = + // "zs1qqqqqqqqqqqqqqqqqrjq05nyfku05msvu49mawhg6kr0wwljahypwyk2h88z6975u563j8nfaxd" + // .parse() + // .unwrap(); + + // assert_eq!( + // format!("{}", zo_addr), + // "zs1qqqqqqqqqqqqqqqqqrjq05nyfku05msvu49mawhg6kr0wwljahypwyk2h88z6975u563j8nfaxd" + // ); + // } + + #[test] + fn derive_keys_and_addresses() { + zebra_test::init(); + + let spending_key = keys::SpendingKey::new(&mut OsRng); + + let spend_authorizing_key = keys::SpendAuthorizingKey::from(spending_key); + let proof_authorizing_key = keys::ProofAuthorizingKey::from(spending_key); + + let authorizing_key = keys::AuthorizingKey::from(spend_authorizing_key); + let nullifier_deriving_key = keys::NullifierDerivingKey::from(proof_authorizing_key); + let incoming_viewing_key = + keys::IncomingViewingKey::from((authorizing_key, nullifier_deriving_key)); + + let diversifier = keys::Diversifier::new(&mut OsRng); + let transmission_key = keys::TransmissionKey::from((incoming_viewing_key, diversifier)); + + let _orchard_shielded_address = Address { + network: Network::Mainnet, + diversifier, + transmission_key, + }; + } +} + +#[cfg(test)] +proptest! { + + #[test] + fn orchard_address_roundtrip(zaddr in any::
()) { + zebra_test::init(); + + let string = zaddr.to_string(); + + let zaddr2 = string.parse::
() + .expect("randomized orchard z-addr should deserialize"); + + prop_assert_eq![zaddr, zaddr2]; + } +} diff --git a/zebra-chain/src/orchard/arbitrary.rs b/zebra-chain/src/orchard/arbitrary.rs new file mode 100644 index 00000000..32476426 --- /dev/null +++ b/zebra-chain/src/orchard/arbitrary.rs @@ -0,0 +1,58 @@ +use jubjub::AffinePoint; +use proptest::{arbitrary::any, array, collection::vec, prelude::*}; + +use crate::primitives::Groth16Proof; + +use super::{keys, note, tree, NoteCommitment, Output, Spend, ValueCommitment}; + +impl Arbitrary for Spend { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + ( + any::(), + any::(), + array::uniform32(any::()), + any::(), + vec(any::(), 64), + ) + .prop_map(|(anchor, nullifier, rpk_bytes, proof, sig_bytes)| Self { + anchor, + cv: ValueCommitment(AffinePoint::identity()), + nullifier, + rk: redjubjub::VerificationKeyBytes::from(rpk_bytes), + zkproof: proof, + spend_auth_sig: redjubjub::Signature::from({ + let mut b = [0u8; 64]; + b.copy_from_slice(sig_bytes.as_slice()); + b + }), + }) + .boxed() + } + + type Strategy = BoxedStrategy; +} + +impl Arbitrary for Output { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + ( + any::(), + any::(), + any::(), + ) + .prop_map(|(enc_ciphertext, out_ciphertext, zkproof)| Self { + cv: ValueCommitment(AffinePoint::identity()), + cm_u: NoteCommitment(AffinePoint::identity()).extract_u(), + ephemeral_key: keys::EphemeralPublicKey(AffinePoint::identity()), + enc_ciphertext, + out_ciphertext, + zkproof, + }) + .boxed() + } + + type Strategy = BoxedStrategy; +} diff --git a/zebra-chain/src/orchard/commitment.rs b/zebra-chain/src/orchard/commitment.rs new file mode 100644 index 00000000..e41c6587 --- /dev/null +++ b/zebra-chain/src/orchard/commitment.rs @@ -0,0 +1,438 @@ +//! Note and value commitments. + +#[cfg(test)] +mod test_vectors; + +pub mod pedersen_hashes; + +use std::{convert::TryFrom, fmt, io}; + +use bitvec::prelude::*; +use halo2::pasta::pallas; +use rand_core::{CryptoRng, RngCore}; + +use crate::{ + amount::{Amount, NonNegative}, + serialization::{ + serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize, + }, +}; + +use super::keys::{find_group_hash, Diversifier, TransmissionKey}; + +use pedersen_hashes::*; + +/// The randomness used in the Simsemilla Hash for note commitment. +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct CommitmentRandomness(pallas::Scalar); + +/// Note commitments for the output notes. +#[derive(Clone, Copy, Deserialize, PartialEq, Serialize)] +pub struct NoteCommitment(#[serde(with = "serde_helpers::Affine")] pub pallas::Affine); + +impl fmt::Debug for NoteCommitment { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("NoteCommitment") + .field("u", &hex::encode(self.0.get_u().to_bytes())) + .field("v", &hex::encode(self.0.get_v().to_bytes())) + .finish() + } +} + +impl Eq for NoteCommitment {} + +impl From for NoteCommitment { + fn from(extended_point: jubjub::ExtendedPoint) -> Self { + Self(pallas::Affine::from(extended_point)) + } +} + +impl From for [u8; 32] { + fn from(cm: NoteCommitment) -> [u8; 32] { + cm.0.to_bytes() + } +} + +impl TryFrom<[u8; 32]> for NoteCommitment { + type Error = &'static str; + + fn try_from(bytes: [u8; 32]) -> Result { + let possible_point = pallas::Affine::from_bytes(bytes); + + if possible_point.is_some().into() { + Ok(Self(possible_point.unwrap())) + } else { + Err("Invalid pallas::Affine value") + } + } +} + +impl NoteCommitment { + /// Generate a new _NoteCommitment_ and the randomness used to create it. + /// + /// We return the randomness because it is needed to construct a _Note_, + /// before it is encrypted as part of an _Output Description_. This is a + /// higher level function that calls `NoteCommit^Orchard_rcm` internally. + /// + /// NoteCommit^Orchard_rcm (g*_d , pk*_d , v) := + /// WindowedPedersenCommit_rcm([1; 6] || I2LEBSP_64(v) || g*_d || pk*_d) + /// + /// https://zips.z.cash/protocol/protocol.pdf#concretewindowedcommit + #[allow(non_snake_case)] + pub fn new( + csprng: &mut T, + diversifier: Diversifier, + transmission_key: TransmissionKey, + value: Amount, + ) -> Option<(CommitmentRandomness, Self)> + where + T: RngCore + CryptoRng, + { + // s as in the argument name for WindowedPedersenCommit_r(s) + let mut s: BitVec = BitVec::new(); + + // Prefix + s.append(&mut bitvec![1; 6]); + + // Jubjub repr_J canonical byte encoding + // https://zips.z.cash/protocol/protocol.pdf#jubjub + // + // The `TryFrom` impls for the `jubjub::*Point`s handles + // calling `DiversifyHash` implicitly. + let g_d_bytes: [u8; 32]; + if let Ok(g_d) = pallas::Affine::try_from(diversifier) { + g_d_bytes = g_d.to_bytes(); + } else { + return None; + } + + let pk_d_bytes = <[u8; 32]>::from(transmission_key); + let v_bytes = value.to_bytes(); + + s.append(&mut BitVec::::from_slice(&g_d_bytes[..])); + s.append(&mut BitVec::::from_slice(&pk_d_bytes[..])); + s.append(&mut BitVec::::from_slice(&v_bytes[..])); + + let rcm = CommitmentRandomness(generate_trapdoor(csprng)); + + Some(( + rcm, + NoteCommitment::from(windowed_pedersen_commitment(rcm.0, &s)), + )) + } + + /// Hash Extractor for Pallas (?) + /// + /// https://zips.z.cash/protocol/nu5.pdf#concreteextractorpallas + pub fn extract_x(&self) -> pallas::Base { + self.0.get_u() + } +} + +/// A Homomorphic Pedersen commitment to the value of a note, used in Spend and +/// Output Descriptions. +/// +/// https://zips.z.cash/protocol/protocol.pdf#concretehomomorphiccommit +#[derive(Clone, Copy, Deserialize, PartialEq, Serialize)] +pub struct ValueCommitment(#[serde(with = "serde_helpers::AffinePoint")] pub pallas::Affine); + +impl<'a> std::ops::Add<&'a ValueCommitment> for ValueCommitment { + type Output = Self; + + fn add(self, rhs: &'a ValueCommitment) -> Self::Output { + self + *rhs + } +} + +impl std::ops::Add for ValueCommitment { + type Output = Self; + + fn add(self, rhs: ValueCommitment) -> Self::Output { + let value = self.0.to_extended() + rhs.0.to_extended(); + ValueCommitment(value.into()) + } +} + +impl std::ops::AddAssign for ValueCommitment { + fn add_assign(&mut self, rhs: ValueCommitment) { + *self = *self + rhs + } +} + +impl fmt::Debug for ValueCommitment { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("ValueCommitment") + .field("u", &hex::encode(self.0.get_u().to_bytes())) + .field("v", &hex::encode(self.0.get_v().to_bytes())) + .finish() + } +} + +impl From for ValueCommitment { + fn from(extended_point: jubjub::ExtendedPoint) -> Self { + Self(pallas::Affine::from(extended_point)) + } +} + +impl Eq for ValueCommitment {} + +/// LEBS2OSP256(repr_J(cv)) +/// +/// https://zips.z.cash/protocol/protocol.pdf#spendencoding +/// https://zips.z.cash/protocol/protocol.pdf#jubjub +impl From for [u8; 32] { + fn from(cm: ValueCommitment) -> [u8; 32] { + cm.0.to_bytes() + } +} + +impl<'a> std::ops::Sub<&'a ValueCommitment> for ValueCommitment { + type Output = Self; + + fn sub(self, rhs: &'a ValueCommitment) -> Self::Output { + self - *rhs + } +} + +impl std::ops::Sub for ValueCommitment { + type Output = Self; + + fn sub(self, rhs: ValueCommitment) -> Self::Output { + ValueCommitment((self.0.to_extended() - rhs.0.to_extended()).into()) + } +} + +impl std::ops::SubAssign for ValueCommitment { + fn sub_assign(&mut self, rhs: ValueCommitment) { + *self = *self - rhs; + } +} + +impl std::iter::Sum for ValueCommitment { + fn sum(iter: I) -> Self + where + I: Iterator, + { + iter.fold( + ValueCommitment(pallas::Affine::identity()), + std::ops::Add::add, + ) + } +} + +/// LEBS2OSP256(repr_J(cv)) +/// +/// https://zips.z.cash/protocol/protocol.pdf#spendencoding +/// https://zips.z.cash/protocol/protocol.pdf#jubjub +impl TryFrom<[u8; 32]> for ValueCommitment { + type Error = &'static str; + + fn try_from(bytes: [u8; 32]) -> Result { + let possible_point = pallas::Affine::from_bytes(bytes); + + if possible_point.is_some().into() { + Ok(Self(possible_point.unwrap())) + } else { + Err("Invalid pallas::Affine value") + } + } +} + +impl ZcashSerialize for ValueCommitment { + fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { + writer.write_all(&<[u8; 32]>::from(*self)[..])?; + Ok(()) + } +} + +impl ZcashDeserialize for ValueCommitment { + fn zcash_deserialize(mut reader: R) -> Result { + Self::try_from(reader.read_32_bytes()?).map_err(|e| SerializationError::Parse(e)) + } +} + +impl ValueCommitment { + /// Generate a new _ValueCommitment_. + /// + /// https://zips.z.cash/protocol/protocol.pdf#concretehomomorphiccommit + pub fn randomized(csprng: &mut T, value: Amount) -> Self + where + T: RngCore + CryptoRng, + { + let rcv = generate_trapdoor(csprng); + + Self::new(rcv, value) + } + + /// Generate a new _ValueCommitment_ from an existing _rcv_ on a _value_. + /// + /// https://zips.z.cash/protocol/protocol.pdf#concretehomomorphiccommit + #[allow(non_snake_case)] + pub fn new(rcv: jubjub::Fr, value: Amount) -> Self { + let v = jubjub::Fr::from(value); + + // TODO: These generator points can be generated once somewhere else to + // avoid having to recompute them on every new commitment. + let V = find_group_hash(*b"z.cash:Orchard-cv", b"v"); + let R = find_group_hash(*b"z.cash:Orchard-cv", b"r"); + + Self::from(V * v + R * rcv) + } +} + +#[cfg(test)] +mod tests { + + use std::ops::Neg; + + use super::*; + + #[test] + fn pedersen_hash_to_point_test_vectors() { + zebra_test::init(); + + const D: [u8; 8] = *b"Zcash_PH"; + + for test_vector in test_vectors::TEST_VECTORS.iter() { + let result = + pallas::Affine::from(pedersen_hash_to_point(D, &test_vector.input_bits.clone())); + + assert_eq!(result, test_vector.output_point); + } + } + + #[test] + fn add() { + zebra_test::init(); + + let identity = ValueCommitment(pallas::Affine::identity()); + + let g = ValueCommitment(pallas::Affine::from_raw_unchecked( + jubjub::Fq::from_raw([ + 0xe4b3_d35d_f1a7_adfe, + 0xcaf5_5d1b_29bf_81af, + 0x8b0f_03dd_d60a_8187, + 0x62ed_cbb8_bf37_87c8, + ]), + jubjub::Fq::from_raw([ + 0x0000_0000_0000_000b, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + ]), + )); + + assert_eq!(identity + g, g); + } + + #[test] + fn add_assign() { + zebra_test::init(); + + let mut identity = ValueCommitment(pallas::Affine::identity()); + + let g = ValueCommitment(pallas::Affine::from_raw_unchecked( + jubjub::Fq::from_raw([ + 0xe4b3_d35d_f1a7_adfe, + 0xcaf5_5d1b_29bf_81af, + 0x8b0f_03dd_d60a_8187, + 0x62ed_cbb8_bf37_87c8, + ]), + jubjub::Fq::from_raw([ + 0x0000_0000_0000_000b, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + ]), + )); + + identity += g; + let new_g = identity; + + assert_eq!(new_g, g); + } + + #[test] + fn sub() { + zebra_test::init(); + + let g_point = pallas::Affine::from_raw_unchecked( + jubjub::Fq::from_raw([ + 0xe4b3_d35d_f1a7_adfe, + 0xcaf5_5d1b_29bf_81af, + 0x8b0f_03dd_d60a_8187, + 0x62ed_cbb8_bf37_87c8, + ]), + jubjub::Fq::from_raw([ + 0x0000_0000_0000_000b, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + ]), + ); + + let identity = ValueCommitment(pallas::Affine::identity()); + + let g = ValueCommitment(g_point); + + assert_eq!(identity - g, ValueCommitment(g_point.neg())); + } + + #[test] + fn sub_assign() { + zebra_test::init(); + + let g_point = pallas::Affine::from_raw_unchecked( + jubjub::Fq::from_raw([ + 0xe4b3_d35d_f1a7_adfe, + 0xcaf5_5d1b_29bf_81af, + 0x8b0f_03dd_d60a_8187, + 0x62ed_cbb8_bf37_87c8, + ]), + jubjub::Fq::from_raw([ + 0x0000_0000_0000_000b, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + ]), + ); + + let mut identity = ValueCommitment(pallas::Affine::identity()); + + let g = ValueCommitment(g_point); + + identity -= g; + let new_g = identity; + + assert_eq!(new_g, ValueCommitment(g_point.neg())); + } + + #[test] + fn sum() { + zebra_test::init(); + + let g_point = pallas::Affine::from_raw_unchecked( + jubjub::Fq::from_raw([ + 0xe4b3_d35d_f1a7_adfe, + 0xcaf5_5d1b_29bf_81af, + 0x8b0f_03dd_d60a_8187, + 0x62ed_cbb8_bf37_87c8, + ]), + jubjub::Fq::from_raw([ + 0x0000_0000_0000_000b, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, + ]), + ); + + let g = ValueCommitment(g_point); + let other_g = ValueCommitment(g_point); + + let sum: ValueCommitment = vec![g, other_g].into_iter().sum(); + + let doubled_g = ValueCommitment(g_point.to_extended().double().into()); + + assert_eq!(sum, doubled_g); + } +} diff --git a/zebra-chain/src/orchard/commitment/pedersen_hashes.rs b/zebra-chain/src/orchard/commitment/pedersen_hashes.rs new file mode 100644 index 00000000..e3f69676 --- /dev/null +++ b/zebra-chain/src/orchard/commitment/pedersen_hashes.rs @@ -0,0 +1,147 @@ +//! Pedersen hash functions and helpers. + +use bitvec::prelude::*; +use rand_core::{CryptoRng, RngCore}; + +use super::super::keys::find_group_hash; + +/// I_i +/// +/// Expects i to be 1-indexed from the loop it's called in. +/// +/// https://zips.z.cash/protocol/protocol.pdf#concretepedersenhash +#[allow(non_snake_case)] +fn I_i(domain: [u8; 8], i: u32) -> jubjub::ExtendedPoint { + find_group_hash(domain, &(i - 1).to_le_bytes()) +} + +/// The encoding function ⟨Mᵢ⟩ +/// +/// Σ j={0,k-1}: (1 - 2x₂)⋅(1 + x₀ + 2x₁)⋅2^(4⋅j) +/// +/// https://zips.z.cash/protocol/protocol.pdf#concretepedersenhash +#[allow(non_snake_case)] +fn M_i(segment: &BitSlice) -> jubjub::Fr { + let mut m_i = jubjub::Fr::zero(); + + for (j, chunk) in segment.chunks(3).enumerate() { + // Pad each chunk with zeros. + let mut store = 0u8; + let bits = store.bits_mut::(); + chunk + .iter() + .enumerate() + .for_each(|(i, bit)| bits.set(i, *bit)); + + let mut tmp = jubjub::Fr::one(); + + if bits[0] { + tmp += &jubjub::Fr::one(); + } + + if bits[1] { + tmp += &jubjub::Fr::one().double(); + } + + if bits[2] { + tmp -= tmp.double(); + } + + if j > 0 { + // Inclusive range! + tmp *= (1..=(4 * j)).fold(jubjub::Fr::one(), |acc, _| acc.double()); + } + + m_i += tmp; + } + + m_i +} + +/// "...an algebraic hash function with collision resistance (for fixed input +/// length) derived from assumed hardness of the Discrete Logarithm Problem on +/// the Jubjub curve." +/// +/// PedersenHash is used in the definitions of Pedersen commitments (§ +/// 5.4.7.2 ‘Windowed Pedersen commitments’), and of the Pedersen hash for the +/// Sapling incremental Merkle tree (§ 5.4.1.3 ‘MerkleCRH^Sapling Hash +/// Function’). +/// +/// https://zips.z.cash/protocol/protocol.pdf#concretepedersenhash +#[allow(non_snake_case)] +pub fn pedersen_hash_to_point(domain: [u8; 8], M: &BitVec) -> jubjub::ExtendedPoint { + let mut result = jubjub::ExtendedPoint::identity(); + + // Split M into n segments of 3 * c bits, where c = 63, padding the last + // segment with zeros. + // + // This loop is 1-indexed per the math definitions in the spec. + // + // https://zips.z.cash/protocol/protocol.pdf#concretepedersenhash + for (i, segment) in M + .chunks(189) + .enumerate() + .map(|(i, segment)| (i + 1, segment)) + { + result += I_i(domain, i as u32) * M_i(&segment); + } + + result +} + +/// Pedersen Hash Function +/// +/// This is technically returning 255 (l_MerkleSapling) bits, not 256. +/// +/// https://zips.z.cash/protocol/protocol.pdf#concretepedersenhash +#[allow(non_snake_case)] +pub fn pedersen_hash(domain: [u8; 8], M: &BitVec) -> jubjub::Fq { + jubjub::AffinePoint::from(pedersen_hash_to_point(domain, M)).get_u() +} + +/// Mixing Pedersen Hash Function +/// +/// Used to compute ρ from a note commitment and its position in the note +/// commitment tree. It takes as input a Pedersen commitment P, and hashes it +/// with another input x. +/// +/// MixingPedersenHash(P, x) := P + [x]FindGroupHash^J^(r)(“Zcash_J_”, “”) +/// +/// https://zips.z.cash/protocol/protocol.pdf#concretemixinghash +#[allow(non_snake_case)] +pub fn mixing_pedersen_hash(P: jubjub::ExtendedPoint, x: jubjub::Fr) -> jubjub::ExtendedPoint { + const J: [u8; 8] = *b"Zcash_J_"; + + P + find_group_hash(J, b"") * x +} + +/// Construct a 'windowed' Pedersen commitment by reusing a Pederson hash +/// construction, and adding a randomized point on the Jubjub curve. +/// +/// WindowedPedersenCommit_r (s) := \ +/// PedersenHashToPoint(“Zcash_PH”, s) + [r]FindGroupHash^J^(r)(“Zcash_PH”, “r”) +/// +/// https://zips.z.cash/protocol/protocol.pdf#concretewindowedcommit +pub fn windowed_pedersen_commitment(r: jubjub::Fr, s: &BitVec) -> jubjub::ExtendedPoint { + const D: [u8; 8] = *b"Zcash_PH"; + + pedersen_hash_to_point(D, &s) + find_group_hash(D, b"r") * r +} + +/// Generates a random scalar from the scalar field 𝔽_{r_𝕁}. +/// +/// The prime order subgroup 𝕁^(r) is the order-r_𝕁 subgroup of 𝕁 that consists +/// of the points whose order divides r. This function is useful when generating +/// the uniform distribution on 𝔽_{r_𝕁} needed for Sapling commitment schemes' +/// trapdoor generators. +/// +/// https://zips.z.cash/protocol/protocol.pdf#jubjub +pub fn generate_trapdoor(csprng: &mut T) -> jubjub::Fr +where + T: RngCore + CryptoRng, +{ + let mut bytes = [0u8; 64]; + csprng.fill_bytes(&mut bytes); + // Fr::from_bytes_wide() reduces the input modulo r via Fr::from_u512() + jubjub::Fr::from_bytes_wide(&bytes) +} diff --git a/zebra-chain/src/orchard/commitment/sinsemilla_hashes.rs b/zebra-chain/src/orchard/commitment/sinsemilla_hashes.rs new file mode 100644 index 00000000..c244839e --- /dev/null +++ b/zebra-chain/src/orchard/commitment/sinsemilla_hashes.rs @@ -0,0 +1,108 @@ +//! Sinsemilla hash functions and helpers. + +use bitvec::prelude::*; +use rand_core::{CryptoRng, RngCore}; + +use halo2::pasta::pallas; + +/// [Hash Extractor for Pallas][concreteextractorpallas] +/// +/// P → B^[l^Orchard_Merkle] +/// +/// [concreteextractorpallas]: https://zips.z.cash/protocol/nu5.pdf#concreteextractorpallas +// TODO: should this return the basefield element type, or the bytes? +pub fn extract_p(point: pallas::Point) -> pallas::Base { + match pallas::Affine::from(point).get_xy().into() { + // If Some, it's not the identity. + Some((x, _)) => x, + _ => pallas::Base::zero(), + } +} + +/// Q +/// +/// Q(D) := GroupHash^P(︀“z.cash:SinsemillaQ”, D) +/// +/// https://zips.z.cash/protocol/protocol.pdf#concretesinsemillahash +#[allow(non_snake_case)] +fn Q(domain: [u8; 8]) -> pallas::Point { + pallas::Point::hash_to_curve("z.cash:SinsemillaQ")(&domain[..]) +} + +/// S +/// +/// S(j) := GroupHash^P(︀“z.cash:SinsemillaS”, LEBS2OSP32(I2LEBSP32(j))) +/// +/// https://zips.z.cash/protocol/protocol.pdf#concretesinsemillahash +#[allow(non_snake_case)] +fn S(j: [u8; 2]) -> pallas::Point { + pallas::Point::hash_to_curve("z.cash:SinsemillaS")(&j) +} + +/// "...an algebraic hash function with collision resistance (for fixed input +/// length) derived from assumed hardness of the Discrete Logarithm Problem on +/// the Jubjub curve." +/// +/// SinsemillaHash is used in the definitions of Sinsemilla commitments and of +/// the Sinsemilla hash for the Sapling incremental Merkle tree (§ 5.4.1.3 +/// ‘MerkleCRH^Orchard Hash Function’). +/// +/// https://zips.z.cash/protocol/protocol.pdf#concretesinsemillahash +#[allow(non_snake_case)] +pub fn sinsemilla_hash_to_point(domain: [u8; 8], M: &BitVec) -> pallas::Point { + const K: u8 = 10; + const C: u8 = 253; + + assert!(M.len() <= K * C); + + let mut acc = Q(domain); + + // Split M into n segments of k bits, where k = 10 and c = 253, padding + // the last segment with zeros. + // + // https://zips.z.cash/protocol/protocol.pdf#concretesinsemillahash + for chunk in M.chunks(K) { + // Pad each chunk with zeros. + let mut store = 0u8; + let bits = store.bits_mut::(); + chunk + .iter() + .enumerate() + .for_each(|(i, bit)| bits.set(i, *bit)); + + // An instance of LEBS2IP_k + let j = &bits.iter().fold(0u16, |j, &bit| j * *2 + bit as u16); + + acc += (acc + S(j.to_le_bytes())); + } + + acc +} + +/// Sinsemilla Hash Function +/// +/// "SinsemillaHash is an algebraic hash function with collision resistance (for +/// fixed input length) derived from assumed hardness of the Discrete Logarithm +/// Problem. It is designed by Sean Bowe and Daira Hopwood. The motivation for +/// introducing a new discrete-log-based hash function (rather than using +/// PedersenHash) is to make efcient use of the lookups available in recent +/// proof systems including Halo 2." +/// +/// https://zips.z.cash/protocol/protocol.pdf#concretesinsemillahash +#[allow(non_snake_case)] +pub fn sinsemilla_hash(domain: [u8; 8], M: &BitVec) -> pallas::Base { + extract_p(sinsemilla_hash_to_point(domain, M)) +} + +/// Generates a random scalar from the scalar field 𝔽_{q_P}. +/// +/// https://zips.z.cash/protocol/nu5.pdf#pallasandvesta +pub fn generate_trapdoor(csprng: &mut T) -> pallas::Scalar +where + T: RngCore + CryptoRng, +{ + let mut bytes = [0u8; 64]; + csprng.fill_bytes(&mut bytes); + // Scalar::from_bytes_wide() reduces the input modulo q under the hood. + pallas::Scalar::from_bytes_wide(&bytes) +} diff --git a/zebra-chain/src/orchard/commitment/test_vectors.rs b/zebra-chain/src/orchard/commitment/test_vectors.rs new file mode 100644 index 00000000..0a8c0852 --- /dev/null +++ b/zebra-chain/src/orchard/commitment/test_vectors.rs @@ -0,0 +1,299 @@ +// Test vector data generated from +// https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/sapling_pedersen.py +// +// These vectors in particular correspond to the Personalization::NoteCommitment +// enum variant from the original source. +// +// The Python hex-encoded outputs for these test vectors were output in +// big-endian byte order, so to parse them, we reversed their order; in +// librustzcash, they match their Display impl to match the Python hex strings +// and that's what they compare in their unit tests, not the bytes. + +#![allow(dead_code)] + +use bitvec::prelude::*; +use lazy_static::lazy_static; + +fn point_from_hex>(point_in_hex: T) -> jubjub::AffinePoint { + let mut bytes = [0u8; 32]; + let _ = hex::decode_to_slice(point_in_hex, &mut bytes); + + jubjub::AffinePoint::from_bytes(bytes).unwrap() +} + +pub struct TestVector { + pub input_bits: BitVec, + pub output_point: jubjub::AffinePoint, +} + +lazy_static! { + pub static ref TEST_VECTORS: [TestVector; 12] = [ + TestVector { + input_bits: bitvec![Lsb0, u8; 1, 1, 1, 1, 1, 1], + // original librustzcash affine point test vector (in reversed-endian byte order): + // "06b1187c11ca4fb4383b2e0d0dbbde3ad3617338b5029187ec65a5eaed5e4d0b", + // "3ce70f536652f0dea496393a1e55c4e08b9d55508e16d11e5db40d4810cbc982" + output_point: point_from_hex( + "82c9cb10480db45d1ed1168e50559d8be0c4551e3a3996a4def05266530fe7bc" + ) + }, + TestVector { + input_bits: bitvec![Lsb0, u8; 1, 1, 1, 1, 1, 1, 0], + // Original librustzcash affine point test vector (in reversed-endian byte order): + // "2fc3bc454c337f71d4f04f86304262fcbfc9ecd808716b92fc42cbe6827f7f1a", + // "46d0d25bf1a654eedc6a9b1e5af398925113959feac31b7a2c036ff9b9ec0638" + output_point: point_from_hex( + "3806ecb9f96f032c7a1bc3ea9f9513519298f35a1e9b6adcee54a6f15bd2d046" + ) + }, + TestVector { + input_bits: bitvec![Lsb0, u8; 1, 1, 1, 1, 1, 1, 1], + // Original librustzcash affine point test vector (in reversed-endian byte order): + // "4f8ce0e0a9e674b3ab9606a7d7aefba386e81583d81918127814cde41d209d97", + // "312b5ab93b14c9b9af334fe1fe3c50fffb53fbd074fa40ca600febde7c97e346" + output_point: point_from_hex( + "46e3977cdeeb0f60ca40fa74d0fb53fbff503cfee14f33afb9c9143bb95a2bb1" + ) + }, + TestVector { + input_bits: bitvec![Lsb0, u8; 1, 1, 1, 1, 1, 1, 1, 0, 0], + // Original librustzcash affine point test vector (in reversed-endian byte order): + // "4f8ce0e0a9e674b3ab9606a7d7aefba386e81583d81918127814cde41d209d97", + // "312b5ab93b14c9b9af334fe1fe3c50fffb53fbd074fa40ca600febde7c97e346" + output_point: point_from_hex( + "46e3977cdeeb0f60ca40fa74d0fb53fbff503cfee14f33afb9c9143bb95a2bb1" + ), + }, + TestVector { + input_bits: bitvec![Lsb0, u8; + 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, + 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, + 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, + 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, + 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, + ], + // Original librustzcash affine point test vector (in reversed-endian byte order): + // "599ab788360ae8c6d5bb7618aec37056d6227408d857fdc394078a3d7afdfe0f", + // "4320c373da670e28d168f4ffd72b43208e8c815f40841682c57a3ee1d005a527" + output_point: point_from_hex( + "27a505d0e13e7ac5821684405f818c8e20432bd7fff468d1280e67da73c320c3" + ), + }, + TestVector { + input_bits: bitvec![Lsb0, u8; + 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, + 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, + 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, + 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, + ], + // Original librustzcash affine point test vector (in reversed-endian byte order): + // "2da510317620f5dfdce1f31db6019f947eedcf02ff2972cff597a5c3ad21f5dd", + // "198789969c0c33e6c359b9da4a51771f4d50863f36beef90436944fe568399f2" + output_point: point_from_hex( + "f2998356fe44694390efbe363f86504d1f77514adab959c3e6330c9c96898799" + ), + }, + TestVector { + input_bits: bitvec![Lsb0, u8; + 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, + 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, + 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, + 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, + ], + // Original librustzcash affine point test vector (in reversed-endian byte order): + // "601247c7e640992d193dfb51df6ed93446687a7f2bcd0e4a598e6feb1ef20c40", + // "371931733b73e7b95c2cad55a6cebd15c83619f697c64283e54e5ef61442a743" + output_point: point_from_hex( + "43a74214f65e4ee58342c697f61936c815bdcea655ad2c5cb9e7733b73311937" + ) + }, + TestVector { + input_bits: bitvec![Lsb0, u8; + 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, + 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, + 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, + 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, + 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, + 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, + 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, + 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, + 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, + 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, + 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, + 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, + 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, + 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, + 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, + 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, + 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, + 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, + 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, + 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, + 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, + 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, + 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, + ], + // Original librustzcash affine point test vector (in reversed-endian byte order): + // "314192ecb1f2d8806a8108704c875a25d9fb7e444f9f373919adedebe8f2ae27", + // "6b12b32f1372ad574799dee9eb591d961b704bf611f55fcc71f7e82cd3330b74" + output_point: point_from_hex( + "740b33d32ce8f771cc5ff511f64b701b961d59ebe9de994757ad72132fb312eb" + ), + }, + TestVector { + input_bits: bitvec![Lsb0, u8; + 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, + 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, + 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, + 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, + 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, + 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, + 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, + 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, + 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, + 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, + 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, + 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, + 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, + 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, + 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, + 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, + 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, + 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, + 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, + 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, + 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, + 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, + 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, + 0, + ], + // Original librustzcash affine point test vector (in reversed-endian byte order): + // "0666c2bce7f362a2b807d212e9a577f116891a932affd7addec39fbf372c494e", + // "6758bccfaf2e47c07756b96edea23aa8d10c33b38220bd1c411af612eeec18ab" + output_point: point_from_hex( + "ab18ecee12f61a411cbd2082b3330cd1a83aa2de6eb95677c0472eafcfbc5867" + ), + }, + TestVector { + input_bits: bitvec![Lsb0, u8; + 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, + 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, + 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, + 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, + 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, + 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, + 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, + 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, + 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, + 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, + 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, + 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, + 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, + 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, + 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, + 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, + 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, + 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, + 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, + 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, + 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, + 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, + 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, + ], + // Original librustzcash affine point test vector (in reversed-endian byte order): + // "130afe02b99375484efb0998f5331d2178e1d00e803049bb0769099420624f5f", + // "5e2fc6970554ffe358652aa7968ac4fcf3de0c830e6ea492e01a38fafb68cd71" + output_point: point_from_hex( + "71cd68fbfa381ae092a46e0e830cdef3fcc48a96a72a6558e3ff540597c62fde" + ), + }, + TestVector { + input_bits: bitvec![Lsb0, u8; + 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, + 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, + 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, + 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, + 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, + 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, + 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, + 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, + 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, + 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, + 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, + 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, + 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, + 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, + 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, + 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, + 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, + 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, + 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, + 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, + 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, + 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, + 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, + 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, + 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, + 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, + 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, + 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, + 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, + ], + // Original librustzcash affine point test vector (in reversed-endian byte order): + // "67914ebd539961b70f468fa23d4cb42133693a8ac57cd35a1e6369fe34fbedf7", + // "44770870c0f0cfe59a10df95d6c21e6f1514a2f464b66377599438c126052d9f" + output_point: point_from_hex( + "9f2d0526c13894597763b664f4a214156f1ec2d695df109ae5cff0c0700877c4" + ), + }, + TestVector { + input_bits: bitvec![Lsb0, u8; + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + ], + // Original librustzcash affine point test vector (in reversed-endian byte order): + // "329e3bb2ca31ea6e13a986730237f6fd16b842a510cbabe851bdbcf57d75ee0d", + // "471d2109656afcb96d0609b371b132b97efcf72c6051064dd19fdc004799bfa9" + output_point: point_from_hex( + "a9bf994700dc9fd14d0651602cf7fc7eb932b171b309066db9fc6a6509211dc7" + ), + }, + ]; +} diff --git a/zebra-chain/src/orchard/keys.rs b/zebra-chain/src/orchard/keys.rs new file mode 100644 index 00000000..a7516ec3 --- /dev/null +++ b/zebra-chain/src/orchard/keys.rs @@ -0,0 +1,837 @@ +//! Orchard key types. +//! +//! [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents +#![allow(clippy::unit_arg)] + +// #[cfg(test)] +// mod test_vectors; +#[cfg(test)] +mod tests; + +use std::{ + convert::{From, Into, TryFrom}, + fmt, + io::{self, Write}, + str::FromStr, +}; + +use bech32::{self, FromBase32, ToBase32, Variant}; +use rand_core::{CryptoRng, RngCore}; + +use crate::{ + parameters::Network, + primitives::redpallas::{self, SpendAuth}, + serialization::{ + serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize, + }, +}; + +/// Used to derive the outgoing cipher key _ock_ used to encrypt an Output ciphertext. +/// +/// PRF^ovk(ock, cv, cm_x, ephemeralKey) := BLAKE2b-256(“Zcash_Orchardock”, ovk || cv || cm_x || ephemeralKey) +/// +/// https://zips.z.cash/protocol/nu5.pdf#concreteprfs +fn prf_ovk(ovk: [u8; 32], cv: [u8; 32], cm_x: [u8; 32], ephemeral_key: [u8; 32]) -> [u8; 32] { + let hash = blake2b_simd::Params::new() + .hash_length(32) + .personal(b"Zcash_Orchardock") + .to_state() + .update(ovk) + .update(cv) + .update(cm_x) + .update(ephemeral_key) + .finalize(); + + *hash.as_array() +} + +/// Invokes Blake2s-256 as _CRH^ivk_, to derive the IncomingViewingKey +/// bytes from an AuthorizingKey and NullifierDerivingKey. +/// +/// _CRH^ivk(ak, nk) := BLAKE2s-256("Zcashivk", ak || nk)_ +/// +/// https://zips.z.cash/protocol/protocol.pdf#concretecrhivk +fn crh_ivk(ak: [u8; 32], nk: [u8; 32]) -> [u8; 32] { + let hash = blake2s_simd::Params::new() + .hash_length(32) + .personal(b"Zcashivk") + .to_state() + .update(&ak[..]) + .update(&nk[..]) + .finalize(); + + *hash.as_array() +} + +/// Used to derive a diversified base point from a diversifier value. +/// +/// https://zips.z.cash/protocol/protocol.pdf#concretediversifyhash +fn diversify_hash(d: [u8; 11]) -> Option { + jubjub_group_hash(*b"Zcash_gd", &d) +} + +// TODO: replace with reference to redjubjub or jubjub when merged and +// exported. +type Scalar = jubjub::Fr; + +/// Magic human-readable strings used to identify what networks +/// Sapling Spending Keys are associated with when encoded/decoded +/// with bech32. +mod sk_hrp { + pub const MAINNET: &str = "secret-spending-key-main"; + pub const TESTNET: &str = "secret-spending-key-test"; +} + +/// A _Spending Key_, as described in [protocol specification +/// §4.2.2][ps]. +/// +/// Our root secret key of the Sapling key derivation tree. All other +/// Sapling key types derive from the SpendingKey value. +/// +/// [ps]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + any(test, feature = "proptest-impl"), + derive(proptest_derive::Arbitrary) +)] +pub struct SpendingKey { + network: Network, + bytes: [u8; 32], +} + +// TODO: impl a From that accepts a Network? + +impl From<[u8; 32]> for SpendingKey { + /// Generate a _SpendingKey_ from existing bytes. + fn from(bytes: [u8; 32]) -> Self { + Self { + network: Network::default(), + bytes, + } + } +} + +impl fmt::Display for SpendingKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let hrp = match self.network { + Network::Mainnet => sk_hrp::MAINNET, + _ => sk_hrp::TESTNET, + }; + + bech32::encode_to_fmt(f, hrp, &self.bytes.to_base32(), Variant::Bech32).unwrap() + } +} + +impl FromStr for SpendingKey { + type Err = SerializationError; + + fn from_str(s: &str) -> Result { + match bech32::decode(s) { + Ok((hrp, bytes, Variant::Bech32)) => { + let decoded = Vec::::from_base32(&bytes).unwrap(); + + let mut decoded_bytes = [0u8; 32]; + decoded_bytes[..].copy_from_slice(&decoded[0..32]); + + Ok(SpendingKey { + network: match hrp.as_str() { + sk_hrp::MAINNET => Network::Mainnet, + _ => Network::Testnet, + }, + bytes: decoded_bytes, + }) + } + _ => Err(SerializationError::Parse("bech32 decoding error")), + } + } +} + +impl SpendingKey { + /// Generate a new _SpendingKey_. + pub fn new(csprng: &mut T) -> Self + where + T: RngCore + CryptoRng, + { + let mut bytes = [0u8; 32]; + csprng.fill_bytes(&mut bytes); + + Self::from(bytes) + } +} + +/// A _Spend Authorizing Key_, as described in [protocol specification +/// §4.2.2][ps]. +/// +/// Used to generate _spend authorization randomizers_ to sign each +/// _Spend Description_, proving ownership of notes. +/// +/// [ps]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct SpendAuthorizingKey(pub Scalar); + +impl fmt::Debug for SpendAuthorizingKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("SpendAuthorizingKey") + .field(&hex::encode(<[u8; 32]>::from(*self))) + .finish() + } +} + +impl From for [u8; 32] { + fn from(sk: SpendAuthorizingKey) -> Self { + sk.0.to_bytes() + } +} + +impl From for SpendAuthorizingKey { + /// Invokes Blake2b-512 as _PRF^expand_, t=0, to derive a + /// SpendAuthorizingKey from a SpendingKey. + /// + /// https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents + /// https://zips.z.cash/protocol/protocol.pdf#concreteprfs + fn from(spending_key: SpendingKey) -> SpendAuthorizingKey { + let hash_bytes = prf_expand(spending_key.bytes, &[0]); + + Self(Scalar::from_bytes_wide(&hash_bytes)) + } +} + +impl PartialEq<[u8; 32]> for SpendAuthorizingKey { + fn eq(&self, other: &[u8; 32]) -> bool { + <[u8; 32]>::from(*self) == *other + } +} + +/// A _Proof Authorizing Key_, as described in [protocol specification +/// §4.2.2][ps]. +/// +/// Used in the _Spend Statement_ to prove nullifier integrity. +/// +/// [ps]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct ProofAuthorizingKey(pub Scalar); + +impl fmt::Debug for ProofAuthorizingKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("ProofAuthorizingKey") + .field(&hex::encode(<[u8; 32]>::from(*self))) + .finish() + } +} + +impl From for [u8; 32] { + fn from(nsk: ProofAuthorizingKey) -> Self { + nsk.0.to_bytes() + } +} + +impl From for ProofAuthorizingKey { + /// For this invocation of Blake2b-512 as _PRF^expand_, t=1. + /// + /// https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents + /// https://zips.z.cash/protocol/protocol.pdf#concreteprfs + fn from(spending_key: SpendingKey) -> ProofAuthorizingKey { + let hash_bytes = prf_expand(spending_key.bytes, &[1]); + + Self(Scalar::from_bytes_wide(&hash_bytes)) + } +} + +impl PartialEq<[u8; 32]> for ProofAuthorizingKey { + fn eq(&self, other: &[u8; 32]) -> bool { + <[u8; 32]>::from(*self) == *other + } +} + +/// An _Outgoing Viewing Key_, as described in [protocol specification +/// §4.2.2][ps]. +/// +/// Used to decrypt outgoing notes without spending them. +/// +/// [ps]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct OutgoingViewingKey(pub [u8; 32]); + +impl fmt::Debug for OutgoingViewingKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("OutgoingViewingKey") + .field(&hex::encode(&self.0)) + .finish() + } +} + +impl From<[u8; 32]> for OutgoingViewingKey { + /// Generate an _OutgoingViewingKey_ from existing bytes. + fn from(bytes: [u8; 32]) -> Self { + Self(bytes) + } +} + +impl From for [u8; 32] { + fn from(ovk: OutgoingViewingKey) -> [u8; 32] { + ovk.0 + } +} + +impl From for OutgoingViewingKey { + /// For this invocation of Blake2b-512 as _PRF^expand_, t=2. + /// + /// https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents + /// https://zips.z.cash/protocol/protocol.pdf#concreteprfs + fn from(spending_key: SpendingKey) -> OutgoingViewingKey { + let hash_bytes = prf_expand(spending_key.bytes, &[2]); + + let mut bytes = [0u8; 32]; + bytes[..].copy_from_slice(&hash_bytes[0..32]); + + Self(bytes) + } +} + +impl PartialEq<[u8; 32]> for OutgoingViewingKey { + fn eq(&self, other: &[u8; 32]) -> bool { + self.0 == *other + } +} + +/// An _Authorizing Key_, as described in [protocol specification +/// §4.2.2][ps]. +/// +/// Used to validate _Spend Authorization Signatures_, proving +/// ownership of notes. +/// +/// [ps]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents +#[derive(Copy, Clone, Debug)] +pub struct AuthorizingKey(pub redjubjub::VerificationKey); + +impl Eq for AuthorizingKey {} + +impl From<[u8; 32]> for AuthorizingKey { + fn from(bytes: [u8; 32]) -> Self { + Self(redjubjub::VerificationKey::try_from(bytes).unwrap()) + } +} + +impl From for [u8; 32] { + fn from(ak: AuthorizingKey) -> [u8; 32] { + ak.0.into() + } +} + +impl From for AuthorizingKey { + fn from(ask: SpendAuthorizingKey) -> Self { + let sk = redjubjub::SigningKey::::try_from(<[u8; 32]>::from(ask)).unwrap(); + Self(redjubjub::VerificationKey::from(&sk)) + } +} + +impl PartialEq for AuthorizingKey { + fn eq(&self, other: &Self) -> bool { + <[u8; 32]>::from(self.0) == <[u8; 32]>::from(other.0) + } +} + +impl PartialEq<[u8; 32]> for AuthorizingKey { + fn eq(&self, other: &[u8; 32]) -> bool { + <[u8; 32]>::from(self.0) == *other + } +} + +/// A _Nullifier Deriving Key_, as described in [protocol +/// specification §4.2.2][ps]. +/// +/// Used to create a _Nullifier_ per note. +/// +/// [ps]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents +#[derive(Copy, Clone, PartialEq)] +pub struct NullifierDerivingKey(pub jubjub::AffinePoint); + +impl fmt::Debug for NullifierDerivingKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("NullifierDerivingKey") + .field("u", &hex::encode(self.0.get_u().to_bytes())) + .field("v", &hex::encode(self.0.get_v().to_bytes())) + .finish() + } +} + +impl From<[u8; 32]> for NullifierDerivingKey { + fn from(bytes: [u8; 32]) -> Self { + Self(jubjub::AffinePoint::from_bytes(bytes).unwrap()) + } +} + +impl Eq for NullifierDerivingKey {} + +impl From for [u8; 32] { + fn from(nk: NullifierDerivingKey) -> [u8; 32] { + nk.0.to_bytes() + } +} + +impl From<&NullifierDerivingKey> for [u8; 32] { + fn from(nk: &NullifierDerivingKey) -> [u8; 32] { + nk.0.to_bytes() + } +} + +impl From for NullifierDerivingKey { + /// Requires JubJub's _FindGroupHash^J("Zcash_H_", "")_, then uses + /// the resulting generator point to scalar multiply the + /// ProofAuthorizingKey into the new NullifierDerivingKey + /// + /// https://github.com/zcash/librustzcash/blob/master/zcash_primitives/src/group_hash.rs + /// https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents + /// https://zips.z.cash/protocol/protocol.pdf#concretegrouphashjubjub + fn from(nsk: ProofAuthorizingKey) -> Self { + // Should this point, when generated, be fixed for the rest of + // the protocol instance? Since this is kind of hash-and-pray, it + // seems it might not always return the same result? + let generator_point = zcash_h(); + + // TODO: impl Mul for Fr, so we can reverse + // this to match the math in the spec / general scalar mult + // notation convention. + Self(jubjub::AffinePoint::from(generator_point * nsk.0)) + } +} + +impl PartialEq<[u8; 32]> for NullifierDerivingKey { + fn eq(&self, other: &[u8; 32]) -> bool { + <[u8; 32]>::from(*self) == *other + } +} + +/// Magic human-readable strings used to identify what networks +/// Sapling IncomingViewingKeys are associated with when +/// encoded/decoded with bech32. +mod ivk_hrp { + pub const MAINNET: &str = "zivks"; + pub const TESTNET: &str = "zivktestsapling"; +} + +/// An _Incoming Viewing Key_, as described in [protocol specification +/// §4.2.2][ps]. +/// +/// Used to decrypt incoming notes without spending them. +/// +/// [ps]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct IncomingViewingKey { + network: Network, + scalar: Scalar, +} + +// TODO: impl a From that accepts a Network? + +impl fmt::Debug for IncomingViewingKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("IncomingViewingKey") + .field(&hex::encode(self.scalar.to_bytes())) + .finish() + } +} + +impl fmt::Display for IncomingViewingKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let hrp = match self.network { + Network::Mainnet => ivk_hrp::MAINNET, + _ => ivk_hrp::TESTNET, + }; + + bech32::encode_to_fmt(f, hrp, &self.scalar.to_bytes().to_base32(), Variant::Bech32).unwrap() + } +} + +impl From<[u8; 32]> for IncomingViewingKey { + /// Generate an _IncomingViewingKey_ from existing bytes. + fn from(mut bytes: [u8; 32]) -> Self { + // Drop the most significant five bits, so it can be interpreted + // as a scalar. + // + // I don't want to put this inside crh_ivk, but does it belong + // inside Scalar/Fr::from_bytes()? That seems the better + // place... + // + // https://github.com/zcash/librustzcash/blob/master/zcash_primitives/src/primitives.rs#L86 + bytes[31] &= 0b0000_0111; + + Self { + // TODO: handle setting the Network better. + network: Network::default(), + scalar: Scalar::from_bytes(&bytes).unwrap(), + } + } +} + +impl From<(AuthorizingKey, NullifierDerivingKey)> for IncomingViewingKey { + /// For this invocation of Blake2s-256 as _CRH^ivk_. + /// + /// https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents + /// https://zips.z.cash/protocol/protocol.pdf#concreteprfs + /// https://zips.z.cash/protocol/protocol.pdf#jubjub + // TODO: return None if ivk = 0 + // + // "If ivk = 0, discard this key and start over with a new + // [spending key]." - [§4.2.2][ps] + // + // [ps]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents + fn from((ask, nk): (AuthorizingKey, NullifierDerivingKey)) -> Self { + let hash_bytes = crh_ivk(ask.into(), nk.into()); + + IncomingViewingKey::from(hash_bytes) + } +} + +impl FromStr for IncomingViewingKey { + type Err = SerializationError; + + fn from_str(s: &str) -> Result { + match bech32::decode(s) { + Ok((hrp, bytes, Variant::Bech32)) => { + let decoded = Vec::::from_base32(&bytes).unwrap(); + + let mut scalar_bytes = [0u8; 32]; + scalar_bytes[..].copy_from_slice(&decoded[0..32]); + + Ok(IncomingViewingKey { + network: match hrp.as_str() { + ivk_hrp::MAINNET => Network::Mainnet, + _ => Network::Testnet, + }, + scalar: Scalar::from_bytes(&scalar_bytes).unwrap(), + }) + } + _ => Err(SerializationError::Parse("bech32 decoding error")), + } + } +} + +impl PartialEq<[u8; 32]> for IncomingViewingKey { + fn eq(&self, other: &[u8; 32]) -> bool { + self.scalar.to_bytes() == *other + } +} + +/// A _Diversifier_, as described in [protocol specification §4.2.2][ps]. +/// +/// Combined with an _IncomingViewingKey_, produces a _diversified +/// payment address_. +/// +/// [ps]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents +#[derive(Copy, Clone, Eq, PartialEq)] +#[cfg_attr( + any(test, feature = "proptest-impl"), + derive(proptest_derive::Arbitrary) +)] +pub struct Diversifier(pub [u8; 11]); + +impl fmt::Debug for Diversifier { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("Diversifier") + .field(&hex::encode(&self.0)) + .finish() + } +} + +impl From<[u8; 11]> for Diversifier { + fn from(bytes: [u8; 11]) -> Self { + Self(bytes) + } +} + +impl From for [u8; 11] { + fn from(d: Diversifier) -> [u8; 11] { + d.0 + } +} + +impl TryFrom for jubjub::AffinePoint { + type Error = &'static str; + + /// Get a diversified base point from a diversifier value in affine + /// representation. + fn try_from(d: Diversifier) -> Result { + if let Ok(extended_point) = pallas::Point::try_from(d) { + Ok(extended_point.into()) + } else { + Err("Invalid Diversifier -> jubjub::AffinePoint") + } + } +} + +impl TryFrom for pallas::Point { + type Error = &'static str; + + fn try_from(d: Diversifier) -> Result { + let possible_point = diversify_hash(d.0); + + if let Some(point) = possible_point { + Ok(point) + } else { + Err("Invalid Diversifier -> pallas::Point") + } + } +} + +impl From for Diversifier { + /// Derives a [_default diversifier_][4.2.2] from a SpendingKey. + /// + /// 'For each spending key, there is also a default diversified + /// payment address with a “random-looking” diversifier. This + /// allows an implementation that does not expose diversified + /// addresses as a user-visible feature, to use a default address + /// that cannot be distinguished (without knowledge of the + /// spending key) from one with a random diversifier...' + /// + /// [4.2.2]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents + fn from(sk: SpendingKey) -> Diversifier { + let mut i = 0u8; + + loop { + let mut d_bytes = [0u8; 11]; + d_bytes[..].copy_from_slice(&prf_expand(sk.bytes, &[3, i])[..11]); + + if diversify_hash(d_bytes).is_some() { + break Self(d_bytes); + } + + assert!(i < 255); + + i += 1; + } + } +} + +impl PartialEq<[u8; 11]> for Diversifier { + fn eq(&self, other: &[u8; 11]) -> bool { + self.0 == *other + } +} + +impl Diversifier { + /// Generate a new _Diversifier_ that has already been confirmed + /// as a preimage to a valid diversified base point when used to + /// derive a diversified payment address. + /// + /// https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents + /// https://zips.z.cash/protocol/protocol.pdf#concretediversifyhash + pub fn new(csprng: &mut T) -> Self + where + T: RngCore + CryptoRng, + { + loop { + let mut bytes = [0u8; 11]; + csprng.fill_bytes(&mut bytes); + + if diversify_hash(bytes).is_some() { + break Self(bytes); + } + } + } +} + +/// A (diversified) _TransmissionKey_ +/// +/// In Sapling, secrets need to be transmitted to a recipient of funds +/// in order for them to be later spent. To transmit these secrets +/// securely to a recipient without requiring an out-of-band +/// communication channel, the diversified transmission key is used to +/// encrypt them. +/// +/// Derived by multiplying a JubJub point [derived][ps] from a +/// _Diversifier_ by the _IncomingViewingKey_ scalar. +/// +/// [ps]: https://zips.z.cash/protocol/protocol.pdf#concretediversifyhash +#[derive(Copy, Clone, PartialEq)] +pub struct TransmissionKey(pub jubjub::AffinePoint); + +impl fmt::Debug for TransmissionKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("TransmissionKey") + .field("u", &hex::encode(self.0.get_u().to_bytes())) + .field("v", &hex::encode(self.0.get_v().to_bytes())) + .finish() + } +} + +impl Eq for TransmissionKey {} + +impl From<[u8; 32]> for TransmissionKey { + /// Attempts to interpret a byte representation of an + /// affine point, failing if the element is not on + /// the curve or non-canonical. + /// + /// https://github.com/zkcrypto/jubjub/blob/master/src/lib.rs#L411 + fn from(bytes: [u8; 32]) -> Self { + Self(jubjub::AffinePoint::from_bytes(bytes).unwrap()) + } +} + +impl From for [u8; 32] { + fn from(pk_d: TransmissionKey) -> [u8; 32] { + pk_d.0.to_bytes() + } +} + +impl From<(IncomingViewingKey, Diversifier)> for TransmissionKey { + /// This includes _KA^Sapling.DerivePublic(ivk, G_d)_, which is just a + /// scalar mult _\[ivk\]G_d_. + /// + /// https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents + /// https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement + fn from((ivk, d): (IncomingViewingKey, Diversifier)) -> Self { + Self(jubjub::AffinePoint::from( + diversify_hash(d.0).unwrap() * ivk.scalar, + )) + } +} + +impl PartialEq<[u8; 32]> for TransmissionKey { + fn eq(&self, other: &[u8; 32]) -> bool { + <[u8; 32]>::from(*self) == *other + } +} + +/// Magic human-readable strings used to identify what networks +/// Sapling FullViewingKeys are associated with when encoded/decoded +/// with bech32. +mod fvk_hrp { + pub const MAINNET: &str = "zviews"; + pub const TESTNET: &str = "zviewtestsapling"; +} + +/// Full Viewing Keys +/// +/// Allows recognizing both incoming and outgoing notes without having +/// spend authority. +/// +/// For incoming viewing keys on the production network, the +/// Human-Readable Part is “zviews”. For incoming viewing keys on the +/// test network, the Human-Readable Part is “zviewtestsapling”. +/// +/// https://zips.z.cash/protocol/protocol.pdf#saplingfullviewingkeyencoding +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct FullViewingKey { + network: Network, + authorizing_key: AuthorizingKey, + nullifier_deriving_key: NullifierDerivingKey, + outgoing_viewing_key: OutgoingViewingKey, +} + +// TODO: impl a From that accepts a Network? + +impl fmt::Debug for FullViewingKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("FullViewingKey") + .field("network", &self.network) + .field("authorizing_key", &self.authorizing_key) + .field("nullifier_deriving_key", &self.nullifier_deriving_key) + .field("outgoing_viewing_key", &self.outgoing_viewing_key) + .finish() + } +} + +impl fmt::Display for FullViewingKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut bytes = io::Cursor::new(Vec::new()); + + let _ = bytes.write_all(&<[u8; 32]>::from(self.authorizing_key)); + let _ = bytes.write_all(&<[u8; 32]>::from(self.nullifier_deriving_key)); + let _ = bytes.write_all(&<[u8; 32]>::from(self.outgoing_viewing_key)); + + let hrp = match self.network { + Network::Mainnet => fvk_hrp::MAINNET, + _ => fvk_hrp::TESTNET, + }; + + bech32::encode_to_fmt(f, hrp, bytes.get_ref().to_base32(), Variant::Bech32).unwrap() + } +} + +impl FromStr for FullViewingKey { + type Err = SerializationError; + + fn from_str(s: &str) -> Result { + match bech32::decode(s) { + Ok((hrp, bytes, Variant::Bech32)) => { + let mut decoded_bytes = io::Cursor::new(Vec::::from_base32(&bytes).unwrap()); + + let authorizing_key_bytes = decoded_bytes.read_32_bytes()?; + let nullifier_deriving_key_bytes = decoded_bytes.read_32_bytes()?; + let outgoing_key_bytes = decoded_bytes.read_32_bytes()?; + + Ok(FullViewingKey { + network: match hrp.as_str() { + fvk_hrp::MAINNET => Network::Mainnet, + _ => Network::Testnet, + }, + authorizing_key: AuthorizingKey::from(authorizing_key_bytes), + nullifier_deriving_key: NullifierDerivingKey::from( + nullifier_deriving_key_bytes, + ), + outgoing_viewing_key: OutgoingViewingKey::from(outgoing_key_bytes), + }) + } + _ => Err(SerializationError::Parse("bech32 decoding error")), + } + } +} + +/// An ephemeral public key for Sapling key agreement. +/// +/// https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement +#[derive(Copy, Clone, Deserialize, PartialEq, Serialize)] +pub struct EphemeralPublicKey( + #[serde(with = "serde_helpers::AffinePoint")] pub jubjub::AffinePoint, +); + +impl fmt::Debug for EphemeralPublicKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("EphemeralPublicKey") + .field("u", &hex::encode(self.0.get_u().to_bytes())) + .field("v", &hex::encode(self.0.get_v().to_bytes())) + .finish() + } +} + +impl Eq for EphemeralPublicKey {} + +impl From<&EphemeralPublicKey> for [u8; 32] { + fn from(nk: &EphemeralPublicKey) -> [u8; 32] { + nk.0.to_bytes() + } +} + +impl PartialEq<[u8; 32]> for EphemeralPublicKey { + fn eq(&self, other: &[u8; 32]) -> bool { + <[u8; 32]>::from(self) == *other + } +} + +impl TryFrom<[u8; 32]> for EphemeralPublicKey { + type Error = &'static str; + + fn try_from(bytes: [u8; 32]) -> Result { + let possible_point = jubjub::AffinePoint::from_bytes(bytes); + + if possible_point.is_some().into() { + Ok(Self(possible_point.unwrap())) + } else { + Err("Invalid jubjub::AffinePoint value") + } + } +} + +impl ZcashSerialize for EphemeralPublicKey { + fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { + writer.write_all(&<[u8; 32]>::from(self)[..])?; + Ok(()) + } +} + +impl ZcashDeserialize for EphemeralPublicKey { + fn zcash_deserialize(mut reader: R) -> Result { + Self::try_from(reader.read_32_bytes()?).map_err(|e| SerializationError::Parse(e)) + } +} diff --git a/zebra-chain/src/orchard/keys/test_vectors.rs b/zebra-chain/src/orchard/keys/test_vectors.rs new file mode 100644 index 00000000..8e18a8af --- /dev/null +++ b/zebra-chain/src/orchard/keys/test_vectors.rs @@ -0,0 +1,641 @@ +// Generated from https://github.com/zcash-hackworks/zcash-test-vectors/blob/07dc43fd90cd78a0b45b2eb5d2be3ce3c1841603/sapling_key_components.py + +pub struct TestVector { + pub sk: [u8; 32], + pub ask: [u8; 32], + pub nsk: [u8; 32], + pub ovk: [u8; 32], + pub ak: [u8; 32], + pub nk: [u8; 32], + pub ivk: [u8; 32], + pub default_d: [u8; 11], + pub default_pk_d: [u8; 32], + pub note_v: u64, + pub note_r: [u8; 32], + pub note_cmu: [u8; 32], + pub note_pos: u64, + pub note_nf: [u8; 32], +} + +pub const TEST_VECTORS: [TestVector; 10] = [ + TestVector { + sk: [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ], + ask: [ + 0x85, 0x48, 0xa1, 0x4a, 0x47, 0x3e, 0xa5, 0x47, 0xaa, 0x23, 0x78, 0x40, 0x20, 0x44, + 0xf8, 0x18, 0xcf, 0x19, 0x11, 0xcf, 0x5d, 0xd2, 0x05, 0x4f, 0x67, 0x83, 0x45, 0xf0, + 0x0d, 0x0e, 0x88, 0x06, + ], + nsk: [ + 0x30, 0x11, 0x4e, 0xa0, 0xdd, 0x0b, 0xb6, 0x1c, 0xf0, 0xea, 0xea, 0xb6, 0xec, 0x33, + 0x31, 0xf5, 0x81, 0xb0, 0x42, 0x5e, 0x27, 0x33, 0x85, 0x01, 0x26, 0x2d, 0x7e, 0xac, + 0x74, 0x5e, 0x6e, 0x05, + ], + ovk: [ + 0x98, 0xd1, 0x69, 0x13, 0xd9, 0x9b, 0x04, 0x17, 0x7c, 0xab, 0xa4, 0x4f, 0x6e, 0x4d, + 0x22, 0x4e, 0x03, 0xb5, 0xac, 0x03, 0x1d, 0x7c, 0xe4, 0x5e, 0x86, 0x51, 0x38, 0xe1, + 0xb9, 0x96, 0xd6, 0x3b, + ], + ak: [ + 0xf3, 0x44, 0xec, 0x38, 0x0f, 0xe1, 0x27, 0x3e, 0x30, 0x98, 0xc2, 0x58, 0x8c, 0x5d, + 0x3a, 0x79, 0x1f, 0xd7, 0xba, 0x95, 0x80, 0x32, 0x76, 0x07, 0x77, 0xfd, 0x0e, 0xfa, + 0x8e, 0xf1, 0x16, 0x20, + ], + nk: [ + 0xf7, 0xcf, 0x9e, 0x77, 0xf2, 0xe5, 0x86, 0x83, 0x38, 0x3c, 0x15, 0x19, 0xac, 0x7b, + 0x06, 0x2d, 0x30, 0x04, 0x0e, 0x27, 0xa7, 0x25, 0xfb, 0x88, 0xfb, 0x19, 0xa9, 0x78, + 0xbd, 0x3f, 0xd6, 0xba, + ], + ivk: [ + 0xb7, 0x0b, 0x7c, 0xd0, 0xed, 0x03, 0xcb, 0xdf, 0xd7, 0xad, 0xa9, 0x50, 0x2e, 0xe2, + 0x45, 0xb1, 0x3e, 0x56, 0x9d, 0x54, 0xa5, 0x71, 0x9d, 0x2d, 0xaa, 0x0f, 0x5f, 0x14, + 0x51, 0x47, 0x92, 0x04, + ], + default_d: [ + 0xf1, 0x9d, 0x9b, 0x79, 0x7e, 0x39, 0xf3, 0x37, 0x44, 0x58, 0x39, + ], + default_pk_d: [ + 0xdb, 0x4c, 0xd2, 0xb0, 0xaa, 0xc4, 0xf7, 0xeb, 0x8c, 0xa1, 0x31, 0xf1, 0x65, 0x67, + 0xc4, 0x45, 0xa9, 0x55, 0x51, 0x26, 0xd3, 0xc2, 0x9f, 0x14, 0xe3, 0xd7, 0x76, 0xe8, + 0x41, 0xae, 0x74, 0x15, + ], + note_v: 0, + note_r: [ + 0x39, 0x17, 0x6d, 0xac, 0x39, 0xac, 0xe4, 0x98, 0x0e, 0xcc, 0x8d, 0x77, 0x8e, 0x89, + 0x86, 0x02, 0x55, 0xec, 0x36, 0x15, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ], + note_cmu: [ + 0xcb, 0x3c, 0xf9, 0x15, 0x32, 0x70, 0xd5, 0x7e, 0xb9, 0x14, 0xc6, 0xc2, 0xbc, 0xc0, + 0x18, 0x50, 0xc9, 0xfe, 0xd4, 0x4f, 0xce, 0x08, 0x06, 0x27, 0x8f, 0x08, 0x3e, 0xf2, + 0xdd, 0x07, 0x64, 0x39, + ], + note_pos: 0, + note_nf: [ + 0x44, 0xfa, 0xd6, 0x56, 0x4f, 0xfd, 0xec, 0x9f, 0xa1, 0x9c, 0x43, 0xa2, 0x8f, 0x86, + 0x1d, 0x5e, 0xbf, 0x60, 0x23, 0x46, 0x00, 0x7d, 0xe7, 0x62, 0x67, 0xd9, 0x75, 0x27, + 0x47, 0xab, 0x40, 0x63, + ], + }, + TestVector { + sk: [ + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, + ], + ask: [ + 0xc9, 0x43, 0x56, 0x29, 0xbf, 0x8b, 0xff, 0xe5, 0x5e, 0x73, 0x35, 0xec, 0x07, 0x77, + 0x18, 0xba, 0x60, 0xba, 0x28, 0xd7, 0xac, 0x37, 0x94, 0xb7, 0x4f, 0x51, 0x2c, 0x31, + 0xaf, 0x0a, 0x53, 0x04, + ], + nsk: [ + 0x11, 0xac, 0xc2, 0xea, 0xd0, 0x7b, 0x5f, 0x00, 0x8c, 0x1f, 0x0f, 0x09, 0x0c, 0xc8, + 0xdd, 0xf3, 0x35, 0x23, 0x6f, 0xf4, 0xb2, 0x53, 0xc6, 0x49, 0x56, 0x95, 0xe9, 0xd6, + 0x39, 0xda, 0xcd, 0x08, + ], + ovk: [ + 0x3b, 0x94, 0x62, 0x10, 0xce, 0x6d, 0x1b, 0x16, 0x92, 0xd7, 0x39, 0x2a, 0xc8, 0x4a, + 0x8b, 0xc8, 0xf0, 0x3b, 0x72, 0x72, 0x3c, 0x7d, 0x36, 0x72, 0x1b, 0x80, 0x9a, 0x79, + 0xc9, 0xd6, 0xe4, 0x5b, + ], + ak: [ + 0x82, 0xff, 0x5e, 0xff, 0xc5, 0x27, 0xae, 0x84, 0x02, 0x0b, 0xf2, 0xd3, 0x52, 0x01, + 0xc1, 0x02, 0x19, 0x13, 0x19, 0x47, 0xff, 0x4b, 0x96, 0xf8, 0x81, 0xa4, 0x5f, 0x2e, + 0x8a, 0xe3, 0x05, 0x18, + ], + nk: [ + 0xc4, 0x53, 0x4d, 0x84, 0x8b, 0xb9, 0x18, 0xcf, 0x4a, 0x7f, 0x8b, 0x98, 0x74, 0x0a, + 0xb3, 0xcc, 0xee, 0x58, 0x67, 0x95, 0xff, 0x4d, 0xf6, 0x45, 0x47, 0xa8, 0x88, 0x8a, + 0x6c, 0x74, 0x15, 0xd2, + ], + ivk: [ + 0xc5, 0x18, 0x38, 0x44, 0x66, 0xb2, 0x69, 0x88, 0xb5, 0x10, 0x90, 0x67, 0x41, 0x8d, + 0x19, 0x2d, 0x9d, 0x6b, 0xd0, 0xd9, 0x23, 0x22, 0x05, 0xd7, 0x74, 0x18, 0xc2, 0x40, + 0xfc, 0x68, 0xa4, 0x06, + ], + default_d: [ + 0xae, 0xf1, 0x80, 0xf6, 0xe3, 0x4e, 0x35, 0x4b, 0x88, 0x8f, 0x81, + ], + default_pk_d: [ + 0xa6, 0xb1, 0x3e, 0xa3, 0x36, 0xdd, 0xb7, 0xa6, 0x7b, 0xb0, 0x9a, 0x0e, 0x68, 0xe9, + 0xd3, 0xcf, 0xb3, 0x92, 0x10, 0x83, 0x1e, 0xa3, 0xa2, 0x96, 0xba, 0x09, 0xa9, 0x22, + 0x06, 0x0f, 0xd3, 0x8b, + ], + note_v: 12_227_227_834_928_555_328, + note_r: [ + 0x47, 0x8b, 0xa0, 0xee, 0x6e, 0x1a, 0x75, 0xb6, 0x00, 0x03, 0x6f, 0x26, 0xf1, 0x8b, + 0x70, 0x15, 0xab, 0x55, 0x6b, 0xed, 0xdf, 0x8b, 0x96, 0x02, 0x38, 0x86, 0x9f, 0x89, + 0xdd, 0x80, 0x4e, 0x06, + ], + note_cmu: [ + 0xb5, 0x78, 0x93, 0x50, 0x0b, 0xfb, 0x85, 0xdf, 0x2e, 0x8b, 0x01, 0xac, 0x45, 0x2f, + 0x89, 0xe1, 0x0e, 0x26, 0x6b, 0xcf, 0xa3, 0x1c, 0x31, 0xb2, 0x9a, 0x53, 0xae, 0x72, + 0xca, 0xd4, 0x69, 0x50, + ], + note_pos: 763_714_296, + note_nf: [ + 0x67, 0x9e, 0xb0, 0xc3, 0xa7, 0x57, 0xe2, 0xae, 0x83, 0xcd, 0xb4, 0x2a, 0x1a, 0xb2, + 0x59, 0xd7, 0x83, 0x88, 0x31, 0x54, 0x19, 0xad, 0xc7, 0x1d, 0x2e, 0x37, 0x63, 0x17, + 0x4c, 0x2e, 0x9d, 0x93, + ], + }, + TestVector { + sk: [ + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, + ], + ask: [ + 0xee, 0x1c, 0x3d, 0x7e, 0xfe, 0x0a, 0x78, 0x06, 0x3d, 0x6a, 0xf3, 0xd9, 0xd8, 0x12, + 0x12, 0xaf, 0x47, 0xb7, 0xc1, 0xb7, 0x61, 0xf8, 0x5c, 0xcb, 0x06, 0x6f, 0xc1, 0x1a, + 0x6a, 0x42, 0x17, 0x03, + ], + nsk: [ + 0x1d, 0x3b, 0x71, 0x37, 0x55, 0xd7, 0x48, 0x75, 0xe8, 0xea, 0x38, 0xfd, 0x16, 0x6e, + 0x76, 0xc6, 0x2a, 0x42, 0x50, 0x21, 0x6e, 0x6b, 0xbf, 0xe4, 0x8a, 0x5e, 0x2e, 0xab, + 0xad, 0x11, 0x7f, 0x0b, + ], + ovk: [ + 0x8b, 0xf4, 0x39, 0x0e, 0x28, 0xdd, 0xc9, 0x5b, 0x83, 0x02, 0xc3, 0x81, 0xd5, 0x81, + 0x0b, 0x84, 0xba, 0x8e, 0x60, 0x96, 0xe5, 0xa7, 0x68, 0x22, 0x77, 0x4f, 0xd4, 0x9f, + 0x49, 0x1e, 0x8f, 0x49, + ], + ak: [ + 0xab, 0x83, 0x57, 0x4e, 0xb5, 0xde, 0x85, 0x9a, 0x0a, 0xb8, 0x62, 0x9d, 0xec, 0x34, + 0xc7, 0xbe, 0xe8, 0xc3, 0xfc, 0x74, 0xdf, 0xa0, 0xb1, 0x9a, 0x3a, 0x74, 0x68, 0xd1, + 0x5d, 0xca, 0x64, 0xc6, + ], + nk: [ + 0x95, 0xd5, 0x80, 0x53, 0xe0, 0x59, 0x2e, 0x4a, 0x16, 0x9c, 0xc0, 0xb7, 0x92, 0x8a, + 0xaa, 0xc3, 0xde, 0x24, 0xef, 0x15, 0x31, 0xaa, 0x9e, 0xb6, 0xf4, 0xab, 0x93, 0x91, + 0x4d, 0xa8, 0xa0, 0x6e, + ], + ivk: [ + 0x47, 0x1c, 0x24, 0xa3, 0xdc, 0x87, 0x30, 0xe7, 0x50, 0x36, 0xc0, 0xa9, 0x5f, 0x3e, + 0x2f, 0x7d, 0xd1, 0xbe, 0x6f, 0xb9, 0x3a, 0xd2, 0x95, 0x92, 0x20, 0x3d, 0xef, 0x30, + 0x41, 0x95, 0x45, 0x05, + ], + default_d: [ + 0x75, 0x99, 0xf0, 0xbf, 0x9b, 0x57, 0xcd, 0x2d, 0xc2, 0x99, 0xb6, + ], + default_pk_d: [ + 0x66, 0x14, 0x17, 0x39, 0x51, 0x4b, 0x28, 0xf0, 0x5d, 0xef, 0x8a, 0x18, 0xee, 0xee, + 0x5e, 0xed, 0x4d, 0x44, 0xc6, 0x22, 0x5c, 0x3c, 0x65, 0xd8, 0x8d, 0xd9, 0x90, 0x77, + 0x08, 0x01, 0x2f, 0x5a, + ], + note_v: 6_007_711_596_147_559_040, + note_r: [ + 0x14, 0x7c, 0xf2, 0xb5, 0x1b, 0x4c, 0x7c, 0x63, 0xcb, 0x77, 0xb9, 0x9e, 0x8b, 0x78, + 0x3e, 0x5b, 0x51, 0x11, 0xdb, 0x0a, 0x7c, 0xa0, 0x4d, 0x6c, 0x01, 0x4a, 0x1d, 0x7d, + 0xa8, 0x3b, 0xae, 0x0a, + ], + note_cmu: [ + 0xdb, 0x85, 0xa7, 0x0a, 0x98, 0x43, 0x7f, 0x73, 0x16, 0x7f, 0xc3, 0x32, 0xd5, 0xb7, + 0xb7, 0x40, 0x82, 0x96, 0x66, 0x17, 0x70, 0xb1, 0x01, 0xb0, 0xaa, 0x87, 0x83, 0x9f, + 0x4e, 0x55, 0xf1, 0x51, + ], + note_pos: 1_527_428_592, + note_nf: [ + 0xe9, 0x8f, 0x6a, 0x8f, 0x34, 0xff, 0x49, 0x80, 0x59, 0xb3, 0xc7, 0x31, 0xb9, 0x1f, + 0x45, 0x11, 0x08, 0xc4, 0x95, 0x4d, 0x91, 0x94, 0x84, 0x36, 0x1c, 0xf9, 0xb4, 0x8f, + 0x59, 0xae, 0x1d, 0x14, + ], + }, + TestVector { + sk: [ + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, + ], + ask: [ + 0x00, 0xc3, 0xa1, 0xe1, 0xca, 0x8f, 0x4e, 0x04, 0x80, 0xee, 0x1e, 0xe9, 0x0c, 0xa7, + 0x51, 0x78, 0x79, 0xd3, 0xfc, 0x5c, 0x81, 0x5c, 0x09, 0x03, 0xe5, 0xee, 0xbc, 0x94, + 0xbb, 0x80, 0x95, 0x03, + ], + nsk: [ + 0xe6, 0x62, 0x85, 0xa5, 0xe9, 0xb6, 0x5e, 0x15, 0x7a, 0xd2, 0xfc, 0xd5, 0x43, 0xda, + 0xd9, 0x8c, 0x67, 0xa5, 0x8a, 0xbd, 0xf2, 0x87, 0xe0, 0x55, 0x06, 0xbd, 0x1c, 0x2e, + 0x59, 0xb0, 0x72, 0x0b, + ], + ovk: [ + 0x14, 0x76, 0x78, 0xe0, 0x55, 0x3b, 0x97, 0x82, 0x93, 0x47, 0x64, 0x7c, 0x5b, 0xc7, + 0xda, 0xb4, 0xcc, 0x22, 0x02, 0xb5, 0x4e, 0xc2, 0x9f, 0xd3, 0x1a, 0x3d, 0xe6, 0xbe, + 0x08, 0x25, 0xfc, 0x5e, + ], + ak: [ + 0x3c, 0x9c, 0xde, 0x7e, 0x5d, 0x0d, 0x38, 0xa8, 0x61, 0x0f, 0xaa, 0xdb, 0xcf, 0x4c, + 0x34, 0x3f, 0x5d, 0x3c, 0xfa, 0x31, 0x55, 0xa5, 0xb9, 0x46, 0x61, 0xa6, 0x75, 0x3e, + 0x96, 0xe8, 0x84, 0xea, + ], + nk: [ + 0xb7, 0x7d, 0x36, 0xf5, 0x08, 0x94, 0x1d, 0xbd, 0x61, 0xcf, 0xd0, 0xf1, 0x59, 0xee, + 0x05, 0xcf, 0xaa, 0x78, 0xa2, 0x6c, 0x94, 0x92, 0x90, 0x38, 0x06, 0xd8, 0x3b, 0x59, + 0x8d, 0x3c, 0x1c, 0x2a, + ], + ivk: [ + 0x63, 0x6a, 0xa9, 0x64, 0xbf, 0xc2, 0x3c, 0xe4, 0xb1, 0xfc, 0xf7, 0xdf, 0xc9, 0x91, + 0x79, 0xdd, 0xc4, 0x06, 0xff, 0x55, 0x40, 0x0c, 0x92, 0x95, 0xac, 0xfc, 0x14, 0xf0, + 0x31, 0xc7, 0x26, 0x00, + ], + default_d: [ + 0x1b, 0x81, 0x61, 0x4f, 0x1d, 0xad, 0xea, 0x0f, 0x8d, 0x0a, 0x58, + ], + default_pk_d: [ + 0x25, 0xeb, 0x55, 0xfc, 0xcf, 0x76, 0x1f, 0xc6, 0x4e, 0x85, 0xa5, 0x88, 0xef, 0xe6, + 0xea, 0xd7, 0x83, 0x2f, 0xb1, 0xf0, 0xf7, 0xa8, 0x31, 0x65, 0x89, 0x5b, 0xdf, 0xf9, + 0x42, 0x92, 0x5f, 0x5c, + ], + note_v: 18_234_939_431_076_114_368, + note_r: [ + 0x34, 0xa4, 0xb2, 0xa9, 0x14, 0x4f, 0xf5, 0xea, 0x54, 0xef, 0xee, 0x87, 0xcf, 0x90, + 0x1b, 0x5b, 0xed, 0x5e, 0x35, 0xd2, 0x1f, 0xbb, 0xd7, 0x88, 0xd5, 0xbd, 0x9d, 0x83, + 0x3e, 0x11, 0x28, 0x04, + ], + note_cmu: [ + 0xe0, 0x8c, 0xe4, 0x82, 0xb3, 0xa8, 0xfb, 0x3b, 0x35, 0xcc, 0xdb, 0xe3, 0x43, 0x37, + 0xbd, 0x10, 0x5d, 0x88, 0x39, 0x21, 0x2e, 0x0d, 0x16, 0x44, 0xb9, 0xd5, 0x5c, 0xaa, + 0x60, 0xd1, 0x9b, 0x6c, + ], + note_pos: 2_291_142_888, + note_nf: [ + 0x55, 0x47, 0xaa, 0x12, 0xff, 0x80, 0xa6, 0xb3, 0x30, 0x4e, 0x3b, 0x05, 0x86, 0x56, + 0x47, 0x2a, 0xbd, 0x2c, 0x81, 0x83, 0xb5, 0x9d, 0x07, 0x37, 0xb9, 0x3c, 0xee, 0x75, + 0x8b, 0xec, 0x47, 0xa1, + ], + }, + TestVector { + sk: [ + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, + ], + ask: [ + 0x82, 0x36, 0xd1, 0x9d, 0x32, 0x05, 0xd8, 0x55, 0x43, 0xa0, 0x68, 0x11, 0x34, 0x3f, + 0x82, 0x7b, 0x65, 0x63, 0x77, 0x0a, 0x49, 0xaa, 0x4d, 0x0c, 0xa0, 0x08, 0x18, 0x05, + 0xd4, 0xc8, 0xea, 0x0d, + ], + nsk: [ + 0x7e, 0xc1, 0xef, 0x0b, 0xed, 0x82, 0x71, 0x82, 0x72, 0xf0, 0xf4, 0x4f, 0x01, 0x7c, + 0x48, 0x41, 0x74, 0x51, 0x3d, 0x66, 0x1d, 0xd1, 0x68, 0xaf, 0x02, 0xd2, 0x09, 0x2a, + 0x1d, 0x8a, 0x05, 0x07, + ], + ovk: [ + 0x1b, 0x6e, 0x75, 0xec, 0xe3, 0xac, 0xe8, 0xdb, 0xa6, 0xa5, 0x41, 0x0d, 0x9a, 0xd4, + 0x75, 0x56, 0x68, 0xe4, 0xb3, 0x95, 0x85, 0xd6, 0x35, 0xec, 0x1d, 0xa7, 0xc8, 0xdc, + 0xfd, 0x5f, 0xc4, 0xed, + ], + ak: [ + 0x55, 0xe8, 0x83, 0x89, 0xbb, 0x7e, 0x41, 0xde, 0x13, 0x0c, 0xfa, 0x51, 0xa8, 0x71, + 0x5f, 0xde, 0x01, 0xff, 0x9c, 0x68, 0x76, 0x64, 0x7f, 0x01, 0x75, 0xad, 0x34, 0xf0, + 0x58, 0xdd, 0xe0, 0x1a, + ], + nk: [ + 0x72, 0x5d, 0x4a, 0xd6, 0xa1, 0x50, 0x21, 0xcd, 0x1c, 0x48, 0xc5, 0xee, 0x19, 0xde, + 0x6c, 0x1e, 0x76, 0x8a, 0x2c, 0xc0, 0xa9, 0xa7, 0x30, 0xa0, 0x1b, 0xb2, 0x1c, 0x95, + 0xe3, 0xd9, 0xe4, 0x3c, + ], + ivk: [ + 0x67, 0xfa, 0x2b, 0xf7, 0xc6, 0x7d, 0x46, 0x58, 0x24, 0x3c, 0x31, 0x7c, 0x0c, 0xb4, + 0x1f, 0xd3, 0x20, 0x64, 0xdf, 0xd3, 0x70, 0x9f, 0xe0, 0xdc, 0xb7, 0x24, 0xf1, 0x4b, + 0xb0, 0x1a, 0x1d, 0x04, + ], + default_d: [ + 0xfc, 0xfb, 0x68, 0xa4, 0x0d, 0x4b, 0xc6, 0xa0, 0x4b, 0x09, 0xc4, + ], + default_pk_d: [ + 0x8b, 0x2a, 0x33, 0x7f, 0x03, 0x62, 0x2c, 0x24, 0xff, 0x38, 0x1d, 0x4c, 0x54, 0x6f, + 0x69, 0x77, 0xf9, 0x05, 0x22, 0xe9, 0x2f, 0xde, 0x44, 0xc9, 0xd1, 0xbb, 0x09, 0x97, + 0x14, 0xb9, 0xdb, 0x2b, + ], + note_v: 12_015_423_192_295_118_080, + note_r: [ + 0xe5, 0x57, 0x85, 0x13, 0x55, 0x74, 0x7c, 0x09, 0xac, 0x59, 0x01, 0x3c, 0xbd, 0xe8, + 0x59, 0x80, 0x96, 0x4e, 0xc1, 0x84, 0x4d, 0x9c, 0x69, 0x67, 0xca, 0x0c, 0x02, 0x9c, + 0x84, 0x57, 0xbb, 0x04, + ], + note_cmu: [ + 0xbd, 0xc8, 0x54, 0xbf, 0x3e, 0x7b, 0x00, 0x82, 0x1f, 0x3b, 0x8b, 0x85, 0x23, 0x8c, + 0xcf, 0x1e, 0x67, 0x15, 0xbf, 0xe7, 0x0b, 0x63, 0x2d, 0x04, 0x4b, 0x26, 0xfb, 0x2b, + 0xc7, 0x1b, 0x7f, 0x36, + ], + note_pos: 3_054_857_184, + note_nf: [ + 0x8a, 0x9a, 0xbd, 0xa3, 0xd4, 0xef, 0x85, 0xca, 0xf2, 0x2b, 0xfa, 0xf2, 0xc4, 0x8f, + 0x62, 0x38, 0x2a, 0x73, 0xa1, 0x62, 0x4e, 0xb8, 0xeb, 0x2b, 0xd0, 0x0d, 0x27, 0x03, + 0x01, 0xbf, 0x3d, 0x13, + ], + }, + TestVector { + sk: [ + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, + ], + ask: [ + 0xea, 0xe6, 0x88, 0x4d, 0x76, 0x4a, 0x05, 0x40, 0x61, 0xa8, 0xf1, 0xc0, 0x07, 0x6c, + 0x62, 0x4d, 0xcb, 0x73, 0x87, 0x89, 0xf7, 0xad, 0x1e, 0x74, 0x08, 0xe3, 0x1f, 0x24, + 0xdf, 0xc8, 0x26, 0x07, + ], + nsk: [ + 0xfb, 0xe6, 0x10, 0xf4, 0x2a, 0x41, 0x74, 0x9f, 0x9b, 0x6e, 0x6e, 0x4a, 0x54, 0xb5, + 0xa3, 0x2e, 0xbf, 0xe8, 0xf4, 0x38, 0x00, 0x88, 0x1b, 0xa6, 0xcd, 0x13, 0xed, 0x0b, + 0x05, 0x29, 0x46, 0x01, + ], + ovk: [ + 0xc6, 0xbc, 0x1f, 0x39, 0xf0, 0xd7, 0x86, 0x31, 0x4c, 0xb2, 0x0b, 0xf9, 0xab, 0x22, + 0x85, 0x40, 0x91, 0x35, 0x55, 0xf9, 0x70, 0x69, 0x6b, 0x6d, 0x7c, 0x77, 0xbb, 0x33, + 0x23, 0x28, 0x37, 0x2a, + ], + ak: [ + 0xe6, 0x82, 0x76, 0x59, 0x14, 0xe3, 0x86, 0x4c, 0x33, 0x9e, 0x57, 0x82, 0xb8, 0x55, + 0xc0, 0xfd, 0xf4, 0x0e, 0x0d, 0xfc, 0xed, 0xb9, 0xe7, 0xb4, 0x7b, 0xc9, 0x4b, 0x90, + 0xb3, 0xa4, 0xc9, 0x88, + ], + nk: [ + 0x82, 0x25, 0x6b, 0x95, 0x62, 0x3c, 0x67, 0x02, 0x4b, 0x44, 0x24, 0xd9, 0x14, 0x00, + 0xa3, 0x70, 0xe7, 0xac, 0x8e, 0x4d, 0x15, 0x48, 0x2a, 0x37, 0x59, 0xe0, 0x0d, 0x21, + 0x97, 0x49, 0xda, 0xee, + ], + ivk: [ + 0xea, 0x3f, 0x1d, 0x80, 0xe4, 0x30, 0x7c, 0xa7, 0x3b, 0x9f, 0x37, 0x80, 0x1f, 0x91, + 0xfb, 0xa8, 0x10, 0xcc, 0x41, 0xd2, 0x79, 0xfc, 0x29, 0xf5, 0x64, 0x23, 0x56, 0x54, + 0xa2, 0x17, 0x8e, 0x03, + ], + default_d: [ + 0xeb, 0x51, 0x98, 0x82, 0xad, 0x1e, 0x5c, 0xc6, 0x54, 0xcd, 0x59, + ], + default_pk_d: [ + 0x6b, 0x27, 0xda, 0xcc, 0xb5, 0xa8, 0x20, 0x7f, 0x53, 0x2d, 0x10, 0xca, 0x23, 0x8f, + 0x97, 0x86, 0x64, 0x8a, 0x11, 0xb5, 0x96, 0x6e, 0x51, 0xa2, 0xf7, 0xd8, 0x9e, 0x15, + 0xd2, 0x9b, 0x8f, 0xdf, + ], + note_v: 5_795_906_953_514_121_792, + note_r: [ + 0x68, 0xf0, 0x61, 0x04, 0x60, 0x6b, 0x0c, 0x54, 0x49, 0x84, 0x5f, 0xf4, 0xc6, 0x5f, + 0x73, 0xe9, 0x0f, 0x45, 0xef, 0x5a, 0x43, 0xc9, 0xd7, 0x4c, 0xb2, 0xc8, 0x5c, 0xf5, + 0x6c, 0x94, 0xc0, 0x02, + ], + note_cmu: [ + 0xe8, 0x26, 0x7d, 0x30, 0xac, 0x11, 0xc1, 0x00, 0xbc, 0x7a, 0x0f, 0xdf, 0x91, 0xf7, + 0x1d, 0x74, 0xc5, 0xbc, 0xf2, 0xe1, 0xef, 0x95, 0x66, 0x90, 0x44, 0x73, 0x01, 0x69, + 0xde, 0x1a, 0x5b, 0x4c, + ], + note_pos: 3_818_571_480, + note_nf: [ + 0x33, 0x2a, 0xd9, 0x9e, 0xb9, 0xe9, 0x77, 0xeb, 0x62, 0x7a, 0x12, 0x2d, 0xbf, 0xb2, + 0xf2, 0x5f, 0xe5, 0x88, 0xe5, 0x97, 0x75, 0x3e, 0xc5, 0x58, 0x0f, 0xf2, 0xbe, 0x20, + 0xb6, 0xc9, 0xa7, 0xe1, + ], + }, + TestVector { + sk: [ + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, + ], + ask: [ + 0xe8, 0xf8, 0x16, 0xb4, 0xbc, 0x08, 0xa7, 0xe5, 0x66, 0x75, 0x0c, 0xc2, 0x8a, 0xfe, + 0x82, 0xa4, 0xce, 0xa9, 0xc2, 0xbe, 0xf2, 0x44, 0xfa, 0x4b, 0x13, 0xc4, 0x73, 0x9b, + 0x28, 0x07, 0x4c, 0x0d, + ], + nsk: [ + 0x32, 0x61, 0x5b, 0x13, 0x7f, 0x28, 0x01, 0xed, 0x44, 0x6e, 0x48, 0x78, 0x1a, 0xb0, + 0x63, 0x45, 0x72, 0xe1, 0x8c, 0xfb, 0x06, 0x93, 0x72, 0x1b, 0x88, 0x03, 0xc0, 0x5b, + 0x82, 0x27, 0xd1, 0x07, + ], + ovk: [ + 0xf6, 0x2c, 0x05, 0xe8, 0x48, 0xa8, 0x73, 0xef, 0x88, 0x5e, 0x12, 0xb0, 0x8c, 0x5e, + 0x7c, 0xa2, 0xf3, 0x24, 0x24, 0xba, 0xcc, 0x75, 0x4c, 0xb6, 0x97, 0x50, 0x44, 0x4d, + 0x35, 0x5f, 0x51, 0x06, + ], + ak: [ + 0xff, 0x27, 0xdb, 0x07, 0x51, 0x94, 0x5d, 0x3e, 0xe4, 0xbe, 0x9c, 0xf1, 0x5c, 0x2e, + 0xa2, 0x11, 0xb2, 0x4b, 0x16, 0x4d, 0x5f, 0x2d, 0x7d, 0xdf, 0xf5, 0xe4, 0xa0, 0x70, + 0x8f, 0x10, 0xb9, 0x5e, + ], + nk: [ + 0x94, 0x38, 0x85, 0x95, 0x9d, 0x4e, 0xf8, 0xa9, 0xcf, 0xca, 0x07, 0xc4, 0x57, 0xf0, + 0x9e, 0xc7, 0x4b, 0x96, 0xf9, 0x93, 0xd8, 0xe0, 0xfa, 0x32, 0xb1, 0x9c, 0x03, 0xe3, + 0xb0, 0x7a, 0x42, 0x0f, + ], + ivk: [ + 0xb5, 0xc5, 0x89, 0x49, 0x43, 0x95, 0x69, 0x33, 0xc0, 0xe5, 0xc1, 0x2d, 0x31, 0x1f, + 0xc1, 0x2c, 0xba, 0x58, 0x35, 0x4b, 0x5c, 0x38, 0x9e, 0xdc, 0x03, 0xda, 0x55, 0x08, + 0x4f, 0x74, 0xc2, 0x05, + ], + default_d: [ + 0xbe, 0xbb, 0x0f, 0xb4, 0x6b, 0x8a, 0xaf, 0xf8, 0x90, 0x40, 0xf6, + ], + default_pk_d: [ + 0xd1, 0x1d, 0xa0, 0x1f, 0x0b, 0x43, 0xbd, 0xd5, 0x28, 0x8d, 0x32, 0x38, 0x5b, 0x87, + 0x71, 0xd2, 0x23, 0x49, 0x3c, 0x69, 0x80, 0x25, 0x44, 0x04, 0x3f, 0x77, 0xcf, 0x1d, + 0x71, 0xc1, 0xcb, 0x8c, + ], + note_v: 18_023_134_788_442_677_120, + note_r: [ + 0x49, 0xf9, 0x0b, 0x47, 0xfd, 0x52, 0xfe, 0xe7, 0xc1, 0xc8, 0x1f, 0x0d, 0xcb, 0x5b, + 0x74, 0xc3, 0xfb, 0x9b, 0x3e, 0x03, 0x97, 0x6f, 0x8b, 0x75, 0x24, 0xea, 0xba, 0xd0, + 0x08, 0x89, 0x21, 0x07, + ], + note_cmu: [ + 0x57, 0x2b, 0xa2, 0x05, 0x25, 0xb0, 0xac, 0x4d, 0x6d, 0xc0, 0x1a, 0xc2, 0xea, 0x10, + 0x90, 0xb6, 0xe0, 0xf2, 0xf4, 0xbf, 0x4e, 0xc4, 0xa0, 0xdb, 0x5b, 0xbc, 0xcb, 0x5b, + 0x78, 0x3a, 0x1e, 0x55, + ], + note_pos: 287_318_480, + note_nf: [ + 0xfc, 0x74, 0xcd, 0x0e, 0x4b, 0xe0, 0x49, 0x57, 0xb1, 0x96, 0xcf, 0x87, 0x34, 0xae, + 0x99, 0x23, 0x96, 0xaf, 0x4c, 0xfa, 0x8f, 0xec, 0xbb, 0x86, 0xf9, 0x61, 0xe6, 0xb4, + 0x07, 0xd5, 0x1e, 0x11, + ], + }, + TestVector { + sk: [ + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, + ], + ask: [ + 0x74, 0xb4, 0x4a, 0x37, 0xf1, 0x50, 0x23, 0xc0, 0x60, 0x42, 0x7e, 0x1d, 0xae, 0xa3, + 0xf6, 0x43, 0x12, 0xdd, 0x8f, 0xeb, 0x7b, 0x2c, 0xed, 0xf0, 0xdd, 0x55, 0x44, 0x49, + 0x3f, 0x87, 0x2c, 0x06, + ], + nsk: [ + 0x07, 0x5c, 0x35, 0xdb, 0x8b, 0x1b, 0x25, 0x75, 0x42, 0x23, 0xec, 0xee, 0x34, 0xab, + 0x73, 0x0d, 0xdd, 0xd1, 0xf1, 0x4a, 0x6a, 0x54, 0xf4, 0xc6, 0xf4, 0x68, 0x45, 0x3c, + 0x3c, 0x6e, 0xd6, 0x0b, + ], + ovk: [ + 0xe9, 0xe0, 0xdc, 0x1e, 0xd3, 0x11, 0xda, 0xed, 0x64, 0xbd, 0x74, 0xda, 0x5d, 0x94, + 0xfe, 0x88, 0xa6, 0xea, 0x41, 0x4b, 0x73, 0x12, 0xde, 0x3d, 0x2a, 0x78, 0xf6, 0x46, + 0x32, 0xbb, 0xe3, 0x73, + ], + ak: [ + 0x28, 0x3f, 0x9a, 0xaf, 0xa9, 0xbc, 0xb3, 0xe6, 0xce, 0x17, 0xe6, 0x32, 0x12, 0x63, + 0x4c, 0xb3, 0xee, 0x55, 0x0c, 0x47, 0x6b, 0x67, 0x6b, 0xd3, 0x56, 0xa6, 0xdf, 0x8a, + 0xdf, 0x51, 0xd2, 0x5e, + ], + nk: [ + 0xdc, 0x4c, 0x67, 0xb1, 0x0d, 0x4b, 0x0a, 0x21, 0x8d, 0xc6, 0xe1, 0x48, 0x70, 0x66, + 0x74, 0x0a, 0x40, 0x93, 0x17, 0x86, 0x6c, 0x32, 0xe6, 0x64, 0xb5, 0x0e, 0x39, 0x7a, + 0xa8, 0x03, 0x89, 0xd4, + ], + ivk: [ + 0x87, 0x16, 0xc8, 0x28, 0x80, 0xe1, 0x36, 0x83, 0xe1, 0xbb, 0x05, 0x9d, 0xd0, 0x6c, + 0x80, 0xc9, 0x01, 0x34, 0xa9, 0x6d, 0x5a, 0xfc, 0xa8, 0xaa, 0xc2, 0xbb, 0xf6, 0x8b, + 0xb0, 0x5f, 0x84, 0x02, + ], + default_d: [ + 0xad, 0x6e, 0x2e, 0x18, 0x5a, 0x31, 0x00, 0xe3, 0xa6, 0xa8, 0xb3, + ], + default_pk_d: [ + 0x32, 0xcb, 0x28, 0x06, 0xb8, 0x82, 0xf1, 0x36, 0x8b, 0x0d, 0x4a, 0x89, 0x8f, 0x72, + 0xc4, 0xc8, 0xf7, 0x28, 0x13, 0x2c, 0xc1, 0x24, 0x56, 0x94, 0x6e, 0x7f, 0x4c, 0xb0, + 0xfb, 0x05, 0x8d, 0xa9, + ], + note_v: 11_803_618_549_661_680_832, + note_r: [ + 0x51, 0x65, 0xaf, 0xf2, 0x2d, 0xd4, 0xed, 0x56, 0xb4, 0xd8, 0x1d, 0x1f, 0x17, 0x1c, + 0xc3, 0xd6, 0x43, 0x2f, 0xed, 0x1b, 0xeb, 0xf2, 0x0a, 0x7b, 0xea, 0xb1, 0x2d, 0xb1, + 0x42, 0xf9, 0x4a, 0x0c, + ], + note_cmu: [ + 0xab, 0x7f, 0xc5, 0x66, 0x87, 0x3c, 0xcd, 0xe6, 0x71, 0xf5, 0x98, 0x27, 0x67, 0x85, + 0x60, 0xa0, 0x06, 0xf8, 0x2b, 0xb7, 0xad, 0xcd, 0x75, 0x22, 0x3f, 0xa8, 0x59, 0x36, + 0xf7, 0x8c, 0x2b, 0x23, + ], + note_pos: 1_051_032_776, + note_nf: [ + 0xd2, 0xe8, 0x87, 0xbd, 0x85, 0x4a, 0x80, 0x2b, 0xce, 0x85, 0x70, 0x53, 0x02, 0x0f, + 0x5d, 0x3e, 0x7c, 0x8a, 0xe5, 0x26, 0x7c, 0x5b, 0x65, 0x83, 0xb3, 0xd2, 0x12, 0xcc, + 0x8b, 0xb6, 0x98, 0x90, + ], + }, + TestVector { + sk: [ + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, + ], + ask: [ + 0x03, 0x9d, 0xd9, 0x3d, 0xf3, 0x11, 0xff, 0x8f, 0xba, 0xb3, 0xfe, 0x23, 0x02, 0x19, + 0xcd, 0x42, 0xac, 0x87, 0x94, 0x84, 0xf3, 0x0b, 0x90, 0x3a, 0x3c, 0x1e, 0x67, 0xcc, + 0xca, 0x5a, 0x7b, 0x0d, + ], + nsk: [ + 0x04, 0x9f, 0xa1, 0x4f, 0x48, 0x6c, 0x75, 0xb9, 0xfa, 0xd7, 0xe3, 0xb6, 0x73, 0xa4, + 0x43, 0xdd, 0x07, 0x4e, 0xaa, 0x96, 0xed, 0xcb, 0x2a, 0x53, 0xea, 0xaa, 0xbd, 0xaf, + 0x70, 0xff, 0xbb, 0x08, + ], + ovk: [ + 0x14, 0x7d, 0xd1, 0x1d, 0x77, 0xeb, 0xa1, 0xb1, 0x63, 0x6f, 0xd6, 0x19, 0x0c, 0x62, + 0xb9, 0xa5, 0xd0, 0x48, 0x1b, 0xee, 0x7e, 0x91, 0x7f, 0xab, 0x02, 0xe2, 0x18, 0x58, + 0x06, 0x3a, 0xb5, 0x04, + ], + ak: [ + 0x36, 0x40, 0x48, 0xee, 0xdb, 0xe8, 0xca, 0x20, 0x5e, 0xb7, 0xe7, 0xba, 0x0a, 0x90, + 0x12, 0x16, 0x6c, 0x7c, 0x7b, 0xd9, 0xeb, 0x22, 0x8e, 0x08, 0x48, 0x14, 0x48, 0xc4, + 0x88, 0xaa, 0x21, 0xd2, + ], + nk: [ + 0xed, 0x60, 0xaf, 0x1c, 0xe7, 0xdf, 0x38, 0x07, 0x0d, 0x38, 0x51, 0x43, 0x2a, 0x96, + 0x48, 0x0d, 0xb0, 0xb4, 0x17, 0xc3, 0x68, 0x2a, 0x1d, 0x68, 0xe3, 0xe8, 0x93, 0x34, + 0x23, 0x5c, 0x0b, 0xdf, + ], + ivk: [ + 0x99, 0xc9, 0xb4, 0xb8, 0x4f, 0x4b, 0x4e, 0x35, 0x0f, 0x78, 0x7d, 0x1c, 0xf7, 0x05, + 0x1d, 0x50, 0xec, 0xc3, 0x4b, 0x1a, 0x5b, 0x20, 0xd2, 0xd2, 0x13, 0x9b, 0x4a, 0xf1, + 0xf1, 0x60, 0xe0, 0x01, + ], + default_d: [ + 0x21, 0xc9, 0x0e, 0x1c, 0x65, 0x8b, 0x3e, 0xfe, 0x86, 0xaf, 0x58, + ], + default_pk_d: [ + 0x9e, 0x64, 0x17, 0x4b, 0x4a, 0xb9, 0x81, 0x40, 0x5c, 0x32, 0x3b, 0x5e, 0x12, 0x47, + 0x59, 0x45, 0xa4, 0x6d, 0x4f, 0xed, 0xf8, 0x06, 0x08, 0x28, 0x04, 0x1c, 0xd2, 0x0e, + 0x62, 0xfd, 0x2c, 0xef, + ], + note_v: 5_584_102_310_880_684_544, + note_r: [ + 0x8c, 0x3e, 0x56, 0x44, 0x9d, 0xc8, 0x63, 0x54, 0xd3, 0x3b, 0x02, 0x5e, 0xf2, 0x79, + 0x34, 0x60, 0xbc, 0xb1, 0x69, 0xf3, 0x32, 0x4e, 0x4a, 0x6b, 0x64, 0xba, 0xa6, 0x08, + 0x32, 0x31, 0x57, 0x04, + ], + note_cmu: [ + 0x7b, 0x48, 0xa8, 0x37, 0x5d, 0x3e, 0xbd, 0x56, 0xbc, 0x64, 0x9b, 0xb5, 0xb5, 0x24, + 0x23, 0x36, 0xc2, 0xa0, 0x5a, 0x08, 0x03, 0x23, 0x9b, 0x5b, 0x88, 0xfd, 0x92, 0x07, + 0x8f, 0xea, 0x4d, 0x04, + ], + note_pos: 1_814_747_072, + note_nf: [ + 0xa8, 0x2f, 0x17, 0x50, 0xcc, 0x5b, 0x2b, 0xee, 0x64, 0x9a, 0x36, 0x5c, 0x04, 0x20, + 0xed, 0x87, 0x07, 0x5b, 0x88, 0x71, 0xfd, 0xa4, 0xa7, 0xf5, 0x84, 0x0d, 0x6b, 0xbe, + 0xb1, 0x7c, 0xd6, 0x20, + ], + }, + TestVector { + sk: [ + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, + ], + ask: [ + 0xeb, 0xbb, 0x40, 0xa9, 0x80, 0xba, 0x3b, 0x88, 0x60, 0x94, 0x8d, 0x01, 0x1e, 0x1b, + 0xfb, 0x4a, 0xff, 0xe1, 0x6c, 0x65, 0x2e, 0x90, 0xe9, 0x82, 0x58, 0x30, 0x2f, 0x44, + 0x64, 0xc9, 0x1e, 0x0c, + ], + nsk: [ + 0x68, 0x43, 0x1b, 0x19, 0x91, 0x04, 0x21, 0x52, 0x00, 0xb9, 0x5e, 0xe5, 0xcb, 0x71, + 0xbf, 0x8b, 0x88, 0x3a, 0x3e, 0x95, 0xb7, 0x98, 0x9c, 0xad, 0x19, 0x70, 0x63, 0x14, + 0x1e, 0xbb, 0xfd, 0x00, + ], + ovk: [ + 0x57, 0x34, 0x67, 0xa7, 0xb3, 0x0e, 0xad, 0x6c, 0xcc, 0x50, 0x47, 0x44, 0xca, 0x9e, + 0x1a, 0x28, 0x1a, 0x0d, 0x1a, 0x08, 0x73, 0x8b, 0x06, 0xa0, 0x68, 0x4f, 0xea, 0xcd, + 0x1e, 0x9d, 0x12, 0x6d, + ], + ak: [ + 0x71, 0xc3, 0x52, 0x3e, 0xec, 0xa3, 0x53, 0x11, 0xfb, 0xd5, 0xd7, 0xe7, 0xd7, 0x0b, + 0x70, 0x9d, 0x6c, 0x35, 0xa2, 0x4f, 0x26, 0x2b, 0x34, 0xbf, 0x64, 0x05, 0x9b, 0xf2, + 0xc0, 0x2e, 0x0b, 0xa8, + ], + nk: [ + 0x62, 0x44, 0x00, 0x10, 0x3b, 0x65, 0x69, 0xb7, 0x35, 0x8f, 0xe8, 0x0f, 0x6f, 0x6c, + 0xad, 0x43, 0x25, 0xde, 0xfd, 0xa9, 0xd9, 0x49, 0x9c, 0x2b, 0x8f, 0x88, 0x6a, 0x62, + 0x69, 0xa2, 0xaa, 0x52, + ], + ivk: [ + 0xdb, 0x95, 0xea, 0x8b, 0xd9, 0xf9, 0x3d, 0x41, 0xb5, 0xab, 0x2b, 0xeb, 0xc9, 0x1a, + 0x38, 0xed, 0xd5, 0x27, 0x08, 0x3e, 0x2a, 0x6e, 0xf9, 0xf3, 0xc2, 0x97, 0x02, 0xd5, + 0xff, 0x89, 0xed, 0x00, + ], + default_d: [ + 0x23, 0x3c, 0x4a, 0xb8, 0x86, 0xa5, 0x5e, 0x3b, 0xa3, 0x74, 0xc0, + ], + default_pk_d: [ + 0xb6, 0x8e, 0x9e, 0xe0, 0xc0, 0x67, 0x8d, 0x7b, 0x30, 0x36, 0x93, 0x1c, 0x83, 0x1a, + 0x25, 0x25, 0x5f, 0x7e, 0xe4, 0x87, 0x38, 0x5a, 0x30, 0x31, 0x6e, 0x15, 0xf6, 0x48, + 0x2b, 0x87, 0x4f, 0xda, + ], + note_v: 17_811_330_145_809_239_872, + note_r: [ + 0x6e, 0xbb, 0xed, 0x74, 0x36, 0x19, 0xa2, 0x56, 0xf9, 0xad, 0x2e, 0x85, 0x88, 0x0c, + 0xfa, 0xa9, 0x09, 0x8a, 0x5f, 0xdb, 0x16, 0x29, 0x99, 0x0d, 0x9a, 0x7d, 0x3b, 0xb9, + 0x3f, 0xc9, 0x00, 0x03, + ], + note_cmu: [ + 0xd3, 0x76, 0xa7, 0xbe, 0xe8, 0xce, 0x67, 0xf4, 0xef, 0xde, 0x56, 0xaa, 0x77, 0xcf, + 0x64, 0x41, 0x9b, 0x0e, 0x55, 0x0a, 0xbb, 0xcb, 0x8e, 0x2b, 0xcb, 0xda, 0x8b, 0x63, + 0xe4, 0x1d, 0xeb, 0x37, + ], + note_pos: 2_578_461_368, + note_nf: [ + 0x65, 0x36, 0x74, 0x87, 0x3b, 0x3c, 0x67, 0x0c, 0x58, 0x85, 0x84, 0x73, 0xe7, 0xfe, + 0x72, 0x19, 0x72, 0xfb, 0x96, 0xe2, 0x15, 0xb8, 0x73, 0x77, 0xa1, 0x7c, 0xa3, 0x71, + 0x0d, 0x93, 0xc9, 0xe9, + ], + }, +]; diff --git a/zebra-chain/src/orchard/keys/tests.rs b/zebra-chain/src/orchard/keys/tests.rs new file mode 100644 index 00000000..8b8f4322 --- /dev/null +++ b/zebra-chain/src/orchard/keys/tests.rs @@ -0,0 +1,113 @@ +#![allow(clippy::module_inception)] +use super::*; + +#[cfg(test)] +use proptest::prelude::*; + +#[cfg(test)] +impl Arbitrary for TransmissionKey { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + (any::()) + .prop_map(|spending_key| { + let spend_authorizing_key = SpendAuthorizingKey::from(spending_key); + let proof_authorizing_key = ProofAuthorizingKey::from(spending_key); + + let authorizing_key = AuthorizingKey::from(spend_authorizing_key); + let nullifier_deriving_key = NullifierDerivingKey::from(proof_authorizing_key); + + let incoming_viewing_key = + IncomingViewingKey::from((authorizing_key, nullifier_deriving_key)); + + let diversifier = Diversifier::from(spending_key); + + Self::from((incoming_viewing_key, diversifier)) + }) + .boxed() + } + + type Strategy = BoxedStrategy; +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn derive_for_each_test_vector() { + zebra_test::init(); + + for test_vector in test_vectors::TEST_VECTORS.iter() { + let spending_key = SpendingKey::from(test_vector.sk); + + let spend_authorizing_key = SpendAuthorizingKey::from(spending_key); + assert_eq!(spend_authorizing_key, test_vector.ask); + let proof_authorizing_key = ProofAuthorizingKey::from(spending_key); + assert_eq!(proof_authorizing_key, test_vector.nsk); + let outgoing_viewing_key = OutgoingViewingKey::from(spending_key); + assert_eq!(outgoing_viewing_key, test_vector.ovk); + + let authorizing_key = AuthorizingKey::from(spend_authorizing_key); + assert_eq!(authorizing_key, test_vector.ak); + let nullifier_deriving_key = NullifierDerivingKey::from(proof_authorizing_key); + assert_eq!(nullifier_deriving_key, test_vector.nk); + let incoming_viewing_key = + IncomingViewingKey::from((authorizing_key, nullifier_deriving_key)); + assert_eq!(incoming_viewing_key, test_vector.ivk); + + let diversifier = Diversifier::from(spending_key); + assert_eq!(diversifier, test_vector.default_d); + + let transmission_key = TransmissionKey::from((incoming_viewing_key, diversifier)); + assert_eq!(transmission_key, test_vector.default_pk_d); + + let _full_viewing_key = FullViewingKey { + network: Network::default(), + authorizing_key, + nullifier_deriving_key, + outgoing_viewing_key, + }; + } + } +} + +#[cfg(test)] +proptest! { + + #[test] + fn string_roundtrips(spending_key in any::()) { + zebra_test::init(); + + let sk_string = spending_key.to_string(); + let spending_key_2: SpendingKey = sk_string.parse().unwrap(); + prop_assert_eq![spending_key, spending_key_2]; + + let spend_authorizing_key = SpendAuthorizingKey::from(spending_key); + let proof_authorizing_key = ProofAuthorizingKey::from(spending_key); + let outgoing_viewing_key = OutgoingViewingKey::from(spending_key); + + let authorizing_key = AuthorizingKey::from(spend_authorizing_key); + let nullifier_deriving_key = NullifierDerivingKey::from(proof_authorizing_key); + let mut incoming_viewing_key = + IncomingViewingKey::from((authorizing_key, nullifier_deriving_key)); + incoming_viewing_key.network = spending_key.network; + + let ivk_string = incoming_viewing_key.to_string(); + let incoming_viewing_key_2: IncomingViewingKey = ivk_string.parse().unwrap(); + prop_assert_eq![incoming_viewing_key, incoming_viewing_key_2]; + + let full_viewing_key = FullViewingKey { + network: spending_key.network, + authorizing_key, + nullifier_deriving_key, + outgoing_viewing_key, + }; + + let fvk_string = full_viewing_key.to_string(); + let full_viewing_key_2: FullViewingKey = fvk_string.parse().unwrap(); + prop_assert_eq![full_viewing_key, full_viewing_key_2]; + + } +} diff --git a/zebra-chain/src/orchard/note.rs b/zebra-chain/src/orchard/note.rs new file mode 100644 index 00000000..40de64b1 --- /dev/null +++ b/zebra-chain/src/orchard/note.rs @@ -0,0 +1,43 @@ +//! Sapling notes + +#![allow(clippy::unit_arg)] +#![allow(dead_code)] + +mod ciphertexts; +mod nullifiers; + +#[cfg(any(test, feature = "proptest-impl"))] +mod arbitrary; + +use crate::{ + amount::{Amount, NonNegative}, + transaction::Memo, +}; + +use super::{ + commitment::CommitmentRandomness, + keys::{Diversifier, TransmissionKey}, +}; + +pub use ciphertexts::{EncryptedNote, WrappedNoteKey}; + +pub use nullifiers::Nullifier; + +/// A Note represents that a value is spendable by the recipient who +/// holds the spending key corresponding to a given shielded payment +/// address. +#[derive(Clone, Debug)] +pub struct Note { + /// The diversifer of the recipient’s shielded payment address. + pub diversifier: Diversifier, + /// The diversified transmission key of the recipient’s shielded + /// payment address. + pub transmission_key: TransmissionKey, + /// An integer representing the value of the note in zatoshi. + pub value: Amount, + /// A random commitment trapdoor used to produce the associated + /// note commitment. + pub rcm: CommitmentRandomness, + /// The note memo, after decryption + pub memo: Memo, +} diff --git a/zebra-chain/src/orchard/note/arbitrary.rs b/zebra-chain/src/orchard/note/arbitrary.rs new file mode 100644 index 00000000..e6b38f12 --- /dev/null +++ b/zebra-chain/src/orchard/note/arbitrary.rs @@ -0,0 +1,35 @@ +use proptest::{arbitrary::any, collection::vec, prelude::*}; + +use super::*; + +impl Arbitrary for EncryptedNote { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + (vec(any::(), 580)) + .prop_map(|v| { + let mut bytes = [0; 580]; + bytes.copy_from_slice(v.as_slice()); + Self(bytes) + }) + .boxed() + } + + type Strategy = BoxedStrategy; +} + +impl Arbitrary for WrappedNoteKey { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + (vec(any::(), 80)) + .prop_map(|v| { + let mut bytes = [0; 80]; + bytes.copy_from_slice(v.as_slice()); + Self(bytes) + }) + .boxed() + } + + type Strategy = BoxedStrategy; +} diff --git a/zebra-chain/src/orchard/note/ciphertexts.rs b/zebra-chain/src/orchard/note/ciphertexts.rs new file mode 100644 index 00000000..52966e0a --- /dev/null +++ b/zebra-chain/src/orchard/note/ciphertexts.rs @@ -0,0 +1,133 @@ +use std::{fmt, io}; + +use crate::serialization::{serde_helpers, SerializationError, ZcashDeserialize, ZcashSerialize}; + +/// A ciphertext component for encrypted output notes. +/// +/// Corresponds to the Orcahrd 'encCiphertext's +#[derive(Deserialize, Serialize)] +pub struct EncryptedNote(#[serde(with = "serde_helpers::BigArray")] pub [u8; 580]); + +impl fmt::Debug for EncryptedNote { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("EncryptedNote") + .field(&hex::encode(&self.0[..])) + .finish() + } +} + +// These impls all only exist because of array length restrictions. + +impl Copy for EncryptedNote {} + +impl Clone for EncryptedNote { + fn clone(&self) -> Self { + let mut bytes = [0; 580]; + bytes[..].copy_from_slice(&self.0[..]); + Self(bytes) + } +} + +impl PartialEq for EncryptedNote { + fn eq(&self, other: &Self) -> bool { + self.0[..] == other.0[..] + } +} + +impl Eq for EncryptedNote {} + +impl ZcashSerialize for EncryptedNote { + fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { + writer.write_all(&self.0[..])?; + Ok(()) + } +} + +impl ZcashDeserialize for EncryptedNote { + fn zcash_deserialize(mut reader: R) -> Result { + let mut bytes = [0; 580]; + reader.read_exact(&mut bytes[..])?; + Ok(Self(bytes)) + } +} + +/// A ciphertext component for encrypted output notes. +/// +/// Corresponds to Orcahrd's 'outCiphertext' +#[derive(Deserialize, Serialize)] +pub struct WrappedNoteKey(#[serde(with = "serde_helpers::BigArray")] pub [u8; 80]); + +impl fmt::Debug for WrappedNoteKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("WrappedNoteKey") + .field(&hex::encode(&self.0[..])) + .finish() + } +} + +// These impls all only exist because of array length restrictions. + +impl Copy for WrappedNoteKey {} + +impl Clone for WrappedNoteKey { + fn clone(&self) -> Self { + let mut bytes = [0; 80]; + bytes[..].copy_from_slice(&self.0[..]); + Self(bytes) + } +} + +impl PartialEq for WrappedNoteKey { + fn eq(&self, other: &Self) -> bool { + self.0[..] == other.0[..] + } +} + +impl Eq for WrappedNoteKey {} + +impl ZcashSerialize for WrappedNoteKey { + fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { + writer.write_all(&self.0[..])?; + Ok(()) + } +} + +impl ZcashDeserialize for WrappedNoteKey { + fn zcash_deserialize(mut reader: R) -> Result { + let mut bytes = [0; 80]; + reader.read_exact(&mut bytes[..])?; + Ok(Self(bytes)) + } +} + +#[cfg(test)] +use proptest::prelude::*; +#[cfg(test)] +proptest! { + + #[test] + fn encrypted_ciphertext_roundtrip(ec in any::()) { + zebra_test::init(); + + let mut data = Vec::new(); + + ec.zcash_serialize(&mut data).expect("EncryptedNote should serialize"); + + let ec2 = EncryptedNote::zcash_deserialize(&data[..]).expect("randomized EncryptedNote should deserialize"); + + prop_assert_eq![ec, ec2]; + } + + #[test] + fn out_ciphertext_roundtrip(oc in any::()) { + zebra_test::init(); + + let mut data = Vec::new(); + + oc.zcash_serialize(&mut data).expect("WrappedNoteKey should serialize"); + + let oc2 = WrappedNoteKey::zcash_deserialize(&data[..]).expect("randomized WrappedNoteKey should deserialize"); + + prop_assert_eq![oc, oc2]; + } +} diff --git a/zebra-chain/src/orchard/note/nullifiers.rs b/zebra-chain/src/orchard/note/nullifiers.rs new file mode 100644 index 00000000..d7fc1418 --- /dev/null +++ b/zebra-chain/src/orchard/note/nullifiers.rs @@ -0,0 +1,54 @@ +#![allow(clippy::unit_arg)] +#![allow(dead_code)] + +use super::super::{ + commitment::{pedersen_hashes::mixing_pedersen_hash, NoteCommitment}, + keys::NullifierDerivingKey, + tree::Position, +}; + +/// Invokes Blake2s-256 as PRF^nfSapling to derive the nullifier for a +/// Sapling note. +/// +/// PRF^nfSapling(ρ*) := BLAKE2s-256("Zcash_nf", nk* || ρ*) +/// +/// https://zips.z.cash/protocol/protocol.pdf#concreteprfs +fn prf_nf(nk: [u8; 32], rho: [u8; 32]) -> [u8; 32] { + let hash = blake2s_simd::Params::new() + .hash_length(32) + .personal(b"Zcash_nf") + .to_state() + .update(&nk[..]) + .update(&rho[..]) + .finalize(); + + *hash.as_array() +} + +/// A Nullifier for Sapling transactions +#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)] +#[cfg_attr( + any(test, feature = "proptest-impl"), + derive(proptest_derive::Arbitrary) +)] +pub struct Nullifier(pub [u8; 32]); + +impl From<[u8; 32]> for Nullifier { + fn from(buf: [u8; 32]) -> Self { + Self(buf) + } +} + +impl<'a> From<(NoteCommitment, Position, &'a NullifierDerivingKey)> for Nullifier { + fn from((cm, pos, nk): (NoteCommitment, Position, &'a NullifierDerivingKey)) -> Self { + let rho = jubjub::AffinePoint::from(mixing_pedersen_hash(cm.0.into(), pos.0.into())); + + Nullifier(prf_nf(nk.into(), rho.to_bytes())) + } +} + +impl From for [u8; 32] { + fn from(n: Nullifier) -> Self { + n.0 + } +} diff --git a/zebra-chain/src/orchard/tests.rs b/zebra-chain/src/orchard/tests.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/zebra-chain/src/orchard/tests.rs @@ -0,0 +1 @@ + diff --git a/zebra-chain/src/orchard/tree.rs b/zebra-chain/src/orchard/tree.rs new file mode 100644 index 00000000..ad45b40f --- /dev/null +++ b/zebra-chain/src/orchard/tree.rs @@ -0,0 +1,266 @@ +//! Note Commitment Trees. +//! +//! A note commitment tree is an incremental Merkle tree of fixed depth +//! used to store note commitments that JoinSplit transfers or Spend +//! transfers produce. Just as the unspent transaction output set (UTXO +//! set) used in Bitcoin, it is used to express the existence of value and +//! the capability to spend it. However, unlike the UTXO set, it is not +//! the job of this tree to protect against double-spending, as it is +//! append-only. +//! +//! A root of a note commitment tree is associated with each treestate. + +#![allow(clippy::unit_arg)] +#![allow(dead_code)] + +use std::{collections::VecDeque, fmt}; + +use bitvec::prelude::*; +use lazy_static::lazy_static; +#[cfg(any(test, feature = "proptest-impl"))] +use proptest_derive::Arbitrary; + +use super::commitment::{pedersen_hashes::pedersen_hash, NoteCommitment}; + +const MERKLE_DEPTH: usize = 32; + +/// MerkleCRH^Sapling Hash Function +/// +/// Used to hash incremental Merkle tree hash values for Sapling. +/// +/// MerkleCRH^Sapling(layer, left, right) := PedersenHash(“Zcash_PH”, l || left || right) +/// where l = I2LEBSP_6(MerkleDepth^Sapling − 1 − layer) and +/// left, right, and the output are all technically 255 bits (l_MerkleSapling), not 256. +/// +/// https://zips.z.cash/protocol/protocol.pdf#merklecrh +fn merkle_crh_sapling(layer: u8, left: [u8; 32], right: [u8; 32]) -> [u8; 32] { + let mut s = bitvec![Lsb0, u8;]; + + // Prefix: l = I2LEBSP_6(MerkleDepth^Sapling − 1 − layer) + s.extend_from_slice(&layer.bits::()[0..6]); + s.extend_from_slice(&left.bits::()[0..255]); + s.extend_from_slice(&right.bits::()[0..255]); + + pedersen_hash(*b"Zcash_PH", &s).to_bytes() +} + +lazy_static! { + /// Sapling note commitment trees have a max depth of 32. + /// + /// https://zips.z.cash/protocol/protocol.pdf#constants + static ref EMPTY_ROOTS: Vec<[u8; 32]> = { + // Uncommitted^Sapling = I2LEBSP_l_MerkleSapling(1) + let mut v = vec![jubjub::Fq::one().to_bytes()]; + + for d in 0..MERKLE_DEPTH { + let next = merkle_crh_sapling(d as u8, v[d], v[d]); + v.push(next); + } + + v + + }; +} + +/// The index of a note’s commitment at the leafmost layer of its Note +/// Commitment Tree. +/// +/// https://zips.z.cash/protocol/protocol.pdf#merkletree +pub struct Position(pub(crate) u64); + +/// Sapling note commitment tree root node hash. +/// +/// The root hash in LEBS2OSP256(rt) encoding of the Sapling note +/// commitment tree corresponding to the final Sapling treestate of +/// this block. A root of a note commitment tree is associated with +/// each treestate. +#[derive(Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize, Hash)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] +pub struct Root(pub [u8; 32]); + +impl fmt::Debug for Root { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("Root").field(&hex::encode(&self.0)).finish() + } +} + +/// Sapling Note Commitment Tree +#[derive(Clone, Debug, Default, Eq, PartialEq)] +struct NoteCommitmentTree { + /// The root node of the tree (often used as an anchor). + root: Root, + /// The height of the tree (maximum height for Sapling is 32). + height: u8, + /// The number of leaves (note commitments) in this tree. + count: u32, +} + +impl From> for NoteCommitmentTree { + fn from(_values: Vec) -> Self { + unimplemented!(); + } +} + +impl From> for NoteCommitmentTree { + fn from(values: Vec) -> Self { + if values.is_empty() { + return NoteCommitmentTree { + root: Root::default(), + height: 0, + count: 0, + }; + } + + let count = values.len() as u32; + let mut height = 0u8; + let mut current_layer: VecDeque<[u8; 32]> = + values.into_iter().map(|cm_u| cm_u.to_bytes()).collect(); + + while usize::from(height) < MERKLE_DEPTH { + let mut next_layer_up = vec![]; + + while !current_layer.is_empty() { + let left = current_layer.pop_front().unwrap(); + let right; + if current_layer.is_empty() { + right = EMPTY_ROOTS[height as usize]; + } else { + right = current_layer.pop_front().unwrap(); + } + next_layer_up.push(merkle_crh_sapling(height, left, right)); + } + + height += 1; + current_layer = next_layer_up.into(); + } + + assert!(current_layer.len() == 1); + + NoteCommitmentTree { + root: Root(current_layer.pop_front().unwrap()), + height, + count, + } + } +} + +impl NoteCommitmentTree { + /// Get the Jubjub-based Pedersen hash of root node of this merkle tree of + /// commitment notes. + pub fn hash(&self) -> [u8; 32] { + self.root.0 + } +} + +#[cfg(test)] +mod tests { + + use hex::FromHex; + + use super::*; + + #[test] + fn empty_roots() { + zebra_test::init(); + + // From https://github.com/zcash/librustzcash/blob/master/zcash_primitives/src/merkle_tree.rs#L512 + const HEX_EMPTY_ROOTS: [&str; 33] = [ + "0100000000000000000000000000000000000000000000000000000000000000", + "817de36ab2d57feb077634bca77819c8e0bd298c04f6fed0e6a83cc1356ca155", + "ffe9fc03f18b176c998806439ff0bb8ad193afdb27b2ccbc88856916dd804e34", + "d8283386ef2ef07ebdbb4383c12a739a953a4d6e0d6fb1139a4036d693bfbb6c", + "e110de65c907b9dea4ae0bd83a4b0a51bea175646a64c12b4c9f931b2cb31b49", + "912d82b2c2bca231f71efcf61737fbf0a08befa0416215aeef53e8bb6d23390a", + "8ac9cf9c391e3fd42891d27238a81a8a5c1d3a72b1bcbea8cf44a58ce7389613", + "d6c639ac24b46bd19341c91b13fdcab31581ddaf7f1411336a271f3d0aa52813", + "7b99abdc3730991cc9274727d7d82d28cb794edbc7034b4f0053ff7c4b680444", + "43ff5457f13b926b61df552d4e402ee6dc1463f99a535f9a713439264d5b616b", + "ba49b659fbd0b7334211ea6a9d9df185c757e70aa81da562fb912b84f49bce72", + "4777c8776a3b1e69b73a62fa701fa4f7a6282d9aee2c7a6b82e7937d7081c23c", + "ec677114c27206f5debc1c1ed66f95e2b1885da5b7be3d736b1de98579473048", + "1b77dac4d24fb7258c3c528704c59430b630718bec486421837021cf75dab651", + "bd74b25aacb92378a871bf27d225cfc26baca344a1ea35fdd94510f3d157082c", + "d6acdedf95f608e09fa53fb43dcd0990475726c5131210c9e5caeab97f0e642f", + "1ea6675f9551eeb9dfaaa9247bc9858270d3d3a4c5afa7177a984d5ed1be2451", + "6edb16d01907b759977d7650dad7e3ec049af1a3d875380b697c862c9ec5d51c", + "cd1c8dbf6e3acc7a80439bc4962cf25b9dce7c896f3a5bd70803fc5a0e33cf00", + "6aca8448d8263e547d5ff2950e2ed3839e998d31cbc6ac9fd57bc6002b159216", + "8d5fa43e5a10d11605ac7430ba1f5d81fb1b68d29a640405767749e841527673", + "08eeab0c13abd6069e6310197bf80f9c1ea6de78fd19cbae24d4a520e6cf3023", + "0769557bc682b1bf308646fd0b22e648e8b9e98f57e29f5af40f6edb833e2c49", + "4c6937d78f42685f84b43ad3b7b00f81285662f85c6a68ef11d62ad1a3ee0850", + "fee0e52802cb0c46b1eb4d376c62697f4759f6c8917fa352571202fd778fd712", + "16d6252968971a83da8521d65382e61f0176646d771c91528e3276ee45383e4a", + "d2e1642c9a462229289e5b0e3b7f9008e0301cbb93385ee0e21da2545073cb58", + "a5122c08ff9c161d9ca6fc462073396c7d7d38e8ee48cdb3bea7e2230134ed6a", + "28e7b841dcbc47cceb69d7cb8d94245fb7cb2ba3a7a6bc18f13f945f7dbd6e2a", + "e1f34b034d4a3cd28557e2907ebf990c918f64ecb50a94f01d6fda5ca5c7ef72", + "12935f14b676509b81eb49ef25f39269ed72309238b4c145803544b646dca62d", + "b2eed031d4d6a4f02a097f80b54cc1541d4163c6b6f5971f88b6e41d35c53814", + "fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e", + ]; + + for i in 0..EMPTY_ROOTS.len() { + assert_eq!(hex::encode(EMPTY_ROOTS[i]), HEX_EMPTY_ROOTS[i]); + } + } + + #[test] + fn incremental_roots() { + zebra_test::init(); + // From https://github.com/zcash/zcash/blob/master/src/test/data/merkle_commitments_sapling.json + // Byte-reversed from those ones because the original test vectors are loaded using uint256S() + let commitments = [ + "b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55", + "225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b11458", + "7c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e036c", + "50421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a030", + "aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc12", + "f76748d40d5ee5f9a608512e7954dd515f86e8f6d009141c89163de1cf351a02", + "bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0b2e", + "da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a511", + "3a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f77446", + "c7ca8f7df8fd997931d33985d935ee2d696856cc09cc516d419ea6365f163008", + "f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c3702", + "e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c608", + "8cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e826", + "22fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c03", + "f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c", + "3a3661bc12b72646c94bc6c92796e81953985ee62d80a9ec3645a9a95740ac15", + ]; + + // Calculated by modifying TestCommitmentTree in + // https://github.com/zcash/librustzcash/blob/master/zcash_primitives/src/merkle_tree.rs + // to compute the full Sapling height root (32). + let roots = [ + "ee880ed73e96ba0739578c87ba8e6a4bc33b5e63bb98875e6e2f04b214e9fb59", + "321aef631f1a9b7914d40d7bab34c29145ac6cf69d24bf0fc566b33ac9029972", + "ddaa1ab86de5c153993414f34ba97e9674c459dfadde112b89eeeafa0e5a204c", + "0b337c75535b09468955d499e37cb7e2466f1f0c861ddea929aa13c699c1a454", + "5a9b9764d76a45848012eec306d6f6bface319ad5d9bf88db96b3b19edded716", + "004075c72e360d7b2ab113555e97dcf4fb50f211d74841eafb05aaff705e3235", + "ebf2139c2ef10d51f21fee18521963b91b64987f2743d908be2b80b4ae29e622", + "70d07f5662eafaf054327899abce515b1c1cbac6600edea86297c2800e806534", + "f72dad9cd0f4d4783444f6dc64d9be2edc74cffddcb60bf244e56eada508c22a", + "7635d357c7755c91ea4d6b53e8fd42756329118577fe8b9ade3d33b316fa4948", + "fca0c26ce07fc7e563b031d9187f829fa41715f193f08bd0ac25e5122ac75c2e", + "0b727c9c6f66c3c749ef9c1df6c5356db8adf80fcc3c1d7fdf56b82cb8d47a3c", + "d77d030ed3c2521567eae9555b95eca89442b0c263b82fea4359f802e0f31668", + "3d84c8b65e5a8036d115161bb6e3ca2a556e42d376abc3d74a16bc22685b7d61", + "84f752458538a24483e9731e32fa95cabf56aebbbc6bff8475f45299bcdcba35", + "bb3cc8f85773c05f3332a25cc8281a68450a90807cef859b49b2f1d9d2d3a64d", + ]; + + let mut leaves = vec![]; + + for (i, cm_u) in commitments.iter().enumerate() { + let bytes = <[u8; 32]>::from_hex(cm_u).unwrap(); + + leaves.push(jubjub::Fq::from_bytes(&bytes).unwrap()); + + let tree = NoteCommitmentTree::from(leaves.clone()); + + assert_eq!(hex::encode(tree.hash()), roots[i]); + } + } +} diff --git a/zebra-chain/src/primitives.rs b/zebra-chain/src/primitives.rs index 94cfa1da..95423134 100644 --- a/zebra-chain/src/primitives.rs +++ b/zebra-chain/src/primitives.rs @@ -5,9 +5,11 @@ //! whose functionality is implemented elsewhere. mod proofs; +// TODO: re-export redpallas if needed, or reddsa if that gets merged. +pub mod redpallas; pub use ed25519_zebra as ed25519; pub use redjubjub; pub use x25519_dalek as x25519; -pub use proofs::{Bctv14Proof, Groth16Proof, ZkSnarkProof}; +pub use proofs::{Bctv14Proof, Groth16Proof, Halo2Proof, ZkSnarkProof}; diff --git a/zebra-chain/src/primitives/proofs.rs b/zebra-chain/src/primitives/proofs.rs index 5721c423..18c8aba7 100644 --- a/zebra-chain/src/primitives/proofs.rs +++ b/zebra-chain/src/primitives/proofs.rs @@ -8,11 +8,13 @@ use crate::serialization::{ZcashDeserialize, ZcashSerialize}; mod bctv14; mod groth16; +mod halo2; -pub use bctv14::Bctv14Proof; -pub use groth16::Groth16Proof; +pub use self::bctv14::Bctv14Proof; +pub use self::groth16::Groth16Proof; +pub use self::halo2::Halo2Proof; -/// A marker trait used to abstract over BCTV14 or Groth16 proofs. +/// A marker trait used to abstract over BCTV14, Groth16, or Halo2 proofs. pub trait ZkSnarkProof: Copy + Clone @@ -28,6 +30,7 @@ pub trait ZkSnarkProof: } impl ZkSnarkProof for Bctv14Proof {} impl ZkSnarkProof for Groth16Proof {} +impl ZkSnarkProof for Halo2Proof {} mod private { use super::*; @@ -35,4 +38,5 @@ mod private { pub trait Sealed {} impl Sealed for Bctv14Proof {} impl Sealed for Groth16Proof {} + impl Sealed for Halo2Proof {} } diff --git a/zebra-chain/src/primitives/proofs/halo2.rs b/zebra-chain/src/primitives/proofs/halo2.rs new file mode 100644 index 00000000..c633ceb8 --- /dev/null +++ b/zebra-chain/src/primitives/proofs/halo2.rs @@ -0,0 +1,77 @@ +use serde::{Deserialize, Serialize}; +use std::{fmt, io}; + +use crate::serialization::{serde_helpers, SerializationError, ZcashDeserialize, ZcashSerialize}; + +/// An encoding of a Halo2 proof, as used in [Zcash][halo2]. +/// +/// Halo2 proofs in Zcash Orchard do not have a fixed size, hence the newtype +/// around a vector of bytes. +/// +/// [halo2]: https://zips.z.cash/protocol/nu5.pdf#halo2 +#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct Halo2Proof(pub Vec); + +impl fmt::Debug for Halo2Proof { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("Halo2Proof") + .field(&hex::encode(&self.0[..])) + .finish() + } +} + +// These impls all only exist because of array length restrictions. + +// impl Copy for Halo2Proof {} + +// impl Clone for Halo2Proof { +// fn clone(&self) -> Self { +// let mut bytes = [0; 192]; +// bytes[..].copy_from_slice(&self.0[..]); +// Self(bytes) +// } +// } + +// impl PartialEq for Halo2Proof { +// fn eq(&self, other: &Self) -> bool { +// self.0[..] == other.0[..] +// } +// } + +// impl Eq for Halo2Proof {} + +impl ZcashSerialize for Halo2Proof { + fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { + writer.write_all(&self.0[..])?; + Ok(()) + } +} + +impl ZcashDeserialize for Halo2Proof { + fn zcash_deserialize(mut reader: R) -> Result { + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes)?; + + Ok(Self(bytes)) + } +} +// TODO: figure how a Halo2Proof Strategy for generating proofs for proptesting. +// #[cfg(any(test, feature = "proptest-impl"))] +// use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*}; +// +// #[cfg(any(test, feature = "proptest-impl"))] +// impl Arbitrary for Halo2Proof { +// type Parameters = (); + +// fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { +// (vec(any::(), 192)) +// .prop_map(|v| { +// let mut bytes = [0; 192]; +// bytes.copy_from_slice(v.as_slice()); +// Self(bytes) +// }) +// .boxed() +// } + +// type Strategy = BoxedStrategy; +// } diff --git a/zebra-chain/src/primitives/redpallas.rs b/zebra-chain/src/primitives/redpallas.rs new file mode 100644 index 00000000..3cb6f93b --- /dev/null +++ b/zebra-chain/src/primitives/redpallas.rs @@ -0,0 +1,58 @@ +// Extracted from redjubjub for now. + +#![deny(missing_docs)] + +use halo2::pasta::pallas; + +// pub mod batch; +mod constants; +// mod error; +// pub mod frost; +// mod hash; +// mod scalar_mul; +// mod signature; +// mod signing_key; +mod verification_key; + +pub use verification_key::{VerificationKey, VerificationKeyBytes}; + +/// Abstracts over different RedPallas parameter choices, [`Binding`] +/// and [`SpendAuth`]. +/// +/// As described [at the end of §5.4.6][concretereddsa] of the Zcash +/// protocol specification, the generator used in RedPallas is left as +/// an unspecified parameter, chosen differently for each of +/// `BindingSig` and `SpendAuthSig`. +/// +/// To handle this, we encode the parameter choice as a genuine type +/// parameter. +/// +/// [concretereddsa]: https://zips.z.cash/protocol/protocol.pdf#concretereddsa +pub trait SigType: private::Sealed {} + +/// A type variable corresponding to Zcash's `BindingSig`. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum Binding {} +impl SigType for Binding {} + +/// A type variable corresponding to Zcash's `SpendAuthSig`. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum SpendAuth {} +impl SigType for SpendAuth {} + +mod private { + use super::*; + pub trait Sealed: Copy + Clone + Eq + PartialEq + std::fmt::Debug { + fn basepoint() -> pallas::Point; + } + impl Sealed for Binding { + fn basepoint() -> pallas::Point { + pallas::Point::from_bytes(constants::BINDINGSIG_BASEPOINT_BYTES).unwrap() + } + } + impl Sealed for SpendAuth { + fn basepoint() -> pallas::Point { + pallas::Point::from_bytes(constants::SPENDAUTHSIG_BASEPOINT_BYTES).unwrap() + } + } +} diff --git a/zebra-chain/src/primitives/redpallas/constants.rs b/zebra-chain/src/primitives/redpallas/constants.rs new file mode 100644 index 00000000..54bb9d58 --- /dev/null +++ b/zebra-chain/src/primitives/redpallas/constants.rs @@ -0,0 +1,22 @@ +// -*- mode: rust; -*- +// +// This file is part of redjubjub. +// Copyright (c) 2019-2021 Zcash Foundation +// See LICENSE for licensing information. +// +// Authors: +// - Deirdre Connolly + +/// The byte-encoding of the basepoint for `SpendAuthSig` on the [Pallas curve][pallasandvesta]. +/// +/// [pallasandvesta]: https://zips.z.cash/protocol/nu5.pdf#pallasandvesta +pub const SPENDAUTHSIG_BASEPOINT_BYTES: [u8; 32] = [ + 215, 148, 162, 4, 167, 65, 231, 17, 216, 7, 4, 206, 68, 161, 32, 20, 67, 192, 174, 143, 131, + 35, 240, 117, 113, 113, 7, 198, 56, 190, 133, 53, +]; + +/// The byte-encoding of the basepoint for `BindingSig` on the Pallas curve. +pub const BINDINGSIG_BASEPOINT_BYTES: [u8; 32] = [ + 48, 181, 242, 170, 173, 50, 86, 48, 188, 221, 219, 206, 77, 103, 101, 109, 5, 253, 28, 194, + 208, 55, 187, 83, 117, 182, 233, 109, 158, 1, 161, 215, +]; diff --git a/zebra-chain/src/primitives/redpallas/verification_key.rs b/zebra-chain/src/primitives/redpallas/verification_key.rs new file mode 100644 index 00000000..9c9cbd49 --- /dev/null +++ b/zebra-chain/src/primitives/redpallas/verification_key.rs @@ -0,0 +1,190 @@ +use std::{ + convert::TryFrom, + hash::{Hash, Hasher}, + marker::PhantomData, +}; + +use halo2::pasta::pallas; + +use super::{Error, SigType, Signature, SpendAuth}; + +/// A refinement type for `[u8; 32]` indicating that the bytes represent +/// an encoding of a RedPallas verification key. +/// +/// This is useful for representing a compressed verification key; the +/// [`VerificationKey`] type in this library holds other decompressed state +/// used in signature verification. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct VerificationKeyBytes { + pub(crate) bytes: [u8; 32], + pub(crate) _marker: PhantomData, +} + +impl From<[u8; 32]> for VerificationKeyBytes { + fn from(bytes: [u8; 32]) -> VerificationKeyBytes { + VerificationKeyBytes { + bytes, + _marker: PhantomData, + } + } +} + +impl From> for [u8; 32] { + fn from(refined: VerificationKeyBytes) -> [u8; 32] { + refined.bytes + } +} + +impl Hash for VerificationKeyBytes { + fn hash(&self, state: &mut H) { + self.bytes.hash(state); + self._marker.hash(state); + } +} + +/// A valid RedPallas verification key. +/// +/// This type holds decompressed state used in signature verification; if the +/// verification key may not be used immediately, it is probably better to use +/// [`VerificationKeyBytes`], which is a refinement type for `[u8; 32]`. +/// +/// ## Consensus properties +/// +/// The `TryFrom` conversion performs the following Zcash +/// consensus rule checks: +/// +/// 1. The check that the bytes are a canonical encoding of a verification key; +/// 2. The check that the verification key is not a point of small order. +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(try_from = "VerificationKeyBytes"))] +#[cfg_attr(feature = "serde", serde(into = "VerificationKeyBytes"))] +#[cfg_attr(feature = "serde", serde(bound = "T: SigType"))] +pub struct VerificationKey { + pub(crate) point: pallas::Point, + pub(crate) bytes: VerificationKeyBytes, +} + +impl From> for VerificationKeyBytes { + fn from(pk: VerificationKey) -> VerificationKeyBytes { + pk.bytes + } +} + +impl From> for [u8; 32] { + fn from(pk: VerificationKey) -> [u8; 32] { + pk.bytes.bytes + } +} + +impl TryFrom> for VerificationKey { + type Error = Error; + + fn try_from(bytes: VerificationKeyBytes) -> Result { + // XXX-pasta-curves: this should not use CtOption + // XXX-pasta-curves: this takes ownership of bytes, while Fr doesn't. + // This checks that the encoding is canonical... + let maybe_point = pallas::Point::from_bytes(bytes.bytes); + if maybe_point.is_some().into() { + let point = maybe_point.unwrap(); + // This checks that the verification key is not of small order. + if ::from(point.is_small_order()) == false { + Ok(VerificationKey { point, bytes }) + } else { + Err(Error::MalformedVerificationKey) + } + } else { + Err(Error::MalformedVerificationKey) + } + } +} + +impl TryFrom<[u8; 32]> for VerificationKey { + type Error = Error; + + fn try_from(bytes: [u8; 32]) -> Result { + use std::convert::TryInto; + VerificationKeyBytes::from(bytes).try_into() + } +} + +impl VerificationKey { + /// Randomize this verification key with the given `randomizer`. + /// + /// Randomization is only supported for `SpendAuth` keys. + pub fn randomize(&self, randomizer: &Randomizer) -> VerificationKey { + use crate::private::Sealed; + let point = self.point + &(&SpendAuth::basepoint() * randomizer); + let bytes = VerificationKeyBytes { + bytes: point.to_bytes().as_ref().try_into().unwrap(), + _marker: PhantomData, + }; + VerificationKey { bytes, point } + } +} + +impl VerificationKey { + pub(crate) fn from(s: &pallas::Scalar) -> VerificationKey { + let point = &T::basepoint() * s; + let bytes = VerificationKeyBytes { + bytes: point.to_bytes().as_ref().try_into().unwrap(), + _marker: PhantomData, + }; + VerificationKey { bytes, point } + } + + /// Verify a purported `signature` over `msg` made by this verification key. + // This is similar to impl signature::Verifier but without boxed errors + pub fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), Error> { + use crate::HStar; + let c = HStar::default() + .update(&signature.r_bytes[..]) + .update(&self.bytes.bytes[..]) // XXX ugly + .update(msg) + .finalize(); + self.verify_prehashed(signature, c) + } + + /// Verify a purported `signature` with a prehashed challenge. + #[allow(non_snake_case)] + pub(crate) fn verify_prehashed( + &self, + signature: &Signature, + c: pallas::Scalar, + ) -> Result<(), Error> { + let r = { + // XXX-pasta-curves: should not use CtOption here + // XXX-pasta-curves: inconsistent ownership in from_bytes + let maybe_point = pallas::Point::from_bytes(signature.r_bytes); + if maybe_point.is_some().into() { + maybe_point.unwrap() + } else { + return Err(Error::InvalidSignature); + } + }; + + let s = { + // XXX-pasta-curves: should not use CtOption here + let maybe_scalar = pallas::Scalar::from_bytes(&signature.s_bytes); + if maybe_scalar.is_some().into() { + maybe_scalar.unwrap() + } else { + return Err(Error::InvalidSignature); + } + }; + + // XXX rewrite as normal double scalar mul + // Verify check is h * ( - s * B + R + c * A) == 0 + // h * ( s * B - c * A - R) == 0 + let sB = T::basepoint() * s; + let cA = self.point * c; + let check = sB - cA - r; + + if check.is_small_order().into() { + Ok(()) + } else { + Err(Error::InvalidSignature) + } + } +} diff --git a/zebra-chain/src/serialization/serde_helpers.rs b/zebra-chain/src/serialization/serde_helpers.rs index 0eaca843..15c22613 100644 --- a/zebra-chain/src/serialization/serde_helpers.rs +++ b/zebra-chain/src/serialization/serde_helpers.rs @@ -1,3 +1,4 @@ +use halo2::pasta::pallas; use serde_big_array::big_array; big_array! { @@ -35,3 +36,36 @@ impl From for jubjub::Fq { jubjub::Fq::from_bytes(&local.bytes).unwrap() } } + +#[derive(Deserialize, Serialize)] +#[serde(remote = "pallas::Affine")] +pub struct Affine { + #[serde(getter = "pallas::Affine::to_bytes")] + bytes: [u8; 32], +} + +impl From for pallas::Affine { + fn from(local: Affine) -> Self { + pallas::Affine::from_bytes(local.bytes).unwrap() + } +} + +#[derive(Deserialize, Serialize)] +#[serde(remote = "pallas::Scalar")] +pub struct Scalar { + #[serde(getter = "pallas::Scalar::to_bytes")] + bytes: [u8; 32], +} + +impl From for pallas::Scalar { + fn from(local: Scalar) -> Self { + pallas::Scalar::from_bytes(&local.bytes).unwrap() + } +} + +#[derive(Deserialize, Serialize)] +#[serde(remote = "futures::future::Either")] +pub enum Either { + Left(A), + Right(B), +} diff --git a/zebra-chain/src/transaction/serialize.rs b/zebra-chain/src/transaction/serialize.rs index 5198b79b..b60d1b22 100644 --- a/zebra-chain/src/transaction/serialize.rs +++ b/zebra-chain/src/transaction/serialize.rs @@ -4,6 +4,7 @@ use std::{convert::TryInto, io, sync::Arc}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use halo2::pasta::pallas; use crate::{ block::MAX_BLOCK_BYTES, @@ -34,9 +35,19 @@ impl ZcashDeserialize for jubjub::Fq { } } -// Transaction V3 and V4 serialize sprout JoinSplitData in a single continuous -// byte range, so we can implement its serialization and deserialization -// separately. +impl ZcashDeserialize for pallas::Scalar { + fn zcash_deserialize(mut reader: R) -> Result { + let possible_scalar = pallas::Scalar::from_bytes(&reader.read_32_bytes()?); + + if possible_scalar.is_some().into() { + Ok(possible_scalar.unwrap()) + } else { + Err(SerializationError::Parse( + "Invalid pallas::Scalar, input not canonical", + )) + } + } +} impl ZcashSerialize for JoinSplitData

{ fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> {