diff --git a/CHANGELOG.md b/CHANGELOG.md index 028b4fea..d512be7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.9.0 + +- Fix issue with Ethereum RLP encoding related to leading zero in signature S. + ## 3.8.0 - Update dependencies. diff --git a/lib/ethereum/src/transaction/eth_transaction.dart b/lib/ethereum/src/transaction/eth_transaction.dart index bdd10724..9ac38e22 100644 --- a/lib/ethereum/src/transaction/eth_transaction.dart +++ b/lib/ethereum/src/transaction/eth_transaction.dart @@ -65,6 +65,22 @@ class _ETHTransactionUtils { return (BigInt.from(v) - BigInt.from(35)) ~/ BigInt.two; } + static List trimLeadingZero(List bytes) { + List data = bytes; + while (data.isNotEmpty) { + if (data[0] != 0) break; + data = data.sublist(1); + } + return data; + } + + static List leadingZero32Bytes(List bytes) { + if (bytes.length >= 32) return bytes; + final data = List.filled(32, 0); + data.setAll(32 - bytes.length, bytes); + return data; + } + /// Returns the parity for a given integer [v]. /// Returns 0 if [v] is 27, otherwise returns 1. static int parity(int v) => (v == 27) ? 0 : 1; @@ -92,8 +108,10 @@ class _ETHTransactionUtils { ETHSignature? sig; BigInt chainId = BigInt.zero; if (decode.length > 6) { - final List rBytes = List.from(decode[7]); - final List sBytes = List.from(decode[8]); + final List rBytes = + _ETHTransactionUtils.leadingZero32Bytes(List.from(decode[7])); + final List sBytes = + _ETHTransactionUtils.leadingZero32Bytes(List.from(decode[8])); final v = IntUtils.fromBytes(decode[6]); if (rBytes.isEmpty && sBytes.isEmpty) { chainId = BigInt.from(v); @@ -130,8 +148,11 @@ class _ETHTransactionUtils { .toList(); ETHSignature? sig; if (decode.length > 8) { - final sigBytes = - List.from([...decode[9], ...decode[10], ...decode[8]]); + final List rBytes = + _ETHTransactionUtils.leadingZero32Bytes(List.from(decode[9])); + final List sBytes = + _ETHTransactionUtils.leadingZero32Bytes(List.from(decode[10])); + final sigBytes = List.from([...rBytes, ...sBytes, ...decode[8]]); sig = ETHSignature.fromBytes(sigBytes); } return ETHTransaction._( @@ -164,8 +185,11 @@ class _ETHTransactionUtils { .toList(); ETHSignature? sig; if (decode.length > 9) { - final sigBytes = - List.from([...decode[10], ...decode[11], ...decode[9]]); + final List rBytes = + _ETHTransactionUtils.leadingZero32Bytes(List.from(decode[10])); + final List sBytes = + _ETHTransactionUtils.leadingZero32Bytes(List.from(decode[11])); + final sigBytes = List.from([...rBytes, ...sBytes, ...decode[9]]); sig = ETHSignature.fromBytes(sigBytes); } return ETHTransaction._( @@ -374,9 +398,10 @@ class ETHTransaction { if (sig != null) { fields.add( _ETHTransactionUtils.intToBytes(_ETHTransactionUtils.parity(sig.v))); - fields.add(sig.rBytes); - fields.add(sig.sBytes); + fields.add(_ETHTransactionUtils.trimLeadingZero(sig.rBytes)); + fields.add(_ETHTransactionUtils.trimLeadingZero(sig.sBytes)); } + return [ETHTransactionType.eip1559.prefix, ...RLPEncoder.encode(fields)]; } @@ -396,8 +421,8 @@ class ETHTransaction { if (sig != null) { fields.add( _ETHTransactionUtils.intToBytes(_ETHTransactionUtils.parity(sig.v))); - fields.add(sig.rBytes); - fields.add(sig.sBytes); + fields.add(_ETHTransactionUtils.trimLeadingZero(sig.rBytes)); + fields.add(_ETHTransactionUtils.trimLeadingZero(sig.sBytes)); } return [ETHTransactionType.eip2930.prefix, ...RLPEncoder.encode(fields)]; } @@ -432,8 +457,8 @@ class ETHTransaction { throw const MessageException("Mismatch chainID/Signature.V"); } fields.add(BigintUtils.toBytes(v, length: BigintUtils.bitlengthInBytes(v))); - fields.add(sig.rBytes); - fields.add(sig.sBytes); + fields.add(_ETHTransactionUtils.trimLeadingZero(sig.rBytes)); + fields.add(_ETHTransactionUtils.trimLeadingZero(sig.sBytes)); return RLPEncoder.encode(fields); } diff --git a/pubspec.yaml b/pubspec.yaml index 4e13c020..2b805d93 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: on_chain description: Streamline Ethereum, Tron, Solana and Cardano operations. Effortlessly create transactions, interact with smart contracts, sign, and send transactions. -version: 3.8.0 +version: 3.9.0 homepage: "https://github.com/mrtnetwork/on_chain" repository: "https://github.com/mrtnetwork/on_chain" Author: mrhaydari.t@gmail.com diff --git a/test/etherum/transaction_test.dart b/test/etherum/transaction_test.dart new file mode 100644 index 00000000..c6e3ea74 --- /dev/null +++ b/test/etherum/transaction_test.dart @@ -0,0 +1,30 @@ +import 'package:on_chain/ethereum/ethereum.dart'; +import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:test/test.dart'; + +void main() { + /// transaction with leading zero s bytes. + test("transaction leading zero s", () { + final addr = ETHAddress("0x084937B3f86ea7BbCA86F2809809A65ED8A7ADa9"); + final signer = ETHSigner.fromKeyBytes(BytesUtils.fromHexString( + "e9f4fe38ffc54abd156dd4b8a39611fce696af62841ee6422ee36ba7b26c53f5")); + + final receiver = ETHAddress("0x4fAfB33f0e492FD10e91b55ED88872104fFd94ee"); + final transaction = ETHTransaction( + nonce: 0, + from: addr, + type: ETHTransactionType.legacy, + to: receiver, + gasLimit: BigInt.from(21000), + data: const [], + value: ETHHelper.toWei("0.01"), + chainId: BigInt.from(97), + gasPrice: BigInt.from(5000000000)); + final serialize = transaction.serialized; + final sign = signer.sign(serialize); + final signedSerialize = transaction.signedSerialized(sign); + + final decode = ETHTransaction.fromSerialized(signedSerialize); + expect(decode.signedSerialized(), signedSerialize); + }); +}