BIP0141+txscript: awareness of new standard script templates, add helper funcs

This commit introduces a series of internal and external helper
functions which enable the txscript package to be aware of the new
standard script templates introduced as part of BIP0141. The two new
standard script templates recognized are pay-to-witness-key-hash
(P2WKH) and pay-to-witness-script-hash (P2WSH).
This commit is contained in:
Olaoluwa Osuntokun 2016-10-18 17:45:40 -07:00 committed by Dave Collins
parent 98cae74275
commit 469e53ca27
5 changed files with 393 additions and 37 deletions

View file

@ -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

View file

@ -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

View file

@ -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) {

View file

@ -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:
// <pubkey> 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:
// <numsigs> <pubkey> <pubkey> <pubkey>... <numpubkeys> OP_CHECKMULTISIG

View file

@ -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,