Skip to content

Commit

Permalink
#768 Moved/re-implemented ecRecover() from Clique sources to utils/ec…
Browse files Browse the repository at this point in the history
…_recover

why:
  The same functionality was differently implemented in one or the
  other form.

details:
  Caching and non-caching variants available
  • Loading branch information
mjfh authored and jangko committed Aug 5, 2021
1 parent 9aea669 commit 4713bd4
Show file tree
Hide file tree
Showing 12 changed files with 269 additions and 230 deletions.
8 changes: 8 additions & 0 deletions nimbus/constants.nim
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,11 @@ const

# EIP
MaxPrecompilesAddr* = 0xFFFF

EXTRA_SEAL* = ##\
## Fixed number of suffix bytes reserved for signer seal of the `extraData`
## header field. The 65 bytes constant value is for signatures based on the
## standard secp256k1 curve.
65

# End
4 changes: 2 additions & 2 deletions nimbus/p2p/clique/clique_cfg.nim
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import
std/[random, sequtils, strutils, times],
../../db/db_chain,
./clique_cfg/ec_recover,
../../utils/ec_recover,
./clique_defs,
eth/common,
ethash,
Expand Down Expand Up @@ -117,7 +117,7 @@ proc newCliqueCfg*(db: BaseChainDB): CliqueCfg =
# clique/clique.go(145): func ecrecover(header [..]
proc ecRecover*(cfg: CliqueCfg; header: BlockHeader): auto
{.gcsafe, raises: [Defect,CatchableError].} =
cfg.signatures.getEcRecover(header)
cfg.signatures.ecRecover(header)

# ------------------------------------------------------------------------------
# Public setters
Expand Down
103 changes: 0 additions & 103 deletions nimbus/p2p/clique/clique_cfg/ec_recover.nim

This file was deleted.

24 changes: 3 additions & 21 deletions nimbus/p2p/clique/clique_defs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,6 @@ const
## Number of recent vote snapshots to keep in memory.
128

INMEMORY_SIGNATURES* = ##\
## Number of recent block signatures to keep in memory
4096

WIGGLE_TIME* = ##\
## PoA mining only (currently unsupported).
##
Expand All @@ -62,11 +58,6 @@ const
## Suggested 32 bytes to retain the current extra-data allowance and/or use.
32

EXTRA_SEAL* = ##\
## Fixed number of extra-data suffix bytes reserved for signer seal.
## 65 bytes fixed as signatures are based on the standard secp256k1 curve.
65

NONCE_AUTH* = ##\
## Magic nonce number 0xffffffffffffffff to vote on adding a new signer.
0xffffffffffffffffu64.toBlockNonce
Expand Down Expand Up @@ -208,21 +199,11 @@ type
## unknown.
"unknown ancestor"

# errPrunedAncestor = ##\
# ## is returned when validating a block requires an ancestor that is
# ## known, but the state of which is not available.
# "pruned ancestor"

errFutureBlock = ##\
## is returned when a block's timestamp is in the future according to
## the current node.
"block in the future"

# errInvalidNumber = ##\
# ## is returned if a block's number doesn't equal its parent's plus one.
# "invalid block number"


# additional/bespoke errors, manually added
# -----------------------------------------

Expand All @@ -237,8 +218,9 @@ type
## Attempt to assign a value to a non-existing slot
"Missing LRU slot for snapshot"

errSkSigResult ## eth/keys subsytem error: signature
errSkPubKeyResult ## eth/keys subsytem error: public key
errEcRecover = ##\
## Subsytem error"
"ecRecover failed"

errSnapshotLoad ## DB subsytem error
errSnapshotStore ## ..
Expand Down
2 changes: 1 addition & 1 deletion nimbus/p2p/clique/clique_verify.nim
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ proc verifySeal(c: Clique; header: BlockHeader): CliqueOkResult
# Resolve the authorization key and check against signers
let signer = c.cfg.ecRecover(header)
if signer.isErr:
return err(signer.error)
return err((errEcRecover,$signer.error))

if not snapshot.isSigner(signer.value):
return err((errUnauthorizedSigner,""))
Expand Down
16 changes: 9 additions & 7 deletions nimbus/p2p/clique/snapshot/snapshot_apply.nim
Original file line number Diff line number Diff line change
Expand Up @@ -141,23 +141,25 @@ proc snapshotApplySeq*(s: Snapshot; headers: var seq[BlockHeader],
s.recents.del(number - limit)

# Resolve the authorization key and check against signers
let signer = ? s.cfg.ecRecover(header)
let signer = s.cfg.ecRecover(header)
if signer.isErr:
return err((errEcRecover,$signer.error))
#s.say "applySnapshot signer=", s.pp(signer)

if not s.ballot.isAuthSigner(signer):
if not s.ballot.isAuthSigner(signer.value):
s.say "applySnapshot signer not authorised => fail ", s.pp(29)
return err((errUnauthorizedSigner,""))

for recent in s.recents.values:
if recent == signer:
s.say "applySnapshot signer recently seen ", s.pp(signer)
if recent == signer.value:
s.say "applySnapshot signer recently seen ", s.pp(signer.value)
echo "+++ applySnapshot #", header.blockNumber, " err=errRecentlySigned"
return err((errRecentlySigned,""))
s.recents[number] = signer
s.recents[number] = signer.value

# Header authorized, discard any previous vote from the signer
# clique/snapshot.go(233): for i, vote := range snap.Votes {
s.ballot.delVote(signer = signer, address = header.coinbase)
s.ballot.delVote(signer = signer.value, address = header.coinbase)

# Tally up the new vote from the signer
# clique/snapshot.go(244): var authorize bool
Expand All @@ -167,7 +169,7 @@ proc snapshotApplySeq*(s: Snapshot; headers: var seq[BlockHeader],
elif header.nonce != NONCE_DROP:
return err((errInvalidVote,""))
let vote = Vote(address: header.coinbase,
signer: signer,
signer: signer.value,
blockNumber: number,
authorize: authOk)
#s.say "applySnapshot calling addVote ", s.pp(vote)
Expand Down
137 changes: 137 additions & 0 deletions nimbus/utils/ec_recover.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http:https://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http:https://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

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
# ------------------------------------------------------------------------------
Loading

0 comments on commit 4713bd4

Please sign in to comment.