Specification · version 1.0 · 23 September 2026

Open evidence specification

Every byte RiskRouter’s evidence commits to, written down so that anyone can check it without us, and anyone can build a second implementation. Two already exist that share no code: the JavaScript verifier and a Python one, held to identical verdicts by the repository’s tests. Anyone may implement this specification without asking us.

0 · Conventions

Conventions

1 · v1 quote ledger

The quote ledger: a hash chain

1.1 Canonical form (frozen)

chain_index|id|created_at|active_vertical|components|premium|compliance_status|matrix_version|distributor_id|prev_hash

1.2 Attestation (frozen payload)

riskrouter-ledger-attestation|v1|as_of|entries|head_hash

The signed statement that the ledger had entries entries and that head at as_of. A verifier rebuilds this payload from the attestation’s own fields; it never trusts a signed_payload field alone.

1.3 Export and single-entry proof

An export (riskrouter-ledger-export|v1) carries the holder’s entries in full, the (chain_index, row_hash, prev_hash) of every entry, and the signed attestation. A single-entry proof (riskrouter-entry-proof|v1) carries one entry, the links from it to the head, and the attestation. What these prove, precisely: the holder’s entries are rebuilt from content; other entries are followed by digest only and bind because anyone recomputing the full ledger must reach the signed head. A rebuilt ledger is caught by comparing with a head recorded earlier, which is why heads are anchored (section 4).

2 · v2 evidence log

The evidence log: digests of any regulated decision, in a Merkle tree

2.1 What the client sends

The client hashes its own record and sends only record_digest and a kind tag (^[a-z0-9][a-z0-9.-]{0,39}$). The record never leaves the client.

The client must salt the record. Hash a random value of at least 128 bits together with the record, and keep both. A digest of a guessable record, such as “approved”, can be confirmed by anyone who guesses it. The salted construction this specification recommends is SHA-256(salt || record), but any construction the client can reproduce is valid: the log commits to the digest, not to how it was made.

2.2 Leaf (frozen)

leaf string = riskrouter-evidence-leaf|v2|leaf_index|created_at|distributor_id|kind|record_digest
leaf hash   = SHA-256(0x00 || leaf string)

leaf_index counts from 0 with no gaps. created_at is set by the log, never by the client.

2.3 Tree

RFC 6962 / RFC 9162, unchanged: node hash = SHA-256(0x01 || left || right), with the split at the largest power of two smaller than the number of leaves; the root of the empty tree is SHA-256 of the empty string. Inclusion and consistency proofs are the RFC 9162 section 2.1.3 and 2.1.4 constructions, verified by the algorithms given there. The implementation is checked against RFC 6962’s own reference roots.

2.4 Signed tree head (frozen payload)

riskrouter-evidence-head|v2|tree_size|root_hash|timestamp

Signed with the same key as v1 attestations. A verifier must take tree_size and root_hash from the same signed head: an inclusion proof does not bind the tree size on its own. A head with no key bound is returned unsigned and says so; it is never given a signature it does not have.

2.5 Endpoints

POST /api/v2/evidence, GET /api/v2/evidence/head, GET /api/v2/evidence/proof and GET /api/v2/evidence/consistency, described in the API reference and openapi.json. A consistency proof is checked against the root of a head the verifier saved, never a root the log supplies.

3 · Witnesses

Independent witnesses

A witness is anyone who is not the operator. Each time it runs it fetches the current signed head, checks the operator’s signature, requests a consistency proof from the last head it saved, and checks that proof against the root it saved. Only if every check passes does it co-sign:

riskrouter-evidence-cosign|v2|witness_id|tree_size|root_hash|cosigned_at

If a check fails, the witness holds two heads, both signed by the operator, that cannot both be true. That pair is evidence of a rewritten history that the operator cannot disown, produced by someone the operator does not control. The reference witness is tools/witness.mjs; it saves the pair and exits with status 2. There are no independent witnesses yet. How to become one is in witnesses/README.md.

4 · Anchoring

Anchoring heads in time

Signed v1 heads are committed to OpenTimestamps calendars and upgraded to Bitcoin attestations; the 13 September 2026 head is in block 967830 and later. This bounds when a head existed, from above. It says nothing about the moment of each entry before it.

5 · Test vectors and implementations

Check your implementation

spec-vectors.json holds deterministic inputs and expected outputs for every frozen form above: timestamp normalisation, a v1 canonical form and row hash, the attestation payload, eight v2 leaves and the roots of every prefix, an inclusion proof, a consistency proof, a head payload and a cosignature payload. It is regenerated from the reference code and both verifiers must agree with it before any change ships.

A defect in this specification, or a disagreement between it and any implementation, is a bug we want to hear about: security explains how to report one.