交易

“交易”是用户使用比特币的过程。每个交易由几个部分构成,一个交易即可以是简单的直接支付,也可以是复杂的交易。本小节会描述交易的每一个部门,而且说明怎样把各个部分合起来构建成一个完整的交易。

为了简单起便,本小节假设 coinbase transactions 不存在。 coinbase transactions只能被矿机创建并且这些交易是下面很多规则的例外。与其一一指出 coinbase transactions交易的例外,我们更加建议读者阅读本指南”区块链”一章里面关于 coinbase transactions的内容。

上面的图标说明了比特币交易的核心部分。每个交易至少由一个input和一个output。每个input 使用上一个oupt的比特币作为输入。每个output作为未花费的output【Unspent Transaction Output (UTXO)】等待着,直到一个最近的inptut使用这个output。当你的比特币钱包告诉你余额有10BTC时,它真正的意思是说你有10BTC在一个或者多少UTXOs等待被花费出去。

每个交易的前缀由四个字节的“交易版本序号”组成,这可以让比特币节点和矿工知道本交易适用于哪些规则。这也方便开发者未来开发出的新规则和旧规则能相兼容。

下面的图帮助你了解交易的一些特点,图示的工作流是ALICE发送了一个交易给BOB,稍后BOB又把这个交易发送出去。ALICE和BOB使用的都是最常见的标准交易类型,Pay-To-Public-Key-Hash (P2PKH)。P2PKH 让ALICE可以把比特币发送到一个典型的比特币地址上,然后又让BOB可以进一步通过简单的密钥对把比特币发送出去。

在ALICE创建第一笔交易前,Bob必须先生成公钥/私钥对。标准的比特币私钥是长度为256b的随机字符串。一份私钥的数据,最后会经过变成而成“公钥”。因为那个变形可以在稍后重复进行,所以公钥并不需要保存。

接着公钥将加密并未得哈希值。公钥的哈希值同样也可以再次求出来,所以也不需要保存。哈希值比较短,而且也经过混淆,使得手工抄写比较简单,而且也提供了对抗未知问题的安全性,这些未知问题如允许在往后的日子里面通过公钥重新构造成私钥。

BOB把公钥的哈希值提供给ALICE。公钥哈希就是大家所熟知的编码过的比特币地址,编码采用的base58进行,里面包含了一个版本序号、哈希值以及一个用来校验错误的值。比特币地址可能通过任何介质传播,当然也包括单向的介质,这样可以切断发款人和收款人的联系,比特币地址还可以被进一步编码成其它的格式,比如包括”bitcoion:”的二维码地址。

只要ALICE拿到地址,而且解码还原为原来的标准哈希值,她就可以创建第一笔交易了。她创建了一个标准的P2PKH交易输出,里面包括了可以让任何人花费这笔输出的“说明”,只这他能证明他拥有与BOB的公钥对应的私钥。那些说明,称为“script”。

ALICE广播那一笔交易并且把它添加到区块链。比特币网络会收集这一笔交易,并把它作为未花费的交易【Unspent Transaction Output (UTXO),】,并在bob的钱包软件显示为可以使用的余额。

未来,如果bob决定把那个UTXO用出去,他必须创建一个引用了ALICE创建的交易的哈希值(这个哈希值被称为交易标识(txid)),而且还要引用ALICE使用过的那一笔的输出索引。BOB必须创建一个“scriptSig”, scriptSig是指一个包括满足ALICE之前那笔交易设置的条件的数据集合。

Bob并不需要和ALICE进行沟通,BOB只要简单地在比特币点对点网络上证明,他可以满足那些script指定的条件就可恼。对于P2PKH类型的输出,BOB的scriptSig只需要包含下面两个条件即可:

  1. 他提供的完整公钥(未经过哈希运算),以便于script 可以通过重新求得哈希值来验证ALICE提供的哈希值是否一致。

  2. 一个用bob提供私钥采用ECDSA 加密算法计算出的包含确定的交易数据的签名。这个签名用来验证BOB是否拥有那个生成公钥的私钥。

Bob的签名不仅仅是证明BOB拥有他的私钥,签名同时还保证了BOB的签名不被篡改,这样就可以在比特币P2P网络安全地进行广播。

如上图所示,bob的签名包括了txid和前一个交易的输出索引、前一个交易的script、bob创建的可以让下一个接收者花费它这个output的script、总共要转给下一具接者的比特币数量。本质上来说,整个交易都被签名除了scriptSigs,因为scriptSigs持有了公钥以及相关的signatures。

把他的signature 和公钥放到scriptSig后,BOB通过P2P网络广播这一笔交易给比特币矿机。在进一步广播以及被包含进区块链之前,每一个节点和矿机都独立地验证这一笔交易。

P2PKH Script Validation

验证的过程需要重新评估script。在一个P2PKH类型的output里面,script 如下:

OP_DUP OP_HASH160 <PubkeyHash> OP_EQUALVERIFY OP_CHECKSIG

发送者的scriptSig 是经过验证的,而且也是script的开头部分。在一个标准的P2PKH 交易中,scriptSig 包含了一个signature 以及一个完整公钥创建的字符串连接:

<Sig><PubKey> OP_DUP OP_HASH160 <PubkeyHash> OP_EQUALVERIFY OP_CHECKSIG

script 语言是一种类似Forth的基于堆栈的语言,它是故意设计成与状态无关且是”图灵不完备”(Turing complete 图灵完备)的。与其它状态无关保证了只要交易一写入到区块链,就没有其它条件可以把使其永远不可以花费出去。”图灵不完备”可以使得script 语言少了些灵活性,而且预测性更加地好,这极大简化了安全模块。

为了测试一个交易是否合法,一次只把一个scriptSig 和 script的参数推入到堆栈里面去,从bob的scriptSig 开始,以Alice的 script结束。下面的图展示了怎样检验一个标准的 P2PKH script;图示下面是这个过程的描述。

signature (由BOB的scriptSig生成)被添加到一个空栈里面。因为只是一些数据,所以只是简单地把这些数据添加到栈,并没有做其它东西。公钥(同样也从scriptSig获得)被添加到signature的上面。

  • 从ALICE的script里面,操作OP_DUP被添加到堆栈。OP_DUP用下一层的数据把自己替换掉,这样就把BOB提供的公钥复制了一次。

  • 接着继续操作一下个,OP_HASH160,用下一层的数据(也就是BOB的公钥)把自己替换掉。这样就创建了BOB公钥的哈希值。

  • 接着Alice的 script 把从她和BOB的第一笔交易得到的公钥哈希添加到堆栈里面。这样,堆栈顶部就有两份BOB的公钥哈希了。

  • 现在事情变得有趣起来:Alice’的script把OP_EQUALVERIFY添加到堆栈里面,OP_EQUALVERIFY可以展开为OP_EQUALOP_VERIFY(隐性地)。OP_EQUAL(隐性地)检查它下面的两个值;在这个例子里,它会检查BOB提供的完整公钥生成的公钥哈希是否和ALICE创建的交易#1相等。OP_EQUAL接着比较的结果:0(false)或者1(true)替换掉。OP_VERIFY(隐性地)马上检查它下面的值。如果值为false,则马上停止堆栈内的检查,宣告交易检验失败。否则,它会把自己以及true值抛出栈外。

  • 最后,ALICE的 script 把OP_CHECKSIG压入堆栈,用以对BOB提供的signature 和BOB提供的当前经过验证的公钥进行校验。如果signature 符合公钥,OP_CHECKSIG会被替换成true;

P2SH脚本

输出脚本由支付者创建,他们(钱花出去之后)不怎么关心他们消费的比特聪的长期安全或者对别人是否有用。收款人则关心输出脚本指定的条件。如果收款人愿意,他们可以请求支付者使用某种特定脚本。遗憾的是,定制的脚本没有短小的比特币地址方便,也不像P2PKH的公钥哈希方案( P2PKHpubkey hashes)那样容易保护。

为了解决这些问题,“支付到脚本哈希”(P2SH)交易在2012年被创建,它让支付者创建一个输出脚本,里边包含另一个脚本的哈希,另一个脚本称为“认领脚本”。

下图是基本的P2SH流程图,它看起来几乎与P2PKH流程一模一样。Bob随心所欲创建一个认领脚本,计算它的哈希,并把这个哈希值发给Alice。Alice创建一个P2SH风格的输出,其中包含Bob的认领脚本的哈希值。

当Bob想花费这个输出时,他把他的签名和完整的(序列化的)认领脚本放在输入的scriptSig域。比特币网络会确认,认领脚本被哈希后,得到的值与Alice输出里放的脚本哈希相同。然后网络会像处理原生脚本(即由Alice提供的脚本)那样,处理认领脚本,如果返回值为真,则允许Bob花费这个输出。

认领脚本的哈希和公钥哈希有相同的属性(主要指长度和范围),所以它可以被转换为标准的比特币地址格式,除了加上一点点小的变化,把它和标准地址区分开。这让接收P2SH风格的地址就像接收P2PKH风格的地址一样简单。哈希过程也混淆了认领脚本里的所有公钥,这使得P2SH脚本和P2PKH公钥哈希一样安全。

标准交易

必须小心避免非标准的输出脚本。按照比特币核心版本0.9,标准的脚本类型包括:

公钥哈希(P2PKH)

P2PKH是最常见的脚本形式,用于把交易发至一个或多个比特币地址。

script: OP_DUP OP_HASH160 <PubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
scriptSig: <sig> <pubkey>

脚本哈希(P2SH)

P2SH用于把交易发至一个脚本哈希。每一种标准脚本都可以用在P2SH认领脚本里。但实践中,只有多重签名(multisig)脚本是有意义的,除非以后有更多的交易类型变成标准的。

script: OP_HASH160 <redeemscripthash> OP_EQUAL
scriptSig: <sig> [sig] [sig...] <redeemscript>

多重签名

虽然P2SH多重签名现在通常用于多重签名交易,这种基本脚本也可用于要求收集足多个签名,才能花费某个尚未花费的脚本输出。

在称为“n中选m个”的多重签名脚本里,m是需要匹配某个公钥的最少的签名个数,n是提供的公钥个数。m和n都应该是操作码OP_1OP_16中的一个值,(OP_x里的x)与期望的数目对应。

为了兼容原有的比特币实现中的一个“偏移1”的错误,OP_CHECKMULTISIG需从栈里消耗m+1个值(比指定的m个多一),所以scriptSig列表里的签名之前必须增加一个额外的值(OP_0),它会被OP_CHECKMULTISIG指令消耗掉,但是实际上并没有被用到。

script: <m> <pubkey> [pubkey] [pubkey...] <n> OP_CHECKMULTISIG
scriptSig: OP_0 <sig> [sig] [sig...]

虽然P2SH多重签名不是一个单独的交易类型,我们仍然把它单独列出,下面是个“3选2”的P2SH多重签名的例子:

script: OP_HASH160 <redeemscripthash> OP_EQUAL
redeemScript: <OP_2> <pubkey> <pubkey> <pubkey> <OP_3> OP_CHECKMULTISIG
scriptSig: OP_0 <sig> <sig> <redeemscript>

Pubkey公钥脚本

公钥脚本是P2PKH脚本的简化形式,但它们没有P2PKH脚本那么安全,所以在新的交易里通常不用它们了。

script: <pubkey> OP_CHECKSIG
scriptSig: <sig>

空数据

空数据脚本让你以支付一定交易费作为代价,添加一小块任意的数据到区块链里。但是这么做并不被鼓励。(之所以空数据脚本是一种标准脚本类型,只是因为有些人用更有害的方式往区块链里添加数据,所以这种方式被引入,以避免更有害的方式。)

script: OP_RETURN <data>
(Null data scripts cannot be spent, so there's no scriptSig)
(空数据脚本不能被花费,所以这里不存在scriptSig)

非标准交易

如果你使用任何非标准脚本,使用缺省比特币核心设置的对等节点和矿工不会接受或广播你的交易,也不会在挖矿时把你的交易放在区块里。当你试图把你的交易广播到一个使用缺省设置的对等节点时,你会收到一个错误。

不幸的是,如果你创建了非标准的认领脚本,把它哈希,并把哈希用在P2SH输出中时,网络只看到了哈希,所以它会把输出视为有效而接受,而不管认领脚本是不是真的有效。然而当你去花费这个输出时,使用缺省设置的对等节点和矿工会看到认领脚本是非标准的,而拒收它。这样就不可能花掉这个输出,除非你找到一个矿工,它禁用了缺省设置。

依照比特币核心版本0.9,标准交易必须额外满足如下条件:

  • 交易必须已敲定:要么它的锁定时间必须在过去(或者小于等于当前的块高度),要么它的所有序列号都必须是0xffffffff。

  • 交易必须少于100k字节。这大概是一个典型的单输入、单输出P2PKH交易的200倍大。

  • 交易的每个输入都必须小于500字节。这足以容纳P2SH“3选3”的多重签名交易。需要多于3个公钥的多重签名交易目前还不是标准的。

  • 交易的scriptSig 只能往脚本估值栈里推数据,不能推新的操作码,但可以推那些仅仅推数据入栈的操作码。

  • 交易的所有输出,接收的比特聪数目不得小于一个预定义的最小值,当前这个值是546。

签名哈希类型

OP_CHECKSIG从每个它检查的签名里提取一个非入栈参数,这个参数让签名者可以指定签署交易的哪些部分。由于签名保护了这些部分不被修改,这让签名者们(一个交易可以有多个签名者)选择性的允许他人修改他们的交易。 指定签哪些部分的选项被称为签名哈希类型。目前有三种基本签名哈希类型:

  • SIGHASH_ALL, 缺省值,签署所有输入和输出,保护除scriptSig外的所有域不被修改。

  • SIGHASH_NONE签署所有的输入,但不签署任何输出。它允许任何人改变比特聪的去向,除非其他签名用了其他签名哈希类型来保护这些输出。

  • SIGHASH_SINGLE只签署当前输入和一个对应的输出(与这个输入编号相同的输出),保证没人可以更改你那部分交易,但允许其他签名者改变他们那些部分的交易。对应的输出必须存在,否则就会签第一个输出,这样会破坏这个安全机制。

上述基本类型可以用SIGHASH_ANYONECANPAY(任何人均可支付)来修饰,产生三种新的合并类型:

  • SIGHASH_ALL|SIGHASH_ANYONECANPAY 签署所有输出,但是只签署当前输入,它也允许别人增加或移除其他输入,所以任何人都可以贡献额外的比特聪,但是他们不能改变比特聪发送的数目及去向。

  • SIGHASH_NONE|SIGHASH_ANYONECANPAY只签署当前输入,同时允许任何人添加或移除其他输入或输出,所以任何人拿到这个输入的拷贝,都可以随意花费它。

  • SIGHASH_SINGLE|SIGHASH_ANYONECANPAY只签署当前输入和它对应的输出,但它也允许别人添加或移除其他输入。

因为每个输入都被签名了,一个有多个输入的交易可以拥有多个签名哈希,它们分别签署交易的不同部分。比如,一个用NONE标识签名的单输入交易,把它加到区块链的矿工可以改变它的输出。另一方面,如果一个双输入交易的一个输入被用NONE签名,另一个输入被ALL签名,那么签ALL的人可以随意改变比特聪的去向,无需征求NONE签名者的意见,但是其他任何人都不能改变交易。

时间锁定和事件序列号

所有哈希类型的签名标志是交易的时间锁定。该时间锁定表示最早被添加到区块链的交易。

时间锁定此功能允许签署者们创建一个只在未来生效且锁定时间的交易,给签署者们一个改变主意的机会。

如果任一签署者改变主意了,他们能创建一个没有时间锁定的交易。新的交易将使用一个作为输入,一个作为输出,相同的输出作为输入的时间锁定交易。这使得如果新的交易在锁定时间生效前被添加到区块链中,那该交易将失效。

在时间锁定生效前必须小心。对等网络允许区块时间比实际快2小时,所以一个时间锁定的交易能在交易正式生效2小时前被添加到区块链。同时,区块不能创建一个被认可的区间,所以任何人试图取消一个有效的交易,都应在时间锁定生效前几个小时实施。

以前版本的比特币核心提供了一个功能,能防止交易签署者们使用上述方法来取消时间锁定的交易,但为了避免拒绝式服务攻击,这一功能的必要组成部分被禁用。这一功能体系的保留特征是在每个input里面的四个字节的序列号。事件序列号过去是允许多位签署者同意更新交易;当他们完成更新交易后,他们可以同意将每一个输入的事件序列号为无符号四字节最大值(0xffffffff),并且允许在时间还未到时将交易添加入区块内。

即使是今天,设置所有的序列号为0xffffffff(Bitcoin核心的默认设置)仍然可以禁用时间锁定,所以如果你想使用时间锁定的功能,至少一个输入必须有一个事件序列号低于最大值。由于事件序列号不被网络以任何目的所使用,所以设置任一事件序列号为零是足以锁定时间的。 时间锁定自身是一个无签名的4字节数字,可被两种方法所描述:

  • 如果少于5亿,时间锁定被描述成为区块高度。交易能被添加到有它甚至比它高的区块中。

  • 如果大于或等于5亿,时间锁定则使用Unix时间戳格式解析(自1970-01-01t00:00 开始目前超过13.95亿秒了)。交易可以添加到任何时间大于时间锁定的区块。

比特币转账手续费和变化

交易所花的交易费通常基于签名交易的总字节大小所决定。手续费是给比特币矿工的,就像《区块链》那章解释的那样,它最终取决于每个矿工所选择的他们能接受的最小额交易。

在默认情况下,矿工给每个高优先级的交易预留50KB,这些交易需要花费比特币,且并没有花很长时间。每个区块预留的空间通常是分配给基于每个字节的费用,具有较高支付能力的交易被添加进序列直到所有的可用空间被占用。

作为比特币核心0.9版本,每笔交易需要交最低手续费(现在是1000聪),并全网络广播,但这并不包括高优先级的交易。任何只缴纳最低费用的交易应准备等很长时间才能有足够的空闲区块来包括他。请参考《确认交易》那个小节来了解这为何很重要。

由于每笔交易花费未用完交易输出 (UTXOs)并且因为未用完交易输出 (UTXOs)只能用一次,所包含的未用完交易输出全值必须使用出去或给矿工作为交易费用。很少有人会将未用完交易输出完全匹配他们要支付的金额,所以大部分的交易包括“找零输出”。

找零输出为常规输出通常需要花费多余的聪从未用完交易输出给花费比特币的人。他们可以重新使用相同的p2pKH公开秘钥哈希值或者p2sh脚本哈希值用于未完成交易输出,但因某些原因(下一小节将会描述),改变输出并发送到一个新的p2pkh或p2sh地址被强烈推荐。

避免钥匙的重复使用

在一单交易中,交接双方每次都要向公众透露公钥或地址公布以便使用。这就允许每个人都能使用公共区块链来追踪过去和未来别人使用同一公钥或地址的交易。

如果同一公钥被重复使用多次,当人们使用比特币地址(散列的公共密钥)为静态的付款地址时,其他人可以轻松地跟踪接收该人的消费习惯,并且包括他们在已知的地址里掌控多少聪。

它不应该是这样的。如果每个公钥恰巧被用了2次,一次接受一次支付,那用户可以获得大量的金融隐私。

好的方案是,使用新的公钥或单一地址时,接受付款或改变输出可以结合其他讨论后的技术,诸如币的合并或者规避,使得使用区块链本身的可靠的追踪其他人使用比特币变得极为困难。

避免钥匙被重复使用也能避免从公钥重新构建私钥的安全攻击或者对于签名对比构建私钥(在如今某些特定情况下,下面将描述更多的的假设攻击)。

  1. 单一的(非重复使用)p2pkh和p2sh地址保护第一种类型的攻击,因为ECDSA公钥隐藏(散列)直到比特币第一时间传至地址,所以攻击无效,除非他们能在一到两小时内重新为新交易创建私钥,此时间过后中,交易被会很好地被区块链保护起来。

  2. 单一的(非重复使用)的私钥能保护第二种攻击,那种每个私钥只有一个签名, 所以攻击者没有后续的签名来使用的基于对比的攻击(译者注:对比每个签名,并试图根据签名的内容还原私钥)。现如今基于比较的攻击是唯一可行的,不足信息熵用于签名或当熵用的是以某种方式暴露,如侧信道攻击(side-channel attack)。

因此,即是对于隐私又是对于安全,我们建议你尽可能建立自己的应用来避免公钥重复使用.如果可能的话,对于防止用户复用地址。如果你的应用需要提供一个固定的URI,付款应发送,请看下面的Bitcoin:URI部分。

交易延展性

比特币的签名哈希类型都是不保护的script Sig,这为拒绝式服务攻击(交易延展性攻击)开了方便之门。script Sig包含签名,但不能自签,允许攻击者做出非功能性修改使得交易无效。例如,攻击者可以将一些数据添加到script Sig里面,这会使得script Sig在上一个输出处理脚本中被丢弃。

然而修改的都是非功能性的,即这不能改变交易的输入并且不改变交易的输出—但他们确实修改了交易的哈希数值。由于每笔交易连接到之前交易所使用的哈希值作为交易标示符(TxID),所以修改后的交易不会有创建者预期的交易标示符。

所以这个问题还不是比特币交易的最大问题,因为很多交易都是被立即添加区块链的。但它确实成为一个问题,但当一个交易的输出被添加到区块链之前就被花费了。

比特币研发者们一直致力于减少交易类型中的交易延展性,但一个完整的修复仍只在规划阶段。目前,新的交易不应依赖于先前还未添加到区块链的交易,尤其是如果当大量比特币还处于危险的情况下。

交易的延展性问题也能影响到付款追踪。比特币核心远程过程调用协议(RPC)的接口能让你通过交易标示符追踪交易, 但是如果交易标示符改变的话,由于交易已变动,就有可能发生交易在网络上消失。

目前交易追踪最佳的方法是追踪交易未用完交易输出,如输入交易,他们不能因无效的交易而改变。

未来最佳实践规定,如果一个交易消失于网络需要重新被修定,是因失去交易而被修定。最好的永远确保支付方式是花费所有的输出作为输入。

Last updated