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
../blscurve,
./bls12381_curve,
./hash_to_curve
./hash_to_curve,
./bls_signature
# Curve operations
benchScalarMultG1(1000)
benchScalarMultG2(1000)
benchEcAddG1(1000)
benchEcAddG2(1000)
benchPairingViaDoublePairing(1000)
benchPairingViaMultiPairing(1000)
# Pairings
when BLS_BACKEND == BLST:
benchBLSTPairing(1000)
else:
benchMiraclPairingViaDoublePairing(1000)
benchMiraclPairingViaMultiPairing(1000)
echo "\n⚠️ Warning: using draft v5 of IETF Hash-To-Curve (HKDF-based)."
echo " This is an outdated draft.\n"
# Hash-to-curve implementation
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
std/[monotimes, times, strformat, strutils, macros]
from ../blscurve import BLS_BACKEND
# warmup
proc warmup*() =
# 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)
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:
echo "Running on ", cpuName(), "\n\n"
echo "Running on ", cpuName(), "\n"
when SupportsGetTicks:
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 "\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) =
let ns = inNanoseconds((stop-start) div iters)

View File

@ -8,13 +8,17 @@
# those terms.
import
# Internals
../blscurve/common,
../blscurve/milagro,
../blscurve/hash_to_curve,
# Bench
./bench_templates,
./keygen
std/random,
../blscurve,
./bench_templates
when BLS_BACKEND == BLST:
import
../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) =
var x = generator1()
var scal: BIG_384
random(scal)
when BLS_BACKEND == BLST:
var x{.noInit.}: blst_p1
x.blst_p1_from_affine(BLS12_381_G1) # init from generator
bench("Scalar multiplication G1", iters):
x.mul(scal)
var scal{.noInit.}: array[32, byte]
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) =
var x = generator2()
var scal: BIG_384
random(scal)
when BLS_BACKEND == BLST:
var x{.noInit.}: blst_p2
x.blst_p2_from_affine(BLS12_381_G2) # init from generator
bench("Scalar multiplication G2", iters):
x.mul(scal)
var scal{.noInit.}: array[32, byte]
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) =
var x = generator1()
var y = generator1()
when BLS_BACKEND == BLST:
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):
x.add(y)
bench("EC add G1 (constant-time)", iters):
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) =
var x = generator2()
var y = generator2()
when BLS_BACKEND == BLST:
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):
x.add(y)
bench("EC add G2 (constant-time)", iters):
x.blst_p2_add_or_double(x, y)
else:
var x = generator2()
var y = generator2()
proc benchPairingViaDoublePairing*(iters: int) =
## Builtin Milagro Double-Pairing implementation
# 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_"
bench("EC add G2 (constant-time)", iters):
x.add(y)
# Signing
var sig = hashToG2(msg, domainSepTag)
sig.mul(seckey)
when BLS_BACKEND == BLST:
# 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
proc benchBLSTPairing*(iters: int) =
let (pubkey, seckey) = block:
var pk: PublicKey
var sk: SecretKey
var ikm: array[32, byte]
ikm[0] = 0x12
discard ikm.keygen(pk, sk)
(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) =
## 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) = newKeyPair()
let msg = "msg"
const domainSepTag = "BLS_SIG_BLS12381G2-SHA256-SSWU-RO_POP_"
# Cache the benchmarking context, there will be a ~8MB copy overhead (context size)
let ctxSave = createU(blst_pairing)
ctxSave[] = ctx[]
# Signing
var sig = hashToG2(msg, domainSepTag)
sig.mul(seckey)
ctx[].blst_pairing_commit() # Miller loop
let valid = ctx[].blst_pairing_finalVerify(nil) # Final Exponentiation
doAssert bool valid
# 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
)
# Pairing: e(Q, xP) == e(R, P)
bench("Pairing (Miller loop + Final Exponentiation)", iters):
ctx[] = ctxSave[]
ctx[].blst_pairing_commit() # Miller loop
let valid = ctx[].blst_pairing_finalVerify(nil) # Final Exponentiation
# doAssert bool valid
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:
benchScalarMultG1(1000)
@ -103,5 +230,8 @@ when isMainModule:
benchEcAddG1(1000)
benchEcAddG2(1000)
benchPairingViaDoublePairing(1000)
benchPairingViaMultiPairing(1000)
when BLS_BACKEND == BLST:
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
# those terms.
import
std/random,
../blscurve,
./bench_templates
# ############################################################
#
@ -14,3 +18,72 @@
# (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.
import
# Internals
../blscurve/[common, milagro, hash_to_curve],
# Bench
std/random,
../blscurve,
./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
# 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) =
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):
point = hashToG2(msg, dst)
bench("Hash to G2 (Draft #9) + affine conversion", iters):
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:
echo "⚠️ Warning: using draft v5 of IETF Hash-To-Curve (HKDF-based)."
echo " This is an outdated draft.\n\n"
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
test "-d:BLS_FORCE_BACKEND=blst", "tests/blst_sha256.nim"
# # Ensure benchmarks stay relevant. Ignore Windows 32-bit at the moment
# if not defined(windows) or not existsEnv"PLATFORM" or getEnv"PLATFORM" == "x64":
# exec "nim c -d:danger --outdir:build -r" &
# " --verbosity:0 --hints:off --warnings:off" &
# " benchmarks/bench_all.nim"
# Ensure benchmarks stay relevant.
# TODO, solve "inconsistent operand constraints"
# on 32-bit for asm volatile, this might be due to
# incorrect RDTSC call in benchmark
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":
# if not dirExists "build":
# mkDir "build"
task bench, "Run benchmarks":
if not dirExists "build":
mkDir "build"
# exec "nim c -d:danger --outdir:build -r" &
# " --verbosity:0 --hints:off --warnings:off" &
# " benchmarks/bench_all.nim"
exec "nim c -d:danger --outdir:build -r" &
" --verbosity:0 --hints:off --warnings:off" &
" benchmarks/bench_all.nim"

View File

@ -36,7 +36,8 @@ when (BLS_FORCE_BACKEND == "blst" or AutoSelectBLST) and (
const BLS_BACKEND* = BLST
elif BLS_FORCE_BACKEND == "blst" or AutoSelectBLST:
# 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
{.passC: "-D__BLST_PORTABLE__".}
else: