Some checks failed
smart-byte conformance gate / Validate conformance JSON files (pull_request) Successful in 4s
smart-byte conformance gate / Second implementation reproduces every vector (pull_request) Successful in 5s
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 4s
standards workspaces build + test / eoc-rs (pull_request) Successful in 1m51s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m24s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m41s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m10s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 3m38s
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 7m45s
standards workspaces build + test / aac-rs (pull_request) Successful in 4m3s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 1m51s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 2m3s
standards workspaces build + test / map-rs (pull_request) Successful in 5m27s
standards workspaces build + test / mesh-rs (pull_request) Successful in 2m19s
standards workspaces build + test / openpay (pull_request) Successful in 10m49s
standards workspaces build + test / arl-rs (pull_request) Successful in 47m4s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 46m44s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 42m33s
standards workspaces build + test / joulehook-rs (pull_request) Failing after 18m22s
standards workspaces build + test / sense-rs (pull_request) Successful in 10m25s
standards workspaces build + test / macOS energy backend (jcp-energy) (pull_request) Successful in 4m56s
smart-byte-state implements cluster_state.md: - governance: Change and Proposal, electorates, the quorum, the time lock; - burns: Burn and the retired record; - exports: ExportStatement and Certificate; - the import checks. smart-byte-core: Transition::sign_export and the destination member. The envelope compares frames within a cluster only. smart-byte-lockstep: the committing nodes sign each committed frame's export certificates. The certificates are built from a committing node's report, never from a node whose state diverged. state_vectors.json grows to 23 vectors (state-013 to state-023 new, state-011 extended) and is regenerated in place; v1 is DRAFT. Every new check has a vector that fails an implementation leaving it out. The README documents the 14 new error classes, and the regeneration log records the change. crosscheck/verify_v1.py implements governance, burns, exports and imports from the spec text. It reproduces every vector, 117 of them, both plain and under -O. Co-Authored-By: Claude Opus 5.5 <[email protected]>
295 lines
19 KiB
Markdown
295 lines
19 KiB
Markdown
# 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.
|