比特币源码分析之三:交易脚本
本篇默认读者了解两个基本的概念
1、hash算法,比特币使用的是sha-256,如果不了解,google一下
2、非对称加密算法,比特币使用的是椭圆曲线加密算法后文用ecc代替
这两个概念不需要了解详细的数学实现,只是了解大致的工作原理即可,相信码农应该都有这个基本功
地址
如果使用过比特币,可能会有一个类似下面格式的一个地址
1QAc9S5EmycqjzzWDc1yiWzr9jJLC8sLiY
别人想给你转账,你必须提供一个这样的地址,那么这个地址是什么呢
如果想看这个地址是怎么生成的可以尝试自己调试一把
1、gdb bitcoind
set arg -regtest(注意这里不需要-daemon,因为如果加上这个参数会新fork一个进程不受gdb管控)
b getnewaddress
r
2、新启动一个shell输入bitcoin-cli -regtest getnewaddress
这时候断点到了getnewaddress函数,堆栈如下
3、继续细跟下去就是具体的生成地址流程
关键过程(函数)如下
1、CKey::MakeNewKey(key.cpp),这里的CKey代表的是一个ecc算法的私钥
这个函数很简单,调用GetStrongRandBytes生成了一个大小为32字节随机数(check函数就是验证随机数是否符合私钥的格式)fCompressedIn表示是否压缩,这个涉及到ecc的公钥地址的一个梗,这个先不用关注
2、CKey::GetPubKey(key.cpp) 生成公钥,返回的是CPubKey(代表ecc公钥的类)
这个函数也比较简单,就是利用生成的私钥(32字节的keydata)生成了对应的公钥,公钥长度是65个字节大小(不压缩的情况,fCompressedIn为false)
总结下:
使用随机数生成一个私钥,再用私钥生成一个公钥,这里好奇心比较强的读者可能会疑问,私钥是一个随机数?对应的公钥不怕和别人重复了?这个完全不用担心,首先这个随机数参考了机器上的硬件信息,其次据说在大小在32字节的随机数生成出来的数字,如果和别人重复了,你就相当于一年365天,天天中彩票了。
生成公钥以后会把这个公钥经过加工变成大家看到的一个字符串地址,大概会经过以下步骤
1、先对公钥做一次hash160(内部实现是一个先对公钥sha256然后ripemd160)构造了一个CKeyID对象
这个keyid比较重要后文中介绍脚本的时候还要介绍
2、在keyid数据前加了一个字节的前缀,这个前缀根据网络不同,前缀不同
a)Mainnet是0x00
b)Tesstnet是0x6f
c)Regnet也是0x6f
3、对ver keyid做两次sha256,并且取hash后结果的前四字节作为checksum(校验)
4、最后对ver keyid checksum整体做一次base58(base64算法的修改版本,去掉了一些生僻字符)
3、4两步的代码如下(base58.cpp)
交易脚本
讲交易那一篇中有提到,交易脚本其实有两部分组成
1、输入脚本 (锁)
2、输出脚本(钥匙)
对这个概念不清楚的可以参看《比特币源码分析交易》
为了便于理解,笔者先从一个最为简单的脚本出发来讲解
输入脚本:<sig> <pubkey>
输出脚本:OP_DUP OP_HASH160 <PubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
首先先解释以上脚本指令
OP_DUP 复制指令,将虚拟机栈顶的数据复制一份放到栈顶
OP_HASH160 hash160指令,和计算keyid的hash160是一个算法
OP_EQUALVERIFY 比对指令,比对栈顶两个数据是否相等,如果不等执行失败
OP_CHECKSIG 签名验证指令,取栈顶的两个元素 第一个是pubkey 第二个是签名,该指令比较复杂,简单来说就是通过公钥和签名对交易数据做验证,后续会做详细介绍。
另外除了指令外还有几个<>内的表示一些关键数据
Sig,对交易数据,通过私钥计算出来的签名,签名验证,就是使用公钥对交易数据做运算,然后和签名校对,如果一致表明验证通过
Pubkey,上文中提到的由私钥生成出来的65字节大小的数据(压缩的是33字节)
PubKeyHash,对应上文中的keyid,是通过对pubkey做hash160计算出来的结果
虚拟机的通过栈执行,有数据结构基础的可能会理解,栈是一个先入先出的数据结构。虚拟机执行时会先执行输入脚本,再执行输出脚本,如果出错就验证失败。
1、先入栈sig
2、再入栈pubkey
3、复制栈顶的pubkey
4、做hash160运算
5、入栈PubKeyHash
6、对比栈顶的两个数据是否相等,不相等验证失败
7、取栈顶的pubkey和sig,验证签名,签名验证失败,脚本验证就失败
这里需要结合上一篇一起看,虽然输入脚本是先执行,但是按照出现顺序是先有输出脚本,也就是出钱的交易先出来(锁先出来),然后花钱的交易(也就是钥匙)才出来。这里很容易迷糊
虚拟机执行主逻辑在interpreter.cpp的EvalScript函数中,而调用是通过VerifyScript
下一篇会介绍下签名验证的细节