light_client/consensus/
namespace.rs

1use anyhow::{Context, Result, bail, ensure};
2use espresso_types::{Header, NamespaceId, NsProof, Transaction};
3use hotshot_types::data::VidCommon;
4use serde::{Deserialize, Serialize};
5
6/// Information required to verify a payload.
7#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
8
9pub struct NamespaceProof {
10    proof: Option<NonEmptyNamespaceProof>,
11}
12
13#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
14struct NonEmptyNamespaceProof {
15    proof: NsProof,
16    common: VidCommon,
17}
18
19impl NamespaceProof {
20    /// Construct a [`NamespaceProof`].
21    ///
22    /// Takes the underlying [`NsProof`], plus corresponding [`VidCommon`] data to allow a client to
23    /// verify the proof.
24    pub fn new(proof: NsProof, common: VidCommon) -> Self {
25        Self {
26            proof: Some(NonEmptyNamespaceProof { proof, common }),
27        }
28    }
29
30    /// Create a trivial proof for a namespace which is not present in a given block.
31    pub fn not_present() -> Self {
32        Self { proof: None }
33    }
34
35    /// Verify a [`NamespaceProof`].
36    ///
37    /// If the data in this proof matches the expected `header` and belongs to `namespace`, the list
38    /// of transactions from the namespace is returned.
39    pub fn verify(&self, header: &Header, namespace: NamespaceId) -> Result<Vec<Transaction>> {
40        let Some(proof) = &self.proof else {
41            // A trivial proof is a claim that the requested namespace is not present in the
42            // namespace table. We need to verify this claim.
43            if let Some(ix) = header.ns_table().find_ns_id(&namespace) {
44                bail!(
45                    "received trivial proof for missing namespace, but requested namespace \
46                     {namespace} is present at position {ix:?}"
47                );
48            }
49            return Ok(vec![]);
50        };
51
52        let (txs, ns) = proof
53            .proof
54            .verify(
55                header.ns_table(),
56                &header.payload_commitment(),
57                &proof.common,
58            )
59            .context("invalid namespace proof")?;
60        ensure!(
61            ns == namespace,
62            "proof is for wrong namespace {ns}, expected namespace {namespace}"
63        );
64        Ok(txs)
65    }
66}
67
68#[cfg(test)]
69mod test {
70    use espresso_types::{Leaf2, NodeState};
71    use hotshot_query_service::availability::TransactionIndex;
72    use versions::FEE_VERSION;
73
74    use super::*;
75    use crate::testing::TestClient;
76
77    #[tokio::test]
78    #[test_log::test]
79    async fn test_namespace_proof_non_empty() {
80        let client = TestClient::default();
81        let leaf = client.leaf(1).await;
82        let payload = client.payload(1).await;
83        let common = client.vid_common(1).await;
84
85        let tx = payload
86            .transaction(&TransactionIndex {
87                ns_index: 0.into(),
88                position: 0,
89            })
90            .unwrap();
91        let proof =
92            NamespaceProof::new(NsProof::new(&payload, &0.into(), &common).unwrap(), common);
93        assert_eq!(
94            proof.verify(leaf.header(), tx.namespace()).unwrap(),
95            vec![tx.clone()]
96        );
97
98        // Check that a trivial proof is not accepted for a non-trivial namespace.
99        let err = NamespaceProof::not_present()
100            .verify(leaf.header(), tx.namespace())
101            .unwrap_err();
102        assert!(
103            err.to_string()
104                .contains("received trivial proof for missing namespace"),
105            "{err:#}"
106        );
107    }
108
109    #[tokio::test]
110    #[test_log::test]
111    async fn test_namespace_proof_empty() {
112        let leaf = Leaf2::genesis(&Default::default(), &NodeState::mock(), FEE_VERSION).await;
113        let proof = NamespaceProof::not_present();
114
115        assert_eq!(
116            proof.verify(leaf.block_header(), 0u64.into()).unwrap(),
117            vec![]
118        );
119    }
120
121    #[tokio::test]
122    #[test_log::test]
123    async fn test_namespace_proof_invalid_wrong_payload() {
124        let client = TestClient::default();
125
126        let payload1 = client.payload(1).await;
127        let common1 = client.vid_common(1).await;
128        let tx = payload1
129            .transaction(&TransactionIndex {
130                ns_index: 0.into(),
131                position: 0,
132            })
133            .unwrap();
134        let proof = NamespaceProof::new(
135            NsProof::new(&payload1, &0.into(), &common1).unwrap(),
136            common1,
137        );
138
139        let leaf2 = client.leaf(2).await;
140        let err = proof.verify(leaf2.header(), tx.namespace()).unwrap_err();
141        assert!(
142            err.to_string().contains("invalid namespace proof"),
143            "{err:#}"
144        );
145    }
146
147    #[tokio::test]
148    #[test_log::test]
149    async fn test_namespace_proof_invalid_wrong_namespace() {
150        let client = TestClient::default();
151        let leaf = client.leaf(1).await;
152        let payload = client.payload(1).await;
153        let common = client.vid_common(1).await;
154
155        let tx = payload
156            .transaction(&TransactionIndex {
157                ns_index: 0.into(),
158                position: 0,
159            })
160            .unwrap();
161        let proof =
162            NamespaceProof::new(NsProof::new(&payload, &0.into(), &common).unwrap(), common);
163        let err = proof
164            .verify(
165                leaf.header(),
166                NamespaceId::from(u64::from(tx.namespace()) + 1),
167            )
168            .unwrap_err();
169        assert!(
170            err.to_string().contains("proof is for wrong namespace"),
171            "{err:#}"
172        );
173    }
174}