比特币交易实例分析

概念:

比特币交易确认过程:

 钱包创建交易

钱包软件通过收集UTXO、提供正确的解锁脚本、构造支付给接收者的输出这一系列的方式来创建交易。产生的交易随后将被发送到比特币网络临近的节点,从而使得该交易能够在整个比特币网络中传播。

 交易独立校验

每一个收到交易的比特币节点将会首先验证该交易,有效的交易将被传递到临近的节点,这将确保只有有效的交易才会在网络中传播,而无效的交易将会在第一个节点处就被废弃。

 验证的交易添加到交易池

验证交易后,比特币节点会将这些交易添加到自己的交易池,用来暂存尚未被加入到区块的交易记录。

 节点确认交易

假如D节点的区块链已经收集到了区块277,314,那么D节点会继续监听网络上的交易,在尝试挖掘新区块的同时,也监听着由其他节点发现的区块。当D节点在挖掘新区块时,它从比特币网络收到了区块277,315。这个区块的到来标志着终结了产出区277,315竞赛,与此同时也是产出区块277,316竞赛的开始。

 D节点在接收并验证区块277,315后,会检查内存池中的全部交易, 移除已经在区块277,315中出现过的交易记录,确保任何留在内存池中的交易都是未确认的,等待被记录到新区块中,而被移除的交易记录获得一次“确认”交易。

 把包含在区块内且被添加到区块链上的交易称为“确认”交易,交易经过“确认”之后,当达到6个“确认”数后,认为交易是安全不可更改的,新的拥有者才能够花费他在交易中得到的比特币。

 比特币交易没有过期和超时

一笔交易现在有效,那么它就永远有效。如果一笔交易只在全网广播了一次,那么它只会保存在一个挖矿节点的内存中,一旦这个节点重新启动,内存池中的数据就会被完全擦除。如果一笔有效交易被传播到了全网,但长时间未处理,它将从挖矿节点的内存池中消失。

 交易本应该在一段时间内被处理而实际没有,那么钱包软件应该重新发送交易或重新支付更高的矿工费。

 其中src/目录下在编译成功后会生成以下几个程序:

bitcoind, bitcoin-qt以及bitcoin-cli

bitcoind: 对编程开发非常有用,它提供了一个完整的节点,通过RPC来与端口8332进行通信(或者是testnet的18332端口)

bitcoin-qt:提供一个完整的比特币节点与钱包前端的组态。通过Help菜单,可访问一个的控制台,该控制台可以输入在整个文档中都使用到的RPC命令

bitcoin-cli:允许以命令行形式发送RPC命令到bitcoind。比如bitcoin-clihelp

上面三个程序都是从bitcoin.conf文件中获取设置参数,该文件位于Bitcoin应用程序目录:

Windows: %APPDATA%\Bitcoin\

OSX: $HOME/Library/ApplicationSupport/Bitcoin/

Linux: $HOME/.bitcoin/

若要使用bitcoind和bitcoin-cli,则必须添加RPC命令到bitcoin.conf中。若两个程序都是运行在同一个系统上的同一个用户下,则它们都是从这同一个文件中读取配置参数,因此最好设置任意长的随机密码:

rpcpassword=change_this_to_a_long_random_password

还应该设置bitcoin.conf为只读权限。在Linux, Mac OSX和其他类Unix系统上,可使用如下命令来设置:

chmod 0600 bitcoin.conf

对开发者而言,使用比特币的测试网(testnet)或回归测试模式(regtest)会更安全和更简单,其详细描述会在后面介绍。

关于比特币的问题可到如下两个网址发帖:

1.https://bitcointalk.org/index.php?board=4.0

2. https://en.bitcoin.it/wiki/IRC_channels

错误或建议可在Bitcoin.org的网址提出:

https://github.com/bitcoin-dot-org/bitcoin.org/issues

https://groups.google.com/forum/#!forum/bitcoin-documentation

 关于UTXO概念

<通俗版>

UTXO英文全称为Unspent TransactionOutput,意思未花费事务输出。

拿仓库来打比方,仓库的主要业务就是进和出,仓库会把日常的进出流水账记录下来,为了查询统计方便,除了流水账通常还会汇总一份库存表,比如类似如下:

上图是从17年8月1日到17年8月5日的仓库出入流水账,为了统计方便,仓库还汇总保存了一份每天的库存日报表,类似如下:


每天仓库在需要出库的时候,只要查看以下库存日报表就知道数量是否足够了。

比如8月3日需要出库15支毛笔,此时查看库存表发现毛笔存量为30支足够发出,于是就将库存表中的毛笔数量减掉15,并且将出库明细记录在流水账中。然而,这会出现一个问题就是库存日报表是另外编制保存的,可能发生数据不一致的情况,比如8月2日时毛笔的库存本来是30却误写成20,这会导致后续账务都是错的。因此在有些系统中,为了防止出现这样的不一致,索性不再另外保存库存表,而只是出一张视图统计(逻辑上的统计,不是实际去保存一个统计表)。

比特币中的交易事务过程与上述的库存进出很像,某个钱包地址转入一笔比特币,然后这个地址又向其他钱包地址转出一笔比特币,这些不断发生的入和出跟库存进出是异曲同工的。

但是,在比特币中不会去保存一份库存表,在出库时也不会去库存表中扣除,而是直接消耗入库记录,也就是说在出库时就去找一下有没有之前的入库记录拿来扣除,比如8月3日时需要15支毛笔,此时系统就会去搜索之前的入库记录,发现在8月1日和8月2日分别有一笔数量为10和20的入库记录,为了满足15的发出数量,首先消耗掉10的这一笔,再从20的这一笔中小号5支,经过这个逻辑判断成功后,系统会直接产生一条数量为10的出库记录和数量为5的出库记录,按这种方式,将每一笔的入和出对应起来。

再比特币的交易事务结构中,入就是指金额转入,出就是指金额转出,下面用用一个通俗的示意图来解释一下这个转入和转出:

上图展示了比特币交易事务结构,在比特币的交易事务数据中,存储的就是这样的输入和输出,相当于仓库中的进出流水账,并且输入和输出彼此对应,或者更准确的说,输入就是指向之前的输出,图中的交易流程如下:

1.001号交易为coinbase交易,也就是挖矿交易,在这个交易中,输入部分没有对应的输出,而是由系统直接奖励发行比特币,矿工Alice得到12.5个比特币奖励,放在001号交易的输出部分,此时,对Alice来说,拥有了这12.5个比特币的支配权,这12.5个比特币的输出可以作为下一笔交易的输入,顾名思义,这笔输出就称之为是Alice的为花费输出,也就是Alice的UTXO的意思。

2.002号交易中,Alice转账6比特币到Bob的地址,Alice找到了自己的UTXO(若Alice不止一笔UTXO,可根据一定的规则去选中,比如将小金额的先花费掉)。由于只需要转账6比特币,可UTXO却有12.5个,因此需要找零6.5个到自己的地址中,由此产生002号中的交易输出。注意,在002号交易输出中的Alice地址是可以和001号中的Alice地址不一样的,只要都是属于Alice自己的钱包地址就可以。

3.003号交易中,Bob转账了2比特币到Lily的地址,过程是和002号交易一样的。

再来总结一下UTXO是什么:

1. 比特币交易中不是通过账户的增减来实现的,而是一笔笔关联的输入/输出交易事务。

2. 每一笔的交易都要花费输入,然后产生输出,这个产生的输出就是所谓的“为花费过的交易输出”,也就是UTXO。每一笔交易事务都有一个唯一的编号,即交易事务ID(UTXO),这是通过哈希算法计算而来的,当需要引用某一笔交易事务中的输出时,主要提供交易事务ID和所处输出列表的序号就可以了。

3.  由于没有账户概念,因此当输入部分的金额大于所需的输出时,必须给自己找零,这个找零也是作为交易一部分包含在输出中。

最后怎么证明那一条UTXO是谁的呢?

在比特币中,是使用输入脚本和输出脚本程序实现的,有时也叫锁定脚本和解锁脚本。也就是说,通过锁定脚本,利用私钥签名解锁自己的某一条UTXO(也是之前的输出),然后使用对方的公钥锁定新的输出,成功后,这笔新的输出就成为了对方的UTXO。同样,对方也可使用锁定脚本和解锁脚本进行转账。这个脚本程序本质上就是比特币中的数字合约,这也是为什么比特币被称为可编程数字货币的原因,它的转入和转出或者说输入和输出都是通过脚本程序的组合来自动实现的,实现过程中还使用到了私钥和公钥,也就是公开密钥算法。

 <专业版>:

交易是让用户花费比特币,每个交易都是由一些直接的简单支付和复杂支付构成。这里介绍一下每个部分并演示如何将它们结合起来使用以构成一个交易。

为简单起见,这里都假设coinbase交易不存在。因为coinbase交易是由矿工创建的并且它们对下面所列出的规则来说是一个例外。


上图所示比特币交易的主要部分,每个交易至少有一个输入和一个输出。每个输入花费掉前一个输出的比特币。每个输出随后以UTXO的形式存在等待着下一个输入来花费它。当比特币钱包告诉我有1000个比特币时,这实际上表示我由1000个比特币在一个或多个UTXO中等待被花费。

每个交易的前缀是由4个字节的交易版本号构成,它告诉比特币节点和矿工使用哪个系列的规则来验证它。这就允许开发者在不需要验证前一个交易就可以为未来交易创建新的规则。

一个输出基于它在交易链上的位置对应一个索引号,第一个输出索引号为0。一个输出还含有比特币的数量,它会将这些比特币花费到满足条件的公钥脚本去。任何满足公钥脚本条件的人都可以花费支付给他的比特币数量。

 一个输入使用一个txid和一个输出索引号(vout)来标识某个特定输出被花费。它也有一个签名脚本提供满足公钥脚本中的条件参数。

下面的流程是Alice给Bob发出一个交易,随后Bob再花费这个交易,用来解释一下这些概念。Alice和Bob都会使用标准P2PKH(Pay-To-Public-Key_hash)交易类型的格式,P2PKH让Alice将比特币支付到一个比特币地址,随后Bob再进一步地通过一个简单的密钥对花费这些比特币。


 在Alice还不能创建第一个交易时,Bob必须先生成一个私钥/公钥对。比特币使用了带secp256k1标准所定义的椭圆曲线数字签名,其中secp256k1私钥是一个256位的随机数,该随机数的副本会转换为一个secp256k1的256位公钥。由于该转换在以后可以很可靠的重复尽心,因此该公钥不必保存。

公钥随后被哈希加密,该公钥哈希在以后也是可以可靠重复进行,因此它也是不必保存的。该哈希对公钥进行压缩混淆,使得人工转录更容易并且对意外问题提供了安全保障,它允许在随后的点从公钥数据中重构私钥。

Bob将公钥哈希提供给Alice。公钥哈希几乎都是以比特币地址的形式被编码发送的,它是一个base58编码字符串,包含了地址版本号,哈希值和一个抓取错误字的错误检测校验值。

该地址可通过任何媒介发送,包括禁止发送者与接收者沟通的一次性媒介,并且它可以被更为深入的编码进其他格式,比如包含一个bitcoin: URL的QR代码。

当Alice获得该地址并且将它解码回到一个标准哈希中时,她就能够创建第一个交易了。她创建一个标准P2PKH交易输出,这个输出包含一些指令,这些指令允许那些能够证明自己控制了Bob哈希公钥对应的私钥的人花费那个输出。这些指令叫做公钥脚本。

Alice广播该交易到网络中同时该交易被添加到区块链中,网络将它归类未花费事务输出(UTXO),同时Bob的钱包软件显示它是一个可花费的spendable货币。

一段时间后,Bob决定花费这个UTXO了,他需要创建一个输入来指向Alice创建的交易(叫做一个txid)和那个Alice用它的索引号(输出索引)使用的特定输出。他随后需要创建一个签名脚本,这个脚本是一个数据的集合,用于满足Alice放在前一个输出公钥脚本中的条件。

 公钥脚本和签名脚本将secp256k1公钥,签名和条件逻辑相结合创建一个可编程的授权机制。

对于一个P2PKH类型的输出,Bob的签名脚本需要包含以下两部分数据:

1.他的完全公钥(没有经过哈希的),所以公钥脚本必须能够检测出自己哈希出的公钥哈希值是和Alice提供的是一样的。

2.通过使用椭圆曲线算法加密公式得出的一个secp256k签名,用来将某些交易数据和Bob的私钥相结合。这可以让公钥脚本验证Bob创建该公钥的私钥。

 Bob的secp256k1签名不仅仅是用来证明Bob控制了他的私钥;它还得到他的交易防篡改的非签名脚本部分,因此Bob能够安全地将它们广播到网络的所有节点。

 如上图所示,Bob所签名的数据包含txid和前一个交易的输出索引vout,以及前一个输出公钥脚本,Bob所创建的公钥脚本,它会让下一个接收者花费这个交易的输出,下一个接收者要花费的比特币数量。本质上,除了签名脚本意外,完整的被签名的交易是保存着完整的公钥和secp256k1签名。

在将他的签名和公钥放进签名脚本后,Bob将该交易通过p2p节点广播给网络上的比特币矿工。每个节点和矿工在进一步广播该交易前都会独立的验证该交易或者尝试将它包含进一个新的交易区块中去。

这里使用regtest来进行交易演示:

> bitcoind -regtest -daemon


在regtest模式下启动bitcoind创建一个私有区块链:

>bitcoin-cli -regtest generate 101

使用regtest模式下的RPC命令创建101个区块,在通用PC上需要不到1s即可完成。这是使用比特币默认规则创建的新区块链,第一个区块得到50个比特币的奖励。不像主网的奖励分配机制,在regtest模式下只有在前150个区块能得到50个比特币奖励。

这里一个区块要得到100个确认才能消费奖励的比特币,所以这里才生成101个区块去确认区块1中的基础比特币交易。

输入以下命令查看获得的比特币:

>bitcoin-cli -regtest getbalance

50.00000000

 现在可消费的比特币有50个了。

 regtest钱包和区块链状态(chainstate)都保存在比特币核心配置目录的regtest子目录下。

也可以安全地删除子目录regtest并重启比特币核心程序来启动一个新的regtest。

 记得在做任何危险操作(比如删除)时备份主网钱包

 交易Transactions:

创建交易是比特币程序最重要的任务,这里介绍如何使用比特币核心RPC接口来创建具有不同属性的交易。

简单RAW交易:

比特币核心提供了一些RPC命令用于处理所有的花费细节,包括创建零钱输出以及花费合法的比特币,高级用户会使用这些RPC命令来降低丢失satoshis的概率。

> bitcoin-cli -regtest getnewaddress

> NEW_ADDRESS=mvbnrCX3bg1cDRUu8pkecrvP6vQkSLDSou(以图片中地址为准)

上面是获取一个新的比特币地址并且将它存储在shell变量$NEW_ADDRESS中

> bitcoin-cli -regtest sendtoaddress$NEW_ADDRESS 10.00

上面是使用RPC sendtoaddress发送10个比特币到该地址,返回的十六进制字符串为交易id(txid)

上面的sendtoaddress RPC自动选择一个未花费的交易输出(UTXO == unspent transaction output)。在该条件下,它从可使用的UTXO撤回satoshis,对于区块1的基础比特币交易是随着区块101的创建而成熟的。要花费这一个特定的UTXO,可使用sendfrom RPC。

 交易id:用来独一无二的标识一个交易,它是该交易的sha256d哈希。

交易id不要和Outpoint混淆,Outpoint为带有vout的txid是用来表示一个特定output的。

如何计算该交易ID可参考如下网址:

https://bitcoin.stackexchange.com/questions/32765/how-do-i-calculate-the-txid-of-this-raw-transaction

 > bitcoin-cli -regtest listunspent

上面使用listunspent RPC来显示属于该钱包的UTXO,该列表返回为空是因为它默认值显示被确认的UTXO(因为只会花费已被确认的UTXO)

 > bitcoin-cli -regtest listunspent 0

 上面重新运行带参数0的listunspent命令则显示未被确认的交易,这里显示有两个带有同样txid的UTXO。

第一个UTXO显示一个零钱输出,它是由sendtoaddress从交易池中使用新地址创建的。

第二个UTXO显示对该地址的花费,若将那些satoshis花费给其他人,则第二个交易不会在UTXO列表中显示出来。

 > bitcoin-cli -regtest generate 1

> unset NEW_ADDRESS

上面创建一个新区块来确认交易(耗时不到1s)同时清除shell变量。

  RAW交易的RPC允许用户创建客户端交易,同时允许延迟广播这些交易。然而,由raw交易产生的错误可能不会被比特币核心检测到,并且大量的raw交易用户已经永久丢失了大量的satoshis,因此在主网上使用raw交易时需要格外小心。

 下面是一个简单的raw交易过程:

> bitcoin-cli -regtest listunspent

> UTXO_TXID=cff77a1452edd7c552……a7e087 (这里需要输入完整的txid,此处省略)

> UTXO_VOUT=0

 重新运行命令listunspent,显示有3个已被确认的UTXO,前面创建的两个未花费交易事务输出,再加上上面创建的确认区块的coinbase交易。这里再将后面的这个确认区块的txid和coinbase的输出索引号vout保存到shell变量。

 > bitcoin-cli -regtest getnewaddress

> NEW_ADDRESS=2N4A……Y7W2

为RAW交易创建一个新地址

 >./src/bitcoin-cli -regtestcreaterawtransaction '''

    [

     {

       "txid": "'$UTXO_TXID'",

       "vout": '$UTXO_VOUT'

     }

    ]

   ''' '''

    {

     "'$NEW_ADDRESS'": 49.9999

   }'''

 > RAW_TX=02000000……000000


RAW_TX=需要拷贝完整的hex,前面的[…]为错误方式。

使用createrawtransaction的两个参数创建一个raw格式的交易。

第一个参数(一个json数组)表示区块2  coinbase的txid以及那个未花费交易事务输出的索引号(0)。

第二个参数表示(一个json对象)创建一个对方的比特币地址(公钥哈希)和比特币数量的输出。

最后将结果raw格式的交易保存到shell变量中。

 提示:createrawtransaction不会自动创建找零输出,所以可以很容易地花费一大笔交易费用。在本例中,输入有50比特币同时输出($NEW_ADDRESS)只花费了49.9999比特币,因此0.0001个比特币用于交易费。若只转账10比特币给$NEW_ADDRESS并且在该交易中没有任何找零,则交易费用将高达40比特币。可看一下下面的复杂raw交易部分是如何创建一个带多输出的交易,因此能够返回找零。

> bitcoin-cli -regtestdecoderawtransaction $RAW_TX

上面是使用decoderawtransaction来查看刚刚创建的交易实质内容。

 > bitcoin-cli -regtestsignrawtransaction $RAW_TX

> SIGNED_RAW_TX=020000……000000

上面使用signrawtransaction对用createrawtransaction创建的交易进行数字签名,同时

将返回的hex 根式raw签名保存到shell变量SIGNED_RAW_TX在中。

到目前为止,虽然交易完成,但是连接的比特币核心对该交易一无所知,其他网络对此同样一无所知。

虽然已经创建了一个花费,但实际上没有进行任何花费,而且只需要清空一下$SIGNED_RAW_TX变量就能消除该交易。

 > bitcoin-cli -regtestsendrawtransaction $SIGNED_RAW_TX

c7736a0a0046d5a8cc61c8c3c2821d4d7517f5de2bc66a966011aaa79965ffba

 上面使用命令sendrawtransaction将已签名的交易发送到所有连接的临近节点,这些临近节点在接收完该交易后,通常会将它广播到它自己的临近节点,但实际上因为这里使用的是regtest模式,所以不会连接到其他节点的。

 > bitcoin-cli -regtest generate 1

> unset UTXO_TXID UTXO_VOUT NEW_ADDRESSRAW_TX SIGNED_RAW_TX

这里创建一个确认区块以确认该交易并清除所有shell变量

复杂RAW交易:

若两个输入分别属于不同的人并且这两人又都同意一起创建一个交易(比如CoinJoin交易),因此创建一个具有两个输入和两个输出的交易,而且单独对每个输入作数字签名很有必要。

> bitcoin-cli -regtest listunspent

[

    {

       "txid" : "263c018582731ff54dc72c7d67e858c002ae298835501d\

                  80200f05753de0edf0",

       "vout" : 0,

       "address" : "muhtvdmsnbQEPFuEmxcChX58fGvXaaUoVt",

       "scriptPubKey" : "76a9149ba386253ea698158b6d34802bb9b550\

                          f5ce36dd88ac",

       "amount" : 40.00000000,

       "confirmations" : 2,

       "spendable" : true,

       "solvable" : true

   },

    {

       "txid" : "263c018582731ff54dc72c7d67e858c002ae298835501d\

                  80200f05753de0edf0",

       "vout" : 1,

       "address" : "mvbnrCX3bg1cDRUu8pkecrvP6vQkSLDSou",

       "account" : "",

       "scriptPubKey" : "76a914a57414e5ffae9ef5074bacbe10a320bb\

                          2614e1f388ac",

       "amount" : 10.00000000,

       "confirmations" : 2,

       "spendable" : true,

       "solvable" : true

   },

    {

       "txid" : "78203a8f6b529693759e1917a1b9f05670d036fbb12911\

                  0ed26be6a36de827f3",

       "vout" : 0,

       "address" : "n2KprMQm4z2vmZnPMENfbp2P1LLdAEFRjS",

       "scriptPubKey" : "210229688a74abd0d5ad3b06ddff36fa9cd8ed\

                          d181d97b9489a6adc40431fb56e1d8ac",

       "amount" : 50.00000000,

       "confirmations" : 101,

       "spendable" : true,

       "solvable" : true

   },

    {

       "txid" : "c7736a0a0046d5a8cc61c8c3c2821d4d7517f5de2bc66a\

                  966011aaa79965ffba",

       "vout" : 0,

       "address" : "mz6KvC4aoUeo6wSxtiVQTo7FDwPnkp6URG",

       "account" : "",

       "scriptPubKey" : "76a914cbc20a7664f2f69e5355aa427045bc15\

                          e7c6c77288ac",

       "amount" : 49.99990000,

       "confirmations" : 1,

       "spendable" : true,

       "solvable" : true

    }

]

 >UTXO1_TXID=78203a8f6b529693759e1917a1b9f05670d036fbb129110ed26[...]

> UTXO1_VOUT=0

>UTXO1_ADDRESS=n2KprMQm4z2vmZnPMENfbp2P1LLdAEFRjS

 > UTXO2_TXID=263c018582731ff54dc72c7d67e858c002ae298835501d80200[...]

> UTXO2_VOUT=0

>UTXO2_ADDRESS=muhtvdmsnbQEPFuEmxcChX58fGvXaaUoVt

 

针对两个输入这一点,这里就选择两个UTXO的txid和output索引号(VOUT),并将它们分别存储在两个shell变量中,另外存储交易中等会儿要使用到的对应公钥(哈希或未哈希的)的地址。地址的作用是能够通过它从钱包中获取到对应的私钥。(这里按照前面的非截图部分代码操作,截图部分代码显示不完整)

 > bitcoin-cli -regtest dumpprivkey$UTXO1_ADDRESS

> bitcoin-cli -regtest dumpprivkey$UTXO2_ADDRESS

> UTXO1_PRIVATE_KEY=cUf……uAJP

> UTXO2_PRIVATE_KEY=cT3......NMky

 

使用命令dumpprivkey获取将要转账的两个UTXO对应公钥的私钥,因为要分别对两个输入进行签名需要私钥。到此,交易的输入部分准备完毕!

提示:用户不要在主网上人工管理私钥。正如与raw交易一样,若私钥有误则会引起更加严重的问题,类似一个HD钱包(分层确定性钱包)的跨代私钥妥协(hardened extended key)的情形一样。

> bitcoin-cli -regtest getnewaddress

> bitcoin-cli -regtest getnewaddress

> NEW_ADDRESS1=2N……v1zj

> NEW_ADDRESS2=2N……bex

上面是创建转账接收方的两个新地址用于接收比特币。

 ## Outputs - inputs = transaction fee, soalways double-check your math!

> bitcoin-cli -regtestcreaterawtransaction '''

    [

     {

       "txid": "'$UTXO1_TXID'",

       "vout": '$UTXO1_VOUT'

     },

     {

       "txid": "'$UTXO2_TXID'",

       "vout": '$UTXO2_VOUT'

     }

    ]

   ''' '''

    {

     "'$NEW_ADDRESS1'": 79.9999,

     "'$NEW_ADDRESS2'": 10

   }'''

> RAW_TX=0200000……000000

上面创建raw交易,和前面简单RAW交易方法一样。

 > bitcoin-cli -regtest signrawtransactionwithkey$RAW_TX '''

    [

     "'$UTXO1_PRIVATE_KEY'"

   ]'''

> PARTLY_SIGNED_RAW_TX=020000000……000000

 

上面对交易签名与之前简单RAW交易方法不一样。这里有3个参数:

1.未签名的raw交易

2.一个空的数组。在这个操作中这个参数没起到任何作用,但是必须提供一些有效的JSON给随后的位置参数。

3.一个用于对输入签名的私钥。

结果是只有一个输入被签名;实际是若交易有没有被完全签名是可以通过JSON字段complete看出来的。将这个签名不完整的raw交易hex存储到一个shell变量中去。

> PARTLY_SIGNED_RAW_TX=02000000….00000 (此处为UTXO1签名的hex字段值)

> bitcoin-cli -regtestsignrawtransactionwithkey $PARTLY_SIGNED_RAW_TX '''

    [

     "'$UTXO2_PRIVATE_KEY'"

   ]'''

则会出现以下图片所示的下半部分的情形:

 

上面是在第一个签名的基础上对第二个输入进行签名,此时的字段complete为true,表示将两个UTXO都完整签名。

 > unset PARTLY_SIGNED_RAW_TX RAW_TXNEW_ADDRESS1 [...]

上面清除所有使用到的shell变量,这里不会用sendrawtransaction将该交易送到连接的临近节点。因为下面还要用它来演示在离线签名中如何花费没出现区块链或者说内存池中的交易。

离线签名:

这里来演示花费前面复杂raw交易所创建的UTXO。这与使用离线签名的钱包程序的逻辑是一样的,也就是在不访问当前UTXO集合的前提下对一个交易进行签名。

离线签名是安全的,而且在本例中还会花费不是在区块链中的输出,因为包含该输出的交易还没有被广播出去。

但是这可能会不安全,因为花费未经确认交易事务输出容易受到交易延展性的影响。

在主网上花费未确认交易事务输出之前确保深刻了解交易延展性。

 >OLD_SIGNED_RAW_TX=0100000002f327e86da3e66bd20e1129b1fb36d07056\

     f0b9a117199e759396526b8f3a20780000000049483045022100fce442\

     ec52aa2792efc27fd3ad0eaf7fa69f097fdcefab017ea56d1799b10b21\

     02207a6ae3eb61e11ffaba0453f173d1792f1b7bb8e7422ea945101d68\

     535c4b474801fffffffff0ede03d75050f20801d50358829ae02c058e8\

     677d2cc74df51f738285013c26000000006b483045022100b77f935ff3\

     66a6f3c2fdeb83589c790265d43b3d2cf5e5f0047da56c36de75f40220\

     707ceda75d8dcf2ccaebc506f7293c3dcb910554560763d7659fb202f8\

     ec324b012102240d7d3c7aad57b68aa0178f4c56f997d1bfab2ded3c2f\

     9427686017c603a6d6ffffffff02f028d6dc010000001976a914ffb035\

     781c3c69e076d48b60c3d38592e7ce06a788ac00ca9a3b000000001976\

     a914fa5139067622fd7e1e722a05c17c2bb7d5fd6df088ac00000000

 

上面是将已经签名但还未发出的交易事务输出存储到一个shell变量中

 > bitcoin-cli -regtestdecoderawtransaction $OLD_SIGNED_RAW_TX

{

   "txid" :"682cad881df69cb9df8f0c996ce96ecad758357ded2da03bad\

              40cf18ffbb8e09",

   "hash" :"682cad881df69cb9df8f0c996ce96ecad758357ded2da03bad40cf18ffbb8e09",

   "size" : 340,

   "vsize" : 340,

   "version" : 1,

   "locktime" : 0,

   "vin" : [

       {

           "txid" : "78203a8f6b529693759e1917a1b9f05670d036fbb1\

                     29110ed26be6a36de827f3",

           "vout" : 0,

           "scriptSig" : {

                "asm" :"3045022100fce442ec52aa2792efc27fd3ad0ea\

                        f7fa69f097fdcefab017ea56d1799b10b210220\

                        7a6ae3eb61e11ffaba0453f173d1792f1b7bb8e\

                        7422ea945101d68535c4b474801",

                "hex" :"483045022100FCE442ec52aa2792efc27fd3ad0\

                        eaf7fa69f097fdcefab017ea56d1799b10b2102\

                        207a6ae3eb61e11ffaba0453f173d1792f1b7bb\

                        8e7422ea945101d68535c4b474801"

            },

           "sequence" : 4294967295

       },

       {

           "txid" : "263c018582731ff54dc72c7d67e858c002ae298835\

                     501d80200f05753de0edf0",

           "vout" : 0,

           "scriptSig" : {

                "asm" :"3045022100b77f935ff366a6f3c2fdeb83589c7\

                        90265d43b3d2cf5e5f0047da56c36de75f40220\

                        707ceda75d8dcf2ccaebc506f7293c3dcb91055\

                        4560763d7659fb202f8ec324b01

                         02240d7d3c7aad57b68aa0178f4c56f997d1bfa\

                        b2ded3c2f9427686017c603a6d6",

                "hex" :"483045022100b77f935ff366a6f3c2fdeb83589\

                        c790265d43b3d2cf5e5f0047da56c36de75f402\

                        20707ceda75d8dcf2ccaebc506f7293c3dcb910\

                        554560763d7659fb202f8ec324b012102240d7d\

                        3c7aad57b68aa0178f4c56f997d1bfab2ded3c2\

                        f9427686017c603a6d6"

           },

            "sequence" : 4294967295

       }

   ],

   "vout" : [

       {

           "value" : 79.99990000,

           "n" : 0,

           "scriptPubKey" : {

                "asm" : "OP_DUPOP_HASH160 ffb035781c3c69e076d48\

                         b60c3d38592e7ce06a7OP_EQUALVERIFY OP_CHECKSIG",

                "hex" :"76a914ffb035781c3c69e076d48b60c3d38592e\

                         7ce06a788ac",

                "reqSigs" : 1,

                "type" :"pubkeyhash",

                "addresses" : [

                   "n4puhBEeEWD2VvjdRC9kQuX2abKxSCMNqN"

                ]

           }

       },

       {

           "value" : 10.00000000,

           "n" : 1,

           "scriptPubKey" : {

                "asm" : "OP_DUPOP_HASH160 fa5139067622fd7e1e722\

                         a05c17c2bb7d5fd6df0OP_EQUALVERIFY OP_CHECKSIG",

                "hex" :"76a914fa5139067622fd7e1e722a05c17c2bb7d\

                         5fd6df088ac",

                "reqSigs" : 1,

                "type" :"pubkeyhash",

                "addresses" : [

                   "n4LWXU59yM5MzQev7Jx7VNeq1BqZ85ZbLj"

                ]

           }

       }

    ]

}

 >UTXO_TXID=682cad881df69cb9df8f0c996ce96ecad758357ded2da03bad40cf18ffbb8e09

> UTXO_VOUT=1

> UTXO_OUTPUT_SCRIPT=76a914fa5139067622fd7e1e722a05c17c2bb7d5fd6df088ac00000000

 

 

对该交易的签名进行解码,得到它的txid。选择花费某个UTXO并将该UTXO输出索引号(vout)和hex公钥脚本(scriptPubKey)存储到shell变量中。

 > bitcoin-cli -regtest getnewaddress

> NEW_ADDRESS=2N5h……wP4v

创建一个新地址用于将比特币转账到该地址

## Outputs - inputs = transaction fee, soalways double-check your math!

> bitcoin-cli -regtestcreaterawtransaction '''

    [

     {

       "txid": "'$UTXO_TXID'",

       "vout": '$UTXO_VOUT'

     }

    ]

   ''' '''

    {

     "'$NEW_ADDRESS'": 9.9999

   }'''

> RAW_TX=020000…..00000

如之前一样创建一个新交易。

> bitcoin-cli -regtest signrawtransactionwallet$RAW_TX

这里尝试不带任何参数给新交易签名,如前面在简单raw交易中所做的一样,在这里失败了,结合前面的UTXO专业版介绍可知为什么会失败。

 如上图所示,要签名的数据包含前一个交易的txid和vout,这些信息包含在createrawtransaction的raw交易中了。但是要签名的数据还要包含前一个交易的公钥,即使它既没有出现在未签名也没有出现在签名的交易中,但还是需要这么一个东西在才行。

在上面的另一个raw交易中,前一个输出为钱包所知的UTXO的一部分,所以钱包能够通过txid和输出索引号来找到前一个公钥脚本并自动将它插入。

在此情况下,就花费了一个钱包并不知道的输出,因为它不会自动插入前一个公钥脚本。

> bitcoin-cli -regtestsignrawtransaction $RAW_TX '''

    [

     {

       "txid": "'$UTXO_TXID'",

       "vout": '$UTXO_VOUT',

       "scriptPubKey": "'$UTXO_OUTPUT_SCRIPT'"

     }

   ]'''

{

   "hex" :"0100000001098ebbff18cf40ad3ba02ded7d3558d7ca6ee96c9\

            90c8fdfb99cf61d88ad2c68010000006b483045022100c3f92f\

            b74bfa687d76ebe75a654510bb291b8aab6f89ded4fe26777c2\

            eb233ad02207f779ce2a181cc4055cb0362aba7fd7a6f72d5db\

            b9bd863f4faaf47d8d6c4b500121028e4e62d25760709806131\

            b014e2572f7590e70be01f0ef16bfbd51ea5f389d4dffffffff\

            01f0a29a3b000000001976a914012e2ba6a051c033b03d712ca\

            2ea00a35eac1e7988ac00000000",

   "complete" : true

}

>SIGNED_RAW_TX=0100000001098ebbff18cf40ad3ba02ded7d3558d7ca6ee9[...]

用前一个公钥脚本和其他要求的输入数据成功对该交易进行签名。

一般离线签名钱包都要做该操作。线上钱包为输入创建raw交易并获取前一个公钥脚本。

用户将这些信息带到离线钱包。在向用户显示交易细节后,离线钱包就会像上面一样对该交易做签名。用户将签名的交易返回到在线钱包并对它进行广播。

> bitcoin-cli -regtestsendrawtransaction $SIGNED_RAW_TX

error:{"code":-22,"message":"TX rejected"}

 还没广播第一个交易就尝试广播第二个交易,由于第二个交易在花费一个节点并不知道的UTXO输出,所该节点拒绝该尝试。

 > bitcoin-cli -regtestsendrawtransaction $OLD_SIGNED_RAW_TX

682cad881df69cb9df8f0c996ce96ecad758357ded2da03bad40cf18ffbb8e09

> bitcoin-cli -regtestsendrawtransaction $SIGNED_RAW_TX

67d53afa1a8167ca093d30be7fb9dcb8a64a5fdecacec9d93396330c47052c57

 广播第一个交易成功,随后广播第二个交易,由于节点能够看到该UTXO,所以

也成功。

 > bitcoin-cli -regtest getrawmempool

[

   "67d53afa1a8167ca093d30be7fb9dcb8a64a5fdecacec9d93396330c47052c57",

   "682cad881df69cb9df8f0c996ce96ecad758357ded2da03bad40cf18ffbb8e09"

]

由于还没有生成一个额外的区块,因此上面的交易还不会被添加到regtest区块链中,但是

它们已经是本地节点内存池的一部分了。

> unset OLD_SIGNED_RAW_TX SIGNED_RAW_TXRAW_TX [...]

删除旧的shell变量。

参考网址:

https://bitcoin.org/en/developer-examples#offline-signing


猜你喜欢

转载自blog.csdn.net/huhaoxuan2010/article/details/80183563