比特币开发指南——交易

比特币开发指南——交易

交易使得用户花费聪。每笔交易都由几个部分构成,它们既能实现简易直接支付也能实现复杂的交易。本章将描述每一部分并演示如何一起使用构建完整交易。

为了使得事情简单,本章假设coinbase交易不存在。coinbase交易只能由比特币矿工创建,而且对于下面列举的许多规则来说它们被视之为异常。代之为每种规则指出coinbase交易异常,我们希望您能阅读coinbase交易章节。

image.png

上图展示了比特币交易的主要部分。每笔交易至少有一笔输入和输出。每笔输入花费之前交易中支付给输出的聪(就是说本笔交易的输入就是上一笔交易的输出)。每笔输出将作为未花费输出(UTXO)存在,直到后续交易的输入花费它。当比特币钱包告诉你你有10000聪零钱时,事实上你真的有10000聪在一笔或多笔UTXOs中。

每笔交易都添加了4字节的交易版本号前缀,交易版本号告诉节点和矿工使用哪套规则验证本次交易。这使得开发者能为将来的交易创建新的规则而不用担心以前的交易无效(新规则可能不兼容旧规则)。

image.png

输出都有一个基于在交易中的位置的索引号——第一个输出的索引是0。输出也包括支付给一个条件scriptPubKey的一定数量的聪。任何满足scriptPubKey条件的人都可以花费支付给他的一定数量的聪。

一笔输入使用交易标识符(txid)和输出索引号(也叫vout:output vector)标识一笔要被花费的特定的输出。也有一个提供数据参数满足scriptPubKey条件的签名脚本。(序列号和锁定时间后续子章节会谈到)

下图通过展示Alice支付给Bob一笔交易而Bob之后将会花费这笔交易的工作流,帮助理解这些特性是如何被使用的。Bob和Alice都使用最常见的标准的Pay-To-Public-Key-Hash(P2PKH)交易类型。P2PKH让Alice发送聪到一个比特币地址上,然后Bob使用一个简单的加密密钥对花费这些聪。

image.png

在Alice创建交易之前,Bob必须首先生成一个公私钥对。比特币使用了利用secp256k1曲线的椭圆曲线数字签名算法(ECDSA)。secp256k1私钥是256 bytes的随机数据。因为转换可以可靠地重复,所以公钥不需要保存。

公钥然后被加密hash。公钥hash可以稳定重复,所以也不需要保存。hash缩短和混淆公钥,使得手工抄写更加容易,而且提供安全免于遭受可能在之后的某个时间允许从公钥中重建私钥的问题。

Bob提供公钥hash给Alice。公钥hash几乎总是被编码作为比特币地址——base58编码的字符串,包含一个地址版本号、hash和一个捕获异常的错误检测校验和。地址可通过任意媒体传输,包括避免花费者和接受者通信的单向媒体,更进一步也可被编码成其他形式,比如包含bitcoin:URL的二维码。

一旦Alice拥有地址并解码成标准hash,就可以创建第一笔交易。她创建一个标准的P2PKH交易输出,包含了允许任何人花费该输出的指令,只要他们能提供与Bob的hash公钥相对应的私钥。这些指令叫做公钥脚本(pubkey script或scriptPubKey)。

Alice广播该交易,然后该交易被加入到区块链中。比特币网络把它划分为未花费输出(UTXO),而且Bob的钱包软件将之作为可花费余额展示出来。

一段时间以后,Bob决定花费这笔UTXO,他必须创建一个输入——Alice通过交易标识符(txid)创建的交易,和使用输出索引号标识的特定的输出。然后必须创建一个签名脚本——满足Alice在之前的输出scriptPubKey中设置的条件的数据参数集合。签名脚本也叫做scriptSigs。

scriptPubKey和scriptSigs联合secp256k1公钥和条件逻辑签名,创建一个可编程认证机制。

image.png

对于P2PKH类型的输出,Bob的scriptSigs包含两块数据:

  1. 他的完整公钥(未hash),以便scriptPubKey可以校验该公钥的hash值和Alice提供的公钥hash是否相同。
  2. 一个利用ECDSA加密公式结合具有Bob私钥的交易数据生成的secp256k1签名。使得scriptPubKey验证Bob拥有创建该公钥的私钥。

Bob的secp256k1签名不仅证明Bob控制着他的私钥;也使得他的交易的未scriptSigs部分免遭篡改以便Bob可以将交易安全传播给P2P网络。

image.png

如上图所示,Bob签名的数据包括txid、之前交易的输出索引、之前的scriptPubKey、Bob创建使得下一个接收者花费这笔交易输出的输出scriptPubKey、支付给下一个接受者的一定数量的聪。本质上,除了持有完整公钥和secp256k1签名的scriptSigs外,整个交易都被签名。

在把签名和公钥放到scriptSigs中后,Bob通过P2P网络把交易广播给矿工。在交易被纳入新的区块之前,每个节点和矿工独立验证交易。

P2PKH 脚本验证

验证过程需要scriptSigs和scriptPubKey的评定。在一个P2PKH输出中,scriptPubKey如下:

1
OP_DUP OP_HASH160 <PubkeyHash> OP_EQUALVERIFY OP_CHECKSIG

花费者的scriptSigs被评估并放到脚本开始的地方。在P2PKH交易中,scriptSigs包含一个secp256k1签名(sig)和完整公钥(pubkey),创建如下:

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

这种脚本语言是一种类似于Forth的基于堆栈的语言,它是故意被设计成无状态和非图灵完备的。无状态确保一旦交易被添加到区块链中,就没有条件使得它永久不可花费。图灵不完备(缺少循环或goto)使得脚本语言更少灵活性和更多可预测性,极大地简化了安全模块。

为了测试交易是否合法,scriptSigs和scriptPubKey操作是依次进行。从Bob的scriptSigs开始直到Alice的scriptPubKey末端。下面的图表展示了标准P2PKH scriptPubKey的判断;下图是过程描述:

image.png

  • 签名(源于Bob的scriptSigs)被加入到一个空的栈,公钥(源于Bob的scriptSigs)被放到签名的顶部
  • 从Alice的scriptPubKey中,OP_DUP操作被执行。OP_DUP把当前位于栈顶的数据备份,然后将该备份放到栈顶——本例中创建了一个Bob提供的公钥的备份
  • 下一步执行的操作OP_HASH160,先对栈顶的数据进行hash,再将hash值放到栈顶——本例中创建了Bob的公钥hash
  • Alice将第一次交易中Bob给她的公钥hash(比特币地址)推到栈中。此时,在栈顶有两份Bob的公钥备份
  • 现在开始变得有趣:Alice的scriptPubKey执行OP_EQUALVERIFYOP_EQUALVERIFY等同于先执行OP_EQUAL然后执行OP_VERIFY(没有显示出来)。
    OP_EQUAL(没显示出来)检查栈顶的两个值;本例中,检查由Bob提供的完整公钥生成的公钥hash和由Alice创建交易#1时提供的公钥hash是否相同。
    OP_EQUAL移除要比较的两个值,并替换成比较结果:0(false)或1(true)。
    OP_VERIFY检查栈顶的值。如果值是false立刻终止判断,交易验证失败;否则的话从栈顶把true移除。
  • 最后,Alice的scriptPubKey执行OP_CHECKSIG,该函数检查Bob提供的签名和他提供的及时认证的公钥。如果签名匹配公钥而且是使用所有要签名的数据生成的,OP_CHECKSIG把true推到栈顶。

在scriptPubKey被判断完毕之后,false不位于栈顶,交易就是有效的(证明它没有其他问题)。

P2SH 脚本

scriptPubKey 由对脚本行为感兴趣的花费者创建。接收者只关心脚本条件,而且如果他们愿意的话,可以让花费者使用一个特定的scriptPubKey 。不幸的是,自定义scriptPubKey 较之短小的比特币地址来说更加不方便,而且在之后讨论的BIP70支付协议被广泛实现以前程序之间没有标准的通信方式。

为了解决这些问题,在2012年创建了pay-to-script-hash(P2SH)交易,让花费者创建一个包含另一个脚本hash的scriptPubKey。这另一个脚本即赎回脚本。

基本的P2SH工作流如下,看起来和P2PKH工作流几乎相同。Bob随心所以创建一个赎回脚本,然后对之hash,并将hash值提供给Alice。Alice创建一个P2PSH类型的输出包含了Bob的赎回脚本hash值。

image.png

当Bob想要花费输出时,需要在scriptSigs中提供他的签名和完整的(序列化的)赎回脚本。P2P网络确保把完整的赎回脚本进行hash,得到的hash值和Alice放到她的输出中的script hash相同。然后像处理Alice的scriptPubKey一样处理赎回脚本。

image.png

赎回脚本的hash值和pubkey hash值具有相同的属性——所以可被转换成标准的比特币地址格式,而且二者只有一点小的差异。这使得收集P2SH和P2PKH类型的地址一样简单。hash也混淆了赎回脚本中的公钥,所以P2SH 脚本和P2PKH pubkey 脚本同等安全。

标准交易

在早期的比特币版本中发现了几个危险的bug后,补充了一些测试——只接受网络中scriptPubKey和scriptSigs符合安全模板的交易,且交易的其余部分没有违反另一套实施良好网络行为的规则。这就是IsStandard()测试,通过该测试的交易称之为标准交易。
非标准交易——验证失败——可能会被没有使用比特币核心默认设置的节点接受。如果这些交易被加入到区块中,将会避免IsStandard测试并被处理。
除了使得通过广播有害的交易免费的攻击比特币更加困难之外,也帮助用户避免创建使得在将来添加新的交易属性更加困难的交易。例如,如上所述,每笔交易包含版本号——如果用户任意修改版本号,交易作为引入向后不兼容特性的工具将变得无用。
从比特币核心0.9开始,标准的scriptPubKey类型如下:

支付给公钥hash(P2PKH)

P2PKH是最常用的scriptPubKey形式,用于把交易发送给一个或多个比特币地址。

1
2
Pubkey script: OP_DUP OP_HASH160 <PubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
Signature script: <sig> <pubkey>

支付给脚本hash(P2SH)

P2SH用于发送交易给脚本hash。每个标准的scriptPubKey都可用作P2SH 赎回脚本,但是实际上只有多重签名scriptPubKey有意义,除非更多的交易类型成为标准的交易类型。

1
2
Pubkey script: OP_HASH160 <Hash160(redeemScript)> OP_EQUAL
Signature script: <sig> [sig] [sig...] <redeemScript>

多重签名

虽然P2SH多重签名现在一般用于多重签名交易,这种基本的脚本也可用于要求UTXO被花费之前需要多重签名。

在多重签名scriptPubKey中,叫做m-of-n,m是必须匹配公钥的最大数量的签名;n是被提供的公钥数量。m和n都应该通过 OP_16操作OP_1,对应于目标数字。
因为比特币早期实现中的差一错误(考虑到兼容问题,需要被保留),OP_CHECKMULTISIG从栈中消耗一个比m更大的值,所以scriptSigs中的secp256k1签名列表必须添加一个额外的前缀值(OP_0),该值会被消耗并不使用。
scriptSigs必须提供和scriptPubKey或赎回脚本中出现的pubkey相同顺序的签名。详细请看OP_CHECKMULTISIG描述。

1
2
Pubkey script: <m> <A pubkey> [B pubkey] [C pubkey...] <n> OP_CHECKMULTISIG
Signature script: OP_0 <A sig> [B sig] [C sig...]

虽然不是一种独立的交易类型,这是一个2-of-3 P2SH多重签名:

1
2
3
Pubkey script: OP_HASH160 <Hash160(redeemScript)> OP_EQUAL
Redeem script: <OP_2> <A pubkey> <B pubkey> <C pubkey> <OP_3> OP_CHECKMULTISIG
Signature script: OP_0 <A sig> <C sig> <redeemScript>

公钥

公钥输出是P2PKH scriptPubKey简化的形式,但是并不像P2PKH一样安全,所以在新的交易中它们一般不再被使用。

1
2
Pubkey script: <pubkey> OP_CHECKSIG
Signature script: <sig>

空数据

空数据交易类型在比特币核心0.9及以上版本默认被转发和开采,0.9版本以后添加了任意数据到可证明不可花费的scriptPubKey中,这些scriptPubKey并没有保存到完整节点本地的UTXO数据库中。最好是在交易中使用空数据交易,从而扩充UTXO数据库,因为它们不能自动精简。然而,如果可能的话,一般最好存储外部交易。

非标准交易

如果在输出中除了标准scriptPubKey之外还有其他的东西,使用比特币核心默认设置的节点和矿工将既不接受、广播,也不处理你的交易。当你尝试广播交易给运行默认设置的节点,将收到错误提示。

如果创建一个赎回脚本,对之hash,并在P2SH输出中使用该hash,网络只关注这个hash,所以将会接受输出并视之为有效,而不管该赎回脚本说了些什么。这允许支付给非标准脚本,而且从比特币核心0.11开始,几乎所有有效的赎回脚本都可被花费。异常时使用了未赋值的NOP操作码的脚本。

注:标准交易被设计用于保护和帮助网络,而非阻止你免犯错误。

从比特币核心0.9.3开始,标准交易必须满足以下条件:

  • 交易必须被完成:要么交易的锁定时间必须是过去(少于或等于当前的区块高度),要么交易的所有的序列号都是0xffffffff
  • 交易必须小于100000字节,大概是一个典型的单输入,单输出的P2PKH交易的200倍
  • 每个交易的scriptSigs必须小于1650 bytes。使用压缩公钥的话,足以允许P2SH类型的15-of-15多重签名交易。
  • 赤裸(非P2SH)多重签名交易要求超过三个公钥是非标准的
  • 交易的scriptSigs必须仅把数据推送到脚本的判断栈中。不能推送新的操作码
  • 交易的输出不能少于输入的1/3。当前默认的转发费用是546聪。标准空数据输出必须是0.

签名hash类型

OP_CHECKSIG从它判断的每个签名中提取非堆栈参数,允许签名者自行决定签署交易的哪一部分。因为签名保护交易免遭修改,这让签名者有选择地让其他人修改他们的交易。
要签署的选项叫做签名hash类型,有三种基础的类型:

  • SIGHASH_ALL:默认类型,签署所有的输入和输出,保护所有的事情免遭修改除了scriptSigs。
  • SIGHASH_NONE:签署所有的输入,允许任何人改变聪的去向,除非其他的签名使用了其他签名hash类型包含输出
  • SIGHASH_SINGLE:唯一被签署的输出是与输入相对应的(输出和输入具有相同的输出索引号),确保没人可以修改交易中属于你的部分,但是允许其他签署者改变他们的交易部分。相应的输出必须存在否则value 1将被签署,打破安全机制。输入和输出一样,都被加入到签名中。其他输入的序列号没有被添加到签名中,而且会被更新。

基本类型可以使用SIGHASH_ANYONECANPAY标签被修改,创建三种新的组合类型:

  • SIGHASH_ALL|SIGHASH_ANYONECANPAY:签署所有的输出但只有一个输入,而且允许任何人添加或移除其他的输入,所以任何人都可以贡献额外的聪,但是他们不能改变要发送多少聪和发送到哪
  • SIGHASH_NONE|SIGHASH_ANYONECANPAY:只签署这一个输入,并允许任何人添加或移除其他的输入或输出,所以任何获得输入备份的人都可以花费它
    SIGHASH_SINGLE|SIGHASH_ANYONECANPAY:只签署这一个输入和与之对应的输出,允许任何人添加或移除他们的输入。

因为每个输入都被签署,有多个输入的交易可以有多个签名hash类型签署交易的不同部分。例如,使用NONE签署的单输入交易可以使得它的输出被添加它到区块链上的矿工改变。另一方面,如果双输入交易有一个输入使用NONE签署,另一个使用ALL签署,ALL签署者可以选择把聪花费到哪而不用自行NONE签署者——但是没有其他人可以修改交易。

锁定时间和序列号

所有的签名hash类型都可以签署的一个东西就是锁定时间(在比特币核心源码中叫做nLockTme)。锁定时间意味着可被添加到区块链中的交易的最早时间。

锁定时间允许签署者创建仅在将来才能有效的时间锁定交易,给签署者改变想法的机会

如果任意签署者改变想法,他们可以创建一个新的非锁定时间交易。这笔新的交易将使用在锁定交易中用作输入的一个输出作为它的输入。这使得锁定时间交易无效,如果新的交易在预期的锁定时间之前被添加到区块链中。

必须注意时间锁的有效期限。P2P网络运行区块时间比真实时间早2个小时,所以一笔锁定时间交易可被加入到区块链中直到它的时间锁触发的前两个小时。而且,区块不一定会在保证的周期里创建,所以任何曲线价值交易的请求都应在锁定时间触发之前两小时发出。

比特币核心以前的版本提供了一个特性,避免交易签署者使用上述的方法取消时间锁定的交易,但是该特性的必要的部分无法阻止服务拒绝攻击。但一个系统遗产就是每个输出中的4字节的序列号。序列号意味着允许多个签署者一致同意更新交易;当完成交易更新后,他们同意把每个输入的序列号设置成4字节的无符号的最大值(0xffffffff),允许在时间锁尚未触发时把交易被添加到区块中。

甚至今天,把所有的序列号设置成0xffffffff(比特币核心默认值)仍然可以禁用时间锁。至少一笔输入必须有一个低于最大值的序列号。因此序列号不被网络用作它途,把任意序列号设置成0表示启用时间锁。

时间锁自身是一个4字节的无符号数字,可按照两种方式解析

  • 如果小于5亿,时间锁被解析成区块高度。交易可被添加到有相同或更高高度的区块中。
  • 如果>=5亿,使用Unix纪元时间格式(从1970-01-01T00:00 UTC到当下的秒数)解析时间锁。交易可被添加到区块时间比锁定时间大的任意区块中。

交易费用和找零

交易根据被签署交易总字节大小支付费用。每个字节的费用是根据当前开采区块的空间需求计算的,随着需求的增加,收费也随之增加。正如在区块链章节解释的,交易费用被给予矿工,而且每个矿工自由选择他们愿意接受的最低交易费。

有一个所谓的“高优先级交易”,指花费很久没有被移动的聪的交易。

过去,这些“优先”交易经常不用满足正常费用要求。在比特币核心0.12之前,每个区块为高优先级交易保留50kb空间,然而现在默认设置成0。在该优先区域之后,所有的交易按照每字节费用排序,更高支付的交易按序被加入到区块中,直至所有可用空间被填满。

从比特币核心0.9开始,最低费用要求在网络中广播一个交易(目前是1000聪)。这意味着在区块有足够的多余空间加入它之前,仅支付最低费用的交易应该等待很长时间。请看验证支付章节了解为什么它是非常重要的。
因为每笔交易都花费UTXO,而且一笔UTXO只能花费一次,所以被添加的UTXO的必须要给矿工一些作为交易费。很少人会仅使得UTXO和他们要花费的一样(没有交易费),所以大部分的交易包括一个找零输出。

找零输出是合理的输出,把多余的聪再返回给花费者。他们可以重用在UTXO中使用的相同的P2PKH公钥hash或P2SH脚本hash,但是出于下一子章节描述的原因,强烈建议使用新的P2PKH或P2SH地址花费找零输出。

避免秘钥重用

在交易中,花费者和接收者互相透露在交易中使用的所有的公钥或地址。这允许人使用区块链追踪包含他人相同的公钥或地址的过去和将来的交易。

如果相同的秘钥经常重用,当人们使用比特币地址(公钥hash)作为静态的支付地址时,其他人可以轻易地追踪该用户的花费和接收习惯,包括他们在已知的地址上有多少聪。

并非全部如此,如果每个公钥只用两次——一次接收,一次花费——用户可以获得相当程度的经济隐私。

更好的方法是,接收支付或创建找零输出时,使用新的公钥或唯一地址可以和后续讨论的技术结合起来。

避免秘钥重用也可以提供安全,免遭可能允许从公钥或签名比较中构建私钥的攻击(在下面描述的情况下,使用更多通用的攻击假设)

  1. 唯一的(非重用)P2PKH和P2SH地址保护免遭第一种类型的攻击,通过保持ECDSA公钥隐藏(被hash)直到第一次发送给这些地址的聪被花费,所以攻击很大程度上是无用的,除非他们能够在少于被区块链很好保护的交易所花费的时间里重建私钥。

  2. 唯一的(非重用)私钥保护免遭第二种类型的攻击,通过为每个私钥生成一个签名,所以攻击者从不可能获得随后的签名在基于比较的攻击中使用。现有的基于比较的攻击只有在签名时使用的熵不足或熵在某种程度上暴露了时才具有实用性。

所以,为了隐私和安全,我们鼓励你构建自己的应用避免公钥重用,而且如果可能的话,我们不鼓励你重用地址。如果你的应用需要提供一个固定的URI,请查看bitcoin:URI章节

交易延展性

比特币签名hash类型中没有保护签名脚本的,为服务拒绝攻击敞开大门叫做交易延展性。签名脚本包含了不能签署自身的secp256k1签名,允许攻击者非功能性的修改交易而不会致使其无效。例如,攻击者可以在之前的公钥脚本被处理之前停用的签名脚本中添加一些数据。

虽然修改是非功能性的——所以没有修改交易的输入和输出——修改了交易的hash。每笔交易使用hash作为交易标识符(txid)链接到之前的交易,被修改的交易没有创建者预期的txid。

对于大多数被设计成立刻被添加到区块链中的比特币交易来说,这不算是个问题。但是当源于一笔已被加入到区块链中的交易的输出被花费时,就是个问题了。

比特币开发者已经致力于在标准交易类型中减少交易延展性,努力的成果之一就是BIP 141:Segregated Witness,它已经被比特币核心支持但尚未激活。当下,新的交易不应依赖于尚未被加入到区块链中的之前的交易,尤其大数量的聪交易的时候。

交易延展性也影响了支付追踪。比特币核心RPC接口使得你可以通过txid追踪交易——但是如果因为交易被修改,txid被改变,可能会出现交易从网络中消失的情况。
针对交易追踪,当前最好的方式就是通过交易花费的作为输入的UTXO跟踪交易,因为不验证交易,UTXO无法修改。

更进一步的方式就是如果交易从网络中消失而且需要补发,补发在某种程度上验证了丢失的交易。行之有效的方法就是确保被补发的支付花费丢失的交易用作输入的相同的输出。