Web3 生态里,前端开发常用的签名工具就是 Web3.js,而用来做身份验证的两个核心签名方法——PersonalSign 和 TypedData——经常让刚接触的开发者混淆,尤其是在登录验证这种高频场景里,选错可能会出问题。
一、先搞懂两个签名方法的基本用法
Web3.js里的签名本质是用私钥对一段数据加密,生成唯一的签名,用来证明持有者拥有对应钱包地址的控制权,而PersonalSign和TypedData是最常用的两种实现,先分别看它们的基础用法。
1.1 PersonalSign:简单的文本消息签名
PersonalSign是比较早出现的签名方法,核心是对纯文本字符串进行签名,后端拿到签名后,能算出是哪个地址签的,和用户当前的钱包地址对比,就能确认身份。我们直接看可运行的代码示例,技术栈统一用JavaScript + Web3.js v4.0,确保不会混乱:
// 技术栈:JavaScript + Web3.js v4.0
import Web3 from 'web3';
// 初始化Web3实例,假设用户已经通过MetaMask连接了钱包
const web3 = new Web3(window.ethereum);
await window.ethereum.enable(); // 申请钱包访问权限,必须先执行
const userWallet = web3.eth.defaultAccount; // 获取当前连接的钱包地址
// 登录验证场景:用PersonalSign签名简单的身份信息
async function usePersonalSignForLogin() {
try {
// 构造要签名的消息,必须包含用户钱包地址,防止非本人操作
const signMessage = `我正在使用去中心化应用登录,我的钱包地址是:${userWallet},当前时间戳:${Date.now()}`;
// 调用PersonalSign方法,参数依次是:签名消息、用户钱包地址、钱包密码(MetaMask不需要密码,传空字符串)
const signatureResult = await web3.eth.personal.sign(signMessage, userWallet, '');
console.log('PersonalSign生成的签名:', signatureResult);
// 后端验证签名:用方法恢复签名者地址,和当前用户地址对比
const recoveredAddr = await web3.eth.personal.ecRecover(signMessage, signatureResult);
if (recoveredAddr.toLowerCase() === userWallet.toLowerCase()) {
console.log('PersonalSign登录验证通过,身份确认');
return true;
}
return false;
} catch (err) {
console.error('PersonalSign登录失败,原因:', err.message);
return false;
}
}
这里要注意,PersonalSign的消息是纯文本,钱包会直接显示这段文字给用户看,所以如果消息被钓鱼网站拼接成恶意内容,用户可能没察觉就签了,是常见的安全隐患。
1.2 TypedData:结构化的指定类型数据签名
TypedData是遵循EIP-712规范的签名方法,核心是对结构化、带类型的对象签名,钱包会把对象的每个字段清晰展示给用户,不会被钓鱼混淆,安全性更高。同样的技术栈,示例如下:
// 技术栈:JavaScript + Web3.js v4.0(和之前完全统一)
import Web3 from 'web3';
const web3 = new Web3(window.ethereum);
await window.ethereum.enable();
const userWallet = web3.eth.defaultAccount;
// 构造TypedData的结构化数据,严格遵循EIP-712规范,用于登录验证
const loginTypedData = {
// 定义数据类型:LoginRequest,包含两个必填字段
types: {
LoginRequest: [
{ name: 'userAddress', type: 'address' }, // 钱包地址类型
{ name: 'requestTime', type: 'uint256' } // 时间戳类型,防止重放
]
},
primaryType: 'LoginRequest', // 指定本次签名的核心类型
domain: { // 域标识:隔离不同DApp,防止跨站钓鱼,必须填对
name: '我的去中心化论坛', // DApp名称
version: '1.0', // 协议版本
chainId: 1, // 以太坊主网ID,测试网比如Goerli是5
verifyingContract: '0x0000000000000000000000000000000000000000' // 登录场景用0地址即可
},
message: { // 要签名的具体数据内容
userAddress: userWallet,
requestTime: Date.now()
}
};
// 登录验证场景:用TypedData签名
async function useTypedDataForLogin() {
try {
// 调用TypedData签名方法,参数是用户钱包地址和结构化数据对象
const signatureResult = await web3.eth.signTypedData(userWallet, loginTypedData);
console.log('TypedData生成的签名:', signatureResult);
// 后端验证TypedData:通过结构化数据和签名恢复地址,对比用户地址
const recoveredAddr = await web3.eth.accounts.recoverTypedData(loginTypedData, signatureResult);
if (recoveredAddr.toLowerCase() === userWallet.toLowerCase()) {
console.log('TypedData登录验证通过,身份安全可靠');
return true;
}
return false;
} catch (err) {
console.error('TypedData登录失败,原因:', err.message);
return false;
}
}
TypedData的优势是把消息结构化,用户看到的是"登录请求:钱包地址是XXX,时间是XXX",清清楚楚,不会被钓鱼网站的恶意消息误导。
二、两者的核心区别
2.1 应用场景差异
PersonalSign适合简单的临时身份验证,比如普通的密码登录替代场景,不需要后续验证数据的完整性,代码量少,实现速度快,适合小型DApp或快速迭代的项目。 TypedData适合需要安全保障的复杂场景,比如带时间戳的防重放登录、带操作数据的授权、需要后续审计的身份验证,符合Web3的安全规范,现在主流的DApp(比如Uniswap、OpenSea)都开始转向TypedData。
2.2 技术优缺点对比
PersonalSign的优点:上手简单,代码逻辑短,绝大多数钱包(包括旧版本)都支持,不需要额外规范。缺点:安全性低,消息容易被钓鱼篡改,无法保证数据的完整性,结构混乱,容易被混淆。 TypedData的优点:安全性高,钱包清晰展示签名内容,防钓鱼、防重放、防篡改,遵循EIP-712规范,符合Web3安全标准。缺点:需要遵循严格的数据结构规范,代码稍复杂,旧钱包可能不支持,但现在主流钱包都已兼容TypedData V4版本。
2.3 验证逻辑的差异
PersonalSign的验证逻辑是:直接对原始文本消息和签名,用椭圆曲线算法恢复签名者地址,只要地址匹配就验证通过,没有数据完整性校验。 TypedData的验证逻辑是:先把结构化数据按照EIP-712的规则哈希成一个唯一的消息(哈希时包含了数据的类型、结构和域信息),然后再用同样的算法恢复地址,只要任何一个字段被篡改,哈希结果就会变化,验证必然失败,安全性远高于PersonalSign。
三、登录验证场景中的选择指南
登录验证是DApp最核心的身份入口,选哪种方法可以按以下规则判断:
- 如果是极简原型或小型项目,只需要快速实现登录,不需要长期的安全保障,选PersonalSign即可,完全能满足基础需求。
- 如果是正式上线的DApp,尤其是涉及敏感操作(比如提交数据、授权权限),必须选TypedData,因为它能防止钓鱼攻击和签名重放,避免用户资金或数据损失,是当前行业的主流选择。 举个实际例子:如果你做一个Web3论坛,登录后用户要发帖、参与投票,这些操作都需要绑定登录时的身份,这时候用TypedData,因为登录时的结构化数据(地址+时间)可以被后续操作验证,保证身份的唯一性和不可篡改性,而PersonalSign的简单文本无法做到这一点。
四、常见的坑和注意事项
很多开发者用这两个方法时会踩坑,需要注意这些细节:
- PersonalSign的消息不要包含可被篡改的用户可控内容,比如不要让用户自己填消息,否则容易被钓鱼网站拼接成恶意内容;
- TypedData的
domain必须正确填写,尤其是chainId,如果填错链ID,钱包会直接拒绝签名,还有domain的name和version尽量如实填写,部分钱包会把这些信息展示给用户; - 不要用旧版本的TypedData(比如V3),推荐用V4版本,兼容更多钱包,安全性更高;
- PersonalSign的签名需要注意,不能直接用用户输入的消息,必须自己构造完整的消息,防止用户输入特殊字符导致消息被篡改;
- 验证签名时,必须把恢复的地址转换成小写(或者都转成大写)再对比,避免大小写差异导致验证失败,因为以太坊地址本身不区分大小写,但签名恢复的地址偶尔会有大小写不同的情况。
评论
围绕“Web3.js签名消息的PersonalSign与TypedData区别,在登录验证场景中如何选择”参与讨论