sapling_crypto/
note.rs

1use group::{ff::Field, GroupEncoding};
2use rand_core::{CryptoRng, RngCore};
3use zcash_spec::PrfExpand;
4
5use crate::{
6    keys::{ExpandedSpendingKey, FullViewingKey},
7    zip32::ExtendedSpendingKey,
8};
9
10use super::{
11    keys::EphemeralSecretKey, value::NoteValue, Nullifier, NullifierDerivingKey, PaymentAddress,
12};
13
14mod commitment;
15pub use self::commitment::{ExtractedNoteCommitment, NoteCommitment};
16
17pub(super) mod nullifier;
18
19/// Enum for note randomness before and after [ZIP 212](https://zips.z.cash/zip-0212).
20///
21/// Before ZIP 212, the note commitment trapdoor `rcm` must be a scalar value.
22/// After ZIP 212, the note randomness `rseed` is a 32-byte sequence, used to derive
23/// both the note commitment trapdoor `rcm` and the ephemeral private key `esk`.
24#[derive(Copy, Clone, Debug, PartialEq, Eq)]
25pub enum Rseed {
26    BeforeZip212(jubjub::Fr),
27    AfterZip212([u8; 32]),
28}
29
30impl Rseed {
31    /// Defined in [Zcash Protocol Spec § 4.7.2: Sending Notes (Sapling)][saplingsend].
32    ///
33    /// [saplingsend]: https://zips.z.cash/protocol/protocol.pdf#saplingsend
34    pub(crate) fn rcm(&self) -> commitment::NoteCommitTrapdoor {
35        commitment::NoteCommitTrapdoor(match self {
36            Rseed::BeforeZip212(rcm) => *rcm,
37            Rseed::AfterZip212(rseed) => {
38                jubjub::Fr::from_bytes_wide(&PrfExpand::SAPLING_RCM.with(rseed))
39            }
40        })
41    }
42}
43
44/// A discrete amount of funds received by an address.
45#[derive(Clone, Debug)]
46pub struct Note {
47    /// The recipient of the funds.
48    recipient: PaymentAddress,
49    /// The value of this note.
50    value: NoteValue,
51    /// The seed randomness for various note components.
52    rseed: Rseed,
53}
54
55impl PartialEq for Note {
56    fn eq(&self, other: &Self) -> bool {
57        // Notes are canonically defined by their commitments.
58        self.cmu().eq(&other.cmu())
59    }
60}
61
62impl Eq for Note {}
63
64impl Note {
65    /// Creates a note from its component parts.
66    ///
67    /// # Caveats
68    ///
69    /// This low-level constructor enforces that the provided arguments produce an
70    /// internally valid `Note`. However, it allows notes to be constructed in a way that
71    /// violates required security checks for note decryption, as specified in
72    /// [Section 4.19] of the Zcash Protocol Specification. Users of this constructor
73    /// should only call it with note components that have been fully validated by
74    /// decrypting a received note according to [Section 4.19].
75    ///
76    /// [Section 4.19]: https://zips.z.cash/protocol/protocol.pdf#saplingandorchardinband
77    pub fn from_parts(recipient: PaymentAddress, value: NoteValue, rseed: Rseed) -> Self {
78        Note {
79            recipient,
80            value,
81            rseed,
82        }
83    }
84
85    /// Returns the recipient of this note.
86    pub fn recipient(&self) -> PaymentAddress {
87        self.recipient
88    }
89
90    /// Returns the value of this note.
91    pub fn value(&self) -> NoteValue {
92        self.value
93    }
94
95    /// Returns the rseed value of this note.
96    pub fn rseed(&self) -> &Rseed {
97        &self.rseed
98    }
99
100    /// Computes the note commitment, returning the full point.
101    fn cm_full_point(&self) -> NoteCommitment {
102        NoteCommitment::derive(
103            self.recipient.g_d().to_bytes(),
104            self.recipient.pk_d().to_bytes(),
105            self.value,
106            self.rseed.rcm(),
107        )
108    }
109
110    /// Computes the nullifier given the nullifier deriving key and
111    /// note position
112    pub fn nf(&self, nk: &NullifierDerivingKey, position: u64) -> Nullifier {
113        Nullifier::derive(nk, self.cm_full_point(), position)
114    }
115
116    /// Computes the note commitment
117    pub fn cmu(&self) -> ExtractedNoteCommitment {
118        self.cm_full_point().into()
119    }
120
121    /// Defined in [Zcash Protocol Spec § 4.7.2: Sending Notes (Sapling)][saplingsend].
122    ///
123    /// [saplingsend]: https://zips.z.cash/protocol/protocol.pdf#saplingsend
124    pub fn rcm(&self) -> jubjub::Fr {
125        self.rseed.rcm().0
126    }
127
128    /// Derives `esk` from the internal `Rseed` value, or generates a random value if this
129    /// note was created with a v1 (i.e. pre-ZIP 212) note plaintext.
130    pub fn generate_or_derive_esk<R: RngCore + CryptoRng>(
131        &self,
132        rng: &mut R,
133    ) -> EphemeralSecretKey {
134        self.generate_or_derive_esk_internal(rng)
135    }
136
137    pub(crate) fn generate_or_derive_esk_internal<R: RngCore>(
138        &self,
139        rng: &mut R,
140    ) -> EphemeralSecretKey {
141        match self.derive_esk() {
142            None => EphemeralSecretKey(jubjub::Fr::random(rng)),
143            Some(esk) => esk,
144        }
145    }
146
147    /// Returns the derived `esk` if this note was created after ZIP 212 activated.
148    pub(crate) fn derive_esk(&self) -> Option<EphemeralSecretKey> {
149        match self.rseed {
150            Rseed::BeforeZip212(_) => None,
151            Rseed::AfterZip212(rseed) => Some(EphemeralSecretKey(jubjub::Fr::from_bytes_wide(
152                &PrfExpand::SAPLING_ESK.with(&rseed),
153            ))),
154        }
155    }
156
157    /// Generates a dummy spent note.
158    ///
159    /// Defined in [Zcash Protocol Spec § 4.8.2: Dummy Notes (Sapling)][saplingdummynotes].
160    ///
161    /// [saplingdummynotes]: https://zips.z.cash/protocol/nu5.pdf#saplingdummynotes
162    pub(crate) fn dummy<R: RngCore>(mut rng: R) -> (ExpandedSpendingKey, FullViewingKey, Self) {
163        let mut sk_bytes = [0; 32];
164        rng.fill_bytes(&mut sk_bytes);
165
166        let extsk = ExtendedSpendingKey::master(&sk_bytes[..]);
167        let fvk = extsk.to_diversifiable_full_viewing_key().fvk().clone();
168        let recipient = extsk.default_address();
169
170        let mut rseed_bytes = [0; 32];
171        rng.fill_bytes(&mut rseed_bytes);
172        let rseed = Rseed::AfterZip212(rseed_bytes);
173
174        let note = Note::from_parts(recipient.1, NoteValue::ZERO, rseed);
175
176        (extsk.expsk, fvk, note)
177    }
178}
179
180#[cfg(any(test, feature = "test-dependencies"))]
181pub(super) mod testing {
182    use proptest::{collection::vec, prelude::*};
183
184    use super::{
185        super::{testing::arb_payment_address, value::NoteValue},
186        ExtractedNoteCommitment, Note, Rseed,
187    };
188
189    prop_compose! {
190        pub fn arb_note(value: NoteValue)(
191            recipient in arb_payment_address(),
192            rseed in prop::array::uniform32(prop::num::u8::ANY).prop_map(Rseed::AfterZip212)
193        ) -> Note {
194            Note {
195                recipient,
196                value,
197                rseed
198            }
199        }
200    }
201
202    prop_compose! {
203        pub(crate) fn arb_cmu()(
204            cmu in vec(any::<u8>(), 64)
205                .prop_map(|v| <[u8;64]>::try_from(v.as_slice()).unwrap())
206                .prop_map(|v| bls12_381::Scalar::from_bytes_wide(&v)),
207        ) -> ExtractedNoteCommitment {
208            ExtractedNoteCommitment(cmu)
209        }
210    }
211}