# Smart Byte Conformance Vectors This directory contains the conformance test-vector pack for the Smart Byte substrate specification. Any third-party implementation, in any language, can consume these vectors to self-certify against the canonical wire format *without* trusting the reference implementation. The vectors are the social contract. The spec text in `../spec/` says what the substrate is; the vectors here say what the substrate's wire format *looks like, byte for byte*, on the inputs the spec text describes. An implementation that passes the vectors is canonical. An implementation that fails the vectors is not. The vectors are licensed CC-BY-4.0, consistent with the spec directory. They contain no `$ref` pointers, no external URLs, and no runtime dependencies. ## What is in the pack Each spec version has its own subdirectory (`v1/`, `v2/`, ...). Today only `v1/` exists; it corresponds to the spec text in `../spec/` (`treatise_v1_parts_I_II_III_combined.md`, `identity_and_key_rotation.md`, `envelope_wire_format.md` and `cluster_state.md`). A pack for a given version contains eight JSON files plus `regeneration.md`: | File | Tests | | -------------------------------- | --------------------------------------------------------------------- | | `canonicalisation_vectors.json` | Canonical CBOR: each encoding rule, accepted and refused (identity §2.3) | | `said_vectors.json` | SAID derivation and verification (identity §2.1–§2.5; treatise §8.2) | | `signature_vectors.json` | Ed25519 over the messages Smart Byte signs, verified strictly (RFC 8032) | | `kel_vectors.json` | Key-event logs: inception, rotation, interaction, thresholds (identity §3, §4) | | `envelope_vectors.json` | Envelopes decoded and verified against the logs' key states (envelope wire format) | | `state_vectors.json` | The cluster state machine: frames of inputs, their results, the state and its hash — governance, burns and transfers between clusters included (cluster state) | | `lockstep_vectors.json` | Supermajority commit, frame outcomes, dissent log (treatise §13, §14) | | `gossip_vectors.json` | Receiving SAID-addressed objects (identity §2.6; treatise §11.4, §21.4) | Every value in the pack is generated by the Rust reference implementation (`../smart-byte-rs`, crate `smart-byte-conformance`) and reproduced by an independent second implementation, `crosscheck/verify_v1.py`. `regeneration.md` documents both, and records every change to the pack. ## How to consume the vectors Each JSON file is a top-level array of vector objects. Vector objects share the common shape: ```json { "name": "short-stable-identifier", "description": "human-readable description of what is being tested", ...test-specific fields... } ``` The conventional flow for an implementation under test (IUT): 1. Load the JSON file for the relevant spec version. 2. For each vector, run the IUT against the `input` (or equivalent field) and compare its output to the `expected_*` field. 3. A vector passes if the IUT's output is byte-for-byte equal to the `expected_*` field (or, for negative vectors, the IUT rejects the input with the documented `error` class). 4. The IUT is canonical for a spec version iff every vector in that version's pack passes. There is no scoring, no "mostly conforms" status, and no waiver process. A vector pack is a hard gate. ### Byte strings in JSON JSON has no byte-string type, so an `input` writes a CBOR byte string as an object whose only member is `$bytes`, holding lowercase hex: `{"$bytes": "00ff"}`. `$bytes` is reserved: no map in the pack has a member of that name. Every other JSON value maps to CBOR directly: object → map with text keys, array → array, string → text string, integer → integer, `true` / `false` / `null` → the simple values. The pack contains no JSON number with a fraction or exponent, because identity spec §2.3 forbids floats, and no object names a member twice. Where a vector supplies raw bytes instead (`*_cbor_hex`, `input_cbor_hex`), they are lowercase hex. ### Fields, file by file - **`canonicalisation_vectors.json`** — a positive vector has `input` and `expected_cbor_hex`; a negative one has `input_cbor_hex`, which a strict decoder must refuse with `error`. Refusing means not decoding: an IUT must never re-encode non-canonical input into canonical form and accept it. - **`said_vectors.json`** — `said_field` names the member that carries the SAID (`d`). A positive vector's `input` holds the 53-`#` placeholder there. `canonical_cbor_hex` is the canonical encoding of `input` (the bytes that are hashed), `blake3_hex` is their BLAKE3-256, `expected_said` is that digest written `E` + lowercase unpadded base32, and `said_cbor_hex` is the object with its SAID in place, which must verify. A negative vector's `input_cbor_hex` must fail SAID verification with `error`. The adversarial vectors also give, for illustration, the SAIDs a lenient verifier would compute (`said_of_received_bytes`, `said_after_reencoding`). - **`signature_vectors.json`** — the Ed25519 rules every Smart Byte signature follows: RFC 8032, with strict decoding of keys and scalars. The message is `message_hex`, the canonical encoding of what is signed: sig-001's is an issuer's attestation, sig-002's a key event. `verifying_key` is the key handed to the verifier; when `signing_key_seed` is not null, it is the key that seed derives. Verify `signature` under `verifying_key`: the result must be `expected_verify`, failing with `error` when false. A positive vector's `signature` is the one deterministic signature its seed makes. - **`kel_vectors.json`** — `events` is one controller's log, in order. Each event gives its bytes (`cbor_hex`), the same event as typed JSON (`event`) when the bytes are canonical CBOR, and its `signatures`: each a key `index` and a `signature` — the key's algorithm identifier, `01` for Ed25519, followed by the signature over the event's bytes (identity §3.5). A signature with a `seed` is the one that seed's key makes. Replay the log as a v1 verifier (identity §4). An accepted log gives `expected`: the controller AID, each event's SAID, the key state each establishment event put in force (`sequence`, `keys` in hex, `threshold`), and the next-key commitment in force at the end. A refused log gives the `error` class and `error_at`, the position of the event refused; no later event is considered. - **`envelope_vectors.json`** — `cbor_hex` is an envelope, also given as typed JSON (`envelope`) when the bytes are canonical CBOR. `key_event_logs` names accepted vectors of `kel_vectors.json`; signatures are checked against the key states their replay establishes. Decode and verify the envelope (envelope wire format §8). An accepted envelope gives `expected`: its identity, snapshot SAID, issuer, current owner, history hash (hex) and number of transfers. A refused one gives the `error` class. - **`state_vectors.json`** — `genesis` is a genesis state: as typed JSON (`state`), as bytes (`cbor_hex`), and its `state_hash`. Apply each of `frames` in order, as the cluster state spec (§4) describes: `inputs` are the frame's inputs as submitted, each as bytes (`cbor_hex`) and, when they are canonical CBOR, typed JSON (`input`). `expected_results` gives one result per distinct input, in the order they apply: the input's BLAKE3 digest, the `class` it names (null if none), and `outcome` — `applied`, or `dropped` with the `error` class. Then the state must be `expected_state_cbor_hex`, also given as typed JSON, with hash `expected_state_hash`. - **`lockstep_vectors.json`** — `node_count` is the cluster size n. `node_reported_hashes` are the opaque 32-byte state hashes each node broadcast, and `node_computed_hashes` the ones each computed; they differ only for a node that lies on the wire (treatise §13.4). Hashes are compared only for equality. The IUT computes `expected_threshold` (floor(2n/3)+1), then `expected_commit` and `expected_state_hash` from the reported hashes — the frame's public record. It also computes each node's `expected_outcomes` (`clean_commit`, `commit_with_dissent`, `minority_divergence` or `cluster_split`) and `expected_dissent_logs` from that node's own view: its peers' reported hashes plus the hash it computed. - **`gossip_vectors.json`** — `deliveries` arrive at `receiver` in order, each with the SAID it was announced or requested under (`claimed_said`) and its bytes (`cbor_hex`). Each result is `accepted` or `duplicate` with the SAID, or `rejected` with an error class, per identity spec §2.6. `expected_held` lists what the receiver holds afterwards, in acceptance order: each SAID with exactly the bytes delivered. ### Error classes | Class | Meaning | | ------------------------- | ---------------------------------------------------------- | | `non_shortest_integer` | an integer not written with its shortest head | | `non_shortest_length` | a string, array or map length not written with its shortest head | | `unsorted_map_keys` | map keys not in the bytewise order of their encodings | | `duplicate_map_key` | a key appears twice in one map | | `float_forbidden` | a floating-point value | | `simple_value_forbidden` | a simple value other than `false`, `true` and `null` | | `tag_forbidden` | a CBOR tag | | `indefinite_length` | an indefinite-length item | | `invalid_utf8` | a text string that is not UTF-8 | | `trailing_bytes` | bytes after the one top-level item | | `said_mismatch` | content that does not hash to the SAID it states or arrived under | | `signature_invalid` | a signature that does not verify, including a non-canonical S | | `malformed_signature` | an Ed25519 signature that is not 64 bytes | | `malformed_verifying_key` | a key that is not 32 bytes, or encodes y outside the field | | `unsupported_algorithm` | a key and signature in an algorithm other than Ed25519 (`01`) | | `algorithm_mismatch` | a signature whose algorithm identifier differs from its key's | | `key_index_out_of_range` | a signature by a key index outside the key set | | `duplicate_key_index` | one key's signature listed twice | | `below_threshold` | fewer valid signatures than the key state's threshold | | `unknown_key_state` | a signature naming an establishment event no log establishes | | `malformed_event` | canonical CBOR that is not exactly a v1 event: an undefined member, a member of the wrong type or form, an unknown type or an extension v1 does not define; or an event out of place, such as a log that does not begin with an inception or an inception with anchors | | `unsupported_version` | an event whose `v` is not `SBYTE10JSON` | | `unsupported_event_type` | a `rec` event, which v1 reserves, or a `dip`, which v1 does not define | | `out_of_sequence` | an event whose `s` does not follow the previous event's | | `prior_mismatch` | an event whose `p` is not the previous event's `d`, or an inception's that is not empty | | `controller_mismatch` | an event whose `i` is not its log's controller, or an inception's that is not the AID its SAID names | | `pre_rotation_mismatch` | a rotation revealing keys other than those committed to | | `rotation_not_permitted` | a rotation after an establishment event that committed to no next keys | | `interaction_changes_keys`| an interaction whose key fields differ from those in force | | `invalid_threshold` | a `kt` outside 1 to the number of keys, or an `nt` that is not 0 for no next keys and 1 to their number otherwise | | `duplicate_key` | a key or next-key digest listed twice in one event | | `not_an_envelope` | canonical CBOR that is not exactly an envelope: an undefined member, or a member of the wrong type or form | | `broken_history_chain` | a transition that does not continue the history it follows | | `not_current_owner` | a transition not signed away by the byte's current owner | | `frame_not_increasing` | a transition in the same frame as the one before it, or an earlier one | | `malformed_input` | canonical CBOR that is not exactly a frame input of a class there is | | `controller_exists` | an inception for a controller the cluster already holds | | `unknown_controller` | a key event or signature of a controller whose log the cluster does not hold | | `establishment_not_in_force` | a signature naming an establishment event other than the one in force | | `issuer_not_registered` | a mint by an issuer the registry does not list for the cargo's type | | `duplicate_byte` | a mint of a byte the cluster already holds | | `unknown_byte` | a transfer of a byte the cluster does not hold | | `wrong_frame` | a transfer or burn naming a frame other than the one applied | | `byte_retired` | an input acting on a byte that was burned | | `byte_exported` | an input acting on a byte that left for another cluster | | `wrong_cluster` | an input addressed to another cluster, or a cluster named as its own peer | | `unknown_cluster` | a byte sent to, or imported from, a cluster that is not a peer | | `duplicate_import` | an export certificate the cluster has imported already | | `certificate_mismatch` | an import whose envelope is not the byte its certificate certifies | | `stale_sequence` | a proposal made against a governance sequence number other than the cluster's | | `already_registered` | a proposal to add an issuer already registered for the cargo type | | `not_registered` | a proposal to remove an issuer not registered for the cargo type | | `not_a_peer` | a proposal to remove a cluster that is not a peer | | `no_electorate` | a proposal no issuer can approve | | `not_an_elector` | an approval by an issuer with no vote on the proposal | | `duplicate_approver` | one issuer's approval listed twice | | `below_quorum` | fewer approvals than a supermajority of the electorate | The reference implementation also reports `truncated`, `malformed`, `too_deep`, `not_a_map`, `missing_said_field` and `malformed_said` for inputs no vector exercises, and its extensions outside spec v1 have classes of their own. An input with several defects reports the first in stream order; a key-event log reports the first event refused. ### Completeness A pack is checked whole, not only vector by vector. Every file holds at least one vector, every vector the identity spec cites is present, and every error class in the table above is exercised by some vector — and no vector names a class the table does not list. Both checkers enforce this, so a pack cannot pass by losing vectors. ## Versioning rule Vectors are versioned **per spec version**, not per implementation, not per calendar release of the conformance pack. - `v1/` corresponds to spec v1. Once spec v1 is frozen, `v1/` is append-only in scope (additional vectors may be added that test the *same* canonical wire format) and never modifies existing vectors' `expected_*` outputs. - `v2/` will appear when (and only when) the spec moves to a new version that introduces an incompatible wire-format change. The compatibility matrix lives in `VERSIONS.md`. - In a frozen version, a bug in a vector pack (e.g. an `expected_*` field that disagrees with the spec) is fixed by superseding the vector with a new one whose `name` carries a suffix (`-fixed`, `-corrected`); the original is retained with a `"superseded_by"` field so historical IUTs that pinned to the old vector can detect the supersession. A DRAFT pack is regenerated in place instead, and every change is recorded in its `regeneration.md`. ## CI gate `../../.github/workflows/conformance.yml` is the repository-level CI gate. Every PR that touches `smart-byte/spec/` or `smart-byte/conformance/` must pass the gate. The gate validates: - Every JSON file in `v*/` parses as valid JSON. - The second implementation, `crosscheck/verify_v1.py`, agrees with every vector, and finds the pack complete (see Completeness). - Every spec change is accompanied by a vector-pack update (a PR that modifies `smart-byte/spec/` without modifying `smart-byte/conformance/` is rejected at the gate). Separately, `standards-build` runs `cargo test` in `smart-byte-rs` on every push. That checks every vector against the reference implementation, requires the published files to equal the generator's output byte for byte, and requires identity spec §2.4 to print the worked example's real values. The gate's purpose is procedural: a spec change without a vector change is either a clarifying edit (which should declare itself by also touching the vector pack's `regeneration.md` to record the editorial reason) or a silent wire-format change (which is prohibited). Either way, both directories move together. ## What the pack does not cover The pack is the wire-format gate. It does not cover: - Application-layer cargo schemas. Cargo is opaque to the substrate; what a USD-byte or a joule-byte or a sensor-reading-byte *means* is a layer-2 protocol concern. Layer-2 protocols ship their own vectors. - Performance. The pack tests correctness, not throughput, latency, or energy cost. Performance is documented in the reference implementation's benchmarks. - Operational concerns. Cluster topology choice, key custody, hardware procurement, regulatory compliance for specific cargo types — all out of scope for the substrate's conformance pack. - Witnessing, delegation and recovery. Spec v1 defines none of them, and a v1 verifier refuses events that use them; the reference implementation tests its extensions for them in its own suite. - Cluster membership: how nodes join and leave a cluster, and how a cluster splits or merges (treatise §11.2, §11.3.2). The state machine starts where membership is settled: a cluster's own nodes are outside its state, and its peers' nodes are in it. ## License The vector pack is licensed CC-BY-4.0. See `../../LICENSE` for the repository-level license terms.