# beacon_chain # Copyright (c) 2018-2023 Status Research & Development GmbH # Licensed and distributed under either of # * 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). # at your option. This file may not be copied, modified, or distributed except according to those terms. {.push raises: [].} ## This module contains signature verification helpers corresponding to those ## in signatures.nim, for use with signature sets / batch signature verification ## The functions follow the same structure and use the same arguments, except ## that the flow is split into separate collection and verification steps. import # Status lib blscurve, stew/[byteutils, results], taskpools, bearssl/rand, # Internal "."/[helpers, beaconstate, forks, signatures], "."/datatypes/[altair, bellatrix, phase0] export results, rand, altair, phase0, taskpools, signatures type TaskPoolPtr* = Taskpool BatchVerifier* = object sigVerifCache*: BatchedBLSVerifierCache ## A cache for batch BLS signature verification contexts rng*: ref HmacDrbgContext ## A reference to the Nimbus application-wide RNG taskpool*: TaskPoolPtr proc init*( T: type BatchVerifier, rng: ref HmacDrbgContext, taskpool: TaskPoolPtr): BatchVerifier = BatchVerifier( sigVerifCache: BatchedBLSVerifierCache.init(taskpool), rng: rng, taskpool: taskpool, ) proc new*( T: type BatchVerifier, rng: ref HmacDrbgContext, taskpool: TaskPoolPtr): ref BatchVerifier = (ref BatchVerifier)( sigVerifCache: BatchedBLSVerifierCache.init(taskpool), rng: rng, taskpool: taskpool, ) func `$`*(s: SignatureSet): string = "(pubkey: 0x" & s.pubkey.toHex() & ", signing_root: 0x" & s.message.toHex() & ", signature: 0x" & s.signature.toHex() & ')' # Important: # - Due to lazy loading, when we do crypto verification # and only then state-transition verification, # there is no guarantee that pubkeys and signatures received are valid # unlike when Nimbus did eager loading which ensured they were correct beforehand func init(T: type SignatureSet, pubkey: CookedPubKey, signing_root: Eth2Digest, signature: CookedSig): T = ## Add a new signature set triplet (pubkey, message, signature) ## to a collection of signature sets for batch verification. ( blscurve.PublicKey(pubkey), signing_root.data, blscurve.Signature(signature) ) func aggregateAttesters( validatorIndices: openArray[uint64|ValidatorIndex], validatorKeys: auto, ): Result[CookedPubKey, cstring] = if validatorIndices.len == 0: # Aggregation spec requires non-empty collection # - https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04 # Consensus specs require at least one attesting index in attestation # - https://github.com/ethereum/consensus-specs/blob/v1.4.0-alpha.3/specs/phase0/beacon-chain.md#is_valid_indexed_attestation return err("aggregateAttesters: no attesting indices") let firstKey = validatorKeys.load(validatorIndices[0]).valueOr: return err("aggregateAttesters: invalid attesting index") var attestersAgg{.noinit.}: AggregatePublicKey attestersAgg.init(firstKey) for i in 1 ..< validatorIndices.len: let key = validatorKeys.load(validatorIndices[i]).valueOr: return err("aggregateAttesters: invalid attesting index") attestersAgg.aggregate(key) ok(finish(attestersAgg)) func aggregateAttesters( validatorIndices: openArray[uint64|ValidatorIndex], bits: auto, validatorKeys: auto, ): Result[CookedPubKey, cstring] = if validatorIndices.len == 0: # Aggregation spec requires non-empty collection # - https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04 # Consensus specs require at least one attesting index in attestation # - https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.1/specs/phase0/beacon-chain.md#is_valid_indexed_attestation return err("aggregateAttesters: no attesting indices") var attestersAgg{.noinit.}: AggregatePublicKey var inited = false for i in 0..= ConsensusFork.Altair: if signed_block.message.body.sync_aggregate.sync_committee_bits.isZeros: if signed_block.message.body.sync_aggregate.sync_committee_signature != ValidatorSig.infinity(): return err("collectSignatureSets: empty sync aggregates need signature of point at infinity") else: let current_sync_committee = forkyState.data.get_sync_committee_cache(cache).current_sync_committee previous_slot = max(forkyState.data.slot, Slot(1)) - 1 beacon_block_root = get_block_root_at_slot(forkyState.data, previous_slot) pubkey = ? aggregateAttesters( current_sync_committee, signed_block.message.body.sync_aggregate.sync_committee_bits, validatorKeys) sigs.add sync_committee_message_signature_set( fork, genesis_validators_root, previous_slot, beacon_block_root, pubkey, signed_block.message.body.sync_aggregate.sync_committee_signature.load().valueOr do: return err("collectSignatureSets: cannot load signature")) block: # 8. BLS to execution changes when typeof(signed_block).toFork() >= ConsensusFork.Capella: withState(state): when consensusFork >= ConsensusFork.Capella: for bls_change in signed_block.message.body.bls_to_execution_changes: let sig = bls_change.signature.load.valueOr: return err("collectSignatureSets: cannot load BLS to execution change signature") # Otherwise, expensive loadWithCache can be spammed with irrelevant pubkeys ? check_bls_to_execution_change( genesis_fork, forkyState.data, bls_change, {skipBlsValidation}) let validator_pubkey = bls_change.message.from_bls_pubkey.loadWithCache.valueOr: return err("collectSignatureSets: cannot load BLS to execution change pubkey") sigs.add bls_to_execution_change_signature_set( genesis_fork, genesis_validators_root, bls_change.message, validator_pubkey, sig) ok() proc batchVerify*(verifier: var BatchVerifier, sigs: openArray[SignatureSet]): bool = let bytes = verifier.rng[].generate(array[32, byte]) verifier.taskpool.batchVerify(verifier.sigVerifCache, sigs, bytes)