1use std::{fmt, io};
4
5use bitvec::prelude::*;
6use hex::{FromHex, FromHexError, ToHex};
7use jubjub::ExtendedPoint;
8use lazy_static::lazy_static;
9use rand_core::{CryptoRng, RngCore};
10
11use crate::{
12 amount::{Amount, NonNegative},
13 error::{NoteCommitmentError, RandError},
14 serialization::{
15 serde_helpers, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashSerialize,
16 },
17};
18
19use super::keys::{find_group_hash, Diversifier, TransmissionKey};
20
21pub mod pedersen_hashes;
22
23#[cfg(test)]
24mod test_vectors;
25
26use pedersen_hashes::*;
27
28pub fn generate_trapdoor<T>(csprng: &mut T) -> Result<jubjub::Fr, RandError>
37where
38 T: RngCore + CryptoRng,
39{
40 let mut bytes = [0u8; 64];
41 csprng
42 .try_fill_bytes(&mut bytes)
43 .map_err(|_| RandError::FillBytes)?;
44 Ok(jubjub::Fr::from_bytes_wide(&bytes))
46}
47
48#[derive(Copy, Clone, Debug, PartialEq, Eq)]
50pub struct CommitmentRandomness(jubjub::Fr);
51
52#[derive(Clone, Copy, Deserialize, PartialEq, Eq, Serialize)]
54pub struct NoteCommitment(#[serde(with = "serde_helpers::AffinePoint")] pub jubjub::AffinePoint);
55
56impl fmt::Debug for NoteCommitment {
57 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
58 f.debug_struct("NoteCommitment")
59 .field("u", &hex::encode(self.0.get_u().to_bytes()))
60 .field("v", &hex::encode(self.0.get_v().to_bytes()))
61 .finish()
62 }
63}
64
65impl From<jubjub::ExtendedPoint> for NoteCommitment {
66 fn from(extended_point: jubjub::ExtendedPoint) -> Self {
67 Self(jubjub::AffinePoint::from(extended_point))
68 }
69}
70
71impl From<NoteCommitment> for [u8; 32] {
72 fn from(cm: NoteCommitment) -> [u8; 32] {
73 cm.0.to_bytes()
74 }
75}
76
77impl TryFrom<[u8; 32]> for NoteCommitment {
78 type Error = &'static str;
79
80 fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
81 let possible_point = jubjub::AffinePoint::from_bytes(bytes);
82
83 if possible_point.is_some().into() {
84 Ok(Self(possible_point.unwrap()))
85 } else {
86 Err("Invalid jubjub::AffinePoint value")
87 }
88 }
89}
90
91impl NoteCommitment {
92 #[allow(non_snake_case)]
103 pub fn new<T>(
104 csprng: &mut T,
105 diversifier: Diversifier,
106 transmission_key: TransmissionKey,
107 value: Amount<NonNegative>,
108 ) -> Result<(CommitmentRandomness, Self), NoteCommitmentError>
109 where
110 T: RngCore + CryptoRng,
111 {
112 let mut s: BitVec<u8, Lsb0> = BitVec::new();
114
115 s.append(&mut bitvec![1; 6]);
117
118 let g_d_bytes = jubjub::AffinePoint::try_from(diversifier)
125 .map_err(|_| NoteCommitmentError::InvalidDiversifier)?
126 .to_bytes();
127
128 let pk_d_bytes = <[u8; 32]>::from(transmission_key);
129 let v_bytes = value.to_bytes();
130
131 s.extend(g_d_bytes);
132 s.extend(pk_d_bytes);
133 s.extend(v_bytes);
134
135 let rcm = CommitmentRandomness(generate_trapdoor(csprng)?);
136
137 Ok((
138 rcm,
139 NoteCommitment::from(windowed_pedersen_commitment(rcm.0, &s)),
140 ))
141 }
142
143 pub fn extract_u(&self) -> jubjub::Fq {
147 self.0.get_u()
148 }
149}
150
151#[derive(Clone, Copy, Deserialize, PartialEq, Eq, Serialize)]
159#[cfg_attr(any(test, feature = "proptest-impl"), derive(Default))]
160pub struct ValueCommitment(#[serde(with = "serde_helpers::AffinePoint")] jubjub::AffinePoint);
161
162impl<'a> std::ops::Add<&'a ValueCommitment> for ValueCommitment {
163 type Output = Self;
164
165 fn add(self, rhs: &'a ValueCommitment) -> Self::Output {
166 self + *rhs
167 }
168}
169
170impl std::ops::Add<ValueCommitment> for ValueCommitment {
171 type Output = Self;
172
173 fn add(self, rhs: ValueCommitment) -> Self::Output {
174 let value = self.0.to_extended() + rhs.0.to_extended();
175 ValueCommitment(value.into())
176 }
177}
178
179impl std::ops::AddAssign<ValueCommitment> for ValueCommitment {
180 fn add_assign(&mut self, rhs: ValueCommitment) {
181 *self = *self + rhs
182 }
183}
184
185impl fmt::Debug for ValueCommitment {
186 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
187 f.debug_struct("ValueCommitment")
188 .field("u", &hex::encode(self.0.get_u().to_bytes()))
189 .field("v", &hex::encode(self.0.get_v().to_bytes()))
190 .finish()
191 }
192}
193
194impl From<jubjub::ExtendedPoint> for ValueCommitment {
195 fn from(extended_point: jubjub::ExtendedPoint) -> Self {
197 Self(jubjub::AffinePoint::from(extended_point))
198 }
199}
200
201impl From<ValueCommitment> for [u8; 32] {
206 fn from(cm: ValueCommitment) -> [u8; 32] {
207 cm.0.to_bytes()
208 }
209}
210
211impl<'a> std::ops::Sub<&'a ValueCommitment> for ValueCommitment {
212 type Output = Self;
213
214 fn sub(self, rhs: &'a ValueCommitment) -> Self::Output {
215 self - *rhs
216 }
217}
218
219impl std::ops::Sub<ValueCommitment> for ValueCommitment {
220 type Output = Self;
221
222 fn sub(self, rhs: ValueCommitment) -> Self::Output {
223 ValueCommitment((self.0.to_extended() - rhs.0.to_extended()).into())
224 }
225}
226
227impl std::ops::SubAssign<ValueCommitment> for ValueCommitment {
228 fn sub_assign(&mut self, rhs: ValueCommitment) {
229 *self = *self - rhs;
230 }
231}
232
233impl std::iter::Sum for ValueCommitment {
234 fn sum<I>(iter: I) -> Self
235 where
236 I: Iterator<Item = Self>,
237 {
238 iter.fold(
239 ValueCommitment(jubjub::AffinePoint::identity()),
240 std::ops::Add::add,
241 )
242 }
243}
244
245impl TryFrom<[u8; 32]> for ValueCommitment {
250 type Error = &'static str;
251
252 fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
253 let possible_point = jubjub::AffinePoint::from_bytes(bytes);
254
255 if possible_point.is_some().into() {
256 let point = possible_point.unwrap();
257 Ok(ExtendedPoint::from(point).into())
258 } else {
259 Err("Invalid jubjub::AffinePoint value")
260 }
261 }
262}
263
264impl ValueCommitment {
265 pub fn randomized<T>(csprng: &mut T, value: Amount) -> Result<Self, RandError>
269 where
270 T: RngCore + CryptoRng,
271 {
272 let rcv = generate_trapdoor(csprng)?;
273
274 Ok(Self::new(rcv, value))
275 }
276
277 #[allow(non_snake_case)]
281 pub fn new(rcv: jubjub::Fr, value: Amount) -> Self {
282 let v = jubjub::Fr::from(value);
283 Self::from(*V * v + *R * rcv)
284 }
285}
286
287lazy_static! {
288 static ref V: ExtendedPoint = find_group_hash(*b"Zcash_cv", b"v");
289 static ref R: ExtendedPoint = find_group_hash(*b"Zcash_cv", b"r");
290}
291
292#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq, Serialize)]
304#[cfg_attr(any(test, feature = "proptest-impl"), derive(Default))]
305pub struct NotSmallOrderValueCommitment(ValueCommitment);
306
307impl NotSmallOrderValueCommitment {
308 pub fn bytes_in_display_order(&self) -> [u8; 32] {
313 let mut reversed_bytes = self.0 .0.to_bytes();
314 reversed_bytes.reverse();
315 reversed_bytes
316 }
317}
318
319impl From<&NotSmallOrderValueCommitment> for [u8; 32] {
320 fn from(cv: &NotSmallOrderValueCommitment) -> [u8; 32] {
321 cv.0.into()
322 }
323}
324
325impl TryFrom<ValueCommitment> for NotSmallOrderValueCommitment {
326 type Error = &'static str;
327
328 fn try_from(value_commitment: ValueCommitment) -> Result<Self, Self::Error> {
343 if value_commitment.0.is_small_order().into() {
344 Err("jubjub::AffinePoint value for Sapling ValueCommitment is of small order")
345 } else {
346 Ok(Self(value_commitment))
347 }
348 }
349}
350
351impl TryFrom<jubjub::ExtendedPoint> for NotSmallOrderValueCommitment {
352 type Error = &'static str;
353
354 fn try_from(extended_point: jubjub::ExtendedPoint) -> Result<Self, Self::Error> {
356 ValueCommitment::from(extended_point).try_into()
357 }
358}
359
360impl From<NotSmallOrderValueCommitment> for ValueCommitment {
361 fn from(cv: NotSmallOrderValueCommitment) -> Self {
362 cv.0
363 }
364}
365
366impl From<NotSmallOrderValueCommitment> for jubjub::AffinePoint {
367 fn from(cv: NotSmallOrderValueCommitment) -> Self {
368 cv.0 .0
369 }
370}
371
372impl ZcashSerialize for NotSmallOrderValueCommitment {
373 fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
374 writer.write_all(&<[u8; 32]>::from(self.0)[..])?;
375 Ok(())
376 }
377}
378
379impl ZcashDeserialize for NotSmallOrderValueCommitment {
380 fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
381 let vc = ValueCommitment::try_from(reader.read_32_bytes()?)
382 .map_err(SerializationError::Parse)?;
383 vc.try_into().map_err(SerializationError::Parse)
384 }
385}
386
387impl ToHex for &NotSmallOrderValueCommitment {
388 fn encode_hex<T: FromIterator<char>>(&self) -> T {
389 self.bytes_in_display_order().encode_hex()
390 }
391
392 fn encode_hex_upper<T: FromIterator<char>>(&self) -> T {
393 self.bytes_in_display_order().encode_hex_upper()
394 }
395}
396
397impl FromHex for NotSmallOrderValueCommitment {
398 type Error = FromHexError;
399
400 fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
401 let mut bytes = <[u8; 32]>::from_hex(hex)?;
403 bytes.reverse();
405
406 Self::zcash_deserialize(io::Cursor::new(&bytes))
407 .map_err(|_| FromHexError::InvalidStringLength)
408 }
409}
410
411#[cfg(test)]
412mod tests {
413
414 use std::ops::Neg;
415
416 use super::*;
417
418 #[test]
419 fn pedersen_hash_to_point_test_vectors() {
420 let _init_guard = zebra_test::init();
421
422 const D: [u8; 8] = *b"Zcash_PH";
423
424 for test_vector in test_vectors::TEST_VECTORS.iter() {
425 let result = jubjub::AffinePoint::from(pedersen_hash_to_point(
426 D,
427 &test_vector.input_bits.clone(),
428 ));
429
430 assert_eq!(result, test_vector.output_point);
431 }
432 }
433
434 #[test]
435 fn add() {
436 let _init_guard = zebra_test::init();
437
438 let identity = ValueCommitment(jubjub::AffinePoint::identity());
439
440 let g = ValueCommitment(jubjub::AffinePoint::from_raw_unchecked(
441 jubjub::Fq::from_raw([
442 0xe4b3_d35d_f1a7_adfe,
443 0xcaf5_5d1b_29bf_81af,
444 0x8b0f_03dd_d60a_8187,
445 0x62ed_cbb8_bf37_87c8,
446 ]),
447 jubjub::Fq::from_raw([
448 0x0000_0000_0000_000b,
449 0x0000_0000_0000_0000,
450 0x0000_0000_0000_0000,
451 0x0000_0000_0000_0000,
452 ]),
453 ));
454
455 assert_eq!(identity + g, g);
456 }
457
458 #[test]
459 fn add_assign() {
460 let _init_guard = zebra_test::init();
461
462 let mut identity = ValueCommitment(jubjub::AffinePoint::identity());
463
464 let g = ValueCommitment(jubjub::AffinePoint::from_raw_unchecked(
465 jubjub::Fq::from_raw([
466 0xe4b3_d35d_f1a7_adfe,
467 0xcaf5_5d1b_29bf_81af,
468 0x8b0f_03dd_d60a_8187,
469 0x62ed_cbb8_bf37_87c8,
470 ]),
471 jubjub::Fq::from_raw([
472 0x0000_0000_0000_000b,
473 0x0000_0000_0000_0000,
474 0x0000_0000_0000_0000,
475 0x0000_0000_0000_0000,
476 ]),
477 ));
478
479 identity += g;
480 let new_g = identity;
481
482 assert_eq!(new_g, g);
483 }
484
485 #[test]
486 fn sub() {
487 let _init_guard = zebra_test::init();
488
489 let g_point = jubjub::AffinePoint::from_raw_unchecked(
490 jubjub::Fq::from_raw([
491 0xe4b3_d35d_f1a7_adfe,
492 0xcaf5_5d1b_29bf_81af,
493 0x8b0f_03dd_d60a_8187,
494 0x62ed_cbb8_bf37_87c8,
495 ]),
496 jubjub::Fq::from_raw([
497 0x0000_0000_0000_000b,
498 0x0000_0000_0000_0000,
499 0x0000_0000_0000_0000,
500 0x0000_0000_0000_0000,
501 ]),
502 );
503
504 let identity = ValueCommitment(jubjub::AffinePoint::identity());
505
506 let g = ValueCommitment(g_point);
507
508 assert_eq!(identity - g, ValueCommitment(g_point.neg()));
509 }
510
511 #[test]
512 fn sub_assign() {
513 let _init_guard = zebra_test::init();
514
515 let g_point = jubjub::AffinePoint::from_raw_unchecked(
516 jubjub::Fq::from_raw([
517 0xe4b3_d35d_f1a7_adfe,
518 0xcaf5_5d1b_29bf_81af,
519 0x8b0f_03dd_d60a_8187,
520 0x62ed_cbb8_bf37_87c8,
521 ]),
522 jubjub::Fq::from_raw([
523 0x0000_0000_0000_000b,
524 0x0000_0000_0000_0000,
525 0x0000_0000_0000_0000,
526 0x0000_0000_0000_0000,
527 ]),
528 );
529
530 let mut identity = ValueCommitment(jubjub::AffinePoint::identity());
531
532 let g = ValueCommitment(g_point);
533
534 identity -= g;
535 let new_g = identity;
536
537 assert_eq!(new_g, ValueCommitment(g_point.neg()));
538 }
539
540 #[test]
541 fn sum() {
542 let _init_guard = zebra_test::init();
543
544 let g_point = jubjub::AffinePoint::from_raw_unchecked(
545 jubjub::Fq::from_raw([
546 0xe4b3_d35d_f1a7_adfe,
547 0xcaf5_5d1b_29bf_81af,
548 0x8b0f_03dd_d60a_8187,
549 0x62ed_cbb8_bf37_87c8,
550 ]),
551 jubjub::Fq::from_raw([
552 0x0000_0000_0000_000b,
553 0x0000_0000_0000_0000,
554 0x0000_0000_0000_0000,
555 0x0000_0000_0000_0000,
556 ]),
557 );
558
559 let g = ValueCommitment(g_point);
560 let other_g = ValueCommitment(g_point);
561
562 let sum: ValueCommitment = vec![g, other_g].into_iter().sum();
563
564 let doubled_g = ValueCommitment(g_point.to_extended().double().into());
565
566 assert_eq!(sum, doubled_g);
567 }
568
569 #[test]
570 fn value_commitment_hex_roundtrip() {
571 use hex::{FromHex, ToHex};
572
573 let _init_guard = zebra_test::init();
574
575 let g_point = jubjub::AffinePoint::from_raw_unchecked(
576 jubjub::Fq::from_raw([
577 0xe4b3_d35d_f1a7_adfe,
578 0xcaf5_5d1b_29bf_81af,
579 0x8b0f_03dd_d60a_8187,
580 0x62ed_cbb8_bf37_87c8,
581 ]),
582 jubjub::Fq::from_raw([
583 0x0000_0000_0000_000b,
584 0x0000_0000_0000_0000,
585 0x0000_0000_0000_0000,
586 0x0000_0000_0000_0000,
587 ]),
588 );
589
590 let value_commitment = ValueCommitment(g_point);
591 let original = NotSmallOrderValueCommitment::try_from(value_commitment)
592 .expect("constructed point must not be small order");
593
594 let hex_str = (&original).encode_hex::<String>();
595
596 let decoded = NotSmallOrderValueCommitment::from_hex(&hex_str)
597 .expect("hex string should decode successfully");
598
599 assert_eq!(original, decoded);
600 }
601}