Skip to content

Commit

Permalink
Fix transaction rlp enconding (#60)
Browse files Browse the repository at this point in the history
  • Loading branch information
elgatovital authored Aug 29, 2023
1 parent a84316d commit 2177c9b
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package pm.gnosis.svalinn.accounts.utils

import androidx.annotation.VisibleForTesting
import org.kethereum.functions.rlp.RLPElement
import org.kethereum.functions.rlp.RLPList
import org.kethereum.functions.rlp.RLPType
Expand All @@ -13,7 +14,6 @@ import java.math.BigInteger

fun Transaction.Eip1559.rlp(signature: ECDSASignature? = null): ByteArray {
val items = ArrayList<RLPType>()
items.add(type.toRLP())
items.add(chainId.toRLP())
items.add(nonce!!.toRLP())
items.add((maxPriorityFee ?: BigInteger.ZERO).toRLP())
Expand Down Expand Up @@ -46,7 +46,7 @@ private fun Transaction.Eip1559.adjustV(v: Byte): BigInteger {
return BigInteger.valueOf(v.toLong() - 27)
}

fun Transaction.Eip1559.hash(ecdsaSignature: ECDSASignature? = null) = rlp(ecdsaSignature).let { Sha3Utils.keccak(it) }
fun Transaction.Eip1559.hash(ecdsaSignature: ECDSASignature? = null) = byteArrayOf(type, *rlp(ecdsaSignature)).let { Sha3Utils.keccak(it) }

fun Transaction.Legacy.rlp(signature: ECDSASignature? = null): ByteArray {
val items = ArrayList<RLPElement>()
Expand All @@ -60,7 +60,7 @@ fun Transaction.Legacy.rlp(signature: ECDSASignature? = null): ByteArray {
items.add(adjustV(signature.v).toRLP())
items.add(signature.r.toRLP())
items.add(signature.s.toRLP())
} else {
} else if (chainId > BigInteger.ZERO) {
items.add(chainId.toRLP())
items.add(0.toRLP())
items.add(0.toRLP())
Expand All @@ -71,14 +71,15 @@ fun Transaction.Legacy.rlp(signature: ECDSASignature? = null): ByteArray {

fun Transaction.Legacy.hash(ecdsaSignature: ECDSASignature? = null) = rlp(ecdsaSignature).let { Sha3Utils.keccak(it) }

private fun Transaction.Legacy.adjustV(v: Byte): BigInteger {
@VisibleForTesting
fun Transaction.Legacy.adjustV(v: Byte): BigInteger {
// requires v = {0, 1} or v = {27, 28}
if (chainId > BigInteger.ZERO) {
// EIP-155
// If you do, then the v of the signature MUST be set to {0,1} + CHAIN_ID * 2 + 35
// otherwise then v continues to be set to {0,1} + 27 as previously.
if (v in 0..1) {
BigInteger.valueOf(v.toLong()).plus(chainId.multiply(BigInteger.valueOf(2)).plus(BigInteger.valueOf(35))).toByte()
return BigInteger.valueOf(v.toLong()).plus(chainId.multiply(BigInteger.valueOf(2)).plus(BigInteger.valueOf(35)))
} else if (v in 27..28) {
// KeyPair signature is always 27 or 28
return BigInteger.valueOf(v.toLong() - 27).plus(chainId.multiply(BigInteger.valueOf(2)).plus(BigInteger.valueOf(35)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import pm.gnosis.svalinn.accounts.base.models.Signature
import pm.gnosis.svalinn.accounts.data.db.AccountDao
import pm.gnosis.svalinn.accounts.data.db.AccountsDatabase
import pm.gnosis.svalinn.accounts.repositories.impls.models.db.AccountDb
import pm.gnosis.svalinn.accounts.utils.adjustV
import pm.gnosis.svalinn.accounts.utils.hash
import pm.gnosis.svalinn.accounts.utils.rlp
import pm.gnosis.svalinn.common.PreferencesManager
Expand Down Expand Up @@ -66,7 +67,7 @@ class KethereumAccountsRepositoryTest {
}

@Test
fun signTransactionEIP155Example() {
fun signTransactionLegacyEIP155Example() {
/* Node.js code:
const Tx = require('ethereumjs-tx')
var tra = {nonce: 9, gasPrice: web3.toHex(20000000000), gasLimit: web3.toHex(21000), data: '', to: '0x3535353535353535353535353535353535353535', chainId: 1, value: 1000000000000000000};
Expand Down Expand Up @@ -96,7 +97,32 @@ class KethereumAccountsRepositoryTest {
}

@Test
fun rlpTransactionExtension() {
fun rlpTransactionLegacyAdjustV() {
val tx = Transaction.Legacy(
to = Solidity.Address(BigInteger.ZERO),
nonce = BigInteger.ZERO,
data = "",
gas = BigInteger.ZERO,
gasPrice = BigInteger.ZERO,
chainId = BigInteger.valueOf(1)
)

val v1 = 0
val v1Adjusted = tx.adjustV(v1.toByte())
assertEquals(BigInteger.valueOf(37), v1Adjusted)

val v2 = 27
val v2Adjusted = tx.adjustV(v2.toByte())
assertEquals(BigInteger.valueOf(37), v2Adjusted)

val txZeroChainId = tx.copy(chainId = BigInteger.ZERO)
val v3 = 0
val v3Adjusted = txZeroChainId.adjustV(v3.toByte())
assertEquals(BigInteger.ZERO, v3Adjusted)
}

@Test
fun rlpTransactionLegacyExtension() {
val address = "0x19fd8863ea1185d8ef7ab3f2a8f4d469dc35dd52".asEthereumAddress()!!
val nonce = BigInteger("13")
val gas = BigInteger("2034776")
Expand All @@ -108,12 +134,25 @@ class KethereumAccountsRepositoryTest {
}

@Test
fun transactionHashWithSignature() {
fun rlpTransactionEip1559Extension() {
val address = "0x19fd8863ea1185d8ef7ab3f2a8f4d469dc35dd52".asEthereumAddress()!!
val nonce = BigInteger("13")
val gas = BigInteger("2034776")
val maxPriorityFee = BigInteger("20000000000")
val maxFeePerGas = BigInteger("20000000001")
val transaction =
Transaction.Eip1559(to = address, nonce = nonce, data = "0xe411526d", gas = gas, maxPriorityFee = maxPriorityFee, maxFeePerGas = maxFeePerGas, chainId = BigInteger.valueOf(28))
val expectedString = "ee1c0d8504a817c8008504a817c801831f0c589419fd8863ea1185d8ef7ab3f2a8f4d469dc35dd528084e411526dc0"
assertEquals(expectedString, transaction.rlp().toHexString())
}

@Test
fun transactionLegacyHashWithSignature() {
val tx = Transaction.Legacy(
to = Solidity.Address(BigInteger.ZERO),
gas = 411632.toBigInteger(),
gasPrice = 4000000000.toBigInteger(),
data = "0x608060405234801561001057600080fd5b50604051610502380380610502833981018060405281019080805190602001909291908051820192919060200180519060200190929190805190602001909291908051906020019092919050505084848160008173ffffffffffffffffffffffffffffffffffffffff1614151515610116576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001807f496e76616c6964206d617374657220636f707920616464726573732070726f7681526020017f696465640000000000000000000000000000000000000000000000000000000081525060400191505060405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550506000815111156101a35773ffffffffffffffffffffffffffffffffffffffff60005416600080835160208501846127105a03f46040513d6000823e600082141561019f573d81fd5b5050505b5050600081111561038357600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561022f578273ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050158015610229573d6000803e3d6000fd5b50610382565b8173ffffffffffffffffffffffffffffffffffffffff1663a9059cbb84836040518363ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b1580156102d257600080fd5b505af11580156102e6573d6000803e3d6000fd5b505050506040513d60208110156102fc57600080fd5b81019080805190602001909291905050501515610381576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601f8152602001807f436f756c64206e6f74206578656375746520746f6b656e207061796d656e740081525060200191505060405180910390fd5b5b5b505050505061016b806103976000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680634555d5c91461008b5780635c60da1b146100b6575b73ffffffffffffffffffffffffffffffffffffffff600054163660008037600080366000845af43d6000803e6000811415610086573d6000fd5b3d6000f35b34801561009757600080fd5b506100a061010d565b6040518082815260200191505060405180910390f35b3480156100c257600080fd5b506100cb610116565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60006002905090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050905600a165627a7a723058207b6793b265137e04ae615face5a75e9297b0f75d2194a1fd45dde37e08af9e730029000000000000000000000000ec7c75c1548765ab51a165873b0b1b71663c126600000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000ab8c18e66135561676f0781555d05cf6b22024a300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005d9822c8d80000000000000000000000000000000000000000000000000000000000000000164a04222e1000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000040000000000000000000000007b35526cab9f5599de410160a0e51533cfafc33e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d84ab641422351a85914657cdbd9ec28aaa286af00000000000000000000000048e3b0ff14c8913fd515578b7b7499bf2443dbb80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
data = TX_DATA,
nonce = BigInteger.ZERO
)
val hash = tx.hash(
Expand All @@ -125,6 +164,25 @@ class KethereumAccountsRepositoryTest {
assertEquals("34aa96388d39b09285391e49c4ad03bbffc535cd94c27f2fa287fe68f83f584e", hash)
}

@Test
fun transactionEip1559HashWithSignature() {
val tx = Transaction.Eip1559(
to = Solidity.Address(BigInteger.ZERO),
gas = 411632.toBigInteger(),
maxPriorityFee = 4000000000.toBigInteger(),
maxFeePerGas = 4000000001.toBigInteger(),
data = TX_DATA,
nonce = BigInteger.ZERO
)
val hash = tx.hash(
ECDSASignature(
r = BigInteger("18791006335898280803252024165139053571562361633090590726732379820185947067"),
s = BigInteger("2647276639964841513316066866950659665933281879584930681484385164847771956325")
).apply { v = 28.toByte() }
).toHexString()
assertEquals("9ef0dc2f08189b8993ef56550a8056b80260ad7402d8dca72ac0ab0e8780b901", hash)
}

@Test
fun signTransactionCreateSafe() {
/* Node.js code:
Expand All @@ -149,7 +207,7 @@ class KethereumAccountsRepositoryTest {
repository.signTransaction(transaction).subscribe(testObserver)

val expectedTx =
"0xf8ea0d8504a817c800831f0c589419fd8863ea1185d8ef7ab3f2a8f4d469dc35dd5280b884e411526d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a5056c8efadb5d6a1a6eb0176615692b6e6483131ba0f5493cfd4dcf5f2a2092e58bf9020ab67d4b8a05bf41cce017fb9f6d59ced65aa0046b9892f40592d6cc156008eaf2b43d72a4a3c016e8f66e61890868c8c0d8b5"
"0xf8ea0d8504a817c800831f0c589419fd8863ea1185d8ef7ab3f2a8f4d469dc35dd5280b884e411526d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a5056c8efadb5d6a1a6eb0176615692b6e6483131ba067f34dee111e9ab4a034eccb458403b1ee5b80ac801f161d8151f75faa5fbb71a05c88f85eaa2bd540c6d7778787016c27ac92e706dbb19e07c61433d61bf6925f"
testObserver.assertResult(expectedTx)
}

Expand Down Expand Up @@ -200,5 +258,8 @@ class KethereumAccountsRepositoryTest {
"0000000000000000000000000000000000000000000000000000000000000001" +
"0000000000000000000000000000000000000000000000000000000000000001" +
"000000000000000000000000a5056c8efadb5d6a1a6eb0176615692b6e648313"

const val TX_DATA =
"0x608060405234801561001057600080fd5b50604051610502380380610502833981018060405281019080805190602001909291908051820192919060200180519060200190929190805190602001909291908051906020019092919050505084848160008173ffffffffffffffffffffffffffffffffffffffff1614151515610116576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001807f496e76616c6964206d617374657220636f707920616464726573732070726f7681526020017f696465640000000000000000000000000000000000000000000000000000000081525060400191505060405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550506000815111156101a35773ffffffffffffffffffffffffffffffffffffffff60005416600080835160208501846127105a03f46040513d6000823e600082141561019f573d81fd5b5050505b5050600081111561038357600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561022f578273ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050158015610229573d6000803e3d6000fd5b50610382565b8173ffffffffffffffffffffffffffffffffffffffff1663a9059cbb84836040518363ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b1580156102d257600080fd5b505af11580156102e6573d6000803e3d6000fd5b505050506040513d60208110156102fc57600080fd5b81019080805190602001909291905050501515610381576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601f8152602001807f436f756c64206e6f74206578656375746520746f6b656e207061796d656e740081525060200191505060405180910390fd5b5b5b505050505061016b806103976000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680634555d5c91461008b5780635c60da1b146100b6575b73ffffffffffffffffffffffffffffffffffffffff600054163660008037600080366000845af43d6000803e6000811415610086573d6000fd5b3d6000f35b34801561009757600080fd5b506100a061010d565b6040518082815260200191505060405180910390f35b3480156100c257600080fd5b506100cb610116565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60006002905090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050905600a165627a7a723058207b6793b265137e04ae615face5a75e9297b0f75d2194a1fd45dde37e08af9e730029000000000000000000000000ec7c75c1548765ab51a165873b0b1b71663c126600000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000ab8c18e66135561676f0781555d05cf6b22024a300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005d9822c8d80000000000000000000000000000000000000000000000000000000000000000164a04222e1000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000040000000000000000000000007b35526cab9f5599de410160a0e51533cfafc33e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d84ab641422351a85914657cdbd9ec28aaa286af00000000000000000000000048e3b0ff14c8913fd515578b7b7499bf2443dbb80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import pm.gnosis.ethereum.rpc.models.*
class RetrofitEthereumRpcConnector(private val api: RetrofitEthereumRpcApi, override var rpcUrl: String) : EthereumRpcConnector {

override suspend fun receipt(jsonRpcRequest: JsonRpcRequest): JsonRpcTransactionReceiptResult {
return api.receipt(rpcUrl,jsonRpcRequest)
return api.receipt(rpcUrl, jsonRpcRequest)
}

override suspend fun block(jsonRpcRequest: JsonRpcRequest): JsonRpcBlockResult {
Expand Down
2 changes: 1 addition & 1 deletion models/src/main/java/pm/gnosis/models/Transaction.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ sealed class Transaction {
val accessList: List<Pair<String, List<String>>> = emptyList()
) : Transaction() {
// Type of a transaction with priority fee. Defined in EIP-1559
val type: BigInteger = "0x02".hexAsBigInteger()
val type: Byte = "0x02".hexAsBigInteger().toByte()
}

companion object {
Expand Down
2 changes: 2 additions & 0 deletions utils/src/main/java/pm/gnosis/utils/NumberUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ fun ByteArray.asBigInteger() = BigInteger(1, this)

fun BigInteger.toHexString() = this.toString(16).addHexPrefix()

fun Byte.toHexString() = this.toString(16).addHexPrefix()


fun BigInteger.isValidEthereumAddress() = this <= BigInteger.valueOf(2).pow(160).minus(BigInteger.ONE)

Expand Down

0 comments on commit 2177c9b

Please sign in to comment.