From 161bb80ce8fc131bd7fea3a95723b34f1795716e Mon Sep 17 00:00:00 2001 From: teor Date: Sun, 30 Oct 2022 06:59:55 +1000 Subject: [PATCH] cleanup(cryptography): Remove unused shielded key and address code (#5476) * Remove unused and buggy Sprout key and address code * Remove unused, buggy Sapling address, key, and commitment code * Delete unused Orchard key code * Move almost all the buggy Orchard key code into a test-only module * Remove Orchard keys and addresses that aren't used in production code * Remove unused prf_expand() function and unimplemented poseidon_hash() function * Remove unused Orchard key types * Remove unused sinsemilla commit code * Update zebra-chain/src/sprout/keys.rs * Update zebra-chain/src/sprout/keys.rs Co-authored-by: Deirdre Connolly --- zebra-chain/src/orchard.rs | 5 +- zebra-chain/src/orchard/address.rs | 36 - zebra-chain/src/orchard/arbitrary.rs | 51 +- zebra-chain/src/orchard/commitment.rs | 80 +- zebra-chain/src/orchard/keys.rs | 924 +----------------- zebra-chain/src/orchard/keys/tests.rs | 103 -- zebra-chain/src/orchard/note.rs | 23 +- zebra-chain/src/orchard/note/nullifiers.rs | 66 +- zebra-chain/src/orchard/sinsemilla.rs | 31 - zebra-chain/src/sapling.rs | 9 +- zebra-chain/src/sapling/address.rs | 182 ---- zebra-chain/src/sapling/commitment.rs | 10 +- .../src/sapling/commitment/pedersen_hashes.rs | 16 - .../src/sapling/commitment/test_vectors.rs | 22 +- zebra-chain/src/sapling/keys.rs | 752 +------------- zebra-chain/src/sapling/keys/test_vectors.rs | 6 +- zebra-chain/src/sapling/keys/tests.rs | 114 --- zebra-chain/src/sapling/note.rs | 2 - zebra-chain/src/sapling/note/nullifiers.rs | 34 +- zebra-chain/src/sprout.rs | 6 +- zebra-chain/src/sprout/address.rs | 175 ---- zebra-chain/src/sprout/keys.rs | 381 +------- zebra-chain/src/sprout/note.rs | 7 +- zebra-chain/src/sprout/note/nullifiers.rs | 38 +- 24 files changed, 69 insertions(+), 3004 deletions(-) delete mode 100644 zebra-chain/src/orchard/keys/tests.rs delete mode 100644 zebra-chain/src/sapling/address.rs delete mode 100644 zebra-chain/src/sapling/keys/tests.rs delete mode 100644 zebra-chain/src/sprout/address.rs diff --git a/zebra-chain/src/orchard.rs b/zebra-chain/src/orchard.rs index ea5a9614e..be96644c8 100644 --- a/zebra-chain/src/orchard.rs +++ b/zebra-chain/src/orchard.rs @@ -4,11 +4,12 @@ mod action; mod address; -#[cfg(any(test, feature = "proptest-impl"))] -mod arbitrary; mod commitment; mod note; mod sinsemilla; + +#[cfg(any(test, feature = "proptest-impl"))] +mod arbitrary; #[cfg(test)] mod tests; diff --git a/zebra-chain/src/orchard/address.rs b/zebra-chain/src/orchard/address.rs index d1e4acac4..f37cdbb87 100644 --- a/zebra-chain/src/orchard/address.rs +++ b/zebra-chain/src/orchard/address.rs @@ -24,39 +24,3 @@ impl fmt::Debug for Address { .finish() } } - -#[cfg(test)] -mod tests { - - use rand_core::OsRng; - - use crate::parameters::Network; - - use super::*; - - #[test] - fn derive_keys_and_addresses() { - let _init_guard = zebra_test::init(); - - let network = Network::Mainnet; - - let spending_key = keys::SpendingKey::new(&mut OsRng, network); - - let full_viewing_key = keys::FullViewingKey::from(spending_key); - - // Default diversifier, where index = 0. - let diversifier_key = keys::DiversifierKey::from(full_viewing_key); - - // This should fail with negligible probability. - let incoming_viewing_key = keys::IncomingViewingKey::try_from(full_viewing_key) - .expect("a valid incoming viewing key"); - - let diversifier = keys::Diversifier::from(diversifier_key); - let transmission_key = keys::TransmissionKey::from((incoming_viewing_key, diversifier)); - - let _orchard_shielded_address = Address { - diversifier, - transmission_key, - }; - } -} diff --git a/zebra-chain/src/orchard/arbitrary.rs b/zebra-chain/src/orchard/arbitrary.rs index 187465d17..78b8141d0 100644 --- a/zebra-chain/src/orchard/arbitrary.rs +++ b/zebra-chain/src/orchard/arbitrary.rs @@ -1,3 +1,7 @@ +//! Randomised data generation for Orchard types. + +use std::marker::PhantomData; + use group::{ff::PrimeField, prime::PrimeCurveAffine}; use halo2::{arithmetic::FieldExt, pasta::pallas}; use proptest::{arbitrary::any, array, collection::vec, prelude::*}; @@ -5,13 +9,7 @@ use proptest::{arbitrary::any, array, collection::vec, prelude::*}; use crate::primitives::redpallas::{Signature, SpendAuth, VerificationKey, VerificationKeyBytes}; use super::{ - keys, note, tree, Action, Address, AuthorizedAction, Diversifier, Flags, NoteCommitment, - ValueCommitment, -}; - -use std::{ - convert::{TryFrom, TryInto}, - marker::PhantomData, + keys::*, note, tree, Action, AuthorizedAction, Flags, NoteCommitment, ValueCommitment, }; impl Arbitrary for Action { @@ -29,7 +27,7 @@ impl Arbitrary for Action { nullifier, rk, cm_x: NoteCommitment(pallas::Affine::identity()).extract_x(), - ephemeral_key: keys::EphemeralPublicKey(pallas::Affine::generator()), + ephemeral_key: EphemeralPublicKey(pallas::Affine::generator()), enc_ciphertext, out_ciphertext, }) @@ -128,40 +126,3 @@ impl Arbitrary for tree::Root { type Strategy = BoxedStrategy; } - -impl Arbitrary for keys::TransmissionKey { - type Parameters = (); - - fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - (any::()) - .prop_map(|spending_key| { - let full_viewing_key = keys::FullViewingKey::from(spending_key); - - let diversifier_key = keys::DiversifierKey::from(full_viewing_key); - - let diversifier = Diversifier::from(diversifier_key); - let incoming_viewing_key = keys::IncomingViewingKey::try_from(full_viewing_key) - .expect("a valid incoming viewing key"); - - Self::from((incoming_viewing_key, diversifier)) - }) - .boxed() - } - - type Strategy = BoxedStrategy; -} - -impl Arbitrary for Address { - type Parameters = (); - - fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - (any::(), any::()) - .prop_map(|(diversifier, transmission_key)| Self { - diversifier, - transmission_key, - }) - .boxed() - } - - type Strategy = BoxedStrategy; -} diff --git a/zebra-chain/src/orchard/commitment.rs b/zebra-chain/src/orchard/commitment.rs index cea1942db..db2540b00 100644 --- a/zebra-chain/src/orchard/commitment.rs +++ b/zebra-chain/src/orchard/commitment.rs @@ -1,8 +1,7 @@ //! Note and value commitments. -use std::{convert::TryFrom, fmt, io}; +use std::{fmt, io}; -use bitvec::prelude::*; use group::{ff::PrimeField, prime::PrimeCurveAffine, GroupEncoding}; use halo2::{ arithmetic::{Coordinates, CurveAffine, FieldExt}, @@ -18,11 +17,7 @@ use crate::{ }, }; -use super::{ - keys::prf_expand, - note::{Note, Psi, SeedRandomness}, - sinsemilla::*, -}; +use super::sinsemilla::*; /// Generates a random scalar from the scalar field 𝔽_{q_P}. /// @@ -41,18 +36,6 @@ where #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct CommitmentRandomness(pallas::Scalar); -impl From for CommitmentRandomness { - /// rcm = ToScalar^Orchard((PRF^expand_rseed (\[5\])) - /// - /// - fn from(rseed: SeedRandomness) -> Self { - Self(pallas::Scalar::from_bytes_wide(&prf_expand( - rseed.0, - vec![&[5]], - ))) - } -} - /// Note commitments for the output notes. #[derive(Clone, Copy, Deserialize, PartialEq, Eq, Serialize)] pub struct NoteCommitment(#[serde(with = "serde_helpers::Affine")] pub pallas::Affine); @@ -103,51 +86,6 @@ impl TryFrom<[u8; 32]> for NoteCommitment { } impl NoteCommitment { - /// Generate a new _NoteCommitment_. - /// - /// Unlike in Sapling, the definition of an Orchard _note_ includes the ρ - /// field; the _note_'s position in the _note commitment tree_ does not need - /// to be known in order to compute this value. - /// - /// NoteCommit^Orchard_rcm(repr_P(gd),repr_P(pkd), v, ρ, ψ) := - /// - /// - #[allow(non_snake_case)] - #[allow(clippy::unwrap_in_result)] - pub fn new(note: Note) -> Option { - // s as in the argument name for WindowedPedersenCommit_r(s) - let mut s: BitVec = BitVec::new(); - - // Prefix - s.append(&mut bitvec![1; 6]); - - // The `TryFrom` impls for the `pallas::*Point`s handles - // calling `DiversifyHash` implicitly. - // _diversified base_ - let g_d_bytes = pallas::Affine::try_from(note.address.diversifier) - .ok()? - .to_bytes(); - - let pk_d_bytes: [u8; 32] = note.address.transmission_key.into(); - let v_bytes = note.value.to_bytes(); - let rho_bytes: [u8; 32] = note.rho.into(); - let psi_bytes: [u8; 32] = Psi::from(note.rseed).into(); - - // g*d || pk*d || I2LEBSP_64(v) || I2LEBSP_l^Orchard_Base(ρ) || I2LEBSP_l^Orchard_base(ψ) - s.extend(g_d_bytes); - s.extend(pk_d_bytes); - s.extend(v_bytes); - s.extend(rho_bytes); - s.extend(psi_bytes); - - let rcm = CommitmentRandomness::from(note.rseed); - - Some(NoteCommitment::from( - sinsemilla_commit(rcm.0, b"z.cash:Orchard-NoteCommit", &s) - .expect("valid orchard note commitment, not ⊥ "), - )) - } - /// Extract the x coordinate of the note commitment. pub fn extract_x(&self) -> pallas::Base { extract_p(self.0.into()) @@ -320,20 +258,6 @@ mod tests { use super::*; - // #[test] - // fn sinsemilla_hash_to_point_test_vectors() { - // let _init_guard = zebra_test::init(); - - // const D: [u8; 8] = *b"Zcash_PH"; - - // for test_vector in test_vectors::TEST_VECTORS.iter() { - // let result = - // pallas::Affine::from(sinsemilla_hash_to_point(D, &test_vector.input_bits.clone())); - - // assert_eq!(result, test_vector.output_point); - // } - // } - #[test] fn add() { let _init_guard = zebra_test::init(); diff --git a/zebra-chain/src/orchard/keys.rs b/zebra-chain/src/orchard/keys.rs index 2ae959733..7fdbfb810 100644 --- a/zebra-chain/src/orchard/keys.rs +++ b/zebra-chain/src/orchard/keys.rs @@ -1,80 +1,24 @@ //! Orchard key types. //! +//! Unused key types are not implemented, see PR #5476. +//! //! -#![allow(clippy::fallible_impl_from)] - -#[cfg(test)] -mod tests; use std::{fmt, io}; -use aes::Aes256; -use bech32::{self, ToBase32, Variant}; -use bitvec::prelude::*; -use fpe::ff1::{BinaryNumeralString, FF1}; use group::{ff::PrimeField, prime::PrimeCurveAffine, Group, GroupEncoding}; use halo2::{ - arithmetic::{Coordinates, CurveAffine, Field, FieldExt}, + arithmetic::{Coordinates, CurveAffine}, pasta::pallas, }; use rand_core::{CryptoRng, RngCore}; -use subtle::{Choice, ConstantTimeEq}; -use crate::{ - parameters::Network, - primitives::redpallas::{self, SpendAuth}, - serialization::{ - serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize, - }, +use crate::serialization::{ + serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize, }; use super::sinsemilla::*; -/// PRP^d_K(d) := FF1-AES256_K("", d) -/// -/// "Let FF1-AES256_K(tweak, x) be the FF1 format-preserving encryption -/// algorithm using AES with a 256-bit key K, and parameters radix = 2, minlen = -/// 88, maxlen = 88. It will be used only with the empty string "" as the -/// tweak. x is a sequence of 88 bits, as is the output." -/// -/// -#[allow(non_snake_case)] -fn prp_d(K: [u8; 32], d: [u8; 11]) -> [u8; 11] { - let radix = 2; - let tweak = b""; - - let ff = FF1::::new(&K, radix).expect("valid radix"); - - ff.encrypt(tweak, &BinaryNumeralString::from_bytes_le(&d)) - .unwrap() - .to_bytes_le() - .try_into() - .unwrap() -} - -/// Invokes Blake2b-512 as PRF^expand with parameter t. -/// -/// PRF^expand(sk, t) := BLAKE2b-512("Zcash_ExpandSeed", sk || t) -/// -/// -// TODO: This is basically a duplicate of the one in our sapling module, its -// definition in the draft Nu5 spec is incomplete so I'm putting it here in case -// it changes. -pub fn prf_expand(sk: [u8; 32], t: Vec<&[u8]>) -> [u8; 64] { - let mut state = blake2b_simd::Params::new() - .hash_length(64) - .personal(b"Zcash_ExpandSeed") - .to_state(); - - state.update(&sk[..]); - - for t_i in t { - state.update(t_i); - } - - *state.finalize().as_array() -} - /// Used to derive a diversified base point from a diversifier value. /// /// DiversifyHash^Orchard(d) := {︃ GroupHash^P("z.cash:Orchard-gd",""), if P = 0_P @@ -93,773 +37,6 @@ fn diversify_hash(d: &[u8]) -> pallas::Point { } } -/// Magic human-readable strings used to identify what networks Orchard spending -/// keys are associated with when encoded/decoded with bech32. -/// -/// [orchardspendingkeyencoding]: https://zips.z.cash/protocol/nu5.pdf#orchardspendingkeyencoding -mod sk_hrp { - pub const MAINNET: &str = "secret-orchard-sk-main"; - pub const TESTNET: &str = "secret-orchard-sk-test"; -} - -/// A spending key, as described in [protocol specification §4.2.3][ps]. -/// -/// Our root secret key of the Orchard key derivation tree. All other Orchard -/// key types derive from the [`SpendingKey`] value. -/// -/// [ps]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents -#[derive(Copy, Clone, Debug)] -#[cfg_attr( - any(test, feature = "proptest-impl"), - derive(proptest_derive::Arbitrary) -)] -pub struct SpendingKey { - network: Network, - bytes: [u8; 32], -} - -impl ConstantTimeEq for SpendingKey { - /// Check whether two `SpendingKey`s are equal, runtime independent of the - /// value of the secret. - /// - /// # Note - /// - /// This function short-circuits if the networks of the keys are different. - /// Otherwise, it should execute in time independent of the `bytes` value. - fn ct_eq(&self, other: &Self) -> Choice { - if self.network != other.network { - return Choice::from(0); - } - - self.bytes.ct_eq(&other.bytes) - } -} - -impl fmt::Display for SpendingKey { - #[allow(clippy::unwrap_in_result)] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let hrp = match self.network { - Network::Mainnet => sk_hrp::MAINNET, - Network::Testnet => sk_hrp::TESTNET, - }; - - bech32::encode_to_fmt(f, hrp, &self.bytes.to_base32(), Variant::Bech32) - .expect("hrp is valid") - } -} - -impl From for [u8; 32] { - fn from(sk: SpendingKey) -> Self { - sk.bytes - } -} - -impl Eq for SpendingKey {} - -impl PartialEq for SpendingKey { - fn eq(&self, other: &Self) -> bool { - self.ct_eq(other).unwrap_u8() == 1u8 - } -} - -impl SpendingKey { - /// Generate a new `SpendingKey`. - /// - /// When generating, we check that the corresponding `SpendAuthorizingKey` - /// is not zero, else fail. - /// - /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents - pub fn new(csprng: &mut T, network: Network) -> Self - where - T: RngCore + CryptoRng, - { - loop { - let mut bytes = [0u8; 32]; - csprng.fill_bytes(&mut bytes); - - let sk = Self::from_bytes(bytes, network); - - // "if ask = 0, discard this key and repeat with a new sk" - if SpendAuthorizingKey::from(sk).0.is_zero().into() { - continue; - } - - // "if ivk ∈ {0, ⊥}, discard this key and repeat with a new sk" - if IncomingViewingKey::try_from(FullViewingKey::from(sk)).is_err() { - continue; - } - - break sk; - } - } - - /// Generate a `SpendingKey` from existing bytes. - pub fn from_bytes(bytes: [u8; 32], network: Network) -> Self { - Self { network, bytes } - } -} - -/// A Spend authorizing key (_ask_), as described in [protocol specification -/// §4.2.3][orchardkeycomponents]. -/// -/// Used to generate _spend authorization randomizers_ to sign each _Action -/// Description_ that spends notes, proving ownership of notes. -/// -/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents -#[derive(Copy, Clone, Eq)] -pub struct SpendAuthorizingKey(pub(crate) pallas::Scalar); - -impl ConstantTimeEq for SpendAuthorizingKey { - /// Check whether two `SpendAuthorizingKey`s are equal, runtime independent - /// of the value of the secret. - fn ct_eq(&self, other: &Self) -> Choice { - self.0.to_repr().ct_eq(&other.0.to_repr()) - } -} - -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_repr() - } -} - -impl From for SpendAuthorizingKey { - /// Invokes Blake2b-512 as _PRF^expand_, t=6, to derive a - /// `SpendAuthorizingKey` from a `SpendingKey`. - /// - /// ask := ToScalar^Orchard(PRF^expand(sk, \[6\])) - /// - /// - /// - fn from(spending_key: SpendingKey) -> SpendAuthorizingKey { - // Handles ToScalar^Orchard - let ask = pallas::Scalar::from_bytes_wide(&prf_expand(spending_key.bytes, vec![&[6]])); - - // let ak^P = SpendAuthSig^Orchard.DerivePublic(ask)... - let ak_bytes: [u8; 32] = SpendValidatingKey::from(SpendAuthorizingKey(ask)).into(); - - // if the last bit (that is, the 𝑦˜ bit) of repr_P (ak^P ) is 1: - // set ask ← −ask - match (ak_bytes[31] >> 7) == 0b0000_0001 { - true => Self(-ask), - false => Self(ask), - } - } -} - -impl PartialEq for SpendAuthorizingKey { - fn eq(&self, other: &Self) -> bool { - self.ct_eq(other).unwrap_u8() == 1u8 - } -} - -impl PartialEq<[u8; 32]> for SpendAuthorizingKey { - fn eq(&self, other: &[u8; 32]) -> bool { - self.0.to_repr().ct_eq(other).unwrap_u8() == 1u8 - } -} - -/// A Spend validating key, as described in [protocol specification -/// §4.2.3][orchardkeycomponents]. -/// -/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents -#[derive(Copy, Clone, Debug)] -pub struct SpendValidatingKey(pub(crate) redpallas::VerificationKey); - -impl Eq for SpendValidatingKey {} - -impl From<[u8; 32]> for SpendValidatingKey { - fn from(bytes: [u8; 32]) -> Self { - Self(redpallas::VerificationKey::try_from(bytes).unwrap()) - } -} - -impl From for [u8; 32] { - fn from(ak: SpendValidatingKey) -> [u8; 32] { - ak.0.into() - } -} - -impl From for SpendValidatingKey { - fn from(ask: SpendAuthorizingKey) -> Self { - let sk = redpallas::SigningKey::::try_from(<[u8; 32]>::from(ask)).expect( - "a scalar converted to byte array and then converted back to a scalar should not fail", - ); - - Self(redpallas::VerificationKey::from(&sk)) - } -} - -impl PartialEq for SpendValidatingKey { - fn eq(&self, other: &Self) -> bool { - // XXX: These redpallas::VerificationKey(Bytes) fields are pub(crate) - self.0.bytes.bytes == other.0.bytes.bytes - } -} - -impl PartialEq<[u8; 32]> for SpendValidatingKey { - fn eq(&self, other: &[u8; 32]) -> bool { - // XXX: These redpallas::VerificationKey(Bytes) fields are pub(crate) - self.0.bytes.bytes == *other - } -} - -/// A Orchard nullifier deriving key, as described in [protocol specification -/// §4.2.3][orchardkeycomponents]. -/// -/// Used to create a _Nullifier_ per note. -/// -/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents -#[derive(Copy, Clone)] -pub struct NullifierDerivingKey(pub(crate) pallas::Base); - -impl ConstantTimeEq for NullifierDerivingKey { - /// Check whether two `NullifierDerivingKey`s are equal, runtime independent - /// of the value of the secret. - fn ct_eq(&self, other: &Self) -> Choice { - self.0.to_repr().ct_eq(&other.0.to_repr()) - } -} - -impl fmt::Debug for NullifierDerivingKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_tuple("NullifierDerivingKey") - .field(&hex::encode(self.0.to_repr())) - .finish() - } -} - -impl Eq for NullifierDerivingKey {} - -impl From for [u8; 32] { - fn from(nk: NullifierDerivingKey) -> [u8; 32] { - nk.0.to_repr() - } -} - -impl From<&NullifierDerivingKey> for [u8; 32] { - fn from(nk: &NullifierDerivingKey) -> [u8; 32] { - nk.0.to_repr() - } -} - -impl From for pallas::Base { - fn from(nk: NullifierDerivingKey) -> pallas::Base { - nk.0 - } -} - -impl From<[u8; 32]> for NullifierDerivingKey { - fn from(bytes: [u8; 32]) -> Self { - Self(pallas::Base::from_repr(bytes).unwrap()) - } -} - -impl From for NullifierDerivingKey { - /// nk = ToBase^Orchard(PRF^expand_sk (\[7\])) - /// - /// - fn from(sk: SpendingKey) -> Self { - Self(pallas::Base::from_bytes_wide(&prf_expand( - sk.into(), - vec![&[7]], - ))) - } -} - -impl PartialEq for NullifierDerivingKey { - fn eq(&self, other: &Self) -> bool { - self.ct_eq(other).unwrap_u8() == 1u8 - } -} - -impl PartialEq<[u8; 32]> for NullifierDerivingKey { - fn eq(&self, other: &[u8; 32]) -> bool { - self.0.to_repr().ct_eq(other).unwrap_u8() == 1u8 - } -} - -/// Commit^ivk randomness. -/// -/// -// XXX: Should this be replaced by commitment::CommitmentRandomness? -#[derive(Copy, Clone)] -pub struct IvkCommitRandomness(pub(crate) pallas::Scalar); - -impl ConstantTimeEq for IvkCommitRandomness { - /// Check whether two `IvkCommitRandomness`s are equal, runtime independent - /// of the value of the secret. - fn ct_eq(&self, other: &Self) -> Choice { - self.0.to_repr().ct_eq(&other.0.to_repr()) - } -} - -impl fmt::Debug for IvkCommitRandomness { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_tuple("IvkCommitRandomness") - .field(&hex::encode(self.0.to_repr())) - .finish() - } -} - -impl Eq for IvkCommitRandomness {} - -impl From for IvkCommitRandomness { - /// rivk = ToScalar^Orchard(PRF^expand_sk (\[8\])) - /// - /// - fn from(sk: SpendingKey) -> Self { - let scalar = pallas::Scalar::from_bytes_wide(&prf_expand(sk.into(), vec![&[8]])); - - Self(scalar) - } -} - -impl From for [u8; 32] { - fn from(rivk: IvkCommitRandomness) -> Self { - rivk.0.into() - } -} - -impl From for pallas::Scalar { - fn from(rivk: IvkCommitRandomness) -> Self { - rivk.0 - } -} - -impl PartialEq for IvkCommitRandomness { - fn eq(&self, other: &Self) -> bool { - self.ct_eq(other).unwrap_u8() == 1u8 - } -} - -impl PartialEq<[u8; 32]> for IvkCommitRandomness { - fn eq(&self, other: &[u8; 32]) -> bool { - self.0.to_repr().ct_eq(other).unwrap_u8() == 1u8 - } -} - -impl TryFrom<[u8; 32]> for IvkCommitRandomness { - type Error = &'static str; - - fn try_from(bytes: [u8; 32]) -> Result { - let possible_scalar = pallas::Scalar::from_repr(bytes); - - if possible_scalar.is_some().into() { - Ok(Self(possible_scalar.unwrap())) - } else { - Err("Invalid pallas::Scalar value") - } - } -} - -/// _Full viewing keys_ -/// -/// Allows recognizing both incoming and outgoing notes without having -/// spend authority. -/// -/// -#[derive(Copy, Clone)] -pub struct FullViewingKey { - spend_validating_key: SpendValidatingKey, - nullifier_deriving_key: NullifierDerivingKey, - ivk_commit_randomness: IvkCommitRandomness, -} - -impl FullViewingKey { - /// [4.2.3]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents - #[allow(non_snake_case)] - pub fn to_R(self) -> [u8; 64] { - // let K = I2LEBSP_l_sk(rivk) - let K: [u8; 32] = self.ivk_commit_randomness.into(); - - let mut t: Vec<&[u8]> = vec![&[0x82u8]]; - - let ak_bytes = <[u8; 32]>::from(self.spend_validating_key); - t.push(&ak_bytes); - - let nk_bytes = <[u8; 32]>::from(self.nullifier_deriving_key); - t.push(&nk_bytes); - - // let R = PRF^expand_K( [0x82] || I2LEOSP256(ak) || I2LEOSP256(nk) ) - prf_expand(K, t) - } -} - -impl ConstantTimeEq for FullViewingKey { - /// Check whether two `FullViewingKey`s are equal, runtime independent of - /// the value of the secrets. - /// - /// # Note - /// - /// This function short-circuits if the spend validating keys - /// are different. Otherwise, it should execute in time independent of the - /// secret component values. - fn ct_eq(&self, other: &Self) -> Choice { - if self.spend_validating_key != other.spend_validating_key { - return Choice::from(0); - } - - // Uses std::ops::BitAnd - self.nullifier_deriving_key - .ct_eq(&other.nullifier_deriving_key) - & self - .ivk_commit_randomness - .ct_eq(&other.ivk_commit_randomness) - } -} - -impl fmt::Debug for FullViewingKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("FullViewingKey") - .field("spend_validating_key", &self.spend_validating_key) - .field("nullifier_deriving_key", &self.nullifier_deriving_key) - .field("ivk_commit_randomness", &self.ivk_commit_randomness) - .finish() - } -} - -impl fmt::Display for FullViewingKey { - /// The _raw encoding_ of an **Orchard** _full viewing key_. - /// - /// - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(&hex::encode(<[u8; 96]>::from(*self))) - } -} - -impl From for [u8; 96] { - fn from(fvk: FullViewingKey) -> [u8; 96] { - let mut bytes = [0u8; 96]; - - bytes[..32].copy_from_slice(&<[u8; 32]>::from(fvk.spend_validating_key)); - bytes[32..64].copy_from_slice(&<[u8; 32]>::from(fvk.nullifier_deriving_key)); - bytes[64..].copy_from_slice(&<[u8; 32]>::from(fvk.ivk_commit_randomness)); - - bytes - } -} - -impl From for FullViewingKey { - /// Derive a _full viewing key_ from a existing _spending key_. - /// - /// - /// - fn from(sk: SpendingKey) -> FullViewingKey { - let spend_authorizing_key = SpendAuthorizingKey::from(sk); - - Self { - spend_validating_key: SpendValidatingKey::from(spend_authorizing_key), - nullifier_deriving_key: NullifierDerivingKey::from(sk), - ivk_commit_randomness: IvkCommitRandomness::from(sk), - } - } -} - -impl PartialEq for FullViewingKey { - fn eq(&self, other: &Self) -> bool { - self.ct_eq(other).unwrap_u8() == 1u8 - } -} - -/// An _incoming viewing key_, as described in [protocol specification -/// §4.2.3][ps]. -/// -/// Used to decrypt incoming notes without spending them. -/// -/// [ps]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents -#[derive(Copy, Clone)] -pub struct IncomingViewingKey { - dk: DiversifierKey, - // TODO: refine type, so that IncomingViewingkey.ivk cannot be 0 - ivk: pallas::Scalar, -} - -impl ConstantTimeEq for IncomingViewingKey { - /// Check whether two `IncomingViewingKey`s are equal, runtime independent - /// of the value of the secret. - /// - /// # Note - /// - /// This function should execute in time independent of the `dk` and `ivk` values. - fn ct_eq(&self, other: &Self) -> Choice { - <[u8; 64]>::from(*self).ct_eq(&<[u8; 64]>::from(*other)) - } -} - -impl fmt::Debug for IncomingViewingKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("IncomingViewingKey") - .field("dk", &self.dk) - .field("ivk", &self.ivk) - .finish() - } -} - -impl fmt::Display for IncomingViewingKey { - /// The _raw encoding_ of an **Orchard** _incoming viewing key_. - /// - /// - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(&hex::encode(<[u8; 64]>::from(*self))) - } -} - -impl Eq for IncomingViewingKey {} - -impl From for [u8; 64] { - fn from(ivk: IncomingViewingKey) -> [u8; 64] { - let mut bytes = [0u8; 64]; - - bytes[..32].copy_from_slice(&<[u8; 32]>::from(ivk.dk)); - bytes[32..].copy_from_slice(&<[u8; 32]>::from(ivk.ivk)); - - bytes - } -} - -impl TryFrom<[u8; 64]> for IncomingViewingKey { - type Error = &'static str; - - /// Convert an array of bytes into a [`IncomingViewingKey`]. - /// - /// Returns an error if the encoding is malformed or if it [encodes the scalar additive - /// identity, 0][1]. - /// - /// > ivk MUST be in the range {1 .. 𝑞P - 1} - /// - /// [1]: https://zips.z.cash/protocol/protocol.pdf#orchardinviewingkeyencoding - fn try_from(bytes: [u8; 64]) -> Result { - let mut dk_bytes = [0u8; 32]; - dk_bytes.copy_from_slice(&bytes[..32]); - let dk = DiversifierKey::from(dk_bytes); - - let mut ivk_bytes = [0u8; 32]; - ivk_bytes.copy_from_slice(&bytes[32..]); - - let possible_scalar = pallas::Scalar::from_repr(ivk_bytes); - - if possible_scalar.is_some().into() { - let scalar = possible_scalar.unwrap(); - if scalar.is_zero().into() { - Err("pallas::Scalar value for Orchard IncomingViewingKey is 0") - } else { - Ok(Self { - dk, - ivk: possible_scalar.unwrap(), - }) - } - } else { - Err("Invalid pallas::Scalar value for Orchard IncomingViewingKey") - } - } -} - -impl TryFrom for IncomingViewingKey { - type Error = &'static str; - - /// Commit^ivk_rivk(ak, nk) := - /// SinsemillaShortCommit_rcm(︁ - /// "z.cash:Orchard-CommitIvk", - /// I2LEBSP_l^Orchard_base(ak) || I2LEBSP_l^Orchard_base(nk)︁ - /// ) mod r_P - /// - /// - /// - #[allow(non_snake_case)] - #[allow(clippy::unwrap_in_result)] - fn try_from(fvk: FullViewingKey) -> Result { - let mut M: BitVec = BitVec::new(); - - // I2LEBSP_l^Orchard_base(ak)︁ - let ak_bytes = - extract_p(pallas::Point::from_bytes(&fvk.spend_validating_key.into()).unwrap()) - .to_repr(); - M.extend_from_bitslice(&BitArray::<_, Lsb0>::from(ak_bytes)[0..255]); - - // I2LEBSP_l^Orchard_base(nk)︁ - let nk_bytes: [u8; 32] = fvk.nullifier_deriving_key.into(); - M.extend_from_bitslice(&BitArray::<_, Lsb0>::from(nk_bytes)[0..255]); - - // Commit^ivk_rivk - // rivk needs to be 255 bits long - let commit_x = sinsemilla_short_commit( - fvk.ivk_commit_randomness.into(), - b"z.cash:Orchard-CommitIvk", - &M, - ) - .expect("deriving orchard commit^ivk should not output ⊥ "); - - let ivk_ctoption = pallas::Scalar::from_repr(commit_x.into()); - - // if ivk ∈ {0, ⊥}, discard this key - - // [`Scalar::is_zero()`] is constant-time under the hood, and ivk is mod r_P - if ivk_ctoption.is_some().into() && !::from(ivk_ctoption.unwrap().is_zero()) { - Ok(Self { - dk: fvk.into(), - ivk: ivk_ctoption.unwrap(), - }) - } else { - Err("generated ivk is the additive identity 0, invalid") - } - } -} - -impl PartialEq for IncomingViewingKey { - fn eq(&self, other: &Self) -> bool { - self.ct_eq(other).unwrap_u8() == 1u8 - } -} - -/// An outgoing viewing key, as described in [protocol specification -/// §4.2.3][ps]. -/// -/// Used to decrypt outgoing notes without spending them. -/// -/// [ps]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents -#[derive(Copy, Clone)] -pub struct OutgoingViewingKey(pub(crate) [u8; 32]); - -impl ConstantTimeEq for OutgoingViewingKey { - /// Check whether two `OutgoingViewingKey`s are equal, runtime independent - /// of the value of the secret. - fn ct_eq(&self, other: &Self) -> Choice { - self.0.ct_eq(&other.0) - } -} - -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 Eq for OutgoingViewingKey {} - -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 { - /// Derive an `OutgoingViewingKey` from a `FullViewingKey`. - /// - /// [4.2.3]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents - #[allow(non_snake_case)] - fn from(fvk: FullViewingKey) -> OutgoingViewingKey { - let R = fvk.to_R(); - - // let ovk be the remaining [32] bytes of R [which is 64 bytes] - let ovk_bytes: [u8; 32] = R[32..64].try_into().expect("32 byte array"); - - Self::from(ovk_bytes) - } -} - -impl PartialEq for OutgoingViewingKey { - fn eq(&self, other: &Self) -> bool { - self.ct_eq(other).unwrap_u8() == 1u8 - } -} - -impl PartialEq<[u8; 32]> for OutgoingViewingKey { - fn eq(&self, other: &[u8; 32]) -> bool { - self.0.ct_eq(other).unwrap_u8() == 1u8 - } -} - -/// A _diversifier key_. -/// -/// "We define a mechanism for deterministically deriving a sequence of -/// diversifiers, without leaking how many diversified addresses have already -/// been generated for an account. Unlike Sapling, we do so by deriving a -/// _diversifier key_ directly from the _full viewing key_, instead of as part -/// of the _extended spending key_. This means that the _full viewing key_ -/// provides the capability to determine the position of a _diversifier_ within -/// the sequence, which matches the capabilities of a Sapling _extended full -/// viewing key_ but simplifies the key structure." -/// -/// [4.2.3]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents -/// [ZIP-32]: https://zips.z.cash/zip-0032#orchard-diversifier-derivation -#[derive(Copy, Clone, Debug)] -pub struct DiversifierKey([u8; 32]); - -impl ConstantTimeEq for DiversifierKey { - /// Check whether two `DiversifierKey`s are equal, runtime independent of - /// the value of the secret. - fn ct_eq(&self, other: &Self) -> Choice { - self.0.ct_eq(&other.0) - } -} - -impl Eq for DiversifierKey {} - -impl From for DiversifierKey { - /// Derives a _diversifier key_ from a `FullViewingKey`. - /// - /// '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...' - /// - /// Derived as specified in section [4.2.3] of the spec, and [ZIP-32]. - /// - /// [4.2.3]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents - /// [ZIP-32]: https://zips.z.cash/zip-0032#orchard-diversifier-derivation - #[allow(non_snake_case)] - fn from(fvk: FullViewingKey) -> DiversifierKey { - let R = fvk.to_R(); - - // "let dk be the first [32] bytes of R" - Self(R[..32].try_into().expect("subslice of R is a valid array")) - } -} - -impl From<[u8; 32]> for DiversifierKey { - fn from(bytes: [u8; 32]) -> DiversifierKey { - DiversifierKey(bytes) - } -} - -impl From for [u8; 32] { - fn from(dk: DiversifierKey) -> [u8; 32] { - dk.0 - } -} - -impl PartialEq for DiversifierKey { - fn eq(&self, other: &Self) -> bool { - self.ct_eq(other).unwrap_u8() == 1u8 - } -} - -impl PartialEq<[u8; 32]> for DiversifierKey { - fn eq(&self, other: &[u8; 32]) -> bool { - self.0.ct_eq(other).unwrap_u8() == 1u8 - } -} - /// A _diversifier_, as described in [protocol specification §4.2.3][ps]. /// /// [ps]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents @@ -884,16 +61,6 @@ impl From<[u8; 11]> for Diversifier { } } -impl From for Diversifier { - /// Generates the _default diversifier_, where the index into the - /// `DiversifierKey` is 0. - /// - /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents - fn from(dk: DiversifierKey) -> Self { - Self(prp_d(dk.into(), [0u8; 11])) - } -} - impl From for [u8; 11] { fn from(d: Diversifier) -> [u8; 11] { d.0 @@ -982,99 +149,18 @@ impl fmt::Debug for TransmissionKey { 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. - fn from(bytes: [u8; 32]) -> Self { - Self(pallas::Affine::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^Orchard.DerivePublic(ivk, G_d)_, which is just a - /// scalar mult _\[ivk\]G_d_. - /// - /// KA^Orchard.DerivePublic(sk, B) := \[sk\] B - /// - /// - /// - fn from((ivk, d): (IncomingViewingKey, Diversifier)) -> Self { - let g_d = pallas::Point::from(d); - - Self(pallas::Affine::from(g_d * ivk.ivk)) - } -} - impl PartialEq<[u8; 32]> for TransmissionKey { fn eq(&self, other: &[u8; 32]) -> bool { &self.0.to_bytes() == other } } -/// An _outgoing cipher key_ for Orchard note encryption/decryption. -/// -/// -// TODO: derive `OutgoingCipherKey`: https://github.com/ZcashFoundation/zebra/issues/2041 -#[derive(Copy, Clone, PartialEq, Eq)] -pub struct OutgoingCipherKey([u8; 32]); - -impl fmt::Debug for OutgoingCipherKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_tuple("OutgoingCipherKey") - .field(&hex::encode(self.0)) - .finish() - } -} - -impl From<&OutgoingCipherKey> for [u8; 32] { - fn from(ock: &OutgoingCipherKey) -> [u8; 32] { - ock.0 - } -} - -// TODO: implement PrivateKey: #2192 - -/// An _ephemeral private key_ for Orchard key agreement. -/// -/// -/// -// TODO: refine so that the inner `Scalar` != 0 -#[derive(Copy, Clone, Debug)] -pub struct EphemeralPrivateKey(pub(crate) pallas::Scalar); - -impl ConstantTimeEq for EphemeralPrivateKey { - /// Check whether two `EphemeralPrivateKey`s are equal, runtime independent - /// of the value of the secret. - fn ct_eq(&self, other: &Self) -> Choice { - self.0.to_repr().ct_eq(&other.0.to_repr()) - } -} - -impl Eq for EphemeralPrivateKey {} - -impl From for [u8; 32] { - fn from(esk: EphemeralPrivateKey) -> Self { - esk.0.to_repr() - } -} - -impl PartialEq for EphemeralPrivateKey { - fn eq(&self, other: &Self) -> bool { - self.ct_eq(other).unwrap_u8() == 1u8 - } -} - -impl PartialEq<[u8; 32]> for EphemeralPrivateKey { - fn eq(&self, other: &[u8; 32]) -> bool { - self.0.to_repr().ct_eq(other).unwrap_u8() == 1u8 - } -} /// An ephemeral public key for Orchard key agreement. /// /// diff --git a/zebra-chain/src/orchard/keys/tests.rs b/zebra-chain/src/orchard/keys/tests.rs deleted file mode 100644 index 48c7c9b61..000000000 --- a/zebra-chain/src/orchard/keys/tests.rs +++ /dev/null @@ -1,103 +0,0 @@ -#![allow(clippy::module_inception)] - -use super::*; -use crate::orchard::tests::vectors::KEY_COMPONENTS; - -use proptest::prelude::*; - -#[test] -fn generate_keys_from_test_vectors() { - let _init_guard = zebra_test::init(); - - for test_vector in KEY_COMPONENTS.iter() { - let spending_key = SpendingKey::from_bytes(test_vector.sk, Network::Mainnet); - - let spend_authorizing_key = SpendAuthorizingKey::from(spending_key); - assert_eq!(spend_authorizing_key, test_vector.ask); - - let spend_validating_key = SpendValidatingKey::from(spend_authorizing_key); - assert_eq!(<[u8; 32]>::from(spend_validating_key), test_vector.ak); - - let nullifier_deriving_key = NullifierDerivingKey::from(spending_key); - assert_eq!(nullifier_deriving_key, test_vector.nk); - - let ivk_commit_randomness = IvkCommitRandomness::from(spending_key); - assert_eq!(ivk_commit_randomness, test_vector.rivk); - - let full_viewing_key = FullViewingKey { - spend_validating_key, - nullifier_deriving_key, - ivk_commit_randomness, - }; - - let diversifier_key = DiversifierKey::from(full_viewing_key); - assert_eq!(diversifier_key, test_vector.dk); - - let incoming_viewing_key = - IncomingViewingKey::try_from(full_viewing_key).expect("a valid incoming viewing key"); - assert_eq!(<[u8; 32]>::from(incoming_viewing_key.ivk), test_vector.ivk); - - let outgoing_viewing_key = OutgoingViewingKey::from(full_viewing_key); - assert_eq!(outgoing_viewing_key, test_vector.ovk); - - let diversifier = Diversifier::from(diversifier_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); - } -} - -proptest! { - - #[test] - #[allow(clippy::clone_on_copy, clippy::cmp_owned)] - fn generate_keys(spending_key in any::()) { - let _init_guard = zebra_test::init(); - - // Test ConstantTimeEq, Eq, PartialEq - assert_eq!(spending_key, SpendingKey::from_bytes(spending_key.bytes, spending_key.network)); - - let spend_authorizing_key = SpendAuthorizingKey::from(spending_key); - // Test ConstantTimeEq, Eq, PartialEq - assert_eq!(spend_authorizing_key, spend_authorizing_key.clone()); - - // ConstantTimeEq not implemented as it's a public value - let spend_validating_key = SpendValidatingKey::from(spend_authorizing_key); - - let nullifier_deriving_key = NullifierDerivingKey::from(spending_key); - // Test ConstantTimeEq, Eq, PartialEq - assert_eq!(nullifier_deriving_key, nullifier_deriving_key.clone()); - - let ivk_commit_randomness = IvkCommitRandomness::from(spending_key); - // Test ConstantTimeEq, Eq, PartialEq - assert_eq!(ivk_commit_randomness, ivk_commit_randomness.clone()); - - let full_viewing_key = FullViewingKey { - spend_validating_key, - nullifier_deriving_key, - ivk_commit_randomness, - }; - // Test ConstantTimeEq, Eq, PartialEq - assert_eq!(full_viewing_key, full_viewing_key.clone()); - - let diversifier_key = DiversifierKey::from(full_viewing_key); - // Test ConstantTimeEq, Eq, PartialEq - assert_eq!(diversifier_key, diversifier_key.clone()); - - let incoming_viewing_key = IncomingViewingKey::try_from(full_viewing_key).expect("a valid incoming viewing key"); - // Test ConstantTimeEq, Eq, PartialEq - assert_eq!(incoming_viewing_key, incoming_viewing_key.clone()); - - let outgoing_viewing_key = OutgoingViewingKey::from(full_viewing_key); - // Test ConstantTimeEq, Eq, PartialEq - assert_eq!(outgoing_viewing_key, outgoing_viewing_key.clone()); - - // ConstantTimeEq not implemented for Diversifier as it's a public value - let diversifier = Diversifier::from(diversifier_key); - - // ConstantTimeEq not implemented as it's a public value - let _transmission_key = TransmissionKey::from((incoming_viewing_key, diversifier)); - - } -} diff --git a/zebra-chain/src/orchard/note.rs b/zebra-chain/src/orchard/note.rs index 811639b13..5eb78236b 100644 --- a/zebra-chain/src/orchard/note.rs +++ b/zebra-chain/src/orchard/note.rs @@ -1,23 +1,22 @@ //! Orchard notes -#![allow(clippy::unit_arg)] - use group::{ff::PrimeField, GroupEncoding}; -use halo2::{arithmetic::FieldExt, pasta::pallas}; +use halo2::pasta::pallas; use rand_core::{CryptoRng, RngCore}; use crate::amount::{Amount, NonNegative}; -use super::{address::Address, keys::prf_expand, sinsemilla::extract_p}; +use super::{address::Address, sinsemilla::extract_p}; -#[cfg(any(test, feature = "proptest-impl"))] -mod arbitrary; mod ciphertexts; mod nullifiers; pub use ciphertexts::{EncryptedNote, WrappedNoteKey}; pub use nullifiers::Nullifier; +#[cfg(any(test, feature = "proptest-impl"))] +mod arbitrary; + #[derive(Clone, Copy, Debug)] /// A random seed (rseed) used in the Orchard note creation. pub struct SeedRandomness(pub(crate) [u8; 32]); @@ -81,18 +80,6 @@ impl From for [u8; 32] { } } -impl From for Psi { - /// rcm = ToScalar^Orchard((PRF^expand_rseed (\[9\])) - /// - /// - fn from(rseed: SeedRandomness) -> Self { - Self(pallas::Base::from_bytes_wide(&prf_expand( - rseed.0, - vec![&[9]], - ))) - } -} - /// A Note represents that a value is spendable by the recipient who holds the /// spending key corresponding to a given shielded payment address. /// diff --git a/zebra-chain/src/orchard/note/nullifiers.rs b/zebra-chain/src/orchard/note/nullifiers.rs index d739fda0e..e9a9109a5 100644 --- a/zebra-chain/src/orchard/note/nullifiers.rs +++ b/zebra-chain/src/orchard/note/nullifiers.rs @@ -1,46 +1,11 @@ -#![allow(clippy::unit_arg)] +//! Orchard nullifier types and conversions. -use std::{ - convert::TryFrom, - hash::{Hash, Hasher}, -}; +use std::hash::{Hash, Hasher}; use halo2::pasta::{group::ff::PrimeField, pallas}; use crate::serialization::{serde_helpers, SerializationError}; -use super::super::{ - commitment::NoteCommitment, - keys::NullifierDerivingKey, - note::{Note, Psi}, - sinsemilla::*, -}; - -/// A cryptographic permutation, defined in [poseidonhash]. -/// -/// PoseidonHash(x, y) = f([x, y, 0])_1 (using 1-based indexing). -/// -/// [poseidonhash]: https://zips.z.cash/protocol/nu5.pdf#poseidonhash -fn poseidon_hash(_x: pallas::Base, _y: pallas::Base) -> pallas::Base { - // TODO: implement: #2064 - unimplemented!("PoseidonHash is not yet implemented (#2064)") -} - -/// Used as part of deriving the _nullifier_ for a Orchard _note_. -/// -/// PRF^nfOrchard: F_𝑞P × F_𝑞P → F_𝑞P -/// -/// Instantiated using the PoseidonHash hash function defined in [§5.4.1.10 -/// ‘PoseidonHash Function’][poseidonhash]: -/// -/// PRF^nfOrchard(nk*, ρ*) := PoseidonHash(nk*, ρ*) -/// -/// [concreteprfs]: https://zips.z.cash/protocol/nu5.pdf#concreteprfs -/// [poseidonhash]: https://zips.z.cash/protocol/nu5.pdf#poseidonhash -fn prf_nf(nk: pallas::Base, rho: pallas::Base) -> pallas::Base { - poseidon_hash(nk, rho) -} - /// A Nullifier for Orchard transactions #[derive(Clone, Copy, Debug, Eq, Serialize, Deserialize)] pub struct Nullifier(#[serde(with = "serde_helpers::Base")] pub(crate) pallas::Base); @@ -73,33 +38,6 @@ impl PartialEq for Nullifier { } } -impl From<(NullifierDerivingKey, Note, NoteCommitment)> for Nullifier { - /// Derive a `Nullifier` for an Orchard _note_. - /// - /// nk is the _nullifier deriving key_ associated with the _note_; ρ and ψ - /// are part of the _note_; and cm is the _note commitment_. - /// - /// DeriveNullifier_nk(ρ, ψ, cm) = Extract_P(︀ [︀ (PRF^nfOrchard_nk(ρ) + ψ) mod q_P ]︀ K^Orchard + cm)︀ - /// - /// - #[allow(non_snake_case)] - // TODO: tidy prf_nf, notes/rho/psi - fn from((nk, note, cm): (NullifierDerivingKey, Note, NoteCommitment)) -> Self { - let K = pallas_group_hash(b"z.cash:Orchard", b"K"); - - let psi: Psi = note.rseed.into(); - - // impl Add for pallas::Base reduces by the modulus (q_P) - // - // [︀ (PRF^nfOrchard_nk(ρ) + ψ) mod q_P ]︀ K^Orchard + cm - let scalar = - pallas::Scalar::from_repr((prf_nf(nk.0, note.rho.0) + psi.0).to_repr()).unwrap(); - - // Basically a new-gen Pedersen hash? - Nullifier(extract_p((K * scalar) + cm.0)) - } -} - impl From for [u8; 32] { fn from(n: Nullifier) -> Self { n.0.into() diff --git a/zebra-chain/src/orchard/sinsemilla.rs b/zebra-chain/src/orchard/sinsemilla.rs index df4b61638..060fbfb39 100644 --- a/zebra-chain/src/orchard/sinsemilla.rs +++ b/zebra-chain/src/orchard/sinsemilla.rs @@ -159,37 +159,6 @@ pub fn sinsemilla_hash(D: &[u8], M: &BitVec) -> Option { extract_p_bottom(sinsemilla_hash_to_point(D, M)) } -/// Sinsemilla commit -/// -/// We construct Sinsemilla commitments by hashing to a point with Sinsemilla -/// hash, and adding a randomized point on the Pallas curve (with complete -/// addition, vs incomplete addition as used in [`sinsemilla_hash_to_point`]). -/// -/// SinsemillaCommit_r(D, M) := SinsemillaHashToPoint(D || "-M", M) + \[r\]GroupHash^P(D || "-r", "") -/// -/// -#[allow(non_snake_case)] -pub fn sinsemilla_commit( - r: pallas::Scalar, - D: &[u8], - M: &BitVec, -) -> Option { - sinsemilla_hash_to_point(&[D, b"-M"].concat(), M) - .map(|point| point + pallas_group_hash(&[D, b"-r"].concat(), b"") * r) -} - -/// SinsemillaShortCommit_r(D, M) := Extract⊥ P(SinsemillaCommit_r(D, M)) -/// -/// -#[allow(non_snake_case)] -pub fn sinsemilla_short_commit( - r: pallas::Scalar, - D: &[u8], - M: &BitVec, -) -> Option { - extract_p_bottom(sinsemilla_commit(r, D, M)) -} - // TODO: test the above correctness and compatibility with the zcash-hackworks test vectors // https://github.com/ZcashFoundation/zebra/issues/2079 // https://github.com/zcash-hackworks/zcash-test-vectors/pulls diff --git a/zebra-chain/src/sapling.rs b/zebra-chain/src/sapling.rs index c96233b0d..11bfd899d 100644 --- a/zebra-chain/src/sapling.rs +++ b/zebra-chain/src/sapling.rs @@ -10,23 +10,20 @@ //! points occur, and non-canonical point encodings are rejected. This is //! enforced by the jubjub crate, which is also used by the redjubjub crate. -mod address; -#[cfg(any(test, feature = "proptest-impl"))] -mod arbitrary; mod commitment; mod note; + +#[cfg(any(test, feature = "proptest-impl"))] +mod arbitrary; #[cfg(test)] mod tests; -// XXX clean up these modules - pub mod keys; pub mod output; pub mod shielded_data; pub mod spend; pub mod tree; -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/sapling/address.rs b/zebra-chain/src/sapling/address.rs deleted file mode 100644 index ebc9a4163..000000000 --- a/zebra-chain/src/sapling/address.rs +++ /dev/null @@ -1,182 +0,0 @@ -//! Shielded addresses. - -use std::{ - convert::TryFrom, - 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 = "zs"; - pub const TESTNET: &str = "ztestsapling"; -} - -/// A Sapling _shielded payment address_. -/// -/// Also known as a _diversified payment address_ for Sapling, as -/// defined in [§4.2.2][4.2.2]. -/// -/// [4.2.2]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents -#[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("SaplingAddress") - .field("network", &self.network) - .field("diversifier", &self.diversifier) - .field("transmission_key", &self.transmission_key) - .finish() - } -} - -impl fmt::Display for Address { - #[allow(clippy::unwrap_in_result)] - 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) - .expect("hrp is valid") - } -} - -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).map_err(|_| { - SerializationError::Parse("bech32::decode guarantees valid base32") - })?); - - 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::try_from(transmission_key_bytes) - .map_err(|_| SerializationError::Parse("invalid 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() { - let _init_guard = zebra_test::init(); - - let zs_addr: Address = - "zs1qqqqqqqqqqqqqqqqqrjq05nyfku05msvu49mawhg6kr0wwljahypwyk2h88z6975u563j8nfaxd" - .parse() - .unwrap(); - - assert_eq!( - format!("{}", zs_addr), - "zs1qqqqqqqqqqqqqqqqqrjq05nyfku05msvu49mawhg6kr0wwljahypwyk2h88z6975u563j8nfaxd" - ); - } - - #[test] - fn derive_keys_and_addresses() { - let _init_guard = 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::try_from((incoming_viewing_key, diversifier)) - .expect("should be a valid transmission key"); - - let _sapling_shielded_address = Address { - network: Network::Mainnet, - diversifier, - transmission_key, - }; - } -} - -#[cfg(test)] -proptest! { - - #[test] - fn sapling_address_roundtrip(zaddr in any::
()) { - let _init_guard = zebra_test::init(); - - let string = zaddr.to_string(); - - let zaddr2 = string.parse::
() - .expect("randomized sapling z-addr should deserialize"); - - prop_assert_eq![zaddr, zaddr2]; - } -} diff --git a/zebra-chain/src/sapling/commitment.rs b/zebra-chain/src/sapling/commitment.rs index 65e54d988..9e0c5983b 100644 --- a/zebra-chain/src/sapling/commitment.rs +++ b/zebra-chain/src/sapling/commitment.rs @@ -1,10 +1,5 @@ //! Note and value commitments. -#[cfg(test)] -mod test_vectors; - -pub mod pedersen_hashes; - use std::{ convert::{TryFrom, TryInto}, fmt, io, @@ -24,6 +19,11 @@ use crate::{ use super::keys::{find_group_hash, Diversifier, TransmissionKey}; +pub mod pedersen_hashes; + +#[cfg(test)] +mod test_vectors; + use pedersen_hashes::*; /// Generates a random scalar from the scalar field 𝔽_{r_𝕁}. diff --git a/zebra-chain/src/sapling/commitment/pedersen_hashes.rs b/zebra-chain/src/sapling/commitment/pedersen_hashes.rs index 5e41854e8..4c033435f 100644 --- a/zebra-chain/src/sapling/commitment/pedersen_hashes.rs +++ b/zebra-chain/src/sapling/commitment/pedersen_hashes.rs @@ -98,22 +98,6 @@ 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_", "") -/// -/// -#[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. /// diff --git a/zebra-chain/src/sapling/commitment/test_vectors.rs b/zebra-chain/src/sapling/commitment/test_vectors.rs index b33c6a810..3e413f9f4 100644 --- a/zebra-chain/src/sapling/commitment/test_vectors.rs +++ b/zebra-chain/src/sapling/commitment/test_vectors.rs @@ -1,13 +1,15 @@ -// 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. +//! 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; diff --git a/zebra-chain/src/sapling/keys.rs b/zebra-chain/src/sapling/keys.rs index 548c66486..fabe2e297 100644 --- a/zebra-chain/src/sapling/keys.rs +++ b/zebra-chain/src/sapling/keys.rs @@ -1,4 +1,6 @@ -//! Key types. +//! Sapling key types. +//! +//! Unused key types are not implemented, see PR #5476. //! //! "The spend authorizing key ask, proof authorizing key (ak, nsk), //! full viewing key (ak, nk, ovk), incoming viewing key ivk, and each @@ -7,32 +9,21 @@ //! //! [ps]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents //! [3.1]: https://zips.z.cash/protocol/protocol.pdf#addressesandkeys -#![allow(clippy::unit_arg)] -#![allow(clippy::fallible_impl_from)] -#[cfg(test)] -mod test_vectors; -#[cfg(test)] -mod tests; +use std::{fmt, io}; -use std::{ - fmt, - io::{self, Write}, - str::FromStr, -}; - -use bech32::{self, FromBase32, ToBase32, Variant}; use rand_core::{CryptoRng, RngCore}; -use subtle::{Choice, ConstantTimeEq}; use crate::{ - parameters::Network, primitives::redjubjub::{self, SpendAuth}, serialization::{ serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize, }, }; +#[cfg(test)] +mod test_vectors; + /// The [Randomness Beacon][1] ("URS"). /// /// First 64 bytes of the BLAKE2s input during JubJub group hash. URS @@ -46,42 +37,6 @@ use crate::{ pub(super) const RANDOMNESS_BEACON_URS: &[u8; 64] = b"096b36a5804bfacef1691e173c366a47ff5ba84a44f26ddd7e8d9f79d5b42df0"; -/// Invokes Blake2b-512 as PRF^expand with parameter t, to derive a -/// SpendAuthorizingKey and ProofAuthorizingKey from SpendingKey. -/// -/// PRF^expand(sk, t) := BLAKE2b-512("Zcash_ExpandSeed", sk || t) -/// -/// -fn prf_expand(sk: [u8; 32], t: &[u8]) -> [u8; 64] { - let hash = blake2b_simd::Params::new() - .hash_length(64) - .personal(b"Zcash_ExpandSeed") - .to_state() - .update(&sk[..]) - .update(t) - .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)_ -/// -/// -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() -} - /// GroupHash into Jubjub, aka _GroupHash_URS_ /// /// Produces a random point in the Jubjub curve. The point is @@ -144,14 +99,6 @@ pub(super) fn find_group_hash(d: [u8; 8], m: &[u8]) -> jubjub::ExtendedPoint { } } -/// Instance of FindGroupHash for JubJub, using personalized by -/// BLAKE2s for picking the proof generation key base point. -/// -/// -fn zcash_h() -> jubjub::ExtendedPoint { - find_group_hash(*b"Zcash_H_", b"") -} - /// Used to derive a diversified base point from a diversifier value. /// /// @@ -159,551 +106,6 @@ 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)] -#[cfg_attr( - any(test, feature = "proptest-impl"), - derive(proptest_derive::Arbitrary) -)] -pub struct SpendingKey { - network: Network, - bytes: [u8; 32], -} - -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) - } -} - -impl ConstantTimeEq for SpendingKey { - /// Check whether two `SpendingKey`s are equal, runtime independent of the - /// value of the secret. - /// - /// # Note - /// - /// This function short-circuits if the networks of the keys are different. - /// Otherwise, it should execute in time independent of the `bytes` value. - fn ct_eq(&self, other: &Self) -> Choice { - if self.network != other.network { - return Choice::from(0); - } - - self.bytes.ct_eq(&other.bytes) - } -} - -impl Eq for SpendingKey {} - -// 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 { - #[allow(clippy::unwrap_in_result)] - 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) - .expect("hrp is valid") - } -} - -impl FromStr for SpendingKey { - type Err = SerializationError; - - #[allow(clippy::unwrap_in_result)] - fn from_str(s: &str) -> Result { - match bech32::decode(s) { - Ok((hrp, bytes, Variant::Bech32)) => { - let decoded = - Vec::::from_base32(&bytes).expect("bech32::decode guarantees valid base32"); - - 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 PartialEq for SpendingKey { - fn eq(&self, other: &Self) -> bool { - self.ct_eq(other).unwrap_u8() == 1u8 - } -} - -/// 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)] -pub struct SpendAuthorizingKey(pub(crate) Scalar); - -impl ConstantTimeEq for SpendAuthorizingKey { - /// Check whether two `SpendAuthorizingKey`s are equal, runtime independent - /// of the value of the secret. - fn ct_eq(&self, other: &Self) -> Choice { - self.0.to_bytes().ct_eq(&other.0.to_bytes()) - } -} - -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 Eq for SpendAuthorizingKey {} - -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. - /// - /// - /// - fn from(spending_key: SpendingKey) -> SpendAuthorizingKey { - let hash_bytes = prf_expand(spending_key.bytes, &[0]); - - Self(Scalar::from_bytes_wide(&hash_bytes)) - } -} - -impl PartialEq for SpendAuthorizingKey { - fn eq(&self, other: &Self) -> bool { - self.ct_eq(other).unwrap_u8() == 1u8 - } -} - -impl PartialEq<[u8; 32]> for SpendAuthorizingKey { - fn eq(&self, other: &[u8; 32]) -> bool { - self.0.to_bytes().ct_eq(other).unwrap_u8() == 1u8 - } -} - -/// 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)] -pub struct ProofAuthorizingKey(pub(crate) Scalar); - -impl ConstantTimeEq for ProofAuthorizingKey { - /// Check whether two `ProofAuthorizingKey`s are equal, runtime independent - /// of the value of the secret. - fn ct_eq(&self, other: &Self) -> Choice { - self.0.to_bytes().ct_eq(&other.0.to_bytes()) - } -} - -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 Eq for ProofAuthorizingKey {} - -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. - /// - /// - /// - fn from(spending_key: SpendingKey) -> ProofAuthorizingKey { - let hash_bytes = prf_expand(spending_key.bytes, &[1]); - - Self(Scalar::from_bytes_wide(&hash_bytes)) - } -} - -impl PartialEq for ProofAuthorizingKey { - fn eq(&self, other: &Self) -> bool { - self.ct_eq(other).unwrap_u8() == 1u8 - } -} - -impl PartialEq<[u8; 32]> for ProofAuthorizingKey { - fn eq(&self, other: &[u8; 32]) -> bool { - self.0.to_bytes().ct_eq(other).unwrap_u8() == 1u8 - } -} - -/// 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(crate) [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. - /// - /// - /// - 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(crate) redjubjub::VerificationKey); - -impl Eq for AuthorizingKey {} - -impl TryFrom<[u8; 32]> for AuthorizingKey { - type Error = &'static str; - - /// Convert an array into an AuthorizingKey. - /// - /// Returns an error if the encoding is malformed or if [it does not encode - /// a prime-order point][1]: - /// - /// > When decoding this representation, the key MUST be considered invalid - /// > if abst_J returns ⊥ for either ak or nk, or if ak not in J^{(r)*} - /// - /// [1]: https://zips.z.cash/protocol/protocol.pdf#saplingfullviewingkeyencoding - fn try_from(bytes: [u8; 32]) -> Result { - let affine_point = jubjub::AffinePoint::from_bytes(bytes); - if affine_point.is_none().into() { - return Err("Invalid jubjub::AffinePoint for Sapling AuthorizingKey"); - } - if affine_point.unwrap().is_prime_order().into() { - Ok(Self(redjubjub::VerificationKey::try_from(bytes).map_err( - |_e| "Invalid jubjub::AffinePoint for Sapling AuthorizingKey", - )?)) - } else { - Err("jubjub::AffinePoint value for Sapling AuthorizingKey is not of prime order") - } - } -} - -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)).expect( - "a scalar converted to byte array and then converted back to a scalar should not fail", - ); - Self(redjubjub::VerificationKey::from(&sk)) - } -} - -impl PartialEq for AuthorizingKey { - fn eq(&self, other: &Self) -> bool { - self == &<[u8; 32]>::from(*other) - } -} - -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)] -pub struct NullifierDerivingKey(pub(crate) jubjub::AffinePoint); - -impl ConstantTimeEq for NullifierDerivingKey { - /// Check whether two `NullifierDerivingKey`s are equal, runtime independent - /// of the value of the secret. - fn ct_eq(&self, other: &Self) -> Choice { - self.0.to_bytes().ct_eq(&other.0.to_bytes()) - } -} - -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 - /// - /// - /// - /// - 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 for NullifierDerivingKey { - fn eq(&self, other: &Self) -> bool { - self.ct_eq(other).unwrap_u8() == 1u8 - } -} - -impl PartialEq<[u8; 32]> for NullifierDerivingKey { - fn eq(&self, other: &[u8; 32]) -> bool { - self.0.to_bytes().ct_eq(other).unwrap_u8() == 1u8 - } -} - -/// 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 { - #[allow(clippy::unwrap_in_result)] - 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) - .expect("hrp is valid") - } -} - -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_. - /// - /// - /// - /// - // 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; - - #[allow(clippy::unwrap_in_result)] - fn from_str(s: &str) -> Result { - match bech32::decode(s) { - Ok((hrp, bytes, Variant::Bech32)) => { - let decoded = - Vec::::from_base32(&bytes).expect("bech32::decode guarantees valid base32"); - - 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 @@ -765,35 +167,6 @@ impl TryFrom for jubjub::ExtendedPoint { } } -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 @@ -879,123 +252,12 @@ impl From for [u8; 32] { } } -impl TryFrom<(IncomingViewingKey, Diversifier)> for TransmissionKey { - type Error = &'static str; - - /// This includes _KA^Sapling.DerivePublic(ivk, G_d)_, which is just a - /// scalar mult _\[ivk\]G_d_. - /// - /// - /// - fn try_from((ivk, d): (IncomingViewingKey, Diversifier)) -> Result { - let affine_point = jubjub::AffinePoint::from( - diversify_hash(d.0).ok_or("invalid diversifier")? * ivk.scalar, - ); - // We need to ensure that the result point is in the prime-order subgroup. - // Since diversify_hash() returns a point in the prime-order subgroup, - // then the result point will also be in the prime-order subgroup - // and there is no need to check anything. - Ok(Self(affine_point)) - } -} - impl PartialEq<[u8; 32]> for TransmissionKey { fn eq(&self, other: &[u8; 32]) -> bool { &self.0.to_bytes() == 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". -/// -/// -#[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 { - #[allow(clippy::unwrap_in_result)] - 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) - .expect("hrp is valid") - } -} - -impl FromStr for FullViewingKey { - type Err = SerializationError; - - #[allow(clippy::unwrap_in_result)] - 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).expect("bech32::decode guarantees valid base32"), - ); - - 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::try_from(authorizing_key_bytes) - .map_err(SerializationError::Parse)?, - 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][1] for Sapling key agreement. /// /// Public keys containing points of small order are not allowed. diff --git a/zebra-chain/src/sapling/keys/test_vectors.rs b/zebra-chain/src/sapling/keys/test_vectors.rs index 8e18a8af1..7741b7014 100644 --- a/zebra-chain/src/sapling/keys/test_vectors.rs +++ b/zebra-chain/src/sapling/keys/test_vectors.rs @@ -1,4 +1,8 @@ -// Generated from https://github.com/zcash-hackworks/zcash-test-vectors/blob/07dc43fd90cd78a0b45b2eb5d2be3ce3c1841603/sapling_key_components.py +//! Sapling key test vectors. +//! +//! Generated from https://github.com/zcash-hackworks/zcash-test-vectors/blob/07dc43fd90cd78a0b45b2eb5d2be3ce3c1841603/sapling_key_components.py + +#![allow(dead_code)] pub struct TestVector { pub sk: [u8; 32], diff --git a/zebra-chain/src/sapling/keys/tests.rs b/zebra-chain/src/sapling/keys/tests.rs deleted file mode 100644 index 5134107df..000000000 --- a/zebra-chain/src/sapling/keys/tests.rs +++ /dev/null @@ -1,114 +0,0 @@ -#![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::try_from((incoming_viewing_key, diversifier)).unwrap() - }) - .boxed() - } - - type Strategy = BoxedStrategy; -} - -#[cfg(test)] -mod tests { - - use super::*; - - #[test] - fn derive_for_each_test_vector() { - let _init_guard = 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::try_from((incoming_viewing_key, diversifier)) - .expect("should be a valid transmission key"); - 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::()) { - let _init_guard = 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/sapling/note.rs b/zebra-chain/src/sapling/note.rs index 72f67bc02..d8e9d99e5 100644 --- a/zebra-chain/src/sapling/note.rs +++ b/zebra-chain/src/sapling/note.rs @@ -1,7 +1,5 @@ //! Sapling notes -#![allow(clippy::unit_arg)] - mod ciphertexts; mod nullifiers; diff --git a/zebra-chain/src/sapling/note/nullifiers.rs b/zebra-chain/src/sapling/note/nullifiers.rs index 7cb4776a3..78b845534 100644 --- a/zebra-chain/src/sapling/note/nullifiers.rs +++ b/zebra-chain/src/sapling/note/nullifiers.rs @@ -1,28 +1,4 @@ -#![allow(clippy::unit_arg)] - -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* || ρ*) -/// -/// -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() -} +//! Sapling nullifiers. /// A Nullifier for Sapling transactions #[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)] @@ -38,14 +14,6 @@ impl From<[u8; 32]> for Nullifier { } } -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/sprout.rs b/zebra-chain/src/sprout.rs index a99caa48e..3c6ab0c7e 100644 --- a/zebra-chain/src/sprout.rs +++ b/zebra-chain/src/sprout.rs @@ -1,14 +1,12 @@ //! Sprout-related functionality. +mod joinsplit; + #[cfg(any(test, feature = "proptest-impl"))] mod arbitrary; -mod joinsplit; #[cfg(test)] mod tests; -// XXX clean up these modules - -pub mod address; pub mod commitment; pub mod keys; pub mod note; diff --git a/zebra-chain/src/sprout/address.rs b/zebra-chain/src/sprout/address.rs deleted file mode 100644 index 09885ad3b..000000000 --- a/zebra-chain/src/sprout/address.rs +++ /dev/null @@ -1,175 +0,0 @@ -//! Sprout Shielded Payment Address types. - -use std::{fmt, io}; - -#[cfg(test)] -use proptest::{arbitrary::Arbitrary, array, prelude::*}; - -use crate::{ - parameters::Network, - serialization::{ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize}, -}; - -use super::keys; - -/// Magic numbers used to identify what networks Sprout Shielded -/// Addresses are associated with. -mod magics { - pub const MAINNET: [u8; 2] = [0x16, 0x9A]; - pub const TESTNET: [u8; 2] = [0x16, 0xB6]; -} - -/// Sprout Shielded Payment Addresses -/// -/// -#[derive(Copy, Clone)] -pub struct SproutShieldedAddress { - network: Network, - paying_key: keys::PayingKey, - transmission_key: keys::TransmissionKey, -} - -impl fmt::Debug for SproutShieldedAddress { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("SproutShieldedAddress") - .field("network", &self.network) - .field("paying_key", &self.paying_key) - // Use hex formatting for the transmission key. - .field( - "transmission_key", - &hex::encode(self.transmission_key.as_bytes()), - ) - .finish() - } -} - -impl PartialEq for SproutShieldedAddress { - fn eq(&self, other: &Self) -> bool { - self.network == other.network - && self.paying_key.0 == other.paying_key.0 - && self.transmission_key.as_bytes() == other.transmission_key.as_bytes() - } -} - -impl Eq for SproutShieldedAddress {} - -impl ZcashSerialize for SproutShieldedAddress { - fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { - match self.network { - Network::Mainnet => writer.write_all(&magics::MAINNET[..])?, - _ => writer.write_all(&magics::TESTNET[..])?, - } - writer.write_all(&self.paying_key.0[..])?; - writer.write_all(self.transmission_key.as_bytes())?; - - Ok(()) - } -} - -impl ZcashDeserialize for SproutShieldedAddress { - fn zcash_deserialize(mut reader: R) -> Result { - let mut version_bytes = [0; 2]; - reader.read_exact(&mut version_bytes)?; - - let network = match version_bytes { - magics::MAINNET => Network::Mainnet, - magics::TESTNET => Network::Testnet, - _ => panic!("SerializationError: bad sprout shielded addr version/type"), - }; - - Ok(SproutShieldedAddress { - network, - paying_key: keys::PayingKey(reader.read_32_bytes()?), - transmission_key: keys::TransmissionKey::from(reader.read_32_bytes()?), - }) - } -} - -impl fmt::Display for SproutShieldedAddress { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut bytes = io::Cursor::new(Vec::new()); - - let _ = self.zcash_serialize(&mut bytes); - - f.write_str(&bs58::encode(bytes.get_ref()).with_check().into_string()) - } -} - -impl std::str::FromStr for SproutShieldedAddress { - type Err = SerializationError; - - fn from_str(s: &str) -> Result { - let result = &bs58::decode(s).with_check(None).into_vec(); - - match result { - Ok(bytes) => Self::zcash_deserialize(&bytes[..]), - Err(_) => Err(SerializationError::Parse("bs58 decoding error")), - } - } -} - -#[cfg(test)] -impl Arbitrary for SproutShieldedAddress { - type Parameters = (); - - fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - ( - any::(), - array::uniform32(any::()), - array::uniform32(any::()), - ) - .prop_map(|(network, paying_key_bytes, transmission_key_bytes)| Self { - network, - paying_key: keys::PayingKey(paying_key_bytes), - transmission_key: keys::TransmissionKey::from(transmission_key_bytes), - }) - .boxed() - } - - type Strategy = BoxedStrategy; -} - -#[cfg(test)] -mod tests { - - use super::*; - - #[test] - fn from_string_debug() { - let _init_guard = zebra_test::init(); - - let string = "zcU1Cd6zYyZCd2VJF8yKgmzjxdiiU1rgTTjEwoN1CGUWCziPkUTXUjXmX7TMqdMNsTfuiGN1jQoVN4kGxUR4sAPN4XZ7pxb"; - let zc_addr = string.parse::().unwrap(); - - assert_eq!(string, zc_addr.to_string()); - } -} - -#[cfg(test)] -proptest! { - - #[test] - fn zcash_de_serialize_roundtrip(zaddr in any::()) { - let _init_guard = zebra_test::init(); - - let mut data = Vec::new(); - - zaddr.zcash_serialize(&mut data).expect("sprout z-addr should serialize"); - - let zaddr2 = SproutShieldedAddress::zcash_deserialize(&data[..]) - .expect("randomized sprout z-addr should deserialize"); - - prop_assert_eq![zaddr, zaddr2]; - } - - #[test] - fn zcash_base58check_roundtrip(zaddr in any::()) { - let _init_guard = zebra_test::init(); - - let string = zaddr.to_string(); - - let zaddr2 = string.parse::().unwrap(); - - prop_assert_eq![zaddr, zaddr2]; - } -} diff --git a/zebra-chain/src/sprout/keys.rs b/zebra-chain/src/sprout/keys.rs index 776aa0432..cd3a5235b 100644 --- a/zebra-chain/src/sprout/keys.rs +++ b/zebra-chain/src/sprout/keys.rs @@ -1,185 +1,18 @@ -//! Sprout key types +//! Sprout key types. +//! +//! Unused key types are not implemented, see PR #5476. //! //! "The receiving key sk_enc, the incoming viewing key ivk = (apk, //! sk_enc), and the shielded payment address addr_pk = (a_pk, pk_enc) are //! derived from a_sk, as described in ['Sprout Key Components'][ps] //! //! [ps]: https://zips.z.cash/protocol/protocol.pdf#sproutkeycomponents -#![allow(clippy::unit_arg)] -use std::{fmt, io}; +use std::fmt; -use byteorder::{ByteOrder, LittleEndian}; -use rand_core::{CryptoRng, RngCore}; -use sha2::digest::generic_array::{typenum::U64, GenericArray}; - -#[cfg(any(test, feature = "proptest-impl"))] -use proptest::{array, prelude::*}; -#[cfg(any(test, feature = "proptest-impl"))] -use proptest_derive::Arbitrary; - -use crate::{ - parameters::Network, - serialization::{ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize}, -}; - -/// Magic numbers used to identify with what networks Sprout Spending -/// Keys are associated. -mod sk_magics { - pub const MAINNET: [u8; 2] = [0xAB, 0x36]; - pub const TESTNET: [u8; 2] = [0xAC, 0x08]; -} - -/// PRF^addr is used to derive a Sprout shielded payment address from -/// a spending key, and instantiated using the SHA-256 compression -/// function. +/// A Sprout _paying key_. /// -/// -/// -fn prf_addr(x: [u8; 32], t: u8) -> [u8; 32] { - let mut state = [0u32; 8]; - let mut block = GenericArray::::default(); - - block.as_mut_slice()[0..32].copy_from_slice(&x[..]); - // The first four bits –i.e. the most signicant four bits of the - // first byte– are used to separate distinct uses - // of SHA256Compress, ensuring that the functions are independent. - block.as_mut_slice()[0] |= 0b1100_0000; - - block.as_mut_slice()[32] = t; - - sha2::compress256(&mut state, &[block]); - - let mut derived_bytes = [0u8; 32]; - LittleEndian::write_u32_into(&state, &mut derived_bytes); - - derived_bytes -} - -/// Our root secret key of the Sprout key derivation tree. -/// -/// All other Sprout key types derive from the SpendingKey value. -/// Actually 252 bits. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] -pub struct SpendingKey { - /// What would normally be the value inside a tuple struct. - pub bytes: [u8; 32], - /// The Zcash network with which this key is associated. - pub network: Network, -} - -impl ZcashSerialize for SpendingKey { - fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { - match self.network { - Network::Mainnet => writer.write_all(&sk_magics::MAINNET[..])?, - _ => writer.write_all(&sk_magics::TESTNET[..])?, - } - writer.write_all(&self.bytes[..])?; - - Ok(()) - } -} - -impl ZcashDeserialize for SpendingKey { - fn zcash_deserialize(mut reader: R) -> Result { - let mut version_bytes = [0; 2]; - reader.read_exact(&mut version_bytes)?; - - let network = match version_bytes { - sk_magics::MAINNET => Network::Mainnet, - sk_magics::TESTNET => Network::Testnet, - _ => panic!("SerializationError: bad sprout spending key version/type"), - }; - - Ok(SpendingKey { - network, - bytes: reader.read_32_bytes()?, - }) - } -} - -impl fmt::Display for SpendingKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut bytes = io::Cursor::new(Vec::new()); - - let _ = self.zcash_serialize(&mut bytes); - - f.write_str(&bs58::encode(bytes.get_ref()).with_check().into_string()) - } -} - -impl From<[u8; 32]> for SpendingKey { - /// Generate a _SpendingKey_ from existing bytes, with the high 4 - /// bits of the first byte set to zero (ie, 256 bits clamped to - /// 252). - fn from(mut bytes: [u8; 32]) -> SpendingKey { - bytes[0] &= 0b0000_1111; // Force the 4 high-order bits to zero. - - SpendingKey { - bytes, - network: Network::default(), - } - } -} - -impl From for [u8; 32] { - fn from(spending_key: SpendingKey) -> [u8; 32] { - spending_key.bytes - } -} - -impl<'a> From<&'a SpendingKey> for [u8; 32] { - fn from(spending_key: &'a SpendingKey) -> [u8; 32] { - spending_key.bytes - } -} - -impl std::str::FromStr for SpendingKey { - type Err = SerializationError; - - fn from_str(s: &str) -> Result { - let result = &bs58::decode(s).with_check(None).into_vec(); - - match result { - Ok(bytes) => Self::zcash_deserialize(&bytes[..]), - Err(_) => Err(SerializationError::Parse("bs58 decoding error")), - } - } -} - -impl SpendingKey { - /// Generate a new _SpendingKey_ with the high 4 bits of the first - /// byte set to zero (ie, 256 random bits, clamped to 252). - pub fn new(csprng: &mut T) -> Self - where - T: RngCore + CryptoRng, - { - let mut bytes = [0u8; 32]; - csprng.fill_bytes(&mut bytes); - - Self::from(bytes) - } -} - -/// Derived from a _SpendingKey_. -pub type ReceivingKey = x25519_dalek::StaticSecret; - -impl From for ReceivingKey { - /// For this invocation of SHA256Compress as PRF^addr, t=0, which - /// is populated by default in an empty block of all zeros to - /// start. - /// - /// - /// - fn from(spending_key: SpendingKey) -> ReceivingKey { - let derived_bytes = prf_addr(spending_key.bytes, 0); - - ReceivingKey::from(derived_bytes) - } -} - -/// Derived from a _SpendingKey_. +/// Derived from a Sprout _spending key. #[derive(Copy, Clone, Eq, PartialEq)] #[cfg_attr( any(test, feature = "proptest-impl"), @@ -200,205 +33,3 @@ impl fmt::Debug for PayingKey { .finish() } } - -impl From for PayingKey { - /// For this invocation of SHA256Compress as PRF^addr, t=1. - /// - /// - /// - fn from(spending_key: SpendingKey) -> PayingKey { - let derived_bytes = prf_addr(spending_key.bytes, 1); - - PayingKey(derived_bytes) - } -} - -/// Derived from a _ReceivingKey_. -pub type TransmissionKey = x25519_dalek::PublicKey; - -/// Magic numbers used to identify with what networks Sprout Incoming -/// Viewing Keys are associated. -mod ivk_magics { - pub const MAINNET: [u8; 3] = [0xA8, 0xAB, 0xD3]; - pub const TESTNET: [u8; 3] = [0xA8, 0xAC, 0x0C]; -} - -/// The recipient's possession of the associated incoming viewing key -/// is used to reconstruct the original note and memo field. -pub struct IncomingViewingKey { - network: Network, - paying_key: PayingKey, - receiving_key: ReceivingKey, -} - -// Can't derive PartialEq because ReceivingKey aka -// x25519_dalek::StaticSecret does not impl it. -impl PartialEq for IncomingViewingKey { - fn eq(&self, other: &Self) -> bool { - self.network == other.network - && self.paying_key.0 == other.paying_key.0 - && self.receiving_key.to_bytes() == other.receiving_key.to_bytes() - } -} - -impl fmt::Debug for IncomingViewingKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("IncomingViewingKey") - .field("network", &self.network) - .field("paying_key", &hex::encode(self.paying_key.0)) - .field("receiving_key", &hex::encode(self.receiving_key.to_bytes())) - .finish() - } -} - -impl ZcashSerialize for IncomingViewingKey { - fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { - match self.network { - Network::Mainnet => writer.write_all(&ivk_magics::MAINNET[..])?, - _ => writer.write_all(&ivk_magics::TESTNET[..])?, - } - writer.write_all(&self.paying_key.0[..])?; - writer.write_all(&self.receiving_key.to_bytes())?; - - Ok(()) - } -} - -impl ZcashDeserialize for IncomingViewingKey { - fn zcash_deserialize(mut reader: R) -> Result { - let mut version_bytes = [0; 3]; - reader.read_exact(&mut version_bytes)?; - - let network = match version_bytes { - ivk_magics::MAINNET => Network::Mainnet, - ivk_magics::TESTNET => Network::Testnet, - _ => panic!("SerializationError: bad sprout incoming viewing key network"), - }; - - Ok(IncomingViewingKey { - network, - paying_key: PayingKey(reader.read_32_bytes()?), - receiving_key: ReceivingKey::from(reader.read_32_bytes()?), - }) - } -} - -impl fmt::Display for IncomingViewingKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut bytes = io::Cursor::new(Vec::new()); - - let _ = self.zcash_serialize(&mut bytes); - - f.write_str(&bs58::encode(bytes.get_ref()).with_check().into_string()) - } -} - -impl std::str::FromStr for IncomingViewingKey { - type Err = SerializationError; - - fn from_str(s: &str) -> Result { - let result = &bs58::decode(s).with_check(None).into_vec(); - - match result { - Ok(bytes) => Self::zcash_deserialize(&bytes[..]), - Err(_) => Err(SerializationError::Parse("bs58 decoding error")), - } - } -} - -#[cfg(any(test, feature = "proptest-impl"))] -impl Arbitrary for IncomingViewingKey { - type Parameters = (); - - fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - ( - any::(), - array::uniform32(any::()), - array::uniform32(any::()), - ) - .prop_map(|(network, paying_key_bytes, receiving_key_bytes)| Self { - network, - paying_key: PayingKey(paying_key_bytes), - receiving_key: ReceivingKey::from(receiving_key_bytes), - }) - .boxed() - } - - type Strategy = BoxedStrategy; -} - -#[cfg(test)] -mod tests { - - use rand_core::OsRng; - - use super::*; - - #[test] - // TODO: test vectors, not just random data - fn derive_keys() { - let _init_guard = zebra_test::init(); - - let spending_key = SpendingKey::new(&mut OsRng); - - let receiving_key = ReceivingKey::from(spending_key); - - let _transmission_key = TransmissionKey::from(&receiving_key); - } -} - -#[cfg(test)] -proptest! { - - #[test] - fn spending_key_roundtrip(sk in any::()) { - let _init_guard = zebra_test::init(); - - let mut data = Vec::new(); - - sk.zcash_serialize(&mut data).expect("sprout spending key should serialize"); - - let sk2 = SpendingKey::zcash_deserialize(&data[..]).expect("randomized sprout spending key should deserialize"); - - prop_assert_eq![sk, sk2]; - - } - - #[test] - fn spending_key_string_roundtrip(sk in any::()) { - let _init_guard = zebra_test::init(); - - let string = sk.to_string(); - - let sk2 = string.parse::().unwrap(); - - prop_assert_eq![sk, sk2]; - - } - - #[test] - fn incoming_viewing_key_roundtrip(ivk in any::()) { - let _init_guard = zebra_test::init(); - - let mut data = Vec::new(); - - ivk.zcash_serialize(&mut data).expect("sprout z-addr should serialize"); - - let ivk2 = IncomingViewingKey::zcash_deserialize(&data[..]).expect("randomized ivk should deserialize"); - - prop_assert_eq![ivk, ivk2]; - - } - - #[test] - fn incoming_viewing_key_string_roundtrip(ivk in any::()) { - let _init_guard = zebra_test::init(); - - let string = ivk.to_string(); - - let ivk2 = string.parse::().unwrap(); - - prop_assert_eq![ivk, ivk2]; - - } -} diff --git a/zebra-chain/src/sprout/note.rs b/zebra-chain/src/sprout/note.rs index 3ea5c7d43..679352304 100644 --- a/zebra-chain/src/sprout/note.rs +++ b/zebra-chain/src/sprout/note.rs @@ -1,13 +1,12 @@ //! Sprout notes -#![allow(clippy::unit_arg)] - -#[cfg(any(test, feature = "proptest-impl"))] -mod arbitrary; mod ciphertexts; mod mac; mod nullifiers; +#[cfg(any(test, feature = "proptest-impl"))] +mod arbitrary; + use crate::{ amount::{Amount, NonNegative}, transaction::Memo, diff --git a/zebra-chain/src/sprout/note/nullifiers.rs b/zebra-chain/src/sprout/note/nullifiers.rs index c3f8549ba..c25d9d072 100644 --- a/zebra-chain/src/sprout/note/nullifiers.rs +++ b/zebra-chain/src/sprout/note/nullifiers.rs @@ -1,34 +1,6 @@ -use byteorder::{ByteOrder, LittleEndian}; +//! Sprout nullifiers. + use serde::{Deserialize, Serialize}; -use sha2::digest::generic_array::{typenum::U64, GenericArray}; - -use super::super::keys::SpendingKey; - -/// PRF^nf is used to derive a Sprout nullifer from the receiver's -/// spending key a_sk and a nullifier seed ρ, instantiated using the -/// SHA-256 compression function. -/// -/// -/// -fn prf_nf(a_sk: [u8; 32], rho: [u8; 32]) -> [u8; 32] { - let mut state = [0u32; 8]; - let mut block = GenericArray::::default(); - - block.as_mut_slice()[0..32].copy_from_slice(&a_sk[..]); - // The first four bits –i.e. the most signicant four bits of the - // first byte– are used to separate distinct uses - // of SHA256Compress, ensuring that the functions are independent. - block.as_mut_slice()[0] |= 0b1100_0000; - - block.as_mut_slice()[32..].copy_from_slice(&rho[..]); - - sha2::compress256(&mut state, &[block]); - - let mut derived_bytes = [0u8; 32]; - LittleEndian::write_u32_into(&state, &mut derived_bytes); - - derived_bytes -} /// Nullifier seed, named rho in the [spec][ps]. /// @@ -73,12 +45,6 @@ impl From<[u8; 32]> for Nullifier { } } -impl<'a> From<(&'a SpendingKey, NullifierSeed)> for Nullifier { - fn from((a_sk, rho): (&'a SpendingKey, NullifierSeed)) -> Self { - Self(prf_nf(a_sk.into(), rho.into())) - } -} - impl From for [u8; 32] { fn from(n: Nullifier) -> Self { n.0