mithril_stm/circuits/halo2/
adapters.rs

1//! Adapters for converting STM-side structures to Halo2 circuit witness structures.
2
3use digest::Digest;
4use thiserror::Error;
5
6use crate::circuits::halo2::types::{MerklePath as Halo2MerklePath, Position};
7use crate::membership_commitment::MerklePath as StmMerklePath;
8use crate::signature_scheme::BaseFieldElement;
9
10/// Errors returned when adapting STM Merkle paths to Halo2 witness paths.
11#[derive(Debug, Error)]
12pub enum MerklePathAdapterError {
13    #[error("invalid merkle digest length")]
14    InvalidDigestLength,
15    #[error("non-canonical merkle digest")]
16    NonCanonicalDigest,
17}
18
19impl<D: Digest> TryFrom<&StmMerklePath<D>> for Halo2MerklePath {
20    type Error = MerklePathAdapterError;
21
22    fn try_from(stm_path: &StmMerklePath<D>) -> Result<Self, Self::Error> {
23        let mut siblings = Vec::with_capacity(stm_path.values.len());
24
25        for (i, value) in stm_path.values.iter().enumerate() {
26            let bytes: [u8; 32] = value
27                .as_slice()
28                .try_into()
29                .map_err(|_| MerklePathAdapterError::InvalidDigestLength)?;
30            let node = BaseFieldElement::from_bytes(&bytes)
31                .ok()
32                .map(|base| base.into())
33                .ok_or(MerklePathAdapterError::NonCanonicalDigest)?;
34            let bit = (stm_path.index >> i) & 1;
35            // At level `i`, `bit = (index >> i) & 1`: `0` means current is left, `1` means right.
36            // STM uses `H(current || sibling)` for `bit == 0`, else `H(sibling || current)`.
37            // Map `0 -> Position::Right` and `1 -> Position::Left` so Halo2 folds identically.
38            let position = if bit == 0 {
39                Position::Right
40            } else {
41                Position::Left
42            };
43            siblings.push((position, node));
44        }
45
46        Ok(Halo2MerklePath::new(siblings))
47    }
48}