Solana SquadX off-chain 登录 /「类签名」

Solana SquadX off-chain 登录 /「类签名」

本文档说明:在 SquadX(Squads 多签)场景下,官方能力不足以覆盖「标准链下 signMessage」时 ,我们如何用 链上 Memo + 随机数 randomR + 交易签名绑定 完成可验证的「授权证明」,以及后续 服务端校验 的思路。


1. 背景:为什么不用「标准 off-chain signMessage」?

1.1 常见钱包(如 Phantom)的做法

许多 Solana 钱包提供 signMessage(或 Wallet Standard 的 signMessage),用户签的是 链下字节,服务端用公钥验 Ed25519 即可。这是 Web3 里最典型的「证明你控制该地址」的方式。

1.2 SquadX / 多签场景的限制

SquadX 面向 Squads 多签 工作流,与「单地址直接签一条链下消息」的模型不一致:

  • 官方文档与产品能力主要围绕 交易提案(proposal)→ 投票 → 执行(execute) 等链上流程。
  • 实际集成中我们发现:不能依赖 与单签钱包相同的「一键 off-chain signMessage」路径来完成业务所需的「用户同意条款 / 授权声明」证明(或该路径不可用、不稳定、与多签模型不兼容,官方原链接)。

因此需要 替代方案 :用 链上可验证数据 代替链下签名。


2. 总体思路:用 Memo 交易代替 signMessage,并用 randomR 绑定「语义」

我们采用 「Memo Program 上链 + 密码学承诺」

  1. 用户仍然「像签名一样」在钱包里 确认一笔交易 (对多签而言,往往是 创建提案 的那笔交易的签名)。
  2. Memo 里放的不是明文长句,而是 commitment :由 随机数 randomR业务消息摘要 共同决定,保证 不可被第三方预先伪造、且可与后续校验对齐。
  3. 交易上链后,我们得到 Solana 交易签名 txHash (Base58 字符串,对应 64 字节 的签名)。
  4. 前端把 randomR + txHash(64 字节) 组合成 signatureHex,交给后端或业务系统,作为「用户完成授权动作」的凭证。
  5. 服务端通过 RPC getTransaction 拉取链上交易,解析 Memo、校验 commitment、校验账户关系(见第 6 节)。

核心点randomR 是我们自己引入的 128-bit/256-bit 级别随机数 (实现为 32 字节),用来把「这次登录尝试」变成 唯一承诺,避免只用固定句子时可能遇到的重放 / 拼接问题,并与链上 Memo 内容一一对应。


3. 密码学步骤(与实现一致)

以下与 app/components/squadx-connect-panel.tsxsignMessageWithSquadX 的逻辑一致(使用 viemkeccak256concattoHex 等)。

3.1 业务消息 msg

例如由 getSignMessage(address) 生成,包含地址与条款说明(UTF-8 字符串)。

3.2 生成 randomR

  • 使用 crypto.getRandomValues 生成 32 字节 随机数 randomR
  • randomRHex = toHex(randomR)(带 0x 前缀的 hex)。
  • 可选:按地址写入 localStorage(如 squadx_R:${address}),便于调试或客户端恢复(注意:敏感业务应以服务端会话为准)。

3.3 消息摘要 messageHash

text 复制代码
messageHex = toHex(UTF8_BYTES(msg))
messageHash = keccak256(messageHex)

即:对 消息的 UTF-8 字节 做 hex,再 Keccak-256。

3.4 承诺 commitment(写入 Memo 的 payload)

text 复制代码
commitment = keccak256(concat([randomRHex, messageHash]))
  • concat字节 拼接 randomRmessageHash 的 hex 解码结果(与代码一致)。
  • 链上 Memo 的内容 = 该 commitment(通常为 32 字节的哈希,以 hex 或钱包展示为准)。

这样,任何人若没有正确的 randomR,无法针对同一 messageHash 构造出相同 commitment;后端可根据用户提交的 randomR 复算并比对链上 Memo。

3.5 发送 Memo 交易

  • 使用 Solana Memo ProgramMemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr)创建一条 Memo 指令 ,data 为上述 commitment(实现里与 memoPayload 一致)。
  • Fee payer 必须为连接的钱包地址(多签场景下按你们产品要求设置)。
  • 使用较新的 blockhash ,并对 Blockhash not found 做重试(用户在钱包里停留过久会导致 blockhash 过期)。

成功后得到 txHash (Base58,64 字节)。

3.6 组装对外暴露的 signatureHex

定义固定 4 字节前缀(与链上协议无关,仅用于识别我们的格式):

text 复制代码
PREFIX = 0xFF 0xFF 0xFF 0xFE   // 即 SIGNATURE_PREFIX_BYTES

Solana 交易签名 转为 64 字节(Base58 decode):

text 复制代码
txHashBytes64 = base58Decode(txHash)  // 长度 64

最终 100 字节 二进制结构:

偏移 长度 含义
0 4 PREFIX
4 32 randomR
36 64 txHashBytes64(整条链上交易的签名字节)
text 复制代码
signatureBytes = PREFIX || randomR || txHashBytes64   // 4 + 32 + 64 = 100 字节
signatureHex = toHex(signatureBytes)                  // 0x 开头,200 hex 字符 + 0x

解析函数 decryptSignatureHex(同名在组件内)即按上表拆回 prefixrandomR(hex)、txHash(Base58 字符串)。


4. 多签(SquadX)特别注意:提案签名 vs 执行签名

Squads 多签中,用户在前端点「确认」后,第一笔返回的 txHash 往往是「创建提案」的交易签名 ,而不是最终 execute 那笔。

  • 若业务只关心「用户发起了带某 Memo 的提案」 :使用提案 txHash 绑定即可(需后端校验逻辑与之一致)。
  • 若业务要求「多签已执行」才认登录成功 :必须在提案通过后,再取 execute 交易的 signature ,并用它替换上面结构里的 txHashBytes64(或单独字段存储执行哈希)。

项目里已提供辅助能力:

  • app/lib/sqds.ts 中的 waitForMultisigExecution :根据 提案交易签名 轮询链上 Proposal 状态,直到 Executed,再解析出 执行交易 signature
  • app/lib/transaction.ts 中的 withWalletSupport :在钱包名为 SquadsX 时,可在发送交易后 自动等待 执行完成并返回执行签名(按产品需要选用)。

与前端同事对齐时务必约定signatureHex 里的 txHash 到底是 提案 还是 执行,避免后端验错交易。


5. 前端交付给后端的最小字段建议

推荐在登录请求里携带(或等价信息):

字段 说明
address 用户公钥(Base58)
message 用户看到的完整 UTF-8 文案(或与标准文案的 hash 方案一致)
randomR 0x + 64 hex
messageHash keccak256(toHex(utf8(message)))
commitment keccak256(concat(randomRHex, messageHash))
memoTxHash / txSignature 用于 getTransaction 的签名(约定是提案或执行)
signatureHex 100 字节结构的 hex,或后端只收 randomR + txSignature 自行拼

后端 必须 用 RPC 拉取 txSignature 对应交易,验 Memo、账户、Squads 程序参与关系(见下节)。


6. 服务端校验(概念)

app/lib/verifier.ts 中的 verifySquadsLogin 展示了链上侧验证思路(需与你们 RPC、jsonParsed 能力对齐):

  1. getTransaction(txSignature, { maxSupportedTransactionVersion: 0, commitment: 'confirmed' })
  2. 检查交易成功(meta.err 为空)。
  3. 账户列表 中验证:用户地址、Memo Program、Squads Program 等是否出现。
  4. Squads 指令 中验证用户地址参与方式是否符合预期。
  5. inner instructions 中找到 Memo,解析出 memo 内容,与 commitment 或解析后的明文策略一致。
  6. 校验 Memo 账户与 Squads 指令账户的 包含关系,防止「把 Memo 挂到无关指令上」的拼接攻击(具体逻辑以代码为准)。

安全提醒 :仅客户端自证不够;以服务端链上校验为准 。客户端传来的 randomRtxHash 若被篡改,应在服务端复算 commitment 并与链上 Memo 比对失败。


7. 安全与产品注意点(摘要)

  1. 重放commitment单次使用 ;后端应对 txSignaturecommitment幂等 / 已使用标记
  2. 时效 :可结合区块时间或业务 nonce
  3. 多签流程 :明确使用 提案 还是 执行 签名。
  4. RPC :生产环境使用可靠节点;getTransaction 对历史交易可能需要 searchTransactionHistory 等(视节点而定)。
  5. 文案一致性message 与后端计算的 messageHash 必须同源,避免用户看到的是 A、哈希按 B 计算。

8. 代码索引(便于跳转)

内容 位置
随机数、commitment、Memo 发送、signatureHex 组装 app/components/squadx-connect-panel.tsxsignMessageWithSquadXbuildSignaturetxHashToBytes64
signatureHex 解析 同文件 decryptSignatureHex
多签执行轮询 app/lib/sqds.tswaitForMultisigExecution
发送后钱包特殊处理 app/lib/transaction.tswithWalletSupport
登录链上验证示例 app/lib/verifier.tsverifySquadsLogin

9. 小结

  • 原因 :SquadX 多签场景下,不能依赖 与单签相同的 标准 off-chain signMessage 完成我们的业务证明。
  • 做法 :用 Memo 交易 承载 commitment = hash(randomR, messageHash) ,再用 PREFIX || randomR || txSignatureBytes(64) 形成可传输的 signatureHex
  • 多签 :区分 提案 txexecute tx ;需要执行完成时,用 waitForMultisigExecution 或产品层按钮异步获取 执行签名 再绑定。
  • 验证 :服务端 getTransaction + Memo / 账户 / Squads 关系校验,并 复算 commitment

相关推荐
暴躁小师兄数据学院1 天前
【WEB3.0零基础转换笔记】Rust编程篇-第4讲:控制流
开发语言·笔记·rust·web3·区块链·智能合约
暴躁小师兄数据学院3 天前
【WEB3.0零基础转行笔记】Solidity编程篇-《Foundry基础》:第1讲:Foundry简易存储
web3·区块链·智能合约
EutoCool3 天前
Web3:在你的项目中如何使用Hardhat框架
web3
技术路线图3 天前
告别存量厮杀!深度解构 LV纪元:Web3 的终极价值引擎
web3
暴躁小师兄数据学院5 天前
【WEB3.0零基础转行笔记】go编程篇-第12讲:go-zero入门实战
开发语言·笔记·golang·web3·区块链
Joy T6 天前
【Web3】智能合约多环境部署架构:Mock机制与依赖注入实战
开发语言·架构·web3·区块链·php·智能合约·mock合约
Blockchina6 天前
一个Web3项目从0到上线全流程(含技术架构全拆解)
架构·web3·区块链·永续去中心化交易所
Joy T7 天前
【Web3】告别Remix:基于Hardhat的智能合约工程化构建与实战
git·web3·node·智能合约·hardhat
Joy T7 天前
【Web3】跨越纪元:Hardhat 2.x 与 3.x 深度对比
web3·回滚·hardhat 3.x·底层架构·跨链测试·网络并发