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
|
||||
* rootNode() -- get 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
|
||||
|
||||
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])
|
||||
|
||||
Normally you would not set the rootHash when constructing an empty Binary-trie.
|
||||
|
|
|
@ -10,7 +10,7 @@ requires "nim >= 1.6.0",
|
|||
"nimcrypto",
|
||||
"stint",
|
||||
"secp256k1",
|
||||
"chronos#head",
|
||||
"chronos",
|
||||
"chronicles",
|
||||
"stew",
|
||||
"nat_traversal",
|
||||
|
@ -47,9 +47,6 @@ proc run(path, outdir: string) =
|
|||
task test_keyfile, "Run keyfile tests":
|
||||
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":
|
||||
run "tests/p2p/all_discv5_tests", "p2p"
|
||||
|
||||
|
@ -78,7 +75,6 @@ task test, "Run all tests":
|
|||
run "tests/test_bloom", ""
|
||||
|
||||
test_keyfile_task()
|
||||
test_keys_task()
|
||||
test_rlp_task()
|
||||
test_p2p_task()
|
||||
test_trie_task()
|
||||
|
@ -87,7 +83,6 @@ task test, "Run all tests":
|
|||
test_common_task()
|
||||
|
||||
task test_discv5_full, "Run discovery v5 and its dependencies tests":
|
||||
test_keys_task()
|
||||
test_rlp_task()
|
||||
test_discv5_task()
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import stint, ./common/eth_hash
|
||||
import stint, ./common/[addresses, base, hashes]
|
||||
|
||||
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[2], h.data[3]]
|
||||
yield [h.data[4], h.data[5]]
|
||||
|
@ -12,28 +12,34 @@ proc chunkToBloomBits(chunk: array[2, uint8]): UInt2048 =
|
|||
let l = chunk[1].int
|
||||
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):
|
||||
yield chunkToBloomBits(chunk)
|
||||
|
||||
type BloomFilter* = object
|
||||
value*: UInt2048
|
||||
|
||||
proc incl*(f: var BloomFilter, h: MDigest[256]) =
|
||||
proc incl*(f: var BloomFilter, h: Hash32) =
|
||||
for bits in bloomBits(h):
|
||||
f.value = f.value or bits
|
||||
|
||||
proc init*(_: type BloomFilter, h: MDigest[256]): BloomFilter =
|
||||
proc init*(_: type BloomFilter, h: Hash32): BloomFilter =
|
||||
result.incl(h)
|
||||
|
||||
# TODO: The following 2 procs should be one genric, but it doesn't compile. Nim bug?
|
||||
proc incl*(f: var BloomFilter, v: string) = f.incl(keccakHash(v))
|
||||
proc incl*(f: var BloomFilter, v: openArray[byte]) = f.incl(keccakHash(v))
|
||||
proc incl*[T: byte|char](f: var BloomFilter, v: openArray[T]) =
|
||||
f.incl(keccak256(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):
|
||||
if (f.value and bits).isZero: return false
|
||||
if (f.value and bits).isZero:
|
||||
return false
|
||||
return true
|
||||
|
||||
template contains*[T](f: BloomFilter, v: openArray[T]): bool =
|
||||
f.contains(keccakHash(v))
|
||||
template contains*(f: BloomFilter, v: openArray): bool =
|
||||
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]
|
||||
export eth_types_rlp, utils
|
||||
import ./common/eth_types_rlp
|
||||
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
|
||||
# * 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).
|
||||
|
@ -6,41 +7,28 @@
|
|||
|
||||
{.push raises: [].}
|
||||
|
||||
## keccak256 is used across ethereum as the "default" hash function and this
|
||||
## module provides a type and some helpers to produce such hashes
|
||||
# Minimal compatibility layer with earlier versions of this file, to be removed
|
||||
# when users have upgraded
|
||||
|
||||
import
|
||||
nimcrypto/[keccak, hash]
|
||||
{.deprecated.}
|
||||
|
||||
export
|
||||
keccak.update, keccak.finish, hash
|
||||
import ./[addresses, hashes]
|
||||
|
||||
export hashes
|
||||
|
||||
from nimcrypto import MDigest
|
||||
|
||||
type
|
||||
KeccakHash* = MDigest[256]
|
||||
## A hash value computed using keccak256
|
||||
## note: this aliases Eth2Digest too, which uses a different hash!
|
||||
Hash256* {.deprecated.} = Hash32
|
||||
KeccakHash* {.deprecated.} = Hash32
|
||||
|
||||
template withKeccakHash*(body: untyped): KeccakHash =
|
||||
## This little helper will init the hash function and return the sliced
|
||||
## hash:
|
||||
## let hashOfData = withHash: h.update(data)
|
||||
block:
|
||||
var h {.inject.}: keccak256
|
||||
# init(h) # not needed for new instance
|
||||
body
|
||||
finish(h)
|
||||
template keccakHash*(v: openArray[byte]): Hash32 {.deprecated.} =
|
||||
keccak256(v)
|
||||
|
||||
func keccakHash*(input: openArray[byte]): KeccakHash {.noinit.} =
|
||||
# We use the init-update-finish interface to avoid
|
||||
# the expensive burning/clearing memory (20~30% perf)
|
||||
var ctx: keccak256
|
||||
ctx.update(input)
|
||||
ctx.finish()
|
||||
template keccakHash*(v: Address): Hash32 {.deprecated.} =
|
||||
keccak256(v.data)
|
||||
|
||||
func keccakHash*(input: openArray[char]): KeccakHash {.noinit.} =
|
||||
keccakHash(input.toOpenArrayByte(0, input.high()))
|
||||
from nimcrypto/hash import MDigest
|
||||
|
||||
func keccakHash*(a, b: openArray[byte]): KeccakHash =
|
||||
withKeccakHash:
|
||||
h.update a
|
||||
h.update b
|
||||
converter toMDigest*(v: Hash32): MDigest[256] {.deprecated.} =
|
||||
MDigest[256](data: v.data)
|
||||
|
|
|
@ -10,301 +10,41 @@
|
|||
## from many places
|
||||
|
||||
import
|
||||
std/[hashes, strutils],
|
||||
stew/[byteutils, endians2],
|
||||
stint,
|
||||
results,
|
||||
./eth_hash,
|
||||
./eth_times
|
||||
stew/byteutils,
|
||||
std/strutils,
|
||||
"."/
|
||||
[accounts, addresses, base, blocks, hashes, headers, receipts, times, transactions]
|
||||
|
||||
export
|
||||
results,
|
||||
stint,
|
||||
eth_hash,
|
||||
eth_times
|
||||
export accounts, addresses, base, blocks, hashes, headers, receipts, times, transactions
|
||||
|
||||
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
|
||||
case isHash*: bool
|
||||
of true:
|
||||
hash*: Hash256
|
||||
hash*: Hash32
|
||||
else:
|
||||
number*: uint64
|
||||
number*: BlockNumber
|
||||
|
||||
ValidationResult* {.pure.} = enum
|
||||
OK
|
||||
Error
|
||||
|
||||
const
|
||||
LegacyReceipt* = TxLegacy
|
||||
Eip2930Receipt* = TxEip2930
|
||||
Eip1559Receipt* = TxEip1559
|
||||
Eip4844Receipt* = TxEip4844
|
||||
Eip7702Receipt* = TxEip7702
|
||||
|
||||
EMPTY_ROOT_HASH* = "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".toDigest
|
||||
EMPTY_UNCLE_HASH* = "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347".toDigest
|
||||
EMPTY_CODE_HASH* = "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470".toDigest
|
||||
|
||||
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
|
||||
# Convenience names for types that exist in multiple specs and therefore
|
||||
# frequently conflict, name-wise.
|
||||
# These names are intended to be used in "boundary" code that translates
|
||||
# between types (consensus/json-rpc/rest/etc) while other code should use
|
||||
# native names within their domain
|
||||
EthAccount* = Account
|
||||
EthAddress* = Address
|
||||
EthBlock* = Block
|
||||
EthConsolidationRequest* = ConsolidationRequest
|
||||
EthDepositRequest* = DepositRequest
|
||||
EthHash32* = Hash32
|
||||
EthHeader* = Header
|
||||
EthTransaction* = Transaction
|
||||
EthReceipt* = Receipt
|
||||
EthWithdrawapRequest* = WithdrawalRequest
|
||||
|
||||
template contractCreation*(tx: Transaction): bool =
|
||||
tx.to.isNone
|
||||
|
||||
func destination*(tx: Transaction): EthAddress =
|
||||
# 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].} =
|
||||
func init*(T: type BlockHashOrNumber, str: string): T {.raises: [ValueError].} =
|
||||
if str.startsWith "0x":
|
||||
if str.len != sizeof(default(T).hash.data) * 2 + 2:
|
||||
raise newException(ValueError, "Block hash has incorrect length")
|
||||
|
@ -321,51 +61,25 @@ func `$`*(x: BlockHashOrNumber): string =
|
|||
else:
|
||||
$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
|
||||
template deref*(o: Opt): auto = o.get
|
||||
import ./eth_hash
|
||||
export eth_hash
|
||||
|
||||
func `==`*(a, b: NetworkId): bool =
|
||||
a.uint == b.uint
|
||||
type
|
||||
# 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 =
|
||||
`$`(uint(x))
|
||||
func toBlockNonce*(n: uint64): BlockNonce {.deprecated.} =
|
||||
n.to(BlockNonce)
|
||||
|
||||
func `==`*(a, b: EthAddress): bool {.inline.} =
|
||||
equalMem(unsafeAddr a[0], unsafeAddr b[0], a.len)
|
||||
|
||||
# TODO https://github.com/nim-lang/Nim/issues/23678
|
||||
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
|
||||
func newAccount*(
|
||||
nonce: AccountNonce = 0, balance: UInt256 = 0.u256
|
||||
): Account {.deprecated: "Account.init".} =
|
||||
Account.init(nonce = nonce, balance = balance)
|
||||
|
|
|
@ -6,13 +6,9 @@
|
|||
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/[times, net],
|
||||
json_serialization, nimcrypto/[hash, utils],
|
||||
./eth_types
|
||||
import std/[times, net], json_serialization, nimcrypto/[hash, utils], ./eth_types
|
||||
|
||||
export
|
||||
json_serialization
|
||||
export json_serialization
|
||||
|
||||
proc writeValue*(w: var JsonWriter, a: MDigest) {.raises: [IOError].} =
|
||||
w.writeValue a.data.toHex(true)
|
||||
|
@ -25,8 +21,29 @@ proc readValue*(
|
|||
except ValueError:
|
||||
raiseUnexpectedValue(r, "Hex string expected")
|
||||
|
||||
proc writeValue*(
|
||||
w: var JsonWriter, value: StUint) {.inline, raises: [IOError].} =
|
||||
proc writeValue*(w: var JsonWriter, a: Hash32) {.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
|
||||
|
||||
proc readValue*(
|
||||
|
@ -48,20 +65,7 @@ proc readValue*(
|
|||
) {.inline, raises: [IOError, SerializationError].} =
|
||||
t = fromUnix r.readValue(int)
|
||||
|
||||
# TODO: remove this once case object are fully supported
|
||||
# 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].} =
|
||||
proc writeValue*(w: var JsonWriter, value: BlockHashOrNumber) {.raises: [IOError].} =
|
||||
w.writeValue $value
|
||||
|
||||
proc readValue*(
|
||||
|
@ -70,4 +74,6 @@ proc readValue*(
|
|||
try:
|
||||
value = init(BlockHashOrNumber, r.readValue(string))
|
||||
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.
|
||||
|
||||
import
|
||||
"."/[eth_types, eth_hash_rlp],
|
||||
"."/[
|
||||
accounts_rlp, addresses_rlp, base_rlp, blocks_rlp, eth_types, hashes_rlp,
|
||||
receipts_rlp, transactions_rlp,
|
||||
],
|
||||
../rlp
|
||||
|
||||
from stew/objects
|
||||
import checkedEnumAssign
|
||||
|
||||
export
|
||||
eth_types, eth_hash_rlp, rlp
|
||||
accounts_rlp, addresses_rlp, base_rlp, blocks_rlp, eth_types, hashes_rlp,
|
||||
receipts_rlp, transactions_rlp, rlp
|
||||
|
||||
#
|
||||
# Rlp serialization:
|
||||
#
|
||||
proc read*(rlp: var Rlp, T: type EthTime): T {.raises: [RlpError].} =
|
||||
EthTime rlp.read(uint64)
|
||||
|
||||
proc read*(rlp: var Rlp, T: type StUint): T {.inline.} =
|
||||
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) =
|
||||
proc append*(rlpWriter: var RlpWriter, value: BlockHashOrNumber) =
|
||||
case value.isHash
|
||||
of true:
|
||||
rlpWriter.append(value.hash)
|
||||
else:
|
||||
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:
|
||||
result = HashOrNum(isHash: true, hash: rlp.read(Hash256))
|
||||
BlockHashOrNumber(isHash: true, hash: rlp.read(Hash32))
|
||||
else:
|
||||
result = HashOrNum(isHash: false, number: rlp.read(BlockNumber))
|
||||
BlockHashOrNumber(isHash: false, number: rlp.read(BlockNumber))
|
||||
|
||||
proc append*(rlpWriter: var RlpWriter, t: EthTime) {.inline.} =
|
||||
rlpWriter.append(t.uint64)
|
||||
|
||||
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 rlpHash*[T](v: T): Hash32 =
|
||||
keccak256(rlp.encode(v))
|
||||
|
||||
proc read*(rlp: var Rlp, T: type DepositRequest): T =
|
||||
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)
|
||||
proc rlpHash*(tx: PooledTransaction): Hash32 =
|
||||
keccak256(rlp.encode(tx.tx))
|
||||
|
||||
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 =
|
||||
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
|
||||
func blockHash*(h: Header): Hash32 {.inline.} =
|
||||
rlpHash(h)
|
||||
|
|
|
@ -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
|
||||
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
||||
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
./eth_hash,
|
||||
../rlp
|
||||
{.push raises: [].}
|
||||
|
||||
export eth_hash, rlp
|
||||
import "."/hashes, ../rlp
|
||||
|
||||
proc read*(rlp: var Rlp, T: typedesc[MDigest]): T =
|
||||
result.data = rlp.read(type(result.data))
|
||||
export hashes, rlp
|
||||
|
||||
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)
|
|
@ -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
|
||||
# except according to those terms.
|
||||
|
||||
import
|
||||
std/times
|
||||
from std/times import getTime, toUnix
|
||||
|
||||
type
|
||||
EthTime* = distinct uint64
|
||||
|
||||
proc now*(_: type EthTime): EthTime =
|
||||
getTime().utc.toTime.toUnix.EthTime
|
||||
getTime().toUnix.EthTime
|
||||
|
||||
func `+`*(a: EthTime, b: EthTime): EthTime =
|
||||
EthTime(a.uint64 + b.uint64)
|
|
@ -1,115 +1,3 @@
|
|||
import
|
||||
./eth_types_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))
|
||||
{.deprecated: "transactions_rlp".}
|
||||
import transactions_rlp
|
||||
export transactions_rlp
|
|
@ -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],
|
||||
nimcrypto/[bcmode, hmac, rijndael, pbkdf2, sha2, sysrand, utils, keccak, scrypt],
|
||||
results,
|
||||
../keys,
|
||||
".."/common/[addresses, keys],
|
||||
./uuid
|
||||
|
||||
export results
|
||||
|
@ -170,7 +170,7 @@ proc deriveKey(password: string,
|
|||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
ok(output)
|
||||
of HashKECCAK256:
|
||||
var ctx: HMAC[keccak256]
|
||||
var ctx: HMAC[keccak.keccak256]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
ok(output)
|
||||
of HashKECCAK384:
|
||||
|
@ -345,7 +345,7 @@ proc createKeyFileJson*(seckey: PrivateKey,
|
|||
|
||||
ok(%*
|
||||
{
|
||||
"address": seckey.toPublicKey().toAddress(false),
|
||||
"address": seckey.toPublicKey().to(Address).toHex(),
|
||||
"crypto": {
|
||||
"cipher": $cryptkind,
|
||||
"cipherparams": {
|
||||
|
|
287
eth/keys.nim
287
eth/keys.nim
|
@ -1,285 +1,4 @@
|
|||
# Nim Ethereum Keys
|
||||
# Copyright (c) 2020-2024 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# - Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||
# - MIT license (LICENSE-MIT)
|
||||
#
|
||||
{.deprecated.}
|
||||
|
||||
# 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
|
||||
|
||||
{.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)
|
||||
import ./common/keys
|
||||
export keys
|
||||
|
|
|
@ -12,7 +12,7 @@ import
|
|||
std/[os, strutils, times],
|
||||
results, nat_traversal/[miniupnpc, natpmp],
|
||||
chronicles, json_serialization/std/net, chronos,
|
||||
../common/utils, ./utils as netutils
|
||||
./utils as netutils
|
||||
|
||||
export results
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
import
|
||||
std/[tables, algorithm, random, typetraits, strutils, net],
|
||||
chronos, chronos/timer, chronicles,
|
||||
./keys, ./p2p/private/p2p_types,
|
||||
./common/keys, ./p2p/private/p2p_types,
|
||||
./p2p/[kademlia, discovery, enode, peer_pool, rlpx]
|
||||
|
||||
export
|
||||
|
|
|
@ -16,11 +16,14 @@ import
|
|||
nimcrypto/[rijndael, keccak, utils],
|
||||
stew/[arrayops, byteutils, endians2, objects],
|
||||
results,
|
||||
".."/[keys, rlp],
|
||||
../rlp,
|
||||
../common/keys,
|
||||
./ecies
|
||||
|
||||
export results
|
||||
|
||||
type keccak256 = keccak.keccak256
|
||||
|
||||
const
|
||||
SupportedRlpxVersion* = 4'u8
|
||||
|
||||
|
|
|
@ -11,7 +11,8 @@ import
|
|||
std/[times, net],
|
||||
chronos, stint, nimcrypto/keccak, chronicles,
|
||||
stew/objects, results,
|
||||
".."/[keys, rlp],
|
||||
../rlp,
|
||||
../common/keys,
|
||||
"."/[kademlia, enode]
|
||||
|
||||
export
|
||||
|
@ -43,6 +44,8 @@ type
|
|||
|
||||
DiscResult*[T] = Result[T, cstring]
|
||||
|
||||
keccak256 = keccak.keccak256
|
||||
|
||||
const MinListLen: array[CommandId, int] = [4, 3, 2, 2, 1, 2]
|
||||
|
||||
proc append*(w: var RlpWriter, a: IpAddress) =
|
||||
|
|
|
@ -18,7 +18,8 @@ import
|
|||
nimcrypto/[bcmode, rijndael, sha2], stint, chronicles,
|
||||
stew/[byteutils, endians2], metrics,
|
||||
results,
|
||||
".."/../[rlp, keys],
|
||||
../../rlp,
|
||||
../../common/keys,
|
||||
"."/[messages_encoding, node, enr, hkdf, sessions]
|
||||
|
||||
from stew/objects import checkedEnumAssign
|
||||
|
|
|
@ -16,7 +16,8 @@ import
|
|||
stew/base64,
|
||||
results,
|
||||
chronicles,
|
||||
".."/../[rlp, keys],
|
||||
../../rlp,
|
||||
../../common/keys,
|
||||
../../net/utils
|
||||
|
||||
export results, rlp, keys
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
import
|
||||
std/[hashes, net],
|
||||
nimcrypto/keccak, stint, chronos, chronicles, results,
|
||||
../../keys, ../../net/utils,
|
||||
../../common/keys, ../../net/utils,
|
||||
./enr
|
||||
|
||||
export stint, results
|
||||
|
|
|
@ -84,7 +84,8 @@ import
|
|||
std/[tables, sets, math, sequtils, algorithm],
|
||||
json_serialization/std/net,
|
||||
results, chronicles, chronos, stint, metrics,
|
||||
".."/../[rlp, keys],
|
||||
../../rlp,
|
||||
../../common/keys,
|
||||
"."/[messages_encoding, encoding, node, routing_table, enr, random2, sessions,
|
||||
ip_vote, nodes_verification]
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
import
|
||||
stew/endians2, results,
|
||||
nimcrypto/[rijndael, bcmode, hash, hmac, sha2, utils],
|
||||
../keys
|
||||
../common/keys
|
||||
|
||||
export results
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
import
|
||||
std/[uri, strutils, net],
|
||||
pkg/chronicles,
|
||||
../keys
|
||||
../common/keys
|
||||
|
||||
export keys
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
import
|
||||
std/[tables, hashes, times, algorithm, sets, sequtils],
|
||||
chronos, chronicles, stint, nimcrypto/keccak, metrics,
|
||||
../keys, ./discoveryv5/random2,
|
||||
../common/keys, ./discoveryv5/random2,
|
||||
./enode
|
||||
|
||||
export sets # TODO: This should not be needed, but compilation fails otherwise
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
import
|
||||
std/[os, tables, times, random, sequtils, options],
|
||||
chronos, chronicles,
|
||||
".."/[keys, common],
|
||||
./private/p2p_types, "."/[discovery, kademlia, rlpx, enode]
|
||||
|
||||
logScope:
|
||||
|
|
|
@ -14,10 +14,10 @@ import
|
|||
std/[deques, tables],
|
||||
chronos,
|
||||
results,
|
||||
".."/../[rlp, keys], ../../common/eth_types,
|
||||
".."/../[rlp], ../../common/[base, keys],
|
||||
".."/[enode, kademlia, discovery, rlpxcrypt]
|
||||
|
||||
export eth_types.NetworkId
|
||||
export base.NetworkId
|
||||
|
||||
const
|
||||
useSnappy* = defined(useSnappy)
|
||||
|
@ -212,6 +212,8 @@ type
|
|||
MessageTimeout = 0x0B,
|
||||
SubprotocolReason = 0x10
|
||||
|
||||
Address = enode.Address
|
||||
|
||||
proc `$`*(peer: Peer): string = $peer.remote
|
||||
|
||||
proc toENode*(v: EthereumNode): ENode =
|
||||
|
|
|
@ -25,9 +25,9 @@
|
|||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/[tables, algorithm, deques, hashes, options, typetraits, os],
|
||||
std/[algorithm, deques, options, typetraits, os],
|
||||
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]
|
||||
|
||||
# TODO: This doesn't get enabled currently in any of the builds, so we send a
|
||||
|
@ -76,6 +76,8 @@ type
|
|||
DisconnectionReasonList = object
|
||||
value: DisconnectionReason
|
||||
|
||||
Address = enode.Address
|
||||
|
||||
proc read(rlp: var Rlp; T: type DisconnectionReasonList): T
|
||||
{.gcsafe, raises: [RlpError].} =
|
||||
## Rlp mixin: `DisconnectionReasonList` parser
|
||||
|
|
|
@ -70,7 +70,7 @@ proc get*(self: BinaryTrie, key: openArray[byte]): seq[byte] {.inline.} =
|
|||
return self.getAux(self.rootHash, keyBits)
|
||||
|
||||
proc hashAndSave*(self: BinaryTrie, node: openArray[byte]): TrieNodeKey =
|
||||
result = @(keccakHash(node).data)
|
||||
result = @(keccak256(node).data)
|
||||
self.db.put(result, node)
|
||||
|
||||
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()
|
||||
for node in branch:
|
||||
doAssert(node.len != 0)
|
||||
let nodeHash = keccakHash(node)
|
||||
let nodeHash = keccak256(node)
|
||||
db.put(nodeHash.data, node)
|
||||
|
||||
var trie = initBinaryTrie(db, rootHash)
|
||||
|
|
|
@ -13,7 +13,7 @@ import
|
|||
|
||||
type
|
||||
TrieNodeKey = object
|
||||
hash: KeccakHash
|
||||
hash: Hash32
|
||||
usedBytes: uint8
|
||||
|
||||
DB = TrieDatabaseRef
|
||||
|
@ -44,16 +44,16 @@ proc dbPut(db: DB, data: openArray[byte]): TrieNodeKey
|
|||
template get(db: DB, key: Rlp): seq[byte] =
|
||||
db.get(key.expectHash)
|
||||
|
||||
converter toTrieNodeKey(hash: KeccakHash): TrieNodeKey =
|
||||
converter toTrieNodeKey(hash: Hash32): TrieNodeKey =
|
||||
result.hash = hash
|
||||
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.root = rootHash
|
||||
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)
|
||||
|
||||
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 =
|
||||
SecureHexaryTrie initHexaryTrie(db, isPruning)
|
||||
|
||||
proc rootHash*(t: HexaryTrie): KeccakHash =
|
||||
proc rootHash*(t: HexaryTrie): Hash32 =
|
||||
t.root.hash
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
{.raises: [].} =
|
||||
result.hash = data.keccakHash
|
||||
result.hash = data.keccak256
|
||||
result.usedBytes = 32
|
||||
put(db, result.asDbKey, data)
|
||||
|
||||
|
@ -545,7 +545,7 @@ proc del*(self: var HexaryTrie; key: openArray[byte]) =
|
|||
self.prune(self.root.asDbKey)
|
||||
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],
|
||||
isInline = false): seq[byte]
|
||||
{.gcsafe, raises: [RlpError].}
|
||||
|
@ -553,7 +553,7 @@ proc mergeAt(self: var HexaryTrie, orig: Rlp, origHash: KeccakHash,
|
|||
proc mergeAt(self: var HexaryTrie, rlp: Rlp,
|
||||
key: NibblesSeq, value: openArray[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,
|
||||
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)
|
||||
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],
|
||||
isInline = false): seq[byte]
|
||||
{.gcsafe, raises: [RlpError].} =
|
||||
|
@ -672,15 +672,15 @@ proc put*(self: var HexaryTrie; key, value: openArray[byte]) =
|
|||
self.root = self.db.dbPut(newRootBytes)
|
||||
|
||||
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] =
|
||||
return get(HexaryTrie(self), key.keccakHash.data)
|
||||
return get(HexaryTrie(self), key.keccak256.data)
|
||||
|
||||
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 isPruning*(self: SecureHexaryTrie): bool {.borrow.}
|
||||
|
||||
|
@ -689,14 +689,14 @@ template contains*(self: HexaryTrie | SecureHexaryTrie;
|
|||
self.get(key).len > 0
|
||||
|
||||
# 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
|
||||
doAssert(branch.len != 0)
|
||||
|
||||
var db = newMemoryDB()
|
||||
for node in branch:
|
||||
doAssert(node.len != 0)
|
||||
let nodeHash = keccakHash(node)
|
||||
let nodeHash = keccak256(node)
|
||||
db.put(nodeHash.data, node)
|
||||
|
||||
var trie = initHexaryTrie(db, rootHash)
|
||||
|
|
|
@ -186,7 +186,7 @@ proc verifyProof(
|
|||
|
||||
proc verifyMptProof*(
|
||||
branch: seq[seq[byte]],
|
||||
rootHash: KeccakHash,
|
||||
rootHash: Hash32,
|
||||
key: seq[byte],
|
||||
value: seq[byte]): MptProofVerificationResult =
|
||||
## Verifies provided proof of inclusion (trie branch) against provided trie
|
||||
|
@ -215,7 +215,7 @@ proc verifyMptProof*(
|
|||
for node in branch:
|
||||
if len(node) == 0:
|
||||
return invalidProof("empty mpt node in proof")
|
||||
let nodeHash = keccakHash(node)
|
||||
let nodeHash = keccak256(node)
|
||||
db.put(nodeHash.data, node)
|
||||
|
||||
let
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import
|
||||
../common/eth_hash_rlp
|
||||
../common/hashes_rlp
|
||||
|
||||
export eth_hash_rlp
|
||||
export hashes_rlp
|
||||
|
||||
type
|
||||
TrieError* = object of CatchableError
|
||||
|
@ -17,6 +17,5 @@ type
|
|||
# will be a more appropriate response.
|
||||
|
||||
const
|
||||
blankStringHash* = "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470".toDigest
|
||||
emptyRlp* = @[128.byte]
|
||||
emptyRlpHash* = "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".toDigest
|
||||
emptyRlpHash* = hash32"56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
|
||||
|
|
|
@ -1,17 +1,10 @@
|
|||
import
|
||||
stew/byteutils,
|
||||
./trie_defs
|
||||
|
||||
export trie_defs
|
||||
|
||||
template checkValidHashZ*(x: untyped) =
|
||||
when x.type isnot KeccakHash:
|
||||
doAssert(x.len == 32 or x.len == 0)
|
||||
|
||||
template isZeroHash*(x: openArray[byte]): bool =
|
||||
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,
|
||||
../p2p/discoveryv5/[protocol, messages_encoding, encoding],
|
||||
./utp_router,
|
||||
../keys
|
||||
../common/keys
|
||||
|
||||
export utp_router, protocol, results
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import
|
|||
std/[tables, options, hashes, math],
|
||||
chronos, chronicles,
|
||||
./utp_router,
|
||||
../keys
|
||||
../common/keys
|
||||
|
||||
logScope:
|
||||
topics = "eth utp"
|
||||
|
|
|
@ -10,7 +10,7 @@ import
|
|||
std/tables,
|
||||
chronos, chronicles, metrics,
|
||||
results,
|
||||
../keys,
|
||||
../common/keys,
|
||||
./utp_socket,
|
||||
./packets
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
import
|
||||
./utp/all_utp_tests,
|
||||
./keyfile/all_tests,
|
||||
./keys/all_tests,
|
||||
./p2p/all_tests,
|
||||
./rlp/all_tests,
|
||||
./trie/all_tests,
|
||||
|
|
|
@ -9,8 +9,9 @@
|
|||
# according to those terms.
|
||||
|
||||
import
|
||||
test_eth_types,
|
||||
test_eth_types_rlp,
|
||||
test_common,
|
||||
test_eip4844,
|
||||
test_eip7702
|
||||
test_eip7702,
|
||||
test_eth_types,
|
||||
test_eth_types_rlp,
|
||||
test_keys
|
||||
|
|
|
@ -14,7 +14,7 @@ import
|
|||
|
||||
type
|
||||
EthHeader = object
|
||||
header: BlockHeader
|
||||
header: Header
|
||||
|
||||
proc loadFile(x: int) =
|
||||
let fileName = "tests" / "common" / "eip2718" / "acl_block_" & $x & ".json"
|
||||
|
@ -38,8 +38,7 @@ proc loadFile(x: int) =
|
|||
check blk1 == blk3
|
||||
check bytes1 == bytes3
|
||||
|
||||
proc suite1() =
|
||||
suite "RLP encoding":
|
||||
suite "RLP encoding":
|
||||
test "Receipt roundtrip":
|
||||
let a = Receipt(
|
||||
receiptType: LegacyReceipt,
|
||||
|
@ -86,7 +85,7 @@ proc suite1() =
|
|||
let a = Withdrawal(
|
||||
index: 1,
|
||||
validatorIndex: 2,
|
||||
address: EthAddress [
|
||||
address: Address [
|
||||
0.byte, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
|
||||
11, 12, 13, 14, 15, 16, 17, 18, 19],
|
||||
amount: 4)
|
||||
|
@ -95,18 +94,17 @@ proc suite1() =
|
|||
let aa = rlp.decode(abytes, Withdrawal)
|
||||
check aa == a
|
||||
|
||||
proc suite2() =
|
||||
suite "EIP-2718 transaction / receipt":
|
||||
suite "EIP-2718 transaction / receipt":
|
||||
for i in 0..<10:
|
||||
loadFile(i)
|
||||
|
||||
test "BlockHeader: rlp roundtrip EIP-1559 / EIP-4895 / EIP-4844":
|
||||
proc doTest(h: BlockHeader) =
|
||||
test "Header: rlp roundtrip EIP-1559 / EIP-4895 / EIP-4844":
|
||||
proc doTest(h: Header) =
|
||||
let xy = rlp.encode(h)
|
||||
let hh = rlp.decode(xy, BlockHeader)
|
||||
let hh = rlp.decode(xy, Header)
|
||||
check h == hh
|
||||
|
||||
var h: BlockHeader
|
||||
var h: Header
|
||||
doTest h
|
||||
|
||||
# EIP-1559
|
||||
|
@ -114,8 +112,7 @@ proc suite2() =
|
|||
doTest h
|
||||
|
||||
# EIP-4895
|
||||
h.withdrawalsRoot = Opt.some Hash256.fromHex(
|
||||
"0x7a64245f7f95164f6176d90bd4903dbdd3e5433d555dd1385e81787f9672c588")
|
||||
h.withdrawalsRoot = Opt.some hash32"0x7a64245f7f95164f6176d90bd4903dbdd3e5433d555dd1385e81787f9672c588"
|
||||
doTest h
|
||||
|
||||
# EIP-4844
|
||||
|
@ -199,6 +196,3 @@ proc suite2() =
|
|||
check receipt.receiptType == Eip1559Receipt
|
||||
let encoded = rlp.encode(receipt)
|
||||
check receiptsBytes == encoded
|
||||
|
||||
suite1()
|
||||
suite2()
|
||||
|
|
|
@ -16,11 +16,10 @@ import
|
|||
../../eth/rlp,
|
||||
../../eth/common/transaction
|
||||
|
||||
|
||||
const
|
||||
recipient = hexToByteArray[20]("095e7baea6a6c7c4c2dfeb977efac326af552d87")
|
||||
zeroG1 = hexToByteArray[48]("0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||
source = hexToByteArray[20]("0x0000000000000000000000000000000000000001")
|
||||
recipient = address"095e7baea6a6c7c4c2dfeb977efac326af552d87"
|
||||
zeroG1 = bytes48"0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
source = address"0x0000000000000000000000000000000000000001"
|
||||
storageKey= default(StorageKey)
|
||||
accesses = @[AccessPair(address: source, storageKeys: @[storageKey])]
|
||||
blob = default(NetworkBlob)
|
||||
|
@ -95,7 +94,7 @@ proc tx5(i: int): PooledTransaction =
|
|||
|
||||
proc tx6(i: int): PooledTransaction =
|
||||
const
|
||||
digest = "010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014".toDigest
|
||||
digest = bytes32"010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014"
|
||||
|
||||
PooledTransaction(
|
||||
tx: Transaction(
|
||||
|
@ -114,7 +113,7 @@ proc tx6(i: int): PooledTransaction =
|
|||
|
||||
proc tx7(i: int): PooledTransaction =
|
||||
const
|
||||
digest = "01624652859a6e98ffc1608e2af0147ca4e86e1ce27672d8d3f3c9d4ffd6ef7e".toDigest
|
||||
digest = bytes32"01624652859a6e98ffc1608e2af0147ca4e86e1ce27672d8d3f3c9d4ffd6ef7e"
|
||||
|
||||
PooledTransaction(
|
||||
tx: Transaction(
|
||||
|
@ -130,7 +129,7 @@ proc tx7(i: int): PooledTransaction =
|
|||
|
||||
proc tx8(i: int): PooledTransaction =
|
||||
const
|
||||
digest = "01624652859a6e98ffc1608e2af0147ca4e86e1ce27672d8d3f3c9d4ffd6ef7e".toDigest
|
||||
digest = bytes32"01624652859a6e98ffc1608e2af0147ca4e86e1ce27672d8d3f3c9d4ffd6ef7e"
|
||||
|
||||
PooledTransaction(
|
||||
tx: Transaction(
|
||||
|
|
|
@ -15,14 +15,12 @@ import
|
|||
unittest2,
|
||||
../../eth/common,
|
||||
../../eth/rlp,
|
||||
../../eth/common/transaction,
|
||||
../../eth/keys
|
||||
../../eth/common/[keys, transactions_rlp]
|
||||
|
||||
const
|
||||
recipient = hexToByteArray[20]("095e7baea6a6c7c4c2dfeb977efac326af552d87")
|
||||
zeroG1 = hexToByteArray[48]("0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||
source = hexToByteArray[20]("0x0000000000000000000000000000000000000001")
|
||||
storageKey= default(StorageKey)
|
||||
recipient = address"095e7baea6a6c7c4c2dfeb977efac326af552d87"
|
||||
source = address"0x0000000000000000000000000000000000000001"
|
||||
storageKey= default(Bytes32)
|
||||
accesses = @[AccessPair(address: source, storageKeys: @[storageKey])]
|
||||
abcdef = hexToSeqByte("abcdef")
|
||||
authList = @[Authorization(
|
||||
|
@ -99,9 +97,7 @@ suite "Transaction EIP-7702 tests":
|
|||
tx = tx0(2)
|
||||
|
||||
let
|
||||
privateKey = PrivateKey.fromHex(keyHex).valueOr:
|
||||
echo "ERROR: ", error
|
||||
quit(QuitFailure)
|
||||
privateKey = PrivateKey.fromHex(keyHex).expect("valid key")
|
||||
rlpTx = rlpEncode(tx)
|
||||
sig = sign(privateKey, rlpTx).toRaw
|
||||
|
||||
|
|
|
@ -2,11 +2,10 @@
|
|||
|
||||
import
|
||||
unittest2,
|
||||
nimcrypto/hash,
|
||||
std/strutils,
|
||||
serialization/testing/generic_suite,
|
||||
../../eth/common/[eth_types, eth_types_json_serialization],
|
||||
../../eth/common/eth_types_rlp,
|
||||
../../eth/rlp
|
||||
../../eth/common/eth_types_rlp
|
||||
|
||||
func `==`*(lhs, rhs: BlockHashOrNumber): bool =
|
||||
if lhs.isHash != rhs.isHash:
|
||||
|
@ -17,8 +16,8 @@ func `==`*(lhs, rhs: BlockHashOrNumber): bool =
|
|||
else:
|
||||
lhs.number == rhs.number
|
||||
|
||||
const
|
||||
testHash = Hash256.fromHex "0x7a64245f7f95164f6176d90bd4903dbdd3e5433d555dd1385e81787f9672c588"
|
||||
const testHash =
|
||||
hash32"0x7a64245f7f95164f6176d90bd4903dbdd3e5433d555dd1385e81787f9672c588"
|
||||
|
||||
suite "BlockHashOrNumber":
|
||||
test "construction":
|
||||
|
@ -49,7 +48,8 @@ suite "BlockHashOrNumber":
|
|||
echo "A longer hash should not produce the value ", x
|
||||
|
||||
test "serialization":
|
||||
let hash = Hash256.fromHex "0x7a64245f7f95164f6176d90bd4903dbdd3e5433d555dd1385e81787f9672c588"
|
||||
const hash =
|
||||
hash32"0x7a64245f7f95164f6176d90bd4903dbdd3e5433d555dd1385e81787f9672c588"
|
||||
|
||||
Json.roundtripTest BlockHashOrNumber(isHash: true, hash: hash),
|
||||
"\"0x7a64245f7f95164f6176d90bd4903dbdd3e5433d555dd1385e81787f9672c588\""
|
||||
|
@ -57,6 +57,7 @@ suite "BlockHashOrNumber":
|
|||
Json.roundtripTest BlockHashOrNumber(isHash: false, number: 1209231231),
|
||||
"\"1209231231\""
|
||||
|
||||
suite "Block encodings":
|
||||
test "EIP-4399 prevRandao field":
|
||||
var blk: BlockHeader
|
||||
blk.prevRandao = testHash
|
||||
|
@ -75,3 +76,56 @@ suite "BlockHashOrNumber":
|
|||
let dh = rlp.decode(rlpBytes, BlockHeader)
|
||||
check dh.parentBeaconBlockRoot.isSome
|
||||
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)
|
||||
|
||||
test "Header + none(baseFee) + some(withdrawalsRoot)":
|
||||
let h = BlockHeader(withdrawalsRoot: Opt.some(Hash256()))
|
||||
let h = BlockHeader(withdrawalsRoot: Opt.some(default(Hash32)))
|
||||
expect AssertionDefect:
|
||||
roundTrip(h)
|
||||
|
||||
test "Header + none(baseFee) + some(withdrawalsRoot) + " &
|
||||
"some(blobGasUsed) + some(excessBlobGas)":
|
||||
let h = BlockHeader(
|
||||
withdrawalsRoot: Opt.some(Hash256()),
|
||||
withdrawalsRoot: Opt.some(default(Hash32)),
|
||||
blobGasUsed: Opt.some(1'u64),
|
||||
excessBlobGas: Opt.some(1'u64)
|
||||
)
|
||||
|
@ -105,7 +105,7 @@ suite "BlockHeader roundtrip test":
|
|||
test "Header + some(baseFee) + some(withdrawalsRoot)":
|
||||
let h = BlockHeader(
|
||||
baseFeePerGas: Opt.some(2.u256),
|
||||
withdrawalsRoot: Opt.some(Hash256())
|
||||
withdrawalsRoot: Opt.some(default(Hash32))
|
||||
)
|
||||
roundTrip(h)
|
||||
|
||||
|
@ -113,7 +113,7 @@ suite "BlockHeader roundtrip test":
|
|||
"some(blobGasUsed) + some(excessBlobGas)":
|
||||
let h = BlockHeader(
|
||||
baseFeePerGas: Opt.some(2.u256),
|
||||
withdrawalsRoot: Opt.some(Hash256()),
|
||||
withdrawalsRoot: Opt.some(default(Hash32)),
|
||||
blobGasUsed: Opt.some(1'u64),
|
||||
excessBlobGas: Opt.some(1'u64)
|
||||
)
|
||||
|
@ -176,12 +176,12 @@ genTest(BlockBody)
|
|||
|
||||
type
|
||||
BlockHeaderOpt* = object
|
||||
parentHash*: Hash256
|
||||
ommersHash*: Hash256
|
||||
coinbase*: EthAddress
|
||||
stateRoot*: Hash256
|
||||
txRoot*: Hash256
|
||||
receiptRoot*: Hash256
|
||||
parentHash*: Hash32
|
||||
ommersHash*: Hash32
|
||||
coinbase*: Address
|
||||
stateRoot*: Hash32
|
||||
txRoot*: Hash32
|
||||
receiptRoot*: Hash32
|
||||
bloom*: BloomFilter
|
||||
difficulty*: DifficultyInt
|
||||
blockNumber*: BlockNumber
|
||||
|
@ -189,10 +189,10 @@ type
|
|||
gasUsed*: GasInt
|
||||
timestamp*: EthTime
|
||||
extraData*: Blob
|
||||
mixDigest*: Hash256
|
||||
mixDigest*: Hash32
|
||||
nonce*: BlockNonce
|
||||
fee*: Opt[UInt256]
|
||||
withdrawalsRoot*: Opt[Hash256]
|
||||
withdrawalsRoot*: Opt[Hash32]
|
||||
blobGasUsed*: Opt[GasInt]
|
||||
excessBlobGas*: Opt[GasInt]
|
||||
|
||||
|
@ -252,27 +252,27 @@ suite "EIP-7865 tests":
|
|||
Request(
|
||||
requestType: DepositRequestType,
|
||||
deposit: DepositRequest(
|
||||
pubkey : hexToByteArray[48]("0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
|
||||
withdrawalCredentials: hexToByteArray[32]("0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"),
|
||||
pubkey : bytes48"0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
withdrawalCredentials: bytes32"0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
|
||||
amount : 1,
|
||||
signature : hexToByteArray[96]("0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
|
||||
signature : bytes96"0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
index : 3,
|
||||
)
|
||||
),
|
||||
Request(
|
||||
requestType: WithdrawalRequestType,
|
||||
withdrawal: WithdrawalRequest(
|
||||
sourceAddress : hexToByteArray[20]("0xDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"),
|
||||
validatorPubkey: hexToByteArray[48]("0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
|
||||
sourceAddress : address"0xDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
|
||||
validatorPubkey: bytes48"0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
amount : 7,
|
||||
)
|
||||
),
|
||||
Request(
|
||||
requestType: ConsolidationRequestType,
|
||||
consolidation: ConsolidationRequest(
|
||||
sourceAddress: hexToByteArray[20]("0xEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"),
|
||||
sourcePubkey : hexToByteArray[48]("0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
|
||||
targetPubkey : hexToByteArray[48]("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"),
|
||||
sourceAddress: address"0xEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE",
|
||||
sourcePubkey : bytes48"0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
targetPubkey : bytes48"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
|
||||
)
|
||||
)
|
||||
]
|
||||
|
|
|
@ -11,19 +11,11 @@
|
|||
|
||||
import
|
||||
unittest2,
|
||||
nimcrypto/hash, nimcrypto/keccak, nimcrypto/utils, stew/byteutils,
|
||||
../../eth/keys
|
||||
nimcrypto/utils, stew/byteutils,
|
||||
../../eth/common/[addresses, keys, hashes]
|
||||
|
||||
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 rng = newRng()
|
||||
|
||||
|
@ -79,7 +71,7 @@ suite "ECC/ECDSA/ECDHE tests suite":
|
|||
|
||||
test "test_recover_from_signature_obj":
|
||||
var s = PrivateKey.fromHex(pkbytes)[]
|
||||
var mhash = keccak256.digest(message)
|
||||
var mhash = keccak256(message)
|
||||
var signature = s.sign(message)
|
||||
var p = recover(signature, SkMessage(mhash.data))[]
|
||||
check:
|
||||
|
@ -94,8 +86,8 @@ suite "ECC/ECDSA/ECDHE tests suite":
|
|||
test "test_to_canonical_address_from_public_key":
|
||||
var s = PrivateKey.fromHex(pkbytes)[]
|
||||
var chk = s.toPublicKey().toCanonicalAddress()
|
||||
var expect = fromHex(stripSpaces(address))
|
||||
check compare(chk, expect) == true
|
||||
var expect = Address.fromHex(stripSpaces(address))
|
||||
check chk == expect
|
||||
|
||||
test "test_to_checksum_address_from_public_key":
|
||||
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
|
||||
var expectm = """
|
||||
8ac7e464348b85d9fdfc0a81f2fdc0bbbb8ee5fb3840de6ed60ad9372e718977"""
|
||||
var s = PrivateKey.fromRaw(keccak256.digest("ecdhAgree").data)[]
|
||||
var s = PrivateKey.fromRaw(keccak256("ecdhAgree").data)[]
|
||||
var p = s.toPublicKey()
|
||||
let expect = fromHex(stripSpaces(expectm))
|
||||
let secret = ecdhSharedSecret(s, p)
|
||||
|
@ -191,7 +183,7 @@ suite "ECC/ECDSA/ECDHE tests suite":
|
|||
let expect = fromHex(stripSpaces(e0))
|
||||
let secret = ecdhSharedSecret(s, p)
|
||||
check:
|
||||
compare(expect, secret.data) == true
|
||||
expect == secret.data
|
||||
|
||||
test "ECDSA/cpp-ethereum crypto.cpp#L132":
|
||||
# ECDSA test vectors
|
||||
|
@ -205,15 +197,15 @@ suite "ECC/ECDSA/ECDHE tests suite":
|
|||
var check1 = fromHex(stripSpaces(signature))
|
||||
var check2 = fromHex(stripSpaces(pubkey))
|
||||
|
||||
var s = PrivateKey.fromRaw(keccak256.digest("sec").data)[]
|
||||
var m = keccak256.digest("msg")
|
||||
var s = PrivateKey.fromRaw(keccak256("sec").data)[]
|
||||
var m = keccak256("msg")
|
||||
var sig = sign(s, SkMessage(m.data))
|
||||
var sersig = sig.toRaw()
|
||||
var key = recover(sig, SkMessage(m.data))[]
|
||||
var serkey = key.toRaw()
|
||||
check:
|
||||
compare(sersig, check1) == true
|
||||
compare(serkey, check2) == true
|
||||
sersig == check1
|
||||
serkey == check2
|
||||
|
||||
test "ECDSA/100 signatures":
|
||||
# signature test
|
|
@ -12,7 +12,7 @@
|
|||
import
|
||||
std/[json, os],
|
||||
unittest2,
|
||||
../../eth/keys, ../../eth/keyfile/[keyfile]
|
||||
../../eth/common/keys, ../../eth/keyfile/[keyfile]
|
||||
|
||||
# Test vectors copied from
|
||||
# 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
|
||||
std/net,
|
||||
chronos,
|
||||
../../eth/keys,
|
||||
../../eth/p2p/discoveryv5/[enr, node, routing_table],
|
||||
../../eth/p2p/discoveryv5/protocol as discv5_protocol
|
||||
|
||||
|
|
|
@ -20,8 +20,8 @@ p2pProtocol eth(version = 63,
|
|||
discard await peer.status(63,
|
||||
network.networkId,
|
||||
0.u256,
|
||||
Hash256(),
|
||||
Hash256(),
|
||||
default(Hash32),
|
||||
default(Hash32),
|
||||
timeout = chronos.seconds(10))
|
||||
|
||||
handshake:
|
||||
|
@ -29,26 +29,26 @@ p2pProtocol eth(version = 63,
|
|||
protocolVersion: uint,
|
||||
networkId: NetworkId,
|
||||
totalDifficulty: DifficultyInt,
|
||||
bestHash: KeccakHash,
|
||||
genesisHash: KeccakHash)
|
||||
bestHash: Hash32,
|
||||
genesisHash: Hash32)
|
||||
|
||||
requestResponse:
|
||||
proc getBlockHeaders(peer: Peer, request: openArray[KeccakHash]) {.gcsafe.} =
|
||||
var headers: seq[BlockHeader]
|
||||
proc getBlockHeaders(peer: Peer, request: openArray[Hash32]) {.gcsafe.} =
|
||||
var headers: seq[Header]
|
||||
await response.send(headers)
|
||||
|
||||
proc blockHeaders(p: Peer, headers: openArray[BlockHeader])
|
||||
proc blockHeaders(p: Peer, headers: openArray[Header])
|
||||
|
||||
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])
|
||||
|
||||
nextID 13
|
||||
|
||||
requestResponse:
|
||||
proc getNodeData(peer: Peer, hashes: openArray[KeccakHash]) = discard
|
||||
proc nodeData(peer: Peer, data: openArray[Blob])
|
||||
proc getNodeData(peer: Peer, hashes: openArray[Hash32]) = discard
|
||||
proc nodeData(peer: Peer, data: openArray[seq[byte]])
|
||||
|
||||
requestResponse:
|
||||
proc getReceipts(peer: Peer, hashes: openArray[KeccakHash]) = discard
|
||||
proc getReceipts(peer: Peer, hashes: openArray[Hash32]) = discard
|
||||
proc receipts(peer: Peer, receipts: openArray[Receipt])
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
import
|
||||
std/strutils,
|
||||
chronos,
|
||||
../../eth/[keys, p2p], ../../eth/p2p/[discovery, enode]
|
||||
../../eth/p2p, ../../eth/common/keys, ../../eth/p2p/[discovery, enode]
|
||||
|
||||
var nextPort = 30303
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
import
|
||||
unittest2,
|
||||
nimcrypto/[utils, keccak],
|
||||
../../eth/keys, ../../eth/p2p/auth
|
||||
../../eth/common/keys, ../../eth/p2p/auth
|
||||
|
||||
# This was generated by `print` actual auth message generated by
|
||||
# https://github.com/ethereum/py-evm/blob/master/tests/p2p/test_auth.py
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
import
|
||||
unittest2,
|
||||
nimcrypto/[utils, sysrand],
|
||||
../../eth/keys, ../../eth/p2p/[auth, rlpxcrypt]
|
||||
../../eth/common/keys, ../../eth/p2p/[auth, rlpxcrypt]
|
||||
|
||||
const data = [
|
||||
("initiator_private_key",
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
|
||||
import
|
||||
std/sequtils,
|
||||
chronos, stew/byteutils, nimcrypto, testutils/unittests,
|
||||
../../eth/keys, ../../eth/p2p/[discovery, kademlia, enode],
|
||||
chronos, stew/byteutils, nimcrypto/keccak, testutils/unittests,
|
||||
../../eth/common/keys, ../../eth/p2p/[discovery, kademlia, enode],
|
||||
../stubloglevel
|
||||
|
||||
proc localAddress(port: int): Address =
|
||||
|
|
|
@ -11,7 +11,7 @@ import
|
|||
std/[tables, sequtils, net],
|
||||
chronos, chronicles, stint, testutils/unittests,
|
||||
stew/byteutils,
|
||||
../../eth/keys,
|
||||
../../eth/common/keys,
|
||||
../../eth/p2p/discoveryv5/[enr, node, routing_table, encoding, sessions,
|
||||
messages, nodes_verification],
|
||||
../../eth/p2p/discoveryv5/protocol as discv5_protocol,
|
||||
|
|
|
@ -11,7 +11,6 @@ import
|
|||
std/[options, sequtils, tables, net],
|
||||
unittest2,
|
||||
stint, stew/byteutils,
|
||||
../../eth/keys,
|
||||
../../eth/p2p/discoveryv5/[messages_encoding, encoding, enr, node, sessions],
|
||||
../stubloglevel
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
import
|
||||
unittest2,
|
||||
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 =
|
||||
result = true
|
||||
|
|
|
@ -10,7 +10,7 @@ import
|
|||
std/[sequtils, net],
|
||||
stew/byteutils,
|
||||
unittest2,
|
||||
../../eth/p2p/discoveryv5/enr, ../../eth/[keys, rlp]
|
||||
../../eth/p2p/discoveryv5/enr, ../../eth/rlp, ../../eth/common/keys
|
||||
|
||||
let rng = newRng()
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
import
|
||||
std/net,
|
||||
unittest2,
|
||||
../../eth/keys, ../../eth/p2p/discoveryv5/[node, ip_vote]
|
||||
../../eth/common/keys, ../../eth/p2p/discoveryv5/[node, ip_vote]
|
||||
|
||||
suite "IP vote":
|
||||
let rng = newRng()
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import
|
||||
unittest2,
|
||||
../../eth/keys, ../../eth/p2p/discoveryv5/[routing_table, node, enr],
|
||||
../../eth/common/keys, ../../eth/p2p/discoveryv5/[routing_table, node, enr],
|
||||
./discv5_test_helper
|
||||
|
||||
func customDistance*(a, b: NodeId): UInt256 =
|
||||
|
|
|
@ -22,7 +22,7 @@ proc test_blockBodyTranscode() =
|
|||
Transaction(nonce: 1)]),
|
||||
BlockBody(
|
||||
uncles: @[
|
||||
BlockHeader(nonce: [0x20u8,0,0,0,0,0,0,0])]),
|
||||
BlockHeader(nonce: BlockNonce([0x20u8,0,0,0,0,0,0,0]))]),
|
||||
BlockBody(),
|
||||
BlockBody(
|
||||
transactions: @[
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import
|
||||
unittest2,
|
||||
stew/byteutils, nimcrypto/[keccak, hash],
|
||||
stew/byteutils,
|
||||
../../eth/trie/[db, binary, binaries, branches]
|
||||
|
||||
suite "examples":
|
||||
|
@ -77,7 +77,7 @@ suite "examples":
|
|||
check branchs.len < beforeDeleteLen
|
||||
|
||||
var node = branchs[1]
|
||||
let nodeHash = keccak256.digest(node)
|
||||
let nodeHash = keccak256(node)
|
||||
var nodes = getTrieNodes(db, @(nodeHash.data))
|
||||
check nodes.len == branchs.len - 1
|
||||
|
||||
|
|
|
@ -13,13 +13,11 @@ import
|
|||
std/sequtils,
|
||||
unittest2,
|
||||
stint,
|
||||
nimcrypto/hash,
|
||||
stew/byteutils,
|
||||
../../eth/trie/[hexary, db, trie_defs, hexary_proof_verification]
|
||||
|
||||
proc getKeyBytes(i: int): seq[byte] =
|
||||
let hash = keccakHash(u256(i).toBytesBE())
|
||||
return toSeq(hash.data)
|
||||
@(u256(i).toBytesBE())
|
||||
|
||||
suite "MPT trie proof verification":
|
||||
test "Validate proof for existing value":
|
||||
|
@ -53,7 +51,7 @@ suite "MPT trie proof verification":
|
|||
trie.put(bytes, bytes)
|
||||
|
||||
let
|
||||
nonExistingKey = toSeq(keccakHash(toBytesBE(u256(numValues + 1))).data)
|
||||
nonExistingKey = toSeq(toBytesBE(u256(numValues + 1)))
|
||||
proof = trie.getBranch(nonExistingKey)
|
||||
root = trie.rootHash()
|
||||
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)
|
||||
|
||||
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 == @[emptyRlp]
|
||||
res.kind == InvalidProof
|
||||
|
|
|
@ -260,7 +260,7 @@ suite "hexary trie":
|
|||
History = object
|
||||
keys: seq[seq[byte]]
|
||||
values: seq[seq[byte]]
|
||||
rootHash: KeccakHash
|
||||
rootHash: Hash32
|
||||
|
||||
const
|
||||
listLength = 30
|
||||
|
@ -334,7 +334,7 @@ suite "hexary trie":
|
|||
nonPruningTrie = initHexaryTrie(memdb, false)
|
||||
keys = randList(seq[byte], randGen(5, 77), 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:
|
||||
nonPruningTrie.put(keys[i], vals[i])
|
||||
|
|
|
@ -14,7 +14,6 @@ import
|
|||
../../eth/p2p/discoveryv5/[enr, node, routing_table],
|
||||
../../eth/p2p/discoveryv5/protocol as discv5_protocol,
|
||||
../../eth/utp/utp_discv5_protocol,
|
||||
../../eth/keys,
|
||||
../../eth/utp/utp_router as rt,
|
||||
../p2p/discv5_test_helper,
|
||||
../stubloglevel
|
||||
|
|
|
@ -9,8 +9,7 @@
|
|||
import
|
||||
std/options,
|
||||
unittest2,
|
||||
../../eth/utp/packets,
|
||||
../../eth/keys
|
||||
../../eth/utp/packets
|
||||
|
||||
suite "uTP packet encoding":
|
||||
test "Encode/decode SYN packet":
|
||||
|
|
|
@ -11,7 +11,7 @@ import
|
|||
chronos,
|
||||
testutils/unittests,
|
||||
./test_utils,
|
||||
../../eth/keys,
|
||||
../../eth/common/keys,
|
||||
../../eth/utp/[utp_router, utp_protocol],
|
||||
../stubloglevel
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import
|
|||
testutils/unittests,
|
||||
../../eth/utp/utp_router,
|
||||
../../eth/utp/utp_protocol,
|
||||
../../eth/keys,
|
||||
../../eth/common/keys,
|
||||
../../eth/p2p/discoveryv5/random2,
|
||||
../stubloglevel
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import
|
||||
chronos,
|
||||
../../eth/utp/utp_socket,
|
||||
../../eth/utp/packets,
|
||||
../../eth/keys
|
||||
../../eth/utp/packets
|
||||
|
||||
type AssertionCallback = proc(): bool {.gcsafe, raises: [].}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import
|
|||
./test_utils,
|
||||
../../eth/utp/utp_router,
|
||||
../../eth/utp/packets,
|
||||
../../eth/keys,
|
||||
../../eth/common/keys,
|
||||
../stubloglevel
|
||||
|
||||
proc hash*(x: UtpSocketKey[int]): Hash =
|
||||
|
|
|
@ -14,7 +14,7 @@ import
|
|||
../../eth/utp/utp_router,
|
||||
../../eth/utp/utp_socket,
|
||||
../../eth/utp/packets,
|
||||
../../eth/keys,
|
||||
../../eth/common/keys,
|
||||
../stubloglevel
|
||||
|
||||
procSuite "uTP socket":
|
||||
|
|
|
@ -15,7 +15,7 @@ import
|
|||
../../eth/utp/utp_router,
|
||||
../../eth/utp/utp_socket,
|
||||
../../eth/utp/packets,
|
||||
../../eth/keys,
|
||||
../../eth/common/keys,
|
||||
../stubloglevel
|
||||
|
||||
procSuite "uTP socket selective acks":
|
||||
|
|
|
@ -9,8 +9,7 @@
|
|||
import
|
||||
stew/byteutils,
|
||||
unittest2,
|
||||
../../eth/utp/packets,
|
||||
../../eth/keys
|
||||
../../eth/utp/packets
|
||||
|
||||
suite "uTP packets test vectors":
|
||||
test "SYN packet":
|
||||
|
|
Loading…
Reference in New Issue