mirror of https://github.com/status-im/nim-eth.git
transaction signing helpers (#742)
Transaction signing is something that happens in a lot of places - this PR introduces primitives for transaction signing in `transaction_utils` such that we can use the same logic across web3/eth1/etc for this simple operation. `transaction_utils` also contains a few more "spec-derived" helpers for working with transactions, such as the computation of a contract address etc that cannot easily be introduced in `transactions` itself without bringing in dependencies like secp and rlp, so they end up in a separate module. Finally, since these modules collect "versions" of these transaction types across different eips, some tests are moved to follow the same structure.
This commit is contained in:
parent
84664b0fc0
commit
4ea11b9fb9
|
@ -41,9 +41,6 @@ type
|
||||||
EthReceipt* = Receipt
|
EthReceipt* = Receipt
|
||||||
EthWithdrawapRequest* = WithdrawalRequest
|
EthWithdrawapRequest* = WithdrawalRequest
|
||||||
|
|
||||||
template contractCreation*(tx: Transaction): bool =
|
|
||||||
tx.to.isNone
|
|
||||||
|
|
||||||
func init*(T: type BlockHashOrNumber, str: string): T {.raises: [ValueError].} =
|
func init*(T: type BlockHashOrNumber, str: string): T {.raises: [ValueError].} =
|
||||||
if str.startsWith "0x":
|
if str.startsWith "0x":
|
||||||
if str.len != sizeof(default(T).hash.data) * 2 + 2:
|
if str.len != sizeof(default(T).hash.data) * 2 + 2:
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
# eth
|
||||||
|
# Copyright (c) 2024 Status Research & Development GmbH
|
||||||
|
# Licensed and distributed under either of
|
||||||
|
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
||||||
|
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
||||||
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||||
|
|
||||||
|
import ./[keys, transactions, transactions_rlp]
|
||||||
|
|
||||||
|
export keys, transactions
|
||||||
|
|
||||||
|
proc signature*(tx: Transaction): Opt[Signature] =
|
||||||
|
var bytes {.noinit.}: array[65, byte]
|
||||||
|
bytes[0 .. 31] = tx.R.toBytesBE()
|
||||||
|
bytes[32 .. 63] = tx.S.toBytesBE()
|
||||||
|
|
||||||
|
bytes[64] =
|
||||||
|
if tx.txType != TxLegacy:
|
||||||
|
tx.V.byte
|
||||||
|
elif tx.V >= EIP155_CHAIN_ID_OFFSET:
|
||||||
|
byte(1 - (tx.V and 1))
|
||||||
|
elif tx.V == 27 or tx.V == 28:
|
||||||
|
byte(tx.V - 27)
|
||||||
|
else:
|
||||||
|
return Opt.none(Signature)
|
||||||
|
|
||||||
|
Signature.fromRaw(bytes).mapConvertErr(void)
|
||||||
|
|
||||||
|
proc `signature=`*(tx: var Transaction, param: tuple[sig: Signature, eip155: bool]) =
|
||||||
|
let raw = param.sig.toRaw()
|
||||||
|
|
||||||
|
tx.R = UInt256.fromBytesBE(raw.toOpenArray(0, 31))
|
||||||
|
tx.S = UInt256.fromBytesBE(raw.toOpenArray(32, 63))
|
||||||
|
|
||||||
|
let v = raw[64].uint64
|
||||||
|
tx.V =
|
||||||
|
case tx.txType
|
||||||
|
of TxLegacy:
|
||||||
|
if param.eip155:
|
||||||
|
v + uint64(tx.chainId) * 2 + 35
|
||||||
|
else:
|
||||||
|
v + 27'u64
|
||||||
|
else:
|
||||||
|
v
|
||||||
|
|
||||||
|
proc sign*(tx: Transaction, pk: PrivateKey, eip155: bool): (Signature, bool) =
|
||||||
|
let hash = tx.rlpHashForSigning(eip155)
|
||||||
|
|
||||||
|
(sign(pk, SkMessage(hash.data)), eip155)
|
||||||
|
|
||||||
|
proc recoverKey*(tx: Transaction): Opt[PublicKey] =
|
||||||
|
## Recovering key / sender is a costly operation - make sure to reuse the
|
||||||
|
## outcome!
|
||||||
|
##
|
||||||
|
## Returns `none` if the signature is invalid with respect to the rest of
|
||||||
|
## the transaction data.
|
||||||
|
let
|
||||||
|
sig = ?tx.signature()
|
||||||
|
txHash = tx.rlpHashForSigning(tx.isEip155())
|
||||||
|
|
||||||
|
recover(sig, SkMessage(txHash.data)).mapConvertErr(void)
|
||||||
|
|
||||||
|
proc recoverSender*(tx: Transaction): Opt[Address] =
|
||||||
|
## Recovering key / sender is a costly operation - make sure to reuse the
|
||||||
|
## outcome!
|
||||||
|
##
|
||||||
|
## Returns `none` if the signature is invalid with respect to the rest of
|
||||||
|
## the transaction data.
|
||||||
|
let key = ?tx.recoverKey()
|
||||||
|
ok key.to(Address)
|
||||||
|
|
||||||
|
proc creationAddress*(tx: Transaction, sender: Address): Address =
|
||||||
|
let hash = keccak256(rlp.encodeList(sender, tx.nonce))
|
||||||
|
hash.to(Address)
|
||||||
|
|
||||||
|
proc getRecipient*(tx: Transaction, sender: Address): Address =
|
||||||
|
tx.to.valueOr(tx.creationAddress(sender))
|
|
@ -11,6 +11,8 @@ import "."/[addresses, base, hashes]
|
||||||
|
|
||||||
export addresses, base, hashes
|
export addresses, base, hashes
|
||||||
|
|
||||||
|
const EIP155_CHAIN_ID_OFFSET* = 35'u64
|
||||||
|
|
||||||
type
|
type
|
||||||
AccessPair* = object
|
AccessPair* = object
|
||||||
address* : Address
|
address* : Address
|
||||||
|
@ -71,3 +73,9 @@ func destination*(tx: Transaction): Address =
|
||||||
# use getRecipient if you also want to get
|
# use getRecipient if you also want to get
|
||||||
# the contract address
|
# the contract address
|
||||||
tx.to.valueOr(default(Address))
|
tx.to.valueOr(default(Address))
|
||||||
|
|
||||||
|
func isEip155*(tx: Transaction): bool =
|
||||||
|
tx.V >= EIP155_CHAIN_ID_OFFSET
|
||||||
|
|
||||||
|
func contractCreation*(tx: Transaction): bool =
|
||||||
|
tx.to.isNone
|
||||||
|
|
|
@ -128,8 +128,6 @@ proc append*(w: var RlpWriter, tx: PooledTransaction) =
|
||||||
if tx.networkPayload != nil:
|
if tx.networkPayload != nil:
|
||||||
w.append(tx.networkPayload)
|
w.append(tx.networkPayload)
|
||||||
|
|
||||||
const EIP155_CHAIN_ID_OFFSET* = 35'u64
|
|
||||||
|
|
||||||
proc rlpEncodeLegacy(tx: Transaction): seq[byte] =
|
proc rlpEncodeLegacy(tx: Transaction): seq[byte] =
|
||||||
var w = initRlpWriter()
|
var w = initRlpWriter()
|
||||||
w.startList(6)
|
w.startList(6)
|
||||||
|
@ -142,7 +140,6 @@ proc rlpEncodeLegacy(tx: Transaction): seq[byte] =
|
||||||
w.finish()
|
w.finish()
|
||||||
|
|
||||||
proc rlpEncodeEip155(tx: Transaction): seq[byte] =
|
proc rlpEncodeEip155(tx: Transaction): seq[byte] =
|
||||||
let chainId = (tx.V - EIP155_CHAIN_ID_OFFSET) div 2
|
|
||||||
var w = initRlpWriter()
|
var w = initRlpWriter()
|
||||||
w.startList(9)
|
w.startList(9)
|
||||||
w.append(tx.nonce)
|
w.append(tx.nonce)
|
||||||
|
@ -151,7 +148,7 @@ proc rlpEncodeEip155(tx: Transaction): seq[byte] =
|
||||||
w.append(tx.to)
|
w.append(tx.to)
|
||||||
w.append(tx.value)
|
w.append(tx.value)
|
||||||
w.append(tx.payload)
|
w.append(tx.payload)
|
||||||
w.append(chainId)
|
w.append(tx.chainId)
|
||||||
w.append(0'u8)
|
w.append(0'u8)
|
||||||
w.append(0'u8)
|
w.append(0'u8)
|
||||||
w.finish()
|
w.finish()
|
||||||
|
@ -218,10 +215,12 @@ proc rlpEncodeEip7702(tx: Transaction): seq[byte] =
|
||||||
w.append(tx.authorizationList)
|
w.append(tx.authorizationList)
|
||||||
w.finish()
|
w.finish()
|
||||||
|
|
||||||
proc rlpEncode*(tx: Transaction): seq[byte] =
|
proc encodeForSigning*(tx: Transaction, eip155: bool): seq[byte] =
|
||||||
|
## Encode transaction data in preparation for signing or signature checking.
|
||||||
|
## For signature checking, set `eip155 = tx.isEip155`
|
||||||
case tx.txType
|
case tx.txType
|
||||||
of TxLegacy:
|
of TxLegacy:
|
||||||
if tx.V >= EIP155_CHAIN_ID_OFFSET: tx.rlpEncodeEip155 else: tx.rlpEncodeLegacy
|
if eip155: tx.rlpEncodeEip155 else: tx.rlpEncodeLegacy
|
||||||
of TxEip2930:
|
of TxEip2930:
|
||||||
tx.rlpEncodeEip2930
|
tx.rlpEncodeEip2930
|
||||||
of TxEip1559:
|
of TxEip1559:
|
||||||
|
@ -231,11 +230,17 @@ proc rlpEncode*(tx: Transaction): seq[byte] =
|
||||||
of TxEip7702:
|
of TxEip7702:
|
||||||
tx.rlpEncodeEip7702
|
tx.rlpEncodeEip7702
|
||||||
|
|
||||||
func txHashNoSignature*(tx: Transaction): Hash32 =
|
template rlpEncode*(tx: Transaction): seq[byte] {.deprecated.} =
|
||||||
# Hash transaction without signature
|
encodeForSigning(tx, tx.isEip155())
|
||||||
keccak256(rlpEncode(tx))
|
|
||||||
|
|
||||||
proc readTxLegacy*(rlp: var Rlp, tx: var Transaction) {.raises: [RlpError].} =
|
func rlpHashForSigning*(tx: Transaction, eip155: bool): Hash32 =
|
||||||
|
# Hash transaction without signature
|
||||||
|
keccak256(encodeForSigning(tx, eip155))
|
||||||
|
|
||||||
|
template txHashNoSignature*(tx: Transaction): Hash32 {.deprecated.} =
|
||||||
|
rlpHashForSigning(tx, tx.isEip155())
|
||||||
|
|
||||||
|
proc readTxLegacy(rlp: var Rlp, tx: var Transaction) {.raises: [RlpError].} =
|
||||||
tx.txType = TxLegacy
|
tx.txType = TxLegacy
|
||||||
rlp.tryEnterList()
|
rlp.tryEnterList()
|
||||||
rlp.read(tx.nonce)
|
rlp.read(tx.nonce)
|
||||||
|
@ -248,6 +253,9 @@ proc readTxLegacy*(rlp: var Rlp, tx: var Transaction) {.raises: [RlpError].} =
|
||||||
rlp.read(tx.R)
|
rlp.read(tx.R)
|
||||||
rlp.read(tx.S)
|
rlp.read(tx.S)
|
||||||
|
|
||||||
|
if tx.V >= EIP155_CHAIN_ID_OFFSET:
|
||||||
|
tx.chainId = ChainId((tx.V - EIP155_CHAIN_ID_OFFSET) div 2)
|
||||||
|
|
||||||
proc readTxEip2930(rlp: var Rlp, tx: var Transaction) {.raises: [RlpError].} =
|
proc readTxEip2930(rlp: var Rlp, tx: var Transaction) {.raises: [RlpError].} =
|
||||||
tx.txType = TxEip2930
|
tx.txType = TxEip2930
|
||||||
rlp.tryEnterList()
|
rlp.tryEnterList()
|
||||||
|
@ -375,7 +383,7 @@ proc readTxPayload(
|
||||||
of TxEip7702:
|
of TxEip7702:
|
||||||
rlp.readTxEip7702(tx)
|
rlp.readTxEip7702(tx)
|
||||||
|
|
||||||
proc readTxTyped*(rlp: var Rlp, tx: var Transaction) {.raises: [RlpError].} =
|
proc readTxTyped(rlp: var Rlp, tx: var Transaction) {.raises: [RlpError].} =
|
||||||
let txType = rlp.readTxType()
|
let txType = rlp.readTxType()
|
||||||
rlp.readTxPayload(tx, txType)
|
rlp.readTxPayload(tx, txType)
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,8 @@
|
||||||
|
|
||||||
import
|
import
|
||||||
test_common,
|
test_common,
|
||||||
test_eip4844,
|
|
||||||
test_eip7702,
|
|
||||||
test_eth_types,
|
test_eth_types,
|
||||||
test_eth_types_rlp,
|
test_eth_types_rlp,
|
||||||
test_keys
|
test_keys,
|
||||||
|
test_receipts,
|
||||||
|
test_transactions
|
||||||
|
|
|
@ -17,7 +17,7 @@ type
|
||||||
header: Header
|
header: Header
|
||||||
|
|
||||||
proc loadFile(x: int) =
|
proc loadFile(x: int) =
|
||||||
let fileName = "tests" / "common" / "eip2718" / "acl_block_" & $x & ".json"
|
let fileName = currentSourcePath.parentDir / "eip2718" / "acl_block_" & $x & ".json"
|
||||||
test fileName:
|
test fileName:
|
||||||
let n = json.parseFile(fileName)
|
let n = json.parseFile(fileName)
|
||||||
let data = n["rlp"].getStr()
|
let data = n["rlp"].getStr()
|
||||||
|
|
|
@ -1,134 +0,0 @@
|
||||||
# Nimbus
|
|
||||||
# Copyright (c) 2024 Status Research & Development GmbH
|
|
||||||
# Licensed under either of
|
|
||||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0)
|
|
||||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
|
||||||
# http://opensource.org/licenses/MIT)
|
|
||||||
# at your option. This file may not be copied, modified, or distributed except
|
|
||||||
# according to those terms.
|
|
||||||
{.used.}
|
|
||||||
|
|
||||||
import
|
|
||||||
stew/byteutils,
|
|
||||||
results,
|
|
||||||
unittest2,
|
|
||||||
../../eth/common,
|
|
||||||
../../eth/rlp,
|
|
||||||
../../eth/common/[keys, transactions_rlp]
|
|
||||||
|
|
||||||
const
|
|
||||||
recipient = address"095e7baea6a6c7c4c2dfeb977efac326af552d87"
|
|
||||||
source = address"0x0000000000000000000000000000000000000001"
|
|
||||||
storageKey= default(Bytes32)
|
|
||||||
accesses = @[AccessPair(address: source, storageKeys: @[storageKey])]
|
|
||||||
abcdef = hexToSeqByte("abcdef")
|
|
||||||
authList = @[Authorization(
|
|
||||||
chainID: 1.ChainId,
|
|
||||||
address: source,
|
|
||||||
nonce: 2.AccountNonce,
|
|
||||||
yParity: 3,
|
|
||||||
R: 4.u256,
|
|
||||||
S: 5.u256
|
|
||||||
)]
|
|
||||||
|
|
||||||
proc tx0(i: int): Transaction =
|
|
||||||
Transaction(
|
|
||||||
txType: TxEip7702,
|
|
||||||
chainId: 1.ChainId,
|
|
||||||
nonce: i.AccountNonce,
|
|
||||||
maxPriorityFeePerGas: 2.GasInt,
|
|
||||||
maxFeePerGas: 3.GasInt,
|
|
||||||
gasLimit: 4.GasInt,
|
|
||||||
to: Opt.some recipient,
|
|
||||||
value: 5.u256,
|
|
||||||
payload: abcdef,
|
|
||||||
accessList: accesses,
|
|
||||||
authorizationList: authList
|
|
||||||
)
|
|
||||||
|
|
||||||
func `==`(a, b: ChainId): bool =
|
|
||||||
a.uint64 == b.uint64
|
|
||||||
|
|
||||||
template roundTrip(txFunc: untyped, i: int) =
|
|
||||||
let tx = txFunc(i)
|
|
||||||
let bytes = rlp.encode(tx)
|
|
||||||
let tx2 = rlp.decode(bytes, Transaction)
|
|
||||||
let bytes2 = rlp.encode(tx2)
|
|
||||||
check bytes == bytes2
|
|
||||||
|
|
||||||
template read[T](rlp: var Rlp, val: var T) =
|
|
||||||
val = rlp.read(type val)
|
|
||||||
|
|
||||||
proc read[T](rlp: var Rlp, val: var Opt[T]) =
|
|
||||||
if rlp.blobLen != 0:
|
|
||||||
val = Opt.some(rlp.read(T))
|
|
||||||
else:
|
|
||||||
rlp.skipElem
|
|
||||||
|
|
||||||
proc readTx(rlp: var Rlp, tx: var Transaction) =
|
|
||||||
rlp.tryEnterList()
|
|
||||||
tx.chainId = rlp.read(uint64).ChainId
|
|
||||||
rlp.read(tx.nonce)
|
|
||||||
rlp.read(tx.maxPriorityFeePerGas)
|
|
||||||
rlp.read(tx.maxFeePerGas)
|
|
||||||
rlp.read(tx.gasLimit)
|
|
||||||
rlp.read(tx.to)
|
|
||||||
rlp.read(tx.value)
|
|
||||||
rlp.read(tx.payload)
|
|
||||||
rlp.read(tx.accessList)
|
|
||||||
rlp.read(tx.authorizationList)
|
|
||||||
|
|
||||||
proc decodeTxEip7702(bytes: openArray[byte]): Transaction =
|
|
||||||
var rlp = rlpFromBytes(bytes)
|
|
||||||
result.txType = TxType(rlp.getByteValue)
|
|
||||||
rlp.position += 1
|
|
||||||
readTx(rlp, result)
|
|
||||||
|
|
||||||
suite "Transaction EIP-7702 tests":
|
|
||||||
test "Tx RLP roundtrip":
|
|
||||||
roundTrip(tx0, 1)
|
|
||||||
|
|
||||||
test "Tx Sign":
|
|
||||||
const
|
|
||||||
keyHex = "63b508a03c3b5937ceb903af8b1b0c191012ef6eb7e9c3fb7afa94e5d214d376"
|
|
||||||
|
|
||||||
var
|
|
||||||
tx = tx0(2)
|
|
||||||
|
|
||||||
let
|
|
||||||
privateKey = PrivateKey.fromHex(keyHex).expect("valid key")
|
|
||||||
rlpTx = rlpEncode(tx)
|
|
||||||
sig = sign(privateKey, rlpTx).toRaw
|
|
||||||
|
|
||||||
tx.V = sig[64].uint64
|
|
||||||
tx.R = UInt256.fromBytesBE(sig[0..31])
|
|
||||||
tx.S = UInt256.fromBytesBE(sig[32..63])
|
|
||||||
|
|
||||||
let
|
|
||||||
bytes = rlp.encode(tx)
|
|
||||||
decodedTx = rlp.decode(bytes, Transaction)
|
|
||||||
decodedNoSig = decodeTxEip7702(rlpTx)
|
|
||||||
|
|
||||||
var
|
|
||||||
expectedTx = tx0(2)
|
|
||||||
|
|
||||||
check expectedTx == decodedNoSig
|
|
||||||
|
|
||||||
expectedTx.V = tx.V
|
|
||||||
expectedTx.R = tx.R
|
|
||||||
expectedTx.S = tx.S
|
|
||||||
|
|
||||||
check expectedTx == tx
|
|
||||||
|
|
||||||
test "Receipt RLP roundtrip":
|
|
||||||
let rec = Receipt(
|
|
||||||
receiptType: Eip7702Receipt,
|
|
||||||
isHash: false,
|
|
||||||
status: false,
|
|
||||||
cumulativeGasUsed: 100.GasInt)
|
|
||||||
|
|
||||||
let bytes = rlp.encode(rec)
|
|
||||||
let zz = rlp.decode(bytes, Receipt)
|
|
||||||
let bytes2 = rlp.encode(zz)
|
|
||||||
check bytes2 == bytes
|
|
|
@ -296,4 +296,3 @@ suite "EIP-7865 tests":
|
||||||
|
|
||||||
check decodedBody == body
|
check decodedBody == body
|
||||||
check decodedBlk == blk
|
check decodedBlk == blk
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
# Nimbus
|
||||||
|
# Copyright (c) 2023-2024 Status Research & Development GmbH
|
||||||
|
# Licensed under either of
|
||||||
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
||||||
|
# http://opensource.org/licenses/MIT)
|
||||||
|
# at your option. This file may not be copied, modified, or distributed except
|
||||||
|
# according to those terms.
|
||||||
|
{.used.}
|
||||||
|
|
||||||
|
import
|
||||||
|
unittest2,
|
||||||
|
../../eth/common/[receipts_rlp]
|
||||||
|
|
||||||
|
template roundTrip(v: untyped) =
|
||||||
|
let bytes = rlp.encode(v)
|
||||||
|
let v2 = rlp.decode(bytes, Receipt)
|
||||||
|
let bytes2 = rlp.encode(v2)
|
||||||
|
check bytes == bytes2
|
||||||
|
|
||||||
|
suite "Receipts":
|
||||||
|
test "EIP-4844":
|
||||||
|
let rec = Receipt(
|
||||||
|
receiptType: Eip4844Receipt,
|
||||||
|
isHash: false,
|
||||||
|
status: false,
|
||||||
|
cumulativeGasUsed: 100.GasInt)
|
||||||
|
|
||||||
|
roundTrip(rec)
|
||||||
|
|
||||||
|
test "EIP-7702":
|
||||||
|
let rec = Receipt(
|
||||||
|
receiptType: Eip7702Receipt,
|
||||||
|
isHash: false,
|
||||||
|
status: false,
|
||||||
|
cumulativeGasUsed: 100.GasInt)
|
||||||
|
|
||||||
|
roundTrip(rec)
|
|
@ -12,18 +12,24 @@
|
||||||
import
|
import
|
||||||
stew/byteutils,
|
stew/byteutils,
|
||||||
unittest2,
|
unittest2,
|
||||||
../../eth/common,
|
../../eth/common/[transactions_rlp, transaction_utils]
|
||||||
../../eth/rlp,
|
|
||||||
../../eth/common/transactions_rlp
|
|
||||||
|
|
||||||
const
|
const
|
||||||
recipient = address"095e7baea6a6c7c4c2dfeb977efac326af552d87"
|
recipient = address"095e7baea6a6c7c4c2dfeb977efac326af552d87"
|
||||||
zeroG1 = bytes48"0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
zeroG1 = bytes48"0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||||
source = address"0x0000000000000000000000000000000000000001"
|
source = address"0x0000000000000000000000000000000000000001"
|
||||||
storageKey= default(StorageKey)
|
storageKey= default(Bytes32)
|
||||||
accesses = @[AccessPair(address: source, storageKeys: @[storageKey])]
|
accesses = @[AccessPair(address: source, storageKeys: @[storageKey])]
|
||||||
blob = default(NetworkBlob)
|
blob = default(NetworkBlob)
|
||||||
abcdef = hexToSeqByte("abcdef")
|
abcdef = hexToSeqByte("abcdef")
|
||||||
|
authList = @[Authorization(
|
||||||
|
chainID: 1.ChainId,
|
||||||
|
address: source,
|
||||||
|
nonce: 2.AccountNonce,
|
||||||
|
yParity: 3,
|
||||||
|
R: 4.u256,
|
||||||
|
S: 5.u256
|
||||||
|
)]
|
||||||
|
|
||||||
proc tx0(i: int): PooledTransaction =
|
proc tx0(i: int): PooledTransaction =
|
||||||
PooledTransaction(
|
PooledTransaction(
|
||||||
|
@ -144,6 +150,23 @@ proc tx8(i: int): PooledTransaction =
|
||||||
versionedHashes: @[digest],
|
versionedHashes: @[digest],
|
||||||
maxFeePerBlobGas: 10000000.u256))
|
maxFeePerBlobGas: 10000000.u256))
|
||||||
|
|
||||||
|
proc txEip7702(i: int): PooledTransaction =
|
||||||
|
PooledTransaction(
|
||||||
|
tx: Transaction(
|
||||||
|
txType: TxEip7702,
|
||||||
|
chainId: 1.ChainId,
|
||||||
|
nonce: i.AccountNonce,
|
||||||
|
maxPriorityFeePerGas: 2.GasInt,
|
||||||
|
maxFeePerGas: 3.GasInt,
|
||||||
|
gasLimit: 4.GasInt,
|
||||||
|
to: Opt.some recipient,
|
||||||
|
value: 5.u256,
|
||||||
|
payload: abcdef,
|
||||||
|
accessList: accesses,
|
||||||
|
authorizationList: authList
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
template roundTrip(txFunc: untyped, i: int) =
|
template roundTrip(txFunc: untyped, i: int) =
|
||||||
let tx = txFunc(i)
|
let tx = txFunc(i)
|
||||||
let bytes = rlp.encode(tx)
|
let bytes = rlp.encode(tx)
|
||||||
|
@ -151,7 +174,7 @@ template roundTrip(txFunc: untyped, i: int) =
|
||||||
let bytes2 = rlp.encode(tx2)
|
let bytes2 = rlp.encode(tx2)
|
||||||
check bytes == bytes2
|
check bytes == bytes2
|
||||||
|
|
||||||
suite "Transaction RLP Encoding":
|
suite "Transactions":
|
||||||
test "Legacy Tx Call":
|
test "Legacy Tx Call":
|
||||||
roundTrip(tx0, 1)
|
roundTrip(tx0, 1)
|
||||||
|
|
||||||
|
@ -179,6 +202,9 @@ suite "Transaction RLP Encoding":
|
||||||
test "Minimal Blob Tx contract creation":
|
test "Minimal Blob Tx contract creation":
|
||||||
roundTrip(tx8, 9)
|
roundTrip(tx8, 9)
|
||||||
|
|
||||||
|
test "EIP 7702":
|
||||||
|
roundTrip(txEip7702, 9)
|
||||||
|
|
||||||
test "Network payload survive encode decode":
|
test "Network payload survive encode decode":
|
||||||
let tx = tx6(10)
|
let tx = tx6(10)
|
||||||
let bytes = rlp.encode(tx)
|
let bytes = rlp.encode(tx)
|
||||||
|
@ -227,14 +253,47 @@ suite "Transaction RLP Encoding":
|
||||||
let bytes2 = rlp.encode(zz)
|
let bytes2 = rlp.encode(zz)
|
||||||
check bytes2 == bytes
|
check bytes2 == bytes
|
||||||
|
|
||||||
test "Receipts":
|
test "EIP-155 signature":
|
||||||
let rec = Receipt(
|
# https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md#example
|
||||||
receiptType: Eip4844Receipt,
|
var
|
||||||
isHash: false,
|
tx = Transaction(
|
||||||
status: false,
|
txType: TxLegacy,
|
||||||
cumulativeGasUsed: 100.GasInt)
|
chainId: ChainId(1),
|
||||||
|
nonce: 9,
|
||||||
|
gasPrice: 20000000000'u64,
|
||||||
|
gasLimit: 21000'u64,
|
||||||
|
to: Opt.some address"0x3535353535353535353535353535353535353535",
|
||||||
|
value: u256"1000000000000000000",
|
||||||
|
)
|
||||||
|
txEnc = tx.encodeForSigning(true)
|
||||||
|
txHash = tx.rlpHashForSigning(true)
|
||||||
|
key = PrivateKey.fromHex("0x4646464646464646464646464646464646464646464646464646464646464646").expect(
|
||||||
|
"working key"
|
||||||
|
)
|
||||||
|
|
||||||
let bytes = rlp.encode(rec)
|
tx.signature = tx.sign(key, true)
|
||||||
let zz = rlp.decode(bytes, Receipt)
|
|
||||||
let bytes2 = rlp.encode(zz)
|
check:
|
||||||
check bytes2 == bytes
|
txEnc.to0xHex == "0xec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080"
|
||||||
|
txHash == hash32"0xdaf5a779ae972f972197303d7b574746c7ef83eadac0f2791ad23db92e4c8e53"
|
||||||
|
tx.V == 37
|
||||||
|
tx.R ==
|
||||||
|
u256"18515461264373351373200002665853028612451056578545711640558177340181847433846"
|
||||||
|
tx.S ==
|
||||||
|
u256"46948507304638947509940763649030358759909902576025900602547168820602576006531"
|
||||||
|
|
||||||
|
test "sign transaction":
|
||||||
|
let
|
||||||
|
txs = @[
|
||||||
|
tx0(3).tx, tx1(3).tx, tx2(3).tx, tx3(3).tx, tx4(3).tx,
|
||||||
|
tx5(3).tx, tx6(3).tx, tx7(3).tx, tx8(3).tx, txEip7702(3).tx]
|
||||||
|
|
||||||
|
privKey = PrivateKey.fromHex("63b508a03c3b5937ceb903af8b1b0c191012ef6eb7e9c3fb7afa94e5d214d376").expect("valid key")
|
||||||
|
sender = privKey.toPublicKey().to(Address)
|
||||||
|
|
||||||
|
for tx in txs:
|
||||||
|
var tx = tx
|
||||||
|
tx.signature = tx.sign(privKey, true)
|
||||||
|
|
||||||
|
check:
|
||||||
|
tx.recoverKey().expect("valid key").to(Address) == sender
|
Loading…
Reference in New Issue