decred.org/dcrdex@v1.0.3/dex/networks/dcr/script.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package dcr
     5  
     6  import (
     7  	"bytes"
     8  	"crypto/sha256"
     9  	"encoding/binary"
    10  	"errors"
    11  	"fmt"
    12  
    13  	"decred.org/dcrdex/dex"
    14  	"decred.org/dcrdex/server/account"
    15  	"decred.org/dcrwallet/v4/wallet/txsizes"
    16  	"github.com/decred/dcrd/chaincfg/v3"
    17  	"github.com/decred/dcrd/dcrec"
    18  	"github.com/decred/dcrd/dcrutil/v4"
    19  	"github.com/decred/dcrd/txscript/v4"
    20  	"github.com/decred/dcrd/txscript/v4/stdaddr"
    21  	"github.com/decred/dcrd/txscript/v4/stdscript"
    22  	"github.com/decred/dcrd/wire"
    23  )
    24  
    25  const (
    26  	// MaxCLTVScriptNum is the largest usable value for a CLTV lockTime. This
    27  	// will actually be stored in a 5-byte ScriptNum since they have a sign bit,
    28  	// however, it is not 2^39-1 since the spending transaction's nLocktime is
    29  	// an unsigned 32-bit integer and it must be at least the CLTV value. This
    30  	// establishes a maximum lock time of February 7, 2106. Any later requires
    31  	// using a block height instead of a unix epoch time stamp.
    32  	MaxCLTVScriptNum = 1<<32 - 1 // 0xffff_ffff a.k.a. 2^32-1
    33  
    34  	// SecretHashSize is the byte-length of the hash of the secret key used in an
    35  	// atomic swap.
    36  	SecretHashSize = 32
    37  
    38  	// SecretKeySize is the byte-length of the secret key used in an atomic swap.
    39  	SecretKeySize = 32
    40  
    41  	// Size of serialized compressed public key.
    42  	pubkeyLength = 33 // Length of a serialized compressed pubkey.
    43  
    44  	// SwapContractSize is the worst case scenario size for a swap contract,
    45  	// which is the pk-script of the non-change output of an initialization
    46  	// transaction as used in execution of an atomic swap.
    47  	// See ExtractSwapDetails for a breakdown of the bytes.
    48  	SwapContractSize = 97
    49  
    50  	// TxInOverhead is the overhead for a wire.TxIn with a scriptSig length <
    51  	// 254. prefix (41 bytes) + ValueIn (8 bytes) + BlockHeight (4 bytes) +
    52  	// BlockIndex (4 bytes) + sig script var int (at least 1 byte)
    53  	TxInOverhead = 41 + 8 + 4 + 4 // 57 + at least 1 more
    54  
    55  	P2PKSigScriptSize = txsizes.RedeemP2PKSigScriptSize
    56  
    57  	P2PKHSigScriptSize = txsizes.RedeemP2PKHSigScriptSize
    58  	P2PKHInputSize     = TxInOverhead + 1 + P2PKHSigScriptSize // 57 + 1 + 108 = 166
    59  
    60  	// P2SHSigScriptSize and P2SHInputSize do not include the redeem script size
    61  	// (unknown), which is concatenated on execution with the p2sh pkScript.
    62  	//P2SHSigScriptSize = txsizes.RedeemP2SHSigScriptSize      // 110 + redeem script!
    63  	//P2SHInputSize     = TxInOverhead + 1 + P2SHSigScriptSize // 57 + 1 + 110 = 168 + redeem script!
    64  
    65  	// TxOutOverhead is the overhead associated with a transaction output.
    66  	// 8 bytes value + 2 bytes version + at least 1 byte varint script size
    67  	TxOutOverhead = 8 + 2 + 1
    68  
    69  	P2PKHOutputSize = TxOutOverhead + txsizes.P2PKHPkScriptSize // 36
    70  	P2SHOutputSize  = TxOutOverhead + txsizes.P2SHPkScriptSize  // 34
    71  
    72  	// MsgTxOverhead is 4 bytes version (lower 2 bytes for the real transaction
    73  	// version and upper 2 bytes for the serialization type) + 4 bytes locktime
    74  	// + 4 bytes expiry + 3 bytes of varints for the number of transaction
    75  	// inputs (x2 for witness and prefix) and outputs
    76  	MsgTxOverhead = 4 + 4 + 4 + 3 // 15
    77  
    78  	// InitTxSizeBase is the size of a standard serialized atomic swap
    79  	// initialization transaction with one change output and no inputs. MsgTx
    80  	// overhead is 4 bytes version + 4 bytes locktime + 4 bytes expiry + 3 bytes
    81  	// of varints for the number of transaction inputs (x2 for witness and
    82  	// prefix) and outputs. There is one P2SH output with a 23 byte pkScript,
    83  	// and one P2PKH change output with a 25 byte pkScript.
    84  	InitTxSizeBase = MsgTxOverhead + P2PKHOutputSize + P2SHOutputSize // 15 + 36 + 34 = 85
    85  
    86  	// InitTxSize is InitTxBaseSize + 1 P2PKH input
    87  	InitTxSize = InitTxSizeBase + P2PKHInputSize // 85(83) + 166 = 251
    88  
    89  	// DERSigLength is the maximum length of a DER encoded signature.
    90  	DERSigLength = 73
    91  
    92  	// RedeemSwapSigScriptSize is the worst case (largest) serialize size
    93  	// of a transaction signature script that redeems atomic swap output contract.
    94  	// It is calculated as:
    95  	//
    96  	//   - OP_DATA_73
    97  	//   - 72 bytes DER signature + 1 byte sighash type
    98  	//   - OP_DATA_33
    99  	//   - 33 bytes serialized compressed pubkey
   100  	//   - OP_DATA_32
   101  	//   - 32 bytes secret key
   102  	//   - OP_1
   103  	//   - varint 97 => OP_PUSHDATA1(0x4c) + 0x61
   104  	//   - 97 bytes contract script
   105  	RedeemSwapSigScriptSize = 1 + DERSigLength + 1 + pubkeyLength + 1 + 32 + 1 + 2 + SwapContractSize // 241
   106  
   107  	// RefundSigScriptSize is the worst case (largest) serialize size
   108  	// of a transaction input script that refunds a compressed P2PKH output.
   109  	// It is calculated as:
   110  	//
   111  	//   - OP_DATA_73
   112  	//   - 72 bytes DER signature + 1 byte sighash type
   113  	//   - OP_DATA_33
   114  	//   - 33 bytes serialized compressed pubkey
   115  	//   - OP_0
   116  	//   - varint 97 => OP_PUSHDATA1(0x4c) + 0x61
   117  	//   - 97 bytes contract script
   118  	RefundSigScriptSize = 1 + DERSigLength + 1 + pubkeyLength + 1 + 2 + SwapContractSize // 208
   119  
   120  	// BondScriptSize is the maximum size of a DEX time-locked fidelity bond
   121  	// output script to which a bond P2SH pays:
   122  	//   OP_DATA_4/5 (4/5 bytes lockTime) OP_CHECKLOCKTIMEVERIFY OP_DROP OP_DUP OP_HASH160 OP_DATA_20 (20-byte pubkey hash160) OP_EQUALVERIFY OP_CHECKSIG
   123  	BondScriptSize = 1 + 5 + 1 + 1 + 1 + 1 + 1 + 20 + 1 + 1 // 33
   124  
   125  	// RedeemBondSigScriptSize is the worst case size of a fidelity bond
   126  	// signature script that spends a bond output. It includes a signature, a
   127  	// compressed pubkey, and the bond script. Each of said data pushes use an
   128  	// OP_DATA_ code.
   129  	RedeemBondSigScriptSize = 1 + DERSigLength + 1 + pubkeyLength + 1 + BondScriptSize // 142
   130  
   131  	// BondPushDataSize is the size of the nulldata in a bond commitment output:
   132  	//  OP_RETURN <pushData: ver[2] | account_id[32] | lockTime[4] | pkh[20]>
   133  	BondPushDataSize = 2 + account.HashSize + 4 + 20
   134  )
   135  
   136  // redeemP2SHTxSize calculates the size of the redeeming transaction for a
   137  // P2SH transaction with the given sigScipt (or witness data) size.
   138  func redeemP2SHTxSize(redeemSigScriptSize uint64) uint64 {
   139  	inputSize := TxInOverhead + redeemSigScriptSize
   140  	return MsgTxOverhead + inputSize + P2PKHOutputSize
   141  }
   142  
   143  // redeemSwapTxSize returns the size of a swap refund tx.
   144  func redeemSwapTxSize() uint64 {
   145  	return redeemP2SHTxSize(RedeemSwapSigScriptSize)
   146  }
   147  
   148  // refundBondTxSize returns the size of a bond refund tx.
   149  func refundBondTxSize() uint64 {
   150  	return redeemP2SHTxSize(RedeemBondSigScriptSize)
   151  }
   152  
   153  // minHTLCValue calculates the minimum value for the output of a chained
   154  // P2SH -> P2WPKH transaction pair where the spending tx size is known.
   155  func minHTLCValue(maxFeeRate, redeemTxSize uint64) uint64 {
   156  	// Reversing IsDustVal.
   157  	// totalSize adds some buffer for the spending transaction.
   158  	var outputSize uint64 = P2PKHOutputSize // larger of bonds p2sh output and refund's p2pkh output.
   159  	totalSize := outputSize + 165
   160  	minInitTxValue := maxFeeRate * totalSize * 3
   161  
   162  	// The minInitTxValue would get the bond tx accepted, but when we go to
   163  	// refund, we need that output to pass too, So let's add the fees for the
   164  	// refund transaction.
   165  	redeemFees := redeemTxSize * maxFeeRate
   166  	return minInitTxValue + redeemFees
   167  }
   168  
   169  // MinBondSize is the minimum bond size that avoids dust for a given max network
   170  // fee rate.
   171  func MinBondSize(maxFeeRate uint64) uint64 {
   172  	refundTxSize := refundBondTxSize()
   173  	return minHTLCValue(maxFeeRate, refundTxSize)
   174  }
   175  
   176  // MinLotSize is the minimum lot size that avoids dust for a given max network
   177  // fee rate.
   178  func MinLotSize(maxFeeRate uint64) uint64 {
   179  	redeemSize := redeemSwapTxSize()
   180  	return minHTLCValue(maxFeeRate, redeemSize)
   181  }
   182  
   183  // ScriptType is a bitmask with information about a pubkey script and
   184  // possibly its redeem script.
   185  type ScriptType uint8
   186  
   187  const (
   188  	ScriptP2PKH ScriptType = 1 << iota
   189  	ScriptP2PK
   190  	ScriptP2SH
   191  	ScriptStake
   192  	ScriptMultiSig
   193  	ScriptSigEdwards
   194  	ScriptSigSchnorr
   195  	ScriptUnsupported
   196  )
   197  
   198  // ParseScriptType creates a ScriptType bitmask for a pkScript.
   199  func ParseScriptType(scriptVersion uint16, pkScript []byte) ScriptType {
   200  	st := stdscript.DetermineScriptType(scriptVersion, pkScript)
   201  	return convertScriptType(st)
   202  }
   203  
   204  // IsP2SH will return boolean true if the script is a P2SH script.
   205  func (s ScriptType) IsP2SH() bool {
   206  	return s&ScriptP2SH != 0
   207  }
   208  
   209  // IsStake will return boolean true if the pubkey script it tagged with a stake
   210  // opcode.
   211  func (s ScriptType) IsStake() bool {
   212  	return s&ScriptStake != 0
   213  }
   214  
   215  // IsP2PK will return boolean true if the script is a P2PK script.
   216  func (s ScriptType) IsP2PK() bool {
   217  	return s&ScriptP2PK != 0
   218  }
   219  
   220  // IsP2PKH will return boolean true if the script is a P2PKH script.
   221  func (s ScriptType) IsP2PKH() bool {
   222  	return s&ScriptP2PKH != 0
   223  }
   224  
   225  // IsMultiSig is whether the pkscript references a multi-sig redeem script.
   226  // Since the DEX will know the redeem script, we can say whether it's multi-sig.
   227  func (s ScriptType) IsMultiSig() bool {
   228  	return s&ScriptMultiSig != 0
   229  }
   230  
   231  func convertScriptType(st stdscript.ScriptType) ScriptType {
   232  	var scriptType ScriptType
   233  	switch st {
   234  	// P2PK
   235  	case stdscript.STPubKeyEcdsaSecp256k1:
   236  		scriptType = ScriptP2PK
   237  	case stdscript.STPubKeyEd25519:
   238  		scriptType = ScriptP2PK | ScriptSigEdwards
   239  	case stdscript.STPubKeySchnorrSecp256k1:
   240  		scriptType = ScriptP2PK | ScriptSigSchnorr
   241  	// P2PKH
   242  	case stdscript.STPubKeyHashEcdsaSecp256k1:
   243  		scriptType = ScriptP2PKH
   244  	case stdscript.STPubKeyHashEd25519:
   245  		scriptType = ScriptP2PKH | ScriptSigEdwards
   246  	case stdscript.STPubKeyHashSchnorrSecp256k1:
   247  		scriptType = ScriptP2PKH | ScriptSigSchnorr
   248  	case stdscript.STStakeSubmissionPubKeyHash, stdscript.STStakeChangePubKeyHash,
   249  		stdscript.STStakeGenPubKeyHash, stdscript.STStakeRevocationPubKeyHash,
   250  		stdscript.STTreasuryGenPubKeyHash:
   251  		scriptType = ScriptP2PKH | ScriptStake
   252  	// P2SH
   253  	case stdscript.STScriptHash:
   254  		scriptType = ScriptP2SH
   255  	case stdscript.STStakeChangeScriptHash, stdscript.STStakeGenScriptHash,
   256  		stdscript.STStakeRevocationScriptHash, stdscript.STStakeSubmissionScriptHash,
   257  		stdscript.STTreasuryGenScriptHash:
   258  		scriptType = ScriptP2SH | ScriptStake
   259  	// P2MS
   260  	case stdscript.STMultiSig:
   261  		scriptType = ScriptMultiSig
   262  	default: // STNonStandard, STNullData, STTreasuryAdd, etc
   263  		return ScriptUnsupported
   264  	}
   265  	return scriptType
   266  }
   267  
   268  // AddressScript returns the raw bytes of the address to be used when inserting
   269  // the address into a txout's script. This is currently the address' pubkey or
   270  // script hash160. Other address types are unsupported.
   271  func AddressScript(addr stdaddr.Address) ([]byte, error) {
   272  	switch addr := addr.(type) {
   273  	case stdaddr.SerializedPubKeyer:
   274  		return addr.SerializedPubKey(), nil
   275  	case stdaddr.Hash160er:
   276  		return addr.Hash160()[:], nil
   277  	default:
   278  		return nil, fmt.Errorf("unsupported address type %T", addr)
   279  	}
   280  }
   281  
   282  // AddressSigType returns the digital signature algorithm for the specified
   283  // address.
   284  func AddressSigType(addr stdaddr.Address) (sigType dcrec.SignatureType, err error) {
   285  	switch addr.(type) {
   286  	case *stdaddr.AddressPubKeyEcdsaSecp256k1V0:
   287  		sigType = dcrec.STEcdsaSecp256k1
   288  	case *stdaddr.AddressPubKeyHashEcdsaSecp256k1V0:
   289  		sigType = dcrec.STEcdsaSecp256k1
   290  
   291  	case *stdaddr.AddressPubKeyEd25519V0:
   292  		sigType = dcrec.STEd25519
   293  	case *stdaddr.AddressPubKeyHashEd25519V0:
   294  		sigType = dcrec.STEd25519
   295  
   296  	case *stdaddr.AddressPubKeySchnorrSecp256k1V0:
   297  		sigType = dcrec.STSchnorrSecp256k1
   298  	case *stdaddr.AddressPubKeyHashSchnorrSecp256k1V0:
   299  		sigType = dcrec.STSchnorrSecp256k1
   300  
   301  	default:
   302  		err = fmt.Errorf("unsupported signature type")
   303  	}
   304  	return sigType, err
   305  }
   306  
   307  // MakeContract creates an atomic swap contract. The secretHash MUST be computed
   308  // from a secret of length SecretKeySize bytes or the resulting contract will be
   309  // invalid.
   310  func MakeContract(recipient, sender string, secretHash []byte, lockTime int64, chainParams *chaincfg.Params) ([]byte, error) {
   311  	parseAddress := func(address string) (*stdaddr.AddressPubKeyHashEcdsaSecp256k1V0, error) {
   312  		addr, err := stdaddr.DecodeAddress(address, chainParams)
   313  		if err != nil {
   314  			return nil, fmt.Errorf("error decoding address %s: %w", address, err)
   315  		}
   316  		if addr, ok := addr.(*stdaddr.AddressPubKeyHashEcdsaSecp256k1V0); ok {
   317  			return addr, nil
   318  		} else {
   319  			return nil, fmt.Errorf("address %s is not a pubkey-hash address or "+
   320  				"signature algorithm is unsupported", address)
   321  		}
   322  	}
   323  
   324  	rAddr, err := parseAddress(recipient)
   325  	if err != nil {
   326  		return nil, fmt.Errorf("invalid recipient address: %w", err)
   327  	}
   328  	sAddr, err := parseAddress(sender)
   329  	if err != nil {
   330  		return nil, fmt.Errorf("invalid sender address: %w", err)
   331  	}
   332  
   333  	if len(secretHash) != SecretHashSize {
   334  		return nil, fmt.Errorf("secret hash of length %d not supported", len(secretHash))
   335  	}
   336  
   337  	return txscript.NewScriptBuilder().
   338  		AddOps([]byte{
   339  			txscript.OP_IF,
   340  			txscript.OP_SIZE,
   341  		}).AddInt64(SecretKeySize).
   342  		AddOps([]byte{
   343  			txscript.OP_EQUALVERIFY,
   344  			txscript.OP_SHA256,
   345  		}).AddData(secretHash).
   346  		AddOps([]byte{
   347  			txscript.OP_EQUALVERIFY,
   348  			txscript.OP_DUP,
   349  			txscript.OP_HASH160,
   350  		}).AddData(rAddr.Hash160()[:]).
   351  		AddOp(txscript.OP_ELSE).
   352  		AddInt64(lockTime).
   353  		AddOps([]byte{
   354  			txscript.OP_CHECKLOCKTIMEVERIFY,
   355  			txscript.OP_DROP,
   356  			txscript.OP_DUP,
   357  			txscript.OP_HASH160,
   358  		}).AddData(sAddr.Hash160()[:]).
   359  		AddOps([]byte{
   360  			txscript.OP_ENDIF,
   361  			txscript.OP_EQUALVERIFY,
   362  			txscript.OP_CHECKSIG,
   363  		}).Script()
   364  }
   365  
   366  // RedeemP2SHContract returns the signature script to redeem a contract output
   367  // using the redeemer's signature and the initiator's secret.  This function
   368  // assumes P2SH and appends the contract as the final data push.
   369  func RedeemP2SHContract(contract, sig, pubkey, secret []byte) ([]byte, error) {
   370  	return txscript.NewScriptBuilder().
   371  		AddData(sig).
   372  		AddData(pubkey).
   373  		AddData(secret).
   374  		AddInt64(1).
   375  		AddData(contract).
   376  		Script()
   377  }
   378  
   379  // RefundP2SHContract returns the signature script to refund a contract output
   380  // using the contract author's signature after the locktime has been reached.
   381  // This function assumes P2SH and appends the contract as the final data push.
   382  func RefundP2SHContract(contract, sig, pubkey []byte) ([]byte, error) {
   383  	return txscript.NewScriptBuilder().
   384  		AddData(sig).
   385  		AddData(pubkey).
   386  		AddInt64(0).
   387  		AddData(contract).
   388  		Script()
   389  }
   390  
   391  // OP_RETURN <pushData: ver[2] | account_id[32] | lockTime[4] | pkh[20]>
   392  func extractBondCommitDataV0(pushData []byte) (acct account.AccountID, lockTime uint32, pubkeyHash [20]byte, err error) {
   393  	if len(pushData) < 2 {
   394  		err = errors.New("invalid data")
   395  		return
   396  	}
   397  	ver := binary.BigEndian.Uint16(pushData)
   398  	if ver != 0 {
   399  		err = fmt.Errorf("unexpected bond commitment version %d, expected 0", ver)
   400  		return
   401  	}
   402  
   403  	if len(pushData) != BondPushDataSize {
   404  		err = fmt.Errorf("invalid bond commitment output script length: %d", len(pushData))
   405  		return
   406  	}
   407  
   408  	pushData = pushData[2:] // pop off ver
   409  
   410  	copy(acct[:], pushData)
   411  	pushData = pushData[account.HashSize:]
   412  
   413  	lockTime = binary.BigEndian.Uint32(pushData)
   414  	pushData = pushData[4:]
   415  
   416  	copy(pubkeyHash[:], pushData)
   417  
   418  	return
   419  }
   420  
   421  // ExtractBondCommitDataV0 parses a v0 bond commitment output script. This is
   422  // the OP_RETURN output, not the P2SH bond output. Use ExtractBondDetailsV0 to
   423  // parse the P2SH bond output's redeem script.
   424  //
   425  // If the decoded commitment data indicates a version other than 0, an error is
   426  // returned.
   427  func ExtractBondCommitDataV0(scriptVer uint16, pkScript []byte) (acct account.AccountID, lockTime uint32, pubkeyHash [20]byte, err error) {
   428  	tokenizer := txscript.MakeScriptTokenizer(scriptVer, pkScript)
   429  	if !tokenizer.Next() {
   430  		err = tokenizer.Err()
   431  		return
   432  	}
   433  
   434  	if tokenizer.Opcode() != txscript.OP_RETURN {
   435  		err = errors.New("not a null data output")
   436  		return
   437  	}
   438  
   439  	if !tokenizer.Next() {
   440  		err = tokenizer.Err()
   441  		return
   442  	}
   443  
   444  	pushData := tokenizer.Data()
   445  	acct, lockTime, pubkeyHash, err = extractBondCommitDataV0(pushData)
   446  	if err != nil {
   447  		return
   448  	}
   449  
   450  	if !tokenizer.Done() {
   451  		err = errors.New("script has extra opcodes")
   452  		return
   453  	}
   454  
   455  	return
   456  }
   457  
   458  // MakeBondScript constructs a versioned bond output script for the provided
   459  // lock time and pubkey hash. Only version 0 is supported at present. The lock
   460  // time must be less than 2^32-1 so that it uses at most 5 bytes. The lockTime
   461  // is also required to use at least 4 bytes (time stamp, not block time).
   462  func MakeBondScript(ver uint16, lockTime uint32, pubkeyHash []byte) ([]byte, error) {
   463  	if ver != 0 {
   464  		return nil, errors.New("only version 0 bonds supported")
   465  	}
   466  	if lockTime >= MaxCLTVScriptNum { // == should be OK, but let's not
   467  		return nil, errors.New("invalid lock time")
   468  	}
   469  	lockTimeBytes := txscript.ScriptNum(lockTime).Bytes()
   470  	if n := len(lockTimeBytes); n < 4 || n > 5 {
   471  		return nil, errors.New("invalid lock time")
   472  	}
   473  	if len(pubkeyHash) != 20 {
   474  		return nil, errors.New("invalid pubkey hash")
   475  	}
   476  	return txscript.NewScriptBuilder().
   477  		AddData(lockTimeBytes).
   478  		AddOp(txscript.OP_CHECKLOCKTIMEVERIFY).
   479  		AddOp(txscript.OP_DROP).
   480  		AddOp(txscript.OP_DUP).
   481  		AddOp(txscript.OP_HASH160).
   482  		AddData(pubkeyHash).
   483  		AddOp(txscript.OP_EQUALVERIFY).
   484  		AddOp(txscript.OP_CHECKSIG).
   485  		Script()
   486  }
   487  
   488  // RefundBondScript builds the signature script to refund a time-locked fidelity
   489  // bond in a P2SH output paying to the provided P2PKH bondScript.
   490  func RefundBondScript(bondScript, sig, pubkey []byte) ([]byte, error) {
   491  	return txscript.NewScriptBuilder().
   492  		AddData(sig).
   493  		AddData(pubkey).
   494  		AddData(bondScript).
   495  		Script()
   496  }
   497  
   498  // ExtractBondDetailsV0 validates the provided bond redeem script, extracting
   499  // the lock time and pubkey. The V0 format of the script must be as follows:
   500  //
   501  //	<lockTime> OP_CHECKLOCKTIMEVERIFY OP_DROP OP_DUP OP_HASH160 <pubkeyhash[20]> OP_EQUALVERIFY OP_CHECKSIG
   502  //
   503  // The script version refers to the pkScript version, not bond version, which
   504  // pertains to DEX's version of the bond script.
   505  func ExtractBondDetailsV0(scriptVersion uint16, bondScript []byte) (lockTime uint32, pkh []byte, err error) {
   506  	type templateMatch struct {
   507  		expectInt     bool
   508  		maxIntBytes   int
   509  		opcode        byte
   510  		extractedInt  int64
   511  		extractedData []byte
   512  	}
   513  	var template = [...]templateMatch{
   514  		{expectInt: true, maxIntBytes: txscript.CltvMaxScriptNumLen}, // extractedInt
   515  		{opcode: txscript.OP_CHECKLOCKTIMEVERIFY},
   516  		{opcode: txscript.OP_DROP},
   517  		{opcode: txscript.OP_DUP},
   518  		{opcode: txscript.OP_HASH160},
   519  		{opcode: txscript.OP_DATA_20}, // extractedData
   520  		{opcode: txscript.OP_EQUALVERIFY},
   521  		{opcode: txscript.OP_CHECKSIG},
   522  	}
   523  
   524  	var templateOffset int
   525  	tokenizer := txscript.MakeScriptTokenizer(scriptVersion, bondScript)
   526  	for tokenizer.Next() {
   527  		if templateOffset >= len(template) {
   528  			return 0, nil, errors.New("too many script elements")
   529  		}
   530  
   531  		op, data := tokenizer.Opcode(), tokenizer.Data()
   532  		tplEntry := &template[templateOffset]
   533  		if tplEntry.expectInt {
   534  			switch {
   535  			case data != nil:
   536  				val, err := txscript.MakeScriptNum(data, tplEntry.maxIntBytes)
   537  				if err != nil {
   538  					return 0, nil, err
   539  				}
   540  				tplEntry.extractedInt = int64(val)
   541  
   542  			case txscript.IsSmallInt(op): // not expected for our lockTimes, but it is an integer
   543  				tplEntry.extractedInt = int64(txscript.AsSmallInt(op))
   544  
   545  			default:
   546  				return 0, nil, errors.New("expected integer")
   547  			}
   548  		} else {
   549  			if op != tplEntry.opcode {
   550  				return 0, nil, fmt.Errorf("expected opcode %v, got %v", tplEntry.opcode, op)
   551  			}
   552  
   553  			tplEntry.extractedData = data
   554  		}
   555  
   556  		templateOffset++
   557  	}
   558  	if err := tokenizer.Err(); err != nil {
   559  		return 0, nil, err
   560  	}
   561  	if !tokenizer.Done() || templateOffset != len(template) {
   562  		return 0, nil, errors.New("incorrect script length")
   563  	}
   564  
   565  	// The script matches in structure. Now validate the two pushes.
   566  
   567  	lockTime64 := template[0].extractedInt
   568  	if lockTime64 <= 0 || lockTime64 > MaxCLTVScriptNum {
   569  		return 0, nil, fmt.Errorf("invalid locktime %d", lockTime64)
   570  	}
   571  	lockTime = uint32(lockTime64)
   572  
   573  	const pubkeyHashLen = 20
   574  	bondPubKeyHash := template[5].extractedData
   575  	if len(bondPubKeyHash) != pubkeyHashLen {
   576  		err = errors.New("missing or invalid pubkeyhash data")
   577  		return
   578  	}
   579  	pkh = make([]byte, pubkeyHashLen)
   580  	copy(pkh, bondPubKeyHash)
   581  
   582  	return
   583  }
   584  
   585  // ExtractSwapDetails extacts the sender and receiver addresses from a swap
   586  // contract. If the provided script is not a swap contract, an error will be
   587  // returned.
   588  func ExtractSwapDetails(pkScript []byte, chainParams *chaincfg.Params) (
   589  	sender, receiver stdaddr.Address, lockTime uint64, secretHash []byte, err error) {
   590  	// A swap redemption sigScript is <pubkey> <secret> and satisfies the
   591  	// following swap contract, allowing only for a secret of size
   592  	//
   593  	// OP_IF
   594  	//  OP_SIZE OP_DATA_1 secretSize OP_EQUALVERIFY OP_SHA256 OP_DATA_32 secretHash OP_EQUALVERIFY OP_DUP OP_HASH160 OP_DATA20 pkHashReceiver
   595  	//     1   +   1     +    1     +      1       +    1    +    1     +    32    +      1       +   1  +    1     +    1    +    20
   596  	// OP_ELSE
   597  	//  OP_DATA4 lockTime OP_CHECKLOCKTIMEVERIFY OP_DROP OP_DUP OP_HASH160 OP_DATA_20 pkHashSender
   598  	//     1    +    4   +           1          +   1   +  1   +    1     +   1      +    20
   599  	// OP_ENDIF
   600  	// OP_EQUALVERIFY
   601  	// OP_CHECKSIG
   602  	//
   603  	// 5 bytes if-else-endif-equalverify-checksig
   604  	// 1 + 1 + 1 + 1 + 1 + 1 + 32 + 1 + 1 + 1 + 1 + 20 = 62 bytes for redeem block
   605  	// 1 + 4 + 1 + 1 + 1 + 1 + 1 + 20 = 30 bytes for refund block
   606  	// 5 + 62 + 30 = 97 bytes
   607  	//
   608  	// Note that this allows for a secret size of up to 75 bytes, but the secret
   609  	// must be 32 bytes to be considered valid.
   610  	if len(pkScript) != SwapContractSize {
   611  		err = fmt.Errorf("incorrect swap contract length. expected %d, got %d",
   612  			SwapContractSize, len(pkScript))
   613  		return
   614  	}
   615  
   616  	if pkScript[0] == txscript.OP_IF &&
   617  		pkScript[1] == txscript.OP_SIZE &&
   618  		pkScript[2] == txscript.OP_DATA_1 &&
   619  		// secretSize (1 bytes)
   620  		pkScript[4] == txscript.OP_EQUALVERIFY &&
   621  		pkScript[5] == txscript.OP_SHA256 &&
   622  		pkScript[6] == txscript.OP_DATA_32 &&
   623  		// secretHash (32 bytes)
   624  		pkScript[39] == txscript.OP_EQUALVERIFY &&
   625  		pkScript[40] == txscript.OP_DUP &&
   626  		pkScript[41] == txscript.OP_HASH160 &&
   627  		pkScript[42] == txscript.OP_DATA_20 &&
   628  		// receiver's pkh (20 bytes)
   629  		pkScript[63] == txscript.OP_ELSE &&
   630  		pkScript[64] == txscript.OP_DATA_4 &&
   631  		// lockTime (4 bytes)
   632  		pkScript[69] == txscript.OP_CHECKLOCKTIMEVERIFY &&
   633  		pkScript[70] == txscript.OP_DROP &&
   634  		pkScript[71] == txscript.OP_DUP &&
   635  		pkScript[72] == txscript.OP_HASH160 &&
   636  		pkScript[73] == txscript.OP_DATA_20 &&
   637  		// sender's pkh (20 bytes)
   638  		pkScript[94] == txscript.OP_ENDIF &&
   639  		pkScript[95] == txscript.OP_EQUALVERIFY &&
   640  		pkScript[96] == txscript.OP_CHECKSIG {
   641  
   642  		if ssz := pkScript[3]; ssz != SecretKeySize {
   643  			return nil, nil, 0, nil, fmt.Errorf("invalid secret size %d", ssz)
   644  		}
   645  
   646  		receiver, err = stdaddr.NewAddressPubKeyHashEcdsaSecp256k1V0(pkScript[43:63], chainParams)
   647  		if err != nil {
   648  			return nil, nil, 0, nil, fmt.Errorf("error decoding address from recipient's pubkey hash")
   649  		}
   650  
   651  		sender, err = stdaddr.NewAddressPubKeyHashEcdsaSecp256k1V0(pkScript[74:94], chainParams)
   652  		if err != nil {
   653  			return nil, nil, 0, nil, fmt.Errorf("error decoding address from sender's pubkey hash")
   654  		}
   655  
   656  		lockTime = uint64(binary.LittleEndian.Uint32(pkScript[65:69]))
   657  		secretHash = pkScript[7:39]
   658  
   659  		return
   660  	}
   661  
   662  	err = fmt.Errorf("invalid swap contract")
   663  	return
   664  }
   665  
   666  // IsDust returns whether or not the passed transaction output amount is
   667  // considered dust or not based on the passed minimum transaction relay fee.
   668  // Dust is defined in terms of the minimum transaction relay fee.
   669  // See dcrd/mempool/policy isDust for further documentation, though this version
   670  // accepts atoms/byte rather than atoms/kB.
   671  func IsDust(txOut *wire.TxOut, minRelayTxFee uint64) bool {
   672  	// Unspendable outputs are considered dust.
   673  	if txscript.IsUnspendable(txOut.Value, txOut.PkScript) {
   674  		return true
   675  	}
   676  	return IsDustVal(uint64(txOut.SerializeSize()), uint64(txOut.Value), minRelayTxFee)
   677  }
   678  
   679  // IsDustVal is like IsDust but it only needs the size of the serialized output
   680  // and its amount.
   681  func IsDustVal(txOutSize, amt, minRelayTxFee uint64) bool {
   682  	totalSize := txOutSize + 165
   683  	return amt/(3*totalSize) < minRelayTxFee
   684  }
   685  
   686  // ExtractScriptHash extracts the script hash from a P2SH pkScript of a
   687  // particular script version.
   688  func ExtractScriptHash(version uint16, script []byte) []byte {
   689  	switch version {
   690  	case 0:
   691  		return ExtractScriptHashV0(script)
   692  	}
   693  	return nil
   694  }
   695  
   696  // ExtractScriptHashV0 extracts the script hash from a P2SH pkScript. This is
   697  // valid only for version 0 scripts.
   698  func ExtractScriptHashV0(script []byte) []byte {
   699  	if h := stdscript.ExtractScriptHashV0(script); h != nil {
   700  		return h
   701  	}
   702  	return stdscript.ExtractStakeScriptHashV0(script)
   703  }
   704  
   705  // ExtractScriptData extracts script type, addresses, and required signature
   706  // count from a pkScript. Non-standard scripts are not an error; non-nil errors
   707  // are only returned if the script cannot be parsed. See also InputInfo for
   708  // additional signature script size data
   709  func ExtractScriptData(version uint16, script []byte, chainParams *chaincfg.Params) (ScriptType, []string, int) {
   710  	class, addrs := stdscript.ExtractAddrs(version, script, chainParams)
   711  	if class == stdscript.STNonStandard {
   712  		return ScriptUnsupported, nil, 0
   713  	}
   714  	numRequired := stdscript.DetermineRequiredSigs(version, script)
   715  	scriptType := convertScriptType(class)
   716  	addresses := make([]string, len(addrs))
   717  	for i, addr := range addrs {
   718  		addresses[i] = addr.String()
   719  	}
   720  
   721  	return scriptType, addresses, int(numRequired)
   722  }
   723  
   724  // ScriptAddrs is information about the pubkeys or pubkey hashes present in a
   725  // scriptPubKey (and the redeem script, for p2sh). This information can be used
   726  // to estimate the spend script size, e.g. pubkeys in a redeem script don't
   727  // require pubkeys in the scriptSig, but pubkey hashes do.
   728  type ScriptAddrs struct {
   729  	PubKeys   []stdaddr.Address
   730  	PkHashes  []stdaddr.Address
   731  	NRequired int
   732  }
   733  
   734  // ExtractScriptAddrs extracts the addresses from script. Addresses are
   735  // separated into pubkey and pubkey hash, where the pkh addresses are actually a
   736  // catch all for non-P2PK addresses. As such, this function is not intended for
   737  // use on P2SH pkScripts. Rather, the corresponding redeem script should be
   738  // processed with ExtractScriptAddrs. The returned bool indicates if the script
   739  // is non-standard.
   740  func ExtractScriptAddrs(version uint16, script []byte, chainParams *chaincfg.Params) (ScriptType, *ScriptAddrs) {
   741  	pubkeys := make([]stdaddr.Address, 0)
   742  	pkHashes := make([]stdaddr.Address, 0)
   743  	class, addrs := stdscript.ExtractAddrs(version, script, chainParams)
   744  	if class == stdscript.STNonStandard {
   745  		return ScriptUnsupported, &ScriptAddrs{}
   746  	}
   747  	for _, addr := range addrs {
   748  		// If the address is an unhashed public key, is won't need a pubkey as
   749  		// part of its sigScript, so count them separately.
   750  		_, isPubkey := addr.(stdaddr.SerializedPubKeyer)
   751  		if isPubkey {
   752  			pubkeys = append(pubkeys, addr)
   753  		} else {
   754  			pkHashes = append(pkHashes, addr)
   755  		}
   756  	}
   757  	numRequired := stdscript.DetermineRequiredSigs(version, script)
   758  	scriptType := convertScriptType(class)
   759  	return scriptType, &ScriptAddrs{
   760  		PubKeys:   pubkeys,
   761  		PkHashes:  pkHashes,
   762  		NRequired: int(numRequired),
   763  	}
   764  }
   765  
   766  // SpendInfo is information about an input and it's previous outpoint.
   767  type SpendInfo struct {
   768  	SigScriptSize     uint32
   769  	ScriptAddrs       *ScriptAddrs
   770  	ScriptType        ScriptType
   771  	NonStandardScript bool // refers to redeem script for P2SH ScriptType
   772  }
   773  
   774  // Size is the serialized size of the input.
   775  func (nfo *SpendInfo) Size() uint32 {
   776  	return TxInOverhead + uint32(wire.VarIntSerializeSize(uint64(nfo.SigScriptSize))) + nfo.SigScriptSize
   777  }
   778  
   779  // DataPrefixSize returns the size of the size opcodes that would precede the
   780  // data in a script. Certain data of length 0 and 1 is represented with OP_# or
   781  // OP_1NEGATE codes directly without preceding OP_DATA_# OR OP_PUSHDATA# codes.
   782  func DataPrefixSize(data []byte) uint8 {
   783  	serSz := txscript.CanonicalDataSize(data)
   784  	sz := len(data)
   785  	prefixSize := serSz - sz
   786  	if sz == 0 {
   787  		prefixSize-- // empty array uses a byte
   788  	}
   789  	return uint8(prefixSize)
   790  }
   791  
   792  // InputInfo is some basic information about the input required to spend an
   793  // output. Only P2PKH and P2SH pkScripts are supported. If the pubkey script
   794  // parses as P2SH, the redeem script must be provided.
   795  func InputInfo(version uint16, pkScript, redeemScript []byte, chainParams *chaincfg.Params) (*SpendInfo, error) {
   796  	scriptType, scriptAddrs := ExtractScriptAddrs(version, pkScript, chainParams)
   797  	switch {
   798  	case scriptType == ScriptUnsupported:
   799  		return nil, dex.UnsupportedScriptError
   800  	case scriptType.IsP2PK():
   801  		return &SpendInfo{
   802  			SigScriptSize: P2PKSigScriptSize,
   803  			ScriptAddrs:   scriptAddrs,
   804  			ScriptType:    scriptType,
   805  		}, nil
   806  	case scriptType.IsP2PKH():
   807  		return &SpendInfo{
   808  			SigScriptSize: P2PKHSigScriptSize,
   809  			ScriptAddrs:   scriptAddrs,
   810  			ScriptType:    scriptType,
   811  		}, nil
   812  	case scriptType.IsP2SH():
   813  	default:
   814  		return nil, fmt.Errorf("unsupported pkScript: %d", scriptType) // dex.UnsupportedScriptError too?
   815  	}
   816  
   817  	// P2SH
   818  	if len(redeemScript) == 0 {
   819  		return nil, fmt.Errorf("no redeem script provided for P2SH pubkey script")
   820  	}
   821  
   822  	var redeemScriptType ScriptType
   823  	redeemScriptType, scriptAddrs = ExtractScriptAddrs(version, redeemScript, chainParams)
   824  	if redeemScriptType == ScriptUnsupported {
   825  		return &SpendInfo{
   826  			// SigScriptSize cannot be determined, leave zero.
   827  			ScriptAddrs:       scriptAddrs,
   828  			ScriptType:        scriptType, // still P2SH
   829  			NonStandardScript: true,       // but non-standard redeem script (e.g. contract)
   830  		}, nil
   831  	}
   832  
   833  	// NOTE: scriptAddrs are now from the redeemScript, and we add the multisig
   834  	// bit to scriptType.
   835  	if redeemScriptType.IsMultiSig() {
   836  		scriptType |= ScriptMultiSig
   837  	}
   838  
   839  	// If it's a P2SH, with a standard redeem script, compute the sigScript size
   840  	// for the expected number of signatures, pubkeys, and the redeem script
   841  	// itself. Start with the signatures.
   842  	sigScriptSize := (1 + DERSigLength) * scriptAddrs.NRequired // 73 max for sig, 1 for push code
   843  	// If there are pubkey-hash addresses, they'll need pubkeys.
   844  	if len(scriptAddrs.PkHashes) > 0 {
   845  		sigScriptSize += scriptAddrs.NRequired * (pubkeyLength + 1)
   846  	}
   847  	// Then add the length of the script and the push opcode byte(s).
   848  	sigScriptSize += len(redeemScript) + int(DataPrefixSize(redeemScript)) // push data length op code might be >1 byte
   849  	return &SpendInfo{
   850  		SigScriptSize: uint32(sigScriptSize),
   851  		ScriptAddrs:   scriptAddrs,
   852  		ScriptType:    scriptType,
   853  	}, nil
   854  }
   855  
   856  // IsRefundScript checks if the signature script is of the expected format for
   857  // the standard swap contract refund. The signature and pubkey data pushes are
   858  // not validated other than ensuring they are data pushes. The provided contract
   859  // must correspond to the final data push in the sigScript, but it is otherwise
   860  // not validated either.
   861  func IsRefundScript(scriptVersion uint16, sigScript, contract []byte) bool {
   862  	tokenizer := txscript.MakeScriptTokenizer(scriptVersion, sigScript)
   863  	// sig
   864  	if !tokenizer.Next() {
   865  		return false
   866  	}
   867  	if tokenizer.Data() == nil {
   868  		return false // should be a der signature
   869  	}
   870  
   871  	// pubkey
   872  	if !tokenizer.Next() {
   873  		return false
   874  	}
   875  	if tokenizer.Data() == nil {
   876  		return false // should be a pubkey
   877  	}
   878  
   879  	// OP_0
   880  	if !tokenizer.Next() {
   881  		return false
   882  	}
   883  	if tokenizer.Opcode() != txscript.OP_0 {
   884  		return false
   885  	}
   886  
   887  	// contract script
   888  	if !tokenizer.Next() {
   889  		return false
   890  	}
   891  	push := tokenizer.Data()
   892  	if len(push) != SwapContractSize {
   893  		return false
   894  	}
   895  	if !bytes.Equal(push, contract) {
   896  		return false
   897  	}
   898  
   899  	return tokenizer.Done()
   900  }
   901  
   902  // FindKeyPush attempts to extract the secret key from the signature script. The
   903  // contract must be provided for the search algorithm to verify the correct data
   904  // push. Only contracts of length SwapContractSize that can be validated by
   905  // ExtractSwapDetails are recognized.
   906  func FindKeyPush(scriptVersion uint16, sigScript, contractHash []byte, chainParams *chaincfg.Params) ([]byte, error) {
   907  	tokenizer := txscript.MakeScriptTokenizer(scriptVersion, sigScript)
   908  
   909  	// The contract is pushed after the key, find the contract starting with the
   910  	// first data push and record all data pushes encountered before the contract
   911  	// push. One of those preceding pushes should be the key push.
   912  	var dataPushesUpTillContract [][]byte
   913  	var keyHash []byte
   914  	var err error
   915  	for tokenizer.Next() {
   916  		push := tokenizer.Data()
   917  
   918  		// Only hash if ExtractSwapDetails will recognize it.
   919  		if len(push) == SwapContractSize {
   920  			h := dcrutil.Hash160(push)
   921  			if bytes.Equal(h, contractHash) {
   922  				_, _, _, keyHash, err = ExtractSwapDetails(push, chainParams)
   923  				if err != nil {
   924  					return nil, fmt.Errorf("error extracting atomic swap details: %w", err)
   925  				}
   926  				break // contract is pushed after the key, if we've encountered the contract, we must have just passed the key
   927  			}
   928  		}
   929  
   930  		// Save this push as preceding the contract push.
   931  		if push != nil {
   932  			dataPushesUpTillContract = append(dataPushesUpTillContract, push)
   933  		}
   934  	}
   935  	if tokenizer.Err() != nil {
   936  		return nil, tokenizer.Err()
   937  	}
   938  
   939  	if len(keyHash) > 0 {
   940  		// The key push should be the data push immediately preceding the contract
   941  		// push, but iterate through all of the preceding pushes backwards to ensure
   942  		// it is not hidden behind some non-standard script.
   943  		for i := len(dataPushesUpTillContract) - 1; i >= 0; i-- {
   944  			push := dataPushesUpTillContract[i]
   945  
   946  			// We have the key hash from the contract. See if this is the key.
   947  			h := sha256.Sum256(push)
   948  			if bytes.Equal(h[:], keyHash) {
   949  				return push, nil
   950  			}
   951  		}
   952  	}
   953  
   954  	return nil, fmt.Errorf("key not found")
   955  }