Benchmarks rework (#97)

* revive Miracl primitives benchmark

* Revive BLST benchmarks

* Bench hash-to-curve

* Add benchmark of BLS sign, verify and fastAggregateVerify

* Bench all + add benchmarks to CI

* don't bench on 32-bit, inline ASM issue with low-level calls (but high level calls are fine)

* Actually it's the SHA256 tests on 32-bit that causes ASM issue due to inlined headers

* don't bench at all on 32-bit for now

* fix: don't test SH1256 on PowerPC
This commit is contained in:
Mamy Ratsimbazafy 2020-12-05 14:20:18 +01:00 committed by GitHub
parent 282d1f6831
commit c925b0c13c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 333 additions and 134 deletions

View File

@ -1,15 +1,26 @@
import import
../blscurve,
./bls12381_curve, ./bls12381_curve,
./hash_to_curve ./hash_to_curve,
./bls_signature
# Curve operations
benchScalarMultG1(1000) benchScalarMultG1(1000)
benchScalarMultG2(1000) benchScalarMultG2(1000)
benchEcAddG1(1000) benchEcAddG1(1000)
benchEcAddG2(1000) benchEcAddG2(1000)
benchPairingViaDoublePairing(1000) # Pairings
benchPairingViaMultiPairing(1000) when BLS_BACKEND == BLST:
benchBLSTPairing(1000)
else:
benchMiraclPairingViaDoublePairing(1000)
benchMiraclPairingViaMultiPairing(1000)
echo "\n⚠️ Warning: using draft v5 of IETF Hash-To-Curve (HKDF-based)." # Hash-to-curve implementation
echo " This is an outdated draft.\n"
benchHashToG2(1000) benchHashToG2(1000)
# High-level BLS signature scheme
benchSign(1000)
benchVerify(1000)
benchFastAggregateVerify(numKeys = 128, iters = 10)

View File

@ -13,6 +13,8 @@ import
# Standard library # Standard library
std/[monotimes, times, strformat, strutils, macros] std/[monotimes, times, strformat, strutils, macros]
from ../blscurve import BLS_BACKEND
# warmup # warmup
proc warmup*() = proc warmup*() =
# Warmup - make sure cpu is on max perf # Warmup - make sure cpu is on max perf
@ -41,19 +43,15 @@ else:
echo "Optimization level => no optimization: ", not defined(release), " | release: ", defined(release), " | danger: ", defined(danger) echo "Optimization level => no optimization: ", not defined(release), " | release: ", defined(release), " | danger: ", defined(danger)
when (sizeof(int) == 4) or defined(use32):
echo "⚠️ Warning: using Milagro with 32-bit limbs"
else:
echo "Using Milagro with 64-bit limbs"
when SupportsCPUName: when SupportsCPUName:
echo "Running on ", cpuName(), "\n\n" echo "Running on ", cpuName(), "\n"
when SupportsGetTicks: when SupportsGetTicks:
echo "\n⚠️ Cycles measurements are approximate and use the CPU nominal clock: Turbo-Boost and overclocking will skew them." echo "\n⚠️ Cycles measurements are approximate and use the CPU nominal clock: Turbo-Boost and overclocking will skew them."
echo "i.e. a 20% overclock will be about 20% off (assuming no dynamic frequency scaling)" echo "i.e. a 20% overclock will be about 20% off (assuming no dynamic frequency scaling)"
echo "\n=================================================================================================================\n" echo "\nBackend: ", $BLS_BACKEND, ", mode: ", if defined(use32): $32 else: $(sizeof(int) * 8), "-bit"
echo "=================================================================================================================\n"
proc report(op: string, start, stop: MonoTime, startClk, stopClk: int64, iters: int) = proc report(op: string, start, stop: MonoTime, startClk, stopClk: int64, iters: int) =
let ns = inNanoseconds((stop-start) div iters) let ns = inNanoseconds((stop-start) div iters)

View File

@ -8,13 +8,17 @@
# those terms. # those terms.
import import
# Internals std/random,
../blscurve/common, ../blscurve,
../blscurve/milagro, ./bench_templates
../blscurve/hash_to_curve,
# Bench when BLS_BACKEND == BLST:
./bench_templates, import
./keygen ../blscurve/blst/blst_abi
else:
import
../blscurve/miracl/[common, milagro],
../blscurve/miracl/hash_to_curve
# ############################################################ # ############################################################
# #
@ -23,79 +27,202 @@ import
# #
# ############################################################ # ############################################################
var benchRNG = initRand(0xFACADE)
proc benchScalarMultG1*(iters: int) = proc benchScalarMultG1*(iters: int) =
var x = generator1() when BLS_BACKEND == BLST:
var scal: BIG_384 var x{.noInit.}: blst_p1
random(scal) x.blst_p1_from_affine(BLS12_381_G1) # init from generator
bench("Scalar multiplication G1", iters): var scal{.noInit.}: array[32, byte]
x.mul(scal) for val in scal.mitems:
val = byte benchRNG.rand(0xFF)
var scalar{.noInit.}: blst_scalar
scalar.blst_scalar_from_bendian(scal)
bench("Scalar multiplication G1 (255-bit, constant-time)", iters):
x.blst_p1_mult(x, scalar, 255)
else:
var x = generator1()
var scal: BIG_384
random(scal)
scal.BIG_384_mod(CURVE_Order)
bench("Scalar multiplication G1 (255-bit, constant-time)", iters):
x.mul(scal)
proc benchScalarMultG2*(iters: int) = proc benchScalarMultG2*(iters: int) =
var x = generator2() when BLS_BACKEND == BLST:
var scal: BIG_384 var x{.noInit.}: blst_p2
random(scal) x.blst_p2_from_affine(BLS12_381_G2) # init from generator
bench("Scalar multiplication G2", iters): var scal{.noInit.}: array[32, byte]
x.mul(scal) for val in scal.mitems:
val = byte benchRNG.rand(0xFF)
var scalar{.noInit.}: blst_scalar
scalar.blst_scalar_from_bendian(scal)
bench("Scalar multiplication G2 (255-bit, constant-time)", iters):
x.blst_p2_mult(x, scalar, 255)
else:
var x = generator2()
var scal: BIG_384
random(scal)
scal.BIG_384_mod(CURVE_Order)
bench("Scalar multiplication G2 (255-bit, constant-time)", iters):
x.mul(scal)
proc benchECAddG1*(iters: int) = proc benchECAddG1*(iters: int) =
var x = generator1() when BLS_BACKEND == BLST:
var y = generator1() var x{.noInit.}, y{.noInit.}: blst_p1
x.blst_p1_from_affine(BLS12_381_G1) # init from generator
y = x
bench("EC add G1", iters): bench("EC add G1 (constant-time)", iters):
x.add(y) x.blst_p1_add_or_double(x, y)
else:
var x = generator1()
var y = generator1()
bench("EC add G1 (constant-time)", iters):
x.add(y)
proc benchECAddG2*(iters: int) = proc benchECAddG2*(iters: int) =
var x = generator2() when BLS_BACKEND == BLST:
var y = generator2() var x{.noInit.}, y{.noInit.}: blst_p2
x.blst_p2_from_affine(BLS12_381_G2) # init from generator
y = x
bench("EC add G2", iters): bench("EC add G2 (constant-time)", iters):
x.add(y) x.blst_p2_add_or_double(x, y)
else:
var x = generator2()
var y = generator2()
proc benchPairingViaDoublePairing*(iters: int) = bench("EC add G2 (constant-time)", iters):
## Builtin Milagro Double-Pairing implementation x.add(y)
# Ideally we don't depend on the bls_signature_scheme but it's much simpler
let (pubkey, seckey) = newKeyPair()
let msg = "msg"
const domainSepTag = "BLS_SIG_BLS12381G2-SHA256-SSWU-RO_POP_"
# Signing when BLS_BACKEND == BLST:
var sig = hashToG2(msg, domainSepTag)
sig.mul(seckey)
# Verification proc benchBLSTPairing*(iters: int) =
let generator = generator1() let (pubkey, seckey) = block:
let Q = hashToG2(msg, domainSepTag) var pk: PublicKey
# Pairing: e(Q, xP) == e(R, P) var sk: SecretKey
bench("Pairing (Milagro builtin double pairing)", iters): var ikm: array[32, byte]
let valid = doublePairing( ikm[0] = 0x12
Q, pubkey, discard ikm.keygen(pk, sk)
sig, generator (cast[blst_p1_affine](pk), cast[blst_scalar](sk))
let msg = "Mr F was here"
const domainSepTag = "BLS_SIG_BLS12381G2-SHA256-SSWU-RO_POP_"
# Signing
var sig = block:
var sig {.noInit.}: blst_p2_affine
var s {.noInit.}: blst_p2
s.blst_hash_to_g2(
msg,
domainSepTag,
aug = ""
)
s.blst_sign_pk_in_g1(s, seckey)
sig.blst_p2_to_affine(s)
sig
# Verification
let ctx = createU(blst_pairing) # Heap to avoid stack smashing
ctx[].blst_pairing_init(
hash_or_encode = kHash,
domainSepTag
)
doAssert BLST_SUCCESS == ctx[].blst_pairing_aggregate_pk_in_g1(
PK = pubkey.unsafeAddr,
signature = nil,
msg,
aug = ""
)
doAssert BLST_SUCCESS == ctx[].blst_pairing_aggregate_pk_in_g1(
PK = nil,
signature = sig.unsafeAddr,
msg = "",
aug = ""
) )
proc benchPairingViaMultiPairing*(iters: int) = # Cache the benchmarking context, there will be a ~8MB copy overhead (context size)
## MultiPairing implementation let ctxSave = createU(blst_pairing)
## Using deferred Miller loop + Final Exponentiation ctxSave[] = ctx[]
# Ideally we don't depend on the bls_signature_scheme but it's much simpler
let (pubkey, seckey) = newKeyPair()
let msg = "msg"
const domainSepTag = "BLS_SIG_BLS12381G2-SHA256-SSWU-RO_POP_"
# Signing ctx[].blst_pairing_commit() # Miller loop
var sig = hashToG2(msg, domainSepTag) let valid = ctx[].blst_pairing_finalVerify(nil) # Final Exponentiation
sig.mul(seckey) doAssert bool valid
# Verification # Pairing: e(Q, xP) == e(R, P)
let generator = generator1() bench("Pairing (Miller loop + Final Exponentiation)", iters):
let Q = hashToG2(msg, domainSepTag) ctx[] = ctxSave[]
# Pairing: e(Q, xP) == e(R, P) ctx[].blst_pairing_commit() # Miller loop
bench("Pairing (Multi-Pairing with delayed Miller and Exp)", iters): let valid = ctx[].blst_pairing_finalVerify(nil) # Final Exponentiation
let valid = multiPairing( # doAssert bool valid
Q, pubkey,
sig, generator else:
)
proc benchMiraclPairingViaDoublePairing*(iters: int) =
## Builtin Miracl Double-Pairing implementation
# Ideally we don't depend on the bls_signature_scheme but it's much simpler
let (pubkey, seckey) = block:
var pk: PublicKey
var sk: SecretKey
var ikm: array[32, byte]
ikm[0] = 0x12
discard ikm.keygen(pk, sk)
(cast[ECP_BLS12381](pk), cast[BIG_384](sk))
let msg = "Mr F was here"
const domainSepTag = "BLS_SIG_BLS12381G2-SHA256-SSWU-RO_POP_"
# Signing
var sig = hashToG2(msg, domainSepTag)
sig.mul(seckey)
# Verification
let generator = generator1()
let Q = hashToG2(msg, domainSepTag)
# Pairing: e(Q, xP) == e(R, P)
bench("Pairing (Milagro builtin double pairing)", iters):
let valid = doublePairing(
Q, pubkey,
sig, generator
)
# doAssert valid
proc benchMiraclPairingViaMultiPairing*(iters: int) =
## MultiPairing implementation
## Using deferred Miller loop + Final Exponentiation
# Ideally we don't depend on the bls_signature_scheme but it's much simpler
let (pubkey, seckey) = block:
var pk: PublicKey
var sk: SecretKey
var ikm: array[32, byte]
ikm[0] = 0x12
discard ikm.keygen(pk, sk)
(cast[ECP_BLS12381](pk), cast[BIG_384](sk))
let msg = "Mr F was here"
const domainSepTag = "BLS_SIG_BLS12381G2-SHA256-SSWU-RO_POP_"
# Signing
var sig = hashToG2(msg, domainSepTag)
sig.mul(seckey)
# Verification
let generator = generator1()
let Q = hashToG2(msg, domainSepTag)
# Pairing: e(Q, xP) == e(R, P)
bench("Pairing (Multi-Pairing with delayed Miller and Exp)", iters):
let valid = multiPairing(
Q, pubkey,
sig, generator
)
# doAssert valid
when isMainModule: when isMainModule:
benchScalarMultG1(1000) benchScalarMultG1(1000)
@ -103,5 +230,8 @@ when isMainModule:
benchEcAddG1(1000) benchEcAddG1(1000)
benchEcAddG2(1000) benchEcAddG2(1000)
benchPairingViaDoublePairing(1000) when BLS_BACKEND == BLST:
benchPairingViaMultiPairing(1000) benchBLSTPairing(1000)
else:
benchMiraclPairingViaDoublePairing(1000)
benchMiraclPairingViaMultiPairing(1000)

View File

@ -7,6 +7,10 @@
# This file may not be copied, modified, or distributed except according to # This file may not be copied, modified, or distributed except according to
# those terms. # those terms.
import
std/random,
../blscurve,
./bench_templates
# ############################################################ # ############################################################
# #
@ -14,3 +18,72 @@
# (Boneh-Lynn-Schacham) # (Boneh-Lynn-Schacham)
# #
# ############################################################ # ############################################################
var benchRNG = initRand(0xFACADE)
proc benchSign*(iters: int) =
let msg = "Mr F was here"
var pk: PublicKey
var sk: SecretKey
var ikm: array[32, byte]
for b in ikm.mitems:
b = byte benchRNG.rand(0xFF)
doAssert ikm.keyGen(pk, sk)
bench("BLS signature", iters):
let sig = sk.sign(msg)
proc benchVerify*(iters: int) =
let msg = "Mr F was here"
var pk: PublicKey
var sk: SecretKey
var ikm: array[32, byte]
for b in ikm.mitems:
b = byte benchRNG.rand(0xFF)
doAssert ikm.keyGen(pk, sk)
let sig = sk.sign(msg)
bench("BLS verification", iters):
let valid = pk.verify(msg, sig)
# doAssert valid
proc benchFastAggregateVerify*(numKeys, iters: int) =
let msg = "Mr F was here"
var validators = newSeq[PublicKey](numKeys)
var aggSig: AggregateSignature
for i in 0 ..< numKeys:
var pk: PublicKey
var sk: SecretKey
var ikm: array[32, byte]
for b in ikm.mitems:
b = byte benchRNG.rand(0xFF)
doAssert ikm.keyGen(pk, sk)
validators[i] = pk
let sig = sk.sign(msg)
if i == 0:
aggSig.init(sig)
else:
aggSig.aggregate(sig)
var finalSig: Signature
finalSig.finish(aggSig)
bench("BLS agg verif of 1 msg by " & $numKeys & " pubkeys", iters):
let valid = validators.fastAggregateVerify(msg, finalSig)
doAssert valid
when isMainModule:
benchSign(1000)
benchVerify(1000)
benchFastAggregateVerify(numKeys = 128, iters = 10)

View File

@ -8,30 +8,46 @@
# those terms. # those terms.
import import
# Internals std/random,
../blscurve/[common, milagro, hash_to_curve], ../blscurve,
# Bench
./bench_templates ./bench_templates
when BLS_BACKEND == BLST:
import
../blscurve/blst/blst_abi
else:
import
../blscurve/miracl/[common, milagro],
../blscurve/miracl/hash_to_curve
# ############################################################ # ############################################################
# #
# Benchmark of Hash to G2 of BLS12-381 # Benchmark of Hash to G2 of BLS12-381
# Using Draft #5 of IETF spec (HKDF-based) # Using Draft #9 of IETF spec
# #
# ############################################################ # ############################################################
# https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-05#appendix-C.3 # https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-09#appendix-H.10
proc benchHashToG2*(iters: int) = proc benchHashToG2*(iters: int) =
const dst = "BLS_SIG_BLS12381G2-SHA256-SSWU-RO_POP_" const dst = "BLS_SIG_BLS12381G2-SHA256-SSWU-RO_POP_"
let msg = "msg" let msg = "Mr F was here"
var point: ECP2_BLS12381 when BLS_BACKEND == BLST:
var P: blst_p2
var Paff: blst_p2_affine
bench("Hash to G2 (Draft #5)", iters): bench("Hash to G2 (Draft #9) + affine conversion", iters):
point = hashToG2(msg, dst) P.blst_hash_to_g2(
msg,
dst,
aug = ""
)
Paff.blst_p2_to_affine(P)
else:
var point: ECP2_BLS12381
bench("Hash to G2 (Draft #9)", iters):
point = hashToG2(msg, dst)
when isMainModule: when isMainModule:
echo "⚠️ Warning: using draft v5 of IETF Hash-To-Curve (HKDF-based)."
echo " This is an outdated draft.\n\n"
benchHashToG2(1000) benchHashToG2(1000)

View File

@ -1,35 +0,0 @@
# Nim-BLSCurve
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.
import
# Status libraries
nimcrypto/sysrand,
# Internals
../blscurve/bls_signature_scheme,
../blscurve/milagro
proc newKeyPair*(): tuple[pubkey: ECP_BLS12381, seckey: BIG_384] {.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"
var pk: PublicKey
var sk: SecretKey
doAssert keyGen(ikm, pk, sk), "Key generation failure"
# We cast because the fields are normally private to the signature module
result.pubkey = cast[ECP_BLS12381](pk)
result.seckey = cast[BIG_384](sk)

View File

@ -47,18 +47,23 @@ task test, "Run all tests":
# Internal SHA256 # Internal SHA256
test "-d:BLS_FORCE_BACKEND=blst", "tests/blst_sha256.nim" test "-d:BLS_FORCE_BACKEND=blst", "tests/blst_sha256.nim"
# # Ensure benchmarks stay relevant. Ignore Windows 32-bit at the moment # Ensure benchmarks stay relevant.
# if not defined(windows) or not existsEnv"PLATFORM" or getEnv"PLATFORM" == "x64": # TODO, solve "inconsistent operand constraints"
# exec "nim c -d:danger --outdir:build -r" & # on 32-bit for asm volatile, this might be due to
# " --verbosity:0 --hints:off --warnings:off" & # incorrect RDTSC call in benchmark
# " benchmarks/bench_all.nim" when defined(arm64) or defined(amd64):
exec "nim c -d:BLS_FORCE_BACKEND=miracl -d:danger --outdir:build -r" &
" --verbosity:0 --hints:off --warnings:off" &
" benchmarks/bench_all.nim"
# TODO: update benchmarks exec "nim c -d:BLS_FORCE_BACKEND=blst -d:danger --outdir:build -r" &
" --verbosity:0 --hints:off --warnings:off" &
" benchmarks/bench_all.nim"
# task bench, "Run benchmarks": task bench, "Run benchmarks":
# if not dirExists "build": if not dirExists "build":
# mkDir "build" mkDir "build"
# exec "nim c -d:danger --outdir:build -r" & exec "nim c -d:danger --outdir:build -r" &
# " --verbosity:0 --hints:off --warnings:off" & " --verbosity:0 --hints:off --warnings:off" &
# " benchmarks/bench_all.nim" " benchmarks/bench_all.nim"

View File

@ -36,7 +36,8 @@ when (BLS_FORCE_BACKEND == "blst" or AutoSelectBLST) and (
const BLS_BACKEND* = BLST const BLS_BACKEND* = BLST
elif BLS_FORCE_BACKEND == "blst" or AutoSelectBLST: elif BLS_FORCE_BACKEND == "blst" or AutoSelectBLST:
# CPU doesn't support SSE3 which is used in optimized SHA256 # CPU doesn't support SSE3 which is used in optimized SHA256
# BLST_PORTABLE is a no-op on ARM # On ARM, BLST_PORTABLE will prevent use builtin SHA256
# which is unsupported by Raspberry Pi, detection via (__ARM_FEATURE_CRYPTO)
const BLS_BACKEND* = BLST const BLS_BACKEND* = BLST
{.passC: "-D__BLST_PORTABLE__".} {.passC: "-D__BLST_PORTABLE__".}
else: else: