nimbus-eth1/nimbus/utils/ec_recover.nim

141 lines
5.1 KiB
Nim

# Nimbus
# 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.
##
## Recover Address From Signature
## ==============================
##
## This module provides caching and direct versions for recovering the
## `EthAddress` from an extended signature. The caching version reduces
## calculation time for the price of maintaing it in a LRU cache.
import
./utils_defs,
./lru_cache,
../constants,
eth/[common, keys, rlp],
nimcrypto,
stew/results,
stint
export
utils_defs
const
INMEMORY_SIGNATURES* = ##\
## Number of recent block signatures to keep in memory
4096
type
# simplify Hash256 for rlp serialisation
EcKey32 = array[32, byte]
EcRecover* = LruCache[BlockHeader,EcKey32,EthAddress,UtilsError]
{.push raises: [Defect].}
# ------------------------------------------------------------------------------
# Private helpers
# ------------------------------------------------------------------------------
proc encodePreSealed(header: BlockHeader): seq[byte] {.inline.} =
## Cut sigature off `extraData` header field and consider new `baseFee`
## field for Eip1559.
doAssert EXTRA_SEAL < header.extraData.len
var rlpHeader = header
rlpHeader.extraData.setLen(header.extraData.len - EXTRA_SEAL)
rlp.encode(rlpHeader)
proc hashPreSealed(header: BlockHeader): Hash256 {.inline.} =
## Returns the hash of a block prior to it being sealed.
keccak256.digest header.encodePreSealed
proc ecRecover*(extraData: openArray[byte];
hash: Hash256): Result[EthAddress,UtilsError] {.inline.} =
## Extract account address from the last 65 bytes of the `extraData` argument
## (which is typically the bock header field with the same name.) The second
## argument `hash` is used to extract the intermediate public key. Typically,
## this would be the hash of the block header without the last 65 bytes of
## the `extraData` field reserved for the signature.
if extraData.len < EXTRA_SEAL:
return err((errMissingSignature,""))
let sig = Signature.fromRaw(
extraData.toOpenArray(extraData.len - EXTRA_SEAL, extraData.high))
if sig.isErr:
return err((errSkSigResult,$sig.error))
# Recover the public key from signature and seal hash
let pubKey = recover(sig.value, SKMessage(hash.data))
if pubKey.isErr:
return err((errSkPubKeyResult,$pubKey.error))
# Convert public key to address.
return ok(pubKey.value.toCanonicalAddress)
# ------------------------------------------------------------------------------
# Public function: straight ecRecover version
# ------------------------------------------------------------------------------
proc ecRecover*(header: BlockHeader): Result[EthAddress,UtilsError] =
## Extract account address from the `extraData` field (last 65 bytes) of the
## argument header.
header.extraData.ecRecover(header.hashPreSealed)
# ------------------------------------------------------------------------------
# Public constructor for caching ecRecover version
# ------------------------------------------------------------------------------
proc initEcRecover*(cache: var EcRecover; cacheSize = INMEMORY_SIGNATURES) =
var toKey: LruKey[BlockHeader,EcKey32] =
proc(header:BlockHeader): EcKey32 =
header.blockHash.data
cache.initCache(toKey, ecRecover, cacheSize)
proc initEcRecover*: EcRecover {.gcsafe, raises: [Defect].} =
result.initEcRecover
# ------------------------------------------------------------------------------
# Public function: caching ecRecover version
# ------------------------------------------------------------------------------
proc ecRecover*(addrCache: var EcRecover;
header: BlockHeader): Result[EthAddress,UtilsError]
{.gcsafe, raises: [Defect,CatchableError].} =
## Extract account address from `extraData` field (last 65 bytes) of the
## argument header. The result is kept in a LRU cache to re-purposed for
## improved result delivery avoiding calculations.
addrCache.getItem(header)
# ------------------------------------------------------------------------------
# Public PLP mixin functions for caching version
# ------------------------------------------------------------------------------
proc append*(rw: var RlpWriter; ecRec: EcRecover) {.
inline, raises: [Defect,KeyError].} =
## Generic support for `rlp.encode(ecRec)`
rw.append(ecRec.data)
proc read*(rlp: var Rlp; Q: type EcRecover): Q {.
inline, raises: [Defect,KeyError].} =
## Generic support for `rlp.decode(bytes)` for loading the cache from a
## serialised data stream.
result.initEcRecover
result.data = rlp.read(type result.data)
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------