Skip to content

Sign & verify evidence

🟡 Partial — ECDSA-P256+DSSE+in-toto signing done; pluggable backend (local + Scaleway KMS); keyless identity anchoring (Sigstore) pending.

Every output of the froga engine — the evidence bundle, the ISO 23894 cycle reconstruct, the Annex IV, and conformance reports — is signed before being written to disk. Signing is an explicit requirement of the EU AI Act: Annex IV §2(g) requires that evaluation records be “dated and signed”. Cryptographic integrity makes any subsequent modification detectable.


Signing scheme: ECDSA-P256 (RFC 6979) + DSSE + in-toto

Section titled “Signing scheme: ECDSA-P256 (RFC 6979) + DSSE + in-toto”

Each .froga/* artifact has a sibling .sig file containing a DSSE envelope (Dead Simple Signing Envelope) with an in-toto Statement signed with ECDSA over the NIST P-256 curve (SHA-256). Signatures are deterministic (RFC 6979): the same key over the same content always yields the same value.

The signing flow is:

  1. The artifact JSON is serialized in JCS canonical form (RFC 8785) — stable, idempotent bytes regardless of key ordering.
  2. An in-toto Statement is constructed whose subject is the SHA-256 digest of the canonical JSON. This binds the signature to the exact artifact content.
  3. The Statement becomes the payload of the DSSE envelope. The envelope applies Pre-Authentication Encoding (PAE) before signing.
  4. Signed with the p256 crate (RustCrypto). The signature is stored as raw r‖s (64 bytes) base64-encoded inside the envelope.
  5. The serialized envelope is written to <artifact>.sig.

The envelope’s keyid is hex(SHA-256(uncompressed SEC1 pubkey, 65 B)): it identifies the public key (0x04 ‖ X ‖ Y) without transporting the whole key and anchors verification to the expected key.

bundle.json ← evidence bundle (gate, controls, risk analysis…)
bundle.json.sig ← DSSE envelope · in-toto Statement · ECDSA-P256 signed

The same applies to reconstruct.json and conformance/<slug>.json.

The signer is pluggable via the FROGA_SIGNING_BACKEND environment variable:

  • local (default) — local ECDSA-P256 key. The private scalar is read from FROGA_SIGNING_KEY (32 bytes in hex, 64 characters); if not set, a fixed, known development scalar is used — it provides integrity/tamper-evidence only, not non-repudiation.
  • scaleway-kms — signing is delegated to the Scaleway Key Manager (algorithm EC-P256-SHA256); the private key never leaves the KMS. The verifying pubkey is obtained from the KMS in uncompressed SEC1 form (65 B).

froga pubkey prints the verifying pubkey of the configured signer (uncompressed SEC1, 130-character hex). The customer exports it and registers it per connection in the cloud (ScmConnection.signerPubkey), so the cloud verifies each piece of evidence against the expected key of THAT connection.


froga verify verifies the entire evidence dossier present in .froga/bundle.json, reconstruct.json, conformance/*.json and risk-projection/*.json, each against its .sig (entitlement.json is excluded: it is signed by the Venturalítica issuer, not by the system; and a dossier artifact missing its .sig is a failure). For each piece it checks four conditions in sequence:

  1. The envelope is a valid DSSE JSON with payloadType: "application/vnd.in-toto+json".
  2. The envelope’s keyid matches hex(SHA-256(SEC1 pubkey)) of the resolved public key (see below).
  3. The ECDSA-P256 signature (raw r‖s) over the PAE is valid against that public key.
  4. The Statement’s subject.sha256 matches the digest of the given artifact.

If all pass on every piece, the command prints froga verify: firma VÁLIDA and returns exit 0. If any condition fails, it returns exit 1 with a descriptive message.

Verify the integrity of the evidence dossier
froga verify --repo /path/to/project --pubkey 04a1b2…

Expected output after a successful froga run:

froga verify: firma VÁLIDA

froga verify is the authoritative verifier. The cloud plane implements the same logic in TypeScript with @noble/curves (P-256) and verifies per connection against the signerPubkey registered for that ScmConnection, in a fail-loud manner: if the connection has no configured pubkey, the cloud does not verify silently but fails. Even so, the definitive result is always from the froga binary; cloud verification is a convenience and MUST match.


ScenarioDetected by froga verify?
Bundle manually modified after froga runYes — SHA-256 digest of the subject does not match
.sig replaced by a valid one from another bundleYes — subject points to the original bundle
Forged signature with a different keyYes — the keyid does not match the resolved pubkey (--pubkey / PUBKEY.txt)
Valid signature over intact contentNot a problem — result: VALID

The signature does not cover git history integrity: froga reconstruct — which replays bundle commits — is the mechanism for verifying temporal lineage.


Keyless identity anchoring (Sigstore / Fulcio / Rekor) is pending. This mechanism would associate the public key with a verified OIDC identity (e.g. a CI/CD account) and deposit a transparency log entry in Rekor, eliminating the dependency on the verifier knowing the public key in advance.

The crate guard reflects this state: the froga-sign module explicitly names “keyless identity anchoring pending” in its internal documentation.

For more detail on .froga/* artifacts and their envelope structure, see Reference: .froga/* artifacts. For context on current engine incompleteness, see State & incompleteness.