Published: 2018-12-01

BIP16(P2SH)

1 BIP地址

BIP16

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个运算符)。计算多少个运算符的规则是:

  1. OP_CHECKSIGOP_CHECKSIGVERIFY 算作一个运算符
  2. OP_CHECKMULTISIGOP_CHECKMULTISIGVERIFY 如果前面跟有 OP_1OP_16 等,那么就算作 1到16的运算符
  3. 其他的 OP_CHECKMULTISIGOP_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

  1. 如何计算多少个运算符
    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
    }
    
    
    
  2. 挖矿时的验证
    // 这是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
        }
    
        /* 省略其它部分 */
    
    
    }
    
    
    
  3. 节点接受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相关

  1. 判断脚本类型
    // 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
    }
    
    
    
  2. 判断是否是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?)

6 END

Author: Nisen

Email: imnisen@163.com