Ucotron Cortex

Reportes firmados

Verificar la firma con /v1/verify y offline con la clave pública.

Un reporte aprobado trae siempre su manifiesto firmado. El contrato lo impone: aprobado sin firma no es evidencia verificable, y publicarlo como si lo fuera es peor que no tener firma.

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)'

Verificación offline

La firma es Ed25519 sobre el payload canonicalizado (json-stable-stringify-v1). No hace falta red, base de datos ni runtime: con la clave pública publicada alcanza. Eso es lo que hace que el reporte siga siendo verificable cuando Ucotron no está del otro lado.

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

Verificar un PDF de peritaje

Los tres productos —rayos, edificación y peritaje— firman igual, y el PDF trae todo lo necesario en su bloque de procedencia al pie: el hash de contenido, la firma, el identificador de la clave y la URL donde está publicada.

El directorio de claves es público y no pide credenciales. Es deliberado: quien tiene que poder comprobar un peritaje es un perito de contraparte, un tribunal o un reasegurador, y ninguno de ellos tiene cuenta con nosotros. Un documento cuya verificación exige credenciales del emisor no es evidencia.

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

Devuelve cada clave con su ventana de vigencia. Las retiradas se siguen publicando, con su retiredAt: una firma hecha mientras la clave estaba vigente sigue siendo válida, y borrarla dejaría sin verificar todo lo que se emitió en ese período.

Con eso, la comprobación es de una línea y no habla con nosotros:

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

// Los tres valores salen del bloque de procedencia impreso en el PDF.
const contentHash = "…";   // 64 caracteres hexadecimales
const signature = "…";     // base64url
const publicKey = "…";     // el campo `publicKey` de la clave que el PDF nombra

const valido = verify(
  null,
  Buffer.from(contentHash),                    // se firma el hash en ASCII, tal cual
  createPublicKey({
    key: Buffer.from(publicKey, "base64url"),
    format: "der",
    type: "spki",
  }),
  Buffer.from(signature, "base64url"),
);

Lo que esto prueba y lo que no: prueba que el documento lo emitimos nosotros y que su contenido no cambió desde entonces. No prueba que el análisis sea correcto — para eso está la sección de limitaciones de cada informe, y la procedencia que dice sobre qué escenas se calculó.

Por qué no es un HMAC

Un HMAC lo verifica quien tiene el secreto con el que se hizo, es decir quien puede fabricarlo. Sirve para detectar corrupción, no para probar autoría ante un tercero, que es lo único que le importa a un expediente. Con Ed25519 la privada firma y la pública verifica, y esa asimetría es toda la diferencia: entregamos la clave a quien la pida sin habilitar a nadie a firmar en nuestro nombre.

La privada vive en un custodio de secretos y nunca sale de ahí — no está en el repositorio, ni en una variable de entorno, ni en el bundle del navegador. Y el emisor se niega a firmar si su clave no figura en el directorio público: un documento firmado con una clave que nadie puede verificar sería el peor error posible, porque se descubre recién cuando alguien audita.

Lo que se rompe

Un manifiesto con clave o firma corrupta devuelve "no verifica", no un 500. Un 500 ante entrada hostil le confirma al atacante que llegó a tocar el motor y le niega al integrador honesto la única respuesta que le sirve.

Un manifiesto alterado devuelve 422 con invalid_ed25519_signature, incluso si el resto del documento está intacto.

Endpoints de esta guía

On this page