Skip to content

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:

  1. content_hash, SHA-256 of the canonical content. Change one byte of the record and this changes.
  2. entry_hash, SHA-256 of sequence | previous_hash | content_hash | type | timestamp. The previous_hash link chains every entry to the one before it, so no entry can be inserted, removed, or reordered without breaking the chain.
  3. 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.

curl -O https://docs.xybern.com/authorization/verify.py
pip install cryptography

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.

python verify.py bundle.json
✓ VERIFIED — all 1432 entries are authentic, untampered, and correctly chained.

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:

✗ FAILED — content hash mismatch (content was altered)

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: a policy_set_hash identifying 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 own rule_hash, conditions, and decision).
  • The first time a given policy_set_hash is 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, via GET /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).