ironfish_primitives/
sapling.rs

1//! Structs and constants specific to the Sapling shielded pool.
2
3pub mod group_hash;
4pub mod keys;
5pub mod note_encryption;
6pub mod pedersen_hash;
7pub mod prover;
8pub mod redjubjub;
9pub mod util;
10
11use bitvec::{order::Lsb0, view::AsBits};
12use blake2s_simd::Params as Blake2sParams;
13use byteorder::{LittleEndian, WriteBytesExt};
14use ff::{Field, PrimeField};
15use group::{Curve, Group, GroupEncoding};
16use incrementalmerkletree::{self, Altitude};
17use lazy_static::lazy_static;
18use rand_core::{CryptoRng, RngCore};
19use std::array::TryFromSliceError;
20use std::convert::{TryFrom, TryInto};
21use std::io::{self, Read, Write};
22use subtle::{Choice, ConstantTimeEq};
23
24use crate::{
25    constants::{self, SPENDING_KEY_GENERATOR},
26    keys::prf_expand,
27    merkle_tree::{HashSer, Hashable},
28    transaction::components::amount::MAX_MONEY,
29};
30
31use self::{
32    group_hash::group_hash,
33    pedersen_hash::{pedersen_hash, Personalization},
34    redjubjub::{PrivateKey, PublicKey, Signature},
35};
36
37pub const SAPLING_COMMITMENT_TREE_DEPTH: usize = 32;
38pub const SAPLING_COMMITMENT_TREE_DEPTH_U8: u8 = 32;
39
40/// Compute a parent node in the Sapling commitment tree given its two children.
41pub fn merkle_hash(depth: usize, lhs: &[u8; 32], rhs: &[u8; 32]) -> [u8; 32] {
42    let lhs = {
43        let mut tmp = [false; 256];
44        for (a, b) in tmp.iter_mut().zip(lhs.as_bits::<Lsb0>()) {
45            *a = *b;
46        }
47        tmp
48    };
49
50    let rhs = {
51        let mut tmp = [false; 256];
52        for (a, b) in tmp.iter_mut().zip(rhs.as_bits::<Lsb0>()) {
53            *a = *b;
54        }
55        tmp
56    };
57
58    ironfish_jubjub::ExtendedPoint::from(pedersen_hash(
59        Personalization::MerkleTree(depth),
60        lhs.iter()
61            .copied()
62            .take(blstrs::Scalar::NUM_BITS as usize)
63            .chain(
64                rhs.iter()
65                    .copied()
66                    .take(blstrs::Scalar::NUM_BITS as usize),
67            ),
68    ))
69    .to_affine()
70    .get_u()
71    .to_repr()
72}
73
74/// A node within the Sapling commitment tree.
75#[derive(Clone, Copy, Debug, PartialEq)]
76pub struct Node {
77    repr: [u8; 32],
78}
79
80impl Node {
81    pub fn new(repr: [u8; 32]) -> Self {
82        Node { repr }
83    }
84}
85
86impl incrementalmerkletree::Hashable for Node {
87    fn empty_leaf() -> Self {
88        Node {
89            repr: Note::uncommitted().to_repr(),
90        }
91    }
92
93    fn combine(altitude: Altitude, lhs: &Self, rhs: &Self) -> Self {
94        Node {
95            repr: merkle_hash(altitude.into(), &lhs.repr, &rhs.repr),
96        }
97    }
98
99    fn empty_root(altitude: Altitude) -> Self {
100        EMPTY_ROOTS[<usize>::from(altitude)]
101    }
102}
103
104impl HashSer for Node {
105    fn read<R: Read>(mut reader: R) -> io::Result<Self> {
106        let mut repr = [0u8; 32];
107        reader.read_exact(&mut repr)?;
108        Ok(Node::new(repr))
109    }
110
111    fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
112        writer.write_all(self.repr.as_ref())
113    }
114}
115
116impl From<Node> for blstrs::Scalar {
117    fn from(node: Node) -> Self {
118        // Tree nodes should be in the prime field.
119        blstrs::Scalar::from_repr(node.repr).unwrap()
120    }
121}
122
123lazy_static! {
124    static ref EMPTY_ROOTS: Vec<Node> = {
125        let mut v = vec![Node::blank()];
126        for d in 0..SAPLING_COMMITMENT_TREE_DEPTH {
127            let next = Node::combine(d, &v[d], &v[d]);
128            v.push(next);
129        }
130        v
131    };
132}
133
134/// Create the spendAuthSig for a Sapling SpendDescription.
135pub fn spend_sig<R: RngCore + CryptoRng>(
136    ask: PrivateKey,
137    ar: ironfish_jubjub::Fr,
138    sighash: &[u8; 32],
139    rng: &mut R,
140) -> Signature {
141    spend_sig_internal(ask, ar, sighash, rng)
142}
143
144pub(crate) fn spend_sig_internal<R: RngCore>(
145    ask: PrivateKey,
146    ar: ironfish_jubjub::Fr,
147    sighash: &[u8; 32],
148    rng: &mut R,
149) -> Signature {
150    // We compute `rsk`...
151    let rsk = ask.randomize(ar);
152
153    // We compute `rk` from there (needed for key prefixing)
154    let rk = PublicKey::from_private(&rsk, *SPENDING_KEY_GENERATOR);
155
156    // Compute the signature's message for rk/spend_auth_sig
157    let mut data_to_be_signed = [0u8; 64];
158    data_to_be_signed[0..32].copy_from_slice(&rk.0.to_bytes());
159    (&mut data_to_be_signed[32..64]).copy_from_slice(&sighash[..]);
160
161    // Do the signing
162    rsk.sign(&data_to_be_signed, rng, *SPENDING_KEY_GENERATOR)
163}
164
165#[derive(Clone)]
166pub struct ValueCommitment {
167    pub value: u64,
168    pub randomness: ironfish_jubjub::Fr,
169}
170
171impl ValueCommitment {
172    pub fn commitment(&self) -> ironfish_jubjub::SubgroupPoint {
173        (*constants::VALUE_COMMITMENT_VALUE_GENERATOR * ironfish_jubjub::Fr::from(self.value))
174            + (*constants::VALUE_COMMITMENT_RANDOMNESS_GENERATOR * self.randomness)
175    }
176}
177
178#[derive(Clone)]
179pub struct ProofGenerationKey {
180    pub ak: ironfish_jubjub::SubgroupPoint,
181    pub nsk: ironfish_jubjub::Fr,
182}
183
184impl ProofGenerationKey {
185    pub fn to_viewing_key(&self) -> ViewingKey {
186        ViewingKey {
187            ak: self.ak,
188            nk: *constants::PROOF_GENERATION_KEY_GENERATOR * self.nsk,
189        }
190    }
191}
192
193#[derive(Debug, Clone)]
194pub struct ViewingKey {
195    pub ak: ironfish_jubjub::SubgroupPoint,
196    pub nk: ironfish_jubjub::SubgroupPoint,
197}
198
199impl ViewingKey {
200    pub fn rk(&self, ar: ironfish_jubjub::Fr) -> ironfish_jubjub::SubgroupPoint {
201        self.ak + *constants::SPENDING_KEY_GENERATOR * ar
202    }
203
204    pub fn ivk(&self) -> SaplingIvk {
205        let mut h = [0; 32];
206        h.copy_from_slice(
207            Blake2sParams::new()
208                .hash_length(32)
209                .personal(constants::CRH_IVK_PERSONALIZATION)
210                .to_state()
211                .update(&self.ak.to_bytes())
212                .update(&self.nk.to_bytes())
213                .finalize()
214                .as_bytes(),
215        );
216
217        // Drop the most significant five bits, so it can be interpreted as a scalar.
218        h[31] &= 0b0000_0111;
219
220        SaplingIvk(ironfish_jubjub::Fr::from_repr(h).unwrap())
221    }
222
223    pub fn to_payment_address(&self, diversifier: Diversifier) -> Option<PaymentAddress> {
224        self.ivk().to_payment_address(diversifier)
225    }
226}
227
228#[derive(Debug, Clone)]
229pub struct SaplingIvk(pub ironfish_jubjub::Fr);
230
231impl SaplingIvk {
232    pub fn to_payment_address(&self, diversifier: Diversifier) -> Option<PaymentAddress> {
233        diversifier.g_d().and_then(|g_d| {
234            let pk_d = g_d * self.0;
235
236            PaymentAddress::from_parts(diversifier, pk_d)
237        })
238    }
239
240    pub fn to_repr(&self) -> [u8; 32] {
241        self.0.to_repr()
242    }
243}
244
245#[derive(Copy, Clone, Debug, PartialEq)]
246pub struct Diversifier(pub [u8; 11]);
247
248impl Diversifier {
249    pub fn g_d(&self) -> Option<ironfish_jubjub::SubgroupPoint> {
250        group_hash(&self.0, constants::KEY_DIVERSIFICATION_PERSONALIZATION)
251    }
252}
253
254/// A Sapling payment address.
255///
256/// # Invariants
257///
258/// `pk_d` is guaranteed to be prime-order (i.e. in the prime-order subgroup of Jubjub,
259/// and not the identity).
260#[derive(Clone, Debug)]
261pub struct PaymentAddress {
262    pk_d: ironfish_jubjub::SubgroupPoint,
263    diversifier: Diversifier,
264}
265
266impl PartialEq for PaymentAddress {
267    fn eq(&self, other: &Self) -> bool {
268        self.pk_d == other.pk_d && self.diversifier == other.diversifier
269    }
270}
271
272impl PaymentAddress {
273    /// Constructs a PaymentAddress from a diversifier and a Jubjub point.
274    ///
275    /// Returns None if `pk_d` is the identity.
276    pub fn from_parts(diversifier: Diversifier, pk_d: ironfish_jubjub::SubgroupPoint) -> Option<Self> {
277        if pk_d.is_identity().into() {
278            None
279        } else {
280            Some(PaymentAddress { pk_d, diversifier })
281        }
282    }
283
284    /// Constructs a PaymentAddress from a diversifier and a Jubjub point.
285    ///
286    /// Only for test code, as this explicitly bypasses the invariant.
287    #[cfg(test)]
288    pub(crate) fn from_parts_unchecked(
289        diversifier: Diversifier,
290        pk_d: ironfish_jubjub::SubgroupPoint,
291    ) -> Self {
292        PaymentAddress { pk_d, diversifier }
293    }
294
295    /// Parses a PaymentAddress from bytes.
296    pub fn from_bytes(bytes: &[u8; 43]) -> Option<Self> {
297        let diversifier = {
298            let mut tmp = [0; 11];
299            tmp.copy_from_slice(&bytes[0..11]);
300            Diversifier(tmp)
301        };
302        // Check that the diversifier is valid
303        diversifier.g_d()?;
304
305        let pk_d = ironfish_jubjub::SubgroupPoint::from_bytes(bytes[11..43].try_into().unwrap());
306        if pk_d.is_some().into() {
307            PaymentAddress::from_parts(diversifier, pk_d.unwrap())
308        } else {
309            None
310        }
311    }
312
313    /// Returns the byte encoding of this `PaymentAddress`.
314    pub fn to_bytes(&self) -> [u8; 43] {
315        let mut bytes = [0; 43];
316        bytes[0..11].copy_from_slice(&self.diversifier.0);
317        bytes[11..].copy_from_slice(&self.pk_d.to_bytes());
318        bytes
319    }
320
321    /// Returns the [`Diversifier`] for this `PaymentAddress`.
322    pub fn diversifier(&self) -> &Diversifier {
323        &self.diversifier
324    }
325
326    /// Returns `pk_d` for this `PaymentAddress`.
327    pub fn pk_d(&self) -> &ironfish_jubjub::SubgroupPoint {
328        &self.pk_d
329    }
330
331    pub fn g_d(&self) -> Option<ironfish_jubjub::SubgroupPoint> {
332        self.diversifier.g_d()
333    }
334
335    pub fn create_note(&self, value: u64, rseed: Rseed) -> Option<Note> {
336        self.g_d().map(|g_d| Note {
337            value,
338            rseed,
339            g_d,
340            pk_d: self.pk_d,
341        })
342    }
343}
344
345/// Enum for note randomness before and after [ZIP 212](https://zips.z.cash/zip-0212).
346///
347/// Before ZIP 212, the note commitment trapdoor `rcm` must be a scalar value.
348/// After ZIP 212, the note randomness `rseed` is a 32-byte sequence, used to derive
349/// both the note commitment trapdoor `rcm` and the ephemeral private key `esk`.
350#[derive(Copy, Clone, Debug)]
351pub enum Rseed {
352    BeforeZip212(ironfish_jubjub::Fr),
353    AfterZip212([u8; 32]),
354}
355
356/// Typesafe wrapper for nullifier values.
357#[derive(Copy, Clone, Debug, PartialEq, Eq)]
358pub struct Nullifier(pub [u8; 32]);
359
360impl Nullifier {
361    pub fn from_slice(bytes: &[u8]) -> Result<Nullifier, TryFromSliceError> {
362        bytes.try_into().map(Nullifier)
363    }
364
365    pub fn to_vec(&self) -> Vec<u8> {
366        self.0.to_vec()
367    }
368}
369impl AsRef<[u8]> for Nullifier {
370    fn as_ref(&self) -> &[u8] {
371        &self.0
372    }
373}
374
375impl ConstantTimeEq for Nullifier {
376    fn ct_eq(&self, other: &Self) -> Choice {
377        self.0.ct_eq(&other.0)
378    }
379}
380
381#[derive(Clone, Copy, Debug, PartialEq)]
382pub struct NoteValue(u64);
383
384impl TryFrom<u64> for NoteValue {
385    type Error = ();
386
387    fn try_from(value: u64) -> Result<Self, Self::Error> {
388        if value <= MAX_MONEY as u64 {
389            Ok(NoteValue(value))
390        } else {
391            Err(())
392        }
393    }
394}
395
396impl From<NoteValue> for u64 {
397    fn from(value: NoteValue) -> u64 {
398        value.0
399    }
400}
401
402#[derive(Clone, Debug)]
403pub struct Note {
404    /// The value of the note
405    pub value: u64,
406    /// The diversified base of the address, GH(d)
407    pub g_d: ironfish_jubjub::SubgroupPoint,
408    /// The public key of the address, g_d^ivk
409    pub pk_d: ironfish_jubjub::SubgroupPoint,
410    /// rseed
411    pub rseed: Rseed,
412}
413
414impl PartialEq for Note {
415    fn eq(&self, other: &Self) -> bool {
416        self.value == other.value
417            && self.g_d == other.g_d
418            && self.pk_d == other.pk_d
419            && self.rcm() == other.rcm()
420    }
421}
422
423impl Note {
424    pub fn uncommitted() -> blstrs::Scalar {
425        // The smallest u-coordinate that is not on the curve
426        // is one.
427        blstrs::Scalar::one()
428    }
429
430    /// Computes the note commitment, returning the full point.
431    fn cm_full_point(&self) -> ironfish_jubjub::SubgroupPoint {
432        // Calculate the note contents, as bytes
433        let mut note_contents = vec![];
434
435        // Writing the value in little endian
436        (&mut note_contents)
437            .write_u64::<LittleEndian>(self.value)
438            .unwrap();
439
440        // Write g_d
441        note_contents.extend_from_slice(&self.g_d.to_bytes());
442
443        // Write pk_d
444        note_contents.extend_from_slice(&self.pk_d.to_bytes());
445
446        assert_eq!(note_contents.len(), 32 + 32 + 8);
447
448        // Compute the Pedersen hash of the note contents
449        let hash_of_contents = pedersen_hash(
450            Personalization::NoteCommitment,
451            note_contents
452                .into_iter()
453                .flat_map(|byte| (0..8).map(move |i| ((byte >> i) & 1) == 1)),
454        );
455
456        // Compute final commitment
457        (*constants::NOTE_COMMITMENT_RANDOMNESS_GENERATOR * self.rcm()) + hash_of_contents
458    }
459
460    /// Computes the nullifier given the viewing key and
461    /// note position
462    pub fn nf(&self, viewing_key: &ViewingKey, position: u64) -> Nullifier {
463        // Compute rho = cm + position.G
464        let rho = self.cm_full_point()
465            + (*constants::NULLIFIER_POSITION_GENERATOR * ironfish_jubjub::Fr::from(position));
466
467        // Compute nf = BLAKE2s(nk | rho)
468        Nullifier::from_slice(
469            Blake2sParams::new()
470                .hash_length(32)
471                .personal(constants::PRF_NF_PERSONALIZATION)
472                .to_state()
473                .update(&viewing_key.nk.to_bytes())
474                .update(&rho.to_bytes())
475                .finalize()
476                .as_bytes(),
477        )
478        .unwrap()
479    }
480
481    /// Computes the note commitment
482    pub fn cmu(&self) -> blstrs::Scalar {
483        // The commitment is in the prime order subgroup, so mapping the
484        // commitment to the u-coordinate is an injective encoding.
485        ironfish_jubjub::ExtendedPoint::from(self.cm_full_point())
486            .to_affine()
487            .get_u()
488    }
489
490    pub fn rcm(&self) -> ironfish_jubjub::Fr {
491        match self.rseed {
492            Rseed::BeforeZip212(rcm) => rcm,
493            Rseed::AfterZip212(rseed) => {
494                ironfish_jubjub::Fr::from_bytes_wide(prf_expand(&rseed, &[0x04]).as_array())
495            }
496        }
497    }
498
499    pub fn generate_or_derive_esk<R: RngCore + CryptoRng>(&self, rng: &mut R) -> ironfish_jubjub::Fr {
500        self.generate_or_derive_esk_internal(rng)
501    }
502
503    pub(crate) fn generate_or_derive_esk_internal<R: RngCore>(&self, rng: &mut R) -> ironfish_jubjub::Fr {
504        match self.derive_esk() {
505            None => ironfish_jubjub::Fr::random(rng),
506            Some(esk) => esk,
507        }
508    }
509
510    /// Returns the derived `esk` if this note was created after ZIP 212 activated.
511    pub fn derive_esk(&self) -> Option<ironfish_jubjub::Fr> {
512        match self.rseed {
513            Rseed::BeforeZip212(_) => None,
514            Rseed::AfterZip212(rseed) => Some(ironfish_jubjub::Fr::from_bytes_wide(
515                prf_expand(&rseed, &[0x05]).as_array(),
516            )),
517        }
518    }
519}
520
521#[cfg(any(test, feature = "test-dependencies"))]
522pub mod testing {
523    use proptest::prelude::*;
524    use std::cmp::min;
525    use std::convert::TryFrom;
526
527    use crate::{
528        transaction::components::amount::MAX_MONEY, zip32::testing::arb_extended_spending_key,
529    };
530
531    use super::{Node, Note, NoteValue, PaymentAddress, Rseed};
532
533    prop_compose! {
534        pub fn arb_note_value()(value in 0u64..=MAX_MONEY as u64) -> NoteValue {
535            NoteValue::try_from(value).unwrap()
536        }
537    }
538
539    prop_compose! {
540        /// The
541        pub fn arb_positive_note_value(bound: u64)(
542            value in 1u64..=(min(bound, MAX_MONEY as u64))
543        ) -> NoteValue {
544            NoteValue::try_from(value).unwrap()
545        }
546    }
547
548    pub fn arb_payment_address() -> impl Strategy<Value = PaymentAddress> {
549        arb_extended_spending_key().prop_map(|sk| sk.default_address().1)
550    }
551
552    prop_compose! {
553        pub fn arb_node()(value in prop::array::uniform32(prop::num::u8::ANY)) -> Node {
554            Node::new(value)
555        }
556    }
557
558    prop_compose! {
559        pub fn arb_note(value: NoteValue)(
560            addr in arb_payment_address(),
561            rseed in prop::array::uniform32(prop::num::u8::ANY).prop_map(Rseed::AfterZip212)
562        ) -> Note {
563            Note {
564                value: value.into(),
565                g_d: addr.g_d().unwrap(), // this unwrap is safe because arb_payment_address always generates an address with a valid g_d
566                pk_d: *addr.pk_d(),
567                rseed
568            }
569        }
570    }
571}