钱包、签名与身份:连接、登录和授权
# 钱包、签名与身份:连接、登录和授权
连接钱包只让网站知道可以请求哪个账户;登录签名证明你控制账户;资产授权允许特定操作。三者必须分开。
基础密码学复用 密码学与交易生命周期,项目登录链路复用 wagmi、viem 与 SIWE。
# 钱包究竟保存什么
资产记录在链上,钱包管理签名能力并展示资产。传统软件钱包常由助记词派生多个私钥,私钥计算得到公钥,再按具体网络规则得到地址。地址可以公开;私钥和助记词不能交给网站、客服或 AI。
硬件钱包把签名限制在设备中,但用户仍可能同意恶意交易。合约钱包可定义多签、恢复和额度规则,也引入合约实现与管理员风险。托管钱包则把部分或全部控制权交给服务商,不能只因为有钱包界面就认定用户完全自托管。
# 四种操作不要混淆
| 操作 | 用户同意什么 | 服务端能据此做什么 |
|---|---|---|
| 连接钱包 | 网站读取获准账户并请求后续操作 | 不能仅凭地址签发登录会话 |
| SIWE 登录签名 | 在指定域名和时间内证明地址控制权 | 验证后建立自己的 Web Session |
approve / Permit | 允许 spender 在规定条件下使用代币 | 只能按合约定义使用,不代表登录授权 |
| 发送交易 | 执行特定链上操作 | 跟踪回执与事件,不能只凭点击成功更新资产 |
Permit 常用结构化签名表达授权,签名时可以不上链,后续由别人提交到合约生效。 “不花 Gas 的签名” 不等于没有资产风险。
# SIWE 登录必须由后端验证
SIWE 是 Sign-In with Ethereum,对应 EIP-4361。一次登录应按以下顺序进行:
- 后端生成不可预测、短期有效的一次性 nonce,并绑定当前登录尝试。
- 前端构造包含 domain、URI、chainId、nonce 和时间的消息,钱包签名。
- 后端解析原消息,逐项检查域名、URI、允许的链、有效期和 nonce,验证签名与地址的关系。
- 后端原子消费 nonce,再创建 Session,返回安全 Cookie。
原子消费表示 “检查仍未使用” 和 “标记已使用” 不能被两个并发请求同时通过,例如数据库条件更新只允许一条请求成功。签名正确但 domain 错了,也不能放行。
普通 EOA 可用签名恢复等方式验证;合约钱包应支持 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 行。
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 并绑定登录尝试,验证签名时检查域名、链、时间和绑定关系,最后通过条件更新原子消费。只有成功消费的请求才能建立会话。这样既防止拿别的站点签名来登录,也防止同一签名被并发提交多次;只检查签名数学上有效是不够的。