LOADING...
LOADING...
LOADING...
当前位置: 玩币族首页 > 行情分析 > 硬核分析: tBTC 5月18日存款暂停原因与解决途径追溯

硬核分析: tBTC 5月18日存款暂停原因与解决途径追溯

2020-05-29 洁sir 来源:区块链网络

在2020年5月18日上午,Keep团队在以太坊和比特币主网上进行了大约48小时的测试之后,发现在使用某些类型的比特币地址进行赎回的存款合约赎回流程中存在一个重大问题,问题触发了TBTC系统合约允许的10天紧急存款暂停。

实际上,暂停后不久,团队提供了1 BTC交换来恢复TBTC的供应,从而使该地址的供应恢复了99.83%。该团队将触发对公开存款的有控制赎回,以释放负责支持这些存款的单一债券持有人的剩余债券。该团队还将协调除去系统中任何未使用的ETH,尽管它们没有危险。

发生了什么

首先,为不熟悉tBTC系统的人提供一些设置。在tBTC中,有权访问BTC(存款人)的人可以在以太坊链上开设tBTC存款。这笔存款是一个智能合约,可与一个单独网络中的3个签名者进行交互,这些签名者共同生成和控制一个比特币钱包,以使单个签名者都无法访问该钱包。当打开存款时,存款人会选择几种可用钱包,一旦存款存到了钱包,就将那个数量的BTC发送到其比特币地址。然后,存款人向存款智能合约提交BTC转移已经发生的证据,并能够铸造等量的TBTC(以太坊ERC-20代币)。这允许BTC的持有者进入tBTC系统,然后使用其TBTC代币余额与支持该代币的智能合约进行交互。


为了正确地证明以太坊链上的比特币交易,采用了一个中继,将有关比特币区块的足够数据传达给以太坊智能合约,以确认比特币交易已经积累了一定数量的确认信息。这用于确保交易(a)存在于比特币链上,并且(b)被充分确认有合理的确定性,即比特币链的叉子将无法将其删除。


为了确保控制钱包的签名者不能以未经授权的方式非法转移他们共同持有的BTC,他们必须提供相当于ETH BTC价值150%的保证金。该债券由存款智能合约持有,直到赎回存款为止。

当以太坊上的TBTC持有人有兴趣获取BTC时,他们可以通过称为赎回的流程来撤销此转移。赎回允许以太坊用户(赎回者)支付手续费加上少量签字人费用,指定比特币地址,并授权3个签字人共同产生一个签名,该签名完成将比特币从存款转移到指定地址的比特币交易。这使TBTC的持有者可以退出tBTC系统回到比特币链,并将签名者的债券返还到各自的可用资金池中以支持新的存款。签署人将赎回人支付的签署人费用分开。

事件时间表


我们认为该事件是在部署tBTC时开始的,该过程于2020年3月15日世界标准时间(UTC)完成,并创建了tBTC系统的分类池。分类池本身的部署没有使任何资金面临风险,但是分类池用于随机选择签名者,这些签名者具有足够的担保来支持新的存款。签名者必须通过将ETH放入绑定合约中来选择使用分类池。然后,该合约需要签署人的额外授权,以使他们投入债券合约的资金能够用于tBTC系统使用的特定分类池。最后,签名人必须在分类池中注册自己,以填充在存款开放期间用于选择签名者的数据。


在接下来的三天左右的时间里,几个签名人提供了保证金并授权了分类池,但是只有3个签名人在池中注册以在存款开放期间使用,这3个签名人由一个人控制,经过全面设置以帮助团队,我们在这段时间内测试了存款和赎回。


存款可通过https://dapp.tbtc.network/上的alpha dApp广泛获得,限制为0.001 BTC存款大小。由三个可操作的签署者提供的ETH债券,也对可以铸造多少TBTC设置了上限,因为每个TBTC存款都需要以其ETH价值的1.5倍进行债券交易。在调查潜在问题时,dApp在2020年5月15日晚上短暂撤下,但一旦了解到该问题,便会在5月16日重新启用。此外,社区中的几位成员设置了dApp的本地版本,并使用它们来开设大于0.001 BTC批量的存款。


5月18日世界标准时间2:29,控制这3个签名者的运营商试图赎回他们已开立的存款,但无法完成赎回过程。他们联系了一个团队成员,后者注意到另一个团队成员的较早通信,表明以太坊链上的高gas导致中继站对比特币状态的更新落后了几个街区。我们告知操作员这很可能是他所看到的问题,继电器开始使用更高的gas价格,并赶在世界标准时间3:07。

在世界标准时间3:13,操作员标记他们仍在兑换时遇到问题,我们使用了本地版本的dApp来调查问题,这时我们观察到存款合同有错误,“ Tx将价值发送给错误的pubkeyhash ”。该错误表明dApp正在构建的证明以太坊链已成功完成兑换的证明不正确;特别是,证明未能成功证明交易已将存款的BTC持有量发送到正确的赎回者地址。


经过30分钟的调查,我们怀疑该问题不一定在dApp的客户证明中,而是在赎回时使用的特定比特币地址及其在以太坊链中的可证明性。在证实这种怀疑之前,通知Matt Luongo确保触发紧急停顿所需的3个人中有2个人准备就绪,以防万一。然后,我们调查了智能合约中该问题的含义,并确定了潜在的签署人债券危险。由于签署人的保证金只能在6小时后才能扣押,而没有赎回证明,因此决定继续调查并确认合同问题,然后再采取进一步行动。


在世界标准时间4:43,Matt通知了James Prestwich,他具有丰富的比特币经验,撰写了许多tBTC合约的第一版,并且是用于验证以太坊链上的比特币交易的bitcoin-spv和中继库的作者。 James在世界标准时间5:02证实了这一发现,那时我们立即开始将托管的dApp URL重定向到tBTC主页,以防止进一步的新存款被打开。

在世界标准时间5:18,在确认问题并确认合约之外无法解决问题后,决定触发tBTC系统合约中提供的一次性使用10天紧急停顿。此功能可防止新的存款打开10天,但不会影响任何已经打开的存款功能。为了安全起见,触发任何tBTC系统更新的过程需要技术钱包团队的3名成员中的2名手工制作以太坊交易,然后使用隔离机对交易信息进行签名,最后将交易和签名提交给以太坊链。该过程已在世界标准时间5:45完成。


第二天早上(东部时间),我们意识到,尽管托管dApp的登录页面已重定向到tBTC主页,但托管dApp上的其他页面,例如特定的存款页面却没有。不是冒着任何现有存款无意间触发赎回漏洞的风险,而是将托管的dApp页面的其余部分在UTC时间14:11重定向到tBTC主页。

技术问题说明


该问题本身源于证明赎回交易实际上已在比特币链上进行的过程。在正常情况下,为比特币交易提供有效签名的签名者可能会立即释放其债券,而由赎回者负责在比特币链上广播该交易。但是,如果tBTC系统在此阶段将签署者从其经济义务中解除,则签署者将有机会产生不同的签名,以进行将资金发送到赎回者地址以外的地址,并在赎回者收到之前进行广播的交易。有机会播报自己的交易。这样,一旦签名人产生了有效的签名并证明交易在比特币链上被接受,tBTC系统便会释放签名人债券。


赎回交易已在比特币链上得到充分确认的证据应用了一些健全性检查;其中之一是验证比特币交易是否将签名者共同控制的资金发送到所请求的兑换地址。这些检查由redemptionTransactionChecks函数执行:

function redemptionTransactionChecks(
DepositUtils.Deposit storage _d,
bytes memory _txInputVector,
bytes memory _txOutputVector
) public view returns (uint256) {
require(
_txInputVector.validateVin(),
“invalid input vector provided”
);
require(
_txOutputVector.validateVout(),
“invalid output vector provided”
);
bytes memory _input =
_txInputVector.slice(1, _txInputVector.length-1);
bytes memory _output =
_txOutputVector.slice(1, _txOutputVector.length-1);
require(
keccak256(_input.extractOutpoint()) ==
keccak256(_d.utxoOutpoint),
“Tx spends the wrong UTXO”
);
require(
keccak256(_output.slice(8, 3).concat(_output.extractHash())) ==
keccak256(abi.encodePacked(_d.redeemerOutputScript)),
“Tx sends value to wrong pubkeyhash”
);
return (uint256(_output.extractValue()));
}

我们观察到的错误是在最后一次检查中,“ Tx将值发送到错误的pubkeyhash”:

require(
keccak256(_output.slice(8, 3).concat(_output.extractHash())) ==
keccak256(abi.encodePacked(_d.redeemerOutputScript)),
“Tx sends value to wrong pubkeyhash”
);

比特币有几种类型的输出脚本。最常见的种类-付给pubkeyhash(p2pkh),付给scripthash(p2sh),付钱给见证pubkeyhash(p2wpkh)和付钱给见证者scripthash(p2wsh)-可以被编码为地址。我们将这些称为标准输出类型。该地址表示20或32字节的哈希,校验和以及有关输出脚本类型的信息。类型信息用于将哈希插入标准模板。这将创建相应的输出脚本。


输出脚本的长度不同。结果,所有输出脚本都以1个字节为前缀,以字节为单位对脚本的长度进行编码。例如,一个标准的p2pkh输出脚本的长度为25个字节,或者26个字节(以其长度前缀计)。输出的值表示为8字节的little-endian整数,在输出脚本之前立即序列化。因此,标准输出的长度在(8 +1 + 22 =)31到(8 +1 + 34 =)43个字节之间。


BTCUtils.extractHash()从标准输出中提取哈希。它通过检查输出脚本的前缀和后缀来确定哈希的位置,从而做到这一点。如果输出脚本为非标准脚本,则返回空字节数组。


我们已经可以看到一些模式。输出字节0-7始终是该值。所有旧类型都有后缀,而所有见证类型都没有后缀。除p2pkh以外的所有类型都将在输出的(8 + 1 + 2 =)第十个字节处开始哈希,该字节位于索引处:

(_output.slice(8, 3).concat(_output.extractHash()))

此表达式占用字节8、9和10,并连接哈希。对于见证类型,字节8是长度前缀,而字节9和10是模板前缀,因此很容易看出,将它们连接到散列将产生(长度前缀)输出脚本。但是,对于p2sh地址,此表达式将不会附加模板后缀。对于p2pkh地址,它将仅提取前缀的2个字节,并且将再次不附加后缀。这意味着该表达式会修改旧版输出脚本,并且永远不会输出有效的旧版输出脚本。

bytes memory _modifiedLegacyOutputScript =
(_output.slice(8, 3).concat(_output.extractHash()));
require(
keccak256(_modifiedLegacyOutputScript) ==
keccak256(abi.encodePacked(_d.redeemerOutputScript))
);

此代码等效于已部署的代码。意外修改旧脚本后,它将其与未修改的旧输出脚本进行比较。每当_d.redeemerOutputScript是旧脚本时,此相等性将始终失败,并且事务将始终被还原。


此错误既不会损害赎回者,也不会损害存款者的利益,也就是说,用户的资金是安全的。实际上,由于此代码验证了兑换证明,因此仅在兑换者收到BTC之后运行。但是,由于系统无法验证赎回是否成功,因此签名人债券变得可用于赎回,就像赎回失败一样。特别是,如果在签署人提供了该笔交易的签名后的6个小时内,尚未证明存款合约发生了赎回交易,则赎回者可以通知合约,赎回证明已超时。

赎回证明超时通知被视为签名者中止,这意味着签名者不满足系统要求,但系统不视为恶意的情况。在赎回期间,这意味着系统无法验证赎回者已收到他们的资金。在这种情况下,系统会抓住签名人的保证金,并将全部保证金价值提供给救赎者作为补偿。采取这种方法是为了防止以下情况:签名者在请求的赎回交易上生成签名,但又合谋在另一笔交易上生成签名,然后在确认正确的交易之前竞相确认其交易。

在正常情况下,发生不良交易的签名者可以通过证明欺诈行为受到惩罚,证明他们签名了未经授权的数据。该证明的效果是相同的,抓住了签署人的债券并将全部金额提供给了赎回者。但是,除非系统验证赎回交易的输出脚本,否则签名者可以简单地证明其恶意交易。这样,兑换证明必须检查输出脚本,既需要在兑换请求时进行验证,又需要在兑换证明时进行调整。

其他观察

上面的问题不是兑换代码的唯一问题。实际上,即使证明代码正确无误,由于有关OP_VERIF和OP_VERNOTIF的共识规则,恶意的赎回者仍可能会指定一个生成无效比特币交易的输出脚本。这将迫使交易永远不会包含在比特币区块中。在这种情况下,能够确认交易的输出脚本是无关紧要的,并且赎回者将能够保证收到保证金,同时将BTC留给签字人。这样,除了一旦进行兑换后验证不正确之外,在请求兑换时缺少验证。因此,将来的版本必须仅支持标准地址类型。

值得注意的是,如果赎回者指定了一个输出脚本,该脚本将导致无效的比特币交易,尽管他们能够抓住签署人的保证金,但签署人仍将控制BTC存款。因此,总价值损失将少于无法证明为存款合同的有效交易。尽管如此,签署人债券的超额抵押意味着恶意赎回者仍将从这种情况中获得总体收益。错误的验证码也存在于无效的代码路径中,该路径已在错误修正PR中删除。

什么地方出了错


最终,这里有几个问题:


首先,我们未能捕获到一个动作项,无法在原始提交消息之外添加其他测试向量。结果,我们错过了在开发过程中抓住问题的机会。


在基于dApp的手动质量检查过程中,我们没有验证UI中成功的赎回是否导致链上封闭存款。结果,我们错过了在手动质量检查过程中发现问题的机会。


我们在兑换的入口节点没有完全考虑输入验证。这是系统中为数不多的完全由用户控制的数据之一,因此,输入验证应该成为首要任务。


我们没有花费足够的时间来生成用于单元测试的比特币测试向量。特别是,对赎回流程的模糊测试可能会同时捕获请求的输出脚本验证和事务证明问题。

下一步


除了用于在运行系统中赎回未清存款并确保当时正在绑定ETH的运营商收回债券的现有措施外,我们已经采取了多项措施,并且将采取更多措施,因为我们希望重新启动tBTC系统。

我们所做的


James Prestwich已在GitHub上开放了PR,并提供了建议的修复程序。在接下来的几天合并之前,我们将添加测试向量。


我们已经调整了周一开始的计划中的位跟踪审计的范围,该审计从积极关注网络中的Go客户端开始,改为在Go客户端和系统中的智能合约之间分配时间。


我们已将问题传达给我们先前的审核员ConsenSys Diligence,并将其修正为我们的现有审核员Trail of Bits,以进行确认和进一步的反馈。


通过提供1.005 BTC对1 TBTC购买未偿还TBTC,我们已经收回了TBTC的99.83%。我们将使用有抵押的TBTC赎回未结存款并释放抵押的ETH。

我们将要做什么


如果拉动请求与尚待处理的将来更改合并在一起,我们将澄清和改进捕获后续工作的流程。
在系统中使用用户控制的输入而没有足够的测试的情况下,我们将在整个团队中进行改进以阻止请求合并的过程。


我们会将失败的兑换作为测试向量导入到我们的测试套件中。我们将根据测试套件的各种已知地址类型生成其他测试向量。我们将探索发现或构建比特币交易模糊测试工具的可行性。
我们将与Trail of Bits合作,以扩展和自动化tBTC的更多集成和系统测试,并为当前仅具有更简单单元测试的系统各个组件添加模糊测试和属性测试。我们将与“比特追踪”一起确定可能需要进一步检查的系统其他任何区域。

除了我们将进行的技术和流程更改之外,在接下来的几天中,我们还将宣布我们计划如何进行tBTC系统的重新部署,以及这将如何影响KEEP代币分配权益下降。我们期待向世界展示以太坊上更强大,更安全的比特币。

原文链接:https://blog.keep.network/details-of-the-tbtc-deposit-pause-on-may-18-2020-38d7dd555663

—-

编译者/作者:洁sir

玩币族申明:玩币族作为开放的资讯翻译/分享平台,所提供的所有资讯仅代表作者个人观点,与玩币族平台立场无关,且不构成任何投资理财建议。文章版权归原作者所有。

LOADING...
LOADING...