zoukankan      html  css  js  c++  java
  • 比特币交易的签名和验证

     疑惑的根源

    相信大家都看过上面的这张图,这张图来自中本聪的比特币白皮书,用来介绍比特币的交易。在这张图的上面,中本聪写下了这样几句话: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. 为了保持原汁原味,我就不翻译了,相信大家这点阅读理解的功力还保留得住。

    但问题是,他说的每一句话我都看懂了,可我还是看不明白这张图到底在说什么。也许比特币的交易在中本聪的脑海里就是这个样子,所以我一直怀疑他是不是潜伏在地球的外星人,脑回路跟地球人不一样,包括那个UTXO的概念,实在有悖于人类正常的思维。所以,这篇文章的任务就是尽量将这张图转换成地球人容易理解的形式。


    交易的构成

     

    如上图所示,这是一个比特币交易的简化结构(忽略了一些参数,简化了id)。

    在介绍交易结构之前,先简单说一下比特币的UTXO概念,方便零基础的读者阅读。在比特币系统中,没有余额的概念,只有UTXO(Unspent Transaction Output),即未花费的交易输出。翻译成大白话就是我花的钱都是别人给我的钱,而且花的时候必须花完。比如我有一个2 BTC(比特币) 的UTXO,我想给你1 BTC,我就必须把这个 2BTC的UTXO 花掉,然后生成两个UTXO,一个 1BTC 的UTXO给你,一个UTXO(小于1 BTC,差值为交易费)给我自己。想看余额怎么办,就查一下这个地址里有多少UTXO,把里面的BTC加起来就是余额。

    好了,现在我们来看交易的结构。比特币的交易主要由两部分构成:输入(input)和输出(output)。

    Input是要说明我打算花的这UTXO是从哪儿来的。

    具体的参数包括:

    txid : 引用的UTXO所在的那笔交易ID

    vout : 引用的UTXO所在交易的输出中的序号(从0开始)

    scriptSig : 解锁脚本,包含付款人对本次交易的签名(<sig>)和付款人公钥(<PubK(A)>)。

    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 ),一种利用椭圆曲线进行数字签名和验证的算法。下面将简单介绍这种算法是如何用来进行比特币交易的签名和验证的。涉及到的数学知识不作深入介绍,感兴趣的读者可参照文末的文章链接深入了解。


    ECDSA

     

    首先,我们看一下椭圆曲线的形状,如上图红线。我们可以把这个曲线上的点定义一种加法:连接两点的直线与椭圆曲线的交点相对于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)

    签名过程:

    1、根据ECC算法随机生成一个密钥对(k, R), R=(xR, yR)

    2、令 r = xR mod n,如果r = 0,则返回步骤1

    3、计算 H = Hash(M)

    4、按照数据类型转换规则,将H转化为一个big endian的整数e

    5、s = k^-1 (e + rd) mod n,若s = 0, 则返回步骤1

    6、输出的S =(r,s)即为签名。

    验证过程:

    1、 计算 H = Hash(M)

    2、按照数据类型转换规则,将H转化为一个big endian的整数e

    3、计算 u1 = es^-1 mod n, u2 = rs^-1 mod n

    4、计算 R = (xR, yR) = u1G + u2Q, 如果R = 零点,则验证该签名无效

    5、令 v = xR mod n

    6、若 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

  • 相关阅读:
    KnockoutJS(2)-监控属性
    KnockoutJS(1)-数据模型
    Stimulsoft Reports报表工具
    Knockout.js 初探
    web网页的表单排版利器--960css
    用一个div模拟textarea的实现
    正则表达式笔记4-封装class
    正则表达式笔记3
    正则表达式笔记2
    正则表达式笔记1
  • 原文地址:https://www.cnblogs.com/linguoguo/p/10392479.html
Copyright © 2011-2022 走看看