diff --git a/beacon_chain/attestation_pool.nim b/beacon_chain/attestation_pool.nim index 9e8bab09d..1e145a3a8 100644 --- a/beacon_chain/attestation_pool.nim +++ b/beacon_chain/attestation_pool.nim @@ -31,7 +31,7 @@ proc combine*(tgt: var Attestation, src: Attestation, flags: UpdateFlags) = tgt.aggregation_bits.combine(src.aggregation_bits) if skipBlsValidation notin flags: - tgt.signature.combine(src.signature) + tgt.signature.aggregate(src.signature) else: trace "Ignoring overlapping attestations" @@ -341,7 +341,7 @@ proc getAttestationsForBlock*( # one new attestation in there if not attestation.aggregation_bits.overlaps(v.aggregation_bits): attestation.aggregation_bits.combine(v.aggregation_bits) - attestation.signature.combine(v.aggregate_signature) + attestation.signature.aggregate(v.aggregate_signature) result.add(attestation) diff --git a/beacon_chain/conf.nim b/beacon_chain/conf.nim index a8803e0cd..a4fba6db9 100644 --- a/beacon_chain/conf.nim +++ b/beacon_chain/conf.nim @@ -293,4 +293,3 @@ iterator validatorKeys*(conf: BeaconNodeConf): ValidatorPrivKey = template writeValue*(writer: var JsonWriter, value: TypedInputFile|InputFile|InputDir|OutPath|OutDir|OutFile) = writer.writeValue(string value) - diff --git a/beacon_chain/interop.nim b/beacon_chain/interop.nim index 11f9c5797..379f4b6fd 100644 --- a/beacon_chain/interop.nim +++ b/beacon_chain/interop.nim @@ -15,24 +15,19 @@ func get_eth1data_stub*(deposit_count: uint64, current_epoch: Epoch): Eth1Data = block_hash: hash_tree_root(hash_tree_root(voting_period).data), ) -when ValidatorPrivKey is BlsValue: - func makeInteropPrivKey*(i: int): ValidatorPrivKey = - discard - {.fatal: "todo/unused?".} -else: - func makeInteropPrivKey*(i: int): ValidatorPrivKey = - var bytes: array[32, byte] - bytes[0..7] = uint64(i).toBytesLE() +func makeInteropPrivKey*(i: int): ValidatorPrivKey = + var bytes: array[32, byte] + bytes[0..7] = uint64(i).toBytesLE() - let - # BLS381-12 curve order - same as milagro but formatted different - curveOrder = - "52435875175126190479447740508185965837690552500527637822603658699938581184513".parse(UInt256) + let + # BLS381-12 curve order - same as milagro but formatted different + curveOrder = + "52435875175126190479447740508185965837690552500527637822603658699938581184513".parse(UInt256) - privkeyBytes = eth2hash(bytes) - key = (UInt256.fromBytesLE(privkeyBytes.data) mod curveOrder).toBytesBE() + privkeyBytes = eth2hash(bytes) + key = (UInt256.fromBytesLE(privkeyBytes.data) mod curveOrder).toBytesBE() - ValidatorPrivKey.init(key) + result.initFromBytes(key) const eth1BlockHash* = block: var x: Eth2Digest @@ -56,10 +51,10 @@ func makeDeposit*( pubkey: pubkey, withdrawal_credentials: makeWithdrawalCredentials(pubkey))) - if skipBlsValidation notin flags: - ret.data.signature = - bls_sign( - privkey, hash_tree_root(ret.getDepositMessage).data, - compute_domain(DOMAIN_DEPOSIT)) + if skipBLSValidation notin flags: + let domain = compute_domain(DOMAIN_DEPOSIT) + let signing_root = compute_signing_root(ret.getDepositMessage, domain) + + ret.data.signature = bls_sign(privkey, signing_root.data) ret diff --git a/beacon_chain/spec/beaconstate.nim b/beacon_chain/spec/beaconstate.nim index 2850ab691..5677a779d 100644 --- a/beacon_chain/spec/beaconstate.nim +++ b/beacon_chain/spec/beaconstate.nim @@ -73,9 +73,11 @@ proc process_deposit*( if index == -1: # Verify the deposit signature (proof of possession) - if skipBlsValidation notin flags and not bls_verify( - pubkey, hash_tree_root(deposit.getDepositMessage).data, - deposit.data.signature, compute_domain(DOMAIN_DEPOSIT)): + let domain = compute_domain(DOMAIN_DEPOSIT) + let signing_root = compute_signing_root(deposit.getDepositMessage, domain) + if skipBLSValidation notin flags and not bls_verify( + pubkey, signing_root.data, + deposit.data.signature): return false # Add validator and balance entries @@ -214,7 +216,7 @@ func initialize_beacon_state_from_eth1*( latest_block_header: BeaconBlockHeader( body_root: hash_tree_root(BeaconBlockBody( - randao_reveal: BlsValue[Signature](kind: OpaqueBlob) + randao_reveal: ValidatorSig(kind: OpaqueBlob) )) ) ) @@ -263,7 +265,7 @@ func get_initial_beacon_block*(state: BeaconState): SignedBeaconBlock = state_root: hash_tree_root(state), body: BeaconBlockBody( # TODO: This shouldn't be necessary if OpaqueBlob is the default - randao_reveal: BlsValue[Signature](kind: OpaqueBlob)))) + randao_reveal: ValidatorSig(kind: OpaqueBlob)))) # parent_root, randao_reveal, eth1_data, signature, and body automatically # initialized to default values. @@ -381,13 +383,13 @@ proc is_valid_indexed_attestation*( return false # Verify aggregate signature - if skipBlsValidation notin flags and not bls_verify( - bls_aggregate_pubkeys(mapIt(indices, state.validators[it.int].pubkey)), - hash_tree_root(indexed_attestation.data).data, - indexed_attestation.signature, - get_domain( - state, DOMAIN_BEACON_ATTESTER, indexed_attestation.data.target.epoch) - ): + let pubkeys = mapIt(indices, state.validators[it.int].pubkey) # TODO: fuse loops with blsFastAggregateVerify + let domain = state.get_domain(DOMAIN_BEACON_ATTESTER, indexed_attestation.data.target.epoch) + let signing_root = compute_signing_root(indexed_attestation.data, domain) + if skipBLSValidation notin flags and + not blsFastAggregateVerify( + pubkeys, signing_root.data, indexed_attestation.signature + ): notice "indexed attestation: signature verification failure" return false diff --git a/beacon_chain/spec/crypto.nim b/beacon_chain/spec/crypto.nim index fe5ed5876..0f321a708 100644 --- a/beacon_chain/spec/crypto.nim +++ b/beacon_chain/spec/crypto.nim @@ -22,18 +22,26 @@ # even if in theory it is possible to allow this in BLS. import - stew/[endians2, objects, byteutils], hashes, nimcrypto/utils, + # Internal + ./digest, + # Status + stew/[endians2, objects, byteutils], + nimcrypto/[utils, sysrand], blscurve, json_serialization, - digest, - chronicles + chronicles, + # Standard library + hashes export json_serialization -export - blscurve.init, blscurve.getBytes, blscurve.combine, - blscurve.`$`, blscurve.`==`, - blscurve.Signature +# export +# blscurve.init, blscurve.getBytes, blscurve.combine, +# blscurve.`$`, blscurve.`==`, +# blscurve.Signature + +# Type definitions +# ---------------------------------------------------------------------- type BlsValueType* = enum @@ -52,34 +60,22 @@ type else: blob*: array[48, byte] - ValidatorPubKey* = BlsValue[blscurve.VerKey] - # ValidatorPubKey* = blscurve.VerKey + ValidatorPubKey* = BlsValue[blscurve.PublicKey] + # Alternatives + # ValidatorPubKey* = blscurve.PublicKey + # 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. - # 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 + ValidatorPrivKey* = blscurve.SecretKey + # ValidatorPrivKey* = BlsValue[blscurve.SecretKey] ValidatorSig* = BlsValue[blscurve.Signature] - BlsCurveType* = VerKey|SigKey|Signature + BlsCurveType* = PublicKey|SecretKey|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: @@ -87,11 +83,123 @@ func `==`*(a, b: BlsValue): bool = else: return a.blob == b.blob -func getBytes*(x: BlsValue): auto = - if x.kind == Real: - getBytes x.blsValue +template `==`*[T](a: BlsValue[T], b: T): bool = + a.blsValue == b + +template `==`*[T](a: T, b: BlsValue[T]): bool = + a == b.blsValue + +# API +# ---------------------------------------------------------------------- +# https://github.com/ethereum/eth2.0-specs/blob/v0.10.1/specs/phase0/beacon-chain.md#bls-signatures + +func pubKey*(privkey: ValidatorPrivKey): ValidatorPubKey = + ## Create a private key from a public key + # Un-specced in either hash-to-curve or Eth2 + # TODO: Test suite should use `keyGen` instead + when ValidatorPubKey is BlsValue: + ValidatorPubKey(kind: Real, blsValue: privkey.privToPub()) + elif ValidatorPubKey is array: + privkey.getKey.getBytes else: - x.blob + privkey.getKey + +# https://github.com/ethereum/eth2.0-specs/blob/v0.10.1/specs/phase0/beacon-chain.md#bls-signatures +func aggregate*[T](values: openarray[ValidatorSig]): ValidatorSig = + ## Aggregate arrays of sequences of Validator Signatures + ## This assumes that they are real signatures + + result = BlsValue[T](kind: Real, blsValue: values[0].BlsValue) + + for i in 1 ..< values.len: + result.blsValue.aggregate(values[i].blsValue) + +func aggregate*(x: var ValidatorSig, other: ValidatorSig) = + ## Aggregate 2 Validator Signatures + ## This assumes that they are real signatures + x.blsValue.aggregate(other.blsValue) + +# https://github.com/ethereum/eth2.0-specs/blob/v0.10.1/specs/phase0/beacon-chain.md#bls-signatures +func blsVerify*( + pubkey: ValidatorPubKey, message: openArray[byte], + signature: ValidatorSig): bool = + ## Check that a signature is valid for a message + ## under the provided public key. + ## returns `true` if the signature is valid, `false` otherwise. + ## + ## The proof-of-possession MUST be verified before calling this function. + ## It is recommended to use the overload that accepts a proof-of-possession + ## to enforce correct usage. + if signature.kind != Real: + # Invalid signatures are possible in deposits (discussed with Danny) + return false + if pubkey.kind != Real: + # TODO: chronicles warning + return false + + # TODO: remove fully if the comment below is not true anymore and + # and we don't need this workaround + # # 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. Sans a + # # getBytes() or similar mechanism, pubKey == default(ValidatorPubKey) is + # # a way to create many false positive matches. This seems odd. + # if pubkey.getBytes() == default(ValidatorPubKey).getBytes(): + # return true + pubkey.blsValue.verify(message, signature.blsValue) + +func blsSign*(privkey: ValidatorPrivKey, message: openarray[byte]): ValidatorSig = + ## Computes a signature from a secret key and a message + ValidatorSig(kind: Real, blsValue: privkey.sign(message)) + +func blsFastAggregateVerify*[T: byte|char]( + publicKeys: openarray[ValidatorPubKey], + message: openarray[T], + signature: ValidatorSig + ): bool = + ## Verify the aggregate of multiple signatures on the same message + ## This function is faster than AggregateVerify + ## + ## The proof-of-possession MUST be verified before calling this function. + ## It is recommended to use the overload that accepts a proof-of-possession + ## to enforce correct usage. + # TODO: Note: `invalid` in the following paragraph means invalid by construction + # The keys/signatures are not even points on the elliptic curves. + # To respect both the IETF API and the fact that + # we can have invalid public keys (as in not point on the elliptic curve), + # requiring a wrapper indirection, + # we need a first pass to extract keys from the wrapper + # and then call fastAggregateVerify. + # Instead: + # - either we expose a new API: context + init-update-finish + # in blscurve which already exists internally + # - or at network/databases/serialization boundaries we do not + # allow invalid BLS objects to pollute consensus routines + if signature.kind != Real: + return false + var unwrapped: seq[PublicKey] + for pubkey in publicKeys: + if pubkey.kind != Real: + return false + unwrapped.add pubkey.blsValue + return fastAggregateVerify(unwrapped, message, signature.blsValue) + +proc newKeyPair*(): tuple[pub: ValidatorPubKey, priv: ValidatorPrivKey] {.noInit.}= + ## Generates a new public-private keypair + ## This requires entropy on the system + # The input-keying-material requires 32 bytes at least for security + # The generation is deterministic and the input-keying-material + # must be protected against side-channel attacks + + var ikm: array[32, byte] + let written = randomBytes(ikm) + doAssert written >= 32, "Key generation failure" + + result.pub.kind = Real + doAssert keyGen(ikm, result.pub.blsValue, result.priv), "Key generation failure" + +# Logging +# ---------------------------------------------------------------------- func shortLog*(x: BlsValue): string = ($x)[0..7] @@ -99,93 +207,30 @@ func shortLog*(x: BlsValue): string = func shortLog*(x: BlsCurveType): string = ($x)[0..7] -func hash*(x: BlsValue): Hash {.inline.} = +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])) + +# Codecs +# ---------------------------------------------------------------------- + +func `$`*(x: BlsValue): string = if x.kind == Real: - hash x.blsValue.getBytes() + "r: 0x" & x.blsValue.toHex() else: - hash x.blob + # r: is short for random. The prefix must be short + # due to the mechanics of the `shortLog` function. + "r: 0x" & x.blob.toHex(lowercase = true) -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 +func getBytes*(x: BlsValue): auto = + if x.kind == Real: + x.blsValue.exportRaw() else: - pk.getKey + x.blob -func init*(T: type VerKey): VerKey = - result.point.inf() - -func init*(T: type Signature): Signature = - result.point.inf() - -func 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) - -func 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.4/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.4/specs/bls_signature.md#bls_aggregate_signatures -func bls_aggregate_signatures*(keys: openArray[ValidatorSig]): ValidatorSig = - keys.combine() - -# https://github.com/ethereum/eth2.0-specs/blob/v0.9.4/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 - # 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. Sans a - # getBytes() or similar mechanism, pubKey == default(ValidatorPubKey) is - # a way to create many false positive matches. This seems odd. - if pubkey.getBytes() == default(ValidatorPubKey).getBytes(): - return true - - sig.blsValue.verify(msg, domain, pubkey.blsValue) - else: - sig.verify(msg, domain, pubkey) - -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 = +func initFromBytes[T](val: var BlsValue[T], bytes: openarray[byte]) = # This is a workaround, so that we can deserialize the serialization of a # default-initialized BlsValue without raising an exception when defined(ssz_testing): @@ -194,22 +239,40 @@ func fromBytes*[T](R: type BlsValue[T], bytes: openarray[byte]): R = else: # Try if valid BLS value # TODO: address the side-effects in nim-blscurve - {.noSideEffect.}: - let success = init(result.blsValue, bytes) + val = BlsValue[T](kind: Real) + let success = val.blsValue.fromBytes(bytes) if not success: # TODO: chronicles trace - result = R(kind: OpaqueBlob) - doAssert result.blob.len == bytes.len - result.blob[result.blob.low .. result.blob.high] = bytes + val = BlsValue[T](kind: OpaqueBlob) + val.blob[val.blob.low .. val.blob.high] = bytes -func fromHex*[T](R: type BlsValue[T], hexStr: string): R = - fromBytes(R, hexToSeqByte(hexStr)) +func initFromBytes*(val: var ValidatorPrivKey, bytes: openarray[byte]) {.inline.} = + discard val.fromBytes(bytes) -func initFromBytes*[T](val: var BlsValue[T], bytes: openarray[byte]) = - val = fromBytes(BlsValue[T], bytes) +func fromBytes[T](R: type BlsValue[T], bytes: openarray[byte]): R {.inline.}= + result.initFromBytes(bytes) -func initFromBytes*(val: var BlsCurveType, bytes: openarray[byte]) = - val = init(type(val), bytes) +func fromHex*[T](R: var BlsValue[T], hexStr: string) {.inline.} = + ## Initialize a BLSValue from its hex representation + R.fromBytes(hexStr.hexToSeqByte()) + +# Hashing +# ---------------------------------------------------------------------- + +func hash*(x: BlsValue): Hash {.inline.} = + # TODO: we can probably just slice the BlsValue + if x.kind == Real: + hash x.blsValue.exportRaw() + else: + hash x.blob + +template hash*(x: BlsCurveType): Hash = + # TODO: prevent using secret keys + bind getBytes + hash(getBytes(x)) + +# Serialization +# ---------------------------------------------------------------------- proc writeValue*(writer: var JsonWriter, value: ValidatorPubKey) {.inline.} = when value is BlsValue: @@ -246,30 +309,26 @@ proc writeValue*(writer: var JsonWriter, value: ValidatorPrivKey) {.inline.} = 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() - -proc writeValue*(writer: var JsonWriter, value: VerKey) {.inline.} = +proc writeValue*(writer: var JsonWriter, value: PublicKey) {.inline.} = writer.writeValue($value) -proc readValue*(reader: var JsonReader, value: var VerKey) {.inline.} = - value = VerKey.init(reader.readValue(string)) +proc readValue*(reader: var JsonReader, value: var PublicKey) {.inline.} = + let hex = reader.readValue(string) + let ok = value.fromHex(hex) + doAssert ok, "Invalid public key: " & hex 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])) + let hex = reader.readValue(string) + let ok = value.fromHex(hex) + doAssert ok, "Invalid signature: " & hex template fromSszBytes*(T: type BlsValue, bytes: openarray[byte]): auto = fromBytes(T, bytes) + +# For confutils +func init*(T: typedesc[ValidatorPrivKey], hex: string): T {.inline.} = + let success = result.fromHex(hex) + doAssert success, "Private key is invalid" # Don't display private keys even if invalid diff --git a/beacon_chain/spec/datatypes.nim b/beacon_chain/spec/datatypes.nim index 956e30399..7a85f3eb5 100644 --- a/beacon_chain/spec/datatypes.nim +++ b/beacon_chain/spec/datatypes.nim @@ -96,6 +96,9 @@ type DOMAIN_SHARD_PROPOSER = 128 DOMAIN_SHARD_ATTESTER = 129 + # https://github.com/ethereum/eth2.0-specs/blob/v0.10.1/specs/phase0/beacon-chain.md#custom-types + Domain* = array[8, byte] + # https://github.com/nim-lang/Nim/issues/574 and be consistent across # 32-bit and 64-bit word platforms. # TODO VALIDATOR_REGISTRY_LIMIT is 1 shl 40 in 0.8.3, and @@ -324,7 +327,7 @@ type # https://github.com/ethereum/eth2.0-specs/blob/v0.10.1/specs/phase0/beacon-chain.md#signingroot SigningRoot* = object object_root*: Eth2Digest - domain*: uint64 + domain*: Domain # https://github.com/ethereum/eth2.0-specs/blob/v0.10.1/specs/phase0/beacon-chain.md#signedvoluntaryexit SignedVoluntaryExit* = object diff --git a/beacon_chain/spec/helpers.nim b/beacon_chain/spec/helpers.nim index c8d88b388..f9610e00e 100644 --- a/beacon_chain/spec/helpers.nim +++ b/beacon_chain/spec/helpers.nim @@ -12,7 +12,6 @@ import math, # Third-party stew/endians2, - blscurve, # defines Domain # Internal ./datatypes, ./digest, ../ssz diff --git a/beacon_chain/spec/state_transition_block.nim b/beacon_chain/spec/state_transition_block.nim index a355cb2d8..093ba1f79 100644 --- a/beacon_chain/spec/state_transition_block.nim +++ b/beacon_chain/spec/state_transition_block.nim @@ -100,12 +100,9 @@ proc process_randao( let proposer = addr state.validators[proposer_index.get] # Verify that the provided randao value is valid - if skipBlsValidation notin flags and not bls_verify( - proposer.pubkey, - hash_tree_root(epoch.uint64).data, - body.randao_reveal, - get_domain(state, DOMAIN_RANDAO)): - + let signing_root = compute_signing_root(epoch, get_domain(state, DOMAIN_RANDAO)) + if skipBLSValidation notin flags: + if not blsVerify(proposer.pubkey, signing_root.data, body.randao_reveal): notice "Randao mismatch", proposer_pubkey = proposer.pubkey, message = epoch, signature = body.randao_reveal, @@ -167,13 +164,12 @@ proc process_proposer_slashing*( if skipBlsValidation notin flags: for i, signed_header in [proposer_slashing.signed_header_1, proposer_slashing.signed_header_2]: - if not bls_verify( - proposer.pubkey, - hash_tree_root(signed_header.message).data, - signed_header.signature, - get_domain( + let domain = get_domain( state, DOMAIN_BEACON_PROPOSER, - compute_epoch_at_slot(signed_header.message.slot))): + compute_epoch_at_slot(signed_header.message.slot) + ) + let signing_root = compute_signing_root(signed_header.message, domain) + if not blsVerify(proposer.pubkey, signing_root.data, signed_header.signature): notice "Proposer slashing: invalid signature", signature_index = i return false @@ -339,11 +335,8 @@ proc process_voluntary_exit*( # Verify signature if skipBlsValidation notin flags: let domain = get_domain(state, DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch) - if not bls_verify( - validator.pubkey, - hash_tree_root(voluntary_exit).data, - signed_voluntary_exit.signature, - domain): + let signing_root = compute_signing_root(voluntary_exit, domain) + if not bls_verify(validator.pubkey, signing_root.data, signed_voluntary_exit.signature): notice "Exit: invalid signature" return false diff --git a/beacon_chain/validator_pool.nim b/beacon_chain/validator_pool.nim index 28697cbb1..da528616b 100644 --- a/beacon_chain/validator_pool.nim +++ b/beacon_chain/validator_pool.nim @@ -24,6 +24,7 @@ func getValidator*(pool: ValidatorPool, validatorKey: ValidatorPubKey): AttachedValidator = pool.validators.getOrDefault(validatorKey) +# TODO: Honest validator - https://github.com/ethereum/eth2.0-specs/blob/v0.10.1/specs/phase0/validator.md proc signBlockProposal*(v: AttachedValidator, fork: Fork, slot: Slot, blockRoot: Eth2Digest): Future[ValidatorSig] {.async.} = @@ -38,7 +39,8 @@ proc signBlockProposal*(v: AttachedValidator, fork: Fork, slot: Slot, # replaced by something more sensible await sleepAsync(chronos.milliseconds(1)) - result = bls_sign(v.privKey, blockRoot.data, domain) + let signing_root = compute_signing_root(blockRoot, domain) + result = blsSign(v.privKey, signing_root.data) else: error "Unimplemented" quit 1 @@ -56,18 +58,20 @@ proc signAttestation*(v: AttachedValidator, # replaced by something more sensible await sleepAsync(chronos.milliseconds(1)) - result = bls_sign(v.privKey, attestationRoot.data, domain) + let signing_root = compute_signing_root(attestationRoot, domain) + result = blsSign(v.privKey, signing_root.data) else: error "Unimplemented" quit 1 +# https://github.com/ethereum/eth2.0-specs/blob/v0.10.1/specs/phase0/validator.md#randao-reveal func genRandaoReveal*(k: ValidatorPrivKey, fork: Fork, slot: Slot): ValidatorSig = let domain = get_domain(fork, DOMAIN_RANDAO, compute_epoch_at_slot(slot)) - root = hash_tree_root(compute_epoch_at_slot(slot).uint64).data + signing_root = compute_signing_root(compute_epoch_at_slot(slot).uint64, domain) - bls_sign(k, root, domain) + bls_sign(k, signing_root.data) func genRandaoReveal*(v: AttachedValidator, fork: Fork, slot: Slot): ValidatorSig = diff --git a/benchmarks/bench_bls_sig_agggregation.nim b/benchmarks/bench_bls_sig_agggregation.nim index 6bc2ab31f..faefbcd63 100644 --- a/benchmarks/bench_bls_sig_agggregation.nim +++ b/benchmarks/bench_bls_sig_agggregation.nim @@ -138,18 +138,25 @@ proc main(nb_samples: Natural) = for i in 0 ..< proof_of_possessions.len: pop_valid = pop_valid and proof_of_possessions[i].verifyPoP(pubkeys[i]) - var agg_pubkey: VerKey - bench &"Benchmarking {num_validators} public keys aggregation", agg_pubkey: - agg_pubkey = combine(pubkeys) + # TODO: update with IETF API (Eth2 v0.10.1) + # func fastAggregateVerify*[T: byte|char]( + # publicKeys: openarray[PublicKey], + # message: openarray[T], + # signature: Signature # Aggregated signature + # ): bool - var agg_sig: Signature - bench &"Benchmarking {num_validators} signatures aggregation", agg_sig: - agg_sig = combine(signatures) + # var agg_pubkey: VerKey + # bench &"Benchmarking {num_validators} public keys aggregation", agg_pubkey: + # agg_pubkey = combine(pubkeys) - var msg_verif: bool - bench "Benchmarking message verification", msg_verif: - let domain = 0'u64 - msg_verif = agg_sig.verify(msg.data, domain, agg_pubkey) + # var agg_sig: Signature + # bench &"Benchmarking {num_validators} signatures aggregation", agg_sig: + # agg_sig = combine(signatures) + + # var msg_verif: bool + # bench "Benchmarking message verification", msg_verif: + # let domain = 0'u64 + # msg_verif = agg_sig.verify(msg.data, domain, agg_pubkey) ##################### # diff --git a/tests/all_tests.nim b/tests/all_tests.nim index 6df21ff00..a278bb137 100644 --- a/tests/all_tests.nim +++ b/tests/all_tests.nim @@ -18,7 +18,6 @@ import # Unit test ./test_block_pool, ./test_discovery_helpers, ./test_helpers, - ./test_interop, ./test_kvstore, ./test_kvstore_lmdb, ./test_kvstore_sqlite3, @@ -31,6 +30,11 @@ import # Unit test ./test_sync_manager, ./test_honest_validator + # ./test_interop + # TODO: BLS changes in v0.10.1 will generate different interop signatures + # Requires an update of the interop mocked start: https://github.com/ethereum/eth2.0-pm/tree/master/interop/mocked_start + # or of ZRNT / ZCLI to v0.10.1 + import # Refactor state transition unit tests # TODO re-enable when useful # ./spec_block_processing/test_genesis, diff --git a/tests/mocking/mock_attestations.nim b/tests/mocking/mock_attestations.nim index 57b7e15ca..6feb63bd9 100644 --- a/tests/mocking/mock_attestations.nim +++ b/tests/mocking/mock_attestations.nim @@ -60,16 +60,14 @@ proc get_attestation_signature( ): ValidatorSig = let msg = attestation_data.hash_tree_root() - - return bls_sign( - key = privkey, - msg = msg.data, - domain = get_domain( + let domain = get_domain( state = state, domain_type = DOMAIN_BEACON_ATTESTER, message_epoch = attestation_data.target.epoch ) - ) + let signing_root = compute_signing_root(msg, domain) + + return bls_sign(privkey, signing_root.data) proc signMockAttestation*(state: BeaconState, attestation: var Attestation) = var cache = get_empty_per_epoch_cache() @@ -89,7 +87,7 @@ proc signMockAttestation*(state: BeaconState, attestation: var Attestation) = attestation.signature = sig first_iter = false else: - combine(attestation.signature, sig) + aggregate(attestation.signature, sig) proc mockAttestationImpl( state: BeaconState, diff --git a/tests/mocking/mock_blocks.nim b/tests/mocking/mock_blocks.nim index 7fd1d987c..06ecb7553 100644 --- a/tests/mocking/mock_blocks.nim +++ b/tests/mocking/mock_blocks.nim @@ -27,28 +27,27 @@ proc signMockBlockImpl( let privkey = MockPrivKeys[proposer_index] - signedBlock.message.body.randao_reveal = bls_sign( - key = privkey, - msg = block_slot - .compute_epoch_at_slot() - .hash_tree_root() - .data, - domain = get_domain( - state, - DOMAIN_RANDAO, - message_epoch = block_slot.compute_epoch_at_slot(), - ) - ) - signedBlock.signature = bls_sign( - key = privkey, - msg = signedBlock.message.hash_tree_root().data, - domain = get_domain( - state, - DOMAIN_BEACON_PROPOSER, - message_epoch = block_slot.compute_epoch_at_slot(), - ) - ) + block: + let domain = get_domain( + state, + DOMAIN_RANDAO, + message_epoch = block_slot.compute_epoch_at_slot(), + ) + let signing_root = compute_signing_root( + block_slot.compute_epoch_at_slot(), + domain + ) + signedBlock.message.body.randao_reveal = bls_sign(privkey, signing_root.data) + + block: + let domain = get_domain( + state, + DOMAIN_BEACON_PROPOSER, + message_epoch = block_slot.compute_epoch_at_slot(), + ) + let signing_root = compute_signing_root(signedBlock.message, domain) + signedBlock.signature = bls_sign(privkey, signing_root.data) proc signMockBlock*( state: BeaconState, diff --git a/tests/mocking/mock_deposits.nim b/tests/mocking/mock_deposits.nim index 4af959d8a..8d53bda15 100644 --- a/tests/mocking/mock_deposits.nim +++ b/tests/mocking/mock_deposits.nim @@ -23,13 +23,17 @@ func signMockDepositData( privkey: ValidatorPrivKey ) = # No state --> Genesis - deposit_data.signature = bls_sign( - key = privkey, - msg = deposit_data.getDepositMessage().hash_tree_root().data, - domain = compute_domain( + let domain = compute_domain( DOMAIN_DEPOSIT, default(array[4, byte]) # Genesis is fork_version 0 ) + let signing_root = compute_signing_root( + deposit_data.getDepositMessage(), + domain + ) + deposit_data.signature = blsSign( + privkey, + signing_root.data ) func signMockDepositData( @@ -37,13 +41,17 @@ func signMockDepositData( privkey: ValidatorPrivKey, state: BeaconState ) = - deposit_data.signature = bls_sign( - key = privkey, - msg = deposit_data.getDepositMessage().hash_tree_root().data, - domain = get_domain( - state, - DOMAIN_DEPOSIT + let domain = compute_domain( + DOMAIN_DEPOSIT, + default(array[4, byte]) # Genesis is fork_version 0 ) + let signing_root = compute_signing_root( + deposit_data.getDepositMessage(), + domain + ) + deposit_data.signature = blsSign( + privkey, + signing_root.data ) func mockDepositData( diff --git a/tests/mocking/mock_validator_keys.nim b/tests/mocking/mock_validator_keys.nim index 062987d93..07424083f 100644 --- a/tests/mocking/mock_validator_keys.nim +++ b/tests/mocking/mock_validator_keys.nim @@ -17,7 +17,8 @@ import let MockPrivKeys* = block: var privkeys: array[MIN_GENESIS_ACTIVE_VALIDATOR_COUNT, ValidatorPrivKey] for pk in privkeys.mitems(): - pk = newPrivKey() + let pair = newKeyPair() + pk = pair.priv privkeys let MockPubKeys* = block: diff --git a/tests/test_beacon_chain_db.nim b/tests/test_beacon_chain_db.nim index 5144c02bf..cc94ef1b1 100644 --- a/tests/test_beacon_chain_db.nim +++ b/tests/test_beacon_chain_db.nim @@ -60,11 +60,6 @@ suite "Beacon chain DB" & preset(): timedTest "find ancestors" & preset(): var db = init(BeaconChainDB, kvStore MemoryStoreRef.init()) - x: ValidatorSig - y = init(ValidatorSig, x.getBytes()) - - # Silly serialization check that fails without the right import - check: x == y let a0 = SignedBeaconBlock(message: BeaconBlock(slot: GENESIS_SLOT + 0)) diff --git a/tests/test_interop.nim b/tests/test_interop.nim index ab6e7d6a7..f60afdec6 100644 --- a/tests/test_interop.nim +++ b/tests/test_interop.nim @@ -5,6 +5,10 @@ import ../beacon_chain/[extras, interop, ssz], ../beacon_chain/spec/[beaconstate, crypto, helpers, datatypes] +# TODO: BLS changes in v0.10.1 will generate different interop signatures +# Requires an update of the interop mocked start +# or of ZRNT / ZCLI to v0.10.1 + # Interop test yaml, found here: # https://github.com/ethereum/eth2.0-pm/blob/a0b9d22fad424574b1307828f867b30237758468/interop/mocked_start/keygen_10_validators.yaml diff --git a/tests/test_zero_signature.nim b/tests/test_zero_signature.nim index 868d4e517..c259ab859 100644 --- a/tests/test_zero_signature.nim +++ b/tests/test_zero_signature.nim @@ -39,7 +39,7 @@ suite "Zero signature sanity checks": timedTest "SSZ serialization roundtrip of SignedBeaconBlockHeader": let defaultBlockHeader = SignedBeaconBlockHeader( - signature: BlsValue[Signature](kind: OpaqueBlob) + signature: ValidatorSig(kind: OpaqueBlob) ) check: diff --git a/tests/testblockutil.nim b/tests/testblockutil.nim index 504924ae6..34bfb3f05 100644 --- a/tests/testblockutil.nim +++ b/tests/testblockutil.nim @@ -13,21 +13,12 @@ import ../beacon_chain/spec/[beaconstate, crypto, datatypes, digest, helpers, validator] -when ValidatorPrivKey is BlsValue: - func makeFakeValidatorPrivKey(i: int): ValidatorPrivKey = - # 0 is not a valid BLS private key - 1000 helps interop with rust BLS library, - # lighthouse. - # TODO: switch to https://github.com/ethereum/eth2.0-pm/issues/60 - result.kind = BlsValueType.Real - var bytes = uint64(i + 1000).toBytesLE() - copyMem(addr result.blsValue.x[0], addr bytes[0], sizeof(bytes)) -else: - func makeFakeValidatorPrivKey(i: int): ValidatorPrivKey = - # 0 is not a valid BLS private key - 1000 helps interop with rust BLS library, - # lighthouse. - # TODO: switch to https://github.com/ethereum/eth2.0-pm/issues/60 - var bytes = uint64(i + 1000).toBytesLE() - copyMem(addr result.x[0], addr bytes[0], sizeof(bytes)) +func makeFakeValidatorPrivKey(i: int): ValidatorPrivKey = + # 0 is not a valid BLS private key - 1000 helps interop with rust BLS library, + # lighthouse. + # TODO: switch to https://github.com/ethereum/eth2.0-pm/issues/60 + var bytes = uint64(i + 1000).toBytesLE() + copyMem(addr result, addr bytes[0], sizeof(bytes)) func makeFakeHash(i: int): Eth2Digest = var bytes = uint64(i).toBytesLE() @@ -62,10 +53,9 @@ func makeDeposit(i: int, flags: UpdateFlags): Deposit = ) ) - if skipBlsValidation notin flags: - result.data.signature = - bls_sign(privkey, hash_tree_root(result.getDepositMessage).data, - domain) + if skipBLSValidation notin flags: + let signing_root = compute_signing_root(result.getDepositMessage, domain) + result.data.signature = bls_sign(privkey, signing_root.data) func makeInitialDeposits*( n = SLOTS_PER_EPOCH, flags: UpdateFlags = {}): seq[Deposit] = @@ -123,20 +113,16 @@ proc addBlock*( doAssert privKey.pubKey() == proposer.pubkey, "signature key should be derived from private key! - wrong privkey?" - if skipBlsValidation notin flags: - let block_root = hash_tree_root(new_block.message) + if skipBLSValidation notin flags: + let domain = get_domain(state, DOMAIN_BEACON_PROPOSER, + compute_epoch_at_slot(new_block.message.slot)) + let signing_root = compute_signing_root(new_block.message, domain) # We have a signature - put it in the block and we should be done! - new_block.signature = - bls_sign(privKey, block_root.data, - get_domain(state, DOMAIN_BEACON_PROPOSER, - compute_epoch_at_slot(new_block.message.slot))) + new_block.signature = bls_sign(privKey, signing_root.data) doAssert bls_verify( proposer.pubkey, - block_root.data, new_block.signature, - get_domain( - state, DOMAIN_BEACON_PROPOSER, - compute_epoch_at_slot(new_block.message.slot))), + signing_root.data, new_block.signature), "we just signed this message - it should pass verification!" new_block @@ -171,12 +157,11 @@ proc makeAttestation*( aggregation_bits.setBit sac_index let - msg = hash_tree_root(data) + domain = get_domain(state, DOMAIN_BEACON_ATTESTER, data.target.epoch) + signing_root = compute_signing_root(data, domain) sig = - if skipBlsValidation notin flags: - bls_sign( - hackPrivKey(validator), msg.data, - get_domain(state, DOMAIN_BEACON_ATTESTER, data.target.epoch)) + if skipBLSValidation notin flags: + bls_sign(hackPrivKey(validator), signing_root.data) else: ValidatorSig() @@ -223,19 +208,27 @@ proc makeFullAttestations*( let committee = get_beacon_committee(state, slot, index, cache) data = makeAttestationData(state, slot, index, beacon_block_root) - msg = hash_tree_root(data) + domain = get_domain(state, DOMAIN_BEACON_ATTESTER, data.target.epoch) + signing_root = compute_signing_root(data, domain) - var - attestation = Attestation( - aggregation_bits: CommitteeValidatorsBits.init(committee.len), - data: data, - signature: ValidatorSig(kind: Real, blsValue: Signature.init()) + doAssert committee.len() >= 1 + # Initial attestation + var attestation = Attestation( + aggregation_bits: CommitteeValidatorsBits.init(committee.len), + data: data, + signature: bls_sign( + hackPrivKey(state.validators[committee[0]]), + signing_root.data ) - for j in 0..