//! Note and value commitments. use std::{convert::TryFrom, fmt, io}; use bitvec::prelude::*; use group::{ff::PrimeField, prime::PrimeCurveAffine, GroupEncoding}; use halo2::{ arithmetic::{Coordinates, CurveAffine, FieldExt}, pasta::pallas, }; use lazy_static::lazy_static; use rand_core::{CryptoRng, RngCore}; use crate::{ amount::Amount, serialization::{ serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize, }, }; use super::{ keys::prf_expand, note::{Note, Psi, SeedRandomness}, sinsemilla::*, }; /// Generates a random scalar from the scalar field 𝔽_{q_P}. /// /// pub fn generate_trapdoor(csprng: &mut T) -> pallas::Scalar where T: RngCore + CryptoRng, { let mut bytes = [0u8; 64]; csprng.fill_bytes(&mut bytes); // pallas::Scalar::from_bytes_wide() reduces the input modulo q_P under the hood. pallas::Scalar::from_bytes_wide(&bytes) } /// The randomness used in the Simsemilla hash for note commitment. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct CommitmentRandomness(pallas::Scalar); impl From for CommitmentRandomness { /// rcm = ToScalar^Orchard((PRF^expand_rseed ([5])) /// /// 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, Eq, Serialize)] pub struct NoteCommitment(#[serde(with = "serde_helpers::Affine")] pub pallas::Affine); impl fmt::Debug for NoteCommitment { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut d = f.debug_struct("NoteCommitment"); let option: Option> = self.0.coordinates().into(); match option { Some(coordinates) => d .field("x", &hex::encode(coordinates.x().to_repr())) .field("y", &hex::encode(coordinates.y().to_repr())) .finish(), None => d .field("x", &hex::encode(pallas::Base::zero().to_repr())) .field("y", &hex::encode(pallas::Base::zero().to_repr())) .finish(), } } } impl From for NoteCommitment { fn from(projective_point: pallas::Point) -> Self { Self(pallas::Affine::from(projective_point)) } } impl From for [u8; 32] { fn from(cm: NoteCommitment) -> [u8; 32] { cm.0.to_bytes() } } impl TryFrom<[u8; 32]> for NoteCommitment { type Error = &'static str; fn try_from(bytes: [u8; 32]) -> Result { let possible_point = pallas::Affine::from_bytes(&bytes); if possible_point.is_some().into() { Ok(Self(possible_point.unwrap())) } else { Err("Invalid pallas::Affine value") } } } impl NoteCommitment { /// Generate a new _NoteCommitment_. /// /// Unlike in Sapling, the definition of an Orchard _note_ includes the ρ /// field; the _note_'s position in the _note commitment tree_ does not need /// to be known in order to compute this value. /// /// NoteCommit^Orchard_rcm(repr_P(gd),repr_P(pkd), v, ρ, ψ) := /// /// #[allow(non_snake_case)] pub fn new(note: Note) -> Option { // s as in the argument name for WindowedPedersenCommit_r(s) let mut s: BitVec = BitVec::new(); // Prefix s.append(&mut bitvec![1; 6]); // The `TryFrom` impls for the `pallas::*Point`s handles // calling `DiversifyHash` implicitly. // _diversified base_ let g_d_bytes = pallas::Affine::try_from(note.address.diversifier) .ok()? .to_bytes(); let pk_d_bytes: [u8; 32] = note.address.transmission_key.into(); let v_bytes = note.value.to_bytes(); let rho_bytes: [u8; 32] = note.rho.into(); let psi_bytes: [u8; 32] = Psi::from(note.rseed).into(); // g*d || pk*d || I2LEBSP_64(v) || I2LEBSP_l^Orchard_Base(ρ) || I2LEBSP_l^Orchard_base(ψ) s.extend(g_d_bytes); s.extend(pk_d_bytes); s.extend(v_bytes); s.extend(rho_bytes); s.extend(psi_bytes); let rcm = CommitmentRandomness::from(note.rseed); Some(NoteCommitment::from( sinsemilla_commit(rcm.0, b"z.cash:Orchard-NoteCommit", &s) .expect("valid orchard note commitment, not ⊥ "), )) } /// Extract the x coordinate of the note commitment. pub fn extract_x(&self) -> pallas::Base { extract_p(self.0.into()) } } /// A homomorphic Pedersen commitment to the net value of a _note_, used in /// Action descriptions. /// /// #[derive(Clone, Copy, Deserialize, PartialEq, Eq, Serialize)] pub struct ValueCommitment(#[serde(with = "serde_helpers::Affine")] pub pallas::Affine); impl<'a> std::ops::Add<&'a ValueCommitment> for ValueCommitment { type Output = Self; fn add(self, rhs: &'a ValueCommitment) -> Self::Output { self + *rhs } } impl std::ops::Add for ValueCommitment { type Output = Self; fn add(self, rhs: ValueCommitment) -> Self::Output { ValueCommitment((self.0 + rhs.0).into()) } } impl std::ops::AddAssign for ValueCommitment { fn add_assign(&mut self, rhs: ValueCommitment) { *self = *self + rhs } } impl fmt::Debug for ValueCommitment { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut d = f.debug_struct("ValueCommitment"); let option: Option> = self.0.coordinates().into(); match option { Some(coordinates) => d .field("x", &hex::encode(coordinates.x().to_repr())) .field("y", &hex::encode(coordinates.y().to_repr())) .finish(), None => d .field("x", &hex::encode(pallas::Base::zero().to_repr())) .field("y", &hex::encode(pallas::Base::zero().to_repr())) .finish(), } } } impl From for ValueCommitment { fn from(projective_point: pallas::Point) -> Self { Self(pallas::Affine::from(projective_point)) } } /// LEBS2OSP256(repr_P(cv)) /// /// impl From for [u8; 32] { fn from(cm: ValueCommitment) -> [u8; 32] { cm.0.to_bytes() } } impl<'a> std::ops::Sub<&'a ValueCommitment> for ValueCommitment { type Output = Self; fn sub(self, rhs: &'a ValueCommitment) -> Self::Output { self - *rhs } } impl std::ops::Sub for ValueCommitment { type Output = Self; fn sub(self, rhs: ValueCommitment) -> Self::Output { ValueCommitment((self.0 - rhs.0).into()) } } impl std::ops::SubAssign for ValueCommitment { fn sub_assign(&mut self, rhs: ValueCommitment) { *self = *self - rhs; } } impl std::iter::Sum for ValueCommitment { fn sum(iter: I) -> Self where I: Iterator, { iter.fold( ValueCommitment(pallas::Affine::identity()), std::ops::Add::add, ) } } /// LEBS2OSP256(repr_P(cv)) /// /// impl TryFrom<[u8; 32]> for ValueCommitment { type Error = &'static str; fn try_from(bytes: [u8; 32]) -> Result { let possible_point = pallas::Affine::from_bytes(&bytes); if possible_point.is_some().into() { Ok(Self(possible_point.unwrap())) } else { Err("Invalid pallas::Affine value") } } } impl ZcashSerialize for ValueCommitment { fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { writer.write_all(&<[u8; 32]>::from(*self)[..])?; Ok(()) } } impl ZcashDeserialize for ValueCommitment { fn zcash_deserialize(mut reader: R) -> Result { Self::try_from(reader.read_32_bytes()?).map_err(SerializationError::Parse) } } impl ValueCommitment { /// Generate a new _ValueCommitment_. /// /// pub fn randomized(csprng: &mut T, value: Amount) -> Self where T: RngCore + CryptoRng, { let rcv = generate_trapdoor(csprng); Self::new(rcv, value) } /// Generate a new `ValueCommitment` from an existing `rcv on a `value`. /// /// ValueCommit^Orchard(v) := /// /// #[allow(non_snake_case)] pub fn new(rcv: pallas::Scalar, value: Amount) -> Self { lazy_static! { static ref V: pallas::Point = pallas_group_hash(b"z.cash:Orchard-cv", b"v"); static ref R: pallas::Point = pallas_group_hash(b"z.cash:Orchard-cv", b"r"); } let v = pallas::Scalar::from(value); Self::from(*V * v + *R * rcv) } } #[cfg(test)] mod tests { use std::ops::Neg; use group::Group; use super::*; // #[test] // fn sinsemilla_hash_to_point_test_vectors() { // zebra_test::init(); // const D: [u8; 8] = *b"Zcash_PH"; // for test_vector in test_vectors::TEST_VECTORS.iter() { // let result = // pallas::Affine::from(sinsemilla_hash_to_point(D, &test_vector.input_bits.clone())); // assert_eq!(result, test_vector.output_point); // } // } #[test] fn add() { zebra_test::init(); let identity = ValueCommitment(pallas::Affine::identity()); let g = ValueCommitment(pallas::Affine::generator()); assert_eq!(identity + g, g); } #[test] fn add_assign() { zebra_test::init(); let mut identity = ValueCommitment(pallas::Affine::identity()); let g = ValueCommitment(pallas::Affine::generator()); identity += g; let new_g = identity; assert_eq!(new_g, g); } #[test] fn sub() { zebra_test::init(); let g_point = pallas::Affine::generator(); let identity = ValueCommitment(pallas::Affine::identity()); let g = ValueCommitment(g_point); assert_eq!(identity - g, ValueCommitment(g_point.neg())); } #[test] fn sub_assign() { zebra_test::init(); let g_point = pallas::Affine::generator(); let mut identity = ValueCommitment(pallas::Affine::identity()); let g = ValueCommitment(g_point); identity -= g; let new_g = identity; assert_eq!(new_g, ValueCommitment(g_point.neg())); } #[test] fn sum() { zebra_test::init(); let g_point = pallas::Affine::generator(); let g = ValueCommitment(g_point); let other_g = ValueCommitment(g_point); let sum: ValueCommitment = vec![g, other_g].into_iter().sum(); let doubled_g = ValueCommitment(g_point.to_curve().double().into()); assert_eq!(sum, doubled_g); } }