From 9b2b05155b253eb0217384cc62440576f7612e55 Mon Sep 17 00:00:00 2001 From: dev Date: Sun, 21 Jul 2024 23:53:02 +0800 Subject: [PATCH 01/24] parse inscription like witness data --- zetaclient/chains/bitcoin/observer/inbound.go | 36 +++ zetaclient/chains/bitcoin/tx_script.go | 230 ++++++++++++++++++ zetaclient/chains/bitcoin/tx_script_test.go | 63 +++++ 3 files changed, 329 insertions(+) diff --git a/zetaclient/chains/bitcoin/observer/inbound.go b/zetaclient/chains/bitcoin/observer/inbound.go index 15a3bfdc99..517bfd0d23 100644 --- a/zetaclient/chains/bitcoin/observer/inbound.go +++ b/zetaclient/chains/bitcoin/observer/inbound.go @@ -477,3 +477,39 @@ func GetBtcEvent( } return nil, nil } + +// GetBtcEventWithWitness either returns a valid BTCInboundEvent or nil. +// This method supports data with more than 80 bytes by scanning the witness for possible presence of a tapscript. +// It will first prioritize OP_RETURN over tapscript. +// The format of the tapscript is +func GetBtcEventWithWitness( + rpcClient interfaces.BTCRPCClient, + tx btcjson.TxRawResult, + tssAddress string, + blockNumber uint64, + logger zerolog.Logger, + netParams *chaincfg.Params, + depositorFee float64, +) (*BTCInboundEvent, error) { + // first check for OP_RETURN data + event, err := GetBtcEvent( + rpcClient, + tx, + tssAddress, + blockNumber, + logger, + netParams, + depositorFee, + ) + + if err != nil { // should never happen + return nil, err + } + if event != nil { + return event, nil + } + + // TODO: integrate parsing script + + return nil, nil +} diff --git a/zetaclient/chains/bitcoin/tx_script.go b/zetaclient/chains/bitcoin/tx_script.go index b5f0bed226..b6ff4d2bc7 100644 --- a/zetaclient/chains/bitcoin/tx_script.go +++ b/zetaclient/chains/bitcoin/tx_script.go @@ -3,6 +3,7 @@ package bitcoin // #nosec G507 ripemd160 required for bitcoin address encoding import ( "bytes" + "encoding/binary" "encoding/hex" "fmt" "strconv" @@ -34,6 +35,12 @@ const ( // LengthScriptP2PKH is the length of P2PKH script [OP_DUP OP_HASH160 0x14 <20-byte-hash> OP_EQUALVERIFY OP_CHECKSIG] LengthScriptP2PKH = 25 + + // LengthInscriptionEnvelope is the length of the witness tapscript envelope for holding pushed data + LengthInscriptionEnvelope = 34 + + // LengthInscriptionWrapper is the length of the witness tapscript envelope for holding pushed data + LengthInscriptionWrapper = 34 ) // PayToAddrScript creates a new script to pay a transaction output to a the @@ -192,6 +199,23 @@ func DecodeOpReturnMemo(scriptHex string, txid string) ([]byte, bool, error) { return nil, false, nil } +// DecodeScript decodes memo wrapped in a inscription like script in witness +// returns (memo, found, error) +func DecodeScript(script []byte) ([]byte, bool, error) { + t := makeScriptTokenizer(script) + + if err := checkInscriptionEnvelope(&t); err != nil { + return nil, false, err + } + + memoBytes, err := decodeInscriptionPayload(&t) + if err != nil { + return nil, false, err + } + + return memoBytes, true, nil +} + // EncodeAddress returns a human-readable payment address given a ripemd160 hash // and netID which encodes the bitcoin network and address type. It is used // in both pay-to-pubkey-hash (P2PKH) and pay-to-script-hash (P2SH) address @@ -245,3 +269,209 @@ func DecodeTSSVout(vout btcjson.Vout, receiverExpected string, chain chains.Chai return receiverVout, amount, nil } + +// decodeInscriptionPayload checks the envelope for the script monitoring. The format is +// OP_FALSE +// OP_IF +// +// OP_PUSHDATA_N ... +// +// OP_ENDIF +// +// Note: the data pushed in OP_PUSHDATA_N will always be more than 80 bytes and not greater than 520 bytes. +func decodeInscriptionPayload(t *scriptTokenizer) ([]byte, error) { + if !t.Next() || t.Opcode() != txscript.OP_FALSE { + return nil, fmt.Errorf("OP_FALSE not found") + } + + if !t.Next() || t.Opcode() != txscript.OP_IF { + return nil, fmt.Errorf("OP_IF not found") + } + + memo := make([]byte, 0) + next := byte(txscript.OP_IF) + for { + if !t.Next() { + if t.Err() != nil { + return nil, t.Err() + } + return nil, fmt.Errorf("should contain more data, but script ended") + } + + next = t.Opcode() + + if next == txscript.OP_ENDIF { + break + } + + if next < txscript.OP_DATA_1 || next > txscript.OP_PUSHDATA4 { + return nil, fmt.Errorf("expecting data push, found %d", next) + } + + memo = append(memo, t.Data()...) + } + + return memo, nil +} + +// checkInscriptionEnvelope decodes the envelope for the script monitoring. The format is +// OP_PUSHBYTES_32 <32 bytes> OP_CHECKSIG +func checkInscriptionEnvelope(t *scriptTokenizer) error { + if !t.Next() || t.Opcode() != txscript.OP_DATA_32 { + return fmt.Errorf("cannot obtain public key bytes") + } + + if !t.Next() || t.Opcode() != txscript.OP_CHECKSIG { + return fmt.Errorf("cannot parse OP_CHECKSIG") + } + + return nil +} + +func makeScriptTokenizer(script []byte) scriptTokenizer { + return scriptTokenizer{ + script: script, + offset: 0, + } +} + +// scriptTokenizer is supposed to be replaced by txscript.ScriptTokenizer. However, +// it seems currently the btcsuite version does not have ScriptTokenizer. A simplified +// version of that is implemented here. This is fully compatible with txscript.ScriptTokenizer +// one should consider upgrading txscript and remove this implementation +type scriptTokenizer struct { + script []byte + offset int32 + op byte + data []byte + err error +} + +// Done returns true when either all opcodes have been exhausted or a parse +// failure was encountered and therefore the state has an associated error. +func (t *scriptTokenizer) Done() bool { + return t.err != nil || t.offset >= int32(len(t.script)) +} + +// Data returns the data associated with the most recently successfully parsed +// opcode. +func (t *scriptTokenizer) Data() []byte { + return t.data +} + +// Err returns any errors currently associated with the tokenizer. This will +// only be non-nil in the case a parsing error was encountered. +func (t *scriptTokenizer) Err() error { + return t.err +} + +// Opcode returns the current opcode associated with the tokenizer. +func (t *scriptTokenizer) Opcode() byte { + return t.op +} + +// Next attempts to parse the next opcode and returns whether or not it was +// successful. It will not be successful if invoked when already at the end of +// the script, a parse failure is encountered, or an associated error already +// exists due to a previous parse failure. +// +// In the case of a true return, the parsed opcode and data can be obtained with +// the associated functions and the offset into the script will either point to +// the next opcode or the end of the script if the final opcode was parsed. +// +// In the case of a false return, the parsed opcode and data will be the last +// successfully parsed values (if any) and the offset into the script will +// either point to the failing opcode or the end of the script if the function +// was invoked when already at the end of the script. +// +// Invoking this function when already at the end of the script is not +// considered an error and will simply return false. +func (t *scriptTokenizer) Next() bool { + if t.Done() { + return false + } + + op := t.script[t.offset] + + // Only the following op_code will be encountered: + // OP_PUSHDATA*, OP_DATA_*, OP_CHECKSIG, OP_IF, OP_ENDIF, OP_FALSE + switch { + // No additional data. Note that some of the opcodes, notably OP_1NEGATE, + // OP_0, and OP_[1-16] represent the data themselves. + case op == txscript.OP_FALSE || op == txscript.OP_IF || op == txscript.OP_CHECKSIG || op == txscript.OP_ENDIF: + t.offset++ + t.op = op + t.data = nil + return true + + // Data pushes of specific lengths -- OP_DATA_[1-75]. + case op >= txscript.OP_DATA_1 && op <= txscript.OP_DATA_75: + script := t.script[t.offset:] + + // add 2 instead of 1 because script includes the opcode as well + length := int32(op) - txscript.OP_DATA_1 + 2 + if int32(len(script)) < length { + t.err = fmt.Errorf("opcode %d requires %d bytes, but script only "+ + "has %d remaining", op, length, len(script)) + return false + } + + // Move the offset forward and set the opcode and data accordingly. + t.offset += length + t.op = op + t.data = script[1:length] + + return true + case op > txscript.OP_PUSHDATA4: + t.err = fmt.Errorf("unexpected op code") + return false + // Data pushes with parsed lengths -- OP_PUSHDATA{1,2,4}. + default: + var length int32 + switch op { + case txscript.OP_PUSHDATA1: + length = 1 + case txscript.OP_PUSHDATA2: + length = 2 + default: + length = 4 + } + + script := t.script[t.offset+1:] + if int32(len(script)) < length { + t.err = fmt.Errorf("opcode %d requires %d bytes, but script only "+ + "has %d remaining", op, length, len(script)) + return false + } + + // Next -length bytes are little endian length of data. + var dataLen int32 + switch length { + case 1: + dataLen = int32(script[0]) + case 2: + dataLen = int32(binary.LittleEndian.Uint16(script[:2])) + case 4: + dataLen = int32(binary.LittleEndian.Uint32(script[:4])) + default: + t.err = fmt.Errorf("invalid opcode length %d", length) + return false + } + + // Move to the beginning of the data. + script = script[length:] + + // Disallow entries that do not fit script or were sign extended. + if dataLen > int32(len(script)) || dataLen < 0 { + t.err = fmt.Errorf("opcode %d pushes %d bytes, but script only "+ + "has %d remaining", op, dataLen, len(script)) + return false + } + + // Move the offset forward and set the opcode and data accordingly. + t.offset += 1 + int32(length) + dataLen + t.op = op + t.data = script[:dataLen] + return true + } +} diff --git a/zetaclient/chains/bitcoin/tx_script_test.go b/zetaclient/chains/bitcoin/tx_script_test.go index eea97fc7b5..f1b17f2119 100644 --- a/zetaclient/chains/bitcoin/tx_script_test.go +++ b/zetaclient/chains/bitcoin/tx_script_test.go @@ -491,3 +491,66 @@ func TestDecodeTSSVoutErrors(t *testing.T) { require.Zero(t, amount) }) } + +func TestDecodeScript(t *testing.T) { + t.Run("should decode longer data ok", func(t *testing.T) { + // 600 bytes of random data generated offline + data := "2001a7bae79bd61c2368fe41a565061d6cf22b4f509fbc1652caea06d98b8fd0c7ac00634d0802c7faa771dd05f27993d22c42988758882d20080241074462884c8774e1cdf4b04e5b3b74b6568bd1769722708306c66270b6b2a7f68baced83627eeeb2d494e8a1749277b92a4c5a90b1b4f6038e5f704405515109d4d0021612ad298b8dad6e12245f8f0020e11a7a319652ba6abe261958201ce5e83131cd81302c0ecec60d4afa9f72540fc84b6b9c1f3d903ab25686df263b192a403a4aa22b799ba24369c49ff4042012589a07d4211e05f80f18a1262de5a1577ce0ec9e1fa9283cfa25d98d7d0b4217951dfcb8868570318c63f1e1424cfdb7d7a33c6b9e3ced4b2ffa0178b3a5fac8bace2991e382a402f56a2c6a9191463740910056483e4fd0f5ac729ffac66bf1b3ec4570c4e75c116f7d9fd65718ec3ed6c7647bf335b77e7d6a4e2011276dc8031b78403a1ad82c92fb339ec916c263b6dd0f003ba4381ad5410e90e88effbfa7f961b8e8a6011c525643a434f7abe2c1928a892cc57d6291831216c4e70cb80a39a79a3889211070e767c23db396af9b4c2093c3743d8cbcbfcb73d29361ecd3857e94ab3c800be1299fd36a5685ec60607a60d8c2e0f99ff0b8b9e86354d39a43041f7d552e95fe2d33b6fc0f540715da0e7e1b344c778afe73f82d00881352207b719f67dcb00b4ff645974d4fd7711363d26400e2852890cb6ea9cbfe63ac43080870049b1023be984331560c6350bb64da52b4b81bc8910934915f0a96701f4c50646d5386146596443bee9b2d116706e1687697fb42542196c1d764419c23a914896f9212946518ac59e1ba5d1fc37e503313133ebdf2ced5785e0eaa9738fe3f9ad73646e733931ebb7cff26e96106fe68" + script, _ := hex.DecodeString(data) + + memo, isFound, err := DecodeScript(script) + require.Nil(t, err) + require.True(t, isFound) + + // the expected memo + expected := "c7faa771dd05f27993d22c42988758882d20080241074462884c8774e1cdf4b04e5b3b74b6568bd1769722708306c66270b6b2a7f68baced83627eeeb2d494e8a1749277b92a4c5a90b1b4f6038e5f704405515109d4d0021612ad298b8dad6e12245f8f0020e11a7a319652ba6abe261958201ce5e83131cd81302c0ecec60d4afa9f72540fc84b6b9c1f3d903ab25686df263b192a403a4aa22b799ba24369c49ff4042012589a07d4211e05f80f18a1262de5a1577ce0ec9e1fa9283cfa25d98d7d0b4217951dfcb8868570318c63f1e1424cfdb7d7a33c6b9e3ced4b2ffa0178b3a5fac8bace2991e382a402f56a2c6a9191463740910056483e4fd0f5ac729ffac66bf1b3ec4570c4e75c116f7d9fd65718ec3ed6c7647bf335b77e7d6a4e2011276dc8031b78403a1ad82c92fb339ec916c263b6dd0f003ba4381ad5410e90e88effbfa7f961b8e8a6011c525643a434f7abe2c1928a892cc57d6291831216c4e70cb80a39a79a3889211070e767c23db396af9b4c2093c3743d8cbcbfcb73d29361ecd3857e94ab3c800be1299fd36a5685ec60607a60d8c2e0f99ff0b8b9e86354d39a43041f7d552e95fe2d33b6fc0f540715da0e7e1b344c778afe73f82d00881352207b719f67dcb00b4ff645974d4fd7711363d26400e2852890cb6ea9cbfe63ac43080870049b1023be984331560c6350bb64da52b4b81bc8910934915f0a96701f646d5386146596443bee9b2d116706e1687697fb42542196c1d764419c23a914896f9212946518ac59e1ba5d1fc37e503313133ebdf2ced5785e0eaa9738fe3f9ad73646e733931ebb7cff26e96106fe" + require.Equal(t, hex.EncodeToString(memo), expected) + }) + + t.Run("should decode shorter data ok", func(t *testing.T) { + // 81 bytes of random data generated offline + data := "20d6f59371037bf30115d9fd6016f0e3ef552cdfc0367ee20aa9df3158f74aaeb4ac00634c51bdd33073d76f6b4ae6510d69218100575eafabadd16e5faf9f42bd2fbbae402078bdcaa4c0413ce96d053e3c0bbd4d5944d6857107d640c248bdaaa7de959d9c1e6b9962b51428e5a554c28c397160881668" + script, _ := hex.DecodeString(data) + + memo, isFound, err := DecodeScript(script) + require.Nil(t, err) + require.True(t, isFound) + + // the expected memo + expected := "bdd33073d76f6b4ae6510d69218100575eafabadd16e5faf9f42bd2fbbae402078bdcaa4c0413ce96d053e3c0bbd4d5944d6857107d640c248bdaaa7de959d9c1e6b9962b51428e5a554c28c3971608816" + require.Equal(t, hex.EncodeToString(memo), expected) + }) + + t.Run("decode error due to missing data byte", func(t *testing.T) { + // missing OP_ENDIF at the end + data := "20cabd6ecc0245c40f27ca6299dcd3732287c317f3946734f04e27568fc5334218ac00634d0802000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000068" + script, _ := hex.DecodeString(data) + + memo, isFound, err := DecodeScript(script) + require.ErrorContains(t, err, "should contain more data, but script ended") + require.False(t, isFound) + require.Nil(t, memo) + }) + + t.Run("decode error due to missing data for public key", func(t *testing.T) { + // missing OP_ENDIF at the end + data := "2001a7bae79bd61c2368fe41a565061d6cf22b4f509fbc1652caea06d98b8fd0" + script, _ := hex.DecodeString(data) + + memo, isFound, err := DecodeScript(script) + require.ErrorContains(t, err, "cannot obtain public key bytes") + require.False(t, isFound) + require.Nil(t, memo) + }) + + t.Run("decode error due to missing OP_CHECKSIG", func(t *testing.T) { + // missing OP_ENDIF at the end + data := "2001a7bae79bd61c2368fe41a565061d6cf22b4f509fbc1652caea06d98b8fd0c7ab" + script, _ := hex.DecodeString(data) + + memo, isFound, err := DecodeScript(script) + require.ErrorContains(t, err, "cannot parse OP_CHECKSIG") + require.False(t, isFound) + require.Nil(t, memo) + }) +} From 54474202188247477150242d6fd510564ea1081f Mon Sep 17 00:00:00 2001 From: dev Date: Mon, 22 Jul 2024 00:05:31 +0800 Subject: [PATCH 02/24] more comment --- zetaclient/chains/bitcoin/tx_script.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/zetaclient/chains/bitcoin/tx_script.go b/zetaclient/chains/bitcoin/tx_script.go index b6ff4d2bc7..6910f5b87d 100644 --- a/zetaclient/chains/bitcoin/tx_script.go +++ b/zetaclient/chains/bitcoin/tx_script.go @@ -201,6 +201,19 @@ func DecodeOpReturnMemo(scriptHex string, txid string) ([]byte, bool, error) { // DecodeScript decodes memo wrapped in a inscription like script in witness // returns (memo, found, error) +// +// Note: the format of the script is following that of "inscription" defined in ordinal theory. +// However, to separate from inscription (as this use case is not an NFT), simplifications are made. +// The bitcoin envelope script is as follows: +// OP_DATA_32 <32 byte of public key> OP_CHECKSIG +// OP_FALSE +// OP_IF +// +// OP_PUSH 0x... +// OP_PUSH 0x... +// +// OP_ENDIF +// There are no content-type or any other attributes, it's just raw bytes. func DecodeScript(script []byte) ([]byte, bool, error) { t := makeScriptTokenizer(script) From d767352ac035869f3689f91057b20243e2f96a1d Mon Sep 17 00:00:00 2001 From: dev Date: Mon, 22 Jul 2024 00:20:28 +0800 Subject: [PATCH 03/24] remove unused code --- zetaclient/chains/bitcoin/tx_script.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/zetaclient/chains/bitcoin/tx_script.go b/zetaclient/chains/bitcoin/tx_script.go index 6910f5b87d..c2fd6d54d6 100644 --- a/zetaclient/chains/bitcoin/tx_script.go +++ b/zetaclient/chains/bitcoin/tx_script.go @@ -35,12 +35,6 @@ const ( // LengthScriptP2PKH is the length of P2PKH script [OP_DUP OP_HASH160 0x14 <20-byte-hash> OP_EQUALVERIFY OP_CHECKSIG] LengthScriptP2PKH = 25 - - // LengthInscriptionEnvelope is the length of the witness tapscript envelope for holding pushed data - LengthInscriptionEnvelope = 34 - - // LengthInscriptionWrapper is the length of the witness tapscript envelope for holding pushed data - LengthInscriptionWrapper = 34 ) // PayToAddrScript creates a new script to pay a transaction output to a the @@ -199,7 +193,7 @@ func DecodeOpReturnMemo(scriptHex string, txid string) ([]byte, bool, error) { return nil, false, nil } -// DecodeScript decodes memo wrapped in a inscription like script in witness +// DecodeScript decodes memo wrapped in an inscription like script in witness // returns (memo, found, error) // // Note: the format of the script is following that of "inscription" defined in ordinal theory. @@ -287,11 +281,13 @@ func DecodeTSSVout(vout btcjson.Vout, receiverExpected string, chain chains.Chai // OP_FALSE // OP_IF // -// OP_PUSHDATA_N ... +// OP_PUSHDATA_N ... +// ... +// OP_PUSHDATA_N ... // // OP_ENDIF // -// Note: the data pushed in OP_PUSHDATA_N will always be more than 80 bytes and not greater than 520 bytes. +// Note: the total data pushed will always be more than 80 bytes and within the btc transaction size limit. func decodeInscriptionPayload(t *scriptTokenizer) ([]byte, error) { if !t.Next() || t.Opcode() != txscript.OP_FALSE { return nil, fmt.Errorf("OP_FALSE not found") @@ -302,7 +298,7 @@ func decodeInscriptionPayload(t *scriptTokenizer) ([]byte, error) { } memo := make([]byte, 0) - next := byte(txscript.OP_IF) + var next byte for { if !t.Next() { if t.Err() != nil { From b83f923c3168c9e506998cb8a557747dd5423f12 Mon Sep 17 00:00:00 2001 From: dev-bitSmiley <153714963+bitSmiley@users.noreply.github.com> Date: Mon, 22 Jul 2024 18:59:16 +0800 Subject: [PATCH 04/24] Update zetaclient/chains/bitcoin/tx_script.go Co-authored-by: Dmitry S <11892559+swift1337@users.noreply.github.com> --- zetaclient/chains/bitcoin/tx_script.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zetaclient/chains/bitcoin/tx_script.go b/zetaclient/chains/bitcoin/tx_script.go index c2fd6d54d6..873efd5711 100644 --- a/zetaclient/chains/bitcoin/tx_script.go +++ b/zetaclient/chains/bitcoin/tx_script.go @@ -217,7 +217,7 @@ func DecodeScript(script []byte) ([]byte, bool, error) { memoBytes, err := decodeInscriptionPayload(&t) if err != nil { - return nil, false, err + return nil, false, errors.Wrap(err, "unable to decode the payload") } return memoBytes, true, nil From 6fb2dfddf407ca89e0f50e1064ec14ed90b40ecf Mon Sep 17 00:00:00 2001 From: dev-bitSmiley <153714963+bitSmiley@users.noreply.github.com> Date: Mon, 22 Jul 2024 18:59:36 +0800 Subject: [PATCH 05/24] Update zetaclient/chains/bitcoin/observer/inbound.go Co-authored-by: Dmitry S <11892559+swift1337@users.noreply.github.com> --- zetaclient/chains/bitcoin/observer/inbound.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zetaclient/chains/bitcoin/observer/inbound.go b/zetaclient/chains/bitcoin/observer/inbound.go index 517bfd0d23..3bc532c3a1 100644 --- a/zetaclient/chains/bitcoin/observer/inbound.go +++ b/zetaclient/chains/bitcoin/observer/inbound.go @@ -503,7 +503,7 @@ func GetBtcEventWithWitness( ) if err != nil { // should never happen - return nil, err + return nil, errors.Wrap(err, "unable to get btc event") } if event != nil { return event, nil From d00d739779244875e3d36d26029153141167009a Mon Sep 17 00:00:00 2001 From: dev-bitSmiley <153714963+bitSmiley@users.noreply.github.com> Date: Mon, 22 Jul 2024 18:59:44 +0800 Subject: [PATCH 06/24] Update zetaclient/chains/bitcoin/tx_script.go Co-authored-by: Dmitry S <11892559+swift1337@users.noreply.github.com> --- zetaclient/chains/bitcoin/tx_script.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zetaclient/chains/bitcoin/tx_script.go b/zetaclient/chains/bitcoin/tx_script.go index 873efd5711..a6a341ae2f 100644 --- a/zetaclient/chains/bitcoin/tx_script.go +++ b/zetaclient/chains/bitcoin/tx_script.go @@ -212,7 +212,7 @@ func DecodeScript(script []byte) ([]byte, bool, error) { t := makeScriptTokenizer(script) if err := checkInscriptionEnvelope(&t); err != nil { - return nil, false, err + return nil, false, errors.Wrap(err, "unable to check the envelope") } memoBytes, err := decodeInscriptionPayload(&t) From 1c445568c8f3ddf8d656b9c4ab1ee6ec868219c4 Mon Sep 17 00:00:00 2001 From: dev-bitSmiley <153714963+bitSmiley@users.noreply.github.com> Date: Tue, 23 Jul 2024 09:11:23 +0800 Subject: [PATCH 07/24] Update zetaclient/chains/bitcoin/tx_script.go Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- zetaclient/chains/bitcoin/tx_script.go | 31 +++++--------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/zetaclient/chains/bitcoin/tx_script.go b/zetaclient/chains/bitcoin/tx_script.go index a6a341ae2f..04e793dc3f 100644 --- a/zetaclient/chains/bitcoin/tx_script.go +++ b/zetaclient/chains/bitcoin/tx_script.go @@ -277,17 +277,6 @@ func DecodeTSSVout(vout btcjson.Vout, receiverExpected string, chain chains.Chai return receiverVout, amount, nil } -// decodeInscriptionPayload checks the envelope for the script monitoring. The format is -// OP_FALSE -// OP_IF -// -// OP_PUSHDATA_N ... -// ... -// OP_PUSHDATA_N ... -// -// OP_ENDIF -// -// Note: the total data pushed will always be more than 80 bytes and within the btc transaction size limit. func decodeInscriptionPayload(t *scriptTokenizer) ([]byte, error) { if !t.Next() || t.Opcode() != txscript.OP_FALSE { return nil, fmt.Errorf("OP_FALSE not found") @@ -299,28 +288,20 @@ func decodeInscriptionPayload(t *scriptTokenizer) ([]byte, error) { memo := make([]byte, 0) var next byte - for { - if !t.Next() { - if t.Err() != nil { - return nil, t.Err() - } - return nil, fmt.Errorf("should contain more data, but script ended") - } - + for t.Next() { next = t.Opcode() - if next == txscript.OP_ENDIF { - break + return memo, nil } - if next < txscript.OP_DATA_1 || next > txscript.OP_PUSHDATA4 { return nil, fmt.Errorf("expecting data push, found %d", next) } - memo = append(memo, t.Data()...) } - - return memo, nil + if t.Err() != nil { + return nil, t.Err() + } + return nil, fmt.Errorf("should contain more data, but script ended") } // checkInscriptionEnvelope decodes the envelope for the script monitoring. The format is From 61e8700fad181a2835e54f27250dc66b75b1adff Mon Sep 17 00:00:00 2001 From: dev Date: Tue, 23 Jul 2024 09:16:22 +0800 Subject: [PATCH 08/24] pull origin --- zetaclient/chains/bitcoin/tx_script.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zetaclient/chains/bitcoin/tx_script.go b/zetaclient/chains/bitcoin/tx_script.go index 04e793dc3f..96c87b0134 100644 --- a/zetaclient/chains/bitcoin/tx_script.go +++ b/zetaclient/chains/bitcoin/tx_script.go @@ -212,12 +212,12 @@ func DecodeScript(script []byte) ([]byte, bool, error) { t := makeScriptTokenizer(script) if err := checkInscriptionEnvelope(&t); err != nil { - return nil, false, errors.Wrap(err, "unable to check the envelope") + return nil, false, errors.Wrap(err, "checkInscriptionEnvelope: unable to check the envelope") } memoBytes, err := decodeInscriptionPayload(&t) if err != nil { - return nil, false, errors.Wrap(err, "unable to decode the payload") + return nil, false, errors.Wrap(err, "decodeInscriptionPayload: unable to decode the payload") } return memoBytes, true, nil From 59e8adc8d2261eb131aeec50495c7b4d15b979c2 Mon Sep 17 00:00:00 2001 From: dev-bitSmiley <153714963+bitSmiley@users.noreply.github.com> Date: Tue, 23 Jul 2024 09:18:21 +0800 Subject: [PATCH 09/24] Update zetaclient/chains/bitcoin/observer/inbound.go Co-authored-by: Dmitry S <11892559+swift1337@users.noreply.github.com> --- zetaclient/chains/bitcoin/observer/inbound.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zetaclient/chains/bitcoin/observer/inbound.go b/zetaclient/chains/bitcoin/observer/inbound.go index 3bc532c3a1..f56292d886 100644 --- a/zetaclient/chains/bitcoin/observer/inbound.go +++ b/zetaclient/chains/bitcoin/observer/inbound.go @@ -483,7 +483,7 @@ func GetBtcEvent( // It will first prioritize OP_RETURN over tapscript. // The format of the tapscript is func GetBtcEventWithWitness( - rpcClient interfaces.BTCRPCClient, + client interfaces.BTCRPCClient, tx btcjson.TxRawResult, tssAddress string, blockNumber uint64, From 25d6c4eca2438b9b3f41487d68848874731d7b77 Mon Sep 17 00:00:00 2001 From: dev Date: Tue, 23 Jul 2024 09:31:24 +0800 Subject: [PATCH 10/24] review feedbacks --- zetaclient/chains/bitcoin/observer/inbound.go | 1 - zetaclient/chains/bitcoin/tokenizer.go | 155 ++++++++++++++++++ zetaclient/chains/bitcoin/tx_script.go | 155 +----------------- 3 files changed, 158 insertions(+), 153 deletions(-) create mode 100644 zetaclient/chains/bitcoin/tokenizer.go diff --git a/zetaclient/chains/bitcoin/observer/inbound.go b/zetaclient/chains/bitcoin/observer/inbound.go index f56292d886..89a5a5be70 100644 --- a/zetaclient/chains/bitcoin/observer/inbound.go +++ b/zetaclient/chains/bitcoin/observer/inbound.go @@ -481,7 +481,6 @@ func GetBtcEvent( // GetBtcEventWithWitness either returns a valid BTCInboundEvent or nil. // This method supports data with more than 80 bytes by scanning the witness for possible presence of a tapscript. // It will first prioritize OP_RETURN over tapscript. -// The format of the tapscript is func GetBtcEventWithWitness( client interfaces.BTCRPCClient, tx btcjson.TxRawResult, diff --git a/zetaclient/chains/bitcoin/tokenizer.go b/zetaclient/chains/bitcoin/tokenizer.go new file mode 100644 index 0000000000..e83694dabc --- /dev/null +++ b/zetaclient/chains/bitcoin/tokenizer.go @@ -0,0 +1,155 @@ +package bitcoin + +import ( + "encoding/binary" + "fmt" + "github.com/btcsuite/btcd/txscript" +) + +func newScriptTokenizer(script []byte) scriptTokenizer { + return scriptTokenizer{ + script: script, + offset: 0, + } +} + +// scriptTokenizer is supposed to be replaced by txscript.ScriptTokenizer. However, +// it seems currently the btcsuite version does not have ScriptTokenizer. A simplified +// version of that is implemented here. This is fully compatible with txscript.ScriptTokenizer +// one should consider upgrading txscript and remove this implementation +type scriptTokenizer struct { + script []byte + offset int32 + op byte + data []byte + err error +} + +// Done returns true when either all opcodes have been exhausted or a parse +// failure was encountered and therefore the state has an associated error. +func (t *scriptTokenizer) Done() bool { + return t.err != nil || t.offset >= int32(len(t.script)) +} + +// Data returns the data associated with the most recently successfully parsed +// opcode. +func (t *scriptTokenizer) Data() []byte { + return t.data +} + +// Err returns any errors currently associated with the tokenizer. This will +// only be non-nil in the case a parsing error was encountered. +func (t *scriptTokenizer) Err() error { + return t.err +} + +// Opcode returns the current opcode associated with the tokenizer. +func (t *scriptTokenizer) Opcode() byte { + return t.op +} + +// Next attempts to parse the next opcode and returns whether or not it was +// successful. It will not be successful if invoked when already at the end of +// the script, a parse failure is encountered, or an associated error already +// exists due to a previous parse failure. +// +// In the case of a true return, the parsed opcode and data can be obtained with +// the associated functions and the offset into the script will either point to +// the next opcode or the end of the script if the final opcode was parsed. +// +// In the case of a false return, the parsed opcode and data will be the last +// successfully parsed values (if any) and the offset into the script will +// either point to the failing opcode or the end of the script if the function +// was invoked when already at the end of the script. +// +// Invoking this function when already at the end of the script is not +// considered an error and will simply return false. +func (t *scriptTokenizer) Next() bool { + if t.Done() { + return false + } + + op := t.script[t.offset] + + // Only the following op_code will be encountered: + // OP_PUSHDATA*, OP_DATA_*, OP_CHECKSIG, OP_IF, OP_ENDIF, OP_FALSE + switch { + // No additional data. Note that some of the opcodes, notably OP_1NEGATE, + // OP_0, and OP_[1-16] represent the data themselves. + case op == txscript.OP_FALSE || op == txscript.OP_IF || op == txscript.OP_CHECKSIG || op == txscript.OP_ENDIF: + t.offset++ + t.op = op + t.data = nil + return true + + // Data pushes of specific lengths -- OP_DATA_[1-75]. + case op >= txscript.OP_DATA_1 && op <= txscript.OP_DATA_75: + script := t.script[t.offset:] + + // add 2 instead of 1 because script includes the opcode as well + length := int32(op) - txscript.OP_DATA_1 + 2 + if int32(len(script)) < length { + t.err = fmt.Errorf("opcode %d requires %d bytes, but script only "+ + "has %d remaining", op, length, len(script)) + return false + } + + // Move the offset forward and set the opcode and data accordingly. + t.offset += length + t.op = op + t.data = script[1:length] + + return true + case op > txscript.OP_PUSHDATA4: + t.err = fmt.Errorf("unexpected op code %d", op) + return false + // Data pushes with parsed lengths -- OP_PUSHDATA{1,2,4}. + default: + var length int32 + switch op { + case txscript.OP_PUSHDATA1: + length = 1 + case txscript.OP_PUSHDATA2: + length = 2 + default: + length = 4 + } + + script := t.script[t.offset+1:] + if int32(len(script)) < length { + t.err = fmt.Errorf("opcode %d requires %d bytes, but script only "+ + "has %d remaining", op, length, len(script)) + return false + } + + // Next -length bytes are little endian length of data. + var dataLen int32 + switch length { + case 1: + dataLen = int32(script[0]) + case 2: + dataLen = int32(binary.LittleEndian.Uint16(script[:2])) + case 4: + dataLen = int32(binary.LittleEndian.Uint32(script[:4])) + default: + t.err = fmt.Errorf("invalid opcode length %d", length) + return false + } + + // Move to the beginning of the data. + script = script[length:] + + // Disallow entries that do not fit script or were sign extended. + if dataLen > int32(len(script)) || dataLen < 0 { + t.err = fmt.Errorf("opcode %d pushes %d bytes, but script only "+ + "has %d remaining", op, dataLen, len(script)) + return false + } + + // Move the offset forward and set the opcode and data accordingly. + t.offset += 1 + int32(length) + dataLen + t.op = op + t.data = script[:dataLen] + return true + } +} diff --git a/zetaclient/chains/bitcoin/tx_script.go b/zetaclient/chains/bitcoin/tx_script.go index 96c87b0134..8c0ebf5ff4 100644 --- a/zetaclient/chains/bitcoin/tx_script.go +++ b/zetaclient/chains/bitcoin/tx_script.go @@ -3,7 +3,6 @@ package bitcoin // #nosec G507 ripemd160 required for bitcoin address encoding import ( "bytes" - "encoding/binary" "encoding/hex" "fmt" "strconv" @@ -209,7 +208,7 @@ func DecodeOpReturnMemo(scriptHex string, txid string) ([]byte, bool, error) { // OP_ENDIF // There are no content-type or any other attributes, it's just raw bytes. func DecodeScript(script []byte) ([]byte, bool, error) { - t := makeScriptTokenizer(script) + t := newScriptTokenizer(script) if err := checkInscriptionEnvelope(&t); err != nil { return nil, false, errors.Wrap(err, "checkInscriptionEnvelope: unable to check the envelope") @@ -308,160 +307,12 @@ func decodeInscriptionPayload(t *scriptTokenizer) ([]byte, error) { // OP_PUSHBYTES_32 <32 bytes> OP_CHECKSIG func checkInscriptionEnvelope(t *scriptTokenizer) error { if !t.Next() || t.Opcode() != txscript.OP_DATA_32 { - return fmt.Errorf("cannot obtain public key bytes") + return fmt.Errorf("cannot obtain public key bytes op %d or err %s", t.Opcode(), t.Err()) } if !t.Next() || t.Opcode() != txscript.OP_CHECKSIG { - return fmt.Errorf("cannot parse OP_CHECKSIG") + return fmt.Errorf("cannot parse OP_CHECKSIG, op %d or err %s", t.Opcode(), t.Err()) } return nil } - -func makeScriptTokenizer(script []byte) scriptTokenizer { - return scriptTokenizer{ - script: script, - offset: 0, - } -} - -// scriptTokenizer is supposed to be replaced by txscript.ScriptTokenizer. However, -// it seems currently the btcsuite version does not have ScriptTokenizer. A simplified -// version of that is implemented here. This is fully compatible with txscript.ScriptTokenizer -// one should consider upgrading txscript and remove this implementation -type scriptTokenizer struct { - script []byte - offset int32 - op byte - data []byte - err error -} - -// Done returns true when either all opcodes have been exhausted or a parse -// failure was encountered and therefore the state has an associated error. -func (t *scriptTokenizer) Done() bool { - return t.err != nil || t.offset >= int32(len(t.script)) -} - -// Data returns the data associated with the most recently successfully parsed -// opcode. -func (t *scriptTokenizer) Data() []byte { - return t.data -} - -// Err returns any errors currently associated with the tokenizer. This will -// only be non-nil in the case a parsing error was encountered. -func (t *scriptTokenizer) Err() error { - return t.err -} - -// Opcode returns the current opcode associated with the tokenizer. -func (t *scriptTokenizer) Opcode() byte { - return t.op -} - -// Next attempts to parse the next opcode and returns whether or not it was -// successful. It will not be successful if invoked when already at the end of -// the script, a parse failure is encountered, or an associated error already -// exists due to a previous parse failure. -// -// In the case of a true return, the parsed opcode and data can be obtained with -// the associated functions and the offset into the script will either point to -// the next opcode or the end of the script if the final opcode was parsed. -// -// In the case of a false return, the parsed opcode and data will be the last -// successfully parsed values (if any) and the offset into the script will -// either point to the failing opcode or the end of the script if the function -// was invoked when already at the end of the script. -// -// Invoking this function when already at the end of the script is not -// considered an error and will simply return false. -func (t *scriptTokenizer) Next() bool { - if t.Done() { - return false - } - - op := t.script[t.offset] - - // Only the following op_code will be encountered: - // OP_PUSHDATA*, OP_DATA_*, OP_CHECKSIG, OP_IF, OP_ENDIF, OP_FALSE - switch { - // No additional data. Note that some of the opcodes, notably OP_1NEGATE, - // OP_0, and OP_[1-16] represent the data themselves. - case op == txscript.OP_FALSE || op == txscript.OP_IF || op == txscript.OP_CHECKSIG || op == txscript.OP_ENDIF: - t.offset++ - t.op = op - t.data = nil - return true - - // Data pushes of specific lengths -- OP_DATA_[1-75]. - case op >= txscript.OP_DATA_1 && op <= txscript.OP_DATA_75: - script := t.script[t.offset:] - - // add 2 instead of 1 because script includes the opcode as well - length := int32(op) - txscript.OP_DATA_1 + 2 - if int32(len(script)) < length { - t.err = fmt.Errorf("opcode %d requires %d bytes, but script only "+ - "has %d remaining", op, length, len(script)) - return false - } - - // Move the offset forward and set the opcode and data accordingly. - t.offset += length - t.op = op - t.data = script[1:length] - - return true - case op > txscript.OP_PUSHDATA4: - t.err = fmt.Errorf("unexpected op code") - return false - // Data pushes with parsed lengths -- OP_PUSHDATA{1,2,4}. - default: - var length int32 - switch op { - case txscript.OP_PUSHDATA1: - length = 1 - case txscript.OP_PUSHDATA2: - length = 2 - default: - length = 4 - } - - script := t.script[t.offset+1:] - if int32(len(script)) < length { - t.err = fmt.Errorf("opcode %d requires %d bytes, but script only "+ - "has %d remaining", op, length, len(script)) - return false - } - - // Next -length bytes are little endian length of data. - var dataLen int32 - switch length { - case 1: - dataLen = int32(script[0]) - case 2: - dataLen = int32(binary.LittleEndian.Uint16(script[:2])) - case 4: - dataLen = int32(binary.LittleEndian.Uint32(script[:4])) - default: - t.err = fmt.Errorf("invalid opcode length %d", length) - return false - } - - // Move to the beginning of the data. - script = script[length:] - - // Disallow entries that do not fit script or were sign extended. - if dataLen > int32(len(script)) || dataLen < 0 { - t.err = fmt.Errorf("opcode %d pushes %d bytes, but script only "+ - "has %d remaining", op, dataLen, len(script)) - return false - } - - // Move the offset forward and set the opcode and data accordingly. - t.offset += 1 + int32(length) + dataLen - t.op = op - t.data = script[:dataLen] - return true - } -} From 8790c55e2c5371d60c5f8b6d2803c9511467e350 Mon Sep 17 00:00:00 2001 From: dev Date: Tue, 23 Jul 2024 09:34:57 +0800 Subject: [PATCH 11/24] update review feedbacks --- zetaclient/chains/bitcoin/observer/inbound.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zetaclient/chains/bitcoin/observer/inbound.go b/zetaclient/chains/bitcoin/observer/inbound.go index 89a5a5be70..be61c0ed52 100644 --- a/zetaclient/chains/bitcoin/observer/inbound.go +++ b/zetaclient/chains/bitcoin/observer/inbound.go @@ -492,7 +492,7 @@ func GetBtcEventWithWitness( ) (*BTCInboundEvent, error) { // first check for OP_RETURN data event, err := GetBtcEvent( - rpcClient, + client, tx, tssAddress, blockNumber, From b482706cf65ecd0a7bac4300fe1b732b682e5042 Mon Sep 17 00:00:00 2001 From: dev Date: Thu, 25 Jul 2024 17:29:27 +0800 Subject: [PATCH 12/24] update make generate --- zetaclient/chains/bitcoin/tokenizer.go | 1 + 1 file changed, 1 insertion(+) diff --git a/zetaclient/chains/bitcoin/tokenizer.go b/zetaclient/chains/bitcoin/tokenizer.go index e83694dabc..b5e74fa0a8 100644 --- a/zetaclient/chains/bitcoin/tokenizer.go +++ b/zetaclient/chains/bitcoin/tokenizer.go @@ -3,6 +3,7 @@ package bitcoin import ( "encoding/binary" "fmt" + "github.com/btcsuite/btcd/txscript" ) From 1a30f3990feb182cf1bdc84143095672b70ab335 Mon Sep 17 00:00:00 2001 From: dev Date: Fri, 26 Jul 2024 15:01:45 +0800 Subject: [PATCH 13/24] fix linter --- zetaclient/chains/bitcoin/tokenizer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zetaclient/chains/bitcoin/tokenizer.go b/zetaclient/chains/bitcoin/tokenizer.go index b5e74fa0a8..901240c565 100644 --- a/zetaclient/chains/bitcoin/tokenizer.go +++ b/zetaclient/chains/bitcoin/tokenizer.go @@ -148,7 +148,7 @@ func (t *scriptTokenizer) Next() bool { } // Move the offset forward and set the opcode and data accordingly. - t.offset += 1 + int32(length) + dataLen + t.offset += 1 + length + dataLen t.op = op t.data = script[:dataLen] return true From 06f46e957a9803727aa38c18c0c8997860280f8c Mon Sep 17 00:00:00 2001 From: dev Date: Fri, 26 Jul 2024 15:10:11 +0800 Subject: [PATCH 14/24] remove over flow --- zetaclient/chains/bitcoin/tokenizer.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/zetaclient/chains/bitcoin/tokenizer.go b/zetaclient/chains/bitcoin/tokenizer.go index 901240c565..465773736a 100644 --- a/zetaclient/chains/bitcoin/tokenizer.go +++ b/zetaclient/chains/bitcoin/tokenizer.go @@ -20,7 +20,7 @@ func newScriptTokenizer(script []byte) scriptTokenizer { // one should consider upgrading txscript and remove this implementation type scriptTokenizer struct { script []byte - offset int32 + offset int op byte data []byte err error @@ -29,7 +29,7 @@ type scriptTokenizer struct { // Done returns true when either all opcodes have been exhausted or a parse // failure was encountered and therefore the state has an associated error. func (t *scriptTokenizer) Done() bool { - return t.err != nil || t.offset >= int32(len(t.script)) + return t.err != nil || t.offset >= len(t.script) } // Data returns the data associated with the most recently successfully parsed @@ -88,8 +88,8 @@ func (t *scriptTokenizer) Next() bool { script := t.script[t.offset:] // add 2 instead of 1 because script includes the opcode as well - length := int32(op) - txscript.OP_DATA_1 + 2 - if int32(len(script)) < length { + length := int(op) - txscript.OP_DATA_1 + 2 + if len(script) < length { t.err = fmt.Errorf("opcode %d requires %d bytes, but script only "+ "has %d remaining", op, length, len(script)) return false @@ -106,7 +106,7 @@ func (t *scriptTokenizer) Next() bool { return false // Data pushes with parsed lengths -- OP_PUSHDATA{1,2,4}. default: - var length int32 + var length int switch op { case txscript.OP_PUSHDATA1: length = 1 @@ -117,21 +117,21 @@ func (t *scriptTokenizer) Next() bool { } script := t.script[t.offset+1:] - if int32(len(script)) < length { + if len(script) < length { t.err = fmt.Errorf("opcode %d requires %d bytes, but script only "+ "has %d remaining", op, length, len(script)) return false } // Next -length bytes are little endian length of data. - var dataLen int32 + var dataLen int switch length { case 1: - dataLen = int32(script[0]) + dataLen = int(script[0]) case 2: - dataLen = int32(binary.LittleEndian.Uint16(script[:2])) + dataLen = int(binary.LittleEndian.Uint16(script[:2])) case 4: - dataLen = int32(binary.LittleEndian.Uint32(script[:4])) + dataLen = int(binary.LittleEndian.Uint32(script[:4])) default: t.err = fmt.Errorf("invalid opcode length %d", length) return false @@ -141,7 +141,7 @@ func (t *scriptTokenizer) Next() bool { script = script[length:] // Disallow entries that do not fit script or were sign extended. - if dataLen > int32(len(script)) || dataLen < 0 { + if dataLen > len(script) || dataLen < 0 { t.err = fmt.Errorf("opcode %d pushes %d bytes, but script only "+ "has %d remaining", op, dataLen, len(script)) return false From 47e851e30f68639a5ce871d2f71bec23252a6321 Mon Sep 17 00:00:00 2001 From: dev-bitSmiley <153714963+bitSmiley@users.noreply.github.com> Date: Tue, 30 Jul 2024 10:45:10 +0800 Subject: [PATCH 15/24] Update zetaclient/chains/bitcoin/observer/inbound.go Co-authored-by: Francisco de Borja Aranda Castillejo --- zetaclient/chains/bitcoin/observer/inbound.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zetaclient/chains/bitcoin/observer/inbound.go b/zetaclient/chains/bitcoin/observer/inbound.go index be61c0ed52..470c56d86f 100644 --- a/zetaclient/chains/bitcoin/observer/inbound.go +++ b/zetaclient/chains/bitcoin/observer/inbound.go @@ -501,9 +501,10 @@ func GetBtcEventWithWitness( depositorFee, ) - if err != nil { // should never happen + if err != nil { return nil, errors.Wrap(err, "unable to get btc event") } + if event != nil { return event, nil } From 898edbae72e373996c376027c3feba66841b6c02 Mon Sep 17 00:00:00 2001 From: dev-bitSmiley <153714963+bitSmiley@users.noreply.github.com> Date: Tue, 30 Jul 2024 10:49:45 +0800 Subject: [PATCH 16/24] Update zetaclient/chains/bitcoin/tokenizer.go Co-authored-by: Francisco de Borja Aranda Castillejo --- zetaclient/chains/bitcoin/tokenizer.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zetaclient/chains/bitcoin/tokenizer.go b/zetaclient/chains/bitcoin/tokenizer.go index 465773736a..182445a0aa 100644 --- a/zetaclient/chains/bitcoin/tokenizer.go +++ b/zetaclient/chains/bitcoin/tokenizer.go @@ -99,11 +99,12 @@ func (t *scriptTokenizer) Next() bool { t.offset += length t.op = op t.data = script[1:length] - return true + case op > txscript.OP_PUSHDATA4: t.err = fmt.Errorf("unexpected op code %d", op) return false + // Data pushes with parsed lengths -- OP_PUSHDATA{1,2,4}. default: var length int From 007084178871e5d0048b8b826ad21263733dc905 Mon Sep 17 00:00:00 2001 From: dev-bitSmiley <153714963+bitSmiley@users.noreply.github.com> Date: Tue, 30 Jul 2024 10:51:08 +0800 Subject: [PATCH 17/24] Update zetaclient/chains/bitcoin/tokenizer.go Co-authored-by: Francisco de Borja Aranda Castillejo --- zetaclient/chains/bitcoin/tokenizer.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/zetaclient/chains/bitcoin/tokenizer.go b/zetaclient/chains/bitcoin/tokenizer.go index 182445a0aa..eb80d18c62 100644 --- a/zetaclient/chains/bitcoin/tokenizer.go +++ b/zetaclient/chains/bitcoin/tokenizer.go @@ -90,8 +90,7 @@ func (t *scriptTokenizer) Next() bool { // add 2 instead of 1 because script includes the opcode as well length := int(op) - txscript.OP_DATA_1 + 2 if len(script) < length { - t.err = fmt.Errorf("opcode %d requires %d bytes, but script only "+ - "has %d remaining", op, length, len(script)) + t.err = fmt.Errorf("opcode %d detected, but script only %d bytes remaining", op, len(script)) return false } From 57716fa75e5b701735278cfef007127965bc22d1 Mon Sep 17 00:00:00 2001 From: dev-bitSmiley <153714963+bitSmiley@users.noreply.github.com> Date: Tue, 30 Jul 2024 10:51:47 +0800 Subject: [PATCH 18/24] Update zetaclient/chains/bitcoin/tokenizer.go Co-authored-by: Francisco de Borja Aranda Castillejo --- zetaclient/chains/bitcoin/tokenizer.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zetaclient/chains/bitcoin/tokenizer.go b/zetaclient/chains/bitcoin/tokenizer.go index eb80d18c62..22d202f2bc 100644 --- a/zetaclient/chains/bitcoin/tokenizer.go +++ b/zetaclient/chains/bitcoin/tokenizer.go @@ -112,8 +112,10 @@ func (t *scriptTokenizer) Next() bool { length = 1 case txscript.OP_PUSHDATA2: length = 2 + case txscript.OP_PUSHDATA4: + length = 4 default: - length = 4 + return fmt.Errorf("unexpected op code %d", op) } script := t.script[t.offset+1:] From b97c2261f0c1b1dc0f25ef1719f7b4ab0e7da176 Mon Sep 17 00:00:00 2001 From: dev-bitSmiley <153714963+bitSmiley@users.noreply.github.com> Date: Tue, 30 Jul 2024 10:52:18 +0800 Subject: [PATCH 19/24] Update zetaclient/chains/bitcoin/tokenizer.go Co-authored-by: Francisco de Borja Aranda Castillejo --- zetaclient/chains/bitcoin/tokenizer.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zetaclient/chains/bitcoin/tokenizer.go b/zetaclient/chains/bitcoin/tokenizer.go index 22d202f2bc..eaabc774a5 100644 --- a/zetaclient/chains/bitcoin/tokenizer.go +++ b/zetaclient/chains/bitcoin/tokenizer.go @@ -131,9 +131,9 @@ func (t *scriptTokenizer) Next() bool { case 1: dataLen = int(script[0]) case 2: - dataLen = int(binary.LittleEndian.Uint16(script[:2])) + dataLen = int(binary.LittleEndian.Uint16(script[:length])) case 4: - dataLen = int(binary.LittleEndian.Uint32(script[:4])) + dataLen = int(binary.LittleEndian.Uint32(script[:length])) default: t.err = fmt.Errorf("invalid opcode length %d", length) return false From fb8076cb2a26990fcf26233b36ef07dd38a2e71b Mon Sep 17 00:00:00 2001 From: dev Date: Tue, 30 Jul 2024 11:39:04 +0800 Subject: [PATCH 20/24] update review feedback --- zetaclient/chains/bitcoin/tokenizer.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/zetaclient/chains/bitcoin/tokenizer.go b/zetaclient/chains/bitcoin/tokenizer.go index eaabc774a5..4c4a6fb429 100644 --- a/zetaclient/chains/bitcoin/tokenizer.go +++ b/zetaclient/chains/bitcoin/tokenizer.go @@ -113,9 +113,10 @@ func (t *scriptTokenizer) Next() bool { case txscript.OP_PUSHDATA2: length = 2 case txscript.OP_PUSHDATA4: - length = 4 + length = 4 default: - return fmt.Errorf("unexpected op code %d", op) + t.err = fmt.Errorf("unexpected op code %d", op) + return false } script := t.script[t.offset+1:] From 2dea0c54c79d934f581d699bc7a76e4967af9871 Mon Sep 17 00:00:00 2001 From: dev Date: Tue, 30 Jul 2024 11:43:19 +0800 Subject: [PATCH 21/24] update code commnet --- zetaclient/chains/bitcoin/tokenizer.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zetaclient/chains/bitcoin/tokenizer.go b/zetaclient/chains/bitcoin/tokenizer.go index 4c4a6fb429..3ccafb77d8 100644 --- a/zetaclient/chains/bitcoin/tokenizer.go +++ b/zetaclient/chains/bitcoin/tokenizer.go @@ -87,8 +87,10 @@ func (t *scriptTokenizer) Next() bool { case op >= txscript.OP_DATA_1 && op <= txscript.OP_DATA_75: script := t.script[t.offset:] + // the length should be: int(op) - txscript.OP_DATA_1 + 2 // add 2 instead of 1 because script includes the opcode as well - length := int(op) - txscript.OP_DATA_1 + 2 + // since txscript.OP_DATA_1 is 1, then length is just int(op) + 1 + length := int(op) + 1 if len(script) < length { t.err = fmt.Errorf("opcode %d detected, but script only %d bytes remaining", op, len(script)) return false From c424b650b21616b7abe7fd04b973c4fb4cc86258 Mon Sep 17 00:00:00 2001 From: dev Date: Tue, 30 Jul 2024 21:43:47 +0800 Subject: [PATCH 22/24] update comment --- zetaclient/chains/bitcoin/tokenizer.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/zetaclient/chains/bitcoin/tokenizer.go b/zetaclient/chains/bitcoin/tokenizer.go index 3ccafb77d8..89a58516db 100644 --- a/zetaclient/chains/bitcoin/tokenizer.go +++ b/zetaclient/chains/bitcoin/tokenizer.go @@ -87,9 +87,10 @@ func (t *scriptTokenizer) Next() bool { case op >= txscript.OP_DATA_1 && op <= txscript.OP_DATA_75: script := t.script[t.offset:] - // the length should be: int(op) - txscript.OP_DATA_1 + 2 - // add 2 instead of 1 because script includes the opcode as well - // since txscript.OP_DATA_1 is 1, then length is just int(op) + 1 + // The length should be: int(op) - txscript.OP_DATA_1 + 2, i.e. op is txscript.OP_DATA_10, that means + // the data length should be 10, which is txscript.OP_DATA_10 - txscript.OP_DATA_1 + 1. + // Here, 2 instead of 1 because `script` also includes the opcode which means it contains one more byte. + // Since txscript.OP_DATA_1 is 1, then length is just int(op) - 1 + 2 = int(op) + 1 length := int(op) + 1 if len(script) < length { t.err = fmt.Errorf("opcode %d detected, but script only %d bytes remaining", op, len(script)) @@ -123,8 +124,7 @@ func (t *scriptTokenizer) Next() bool { script := t.script[t.offset+1:] if len(script) < length { - t.err = fmt.Errorf("opcode %d requires %d bytes, but script only "+ - "has %d remaining", op, length, len(script)) + t.err = fmt.Errorf("opcode %d requires %d bytes, only %d remaining", op, length, len(script)) return false } @@ -147,8 +147,7 @@ func (t *scriptTokenizer) Next() bool { // Disallow entries that do not fit script or were sign extended. if dataLen > len(script) || dataLen < 0 { - t.err = fmt.Errorf("opcode %d pushes %d bytes, but script only "+ - "has %d remaining", op, dataLen, len(script)) + t.err = fmt.Errorf("opcode %d pushes %d bytes, only %d remaining", op, dataLen, len(script)) return false } From abf043de663b0f0d8cc3de62805b049cd8f9aa11 Mon Sep 17 00:00:00 2001 From: dev Date: Tue, 30 Jul 2024 21:46:24 +0800 Subject: [PATCH 23/24] more comments --- zetaclient/chains/bitcoin/tokenizer.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zetaclient/chains/bitcoin/tokenizer.go b/zetaclient/chains/bitcoin/tokenizer.go index 89a58516db..5708bfa250 100644 --- a/zetaclient/chains/bitcoin/tokenizer.go +++ b/zetaclient/chains/bitcoin/tokenizer.go @@ -152,6 +152,8 @@ func (t *scriptTokenizer) Next() bool { } // Move the offset forward and set the opcode and data accordingly. + // 1 is the opcode size, which is just 1 byte. int(op) is the opcode value, + // it should not be mixed with the size. t.offset += 1 + length + dataLen t.op = op t.data = script[:dataLen] From 27a3ca2c68b5e789dcaf90d22c754cbb96417a51 Mon Sep 17 00:00:00 2001 From: dev-bitSmiley <153714963+bitSmiley@users.noreply.github.com> Date: Tue, 30 Jul 2024 22:35:39 +0800 Subject: [PATCH 24/24] Update changelog.md --- changelog.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index d00533e4c7..35e1381426 100644 --- a/changelog.md +++ b/changelog.md @@ -38,6 +38,7 @@ * [2518](https://github.com/zeta-chain/node/pull/2518) - add support for Solana address in zetacore * [2483](https://github.com/zeta-chain/node/pull/2483) - add priorityFee (gasTipCap) gas to the state * [2567](https://github.com/zeta-chain/node/pull/2567) - add sign latency metric to zetaclient (zetaclient_sign_latency) +* [2524](https://github.com/zeta-chain/node/pull/2524) - add inscription envolop parsing ### Refactor @@ -584,4 +585,4 @@ Getting the correct TSS address for Bitcoin now requires proviidng the Bitcoin c ### CI * [1218](https://github.com/zeta-chain/node/pull/1218) - cross-compile release binaries and simplify PR testings -* [1302](https://github.com/zeta-chain/node/pull/1302) - add mainnet builds to goreleaser \ No newline at end of file +* [1302](https://github.com/zeta-chain/node/pull/1302) - add mainnet builds to goreleaser