Provenance for AI-Assisted Work

Attested AI-Assisted Work

Draft Specification · v0.3

Status: Open for Comment

A receipt format for verifiable AI-assisted work. Model-provider neutral.


Context

Modern LLM providers operate the inference and hold the signing keys. Server-side integrity has established attestation paths in confidential-inference deployments (TEEs, Apple Private Cloud Compute), but the standard text-completion APIs ship without any per-completion provenance surface returned to the caller. What's missing is the inverse surface: a receipt the server returns to the client proving this prompt produced this output on this model at this time. This spec proposes that surface in a form any model provider can implement.


Primitive — the signed receipt

Receipt {
  receipt_id     : uuid          // unique per completion
  model_id       : string        // canonical model identifier (provider-namespaced)
  prompt_hash    : sha256        // hash of canonicalized request body
  output_hash    : sha256        // hash of full response body
  issued_at      : iso8601
  nonce          : base64url(16) // 128-bit random; required, prevents replay
  weight_hash    : sha256        // OPTIONAL; weights snapshot for TEE-attested issuers
  key_id         : string        // public-key reference for verification
  signature      : ed25519       // signature over all preceding fields
}

Prompt and output are hashed, not stored — content stays private; verifiability is preserved.

Field encodings (normative)


Canonicalization (normative)

The signature is computed over a canonical serialization of the receipt that issuer and verifier MUST reproduce byte-for-byte. This spec adopts RFC 8785 JSON Canonicalization Scheme (JCS) over the receipt object, with two rules:

  1. The signature field is omitted (it cannot sign itself).
  2. The OPTIONAL weight_hash field is omitted when absent (it is included only when an issuer populates it).

Because every receipt field is a JSON string, JCS reduces to a small, auditable construction: the object's members are emitted in lexicographically-sorted key order, with no insignificant whitespace, and string values escaped per JCS (only ", \, and the C0 control characters are escaped — <, >, &, and non-ASCII pass through literally as UTF-8). The signature is ed25519(privkey, utf8_bytes(canonical_json)).

Worked example (line-wrapped here for readability; the real value has no whitespace):

{"issued_at":"2026-04-12T14:32:00Z","key_id":"gitjob-demo-2026q2",
 "model_id":"gitjob.io/claude-sonnet-4-6","nonce":"6tmw7Mc3Ylw3molRAzstIg",
 "output_hash":"8c6c…7cb8","prompt_hash":"26ac…612a","receipt_id":"demo-valid-001"}

Rationale. JCS is the defensible, interoperable choice: it is a published RFC with implementations in every major language, so the standalone @gitjob/attest verifier and any third-party reimplementation converge on the same bytes. The all-string receipt sidesteps JCS's only genuinely tricky area (canonical number formatting), so a from-scratch implementation is a few dozen lines. Reference: internal/attest/canonical.go.


API surface (proposed)

The spec is transport-agnostic; the shape below is a representative HTTP mapping. Providers may bind it onto their existing completion endpoints.

Key set (normative)

GET /v1/receipts/keys returns the issuer's published public keys:

{
  "keys": [
    { "key_id": "gitjob-demo-2026q2",
      "public_key": "<base64 of the 32-byte ed25519 public key>",
      "status": "active",
      "created_at": "2026-04-01T00:00:00Z",
      "rotated_at": null },
    { "key_id": "gitjob-demo-2025q4",
      "public_key": "<base64…>",
      "status": "revoked",
      "created_at": "2025-09-01T00:00:00Z",
      "rotated_at": "2025-11-01T00:00:00Z" }
  ]
}

Only public keys appear here; the signing private key is never served. The set is cacheable — a verifier need not call the issuer at verify time. Revoked keys remain listed (with rotated_at) so receipts they signed can still be evaluated against the revocation timeline.


Verification flow

A verifier resolves exactly one status, in this precedence order:

  1. unknown_keykey_id is absent from the published key set.
  2. revoked — the named key is marked revoked as of issued_at (i.e. issued_at >= rotated_at). A receipt signed before its key's revocation is not revoked — it continues to the signature check.
  3. tampered — the ed25519 signature does not verify over the canonical bytes, or (when content is supplied) a recomputed prompt_hash/output_hash does not match.
  4. valid — key known, not revoked-as-of-issuance, signature and any supplied hashes match.

Anyone can verify; nobody can forge. The reference implementation is internal/attest (Go, MIT); conformance vectors live at internal/attest/testdata/conformance/.


Reference implementation — gitjob.io commits to

Posture: spec, verifier, and test suite published under MIT. Receipt issuance remains the model provider's; verification is public. Anyone can verify, nobody can forge.


Open questions — for implementing providers


This spec is one set of design choices in a growing space. The choices are scoped to a specific use case — work provenance, where a third party verifies that a specific human prompt produced a specific model output — and a specific consumer model: artifact-scoped receipts that outlive the API call and are verified offline.

Coordination on a single receipt envelope across providers, regulators, and verifiers is more usefully tracked at IETF SCITT than against any individual draft.


Changelog


Justin Higgins · justin.c.higgins@gmail.com · gitjob.io