mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-01-09 22:06:21 +00:00
22591deced
When the connect_to_testnet script is invoked it will first verify that the genesis file of the testnet hasn't changed. If it has changed, any previously created database associated with the testnet will be erased. To facilitate this, the genesis file of each network is written to the data folder of the beacon node. The beacon node will refuse to start if it detects a discrepancy between the data folder and any state snapshot specified on the command-line. Since the testnet sharing spec requires us to use SSZ snapshots, the Json support is now phased out. To help with the transition and to preserve the functionality of the multinet scripts, the beacon node now supports a CLI query command that can extract any data from the genesis state. This is based on new developments in the SSZ navigators.
338 lines
12 KiB
Nim
338 lines
12 KiB
Nim
# beacon_chain
|
|
# Copyright (c) 2018-2019 Status Research & Development GmbH
|
|
# Licensed and distributed under either of
|
|
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
|
|
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
|
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
|
|
|
# At the time of writing, the exact definitions of what should be used for
|
|
# cryptography in the spec is in flux, with sizes and test vectors still being
|
|
# hashed out. This layer helps isolate those chagnes.
|
|
|
|
# Useful conversation about BLS signatures (TODO: condense this)
|
|
#
|
|
# I can probably google this somehow, but bls signatures, anyone knows off the
|
|
# top of their head if they have to be combined one by one, or can two group
|
|
# signatures be combined? what happens to overlap then?
|
|
# Danny Ryan
|
|
# @djrtwo
|
|
# Dec 21 12:00
|
|
# Yeah, you can do any linear combination of signatures. but you have to
|
|
# remember the linear combination of pubkeys that constructed
|
|
# if you have two instances of a signature from pubkey p, then you need 2*p in
|
|
# the group pubkey
|
|
# because the attestation bitfield is only 1 bit per pubkey right now,
|
|
# attestations do not support this
|
|
# it could be extended to support N overlaps up to N times per pubkey if we
|
|
# had N bits per validator instead of 1
|
|
# We are shying away from this for the time being. If there end up being
|
|
# substantial difficulties in network layer aggregation, then adding bits
|
|
# to aid in supporting overlaps is one potential solution
|
|
# Jacek Sieka
|
|
# @arnetheduck
|
|
# Dec 21 12:02
|
|
# ah nice, you anticipated my followup question there :) so it's not a
|
|
# straight-off set union operation
|
|
# Danny Ryan
|
|
# @djrtwo
|
|
# Dec 21 12:02
|
|
# depending on the particular network level troubles we run into
|
|
# right
|
|
# aggregatng sigs and pubkeys are both just ec adds
|
|
# https://github.com/ethereum/py-evm/blob/d82b10ae361cde6abbac62f171fcea7809c4e3cf/eth/_utils/bls.py#L191-L202
|
|
# subtractions work too (i suppose this is obvious). You can linearly combine
|
|
# sigs or pubs in any way
|
|
|
|
|
|
import
|
|
sequtils,
|
|
stew/[endians2, objects, byteutils], hashes, nimcrypto/utils,
|
|
blscurve, json_serialization,
|
|
../version, digest,
|
|
chronicles
|
|
|
|
export
|
|
json_serialization
|
|
|
|
export
|
|
blscurve.init, blscurve.getBytes, blscurve.combine,
|
|
blscurve.`$`, blscurve.`==`,
|
|
blscurve.Signature
|
|
|
|
type
|
|
BlsValueType* = enum
|
|
Real
|
|
OpaqueBlob
|
|
|
|
BlsValue*[T] = object
|
|
# TODO This is a temporary type needed until we sort out the
|
|
# issues with invalid BLS values appearing in the SSZ test suites.
|
|
case kind*: BlsValueType
|
|
of Real:
|
|
blsValue*: T
|
|
of OpaqueBlob:
|
|
when T is blscurve.Signature:
|
|
blob*: array[96, byte]
|
|
else:
|
|
blob*: array[48, byte]
|
|
|
|
ValidatorPubKey* = BlsValue[blscurve.VerKey]
|
|
# ValidatorPubKey* = blscurve.VerKey
|
|
|
|
# ValidatorPubKey* = array[48, byte]
|
|
# The use of byte arrays proved to be a dead end pretty quickly.
|
|
# Plenty of code needs to be modified for a successful build and
|
|
# the changes will negatively affect the performance.
|
|
|
|
# ValidatorPrivKey* = BlsValue[blscurve.SigKey]
|
|
ValidatorPrivKey* = blscurve.SigKey
|
|
|
|
ValidatorSig* = BlsValue[blscurve.Signature]
|
|
|
|
BlsCurveType* = VerKey|SigKey|Signature
|
|
ValidatorPKI* = ValidatorPrivKey|ValidatorPubKey|ValidatorSig
|
|
|
|
proc init*[T](BLS: type BlsValue[T], val: auto): BLS =
|
|
result.kind = BlsValueType.Real
|
|
result.blsValue = init(T, val)
|
|
|
|
func `$`*(x: BlsValue): string =
|
|
if x.kind == Real:
|
|
$x.blsValue
|
|
else:
|
|
# r: is short for random. The prefix must be short
|
|
# due to the mechanics of the `shortLog` function.
|
|
"r:" & toHex(x.blob, true)
|
|
|
|
func `==`*(a, b: BlsValue): bool =
|
|
if a.kind != b.kind: return false
|
|
if a.kind == Real:
|
|
return a.blsValue == b.blsValue
|
|
else:
|
|
return a.blob == b.blob
|
|
|
|
func getBytes*(x: BlsValue): auto =
|
|
if x.kind == Real:
|
|
getBytes x.blsValue
|
|
else:
|
|
x.blob
|
|
|
|
func shortLog*(x: BlsValue): string =
|
|
($x)[0..7]
|
|
|
|
func shortLog*(x: BlsCurveType): string =
|
|
($x)[0..7]
|
|
|
|
proc hash*(x: BlsValue): Hash {.inline.} =
|
|
if x.kind == Real:
|
|
hash x.blsValue.getBytes()
|
|
else:
|
|
hash x.blob
|
|
|
|
template hash*(x: BlsCurveType): Hash =
|
|
hash(getBytes(x))
|
|
|
|
template `==`*[T](a: BlsValue[T], b: T): bool =
|
|
a.blsValue == b
|
|
|
|
template `==`*[T](a: T, b: BlsValue[T]): bool =
|
|
a == b.blsValue
|
|
|
|
func pubKey*(pk: ValidatorPrivKey): ValidatorPubKey =
|
|
when ValidatorPubKey is BlsValue:
|
|
ValidatorPubKey(kind: Real, blsValue: pk.getKey())
|
|
elif ValidatorPubKey is array:
|
|
pk.getKey.getBytes
|
|
else:
|
|
pk.getKey
|
|
|
|
proc init(T: type VerKey): VerKey =
|
|
result.point.inf()
|
|
|
|
proc init(T: type SigKey): SigKey =
|
|
result.point.inf()
|
|
|
|
proc combine*[T](values: openarray[BlsValue[T]]): BlsValue[T] =
|
|
result = BlsValue[T](kind: Real, blsValue: T.init())
|
|
|
|
for value in values:
|
|
result.blsValue.combine(value.blsValue)
|
|
|
|
proc combine*[T](x: var BlsValue[T], other: BlsValue[T]) =
|
|
doAssert x.kind == Real and other.kind == Real
|
|
x.blsValue.combine(other.blsValue)
|
|
|
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.1/specs/bls_signature.md#bls_aggregate_pubkeys
|
|
func bls_aggregate_pubkeys*(keys: openArray[ValidatorPubKey]): ValidatorPubKey =
|
|
keys.combine()
|
|
|
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.1/specs/bls_signature.md#bls_verify
|
|
func bls_verify*(
|
|
pubkey: ValidatorPubKey, msg: openArray[byte], sig: ValidatorSig,
|
|
domain: Domain): bool =
|
|
# name from spec!
|
|
if sig.kind != Real:
|
|
# Invalid signatures are possible in deposits (discussed with Danny)
|
|
return false
|
|
when ValidatorPubKey is BlsValue:
|
|
if sig.kind != Real or pubkey.kind != Real:
|
|
# TODO: chronicles warning
|
|
return false
|
|
sig.blsValue.verify(msg, domain, pubkey.blsValue)
|
|
else:
|
|
sig.verify(msg, domain, pubkey)
|
|
|
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.8.4/specs/bls_signature.md#bls_verify_multiple
|
|
proc bls_verify_multiple*(
|
|
pubkeys: seq[ValidatorPubKey], message_hashes: openArray[Eth2Digest],
|
|
sig: ValidatorSig, domain: Domain): bool =
|
|
# {.noSideEffect.} - https://github.com/status-im/nim-chronicles/issues/62
|
|
let L = len(pubkeys)
|
|
doAssert L == len(message_hashes)
|
|
if sig.kind != Real:
|
|
warn "Raw bytes do not match with a BLS signature."
|
|
return false
|
|
|
|
# TODO optimize using multiPairing
|
|
for pubkey_message_hash in zip(pubkeys, message_hashes):
|
|
let (pubkey, message_hash) = pubkey_message_hash
|
|
doAssert pubkey.kind == Real
|
|
# TODO spec doesn't say to handle this specially, but it's silly to
|
|
# validate without any actual public keys.
|
|
if pubkey.blsValue == VerKey():
|
|
trace "Received empty public key, skipping verification."
|
|
continue
|
|
if not sig.blsValue.verify(message_hash.data, domain, pubkey.blsValue):
|
|
return false
|
|
|
|
true
|
|
|
|
when ValidatorPrivKey is BlsValue:
|
|
func bls_sign*(key: ValidatorPrivKey, msg: openarray[byte],
|
|
domain: Domain): ValidatorSig =
|
|
# name from spec!
|
|
if key.kind == Real:
|
|
ValidatorSig(kind: Real, blsValue: key.blsValue.sign(domain, msg))
|
|
else:
|
|
ValidatorSig(kind: OpaqueBlob)
|
|
else:
|
|
func bls_sign*(key: ValidatorPrivKey, msg: openarray[byte],
|
|
domain: Domain): ValidatorSig =
|
|
# name from spec!
|
|
ValidatorSig(kind: Real, blsValue: key.sign(domain, msg))
|
|
|
|
func fromBytes*[T](R: type BlsValue[T], bytes: openarray[byte]): R =
|
|
# This is a workaround, so that we can deserialize the serialization of a
|
|
# default-initialized BlsValue without raising an exception
|
|
when defined(ssz_testing):
|
|
# Only for SSZ parsing tests, everything is an opaque blob
|
|
R(kind: OpaqueBlob, blob: toArray(result.blob.len, bytes))
|
|
else:
|
|
# Try if valid BLS value
|
|
# TODO: address the side-effects in nim-blscurve
|
|
{.noSideEffect.}:
|
|
let success = init(result.blsValue, bytes)
|
|
if not success:
|
|
# TODO: chronicles trace
|
|
result = R(kind: OpaqueBlob)
|
|
assert result.blob.len == bytes.len
|
|
result.blob[result.blob.low .. result.blob.high] = bytes
|
|
|
|
func fromHex*[T](R: type BlsValue[T], hexStr: string): R =
|
|
fromBytes(R, hexToSeqByte(hexStr))
|
|
|
|
func initFromBytes*[T](val: var BlsValue[T], bytes: openarray[byte]) =
|
|
val = fromBytes(BlsValue[T], bytes)
|
|
|
|
func initFromBytes*(val: var BlsCurveType, bytes: openarray[byte]) =
|
|
val = init(type(val), bytes)
|
|
|
|
proc writeValue*(writer: var JsonWriter, value: ValidatorPubKey) {.inline.} =
|
|
when value is BlsValue:
|
|
doAssert value.kind == Real
|
|
writer.writeValue($value.blsValue)
|
|
else:
|
|
writer.writeValue($value)
|
|
|
|
proc readValue*(reader: var JsonReader, value: var ValidatorPubKey) {.inline.} =
|
|
value.initFromBytes(fromHex reader.readValue(string))
|
|
|
|
proc writeValue*(writer: var JsonWriter, value: ValidatorSig) {.inline.} =
|
|
when value is BlsValue:
|
|
if value.kind == Real:
|
|
writer.writeValue($value.blsValue)
|
|
else:
|
|
# Workaround: https://github.com/status-im/nim-beacon-chain/issues/374
|
|
let asHex = toHex(value.blob, true)
|
|
# echo "[Warning] writing raw opaque signature: ", asHex
|
|
writer.writeValue(asHex)
|
|
else:
|
|
writer.writeValue($value)
|
|
|
|
proc readValue*(reader: var JsonReader, value: var ValidatorSig) {.inline.} =
|
|
value.initFromBytes(fromHex reader.readValue(string))
|
|
|
|
proc writeValue*(writer: var JsonWriter, value: ValidatorPrivKey) {.inline.} =
|
|
when value is BlsValue:
|
|
doAssert value.kind == Real
|
|
writer.writeValue($value.blsValue)
|
|
else:
|
|
writer.writeValue($value)
|
|
|
|
proc readValue*(reader: var JsonReader, value: var ValidatorPrivKey) {.inline.} =
|
|
value.initFromBytes(fromHex reader.readValue(string))
|
|
|
|
when ValidatorPrivKey is BlsValue:
|
|
proc newPrivKey*(): ValidatorPrivKey =
|
|
ValidatorPrivKey(kind: Real, blsValue: SigKey.random())
|
|
else:
|
|
proc newPrivKey*(): ValidatorPrivKey =
|
|
SigKey.random()
|
|
|
|
when networkBackend == rlpxBackend:
|
|
import eth/rlp
|
|
|
|
when ValidatorPubKey is BlsValue:
|
|
proc append*(writer: var RlpWriter, value: ValidatorPubKey) =
|
|
writer.append if value.kind == Real: value.blsValue.getBytes()
|
|
else: value.blob
|
|
else:
|
|
proc append*(writer: var RlpWriter, value: ValidatorPubKey) =
|
|
writer.append value.getBytes()
|
|
|
|
proc read*(rlp: var Rlp, T: type ValidatorPubKey): T {.inline.} =
|
|
result.initFromBytes rlp.toBytes.toOpenArray
|
|
|
|
when ValidatorSig is BlsValue:
|
|
proc append*(writer: var RlpWriter, value: ValidatorSig) =
|
|
writer.append if value.kind == Real: value.blsValue.getBytes()
|
|
else: value.blob
|
|
else:
|
|
proc append*(writer: var RlpWriter, value: ValidatorSig) =
|
|
writer.append value.getBytes()
|
|
|
|
proc read*(rlp: var Rlp, T: type ValidatorSig): T {.inline.} =
|
|
result.initFromBytes rlp.toBytes.toOpenArray
|
|
|
|
proc writeValue*(writer: var JsonWriter, value: VerKey) {.inline.} =
|
|
writer.writeValue($value)
|
|
|
|
proc readValue*(reader: var JsonReader, value: var VerKey) {.inline.} =
|
|
value = VerKey.init(reader.readValue(string))
|
|
|
|
proc writeValue*(writer: var JsonWriter, value: Signature) {.inline.} =
|
|
writer.writeValue($value)
|
|
|
|
proc readValue*(reader: var JsonReader, value: var Signature) {.inline.} =
|
|
value = Signature.init(reader.readValue(string))
|
|
|
|
proc toGaugeValue*(hash: Eth2Digest): int64 =
|
|
# Only the last 8 bytes are taken into consideration in accordance
|
|
# to the ETH2 metrics spec:
|
|
# https://github.com/ethereum/eth2.0-metrics/blob/6a79914cb31f7d54858c7dd57eee75b6162ec737/metrics.md#interop-metrics
|
|
cast[int64](uint64.fromBytesLE(hash.data[24..31]))
|
|
|
|
template fromSszBytes*(T: type BlsValue, bytes: openarray[byte]): auto =
|
|
fromBytes(T, bytes)
|
|
|