Refactor Sapling note commitment derivation
This commit is contained in:
parent
65271b49e5
commit
88d46fd6b3
|
@ -1,15 +1,14 @@
|
||||||
use byteorder::{LittleEndian, WriteBytesExt};
|
|
||||||
use group::{
|
use group::{
|
||||||
ff::{Field, PrimeField},
|
ff::{Field, PrimeField},
|
||||||
Curve, GroupEncoding,
|
GroupEncoding,
|
||||||
};
|
};
|
||||||
use rand_core::{CryptoRng, RngCore};
|
use rand_core::{CryptoRng, RngCore};
|
||||||
|
|
||||||
use super::{
|
use super::{value::NoteValue, Node, Nullifier, NullifierDerivingKey};
|
||||||
pedersen_hash::{pedersen_hash, Personalization},
|
use crate::keys::prf_expand;
|
||||||
Node, Nullifier, NullifierDerivingKey,
|
|
||||||
};
|
mod commitment;
|
||||||
use crate::{constants, keys::prf_expand};
|
pub use self::commitment::{ExtractedNoteCommitment, NoteCommitment};
|
||||||
|
|
||||||
pub(super) mod nullifier;
|
pub(super) mod nullifier;
|
||||||
|
|
||||||
|
@ -24,6 +23,20 @@ pub enum Rseed {
|
||||||
AfterZip212([u8; 32]),
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Note {
|
pub struct Note {
|
||||||
/// The value of the note
|
/// The value of the note
|
||||||
|
@ -53,31 +66,13 @@ impl Note {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes the note commitment, returning the full point.
|
/// Computes the note commitment, returning the full point.
|
||||||
fn cm_full_point(&self) -> jubjub::SubgroupPoint {
|
fn cm_full_point(&self) -> NoteCommitment {
|
||||||
// Calculate the note contents, as bytes
|
NoteCommitment::derive(
|
||||||
let mut note_contents = vec![];
|
self.g_d.to_bytes(),
|
||||||
|
self.pk_d.to_bytes(),
|
||||||
// Writing the value in little endian
|
NoteValue::from_raw(self.value),
|
||||||
note_contents.write_u64::<LittleEndian>(self.value).unwrap();
|
self.rseed.rcm(),
|
||||||
|
)
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes the nullifier given the nullifier deriving key and
|
/// Computes the nullifier given the nullifier deriving key and
|
||||||
|
@ -88,20 +83,12 @@ impl Note {
|
||||||
|
|
||||||
/// Computes the note commitment
|
/// Computes the note commitment
|
||||||
pub fn cmu(&self) -> bls12_381::Scalar {
|
pub fn cmu(&self) -> bls12_381::Scalar {
|
||||||
// The commitment is in the prime order subgroup, so mapping the
|
// TODO: Expose typed representation.
|
||||||
// commitment to the u-coordinate is an injective encoding.
|
ExtractedNoteCommitment::from(self.cm_full_point()).inner()
|
||||||
jubjub::ExtendedPoint::from(self.cm_full_point())
|
|
||||||
.to_affine()
|
|
||||||
.get_u()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rcm(&self) -> jubjub::Fr {
|
pub fn rcm(&self) -> jubjub::Fr {
|
||||||
match self.rseed {
|
self.rseed.rcm().0
|
||||||
Rseed::BeforeZip212(rcm) => rcm,
|
|
||||||
Rseed::AfterZip212(rseed) => {
|
|
||||||
jubjub::Fr::from_bytes_wide(prf_expand(&rseed, &[0x04]).as_array())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_or_derive_esk<R: RngCore + CryptoRng>(&self, rng: &mut R) -> jubjub::Fr {
|
pub fn generate_or_derive_esk<R: RngCore + CryptoRng>(&self, rng: &mut R) -> jubjub::Fr {
|
||||||
|
|
|
@ -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 {}
|
|
@ -2,6 +2,7 @@ use std::array::TryFromSliceError;
|
||||||
|
|
||||||
use subtle::{Choice, ConstantTimeEq};
|
use subtle::{Choice, ConstantTimeEq};
|
||||||
|
|
||||||
|
use super::NoteCommitment;
|
||||||
use crate::sapling::{
|
use crate::sapling::{
|
||||||
keys::NullifierDerivingKey,
|
keys::NullifierDerivingKey,
|
||||||
spec::{mixing_pedersen_hash, prf_nf},
|
spec::{mixing_pedersen_hash, prf_nf},
|
||||||
|
@ -25,12 +26,8 @@ impl Nullifier {
|
||||||
/// Defined in [Zcash Protocol Spec § 4.16: Note Commitments and Nullifiers][commitmentsandnullifiers].
|
/// Defined in [Zcash Protocol Spec § 4.16: Note Commitments and Nullifiers][commitmentsandnullifiers].
|
||||||
///
|
///
|
||||||
/// [commitmentsandnullifiers]: https://zips.z.cash/protocol/protocol.pdf#commitmentsandnullifiers
|
/// [commitmentsandnullifiers]: https://zips.z.cash/protocol/protocol.pdf#commitmentsandnullifiers
|
||||||
pub(super) fn derive(
|
pub(super) fn derive(nk: &NullifierDerivingKey, cm: NoteCommitment, position: u64) -> Self {
|
||||||
nk: &NullifierDerivingKey,
|
let rho = mixing_pedersen_hash(cm.inner(), position);
|
||||||
cm: jubjub::SubgroupPoint,
|
|
||||||
position: u64,
|
|
||||||
) -> Self {
|
|
||||||
let rho = mixing_pedersen_hash(cm, position);
|
|
||||||
Nullifier(prf_nf(&nk.0, &rho))
|
Nullifier(prf_nf(&nk.0, &rho))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
//! Helper functions defined in the Zcash Protocol Specification.
|
//! Helper functions defined in the Zcash Protocol Specification.
|
||||||
|
|
||||||
use blake2s_simd::Params as Blake2sParams;
|
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$.
|
/// $MixingPedersenHash$.
|
||||||
///
|
///
|
||||||
|
@ -34,3 +37,32 @@ pub(crate) fn prf_nf(nk: &jubjub::SubgroupPoint, rho: &jubjub::SubgroupPoint) ->
|
||||||
.try_into()
|
.try_into()
|
||||||
.expect("output length is correct")
|
.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()
|
||||||
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
//! [`SaplingBuilder::add_output`]: crate::transaction::components::sapling::builder::SaplingBuilder::add_output
|
//! [`SaplingBuilder::add_output`]: crate::transaction::components::sapling::builder::SaplingBuilder::add_output
|
||||||
//! [Rust documentation]: https://doc.rust-lang.org/stable/std/primitive.i64.html
|
//! [Rust documentation]: https://doc.rust-lang.org/stable/std/primitive.i64.html
|
||||||
|
|
||||||
|
use bitvec::{array::BitArray, order::Lsb0};
|
||||||
use ff::Field;
|
use ff::Field;
|
||||||
use group::GroupEncoding;
|
use group::GroupEncoding;
|
||||||
use rand::RngCore;
|
use rand::RngCore;
|
||||||
|
@ -67,6 +68,10 @@ impl NoteValue {
|
||||||
pub fn from_raw(value: u64) -> Self {
|
pub fn from_raw(value: u64) -> Self {
|
||||||
NoteValue(value)
|
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`].
|
/// The blinding factor for a [`ValueCommitment`].
|
||||||
|
|
Loading…
Reference in New Issue