zebra_chain/orchard/
note.rs

1//! Orchard notes
2
3use group::{ff::PrimeField, GroupEncoding};
4use halo2::pasta::pallas;
5use rand_core::{CryptoRng, RngCore};
6
7use crate::{
8    amount::{Amount, NonNegative},
9    error::{NoteError, RandError},
10};
11
12use super::{address::Address, sinsemilla::extract_p};
13
14mod ciphertexts;
15mod nullifiers;
16
17pub use ciphertexts::{EncryptedNote, WrappedNoteKey};
18pub use nullifiers::Nullifier;
19
20#[cfg(any(test, feature = "proptest-impl"))]
21mod arbitrary;
22
23/// A random seed (rseed) used in the Orchard note creation.
24#[derive(Clone, Copy, Debug)]
25// At the moment this field is never read.
26//
27// TODO: consider replacing this code with the equivalent `orchard` crate code,
28//       which is better tested.
29#[allow(dead_code)]
30pub struct SeedRandomness(pub(crate) [u8; 32]);
31
32impl SeedRandomness {
33    pub fn new<T>(csprng: &mut T) -> Result<Self, RandError>
34    where
35        T: RngCore + CryptoRng,
36    {
37        let mut bytes = [0u8; 32];
38        csprng
39            .try_fill_bytes(&mut bytes)
40            .map_err(|_| RandError::FillBytes)?;
41        Ok(Self(bytes))
42    }
43}
44
45/// Used as input to PRF^nf as part of deriving the _nullifier_ of the _note_.
46///
47/// When creating a new note from spending an old note, the new note's _rho_ is
48/// the _nullifier_ of the previous note. If creating a note from scratch (like
49/// a miner reward), a dummy note is constructed, and its nullifier as the _rho_
50/// for the actual output note. When creating a dummy note, its _rho_ is chosen
51/// as a random Pallas point's x-coordinate.
52///
53/// <https://zips.z.cash/protocol/nu5.pdf#orcharddummynotes>
54#[derive(Clone, Debug)]
55pub struct Rho(pub(crate) pallas::Base);
56
57impl From<Rho> for [u8; 32] {
58    fn from(rho: Rho) -> Self {
59        rho.0.to_repr()
60    }
61}
62
63impl From<Nullifier> for Rho {
64    fn from(nf: Nullifier) -> Self {
65        Self(nf.0)
66    }
67}
68
69impl Rho {
70    pub fn new<T>(csprng: &mut T) -> Result<Self, NoteError>
71    where
72        T: RngCore + CryptoRng,
73    {
74        let mut bytes = [0u8; 32];
75        csprng
76            .try_fill_bytes(&mut bytes)
77            .map_err(|_| NoteError::from(RandError::FillBytes))?;
78
79        let possible_point = pallas::Point::from_bytes(&bytes);
80
81        if possible_point.is_some().into() {
82            Ok(Self(extract_p(possible_point.unwrap())))
83        } else {
84            Err(NoteError::InvalidRho)
85        }
86    }
87}
88
89/// Additional randomness used in deriving the _nullifier_.
90///
91/// <https://zips.z.cash/protocol/nu5.pdf#orchardsend>
92#[derive(Clone, Debug)]
93pub struct Psi(pub(crate) pallas::Base);
94
95impl From<Psi> for [u8; 32] {
96    fn from(psi: Psi) -> Self {
97        psi.0.to_repr()
98    }
99}
100
101/// A Note represents that a value is spendable by the recipient who holds the
102/// spending key corresponding to a given shielded payment address.
103///
104/// <https://zips.z.cash/protocol/protocol.pdf#notes>
105#[derive(Clone, Debug)]
106pub struct Note {
107    /// The recipient's shielded payment address.
108    pub address: Address,
109    /// An integer representing the value of the _note_ in zatoshi.
110    pub value: Amount<NonNegative>,
111    /// Used as input to PRF^nfOrchard_nk as part of deriving the _nullifier_ of
112    /// the _note_.
113    pub rho: Rho,
114    /// 32 random bytes from which _rcm_, _psi_, and the _ephemeral private key_
115    /// are derived.
116    pub rseed: SeedRandomness,
117}
118
119impl Note {
120    /// Create an Orchard _note_, by choosing 32 uniformly random bytes for
121    /// rseed.
122    ///
123    /// <https://zips.z.cash/protocol/protocol.pdf#notes>
124    pub fn new<T>(
125        csprng: &mut T,
126        address: Address,
127        value: Amount<NonNegative>,
128        nf_old: Nullifier,
129    ) -> Result<Self, RandError>
130    where
131        T: RngCore + CryptoRng,
132    {
133        Ok(Self {
134            address,
135            value,
136            rho: nf_old.into(),
137            rseed: SeedRandomness::new(csprng)?,
138        })
139    }
140}