Selective-disclosure extension for draft-farley-acta-signed-receipts. Demonstrates SHA-256 field commitments, algorithm-agile signing, chain integrity without openings, and composition with longfellow-zk/libzk for privacy-preserving principal bindings.
Conceptually aligned with SD-JWT (salt-hash selective disclosure for JWTs), adapted for chained audit logs rather than bearer tokens.
An AI agent's audit trail records what happened. A regulator needs to verify the trail is intact. But revealing the full trail also reveals internal operations, business logic, and potentially personal data.
Today's receipt format signs cleartext fields. Anyone who holds a receipt sees everything: principal, action, resource, decision, policy. The only privacy primitive is the VOPRF issuer-blind nullifier, which hides the signer's identity but not the signed content.
This is a structural tension between two requirements:
- EU AI Act Article 12: Log everything. Complete, tamper-evident records.
- GDPR data minimization: Reveal only what is necessary for the purpose.
Cleartext receipts satisfy Article 12 but violate minimization. Omitting fields satisfies minimization but violates Article 12.
Each receipt field is independently committed using SHA-256:
commitment = SHA-256(salt || field_value)
The salt is prepended (following the SD-JWT convention) so the commitment is domain-separated by random bytes before any attacker-controlled value bytes.
The receipt payload contains only the commitments, never cleartext:
{
"type": "scopeblind.receipt.committed.v1",
"commitments": {
"principal": "a8f3c9d2...",
"action": "7b2e1d4f...",
"resource": "4c9a3f88...",
"decision": "e1b7f2a3...",
"context": "d5c8e9f1...",
"policy_id": "9d4e6abc..."
},
"sequence": 47,
"timestamp": "2026-04-22T14:30:00Z",
"previousReceiptHash": "sha256:...",
"signature": "ed25519:...",
"publicKey": "ed25519:..."
}To reveal a field to an auditor, the receipt owner provides the cleartext value and its salt. The auditor verifies:
SHA-256(salt || disclosed_value) == commitment
If the hash matches, the disclosed value is authentic. Other fields remain committed.
This is designed to address both requirements simultaneously:
- Article 12: Complete records exist (all fields are committed and chain-linked). The chain is tamper-evident and verifiable without openings.
- GDPR minimization: Only the fields the auditor needs are disclosed. The rest remain cryptographically hidden.
BBS+ signatures provide native selective disclosure but are pairing-based (BLS12-381) and not post-quantum resistant. SHA-256 commitments:
- Are post-quantum safe (128-bit security against quantum attacks).
- Decouple selective disclosure from the signature scheme entirely.
- Work with any outer signature (Ed25519 today, ML-DSA tomorrow).
- Have no exotic dependencies (every platform has SHA-256).
SHA-256 commitments support selective disclosure (opening individual fields). If future use cases require arithmetic proofs over committed values without opening them (e.g., "prove the committed timestamp is within business hours without revealing the exact time"), the commitment primitive can migrate to Poseidon2 or Pedersen commitments without changing the receipt envelope structure.
The outer signature covers the commitment vector. The signature scheme is structurally independent of the commitment layer. Swapping Ed25519 for ML-DSA (FIPS 204) changes one function call without touching the commitment structure, chain logic, or disclosure mechanism.
draft-google-cfrg-libzk proves properties of existing credentials (mdoc, JWT, W3C VC) without revealing the credential. The two drafts compose at the commitment layer:
- The receipt's
commitment_principalcommits to the holder's identity. - Instead of opening the commitment with a cleartext principal, the holder provides a libzk proof that the committed value satisfies a predicate (e.g., "age >= 18 AND EU resident").
- The verifier checks:
- (a) libzk proof is valid (libzk verifier)
- (b) proof's context binding matches this receipt's hash (anti-replay)
- (c) proof attests the committed principal satisfies the predicate
- (d) receipt signature is valid over the full commitment vector
- (e) receipt chain is intact
A valid libzk proof must be bound to the specific receipt it was created for. Otherwise, an attacker could extract a proof from one receipt and replay it in another. The proof's public inputs should include the receipt's commitment vector hash, so the verifier can confirm the proof was generated for this exact receipt.
No in-circuit signature verification is required. The receipt's signing curve (Ed25519 or ML-DSA) and libzk's proving curve (ECDSA-P256 for existing credentials) are completely independent. The binding is a hash-layer reference, not a curve-layer operation.
Per-field salts are derived deterministically via HMAC-SHA256:
salt = HMAC-SHA256(masterSecret, "commitment-receipt:{sequence}:{fieldName}")
The receipt owner stores one master secret, not individual salts. The master secret MUST be stored securely and separately from the receipts.
- Master secret compromise: all historical salts are derivable, making all past commitments openable. Equivalent to retroactive cleartext. Mitigation: hardware-backed key storage (HSM, TPM, Secure Enclave).
- Individual salt leak: only that specific field of that specific receipt becomes openable. No cascading exposure.
- Master secret loss: all commitments become permanently unopenable. This is a feature for GDPR erasure (see below) but a severe hazard if unintentional. Mitigation: key backup procedures.
Future versions may support forward-secret salt derivation (per-epoch key rotation via HKDF) so that compromise of the current key does not expose historical salts.
When a data subject invokes the right to erasure:
- Destroy the master secret (or the per-subject derivation key).
- All commitments for that subject become permanently unopenable.
- The chain remains intact and verifiable (chain hashes are over commitments, not cleartext).
- An auditor can verify: "a receipt existed at this position with a valid signature and chain link, but its contents are cryptographically irrecoverable."
This is a structural guarantee, not an operational promise.
cd examples/commitment-receipts
npm install
npm run demoThe demo creates a chain of 3 committed receipts, verifies chain integrity without revealing any field values, demonstrates selective disclosure to a simulated auditor, demonstrates tamper detection when a false value is claimed, and stubs in the libzk proof binding surface with context binding for anti-replay.
| Property | Cleartext receipts | Committed receipts |
|---|---|---|
| Receipt size | ~200-400 bytes | ~500-700 bytes (32-byte hash per field) |
| Verification | Single-pass | Two-pass (signature + openings) |
| Debugging | Inspect receipt directly | Need salts to inspect contents |
| Privacy | None (all fields visible) | Full (selective disclosure per field) |
| PQ safety (commitments) | N/A | Yes (SHA-256) |
| PQ safety (signature) | Depends on scheme | Depends on scheme (decoupled) |
| Salt management | None | Must protect master secret |
| GDPR erasure | Delete data | Destroy master secret (provable) |
Both modes should coexist. Cleartext is the right default for the Transparent tier. Committed mode is for Auditable and High-Assurance tiers where privacy-preserving compliance is required.
This is an optional commitment envelope for the receipt format. The next revision of the draft will define commitment mode as an opt-in extension alongside the existing cleartext format. The wire format, chain structure, and verification semantics are fully backwards compatible.
Apache-2.0