返回首页
/post-69366e4d2eaa480716a28716

关于钱包加密签名验证

创建于 2025/12/8 06:21:01更新于 2026/3/20 10:05:53

写在前面,其实分析这个业务之前做了大量理论查询和论证,以及参考了大量钱包作为对比,然后算是一篇总结性的理论吧。

除了前端的架构就是浏览器框架插件那块,单拎一个签名验证出来来分析下业务, 其实熟悉了也还行,就是刚开始的时候需要一些理解。

这是我们定好的通讯协议内容 密钥协商 X25519 对称加密 AES-256GCM

很简短对吧,X25519 需要俩端自己独立计算,由插件端计算完成后, 使用 AES-256GCM 协议加密传输到后端,后端相对的拿到加密串进行解密验证

业务完成。

上面是用最简短的人话说了上面的业务,只是业务刨除什么浏览器框架那一套以及前端框架那些,这篇只暂时针对这个业务以及业务完成闭环逻辑。至于 X25519 和 AES-256GCM 这俩协议包要研究那算是另外的课题了。

好,回到整体业务, 前端插件需要实现的协议要求: 密钥协商:X25519(用于建立共享密钥) 对称加密:AES-256-GCM(用于加密实际消息)

翻译成现实应用就是: 用 X25519 生成一个 双方都能推导出的会话密钥(shared key), 再从这个 shared key 派生出一个 256-bit 对称密钥, 用 AES-256-GCM 加密所有通信。

其实细化来说, 当然是想到哪儿说哪儿 x25519 推导会话密钥, AES-256-GCM 进行加密,当然涉及到许多问题, 简单来说,在当前会话的所有消息都会采用上述加解密,当然会有一些性能问题,但是安全需要做这种端对端的加密,当然派生问题也不少其实。

比如会话密钥的使用时机

  1. 保存 AES-GCM session key(只保存在内存)
  2. 所有请求 → 用 AES-GCM 加密
  3. 所有响应 ← 用 AES-GCM 解密 会话流程解决的是: 身份认证 加密通信 数据完整性 中间代理不可信 防止 replay 防止伪造请求 防止内部系统泄露明文 会话建立只是“安全通道建立的第一步”。 建立好之后,你要“使用”这个通道来收发数据。

解决的主要问题还是当前会话session建立之后,我们一直使用这个会话,直到页面刷新或者时间过期当然这个都是业务层面商定的。

其实聊到主流钱包到底需不需要这种端对端通信加密的这个业务, 就举例walletconnect肯定是需要的, 因为它们的通信路径是 跨设备,例如: 网页 ↔ 手机钱包 手机 ↔ 桌面应用 DApp ↔ Relay Server ↔ Wallet 不同设备之间不共享内存 有可能经过第三方中继服务器 网络可被监听 中继服务器不可信

只要过三方中继器,那么就需要加密机制,私钥泄漏这个很严重的,而且也有第三方不可信的这么这个问题。分享的事共享密钥,第三方拿到没有对应的机制也没有办法进行加密解密,这个要配合双方私钥对应使用的。

而 metaMask 不做的理由是, 因为: MetaMask 是本地注入,不经过网络 网页本身在浏览器沙箱内 origin 校验足够做身份认证 没有跨设备通信,也不存在中间人攻击 所有 sensitive 操作会弹 UI(用户确认) 所以 MetaMask 设计上不需要额外的 ECDH 加密层。 其实链上授权本身和这种加密通讯就是俩码事儿,根据业务选择可以做可以不做。

说白还是多签钱包的初步起始阶段,通讯内容最终使用AES-GCM-256进行一个加密流程。

┌────────────────────────────┐ ┌─────────────────────────────┐ │ 插件钱包 (Extension) │ │ 后端服务 / DApp Host │ │ - curve25519-js (X25519) │ │ - X25519 / curve25519 实现 │ │ - AES-256-GCM │ │ - AES-256-GCM │ └────────────┬───────────────┘ └─────────────┬──────────────┘ │ │ │ ① 生成本地密钥对(只在本地) │ │──────────────────────────────────────────────────▶│ │ pubWallet = generateKeyPair().public │ │ privWallet = generateKeyPair().private │ │ │ │ ② 发送自己的公钥 pubWallet │ ├──────────────────────────────────────────────────▶│ │ (例如通过 postMessage / HTTP / WebSocket) │ │ │ │ │ ③ 生成对端密钥对 │ │ pubRemote, privRemote │ │ │ ④ 返回 pubRemote │ │◀───────────────────────────────────────────────────┤ │ │ │ ⑤ 双方本地各自计算 sharedKey(同一个值) │ │ 插件: │ │ sharedKey = X25519(privWallet, pubRemote) │ │ │ │ │ 服务端: │ │ sharedKey = X25519(privRemote, pubWallet) │ │ │ ⑥ 各自在本地做 KDF,派生对称密钥 aesKey │ │ aesKey = SHA-256(sharedKey)[0:32] │ │ │ │ │ 同样: │ │ aesKey = SHA-256(sharedKey)[0:32] │ │ │ ⑦ 之后通信都走 AES-256-GCM │ │ (插件这边) │ │ - 生成随机 iv │ │ - ciphertext = AES-GCM-Encrypt(aesKey, iv, msg) │ │ - 发送 { iv, ciphertext } │ ├──────────────────────────────────────────────────▶│ │ │ │ │ ⑧ 服务端用同一个 aesKey 解密: │ │ msg = AES-GCM-Decrypt(aesKey, iv, ciphertext) │ │ │ │ ⑨ 服务端回复时同样用 aesKey + AES-GCM 加密 │◀───────────────────────────────────────────────────┤ │ │ ▼ ▼ ❗ 私钥始终在插件本地 ❗ 私钥始终在服务端本地

  • privWallet 不发给任何网页 - privRemote 不发给插件
  • 助记词 / 钱包私钥也只在插件内部 - 只对等持有各自私钥

上面就是走的一个完整的流程图

游客模式只能查看,登录后可编辑和删除