From eb239d52f346ee01544f13800c36059c33733d22 Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Fri, 3 May 2024 11:56:06 -0400 Subject: [PATCH 1/3] Add functions for round trip decoding of transaction payloads Signed-off-by: Peter Broadhurst --- internal/signermsgs/en_error_messges.go | 6 + pkg/ethsigner/transaction.go | 141 +++++++++++++++++++- pkg/ethsigner/transaction_test.go | 164 +++++++++++++++++------- pkg/ethsigner/typed_data.go | 2 +- pkg/ethsigner/wallet.go | 2 +- pkg/rlp/decode.go | 2 +- pkg/rlp/encode.go | 2 +- pkg/rlp/rlp.go | 35 ++++- pkg/rlp/rlp_test.go | 54 ++++++++ 9 files changed, 356 insertions(+), 52 deletions(-) create mode 100644 pkg/rlp/rlp_test.go diff --git a/internal/signermsgs/en_error_messges.go b/internal/signermsgs/en_error_messges.go index 5ec6fdc..a25968a 100644 --- a/internal/signermsgs/en_error_messges.go +++ b/internal/signermsgs/en_error_messges.go @@ -97,4 +97,10 @@ var ( MsgEIP712ValueNotArray = ffe("FF22078", "Value for '%s' not an array (%T)") MsgEIP712InvalidArrayLen = ffe("FF22079", "Value for '%s' must have %d entries (found %d)") MsgEIP712PrimaryTypeRequired = ffe("FF22080", "Primary type must be specified") + MsgEmptyTransactionBytes = ffe("FF22081", "Transaction payload is empty") + MsgUnsupportedTransactionType = ffe("FF22082", "Unsupported transaction type 0x%02x") + MsgInvalidLegacyTransaction = ffe("FF22083", "Transaction payload invalid (legacy): %v") + MsgInvalidEIP1559Transaction = ffe("FF22084", "Transaction payload invalid (EIP-1559): %v") + MsgInvalidEIP155TransactionV = ffe("FF22085", "Invalid V value from EIP-155 transaction (chainId=%d): %d") + MsgInvalidChainID = ffe("FF22086", "Invalid chainId expected=%d actual=%d") ) diff --git a/pkg/ethsigner/transaction.go b/pkg/ethsigner/transaction.go index 191d892..b2080f3 100644 --- a/pkg/ethsigner/transaction.go +++ b/pkg/ethsigner/transaction.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Kaleido, Inc. +// Copyright © 2024 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -23,6 +23,7 @@ import ( "math/big" "github.com/hyperledger/firefly-common/pkg/i18n" + "github.com/hyperledger/firefly-common/pkg/log" "github.com/hyperledger/firefly-signer/internal/signermsgs" "github.com/hyperledger/firefly-signer/pkg/ethtypes" "github.com/hyperledger/firefly-signer/pkg/rlp" @@ -74,7 +75,7 @@ func (t *Transaction) BuildLegacy() rlp.List { return rlpList } -func (t *Transaction) AddEIP155HashValues(rlpList rlp.List, chainID int64) rlp.List { +func AddEIP155HashValuesToRLPList(rlpList rlp.List, chainID int64) rlp.List { // These values go into the hash of the transaction rlpList = append(rlpList, rlp.WrapInt(big.NewInt(chainID))) rlpList = append(rlpList, rlp.WrapInt(big.NewInt(0))) @@ -82,6 +83,10 @@ func (t *Transaction) AddEIP155HashValues(rlpList rlp.List, chainID int64) rlp.L return rlpList } +func (t *Transaction) AddEIP155HashValues(rlpList rlp.List, chainID int64) rlp.List { + return AddEIP155HashValuesToRLPList(rlpList, chainID) +} + func (t *Transaction) Build1559(chainID int64) rlp.List { rlpList := make(rlp.List, 0, 9) rlpList = append(rlpList, rlp.WrapInt(big.NewInt(chainID))) @@ -211,6 +216,138 @@ func (t *Transaction) SignEIP1559(signer secp256k1.Signer, chainID int64) ([]byt return append([]byte{TransactionType1559}, rlpList.Encode()...), nil } +func RecoverLegacyRawTransaction(ctx context.Context, rawTx ethtypes.HexBytes0xPrefix, chainID int64) (*ethtypes.Address0xHex, *Transaction, error) { + + decoded, _, err := rlp.Decode(rawTx) + if err != nil { + log.L(ctx).Errorf("Invalid legacy transaction data '%s': %s", rawTx, err) + return nil, nil, i18n.NewError(ctx, signermsgs.MsgInvalidLegacyTransaction, err) + } + + if decoded == nil || len(decoded.(rlp.List)) < 9 { + log.L(ctx).Errorf("Invalid legacy transaction data '%s': EOF", rawTx) + return nil, nil, i18n.NewError(ctx, signermsgs.MsgInvalidLegacyTransaction, "EOF") + } + rlpList := decoded.(rlp.List) + + tx := &Transaction{ + Nonce: (*ethtypes.HexInteger)(rlpList[0].ToData().Int()), + GasPrice: (*ethtypes.HexInteger)(rlpList[1].ToData().Int()), + GasLimit: (*ethtypes.HexInteger)(rlpList[2].ToData().Int()), + To: rlpList[3].ToData().Address(), + Value: (*ethtypes.HexInteger)(rlpList[4].ToData().Int()), + Data: ethtypes.HexBytes0xPrefix(rlpList[5].ToData()), + } + + foundSig := &secp256k1.SignatureData{ + V: new(big.Int), + R: new(big.Int), + S: new(big.Int), + } + vValue := rlpList[6].ToData().Int().Int64() + foundSig.R.SetBytes(rlpList[7].ToData().BytesNotNil()) + foundSig.S.SetBytes(rlpList[8].ToData().BytesNotNil()) + + var message []byte + if vValue > 28 { + // Legacy with EIP155 extensions + newV := vValue - (chainID * 2) - 8 + if newV != 27 && newV != 28 { + return nil, nil, i18n.NewError(ctx, signermsgs.MsgInvalidEIP155TransactionV, chainID, foundSig.V.Int64()) + } + foundSig.V.SetInt64(newV) + + signedRLPList := make(rlp.List, 6, 9) + copy(signedRLPList, rlpList[0:6]) + signedRLPList = AddEIP155HashValuesToRLPList(signedRLPList, chainID) + message = signedRLPList.Encode() + } else { + // Legacy original transaction + foundSig.V.SetInt64(vValue) + message = (rlpList[0:6]).Encode() + } + + signer, err := foundSig.Recover(message, chainID) + if err != nil { + return nil, nil, err + } + + return signer, tx, nil +} + +func RecoverEIP1559Transaction(ctx context.Context, rawTx ethtypes.HexBytes0xPrefix, chainID int64) (*ethtypes.Address0xHex, *Transaction, error) { + + if len(rawTx) == 0 || rawTx[0] != TransactionType1559 { + return nil, nil, i18n.NewError(ctx, signermsgs.MsgInvalidEIP1559Transaction, "TransactionType") + } + + rawTx = rawTx[1:] + decoded, _, err := rlp.Decode(rawTx) + if err != nil { + log.L(ctx).Errorf("Invalid EIP-1559 transaction data '%s': %s", rawTx, err) + return nil, nil, i18n.NewError(ctx, signermsgs.MsgInvalidEIP1559Transaction, err) + } + + if decoded == nil || len(decoded.(rlp.List)) < 12 { + log.L(ctx).Errorf("Invalid EIP-1559 transaction data '%s': EOF", rawTx) + return nil, nil, i18n.NewError(ctx, signermsgs.MsgInvalidEIP1559Transaction, "EOF") + } + rlpList := decoded.(rlp.List) + + encodedChainID := rlpList[0].ToData().IntOrZero().Int64() + if encodedChainID != chainID { + return nil, nil, i18n.NewError(ctx, signermsgs.MsgInvalidChainID, chainID, encodedChainID) + } + tx := &Transaction{ + Nonce: (*ethtypes.HexInteger)(rlpList[1].ToData().Int()), + MaxPriorityFeePerGas: (*ethtypes.HexInteger)(rlpList[2].ToData().Int()), + MaxFeePerGas: (*ethtypes.HexInteger)(rlpList[3].ToData().Int()), + GasLimit: (*ethtypes.HexInteger)(rlpList[4].ToData().Int()), + To: rlpList[5].ToData().Address(), + Value: (*ethtypes.HexInteger)(rlpList[6].ToData().Int()), + Data: ethtypes.HexBytes0xPrefix(rlpList[7].ToData()), + // No access list support + } + + foundSig := &secp256k1.SignatureData{ + V: new(big.Int), + R: new(big.Int), + S: new(big.Int), + } + foundSig.V.SetInt64(rlpList[9].ToData().Int().Int64()) + foundSig.R.SetBytes(rlpList[10].ToData().BytesNotNil()) + foundSig.S.SetBytes(rlpList[11].ToData().BytesNotNil()) + + message := append([]byte{TransactionType1559}, (rlpList[0:9]).Encode()...) + fmt.Println((ethtypes.HexBytes0xPrefix)(message).String()) + + signer, err := foundSig.Recover(message, chainID) + if err != nil { + return nil, nil, err + } + + return signer, tx, nil +} + +func RecoverRawTransaction(ctx context.Context, rawTx ethtypes.HexBytes0xPrefix, chainID int64) (*ethtypes.Address0xHex, *Transaction, error) { + + // The first byte of the payload (per EIP-2718) is either `>= 0xc0` for legacy transactions, + // or a transaction type selector (up to `0x7f`). + if len(rawTx) == 0 { + return nil, nil, i18n.NewError(ctx, signermsgs.MsgEmptyTransactionBytes) + } + txTypeByte := rawTx[0] + switch { + case txTypeByte >= 0xc7: + return RecoverLegacyRawTransaction(ctx, rawTx, chainID) + case txTypeByte == TransactionType1559: + return RecoverEIP1559Transaction(ctx, rawTx, chainID) + default: + return nil, nil, i18n.NewError(ctx, signermsgs.MsgUnsupportedTransactionType, txTypeByte) + } + +} + func (t *Transaction) addSignature(rlpList rlp.List, sig *secp256k1.SignatureData) rlp.List { rlpList = append(rlpList, rlp.WrapInt(sig.V)) rlpList = append(rlpList, rlp.WrapInt(sig.R)) diff --git a/pkg/ethsigner/transaction_test.go b/pkg/ethsigner/transaction_test.go index 6f7c812..b9c0cbc 100644 --- a/pkg/ethsigner/transaction_test.go +++ b/pkg/ethsigner/transaction_test.go @@ -17,7 +17,9 @@ package ethsigner import ( + "context" "encoding/hex" + "encoding/json" "fmt" "math/big" "testing" @@ -100,7 +102,7 @@ func TestEncodeExistingEIP1559(t *testing.T) { } -func TestSignAutoEIP155(t *testing.T) { +func TestSignLegacyEIP155(t *testing.T) { inputData, err := hex.DecodeString( "3674e15c00000000000000000000000000000000000000000000000000000000000000a03f04a4e93ded4d2aaa1a41d617e55c59ac5f1b28a47047e2a526e76d45eb9681d19642e9120d63a9b7f5f537565a430d8ad321ef1bc76689a4b3edc861c640fc00000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000966665f73797374656d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002e516d58747653456758626265506855684165364167426f3465796a7053434b437834515a4c50793548646a6177730000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a1f7502c8f8797999c0c6b9c2da653ea736598ed0daa856c47ae71411aa8fea2") @@ -117,26 +119,17 @@ func TestSignAutoEIP155(t *testing.T) { keypair, err := secp256k1.GenerateSecp256k1KeyPair() assert.NoError(t, err) - raw, err := txn.Sign(keypair, 1001) + raw, err := txn.SignLegacyEIP155(keypair, 1001) assert.NoError(t, err) - rlpList, _, err := rlp.Decode(raw) + signer, txr, err := RecoverRawTransaction(context.Background(), raw, 1001) assert.NoError(t, err) - foundSig := &secp256k1.SignatureData{ - V: new(big.Int), - R: new(big.Int), - S: new(big.Int), - } - foundSig.V.SetBytes([]byte(rlpList.(rlp.List)[6].(rlp.Data))) - foundSig.R.SetBytes([]byte(rlpList.(rlp.List)[7].(rlp.Data))) - foundSig.S.SetBytes([]byte(rlpList.(rlp.List)[8].(rlp.Data))) + assert.Equal(t, keypair.Address.String(), signer.String()) + jsonCompare(t, txn, *txr) - signaturePayload := txn.SignaturePayload(1001) - addr, err := foundSig.Recover(signaturePayload.Bytes(), 1001) - assert.NoError(t, err) - assert.Equal(t, keypair.Address.String(), addr.String()) + _, _, err = RecoverRawTransaction(context.Background(), raw, 1002) + assert.Regexp(t, "FF22085", err) - assert.Equal(t, "0x4524b8ac39ace2a3a2c061b73125c19c76daf0d25d44a4d88799f3c2ba686fe6", signaturePayload.Hash().String()) } func TestSignAutoEIP1559(t *testing.T) { @@ -160,22 +153,10 @@ func TestSignAutoEIP1559(t *testing.T) { raw, err := txn.Sign(keypair, 1001) assert.NoError(t, err) - assert.Equal(t, TransactionType1559, raw[0]) - rlpList, _, err := rlp.Decode(raw[1:]) + signer, txr, err := RecoverRawTransaction(context.Background(), raw, 1001) assert.NoError(t, err) - foundSig := &secp256k1.SignatureData{ - V: new(big.Int), - R: new(big.Int), - S: new(big.Int), - } - foundSig.V.SetBytes([]byte(rlpList.(rlp.List)[9].(rlp.Data))) - foundSig.R.SetBytes([]byte(rlpList.(rlp.List)[10].(rlp.Data))) - foundSig.S.SetBytes([]byte(rlpList.(rlp.List)[11].(rlp.Data))) - - signaturePayload := txn.SignaturePayload(1001) - addr, err := foundSig.Recover(signaturePayload.Bytes(), 1001) - assert.NoError(t, err) - assert.Equal(t, keypair.Address.String(), addr.String()) + assert.Equal(t, keypair.Address.String(), signer.String()) + jsonCompare(t, txn, *txr) } @@ -199,21 +180,10 @@ func TestSignLegacyOriginal(t *testing.T) { raw, err := txn.SignLegacyOriginal(keypair) assert.NoError(t, err) - rlpList, _, err := rlp.Decode(raw) + signer, txr, err := RecoverRawTransaction(context.Background(), raw, 1001) assert.NoError(t, err) - foundSig := &secp256k1.SignatureData{ - V: new(big.Int), - R: new(big.Int), - S: new(big.Int), - } - foundSig.V.SetBytes([]byte(rlpList.(rlp.List)[6].(rlp.Data))) - foundSig.R.SetBytes([]byte(rlpList.(rlp.List)[7].(rlp.Data))) - foundSig.S.SetBytes([]byte(rlpList.(rlp.List)[8].(rlp.Data))) - - signaturePayload := txn.SignaturePayloadLegacyOriginal() - addr, err := foundSig.Recover(signaturePayload.Bytes(), 0) - assert.NoError(t, err) - assert.Equal(t, keypair.Address.String(), addr.String()) + assert.Equal(t, keypair.Address.String(), signer.String()) + jsonCompare(t, txn, *txr) } @@ -270,3 +240,107 @@ func TestSignEIP1559Error(t *testing.T) { func TestEthTXDocumented(t *testing.T) { ffapi.CheckObjectDocumented(&Transaction{}) } + +func jsonCompare(t *testing.T, expected, actual interface{}) { + expectedJSON, err := json.Marshal(expected) + assert.NoError(t, err) + actualJSON, err := json.Marshal(actual) + assert.NoError(t, err) + assert.JSONEq(t, (string)(expectedJSON), (string)(actualJSON)) + +} + +func TestRecoverRawTransactionEmpty(t *testing.T) { + _, _, err := RecoverRawTransaction(context.Background(), []byte{}, 1001) + assert.Regexp(t, "FF22081", err) +} + +func TestRecoverRawTransactionInvalidType(t *testing.T) { + _, _, err := RecoverRawTransaction(context.Background(), []byte{0x03}, 1001) + assert.Regexp(t, "FF22082.*0x03", err) +} + +func TestRecoverLegacyTransactionEmpty(t *testing.T) { + _, _, err := RecoverLegacyRawTransaction(context.Background(), []byte{}, 1001) + assert.Regexp(t, "FF22083", err) +} + +func TestRecoverLegacyBadData(t *testing.T) { + _, _, err := RecoverLegacyRawTransaction(context.Background(), []byte{0xff}, 1001) + assert.Regexp(t, "FF22083", err) +} + +func TestRecoverLegacyBadStructure(t *testing.T) { + _, _, err := RecoverLegacyRawTransaction(context.Background(), (rlp.List{ + rlp.WrapInt(big.NewInt(12345)), + }).Encode(), 1001) + assert.Regexp(t, "FF22083.*EOF", err) +} + +func TestRecoverLegacyBadSignature(t *testing.T) { + _, _, err := RecoverLegacyRawTransaction(context.Background(), (rlp.List{ + rlp.WrapInt(big.NewInt(111)), + rlp.WrapInt(big.NewInt(222)), + rlp.WrapInt(big.NewInt(333)), + rlp.WrapInt(big.NewInt(444)), + rlp.WrapInt(big.NewInt(555)), + rlp.WrapInt(big.NewInt(666)), + rlp.WrapInt(big.NewInt(26 /* bad V */)), + rlp.WrapInt(big.NewInt(888)), + rlp.WrapInt(big.NewInt(999)), + }).Encode(), 1001) + assert.Regexp(t, "invalid", err) +} + +func TestRecoverEIP1559TransactionEmpty(t *testing.T) { + _, _, err := RecoverEIP1559Transaction(context.Background(), []byte{}, 1001) + assert.Regexp(t, "FF22084.*TransactionType", err) +} + +func TestRecoverEIP1559BadData(t *testing.T) { + _, _, err := RecoverEIP1559Transaction(context.Background(), []byte{TransactionType1559, 0xff}, 1001) + assert.Regexp(t, "FF22084", err) +} + +func TestRecoverEIP1559BadStructure(t *testing.T) { + _, _, err := RecoverEIP1559Transaction(context.Background(), append([]byte{TransactionType1559}, (rlp.List{ + rlp.WrapInt(big.NewInt(12345)), + }).Encode()...), 1001) + assert.Regexp(t, "FF22084.*EOF", err) +} + +func TestRecoverEIP1559BadChainID(t *testing.T) { + _, _, err := RecoverEIP1559Transaction(context.Background(), append([]byte{TransactionType1559}, (rlp.List{ + rlp.WrapInt(big.NewInt(111)), + rlp.WrapInt(big.NewInt(222)), + rlp.WrapInt(big.NewInt(333)), + rlp.WrapInt(big.NewInt(444)), + rlp.WrapInt(big.NewInt(555)), + rlp.WrapInt(big.NewInt(666)), + rlp.WrapInt(big.NewInt(777)), + rlp.WrapInt(big.NewInt(888)), + rlp.WrapInt(big.NewInt(999)), + rlp.WrapInt(big.NewInt(111)), + rlp.WrapInt(big.NewInt(223)), + rlp.WrapInt(big.NewInt(333)), + }).Encode()...), 1001) + assert.Regexp(t, "FF22086.*1,001.*111", err) +} + +func TestRecoverEIP1559Signature(t *testing.T) { + _, _, err := RecoverEIP1559Transaction(context.Background(), append([]byte{TransactionType1559}, (rlp.List{ + rlp.WrapInt(big.NewInt(1001)), + rlp.WrapInt(big.NewInt(222)), + rlp.WrapInt(big.NewInt(333)), + rlp.WrapInt(big.NewInt(444)), + rlp.WrapInt(big.NewInt(555)), + rlp.WrapInt(big.NewInt(666)), + rlp.WrapInt(big.NewInt(777)), + rlp.WrapInt(big.NewInt(888)), + rlp.WrapInt(big.NewInt(999)), + rlp.WrapInt(big.NewInt(111)), + rlp.WrapInt(big.NewInt(223)), + rlp.WrapInt(big.NewInt(333)), + }).Encode()...), 1001) + assert.Regexp(t, "invalid", err) +} diff --git a/pkg/ethsigner/typed_data.go b/pkg/ethsigner/typed_data.go index 63aaeba..0996bba 100644 --- a/pkg/ethsigner/typed_data.go +++ b/pkg/ethsigner/typed_data.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Kaleido, Inc. +// Copyright © 2024 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // diff --git a/pkg/ethsigner/wallet.go b/pkg/ethsigner/wallet.go index 30d66cf..9c8cd22 100644 --- a/pkg/ethsigner/wallet.go +++ b/pkg/ethsigner/wallet.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Kaleido, Inc. +// Copyright © 2024 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // diff --git a/pkg/rlp/decode.go b/pkg/rlp/decode.go index 037ac1b..64d796e 100644 --- a/pkg/rlp/decode.go +++ b/pkg/rlp/decode.go @@ -1,4 +1,4 @@ -// Copyright © 2022 Kaleido, Inc. +// Copyright © 2024 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // diff --git a/pkg/rlp/encode.go b/pkg/rlp/encode.go index 4dc41c8..5002960 100644 --- a/pkg/rlp/encode.go +++ b/pkg/rlp/encode.go @@ -1,4 +1,4 @@ -// Copyright © 2022 Kaleido, Inc. +// Copyright © 2024 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // diff --git a/pkg/rlp/rlp.go b/pkg/rlp/rlp.go index 6c5cb79..59b5208 100644 --- a/pkg/rlp/rlp.go +++ b/pkg/rlp/rlp.go @@ -1,4 +1,4 @@ -// Copyright © 2022 Kaleido, Inc. +// Copyright © 2024 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -36,6 +36,8 @@ type Element interface { IsList() bool // Encode converts the element to a byte array Encode() []byte + // Safe function that will give an entry as data, to use the nil-safe functions on it to get the value (will be treated as nil data for list) + ToData() Data } // WrapString converts a plain string to an RLP Data element for encoding @@ -83,6 +85,28 @@ func (r Data) Int() *big.Int { return i.SetBytes(r) } +func (r Data) IntOrZero() *big.Int { + if r == nil { + return big.NewInt(0) + } + i := new(big.Int) + return i.SetBytes(r) +} + +func (r Data) BytesNotNil() []byte { + if r == nil { + return []byte{} + } + return r +} + +func (r Data) Address() *ethtypes.Address0xHex { + if r == nil || len(r) != 20 { + return nil + } + return (*ethtypes.Address0xHex)(r) +} + // Encode encodes this individual RLP Data element func (r Data) Encode() []byte { return encodeBytes(r, false) @@ -93,6 +117,10 @@ func (r Data) IsList() bool { return false } +func (r Data) ToData() Data { + return r +} + // Encode encodes the RLP List to a byte array, including recursing into child arrays func (l List) Encode() []byte { if len(l) == 0 { @@ -110,3 +138,8 @@ func (l List) Encode() []byte { func (l List) IsList() bool { return true } + +func (l List) ToData() Data { + // This allows code to not worry about lots of type checking - a list is treated as nil data + return nil +} diff --git a/pkg/rlp/rlp_test.go b/pkg/rlp/rlp_test.go new file mode 100644 index 0000000..4c137dc --- /dev/null +++ b/pkg/rlp/rlp_test.go @@ -0,0 +1,54 @@ +// Copyright © 2024 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rlp + +import ( + "testing" + + "github.com/hyperledger/firefly-signer/pkg/ethtypes" + "github.com/stretchr/testify/assert" +) + +func TestDataBytes(t *testing.T) { + + assert.Nil(t, ((List)(nil)).ToData()) + assert.Nil(t, ((Data)(nil)).ToData()) + assert.Equal(t, Data{0xff}, ((Data)([]byte{0xff})).ToData()) + +} + +func TestDataIntOrZero(t *testing.T) { + + assert.Equal(t, int64(0), ((List)(nil)).ToData().IntOrZero().Int64()) + assert.Equal(t, int64(0xff), ((Data)([]byte{0xff})).ToData().IntOrZero().Int64()) + +} + +func TestDataBytesNotNil(t *testing.T) { + + assert.Equal(t, []byte{}, ((List)(nil)).ToData().BytesNotNil()) + assert.Equal(t, []byte{0xff}, ((Data)([]byte{0xff})).ToData().BytesNotNil()) + +} + +func TestAddress(t *testing.T) { + + assert.Nil(t, ((List)(nil)).ToData().Address()) + assert.Nil(t, (Data{0x00}).Address()) + assert.Equal(t, "0x4f78181c7fdc267d953a3cba8079f899d7f5ba78", (Data)(ethtypes.MustNewAddress("0x4F78181C7fdC267d953A3cBa8079f899D7F5BA78")[:]).Address().String()) + +} From f5a3398645c6603f3e40c1b8e2745a1d0887ac17 Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Fri, 3 May 2024 12:02:59 -0400 Subject: [PATCH 2/3] Missed copyright Signed-off-by: Peter Broadhurst --- internal/signermsgs/en_error_messges.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/signermsgs/en_error_messges.go b/internal/signermsgs/en_error_messges.go index a25968a..743a033 100644 --- a/internal/signermsgs/en_error_messges.go +++ b/internal/signermsgs/en_error_messges.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Kaleido, Inc. +// Copyright © 2024 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // From 3e5403b6b347d205b5eff4944a97f8a673a4d98d Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Fri, 3 May 2024 13:23:08 -0400 Subject: [PATCH 3/3] Correct v matching logic for EIP-155 detection Signed-off-by: Peter Broadhurst --- internal/signermsgs/en_error_messges.go | 2 +- pkg/ethsigner/transaction.go | 58 ++++++++++++------------- pkg/ethsigner/transaction_test.go | 15 ------- 3 files changed, 28 insertions(+), 47 deletions(-) diff --git a/internal/signermsgs/en_error_messges.go b/internal/signermsgs/en_error_messges.go index 743a033..4528d4e 100644 --- a/internal/signermsgs/en_error_messges.go +++ b/internal/signermsgs/en_error_messges.go @@ -101,6 +101,6 @@ var ( MsgUnsupportedTransactionType = ffe("FF22082", "Unsupported transaction type 0x%02x") MsgInvalidLegacyTransaction = ffe("FF22083", "Transaction payload invalid (legacy): %v") MsgInvalidEIP1559Transaction = ffe("FF22084", "Transaction payload invalid (EIP-1559): %v") - MsgInvalidEIP155TransactionV = ffe("FF22085", "Invalid V value from EIP-155 transaction (chainId=%d): %d") + MsgInvalidEIP155TransactionV = ffe("FF22085", "Invalid V value from EIP-155 transaction (chainId=%d)") MsgInvalidChainID = ffe("FF22086", "Invalid chainId expected=%d actual=%d") ) diff --git a/pkg/ethsigner/transaction.go b/pkg/ethsigner/transaction.go index b2080f3..f5e4a8f 100644 --- a/pkg/ethsigner/transaction.go +++ b/pkg/ethsigner/transaction.go @@ -239,23 +239,17 @@ func RecoverLegacyRawTransaction(ctx context.Context, rawTx ethtypes.HexBytes0xP Data: ethtypes.HexBytes0xPrefix(rlpList[5].ToData()), } - foundSig := &secp256k1.SignatureData{ - V: new(big.Int), - R: new(big.Int), - S: new(big.Int), - } vValue := rlpList[6].ToData().Int().Int64() - foundSig.R.SetBytes(rlpList[7].ToData().BytesNotNil()) - foundSig.S.SetBytes(rlpList[8].ToData().BytesNotNil()) + rValue := rlpList[7].ToData().BytesNotNil() + sValue := rlpList[8].ToData().BytesNotNil() var message []byte - if vValue > 28 { + if vValue != 27 && vValue != 28 { // Legacy with EIP155 extensions - newV := vValue - (chainID * 2) - 8 - if newV != 27 && newV != 28 { - return nil, nil, i18n.NewError(ctx, signermsgs.MsgInvalidEIP155TransactionV, chainID, foundSig.V.Int64()) + vValue = vValue - (chainID * 2) - 8 + if vValue != 27 && vValue != 28 { + return nil, nil, i18n.NewError(ctx, signermsgs.MsgInvalidEIP155TransactionV, chainID) } - foundSig.V.SetInt64(newV) signedRLPList := make(rlp.List, 6, 9) copy(signedRLPList, rlpList[0:6]) @@ -263,10 +257,23 @@ func RecoverLegacyRawTransaction(ctx context.Context, rawTx ethtypes.HexBytes0xP message = signedRLPList.Encode() } else { // Legacy original transaction - foundSig.V.SetInt64(vValue) message = (rlpList[0:6]).Encode() } + return recoverCommon(tx, message, chainID, vValue, rValue, sValue) + +} + +func recoverCommon(tx *Transaction, message []byte, chainID int64, v int64, r, s []byte) (*ethtypes.Address0xHex, *Transaction, error) { + foundSig := &secp256k1.SignatureData{ + V: new(big.Int), + R: new(big.Int), + S: new(big.Int), + } + foundSig.V.SetInt64(v) + foundSig.R.SetBytes(r) + foundSig.S.SetBytes(s) + signer, err := foundSig.Recover(message, chainID) if err != nil { return nil, nil, err @@ -309,24 +316,13 @@ func RecoverEIP1559Transaction(ctx context.Context, rawTx ethtypes.HexBytes0xPre // No access list support } - foundSig := &secp256k1.SignatureData{ - V: new(big.Int), - R: new(big.Int), - S: new(big.Int), - } - foundSig.V.SetInt64(rlpList[9].ToData().Int().Int64()) - foundSig.R.SetBytes(rlpList[10].ToData().BytesNotNil()) - foundSig.S.SetBytes(rlpList[11].ToData().BytesNotNil()) - - message := append([]byte{TransactionType1559}, (rlpList[0:9]).Encode()...) - fmt.Println((ethtypes.HexBytes0xPrefix)(message).String()) - - signer, err := foundSig.Recover(message, chainID) - if err != nil { - return nil, nil, err - } - - return signer, tx, nil + return recoverCommon(tx, + append([]byte{TransactionType1559}, (rlpList[0:9]).Encode()...), + chainID, + rlpList[9].ToData().Int().Int64(), + rlpList[10].ToData().BytesNotNil(), + rlpList[11].ToData().BytesNotNil(), + ) } func RecoverRawTransaction(ctx context.Context, rawTx ethtypes.HexBytes0xPrefix, chainID int64) (*ethtypes.Address0xHex, *Transaction, error) { diff --git a/pkg/ethsigner/transaction_test.go b/pkg/ethsigner/transaction_test.go index b9c0cbc..d34bd4e 100644 --- a/pkg/ethsigner/transaction_test.go +++ b/pkg/ethsigner/transaction_test.go @@ -277,21 +277,6 @@ func TestRecoverLegacyBadStructure(t *testing.T) { assert.Regexp(t, "FF22083.*EOF", err) } -func TestRecoverLegacyBadSignature(t *testing.T) { - _, _, err := RecoverLegacyRawTransaction(context.Background(), (rlp.List{ - rlp.WrapInt(big.NewInt(111)), - rlp.WrapInt(big.NewInt(222)), - rlp.WrapInt(big.NewInt(333)), - rlp.WrapInt(big.NewInt(444)), - rlp.WrapInt(big.NewInt(555)), - rlp.WrapInt(big.NewInt(666)), - rlp.WrapInt(big.NewInt(26 /* bad V */)), - rlp.WrapInt(big.NewInt(888)), - rlp.WrapInt(big.NewInt(999)), - }).Encode(), 1001) - assert.Regexp(t, "invalid", err) -} - func TestRecoverEIP1559TransactionEmpty(t *testing.T) { _, _, err := RecoverEIP1559Transaction(context.Background(), []byte{}, 1001) assert.Regexp(t, "FF22084.*TransactionType", err)