主页 > imtoken浏览器 > 为什么比特币采用UTXO模型

为什么比特币采用UTXO模型

imtoken浏览器 2024-01-26 05:08:23

本期漏洞主题为比特币漏洞CVE-2010-5141。 这个漏洞可以让攻击者窃取任何人的比特币,危害非常严重。 好在这个漏洞没有被利用,修复速度极快。 该漏洞与比特币的脚本引擎相关,对公链开发者具有参考意义; 从目前市场上的公链来看,大多都内置了虚拟机或者脚本引擎来打造DApp生态,这也是区块链的一大趋势。

1、比特币中的UTXO模型是什么?

Tips:漏洞代码片段中涉及到一些UTXO相关的知识和概念,所以在对漏洞进行理论分析之前需要了解这些知识点,已经了解的可以跳过。

一、 账户模型和 UTXO 模型

在我们看 UTXO 模型之前,让我们先谈谈常见的账户模型。 什么是账户模型? 账户模型的数据结构简单,可以理解为“账户=”余额”,每个账户对应一个余额。例如:账户A转200到账户B,只需要A-200,然后B +200在账户模型中完成转账操作;目前大部分软件都采用账户模型,如银行系统、以太坊等。

但是,比特币使用的是自研的 UTXO 模型。 UTXO中没有“account=”balance“这样的数据结构,那么怎么转钱呢?

二. 如何操作比特币转账

以上面A到B的转账为例,在UTXO中完成本次转账需要进行如下操作:

1、找到A账户下200余额的来源,也就是找到A收到200 x的交易

2. 以x笔交易为输入,以向B转200的交易y为输出,x对应y且x和y的转账金额必须相等

比特币UTXO_比特币比特币的行情_比特币每十分钟产生多少个比特币

3. x交易标记为已花费,y交易标记为未花费

两笔交易的转账金额必须相等。 简单的解释就是你收到多少才能转多少,事实确实如此。

但是,当我只需要将一部分转让给他人时,我该怎么办? 答案是只转一部分给别人,剩下的部分自己转另一个号码。

三、 引用两张网上的图文:

比特币为什么要采用UTXO模型

比特币为什么要采用UTXO模型

在本文中,比特币为什么采用UTXO模型不是重点,我们只需要了解UTXO的原理即可。

2. 比特币脚本引擎

比特币比特币的行情_比特币每十分钟产生多少个比特币_比特币UTXO

比特币脚本不是图灵完备的。 比特币使用自定义脚本进行交易和其他操作,为比特币提供的灵活性有限。 实现多重签名、冻结资金等简单功能,多了就不行了。

比特币之所以这样做,是为了牺牲一定的完整性来保证安全性。 比特币脚本的原理是先定义一堆操作码,然后脚本引擎根据栈一个一个地执行每一个操作码。

堆栈很好理解。 队列是先进后出的,栈正好相反。 它是先进先出的。 一个元素被压入(push)入栈后,该元素会先出栈。

在早期版本的比特币中,发送一个标准的转账(pay-to-pubkey)交易需要脚本签名(scriptSig)和公钥验证脚本(scriptPubKey)。 具体处理流程如下:

先填入要执行的脚本(Script),然后将签名(sig)和公钥(pubKey)压栈,然后操作码OP_CHECKSIG会校验签名等,校验通过则为true将被压入堆栈,否则将被压入堆栈。

三、CVE-2010-5141漏洞分析

了解了以上知识后,就可以开始分析CVE-2010-5141漏洞了。 笔者下载了0.3.3漏洞版本,下载地址可在github的比特币仓库中找到。

script.cpp 代码片段 VerifySignature 函数:

比特币比特币的行情_比特币每十分钟产生多少个比特币_比特币UTXO

每笔交易都会调用VerifySignature函数,用于执行脚本和验证签名比特币UTXO,然后标记交易是否花费。

首先,txFrom参数是上一笔交易,txTo是正在处理的交易。 如果你理解上一章讲解的UTXO模型,到这里就不难理解了。 重点关注1125行代码,调用EvalScript函数,第一个参数是txin.scriptSig(包含签名信息)+分隔的操作码OP_CODESEPARATOR+ txout.scriptPunKey(包含公钥信息,OP_CHECKSIG指令),这些是要执行的脚本EvalScript函数,后面的参数可以暂时忽略,只要EvalScript函数返回true,验证签名就通过了。 EvalScript 函数如何返回 true?

首先栈不能为空,然后强制bool后栈顶必须为true。 作者简单的解释为必须有栈顶,且值不能为0。

然后看关键的OP_CHECKSIG操作码

(注:由于操作码过多,本文针对OP_CHECKSIG操作码)

上面的代码不难理解,调用Checksig函数验证签名,然后返回FSuccess变量,如果为真则压入一个vchTrue(非0)入栈,否则压入一个vchFalse(0)入栈; 如果操作码是 OP_CHECKSIGVERIFY 而不是 For OP_CHECKSIG,则让 vchTrue 从堆栈中弹出并开始执行以下操作码。

按照OP_CHECKSIG的正常逻辑,如果签名校验不成功,栈顶肯定还剩下一个vchFalse。 虽然栈不为空,但是栈顶的值为0,还是会返回false。

回到前面的代码,EvalScript函数执行的脚本主要由以下变量组成:

比特币UTXO_比特币每十分钟产生多少个比特币_比特币比特币的行情

1. txin.scriptSig

2. OP_CODESEPARATOR

3. txout.scriptPubKey

第一个签名信息是可控的比特币UTXO,第二个只是一个分隔符,会被删除,第三个是不可控的,因为它来自于之前的交易。

第一个变量是可控的,作为脚本执行,所以这个变量既可以是签名信息,也可以是操作码,处理起来比较方便。 接下来,我们需要引用一个神奇的操作码 OP_PUSHDATA4。 我们来看看比特币 0.3.3 是如何处理这个操作码的:

先获取opcode,如果opcode的值小于等于OP_PUSHDATA4的值,则将vchPushValue全部压入栈中,再执行GetOp函数

看了源码发现OP_PUSHDATA4指令定义为78,所以当函数遇到OP_PUSHDATA4时,指针会移动78+4=82位,将78位的数据压入栈,所以只要在 txin.scriptSig 中注入一个 OP_PUSHDATA4 操作码,后面的公钥信息和 OP_CHECKSIG 指令都会被“吃掉”,作为参数压入栈中。 当指针到达末尾时,进行最终判断:

1.栈是否为空?否

比特币UTXO_比特币每十分钟产生多少个比特币_比特币比特币的行情

2.栈顶元素是否为0?否

所以如果满足条件,EvalScript 函数返回 true,然后 VerifySignature 函数也返回 true。 由于绕过了签名验证,其他人的比特币就可以被任意花费。

4. CVE-2010-5141漏洞修复方案

笔者下载了比特币0.3.8版本,直接看关键部分代码

比特币为什么要采用UTXO模型

修复方案也很明确,分别执行scriptSig和scriptPubkey,不管你的scriptSig里有什么,都不会影响后续scriptPubkey的执行。

写在最后:

因为比特币漏洞分析是从第一期DVP漏洞专题开始连载的,目前的素材是2010年的,目前的漏洞分析主要有以下难点:

1.关于漏洞的信息非常少。 大多数漏洞只有一个 CVE 编号和一个简短的介绍。 不查阅大量资料是不可能入门的。

2、环境搭建困难,比如编译和私链搭建(早期的比特币甚至没有私链的概念)等,很多比特币早期源码编译需要的依赖已经不再维护和离线。

由于这些原因,笔者只进行了理论研究,并未进行实践验证。 如有错误请指正。