From c3b00ea4e73d7d29e4721c1c9746777c68fa8ef1 Mon Sep 17 00:00:00 2001 From: Donald Adu-Poku Date: Sat, 8 Feb 2020 16:37:49 +0000 Subject: [PATCH] multi: replace ExtractPkScriptAddrs consensus calls. --- blockchain/indexers/existsaddrindex.go | 24 +- blockchain/stake/go.mod | 2 + blockchain/stake/scripttype.go | 588 ++++++++++++++++++++++++- blockchain/stake/scripttype_test.go | 497 ++++++++++++++++++++- blockchain/stake/staketx.go | 44 -- blockchain/stake/staketx_test.go | 68 --- blockchain/stakeext.go | 4 +- go.sum | 9 + 8 files changed, 1100 insertions(+), 136 deletions(-) diff --git a/blockchain/indexers/existsaddrindex.go b/blockchain/indexers/existsaddrindex.go index e03126731f..b5b3e966d2 100644 --- a/blockchain/indexers/existsaddrindex.go +++ b/blockchain/indexers/existsaddrindex.go @@ -240,10 +240,10 @@ func (idx *ExistsAddrIndex) ConnectBlock(dbTx database.Tx, block, parent *dcruti // is used for the script version. This will ultimately need to // updated to support new script versions. const scriptVersion = 0 - if txscript.IsMultisigSigScript(txIn.SignatureScript) { - rs := txscript.MultisigRedeemScriptFromScriptSig( + if stake.IsMultisigSigScript(scriptVersion, txIn.SignatureScript) { + rs := stake.MultisigRedeemScriptFromScriptSig(scriptVersion, txIn.SignatureScript) - class, addrs, _, err := txscript.ExtractPkScriptAddrs( + class, addrs, _, err := stake.ExtractPkScriptAddrs( scriptVersion, rs, idx.chainParams) if err != nil { // Non-standard outputs are skipped. @@ -266,7 +266,7 @@ func (idx *ExistsAddrIndex) ConnectBlock(dbTx database.Tx, block, parent *dcruti } for _, txOut := range tx.MsgTx().TxOut { - class, addrs, _, err := txscript.ExtractPkScriptAddrs( + class, addrs, _, err := stake.ExtractPkScriptAddrs( txOut.Version, txOut.PkScript, idx.chainParams) if err != nil { // Non-standard outputs are skipped. @@ -355,14 +355,14 @@ func (idx *ExistsAddrIndex) DisconnectBlock(dbTx database.Tx, block, parent *dcr func (idx *ExistsAddrIndex) addUnconfirmedTx(tx *wire.MsgTx) { isSStx := stake.IsSStx(tx) for _, txIn := range tx.TxIn { - if txscript.IsMultisigSigScript(txIn.SignatureScript) { - // Note that the functions used here require v0 scripts. Hence it - // is used for the script version. This will ultimately need to - // updated to support new script versions. - const scriptVersion = 0 - rs := txscript.MultisigRedeemScriptFromScriptSig( + // Note that the functions used here require v0 scripts. Hence it + // is used for the script version. This will ultimately need to + // updated to support new script versions. + const scriptVersion = 0 + if stake.IsMultisigSigScript(scriptVersion, txIn.SignatureScript) { + rs := stake.MultisigRedeemScriptFromScriptSig(scriptVersion, txIn.SignatureScript) - class, addrs, _, err := txscript.ExtractPkScriptAddrs( + class, addrs, _, err := stake.ExtractPkScriptAddrs( scriptVersion, rs, idx.chainParams) if err != nil { // Non-standard outputs are skipped. @@ -387,7 +387,7 @@ func (idx *ExistsAddrIndex) addUnconfirmedTx(tx *wire.MsgTx) { } for _, txOut := range tx.TxOut { - class, addrs, _, err := txscript.ExtractPkScriptAddrs(txOut.Version, + class, addrs, _, err := stake.ExtractPkScriptAddrs(txOut.Version, txOut.PkScript, idx.chainParams) if err != nil { // Non-standard outputs are skipped. diff --git a/blockchain/stake/go.mod b/blockchain/stake/go.mod index d7af444cdb..910e100d1a 100644 --- a/blockchain/stake/go.mod +++ b/blockchain/stake/go.mod @@ -4,9 +4,11 @@ go 1.11 require ( github.com/decred/dcrd/chaincfg/chainhash v1.0.2 + github.com/decred/dcrd/chaincfg/v2 v2.3.0 github.com/decred/dcrd/chaincfg/v3 v3.0.0-00010101000000-000000000000 github.com/decred/dcrd/database/v2 v2.0.1 github.com/decred/dcrd/dcrec v1.0.0 + github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0-00010101000000-000000000000 github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200104000002-54b67d3474fb github.com/decred/dcrd/txscript/v3 v3.0.0-20200104000002-54b67d3474fb github.com/decred/dcrd/wire v1.3.0 diff --git a/blockchain/stake/scripttype.go b/blockchain/stake/scripttype.go index 727b05f9d8..2e53aa7a4a 100644 --- a/blockchain/stake/scripttype.go +++ b/blockchain/stake/scripttype.go @@ -5,31 +5,236 @@ package stake import ( + "fmt" + + "github.com/decred/dcrd/dcrec" + "github.com/decred/dcrd/dcrec/secp256k1/v3" + "github.com/decred/dcrd/dcrutil/v3" "github.com/decred/dcrd/txscript/v3" ) -// isScriptHashScript returns whether or not the passed script is a -// pay-to-script-hash script per consensus rules. -func isScriptHashScript(script []byte) bool { +// extractScriptHash extracts the script hash from the passed script if it is a +// pay-to-script-hash script. It will return nil otherwise. +// +// NOTE: This function is only valid for version 0 opcodes. +func extractScriptHash(script []byte) []byte { // A pay-to-script-hash script is of the form: // OP_HASH160 <20-byte scripthash> OP_EQUAL - return len(script) == 23 && + if len(script) == 23 && script[0] == txscript.OP_HASH160 && script[1] == txscript.OP_DATA_20 && - script[22] == txscript.OP_EQUAL + script[22] == txscript.OP_EQUAL { + return script[2:22] + } + return nil +} + +// isScriptHashScript returns whether or not the passed script is a +// pay-to-script-hash script per consensus rules. +// +// NOTE: This function is only valid for version 0 opcodes. +func isScriptHashScript(script []byte) bool { + return extractScriptHash(script) != nil +} + +// extractPubKeyHash extracts the public key hash from the passed script if it +// is a standard pay-to-pubkey-hash script. It will return nil otherwise. +// +// NOTE: This function is only valid for version 0 opcodes. +func extractPubKeyHash(script []byte) []byte { + // A pay-to-pubkey-hash script is of the form: + // OP_DUP OP_HASH160 <20-byte hash> OP_EQUALVERIFY OP_CHECKSIG + if len(script) == 25 && + script[0] == txscript.OP_DUP && + script[1] == txscript.OP_HASH160 && + script[2] == txscript.OP_DATA_20 && + script[23] == txscript.OP_EQUALVERIFY && + script[24] == txscript.OP_CHECKSIG { + return script[3:23] + } + return nil +} + +// extractStakeScriptHash extracts a script hash from the passed public key +// script if it is a standard pay-to-script-hash script tagged with the provided +// stake opcode. It will return nil otherwise. +// +// NOTE: This function is only valid for version 0 opcodes. +func extractStakeScriptHash(script []byte, stakeOpcode byte) []byte { + if len(script) == 24 && + script[0] == stakeOpcode && + script[1] == txscript.OP_HASH160 && + script[2] == txscript.OP_DATA_20 && + script[23] == txscript.OP_EQUAL { + return script[3:23] + } + return nil } // isPubKeyHashScript returns whether or not the passed script is // a pay-to-pubkey-hash script per consensus rules. func isPubKeyHashScript(script []byte) bool { - // A pay-to-pubkey-hash script is of the form: - // OP_DUP OP_HASH160 <20-byte hash> OP_EQUALVERIFY OP_CHECKSIG - return len(script) == 25 && + return extractPubKeyHash(script) != nil +} + +// isSmallInt returns whether or not the opcode is considered a small integer, +// which is an OP_0, or OP_1 through OP_16. +// +// NOTE: This function is only valid for version 0 opcodes. Since the function +// does not accept a script version, the results are undefined for other script +// versions. +func isSmallInt(op byte) bool { + return op == txscript.OP_0 || (op >= txscript.OP_1 && op <= txscript.OP_16) +} + +// asSmallInt returns the passed opcode, which must be true according to +// isSmallInt(), as an integer. +func asSmallInt(op byte) int { + if op == txscript.OP_0 { + return 0 + } + return int(op - (txscript.OP_1 - 1)) +} + +// isStandardAltSignatureType returns whether or not the provided opcode +// represents a push of a alt signature type. +func isStandardAltSignatureType(op byte) bool { + if !isSmallInt(op) { + return false + } + sigType := asSmallInt(op) + return sigType == dcrec.STEd25519 || sigType == dcrec.STSchnorrSecp256k1 +} + +// extractPubKeyHashAltDetails extracts the public key hash and signature type +// from the passed script if it is a pay-to-alt-pubkey-hash script. It +// will return nil otherwise. +func extractPubKeyHashAltDetails(script []byte) ([]byte, dcrec.SignatureType) { + // A pay-to-alt-pubkey-hash script is of the form: + // DUP HASH160 <20-byte hash> EQUALVERIFY SIGTYPE CHECKSIG + // + // The only two currently supported alternative signature types are ed25519 + // and schnorr + secp256k1 (with a compressed pubkey). + // + // DUP HASH160 <20-byte hash> EQUALVERIFY <1-byte ed25519 sigtype> CHECKSIG + // DUP HASH160 <20-byte hash> EQUALVERIFY <1-byte schnorr+secp sigtype> CHECKSIG + // + // Notice that OP_0 is not specified since signature type 0 disabled. + + if len(script) == 26 && script[0] == txscript.OP_DUP && script[1] == txscript.OP_HASH160 && script[2] == txscript.OP_DATA_20 && script[23] == txscript.OP_EQUALVERIFY && - script[24] == txscript.OP_CHECKSIG + isStandardAltSignatureType(script[24]) && + script[25] == txscript.OP_CHECKSIGALT { + return script[3:23], dcrec.SignatureType(asSmallInt(script[24])) + } + return nil, 0 +} + +// extractPubKey extracts either a compressed or uncompressed public key from the +// passed script if it is either a standard pay-to-compressed-secp256k1-pubkey +// or pay-to-uncompressed-secp256k1-pubkey script, respectively. It will return +// nil otherwise. +func extractPubKey(script []byte) []byte { + var pk []byte + + // A pay-to-compressed-pubkey script is of the form: + // OP_DATA_33 <33-byte compressed pubkey> OP_CHECKSIG + // + // All compressed secp256k1 public keys must start with 0x02 or 0x03. + if len(script) == 35 && + script[34] == txscript.OP_CHECKSIG && + script[0] == txscript.OP_DATA_33 && + (script[1] == 0x02 || script[1] == 0x03) { + pk = script[1:34] + } + + if pk == nil { + // A pay-to-uncompressed-pubkey script is of the form: + // OP_DATA_65 <65-byte uncompressed pubkey> OP_CHECKSIG + // + // All non-hybrid uncompressed secp256k1 public keys must start with 0x04. + if len(script) == 67 && + script[66] == txscript.OP_CHECKSIG && + script[0] == txscript.OP_DATA_65 && + script[1] == 0x04 { + pk = script[1:66] + } + } + + return pk +} + +// isStrictPubKeyEncoding returns whether or not the passed public key adheres +// to the strict encoding requirements. +func isStrictPubKeyEncoding(pubKey []byte) bool { + // Check for a compressed public key. + if len(pubKey) == 33 && (pubKey[0] == 0x02 || pubKey[0] == 0x03) { + return true + } + + // Check for an uncompressed public key. + if len(pubKey) == 65 && pubKey[0] == 0x04 { + return true + } + + return false +} + +// extractPubKeyAltDetails extracts the public key and signature type from the +// passed script if it is a standard pay-to-alt-pubkey script. It will return +// nil otherwise. +func extractPubKeyAltDetails(script []byte) ([]byte, dcrec.SignatureType) { + // A pay-to-alt-pubkey script is of the form: + // PUBKEY SIGTYPE OP_CHECKSIGALT + // + // The only two currently supported alternative signature types are ed25519 + // and schnorr + secp256k1 (with a compressed pubkey). + // + // OP_DATA_32 <32-byte pubkey> <1-byte ed25519 sigtype> OP_CHECKSIGALT + // OP_DATA_33 <33-byte pubkey> <1-byte schnorr+secp sigtype> OP_CHECKSIGALT + + // The script can't possibly be a pay-to-alt-pubkey script if it doesn't + // end with OP_CHECKSIGALT or have at least two small integer pushes + // preceding it (although any reasonable pubkey will certainly be larger). + // Fail fast to avoid more work below. + if len(script) < 3 || script[len(script)-1] != txscript.OP_CHECKSIGALT { + return nil, 0 + } + + if len(script) == 35 && script[0] == txscript.OP_DATA_32 && + isSmallInt(script[33]) && asSmallInt(script[33]) == dcrec.STEd25519 { + + return script[1:33], dcrec.STEd25519 + } + + if len(script) == 36 && script[0] == txscript.OP_DATA_33 && + isSmallInt(script[34]) && + asSmallInt(script[34]) == dcrec.STSchnorrSecp256k1 && + isStrictPubKeyEncoding(script[1:34]) { + + return script[1:34], dcrec.STSchnorrSecp256k1 + } + + return nil, 0 +} + +// extractStakePubKeyHash extracts a pubkey hash from the passed public key +// script if it is a standard pay-to-pubkey-hash script tagged with the provided +// stake opcode. It will return nil otherwise. +func extractStakePubKeyHash(script []byte, stakeOpcode byte) []byte { + if len(script) == 26 && + script[0] == stakeOpcode && + script[1] == txscript.OP_DUP && + script[2] == txscript.OP_HASH160 && + script[3] == txscript.OP_DATA_20 && + script[24] == txscript.OP_EQUALVERIFY && + script[25] == txscript.OP_CHECKSIG { + return script[4:24] + } + return nil } // isTaggedScript checks if the provided script is tagged by the @@ -74,3 +279,368 @@ func IsStakeChangeScript(version uint16, script []byte) bool { func IsVoteScript(version uint16, script []byte) bool { return isTaggedScript(version, script, txscript.OP_SSGEN) } + +// IsNullDataScript returns whether or not the passed script is a null +// data script. +// +// NOTE: This function is only valid for version 0 scripts. It will always +// return false for other script versions. +func IsNullDataScript(scriptVersion uint16, script []byte) bool { + // The only supported script version is 0. + if scriptVersion != 0 { + return false + } + + // A null script is of the form: + // OP_RETURN + // + // Thus, it can either be a single OP_RETURN or an OP_RETURN followed by a + // data push up to MaxDataCarrierSize bytes. + + // The script can't possibly be a null data script if it doesn't start + // with OP_RETURN. Fail fast to avoid more work below. + if len(script) < 1 || script[0] != txscript.OP_RETURN { + return false + } + + // Single OP_RETURN. + if len(script) == 1 { + return true + } + + // OP_RETURN followed by data push up to MaxDataCarrierSize bytes. + tokenizer := txscript.MakeScriptTokenizer(scriptVersion, script[1:]) + return tokenizer.Next() && tokenizer.Done() && + (isSmallInt(tokenizer.Opcode()) || tokenizer.Opcode() <= txscript.OP_PUSHDATA4) && + len(tokenizer.Data()) <= txscript.MaxDataCarrierSize +} + +// multiSigDetails houses details extracted from a standard multisig script. +type multiSigDetails struct { + requiredSigs int + numPubKeys int + pubKeys [][]byte + valid bool +} + +// extractMultisigScriptDetails attempts to extract details from the passed +// script if it is a standard multisig script. The returned details struct will +// have the valid flag set to false otherwise. +// +// The extract pubkeys flag indicates whether or not the pubkeys themselves +// should also be extracted and is provided because extracting them results in +// an allocation that the caller might wish to avoid. The pubKeys member of +// the returned details struct will be nil when the flag is false. +// +// NOTE: This function is only valid for version 0 scripts. The returned +// details struct will always be empty and have the valid flag set to false for +// other script versions. +func extractMultisigScriptDetails(scriptVersion uint16, script []byte, extractPubKeys bool) multiSigDetails { + // The only supported version is 0. + if scriptVersion != 0 { + return multiSigDetails{} + } + + // A multi-signature script is of the form: + // NUM_SIGS PUBKEY PUBKEY PUBKEY ... NUM_PUBKEYS OP_CHECKMULTISIG + // + // It therefore must end with OP_CHECKMULTISIG and have at least two + // small integer pushes preceding it. + if len(script) < 3 || script[len(script)-1] != txscript.OP_CHECKMULTISIG { + return multiSigDetails{} + } + + // The first opcode must be a small integer specifying the number of + // signatures required. + tokenizer := txscript.MakeScriptTokenizer(scriptVersion, script) + if !tokenizer.Next() || !isSmallInt(tokenizer.Opcode()) { + return multiSigDetails{} + } + requiredSigs := asSmallInt(tokenizer.Opcode()) + + // The next series of opcodes must either push public keys or be a small + // integer specifying the number of public keys. + var numPubKeys int + var pubKeys [][]byte + if extractPubKeys { + pubKeys = make([][]byte, 0, txscript.MaxPubKeysPerMultiSig) + } + for tokenizer.Next() { + data := tokenizer.Data() + if !isStrictPubKeyEncoding(data) { + break + } + numPubKeys++ + if extractPubKeys { + pubKeys = append(pubKeys, data) + } + } + if tokenizer.Done() { + return multiSigDetails{} + } + + // The next opcode must be a small integer specifying the number of public + // keys required. + op := tokenizer.Opcode() + if !isSmallInt(op) || asSmallInt(op) != numPubKeys { + return multiSigDetails{} + } + + // There must only be a single opcode left unparsed which will be + // OP_CHECKMULTISIG per the check above. + if int32(len(tokenizer.Script()))-tokenizer.ByteIndex() != 1 { + return multiSigDetails{} + } + + return multiSigDetails{ + requiredSigs: requiredSigs, + numPubKeys: numPubKeys, + pubKeys: pubKeys, + valid: true, + } +} + +// isMultisigScript returns whether or not the passed script is a standard +// multisig script. +// +// NOTE: This function is only valid for version 0 scripts. It will always +// return false for other script versions. +func isMultisigScript(scriptVersion uint16, script []byte) bool { + details := extractMultisigScriptDetails(scriptVersion, script, false) + return details.valid +} + +// finalOpcodeData returns the data associated with the final opcode in the +// script. It will return nil if the script fails to parse. +func finalOpcodeData(scriptVersion uint16, script []byte) []byte { + // The only supported script version is 0. + if len(script) == 0 { + return nil + } + + var data []byte + tokenizer := txscript.MakeScriptTokenizer(scriptVersion, script) + for tokenizer.Next() { + data = tokenizer.Data() + } + if tokenizer.Err() != nil { + return nil + } + return data +} + +// IsMultisigSigScript returns whether or not the passed script appears to be a +// signature script which consists of a pay-to-script-hash multi-signature +// redeem script. +func IsMultisigSigScript(scriptVersion uint16, script []byte) bool { + // The only supported script version is 0. + if len(script) == 0 { + return false + } + + // A multisig signature script must end with OP_CHECKMULTISIG in the + // redeem script and have at least two small integers preceding it. + // The redeem script itself must be preceded by at least a data + // push opcode. + if len(script) < 4 || script[len(script)-1] != txscript.OP_CHECKMULTISIG { + return false + } + + possibleRedeemScript := finalOpcodeData(scriptVersion, script) + if possibleRedeemScript == nil { + return false + } + + return isMultisigScript(scriptVersion, possibleRedeemScript) +} + +// MultisigRedeemScriptFromScriptSig attempts to extract a multi-signature +// redeem script from a P2SH-redeeming input. The script is expected to already +// have been checked to be a multisignature script prior to calling this +// function. The results are undefined for other script types. +// +// NOTE: This function is only valid for version 0 scripts. +func MultisigRedeemScriptFromScriptSig(version uint16, script []byte) []byte { + // The redeemScript is always the last item on the stack of the script sig. + return finalOpcodeData(version, script) +} + +// ExtractPkScriptAddrs returns the type of script, addresses and required +// signatures associated with the passed PkScript. Note that it only works for +// ransaction script types accepted as consensus. Any data such as public +// keys which are invalid are omitted from the results. +// +// NOTE: This function only attempts to identify version 0 scripts. The return +// value will indicate a nonstandard script type for other script versions along +// with an invalid script version error. +func ExtractPkScriptAddrs(version uint16, pkScript []byte, + chainParams dcrutil.AddressParams) (txscript.ScriptClass, []dcrutil.Address, int, error) { + if version != 0 { + return txscript.NonStandardTy, nil, 0, fmt.Errorf("invalid script version") + } + + // Check for pay-to-pubkey-hash script. + if hash := extractPubKeyHash(pkScript); hash != nil { + addr, err := dcrutil.NewAddressPubKeyHash(hash, chainParams, + dcrec.STEcdsaSecp256k1) + if err != nil { + return txscript.NonStandardTy, nil, 0, err + } + return txscript.PubKeyHashTy, []dcrutil.Address{addr}, 1, nil + } + + // Check for pay-to-script-hash. + if hash := extractScriptHash(pkScript); hash != nil { + addr, err := dcrutil.NewAddressScriptHashFromHash(hash, chainParams) + if err != nil { + return txscript.NonStandardTy, nil, 0, err + } + return txscript.ScriptHashTy, []dcrutil.Address{addr}, 1, nil + } + + // Check for pay-to-alt-pubkey-hash script. + if data, sigType := extractPubKeyHashAltDetails(pkScript); data != nil { + addr, err := dcrutil.NewAddressPubKeyHash(data, chainParams, sigType) + if err != nil { + return txscript.NonStandardTy, nil, 0, err + } + return txscript.PubkeyHashAltTy, []dcrutil.Address{addr}, 1, nil + } + + // Check for pay-to-pubkey script. + if data := extractPubKey(pkScript); data != nil { + pk, err := secp256k1.ParsePubKey(data) + if err != nil { + return txscript.NonStandardTy, nil, 0, err + } + addr, err := dcrutil.NewAddressSecpPubKeyCompressed(pk, chainParams) + if err != nil { + return txscript.NonStandardTy, nil, 0, err + } + return txscript.PubKeyTy, []dcrutil.Address{addr}, 1, nil + } + + // Check for pay-to-alt-pubkey script. + if pk, sigType := extractPubKeyAltDetails(pkScript); pk != nil { + switch sigType { + case dcrec.STEd25519: + addr, err := dcrutil.NewAddressEdwardsPubKey(pk, chainParams) + if err != nil { + return txscript.NonStandardTy, nil, 0, err + } + return txscript.PubkeyAltTy, []dcrutil.Address{addr}, 1, nil + + case dcrec.STSchnorrSecp256k1: + addr, err := dcrutil.NewAddressSecSchnorrPubKey(pk, chainParams) + if err == nil { + return txscript.NonStandardTy, nil, 0, err + } + return txscript.PubkeyAltTy, []dcrutil.Address{addr}, 1, nil + + default: + return txscript.NonStandardTy, nil, 0, + fmt.Errorf("unknown pay-to-alt-pubkey script type") + } + } + + // Check for multi-signature script. + details := extractMultisigScriptDetails(version, pkScript, true) + if details.valid { + // Convert the public keys while skipping any that are invalid. + addrs := make([]dcrutil.Address, 0, details.numPubKeys) + for i := 0; i < details.numPubKeys; i++ { + pubkey, err := secp256k1.ParsePubKey(details.pubKeys[i]) + if err == nil { + addr, err := dcrutil.NewAddressSecpPubKeyCompressed(pubkey, + chainParams) + if err == nil { + addrs = append(addrs, addr) + } + } + } + return txscript.MultiSigTy, addrs, details.requiredSigs, nil + } + + // Check for stake submission script. Only stake-submission-tagged + // pay-to-pubkey-hash and pay-to-script-hash are allowed. + if hash := extractStakePubKeyHash(pkScript, txscript.OP_SSTX); hash != nil { + addr, err := dcrutil.NewAddressPubKeyHash(hash, chainParams, + dcrec.STEcdsaSecp256k1) + if err != nil { + return txscript.NonStandardTy, nil, 0, err + } + return txscript.StakeSubmissionTy, []dcrutil.Address{addr}, 1, nil + } + + if hash := extractStakeScriptHash(pkScript, txscript.OP_SSTX); hash != nil { + addr, err := dcrutil.NewAddressScriptHashFromHash(hash, chainParams) + if err != nil { + return txscript.NonStandardTy, nil, 0, err + } + return txscript.StakeSubmissionTy, []dcrutil.Address{addr}, 1, nil + } + + // Check for stake generation script. Only stake-generation-tagged + // pay-to-pubkey-hash and pay-to-script-hash are allowed. + if hash := extractStakePubKeyHash(pkScript, txscript.OP_SSGEN); hash != nil { + addr, err := dcrutil.NewAddressPubKeyHash(hash, chainParams, + dcrec.STEcdsaSecp256k1) + if err != nil { + return txscript.NonStandardTy, nil, 0, err + } + return txscript.StakeGenTy, []dcrutil.Address{addr}, 1, nil + } + if hash := extractStakeScriptHash(pkScript, txscript.OP_SSGEN); hash != nil { + addr, err := dcrutil.NewAddressScriptHashFromHash(hash, chainParams) + if err != nil { + return txscript.NonStandardTy, nil, 0, err + } + return txscript.StakeGenTy, []dcrutil.Address{addr}, 1, nil + } + + // Check for stake revocation script. Only stake-revocation-tagged + // pay-to-pubkey-hash and pay-to-script-hash are allowed. + if hash := extractStakePubKeyHash(pkScript, txscript.OP_SSRTX); hash != nil { + addr, err := dcrutil.NewAddressPubKeyHash(hash, chainParams, + dcrec.STEcdsaSecp256k1) + if err != nil { + return txscript.NonStandardTy, nil, 0, err + } + return txscript.StakeRevocationTy, []dcrutil.Address{addr}, 1, nil + } + if hash := extractStakeScriptHash(pkScript, txscript.OP_SSRTX); hash != nil { + addr, err := dcrutil.NewAddressScriptHashFromHash(hash, chainParams) + if err != nil { + return txscript.NonStandardTy, nil, 0, err + } + return txscript.StakeRevocationTy, []dcrutil.Address{addr}, 1, nil + } + + // Check for stake change script. Only stake-change-tagged + // pay-to-pubkey-hash and pay-to-script-hash are allowed. + if hash := extractStakePubKeyHash(pkScript, txscript.OP_SSTXCHANGE); hash != nil { + addr, err := dcrutil.NewAddressPubKeyHash(hash, chainParams, + dcrec.STEcdsaSecp256k1) + if err != nil { + return txscript.NonStandardTy, nil, 0, err + } + return txscript.StakeSubChangeTy, []dcrutil.Address{addr}, 1, nil + } + if hash := extractStakeScriptHash(pkScript, txscript.OP_SSTXCHANGE); hash != nil { + addr, err := dcrutil.NewAddressScriptHashFromHash(hash, chainParams) + if err != nil { + return txscript.NonStandardTy, nil, 0, err + } + return txscript.StakeSubChangeTy, []dcrutil.Address{addr}, 1, nil + } + + // Check for null data script. + if IsNullDataScript(version, pkScript) { + // Null data transactions have no addresses or required signatures. + return txscript.NullDataTy, nil, 0, nil + } + + // Don't attempt to extract addresses or required signatures for nonstandard + // transactions. + return txscript.NonStandardTy, nil, 0, nil +} diff --git a/blockchain/stake/scripttype_test.go b/blockchain/stake/scripttype_test.go index 2fc83d72f1..58eaa63909 100644 --- a/blockchain/stake/scripttype_test.go +++ b/blockchain/stake/scripttype_test.go @@ -1,12 +1,19 @@ -// Copyright (c) 2020 The Decred developers +// Copyright (c) 2019 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package stake import ( + "crypto/rand" + "encoding/hex" + "math/big" + "reflect" "testing" + "github.com/decred/dcrd/chaincfg/v2" + "github.com/decred/dcrd/dcrec" + "github.com/decred/dcrd/dcrec/secp256k1/v3" "github.com/decred/dcrd/dcrutil/v3" "github.com/decred/dcrd/txscript/v3" ) @@ -16,6 +23,8 @@ var ( ) func TestIsRevocationScript(t *testing.T) { + t.Parallel() + tests := []struct { name string scriptSource *txscript.ScriptBuilder @@ -100,6 +109,8 @@ func TestIsRevocationScript(t *testing.T) { } func TestIsTicketPurchaseScript(t *testing.T) { + t.Parallel() + tests := []struct { name string scriptSource *txscript.ScriptBuilder @@ -184,6 +195,8 @@ func TestIsTicketPurchaseScript(t *testing.T) { } func TestIsVoteScript(t *testing.T) { + t.Parallel() + tests := []struct { name string scriptSource *txscript.ScriptBuilder @@ -268,6 +281,8 @@ func TestIsVoteScript(t *testing.T) { } func TestIsStakeChangeScript(t *testing.T) { + t.Parallel() + tests := []struct { name string scriptSource *txscript.ScriptBuilder @@ -350,3 +365,483 @@ func TestIsStakeChangeScript(t *testing.T) { } } } + +func TestIsNullDataScript(t *testing.T) { + t.Parallel() + + var overMaxDataCarrierSize = make([]byte, txscript.MaxDataCarrierSize+1) + var underMaxDataCarrierSize = make([]byte, txscript.MaxDataCarrierSize/2) + _, err := rand.Read(overMaxDataCarrierSize) + if err != nil { + t.Fatalf("[Read]: unexpected error: %v", err) + } + _, err = rand.Read(underMaxDataCarrierSize) + if err != nil { + t.Fatalf("[Read]: unexpected error: %v", err) + } + + tests := []struct { + name string + scriptSource *txscript.ScriptBuilder + version uint16 + expected bool + }{ + { + name: "OP_RETURN script", + scriptSource: txscript.NewScriptBuilder(). + AddOp(txscript.OP_RETURN), + version: 0, + expected: true, + }, + { + name: "OP_RETURN script with unsupported version", + scriptSource: txscript.NewScriptBuilder(). + AddOp(txscript.OP_RETURN), + version: 100, + expected: false, + }, + { + name: "OP_RETURN script with data under MaxDataCarrierSize", + scriptSource: txscript.NewScriptBuilder(). + AddOp(txscript.OP_RETURN).AddData(underMaxDataCarrierSize), + version: 0, + expected: true, + }, + { + name: "OP_RETURN script with data over MaxDataCarrierSize", + scriptSource: txscript.NewScriptBuilder(). + AddOp(txscript.OP_RETURN).AddData(overMaxDataCarrierSize), + version: 0, + expected: false, + }, + { + name: "revocation-tagged p2pkh script", + scriptSource: txscript.NewScriptBuilder(). + AddOp(txscript.OP_SSRTX).AddOp(txscript.OP_DUP). + AddOp(txscript.OP_HASH160).AddData(hash160). + AddOp(txscript.OP_EQUALVERIFY).AddOp(txscript.OP_CHECKSIG), + version: 0, + expected: false, + }, + } + + for _, test := range tests { + script, err := test.scriptSource.Script() + if err != nil { + t.Fatalf("%s: unexpected script generation error: %s", + test.name, err) + } + + result := IsNullDataScript(test.version, script) + if result != test.expected { + t.Fatalf("%s: expected %v, got %v", test.name, + test.expected, result) + } + } +} + +func TestIsMultisigSigScript(t *testing.T) { + t.Parallel() + + var pubKeys [][]byte + for i := 0; i < 2; i++ { + pk := secp256k1.NewPrivateKey(big.NewInt(0)) + pubKeys = append(pubKeys, (*secp256k1.PublicKey)(&pk.PublicKey).SerializeCompressed()) + } + + tests := []struct { + name string + scriptSource *txscript.ScriptBuilder + version uint16 + expected bool + }{ + { + name: "multisig script", + scriptSource: txscript.NewScriptBuilder(). + AddOp(txscript.OP_1).AddData(pubKeys[0]).AddData(pubKeys[1]). + AddOp(txscript.OP_2).AddOp(txscript.OP_CHECKMULTISIG), + version: 0, + expected: true, + }, + { + name: "multisig script with invalid version", + scriptSource: txscript.NewScriptBuilder(). + AddOp(txscript.OP_1).AddData(pubKeys[0]).AddData(pubKeys[1]). + AddOp(txscript.OP_2).AddOp(txscript.OP_CHECKMULTISIG), + version: 100, + expected: false, + }, + { + name: "p2pkh script", + scriptSource: txscript.NewScriptBuilder(). + AddOp(txscript.OP_DUP).AddOp(txscript.OP_HASH160). + AddData(hash160).AddOp(txscript.OP_EQUALVERIFY). + AddOp(txscript.OP_CHECKSIG), + version: 0, + expected: false, + }, + { + name: "p2sh script", + scriptSource: txscript.NewScriptBuilder(). + AddOp(txscript.OP_HASH160).AddData(hash160). + AddOp(txscript.OP_EQUAL), + version: 0, + expected: false, + }, + } + + for _, test := range tests { + multiSig, err := test.scriptSource.Script() + if err != nil { + t.Fatalf("%s: unexpected multi sig script generation error: %s", + test.name, err) + } + + sigHex := dcrutil.Hash160(multiSig) + script, err := txscript.NewScriptBuilder().AddData(sigHex). + AddData(multiSig).Script() + if err != nil { + t.Fatalf("%s: unexpected script generation error: %s", + test.name, err) + } + + result := IsMultisigSigScript(test.version, script) + if result != test.expected { + t.Fatalf("%s: expected %v, got %v", test.name, + test.expected, result) + } + } +} + +var mainNetParams = chaincfg.MainNetParams() + +// hexToBytes converts the passed hex string into bytes and will panic if there +// is an error. This is only provided for the hard-coded constants so errors in +// the source code can be detected. It will only (and must only) be called with +// hard-coded values. +func hexToBytes(s string) []byte { + b, err := hex.DecodeString(s) + if err != nil { + panic("invalid hex in source file: " + s) + } + return b +} + +// newAddressPubKey returns a new dcrutil.AddressPubKey from the provided +// serialized public key. It panics if an error occurs. This is only used in +// the tests as a helper since the only way it can fail is if there is an error +// in the test source code. +func newAddressPubKey(serializedPubKey []byte) dcrutil.Address { + pubkey, err := secp256k1.ParsePubKey(serializedPubKey) + if err != nil { + panic("invalid public key in test source") + } + addr, err := dcrutil.NewAddressSecpPubKeyCompressed(pubkey, mainNetParams) + if err != nil { + panic("invalid public key in test source") + } + + return addr +} + +// newAddressPubKeyHash returns a new dcrutil.AddressPubKeyHash from the +// provided hash. It panics if an error occurs. This is only used in the tests +// as a helper since the only way it can fail is if there is an error in the +// test source code. +func newAddressPubKeyHash(pkHash []byte) dcrutil.Address { + addr, err := dcrutil.NewAddressPubKeyHash(pkHash, mainNetParams, + dcrec.STEcdsaSecp256k1) + if err != nil { + panic("invalid public key hash in test source") + } + + return addr +} + +// newAddressScriptHash returns a new dcrutil.AddressScriptHash from the +// provided hash. It panics if an error occurs. This is only used in the tests +// as a helper since the only way it can fail is if there is an error in the +// test source code. +func newAddressScriptHash(scriptHash []byte) dcrutil.Address { + addr, err := dcrutil.NewAddressScriptHashFromHash(scriptHash, mainNetParams) + if err != nil { + panic("invalid script hash in test source") + } + + return addr +} + +func TestExtractPkScriptAddrs(t *testing.T) { + t.Parallel() + + const scriptVersion = 0 + tests := []struct { + name string + script []byte + addrs []dcrutil.Address + reqSigs int + class txscript.ScriptClass + noparse bool + }{ + { + name: "standard p2pk with compressed pubkey (0x02)", + script: hexToBytes("2102192d74d0cb94344c9569c2e779015" + + "73d8d7903c3ebec3a957724895dca52c6b4ac"), + addrs: []dcrutil.Address{ + newAddressPubKey(hexToBytes("02192d74d0cb9434" + + "4c9569c2e77901573d8d7903c3ebec3a9577" + + "24895dca52c6b4")), + }, + reqSigs: 1, + class: txscript.PubKeyTy, + }, + { + name: "standard p2pk with uncompressed pubkey (0x04)", + script: hexToBytes("410411db93e1dcdb8a016b49840f8c53b" + + "c1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddf" + + "b84ccf9744464f82e160bfa9b8b64f9d4c03f999b864" + + "3f656b412a3ac"), + addrs: []dcrutil.Address{ + newAddressPubKey(hexToBytes("0411db93e1dcdb8a" + + "016b49840f8c53bc1eb68a382e97b1482eca" + + "d7b148a6909a5cb2e0eaddfb84ccf9744464" + + "f82e160bfa9b8b64f9d4c03f999b8643f656" + + "b412a3")), + }, + reqSigs: 1, + class: txscript.PubKeyTy, + }, + { + name: "standard p2pk with compressed pubkey (0x03)", + script: hexToBytes("2103b0bd634234abbb1ba1e986e884185" + + "c61cf43e001f9137f23c2c409273eb16e65ac"), + addrs: []dcrutil.Address{ + newAddressPubKey(hexToBytes("03b0bd634234abbb" + + "1ba1e986e884185c61cf43e001f9137f23c2" + + "c409273eb16e65")), + }, + reqSigs: 1, + class: txscript.PubKeyTy, + }, + { + name: "2nd standard p2pk with uncompressed pubkey (0x04)", + script: hexToBytes("4104b0bd634234abbb1ba1e986e884185" + + "c61cf43e001f9137f23c2c409273eb16e6537a576782" + + "eba668a7ef8bd3b3cfb1edb7117ab65129b8a2e681f3" + + "c1e0908ef7bac"), + addrs: []dcrutil.Address{ + newAddressPubKey(hexToBytes("04b0bd634234abbb" + + "1ba1e986e884185c61cf43e001f9137f23c2" + + "c409273eb16e6537a576782eba668a7ef8bd" + + "3b3cfb1edb7117ab65129b8a2e681f3c1e09" + + "08ef7b")), + }, + reqSigs: 1, + class: txscript.PubKeyTy, + }, + { + name: "standard p2pkh", + script: hexToBytes("76a914ad06dd6ddee55cbca9a9e3713bd" + + "7587509a3056488ac"), + addrs: []dcrutil.Address{ + newAddressPubKeyHash(hexToBytes("ad06dd6ddee5" + + "5cbca9a9e3713bd7587509a30564")), + }, + reqSigs: 1, + class: txscript.PubKeyHashTy, + }, + { + name: "standard p2sh", + script: hexToBytes("a91463bcc565f9e68ee0189dd5cc67f1b" + + "0e5f02f45cb87"), + addrs: []dcrutil.Address{ + newAddressScriptHash(hexToBytes("63bcc565f9e6" + + "8ee0189dd5cc67f1b0e5f02f45cb")), + }, + reqSigs: 1, + class: txscript.ScriptHashTy, + }, + // from real tx 60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1, vout 0 + { + name: "standard 1 of 2 multisig", + script: hexToBytes("514104cc71eb30d653c0c3163990c47b9" + + "76f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a47" + + "3e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d1" + + "1fcdd0d348ac4410461cbdcc5409fb4b4d42b51d3338" + + "1354d80e550078cb532a34bfa2fcfdeb7d76519aecc6" + + "2770f5b0e4ef8551946d8a540911abe3e7854a26f39f" + + "58b25c15342af52ae"), + addrs: []dcrutil.Address{ + newAddressPubKey(hexToBytes("04cc71eb30d653c0" + + "c3163990c47b976f3fb3f37cccdcbedb169a" + + "1dfef58bbfbfaff7d8a473e7e2e6d317b87b" + + "afe8bde97e3cf8f065dec022b51d11fcdd0d" + + "348ac4")), + newAddressPubKey(hexToBytes("0461cbdcc5409fb4" + + "b4d42b51d33381354d80e550078cb532a34b" + + "fa2fcfdeb7d76519aecc62770f5b0e4ef855" + + "1946d8a540911abe3e7854a26f39f58b25c1" + + "5342af")), + }, + reqSigs: 1, + class: txscript.MultiSigTy, + }, + // from real tx d646f82bd5fbdb94a36872ce460f97662b80c3050ad3209bef9d1e398ea277ab, vin 1 + { + name: "standard 2 of 3 multisig", + script: hexToBytes("524104cb9c3c222c5f7a7d3b9bd152f36" + + "3a0b6d54c9eb312c4d4f9af1e8551b6c421a6a4ab0e2" + + "9105f24de20ff463c1c91fcf3bf662cdde4783d4799f" + + "787cb7c08869b4104ccc588420deeebea22a7e900cc8" + + "b68620d2212c374604e3487ca08f1ff3ae12bdc63951" + + "4d0ec8612a2d3c519f084d9a00cbbe3b53d071e9b09e" + + "71e610b036aa24104ab47ad1939edcb3db65f7fedea6" + + "2bbf781c5410d3f22a7a3a56ffefb2238af8627363bd" + + "f2ed97c1f89784a1aecdb43384f11d2acc64443c7fc2" + + "99cef0400421a53ae"), + addrs: []dcrutil.Address{ + newAddressPubKey(hexToBytes("04cb9c3c222c5f7a" + + "7d3b9bd152f363a0b6d54c9eb312c4d4f9af" + + "1e8551b6c421a6a4ab0e29105f24de20ff46" + + "3c1c91fcf3bf662cdde4783d4799f787cb7c" + + "08869b")), + newAddressPubKey(hexToBytes("04ccc588420deeeb" + + "ea22a7e900cc8b68620d2212c374604e3487" + + "ca08f1ff3ae12bdc639514d0ec8612a2d3c5" + + "19f084d9a00cbbe3b53d071e9b09e71e610b" + + "036aa2")), + newAddressPubKey(hexToBytes("04ab47ad1939edcb" + + "3db65f7fedea62bbf781c5410d3f22a7a3a5" + + "6ffefb2238af8627363bdf2ed97c1f89784a" + + "1aecdb43384f11d2acc64443c7fc299cef04" + + "00421a")), + }, + reqSigs: 2, + class: txscript.MultiSigTy, + }, + + // The below are nonstandard script due to things such as + // invalid pubkeys, failure to parse, and not being of a + // standard form. + + { + name: "p2pk with uncompressed pk missing OP_CHECKSIG", + script: hexToBytes("410411db93e1dcdb8a016b49840f8c53b" + + "c1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddf" + + "b84ccf9744464f82e160bfa9b8b64f9d4c03f999b864" + + "3f656b412a3"), + addrs: nil, + reqSigs: 0, + class: txscript.NonStandardTy, + }, + { + name: "valid signature from a sigscript - no addresses", + script: hexToBytes("47304402204e45e16932b8af514961a1d" + + "3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd41022" + + "0181522ec8eca07de4860a4acdd12909d831cc56cbba" + + "c4622082221a8768d1d0901"), + addrs: nil, + reqSigs: 0, + class: txscript.NonStandardTy, + }, + // Note the technically the pubkey is the second item on the + // stack, but since the address extraction intentionally only + // works with standard PkScripts, this should not return any + // addresses. + { + name: "valid sigscript to redeem p2pk - no addresses", + script: hexToBytes("493046022100ddc69738bf2336318e4e0" + + "41a5a77f305da87428ab1606f023260017854350ddc0" + + "22100817af09d2eec36862d16009852b7e3a0f6dd765" + + "98290b7834e1453660367e07a014104cd4240c198e12" + + "523b6f9cb9f5bed06de1ba37e96a1bbd13745fcf9d11" + + "c25b1dff9a519675d198804ba9962d3eca2d5937d58e" + + "5a75a71042d40388a4d307f887d"), + addrs: nil, + reqSigs: 0, + class: txscript.NonStandardTy, + }, + // adapted from btc: + // tx 691dd277dc0e90a462a3d652a1171686de49cf19067cd33c7df0392833fb986a, vout 0 + // invalid public keys + { + name: "1 of 3 multisig with invalid pubkeys", + script: hexToBytes("5141042200007353455857696b696c656" + + "16b73204361626c6567617465204261636b75700a0a6" + + "361626c65676174652d3230313031323034313831312" + + "e377a0a0a446f41046e6c6f61642074686520666f6c6" + + "c6f77696e67207472616e73616374696f6e732077697" + + "468205361746f736869204e616b616d6f746f2773206" + + "46f776e6c6f61410420746f6f6c2077686963680a636" + + "16e20626520666f756e6420696e207472616e7361637" + + "4696f6e2036633533636439383731313965663739376" + + "435616463636453ae"), + addrs: []dcrutil.Address{}, + reqSigs: 1, + class: txscript.MultiSigTy, + }, + // adapted from btc: + // tx 691dd277dc0e90a462a3d652a1171686de49cf19067cd33c7df0392833fb986a, vout 44 + // invalid public keys + { + name: "1 of 3 multisig with invalid pubkeys 2", + script: hexToBytes("514104633365633235396337346461636" + + "536666430383862343463656638630a6336366263313" + + "93936633862393461333831316233363536313866653" + + "16539623162354104636163636539393361333938386" + + "134363966636336643664616266640a3236363363666" + + "13963663463303363363039633539336333653931666" + + "56465373032392102323364643432643235363339643" + + "338613663663530616234636434340a00000053ae"), + addrs: []dcrutil.Address{}, + reqSigs: 1, + class: txscript.MultiSigTy, + }, + { + name: "empty script", + script: []byte{}, + addrs: nil, + reqSigs: 0, + class: txscript.NonStandardTy, + }, + { + name: "script that does not parse", + script: []byte{txscript.OP_DATA_45}, + addrs: nil, + reqSigs: 0, + class: txscript.NonStandardTy, + noparse: true, + }, + } + + t.Logf("Running %d tests.", len(tests)) + for i, test := range tests { + class, addrs, reqSigs, err := txscript.ExtractPkScriptAddrs(scriptVersion, + test.script, mainNetParams) + if err != nil && !test.noparse { + t.Errorf("ExtractPkScriptAddrs #%d (%s): %v", i, + test.name, err) + } + + if !reflect.DeepEqual(addrs, test.addrs) { + t.Errorf("ExtractPkScriptAddrs #%d (%s) unexpected "+ + "addresses\ngot %v\nwant %v", i, test.name, + addrs, test.addrs) + continue + } + + if reqSigs != test.reqSigs { + t.Errorf("ExtractPkScriptAddrs #%d (%s) unexpected "+ + "number of required signatures - got %d, "+ + "want %d", i, test.name, reqSigs, test.reqSigs) + continue + } + + if class != test.class { + t.Errorf("ExtractPkScriptAddrs #%d (%s) unexpected "+ + "script type - got %s, want %s", i, test.name, + class, test.class) + continue + } + } +} diff --git a/blockchain/stake/staketx.go b/blockchain/stake/staketx.go index 6285480e7d..beaa97566a 100644 --- a/blockchain/stake/staketx.go +++ b/blockchain/stake/staketx.go @@ -234,50 +234,6 @@ func isNullFraudProof(tx *wire.MsgTx) bool { return true } -// isSmallInt returns whether or not the opcode is considered a small integer, -// which is an OP_0, or OP_1 through OP_16. -// -// NOTE: This function is only valid for version 0 opcodes. -func isSmallInt(op byte) bool { - return op == txscript.OP_0 || (op >= txscript.OP_1 && op <= txscript.OP_16) -} - -// IsNullDataScript returns whether or not the passed script is a null -// data script. -// -// NOTE: This function is only valid for version 0 scripts. It will always -// return false for other script versions. -func IsNullDataScript(scriptVersion uint16, script []byte) bool { - // The only supported script version is 0. - if scriptVersion != 0 { - return false - } - - // A null script is of the form: - // OP_RETURN - // - // Thus, it can either be a single OP_RETURN or an OP_RETURN followed by a - // data push up to MaxDataCarrierSize bytes. - - // The script can't possibly be a null data script if it doesn't start - // with OP_RETURN. Fail fast to avoid more work below. - if len(script) < 1 || script[0] != txscript.OP_RETURN { - return false - } - - // Single OP_RETURN. - if len(script) == 1 { - return true - } - - // OP_RETURN followed by data push up to MaxDataCarrierSize bytes. - tokenizer := txscript.MakeScriptTokenizer(scriptVersion, script[1:]) - return tokenizer.Next() && tokenizer.Done() && - (isSmallInt(tokenizer.Opcode()) || - tokenizer.Opcode() <= txscript.OP_PUSHDATA4) && - len(tokenizer.Data()) <= txscript.MaxDataCarrierSize -} - // IsStakeBase returns whether or not a tx could be considered as having a // topically valid stake base present. func IsStakeBase(tx *wire.MsgTx) bool { diff --git a/blockchain/stake/staketx_test.go b/blockchain/stake/staketx_test.go index 5f334948c0..4391f57989 100644 --- a/blockchain/stake/staketx_test.go +++ b/blockchain/stake/staketx_test.go @@ -6,7 +6,6 @@ package stake_test import ( "bytes" - "math/rand" "reflect" "testing" @@ -1020,73 +1019,6 @@ func TestGetStakeRewards(t *testing.T) { } } -func TestIsNullDataScript(t *testing.T) { - var hash160 = dcrutil.Hash160([]byte("test")) - var overMaxDataCarrierSize = make([]byte, txscript.MaxDataCarrierSize+1) - var underMaxDataCarrierSize = make([]byte, txscript.MaxDataCarrierSize/2) - rand.Read(overMaxDataCarrierSize) - rand.Read(underMaxDataCarrierSize) - - tests := []struct { - name string - scriptSource *txscript.ScriptBuilder - version uint16 - expected bool - }{ - { - name: "OP_RETURN script", - scriptSource: txscript.NewScriptBuilder(). - AddOp(txscript.OP_RETURN), - version: 0, - expected: true, - }, - { - name: "OP_RETURN script with unsupported version", - scriptSource: txscript.NewScriptBuilder(). - AddOp(txscript.OP_RETURN), - version: 100, - expected: false, - }, - { - name: "OP_RETURN script with data under MaxDataCarrierSize", - scriptSource: txscript.NewScriptBuilder(). - AddOp(txscript.OP_RETURN).AddData(underMaxDataCarrierSize), - version: 0, - expected: true, - }, - { - name: "OP_RETURN script with data over MaxDataCarrierSize", - scriptSource: txscript.NewScriptBuilder(). - AddOp(txscript.OP_RETURN).AddData(overMaxDataCarrierSize), - version: 0, - expected: false, - }, - { - name: "revocation-tagged p2pkh script", - scriptSource: txscript.NewScriptBuilder(). - AddOp(txscript.OP_SSRTX).AddOp(txscript.OP_DUP). - AddOp(txscript.OP_HASH160).AddData(hash160). - AddOp(txscript.OP_EQUALVERIFY).AddOp(txscript.OP_CHECKSIG), - version: 0, - expected: false, - }, - } - - for _, test := range tests { - script, err := test.scriptSource.Script() - if err != nil { - t.Fatalf("%s: unexpected script generation error: %s", - test.name, err) - } - - result := stake.IsNullDataScript(test.version, script) - if result != test.expected { - t.Fatalf("%s: expected %v, got %v", test.name, - test.expected, result) - } - } -} - // -------------------------------------------------------------------------------- // TESTING VARIABLES BEGIN HERE diff --git a/blockchain/stakeext.go b/blockchain/stakeext.go index 465fd9859c..c8dc2eac91 100644 --- a/blockchain/stakeext.go +++ b/blockchain/stakeext.go @@ -8,10 +8,10 @@ package blockchain import ( "fmt" + "github.com/decred/dcrd/blockchain/stake/v3" "github.com/decred/dcrd/chaincfg/chainhash" "github.com/decred/dcrd/database/v2" "github.com/decred/dcrd/dcrutil/v3" - "github.com/decred/dcrd/txscript/v3" ) // NextLotteryData returns the next tickets eligible for spending as SSGen @@ -118,7 +118,7 @@ func (b *BlockChain) TicketsWithAddress(address dcrutil.Address) ([]chainhash.Ha } _, addrs, _, err := - txscript.ExtractPkScriptAddrs(utxo.ScriptVersionByIndex(0), + stake.ExtractPkScriptAddrs(utxo.ScriptVersionByIndex(0), utxo.PkScriptByIndex(0), b.chainParams) if err != nil { return err diff --git a/go.sum b/go.sum index 9bb24a4204..40fb0d75d0 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,12 @@ github.com/dchest/siphash v1.2.1 h1:4cLinnzVJDKxTCl9B01807Yiy+W7ZzVHj/KIroQRvT4= github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= github.com/decred/base58 v1.0.2 h1:yupIH6bg+q7KYfBk7oUv3xFjKGb5Ypm4+v/61X4keGY= github.com/decred/base58 v1.0.2/go.mod h1:pXP9cXCfM2sFLb2viz2FNIdeMWmZDBKG3ZBYbiSM78E= +github.com/decred/dcrd/chaincfg v1.5.2 h1:dd6l9rqcpxg2GF5neBmE2XxRc5Lqda45fWmN4XOJRW8= +github.com/decred/dcrd/chaincfg/v2 v2.3.0 h1:ItmU+7DeUtyiabrcW+16MJFgY/BBeeYaPfkBLrFLyjo= +github.com/decred/dcrd/chaincfg/v2 v2.3.0/go.mod h1:7qUJTvn+y/kswSRZ4sT2+EmvlDTDyy2InvNFtX/hxk0= github.com/decred/dcrd/dcrec/edwards/v2 v2.0.0 h1:E5KszxGgpjpmW8vN811G6rBAZg0/S/DftdGqN4FW5x4= github.com/decred/dcrd/dcrec/edwards/v2 v2.0.0/go.mod h1:d0H8xGMWbiIQP7gN3v2rByWUcuZPm9YsgmnfoxgbINc= +github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.0 h1:3GIJYXQDAKpLEFriGFN8SbSffak10UXHGdIcFaMPykY= github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.0/go.mod h1:3s92l0paYkZoIHuj4X93Teg/HB7eGM9x/zokGw+u4mY= github.com/decred/dcrd/rpc/jsonrpc/types v1.0.1 h1:sWsGtWzdmrna6aysDCHwjANTJh+Lxt2xp6S10ahP79Y= github.com/decred/dcrd/rpc/jsonrpc/types v1.0.1/go.mod h1:dJUp9PoyFYklzmlImpVkVLOr6j4zKuUv66YgemP2sd8= @@ -40,12 +44,16 @@ github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34= github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/syndtr/goleveldb v0.0.0-20180708030551-c4c61651e9e3 h1:sAlSBRDl4psFR3ysKXRSE8ss6Mt90+ma1zRTroTNBJA= github.com/syndtr/goleveldb v0.0.0-20180708030551-c4c61651e9e3/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= +github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d h1:gZZadD8H+fF+n9CmNhYL1Y0dJB+kLOmKd7FbPJLeGHs= +github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -71,5 +79,6 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=