Skip to main content

orchard/
note.rs

1//! Data structures used for note construction.
2use core::fmt;
3use memuse::DynamicUsage;
4
5use ff::PrimeField;
6use group::GroupEncoding;
7use pasta_curves::pallas;
8use rand::RngCore;
9use subtle::CtOption;
10
11use crate::{
12    keys::{EphemeralSecretKey, FullViewingKey, Scope, SpendingKey},
13    spec::{to_base, to_scalar, NonZeroPallasScalar, PrfExpand},
14    value::NoteValue,
15    Address,
16};
17
18/// Note commitment types.
19pub mod commitment;
20pub use self::commitment::{ExtractedNoteCommitment, NoteCommitTrapdoor, NoteCommitment};
21
22/// Nullifier types and derivation.
23pub mod nullifier;
24pub use self::nullifier::Nullifier;
25
26/// The randomness used to construct a note.
27#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
28pub struct Rho(pallas::Base);
29
30// We know that `pallas::Base` doesn't allocate internally.
31memuse::impl_no_dynamic_usage!(Rho);
32
33impl Rho {
34    /// Deserialize the rho value from a byte array.
35    ///
36    /// This should only be used in cases where the components of a `Note` are being serialized and
37    /// stored individually. Use [`Action::rho`] or [`CompactAction::rho`] to obtain the [`Rho`]
38    /// value otherwise.
39    ///
40    /// [`Action::rho`]: crate::action::Action::rho
41    /// [`CompactAction::rho`]: crate::note_encryption::CompactAction::rho
42    pub fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
43        pallas::Base::from_repr(*bytes).map(Rho)
44    }
45
46    /// Serialize the rho value to its canonical byte representation.
47    pub fn to_bytes(self) -> [u8; 32] {
48        self.0.to_repr()
49    }
50
51    /// Constructs the [`Rho`] value to be used to construct a new note from the revealed nullifier
52    /// of the note being spent in the [`Action`] under construction.
53    ///
54    /// [`Action`]: crate::action::Action
55    pub fn from_nf_old(nf: Nullifier) -> Self {
56        Rho(nf.0)
57    }
58
59    /// Consumes `self` and returns the inner field element.
60    pub fn into_inner(self) -> pallas::Base {
61        self.0
62    }
63}
64
65/// The ZIP 212 seed randomness for a note.
66#[derive(Copy, Clone, Debug)]
67pub struct RandomSeed([u8; 32]);
68
69impl RandomSeed {
70    pub(crate) fn random(rng: &mut impl RngCore, rho: &Rho) -> Self {
71        loop {
72            let mut bytes = [0; 32];
73            rng.fill_bytes(&mut bytes);
74            let rseed = RandomSeed::from_bytes(bytes, rho);
75            if rseed.is_some().into() {
76                break rseed.unwrap();
77            }
78        }
79    }
80
81    /// Reads a note's random seed from bytes, given the note's rho value.
82    ///
83    /// Returns `None` if the rho value is not for the same note as the seed.
84    pub fn from_bytes(rseed: [u8; 32], rho: &Rho) -> CtOption<Self> {
85        let rseed = RandomSeed(rseed);
86        let esk = rseed.esk_inner(rho);
87        CtOption::new(rseed, esk.is_some())
88    }
89
90    /// Returns the byte array corresponding to this seed.
91    pub fn as_bytes(&self) -> &[u8; 32] {
92        &self.0
93    }
94
95    /// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend].
96    ///
97    /// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
98    /// Derives the psi value for this random seed and rho.
99    pub fn psi(&self, rho: &Rho) -> pallas::Base {
100        to_base(PrfExpand::PSI.with(&self.0, &rho.to_bytes()))
101    }
102
103    /// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend].
104    ///
105    /// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
106    fn esk_inner(&self, rho: &Rho) -> CtOption<NonZeroPallasScalar> {
107        NonZeroPallasScalar::from_scalar(to_scalar(
108            PrfExpand::ORCHARD_ESK.with(&self.0, &rho.to_bytes()),
109        ))
110    }
111
112    /// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend].
113    ///
114    /// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
115    fn esk(&self, rho: &Rho) -> NonZeroPallasScalar {
116        // We can't construct a RandomSeed for which this unwrap fails.
117        self.esk_inner(rho).unwrap()
118    }
119
120    /// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend].
121    ///
122    /// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
123    /// Derives the note commitment trapdoor for this random seed and rho.
124    pub fn rcm(&self, rho: &Rho) -> commitment::NoteCommitTrapdoor {
125        commitment::NoteCommitTrapdoor(to_scalar(
126            PrfExpand::ORCHARD_RCM.with(&self.0, &rho.to_bytes()),
127        ))
128    }
129}
130
131/// A discrete amount of funds received by an address.
132#[derive(Debug, Copy, Clone)]
133pub struct Note {
134    /// The recipient of the funds.
135    recipient: Address,
136    /// The value of this note.
137    value: NoteValue,
138    /// A unique creation ID for this note.
139    ///
140    /// This is produced from the nullifier of the note that will be spent in the [`Action`] that
141    /// creates this note.
142    ///
143    /// [`Action`]: crate::action::Action
144    rho: Rho,
145    /// The seed randomness for various note components.
146    rseed: RandomSeed,
147}
148
149impl PartialEq for Note {
150    fn eq(&self, other: &Self) -> bool {
151        // Notes are canonically defined by their commitments.
152        ExtractedNoteCommitment::from(self.commitment())
153            .eq(&ExtractedNoteCommitment::from(other.commitment()))
154    }
155}
156
157impl Eq for Note {}
158
159impl Note {
160    /// Creates a `Note` from its component parts.
161    ///
162    /// Returns `None` if a valid [`NoteCommitment`] cannot be derived from the note.
163    ///
164    /// # Caveats
165    ///
166    /// This low-level constructor enforces that the provided arguments produce an
167    /// internally valid `Note`. However, it allows notes to be constructed in a way that
168    /// violates required security checks for note decryption, as specified in
169    /// [Section 4.19] of the Zcash Protocol Specification. Users of this constructor
170    /// should only call it with note components that have been fully validated by
171    /// decrypting a received note according to [Section 4.19].
172    ///
173    /// [Section 4.19]: https://zips.z.cash/protocol/protocol.pdf#saplingandorchardinband
174    pub fn from_parts(
175        recipient: Address,
176        value: NoteValue,
177        rho: Rho,
178        rseed: RandomSeed,
179    ) -> CtOption<Self> {
180        let note = Note {
181            recipient,
182            value,
183            rho,
184            rseed,
185        };
186        CtOption::new(note, note.commitment_inner().is_some())
187    }
188
189    /// Generates a new note.
190    ///
191    /// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend].
192    ///
193    /// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
194    pub fn new(recipient: Address, value: NoteValue, rho: Rho, mut rng: impl RngCore) -> Self {
195        loop {
196            let note = Note::from_parts(recipient, value, rho, RandomSeed::random(&mut rng, &rho));
197            if note.is_some().into() {
198                break note.unwrap();
199            }
200        }
201    }
202
203    /// Generates a dummy spent note.
204    ///
205    /// Defined in [Zcash Protocol Spec § 4.8.3: Dummy Notes (Orchard)][orcharddummynotes].
206    ///
207    /// [orcharddummynotes]: https://zips.z.cash/protocol/nu5.pdf#orcharddummynotes
208    pub fn dummy(rng: &mut impl RngCore, rho: Option<Rho>) -> (SpendingKey, FullViewingKey, Self) {
209        let sk = SpendingKey::random(rng);
210        let fvk: FullViewingKey = (&sk).into();
211        let recipient = fvk.address_at(0u32, Scope::External);
212
213        let note = Note::new(
214            recipient,
215            NoteValue::zero(),
216            rho.unwrap_or_else(|| Rho::from_nf_old(Nullifier::dummy(rng))),
217            rng,
218        );
219
220        (sk, fvk, note)
221    }
222
223    /// Returns the recipient of this note.
224    pub fn recipient(&self) -> Address {
225        self.recipient
226    }
227
228    /// Returns the value of this note.
229    pub fn value(&self) -> NoteValue {
230        self.value
231    }
232
233    /// Returns the rseed value of this note.
234    pub fn rseed(&self) -> &RandomSeed {
235        &self.rseed
236    }
237
238    /// Derives the ephemeral secret key for this note.
239    pub(crate) fn esk(&self) -> EphemeralSecretKey {
240        EphemeralSecretKey(self.rseed.esk(&self.rho))
241    }
242
243    /// Returns rho of this note.
244    pub fn rho(&self) -> Rho {
245        self.rho
246    }
247
248    /// Derives the commitment to this note.
249    ///
250    /// Defined in [Zcash Protocol Spec § 3.2: Notes][notes].
251    ///
252    /// [notes]: https://zips.z.cash/protocol/nu5.pdf#notes
253    pub fn commitment(&self) -> NoteCommitment {
254        // `Note` will always have a note commitment by construction.
255        self.commitment_inner().unwrap()
256    }
257
258    /// Derives the commitment to this note.
259    ///
260    /// This is the internal fallible API, used to check at construction time that the
261    /// note has a commitment. Once you have a [`Note`] object, use `note.commitment()`
262    /// instead.
263    ///
264    /// Defined in [Zcash Protocol Spec § 3.2: Notes][notes].
265    ///
266    /// [notes]: https://zips.z.cash/protocol/nu5.pdf#notes
267    fn commitment_inner(&self) -> CtOption<NoteCommitment> {
268        let g_d = self.recipient.g_d();
269
270        NoteCommitment::derive(
271            g_d.to_bytes(),
272            self.recipient.pk_d().to_bytes(),
273            self.value,
274            self.rho.0,
275            self.rseed.psi(&self.rho),
276            self.rseed.rcm(&self.rho),
277        )
278    }
279
280    /// Derives the nullifier for this note.
281    pub fn nullifier(&self, fvk: &FullViewingKey) -> Nullifier {
282        Nullifier::derive(
283            fvk.nk(),
284            self.rho.0,
285            self.rseed.psi(&self.rho),
286            self.commitment(),
287        )
288    }
289}
290
291/// An encrypted note.
292#[derive(Clone)]
293pub struct TransmittedNoteCiphertext {
294    /// The serialization of the ephemeral public key
295    pub epk_bytes: [u8; 32],
296    /// The encrypted note ciphertext
297    pub enc_ciphertext: [u8; 580],
298    /// An encrypted value that allows the holder of the outgoing cipher
299    /// key for the note to recover the note plaintext.
300    pub out_ciphertext: [u8; 80],
301}
302
303impl fmt::Debug for TransmittedNoteCiphertext {
304    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
305        f.debug_struct("TransmittedNoteCiphertext")
306            .field("epk_bytes", &self.epk_bytes)
307            .field("enc_ciphertext", &hex::encode(self.enc_ciphertext))
308            .field("out_ciphertext", &hex::encode(self.out_ciphertext))
309            .finish()
310    }
311}
312
313/// Generators for property testing.
314#[cfg(any(test, feature = "test-dependencies"))]
315#[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))]
316pub mod testing {
317    use proptest::prelude::*;
318
319    use crate::{
320        address::testing::arb_address, note::nullifier::testing::arb_nullifier, value::NoteValue,
321    };
322
323    use super::{Note, RandomSeed, Rho};
324
325    prop_compose! {
326        /// Generate an arbitrary random seed
327        pub(crate) fn arb_rseed()(elems in prop::array::uniform32(prop::num::u8::ANY)) -> RandomSeed {
328            RandomSeed(elems)
329        }
330    }
331
332    prop_compose! {
333        /// Generate an action without authorization data.
334        pub fn arb_note(value: NoteValue)(
335            recipient in arb_address(),
336            rho in arb_nullifier().prop_map(Rho::from_nf_old),
337            rseed in arb_rseed(),
338        ) -> Note {
339            Note {
340                recipient,
341                value,
342                rho,
343                rseed,
344            }
345        }
346    }
347}