Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions services/legacy/txscript/opcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -1583,15 +1583,18 @@ func opcodeSize(op *parsedOpcode, vm *Engine) error {
//
// Stack transformation: a -> ~a
func opcodeInvert(op *parsedOpcode, vm *Engine) error {
ba, err := vm.dstack.PeekByteArray(0)
ba, err := vm.dstack.PopByteArray()
if err != nil {
return err
}

out := make([]byte, len(ba))
for i := range ba {
ba[i] = ba[i] ^ 0xFF
out[i] = ba[i] ^ 0xFF
}

vm.dstack.PushByteArray(out)

return nil
}

Expand Down
56 changes: 56 additions & 0 deletions services/legacy/txscript/opcode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"strconv"
"strings"
"testing"

"github.com/stretchr/testify/require"
)

// TestOpcodeDisabled tests the opcodeDisabled function manually because all
Expand Down Expand Up @@ -209,3 +211,57 @@ func TestOpcodeDisasm(t *testing.T) {
}
}
}

// TestOpcodeInvert_NoAliasing ensures opcodeInvert does not mutate stack items
// that share a backing array with the top of stack (e.g. after OP_DUP).
func TestOpcodeInvert_NoAliasing(t *testing.T) {
originalBytes := []byte{0x00, 0x0F, 0xF0, 0xFF}
expectedInverted := []byte{0xFF, 0xF0, 0x0F, 0x00}

vm := &Engine{}
vm.dstack.PushByteArray(originalBytes)

require.NoError(t, vm.dstack.DupN(1))
require.Equal(t, int32(2), vm.dstack.Depth())

pop := parsedOpcode{opcode: &opcodeArray[OP_INVERT], data: nil}
require.NoError(t, opcodeInvert(&pop, vm))

top, err := vm.dstack.PeekByteArray(0)
require.NoError(t, err)
require.Equal(t, expectedInverted, top, "top of stack must contain bitwise NOT of original bytes")

below, err := vm.dstack.PeekByteArray(1)
require.NoError(t, err)
require.Equal(t, []byte{0x00, 0x0F, 0xF0, 0xFF}, below,
"duplicate copy on stack must remain unchanged after OP_INVERT")
}

// TestOpcodeInvert_BasicCorrectness ensures opcodeInvert performs a bitwise
// NOT on the top stack item.
func TestOpcodeInvert_BasicCorrectness(t *testing.T) {
tests := []struct {
name string
in []byte
want []byte
}{
{"empty", []byte{}, []byte{}},
{"single byte zero", []byte{0x00}, []byte{0xFF}},
{"single byte ff", []byte{0xFF}, []byte{0x00}},
{"mixed", []byte{0xAA, 0x55, 0x0F, 0xF0}, []byte{0x55, 0xAA, 0xF0, 0x0F}},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
vm := &Engine{}
vm.dstack.PushByteArray(tc.in)

pop := parsedOpcode{opcode: &opcodeArray[OP_INVERT], data: nil}
require.NoError(t, opcodeInvert(&pop, vm))

got, err := vm.dstack.PeekByteArray(0)
require.NoError(t, err)
require.Equal(t, tc.want, got)
})
}
}
Loading