相信你们都看过上面的这张图,这张图来自中本聪的比特币白皮书,用来介绍比特币的交易。在这张图的上面,中本聪写下了这样几句话:We define an electronic coin as a chain of digital signatures. Each owner transfers the coin to the next by digitally signing a hash of the previous transaction and the public key of the next owner and adding these to the end of the coin. A payee can verify the signatures to verify the chain of ownership. 为了保持原汁原味,我就不翻译了,相信你们这点阅读理解的功力还保留得住。git
但问题是,他说的每一句话我都看懂了,可我仍是看不明白这张图到底在说什么。也许比特币的交易在中本聪的脑海里就是这个样子,因此我一直怀疑他是否是潜伏在地球的外星人,脑回路跟地球人不同,包括那个UTXO的概念,实在有悖于人类正常的思惟。因此,这篇文章的任务就是尽可能将这张图转换成地球人容易理解的形式。算法
如上图所示,这是一个比特币交易的简化结构(忽略了一些参数,简化了id)。安全
在介绍交易结构以前,先简单说一下比特币的UTXO概念,方便零基础的读者阅读。在比特币系统中,没有余额的概念,只有UTXO(Unspent Transaction Output),即未花费的交易输出。翻译成大白话就是我花的钱都是别人给个人钱,并且花的时候必须花完。好比我有一个2 BTC(比特币) 的UTXO,我想给你1 BTC,我就必须把这个 2BTC的UTXO 花掉,而后生成两个UTXO,一个 1BTC 的UTXO给你,一个UTXO(小于1 BTC,差值为交易费)给我本身。想看余额怎么办,就查一下这个地址里有多少UTXO,把里面的BTC加起来就是余额。数据结构
好了,如今咱们来看交易的结构。比特币的交易主要由两部分构成:输入(input)和输出(output)。electron
Input是要说明我打算花的这UTXO是从哪儿来的。学习
具体的参数包括:spa
txid : 引用的UTXO所在的那笔交易ID.net
vout : 引用的UTXO所在交易的输出中的序号(从0开始)翻译
scriptSig : 解锁脚本,包含付款人对本次交易的签名(<sig>)和付款人公钥(<PubK(A)>)。blog
Output是要说明我打算生成几个UTXO,分别给谁,每一个UTXO里面有多少BTC。
具体的参数包括:
value : 比特币数量
n : UTXO序号(从0开始)
scriptPubkey : 锁定脚本,包含命令(OP_DUP等)和收款人的公钥哈希(<PubKHash(B)>)。
了解了交易的结构以后,如今咱们经过一个交易的示例,来看看签名和验证是如何进行的。
咱们来看上面这张图,A经过交易001转给B 1BTC,B经过交易002转给C 1BTC,简化起见,忽略交易费的问题。
咱们来重点分析交易002。
B 打算转给C 1BTC,他先找到A转给他的那个UTXO,即交易001的out中n=0的那个UTXO,把相关参数写入交易002中的in。而后在out中输入比特币数量,UTXO序号,锁定脚本。锁定脚本中的命令都是固定的,C的公钥哈希(<PubKHash(C)>)可经过C的钱包地址解码得到。
这样,交易002相关的数据都已经准备好了,就差最后的签名了。这个签名就相似于开支票时的签名,证实我赞成把这笔钱给你。可是具体的实现要比签个字复杂不少,缘由就在于互联网中一切都是能够复制的,如何证实你拥有这笔钱,如何证实这个交易是你建立的且没有被修改过,这背后都有严密的数学理论和算法来保证。
咱们先来看下签名的过程:
签名的输入:
1. 待签名的交易数据(输入和输出),即<tx002>。
2. 引用的UTXO相关信息(交易ID、序号、锁定脚本)
3. B的私钥,即<PriK(B)>。
4. 签名类型
签名的输出:
1. scriptSig ,即解锁脚本,包含签名(<sig>)和 B的公钥(<PubK(B)>)。
至此,一个完整的交易即建立成功,能够发送给其它节点验证了。
这里多说一句,细心的读者可能会发现,输入2的信息其实输入1已经包含了,或者能够根据输入1查的到,为何还要单独列出呢。目前我也没有找到明确的可信服的解释,不知道是否还有其它深意。期待大神们的指教。
交易发送至其它节点后,其它节点会对其进行验证,只有验证经过的交易才会被继续传播。交易验证的项目不少,这里只讲关于签名的验证。
签名验证的目的有两个:
1. 证实交易所引用的UTXO的确属于付款人。
具体到本次交易,就是证实交易001的序号为0的UTXO的确是发给B的。
2. 证实交易的全部数据的确是付款人提供的,且未被修改过。
具体到本次交易,就是证实B的确建立了交易002,且交易内的数据未被修改过。
下面咱们来看验证是如何进行的,其实很简单,就是用解锁脚本解锁对应UTXO的锁定脚本,对应上图就是橙色线所链接的两个脚本:
<sig><PubK(B)> OP_DUP OP_HASH160 <PubKHash(B)> OP_EQUALVERIFY OP_CHECKSIG
比特币脚本的执行基于堆栈模型,遵循从左到右,后入先出的原则。关于堆栈的介绍,文末的参考文章中有比较清晰的图示,不清楚的读者能够参考。本文为方便阐释各步骤的意义,采用文字方式描述。各步操做以下:
1. <sig> <sig> 入栈
2. <PubK(B)> <PubK(B)>入栈
3. OP_DUP 复制位于栈顶的<PubK(B)> ,将副本置于栈顶。
4. OP_HASH160 对位于栈顶的<PubK(B)>副本进行HASH160,<PubK(B)>转变为<PubKHash(B)>。
5. <PubKHash(B)> <PubKHash(B)>入栈
6. OP_EQUALVERIFY 比较位于栈顶的两个元素是否相同,若相同则移除这两个元素,继续执行。若不一样,则中断执行,返回失败。
7. OP_CHECKSIG 检查签名(注意栈内现有的元素为<sig><PubK(B)>),根据结果返回成功或失败。
下面咱们来分析下每一步的意义,步骤1~6的意义其实很明显,用B提供的公钥(<PubK(B)>)进行双哈希(HASH160),而后与锁定脚本中的公钥哈希(<PubKHash(B)>)做对比,相同则返回成功。咱们知道公钥哈希(<PubKHash(B)>)就是A在建立交易时根据B的地址生成的,它就是B的公钥通过双哈希运算得来的,因此这一步只要提供了B的公钥,验证确定是成功的。因此,步骤1~6 就至关于A把1 BTC发给了B的邮箱,B拿把钥匙打开了邮箱,证实了B确实拥有这1 BTC。也就是证实了上文中提到的验证目的1:证实交易001的序号为0的UTXO的确是发给B的。
比较麻烦的是第7步,不少文章说到这里都只是泛泛而谈,或是一笔带过,我学习到这里的时候真是如堕雾中,四顾茫然啊。如今回过头再去看一些文章中的表述是很是不许确的。这一步简单的CHECKSIG操做,实际上蕴含了复杂的密码学和数学原理,证实的其实不是全部权的问题,而是证实了B的确建立了交易002,且交易内的数据未被修改过,也就是上文中提到的验证目的2。
那么,CHECKSIG的验证是如何实现的呢?这里运用了椭圆曲线数字签名算法(ECDSA:The Elliptic Curve Digital Signature Algorithm ),一种利用椭圆曲线进行数字签名和验证的算法。下面将简单介绍这种算法是如何用来进行比特币交易的签名和验证的。涉及到的数学知识不做深刻介绍,感兴趣的读者可参照文末的文章连接深刻了解。
首先,咱们看一下椭圆曲线的形状,如上图红线。咱们能够把这个曲线上的点定义一种加法:链接两点的直线与椭圆曲线的交点相对于X轴的对称点,即为两点之和。如图中的 A+B=C。
A+A时咱们取A点的切线与曲线交点相对X轴的对称点。有了A+A,咱们就能很方便的定义出乘法。有了乘法,咱们选择一个基点G,可以很方便地计算出 K=kG,然而,给定K和G,却很难计算k(至今没有有效的算法),这就是椭圆曲线离散对数问题。而椭圆曲线密码学(ECC Elliptic Curves Cryptography)的安全性正是创建于椭圆曲线离散对数问题的困难性之上。基于此,在ECC中咱们定义k为私钥,K为公钥。
而后,咱们再来看一下基于有限域Fp的椭圆曲线域E(Fp):
y^2 ≡ x^3 + ax + b (mod p)
当:a, b ∈ Fp 且知足 4a^3+27b^2 ≠ 0 (mod p). , x, y ∈ Fp时,这条曲线上的点的集合P=(x,y)就构成了一个基于有限域Fp的椭圆曲线域E(Fp)。
完整描述一个椭圆曲线域实际须要6个参数:
p:限定有限域边界的质数
a,b:椭圆曲线的参数
G:基点
n:G的阶,nG=O∞
h:余因数,控制点的密度。
能够将椭圆曲线域简单理解为只取椭圆曲线上的那些整数点,可是因为多了一步模运算,所以展现出的形状与以前的平滑曲线是有差异的(以下图),可是以前定义的加法乘法规则是不变的。
好了,有了这些概念,咱们如今来看一下签名和验证的过程:
签名者的密钥对:(d, Q);(d为私钥,Q为公钥)
待签名的信息:M;
签名:Signature(M) = ( r, s)
签名过程:
一、根据ECC算法随机生成一个密钥对(k, R), R=(xR, yR)
二、令 r = xR mod n,若是r = 0,则返回步骤1
三、计算 H = Hash(M)
四、按照数据类型转换规则,将H转化为一个big endian的整数e
五、s = k^-1 (e + rd) mod n,若s = 0, 则返回步骤1
六、输出的S =(r,s)即为签名。
验证过程:
一、 计算 H = Hash(M)
二、按照数据类型转换规则,将H转化为一个big endian的整数e
三、计算 u1 = es^-1 mod n, u2 = rs^-1 mod n
四、计算 R = (xR, yR) = u1G + u2Q, 若是R = 零点,则验证该签名无效
五、令 v = xR mod n
六、若 v == r,则签名有效,若 v ≠ r, 则签名无效。
从数学上能够证实,若 v == r,便可证实信息M的确为持有密钥对(d, Q)的签名者所签署,且未被修改过。
上述过程当中的Q、S、R均为椭圆曲线域中的点。
咱们把上述示例中的输入参数与比特币交易002中的参数作个对比:
如上图所示,能够看出这两个过程当中的参数是一一对应的,咱们把交易002中相关的参数按照示例作相应的操做,就不难理解签名和验证的具体过程了。因为数学原理上的保证,若签名验证成功,便可证实B的确建立了交易002,且交易内的数据未被修改过,也就是上文中提到的验证目的2。
好了,以上就是笔者目前关于比特币交易中签名和验证的所有理解和思考,水平有限,难免存在谬误和纰漏,欢迎各位大神批评指正!
ECDSA 部分的两张动图引自参考文章,对原做者Nick Sullivan表示感谢,同时对全部参考文章的做者表示感谢!
[1] 深刻比特币原理(四)——锁定脚本(locking script)与解锁脚本(unlocking script)
https://bbs.huaweicloud.com/blogs/d4c97558190611e89fc57ca23e93a89f
[2] 比特币交易的数据结构与签名类型
https://blog.csdn.net/awewong/article/details/78310017
[3] 椭圆曲线密码学的简单介绍
https://zhuanlan.zhihu.com/p/26029199
[4] 比特币系统采用的公钥密码学方案和ECDSA签名算法介绍——第一部分:原理
http://www.8btc.com/btc_ecc_dsa_a