From 376603d4c03e03984972de2ee6eec4246fed01eb Mon Sep 17 00:00:00 2001 From: Deirdre Connolly Date: Sat, 27 Mar 2021 20:31:08 -0400 Subject: [PATCH] Flesh out Orchard note and nullifier derivation --- zebra-chain/src/lib.rs | 2 - zebra-chain/src/orchard/commitment.rs | 29 ++++++-- zebra-chain/src/orchard/keys.rs | 4 +- zebra-chain/src/orchard/note.rs | 68 +++++++++++++++++-- zebra-chain/src/orchard/note/nullifiers.rs | 3 +- zebra-chain/src/parameters/network_upgrade.rs | 6 +- 6 files changed, 94 insertions(+), 18 deletions(-) diff --git a/zebra-chain/src/lib.rs b/zebra-chain/src/lib.rs index e55b6351..1336d488 100644 --- a/zebra-chain/src/lib.rs +++ b/zebra-chain/src/lib.rs @@ -7,8 +7,6 @@ #![doc(html_logo_url = "https://www.zfnd.org/images/zebra-icon.png")] #![doc(html_root_url = "https://doc.zebra.zfnd.org/zebra_chain")] #![allow(clippy::try_err)] -// Use the old lint name to build without warnings on stable until 1.51 is stable -#![allow(clippy::unknown_clippy_lints)] // The actual lints we want to disable #![allow(clippy::unnecessary_wraps)] // Disable some broken or unwanted clippy nightly lints diff --git a/zebra-chain/src/orchard/commitment.rs b/zebra-chain/src/orchard/commitment.rs index 447542c1..cf45bf94 100644 --- a/zebra-chain/src/orchard/commitment.rs +++ b/zebra-chain/src/orchard/commitment.rs @@ -18,8 +18,8 @@ use crate::{ }; use super::{ - keys::{Diversifier, TransmissionKey}, - note::Note, + keys::{prf_expand, Diversifier, TransmissionKey}, + note::{self, SeedRandomness}, sinsemilla::*, }; @@ -36,10 +36,22 @@ where pallas::Scalar::from_bytes_wide(&bytes) } -/// The randomness used in the Simsemilla Hash for note commitment. +/// The randomness used in the Simsemilla hash for note commitment. #[derive(Copy, Clone, Debug, PartialEq)] pub struct CommitmentRandomness(pallas::Scalar); +impl From for CommitmentRandomness { + /// rcm = ToScalar^Orchard((PRF^expand_rseed ([5])) + /// + /// https://zips.z.cash/protocol/nu5.pdf#orchardsend + 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, Serialize)] pub struct NoteCommitment(#[serde(with = "serde_helpers::Affine")] pub pallas::Affine); @@ -106,7 +118,8 @@ impl NoteCommitment { diversifier: Diversifier, transmission_key: TransmissionKey, value: Amount, - _note: Note, + rho: note::Rho, + psi: note::Psi, ) -> Option<(CommitmentRandomness, Self)> where T: RngCore + CryptoRng, @@ -126,13 +139,17 @@ impl NoteCommitment { return None; } - let pk_d_bytes = <[u8; 32]>::from(transmission_key); + let pk_d_bytes: [u8; 32] = transmission_key.into(); let v_bytes = value.to_bytes(); + let rho_bytes: [u8; 32] = rho.into(); + let psi_bytes: [u8; 32] = psi.into(); - // g*d || pk*d || I2LEBSP64(v) + // g*d || pk*d || I2LEBSP_64(v) || I2LEBSP_l^Orchard_Base(ρ) || I2LEBSP_l^Orchard_base(ψ) s.append(&mut BitVec::::from_slice(&g_d_bytes[..])); s.append(&mut BitVec::::from_slice(&pk_d_bytes[..])); s.append(&mut BitVec::::from_slice(&v_bytes[..])); + s.append(&mut BitVec::::from_slice(&rho_bytes[..])); + s.append(&mut BitVec::::from_slice(&psi_bytes[..])); let rcm = CommitmentRandomness(generate_trapdoor(csprng)); diff --git a/zebra-chain/src/orchard/keys.rs b/zebra-chain/src/orchard/keys.rs index edb6e2ee..7ddc2868 100644 --- a/zebra-chain/src/orchard/keys.rs +++ b/zebra-chain/src/orchard/keys.rs @@ -63,9 +63,9 @@ fn prp_d(K: [u8; 32], d: [u8; 11]) -> [u8; 11] { /// /// https://zips.z.cash/protocol/protocol.pdf#concreteprfs // 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 +// definition in the draft Nu5 spec is incomplete so I'm putting it here in case // it changes. -fn prf_expand(sk: [u8; 32], t: Vec<&[u8]>) -> [u8; 64] { +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") diff --git a/zebra-chain/src/orchard/note.rs b/zebra-chain/src/orchard/note.rs index 627e7b97..7bcc0a89 100644 --- a/zebra-chain/src/orchard/note.rs +++ b/zebra-chain/src/orchard/note.rs @@ -3,7 +3,9 @@ #![allow(clippy::unit_arg)] #![allow(dead_code)] -use halo2::pasta::pallas; +use group::GroupEncoding; +use halo2::{arithmetic::FieldExt, pasta::pallas}; +use rand_core::{CryptoRng, RngCore}; use crate::{ amount::{Amount, NonNegative}, @@ -12,7 +14,8 @@ use crate::{ use super::{ commitment::CommitmentRandomness, - keys::{Diversifier, TransmissionKey}, + keys::{prf_expand, Diversifier, TransmissionKey}, + sinsemilla::extract_p, }; #[cfg(any(test, feature = "proptest-impl"))] @@ -23,6 +26,63 @@ mod nullifiers; pub use ciphertexts::{EncryptedNote, WrappedNoteKey}; pub use nullifiers::Nullifier; +#[derive(Clone, Debug)] +pub struct SeedRandomness(pub(crate) [u8; 32]); + +/// Used as input to PRF^nf as part of deriving the _nullifier_ of the _note_. +/// +/// When creating a new note from spending an old note, the new note's _rho_ is +/// the _nullifier_ of the previous note. If creating a note from scratch (like +/// a miner reward), a dummy note is constructed, and its nullifier as the _rho_ +/// for the actual output note. When creating a dummy note, its _rho_ is chosen +/// as a random Pallas point's x-coordinate. +/// +/// https://zips.z.cash/protocol/nu5.pdf#orcharddummynotes +#[derive(Clone, Debug)] +pub struct Rho(pub(crate) pallas::Base); + +impl From for [u8; 32] { + fn from(rho: Rho) -> Self { + rho.0.to_bytes() + } +} + +impl Rho { + pub fn new(csprng: &mut T) -> Self + where + T: RngCore + CryptoRng, + { + let mut bytes = [0u8; 32]; + csprng.fill_bytes(&mut bytes); + + Self(extract_p(pallas::Point::from_bytes(&bytes).unwrap())) + } +} + +/// Additional randomness used in deriving the _nullifier_. +/// +/// https://zips.z.cash/protocol/nu5.pdf#orchardsend +#[derive(Clone, Debug)] +pub struct Psi(pub(crate) pallas::Base); + +impl From for [u8; 32] { + fn from(psi: Psi) -> Self { + psi.0.to_bytes() + } +} + +impl From for Psi { + /// rcm = ToScalar^Orchard((PRF^expand_rseed ([9])) + /// + /// https://zips.z.cash/protocol/nu5.pdf#orchardsend + 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. @@ -36,9 +96,9 @@ pub struct Note { /// An integer representing the value of the _note_ in zatoshi. pub value: Amount, /// Used as input to PRF^nf as part of deriving the _nullifier_ of the _note_. - pub rho: pallas::Base, // XXX: refine type? + pub rho: Rho, /// Additional randomness used in deriving the _nullifier_. - pub psi: pallas::Base, // XXX: refine type + pub psi: Psi, /// A random _commitment trapdoor_ used to produce the associated note /// commitment. pub rcm: CommitmentRandomness, diff --git a/zebra-chain/src/orchard/note/nullifiers.rs b/zebra-chain/src/orchard/note/nullifiers.rs index 6da56fd1..ea9b5698 100644 --- a/zebra-chain/src/orchard/note/nullifiers.rs +++ b/zebra-chain/src/orchard/note/nullifiers.rs @@ -69,7 +69,8 @@ impl From<(NullifierDerivingKey, Note, NoteCommitment)> for Nullifier { // // [︀ (PRF^nfOrchard_nk(ρ) + ψ) mod q_P ]︀ K^Orchard + cm let scalar = - pallas::Scalar::from_bytes(&(prf_nf(nk.0, note.rho) + note.psi).to_bytes()).unwrap(); + pallas::Scalar::from_bytes(&(prf_nf(nk.0, note.rho.0) + note.psi.0).to_bytes()) + .unwrap(); // Basically a new-gen Pedersen hash? Nullifier(extract_p((K * scalar) + cm.0)) diff --git a/zebra-chain/src/parameters/network_upgrade.rs b/zebra-chain/src/parameters/network_upgrade.rs index 1c61183c..5f7cbbc3 100644 --- a/zebra-chain/src/parameters/network_upgrade.rs +++ b/zebra-chain/src/parameters/network_upgrade.rs @@ -37,7 +37,7 @@ pub enum NetworkUpgrade { Heartwood, /// The Zcash protocol after the Canopy upgrade. Canopy, - /// The Zcash protocol after the NU5 upgrade. + /// The Zcash protocol after the Nu5 upgrade. /// /// Note: Network Upgrade 5 includes the Orchard Shielded Protocol, and /// other changes. The Nu5 code name has not been chosen yet. @@ -56,7 +56,7 @@ pub(crate) const MAINNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] (block::Height(653_600), Blossom), (block::Height(903_000), Heartwood), (block::Height(1_046_400), Canopy), - // TODO: Add NU5 mainnet activation height + // TODO: Add Nu5 mainnet activation height ]; /// Testnet network upgrade activation heights. @@ -71,7 +71,7 @@ pub(crate) const TESTNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] (block::Height(584_000), Blossom), (block::Height(903_800), Heartwood), (block::Height(1_028_500), Canopy), - // TODO: Add NU5 testnet activation height + // TODO: Add Nu5 testnet activation height ]; /// The Consensus Branch Id, used to bind transactions and blocks to a