Refactor Sapling note commitment derivation

This commit is contained in:
Jack Grigg 2023-01-06 16:49:17 +00:00
parent 65271b49e5
commit 88d46fd6b3
5 changed files with 173 additions and 51 deletions

View File

@ -1,15 +1,14 @@
use byteorder::{LittleEndian, WriteBytesExt};
use group::{
ff::{Field, PrimeField},
Curve, GroupEncoding,
GroupEncoding,
};
use rand_core::{CryptoRng, RngCore};
use super::{
pedersen_hash::{pedersen_hash, Personalization},
Node, Nullifier, NullifierDerivingKey,
};
use crate::{constants, keys::prf_expand};
use super::{value::NoteValue, Node, Nullifier, NullifierDerivingKey};
use crate::keys::prf_expand;
mod commitment;
pub use self::commitment::{ExtractedNoteCommitment, NoteCommitment};
pub(super) mod nullifier;
@ -24,6 +23,20 @@ pub enum Rseed {
AfterZip212([u8; 32]),
}
impl Rseed {
/// Defined in [Zcash Protocol Spec § 4.7.2: Sending Notes (Sapling)][saplingsend].
///
/// [saplingsend]: https://zips.z.cash/protocol/protocol.pdf#saplingsend
pub(crate) fn rcm(&self) -> commitment::NoteCommitTrapdoor {
commitment::NoteCommitTrapdoor(match self {
Rseed::BeforeZip212(rcm) => *rcm,
Rseed::AfterZip212(rseed) => {
jubjub::Fr::from_bytes_wide(prf_expand(rseed, &[0x04]).as_array())
}
})
}
}
#[derive(Clone, Debug)]
pub struct Note {
/// The value of the note
@ -53,31 +66,13 @@ impl Note {
}
/// Computes the note commitment, returning the full point.
fn cm_full_point(&self) -> jubjub::SubgroupPoint {
// Calculate the note contents, as bytes
let mut note_contents = vec![];
// Writing the value in little endian
note_contents.write_u64::<LittleEndian>(self.value).unwrap();
// Write g_d
note_contents.extend_from_slice(&self.g_d.to_bytes());
// Write pk_d
note_contents.extend_from_slice(&self.pk_d.to_bytes());
assert_eq!(note_contents.len(), 32 + 32 + 8);
// Compute the Pedersen hash of the note contents
let hash_of_contents = pedersen_hash(
Personalization::NoteCommitment,
note_contents
.into_iter()
.flat_map(|byte| (0..8).map(move |i| ((byte >> i) & 1) == 1)),
);
// Compute final commitment
(constants::NOTE_COMMITMENT_RANDOMNESS_GENERATOR * self.rcm()) + hash_of_contents
fn cm_full_point(&self) -> NoteCommitment {
NoteCommitment::derive(
self.g_d.to_bytes(),
self.pk_d.to_bytes(),
NoteValue::from_raw(self.value),
self.rseed.rcm(),
)
}
/// Computes the nullifier given the nullifier deriving key and
@ -88,20 +83,12 @@ impl Note {
/// Computes the note commitment
pub fn cmu(&self) -> bls12_381::Scalar {
// The commitment is in the prime order subgroup, so mapping the
// commitment to the u-coordinate is an injective encoding.
jubjub::ExtendedPoint::from(self.cm_full_point())
.to_affine()
.get_u()
// TODO: Expose typed representation.
ExtractedNoteCommitment::from(self.cm_full_point()).inner()
}
pub fn rcm(&self) -> jubjub::Fr {
match self.rseed {
Rseed::BeforeZip212(rcm) => rcm,
Rseed::AfterZip212(rseed) => {
jubjub::Fr::from_bytes_wide(prf_expand(&rseed, &[0x04]).as_array())
}
}
self.rseed.rcm().0
}
pub fn generate_or_derive_esk<R: RngCore + CryptoRng>(&self, rng: &mut R) -> jubjub::Fr {

View File

@ -0,0 +1,101 @@
use core::iter;
use bitvec::{array::BitArray, order::Lsb0};
use group::ff::PrimeField;
use subtle::{ConstantTimeEq, CtOption};
use crate::sapling::{
pedersen_hash::Personalization,
spec::{extract_p, windowed_pedersen_commit},
value::NoteValue,
};
/// The trapdoor for a Sapling note commitment.
#[derive(Clone, Debug)]
pub(crate) struct NoteCommitTrapdoor(pub(super) jubjub::Fr);
/// A commitment to a note.
#[derive(Clone, Debug)]
pub struct NoteCommitment(jubjub::SubgroupPoint);
impl NoteCommitment {
pub(crate) fn inner(&self) -> jubjub::SubgroupPoint {
self.0
}
}
impl NoteCommitment {
/// $NoteCommit^Sapling$.
///
/// Defined in [Zcash Protocol Spec § 5.4.8.2: Windowed Pedersen commitments][concretewindowedcommit].
///
/// [concretewindowedcommit]: https://zips.z.cash/protocol/protocol.pdf#concretewindowedcommit
pub(super) fn derive(
g_d: [u8; 32],
pk_d: [u8; 32],
v: NoteValue,
rcm: NoteCommitTrapdoor,
) -> Self {
NoteCommitment(windowed_pedersen_commit(
Personalization::NoteCommitment,
iter::empty()
.chain(v.to_le_bits().iter().by_vals())
.chain(BitArray::<_, Lsb0>::new(g_d).iter().by_vals())
.chain(BitArray::<_, Lsb0>::new(pk_d).iter().by_vals()),
rcm.0,
))
}
}
/// The u-coordinate of the commitment to a note.
#[derive(Copy, Clone, Debug)]
pub struct ExtractedNoteCommitment(pub(super) bls12_381::Scalar);
impl ExtractedNoteCommitment {
/// Deserialize the extracted note commitment from a byte array.
///
/// This method enforces the [consensus rule][cmucanon] that the byte representation
/// of cmu MUST be canonical.
///
/// [cmucanon]: https://zips.z.cash/protocol/protocol.pdf#outputencodingandconsensus
pub fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
bls12_381::Scalar::from_repr(*bytes).map(ExtractedNoteCommitment)
}
/// Serialize the value commitment to its canonical byte representation.
pub fn to_bytes(self) -> [u8; 32] {
self.0.to_repr()
}
}
impl From<NoteCommitment> for ExtractedNoteCommitment {
fn from(cm: NoteCommitment) -> Self {
ExtractedNoteCommitment(extract_p(&cm.0))
}
}
impl ExtractedNoteCommitment {
pub(crate) fn inner(&self) -> bls12_381::Scalar {
self.0
}
}
impl From<&ExtractedNoteCommitment> for [u8; 32] {
fn from(cmu: &ExtractedNoteCommitment) -> Self {
cmu.to_bytes()
}
}
impl ConstantTimeEq for ExtractedNoteCommitment {
fn ct_eq(&self, other: &Self) -> subtle::Choice {
self.0.ct_eq(&other.0)
}
}
impl PartialEq for ExtractedNoteCommitment {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl Eq for ExtractedNoteCommitment {}

View File

@ -2,6 +2,7 @@ use std::array::TryFromSliceError;
use subtle::{Choice, ConstantTimeEq};
use super::NoteCommitment;
use crate::sapling::{
keys::NullifierDerivingKey,
spec::{mixing_pedersen_hash, prf_nf},
@ -25,12 +26,8 @@ impl Nullifier {
/// Defined in [Zcash Protocol Spec § 4.16: Note Commitments and Nullifiers][commitmentsandnullifiers].
///
/// [commitmentsandnullifiers]: https://zips.z.cash/protocol/protocol.pdf#commitmentsandnullifiers
pub(super) fn derive(
nk: &NullifierDerivingKey,
cm: jubjub::SubgroupPoint,
position: u64,
) -> Self {
let rho = mixing_pedersen_hash(cm, position);
pub(super) fn derive(nk: &NullifierDerivingKey, cm: NoteCommitment, position: u64) -> Self {
let rho = mixing_pedersen_hash(cm.inner(), position);
Nullifier(prf_nf(&nk.0, &rho))
}
}

View File

@ -1,9 +1,12 @@
//! Helper functions defined in the Zcash Protocol Specification.
use blake2s_simd::Params as Blake2sParams;
use group::GroupEncoding;
use group::{Curve, GroupEncoding};
use crate::constants::{NULLIFIER_POSITION_GENERATOR, PRF_NF_PERSONALIZATION};
use super::pedersen_hash::{pedersen_hash, Personalization};
use crate::constants::{
NOTE_COMMITMENT_RANDOMNESS_GENERATOR, NULLIFIER_POSITION_GENERATOR, PRF_NF_PERSONALIZATION,
};
/// $MixingPedersenHash$.
///
@ -34,3 +37,32 @@ pub(crate) fn prf_nf(nk: &jubjub::SubgroupPoint, rho: &jubjub::SubgroupPoint) ->
.try_into()
.expect("output length is correct")
}
/// $WindowedPedersenCommit_r(s)$
///
/// Defined in [Zcash Protocol Spec § 5.4.8.2: Windowed Pedersen commitments][concretewindowedcommit].
///
/// [concretewindowedcommit]: https://zips.z.cash/protocol/protocol.pdf#concretewindowedcommit
pub(crate) fn windowed_pedersen_commit<I>(
personalization: Personalization,
s: I,
r: jubjub::Scalar,
) -> jubjub::SubgroupPoint
where
I: IntoIterator<Item = bool>,
{
pedersen_hash(personalization, s) + (NOTE_COMMITMENT_RANDOMNESS_GENERATOR * r)
}
/// Coordinate extractor for Jubjub.
///
/// Defined in [Zcash Protocol Spec § 5.4.9.4: Coordinate Extractor for Jubjub][concreteextractorjubjub].
///
/// [concreteextractorjubjub]: https://zips.z.cash/protocol/protocol.pdf#concreteextractorjubjub
pub(crate) fn extract_p(point: &jubjub::SubgroupPoint) -> bls12_381::Scalar {
// The commitment is in the prime order subgroup, so mapping the
// commitment to the u-coordinate is an injective encoding.
Into::<&jubjub::ExtendedPoint>::into(point)
.to_affine()
.get_u()
}

View File

@ -37,6 +37,7 @@
//! [`SaplingBuilder::add_output`]: crate::transaction::components::sapling::builder::SaplingBuilder::add_output
//! [Rust documentation]: https://doc.rust-lang.org/stable/std/primitive.i64.html
use bitvec::{array::BitArray, order::Lsb0};
use ff::Field;
use group::GroupEncoding;
use rand::RngCore;
@ -67,6 +68,10 @@ impl NoteValue {
pub fn from_raw(value: u64) -> Self {
NoteValue(value)
}
pub(crate) fn to_le_bits(self) -> BitArray<[u8; 8], Lsb0> {
BitArray::<_, Lsb0>::new(self.0.to_le_bytes())
}
}
/// The blinding factor for a [`ValueCommitment`].