Esempio n. 1
0
// 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
}
Esempio n. 2
0
// 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
		}
	}
}
Esempio n. 3
0
// 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)
		}
	}
}
Esempio n. 4
0
// 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)
}
Esempio n. 5
0
// 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
}
Esempio n. 6
0
// 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
		}
	}
}
Esempio n. 7
0
// 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
}
Esempio n. 8
0
// 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
}
Esempio n. 9
0
// 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
}