// isNonstandardTransaction determines whether a transaction contains any // scripts which are not one of the standard types. func isNonstandardTransaction(tx *dcrutil.Tx) bool { // Check all of the output public key scripts for non-standard scripts. for _, txOut := range tx.MsgTx().TxOut { scriptClass := txscript.GetScriptClass(txOut.Version, txOut.PkScript) if scriptClass == txscript.NonStandardTy { return true } } return false }
// 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(txscript.DefaultScriptVersion, script) if class != test.class { t.Errorf("%s: expected %s got %s", test.name, test.class, class) return } } }
// 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(pkScrVer uint16, pkScript []byte, outHash *chainhash.Hash, outIdx uint32, outTree int8) { switch bf.msgFilterLoad.Flags { case wire.BloomUpdateAll: outpoint := wire.NewOutPoint(outHash, outIdx, outTree) bf.addOutPoint(outpoint) case wire.BloomUpdateP2PubkeyOnly: class := txscript.GetScriptClass(pkScrVer, pkScript) if class == txscript.PubKeyTy || class == txscript.MultiSigTy { outpoint := wire.NewOutPoint(outHash, outIdx, outTree) bf.addOutPoint(outpoint) } } }
// IsDustOutput determines whether a transaction output is considered dust. // Transactions with dust outputs are not standard and are rejected by mempools // with default policies. func IsDustOutput(output *wire.TxOut, relayFeePerKb dcrutil.Amount) bool { // Unspendable outputs which solely carry data are not checked for dust. if txscript.GetScriptClass(output.Version, output.PkScript) == txscript.NullDataTy { return false } // All other unspendable outputs are considered dust. if txscript.IsUnspendable(output.Value, output.PkScript) { return true } return IsDustAmount(dcrutil.Amount(output.Value), len(output.PkScript), relayFeePerKb) }
// 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 *dcrutil.Tx, txType stake.TxType, height int64, timeSource blockchain.MedianTimeSource, minRelayTxFee dcrutil.Amount) error { // The transaction must be a currently supported version. msgTx := tx.MsgTx() if !wire.IsSupportedMsgTxVersion(msgTx) { 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.Version, txOut.PkScript) err := checkPkScriptStandard(txOut.Version, 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, found := extractRejectCode(err) if !found { rejectCode = wire.RejectNonstandard } 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) && txType != stake.TxTypeSStx { 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. However, certain types of standard stake transactions // are allowed to have multiple OP_RETURN outputs, so only throw an error here // if the tx is TxTypeRegular. if numNullDataOutputs > maxNullDataOutputs && txType == stake.TxTypeRegular { str := "more than one transaction output in a nulldata script for a " + "regular type tx" return txRuleError(wire.RejectNonstandard, str) } return nil }
// TestCheckPkScriptStandard tests the checkPkScriptStandard API. func TestCheckPkScriptStandard(t *testing.T) { var pubKeys [][]byte for i := 0; i < 4; i++ { pk := chainec.Secp256k1.NewPrivateKey(big.NewInt(int64(chainec.ECTypeSecp256k1))) pubKeys = append(pubKeys, chainec.Secp256k1.NewPublicKey(pk.Public()).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(0, script) got := checkPkScriptStandard(0, script, scriptClass) if (test.isStandard && got != nil) || (!test.isStandard && got == nil) { t.Fatalf("TestCheckPkScriptStandard test '%s' failed", test.name) return } } }
// IsSSGen returns whether or not a transaction is an SSGen tx. It does some // simple validation steps to make sure the number of inputs, number of // outputs, and the input/output scripts are valid. // // This does NOT check to see if the subsidy is valid or whether or not the // value of input[0] + subsidy = value of the outputs. // // SSGen transactions are specified as below. // Inputs: // Stakebase null input [index 0] // SStx-tagged output [index 1] // // Outputs: // OP_RETURN push of 40 bytes containing: [index 0] // i. 32-byte block header of block being voted on. // ii. 8-byte int of this block's height. // OP_RETURN push of 2 bytes containing votebits [index 1] // SSGen-tagged output to address from SStx-tagged output's tx index output 1 // [index 2] // SSGen-tagged output to address from SStx-tagged output's tx index output 2 // [index 3] // ... // SSGen-tagged output to address from SStx-tagged output's tx index output // MaxInputsPerSStx [index MaxOutputsPerSSgen - 1] // // The errors in this function can be ignored if you want to use it in to // identify SSGen from a list of stake tx. func IsSSGen(tx *wire.MsgTx) (bool, error) { // Check to make sure there aren't too many inputs. // CheckTransactionSanity already makes sure that number of inputs is // greater than 0, so no need to check that. if len(tx.TxIn) != NumInputsPerSSGen { return false, stakeRuleError(ErrSSGenWrongNumInputs, "SSgen tx has an "+ "invalid number of inputs") } // Check to make sure there aren't too many outputs. if len(tx.TxOut) > MaxOutputsPerSSGen { return false, stakeRuleError(ErrSSGenTooManyOutputs, "SSgen tx has too "+ "many outputs") } // Check to make sure there are some outputs. if len(tx.TxOut) == 0 { return false, stakeRuleError(ErrSSGenNoOutputs, "SSgen tx no "+ "many outputs") } // Ensure that the first input is a stake base null input. // Also checks to make sure that there aren't too many or too few inputs. if !IsStakeBase(tx) { return false, stakeRuleError(ErrSSGenNoStakebase, "SSGen tx did not "+ "include a stakebase in the zeroeth input position") } // Check to make sure that the output used as input came from TxTreeStake. for i, txin := range tx.TxIn { // Skip the stakebase if i == 0 { continue } if txin.PreviousOutPoint.Index != 0 { errStr := fmt.Sprintf("SSGen used an invalid input idx (got %v, "+ "want 0)", txin.PreviousOutPoint.Index) return false, stakeRuleError(ErrSSGenWrongIndex, errStr) } if txin.PreviousOutPoint.Tree != wire.TxTreeStake { return false, stakeRuleError(ErrSSGenWrongTxTree, "SSGen used "+ "a non-stake input") } } // Check to make sure that all output scripts are the default version. for _, txOut := range tx.TxOut { if txOut.Version != txscript.DefaultScriptVersion { return false, stakeRuleError(ErrSSGenBadGenOuts, "invalid "+ "script version found in txOut") } } // Ensure the number of outputs is equal to the number of inputs found in // the original SStx + 2. // TODO: Do this in validate, requires DB and valid chain. // Ensure that the second input is an SStx tagged output. // TODO: Do this in validate, as we don't want to actually lookup // old tx here. This function is for more general sorting. // Ensure that the first output is an OP_RETURN push. zeroethOutputVersion := tx.TxOut[0].Version zeroethOutputScript := tx.TxOut[0].PkScript if txscript.GetScriptClass(zeroethOutputVersion, zeroethOutputScript) != txscript.NullDataTy { return false, stakeRuleError(ErrSSGenNoReference, "First SSGen output "+ "should have been an OP_RETURN data push, but was not") } // Ensure that the first output is the correct size. if len(zeroethOutputScript) != SSGenBlockReferenceOutSize { return false, stakeRuleError(ErrSSGenBadReference, "First SSGen output "+ "should have been 43 bytes long, but was not") } // The OP_RETURN output script prefix for block referencing should // conform to the standard. zeroethOutputScriptBuffer := bytes.NewBuffer(zeroethOutputScript) zeroethOutputScriptPrefix := zeroethOutputScriptBuffer.Next(2) if !bytes.Equal(zeroethOutputScriptPrefix, validSSGenReferenceOutPrefix) { return false, stakeRuleError(ErrSSGenBadReference, "First SSGen output "+ "had an invalid prefix") } // Ensure that the block header hash given in the first 32 bytes of the // OP_RETURN push is a valid block header and found in the main chain. // TODO: This is validate level stuff, do this there. // Ensure that the second output is an OP_RETURN push. firstOutputVersion := tx.TxOut[1].Version firstOutputScript := tx.TxOut[1].PkScript if txscript.GetScriptClass(firstOutputVersion, firstOutputScript) != txscript.NullDataTy { return false, stakeRuleError(ErrSSGenNoVotePush, "Second SSGen output "+ "should have been an OP_RETURN data push, but was not") } // The length of the output script should be between 4 and 77 bytes long. if len(firstOutputScript) < SSGenVoteBitsOutputMinSize || len(firstOutputScript) > SSGenVoteBitsOutputMaxSize { str := fmt.Sprintf("SSGen votebits output at output index 1 was a " + "NullData (OP_RETURN) push of the wrong size") return false, stakeRuleError(ErrSSGenBadVotePush, str) } // The OP_RETURN output script prefix for voting should conform to the // standard. firstOutputScriptBuffer := bytes.NewBuffer(firstOutputScript) firstOutputScriptPrefix := firstOutputScriptBuffer.Next(2) minPush := uint8(validSSGenVoteOutMinPrefix[1]) maxPush := uint8(validSSGenVoteOutMinPrefix[1]) + (MaxSingleBytePushLength - minPush) pushLen := uint8(firstOutputScriptPrefix[1]) pushLengthValid := (pushLen >= minPush) && (pushLen <= maxPush) // The first byte should be OP_RETURN, while the second byte should be a // valid push length. if !(firstOutputScriptPrefix[0] == validSSGenVoteOutMinPrefix[0]) || !pushLengthValid { return false, stakeRuleError(ErrSSGenBadVotePush, "Second SSGen output "+ "had an invalid prefix") } // Ensure that the tx height given in the last 8 bytes is StakeMaturity // many blocks ahead of the block in which that SStx appears, otherwise // this ticket has failed to mature and the SStx must be invalid. // TODO: This is validate level stuff, do this there. // Ensure that the remaining outputs are OP_SSGEN tagged. for outTxIndex := 2; outTxIndex < len(tx.TxOut); outTxIndex++ { scrVersion := tx.TxOut[outTxIndex].Version rawScript := tx.TxOut[outTxIndex].PkScript // The script should be a OP_SSGEN tagged output. if txscript.GetScriptClass(scrVersion, rawScript) != txscript.StakeGenTy { str := fmt.Sprintf("SSGen tx output at output index %d was not "+ "an OP_SSGEN tagged output", outTxIndex) return false, stakeRuleError(ErrSSGenBadGenOuts, str) } } return true, nil }
// IsSStx returns whether or not a transaction is an SStx. It does some // simple validation steps to make sure the number of inputs, number of // outputs, and the input/output scripts are valid. // // SStx transactions are specified as below. // Inputs: // untagged output 1 [index 0] // untagged output 2 [index 1] // ... // untagged output MaxInputsPerSStx [index MaxInputsPerSStx-1] // // Outputs: // OP_SSTX tagged output [index 0] // OP_RETURN push of input 1's address for reward receiving [index 1] // OP_SSTXCHANGE tagged output for input 1 [index 2] // OP_RETURN push of input 2's address for reward receiving [index 3] // OP_SSTXCHANGE tagged output for input 2 [index 4] // ... // OP_RETURN push of input MaxInputsPerSStx's address for reward receiving // [index (MaxInputsPerSStx*2)-2] // OP_SSTXCHANGE tagged output [index (MaxInputsPerSStx*2)-1] // // The output OP_RETURN pushes should be of size 20 bytes (standard address). // // The errors in this function can be ignored if you want to use it in to // identify SStx from a list of stake tx. func IsSStx(tx *wire.MsgTx) (bool, error) { // Check to make sure there aren't too many inputs. // CheckTransactionSanity already makes sure that number of inputs is // greater than 0, so no need to check that. if len(tx.TxIn) > MaxInputsPerSStx { return false, stakeRuleError(ErrSStxTooManyInputs, "SStx has too many "+ "inputs") } // Check to make sure there aren't too many outputs. if len(tx.TxOut) > MaxOutputsPerSStx { return false, stakeRuleError(ErrSStxTooManyOutputs, "SStx has too many "+ "outputs") } // Check to make sure there are some outputs. if len(tx.TxOut) == 0 { return false, stakeRuleError(ErrSStxNoOutputs, "SStx has no "+ "outputs") } // Check to make sure that all output scripts are the default version. for idx, txOut := range tx.TxOut { if txOut.Version != txscript.DefaultScriptVersion { errStr := fmt.Sprintf("invalid script version found in "+ "txOut idx %v", idx) return false, stakeRuleError(ErrSStxInvalidOutputs, errStr) } } // Ensure that the first output is tagged OP_SSTX. if txscript.GetScriptClass(tx.TxOut[0].Version, tx.TxOut[0].PkScript) != txscript.StakeSubmissionTy { return false, stakeRuleError(ErrSStxInvalidOutputs, "First SStx output "+ "should have been OP_SSTX tagged, but it was not") } // Ensure that the number of outputs is equal to the number of inputs // + 1. if (len(tx.TxIn)*2 + 1) != len(tx.TxOut) { return false, stakeRuleError(ErrSStxInOutProportions, "The number of "+ "inputs in the SStx tx was not the number of outputs/2 - 1") } // Ensure that the rest of the odd outputs are 28-byte OP_RETURN pushes that // contain putative pubkeyhashes, and that the rest of the odd outputs are // OP_SSTXCHANGE tagged. for outTxIndex := 1; outTxIndex < len(tx.TxOut); outTxIndex++ { scrVersion := tx.TxOut[outTxIndex].Version rawScript := tx.TxOut[outTxIndex].PkScript // Check change outputs. if outTxIndex%2 == 0 { if txscript.GetScriptClass(scrVersion, rawScript) != txscript.StakeSubChangeTy { str := fmt.Sprintf("SStx output at output index %d was not "+ "an sstx change output", outTxIndex) return false, stakeRuleError(ErrSStxInvalidOutputs, str) } continue } // Else (odd) check commitment outputs. The script should be a // NullDataTy output. if txscript.GetScriptClass(scrVersion, rawScript) != txscript.NullDataTy { str := fmt.Sprintf("SStx output at output index %d was not "+ "a NullData (OP_RETURN) push", outTxIndex) return false, stakeRuleError(ErrSStxInvalidOutputs, str) } // The length of the output script should be between 32 and 77 bytes long. if len(rawScript) < SStxPKHMinOutSize || len(rawScript) > SStxPKHMaxOutSize { str := fmt.Sprintf("SStx output at output index %d was a "+ "NullData (OP_RETURN) push of the wrong size", outTxIndex) return false, stakeRuleError(ErrSStxInvalidOutputs, str) } // The OP_RETURN output script prefix should conform to the standard. outputScriptBuffer := bytes.NewBuffer(rawScript) outputScriptPrefix := outputScriptBuffer.Next(2) minPush := uint8(validSStxAddressOutMinPrefix[1]) maxPush := uint8(validSStxAddressOutMinPrefix[1]) + (MaxSingleBytePushLength - minPush) pushLen := uint8(outputScriptPrefix[1]) pushLengthValid := (pushLen >= minPush) && (pushLen <= maxPush) // The first byte should be OP_RETURN, while the second byte should be a // valid push length. if !(outputScriptPrefix[0] == validSStxAddressOutMinPrefix[0]) || !pushLengthValid { errStr := fmt.Sprintf("sstx commitment at output idx %v had "+ "an invalid prefix", outTxIndex) return false, stakeRuleError(ErrSStxInvalidOutputs, errStr) } } return true, nil }
// IsSSRtx returns whether or not a transaction is an SSRtx. It does some // simple validation steps to make sure the number of inputs, number of // outputs, and the input/output scripts are valid. // // SSRtx transactions are specified as below. // Inputs: // SStx-tagged output [index 0] // // Outputs: // SSGen-tagged output to address from SStx-tagged output's tx index output 1 // [index 0] // SSGen-tagged output to address from SStx-tagged output's tx index output 2 // [index 1] // ... // SSGen-tagged output to address from SStx-tagged output's tx index output // MaxInputsPerSStx [index MaxOutputsPerSSRtx - 1] // // The errors in this function can be ignored if you want to use it in to // identify SSRtx from a list of stake tx. func IsSSRtx(tx *wire.MsgTx) (bool, error) { // Check to make sure there is the correct number of inputs. // CheckTransactionSanity already makes sure that number of inputs is // greater than 0, so no need to check that. if len(tx.TxIn) != NumInputsPerSSRtx { return false, stakeRuleError(ErrSSRtxWrongNumInputs, "SSRtx has an "+ " invalid number of inputs") } // Check to make sure there aren't too many outputs. if len(tx.TxOut) > MaxOutputsPerSSRtx { return false, stakeRuleError(ErrSSRtxTooManyOutputs, "SSRtx has too "+ "many outputs") } // Check to make sure there are some outputs. if len(tx.TxOut) == 0 { return false, stakeRuleError(ErrSSRtxNoOutputs, "SSRtx has no "+ "outputs") } // Check to make sure that all output scripts are the default version. for _, txOut := range tx.TxOut { if txOut.Version != txscript.DefaultScriptVersion { return false, stakeRuleError(ErrSSRtxBadOuts, "invalid "+ "script version found in txOut") } } // Check to make sure that the output used as input came from TxTreeStake. for _, txin := range tx.TxIn { if txin.PreviousOutPoint.Tree != wire.TxTreeStake { return false, stakeRuleError(ErrSSRtxWrongTxTree, "SSRtx used "+ "a non-stake input") } } // Ensure that the first input is an SStx tagged output. // TODO: Do this in validate, needs a DB and chain. // Ensure that the tx height given in the last 8 bytes is StakeMaturity // many blocks ahead of the block in which that SStx appear, otherwise // this ticket has failed to mature and the SStx must be invalid. // TODO: Do this in validate, needs a DB and chain. // Ensure that the outputs are OP_SSRTX tagged. // Ensure that the tx height given in the last 8 bytes is StakeMaturity // many blocks ahead of the block in which that SStx appear, otherwise // this ticket has failed to mature and the SStx must be invalid. // TODO: This is validate level stuff, do this there. // Ensure that the outputs are OP_SSRTX tagged. for outTxIndex := 0; outTxIndex < len(tx.TxOut); outTxIndex++ { scrVersion := tx.TxOut[outTxIndex].Version rawScript := tx.TxOut[outTxIndex].PkScript // The script should be a OP_SSRTX tagged output. if txscript.GetScriptClass(scrVersion, rawScript) != txscript.StakeRevocationTy { str := fmt.Sprintf("SSRtx output at output index %d was not "+ "an OP_SSRTX tagged output", outTxIndex) return false, stakeRuleError(ErrSSRtxBadOuts, str) } } // Ensure the number of outputs is equal to the number of inputs found in // the original SStx. // TODO: Do this in validate, needs a DB and chain. return true, nil }