From b8cdc717f856cf1fe2583dedb7734758daf2506e Mon Sep 17 00:00:00 2001 From: Matt Venables Date: Thu, 27 Aug 2026 11:01:03 -0400 Subject: [PATCH] docs(rfc): propose ACK v2 ACK-ID: a core RFC (identities spelled did:web, one-fetch key resolution, grants, RFC 9421 signed requests, verification checklist, revocation levers, crit-based extension mechanism) plus six extensions, with ext-controller drafted in full. ACK-Pay: a design language plus one normative profile adopting the x402 offer-receipt artifacts and binding receipts to the authorizing grant. The README records settled decisions and the document map. --- docs/ack-id/rfc/README.md | 184 ++++++++++ docs/ack-id/rfc/core.md | 473 ++++++++++++++++++++++++++ docs/ack-id/rfc/ext-attestations.md | 67 ++++ docs/ack-id/rfc/ext-audit.md | 37 ++ docs/ack-id/rfc/ext-controller.md | 314 +++++++++++++++++ docs/ack-id/rfc/ext-delegation.md | 94 +++++ docs/ack-id/rfc/ext-revocation.md | 41 +++ docs/ack-id/rfc/ext-web.md | 52 +++ docs/ack-pay/receipt-verification.mdx | 5 +- docs/ack-pay/rfc/README.md | 50 +++ docs/ack-pay/rfc/core.md | 177 ++++++++++ 11 files changed, 1492 insertions(+), 2 deletions(-) create mode 100644 docs/ack-id/rfc/README.md create mode 100644 docs/ack-id/rfc/core.md create mode 100644 docs/ack-id/rfc/ext-attestations.md create mode 100644 docs/ack-id/rfc/ext-audit.md create mode 100644 docs/ack-id/rfc/ext-controller.md create mode 100644 docs/ack-id/rfc/ext-delegation.md create mode 100644 docs/ack-id/rfc/ext-revocation.md create mode 100644 docs/ack-id/rfc/ext-web.md create mode 100644 docs/ack-pay/rfc/README.md create mode 100644 docs/ack-pay/rfc/core.md diff --git a/docs/ack-id/rfc/README.md b/docs/ack-id/rfc/README.md new file mode 100644 index 00000000..c8d17cb9 --- /dev/null +++ b/docs/ack-id/rfc/README.md @@ -0,0 +1,184 @@ +# ACK-ID Core RFC (proposal) + +Draft restructuring of the ACK-ID v2 working draft into a small core RFC plus +named extensions. Not wired into the docs site; discussion material only. + +The v2 working draft referenced throughout (`docs/ack-id/specification.mdx` +and its section numbers) lives on the `ack-id-v2` branch +([PR #7](https://github.com/catena-labs/ack-private/pull/7)), not on `main`. + +## Motivation + +The v2 spec's L1-L4 conformance levels are an admission that it is four specs +stapled together. This proposal makes that structure literal: the core RFC is +roughly today's L1, and everything above it becomes an extension document that +can be adopted, versioned, and argued about independently. + +Design guidelines for the core: + +1. Use grants. +2. Work with Web Bot Auth as deployed today. +3. Plain JOSE; no VC machinery. +4. "It works with what you have", and a missing document is a failure, + never a fallback. Core resolves keys from one fixed path per identity, + the did:web resolution rule (`/did.json`; + `/.well-known/did.json` for a bare domain), and reads only the + `publicKeyJwk` entries. Orgs whose keys live behind an OIDC `jwks_uri` + reuse them via the discovery profile in ext-web. That profile is opt-in + rather than core, for two reasons: a fallback triggered by a 404 turns + key removal (core's revocation lever) into a silent widening of the + trusted key set, and the discovery locations real OIDC providers use do + not match path identities anyway. + +The target: implementable with a stock JOSE library and an HTTP client in an +afternoon. + +The through-line of the split: the ideas DIDs and VCs were for survive in +core (self-issued identifiers with no central registry, key rotation that +survives identity, signed portable claims about who may do what, +callback-free verification), while their encodings (JSON-LD, the VC data +model, presentation exchange, generalized DID resolution) move out. +Semantically a grant is a verifiable credential; ext-attestations keeps a +mechanical, non-normative mapping to the VC data model, so the VC and eIDAS +world re-enters as an adapter, never as a core dependency. Core dependencies +can never be removed; adapters can be added whenever demand shows up. + +Core is the foundation. The pairing that carries the original ACK-ID +promise, a verifiable line from a request back to the accountable entity +behind an agent, is **core + ext-controller**, which is why +ext-controller is drafted as a full RFC while the other extensions remain +stubs. The other flagship pairing is core + ACK-Pay: payment receipts that +walk back to an owner. + +## Document map + +| Current v2 spec section | Lands in | +| ------------------------------------------------------------------------------------- | ----------------------------------------------------------- | +| 3.2 documents (keys only), 4 keys, 4.1 thumbprints | [core](./core.md) | +| 6 grant format, 6.1 leaf rules | [core](./core.md) | +| 8.1 minimal verification, 8.2 signed requests | [core](./core.md) | +| 9 revocation levers (jti single-use, short exp, key removal, owner unpinning) | [core](./core.md) | +| 3.1 controllers; 5 ownership proofs; 6.3 control grants; 4.2 key purpose | [ext-controller](./ext-controller.md) | +| 3.1 did:key; 6.2 chains and attenuation; 6.4 issuance service; 6.5 registration | [ext-delegation](./ext-delegation.md) | +| 9.1 signed revocation list; 9.2 status list | [ext-revocation](./ext-revocation.md) | +| 8.2 OAuth carriage; CIMD; DPoP; keyless clients; Web Bot Auth directory; RP discovery | [ext-web](./ext-web.md) | +| 7 attestations | [ext-attestations](./ext-attestations.md) | +| 8.3 full verification | [ext-controller](./ext-controller.md) | +| 8.4 evidence bundles; 10 event log | [ext-audit](./ext-audit.md) | +| 11 artifact type discipline | core Section 9 (extensions append rows) | +| 12 security considerations | core Section 10 (extensions carry their own) | +| 1.2 test vectors | core deliverable, tracked alongside it | +| 4.3 signer backend interface | cut; what survives is core Section 9's signing-tooling note | +| 13 hosted directory profile | stays a non-normative profile | + +[ext-controller](./ext-controller.md) is drafted as a full RFC; it carries +the ownership pitch and deserves the same scrutiny as core. The remaining +extension stubs summarize scope and defer normative text to the v2 working +draft sections they extract. + +## What stays in core even though it looks like extra + +- `cnf` possession binding. Without it grants are bearer tokens. +- The `crit` rule and rejected unknown `constraints`: a verifier rejects + any grant whose `crit` names a claim it does not implement, and any grant + whose `constraints` carry a member it does not understand. These two + rules are the extension mechanism; a meaning-changing extension claim + (`chain`, `status`) rides under `crit` and can never downgrade a + core-only verifier. +- A complete minimal revocation story (`jti` single-use, short `exp`, agent + key removal, owner-key unpinning). Revocable is half the point of grants; + the list mechanisms move out, the levers stay. + +## Settled decisions + +1. **Identifier spelling in `iss`/`sub`: did:web stays canonical, presented + URL-first.** Earlier drafts of this proposal spelled identities as bare + URLs in core and moved DIDs to ext-controller. Two things settled it the + other way: the x402 offer-receipt extension identifies JWS signers as + did:web and resolves their keys from the same `did.json` core reads, so a + URL-only core would reintroduce a second spelling the moment payments + enter; and ext-controller's controller derivation operates on the DID + path. The Cloudflare-shaped objection (their Wallets product + deliberately avoids DID-like schemes) changed the presentation and left + the wire format alone: core Section 3 now leads with the identity URL, + treats the DID spelling as a serialization requiring no DID library, + and uses the URL form wherever an external spec requires a URL + (`Signature-Agent`, per Web Bot Auth), with a bijective mapping and no + aliasing. +2. **Key location: the did:web resolution rule.** `/did.json` + for path identities, `/.well-known/did.json` for a bare domain; core reads + only the `publicKeyJwk` entries and ignores the rest of the document. One + hosted document then serves ACK-ID, ACK-Pay receipts, and x402 + offer-receipt verification. (Earlier drafts used separate `jwks.json` + paths; that meant a second document for no added capability.) Fetches + follow redirects, with the SSRF checks applied to every hop: the fixed + path is where resolution starts, not a constraint on serving topology. +3. **Control grants live in ext-controller.** Core verifiers pin owner + keys at onboarding and never consume a control grant, so moving them + into core would add surface with no core consumer. The counterargument + (they are the ownership story, and burying them weakens the pitch) is + answered by framing: ext-controller is drafted as a full RFC, and + core + ext-controller is presented as the flagship pairing throughout. +4. **did:key lives in ext-delegation, not ext-controller.** The ephemeral + sub-agent case is delegation-shaped (owner signs an intermediate, an + orchestrator mints a chained leaf for a did:key worker), so the whole + story reads in one document. ext-controller is about ownership of + consequential identities, which did:key can never anchor. +5. **The authority artifact is a grant, not a mandate.** Earlier drafts + called it a mandate. That word now carries at least three meanings in + agent commerce: AP2's Intent and Cart Mandates (a human approving a + purchase), a policy-shaped usage in finance ("keep the portfolio + balanced"), and this artifact. The first two are not ours to rename, and + an AP2 mandate can ride in the same request as this artifact, so one word + for both is unworkable. Qualifiers ("agent mandate") do not survive into + `typ` values or HTTP field names. "Grant" is the verb core already used + to define the artifact, it names a signed instrument rather than a rule + (which "permission" does not), and `Grant` is free as an HTTP field name + where `Authorization` and `Authority` are taken. The layer boundary this + makes explicit: an AP2 mandate says a human approved a transaction; an + ACK-ID grant says an owner authorized an agent to act. ACK documents use + "mandate" only when citing AP2. +6. **Controller derivation never crosses DNS labels.** A subdomain-hosted + agent (`agent.company.service.com`) is a bare origin, legal everywhere in + core, but in ext-controller it is an apex with no derived controller. A + label-based rule would need the Public Suffix List as its stop boundary + (an external, continuously edited dependency on which two verifiers + could disagree), and DNS delegation does not track organizational + control. + Platforms that want the ownership chain name agents as path identities + under the company host (`did:web:company.service.com:agent-name`); + identities name where keys live rather than where traffic is served, so + per-agent subdomains can stay for routing. +7. **`chain` carries one hash: the immediate parent.** The working draft + (6.2) had the leaf enumerate its full ancestry plus a path-consistency + rule; ext-delegation now specifies pairwise linkage: each artifact hashes + only its immediate parent, and ancestry is pinned transitively because a + parent's hash covers the parent's own `chain`. The commitment is + equivalent; the single entry is structurally decoy-proof where the flat + list needed the consistency rule to be enforced correctly; delegation is + local (a holder extends the chain knowing only its parent); and pairwise + hashing is the AP2 mandate-chain convention, so grants slot into AP2/UCP + flows without redesign. Field-tested in the id.sh reference + implementation, whose panel regressions include the decoy-hash rejection. + Known limit, true of any hash-linked format: an ancestor can never be + inserted above an issued root without reissuing everything below it. +8. **Unknown grant claims are ignored; `crit` marks the exceptions.** + Earlier drafts closed the claim table: a core verifier rejected any + top-level claim it did not recognize. PR #9 review argued that JWT + implementers expect additive claims to be ignored, and the closed table + made every extension claim a breaking change even when ignoring it was + safe. Core now follows the RFC 7515 `crit` pattern on payload claims: + verifiers ignore unrecognized claims, and a claim that changes the + grant's meaning MUST be named in `crit`, which verifiers without the + extension reject. The cost, stated openly: the safety of the rule moved + from the verifier to the issuer. Extensions therefore also give their + artifacts a second rejection surface (a distinct `typ`, a reserved + scope token, an `aud` shape core rejects) where ignoring a claim would + widen authority. +9. **Wildcard scope tokens are not reserved.** Earlier drafts reserved any + scope token containing `*` for ext-delegation. Scope meaning is + audience-owned, and the blanket reservation blocked RPs from their own + wildcard grammar (PR #9 review). Only `control` and `register` stay + reserved; ext-delegation's attenuation wildcards apply to + parent-to-child containment inside chains, which core-only verifiers + never accept anyway. diff --git a/docs/ack-id/rfc/core.md b/docs/ack-id/rfc/core.md new file mode 100644 index 00000000..5ce4089a --- /dev/null +++ b/docs/ack-id/rfc/core.md @@ -0,0 +1,473 @@ +# ACK-ID Core (draft RFC) + +**Status: proposal draft.** The key words MUST, MUST NOT, SHOULD, SHOULD NOT, +and MAY are to be interpreted as described in RFC 2119. + +## 1. Scope + +ACK-ID Core defines how an agent proves who it is and what it may do. The +proof is cryptographic and needs no callbacks. In core, the relying party +already knows and trusts the owner that authorized the agent. Core defines: + +- identities and key resolution, +- keys and thumbprints, +- the grant artifact, +- the signed-request carriage (Web Bot Auth compatible), +- the verification checklist, +- the revocation levers, +- the extension mechanism. + +All other topics live in extension documents layered on this one: ownership +discovery for unknown counterparties, delegation chains, revocation lists, +the OAuth surface, attestations, and audit logs. A core-only deployment is +complete and useful on its own. Example: an organization that authenticates +its own agents to its own services. To serve unknown counterparties, and to +trace a request back to an accountable entity, pair core with +ext-controller. + +### 1.1 How it works + +Three parties. An **owner** (Acme) is accountable for an **agent** (Acme's +invoice bot). A **relying party** (the API the bot calls) accepts the +agent's requests because the owner authorized them. + +Two keypairs: + +- The agent holds a keypair and publishes the public key in one JSON + document at its identity URL (Section 3). +- The owner holds a keypair and gives the public key to the relying party + once, at onboarding (Section 2). + +The flow: + +1. The owner signs a **grant**: a short-lived JWT saying this agent may + perform these actions at this relying party (Section 5). +2. The agent signs each HTTP request with its own key (RFC 9421) and + attaches the grant in the `Grant` field (Section 6). +3. The relying party verifies the request signature against the agent's + published keys (one HTTPS fetch). It verifies the grant against the + owner key it pinned at onboarding. It then checks that the grant's scope + covers the request (Section 7). + +Nobody calls anybody back. The owner can be offline. The agent proves +possession of its key on every request. The relying party verifies +everything locally except the one fetch of the agent's published keys. + +Every artifact verifies with a stock JOSE library. Conversion between an +identity's two spellings (Section 3.1) is string manipulation. + +## 2. Terminology + +- **Relying party (RP)**: the service an agent calls. It verifies what the + agent presents. This document says **verifier** when a rule also applies + to parties that are not the target of a request (for example, an auditor + who walks an ACK-Pay receipt trail). +- **Owner**: the accountable party behind an agent. In core, the RP knows + the owner out of band and pins its keys at onboarding. +- **Agent**: software acting under an owner's authority. +- **Identity**: an owner or agent, named by its HTTPS **identity URL** and + spelled as a did:web DID in artifacts (Section 3). +- **Grant**: a JWT in which an issuer gives a subject bounded authority to + act (Section 5). +- **Artifact**: any signed object this protocol defines. In core: grants + and signed requests. +- **Onboarding**: the out-of-band step in which an RP learns an owner's + grant-signing public keys and records (**pins**) them against the + owner's identity. The channel belongs to the RP: a business agreement, a + dashboard, a config file. Core requires only that the channel exists and + supports unpinning (Section 8). + +## 3. Identity and key resolution + +### 3.1 Identities + +An identity has two equivalent spellings: + +- the **identity URL**: `https://acme.com` for an owner, + `https://acme.com/invoice-bot` for an agent. The URL is where the + identity's keys are published (Section 3.2). +- the **did:web DID**: `did:web:acme.com`, `did:web:acme.com:invoice-bot`. + This spelling appears inside grants, as `iss` and `sub`. + +Conversion between the two is string manipulation: take the host, then the +path segments, and join them with `:` in the DID spelling. The mapping +works in both directions and gives exactly one result each way. One +identity, two spellings, each used only where this document says so. The +DID spelling exists for compatibility with adjacent layers that name +signers as DIDs; the x402 offer-receipt extension resolves its signers' +keys from the same document Section 3.2 defines. The URL spelling appears +where an external spec requires a URL (`Signature-Agent`, per Web Bot +Auth, Section 6). + +A host may carry any number of DNS labels: +`https://agent.company.service.com` is a legal root identity, spelled +`did:web:agent.company.service.com`, with keys resolved per Section 3.2 +rule 2. Host labels carry no relationship: core treats identities on +different hosts as unrelated, whatever labels they share. The +agent-to-owner binding is the grant (Section 5), and its `iss` may name an +owner on any host the RP has onboarded. Controller derivation +(ext-controller) operates on path segments only, never on host labels. A +deployment that must choose between subdomain-per-agent and path-per-agent +layouts should read that document's Section 2. + +Before a verifier uses an identity for comparison or URL construction, it +MUST reject the identity if its URL form contains any of: a userinfo +component, a port, a query, a fragment, a trailing slash, a dot-segment +(`.` or `..`), an empty path segment, percent-encoding, or `.well-known` +as its first path segment. The `.well-known` rule closes an aliasing hole. +The path identity `https://acme.com/.well-known` would publish its keys at +`https://acme.com/.well-known/did.json`. That is the same location that +serves the root identity `https://acme.com`, so one document would answer +for two identities. To compare two identities, a verifier MUST map both to +one spelling, lowercase the host (and the scheme, in the URL form), and +compare byte-for-byte. No other normalization is defined. + +### 3.2 Key resolution + +Given an identity, a verifier resolves its current public keys from its +**DID document**, fetched from exactly one location (the did:web resolution +rule): + +1. **Path identities** (URL form has a path): `/did.json`. +2. **Root identities** (bare origin): `https:///.well-known/did.json`. + +A verifier reads exactly one thing from the document: the key set. A key is +a `verificationMethod` entry whose `publicKeyJwk` member carries a JWK (RFC +7517). Core ignores every other member (`controller`, service endpoints, +non-JWK key encodings); ext-controller assigns them meaning. A document +with no parseable `publicKeyJwk` entries is malformed. + +A missing or malformed document ends resolution with failure. There is no +fallback location. That strictness is what makes key removal a revocation +lever (Section 8): a resolver that tried another location on 404 would +quietly widen a key set its publisher deleted. Orgs whose keys live behind +an OIDC `jwks_uri` use the opt-in discovery profile in ext-web. + +Non-normative: the same document serves the x402 offer-receipt extension's +JWS key discovery, which resolves did:web signers from `did.json`. One +hosted document covers ACK-ID verification and ACK-Pay artifact +verification. + +Resolution rules: + +- All fetches MUST use HTTPS, MUST time out, and MUST cap response size. + Fetchers MAY follow redirects to a bounded depth (RECOMMENDED limit 3). + Every hop MUST use HTTPS and MUST pass the address checks below. +- The identity URL is attacker-influenced until a signature verifies. + Fetchers MUST NOT connect to loopback, private (RFC 1918), link-local, + or unique-local addresses, on any hop. Deployments that face hostile + input SHOULD resolve DNS once and pin the address for the connection. +- Key currency is judged against a fresh read of the document. A verifier + MUST NOT use a read older than 300 seconds and SHOULD cache no longer + than 60. Publishers MUST serve the document with cache lifetimes of 60 + seconds or less. Removal of an agent key from the DID document then + invalidates everything that key signs within the cache bound: at most + 300 seconds. (Owner keys are pinned, not resolved; their lever is + unpinning, Section 8.) + +## 4. Keys + +- **Algorithms.** A verifier MUST accept both `EdDSA` (Ed25519, + `kty: "OKP"`, `crv: "Ed25519"`) and `ES256` (P-256, `kty: "EC"`, + `crv: "P-256"`), and MUST reject any other `alg`. A verifier MUST select + the verification key by `kid` and MUST confirm the selected key's + `kty`/`crv` matches the asserted `alg`. Keys outside this set MUST be + ignored: a co-hosted DID document may carry RSA keys for other + protocols, and they play no part here. Issuers SHOULD issue Ed25519 by + default; P-256 exists for hardware-backed and FIPS-constrained keys. +- **Custody.** Private keys are generated by the holder and MUST NOT be + transmitted to any server. The owner's key signs grants; each agent's key + signs that agent's requests. Owner keys SHOULD NOT live on the machines + running the agents they authorize. +- **Rotation.** An identity holds one or more keys. Rotation is + add-then-remove and MUST NOT change the identity. + +### 4.1 Thumbprints + +Keys are named by their RFC 7638 JWK thumbprint, base64url encoded +(computed per RFC 8037 for Ed25519). The thumbprint is the published key's +name everywhere: the JWK `kid`, the `verificationMethod` fragment, the +HTTP signature `keyid`, and the grant `cnf.jkt`. Lookup is by recomputed +thumbprint. Given a `kid` or `keyid`, the verifier selects the resolved +entry whose recomputed RFC 7638 thumbprint equals it. A stated JWK `kid` +or fragment is a label, never the selector, so a published entry cannot +claim another key's name. Exactly one entry may match. Zero matches, or +more than one, is a rejection. + +## 5. Grants + +A grant is a JWT: header `alg` per Section 4, `typ: "grant+jwt"`, `kid` the +issuer key's thumbprint. Verifiers MUST reject a grant whose `typ` differs. + +```json +{ + "iss": "did:web:acme.com", + "sub": "did:web:acme.com:invoice-bot", + "aud": "https://api.examplebank.com", + "scope": "invoices:read", + "constraints": { "region": "us" }, + "iat": 1781035200, + "exp": 1781121600, + "jti": "grn_4kq8", + "cnf": { "jkt": "tH5Qw9..." } +} +``` + +The example is non-normative; the recognized claims and their requiredness +are: + +| claim | requiredness | rule | +| ------------- | ------------ | --------------------------------- | +| `iss` | REQUIRED | DID of an onboarded owner | +| `sub` | REQUIRED | DID of the agent | +| `aud` | REQUIRED | single string, exact match | +| `scope` | REQUIRED | space-delimited action scopes | +| `iat`, `exp` | REQUIRED | numeric; time rules below | +| `jti` | REQUIRED | unique per issuer | +| `cnf` | REQUIRED | `jkt` possession pin | +| `constraints` | OPTIONAL | open object; members only narrow | +| `nbf` | OPTIONAL | numeric; time rules below | +| `crit` | OPTIONAL | critical claim names; rules below | + +**Unknown claims are ignored; `crit` names the ones that must not be.** A +verifier ignores any top-level claim it does not recognize. A claim that +changes what the grant means MUST be named in `crit` by its issuer. A +verifier MUST reject a grant whose `crit` names a claim the verifier does +not implement and evaluate. This is the RFC 7515 `crit` pattern, applied +to payload claims, and it is the extension mechanism. A claim like `chain` +(ext-delegation) or `status` (ext-revocation) rides under `crit`, so a +verifier that cannot evaluate it rejects the grant. `crit`, when present, +MUST be a non-empty array of unique strings. It MUST NOT name any claim in +the table above, and MUST NOT name a claim absent from the payload. A +violation of any of these rules is a rejection. + +- `iss` and `sub` are DIDs (Section 3.1). `iss` MUST name an owner the RP + has onboarded (Section 2). The grant MUST verify against a key pinned + for exactly that owner (Section 7). +- `aud` MUST be a single string exactly matching an identifier the RP + recognizes as its own. (Extensions define artifacts with other `aud` + shapes; core verifiers never accept those artifacts, by the rules above.) +- `scope` is space-delimited. Every token is an action scope whose meaning + is owned by the audience, wildcard grammars included. The tokens + `control` and `register` are reserved for extensions (ext-controller and + ext-delegation define them). A core verifier MUST reject a grant that + carries either. +- `constraints` is an object of audience-defined members. A member only + ever narrows what the grant allows: `{"region": "us"}` grants less than + no constraint at all. Because members only narrow, a verifier MUST + reject a grant that carries a member the verifier does not understand; + to skip one would widen the grant. This document assigns no meaning to + any member. Data that does not narrow authority does not belong in a + grant. +- Time rules, with a clock-skew allowance `skew` (RECOMMENDED 60 seconds) + applied uniformly: `iat <= now + skew`; `nbf <= now + skew` when + present; `exp > now - skew`; `exp > iat`; and `exp > nbf` when `nbf` is + present. The last two compare issuer-set values against each other, so + no skew applies: an empty validity interval makes the grant malformed. + A malformed or non-numeric time claim is a rejection. (`nbf` is + recognized because some JOSE libraries add it by default.) +- `jti` MUST be unique per issuer, with enough entropy that revocation and + single-use tracking by `jti` are well defined. +- `cnf.jkt` pins the key that must prove possession at presentation. A + grant without it would be a bearer artifact, and core has no bearer + artifacts. Verifiers MUST reject a grant that lacks `cnf.jkt`. Verifiers + MUST match `cnf.jkt` against the possession key exactly, never against + "any current key of the subject." + +## 6. Signed requests + +Agents sign requests with RFC 9421 HTTP message signatures. The profile +stays within what Web Bot Auth infrastructure verifies as deployed today, +so one signature serves ACK-ID verifiers and WBA edges alike. The parts +have three sources. RFC 9421 defines the signature itself and carries no +credential. `Signature-Agent` comes from Web Bot Auth. The `Grant` field +is defined by this document. + +- The signature MUST cover `@method`, `@target-uri`, `content-digest` when a + body is present, the `Signature-Agent` field, and the `Grant` field with + the `sf` parameter when grants are presented. +- `Signature-Agent` carries the agent's identity URL (the URL spelling of + its DID, Section 3.1; Web Bot Auth requires a URL here). It is an + untrusted hint until the signature verifies. Because it is under the + signature, a middlebox cannot swap it. +- `keyid` is the signing key's thumbprint. `created` (and `expires` where + used) bound replay. Verifiers MUST enforce a maximum `created` age + (RECOMMENDED default 300 seconds); the maximum is the RP's + replay-exposure ceiling, a policy choice like the Section 8 lifetime + maximum. When `expires` is present, it MUST be covered by the signature, + and a verifier MUST reject a request past it (the Section 5 `skew` + applies). `expires` only ever narrows the window: an `expires` beyond + the maximum `created` age does not extend acceptance. A captured request + replays verbatim within that window. RPs that accept non-idempotent + requests MUST therefore enforce replay protection: a cache over the + signature value, or the RFC 9421 `nonce` parameter. +- `Grant` is a structured-field List of Tokens (RFC 9651), one token per + compact JWT. (An RFC 9651 Token must begin with an alphabetic character + or `*`. A compact JWT's first segment is base64url of a JSON header and + always begins `eyJ`, so grants satisfy the grammar.) A verifier MUST + reject a request whose `Grant` field fails structured-field parsing; it + MUST NOT salvage tokens from it. An unsigned `Grant` field is open to + middlebox changes. Coverage is what binds the grants to the request, and + a verifier MUST check that coverage, never assume it. +- Each presented grant is evaluated independently; the request is authorized + only by grants that individually pass. + +Non-normative: a Web Bot Auth edge (for example, a CDN verifying crawler +signatures) discovers keys per its own draft, at +`/.well-known/http-message-signatures-directory` on the `Signature-Agent` +origin, not via Section 3.2. An agent that wants those edges to recognize +it also hosts that directory; see ext-web. The signature itself is the +same either way. + +## 7. Verification + +The RP holds the owner's grant-signing public keys, pinned at onboarding +(Section 2). The pinned set MUST contain grant-signing keys only; never, +for example, an issuer's continuously online revocation key +(ext-revocation). Three checks authorize a request. The only document +fetch is the resolution of the agent's keys per Section 3.2. + +1. **Possession**: verify the request signature (Section 6) against the + agent's keys, resolved fresh per Section 3.2 from the `Signature-Agent` + identity URL, with the Section 4.1 thumbprint check against `keyid`. +2. **Grant**, all of: + - `typ` is `grant+jwt`. + - The signature verifies against a key pinned for the owner named in + `iss`. Never merely "some pinned key": an RP with more than one + onboarded owner binds `iss` to the pinning entry, which stops one + owner minting grants attributed to another. + - `sub` equals the requesting agent's DID, mapped from the + `Signature-Agent` URL per Section 3.1. + - `aud` matches the RP exactly. + - The Section 5 claim table, `crit` rule, and time rules pass. + - The lifetime `exp - iat` does not exceed the RP's maximum (Section 8). + - No reserved scope tokens are present. + - `cnf.jkt` equals the possession key thumbprint. + - Every `constraints` member is understood and satisfied. + - The RP's Section 8 revocation posture holds. +3. **Scope**: the grant's scope covers the requested action. + +Pinning is core's stance, and it is also core's boundary. An RP that must +verify owners it never onboarded resolves issuer keys instead of pinning +them; that is ext-controller's Section 6. Nothing else in the checklist +changes. + +A passing verification proves continuity of key control and authorization. +It never proves human presence, consent, or the legal identity behind a +domain. + +## 8. Revocation + +Core's revocation story is complete against agent-key and grant +compromise. It consists of these levers; the list mechanisms live in +ext-revocation. + +- **Short `exp` is the primary mechanism.** Grant lifetimes SHOULD be + short (minutes to days). An `exp` on the order of minutes bounds + exposure with no revocation check at all. +- **Single-use grants.** For one-shot authority, the RP records the `jti` + at first acceptance and rejects reuse; the grant is spent when used. + Which actions need single-use authority is the RP's call (a payment + authorization, a one-shot registration); core supplies the mechanism. + Redemption MUST be atomic: a check-and-set keyed by (`iss`, `jti`). A + separate read-then-write lets two concurrent presentations both pass. + The RP keeps the record until the grant's `exp` has passed. +- **Key removal.** Removing an agent's keys from its DID document + invalidates everything the agent signs within the cache bound (Section + 3.2). +- **Owner-key unpinning.** Pinned owner keys are established out of band + at onboarding, and they are revoked the same way. RPs MUST support + unpinning a compromised owner key through the onboarding channel. + Unpinning invalidates every grant that key signed. + +A core RP MUST define a maximum acceptable grant lifetime, as a duration. +It MUST reject any grant whose lifetime `exp - iat` exceeds it; the +Section 7 checklist enforces this. Because acceptance also requires +`iat <= now + skew`, the same bound caps remaining validity at acceptance. +The maximum is the RP's exposure ceiling. An RP that needs to accept +longer lifetimes implements ext-revocation and learns the issuer's +mechanism at onboarding. There is no middle state in which a revoked, +unexpired grant is knowingly accepted. + +One consequence to weigh openly: core has no delegation, so an owner key +signs every grant, and minute-scale lifetimes keep that key signing +continuously. A core-only deployment therefore chooses between an online +owner key and longer grant lifetimes. ext-delegation resolves the tension: +the owner signs one medium-lived intermediate offline, and a hot service +mints the short-lived leaves. + +## 9. Extension mechanism + +Extensions add capability without weakening a core-only verifier, through +three surfaces: + +- **Critical claims.** An extension that changes what a grant means + defines a claim (`chain`, `status`) and requires issuers to name it in + `crit` (Section 5). Verifiers that implement the extension evaluate the + claim; every other verifier rejects the grant. A claim that is safe to + ignore is carried without a `crit` entry, and verifiers ignore it. +- **Constraints and scopes.** Narrowing semantics ship as `constraints` + members: an unrecognized member is a rejection (Section 5), so a + restriction is never skipped. Granting semantics ship as scope tokens: + an unrecognized token authorizes nothing, because no requested action + matches it. An extension MUST NOT ship narrowing semantics as scope + tokens. The reserved tokens `control` and `register` are rejected by + core verifiers until the defining extension is implemented. +- **Artifact types.** Every protocol artifact carries a distinct JOSE + `typ`. Verifiers MUST reject an artifact whose `typ` does not match its + context. The registry is this table plus each extension's "Registers" + section, governed with this specification. No external registry is + consulted at verification time. (Attestations are third-party artifacts + outside this registry by design; see ext-attestations.) + + | `typ` | artifact | defined by | + | ----------- | ------------------ | ---------- | + | `grant+jwt` | grants (Section 5) | core | + + Non-normative: signing tooling should not expose generic sign-anything + operations for protocol `typ` values. Mint a `typ` only through tooling + that enforces its artifact rules; that is what keeps one keypair safe + across every use. + +An RP MAY advertise accepted extensions, audience identifier, and required +scopes in a well-known discovery document (ext-web); core assigns it no +location or schema. An implementation states which extensions it supports. +There are no numbered conformance levels. + +## 10. Security considerations + +- **Key custody.** The machine holding a key is the trust boundary. A + pinned owner key co-located with its agents reduces every grant to + self-issuance. +- **Replay.** Signed requests carry bounded `created` windows, and + non-idempotent requests require a replay cache or nonce (Section 6). + Grants carry `exp` and `jti`; single-use redemption is `jti` + replay-checking. +- **Header integrity.** Unsigned `Grant` and `Signature-Agent` fields are + strippable and substitutable. Coverage (Section 6) is mandatory. A + verifier MUST verify the coverage, never assume it. +- **Key resolution SSRF.** Section 3.2 fetches an attacker-influenced URL. + Apply the stated protections to every fetch and every redirect hop. +- **Critical claims.** The `crit` rule rests on issuers naming every claim + that changes a grant's meaning. A meaning-changing claim omitted from + `crit` is ignored by verifiers without the extension. Extensions + therefore also give their artifacts a second rejection surface where + ignoring a claim would widen authority: a distinct `typ`, a reserved + scope token, or an `aud` shape core rejects. +- **Redirected key fetches.** Key resolution follows redirects (Section + 3.2), so whoever can set a redirect at the identity's origin chooses + where keys are read from. Publishers SHOULD serve `did.json` directly. + An RP MAY refuse redirected resolution where its policy needs the + stronger property. +- **Shared key sets.** A key set shared across identities (a root + identity's `/.well-known/did.json` serving a whole org) weakens what a + request signature proves. A passing signature proves possession of some + key in that set, never which party holds it. Any key-holder in the set + can pass Section 7 step 1 for any identity the set serves. + `Signature-Agent` MUST NOT be treated as authenticated identity on its + own; the `cnf`-bound grant identifies the agent. Publishers SHOULD serve + per-agent key sets, at path identities or per-agent hosts, where + isolation matters. +- **Privacy.** Verification is callback-free: credential use does not + reveal itself to issuers. Published DID documents are public; put no + personal data in identities or key metadata. diff --git a/docs/ack-id/rfc/ext-attestations.md b/docs/ack-id/rfc/ext-attestations.md new file mode 100644 index 00000000..23dc5d76 --- /dev/null +++ b/docs/ack-id/rfc/ext-attestations.md @@ -0,0 +1,67 @@ +# Extension: Attestations (stub) + +**Extracts v2 working draft section 7.** + +Third-party claims about an identity (KYB status, compliance level, audit +results), distinct from grants: an attestation asserts, a grant authorizes. + +## Adds + +- **Carriage.** JWTs (`typ: "jwt"`) or SD-JWTs (`typ: "dc+sd-jwt"`), issued by + any party under its own keys, typed by issuer-controlled `vct` per SD-JWT + VC. No central attestation-type registry. +- **Subject binding** (the one normative rule): the attestation issuer MUST + verify, within its own session with the subject, that the subject controls + the identity being named (a session-bound challenge signed by a current key + of that identity). RPs SHOULD require issuers to document their binding + procedure. +- **Selective disclosure.** SD-JWT only, and only here. Guidance: assert + results ("verified at level 2") rather than raw attributes (a date of + birth). +- **Freshness.** Attestations SHOULD include `cnf` (an RFC 7638 thumbprint + of the subject key the issuer verified at binding time) or a log-head + reference (ext-audit), so post-issuance key-set divergence is detectable. + A verifier compares `cnf` against the key the subject proves possession + of in the current interaction. It compares a log-head reference against + the subject's current published head. A mismatch fails the attestation, + never the session. Whether absent evidence downgrades or rejects is RP + policy, declared in advance. Exact binding and failure semantics are a + drafting requirement for this extension's normative text. + +## VC bridge (non-normative) + +Semantically a grant is a verifiable credential: an issuer, a subject, +claims, validity, and proof. The mapping to the W3C VC data model is +mechanical and value-preserving: the table round-trips the data. It does +not carry core's enforcement semantics (exact-`aud` matching, `cnf` +possession, the `crit` rule, rejected unknown constraints), which is why it +is an adapter, never an equivalence: + +| grant | VC data model | +| ----------------------------- | --------------------------- | +| `iss` | `issuer` | +| `sub` | `credentialSubject.id` | +| `aud`, `scope`, `constraints` | `credentialSubject` members | +| `iat` / `exp` | `validFrom` / `validUntil` | +| `jti` | `id` | +| JWS signature | `proof` | + +A gateway can wrap a grant as a `vc+jwt` for VC-consuming systems (or +translate a VC-shaped credential back into a grant) without losing data. This +extension is also where VC-formatted evidence enters ACK: an attestation MAY +be presented as a W3C VC and verified under this extension's rules. Wallet +and eIDAS ecosystems integrate here while core keeps zero VC dependencies. +The ideas survive in core; the encodings are adapters. A +normative adapter is a drafting requirement before any W3C VC is accepted +under this extension: claim-by-claim rules, rejection of unmappable claims, +and the accepted serializations (`vc+jwt`, `dc+sd-jwt`; no Data Integrity +proofs, matching core's JOSE-only posture). + +## Registers + +Nothing in the protocol `typ` registry, deliberately. Attestations are not +protocol artifacts: they ride the SD-JWT VC ecosystem's types (`typ: "jwt"`, +`typ: "dc+sd-jwt"`) and are typed by issuer-controlled `vct`, so existing +wallets and verifiers consume them unchanged. Core's distinct-`typ` rule +governs protocol artifacts only; an attestation can never be confused with a +grant because a grant MUST carry `typ: "grant+jwt"`. diff --git a/docs/ack-id/rfc/ext-audit.md b/docs/ack-id/rfc/ext-audit.md new file mode 100644 index 00000000..d69c8ad7 --- /dev/null +++ b/docs/ack-id/rfc/ext-audit.md @@ -0,0 +1,37 @@ +# Extension: Audit (stub) + +**Extracts v2 working draft sections 8.4 and 10.** + +Two audit mechanisms with very different costs: evidence bundles work today +with zero ecosystem; the event log adds detectable-history guarantees for +identities that need them. + +## Adds + +- **Evidence bundles.** RPs accepting high-stakes grants retain: the + presented JWTs; the exact fetched bytes of the key documents and + anchors, with retrieval times and what was validated against them; the + revocation artifact actually checked; and any signed log heads. The + result is a self-contained, independently re-verifiable dispute record + that survives any host disappearing or republishing. Re-fetches never + repair a bundle; the record is what was seen. Works with core alone; + SHOULD-level guidance. +- **Event log.** An append-only, hash-chained log of lifecycle events (key + add/remove, proof add/retract, revocations, deactivation): JCS-hashed + entries, `prev` linkage, per-entry JWS by the authorizing key + (`typ: "log-entry+jws"`), signed heads (`typ: "log-head+jwt"`), optional + host receipts (`typ: "log-receipt+jws"`). Replaying the log MUST reproduce + the identity's published documents exactly; divergence or conflicting + signed entries is portable proof of misbehavior. +- **Witnessing.** Heads published to at least one independent append-only + location, named in the identity's documents, so a host cannot present a + fabricated history to a first-time verifier. Witness trust belongs to the + verifier, never the host: a location the host nominates adds nothing + against that host fabricating history. RPs accept only witnesses they + trust independently: preconfigured, or a quorum across operators. + Revocation lists from log-backed issuers SHOULD commit to the log head + they reflect. + +## Registers + +- Artifact types `log-entry+jws`, `log-head+jwt`, `log-receipt+jws`. diff --git a/docs/ack-id/rfc/ext-controller.md b/docs/ack-id/rfc/ext-controller.md new file mode 100644 index 00000000..54e15529 --- /dev/null +++ b/docs/ack-id/rfc/ext-controller.md @@ -0,0 +1,314 @@ +# ACK-ID Extension: Controllers and Ownership (draft RFC) + +**Status: proposal draft.** The key words MUST, MUST NOT, SHOULD, SHOULD NOT, +and MAY are to be interpreted as described in RFC 2119. Extracts v2 working +draft sections 3.1 (controllers), 4.2, 5, 6.3, and 8.3 steps 2, 4, 5. + +## 1. Scope + +Core authorizes agents whose owner the RP onboarded out of band. Identity +there is pinned keys plus a claims check. This extension serves the other +case: an agent presents, and the RP has never met its owner. The RP needs +to establish, offline and without a callback, who stands behind the agent. +This extension defines: + +- the controller relationship, derived mechanically from the identity name, +- key purpose segregation within resolved DID documents, +- control grants (the issuer asserts it controls the subject), +- ownership proofs (binding an owner to an external anchor), +- the full verification checklist. + +Together with core, this is the line back to the accountable entity: request +to agent key, agent to owner through the grant and the control grant, +owner to anchor through an ownership proof, anchor to legal entity through +the RP's own records. Core + this extension is the flagship pairing; a +deployment that never verifies unknown counterparties does not need it. + +This document layers on core. Identities and key resolution (core Sections +3.1-3.2), keys and thumbprints (core Section 4), grants (core Section 5), +and signed requests (core Section 6) apply here without redefinition. One +core premise is replaced: core verifies grants against pinned owner keys, +while this extension verifies them against assertion keys resolved from the +issuer's DID document (Section 6). That substitution is what removes the +onboarding requirement. + +## 2. Controllers + +Each agent identity has exactly one **controller**, derived by an exact rule: +**the controller is the DID with the final path segment removed.** The +recursion grounds at the bare origin. Multi-level paths are legal and derive +recursively (`did:web:acme.com:teams:invoice-bot` is controlled by +`did:web:acme.com:teams`, which is controlled by `did:web:acme.com`); +one-segment identities derive the bare origin directly. + +A bare origin can be an owner identity (a self-hosted deployment, +`did:web:acme.com`) or a hosted directory's apex serving many tenants. A +verifier never classifies it in advance; what verifies decides it. The +derivation names exactly one candidate controller per level. When a control +grant from the candidate verifies (Section 4), the candidate is the verified +controller. When none does (the directory-apex case, where the host never +asserts control of its tenants), the identity is a top-level owner: it has +no controller, and for consequential decisions it anchors directly through +ownership proofs (Section 5). + +The agent's DID document `controller` property MUST equal the derived +controller; verifiers MUST reject a document that disagrees with the +derivation. Because the rule is mechanical and there is exactly one +legitimate control-grant issuer per agent (Section 4), two verifiers can +never disagree about who the controller is. Verifiers MUST key policy +decisions on the canonical DID, never on an origin or display string. + +Derivation operates on path segments only, never on DNS labels. Every bare +origin, including a multi-label host such as +`did:web:agent.company.service.com`, is an apex: it has no derived +controller, and a control grant asserting control of a bare origin has no +legitimate issuer (Section 4) and MUST be rejected. A label-based rule is +deliberately not defined. It would need a stop boundary between +organizational labels and registry labels, which is the Public Suffix List. +That list is an external, continuously edited dependency: two verifiers +with different copies would derive different controllers, and the +never-disagree property above would be lost. DNS delegation also does not +reliably track organizational control: the platform operating `service.com` +controls every label beneath it, whoever a subdomain nominally belongs to. + +A deployment that gives each agent its own subdomain therefore chooses +between two shapes. Treat each agent subdomain as a top-level owner in its +own right, pinned at onboarding (core) or anchored directly by ownership +proofs (Section 5), with no derived chain. Or name agents as path +identities under the company's host +(`did:web:company.service.com:agent-name`), which restores the mechanical +chain: the agent's controller is `did:web:company.service.com`, itself +anchorable to the company's own domain by an ownership proof. The identity +names where keys live (core Section 3.2), never where the agent serves +traffic. A platform can keep per-agent subdomains for routing while naming +identities as paths. + +Core reads only `publicKeyJwk` entries from the DID document (core Section +3.2); this extension is what assigns the document's `controller` and +verification-relationship members their meaning and enforcement. + +## 3. Key purpose + +Keys are segregated by purpose through the DID document's verification +relationships, and verifiers implementing this extension MUST enforce the +segregation: + +- **Assertion keys** appear in `assertionMethod`. Only these keys sign + grants and proof claims. +- **Request keys** appear in `authentication`. They sign HTTP requests + (core Section 6) and DPoP proofs (ext-web); typically the agent's key. +- **Revocation keys** (ext-revocation) appear in the DID document but MUST + NOT appear in `assertionMethod`. They sign revocation artifacts and are + expected to be online continuously; the exclusion is what keeps a hot key + from minting grants. + +Enforcement, from published documents: a verifier MUST reject a grant or +proof claim signed by a key outside the issuer's `assertionMethod`. It MUST +verify request signatures only against keys in the subject's +`authentication`. It MUST reject a revocation artifact whose signing key is +absent from the issuer's DID document or present in `assertionMethod`. In +core, where the RP pins exact keys and never reads verification +relationships, segregation reduces to core's existing rules: the pinned set +contains grant-signing keys only (core Section 7), and possession matches +`cnf.jkt` exactly. + +Relationship entries resolve under a closed profile: `assertionMethod`, +`authentication`, and the revocation relationship carry fragment references +to the document's own top-level `verificationMethod` entries (the +`publicKeyJwk` entries core reads). Embedded verification methods inside a +relationship array, and references to anything outside the document, are +outside this profile and MUST be ignored. A reference with no matching +top-level entry is ignored the same way: the key is unavailable for that +purpose, and a dangling reference never widens what a key may do. Duplicate +`verificationMethod` ids make the document malformed (core Section 3.2). + +## 4. Control grants + +A control grant asserts the controller relationship as a verifiable +artifact: the issuer asserts it controls the subject. + +Shape: a grant (core Section 5, `typ: "grant+jwt"`) with these +deviations from core's claim table, which this extension defines for the +`control` scope only: + +| claim | rule | +| ------- | -------------------------------------------------------------------------------------- | +| `scope` | exactly the single token `control`; mixing with action scopes is rejected | +| `iss` | MUST equal the controller derived from `sub` (Section 2); any other issuer is rejected | +| `sub` | the agent DID | +| `aud` | MUST be absent | +| `cnf` | OPTIONAL | +| `exp` | REQUIRED; SHOULD be at most 90 days, reissued before expiry | + +It binds DIDs rather than keys, so key rotation invalidates nothing. The signature +MUST verify against a current assertion key of the derived controller +(Section 3). Because `iss` is derivable from `sub` by any verifier, there is +exactly one legitimate issuer, and a control grant from anyone else is +rejected regardless of signature validity. + +The table is the complete set of overrides. A verifier recognizing the +`control` scope evaluates the artifact under core Section 5 with exactly +these deviations and no others: `typ`, the claim table, the `crit` rule, +time rules, lifetime bound, and `jti` uniqueness stand unchanged. Core's +owner-onboarding premise is replaced per Section 6: the signature verifies +against resolved assertion keys, never a pinned set. Possession is carried +by the leaf grants presented alongside, whose `cnf.jkt` core already +requires. The control grant's `sub` MUST equal the presenting agent's DID +(the `Signature-Agent` mapping, core Section 3.1), or the grant is ignored +for this presentation. + +A verifier obtains the control grant from the presentation itself. An +agent operating under this extension presents it in the `Grant` field +alongside its leaf grants (core Section 6; each presented grant is +evaluated independently). It MAY additionally be published with the +subject's documents; publication location is deployment-defined. A lapsed +control grant means the ownership relationship no longer verifies +offline, and that state SHOULD be publicly legible wherever the identity is +rendered. + +Control grants need no distinct `typ`, unlike ext-delegation's +intermediates: the `control` scope token is mandatory and reserved, so every +core-only verifier already rejects them unconditionally (core Section 5). + +## 5. Ownership proofs + +A proof binds an identity to an external anchor. It has two halves, and a +verifier checks both directly, without reference to any directory: + +1. **The claim**: a JWS signed by a current assertion key of the identity, + header `typ: "proof+jwt"`, payload naming the anchor: + + ```json + { + "iss": "did:web:acme.com", + "type": "domain", + "anchor": "acme.com", + "iat": 1781035200, + "jti": "prf_8fk2" + } + ``` + +2. **The anchor**: a record placed where only the anchor's owner could put + it. + +### 5.1 Domain anchor + +Either of: + +- DNS TXT at `_ack-id.`: + + ``` + _ack-id.acme.com. TXT "ack-id=did:web:acme.com;jkt=kWmvje3K..." + ``` + + The `jkt` pins a key, and the pinned key MUST be a current **assertion + key** of the named identity; a pin naming a key outside `assertionMethod` + makes the proof fail. A verifier that reads a pinned assertion key from + DNS the counterparty controls, then verifies grants against that key, + has verified the chain with no reference to any key directory. `jkt` MAY + be omitted, but verifiers SHOULD prefer pinned anchors. Multiple `_ack-id` + records are evaluated independently; a record naming a different identity + MUST be surfaced, not silently ignored. + +- HTTPS file `https:///.well-known/ack-id.json`, embedding the claim + so the file is self-contained: + + ```json + { "claims": [""] } + ``` + +### 5.2 Code-host anchor + +A file committed where only the org could commit it. One exact location per +host, so two verifiers can never fetch different records. For GitHub: the +repository named `.ack-id` under the owner, file `ack-id.json` at the +repository root, read from the default branch through the host's raw +endpoint (`https://raw.githubusercontent.com//.ack-id/HEAD/ack-id.json`). +Profiles for other hosts pin their equivalents. The shape is the same as +the well-known file, with `anchor` = `github:` or the host's +equivalent. The proof record MUST store the host's stable numeric owner ID +at creation. Verification MUST NOT follow rename or transfer redirects and +MUST treat an owner-ID mismatch as revoked. Verifiers SHOULD weight domain +anchors above code-host anchors: anyone with repository-creation rights in +an org can mint the latter. + +### 5.3 Semantics + +A proof asserts control of that anchor and nothing more. RP policy MUST +compare proven anchors against out-of-band expectations of the counterparty +(a contract, a merchant record, an allowlist) and MUST NOT treat the +identity's name string as evidence of anything. + +Anchors MUST be re-checked periodically by whoever publishes proof status, +and a claim whose signing key is removed is dropped with it. Core's fetch +rules (SSRF checks on every hop, timeouts, size caps; core Section 3.2) +apply to every anchor fetch. Code-host anchor verification MUST NOT follow +rename or transfer redirects (5.2); that rule is this extension's, not +core's. + +## 6. Full verification + +In order, for consequential decisions or unknown counterparties: + +1. **Possession**: as core Section 7 step 1, with request keys drawn from + `authentication` only (Section 3). +2. **Canonical identity**: map the `Signature-Agent` URL to the agent's DID + (core Section 3.1) and resolve its DID document; policy keys on the DID. +3. **Each leaf grant**: core Section 7 step 2, with one substitution: the + signature verifies against a current assertion key of the issuer, + resolved per core Section 3.2 and segregated per Section 3, rather than a + pinned key. Where the issuer advertises a revocation mechanism + (ext-revocation), its status for the `jti` MUST be checked; otherwise + core's Section 8 posture (maximum acceptable lifetime) applies. +4. **Controller**: derive the controller (Section 2) and verify the + subject's control grant (Section 4). If it is absent or expired there + is no offline-verifiable owner; the RP fails or knowingly falls back to + host-asserted data at its own risk. +5. **Proofs and policy**: verify the controller's ownership proofs + (Section 5) and compare proven anchors against out-of-band expectations + (5.3). The chain holds only when the leaf grant's issuer is the + verified controller (or chains to it, ext-delegation); an action grant + not linked to the controller is third-party authorization the RP's policy + must independently accept. + +What a passing verification proves: continuity of key control, the control +relationship, and control of the proven anchors. The step from anchor to +legal entity belongs to the RP's records (the contract or merchant record +that names the domain), never to the protocol. It still never proves human +presence or consent. + +## 7. Interaction with core + +A core-only verifier rejects every artifact this extension defines: control +grants fall to core's reserved scope token, and a proof claim presented as +a grant falls to core's `typ` check (`proof+jwt` is not `grant+jwt`). +There is no downgrade path. The two verification modes compose: an RP MAY +run core verification for onboarded owners and full verification for +unknown ones; nothing in this extension changes the meaning of a core +verification that passes. + +## 8. Security considerations + +- **Name-derived control.** The controller relationship is derived from the + name, and whoever controls the origin serving the documents can publish + keys for every identity under it. The derivation rule makes this legible; + it does not remove it. Where that concentration matters, pin keys instead + of locations: a DNS `jkt` pin (5.1) verifies the chain with no reference + to the serving origin's key documents. +- **Display strings.** Names prove nothing (5.3). Any UI that renders an + identity SHOULD use its proven anchors as the trust signal, never its + name string. Lookalike names over unproven anchors are the expected + phishing shape. +- **Anchor fetching.** Server-side anchor fetching is an SSRF surface; core + Section 3.2 rules apply. DNS SHOULD be queried from multiple vantage + points and DNSSEC validated where present. +- **Code-host anchors.** Weight below domain anchors (5.2); repository + creation is a much weaker capability than DNS control. +- **Lapsed control grants.** A lapsed control grant is a silent + downgrade from verified ownership to host-asserted data. RPs SHOULD treat + it as a state change rather than a steady state (Section 4). + +## Registers + +- Scope token `control`, with the Section 4 claim table. +- Artifact type `proof+jwt`. diff --git a/docs/ack-id/rfc/ext-delegation.md b/docs/ack-id/rfc/ext-delegation.md new file mode 100644 index 00000000..619c3b56 --- /dev/null +++ b/docs/ack-id/rfc/ext-delegation.md @@ -0,0 +1,94 @@ +# Extension: Delegation Chains (stub) + +**Extracts v2 working draft sections 6.2, 6.4, and 6.5, plus the did:key +portion of 3.1.** + +Core grants are direct: owner signs, agent presents. This extension adds +re-delegation with normative attenuation. That is what lets owner keys stay +offline while short-lived grants are minted continuously. It is also where +the ephemeral sub-agent story lives: a chained leaf whose subject is a +did:key needs no hosted document at all. + +## Adds + +- **The `chain` claim** (reserved in core): exactly one entry, the SHA-256 + hash (over the ASCII bytes of the compact serialization) of the immediate + parent. Each parent carries its own `chain`, so the ancestry is a walked + linked list, and the full ancestry is pinned transitively: a parent's hash + covers the parent's own `chain` claim. Presenters carry every ancestor, + and every chained artifact names `chain` in `crit` (core Section 5), so + a verifier without this extension rejects it. Verifiers MUST reject a + `chain` with more than one entry: a longer list carries decoy hashes that + ride along unverified. Verifiers MUST detect cycles. They MUST check + issuer-subject linkage (`child.iss == parent.sub`), each ancestor's + signature, expiry, and revocation, and the root's `iss` against the RP's + trust anchor. This supersedes working draft 6.2's format (full-ancestry + list plus a path-consistency rule). The commitment is cryptographically + equivalent. The single entry is decoy-proof structurally rather than by + an extra rule, and pairwise hash linkage is the AP2 mandate-chain + convention, so a grant can slot into AP2/UCP flows without redesign. +- **Key binding through the chain.** A parent's `cnf.jkt` pins the exact key + that must sign its child. +- **Attenuation rules.** A child never exceeds its parent: `exp` never later, + `aud` never wider, every scope token authorized verbatim or by a parent + wildcard (`*`, or prefix form `invoices:*`), constraints provably within the + parent's under the audience's semantics, else reject. +- **Intermediates.** Delegation envelopes, never accepted as direct grants. + Attenuation wildcards and array/absent `aud` are legal only here, and an + intermediate MUST be structurally marked (a distinct `typ`, + `grant-int+jwt`, registered by this extension) so that every verifier + rejects it as a direct grant unconditionally, even when it carries a + plain shape. Without the marker, a plain-shaped intermediate + presented by a compromised issuance service would satisfy every core rule + at its full unattenuated breadth. +- **The issuance-service pattern.** Owner signs one medium-lived intermediate + offline; a hot service mints short-lived leaves; compromise is bounded by + the intermediate and revocable by its `jti`. The minting interface is + outside verification, and this extension will profile one for interop: an + OAuth 2.0 Token Exchange (RFC 8693) request whose `subject_token` is the + intermediate, whose `requested_token_type` names `grant+jwt`, and whose + response carries the leaf. Token-exchange-capable issuers (a Keycloak + realm, a custody provider's signing API) can expose it directly. + Verification of the resulting artifacts never involves the exchange + endpoint; every leaf still verifies under this extension's chain rules + alone, so the profile adds a mint surface and no trust surface. +- **did:key identities for ephemeral sub-agents.** A did:key carries its + public key inside the identifier, so it resolves with no fetch; it has no + hosted document, controller, rotation, or revocation, so it can never + anchor ownership. It exists to be the short-lived `sub` of a chained leaf + (owner signs the intermediate; an orchestrator mints a leaf whose subject + is the worker's did:key), and the worker dies with its grant. Encodes an + Ed25519 or P-256 key (core Section 4). Open item: signed-request carriage. + `Signature-Agent` requires a URL (core Section 6) and a did:key has none, + so the presentation profile for did:key subjects (key resolution directly + from the identifier, `Signature-Agent` absent or repurposed) needs + definition here. +- **Registration grants.** `scope: "register"` (reserved in core): + single-use, key-pinned, one-hour authority to create a named identity at a + hosted directory, with namespace-derivation checks so an owner's signature + never authorizes a name in someone else's namespace. Drafting requirements + for the normative text: `aud` names the directory, and the requested name + is carried explicitly in the grant, never inferred. Namespace containment + is byte-exact against the owner's identity after core Section 3.1 + validation (which already rejects names that would need escaping). + Redemption at the directory is an atomic check-and-set keyed by + (`iss`, `jti`), retained through `exp`. + +## Registers + +- Claim `chain`. +- Artifact type `grant-int+jwt` (intermediates). +- Scope token `register`, and the wildcard attenuation grammar evaluated + inside chains (`*`, prefix form). The grammar governs parent-to-child + containment only; what a scope token means to an audience stays the + audience's (core Section 5). + +## Interaction with core + +A core-only verifier rejects every artifact this extension defines: chained +leaves fall to core's `crit` rule (`chain` is always critical), +intermediates to core's `typ` check (`grant-int+jwt`), registration grants +to the reserved `register` scope token. There is no downgrade path. One consequence worth +naming: without this extension, every grant is signed directly by an owner +key, so short grant lifetimes keep that key hot (core Section 8 states the +tradeoff). diff --git a/docs/ack-id/rfc/ext-revocation.md b/docs/ack-id/rfc/ext-revocation.md new file mode 100644 index 00000000..91900556 --- /dev/null +++ b/docs/ack-id/rfc/ext-revocation.md @@ -0,0 +1,41 @@ +# Extension: Revocation Mechanisms (stub) + +**Extracts v2 working draft sections 9.1 and 9.2.** + +Core's revocation levers are short `exp`, single-use `jti`, agent key +removal, and owner-key unpinning. This extension defines the two +discoverable mechanisms for pulling back a still-valid grant by `jti`. It +also carries the issuer-side obligation: an issuer of grants that outlive its +RPs' acceptable exposure window MUST expose one of these mechanisms, and a +verifier implementing this extension MUST check it. + +## Adds + +- **Signed revocation list** (baseline): a short-lived JWT + (`typ: "revocation-list+jwt"`) listing revoked `jti` values, `exp` on the + order of minutes so staleness is legible, entries retained until the revoked + artifact's own `exp` passes. Fully publicly auditable; no holder privacy. +- **Status list** (optional profile): the IETF OAuth Token Status List, JWT + form only. Covered grants carry `status.status_list.uri` and `idx`, and + name `status` in `crit` (core Section 5); the + verifier fetches the whole list and reads the bit locally, so the issuer + never learns which credential was checked. For issuers with many holders to + protect; requires scale, randomized indexes, and decoys to deliver herd + privacy. The W3C BitstringStatusList (JSON-LD) MUST NOT be used. +- **Revocation keys.** Both artifacts are signed by a dedicated + revocation-purpose key, published in the DID document but excluded from + assertion use, so a continuously online key can never mint grants. + ext-controller's key-purpose segregation enforces the exclusion and is a + declared dependency of that guarantee. A deployment running this + extension without ext-controller enforces it at onboarding instead: a + core RP MUST NOT pin an issuer's revocation key among that issuer's + grant-signing keys. +- **Discovery.** Advertised via the `RevocationList` service entry in the DID + document (ext-controller) or learned at onboarding (core). + +## Registers + +- Artifact type `revocation-list+jwt`. +- Grant claim `status` (per the IETF status list draft), always named in + `crit`, so a status-bound grant is rejected by any verifier that cannot + check its status (core Section 5). diff --git a/docs/ack-id/rfc/ext-web.md b/docs/ack-id/rfc/ext-web.md new file mode 100644 index 00000000..58b85a00 --- /dev/null +++ b/docs/ack-id/rfc/ext-web.md @@ -0,0 +1,52 @@ +# Extension: Web Surface (stub) + +**Extracts v2 working draft section 8.2 (OAuth carriage, discovery, keyless +clients) and the Web Bot Auth directory (3.2, L3).** + +Core's signed-request carriage already uses the Web Bot Auth wire format. This +extension adds the surfaces that make an agent legible to infrastructure that +is not an ACK verifier: WBA edges, OAuth authorization servers, MCP servers. + +## Adds + +- **OIDC discovery key resolution** (opt-in profile). For orgs whose JWKS + lives behind an OAuth2/OIDC `jwks_uri` (a Keycloak realm, a hosted IdP) + rather than at core's fixed paths. + Explicitly configured per identity by the verifier, never triggered by a + 404 on the fixed path (core resolution has no fallback). For a configured + identity, the discovered `jwks_uri` is the sole key location and replaces + core's fixed path. Key sets from the two locations MUST NOT be unioned; a + union would stop key removal at either location from revoking. + Requirements: the RFC 8414 insertion form is fetched first and, when it + yields a valid document, is authoritative. The OIDC path-append form is + consulted only when the insertion form returns no document. When both + were fetched and disagree on `issuer` or `jwks_uri`, the verifier MUST + reject rather than choose. The discovery document's `issuer` MUST exactly + equal the identity URL. The `jwks_uri` MUST be same-origin with the + identity URL. Core's SSRF, redirect, timeout, and size rules apply to + every fetch, including the `jwks_uri` target. +- **Web Bot Auth directory.** Hosting + `/.well-known/http-message-signatures-directory` at the agent's signing + origin (a dedicated subdomain), per the HTTP message signatures directory + draft, so CDN-style edges that speak that draft recognize the agent. Same + keys as the core DID document, republished at the location their spec + requires. +- **OAuth via CIMD.** The identity URL doubles as an OAuth Client ID Metadata + Document `client_id`: registration by URL alone, `private_key_jwt` client + authentication, no client secret. Hardware-backed P-256 keys plug in here. +- **DPoP-bound tokens** (RFC 9449), with the `mh` claim binding the `Grant` + field on the OAuth carriage: base64url SHA-256 over the re-serialized + (RFC 9651) structured-field form, never raw wire bytes. +- **RP discovery.** The well-known document advertising audience identifier, + required scopes, accepted carriages, and supported extensions; 401 + challenges naming the same parameters; RFC 9728 protected-resource metadata + for MCP servers. +- **Keyless clients.** MCP assistant clients with + `token_endpoint_auth_method: none` and PKCE: bearer sessions that satisfy + no `cnf`-bound grant and are never an ACK-verified chain; ACK guarantees + come from a key-holding gateway signing upstream. + +## Registers + +- Artifact type `dpop+jwt` usage profile and the `mh` DPoP claim. +- The RP discovery document location and schema. diff --git a/docs/ack-pay/receipt-verification.mdx b/docs/ack-pay/receipt-verification.mdx index ec449e4c..d4183ca2 100644 --- a/docs/ack-pay/receipt-verification.mdx +++ b/docs/ack-pay/receipt-verification.mdx @@ -66,8 +66,9 @@ _Example ACK Receipt Verifiable Credential:_ `credentialSubject.metadata` is optional and should be treated as an extension point for verifier-specific payment evidence. Keep the core receipt fields -stable, and put references or hashes to external policy, mandate, execution, -and settlement records in metadata when a deployment needs a richer audit trail. +stable, and put references or hashes to external policy, AP2 mandate, +execution, and settlement records in metadata when a deployment needs a richer +audit trail. Metadata fields are non-normative: ACK-Pay verifies the receipt signature and the bound payment request token, but applications decide which metadata fields diff --git a/docs/ack-pay/rfc/README.md b/docs/ack-pay/rfc/README.md new file mode 100644 index 00000000..f258c084 --- /dev/null +++ b/docs/ack-pay/rfc/README.md @@ -0,0 +1,50 @@ +# ACK-Pay Core RFC (proposal) + +Draft restructuring of ACK-Pay into a design language plus one small +normative profile. Not wired into the docs site; discussion material only. + +Depends on the [ACK-ID core RFC](../../ack-id/rfc/README.md) for identities, +key resolution, grants, and signed requests. Read that first. + +## Motivation + +The payments wire layer is already won: x402, l402, h402, and the card +networks' agent programs define how money moves. A competing ACK wire +protocol would add confusion and get no adoption. None of them provides +accountability: a quote a seller cannot later deny, a proof of payment a +buyer can show to a third party, and a trail from that payment back to the +owner behind the paying agent. + +x402 now ships signed offers and receipts as an extension +([offer-receipt](https://docs.x402.org/extensions/offer-receipt)), with JWS +signing and did:web key discovery from `did.json`. That is the artifact layer +ACK-Pay would otherwise have had to invent, discoverable from the same DID +document ACK-ID core already reads. So ACK-Pay stops being a protocol and +becomes: + +1. **A design language** (non-normative): the roles, the client- and + server-initiated flows, human oversight points, and how the offer/receipt + pattern maps onto any rail. +2. **One normative profile** ([core](./core.md)): adopt the x402 + offer-receipt artifacts (pinned at a named version, core Section 1), sign + them with ACK-ID identities, and bind receipts to the grant that + authorized the payment. That binding is the trail: receipt to agent, + agent to grant, grant to owner, owner to legal entity (ACK-ID + ext-controller). Core + this profile is one of the two flagship pairings + named in the ACK-ID RFC; the accountability trail is the gap the wire + protocols leave. + +## Document map + +| Current ACK-Pay material | Lands in | +| -------------------------------------------------------- | ----------------------------------------------------- | +| introduction, components-roles, use-cases | design language (non-normative) | +| client-initiated-sequence, server-initiated-sequence | design language (non-normative) | +| core-payment-sequences, hitl, operational-considerations | design language (non-normative) | +| payment-request-payload, payment-service | superseded by signed offers ([core](./core.md)) | +| receipt-verification (VC receipts) | superseded by the receipt profile ([core](./core.md)) | +| `packages/ack-pay` (JWT request tokens, VC receipts) | recut against the profile after RFC decisions | + +## Open decisions + +See [core Section 8](./core.md#8-open-decisions). diff --git a/docs/ack-pay/rfc/core.md b/docs/ack-pay/rfc/core.md new file mode 100644 index 00000000..d73d31b7 --- /dev/null +++ b/docs/ack-pay/rfc/core.md @@ -0,0 +1,177 @@ +# ACK-Pay Core (draft RFC) + +**Status: proposal draft.** The key words MUST, MUST NOT, SHOULD, SHOULD NOT, +and MAY are to be interpreted as described in RFC 2119. + +## 1. Scope + +ACK-Pay Core defines how a payment leaves verifiable evidence: a **signed +offer** the seller cannot later deny quoting, a **signed receipt** the buyer +can present to third parties, and a binding from that receipt to the ACK-ID +grant that authorized the payment. It defines no wire protocol, no +settlement mechanism, and no new artifact formats. The artifacts are the +x402 [offer-receipt extension](https://docs.x402.org/extensions/offer-receipt), +adopted as published and profiled here. This profile pins the extension as +shipped in `@x402/extensions` 2.22.0 (x402 repo, offer-receipt source at +commit `59ac597`, 2026-06-17). Upstream changes flow into this profile only +by re-pinning here, never implicitly. Everything else ACK-Pay describes +(roles, payment flows, human oversight, rails other than x402) is design +language: non-normative patterns that define no conformance. + +This document layers on ACK-ID core: identities and key resolution (ACK-ID +Sections 3.1-3.2), keys and thumbprints (Section 4), grants (Section 5), +and signed requests (Section 6) are used here without redefinition. + +## 2. Terminology + +- **Seller**: the party serving the paid resource and signing offers and + receipts. The x402 resource server. +- **Buyer**: the agent paying for the resource, acting under an ACK-ID + grant from its owner. +- **Offer**: the seller-signed quote attached to a `402 Payment Required` + response. +- **Receipt**: the seller-signed acknowledgment attached to the `200 OK` + response after payment. +- **Rail**: the mechanism that moves the money. Core profiles x402; other + rails are design language (Section 6). + +## 3. Artifacts + +The offer and receipt payloads, their placement (offers in the 402 response's +`extensions` member; receipts in the `PAYMENT-RESPONSE` `extensions`), their +field sets (`resourceUrl`, `offerType`, `network`, `amount`, `payTo`, +`validUntil`; `resourceUrl`, `payer`, `network`, `issuedAt`, optional +`txHash`), and their signature encodings are as defined by the x402 +offer-receipt extension. This profile constrains them: + +- **Signature scheme.** ACK-Pay conformance requires the JWS scheme. An + EIP-712/did:pkh signature MAY additionally be present; it carries no + ACK-Pay semantics and is passed through unevaluated. +- **Signer identity.** The JWS signer MUST be a did:web identity per ACK-ID + core Section 3.1, and verifiers MUST resolve its keys per ACK-ID core + Section 3.2. This is the same `did.json` the x402 extension documents for + key discovery: one hosted document serves both protocols. ACK-ID core's + resolution rules (one fixed location, SSRF protections, freshness) + apply. +- **Algorithms.** EdDSA and ES256, per ACK-ID core Section 4. Whether ACK + verifiers also accept ES256K for wallet-adjacent deployments is an open + decision (Section 8). +- **Key separation.** The offer/receipt signing key MUST NOT be the `payTo` + account and SHOULD NOT be an owner grant-signing key. A dedicated + signing key bounds what a compromise can mint. +- **Offer expiry.** Offers MUST carry `validUntil`, and verifiers MUST + reject an offer past it. Sellers SHOULD keep offer validity short (the + x402 default of 300 seconds is a reasonable ceiling). + +## 4. Identity binding + +A receipt's `payer` is a settlement address, which moves money and names no +accountable party. The binding that makes a receipt an accountability +artifact: + +- When the paid request was ACK-verified (signed request plus grant, ACK-ID + core Sections 6-7), the seller SHOULD include in the receipt payload a + single profile-owned member, `ack`, carrying the agent's DID and the `jti` + of the grant presented: + + ```json + "ack": { "agent": "did:web:acme.com:shopper", "grantId": "grn_4kq8" } + ``` + +- The `ack` member rides inside the extension's JWS receipt payload and is + covered by its signature. It has no EIP-712 representation: that schema + is fixed-typed and cannot carry it, which is one more reason conformance + requires the JWS scheme (Section 3). A canonical bound-receipt fixture + ships with the test vectors the ACK-ID RFC tracks as a core deliverable. + Namespacing the binding under one member keeps + the profile's footprint in the upstream payload to a single name, so + upstream evolution cannot collide with it member-by-member. A verifier + that does not recognize it ignores it: the binding only ever adds + evidence, never authority, so it is exactly the kind of member that is + never named `crit` (ACK-ID core Section 5). +- A receipt without the binding is still a valid x402 receipt: it proves + payment and attributes it to no one. RPs that need the trail reject + unbound receipts as a policy choice. + +The resulting chain, each link independently verifiable: receipt names the +agent (`ack.agent`, signed by the seller); the grant named by +`ack.grantId` binds that agent to its owner (`iss`, signed by the owner); +the owner's legal identity is anchored per ACK-ID ext-controller (ownership +proofs). This is the full trail from a payment event to the legal entity +behind the paying agent, walkable by a third party with no callback to any +participant. + +## 5. Third-party verification + +A third party verifies a receipt (with its offer, when presented together) +as follows. Checks 1-4 restate the x402 extension's verification in ACK-ID +terms; check 5 is this profile's addition. + +1. **Keys**: resolve the signer's keys per ACK-ID core Section 3.2 from the + signer DID; thumbprint rules per ACK-ID core Section 4.1. +2. **Signatures**: verify the JWS on the receipt, and on the offer when + present. +3. **Matching**: receipt and offer agree on `resourceUrl` and `network`; the + offer's `validUntil` had not passed at `issuedAt`. +4. **Freshness**: `issuedAt` is sane for the claimed transaction; where + `txHash` is present, it MAY be checked against the named network. +5. **The trail**: when the `ack` member is present and the named grant is + presented alongside the receipt (presenters retain and supply it; + ext-audit's evidence bundles are the retention shape), verify the grant + per ACK-ID core Section 7 rules: signature against the owner's keys, + `sub` equals `ack.agent`, `jti` equals `ack.grantId`. Where + legal-entity assurance is required, verify the owner's anchors per + ext-controller. A receipt whose `ack` member arrives without the grant + attributes the payment but proves no authorization; RPs that need the + trail treat it as unbound. + +What a passing verification proves: the named seller quoted these terms, +acknowledged payment for this resource, and attributed the payment to this +agent under this owner's grant. The receipt carries no amount or `payTo`. +The offer's signature proves the terms. The seller's signed acknowledgment +attests their satisfaction, and `txHash` (check 4) is the on-chain +corroboration where present. It does not prove the resource was +delivered or fit for purpose, that settlement is final on any particular +rail, or that a human approved the payment. + +## 6. Other rails (design language) + +The offer/receipt pattern is rail-independent: an offer is a signed quote +before payment, a receipt a signed acknowledgment after, whatever moved the +money between them. Mappings for l402, card-network agent programs +(Mastercard Agent Pay and its peers), and bank transfers follow the same +shape and are non-normative until a deployment needs one written down. The +card networks are converging on the same primitives: Mastercard's +Verifiable Intent pilots carry signed, key-bound SD-JWT mandates, so a +mapping there is translation rather than invention. This section is the design-language boundary: flows, +oversight points, and rail mappings live in the ACK-Pay pattern docs and +never define conformance. + +## 7. Relationship to the existing ACK-Pay spec + +- The payment-request token and payment-service flows are superseded by + signed offers: the seller signs the quote at the 402, and no intermediary + mints it. +- VC receipts are superseded by this profile's receipts: plain JOSE, same + no-VC posture as ACK-ID core. +- The flow and role documents remain as the design language, marked + non-normative. +- `packages/ack-pay` gets recut against this profile after the RFC + decisions land, in the same sequence as the ACK-ID SDK. + +## 8. Open decisions + +1. **Upstreaming the binding member.** `ack` is a profile-defined member of + the extension's receipt payload; the single namespaced name confines + collision risk with upstream evolution, but upstreaming is still the + durable answer. Propose to the x402 extension either the `ack` member as + an extension point or native attribution fields, so non-ACK verifiers + learn to display the binding. This should be pursued now, while the + extension is young; the answer decides whether the member stays + namespaced or migrates to upstream-native fields. +2. **ES256K.** The x402 JWS scheme allows secp256k1. Accepting it widens + wallet-key reuse; rejecting it keeps ACK-ID core's two-algorithm + discipline. Current draft: reject, revisit on deployment evidence. +3. **Offer presentation.** Receipts verify without their offer; disputes + want both. Should the profile require buyers to retain offers + (evidence-bundle style, ACK-ID ext-audit) or leave retention to policy?