查询 25 次,耗时 0.2366 秒

从“看懂再签”到链上复核:一套适用于 EVM 与比特币钱包的交易安全检查流程

lawrenceyang

New member

lawrenceyang Rep
0
0
0
Rep
0
lawrenceyang Vouches
Vouches
0
1 MONTH
1 1 MONTH OF SERVICE
lawrenceyang 获得
0
0
0
获得
0
lawrenceyang 凭证
0
0
0
凭证
0
主题
8
点赞
0
HakB
1 月
在社区成长 1 月
LEVEL 1 1 XP
 
一、背景:钱包安全的核心不是保管助记词这么简单

很多资产损失并不是因为助记词直接泄露,而是用户主动签署了自己没有理解的交易或消息。常见场景包括:转账地址被剪贴板恶意软件替换、收到地址投毒的小额交易后复制了错误地址、连接到仿冒 DApp、对陌生合约授予无限额度、签署 Permit 或订单类消息,以及钱包连接到了错误链或不可信 RPC。

钱包界面通常会把复杂的 calldata、脚本或结构化签名简化成“确认”按钮。工程上更可靠的做法,是建立一条固定流程:确认网络与来源、独立核对地址、解析交易内容、模拟执行、使用小额验证、广播后检查回执,并定期清理授权。

本文只讨论防御性检查方法。所有测试应在本地节点、测试网、授权环境或自己的钱包上进行,不要尝试操作不属于自己的资产或账户。

二、基本原理:你签署的究竟是什么

1. EVM 交易

一笔典型 EVM 交易包含 chainId、nonce、to、value、data、gasLimit,以及费用参数。需要特别注意:

(1)to 是接收原生币的地址,也可能是被调用的合约地址。
(2)value 表示随交易发送的原生币数量,不等于 ERC-20 转账数量。
(3)data 决定合约执行什么函数。即使 value 为零,data 仍可能授权第三方转走代币或 NFT。
(4)chainId 用于区分网络。同一地址可能同时存在于多条链上,但其合约代码和资产含义可能完全不同。

常见函数选择器包括:

ERC-20 transfer(address,uint256):0xa9059cbb
ERC-20 approve(address,uint256):0x095ea7b3
NFT setApprovalForAll(address,bool):0xa22cb465

前 4 字节只能帮助识别可能的函数,不能单独证明交易安全。函数选择器可能碰撞,代理合约还可能把调用转发到其他实现合约,因此必须结合合约地址、ABI、源码验证状态和模拟结果判断。

2. 消息签名

“签名不花 Gas”不等于“没有风险”。Permit、Permit2、NFT 市场订单和 EIP-712 结构化消息,都可能给第三方可执行的权限。检查时至少应关注:

(1)domain 中的 chainId 和 verifyingContract;
(2)spender、operator、recipient 等权限接收者;
(3)token、amount、tokenId;
(4)deadline 或 expiration;
(5)nonce;
(6)是否允许批量操作或转移全部资产。

如果钱包只能显示十六进制字符串或提示 blind signing,而你无法在可信工具中还原其含义,最稳妥的选择是拒绝签名。

3. 比特币交易

比特币采用 UTXO 模型。检查重点不是账户余额,而是:使用了哪些输入、产生了哪些输出、哪个输出是找零、矿工费是多少,以及脚本类型是否符合预期。恶意或错误构造的 PSBT 可能把找零发往非本人控制的地址,因此硬件钱包上的输出地址与金额核对非常重要。

三、前置条件

建议准备以下环境:

1. 一台尽量干净的电脑,系统和钱包软件来自官方渠道并已校验版本来源。
2. 硬件钱包或隔离签名设备;大额钱包与日常交互钱包分离。
3. 至少两个相互独立的 RPC 或区块浏览器用于交叉验证。
4. EVM 工具 Foundry,其中 cast 用于编码、读取和模拟,anvil 用于本地链实验。
5. 如需检查比特币原始交易,准备 Bitcoin Core,并只在自己的节点或授权测试环境中操作。
6. jq、curl 等基础命令行工具。

开始前先记录预期结果:目标网络、接收地址、资产合约、数量、允许的最大费用、是否需要授权。没有明确预期,就很难判断模拟结果是否异常。

四、分步骤实践:EVM 交易检查

步骤 1:确认 chainId,避免签错网络

使用自己的 RPC 查询:

curl -s -X POST "$RPC_URL" -H "content-type: application/json" --data '{"jsonrpc":"2.0","id":1,"method":"eth_chainId","params":[]}'

也可以使用:

cast chain-id --rpc-url "$RPC_URL"

不要只相信网页显示的网络名称。RPC 返回值、钱包网络和 DApp 预期网络应当一致。如果网站突然要求切换到陌生网络,应停止操作并重新核实入口域名。

步骤 2:核对地址,而不是只看首尾几位

接收地址应从独立渠道获取,例如项目官方文档与硬件钱包屏幕,而不是从交易历史中的相似地址复制。地址投毒经常利用首尾字符相似来诱导误复制。

建议至少核对完整地址,或分组检查全部字符。对于经常使用的地址,可以建立带用途说明的本地地址簿,并通过一次小额转账验证。ENS 等名称解析也应确认解析结果,不要只看名称本身。

步骤 3:检查目标是否为合约

cast code 0x目标地址 --rpc-url "$RPC_URL"

若返回 0x,通常表示当前链上该地址没有运行时代码;若返回较长字节码,则是合约地址。注意,这并不能证明合约可信。代理合约还需要继续检查其实现地址、管理员权限和升级机制。

同一个地址在不同网络上可能有不同代码,因此每次检查必须绑定 chainId。

步骤 4:解析 calldata

假设钱包或 DApp 给出的 data 保存在 INPUT 中:

cast 4byte-decode "$INPUT"

也可以自行生成预期 calldata,再与待签交易逐字节比较:

cast calldata "transfer(address,uint256)" 0x接收地址 1000000

如果代币精度为 6,1000000 通常代表 1 个代币单位;但精度必须从合约读取,不能凭经验猜测:

cast call 0x代币合约 "decimals()(uint8)" --rpc-url "$RPC_URL"
cast call 0x代币合约 "symbol()(string)" --rpc-url "$RPC_URL"

symbol 和 decimals 都可能由恶意合约伪造,因此它们只用于显示,合约地址才是资产身份的关键。

步骤 5:重点检查授权交易

读取现有 ERC-20 授权:

cast call 0x代币合约 "allowance(address,address)(uint256)" 0x钱包地址 0xSpender地址 --rpc-url "$RPC_URL"

构造精确额度授权的预期 calldata:

cast calldata "approve(address,uint256)" 0xSpender地址 1000000

不建议为了省一次交易而长期保留无限授权。授权额度应与本次用途接近,使用结束后再评估是否撤销。部分旧代币在修改非零授权时可能要求先设为零;实际操作前应阅读对应代币的已验证源码或官方说明。

NFT 还要检查:

cast call 0xNFT合约 "isApprovedForAll(address,address)(bool)" 0x钱包地址 0xOperator地址 --rpc-url "$RPC_URL"

setApprovalForAll 会影响该集合下的全部 NFT,其风险通常高于单个 tokenId 的授权。

步骤 6:在不广播的情况下模拟

对只读调用可直接使用 cast call。对于完整交易,可使用支持 eth_call、trace_call 或交易模拟的可信节点。示例:

cast call 0x目标合约 --data "$INPUT" --from 0x钱包地址 --value 0 --rpc-url "$RPC_URL"

模拟时应关注:

(1)调用是否成功;
(2)返回值是否符合预期;
(3)是否产生额外的内部调用;
(4)是否转移了非预期代币或 NFT;
(5)是否修改了代理实现、管理员或授权状态;
(6)模拟区块与真实广播时间之间,合约状态是否可能变化。

模拟成功不等于绝对安全。可升级合约、依赖时间或价格的逻辑、抢跑以及状态变化,都可能让真实执行与模拟不同。

步骤 7:先做小额验证,再执行主要操作

首次使用新地址、新跨链桥或新合约时,先发送可承受损失的小额金额。确认接收方到账、资产合约正确、网络正确后,再考虑后续操作。小额验证不能证明合约永远安全,但能发现地址、网络、精度和基本流程错误。

步骤 8:广播后检查回执

cast receipt 0x交易哈希 --rpc-url "$RPC_URL"

重点查看 status、to、from、logs、gasUsed 和区块确认情况。不要只依据前端显示“成功”。对于代币转账,应检查日志中的合约地址、发送方、接收方和数量;对于授权,应重新读取 allowance 或 isApprovedForAll,确认最终状态与预期一致。

如需复盘一笔自己的历史交易,可以尝试:

cast run 0x交易哈希 --rpc-url "$RPC_URL"

该操作是否可用取决于节点是否保留所需历史状态。执行轨迹适合排查内部调用,但仍应结合已验证源码与事件日志分析。

五、本地实验:用 Anvil 建立无资产风险的检查习惯

启动本地 EVM:

anvil

在另一个终端确认网络:

export RPC_URL=http://127.0.0.1:8545
cast chain-id --rpc-url "$RPC_URL"
cast block-number --rpc-url "$RPC_URL"

Anvil 会显示仅用于本地测试的预置账户和私钥。这些私钥是公开测试凭据,绝不能用于主网或保存真实资产。

可以先练习 calldata 编码:

cast calldata "approve(address,uint256)" 0x1111111111111111111111111111111111111111 5000000
cast calldata "setApprovalForAll(address,bool)" 0x2222222222222222222222222222222222222222 true

逐段观察编码结果,并训练自己在看到钱包提示时主动回答三个问题:授权给谁、授权什么资产、授权多少或多大范围。之后可在本地部署自有测试合约,分别模拟精确授权、无限授权、授权撤销和事件日志检查。这样不会触碰真实资产,也更容易形成固定操作清单。

六、比特币原始交易与 PSBT 检查

对于已经构造但尚未广播的原始交易,可在自己的 Bitcoin Core 节点上解码:

bitcoin-cli decoderawtransaction "$RAW_TX_HEX"

检查 vin 和 vout,确认每个输出的地址与金额。找零地址必须属于自己的钱包,不能因为金额看起来接近就默认它是找零。

在不广播的情况下检查节点是否接受该交易:

bitcoin-cli testmempoolaccept "[\"$RAW_TX_HEX\"]"

该命令只进行内存池接收规则测试,不代表未来一定能确认,也不能替代对输出地址、费用和签名设备屏幕的核对。

如果使用 PSBT,建议先解码:

bitcoin-cli decodepsbt "$PSBT"

重点检查输入对应的 UTXO、每个输出、手续费、找零位置和脚本类型。硬件钱包最终显示的信息应与离线解码结果一致。若软件界面与硬件屏幕不一致,应以停止签名并排查环境为优先,而不是选择其中一个“看起来更合理”的结果。

七、链上数据中的风险识别

链上记录透明,但“有历史”不等于“可信”。实用的风险信号包括:

1. 地址刚创建不久,却要求大额或无限授权。
2. 合约源码未验证,或代理实现无法确认。
3. 前端域名、合约地址与官方多渠道公布的信息不一致。
4. 交易模拟出现意料之外的 transferFrom、delegatecall 或批量调用。
5. 签名消息的 verifyingContract、spender、deadline 与页面描述不一致。
6. 收到大量极小额转账,且对方地址与常用地址首尾相似,这可能是地址投毒。
7. 代币名称和符号与知名资产相同,但合约地址不同。
8. 授权对象本身是可升级合约,且管理员权限集中或升级机制不透明。

不要仅凭单一标签或某个浏览器的风险提示下结论。标签可能过时,也可能同名。可靠判断应结合合约代码、部署关系、交易轨迹、权限结构和多个独立数据源。

八、风险与注意事项

1. 不要把助记词、私钥或完整 keystore 粘贴到网页、聊天工具或命令行历史中。
2. 命令示例中的地址都应替换成你已独立核实的地址。
3. 公共 RPC 能看到你的 IP、查询地址和访问模式。高隐私需求应使用自建节点或经过评估的基础设施。
4. 浏览器扩展钱包与日常浏览环境共享攻击面。建议将大额冷钱包、日常热钱包和 DApp 实验钱包分开。
5. 硬件钱包不是“自动安全”。如果设备屏幕显示的是陌生地址、无限授权或不可读数据,应拒绝签名。
6. 撤销授权本身也是链上交易,要确认撤销的是正确网络、正确代币和正确 spender。
7. 可升级代理今天的逻辑正常,不代表未来实现不会变化。长期授权应定期复查。
8. 不要把测试网私钥复用于主网,也不要向公开示例私钥转入真实资产。

九、常见问题

问题 1:value 为 0 的交易是不是安全?

不是。ERC-20 授权、NFT 全量授权、代理升级和很多合约管理操作的 value 都可能为 0,但仍能改变关键权限。

问题 2:区块浏览器显示合约已验证,是否可以放心交互?

源码验证只说明公开源码与链上字节码存在可验证关系,不代表业务逻辑没有漏洞,也不代表管理员权限合理。还要检查代理实现、所有者、升级权限和实际调用参数。

问题 3:无限授权一定是骗局吗?

不一定。有些协议为了减少重复授权会请求较大额度,但这会扩大合约漏洞、私钥泄露或权限滥用时的损失范围。更稳妥的做法是按需授权,并在使用后复查。

问题 4:交易模拟成功,为什么实际交易仍可能失败?

模拟依赖某一时刻的链上状态。余额、nonce、价格、区块时间、流动性和其他交易排序都可能变化。模拟是风险过滤器,不是执行保证。

问题 5:只核对地址前 6 位和后 4 位够吗?

不够。攻击者可以生成局部相似地址。大额转账应核对完整地址,并通过独立渠道或硬件设备确认。

问题 6:签名消息不广播交易,为什么还能转走资产?

某些签名会生成可由第三方提交的授权或订单。第三方拿到有效签名后,可以在截止时间前按合约规则执行,因此消息签名应按交易级别对待。

十、总结:把钱包操作当作一次代码变更审查

安全的钱包工作流与高质量开发流程很相似:先明确预期,再检查输入,随后进行静态解析和动态模拟,最后小范围验证并复核结果。可落地的最小清单是:

1. 核对域名、chainId 和完整地址;
2. 确认资产合约与精度;
3. 解析 calldata 或结构化消息;
4. 重点检查 spender、operator、amount、deadline;
5. 使用可信节点进行模拟;
6. 新地址和新协议先小额验证;
7. 广播后检查回执、日志与最终授权状态;
8. 定期清理不再使用的授权;
9. 大额资产与日常 DApp 钱包隔离;
10. 任何无法解释的签名都选择拒绝。

这套流程不会消除所有智能合约、终端设备和供应链风险,但能显著减少因网络选错、地址投毒、盲签、无限授权和错误找零造成的可避免损失。真正可靠的目标不是“更快点确认”,而是确保每次签名都能回答:在哪条链上、对哪个合约、授予谁什么权限、最多影响多少资产,以及执行后如何验证。
 
顶部