Align core types with execution spec (#733)

Since these types were written, we've gained an executable spec:

https://github.com/ethereum/execution-specs

This PR aligns some of the types we use with this spec to simplify
comparisons and cross-referencing.

Using a `distinct` type is a tradeoff between nim ergonomics, type
safety and the ability to work around nim quirks and stdlib weaknesses.

In particular, it allows us to overload common functions such as `hash`
with correct and performant versions as well as maintain control over
string conversions etc at the cost of a little bit of ceremony when
instantiating them.

Apart from distinct byte types, `Hash32`, is introduced in lieu of the
existing `Hash256`, again aligning this commonly used type with the spec
which picks bytes rather than bits in the name.
This commit is contained in:
Jacek Sieka 2024-09-29 10:52:19 +02:00 committed by GitHub
parent 3d51887c15
commit 6bd6bae86c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
90 changed files with 2339 additions and 2020 deletions

View File

@ -78,11 +78,11 @@ Additional APIs are:
that starts with the same key prefix that starts with the same key prefix
* rootNode() -- get root node * rootNode() -- get root node
* rootNode(node) -- replace the root node * rootNode(node) -- replace the root node
* getRootHash(): `KeccakHash` with `seq[byte]` type * getRootHash(): `Hash32` with `seq[byte]` type
* getDB(): `DB` -- get flat-db pointer * getDB(): `DB` -- get flat-db pointer
Constructor API: Constructor API:
* initBinaryTrie(DB, rootHash[optional]) -- rootHash has `seq[byte]` or KeccakHash type * initBinaryTrie(DB, rootHash[optional]) -- rootHash has `seq[byte]` or Hash32 type
* init(BinaryTrie, DB, rootHash[optional]) * init(BinaryTrie, DB, rootHash[optional])
Normally you would not set the rootHash when constructing an empty Binary-trie. Normally you would not set the rootHash when constructing an empty Binary-trie.

View File

@ -10,7 +10,7 @@ requires "nim >= 1.6.0",
"nimcrypto", "nimcrypto",
"stint", "stint",
"secp256k1", "secp256k1",
"chronos#head", "chronos",
"chronicles", "chronicles",
"stew", "stew",
"nat_traversal", "nat_traversal",
@ -47,9 +47,6 @@ proc run(path, outdir: string) =
task test_keyfile, "Run keyfile tests": task test_keyfile, "Run keyfile tests":
run "tests/keyfile/all_tests", "keyfile" run "tests/keyfile/all_tests", "keyfile"
task test_keys, "Run keys tests":
run "tests/keys/all_tests", "keys"
task test_discv5, "Run discovery v5 tests": task test_discv5, "Run discovery v5 tests":
run "tests/p2p/all_discv5_tests", "p2p" run "tests/p2p/all_discv5_tests", "p2p"
@ -78,7 +75,6 @@ task test, "Run all tests":
run "tests/test_bloom", "" run "tests/test_bloom", ""
test_keyfile_task() test_keyfile_task()
test_keys_task()
test_rlp_task() test_rlp_task()
test_p2p_task() test_p2p_task()
test_trie_task() test_trie_task()
@ -87,7 +83,6 @@ task test, "Run all tests":
test_common_task() test_common_task()
task test_discv5_full, "Run discovery v5 and its dependencies tests": task test_discv5_full, "Run discovery v5 and its dependencies tests":
test_keys_task()
test_rlp_task() test_rlp_task()
test_discv5_task() test_discv5_task()

View File

@ -1,8 +1,8 @@
import stint, ./common/eth_hash import stint, ./common/[addresses, base, hashes]
type UInt2048 = StUint[2048] type UInt2048 = StUint[2048]
iterator chunksForBloom(h: MDigest[256]): array[2, uint8] = iterator chunksForBloom(h: Hash32): array[2, uint8] =
yield [h.data[0], h.data[1]] yield [h.data[0], h.data[1]]
yield [h.data[2], h.data[3]] yield [h.data[2], h.data[3]]
yield [h.data[4], h.data[5]] yield [h.data[4], h.data[5]]
@ -12,28 +12,34 @@ proc chunkToBloomBits(chunk: array[2, uint8]): UInt2048 =
let l = chunk[1].int let l = chunk[1].int
one(UInt2048) shl ((l + (h shl 8)) and 2047) one(UInt2048) shl ((l + (h shl 8)) and 2047)
iterator bloomBits(h: MDigest[256]): UInt2048 = iterator bloomBits(h: Hash32): UInt2048 =
for chunk in chunksForBloom(h): for chunk in chunksForBloom(h):
yield chunkToBloomBits(chunk) yield chunkToBloomBits(chunk)
type BloomFilter* = object type BloomFilter* = object
value*: UInt2048 value*: UInt2048
proc incl*(f: var BloomFilter, h: MDigest[256]) = proc incl*(f: var BloomFilter, h: Hash32) =
for bits in bloomBits(h): for bits in bloomBits(h):
f.value = f.value or bits f.value = f.value or bits
proc init*(_: type BloomFilter, h: MDigest[256]): BloomFilter = proc init*(_: type BloomFilter, h: Hash32): BloomFilter =
result.incl(h) result.incl(h)
# TODO: The following 2 procs should be one genric, but it doesn't compile. Nim bug? proc incl*[T: byte|char](f: var BloomFilter, v: openArray[T]) =
proc incl*(f: var BloomFilter, v: string) = f.incl(keccakHash(v)) f.incl(keccak256(v))
proc incl*(f: var BloomFilter, v: openArray[byte]) = f.incl(keccakHash(v))
proc contains*(f: BloomFilter, h: MDigest[256]): bool = proc incl*(f: var BloomFilter, v: Address | Bytes32) =
f.incl(v.data)
proc contains*(f: BloomFilter, h: Hash32): bool =
for bits in bloomBits(h): for bits in bloomBits(h):
if (f.value and bits).isZero: return false if (f.value and bits).isZero:
return false
return true return true
template contains*[T](f: BloomFilter, v: openArray[T]): bool = template contains*(f: BloomFilter, v: openArray): bool =
f.contains(keccakHash(v)) f.contains(keccak256(v))
proc contains*(f: BloomFilter, v: Address | Bytes32): bool =
f.contains(v.data)

View File

@ -1,2 +1,2 @@
import ./common/[eth_types_rlp, utils] import ./common/eth_types_rlp
export eth_types_rlp, utils export eth_types_rlp

35
eth/common/accounts.nim Normal file
View File

@ -0,0 +1,35 @@
# 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.
{.push raises: [].}
import ./[base, hashes]
export base, hashes
type Account* = object
## Account with fields in RLP order, per `encode_account` spec function
## https://github.com/ethereum/execution-specs/blob/51fac24740e662844446439ceeb96a460aae0ba0/src/ethereum/paris/fork_types.py#L36
nonce*: AccountNonce
balance*: UInt256
storageRoot*: Root
codeHash*: Hash32
const
EMPTY_ROOT_HASH* = emptyRoot
EMPTY_CODE_HASH* = emptyKeccak256
func init*(
T: type Account,
nonce = default(AccountNonce),
balance = default(UInt256),
storageRoot = EMPTY_ROOT_HASH,
codeHash = EMPTY_CODE_HASH,
): T =
T(nonce: nonce, balance: balance, storageRoot: storageRoot, codeHash: codeHash)
const EMPTY_ACCOUNT* = Account.init()

View File

@ -0,0 +1,12 @@
# 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.
{.push raises: [].}
import ./[accounts, base_rlp, hashes_rlp], ../rlp
export accounts, base_rlp, hashes_rlp, rlp

111
eth/common/addresses.nim Normal file
View File

@ -0,0 +1,111 @@
# 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.
{.push raises: [].}
## 20-byte ethereum account address, as derived from the keypair controlling it
## https://ethereum.org/en/developers/docs/accounts/#account-creation
import std/[typetraits, hashes as std_hashes], "."/[base, hashes], stew/assign2
export hashes
type Address* = distinct Bytes20
## https://github.com/ethereum/execution-specs/blob/51fac24740e662844446439ceeb96a460aae0ba0/src/ethereum/paris/fork_types.py#L28
const zeroAddress* = system.default(Address)
## Address consisting of all zeroes.
## Transactions to zeroAddress are legitimate transfers to that account, not
## contract creations. They are used to "burn" Eth. People also send Eth to
## address zero by accident, unrecoverably, due to poor user interface issues.
template to*(v: array[20, byte], _: type Address): Address =
Address(v)
func to*(s: Bytes32 | Hash32, _: type Address): Address =
## Take the last 20 bytes of the given hash to form an Address - this is a
## lossy conversion which discards the first 12 bytes
assign(result.data, s.data.toOpenArray(12, 31))
func to*(a: Address, _: type Bytes32): Bytes32 =
## Place the address in the last 20 bytes of the result, filling the rest with 0
assign(result.data.toOpenArray(12, 31), a.data)
template data*(v: Address): array[20, byte] =
distinctBase(v)
template `data=`*[N: static int](a: FixedBytes[N], b: array[N, byte]) =
assign(distinctBase(a), b)
template copyFrom*(T: type Address, v: openArray[byte], start = 0): T =
## Copy up to N bytes from the given openArray, starting at `start` and
## filling any missing bytes with zero.
##
## This is a lenient function in that `v` may contain both fewer and more
## bytes than N and start might be out of bounds.
Address(Bytes20.copyFrom(v, start))
template default*(_: type Address): Address =
# Avoid bad codegen where fixed bytes are zeroed byte-by-byte at call site
zeroAddress
func `==`*(a, b: Address): bool {.borrow.}
func hash*(a: Address): Hash {.inline.} =
# Addresses are more or less random so we should not need a fancy mixing
# function
var a0 {.noinit.}, a1 {.noinit.}: uint64
var a2 {.noinit.}: uint32
copyMem(addr a0, unsafeAddr a.data[0], sizeof(a0))
copyMem(addr a1, unsafeAddr a.data[8], sizeof(a1))
copyMem(addr a2, unsafeAddr a.data[16], sizeof(a2))
cast[Hash](a0 + a1 + uint64(a2))
func toHex*(a: Address): string {.borrow.}
func to0xHex*(a: Address): string {.borrow.}
func `$`*(a: Address): string {.borrow.}
func fromHex*(_: type Address, s: openArray[char]): Address {.raises: [ValueError].} =
Address(Bytes20.fromHex(s))
template to*(s: static string, _: type Address): Address =
const hash = Address.fromHex(s)
hash
template address*(s: static string): Address =
s.to(Address)
func toChecksum0xHex*(a: Address): string =
## Convert the address to 0x-prefixed mixed-case EIP-55 format
let
# TODO avoid memory allocations here
hhash1 = a.toHex()
hhash2 = keccak256(hhash1).toHex()
result = newStringOfCap(hhash2.len + 2)
result.add "0x"
for i, c in hhash1:
if hhash2[i] >= '0' and hhash2[i] <= '7':
result.add c
else:
if c >= '0' and c <= '9':
result.add c
else:
result.add chr(ord(c) - ord('a') + ord('A'))
func hasValidChecksum*(_: type Address, a: string): bool =
## Validate checksumable mixed-case address (EIP-55).
let address =
try:
Address.fromHex(a)
except ValueError:
return false
a == address.toChecksum0xHex()

View File

@ -0,0 +1,19 @@
# 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.
{.push raises: [].}
import ./addresses, ../rlp
export addresses, rlp
proc read*[T: Address](rlp: var Rlp, _: type T): T {.raises: [RlpError].} =
T(rlp.read(type(result.data)))
proc append*(w: var RlpWriter, val: Address) =
mixin append
w.append(val.data())

185
eth/common/base.nim Normal file
View File

@ -0,0 +1,185 @@
# 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.
{.push raises: [].}
## Base primitive types used in ethereum, as specified in the execution specs:
## https://github.com/ethereum/execution-specs/
##
## For all of `UInt` and `UIntXX`, we use native `uintXX` types and/or `stint`.
##
## In the specification `UInt` is often used to denote an unsigned
## arbitrary-precision integers - in actual code we opt for a bounded type
## instead depending on "reasonable bounds", ie bounds that are unlikely to be
## exceeded in the foreseeable future.
import
std/[hashes, macros, typetraits],
stint,
results,
stew/[assign2, byteutils, endians2, staticfor]
export stint, hashes, results
type
FixedBytes*[N: static int] = distinct array[N, byte]
## Fixed-length byte sequence holding arbitrary data
## https://github.com/ethereum/execution-specs/blob/51fac24740e662844446439ceeb96a460aae0ba0/src/ethereum/base_types.py
##
## This type is specialized to `Bytes4`, `Bytes8` etc below.
# A distinct array is used to avoid copying on trivial type conversions
# to and from other array-based types
ChainId* = distinct uint64
## Chain identifier used for transaction signing to guard against replay
## attacks between networks
##
## https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md
NetworkId* = distinct uint64
## Network identifier - similar to chain id but used for network
## communication to ensure connectivity with peers on the same network.
## Often has the same value as ChainId, but not always
GasInt* = uint64
## Integer used to comput gas usage in individual blocks / transactions -
## here, a smaller type is convenient since gas computations are expensive.
##
## Care must be taken since the sum of gas usage across many blocks may
## exceed the uint64 range.
##
## See also:
## * https://github.com/status-im/nimbus-eth1/issues/35
template to*[N: static int](v: array[N, byte], T: type FixedBytes[N]): T =
T(v)
template data*[N: static int](v: FixedBytes[N]): array[N, byte] =
distinctBase(v)
template `data=`*[N: static int](a: FixedBytes[N], b: array[N, byte]) =
assign(distinctBase(a), b)
func copyFrom*[N: static int](T: type FixedBytes[N], v: openArray[byte], start = 0): T =
## Copy up to N bytes from the given openArray, starting at `start` and
## filling any missing bytes with zero.
##
## This is a lenient function in that `v` may contain both fewer and more
## bytes than N and start might be out of bounds.
if v.len > start:
let n = min(N, v.len - start)
assign(result.data.toOpenArray(0, n - 1), v.toOpenArray(start, start + n - 1))
template default*[N](T: type FixedBytes[N]): T =
# Avoid bad codegen where fixed bytes are zeroed byte-by-byte at call site
const def = system.default(T)
def
func `==`*(a, b: FixedBytes): bool {.inline.} =
equalMem(addr a.data[0], addr b.data[0], a.N)
func hash*[N: static int](v: FixedBytes[N]): Hash {.inline.} =
copyMem(addr result, addr v.data[0], min(N, sizeof(Hash)))
when N > sizeof(Hash):
var tmp: Hash
staticFor i, 1 ..< N div sizeof(Hash):
copyMem(addr tmp, addr v.data[i * sizeof(Hash)], sizeof(Hash))
result = result !& tmp
const last = N mod sizeof(Hash)
when last > 0:
copyMem(addr tmp, addr v.data[N - last], last)
result !& tmp
func toHex*(v: FixedBytes): string =
## Convert to lowercase hex without 0x prefix
toHex(v.data)
func to0xHex*(v: FixedBytes): string =
## Convert to lowercase hex with 0x prefix
to0xHex(v.data)
func `$`*(v: FixedBytes): string =
## Convert the given value to a string representation suitable for presentation
## To convert to a specific string encoding, use `toHex`, `to0xHex` etc
to0xHex(v)
func fromHex*(T: type FixedBytes, c: openArray[char]): T {.raises: [ValueError].} =
## Parse a string as hex after optionally stripping "0x", raising ValueError if:
## * the string is too long or to short
## * the string can't be parsed as hex
T(hexToByteArrayStrict(c, T.N))
template makeFixedBytesN(N: static int) =
# Create specific numbered instantiations along with helpers
type `Bytes N`* = FixedBytes[N]
const `zeroBytes N`* = system.default(`Bytes N`)
template default*(T: type `Bytes N`): `Bytes N` =
# reuse single constant for precomputed N
`zeroBytes N`
template `bytes N`*(s: static string): `Bytes N` =
`Bytes N`.fromHex(s)
makeFixedBytesN(4)
makeFixedBytesN(8)
makeFixedBytesN(20)
makeFixedBytesN(32)
makeFixedBytesN(48)
makeFixedBytesN(64)
makeFixedBytesN(96)
makeFixedBytesN(256)
# Ethereum keeps integers as big-endian
template to*(v: uint32, T: type Bytes4): T =
T v.toBytesBE()
template to*(v: Bytes4, T: type uint32): T =
T.fromBytesBE(v)
template to*(v: uint64, T: type Bytes8): T =
T v.toBytesBE()
template to*(v: Bytes8, T: type uint64): T =
T.fromBytesBE(v)
template to*[M, N: static int](v: FixedBytes[M], T: type StUint[N]): T =
static:
assert N == M * 8
T.fromBytesBE(v.data)
template to*[M, N: static int](v: StUint[M], T: type FixedBytes[N]): T =
static:
assert N * 8 == M
T v.toBytesBE()
type
# Aliases commonly found in the spec - reasons to use an alias instead of the
# underlying type include:
# * the spec says `UInt` and we use a bounded type instead (uint64 or UInt256)
# * the spec consistently uses the alias and we're translating from there
# directly
# * we have distinct behaviour attached to it that shouldn't "pollute" other
# usages of the same type - `$` is the canonical example
# * the algorithm is specific to ethereum and not of "general interest"
#
# In most other cases, code is easier to read and more flexible when it
# doesn't use these aliases.
AccountNonce* = uint64
BlockNumber* = uint64
Bloom* = Bytes256
Bytes* = seq[byte] # TODO distinct?
KzgCommitment* = Bytes48
KzgProof* = Bytes48
ForkID* = tuple[crc: uint32, nextFork: uint64] ## EIP 2364/2124
func `==`*(a, b: NetworkId): bool {.borrow.}
func `$`*(x: NetworkId): string {.borrow.}
func `==`*(a, b: ChainId): bool {.borrow.}
func `$`*(x: ChainId): string {.borrow.}

102
eth/common/base_rlp.nim Normal file
View File

@ -0,0 +1,102 @@
# 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.
{.push raises: [].}
import std/typetraits, ./base, ../rlp
export base, rlp
# TODO why is rlp serialization of `Opt` here and not in rlp?
proc append*[T](w: var RlpWriter, val: Opt[T]) =
mixin append
if val.isSome:
w.append(val.get())
else:
w.append("")
template read*[T](rlp: var Rlp, val: var T) =
mixin read
val = rlp.read(type val)
proc read*[T](rlp: var Rlp, val: var Opt[T]) {.raises: [RlpError].} =
mixin read
if rlp.blobLen != 0:
val = Opt.some(rlp.read(T))
else:
rlp.skipElem
proc read*(rlp: var Rlp, T: type StUint): T {.raises: [RlpError].} =
if rlp.isBlob:
let bytes = rlp.toBytes
if bytes.len > 0:
# be sure the amount of bytes matches the size of the stint
if bytes.len <= sizeof(result):
result.initFromBytesBE(bytes)
else:
raise newException(
RlpTypeMismatch,
"Unsigned integer expected, but the source RLP has the wrong length",
)
else:
result = 0.to(T)
else:
raise newException(
RlpTypeMismatch, "Unsigned integer expected, but the source RLP is a list"
)
rlp.skipElem
func significantBytesBE(val: openArray[byte]): int =
## Returns the number of significant trailing bytes in a big endian
## representation of a number.
for i in 0 ..< val.len:
if val[i] != 0:
return val.len - i
return 1
proc append*(w: var RlpWriter, value: StUint) =
if value > 128:
let bytes = value.toByteArrayBE
let nonZeroBytes = significantBytesBE(bytes)
w.append bytes.toOpenArray(bytes.len - nonZeroBytes, bytes.len - 1)
else:
w.append(value.truncate(uint))
proc read*(rlp: var Rlp, T: type StInt): T =
# The Ethereum Yellow Paper defines the RLP serialization only
# for unsigned integers:
{.fatal: "RLP serialization of signed integers is not allowed".}
discard
proc append*(w: var RlpWriter, value: StInt) =
# The Ethereum Yellow Paper defines the RLP serialization only
# for unsigned integers:
{.fatal: "RLP serialization of signed integers is not allowed".}
discard
proc append*(w: var RlpWriter, val: FixedBytes) =
mixin append
w.append(val.data())
proc read*[N: static int](
rlp: var Rlp, T: type FixedBytes[N]
): T {.raises: [RlpError].} =
T(rlp.read(type(result.data)))
proc append*(w: var RlpWriter, id: ChainId) =
w.append(distinctBase id)
proc read*(rlp: var Rlp, T: type ChainId): T {.raises: [RlpError].} =
T(rlp.read(distinctBase T))
proc append*(w: var RlpWriter, id: NetworkId) =
w.append(distinctBase id)
proc read*(rlp: var Rlp, T: type NetworkId): T {.raises: [RlpError].} =
T(rlp.read(distinctBase T))

91
eth/common/blocks.nim Normal file
View File

@ -0,0 +1,91 @@
# 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.
{.push raises: [].}
import "."/[addresses, base, headers, transactions]
export addresses, base, headers, transactions
type
Withdrawal* = object # EIP-4895
index* : uint64
validatorIndex*: uint64
address* : Address
amount* : uint64
DepositRequest* = object # EIP-6110
pubkey* : Bytes48
withdrawalCredentials*: Bytes32
amount* : uint64
signature* : Bytes96
index* : uint64
WithdrawalRequest* = object # EIP-7002
sourceAddress* : Address
validatorPubkey*: Bytes48
amount* : uint64
ConsolidationRequest* = object # EIP-7251
sourceAddress*: Address
sourcePubkey* : Bytes48
targetPubkey* : Bytes48
RequestType* = enum
DepositRequestType # EIP-6110
WithdrawalRequestType # EIP-7002
ConsolidationRequestType # EIP-7251
Request* = object
case requestType*: RequestType
of DepositRequestType:
deposit*: DepositRequest
of WithdrawalRequestType:
withdrawal*: WithdrawalRequest
of ConsolidationRequestType:
consolidation*: ConsolidationRequest
BlockBody* = object
transactions*: seq[Transaction]
uncles*: seq[Header]
withdrawals*: Opt[seq[Withdrawal]] # EIP-4895
requests*: Opt[seq[Request]] # EIP-7865
Block* = object
header* : Header
transactions*: seq[Transaction]
uncles* : seq[Header]
withdrawals*: Opt[seq[Withdrawal]] # EIP-4895
requests*: Opt[seq[Request]] # EIP-7865
const
EMPTY_UNCLE_HASH* = hash32"1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
# TODO https://github.com/nim-lang/Nim/issues/23354 - parameters should be sink
func init*(T: type Block, header: Header, body: BlockBody): T =
T(
header: header,
transactions: body.transactions,
uncles: body.uncles,
withdrawals: body.withdrawals,
)
template txs*(blk: Block): seq[Transaction] =
# Legacy name emulation
blk.transactions
func `==`*(a, b: Request): bool =
if a.requestType != b.requestType:
return false
case a.requestType
of DepositRequestType:
a.deposit == b.deposit
of WithdrawalRequestType:
a.withdrawal == b.withdrawal
of ConsolidationRequestType:
a.consolidation == b.consolidation

157
eth/common/blocks_rlp.nim Normal file
View File

@ -0,0 +1,157 @@
# 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.
{.push raises: [].}
import ./[addresses_rlp, blocks, base_rlp, hashes_rlp], ../rlp
from stew/objects import checkedEnumAssign
export addresses_rlp, blocks, base_rlp, hashes_rlp, rlp
proc append*(rlpWriter: var RlpWriter, request: DepositRequest) =
rlpWriter.appendRawBytes([DepositRequestType.byte])
rlpWriter.startList(5)
rlpWriter.append(request.pubkey)
rlpWriter.append(request.withdrawalCredentials)
rlpWriter.append(request.amount)
rlpWriter.append(request.signature)
rlpWriter.append(request.index)
proc read*(rlp: var Rlp, T: type DepositRequest): T {.raises: [RlpError].} =
if not rlp.hasData:
raise (ref MalformedRlpError)(
msg: "DepositRequestType expected but source RLP is empty"
)
let reqType = rlp.readRawByte()
if reqType != DepositRequestType:
raise (ref UnsupportedRlpError)(msg: "Unexpected DepositRequestType: " & $reqType)
var res: DepositRequest
rlp.tryEnterList()
rlp.read(res.pubkey)
rlp.read(res.withdrawalCredentials)
rlp.read(res.amount)
rlp.read(res.signature)
rlp.read(res.index)
if rlp.hasData:
raise (ref MalformedRlpError)(msg: "Extra data after DepositRequest")
res
proc append*(rlpWriter: var RlpWriter, request: WithdrawalRequest) =
rlpWriter.appendRawBytes([WithdrawalRequestType.byte])
rlpWriter.startList(3)
rlpWriter.append(request.sourceAddress)
rlpWriter.append(request.validatorPubkey)
rlpWriter.append(request.amount)
proc read*(rlp: var Rlp, T: type WithdrawalRequest): T {.raises: [RlpError].} =
if not rlp.hasData:
raise (ref MalformedRlpError)(
msg: "WithdrawalRequestType expected but source RLP is empty"
)
let reqType = rlp.readRawByte()
if reqType != WithdrawalRequestType:
raise
(ref UnsupportedRlpError)(msg: "Unexpected WithdrawalRequestType: " & $reqType)
var res: WithdrawalRequest
rlp.tryEnterList()
rlp.read(res.sourceAddress)
rlp.read(res.validatorPubkey)
rlp.read(res.amount)
if rlp.hasData:
raise (ref MalformedRlpError)(msg: "Extra data after WithdrawalRequest")
res
proc append*(rlpWriter: var RlpWriter, request: ConsolidationRequest) =
rlpWriter.appendRawBytes([ConsolidationRequestType.byte])
rlpWriter.startList(3)
rlpWriter.append(request.sourceAddress)
rlpWriter.append(request.sourcePubkey)
rlpWriter.append(request.targetPubkey)
proc read*(rlp: var Rlp, T: type ConsolidationRequest): T {.raises: [RlpError].} =
if not rlp.hasData:
raise (ref MalformedRlpError)(
msg: "ConsolidationRequestType expected but source RLP is empty"
)
let reqType = rlp.readRawByte()
if reqType != ConsolidationRequestType:
raise
(ref UnsupportedRlpError)(msg: "Unexpected ConsolidationRequestType: " & $reqType)
var res: ConsolidationRequest
rlp.tryEnterList()
rlp.read(res.sourceAddress)
rlp.read(res.sourcePubkey)
rlp.read(res.targetPubkey)
if rlp.hasData:
raise (ref MalformedRlpError)(msg: "Extra data after ConsolidationRequest")
res
proc append*(rlpWriter: var RlpWriter, request: Request) =
case request.requestType
of DepositRequestType:
rlpWriter.append(request.deposit)
of WithdrawalRequestType:
rlpWriter.append(request.withdrawal)
of ConsolidationRequestType:
rlpWriter.append(request.consolidation)
proc append*(rlpWriter: var RlpWriter, reqs: seq[Request] | openArray[Request]) =
rlpWriter.startList(reqs.len)
for req in reqs:
rlpWriter.append(rlp.encode(req))
proc read*(rlp: var Rlp, T: type Request): T {.raises: [RlpError].} =
if not rlp.hasData:
raise newException(MalformedRlpError, "Request expected but source RLP is empty")
if not rlp.isSingleByte:
raise newException(
MalformedRlpError, "RequestType byte is out of range, must be 0x00 to 0x7f"
)
let reqType = rlp.getByteValue
rlp.position += 1
var reqVal: RequestType
if checkedEnumAssign(reqVal, reqType):
result = Request(requestType: reqVal)
rlp.tryEnterList()
case reqVal
of DepositRequestType:
rlp.read(result.deposit.pubkey)
rlp.read(result.deposit.withdrawalCredentials)
rlp.read(result.deposit.amount)
rlp.read(result.deposit.signature)
rlp.read(result.deposit.index)
of WithdrawalRequestType:
rlp.read(result.withdrawal.sourceAddress)
rlp.read(result.withdrawal.validatorPubkey)
rlp.read(result.withdrawal.amount)
of ConsolidationRequestType:
rlp.read(result.consolidation.sourceAddress)
rlp.read(result.consolidation.sourcePubkey)
rlp.read(result.consolidation.targetPubkey)
else:
raise (ref UnsupportedRlpError)(msg: "Unexpected RequestType: " & $reqType)
proc read*(
rlp: var Rlp, T: (type seq[Request]) | (type openArray[Request])
): seq[Request] {.raises: [RlpError].} =
if not rlp.isList:
raise newException(
RlpTypeMismatch, "Requests list expected, but source RLP is not a list"
)
var reqs: seq[Request]
for item in rlp:
var rr = rlpFromBytes(rlp.read(seq[byte]))
reqs.add rr.read(Request)
reqs

View File

@ -1,4 +1,5 @@
# Copyright (c) 2022-2023 Status Research & Development GmbH # eth
# Copyright (c) 2024 Status Research & Development GmbH
# Licensed and distributed under either of # Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). # * 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). # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@ -6,41 +7,28 @@
{.push raises: [].} {.push raises: [].}
## keccak256 is used across ethereum as the "default" hash function and this # Minimal compatibility layer with earlier versions of this file, to be removed
## module provides a type and some helpers to produce such hashes # when users have upgraded
import {.deprecated.}
nimcrypto/[keccak, hash]
export import ./[addresses, hashes]
keccak.update, keccak.finish, hash
export hashes
from nimcrypto import MDigest
type type
KeccakHash* = MDigest[256] Hash256* {.deprecated.} = Hash32
## A hash value computed using keccak256 KeccakHash* {.deprecated.} = Hash32
## note: this aliases Eth2Digest too, which uses a different hash!
template withKeccakHash*(body: untyped): KeccakHash = template keccakHash*(v: openArray[byte]): Hash32 {.deprecated.} =
## This little helper will init the hash function and return the sliced keccak256(v)
## hash:
## let hashOfData = withHash: h.update(data)
block:
var h {.inject.}: keccak256
# init(h) # not needed for new instance
body
finish(h)
func keccakHash*(input: openArray[byte]): KeccakHash {.noinit.} = template keccakHash*(v: Address): Hash32 {.deprecated.} =
# We use the init-update-finish interface to avoid keccak256(v.data)
# the expensive burning/clearing memory (20~30% perf)
var ctx: keccak256
ctx.update(input)
ctx.finish()
func keccakHash*(input: openArray[char]): KeccakHash {.noinit.} = from nimcrypto/hash import MDigest
keccakHash(input.toOpenArrayByte(0, input.high()))
func keccakHash*(a, b: openArray[byte]): KeccakHash = converter toMDigest*(v: Hash32): MDigest[256] {.deprecated.} =
withKeccakHash: MDigest[256](data: v.data)
h.update a
h.update b

View File

@ -10,301 +10,41 @@
## from many places ## from many places
import import
std/[hashes, strutils], stew/byteutils,
stew/[byteutils, endians2], std/strutils,
stint, "."/
results, [accounts, addresses, base, blocks, hashes, headers, receipts, times, transactions]
./eth_hash,
./eth_times
export export accounts, addresses, base, blocks, hashes, headers, receipts, times, transactions
results,
stint,
eth_hash,
eth_times
type type
Hash256* = MDigest[256]
VMWord* = UInt256
BlockNonce* = array[8, byte]
AccountNonce* = uint64
Blob* = seq[byte]
BloomFilter* = array[256, byte]
EthAddress* = array[20, byte]
DifficultyInt* = UInt256
GasInt* = uint64
## Type alias used for gas computation
# For reference - https://github.com/status-im/nimbus/issues/35#issuecomment-391726518
Topic* = array[32, byte]
# topic can be Hash256 or zero padded bytes array
ForkID* = tuple[crc: uint32, nextFork: uint64]
# EIP 2364/2124
BlockNumber* = uint64
StorageKey* = array[32, byte]
# beware that although in some cases
# chainId have identical value to networkId
# they are separate entity
ChainId* = distinct uint64
NetworkId* = distinct uint
Account* = object
nonce*: AccountNonce
balance*: UInt256
storageRoot*: Hash256
codeHash*: Hash256
AccessPair* = object
address* : EthAddress
storageKeys*: seq[StorageKey]
AccessList* = seq[AccessPair]
VersionedHash* = Hash256
VersionedHashes* = seq[VersionedHash]
KzgCommitment* = array[48, byte]
KzgProof* = array[48, byte]
# 32 -> UInt256
# 4096 -> FIELD_ELEMENTS_PER_BLOB
NetworkBlob* = array[32*4096, byte]
TxType* = enum
TxLegacy # 0
TxEip2930 # 1
TxEip1559 # 2
TxEip4844 # 3
TxEip7702 # 4
NetworkPayload* = ref object
blobs* : seq[NetworkBlob]
commitments* : seq[KzgCommitment]
proofs* : seq[KzgProof]
Authorization* = object
chainId*: ChainId
address*: EthAddress
nonce*: AccountNonce
yParity*: uint64
R*: UInt256
S*: UInt256
Transaction* = object
txType* : TxType # EIP-2718
chainId* : ChainId # EIP-2930
nonce* : AccountNonce
gasPrice* : GasInt
maxPriorityFeePerGas*: GasInt # EIP-1559
maxFeePerGas* : GasInt # EIP-1559
gasLimit* : GasInt
to* : Opt[EthAddress]
value* : UInt256
payload* : Blob
accessList* : AccessList # EIP-2930
maxFeePerBlobGas*: UInt256 # EIP-4844
versionedHashes*: VersionedHashes # EIP-4844
authorizationList*: seq[Authorization]# EIP-7702
V* : uint64
R*, S* : UInt256
PooledTransaction* = object
tx*: Transaction
networkPayload*: NetworkPayload # EIP-4844
TransactionStatus* = enum
Unknown,
Queued,
Pending,
Included,
Error
TransactionStatusMsg* = object
status*: TransactionStatus
data*: Blob
Withdrawal* = object # EIP-4895
index* : uint64
validatorIndex*: uint64
address* : EthAddress
amount* : uint64
DepositRequest* = object # EIP-6110
pubkey* : array[48, byte]
withdrawalCredentials*: array[32, byte]
amount* : uint64
signature* : array[96, byte]
index* : uint64
WithdrawalRequest* = object # EIP-7002
sourceAddress* : array[20, byte]
validatorPubkey*: array[48, byte]
amount* : uint64
ConsolidationRequest* = object # EIP-7251
sourceAddress*: array[20, byte]
sourcePubkey* : array[48, byte]
targetPubkey* : array[48, byte]
# https://eips.ethereum.org/EIPS/eip-4844#header-extension
BlockHeader* = object
parentHash*: Hash256
ommersHash*: Hash256
coinbase*: EthAddress
stateRoot*: Hash256
txRoot*: Hash256
receiptsRoot*: Hash256
logsBloom*: BloomFilter
difficulty*: DifficultyInt
number*: BlockNumber
gasLimit*: GasInt
gasUsed*: GasInt
timestamp*: EthTime
extraData*: Blob
mixHash*: Hash256
nonce*: BlockNonce
baseFeePerGas*: Opt[UInt256] # EIP-1559
withdrawalsRoot*: Opt[Hash256] # EIP-4895
blobGasUsed*: Opt[uint64] # EIP-4844
excessBlobGas*: Opt[uint64] # EIP-4844
parentBeaconBlockRoot*: Opt[Hash256] # EIP-4788
requestsRoot*: Opt[Hash256] # EIP-7685
RequestType* = enum
DepositRequestType # EIP-6110
WithdrawalRequestType # EIP-7002
ConsolidationRequestType # EIP-7251
Request* = object
case requestType*: RequestType
of DepositRequestType:
deposit*: DepositRequest
of WithdrawalRequestType:
withdrawal*: WithdrawalRequest
of ConsolidationRequestType:
consolidation*: ConsolidationRequest
BlockBody* = object
transactions*: seq[Transaction]
uncles*: seq[BlockHeader]
withdrawals*: Opt[seq[Withdrawal]] # EIP-4895
requests*: Opt[seq[Request]] # EIP-7865
Log* = object
address*: EthAddress
topics*: seq[Topic]
data*: Blob
# easily convertible between
# ReceiptType and TxType
ReceiptType* = TxType
# LegacyReceipt = TxLegacy
# Eip2930Receipt = TxEip2930
# Eip1559Receipt = TxEip1559
# Eip4844Receipt = TxEip4844
# Eip7702Receipt = TxEip7702
Receipt* = object
receiptType* : ReceiptType
isHash* : bool # hash or status
status* : bool # EIP-658
hash* : Hash256
cumulativeGasUsed*: GasInt
logsBloom* : BloomFilter
logs* : seq[Log]
EthBlock* = object
header* : BlockHeader
transactions*: seq[Transaction]
uncles* : seq[BlockHeader]
withdrawals*: Opt[seq[Withdrawal]] # EIP-4895
requests*: Opt[seq[Request]] # EIP-7865
BlobsBundle* = object
commitments*: seq[KzgCommitment]
proofs*: seq[KzgProof]
blobs*: seq[NetworkBlob]
# TODO: Make BlockNumber a uint64 and deprecate either this or BlockHashOrNumber
HashOrNum* = object
case isHash*: bool
of true:
hash*: Hash256
else:
number*: BlockNumber
BlockHashOrNumber* = object BlockHashOrNumber* = object
case isHash*: bool case isHash*: bool
of true: of true:
hash*: Hash256 hash*: Hash32
else: else:
number*: uint64 number*: BlockNumber
ValidationResult* {.pure.} = enum # Convenience names for types that exist in multiple specs and therefore
OK # frequently conflict, name-wise.
Error # These names are intended to be used in "boundary" code that translates
# between types (consensus/json-rpc/rest/etc) while other code should use
const # native names within their domain
LegacyReceipt* = TxLegacy EthAccount* = Account
Eip2930Receipt* = TxEip2930 EthAddress* = Address
Eip1559Receipt* = TxEip1559 EthBlock* = Block
Eip4844Receipt* = TxEip4844 EthConsolidationRequest* = ConsolidationRequest
Eip7702Receipt* = TxEip7702 EthDepositRequest* = DepositRequest
EthHash32* = Hash32
EMPTY_ROOT_HASH* = "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".toDigest EthHeader* = Header
EMPTY_UNCLE_HASH* = "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347".toDigest EthTransaction* = Transaction
EMPTY_CODE_HASH* = "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470".toDigest EthReceipt* = Receipt
EthWithdrawapRequest* = WithdrawalRequest
template txs*(blk: EthBlock): seq[Transaction] =
# Legacy name emulation
blk.transactions
# starting from EIP-4399, `mixHash`/`mixDigest` field will be alled `prevRandao`
template prevRandao*(h: BlockHeader): Hash256 =
h.mixHash
template `prevRandao=`*(h: BlockHeader, hash: Hash256) =
h.mixHash = hash
func toBlockNonce*(n: uint64): BlockNonce =
n.toBytesBE()
func toUint*(n: BlockNonce): uint64 =
uint64.fromBytesBE(n)
func newAccount*(nonce: AccountNonce = 0, balance: UInt256 = 0.u256): Account =
result.nonce = nonce
result.balance = balance
result.storageRoot = EMPTY_ROOT_HASH
result.codeHash = EMPTY_CODE_HASH
func hasStatus*(rec: Receipt): bool {.inline.} =
rec.isHash == false
func hasStateRoot*(rec: Receipt): bool {.inline.} =
rec.isHash == true
func stateRoot*(rec: Receipt): Hash256 {.inline.} =
doAssert(rec.hasStateRoot)
rec.hash
template contractCreation*(tx: Transaction): bool = template contractCreation*(tx: Transaction): bool =
tx.to.isNone tx.to.isNone
func destination*(tx: Transaction): EthAddress = func init*(T: type BlockHashOrNumber, str: string): T {.raises: [ValueError].} =
# use getRecipient if you also want to get
# the contract address
if tx.to.isSome:
return tx.to.get
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:
raise newException(ValueError, "Block hash has incorrect length") raise newException(ValueError, "Block hash has incorrect length")
@ -321,51 +61,25 @@ func `$`*(x: BlockHashOrNumber): string =
else: else:
$x.number $x.number
template hasData*(b: Blob): bool = b.len > 0 # Backwards-compatibility section - this will be removed in future versions of
# this file
template deref*(b: Blob): auto = b import ./eth_hash
template deref*(o: Opt): auto = o.get export eth_hash
func `==`*(a, b: NetworkId): bool = type
a.uint == b.uint # Names that don't appear in the spec and have no particular purpose any more -
# just use the underlying type directly
BloomFilter* {.deprecated.} = Bloom
StorageKey* {.deprecated.} = Bytes32
Blob* {.deprecated.} = seq[byte]
VersionedHashes* {.deprecated.} = seq[VersionedHash]
BlockNonce* {.deprecated.} = Bytes8
func `$`*(x: NetworkId): string = func toBlockNonce*(n: uint64): BlockNonce {.deprecated.} =
`$`(uint(x)) n.to(BlockNonce)
func `==`*(a, b: EthAddress): bool {.inline.} = func newAccount*(
equalMem(unsafeAddr a[0], unsafeAddr b[0], a.len) nonce: AccountNonce = 0, balance: UInt256 = 0.u256
): Account {.deprecated: "Account.init".} =
# TODO https://github.com/nim-lang/Nim/issues/23678 Account.init(nonce = nonce, balance = balance)
func hash*(a: EthAddress): Hash {.inline.} =
static: doAssert sizeof(a) == 20
var a0{.noinit.}, a1 {.noinit.}: uint64
var a2{.noinit.}: uint32
# Addresses are more or less random so we should not need a fancy mixing
# function
copyMem(addr a0, unsafeAddr a[0], sizeof(a0))
copyMem(addr a1, unsafeAddr a[8], sizeof(a1))
copyMem(addr a2, unsafeAddr a[16], sizeof(a2))
cast[Hash](a0 xor a1 xor uint64(a2))
# TODO https://github.com/nim-lang/Nim/issues/23354 - parameters should be sink
func init*(T: type EthBlock, header: BlockHeader, body: BlockBody): T =
T(
header: header,
transactions: body.transactions,
uncles: body.uncles,
withdrawals: body.withdrawals,
)
func `==`*(a, b: Request): bool =
if a.requestType != b.requestType:
return false
case a.requestType
of DepositRequestType:
a.deposit == b.deposit
of WithdrawalRequestType:
a.withdrawal == b.withdrawal
of ConsolidationRequestType:
a.consolidation == b.consolidation

View File

@ -6,13 +6,9 @@
{.push raises: [].} {.push raises: [].}
import import std/[times, net], json_serialization, nimcrypto/[hash, utils], ./eth_types
std/[times, net],
json_serialization, nimcrypto/[hash, utils],
./eth_types
export export json_serialization
json_serialization
proc writeValue*(w: var JsonWriter, a: MDigest) {.raises: [IOError].} = proc writeValue*(w: var JsonWriter, a: MDigest) {.raises: [IOError].} =
w.writeValue a.data.toHex(true) w.writeValue a.data.toHex(true)
@ -25,8 +21,29 @@ proc readValue*(
except ValueError: except ValueError:
raiseUnexpectedValue(r, "Hex string expected") raiseUnexpectedValue(r, "Hex string expected")
proc writeValue*( proc writeValue*(w: var JsonWriter, a: Hash32) {.raises: [IOError].} =
w: var JsonWriter, value: StUint) {.inline, raises: [IOError].} = w.writeValue a.data.to0xHex()
proc readValue*(
r: var JsonReader, a: var Hash32
) {.inline, raises: [IOError, SerializationError].} =
try:
a = fromHex(type(a), r.readValue(string))
except ValueError:
raiseUnexpectedValue(r, "Hex string expected")
proc writeValue*(w: var JsonWriter, a: FixedBytes) {.raises: [IOError].} =
w.writeValue a.data.to0xHex()
proc readValue*[N](
r: var JsonReader, a: var FixedBytes[N]
) {.inline, raises: [IOError, SerializationError].} =
try:
a = fromHex(type(a), r.readValue(string))
except ValueError:
raiseUnexpectedValue(r, "Hex string expected")
proc writeValue*(w: var JsonWriter, value: StUint) {.inline, raises: [IOError].} =
w.writeValue $value w.writeValue $value
proc readValue*( proc readValue*(
@ -48,20 +65,7 @@ proc readValue*(
) {.inline, raises: [IOError, SerializationError].} = ) {.inline, raises: [IOError, SerializationError].} =
t = fromUnix r.readValue(int) t = fromUnix r.readValue(int)
# TODO: remove this once case object are fully supported proc writeValue*(w: var JsonWriter, value: BlockHashOrNumber) {.raises: [IOError].} =
# by the serialization library
proc writeValue*(
w: var JsonWriter, value: HashOrNum) {.raises: [IOError].} =
w.beginRecord(HashOrNum)
w.writeField("isHash", value.isHash)
if value.isHash:
w.writeField("hash", value.hash)
else:
w.writeField("number", value.number)
w.endRecord()
proc writeValue*(
w: var JsonWriter, value: BlockHashOrNumber) {.raises: [IOError].} =
w.writeValue $value w.writeValue $value
proc readValue*( proc readValue*(
@ -70,4 +74,6 @@ proc readValue*(
try: try:
value = init(BlockHashOrNumber, r.readValue(string)) value = init(BlockHashOrNumber, r.readValue(string))
except ValueError: except ValueError:
r.raiseUnexpectedValue("A hex-encoded block hash or a decimal block number expected") r.raiseUnexpectedValue(
"A hex-encoded block hash or a decimal block number expected"
)

View File

@ -5,725 +5,40 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import import
"."/[eth_types, eth_hash_rlp], "."/[
accounts_rlp, addresses_rlp, base_rlp, blocks_rlp, eth_types, hashes_rlp,
receipts_rlp, transactions_rlp,
],
../rlp ../rlp
from stew/objects
import checkedEnumAssign
export export
eth_types, eth_hash_rlp, rlp accounts_rlp, addresses_rlp, base_rlp, blocks_rlp, eth_types, hashes_rlp,
receipts_rlp, transactions_rlp, rlp
# proc read*(rlp: var Rlp, T: type EthTime): T {.raises: [RlpError].} =
# Rlp serialization: EthTime rlp.read(uint64)
#
proc read*(rlp: var Rlp, T: type StUint): T {.inline.} = proc append*(rlpWriter: var RlpWriter, value: BlockHashOrNumber) =
if rlp.isBlob:
let bytes = rlp.toBytes
if bytes.len > 0:
# be sure the amount of bytes matches the size of the stint
if bytes.len <= sizeof(result):
result.initFromBytesBE(bytes)
else:
raise newException(RlpTypeMismatch, "Unsigned integer expected, but the source RLP has the wrong length")
else:
result = 0.to(T)
else:
raise newException(RlpTypeMismatch, "Unsigned integer expected, but the source RLP is a list")
rlp.skipElem
func significantBytesBE(val: openArray[byte]): int =
## Returns the number of significant trailing bytes in a big endian
## representation of a number.
# TODO: move that in https://github.com/status-im/nim-byteutils
for i in 0 ..< val.len:
if val[i] != 0:
return val.len - i
return 1
proc append*(rlpWriter: var RlpWriter, value: StUint) =
if value > 128:
let bytes = value.toByteArrayBE
let nonZeroBytes = significantBytesBE(bytes)
rlpWriter.append bytes.toOpenArray(bytes.len - nonZeroBytes,
bytes.len - 1)
else:
rlpWriter.append(value.truncate(uint))
proc read*(rlp: var Rlp, T: type StInt): T {.inline.} =
# The Ethereum Yellow Paper defines the RLP serialization only
# for unsigned integers:
{.fatal: "RLP serialization of signed integers is not allowed".}
discard
proc append*(rlpWriter: var RlpWriter, value: StInt) =
# The Ethereum Yellow Paper defines the RLP serialization only
# for unsigned integers:
{.fatal: "RLP serialization of signed integers is not allowed".}
discard
proc append*[T](w: var RlpWriter, val: Opt[T]) =
if val.isSome:
w.append(val.get())
else:
w.append("")
proc appendTxLegacy(w: var RlpWriter, tx: Transaction) =
w.startList(9)
w.append(tx.nonce)
w.append(tx.gasPrice)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.V)
w.append(tx.R)
w.append(tx.S)
proc appendTxEip2930(w: var RlpWriter, tx: Transaction) =
w.startList(11)
w.append(tx.chainId.uint64)
w.append(tx.nonce)
w.append(tx.gasPrice)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.accessList)
w.append(tx.V)
w.append(tx.R)
w.append(tx.S)
proc appendTxEip1559(w: var RlpWriter, tx: Transaction) =
w.startList(12)
w.append(tx.chainId.uint64)
w.append(tx.nonce)
w.append(tx.maxPriorityFeePerGas)
w.append(tx.maxFeePerGas)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.accessList)
w.append(tx.V)
w.append(tx.R)
w.append(tx.S)
proc appendTxEip4844(w: var RlpWriter, tx: Transaction) =
w.startList(14)
w.append(tx.chainId.uint64)
w.append(tx.nonce)
w.append(tx.maxPriorityFeePerGas)
w.append(tx.maxFeePerGas)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.accessList)
w.append(tx.maxFeePerBlobGas)
w.append(tx.versionedHashes)
w.append(tx.V)
w.append(tx.R)
w.append(tx.S)
proc append*(w: var RlpWriter, x: Authorization) =
w.startList(6)
w.append(x.chainId.uint64)
w.append(x.address)
w.append(x.nonce)
w.append(x.yParity)
w.append(x.R)
w.append(x.S)
proc appendTxEip7702(w: var RlpWriter, tx: Transaction) =
w.startList(13)
w.append(tx.chainId.uint64)
w.append(tx.nonce)
w.append(tx.maxPriorityFeePerGas)
w.append(tx.maxFeePerGas)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.accessList)
w.append(tx.authorizationList)
w.append(tx.V)
w.append(tx.R)
w.append(tx.S)
proc appendTxPayload(w: var RlpWriter, tx: Transaction) =
case tx.txType
of TxLegacy:
w.appendTxLegacy(tx)
of TxEip2930:
w.appendTxEip2930(tx)
of TxEip1559:
w.appendTxEip1559(tx)
of TxEip4844:
w.appendTxEip4844(tx)
of TxEip7702:
w.appendTxEip7702(tx)
proc append*(w: var RlpWriter, tx: Transaction) =
if tx.txType != TxLegacy:
w.append(tx.txType)
w.appendTxPayload(tx)
proc append(w: var RlpWriter, networkPayload: NetworkPayload) =
w.append(networkPayload.blobs)
w.append(networkPayload.commitments)
w.append(networkPayload.proofs)
proc append*(w: var RlpWriter, tx: PooledTransaction) =
if tx.tx.txType != TxLegacy:
w.append(tx.tx.txType)
if tx.networkPayload != nil:
w.startList(4) # spec: rlp([tx_payload, blobs, commitments, proofs])
w.appendTxPayload(tx.tx)
if tx.networkPayload != nil:
w.append(tx.networkPayload)
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 readTxLegacy(rlp: var Rlp, tx: var Transaction) =
tx.txType = TxLegacy
rlp.tryEnterList()
rlp.read(tx.nonce)
rlp.read(tx.gasPrice)
rlp.read(tx.gasLimit)
rlp.read(tx.to)
rlp.read(tx.value)
rlp.read(tx.payload)
rlp.read(tx.V)
rlp.read(tx.R)
rlp.read(tx.S)
proc readTxEip2930(rlp: var Rlp, tx: var Transaction) =
tx.txType = TxEip2930
rlp.tryEnterList()
tx.chainId = rlp.read(uint64).ChainId
rlp.read(tx.nonce)
rlp.read(tx.gasPrice)
rlp.read(tx.gasLimit)
rlp.read(tx.to)
rlp.read(tx.value)
rlp.read(tx.payload)
rlp.read(tx.accessList)
rlp.read(tx.V)
rlp.read(tx.R)
rlp.read(tx.S)
proc readTxEip1559(rlp: var Rlp, tx: var Transaction) =
tx.txType = TxEip1559
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.V)
rlp.read(tx.R)
rlp.read(tx.S)
proc readTxEip4844(rlp: var Rlp, tx: var Transaction) =
tx.txType = TxEip4844
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.maxFeePerBlobGas)
rlp.read(tx.versionedHashes)
rlp.read(tx.V)
rlp.read(tx.R)
rlp.read(tx.S)
proc read*(rlp: var Rlp, T: type Authorization): T =
rlp.tryEnterList()
result.chainId = rlp.read(uint64).ChainId
rlp.read(result.address)
rlp.read(result.nonce)
rlp.read(result.yParity)
rlp.read(result.R)
rlp.read(result.S)
proc readTxEip7702(rlp: var Rlp, tx: var Transaction) =
tx.txType = TxEip7702
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)
rlp.read(tx.V)
rlp.read(tx.R)
rlp.read(tx.S)
proc readTxType(rlp: var Rlp): TxType =
if rlp.isList:
raise newException(RlpTypeMismatch,
"Transaction type expected, but source RLP is a list")
# EIP-2718: We MUST decode the first byte as a byte, not `rlp.read(int)`.
# If decoded with `rlp.read(int)`, bad transaction data (from the network)
# or even just incorrectly framed data for other reasons fails with
# any of these misleading error messages:
# - "Message too large to fit in memory"
# - "Number encoded with a leading zero"
# - "Read past the end of the RLP stream"
# - "Small number encoded in a non-canonical way"
# - "Attempt to read an Int value past the RLP end"
# - "The RLP contains a larger than expected Int value"
if not rlp.isSingleByte:
if not rlp.hasData:
raise newException(MalformedRlpError,
"Transaction expected but source RLP is empty")
raise newException(MalformedRlpError,
"TypedTransaction type byte is out of range, must be 0x00 to 0x7f")
let txType = rlp.getByteValue
rlp.position += 1
var txVal: TxType
if checkedEnumAssign(txVal, txType):
return txVal
raise newException(UnsupportedRlpError,
"TypedTransaction type must be 1, 2, or 3 in this version, got " & $txType)
proc readTxPayload(rlp: var Rlp, tx: var Transaction, txType: TxType) =
case txType
of TxLegacy:
raise newException(RlpTypeMismatch,
"LegacyTransaction should not be wrapped in a list")
of TxEip2930:
rlp.readTxEip2930(tx)
of TxEip1559:
rlp.readTxEip1559(tx)
of TxEip4844:
rlp.readTxEip4844(tx)
of TxEip7702:
rlp.readTxEip7702(tx)
proc readTxTyped(rlp: var Rlp, tx: var Transaction) =
let txType = rlp.readTxType()
rlp.readTxPayload(tx, txType)
proc read*(rlp: var Rlp, T: type Transaction): T =
# Individual transactions are encoded and stored as either `RLP([fields..])`
# for legacy transactions, or `Type || RLP([fields..])`. Both of these
# encodings are byte sequences. The part after `Type` doesn't have to be
# RLP in theory, but all types so far use RLP. EIP-2718 covers this.
if rlp.isList:
rlp.readTxLegacy(result)
else:
rlp.readTxTyped(result)
proc read(rlp: var Rlp, T: type NetworkPayload): T =
result = NetworkPayload()
rlp.read(result.blobs)
rlp.read(result.commitments)
rlp.read(result.proofs)
proc readTxTyped(rlp: var Rlp, tx: var PooledTransaction) =
let
txType = rlp.readTxType()
hasNetworkPayload =
if txType == TxEip4844:
rlp.listLen == 4
else:
false
if hasNetworkPayload:
rlp.tryEnterList() # spec: rlp([tx_payload, blobs, commitments, proofs])
rlp.readTxPayload(tx.tx, txType)
if hasNetworkPayload:
rlp.read(tx.networkPayload)
proc read*(rlp: var Rlp, T: type PooledTransaction): T =
if rlp.isList:
rlp.readTxLegacy(result.tx)
else:
rlp.readTxTyped(result)
proc read*(
rlp: var Rlp,
T: (type seq[Transaction]) | (type openArray[Transaction])
): seq[Transaction] =
# In arrays (sequences), transactions are encoded as either `RLP([fields..])`
# for legacy transactions, or `RLP(Type || RLP([fields..]))` for all typed
# transactions to date. Spot the extra `RLP(..)` blob encoding, to make it
# valid RLP inside a larger RLP. EIP-2976 covers this, "Typed Transactions
# over Gossip", although it's not very clear about the blob encoding.
#
# In practice the extra `RLP(..)` applies to all arrays/sequences of
# transactions. In principle, all aggregates (objects etc.), but
# arrays/sequences are enough. In `eth/65` protocol this is essential for
# the correct encoding/decoding of `Transactions`, `NewBlock`, and
# `PooledTransactions` network calls. We need a type match on both
# `openArray[Transaction]` and `seq[Transaction]` to catch all cases.
if not rlp.isList:
raise newException(RlpTypeMismatch,
"Transaction list expected, but source RLP is not a list")
for item in rlp:
var tx: Transaction
if item.isList:
item.readTxLegacy(tx)
else:
var rr = rlpFromBytes(rlp.read(Blob))
rr.readTxTyped(tx)
result.add tx
proc read*(
rlp: var Rlp,
T: (type seq[PooledTransaction]) | (type openArray[PooledTransaction])
): seq[PooledTransaction] =
if not rlp.isList:
raise newException(RlpTypeMismatch,
"PooledTransaction list expected, but source RLP is not a list")
for item in rlp:
var tx: PooledTransaction
if item.isList:
item.readTxLegacy(tx.tx)
else:
var rr = rlpFromBytes(rlp.read(Blob))
rr.readTxTyped(tx)
result.add tx
proc append*(rlpWriter: var RlpWriter,
txs: seq[Transaction] | openArray[Transaction]) {.inline.} =
# See above about encoding arrays/sequences of transactions.
rlpWriter.startList(txs.len)
for tx in txs:
if tx.txType == TxLegacy:
rlpWriter.append(tx)
else:
rlpWriter.append(rlp.encode(tx))
proc append*(
rlpWriter: var RlpWriter,
txs: seq[PooledTransaction] | openArray[PooledTransaction]) {.inline.} =
rlpWriter.startList(txs.len)
for tx in txs:
if tx.tx.txType == TxLegacy:
rlpWriter.append(tx)
else:
rlpWriter.append(rlp.encode(tx))
proc append*(w: var RlpWriter, rec: Receipt) =
if rec.receiptType in {Eip2930Receipt, Eip1559Receipt, Eip4844Receipt, Eip7702Receipt}:
w.append(rec.receiptType.uint)
w.startList(4)
if rec.isHash:
w.append(rec.hash)
else:
w.append(rec.status.uint8)
w.append(rec.cumulativeGasUsed)
w.append(rec.logsBloom)
w.append(rec.logs)
proc readReceiptLegacy(rlp: var Rlp, receipt: var Receipt) =
receipt.receiptType = LegacyReceipt
rlp.tryEnterList()
if rlp.isBlob and rlp.blobLen in {0, 1}:
receipt.isHash = false
receipt.status = rlp.read(uint8) == 1
elif rlp.isBlob and rlp.blobLen == 32:
receipt.isHash = true
receipt.hash = rlp.read(Hash256)
else:
raise newException(RlpTypeMismatch,
"HashOrStatus expected, but the source RLP is not a blob of right size.")
rlp.read(receipt.cumulativeGasUsed)
rlp.read(receipt.logsBloom)
rlp.read(receipt.logs)
proc readReceiptTyped(rlp: var Rlp, receipt: var Receipt) =
if not rlp.hasData:
raise newException(MalformedRlpError,
"Receipt expected but source RLP is empty")
if not rlp.isSingleByte:
raise newException(MalformedRlpError,
"ReceiptType byte is out of range, must be 0x00 to 0x7f")
let recType = rlp.getByteValue
rlp.position += 1
var txVal: ReceiptType
if checkedEnumAssign(txVal, recType):
case txVal:
of Eip2930Receipt, Eip1559Receipt, Eip4844Receipt, Eip7702Receipt:
receipt.receiptType = txVal
of LegacyReceipt:
# The legacy type should not be used here.
raise newException(MalformedRlpError,
"Invalid ReceiptType: " & $recType)
else:
raise newException(UnsupportedRlpError,
"Unsupported ReceiptType: " & $recType)
# Note: This currently remains the same as the legacy receipt.
rlp.tryEnterList()
if rlp.isBlob and rlp.blobLen in {0, 1}:
receipt.isHash = false
receipt.status = rlp.read(uint8) == 1
elif rlp.isBlob and rlp.blobLen == 32:
receipt.isHash = true
receipt.hash = rlp.read(Hash256)
else:
raise newException(RlpTypeMismatch,
"HashOrStatus expected, but the source RLP is not a blob of right size.")
rlp.read(receipt.cumulativeGasUsed)
rlp.read(receipt.logsBloom)
rlp.read(receipt.logs)
proc read*(rlp: var Rlp, T: type Receipt): T =
# Individual receipts are encoded and stored as either `RLP([fields..])`
# for legacy receipts, or `Type || RLP([fields..])`. Both of these
# encodings are byte sequences. The part after `Type` doesn't have to be
# RLP in theory, but all types so far use RLP. EIP-2718 covers this.
var receipt: Receipt
if rlp.isList:
rlp.readReceiptLegacy(receipt)
else:
rlp.readReceiptTyped(receipt)
receipt
proc read*(
rlp: var Rlp,
T: (type seq[Receipt]) | (type openArray[Receipt])
): seq[Receipt] =
# In arrays (sequences), receipts are encoded as either `RLP([fields..])`
# for legacy receipts, or `RLP(Type || RLP([fields..]))` for all typed
# receipts to date. Spot the extra `RLP(..)` blob encoding, to make it
# valid RLP inside a larger RLP. EIP-2976 covers this, "Typed Transactions
# over Gossip", although it's not very clear about the blob encoding.
#
# See also note about transactions above.
if not rlp.isList:
raise newException(RlpTypeMismatch,
"Receipts list expected, but source RLP is not a list")
var receipts: seq[Receipt]
for item in rlp:
var receipt: Receipt
if item.isList:
item.readReceiptLegacy(receipt)
else:
var rr = rlpFromBytes(rlp.read(Blob))
rr.readReceiptTyped(receipt)
receipts.add receipt
receipts
proc append*(
rlpWriter: var RlpWriter, receipts: seq[Receipt] | openArray[Receipt]
) =
# See above about encoding arrays/sequences of receipts.
rlpWriter.startList(receipts.len)
for receipt in receipts:
if receipt.receiptType == LegacyReceipt:
rlpWriter.append(receipt)
else:
rlpWriter.append(rlp.encode(receipt))
proc read*(rlp: var Rlp, T: type EthTime): T {.inline.} =
result = EthTime rlp.read(uint64)
proc append*(rlpWriter: var RlpWriter, value: HashOrNum) =
case value.isHash case value.isHash
of true: of true:
rlpWriter.append(value.hash) rlpWriter.append(value.hash)
else: else:
rlpWriter.append(value.number) rlpWriter.append(value.number)
proc read*(rlp: var Rlp, T: type HashOrNum): T = proc read*(rlp: var Rlp, T: type BlockHashOrNumber): T =
if rlp.blobLen == 32: if rlp.blobLen == 32:
result = HashOrNum(isHash: true, hash: rlp.read(Hash256)) BlockHashOrNumber(isHash: true, hash: rlp.read(Hash32))
else: else:
result = HashOrNum(isHash: false, number: rlp.read(BlockNumber)) BlockHashOrNumber(isHash: false, number: rlp.read(BlockNumber))
proc append*(rlpWriter: var RlpWriter, t: EthTime) {.inline.} = proc append*(rlpWriter: var RlpWriter, t: EthTime) {.inline.} =
rlpWriter.append(t.uint64) rlpWriter.append(t.uint64)
proc append*(rlpWriter: var RlpWriter, request: DepositRequest) = proc rlpHash*[T](v: T): Hash32 =
rlpWriter.appendRawBytes([DepositRequestType.byte]) keccak256(rlp.encode(v))
rlpWriter.startList(5)
rlpWriter.append(request.pubkey)
rlpWriter.append(request.withdrawalCredentials)
rlpWriter.append(request.amount)
rlpWriter.append(request.signature)
rlpWriter.append(request.index)
proc read*(rlp: var Rlp, T: type DepositRequest): T = proc rlpHash*(tx: PooledTransaction): Hash32 =
if not rlp.hasData: keccak256(rlp.encode(tx.tx))
raise (ref MalformedRlpError)(msg:
"DepositRequestType expected but source RLP is empty")
let reqType = rlp.readRawByte()
if reqType != DepositRequestType:
raise (ref UnsupportedRlpError)(msg:
"Unexpected DepositRequestType: " & $reqType)
var res: DepositRequest func blockHash*(h: Header): Hash32 {.inline.} =
rlp.tryEnterList() rlpHash(h)
rlp.read(res.pubkey)
rlp.read(res.withdrawalCredentials)
rlp.read(res.amount)
rlp.read(res.signature)
rlp.read(res.index)
if rlp.hasData:
raise (ref MalformedRlpError)(msg: "Extra data after DepositRequest")
res
proc append*(rlpWriter: var RlpWriter, request: WithdrawalRequest) =
rlpWriter.appendRawBytes([WithdrawalRequestType.byte])
rlpWriter.startList(3)
rlpWriter.append(request.sourceAddress)
rlpWriter.append(request.validatorPubkey)
rlpWriter.append(request.amount)
proc read*(rlp: var Rlp, T: type WithdrawalRequest): T =
if not rlp.hasData:
raise (ref MalformedRlpError)(msg:
"WithdrawalRequestType expected but source RLP is empty")
let reqType = rlp.readRawByte()
if reqType != WithdrawalRequestType:
raise (ref UnsupportedRlpError)(msg:
"Unexpected WithdrawalRequestType: " & $reqType)
var res: WithdrawalRequest
rlp.tryEnterList()
rlp.read(res.sourceAddress)
rlp.read(res.validatorPubkey)
rlp.read(res.amount)
if rlp.hasData:
raise (ref MalformedRlpError)(msg: "Extra data after WithdrawalRequest")
res
proc append*(rlpWriter: var RlpWriter, request: ConsolidationRequest) =
rlpWriter.appendRawBytes([ConsolidationRequestType.byte])
rlpWriter.startList(3)
rlpWriter.append(request.sourceAddress)
rlpWriter.append(request.sourcePubkey)
rlpWriter.append(request.targetPubkey)
proc read*(rlp: var Rlp, T: type ConsolidationRequest): T =
if not rlp.hasData:
raise (ref MalformedRlpError)(msg:
"ConsolidationRequestType expected but source RLP is empty")
let reqType = rlp.readRawByte()
if reqType != ConsolidationRequestType:
raise (ref UnsupportedRlpError)(msg:
"Unexpected ConsolidationRequestType: " & $reqType)
var res: ConsolidationRequest
rlp.tryEnterList()
rlp.read(res.sourceAddress)
rlp.read(res.sourcePubkey)
rlp.read(res.targetPubkey)
if rlp.hasData:
raise (ref MalformedRlpError)(msg: "Extra data after ConsolidationRequest")
res
proc append*(rlpWriter: var RlpWriter, request: Request) =
case request.requestType
of DepositRequestType:
rlpWriter.append(request.deposit)
of WithdrawalRequestType:
rlpWriter.append(request.withdrawal)
of ConsolidationRequestType:
rlpWriter.append(request.consolidation)
proc append*(
rlpWriter: var RlpWriter, reqs: seq[Request] | openArray[Request]
) =
rlpWriter.startList(reqs.len)
for req in reqs:
rlpWriter.append(rlp.encode(req))
proc read*(rlp: var Rlp, T: type Request): T =
if not rlp.hasData:
raise newException(MalformedRlpError,
"Request expected but source RLP is empty")
if not rlp.isSingleByte:
raise newException(MalformedRlpError,
"RequestType byte is out of range, must be 0x00 to 0x7f")
let reqType = rlp.getByteValue
rlp.position += 1
var reqVal: RequestType
if checkedEnumAssign(reqVal, reqType):
result = Request(requestType: reqVal)
rlp.tryEnterList()
case reqVal
of DepositRequestType:
rlp.read(result.deposit.pubkey)
rlp.read(result.deposit.withdrawalCredentials)
rlp.read(result.deposit.amount)
rlp.read(result.deposit.signature)
rlp.read(result.deposit.index)
of WithdrawalRequestType:
rlp.read(result.withdrawal.sourceAddress)
rlp.read(result.withdrawal.validatorPubkey)
rlp.read(result.withdrawal.amount)
of ConsolidationRequestType:
rlp.read(result.consolidation.sourceAddress)
rlp.read(result.consolidation.sourcePubkey)
rlp.read(result.consolidation.targetPubkey)
else:
raise (ref UnsupportedRlpError)(msg:
"Unexpected RequestType: " & $reqType)
proc read*(
rlp: var Rlp,
T: (type seq[Request]) | (type openArray[Request])
): seq[Request] =
if not rlp.isList:
raise newException(RlpTypeMismatch,
"Requests list expected, but source RLP is not a list")
var reqs: seq[Request]
for item in rlp:
var rr = rlpFromBytes(rlp.read(Blob))
reqs.add rr.read(Request)
reqs
proc rlpHash*[T](v: T): Hash256 =
keccakHash(rlp.encode(v))
proc rlpHash*(tx: PooledTransaction): Hash256 =
keccakHash(rlp.encode(tx.tx))
func blockHash*(h: BlockHeader): KeccakHash {.inline.} = rlpHash(h)
proc append*(rlpWriter: var RlpWriter, id: NetworkId) =
rlpWriter.append(id.uint)
proc read*(rlp: var Rlp, T: type NetworkId): T =
rlp.read(uint).NetworkId

99
eth/common/hashes.nim Normal file
View File

@ -0,0 +1,99 @@
# 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.
{.push raises: [].}
## Keccak256 hash function use thoughout the ethereum execution specification
## https://github.com/ethereum/execution-specs/blob/51fac24740e662844446439ceeb96a460aae0ba0/src/ethereum/crypto/hash.py
##
## The execution spec also declares a Hash64 type of mainly historical interest.
## Its usage was limited to ethash, the proof-of-work algorithm that has been
## replaced with proof-of-stake.
import std/[typetraits, hashes], nimcrypto/keccak, ./base, stew/assign2
export hashes, keccak.update, keccak.finish
type
Hash32* = distinct Bytes32
## https://github.com/ethereum/execution-specs/blob/51fac24740e662844446439ceeb96a460aae0ba0/src/ethereum/crypto/hash.py#L19
Root* = Hash32
## Alias used for MPT roots
const zeroHash32* = system.default(Hash32) ## Hash32 value consisting of all zeroes
template to*(v: array[32, byte], _: type Hash32): Hash32 =
Address(v)
template data*(v: Hash32): array[32, byte] =
distinctBase(v)
template `data=`*(a: Hash32, b: array[32, byte]) =
assign(distinctBase(a), b)
template copyFrom*(T: type Hash32, v: openArray[byte], start = 0): T =
## Copy up to N bytes from the given openArray, starting at `start` and
## filling any missing bytes with zero.
##
## This is a lenient function in that `v` may contain both fewer and more
## bytes than N and start might be out of bounds.
Hash32(Bytes32.copyFrom(v, start))
template default*(_: type Hash32): Hash32 =
# Avoid bad codegen where fixed bytes are zeroed byte-by-byte at call site
zeroHash32
func `==`*(a, b: Hash32): bool {.borrow.}
func hash*(a: Hash32): Hash {.inline.} =
# Hashes are already supposed to be random so we use a faster mixing function
var tmp {.noinit.}: array[4, uint64]
copyMem(addr tmp[0], addr a.data[0], sizeof(a))
cast[Hash](tmp[0] + tmp[1] + tmp[2] + tmp[3])
func toHex*(a: Hash32): string {.borrow.}
func to0xHex*(a: Hash32): string {.borrow.}
func `$`*(a: Hash32): string {.borrow.}
func fromHex*(_: type Hash32, s: openArray[char]): Hash32 {.raises: [ValueError].} =
Hash32(Bytes32.fromHex(s))
template to*(s: static string, _: type Hash32): Hash32 =
const hash = Hash32.fromHex(s)
hash
template hash32*(s: static string): Hash32 =
s.to(Hash32)
template to*(v: MDigest[256], _: type Hash32): Hash32 =
Hash32(v.data)
template to*(v: Hash32, _: type MDigest[256]): MDigest[256] =
var tmp {.noinit.}: MDigest[256]
assign(tmp.data, v.data)
tmp
const
emptyKeccak256* =
hash32"c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
## Hash value of `keccak256([])`, ie the empty string
emptyRoot* = hash32"56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
## Hash value of `keccak256(rlp(null))` which corresponds to the encoding
## of an empty MPT trie
func keccak256*(input: openArray[byte]): Hash32 {.noinit.} =
var ctx: keccak.keccak256
ctx.update(input)
ctx.finish().to(Hash32)
func keccak256*(input: openArray[char]): Hash32 {.noinit.} =
keccak256(input.toOpenArrayByte(0, input.high))
template withKeccak256*(body: untyped): Hash32 =
var h {.inject.}: keccak.keccak256
body
h.finish().to(Hash32)

View File

@ -1,17 +1,18 @@
# Copyright (c) 2022 Status Research & Development GmbH # eth
# Copyright (c) 2024 Status Research & Development GmbH
# Licensed and distributed under either of # Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). # * 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). # * 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. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import {.push raises: [].}
./eth_hash,
../rlp
export eth_hash, rlp import "."/hashes, ../rlp
proc read*(rlp: var Rlp, T: typedesc[MDigest]): T = export hashes, rlp
result.data = rlp.read(type(result.data))
proc append*(rlpWriter: var RlpWriter, a: MDigest) = proc read*(rlp: var Rlp, T: type Hash32): Hash32 {.raises: [RlpError].} =
Hash32(rlp.read(type(result.data)))
proc append*(rlpWriter: var RlpWriter, a: Hash32) =
rlpWriter.append(a.data) rlpWriter.append(a.data)

50
eth/common/headers.nim Normal file
View File

@ -0,0 +1,50 @@
# 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.
{.push raises: [].}
import "."/[addresses, base, hashes, times]
export addresses, base, hashes, times
type
DifficultyInt* = UInt256
Header* = object
# https://github.com/ethereum/execution-specs/blob/51fac24740e662844446439ceeb96a460aae0ba0/src/ethereum/cancun/blocks.py
parentHash*: Hash32
ommersHash*: Hash32
coinbase*: Address
stateRoot*: Root
transactionsRoot*: Root
receiptsRoot*: Root
logsBloom*: Bloom
difficulty*: DifficultyInt
number*: BlockNumber
gasLimit*: GasInt
gasUsed*: GasInt
timestamp*: EthTime
extraData*: seq[byte]
mixHash*: Hash32
nonce*: Bytes8
baseFeePerGas*: Opt[UInt256] # EIP-1559
withdrawalsRoot*: Opt[Hash32] # EIP-4895
blobGasUsed*: Opt[uint64] # EIP-4844
excessBlobGas*: Opt[uint64] # EIP-4844
parentBeaconBlockRoot*: Opt[Hash32] # EIP-4788
requestsRoot*: Opt[Hash32] # EIP-7685
BlockHeader*{.deprecated: "Header".} = Header
# starting from EIP-4399, `mixDigest` field is called `prevRandao`
template prevRandao*(h: Header): Hash32 =
h.mixHash
template `prevRandao=`*(h: Header, hash: Hash32) =
h.mixHash = hash
template txRoot*(h: Header): Root = h.transactionsRoot

249
eth/common/keys.nim Normal file
View File

@ -0,0 +1,249 @@
# 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.
{.push raises: [].}
# This module contains adaptations of the general secp interface to help make
# working with keys and signatures as they appear in Ethereum in particular:
#
# * Public keys as serialized in uncompressed format without the initial byte
# * Shared secrets are serialized in raw format without the initial byte
# * distinct types are used to avoid confusion with the "standard" secp types
import
std/strformat,
secp256k1, bearssl/rand,
stew/[byteutils, objects, ptrops],
results,
"."/[hashes, addresses]
from nimcrypto/utils import burnMem
export secp256k1, results, rand
const
KeyLength* = SkEcdhSecretSize
## Ecdh shared secret key length without leading byte
## (publicKey * privateKey).x, where length of x is 32 bytes
FullKeyLength* = KeyLength + 1
## Ecdh shared secret with leading byte 0x02 or 0x03
RawPublicKeySize* = SkRawPublicKeySize - 1
## Size of uncompressed public key without format marker (0x04)
RawSignatureSize* = SkRawRecoverableSignatureSize
RawSignatureNRSize* = SkRawSignatureSize
type
PrivateKey* = distinct SkSecretKey
PublicKey* = distinct SkPublicKey
## Public key that's serialized to raw format without 0x04 marker
Signature* = distinct SkRecoverableSignature
## Ethereum uses recoverable signatures allowing some space savings
SignatureNR* = distinct SkSignature
## ...but ENR uses non-recoverable signatures!
SharedSecretFull* = object
## Representation of ECDH shared secret, with leading `y` byte
## (`y` is 0x02 when (publicKey * privateKey).y is even or 0x03 when odd)
data*: array[FullKeyLength, byte]
SharedSecret* = object
## Representation of ECDH shared secret, without leading `y` byte
data*: array[KeyLength, byte]
KeyPair* = distinct SkKeyPair
template pubkey*(v: KeyPair): PublicKey = PublicKey(SkKeyPair(v).pubkey)
template seckey*(v: KeyPair): PrivateKey = PrivateKey(SkKeyPair(v).seckey)
proc newRng*(): ref HmacDrbgContext =
# You should only create one instance of the RNG per application / library
# Ref is used so that it can be shared between components
HmacDrbgContext.new()
proc random*(T: type PrivateKey, rng: var HmacDrbgContext): T =
let rngPtr = unsafeAddr rng # doesn't escape
proc callRng(data: var openArray[byte]) =
generate(rngPtr[], data)
T(SkSecretKey.random(callRng))
func fromRaw*(T: type PrivateKey, data: openArray[byte]): SkResult[T] =
SkSecretKey.fromRaw(data).mapConvert(T)
func fromHex*(T: type PrivateKey, data: string): SkResult[T] =
SkSecretKey.fromHex(data).mapConvert(T)
func toRaw*(seckey: PrivateKey): array[SkRawSecretKeySize, byte] =
SkSecretKey(seckey).toRaw()
func toPublicKey*(seckey: PrivateKey): PublicKey {.borrow.}
func fromRaw*(T: type PublicKey, data: openArray[byte]): SkResult[T] =
if data.len() == SkRawCompressedPublicKeySize:
return SkPublicKey.fromRaw(data).mapConvert(T)
if len(data) < SkRawPublicKeySize - 1:
return err(static(
&"keys: raw eth public key should be {SkRawPublicKeySize - 1} bytes"))
var d: array[SkRawPublicKeySize, byte]
d[0] = 0x04'u8
copyMem(addr d[1], unsafeAddr data[0], 64)
SkPublicKey.fromRaw(d).mapConvert(T)
func fromHex*(T: type PublicKey, data: string): SkResult[T] =
T.fromRaw(? seq[byte].fromHex(data))
func toRaw*(pubkey: PublicKey): array[RawPublicKeySize, byte] =
let tmp = SkPublicKey(pubkey).toRaw()
copyMem(addr result[0], unsafeAddr tmp[1], 64)
func toRawCompressed*(pubkey: PublicKey): array[33, byte] {.borrow.}
proc random*(T: type KeyPair, rng: var HmacDrbgContext): T =
let seckey = SkSecretKey(PrivateKey.random(rng))
KeyPair(SkKeyPair(
seckey: seckey,
pubkey: seckey.toPublicKey()
))
func toKeyPair*(seckey: PrivateKey): KeyPair =
KeyPair(SkKeyPair(
seckey: SkSecretKey(seckey), pubkey: SkSecretKey(seckey).toPublicKey()))
func fromRaw*(T: type Signature, data: openArray[byte]): SkResult[T] =
SkRecoverableSignature.fromRaw(data).mapConvert(T)
func fromHex*(T: type Signature, data: string): SkResult[T] =
T.fromRaw(? seq[byte].fromHex(data))
func toRaw*(sig: Signature): array[RawSignatureSize, byte] {.borrow.}
func fromRaw*(T: type SignatureNR, data: openArray[byte]): SkResult[T] =
SkSignature.fromRaw(data).mapConvert(T)
func toRaw*(sig: SignatureNR): array[RawSignatureNRSize, byte] {.borrow.}
func to*(pubkey: PublicKey, _: type Address): Address =
## Convert public key to canonical address.
let hash = keccak256(pubkey.toRaw())
hash.to(Address)
func toAddress*(pubkey: PublicKey): string {.deprecated.} =
## Convert public key to hexadecimal string address.
pubkey.to(Address).to0xHex()
func toChecksumAddress*(pubkey: PublicKey): string =
## Convert public key to checksumable mixed-case address (EIP-55).
pubkey.to(Address).toChecksum0xHex()
func validateChecksumAddress*(a: string): bool =
## Validate checksumable mixed-case address (EIP-55).
Address.hasValidChecksum(a)
template toCanonicalAddress*(pubkey: PublicKey): Address =
## Convert public key to canonical address.
pubkey.to(Address)
func `$`*(pubkey: PublicKey): string =
## Convert public key to hexadecimal string representation.
toHex(pubkey.toRaw())
func `$`*(sig: Signature): string =
## Convert signature to hexadecimal string representation.
toHex(sig.toRaw())
func `$`*(seckey: PrivateKey): string =
## Convert private key to hexadecimal string representation
toHex(seckey.toRaw())
func `==`*(lhs, rhs: PublicKey): bool {.borrow.}
func `==`*(lhs, rhs: Signature): bool {.borrow.}
func `==`*(lhs, rhs: SignatureNR): bool {.borrow.}
func clear*(v: var PrivateKey) {.borrow.}
func clear*(v: var KeyPair) =
v.seckey.clear()
func clear*(v: var SharedSecret) = burnMem(v.data)
func clear*(v: var SharedSecretFull) = burnMem(v.data)
func sign*(seckey: PrivateKey, msg: SkMessage): Signature =
Signature(signRecoverable(SkSecretKey(seckey), msg))
func sign*(seckey: PrivateKey, msg: openArray[byte]): Signature =
let hash = keccak256(msg)
sign(seckey, SkMessage(hash.data))
func signNR*(seckey: PrivateKey, msg: SkMessage): SignatureNR =
SignatureNR(sign(SkSecretKey(seckey), msg))
func signNR*(seckey: PrivateKey, msg: openArray[byte]): SignatureNR =
let hash = keccak256(msg)
signNR(seckey, SkMessage(hash.data))
func recover*(sig: Signature, msg: SkMessage): SkResult[PublicKey] =
recover(SkRecoverableSignature(sig), msg).mapConvert(PublicKey)
func recover*(sig: Signature, msg: openArray[byte]): SkResult[PublicKey] =
let hash = keccak256(msg)
recover(sig, SkMessage(hash.data))
func verify*(sig: SignatureNR, msg: SkMessage, key: PublicKey): bool =
verify(SkSignature(sig), msg, SkPublicKey(key))
func verify*(sig: SignatureNR, msg: openArray[byte], key: PublicKey): bool =
let hash = keccak256(msg)
verify(sig, SkMessage(hash.data), key)
proc ecdhSharedSecretHash(output: ptr byte, x32, y32: ptr byte, data: pointer): cint
{.cdecl, raises: [].} =
## Hash function used by `ecdhSharedSecret` below
# `x32` and `y32` are result of scalar multiplication of publicKey * privateKey.
# Both `x32` and `y32` are 32 bytes length.
# Take the `x32` part as ecdh shared secret.
# output length is derived from x32 length and taken from ecdh
# generic parameter `KeyLength`
copyMem(output, x32, KeyLength)
return 1
func ecdhSharedSecret*(seckey: PrivateKey, pubkey: PublicKey): SharedSecret =
## Compute ecdh agreed shared secret.
let res = ecdh[KeyLength](SkSecretKey(seckey), SkPublicKey(pubkey), ecdhSharedSecretHash, nil)
# This function only fail if the hash function return zero.
# Because our hash function always success, we can turn the error into defect
doAssert res.isOk, $res.error
SharedSecret(data: res.get)
proc ecdhSharedSecretFullHash(output: ptr byte, x32, y32: ptr byte, data: pointer): cint
{.cdecl, raises: [].} =
## Hash function used by `ecdhSharedSecretFull` below
# `x32` and `y32` are result of scalar multiplication of publicKey * privateKey.
# Leading byte is 0x02 if `y32` is even and 0x03 if odd. Then concat with `x32`.
# output length is derived from `x32` length + 1 and taken from ecdh
# generic parameter `FullKeyLength`
# output[0] = 0x02 | (y32[31] & 1)
output[] = 0x02 or (y32.offset(31)[] and 0x01)
copyMem(output.offset(1), x32, KeyLength)
return 1
func ecdhSharedSecretFull*(seckey: PrivateKey, pubkey: PublicKey): SharedSecretFull =
## Compute ecdh agreed shared secret with leading byte.
let res = ecdh[FullKeyLength](SkSecretKey(seckey), SkPublicKey(pubkey), ecdhSharedSecretFullHash, nil)
# This function only fail if the hash function return zero.
# Because our hash function always success, we can turn the error into defect
doAssert res.isOk, $res.error
SharedSecretFull(data: res.get)

56
eth/common/receipts.nim Normal file
View File

@ -0,0 +1,56 @@
# 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.
{.push raises: [].}
import "."/[addresses, base, hashes, transactions]
export addresses, base, hash, transactions
type
Topic* = Bytes32
# topic can be Hash32 or zero padded bytes array
Log* = object
address*: Address
topics*: seq[Topic]
data*: seq[byte]
# easily convertible between
# ReceiptType and TxType
ReceiptType* = TxType
# LegacyReceipt = TxLegacy
# Eip2930Receipt = TxEip2930
# Eip1559Receipt = TxEip1559
# Eip4844Receipt = TxEip4844
# Eip7702Receipt = TxEip7702
Receipt* = object
receiptType* : ReceiptType
isHash* : bool # hash or status
status* : bool # EIP-658
hash* : Hash32
cumulativeGasUsed*: GasInt
logsBloom* : Bloom
logs* : seq[Log]
const
LegacyReceipt* = TxLegacy
Eip2930Receipt* = TxEip2930
Eip1559Receipt* = TxEip1559
Eip4844Receipt* = TxEip4844
Eip7702Receipt* = TxEip7702
func hasStatus*(rec: Receipt): bool {.inline.} =
rec.isHash == false
func hasStateRoot*(rec: Receipt): bool {.inline.} =
rec.isHash == true
func stateRoot*(rec: Receipt): Hash32 {.inline.} =
doAssert(rec.hasStateRoot)
rec.hash

134
eth/common/receipts_rlp.nim Normal file
View File

@ -0,0 +1,134 @@
# 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.
{.push raises: [].}
import ./[addresses_rlp, base_rlp, hashes_rlp, receipts], ../rlp
from stew/objects import checkedEnumAssign
export addresses_rlp, base_rlp, hashes_rlp, receipts, rlp
proc append*(w: var RlpWriter, rec: Receipt) =
if rec.receiptType in {Eip2930Receipt, Eip1559Receipt, Eip4844Receipt, Eip7702Receipt}:
w.append(rec.receiptType.uint)
w.startList(4)
if rec.isHash:
w.append(rec.hash)
else:
w.append(rec.status.uint8)
w.append(rec.cumulativeGasUsed)
w.append(rec.logsBloom)
w.append(rec.logs)
proc readReceiptLegacy(rlp: var Rlp, receipt: var Receipt) {.raises: [RlpError].} =
receipt.receiptType = LegacyReceipt
rlp.tryEnterList()
if rlp.isBlob and rlp.blobLen in {0, 1}:
receipt.isHash = false
receipt.status = rlp.read(uint8) == 1
elif rlp.isBlob and rlp.blobLen == 32:
receipt.isHash = true
receipt.hash = rlp.read(Hash32)
else:
raise newException(
RlpTypeMismatch,
"HashOrStatus expected, but the source RLP is not a blob of right size.",
)
rlp.read(receipt.cumulativeGasUsed)
rlp.read(receipt.logsBloom)
rlp.read(receipt.logs)
proc readReceiptTyped(rlp: var Rlp, receipt: var Receipt) {.raises: [RlpError].} =
if not rlp.hasData:
raise newException(MalformedRlpError, "Receipt expected but source RLP is empty")
if not rlp.isSingleByte:
raise newException(
MalformedRlpError, "ReceiptType byte is out of range, must be 0x00 to 0x7f"
)
let recType = rlp.getByteValue
rlp.position += 1
var txVal: ReceiptType
if checkedEnumAssign(txVal, recType):
case txVal
of Eip2930Receipt, Eip1559Receipt, Eip4844Receipt, Eip7702Receipt:
receipt.receiptType = txVal
of LegacyReceipt:
# The legacy type should not be used here.
raise newException(MalformedRlpError, "Invalid ReceiptType: " & $recType)
else:
raise newException(UnsupportedRlpError, "Unsupported ReceiptType: " & $recType)
# Note: This currently remains the same as the legacy receipt.
rlp.tryEnterList()
if rlp.isBlob and rlp.blobLen in {0, 1}:
receipt.isHash = false
receipt.status = rlp.read(uint8) == 1
elif rlp.isBlob and rlp.blobLen == 32:
receipt.isHash = true
receipt.hash = rlp.read(Hash32)
else:
raise newException(
RlpTypeMismatch,
"HashOrStatus expected, but the source RLP is not a blob of right size.",
)
rlp.read(receipt.cumulativeGasUsed)
rlp.read(receipt.logsBloom)
rlp.read(receipt.logs)
proc read*(rlp: var Rlp, T: type Receipt): T {.raises: [RlpError].} =
# Individual receipts are encoded and stored as either `RLP([fields..])`
# for legacy receipts, or `Type || RLP([fields..])`. Both of these
# encodings are byte sequences. The part after `Type` doesn't have to be
# RLP in theory, but all types so far use RLP. EIP-2718 covers this.
var receipt: Receipt
if rlp.isList:
rlp.readReceiptLegacy(receipt)
else:
rlp.readReceiptTyped(receipt)
receipt
proc read*(
rlp: var Rlp, T: (type seq[Receipt]) | (type openArray[Receipt])
): seq[Receipt] {.raises: [RlpError].} =
# In arrays (sequences), receipts are encoded as either `RLP([fields..])`
# for legacy receipts, or `RLP(Type || RLP([fields..]))` for all typed
# receipts to date. Spot the extra `RLP(..)` blob encoding, to make it
# valid RLP inside a larger RLP. EIP-2976 covers this, "Typed Transactions
# over Gossip", although it's not very clear about the blob encoding.
#
# See also note about transactions above.
if not rlp.isList:
raise newException(
RlpTypeMismatch, "Receipts list expected, but source RLP is not a list"
)
var receipts: seq[Receipt]
for item in rlp:
var receipt: Receipt
if item.isList:
item.readReceiptLegacy(receipt)
else:
var rr = rlpFromBytes(rlp.read(seq[byte]))
rr.readReceiptTyped(receipt)
receipts.add receipt
receipts
proc append*(rlpWriter: var RlpWriter, receipts: seq[Receipt] | openArray[Receipt]) =
# See above about encoding arrays/sequences of receipts.
rlpWriter.startList(receipts.len)
for receipt in receipts:
if receipt.receiptType == LegacyReceipt:
rlpWriter.append(receipt)
else:
rlpWriter.append(rlp.encode(receipt))

View File

@ -8,14 +8,13 @@
# at your option. This file may not be copied, modified, or distributed # at your option. This file may not be copied, modified, or distributed
# except according to those terms. # except according to those terms.
import from std/times import getTime, toUnix
std/times
type type
EthTime* = distinct uint64 EthTime* = distinct uint64
proc now*(_: type EthTime): EthTime = proc now*(_: type EthTime): EthTime =
getTime().utc.toTime.toUnix.EthTime getTime().toUnix.EthTime
func `+`*(a: EthTime, b: EthTime): EthTime = func `+`*(a: EthTime, b: EthTime): EthTime =
EthTime(a.uint64 + b.uint64) EthTime(a.uint64 + b.uint64)

View File

@ -1,115 +1,3 @@
import {.deprecated: "transactions_rlp".}
./eth_types_rlp import transactions_rlp
export transactions_rlp
export eth_types_rlp
const
EIP155_CHAIN_ID_OFFSET* = 35'u64
func rlpEncodeLegacy(tx: Transaction): auto =
var w = initRlpWriter()
w.startList(6)
w.append(tx.nonce)
w.append(tx.gasPrice)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.finish()
func rlpEncodeEip155(tx: Transaction): auto =
let chainId = (tx.V - EIP155_CHAIN_ID_OFFSET) div 2
var w = initRlpWriter()
w.startList(9)
w.append(tx.nonce)
w.append(tx.gasPrice)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(chainId)
w.append(0'u8)
w.append(0'u8)
w.finish()
func rlpEncodeEip2930(tx: Transaction): auto =
var w = initRlpWriter()
w.append(TxEip2930)
w.startList(8)
w.append(tx.chainId.uint64)
w.append(tx.nonce)
w.append(tx.gasPrice)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.accessList)
w.finish()
func rlpEncodeEip1559(tx: Transaction): auto =
var w = initRlpWriter()
w.append(TxEip1559)
w.startList(9)
w.append(tx.chainId.uint64)
w.append(tx.nonce)
w.append(tx.maxPriorityFeePerGas)
w.append(tx.maxFeePerGas)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.accessList)
w.finish()
func rlpEncodeEip4844(tx: Transaction): auto =
var w = initRlpWriter()
w.append(TxEip4844)
w.startList(11)
w.append(tx.chainId.uint64)
w.append(tx.nonce)
w.append(tx.maxPriorityFeePerGas)
w.append(tx.maxFeePerGas)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.accessList)
w.append(tx.maxFeePerBlobGas)
w.append(tx.versionedHashes)
w.finish()
func rlpEncodeEip7702(tx: Transaction): auto =
var w = initRlpWriter()
w.append(TxEip7702)
w.startList(10)
w.append(tx.chainId.uint64)
w.append(tx.nonce)
w.append(tx.maxPriorityFeePerGas)
w.append(tx.maxFeePerGas)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.accessList)
w.append(tx.authorizationList)
w.finish()
func rlpEncode*(tx: Transaction): auto =
case tx.txType
of TxLegacy:
if tx.V >= EIP155_CHAIN_ID_OFFSET:
tx.rlpEncodeEip155
else:
tx.rlpEncodeLegacy
of TxEip2930:
tx.rlpEncodeEip2930
of TxEip1559:
tx.rlpEncodeEip1559
of TxEip4844:
tx.rlpEncodeEip4844
of TxEip7702:
tx.rlpEncodeEip7702
func txHashNoSignature*(tx: Transaction): Hash256 =
# Hash transaction without signature
keccakHash(rlpEncode(tx))

View File

@ -0,0 +1,75 @@
# 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.
{.push raises: [].}
import "."/addresses, base
export addresses, base
type
AccessPair* = object
address* : Address
storageKeys*: seq[Bytes32]
AccessList* = seq[AccessPair]
VersionedHash* = Bytes32
Authorization* = object
chainId*: ChainId
address*: Address
nonce*: AccountNonce
yParity*: uint64
R*: UInt256
S*: UInt256
TxType* = enum
TxLegacy # 0
TxEip2930 # 1
TxEip1559 # 2
TxEip4844 # 3
TxEip7702 # 4
Transaction* = object
txType* : TxType # EIP-2718
chainId* : ChainId # EIP-2930
nonce* : AccountNonce
gasPrice* : GasInt
maxPriorityFeePerGas*: GasInt # EIP-1559
maxFeePerGas* : GasInt # EIP-1559
gasLimit* : GasInt
to* : Opt[Address]
value* : UInt256
payload* : seq[byte]
accessList* : AccessList # EIP-2930
maxFeePerBlobGas*: UInt256 # EIP-4844
versionedHashes*: seq[VersionedHash] # EIP-4844
authorizationList*: seq[Authorization]# EIP-7702
V* : uint64
R*, S* : UInt256
# 32 -> UInt256
# 4096 -> FIELD_ELEMENTS_PER_BLOB
NetworkBlob* = array[32*4096, byte]
BlobsBundle* = object
commitments*: seq[KzgCommitment]
proofs*: seq[KzgProof]
blobs*: seq[NetworkBlob]
# TODO why was this part of eth types?
NetworkPayload* = ref BlobsBundle
PooledTransaction* = object
tx*: Transaction
networkPayload*: NetworkPayload # EIP-4844
func destination*(tx: Transaction): Address =
# use getRecipient if you also want to get
# the contract address
tx.to.valueOr(default(Address))

View File

@ -0,0 +1,479 @@
# 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.
{.push raises: [].}
import "."/[addresses_rlp, base_rlp, transactions], ../rlp
from stew/objects import checkedEnumAssign
export addresses_rlp, base_rlp, transactions, rlp
proc appendTxLegacy(w: var RlpWriter, tx: Transaction) =
w.startList(9)
w.append(tx.nonce)
w.append(tx.gasPrice)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.V)
w.append(tx.R)
w.append(tx.S)
proc appendTxEip2930(w: var RlpWriter, tx: Transaction) =
w.startList(11)
w.append(tx.chainId.uint64)
w.append(tx.nonce)
w.append(tx.gasPrice)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.accessList)
w.append(tx.V)
w.append(tx.R)
w.append(tx.S)
proc appendTxEip1559(w: var RlpWriter, tx: Transaction) =
w.startList(12)
w.append(tx.chainId.uint64)
w.append(tx.nonce)
w.append(tx.maxPriorityFeePerGas)
w.append(tx.maxFeePerGas)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.accessList)
w.append(tx.V)
w.append(tx.R)
w.append(tx.S)
proc appendTxEip4844(w: var RlpWriter, tx: Transaction) =
w.startList(14)
w.append(tx.chainId.uint64)
w.append(tx.nonce)
w.append(tx.maxPriorityFeePerGas)
w.append(tx.maxFeePerGas)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.accessList)
w.append(tx.maxFeePerBlobGas)
w.append(tx.versionedHashes)
w.append(tx.V)
w.append(tx.R)
w.append(tx.S)
proc append*(w: var RlpWriter, x: Authorization) =
w.startList(6)
w.append(x.chainId.uint64)
w.append(x.address)
w.append(x.nonce)
w.append(x.yParity)
w.append(x.R)
w.append(x.S)
proc appendTxEip7702(w: var RlpWriter, tx: Transaction) =
w.startList(13)
w.append(tx.chainId.uint64)
w.append(tx.nonce)
w.append(tx.maxPriorityFeePerGas)
w.append(tx.maxFeePerGas)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.accessList)
w.append(tx.authorizationList)
w.append(tx.V)
w.append(tx.R)
w.append(tx.S)
proc appendTxPayload(w: var RlpWriter, tx: Transaction) =
case tx.txType
of TxLegacy:
w.appendTxLegacy(tx)
of TxEip2930:
w.appendTxEip2930(tx)
of TxEip1559:
w.appendTxEip1559(tx)
of TxEip4844:
w.appendTxEip4844(tx)
of TxEip7702:
w.appendTxEip7702(tx)
proc append*(w: var RlpWriter, tx: Transaction) =
if tx.txType != TxLegacy:
w.append(tx.txType)
w.appendTxPayload(tx)
proc append(w: var RlpWriter, networkPayload: NetworkPayload) =
w.append(networkPayload.blobs)
w.append(networkPayload.commitments)
w.append(networkPayload.proofs)
proc append*(w: var RlpWriter, tx: PooledTransaction) =
if tx.tx.txType != TxLegacy:
w.append(tx.tx.txType)
if tx.networkPayload != nil:
w.startList(4) # spec: rlp([tx_payload, blobs, commitments, proofs])
w.appendTxPayload(tx.tx)
if tx.networkPayload != nil:
w.append(tx.networkPayload)
const EIP155_CHAIN_ID_OFFSET* = 35'u64
proc rlpEncodeLegacy(tx: Transaction): seq[byte] =
var w = initRlpWriter()
w.startList(6)
w.append(tx.nonce)
w.append(tx.gasPrice)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.finish()
proc rlpEncodeEip155(tx: Transaction): seq[byte] =
let chainId = (tx.V - EIP155_CHAIN_ID_OFFSET) div 2
var w = initRlpWriter()
w.startList(9)
w.append(tx.nonce)
w.append(tx.gasPrice)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(chainId)
w.append(0'u8)
w.append(0'u8)
w.finish()
proc rlpEncodeEip2930(tx: Transaction): seq[byte] =
var w = initRlpWriter()
w.append(TxEip2930)
w.startList(8)
w.append(tx.chainId.uint64)
w.append(tx.nonce)
w.append(tx.gasPrice)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.accessList)
w.finish()
proc rlpEncodeEip1559(tx: Transaction): seq[byte] =
var w = initRlpWriter()
w.append(TxEip1559)
w.startList(9)
w.append(tx.chainId.uint64)
w.append(tx.nonce)
w.append(tx.maxPriorityFeePerGas)
w.append(tx.maxFeePerGas)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.accessList)
w.finish()
proc rlpEncodeEip4844(tx: Transaction): seq[byte] =
var w = initRlpWriter()
w.append(TxEip4844)
w.startList(11)
w.append(tx.chainId.uint64)
w.append(tx.nonce)
w.append(tx.maxPriorityFeePerGas)
w.append(tx.maxFeePerGas)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.accessList)
w.append(tx.maxFeePerBlobGas)
w.append(tx.versionedHashes)
w.finish()
proc rlpEncodeEip7702(tx: Transaction): seq[byte] =
var w = initRlpWriter()
w.append(TxEip7702)
w.startList(10)
w.append(tx.chainId.uint64)
w.append(tx.nonce)
w.append(tx.maxPriorityFeePerGas)
w.append(tx.maxFeePerGas)
w.append(tx.gasLimit)
w.append(tx.to)
w.append(tx.value)
w.append(tx.payload)
w.append(tx.accessList)
w.append(tx.authorizationList)
w.finish()
proc rlpEncode*(tx: Transaction): seq[byte] =
case tx.txType
of TxLegacy:
if tx.V >= EIP155_CHAIN_ID_OFFSET: tx.rlpEncodeEip155 else: tx.rlpEncodeLegacy
of TxEip2930:
tx.rlpEncodeEip2930
of TxEip1559:
tx.rlpEncodeEip1559
of TxEip4844:
tx.rlpEncodeEip4844
of TxEip7702:
tx.rlpEncodeEip7702
func txHashNoSignature*(tx: Transaction): Hash32 =
# Hash transaction without signature
keccak256(rlpEncode(tx))
proc readTxLegacy*(rlp: var Rlp, tx: var Transaction) {.raises: [RlpError].} =
tx.txType = TxLegacy
rlp.tryEnterList()
rlp.read(tx.nonce)
rlp.read(tx.gasPrice)
rlp.read(tx.gasLimit)
rlp.read(tx.to)
rlp.read(tx.value)
rlp.read(tx.payload)
rlp.read(tx.V)
rlp.read(tx.R)
rlp.read(tx.S)
proc readTxEip2930(rlp: var Rlp, tx: var Transaction) {.raises: [RlpError].} =
tx.txType = TxEip2930
rlp.tryEnterList()
tx.chainId = rlp.read(uint64).ChainId
rlp.read(tx.nonce)
rlp.read(tx.gasPrice)
rlp.read(tx.gasLimit)
rlp.read(tx.to)
rlp.read(tx.value)
rlp.read(tx.payload)
rlp.read(tx.accessList)
rlp.read(tx.V)
rlp.read(tx.R)
rlp.read(tx.S)
proc readTxEip1559(rlp: var Rlp, tx: var Transaction) {.raises: [RlpError].} =
tx.txType = TxEip1559
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.V)
rlp.read(tx.R)
rlp.read(tx.S)
proc readTxEip4844(rlp: var Rlp, tx: var Transaction) {.raises: [RlpError].} =
tx.txType = TxEip4844
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.maxFeePerBlobGas)
rlp.read(tx.versionedHashes)
rlp.read(tx.V)
rlp.read(tx.R)
rlp.read(tx.S)
proc read*(rlp: var Rlp, T: type Authorization): T {.raises: [RlpError].} =
rlp.tryEnterList()
result.chainId = rlp.read(uint64).ChainId
rlp.read(result.address)
rlp.read(result.nonce)
rlp.read(result.yParity)
rlp.read(result.R)
rlp.read(result.S)
proc readTxEip7702(rlp: var Rlp, tx: var Transaction) {.raises: [RlpError].} =
tx.txType = TxEip7702
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)
rlp.read(tx.V)
rlp.read(tx.R)
rlp.read(tx.S)
proc readTxType(rlp: var Rlp): TxType {.raises: [RlpError].} =
if rlp.isList:
raise newException(
RlpTypeMismatch, "Transaction type expected, but source RLP is a list"
)
# EIP-2718: We MUST decode the first byte as a byte, not `rlp.read(int)`.
# If decoded with `rlp.read(int)`, bad transaction data (from the network)
# or even just incorrectly framed data for other reasons fails with
# any of these misleading error messages:
# - "Message too large to fit in memory"
# - "Number encoded with a leading zero"
# - "Read past the end of the RLP stream"
# - "Small number encoded in a non-canonical way"
# - "Attempt to read an Int value past the RLP end"
# - "The RLP contains a larger than expected Int value"
if not rlp.isSingleByte:
if not rlp.hasData:
raise
newException(MalformedRlpError, "Transaction expected but source RLP is empty")
raise newException(
MalformedRlpError,
"TypedTransaction type byte is out of range, must be 0x00 to 0x7f",
)
let txType = rlp.getByteValue
rlp.position += 1
var txVal: TxType
if checkedEnumAssign(txVal, txType):
return txVal
raise newException(
UnsupportedRlpError,
"TypedTransaction type must be 1, 2, or 3 in this version, got " & $txType,
)
proc readTxPayload(
rlp: var Rlp, tx: var Transaction, txType: TxType
) {.raises: [RlpError].} =
case txType
of TxLegacy:
raise
newException(RlpTypeMismatch, "LegacyTransaction should not be wrapped in a list")
of TxEip2930:
rlp.readTxEip2930(tx)
of TxEip1559:
rlp.readTxEip1559(tx)
of TxEip4844:
rlp.readTxEip4844(tx)
of TxEip7702:
rlp.readTxEip7702(tx)
proc readTxTyped*(rlp: var Rlp, tx: var Transaction) {.raises: [RlpError].} =
let txType = rlp.readTxType()
rlp.readTxPayload(tx, txType)
proc read*(rlp: var Rlp, T: type Transaction): T {.raises: [RlpError].} =
# Individual transactions are encoded and stored as either `RLP([fields..])`
# for legacy transactions, or `Type || RLP([fields..])`. Both of these
# encodings are byte sequences. The part after `Type` doesn't have to be
# RLP in theory, but all types so far use RLP. EIP-2718 covers this.
if rlp.isList:
rlp.readTxLegacy(result)
else:
rlp.readTxTyped(result)
proc read(rlp: var Rlp, T: type NetworkPayload): T {.raises: [RlpError].} =
result = NetworkPayload()
rlp.read(result.blobs)
rlp.read(result.commitments)
rlp.read(result.proofs)
proc readTxTyped(rlp: var Rlp, tx: var PooledTransaction) {.raises: [RlpError].} =
let
txType = rlp.readTxType()
hasNetworkPayload =
if txType == TxEip4844:
rlp.listLen == 4
else:
false
if hasNetworkPayload:
rlp.tryEnterList() # spec: rlp([tx_payload, blobs, commitments, proofs])
rlp.readTxPayload(tx.tx, txType)
if hasNetworkPayload:
rlp.read(tx.networkPayload)
proc read*(rlp: var Rlp, T: type PooledTransaction): T {.raises: [RlpError].} =
if rlp.isList:
rlp.readTxLegacy(result.tx)
else:
rlp.readTxTyped(result)
proc read*(
rlp: var Rlp, T: (type seq[Transaction]) | (type openArray[Transaction])
): seq[Transaction] {.raises: [RlpError].} =
# In arrays (sequences), transactions are encoded as either `RLP([fields..])`
# for legacy transactions, or `RLP(Type || RLP([fields..]))` for all typed
# transactions to date. Spot the extra `RLP(..)` blob encoding, to make it
# valid RLP inside a larger RLP. EIP-2976 covers this, "Typed Transactions
# over Gossip", although it's not very clear about the blob encoding.
#
# In practice the extra `RLP(..)` applies to all arrays/sequences of
# transactions. In principle, all aggregates (objects etc.), but
# arrays/sequences are enough. In `eth/65` protocol this is essential for
# the correct encoding/decoding of `Transactions`, `NewBlock`, and
# `PooledTransactions` network calls. We need a type match on both
# `openArray[Transaction]` and `seq[Transaction]` to catch all cases.
if not rlp.isList:
raise newException(
RlpTypeMismatch, "Transaction list expected, but source RLP is not a list"
)
for item in rlp:
var tx: Transaction
if item.isList:
item.readTxLegacy(tx)
else:
var rr = rlpFromBytes(rlp.read(seq[byte]))
rr.readTxTyped(tx)
result.add tx
proc read*(
rlp: var Rlp, T: (type seq[PooledTransaction]) | (type openArray[PooledTransaction])
): seq[PooledTransaction] {.raises: [RlpError].} =
if not rlp.isList:
raise newException(
RlpTypeMismatch, "PooledTransaction list expected, but source RLP is not a list"
)
for item in rlp:
var tx: PooledTransaction
if item.isList:
item.readTxLegacy(tx.tx)
else:
var rr = rlpFromBytes(rlp.read(seq[byte]))
rr.readTxTyped(tx)
result.add tx
proc append*(rlpWriter: var RlpWriter, txs: seq[Transaction] | openArray[Transaction]) =
# See above about encoding arrays/sequences of transactions.
rlpWriter.startList(txs.len)
for tx in txs:
if tx.txType == TxLegacy:
rlpWriter.append(tx)
else:
rlpWriter.append(rlp.encode(tx))
proc append*(
rlpWriter: var RlpWriter, txs: seq[PooledTransaction] | openArray[PooledTransaction]
) =
rlpWriter.startList(txs.len)
for tx in txs:
if tx.tx.txType == TxLegacy:
rlpWriter.append(tx)
else:
rlpWriter.append(rlp.encode(tx))

View File

@ -1,20 +0,0 @@
# Copyright (c) 2019-2020 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.
import
std/[hashes],
nimcrypto/hash, stew/byteutils,
./eth_types
proc hash*(d: MDigest): Hash {.inline.} = hash(d.data)
proc parseAddress*(hexString: string): EthAddress =
hexToPaddedByteArray[20](hexString)
proc `$`*(a: EthAddress): string =
a.toHex()

View File

@ -13,7 +13,7 @@ import
std/[strutils, json], std/[strutils, json],
nimcrypto/[bcmode, hmac, rijndael, pbkdf2, sha2, sysrand, utils, keccak, scrypt], nimcrypto/[bcmode, hmac, rijndael, pbkdf2, sha2, sysrand, utils, keccak, scrypt],
results, results,
../keys, ".."/common/[addresses, keys],
./uuid ./uuid
export results export results
@ -170,7 +170,7 @@ proc deriveKey(password: string,
discard ctx.pbkdf2(password, salt, c, output) discard ctx.pbkdf2(password, salt, c, output)
ok(output) ok(output)
of HashKECCAK256: of HashKECCAK256:
var ctx: HMAC[keccak256] var ctx: HMAC[keccak.keccak256]
discard ctx.pbkdf2(password, salt, c, output) discard ctx.pbkdf2(password, salt, c, output)
ok(output) ok(output)
of HashKECCAK384: of HashKECCAK384:
@ -345,7 +345,7 @@ proc createKeyFileJson*(seckey: PrivateKey,
ok(%* ok(%*
{ {
"address": seckey.toPublicKey().toAddress(false), "address": seckey.toPublicKey().to(Address).toHex(),
"crypto": { "crypto": {
"cipher": $cryptkind, "cipher": $cryptkind,
"cipherparams": { "cipherparams": {

View File

@ -1,285 +1,4 @@
# Nim Ethereum Keys {.deprecated.}
# Copyright (c) 2020-2024 Status Research & Development GmbH
# Licensed under either of
# - Apache License, version 2.0, (LICENSE-APACHEv2)
# - MIT license (LICENSE-MIT)
#
# This module contains adaptations of the general secp interface to help make import ./common/keys
# working with keys and signatures as they appear in Ethereum in particular: export keys
#
# * Public keys as serialized in uncompressed format without the initial byte
# * Shared secrets are serialized in raw format without the initial byte
# * distinct types are used to avoid confusion with the "standard" secp types
{.push raises: [].}
import
std/strformat,
secp256k1, bearssl/hash as bhash, bearssl/rand,
stew/[byteutils, objects, ptrops],
results,
./common/eth_hash
from nimcrypto/utils import burnMem
export secp256k1, results, rand
const
KeyLength* = SkEcdhSecretSize
## Ecdh shared secret key length without leading byte
## (publicKey * privateKey).x, where length of x is 32 bytes
FullKeyLength* = KeyLength + 1
## Ecdh shared secret with leading byte 0x02 or 0x03
RawPublicKeySize* = SkRawPublicKeySize - 1
## Size of uncompressed public key without format marker (0x04)
RawSignatureSize* = SkRawRecoverableSignatureSize
RawSignatureNRSize* = SkRawSignatureSize
type
PrivateKey* = distinct SkSecretKey
PublicKey* = distinct SkPublicKey
## Public key that's serialized to raw format without 0x04 marker
Signature* = distinct SkRecoverableSignature
## Ethereum uses recoverable signatures allowing some space savings
SignatureNR* = distinct SkSignature
## ...but ENR uses non-recoverable signatures!
SharedSecretFull* = object
## Representation of ECDH shared secret, with leading `y` byte
## (`y` is 0x02 when (publicKey * privateKey).y is even or 0x03 when odd)
data*: array[FullKeyLength, byte]
SharedSecret* = object
## Representation of ECDH shared secret, without leading `y` byte
data*: array[KeyLength, byte]
KeyPair* = distinct SkKeyPair
template pubkey*(v: KeyPair): PublicKey = PublicKey(SkKeyPair(v).pubkey)
template seckey*(v: KeyPair): PrivateKey = PrivateKey(SkKeyPair(v).seckey)
proc newRng*(): ref HmacDrbgContext =
# You should only create one instance of the RNG per application / library
# Ref is used so that it can be shared between components
HmacDrbgContext.new()
proc random*(T: type PrivateKey, rng: var HmacDrbgContext): T =
let rngPtr = unsafeAddr rng # doesn't escape
proc callRng(data: var openArray[byte]) =
generate(rngPtr[], data)
T(SkSecretKey.random(callRng))
func fromRaw*(T: type PrivateKey, data: openArray[byte]): SkResult[T] =
SkSecretKey.fromRaw(data).mapConvert(T)
func fromHex*(T: type PrivateKey, data: string): SkResult[T] =
SkSecretKey.fromHex(data).mapConvert(T)
func toRaw*(seckey: PrivateKey): array[SkRawSecretKeySize, byte] =
SkSecretKey(seckey).toRaw()
func toPublicKey*(seckey: PrivateKey): PublicKey {.borrow.}
func fromRaw*(T: type PublicKey, data: openArray[byte]): SkResult[T] =
if data.len() == SkRawCompressedPublicKeySize:
return SkPublicKey.fromRaw(data).mapConvert(T)
if len(data) < SkRawPublicKeySize - 1:
return err(static(
&"keys: raw eth public key should be {SkRawPublicKeySize - 1} bytes"))
var d: array[SkRawPublicKeySize, byte]
d[0] = 0x04'u8
copyMem(addr d[1], unsafeAddr data[0], 64)
SkPublicKey.fromRaw(d).mapConvert(T)
func fromHex*(T: type PublicKey, data: string): SkResult[T] =
T.fromRaw(? seq[byte].fromHex(data))
func toRaw*(pubkey: PublicKey): array[RawPublicKeySize, byte] =
let tmp = SkPublicKey(pubkey).toRaw()
copyMem(addr result[0], unsafeAddr tmp[1], 64)
func toRawCompressed*(pubkey: PublicKey): array[33, byte] {.borrow.}
proc random*(T: type KeyPair, rng: var HmacDrbgContext): T =
let seckey = SkSecretKey(PrivateKey.random(rng))
KeyPair(SkKeyPair(
seckey: seckey,
pubkey: seckey.toPublicKey()
))
func toKeyPair*(seckey: PrivateKey): KeyPair =
KeyPair(SkKeyPair(
seckey: SkSecretKey(seckey), pubkey: SkSecretKey(seckey).toPublicKey()))
func fromRaw*(T: type Signature, data: openArray[byte]): SkResult[T] =
SkRecoverableSignature.fromRaw(data).mapConvert(T)
func fromHex*(T: type Signature, data: string): SkResult[T] =
T.fromRaw(? seq[byte].fromHex(data))
func toRaw*(sig: Signature): array[RawSignatureSize, byte] {.borrow.}
func fromRaw*(T: type SignatureNR, data: openArray[byte]): SkResult[T] =
SkSignature.fromRaw(data).mapConvert(T)
func toRaw*(sig: SignatureNR): array[RawSignatureNRSize, byte] {.borrow.}
func toAddress*(pubkey: PublicKey, with0x = true): string =
## Convert public key to hexadecimal string address.
var hash = keccakHash(pubkey.toRaw())
result = if with0x: "0x" else: ""
result.add(toHex(toOpenArray(hash.data, 12, len(hash.data) - 1)))
func toChecksumAddress*(pubkey: PublicKey, with0x = true): string =
## Convert public key to checksumable mixed-case address (EIP-55).
result = if with0x: "0x" else: ""
var hash1 = keccakHash(pubkey.toRaw())
var hhash1 = toHex(toOpenArray(hash1.data, 12, len(hash1.data) - 1))
var hash2 = keccakHash(hhash1)
var hhash2 = toHex(hash2.data)
for i in 0..<len(hhash1):
if hhash2[i] >= '0' and hhash2[i] <= '7':
result.add(hhash1[i])
else:
if hhash1[i] >= '0' and hhash1[i] <= '9':
result.add(hhash1[i])
else:
let ch = chr(ord(hhash1[i]) - ord('a') + ord('A'))
result.add(ch)
func validateChecksumAddress*(a: string): bool =
## Validate checksumable mixed-case address (EIP-55).
var address = ""
var check = "0x"
if len(a) != 42:
return false
if a[0] != '0' and a[1] != 'x':
return false
for i in 2..41:
let ch = a[i]
if ch in {'0'..'9'} or ch in {'a'..'f'}:
address &= ch
elif ch in {'A'..'F'}:
address &= chr(ord(ch) - ord('A') + ord('a'))
else:
return false
var hash = keccakHash(address)
var hexhash = toHex(hash.data)
for i in 0..<len(address):
if hexhash[i] >= '0' and hexhash[i] <= '7':
check.add(address[i])
else:
if address[i] >= '0' and address[i] <= '9':
check.add(address[i])
else:
let ch = chr(ord(address[i]) - ord('a') + ord('A'))
check.add(ch)
result = (check == a)
func toCanonicalAddress*(pubkey: PublicKey): array[20, byte] =
## Convert public key to canonical address.
var hash = keccakHash(pubkey.toRaw())
copyMem(addr result[0], addr hash.data[12], 20)
func `$`*(pubkey: PublicKey): string =
## Convert public key to hexadecimal string representation.
toHex(pubkey.toRaw())
func `$`*(sig: Signature): string =
## Convert signature to hexadecimal string representation.
toHex(sig.toRaw())
func `$`*(seckey: PrivateKey): string =
## Convert private key to hexadecimal string representation
toHex(seckey.toRaw())
func `==`*(lhs, rhs: PublicKey): bool {.borrow.}
func `==`*(lhs, rhs: Signature): bool {.borrow.}
func `==`*(lhs, rhs: SignatureNR): bool {.borrow.}
func clear*(v: var PrivateKey) {.borrow.}
func clear*(v: var KeyPair) =
v.seckey.clear()
func clear*(v: var SharedSecret) = burnMem(v.data)
func clear*(v: var SharedSecretFull) = burnMem(v.data)
func sign*(seckey: PrivateKey, msg: SkMessage): Signature =
Signature(signRecoverable(SkSecretKey(seckey), msg))
func sign*(seckey: PrivateKey, msg: openArray[byte]): Signature =
let hash = keccakHash(msg)
sign(seckey, SkMessage(hash.data))
func signNR*(seckey: PrivateKey, msg: SkMessage): SignatureNR =
SignatureNR(sign(SkSecretKey(seckey), msg))
func signNR*(seckey: PrivateKey, msg: openArray[byte]): SignatureNR =
let hash = keccakHash(msg)
signNR(seckey, SkMessage(hash.data))
func recover*(sig: Signature, msg: SkMessage): SkResult[PublicKey] =
recover(SkRecoverableSignature(sig), msg).mapConvert(PublicKey)
func recover*(sig: Signature, msg: openArray[byte]): SkResult[PublicKey] =
let hash = keccakHash(msg)
recover(sig, SkMessage(hash.data))
func verify*(sig: SignatureNR, msg: SkMessage, key: PublicKey): bool =
verify(SkSignature(sig), msg, SkPublicKey(key))
func verify*(sig: SignatureNR, msg: openArray[byte], key: PublicKey): bool =
let hash = keccakHash(msg)
verify(sig, SkMessage(hash.data), key)
proc ecdhSharedSecretHash(output: ptr byte, x32, y32: ptr byte, data: pointer): cint
{.cdecl, raises: [].} =
## Hash function used by `ecdhSharedSecret` below
# `x32` and `y32` are result of scalar multiplication of publicKey * privateKey.
# Both `x32` and `y32` are 32 bytes length.
# Take the `x32` part as ecdh shared secret.
# output length is derived from x32 length and taken from ecdh
# generic parameter `KeyLength`
copyMem(output, x32, KeyLength)
return 1
func ecdhSharedSecret*(seckey: PrivateKey, pubkey: PublicKey): SharedSecret =
## Compute ecdh agreed shared secret.
let res = ecdh[KeyLength](SkSecretKey(seckey), SkPublicKey(pubkey), ecdhSharedSecretHash, nil)
# This function only fail if the hash function return zero.
# Because our hash function always success, we can turn the error into defect
doAssert res.isOk, $res.error
SharedSecret(data: res.get)
proc ecdhSharedSecretFullHash(output: ptr byte, x32, y32: ptr byte, data: pointer): cint
{.cdecl, raises: [].} =
## Hash function used by `ecdhSharedSecretFull` below
# `x32` and `y32` are result of scalar multiplication of publicKey * privateKey.
# Leading byte is 0x02 if `y32` is even and 0x03 if odd. Then concat with `x32`.
# output length is derived from `x32` length + 1 and taken from ecdh
# generic parameter `FullKeyLength`
# output[0] = 0x02 | (y32[31] & 1)
output[] = 0x02 or (y32.offset(31)[] and 0x01)
copyMem(output.offset(1), x32, KeyLength)
return 1
func ecdhSharedSecretFull*(seckey: PrivateKey, pubkey: PublicKey): SharedSecretFull =
## Compute ecdh agreed shared secret with leading byte.
let res = ecdh[FullKeyLength](SkSecretKey(seckey), SkPublicKey(pubkey), ecdhSharedSecretFullHash, nil)
# This function only fail if the hash function return zero.
# Because our hash function always success, we can turn the error into defect
doAssert res.isOk, $res.error
SharedSecretFull(data: res.get)

View File

@ -12,7 +12,7 @@ import
std/[os, strutils, times], std/[os, strutils, times],
results, nat_traversal/[miniupnpc, natpmp], results, nat_traversal/[miniupnpc, natpmp],
chronicles, json_serialization/std/net, chronos, chronicles, json_serialization/std/net, chronos,
../common/utils, ./utils as netutils ./utils as netutils
export results export results

View File

@ -10,7 +10,7 @@
import import
std/[tables, algorithm, random, typetraits, strutils, net], std/[tables, algorithm, random, typetraits, strutils, net],
chronos, chronos/timer, chronicles, chronos, chronos/timer, chronicles,
./keys, ./p2p/private/p2p_types, ./common/keys, ./p2p/private/p2p_types,
./p2p/[kademlia, discovery, enode, peer_pool, rlpx] ./p2p/[kademlia, discovery, enode, peer_pool, rlpx]
export export

View File

@ -16,11 +16,14 @@ import
nimcrypto/[rijndael, keccak, utils], nimcrypto/[rijndael, keccak, utils],
stew/[arrayops, byteutils, endians2, objects], stew/[arrayops, byteutils, endians2, objects],
results, results,
".."/[keys, rlp], ../rlp,
../common/keys,
./ecies ./ecies
export results export results
type keccak256 = keccak.keccak256
const const
SupportedRlpxVersion* = 4'u8 SupportedRlpxVersion* = 4'u8

View File

@ -11,7 +11,8 @@ import
std/[times, net], std/[times, net],
chronos, stint, nimcrypto/keccak, chronicles, chronos, stint, nimcrypto/keccak, chronicles,
stew/objects, results, stew/objects, results,
".."/[keys, rlp], ../rlp,
../common/keys,
"."/[kademlia, enode] "."/[kademlia, enode]
export export
@ -43,6 +44,8 @@ type
DiscResult*[T] = Result[T, cstring] DiscResult*[T] = Result[T, cstring]
keccak256 = keccak.keccak256
const MinListLen: array[CommandId, int] = [4, 3, 2, 2, 1, 2] const MinListLen: array[CommandId, int] = [4, 3, 2, 2, 1, 2]
proc append*(w: var RlpWriter, a: IpAddress) = proc append*(w: var RlpWriter, a: IpAddress) =

View File

@ -18,7 +18,8 @@ import
nimcrypto/[bcmode, rijndael, sha2], stint, chronicles, nimcrypto/[bcmode, rijndael, sha2], stint, chronicles,
stew/[byteutils, endians2], metrics, stew/[byteutils, endians2], metrics,
results, results,
".."/../[rlp, keys], ../../rlp,
../../common/keys,
"."/[messages_encoding, node, enr, hkdf, sessions] "."/[messages_encoding, node, enr, hkdf, sessions]
from stew/objects import checkedEnumAssign from stew/objects import checkedEnumAssign

View File

@ -16,7 +16,8 @@ import
stew/base64, stew/base64,
results, results,
chronicles, chronicles,
".."/../[rlp, keys], ../../rlp,
../../common/keys,
../../net/utils ../../net/utils
export results, rlp, keys export results, rlp, keys

View File

@ -10,7 +10,7 @@
import import
std/[hashes, net], std/[hashes, net],
nimcrypto/keccak, stint, chronos, chronicles, results, nimcrypto/keccak, stint, chronos, chronicles, results,
../../keys, ../../net/utils, ../../common/keys, ../../net/utils,
./enr ./enr
export stint, results export stint, results

View File

@ -84,7 +84,8 @@ import
std/[tables, sets, math, sequtils, algorithm], std/[tables, sets, math, sequtils, algorithm],
json_serialization/std/net, json_serialization/std/net,
results, chronicles, chronos, stint, metrics, results, chronicles, chronos, stint, metrics,
".."/../[rlp, keys], ../../rlp,
../../common/keys,
"."/[messages_encoding, encoding, node, routing_table, enr, random2, sessions, "."/[messages_encoding, encoding, node, routing_table, enr, random2, sessions,
ip_vote, nodes_verification] ip_vote, nodes_verification]

View File

@ -15,7 +15,7 @@
import import
stew/endians2, results, stew/endians2, results,
nimcrypto/[rijndael, bcmode, hash, hmac, sha2, utils], nimcrypto/[rijndael, bcmode, hash, hmac, sha2, utils],
../keys ../common/keys
export results export results

View File

@ -13,7 +13,7 @@
import import
std/[uri, strutils, net], std/[uri, strutils, net],
pkg/chronicles, pkg/chronicles,
../keys ../common/keys
export keys export keys

View File

@ -10,7 +10,7 @@
import import
std/[tables, hashes, times, algorithm, sets, sequtils], std/[tables, hashes, times, algorithm, sets, sequtils],
chronos, chronicles, stint, nimcrypto/keccak, metrics, chronos, chronicles, stint, nimcrypto/keccak, metrics,
../keys, ./discoveryv5/random2, ../common/keys, ./discoveryv5/random2,
./enode ./enode
export sets # TODO: This should not be needed, but compilation fails otherwise export sets # TODO: This should not be needed, but compilation fails otherwise

View File

@ -13,7 +13,6 @@
import import
std/[os, tables, times, random, sequtils, options], std/[os, tables, times, random, sequtils, options],
chronos, chronicles, chronos, chronicles,
".."/[keys, common],
./private/p2p_types, "."/[discovery, kademlia, rlpx, enode] ./private/p2p_types, "."/[discovery, kademlia, rlpx, enode]
logScope: logScope:
@ -220,7 +219,7 @@ proc maybeConnectToMorePeers(p: PeerPool) {.async.} =
else: else:
for n in p.nodesToConnect(): for n in p.nodesToConnect():
await p.connQueue.addLast(n) await p.connQueue.addLast(n)
# The old version of the code (which did all the connection # The old version of the code (which did all the connection
# attempts in serial, not parallel) actually *awaited* all # attempts in serial, not parallel) actually *awaited* all
# the connection attempts before reaching the code at the # the connection attempts before reaching the code at the
@ -235,7 +234,7 @@ proc maybeConnectToMorePeers(p: PeerPool) {.async.} =
# #
# --Adam, Dec. 2022 # --Adam, Dec. 2022
await sleepAsync(sleepBeforeTryingARandomBootnode) await sleepAsync(sleepBeforeTryingARandomBootnode)
# In some cases (e.g ROPSTEN or private testnets), the discovery table might # In some cases (e.g ROPSTEN or private testnets), the discovery table might
# be full of bad peers, so if we can't connect to any peers we try a random # be full of bad peers, so if we can't connect to any peers we try a random
# bootstrap node as well. # bootstrap node as well.

View File

@ -14,10 +14,10 @@ import
std/[deques, tables], std/[deques, tables],
chronos, chronos,
results, results,
".."/../[rlp, keys], ../../common/eth_types, ".."/../[rlp], ../../common/[base, keys],
".."/[enode, kademlia, discovery, rlpxcrypt] ".."/[enode, kademlia, discovery, rlpxcrypt]
export eth_types.NetworkId export base.NetworkId
const const
useSnappy* = defined(useSnappy) useSnappy* = defined(useSnappy)
@ -212,6 +212,8 @@ type
MessageTimeout = 0x0B, MessageTimeout = 0x0B,
SubprotocolReason = 0x10 SubprotocolReason = 0x10
Address = enode.Address
proc `$`*(peer: Peer): string = $peer.remote proc `$`*(peer: Peer): string = $peer.remote
proc toENode*(v: EthereumNode): ENode = proc toENode*(v: EthereumNode): ENode =

View File

@ -25,9 +25,9 @@
{.push raises: [].} {.push raises: [].}
import import
std/[tables, algorithm, deques, hashes, options, typetraits, os], std/[algorithm, deques, options, typetraits, os],
stew/shims/macros, chronicles, nimcrypto/utils, chronos, metrics, stew/shims/macros, chronicles, nimcrypto/utils, chronos, metrics,
".."/[rlp, common, keys, async_utils], ".."/[rlp, common, async_utils],
./private/p2p_types, "."/[kademlia, auth, rlpxcrypt, enode, p2p_protocol_dsl] ./private/p2p_types, "."/[kademlia, auth, rlpxcrypt, enode, p2p_protocol_dsl]
# TODO: This doesn't get enabled currently in any of the builds, so we send a # TODO: This doesn't get enabled currently in any of the builds, so we send a
@ -76,6 +76,8 @@ type
DisconnectionReasonList = object DisconnectionReasonList = object
value: DisconnectionReason value: DisconnectionReason
Address = enode.Address
proc read(rlp: var Rlp; T: type DisconnectionReasonList): T proc read(rlp: var Rlp; T: type DisconnectionReasonList): T
{.gcsafe, raises: [RlpError].} = {.gcsafe, raises: [RlpError].} =
## Rlp mixin: `DisconnectionReasonList` parser ## Rlp mixin: `DisconnectionReasonList` parser

View File

@ -70,7 +70,7 @@ proc get*(self: BinaryTrie, key: openArray[byte]): seq[byte] {.inline.} =
return self.getAux(self.rootHash, keyBits) return self.getAux(self.rootHash, keyBits)
proc hashAndSave*(self: BinaryTrie, node: openArray[byte]): TrieNodeKey = proc hashAndSave*(self: BinaryTrie, node: openArray[byte]): TrieNodeKey =
result = @(keccakHash(node).data) result = @(keccak256(node).data)
self.db.put(result, node) self.db.put(result, node)
template saveKV(self: BinaryTrie, keyPath: TrieBitSeq | bool, child: openArray[byte]): untyped = template saveKV(self: BinaryTrie, keyPath: TrieBitSeq | bool, child: openArray[byte]): untyped =

View File

@ -90,7 +90,7 @@ proc isValidBranch*(branch: seq[seq[byte]], rootHash: seq[byte], key, value: ope
var db = newMemoryDB() var db = newMemoryDB()
for node in branch: for node in branch:
doAssert(node.len != 0) doAssert(node.len != 0)
let nodeHash = keccakHash(node) let nodeHash = keccak256(node)
db.put(nodeHash.data, node) db.put(nodeHash.data, node)
var trie = initBinaryTrie(db, rootHash) var trie = initBinaryTrie(db, rootHash)

View File

@ -13,7 +13,7 @@ import
type type
TrieNodeKey = object TrieNodeKey = object
hash: KeccakHash hash: Hash32
usedBytes: uint8 usedBytes: uint8
DB = TrieDatabaseRef DB = TrieDatabaseRef
@ -44,16 +44,16 @@ proc dbPut(db: DB, data: openArray[byte]): TrieNodeKey
template get(db: DB, key: Rlp): seq[byte] = template get(db: DB, key: Rlp): seq[byte] =
db.get(key.expectHash) db.get(key.expectHash)
converter toTrieNodeKey(hash: KeccakHash): TrieNodeKey = converter toTrieNodeKey(hash: Hash32): TrieNodeKey =
result.hash = hash result.hash = hash
result.usedBytes = 32 result.usedBytes = 32
proc initHexaryTrie*(db: DB, rootHash: KeccakHash, isPruning = true): HexaryTrie = proc initHexaryTrie*(db: DB, rootHash: Hash32, isPruning = true): HexaryTrie =
result.db = db result.db = db
result.root = rootHash result.root = rootHash
result.isPruning = isPruning result.isPruning = isPruning
template initSecureHexaryTrie*(db: DB, rootHash: KeccakHash, isPruning = true): SecureHexaryTrie = template initSecureHexaryTrie*(db: DB, rootHash: Hash32, isPruning = true): SecureHexaryTrie =
SecureHexaryTrie initHexaryTrie(db, rootHash, isPruning) SecureHexaryTrie initHexaryTrie(db, rootHash, isPruning)
proc initHexaryTrie*(db: DB, isPruning = true): HexaryTrie proc initHexaryTrie*(db: DB, isPruning = true): HexaryTrie
@ -65,7 +65,7 @@ proc initHexaryTrie*(db: DB, isPruning = true): HexaryTrie
template initSecureHexaryTrie*(db: DB, isPruning = true): SecureHexaryTrie = template initSecureHexaryTrie*(db: DB, isPruning = true): SecureHexaryTrie =
SecureHexaryTrie initHexaryTrie(db, isPruning) SecureHexaryTrie initHexaryTrie(db, isPruning)
proc rootHash*(t: HexaryTrie): KeccakHash = proc rootHash*(t: HexaryTrie): Hash32 =
t.root.hash t.root.hash
proc rootHashHex*(t: HexaryTrie): string = proc rootHashHex*(t: HexaryTrie): string =
@ -354,11 +354,11 @@ proc getBranch*(self: HexaryTrie; key: openArray[byte]): seq[seq[byte]] =
getBranchAux(self.db, node, initNibbleRange(key), result) getBranchAux(self.db, node, initNibbleRange(key), result)
proc dbDel(t: var HexaryTrie, data: openArray[byte]) = proc dbDel(t: var HexaryTrie, data: openArray[byte]) =
if data.len >= 32: t.prune(data.keccakHash.data) if data.len >= 32: t.prune(data.keccak256.data)
proc dbPut(db: DB, data: openArray[byte]): TrieNodeKey proc dbPut(db: DB, data: openArray[byte]): TrieNodeKey
{.raises: [].} = {.raises: [].} =
result.hash = data.keccakHash result.hash = data.keccak256
result.usedBytes = 32 result.usedBytes = 32
put(db, result.asDbKey, data) put(db, result.asDbKey, data)
@ -545,7 +545,7 @@ proc del*(self: var HexaryTrie; key: openArray[byte]) =
self.prune(self.root.asDbKey) self.prune(self.root.asDbKey)
self.root = self.db.dbPut(newRootBytes) self.root = self.db.dbPut(newRootBytes)
proc mergeAt(self: var HexaryTrie, orig: Rlp, origHash: KeccakHash, proc mergeAt(self: var HexaryTrie, orig: Rlp, origHash: Hash32,
key: NibblesSeq, value: openArray[byte], key: NibblesSeq, value: openArray[byte],
isInline = false): seq[byte] isInline = false): seq[byte]
{.gcsafe, raises: [RlpError].} {.gcsafe, raises: [RlpError].}
@ -553,7 +553,7 @@ proc mergeAt(self: var HexaryTrie, orig: Rlp, origHash: KeccakHash,
proc mergeAt(self: var HexaryTrie, rlp: Rlp, proc mergeAt(self: var HexaryTrie, rlp: Rlp,
key: NibblesSeq, value: openArray[byte], key: NibblesSeq, value: openArray[byte],
isInline = false): seq[byte] = isInline = false): seq[byte] =
self.mergeAt(rlp, rlp.rawData.keccakHash, key, value, isInline) self.mergeAt(rlp, rlp.rawData.keccak256, key, value, isInline)
proc mergeAtAux(self: var HexaryTrie, output: var RlpWriter, orig: Rlp, proc mergeAtAux(self: var HexaryTrie, output: var RlpWriter, orig: Rlp,
key: NibblesSeq, value: openArray[byte]) = key: NibblesSeq, value: openArray[byte]) =
@ -566,7 +566,7 @@ proc mergeAtAux(self: var HexaryTrie, output: var RlpWriter, orig: Rlp,
let b = self.mergeAt(resolved, key, value, not isRemovable) let b = self.mergeAt(resolved, key, value, not isRemovable)
output.appendAndSave(b, self.db) output.appendAndSave(b, self.db)
proc mergeAt(self: var HexaryTrie, orig: Rlp, origHash: KeccakHash, proc mergeAt(self: var HexaryTrie, orig: Rlp, origHash: Hash32,
key: NibblesSeq, value: openArray[byte], key: NibblesSeq, value: openArray[byte],
isInline = false): seq[byte] isInline = false): seq[byte]
{.gcsafe, raises: [RlpError].} = {.gcsafe, raises: [RlpError].} =
@ -672,15 +672,15 @@ proc put*(self: var HexaryTrie; key, value: openArray[byte]) =
self.root = self.db.dbPut(newRootBytes) self.root = self.db.dbPut(newRootBytes)
proc put*(self: var SecureHexaryTrie; key, value: openArray[byte]) = proc put*(self: var SecureHexaryTrie; key, value: openArray[byte]) =
put(HexaryTrie(self), key.keccakHash.data, value) put(HexaryTrie(self), key.keccak256.data, value)
proc get*(self: SecureHexaryTrie; key: openArray[byte]): seq[byte] = proc get*(self: SecureHexaryTrie; key: openArray[byte]): seq[byte] =
return get(HexaryTrie(self), key.keccakHash.data) return get(HexaryTrie(self), key.keccak256.data)
proc del*(self: var SecureHexaryTrie; key: openArray[byte]) = proc del*(self: var SecureHexaryTrie; key: openArray[byte]) =
del(HexaryTrie(self), key.keccakHash.data) del(HexaryTrie(self), key.keccak256.data)
proc rootHash*(self: SecureHexaryTrie): KeccakHash {.borrow.} proc rootHash*(self: SecureHexaryTrie): Hash32 {.borrow.}
proc rootHashHex*(self: SecureHexaryTrie): string {.borrow.} proc rootHashHex*(self: SecureHexaryTrie): string {.borrow.}
proc isPruning*(self: SecureHexaryTrie): bool {.borrow.} proc isPruning*(self: SecureHexaryTrie): bool {.borrow.}
@ -689,14 +689,14 @@ template contains*(self: HexaryTrie | SecureHexaryTrie;
self.get(key).len > 0 self.get(key).len > 0
# Validates merkle proof against provided root hash # Validates merkle proof against provided root hash
proc isValidBranch*(branch: seq[seq[byte]], rootHash: KeccakHash, key, value: seq[byte]): bool = proc isValidBranch*(branch: seq[seq[byte]], rootHash: Hash32, key, value: seq[byte]): bool =
# branch must not be empty # branch must not be empty
doAssert(branch.len != 0) doAssert(branch.len != 0)
var db = newMemoryDB() var db = newMemoryDB()
for node in branch: for node in branch:
doAssert(node.len != 0) doAssert(node.len != 0)
let nodeHash = keccakHash(node) let nodeHash = keccak256(node)
db.put(nodeHash.data, node) db.put(nodeHash.data, node)
var trie = initHexaryTrie(db, rootHash) var trie = initHexaryTrie(db, rootHash)

View File

@ -186,7 +186,7 @@ proc verifyProof(
proc verifyMptProof*( proc verifyMptProof*(
branch: seq[seq[byte]], branch: seq[seq[byte]],
rootHash: KeccakHash, rootHash: Hash32,
key: seq[byte], key: seq[byte],
value: seq[byte]): MptProofVerificationResult = value: seq[byte]): MptProofVerificationResult =
## Verifies provided proof of inclusion (trie branch) against provided trie ## Verifies provided proof of inclusion (trie branch) against provided trie
@ -215,7 +215,7 @@ proc verifyMptProof*(
for node in branch: for node in branch:
if len(node) == 0: if len(node) == 0:
return invalidProof("empty mpt node in proof") return invalidProof("empty mpt node in proof")
let nodeHash = keccakHash(node) let nodeHash = keccak256(node)
db.put(nodeHash.data, node) db.put(nodeHash.data, node)
let let

View File

@ -1,7 +1,7 @@
import import
../common/eth_hash_rlp ../common/hashes_rlp
export eth_hash_rlp export hashes_rlp
type type
TrieError* = object of CatchableError TrieError* = object of CatchableError
@ -17,6 +17,5 @@ type
# will be a more appropriate response. # will be a more appropriate response.
const const
blankStringHash* = "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470".toDigest
emptyRlp* = @[128.byte] emptyRlp* = @[128.byte]
emptyRlpHash* = "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".toDigest emptyRlpHash* = hash32"56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"

View File

@ -1,17 +1,10 @@
import import
stew/byteutils,
./trie_defs ./trie_defs
export trie_defs export trie_defs
template checkValidHashZ*(x: untyped) = template checkValidHashZ*(x: untyped) =
when x.type isnot KeccakHash: doAssert(x.len == 32 or x.len == 0)
doAssert(x.len == 32 or x.len == 0)
template isZeroHash*(x: openArray[byte]): bool = template isZeroHash*(x: openArray[byte]): bool =
x.len == 0 x.len == 0
proc hashFromHex*(bits: static[int], input: string): MDigest[bits] =
MDigest(data: hexToByteArray[bits div 8](input))
template hashFromHex*(s: static[string]): untyped = hashFromHex(s.len * 4, s)

View File

@ -12,7 +12,7 @@ import
results, results,
../p2p/discoveryv5/[protocol, messages_encoding, encoding], ../p2p/discoveryv5/[protocol, messages_encoding, encoding],
./utp_router, ./utp_router,
../keys ../common/keys
export utp_router, protocol, results export utp_router, protocol, results

View File

@ -10,7 +10,7 @@ import
std/[tables, options, hashes, math], std/[tables, options, hashes, math],
chronos, chronicles, chronos, chronicles,
./utp_router, ./utp_router,
../keys ../common/keys
logScope: logScope:
topics = "eth utp" topics = "eth utp"

View File

@ -10,7 +10,7 @@ import
std/tables, std/tables,
chronos, chronicles, metrics, chronos, chronicles, metrics,
results, results,
../keys, ../common/keys,
./utp_socket, ./utp_socket,
./packets ./packets

View File

@ -13,10 +13,9 @@
import import
./utp/all_utp_tests, ./utp/all_utp_tests,
./keyfile/all_tests, ./keyfile/all_tests,
./keys/all_tests,
./p2p/all_tests, ./p2p/all_tests,
./rlp/all_tests, ./rlp/all_tests,
./trie/all_tests, ./trie/all_tests,
./db/all_tests, ./db/all_tests,
./common/all_tests, ./common/all_tests,
./test_bloom ./test_bloom

View File

@ -9,8 +9,9 @@
# according to those terms. # according to those terms.
import import
test_eth_types,
test_eth_types_rlp,
test_common, test_common,
test_eip4844, test_eip4844,
test_eip7702 test_eip7702,
test_eth_types,
test_eth_types_rlp,
test_keys

View File

@ -14,7 +14,7 @@ import
type type
EthHeader = object EthHeader = object
header: BlockHeader header: Header
proc loadFile(x: int) = proc loadFile(x: int) =
let fileName = "tests" / "common" / "eip2718" / "acl_block_" & $x & ".json" let fileName = "tests" / "common" / "eip2718" / "acl_block_" & $x & ".json"
@ -38,167 +38,161 @@ proc loadFile(x: int) =
check blk1 == blk3 check blk1 == blk3
check bytes1 == bytes3 check bytes1 == bytes3
proc suite1() = suite "RLP encoding":
suite "RLP encoding": test "Receipt roundtrip":
test "Receipt roundtrip": let a = Receipt(
let a = Receipt( receiptType: LegacyReceipt,
receiptType: LegacyReceipt, isHash: false,
isHash: false, status: false,
status: false, cumulativeGasUsed: 51000
cumulativeGasUsed: 51000 )
)
let hash = rlpHash(a) let hash = rlpHash(a)
let b = Receipt( let b = Receipt(
receiptType: LegacyReceipt, receiptType: LegacyReceipt,
isHash: true, isHash: true,
hash: hash, hash: hash,
cumulativeGasUsed: 21000 cumulativeGasUsed: 21000
) )
let abytes = rlp.encode(a) let abytes = rlp.encode(a)
let bbytes = rlp.encode(b) let bbytes = rlp.encode(b)
let aa = rlp.decode(abytes, Receipt) let aa = rlp.decode(abytes, Receipt)
let bb = rlp.decode(bbytes, Receipt) let bb = rlp.decode(bbytes, Receipt)
check aa == a check aa == a
check bb == b check bb == b
test "EIP-2930 receipt": test "EIP-2930 receipt":
let a = Receipt( let a = Receipt(
receiptType: Eip2930Receipt, receiptType: Eip2930Receipt,
status: true status: true
) )
let b = Receipt( let b = Receipt(
receiptType: Eip2930Receipt, receiptType: Eip2930Receipt,
status: false, status: false,
cumulativeGasUsed: 21000 cumulativeGasUsed: 21000
) )
let abytes = rlp.encode(a) let abytes = rlp.encode(a)
let bbytes = rlp.encode(b) let bbytes = rlp.encode(b)
let aa = rlp.decode(abytes, Receipt) let aa = rlp.decode(abytes, Receipt)
let bb = rlp.decode(bbytes, Receipt) let bb = rlp.decode(bbytes, Receipt)
check aa == a check aa == a
check bb == b check bb == b
test "EIP-4895 roundtrip": test "EIP-4895 roundtrip":
let a = Withdrawal( let a = Withdrawal(
index: 1, index: 1,
validatorIndex: 2, validatorIndex: 2,
address: EthAddress [ address: Address [
0.byte, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0.byte, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19], 11, 12, 13, 14, 15, 16, 17, 18, 19],
amount: 4) amount: 4)
let abytes = rlp.encode(a) let abytes = rlp.encode(a)
let aa = rlp.decode(abytes, Withdrawal) let aa = rlp.decode(abytes, Withdrawal)
check aa == a check aa == a
proc suite2() = suite "EIP-2718 transaction / receipt":
suite "EIP-2718 transaction / receipt": for i in 0..<10:
for i in 0..<10: loadFile(i)
loadFile(i)
test "BlockHeader: rlp roundtrip EIP-1559 / EIP-4895 / EIP-4844": test "Header: rlp roundtrip EIP-1559 / EIP-4895 / EIP-4844":
proc doTest(h: BlockHeader) = proc doTest(h: Header) =
let xy = rlp.encode(h) let xy = rlp.encode(h)
let hh = rlp.decode(xy, BlockHeader) let hh = rlp.decode(xy, Header)
check h == hh check h == hh
var h: BlockHeader var h: Header
doTest h doTest h
# EIP-1559 # EIP-1559
h.baseFeePerGas = Opt.some 1234.u256 h.baseFeePerGas = Opt.some 1234.u256
doTest h doTest h
# EIP-4895 # EIP-4895
h.withdrawalsRoot = Opt.some Hash256.fromHex( h.withdrawalsRoot = Opt.some hash32"0x7a64245f7f95164f6176d90bd4903dbdd3e5433d555dd1385e81787f9672c588"
"0x7a64245f7f95164f6176d90bd4903dbdd3e5433d555dd1385e81787f9672c588") doTest h
doTest h
# EIP-4844 # EIP-4844
h.blobGasUsed = Opt.some 1234'u64 h.blobGasUsed = Opt.some 1234'u64
h.excessBlobGas = Opt.some 1234'u64 h.excessBlobGas = Opt.some 1234'u64
doTest h doTest h
test "Receipts EIP-2718 + EIP-2976 encoding": test "Receipts EIP-2718 + EIP-2976 encoding":
const const
# Test payload from # Test payload from
# https://github.com/ethereum/go-ethereum/blob/253447a4f5e5f7f65c0605d490360bb58fb5f8e0/core/types/receipt_test.go#L370 # https://github.com/ethereum/go-ethereum/blob/253447a4f5e5f7f65c0605d490360bb58fb5f8e0/core/types/receipt_test.go#L370
payload = "f9043eb9010c01f90108018262d4b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010c01f901080182cd14b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010d01f901090183013754b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010d01f90109018301a194b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0" payload = "f9043eb9010c01f90108018262d4b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010c01f901080182cd14b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010d01f901090183013754b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010d01f90109018301a194b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0"
receiptsBytes = hexToSeqByte(payload) receiptsBytes = hexToSeqByte(payload)
let receipts = rlp.decode(receiptsBytes, seq[Receipt]) let receipts = rlp.decode(receiptsBytes, seq[Receipt])
check receipts.len() == 4 check receipts.len() == 4
for receipt in receipts: for receipt in receipts:
check receipt.receiptType == TxEip2930 check receipt.receiptType == TxEip2930
let encoded = rlp.encode(receipts) let encoded = rlp.encode(receipts)
check receiptsBytes == encoded check receiptsBytes == encoded
test "Receipts EIP-2718 encoding - invalid - empty": test "Receipts EIP-2718 encoding - invalid - empty":
let receiptBytes: seq[byte] = @[] let receiptBytes: seq[byte] = @[]
expect MalformedRlpError: expect MalformedRlpError:
let _ = rlp.decode(receiptBytes, Receipt) let _ = rlp.decode(receiptBytes, Receipt)
test "Receipts EIP-2718 encoding - invalid - unsupported tx type": test "Receipts EIP-2718 encoding - invalid - unsupported tx type":
let receiptBytes: seq[byte] = @[0x05] let receiptBytes: seq[byte] = @[0x05]
expect UnsupportedRlpError: expect UnsupportedRlpError:
let _ = rlp.decode(receiptBytes, Receipt) let _ = rlp.decode(receiptBytes, Receipt)
test "Receipts EIP-2718 encoding - invalid - legacy tx type": test "Receipts EIP-2718 encoding - invalid - legacy tx type":
let receiptBytes: seq[byte] = @[0x00] let receiptBytes: seq[byte] = @[0x00]
expect MalformedRlpError: expect MalformedRlpError:
let _ = rlp.decode(receiptBytes, Receipt) let _ = rlp.decode(receiptBytes, Receipt)
test "Receipts EIP-2718 encoding - invalid - out of bounds tx type": test "Receipts EIP-2718 encoding - invalid - out of bounds tx type":
let receiptBytes: seq[byte] = @[0x81, 0x80] let receiptBytes: seq[byte] = @[0x81, 0x80]
expect MalformedRlpError: expect MalformedRlpError:
let _ = rlp.decode(receiptBytes, Receipt) let _ = rlp.decode(receiptBytes, Receipt)
test "Receipts EIP-2718 encoding - invalid - empty receipt payload": test "Receipts EIP-2718 encoding - invalid - empty receipt payload":
let receiptBytes: seq[byte] = @[0x02] let receiptBytes: seq[byte] = @[0x02]
expect RlpTypeMismatch: expect RlpTypeMismatch:
let _ = rlp.decode(receiptBytes, Receipt) let _ = rlp.decode(receiptBytes, Receipt)
test "Receipt legacy": test "Receipt legacy":
const const
# Test payload from # Test payload from
# https://github.com/ethereum/go-ethereum/blob/253447a4f5e5f7f65c0605d490360bb58fb5f8e0/core/types/receipt_test.go#L417 # https://github.com/ethereum/go-ethereum/blob/253447a4f5e5f7f65c0605d490360bb58fb5f8e0/core/types/receipt_test.go#L417
payload = "f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff" payload = "f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"
receiptsBytes = hexToSeqByte(payload) receiptsBytes = hexToSeqByte(payload)
let receipt = rlp.decode(receiptsBytes, Receipt) let receipt = rlp.decode(receiptsBytes, Receipt)
check receipt.receiptType == LegacyReceipt check receipt.receiptType == LegacyReceipt
let encoded = rlp.encode(receipt) let encoded = rlp.encode(receipt)
check receiptsBytes == encoded check receiptsBytes == encoded
test "Receipt EIP-2930": test "Receipt EIP-2930":
const const
# Test payload from # Test payload from
# https://github.com/ethereum/go-ethereum/blob/253447a4f5e5f7f65c0605d490360bb58fb5f8e0/core/types/receipt_test.go#L435 # https://github.com/ethereum/go-ethereum/blob/253447a4f5e5f7f65c0605d490360bb58fb5f8e0/core/types/receipt_test.go#L435
payload = "01f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff" payload = "01f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"
receiptsBytes = hexToSeqByte(payload) receiptsBytes = hexToSeqByte(payload)
let receipt = rlp.decode(receiptsBytes, Receipt) let receipt = rlp.decode(receiptsBytes, Receipt)
check receipt.receiptType == Eip2930Receipt check receipt.receiptType == Eip2930Receipt
let encoded = rlp.encode(receipt) let encoded = rlp.encode(receipt)
check receiptsBytes == encoded check receiptsBytes == encoded
test "Receipt EIP-1559": test "Receipt EIP-1559":
const const
# Test payload from # Test payload from
# https://github.com/ethereum/go-ethereum/blob/253447a4f5e5f7f65c0605d490360bb58fb5f8e0/core/types/receipt_test.go#L453 # https://github.com/ethereum/go-ethereum/blob/253447a4f5e5f7f65c0605d490360bb58fb5f8e0/core/types/receipt_test.go#L453
payload = "02f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff" payload = "02f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"
receiptsBytes = hexToSeqByte(payload) receiptsBytes = hexToSeqByte(payload)
let receipt = rlp.decode(receiptsBytes, Receipt) let receipt = rlp.decode(receiptsBytes, Receipt)
check receipt.receiptType == Eip1559Receipt check receipt.receiptType == Eip1559Receipt
let encoded = rlp.encode(receipt) let encoded = rlp.encode(receipt)
check receiptsBytes == encoded check receiptsBytes == encoded
suite1()
suite2()

View File

@ -16,11 +16,10 @@ import
../../eth/rlp, ../../eth/rlp,
../../eth/common/transaction ../../eth/common/transaction
const const
recipient = hexToByteArray[20]("095e7baea6a6c7c4c2dfeb977efac326af552d87") recipient = address"095e7baea6a6c7c4c2dfeb977efac326af552d87"
zeroG1 = hexToByteArray[48]("0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") zeroG1 = bytes48"0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
source = hexToByteArray[20]("0x0000000000000000000000000000000000000001") source = address"0x0000000000000000000000000000000000000001"
storageKey= default(StorageKey) storageKey= default(StorageKey)
accesses = @[AccessPair(address: source, storageKeys: @[storageKey])] accesses = @[AccessPair(address: source, storageKeys: @[storageKey])]
blob = default(NetworkBlob) blob = default(NetworkBlob)
@ -95,7 +94,7 @@ proc tx5(i: int): PooledTransaction =
proc tx6(i: int): PooledTransaction = proc tx6(i: int): PooledTransaction =
const const
digest = "010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014".toDigest digest = bytes32"010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014"
PooledTransaction( PooledTransaction(
tx: Transaction( tx: Transaction(
@ -114,7 +113,7 @@ proc tx6(i: int): PooledTransaction =
proc tx7(i: int): PooledTransaction = proc tx7(i: int): PooledTransaction =
const const
digest = "01624652859a6e98ffc1608e2af0147ca4e86e1ce27672d8d3f3c9d4ffd6ef7e".toDigest digest = bytes32"01624652859a6e98ffc1608e2af0147ca4e86e1ce27672d8d3f3c9d4ffd6ef7e"
PooledTransaction( PooledTransaction(
tx: Transaction( tx: Transaction(
@ -130,7 +129,7 @@ proc tx7(i: int): PooledTransaction =
proc tx8(i: int): PooledTransaction = proc tx8(i: int): PooledTransaction =
const const
digest = "01624652859a6e98ffc1608e2af0147ca4e86e1ce27672d8d3f3c9d4ffd6ef7e".toDigest digest = bytes32"01624652859a6e98ffc1608e2af0147ca4e86e1ce27672d8d3f3c9d4ffd6ef7e"
PooledTransaction( PooledTransaction(
tx: Transaction( tx: Transaction(

View File

@ -15,14 +15,12 @@ import
unittest2, unittest2,
../../eth/common, ../../eth/common,
../../eth/rlp, ../../eth/rlp,
../../eth/common/transaction, ../../eth/common/[keys, transactions_rlp]
../../eth/keys
const const
recipient = hexToByteArray[20]("095e7baea6a6c7c4c2dfeb977efac326af552d87") recipient = address"095e7baea6a6c7c4c2dfeb977efac326af552d87"
zeroG1 = hexToByteArray[48]("0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") source = address"0x0000000000000000000000000000000000000001"
source = hexToByteArray[20]("0x0000000000000000000000000000000000000001") storageKey= default(Bytes32)
storageKey= default(StorageKey)
accesses = @[AccessPair(address: source, storageKeys: @[storageKey])] accesses = @[AccessPair(address: source, storageKeys: @[storageKey])]
abcdef = hexToSeqByte("abcdef") abcdef = hexToSeqByte("abcdef")
authList = @[Authorization( authList = @[Authorization(
@ -99,9 +97,7 @@ suite "Transaction EIP-7702 tests":
tx = tx0(2) tx = tx0(2)
let let
privateKey = PrivateKey.fromHex(keyHex).valueOr: privateKey = PrivateKey.fromHex(keyHex).expect("valid key")
echo "ERROR: ", error
quit(QuitFailure)
rlpTx = rlpEncode(tx) rlpTx = rlpEncode(tx)
sig = sign(privateKey, rlpTx).toRaw sig = sign(privateKey, rlpTx).toRaw

View File

@ -2,11 +2,10 @@
import import
unittest2, unittest2,
nimcrypto/hash, std/strutils,
serialization/testing/generic_suite, serialization/testing/generic_suite,
../../eth/common/[eth_types, eth_types_json_serialization], ../../eth/common/[eth_types, eth_types_json_serialization],
../../eth/common/eth_types_rlp, ../../eth/common/eth_types_rlp
../../eth/rlp
func `==`*(lhs, rhs: BlockHashOrNumber): bool = func `==`*(lhs, rhs: BlockHashOrNumber): bool =
if lhs.isHash != rhs.isHash: if lhs.isHash != rhs.isHash:
@ -17,8 +16,8 @@ func `==`*(lhs, rhs: BlockHashOrNumber): bool =
else: else:
lhs.number == rhs.number lhs.number == rhs.number
const const testHash =
testHash = Hash256.fromHex "0x7a64245f7f95164f6176d90bd4903dbdd3e5433d555dd1385e81787f9672c588" hash32"0x7a64245f7f95164f6176d90bd4903dbdd3e5433d555dd1385e81787f9672c588"
suite "BlockHashOrNumber": suite "BlockHashOrNumber":
test "construction": test "construction":
@ -49,14 +48,16 @@ suite "BlockHashOrNumber":
echo "A longer hash should not produce the value ", x echo "A longer hash should not produce the value ", x
test "serialization": test "serialization":
let hash = Hash256.fromHex "0x7a64245f7f95164f6176d90bd4903dbdd3e5433d555dd1385e81787f9672c588" const hash =
hash32"0x7a64245f7f95164f6176d90bd4903dbdd3e5433d555dd1385e81787f9672c588"
Json.roundtripTest BlockHashOrNumber(isHash: true, hash: hash), Json.roundtripTest BlockHashOrNumber(isHash: true, hash: hash),
"\"0x7a64245f7f95164f6176d90bd4903dbdd3e5433d555dd1385e81787f9672c588\"" "\"0x7a64245f7f95164f6176d90bd4903dbdd3e5433d555dd1385e81787f9672c588\""
Json.roundtripTest BlockHashOrNumber(isHash: false, number: 1209231231), Json.roundtripTest BlockHashOrNumber(isHash: false, number: 1209231231),
"\"1209231231\"" "\"1209231231\""
suite "Block encodings":
test "EIP-4399 prevRandao field": test "EIP-4399 prevRandao field":
var blk: BlockHeader var blk: BlockHeader
blk.prevRandao = testHash blk.prevRandao = testHash
@ -75,3 +76,56 @@ suite "BlockHashOrNumber":
let dh = rlp.decode(rlpBytes, BlockHeader) let dh = rlp.decode(rlpBytes, BlockHeader)
check dh.parentBeaconBlockRoot.isSome check dh.parentBeaconBlockRoot.isSome
check dh.parentBeaconBlockRoot.get == testHash check dh.parentBeaconBlockRoot.get == testHash
suite "Address":
test "Bytes conversion":
let bytes =
bytes32"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
check:
bytes.to(Address) == address"ccddeeff00112233445566778899aabbccddeeff"
bytes.to(Address).to(Bytes32) ==
bytes32"000000000000000000000000ccddeeff00112233445566778899aabbccddeeff"
test "EIP-55 checksum":
let
cases = [
"0x52908400098527886E0F7030069857D2E4169EE7",
"0x8617E340B3D01FA5F11F306F4090FD50E238070D",
"0xde709f2102306220921060314715629080e2fb77",
"0x27b1fdb04752bbc536007a920d24acb045561c26",
"0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
"0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359",
"0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB",
"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb",
]
fails = cases[4 .. 7]
# https://eips.ethereum.org/EIPS/eip-55#test-cases
for s in cases:
check:
Address.fromHex(s).toChecksum0xHex() == s
Address.hasValidChecksum(s)
for s in fails:
check:
not Address.hasValidChecksum(s.toLowerAscii)
suite "Bytes":
test "copyFrom":
check:
Bytes4.copyFrom([]) == default(Bytes4)
Bytes4.copyFrom([byte 0]) == default(Bytes4)
Bytes4.copyFrom([byte 0, 0, 0, 0, 0]) == default(Bytes4)
Bytes4.copyFrom([byte 1, 0], 1) == default(Bytes4)
Bytes4.copyFrom([byte 1, 1], 2) == default(Bytes4)
Bytes4.copyFrom([byte 1, 1], 20) == default(Bytes4)
test "toHex":
check:
bytes4"0xaabbccdd".toHex == "aabbccdd"
bytes4"0xaabbccdd".to0xHex == "0xaabbccdd"
suite "Hashes":
test "constants":
check:
emptyKeccak256 == keccak256(default(array[0, byte]))
emptyRoot == keccak256([byte 128])

View File

@ -69,14 +69,14 @@ suite "BlockHeader roundtrip test":
roundTrip(h) roundTrip(h)
test "Header + none(baseFee) + some(withdrawalsRoot)": test "Header + none(baseFee) + some(withdrawalsRoot)":
let h = BlockHeader(withdrawalsRoot: Opt.some(Hash256())) let h = BlockHeader(withdrawalsRoot: Opt.some(default(Hash32)))
expect AssertionDefect: expect AssertionDefect:
roundTrip(h) roundTrip(h)
test "Header + none(baseFee) + some(withdrawalsRoot) + " & test "Header + none(baseFee) + some(withdrawalsRoot) + " &
"some(blobGasUsed) + some(excessBlobGas)": "some(blobGasUsed) + some(excessBlobGas)":
let h = BlockHeader( let h = BlockHeader(
withdrawalsRoot: Opt.some(Hash256()), withdrawalsRoot: Opt.some(default(Hash32)),
blobGasUsed: Opt.some(1'u64), blobGasUsed: Opt.some(1'u64),
excessBlobGas: Opt.some(1'u64) excessBlobGas: Opt.some(1'u64)
) )
@ -105,7 +105,7 @@ suite "BlockHeader roundtrip test":
test "Header + some(baseFee) + some(withdrawalsRoot)": test "Header + some(baseFee) + some(withdrawalsRoot)":
let h = BlockHeader( let h = BlockHeader(
baseFeePerGas: Opt.some(2.u256), baseFeePerGas: Opt.some(2.u256),
withdrawalsRoot: Opt.some(Hash256()) withdrawalsRoot: Opt.some(default(Hash32))
) )
roundTrip(h) roundTrip(h)
@ -113,7 +113,7 @@ suite "BlockHeader roundtrip test":
"some(blobGasUsed) + some(excessBlobGas)": "some(blobGasUsed) + some(excessBlobGas)":
let h = BlockHeader( let h = BlockHeader(
baseFeePerGas: Opt.some(2.u256), baseFeePerGas: Opt.some(2.u256),
withdrawalsRoot: Opt.some(Hash256()), withdrawalsRoot: Opt.some(default(Hash32)),
blobGasUsed: Opt.some(1'u64), blobGasUsed: Opt.some(1'u64),
excessBlobGas: Opt.some(1'u64) excessBlobGas: Opt.some(1'u64)
) )
@ -176,12 +176,12 @@ genTest(BlockBody)
type type
BlockHeaderOpt* = object BlockHeaderOpt* = object
parentHash*: Hash256 parentHash*: Hash32
ommersHash*: Hash256 ommersHash*: Hash32
coinbase*: EthAddress coinbase*: Address
stateRoot*: Hash256 stateRoot*: Hash32
txRoot*: Hash256 txRoot*: Hash32
receiptRoot*: Hash256 receiptRoot*: Hash32
bloom*: BloomFilter bloom*: BloomFilter
difficulty*: DifficultyInt difficulty*: DifficultyInt
blockNumber*: BlockNumber blockNumber*: BlockNumber
@ -189,10 +189,10 @@ type
gasUsed*: GasInt gasUsed*: GasInt
timestamp*: EthTime timestamp*: EthTime
extraData*: Blob extraData*: Blob
mixDigest*: Hash256 mixDigest*: Hash32
nonce*: BlockNonce nonce*: BlockNonce
fee*: Opt[UInt256] fee*: Opt[UInt256]
withdrawalsRoot*: Opt[Hash256] withdrawalsRoot*: Opt[Hash32]
blobGasUsed*: Opt[GasInt] blobGasUsed*: Opt[GasInt]
excessBlobGas*: Opt[GasInt] excessBlobGas*: Opt[GasInt]
@ -252,27 +252,27 @@ suite "EIP-7865 tests":
Request( Request(
requestType: DepositRequestType, requestType: DepositRequestType,
deposit: DepositRequest( deposit: DepositRequest(
pubkey : hexToByteArray[48]("0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), pubkey : bytes48"0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
withdrawalCredentials: hexToByteArray[32]("0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"), withdrawalCredentials: bytes32"0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
amount : 1, amount : 1,
signature : hexToByteArray[96]("0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), signature : bytes96"0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
index : 3, index : 3,
) )
), ),
Request( Request(
requestType: WithdrawalRequestType, requestType: WithdrawalRequestType,
withdrawal: WithdrawalRequest( withdrawal: WithdrawalRequest(
sourceAddress : hexToByteArray[20]("0xDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"), sourceAddress : address"0xDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
validatorPubkey: hexToByteArray[48]("0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), validatorPubkey: bytes48"0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
amount : 7, amount : 7,
) )
), ),
Request( Request(
requestType: ConsolidationRequestType, requestType: ConsolidationRequestType,
consolidation: ConsolidationRequest( consolidation: ConsolidationRequest(
sourceAddress: hexToByteArray[20]("0xEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"), sourceAddress: address"0xEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE",
sourcePubkey : hexToByteArray[48]("0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), sourcePubkey : bytes48"0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
targetPubkey : hexToByteArray[48]("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), targetPubkey : bytes48"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
) )
) )
] ]

View File

@ -11,19 +11,11 @@
import import
unittest2, unittest2,
nimcrypto/hash, nimcrypto/keccak, nimcrypto/utils, stew/byteutils, nimcrypto/utils, stew/byteutils,
../../eth/keys ../../eth/common/[addresses, keys, hashes]
from strutils import toLowerAscii from strutils import toLowerAscii
proc compare(x: openArray[byte], y: openArray[byte]): bool =
result = len(x) == len(y)
if result:
for i in 0..(len(x) - 1):
if x[i] != y[i]:
result = false
break
let message = "message".toBytes() let message = "message".toBytes()
let rng = newRng() let rng = newRng()
@ -79,7 +71,7 @@ suite "ECC/ECDSA/ECDHE tests suite":
test "test_recover_from_signature_obj": test "test_recover_from_signature_obj":
var s = PrivateKey.fromHex(pkbytes)[] var s = PrivateKey.fromHex(pkbytes)[]
var mhash = keccak256.digest(message) var mhash = keccak256(message)
var signature = s.sign(message) var signature = s.sign(message)
var p = recover(signature, SkMessage(mhash.data))[] var p = recover(signature, SkMessage(mhash.data))[]
check: check:
@ -94,8 +86,8 @@ suite "ECC/ECDSA/ECDHE tests suite":
test "test_to_canonical_address_from_public_key": test "test_to_canonical_address_from_public_key":
var s = PrivateKey.fromHex(pkbytes)[] var s = PrivateKey.fromHex(pkbytes)[]
var chk = s.toPublicKey().toCanonicalAddress() var chk = s.toPublicKey().toCanonicalAddress()
var expect = fromHex(stripSpaces(address)) var expect = Address.fromHex(stripSpaces(address))
check compare(chk, expect) == true check chk == expect
test "test_to_checksum_address_from_public_key": test "test_to_checksum_address_from_public_key":
var s = PrivateKey.fromHex(pkbytes)[] var s = PrivateKey.fromHex(pkbytes)[]
@ -169,7 +161,7 @@ suite "ECC/ECDSA/ECDHE tests suite":
# Copied from https://github.com/ethereum/cpp-ethereum/blob/develop/test/unittests/libdevcrypto/crypto.cpp#L394 # Copied from https://github.com/ethereum/cpp-ethereum/blob/develop/test/unittests/libdevcrypto/crypto.cpp#L394
var expectm = """ var expectm = """
8ac7e464348b85d9fdfc0a81f2fdc0bbbb8ee5fb3840de6ed60ad9372e718977""" 8ac7e464348b85d9fdfc0a81f2fdc0bbbb8ee5fb3840de6ed60ad9372e718977"""
var s = PrivateKey.fromRaw(keccak256.digest("ecdhAgree").data)[] var s = PrivateKey.fromRaw(keccak256("ecdhAgree").data)[]
var p = s.toPublicKey() var p = s.toPublicKey()
let expect = fromHex(stripSpaces(expectm)) let expect = fromHex(stripSpaces(expectm))
let secret = ecdhSharedSecret(s, p) let secret = ecdhSharedSecret(s, p)
@ -191,7 +183,7 @@ suite "ECC/ECDSA/ECDHE tests suite":
let expect = fromHex(stripSpaces(e0)) let expect = fromHex(stripSpaces(e0))
let secret = ecdhSharedSecret(s, p) let secret = ecdhSharedSecret(s, p)
check: check:
compare(expect, secret.data) == true expect == secret.data
test "ECDSA/cpp-ethereum crypto.cpp#L132": test "ECDSA/cpp-ethereum crypto.cpp#L132":
# ECDSA test vectors # ECDSA test vectors
@ -205,15 +197,15 @@ suite "ECC/ECDSA/ECDHE tests suite":
var check1 = fromHex(stripSpaces(signature)) var check1 = fromHex(stripSpaces(signature))
var check2 = fromHex(stripSpaces(pubkey)) var check2 = fromHex(stripSpaces(pubkey))
var s = PrivateKey.fromRaw(keccak256.digest("sec").data)[] var s = PrivateKey.fromRaw(keccak256("sec").data)[]
var m = keccak256.digest("msg") var m = keccak256("msg")
var sig = sign(s, SkMessage(m.data)) var sig = sign(s, SkMessage(m.data))
var sersig = sig.toRaw() var sersig = sig.toRaw()
var key = recover(sig, SkMessage(m.data))[] var key = recover(sig, SkMessage(m.data))[]
var serkey = key.toRaw() var serkey = key.toRaw()
check: check:
compare(sersig, check1) == true sersig == check1
compare(serkey, check2) == true serkey == check2
test "ECDSA/100 signatures": test "ECDSA/100 signatures":
# signature test # signature test

View File

@ -12,7 +12,7 @@
import import
std/[json, os], std/[json, os],
unittest2, unittest2,
../../eth/keys, ../../eth/keyfile/[keyfile] ../../eth/common/keys, ../../eth/keyfile/[keyfile]
# Test vectors copied from # Test vectors copied from
# https://github.com/ethereum/tests/blob/develop/KeyStoreTests/basic_tests.json # https://github.com/ethereum/tests/blob/develop/KeyStoreTests/basic_tests.json

View File

@ -1,3 +0,0 @@
import
./test_keys,
./test_private_public_key_consistency

View File

@ -1,79 +0,0 @@
# Nim Eth-keys
# Copyright (c) 2018 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.
# This is a sample of signatures generated with a known-good implementation of the ECDSA
# algorithm, which we use to test our ECC backends. If necessary, it can be generated from scratch
# with the following code:
#
# """python
# from devp2p import crypto
# from eth_utils import encode_hex
# msg = b'message'
# msghash = crypto.sha3(b'message')
# for secret in ['alice', 'bob', 'eve']:
# print("'{}': dict(".format(secret))
# privkey = crypto.mk_privkey(secret)
# pubkey = crypto.privtopub(privkey)
# print(" privkey='{}',".format(encode_hex(privkey)))
# print(" pubkey='{}',".format(encode_hex(crypto.privtopub(privkey))))
# ecc = crypto.ECCx(raw_privkey=privkey)
# sig = ecc.sign(msghash)
# print(" sig='{}',".format(encode_hex(sig)))
# print(" raw_sig='{}')".format(crypto._decode_sig(sig)))
# doAssert crypto.ecdsa_recover(msghash, sig) == pubkey
# """
import nimcrypto/keccak
type
testKeySig* = object
privkey*: string
pubkey*: string
raw_sig*: tuple[v: int, r, s: string]
serialized_sig*: string
let
MSG* = "message"
MSGHASH* = keccak256.digest(MSG)
# Conversion done through https://www.mobilefish.com/services/big_number/big_number.php
let
alice* = testKeySig(
privkey: "9c0257114eb9399a2985f8e75dad7600c5d89fe3824ffa99ec1c3eb8bf3b0501",
pubkey: "5eed5fa3a67696c334762bb4823e585e2ee579aba3558d9955296d6c04541b426078dbd48d74af1fd0c72aa1a05147cf17be6b60bdbed6ba19b08ec28445b0ca",
raw_sig: (
v: 1,
r: "B20E2EA5D3CBAA83C1E0372F110CF12535648613B479B64C1A8C1A20C5021F38", # Decimal "80536744857756143861726945576089915884233437828013729338039544043241440681784",
s: "0434D07EC5795E3F789794351658E80B7FAF47A46328F41E019D7B853745CDFD" # Decimal "1902566422691403459035240420865094128779958320521066670269403689808757640701"
),
serialized_sig: "b20e2ea5d3cbaa83c1e0372f110cf12535648613b479b64c1a8c1a20c5021f380434d07ec5795e3f789794351658e80b7faf47a46328f41e019d7b853745cdfd01"
)
bob* = testKeySig(
privkey: "38e47a7b719dce63662aeaf43440326f551b8a7ee198cee35cb5d517f2d296a2",
pubkey: "347746ccb908e583927285fa4bd202f08e2f82f09c920233d89c47c79e48f937d049130e3d1c14cf7b21afefc057f71da73dec8e8ff74ff47dc6a574ccd5d570",
raw_sig: (
v: 1,
r: "5C48EA4F0F2257FA23BD25E6FCB0B75BBE2FF9BBDA0167118DAB2BB6E31BA76E", # Decimal "41741612198399299636429810387160790514780876799439767175315078161978521003886",
s: "691DBDAF2A231FC9958CD8EDD99507121F8184042E075CF10F98BA88ABFF1F36" # Decimal "47545396818609319588074484786899049290652725314938191835667190243225814114102"
),
serialized_sig: "5c48ea4f0f2257fa23bd25e6fcb0b75bbe2ff9bbda0167118dab2bb6e31ba76e691dbdaf2a231fc9958cd8edd99507121f8184042e075cf10f98ba88abff1f3601"
)
eve* = testKeySig(
privkey: "876be0999ed9b7fc26f1b270903ef7b0c35291f89407903270fea611c85f515c",
pubkey: "c06641f0d04f64dba13eac9e52999f2d10a1ff0ca68975716b6583dee0318d91e7c2aed363ed22edeba2215b03f6237184833fd7d4ad65f75c2c1d5ea0abecc0",
raw_sig: (
v: 0,
r: "BABEEFC5082D3CA2E0BC80532AB38F9CFB196FB9977401B2F6A98061F15ED603", # Decimal "84467545608142925331782333363288012579669270632210954476013542647119929595395",
s: "603D0AF084BF906B2CDF6CDDE8B2E1C3E51A41AF5E9ADEC7F3643B3F1AA2AADF" # Decimal "43529886636775750164425297556346136250671451061152161143648812009114516499167"
),
serialized_sig: "babeefc5082d3ca2e0bc80532ab38f9cfb196fb9977401b2f6a98061f15ed603603d0af084bf906b2cdf6cdde8b2e1c3e51a41af5e9adec7f3643b3f1aa2aadf00"
)

View File

@ -1,66 +0,0 @@
# Nim Eth-keys
# Copyright (c) 2018 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.
import
unittest2,
../../eth/keys, #../src/private/conversion_bytes,
./config
suite "Test key and signature data structure":
test "Signing from private key object (ported from official eth-keys)":
for person in [alice, bob, eve]:
let
pk = PrivateKey.fromHex(person.privkey)[]
signature = pk.sign_msg(MSG)
check: verify_msg(pk.public_key, MSG, signature)
test "Hash signing from private key object (ported from official eth-keys)":
for person in [alice, bob, eve]:
let
pk = PrivateKey.fromHex(person.privkey)[]
signature = pk.sign_msg(MSGHASH)
check: verify_msg(pk.public_key, MSGHASH, signature)
test "Recover public key from message":
for person in [alice, bob, eve]:
let
pk = PrivateKey.fromHex(person.privkey)[]
signature = pk.sign_msg(MSG)
recovered_pubkey = recover_pubkey_from_msg(MSG, signature)
check: pk.public_key == recovered_pubkey
test "Recover public key from message hash":
for person in [alice, bob, eve]:
let
pk = PrivateKey.formHex(person.privkey)[]
signature = pk.sign_msg(MSGHASH)
recovered_pubkey = recover_pubkey_from_msg(MSGHASH, signature)
check: pk.public_key == recovered_pubkey
test "Signature serialization and deserialization":
for person in [alice, bob, eve]:
let
pk = PrivateKey.fromHex(person.privkey)[]
signature = pk.sign_msg(MSG)
deserializedSignature = parseSignature(hexToSeqByteBE(person.serialized_sig))
var serialized_sig: array[65, byte]
signature.serialize(serialized_sig)
check:
signature == deserializedSignature
serialized_sig.toHex() == person.serialized_sig
$signature == person.serialized_sig

View File

@ -1,29 +0,0 @@
# Nim Eth-keys
# Copyright (c) 2018 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/keys,
./config
suite "Testing private -> public key conversion":
test "Known private to known public keys (test data from Ethereum eth-keys)":
for person in [alice, bob, eve]:
let
privKey = PrivateKey.fromHex(person.privkey)[]
pubKey = privKey.toPublicKey()
check:
# Compare as strings
$pubKey == person.pubkey
# Compare as keys
pubKey == PublicKey.fromHex(person.pubkey)[]

View File

@ -8,7 +8,6 @@
import import
std/net, std/net,
chronos, chronos,
../../eth/keys,
../../eth/p2p/discoveryv5/[enr, node, routing_table], ../../eth/p2p/discoveryv5/[enr, node, routing_table],
../../eth/p2p/discoveryv5/protocol as discv5_protocol ../../eth/p2p/discoveryv5/protocol as discv5_protocol

View File

@ -20,8 +20,8 @@ p2pProtocol eth(version = 63,
discard await peer.status(63, discard await peer.status(63,
network.networkId, network.networkId,
0.u256, 0.u256,
Hash256(), default(Hash32),
Hash256(), default(Hash32),
timeout = chronos.seconds(10)) timeout = chronos.seconds(10))
handshake: handshake:
@ -29,26 +29,26 @@ p2pProtocol eth(version = 63,
protocolVersion: uint, protocolVersion: uint,
networkId: NetworkId, networkId: NetworkId,
totalDifficulty: DifficultyInt, totalDifficulty: DifficultyInt,
bestHash: KeccakHash, bestHash: Hash32,
genesisHash: KeccakHash) genesisHash: Hash32)
requestResponse: requestResponse:
proc getBlockHeaders(peer: Peer, request: openArray[KeccakHash]) {.gcsafe.} = proc getBlockHeaders(peer: Peer, request: openArray[Hash32]) {.gcsafe.} =
var headers: seq[BlockHeader] var headers: seq[Header]
await response.send(headers) await response.send(headers)
proc blockHeaders(p: Peer, headers: openArray[BlockHeader]) proc blockHeaders(p: Peer, headers: openArray[Header])
requestResponse: requestResponse:
proc getBlockBodies(peer: Peer, hashes: openArray[KeccakHash]) {.gcsafe.} = discard proc getBlockBodies(peer: Peer, hashes: openArray[Hash32]) {.gcsafe.} = discard
proc blockBodies(peer: Peer, blocks: openArray[BlockBody]) proc blockBodies(peer: Peer, blocks: openArray[BlockBody])
nextID 13 nextID 13
requestResponse: requestResponse:
proc getNodeData(peer: Peer, hashes: openArray[KeccakHash]) = discard proc getNodeData(peer: Peer, hashes: openArray[Hash32]) = discard
proc nodeData(peer: Peer, data: openArray[Blob]) proc nodeData(peer: Peer, data: openArray[seq[byte]])
requestResponse: requestResponse:
proc getReceipts(peer: Peer, hashes: openArray[KeccakHash]) = discard proc getReceipts(peer: Peer, hashes: openArray[Hash32]) = discard
proc receipts(peer: Peer, receipts: openArray[Receipt]) proc receipts(peer: Peer, receipts: openArray[Receipt])

View File

@ -11,7 +11,7 @@
import import
std/strutils, std/strutils,
chronos, chronos,
../../eth/[keys, p2p], ../../eth/p2p/[discovery, enode] ../../eth/p2p, ../../eth/common/keys, ../../eth/p2p/[discovery, enode]
var nextPort = 30303 var nextPort = 30303

View File

@ -12,7 +12,7 @@
import import
unittest2, unittest2,
nimcrypto/[utils, keccak], nimcrypto/[utils, keccak],
../../eth/keys, ../../eth/p2p/auth ../../eth/common/keys, ../../eth/p2p/auth
# This was generated by `print` actual auth message generated by # This was generated by `print` actual auth message generated by
# https://github.com/ethereum/py-evm/blob/master/tests/p2p/test_auth.py # https://github.com/ethereum/py-evm/blob/master/tests/p2p/test_auth.py

View File

@ -12,7 +12,7 @@
import import
unittest2, unittest2,
nimcrypto/[utils, sysrand], nimcrypto/[utils, sysrand],
../../eth/keys, ../../eth/p2p/[auth, rlpxcrypt] ../../eth/common/keys, ../../eth/p2p/[auth, rlpxcrypt]
const data = [ const data = [
("initiator_private_key", ("initiator_private_key",

View File

@ -11,8 +11,8 @@
import import
std/sequtils, std/sequtils,
chronos, stew/byteutils, nimcrypto, testutils/unittests, chronos, stew/byteutils, nimcrypto/keccak, testutils/unittests,
../../eth/keys, ../../eth/p2p/[discovery, kademlia, enode], ../../eth/common/keys, ../../eth/p2p/[discovery, kademlia, enode],
../stubloglevel ../stubloglevel
proc localAddress(port: int): Address = proc localAddress(port: int): Address =

View File

@ -11,7 +11,7 @@ import
std/[tables, sequtils, net], std/[tables, sequtils, net],
chronos, chronicles, stint, testutils/unittests, chronos, chronicles, stint, testutils/unittests,
stew/byteutils, stew/byteutils,
../../eth/keys, ../../eth/common/keys,
../../eth/p2p/discoveryv5/[enr, node, routing_table, encoding, sessions, ../../eth/p2p/discoveryv5/[enr, node, routing_table, encoding, sessions,
messages, nodes_verification], messages, nodes_verification],
../../eth/p2p/discoveryv5/protocol as discv5_protocol, ../../eth/p2p/discoveryv5/protocol as discv5_protocol,

View File

@ -11,7 +11,6 @@ import
std/[options, sequtils, tables, net], std/[options, sequtils, tables, net],
unittest2, unittest2,
stint, stew/byteutils, stint, stew/byteutils,
../../eth/keys,
../../eth/p2p/discoveryv5/[messages_encoding, encoding, enr, node, sessions], ../../eth/p2p/discoveryv5/[messages_encoding, encoding, enr, node, sessions],
../stubloglevel ../stubloglevel

View File

@ -12,7 +12,7 @@
import import
unittest2, unittest2,
nimcrypto/[utils, sha2, hmac, rijndael], nimcrypto/[utils, sha2, hmac, rijndael],
../../eth/keys, ../../eth/p2p/ecies ../../eth/common/keys, ../../eth/p2p/ecies
proc compare[A, B](x: openArray[A], y: openArray[B], s: int = 0): bool = proc compare[A, B](x: openArray[A], y: openArray[B], s: int = 0): bool =
result = true result = true

View File

@ -10,7 +10,7 @@ import
std/[sequtils, net], std/[sequtils, net],
stew/byteutils, stew/byteutils,
unittest2, unittest2,
../../eth/p2p/discoveryv5/enr, ../../eth/[keys, rlp] ../../eth/p2p/discoveryv5/enr, ../../eth/rlp, ../../eth/common/keys
let rng = newRng() let rng = newRng()

View File

@ -10,7 +10,7 @@
import import
std/net, std/net,
unittest2, unittest2,
../../eth/keys, ../../eth/p2p/discoveryv5/[node, ip_vote] ../../eth/common/keys, ../../eth/p2p/discoveryv5/[node, ip_vote]
suite "IP vote": suite "IP vote":
let rng = newRng() let rng = newRng()

View File

@ -2,7 +2,7 @@
import import
unittest2, unittest2,
../../eth/keys, ../../eth/p2p/discoveryv5/[routing_table, node, enr], ../../eth/common/keys, ../../eth/p2p/discoveryv5/[routing_table, node, enr],
./discv5_test_helper ./discv5_test_helper
func customDistance*(a, b: NodeId): UInt256 = func customDistance*(a, b: NodeId): UInt256 =

View File

@ -22,7 +22,7 @@ proc test_blockBodyTranscode() =
Transaction(nonce: 1)]), Transaction(nonce: 1)]),
BlockBody( BlockBody(
uncles: @[ uncles: @[
BlockHeader(nonce: [0x20u8,0,0,0,0,0,0,0])]), BlockHeader(nonce: BlockNonce([0x20u8,0,0,0,0,0,0,0]))]),
BlockBody(), BlockBody(),
BlockBody( BlockBody(
transactions: @[ transactions: @[

View File

@ -8,7 +8,7 @@
import import
unittest2, unittest2,
stew/byteutils, nimcrypto/[keccak, hash], stew/byteutils,
../../eth/trie/[db, binary, binaries, branches] ../../eth/trie/[db, binary, binaries, branches]
suite "examples": suite "examples":
@ -77,7 +77,7 @@ suite "examples":
check branchs.len < beforeDeleteLen check branchs.len < beforeDeleteLen
var node = branchs[1] var node = branchs[1]
let nodeHash = keccak256.digest(node) let nodeHash = keccak256(node)
var nodes = getTrieNodes(db, @(nodeHash.data)) var nodes = getTrieNodes(db, @(nodeHash.data))
check nodes.len == branchs.len - 1 check nodes.len == branchs.len - 1

View File

@ -13,13 +13,11 @@ import
std/sequtils, std/sequtils,
unittest2, unittest2,
stint, stint,
nimcrypto/hash,
stew/byteutils, stew/byteutils,
../../eth/trie/[hexary, db, trie_defs, hexary_proof_verification] ../../eth/trie/[hexary, db, trie_defs, hexary_proof_verification]
proc getKeyBytes(i: int): seq[byte] = proc getKeyBytes(i: int): seq[byte] =
let hash = keccakHash(u256(i).toBytesBE()) @(u256(i).toBytesBE())
return toSeq(hash.data)
suite "MPT trie proof verification": suite "MPT trie proof verification":
test "Validate proof for existing value": test "Validate proof for existing value":
@ -53,7 +51,7 @@ suite "MPT trie proof verification":
trie.put(bytes, bytes) trie.put(bytes, bytes)
let let
nonExistingKey = toSeq(keccakHash(toBytesBE(u256(numValues + 1))).data) nonExistingKey = toSeq(toBytesBE(u256(numValues + 1)))
proof = trie.getBranch(nonExistingKey) proof = trie.getBranch(nonExistingKey)
root = trie.rootHash() root = trie.rootHash()
res = verifyMptProof(proof, root, nonExistingKey, nonExistingKey) res = verifyMptProof(proof, root, nonExistingKey, nonExistingKey)
@ -72,7 +70,7 @@ suite "MPT trie proof verification":
res = verifyMptProof(proof, trie.rootHash, "not-exist".toBytes, "not-exist".toBytes) res = verifyMptProof(proof, trie.rootHash, "not-exist".toBytes, "not-exist".toBytes)
check: check:
trie.rootHash == keccakHash(emptyRlp) trie.rootHash == keccak256(emptyRlp)
proof.len() == 1 # Note that the Rust implementation returns an empty list for this scenario proof.len() == 1 # Note that the Rust implementation returns an empty list for this scenario
proof == @[emptyRlp] proof == @[emptyRlp]
res.kind == InvalidProof res.kind == InvalidProof

View File

@ -260,7 +260,7 @@ suite "hexary trie":
History = object History = object
keys: seq[seq[byte]] keys: seq[seq[byte]]
values: seq[seq[byte]] values: seq[seq[byte]]
rootHash: KeccakHash rootHash: Hash32
const const
listLength = 30 listLength = 30
@ -334,7 +334,7 @@ suite "hexary trie":
nonPruningTrie = initHexaryTrie(memdb, false) nonPruningTrie = initHexaryTrie(memdb, false)
keys = randList(seq[byte], randGen(5, 77), randGen(numKeyVal)) keys = randList(seq[byte], randGen(5, 77), randGen(numKeyVal))
vals = randList(seq[byte], randGen(1, 57), randGen(numKeyVal)) vals = randList(seq[byte], randGen(1, 57), randGen(numKeyVal))
roots = newSeq[KeccakHash](numKeyVal) roots = newSeq[Hash32](numKeyVal)
for i in 0 ..< keys.len: for i in 0 ..< keys.len:
nonPruningTrie.put(keys[i], vals[i]) nonPruningTrie.put(keys[i], vals[i])

View File

@ -14,7 +14,6 @@ import
../../eth/p2p/discoveryv5/[enr, node, routing_table], ../../eth/p2p/discoveryv5/[enr, node, routing_table],
../../eth/p2p/discoveryv5/protocol as discv5_protocol, ../../eth/p2p/discoveryv5/protocol as discv5_protocol,
../../eth/utp/utp_discv5_protocol, ../../eth/utp/utp_discv5_protocol,
../../eth/keys,
../../eth/utp/utp_router as rt, ../../eth/utp/utp_router as rt,
../p2p/discv5_test_helper, ../p2p/discv5_test_helper,
../stubloglevel ../stubloglevel

View File

@ -9,8 +9,7 @@
import import
std/options, std/options,
unittest2, unittest2,
../../eth/utp/packets, ../../eth/utp/packets
../../eth/keys
suite "uTP packet encoding": suite "uTP packet encoding":
test "Encode/decode SYN packet": test "Encode/decode SYN packet":

View File

@ -11,7 +11,7 @@ import
chronos, chronos,
testutils/unittests, testutils/unittests,
./test_utils, ./test_utils,
../../eth/keys, ../../eth/common/keys,
../../eth/utp/[utp_router, utp_protocol], ../../eth/utp/[utp_router, utp_protocol],
../stubloglevel ../stubloglevel

View File

@ -12,7 +12,7 @@ import
testutils/unittests, testutils/unittests,
../../eth/utp/utp_router, ../../eth/utp/utp_router,
../../eth/utp/utp_protocol, ../../eth/utp/utp_protocol,
../../eth/keys, ../../eth/common/keys,
../../eth/p2p/discoveryv5/random2, ../../eth/p2p/discoveryv5/random2,
../stubloglevel ../stubloglevel

View File

@ -1,8 +1,7 @@
import import
chronos, chronos,
../../eth/utp/utp_socket, ../../eth/utp/utp_socket,
../../eth/utp/packets, ../../eth/utp/packets
../../eth/keys
type AssertionCallback = proc(): bool {.gcsafe, raises: [].} type AssertionCallback = proc(): bool {.gcsafe, raises: [].}

View File

@ -13,7 +13,7 @@ import
./test_utils, ./test_utils,
../../eth/utp/utp_router, ../../eth/utp/utp_router,
../../eth/utp/packets, ../../eth/utp/packets,
../../eth/keys, ../../eth/common/keys,
../stubloglevel ../stubloglevel
proc hash*(x: UtpSocketKey[int]): Hash = proc hash*(x: UtpSocketKey[int]): Hash =

View File

@ -14,7 +14,7 @@ import
../../eth/utp/utp_router, ../../eth/utp/utp_router,
../../eth/utp/utp_socket, ../../eth/utp/utp_socket,
../../eth/utp/packets, ../../eth/utp/packets,
../../eth/keys, ../../eth/common/keys,
../stubloglevel ../stubloglevel
procSuite "uTP socket": procSuite "uTP socket":

View File

@ -15,7 +15,7 @@ import
../../eth/utp/utp_router, ../../eth/utp/utp_router,
../../eth/utp/utp_socket, ../../eth/utp/utp_socket,
../../eth/utp/packets, ../../eth/utp/packets,
../../eth/keys, ../../eth/common/keys,
../stubloglevel ../stubloglevel
procSuite "uTP socket selective acks": procSuite "uTP socket selective acks":

View File

@ -9,8 +9,7 @@
import import
stew/byteutils, stew/byteutils,
unittest2, unittest2,
../../eth/utp/packets, ../../eth/utp/packets
../../eth/keys
suite "uTP packets test vectors": suite "uTP packets test vectors":
test "SYN packet": test "SYN packet":