IPIP-0550: PBNode field ordering

Related Issue
ipfs/specs/issues/533
History
Commit History
Feedback
GitHub ipfs/specs (inspect source, open issue)

1. Summary

Add an opt-in Data-first field ordering for PBNode protobuf messages, so that streaming readers can process UnixFS metadata before links, and formalize that readers accept both orderings. No profile enables it: all profiles keep the canonical Links-first ordering and their CIDs, and writing Data-first requires an explicit opt-in setting.

2. Motivation

Regular UnixFS and HAMT-sharded directories are encoded as PBNode protobuf messages.

HAMT-sharded directory entries have the characteristic of prefixing the name of each entry with a number of characters drawn from the hash of the directory entry name.

When multiple entries land in the same bucket, that bucket is replaced by a sub-shard with its own CID/block that contains those entries.

The settings used to derive the prefix characters are stored in the Data field of the PBNode protobuf message.

This means that all Link messages must be read from the PBNode message before we can read the hashType (a multihash code) and fanout values that let us calculate the prefix length for a given directory entry.

When the reader is attempting to traverse to a single entry deep in the shard, they are forced to read all entries for the current sub-shard before they can move deeper within the shard, which leads to inefficient traversals.

Independent of any efficiency gains, both orderings already exist in the wild: the DAG-PB codec specification requires decoders to accept either order "as IPFS data exists in both forms". Implementations comply, but by accident. Neither the UnixFS specification nor any test suite said so, and tolerance that is unspecified and untested is how interoperability decays: the code path works until the day it silently does not, and nothing catches it.

The primary goal of this IPIP is the read side: every implementation can read UnixFS DAGs in either field order, and fixtures and conformance tests prove it. Producing the non-canonical Data-first order is optional and opt-in, but keeping a conforming writer around matters too: it is what keeps the tolerant read path exercised, so reading and writing such DAGs both stay interoperable instead of becoming an untested promise.

This is the robustness principle applied: "Be strict about the outcomes, be tolerant about the methods". The outcome is strict: the same logical UnixFS DAG, pinned by byte-exact fixtures and CIDs. The method, the field order on the wire, is tolerated in both forms, and, per the same principle's warning about silent tolerance, kept honest with tests rather than left to accident.

3. Detailed design

If content authors are allowed to write the Data field first, readers can process PBNode messages with a streaming parser: the UnixFS metadata (for HAMT shards, the hashType and fanout parameters) arrives before the links, so a reader looking for a specific entry can stop reading links as soon as it finds the one it needs.

3.1 The PBNode field order parameter

Writing the Data field first changes the CID generated for a piece of content, so this is an opt-in change.

We add a new parameter to the set defined in IPIP-0499:

Parameter Values
PBNode field order links-first (default) or data-first

When a profile does not define PBNode field order, it is links-first. This IPIP amends the IPIP-0499 profile tables so unixfs-v0-2015 and unixfs-v1-2025 state links-first explicitly; they continue to produce the same CIDs as today.

No profile sets data-first (see Alternatives for why). Implementations MAY expose an explicit low-level opt-in setting for writers that need it; enabling it changes the CID of every written node.

3.2 Changes to existing specifications

This IPIP amends the UnixFS specification:

It also amends the profile tables in IPIP-0499 with an explicit PBNode field order: links-first row, so the preexisting profiles are protected from unintentional change.

4. Design rationale

Traversing HAMT shards is more expensive than it needs to be, which disproportionately affects resource-constrained environments and inefficient runtimes.

Working code: opt-in data-first writing merged in boxo (ipfs/boxo#1212) and Kubo (ipfs/kubo#11439), and shipped in @ipld/dag-pb v4.2.0 (ipld/js-dag-pb#111); cross-ordering read tests and CAR fixtures shipped in gateway-conformance v0.14.1.

4.1 User benefit

Traversing HAMT shards will become faster in resource-constrained environments and inefficient runtimes.

Existing content and workflows are unaffected: the canonical ordering remains the default everywhere, and an explicit opt-in is available for new developments that want the more efficient streaming reads.

4.2 Compatibility

PBNode wire ordering is governed by the DAG-PB codec specification, which requires decoders to accept both field orders ("as IPFS data exists in both forms") and, since ipld/ipld#383, explicitly permits encoders to write either order. Deployed implementations (go-codec-dagpb, js-dag-pb) already read both orders, so Data-first blocks are readable by existing software, and the two specifications agree. Should they diverge again, the UnixFS specification governs UnixFS data.

Backward compatibility is preserved by keeping the new ordering opt-in. Writers emit it only when a user explicitly enables the setting; every profile and default keeps the canonical ordering, so already-published CIDs and the CID determinism guarantees of unixfs-v1-2025 are unchanged.

4.3 Security

Two valid encodings for the same data are possible.

4.4 Alternatives

5. Test fixtures

The table below lists minimal single-block vectors for both orderings. To verify compliance, decode each block, confirm the CID, and confirm that re-encoding the decoded node with the stated ordering reproduces the block bytes. Readers MUST resolve hello.txt through all four directory roots.

Description Ordering CID Block (base16 encoded)
hello.txt, file content "hello\n" raw leaf bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am 68656c6c6f0a
Directory containing hello.txt Data-first (opt-in) bafybeigqvyloizmfcdy6scaxnyltftzptaruqa3hnnplfzsbf4sqteiwlm 0a02080112330a24015512205891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03120968656c6c6f2e7478741806
Directory containing hello.txt Links-first (canonical) bafybeigdcg7pksx2zk5336vrfsktjodlr4rbfz37qr3koc5xboxe5ekv24 12330a24015512205891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03120968656c6c6f2e74787418060a020801
HAMTShard containing hello.txt Data-first (hand-crafted) bafybeicwgy2rlqmqqu3yy2tqvm2wbgdvy3snu4sbbv4wqpvpnoplpzxz74 0a250805121c80000000000000000000000000000000000000000000000000000000282230800212350a24015512205891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03120b444668656c6c6f2e7478741806
HAMTShard containing hello.txt Links-first (hand-crafted) bafybeicjwkfslu7gwyywffvqgse5kiibojtktxcdqhgv7ldj5fjdacuceq 12350a24015512205891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03120b444668656c6c6f2e74787418060a250805121c800000000000000000000000000000000000000000000000000000002822308002

The HAMTShard blocks are hand-crafted for parser testing; no profile shards a single-entry directory via import (sharding starts above the 256 KiB threshold). The entry name hello.txt hashed with murmur3-x64-64 (hashType 0x22) yields 0xDF as the first byte, selecting bucket 223 at fanout 256: the link name is DF + hello.txt, and bit 223 is set in the decode(PBNode.Data).Data bitfield.

All five blocks ship as pbnode-field-orders.car in gateway-conformance v0.14.1, whose conformance tests exercise both orderings.

A. References

[rfc2119]
Key words for use in RFCs to Indicate Requirement Levels. S. Bradner. IETF. March 1997. Best Current Practice. URL: https://www.rfc-editor.org/rfc/rfc2119

B. Acknowledgments

We gratefully acknowledge the following individuals for their valuable contributions, ranging from minor suggestions to major insights, which have shaped and improved this specification.

Editors
Alex Potsides (Shipyard) GitHub
Marcin Rataj (Shipyard) GitHub
Special Thanks
Rod Vagg GitHub
Volker Mische GitHub