From b511f3eeb7982cb84f8aa4ad08fbec310b4693f7 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Fri, 16 Aug 2024 23:42:46 +0200 Subject: [PATCH] Use Pippenger multiplication for combining multiple sigs of same msg (#6484) Newer `blst` releases expose multiscalar Pippenger multiplication that allows accelerated verification of signatures pertaining to same msg. - https://gist.github.com/wemeetagain/d52fc4b077f80db6e423935244c2afb2 --- .../gossip_processing/batch_validation.nim | 60 +++++++++++++------ beacon_chain/spec/crypto.nim | 8 +-- 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/beacon_chain/gossip_processing/batch_validation.nim b/beacon_chain/gossip_processing/batch_validation.nim index 5d8445e644..5c98cf10c7 100644 --- a/beacon_chain/gossip_processing/batch_validation.nim +++ b/beacon_chain/gossip_processing/batch_validation.nim @@ -99,7 +99,7 @@ type Batch* = object ## A batch represents up to BatchedCryptoSize non-aggregated signatures created: Moment - sigsets: seq[SignatureSet] + multiSets: Table[array[32, byte], MultiSignatureSet] items: seq[BatchItem] VerifierItem = object @@ -198,7 +198,7 @@ proc complete(batchCrypto: var BatchCrypto, batch: var Batch, ok: bool) = batchCrypto.counts.batches += 1 batchCrypto.counts.signatures += batch.items.len() - batchCrypto.counts.aggregates += batch.sigsets.len() + batchCrypto.counts.aggregates += batch.multiSets.len() if batchCrypto.counts.batches >= 256: # Not too often, so as not to overwhelm our metrics @@ -227,12 +227,29 @@ proc spawnBatchVerifyTask(tp: Taskpool, task: ptr BatchTask) = # Possibly related to: https://github.com/nim-lang/Nim/issues/22305 tp.spawn batchVerifyTask(task) -proc batchVerifyAsync*( - verifier: ref BatchVerifier, signal: ThreadSignalPtr, +func combine( + multiSet: MultiSignatureSet, + verifier: ref BatchVerifier): SignatureSet = + var secureRandomBytes: array[32, byte] + verifier[].rng[].generate(secureRandomBytes) + multiSet.combine(secureRandomBytes) + +func combineAll( + multiSets: Table[array[32, byte], MultiSignatureSet], + verifier: ref BatchVerifier): seq[SignatureSet] = + var sigsets = newSeqOfCap[SignatureSet](multiSets.len) + for multiSet in multiSets.values(): + sigsets.add multiSet.combine(verifier) + sigsets + +proc batchVerifyAsync( + verifier: ref BatchVerifier, + signal: ThreadSignalPtr, batch: ref Batch): Future[bool] {.async: (raises: [CancelledError]).} = + let sigsets = batch[].multiSets.combineAll(verifier) var task = BatchTask( - setsPtr: makeUncheckedArray(baseAddr batch[].sigsets), - numSets: batch[].sigsets.len, + setsPtr: makeUncheckedArray(baseAddr sigsets), + numSets: sigsets.len, taskpool: verifier[].taskpool, cache: addr verifier[].sigVerifCache, signal: signal, @@ -254,18 +271,18 @@ proc batchVerifyAsync*( task.ok.load() proc processBatch( - batchCrypto: ref BatchCrypto, batch: ref Batch, - verifier: ref BatchVerifier, signal: ThreadSignalPtr) {.async: (raises: [CancelledError]).} = - let - numSets = batch[].sigsets.len() + batchCrypto: ref BatchCrypto, + batch: ref Batch, + verifier: ref BatchVerifier, + signal: ThreadSignalPtr) {.async: (raises: [CancelledError]).} = + let numSets = batch[].multiSets.len if numSets == 0: # Nothing to do in this batch, can happen when a batch is created without # there being any signatures successfully added to it return - let - startTick = Moment.now() + let startTick = Moment.now() # If the hardware is too slow to keep up or an event caused a temporary # buildup of signature verification tasks, the batch will be dropped so as to @@ -290,13 +307,19 @@ proc processBatch( # may not be beneficial to use batch verification: # https://github.com/status-im/nim-blscurve/blob/3956f63dd0ed5d7939f6195ee09e4c5c1ace9001/blscurve/bls_batch_verifier.nim#L390 if numSets == 1: - blsVerify(batch[].sigsets[0]) + var r: bool + for multiSet in batch[].multiSets.values(): + r = blsVerify(multiSet.combine(verifier)) + break + r elif batchCrypto[].taskpool.numThreads > 1 and numSets > 3: await batchVerifyAsync(verifier, signal, batch) else: let secureRandomBytes = verifier[].rng[].generate(array[32, byte]) batchVerifySerial( - verifier[].sigVerifCache, batch.sigsets, secureRandomBytes) + verifier[].sigVerifCache, + batch.multiSets.combineAll(verifier), + secureRandomBytes) trace "batch crypto - finished", numSets, items = batch[].items.len(), ok, @@ -356,11 +379,10 @@ proc verifySoon( batch = batchCrypto[].getBatch() fut = newFuture[BatchResult](name) - # TODO If there is a signature set `item in batch[].sigsets.mitems()` - # with `item.message == sigset.message`, further performance could be gained - # by implementing Pippenger multi-scalar multiplication in `nim-blscurve`. - # https://gist.github.com/wemeetagain/d52fc4b077f80db6e423935244c2afb2 - batch[].sigsets.add sigset + batch[].multiSets.withValue(sigset.message, multiSet): + multiSet[].add sigset + do: + batch[].multiSets[sigset.message] = MultiSignatureSet.init sigset # We need to keep the "original" sigset to allow verifying each signature # one by one in the case the combined operation fails diff --git a/beacon_chain/spec/crypto.nim b/beacon_chain/spec/crypto.nim index 9529229f96..d511760761 100644 --- a/beacon_chain/spec/crypto.nim +++ b/beacon_chain/spec/crypto.nim @@ -245,14 +245,14 @@ proc blsVerify*( # Guard against invalid signature blobs that fail to parse parsedSig.isSome() and blsVerify(pubkey, message, parsedSig.get()) -func blsVerify*(sigSet: SignatureSet): bool = +func blsVerify*(sigset: SignatureSet): bool = ## Unbatched verification ## of 1 SignatureSet ## tuple[pubkey: blscurve.PublicKey, message: array[32, byte], blscurve.signature: Signature] verify( - sigSet.pubkey, - sigSet.message, - sigSet.signature + sigset.pubkey, + sigset.message, + sigset.signature ) func blsSign*(privkey: ValidatorPrivKey, message: openArray[byte]): CookedSig =