Test vectors (Appendix A)
Every constant below is frozen. An implementation that reproduces all of them byte-exactly, and enforces the rejection cases, can be considered wire-compatible. Hex is lowercase.
Two fixed 32-byte digests are used throughout:
A = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
(bytes 0,1,2,…,31)
B = fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0
(bytes 255,254,…,224)#A.1 Canonical CBOR
| value | encoding (hex) |
|---|---|
0 | 00 |
1 | 01 |
23 | 17 |
24 | 1818 |
255 | 18ff |
256 | 190100 |
65535 | 19ffff |
65536 | 1a00010000 |
4294967295 | 1affffffff |
4294967296 | 1b0000000100000000 |
9007199254740991 (2^53−1) | 1b001fffffffffffff |
−1 | 20 |
−24 | 37 |
−25 | 3818 |
−256 | 38ff |
−1000 | 3903e7 |
−4294967296 | 3affffffff |
−9007199254740991 (−(2^53−1)) | 3b001ffffffffffffe |
h'' (empty bytes) | 40 |
h'0001ff' | 430001ff |
"" | 60 |
"a" | 6161 |
"hello" | 6568656c6c6f |
"é" | 62c3a9 |
[] | 80 |
[1, 2, 3] | 83010203 |
[[]] | 8180 |
{} | a0 |
{t: "f", v: 1} | a261746166617601 |
true | f5 |
false | f4 |
null | f6 |
Key ordering (length first, then bytewise) — {z: 1, aa: 2, b: 3} encodes
with key order b, z, aa:
a3616203617a0162616102Absent-field omission: {b: absent, a: 1} → a1616101; {a: absent} →
a0.
Integer range rejections (§2.3, §5.3) — well-formed CBOR, out of
range, MUST be rejected. 2^53 and −2^53 are valid DAG-CBOR (within
its 64-bit signed range), rejected only by this profile. 2^64−1 and
−2^64 lie outside DAG-CBOR's integer domain and are rejected by
DAG-CBOR implementations and this profile alike:
| value | encoding (hex) |
|---|---|
2^53 | 1b0020000000000000 |
2^64−1 | 1bffffffffffffffff |
−2^53 | 3b001fffffffffffff |
−2^64 | 3bffffffffffffffff |
Floating-point rejections (§5.3) — the double form (fb…) is valid
DAG-CBOR rejected only by this profile. The half (f9…) and single
(fa…) forms are accepted only by relaxed DAG-CBOR decoders (strict
ones reject non-64-bit widths). NaN is rejected by DAG-CBOR itself in
every width:
| value | encoding (hex) |
|---|---|
1.0 (half) | f93c00 |
100000.0 (single) | fa47c35000 |
12.5 (double) | fb4029000000000000 |
NaN (half) | f97e00 |
The simple value "undefined" (f7) MUST likewise be rejected (§5.3);
DAG-CBOR rejects it as well.
Canonical-key-order rejections (§5.3) — well-formed CBOR maps whose keys are out of canonical order MUST be rejected:
| map (out-of-order encoding) | encoding (hex) | canonical form |
|---|---|---|
{b: 1, a: 2} keyed b, a | a2616201616102 | a2616102616201 |
{aa: 2, b: 3} keyed aa, b | a262616102616203 | a261620362616102 |
Round-trip requirement: strict-decoding any accepted encoding above and re-encoding it MUST reproduce the same bytes.
#A.2 Refs (CIDs) and base32
Ref(raw, A):
binary CID : 01551220 || A
string form: bafkreiaaaebagbafaydqqcikbmga2dqpcaireeyuculbogazdinryhi6d4
CBOR form: d82a58250001551220 || A
Ref(dag-cbor, B):
binary CID : 01711220 || B
string form: bafyreih77367z6727h4pp5xv6tz7f4pq57xo33hl5lu6rz7g4xsohyxb4a
CBOR form: d82a58250001711220 || B
{r: Ref(raw, A)} → a16172 || d82a58250001551220 || A
[Ref(raw, A), Ref(dag-cbor, B)] → 82 || d82a58250001551220 || A
|| d82a58250001711220 || BRejection cases (§4, §5.3) — each MUST be rejected:
- tag 117 (
d875582100 || A) — an unknown tag, rejected like any other unknown tag; - tag 42 with a text payload (
d82a63616263); - missing identity multibase prefix:
d82a582401551220 || A(a 36-byte payload beginning directly with the CID); - CIDv0 payload:
d82a5823001220 || A(prefix + bare multihash — no0x01version byte); - identity multihash:
d82a4a000155000568656c6c6f(the five bytes"hello"carried inside the ref); - unsupported codec:
d82a58250001701220 || A(0x70dag-pb — a valid CID, but not a ref of this format); - digest length outside [20, 64]:
d82a58180001551213+ 19 bytes; - truncated digest:
d82a550001551220+ 16 bytes (the multihash declares 32 digest bytes; the payload ends after 16); d82a582100 || A— the prefix byte0x00is followed byA, whose first byte0x00is not a valid CIDv1 version: a malformed CID.
Base32: "74" decodes to ff (canonical). "75" MUST be rejected
(non-zero padding bits). "a", "aaa", "aaaaaa" MUST be rejected
(impossible lengths). Any uppercase or non-alphabet character MUST be
rejected.
#A.3 Entries and nodes
File entry with every stat field —
{t:"f", sz:5, d:"hello", m:0o644, mt:1700000000, mtn:500, uid:1000, gid:100, un:"u", gn:"g", x:{k: h'0102'}, ino:7}:
ac61644568656c6c6f616d1901a4617461666178a1616b42010262676e6167626d741a
6553f10062737a0562756e617563676964186463696e6f07636d746e1901f463756964
1903e8Directory entry {t:"d", r: Ref(dag-cbor, B), m:0o755}:
a3616d1901ed6172 || d82a58250001711220 || B || 61746164Symlink entry {t:"l", tgt:"a/b", mt:−1} (note: / is legal inside a
symlink target, only names forbid it):
a36174616c626d74206374677463612f62Character-device entry {t:"c", rdev:[1, 3]}:
a2617461636472646576820103Directory node
{v:1, t:"d", e:[["a.txt", {t:"f", sz:2, d:"hi"}], ["b", <symlink entry above>]]}:
a36165828265612e747874a361644268696174616662737a02826162
|| a36174616c626d74206374677463612f62 || 61746164617601File node {v:1, t:"f", sz:12, c:[[8, Ref(raw, A)], [4, Ref(raw, B)]]}:
a461638282 08 d82a58250001551220 || A || 8204 d82a58250001551220 || B
|| 6174616661760162737a0cRoot node
{v:1, t:"r", e:{t:"d", r: Ref(dag-cbor, B)}, ts:1700000000, note:"golden", prev: Ref(dag-cbor, A)}:
a66165a26172 || d82a58250001711220 || B || 61746164617461726176016274731a6553f100
|| 646e6f746566676f6c64656e6470726576d82a58250001711220 || A(|| marks concatenation only; the vector is the joined hex.)
#A.4 GEAR table and FastCDC
First eight GEAR table entries (splitmix32, seed 0 — §9.4):
GEAR[0..7] = 0x64625032, 0xd9c0799c, 0xaf362e10, 0x7fa88912,
0xc4671b39, 0xf1d2eee4, 0x867a4029, 0xa3772475Deterministic data generator used by the chunking and tree vectors — an
xorshift32 variant. All state is 32-bit; ^ is XOR, << shifts modulo
2^32:
prand(n, seed):
s = seed mod 2^32
out = byte[n]
for i in 0 .. n−1:
s = (s ^ (s << 13)) mod 2^32
s = s ^ sar32(s, 17)
s = (s ^ (s << 5)) mod 2^32
out[i] = s AND 0xff
return out
sar32(x, k): # ARITHMETIC right shift on the 32-bit pattern
interpret x as a signed two's-complement 32-bit integer,
shift right by k with sign extension,
reinterpret the result as unsigned 32-bit.
(Equivalently: x >> k logically, then OR the top k bits with ones
when bit 31 of x is set.)⚠ Note the middle step uses an arithmetic (sign-extending) right shift, where textbook xorshift32 uses a logical one. This is deliberate and pinned by the vectors below: a logical shift produces entirely different data.
Chunk-size vector: FastCDC with min=256, avg=1024, max=4096 over
prand(131072, 7) MUST produce exactly these 117 chunk lengths, in order
(they sum to 131072):
1052, 1409, 425, 1512, 1110, 1139, 1207, 1125, 1299, 1916, 1156, 1580,
1183, 1351, 1056, 906, 1034, 1575, 1332, 1307, 1054, 430, 381, 1404,
334, 364, 372, 1097, 1112, 1326, 840, 1402, 1066, 1344, 290, 1031, 476,
1024, 1053, 1053, 1071, 1137, 1429, 1277, 629, 1301, 1106, 1332, 1190,
1083, 1023, 1048, 1501, 1038, 556, 1190, 1116, 1385, 1057, 1075, 1125,
1102, 1067, 1636, 732, 1336, 1389, 788, 1715, 1178, 1205, 1351, 1366,
1147, 1278, 1046, 1944, 1100, 1121, 1041, 1211, 509, 1675, 1542, 1110,
1792, 933, 1114, 1634, 560, 1380, 654, 581, 1055, 1485, 1205, 1421,
1156, 1073, 1442, 1066, 607, 1426, 1459, 1063, 1100, 1242, 1278, 1088,
1155, 1224, 1081, 1430, 1569, 435, 734, 245No length equals max: the cut predicate (§9.4)
tests the high bits of the rolling hash, so every boundary here is
content-defined rather than forced by the max bound.
#A.5 End-to-end tree vectors
These pin the full pipeline — chunking, entry/node encoding, directory building, hashing (SHA2-256), refs — as ref strings. All parameters at defaults except where stated.
Tree A (fixed-size chunker, size 1024):
big= file fromprand(10000, 1)with{m: 0o644, mt: 1700000000, mtn: 123456789}.nested= file from the UTF-8 bytes of"nested content\n"with{mt: 1700000000}.sub= directory[["nested.txt", nested]]with{m: 0o755}.Root directory, entries (any input order — the writer sorts):
"data.bin"→big"hello.txt"→ file"hello format\n"with{m: 0o600}"empty"→ file of zero bytes"link"→ symlink target"data.bin",{m: 0o777}"dev"→ char devicerdev [1, 3]"fifo"→ fifo"sub"→sub
with directory stat
{m: 0o755, mt: 1700000000, uid: 1000, gid: 100}.Root node over that directory entry with
{ts: 1700000000, note: "golden-tree-a"}.
Expected refs:
big.r (file "f" node) = bafyreiftz6g4uc6c6sf6zz7z63r62cky3x3r4xuqie6w3cgc3klwyn444y
dir.r ("d" node) = bafyreihkkpgwse5yjsbh3guppaqaagjhpjiqakwrk7nsqdwlfbihnlr7uy
root ("r" node) = bafyreigevcn2mtvodxz2st4vnyy7z4quorzzord3x2dyv6pncggw3dtfbaTree B (default FastCDC 8192/32768/98304):
entry = file from prand(1500000, 2) with {m: 0o644, mt: 1700000000} (multi-chunk — 41 chunks; its ref is dag-cbor-codec).[["big.bin", entry]] with {m: 0o755}.{ts: 1700000000}.Expected refs:
entry.r (file "f" node) = bafyreigyfd2wkluvivbdr2klfplazd2iagb5s463uv77ta35zpwuwomt5e
root ("r" node) = bafyreigna5rmeof5xgauoinhnpdmv7e4xlovfcytnac52y6yln4qn7noyiDeterminism checks implied by these vectors: supplying tree A's directory
entries in any order yields the same dir.r. Supplying file content as one
buffer, as arbitrary piece slicings, or with any internal concurrency
yields the same refs. Exporting a canonical CAR pack of a tree, importing
it into an empty store, and re-exporting yields byte-identical CARv1
bytes (A.7).
#A.6 Directory segmentation
Boundary predicate pins (§8.2; SHA2-256, SEG_MASK = 0x3ff). u32 is
the big-endian first 4 bytes of the hash of the name's UTF-8 bytes:
| name | u32 | u32 AND 0x3ff | boundary? |
|---|---|---|---|
"a" | 0xca978112 | 0x112 | no |
"hello" | 0x2cf24dba | 0x1ba | no |
"xw" | 0x55b42800 | 0x000 | yes |
"ass" | 0x2062f800 | 0x000 | yes |
"f0912" | 0xe125ac00 | 0x000 | yes |
"f2773" | 0xfe203800 | 0x000 | yes |
Segmentation vector: a directory of 3000 entries named f0000 …
f2999 (4-digit decimal), each the empty file {t:"f", sz:0} (pairSize
16), built with pageThreshold = 4096 (all else default, SHA2-256), MUST
produce exactly 13 segments, with firstNames
f0000, f0256, f0512, f0768, f0912, f1168, f1424, f1680,
f1936, f2192, f2448, f2704, f2773and entry counts 256, 256, 256, 144, 256, 256, 256, 256, 256, 256, 256,
69, 227. The cuts at f0912 and f2773 are boundary-name cuts. Every
other cut is size-forced (256 pairs × 16 bytes = 4096). Expected ref:
dir.r ("d" node) = bafyreiei7rbwv4lgezyf3pqsidvf5vtc2vmxfwqi4nwkhwryja3mxjspc4The trees of A.5 are unaffected by segmentation: both directories are flat
at the default pageThreshold.
#A.7 Pack vector (CARv1)
The canonical export (§12.2) of Tree A (A.5), with its root node as the single root, from a SHA2-256 store. The pack is a CARv1 byte stream (§12.1). Its header frame is, byte-exactly:
3a a265726f6f747381 d82a582500 01711220
c4a89ba64eae1df3a94f956e31fcf214747397447bbe878af9ed118d6d8e6508
6776657273696f6e 01(varint header length 3a = 58. Header {roots: [root], version: 1} in
canonical key order — roots, then version (§12.1). The single root
is a tag-42 link whose payload is 0x00 plus the binary CID of the A.5
root, i.e. the base32 decoding of bafyreigevcn2….)
It is followed by exactly 14 block sections, each
varint(len) || CID || blockBytes, in this order: the "r" root node,
the "d" directory node, the "f" node of data.bin, its 10 raw chunks
in chunk order, and the "d" node of sub — the depth-first preorder of
§12.2 with per-multihash section dedupe. The complete pack is
11496 bytes and its SHA2-256 is:
066f72b5e37d043b527f1b2a77deea2fc3f10e642fd425782f8b7eef5c4e8be1Importing this pack into an empty store and re-exporting the reported root MUST reproduce the same 11496 bytes. Exporting with the root supplied twice MUST also reproduce them (root deduplication, §12.2). Every prefix of this pack cut at a section boundary MUST itself import as a valid (thinner) pack (§12.1).