Skip to main content

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    /// Inspect the namespace proof and the VID common data it is checked against, without
36    /// verifying them.
37    ///
38    /// [`None`] for a trivial proof, which claims the namespace is not present in the block.
39    pub fn contents(&self) -> Option<(&NsProof, &VidCommon)> {
40        self.proof
41            .as_ref()
42            .map(|proof| (&proof.proof, &proof.common))
43    }
44
45    /// Verify a [`NamespaceProof`].
46    ///
47    /// If the data in this proof matches the expected `header` and belongs to `namespace`, the list
48    /// of transactions from the namespace is returned.
49    pub fn verify(&self, header: &Header, namespace: NamespaceId) -> Result<Vec<Transaction>> {
50        let Some(proof) = &self.proof else {
51            // A trivial proof is a claim that the requested namespace is not present in the
52            // namespace table. We need to verify this claim.
53            if let Some(ix) = header.ns_table().find_ns_id(&namespace) {
54                bail!(
55                    "received trivial proof for missing namespace, but requested namespace \
56                     {namespace} is present at position {ix:?}"
57                );
58            }
59            return Ok(vec![]);
60        };
61
62        let (txs, ns) = proof
63            .proof
64            .verify(
65                header.ns_table(),
66                &header.payload_commitment(),
67                &proof.common,
68            )
69            .context("invalid namespace proof")?;
70        ensure!(
71            ns == namespace,
72            "proof is for wrong namespace {ns}, expected namespace {namespace}"
73        );
74        Ok(txs)
75    }
76}
77
78#[cfg(test)]
79mod test {
80    use espresso_types::{Leaf2, NodeState};
81    use hotshot_query_service_types::availability::TransactionIndex;
82    use versions::FEE_VERSION;
83
84    use super::*;
85    use crate::testing::TestClient;
86
87    #[tokio::test]
88    #[test_log::test]
89    async fn test_namespace_proof_non_empty() {
90        let client = TestClient::default();
91        let leaf = client.leaf(1).await;
92        let payload = client.payload(1).await;
93        let common = client.vid_common(1).await;
94
95        let tx = payload
96            .transaction(&TransactionIndex {
97                ns_index: 0.into(),
98                position: 0,
99            })
100            .unwrap();
101        let proof =
102            NamespaceProof::new(NsProof::new(&payload, &0.into(), &common).unwrap(), common);
103        assert_eq!(
104            proof.verify(leaf.header(), tx.namespace()).unwrap(),
105            vec![tx.clone()]
106        );
107
108        // Check that a trivial proof is not accepted for a non-trivial namespace.
109        let err = NamespaceProof::not_present()
110            .verify(leaf.header(), tx.namespace())
111            .unwrap_err();
112        assert!(
113            err.to_string()
114                .contains("received trivial proof for missing namespace"),
115            "{err:#}"
116        );
117    }
118
119    #[tokio::test]
120    #[test_log::test]
121    async fn test_namespace_proof_empty() {
122        let leaf = Leaf2::genesis(&Default::default(), &NodeState::mock(), FEE_VERSION).await;
123        let proof = NamespaceProof::not_present();
124
125        assert_eq!(
126            proof.verify(leaf.block_header(), 0u64.into()).unwrap(),
127            vec![]
128        );
129    }
130
131    #[tokio::test]
132    #[test_log::test]
133    async fn test_namespace_proof_invalid_wrong_payload() {
134        let client = TestClient::default();
135
136        let payload1 = client.payload(1).await;
137        let common1 = client.vid_common(1).await;
138        let tx = payload1
139            .transaction(&TransactionIndex {
140                ns_index: 0.into(),
141                position: 0,
142            })
143            .unwrap();
144        let proof = NamespaceProof::new(
145            NsProof::new(&payload1, &0.into(), &common1).unwrap(),
146            common1,
147        );
148
149        let leaf2 = client.leaf(2).await;
150        let err = proof.verify(leaf2.header(), tx.namespace()).unwrap_err();
151        assert!(
152            err.to_string().contains("invalid namespace proof"),
153            "{err:#}"
154        );
155    }
156
157    #[tokio::test]
158    #[test_log::test]
159    async fn test_namespace_proof_invalid_wrong_namespace() {
160        let client = TestClient::default();
161        let leaf = client.leaf(1).await;
162        let payload = client.payload(1).await;
163        let common = client.vid_common(1).await;
164
165        let tx = payload
166            .transaction(&TransactionIndex {
167                ns_index: 0.into(),
168                position: 0,
169            })
170            .unwrap();
171        let proof =
172            NamespaceProof::new(NsProof::new(&payload, &0.into(), &common).unwrap(), common);
173        let err = proof
174            .verify(
175                leaf.header(),
176                NamespaceId::from(u64::from(tx.namespace()) + 1),
177            )
178            .unwrap_err();
179        assert!(
180            err.to_string().contains("proof is for wrong namespace"),
181            "{err:#}"
182        );
183    }
184}