// isNonstandardTransaction determines whether a transaction contains any // scripts which are not one of the standard types. func isNonstandardTransaction(tx *coinutil.Tx) bool { // TODO(davec): Should there be checks for the input signature scripts? // Check all of the output public key scripts for non-standard scripts. for _, txOut := range tx.MsgTx().TxOut { scriptClass := txscript.GetScriptClass(txOut.PkScript) if scriptClass == txscript.NonStandardTy { return true } } return false }
// maybeAddOutpoint potentially adds the passed outpoint to the bloom filter // depending on the bloom update flags and the type of the passed public key // script. // // This function MUST be called with the filter lock held. func (bf *Filter) maybeAddOutpoint(pkScript []byte, outHash *wire.ShaHash, outIdx uint32) { switch bf.msgFilterLoad.Flags { case wire.BloomUpdateAll: outpoint := wire.NewOutPoint(outHash, outIdx) bf.addOutPoint(outpoint) case wire.BloomUpdateP2PubkeyOnly: class := txscript.GetScriptClass(pkScript) if class == txscript.PubKeyTy || class == txscript.MultiSigTy { outpoint := wire.NewOutPoint(outHash, outIdx) bf.addOutPoint(outpoint) } } }
// TestScriptClass ensures all the scripts in scriptClassTests have the expected // class. func TestScriptClass(t *testing.T) { t.Parallel() for _, test := range scriptClassTests { script := mustParseShortForm(test.script) class := txscript.GetScriptClass(script) if class != test.class { t.Errorf("%s: expected %s got %s", test.name, test.class, class) return } } }
// TestCheckPkScriptStandard tests the checkPkScriptStandard API. func TestCheckPkScriptStandard(t *testing.T) { var pubKeys [][]byte for i := 0; i < 4; i++ { pk, err := btcec.NewPrivateKey(btcec.S256()) if err != nil { t.Fatalf("TestCheckPkScriptStandard NewPrivateKey failed: %v", err) return } pubKeys = append(pubKeys, pk.PubKey().SerializeCompressed()) } tests := []struct { name string // test description. script *txscript.ScriptBuilder isStandard bool }{ { "key1 and key2", txscript.NewScriptBuilder().AddOp(txscript.OP_2). AddData(pubKeys[0]).AddData(pubKeys[1]). AddOp(txscript.OP_2).AddOp(txscript.OP_CHECKMULTISIG), true, }, { "key1 or key2", txscript.NewScriptBuilder().AddOp(txscript.OP_1). AddData(pubKeys[0]).AddData(pubKeys[1]). AddOp(txscript.OP_2).AddOp(txscript.OP_CHECKMULTISIG), true, }, { "escrow", txscript.NewScriptBuilder().AddOp(txscript.OP_2). AddData(pubKeys[0]).AddData(pubKeys[1]). AddData(pubKeys[2]). AddOp(txscript.OP_3).AddOp(txscript.OP_CHECKMULTISIG), true, }, { "one of four", txscript.NewScriptBuilder().AddOp(txscript.OP_1). AddData(pubKeys[0]).AddData(pubKeys[1]). AddData(pubKeys[2]).AddData(pubKeys[3]). AddOp(txscript.OP_4).AddOp(txscript.OP_CHECKMULTISIG), false, }, { "malformed1", txscript.NewScriptBuilder().AddOp(txscript.OP_3). AddData(pubKeys[0]).AddData(pubKeys[1]). AddOp(txscript.OP_2).AddOp(txscript.OP_CHECKMULTISIG), false, }, { "malformed2", txscript.NewScriptBuilder().AddOp(txscript.OP_2). AddData(pubKeys[0]).AddData(pubKeys[1]). AddOp(txscript.OP_3).AddOp(txscript.OP_CHECKMULTISIG), false, }, { "malformed3", txscript.NewScriptBuilder().AddOp(txscript.OP_0). AddData(pubKeys[0]).AddData(pubKeys[1]). AddOp(txscript.OP_2).AddOp(txscript.OP_CHECKMULTISIG), false, }, { "malformed4", txscript.NewScriptBuilder().AddOp(txscript.OP_1). AddData(pubKeys[0]).AddData(pubKeys[1]). AddOp(txscript.OP_0).AddOp(txscript.OP_CHECKMULTISIG), false, }, { "malformed5", txscript.NewScriptBuilder().AddOp(txscript.OP_1). AddData(pubKeys[0]).AddData(pubKeys[1]). AddOp(txscript.OP_CHECKMULTISIG), false, }, { "malformed6", txscript.NewScriptBuilder().AddOp(txscript.OP_1). AddData(pubKeys[0]).AddData(pubKeys[1]), false, }, } for _, test := range tests { script, err := test.script.Script() if err != nil { t.Fatalf("TestCheckPkScriptStandard test '%s' "+ "failed: %v", test.name, err) continue } scriptClass := txscript.GetScriptClass(script) got := checkPkScriptStandard(script, scriptClass) if (test.isStandard && got != nil) || (!test.isStandard && got == nil) { t.Fatalf("TestCheckPkScriptStandard test '%s' failed", test.name) return } } }
// checkTransactionStandard performs a series of checks on a transaction to // ensure it is a "standard" transaction. A standard transaction is one that // conforms to several additional limiting cases over what is considered a // "sane" transaction such as having a version in the supported range, being // finalized, conforming to more stringent size constraints, having scripts // of recognized forms, and not containing "dust" outputs (those that are // so small it costs more to process them than they are worth). func checkTransactionStandard(tx *coinutil.Tx, height int32, timeSource blockchain.MedianTimeSource, minRelayTxFee coinutil.Amount) error { // The transaction must be a currently supported version. msgTx := tx.MsgTx() if msgTx.Version > wire.TxVersion || msgTx.Version < 1 { str := fmt.Sprintf("transaction version %d is not in the "+ "valid range of %d-%d", msgTx.Version, 1, wire.TxVersion) return txRuleError(wire.RejectNonstandard, str) } // The transaction must be finalized to be standard and therefore // considered for inclusion in a block. adjustedTime := timeSource.AdjustedTime() if !blockchain.IsFinalizedTransaction(tx, height, adjustedTime) { return txRuleError(wire.RejectNonstandard, "transaction is not finalized") } // Since extremely large transactions with a lot of inputs can cost // almost as much to process as the sender fees, limit the maximum // size of a transaction. This also helps mitigate CPU exhaustion // attacks. serializedLen := msgTx.SerializeSize() if serializedLen > maxStandardTxSize { str := fmt.Sprintf("transaction size of %v is larger than max "+ "allowed size of %v", serializedLen, maxStandardTxSize) return txRuleError(wire.RejectNonstandard, str) } for i, txIn := range msgTx.TxIn { // Each transaction input signature script must not exceed the // maximum size allowed for a standard transaction. See // the comment on maxStandardSigScriptSize for more details. sigScriptLen := len(txIn.SignatureScript) if sigScriptLen > maxStandardSigScriptSize { str := fmt.Sprintf("transaction input %d: signature "+ "script size of %d bytes is large than max "+ "allowed size of %d bytes", i, sigScriptLen, maxStandardSigScriptSize) return txRuleError(wire.RejectNonstandard, str) } // Each transaction input signature script must only contain // opcodes which push data onto the stack. if !txscript.IsPushOnlyScript(txIn.SignatureScript) { str := fmt.Sprintf("transaction input %d: signature "+ "script is not push only", i) return txRuleError(wire.RejectNonstandard, str) } } // None of the output public key scripts can be a non-standard script or // be "dust" (except when the script is a null data script). numNullDataOutputs := 0 for i, txOut := range msgTx.TxOut { scriptClass := txscript.GetScriptClass(txOut.PkScript) err := checkPkScriptStandard(txOut.PkScript, scriptClass) if err != nil { // Attempt to extract a reject code from the error so // it can be retained. When not possible, fall back to // a non standard error. rejectCode := wire.RejectNonstandard if rejCode, found := extractRejectCode(err); found { rejectCode = rejCode } str := fmt.Sprintf("transaction output %d: %v", i, err) return txRuleError(rejectCode, str) } // Accumulate the number of outputs which only carry data. For // all other script types, ensure the output value is not // "dust". if scriptClass == txscript.NullDataTy { numNullDataOutputs++ } else if isDust(txOut, minRelayTxFee) { str := fmt.Sprintf("transaction output %d: payment "+ "of %d is dust", i, txOut.Value) return txRuleError(wire.RejectDust, str) } } // A standard transaction must not have more than one output script that // only carries data. if numNullDataOutputs > 1 { str := "more than one transaction output in a nulldata script" return txRuleError(wire.RejectNonstandard, str) } return nil }