148 lines
4.8 KiB
Nim
148 lines
4.8 KiB
Nim
# Fluffy
|
|
# Copyright (c) 2021-2023 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.
|
|
|
|
# As per spec:
|
|
# https://github.com/ethereum/portal-network-specs/blob/master/state-network.md#content-keys-and-content-ids
|
|
|
|
{.push raises: [].}
|
|
|
|
import
|
|
nimcrypto/[hash, sha2, keccak], stew/[objects, results, endians2], stint,
|
|
ssz_serialization,
|
|
../../common/common_types
|
|
|
|
export ssz_serialization, common_types, hash, results
|
|
|
|
type
|
|
NodeHash* = MDigest[32 * 8] # keccak256
|
|
CodeHash* = MDigest[32 * 8] # keccak256
|
|
Address* = array[20, byte]
|
|
|
|
ContentType* = enum
|
|
# Note: Need to add this unused value as a case object with an enum without
|
|
# a 0 valueis not allowed: "low(contentType) must be 0 for discriminant".
|
|
# For prefix values that are in the enum gap, the deserialization will fail
|
|
# at runtime as is wanted.
|
|
# In the future it might be possible that this will fail at compile time for
|
|
# the SSZ Union type, but currently it is allowed in the implementation, and
|
|
# the SSZ spec is not explicit about disallowing this.
|
|
unused = 0x00
|
|
accountTrieNode = 0x20
|
|
contractStorageTrieNode = 0x21
|
|
accountTrieProof = 0x22
|
|
contractStorageTrieProof = 0x23
|
|
contractBytecode = 0x24
|
|
|
|
AccountTrieNodeKey* = object
|
|
path*: ByteList
|
|
nodeHash*: NodeHash
|
|
stateRoot*: Bytes32
|
|
|
|
ContractStorageTrieNodeKey* = object
|
|
address*: Address
|
|
path*: ByteList
|
|
nodeHash*: NodeHash
|
|
stateRoot*: Bytes32
|
|
|
|
AccountTrieProofKey* = object
|
|
address*: Address
|
|
stateRoot*: Bytes32
|
|
|
|
ContractStorageTrieProofKey* = object
|
|
address*: Address
|
|
slot*: UInt256
|
|
stateRoot*: Bytes32
|
|
|
|
ContractBytecodeKey* = object
|
|
address*: Address
|
|
codeHash*: CodeHash
|
|
|
|
ContentKey* = object
|
|
case contentType*: ContentType
|
|
of unused:
|
|
discard
|
|
of accountTrieNode:
|
|
accountTrieNodeKey*: AccountTrieNodeKey
|
|
of contractStorageTrieNode:
|
|
contractStorageTrieNodeKey*: ContractStorageTrieNodeKey
|
|
of accountTrieProof:
|
|
accountTrieProofKey*: AccountTrieProofKey
|
|
of contractStorageTrieProof:
|
|
contractStorageTrieProofKey*: ContractStorageTrieProofKey
|
|
of contractBytecode:
|
|
contractBytecodeKey*: ContractBytecodeKey
|
|
|
|
func encode*(contentKey: ContentKey): ByteList =
|
|
doAssert(contentKey.contentType != unused)
|
|
ByteList.init(SSZ.encode(contentKey))
|
|
|
|
proc readSszBytes*(
|
|
data: openArray[byte], val: var ContentKey
|
|
) {.raises: [SszError].} =
|
|
mixin readSszValue
|
|
if data.len() > 0 and data[0] == ord(unused):
|
|
raise newException(MalformedSszError, "SSZ selector is unused value")
|
|
|
|
readSszValue(data, val)
|
|
|
|
func decode*(contentKey: ByteList): Opt[ContentKey] =
|
|
try:
|
|
Opt.some(SSZ.decode(contentKey.asSeq(), ContentKey))
|
|
except SerializationError:
|
|
return Opt.none(ContentKey)
|
|
|
|
template computeContentId*(digestCtxType: type, body: untyped): ContentId =
|
|
var h {.inject.}: digestCtxType
|
|
init(h)
|
|
body
|
|
let idHash = finish(h)
|
|
readUintBE[256](idHash.data)
|
|
|
|
func toContentId*(contentKey: ContentKey): ContentId =
|
|
case contentKey.contentType:
|
|
of unused:
|
|
raiseAssert "Should not be used and fail at decoding"
|
|
of accountTrieNode: # sha256(path | node_hash)
|
|
let key = contentKey.accountTrieNodeKey
|
|
computeContentId sha256:
|
|
h.update(key.path.asSeq())
|
|
h.update(key.nodeHash.data)
|
|
of contractStorageTrieNode: # sha256(address | path | node_hash)
|
|
let key = contentKey.contractStorageTrieNodeKey
|
|
computeContentId sha256:
|
|
h.update(key.address)
|
|
h.update(key.path.asSeq())
|
|
h.update(key.nodeHash.data)
|
|
of accountTrieProof: # keccak(address)
|
|
let key = contentKey.accountTrieProofKey
|
|
computeContentId keccak256:
|
|
h.update(key.address)
|
|
of contractStorageTrieProof: # (keccak(address) + keccak(slot)) % 2**256
|
|
# TODO: Why is keccak run on slot, when it can be used directly?
|
|
# Also, value to LE or BE? Not mentioned in specification.
|
|
let key = contentKey.contractStorageTrieProofKey
|
|
let n1 =
|
|
block: computeContentId keccak256:
|
|
h.update(key.address)
|
|
let n2 =
|
|
block: computeContentId keccak256:
|
|
h.update(toBytesBE(key.slot))
|
|
|
|
n1 + n2 # uint256 will wrap arround, practically applying the modulo 256
|
|
of contractBytecode: # sha256(address | code_hash)
|
|
let key = contentKey.contractBytecodeKey
|
|
computeContentId sha256:
|
|
h.update(key.address)
|
|
h.update(key.codeHash.data)
|
|
|
|
func toContentId*(contentKey: ByteList): results.Opt[ContentId] =
|
|
let key = decode(contentKey)
|
|
if key.isSome():
|
|
ok(key.get().toContentId())
|
|
else:
|
|
Opt.none(ContentId)
|