runx_runtime/receipts/
tree.rs1use 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}