数据截至 (上游 commit e1ea56db72a6)
AP2 — Mandate 与角色(核心概念)
这一章讲 AP2 的两块基石:mandate 的开/闭两态,和谁来签、谁来验。把这两件事搞清,后面所有密码学细节都只是「怎么实现它」。
1. Mandate 是什么
一句话: mandate(授权书)是一份人类批准的、密码学签名的凭证,告诉验证方「这个 agent 被授权做这件事」。
AP2 的核心不是发明新的加密,而是发明一种结构:把「授权」拆成两个状态(agent_authorization.md:392-399):
| 状态 | 含义 | 由谁签 | 含 cnf(下一跳公钥)? |
|---|---|---|---|
| 开 (Open) | 还没绑到具体交易,只带一组约束 + 授权某个 agent 使用 | 用户(经 Trusted Surface) | 是(绑定允许使用它的 agent) |
| 闭 (Closed) | 绑死到一笔具体交易,授权 agent 对某验证方执行一个动作 | 用户本人,或 agent 用开 mandate 里 cnf 指定的钥匙 | 否(终端,不再往下委托) |
为什么要拆成两态
因为 agent 是非确定性的,你不能给它一张「随便花」的空白支票。开 mandate 解决的就是:
「人不在场时,怎么既让 agent 自主成交,又把它框住?」(
agent_authorization.md:400-402)
用户签的是规则(开),agent 成交后补签事实(闭),验证方检查「事实是否落在规则内」。这样:
- 用户不必为每笔交易在场点确认;
- agent 的越权(超预算、换商家)会在验证时被当场抓住;
- 整条链是自证的——验证方只信任根(用户/银行的钥匙),不必信任 agent。
2. 五种角色,各管一段
AP2 把一笔交易拆给五个角色,核心区分是**「谁是 LLM(潜在攻击者)、谁必须是确定性代码」**(specification.md:59-98):
┌──────────────┐
用户 ───▶│ Trusted │ 必须非 agentic:唯一能签「用户 mandate」的地方
│ Surface (TS) │ 拿到知情同意 → 用 user_sk 签名
└──────┬───────┘
│ 把签好的 mandate 交回
▼
┌──────────────┐ 谈货/组车/出示 mandate ┌───────────┐
│ Shopping │──────────────────────────────▶│ Merchant │
│ Agent (SA) │ └───────────┘
│ 预期 agentic │── 拿支付凭证 ──▶┌──────────────┐
└──────────────┘ │ Credential │
│ Provider(CP)│
└──────────────┘
│ 处理支付
▼
┌──────────────┐
│ Merchant Pay │
│ Processor MPP│
└──────────────┘
怎么读这张图: 唯一能签出「用户授权」的是 Trusted Surface,且它必须是确定性代码(specification.md:78-80)。其余三个验证方(M/CP/MPP)可以是 agentic 也可以不是,但它们的验证逻辑必须跑在确定性代码里(specification.md:96-98)——这是协议最硬的一条安全约束。
3. 两种模式:人在场 vs 人不在场
这是 AP2 最常被问到的区别(specification.md:166-190)。验证方收到的永远是闭 mandate,差别只在「信任的根是谁」:
| Human Present(直接) | Human Not Present(自主) | |
|---|---|---|
| 用户批准什么 | 闭 mandate(看到真实成交,亲自批准) | 开 mandate(只批准一组约束) |
| 闭 mandate 谁签 | 用户(用户钥匙直接签) | agent(用开 mandate cnf 授权的 agent 钥匙) |
| 验证方信任链 | 直接验用户签名 | 验开 mandate(用户签)→ 再验闭 mandate(agent 签)落在约束内 |
| 链长 | 1 跳(根即闭) | ≥2 跳(开根 + agent 闭) |
巧妙处: 人在场时,因为用户亲自批了闭 mandate,这一步常常可以退化成传统电商流程——商家和 Trusted Surface 直接通信即可(
specification.md:217-219)。AP2 不是要取代电商,而是只在「agent 自 主」时才加那层密码学。
防双花的硬规则: 自主模式下,agent 必须先收到上一份开 mandate 的拒绝收据,才能出示下一份开 mandate(
specification.md:237-240),防止用同一份开 mandate 批准多个不同 checkout。
4. 三类 / 两类 mandate(命名陷阱预警)
这里有个容易把人绕晕的地方,先讲清:
- 协议规范 v0.2 只定义两类 mandate:Checkout Mandate(保护「买什么」)和 Payment Mandate(保护「怎么付」),都用 SD-JWT 承载(
specification.md:106-107)。 - 旧版 Pydantic 模型 还在用三类:
IntentMandate(意图)、CartMandate(购物车,商家签 JWT)、PaymentMandate(支付)(code/sdk/python/ap2/models/mandate.py:32,107,167)。
这两套并存于同一仓库。为什么会这样、哪套用在哪个样例,见 04-two-model-generations.md。这里你只要记住一个映射直觉:
旧三类 v0.2 两类(SD-JWT-VC)
IntentMandate (用户意图) ─┐
CartMandate (商家签购物车) ─┴──▶ Checkout Mandate(开/闭)
PaymentMandate(支付授权) ───▶ Payment Mandate (开/闭)
旧 CartMandate:一个值得看的真实片段
旧模型里 CartMandate 把「商家对购物车的担保」做成一个嵌套的商家签名 JWT(code/sdk/python/ap2/models/mandate.py:107-136):
# code/sdk/python/ap2/models/mandate.py:113-135(节选)
class CartMandate(BaseModel):
contents: CartContents # 购物车真实内容
merchant_authorization: str | None # 商家签名的 JWT
那个 merchant_authorization JWT 的 payload 里藏着一个关键字段 cart_hash——对 CartContents 的规范 JSON 算的哈希,外加短时 exp(5-15 分钟)和防重放的 jti。这就是「商家担保此购物车在限定时间内价格/库存不变」的密码学落点。PaymentMandate.user_authorization 同理,是一个对 cart+payment 双哈希签名的可验证表述(mandate.py:184-203)。
注意这套旧模型的绑定是字段里塞 JWT/哈希,而新 SD-JWT 运行时是整条链用
sd_hash串起来——这是两代设计最本质的差异。
5. 边界与诚实
- AP2 不是电商协议本身。 目录 API、checkout 更新、角色间具体 API 都明确在范围外(
specification.md:20-24)。它只管「在已有 commerce 协议上加一层支付安全」,并声明与 UCP(Universal Commerce Protocol)兼容。 - mandate 选择机制在范围外。 agent 怎么从一堆开 mandate 里挑出适用的那张,规范明说是实现细节(
flows.md:140-141、agent_authorization.md:489-491)。 - Agent 间委托(一个 SA 把 mandate 转授给另一个 SA)规范承认「概念上可行」但当前版本不覆盖(
specification.md:256-260)。
下一章进入实现:这些「开/闭、委托、绑定」到底用 SD-JWT 怎么拼出来。→ 02-sdjwt-delegation-chain.md
6. 代码地图
| 主题 | 文件 | 符号名 |
|---|---|---|
| 角色与模式定义 | docs/ap2/specification.md | (Roles / Modes / Agentic vs Non-Agentic) |
| 开/闭两态、委托框架 | docs/ap2/agent_authorization.md | (Mandate Structure) |
| 旧版意图 mandate | code/sdk/python/ap2/models/mandate.py | IntentMandate |
| 旧版购物车 mandate(商家签) | code/sdk/python/ap2/models/mandate.py | CartMandate CartContents |
| 旧版支付 mandate | code/sdk/python/ap2/models/mandate.py | PaymentMandate PaymentMandateContents |