From 83b0701c75b3434a8d51908a9831976c565526b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Thu, 2 Dec 2021 15:03:52 +0000 Subject: [PATCH] Enhancement: Append Opcodes (#89) * fixed linter * removed uppercase code in AppendOpcodes * removed uppercase code in AppendOpcodes * docstring --- bscript/errors.go | 9 +++--- bscript/interpreter/reference_test.go | 6 ++-- bscript/script.go | 25 +++++++++------ bscript/script_test.go | 44 +++++++++++++++++++++++++++ txoutput.go | 15 +++------ 5 files changed, 72 insertions(+), 27 deletions(-) diff --git a/bscript/errors.go b/bscript/errors.go index 1fad31c2..0f2256e7 100644 --- a/bscript/errors.go +++ b/bscript/errors.go @@ -27,8 +27,9 @@ var ( // Sentinel errors raised by the package. var ( - ErrInvalidPKLen = errors.New("invalid public key length") - ErrInvalidOpCode = errors.New("invalid opcode data") - ErrEmptyScript = errors.New("script is empty") - ErrNotP2PKH = errors.New("not a P2PKH") + ErrInvalidPKLen = errors.New("invalid public key length") + ErrInvalidOpCode = errors.New("invalid opcode data") + ErrEmptyScript = errors.New("script is empty") + ErrNotP2PKH = errors.New("not a P2PKH") + ErrInvalidOpcodeType = errors.New("use AppendPushData for push data funcs") ) diff --git a/bscript/interpreter/reference_test.go b/bscript/interpreter/reference_test.go index 59267169..7f527ccb 100644 --- a/bscript/interpreter/reference_test.go +++ b/bscript/interpreter/reference_test.go @@ -89,9 +89,9 @@ func parseShortForm(script string) (*bscript.Script, error) { // if parses as a plain number if num, err := strconv.ParseInt(tok, 10, 64); err == nil { if num == 0 { - scr.AppendOpCode(bscript.Op0) + scr.AppendOpcodes(bscript.Op0) } else if num == -1 || (1 <= num && num <= 16) { - scr.AppendOpCode((bscript.Op1 - 1) + byte(num)) + scr.AppendOpcodes((bscript.Op1 - 1) + byte(num)) } else { n := &scriptNumber{val: big.NewInt(num)} scr.AppendPushData(n.Bytes()) @@ -106,7 +106,7 @@ func parseShortForm(script string) (*bscript.Script, error) { tok[0] == '\'' && tok[len(tok)-1] == '\'' { scr.AppendPushData([]byte(tok[1 : len(tok)-1])) } else if opcode, ok := shortFormOps[tok]; ok { - scr.AppendOpCode(opcode) + scr = append(scr, opcode) } else { return nil, fmt.Errorf("bad token %q", tok) } diff --git a/bscript/script.go b/bscript/script.go index bec8d8b9..66a31ff0 100644 --- a/bscript/script.go +++ b/bscript/script.go @@ -48,7 +48,7 @@ func NewFromASM(str string) (*Script, error) { for _, section := range strings.Split(str, " ") { if val, ok := opCodeStrings[section]; ok { - s.AppendOpCode(val) + _ = s.AppendOpcodes(val) } else { if err := s.AppendPushDataHexString(section); err != nil { return nil, ErrInvalidOpCode @@ -124,14 +124,12 @@ func NewP2PKHFromAddress(addr string) (*Script, error) { return nil, err } - s := new(Script). - AppendOpCode(OpDUP). - AppendOpCode(OpHASH160) + s := new(Script) + _ = s.AppendOpcodes(OpDUP, OpHASH160) if err = s.AppendPushData(publicKeyHashBytes); err != nil { return nil, err } - s.AppendOpCode(OpEQUALVERIFY). - AppendOpCode(OpCHECKSIG) + _ = s.AppendOpcodes(OpEQUALVERIFY, OpCHECKSIG) return s, nil } @@ -210,10 +208,17 @@ func (s *Script) AppendPushDataStrings(pushDataStrings []string) error { return s.AppendPushDataArray(dataBytes) } -// AppendOpCode appends an opcode type to the script -func (s *Script) AppendOpCode(o uint8) *Script { - *s = append(*s, o) - return s +// AppendOpcodes appends opcodes type to the script. +// This does not support appending OP_PUSHDATA opcodes, so use `Script.AppendPushData` instead. +func (s *Script) AppendOpcodes(oo ...uint8) error { + for _, o := range oo { + switch o { + case OpPUSHDATA1, OpPUSHDATA2, OpPUSHDATA4: + return fmt.Errorf("%w: %s", ErrInvalidOpcodeType, opCodeValues[o]) + } + } + *s = append(*s, oo...) + return nil } // String implements the stringer interface and returns the hex string of script. diff --git a/bscript/script_test.go b/bscript/script_test.go index 0fecd7e6..fa9dee31 100644 --- a/bscript/script_test.go +++ b/bscript/script_test.go @@ -10,6 +10,7 @@ import ( "github.com/libsv/go-bk/bec" "github.com/libsv/go-bk/bip32" "github.com/libsv/go-bk/chaincfg" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/libsv/go-bt/v2/bscript" @@ -218,6 +219,49 @@ func TestErrorIsAppended(t *testing.T) { assert.True(t, strings.HasSuffix(asm, "[error]"), "toASM() should end with [error]") } +func TestScript_AppendOpcodes(t *testing.T) { + tests := map[string]struct { + script string + appends []byte + expScript string + expErr error + }{ + "successful single append": { + script: "OP_2 OP_2 OP_ADD", + appends: []byte{bscript.OpEQUALVERIFY}, + expScript: "OP_2 OP_2 OP_ADD OP_EQUALVERIFY", + }, + "successful multiple append": { + script: "OP_2 OP_2 OP_ADD", + appends: []byte{bscript.OpEQUAL, bscript.OpVERIFY}, + expScript: "OP_2 OP_2 OP_ADD OP_EQUAL OP_VERIFY", + }, + "unsuccessful push adata append": { + script: "OP_2 OP_2 OP_ADD", + appends: []byte{bscript.OpEQUAL, bscript.OpPUSHDATA1, 0x44}, + expErr: bscript.ErrInvalidOpcodeType, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + script, err := bscript.NewFromASM(test.script) + assert.NoError(t, err) + + err = script.AppendOpcodes(test.appends...) + if test.expErr != nil { + assert.Error(t, err) + assert.EqualError(t, test.expErr, errors.Unwrap(err).Error()) + } else { + assert.NoError(t, err) + asm, err := script.ToASM() + assert.NoError(t, err) + assert.Equal(t, test.expScript, asm) + } + }) + } +} + func TestScript_Equals(t *testing.T) { t.Parallel() tests := map[string]struct { diff --git a/txoutput.go b/txoutput.go index 9d75eb2a..e6a241e2 100644 --- a/txoutput.go +++ b/txoutput.go @@ -135,21 +135,18 @@ func (tx *Tx) AddHashPuzzleOutput(secret, publicKeyHash string, satoshis uint64) s := &bscript.Script{} - s.AppendOpCode(bscript.OpHASH160) + _ = s.AppendOpcodes(bscript.OpHASH160) secretBytesHash := crypto.Hash160([]byte(secret)) if err = s.AppendPushData(secretBytesHash); err != nil { return err } - s.AppendOpCode(bscript.OpEQUALVERIFY). - AppendOpCode(bscript.OpDUP). - AppendOpCode(bscript.OpHASH160) + _ = s.AppendOpcodes(bscript.OpEQUALVERIFY, bscript.OpDUP, bscript.OpHASH160) if err = s.AppendPushData(publicKeyHashBytes); err != nil { return err } - s.AppendOpCode(bscript.OpEQUALVERIFY). - AppendOpCode(bscript.OpCHECKSIG) + _ = s.AppendOpcodes(bscript.OpEQUALVERIFY, bscript.OpCHECKSIG) tx.AddOutput(&Output{ Satoshis: satoshis, @@ -184,10 +181,8 @@ func (tx *Tx) AddOpReturnPartsOutput(data [][]byte) error { func createOpReturnOutput(data [][]byte) (*Output, error) { s := &bscript.Script{} - s.AppendOpCode(bscript.OpFALSE) - s.AppendOpCode(bscript.OpRETURN) - err := s.AppendPushDataArray(data) - if err != nil { + _ = s.AppendOpcodes(bscript.OpFALSE, bscript.OpRETURN) + if err := s.AppendPushDataArray(data); err != nil { return nil, err }