Skip to content

RFC: signed provenance claims (JWS over the provenance object) #4441

@bokelley

Description

@bokelley

Problem

declared_by is a name in a JSON object. Anyone can declare anything. A regulator asking a publisher "who told you this was human-made" should get back a signed claim from the agency, verified against their published key — not an unsigned object that anyone in the chain could have forged or modified.

C2PA itself uses signed manifests. AdCP not doing the equivalent on the disclosure side is the most obvious gap a security-focused reviewer will find. It also gives sellers a real legal posture for PROVENANCE_CLAIM_CONTRADICTED: they have a signed misrepresentation to point to, not an unsigned one a buyer can later disown.

Proposed wire shape

A detached JWS signature alongside the provenance object, with key discovery via the declaring party's agent_url and a .well-known jwks endpoint:

{
  "provenance": {
    "digital_source_type": "trained_algorithmic_media",
    "human_oversight": "directed",
    "ai_tool": { "name": "Midjourney", "version": "v7" },
    "declared_by": { "role": "agency", "agent_url": "https://creative.novabrands.example.com" },
    "declared_at": "2026-05-12T14:22:01Z",
    "disclosure": { "required": true, "jurisdictions": [...] }
  },
  "provenance_signature": {
    "alg": "ES256",
    "kid": "agency-prov-2026q2",
    "signed_payload": "<base64url JWS detached signature>",
    "key_discovery": "https://creative.novabrands.example.com/.well-known/adcp-jwks.json",
    "signed_at": "2026-05-12T14:22:01Z"
  }
}

Signing input: the canonicalized JSON of the provenance object (JCS / RFC 8785), excluding the signature field itself. Detached signature so the provenance object remains human-inspectable.

Key discovery: matches the pattern already established for KMS-backed agent identity in the repo. New .well-known/adcp-jwks.json path (or reuse the existing JWKS surface if there is one — verify before speccing).

adcp_use purpose-binding: the JWK MUST carry adcp_use: provenance-claim (matches the established pattern from #3360 / KMS-distinct-keys-per-purpose). A receiver MUST reject a signature made with a JWK whose adcp_use is anything else, even if kid matches.

Verifiers reject unsigned claims under policy: a seller's creative_policy gains provenance_requirements.require_signed_claim: bool. When true, missing or invalid signatures emit a new PROVENANCE_CLAIM_UNSIGNED or PROVENANCE_CLAIM_SIGNATURE_INVALID from the structural-rejection family.

Open questions

  1. Detached vs embedded JWS. Detached preserves human-readability of provenance; embedded simplifies serialization. Detached is the leaning here for parity with how C2PA carries its manifest separately.
  2. Multi-party signing. When creator → agency → DSP each modify provenance, do we want a single signature from the most recent modifier, or a chain? Probably "single signature on the current state, chain history captured separately in provenance_history (#TBD)" — but the call interacts with that issue.
  3. Key rotation and revocation. kid in the signed header + JWKS publication handles rotation. Revocation needs a published CRL or short-lived keys; lean toward short-lived (≤30 days) with the signed_at boundary enforced rather than CRL infrastructure.
  4. Federated identity. When an agency signs on behalf of an advertiser, do we need a delegated_from claim in the JWS payload, or is declared_by.role: agency sufficient at the protocol layer? Probably the latter, with delegation evidence kept off-protocol.
  5. Required vs optional pre-GA. I'd lean optional pre-GA (additive), with the matching provenance_requirements.require_signed_claim already in place so sellers can ratchet to required at their pace.

Out of scope

  • Specific KMS provider (GCP / AWS / etc.). The protocol cares about the JWKS surface, not the key custody.
  • Signing the entire creative manifest. This issue is provenance-only; manifest signing is a separate question.
  • Cross-protocol harmonization with C2PA's signature format. Worth a follow-up — C2PA uses CBOR Web Tokens / COSE, not JWS, so full interop is a translation layer not a shared format.

Refs

Priority

Pre-GA candidate per the GA-readiness review. Pairs with #TBD (served-disclosure receipt) as the two highest-leverage legal-gap closures.

Metadata

Metadata

Assignees

No one assigned

    Labels

    creativegovernanceIssue concerns the governance protocol domainrfcProtocol change — auto-adds to roadmap boardschemaJSON Schema source-of-truth: definitions, codegen artifacts, validation, hygienespec / protocol

    Type

    No type

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions