open-standards/smart-byte/conformance/v1/canonicalisation_vectors.json
Transaction Science 7dba0f3db9
All checks were successful
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 1m56s
standards workspaces build + test / aac-rs (pull_request) Successful in 46s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 42s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 39s
standards workspaces build + test / map-rs (pull_request) Successful in 32s
standards workspaces build + test / mesh-rs (pull_request) Successful in 37s
standards workspaces build + test / openpay (pull_request) Successful in 2m0s
smart-byte conformance gate / Validate conformance JSON files (pull_request) Successful in 3s
smart-byte conformance gate / Second implementation reproduces every vector (pull_request) Successful in 3s
smart-byte conformance gate / Spec changes require vector-pack updates (pull_request) Successful in 4s
smart-byte conformance gate / VERSIONS.md and README.md must exist (pull_request) Successful in 3s
no-vanity-metrics / Scan committed Markdown for vanity metrics (pull_request) Successful in 3s
standards workspaces build + test / eoc-rs (pull_request) Successful in 1m53s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m20s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m24s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m11s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 2m44s
standards workspaces build + test / arl-rs (pull_request) Successful in 4m0s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 3m24s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 2m43s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 3m6s
standards workspaces build + test / sense-rs (pull_request) Successful in 3m9s
standards workspaces build + test / macOS energy backend (jcp-energy) (pull_request) Successful in 2m46s
smart-byte: the conformance pack, corrected against an independent review
The review found the pack and its checkers wrong in ways two agreeing
implementations could not catch.

Spec:
- Identity spec §2.1, §2.3, §2.4 and §4 step 1 told a verifier to re-encode
  an object canonically. A verifier written to them accepts said-009, the
  re-encoding attack §2.5 exists to refuse. All four now require received
  bytes to be canonical already.
- New §2.6 fixes how a SAID-addressed object is received: canonical bytes,
  then the SAID they state, then the SAID they arrived under, and only then
  deduplication.
- §2.3 forbids simple values other than false, true and null.
- §3.3/§3.4 say a key is a byte string, as said-007 already encoded it.

Vectors:
- Lockstep vectors separate the hash a node broadcast from the one it
  computed, and give each node's outcome and dissent log from its own view.
  The old single view called a node a "liar" while modelling divergence;
  lockstep-006/007 now pin treatise §13.4's transport liar, who commits in
  its own view while its peers log it.
- Gossip vectors carry generic SAID-addressed objects. Envelope-shaped ones
  with null or filler provenance are bytes a provenance-checking node must
  refuse (treatise §8.3), so they could not be MUST-pass. `expected_held`
  now carries the bytes held.
- New canon-020 (simple value) and sig-007 (a key spelled with y = p + 3, a
  second spelling of a valid point that ed25519-dalek alone accepts).
- SAID vector descriptions no longer claim fidelity to treatise §8 or a
  frame the data never mentions.

Checkers:
- Both now check the pack whole. An empty pack, a deleted vector the spec
  cites, a deleted class, or an unlisted class all fail; before, an empty
  pack passed both checkers.
- The Python checker no longer uses `assert`. Under `python -O` it passed a
  corrupted pack.
- Both refuse JSON that names a member twice.
- The Rust verifier no longer panics on an empty stated SAID.
- The gossip expectations come from smart-byte-core's inbox, so every value
  in the pack is now the reference implementation's.

Falsified:
- The Rust verifier catches 19 of 19 planted defects, each on its own vector,
  plus the empty-pack, deleted-vector, deleted-class, unlisted-class and
  duplicate-member cases.
- The Python checker catches 15 of 15 planted defects in both normal and -O
  modes, plus the same pack-level cases.

regeneration.md is consolidated into one accurate entry, which corrects the
earlier claim that no draft vector was passable. VERSIONS.md rule 4 and the
README now scope supersession to frozen packs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-30 21:34:19 -04:00

174 lines
7.9 KiB
JSON

[
{
"name": "canon-001-sorted-map-keys",
"description": "Map keys are written in the bytewise order of their canonical encodings (identity spec §2.3). The input lists the keys out of order; the canonical encoding sorts them.",
"input": {
"z": 1,
"a": 2,
"m": 3
},
"expected_cbor_hex": "a3616102616d03617a01"
},
{
"name": "canon-002-shortest-form-integers",
"description": "Integers use the shortest head that holds them: no leading zeros, no over-wide argument. 0, 23, 24, 255, 256, 65535 and 65536 sit on the boundaries of CBOR's argument widths.",
"input": {
"i0": 0,
"i23": 23,
"i24": 24,
"i255": 255,
"i256": 256,
"i65535": 65535,
"i65536": 65536
},
"expected_cbor_hex": "a7626930006369323317636932341818646932353518ff64693235361901006669363535333519ffff666936353533361a00010000"
},
{
"name": "canon-003-no-tags",
"description": "A byte string is written untagged, as major type 2. Wrapping bytes in tag 21 (\"expected base64url conversion\") is tempting, but canonical encoding forbids tags (identity spec §2.3). canon-015 is the tagged form, which a decoder refuses rather than silently stripping the tag.",
"input": {
"payload": {
"$bytes": "0001020304050607"
}
},
"expected_cbor_hex": "a1677061796c6f6164480001020304050607"
},
{
"name": "canon-004-floats-forbidden",
"description": "Floats are forbidden (identity spec §2.3): a value that would be a float elsewhere is carried as a fixed-point integer pair. The input is a map holding 1.5 as a half-precision float, and a decoder refuses it.",
"input_cbor_hex": "a16a65786163745f68616c66f93e00",
"error": "float_forbidden"
},
{
"name": "canon-005-bytes-not-text-for-byte-arrays",
"description": "Byte values are byte strings (major type 2) — never text, and never an array of small integers, which is what a generic serializer writes for a fixed-size byte array by default. The members hold a 32-byte digest, a 32-byte public key and a 64-byte signature. A SAID's text form (identity spec §2.2) is text; the digest it spells is bytes.",
"input": {
"said": {
"$bytes": "0000000000000000000000000000000000000000000000000000000000000000"
},
"key": {
"$bytes": "1111111111111111111111111111111111111111111111111111111111111111"
},
"sig": {
"$bytes": "22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222"
}
},
"expected_cbor_hex": "a3636b65795820111111111111111111111111111111111111111111111111111111111111111163736967584022222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222647361696458200000000000000000000000000000000000000000000000000000000000000000"
},
{
"name": "canon-006-key-order-follows-encoded-bytes",
"description": "Keys sort by their encoded bytes, not their text (identity spec §2.3). As text \"aa\" comes before \"b\"; encoded, \"b\" (0x61 0x62) comes before \"aa\" (0x62 0x61 0x61), because a string's length is in its first byte. canon-012 is the text-ordered encoding, which a decoder refuses.",
"input": {
"aa": 1,
"b": 2
},
"expected_cbor_hex": "a261620262616101"
},
{
"name": "canon-007-integer-boundaries-and-negatives",
"description": "Negative integers (major type 1) and the 32-bit/64-bit boundary. -1 is 0x20, and -24 is the last one-byte form; 4294967295 is the largest four-byte argument and 4294967296 the smallest eight-byte one.",
"input": [
-1,
-24,
-25,
-256,
-257,
-65536,
-65537,
4294967295,
4294967296
],
"expected_cbor_hex": "892037381838ff39010039ffff3a000100001affffffff1b0000000100000000"
},
{
"name": "canon-008-string-length-boundary",
"description": "A length uses the shortest head too. A 23-character text string carries its length in its first byte; a 24-character one needs the one-byte argument.",
"input": [
"aaaaaaaaaaaaaaaaaaaaaaa",
"bbbbbbbbbbbbbbbbbbbbbbbb"
],
"expected_cbor_hex": "827761616161616161616161616161616161616161616161617818626262626262626262626262626262626262626262626262"
},
{
"name": "canon-009-empty-containers-and-simple-values",
"description": "An empty array, map, text string and byte string, and the simple values null, true and false — each a single byte.",
"input": {
"a": [],
"b": {},
"c": "",
"d": {
"$bytes": ""
},
"e": null,
"f": true,
"g": false
},
"expected_cbor_hex": "a76161806162a06163606164406165f66166f56167f4"
},
{
"name": "canon-010-non-shortest-integer-refused",
"description": "Zero written with a one-byte argument (0x18 0x00). Identity spec §2.3: \"a value of 0 is encoded as 0x00, never as 0x18 0x00\". A decoder refuses it rather than normalising it.",
"input_cbor_hex": "1800",
"error": "non_shortest_integer"
},
{
"name": "canon-011-non-shortest-length-refused",
"description": "The one-character text \"a\" with its length in a one-byte argument (0x78 0x01) rather than in the head. A length has one canonical form too.",
"input_cbor_hex": "780161",
"error": "non_shortest_length"
},
{
"name": "canon-012-text-ordered-keys-refused",
"description": "canon-006's map with its keys in text order (\"aa\" before \"b\"). The keys are distinct and the map is well-formed, but the order is not the canonical one, so the encoding is refused.",
"input_cbor_hex": "a262616101616202",
"error": "unsorted_map_keys"
},
{
"name": "canon-013-duplicate-key-refused",
"description": "A map holding the key \"a\" twice. It has no canonical form, and which of the two values a lenient decoder keeps differs between implementations.",
"input_cbor_hex": "a2616101616102",
"error": "duplicate_map_key"
},
{
"name": "canon-014-indefinite-length-refused",
"description": "An array written with an indefinite length (0x9f … 0xff). Identity spec §2.3 forbids indefinite-length items: the same array has a definite-length encoding.",
"input_cbor_hex": "9f0102ff",
"error": "indefinite_length"
},
{
"name": "canon-015-tagged-byte-string-refused",
"description": "canon-003's map with its byte string wrapped in tag 21. Tags are forbidden (identity spec §2.3): the decoder refuses the input; it does not strip the tag and accept what is left.",
"input_cbor_hex": "a1677061796c6f6164d5480001020304050607",
"error": "tag_forbidden"
},
{
"name": "canon-016-single-precision-float-refused",
"description": "A single-precision float (the largest finite one). Refused, as every float is.",
"input_cbor_hex": "fa7f7fffff",
"error": "float_forbidden"
},
{
"name": "canon-017-double-precision-float-refused",
"description": "A double-precision float (the largest finite one). Refused, as every float is.",
"input_cbor_hex": "fb7fefffffffffffff",
"error": "float_forbidden"
},
{
"name": "canon-018-trailing-bytes-refused",
"description": "Two items back to back. A canonical encoding is exactly one item: bytes after it would sit outside the value a SAID commits to, so the input is refused.",
"input_cbor_hex": "0101",
"error": "trailing_bytes"
},
{
"name": "canon-019-invalid-utf8-refused",
"description": "A text string whose bytes (0xc3 0x28) are not UTF-8. CBOR text strings are UTF-8 (RFC 8949 §3.1), so the input is refused.",
"input_cbor_hex": "62c328",
"error": "invalid_utf8"
},
{
"name": "canon-020-simple-value-refused",
"description": "The simple value undefined (0xf7). Identity spec §2.3 admits false, true and null and no other simple value, so the data model has nothing to decode it to and the input is refused.",
"input_cbor_hex": "f7",
"error": "simple_value_forbidden"
}
]