use crate::once::OnceTable;
use bitvec::{order::Lsb0, view::AsBits};
use group::{Curve, ff::PrimeField};
use incrementalmerkletree::{Hashable, Level};
use subtle::CtOption;
use alloc::vec::Vec;
use core::fmt;
use super::{
note::ExtractedNoteCommitment,
pedersen_hash::{Personalization, pedersen_hash},
};
pub const NOTE_COMMITMENT_TREE_DEPTH: u8 = 32;
pub type CommitmentTree =
incrementalmerkletree::frontier::CommitmentTree<Node, NOTE_COMMITMENT_TREE_DEPTH>;
pub type IncrementalWitness =
incrementalmerkletree::witness::IncrementalWitness<Node, NOTE_COMMITMENT_TREE_DEPTH>;
pub type MerklePath = incrementalmerkletree::MerklePath<Node, NOTE_COMMITMENT_TREE_DEPTH>;
fn uncommitted_sapling() -> bls12_381::Scalar {
bls12_381::Scalar::one()
}
static EMPTY_ROOTS: OnceTable<Vec<Node>> = OnceTable::new();
fn empty_roots() -> &'static [Node] {
EMPTY_ROOTS.get_or_init(|| {
let mut v = vec![Node::empty_leaf()];
for d in 0..NOTE_COMMITMENT_TREE_DEPTH {
let next = Node::combine(d.into(), &v[usize::from(d)], &v[usize::from(d)]);
v.push(next);
}
v
})
}
pub fn merkle_hash(depth: usize, lhs: &[u8; 32], rhs: &[u8; 32]) -> [u8; 32] {
merkle_hash_field(depth, lhs, rhs).to_repr()
}
fn merkle_hash_field(depth: usize, lhs: &[u8; 32], rhs: &[u8; 32]) -> jubjub::Base {
let lhs = {
let mut tmp = [false; 256];
for (a, b) in tmp.iter_mut().zip(lhs.as_bits::<Lsb0>()) {
*a = *b;
}
tmp
};
let rhs = {
let mut tmp = [false; 256];
for (a, b) in tmp.iter_mut().zip(rhs.as_bits::<Lsb0>()) {
*a = *b;
}
tmp
};
jubjub::ExtendedPoint::from(pedersen_hash(
Personalization::MerkleTree(depth),
lhs.iter()
.copied()
.take(bls12_381::Scalar::NUM_BITS as usize)
.chain(
rhs.iter()
.copied()
.take(bls12_381::Scalar::NUM_BITS as usize),
),
))
.to_affine()
.get_u()
}
#[derive(Eq, PartialEq, Clone, Copy, Debug)]
pub struct Anchor(jubjub::Base);
impl From<jubjub::Base> for Anchor {
fn from(anchor_field: jubjub::Base) -> Anchor {
Anchor(anchor_field)
}
}
impl From<Node> for Anchor {
fn from(anchor: Node) -> Anchor {
Anchor(anchor.0)
}
}
impl Anchor {
pub fn empty_tree() -> Anchor {
Anchor(Node::empty_root(NOTE_COMMITMENT_TREE_DEPTH.into()).0)
}
pub(crate) fn inner(&self) -> jubjub::Base {
self.0
}
pub fn from_bytes(bytes: [u8; 32]) -> CtOption<Anchor> {
jubjub::Base::from_repr(bytes).map(Self)
}
pub fn to_bytes(self) -> [u8; 32] {
self.0.to_repr()
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Node(jubjub::Base);
impl fmt::Debug for Node {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Node")
.field("repr", &hex::encode(self.0.to_bytes()))
.finish()
}
}
impl Node {
pub fn from_cmu(value: &ExtractedNoteCommitment) -> Self {
Node(value.inner())
}
pub fn from_scalar(cmu: bls12_381::Scalar) -> Self {
Self(cmu)
}
pub fn from_bytes(bytes: [u8; 32]) -> CtOption<Self> {
jubjub::Base::from_repr(bytes).map(Self)
}
pub fn to_bytes(&self) -> [u8; 32] {
self.0.to_repr()
}
#[cfg(feature = "circuit")]
pub(crate) fn inner(&self) -> &jubjub::Base {
&self.0
}
}
impl Hashable for Node {
fn empty_leaf() -> Self {
Node(uncommitted_sapling())
}
fn combine(level: Level, lhs: &Self, rhs: &Self) -> Self {
Node(merkle_hash_field(
level.into(),
&lhs.0.to_bytes(),
&rhs.0.to_bytes(),
))
}
fn empty_root(level: Level) -> Self {
empty_roots()[<usize>::from(level)]
}
}
impl From<Node> for bls12_381::Scalar {
fn from(node: Node) -> Self {
node.0
}
}
#[cfg(any(test, feature = "test-dependencies"))]
pub(super) mod testing {
use ff::Field;
use proptest::prelude::*;
use rand::{
Rng as RandRng,
distr::{Distribution, StandardUniform},
};
use super::Node;
use crate::note::testing::arb_cmu;
prop_compose! {
pub fn arb_node()(cmu in arb_cmu()) -> Node {
Node::from_cmu(&cmu)
}
}
impl Node {
pub fn random(rng: &mut impl RandRng) -> Self {
StandardUniform.sample(rng)
}
}
impl Distribution<Node> for StandardUniform {
fn sample<R: RandRng + ?Sized>(&self, rng: &mut R) -> Node {
Node::from_scalar(bls12_381::Scalar::random(rng))
}
}
}