Flesh out Orchard note and nullifier derivation

This commit is contained in:
Deirdre Connolly 2021-03-27 20:31:08 -04:00 committed by Deirdre Connolly
parent 37c32e9c94
commit 376603d4c0
6 changed files with 94 additions and 18 deletions

View File

@ -7,8 +7,6 @@
#![doc(html_logo_url = "https://www.zfnd.org/images/zebra-icon.png")] #![doc(html_logo_url = "https://www.zfnd.org/images/zebra-icon.png")]
#![doc(html_root_url = "https://doc.zebra.zfnd.org/zebra_chain")] #![doc(html_root_url = "https://doc.zebra.zfnd.org/zebra_chain")]
#![allow(clippy::try_err)] #![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 // The actual lints we want to disable
#![allow(clippy::unnecessary_wraps)] #![allow(clippy::unnecessary_wraps)]
// Disable some broken or unwanted clippy nightly lints // Disable some broken or unwanted clippy nightly lints

View File

@ -18,8 +18,8 @@ use crate::{
}; };
use super::{ use super::{
keys::{Diversifier, TransmissionKey}, keys::{prf_expand, Diversifier, TransmissionKey},
note::Note, note::{self, SeedRandomness},
sinsemilla::*, sinsemilla::*,
}; };
@ -36,10 +36,22 @@ where
pallas::Scalar::from_bytes_wide(&bytes) 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)] #[derive(Copy, Clone, Debug, PartialEq)]
pub struct CommitmentRandomness(pallas::Scalar); pub struct CommitmentRandomness(pallas::Scalar);
impl From<SeedRandomness> 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. /// Note commitments for the output notes.
#[derive(Clone, Copy, Deserialize, PartialEq, Serialize)] #[derive(Clone, Copy, Deserialize, PartialEq, Serialize)]
pub struct NoteCommitment(#[serde(with = "serde_helpers::Affine")] pub pallas::Affine); pub struct NoteCommitment(#[serde(with = "serde_helpers::Affine")] pub pallas::Affine);
@ -106,7 +118,8 @@ impl NoteCommitment {
diversifier: Diversifier, diversifier: Diversifier,
transmission_key: TransmissionKey, transmission_key: TransmissionKey,
value: Amount<NonNegative>, value: Amount<NonNegative>,
_note: Note, rho: note::Rho,
psi: note::Psi,
) -> Option<(CommitmentRandomness, Self)> ) -> Option<(CommitmentRandomness, Self)>
where where
T: RngCore + CryptoRng, T: RngCore + CryptoRng,
@ -126,13 +139,17 @@ impl NoteCommitment {
return None; 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 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::<Lsb0, u8>::from_slice(&g_d_bytes[..])); s.append(&mut BitVec::<Lsb0, u8>::from_slice(&g_d_bytes[..]));
s.append(&mut BitVec::<Lsb0, u8>::from_slice(&pk_d_bytes[..])); s.append(&mut BitVec::<Lsb0, u8>::from_slice(&pk_d_bytes[..]));
s.append(&mut BitVec::<Lsb0, u8>::from_slice(&v_bytes[..])); s.append(&mut BitVec::<Lsb0, u8>::from_slice(&v_bytes[..]));
s.append(&mut BitVec::<Lsb0, u8>::from_slice(&rho_bytes[..]));
s.append(&mut BitVec::<Lsb0, u8>::from_slice(&psi_bytes[..]));
let rcm = CommitmentRandomness(generate_trapdoor(csprng)); let rcm = CommitmentRandomness(generate_trapdoor(csprng));

View File

@ -63,9 +63,9 @@ fn prp_d(K: [u8; 32], d: [u8; 11]) -> [u8; 11] {
/// ///
/// https://zips.z.cash/protocol/protocol.pdf#concreteprfs /// https://zips.z.cash/protocol/protocol.pdf#concreteprfs
// TODO: This is basically a duplicate of the one in our sapling module, its // 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. // 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() let mut state = blake2b_simd::Params::new()
.hash_length(64) .hash_length(64)
.personal(b"Zcash_ExpandSeed") .personal(b"Zcash_ExpandSeed")

View File

@ -3,7 +3,9 @@
#![allow(clippy::unit_arg)] #![allow(clippy::unit_arg)]
#![allow(dead_code)] #![allow(dead_code)]
use halo2::pasta::pallas; use group::GroupEncoding;
use halo2::{arithmetic::FieldExt, pasta::pallas};
use rand_core::{CryptoRng, RngCore};
use crate::{ use crate::{
amount::{Amount, NonNegative}, amount::{Amount, NonNegative},
@ -12,7 +14,8 @@ use crate::{
use super::{ use super::{
commitment::CommitmentRandomness, commitment::CommitmentRandomness,
keys::{Diversifier, TransmissionKey}, keys::{prf_expand, Diversifier, TransmissionKey},
sinsemilla::extract_p,
}; };
#[cfg(any(test, feature = "proptest-impl"))] #[cfg(any(test, feature = "proptest-impl"))]
@ -23,6 +26,63 @@ mod nullifiers;
pub use ciphertexts::{EncryptedNote, WrappedNoteKey}; pub use ciphertexts::{EncryptedNote, WrappedNoteKey};
pub use nullifiers::Nullifier; 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<Rho> for [u8; 32] {
fn from(rho: Rho) -> Self {
rho.0.to_bytes()
}
}
impl Rho {
pub fn new<T>(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<Psi> for [u8; 32] {
fn from(psi: Psi) -> Self {
psi.0.to_bytes()
}
}
impl From<SeedRandomness> 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 /// A Note represents that a value is spendable by the recipient who
/// holds the spending key corresponding to a given shielded payment /// holds the spending key corresponding to a given shielded payment
/// address. /// address.
@ -36,9 +96,9 @@ pub struct Note {
/// An integer representing the value of the _note_ in zatoshi. /// An integer representing the value of the _note_ in zatoshi.
pub value: Amount<NonNegative>, pub value: Amount<NonNegative>,
/// Used as input to PRF^nf as part of deriving the _nullifier_ of the _note_. /// 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_. /// 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 /// A random _commitment trapdoor_ used to produce the associated note
/// commitment. /// commitment.
pub rcm: CommitmentRandomness, pub rcm: CommitmentRandomness,

View File

@ -69,7 +69,8 @@ impl From<(NullifierDerivingKey, Note, NoteCommitment)> for Nullifier {
// //
// [ (PRF^nfOrchard_nk(ρ) + ψ) mod q_P ] K^Orchard + cm // [ (PRF^nfOrchard_nk(ρ) + ψ) mod q_P ] K^Orchard + cm
let scalar = 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? // Basically a new-gen Pedersen hash?
Nullifier(extract_p((K * scalar) + cm.0)) Nullifier(extract_p((K * scalar) + cm.0))

View File

@ -37,7 +37,7 @@ pub enum NetworkUpgrade {
Heartwood, Heartwood,
/// The Zcash protocol after the Canopy upgrade. /// The Zcash protocol after the Canopy upgrade.
Canopy, 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 /// Note: Network Upgrade 5 includes the Orchard Shielded Protocol, and
/// other changes. The Nu5 code name has not been chosen yet. /// 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(653_600), Blossom),
(block::Height(903_000), Heartwood), (block::Height(903_000), Heartwood),
(block::Height(1_046_400), Canopy), (block::Height(1_046_400), Canopy),
// TODO: Add NU5 mainnet activation height // TODO: Add Nu5 mainnet activation height
]; ];
/// Testnet network upgrade activation heights. /// 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(584_000), Blossom),
(block::Height(903_800), Heartwood), (block::Height(903_800), Heartwood),
(block::Height(1_028_500), Canopy), (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 /// The Consensus Branch Id, used to bind transactions and blocks to a