clean up eth_types (#531)

`eth_types` is being imported from many projects and ends up causing
long build times due to its extensive import lists - this PR starts
cleaning some of that up by moving the chain DB and RLP to their own
modules.

this PR also moves `keccakHash` to its own module and uses it in many
places.
This commit is contained in:
Jacek Sieka 2022-09-02 16:57:52 +02:00 committed by GitHub
parent 4f0155e626
commit d31abca010
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 564 additions and 486 deletions

View File

@ -1,4 +1,4 @@
import stint, nimcrypto/[keccak, hash]
import stint, ./common/eth_hash
type UInt2048 = StUint[2048]
@ -27,8 +27,8 @@ proc init*(_: type BloomFilter, h: MDigest[256]): 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(keccak256.digest(v))
proc incl*(f: var BloomFilter, v: openArray[byte]) = f.incl(keccak256.digest(v))
proc incl*(f: var BloomFilter, v: string) = f.incl(keccakHash(v))
proc incl*(f: var BloomFilter, v: openArray[byte]) = f.incl(keccakHash(v))
proc contains*(f: BloomFilter, h: MDigest[256]): bool =
for bits in bloomBits(h):
@ -36,4 +36,4 @@ proc contains*(f: BloomFilter, h: MDigest[256]): bool =
return true
template contains*[T](f: BloomFilter, v: openArray[T]): bool =
f.contains(keccak256.digest(v))
f.contains(keccakHash(v))

View File

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

97
eth/common/chaindb.nim Normal file
View File

@ -0,0 +1,97 @@
# Copyright (c) 2022 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
chronicles,
./eth_types_rlp,
../rlp,
../trie/db
export eth_types_rlp, rlp, db
type
AbstractChainDB* = ref object of RootRef
proc notImplemented(name: string) =
debug "Method not implemented", meth = name
method genesisHash*(db: AbstractChainDB): KeccakHash
{.base, gcsafe, raises: [Defect].} =
notImplemented("genesisHash")
method getBlockHeader*(db: AbstractChainDB, b: HashOrNum,
output: var BlockHeader): bool {.base, gcsafe, raises: [RlpError, Defect].} =
notImplemented("getBlockHeader")
proc getBlockHeader*(db: AbstractChainDB, hash: KeccakHash): BlockHeaderRef {.gcsafe.} =
new result
if not db.getBlockHeader(HashOrNum(isHash: true, hash: hash), result[]):
return nil
proc getBlockHeader*(db: AbstractChainDB, b: BlockNumber): BlockHeaderRef {.gcsafe.} =
new result
if not db.getBlockHeader(HashOrNum(isHash: false, number: b), result[]):
return nil
# Need to add `RlpError` and sometimes `CatchableError` as the implementations
# of these methods in nimbus-eth1 will raise these. Using `CatchableError`
# because some can raise for errors not know to this repository such as
# `CanonicalHeadNotFound`. It would probably be better to use Result.
method getBestBlockHeader*(self: AbstractChainDB): BlockHeader
{.base, gcsafe, raises: [RlpError, CatchableError, Defect].} =
notImplemented("getBestBlockHeader")
method getSuccessorHeader*(db: AbstractChainDB, h: BlockHeader,
output: var BlockHeader, skip = 0'u): bool
{.base, gcsafe, raises: [RlpError, Defect].} =
notImplemented("getSuccessorHeader")
method getAncestorHeader*(db: AbstractChainDB, h: BlockHeader,
output: var BlockHeader, skip = 0'u): bool
{.base, gcsafe, raises: [RlpError, Defect].} =
notImplemented("getAncestorHeader")
method getBlockBody*(db: AbstractChainDB, blockHash: KeccakHash): BlockBodyRef
{.base, gcsafe, raises: [Defect].} =
notImplemented("getBlockBody")
method getReceipt*(db: AbstractChainDB, hash: KeccakHash): ReceiptRef {.base, gcsafe.} =
notImplemented("getReceipt")
method getTrieDB*(db: AbstractChainDB): TrieDatabaseRef
{.base, gcsafe, raises: [Defect].} =
notImplemented("getTrieDB")
method getCodeByHash*(db: AbstractChainDB, hash: KeccakHash): Blob {.base, gcsafe.} =
notImplemented("getCodeByHash")
method getSetting*(db: AbstractChainDB, key: string): seq[byte] {.base, gcsafe.} =
notImplemented("getSetting")
method setSetting*(db: AbstractChainDB, key: string, val: openArray[byte]) {.base, gcsafe.} =
notImplemented("setSetting")
method getHeaderProof*(db: AbstractChainDB, req: ProofRequest): Blob {.base, gcsafe.} =
notImplemented("getHeaderProof")
method getProof*(db: AbstractChainDB, req: ProofRequest): Blob {.base, gcsafe.} =
notImplemented("getProof")
method getHelperTrieProof*(db: AbstractChainDB, req: HelperTrieProofRequest): Blob {.base, gcsafe.} =
notImplemented("getHelperTrieProof")
method getTransactionStatus*(db: AbstractChainDB, txHash: KeccakHash): TransactionStatusMsg {.base, gcsafe.} =
notImplemented("getTransactionStatus")
method addTransactions*(db: AbstractChainDB, transactions: openArray[Transaction]) {.base, gcsafe.} =
notImplemented("addTransactions")
method persistBlocks*(db: AbstractChainDB, headers: openArray[BlockHeader], bodies: openArray[BlockBody]): ValidationResult {.base, gcsafe.} =
notImplemented("persistBlocks")
method getForkId*(db: AbstractChainDB, n: BlockNumber): ForkID {.base, gcsafe.} =
# EIP 2364/2124
notImplemented("getForkId")

44
eth/common/eth_hash.nim Normal file
View File

@ -0,0 +1,44 @@
# Copyright (c) 2022 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.
when (NimMajor, NimMinor) < (1, 4):
{.push raises: [Defect].}
else:
{.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
import
nimcrypto/[keccak, hash]
export
keccak.update, keccak.finish, hash
type
KeccakHash* = MDigest[256]
## A hash value computed using keccak256
## note: this aliases Eth2Digest too, which uses a different hash!
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)
func keccakHash*(input: openArray[byte]): KeccakHash =
keccak256.digest(input)
func keccakHash*(input: openArray[char]): KeccakHash =
keccak256.digest(input)
func keccakHash*(a, b: openArray[byte]): KeccakHash =
withKeccakHash:
h.update a
h.update b

View File

@ -0,0 +1,17 @@
# Copyright (c) 2022 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
export eth_hash, rlp
proc read*(rlp: var Rlp, T: typedesc[MDigest]): T =
result.data = rlp.read(type(result.data))
proc append*(rlpWriter: var RlpWriter, a: MDigest) =
rlpWriter.append(a.data)

View File

@ -1,13 +1,22 @@
import
std/[strutils, options, times],
stew/[endians2, byteutils], chronicles, stint, nimcrypto/[keccak, hash],
../rlp, ../trie/[trie_defs, db]
# Copyright (c) 2022 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.
from stew/objects
import checkedEnumAssign
when (NimMajor, NimMinor) < (1, 4):
{.push raises: [Defect].}
else:
{.push raises: [].}
import
std/[options, strutils, times],
stew/[byteutils, endians2], stint,
./eth_hash
export
stint, read, append, KeccakHash, rlp, options, hash.`$`
options, stint, eth_hash,
times.Time, times.fromUnix, times.toUnix
type
Hash256* = MDigest[256]
@ -184,8 +193,6 @@ type
fromLevel*: uint
auxReq*: uint
AbstractChainDB* = ref object of RootRef
BlockHeaderRef* = ref BlockHeader
BlockBodyRef* = ref BlockBody
ReceiptRef* = ref Receipt
@ -201,8 +208,10 @@ const
Eip2930Receipt* = TxEip2930
Eip1559Receipt* = TxEip1559
BLANK_ROOT_HASH* = "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".toDigest
# TODO clean these up
EMPTY_ROOT_HASH* = "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".toDigest
EMPTY_UNCLE_HASH* = "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347".toDigest
EMPTY_CODE_HASH* = "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470".toDigest
when BlockNumber is int64:
## The goal of these templates is to make it easier to switch
@ -264,8 +273,8 @@ func toUint*(n: BlockNonce): uint64 =
proc newAccount*(nonce: AccountNonce = 0, balance: UInt256 = 0.u256): Account =
result.nonce = nonce
result.balance = balance
result.storageRoot = emptyRlpHash
result.codeHash = blankStringHash
result.storageRoot = EMPTY_ROOT_HASH
result.codeHash = EMPTY_CODE_HASH
proc hasStatus*(rec: Receipt): bool {.inline.} =
rec.isHash == false
@ -286,240 +295,6 @@ func destination*(tx: Transaction): EthAddress =
if tx.to.isSome:
return tx.to.get
#
# Rlp serialization:
#
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
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(int))
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: Option[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.append(1)
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.append(2)
w.startList(12)
w.append(tx.chainId.uint64)
w.append(tx.nonce)
w.append(tx.maxPriorityFee)
w.append(tx.maxFee)
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 append*(w: var RlpWriter, tx: Transaction) =
case tx.txType
of TxLegacy:
w.appendTxLegacy(tx)
of TxEip2930:
w.appendTxEip2930(tx)
of TxEip1559:
w.appendTxEip1559(tx)
template read[T](rlp: var Rlp, val: var T)=
val = rlp.read(type val)
proc read[T](rlp: var Rlp, val: var Option[T])=
if rlp.blobLen != 0:
val = 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.maxPriorityFee)
rlp.read(tx.maxFee)
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 readTxTyped(rlp: var Rlp, tx: var Transaction) {.inline.} =
# 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):
case txVal:
of TxEip2930:
rlp.readTxEip2930(tx)
return
of TxEip1559:
rlp.readTxEip1559(tx)
return
else:
discard
raise newException(UnsupportedRlpError,
"TypedTransaction type must be 1 or 2 in this version, got " & $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 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 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))
func init*(T: type BlockHashOrNumber, str: string): T
{.raises: [ValueError, Defect].} =
if str.startsWith "0x":
@ -538,178 +313,9 @@ func `$`*(x: BlockHashOrNumber): string =
else:
$x.number
proc append*(w: var RlpWriter, rec: Receipt) =
if rec.receiptType in {Eip2930Receipt, Eip1559Receipt}:
w.append(rec.receiptType.int)
w.startList(4)
if rec.isHash:
w.append(rec.hash)
else:
w.append(rec.status.uint8)
w.append(rec.cumulativeGasUsed)
w.append(rec.bloom)
w.append(rec.logs)
proc read*(rlp: var Rlp, T: type Receipt): T =
if rlp.isList:
result.receiptType = LegacyReceipt
else:
# EIP 2718
let recType = rlp.read(int)
if recType notin {1, 2}:
raise newException(UnsupportedRlpError,
"TxType expect 1 or 2 got " & $recType)
result.receiptType = ReceiptType(recType)
rlp.tryEnterList()
if rlp.isBlob and rlp.blobLen in {0, 1}:
result.isHash = false
result.status = rlp.read(uint8) == 1
elif rlp.isBlob and rlp.blobLen == 32:
result.isHash = true
result.hash = rlp.read(Hash256)
else:
raise newException(RlpTypeMismatch,
"HashOrStatus expected, but the source RLP is not a blob of right size.")
rlp.read(result.cumulativeGasUsed)
rlp.read(result.bloom)
rlp.read(result.logs)
proc read*(rlp: var Rlp, T: type Time): T {.inline.} =
result = fromUnix(rlp.read(int64))
proc append*(rlpWriter: var RlpWriter, value: HashOrNum) =
case value.isHash
of true:
rlpWriter.append(value.hash)
else:
rlpWriter.append(value.number)
proc read*(rlp: var Rlp, T: type HashOrNum): T =
if rlp.blobLen == 32:
result = HashOrNum(isHash: true, hash: rlp.read(Hash256))
else:
result = HashOrNum(isHash: false, number: rlp.read(BlockNumber))
proc append*(rlpWriter: var RlpWriter, t: Time) {.inline.} =
rlpWriter.append(t.toUnix())
proc append*(w: var RlpWriter, h: BlockHeader) =
w.startList(if h.fee.isSome: 16 else: 15)
for k, v in fieldPairs(h):
when k != "fee":
w.append(v)
if h.fee.isSome:
w.append(h.fee.get())
proc read*(rlp: var Rlp, T: type BlockHeader): T =
let len = rlp.listLen
if len notin {15, 16}:
raise newException(UnsupportedRlpError,
"BlockHeader elems should be 15 or 16 got " & $len)
rlp.tryEnterList()
for k, v in fieldPairs(result):
when k != "fee":
v = rlp.read(type v)
if len == 16:
# EIP-1559
result.baseFee = rlp.read(UInt256)
proc rlpHash*[T](v: T): Hash256 =
keccak256.digest(rlp.encode(v))
func blockHash*(h: BlockHeader): KeccakHash {.inline.} = rlpHash(h)
proc notImplemented(name: string) =
debug "Method not implemented", meth = name
template hasData*(b: Blob): bool = b.len > 0
template hasData*(r: EthResourceRefs): bool = r != nil
template deref*(b: Blob): auto = b
template deref*(o: Option): auto = o.get
template deref*(r: EthResourceRefs): auto = r[]
method genesisHash*(db: AbstractChainDB): KeccakHash
{.base, gcsafe, raises: [Defect].} =
notImplemented("genesisHash")
method getBlockHeader*(db: AbstractChainDB, b: HashOrNum,
output: var BlockHeader): bool {.base, gcsafe, raises: [RlpError, Defect].} =
notImplemented("getBlockHeader")
proc getBlockHeader*(db: AbstractChainDB, hash: KeccakHash): BlockHeaderRef {.gcsafe.} =
new result
if not db.getBlockHeader(HashOrNum(isHash: true, hash: hash), result[]):
return nil
proc getBlockHeader*(db: AbstractChainDB, b: BlockNumber): BlockHeaderRef {.gcsafe.} =
new result
if not db.getBlockHeader(HashOrNum(isHash: false, number: b), result[]):
return nil
# Need to add `RlpError` and sometimes `CatchableError` as the implementations
# of these methods in nimbus-eth1 will raise these. Using `CatchableError`
# because some can raise for errors not know to this repository such as
# `CanonicalHeadNotFound`. It would probably be better to use Result.
method getBestBlockHeader*(self: AbstractChainDB): BlockHeader
{.base, gcsafe, raises: [RlpError, CatchableError, Defect].} =
notImplemented("getBestBlockHeader")
method getSuccessorHeader*(db: AbstractChainDB, h: BlockHeader,
output: var BlockHeader, skip = 0'u): bool
{.base, gcsafe, raises: [RlpError, Defect].} =
notImplemented("getSuccessorHeader")
method getAncestorHeader*(db: AbstractChainDB, h: BlockHeader,
output: var BlockHeader, skip = 0'u): bool
{.base, gcsafe, raises: [RlpError, Defect].} =
notImplemented("getAncestorHeader")
method getBlockBody*(db: AbstractChainDB, blockHash: KeccakHash): BlockBodyRef
{.base, gcsafe, raises: [Defect].} =
notImplemented("getBlockBody")
method getReceipt*(db: AbstractChainDB, hash: KeccakHash): ReceiptRef {.base, gcsafe.} =
notImplemented("getReceipt")
method getTrieDB*(db: AbstractChainDB): TrieDatabaseRef
{.base, gcsafe, raises: [Defect].} =
notImplemented("getTrieDB")
method getCodeByHash*(db: AbstractChainDB, hash: KeccakHash): Blob {.base, gcsafe.} =
notImplemented("getCodeByHash")
method getSetting*(db: AbstractChainDB, key: string): seq[byte] {.base, gcsafe.} =
notImplemented("getSetting")
method setSetting*(db: AbstractChainDB, key: string, val: openArray[byte]) {.base, gcsafe.} =
notImplemented("setSetting")
method getHeaderProof*(db: AbstractChainDB, req: ProofRequest): Blob {.base, gcsafe.} =
notImplemented("getHeaderProof")
method getProof*(db: AbstractChainDB, req: ProofRequest): Blob {.base, gcsafe.} =
notImplemented("getProof")
method getHelperTrieProof*(db: AbstractChainDB, req: HelperTrieProofRequest): Blob {.base, gcsafe.} =
notImplemented("getHelperTrieProof")
method getTransactionStatus*(db: AbstractChainDB, txHash: KeccakHash): TransactionStatusMsg {.base, gcsafe.} =
notImplemented("getTransactionStatus")
method addTransactions*(db: AbstractChainDB, transactions: openArray[Transaction]) {.base, gcsafe.} =
notImplemented("addTransactions")
method persistBlocks*(db: AbstractChainDB, headers: openArray[BlockHeader], bodies: openArray[BlockBody]): ValidationResult {.base, gcsafe.} =
notImplemented("persistBlocks")
method getForkId*(db: AbstractChainDB, n: BlockNumber): ForkID {.base, gcsafe.} =
# EIP 2364/2124
notImplemented("getForkId")

View File

@ -0,0 +1,338 @@
# Copyright (c) 2022 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_types, eth_hash_rlp],
../rlp
from stew/objects
import checkedEnumAssign
export
eth_types, eth_hash_rlp, rlp
#
# Rlp serialization:
#
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
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(int))
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: Option[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.append(1)
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.append(2)
w.startList(12)
w.append(tx.chainId.uint64)
w.append(tx.nonce)
w.append(tx.maxPriorityFee)
w.append(tx.maxFee)
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 append*(w: var RlpWriter, tx: Transaction) =
case tx.txType
of TxLegacy:
w.appendTxLegacy(tx)
of TxEip2930:
w.appendTxEip2930(tx)
of TxEip1559:
w.appendTxEip1559(tx)
template read[T](rlp: var Rlp, val: var T)=
val = rlp.read(type val)
proc read[T](rlp: var Rlp, val: var Option[T])=
if rlp.blobLen != 0:
val = 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.maxPriorityFee)
rlp.read(tx.maxFee)
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 readTxTyped(rlp: var Rlp, tx: var Transaction) {.inline.} =
# 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):
case txVal:
of TxEip2930:
rlp.readTxEip2930(tx)
return
of TxEip1559:
rlp.readTxEip1559(tx)
return
else:
discard
raise newException(UnsupportedRlpError,
"TypedTransaction type must be 1 or 2 in this version, got " & $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 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 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*(w: var RlpWriter, rec: Receipt) =
if rec.receiptType in {Eip2930Receipt, Eip1559Receipt}:
w.append(rec.receiptType.int)
w.startList(4)
if rec.isHash:
w.append(rec.hash)
else:
w.append(rec.status.uint8)
w.append(rec.cumulativeGasUsed)
w.append(rec.bloom)
w.append(rec.logs)
proc read*(rlp: var Rlp, T: type Receipt): T =
if rlp.isList:
result.receiptType = LegacyReceipt
else:
# EIP 2718
let recType = rlp.read(int)
if recType notin {1, 2}:
raise newException(UnsupportedRlpError,
"TxType expect 1 or 2 got " & $recType)
result.receiptType = ReceiptType(recType)
rlp.tryEnterList()
if rlp.isBlob and rlp.blobLen in {0, 1}:
result.isHash = false
result.status = rlp.read(uint8) == 1
elif rlp.isBlob and rlp.blobLen == 32:
result.isHash = true
result.hash = rlp.read(Hash256)
else:
raise newException(RlpTypeMismatch,
"HashOrStatus expected, but the source RLP is not a blob of right size.")
rlp.read(result.cumulativeGasUsed)
rlp.read(result.bloom)
rlp.read(result.logs)
proc read*(rlp: var Rlp, T: type Time): T {.inline.} =
result = fromUnix(rlp.read(int64))
proc append*(rlpWriter: var RlpWriter, value: HashOrNum) =
case value.isHash
of true:
rlpWriter.append(value.hash)
else:
rlpWriter.append(value.number)
proc read*(rlp: var Rlp, T: type HashOrNum): T =
if rlp.blobLen == 32:
result = HashOrNum(isHash: true, hash: rlp.read(Hash256))
else:
result = HashOrNum(isHash: false, number: rlp.read(BlockNumber))
proc append*(rlpWriter: var RlpWriter, t: Time) {.inline.} =
rlpWriter.append(t.toUnix())
proc append*(w: var RlpWriter, h: BlockHeader) =
w.startList(if h.fee.isSome: 16 else: 15)
for k, v in fieldPairs(h):
when k != "fee":
w.append(v)
if h.fee.isSome:
w.append(h.fee.get())
proc read*(rlp: var Rlp, T: type BlockHeader): T =
let len = rlp.listLen
if len notin {15, 16}:
raise newException(UnsupportedRlpError,
"BlockHeader elems should be 15 or 16 got " & $len)
rlp.tryEnterList()
for k, v in fieldPairs(result):
when k != "fee":
v = rlp.read(type v)
if len == 16:
# EIP-1559
result.baseFee = rlp.read(UInt256)
proc rlpHash*[T](v: T): Hash256 =
keccakHash(rlp.encode(v))
func blockHash*(h: BlockHeader): KeccakHash {.inline.} = rlpHash(h)

View File

@ -1,6 +1,9 @@
import
../trie/[trie_defs, db, hexary], ../rlp,
./eth_types
../trie/[trie_defs, db, hexary],
../rlp,
./chaindb
export chaindb
proc getAccount*(db: TrieDatabaseRef,
rootHash: KeccakHash,

View File

@ -1,6 +1,7 @@
import
nimcrypto/keccak,
".."/[common, rlp, keys]
../common/eth_types_rlp
export eth_types_rlp
const
EIP155_CHAIN_ID_OFFSET* = 35'i64
@ -74,4 +75,4 @@ func rlpEncode*(tx: Transaction): auto =
func txHashNoSignature*(tx: Transaction): Hash256 =
# Hash transaction without signature
keccak256.digest(rlpEncode(tx))
keccakHash(rlpEncode(tx))

View File

@ -18,7 +18,7 @@ import
std/strformat,
secp256k1, bearssl/hash as bhash, bearssl/rand,
stew/[byteutils, objects, results],
nimcrypto/[hash, keccak]
./common/eth_hash
from nimcrypto/utils import burnMem
@ -124,16 +124,16 @@ func toRaw*(sig: SignatureNR): array[RawSignatureNRSize, byte] {.borrow.}
func toAddress*(pubkey: PublicKey, with0x = true): string =
## Convert public key to hexadecimal string address.
var hash = keccak256.digest(pubkey.toRaw())
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 = keccak256.digest(pubkey.toRaw())
var hash1 = keccakHash(pubkey.toRaw())
var hhash1 = toHex(toOpenArray(hash1.data, 12, len(hash1.data) - 1))
var hash2 = keccak256.digest(hhash1)
var hash2 = keccakHash(hhash1)
var hhash2 = toHex(hash2.data)
for i in 0..<len(hhash1):
if hhash2[i] >= '0' and hhash2[i] <= '7':
@ -161,7 +161,7 @@ func validateChecksumAddress*(a: string): bool =
address &= chr(ord(ch) - ord('A') + ord('a'))
else:
return false
var hash = keccak256.digest(address)
var hash = keccakHash(address)
var hexhash = toHex(hash.data)
for i in 0..<len(address):
if hexhash[i] >= '0' and hexhash[i] <= '7':
@ -176,7 +176,7 @@ func validateChecksumAddress*(a: string): bool =
func toCanonicalAddress*(pubkey: PublicKey): array[20, byte] =
## Convert public key to canonical address.
var hash = keccak256.digest(pubkey.toRaw())
var hash = keccakHash(pubkey.toRaw())
copyMem(addr result[0], addr hash.data[12], 20)
func `$`*(pubkey: PublicKey): string =
@ -206,28 +206,28 @@ func sign*(seckey: PrivateKey, msg: SkMessage): Signature =
Signature(signRecoverable(SkSecretKey(seckey), msg))
func sign*(seckey: PrivateKey, msg: openArray[byte]): Signature =
let hash = keccak256.digest(msg)
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 = keccak256.digest(msg)
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 = keccak256.digest(msg)
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 = keccak256.digest(msg)
let hash = keccakHash(msg)
verify(sig, SkMessage(hash.data), key)
func ecdhRaw*(seckey: PrivateKey, pubkey: PublicKey): SharedSecret =

View File

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

View File

@ -10,9 +10,12 @@ import
std/[deques, tables],
chronos,
stew/results,
".."/../[rlp, keys], ".."/../common/eth_types,
../../common/chaindb,
".."/../[rlp, keys],
".."/[enode, kademlia, discovery, rlpxcrypt]
export chaindb
const
useSnappy* = defined(useSnappy)

View File

@ -342,7 +342,7 @@ proc queueMessage(node: EthereumNode, msg: Message): bool =
proc postMessage*(node: EthereumNode, pubKey = none[PublicKey](),
symKey = none[SymKey](), src = none[PrivateKey](),
ttl: uint32, topic: Topic, payload: seq[byte],
ttl: uint32, topic: whisper_types.Topic, payload: seq[byte],
padding = none[seq[byte]](), powTime = 1'f,
powTarget = defaultMinPow,
targetPeer = none[NodeId]()): bool =

View File

@ -13,7 +13,7 @@
{.push raises: [Defect].}
import
nimcrypto, stew/results
nimcrypto/[bcmode, keccak, rijndael, utils], stew/results
from auth import ConnectionSecret
export results

View File

@ -27,7 +27,7 @@ proc run(s: FullNodeSyncer) {.async.} =
# Ensure we have the state for our current head.
head = await self.wait(self.chaindb.coro_get_canonical_head())
if head.state_root != BLANK_ROOT_HASH and head.state_root not in self.base_db:
if head.state_root != EMPTY_ROOT_HASH and head.state_root not in self.base_db:
self.logger.info(
"Missing state for current head (#%d), downloading it", head.block_number)
downloader = StateDownloader(

View File

@ -2,7 +2,6 @@
import
std/[tables, hashes, sets],
nimcrypto/[hash, keccak],
"."/[trie_defs, db_tracing]
type
@ -54,9 +53,6 @@ proc get*(db: TrieDatabaseRef, key: openArray[byte]): seq[byte] {.gcsafe.}
proc del*(db: TrieDatabaseRef, key: openArray[byte]) {.gcsafe.}
proc beginTransaction*(db: TrieDatabaseRef): DbTransaction {.gcsafe.}
proc keccak*(r: openArray[byte]): KeccakHash =
keccak256.digest r
proc get*(db: MemoryLayer, key: openArray[byte]): seq[byte] =
result = db.records.getOrDefault(@key).value
traceGet key, result

View File

@ -21,9 +21,6 @@ type
template len(key: TrieNodeKey): int =
key.usedBytes.int
proc keccak*(r: openArray[byte]): KeccakHash =
keccak256.digest r
template asDbKey(k: TrieNodeKey): untyped =
doAssert k.usedBytes == 32
k.hash.data
@ -350,11 +347,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.keccak.data)
if data.len >= 32: t.prune(data.keccakHash.data)
proc dbPut(db: DB, data: openArray[byte]): TrieNodeKey
{.raises: [Defect].} =
result.hash = data.keccak
result.hash = data.keccakHash
result.usedBytes = 32
put(db, result.asDbKey, data)
@ -549,7 +546,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.keccak, key, value, isInline)
self.mergeAt(rlp, rlp.rawData.keccakHash, key, value, isInline)
proc mergeAtAux(self: var HexaryTrie, output: var RlpWriter, orig: Rlp,
key: NibblesSeq, value: openArray[byte]) =
@ -662,13 +659,13 @@ 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.keccak.data, value)
put(HexaryTrie(self), key.keccakHash.data, value)
proc get*(self: SecureHexaryTrie; key: openArray[byte]): seq[byte] =
return get(HexaryTrie(self), key.keccak.data)
return get(HexaryTrie(self), key.keccakHash.data)
proc del*(self: var SecureHexaryTrie; key: openArray[byte]) =
del(HexaryTrie(self), key.keccak.data)
del(HexaryTrie(self), key.keccakHash.data)
proc rootHash*(self: SecureHexaryTrie): KeccakHash {.borrow.}
proc rootHashHex*(self: SecureHexaryTrie): string {.borrow.}
@ -686,7 +683,7 @@ proc isValidBranch*(branch: seq[seq[byte]], rootHash: KeccakHash, key, value: se
var db = newMemoryDB()
for node in branch:
doAssert(node.len != 0)
let nodeHash = hexary.keccak(node)
let nodeHash = keccakHash(node)
db.put(nodeHash.data, node)
var trie = initHexaryTrie(db, rootHash)

View File

@ -29,9 +29,9 @@ proc verifyProofAux*(proof: seq[seq[byte]], root, key, value: openArray[byte]):
if node.len != 32: return false
if path[i]: # right
# reuse curHash without more alloc
curHash.data.keccakHash(node, curHash.data)
curHash = keccakHash(node, curHash.data)
else:
curHash.data.keccakHash(curHash.data, node)
curHash = keccakHash(curHash.data, node)
result = curHash.data == root

View File

@ -1,10 +1,9 @@
import
nimcrypto/hash,
../rlp
../common/eth_hash_rlp
export eth_hash_rlp
type
KeccakHash* = MDigest[256]
TrieError* = object of CatchableError
# A common base type of all Trie errors.
@ -21,9 +20,3 @@ const
blankStringHash* = "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470".toDigest
emptyRlp* = @[128.byte]
emptyRlpHash* = "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".toDigest
proc read*(rlp: var Rlp, T: typedesc[MDigest]): T {.inline.} =
result.data = rlp.read(type(result.data))
proc append*(rlpWriter: var RlpWriter, a: MDigest) {.inline.} =
rlpWriter.append(a.data)

View File

@ -1,8 +1,9 @@
import
stew/byteutils,
nimcrypto/[hash, keccak],
./trie_defs
export trie_defs
template checkValidHashZ*(x: untyped) =
when x.type isnot KeccakHash:
doAssert(x.len == 32 or x.len == 0)
@ -14,21 +15,3 @@ 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)
proc keccakHash*(input: openArray[byte]): KeccakHash =
keccak256.digest(input)
proc keccakHash*(dest: var openArray[byte], a, b: openArray[byte]) =
var ctx: keccak256
ctx.init()
if a.len != 0:
ctx.update(a[0].unsafeAddr, uint(a.len))
if b.len != 0:
ctx.update(b[0].unsafeAddr, uint(b.len))
ctx.finish dest
ctx.clear()
proc keccakHash*(a, b: openArray[byte]): KeccakHash =
var s: array[32, byte]
keccakHash(s, a, b)
KeccakHash(data: s)