// CountP2SHSigOps returns the number of signature operations for all input // transactions which are of the pay-to-script-hash type. This uses the // precise, signature operation counting mechanism from the script engine which // requires access to the input transaction scripts. func CountP2SHSigOps(tx *btcutil.Tx, isCoinBaseTx bool, txStore TxStore) (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 _, txIn := range msgTx.TxIn { // Ensure the referenced input transaction is available. txInHash := &txIn.PreviousOutPoint.Hash originTx, exists := txStore[*txInHash] if !exists || originTx.Err != nil || originTx.Tx == nil { str := fmt.Sprintf("unable to find input transaction "+ "%v referenced from transaction %v", txInHash, tx.Sha()) return 0, ruleError(ErrMissingTx, str) } originMsgTx := originTx.Tx.MsgTx() // Ensure the output index in the referenced transaction is // available. originTxIndex := txIn.PreviousOutPoint.Index if originTxIndex >= uint32(len(originMsgTx.TxOut)) { str := fmt.Sprintf("out of bounds input index %d in "+ "transaction %v referenced from transaction %v", originTxIndex, txInHash, tx.Sha()) return 0, ruleError(ErrBadTxInput, str) } // We're only interested in pay-to-script-hash types, so skip // this input if it's not one. pkScript := originMsgTx.TxOut[originTxIndex].PkScript if !txscript.IsPayToScriptHash(pkScript) { continue } // Count the precise number of signature operations in the // referenced public key script. sigScript := txIn.SignatureScript numSigOps := txscript.GetPreciseSigOpCount(sigScript, pkScript, true) // We could potentially overflow the accumulator so check for // overflow. lastSigOps := totalSigOps totalSigOps += numSigOps if totalSigOps < lastSigOps { str := fmt.Sprintf("the public key script from "+ "output index %d in transaction %v contains "+ "too many signature operations - overflow", originTxIndex, txInHash) return 0, ruleError(ErrTooManySigOps, str) } } return totalSigOps, nil }
// TestGetPreciseSigOps ensures the more precise signature operation counting // mechanism which includes signatures in P2SH scripts works as expected. func TestGetPreciseSigOps(t *testing.T) { t.Parallel() tests := []struct { name string scriptSig []byte nSigOps int err error }{ { name: "scriptSig doesn't parse", scriptSig: []byte{txscript.OP_PUSHDATA1, 2}, err: txscript.ErrStackShortScript, }, { name: "scriptSig isn't push only", scriptSig: []byte{txscript.OP_1, txscript.OP_DUP}, nSigOps: 0, }, { name: "scriptSig length 0", scriptSig: nil, nSigOps: 0, }, { name: "No script at the end", // No script at end but still push only. scriptSig: []byte{txscript.OP_1, txscript.OP_1}, nSigOps: 0, }, { name: "pushed script doesn't parse", scriptSig: []byte{txscript.OP_DATA_2, txscript.OP_PUSHDATA1, 2}, err: txscript.ErrStackShortScript, }, } // The signature in the p2sh script is nonsensical for the tests since // this script will never be executed. What matters is that it matches // the right pattern. pkScript := mustParseShortForm("HASH160 DATA_20 0x433ec2ac1ffa1b7b7d0" + "27f564529c57197f9ae88 EQUAL") for _, test := range tests { count := txscript.GetPreciseSigOpCount(test.scriptSig, pkScript, true) if count != test.nSigOps { t.Errorf("%s: expected count of %d, got %d", test.name, test.nSigOps, count) } } }