Summary
The Yellowpaper RSMT/RSMST specification now defines keys, regions, and inclusion bitmaps using big-endian bit-string notation:
- SMT keys are 32-byte values read as 256-bit big-endian bit strings.
- Depth
d addresses bit d of that big-endian bit string.
rsmt_sort_key(k) = k; ordinary unsigned lexicographic byte order is the tree traversal order.
- Inclusion certificate bitmaps are 256-bit big-endian bit vectors.
- Regions are packed as 256-bit big-endian bit strings: the first
d bits are the key prefix and the suffix is zero.
The Go Aggregator still has explicit LSB-in-byte assumptions in the SMT implementation, proof verification, sharding prefix checks, and disk SMT encoding. Update the implementation and tests to match the spec.
Why
The change should make Go follow the natural bit order used by ordinary byte comparisons and big-endian bit-string notation.
Current Hotspots
Likely places that need changes:
-
pkg/api/state_id.go
PathToFixedBytes and FixedBytesToPath currently reverse byte order for an LSB-first SMT key layout.
- Comments explicitly say
key bit d is bit (d%8) of key[d/8].
-
pkg/api/state_id_bitorder_test.go
- Tests pin LSB-first semantics (
TestFixedBytesToPath_UsesLSBFirstBitAddressing, RoundtripLSBFirst).
-
pkg/api/inclusion_cert.go
verifyBitmapPath tests bitmap bits using (bitmap[d/8] >> (d%8)) & 1.
keyBitAt uses LSB-in-byte.
RegionFromKeyBytes packs prefixes by copying low-order bits in each byte and masking with (1 << rem) - 1.
- Comments mention LSB-first layout.
-
pkg/api/inclusion_cert_compose.go
bitmapDepthRange uses LSB-in-byte bitmap indexing.
-
pkg/api/smt.go
RegionFromPathBits packs path.Bit(i) into region[i/8] |= 1 << (i % 8).
- Comments describe v6a region bits as LSB-in-byte.
-
pkg/api/shard_match.go
MatchesShardPrefix checks LSB-first key bits and reads expected shard bits from the low bits of shardBitmask.
- This should be reconciled with the spec's shard-prefix interpretation.
-
internal/smt/smt.go
populate sets region bits with rightRegion[depth/8] |= 1 << (depth % 8).
- Parent-mode shard tree construction depends on this bit convention.
-
internal/smt/disk/hash.go
HashNode comment says region is packed LSB-in-byte.
RegionFromKey and KeyBit use LSB-in-byte.
-
internal/smt/disk/path.go
CompressedPath construction, BitAt, and unused-bit canonicalization use LSB-in-byte packing.
-
internal/smt/disk/node_key.go
clearUnusedPrefixBits masks unused bits as if prefix bits are LSB-in-byte.
- Persisted node keys include prefix bytes, so this affects disk compatibility and migration decisions.
-
internal/smt/disk/persist/tree.go
- Inclusion certificate generation sets bitmap bits with
1 << (depth % 8).
setPrefixBit uses LSB-in-byte prefix packing.
-
Test/vector files
internal/smt/golden_vectors_test.go
internal/smt/v6a_interop_vectors_test.go
internal/smt/yellowpaper_hash_semantics_test.go
pkg/api/inclusion_cert_test.go
internal/smt/disk/*_test.go
- Any golden roots or certificates generated under the old convention must be regenerated or explicitly marked legacy.
Implementation Notes
Prefer introducing small shared helpers instead of repeating shifts everywhere:
KeyBit(key []byte, d int) byte: return (key[d/8] >> (7 - uint(d%8))) & 1
SetBitBE(bits []byte, d int): bits[d/8] |= 0x80 >> uint(d%8)
ClearSuffixBE(prefix []byte, bitLen int) for region/bitmap/path canonicalization.
RegionFromKeyBytes(key []byte, depth int) should copy the prefix bytes and keep the high depth % 8 bits in the boundary byte.
For non-byte-aligned prefixes, the old LSB mask (1 << rem) - 1 becomes a high-bit mask, e.g. 0xff << (8 - rem) when rem != 0.
Migration / Compatibility
Changing bit order changes:
- SMT root hashes.
- Inclusion certificate bitmap bytes.
- Internal-node region bytes, hence node hashes.
- Shard routing and split behavior.
- Persisted disk SMT node keys and stored roots.
- Existing golden vectors.
No backwards compatibility needed. Old-format disk-backed SMT data can be deleted.
Acceptance Criteria
- All SMT key-bit access, bitmap access, region packing, and prefix packing in Aggregator use big-endian bit-string semantics.
FixedBytesToPath / PathToFixedBytes round-trip without reversing the 32-byte key order relative to spec key bytes.
- Inclusion certificate verification and generation use big-endian bitmap bits.
- RSMT node hashes match the updated Yellowpaper region packing.
- Batch/traversal ordering is ordinary unsigned lexicographic byte order (
rsmt_sort_key(k)=k).
- Shard-prefix matching and shard split/parent proof logic are aligned with the same bit convention.
- Disk SMT metadata/versioning prevents old-layout persisted data from being silently interpreted as new-layout data.
- Tests cover boundary depths:
0, 1, 7, 8, 200, 255.
- Golden vectors are regenerated for the new convention, with any retained old vectors clearly labeled legacy.
Out of Scope
- Changing CBOR serialization structure or public proof envelope shapes.
- Changing hash domain separators.
- Changing key byte order on the wire; only bit indexing within the existing byte string changes.
Summary
The Yellowpaper RSMT/RSMST specification now defines keys, regions, and inclusion bitmaps using big-endian bit-string notation:
daddresses bitdof that big-endian bit string.rsmt_sort_key(k) = k; ordinary unsigned lexicographic byte order is the tree traversal order.dbits are the key prefix and the suffix is zero.The Go Aggregator still has explicit LSB-in-byte assumptions in the SMT implementation, proof verification, sharding prefix checks, and disk SMT encoding. Update the implementation and tests to match the spec.
Why
The change should make Go follow the natural bit order used by ordinary byte comparisons and big-endian bit-string notation.
Current Hotspots
Likely places that need changes:
pkg/api/state_id.goPathToFixedBytesandFixedBytesToPathcurrently reverse byte order for an LSB-first SMT key layout.key bit d is bit (d%8) of key[d/8].pkg/api/state_id_bitorder_test.goTestFixedBytesToPath_UsesLSBFirstBitAddressing,RoundtripLSBFirst).pkg/api/inclusion_cert.goverifyBitmapPathtests bitmap bits using(bitmap[d/8] >> (d%8)) & 1.keyBitAtuses LSB-in-byte.RegionFromKeyBytespacks prefixes by copying low-order bits in each byte and masking with(1 << rem) - 1.pkg/api/inclusion_cert_compose.gobitmapDepthRangeuses LSB-in-byte bitmap indexing.pkg/api/smt.goRegionFromPathBitspackspath.Bit(i)intoregion[i/8] |= 1 << (i % 8).pkg/api/shard_match.goMatchesShardPrefixchecks LSB-first key bits and reads expected shard bits from the low bits ofshardBitmask.internal/smt/smt.gopopulatesets region bits withrightRegion[depth/8] |= 1 << (depth % 8).internal/smt/disk/hash.goHashNodecomment says region is packed LSB-in-byte.RegionFromKeyandKeyBituse LSB-in-byte.internal/smt/disk/path.goCompressedPathconstruction,BitAt, and unused-bit canonicalization use LSB-in-byte packing.internal/smt/disk/node_key.goclearUnusedPrefixBitsmasks unused bits as if prefix bits are LSB-in-byte.internal/smt/disk/persist/tree.go1 << (depth % 8).setPrefixBituses LSB-in-byte prefix packing.Test/vector files
internal/smt/golden_vectors_test.gointernal/smt/v6a_interop_vectors_test.gointernal/smt/yellowpaper_hash_semantics_test.gopkg/api/inclusion_cert_test.gointernal/smt/disk/*_test.goImplementation Notes
Prefer introducing small shared helpers instead of repeating shifts everywhere:
KeyBit(key []byte, d int) byte:return (key[d/8] >> (7 - uint(d%8))) & 1SetBitBE(bits []byte, d int):bits[d/8] |= 0x80 >> uint(d%8)ClearSuffixBE(prefix []byte, bitLen int)for region/bitmap/path canonicalization.RegionFromKeyBytes(key []byte, depth int)should copy the prefix bytes and keep the highdepth % 8bits in the boundary byte.For non-byte-aligned prefixes, the old LSB mask
(1 << rem) - 1becomes a high-bit mask, e.g.0xff << (8 - rem)whenrem != 0.Migration / Compatibility
Changing bit order changes:
No backwards compatibility needed. Old-format disk-backed SMT data can be deleted.
Acceptance Criteria
FixedBytesToPath/PathToFixedBytesround-trip without reversing the 32-byte key order relative to spec key bytes.rsmt_sort_key(k)=k).0,1,7,8,200,255.Out of Scope