From 680c917ce65a173a5e09eafda1585e30630b9dd5 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 13 Mar 2021 13:04:13 +1300 Subject: [PATCH] Note commitment derivation --- src/address.rs | 19 ++++++++++++++- src/keys.rs | 14 ++++++++++- src/note.rs | 52 +++++++++++++++++++++++++++------------ src/note/commitment.rs | 55 ++++++++++++++++++++++++++++++++++++++++++ src/value.rs | 10 +++++++- 5 files changed, 132 insertions(+), 18 deletions(-) create mode 100644 src/note/commitment.rs diff --git a/src/address.rs b/src/address.rs index 6dbed73b..96328985 100644 --- a/src/address.rs +++ b/src/address.rs @@ -1,4 +1,9 @@ -use crate::keys::{DiversifiedTransmissionKey, Diversifier}; +use pasta_curves::pallas; + +use crate::{ + keys::{DiversifiedTransmissionKey, Diversifier}, + spec::diversify_hash, +}; /// A shielded payment address. /// @@ -18,6 +23,18 @@ pub struct Address { impl Address { pub(crate) fn from_parts(d: Diversifier, pk_d: DiversifiedTransmissionKey) -> Self { + // We assume here that pk_d is correctly-derived from d. We ensure this for + // internal APIs. For parsing from raw byte encodings, we assume that users aren't + // modifying internals of encoded address formats. If they do, that can result in + // lost funds, but we can't defend against that from here. Address { d, pk_d } } + + pub(crate) fn g_d(&self) -> pallas::Point { + diversify_hash(self.d.as_array()) + } + + pub(crate) fn pk_d(&self) -> &DiversifiedTransmissionKey { + &self.pk_d + } } diff --git a/src/keys.rs b/src/keys.rs index afb0e3aa..860c2a25 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -229,6 +229,13 @@ impl DiversifierKey { #[derive(Debug)] pub struct Diversifier([u8; 11]); +impl Diversifier { + /// Returns the byte array corresponding to this diversifier. + pub fn as_array(&self) -> &[u8; 11] { + &self.0 + } +} + /// A key that provides the capability to detect and decrypt incoming notes from the block /// chain, without being able to spend the notes or detect when they are spent. /// @@ -290,7 +297,12 @@ impl DiversifiedTransmissionKey { /// /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents fn derive(ivk: &IncomingViewingKey, d: &Diversifier) -> Self { - let g_d = diversify_hash(&d.0); + let g_d = diversify_hash(&d.as_array()); DiversifiedTransmissionKey(ka_orchard(&ivk.0, &g_d)) } + + /// $repr_P(self)$ + pub(crate) fn to_bytes(&self) -> [u8; 32] { + self.0.to_bytes() + } } diff --git a/src/note.rs b/src/note.rs index dbfa575c..a4cdda13 100644 --- a/src/note.rs +++ b/src/note.rs @@ -1,20 +1,33 @@ -use crate::{keys::FullViewingKey, value::NoteValue, Address}; +use group::GroupEncoding; +use pasta_curves::pallas; + +use crate::{ + keys::FullViewingKey, + spec::{prf_expand, to_base, to_scalar}, + value::NoteValue, + Address, +}; + +mod commitment; +pub use self::commitment::NoteCommitment; /// The ZIP 212 seed randomness for a note. #[derive(Debug)] struct RandomSeed([u8; 32]); impl RandomSeed { - fn psi(&self) -> () { - todo!() + /// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend]. + /// + /// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend + fn psi(&self) -> pallas::Base { + to_base(prf_expand(&self.0, &[0x09])) } - fn rcm(&self) -> () { - todo!() - } - - fn esk(&self) -> () { - todo!() + /// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend]. + /// + /// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend + fn esk(&self) -> pallas::Scalar { + to_scalar(prf_expand(&self.0, &[0x04])) } } @@ -38,8 +51,21 @@ pub struct Note { impl Note { /// Derives the commitment to this note. + /// + /// Defined in [Zcash Protocol Spec § 3.2: Notes][notes]. + /// + /// [notes]: https://zips.z.cash/protocol/nu5.pdf#notes pub fn commitment(&self) -> NoteCommitment { - todo!() + let g_d = self.recipient.g_d(); + + NoteCommitment::derive( + g_d.to_bytes(), + self.recipient.pk_d().to_bytes(), + self.value, + self.rho.0, + self.rseed.psi(), + (&self.rseed).into(), + ) } /// Derives the nullifier for this note. @@ -52,10 +78,6 @@ impl Note { #[derive(Debug)] pub struct EncryptedNote; -/// A commitment to a note. -#[derive(Debug)] -pub struct NoteCommitment; - /// A unique nullifier for a note. #[derive(Debug)] -pub struct Nullifier; +pub struct Nullifier(pallas::Base); diff --git a/src/note/commitment.rs b/src/note/commitment.rs new file mode 100644 index 00000000..4a7cbd65 --- /dev/null +++ b/src/note/commitment.rs @@ -0,0 +1,55 @@ +use std::iter; + +use bitvec::{array::BitArray, order::Lsb0}; +use ff::PrimeField; +use pasta_curves::pallas; + +use crate::{ + constants::L_ORCHARD_BASE, + primitives::sinsemilla, + spec::{prf_expand, to_scalar}, + value::NoteValue, +}; + +use super::RandomSeed; + +pub(super) struct NoteCommitTrapdoor(pallas::Scalar); + +impl From<&RandomSeed> for NoteCommitTrapdoor { + fn from(rseed: &RandomSeed) -> Self { + NoteCommitTrapdoor(to_scalar(prf_expand(&rseed.0, &[0x05]))) + } +} + +/// A commitment to a note. +#[derive(Debug)] +pub struct NoteCommitment(pallas::Point); + +impl NoteCommitment { + /// $NoteCommit^Orchard$. + /// + /// Defined in [Zcash Protocol Spec § 5.4.8.4: Sinsemilla commitments][concretesinsemillacommit]. + /// + /// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit + pub(super) fn derive( + g_d: [u8; 32], + pk_d: [u8; 32], + v: NoteValue, + rho: pallas::Base, + psi: pallas::Base, + rcm: NoteCommitTrapdoor, + ) -> Self { + let domain = sinsemilla::CommitDomain::new("z.cash:Orchard-NoteCommit"); + NoteCommitment( + domain.commit( + iter::empty() + .chain(BitArray::::new(g_d).iter().by_val()) + .chain(BitArray::::new(pk_d).iter().by_val()) + .chain(v.to_le_bits().iter().by_val()) + .chain(rho.to_le_bits().iter().by_val().take(L_ORCHARD_BASE)) + .chain(psi.to_le_bits().iter().by_val().take(L_ORCHARD_BASE)), + &rcm.0, + ), + ) + } +} diff --git a/src/value.rs b/src/value.rs index 6cf96cdc..4a476cfc 100644 --- a/src/value.rs +++ b/src/value.rs @@ -13,10 +13,18 @@ //! [`Action`]: crate::bundle::Action //! [`Bundle`]: crate::bundle::Bundle +use bitvec::{array::BitArray, order::Lsb0}; + /// The value of an individual Orchard note. -#[derive(Debug)] +#[derive(Clone, Copy, Debug)] pub struct NoteValue(u64); +impl NoteValue { + pub(crate) fn to_le_bits(&self) -> BitArray { + BitArray::::new(self.0.to_le_bytes()) + } +} + /// A sum of Orchard note values. #[derive(Debug)] pub struct ValueSum(i64);