Skip to main content

sapling_crypto/
tree.rs

1use crate::once::OnceTable;
2use bitvec::{order::Lsb0, view::AsBits};
3use group::{Curve, ff::PrimeField};
4use incrementalmerkletree::{Hashable, Level};
5use subtle::CtOption;
6
7use alloc::vec::Vec;
8use core::fmt;
9
10use super::{
11    note::ExtractedNoteCommitment,
12    pedersen_hash::{Personalization, pedersen_hash},
13};
14
15pub const NOTE_COMMITMENT_TREE_DEPTH: u8 = 32;
16pub type CommitmentTree =
17    incrementalmerkletree::frontier::CommitmentTree<Node, NOTE_COMMITMENT_TREE_DEPTH>;
18pub type IncrementalWitness =
19    incrementalmerkletree::witness::IncrementalWitness<Node, NOTE_COMMITMENT_TREE_DEPTH>;
20pub type MerklePath = incrementalmerkletree::MerklePath<Node, NOTE_COMMITMENT_TREE_DEPTH>;
21
22fn uncommitted_sapling() -> bls12_381::Scalar {
23    bls12_381::Scalar::one()
24}
25
26static EMPTY_ROOTS: OnceTable<Vec<Node>> = OnceTable::new();
27
28fn empty_roots() -> &'static [Node] {
29    EMPTY_ROOTS.get_or_init(|| {
30        let mut v = vec![Node::empty_leaf()];
31        for d in 0..NOTE_COMMITMENT_TREE_DEPTH {
32            let next = Node::combine(d.into(), &v[usize::from(d)], &v[usize::from(d)]);
33            v.push(next);
34        }
35        v
36    })
37}
38
39/// Compute a parent node in the Sapling commitment tree given its two children.
40pub fn merkle_hash(depth: usize, lhs: &[u8; 32], rhs: &[u8; 32]) -> [u8; 32] {
41    merkle_hash_field(depth, lhs, rhs).to_repr()
42}
43
44fn merkle_hash_field(depth: usize, lhs: &[u8; 32], rhs: &[u8; 32]) -> jubjub::Base {
45    let lhs = {
46        let mut tmp = [false; 256];
47        for (a, b) in tmp.iter_mut().zip(lhs.as_bits::<Lsb0>()) {
48            *a = *b;
49        }
50        tmp
51    };
52
53    let rhs = {
54        let mut tmp = [false; 256];
55        for (a, b) in tmp.iter_mut().zip(rhs.as_bits::<Lsb0>()) {
56            *a = *b;
57        }
58        tmp
59    };
60
61    jubjub::ExtendedPoint::from(pedersen_hash(
62        Personalization::MerkleTree(depth),
63        lhs.iter()
64            .copied()
65            .take(bls12_381::Scalar::NUM_BITS as usize)
66            .chain(
67                rhs.iter()
68                    .copied()
69                    .take(bls12_381::Scalar::NUM_BITS as usize),
70            ),
71    ))
72    .to_affine()
73    .get_u()
74}
75
76/// The root of a Sapling commitment tree.
77#[derive(Eq, PartialEq, Clone, Copy, Debug)]
78pub struct Anchor(jubjub::Base);
79
80impl From<jubjub::Base> for Anchor {
81    fn from(anchor_field: jubjub::Base) -> Anchor {
82        Anchor(anchor_field)
83    }
84}
85
86impl From<Node> for Anchor {
87    fn from(anchor: Node) -> Anchor {
88        Anchor(anchor.0)
89    }
90}
91
92impl Anchor {
93    /// The anchor of the empty Sapling note commitment tree.
94    ///
95    /// This anchor does not correspond to any valid anchor for a spend, so it
96    /// may only be used for coinbase bundles or in circumstances where Sapling
97    /// functionality is not active.
98    pub fn empty_tree() -> Anchor {
99        Anchor(Node::empty_root(NOTE_COMMITMENT_TREE_DEPTH.into()).0)
100    }
101
102    pub(crate) fn inner(&self) -> jubjub::Base {
103        self.0
104    }
105
106    /// Parses a Sapling anchor from a byte encoding.
107    pub fn from_bytes(bytes: [u8; 32]) -> CtOption<Anchor> {
108        jubjub::Base::from_repr(bytes).map(Self)
109    }
110
111    /// Returns the byte encoding of this anchor.
112    pub fn to_bytes(self) -> [u8; 32] {
113        self.0.to_repr()
114    }
115}
116
117/// A node within the Sapling commitment tree.
118#[derive(Clone, Copy, PartialEq, Eq)]
119pub struct Node(jubjub::Base);
120
121impl fmt::Debug for Node {
122    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123        f.debug_struct("Node")
124            .field("repr", &hex::encode(self.0.to_bytes()))
125            .finish()
126    }
127}
128
129impl Node {
130    /// Creates a tree leaf from the given Sapling note commitment.
131    pub fn from_cmu(value: &ExtractedNoteCommitment) -> Self {
132        Node(value.inner())
133    }
134
135    /// Constructs a new note commitment tree node from a [`bls12_381::Scalar`]
136    pub fn from_scalar(cmu: bls12_381::Scalar) -> Self {
137        Self(cmu)
138    }
139
140    /// Parses a tree leaf from the bytes of a Sapling note commitment.
141    ///
142    /// Returns `None` if the provided bytes represent a non-canonical encoding.
143    pub fn from_bytes(bytes: [u8; 32]) -> CtOption<Self> {
144        jubjub::Base::from_repr(bytes).map(Self)
145    }
146
147    /// Returns the canonical byte representation of this node.
148    pub fn to_bytes(&self) -> [u8; 32] {
149        self.0.to_repr()
150    }
151
152    /// Returns the wrapped value
153    #[cfg(feature = "circuit")]
154    pub(crate) fn inner(&self) -> &jubjub::Base {
155        &self.0
156    }
157}
158
159impl Hashable for Node {
160    fn empty_leaf() -> Self {
161        Node(uncommitted_sapling())
162    }
163
164    fn combine(level: Level, lhs: &Self, rhs: &Self) -> Self {
165        Node(merkle_hash_field(
166            level.into(),
167            &lhs.0.to_bytes(),
168            &rhs.0.to_bytes(),
169        ))
170    }
171
172    fn empty_root(level: Level) -> Self {
173        empty_roots()[<usize>::from(level)]
174    }
175}
176
177impl From<Node> for bls12_381::Scalar {
178    fn from(node: Node) -> Self {
179        node.0
180    }
181}
182
183#[cfg(any(test, feature = "test-dependencies"))]
184pub(super) mod testing {
185    use ff::Field;
186    use proptest::prelude::*;
187    use rand::{
188        Rng as RandRng,
189        distr::{Distribution, StandardUniform},
190    };
191
192    use super::Node;
193    use crate::note::testing::arb_cmu;
194
195    prop_compose! {
196        pub fn arb_node()(cmu in arb_cmu()) -> Node {
197            Node::from_cmu(&cmu)
198        }
199    }
200
201    impl Node {
202        /// Return a random fake `MerkleHashOrchard`.
203        pub fn random(rng: &mut impl RandRng) -> Self {
204            StandardUniform.sample(rng)
205        }
206    }
207
208    impl Distribution<Node> for StandardUniform {
209        fn sample<R: RandRng + ?Sized>(&self, rng: &mut R) -> Node {
210            Node::from_scalar(bls12_381::Scalar::random(rng))
211        }
212    }
213}