penumbra_sdk_tct/internal/
proof.rs

1//! Transparent merkle inclusion proofs defined generically for trees of any height.
2//!
3//! These are wrapped in mode specific domain types by the exposed crate API to make it more
4//! comprehensible.
5
6use std::fmt::Debug;
7
8use crate::prelude::*;
9
10/// A proof of inclusion for a single [`Commitment`](crate::Commitment) commitment in a tree.
11#[derive(Derivative)]
12#[derivative(
13    Debug(bound = "<Tree::Height as path::Path>::Path: Debug"),
14    Clone(bound = "<Tree::Height as path::Path>::Path: Clone"),
15    PartialEq(bound = "<Tree::Height as path::Path>::Path: PartialEq"),
16    Eq(bound = "<Tree::Height as path::Path>::Path: Eq")
17)]
18pub struct Proof<Tree: Height> {
19    pub(crate) position: u64,
20    pub(crate) auth_path: AuthPath<Tree>,
21    pub(crate) leaf: StateCommitment,
22}
23
24impl<Tree: Height> Proof<Tree> {
25    /// Verify a [`Proof`] of inclusion against the root [`struct@Hash`] of a tree.
26    ///
27    /// Returns [`VerifyError`] if the proof is invalid.
28    pub fn verify(&self, root: Hash) -> Result<(), VerifyError> {
29        if root == self.root() {
30            Ok(())
31        } else {
32            Err(VerifyError { root })
33        }
34    }
35
36    /// Get the root of the tree from which the proof was generated.
37    pub fn root(&self) -> Hash {
38        Tree::Height::root(&self.auth_path, self.position, Hash::of(self.leaf))
39    }
40
41    /// Get the index of the item this proof claims to witness.
42    pub fn index(&self) -> u64 {
43        self.position
44    }
45
46    /// Get the [`AuthPath`] of this proof, representing the path from the root to the leaf of the
47    /// tree that proves the leaf was included in the tree.
48    pub fn auth_path(&self) -> &AuthPath<Tree> {
49        &self.auth_path
50    }
51}
52
53/// A proof of inclusion did not verify against the provided root hash.
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
55#[error("invalid inclusion proof for root hash {root:?}")]
56pub struct VerifyError {
57    root: Hash,
58}
59
60impl VerifyError {
61    /// Get the root hash against which the proof failed to verify.
62    pub fn root(&self) -> Hash {
63        self.root
64    }
65}
66
67/// When deserializing a proof, it was malformed.
68#[derive(Debug, Clone, Copy, Eq, PartialEq, Error)]
69#[error("could not decode proof")]
70pub struct ProofDecodeError;
71
72use decaf377::Fq;
73use penumbra_sdk_proto::penumbra::crypto::tct::v1 as pb;
74
75impl<Tree: Height> From<Proof<Tree>> for pb::StateCommitmentProof
76where
77    Vec<pb::MerklePathChunk>: From<AuthPath<Tree>>,
78{
79    fn from(proof: Proof<Tree>) -> Self {
80        Self {
81            position: proof.position,
82            auth_path: proof.auth_path.into(),
83            note_commitment: Some(proof.leaf.into()),
84        }
85    }
86}
87
88impl<Tree: Height> TryFrom<pb::StateCommitmentProof> for Proof<Tree>
89where
90    AuthPath<Tree>: TryFrom<Vec<pb::MerklePathChunk>>,
91{
92    type Error = ProofDecodeError;
93
94    fn try_from(proof: pb::StateCommitmentProof) -> Result<Self, Self::Error> {
95        let position = proof.position;
96        let auth_path = proof.auth_path.try_into().map_err(|_| ProofDecodeError)?;
97        let leaf = StateCommitment(
98            Fq::from_bytes_checked(
99                &proof
100                    .note_commitment
101                    .ok_or(ProofDecodeError)?
102                    .inner
103                    .try_into()
104                    .map_err(|_| ProofDecodeError)?,
105            )
106            .map_err(|_| ProofDecodeError)?,
107        );
108
109        Ok(Self {
110            position,
111            auth_path,
112            leaf,
113        })
114    }
115}