BIP16(P2SH)
1 BIP地址
BIP: 16 Layer: Consensus (soft fork) Title: Pay to Script Hash Author: Gavin Andresen <gavinandresen@gmail.com> Comments-Summary: No comments yet. Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0016 Status: Final Type: Standards Track Created: 2012-01-03
2 原因和目的
BIP16定义了新的标准交易类型,将提供花费UTXO所需的条件的责任,由发送者转移到了接受者。
好处是现在发送者不管需要生成多复杂的条件,都可以使用固定长度的hash值替代(具体的条件由使用者提供)。
3 细节
PubScript格式: OP_HASH160 [20-byte-hash-value] OP_EQUAL
[20-byte-hash-value]
是 opcode(0x14) 后跟20bytes。
ScriptSig格式: ...signatures... {serialized script}
"serialized script" 又称"redeem script"
新的验证脚本有效性的规则在block的timestamps >= 1333238400(Apr 1 2012)后实行。(TODO 和下面的部署冲突吗)
为了防止节点发起拒绝服务攻击(比如恶意节点广播一个区块,需要验证redeem script里的成千上外个ecdsa签名计算,那么其他旷工可能就在验证计算而自己又可能抢险挖到区块),所以redeem script里的签名运算符需要被限制(一个Block最大20000个运算符)。计算多少个运算符的规则是:
OP_CHECKSIG
和OP_CHECKSIGVERIFY
算作一个运算符OP_CHECKMULTISIG
和OP_CHECKMULTISIGVERIFY
如果前面跟有OP_1
到OP_16
等,那么就算作 1到16的运算符- 其他的
OP_CHECKMULTISIG
和OP_CHECKMULTISIGVERIFY
算作20个运算符
例子:
{2 [pubkey1] [pubkey2] [pubkey3] 3 OP_CHECKMULTISIG}
: 3个运算符
{OP_CHECKSIG OP_IF OP_CHECKSIGVERIFY OP_ELSE OP_CHECKMULTISIGVERIFY OP_ENDIF}
: 22个运算符
4 相关实现
以btcd的实现为例
4.1 SigOpCost
- 如何计算多少个运算符
func GetSigOpCost(tx *btcutil.Tx, isCoinBaseTx bool, utxoView *UtxoViewpoint, bip16, segWit bool) (int, error) { numSigOps := CountSigOps(tx) * WitnessScaleFactor //错略估算(p2sh那部分没算,而是在下面单独处理) // 如果启用了BIP16 if bip16 { numP2SHSigOps, err := CountP2SHSigOps(tx, isCoinBaseTx, utxoView) if err != nil { return 0, nil } numSigOps += (numP2SHSigOps * WitnessScaleFactor) } /* 省略segwit对SigOpCost的影响部分,暂时略去 */ return numSigOps, nil } func CountP2SHSigOps(tx *btcutil.Tx, isCoinBaseTx bool, utxoView *UtxoViewpoint) (int, error) { // Coinbase transactions have no interesting inputs. if isCoinBaseTx { return 0, nil } // Accumulate the number of signature operations in all transaction // inputs. msgTx := tx.MsgTx() totalSigOps := 0 for txInIndex, txIn := range msgTx.TxIn { /* 省略有效性验证部分 */ sigScript := txIn.SignatureScript // pkScript 为txIn.PreviousOutPoint指向的utxo的PkScript numSigOps := txscript.GetPreciseSigOpCount(sigScript, pkScript, true) /* 省略overflow价差部分*/ } return totalSigOps, nil } func GetPreciseSigOpCount(scriptSig, scriptPubKey []byte, bip16 bool) int { /* 省略验证和判断部分 */ shPops, _ := parseScript(shScript) return getSigOpCount(shPops, true) } func getSigOpCount(pops []parsedOpcode, precise bool) int { nSigs := 0 for i, pop := range pops { switch pop.opcode.value { case OP_CHECKSIG: fallthrough case OP_CHECKSIGVERIFY: nSigs++ // SigOp +1 case OP_CHECKMULTISIG: fallthrough case OP_CHECKMULTISIGVERIFY: // If we are being precise then look for familiar // patterns for multisig, for now all we recognize is // OP_1 - OP_16 to signify the number of pubkeys. // Otherwise, we use the max of 20. if precise && i > 0 && pops[i-1].opcode.value >= OP_1 && pops[i-1].opcode.value <= OP_16 { nSigs += asSmallInt(pops[i-1].opcode) // 前一个操作符在OP_1~OP_16间, SigOPs +1~16 } else { nSigs += MaxPubKeysPerMultiSig // 其它情况 +20 } default: // Not a sigop. } } return nSigs }
- 挖矿时的验证
// 这是2000*4, 是segwit的影响,将一般操作符都扩大4倍 MaxBlockSigOpsCost = 80000 func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress btcutil.Address) (*BlockTemplate, error) { /* 省略其它部分 */ // 挖矿时计算交易里有多少SigOpCost sigOpCost, err := blockchain.GetSigOpCost(tx, false, blockUtxos, true, segwitActive) if err != nil { log.Tracef("Skipping tx %s due to error in "+ "GetSigOpCost: %v", tx.Hash(), err) logSkippedDeps(tx, deps) continue } // 判断总的SigOpCost是否超过一个区块的上限 if blockSigOpCost+int64(sigOpCost) < blockSigOpCost || blockSigOpCost+int64(sigOpCost) > blockchain.MaxBlockSigOpsCost { log.Tracef("Skipping tx %s because it would "+ "exceed the maximum sigops per block", tx.Hash()) logSkippedDeps(tx, deps) continue } /* 省略其它部分 */ }
- 节点接受block时的验证
func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block, view *UtxoViewpoint, stxos *[]SpentTxOut) error { /* 省略其它部分 */ totalSigOpCost := 0 for i, tx := range transactions { // 获取sigOpCost sigOpCost, err := GetSigOpCost(tx, i == 0, view, enforceBIP0016, enforceSegWit) if err != nil { return err } // 判断是否overflow和超过MaxBlockSigOpsCost lastSigOpCost := totalSigOpCost totalSigOpCost += sigOpCost if totalSigOpCost < lastSigOpCost || totalSigOpCost > MaxBlockSigOpsCost { str := fmt.Sprintf("block contains too many "+ "signature operations - got %v, max %v", totalSigOpCost, MaxBlockSigOpsCost) return ruleError(ErrTooManySigOps, str) } } /* 省略其它部分 */ }
4.2 txscript相关
- 判断脚本类型
// scriptType returns the type of the script being inspected from the known // standard types. func typeOfScript(pops []parsedOpcode) ScriptClass { if isPubkey(pops) { return PubKeyTy } else if isPubkeyHash(pops) { return PubKeyHashTy } else if isWitnessPubKeyHash(pops) { return WitnessV0PubKeyHashTy } else if isScriptHash(pops) { // 判断是否是p2sh return ScriptHashTy } else if isWitnessScriptHash(pops) { return WitnessV0ScriptHashTy } else if isMultiSig(pops) { return MultiSigTy } else if isNullData(pops) { return NullDataTy } return NonStandardTy }
- 判断是否是p2sh
// IsPayToScriptHash returns true if the script is in the standard // pay-to-script-hash (P2SH) format, false otherwise. func IsPayToScriptHash(script []byte) bool { pops, err := parseScript(script) if err != nil { return false } return isScriptHash(pops) } // isScriptHash returns true if the script passed is a pay-to-script-hash // transaction, false otherwise. func isScriptHash(pops []parsedOpcode) bool { return len(pops) == 3 && pops[0].opcode.value == OP_HASH160 && pops[1].opcode.value == OP_DATA_20 && pops[2].opcode.value == OP_EQUAL }
5 兼容性和部署
P2SH对于旧的实现来说被认为是非标准的脚本,不会被传播或包含到区块。
为了防止恶意的p2sh交易导致的区块链分叉,在新的节点上无效而在旧的节点上有效的p2sh交易需要小心处理。
为了能够很好地升级并且确保不会有长时间存在的分叉,多余50%的矿工需要在同一时间切换到对新验证规则的支持。
为了判断是否有50%以上哈希算力支持BIP16,矿工需要在挖到的区块里将 /P2SH/
写进coinbase交易的Input里。
2012年2月1号,会检查过去7天(大约1000个区块)里是否有550个区块包含了 /P2SH/
在coinbase里,
如果有, 2012年2月15号之后的区块需要提供该BIP要求的验证。
如果没有,那再看情况(what?)