钱包连接与签名
dApp 没有"注册/登录"表单,取而代之的是"连接钱包"。这一章讲清楚连接背后的标准接口(EIP-1193)、三种签名的本质区别,以及一个优雅的"用以太坊登录"方案。理解签名的类型至关重要——因为最贵的教训往往来自"用户不知道自己签了什么"。
连接:EIP-1193 注入的 provider
安装了 MetaMask 后,它会往网页里注入一个 window.ethereum 对象,遵循 EIP-1193 标准接口。连接就是请求授权访问用户地址:
if (!window.ethereum) throw new Error('请安装钱包'); // 弹出钱包,请求用户授权连接 const accounts = await window.ethereum.request({ method: 'eth_requestAccounts', }); const address = accounts[0]; // 监听用户切换账户 / 网络,及时更新 UI window.ethereum.on('accountsChanged', (a) => { /* 重新加载 */ }); window.ethereum.on('chainChanged', (id) => { /* 检查链是否对 */ });
"连接钱包"只是让 dApp 读到你的地址,它不给 dApp 动你资产的权力——连接本身零风险,资产转移永远需要你后续对具体交易签名。别被"连接了就会被盗"的谣言吓到;真正的风险在于你连接后签署了什么。同时要处理多钱包共存(EIP-6963)和用户中途切网络的情况。
三种签名,天差地别
钱包能签名的东西分几类,风险递增,务必分清:
| 类型 | 用户签的是 | 会花钱吗 | 典型用途 |
|---|---|---|---|
personal_sign | 一段纯文本消息 | 不会(链下) | 证明"我拥有这个地址",如登录 |
eth_signTypedData_v4(EIP-712) | 结构化的、人类可读的数据 | 不会(链下) | 授权、订单、许可(permit) |
| 发送交易 | 一笔会上链的交易 | 会,花 gas 改状态 | 转账、调合约 |
personal_sign:证明"我是我"
const message = 'Sign in to MyDApp at 2026-07-06T…'; const signature = await window.ethereum.request({ method: 'personal_sign', params: [message, address], }); // 后端用 ecrecover 从签名恢复出地址,验证 == address 即证明持有私钥
它不花 gas、不上链,只是用私钥对一段话签名。后端可以从签名反推出签名者地址(第 3 章公私钥的逆用),从而确认"这个人确实控制着这个地址",无需密码。
EIP-712:让"签什么"看得懂
盲签一串十六进制是灾难之源。EIP-712 让钱包把签名内容展示成结构化、带字段名的样子,用户能看清自己在授权什么:
{
domain: { name: "MyDApp", chainId: 1, verifyingContract: "0x…" },
types: {
Order: [
{ name: "maker", type: "address" },
{ name: "amount", type: "uint256" },
{ name: "expiry", type: "uint256" }
]
},
message: { maker: "0xAlice…", amount: "100", expiry: "1720000000" }
}
钱包会把它显示成"maker: 0xAlice…, amount: 100, expiry: …"而不是一坨字节。它常用于链下签名、链上验证的模式:比如 DEX 的挂单(用户签一个订单,不上链、零 gas,撮合时才由合约验证并执行),或 ERC-20 的 permit(签一个授权,省掉单独的 approve 交易)。
钓鱼的重灾区正是诱导用户签一个看似无害、实则是恶意授权的 EIP-712 消息——比如一个伪装成"验证钱包"的签名,实际是把你 NFT 的操作权授予攻击者。用户教育的铁律:看清楚每一个签名请求的 domain(是不是你信任的站点)和内容,任何看不懂的签名都拒绝。作为开发者,你有责任让签名内容清晰,并绝不训练用户养成盲签习惯。
SIWE:用以太坊登录
Sign-In with Ethereum(EIP-4361)把 personal_sign 标准化成一套安全的登录协议。流程:
- 后端生成一段带 nonce(随机数,防重放)、域名、时间的标准消息。
- 用户用钱包 personal_sign 这段消息(零 gas)。
- 后端验证签名 + nonce,确认无误后签发一个 session(如 JWT)。
SIWE 就是把"公私钥"当作登录凭证的挑战-响应认证,概念上和你熟悉的"服务器发 challenge、客户端用密钥签名、服务器验签发 token"一模一样(类似某些无密码登录 / 设备证明)。区别是身份不由某个中心账户系统颁发,而是用户自带的钱包地址。nonce 就是那个防重放的一次性挑战值,和你做安全认证时的思路完全一致。
连接钱包(EIP-1193)只暴露地址,零风险;风险全在之后签了什么。三类签名要分清:personal_sign(证明身份、链下)、EIP-712(结构化可读、常用于链下授权)、发送交易(上链花钱)。SIWE 用签名实现无密码登录。作为工程师,你的天职是让每个签名请求都清晰可懂,永不诱导盲签。下一章我们把连接、读、写、事件监听全串起来,做一个完整可跑的 dApp。
本章小结
- 钱包通过 EIP-1193 注入 window.ethereum;连接只读取地址,不授予动资产的权力。
- 三类签名:personal_sign(身份、链下免费)、EIP-712 typed data(结构化可读授权)、发送交易(上链花 gas)。
- EIP-712 支持"链下签名、链上验证",用于挂单、permit 等;也是钓鱼重灾区,须让用户看懂再签。
- SIWE(EIP-4361)用带 nonce 的签名实现无密码登录,本质是挑战-响应认证。