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:
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
- 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.
- 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.
- 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.
- 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.
- 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.
Problem
declared_byis 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_urland a.well-knownjwks 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
provenanceobject (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.jsonpath (or reuse the existing JWKS surface if there is one — verify before speccing).adcp_usepurpose-binding: the JWK MUST carryadcp_use: provenance-claim(matches the established pattern from #3360 / KMS-distinct-keys-per-purpose). A receiver MUST reject a signature made with a JWK whoseadcp_useis anything else, even ifkidmatches.Verifiers reject unsigned claims under policy: a seller's
creative_policygainsprovenance_requirements.require_signed_claim: bool. When true, missing or invalid signatures emit a newPROVENANCE_CLAIM_UNSIGNEDorPROVENANCE_CLAIM_SIGNATURE_INVALIDfrom the structural-rejection family.Open questions
provenance; embedded simplifies serialization. Detached is the leaning here for parity with how C2PA carries its manifest separately.kidin the signed header + JWKS publication handles rotation. Revocation needs a published CRL or short-lived keys; lean toward short-lived (≤30 days) with thesigned_atboundary enforced rather than CRL infrastructure.delegated_fromclaim in the JWS payload, or isdeclared_by.role: agencysufficient at the protocol layer? Probably the latter, with delegation evidence kept off-protocol.provenance_requirements.require_signed_claimalready in place so sellers can ratchet to required at their pace.Out of scope
Refs
adcp_usepurpose-binding patternadcp_use— receivers enforce purpose at JWK adcp_usePriority
Pre-GA candidate per the GA-readiness review. Pairs with #TBD (served-disclosure receipt) as the two highest-leverage legal-gap closures.