txscript: Make canonicalPush accept raw opcode.

This renames the canonicalPush function to isCanonicalPush and converts
it to accept an opcode as a byte and the associate data as a byte slice
instead of the internal parse opcode data struct in order to make it
more flexible for raw script analysis.

It also updates all callers and tests accordingly.
This commit is contained in:
Dave Collins 2019-03-13 01:12:22 -05:00 committed by Olaoluwa Osuntokun
parent 0a4f228dd1
commit da9fdabbd5
No known key found for this signature in database
GPG key ID: 3BBD59E99B280306
4 changed files with 49 additions and 49 deletions

View file

@ -875,6 +875,7 @@ func (vm *Engine) SetAltStack(data [][]byte) {
// engine according to the description provided by each flag.
func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags,
sigCache *SigCache, hashCache *TxSigHashes, inputAmount int64) (*Engine, error) {
const scriptVersion = 0
// The provided transaction input index must refer to a valid input.
if txIdx < 0 || txIdx >= len(tx.TxIn) {
@ -994,7 +995,9 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags
// data push of the witness program, otherwise we
// reintroduce malleability.
sigPops := vm.scripts[0]
if len(sigPops) == 1 && canonicalPush(sigPops[0]) &&
if len(sigPops) == 1 &&
isCanonicalPush(sigPops[0].opcode.value,
sigPops[0].data) &&
IsWitnessProgram(sigPops[0].data) {
witProgram = sigPops[0].data

View file

@ -128,7 +128,7 @@ func IsWitnessProgram(script []byte) bool {
func isWitnessProgram(pops []parsedOpcode) bool {
return len(pops) == 2 &&
isSmallInt(pops[0].opcode.value) &&
canonicalPush(pops[1]) &&
isCanonicalPush(pops[1].opcode.value, pops[1].data) &&
(len(pops[1].data) >= 2 && len(pops[1].data) <= 40)
}
@ -305,13 +305,16 @@ func removeOpcode(pkscript []parsedOpcode, opcode byte) []parsedOpcode {
return retScript
}
// canonicalPush returns true if the object is either not a push instruction
// or the push instruction contained wherein is matches the canonical form
// or using the smallest instruction to do the job. False otherwise.
func canonicalPush(pop parsedOpcode) bool {
opcode := pop.opcode.value
data := pop.data
dataLen := len(pop.data)
// isCanonicalPush returns true if the opcode is either not a push instruction
// or the data associated with the push instruction uses the smallest
// instruction to do the job. False otherwise.
//
// For example, it is possible to push a value of 1 to the stack as "OP_1",
// "OP_DATA_1 0x01", "OP_PUSHDATA1 0x01 0x01", and others, however, the first
// only takes a single byte, while the rest take more. Only the first is
// considered canonical.
func isCanonicalPush(opcode byte, data []byte) bool {
dataLen := len(data)
if opcode > OP_16 {
return true
}
@ -336,7 +339,9 @@ func canonicalPush(pop parsedOpcode) bool {
func removeOpcodeByData(pkscript []parsedOpcode, data []byte) []parsedOpcode {
retScript := make([]parsedOpcode, 0, len(pkscript))
for _, pop := range pkscript {
if !canonicalPush(pop) || !bytes.Contains(pop.data, data) {
if !isCanonicalPush(pop.opcode.value, pop.data) ||
!bytes.Contains(pop.data, data) {
retScript = append(retScript, pop)
}
}

View file

@ -3740,31 +3740,25 @@ func TestPushedData(t *testing.T) {
}
}
// TestHasCanonicalPush ensures the canonicalPush function works as expected.
// TestHasCanonicalPush ensures the isCanonicalPush function works as expected.
func TestHasCanonicalPush(t *testing.T) {
t.Parallel()
const scriptVersion = 0
for i := 0; i < 65535; i++ {
script, err := NewScriptBuilder().AddInt64(int64(i)).Script()
if err != nil {
t.Errorf("Script: test #%d unexpected error: %v\n", i,
err)
t.Errorf("Script: test #%d unexpected error: %v\n", i, err)
continue
}
if result := IsPushOnlyScript(script); !result {
t.Errorf("IsPushOnlyScript: test #%d failed: %x\n", i,
script)
if !IsPushOnlyScript(script) {
t.Errorf("IsPushOnlyScript: test #%d failed: %x\n", i, script)
continue
}
pops, err := parseScript(script)
if err != nil {
t.Errorf("parseScript: #%d failed: %v", i, err)
continue
}
for _, pop := range pops {
if result := canonicalPush(pop); !result {
t.Errorf("canonicalPush: test #%d failed: %x\n",
i, script)
tokenizer := MakeScriptTokenizer(scriptVersion, script)
for tokenizer.Next() {
if !isCanonicalPush(tokenizer.Opcode(), tokenizer.Data()) {
t.Errorf("isCanonicalPush: test #%d failed: %x\n", i, script)
break
}
}
@ -3774,21 +3768,17 @@ func TestHasCanonicalPush(t *testing.T) {
builder.AddData(bytes.Repeat([]byte{0x49}, i))
script, err := builder.Script()
if err != nil {
t.Errorf("StandardPushesTests test #%d unexpected error: %v\n", i, err)
t.Errorf("Script: test #%d unexpected error: %v\n", i, err)
continue
}
if result := IsPushOnlyScript(script); !result {
t.Errorf("StandardPushesTests IsPushOnlyScript test #%d failed: %x\n", i, script)
if !IsPushOnlyScript(script) {
t.Errorf("IsPushOnlyScript: test #%d failed: %x\n", i, script)
continue
}
pops, err := parseScript(script)
if err != nil {
t.Errorf("StandardPushesTests #%d failed to TstParseScript: %v", i, err)
continue
}
for _, pop := range pops {
if result := canonicalPush(pop); !result {
t.Errorf("StandardPushesTests TstHasCanonicalPushes test #%d failed: %x\n", i, script)
tokenizer := MakeScriptTokenizer(scriptVersion, script)
for tokenizer.Next() {
if !isCanonicalPush(tokenizer.Opcode(), tokenizer.Data()) {
t.Errorf("isCanonicalPush: test #%d failed: %x\n", i, script)
break
}
}
@ -4213,11 +4203,13 @@ func TestIsPayToWitnessPubKeyHash(t *testing.T) {
}
}
// TestHasCanonicalPushes ensures the canonicalPush function properly determines
// what is considered a canonical push for the purposes of removeOpcodeByData.
// TestHasCanonicalPushes ensures the isCanonicalPush function properly
// determines what is considered a canonical push for the purposes of
// removeOpcodeByData.
func TestHasCanonicalPushes(t *testing.T) {
t.Parallel()
const scriptVersion = 0
tests := []struct {
name string
script string
@ -4236,20 +4228,20 @@ func TestHasCanonicalPushes(t *testing.T) {
},
}
for i, test := range tests {
for _, test := range tests {
script := mustParseShortForm(test.script)
pops, err := parseScript(script)
if err != nil {
if err := checkScriptParses(scriptVersion, script); err != nil {
if test.expected {
t.Errorf("TstParseScript #%d failed: %v", i, err)
t.Errorf("%q: script parse failed: %v", test.name, err)
}
continue
}
for _, pop := range pops {
if canonicalPush(pop) != test.expected {
t.Errorf("canonicalPush: #%d (%s) wrong result"+
"\ngot: %v\nwant: %v", i, test.name,
true, test.expected)
tokenizer := MakeScriptTokenizer(scriptVersion, script)
for tokenizer.Next() {
result := isCanonicalPush(tokenizer.Opcode(), tokenizer.Data())
if result != test.expected {
t.Errorf("%q: isCanonicalPush wrong result\ngot: %v\nwant: %v",
test.name, result, test.expected)
break
}
}

View file

@ -953,7 +953,7 @@ func ExtractAtomicSwapDataPushes(version uint16, pkScript []byte) (*AtomicSwapDa
}
isAtomicSwap := pops[0].opcode.value == OP_IF &&
pops[1].opcode.value == OP_SIZE &&
canonicalPush(pops[2]) &&
isCanonicalPush(pops[2].opcode.value, pops[2].data) &&
pops[3].opcode.value == OP_EQUALVERIFY &&
pops[4].opcode.value == OP_SHA256 &&
pops[5].opcode.value == OP_DATA_32 &&
@ -962,7 +962,7 @@ func ExtractAtomicSwapDataPushes(version uint16, pkScript []byte) (*AtomicSwapDa
pops[8].opcode.value == OP_HASH160 &&
pops[9].opcode.value == OP_DATA_20 &&
pops[10].opcode.value == OP_ELSE &&
canonicalPush(pops[11]) &&
isCanonicalPush(pops[11].opcode.value, pops[11].data) &&
pops[12].opcode.value == OP_CHECKLOCKTIMEVERIFY &&
pops[13].opcode.value == OP_DROP &&
pops[14].opcode.value == OP_DUP &&