referrerpolicy=no-referrer-when-downgrade

cumulus_primitives_spec_messaging/
mmr.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Cumulus.
3// SPDX-License-Identifier: Apache-2.0
4
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// 	http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17//! MMR primitives for the Speculative Messaging protocol.
18//!
19//! Hashing is [`SpecHasher`] with domain tags: leaves by [`crate::message::leaf_hash`]
20//! (`LEAF_TAG`), inner nodes by [`SpecMerge::merge`] (`INNER_TAG`), peak bagging by
21//! [`SpecMerge::merge_peaks`] (`PEAK_TAG`). `mmr_lib` calls `merge` for tree nodes and
22//! `merge_peaks` when bagging, so no two roles collide. The stream root binds the leaf count over
23//! the bagged peaks (`ROOT_TAG`, [`MmrRoot::from_bag`]).
24//!
25//! [`SpecMerge`] is the `mmr_lib::Merge` for inclusion proofs (`gen_proof` / `MerkleProof::verify`)
26//! and ancestry proofs (`gen_ancestry_proof` / `verify_incremental`). [`MmrFrontier`] is the
27//! peaks-only state: the sender's on-chain accumulator, and what proofs extend from.
28
29use alloc::vec::Vec;
30use codec::{Decode, DecodeWithMemTracking, Encode, Error as CodecError, Input, MaxEncodedLen};
31use mmr_lib::{ancestry_proof::bagging_peaks_hashes, Error as MmrError, Merge};
32use polkadot_core_primitives::Hash;
33use scale_info::TypeInfo;
34use sp_core::ConstU32;
35use sp_runtime::{traits::Hash as HashT, BoundedVec};
36
37use crate::{SpecHasher, INNER_TAG, PEAK_TAG, ROOT_TAG};
38
39/// Upper bound on any leaf count node positions are derived from; `mmr_lib`'s node count overflows
40/// `u64` above 2^63. Peer-supplied counts are checked against this first.
41pub const MAX_MMR_LEAF_COUNT: u64 = 1 << 48;
42
43/// Upper bound on a peak set or other hash list that a leaf count determines: one per bit of a
44/// `u64`.
45pub const MAX_MMR_HASHES: u32 = 64;
46
47/// A leaf position within a stream's MMR.
48#[derive(
49	Clone,
50	Copy,
51	Encode,
52	Decode,
53	DecodeWithMemTracking,
54	MaxEncodedLen,
55	PartialEq,
56	Eq,
57	PartialOrd,
58	Ord,
59	Debug,
60	Default,
61	TypeInfo,
62)]
63pub struct MessagePosition(pub u64);
64
65/// Domain-tagged `mmr_lib::Merge` for the speculative-messaging MMR.
66pub struct SpecMerge;
67
68impl Merge for SpecMerge {
69	type Item = Hash;
70
71	fn merge(left: &Hash, right: &Hash) -> Result<Hash, MmrError> {
72		Ok(tagged_node(INNER_TAG, left, right))
73	}
74
75	/// Tagged apart from `merge`, so a bagged value can never be read as an inner node.
76	fn merge_peaks(left: &Hash, right: &Hash) -> Result<Hash, MmrError> {
77		Ok(tagged_node(PEAK_TAG, left, right))
78	}
79}
80
81fn tagged_node(tag: u8, left: &Hash, right: &Hash) -> Hash {
82	let mut preimage = [0u8; 1 + 32 + 32];
83	preimage[0] = tag;
84	preimage[1..33].copy_from_slice(left.as_bytes());
85	preimage[33..65].copy_from_slice(right.as_bytes());
86	<SpecHasher as HashT>::hash(&preimage)
87}
88
89/// The root of an empty MMR.
90pub fn empty_root() -> MmrRoot {
91	MmrRoot::from_bag(0, Hash::zero())
92}
93
94/// Bag the peaks (highest to lowest) with `mmr_lib`'s own bagging, so the result equals
95/// `MMR::get_root`. Not a stream root: [`MmrRoot::from_bag`] binds the leaf count over it. No peaks
96/// bag to zero.
97pub fn bag_peaks(peaks: &[Hash]) -> Hash {
98	// `SpecMerge` is infallible, so `Err` means no peaks.
99	bagging_peaks_hashes::<Hash, SpecMerge>(peaks.to_vec()).unwrap_or_default()
100}
101
102/// [`MmrFrontier::try_append`] refused a leaf: the frontier already holds [`MAX_MMR_LEAF_COUNT`].
103#[derive(Clone, Copy, PartialEq, Eq, Debug)]
104pub struct FrontierFull;
105
106/// A stream's committed root at some point in its history: the leaf count bound over the bagged
107/// peaks. A newtype so it cannot be confused with a `StreamsRoot`, the commitment-tree root.
108#[derive(Clone, Copy, Encode, Decode, DecodeWithMemTracking, PartialEq, Eq, Debug, TypeInfo)]
109pub struct MmrRoot(pub Hash);
110
111impl MmrRoot {
112	/// Peak hashes do not commit their heights, so the same peaks bag identically at many leaf
113	/// counts; binding the count makes a forged count, and so a forged position, yield a root
114	/// nothing commits to.
115	pub fn from_bag(leaf_count: u64, bag: Hash) -> Self {
116		let mut preimage = [0u8; 1 + 8 + 32];
117		preimage[0] = ROOT_TAG;
118		preimage[1..9].copy_from_slice(&leaf_count.to_le_bytes());
119		preimage[9..41].copy_from_slice(bag.as_bytes());
120		Self(<SpecHasher as HashT>::hash(&preimage))
121	}
122}
123
124/// A peaks-only MMR frontier: the O(log n) state a stream continues from, and the accumulator that
125/// continues it. `leaf_count` fixes where an extension proof's connecting nodes go.
126///
127/// Peak hashes equal `mmr_lib`'s, so [`root`](Self::root) is `mmr_lib::MMR::get_root` bound to the
128/// leaf count, and proofs from a full `mmr_lib::MMR` over the same leaves verify against it.
129///
130/// Fields are private. [`new`](Self::new), [`from_parts`](Self::from_parts) and `Decode` are the
131/// only constructors, and each enforces one peak per set bit of `leaf_count` and `leaf_count <=
132/// MAX_MMR_LEAF_COUNT`.
133///
134/// Encodes as `leaf_count ‖ peaks`. A decoder reads the count first, and the `BoundedVec` rejects
135/// an over-long length prefix before any peak is read.
136#[derive(Clone, Encode, PartialEq, Eq, Debug, TypeInfo, Default)]
137pub struct MmrFrontier {
138	/// Number of leaves these peaks summarize; also the position of the next message to append.
139	leaf_count: u64,
140	/// MMR peaks, highest to lowest. At most [`MAX_MMR_HASHES`].
141	peaks: BoundedVec<Hash, ConstU32<MAX_MMR_HASHES>>,
142}
143
144impl MmrFrontier {
145	pub fn new() -> Self {
146		Self::default()
147	}
148
149	/// A frontier from `(peaks, leaf_count)`. `None` unless there is one peak per set bit of
150	/// `leaf_count` and `leaf_count <= MAX_MMR_LEAF_COUNT`.
151	pub fn from_parts(peaks: Vec<Hash>, leaf_count: u64) -> Option<Self> {
152		if leaf_count > MAX_MMR_LEAF_COUNT || peaks.len() != leaf_count.count_ones() as usize {
153			return None;
154		}
155		// ≤ 64 is implied by the set-bit count of a `u64`, so this cannot fail.
156		BoundedVec::try_from(peaks).ok().map(|peaks| Self { leaf_count, peaks })
157	}
158
159	/// The peaks, highest to lowest.
160	pub fn peaks(&self) -> &[Hash] {
161		&self.peaks
162	}
163
164	pub fn leaf_count(&self) -> u64 {
165		self.leaf_count
166	}
167
168	/// Append a leaf, merging equal-height peaks as `mmr_lib` does. At [`MAX_MMR_LEAF_COUNT`] the
169	/// leaf is refused and the frontier left unchanged, so every frontier still decodes.
170	pub fn try_append(&mut self, leaf: Hash) -> Result<(), FrontierFull> {
171		if self.leaf_count >= MAX_MMR_LEAF_COUNT {
172			return Err(FrontierFull);
173		}
174		let mut node = leaf;
175		// Going from `leaf_count` to `leaf_count + 1` merges exactly
176		// `trailing_zeros(leaf_count + 1)` pairs of peaks (binary-counter carry).
177		let merges = (self.leaf_count + 1).trailing_zeros();
178		for _ in 0..merges {
179			let left = self
180				.peaks
181				.pop()
182				.expect("the constructors enforce one peak per set bit of `leaf_count`; qed");
183			node = <SpecMerge as Merge>::merge(&left, &node).expect("SpecMerge is infallible; qed");
184		}
185		self.peaks
186			.try_push(node)
187			.expect("a `u64` leaf count never has more than 64 set bits; qed");
188		self.leaf_count += 1;
189		Ok(())
190	}
191
192	/// [`try_append`](Self::try_append) for a frontier this chain built itself, from the empty
193	/// frontier one leaf at a time.
194	///
195	/// # Panics
196	///
197	/// At [`MAX_MMR_LEAF_COUNT`], which no stream reaches: `2^48` leaves are beyond any chain's
198	/// lifetime. Never call it on a frontier built from untrusted parts.
199	pub fn append(&mut self, leaf: Hash) {
200		self.try_append(leaf)
201			.expect("a frontier built leaf by leaf never reaches `MAX_MMR_LEAF_COUNT`; qed")
202	}
203
204	/// The root: the leaf count bound over the bagged peaks ([`MmrRoot::from_bag`]). The empty
205	/// frontier bags to zero ([`empty_root`]), so a frontier that has consumed nothing compares and
206	/// extends like any other.
207	pub fn root(&self) -> MmrRoot {
208		MmrRoot::from_bag(self.leaf_count, bag_peaks(&self.peaks))
209	}
210
211	/// The `mmr_lib` node count (size) of this frontier's MMR. Total under the leaf-count
212	/// invariant.
213	pub(crate) fn mmr_size(&self) -> u64 {
214		mmr_size(self.leaf_count)
215	}
216}
217
218/// The `mmr_lib` node count (size) of an MMR with `leaf_count` leaves. Total up to
219/// [`MAX_MMR_LEAF_COUNT`].
220pub(crate) fn mmr_size(leaf_count: u64) -> u64 {
221	if leaf_count == 0 {
222		0
223	} else {
224		mmr_lib::leaf_index_to_mmr_size(leaf_count - 1)
225	}
226}
227
228/// Field order matches the derived `Encode`. The decoded pair goes through
229/// [`from_parts`](MmrFrontier::from_parts), so a frontier off the wire holds the same invariants as
230/// one built here.
231impl Decode for MmrFrontier {
232	fn decode<I: Input>(input: &mut I) -> Result<Self, CodecError> {
233		let leaf_count = u64::decode(input)?;
234		let peaks = BoundedVec::<Hash, ConstU32<MAX_MMR_HASHES>>::decode(input)?;
235		Self::from_parts(peaks.into_inner(), leaf_count)
236			.ok_or_else(|| "MmrFrontier: inconsistent peaks / leaf count".into())
237	}
238}
239
240impl DecodeWithMemTracking for MmrFrontier {}
241
242#[cfg(test)]
243mod tests {
244	use super::*;
245	use mmr_lib::{
246		leaf_index_to_pos,
247		util::{MemMMR, MemStore},
248		MerkleProof,
249	};
250	fn h(byte: u8) -> Hash {
251		Hash::repeat_byte(byte)
252	}
253
254	#[test]
255	fn merge_and_merge_peaks_are_domain_separated() {
256		let a = h(1);
257		let b = h(2);
258		// Inner-node merge and peak-bagging of the same inputs must differ (domain tags).
259		assert_ne!(
260			<SpecMerge as Merge>::merge(&a, &b).unwrap(),
261			<SpecMerge as Merge>::merge_peaks(&a, &b).unwrap()
262		);
263	}
264
265	#[test]
266	fn merge_is_order_sensitive() {
267		let a = h(1);
268		let b = h(2);
269		assert_ne!(
270			<SpecMerge as Merge>::merge(&a, &b).unwrap(),
271			<SpecMerge as Merge>::merge(&b, &a).unwrap()
272		);
273	}
274
275	#[test]
276	fn from_parts_enforces_append_invariants() {
277		// Consistent pairs round-trip, including the empty accumulator.
278		assert!(MmrFrontier::from_parts(Vec::new(), 0).is_some());
279		assert!(MmrFrontier::from_parts(vec![h(1)], 1).is_some());
280		assert!(MmrFrontier::from_parts(vec![h(1), h(2)], 3).is_some());
281		// One peak per set bit, or `append` would pop from an empty vec.
282		assert!(MmrFrontier::from_parts(Vec::new(), 1).is_none());
283		assert!(MmrFrontier::from_parts(vec![h(1)], 3).is_none());
284		assert!(MmrFrontier::from_parts(vec![h(1), h(2)], 1).is_none());
285		// And within the ceiling, or the node-position arithmetic downstream is not total.
286		assert!(MmrFrontier::from_parts(vec![h(1)], MAX_MMR_LEAF_COUNT).is_some());
287		assert!(MmrFrontier::from_parts(vec![h(1)], 1 << 63).is_none());
288		assert!(MmrFrontier::from_parts(vec![h(1); MAX_MMR_HASHES as usize], u64::MAX).is_none());
289	}
290
291	#[test]
292	fn accumulator_root_matches_mmr_lib() {
293		// Build via the peaks-only accumulator and via a full mmr_lib MMR; the
294		// roots and peak-count invariant must agree.
295		let mut acc = MmrFrontier::new();
296		let store = MemStore::<Hash>::default();
297		let mut reference = MemMMR::<Hash, SpecMerge>::new(0, &store);
298		for i in 1..=5u8 {
299			acc.append(h(i));
300			reference.push(h(i)).unwrap();
301			assert_eq!(acc.peaks().len() as u32, acc.leaf_count().count_ones());
302		}
303		assert_eq!(acc.root(), MmrRoot::from_bag(5, reference.get_root().unwrap()));
304	}
305
306	#[test]
307	fn empty_accumulator_root_is_empty_root() {
308		// A fresh accumulator is valid, and its root is the defined constant, not a panic in
309		// `bag_peaks`.
310		assert_eq!(bag_peaks(&[]), Hash::zero());
311		assert_eq!(MmrFrontier::new().root(), empty_root());
312		assert_eq!(MmrFrontier::from_parts(Vec::new(), 0).unwrap().root(), empty_root());
313	}
314
315	#[test]
316	fn decode_enforces_the_same_invariants_as_from_parts() {
317		// A frontier off the wire goes through `from_parts`, so it holds the invariants `append`
318		// and the position arithmetic rely on. Consistent bytes round-trip;
319		let good = MmrFrontier::from_parts(vec![h(1), h(2)], 3).unwrap();
320		assert_eq!(MmrFrontier::decode(&mut &good.encode()[..]).unwrap(), good);
321		// … inconsistent ones are rejected at decode, not later in a pop or a multiply.
322		let bad_shape = (3u64, vec![h(1)]).encode();
323		assert!(MmrFrontier::decode(&mut &bad_shape[..]).is_err());
324		let too_large = (1u64 << 63, vec![h(1)]).encode();
325		assert!(MmrFrontier::decode(&mut &too_large[..]).is_err());
326		// The bound is checked at the length prefix: 65 peaks are refused before being read.
327		let too_many = (u64::MAX, vec![h(1); 65]).encode();
328		assert!(MmrFrontier::decode(&mut &too_many[..]).is_err());
329		// And the wire order is the design's: `leaf_count` first.
330		assert_eq!(good.encode(), (3u64, vec![h(1), h(2)]).encode());
331	}
332
333	#[test]
334	fn empty_root_frozen_vector() {
335		// FROZEN consensus vector: blake2b-256 of `ROOT_TAG = 0x04`, a zero `u64` count and a zero
336		// bag, the defined root of an empty frontier.
337		assert_eq!(
338			empty_root().0,
339			Hash::from(hex_literal::hex!(
340				"3efd2c3528b8f163c2e2349211af73f86181b748be1b6a5eebfbadc9428643ce"
341			))
342		);
343	}
344
345	#[test]
346	fn mmr_root_frozen_vector() {
347		// Frozen consensus vector for tags `LEAF/INNER/PEAK/ROOT = 0x1/0x2/0x3/0x4`: a change to
348		// the tags or the bagging order is a consensus break and fails this.
349		let mut acc = MmrFrontier::new();
350		for i in 1..=5u8 {
351			acc.append(h(i));
352		}
353		assert_eq!(
354			acc.root().0,
355			Hash::from(hex_literal::hex!(
356				"1190cb02577a8bafec9f05c38e64ca9155b65e9d862604c4f3d920dff4e5a85f"
357			))
358		);
359	}
360
361	#[test]
362	fn mmr_root_frozen_vectors_at_64_and_65_leaves() {
363		// Frozen vectors, computed by an independent Python model: 64 leaves make one peak, 65 add
364		// a second to bag.
365		let mut acc = MmrFrontier::new();
366		for i in 1..=64u8 {
367			acc.append(h(i));
368		}
369		assert_eq!(acc.peaks().len(), 1);
370		assert_eq!(
371			acc.root().0,
372			Hash::from(hex_literal::hex!(
373				"41cec1de3c0e0af93a02932404fc195f0fbb56f542abbce0681b4632da0848c2"
374			))
375		);
376		acc.append(h(65));
377		assert_eq!(acc.peaks().len(), 2);
378		assert_eq!(
379			acc.root().0,
380			Hash::from(hex_literal::hex!(
381				"109ccf71e0b753f6ff76d7f48e59f6376c1347bea4556b20c4a453a2cb8ca28d"
382			))
383		);
384	}
385
386	#[test]
387	fn no_append_carries_a_frontier_past_the_ceiling() {
388		// A frontier built from untrusted parts can sit at the ceiling. Its next leaf is refused
389		// and the frontier left as it was, so it still decodes; one below, the last leaf fits.
390		let mut full = MmrFrontier::from_parts(vec![h(1)], MAX_MMR_LEAF_COUNT).unwrap();
391		assert_eq!(full.try_append(h(2)), Err(FrontierFull));
392		assert_eq!(full.leaf_count(), MAX_MMR_LEAF_COUNT);
393		assert_eq!(MmrFrontier::decode(&mut &full.encode()[..]).unwrap(), full);
394
395		let below = MAX_MMR_LEAF_COUNT - 1;
396		let mut last =
397			MmrFrontier::from_parts(vec![h(1); below.count_ones() as usize], below).unwrap();
398		assert_eq!(last.try_append(h(2)), Ok(()));
399		assert_eq!(last.leaf_count(), MAX_MMR_LEAF_COUNT);
400		assert_eq!(MmrFrontier::decode(&mut &last.encode()[..]).unwrap(), last);
401	}
402
403	#[test]
404	fn inclusion_proof_round_trips() {
405		let store = MemStore::<Hash>::default();
406		let mut mmr = MemMMR::<Hash, SpecMerge>::new(0, &store);
407		let positions: Vec<u64> = (0..6u8).map(|i| mmr.push(h(i)).unwrap()).collect();
408		let root = mmr.get_root().unwrap();
409
410		let proof: MerkleProof<Hash, SpecMerge> =
411			mmr.gen_proof(vec![leaf_index_to_pos(1), leaf_index_to_pos(4)]).unwrap();
412
413		assert!(proof.verify(root, vec![(positions[1], h(1)), (positions[4], h(4))]).unwrap());
414		assert!(!proof.verify(root, vec![(positions[1], h(99)), (positions[4], h(4))]).unwrap());
415	}
416}