1use group::{GroupEncoding, ff::Field};
2use rand_core::{CryptoRng, Rng};
3use zcash_spec::PrfExpand;
4
5use crate::keys::{ExpandedSpendingKey, FullViewingKey};
6
7use super::{
8 Diversifier, Nullifier, NullifierDerivingKey, PaymentAddress, keys::EphemeralSecretKey,
9 value::NoteValue,
10};
11
12mod commitment;
13pub use self::commitment::{ExtractedNoteCommitment, NoteCommitment};
14
15pub(super) mod nullifier;
16
17#[derive(Copy, Clone, Debug, PartialEq, Eq)]
23pub enum Rseed {
24 BeforeZip212(jubjub::Fr),
25 AfterZip212([u8; 32]),
26}
27
28impl Rseed {
29 pub(crate) fn rcm(&self) -> commitment::NoteCommitTrapdoor {
33 commitment::NoteCommitTrapdoor(match self {
34 Rseed::BeforeZip212(rcm) => *rcm,
35 Rseed::AfterZip212(rseed) => {
36 jubjub::Fr::from_bytes_wide(&PrfExpand::SAPLING_RCM.with(rseed))
37 }
38 })
39 }
40}
41
42#[derive(Clone, Debug)]
44pub struct Note {
45 recipient: PaymentAddress,
47 value: NoteValue,
49 rseed: Rseed,
51}
52
53impl PartialEq for Note {
54 fn eq(&self, other: &Self) -> bool {
55 self.cmu().eq(&other.cmu())
57 }
58}
59
60impl Eq for Note {}
61
62impl Note {
63 pub fn from_parts(recipient: PaymentAddress, value: NoteValue, rseed: Rseed) -> Self {
76 Note {
77 recipient,
78 value,
79 rseed,
80 }
81 }
82
83 pub fn recipient(&self) -> PaymentAddress {
85 self.recipient
86 }
87
88 pub fn value(&self) -> NoteValue {
90 self.value
91 }
92
93 pub fn rseed(&self) -> &Rseed {
95 &self.rseed
96 }
97
98 fn cm_full_point(&self) -> NoteCommitment {
100 NoteCommitment::derive(
101 self.recipient.g_d().to_bytes(),
102 self.recipient.pk_d().to_bytes(),
103 self.value,
104 self.rseed.rcm(),
105 )
106 }
107
108 pub fn nf(&self, nk: &NullifierDerivingKey, position: u64) -> Nullifier {
111 Nullifier::derive(nk, self.cm_full_point(), position)
112 }
113
114 pub fn cmu(&self) -> ExtractedNoteCommitment {
116 self.cm_full_point().into()
117 }
118
119 pub fn rcm(&self) -> jubjub::Fr {
123 self.rseed.rcm().0
124 }
125
126 pub fn generate_or_derive_esk<R: Rng + CryptoRng>(&self, rng: &mut R) -> EphemeralSecretKey {
129 self.generate_or_derive_esk_internal(rng)
130 }
131
132 pub(crate) fn generate_or_derive_esk_internal<R: Rng>(
133 &self,
134 rng: &mut R,
135 ) -> EphemeralSecretKey {
136 match self.derive_esk() {
137 None => EphemeralSecretKey(jubjub::Fr::random(rng)),
138 Some(esk) => esk,
139 }
140 }
141
142 pub(crate) fn derive_esk(&self) -> Option<EphemeralSecretKey> {
144 match self.rseed {
145 Rseed::BeforeZip212(_) => None,
146 Rseed::AfterZip212(rseed) => Some(EphemeralSecretKey(jubjub::Fr::from_bytes_wide(
147 &PrfExpand::SAPLING_ESK.with(&rseed),
148 ))),
149 }
150 }
151
152 pub(crate) fn dummy<R: Rng>(mut rng: R) -> (ExpandedSpendingKey, FullViewingKey, Self) {
158 let mut sk_bytes = [0; 32];
159 rng.fill_bytes(&mut sk_bytes);
160
161 let expanded = crate::keys::master_key_expansion(&sk_bytes);
162 let expsk = ExpandedSpendingKey::from_spending_key(&expanded.as_bytes()[..32]);
163 let fvk = FullViewingKey::from_expanded_spending_key(&expsk);
164 let ivk = fvk.vk.ivk();
167 let recipient = loop {
168 let mut diversifier = Diversifier(Default::default());
169 rng.fill_bytes(&mut diversifier.0);
170 if let Some(recipient) = ivk.to_payment_address(diversifier) {
171 break recipient;
172 }
173 };
174
175 let mut rseed_bytes = [0; 32];
176 rng.fill_bytes(&mut rseed_bytes);
177 let rseed = Rseed::AfterZip212(rseed_bytes);
178
179 let note = Note::from_parts(recipient, NoteValue::ZERO, rseed);
180
181 (expsk, fvk, note)
182 }
183}
184
185#[cfg(any(test, feature = "test-dependencies"))]
186pub(super) mod testing {
187 use proptest::{collection::vec, prelude::*};
188
189 use super::{
190 super::{testing::arb_payment_address, value::NoteValue},
191 ExtractedNoteCommitment, Note, Rseed,
192 };
193
194 #[cfg(test)]
195 pub(super) fn arb_dummy_note() -> impl Strategy<
196 Value = (
197 crate::keys::ExpandedSpendingKey,
198 crate::keys::FullViewingKey,
199 Note,
200 ),
201 > {
202 use rand::{SeedableRng, rngs::StdRng};
203
204 prop::array::uniform32(any::<u8>()).prop_map(|seed| Note::dummy(StdRng::from_seed(seed)))
205 }
206
207 prop_compose! {
208 pub fn arb_note(value: NoteValue)(
209 recipient in arb_payment_address(),
210 rseed in prop::array::uniform32(prop::num::u8::ANY).prop_map(Rseed::AfterZip212)
211 ) -> Note {
212 Note {
213 recipient,
214 value,
215 rseed
216 }
217 }
218 }
219
220 prop_compose! {
221 pub(crate) fn arb_cmu()(
222 cmu in vec(any::<u8>(), 64)
223 .prop_map(|v| <[u8;64]>::try_from(v.as_slice()).unwrap())
224 .prop_map(|v| bls12_381::Scalar::from_bytes_wide(&v)),
225 ) -> ExtractedNoteCommitment {
226 ExtractedNoteCommitment(cmu)
227 }
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234 use proptest::prelude::*;
235 use rand_core::Rng;
236
237 struct CountingRng {
238 inner: rand::rngs::StdRng,
239 consumed: usize,
240 }
241
242 impl CountingRng {
243 fn new(seed: [u8; 32]) -> Self {
244 use rand::SeedableRng;
245 Self {
246 inner: rand::rngs::StdRng::from_seed(seed),
247 consumed: 0,
248 }
249 }
250 }
251
252 impl rand_core::TryRng for CountingRng {
253 type Error = core::convert::Infallible;
254
255 fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
256 self.consumed += 4;
257 Ok(self.inner.next_u32())
258 }
259
260 fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
261 self.consumed += 8;
262 Ok(self.inner.next_u64())
263 }
264
265 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
266 self.consumed += dest.len();
267 self.inner.fill_bytes(dest);
268 Ok(())
269 }
270 }
271
272 #[test]
273 fn dummy_note_derivation_and_randomness_are_unchanged() {
274 for (seed, expected_digest, expected_consumed, expected_next) in [
276 (
277 0,
278 [
279 41, 218, 228, 136, 208, 212, 88, 195, 115, 92, 245, 135, 95, 110, 148, 169,
280 158, 88, 190, 39, 10, 7, 195, 164, 90, 163, 1, 153, 243, 52, 195, 120,
281 ],
282 75,
283 13252731160361427084,
284 ),
285 (
286 1,
287 [
288 80, 222, 41, 137, 135, 161, 238, 87, 159, 97, 83, 44, 55, 26, 90, 235, 73, 242,
289 19, 253, 241, 193, 97, 107, 128, 181, 30, 4, 36, 54, 219, 95,
290 ],
291 97,
292 5790603339235468134,
293 ),
294 (
295 90,
296 [
297 37, 56, 194, 138, 186, 67, 253, 3, 44, 106, 220, 35, 50, 167, 23, 229, 103,
298 254, 35, 78, 246, 202, 154, 49, 81, 75, 110, 47, 145, 89, 215, 103,
299 ],
300 86,
301 1661224483263062408,
302 ),
303 (
304 255,
305 [
306 129, 220, 116, 71, 84, 161, 163, 78, 90, 212, 167, 228, 217, 73, 167, 88, 28,
307 140, 94, 132, 29, 33, 79, 149, 187, 226, 153, 216, 31, 70, 156, 85,
308 ],
309 97,
310 4758269131877494279,
311 ),
312 ] {
313 let mut rng = CountingRng::new([seed; 32]);
314 let (expsk, fvk, note) = Note::dummy(&mut rng);
315 let mut hash = blake2b_simd::Params::new().hash_length(32).to_state();
316 hash.update(&expsk.to_bytes());
317 hash.update(&fvk.to_bytes());
318 hash.update(¬e.recipient().to_bytes());
319 hash.update(¬e.cmu().to_bytes());
320 match note.rseed() {
321 Rseed::AfterZip212(seed) => {
322 hash.update(seed);
323 }
324 _ => panic!("dummy notes use ZIP 212 randomness"),
325 }
326 let consumed = rng.consumed;
327 let next = rng.next_u64();
328 assert_eq!(hash.finalize().as_bytes(), expected_digest);
329 assert_eq!(consumed, expected_consumed);
330 assert_eq!(next, expected_next);
331 }
332 }
333
334 proptest! {
335 #[test]
336 fn dummy_note_preserves_spend_inputs((expsk, fvk, note) in testing::arb_dummy_note()) {
337 prop_assert_eq!(fvk.to_bytes(), FullViewingKey::from_expanded_spending_key(&expsk).to_bytes());
338 prop_assert_eq!(note.value(), NoteValue::ZERO);
339 prop_assert!(matches!(note.rseed(), Rseed::AfterZip212(_)));
340 prop_assert_eq!(
341 Some(note.recipient()),
342 fvk.vk.ivk().to_payment_address(*note.recipient().diversifier())
343 );
344 }
345 }
346}