小葱APP 9月28日讯,作者:殷耀平,转载请注明出处。
如果你在使用比特币钱包,却无法回答上述3个问题,那么这篇文章专为你定制。
比特币被锁死在无效地址
近日,安比(SECBIT)实验室在审计数字钱包源码时,发现一个名为 pywallet 的比特币钱包开源库内含一个严重缺陷:如果向 pywallet 生成的 OmniLayer 收款地址转账,将导致资产永久丢失。
据安比实验室区块链技术专家 zer0to0ne 解释,OmniLayer 协议允许在比特币区块链上发行自定义资产(比如 USDT)。OmniLayer 资产交易的本质是比特币交易。
比特币交易的代码库有很多,pywallet 便是其中一种。它能方便地构造符合 OmniLayer 格式的比特币交易。目前 pywallet 已经被应用在一些数字钱包软件中。
但开源库 pywallet 在生成 OmniLayer 钱包地址的时候,误将地址的前缀写反了,致使若干比特币资产被锁死在无效的地址内。
图1为 pywallet 相关错误代码截图
小葱注:比特币网络上最常见的地址类型有三种:普通公钥地址(1-地址),脚本哈希地址(3-地址)和隔离见证地址(bc1-地址),地址类型通过地址的前缀来区分。其中1-地址的前缀为 0x00,3-地址 的前缀为 0x05。三类地址用途各异。
前述的开源库 pywallet 颠倒了地址前缀,将1-地址错误地设置为3-地址。因此原本要转给1-地址的资产会误转入3-地址。
当用户以1-地址的验证方式(即私钥签名)去取出资产时,区块链网络却以3-地址执行脚本的方式来执行验证,从而导致用户无法正常取出资产。
安全专家:比特币从未真正实现过转账功能
众所周知,比特币的实现是基于 UTXO 模型,这与我们直观理解的账户模型有很大区别。
安比实验室区块链专家zer0to0ne告诉小葱,实际上比特币从未真正实现过通常意义上的转账功能。中本聪只给比特币设计了一系列比特币脚本操作符和比特币脚本执行器,而所谓的转账过程,实际是由一段比特币脚本锁定、解锁过程来模拟。这与日常生活中的账本概念(或称账户模型)是不一样的。
为便于理解,我们可以把比特币区块链上的资产交易比喻成将资产锁进保险箱,只有持有保险箱钥匙的人(即收款人)才能拿出保险箱中的资产进行交易。
来源:比特币白皮书https://bitcoin.org/bitcoin.pdf
举个栗子:
【如果 Alice 要向 Bob 支付一笔资产,Alice 需将这笔资产锁进一个保险箱中,只有 Bob 才有这个保险箱的钥匙,即只有Bob才能取出这笔资产。如果 Bob 想取出资产,他必须同时花掉这笔资产(即锁入另一个保险箱)。在 Bob 未取出资产前,资产并不真正属于 Bob】【试想若 Bob 丢了钥匙,他将无法再取出资产。 换句话说,当这笔资产存于保险箱时,它既不属于Alice,也不完全属于Bob。当然,Alice 也可以把资产放入任何人都可以打开的保险箱中,这也被称之为 Anyone-Can-Spend 交易】
付款人为收款人定制一个保险箱,将资产放入保险箱中并上锁,再将保险箱丢到公共场所,收款人自行前往解锁。由于比特币区块链上的收款地址类型不同,相应的保险箱类型、开启保险箱的钥匙,以及保险箱的解锁过程也有区别。
为什么说比特币从未实现真正意义上的转账功能?
因为比特币系统中根本就不存在账户概念,账户之间的转账也无从谈起。一个人能在未来打开多少个保险箱,也还是未知数。
基于上述解释,我们很容易理解:当 pywallet 开源库误将1-地址识别为3-地址时,就好像将原本的1-类保险箱改造成了3-类保险箱,而账户持有者还是拿着1-类保险箱的钥匙去解锁,能打开才怪。
被误锁的资产还有救吗?能否采用1-地址的钥匙去开启3-保险箱?
那么此前提及的被误锁住的 OmniLayer 数字资产究竟能不能找回呢?
安比实验室zer0to0ne 表示,小白可能首先需要弄懂两个重要概念 P2PKH(Pay to Public Key Hash) 与 P2SH (Pay to Script Hash)才能理解答案。这两个名词分别代表地是两种不同比特币交易类型。
P2PKH:中本聪的伟大发明
P2PKH(Pay to Public Key Hash),即将比特币放入一个保险箱,钥匙孔为公钥 Hash(Public Key Hash)。我们最常见到的1地址本质上就是 Public Key Hash 的一种编码。1-地址的生成过程很简单,将公钥经过Hash160运算得到 Public Key Hash,在 Public Key Hash 头部补上前缀 0x00,尾部补上校验和,经过Base58便得到了1开头的比特币地址。
再看看P2PKH交易类型的保险箱构造过程,以Alice发送比特币给Bob为例。付款方 Alice 在构造保险箱的时候需设置一个锁定脚本:
注:可把这一步理解为 Alice 为 Bob 定制了一个保险箱,把比特币放入保险箱并用 Bob 的公钥 PubKey Hash上锁。现在这把锁除了持有私钥的 Bob,谁都无法打开。
当 Bob 要花费 Alice 给他的比特币时,需提供必要的参数:交易签名 + 公钥(技术黑话:scriptSig)来开启保险箱,使得锁定脚本执行后返回 True,这一步通常由钱包自动完成。
那么比特币节点是如何校验 scriptSig 合法性的?
图片来自Mastering Bitcoin
脚本执行过程上图所示,Bob将交易签名后得到的数据(实际上还要包含数据长度信息),真正的scriptSig 应该为
【首先入栈的是
数字签名除了持有私钥的人,谁也无法伪造。执行至此,一笔比特币P2PKH交易已经安全地完成了。
此时,细心的读者可能会注意一个细节:如果 Bob 取出钥匙,在他还未打开保险箱时,区块链上的任何矿工都能看得见这把钥匙的形状,理论上他们能立即复制一把钥匙,把 Alice 留给 Bob 的保险箱打开并花掉(俗称Front-running 攻击)。
显然中本聪考虑了这个问题,这把钥匙中的交易签名是 Bob 发起的交易的完整签名。假设 Bob 要将 Alice 构造的保险箱中的比特币 装入一个新的保险箱(留给Charlie),这时Bob出示的钥匙包含了Charlie 的公钥Hash,矿工虽然可以复制 Bob 的钥匙,但是这把钥匙已经隐藏了下一个新保险箱的关键信息,因此矿工无法使用这个复制钥匙来完成别的动作(无法挪用数字签名)。
P2SH:后中本聪时代的重大创新
中本聪设计了一个这么强大的脚本系统,只用来构造转账交易似乎太浪费,因此有开发者尝试用其他指令构造一些特别的锁定脚本,并使用其他方式来解锁。
例如构造一个用 Hash 原象(Pre-image)来解锁交易的脚本:
该脚本含义是:当满足 Hash160(Pre-image)==两个神奇的功能:
- 交易构造的输出足够短,意味着比特币节点维护的 UTXO 缓存占用空间会大大减小;
- Pre-image 总是在交易被花费时作为 input 来引用,不会在交易的 output 侧出现,UTXO依然保持精简,同时可以把手续费负担转嫁给接收方。
但是,这个脚本存在很大安全性问题。继续从保险箱的例子来理解,并给这类保险箱起名3-类保险箱。
【Alice 给 Bob 的比特币锁定在一个由上述 Hash160保护的保险箱里,我们姑且称之为哈希锁吧。这把锁依然需要正确的形状才能开启,但安全性弱很多。由于缺少数字签名机制导致钥匙隐藏的关键信息不会随着Bob 新建的保险箱而变化。任何矿工都能在 Bob 亮出钥匙的一瞬间复制出一摸一样的钥匙,抢着去开Alice留给Bob的保险箱(Front-running),将币转给另一个人 (Eve)。】
不慌。比特币核心开发者 Gavin Adresen 提出的P2SH(Pay to Script Hash )技术,正是为了让上述交易方式更安全。
P2SH 的交易输出依然是校验 Hash160(Script)==