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

valueencoding (hex)
000
101
2317
241818
25518ff
256190100
6553519ffff
655361a00010000
42949672951affffffff
42949672961b0000000100000000
9007199254740991 (2^53−1)1b001fffffffffffff
−120
−2437
−253818
−25638ff
−10003903e7
−42949672963affffffff
−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
truef5
falsef4
nullf6

Key ordering (length first, then bytewise) — {z: 1, aa: 2, b: 3} encodes with key order b, z, aa:

a3616203617a0162616102

Absent-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:

valueencoding (hex)
2^531b0020000000000000
2^64−11bffffffffffffffff
−2^533b001fffffffffffff
−2^643bffffffffffffffff

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:

valueencoding (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, aa2616201616102a2616102616201
{aa: 2, b: 3} keyed aa, ba262616102616203a261620362616102

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 || B

Rejection 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: d82a5824 01551220 || A (a 36-byte payload beginning directly with the CID);
  • CIDv0 payload: d82a5823 00 1220 || A (prefix + bare multihash — no 0x01 version byte);
  • identity multihash: d82a4a00015500 05 68656c6c6f (the five bytes "hello" carried inside the ref);
  • unsupported codec: d82a582500 01701220 || A (0x70 dag-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 byte 0x00 is followed by A, whose first byte 0x00 is 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
1903e8

Directory entry {t:"d", r: Ref(dag-cbor, B), m:0o755}:

a3616d1901ed6172 || d82a58250001711220 || B || 61746164

Symlink entry {t:"l", tgt:"a/b", mt:−1} (note: / is legal inside a symlink target, only names forbid it):

a36174616c626d74206374677463612f62

Character-device entry {t:"c", rdev:[1, 3]}:

a2617461636472646576820103

Directory node {v:1, t:"d", e:[["a.txt", {t:"f", sz:2, d:"hi"}], ["b", <symlink entry above>]]}:

a36165828265612e747874a361644268696174616662737a02826162
  || a36174616c626d74206374677463612f62 || 61746164617601

File node {v:1, t:"f", sz:12, c:[[8, Ref(raw, A)], [4, Ref(raw, B)]]}:

a461638282 08 d82a58250001551220 || A || 8204 d82a58250001551220 || B
  || 6174616661760162737a0c

Root 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, 0xa3772475

Deterministic 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, 245

No 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 from prand(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 device rdev [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)      = bafyreigevcn2mtvodxz2st4vnyy7z4quorzzord3x2dyv6pncggw3dtfba

Tree 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).
Directory [["big.bin", entry]] with {m: 0o755}.
Root node with {ts: 1700000000}.

Expected refs:

entry.r (file "f" node) = bafyreigyfd2wkluvivbdr2klfplazd2iagb5s463uv77ta35zpwuwomt5e
root    ("r" node)      = bafyreigna5rmeof5xgauoinhnpdmv7e4xlovfcytnac52y6yln4qn7noyi

Determinism 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:

nameu32u32 AND 0x3ffboundary?
"a"0xca9781120x112no
"hello"0x2cf24dba0x1bano
"xw"0x55b428000x000yes
"ass"0x2062f8000x000yes
"f0912"0xe125ac000x000yes
"f2773"0xfe2038000x000yes

Segmentation vector: a directory of 3000 entries named f0000f2999 (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, f2773

and 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) = bafyreiei7rbwv4lgezyf3pqsidvf5vtc2vmxfwqi4nwkhwryja3mxjspc4

The 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:

066f72b5e37d043b527f1b2a77deea2fc3f10e642fd425782f8b7eef5c4e8be1

Importing 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).

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