BitCoin基础——交易(Transaction)
Bitcoin中的交易代表着一组输入输出的集合,每个输入消费上一个的输出,未被消费的输出被称为Unspent Transaction Output (UTXO)。
每个交易的前缀都是由一个4字节的版本号构成,用于告诉矿机和网络中的节点用什么规则来验证交易。
一个交易输出拥有一定数量的中本聪(satoshis),用于向一个 pubkey script 进行支付。任何满足该 pubkey script 条件的人都可以花费对应的中本聪。
一个输入使用一个交易标志符(transaction identifier,txid)和一个输出索引序号来标志一个将要被消费的输出。同时提供一个签名脚本(signature script),用于提供满足该pubkey script 条件的参数信息。
P2PKH 的流程
通过一张图片来讲述P2PKH(pay-to-public-key-hash)的流程。
在这张图中,首先 Bob 产生一个公私钥对。比特币采用的是Elliptic Curve Digital Signature Algorithm (ECDSA)的secp2561曲线算法,该算法产生的密钥可以确定性的生产对应的公钥,因此保存密钥的情况下,可以不保存公钥。
之后产生的公钥被加密进行 hash,目的是为了公钥的安全和防止私钥被重新构建。
Bob 向接收方的 Alice 传递公钥 hash,这个公钥hash一般被编码成比特币地址(address)的形式进行发送。这个地址信息包含了一个地址版本编号,对应的hash以及用于检查字符错误的校验值。
接收方 Alice 在接收到对应的地址之后,就可以用来创建交易了。创建的交易包含了只允许持有公钥 hash 对应的私钥信息的用户才能对该输出进行消费的指令集。这个指令集就是所谓的 pubkey script 或者称为 scriptPubKey。
接收方 Alice 随后广播该交易信息,并把它加入到区块链中。区块链把该交易分类为未消费交易(UTXO),而发送方的 wallet 软件就可以把其标志位可消费的余额了。
当发送方需要消费该输出时,就需要创建一个与之前涉及的交易标志符和输出索引序号所对应的输入,并创建一个满足接收方 Alice 所提供的 pubkey script 条件的参数集的一个签名脚本(signature script,scriptSigs)。
Pubkey脚本和签名脚本将secp256k1 pubkeys和签名与条件逻辑相结合,创建了可编程授权机制。
消费方 Bob 提供的签名密钥包含以下两部分信息:
- 一个发送方(Bob)的完整的公钥信息,用于比对和接收方(Alice)创建的 pubkey script包含的 hash 是否一致。
-
用于验证消费方是否是 pubkey script 所包含的公钥信息对应的私钥的持有者。
- 消费方在把自己的签名和公钥信息放到脚本中后,即可以向网络中广播当前交易了。
P2PKH脚本的验证
在一个 P2PKH 输出中,一个 pubkey script 格式是这样的:
OP_DUP OP_HASH160
OP_EQUALVERIFY OP_CHECKSIG
在拼接上消费方的签名脚本所包含的签名和公钥信息后,格式变为:
OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG
验证的脚本是一个基于栈的语言所编写的,脚本的验证逻辑就是对应的操作符和对应的参数在栈中的入栈和出栈的操作,已从左到右的形式,概览验证的流程。
- 消费方的签名和公钥信息被压入栈中
- OP_DUP 操作符,对当前栈顶的数据进行复制并入栈。当前栈顶是消费方的公钥信息
- OP_HASH160 操作符,对当前栈顶的信息进行hash,并把值压入栈中。当前即生成消费方公钥的hash
- 接下来的参数,代表着把接收方(Alice)接收发送方(Bob)的公钥 hash 信息压入栈中。此时,栈顶两个元素都是发送方的公钥信息
- OP_EQUALVERIFY 操作符,对栈顶的两个元素(一个是接收方产生 pubkey script 所包含的公钥hash,一个是消费方签名脚本所包含的完整公钥信息所产生的hash)的值是否相等,并将结果true/false压入栈顶
- 最后,OP_CHECKSIG 校验了消费方提供的签名脚本是否和上述验证的公钥信息是否一致。若匹配,则将结果压入栈顶。
P2SH 脚本
P2SH 用于解决难以自定义 pubkey script 的问题。它允许消费方创建一个包含第二个脚本(redeem script)hash的pubkey script。
P2SH的工作流程和 P2PKH 的工作流程基本一致,只是发送方用所想要的脚本创建一个 redeem script,对其进行hash后发送给接收方,之后接收方创建一个包含发送方的 script hash 的 P2SH 类型的输出。
消费方需要提供一个包含消费方签名和完整的 redeem script 的签名脚本来消费接收方创建的 P2SH 类型的输出。流程和 P2PKH 大体一致,只要 redeem script 不返回 false,消费方即可消费该输出。
标准交易(standard transaction)
比特币网络提供了一个 IsStandard() 方法用于验证产生交易的 pubkey script 和 signature script 匹配确信为安全的模板并且交易的其余信息遵循了指定的用于保证网络安全的规则。通过这个校验方法的交易即被称为标准交易。
比特币有以下几种标准 pubkey script 类型:
- P2PKH
- P2SH
- Multisig
- PubKey
- Null Data
前两种类型在之前已经介绍过,在这里看一下后面的三种类型。
Multisig
顾名思义,Multisig 即多重签名的意思。意味着一个 UXTO 需要提供多个签名信息才能被消费。
对于 multisig pubkey script , 也称为 m-of-n, m代表着最少的匹配的公钥签名,n代表着提供的公钥数量。n和m都通过 OP_1 到 OP_16操作符来表示,各自对应着需要的数量信息。
以下展示一个 P2SH 的 2-of-3 的 multisig
Pubkey script: OP_HASH160
OP_EQUAL
Redeem script:OP_CHECKMULTISIG
Signature script: OP_0
可以看到, PubKey script包含了创建该脚本的 redeem script 的 hash 信息,redeem script声明了一个 2-of-3 的 multisig 脚本,签名脚本中的前缀 OP_0 指明 OP_CHECKMULTISIG 操作符需要消费至少 m 个值。
Pubkey
PubKey 脚本简化了 P2PKH 的 pubkey script,但是并不安全,因此已经被废弃。
Pubkey script:
OP_CHECKSIG
Signature script:
Null Data
这个类型的交易的产生和分发是为了添加数据到一个明确的不被消费的 pubkey script 中,而全节点(full node)不需要去储存这些交易。空数据交易类型相比普通交易类型的好处就是普通交易不能被自动清除进而导致数据库被占满。
签名哈希类型(Signature Hash Types)
OP_CHECKSIG 操作符允许签名方决定对交易中的哪部分信息进行签名,签名的部分不能被外界所修改,因此签名方可以选择性地让其他人修改他们的交易。
- SIGHASH_ALL: 对交易的输入和输出进行签名
- SIGHASH_NONE:仅对所有的输入进行签名
- SIGHASH_SINGLE:对与输入索引序号对应的输出索引序号的输出进行签名,允许其他人可以修改其他部分的信息但不能修改当前的输出信息。
时钟和序列号(Locktime and Sequence Number)
所有的签名hash类型签名的一部分信息是交易的锁定时间(locktime),这个锁定时间指明了这个交易最早可以被添加到区块链中的时间,这个时间允许交易发起方最后修改自己交易。如果发起方想要在锁定时间内取消当前交易,只需要创建一个没有锁定时间的,和当前交易有相同的输入和输出的交易,并添加到区块链中,最终在锁定时间结束后当前交易就会因为拥有着相同的输入输出被区块链锁拒绝添加。
上述的这个方法可能导致DOS(拒绝服务攻击)的情况。对此的解决办法是在每个输入添加一个四字节的序列号。这个序列号代表着允许多个签名方去更新一个交易,当他们完成交易更新后,他们可以把每个输入的序列号值置成最大无符号四字节数(0xffffffff),就可以让当前交易在锁定时间过期之前加入到块中。
交易费率及更改(Transaction Fee And Change)
比特币网络中,交易费率是基于签名交易的总字节大小的。在Bticoin Core0.9后,在网络中广播一个交易的最低费率被设置为1000中本聪。
很少有人会拥有与他们想要支付的金额完全匹配的UTXOs,因此大多数交易都包含一个更改输出(change outputs),这个输出把 UTXOs 中多余的中本聪返回给消费者。
避免公钥重复使用(Avoiding Key Reuse)
在一个交易中,发送方和接收方互相公布自己在交易中使用的公钥信息。而交易双方可以通过对方相同的公钥或地址(address)信息去回溯过去或者追踪未来的交易信息。这会导致隐私和安全问题。
如果一个公钥仅仅被使用两次——一次用于接收支付,一次用于发起支付,这样是可以获得很好的隐私安全的。
这个问题可以通过 CoinJoin 或者 Merge Avoidance 方法进行解决。(这是比特币网络中的两个较大的问题了,后面要深究一下)
交易拓展性(Transaction malleability)
官方文档的对这个概念的定义是:某个节点更改未确认的交易而不把该交易置为无效(invalid)的能力,它将更改事务的txid,使子交易无效。
比特币的签名哈希类型(上文涉及的 Signature Hash type)并不能保护签名脚本,签名脚本不能对本身进行签名,这是导致交易拓展性问题的原因。攻击者可以对交易进行非功能性的修改而不把该交易置为无效(invalid)状态。例如,可以向签名脚本中添加一些数据,进而导致签名脚本在 pubkey script 处理是被抛弃,导致一个 DOS 的攻击。
交易拓展性的修改是非功能性的,意味着它并不修改交易的输入和输出,但是它修改了交易的Hash值。而交易是通过hash值进行连接的,修改后的hash值会导致交易的连接错误,同时也影响了根据hash值对交易进行回溯的功能。
解决交易拓展性的办法是使用 Segregated Witness(这个是上面涉及的 CoinJoin 和 Merge Avoidance,都是一个重大课题,后续进行研究总结)。
解决交易拓展性导致的交易回溯功能影响的解决方案,是通过交易输出,既 UTXO,来回溯交易信息,而不是通过 tid。
重新发起一个被交易拓展性所影响的交易的解决方案是重新发起一个消费所有和被影响交易作为输入的相同的输出。(这句话有点绕,就是交易的输入就是上一个交易的输出的意思)。