Skip to main content

runx_runtime/receipts/
tree.rs

1use std::collections::BTreeMap;
2
3use runx_contracts::{Receipt, Reference, ReferenceType};
4use runx_receipts::{
5    ReceiptResolveResult, ReceiptResolver, ReceiptTreeConfig, ReceiptVerification, ResolvedReceipt,
6    verify_receipt_tree_proof_with_resolver,
7};
8
9use super::seal::{RuntimeReceiptProofContextProvider, RuntimeReceiptSignaturePolicy};
10
11#[derive(Clone, Debug, Default)]
12pub struct RuntimeReceiptResolver {
13    receipts: Vec<Receipt>,
14    positions: BTreeMap<String, Vec<usize>>,
15}
16
17impl RuntimeReceiptResolver {
18    #[must_use]
19    pub fn new(receipts: impl IntoIterator<Item = Receipt>) -> Self {
20        let receipts = receipts.into_iter().collect::<Vec<_>>();
21        let positions = receipt_positions(receipts.iter());
22        Self {
23            receipts,
24            positions,
25        }
26    }
27
28    #[must_use]
29    pub fn receipts(&self) -> &[Receipt] {
30        &self.receipts
31    }
32}
33
34impl ReceiptResolver for RuntimeReceiptResolver {
35    fn resolve_child<'a>(&'a self, reference: &Reference) -> ReceiptResolveResult<'a> {
36        let Some(receipt_id) = referenced_receipt_id(reference) else {
37            return ReceiptResolveResult::Malformed;
38        };
39        let Some(indexes) = self.positions.get(receipt_id) else {
40            return ReceiptResolveResult::Missing;
41        };
42        let [index] = indexes.as_slice() else {
43            return ReceiptResolveResult::Ambiguous;
44        };
45        let Some(receipt) = self.receipts.get(*index) else {
46            return ReceiptResolveResult::ResolverError;
47        };
48        ReceiptResolveResult::Found(ResolvedReceipt {
49            path: runtime_receipt_path(*index),
50            receipt,
51        })
52    }
53
54    fn supplied_receipts<'a>(&'a self) -> Vec<ResolvedReceipt<'a>> {
55        self.receipts
56            .iter()
57            .enumerate()
58            .map(|(index, receipt)| ResolvedReceipt {
59                path: runtime_receipt_path(index),
60                receipt,
61            })
62            .collect()
63    }
64}
65
66struct RuntimeReceiptRefResolver<'a> {
67    receipts: Vec<&'a Receipt>,
68    positions: BTreeMap<String, Vec<usize>>,
69}
70
71impl<'a> RuntimeReceiptRefResolver<'a> {
72    fn new(receipts: impl IntoIterator<Item = &'a Receipt>) -> Self {
73        let receipts = receipts.into_iter().collect::<Vec<_>>();
74        let positions = receipt_positions(receipts.iter().copied());
75        Self {
76            receipts,
77            positions,
78        }
79    }
80}
81
82impl ReceiptResolver for RuntimeReceiptRefResolver<'_> {
83    fn resolve_child<'a>(&'a self, reference: &Reference) -> ReceiptResolveResult<'a> {
84        let Some(receipt_id) = referenced_receipt_id(reference) else {
85            return ReceiptResolveResult::Malformed;
86        };
87        let Some(indexes) = self.positions.get(receipt_id) else {
88            return ReceiptResolveResult::Missing;
89        };
90        let [index] = indexes.as_slice() else {
91            return ReceiptResolveResult::Ambiguous;
92        };
93        let Some(receipt) = self.receipts.get(*index) else {
94            return ReceiptResolveResult::ResolverError;
95        };
96        ReceiptResolveResult::Found(ResolvedReceipt {
97            path: runtime_receipt_path(*index),
98            receipt,
99        })
100    }
101
102    fn supplied_receipts<'a>(&'a self) -> Vec<ResolvedReceipt<'a>> {
103        self.receipts
104            .iter()
105            .enumerate()
106            .map(|(index, receipt)| ResolvedReceipt {
107                path: runtime_receipt_path(index),
108                receipt,
109            })
110            .collect()
111    }
112}
113
114pub fn validate_runtime_receipt_tree(
115    root: &Receipt,
116    receipts: impl IntoIterator<Item = Receipt>,
117    config: ReceiptTreeConfig,
118) -> Result<(), ReceiptVerification> {
119    let verification = verify_runtime_receipt_tree(root, receipts, config);
120    if verification.valid {
121        Ok(())
122    } else {
123        Err(verification)
124    }
125}
126
127pub fn validate_runtime_receipt_tree_with_policy(
128    root: &Receipt,
129    receipts: impl IntoIterator<Item = Receipt>,
130    config: ReceiptTreeConfig,
131    signature_policy: RuntimeReceiptSignaturePolicy<'_>,
132) -> Result<(), ReceiptVerification> {
133    let verification =
134        verify_runtime_receipt_tree_with_policy(root, receipts, config, signature_policy);
135    if verification.valid {
136        Ok(())
137    } else {
138        Err(verification)
139    }
140}
141
142pub(crate) fn validate_runtime_receipt_tree_refs_with_policy<'a>(
143    root: &Receipt,
144    receipts: impl IntoIterator<Item = &'a Receipt>,
145    config: ReceiptTreeConfig,
146    signature_policy: RuntimeReceiptSignaturePolicy<'_>,
147) -> Result<(), ReceiptVerification> {
148    let verification =
149        verify_runtime_receipt_tree_refs_with_policy(root, receipts, config, signature_policy);
150    if verification.valid {
151        Ok(())
152    } else {
153        Err(verification)
154    }
155}
156
157#[must_use]
158pub fn verify_runtime_receipt_tree(
159    root: &Receipt,
160    receipts: impl IntoIterator<Item = Receipt>,
161    config: ReceiptTreeConfig,
162) -> ReceiptVerification {
163    verify_runtime_receipt_tree_with_policy(
164        root,
165        receipts,
166        config,
167        RuntimeReceiptSignaturePolicy::local_development(),
168    )
169}
170
171#[must_use]
172pub fn verify_runtime_receipt_tree_with_policy(
173    root: &Receipt,
174    receipts: impl IntoIterator<Item = Receipt>,
175    config: ReceiptTreeConfig,
176    signature_policy: RuntimeReceiptSignaturePolicy<'_>,
177) -> ReceiptVerification {
178    let resolver = RuntimeReceiptResolver::new(receipts);
179    let proof_contexts = RuntimeReceiptProofContextProvider::new(signature_policy);
180    verify_receipt_tree_proof_with_resolver(
181        root,
182        &resolver,
183        runtime_receipt_tree_config(config),
184        &proof_contexts,
185    )
186}
187
188fn verify_runtime_receipt_tree_refs_with_policy<'a>(
189    root: &Receipt,
190    receipts: impl IntoIterator<Item = &'a Receipt>,
191    config: ReceiptTreeConfig,
192    signature_policy: RuntimeReceiptSignaturePolicy<'_>,
193) -> ReceiptVerification {
194    let resolver = RuntimeReceiptRefResolver::new(receipts);
195    let proof_contexts = RuntimeReceiptProofContextProvider::new(signature_policy);
196    verify_receipt_tree_proof_with_resolver(
197        root,
198        &resolver,
199        runtime_receipt_tree_config(config),
200        &proof_contexts,
201    )
202}
203
204fn runtime_receipt_path(index: usize) -> String {
205    format!("runtime_receipts[{index}]")
206}
207
208fn receipt_positions<'a>(
209    receipts: impl Iterator<Item = &'a Receipt>,
210) -> BTreeMap<String, Vec<usize>> {
211    let mut positions: BTreeMap<String, Vec<usize>> = BTreeMap::new();
212    for (index, receipt) in receipts.enumerate() {
213        positions
214            .entry(receipt.id.to_string())
215            .or_default()
216            .push(index);
217    }
218    positions
219}
220
221fn runtime_receipt_tree_config(mut config: ReceiptTreeConfig) -> ReceiptTreeConfig {
222    config.require_parent_links = true;
223    config
224}
225
226fn referenced_receipt_id(reference: &Reference) -> Option<&str> {
227    if reference.reference_type != ReferenceType::Receipt {
228        return None;
229    }
230    reference
231        .uri
232        .strip_prefix("runx:receipt:")
233        .filter(|id| !id.is_empty())
234}