Verifiable Provenance
Verifiable Provenance¶
Every decision and audit event the Authorisation Layer records is written to the Provenance Vault as a hash-chained, cryptographically signed entry. The signatures are asymmetric, so anyone (an auditor, a regulator, an insurer, your customer) can independently verify a record is authentic and untampered, using only Xybern's public key, while nobody, not even Xybern, can forge or backdate one.
Why this matters
A normal audit log only proves something if you trust the system that wrote it. Xybern's Vault is independently verifiable: the proof travels with the record, and you check it yourself with an open-source tool. "Trust us" becomes "verify it."
How an entry is signed¶
When an entry is written, three things are computed:
content_hash,SHA-256of the canonical content. Change one byte of the record and this changes.entry_hash,SHA-256ofsequence | previous_hash | content_hash | type | timestamp. Theprevious_hashlink chains every entry to the one before it, so no entry can be inserted, removed, or reordered without breaking the chain.- Signature, an ECDSA P-256 signature over the
entry_hash. The private key lives only inside AWS KMS (a hardware security module); Xybern signs via an instance role and the key material never leaves AWS.
Because the signature is asymmetric, the public key is enough to verify it, and is published openly. The earlier HMAC scheme was symmetric: the only party who could verify could also forge, so it proved nothing to an outsider. Asymmetric signing removes that limitation entirely.
What a signed entry looks like¶
{
"entry_id": "ve_37270a09…",
"sequence_number": 3812,
"entry_type": "audit_event",
"previous_hash": "…",
"content_hash": "ddbc4272a12c23a0…",
"entry_hash": "acc65aa48e84af32…",
"signed_at": "2026-06-22T13:38:09.063055",
"content": { "action": "wire_transfer", "amount_usd": 50000, "decision": "block" },
"signature": {
"key_id": "vsk_4e0ce1ca…",
"algorithm": "ECDSA_P256_SHA256",
"value": "MEUCIFiC0uCOF0Ghg2mKOEMIgIIJ4dg2hNXOm2jltj6kDyDz…"
}
}
How to verify a record¶
Anyone can verify a Vault record without trusting, or having access to, Xybern. Three steps:
Step 1, Download the verifier. It's a single open-source Python file with one
dependency (cryptography). It contains no secrets and talks to nothing, you can
read every line.
Download verify.py · view source
Step 2, Get the proof bundle. A self-contained, offline-verifiable export (entries + signatures + public keys + the exact hashing recipe). From the Vault entry modal click Download proof bundle, or call the API:
curl -s "https://www.xybern.com/api/sentinel/vault/v2/proof-bundle?workspace_id=YOUR_WS&download=1" -o bundle.json
The signing public keys are bundled inside, and are also published openly (no
auth) at https://www.xybern.com/api/sentinel/vault/v2/keys, so you can confirm
you're checking against Xybern's real keys.
Step 3, Verify.
The verifier independently recomputes every content hash and entry hash, checks the hash-chain links (so nothing was inserted, removed, or reordered), and verifies each ECDSA P-256 signature against the published public key. Tamper with a single field and it rejects the record immediately, naming the entry:
Exit code is 0 when fully verified and 1 on any problem, so it slots straight
into a CI pipeline or an auditor's script.
You don't even need our tool
The signature scheme below is fully specified, so a third party can write their own verifier in any language and trust nothing of ours. The proof travels with the record: keep the bundle and you can prove an action was authorised and untampered even if Xybern no longer exists.
The signature recipe (re-implementable in any language)¶
| Step | Formula |
|---|---|
| Content hash | sha256(json.dumps(content, sort_keys=True, separators=(',',':'))) |
| Entry hash | sha256(f"{sequence}\|{previous_hash or 'genesis'}\|{content_hash}\|{entry_type}\|{signed_at}") |
| Signature | ECDSA P-256 over SHA-256 of entry_hash (hex, UTF-8); DER-encoded, base64-stored |
signed_at is the exact timestamp string that was hashed (ISO-8601, no trailing
Z) and is included in the bundle, so entry_hash can be recomputed byte-for-byte.
Policy-version binding¶
A proof is far stronger when it says which rule produced the verdict, not just the verdict. Every enforcement decision is therefore bound to the exact ruleset that governed it:
- The signed Vault entry includes a
policy_binding: apolicy_set_hashidentifying the precise set of active policies in force at decision time, the policy count, and a self-contained snapshot of the rules that actually fired (each with its ownrule_hash, conditions, and decision). - The first time a given
policy_set_hashis seen, Xybern snapshots the full ruleset. So even after a policy is later edited or deleted, the hash still resolves to the exact rules that were in force, viaGET /api/sentinel/enforcement/policy-set/{policy_set_hash}.
The result: a decision's proof reads as "at time T, policy-set H (these exact
rules) produced verdict BLOCK for this action", and that statement stays
verifiable forever, because both the binding (in the signed entry) and the ruleset
snapshot are immutable. In the Vault entry view, the Policy-version binding
section shows the set hash, the rules that fired, and a View full ruleset action.
External anchoring, provable ordering and time¶
Signatures prove who and what; anchoring proves when and in what order, without trusting Xybern's clock. Periodically the Vault emits a checkpoint: a single Merkle root ("tree head") committing every entry, signed with the KMS key. That root is then anchored to two independent external sources: an RFC-3161 Time-Stamp Authority (a signed token proving the root existed before time T) and OpenTimestamps, which commits the root into the Bitcoin blockchain for public-chain-grade permanence with no trusted third party at all. Either one proves the root, and therefore every entry beneath it, existed at a point in time; Xybern cannot backdate or reorder the log without the external anchors no longer matching.
Any single record can prove it belongs under a signed root via a Merkle
inclusion proof, so a one-line certificate shows
"this record is part of a signed, externally-timestamped commitment to the whole
log." The open-source verifier checks the inclusion proof and the checkpoint
signature automatically; the RFC-3161 token is independently verifiable with
standard tools (openssl ts -verify).
Per-decision certificate¶
For a single action you can export a certificate, one entry plus its
signature, the public keys, the hashing recipe, and (if a checkpoint covers it) the
Merkle inclusion proof to the signed, timestamped root. It's a self-contained,
shareable proof for one decision (a dispute, an audit of a single transaction)
that you can hand to a third party without exposing the rest of the Vault, and it
verifies with the same verify.py. Available from the Vault entry view
(Download certificate) or GET /api/sentinel/vault/v2/entries/{id}/certificate.
What this proves¶
- Authenticity, the entry was issued by Xybern (only the KMS private key could produce a signature that verifies against the published public key).
- Integrity, the content and every chained field are exactly as recorded.
- Order & completeness, no entry was inserted, removed, or reordered.
- Non-forgeability, because the private key lives only in the KMS HSM, not even Xybern can fabricate or alter a record after the fact.
This is the evidentiary foundation for compliance regimes such as EU AI Act Article 12 (record-keeping), ISO 42001, and SOC 2.
Anchors
Each checkpoint root is anchored by RFC-3161 (verify with openssl ts -verify)
and OpenTimestamps/Bitcoin (verify with ots verify). OTS proofs start as
pending and are upgraded to a confirmed Bitcoin attestation automatically once a
block includes them (typically within hours).