
x402 sign-in-with-x 扩展基于 CAIP-122 的钱包认证规范与 SDK 实现详解【免费下载链接】x402A payments protocol for the internet. Built on HTTP.项目地址: https://gitcode.com/GitHub_Trending/x4/x402本文以 sign-in-with-x 扩展规范 为主体完整讲解 x402 协议中sign-in-with-xSIWX扩展的报文结构、字段语义、验证逻辑与安全模型并结合 x402 TypeScript SDK 的扩展包源码 说明 nonce 生成、消息校验、签名验证和钩子Hooks的真实实现方式。读完本文你可以独立实现一个支持已支付用户免重复付款和纯认证路由的 x402 服务端并理解 402 响应到SIGN-IN-WITH-X请求头的完整链路。1. SIWX 定位Server ↔ Client 的钱包认证扩展sign-in-with-x扩展实现了 CAIP-122 规范的钱包认证能力客户端通过签名一个由服务端下发的挑战消息challenge message证明其控制某个钱包地址。它的核心价值有两个已购内容重复访问免支付客户端证明这个钱包曾经为该资源付过款服务端核验后放行不再要求重复付款纯认证路由auth-only路由只要求钱包签名、不要求任何支付。需要特别强调的是规范明确指出这是一个 Server ↔ Client 扩展Facilitator 不参与认证流程。也就是说认证链路上的挑战下发、签名验证全部发生在服务端与客户端之间支付结算方Facilitator只负责此前的付款结算环节。整体交互流程为四步客户端访问受保护资源服务端返回402 Payment Required其中extensions对象携带sign-in-with-x挑战参数客户端用自己的钱包对 CAIP-122 消息签名客户端把签名证明以SIGN-IN-WITH-XHTTP 请求头base64 编码的 JSON重新发送服务端验证签名后根据路由是纯认证型或该钱包已为此资源付过款两种条件之一授予访问权。2. 服务端声明402 Payment Required 中的扩展结构服务端通过在402 Payment Required响应的extensions对象中包含sign-in-with-x键来宣告 SIWX 支持。规范给出的完整报文示例如下EVM 单链场景Base 测试网 USDC{ x402Version: 2, accepts: [ { scheme: exact, network: eip155:8453, amount: 10000, asset: 0x036CbD53842c5426634e7929541eC2318f3dCF7e, payTo: 0x209693Bc6afc0C5328bA36FaF03C514EF312287C, maxTimeoutSeconds: 60, extra: { name: USDC, version: 2 } } ], extensions: { sign-in-with-x: { info: { domain: api.example.com, uri: https://api.example.com/premium-data, version: 1, nonce: a1b2c3d4e5f67890a1b2c3d4e5f67890, issuedAt: 2024-01-15T10:30:00.000Z, expirationTime: 2024-01-15T10:35:00.000Z, statement: Sign in to access premium data, resources: [https://api.example.com/premium-data] }, supportedChains: [ { chainId: eip155:8453, type: eip191 } ], schema: { $schema: https://json-schema.org/draft/2020-12/schema, type: object, properties: { domain: { type: string }, address: { type: string }, statement: { type: string }, uri: { type: string, format: uri }, version: { type: string }, chainId: { type: string }, type: { type: string }, nonce: { type: string }, issuedAt: { type: string, format: date-time }, expirationTime: { type: string, format: date-time }, notBefore: { type: string, format: date-time }, requestId: { type: string }, resources: { type: array, items: { type: string, format: uri } }, signature: { type: string } }, required: [ domain, address, uri, version, chainId, type, nonce, issuedAt, signature ] } } } }扩展对象由三部分组成info消息元数据、supportedChains认证方法声明、schema客户端证明的 JSON Schema。2.1 消息元数据info字段字段类型必填说明domainstring必填服务端域名如api.example.com必须与请求 host 匹配uristring必填正在访问的完整资源 URIversionstring必填CAIP-122 版本号恒为1noncestring必填密码学随机数32 位十六进制字符服务端必须生成issuedAtstring必填挑战创建的 ISO 8601 时间戳statementstring可选人类可读的签名用途说明expirationTimestring可选挑战过期的 ISO 8601 时间戳默认从issuedAt起 5 分钟notBeforestring可选签名生效之前的 ISO 8601 时间戳requestIdstring可选请求关联 IDresourcesstring[]可选与请求关联的 URI 列表2.2 认证方法supportedChains[]字段字段类型必填说明chainIdstring必填CAIP-2 链标识如eip155:8453、solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdptypestring必填签名算法EVM 为eip191Solana 为ed25519signatureSchemestring可选客户端签名 UX 提示eip191、eip1271、eip6492或siws客户端选择supportedChains中与自身钱包匹配的第一个条目。2.3 多链支持同时支持 EVM 与 Solana 的服务端可以在supportedChains中放入多条记录{ x402Version: 2, accepts: [...], extensions: { sign-in-with-x: { info: { domain: api.example.com, uri: https://api.example.com/premium-data, version: 1, nonce: a1b2c3d4e5f67890a1b2c3d4e5f67890, issuedAt: 2024-01-15T10:30:00.000Z, expirationTime: 2024-01-15T10:35:00.000Z, statement: Sign in to access premium data, resources: [https://api.example.com/premium-data] }, supportedChains: [ { chainId: eip155:8453, type: eip191 }, { chainId: solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp, type: ed25519 } ], schema: {...} } } }关键点在于同一条nonce被所有链共享。规范明确这样设计是为了防止用不同钱包认证时发生重放攻击——一次挑战只能兑换一次签名证明无论客户端最终用哪条链的钱包去签。3. 客户端证明SIGN-IN-WITH-X请求头客户端对挑战消息签名后将证明以 base64 编码的 JSON 放入SIGN-IN-WITH-XHTTP 请求头GET /premium-data HTTP/1.1 Host: api.example.com SIGN-IN-WITH-X: eyJkb21haW4iOiJhcGkuZXhhbXBsZS5jb20iLCJhZGRyZXNzIjoiMHg4NTdiMDY1MTlFOTFlM0E1NDUzODc5MWJEYmIwRTIyMzczZTM2YjY2IiwidXJpIjoiaHR0cHM6Ly9hcGkuZXhhbXBsZS5jb20vcHJlbWl1bS1kYXRhIiwidmVyc2lvbiI6IjEiLCJjaGFpbklkIjoiZWlwMTU1Ojg0NTMiLCJ0eXBlIjoiZWlwMTkxIiwibm9uY2UiOiJhMWIyYzNkNGU1ZjY3ODkwYTFiMmMzZDRlNWY2Nzg5MCIsImlzc3VlZEF0IjoiMjAyNC0wMS0xNVQxMDozMDowMC4wMDBaIiwiZXhwaXJhdGlvblRpbWUiOiIyMDI0LTAxLTE1VDEwOjM1OjAwLjAwMFoiLCJzdGF0ZW1lbnQiOiJTaWduIGluIHRvIGFjY2VzcyBwcmVtaXVtIGRhdGEiLCJyZXNvdXJjZXMiOlsiaHR0cHM6Ly9hcGkuZXhhbXBsZS5jb20vcHJlbWl1bS1kYXRhIl0sInNpZ25hdHVyZVNjaGVtZSI6ImVpcDE5MSIsInNpZ25hdHVyZSI6IjB4MmQ2YTc1ODhkNmFjY2E1MDVjYmYwZDlhNGEyMjdlMGM1MmM2YzM0MDA4YzhlODk4NmExMjgzMjU5NzY0MTczNjA4YTJjZTY0OTY2NDJlMzc3ZDZkYThkYmJmNTgzNmU5YmQxNTA5MmY5ZWNhYjA1ZGVkM2Q2MjkzYWYxNDhiNTcxYyJ9该请求头 base64 解码后得到如下证明载荷客户端回显了服务端的所有info字段并追加address、signature等字段{ domain: api.example.com, address: 0x857b06519E91e3A54538791bDbb0E22373e36b66, uri: https://api.example.com/premium-data, version: 1, chainId: eip155:8453, type: eip191, nonce: a1b2c3d4e5f67890a1b2c3d4e5f67890, issuedAt: 2024-01-15T10:30:00.000Z, expirationTime: 2024-01-15T10:35:00.000Z, statement: Sign in to access premium data, resources: [https://api.example.com/premium-data], signatureScheme: eip191, signature: 0x2d6a7588d6acca505cbf0d9a4a227e0c52c6c34008c8e8986a1283259764173608a2ce6496642e377d6da8dbbf5836e9bd15092f9ecab05ded3d6293af148b571c }客户端在回显服务端字段之外需要新增两个必填字段字段类型必填说明addressstring必填签名钱包地址EVM 使用 Checksum 格式Solana 使用 Base58signaturestring必填密码学签名EVM 为0x...十六进制Solana 为 Base58SDK 侧用 Zod 对该载荷做了机器可校验的约束见 SIWxPayloadSchematype只允许eip191/ed25519可选的signatureScheme只允许eip191/eip1271/eip6492/siws这解释了示例载荷中signatureScheme字段的合法取值来源。4. 支持的链与消息格式4.1 EVMeip155:*Typeeip191签名方案eip191EOA、eip1271智能合约钱包、eip6492无预部署/反事实钱包消息格式EIP-4361SIWESign-In With Ethereum链 ID 示例eip155:1Ethereum、eip155:8453Base、eip155:137Polygon对应消息文本为api.example.com wants you to sign in with your Ethereum account: 0x857b06519E91e3A54538791bDbb0E22373e36b66 Sign in to access premium data URI: https://api.example.com/premium-data Version: 1 Chain ID: 8453 Nonce: a1b2c3d4e5f67890a1b2c3d4e5f67890 Issued At: 2024-01-15T10:30:00.000Z Expiration Time: 2024-01-15T10:35:00.000Z Resources: - https://api.example.com/premium-data4.2 Solanasolana:*Typeed25519签名方案siws消息格式Sign-In With SolanaSIWS链 ID 示例solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpmainnet、solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1devnetapi.example.com wants you to sign in with your Solana account: BSmWDgE9ex6dZYbiTsJGcwMEgFp8q4aWh92hdErQPeVW Sign in to access premium data URI: https://api.example.com/premium-data Version: 1 Chain ID: 5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp Nonce: a1b2c3d4e5f67890a1b2c3d4e5f67890 Issued At: 2024-01-15T10:30:00.000Z Expiration Time: 2024-01-15T10:35:00.000Z Resources: - https://api.example.com/premium-dataSDK 中两种消息格式由 createSIWxMessage 按chainId前缀eip155:/solana:统一路由遇到未知命名空间会直接抛出Unsupported chain namespace错误这与服务端验证侧的路由策略保持对称。5. 服务端验证逻辑按规范当服务端收到携带SIGN-IN-WITH-X请求头的请求时应执行四步验证第 1 步解析请求头。Base64 解码头值再 JSON 解析得到证明载荷。第 2 步校验消息字段。Domaindomain必须与请求 host 精确匹配URIuri必须以预期资源来源origin开头Issued At必须足够新鲜默认 5 分钟且不得晚于当前时间不得位于未来Expiration若存在expirationTime必须位于未来Not Before若存在notBefore必须位于过去Nonce必须唯一。服务端应当跟踪已用 nonce 以防重放攻击。第 3 步验证签名。按chainId前缀路由eip155:*重建 SIWE 消息用 ECDSA 恢复EOA或链上验证EIP-1271 / EIP-6492 智能钱包solana:*重建 SIWS 消息验证 Ed25519 签名。第 4 步检查支付历史。若签名有效服务端检查恢复出的address是否曾为该资源付过款。规范明确这是应用层自定义逻辑不属于协议本身。SDK 对第 2、3 步的实现在以下文件中可以逐条对照规范核对validate.tsDEFAULT_MAX_AGE_MS常量固定为 5 分钟第 11 行domain 比对使用资源 URI 的hostname遵循 EIP-4361 约定不含端口第 52-59 行URI 校验允许消息中的uri是资源 origin 或完整 URL第 61-68 行nonce 唯一性通过可选的checkNonce回调交给应用层实现。verify.tsverifySIWxSignature按chainId前缀路由到 EVM 或 Solana 验证器。Solana 侧在验证前做了两道长度检查——Ed25519 签名必须为 64 字节、公钥必须为 32 字节第 166-180 行签名与地址均从 Base58 解码。EVM 侧默认只做本地 ECDSA 恢复传入evmVerifier兼容 viem 的publicClient.verifyMessage后才启用 EIP-1271 / EIP-6492 智能钱包验证这与规范智能钱包验证需要 RPC 调用的描述一致。一个容易忽略的细节从 types.ts 的注释 和 verify.ts 的路由实现 可以确认决定验证算法的是chainId前缀而非type/signatureScheme字段——后两者对客户端只是选择签名 UX 的提示hint。这意味着服务端不能仅凭声明字段就信任签名的算法类型必须按链命名空间实际路由SDK 正是这样实现的。6. 安全设计要点规范列出了五点安全考量均已在 SDK 中落地域名绑定Domain Bindingdomain字段防止签名在不同服务之间被复用。SDK 中validateSIWxMessage对不匹配直接返回valid: falseNonce 唯一性每个挑战必须有唯一 nonce 防重放。SDK 服务端扩展用crypto.getRandomValues生成 16 字节随机数并格式化为 32 位十六进制server.ts 第 64-66 行恰好满足规范32 hex characters的要求时间边界Temporal BoundsissuedAt/expirationTime/notBefore三个字段共同约束签名的有效窗口链特异性验证签名按链适用算法验证防止跨链签名复用智能钱包支持EIP-1271 / EIP-6492 验证需要对钱包合约发起 RPC 调用EOA 验证则完全本地完成。7. SDK 实现深度解析从声明到放行的完整链路7.1 服务端声明siwxResourceServerExtension的自动派生规范中的info字段大多可以从请求上下文自动推导。SDK 的 siwxResourceServerExtension 实现了enrichPaymentRequiredResponse钩子每次构造 402 响应时执行resourceUri缺省时取自请求 URLdomain缺省时从resourceUri的URL.hostname解析networks缺省时从accepts[]即context.requirements中提取去重后的network列表第 55-61 行并据此生成supportedChainsnonce/issuedAt每请求刷新expirationTime仅在配置了expirationSeconds时才写入不配置则载荷中无该字段第 69-74 行schema由buildSIWxSchema()生成即规范示例中的 JSON Schema。注意一个约束对于纯认证路由accepts: []网络无法从支付要求推导因此必须显式传入network参数。这一点在 docs/extensions/sign-in-with-x.mdx 的 API 说明中有专门提示也与 DeclareSIWxOptions 类型定义 中network的注释一致。7.2 支付记录createSIWxSettleHook已支付地址的追踪依赖结算钩子。createSIWxSettleHook 挂在x402ResourceServer.onAfterSettle()上只在结算成功ctx.result.success为真时从 Facilitator 结算结果中取出payer地址并把资源 URL 归一化为路径后写入存储const storage new InMemorySIWxStorage(); const resourceServer new x402ResourceServer(facilitatorClient) .register(NETWORK, new ExactEvmScheme()) .registerExtension(siwxResourceServerExtension) // 每请求刷新 nonce/时间字段 .onAfterSettle(createSIWxSettleHook({ storage })); // 记录支付7.3 访问授予createSIWxRequestHook的判定顺序createSIWxRequestHook 挂在x402HTTPResourceServer.onProtectedRequest()上处理顺序为从适配器读取SIGN-IN-WITH-X请求头大小写不敏感两种形式都尝试没有则直接放行给后续支付流程parseSIWxHeader解析 →validateSIWxMessage校验字段verifySIWxSignature验证签名并得到地址若存储实现了 nonce 追踪先检查hasUsedNonce命中则记录nonce_reused事件并拒绝重放防护判定放行条件routeConfig.accepts为空数组即视为纯认证路由仅签名有效即放行否则要求storage.hasPaid(path, address)为真第 144-160 行放行前把本次 nonce 记录为已用。源码中还有一个健壮性细节钩子创建时校验 nonce 追踪接口的完整性——hasUsedNonce和recordNonce必须同时实现或同时不实现否则直接抛错第 97-104 行避免检查了却记录不了的半吊子状态。7.4 支付历史存储SIWxStorage接口storage.ts 定义了最小接口与两个可选的 nonce 方法interface SIWxStorage { hasPaid(resource: string, address: string): boolean | Promiseboolean; recordPayment(resource: string, address: string): void | Promisevoid; hasUsedNonce?(nonce: string): boolean | Promiseboolean; // 可选防重放 recordNonce?(nonce: string): void | Promisevoid; // 可选防重放 }包内附带 InMemorySIWxStorage 供开发使用地址统一转小写存储。注释明确提示生产多实例部署应自行实现持久化存储数据库、Redis 等且 nonce 记录应考虑过期清理以避免无限增长。7.5 客户端侧钩子与 fetch 包装SDK 提供两种客户端接入方式方式一createSIWxClientHook。挂在x402HTTPClient.onPaymentRequired()上收到 402 后自动检查extensions[sign-in-with-x]按签名者类型匹配supportedChainsSolana 签名者匹配ed25519其余匹配eip191见 hooks.ts 第 187-225 行构造载荷、编码请求头并返回附加 header失败时静默落回正常支付流程const httpClient new x402HTTPClient(client) .onPaymentRequired(createSIWxClientHook(signer)); // 若服务端支持 SIWX请求将自动先尝试认证、失败再付款 const response await httpClient.fetch(https://api.example.com/data);方式二wrapFetchWithSIWx。一个轻量 fetch 包装器fetch.ts 第 42-100 行对 402 响应解码PAYMENT-REQUIRED头若含 SIWX 扩展则用accepts[0].network匹配链、签名并重试若请求中已经带过SIGN-IN-WITH-X头则抛出异常防止无限循环。手动实现同样可行低层 API 组合为declareSIWxExtension构造声明→parseSIWxHeader→validateSIWxMessage(payload, resourceUri, { maxAge?, checkNonce? })→verifySIWxSignature(payload, { evmVerifier? })→ 依据verification.address查支付历史放行。完整的分步示例见 docs/extensions/sign-in-with-x.mdx 的 Manual Usage 小节。8. 端到端示例与延伸阅读仓库提供了可直接运行的 TypeScript 端到端示例服务端示例examples/typescript/servers/sign-in-with-x/index.ts附 README客户端示例examples/typescript/clients/sign-in-with-x/index.ts附 README。其他可参考的仓库位置扩展规范原文specs/extensions/sign-in-with-x.md其References指向核心规范 specs/x402-specification-v2.md用户文档与快速上手docs/extensions/sign-in-with-x.mdx含 Smart Wallet 配置、多链路由示例、Troubleshooting 排障清单SDK 扩展包全部 SIWX 源码typescript/packages/extensions/src/sign-in-with-x/测试typescript/packages/extensions/test/sign-in-with-x.test.ts钩子机制的整体说明onAfterSettle/onProtectedRequest/onPaymentRequired等生命周期概念docs/advanced-concepts/lifecycle-hooks.mdxGo SDK 的扩展文档也涉及该扩展go/extensions/README.md。9. 小结sign-in-with-x是 x402 v2 中一个职责单一的 Server ↔ Client 扩展服务端在 402 响应中用infosupportedChains下发 CAIP-122 挑战客户端在SIGN-IN-WITH-X头中回传 base64 编码的签名证明服务端按domain 绑定 → 时间窗口 → nonce 唯一性 → 链特异性验签 → 支付历史检查的顺序放行。规范定义了报文契约与安全边界SDKx402/extensions包则把 nonce 刷新、字段推导、验签路由、支付记录与纯认证路由判定全部封装为钩子与存储接口开发者只需关注两件事实现符合SIWxStorage语义的持久化存储以及按业务需要决定是否启用 EIP-1271/EIP-6492 智能钱包验证。【免费下载链接】x402A payments protocol for the internet. Built on HTTP.项目地址: https://gitcode.com/GitHub_Trending/x4/x402创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考