news 2026/9/17 20:53:25

x402 sign-in-with-x 扩展:基于 CAIP-122 的钱包认证规范与 SDK 实现详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
x402 sign-in-with-x 扩展:基于 CAIP-122 的钱包认证规范与 SDK 实现详解

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-x(SIWX)扩展的报文结构、字段语义、验证逻辑与安全模型,并结合 x402 TypeScript SDK 的扩展包源码 说明 nonce 生成、消息校验、签名验证和钩子(Hooks)的真实实现方式。读完本文,你可以独立实现一个支持"已支付用户免重复付款"和"纯认证路由"的 x402 服务端,并理解 402 响应到SIGN-IN-WITH-X请求头的完整链路。

1. SIWX 定位:Server ↔ Client 的钱包认证扩展

sign-in-with-x扩展实现了 CAIP-122 规范的钱包认证能力:客户端通过签名一个由服务端下发的挑战消息(challenge message),证明其控制某个钱包地址。它的核心价值有两个:

  1. 已购内容重复访问免支付:客户端证明"这个钱包曾经为该资源付过款",服务端核验后放行,不再要求重复付款;
  2. 纯认证路由(auth-only):路由只要求钱包签名、不要求任何支付。

需要特别强调的是,规范明确指出:这是一个 Server ↔ Client 扩展,Facilitator 不参与认证流程。也就是说,认证链路上的挑战下发、签名验证全部发生在服务端与客户端之间,支付结算方(Facilitator)只负责此前的付款结算环节。

整体交互流程为四步:

  1. 客户端访问受保护资源,服务端返回402 Payment Required,其中extensions对象携带sign-in-with-x挑战参数;
  2. 客户端用自己的钱包对 CAIP-122 消息签名;
  3. 客户端把签名证明以SIGN-IN-WITH-XHTTP 请求头(base64 编码的 JSON)重新发送;
  4. 服务端验证签名后,根据"路由是纯认证型"或"该钱包已为此资源付过款"两种条件之一授予访问权。

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必填正在访问的完整资源 URI
versionstring必填CAIP-122 版本号,恒为"1"
noncestring必填密码学随机数(32 位十六进制字符),服务端必须生成
issuedAtstring必填挑战创建的 ISO 8601 时间戳
statementstring可选人类可读的签名用途说明
expirationTimestring可选挑战过期的 ISO 8601 时间戳,默认从issuedAt起 5 分钟
notBeforestring可选签名生效之前的 ISO 8601 时间戳
requestIdstring可选请求关联 ID
resourcesstring[]可选与请求关联的 URI 列表

2.2 认证方法supportedChains[]字段

字段类型必填说明
chainIdstring必填CAIP-2 链标识,如"eip155:8453""solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
typestring必填签名算法:EVM 为"eip191",Solana 为"ed25519"
signatureSchemestring可选客户端签名 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字段,并追加addresssignature等字段):

{ "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 使用 Base58
signaturestring必填密码学签名,EVM 为0x...十六进制,Solana 为 Base58

SDK 侧用 Zod 对该载荷做了机器可校验的约束(见 SIWxPayloadSchema):type只允许eip191/ed25519,可选的signatureScheme只允许eip191/eip1271/eip6492/siws,这解释了示例载荷中signatureScheme字段的合法取值来源。

4. 支持的链与消息格式

4.1 EVM(eip155:*

  • Typeeip191
  • 签名方案eip191(EOA)、eip1271(智能合约钱包)、eip6492(无预部署/反事实钱包)
  • 消息格式:EIP-4361(SIWE,Sign-In With Ethereum)
  • 链 ID 示例eip155:1(Ethereum)、eip155:8453(Base)、eip155:137(Polygon)

对应消息文本为:

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-data

4.2 Solana(solana:*

  • Typeed25519
  • 签名方案siws
  • 消息格式:Sign-In With Solana(SIWS)
  • 链 ID 示例solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp(mainnet)、solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1(devnet)
api.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-data

SDK 中两种消息格式由 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.ts:DEFAULT_MAX_AGE_MS常量固定为 5 分钟(第 11 行);domain 比对使用资源 URI 的hostname(遵循 EIP-4361 约定,不含端口,第 52-59 行);URI 校验允许消息中的uri是资源 origin 或完整 URL(第 61-68 行);nonce 唯一性通过可选的checkNonce回调交给应用层实现。
  • verify.ts:verifySIWxSignaturechainId前缀路由到 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 Binding)domain字段防止签名在不同服务之间被复用。SDK 中validateSIWxMessage对不匹配直接返回valid: false
  • Nonce 唯一性:每个挑战必须有唯一 nonce 防重放。SDK 服务端扩展用crypto.getRandomValues生成 16 字节随机数并格式化为 32 位十六进制(server.ts 第 64-66 行),恰好满足规范"32 hex characters"的要求;
  • 时间边界(Temporal Bounds)issuedAt/expirationTime/notBefore三个字段共同约束签名的有效窗口;
  • 链特异性验证:签名按链适用算法验证,防止跨链签名复用;
  • 智能钱包支持:EIP-1271 / EIP-6492 验证需要对钱包合约发起 RPC 调用,EOA 验证则完全本地完成。

7. SDK 实现深度解析:从声明到放行的完整链路

7.1 服务端声明:siwxResourceServerExtension的自动派生

规范中的info字段大多可以从请求上下文自动推导。SDK 的 siwxResourceServerExtension 实现了enrichPaymentRequiredResponse钩子,每次构造 402 响应时执行:

  • resourceUri缺省时取自请求 URL;domain缺省时从resourceUriURL.hostname解析;
  • networks缺省时从accepts[](即context.requirements)中提取去重后的network列表(第 55-61 行),并据此生成supportedChains
  • nonce/issuedAt每请求刷新;expirationTime仅在配置了expirationSeconds时才写入(不配置则载荷中无该字段,第 69-74 行);
  • schemabuildSIWxSchema()生成,即规范示例中的 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()上,处理顺序为:

  1. 从适配器读取SIGN-IN-WITH-X请求头(大小写不敏感,两种形式都尝试),没有则直接放行给后续支付流程;
  2. parseSIWxHeader解析 →validateSIWxMessage校验字段;
  3. verifySIWxSignature验证签名并得到地址;
  4. 若存储实现了 nonce 追踪,先检查hasUsedNonce,命中则记录nonce_reused事件并拒绝(重放防护);
  5. 判定放行条件:routeConfig.accepts为空数组即视为纯认证路由(仅签名有效即放行);否则要求storage.hasPaid(path, address)为真(第 144-160 行);
  6. 放行前把本次 nonce 记录为已用。

源码中还有一个健壮性细节:钩子创建时校验 nonce 追踪接口的完整性——hasUsedNoncerecordNonce必须同时实现或同时不实现,否则直接抛错(第 97-104 行),避免"检查了却记录不了"的半吊子状态。

7.4 支付历史存储:SIWxStorage接口

storage.ts 定义了最小接口与两个可选的 nonce 方法:

interface SIWxStorage { hasPaid(resource: string, address: string): boolean | Promise<boolean>; recordPayment(resource: string, address: string): void | Promise<void>; hasUsedNonce?(nonce: string): boolean | Promise<boolean>; // 可选:防重放 recordNonce?(nonce: string): void | Promise<void>; // 可选:防重放 }

包内附带 InMemorySIWxStorage 供开发使用(地址统一转小写存储)。注释明确提示:生产多实例部署应自行实现持久化存储(数据库、Redis 等),且 nonce 记录应考虑过期清理以避免无限增长。

7.5 客户端侧:钩子与 fetch 包装

SDK 提供两种客户端接入方式:

方式一:createSIWxClientHook。挂在x402HTTPClient.onPaymentRequired()上,收到 402 后自动检查extensions["sign-in-with-x"],按签名者类型匹配supportedChains(Solana 签名者匹配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(构造声明)→parseSIWxHeadervalidateSIWxMessage(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.mdx;
  • Go SDK 的扩展文档也涉及该扩展:go/extensions/README.md。

9. 小结

sign-in-with-x是 x402 v2 中一个职责单一的 Server ↔ Client 扩展:服务端在 402 响应中用info+supportedChains下发 CAIP-122 挑战,客户端在SIGN-IN-WITH-X头中回传 base64 编码的签名证明,服务端按domain 绑定 → 时间窗口 → nonce 唯一性 → 链特异性验签 → 支付历史检查的顺序放行。规范定义了报文契约与安全边界,SDK(@x402/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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 20:53:07

非正式市场价格信号扭曲的根源与矫正方法

1. 市场信号扭曲的根源与矫正逻辑在当代社会经济运行中&#xff0c;存在着大量被主流经济学忽视的"非正式市场"——那些不受正式制度保护却真实影响人们生活的交易场域。这些市场的价格信号往往严重偏离其本质功能&#xff0c;形成了独特的价值扭曲现象。作为一名长期…

作者头像 李华
网站建设 2026/9/17 20:51:23

GJB 3206A技术状态管理:标识、基线、更改与审核落地

简介&#xff1a;配置管理与产品数据管理的核心&#xff0c;是让设计、制造、交付各环节对“当前有效版本”有唯一、可追溯的定义。其原理是通过标识、基线、更改控制和记实审核&#xff0c;把产品功能与物理特性固化到受控文件中&#xff0c;并在变更时维持文实一致。技术价值…

作者头像 李华
网站建设 2026/9/17 20:50:49

KernelSU 跑 LSPosed 完整教程:借助 ZygiskNext 快速加载 Xposed 模块

KernelSU 跑 LSPosed 完整教程&#xff1a;借助 ZygiskNext 快速加载 Xposed 模块 【免费下载链接】KernelSU A Kernel based root solution for Android 项目地址: https://gitcode.com/GitHub_Trending/ke/KernelSU 本文解决一个很具体的问题&#xff1a;你的设备已经…

作者头像 李华
网站建设 2026/9/17 20:45:56

西门子家电小程序查找官方客服电话操作流程

步骤 1&#xff1a;搜索进入【西门子家电】小程序打开微信顶部搜索框&#xff0c;输入西门子家电&#xff0c;在使用过的小程序栏目&#xff0c;点击带西门子 SIEMENS 标识、标注【交易保障】的西门子家电官方小程序&#xff0c;进入首页。步骤 2&#xff1a;进入【服务】板块小…

作者头像 李华