DEV Community

João André Gomes Marques
João André Gomes Marques

Posted on • Originally published at asqav.com

ML-DSA receipts in COSE for SCITT transparency services

SCITT, the Supply Chain Integrity, Transparency, and Trust working group at IETF, is the standards track for append-only logs that hold cryptographic statements about software, models, and now AI agent actions. Transparency services consume signed statements, append them to a Merkle log, and hand back inclusion proofs. The receipts have to be in a format the service understands. That format is COSE.

Asqav now exports any signed receipt as a COSE_Sign1 envelope on demand, so you can submit Asqav-signed agent actions to a SCITT transparency service.

Why SCITT

An organisation that wants third-party-verifiable evidence of agent behaviour does not want to run its own append-only log. SCITT separates the signer from the log operator. The signer says "I claim this happened". The transparency service says "I have witnessed and ordered this claim". A consumer checks both signatures and gets non-repudiable, ordered evidence without trusting either party alone.

For AI Act Article 12 records, DORA operational logs, and SOC 2 system descriptions, this two-party setup is what auditors expect for the next decade. Self-hosted log files do not pass that bar.

What COSE_Sign1 looks like

COSE, RFC 9052, is the CBOR analogue of JOSE. A COSE_Sign1 message is a four-element CBOR array:

  1. protected header, a serialised CBOR map with the algorithm and any required parameters
  2. unprotected header, a CBOR map for hints like the key ID
  3. payload, the bytes being signed (canonicalised receipt JSON)
  4. signature, the raw ML-DSA-65 signature over the Sig_structure

The whole envelope is wrapped with CBOR tag 18, which is how COSE labels a single-signer message. ML-DSA is registered with COSE algorithm identifiers in the IANA COSE Algorithms registry under the post-quantum group. Asqav uses ML-DSA-65, which corresponds to FIPS 204 parameter set 3.

The export endpoint

The on-demand export does not change the original receipt. It re-serialises the canonical JSON payload into a COSE_Sign1 envelope and re-signs with the same ML-DSA-65 key. The original JSON receipt and the COSE form are both first-class records, both verifiable independently.

curl -H "Authorization: Bearer $ASQAV_KEY" \
  -H "Accept: application/cose" \
  https://api.asqav.com/v1/receipts/{receipt_id}/cose \
  -o receipt.cose
Enter fullscreen mode Exit fullscreen mode

The response is the raw COSE_Sign1 bytes. Submit that to your SCITT transparency service of choice and you get an inclusion receipt back.

Verifying it locally

You do not need a SCITT service to inspect what came back. The CBOR is parseable with any COSE library. Here is a check using cbor2 in Python:

import cbor2

with open("receipt.cose", "rb") as f:
    msg = cbor2.load(f)

# tagged value, tag 18 = COSE_Sign1
assert msg.tag == 18
protected, unprotected, payload, signature = msg.value

protected_map = cbor2.loads(protected)
print("alg label:", protected_map.get(1))   # 1 = alg
print("payload bytes:", len(payload))
print("signature bytes:", len(signature))   # ML-DSA-65 sig is 3309 bytes
Enter fullscreen mode Exit fullscreen mode

The payload is the same RFC 8785 canonical JSON object you get from the regular receipt endpoint, so a verifier can re-extract it and compare against the JSON form for free. The signature is over the standard COSE Sig_structure, which means any compliant COSE verifier with an ML-DSA-65 implementation will accept it.

One key, two formats

The ML-DSA-65 key that signs your normal receipts is the same key that signs the COSE form. Auditors only have to learn one public key, and they can pick whichever envelope format their tooling supports. The two are bit-equivalent in payload, only the framing differs.

Where to start

Run a SCITT transparency service in front of your Asqav signer and you have a two-party audit trail that any third party can verify, without contacting either of you.

Top comments (0)