读之前先看
本文原文是 Robert Miller 的一次关于私钥恢复风险的实验,并用实际行动证明了以太坊中存在特定的机器人在监控对应的交易。以下为 yudan 在通读原文的基础上进行的翻译,并在原文的基础上进行了一些修饰。
以下为原文翻译:
上周,一个以太坊黑暗森林中的怪物向我显现出了原型。他们花了16小时来发现我放下的“密码学诱饵“。这个诱饵是我从tux(twitter:@__tux)
那边听说到的。能看到这个结果是意料之中的,但是当我刷新 Etherscan
并看到我的所有 ETH
都消失的时候,这个结果还是刷新了我的三观。
这个怪兽在观察所有在以太坊上所有交易创建中一个非常隐蔽的错误:在签名过程中使用一个对某个数字进行重复使用
。我一直在寻找这个怪物,并且放置诱饵,直到我从野外
发现他,并且发现一些难以解释的踪迹。为了弄清这个机器人是怎么工作的,我们需要从对 ECDSA
和数字签名开始讲起。
ECDSA
目前支撑起两大加密货币的(比特币&以太坊
)加密算法是椭圆曲线加密算法(elliptic curve digital signature algorithm),或者我们可以称为ECDSA
。顾名思义,ECDSA
是用来生成数字签名的。这些生成签名是用来验证我们的身份,证明我们拥有这些资产。每个数字签名会对以下两件事情进行证明:
-
你拥有一个很秘密的东西 —
私钥
,每个私钥都和一个公钥进行绑定,公钥是公开的,你的数字身份(地址)就是一个公钥 -
你在用你的私钥来签名一个特定的消息,在这个例子中,消息本身就是我们所说的交易数据
ECDSA
的运作原理是因为你可以轻而易举的使用你的私钥来生成公钥,但是你不能使用公钥来反推私钥。但是,在某些特定的场景下,你可以使用私钥产生的签名来反推出私钥。这里涉及到一些技术细节,但别担心,跟着我的脚步来。
要生成一个 ECDSA
的签名,我们需要到私钥 d
,一个随机的数字 k
,和对应要签名的消息 h
的哈希。将这些数据和与私钥 d
相关联的公钥 Q
,以及由 ECDSA
算法定义的数字 G
和 n
进行组合,就可以根据以下的算法算出对应的加密签名的r
和s
。
r
和 s
结合在一起就形成了数字签名
nonce 是什么
ERCSA
签名中的随机数 k
是一个至关重要的值。这个值永远不应该告诉别人或揭露出来,并且这个值只能使用一次。这就是为什么这个随机数 k
称为 nonce
的原因并且在本文的后续中,我将会使用 nonce
来替代 k
在我所有提到 k
的地方。现在,如果一个攻击者知道你在签名时所使用的 nonce
是什么,那么,他就可以通过你签名的恢复出你的私钥。经过一些代数转换,下面的公式可以通过上文给出的公式推导出来:
简单来说,如果 nonce
在不同的签名中被重复使用的话,用于生成这些签名的私钥就可以就可以被恢复出来。同样的,结合上文提到的公式,通过一些代数转换,我们可以使用以下的等式来推导出所使用的随机数 nonce
:
有了这个随机数之后,我们就可以通过上面的公式来反推出私钥了。
所以,我们如何确定我们重复使用了同一个随机数 nonce
呢?
重新来看 ECDSA
算法中用于生成 r
的算法 r = k * G mod n
。在给定 G
和 n
的情况下,在生成签名的过程中,所剩下的变量只有随机数 k
。作为结果,如果 k
在被重复使用的话。那么生成出来的签名就会拥有相同的 r
值,这就是为什么 k
不能被重复使用的原因。
在了解完这个之后,我们就知道如何筛选以太坊中使用相同的 nonce
进行签名的交易 — 两个来自同一个地址的交易并且拥有相同的 r 值,但拥有不同的 s 值
。我听闻tux(twitter:@__tux
说以太坊上有专门的机器人来捕获这些错误。并且在一次黑客松中得到了我的亲眼见证。
简单说明下,普通的要哦那个户不需要担心这类的攻击向量,因为你没有可能会重用随机数 nonce
,并且你的 nonce
也不会被公布出来。这是一些开发加密算法工具包的开发者应该考虑的事情。
创建一个“密码学”诱饵
我的计划很简单,为了把这个黑暗森林中的生物引诱出来,我将会发送两笔具有相同 r
值的交易,并且在对应的账户地址中放置一些 ETH 作为诱饵。如果有人在监控这些交易,那么他们就能恢复出我的私钥,并且拿走我的 ETH。
(在一开始的时候,我原本是打算使用比特币的交易来构造这个诱饵,但是由于在比特币方面我是新手,我不是很了解那些工具的用法,所以我失败了。)
为了制造一个重复使用 nonce
的诱饵,我必须强制我的交易使用一个相同的数字两次,幸运的是这件事并不难办到。目前来看不能用 MetaMask
来达成这样的操作。但是随着对一个很出名的 web3 工具包 — ethers.js
的深入了解,我发现有一个看起来是用于处理椭圆曲线算法的库文件。
var drbg = new HmacDRBG({
hash: this.hash,
entropy: bkey,
nonce: 1, // changed from "nonce"
pers: options.pers,
persEnc: options.persEnc || 'utf8',
});
(声明:别在你家尝试这种危险操作)
我把 nonce
设置成了 1!然后,我使用一个新的私钥并且在里面放置了 0.04 ETH。然后我写了一个简单的脚本来发送 ETH 给自己。
const ethers = require("ethers")
async function main(){
const privateKey = "not-leaking-it-this-way"
let wallet = new ethers.Wallet(privateKey)
console.log("Using wallet:", wallet.address)
const provider = new ethers.providers.JsonRpcProvider("rpc-endpoint")
let signer = wallet.connect(provider)
const tx = await signer.sendTransaction({
to: wallet.address,
value: ethers.utils.parseEther("0").toString(),
gasLimit: 45000,
})
console.log(tx)
}
main()
我把这个脚本运行了两次,并且立即发送了两笔交易上链。由于我的 nonce
设置成了 1,所以这两笔交易应该要拥有相同的 r
值,但拥有不同的 s
值。我们用一个简单的脚本来确认下:
robert@mbp nonce-reuse-bait % node get_r_s.js
Transaction 1
r: 0x340709f674d030dda4aa8794bffb578030870bdfe583e7f41aea136a7ca1ed94
s: 0x3e5b92d8c5a2a033c9e5eb53ff2946681b28ab82fa5b17d0a9c48d139e78fe2c
Transaction 2
r: 0x340709f674d030dda4aa8794bffb578030870bdfe583e7f41aea136a7ca1ed94
s: 0x2c17cf960d1af7602f875afc56d01eddcc67bb03e962877444ed8d4c1287ab7a
现在,这两笔已经上链的交易可以让任何在监控的人得到在背后的私钥了,诱饵已经放置完毕。
深渊一瞥
在发送了我的第二笔交易后,我立马刷新了 Etherscan
中诱饵账号的显示页面,但是没有任何事情发生。我的钱还是在那里。在几个小时后,我又刷新了一遍,还是没有任何变化,我甚至在怀疑是不是我在什么步骤做错了。
在第二天早上的时候,我重新刷新了页面,我们钱已经没有了!在16个小时之后,一笔交易把我账号中的 0.04 ETH 发送到了一个不知道是哪里的地址。
这只怪兽终于抬起了他的头,它在监控所有拥有相同r
值的以太坊交易,并且拿走他们的钱。我比较疑惑的是它用了相当长的时间才拿走我的钱。但无论如何,这证明完全是可以以这种程序化的形式来获取账户余额的。但是为什么他们不立即提取所有资产呢?目前可能的解释是他们在等他们有没有可能可以从我的账号中拿到更多的资产。尽管我对我这次成功的经历非常满意,但是如果下次我再干这种泄漏私钥的事情,我就会觉得自己是一个傻逼了。
看了下这个地址,可以看到这个机器人还从其他人的账户中拿钱。
目前这个账号有源源不断的以太坊从不同的账号中提取 ETH
,唯一一次它使用 ETH
是为了支付其中的三笔 ERC20
代币转移的交易的手续费。目前这个账号中一共有大概 3700 美元的资金。
为了观察其他重用 ECDSA
算法中 nonce
的交易,我写了一个脚本来快速检查一个账号是否多次使用了相同的 r
值。我把第二个受害者地址代入到我的脚本中。并且发现这个账户多次使用了同一个 r
值
robert@mbp nonce-reuse-bait % node get-tx-history.js
Same r found!
r: 0xf0d7b10f398357f7d140ff2be1bea9165d32238360ad0f82911235868be7c6e1
hash: 0xb5d2454d7380bfa7ac75ec76f15eecb56e60941429153081fe799fb53a7ff901
r: 0xf0d7b10f398357f7d140ff2be1bea9165d32238360ad0f82911235868be7c6e1
hash: 0x9e459be7fa9950835a3c2594d3440c684fed05fa8e12e8088cc7776c4afb364c
Same r found!
r: 0x41d43fd626c24e449ac54257eeff271edb438bbabbc9bee3d60a5bd78dc39d6d
hash: 0x670f66ff71882ae35436cd399adf57805745177b465fdb44a60b31b7c32e4d16
r: 0x41d43fd626c24e449ac54257eeff271edb438bbabbc9bee3d60a5bd78dc39d6d
hash: 0x374180005946ef3b1906ee1677f85fa62eb5a834aa0241b4c9c74174bca26a07
这坚定了我认为该机器人就是在监控以太坊上所有使用了相同随机数 nonce
的交易的信念。有趣的是,这个用户有两个单独不同的例子,在每次例子的两个交易中重用了同一个随机数。同样的,这个黑暗森林中的生物也是以一种难以置信的缓慢速度来转移走这个受害者的资金,尽管这个受害者已经犯下了难以弥补的错误。
在这之后,我又同样再看了两个地址,令我惊讶的是,我发现这些账号从来没有重复使用他们 ECDSA
算法中的 nonce
!在一番调查后,我发现一共有20个不同的账号都把自己的 ETH 发送给了这个攻击者。但只有9个账号是重复使用了他们的 nonce
那另外11个账号是什么原因呢?攻击者是怎么转走他们的钱的呢?我不是很确定,一个可能的答案是这个生物还用了其他的策略来回复私钥,比方说检查账户是否使用一些常见的单词,短语或者数字来组成他们的私钥。还有更复杂的方法来针对一些使用不良随机数生成的攻击。但无论如何,这只是一种猜测,且我的发现中没有能作证这些观点的答案。
黑暗森林的生物可能已经显露原形。但它是什么或它接下来会袭击哪里仍然是个谜。
原文始发于微信公众号(蛋蛋的区块链笔记):深渊一瞥 — 追寻以太坊黑暗森林中的生物