diff --git a/txscript/script.go b/txscript/script.go index 22ec65dea..e611579b3 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -13,7 +13,6 @@ import ( "time" "github.com/btcsuite/btcd/btcec" - "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" ) @@ -770,402 +769,6 @@ func MultiSigScript(pubkeys []*btcutil.AddressPubKey, nrequired int) ([]byte, er return builder.Script() } -// SignatureScript creates an input signature script for tx to spend -// BTC sent from a previous output to the owner of privKey. tx must -// include all transaction inputs and outputs, however txin scripts are -// allowed to be filled or empty. The returned script is calculated to -// be used as the idx'th txin sigscript for tx. subscript is the PkScript -// of the previous output being used as the idx'th input. privKey is -// serialized in either a compressed or uncompressed format based on -// compress. This format must match the same format used to generate -// the payment address, or the script validation will fail. -func SignatureScript(tx *wire.MsgTx, idx int, subscript []byte, hashType SigHashType, privKey *btcec.PrivateKey, compress bool) ([]byte, error) { - sig, err := RawTxInSignature(tx, idx, subscript, hashType, privKey) - if err != nil { - return nil, err - } - - pk := (*btcec.PublicKey)(&privKey.PublicKey) - var pkData []byte - if compress { - pkData = pk.SerializeCompressed() - } else { - pkData = pk.SerializeUncompressed() - } - - return NewScriptBuilder().AddData(sig).AddData(pkData).Script() -} - -// RawTxInSignature returns the serialized ECDSA signature for the input -// idx of the given transaction, with hashType appended to it. -func RawTxInSignature(tx *wire.MsgTx, idx int, subScript []byte, - hashType SigHashType, key *btcec.PrivateKey) ([]byte, error) { - parsedScript, err := parseScript(subScript) - if err != nil { - return nil, fmt.Errorf("cannot parse output script: %v", err) - } - hash := calcScriptHash(parsedScript, hashType, tx, idx) - signature, err := key.Sign(hash) - if err != nil { - return nil, fmt.Errorf("cannot sign tx input: %s", err) - } - - return append(signature.Serialize(), byte(hashType)), nil -} - -func p2pkSignatureScript(tx *wire.MsgTx, idx int, subScript []byte, hashType SigHashType, privKey *btcec.PrivateKey) ([]byte, error) { - sig, err := RawTxInSignature(tx, idx, subScript, hashType, privKey) - if err != nil { - return nil, err - } - - return NewScriptBuilder().AddData(sig).Script() -} - -// signMultiSig signs as many of the outputs in the provided multisig script as -// possible. It returns the generated script and a boolean if the script fulfils -// the contract (i.e. nrequired signatures are provided). Since it is arguably -// legal to not be able to sign any of the outputs, no error is returned. -func signMultiSig(tx *wire.MsgTx, idx int, subScript []byte, hashType SigHashType, - addresses []btcutil.Address, nRequired int, kdb KeyDB) ([]byte, bool) { - // We start with a single OP_FALSE to work around the (now standard) - // but in the reference implementation that causes a spurious pop at - // the end of OP_CHECKMULTISIG. - builder := NewScriptBuilder().AddOp(OP_FALSE) - signed := 0 - for _, addr := range addresses { - key, _, err := kdb.GetKey(addr) - if err != nil { - continue - } - sig, err := RawTxInSignature(tx, idx, subScript, hashType, key) - if err != nil { - continue - } - - builder.AddData(sig) - signed++ - if signed == nRequired { - break - } - - } - - script, _ := builder.Script() - return script, signed == nRequired -} - -func sign(chainParams *chaincfg.Params, tx *wire.MsgTx, idx int, - subScript []byte, hashType SigHashType, kdb KeyDB, sdb ScriptDB) ([]byte, - ScriptClass, []btcutil.Address, int, error) { - - class, addresses, nrequired, err := ExtractPkScriptAddrs(subScript, - chainParams) - if err != nil { - return nil, NonStandardTy, nil, 0, err - } - - switch class { - case PubKeyTy: - // look up key for address - key, _, err := kdb.GetKey(addresses[0]) - if err != nil { - return nil, class, nil, 0, err - } - - script, err := p2pkSignatureScript(tx, idx, subScript, hashType, - key) - if err != nil { - return nil, class, nil, 0, err - } - - return script, class, addresses, nrequired, nil - case PubKeyHashTy: - // look up key for address - key, compressed, err := kdb.GetKey(addresses[0]) - if err != nil { - return nil, class, nil, 0, err - } - - script, err := SignatureScript(tx, idx, subScript, hashType, - key, compressed) - if err != nil { - return nil, class, nil, 0, err - } - - return script, class, addresses, nrequired, nil - case ScriptHashTy: - script, err := sdb.GetScript(addresses[0]) - if err != nil { - return nil, class, nil, 0, err - } - - return script, class, addresses, nrequired, nil - case MultiSigTy: - script, _ := signMultiSig(tx, idx, subScript, hashType, - addresses, nrequired, kdb) - return script, class, addresses, nrequired, nil - case NullDataTy: - return nil, class, nil, 0, - errors.New("can't sign NULLDATA transactions") - default: - return nil, class, nil, 0, - errors.New("can't sign unknown transactions") - } -} - -// mergeScripts merges sigScript and prevScript assuming they are both -// partial solutions for pkScript spending output idx of tx. class, addresses -// and nrequired are the result of extracting the addresses from pkscript. -// The return value is the best effort merging of the two scripts. Calling this -// function with addresses, class and nrequired that do not match pkScript is -// an error and results in undefined behaviour. -func mergeScripts(chainParams *chaincfg.Params, tx *wire.MsgTx, idx int, - pkScript []byte, class ScriptClass, addresses []btcutil.Address, - nRequired int, sigScript, prevScript []byte) []byte { - - // TODO(oga) the scripthash and multisig paths here are overly - // inefficient in that they will recompute already known data. - // some internal refactoring could probably make this avoid needless - // extra calculations. - switch class { - case ScriptHashTy: - // Remove the last push in the script and then recurse. - // this could be a lot less inefficient. - sigPops, err := parseScript(sigScript) - if err != nil || len(sigPops) == 0 { - return prevScript - } - prevPops, err := parseScript(prevScript) - if err != nil || len(prevPops) == 0 { - return sigScript - } - - // assume that script in sigPops is the correct one, we just - // made it. - script := sigPops[len(sigPops)-1].data - - // We already know this information somewhere up the stack. - class, addresses, nrequired, err := - ExtractPkScriptAddrs(script, chainParams) - - // regenerate scripts. - sigScript, _ := unparseScript(sigPops) - prevScript, _ := unparseScript(prevPops) - - // Merge - mergedScript := mergeScripts(chainParams, tx, idx, script, - class, addresses, nrequired, sigScript, prevScript) - - // Reappend the script and return the result. - builder := NewScriptBuilder() - builder.script = mergedScript - builder.AddData(script) - finalScript, _ := builder.Script() - return finalScript - case MultiSigTy: - return mergeMultiSig(tx, idx, addresses, nRequired, pkScript, - sigScript, prevScript) - - // It doesn't actualy make sense to merge anything other than multiig - // and scripthash (because it could contain multisig). Everything else - // has either zero signature, can't be spent, or has a single signature - // which is either present or not. The other two cases are handled - // above. In the conflict case here we just assume the longest is - // correct (this matches behaviour of the reference implementation). - default: - if len(sigScript) > len(prevScript) { - return sigScript - } - return prevScript - } -} - -// mergeMultiSig combines the two signature scripts sigScript and prevScript -// that both provide signatures for pkScript in output idx of tx. addresses -// and nRequired should be the results from extracting the addresses from -// pkScript. Since this function is internal only we assume that the arguments -// have come from other functions internally and thus are all consistent with -// each other, behaviour is undefined if this contract is broken. -func mergeMultiSig(tx *wire.MsgTx, idx int, addresses []btcutil.Address, - nRequired int, pkScript, sigScript, prevScript []byte) []byte { - - // This is an internal only function and we already parsed this script - // as ok for multisig (this is how we got here), so if this fails then - // all assumptions are broken and who knows which way is up? - pkPops, _ := parseScript(pkScript) - - sigPops, err := parseScript(sigScript) - if err != nil || len(sigPops) == 0 { - return prevScript - } - - prevPops, err := parseScript(prevScript) - if err != nil || len(prevPops) == 0 { - return sigScript - } - - // Convenience function to avoid duplication. - extractSigs := func(pops []parsedOpcode, sigs [][]byte) [][]byte { - for _, pop := range pops { - if len(pop.data) != 0 { - sigs = append(sigs, pop.data) - } - } - return sigs - } - - possibleSigs := make([][]byte, 0, len(sigPops)+len(prevPops)) - possibleSigs = extractSigs(sigPops, possibleSigs) - possibleSigs = extractSigs(prevPops, possibleSigs) - - // Now we need to match the signatures to pubkeys, the only real way to - // do that is to try to verify them all and match it to the pubkey - // that verifies it. we then can go through the addresses in order - // to build our script. Anything that doesn't parse or doesn't verify we - // throw away. - addrToSig := make(map[string][]byte) -sigLoop: - for _, sig := range possibleSigs { - - // can't have a valid signature that doesn't at least have a - // hashtype, in practise it is even longer than this. but - // that'll be checked next. - if len(sig) < 1 { - continue - } - tSig := sig[:len(sig)-1] - hashType := SigHashType(sig[len(sig)-1]) - - pSig, err := btcec.ParseDERSignature(tSig, btcec.S256()) - if err != nil { - continue - } - - // We have to do this each round since hash types may vary - // between signatures and so the hash will vary. We can, - // however, assume no sigs etc are in the script since that - // would make the transaction nonstandard and thus not - // MultiSigTy, so we just need to hash the full thing. - hash := calcScriptHash(pkPops, hashType, tx, idx) - - for _, addr := range addresses { - // All multisig addresses should be pubkey addreses - // it is an error to call this internal function with - // bad input. - pkaddr := addr.(*btcutil.AddressPubKey) - - pubKey := pkaddr.PubKey() - - // If it matches we put it in the map. We only - // can take one signature per public key so if we - // already have one, we can throw this away. - if pSig.Verify(hash, pubKey) { - aStr := addr.EncodeAddress() - if _, ok := addrToSig[aStr]; !ok { - addrToSig[aStr] = sig - } - continue sigLoop - } - } - } - - // Extra opcode to handle the extra arg consumed (due to previous bugs - // in the reference implementation). - builder := NewScriptBuilder().AddOp(OP_FALSE) - doneSigs := 0 - // This assumes that addresses are in the same order as in the script. - for _, addr := range addresses { - sig, ok := addrToSig[addr.EncodeAddress()] - if !ok { - continue - } - builder.AddData(sig) - doneSigs++ - if doneSigs == nRequired { - break - } - } - - // padding for missing ones. - for i := doneSigs; i < nRequired; i++ { - builder.AddOp(OP_0) - } - - script, _ := builder.Script() - return script -} - -// KeyDB is an interface type provided to SignTxOutput, it encapsulates -// any user state required to get the private keys for an address. -type KeyDB interface { - GetKey(btcutil.Address) (*btcec.PrivateKey, bool, error) -} - -// KeyClosure implements ScriptDB with a closure -type KeyClosure func(btcutil.Address) (*btcec.PrivateKey, bool, error) - -// GetKey implements KeyDB by returning the result of calling the closure -func (kc KeyClosure) GetKey(address btcutil.Address) (*btcec.PrivateKey, - bool, error) { - return kc(address) -} - -// ScriptDB is an interface type provided to SignTxOutput, it encapsulates -// any user state required to get the scripts for an pay-to-script-hash address. -type ScriptDB interface { - GetScript(btcutil.Address) ([]byte, error) -} - -// ScriptClosure implements ScriptDB with a closure -type ScriptClosure func(btcutil.Address) ([]byte, error) - -// GetScript implements ScriptDB by returning the result of calling the closure -func (sc ScriptClosure) GetScript(address btcutil.Address) ([]byte, error) { - return sc(address) -} - -// SignTxOutput signs output idx of the given tx to resolve the script given in -// pkScript with a signature type of hashType. Any keys required will be -// looked up by calling getKey() with the string of the given address. -// Any pay-to-script-hash signatures will be similarly looked up by calling -// getScript. If previousScript is provided then the results in previousScript -// will be merged in a type-dependant manner with the newly generated. -// signature script. -func SignTxOutput(chainParams *chaincfg.Params, tx *wire.MsgTx, idx int, - pkScript []byte, hashType SigHashType, kdb KeyDB, sdb ScriptDB, - previousScript []byte) ([]byte, error) { - - sigScript, class, addresses, nrequired, err := sign(chainParams, tx, - idx, pkScript, hashType, kdb, sdb) - if err != nil { - return nil, err - } - - if class == ScriptHashTy { - // TODO keep the sub addressed and pass down to merge. - realSigScript, _, _, _, err := sign(chainParams, tx, idx, - sigScript, hashType, kdb, sdb) - if err != nil { - return nil, err - } - - // This is a bad thing. Append the p2sh script as the last - // push in the script. - builder := NewScriptBuilder() - builder.script = realSigScript - builder.AddData(sigScript) - - sigScript, _ = builder.Script() - // TODO keep a copy of the script for merging. - } - - // Merge scripts. with any previous data, if any. - mergedScript := mergeScripts(chainParams, tx, idx, pkScript, class, - addresses, nrequired, sigScript, previousScript) - return mergedScript, nil -} - // expectedInputs returns the number of arguments required by a script. // If the script is of unnown type such that the number can not be determined // then -1 is returned. We are an internal function and thus assume that class diff --git a/txscript/script_test.go b/txscript/script_test.go index 9bc60ad19..af290679e 100644 --- a/txscript/script_test.go +++ b/txscript/script_test.go @@ -6,11 +6,8 @@ package txscript_test import ( "bytes" - "errors" - "fmt" "testing" - "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" @@ -2418,339 +2415,6 @@ func TestIsPayToScriptHash(t *testing.T) { } } -type TstSigScript struct { - name string - inputs []TstInput - hashType txscript.SigHashType - compress bool - scriptAtWrongIndex bool -} - -type TstInput struct { - txout *wire.TxOut - sigscriptGenerates bool - inputValidates bool - indexOutOfRange bool -} - -var coinbaseOutPoint = &wire.OutPoint{ - Index: (1 << 32) - 1, -} - -// Pregenerated private key, with associated public key and pkScripts -// for the uncompressed and compressed hash160. -var ( - privKeyD = []byte{0x6b, 0x0f, 0xd8, 0xda, 0x54, 0x22, 0xd0, 0xb7, - 0xb4, 0xfc, 0x4e, 0x55, 0xd4, 0x88, 0x42, 0xb3, 0xa1, 0x65, - 0xac, 0x70, 0x7f, 0x3d, 0xa4, 0x39, 0x5e, 0xcb, 0x3b, 0xb0, - 0xd6, 0x0e, 0x06, 0x92} - pubkeyX = []byte{0xb2, 0x52, 0xf0, 0x49, 0x85, 0x78, 0x03, 0x03, 0xc8, - 0x7d, 0xce, 0x51, 0x7f, 0xa8, 0x69, 0x0b, 0x91, 0x95, 0xf4, - 0xf3, 0x5c, 0x26, 0x73, 0x05, 0x05, 0xa2, 0xee, 0xbc, 0x09, - 0x38, 0x34, 0x3a} - pubkeyY = []byte{0xb7, 0xc6, 0x7d, 0xb2, 0xe1, 0xff, 0xc8, 0x43, 0x1f, - 0x63, 0x32, 0x62, 0xaa, 0x60, 0xc6, 0x83, 0x30, 0xbd, 0x24, - 0x7e, 0xef, 0xdb, 0x6f, 0x2e, 0x8d, 0x56, 0xf0, 0x3c, 0x9f, - 0x6d, 0xb6, 0xf8} - uncompressedPkScript = []byte{0x76, 0xa9, 0x14, 0xd1, 0x7c, 0xb5, - 0xeb, 0xa4, 0x02, 0xcb, 0x68, 0xe0, 0x69, 0x56, 0xbf, 0x32, - 0x53, 0x90, 0x0e, 0x0a, 0x86, 0xc9, 0xfa, 0x88, 0xac} - compressedPkScript = []byte{0x76, 0xa9, 0x14, 0x27, 0x4d, 0x9f, 0x7f, - 0x61, 0x7e, 0x7c, 0x7a, 0x1c, 0x1f, 0xb2, 0x75, 0x79, 0x10, - 0x43, 0x65, 0x68, 0x27, 0x9d, 0x86, 0x88, 0xac} - shortPkScript = []byte{0x76, 0xa9, 0x14, 0xd1, 0x7c, 0xb5, - 0xeb, 0xa4, 0x02, 0xcb, 0x68, 0xe0, 0x69, 0x56, 0xbf, 0x32, - 0x53, 0x90, 0x0e, 0x0a, 0x88, 0xac} - uncompressedAddrStr = "1L6fd93zGmtzkK6CsZFVVoCwzZV3MUtJ4F" - compressedAddrStr = "14apLppt9zTq6cNw8SDfiJhk9PhkZrQtYZ" -) - -// Pretend output amounts. -const coinbaseVal = 2500000000 -const fee = 5000000 - -var SigScriptTests = []TstSigScript{ - { - name: "one input uncompressed", - inputs: []TstInput{ - { - txout: wire.NewTxOut(coinbaseVal, uncompressedPkScript), - sigscriptGenerates: true, - inputValidates: true, - indexOutOfRange: false, - }, - }, - hashType: txscript.SigHashAll, - compress: false, - scriptAtWrongIndex: false, - }, - { - name: "two inputs uncompressed", - inputs: []TstInput{ - { - txout: wire.NewTxOut(coinbaseVal, uncompressedPkScript), - sigscriptGenerates: true, - inputValidates: true, - indexOutOfRange: false, - }, - { - txout: wire.NewTxOut(coinbaseVal+fee, uncompressedPkScript), - sigscriptGenerates: true, - inputValidates: true, - indexOutOfRange: false, - }, - }, - hashType: txscript.SigHashAll, - compress: false, - scriptAtWrongIndex: false, - }, - { - name: "one input compressed", - inputs: []TstInput{ - { - txout: wire.NewTxOut(coinbaseVal, compressedPkScript), - sigscriptGenerates: true, - inputValidates: true, - indexOutOfRange: false, - }, - }, - hashType: txscript.SigHashAll, - compress: true, - scriptAtWrongIndex: false, - }, - { - name: "two inputs compressed", - inputs: []TstInput{ - { - txout: wire.NewTxOut(coinbaseVal, compressedPkScript), - sigscriptGenerates: true, - inputValidates: true, - indexOutOfRange: false, - }, - { - txout: wire.NewTxOut(coinbaseVal+fee, compressedPkScript), - sigscriptGenerates: true, - inputValidates: true, - indexOutOfRange: false, - }, - }, - hashType: txscript.SigHashAll, - compress: true, - scriptAtWrongIndex: false, - }, - { - name: "hashType SigHashNone", - inputs: []TstInput{ - { - txout: wire.NewTxOut(coinbaseVal, uncompressedPkScript), - sigscriptGenerates: true, - inputValidates: true, - indexOutOfRange: false, - }, - }, - hashType: txscript.SigHashNone, - compress: false, - scriptAtWrongIndex: false, - }, - { - name: "hashType SigHashSingle", - inputs: []TstInput{ - { - txout: wire.NewTxOut(coinbaseVal, uncompressedPkScript), - sigscriptGenerates: true, - inputValidates: true, - indexOutOfRange: false, - }, - }, - hashType: txscript.SigHashSingle, - compress: false, - scriptAtWrongIndex: false, - }, - { - name: "hashType SigHashAnyoneCanPay", - inputs: []TstInput{ - { - txout: wire.NewTxOut(coinbaseVal, uncompressedPkScript), - sigscriptGenerates: true, - inputValidates: true, - indexOutOfRange: false, - }, - }, - hashType: txscript.SigHashAnyOneCanPay, - compress: false, - scriptAtWrongIndex: false, - }, - { - name: "hashType non-standard", - inputs: []TstInput{ - { - txout: wire.NewTxOut(coinbaseVal, uncompressedPkScript), - sigscriptGenerates: true, - inputValidates: true, - indexOutOfRange: false, - }, - }, - hashType: 0x04, - compress: false, - scriptAtWrongIndex: false, - }, - { - name: "invalid compression", - inputs: []TstInput{ - { - txout: wire.NewTxOut(coinbaseVal, uncompressedPkScript), - sigscriptGenerates: true, - inputValidates: false, - indexOutOfRange: false, - }, - }, - hashType: txscript.SigHashAll, - compress: true, - scriptAtWrongIndex: false, - }, - { - name: "short PkScript", - inputs: []TstInput{ - { - txout: wire.NewTxOut(coinbaseVal, shortPkScript), - sigscriptGenerates: false, - indexOutOfRange: false, - }, - }, - hashType: txscript.SigHashAll, - compress: false, - scriptAtWrongIndex: false, - }, - { - name: "valid script at wrong index", - inputs: []TstInput{ - { - txout: wire.NewTxOut(coinbaseVal, uncompressedPkScript), - sigscriptGenerates: true, - inputValidates: true, - indexOutOfRange: false, - }, - { - txout: wire.NewTxOut(coinbaseVal+fee, uncompressedPkScript), - sigscriptGenerates: true, - inputValidates: true, - indexOutOfRange: false, - }, - }, - hashType: txscript.SigHashAll, - compress: false, - scriptAtWrongIndex: true, - }, - { - name: "index out of range", - inputs: []TstInput{ - { - txout: wire.NewTxOut(coinbaseVal, uncompressedPkScript), - sigscriptGenerates: true, - inputValidates: true, - indexOutOfRange: false, - }, - { - txout: wire.NewTxOut(coinbaseVal+fee, uncompressedPkScript), - sigscriptGenerates: true, - inputValidates: true, - indexOutOfRange: false, - }, - }, - hashType: txscript.SigHashAll, - compress: false, - scriptAtWrongIndex: true, - }, -} - -// Test the sigscript generation for valid and invalid inputs, all -// hashTypes, and with and without compression. This test creates -// sigscripts to spend fake coinbase inputs, as sigscripts cannot be -// created for the MsgTxs in txTests, since they come from the blockchain -// and we don't have the private keys. -func TestSignatureScript(t *testing.T) { - t.Parallel() - - privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), privKeyD) - -nexttest: - for i := range SigScriptTests { - tx := wire.NewMsgTx() - - output := wire.NewTxOut(500, []byte{txscript.OP_RETURN}) - tx.AddTxOut(output) - - for _ = range SigScriptTests[i].inputs { - txin := wire.NewTxIn(coinbaseOutPoint, nil) - tx.AddTxIn(txin) - } - - var script []byte - var err error - for j := range tx.TxIn { - var idx int - if SigScriptTests[i].inputs[j].indexOutOfRange { - t.Errorf("at test %v", SigScriptTests[i].name) - idx = len(SigScriptTests[i].inputs) - } else { - idx = j - } - script, err = txscript.SignatureScript(tx, idx, - SigScriptTests[i].inputs[j].txout.PkScript, - SigScriptTests[i].hashType, privKey, - SigScriptTests[i].compress) - - if (err == nil) != SigScriptTests[i].inputs[j].sigscriptGenerates { - if err == nil { - t.Errorf("passed test '%v' incorrectly", - SigScriptTests[i].name) - } else { - t.Errorf("failed test '%v': %v", - SigScriptTests[i].name, err) - } - continue nexttest - } - if !SigScriptTests[i].inputs[j].sigscriptGenerates { - // done with this test - continue nexttest - } - - tx.TxIn[j].SignatureScript = script - } - - // If testing using a correct sigscript but for an incorrect - // index, use last input script for first input. Requires > 0 - // inputs for test. - if SigScriptTests[i].scriptAtWrongIndex { - tx.TxIn[0].SignatureScript = script - SigScriptTests[i].inputs[0].inputValidates = false - } - - // Validate tx input scripts - scriptFlags := txscript.ScriptBip16 | txscript.ScriptVerifyDERSignatures - for j := range tx.TxIn { - vm, err := txscript.NewEngine(SigScriptTests[i]. - inputs[j].txout.PkScript, tx, j, scriptFlags) - if err != nil { - t.Errorf("cannot create script vm for test %v: %v", - SigScriptTests[i].name, err) - continue nexttest - } - err = vm.Execute() - if (err == nil) != SigScriptTests[i].inputs[j].inputValidates { - if err == nil { - t.Errorf("passed test '%v' validation incorrectly: %v", - SigScriptTests[i].name, err) - } else { - t.Errorf("failed test '%v' validation: %v", - SigScriptTests[i].name, err) - } - continue nexttest - } - } - } -} - func TestStringifyClass(t *testing.T) { t.Parallel() @@ -3138,1370 +2802,6 @@ func TestMultiSigScript(t *testing.T) { } } -func signAndCheck(msg string, tx *wire.MsgTx, idx int, pkScript []byte, - hashType txscript.SigHashType, kdb txscript.KeyDB, sdb txscript.ScriptDB, - previousScript []byte) error { - - sigScript, err := txscript.SignTxOutput(&chaincfg.TestNet3Params, tx, - idx, pkScript, hashType, kdb, sdb, []byte{}) - if err != nil { - return fmt.Errorf("failed to sign output %s: %v", msg, err) - } - - return checkScripts(msg, tx, idx, sigScript, pkScript) -} - -func checkScripts(msg string, tx *wire.MsgTx, idx int, sigScript, pkScript []byte) error { - tx.TxIn[idx].SignatureScript = sigScript - vm, err := txscript.NewEngine(pkScript, tx, idx, - txscript.ScriptBip16|txscript.ScriptVerifyDERSignatures) - if err != nil { - return fmt.Errorf("failed to make script engine for %s: %v", - msg, err) - } - - err = vm.Execute() - if err != nil { - return fmt.Errorf("invalid script signature for %s: %v", msg, - err) - } - - return nil -} - -type addressToKey struct { - key *btcec.PrivateKey - compressed bool -} - -func mkGetKey(keys map[string]addressToKey) txscript.KeyDB { - if keys == nil { - return txscript.KeyClosure(func(addr btcutil.Address) (*btcec.PrivateKey, - bool, error) { - return nil, false, errors.New("nope") - }) - } - return txscript.KeyClosure(func(addr btcutil.Address) (*btcec.PrivateKey, - bool, error) { - a2k, ok := keys[addr.EncodeAddress()] - if !ok { - return nil, false, errors.New("nope") - } - return a2k.key, a2k.compressed, nil - }) -} - -func mkGetScript(scripts map[string][]byte) txscript.ScriptDB { - if scripts == nil { - return txscript.ScriptClosure(func(addr btcutil.Address) ( - []byte, error) { - return nil, errors.New("nope") - }) - } - return txscript.ScriptClosure(func(addr btcutil.Address) ([]byte, - error) { - script, ok := scripts[addr.EncodeAddress()] - if !ok { - return nil, errors.New("nope") - } - return script, nil - }) -} - -func TestSignTxOutput(t *testing.T) { - t.Parallel() - - // make key - // make script based on key. - // sign with magic pixie dust. - hashTypes := []txscript.SigHashType{ - txscript.SigHashOld, // no longer used but should act like all - txscript.SigHashAll, - txscript.SigHashNone, - txscript.SigHashSingle, - txscript.SigHashAll | txscript.SigHashAnyOneCanPay, - txscript.SigHashNone | txscript.SigHashAnyOneCanPay, - txscript.SigHashSingle | txscript.SigHashAnyOneCanPay, - } - tx := &wire.MsgTx{ - Version: 1, - TxIn: []*wire.TxIn{ - &wire.TxIn{ - PreviousOutPoint: wire.OutPoint{ - Hash: wire.ShaHash{}, - Index: 0, - }, - Sequence: 4294967295, - }, - &wire.TxIn{ - PreviousOutPoint: wire.OutPoint{ - Hash: wire.ShaHash{}, - Index: 1, - }, - Sequence: 4294967295, - }, - &wire.TxIn{ - PreviousOutPoint: wire.OutPoint{ - Hash: wire.ShaHash{}, - Index: 2, - }, - Sequence: 4294967295, - }, - }, - TxOut: []*wire.TxOut{ - &wire.TxOut{ - Value: 1, - }, - &wire.TxOut{ - Value: 2, - }, - &wire.TxOut{ - Value: 3, - }, - }, - LockTime: 0, - } - - // Pay to Pubkey Hash (uncompressed) - for _, hashType := range hashTypes { - for i := range tx.TxIn { - msg := fmt.Sprintf("%d:%d", hashType, i) - key, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Errorf("failed to make privKey for %s: %v", - msg, err) - break - } - - pk := (*btcec.PublicKey)(&key.PublicKey). - SerializeUncompressed() - address, err := btcutil.NewAddressPubKeyHash( - btcutil.Hash160(pk), &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make address for %s: %v", - msg, err) - break - } - - pkScript, err := txscript.PayToAddrScript(address) - if err != nil { - t.Errorf("failed to make pkscript "+ - "for %s: %v", msg, err) - } - - if err := signAndCheck(msg, tx, i, pkScript, hashType, - mkGetKey(map[string]addressToKey{ - address.EncodeAddress(): {key, false}, - }), mkGetScript(nil), []byte{}); err != nil { - t.Error(err) - break - } - } - } - - // Pay to Pubkey Hash (uncompressed) (merging with correct) - for _, hashType := range hashTypes { - for i := range tx.TxIn { - msg := fmt.Sprintf("%d:%d", hashType, i) - key, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Errorf("failed to make privKey for %s: %v", - msg, err) - break - } - - pk := (*btcec.PublicKey)(&key.PublicKey). - SerializeUncompressed() - address, err := btcutil.NewAddressPubKeyHash( - btcutil.Hash160(pk), &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make address for %s: %v", - msg, err) - break - } - - pkScript, err := txscript.PayToAddrScript(address) - if err != nil { - t.Errorf("failed to make pkscript "+ - "for %s: %v", msg, err) - } - - sigScript, err := txscript.SignTxOutput( - &chaincfg.TestNet3Params, tx, i, pkScript, - hashType, mkGetKey(map[string]addressToKey{ - address.EncodeAddress(): {key, false}, - }), mkGetScript(nil), []byte{}) - if err != nil { - t.Errorf("failed to sign output %s: %v", msg, - err) - break - } - - // by the above loop, this should be valid, now sign - // again and merge. - sigScript, err = txscript.SignTxOutput( - &chaincfg.TestNet3Params, tx, i, pkScript, - hashType, mkGetKey(map[string]addressToKey{ - address.EncodeAddress(): {key, false}, - }), mkGetScript(nil), sigScript) - if err != nil { - t.Errorf("failed to sign output %s a "+ - "second time: %v", msg, err) - break - } - - err = checkScripts(msg, tx, i, sigScript, pkScript) - if err != nil { - t.Errorf("twice signed script invalid for "+ - "%s: %v", msg, err) - break - } - } - } - - // Pay to Pubkey Hash (compressed) - for _, hashType := range hashTypes { - for i := range tx.TxIn { - msg := fmt.Sprintf("%d:%d", hashType, i) - - key, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Errorf("failed to make privKey for %s: %v", - msg, err) - break - } - - pk := (*btcec.PublicKey)(&key.PublicKey). - SerializeCompressed() - address, err := btcutil.NewAddressPubKeyHash( - btcutil.Hash160(pk), &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make address for %s: %v", - msg, err) - break - } - - pkScript, err := txscript.PayToAddrScript(address) - if err != nil { - t.Errorf("failed to make pkscript "+ - "for %s: %v", msg, err) - } - - if err := signAndCheck(msg, tx, i, pkScript, hashType, - mkGetKey(map[string]addressToKey{ - address.EncodeAddress(): {key, true}, - }), mkGetScript(nil), []byte{}); err != nil { - t.Error(err) - break - } - } - } - - // Pay to Pubkey Hash (compressed) with duplicate merge - for _, hashType := range hashTypes { - for i := range tx.TxIn { - msg := fmt.Sprintf("%d:%d", hashType, i) - - key, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Errorf("failed to make privKey for %s: %v", - msg, err) - break - } - - pk := (*btcec.PublicKey)(&key.PublicKey). - SerializeCompressed() - address, err := btcutil.NewAddressPubKeyHash( - btcutil.Hash160(pk), &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make address for %s: %v", - msg, err) - break - } - - pkScript, err := txscript.PayToAddrScript(address) - if err != nil { - t.Errorf("failed to make pkscript "+ - "for %s: %v", msg, err) - } - - sigScript, err := txscript.SignTxOutput( - &chaincfg.TestNet3Params, tx, i, pkScript, - hashType, mkGetKey(map[string]addressToKey{ - address.EncodeAddress(): {key, true}, - }), mkGetScript(nil), []byte{}) - if err != nil { - t.Errorf("failed to sign output %s: %v", msg, - err) - break - } - - // by the above loop, this should be valid, now sign - // again and merge. - sigScript, err = txscript.SignTxOutput( - &chaincfg.TestNet3Params, tx, i, pkScript, - hashType, mkGetKey(map[string]addressToKey{ - address.EncodeAddress(): {key, true}, - }), mkGetScript(nil), sigScript) - if err != nil { - t.Errorf("failed to sign output %s a "+ - "second time: %v", msg, err) - break - } - - err = checkScripts(msg, tx, i, sigScript, pkScript) - if err != nil { - t.Errorf("twice signed script invalid for "+ - "%s: %v", msg, err) - break - } - } - } - - // Pay to PubKey (uncompressed) - for _, hashType := range hashTypes { - for i := range tx.TxIn { - msg := fmt.Sprintf("%d:%d", hashType, i) - - key, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Errorf("failed to make privKey for %s: %v", - msg, err) - break - } - - pk := (*btcec.PublicKey)(&key.PublicKey). - SerializeUncompressed() - address, err := btcutil.NewAddressPubKey(pk, - &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make address for %s: %v", - msg, err) - break - } - - pkScript, err := txscript.PayToAddrScript(address) - if err != nil { - t.Errorf("failed to make pkscript "+ - "for %s: %v", msg, err) - } - - if err := signAndCheck(msg, tx, i, pkScript, hashType, - mkGetKey(map[string]addressToKey{ - address.EncodeAddress(): {key, false}, - }), mkGetScript(nil), []byte{}); err != nil { - t.Error(err) - break - } - } - } - - // Pay to PubKey (uncompressed) - for _, hashType := range hashTypes { - for i := range tx.TxIn { - msg := fmt.Sprintf("%d:%d", hashType, i) - - key, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Errorf("failed to make privKey for %s: %v", - msg, err) - break - } - - pk := (*btcec.PublicKey)(&key.PublicKey). - SerializeUncompressed() - address, err := btcutil.NewAddressPubKey(pk, - &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make address for %s: %v", - msg, err) - break - } - - pkScript, err := txscript.PayToAddrScript(address) - if err != nil { - t.Errorf("failed to make pkscript "+ - "for %s: %v", msg, err) - } - - sigScript, err := txscript.SignTxOutput( - &chaincfg.TestNet3Params, tx, i, pkScript, - hashType, mkGetKey(map[string]addressToKey{ - address.EncodeAddress(): {key, false}, - }), mkGetScript(nil), []byte{}) - if err != nil { - t.Errorf("failed to sign output %s: %v", msg, - err) - break - } - - // by the above loop, this should be valid, now sign - // again and merge. - sigScript, err = txscript.SignTxOutput( - &chaincfg.TestNet3Params, tx, i, pkScript, - hashType, mkGetKey(map[string]addressToKey{ - address.EncodeAddress(): {key, false}, - }), mkGetScript(nil), sigScript) - if err != nil { - t.Errorf("failed to sign output %s a "+ - "second time: %v", msg, err) - break - } - - err = checkScripts(msg, tx, i, sigScript, pkScript) - if err != nil { - t.Errorf("twice signed script invalid for "+ - "%s: %v", msg, err) - break - } - } - } - - // Pay to PubKey (compressed) - for _, hashType := range hashTypes { - for i := range tx.TxIn { - msg := fmt.Sprintf("%d:%d", hashType, i) - - key, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Errorf("failed to make privKey for %s: %v", - msg, err) - break - } - - pk := (*btcec.PublicKey)(&key.PublicKey). - SerializeCompressed() - address, err := btcutil.NewAddressPubKey(pk, - &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make address for %s: %v", - msg, err) - break - } - - pkScript, err := txscript.PayToAddrScript(address) - if err != nil { - t.Errorf("failed to make pkscript "+ - "for %s: %v", msg, err) - } - - if err := signAndCheck(msg, tx, i, pkScript, hashType, - mkGetKey(map[string]addressToKey{ - address.EncodeAddress(): {key, true}, - }), mkGetScript(nil), []byte{}); err != nil { - t.Error(err) - break - } - } - } - - // Pay to PubKey (compressed) with duplicate merge - for _, hashType := range hashTypes { - for i := range tx.TxIn { - msg := fmt.Sprintf("%d:%d", hashType, i) - - key, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Errorf("failed to make privKey for %s: %v", - msg, err) - break - } - - pk := (*btcec.PublicKey)(&key.PublicKey). - SerializeCompressed() - address, err := btcutil.NewAddressPubKey(pk, - &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make address for %s: %v", - msg, err) - break - } - - pkScript, err := txscript.PayToAddrScript(address) - if err != nil { - t.Errorf("failed to make pkscript "+ - "for %s: %v", msg, err) - } - - sigScript, err := txscript.SignTxOutput( - &chaincfg.TestNet3Params, tx, i, pkScript, - hashType, mkGetKey(map[string]addressToKey{ - address.EncodeAddress(): {key, true}, - }), mkGetScript(nil), []byte{}) - if err != nil { - t.Errorf("failed to sign output %s: %v", msg, - err) - break - } - - // by the above loop, this should be valid, now sign - // again and merge. - sigScript, err = txscript.SignTxOutput( - &chaincfg.TestNet3Params, tx, i, pkScript, - hashType, mkGetKey(map[string]addressToKey{ - address.EncodeAddress(): {key, true}, - }), mkGetScript(nil), sigScript) - if err != nil { - t.Errorf("failed to sign output %s a "+ - "second time: %v", msg, err) - break - } - - err = checkScripts(msg, tx, i, sigScript, pkScript) - if err != nil { - t.Errorf("twice signed script invalid for "+ - "%s: %v", msg, err) - break - } - } - } - - // As before, but with p2sh now. - // Pay to Pubkey Hash (uncompressed) - for _, hashType := range hashTypes { - for i := range tx.TxIn { - msg := fmt.Sprintf("%d:%d", hashType, i) - key, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Errorf("failed to make privKey for %s: %v", - msg, err) - break - } - - pk := (*btcec.PublicKey)(&key.PublicKey). - SerializeUncompressed() - address, err := btcutil.NewAddressPubKeyHash( - btcutil.Hash160(pk), &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make address for %s: %v", - msg, err) - break - } - - pkScript, err := txscript.PayToAddrScript(address) - if err != nil { - t.Errorf("failed to make pkscript "+ - "for %s: %v", msg, err) - break - } - - scriptAddr, err := btcutil.NewAddressScriptHash( - pkScript, &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make p2sh addr for %s: %v", - msg, err) - break - } - - scriptPkScript, err := txscript.PayToAddrScript( - scriptAddr) - if err != nil { - t.Errorf("failed to make script pkscript for "+ - "%s: %v", msg, err) - break - } - - if err := signAndCheck(msg, tx, i, scriptPkScript, - hashType, - mkGetKey(map[string]addressToKey{ - address.EncodeAddress(): {key, false}, - }), mkGetScript(map[string][]byte{ - scriptAddr.EncodeAddress(): pkScript, - }), []byte{}); err != nil { - t.Error(err) - break - } - } - } - - // Pay to Pubkey Hash (uncompressed) with duplicate merge - for _, hashType := range hashTypes { - for i := range tx.TxIn { - msg := fmt.Sprintf("%d:%d", hashType, i) - key, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Errorf("failed to make privKey for %s: %v", - msg, err) - break - } - - pk := (*btcec.PublicKey)(&key.PublicKey). - SerializeUncompressed() - address, err := btcutil.NewAddressPubKeyHash( - btcutil.Hash160(pk), &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make address for %s: %v", - msg, err) - break - } - - pkScript, err := txscript.PayToAddrScript(address) - if err != nil { - t.Errorf("failed to make pkscript "+ - "for %s: %v", msg, err) - break - } - - scriptAddr, err := btcutil.NewAddressScriptHash( - pkScript, &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make p2sh addr for %s: %v", - msg, err) - break - } - - scriptPkScript, err := txscript.PayToAddrScript( - scriptAddr) - if err != nil { - t.Errorf("failed to make script pkscript for "+ - "%s: %v", msg, err) - break - } - - sigScript, err := txscript.SignTxOutput( - &chaincfg.TestNet3Params, tx, i, scriptPkScript, - hashType, mkGetKey(map[string]addressToKey{ - address.EncodeAddress(): {key, false}, - }), mkGetScript(map[string][]byte{ - scriptAddr.EncodeAddress(): pkScript, - }), []byte{}) - if err != nil { - t.Errorf("failed to sign output %s: %v", msg, - err) - break - } - - // by the above loop, this should be valid, now sign - // again and merge. - sigScript, err = txscript.SignTxOutput( - &chaincfg.TestNet3Params, tx, i, scriptPkScript, - hashType, mkGetKey(map[string]addressToKey{ - address.EncodeAddress(): {key, false}, - }), mkGetScript(map[string][]byte{ - scriptAddr.EncodeAddress(): pkScript, - }), []byte{}) - if err != nil { - t.Errorf("failed to sign output %s a "+ - "second time: %v", msg, err) - break - } - - err = checkScripts(msg, tx, i, sigScript, scriptPkScript) - if err != nil { - t.Errorf("twice signed script invalid for "+ - "%s: %v", msg, err) - break - } - } - } - - // Pay to Pubkey Hash (compressed) - for _, hashType := range hashTypes { - for i := range tx.TxIn { - msg := fmt.Sprintf("%d:%d", hashType, i) - - key, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Errorf("failed to make privKey for %s: %v", - msg, err) - break - } - - pk := (*btcec.PublicKey)(&key.PublicKey). - SerializeCompressed() - address, err := btcutil.NewAddressPubKeyHash( - btcutil.Hash160(pk), &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make address for %s: %v", - msg, err) - break - } - - pkScript, err := txscript.PayToAddrScript(address) - if err != nil { - t.Errorf("failed to make pkscript "+ - "for %s: %v", msg, err) - } - - scriptAddr, err := btcutil.NewAddressScriptHash( - pkScript, &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make p2sh addr for %s: %v", - msg, err) - break - } - - scriptPkScript, err := txscript.PayToAddrScript( - scriptAddr) - if err != nil { - t.Errorf("failed to make script pkscript for "+ - "%s: %v", msg, err) - break - } - - if err := signAndCheck(msg, tx, i, scriptPkScript, - hashType, - mkGetKey(map[string]addressToKey{ - address.EncodeAddress(): {key, true}, - }), mkGetScript(map[string][]byte{ - scriptAddr.EncodeAddress(): pkScript, - }), []byte{}); err != nil { - t.Error(err) - break - } - } - } - - // Pay to Pubkey Hash (compressed) with duplicate merge - for _, hashType := range hashTypes { - for i := range tx.TxIn { - msg := fmt.Sprintf("%d:%d", hashType, i) - - key, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Errorf("failed to make privKey for %s: %v", - msg, err) - break - } - - pk := (*btcec.PublicKey)(&key.PublicKey). - SerializeCompressed() - address, err := btcutil.NewAddressPubKeyHash( - btcutil.Hash160(pk), &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make address for %s: %v", - msg, err) - break - } - - pkScript, err := txscript.PayToAddrScript(address) - if err != nil { - t.Errorf("failed to make pkscript "+ - "for %s: %v", msg, err) - } - - scriptAddr, err := btcutil.NewAddressScriptHash( - pkScript, &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make p2sh addr for %s: %v", - msg, err) - break - } - - scriptPkScript, err := txscript.PayToAddrScript( - scriptAddr) - if err != nil { - t.Errorf("failed to make script pkscript for "+ - "%s: %v", msg, err) - break - } - - sigScript, err := txscript.SignTxOutput( - &chaincfg.TestNet3Params, tx, i, scriptPkScript, - hashType, mkGetKey(map[string]addressToKey{ - address.EncodeAddress(): {key, true}, - }), mkGetScript(map[string][]byte{ - scriptAddr.EncodeAddress(): pkScript, - }), []byte{}) - if err != nil { - t.Errorf("failed to sign output %s: %v", msg, - err) - break - } - - // by the above loop, this should be valid, now sign - // again and merge. - sigScript, err = txscript.SignTxOutput( - &chaincfg.TestNet3Params, tx, i, scriptPkScript, - hashType, mkGetKey(map[string]addressToKey{ - address.EncodeAddress(): {key, true}, - }), mkGetScript(map[string][]byte{ - scriptAddr.EncodeAddress(): pkScript, - }), []byte{}) - if err != nil { - t.Errorf("failed to sign output %s a "+ - "second time: %v", msg, err) - break - } - - err = checkScripts(msg, tx, i, sigScript, scriptPkScript) - if err != nil { - t.Errorf("twice signed script invalid for "+ - "%s: %v", msg, err) - break - } - } - } - - // Pay to PubKey (uncompressed) - for _, hashType := range hashTypes { - for i := range tx.TxIn { - msg := fmt.Sprintf("%d:%d", hashType, i) - - key, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Errorf("failed to make privKey for %s: %v", - msg, err) - break - } - - pk := (*btcec.PublicKey)(&key.PublicKey). - SerializeUncompressed() - address, err := btcutil.NewAddressPubKey(pk, - &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make address for %s: %v", - msg, err) - break - } - - pkScript, err := txscript.PayToAddrScript(address) - if err != nil { - t.Errorf("failed to make pkscript "+ - "for %s: %v", msg, err) - } - - scriptAddr, err := btcutil.NewAddressScriptHash( - pkScript, &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make p2sh addr for %s: %v", - msg, err) - break - } - - scriptPkScript, err := txscript.PayToAddrScript( - scriptAddr) - if err != nil { - t.Errorf("failed to make script pkscript for "+ - "%s: %v", msg, err) - break - } - - if err := signAndCheck(msg, tx, i, scriptPkScript, - hashType, - mkGetKey(map[string]addressToKey{ - address.EncodeAddress(): {key, false}, - }), mkGetScript(map[string][]byte{ - scriptAddr.EncodeAddress(): pkScript, - }), []byte{}); err != nil { - t.Error(err) - break - } - } - } - - // Pay to PubKey (uncompressed) with duplicate merge - for _, hashType := range hashTypes { - for i := range tx.TxIn { - msg := fmt.Sprintf("%d:%d", hashType, i) - - key, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Errorf("failed to make privKey for %s: %v", - msg, err) - break - } - - pk := (*btcec.PublicKey)(&key.PublicKey). - SerializeUncompressed() - address, err := btcutil.NewAddressPubKey(pk, - &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make address for %s: %v", - msg, err) - break - } - - pkScript, err := txscript.PayToAddrScript(address) - if err != nil { - t.Errorf("failed to make pkscript "+ - "for %s: %v", msg, err) - } - - scriptAddr, err := btcutil.NewAddressScriptHash( - pkScript, &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make p2sh addr for %s: %v", - msg, err) - break - } - - scriptPkScript, err := txscript.PayToAddrScript( - scriptAddr) - if err != nil { - t.Errorf("failed to make script pkscript for "+ - "%s: %v", msg, err) - break - } - - sigScript, err := txscript.SignTxOutput( - &chaincfg.TestNet3Params, tx, i, scriptPkScript, - hashType, mkGetKey(map[string]addressToKey{ - address.EncodeAddress(): {key, false}, - }), mkGetScript(map[string][]byte{ - scriptAddr.EncodeAddress(): pkScript, - }), []byte{}) - if err != nil { - t.Errorf("failed to sign output %s: %v", msg, - err) - break - } - - // by the above loop, this should be valid, now sign - // again and merge. - sigScript, err = txscript.SignTxOutput( - &chaincfg.TestNet3Params, tx, i, scriptPkScript, - hashType, mkGetKey(map[string]addressToKey{ - address.EncodeAddress(): {key, false}, - }), mkGetScript(map[string][]byte{ - scriptAddr.EncodeAddress(): pkScript, - }), []byte{}) - if err != nil { - t.Errorf("failed to sign output %s a "+ - "second time: %v", msg, err) - break - } - - err = checkScripts(msg, tx, i, sigScript, scriptPkScript) - if err != nil { - t.Errorf("twice signed script invalid for "+ - "%s: %v", msg, err) - break - } - } - } - - // Pay to PubKey (compressed) - for _, hashType := range hashTypes { - for i := range tx.TxIn { - msg := fmt.Sprintf("%d:%d", hashType, i) - - key, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Errorf("failed to make privKey for %s: %v", - msg, err) - break - } - - pk := (*btcec.PublicKey)(&key.PublicKey). - SerializeCompressed() - address, err := btcutil.NewAddressPubKey(pk, - &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make address for %s: %v", - msg, err) - break - } - - pkScript, err := txscript.PayToAddrScript(address) - if err != nil { - t.Errorf("failed to make pkscript "+ - "for %s: %v", msg, err) - } - - scriptAddr, err := btcutil.NewAddressScriptHash( - pkScript, &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make p2sh addr for %s: %v", - msg, err) - break - } - - scriptPkScript, err := txscript.PayToAddrScript( - scriptAddr) - if err != nil { - t.Errorf("failed to make script pkscript for "+ - "%s: %v", msg, err) - break - } - - if err := signAndCheck(msg, tx, i, scriptPkScript, - hashType, - mkGetKey(map[string]addressToKey{ - address.EncodeAddress(): {key, true}, - }), mkGetScript(map[string][]byte{ - scriptAddr.EncodeAddress(): pkScript, - }), []byte{}); err != nil { - t.Error(err) - break - } - } - } - - // Pay to PubKey (compressed) - for _, hashType := range hashTypes { - for i := range tx.TxIn { - msg := fmt.Sprintf("%d:%d", hashType, i) - - key, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Errorf("failed to make privKey for %s: %v", - msg, err) - break - } - - pk := (*btcec.PublicKey)(&key.PublicKey). - SerializeCompressed() - address, err := btcutil.NewAddressPubKey(pk, - &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make address for %s: %v", - msg, err) - break - } - - pkScript, err := txscript.PayToAddrScript(address) - if err != nil { - t.Errorf("failed to make pkscript "+ - "for %s: %v", msg, err) - } - - scriptAddr, err := btcutil.NewAddressScriptHash( - pkScript, &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make p2sh addr for %s: %v", - msg, err) - break - } - - scriptPkScript, err := txscript.PayToAddrScript( - scriptAddr) - if err != nil { - t.Errorf("failed to make script pkscript for "+ - "%s: %v", msg, err) - break - } - - sigScript, err := txscript.SignTxOutput( - &chaincfg.TestNet3Params, tx, i, scriptPkScript, - hashType, mkGetKey(map[string]addressToKey{ - address.EncodeAddress(): {key, true}, - }), mkGetScript(map[string][]byte{ - scriptAddr.EncodeAddress(): pkScript, - }), []byte{}) - if err != nil { - t.Errorf("failed to sign output %s: %v", msg, - err) - break - } - - // by the above loop, this should be valid, now sign - // again and merge. - sigScript, err = txscript.SignTxOutput( - &chaincfg.TestNet3Params, tx, i, scriptPkScript, - hashType, mkGetKey(map[string]addressToKey{ - address.EncodeAddress(): {key, true}, - }), mkGetScript(map[string][]byte{ - scriptAddr.EncodeAddress(): pkScript, - }), []byte{}) - if err != nil { - t.Errorf("failed to sign output %s a "+ - "second time: %v", msg, err) - break - } - - err = checkScripts(msg, tx, i, sigScript, scriptPkScript) - if err != nil { - t.Errorf("twice signed script invalid for "+ - "%s: %v", msg, err) - break - } - } - } - - // Basic Multisig - for _, hashType := range hashTypes { - for i := range tx.TxIn { - msg := fmt.Sprintf("%d:%d", hashType, i) - - key1, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Errorf("failed to make privKey for %s: %v", - msg, err) - break - } - - pk1 := (*btcec.PublicKey)(&key1.PublicKey). - SerializeCompressed() - address1, err := btcutil.NewAddressPubKey(pk1, - &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make address for %s: %v", - msg, err) - break - } - - key2, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Errorf("failed to make privKey 2 for %s: %v", - msg, err) - break - } - - pk2 := (*btcec.PublicKey)(&key2.PublicKey). - SerializeCompressed() - address2, err := btcutil.NewAddressPubKey(pk2, - &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make address 2 for %s: %v", - msg, err) - break - } - - pkScript, err := txscript.MultiSigScript( - []*btcutil.AddressPubKey{address1, address2}, - 2) - if err != nil { - t.Errorf("failed to make pkscript "+ - "for %s: %v", msg, err) - } - - scriptAddr, err := btcutil.NewAddressScriptHash( - pkScript, &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make p2sh addr for %s: %v", - msg, err) - break - } - - scriptPkScript, err := txscript.PayToAddrScript( - scriptAddr) - if err != nil { - t.Errorf("failed to make script pkscript for "+ - "%s: %v", msg, err) - break - } - - if err := signAndCheck(msg, tx, i, scriptPkScript, - hashType, - mkGetKey(map[string]addressToKey{ - address1.EncodeAddress(): {key1, true}, - address2.EncodeAddress(): {key2, true}, - }), mkGetScript(map[string][]byte{ - scriptAddr.EncodeAddress(): pkScript, - }), []byte{}); err != nil { - t.Error(err) - break - } - } - } - - // Two part multisig, sign with one key then the other. - for _, hashType := range hashTypes { - for i := range tx.TxIn { - msg := fmt.Sprintf("%d:%d", hashType, i) - - key1, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Errorf("failed to make privKey for %s: %v", - msg, err) - break - } - - pk1 := (*btcec.PublicKey)(&key1.PublicKey). - SerializeCompressed() - address1, err := btcutil.NewAddressPubKey(pk1, - &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make address for %s: %v", - msg, err) - break - } - - key2, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Errorf("failed to make privKey 2 for %s: %v", - msg, err) - break - } - - pk2 := (*btcec.PublicKey)(&key2.PublicKey). - SerializeCompressed() - address2, err := btcutil.NewAddressPubKey(pk2, - &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make address 2 for %s: %v", - msg, err) - break - } - - pkScript, err := txscript.MultiSigScript( - []*btcutil.AddressPubKey{address1, address2}, - 2) - if err != nil { - t.Errorf("failed to make pkscript "+ - "for %s: %v", msg, err) - } - - scriptAddr, err := btcutil.NewAddressScriptHash( - pkScript, &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make p2sh addr for %s: %v", - msg, err) - break - } - - scriptPkScript, err := txscript.PayToAddrScript( - scriptAddr) - if err != nil { - t.Errorf("failed to make script pkscript for "+ - "%s: %v", msg, err) - break - } - - sigScript, err := txscript.SignTxOutput( - &chaincfg.TestNet3Params, tx, i, scriptPkScript, - hashType, mkGetKey(map[string]addressToKey{ - address1.EncodeAddress(): {key1, true}, - }), mkGetScript(map[string][]byte{ - scriptAddr.EncodeAddress(): pkScript, - }), []byte{}) - if err != nil { - t.Errorf("failed to sign output %s: %v", msg, - err) - break - } - - // Only 1 out of 2 signed, this *should* fail. - if checkScripts(msg, tx, i, sigScript, - scriptPkScript) == nil { - t.Errorf("part signed script valid for %s", msg) - break - } - - // Sign with the other key and merge - sigScript, err = txscript.SignTxOutput( - &chaincfg.TestNet3Params, tx, i, scriptPkScript, - hashType, mkGetKey(map[string]addressToKey{ - address2.EncodeAddress(): {key2, true}, - }), mkGetScript(map[string][]byte{ - scriptAddr.EncodeAddress(): pkScript, - }), sigScript) - if err != nil { - t.Errorf("failed to sign output %s: %v", msg, err) - break - } - - err = checkScripts(msg, tx, i, sigScript, - scriptPkScript) - if err != nil { - t.Errorf("fully signed script invalid for "+ - "%s: %v", msg, err) - break - } - } - } - - // Two part multisig, sign with one key then both, check key dedup - // correctly. - for _, hashType := range hashTypes { - for i := range tx.TxIn { - msg := fmt.Sprintf("%d:%d", hashType, i) - - key1, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Errorf("failed to make privKey for %s: %v", - msg, err) - break - } - - pk1 := (*btcec.PublicKey)(&key1.PublicKey). - SerializeCompressed() - address1, err := btcutil.NewAddressPubKey(pk1, - &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make address for %s: %v", - msg, err) - break - } - - key2, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Errorf("failed to make privKey 2 for %s: %v", - msg, err) - break - } - - pk2 := (*btcec.PublicKey)(&key2.PublicKey). - SerializeCompressed() - address2, err := btcutil.NewAddressPubKey(pk2, - &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make address 2 for %s: %v", - msg, err) - break - } - - pkScript, err := txscript.MultiSigScript( - []*btcutil.AddressPubKey{address1, address2}, - 2) - if err != nil { - t.Errorf("failed to make pkscript "+ - "for %s: %v", msg, err) - } - - scriptAddr, err := btcutil.NewAddressScriptHash( - pkScript, &chaincfg.TestNet3Params) - if err != nil { - t.Errorf("failed to make p2sh addr for %s: %v", - msg, err) - break - } - - scriptPkScript, err := txscript.PayToAddrScript( - scriptAddr) - if err != nil { - t.Errorf("failed to make script pkscript for "+ - "%s: %v", msg, err) - break - } - - sigScript, err := txscript.SignTxOutput( - &chaincfg.TestNet3Params, tx, i, scriptPkScript, - hashType, mkGetKey(map[string]addressToKey{ - address1.EncodeAddress(): {key1, true}, - }), mkGetScript(map[string][]byte{ - scriptAddr.EncodeAddress(): pkScript, - }), []byte{}) - if err != nil { - t.Errorf("failed to sign output %s: %v", msg, - err) - break - } - - // Only 1 out of 2 signed, this *should* fail. - if checkScripts(msg, tx, i, sigScript, - scriptPkScript) == nil { - t.Errorf("part signed script valid for %s", msg) - break - } - - // Sign with the other key and merge - sigScript, err = txscript.SignTxOutput( - &chaincfg.TestNet3Params, tx, i, scriptPkScript, - hashType, mkGetKey(map[string]addressToKey{ - address1.EncodeAddress(): {key1, true}, - address2.EncodeAddress(): {key2, true}, - }), mkGetScript(map[string][]byte{ - scriptAddr.EncodeAddress(): pkScript, - }), sigScript) - if err != nil { - t.Errorf("failed to sign output %s: %v", msg, err) - break - } - - // Now we should pass. - err = checkScripts(msg, tx, i, sigScript, - scriptPkScript) - if err != nil { - t.Errorf("fully signed script invalid for "+ - "%s: %v", msg, err) - break - } - } - } -} - func TestCalcMultiSigStats(t *testing.T) { t.Parallel() diff --git a/txscript/sign.go b/txscript/sign.go new file mode 100644 index 000000000..9aa5e631e --- /dev/null +++ b/txscript/sign.go @@ -0,0 +1,411 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package txscript + +import ( + "errors" + "fmt" + + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" +) + +// RawTxInSignature returns the serialized ECDSA signature for the input idx of +// the given transaction, with hashType appended to it. +func RawTxInSignature(tx *wire.MsgTx, idx int, subScript []byte, + hashType SigHashType, key *btcec.PrivateKey) ([]byte, error) { + + parsedScript, err := parseScript(subScript) + if err != nil { + return nil, fmt.Errorf("cannot parse output script: %v", err) + } + hash := calcScriptHash(parsedScript, hashType, tx, idx) + signature, err := key.Sign(hash) + if err != nil { + return nil, fmt.Errorf("cannot sign tx input: %s", err) + } + + return append(signature.Serialize(), byte(hashType)), nil +} + +// SignatureScript creates an input signature script for tx to spend BTC sent +// from a previous output to the owner of privKey. tx must include all +// transaction inputs and outputs, however txin scripts are allowed to be filled +// or empty. The returned script is calculated to be used as the idx'th txin +// sigscript for tx. subscript is the PkScript of the previous output being used +// as the idx'th input. privKey is serialized in either a compressed or +// uncompressed format based on compress. This format must match the same format +// used to generate the payment address, or the script validation will fail. +func SignatureScript(tx *wire.MsgTx, idx int, subscript []byte, hashType SigHashType, privKey *btcec.PrivateKey, compress bool) ([]byte, error) { + sig, err := RawTxInSignature(tx, idx, subscript, hashType, privKey) + if err != nil { + return nil, err + } + + pk := (*btcec.PublicKey)(&privKey.PublicKey) + var pkData []byte + if compress { + pkData = pk.SerializeCompressed() + } else { + pkData = pk.SerializeUncompressed() + } + + return NewScriptBuilder().AddData(sig).AddData(pkData).Script() +} + +func p2pkSignatureScript(tx *wire.MsgTx, idx int, subScript []byte, hashType SigHashType, privKey *btcec.PrivateKey) ([]byte, error) { + sig, err := RawTxInSignature(tx, idx, subScript, hashType, privKey) + if err != nil { + return nil, err + } + + return NewScriptBuilder().AddData(sig).Script() +} + +// signMultiSig signs as many of the outputs in the provided multisig script as +// possible. It returns the generated script and a boolean if the script fulfils +// the contract (i.e. nrequired signatures are provided). Since it is arguably +// legal to not be able to sign any of the outputs, no error is returned. +func signMultiSig(tx *wire.MsgTx, idx int, subScript []byte, hashType SigHashType, + addresses []btcutil.Address, nRequired int, kdb KeyDB) ([]byte, bool) { + // We start with a single OP_FALSE to work around the (now standard) + // but in the reference implementation that causes a spurious pop at + // the end of OP_CHECKMULTISIG. + builder := NewScriptBuilder().AddOp(OP_FALSE) + signed := 0 + for _, addr := range addresses { + key, _, err := kdb.GetKey(addr) + if err != nil { + continue + } + sig, err := RawTxInSignature(tx, idx, subScript, hashType, key) + if err != nil { + continue + } + + builder.AddData(sig) + signed++ + if signed == nRequired { + break + } + + } + + script, _ := builder.Script() + return script, signed == nRequired +} + +func sign(chainParams *chaincfg.Params, tx *wire.MsgTx, idx int, + subScript []byte, hashType SigHashType, kdb KeyDB, sdb ScriptDB) ([]byte, + ScriptClass, []btcutil.Address, int, error) { + + class, addresses, nrequired, err := ExtractPkScriptAddrs(subScript, + chainParams) + if err != nil { + return nil, NonStandardTy, nil, 0, err + } + + switch class { + case PubKeyTy: + // look up key for address + key, _, err := kdb.GetKey(addresses[0]) + if err != nil { + return nil, class, nil, 0, err + } + + script, err := p2pkSignatureScript(tx, idx, subScript, hashType, + key) + if err != nil { + return nil, class, nil, 0, err + } + + return script, class, addresses, nrequired, nil + case PubKeyHashTy: + // look up key for address + key, compressed, err := kdb.GetKey(addresses[0]) + if err != nil { + return nil, class, nil, 0, err + } + + script, err := SignatureScript(tx, idx, subScript, hashType, + key, compressed) + if err != nil { + return nil, class, nil, 0, err + } + + return script, class, addresses, nrequired, nil + case ScriptHashTy: + script, err := sdb.GetScript(addresses[0]) + if err != nil { + return nil, class, nil, 0, err + } + + return script, class, addresses, nrequired, nil + case MultiSigTy: + script, _ := signMultiSig(tx, idx, subScript, hashType, + addresses, nrequired, kdb) + return script, class, addresses, nrequired, nil + case NullDataTy: + return nil, class, nil, 0, + errors.New("can't sign NULLDATA transactions") + default: + return nil, class, nil, 0, + errors.New("can't sign unknown transactions") + } +} + +// mergeScripts merges sigScript and prevScript assuming they are both +// partial solutions for pkScript spending output idx of tx. class, addresses +// and nrequired are the result of extracting the addresses from pkscript. +// The return value is the best effort merging of the two scripts. Calling this +// function with addresses, class and nrequired that do not match pkScript is +// an error and results in undefined behaviour. +func mergeScripts(chainParams *chaincfg.Params, tx *wire.MsgTx, idx int, + pkScript []byte, class ScriptClass, addresses []btcutil.Address, + nRequired int, sigScript, prevScript []byte) []byte { + + // TODO(oga) the scripthash and multisig paths here are overly + // inefficient in that they will recompute already known data. + // some internal refactoring could probably make this avoid needless + // extra calculations. + switch class { + case ScriptHashTy: + // Remove the last push in the script and then recurse. + // this could be a lot less inefficient. + sigPops, err := parseScript(sigScript) + if err != nil || len(sigPops) == 0 { + return prevScript + } + prevPops, err := parseScript(prevScript) + if err != nil || len(prevPops) == 0 { + return sigScript + } + + // assume that script in sigPops is the correct one, we just + // made it. + script := sigPops[len(sigPops)-1].data + + // We already know this information somewhere up the stack. + class, addresses, nrequired, err := + ExtractPkScriptAddrs(script, chainParams) + + // regenerate scripts. + sigScript, _ := unparseScript(sigPops) + prevScript, _ := unparseScript(prevPops) + + // Merge + mergedScript := mergeScripts(chainParams, tx, idx, script, + class, addresses, nrequired, sigScript, prevScript) + + // Reappend the script and return the result. + builder := NewScriptBuilder() + builder.script = mergedScript + builder.AddData(script) + finalScript, _ := builder.Script() + return finalScript + case MultiSigTy: + return mergeMultiSig(tx, idx, addresses, nRequired, pkScript, + sigScript, prevScript) + + // It doesn't actualy make sense to merge anything other than multiig + // and scripthash (because it could contain multisig). Everything else + // has either zero signature, can't be spent, or has a single signature + // which is either present or not. The other two cases are handled + // above. In the conflict case here we just assume the longest is + // correct (this matches behaviour of the reference implementation). + default: + if len(sigScript) > len(prevScript) { + return sigScript + } + return prevScript + } +} + +// mergeMultiSig combines the two signature scripts sigScript and prevScript +// that both provide signatures for pkScript in output idx of tx. addresses +// and nRequired should be the results from extracting the addresses from +// pkScript. Since this function is internal only we assume that the arguments +// have come from other functions internally and thus are all consistent with +// each other, behaviour is undefined if this contract is broken. +func mergeMultiSig(tx *wire.MsgTx, idx int, addresses []btcutil.Address, + nRequired int, pkScript, sigScript, prevScript []byte) []byte { + + // This is an internal only function and we already parsed this script + // as ok for multisig (this is how we got here), so if this fails then + // all assumptions are broken and who knows which way is up? + pkPops, _ := parseScript(pkScript) + + sigPops, err := parseScript(sigScript) + if err != nil || len(sigPops) == 0 { + return prevScript + } + + prevPops, err := parseScript(prevScript) + if err != nil || len(prevPops) == 0 { + return sigScript + } + + // Convenience function to avoid duplication. + extractSigs := func(pops []parsedOpcode, sigs [][]byte) [][]byte { + for _, pop := range pops { + if len(pop.data) != 0 { + sigs = append(sigs, pop.data) + } + } + return sigs + } + + possibleSigs := make([][]byte, 0, len(sigPops)+len(prevPops)) + possibleSigs = extractSigs(sigPops, possibleSigs) + possibleSigs = extractSigs(prevPops, possibleSigs) + + // Now we need to match the signatures to pubkeys, the only real way to + // do that is to try to verify them all and match it to the pubkey + // that verifies it. we then can go through the addresses in order + // to build our script. Anything that doesn't parse or doesn't verify we + // throw away. + addrToSig := make(map[string][]byte) +sigLoop: + for _, sig := range possibleSigs { + + // can't have a valid signature that doesn't at least have a + // hashtype, in practise it is even longer than this. but + // that'll be checked next. + if len(sig) < 1 { + continue + } + tSig := sig[:len(sig)-1] + hashType := SigHashType(sig[len(sig)-1]) + + pSig, err := btcec.ParseDERSignature(tSig, btcec.S256()) + if err != nil { + continue + } + + // We have to do this each round since hash types may vary + // between signatures and so the hash will vary. We can, + // however, assume no sigs etc are in the script since that + // would make the transaction nonstandard and thus not + // MultiSigTy, so we just need to hash the full thing. + hash := calcScriptHash(pkPops, hashType, tx, idx) + + for _, addr := range addresses { + // All multisig addresses should be pubkey addreses + // it is an error to call this internal function with + // bad input. + pkaddr := addr.(*btcutil.AddressPubKey) + + pubKey := pkaddr.PubKey() + + // If it matches we put it in the map. We only + // can take one signature per public key so if we + // already have one, we can throw this away. + if pSig.Verify(hash, pubKey) { + aStr := addr.EncodeAddress() + if _, ok := addrToSig[aStr]; !ok { + addrToSig[aStr] = sig + } + continue sigLoop + } + } + } + + // Extra opcode to handle the extra arg consumed (due to previous bugs + // in the reference implementation). + builder := NewScriptBuilder().AddOp(OP_FALSE) + doneSigs := 0 + // This assumes that addresses are in the same order as in the script. + for _, addr := range addresses { + sig, ok := addrToSig[addr.EncodeAddress()] + if !ok { + continue + } + builder.AddData(sig) + doneSigs++ + if doneSigs == nRequired { + break + } + } + + // padding for missing ones. + for i := doneSigs; i < nRequired; i++ { + builder.AddOp(OP_0) + } + + script, _ := builder.Script() + return script +} + +// KeyDB is an interface type provided to SignTxOutput, it encapsulates +// any user state required to get the private keys for an address. +type KeyDB interface { + GetKey(btcutil.Address) (*btcec.PrivateKey, bool, error) +} + +// KeyClosure implements ScriptDB with a closure +type KeyClosure func(btcutil.Address) (*btcec.PrivateKey, bool, error) + +// GetKey implements KeyDB by returning the result of calling the closure +func (kc KeyClosure) GetKey(address btcutil.Address) (*btcec.PrivateKey, + bool, error) { + return kc(address) +} + +// ScriptDB is an interface type provided to SignTxOutput, it encapsulates any +// user state required to get the scripts for an pay-to-script-hash address. +type ScriptDB interface { + GetScript(btcutil.Address) ([]byte, error) +} + +// ScriptClosure implements ScriptDB with a closure +type ScriptClosure func(btcutil.Address) ([]byte, error) + +// GetScript implements ScriptDB by returning the result of calling the closure +func (sc ScriptClosure) GetScript(address btcutil.Address) ([]byte, error) { + return sc(address) +} + +// SignTxOutput signs output idx of the given tx to resolve the script given in +// pkScript with a signature type of hashType. Any keys required will be +// looked up by calling getKey() with the string of the given address. +// Any pay-to-script-hash signatures will be similarly looked up by calling +// getScript. If previousScript is provided then the results in previousScript +// will be merged in a type-dependant manner with the newly generated. +// signature script. +func SignTxOutput(chainParams *chaincfg.Params, tx *wire.MsgTx, idx int, + pkScript []byte, hashType SigHashType, kdb KeyDB, sdb ScriptDB, + previousScript []byte) ([]byte, error) { + + sigScript, class, addresses, nrequired, err := sign(chainParams, tx, + idx, pkScript, hashType, kdb, sdb) + if err != nil { + return nil, err + } + + if class == ScriptHashTy { + // TODO keep the sub addressed and pass down to merge. + realSigScript, _, _, _, err := sign(chainParams, tx, idx, + sigScript, hashType, kdb, sdb) + if err != nil { + return nil, err + } + + // This is a bad thing. Append the p2sh script as the last + // push in the script. + builder := NewScriptBuilder() + builder.script = realSigScript + builder.AddData(sigScript) + + sigScript, _ = builder.Script() + // TODO keep a copy of the script for merging. + } + + // Merge scripts. with any previous data, if any. + mergedScript := mergeScripts(chainParams, tx, idx, pkScript, class, + addresses, nrequired, sigScript, previousScript) + return mergedScript, nil +} diff --git a/txscript/sign_test.go b/txscript/sign_test.go new file mode 100644 index 000000000..a391693ea --- /dev/null +++ b/txscript/sign_test.go @@ -0,0 +1,1714 @@ +// Copyright (c) 2013-2015 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package txscript_test + +import ( + "errors" + "fmt" + "testing" + + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" +) + +type addressToKey struct { + key *btcec.PrivateKey + compressed bool +} + +func mkGetKey(keys map[string]addressToKey) txscript.KeyDB { + if keys == nil { + return txscript.KeyClosure(func(addr btcutil.Address) (*btcec.PrivateKey, + bool, error) { + return nil, false, errors.New("nope") + }) + } + return txscript.KeyClosure(func(addr btcutil.Address) (*btcec.PrivateKey, + bool, error) { + a2k, ok := keys[addr.EncodeAddress()] + if !ok { + return nil, false, errors.New("nope") + } + return a2k.key, a2k.compressed, nil + }) +} + +func mkGetScript(scripts map[string][]byte) txscript.ScriptDB { + if scripts == nil { + return txscript.ScriptClosure(func(addr btcutil.Address) ( + []byte, error) { + return nil, errors.New("nope") + }) + } + return txscript.ScriptClosure(func(addr btcutil.Address) ([]byte, + error) { + script, ok := scripts[addr.EncodeAddress()] + if !ok { + return nil, errors.New("nope") + } + return script, nil + }) +} + +func checkScripts(msg string, tx *wire.MsgTx, idx int, sigScript, pkScript []byte) error { + tx.TxIn[idx].SignatureScript = sigScript + vm, err := txscript.NewEngine(pkScript, tx, idx, + txscript.ScriptBip16|txscript.ScriptVerifyDERSignatures) + if err != nil { + return fmt.Errorf("failed to make script engine for %s: %v", + msg, err) + } + + err = vm.Execute() + if err != nil { + return fmt.Errorf("invalid script signature for %s: %v", msg, + err) + } + + return nil +} + +func signAndCheck(msg string, tx *wire.MsgTx, idx int, pkScript []byte, + hashType txscript.SigHashType, kdb txscript.KeyDB, sdb txscript.ScriptDB, + previousScript []byte) error { + + sigScript, err := txscript.SignTxOutput(&chaincfg.TestNet3Params, tx, + idx, pkScript, hashType, kdb, sdb, []byte{}) + if err != nil { + return fmt.Errorf("failed to sign output %s: %v", msg, err) + } + + return checkScripts(msg, tx, idx, sigScript, pkScript) +} + +func TestSignTxOutput(t *testing.T) { + t.Parallel() + + // make key + // make script based on key. + // sign with magic pixie dust. + hashTypes := []txscript.SigHashType{ + txscript.SigHashOld, // no longer used but should act like all + txscript.SigHashAll, + txscript.SigHashNone, + txscript.SigHashSingle, + txscript.SigHashAll | txscript.SigHashAnyOneCanPay, + txscript.SigHashNone | txscript.SigHashAnyOneCanPay, + txscript.SigHashSingle | txscript.SigHashAnyOneCanPay, + } + tx := &wire.MsgTx{ + Version: 1, + TxIn: []*wire.TxIn{ + &wire.TxIn{ + PreviousOutPoint: wire.OutPoint{ + Hash: wire.ShaHash{}, + Index: 0, + }, + Sequence: 4294967295, + }, + &wire.TxIn{ + PreviousOutPoint: wire.OutPoint{ + Hash: wire.ShaHash{}, + Index: 1, + }, + Sequence: 4294967295, + }, + &wire.TxIn{ + PreviousOutPoint: wire.OutPoint{ + Hash: wire.ShaHash{}, + Index: 2, + }, + Sequence: 4294967295, + }, + }, + TxOut: []*wire.TxOut{ + &wire.TxOut{ + Value: 1, + }, + &wire.TxOut{ + Value: 2, + }, + &wire.TxOut{ + Value: 3, + }, + }, + LockTime: 0, + } + + // Pay to Pubkey Hash (uncompressed) + for _, hashType := range hashTypes { + for i := range tx.TxIn { + msg := fmt.Sprintf("%d:%d", hashType, i) + key, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Errorf("failed to make privKey for %s: %v", + msg, err) + break + } + + pk := (*btcec.PublicKey)(&key.PublicKey). + SerializeUncompressed() + address, err := btcutil.NewAddressPubKeyHash( + btcutil.Hash160(pk), &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make address for %s: %v", + msg, err) + break + } + + pkScript, err := txscript.PayToAddrScript(address) + if err != nil { + t.Errorf("failed to make pkscript "+ + "for %s: %v", msg, err) + } + + if err := signAndCheck(msg, tx, i, pkScript, hashType, + mkGetKey(map[string]addressToKey{ + address.EncodeAddress(): {key, false}, + }), mkGetScript(nil), []byte{}); err != nil { + t.Error(err) + break + } + } + } + + // Pay to Pubkey Hash (uncompressed) (merging with correct) + for _, hashType := range hashTypes { + for i := range tx.TxIn { + msg := fmt.Sprintf("%d:%d", hashType, i) + key, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Errorf("failed to make privKey for %s: %v", + msg, err) + break + } + + pk := (*btcec.PublicKey)(&key.PublicKey). + SerializeUncompressed() + address, err := btcutil.NewAddressPubKeyHash( + btcutil.Hash160(pk), &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make address for %s: %v", + msg, err) + break + } + + pkScript, err := txscript.PayToAddrScript(address) + if err != nil { + t.Errorf("failed to make pkscript "+ + "for %s: %v", msg, err) + } + + sigScript, err := txscript.SignTxOutput( + &chaincfg.TestNet3Params, tx, i, pkScript, + hashType, mkGetKey(map[string]addressToKey{ + address.EncodeAddress(): {key, false}, + }), mkGetScript(nil), []byte{}) + if err != nil { + t.Errorf("failed to sign output %s: %v", msg, + err) + break + } + + // by the above loop, this should be valid, now sign + // again and merge. + sigScript, err = txscript.SignTxOutput( + &chaincfg.TestNet3Params, tx, i, pkScript, + hashType, mkGetKey(map[string]addressToKey{ + address.EncodeAddress(): {key, false}, + }), mkGetScript(nil), sigScript) + if err != nil { + t.Errorf("failed to sign output %s a "+ + "second time: %v", msg, err) + break + } + + err = checkScripts(msg, tx, i, sigScript, pkScript) + if err != nil { + t.Errorf("twice signed script invalid for "+ + "%s: %v", msg, err) + break + } + } + } + + // Pay to Pubkey Hash (compressed) + for _, hashType := range hashTypes { + for i := range tx.TxIn { + msg := fmt.Sprintf("%d:%d", hashType, i) + + key, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Errorf("failed to make privKey for %s: %v", + msg, err) + break + } + + pk := (*btcec.PublicKey)(&key.PublicKey). + SerializeCompressed() + address, err := btcutil.NewAddressPubKeyHash( + btcutil.Hash160(pk), &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make address for %s: %v", + msg, err) + break + } + + pkScript, err := txscript.PayToAddrScript(address) + if err != nil { + t.Errorf("failed to make pkscript "+ + "for %s: %v", msg, err) + } + + if err := signAndCheck(msg, tx, i, pkScript, hashType, + mkGetKey(map[string]addressToKey{ + address.EncodeAddress(): {key, true}, + }), mkGetScript(nil), []byte{}); err != nil { + t.Error(err) + break + } + } + } + + // Pay to Pubkey Hash (compressed) with duplicate merge + for _, hashType := range hashTypes { + for i := range tx.TxIn { + msg := fmt.Sprintf("%d:%d", hashType, i) + + key, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Errorf("failed to make privKey for %s: %v", + msg, err) + break + } + + pk := (*btcec.PublicKey)(&key.PublicKey). + SerializeCompressed() + address, err := btcutil.NewAddressPubKeyHash( + btcutil.Hash160(pk), &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make address for %s: %v", + msg, err) + break + } + + pkScript, err := txscript.PayToAddrScript(address) + if err != nil { + t.Errorf("failed to make pkscript "+ + "for %s: %v", msg, err) + } + + sigScript, err := txscript.SignTxOutput( + &chaincfg.TestNet3Params, tx, i, pkScript, + hashType, mkGetKey(map[string]addressToKey{ + address.EncodeAddress(): {key, true}, + }), mkGetScript(nil), []byte{}) + if err != nil { + t.Errorf("failed to sign output %s: %v", msg, + err) + break + } + + // by the above loop, this should be valid, now sign + // again and merge. + sigScript, err = txscript.SignTxOutput( + &chaincfg.TestNet3Params, tx, i, pkScript, + hashType, mkGetKey(map[string]addressToKey{ + address.EncodeAddress(): {key, true}, + }), mkGetScript(nil), sigScript) + if err != nil { + t.Errorf("failed to sign output %s a "+ + "second time: %v", msg, err) + break + } + + err = checkScripts(msg, tx, i, sigScript, pkScript) + if err != nil { + t.Errorf("twice signed script invalid for "+ + "%s: %v", msg, err) + break + } + } + } + + // Pay to PubKey (uncompressed) + for _, hashType := range hashTypes { + for i := range tx.TxIn { + msg := fmt.Sprintf("%d:%d", hashType, i) + + key, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Errorf("failed to make privKey for %s: %v", + msg, err) + break + } + + pk := (*btcec.PublicKey)(&key.PublicKey). + SerializeUncompressed() + address, err := btcutil.NewAddressPubKey(pk, + &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make address for %s: %v", + msg, err) + break + } + + pkScript, err := txscript.PayToAddrScript(address) + if err != nil { + t.Errorf("failed to make pkscript "+ + "for %s: %v", msg, err) + } + + if err := signAndCheck(msg, tx, i, pkScript, hashType, + mkGetKey(map[string]addressToKey{ + address.EncodeAddress(): {key, false}, + }), mkGetScript(nil), []byte{}); err != nil { + t.Error(err) + break + } + } + } + + // Pay to PubKey (uncompressed) + for _, hashType := range hashTypes { + for i := range tx.TxIn { + msg := fmt.Sprintf("%d:%d", hashType, i) + + key, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Errorf("failed to make privKey for %s: %v", + msg, err) + break + } + + pk := (*btcec.PublicKey)(&key.PublicKey). + SerializeUncompressed() + address, err := btcutil.NewAddressPubKey(pk, + &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make address for %s: %v", + msg, err) + break + } + + pkScript, err := txscript.PayToAddrScript(address) + if err != nil { + t.Errorf("failed to make pkscript "+ + "for %s: %v", msg, err) + } + + sigScript, err := txscript.SignTxOutput( + &chaincfg.TestNet3Params, tx, i, pkScript, + hashType, mkGetKey(map[string]addressToKey{ + address.EncodeAddress(): {key, false}, + }), mkGetScript(nil), []byte{}) + if err != nil { + t.Errorf("failed to sign output %s: %v", msg, + err) + break + } + + // by the above loop, this should be valid, now sign + // again and merge. + sigScript, err = txscript.SignTxOutput( + &chaincfg.TestNet3Params, tx, i, pkScript, + hashType, mkGetKey(map[string]addressToKey{ + address.EncodeAddress(): {key, false}, + }), mkGetScript(nil), sigScript) + if err != nil { + t.Errorf("failed to sign output %s a "+ + "second time: %v", msg, err) + break + } + + err = checkScripts(msg, tx, i, sigScript, pkScript) + if err != nil { + t.Errorf("twice signed script invalid for "+ + "%s: %v", msg, err) + break + } + } + } + + // Pay to PubKey (compressed) + for _, hashType := range hashTypes { + for i := range tx.TxIn { + msg := fmt.Sprintf("%d:%d", hashType, i) + + key, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Errorf("failed to make privKey for %s: %v", + msg, err) + break + } + + pk := (*btcec.PublicKey)(&key.PublicKey). + SerializeCompressed() + address, err := btcutil.NewAddressPubKey(pk, + &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make address for %s: %v", + msg, err) + break + } + + pkScript, err := txscript.PayToAddrScript(address) + if err != nil { + t.Errorf("failed to make pkscript "+ + "for %s: %v", msg, err) + } + + if err := signAndCheck(msg, tx, i, pkScript, hashType, + mkGetKey(map[string]addressToKey{ + address.EncodeAddress(): {key, true}, + }), mkGetScript(nil), []byte{}); err != nil { + t.Error(err) + break + } + } + } + + // Pay to PubKey (compressed) with duplicate merge + for _, hashType := range hashTypes { + for i := range tx.TxIn { + msg := fmt.Sprintf("%d:%d", hashType, i) + + key, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Errorf("failed to make privKey for %s: %v", + msg, err) + break + } + + pk := (*btcec.PublicKey)(&key.PublicKey). + SerializeCompressed() + address, err := btcutil.NewAddressPubKey(pk, + &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make address for %s: %v", + msg, err) + break + } + + pkScript, err := txscript.PayToAddrScript(address) + if err != nil { + t.Errorf("failed to make pkscript "+ + "for %s: %v", msg, err) + } + + sigScript, err := txscript.SignTxOutput( + &chaincfg.TestNet3Params, tx, i, pkScript, + hashType, mkGetKey(map[string]addressToKey{ + address.EncodeAddress(): {key, true}, + }), mkGetScript(nil), []byte{}) + if err != nil { + t.Errorf("failed to sign output %s: %v", msg, + err) + break + } + + // by the above loop, this should be valid, now sign + // again and merge. + sigScript, err = txscript.SignTxOutput( + &chaincfg.TestNet3Params, tx, i, pkScript, + hashType, mkGetKey(map[string]addressToKey{ + address.EncodeAddress(): {key, true}, + }), mkGetScript(nil), sigScript) + if err != nil { + t.Errorf("failed to sign output %s a "+ + "second time: %v", msg, err) + break + } + + err = checkScripts(msg, tx, i, sigScript, pkScript) + if err != nil { + t.Errorf("twice signed script invalid for "+ + "%s: %v", msg, err) + break + } + } + } + + // As before, but with p2sh now. + // Pay to Pubkey Hash (uncompressed) + for _, hashType := range hashTypes { + for i := range tx.TxIn { + msg := fmt.Sprintf("%d:%d", hashType, i) + key, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Errorf("failed to make privKey for %s: %v", + msg, err) + break + } + + pk := (*btcec.PublicKey)(&key.PublicKey). + SerializeUncompressed() + address, err := btcutil.NewAddressPubKeyHash( + btcutil.Hash160(pk), &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make address for %s: %v", + msg, err) + break + } + + pkScript, err := txscript.PayToAddrScript(address) + if err != nil { + t.Errorf("failed to make pkscript "+ + "for %s: %v", msg, err) + break + } + + scriptAddr, err := btcutil.NewAddressScriptHash( + pkScript, &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make p2sh addr for %s: %v", + msg, err) + break + } + + scriptPkScript, err := txscript.PayToAddrScript( + scriptAddr) + if err != nil { + t.Errorf("failed to make script pkscript for "+ + "%s: %v", msg, err) + break + } + + if err := signAndCheck(msg, tx, i, scriptPkScript, + hashType, + mkGetKey(map[string]addressToKey{ + address.EncodeAddress(): {key, false}, + }), mkGetScript(map[string][]byte{ + scriptAddr.EncodeAddress(): pkScript, + }), []byte{}); err != nil { + t.Error(err) + break + } + } + } + + // Pay to Pubkey Hash (uncompressed) with duplicate merge + for _, hashType := range hashTypes { + for i := range tx.TxIn { + msg := fmt.Sprintf("%d:%d", hashType, i) + key, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Errorf("failed to make privKey for %s: %v", + msg, err) + break + } + + pk := (*btcec.PublicKey)(&key.PublicKey). + SerializeUncompressed() + address, err := btcutil.NewAddressPubKeyHash( + btcutil.Hash160(pk), &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make address for %s: %v", + msg, err) + break + } + + pkScript, err := txscript.PayToAddrScript(address) + if err != nil { + t.Errorf("failed to make pkscript "+ + "for %s: %v", msg, err) + break + } + + scriptAddr, err := btcutil.NewAddressScriptHash( + pkScript, &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make p2sh addr for %s: %v", + msg, err) + break + } + + scriptPkScript, err := txscript.PayToAddrScript( + scriptAddr) + if err != nil { + t.Errorf("failed to make script pkscript for "+ + "%s: %v", msg, err) + break + } + + sigScript, err := txscript.SignTxOutput( + &chaincfg.TestNet3Params, tx, i, scriptPkScript, + hashType, mkGetKey(map[string]addressToKey{ + address.EncodeAddress(): {key, false}, + }), mkGetScript(map[string][]byte{ + scriptAddr.EncodeAddress(): pkScript, + }), []byte{}) + if err != nil { + t.Errorf("failed to sign output %s: %v", msg, + err) + break + } + + // by the above loop, this should be valid, now sign + // again and merge. + sigScript, err = txscript.SignTxOutput( + &chaincfg.TestNet3Params, tx, i, scriptPkScript, + hashType, mkGetKey(map[string]addressToKey{ + address.EncodeAddress(): {key, false}, + }), mkGetScript(map[string][]byte{ + scriptAddr.EncodeAddress(): pkScript, + }), []byte{}) + if err != nil { + t.Errorf("failed to sign output %s a "+ + "second time: %v", msg, err) + break + } + + err = checkScripts(msg, tx, i, sigScript, scriptPkScript) + if err != nil { + t.Errorf("twice signed script invalid for "+ + "%s: %v", msg, err) + break + } + } + } + + // Pay to Pubkey Hash (compressed) + for _, hashType := range hashTypes { + for i := range tx.TxIn { + msg := fmt.Sprintf("%d:%d", hashType, i) + + key, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Errorf("failed to make privKey for %s: %v", + msg, err) + break + } + + pk := (*btcec.PublicKey)(&key.PublicKey). + SerializeCompressed() + address, err := btcutil.NewAddressPubKeyHash( + btcutil.Hash160(pk), &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make address for %s: %v", + msg, err) + break + } + + pkScript, err := txscript.PayToAddrScript(address) + if err != nil { + t.Errorf("failed to make pkscript "+ + "for %s: %v", msg, err) + } + + scriptAddr, err := btcutil.NewAddressScriptHash( + pkScript, &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make p2sh addr for %s: %v", + msg, err) + break + } + + scriptPkScript, err := txscript.PayToAddrScript( + scriptAddr) + if err != nil { + t.Errorf("failed to make script pkscript for "+ + "%s: %v", msg, err) + break + } + + if err := signAndCheck(msg, tx, i, scriptPkScript, + hashType, + mkGetKey(map[string]addressToKey{ + address.EncodeAddress(): {key, true}, + }), mkGetScript(map[string][]byte{ + scriptAddr.EncodeAddress(): pkScript, + }), []byte{}); err != nil { + t.Error(err) + break + } + } + } + + // Pay to Pubkey Hash (compressed) with duplicate merge + for _, hashType := range hashTypes { + for i := range tx.TxIn { + msg := fmt.Sprintf("%d:%d", hashType, i) + + key, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Errorf("failed to make privKey for %s: %v", + msg, err) + break + } + + pk := (*btcec.PublicKey)(&key.PublicKey). + SerializeCompressed() + address, err := btcutil.NewAddressPubKeyHash( + btcutil.Hash160(pk), &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make address for %s: %v", + msg, err) + break + } + + pkScript, err := txscript.PayToAddrScript(address) + if err != nil { + t.Errorf("failed to make pkscript "+ + "for %s: %v", msg, err) + } + + scriptAddr, err := btcutil.NewAddressScriptHash( + pkScript, &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make p2sh addr for %s: %v", + msg, err) + break + } + + scriptPkScript, err := txscript.PayToAddrScript( + scriptAddr) + if err != nil { + t.Errorf("failed to make script pkscript for "+ + "%s: %v", msg, err) + break + } + + sigScript, err := txscript.SignTxOutput( + &chaincfg.TestNet3Params, tx, i, scriptPkScript, + hashType, mkGetKey(map[string]addressToKey{ + address.EncodeAddress(): {key, true}, + }), mkGetScript(map[string][]byte{ + scriptAddr.EncodeAddress(): pkScript, + }), []byte{}) + if err != nil { + t.Errorf("failed to sign output %s: %v", msg, + err) + break + } + + // by the above loop, this should be valid, now sign + // again and merge. + sigScript, err = txscript.SignTxOutput( + &chaincfg.TestNet3Params, tx, i, scriptPkScript, + hashType, mkGetKey(map[string]addressToKey{ + address.EncodeAddress(): {key, true}, + }), mkGetScript(map[string][]byte{ + scriptAddr.EncodeAddress(): pkScript, + }), []byte{}) + if err != nil { + t.Errorf("failed to sign output %s a "+ + "second time: %v", msg, err) + break + } + + err = checkScripts(msg, tx, i, sigScript, scriptPkScript) + if err != nil { + t.Errorf("twice signed script invalid for "+ + "%s: %v", msg, err) + break + } + } + } + + // Pay to PubKey (uncompressed) + for _, hashType := range hashTypes { + for i := range tx.TxIn { + msg := fmt.Sprintf("%d:%d", hashType, i) + + key, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Errorf("failed to make privKey for %s: %v", + msg, err) + break + } + + pk := (*btcec.PublicKey)(&key.PublicKey). + SerializeUncompressed() + address, err := btcutil.NewAddressPubKey(pk, + &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make address for %s: %v", + msg, err) + break + } + + pkScript, err := txscript.PayToAddrScript(address) + if err != nil { + t.Errorf("failed to make pkscript "+ + "for %s: %v", msg, err) + } + + scriptAddr, err := btcutil.NewAddressScriptHash( + pkScript, &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make p2sh addr for %s: %v", + msg, err) + break + } + + scriptPkScript, err := txscript.PayToAddrScript( + scriptAddr) + if err != nil { + t.Errorf("failed to make script pkscript for "+ + "%s: %v", msg, err) + break + } + + if err := signAndCheck(msg, tx, i, scriptPkScript, + hashType, + mkGetKey(map[string]addressToKey{ + address.EncodeAddress(): {key, false}, + }), mkGetScript(map[string][]byte{ + scriptAddr.EncodeAddress(): pkScript, + }), []byte{}); err != nil { + t.Error(err) + break + } + } + } + + // Pay to PubKey (uncompressed) with duplicate merge + for _, hashType := range hashTypes { + for i := range tx.TxIn { + msg := fmt.Sprintf("%d:%d", hashType, i) + + key, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Errorf("failed to make privKey for %s: %v", + msg, err) + break + } + + pk := (*btcec.PublicKey)(&key.PublicKey). + SerializeUncompressed() + address, err := btcutil.NewAddressPubKey(pk, + &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make address for %s: %v", + msg, err) + break + } + + pkScript, err := txscript.PayToAddrScript(address) + if err != nil { + t.Errorf("failed to make pkscript "+ + "for %s: %v", msg, err) + } + + scriptAddr, err := btcutil.NewAddressScriptHash( + pkScript, &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make p2sh addr for %s: %v", + msg, err) + break + } + + scriptPkScript, err := txscript.PayToAddrScript( + scriptAddr) + if err != nil { + t.Errorf("failed to make script pkscript for "+ + "%s: %v", msg, err) + break + } + + sigScript, err := txscript.SignTxOutput( + &chaincfg.TestNet3Params, tx, i, scriptPkScript, + hashType, mkGetKey(map[string]addressToKey{ + address.EncodeAddress(): {key, false}, + }), mkGetScript(map[string][]byte{ + scriptAddr.EncodeAddress(): pkScript, + }), []byte{}) + if err != nil { + t.Errorf("failed to sign output %s: %v", msg, + err) + break + } + + // by the above loop, this should be valid, now sign + // again and merge. + sigScript, err = txscript.SignTxOutput( + &chaincfg.TestNet3Params, tx, i, scriptPkScript, + hashType, mkGetKey(map[string]addressToKey{ + address.EncodeAddress(): {key, false}, + }), mkGetScript(map[string][]byte{ + scriptAddr.EncodeAddress(): pkScript, + }), []byte{}) + if err != nil { + t.Errorf("failed to sign output %s a "+ + "second time: %v", msg, err) + break + } + + err = checkScripts(msg, tx, i, sigScript, scriptPkScript) + if err != nil { + t.Errorf("twice signed script invalid for "+ + "%s: %v", msg, err) + break + } + } + } + + // Pay to PubKey (compressed) + for _, hashType := range hashTypes { + for i := range tx.TxIn { + msg := fmt.Sprintf("%d:%d", hashType, i) + + key, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Errorf("failed to make privKey for %s: %v", + msg, err) + break + } + + pk := (*btcec.PublicKey)(&key.PublicKey). + SerializeCompressed() + address, err := btcutil.NewAddressPubKey(pk, + &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make address for %s: %v", + msg, err) + break + } + + pkScript, err := txscript.PayToAddrScript(address) + if err != nil { + t.Errorf("failed to make pkscript "+ + "for %s: %v", msg, err) + } + + scriptAddr, err := btcutil.NewAddressScriptHash( + pkScript, &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make p2sh addr for %s: %v", + msg, err) + break + } + + scriptPkScript, err := txscript.PayToAddrScript( + scriptAddr) + if err != nil { + t.Errorf("failed to make script pkscript for "+ + "%s: %v", msg, err) + break + } + + if err := signAndCheck(msg, tx, i, scriptPkScript, + hashType, + mkGetKey(map[string]addressToKey{ + address.EncodeAddress(): {key, true}, + }), mkGetScript(map[string][]byte{ + scriptAddr.EncodeAddress(): pkScript, + }), []byte{}); err != nil { + t.Error(err) + break + } + } + } + + // Pay to PubKey (compressed) + for _, hashType := range hashTypes { + for i := range tx.TxIn { + msg := fmt.Sprintf("%d:%d", hashType, i) + + key, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Errorf("failed to make privKey for %s: %v", + msg, err) + break + } + + pk := (*btcec.PublicKey)(&key.PublicKey). + SerializeCompressed() + address, err := btcutil.NewAddressPubKey(pk, + &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make address for %s: %v", + msg, err) + break + } + + pkScript, err := txscript.PayToAddrScript(address) + if err != nil { + t.Errorf("failed to make pkscript "+ + "for %s: %v", msg, err) + } + + scriptAddr, err := btcutil.NewAddressScriptHash( + pkScript, &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make p2sh addr for %s: %v", + msg, err) + break + } + + scriptPkScript, err := txscript.PayToAddrScript( + scriptAddr) + if err != nil { + t.Errorf("failed to make script pkscript for "+ + "%s: %v", msg, err) + break + } + + sigScript, err := txscript.SignTxOutput( + &chaincfg.TestNet3Params, tx, i, scriptPkScript, + hashType, mkGetKey(map[string]addressToKey{ + address.EncodeAddress(): {key, true}, + }), mkGetScript(map[string][]byte{ + scriptAddr.EncodeAddress(): pkScript, + }), []byte{}) + if err != nil { + t.Errorf("failed to sign output %s: %v", msg, + err) + break + } + + // by the above loop, this should be valid, now sign + // again and merge. + sigScript, err = txscript.SignTxOutput( + &chaincfg.TestNet3Params, tx, i, scriptPkScript, + hashType, mkGetKey(map[string]addressToKey{ + address.EncodeAddress(): {key, true}, + }), mkGetScript(map[string][]byte{ + scriptAddr.EncodeAddress(): pkScript, + }), []byte{}) + if err != nil { + t.Errorf("failed to sign output %s a "+ + "second time: %v", msg, err) + break + } + + err = checkScripts(msg, tx, i, sigScript, scriptPkScript) + if err != nil { + t.Errorf("twice signed script invalid for "+ + "%s: %v", msg, err) + break + } + } + } + + // Basic Multisig + for _, hashType := range hashTypes { + for i := range tx.TxIn { + msg := fmt.Sprintf("%d:%d", hashType, i) + + key1, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Errorf("failed to make privKey for %s: %v", + msg, err) + break + } + + pk1 := (*btcec.PublicKey)(&key1.PublicKey). + SerializeCompressed() + address1, err := btcutil.NewAddressPubKey(pk1, + &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make address for %s: %v", + msg, err) + break + } + + key2, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Errorf("failed to make privKey 2 for %s: %v", + msg, err) + break + } + + pk2 := (*btcec.PublicKey)(&key2.PublicKey). + SerializeCompressed() + address2, err := btcutil.NewAddressPubKey(pk2, + &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make address 2 for %s: %v", + msg, err) + break + } + + pkScript, err := txscript.MultiSigScript( + []*btcutil.AddressPubKey{address1, address2}, + 2) + if err != nil { + t.Errorf("failed to make pkscript "+ + "for %s: %v", msg, err) + } + + scriptAddr, err := btcutil.NewAddressScriptHash( + pkScript, &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make p2sh addr for %s: %v", + msg, err) + break + } + + scriptPkScript, err := txscript.PayToAddrScript( + scriptAddr) + if err != nil { + t.Errorf("failed to make script pkscript for "+ + "%s: %v", msg, err) + break + } + + if err := signAndCheck(msg, tx, i, scriptPkScript, + hashType, + mkGetKey(map[string]addressToKey{ + address1.EncodeAddress(): {key1, true}, + address2.EncodeAddress(): {key2, true}, + }), mkGetScript(map[string][]byte{ + scriptAddr.EncodeAddress(): pkScript, + }), []byte{}); err != nil { + t.Error(err) + break + } + } + } + + // Two part multisig, sign with one key then the other. + for _, hashType := range hashTypes { + for i := range tx.TxIn { + msg := fmt.Sprintf("%d:%d", hashType, i) + + key1, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Errorf("failed to make privKey for %s: %v", + msg, err) + break + } + + pk1 := (*btcec.PublicKey)(&key1.PublicKey). + SerializeCompressed() + address1, err := btcutil.NewAddressPubKey(pk1, + &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make address for %s: %v", + msg, err) + break + } + + key2, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Errorf("failed to make privKey 2 for %s: %v", + msg, err) + break + } + + pk2 := (*btcec.PublicKey)(&key2.PublicKey). + SerializeCompressed() + address2, err := btcutil.NewAddressPubKey(pk2, + &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make address 2 for %s: %v", + msg, err) + break + } + + pkScript, err := txscript.MultiSigScript( + []*btcutil.AddressPubKey{address1, address2}, + 2) + if err != nil { + t.Errorf("failed to make pkscript "+ + "for %s: %v", msg, err) + } + + scriptAddr, err := btcutil.NewAddressScriptHash( + pkScript, &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make p2sh addr for %s: %v", + msg, err) + break + } + + scriptPkScript, err := txscript.PayToAddrScript( + scriptAddr) + if err != nil { + t.Errorf("failed to make script pkscript for "+ + "%s: %v", msg, err) + break + } + + sigScript, err := txscript.SignTxOutput( + &chaincfg.TestNet3Params, tx, i, scriptPkScript, + hashType, mkGetKey(map[string]addressToKey{ + address1.EncodeAddress(): {key1, true}, + }), mkGetScript(map[string][]byte{ + scriptAddr.EncodeAddress(): pkScript, + }), []byte{}) + if err != nil { + t.Errorf("failed to sign output %s: %v", msg, + err) + break + } + + // Only 1 out of 2 signed, this *should* fail. + if checkScripts(msg, tx, i, sigScript, + scriptPkScript) == nil { + t.Errorf("part signed script valid for %s", msg) + break + } + + // Sign with the other key and merge + sigScript, err = txscript.SignTxOutput( + &chaincfg.TestNet3Params, tx, i, scriptPkScript, + hashType, mkGetKey(map[string]addressToKey{ + address2.EncodeAddress(): {key2, true}, + }), mkGetScript(map[string][]byte{ + scriptAddr.EncodeAddress(): pkScript, + }), sigScript) + if err != nil { + t.Errorf("failed to sign output %s: %v", msg, err) + break + } + + err = checkScripts(msg, tx, i, sigScript, + scriptPkScript) + if err != nil { + t.Errorf("fully signed script invalid for "+ + "%s: %v", msg, err) + break + } + } + } + + // Two part multisig, sign with one key then both, check key dedup + // correctly. + for _, hashType := range hashTypes { + for i := range tx.TxIn { + msg := fmt.Sprintf("%d:%d", hashType, i) + + key1, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Errorf("failed to make privKey for %s: %v", + msg, err) + break + } + + pk1 := (*btcec.PublicKey)(&key1.PublicKey). + SerializeCompressed() + address1, err := btcutil.NewAddressPubKey(pk1, + &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make address for %s: %v", + msg, err) + break + } + + key2, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Errorf("failed to make privKey 2 for %s: %v", + msg, err) + break + } + + pk2 := (*btcec.PublicKey)(&key2.PublicKey). + SerializeCompressed() + address2, err := btcutil.NewAddressPubKey(pk2, + &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make address 2 for %s: %v", + msg, err) + break + } + + pkScript, err := txscript.MultiSigScript( + []*btcutil.AddressPubKey{address1, address2}, + 2) + if err != nil { + t.Errorf("failed to make pkscript "+ + "for %s: %v", msg, err) + } + + scriptAddr, err := btcutil.NewAddressScriptHash( + pkScript, &chaincfg.TestNet3Params) + if err != nil { + t.Errorf("failed to make p2sh addr for %s: %v", + msg, err) + break + } + + scriptPkScript, err := txscript.PayToAddrScript( + scriptAddr) + if err != nil { + t.Errorf("failed to make script pkscript for "+ + "%s: %v", msg, err) + break + } + + sigScript, err := txscript.SignTxOutput( + &chaincfg.TestNet3Params, tx, i, scriptPkScript, + hashType, mkGetKey(map[string]addressToKey{ + address1.EncodeAddress(): {key1, true}, + }), mkGetScript(map[string][]byte{ + scriptAddr.EncodeAddress(): pkScript, + }), []byte{}) + if err != nil { + t.Errorf("failed to sign output %s: %v", msg, + err) + break + } + + // Only 1 out of 2 signed, this *should* fail. + if checkScripts(msg, tx, i, sigScript, + scriptPkScript) == nil { + t.Errorf("part signed script valid for %s", msg) + break + } + + // Sign with the other key and merge + sigScript, err = txscript.SignTxOutput( + &chaincfg.TestNet3Params, tx, i, scriptPkScript, + hashType, mkGetKey(map[string]addressToKey{ + address1.EncodeAddress(): {key1, true}, + address2.EncodeAddress(): {key2, true}, + }), mkGetScript(map[string][]byte{ + scriptAddr.EncodeAddress(): pkScript, + }), sigScript) + if err != nil { + t.Errorf("failed to sign output %s: %v", msg, err) + break + } + + // Now we should pass. + err = checkScripts(msg, tx, i, sigScript, + scriptPkScript) + if err != nil { + t.Errorf("fully signed script invalid for "+ + "%s: %v", msg, err) + break + } + } + } +} + +type tstInput struct { + txout *wire.TxOut + sigscriptGenerates bool + inputValidates bool + indexOutOfRange bool +} + +type tstSigScript struct { + name string + inputs []tstInput + hashType txscript.SigHashType + compress bool + scriptAtWrongIndex bool +} + +var coinbaseOutPoint = &wire.OutPoint{ + Index: (1 << 32) - 1, +} + +// Pregenerated private key, with associated public key and pkScripts +// for the uncompressed and compressed hash160. +var ( + privKeyD = []byte{0x6b, 0x0f, 0xd8, 0xda, 0x54, 0x22, 0xd0, 0xb7, + 0xb4, 0xfc, 0x4e, 0x55, 0xd4, 0x88, 0x42, 0xb3, 0xa1, 0x65, + 0xac, 0x70, 0x7f, 0x3d, 0xa4, 0x39, 0x5e, 0xcb, 0x3b, 0xb0, + 0xd6, 0x0e, 0x06, 0x92} + pubkeyX = []byte{0xb2, 0x52, 0xf0, 0x49, 0x85, 0x78, 0x03, 0x03, 0xc8, + 0x7d, 0xce, 0x51, 0x7f, 0xa8, 0x69, 0x0b, 0x91, 0x95, 0xf4, + 0xf3, 0x5c, 0x26, 0x73, 0x05, 0x05, 0xa2, 0xee, 0xbc, 0x09, + 0x38, 0x34, 0x3a} + pubkeyY = []byte{0xb7, 0xc6, 0x7d, 0xb2, 0xe1, 0xff, 0xc8, 0x43, 0x1f, + 0x63, 0x32, 0x62, 0xaa, 0x60, 0xc6, 0x83, 0x30, 0xbd, 0x24, + 0x7e, 0xef, 0xdb, 0x6f, 0x2e, 0x8d, 0x56, 0xf0, 0x3c, 0x9f, + 0x6d, 0xb6, 0xf8} + uncompressedPkScript = []byte{0x76, 0xa9, 0x14, 0xd1, 0x7c, 0xb5, + 0xeb, 0xa4, 0x02, 0xcb, 0x68, 0xe0, 0x69, 0x56, 0xbf, 0x32, + 0x53, 0x90, 0x0e, 0x0a, 0x86, 0xc9, 0xfa, 0x88, 0xac} + compressedPkScript = []byte{0x76, 0xa9, 0x14, 0x27, 0x4d, 0x9f, 0x7f, + 0x61, 0x7e, 0x7c, 0x7a, 0x1c, 0x1f, 0xb2, 0x75, 0x79, 0x10, + 0x43, 0x65, 0x68, 0x27, 0x9d, 0x86, 0x88, 0xac} + shortPkScript = []byte{0x76, 0xa9, 0x14, 0xd1, 0x7c, 0xb5, + 0xeb, 0xa4, 0x02, 0xcb, 0x68, 0xe0, 0x69, 0x56, 0xbf, 0x32, + 0x53, 0x90, 0x0e, 0x0a, 0x88, 0xac} + uncompressedAddrStr = "1L6fd93zGmtzkK6CsZFVVoCwzZV3MUtJ4F" + compressedAddrStr = "14apLppt9zTq6cNw8SDfiJhk9PhkZrQtYZ" +) + +// Pretend output amounts. +const coinbaseVal = 2500000000 +const fee = 5000000 + +var sigScriptTests = []tstSigScript{ + { + name: "one input uncompressed", + inputs: []tstInput{ + { + txout: wire.NewTxOut(coinbaseVal, uncompressedPkScript), + sigscriptGenerates: true, + inputValidates: true, + indexOutOfRange: false, + }, + }, + hashType: txscript.SigHashAll, + compress: false, + scriptAtWrongIndex: false, + }, + { + name: "two inputs uncompressed", + inputs: []tstInput{ + { + txout: wire.NewTxOut(coinbaseVal, uncompressedPkScript), + sigscriptGenerates: true, + inputValidates: true, + indexOutOfRange: false, + }, + { + txout: wire.NewTxOut(coinbaseVal+fee, uncompressedPkScript), + sigscriptGenerates: true, + inputValidates: true, + indexOutOfRange: false, + }, + }, + hashType: txscript.SigHashAll, + compress: false, + scriptAtWrongIndex: false, + }, + { + name: "one input compressed", + inputs: []tstInput{ + { + txout: wire.NewTxOut(coinbaseVal, compressedPkScript), + sigscriptGenerates: true, + inputValidates: true, + indexOutOfRange: false, + }, + }, + hashType: txscript.SigHashAll, + compress: true, + scriptAtWrongIndex: false, + }, + { + name: "two inputs compressed", + inputs: []tstInput{ + { + txout: wire.NewTxOut(coinbaseVal, compressedPkScript), + sigscriptGenerates: true, + inputValidates: true, + indexOutOfRange: false, + }, + { + txout: wire.NewTxOut(coinbaseVal+fee, compressedPkScript), + sigscriptGenerates: true, + inputValidates: true, + indexOutOfRange: false, + }, + }, + hashType: txscript.SigHashAll, + compress: true, + scriptAtWrongIndex: false, + }, + { + name: "hashType SigHashNone", + inputs: []tstInput{ + { + txout: wire.NewTxOut(coinbaseVal, uncompressedPkScript), + sigscriptGenerates: true, + inputValidates: true, + indexOutOfRange: false, + }, + }, + hashType: txscript.SigHashNone, + compress: false, + scriptAtWrongIndex: false, + }, + { + name: "hashType SigHashSingle", + inputs: []tstInput{ + { + txout: wire.NewTxOut(coinbaseVal, uncompressedPkScript), + sigscriptGenerates: true, + inputValidates: true, + indexOutOfRange: false, + }, + }, + hashType: txscript.SigHashSingle, + compress: false, + scriptAtWrongIndex: false, + }, + { + name: "hashType SigHashAnyoneCanPay", + inputs: []tstInput{ + { + txout: wire.NewTxOut(coinbaseVal, uncompressedPkScript), + sigscriptGenerates: true, + inputValidates: true, + indexOutOfRange: false, + }, + }, + hashType: txscript.SigHashAnyOneCanPay, + compress: false, + scriptAtWrongIndex: false, + }, + { + name: "hashType non-standard", + inputs: []tstInput{ + { + txout: wire.NewTxOut(coinbaseVal, uncompressedPkScript), + sigscriptGenerates: true, + inputValidates: true, + indexOutOfRange: false, + }, + }, + hashType: 0x04, + compress: false, + scriptAtWrongIndex: false, + }, + { + name: "invalid compression", + inputs: []tstInput{ + { + txout: wire.NewTxOut(coinbaseVal, uncompressedPkScript), + sigscriptGenerates: true, + inputValidates: false, + indexOutOfRange: false, + }, + }, + hashType: txscript.SigHashAll, + compress: true, + scriptAtWrongIndex: false, + }, + { + name: "short PkScript", + inputs: []tstInput{ + { + txout: wire.NewTxOut(coinbaseVal, shortPkScript), + sigscriptGenerates: false, + indexOutOfRange: false, + }, + }, + hashType: txscript.SigHashAll, + compress: false, + scriptAtWrongIndex: false, + }, + { + name: "valid script at wrong index", + inputs: []tstInput{ + { + txout: wire.NewTxOut(coinbaseVal, uncompressedPkScript), + sigscriptGenerates: true, + inputValidates: true, + indexOutOfRange: false, + }, + { + txout: wire.NewTxOut(coinbaseVal+fee, uncompressedPkScript), + sigscriptGenerates: true, + inputValidates: true, + indexOutOfRange: false, + }, + }, + hashType: txscript.SigHashAll, + compress: false, + scriptAtWrongIndex: true, + }, + { + name: "index out of range", + inputs: []tstInput{ + { + txout: wire.NewTxOut(coinbaseVal, uncompressedPkScript), + sigscriptGenerates: true, + inputValidates: true, + indexOutOfRange: false, + }, + { + txout: wire.NewTxOut(coinbaseVal+fee, uncompressedPkScript), + sigscriptGenerates: true, + inputValidates: true, + indexOutOfRange: false, + }, + }, + hashType: txscript.SigHashAll, + compress: false, + scriptAtWrongIndex: true, + }, +} + +// Test the sigscript generation for valid and invalid inputs, all +// hashTypes, and with and without compression. This test creates +// sigscripts to spend fake coinbase inputs, as sigscripts cannot be +// created for the MsgTxs in txTests, since they come from the blockchain +// and we don't have the private keys. +func TestSignatureScript(t *testing.T) { + t.Parallel() + + privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), privKeyD) + +nexttest: + for i := range sigScriptTests { + tx := wire.NewMsgTx() + + output := wire.NewTxOut(500, []byte{txscript.OP_RETURN}) + tx.AddTxOut(output) + + for _ = range sigScriptTests[i].inputs { + txin := wire.NewTxIn(coinbaseOutPoint, nil) + tx.AddTxIn(txin) + } + + var script []byte + var err error + for j := range tx.TxIn { + var idx int + if sigScriptTests[i].inputs[j].indexOutOfRange { + t.Errorf("at test %v", sigScriptTests[i].name) + idx = len(sigScriptTests[i].inputs) + } else { + idx = j + } + script, err = txscript.SignatureScript(tx, idx, + sigScriptTests[i].inputs[j].txout.PkScript, + sigScriptTests[i].hashType, privKey, + sigScriptTests[i].compress) + + if (err == nil) != sigScriptTests[i].inputs[j].sigscriptGenerates { + if err == nil { + t.Errorf("passed test '%v' incorrectly", + sigScriptTests[i].name) + } else { + t.Errorf("failed test '%v': %v", + sigScriptTests[i].name, err) + } + continue nexttest + } + if !sigScriptTests[i].inputs[j].sigscriptGenerates { + // done with this test + continue nexttest + } + + tx.TxIn[j].SignatureScript = script + } + + // If testing using a correct sigscript but for an incorrect + // index, use last input script for first input. Requires > 0 + // inputs for test. + if sigScriptTests[i].scriptAtWrongIndex { + tx.TxIn[0].SignatureScript = script + sigScriptTests[i].inputs[0].inputValidates = false + } + + // Validate tx input scripts + scriptFlags := txscript.ScriptBip16 | txscript.ScriptVerifyDERSignatures + for j := range tx.TxIn { + vm, err := txscript.NewEngine(sigScriptTests[i]. + inputs[j].txout.PkScript, tx, j, scriptFlags) + if err != nil { + t.Errorf("cannot create script vm for test %v: %v", + sigScriptTests[i].name, err) + continue nexttest + } + err = vm.Execute() + if (err == nil) != sigScriptTests[i].inputs[j].inputValidates { + if err == nil { + t.Errorf("passed test '%v' validation incorrectly: %v", + sigScriptTests[i].name, err) + } else { + t.Errorf("failed test '%v' validation: %v", + sigScriptTests[i].name, err) + } + continue nexttest + } + } + } +}