diff --git a/txscript/engine.go b/txscript/engine.go index 0e38be3d..9bea6861 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -645,15 +645,19 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags } // The clean stack flag (ScriptVerifyCleanStack) is not allowed without - // the pay-to-script-hash (P2SH) evaluation (ScriptBip16) flag. + // either the the pay-to-script-hash (P2SH) evaluation (ScriptBip16) + // flag or the Segregated Witness (ScriptVerifyWitness) flag. // // Recall that evaluating a P2SH script without the flag set results in - // non-P2SH evaluation which leaves the P2SH inputs on the stack. Thus, - // allowing the clean stack flag without the P2SH flag would make it - // possible to have a situation where P2SH would not be a soft fork when - // it should be. - vm := Engine{flags: flags, sigCache: sigCache} - if vm.hasFlag(ScriptVerifyCleanStack) && !vm.hasFlag(ScriptBip16) { + // non-P2SH evaluation which leaves the P2SH inputs on the stack. + // Thus, allowing the clean stack flag without the P2SH flag would make + // it possible to have a situation where P2SH would not be a soft fork + // when it should be. The same goes for segwit which will pull in + // additional scripts for execution from the witness stack. + vm := Engine{flags: flags, sigCache: sigCache, hashCache: hashCache, + inputAmount: inputAmount} + if vm.hasFlag(ScriptVerifyCleanStack) && (!vm.hasFlag(ScriptBip16) && + !vm.hasFlag(ScriptVerifyWitness)) { return nil, scriptError(ErrInvalidFlags, "invalid flags combination") } @@ -731,14 +735,17 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags witProgram = scriptPubKey case len(tx.TxIn[txIdx].Witness) != 0 && vm.bip16: + pops, err := parseScript(scriptSig) + if err != nil { + return nil, err + } + // The sigScript MUST be *exactly* a single canonical // data push of the witness program, otherwise we // reintroduce malleability. - dataPush := vm.scripts[0][0] - if len(vm.scripts[0]) == 1 && canonicalPush(dataPush) && - IsWitnessProgram(dataPush.data) { - - witProgram = dataPush.data + if len(pops) == 1 && canonicalPush(pops[0]) && + IsWitnessProgram(pops[0].data) { + witProgram = pops[0].data } else { errStr := "signature script for witness " + "nested p2sh is not canonical" @@ -752,7 +759,6 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags if err != nil { return nil, err } - vm.witness = true } else { // If we didn't find a witness program in either the // pkScript or as a datapush within the sigScript, then diff --git a/txscript/script.go b/txscript/script.go index 30dc9337..0f166e67 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -70,6 +70,98 @@ func IsPayToScriptHash(script []byte) bool { return isScriptHash(pops) } +// isWitnessScriptHash returns true if the passed script is a +// pay-to-witness-script-hash transaction, false otherwise. +func isWitnessScriptHash(pops []parsedOpcode) bool { + return len(pops) == 2 && + pops[0].opcode.value == OP_0 && + pops[1].opcode.value == OP_DATA_32 +} + +// IsPayToWitnessScriptHash returns true if the is in the standard +// pay-to-witness-script-hash (P2WSH) format, false otherwise. +func IsPayToWitnessScriptHash(script []byte) bool { + pops, err := parseScript(script) + if err != nil { + return false + } + return isWitnessScriptHash(pops) +} + +// IsPayToWitnessPubKeyHash returns true if the is in the standard +// pay-to-witness-pubkey-hash (P2WKH) format, false otherwise. +func IsPayToWitnessPubKeyHash(script []byte) bool { + pops, err := parseScript(script) + if err != nil { + return false + } + return isWitnessPubKeyHash(pops) +} + +// isWitnessPubKeyHash returns true if the passed script is a +// pay-to-witness-pubkey-hash, and false otherwise. +func isWitnessPubKeyHash(pops []parsedOpcode) bool { + return len(pops) == 2 && + pops[0].opcode.value == OP_0 && + pops[1].opcode.value == OP_DATA_20 +} + +// IsWitnessProgram returns true if the passed script is a valid witness +// program which is encoded according to the passed witness program version. A +// witness program must be a small integer (from 0-16), followed by 2-40 bytes +// of pushed data. +func IsWitnessProgram(script []byte) bool { + // The length of the script must be between 4 and 42 bytes. The + // smallest program is the witness version, followed by a data push of + // 2 bytes. The largest allowed witness program has a data push of + // 40-bytes. + if len(script) < 4 || len(script) > 42 { + return false + } + + pops, err := parseScript(script) + if err != nil { + return false + } + + return isWitnessProgram(pops) +} + +// isWitnessProgram returns true if the passed script is a witness program, and +// false otherwise. A witness program MUST adhere to the following constraints: +// there must be excatly two pops (program version and the program itself), the +// first opcode MUST be a small integer (0-16), the push data MUST be +// cannonical, and finally the size of the push data must be between 2 and 40 +// bytes. +func isWitnessProgram(pops []parsedOpcode) bool { + return len(pops) == 2 && + isSmallInt(pops[0].opcode) && + canonicalPush(pops[1]) && + (len(pops[1].data) >= 2 && len(pops[1].data) <= 40) +} + +// ExtractWitnessProgramInfo attempts to extract the witness program version, +// as well as the witness program itself from the passed script. +func ExtractWitnessProgramInfo(script []byte) (int, []byte, error) { + pops, err := parseScript(script) + if err != nil { + return 0, nil, err + } + + // If at this point, the scripts doesn't resemble a witness program, + // then we'll exit early as there isn't a valid version or program to + // extract. + if !isWitnessProgram(pops) { + return 0, nil, fmt.Errorf("script is not a witness program, " + + "unable to extract version or witness program") + } + + witnessVersion := asSmallInt(pops[0].opcode) + witnessProgram := pops[1].data + + return witnessVersion, witnessProgram, nil +} + // isPushOnly returns true if the script only pushes data, false otherwise. func isPushOnly(pops []parsedOpcode) bool { // NOTE: This function does NOT verify opcodes directly since it is diff --git a/txscript/script_test.go b/txscript/script_test.go index 1ffd0711..3c16d8a9 100644 --- a/txscript/script_test.go +++ b/txscript/script_test.go @@ -4085,12 +4085,44 @@ func TestIsPayToScriptHash(t *testing.T) { shouldBe := (test.class == ScriptHashTy) p2sh := IsPayToScriptHash(script) if p2sh != shouldBe { - t.Errorf("%s: epxected p2sh %v, got %v", test.name, + t.Errorf("%s: expected p2sh %v, got %v", test.name, shouldBe, p2sh) } } } +// TestIsPayToWitnessScriptHash ensures the IsPayToWitnessScriptHash function +// returns the expected results for all the scripts in scriptClassTests. +func TestIsPayToWitnessScriptHash(t *testing.T) { + t.Parallel() + + for _, test := range scriptClassTests { + script := mustParseShortForm(test.script) + shouldBe := (test.class == WitnessV0ScriptHashTy) + p2wsh := IsPayToWitnessScriptHash(script) + if p2wsh != shouldBe { + t.Errorf("%s: expected p2wsh %v, got %v", test.name, + shouldBe, p2wsh) + } + } +} + +// TestIsPayToWitnessPubKeyHash ensures the IsPayToWitnessPubKeyHash function +// returns the expected results for all the scripts in scriptClassTests. +func TestIsPayToWitnessPubKeyHash(t *testing.T) { + t.Parallel() + + for _, test := range scriptClassTests { + script := mustParseShortForm(test.script) + shouldBe := (test.class == WitnessV0PubKeyHashTy) + p2wkh := IsPayToWitnessPubKeyHash(script) + if p2wkh != shouldBe { + t.Errorf("%s: expected p2wkh %v, got %v", test.name, + shouldBe, p2wkh) + } + } +} + // TestHasCanonicalPushes ensures the canonicalPush function properly determines // what is considered a canonical push for the purposes of removeOpcodeByData. func TestHasCanonicalPushes(t *testing.T) { diff --git a/txscript/standard.go b/txscript/standard.go index 29472f6c..14ea54f4 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" ) @@ -44,23 +45,27 @@ type ScriptClass byte // Classes of script payment known about in the blockchain. const ( - NonStandardTy ScriptClass = iota // None of the recognized forms. - PubKeyTy // Pay pubkey. - PubKeyHashTy // Pay pubkey hash. - ScriptHashTy // Pay to script hash. - MultiSigTy // Multi signature. - NullDataTy // Empty data-only (provably prunable). + NonStandardTy ScriptClass = iota // None of the recognized forms. + PubKeyTy // Pay pubkey. + PubKeyHashTy // Pay pubkey hash. + WitnessV0PubKeyHashTy // Pay witness pubkey hash. + ScriptHashTy // Pay to script hash. + WitnessV0ScriptHashTy // Pay to witness script hash. + MultiSigTy // Multi signature. + NullDataTy // Empty data-only (provably prunable). ) // scriptClassToName houses the human-readable strings which describe each // script class. var scriptClassToName = []string{ - NonStandardTy: "nonstandard", - PubKeyTy: "pubkey", - PubKeyHashTy: "pubkeyhash", - ScriptHashTy: "scripthash", - MultiSigTy: "multisig", - NullDataTy: "nulldata", + NonStandardTy: "nonstandard", + PubKeyTy: "pubkey", + PubKeyHashTy: "pubkeyhash", + WitnessV0PubKeyHashTy: "witness_v0_keyhash", + ScriptHashTy: "scripthash", + WitnessV0ScriptHashTy: "witness_v0_scripthash", + MultiSigTy: "multisig", + NullDataTy: "nulldata", } // String implements the Stringer interface by returning the name of @@ -153,8 +158,12 @@ func typeOfScript(pops []parsedOpcode) ScriptClass { return PubKeyTy } else if isPubkeyHash(pops) { return PubKeyHashTy + } else if isWitnessPubKeyHash(pops) { + return WitnessV0PubKeyHashTy } else if isScriptHash(pops) { return ScriptHashTy + } else if isWitnessScriptHash(pops) { + return WitnessV0ScriptHashTy } else if isMultiSig(pops) { return MultiSigTy } else if isNullData(pops) { @@ -187,10 +196,17 @@ func expectedInputs(pops []parsedOpcode, class ScriptClass) int { case PubKeyHashTy: return 2 + case WitnessV0PubKeyHashTy: + return 2 + case ScriptHashTy: // Not including script. That is handled by the caller. return 1 + case WitnessV0ScriptHashTy: + // Not including script. That is handled by the caller. + return 1 + case MultiSigTy: // Standard multisig has a push a small number for the number // of sigs and number of keys. Check the first push instruction @@ -231,7 +247,9 @@ type ScriptInfo struct { // pair. It will error if the pair is in someway invalid such that they can not // be analysed, i.e. if they do not parse or the pkScript is not a push-only // script -func CalcScriptInfo(sigScript, pkScript []byte, bip16 bool) (*ScriptInfo, error) { +func CalcScriptInfo(sigScript, pkScript []byte, witness wire.TxWitness, + bip16, segwit bool) (*ScriptInfo, error) { + sigPops, err := parseScript(sigScript) if err != nil { return nil, err @@ -254,11 +272,9 @@ func CalcScriptInfo(sigScript, pkScript []byte, bip16 bool) (*ScriptInfo, error) si.ExpectedInputs = expectedInputs(pkPops, si.PkScriptClass) - // All entries pushed to stack (or are OP_RESERVED and exec will fail). - si.NumInputs = len(sigPops) - + switch { // Count sigops taking into account pay-to-script-hash. - if si.PkScriptClass == ScriptHashTy && bip16 { + case si.PkScriptClass == ScriptHashTy && bip16 && !segwit: // The pay-to-hash-script is the final data push of the // signature script. script := sigPops[len(sigPops)-1].data @@ -274,8 +290,62 @@ func CalcScriptInfo(sigScript, pkScript []byte, bip16 bool) (*ScriptInfo, error) si.ExpectedInputs += shInputs } si.SigOps = getSigOpCount(shPops, true) - } else { + + // All entries pushed to stack (or are OP_RESERVED and exec + // will fail). + si.NumInputs = len(sigPops) + + // If segwit is active, and this is a regular p2wkh output, then we'll + // treat the script as a p2pkh output in essence. + case si.PkScriptClass == WitnessV0PubKeyHashTy && segwit: + + si.SigOps = GetWitnessSigOpCount(sigScript, pkScript, witness) + si.NumInputs = len(witness) + + // We'll attempt to detect the nested p2sh case so we can accurately + // count the signature operations involved. + case si.PkScriptClass == ScriptHashTy && + IsWitnessProgram(sigScript[1:]) && bip16 && segwit: + + // Extract the pushed witness program from the sigScript so we + // can determine the number of expected inputs. + pkPops, _ := parseScript(sigScript[1:]) + shInputs := expectedInputs(pkPops, typeOfScript(pkPops)) + if shInputs == -1 { + si.ExpectedInputs = -1 + } else { + si.ExpectedInputs += shInputs + } + + si.SigOps = GetWitnessSigOpCount(sigScript, pkScript, witness) + + si.NumInputs = len(witness) + si.NumInputs += len(sigPops) + + // If segwit is active, and this is a p2wsh output, then we'll need to + // examine the witness script to generate accurate script info. + case si.PkScriptClass == WitnessV0ScriptHashTy && segwit: + // The witness script is the final element of the witness + // stack. + witnessScript := witness[len(witness)-1] + pops, _ := parseScript(witnessScript) + + shInputs := expectedInputs(pops, typeOfScript(pops)) + if shInputs == -1 { + si.ExpectedInputs = -1 + } else { + si.ExpectedInputs += shInputs + } + + si.SigOps = GetWitnessSigOpCount(sigScript, pkScript, witness) + si.NumInputs = len(witness) + + default: si.SigOps = getSigOpCount(pkPops, true) + + // All entries pushed to stack (or are OP_RESERVED and exec + // will fail). + si.NumInputs = len(sigPops) } return si, nil @@ -316,6 +386,12 @@ func payToPubKeyHashScript(pubKeyHash []byte) ([]byte, error) { Script() } +// payToWitnessPubKeyHashScript creates a new script to pay to a version 0 +// pubkey hash witness program. The passed hash is expected to be valid. +func payToWitnessPubKeyHashScript(pubKeyHash []byte) ([]byte, error) { + return NewScriptBuilder().AddOp(OP_0).AddData(pubKeyHash).Script() +} + // payToScriptHashScript creates a new script to pay a transaction output to a // script hash. It is expected that the input is a valid hash. func payToScriptHashScript(scriptHash []byte) ([]byte, error) { @@ -323,6 +399,12 @@ func payToScriptHashScript(scriptHash []byte) ([]byte, error) { AddOp(OP_EQUAL).Script() } +// payToWitnessPubKeyHashScript creates a new script to pay to a version 0 +// script hash witness program. The passed hash is expected to be valid. +func payToWitnessScriptHashScript(scriptHash []byte) ([]byte, error) { + return NewScriptBuilder().AddOp(OP_0).AddData(scriptHash).Script() +} + // payToPubkeyScript creates a new script to pay a transaction output to a // public key. It is expected that the input is a valid pubkey. func payToPubKeyScript(serializedPubKey []byte) ([]byte, error) { @@ -356,6 +438,17 @@ func PayToAddrScript(addr btcutil.Address) ([]byte, error) { nilAddrErrStr) } return payToPubKeyScript(addr.ScriptAddress()) + + case *btcutil.AddressWitnessPubKeyHash: + if addr == nil { + return nil, ErrUnsupportedAddress + } + return payToWitnessPubKeyHashScript(addr.ScriptAddress()) + case *btcutil.AddressWitnessScriptHash: + if addr == nil { + return nil, ErrUnsupportedAddress + } + return payToWitnessScriptHashScript(addr.ScriptAddress()) } str := fmt.Sprintf("unable to generate payment script for unsupported "+ @@ -446,6 +539,18 @@ func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (Script addrs = append(addrs, addr) } + case WitnessV0PubKeyHashTy: + // A pay-to-witness-pubkey-hash script is of thw form: + // OP_0 <20-byte hash> + // Therefore, the pubkey hash is the second item on the stack. + // Skip the pubkey hash if it's invalid for some reason. + requiredSigs = 1 + addr, err := btcutil.NewAddressWitnessPubKeyHash(pops[1].data, + chainParams) + if err == nil { + addrs = append(addrs, addr) + } + case PubKeyTy: // A pay-to-pubkey script is of the form: // OP_CHECKSIG @@ -469,6 +574,18 @@ func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (Script addrs = append(addrs, addr) } + case WitnessV0ScriptHashTy: + // A pay-to-witness-script-hash script is of the form: + // OP_0 <32-byte hash> + // Therefore, the script hash is the second item on the stack. + // Skip the script hash if it's invalid for some reason. + requiredSigs = 1 + addr, err := btcutil.NewAddressWitnessScriptHash(pops[1].data, + chainParams) + if err == nil { + addrs = append(addrs, addr) + } + case MultiSigTy: // A multi-signature script is of the form: // ... OP_CHECKMULTISIG diff --git a/txscript/standard_test.go b/txscript/standard_test.go index 917270af..02826ab5 100644 --- a/txscript/standard_test.go +++ b/txscript/standard_test.go @@ -6,10 +6,12 @@ package txscript import ( "bytes" + "encoding/hex" "reflect" "testing" "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" ) @@ -376,10 +378,14 @@ func TestCalcScriptInfo(t *testing.T) { t.Parallel() tests := []struct { - name string - sigScript string - pkScript string - bip16 bool + name string + sigScript string + pkScript string + witness []string + + bip16 bool + segwit bool + scriptInfo ScriptInfo scriptInfoErr error }{ @@ -456,12 +462,91 @@ func TestCalcScriptInfo(t *testing.T) { SigOps: 3, }, }, + { + // A v0 p2wkh spend. + name: "p2wkh script", + pkScript: "OP_0 DATA_20 0x365ab47888e150ff46f8d51bce36dcd680f1283f", + witness: []string{ + "3045022100ee9fe8f9487afa977" + + "6647ebcf0883ce0cd37454d7ce19889d34ba2c9" + + "9ce5a9f402200341cb469d0efd3955acb9e46" + + "f568d7e2cc10f9084aaff94ced6dc50a59134ad01", + "03f0000d0639a22bfaf217e4c9428" + + "9c2b0cc7fa1036f7fd5d9f61a9d6ec153100e", + }, + segwit: true, + scriptInfo: ScriptInfo{ + PkScriptClass: WitnessV0PubKeyHashTy, + NumInputs: 2, + ExpectedInputs: 2, + SigOps: 1, + }, + }, + { + // Nested p2sh v0 + name: "p2wkh nested inside p2sh", + pkScript: "HASH160 DATA_20 " + + "0xb3a84b564602a9d68b4c9f19c2ea61458ff7826c EQUAL", + sigScript: "DATA_22 0x0014ad0ffa2e387f07e7ead14dc56d5a97dbd6ff5a23", + witness: []string{ + "3045022100cb1c2ac1ff1d57d" + + "db98f7bdead905f8bf5bcc8641b029ce8eef25" + + "c75a9e22a4702203be621b5c86b771288706be5" + + "a7eee1db4fceabf9afb7583c1cc6ee3f8297b21201", + "03f0000d0639a22bfaf217e4c9" + + "4289c2b0cc7fa1036f7fd5d9f61a9d6ec153100e", + }, + segwit: true, + bip16: true, + scriptInfo: ScriptInfo{ + PkScriptClass: ScriptHashTy, + NumInputs: 3, + ExpectedInputs: 3, + SigOps: 1, + }, + }, + { + // A v0 p2wsh spend. + name: "p2wsh spend of a p2wkh witness script", + pkScript: "0 DATA_32 0xe112b88a0cd87ba387f44" + + "9d443ee2596eb353beb1f0351ab2cba8909d875db23", + witness: []string{ + "3045022100cb1c2ac1ff1d57d" + + "db98f7bdead905f8bf5bcc8641b029ce8eef25" + + "c75a9e22a4702203be621b5c86b771288706be5" + + "a7eee1db4fceabf9afb7583c1cc6ee3f8297b21201", + "03f0000d0639a22bfaf217e4c9" + + "4289c2b0cc7fa1036f7fd5d9f61a9d6ec153100e", + "76a914064977cb7b4a2e0c9680df0ef696e9e0e296b39988ac", + }, + segwit: true, + scriptInfo: ScriptInfo{ + PkScriptClass: WitnessV0ScriptHashTy, + NumInputs: 3, + ExpectedInputs: 3, + SigOps: 1, + }, + }, } for _, test := range tests { sigScript := mustParseShortForm(test.sigScript) pkScript := mustParseShortForm(test.pkScript) - si, err := CalcScriptInfo(sigScript, pkScript, test.bip16) + + var witness wire.TxWitness + + for _, witElement := range test.witness { + wit, err := hex.DecodeString(witElement) + if err != nil { + t.Fatalf("unable to decode witness "+ + "element: %v", err) + } + + witness = append(witness, wit) + } + + si, err := CalcScriptInfo(sigScript, pkScript, witness, + test.bip16, test.segwit) if e := tstCheckScriptError(err, test.scriptInfoErr); e != nil { t.Errorf("scriptinfo test %q: %v", test.name, e) continue @@ -945,6 +1030,20 @@ var scriptClassTests = []struct { "3 CHECKMULTISIG", class: NonStandardTy, }, + + // New standard segwit script templates. + { + // A pay to witness pub key hash pk script. + name: "Pay To Witness PubkeyHash", + script: "0 DATA_20 0x1d0f172a0ecb48aee1be1f2687d2963ae33f71a1", + class: WitnessV0PubKeyHashTy, + }, + { + // A pay to witness scripthash pk script. + name: "Pay To Witness Scripthash", + script: "0 DATA_32 0x9f96ade4b41d5433f4eda31e1738ec2b36f6e7d1420d94a6af99801a88f7f7ff", + class: WitnessV0ScriptHashTy, + }, } // TestScriptClass ensures all the scripts in scriptClassTests have the expected @@ -988,11 +1087,21 @@ func TestStringifyClass(t *testing.T) { class: PubKeyHashTy, stringed: "pubkeyhash", }, + { + name: "witnesspubkeyhash", + class: WitnessV0PubKeyHashTy, + stringed: "witness_v0_keyhash", + }, { name: "scripthash", class: ScriptHashTy, stringed: "scripthash", }, + { + name: "witnessscripthash", + class: WitnessV0ScriptHashTy, + stringed: "witness_v0_scripthash", + }, { name: "multisigty", class: MultiSigTy,