mirror of https://github.com/status-im/nim-eth.git
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:
parent
3d51887c15
commit
6bd6bae86c
|
@ -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.
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
import ./common/[eth_types_rlp, utils]
|
import ./common/eth_types_rlp
|
||||||
export eth_types_rlp, utils
|
export eth_types_rlp
|
||||||
|
|
|
@ -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()
|
|
@ -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
|
|
@ -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()
|
|
@ -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())
|
|
@ -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.}
|
|
@ -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))
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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"
|
||||||
|
)
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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))
|
|
@ -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)
|
|
@ -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))
|
|
|
@ -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))
|
|
@ -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))
|
|
@ -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()
|
|
|
@ -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": {
|
||||||
|
|
287
eth/keys.nim
287
eth/keys.nim
|
@ -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)
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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) =
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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 =
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 =
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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])
|
||||||
|
|
|
@ -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",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
import
|
|
||||||
./test_keys,
|
|
||||||
./test_private_public_key_consistency
|
|
|
@ -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"
|
|
||||||
)
|
|
|
@ -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
|
|
|
@ -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)[]
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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])
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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 =
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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 =
|
||||||
|
|
|
@ -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: @[
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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])
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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":
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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: [].}
|
||||||
|
|
||||||
|
|
|
@ -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 =
|
||||||
|
|
|
@ -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":
|
||||||
|
|
|
@ -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":
|
||||||
|
|
|
@ -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":
|
||||||
|
|
Loading…
Reference in New Issue