Skip to content

Commit

Permalink
Update script builder for BIP0062 and enforce limits.
Browse files Browse the repository at this point in the history
BIP0062 defines specific rules and canonical encodings for data pushes.

The existing script builder code already conformed to all but one of the
canonical data push rules that was added after it was originally
implemented (adding a single byte of 0x81 must be converted to
OP_1NEGATE).  This commit implements that case and expands the existing
tests to explicitly cover all cases mentioned in BIP0062.

In addition, as a part of this change, the AddData function has been
modified so that any attempt to push more than the maximum script element
size bytes (520) in one push or any pushes the would cause the script to
exceed the maximum script bytes allowed by the script engine (10000) will
result in the final call to the Script function to only return the script
up to the point of the first error along with the error. This change
should have little effect on existing callers since they are almost
positively not creating scripts which violate these rules as they could
never be executed, however it does mean they need to check the new error
return.

Since the regression tests intentionally need to be able to exceed that
limit, a new function named AddFullData has been added which does not
enforce the limits, but still provides canonical encoding of the pushed
data.

Note that this commit does not affect consensus rules nor modify the
script engine.

Also, the tests have been marked so they can run in parallel.
  • Loading branch information
davecgh committed Jan 29, 2015
1 parent 251d0fc commit af5cbe4
Show file tree
Hide file tree
Showing 5 changed files with 481 additions and 73 deletions.
8 changes: 6 additions & 2 deletions internal_test.go
Expand Up @@ -18,6 +18,10 @@ import (
"github.com/btcsuite/btcwire"
)

// TstMaxScriptSize makes the internal maxScriptSize constant available to the
// test package.
const TstMaxScriptSize = maxScriptSize

// this file is present to export some internal interfaces so that we can
// test them reliably.

Expand Down Expand Up @@ -3781,15 +3785,15 @@ func ParseShortForm(script string) ([]byte, error) {
builder.script = append(builder.script, bts...)
} else if len(tok) >= 2 &&
tok[0] == '\'' && tok[len(tok)-1] == '\'' {
builder.AddData([]byte(tok[1 : len(tok)-1]))
builder.AddFullData([]byte(tok[1 : len(tok)-1]))
} else if opcode, ok := ops[tok]; ok {
builder.AddOp(opcode.value)
} else {
return nil, fmt.Errorf("bad token \"%s\"", tok)
}

}
return builder.Script(), nil
return builder.Script()
}

func TestBitcoindInvalidTests(t *testing.T) {
Expand Down
29 changes: 16 additions & 13 deletions script.go
Expand Up @@ -1019,22 +1019,22 @@ func getSigOpCount(pops []parsedOpcode, precise bool) int {
// payToPubKeyHashScript creates a new script to pay a transaction
// output to a 20-byte pubkey hash. It is expected that the input is a valid
// hash.
func payToPubKeyHashScript(pubKeyHash []byte) []byte {
func payToPubKeyHashScript(pubKeyHash []byte) ([]byte, error) {
return NewScriptBuilder().AddOp(OP_DUP).AddOp(OP_HASH160).
AddData(pubKeyHash).AddOp(OP_EQUALVERIFY).AddOp(OP_CHECKSIG).
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 {
func payToScriptHashScript(scriptHash []byte) ([]byte, error) {
return NewScriptBuilder().AddOp(OP_HASH160).AddData(scriptHash).
AddOp(OP_EQUAL).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 {
func payToPubKeyScript(serializedPubKey []byte) ([]byte, error) {
return NewScriptBuilder().AddData(serializedPubKey).
AddOp(OP_CHECKSIG).Script()
}
Expand All @@ -1047,19 +1047,19 @@ func PayToAddrScript(addr btcutil.Address) ([]byte, error) {
if addr == nil {
return nil, ErrUnsupportedAddress
}
return payToPubKeyHashScript(addr.ScriptAddress()), nil
return payToPubKeyHashScript(addr.ScriptAddress())

case *btcutil.AddressScriptHash:
if addr == nil {
return nil, ErrUnsupportedAddress
}
return payToScriptHashScript(addr.ScriptAddress()), nil
return payToScriptHashScript(addr.ScriptAddress())

case *btcutil.AddressPubKey:
if addr == nil {
return nil, ErrUnsupportedAddress
}
return payToPubKeyScript(addr.ScriptAddress()), nil
return payToPubKeyScript(addr.ScriptAddress())
}

return nil, ErrUnsupportedAddress
Expand All @@ -1085,7 +1085,7 @@ func MultiSigScript(pubkeys []*btcutil.AddressPubKey, nrequired int) ([]byte, er
builder.AddInt64(int64(len(pubkeys)))
builder.AddOp(OP_CHECKMULTISIG)

return builder.Script(), nil
return builder.Script()
}

// SignatureScript creates an input signature script for tx to spend
Expand All @@ -1111,7 +1111,7 @@ func SignatureScript(tx *btcwire.MsgTx, idx int, subscript []byte, hashType SigH
pkData = pk.SerializeUncompressed()
}

return NewScriptBuilder().AddData(sig).AddData(pkData).Script(), nil
return NewScriptBuilder().AddData(sig).AddData(pkData).Script()
}

// RawTxInSignature returns the serialized ECDSA signature for the input
Expand All @@ -1137,7 +1137,7 @@ func p2pkSignatureScript(tx *btcwire.MsgTx, idx int, subScript []byte, hashType
return nil, err
}

return NewScriptBuilder().AddData(sig).Script(), nil
return NewScriptBuilder().AddData(sig).Script()
}

// signMultiSig signs as many of the outputs in the provided multisig script as
Expand Down Expand Up @@ -1169,7 +1169,8 @@ func signMultiSig(tx *btcwire.MsgTx, idx int, subScript []byte, hashType SigHash

}

return builder.Script(), signed == nRequired
script, _ := builder.Script()
return script, signed == nRequired
}

func sign(net *btcnet.Params, tx *btcwire.MsgTx, idx int, subScript []byte,
Expand Down Expand Up @@ -1277,7 +1278,8 @@ func mergeScripts(net *btcnet.Params, tx *btcwire.MsgTx, idx int,
builder := NewScriptBuilder()
builder.script = mergedScript
builder.AddData(script)
return builder.Script()
finalScript, _ := builder.Script()
return finalScript
case MultiSigTy:
return mergeMultiSig(tx, idx, addresses, nRequired, pkScript,
sigScript, prevScript)
Expand Down Expand Up @@ -1407,7 +1409,8 @@ sigLoop:
builder.AddOp(OP_0)
}

return builder.Script()
script, _ := builder.Script()
return script
}

// KeyDB is an interface type provided to SignTxOutput, it encapsulates
Expand Down Expand Up @@ -1470,7 +1473,7 @@ func SignTxOutput(net *btcnet.Params, tx *btcwire.MsgTx, idx int,
builder.script = realSigScript
builder.AddData(sigScript)

sigScript = builder.Script()
sigScript, _ = builder.Script()
// TODO keep a copy of the script for merging.
}

Expand Down
54 changes: 39 additions & 15 deletions script_test.go
Expand Up @@ -17,6 +17,18 @@ import (
"github.com/btcsuite/btcwire"
)

// builderScript is a convenience function which is used in the tests. It
// allows access to the script from a known good script built with the builder.
// Any errors are converted to a panic since it is only, and must only, be used
// with hard coded, and therefore, known good, scripts.
func builderScript(builder *btcscript.ScriptBuilder) []byte {
script, err := builder.Script()
if err != nil {
panic(err)
}
return script
}

func TestPushedData(t *testing.T) {
var tests = []struct {
in []byte
Expand All @@ -29,17 +41,17 @@ func TestPushedData(t *testing.T) {
true,
},
{
btcscript.NewScriptBuilder().AddInt64(16777216).AddInt64(10000000).Script(),
builderScript(btcscript.NewScriptBuilder().AddInt64(16777216).AddInt64(10000000)),
[][]byte{
{0x00, 0x00, 0x00, 0x01}, // 16777216
{0x80, 0x96, 0x98, 0x00}, // 10000000
},
true,
},
{
btcscript.NewScriptBuilder().AddOp(btcscript.OP_DUP).AddOp(btcscript.OP_HASH160).
builderScript(btcscript.NewScriptBuilder().AddOp(btcscript.OP_DUP).AddOp(btcscript.OP_HASH160).
AddData([]byte("17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhem")).AddOp(btcscript.OP_EQUALVERIFY).
AddOp(btcscript.OP_CHECKSIG).Script(),
AddOp(btcscript.OP_CHECKSIG)),
[][]byte{
// 17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhem
{
Expand All @@ -52,8 +64,8 @@ func TestPushedData(t *testing.T) {
true,
},
{
btcscript.NewScriptBuilder().AddOp(btcscript.OP_PUSHDATA4).AddInt64(1000).
AddOp(btcscript.OP_EQUAL).Script(),
builderScript(btcscript.NewScriptBuilder().AddOp(btcscript.OP_PUSHDATA4).AddInt64(1000).
AddOp(btcscript.OP_EQUAL)),
[][]byte{},
false,
},
Expand All @@ -78,25 +90,37 @@ func TestPushedData(t *testing.T) {
}

func TestStandardPushes(t *testing.T) {
for i := 0; i < 1000; i++ {
for i := 0; i < 65535; i++ {
builder := btcscript.NewScriptBuilder()
builder.AddInt64(int64(i))
if result := btcscript.IsPushOnlyScript(builder.Script()); !result {
t.Errorf("StandardPushesTests IsPushOnlyScript test #%d failed: %x\n", i, builder.Script())
script, err := builder.Script()
if err != nil {
t.Errorf("StandardPushesTests test #%d unexpected error: %v\n", i, err)
continue
}
if result := btcscript.HasCanonicalPushes(builder.Script()); !result {
t.Errorf("StandardPushesTests HasCanonicalPushes test #%d failed: %x\n", i, builder.Script())
if result := btcscript.IsPushOnlyScript(script); !result {
t.Errorf("StandardPushesTests IsPushOnlyScript test #%d failed: %x\n", i, script)
continue
}
if result := btcscript.HasCanonicalPushes(script); !result {
t.Errorf("StandardPushesTests HasCanonicalPushes test #%d failed: %x\n", i, script)
continue
}
}
for i := 0; i < 1000; i++ {
for i := 0; i <= btcscript.MaxScriptElementSize; i++ {
builder := btcscript.NewScriptBuilder()
builder.AddData(bytes.Repeat([]byte{0x49}, i))
if result := btcscript.IsPushOnlyScript(builder.Script()); !result {
t.Errorf("StandardPushesTests IsPushOnlyScript test #%d failed: %x\n", i, builder.Script())
script, err := builder.Script()
if err != nil {
t.Errorf("StandardPushesTests test #%d unexpected error: %v\n", i, err)
continue
}
if result := btcscript.IsPushOnlyScript(script); !result {
t.Errorf("StandardPushesTests IsPushOnlyScript test #%d failed: %x\n", i, script)
continue
}
if result := btcscript.HasCanonicalPushes(builder.Script()); !result {
t.Errorf("StandardPushesTests HasCanonicalPushes test #%d failed: %x\n", i, builder.Script())
if result := btcscript.HasCanonicalPushes(script); !result {
t.Errorf("StandardPushesTests HasCanonicalPushes test #%d failed: %x\n", i, script)
continue
}
}
Expand Down

0 comments on commit af5cbe4

Please sign in to comment.