Skip to main content

binius_iop/
merkle_channel.rs

1// Copyright 2026 The Binius Developers
2
3//! Channel abstraction for verifiers of protocols using Merkle commitments.
4//!
5//! This module provides the [`MerkleIPVerifierChannel`] trait, which extends
6//! [`IPVerifierChannel`] with the ability to receive Merkle commitments and openings of the
7//! committed leaves. Protocols like FRI and BaseFold interact with the prover through these
8//! operations instead of reading commitments and opening proofs from a transcript directly.
9//!
10//! The [`VerifierMerkleTranscriptChannel`] implementation wraps a [`VerifierTranscript`] and
11//! verifies openings with a [`BinaryMerkleTreeScheme`]: commitment roots are read as observed
12//! messages, while opening proofs are read as unobserved decommitment advice bound to the
13//! already-observed roots.
14
15use std::{borrow::BorrowMut, marker::PhantomData};
16
17use binius_core::word::Word;
18use binius_field::{BinaryField, Field};
19use binius_hash::HashSuite;
20use binius_ip::channel::{IPVerifierChannel, WordIPVerifierChannel};
21use binius_transcript::{VerifierTranscript, fiat_shamir::Challenger};
22use binius_utils::{DeserializeBytes, FixedSizeSerializeBytes};
23use digest::Output;
24
25use crate::{
26	channel::grinding::GrindingVerifierChannel,
27	merkle_tree::{BinaryMerkleTreeScheme, Commitment, MerkleTreeScheme},
28};
29
30/// An extension of [`WordIPVerifierChannel`] that can receive and open Merkle commitments.
31///
32/// Query indices are [`Self::Word`](WordIPVerifierChannel::Word)s, since a protocol samples them
33/// with [`WordIPVerifierChannel::sample_bits`] and a channel that builds a circuit carries them as
34/// wires.
35pub trait MerkleIPVerifierChannel<F: Field>: WordIPVerifierChannel<F> {
36	/// A Merkle commitment.
37	type Commitment: Clone;
38
39	/// Receives a Merkle commitment for a tree with the given depth and leaf size.
40	///
41	/// The leaves of the Merkle tree each contain exactly `leaf_size` field elements.
42	fn recv_merkle_commitment(
43		&mut self,
44		leaf_size: usize,
45		depth: usize,
46	) -> Result<Self::Commitment, Error>;
47
48	/// Receives a multi-opening of leaves, bound by a Merkle commitment.
49	///
50	/// Each commitment is associated with the `leaf_size` and `depth` requested when received with
51	/// [`Self::recv_merkle_commitment`]. All indices must be less than `2^depth`.
52	///
53	/// Returns `indices.len() * leaf_size` elements, where each chunk of `leaf_size`
54	/// contiguous elements corresponds to one provided index.
55	fn recv_openings(
56		&mut self,
57		commitment: &Self::Commitment,
58		indices: &[Self::Word],
59	) -> Result<Vec<Self::Elem>, Error>;
60
61	/// Receives the full committed vector, bound by a Merkle commitment.
62	///
63	/// Returns `leaf_size << depth` elements, in leaf order.
64	fn recv_committed_vector(
65		&mut self,
66		commitment: &Self::Commitment,
67	) -> Result<Vec<Self::Elem>, Error>;
68}
69
70/// A [`MerkleIPVerifierChannel`] over a [`VerifierTranscript`], verifying openings with a
71/// [`BinaryMerkleTreeScheme`].
72///
73/// The transcript is held through a [`BorrowMut`] bound, so the channel can own the transcript or
74/// mutably borrow one.
75pub struct VerifierMerkleTranscriptChannel<T, Challenger_, F, H: HashSuite> {
76	transcript: T,
77	scheme: BinaryMerkleTreeScheme<F, H>,
78	_challenger_marker: PhantomData<Challenger_>,
79}
80
81impl<T, Challenger_, F, H: HashSuite> VerifierMerkleTranscriptChannel<T, Challenger_, F, H> {
82	/// Constructs a channel over the transcript with a default Merkle tree scheme.
83	pub fn new(transcript: T) -> Self {
84		Self::with_scheme(transcript, BinaryMerkleTreeScheme::new())
85	}
86
87	/// Constructs a channel over the transcript with the given Merkle tree scheme.
88	pub const fn with_scheme(transcript: T, scheme: BinaryMerkleTreeScheme<F, H>) -> Self {
89		Self {
90			transcript,
91			scheme,
92			_challenger_marker: PhantomData,
93		}
94	}
95
96	/// Returns the wrapped transcript.
97	pub fn into_transcript(self) -> T {
98		self.transcript
99	}
100}
101
102/// A Merkle commitment received over a transcript channel.
103#[derive(Debug, Clone)]
104pub struct TranscriptMerkleCommitment<Digest> {
105	/// The commitment root and tree depth.
106	pub commitment: Commitment<Digest>,
107	/// The number of `F` elements in each leaf.
108	pub leaf_size: usize,
109}
110
111impl<F, T, Challenger_, H> IPVerifierChannel<F>
112	for VerifierMerkleTranscriptChannel<T, Challenger_, F, H>
113where
114	F: Field,
115	T: BorrowMut<VerifierTranscript<Challenger_>>,
116	Challenger_: Challenger,
117	H: HashSuite,
118{
119	type Elem = F;
120
121	fn recv_one(&mut self) -> Result<F, binius_ip::channel::Error> {
122		self.transcript.borrow_mut().recv_one()
123	}
124
125	fn recv_many(&mut self, n: usize) -> Result<Vec<F>, binius_ip::channel::Error> {
126		self.transcript.borrow_mut().recv_many(n)
127	}
128
129	fn recv_array<const N: usize>(&mut self) -> Result<[F; N], binius_ip::channel::Error> {
130		self.transcript.borrow_mut().recv_array()
131	}
132
133	fn sample(&mut self) -> F {
134		IPVerifierChannel::sample(self.transcript.borrow_mut())
135	}
136
137	fn observe_one(&mut self, val: F) -> F {
138		self.transcript.borrow_mut().observe_one(val)
139	}
140
141	fn observe_many(&mut self, vals: &[F]) -> Vec<F> {
142		self.transcript.borrow_mut().observe_many(vals)
143	}
144
145	fn assert_zero(&mut self, val: F) -> Result<(), binius_ip::channel::Error> {
146		self.transcript.borrow_mut().assert_zero(val)
147	}
148}
149
150impl<F, T, Challenger_, H> WordIPVerifierChannel<F>
151	for VerifierMerkleTranscriptChannel<T, Challenger_, F, H>
152where
153	F: BinaryField,
154	T: BorrowMut<VerifierTranscript<Challenger_>>,
155	Challenger_: Challenger,
156	H: HashSuite,
157{
158	type Word = Word;
159
160	fn observe_words(&mut self, words: &[Word]) -> Vec<Word> {
161		WordIPVerifierChannel::<F>::observe_words(self.transcript.borrow_mut(), words)
162	}
163
164	fn subset_sum(&mut self, elems: &[F], word: &Word) -> F {
165		WordIPVerifierChannel::<F>::subset_sum(self.transcript.borrow_mut(), elems, word)
166	}
167
168	fn select(&mut self, elems: &[F], word: &Word) -> F {
169		WordIPVerifierChannel::<F>::select(self.transcript.borrow_mut(), elems, word)
170	}
171
172	fn sample_bits(&mut self, bits: usize) -> Word {
173		WordIPVerifierChannel::<F>::sample_bits(self.transcript.borrow_mut(), bits)
174	}
175
176	fn pack_words(&mut self, words: &[Word]) -> Vec<F> {
177		WordIPVerifierChannel::<F>::pack_words(self.transcript.borrow_mut(), words)
178	}
179}
180
181impl<T, Challenger_, F, H: HashSuite> GrindingVerifierChannel
182	for VerifierMerkleTranscriptChannel<T, Challenger_, F, H>
183where
184	T: BorrowMut<VerifierTranscript<Challenger_>>,
185	Challenger_: Challenger,
186{
187	fn verify_grind(&mut self, bits: usize) -> Result<(), binius_transcript::Error> {
188		// Zero difficulty is not a grind, so the tape holds no nonce to read here.
189		if bits == 0 {
190			return Ok(());
191		}
192		self.transcript.borrow_mut().verify_grind(bits)?;
193		Ok(())
194	}
195}
196
197impl<F, T, Challenger_, H> MerkleIPVerifierChannel<F>
198	for VerifierMerkleTranscriptChannel<T, Challenger_, F, H>
199where
200	F: BinaryField + FixedSizeSerializeBytes,
201	T: BorrowMut<VerifierTranscript<Challenger_>>,
202	Challenger_: Challenger,
203	H: HashSuite,
204	Output<H::LeafHash>: DeserializeBytes,
205{
206	type Commitment = TranscriptMerkleCommitment<Output<H::LeafHash>>;
207
208	fn recv_merkle_commitment(
209		&mut self,
210		leaf_size: usize,
211		depth: usize,
212	) -> Result<Self::Commitment, Error> {
213		let root = self.transcript.borrow_mut().message().read()?;
214		Ok(TranscriptMerkleCommitment {
215			commitment: Commitment { root, depth },
216			leaf_size,
217		})
218	}
219
220	fn recv_openings(
221		&mut self,
222		commitment: &Self::Commitment,
223		indices: &[Word],
224	) -> Result<Vec<F>, Error> {
225		let tree_depth = commitment.commitment.depth;
226		let indices = indices
227			.iter()
228			.map(|index| index.as_u64() as usize)
229			.collect::<Vec<_>>();
230		assert!(indices.iter().all(|&index| index < 1 << tree_depth)); // precondition
231
232		// Read and verify the optimal internal layer once, then verify every opening against it.
233		let layer_depth = self.scheme.optimal_verify_layer(indices.len(), tree_depth);
234		let mut advice = self.transcript.borrow_mut().decommitment();
235		let layer_digests = advice.read_vec(1 << layer_depth)?;
236		self.scheme
237			.verify_layer(&commitment.commitment.root, layer_depth, &layer_digests)?;
238
239		let mut values = Vec::with_capacity(indices.len() * commitment.leaf_size);
240		for &index in &indices {
241			let leaf = advice.read_scalar_slice::<F>(commitment.leaf_size)?;
242			self.scheme.verify_opening(
243				index,
244				&leaf,
245				layer_depth,
246				tree_depth,
247				&layer_digests,
248				&mut advice,
249			)?;
250			values.extend_from_slice(&leaf);
251		}
252		Ok(values)
253	}
254
255	fn recv_committed_vector(&mut self, commitment: &Self::Commitment) -> Result<Vec<F>, Error> {
256		let len = commitment.leaf_size << commitment.commitment.depth;
257		let data = self
258			.transcript
259			.borrow_mut()
260			.decommitment()
261			.read_scalar_slice::<F>(len)?;
262		self.scheme
263			.verify_vector(&commitment.commitment.root, &data, commitment.leaf_size)?;
264		Ok(data)
265	}
266}
267
268/// Error type for Merkle channel operations.
269#[derive(Debug, thiserror::Error)]
270pub enum Error {
271	#[error("IP channel error: {0}")]
272	IPChannel(#[from] binius_ip::channel::Error),
273	#[error("Merkle tree error: {0}")]
274	MerkleTree(#[from] crate::merkle_tree::Error),
275	#[error("transcript error: {0}")]
276	Transcript(#[from] binius_transcript::Error),
277}