use alloy_primitives::B256;
use std::fmt;
pub const STEM_LEN: usize = 31;
pub const SUBINDEX_BITS: usize = 8;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Stem(pub [u8; STEM_LEN]);
impl Stem {
pub const fn new(bytes: [u8; STEM_LEN]) -> Self {
Self(bytes)
}
pub fn from_slice(slice: &[u8]) -> Self {
let mut bytes = [0u8; STEM_LEN];
bytes.copy_from_slice(slice);
Self(bytes)
}
pub const fn as_bytes(&self) -> &[u8; STEM_LEN] {
&self.0
}
pub fn is_zero(&self) -> bool {
self.0.iter().all(|&b| b == 0)
}
pub fn bit_at(&self, pos: usize) -> bool {
debug_assert!(pos < STEM_LEN * 8);
let byte_idx = pos / 8;
let bit_idx = 7 - (pos % 8); (self.0[byte_idx] >> bit_idx) & 1 == 1
}
pub fn first_differing_bit(&self, other: &Self) -> Option<usize> {
for i in 0..STEM_LEN {
if self.0[i] != other.0[i] {
let xor = self.0[i] ^ other.0[i];
let bit_in_byte = 7 - xor.leading_zeros() as usize;
return Some(i * 8 + (7 - bit_in_byte));
}
}
None
}
}
impl Ord for Stem {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.cmp(&other.0)
}
}
impl PartialOrd for Stem {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl fmt::Debug for Stem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Stem(0x{})", hex::encode(self.0))
}
}
impl From<[u8; STEM_LEN]> for Stem {
fn from(bytes: [u8; STEM_LEN]) -> Self {
Self(bytes)
}
}
pub type SubIndex = u8;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TreeKey {
pub stem: Stem,
pub subindex: SubIndex,
}
impl TreeKey {
pub const fn new(stem: Stem, subindex: SubIndex) -> Self {
Self { stem, subindex }
}
pub fn from_bytes(bytes: B256) -> Self {
let mut stem_bytes = [0u8; STEM_LEN];
stem_bytes.copy_from_slice(&bytes[..STEM_LEN]);
Self {
stem: Stem(stem_bytes),
subindex: bytes[STEM_LEN],
}
}
pub fn to_bytes(&self) -> B256 {
let mut bytes = [0u8; 32];
bytes[..STEM_LEN].copy_from_slice(&self.stem.0);
bytes[STEM_LEN] = self.subindex;
B256::from(bytes)
}
}
impl fmt::Debug for TreeKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"TreeKey {{ stem: 0x{}, subindex: {} }}",
hex::encode(self.stem.0),
self.subindex
)
}
}
impl From<B256> for TreeKey {
fn from(bytes: B256) -> Self {
Self::from_bytes(bytes)
}
}
impl From<TreeKey> for B256 {
fn from(key: TreeKey) -> Self {
key.to_bytes()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stem_bit_at() {
let mut bytes = [0u8; STEM_LEN];
bytes[0] = 0b10000000; let stem = Stem(bytes);
assert!(stem.bit_at(0));
assert!(!stem.bit_at(1));
assert!(!stem.bit_at(7));
}
#[test]
fn test_first_differing_bit() {
let stem1 = Stem([0u8; STEM_LEN]);
let mut bytes2 = [0u8; STEM_LEN];
bytes2[0] = 0b10000000;
let stem2 = Stem(bytes2);
assert_eq!(stem1.first_differing_bit(&stem2), Some(0));
let mut bytes3 = [0u8; STEM_LEN];
bytes3[0] = 0b00000001;
let stem3 = Stem(bytes3);
assert_eq!(stem1.first_differing_bit(&stem3), Some(7));
}
#[test]
fn test_tree_key_roundtrip() {
let original = B256::repeat_byte(0x42);
let key = TreeKey::from_bytes(original);
let recovered = key.to_bytes();
assert_eq!(original, recovered);
}
}