use std::{fmt, io};
use bitvec::prelude::*;
use jubjub::ExtendedPoint;
use lazy_static::lazy_static;
use rand_core::{CryptoRng, RngCore};
use crate::{
amount::{Amount, NonNegative},
error::{NoteCommitmentError, RandError},
serialization::{
serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize,
},
};
use super::keys::{find_group_hash, Diversifier, TransmissionKey};
pub mod pedersen_hashes;
#[cfg(test)]
mod test_vectors;
use pedersen_hashes::*;
pub fn generate_trapdoor<T>(csprng: &mut T) -> Result<jubjub::Fr, RandError>
where
T: RngCore + CryptoRng,
{
let mut bytes = [0u8; 64];
csprng
.try_fill_bytes(&mut bytes)
.map_err(|_| RandError::FillBytes)?;
Ok(jubjub::Fr::from_bytes_wide(&bytes))
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct CommitmentRandomness(jubjub::Fr);
#[derive(Clone, Copy, Deserialize, PartialEq, Eq, Serialize)]
pub struct NoteCommitment(#[serde(with = "serde_helpers::AffinePoint")] pub jubjub::AffinePoint);
impl fmt::Debug for NoteCommitment {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("NoteCommitment")
.field("u", &hex::encode(self.0.get_u().to_bytes()))
.field("v", &hex::encode(self.0.get_v().to_bytes()))
.finish()
}
}
impl From<jubjub::ExtendedPoint> for NoteCommitment {
fn from(extended_point: jubjub::ExtendedPoint) -> Self {
Self(jubjub::AffinePoint::from(extended_point))
}
}
impl From<NoteCommitment> 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<Self, Self::Error> {
let possible_point = jubjub::AffinePoint::from_bytes(bytes);
if possible_point.is_some().into() {
Ok(Self(possible_point.unwrap()))
} else {
Err("Invalid jubjub::AffinePoint value")
}
}
}
impl NoteCommitment {
#[allow(non_snake_case)]
pub fn new<T>(
csprng: &mut T,
diversifier: Diversifier,
transmission_key: TransmissionKey,
value: Amount<NonNegative>,
) -> Result<(CommitmentRandomness, Self), NoteCommitmentError>
where
T: RngCore + CryptoRng,
{
let mut s: BitVec<u8, Lsb0> = BitVec::new();
s.append(&mut bitvec![1; 6]);
let g_d_bytes = jubjub::AffinePoint::try_from(diversifier)
.map_err(|_| NoteCommitmentError::InvalidDiversifier)?
.to_bytes();
let pk_d_bytes = <[u8; 32]>::from(transmission_key);
let v_bytes = value.to_bytes();
s.extend(g_d_bytes);
s.extend(pk_d_bytes);
s.extend(v_bytes);
let rcm = CommitmentRandomness(generate_trapdoor(csprng)?);
Ok((
rcm,
NoteCommitment::from(windowed_pedersen_commitment(rcm.0, &s)),
))
}
pub fn extract_u(&self) -> jubjub::Fq {
self.0.get_u()
}
}
#[derive(Clone, Copy, Deserialize, PartialEq, Eq, Serialize)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Default))]
pub struct ValueCommitment(#[serde(with = "serde_helpers::AffinePoint")] jubjub::AffinePoint);
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<ValueCommitment> for ValueCommitment {
type Output = Self;
fn add(self, rhs: ValueCommitment) -> Self::Output {
let value = self.0.to_extended() + rhs.0.to_extended();
ValueCommitment(value.into())
}
}
impl std::ops::AddAssign<ValueCommitment> 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 {
f.debug_struct("ValueCommitment")
.field("u", &hex::encode(self.0.get_u().to_bytes()))
.field("v", &hex::encode(self.0.get_v().to_bytes()))
.finish()
}
}
impl From<jubjub::ExtendedPoint> for ValueCommitment {
fn from(extended_point: jubjub::ExtendedPoint) -> Self {
Self(jubjub::AffinePoint::from(extended_point))
}
}
impl From<ValueCommitment> 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<ValueCommitment> for ValueCommitment {
type Output = Self;
fn sub(self, rhs: ValueCommitment) -> Self::Output {
ValueCommitment((self.0.to_extended() - rhs.0.to_extended()).into())
}
}
impl std::ops::SubAssign<ValueCommitment> for ValueCommitment {
fn sub_assign(&mut self, rhs: ValueCommitment) {
*self = *self - rhs;
}
}
impl std::iter::Sum for ValueCommitment {
fn sum<I>(iter: I) -> Self
where
I: Iterator<Item = Self>,
{
iter.fold(
ValueCommitment(jubjub::AffinePoint::identity()),
std::ops::Add::add,
)
}
}
impl TryFrom<[u8; 32]> for ValueCommitment {
type Error = &'static str;
fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
let possible_point = jubjub::AffinePoint::from_bytes(bytes);
if possible_point.is_some().into() {
let point = possible_point.unwrap();
Ok(ExtendedPoint::from(point).into())
} else {
Err("Invalid jubjub::AffinePoint value")
}
}
}
impl ValueCommitment {
pub fn randomized<T>(csprng: &mut T, value: Amount) -> Result<Self, RandError>
where
T: RngCore + CryptoRng,
{
let rcv = generate_trapdoor(csprng)?;
Ok(Self::new(rcv, value))
}
#[allow(non_snake_case)]
pub fn new(rcv: jubjub::Fr, value: Amount) -> Self {
let v = jubjub::Fr::from(value);
Self::from(*V * v + *R * rcv)
}
}
lazy_static! {
static ref V: ExtendedPoint = find_group_hash(*b"Zcash_cv", b"v");
static ref R: ExtendedPoint = find_group_hash(*b"Zcash_cv", b"r");
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq, Serialize)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Default))]
pub struct NotSmallOrderValueCommitment(ValueCommitment);
impl TryFrom<ValueCommitment> for NotSmallOrderValueCommitment {
type Error = &'static str;
fn try_from(value_commitment: ValueCommitment) -> Result<Self, Self::Error> {
if value_commitment.0.is_small_order().into() {
Err("jubjub::AffinePoint value for Sapling ValueCommitment is of small order")
} else {
Ok(Self(value_commitment))
}
}
}
impl TryFrom<jubjub::ExtendedPoint> for NotSmallOrderValueCommitment {
type Error = &'static str;
fn try_from(extended_point: jubjub::ExtendedPoint) -> Result<Self, Self::Error> {
ValueCommitment::from(extended_point).try_into()
}
}
impl From<NotSmallOrderValueCommitment> for ValueCommitment {
fn from(cv: NotSmallOrderValueCommitment) -> Self {
cv.0
}
}
impl From<NotSmallOrderValueCommitment> for jubjub::AffinePoint {
fn from(cv: NotSmallOrderValueCommitment) -> Self {
cv.0 .0
}
}
impl ZcashSerialize for NotSmallOrderValueCommitment {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
writer.write_all(&<[u8; 32]>::from(self.0)[..])?;
Ok(())
}
}
impl ZcashDeserialize for NotSmallOrderValueCommitment {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
let vc = ValueCommitment::try_from(reader.read_32_bytes()?)
.map_err(SerializationError::Parse)?;
vc.try_into().map_err(SerializationError::Parse)
}
}
#[cfg(test)]
mod tests {
use std::ops::Neg;
use super::*;
#[test]
fn pedersen_hash_to_point_test_vectors() {
let _init_guard = zebra_test::init();
const D: [u8; 8] = *b"Zcash_PH";
for test_vector in test_vectors::TEST_VECTORS.iter() {
let result = jubjub::AffinePoint::from(pedersen_hash_to_point(
D,
&test_vector.input_bits.clone(),
));
assert_eq!(result, test_vector.output_point);
}
}
#[test]
fn add() {
let _init_guard = zebra_test::init();
let identity = ValueCommitment(jubjub::AffinePoint::identity());
let g = ValueCommitment(jubjub::AffinePoint::from_raw_unchecked(
jubjub::Fq::from_raw([
0xe4b3_d35d_f1a7_adfe,
0xcaf5_5d1b_29bf_81af,
0x8b0f_03dd_d60a_8187,
0x62ed_cbb8_bf37_87c8,
]),
jubjub::Fq::from_raw([
0x0000_0000_0000_000b,
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
]),
));
assert_eq!(identity + g, g);
}
#[test]
fn add_assign() {
let _init_guard = zebra_test::init();
let mut identity = ValueCommitment(jubjub::AffinePoint::identity());
let g = ValueCommitment(jubjub::AffinePoint::from_raw_unchecked(
jubjub::Fq::from_raw([
0xe4b3_d35d_f1a7_adfe,
0xcaf5_5d1b_29bf_81af,
0x8b0f_03dd_d60a_8187,
0x62ed_cbb8_bf37_87c8,
]),
jubjub::Fq::from_raw([
0x0000_0000_0000_000b,
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
]),
));
identity += g;
let new_g = identity;
assert_eq!(new_g, g);
}
#[test]
fn sub() {
let _init_guard = zebra_test::init();
let g_point = jubjub::AffinePoint::from_raw_unchecked(
jubjub::Fq::from_raw([
0xe4b3_d35d_f1a7_adfe,
0xcaf5_5d1b_29bf_81af,
0x8b0f_03dd_d60a_8187,
0x62ed_cbb8_bf37_87c8,
]),
jubjub::Fq::from_raw([
0x0000_0000_0000_000b,
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
]),
);
let identity = ValueCommitment(jubjub::AffinePoint::identity());
let g = ValueCommitment(g_point);
assert_eq!(identity - g, ValueCommitment(g_point.neg()));
}
#[test]
fn sub_assign() {
let _init_guard = zebra_test::init();
let g_point = jubjub::AffinePoint::from_raw_unchecked(
jubjub::Fq::from_raw([
0xe4b3_d35d_f1a7_adfe,
0xcaf5_5d1b_29bf_81af,
0x8b0f_03dd_d60a_8187,
0x62ed_cbb8_bf37_87c8,
]),
jubjub::Fq::from_raw([
0x0000_0000_0000_000b,
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
]),
);
let mut identity = ValueCommitment(jubjub::AffinePoint::identity());
let g = ValueCommitment(g_point);
identity -= g;
let new_g = identity;
assert_eq!(new_g, ValueCommitment(g_point.neg()));
}
#[test]
fn sum() {
let _init_guard = zebra_test::init();
let g_point = jubjub::AffinePoint::from_raw_unchecked(
jubjub::Fq::from_raw([
0xe4b3_d35d_f1a7_adfe,
0xcaf5_5d1b_29bf_81af,
0x8b0f_03dd_d60a_8187,
0x62ed_cbb8_bf37_87c8,
]),
jubjub::Fq::from_raw([
0x0000_0000_0000_000b,
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
]),
);
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_extended().double().into());
assert_eq!(sum, doubled_g);
}
}