Ucotron Cortex

Signed reports

Verify the signature with /v1/verify and offline with the public key.

An approved report always carries its signed manifest. The contract enforces it: approved without a signature is not verifiable evidence, and publishing it as if it were is worse than having no signature at all.

Diagrama: Reportes firmadosSecuencia de reporte firmado: generar el reporte, descargar el manifiesto con firma Ed25519 y verificarlo offline sin llamar a la API.SBOXClienteSBOXClienteo verificación offline con la clave pública publicadaGET /v1/reports/{id}200 · signature { manifestId, algorithm: Ed25519, publicKeyId }POST /v1/verify200 · verified_ed25519_offline
Diagrama: Reportes firmadosSecuencia de reporte firmado: generar el reporte, descargar el manifiesto con firma Ed25519 y verificarlo offline sin llamar a la API.SBOXClienteSBOXClienteo verificación offline con la clave pública publicadaGET /v1/reports/{id}200 · signature { manifestId, algorithm: Ed25519, publicKeyId }POST /v1/verify200 · verified_ed25519_offline
Fuente del diagrama (mermaid)
sequenceDiagram
    participant C as Cliente
    participant S as SBOX
    C->>S: GET /v1/reports/{id}
    S-->>C: 200 · signature { manifestId, algorithm: Ed25519, publicKeyId }
    C->>S: POST /v1/verify
    S-->>C: 200 · verified_ed25519_offline
    Note over C: o verificación offline con la clave pública publicada
bash
curl -sS "$SBOX/v1/reports" -H "authorization: Bearer $TOKEN" | jq '.data[] | select(.signature != null)'

Offline verification

The signature is Ed25519 over the canonicalized payload (json-stable-stringify-v1). No network, database or runtime needed: the published public key is enough. That is what keeps the report verifiable when Ucotron is not on the other side.

Firma y verificación offline de un reporte La API genera el reporte y entrega un manifiesto con el payload canónico, la firma Ed25519 y la clave pública. La verificación ocurre del lado del integrador, sin llamar a la API: se canonicaliza el payload y se verifica la firma contra la clave pública; el resultado es válido o inválido. Cualquier byte alterado invalida la firma. Lado API reports:read · una vez REPORTE generado MANIFIESTO payload canónico · firma Ed25519 clave pública de acá en adelante, sin red descarga única Lado integrador — verificación offline sin llamar a la API, sin token canonicalJson(payload) bytes deterministas verify(firma, clave pública, bytes) VÁLIDO INVÁLIDO un byte alterado en el payload — un monto, una fecha — invalida la firma: eso es lo que hace al reporte evidencia y no un PDF más

Verifying a damage assessment PDF

All three products — lightning, buildings and damage — sign the same way, and the PDF carries everything you need in its provenance block at the foot: the content hash, the signature, the key identifier and the URL where that key is published.

The key directory is public and asks for no credentials. That is deliberate: the people who need to check an assessment are an opposing expert, a court or a reinsurer, and none of them has an account with us. A document whose verification requires the issuer's credentials is not evidence.

bash
curl -s https://app.ucotron.com/v1/verify/keys

It returns every key with its validity window. Retired keys stay published, carrying their retiredAt: a signature made while the key was active remains valid, and removing it would strand everything issued in that period.

With that, the check is one call and it does not talk to us:

js
import { createPublicKey, verify } from "node:crypto";

// All three values come from the provenance block printed in the PDF.
const contentHash = "…";   // 64 hex characters
const signature = "…";     // base64url
const publicKey = "…";     // the `publicKey` field of the key the PDF names

const valid = verify(
  null,
  Buffer.from(contentHash),                    // the hash is signed as ASCII, as printed
  createPublicKey({
    key: Buffer.from(publicKey, "base64url"),
    format: "der",
    type: "spki",
  }),
  Buffer.from(signature, "base64url"),
);

What this proves and what it does not: it proves we issued the document and that its content has not changed since. It does not prove the analysis is correct — that is what each report's limitations section is for, along with the provenance stating which scenes it was computed over.

Why not an HMAC

An HMAC is verified by whoever holds the secret it was made with — that is, by whoever could forge it. It detects corruption; it does not prove authorship to a third party, which is the only thing a case file cares about. With Ed25519 the private key signs and the public key verifies, and that asymmetry is the whole point: we hand the key to anyone who asks without enabling anyone to sign in our name.

The private key lives in a secret custodian and never leaves it — not in the repository, not in an environment variable, not in the browser bundle. And the issuer refuses to sign if its key is not in the public directory: a document signed with a key nobody can verify would be the worst possible failure, because it surfaces only when someone audits.

What breaks

A manifest with a corrupt key or signature returns "does not verify", not a 500. A 500 on hostile input confirms to the attacker that they reached the engine, and denies the honest integrator the only answer that helps them.

A tampered manifest returns 422 with invalid_ed25519_signature, even if the rest of the document is intact.

Endpoints in this guide

On this page