2018-11-28 19:49:03 +00:00
|
|
|
# beacon_chain
|
2019-02-28 21:24:43 +00:00
|
|
|
# Copyright (c) 2018-2019 Status Research & Development GmbH
|
2018-11-28 19:49:03 +00:00
|
|
|
# Licensed and distributed under either of
|
2019-11-25 15:30:02 +00:00
|
|
|
# * 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).
|
2018-11-28 19:49:03 +00:00
|
|
|
# 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.
|
|
|
|
|
2018-12-27 20:14:37 +00:00
|
|
|
# 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
|
|
|
|
|
|
|
|
|
2018-11-28 19:49:03 +00:00
|
|
|
import
|
2019-10-25 10:59:56 +00:00
|
|
|
stew/[endians2, objects, byteutils], hashes, nimcrypto/utils,
|
2019-03-25 16:46:31 +00:00
|
|
|
blscurve, json_serialization,
|
2019-09-10 22:03:06 +00:00
|
|
|
../version, digest,
|
|
|
|
chronicles
|
2018-12-19 12:58:53 +00:00
|
|
|
|
|
|
|
export
|
2019-03-01 13:34:37 +00:00
|
|
|
json_serialization
|
2019-02-05 16:13:29 +00:00
|
|
|
|
2019-09-03 18:02:21 +00:00
|
|
|
export
|
|
|
|
blscurve.init, blscurve.getBytes, blscurve.combine,
|
|
|
|
blscurve.`$`, blscurve.`==`,
|
|
|
|
blscurve.Signature
|
2018-11-28 19:49:03 +00:00
|
|
|
|
|
|
|
type
|
2019-07-03 07:35:05 +00:00
|
|
|
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]
|
2019-02-05 16:13:29 +00:00
|
|
|
ValidatorPrivKey* = blscurve.SigKey
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
|
|
ValidatorSig* = BlsValue[blscurve.Signature]
|
|
|
|
|
|
|
|
BlsCurveType* = VerKey|SigKey|Signature
|
2019-02-05 16:13:29 +00:00
|
|
|
ValidatorPKI* = ValidatorPrivKey|ValidatorPubKey|ValidatorSig
|
2018-11-29 01:08:34 +00:00
|
|
|
|
2019-07-03 07:35:05 +00:00
|
|
|
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:
|
2019-08-05 00:00:49 +00:00
|
|
|
# r: is short for random. The prefix must be short
|
|
|
|
# due to the mechanics of the `shortLog` function.
|
2019-09-01 15:02:49 +00:00
|
|
|
"r:" & toHex(x.blob, true)
|
2019-07-03 07:35:05 +00:00
|
|
|
|
|
|
|
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 =
|
2019-02-28 21:21:29 +00:00
|
|
|
($x)[0..7]
|
|
|
|
|
2019-11-21 09:15:10 +00:00
|
|
|
func hash*(x: BlsValue): Hash {.inline.} =
|
2019-07-03 07:35:05 +00:00
|
|
|
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
|
|
|
|
|
2019-11-21 09:15:10 +00:00
|
|
|
func init(T: type VerKey): VerKey =
|
2019-09-10 22:03:06 +00:00
|
|
|
result.point.inf()
|
|
|
|
|
2019-11-21 09:15:10 +00:00
|
|
|
func init(T: type SigKey): SigKey =
|
2019-09-10 22:03:06 +00:00
|
|
|
result.point.inf()
|
|
|
|
|
2019-11-21 09:15:10 +00:00
|
|
|
func combine*[T](values: openarray[BlsValue[T]]): BlsValue[T] =
|
2019-09-10 22:03:06 +00:00
|
|
|
result = BlsValue[T](kind: Real, blsValue: T.init())
|
|
|
|
|
|
|
|
for value in values:
|
|
|
|
result.blsValue.combine(value.blsValue)
|
2018-11-29 01:08:34 +00:00
|
|
|
|
2019-11-21 09:15:10 +00:00
|
|
|
func combine*[T](x: var BlsValue[T], other: BlsValue[T]) =
|
2019-07-03 07:35:05 +00:00
|
|
|
doAssert x.kind == Real and other.kind == Real
|
|
|
|
x.blsValue.combine(other.blsValue)
|
2018-12-05 13:58:41 +00:00
|
|
|
|
2019-11-22 08:29:04 +00:00
|
|
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.2/specs/bls_signature.md#bls_aggregate_pubkeys
|
2018-12-17 19:36:17 +00:00
|
|
|
func bls_aggregate_pubkeys*(keys: openArray[ValidatorPubKey]): ValidatorPubKey =
|
2019-09-10 22:03:06 +00:00
|
|
|
keys.combine()
|
2018-12-11 17:55:45 +00:00
|
|
|
|
2019-11-22 08:29:04 +00:00
|
|
|
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.2/specs/bls_signature.md#bls_verify
|
2018-12-17 19:36:17 +00:00
|
|
|
func bls_verify*(
|
2018-12-11 17:55:45 +00:00
|
|
|
pubkey: ValidatorPubKey, msg: openArray[byte], sig: ValidatorSig,
|
2019-09-25 17:07:08 +00:00
|
|
|
domain: Domain): bool =
|
2018-12-11 17:55:45 +00:00
|
|
|
# name from spec!
|
2019-09-11 21:10:54 +00:00
|
|
|
if sig.kind != Real:
|
|
|
|
# Invalid signatures are possible in deposits (discussed with Danny)
|
|
|
|
return false
|
2019-07-03 07:35:05 +00:00
|
|
|
when ValidatorPubKey is BlsValue:
|
2019-09-09 18:40:59 +00:00
|
|
|
if sig.kind != Real or pubkey.kind != Real:
|
|
|
|
# TODO: chronicles warning
|
|
|
|
return false
|
2019-11-13 11:30:11 +00:00
|
|
|
# TODO bls_verify_multiple(...) used to have this workaround, and now it
|
|
|
|
# lives here. No matter the signature, there's also no meaningful way to
|
|
|
|
# verify it -- it's a kind of vacuous truth. No pubkey/sig pairs.
|
2019-11-13 13:18:01 +00:00
|
|
|
if pubkey == default(ValidatorPubKey):
|
2019-11-13 11:30:11 +00:00
|
|
|
return true
|
|
|
|
|
2019-07-03 07:35:05 +00:00
|
|
|
sig.blsValue.verify(msg, domain, pubkey.blsValue)
|
|
|
|
else:
|
|
|
|
sig.verify(msg, domain, pubkey)
|
2019-02-05 16:13:29 +00:00
|
|
|
|
2019-07-03 07:35:05 +00:00
|
|
|
when ValidatorPrivKey is BlsValue:
|
|
|
|
func bls_sign*(key: ValidatorPrivKey, msg: openarray[byte],
|
2019-09-25 17:07:08 +00:00
|
|
|
domain: Domain): ValidatorSig =
|
2019-07-03 07:35:05 +00:00
|
|
|
# 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],
|
2019-09-25 17:07:08 +00:00
|
|
|
domain: Domain): ValidatorSig =
|
2019-07-03 07:35:05 +00:00
|
|
|
# name from spec!
|
|
|
|
ValidatorSig(kind: Real, blsValue: key.sign(domain, msg))
|
|
|
|
|
2019-11-09 10:46:34 +00:00
|
|
|
func fromBytes*[T](R: type BlsValue[T], bytes: openarray[byte]): R =
|
2019-09-05 14:27:28 +00:00
|
|
|
# This is a workaround, so that we can deserialize the serialization of a
|
|
|
|
# default-initialized BlsValue without raising an exception
|
2019-09-05 19:52:34 +00:00
|
|
|
when defined(ssz_testing):
|
2019-09-09 03:33:24 +00:00
|
|
|
# Only for SSZ parsing tests, everything is an opaque blob
|
2019-09-05 19:52:34 +00:00
|
|
|
R(kind: OpaqueBlob, blob: toArray(result.blob.len, bytes))
|
2019-07-03 07:35:05 +00:00
|
|
|
else:
|
2019-09-09 03:33:24 +00:00
|
|
|
# Try if valid BLS value
|
2019-11-09 10:46:34 +00:00
|
|
|
# TODO: address the side-effects in nim-blscurve
|
|
|
|
{.noSideEffect.}:
|
|
|
|
let success = init(result.blsValue, bytes)
|
2019-09-09 03:33:24 +00:00
|
|
|
if not success:
|
|
|
|
# TODO: chronicles trace
|
|
|
|
result = R(kind: OpaqueBlob)
|
2019-11-22 15:47:08 +00:00
|
|
|
doAssert result.blob.len == bytes.len
|
2019-09-09 03:33:24 +00:00
|
|
|
result.blob[result.blob.low .. result.blob.high] = bytes
|
2019-07-03 07:35:05 +00:00
|
|
|
|
2019-11-09 10:46:34 +00:00
|
|
|
func fromHex*[T](R: type BlsValue[T], hexStr: string): R =
|
2019-10-01 13:44:38 +00:00
|
|
|
fromBytes(R, hexToSeqByte(hexStr))
|
|
|
|
|
2019-11-09 10:46:34 +00:00
|
|
|
func initFromBytes*[T](val: var BlsValue[T], bytes: openarray[byte]) =
|
2019-07-03 07:35:05 +00:00
|
|
|
val = fromBytes(BlsValue[T], bytes)
|
|
|
|
|
2019-11-09 10:46:34 +00:00
|
|
|
func initFromBytes*(val: var BlsCurveType, bytes: openarray[byte]) =
|
2019-07-03 07:35:05 +00:00
|
|
|
val = init(type(val), bytes)
|
2018-12-19 12:58:53 +00:00
|
|
|
|
|
|
|
proc writeValue*(writer: var JsonWriter, value: ValidatorPubKey) {.inline.} =
|
2019-07-03 07:35:05 +00:00
|
|
|
when value is BlsValue:
|
|
|
|
doAssert value.kind == Real
|
|
|
|
writer.writeValue($value.blsValue)
|
|
|
|
else:
|
|
|
|
writer.writeValue($value)
|
2018-12-19 12:58:53 +00:00
|
|
|
|
|
|
|
proc readValue*(reader: var JsonReader, value: var ValidatorPubKey) {.inline.} =
|
2019-07-03 07:35:05 +00:00
|
|
|
value.initFromBytes(fromHex reader.readValue(string))
|
2018-12-19 12:58:53 +00:00
|
|
|
|
|
|
|
proc writeValue*(writer: var JsonWriter, value: ValidatorSig) {.inline.} =
|
2019-07-03 07:35:05 +00:00
|
|
|
when value is BlsValue:
|
2019-09-03 18:02:21 +00:00
|
|
|
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)
|
2019-07-03 07:35:05 +00:00
|
|
|
else:
|
|
|
|
writer.writeValue($value)
|
2018-12-19 12:58:53 +00:00
|
|
|
|
|
|
|
proc readValue*(reader: var JsonReader, value: var ValidatorSig) {.inline.} =
|
2019-07-03 07:35:05 +00:00
|
|
|
value.initFromBytes(fromHex reader.readValue(string))
|
2018-12-19 12:58:53 +00:00
|
|
|
|
|
|
|
proc writeValue*(writer: var JsonWriter, value: ValidatorPrivKey) {.inline.} =
|
2019-07-03 07:35:05 +00:00
|
|
|
when value is BlsValue:
|
|
|
|
doAssert value.kind == Real
|
|
|
|
writer.writeValue($value.blsValue)
|
|
|
|
else:
|
|
|
|
writer.writeValue($value)
|
2018-12-19 12:58:53 +00:00
|
|
|
|
|
|
|
proc readValue*(reader: var JsonReader, value: var ValidatorPrivKey) {.inline.} =
|
2019-07-03 07:35:05 +00:00
|
|
|
value.initFromBytes(fromHex reader.readValue(string))
|
2018-12-19 12:58:53 +00:00
|
|
|
|
2019-07-03 07:35:05 +00:00
|
|
|
when ValidatorPrivKey is BlsValue:
|
|
|
|
proc newPrivKey*(): ValidatorPrivKey =
|
|
|
|
ValidatorPrivKey(kind: Real, blsValue: SigKey.random())
|
|
|
|
else:
|
|
|
|
proc newPrivKey*(): ValidatorPrivKey =
|
|
|
|
SigKey.random()
|
2019-03-01 16:51:37 +00:00
|
|
|
|
2019-11-03 23:02:27 +00:00
|
|
|
when networkBackend == rlpx:
|
2019-08-15 16:00:12 +00:00
|
|
|
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()
|
2019-03-01 16:51:37 +00:00
|
|
|
|
2019-08-15 16:00:12 +00:00
|
|
|
proc read*(rlp: var Rlp, T: type ValidatorPubKey): T {.inline.} =
|
|
|
|
result.initFromBytes rlp.toBytes.toOpenArray
|
2019-03-01 16:51:37 +00:00
|
|
|
|
2019-08-15 16:00:12 +00:00
|
|
|
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()
|
2019-03-01 16:51:37 +00:00
|
|
|
|
2019-08-15 16:00:12 +00:00
|
|
|
proc read*(rlp: var Rlp, T: type ValidatorSig): T {.inline.} =
|
|
|
|
result.initFromBytes rlp.toBytes.toOpenArray
|
2019-03-01 16:51:37 +00:00
|
|
|
|
2019-08-07 03:09:26 +00:00
|
|
|
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))
|
2019-10-25 10:59:56 +00:00
|
|
|
|
|
|
|
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]))
|
2019-11-09 10:46:34 +00:00
|
|
|
|
|
|
|
template fromSszBytes*(T: type BlsValue, bytes: openarray[byte]): auto =
|
|
|
|
fromBytes(T, bytes)
|
|
|
|
|