Skip to main content

orchard/note/
nullifier.rs

1use group::{ff::PrimeField, Group};
2use memuse::DynamicUsage;
3use pasta_curves::{arithmetic::CurveExt, pallas};
4use rand::RngCore;
5use subtle::{ConstantTimeEq, CtOption};
6
7use super::NoteCommitment;
8use crate::{
9    keys::NullifierDerivingKey,
10    spec::{extract_p, mod_r_p},
11};
12
13/// A unique nullifier for a note.
14#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
15pub struct Nullifier(pub pallas::Base);
16
17// We know that `pallas::Base` doesn't allocate internally.
18memuse::impl_no_dynamic_usage!(Nullifier);
19
20impl Nullifier {
21    /// Generates a dummy nullifier for use as $\rho$ in dummy spent notes.
22    ///
23    /// Nullifiers are required by consensus to be unique. For dummy output notes, we get
24    /// this restriction as intended: the note's $\rho$ value is set to the nullifier of
25    /// the accompanying spent note within the action, which is constrained by consensus
26    /// to be unique. In the case of dummy spent notes, we get this restriction by
27    /// following the chain backwards: the nullifier of the dummy spent note will be
28    /// constrained by consensus to be unique, and the nullifier's uniqueness is derived
29    /// from the uniqueness of $\rho$.
30    ///
31    /// Instead of explicitly sampling for a unique nullifier, we rely here on the size of
32    /// the base field to make the chance of sampling a colliding nullifier negligible.
33    pub(crate) fn dummy(rng: &mut impl RngCore) -> Self {
34        Nullifier(extract_p(&pallas::Point::random(rng)))
35    }
36
37    /// Deserialize the nullifier from a byte array.
38    pub fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
39        pallas::Base::from_repr(*bytes).map(Nullifier)
40    }
41
42    /// Serialize the nullifier to its canonical byte representation.
43    pub fn to_bytes(self) -> [u8; 32] {
44        self.0.to_repr()
45    }
46
47    /// $DeriveNullifier$.
48    ///
49    /// Defined in [Zcash Protocol Spec ยง 4.16: Note Commitments and Nullifiers][commitmentsandnullifiers].
50    ///
51    /// [commitmentsandnullifiers]: https://zips.z.cash/protocol/nu5.pdf#commitmentsandnullifiers
52    pub(super) fn derive(
53        nk: &NullifierDerivingKey,
54        rho: pallas::Base,
55        psi: pallas::Base,
56        cm: NoteCommitment,
57    ) -> Self {
58        let k = pallas::Point::hash_to_curve("z.cash:Orchard")(b"K");
59
60        Nullifier(extract_p(&(k * mod_r_p(nk.prf_nf(rho) + psi) + cm.0)))
61    }
62}
63
64impl ConstantTimeEq for Nullifier {
65    fn ct_eq(&self, other: &Self) -> subtle::Choice {
66        self.0.ct_eq(&other.0)
67    }
68}
69
70/// Generators for property testing.
71#[cfg(any(test, feature = "test-dependencies"))]
72#[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))]
73pub mod testing {
74    use group::{ff::FromUniformBytes, Group};
75    use pasta_curves::pallas;
76    use proptest::collection::vec;
77    use proptest::prelude::*;
78
79    use super::Nullifier;
80    use crate::spec::extract_p;
81
82    prop_compose! {
83        /// Generate a uniformly distributed nullifier value.
84        pub fn arb_nullifier()(
85            bytes in vec(any::<u8>(), 64)
86        ) -> Nullifier {
87            let point = pallas::Point::generator() * pallas::Scalar::from_uniform_bytes(&<[u8; 64]>::try_from(bytes).unwrap());
88            Nullifier(extract_p(&point))
89        }
90    }
91}