Skip to content

docs(unixfs): match spec to fixtures and defaults - #553

Open
lidel wants to merge 1 commit into
mainfrom
unixfs-hamt-bitfield-accuracy
Open

docs(unixfs): match spec to fixtures and defaults#553
lidel wants to merge 1 commit into
mainfrom
unixfs-hamt-bitfield-accuracy

Conversation

@lidel

@lidel lidel commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Note

TLDR: Low risk, cleaning up small inconsistencies to reflect interop and default reality.
I'm merging this to leave spec in consistent state with implementations before Shipyard shuts down at the end of the month.

Text-only change. Five statements in unixfs.md did not match the bytes in the linked fixtures or what boxo and helia have written since at least January 2023. No requirement on implementations changes.

Corrections

HAMT bitfield byte order and length. Spec said little-endian, exactly fanout/8 bytes. kubo writes a big-endian integer with leading zero bytes stripped, identical from v0.18.1 to master, and helia emits the same bytes1:

occupied bucket bitfield written
0x00 01
0x08 01 00
0x10 01 00 00
0xFF 80 + 31 zero bytes

unixfs-spec-bitfield-verification.zip if anyone wants to double-check with hexeditor

iiuc bucket 8 in the first byte rules out little-endian; the growing length rules out a fixed width. All 1066 HAMT blocks in the fixture CARs decode only under this reading2.

Bit consumption order. Spec said least significant bits first. Both implementations take bits from the most significant end of the big-endian murmur3 digest: 470.txt (006e88df…) sits at 00/6E470.txt in the 1000-file fixture.

Prefix width. Spec said log2(fanout)/4 (2.5 for fanout 1024). Both use the hex width of fanout-1: 2 for 256, 3 for 1024.

Empty Name on file chunk links. Spec said absent. Every multi-block file in the fixtures, and every kubo and helia release checked, writes it present and empty (12 00) on each link3. Omitting it changes the CID.

Profiles and sharding threshold. Profiles are described by their own frozen parameters instead of "Kubo defaults through v0.39", so the table cannot go stale. The threshold points at the IPIP-499 profiles (256 KiB in both) instead of a "256 KiB to 1 MiB" range no implementation uses.

Test vectors. Block sizes, leaf codecs, link order, and drawings corrected against the CAR bytes; the 1000-file HAMT fixture is three levels deep, not single-layer.

Verified against GO and JS releases since 2023

A 3-entry HAMT (hello.txt, 470.txt, 742.txt, threshold 1 byte) and a 3000-byte file in 1024-byte chunks, CIDv1 with raw leaves. Every version below produced the same root blocks: bafybeibxefk42morfuxkbxm2ksusqftpjbno6fafegldo4s2txxun77v2a and bafybeiez22ewz64uclquy3a4m4jecysq5bjhgzuhyhgvshjll5yw77r4s4.

current in kubo ipfs-unixfs-importer @helia/unixfs
Jan 2023 v0.18.1 12.0.0 (js-ipfs era)
Jan 2024 v0.26.0 15.2.4 3.0.0
Jan 2025 v0.33.0 15.3.1 4.0.2
Jan 2026 v0.39.0 16.0.2 7.0.1
Sep 2026 v0.43.0 17.1.1 8.0.5

Footnotes

  1. go-bitfield bitfield.go: bucket i at byte len-1-i/8, Bytes() drops leading zeros. helia's dir-sharded.ts reverses its little-endian array before writing; the "little endian" comment there describes the pre-reverse array, likely the source of the spec wording.

  2. single-layer-hamt-with-multi-block-files.car (237 blocks, June 2023), pbnode-field-orders.car (2), and the two dir-over-hamt CARs in the IPIP-499 fixtures (827). Six 1-byte bitfields read the same either way.

  3. boxo always assigns Name on the link (coding.go) and go-codec-dagpb writes any present field (marshal.go); helia writes Name: ''.

The spec text drifted from what the linked fixtures contain and what
boxo (Go) and helia (JS) already produce by default. No implementation
changes; the text now describes the preexisting behavior.

- hamt: bitfield is big-endian with leading zero bytes stripped, bits
  are consumed most significant first, prefix width is the hex width
  of fanout-1, single-entry sub-shards are collapsed on delete
- hamt: sharding threshold refers to the IPIP-0499 profiles both
  implementations ship, not a 256 KiB-1 MiB range neither uses
- file chunks: links carry an empty Name, as every existing DAG does
- profiles: describe each profile by its own parameters, not by an
  implementation's current defaults
- test vectors: fix block sizes, leaf codecs, link order, and drawings
  to match the fixture bytes
@lidel
lidel marked this pull request as ready for review September 7, 2026 19:39
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

🚀 Build Preview on IPFS ready

  • 🔎 Commit: 6303f55
  • 🔏 CID bafybeia465k7bdtng2gdbfgawfkaswqdmtzvtofkhxtaawt56dcx44wagm
  • 📦 Preview:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant