Skip to main content

binius_circuits/bitcoin/
merkle_path.rs

1// Copyright 2025 Irreducible Inc.
2//! The merkle proof for a transaction in a Bitcoin block.
3
4use binius_core::Word;
5use binius_frontend::{CircuitBuilder, Wire};
6
7use super::double_sha256::double_sha256;
8
9/// Which side of a double-SHA256 pair a merkle sibling sits on.
10#[derive(Debug, Copy, Clone)]
11pub enum SiblingSide {
12	Left,
13	Right,
14}
15
16impl SiblingSide {
17	/// The wire value the side is passed to [`merkle_path`] as.
18	///
19	/// The side wire is read as a `select` condition, which tests the most significant bit, so
20	/// the two sides must be the all-zero and all-one words rather than `0` and `1`.
21	pub const fn to_word(self) -> Word {
22		match self {
23			SiblingSide::Left => Word::ZERO,
24			SiblingSide::Right => Word::ALL_ONE,
25		}
26	}
27}
28
29/// Returns the merkle root obtained by folding `leaf` with `siblings` under double-SHA256.
30///
31/// Every wire is Bitcoin little-endian packed (8 bytes per wire).
32///
33/// Each sibling carries a wire saying whether it is a left or a right sibling, in the encoding
34/// [`SiblingSide::to_word`] produces. `siblings.len()` is the maximal path length; only the first
35/// `length` siblings are folded in, and the remaining levels pass the running digest through
36/// unchanged.
37pub fn merkle_path(
38	builder: &CircuitBuilder,
39	mut leaf: [Wire; 4],
40	siblings: &[([Wire; 4], Wire)],
41	length: Wire,
42) -> [Wire; 4] {
43	for (i, (sibling, is_right)) in siblings.iter().enumerate() {
44		// The pair is hashed in path order: a right sibling is appended after the running
45		// digest, a left one is prepended.
46		let message: Vec<Wire> = (0..4)
47			.map(|j| builder.select(*is_right, leaf[j], sibling[j]))
48			.chain((0..4).map(|j| builder.select(*is_right, sibling[j], leaf[j])))
49			.collect();
50		let digest = double_sha256(builder, &message);
51
52		// Levels at or past `length` are padding: keep the digest reached so far.
53		let within_length = builder.icmp_ult(builder.add_constant_64(i as u64), length);
54		leaf = std::array::from_fn(|j| builder.select(within_length, digest[j], leaf[j]));
55	}
56	leaf
57}
58
59#[cfg(test)]
60mod tests {
61	use std::array;
62
63	use hex_literal::hex;
64
65	use super::*;
66
67	/// Builds a circuit asserting `merkle_path(leaf, siblings, length) == root`, then runs it.
68	fn check_merkle_path(
69		max_path_len: usize,
70		leaf_value: [u8; 32],
71		siblings_value: &[([u8; 32], SiblingSide)],
72		length_value: u64,
73		root_value: [u8; 32],
74	) -> anyhow::Result<()> {
75		let builder = CircuitBuilder::new();
76		let leaf: [Wire; 4] = array::from_fn(|_| builder.add_witness());
77		let siblings: Vec<([Wire; 4], Wire)> = std::iter::repeat_with(|| {
78			(array::from_fn(|_| builder.add_witness()), builder.add_witness())
79		})
80		.take(max_path_len)
81		.collect();
82		let root: [Wire; 4] = array::from_fn(|_| builder.add_witness());
83		let length = builder.add_witness();
84		builder.assert_eq_v("root", merkle_path(&builder, leaf, &siblings, length), root);
85		let circuit = builder.build();
86
87		let mut filler = circuit.new_witness_filler();
88		filler.pack_bytes_le(&leaf, &leaf_value);
89		for ((sibling, is_right), (value, side)) in siblings.iter().zip(siblings_value) {
90			filler.pack_bytes_le(sibling, value);
91			filler[*is_right] = side.to_word();
92		}
93		filler.pack_bytes_le(&root, &root_value);
94		filler[length] = Word(length_value);
95		circuit.populate_wire_witness(&mut filler)?;
96
97		let constraint_system = circuit.constraint_system();
98		constraint_system.verify(&filler.into_value_vec())?;
99		Ok(())
100	}
101
102	const LEAF: [u8; 32] = hex!("a2b6b171aae6007508e5c8fabec6b662bad3e4594e09405cac7b249e5f1e5155");
103	const ROOT: [u8; 32] = hex!("5802c63ef536216cf01a0dd0b32c01f5e31536aa773eb6e1d46fd42f66516eba");
104	const SIBLING_0: [u8; 32] =
105		hex!("1346be1a16a09b5fcc5bca52d39c2529396f0fa6a654f3978807ff79eaf91d66");
106	const SIBLING_1: [u8; 32] =
107		hex!("557cc3606e7197ff5a7b6cda46e409445b1ab58d8d4ebf1bc3d95764c32ad877");
108
109	#[test]
110	fn test_valid() {
111		let siblings = [
112			(SIBLING_0, SiblingSide::Right),
113			(SIBLING_1, SiblingSide::Left),
114		];
115		check_merkle_path(2, LEAF, &siblings, 2, ROOT).unwrap();
116	}
117
118	#[test]
119	fn test_invalid_side() {
120		// Flipping the second sibling's side reverses the order the pair is hashed in.
121		let siblings = [
122			(SIBLING_0, SiblingSide::Right),
123			(SIBLING_1, SiblingSide::Right),
124		];
125		check_merkle_path(2, LEAF, &siblings, 2, ROOT).unwrap_err();
126	}
127
128	#[test]
129	fn test_invalid_path() {
130		let wrong = hex!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
131		let siblings = [(SIBLING_0, SiblingSide::Right), (wrong, SiblingSide::Left)];
132		check_merkle_path(2, LEAF, &siblings, 2, ROOT).unwrap_err();
133	}
134
135	/// A 12-level path in a circuit sized for 30, so the 18 padding levels must pass through.
136	#[test]
137	fn test_valid_long() {
138		let leaf = hex!("6f2f044a225e8b293c6e54cf2771bf4d17ba8904b1f61cf9c392965dcbda0b83");
139		let root = hex!("fc01df2139954b36cebc3fa6fbf6a7160a67d34b67e5c4aa2a7ce46f5bb42a83");
140		let siblings = [
141			(
142				hex!("783089645b0bc42d44e9d6a7ea62adf7a8a2adc6b7f0173d663369217b771b86"),
143				SiblingSide::Right,
144			),
145			(
146				hex!("1eeeeb0cac1753a10ade3b34bd5bf0e005cdec82545abdafa38685c45e5f8ce5"),
147				SiblingSide::Right,
148			),
149			(
150				hex!("e0d7426d603f1a817938cf366c8933d32185625fc821e3b1e964cb5f8e421501"),
151				SiblingSide::Right,
152			),
153			(
154				hex!("7af6e333025422cf892198d216f146d70efe64119071ce0ee96fd195640230df"),
155				SiblingSide::Left,
156			),
157			(
158				hex!("d848bf00d7563a26c9a43ad8cc2fa558f6a299629be20a078a6b197dcf15fc31"),
159				SiblingSide::Right,
160			),
161			(
162				hex!("b643abf3df379ac748494a5eb3025299265fff543571f8b71935e533f672c9e8"),
163				SiblingSide::Right,
164			),
165			(
166				hex!("b07d3ebc129da3ae9d1b9daee64daf74f8504ca5f9194cd006edee48b1bf4d00"),
167				SiblingSide::Right,
168			),
169			(
170				hex!("4cd4173f585e793e48aa479269f38cd986b600c494135e9de33118a8e4ac03ed"),
171				SiblingSide::Right,
172			),
173			(
174				hex!("4b5e59b8d22762cfc2906fa597b29c7eab7cd52d4b0cea9269e84e2aebce4101"),
175				SiblingSide::Right,
176			),
177			(
178				hex!("2321cd016cb8f1a29f1bad981418bed2776bf61b1a729ca86a54f14790ce822b"),
179				SiblingSide::Right,
180			),
181			(
182				hex!("fecdc8a219a271a9a969fdebf38068ffeaf25b7af353ee99e759eb0d05604218"),
183				SiblingSide::Right,
184			),
185			(
186				hex!("1736c19cc6de7296453811916ddedba46c9bbd61a3450ad3dfb8bddb698b6ad0"),
187				SiblingSide::Right,
188			),
189		];
190		check_merkle_path(30, leaf, &siblings, 12, root).unwrap();
191	}
192}