通过一个Capture the Ether挑战(模糊身份)来说明CREATE2
的用法
2019年2月底,操作码[`create2`](https://learnblockchain.cn/article/1297)被添加到以太坊虚拟机。这段操作码引入了第二种计算新智能合约地址的方法(以前只有`CREATE`可用)。使用`CREATE2`当然比最初的`CREATE`更复杂。不再仅仅写`new Token()`就行了,而必须要编写汇编代码。
> 译者注:现在可以不用编写汇编, solidity 0.8之后, 可以通过指定slat 来使用 create2 ,例如: `new Token{salt: bytes32}()`
但是`create2`有一个重要的属性,在某些特定情况下优点更突出:它不依赖于部署地址的当前状态。这意味着可以确保今天计算的合约地址与一年后相同。这一点很重要,因为在智能合约部署到该地址之前,你可以与该地址交互,并向其发送ETH。
由于网上相关的案例很少,我想写一篇简单的博客来解释一下:
1. `CREATE`和`CREATE2`都是怎么工作的
2. 怎样在智能合约中使用`CREATE2`
3. 我怎样用它来完成一个[Capture The Ether](https://capturetheether.com/?ref=hackernoon.com)挑战
## CREATE 操作码
这是部署合约默认的操作码,所部署合约的地址是通过哈希计算得来的:
1. 部署地址
2. 之前在该地址部署的交易数 — `nonce`
“`
keccak256(rlp.encode(deployingAddress, nonce))[12:]
“`
## [CREATE2](https://learnblockchain.cn/article/1297) 操作码
这个操作码本质上是另一种部署智能合约的方法,只是在计算新的合约地址时不一样。它会用到:
1. 部署地址
2. 部署合约代码字节码的哈希
3. 创建者提供的随机的`salt` (32 字节字符串).
“`
keccak256(0xff ++ deployingAddr ++ salt ++ keccak256(bytecode))[12:]
“`
## 挑战: 创建一个特定地址的合约
作为使用`CREATE2`的例子,我将完成[Capture the Ether中的模糊身份](https://capturetheether.com/challenges/accounts/fuzzy-identity/?ref=hackernoon.com)挑战。要完成挑战中任务,需要创建一个具备下面两个属性的合约:
1. 有一个会返回`bytes32(“smarx”)`的`name()`函数
2. 在地址里包含字符串`badc0de`
第一点很容易实现。第二步就是挑战的地方,要完成它,我们需要用到以太坊如何计算合约地址的知识——前面刚刚讨论过!
## 用 CREATE 来解决?
如果用`CREATE`操作码来完成这个挑战,我们需要生成许多私钥,对于每个私钥,我们需要计算相应的以太坊地址,例如:用一个`0`nonce来计算出合约地址。?
## 用 CREATE2 来解决?
只需要用一个以太坊地址,我们可以遍历不同的salt值,直到找到一个有效值。相比生成数十万的私钥,这看起来是一个不错的选择。
Capture the Ether 创建于2018年,`CREATE2`当然不是这个问题预期的解决方案,但我觉得它是更优的选择。
## 解决方案
要用`CREATE2`找到包含`badc0de`的地址,我们需要:
1. 要部署的合约的字节码
2. 部署合约的地址 (用`CREATE2`的合约)
3. salt — 我们将通过计算得出.
## 第一步: 要部署的合约的字节码
第一步是要获取到我们要部署在包含`badc0de`的地址的合约的字节码。通过这个[挑战](https://capturetheether.com/challenges/accounts/fuzzy-identity/?ref=hackernoon.com)的合约很简单,如下:
“`solidity
pragma solidity ^0.5.12;
contract BadCodeSmarx is IName {
function callAuthenticate(address _challenge) public {
FuzzyIdentityChallenge(_challenge).authenticate();
}
function name() external view returns (bytes32) {
return bytes32(“smarx”);
}
}
“`
运行 `truffle compile`, 就可以在`/build/BadCodeSmarx.json`找到字节码:
“`
“bytecode”: “0x608060405234801561001057600080fd5b506101468061002…”
“`
或者用[Remix](https://remix.ethereum.org/?ref=hackernoon.com)代替Truffle也可以。
## 第二步: 用 CREATE2 的合约
现在我们可以定义一个简单的合约,给他一个salt,并且用`CREATE2`来部署字节码:
“`solidity
contract Deployer {
bytes contractBytecode = hex”608060405234801561001057600080fd5b5061015d806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806306fdde031461003b5780637872ab4914610059575b600080fd5b61004361009d565b6040518082815260200191505060405180910390f35b61009b6004803603602081101561006f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506100c5565b005b60007f736d617278000000000000000000000000000000000000000000000000000000905090565b8073ffffffffffffffffffffffffffffffffffffffff1663380c7a676040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561010d57600080fd5b505af1158015610121573d6000803e3d6000fd5b505050505056fea265627a7a72315820fb2fc7a07f0eebf799c680bb1526641d2d905c19393adf340a04e48c9b527de964736f6c634300050c0032″;
function deploy(bytes32 salt) public {
bytes memory bytecode = contractBytecode;
address addr;
assembly {
addr := create2(0, add(bytecode, 0x20), mload(bytecode), salt)
}
}
}
“`
在新的 solidity 里可以使用:
“`solidity
function deploy(bytes32 _salt) public returns (address) {
BadCodeSmarx c = new BadCodeSmarx{salt: _salt}();
return address(c);
}
“`
在Solidity中,`create2()`有4个参数:
– 1: 发送给新合约的wei数(`msg.value`), 本例中是 0 .
– 2–3: 字节码在内存中的位置
– 4: salt — 在第三步计算。我们将它作为参数,可以在计算后再提供
`create2()`返回创建合约的地址——无论你是否想用它,你都必须在变量中捕获地址。
现在,合约已经准备好了。我将它部署到Ropsten测试网: [0xca4dfd86a86c48c5d9c228bedbeb7f218a29c94b](https://ropsten.etherscan.io/address/0xca4dfd86a86c48c5d9c228bedbeb7f218a29c94b?ref=hackernoon.com). 现在我们知道了将要部署`BadCodeSmarx`合约的地址,并且我们已经有字节码了,我们需要做的是计算一个可以使得地址包含`badc0de`的salt值。
## 第三部: 计算 salt
为了找到一个可以让地址包含`badc0de`的salt值,我们需要一个简单的脚本来遍历每一个salt,并计算出用每个salt可生成的地址。
为了确保脚本计算出的地址正确,我用salt`0x00…001`部署了一个合约。然后我用这个合约地址来验证脚本是否正确格式化和哈希参数,从而产生与`CREATE2`在链上相同的地址。
注意,地址创建的公式如下,其中`[12:]`表示删除前12个字节(以便查找地址)。
“`
keccak256(0xff ++ deployingAddr ++ salt ++ keccak256(bytecode))[12:]
“`
下面是我用的脚本,我用了`ethereumjs-util`包来执行keccak256哈希——你可以在[Github](https://github.com/ethereumjs/ethereumjs-util?ref=hackernoon.com)上找到它。
“`javascript
const eth = require(‘ethereumjs-util’)
// 0xff ++ deployingAddress is fixed:
var string1 = ‘0xffca4dfd86a86c48c5d9c228bedbeb7f218a29c94b’
// Hash of the bytecode is fixed. Calculated with eth.keccak256():
var string2 = ‘4670da3f633e838c2746ca61c370ba3dbd257b86b28b78449f4185480e2aba51’
// In each loop, i is the value of the salt we are checking
for (var i = 0; i < 72057594037927936; i++) {
// 1. Convert i to hex, and it pad to 32 bytes:
var saltToBytes = i.toString(16).padStart(64, ‘0’)
// 2. Concatenate this between the other 2 strings
var concatString = string1.concat(saltToBytes).concat(string2)
// 3. Hash the resulting string
var hashed = eth.bufferToHex(eth.keccak256(concatString))
// 4. Remove leading 0x and 12 bytes
// 5. Check if the result contains badc0de
if (hashed.substr(26).includes(‘badc0de’)) {
console.log(saltToBytes)
break
}
}
“`
运行脚本,不到30秒,我的salt值结果出来了:
“`
0x00000000000000000000000000000000000000000000000000000000005b2bfe
“`
然后我要做的就是执行 `Deployer.deploy(0x00…005b2bfe)`. 看,实例`BadCodeSmarx`部署在了地址:
`0xa905a3922a4ebfbc7d257cecdb1df04a3badc0de`
## 引用:
1. [EIP1014 — Skinny CREATE2](https://eips.ethereum.org/EIPS/eip-1014?ref=hackernoon.com)
2. [Capture the Ether — Fuzzy Identity](https://capturetheether.com/challenges/accounts/fuzzy-identity/?ref=hackernoon.com)
3. [Truffle](https://www.trufflesuite.com/?ref=hackernoon.com)
4. [ethereumjs-util](https://github.com/ethereumjs/ethereumjs-util?ref=hackernoon.com)
> 原文链接:https://hackernoon.com/using-ethereums-create2-nw2137q7
2019年2月底,操作码create2
被添加到以太坊虚拟机。这段操作码引入了第二种计算新智能合约地址的方法(以前只有CREATE
可用)。使用CREATE2
当然比最初的CREATE
更复杂。不再仅仅写new Token()
就行了,而必须要编写汇编代码。
译者注:现在可以不用编写汇编, solidity 0.8之后, 可以通过指定slat 来使用 create2 ,例如:
new Token{salt: bytes32}()
但是create2
有一个重要的属性,在某些特定情况下优点更突出:它不依赖于部署地址的当前状态。这意味着可以确保今天计算的合约地址与一年后相同。这一点很重要,因为在智能合约部署到该地址之前,你可以与该地址交互,并向其发送ETH。
由于网上相关的案例很少,我想写一篇简单的博客来解释一下:
CREATE
和CREATE2
都是怎么工作的- 怎样在智能合约中使用
CREATE2
- 我怎样用它来完成一个Capture The Ether挑战
CREATE 操作码
这是部署合约默认的操作码,所部署合约的地址是通过哈希计算得来的:
- 部署地址
- 之前在该地址部署的交易数 —
nonce
keccak256(rlp.encode(deployingAddress, nonce))[12:]
CREATE2 操作码
这个操作码本质上是另一种部署智能合约的方法,只是在计算新的合约地址时不一样。它会用到:
- 部署地址
- 部署合约代码字节码的哈希
- 创建者提供的随机的
salt
(32 字节字符串).
keccak256(0xff ++ deployingAddr ++ salt ++ keccak256(bytecode))[12:]
挑战: 创建一个特定地址的合约
作为使用CREATE2
的例子,我将完成Capture the Ether中的模糊身份挑战。要完成挑战中任务,需要创建一个具备下面两个属性的合约:
- 有一个会返回
bytes32("smarx")
的name()
函数 - 在地址里包含字符串
badc0de
第一点很容易实现。第二步就是挑战的地方,要完成它,我们需要用到以太坊如何计算合约地址的知识——前面刚刚讨论过!
用 CREATE 来解决?
如果用CREATE
操作码来完成这个挑战,我们需要生成许多私钥,对于每个私钥,我们需要计算相应的以太坊地址,例如:用一个0
nonce来计算出合约地址。?
用 CREATE2 来解决?
只需要用一个以太坊地址,我们可以遍历不同的salt值,直到找到一个有效值。相比生成数十万的私钥,这看起来是一个不错的选择。
Capture the Ether 创建于2018年,CREATE2
当然不是这个问题预期的解决方案,但我觉得它是更优的选择。
解决方案
要用CREATE2
找到包含badc0de
的地址,我们需要:
- 要部署的合约的字节码
- 部署合约的地址 (用
CREATE2
的合约) - salt — 我们将通过计算得出.
第一步: 要部署的合约的字节码
第一步是要获取到我们要部署在包含badc0de
的地址的合约的字节码。通过这个挑战的合约很简单,如下:
pragma solidity ^0.5.12;
contract BadCodeSmarx is IName {
function callAuthenticate(address _challenge) public {
FuzzyIdentityChallenge(_challenge).authenticate();
}
function name() external view returns (bytes32) {
return bytes32("smarx");
}
}
运行 truffle compile
, 就可以在/build/BadCodeSmarx.json
找到字节码:
"bytecode": "0x608060405234801561001057600080fd5b506101468061002..."
或者用Remix代替Truffle也可以。
第二步: 用 CREATE2 的合约
现在我们可以定义一个简单的合约,给他一个salt,并且用CREATE2
来部署字节码:
contract Deployer {
bytes contractBytecode = hex"608060405234801561001057600080fd5b5061015d806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806306fdde031461003b5780637872ab4914610059575b600080fd5b61004361009d565b6040518082815260200191505060405180910390f35b61009b6004803603602081101561006f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506100c5565b005b60007f736d617278000000000000000000000000000000000000000000000000000000905090565b8073ffffffffffffffffffffffffffffffffffffffff1663380c7a676040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561010d57600080fd5b505af1158015610121573d6000803e3d6000fd5b505050505056fea265627a7a72315820fb2fc7a07f0eebf799c680bb1526641d2d905c19393adf340a04e48c9b527de964736f6c634300050c0032";
function deploy(bytes32 salt) public {
bytes memory bytecode = contractBytecode;
address addr;
assembly {
addr := create2(0, add(bytecode, 0x20), mload(bytecode), salt)
}
}
}
在新的 solidity 里可以使用:
function deploy(bytes32 _salt) public returns (address) {
BadCodeSmarx c = new BadCodeSmarx{salt: _salt}();
return address(c);
}
在Solidity中,create2()
有4个参数:
- 1: 发送给新合约的wei数(
msg.value
), 本例中是 0 . - 2–3: 字节码在内存中的位置
- 4: salt — 在第三步计算。我们将它作为参数,可以在计算后再提供
create2()
返回创建合约的地址——无论你是否想用它,你都必须在变量中捕获地址。
现在,合约已经准备好了。我将它部署到Ropsten测试网: 0xca4dfd86a86c48c5d9c228bedbeb7f218a29c94b. 现在我们知道了将要部署BadCodeSmarx
合约的地址,并且我们已经有字节码了,我们需要做的是计算一个可以使得地址包含badc0de
的salt值。
第三部: 计算 salt
为了找到一个可以让地址包含badc0de
的salt值,我们需要一个简单的脚本来遍历每一个salt,并计算出用每个salt可生成的地址。
为了确保脚本计算出的地址正确,我用salt0x00...001
部署了一个合约。然后我用这个合约地址来验证脚本是否正确格式化和哈希参数,从而产生与CREATE2
在链上相同的地址。
注意,地址创建的公式如下,其中[12:]
表示删除前12个字节(以便查找地址)。
keccak256(0xff ++ deployingAddr ++ salt ++ keccak256(bytecode))[12:]
下面是我用的脚本,我用了ethereumjs-util
包来执行keccak256哈希——你可以在Github上找到它。
const eth = require('ethereumjs-util')
// 0xff ++ deployingAddress is fixed:
var string1 = '0xffca4dfd86a86c48c5d9c228bedbeb7f218a29c94b'
// Hash of the bytecode is fixed. Calculated with eth.keccak256():
var string2 = '4670da3f633e838c2746ca61c370ba3dbd257b86b28b78449f4185480e2aba51'
// In each loop, i is the value of the salt we are checking
for (var i = 0; i < 72057594037927936; i++) {
// 1. Convert i to hex, and it pad to 32 bytes:
var saltToBytes = i.toString(16).padStart(64, '0')
// 2. Concatenate this between the other 2 strings
var concatString = string1.concat(saltToBytes).concat(string2)
// 3. Hash the resulting string
var hashed = eth.bufferToHex(eth.keccak256(concatString))
// 4. Remove leading 0x and 12 bytes
// 5. Check if the result contains badc0de
if (hashed.substr(26).includes('badc0de')) {
console.log(saltToBytes)
break
}
}
运行脚本,不到30秒,我的salt值结果出来了:
0x00000000000000000000000000000000000000000000000000000000005b2bfe
然后我要做的就是执行 Deployer.deploy(0x00...005b2bfe)
. 看,实例BadCodeSmarx
部署在了地址:
0xa905a3922a4ebfbc7d257cecdb1df04a3badc0de
引用:
- EIP1014 — Skinny CREATE2
- Capture the Ether — Fuzzy Identity
- Truffle
- ethereumjs-util
原文链接:https://hackernoon.com/using-ethereums-create2-nw2137q7
本文参与区块链开发网写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。
- 发表于 2022-06-17 08:35
- 阅读 ( 786 )
- 学分 ( 10 )
- 分类:以太坊