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}