Canonical CBOR

The one and only codec for structured data (nodes, entries, pack headers) is the following strict profile of CBOR (RFC 8949). Writers MUST emit the canonical form; readers MUST reject everything outside it.

This profile is a strict subset of DAG-CBOR (IPLD, §2.5). Every conforming block is a valid DAG-CBOR object under DAG-CBOR's strict encoding rules, and is decodable and link-traversable by generic IPLD tooling.

The reverse inclusion is deliberately not claimed: not every valid DAG-CBOR object is a conforming block of this format. Readers enforce the tighter rules given below — exactly the "strictness opt-in" the DAG-CBOR specification offers "for systems where round-trip determinism is a desirable feature and backward compatibility with old, non-strict data is unnecessary". As a result, a block accepted by this profile is accepted by any conforming DAG-CBOR decoder, while a block rejected only by the tighter rules below is valid DAG-CBOR but is not a block of this format (Appendix B).

#5.1 Value model

The wire model consists of:

  • Unsigned integers — major type 0; values up to 2^53 − 1 (§2.3).
  • Negative integers — major type 1; values down to −(2^53 − 1).
  • Byte strings — major type 2, definite length.
  • Text strings — major type 3, definite length, well-formed UTF-8.
  • Arrays — major type 4, definite length.
  • Maps — major type 5, definite length; keys MUST be text strings.
  • Links — tag 42 (major type 6) wrapping a byte string: the CBOR form of a Ref (§4), per the IPLD DAG-CBOR link rule.
  • Simple valuesfalse (0xf4), true (0xf5), null (0xf6).

Not in the model, and invalid on the wire: floating-point numbers, indefinite-length items, any tag other than 42, any simple value other than the three listed, and any integer of magnitude above 2^53 − 1.

#5.2 Encoding rules (writers)

  • Shortest-form heads. Every head (major type + length/value) MUST use the shortest encoding that fits: values < 24 in the head byte itself, then 1-, 2-, 4-, 8-byte arguments only as needed.
  • Map key order. Map keys MUST be sorted by: shorter UTF-8 encoding first; equal lengths compared bytewise. (Note this differs from name order, §3.3.) For text-string keys — the only keys this profile admits — this is exactly the bytewise lexicographic order of the keys' encoded forms required by RFC 8949 §4.2.1, and exactly the DAG-CBOR map-key order ("sorted in (byte-wise) lexical order, including their major type 3 and length"): a shortest-form text head sorts first by length, then by content. A length-first, an RFC 8949 deterministic-encoding, or a DAG-CBOR library therefore produces the same order here.
  • Absent fields are omitted. An optional field that is not recorded simply does not appear as a map key. Writers MUST NOT encode a "present but undefined" marker.
  • Integers are encoded as major type 0/1. A negative value n is encoded with argument −1 − n.
  • Links are encoded as 0xd8 0x2a (tag 42 — the only tag this profile admits, and the only tag DAG-CBOR admits) followed by a byte-string head and the payload 0x00 || CID: the multibase identity prefix (REQUIRED — DAG-CBOR: the identity prefix "must not be omitted") followed by the referenced block's binary CIDv1 — varint version 0x01, varint content codec (0x55 raw for raw-kind refs, 0x71 dag-cbor for node-kind refs), then the multihash (0x12 0x20 + 32-byte digest for SHA2-256). §4 gives the full grammar and worked bytes.

Under these rules every value has exactly one encoding, and decoding then re-encoding any conforming block is byte-identical.

#5.3 Decoding rules (readers)

Decoders parse untrusted bytes and MUST enforce all of the following:

  • Every read is bounds-checked; truncated input is rejected.
  • Heads MUST be minimally encoded: a 1-byte argument < 24, a 2-byte argument < 0x100, a 4-byte argument < 0x10000, an 8-byte argument < 0x100000000 are all rejected as non-minimal.
  • A declared collection length MUST be capped by the remaining input before allocation: an array of length L needs ≥ L remaining bytes; a map of length L needs ≥ 2·L remaining bytes. (This prevents a 5-byte input from declaring a 10-million-element array.)
  • Nesting depth MUST be capped at 64 (MAX_CBOR_DEPTH).
  • Map keys MUST be text strings; duplicate keys MUST be rejected. Decoded maps MUST NOT be subject to host-language prototype/metatable injection (a key like __proto__ is plain data).
  • Map keys MUST appear in exactly the canonical order of §5.2 rule 2; a map whose keys are out of order MUST be rejected. (Accepting a second key order would give one logical node two accepted encodings — and two hashes.)
  • Text strings MUST be well-formed UTF-8; malformed text MUST be rejected, never replaced with U+FFFD. (Lenient decoding would let byte-distinct blocks decode to identical names, breaking name identity, §3.3.)
  • Only tag 42 is accepted, with the strict payload rule of §4: the payload MUST be a byte string whose first byte is 0x00 (the multibase identity prefix) and whose remainder is a binary CIDv1 whose version is 1, whose content codec is 0x55 (raw) or 0x71 (dag-cbor), and whose multihash is the store's hash function (§10.4) with the store's digest length. CIDv0 payloads (no leading 0x01 version byte), identity-multihash CIDs, any other codec or multihash, and any other tag — including tag 117 (Appendix B) — MUST be rejected. (Everything this bullet rejects beyond "not a well-formed tag-42 link" is valid DAG-CBOR; the rejection is this profile's, not the codec's.)
  • Floating-point items (major type 7, minors 25/26/27) MUST be rejected, whatever their value. (Finite 64-bit doubles are valid DAG-CBOR that this profile rejects, Appendix B; half/single widths are accepted only by relaxed DAG-CBOR decoders — strict ones reject non-64-bit widths; NaN and ±Infinity are rejected by DAG-CBOR itself.)
  • Integers whose magnitude exceeds 2^53 − 1 MUST be rejected (§2.3) — never silently rounded. (The bound holds on the wire, so no decoded value ever needs more than an IEEE-754 double's exact integer range. Stricter than DAG-CBOR, which permits the 64-bit signed range; a conforming writer never emits a larger value, so the bound never affects a conforming block's DAG-CBOR validity.)
  • Trailing bytes after the top-level item MUST be rejected.
  • Every simple value other than false/true/null — including "undefined" (0xf7) — MUST be rejected. (Accepting 0xf7 as "field absent" would create a second encoding of absence; absence has exactly one encoding, §5.2 rule 3.)

unfs  A filesystem you can put in any key–value store.