1use alloc::{collections::BTreeMap, vec::Vec};
40use codec::{Decode, DecodeAll, DecodeWithMemTracking, Encode};
41use polkadot_core_primitives::Hash;
42use sp_core::ConstU32;
43use sp_runtime::{traits::Hash as HashT, BoundedVec, Digest, DigestItem};
44
45use crate::{
46 stream::StreamId, SpecHasher, SPMS_ENGINE_ID, STREAM_ID_LEN, TREE_INNER_TAG, TREE_LEAF_TAG,
47};
48
49const KEY_BITS: usize = STREAM_ID_LEN * 8;
51
52pub use polkadot_primitives::StreamsRoot;
54
55#[derive(
58 Clone, Copy, Encode, Decode, DecodeWithMemTracking, PartialEq, Eq, Debug, scale_info::TypeInfo,
59)]
60pub struct TreeStep {
61 pub split_bit: u8,
64 pub sibling: Hash,
65}
66
67#[derive(
70 Clone, Encode, Decode, DecodeWithMemTracking, PartialEq, Eq, Debug, scale_info::TypeInfo,
71)]
72pub struct StreamProof {
73 pub steps: BoundedVec<TreeStep, ConstU32<{ KEY_BITS as u32 }>>,
76}
77
78fn bit(key: &[u8; STREAM_ID_LEN], i: usize) -> bool {
80 (key[i >> 3] >> (7 - (i & 7))) & 1 == 1
81}
82
83fn first_diverging_bit(a: &[u8; STREAM_ID_LEN], b: &[u8; STREAM_ID_LEN]) -> usize {
85 (u64::from_be_bytes(*a) ^ u64::from_be_bytes(*b)).leading_zeros() as usize
86}
87
88fn hash_leaf(key: &[u8; STREAM_ID_LEN], stream_root: &Hash) -> Hash {
89 let mut pre = [0u8; 1 + STREAM_ID_LEN + 32];
90 pre[0] = TREE_LEAF_TAG;
91 pre[1..1 + STREAM_ID_LEN].copy_from_slice(key);
92 pre[1 + STREAM_ID_LEN..].copy_from_slice(stream_root.as_bytes());
93 <SpecHasher as HashT>::hash(&pre)
94}
95
96fn hash_inner(split_bit: u8, left: &Hash, right: &Hash) -> Hash {
97 let mut pre = [0u8; 1 + 1 + 32 + 32];
98 pre[0] = TREE_INNER_TAG;
99 pre[1] = split_bit;
100 pre[2..34].copy_from_slice(left.as_bytes());
101 pre[34..].copy_from_slice(right.as_bytes());
102 <SpecHasher as HashT>::hash(&pre)
103}
104
105fn split<'a>(
109 entries: &'a [([u8; STREAM_ID_LEN], Hash)],
110) -> (usize, &'a [([u8; STREAM_ID_LEN], Hash)], &'a [([u8; STREAM_ID_LEN], Hash)]) {
111 let split_bit = first_diverging_bit(&entries[0].0, &entries[entries.len() - 1].0);
112 let p = entries.partition_point(|(k, _)| !bit(k, split_bit));
113 let (left, right) = entries.split_at(p);
114 (split_bit, left, right)
115}
116
117fn node_hash(entries: &[([u8; STREAM_ID_LEN], Hash)]) -> Hash {
119 if let [(key, root)] = entries {
120 return hash_leaf(key, root);
121 }
122 let (split_bit, left, right) = split(entries);
123 hash_inner(split_bit as u8, &node_hash(left), &node_hash(right))
124}
125
126fn keyed(entries: &BTreeMap<StreamId, Hash>) -> Vec<([u8; STREAM_ID_LEN], Hash)> {
131 let mut keyed: Vec<_> =
132 entries.iter().map(|(stream, root)| (stream.to_bytes(), *root)).collect();
133 keyed.sort_unstable_by_key(|(key, _)| *key);
134 keyed
135}
136
137pub fn streams_root(entries: &BTreeMap<StreamId, Hash>) -> Option<StreamsRoot> {
144 let entries = keyed(entries);
145 if entries.is_empty() {
146 return None;
147 }
148 Some(StreamsRoot(node_hash(&entries)))
149}
150
151fn prove(
154 entries: &[([u8; STREAM_ID_LEN], Hash)],
155 key: &[u8; STREAM_ID_LEN],
156 steps: &mut Vec<TreeStep>,
157) -> Hash {
158 if let [(k, root)] = entries {
159 return hash_leaf(k, root);
160 }
161 let (split_bit, left, right) = split(entries);
162 let target_right = bit(key, split_bit);
163 let (sibling, descend) =
164 if target_right { (node_hash(left), right) } else { (node_hash(right), left) };
165 steps.push(TreeStep { split_bit: split_bit as u8, sibling });
166 let child = prove(descend, key, steps);
167 if target_right {
168 hash_inner(split_bit as u8, &sibling, &child)
169 } else {
170 hash_inner(split_bit as u8, &child, &sibling)
171 }
172}
173
174pub fn gen_stream_proof(
177 entries: &BTreeMap<StreamId, Hash>,
178 stream: StreamId,
179) -> Option<(StreamsRoot, StreamProof)> {
180 if !entries.contains_key(&stream) {
181 return None;
182 }
183 let entries = keyed(entries);
184 let key = stream.to_bytes();
185 let mut steps = Vec::new();
186 let root = prove(&entries, &key, &mut steps);
187 steps.reverse();
189 let steps = BoundedVec::try_from(steps).expect("trie depth <= KEY_BITS; qed");
190 Some((StreamsRoot(root), StreamProof { steps }))
191}
192
193pub fn read_streams_root(digest: &Digest) -> Option<StreamsRoot> {
197 let mut items = digest.logs().iter().filter_map(|item| match item {
198 DigestItem::Consensus(id, payload) if *id == SPMS_ENGINE_ID => Some(payload),
199 _ => None,
200 });
201 let payload = items.next()?;
202 if items.next().is_some() {
203 return None;
204 }
205 StreamsRoot::decode_all(&mut &payload[..]).ok()
206}
207
208pub fn streams_root_from_proof(
214 stream: StreamId,
215 stream_root: Hash,
216 membership: &StreamProof,
217) -> Option<StreamsRoot> {
218 let key = stream.to_bytes();
219 let mut node = hash_leaf(&key, &stream_root);
220 let mut prev: Option<u8> = None;
224 for step in membership.steps.iter() {
225 let split_bit = step.split_bit as usize;
226 if split_bit >= KEY_BITS {
228 return None;
229 }
230 if prev.is_some_and(|p| step.split_bit >= p) {
231 return None;
232 }
233 prev = Some(step.split_bit);
234 node = if bit(&key, split_bit) {
236 hash_inner(step.split_bit, &step.sibling, &node)
237 } else {
238 hash_inner(step.split_bit, &node, &step.sibling)
239 };
240 }
241 Some(StreamsRoot(node))
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247 use crate::test_utils::ch;
248 use sp_core::H256;
249
250 fn root(b: u8) -> Hash {
251 H256::repeat_byte(b)
252 }
253
254 fn verify_stream_membership(
256 streams_root: StreamsRoot,
257 stream: StreamId,
258 stream_root: Hash,
259 membership: &StreamProof,
260 ) -> bool {
261 streams_root_from_proof(stream, stream_root, membership) == Some(streams_root)
262 }
263 fn entries() -> BTreeMap<StreamId, Hash> {
264 BTreeMap::from([
266 (ch(4000), root(0xB)),
267 (ch(2000), root(0xA)),
268 (ch(3000), root(0xC)),
269 (StreamId::Ack { recipient: 2000.into(), domain: 0, num: 0 }, root(0xD)),
270 (StreamId::Broadcast { domain: 1, subdomain: 0, num: 7 }, root(0xE)),
271 ])
272 }
273
274 #[test]
275 fn streams_root_frozen_vector() {
276 assert_eq!(
279 streams_root(&entries()).unwrap(),
280 StreamsRoot(H256(hex_literal::hex!(
281 "12de235fb6d96933d6ba770efe16f891c87fb8a88a75e37a384b88ba11dee0dc"
282 ))),
283 );
284 }
285
286 #[test]
287 fn round_trip_membership_verifies() {
288 for (stream, sr) in entries() {
289 let (r, proof) = gen_stream_proof(&entries(), stream).unwrap();
290 assert_eq!(Some(r), streams_root(&entries()));
291 assert!(verify_stream_membership(r, stream, sr, &proof), "{stream:?} must verify");
292 }
293 }
294
295 #[test]
296 fn wrong_root_or_stream_or_value_fails() {
297 let (r, proof) = gen_stream_proof(&entries(), ch(3000)).unwrap();
298 assert!(!verify_stream_membership(r, ch(3000), root(0xFF), &proof));
300 assert!(!verify_stream_membership(r, ch(2000), root(0xC), &proof));
302 assert!(!verify_stream_membership(StreamsRoot(root(0x00)), ch(3000), root(0xC), &proof));
304 }
305
306 #[test]
307 fn proof_is_not_transferable_between_streams() {
308 let (r, proof) = gen_stream_proof(&entries(), ch(4000)).unwrap();
310 for (other, other_root) in entries() {
311 if other == ch(4000) {
312 continue;
313 }
314 assert!(
315 !verify_stream_membership(r, other, other_root, &proof),
316 "proof for ch(4000) must not verify as {other:?}"
317 );
318 }
319 }
320
321 #[test]
322 fn order_independent_root() {
323 let reversed: BTreeMap<_, _> = entries().into_iter().rev().collect();
325 assert_eq!(streams_root(&entries()), streams_root(&reversed));
326 }
327
328 #[test]
329 fn single_stream_tree_has_empty_proof() {
330 let one = BTreeMap::from([(ch(2000), root(0xA))]);
331 let (r, proof) = gen_stream_proof(&one, ch(2000)).unwrap();
332 assert!(proof.steps.is_empty());
333 assert_eq!(Some(r), streams_root(&one));
334 assert!(verify_stream_membership(r, ch(2000), root(0xA), &proof));
335 }
336
337 #[test]
338 fn proof_is_logarithmic() {
339 let many: BTreeMap<_, _> = (0..64u32).map(|i| (ch(2000 + i), root(i as u8))).collect();
341 let (_r, proof) = gen_stream_proof(&many, ch(2000 + 33)).unwrap();
342 assert!(proof.steps.len() <= 7, "log2(64) = 6-ish, got {}", proof.steps.len());
343 }
344
345 #[test]
346 fn absent_stream_has_no_proof() {
347 assert!(gen_stream_proof(&entries(), ch(9999)).is_none());
348 }
349
350 #[test]
351 fn out_of_range_split_bit_is_rejected() {
352 let (r, mut proof) = gen_stream_proof(&entries(), ch(3000)).unwrap();
353 let mut steps = vec![TreeStep { split_bit: 64, sibling: root(0) }];
356 steps.extend(proof.steps.iter().copied());
357 proof.steps = steps.try_into().unwrap();
358 assert!(!verify_stream_membership(r, ch(3000), root(0xC), &proof));
359 }
360
361 #[test]
362 fn over_long_proof_is_rejected_at_decode() {
363 let raw = vec![TreeStep { split_bit: 0, sibling: root(0) }; KEY_BITS + 1].encode();
365 assert!(StreamProof::decode(&mut &raw[..]).is_err());
366 }
367
368 #[test]
369 fn non_decreasing_step_order_is_rejected() {
370 let (r, proof) = gen_stream_proof(&entries(), ch(3000)).unwrap();
372 assert!(proof.steps.len() >= 2, "need a multi-step proof");
373 let reversed: Vec<TreeStep> = proof.steps.iter().rev().copied().collect();
374 let bad = StreamProof { steps: reversed.try_into().unwrap() };
375 assert!(!verify_stream_membership(r, ch(3000), root(0xC), &bad));
376 }
377
378 #[test]
379 fn read_streams_root_extracts_digest() {
380 let r = streams_root(&entries()).unwrap();
381 let digest = Digest {
382 logs: vec![
383 DigestItem::Other(vec![1, 2, 3]),
384 DigestItem::Consensus(SPMS_ENGINE_ID, r.encode()),
385 ],
386 };
387 assert_eq!(read_streams_root(&digest), Some(r));
388 assert_eq!(read_streams_root(&Digest { logs: vec![] }), None);
389 }
390
391 #[test]
392 fn read_streams_root_accepts_only_the_canonical_digest() {
393 let r = streams_root(&entries()).unwrap();
394 let spms = |payload: Vec<u8>| DigestItem::Consensus(SPMS_ENGINE_ID, payload);
395 let digest = |logs: Vec<DigestItem>| Digest { logs };
396
397 let mut trailing = r.encode();
399 trailing.push(0);
400 assert_eq!(read_streams_root(&digest(vec![spms(trailing)])), None);
401 assert_eq!(read_streams_root(&digest(vec![spms(r.encode()[..31].to_vec())])), None);
402
403 let other = StreamsRoot(Hash::repeat_byte(0xAB));
405 assert_eq!(read_streams_root(&digest(vec![spms(r.encode()), spms(other.encode())])), None);
406 assert_eq!(read_streams_root(&digest(vec![spms(vec![0; 3]), spms(r.encode())])), None);
407
408 let foreign = DigestItem::Consensus(*b"AURA", vec![0; 32]);
410 assert_eq!(read_streams_root(&digest(vec![foreign, spms(r.encode())])), Some(r));
411 }
412}