1use std::{fmt, io};
4
5use bitvec::prelude::*;
6use jubjub::ExtendedPoint;
7use lazy_static::lazy_static;
8use rand_core::{CryptoRng, RngCore};
9
10use crate::{
11 amount::{Amount, NonNegative},
12 error::{NoteCommitmentError, RandError},
13 serialization::{
14 serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize,
15 },
16};
17
18use super::keys::{find_group_hash, Diversifier, TransmissionKey};
19
20pub mod pedersen_hashes;
21
22#[cfg(test)]
23mod test_vectors;
24
25use pedersen_hashes::*;
26
27pub fn generate_trapdoor<T>(csprng: &mut T) -> Result<jubjub::Fr, RandError>
36where
37 T: RngCore + CryptoRng,
38{
39 let mut bytes = [0u8; 64];
40 csprng
41 .try_fill_bytes(&mut bytes)
42 .map_err(|_| RandError::FillBytes)?;
43 Ok(jubjub::Fr::from_bytes_wide(&bytes))
45}
46
47#[derive(Copy, Clone, Debug, PartialEq, Eq)]
49pub struct CommitmentRandomness(jubjub::Fr);
50
51#[derive(Clone, Copy, Deserialize, PartialEq, Eq, Serialize)]
53pub struct NoteCommitment(#[serde(with = "serde_helpers::AffinePoint")] pub jubjub::AffinePoint);
54
55impl fmt::Debug for NoteCommitment {
56 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
57 f.debug_struct("NoteCommitment")
58 .field("u", &hex::encode(self.0.get_u().to_bytes()))
59 .field("v", &hex::encode(self.0.get_v().to_bytes()))
60 .finish()
61 }
62}
63
64impl From<jubjub::ExtendedPoint> for NoteCommitment {
65 fn from(extended_point: jubjub::ExtendedPoint) -> Self {
66 Self(jubjub::AffinePoint::from(extended_point))
67 }
68}
69
70impl From<NoteCommitment> for [u8; 32] {
71 fn from(cm: NoteCommitment) -> [u8; 32] {
72 cm.0.to_bytes()
73 }
74}
75
76impl TryFrom<[u8; 32]> for NoteCommitment {
77 type Error = &'static str;
78
79 fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
80 let possible_point = jubjub::AffinePoint::from_bytes(bytes);
81
82 if possible_point.is_some().into() {
83 Ok(Self(possible_point.unwrap()))
84 } else {
85 Err("Invalid jubjub::AffinePoint value")
86 }
87 }
88}
89
90impl NoteCommitment {
91 #[allow(non_snake_case)]
102 pub fn new<T>(
103 csprng: &mut T,
104 diversifier: Diversifier,
105 transmission_key: TransmissionKey,
106 value: Amount<NonNegative>,
107 ) -> Result<(CommitmentRandomness, Self), NoteCommitmentError>
108 where
109 T: RngCore + CryptoRng,
110 {
111 let mut s: BitVec<u8, Lsb0> = BitVec::new();
113
114 s.append(&mut bitvec![1; 6]);
116
117 let g_d_bytes = jubjub::AffinePoint::try_from(diversifier)
124 .map_err(|_| NoteCommitmentError::InvalidDiversifier)?
125 .to_bytes();
126
127 let pk_d_bytes = <[u8; 32]>::from(transmission_key);
128 let v_bytes = value.to_bytes();
129
130 s.extend(g_d_bytes);
131 s.extend(pk_d_bytes);
132 s.extend(v_bytes);
133
134 let rcm = CommitmentRandomness(generate_trapdoor(csprng)?);
135
136 Ok((
137 rcm,
138 NoteCommitment::from(windowed_pedersen_commitment(rcm.0, &s)),
139 ))
140 }
141
142 pub fn extract_u(&self) -> jubjub::Fq {
146 self.0.get_u()
147 }
148}
149
150#[derive(Clone, Copy, Deserialize, PartialEq, Eq, Serialize)]
158#[cfg_attr(any(test, feature = "proptest-impl"), derive(Default))]
159pub struct ValueCommitment(#[serde(with = "serde_helpers::AffinePoint")] jubjub::AffinePoint);
160
161impl<'a> std::ops::Add<&'a ValueCommitment> for ValueCommitment {
162 type Output = Self;
163
164 fn add(self, rhs: &'a ValueCommitment) -> Self::Output {
165 self + *rhs
166 }
167}
168
169impl std::ops::Add<ValueCommitment> for ValueCommitment {
170 type Output = Self;
171
172 fn add(self, rhs: ValueCommitment) -> Self::Output {
173 let value = self.0.to_extended() + rhs.0.to_extended();
174 ValueCommitment(value.into())
175 }
176}
177
178impl std::ops::AddAssign<ValueCommitment> for ValueCommitment {
179 fn add_assign(&mut self, rhs: ValueCommitment) {
180 *self = *self + rhs
181 }
182}
183
184impl fmt::Debug for ValueCommitment {
185 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
186 f.debug_struct("ValueCommitment")
187 .field("u", &hex::encode(self.0.get_u().to_bytes()))
188 .field("v", &hex::encode(self.0.get_v().to_bytes()))
189 .finish()
190 }
191}
192
193impl From<jubjub::ExtendedPoint> for ValueCommitment {
194 fn from(extended_point: jubjub::ExtendedPoint) -> Self {
196 Self(jubjub::AffinePoint::from(extended_point))
197 }
198}
199
200impl From<ValueCommitment> for [u8; 32] {
205 fn from(cm: ValueCommitment) -> [u8; 32] {
206 cm.0.to_bytes()
207 }
208}
209
210impl<'a> std::ops::Sub<&'a ValueCommitment> for ValueCommitment {
211 type Output = Self;
212
213 fn sub(self, rhs: &'a ValueCommitment) -> Self::Output {
214 self - *rhs
215 }
216}
217
218impl std::ops::Sub<ValueCommitment> for ValueCommitment {
219 type Output = Self;
220
221 fn sub(self, rhs: ValueCommitment) -> Self::Output {
222 ValueCommitment((self.0.to_extended() - rhs.0.to_extended()).into())
223 }
224}
225
226impl std::ops::SubAssign<ValueCommitment> for ValueCommitment {
227 fn sub_assign(&mut self, rhs: ValueCommitment) {
228 *self = *self - rhs;
229 }
230}
231
232impl std::iter::Sum for ValueCommitment {
233 fn sum<I>(iter: I) -> Self
234 where
235 I: Iterator<Item = Self>,
236 {
237 iter.fold(
238 ValueCommitment(jubjub::AffinePoint::identity()),
239 std::ops::Add::add,
240 )
241 }
242}
243
244impl TryFrom<[u8; 32]> for ValueCommitment {
249 type Error = &'static str;
250
251 fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
252 let possible_point = jubjub::AffinePoint::from_bytes(bytes);
253
254 if possible_point.is_some().into() {
255 let point = possible_point.unwrap();
256 Ok(ExtendedPoint::from(point).into())
257 } else {
258 Err("Invalid jubjub::AffinePoint value")
259 }
260 }
261}
262
263impl ValueCommitment {
264 pub fn randomized<T>(csprng: &mut T, value: Amount) -> Result<Self, RandError>
268 where
269 T: RngCore + CryptoRng,
270 {
271 let rcv = generate_trapdoor(csprng)?;
272
273 Ok(Self::new(rcv, value))
274 }
275
276 #[allow(non_snake_case)]
280 pub fn new(rcv: jubjub::Fr, value: Amount) -> Self {
281 let v = jubjub::Fr::from(value);
282 Self::from(*V * v + *R * rcv)
283 }
284}
285
286lazy_static! {
287 static ref V: ExtendedPoint = find_group_hash(*b"Zcash_cv", b"v");
288 static ref R: ExtendedPoint = find_group_hash(*b"Zcash_cv", b"r");
289}
290
291#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq, Serialize)]
303#[cfg_attr(any(test, feature = "proptest-impl"), derive(Default))]
304pub struct NotSmallOrderValueCommitment(ValueCommitment);
305
306impl TryFrom<ValueCommitment> for NotSmallOrderValueCommitment {
307 type Error = &'static str;
308
309 fn try_from(value_commitment: ValueCommitment) -> Result<Self, Self::Error> {
324 if value_commitment.0.is_small_order().into() {
325 Err("jubjub::AffinePoint value for Sapling ValueCommitment is of small order")
326 } else {
327 Ok(Self(value_commitment))
328 }
329 }
330}
331
332impl TryFrom<jubjub::ExtendedPoint> for NotSmallOrderValueCommitment {
333 type Error = &'static str;
334
335 fn try_from(extended_point: jubjub::ExtendedPoint) -> Result<Self, Self::Error> {
337 ValueCommitment::from(extended_point).try_into()
338 }
339}
340
341impl From<NotSmallOrderValueCommitment> for ValueCommitment {
342 fn from(cv: NotSmallOrderValueCommitment) -> Self {
343 cv.0
344 }
345}
346
347impl From<NotSmallOrderValueCommitment> for jubjub::AffinePoint {
348 fn from(cv: NotSmallOrderValueCommitment) -> Self {
349 cv.0 .0
350 }
351}
352
353impl ZcashSerialize for NotSmallOrderValueCommitment {
354 fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
355 writer.write_all(&<[u8; 32]>::from(self.0)[..])?;
356 Ok(())
357 }
358}
359
360impl ZcashDeserialize for NotSmallOrderValueCommitment {
361 fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
362 let vc = ValueCommitment::try_from(reader.read_32_bytes()?)
363 .map_err(SerializationError::Parse)?;
364 vc.try_into().map_err(SerializationError::Parse)
365 }
366}
367
368#[cfg(test)]
369mod tests {
370
371 use std::ops::Neg;
372
373 use super::*;
374
375 #[test]
376 fn pedersen_hash_to_point_test_vectors() {
377 let _init_guard = zebra_test::init();
378
379 const D: [u8; 8] = *b"Zcash_PH";
380
381 for test_vector in test_vectors::TEST_VECTORS.iter() {
382 let result = jubjub::AffinePoint::from(pedersen_hash_to_point(
383 D,
384 &test_vector.input_bits.clone(),
385 ));
386
387 assert_eq!(result, test_vector.output_point);
388 }
389 }
390
391 #[test]
392 fn add() {
393 let _init_guard = zebra_test::init();
394
395 let identity = ValueCommitment(jubjub::AffinePoint::identity());
396
397 let g = ValueCommitment(jubjub::AffinePoint::from_raw_unchecked(
398 jubjub::Fq::from_raw([
399 0xe4b3_d35d_f1a7_adfe,
400 0xcaf5_5d1b_29bf_81af,
401 0x8b0f_03dd_d60a_8187,
402 0x62ed_cbb8_bf37_87c8,
403 ]),
404 jubjub::Fq::from_raw([
405 0x0000_0000_0000_000b,
406 0x0000_0000_0000_0000,
407 0x0000_0000_0000_0000,
408 0x0000_0000_0000_0000,
409 ]),
410 ));
411
412 assert_eq!(identity + g, g);
413 }
414
415 #[test]
416 fn add_assign() {
417 let _init_guard = zebra_test::init();
418
419 let mut identity = ValueCommitment(jubjub::AffinePoint::identity());
420
421 let g = ValueCommitment(jubjub::AffinePoint::from_raw_unchecked(
422 jubjub::Fq::from_raw([
423 0xe4b3_d35d_f1a7_adfe,
424 0xcaf5_5d1b_29bf_81af,
425 0x8b0f_03dd_d60a_8187,
426 0x62ed_cbb8_bf37_87c8,
427 ]),
428 jubjub::Fq::from_raw([
429 0x0000_0000_0000_000b,
430 0x0000_0000_0000_0000,
431 0x0000_0000_0000_0000,
432 0x0000_0000_0000_0000,
433 ]),
434 ));
435
436 identity += g;
437 let new_g = identity;
438
439 assert_eq!(new_g, g);
440 }
441
442 #[test]
443 fn sub() {
444 let _init_guard = zebra_test::init();
445
446 let g_point = jubjub::AffinePoint::from_raw_unchecked(
447 jubjub::Fq::from_raw([
448 0xe4b3_d35d_f1a7_adfe,
449 0xcaf5_5d1b_29bf_81af,
450 0x8b0f_03dd_d60a_8187,
451 0x62ed_cbb8_bf37_87c8,
452 ]),
453 jubjub::Fq::from_raw([
454 0x0000_0000_0000_000b,
455 0x0000_0000_0000_0000,
456 0x0000_0000_0000_0000,
457 0x0000_0000_0000_0000,
458 ]),
459 );
460
461 let identity = ValueCommitment(jubjub::AffinePoint::identity());
462
463 let g = ValueCommitment(g_point);
464
465 assert_eq!(identity - g, ValueCommitment(g_point.neg()));
466 }
467
468 #[test]
469 fn sub_assign() {
470 let _init_guard = zebra_test::init();
471
472 let g_point = jubjub::AffinePoint::from_raw_unchecked(
473 jubjub::Fq::from_raw([
474 0xe4b3_d35d_f1a7_adfe,
475 0xcaf5_5d1b_29bf_81af,
476 0x8b0f_03dd_d60a_8187,
477 0x62ed_cbb8_bf37_87c8,
478 ]),
479 jubjub::Fq::from_raw([
480 0x0000_0000_0000_000b,
481 0x0000_0000_0000_0000,
482 0x0000_0000_0000_0000,
483 0x0000_0000_0000_0000,
484 ]),
485 );
486
487 let mut identity = ValueCommitment(jubjub::AffinePoint::identity());
488
489 let g = ValueCommitment(g_point);
490
491 identity -= g;
492 let new_g = identity;
493
494 assert_eq!(new_g, ValueCommitment(g_point.neg()));
495 }
496
497 #[test]
498 fn sum() {
499 let _init_guard = zebra_test::init();
500
501 let g_point = jubjub::AffinePoint::from_raw_unchecked(
502 jubjub::Fq::from_raw([
503 0xe4b3_d35d_f1a7_adfe,
504 0xcaf5_5d1b_29bf_81af,
505 0x8b0f_03dd_d60a_8187,
506 0x62ed_cbb8_bf37_87c8,
507 ]),
508 jubjub::Fq::from_raw([
509 0x0000_0000_0000_000b,
510 0x0000_0000_0000_0000,
511 0x0000_0000_0000_0000,
512 0x0000_0000_0000_0000,
513 ]),
514 );
515
516 let g = ValueCommitment(g_point);
517 let other_g = ValueCommitment(g_point);
518
519 let sum: ValueCommitment = vec![g, other_g].into_iter().sum();
520
521 let doubled_g = ValueCommitment(g_point.to_extended().double().into());
522
523 assert_eq!(sum, doubled_g);
524 }
525}