Skip to main content

sapling_crypto/
note.rs

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    Nullifier, NullifierDerivingKey, PaymentAddress, keys::EphemeralSecretKey, value::NoteValue,
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: Rng + CryptoRng>(&self, rng: &mut R) -> EphemeralSecretKey {
131        self.generate_or_derive_esk_internal(rng)
132    }
133
134    pub(crate) fn generate_or_derive_esk_internal<R: Rng>(
135        &self,
136        rng: &mut R,
137    ) -> EphemeralSecretKey {
138        match self.derive_esk() {
139            None => EphemeralSecretKey(jubjub::Fr::random(rng)),
140            Some(esk) => esk,
141        }
142    }
143
144    /// Returns the derived `esk` if this note was created after ZIP 212 activated.
145    pub(crate) fn derive_esk(&self) -> Option<EphemeralSecretKey> {
146        match self.rseed {
147            Rseed::BeforeZip212(_) => None,
148            Rseed::AfterZip212(rseed) => Some(EphemeralSecretKey(jubjub::Fr::from_bytes_wide(
149                &PrfExpand::SAPLING_ESK.with(&rseed),
150            ))),
151        }
152    }
153
154    /// Generates a dummy spent note.
155    ///
156    /// Defined in [Zcash Protocol Spec § 4.8.2: Dummy Notes (Sapling)][saplingdummynotes].
157    ///
158    /// [saplingdummynotes]: https://zips.z.cash/protocol/nu5.pdf#saplingdummynotes
159    pub(crate) fn dummy<R: Rng>(mut rng: R) -> (ExpandedSpendingKey, FullViewingKey, Self) {
160        let mut sk_bytes = [0; 32];
161        rng.fill_bytes(&mut sk_bytes);
162
163        let extsk = ExtendedSpendingKey::master(&sk_bytes[..]);
164        let fvk = extsk.to_diversifiable_full_viewing_key().fvk().clone();
165        let recipient = extsk.default_address();
166
167        let mut rseed_bytes = [0; 32];
168        rng.fill_bytes(&mut rseed_bytes);
169        let rseed = Rseed::AfterZip212(rseed_bytes);
170
171        let note = Note::from_parts(recipient.1, NoteValue::ZERO, rseed);
172
173        (extsk.expsk, fvk, note)
174    }
175}
176
177#[cfg(any(test, feature = "test-dependencies"))]
178pub(super) mod testing {
179    use proptest::{collection::vec, prelude::*};
180
181    use super::{
182        super::{testing::arb_payment_address, value::NoteValue},
183        ExtractedNoteCommitment, Note, Rseed,
184    };
185
186    prop_compose! {
187        pub fn arb_note(value: NoteValue)(
188            recipient in arb_payment_address(),
189            rseed in prop::array::uniform32(prop::num::u8::ANY).prop_map(Rseed::AfterZip212)
190        ) -> Note {
191            Note {
192                recipient,
193                value,
194                rseed
195            }
196        }
197    }
198
199    prop_compose! {
200        pub(crate) fn arb_cmu()(
201            cmu in vec(any::<u8>(), 64)
202                .prop_map(|v| <[u8;64]>::try_from(v.as_slice()).unwrap())
203                .prop_map(|v| bls12_381::Scalar::from_bytes_wide(&v)),
204        ) -> ExtractedNoteCommitment {
205            ExtractedNoteCommitment(cmu)
206        }
207    }
208}