原文:《Vanity Addresses》by foobar

编译:隔夜的粥,DeFi 之道

1.6 亿美元不翼而飞,而丢失资金的 Wintermute 是业内最敏锐的做市基金之一,在 9 月份的一个早上,当 Wintermute 负责人醒来时,发现他们的一个重要钱包丢失了 9 位数的资金。那是什么导致了 Wintermute 被盗?这是由一个靓号地址生成器中糟糕的随机性引起的。黑帽黑客从头开始暴力破解私钥&公开地址对,然后,大量的加密资产就这么被转走了。

还有一个关于 Indexed Finance 被盗的故事,他们在 2021 年 10 月被盗走了 1600 万美元,然后被盗资金转移到了 以 0xba5ed… 开头的地址。他们不知道的是,这个靓号地址也受到了困扰 Wintermute 的糟糕随机性漏洞的影响,2022 年 9 月,所有的钱都再次被盗,流向了另一个黑客钱包地址。盗贼是无情的。

那这些天才开发者到底遇到了什么问题,我们能从中学到什么?

靓号地址容易被盗?一文详解靓号地址安全部署方案

左边是用于 WETH 合约的普通地址。右侧是用于 MEV 机器人优化的带有 14 个前导零的靓号地址。最常见的靓号地址类型是带有许多前导零的地址。

首先,什么是靓号地址(又称虚荣地址)?靓号地址是指用户刻意创建与其钱包或智能合约相关联的公共地址,也许它以 0x0000000 开头,也许以 0xdeadbeef 开头,也许是其他规律的地址。它们受欢迎的原因有几个:

1、Gas 优化:Wintermute 因为使用了一个有多个前导零的 EOA 地址而节省了 15000 美元。听起来很傻吗 ? 很多人都同意,但这就是 EVM 的工作方式 ! 如果你的地址中有很多零,那么交易 gas 费用就能下降。所以你如果用一个很多前导零的智能合约地址,当用户在和它交互时,他们会感到很开心,因为这可以为他们省钱。

靓号地址容易被盗?一文详解靓号地址安全部署方案

以太坊黄皮书描述了前导零地址如何可以实现更便宜的 gas

2、协议品牌。你知道 1inch Token合约是以 0x111111111 开头……的吗?

靓号地址容易被盗?一文详解靓号地址安全部署方案

1inch Token合约

3、多链重复性。在我看来,这是重中之重,也是为什么每个协议都应该为它们的部署使用一个靓号地址。你的应用可以存在于 15 条不同的 EVM 链上,并且在任何地方都具有相同的地址!这对开发者和用户来说不是更容易吗?

靓号地址容易被盗?一文详解靓号地址安全部署方案

那什么时候靓号地址是安全的?

以太坊地址有两种类型:外部拥有账户(EOA)和智能合约账户。如果你使用过像 MetaMask 这样的钱包,其中的每个地址都是一个 EOA,它是用来签署消息和进行交易处理的。将此与 Uniswap 合约等智能合约账户进行比较,人们可以与之交互,但它不能在不被触发的情况下采取自己的行动。总结一下,很简单,靓号地址对 EOA 账户来说是不安全的,但对于智能合约账户是安全的。

那为什么会是这样?我们将在下面进行更详细的解释,但这取决于靓号地址是如何生成的。对于 EOA 账户,你循环使用数百万个私钥,直到找到一个与美观的公共地址相对应的私钥。然而,私钥控制着 EOA 账户内的资金,因此,如果你用来遍历私钥的随机性遭到破坏,那么你的整个账户就会被毁了。另一方面,创建智能合约靓号地址只需要遍历公共种子,这些种子不授予智能合约的任何管理权限。

这就是为什么 Wintermute 会失败,而 OpenSea 却成功的原因——用不安全的软件在不安全的内存中生成私钥是不好的。但是以这种方式生成公共种子却非常好!所以 EOA 靓号地址是一条破产之路,而智能合约靓号地址是一条成功之路。

为什么协议需要靓号地址

更简单的文档!你可以在所有链上指向一个合约地址;

用户可验证!只有当且仅当字节码是逐字节匹配时,才会出现相同的合约地址;

开发者可验证!由于相同的合约地址只发生在完全匹配的情况下,因此你可以在部署脚本中捕捉到棘手的小修改;

更简单的集成!其他协议可以将你的合约地址硬编码到它们的多链代码中,而不必使用基于 chainId 的 if 语句。

注:我们即将深入研究一份详细说明手册。这是第一次把所有的部分放在一起,我们正在深入技术领域,目标受众是具有在链上部署智能合约经验的智能合约开发人员。如果你感兴趣,请继续阅读,但如果不适合你,请不要担心跟不上! 最后还有一个额外的技术挑战 ( 有奖励 )。

智能合约靓号地址

有一种方法可以生成 100% 安全的智能合约靓号地址,无论你使用哪种软件,迭代技术是否公开泄露都无关紧要。它被称为「CREATE2 工厂法」,这不仅提供了靓号地址,而且还是确保你在多条链上拥有相同合约部署地址的万无一失的方法。它还允许其他人无需信任地代表你部署代码,而无需任何私钥共享或 nonce 假设。

首先,快速概述一下如何选择智能合约地址。有两个部署选项,分别是CREATE和CREATE2。当你直接从 EOA 部署智能合约时,默认流程是 CREATE。该地址是通过将合约创建者地址与合约创建者 nonce 进行哈希运算来确定的。这个 nonce 是指一个地址发送了多少笔交易,所以一个新的钱包是从 0 开始,每次发送一笔新交易都会增加 1。以下是 CREATE 部署的智能合约地址的神奇公式:

new_address = hash(sender, nonce)

不太常见,但更有趣的,是使用 CREATE2 部署的智能合约地址,以下是其公式:

new_address = hash(0xFF, sender, salt, bytecode)

前者看起来更简单,对吧?但是,让我们举一个例子,说明与更健壮的 CREATE2 流程相比,这种简单性在哪些方面会产生不利影响。

Airy Alice:多链出了问题

想象一个名叫 Alice 的 crypto 开发者创建了两个智能合约:一个名为 GriddleSwap 的 Uniswap 分叉,以及一个名为 ph00ts 的 NFT 项目。它们都是不可变的独立原语,这意味着没有外部依赖或跨链桥风险。Alice 使用 nonce 0 将 GriddleSwap 部署到以太坊,然后使用 nonce 1 将 ph00ts 部署到以太坊。遗憾的是,Alice 的注意力持续时间很短,在将她的工作部署到第二大智能合约平台币安智能链(BSC)之前,她在加密推特上分心了几分钟。

靓号地址容易被盗?一文详解靓号地址安全部署方案

糟糕,搞砸了部署顺序!

但是等等! 她搞砸了部署顺序,在 GriddleSwap 之前部署了 ph00ts。由于智能合约地址仅依赖于创建者地址,并且在部署区块链中,以太坊 gridleswap 与 BSC ph00ts 具有完全相同的地址 ! 雪上加霜的是,以太坊 ph00ts 的地址与 BSC GriddleSwap 的地址相同。认为终端用户会感到困惑是一种保守的说法。事实上,它可能被恶意部署者滥用,以欺骗人们认为在链上的合约行为是相同的——这是一个公平的假设,给定相同的地址!

细心的 Alice :仍然会有问题

即使 Alice 在部署时很认真,并且从不混淆她的 nonce 顺序,还有其他的问题。如果 Alice 正确部署到以太坊和 BSC 上,但随后在 Polygon 上进行了一笔不相关的交易,则 nonce 0 已被用完。她永远不能在那里部署 GriddleSwap,因为她的 nonce 已经增加了。因此,必须不惜一切代价保护部署者私钥。如果 Alice 泄露了它,恶意破坏者就可以进行不相关的交易。如果 Alice 丢失了它,她也将失去在新链上再次部署到该地址的能力。这是一个永久性的漏洞,依赖于一个诚实的个人来保护私钥。 如果连比特币 core 开发者做不到,那我们其他人又该如何做到呢?

解决方案:CREATE2

值得庆幸的是,有一种更好的方法可以跨链获得一致的地址——不依赖于秘密私钥,不依赖于单个部署者,并且在整个过程中可以抵抗部署者的错误。请记住用于查找使用 CREATE2 部署的智能合约地址的公式:

new_address = hash(0xFF, sender, salt, bytecode)

第一个参数0xFF是一个可以忽略的常数值。第二个参数 (sender address) 可以通过在大多数 EVM 链中选择 z0age 的 CREATE2Factory 部署 0x0000000000FFe8B47B3e2130213B802212439497 来保持一致。第三个参数是一个用户选择的salt,我们可以用它来找到一个靓号地址,然后在链上保持不变。第四个是合约字节码,它可以作为一个有用的完整性检查,以确保我们在链上部署完全相同的功能。无论任何单个部署者做什么,所有四个参数都可以保持相同。

为什么这样更好呢? 与私钥不同,部署者选择的 salt 是可以被公开的! 知道 salt 可以部署合约,但对合约资产或功能的控制为零。因为它不绑定任何秘密信息,所以任何人都可以在不泄露或共享私钥的情况下将合约部署到新链上。字节码参数还确保当且仅当字节码相同时,这些新的无许可部署将具有相同的地址。因此,最终用户无需做详细的代码差异就能得到更强的保证。

有关更深入的概述,请参阅 OpenZeppelin 的科普文章‌。

创建你自己的靓号地址

认为以太坊合并后,工作量证明(PoW)就没用了吗?再想一想!同样的 GPU 功能,有助于为比特币区块寻找具有大量前导零的哈希原像,在为 EVM 智能合约找到具有大量前导零的哈希原像方面也非常出色。来自 OpenSea 的 z0age(这篇文章多亏了他的解释)找到了一个简单的设置来创建你自己的靓号地址。

1、使用 vast.ai‌启动一个 GPU 示例实例,每秒尝试约 20 亿次,成本约为 25 美分 / 小时:

Image:nvidia / opencl

GPU: 1x RTX 3090

需要分配的磁盘空间:1.83 GB

2、SSH 并安装 rust + create2crunch

sudo apt install build-essential -y; curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y; source "$HOME/.cargo/env"; git clone https://github.com/0age/create2crunch && cd create2crunch; sed -i 's/0x4/0x40/g' src/lib.rs

3、运行种子搜索。对于环境变量,INIT_CODE_HASH 是合约创建代码的 keccak256。可以在此处‌找到一个打印出来的样本代工测试 - 确保在消耗大量计算资源之前对其进行验证!LEADING 应该是你想要的前导零字节数,TOTAL 应该是你想要在合约地址中的总零字节数。

export FACTORY="0x0000000000ffe8b47b3e2130213b802212439497"; export CALLER="0x0000000000000000000000000000000000000000"; export INIT_CODE_HASH="0xabc...def"; export LEADING=5; export TOTAL=7; cargo run --release $FACTORY $CALLER $INIT_CODE_HASH 0 $LEADING $TOTAL

当 z0age 首次发布他的 repo 时,它能够在上述 vastAI 硬件上每秒运行 19 亿次尝试。从那时起,矢量化在某些 OpenGL 内核上变得疯狂起来,我观察到每秒有 21.5 亿次尝试。这意味着找到一个 5 前导零字节地址需要 256^5/(2150000000 * 60) ~= 8 分钟,找到一个 6 前导零字节地址需要 256^6/(2150000000 * 3600) ~ = 36 小时,找到一个 7 前导零字节地址需要 256^7/(2150000000 * 86400) ~= 387 天。请注意,一个字节等于两个十六进制字符,因此一个 5 前导字节的地址将有 10 个零。当然,这种搜索可以完全并行化,随着时间的推移,实际成功的概率将遵循泊松分布。

部署 CREATE2 工厂

精明的读者可能已经注意到,CREATE2 Factory 在所有链上都已经存在于 0x0000000000FFe8B47B3e2130213B802212439497。这有点像鸡生蛋还是蛋生鸡的问题,一致的地址部署如何依赖于一致的地址部署?

当我最初了解到这种方法时,我以为它只是一个由比我更聪明的人持有的私钥 ( 上面的「细心的 Alice」场景 )。但它实际上比这健壮得多! ENS 创始人 Nick Johnson 的「无密钥交易」方法利用了这样一个事实,即你可以从任何交易签名中恢复公共地址,而无需知道签署它的相应私钥。因此,可以创建一笔交易(「部署一个 create2 工厂」),然后为其发明一个伪造的签名,例如仅由 2 组成的签名。存在这个伪造签名的私钥,但没有人知道它是什么。但是我们可以恢复「无密钥签名」对应的公共地址,给它发送一些 ETH,然后将签名的交易提交给内存池。尽管这种方法很模糊,但它是一笔有效的交易,而且实际上是唯一可以从这个公共地址发出的有效交易。

结果呢 ? 任何人都可以在没有任何专有信息的情况下将 factory 部署到新的链上,同时防止恶意行为者造成损害。创建一个只能部署一个事务的单用途 EOA 是非常巧妙的技术。

靓号地址容易被盗?一文详解靓号地址安全部署方案

无密钥交易过程中创建的具体地址和合约

这可以通过三个简单的「forge cast」命令来完成。字节码太长,无法在此处复制,但你可以按照 https://github.com/ProjectOpenSea/seaport/blob/main/docs/Deployment.md‌ 上的说明,在你选择的任何链上无需许可地部署 CREATE2 Factory!

当然,如果已经部署过了,就没有必要再部署了。

旁注: EIP-155 要求是糟糕的

简短的尝试来支持我的 L1 治理冒险行为,请随意跳过。EIP-155‌ 是 Vitalik 于 2016 年提出的一项提案,其引入了「链 ID」的概念以防止重放攻击(replay attacks)。每条链都有自己的唯一标识符——以太坊是 1,BSC 是 56,Polygon 是 137——这些标识符将包含在已签名的交易中以防止重放攻击,它很快被以太坊采用,而其他所有 EVM 链都跟着采用了这个提案。这很好,但问题来了,当选择几条链,例如 Evmos 最近决定明确禁止 EIP-155 前的交易‌,在奇怪的理由下,它可以防止一个操作错误,即 Optimism 将 2000 万 OP Token发送到 Wintermute 不存在的一个多重签名(是的,又是他们)地址,声称拥有但从未初始化。但是,禁用 155 之前的交易会显著破坏一整套跨链部署,例如 CREATE2 factory 以及 Seaport 等领先项目。这些治理提案应该立即回滚,像这样的保护细节应该来自钱包而不是共识层。如果多链是未来,那么这些不必要的限制,将是顶级项目部署在你的区块链上的巨大障碍。

有趣的东西:部署赏金

今天 https://delegate.cash 部署在 7 个不同的 EVM 链(Ethereum、Polygon、Optimism、Celo、Avalanche、Fantom 和 Arbitrum)以及与这些链对应的 7 个测试网上。所有这些的合约地址都是相同的:0x00000000000076A84feF008CDAbe6409d2FE638B。

靓号地址容易被盗?一文详解靓号地址安全部署方案

那这就够了吗?不,我们需要更多的链。因为 delegatecash 是一个零依赖的独立原语,这意味着多链的风险实际上为零。这是纯粹的好处!因此,对于前 5 名将 delegatecash 智能合约部署并验证到新链和相应的测试网上的人,我将奖励 100 USDC 奖金!

你需要在此处使用开源存储库中的部署脚本。这可能需要部署 CREATE2 Factory(如果它不存在),并且不要忘记 Etherscan 验证!愉快部署,享受体验式学习!