钱包、签名与身份:连接、登录和授权

# 钱包、签名与身份:连接、登录和授权

连接钱包只让网站知道可以请求哪个账户;登录签名证明你控制账户;资产授权允许特定操作。三者必须分开。

基础密码学复用 密码学与交易生命周期,项目登录链路复用 wagmi、viem 与 SIWE。

# 钱包究竟保存什么

资产记录在链上,钱包管理签名能力并展示资产。传统软件钱包常由助记词派生多个私钥,私钥计算得到公钥,再按具体网络规则得到地址。地址可以公开;私钥和助记词不能交给网站、客服或 AI。

# 钱包与账户类型怎么区分

EOA、冷钱包和非托管钱包不是三选一,而是从不同角度描述同一个账户及其管理方式。

分类角度 类型 回答什么问题
链上账户 EOA、合约账户 操作由私钥直接授权,还是由合约代码验证权限?
私钥保管方式 热钱包、冷钱包 私钥在联网环境中使用,还是离线保管和签名?
控制权归属 托管、非托管钱包 服务商掌握控制权,还是用户自己掌握?

# 账户类型

这里讨论的是以太坊及兼容 EVM 的链,不要把这套分类直接套到所有区块链。

  • EOA(Externally Owned Account,外部拥有账户):由私钥控制,可以签名发起转账或调用合约。MetaMask 中通过助记词创建、导入的普通以太坊账户通常就是 EOA;服务端程序也可以控制 EOA,不只是人手动操作。
  • 合约账户(Contract Account):行为由链上代码决定,被调用时执行逻辑,没有一把与该合约地址直接对应的私钥。合约钱包是在这类账户中实现资产管理,例如 Safe 多签钱包按代码检查多个签名;普通代币合约也是合约账户,但不是钱包。

合约钱包:用代码管理资产

合约钱包就是用智能合约管理资产的钱包,是合约账户的一种用途。例如 Safe (opens new window) 可以让资产由合约地址持有,规定 3 个管理者中至少 2 个签名才能转账。根据具体实现,还可支持恢复和额度限制;操作仍需链上交易触发,不会到时间就自动执行,也要防范合约漏洞和管理员、恢复权限配置错误。

EIP-7702:让 EOA 具备智能账户能力

不能把 “EOA 永远没有代码” 当成通用判断。EIP-7702 允许 EOA 委托代码执行,获得批量操作、Gas 代付等能力,私钥仍需保护;机制区别见下文 账户抽象、EIP-7702 和 DID。

# 冷钱包与热钱包

  • 热钱包:私钥在联网的电脑、手机或服务器环境中使用,签名方便,适合日常交互,但更容易受到恶意软件、钓鱼等攻击。常见例子是直接在 MetaMask 软件中管理私钥的账户。
  • 冷钱包:私钥离线保管,交易在离线签名环境中签好,再交给联网设备广播。适合长期保管资产,但操作相对麻烦。离线的是私钥,不是资产;资产记录始终在链上。

冷钱包也不是绝对安全:助记词泄露,或用户在设备上确认了恶意转账、授权,仍可能丢资产。

# 软件钱包与硬件钱包

这组分类看的是工具形态,冷/热钱包看的是私钥是否离线保管,两者不要混为一谈。

  • 软件钱包:以浏览器插件、手机 App 或桌面程序提供钱包功能,例如 MetaMask。可以直接管理私钥,也可以连接硬件钱包,由硬件设备完成签名。
  • 硬件钱包:用专用设备保存私钥并完成签名,例如 Ledger (opens new window)、Trezor (opens new window),是常见的冷存储工具。连接电脑传递交易数据,不等于把私钥交给电脑。

如果把硬件钱包的同一份助记词导入联网软件钱包,就不再是纯离线保管。

# 托管与非托管钱包

  • 托管钱包:服务商掌握私钥或资产操作权限,用户通过平台账户申请转账、提现,例如中心化交易所的托管服务。使用方便,但要承担平台被盗、冻结或无法兑付的风险。
  • 非托管钱包:用户自己掌握私钥或账户授权与恢复机制,不依赖服务商批准转账。用户也要自己保护这些凭据;私钥、备份和可用恢复手段全部丢失时,通常无法找回资产。

托管与冷热是独立维度。交易所也会用热钱包处理日常提现、用冷钱包保管储备;非托管用户也可以同时使用软件热钱包和硬件钱包。

# 用 MetaMask 和 Ledger 理解

  • 在 MetaMask 中直接创建普通账户:通常是 EOA,由用户自行保管私钥,属于非托管热钱包。
  • 通过 MetaMask 连接 Ledger 账户:链上仍可以是 EOA,但私钥留在 Ledger 中,MetaMask 负责展示和传递签名请求,签名在设备中完成。
  • 用 Ledger 作为 Safe 的一个签名者:资产由 Safe 合约账户管理,Ledger 保护其中一个签名者的私钥。硬件钱包与合约钱包并不冲突。

所以,MetaMask 是钱包工具,EOA 是账户类型,冷钱包描述私钥的保管方式,不能把这几个词当成同一级分类。

# 四种操作不要混淆

操作 用户同意什么 服务端能据此做什么
连接钱包 网站读取获准账户并请求后续操作 不能仅凭地址签发登录会话
SIWE 登录签名 在指定域名和时间内证明地址控制权 验证后建立自己的 Web Session
approve / Permit 允许 spender 在规定条件下使用代币 只能按合约定义使用,不代表登录授权
发送交易 执行特定链上操作 跟踪回执与事件,不能只凭点击成功更新资产

Permit 常用结构化签名表达授权,签名时可以不上链,后续由别人提交到合约生效。 “不花 Gas 的签名” 不等于没有资产风险。

# SIWE 钱包签名登录

SIWE 是 Sign-In with Ethereum(以太坊签名登录),对应 EIP-4361 (opens new window)。它规定登录消息要包含哪些信息,以及后端应该怎样验证。

SIWE 是规范,siwe (opens new window) 是实现这套规范的 JavaScript/TypeScript 库。库提供 SiweMessage 等工具,用于生成、解析标准消息和验证签名,减少手写协议细节的工作。项目仍需配置可信域名和允许的链、管理一次性 nonce,并在验证成功后建立登录会话;使用库不等于整个登录功能都自动完成。

网站准备好符合 SIWE 规范的登录消息后,通过钱包提供的消息签名接口(例如 MetaMask 支持的 personal_sign)请求用户签名,再交给后端验证。SIWE 是登录规范,personal_sign 是钱包的通用签名接口。

一次登录应按以下顺序进行:

  1. 后端生成不可预测、短期有效的一次性 nonce,并绑定当前登录尝试。
  2. 前端构造包含 domain、URI、chainId、nonce 和时间的消息,钱包签名。
  3. 后端解析原消息,逐项检查域名、URI、允许的链、有效期和 nonce,验证签名与地址的关系。
  4. 后端原子消费 nonce,再创建 Session,返回安全 Cookie。

原子消费表示 “检查仍未使用” 和 “标记已使用” 不能被两个并发请求同时通过,例如数据库条件更新只允许一条请求成功。签名正确但 domain 错了,也不能放行。

普通 EOA 可用签名恢复等方式验证;合约钱包应支持 ERC-1271 (opens new window) 等相应验证机制,不能统一假定签名都是一个私钥生成的 ECDSA。具体适配交给可靠的钱包验证库,并测试所支持的账户类型。

标准 SIWE 登录也不上链,不需要用户支付 Gas。用户签的是登录消息,后端验证后建立网站会话,不会广播链上交易。消息里的 Chain ID 用来标明登录所针对的链,不代表要发交易;合约钱包验签时可能通过 RPC 只读调用 ERC-1271 接口,这也不需要用户支付链上 Gas。

ECDSA 与 ERC-1271:签名算法和验证接口

  • ECDSA(Elliptic Curve Digital Signature Algorithm,椭圆曲线数字签名算法):普通 EOA 常用的签名算法。钱包用私钥签名,后端根据消息和签名恢复地址,再与登录地址核对,不需要获取私钥。
  • ERC-1271:合约钱包验证签名的标准接口。后端调用钱包合约的 isValidSignature,由合约判断签名是否符合规则,例如 Safe 要求 3 个管理者中至少 2 个签名。

简单说,ECDSA 是签名算法,ERC-1271 是询问合约是否认可签名的统一接口;合约内部也可以验证 ECDSA 签名,两者不冲突。

Cookie 需要 HttpOnly、Secure 和合适的 SameSite,状态修改接口仍要防 CSRF。钱包地址不是现实身份认证,也不能证明一个地址只属于一个真人。

# 一次性 nonce 怎样真正挡住重放

先校验签名、域名、有效期和本次登录绑定关系,再在数据库中条件消费挑战。下面是 PostgreSQL 教学示例,只展示并发时哪个请求取得消费权,不代替 SIWE 签名验证;数据保存在当前连接的临时表。

CREATE TEMP TABLE note_login_challenges (
  nonce text PRIMARY KEY,
  attempt_id text NOT NULL,
  expires_at timestamptz NOT NULL,
  used_at timestamptz
);
-- 固定字符串只供本地说明;真实 nonce 必须由安全随机数生成。
INSERT INTO note_login_challenges VALUES
  ('local-example-only', 'attempt-1', now() + interval '5 minutes', NULL);

UPDATE note_login_challenges
SET used_at = now()
WHERE nonce = 'local-example-only'
  AND attempt_id = 'attempt-1'
  AND used_at IS NULL AND expires_at > now()
RETURNING nonce; -- 返回 1 行才取得消费权;重复执行返回 0 行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

真实服务通过参数绑定传入值,不拼接 SQL。创建 Session 与挑战消费应设计一致的事务或失败恢复方式;签名验证通过但条件更新没有返回行,仍然不能签发新 Session。两个请求都先 SELECT 到未使用,再分别 UPDATE,则不能保证只有一个通过。

# EIP-712、Permit 与登录不是同一种签名

EIP-712 (opens new window) 规定结构化数据的签名编码与域分隔方式,便于清晰表达字段和签名用途;它本身不自动实现一次性消费或过期控制。具体业务要把 nonce、deadline 等纳入签名并在验证端检查。

例如 Permit 表达 owner 允许 spender 花费一定数量代币,签名被提交后可能影响资产;SIWE 表达的是在指定站点证明身份。界面都可能弹出签名确认,但业务权限完全不同。EIP-712 格式规范也不代表签名内容安全,要核对链、验证合约、授权对象、金额和期限。

合约钱包的有效签名还可能取决于当前管理员或策略。支持 ERC-1271 时,应在允许的链上调用对应验证机制,而不是遇到 ECDSA 恢复失败就让用户换普通钱包。已建立的站点会话还需独立的有效期和退出机制,不能把一次签名当成永久授权。

# 切账户与切链为什么容易出错

页面请求发出时是账户 A,响应回来时用户可能已切到 B。如果只把结果写进通用的余额状态,就会显示错账户数据。

把 chainId、address、合约地址纳入缓存键;请求开始时保存这些值,结果回来后确认上下文仍一致。账户切换后清理与旧身份绑定的页面状态,并按产品规则重新验证会话。切换钱包不代表服务端 Session 自动失效,两边要显式协调。

# 账户抽象、EIP-7702 和 DID

账户抽象让账户验证不只依赖传统单私钥流程,例如多签、恢复、批量操作和 Gas 代付。ERC-4337 使用 UserOperation、Bundler 和 EntryPoint 等组件:用户提交操作,Bundler 将操作打包进链上交易,EntryPoint 执行验证与调度。Paymaster 可按策略承担费用,但不是凭空免费。

EIP-7702 则允许 EOA 设置代码委托;它与 ERC-4337 不是同一个机制,也不是彼此必须排斥。签署委托前应理解目标代码的权限和持续影响。

DID 是 Decentralized Identifier(去中心化标识符),围绕标识、解析文档与控制权验证组织身份。钱包地址登录并不等于完整 DID 系统;只有确实需要跨系统可验证身份或凭证时,再引入相应标准。

# 面试时可以这样回答

为什么不能用前端传来的钱包地址直接登录?

建议回答:地址是公开信息,谁都能提交。后端要给一次性挑战,让钱包签名,再校验地址、域名、链和有效期,并原子消费 nonce。通过后才建立会话。登录签名只证明地址控制权,不能被复用成转账授权。

签名没上链、没花 Gas,为什么仍可能丢资产?

建议回答:签名可以授权别人稍后提交链上操作,比如 Permit 允许指定 spender 使用代币。签名时没付 Gas,只是还没由这一步支付交易费,不代表没有授权。我会把登录和资产签名分开展示,并核对链、合约、金额、nonce 和期限;结构化签名能帮助理解,但不能替用户判断授权对象是否可信。

一次登录签名怎样防止被别人重复使用?

建议回答:服务端生成短期随机 nonce 并绑定登录尝试,验证签名时检查域名、链、时间和绑定关系,最后通过条件更新原子消费。只有成功消费的请求才能建立会话。这样既防止拿别的站点签名来登录,也防止同一签名被并发提交多次;只检查签名数学上有效是不够的。

# 官方参考

上次更新时间: 2026年10月10日 06:39:50