1use group::{GroupEncoding, ff::Field};
2use rand_core::{CryptoRng, Rng};
3use zcash_spec::PrfExpand;
4
5use crate::{
6 keys::{ExpandedSpendingKey, FullViewingKey},
7 zip32::ExtendedSpendingKey,
8};
9
10use super::{
11 Diversifier, Nullifier, NullifierDerivingKey, PaymentAddress, keys::EphemeralSecretKey,
12 value::NoteValue,
13};
14
15mod commitment;
16pub use self::commitment::{ExtractedNoteCommitment, NoteCommitment};
17
18pub(super) mod nullifier;
19
20#[derive(Copy, Clone, Debug, PartialEq, Eq)]
26pub enum Rseed {
27 BeforeZip212(jubjub::Fr),
28 AfterZip212([u8; 32]),
29}
30
31impl Rseed {
32 pub(crate) fn rcm(&self) -> commitment::NoteCommitTrapdoor {
36 commitment::NoteCommitTrapdoor(match self {
37 Rseed::BeforeZip212(rcm) => *rcm,
38 Rseed::AfterZip212(rseed) => {
39 jubjub::Fr::from_bytes_wide(&PrfExpand::SAPLING_RCM.with(rseed))
40 }
41 })
42 }
43}
44
45#[derive(Clone, Debug)]
47pub struct Note {
48 recipient: PaymentAddress,
50 value: NoteValue,
52 rseed: Rseed,
54}
55
56impl PartialEq for Note {
57 fn eq(&self, other: &Self) -> bool {
58 self.cmu().eq(&other.cmu())
60 }
61}
62
63impl Eq for Note {}
64
65impl Note {
66 pub fn from_parts(recipient: PaymentAddress, value: NoteValue, rseed: Rseed) -> Self {
79 Note {
80 recipient,
81 value,
82 rseed,
83 }
84 }
85
86 pub fn recipient(&self) -> PaymentAddress {
88 self.recipient
89 }
90
91 pub fn value(&self) -> NoteValue {
93 self.value
94 }
95
96 pub fn rseed(&self) -> &Rseed {
98 &self.rseed
99 }
100
101 fn cm_full_point(&self) -> NoteCommitment {
103 NoteCommitment::derive(
104 self.recipient.g_d().to_bytes(),
105 self.recipient.pk_d().to_bytes(),
106 self.value,
107 self.rseed.rcm(),
108 )
109 }
110
111 pub fn nf(&self, nk: &NullifierDerivingKey, position: u64) -> Nullifier {
114 Nullifier::derive(nk, self.cm_full_point(), position)
115 }
116
117 pub fn cmu(&self) -> ExtractedNoteCommitment {
119 self.cm_full_point().into()
120 }
121
122 pub fn rcm(&self) -> jubjub::Fr {
126 self.rseed.rcm().0
127 }
128
129 pub fn generate_or_derive_esk<R: Rng + CryptoRng>(&self, rng: &mut R) -> EphemeralSecretKey {
132 self.generate_or_derive_esk_internal(rng)
133 }
134
135 pub(crate) fn generate_or_derive_esk_internal<R: Rng>(
136 &self,
137 rng: &mut R,
138 ) -> EphemeralSecretKey {
139 match self.derive_esk() {
140 None => EphemeralSecretKey(jubjub::Fr::random(rng)),
141 Some(esk) => esk,
142 }
143 }
144
145 pub(crate) fn derive_esk(&self) -> Option<EphemeralSecretKey> {
147 match self.rseed {
148 Rseed::BeforeZip212(_) => None,
149 Rseed::AfterZip212(rseed) => Some(EphemeralSecretKey(jubjub::Fr::from_bytes_wide(
150 &PrfExpand::SAPLING_ESK.with(&rseed),
151 ))),
152 }
153 }
154
155 pub(crate) fn dummy<R: Rng>(mut rng: R) -> (ExpandedSpendingKey, FullViewingKey, Self) {
161 let mut sk_bytes = [0; 32];
162 rng.fill_bytes(&mut sk_bytes);
163
164 let extsk = ExtendedSpendingKey::master(&sk_bytes[..]);
165 let fvk = extsk.to_diversifiable_full_viewing_key().fvk().clone();
166 let ivk = fvk.vk.ivk();
169 let recipient = loop {
170 let mut diversifier = Diversifier(Default::default());
171 rng.fill_bytes(&mut diversifier.0);
172 if let Some(recipient) = ivk.to_payment_address(diversifier) {
173 break recipient;
174 }
175 };
176
177 let mut rseed_bytes = [0; 32];
178 rng.fill_bytes(&mut rseed_bytes);
179 let rseed = Rseed::AfterZip212(rseed_bytes);
180
181 let note = Note::from_parts(recipient, NoteValue::ZERO, rseed);
182
183 (extsk.expsk, fvk, note)
184 }
185}
186
187#[cfg(any(test, feature = "test-dependencies"))]
188pub(super) mod testing {
189 use proptest::{collection::vec, prelude::*};
190
191 use super::{
192 super::{testing::arb_payment_address, value::NoteValue},
193 ExtractedNoteCommitment, Note, Rseed,
194 };
195
196 #[cfg(test)]
197 pub(super) fn arb_dummy_note() -> impl Strategy<
198 Value = (
199 crate::keys::ExpandedSpendingKey,
200 crate::keys::FullViewingKey,
201 Note,
202 ),
203 > {
204 use rand::{SeedableRng, rngs::StdRng};
205
206 prop::array::uniform32(any::<u8>()).prop_map(|seed| Note::dummy(StdRng::from_seed(seed)))
207 }
208
209 prop_compose! {
210 pub fn arb_note(value: NoteValue)(
211 recipient in arb_payment_address(),
212 rseed in prop::array::uniform32(prop::num::u8::ANY).prop_map(Rseed::AfterZip212)
213 ) -> Note {
214 Note {
215 recipient,
216 value,
217 rseed
218 }
219 }
220 }
221
222 prop_compose! {
223 pub(crate) fn arb_cmu()(
224 cmu in vec(any::<u8>(), 64)
225 .prop_map(|v| <[u8;64]>::try_from(v.as_slice()).unwrap())
226 .prop_map(|v| bls12_381::Scalar::from_bytes_wide(&v)),
227 ) -> ExtractedNoteCommitment {
228 ExtractedNoteCommitment(cmu)
229 }
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236 use proptest::prelude::*;
237
238 proptest! {
239 #[test]
240 fn dummy_note_preserves_spend_inputs((expsk, fvk, note) in testing::arb_dummy_note()) {
241 prop_assert_eq!(fvk.to_bytes(), FullViewingKey::from_expanded_spending_key(&expsk).to_bytes());
242 prop_assert_eq!(note.value(), NoteValue::ZERO);
243 prop_assert!(matches!(note.rseed(), Rseed::AfterZip212(_)));
244 prop_assert_eq!(
245 Some(note.recipient()),
246 fvk.vk.ivk().to_payment_address(*note.recipient().diversifier())
247 );
248 }
249 }
250}