diff --git a/doc/trie.md b/doc/trie.md index fddafd9..491358b 100644 --- a/doc/trie.md +++ b/doc/trie.md @@ -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. diff --git a/eth.nimble b/eth.nimble index 6272daa..c2d1acf 100644 --- a/eth.nimble +++ b/eth.nimble @@ -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() diff --git a/eth/bloom.nim b/eth/bloom.nim index 0976deb..c3e0d62 100644 --- a/eth/bloom.nim +++ b/eth/bloom.nim @@ -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) diff --git a/eth/common.nim b/eth/common.nim index 3cba37d..4a63461 100644 --- a/eth/common.nim +++ b/eth/common.nim @@ -1,2 +1,2 @@ -import ./common/[eth_types_rlp, utils] -export eth_types_rlp, utils +import ./common/eth_types_rlp +export eth_types_rlp diff --git a/eth/common/accounts.nim b/eth/common/accounts.nim new file mode 100644 index 0000000..439d3b0 --- /dev/null +++ b/eth/common/accounts.nim @@ -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() diff --git a/eth/common/accounts_rlp.nim b/eth/common/accounts_rlp.nim new file mode 100644 index 0000000..41a1e7e --- /dev/null +++ b/eth/common/accounts_rlp.nim @@ -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 diff --git a/eth/common/addresses.nim b/eth/common/addresses.nim new file mode 100644 index 0000000..db4f27d --- /dev/null +++ b/eth/common/addresses.nim @@ -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() diff --git a/eth/common/addresses_rlp.nim b/eth/common/addresses_rlp.nim new file mode 100644 index 0000000..3a888b8 --- /dev/null +++ b/eth/common/addresses_rlp.nim @@ -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()) diff --git a/eth/common/base.nim b/eth/common/base.nim new file mode 100644 index 0000000..57a805f --- /dev/null +++ b/eth/common/base.nim @@ -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.} diff --git a/eth/common/base_rlp.nim b/eth/common/base_rlp.nim new file mode 100644 index 0000000..d4c9f46 --- /dev/null +++ b/eth/common/base_rlp.nim @@ -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)) diff --git a/eth/common/blocks.nim b/eth/common/blocks.nim new file mode 100644 index 0000000..6d539f9 --- /dev/null +++ b/eth/common/blocks.nim @@ -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 diff --git a/eth/common/blocks_rlp.nim b/eth/common/blocks_rlp.nim new file mode 100644 index 0000000..3769768 --- /dev/null +++ b/eth/common/blocks_rlp.nim @@ -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 diff --git a/eth/common/eth_hash.nim b/eth/common/eth_hash.nim index 6387cf4..a94bcb3 100644 --- a/eth/common/eth_hash.nim +++ b/eth/common/eth_hash.nim @@ -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) diff --git a/eth/common/eth_types.nim b/eth/common/eth_types.nim index 6de6973..ab79b40 100644 --- a/eth/common/eth_types.nim +++ b/eth/common/eth_types.nim @@ -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) diff --git a/eth/common/eth_types_json_serialization.nim b/eth/common/eth_types_json_serialization.nim index 14a90ad..faf0b43 100644 --- a/eth/common/eth_types_json_serialization.nim +++ b/eth/common/eth_types_json_serialization.nim @@ -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" + ) diff --git a/eth/common/eth_types_rlp.nim b/eth/common/eth_types_rlp.nim index 0eab790..5a76c08 100644 --- a/eth/common/eth_types_rlp.nim +++ b/eth/common/eth_types_rlp.nim @@ -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) diff --git a/eth/common/hashes.nim b/eth/common/hashes.nim new file mode 100644 index 0000000..3013aee --- /dev/null +++ b/eth/common/hashes.nim @@ -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) diff --git a/eth/common/eth_hash_rlp.nim b/eth/common/hashes_rlp.nim similarity index 57% rename from eth/common/eth_hash_rlp.nim rename to eth/common/hashes_rlp.nim index 4fcfc04..b8b4d55 100644 --- a/eth/common/eth_hash_rlp.nim +++ b/eth/common/hashes_rlp.nim @@ -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) diff --git a/eth/common/headers.nim b/eth/common/headers.nim new file mode 100644 index 0000000..65e69cb --- /dev/null +++ b/eth/common/headers.nim @@ -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 diff --git a/eth/common/keys.nim b/eth/common/keys.nim new file mode 100644 index 0000000..a5937ab --- /dev/null +++ b/eth/common/keys.nim @@ -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) diff --git a/eth/common/receipts.nim b/eth/common/receipts.nim new file mode 100644 index 0000000..b9b22f0 --- /dev/null +++ b/eth/common/receipts.nim @@ -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 diff --git a/eth/common/receipts_rlp.nim b/eth/common/receipts_rlp.nim new file mode 100644 index 0000000..17e2415 --- /dev/null +++ b/eth/common/receipts_rlp.nim @@ -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)) diff --git a/eth/common/eth_times.nim b/eth/common/times.nim similarity index 94% rename from eth/common/eth_times.nim rename to eth/common/times.nim index fda8e9f..45b3c3f 100644 --- a/eth/common/eth_times.nim +++ b/eth/common/times.nim @@ -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) diff --git a/eth/common/transaction.nim b/eth/common/transaction.nim index ba113ba..c0e322e 100644 --- a/eth/common/transaction.nim +++ b/eth/common/transaction.nim @@ -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 \ No newline at end of file diff --git a/eth/common/transactions.nim b/eth/common/transactions.nim new file mode 100644 index 0000000..751f0d7 --- /dev/null +++ b/eth/common/transactions.nim @@ -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)) diff --git a/eth/common/transactions_rlp.nim b/eth/common/transactions_rlp.nim new file mode 100644 index 0000000..69d8f24 --- /dev/null +++ b/eth/common/transactions_rlp.nim @@ -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)) diff --git a/eth/common/utils.nim b/eth/common/utils.nim deleted file mode 100644 index 094aa80..0000000 --- a/eth/common/utils.nim +++ /dev/null @@ -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() diff --git a/eth/keyfile/keyfile.nim b/eth/keyfile/keyfile.nim index 756e0b9..bbe2b55 100644 --- a/eth/keyfile/keyfile.nim +++ b/eth/keyfile/keyfile.nim @@ -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": { diff --git a/eth/keys.nim b/eth/keys.nim index 617a8d6..747bd62 100644 --- a/eth/keys.nim +++ b/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..= '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..= '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 diff --git a/eth/net/nat.nim b/eth/net/nat.nim index d0f82eb..5ef1bce 100644 --- a/eth/net/nat.nim +++ b/eth/net/nat.nim @@ -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 diff --git a/eth/p2p.nim b/eth/p2p.nim index 68f817c..32647e9 100644 --- a/eth/p2p.nim +++ b/eth/p2p.nim @@ -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 diff --git a/eth/p2p/auth.nim b/eth/p2p/auth.nim index e063b9a..2a97255 100644 --- a/eth/p2p/auth.nim +++ b/eth/p2p/auth.nim @@ -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 diff --git a/eth/p2p/discovery.nim b/eth/p2p/discovery.nim index bfa163c..e089dfb 100644 --- a/eth/p2p/discovery.nim +++ b/eth/p2p/discovery.nim @@ -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) = diff --git a/eth/p2p/discoveryv5/encoding.nim b/eth/p2p/discoveryv5/encoding.nim index 8c0a5a1..f187633 100644 --- a/eth/p2p/discoveryv5/encoding.nim +++ b/eth/p2p/discoveryv5/encoding.nim @@ -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 diff --git a/eth/p2p/discoveryv5/enr.nim b/eth/p2p/discoveryv5/enr.nim index 8eec8ff..803dc91 100644 --- a/eth/p2p/discoveryv5/enr.nim +++ b/eth/p2p/discoveryv5/enr.nim @@ -16,7 +16,8 @@ import stew/base64, results, chronicles, - ".."/../[rlp, keys], + ../../rlp, + ../../common/keys, ../../net/utils export results, rlp, keys diff --git a/eth/p2p/discoveryv5/node.nim b/eth/p2p/discoveryv5/node.nim index 1b92d5f..c1667ce 100644 --- a/eth/p2p/discoveryv5/node.nim +++ b/eth/p2p/discoveryv5/node.nim @@ -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 diff --git a/eth/p2p/discoveryv5/protocol.nim b/eth/p2p/discoveryv5/protocol.nim index 5945853..f8b05ca 100644 --- a/eth/p2p/discoveryv5/protocol.nim +++ b/eth/p2p/discoveryv5/protocol.nim @@ -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] diff --git a/eth/p2p/ecies.nim b/eth/p2p/ecies.nim index 90464cc..122d05d 100644 --- a/eth/p2p/ecies.nim +++ b/eth/p2p/ecies.nim @@ -15,7 +15,7 @@ import stew/endians2, results, nimcrypto/[rijndael, bcmode, hash, hmac, sha2, utils], - ../keys + ../common/keys export results diff --git a/eth/p2p/enode.nim b/eth/p2p/enode.nim index 5d8d6df..3262afb 100644 --- a/eth/p2p/enode.nim +++ b/eth/p2p/enode.nim @@ -13,7 +13,7 @@ import std/[uri, strutils, net], pkg/chronicles, - ../keys + ../common/keys export keys diff --git a/eth/p2p/kademlia.nim b/eth/p2p/kademlia.nim index d000c3e..ad77f64 100644 --- a/eth/p2p/kademlia.nim +++ b/eth/p2p/kademlia.nim @@ -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 diff --git a/eth/p2p/peer_pool.nim b/eth/p2p/peer_pool.nim index 62d49c3..76a0574 100644 --- a/eth/p2p/peer_pool.nim +++ b/eth/p2p/peer_pool.nim @@ -13,7 +13,6 @@ import std/[os, tables, times, random, sequtils, options], chronos, chronicles, - ".."/[keys, common], ./private/p2p_types, "."/[discovery, kademlia, rlpx, enode] logScope: @@ -220,7 +219,7 @@ proc maybeConnectToMorePeers(p: PeerPool) {.async.} = else: for n in p.nodesToConnect(): await p.connQueue.addLast(n) - + # The old version of the code (which did all the connection # attempts in serial, not parallel) actually *awaited* all # the connection attempts before reaching the code at the @@ -235,7 +234,7 @@ proc maybeConnectToMorePeers(p: PeerPool) {.async.} = # # --Adam, Dec. 2022 await sleepAsync(sleepBeforeTryingARandomBootnode) - + # In some cases (e.g ROPSTEN or private testnets), the discovery table might # be full of bad peers, so if we can't connect to any peers we try a random # bootstrap node as well. diff --git a/eth/p2p/private/p2p_types.nim b/eth/p2p/private/p2p_types.nim index 4016ce3..17f9c33 100644 --- a/eth/p2p/private/p2p_types.nim +++ b/eth/p2p/private/p2p_types.nim @@ -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 = diff --git a/eth/p2p/rlpx.nim b/eth/p2p/rlpx.nim index 92c4226..62390b1 100644 --- a/eth/p2p/rlpx.nim +++ b/eth/p2p/rlpx.nim @@ -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 diff --git a/eth/trie/binary.nim b/eth/trie/binary.nim index 51e6007..0ee8c0f 100644 --- a/eth/trie/binary.nim +++ b/eth/trie/binary.nim @@ -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 = diff --git a/eth/trie/branches.nim b/eth/trie/branches.nim index 84a02e3..0abb2aa 100644 --- a/eth/trie/branches.nim +++ b/eth/trie/branches.nim @@ -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) diff --git a/eth/trie/hexary.nim b/eth/trie/hexary.nim index 2144111..85ff728 100644 --- a/eth/trie/hexary.nim +++ b/eth/trie/hexary.nim @@ -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) diff --git a/eth/trie/hexary_proof_verification.nim b/eth/trie/hexary_proof_verification.nim index bb03272..044953b 100644 --- a/eth/trie/hexary_proof_verification.nim +++ b/eth/trie/hexary_proof_verification.nim @@ -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 diff --git a/eth/trie/trie_defs.nim b/eth/trie/trie_defs.nim index df0c137..5c2530a 100644 --- a/eth/trie/trie_defs.nim +++ b/eth/trie/trie_defs.nim @@ -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" diff --git a/eth/trie/trie_utils.nim b/eth/trie/trie_utils.nim index e87a85a..42af63c 100644 --- a/eth/trie/trie_utils.nim +++ b/eth/trie/trie_utils.nim @@ -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) + 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) diff --git a/eth/utp/utp_discv5_protocol.nim b/eth/utp/utp_discv5_protocol.nim index e920920..d103135 100644 --- a/eth/utp/utp_discv5_protocol.nim +++ b/eth/utp/utp_discv5_protocol.nim @@ -12,7 +12,7 @@ import results, ../p2p/discoveryv5/[protocol, messages_encoding, encoding], ./utp_router, - ../keys + ../common/keys export utp_router, protocol, results diff --git a/eth/utp/utp_protocol.nim b/eth/utp/utp_protocol.nim index 20113e1..024b42a 100644 --- a/eth/utp/utp_protocol.nim +++ b/eth/utp/utp_protocol.nim @@ -10,7 +10,7 @@ import std/[tables, options, hashes, math], chronos, chronicles, ./utp_router, - ../keys + ../common/keys logScope: topics = "eth utp" diff --git a/eth/utp/utp_router.nim b/eth/utp/utp_router.nim index c2de158..893bf9e 100644 --- a/eth/utp/utp_router.nim +++ b/eth/utp/utp_router.nim @@ -10,7 +10,7 @@ import std/tables, chronos, chronicles, metrics, results, - ../keys, + ../common/keys, ./utp_socket, ./packets diff --git a/tests/all_tests.nim b/tests/all_tests.nim index 67569b2..7e8967e 100644 --- a/tests/all_tests.nim +++ b/tests/all_tests.nim @@ -13,10 +13,9 @@ import ./utp/all_utp_tests, ./keyfile/all_tests, - ./keys/all_tests, ./p2p/all_tests, ./rlp/all_tests, ./trie/all_tests, - ./db/all_tests, + ./db/all_tests, ./common/all_tests, ./test_bloom diff --git a/tests/common/all_tests.nim b/tests/common/all_tests.nim index f13315b..fccbf74 100644 --- a/tests/common/all_tests.nim +++ b/tests/common/all_tests.nim @@ -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 diff --git a/tests/common/test_common.nim b/tests/common/test_common.nim index b1e4b14..22d190c 100644 --- a/tests/common/test_common.nim +++ b/tests/common/test_common.nim @@ -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,167 +38,161 @@ proc loadFile(x: int) = check blk1 == blk3 check bytes1 == bytes3 -proc suite1() = - suite "RLP encoding": - test "Receipt roundtrip": - let a = Receipt( - receiptType: LegacyReceipt, - isHash: false, - status: false, - cumulativeGasUsed: 51000 - ) +suite "RLP encoding": + test "Receipt roundtrip": + let a = Receipt( + receiptType: LegacyReceipt, + isHash: false, + status: false, + cumulativeGasUsed: 51000 + ) - let hash = rlpHash(a) - let b = Receipt( - receiptType: LegacyReceipt, - isHash: true, - hash: hash, - cumulativeGasUsed: 21000 - ) + let hash = rlpHash(a) + let b = Receipt( + receiptType: LegacyReceipt, + isHash: true, + hash: hash, + cumulativeGasUsed: 21000 + ) - let abytes = rlp.encode(a) - let bbytes = rlp.encode(b) - let aa = rlp.decode(abytes, Receipt) - let bb = rlp.decode(bbytes, Receipt) - check aa == a - check bb == b + let abytes = rlp.encode(a) + let bbytes = rlp.encode(b) + let aa = rlp.decode(abytes, Receipt) + let bb = rlp.decode(bbytes, Receipt) + check aa == a + check bb == b - test "EIP-2930 receipt": - let a = Receipt( - receiptType: Eip2930Receipt, - status: true - ) + test "EIP-2930 receipt": + let a = Receipt( + receiptType: Eip2930Receipt, + status: true + ) - let b = Receipt( - receiptType: Eip2930Receipt, - status: false, - cumulativeGasUsed: 21000 - ) + let b = Receipt( + receiptType: Eip2930Receipt, + status: false, + cumulativeGasUsed: 21000 + ) - let abytes = rlp.encode(a) - let bbytes = rlp.encode(b) - let aa = rlp.decode(abytes, Receipt) - let bb = rlp.decode(bbytes, Receipt) - check aa == a - check bb == b + let abytes = rlp.encode(a) + let bbytes = rlp.encode(b) + let aa = rlp.decode(abytes, Receipt) + let bb = rlp.decode(bbytes, Receipt) + check aa == a + check bb == b - test "EIP-4895 roundtrip": - let a = Withdrawal( - index: 1, - validatorIndex: 2, - address: EthAddress [ - 0.byte, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, - 11, 12, 13, 14, 15, 16, 17, 18, 19], - amount: 4) + test "EIP-4895 roundtrip": + let a = Withdrawal( + index: 1, + validatorIndex: 2, + 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) - let abytes = rlp.encode(a) - let aa = rlp.decode(abytes, Withdrawal) - check aa == a + let abytes = rlp.encode(a) + let aa = rlp.decode(abytes, Withdrawal) + check aa == a -proc suite2() = - suite "EIP-2718 transaction / receipt": - for i in 0..<10: - loadFile(i) +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) = - let xy = rlp.encode(h) - let hh = rlp.decode(xy, BlockHeader) - check h == hh + test "Header: rlp roundtrip EIP-1559 / EIP-4895 / EIP-4844": + proc doTest(h: Header) = + let xy = rlp.encode(h) + let hh = rlp.decode(xy, Header) + check h == hh - var h: BlockHeader - doTest h + var h: Header + doTest h - # EIP-1559 - h.baseFeePerGas = Opt.some 1234.u256 - doTest h + # EIP-1559 + h.baseFeePerGas = Opt.some 1234.u256 + doTest h - # EIP-4895 - h.withdrawalsRoot = Opt.some Hash256.fromHex( - "0x7a64245f7f95164f6176d90bd4903dbdd3e5433d555dd1385e81787f9672c588") - doTest h + # EIP-4895 + h.withdrawalsRoot = Opt.some hash32"0x7a64245f7f95164f6176d90bd4903dbdd3e5433d555dd1385e81787f9672c588" + doTest h - # EIP-4844 - h.blobGasUsed = Opt.some 1234'u64 - h.excessBlobGas = Opt.some 1234'u64 - doTest h + # EIP-4844 + h.blobGasUsed = Opt.some 1234'u64 + h.excessBlobGas = Opt.some 1234'u64 + doTest h - test "Receipts EIP-2718 + EIP-2976 encoding": - const - # Test payload from - # https://github.com/ethereum/go-ethereum/blob/253447a4f5e5f7f65c0605d490360bb58fb5f8e0/core/types/receipt_test.go#L370 - payload = "f9043eb9010c01f90108018262d4b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010c01f901080182cd14b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010d01f901090183013754b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010d01f90109018301a194b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0" - receiptsBytes = hexToSeqByte(payload) - let receipts = rlp.decode(receiptsBytes, seq[Receipt]) + test "Receipts EIP-2718 + EIP-2976 encoding": + const + # Test payload from + # https://github.com/ethereum/go-ethereum/blob/253447a4f5e5f7f65c0605d490360bb58fb5f8e0/core/types/receipt_test.go#L370 + payload = "f9043eb9010c01f90108018262d4b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010c01f901080182cd14b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010d01f901090183013754b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010d01f90109018301a194b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0" + receiptsBytes = hexToSeqByte(payload) + let receipts = rlp.decode(receiptsBytes, seq[Receipt]) - check receipts.len() == 4 - for receipt in receipts: - check receipt.receiptType == TxEip2930 + check receipts.len() == 4 + for receipt in receipts: + check receipt.receiptType == TxEip2930 - let encoded = rlp.encode(receipts) + let encoded = rlp.encode(receipts) - check receiptsBytes == encoded + check receiptsBytes == encoded - test "Receipts EIP-2718 encoding - invalid - empty": - let receiptBytes: seq[byte] = @[] - expect MalformedRlpError: - let _ = rlp.decode(receiptBytes, Receipt) + test "Receipts EIP-2718 encoding - invalid - empty": + let receiptBytes: seq[byte] = @[] + expect MalformedRlpError: + let _ = rlp.decode(receiptBytes, Receipt) - test "Receipts EIP-2718 encoding - invalid - unsupported tx type": - let receiptBytes: seq[byte] = @[0x05] - expect UnsupportedRlpError: - let _ = rlp.decode(receiptBytes, Receipt) + test "Receipts EIP-2718 encoding - invalid - unsupported tx type": + let receiptBytes: seq[byte] = @[0x05] + expect UnsupportedRlpError: + let _ = rlp.decode(receiptBytes, Receipt) - test "Receipts EIP-2718 encoding - invalid - legacy tx type": - let receiptBytes: seq[byte] = @[0x00] - expect MalformedRlpError: - let _ = rlp.decode(receiptBytes, Receipt) + test "Receipts EIP-2718 encoding - invalid - legacy tx type": + let receiptBytes: seq[byte] = @[0x00] + expect MalformedRlpError: + let _ = rlp.decode(receiptBytes, Receipt) - test "Receipts EIP-2718 encoding - invalid - out of bounds tx type": - let receiptBytes: seq[byte] = @[0x81, 0x80] - expect MalformedRlpError: - let _ = rlp.decode(receiptBytes, Receipt) + test "Receipts EIP-2718 encoding - invalid - out of bounds tx type": + let receiptBytes: seq[byte] = @[0x81, 0x80] + expect MalformedRlpError: + let _ = rlp.decode(receiptBytes, Receipt) - test "Receipts EIP-2718 encoding - invalid - empty receipt payload": - let receiptBytes: seq[byte] = @[0x02] - expect RlpTypeMismatch: - let _ = rlp.decode(receiptBytes, Receipt) + test "Receipts EIP-2718 encoding - invalid - empty receipt payload": + let receiptBytes: seq[byte] = @[0x02] + expect RlpTypeMismatch: + let _ = rlp.decode(receiptBytes, Receipt) - test "Receipt legacy": - const - # Test payload from - # https://github.com/ethereum/go-ethereum/blob/253447a4f5e5f7f65c0605d490360bb58fb5f8e0/core/types/receipt_test.go#L417 - payload = "f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff" - receiptsBytes = hexToSeqByte(payload) + test "Receipt legacy": + const + # Test payload from + # https://github.com/ethereum/go-ethereum/blob/253447a4f5e5f7f65c0605d490360bb58fb5f8e0/core/types/receipt_test.go#L417 + payload = "f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff" + receiptsBytes = hexToSeqByte(payload) - let receipt = rlp.decode(receiptsBytes, Receipt) - check receipt.receiptType == LegacyReceipt - let encoded = rlp.encode(receipt) - check receiptsBytes == encoded + let receipt = rlp.decode(receiptsBytes, Receipt) + check receipt.receiptType == LegacyReceipt + let encoded = rlp.encode(receipt) + check receiptsBytes == encoded - test "Receipt EIP-2930": - const - # Test payload from - # https://github.com/ethereum/go-ethereum/blob/253447a4f5e5f7f65c0605d490360bb58fb5f8e0/core/types/receipt_test.go#L435 - payload = "01f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff" - receiptsBytes = hexToSeqByte(payload) + test "Receipt EIP-2930": + const + # Test payload from + # https://github.com/ethereum/go-ethereum/blob/253447a4f5e5f7f65c0605d490360bb58fb5f8e0/core/types/receipt_test.go#L435 + payload = "01f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff" + receiptsBytes = hexToSeqByte(payload) - let receipt = rlp.decode(receiptsBytes, Receipt) - check receipt.receiptType == Eip2930Receipt - let encoded = rlp.encode(receipt) - check receiptsBytes == encoded + let receipt = rlp.decode(receiptsBytes, Receipt) + check receipt.receiptType == Eip2930Receipt + let encoded = rlp.encode(receipt) + check receiptsBytes == encoded - test "Receipt EIP-1559": - const - # Test payload from - # https://github.com/ethereum/go-ethereum/blob/253447a4f5e5f7f65c0605d490360bb58fb5f8e0/core/types/receipt_test.go#L453 - payload = "02f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff" - receiptsBytes = hexToSeqByte(payload) + test "Receipt EIP-1559": + const + # Test payload from + # https://github.com/ethereum/go-ethereum/blob/253447a4f5e5f7f65c0605d490360bb58fb5f8e0/core/types/receipt_test.go#L453 + payload = "02f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff" + receiptsBytes = hexToSeqByte(payload) - let receipt = rlp.decode(receiptsBytes, Receipt) - check receipt.receiptType == Eip1559Receipt - let encoded = rlp.encode(receipt) - check receiptsBytes == encoded - -suite1() -suite2() + let receipt = rlp.decode(receiptsBytes, Receipt) + check receipt.receiptType == Eip1559Receipt + let encoded = rlp.encode(receipt) + check receiptsBytes == encoded diff --git a/tests/common/test_eip4844.nim b/tests/common/test_eip4844.nim index 486b1f1..3097a04 100644 --- a/tests/common/test_eip4844.nim +++ b/tests/common/test_eip4844.nim @@ -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( diff --git a/tests/common/test_eip7702.nim b/tests/common/test_eip7702.nim index 83bd81b..57f6833 100644 --- a/tests/common/test_eip7702.nim +++ b/tests/common/test_eip7702.nim @@ -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 diff --git a/tests/common/test_eth_types.nim b/tests/common/test_eth_types.nim index 56efdc6..72abc58 100644 --- a/tests/common/test_eth_types.nim +++ b/tests/common/test_eth_types.nim @@ -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,14 +48,16 @@ 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\"" + "\"0x7a64245f7f95164f6176d90bd4903dbdd3e5433d555dd1385e81787f9672c588\"" Json.roundtripTest BlockHashOrNumber(isHash: false, number: 1209231231), - "\"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]) diff --git a/tests/common/test_eth_types_rlp.nim b/tests/common/test_eth_types_rlp.nim index 62fced3..391b304 100644 --- a/tests/common/test_eth_types_rlp.nim +++ b/tests/common/test_eth_types_rlp.nim @@ -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", ) ) ] diff --git a/tests/keys/test_keys.nim b/tests/common/test_keys.nim similarity index 92% rename from tests/keys/test_keys.nim rename to tests/common/test_keys.nim index fd946f2..d00ef7e 100644 --- a/tests/keys/test_keys.nim +++ b/tests/common/test_keys.nim @@ -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 diff --git a/tests/keyfile/test_keyfile.nim b/tests/keyfile/test_keyfile.nim index 2ae90bc..a184c10 100644 --- a/tests/keyfile/test_keyfile.nim +++ b/tests/keyfile/test_keyfile.nim @@ -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 diff --git a/tests/keys/all_tests.nim b/tests/keys/all_tests.nim deleted file mode 100644 index ffa8480..0000000 --- a/tests/keys/all_tests.nim +++ /dev/null @@ -1,3 +0,0 @@ -import - ./test_keys, - ./test_private_public_key_consistency diff --git a/tests/keys/config.nim b/tests/keys/config.nim deleted file mode 100644 index c522170..0000000 --- a/tests/keys/config.nim +++ /dev/null @@ -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" - ) diff --git a/tests/keys/disabled_test_key_and_signature_datastructures.nim b/tests/keys/disabled_test_key_and_signature_datastructures.nim deleted file mode 100644 index 97d71fc..0000000 --- a/tests/keys/disabled_test_key_and_signature_datastructures.nim +++ /dev/null @@ -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 diff --git a/tests/keys/test_private_public_key_consistency.nim b/tests/keys/test_private_public_key_consistency.nim deleted file mode 100644 index ce62909..0000000 --- a/tests/keys/test_private_public_key_consistency.nim +++ /dev/null @@ -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)[] diff --git a/tests/p2p/discv5_test_helper.nim b/tests/p2p/discv5_test_helper.nim index 70ffec7..781663a 100644 --- a/tests/p2p/discv5_test_helper.nim +++ b/tests/p2p/discv5_test_helper.nim @@ -8,7 +8,6 @@ import std/net, chronos, - ../../eth/keys, ../../eth/p2p/discoveryv5/[enr, node, routing_table], ../../eth/p2p/discoveryv5/protocol as discv5_protocol diff --git a/tests/p2p/eth_protocol.nim b/tests/p2p/eth_protocol.nim index bb26039..b7ddcb0 100644 --- a/tests/p2p/eth_protocol.nim +++ b/tests/p2p/eth_protocol.nim @@ -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]) diff --git a/tests/p2p/p2p_test_helper.nim b/tests/p2p/p2p_test_helper.nim index a9556be..b76a590 100644 --- a/tests/p2p/p2p_test_helper.nim +++ b/tests/p2p/p2p_test_helper.nim @@ -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 diff --git a/tests/p2p/test_auth.nim b/tests/p2p/test_auth.nim index 0a17115..80c7e2d 100644 --- a/tests/p2p/test_auth.nim +++ b/tests/p2p/test_auth.nim @@ -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 diff --git a/tests/p2p/test_crypt.nim b/tests/p2p/test_crypt.nim index 8a22927..b630a28 100644 --- a/tests/p2p/test_crypt.nim +++ b/tests/p2p/test_crypt.nim @@ -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", diff --git a/tests/p2p/test_discovery.nim b/tests/p2p/test_discovery.nim index 59a667b..c9c5460 100644 --- a/tests/p2p/test_discovery.nim +++ b/tests/p2p/test_discovery.nim @@ -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 = diff --git a/tests/p2p/test_discoveryv5.nim b/tests/p2p/test_discoveryv5.nim index f96e677..4115739 100644 --- a/tests/p2p/test_discoveryv5.nim +++ b/tests/p2p/test_discoveryv5.nim @@ -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, diff --git a/tests/p2p/test_discoveryv5_encoding.nim b/tests/p2p/test_discoveryv5_encoding.nim index 3fe66ae..222446d 100644 --- a/tests/p2p/test_discoveryv5_encoding.nim +++ b/tests/p2p/test_discoveryv5_encoding.nim @@ -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 diff --git a/tests/p2p/test_ecies.nim b/tests/p2p/test_ecies.nim index dd93311..bf21b41 100644 --- a/tests/p2p/test_ecies.nim +++ b/tests/p2p/test_ecies.nim @@ -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 diff --git a/tests/p2p/test_enr.nim b/tests/p2p/test_enr.nim index 01ca4f2..335f785 100644 --- a/tests/p2p/test_enr.nim +++ b/tests/p2p/test_enr.nim @@ -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() diff --git a/tests/p2p/test_ip_vote.nim b/tests/p2p/test_ip_vote.nim index c123619..271941c 100644 --- a/tests/p2p/test_ip_vote.nim +++ b/tests/p2p/test_ip_vote.nim @@ -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() diff --git a/tests/p2p/test_routing_table.nim b/tests/p2p/test_routing_table.nim index a036ad1..54aa99d 100644 --- a/tests/p2p/test_routing_table.nim +++ b/tests/p2p/test_routing_table.nim @@ -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 = diff --git a/tests/rlp/test_api_usage.nim b/tests/rlp/test_api_usage.nim index cc4ec17..2662835 100644 --- a/tests/rlp/test_api_usage.nim +++ b/tests/rlp/test_api_usage.nim @@ -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: @[ diff --git a/tests/trie/test_examples.nim b/tests/trie/test_examples.nim index 6e0a82a..41b2076 100644 --- a/tests/trie/test_examples.nim +++ b/tests/trie/test_examples.nim @@ -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 diff --git a/tests/trie/test_hexary_proof.nim b/tests/trie/test_hexary_proof.nim index b4b0fdf..d33bb97 100644 --- a/tests/trie/test_hexary_proof.nim +++ b/tests/trie/test_hexary_proof.nim @@ -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 diff --git a/tests/trie/test_hexary_trie.nim b/tests/trie/test_hexary_trie.nim index 12524e1..6b0a1b8 100644 --- a/tests/trie/test_hexary_trie.nim +++ b/tests/trie/test_hexary_trie.nim @@ -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]) diff --git a/tests/utp/test_discv5_protocol.nim b/tests/utp/test_discv5_protocol.nim index 3df0342..5794784 100644 --- a/tests/utp/test_discv5_protocol.nim +++ b/tests/utp/test_discv5_protocol.nim @@ -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 diff --git a/tests/utp/test_packets.nim b/tests/utp/test_packets.nim index 9fa27a7..faab1d9 100644 --- a/tests/utp/test_packets.nim +++ b/tests/utp/test_packets.nim @@ -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": diff --git a/tests/utp/test_protocol.nim b/tests/utp/test_protocol.nim index a6d26ed..9ec1cfd 100644 --- a/tests/utp/test_protocol.nim +++ b/tests/utp/test_protocol.nim @@ -11,7 +11,7 @@ import chronos, testutils/unittests, ./test_utils, - ../../eth/keys, + ../../eth/common/keys, ../../eth/utp/[utp_router, utp_protocol], ../stubloglevel diff --git a/tests/utp/test_protocol_integration.nim b/tests/utp/test_protocol_integration.nim index a90819c..085712b 100644 --- a/tests/utp/test_protocol_integration.nim +++ b/tests/utp/test_protocol_integration.nim @@ -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 diff --git a/tests/utp/test_utils.nim b/tests/utp/test_utils.nim index 456bc94..bcd0395 100644 --- a/tests/utp/test_utils.nim +++ b/tests/utp/test_utils.nim @@ -1,8 +1,7 @@ import chronos, ../../eth/utp/utp_socket, - ../../eth/utp/packets, - ../../eth/keys + ../../eth/utp/packets type AssertionCallback = proc(): bool {.gcsafe, raises: [].} diff --git a/tests/utp/test_utp_router.nim b/tests/utp/test_utp_router.nim index 7e8a5b7..760c534 100644 --- a/tests/utp/test_utp_router.nim +++ b/tests/utp/test_utp_router.nim @@ -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 = diff --git a/tests/utp/test_utp_socket.nim b/tests/utp/test_utp_socket.nim index 2f3e09d..3fff036 100644 --- a/tests/utp/test_utp_socket.nim +++ b/tests/utp/test_utp_socket.nim @@ -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": diff --git a/tests/utp/test_utp_socket_sack.nim b/tests/utp/test_utp_socket_sack.nim index 859a67d..7e47675 100644 --- a/tests/utp/test_utp_socket_sack.nim +++ b/tests/utp/test_utp_socket_sack.nim @@ -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": diff --git a/tests/utp/utp_packet_test_vectors.nim b/tests/utp/utp_packet_test_vectors.nim index 82e97d0..0e765e3 100644 --- a/tests/utp/utp_packet_test_vectors.nim +++ b/tests/utp/utp_packet_test_vectors.nim @@ -9,8 +9,7 @@ import stew/byteutils, unittest2, - ../../eth/utp/packets, - ../../eth/keys + ../../eth/utp/packets suite "uTP packets test vectors": test "SYN packet":