Batched API update (#103)

* Parallel API update

* Update blscurve/bls_batch_verifier.nim

Co-authored-by: Jacek Sieka <jacek@status.im>

Co-authored-by: Jacek Sieka <jacek@status.im>
This commit is contained in:
Mamy Ratsimbazafy 2020-12-17 07:20:34 +01:00 committed by GitHub
parent 09b4b07731
commit f7d0341f80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 158 additions and 130 deletions

View File

@ -101,38 +101,41 @@ when BLS_BACKEND == BLST:
proc batchVerifyMultiBatchedSerial*(numSigs, iters: int) =
## Verification of N pubkeys signing for N messages
var batcher = init(BatchedBLSVerifierCache)
var batch: seq[SignatureSet]
for i in 0 ..< numSigs:
let (pk, sk) = keyGen()
var hashedMsg: array[32, byte]
hashedMsg.bls_sha256_digest("msg" & $i)
batcher.add(pk, hashedMsg, sk.sign(hashedMsg))
batch.add((pk, hashedMsg, sk.sign(hashedMsg)))
var secureBlindingBytes: array[32, byte]
secureBlindingBytes.bls_sha256_digest("Mr F was here")
var cache: BatchedBLSVerifierCache
bench("Serial batch verify " & $numSigs & " msgs by "& $numSigs & " pubkeys (with blinding)", iters):
secureBlindingBytes.bls_sha256_digest(secureBlindingBytes)
let ok = batcher.batchVerifySerial(secureBlindingBytes)
let ok = cache.batchVerifySerial(batch, secureBlindingBytes)
proc batchVerifyMultiBatchedParallel*(numSigs, iters: int) =
## Verification of N pubkeys signing for N messages
var batcher = init(BatchedBLSVerifierCache)
var batch: seq[SignatureSet]
for i in 0 ..< numSigs:
let (pk, sk) = keyGen()
var hashedMsg: array[32, byte]
hashedMsg.bls_sha256_digest("msg" & $i)
batcher.add(pk, hashedMsg, sk.sign(hashedMsg))
batch.add((pk, hashedMsg, sk.sign(hashedMsg)))
var cache: BatchedBLSVerifierCache
var secureBlindingBytes: array[32, byte]
secureBlindingBytes.bls_sha256_digest("Mr F was here")
bench("Parallel batch verify of " & $numSigs & " msgs by " & $numSigs & " pubkeys (with blinding)", iters):
secureBlindingBytes.bls_sha256_digest(secureBlindingBytes)
let ok = batcher.batchVerifyParallel(secureBlindingBytes)
let ok = cache.batchVerifyParallel(batch, secureBlindingBytes)
when isMainModule:
benchSign(1000)

View File

@ -31,116 +31,60 @@ import
# for Milagro/Miracl, this wouldn't need to be in the BLST specific file
type
SignatureSet = object
SignatureSet* = tuple[pubkey: PublicKey, message: array[32, byte], signature: Signature]
## A (Public Key, Message, Signature) triplet
## that will be batch verified.
## This should not hold GC-ed memory
## as this would complexify multithreading.
## Consequently this assumes that message
##
## `pubkey` can be an aggregate publickey (via `aggregateAll`)
## if `signature` is the corresponding AggregateSignature
## on the same `message`
##
## This assumes that `message`
## is the output of a fixed size hash function.
signature: Signature
pubkey: PublicKey
message: array[32, byte]
##
## `pubkey` and `signature` are assumed to be grouped checked
## which is guaranteed at deserialization from bytes or hex
BatchedBLSVerifierCache* = object
## A type to batch BLS multi signatures (aggregated or individual)
## verification using multiple cores if compiled with OpenMP
sets: seq[SignatureSet]
## This types hold temporary contexts
## to batch BLS multi signatures (aggregated or individual)
## verification.
## As the contexts are heavy, they can be reused
# Per-batch contexts for multithreaded batch verification
batchContexts: seq[ContextMultiAggregateVerify[DST]]
updateResults: seq[tuple[ok: bool, padCacheLine: array[64, byte]]]
func init*(T: type BatchedBLSVerifierCache): T {.inline.} =
## Initialize or reinitialize a batchedBLS Verifier
discard # default initialization
func clear*(batcher: var BatchedBLSVerifierCache) {.inline.} =
## Initialize or reinitialize a batchedBLS Verifier
batcher.sets.setLen(0)
batcher.batchContexts.setLen(0)
func add*(
batcher: var BatchedBLSVerifierCache,
public_key: PublicKey,
message: array[32, byte],
signature: Signature
) {.inline.} =
## Include a (public key, message, signature) triplet
## to the batch for verification.
##
## Assumes subgroup checks
## and infinity checks were done prior.
## This is currently guaranteed
## on deserialization of public keys and signatures.
##
## The proof-of-possession MUST be verified before calling this function.
## For Eth2, the public key must correspond to a valid deposit.
batcher.sets.add SignatureSet(
signature: signature,
pubkey: public_key,
message: message
)
func add*(
batcher: var BatchedBLSVerifierCache,
public_keys: openarray[PublicKey],
message: array[32, byte],
signature: Signature
): bool =
## Include a (array of public keys, message, signature) triplet
## to the batch for verification.
##
## All public keys sign the same message
## and signature is their aggregated signature
##
## Returns false if no public keys are passed
## Returns true otherwise
##
## Assumes subgroup checks
## and infinity checks were done prior.
## This is currently guaranteed
## on deserialization of public keys and signatures.
##
## The proof-of-possession MUST be verified before calling this function.
## For Eth2, the public key must correspond to a valid deposit.
if publicKeys.len == 0:
return false
var aggAffine{.noInit.}: PublicKey
if not aggAffine.aggregateAll(publicKeys):
return false
batcher.sets.add SignatureSet(
signature: signature,
pubkey: aggAffine,
message: message
)
return true
# Serial Batch Verifier
# ----------------------------------------------------------------------
func batchVerifySerial*(
batcher: var BatchedBLSVerifierCache,
cache: var BatchedBLSVerifierCache,
input: openArray[SignatureSet],
secureRandomBytes: array[32, byte]
): bool =
## Single-threaded batch verification
if batcher.sets.len == 0:
## This will verify all the inputs (PublicKey, message, Signature) triplets
## at once and return true if verification is successful.
## If unsuccessful:
## - The input was empty
## - One or more of the inputs was invalid on aggregation
## - One or more of the inputs had an invalid signature
## If knowing which input was problematic is required, they must be checked one by one.
if input.len == 0:
# Spec precondition
return false
batcher.batchContexts.setLen(1)
template ctx: untyped = batcher.batchContexts[0]
batcher.batchContexts[0].init(secureRandomBytes, "")
cache.batchContexts.setLen(1)
template ctx: untyped = cache.batchContexts[0]
ctx.init(secureRandomBytes, "")
# Accumulate line functions
for i in 0 ..< batcher.sets.len:
for i in 0 ..< input.len:
let ok = ctx.update(
batcher.sets[i].pubkey,
batcher.sets[i].message,
batcher.sets[i].signature
input[i].pubkey,
input[i].message,
input[i].signature
)
if not ok:
return false
@ -151,6 +95,23 @@ func batchVerifySerial*(
# Final exponentiation
return ctx.finalVerify()
func batchVerifySerial*(
input: openArray[SignatureSet],
secureRandomBytes: array[32, byte]
): bool =
## Single-threaded batch verification
## This will verify all the inputs (PublicKey, message, Signature) triplets
## at once and return true if verification is successful.
## If unsuccessful:
## - The input was empty
## - One or more of the inputs was invalid on aggregation
## - One or more of the inputs had an invalid signature
## If knowing which input was problematic is required, they must be checked one by one.
# Don't {.noinit.} this or seq capacity will be != 0.
var batcher: BatchedBLSVerifierCache
return batcher.batchVerifySerial(input, secureRandomBytes)
# Parallelized Batch Verifier
# ----------------------------------------------------------------------
# Parallel pairing computation requires the following steps
@ -219,7 +180,7 @@ template checksAndStackTracesOff(body: untyped): untyped =
{.pop.}
checksAndStackTracesOff:
func toPtrUncheckedArray[T](s: seq[T]): ptr UncheckedArray[T] {.inline.} =
func toPtrUncheckedArray[T](s: openarray[T]): ptr UncheckedArray[T] {.inline.} =
{.pragma: restrict, codegenDecl: "$# __restrict $#".}
let p{.restrict.} = cast[
ptr UncheckedArray[T]](
@ -280,12 +241,20 @@ checksAndStackTracesOff:
return contexts[start].merge(contexts[mid])
proc batchVerifyParallel*(
batcher: var BatchedBLSVerifierCache,
cache: var BatchedBLSVerifierCache,
input: openArray[SignatureSet],
secureRandomBytes: array[32, byte]
): bool {.sideeffect.} =
## Multithreaded batch verification
## Requires OpenMP 3.0 (GCC 4.4, 2008)
let numSets = batcher.sets.len.uint32
## If multithreaded with -d:openmp requires OpenMP 3.0 (GCC 4.4, 2008)
## This will verify all the inputs (PublicKey, message, Signature) triplets
## at once and return true if verification is successful.
## If unsuccessful:
## - The input was empty
## - One or more of the inputs was invalid on aggregation
## - One or more of the inputs had an invalid signature
## If knowing which input was problematic is required, they must be checked one by one.
let numSets = input.len.uint32
if numSets == 0:
# Spec precondition
return false
@ -293,16 +262,16 @@ proc batchVerifyParallel*(
let numBatches = min(numSets, omp_get_max_threads().uint32)
# Stage 0: Accumulators - setLen for noinit of seq
batcher.batchContexts.setLen(numBatches.int)
batcher.updateResults.setLen(numBatches.int)
cache.batchContexts.setLen(numBatches.int)
cache.updateResults.setLen(numBatches.int)
# No stacktrace, exception
# or anything that require a GC in a parallel section
# otherwise "attachGC()" is needed in the parallel prologue
# Hence we use raw ptr UncheckedArray instead of seq
let contextsPtr = batcher.batchContexts.toPtrUncheckedArray()
let setsPtr = batcher.sets.toPtrUncheckedArray()
let updateResultsPtr = batcher.updateResults.toPtrUncheckedArray()
let contextsPtr = cache.batchContexts.toPtrUncheckedArray()
let setsPtr = input.toPtrUncheckedArray()
let updateResultsPtr = cache.updateResults.toPtrUncheckedArray()
# Stage 1: Accumulate partial pairings
checksAndStackTracesOff:
@ -324,7 +293,7 @@ proc batchVerifyParallel*(
chunkStart.uint32, uint32(chunkStart+chunkLen)
)
for i in 0 ..< batcher.updateResults.len:
for i in 0 ..< cache.updateResults.len:
if not updateResultsPtr[i].ok:
return false
@ -345,13 +314,32 @@ proc batchVerifyParallel*(
if not ok:
return false
return batcher.batchContexts[0].finalVerify()
return cache.batchContexts[0].finalVerify()
proc batchVerifyParallel*(
input: openArray[SignatureSet],
secureRandomBytes: array[32, byte]
): bool =
## Multithreaded batch verification
## If multithreaded (with -d:openmp) requires OpenMP 3.0 (GCC 4.4, 2008)
## This will verify all the inputs (PublicKey, message, Signature) triplets
## at once and return true if verification is successful.
## If unsuccessful:
## - The input was empty
## - One or more of the inputs was invalid on aggregation
## - One or more of the inputs had an invalid signature
## If knowing which input was problematic is required, they must be checked one by one.
# Don't {.noinit.} this or seq capacity will be != 0.
var batcher: BatchedBLSVerifierCache
return batcher.batchVerifyParallel(input, secureRandomBytes)
# Autoselect Batch Verifier
# ----------------------------------------------------------------------
proc batchVerify*(
batcher: var BatchedBLSVerifierCache,
cache: var BatchedBLSVerifierCache,
input: openArray[SignatureSet],
secureRandomBytes: array[32, byte]
): bool =
## Verify all signatures in batch at once.
@ -366,7 +354,31 @@ proc batchVerify*(
## The blinding scheme also assumes that the attacker cannot
## resubmit 2^64 times forged (publickey, message, signature) triplets
## against the same `secureRandomBytes`
if batcher.sets.len >= 3:
batcher.batchVerifyParallel(secureRandomBytes)
when defined(openmp):
if input.len >= 3:
return cache.batchVerifyParallel(input, secureRandomBytes)
else:
return cache.batchVerifySerial(input, secureRandomBytes)
else:
batcher.batchVerifySerial(secureRandomBytes)
return cache.batchVerifySerial(input, secureRandomBytes)
proc batchVerify*(
input: openArray[SignatureSet],
secureRandomBytes: array[32, byte]
): bool =
## Verify all signatures in batch at once.
## Returns true if all signatures are correct
## Returns false if there is at least one incorrect signature
##
## This requires securely generated random bytes
## for scalar blinding
## to defend against forged signatures that would not
## verify individually but would verify while aggregated.
##
## The blinding scheme also assumes that the attacker cannot
## resubmit 2^64 times forged (publickey, message, signature) triplets
## against the same `secureRandomBytes`
# Don't {.noinit.} this or seq capacity will be != 0.
var batcher: BatchedBLSVerifierCache
return batcher.batchVerify(input, secureRandomBytes)

View File

@ -44,11 +44,11 @@ proc keyGen(seed: uint64): tuple[pubkey: PublicKey, seckey: SecretKey] =
proc hash[T: byte|char](message: openarray[T]): array[32, byte] {.noInit.}=
result.bls_sha256_digest(message)
proc addExample(batcher: var BatchedBLSVerifierCache, seed: int, message: string) =
proc addExample(batch: var seq[SignatureSet], seed: int, message: string) =
let (pubkey, seckey) = keyGen(seed.uint64)
let hashed = hash(message)
let sig = seckey.sign(hashed)
batcher.add(pubkey, hashed, sig)
batch.add((pubkey, hashed, sig))
# Test strategy
# As we use a tree algorithm we want to test
@ -69,46 +69,57 @@ suite "Batch verification " & omp_status():
let (pubkey, seckey) = keyGen(123)
let sig = seckey.sign(msg)
var batcher = init(BatchedBLSVerifierCache)
var cache: BatchedBLSVerifierCache
var batch: seq[SignatureSet]
batcher.add(pubkey, msg, sig)
batch.add((pubkey, msg, sig))
check:
batcher.batchVerify(fakeRandomBytes)
cache.batchVerify(batch, fakeRandomBytes)
batchVerify(batch, fakeRandomBytes)
wrappedTest "Verify 2 (pubkey, message, signature) triplets":
var batcher = init(BatchedBLSVerifierCache)
batcher.addExample(1, "msg1")
batcher.addExample(2, "msg2")
var cache: BatchedBLSVerifierCache
var batch: seq[SignatureSet]
batch.addExample(1, "msg1")
batch.addExample(2, "msg2")
check:
batcher.batchVerify(fakeRandomBytes)
cache.batchVerify(batch, fakeRandomBytes)
batchVerify(batch, fakeRandomBytes)
wrappedTest "Verify 2^4 - 1 = 15 (pubkey, message, signature) triplets":
var batcher = init(BatchedBLSVerifierCache)
var cache: BatchedBLSVerifierCache
var batch: seq[SignatureSet]
for i in 0 ..< 15:
batcher.addExample(i, "msg" & $i)
batch.addExample(i, "msg" & $i)
check:
batcher.batchVerify(fakeRandomBytes)
cache.batchVerify(batch, fakeRandomBytes)
batchVerify(batch, fakeRandomBytes)
wrappedTest "Verify 2^4 = 16 (pubkey, message, signature) triplets":
var batcher = init(BatchedBLSVerifierCache)
var cache: BatchedBLSVerifierCache
var batch: seq[SignatureSet]
for i in 0 ..< 16:
batcher.addExample(i, "msg" & $i)
batch.addExample(i, "msg" & $i)
check:
batcher.batchVerify(fakeRandomBytes)
cache.batchVerify(batch, fakeRandomBytes)
batchVerify(batch, fakeRandomBytes)
wrappedTest "Verify 2^4 + 1 = 17 (pubkey, message, signature) triplets":
var batcher = init(BatchedBLSVerifierCache)
var cache: BatchedBLSVerifierCache
var batch: seq[SignatureSet]
for i in 0 ..< 17:
batcher.addExample(i, "msg" & $i)
batch.addExample(i, "msg" & $i)
check:
batcher.batchVerify(fakeRandomBytes)
cache.batchVerify(batch, fakeRandomBytes)
batchVerify(batch, fakeRandomBytes)
wrappedTest "Wrong signature":
let msg1 = hash"msg1"
@ -118,9 +129,11 @@ suite "Batch verification " & omp_status():
let (pubkey2, seckey2) = keyGen(2)
var batcher = init(BatchedBLSVerifierCache)
var cache: BatchedBLSVerifierCache
var batch: seq[SignatureSet]
batcher.add(pubkey1, msg1, sig1)
batcher.add(pubkey2, msg2, sig1) # <--- wrong signature
batch.add((pubkey1, msg1, sig1))
batch.add((pubkey2, msg2, sig1)) # <--- wrong signature
check:
not batcher.batchVerify(fakeRandomBytes)
not cache.batchVerify(batch, fakeRandomBytes)
not batchVerify(batch, fakeRandomBytes)