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 values —
false(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 payload0x00 || CID: the multibase identity prefix (REQUIRED — DAG-CBOR: the identity prefix "must not be omitted") followed by the referenced block's binary CIDv1 — varint version0x01, varint content codec (0x55raw for raw-kind refs,0x71dag-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 is0x55(raw) or0x71(dag-cbor), and whose multihash is the store's hash function (§10.4) with the store's digest length. CIDv0 payloads (no leading0x01version 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;
NaNand ±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.)