mirror of
https://github.com/codex-storage/constantine.git
synced 2025-02-25 17:05:24 +00:00
BLS Aggregate and Batch verify (#214)
* pairing -> pairings, and use alloca arrays instead of static arrays * aggregate and batched BLS signature * DLL generation broken by path changes
This commit is contained in:
parent
7c01affe24
commit
ff8c26c1fe
@ -8,42 +8,44 @@
|
||||
|
||||
import
|
||||
# Internals
|
||||
../constantine/blssig_pop_on_bls12381_g2,
|
||||
../constantine/[
|
||||
blssig_pop_on_bls12381_g2,
|
||||
ethereum_eip2333_bls12381_key_derivation],
|
||||
../constantine/math/arithmetic,
|
||||
# Helpers
|
||||
../helpers/prng_unsafe,
|
||||
./bench_blueprint
|
||||
|
||||
proc separator*() = separator(132)
|
||||
proc separator*() = separator(167)
|
||||
|
||||
proc report(op, curve: string, startTime, stopTime: MonoTime, startClk, stopClk: int64, iters: int) =
|
||||
let ns = inNanoseconds((stopTime-startTime) div iters)
|
||||
let throughput = 1e9 / float64(ns)
|
||||
when SupportsGetTicks:
|
||||
echo &"{op:<40} {curve:<15} {throughput:>15.3f} ops/s {ns:>9} ns/op {(stopClk - startClk) div iters:>9} CPU cycles (approx)"
|
||||
echo &"{op:<75} {curve:<15} {throughput:>15.3f} ops/s {ns:>9} ns/op {(stopClk - startClk) div iters:>9} CPU cycles (approx)"
|
||||
else:
|
||||
echo &"{op:<40} {curve:<15} {throughput:>15.3f} ops/s {ns:>9} ns/op"
|
||||
echo &"{op:<75} {curve:<15} {throughput:>15.3f} ops/s {ns:>9} ns/op"
|
||||
|
||||
template bench(op: string, curve: string, iters: int, body: untyped): untyped =
|
||||
measure(iters, startTime, stopTime, startClk, stopClk, body)
|
||||
report(op, curve, startTime, stopTime, startClk, stopClk, iters)
|
||||
|
||||
proc benchDeserPubkey*(iters: int) =
|
||||
var seckey: array[32, byte]
|
||||
for i in 1 ..< 32:
|
||||
seckey[i] = byte 42
|
||||
var
|
||||
sk{.noInit.}: SecretKey
|
||||
pk{.noInit.}: PublicKey
|
||||
pk_comp{.noInit.}: array[48, byte]
|
||||
|
||||
let ok = sk.deserialize_secret_key(seckey)
|
||||
proc demoKeyGen(): tuple[seckey: SecretKey, pubkey: PublicKey] =
|
||||
# Don't do this at home, this is for benchmarking purposes
|
||||
# The RNG is NOT cryptographically secure
|
||||
# The API for keygen is not ready in blssig_pop_on_bls12381_g2
|
||||
let ikm = rng.random_byte_seq(32)
|
||||
doAssert cast[ptr BigInt[255]](result.seckey.addr)[].derive_master_secretKey(ikm)
|
||||
let ok = result.pubkey.derive_public_key(result.seckey)
|
||||
doAssert ok == cttBLS_Success
|
||||
let ok2 = pk.derive_public_key(sk)
|
||||
doAssert ok2 == cttBLS_Success
|
||||
|
||||
proc benchDeserPubkey*(iters: int) =
|
||||
let (sk, pk) = demoKeyGen()
|
||||
var pk_comp{.noInit.}: array[48, byte]
|
||||
|
||||
# Serialize compressed
|
||||
let ok3 = pk_comp.serialize_public_key_compressed(pk)
|
||||
doAssert ok3 == cttBLS_Success
|
||||
let ok = pk_comp.serialize_public_key_compressed(pk)
|
||||
doAssert ok == cttBLS_Success
|
||||
|
||||
var pk2{.noInit.}: PublicKey
|
||||
|
||||
@ -51,22 +53,12 @@ proc benchDeserPubkey*(iters: int) =
|
||||
let status = pk2.deserialize_public_key_compressed(pk_comp)
|
||||
|
||||
proc benchDeserPubkeyUnchecked*(iters: int) =
|
||||
var seckey: array[32, byte]
|
||||
for i in 1 ..< 32:
|
||||
seckey[i] = byte 42
|
||||
var
|
||||
sk{.noInit.}: SecretKey
|
||||
pk{.noInit.}: PublicKey
|
||||
pk_comp{.noInit.}: array[48, byte]
|
||||
|
||||
let ok = sk.deserialize_secret_key(seckey)
|
||||
doAssert ok == cttBLS_Success
|
||||
let ok2 = pk.derive_public_key(sk)
|
||||
doAssert ok2 == cttBLS_Success
|
||||
let (sk, pk) = demoKeyGen()
|
||||
var pk_comp{.noInit.}: array[48, byte]
|
||||
|
||||
# Serialize compressed
|
||||
let ok3 = pk_comp.serialize_public_key_compressed(pk)
|
||||
doAssert ok3 == cttBLS_Success
|
||||
let ok = pk_comp.serialize_public_key_compressed(pk)
|
||||
doAssert ok == cttBLS_Success
|
||||
|
||||
var pk2{.noInit.}: PublicKey
|
||||
|
||||
@ -74,28 +66,19 @@ proc benchDeserPubkeyUnchecked*(iters: int) =
|
||||
let status = pk2.deserialize_public_key_compressed_unchecked(pk_comp)
|
||||
|
||||
proc benchDeserSig*(iters: int) =
|
||||
var seckey: array[32, byte]
|
||||
for i in 1 ..< 32:
|
||||
seckey[i] = byte 42
|
||||
let (sk, pk) = demoKeyGen()
|
||||
const msg = "abcdef0123456789"
|
||||
|
||||
var
|
||||
sk{.noInit.}: SecretKey
|
||||
pk{.noInit.}: PublicKey
|
||||
sig_comp{.noInit.}: array[96, byte]
|
||||
sig {.noInit.}: Signature
|
||||
|
||||
let ok = sk.deserialize_secret_key(seckey)
|
||||
doAssert ok == cttBLS_Success
|
||||
let ok2 = pk.derive_public_key(sk)
|
||||
doAssert ok2 == cttBLS_Success
|
||||
|
||||
let status = sig.sign(sk, msg)
|
||||
doAssert status == cttBLS_Success
|
||||
|
||||
# Serialize compressed
|
||||
let ok3 = sig_comp.serialize_signature_compressed(sig)
|
||||
doAssert ok3 == cttBLS_Success
|
||||
let ok = sig_comp.serialize_signature_compressed(sig)
|
||||
doAssert ok == cttBLS_Success
|
||||
|
||||
var sig2{.noInit.}: Signature
|
||||
|
||||
@ -103,28 +86,19 @@ proc benchDeserSig*(iters: int) =
|
||||
let status = sig2.deserialize_signature_compressed(sig_comp)
|
||||
|
||||
proc benchDeserSigUnchecked*(iters: int) =
|
||||
var seckey: array[32, byte]
|
||||
for i in 1 ..< 32:
|
||||
seckey[i] = byte 42
|
||||
let (sk, pk) = demoKeyGen()
|
||||
const msg = "abcdef0123456789"
|
||||
|
||||
var
|
||||
sk{.noInit.}: SecretKey
|
||||
pk{.noInit.}: PublicKey
|
||||
sig_comp{.noInit.}: array[96, byte]
|
||||
sig {.noInit.}: Signature
|
||||
|
||||
let ok = sk.deserialize_secret_key(seckey)
|
||||
doAssert ok == cttBLS_Success
|
||||
let ok2 = pk.derive_public_key(sk)
|
||||
doAssert ok2 == cttBLS_Success
|
||||
|
||||
let status = sig.sign(sk, msg)
|
||||
doAssert status == cttBLS_Success
|
||||
|
||||
# Serialize compressed
|
||||
let ok3 = sig_comp.serialize_signature_compressed(sig)
|
||||
doAssert ok3 == cttBLS_Success
|
||||
let ok = sig_comp.serialize_signature_compressed(sig)
|
||||
doAssert ok == cttBLS_Success
|
||||
|
||||
var sig2{.noInit.}: Signature
|
||||
|
||||
@ -132,44 +106,91 @@ proc benchDeserSigUnchecked*(iters: int) =
|
||||
let status = sig2.deserialize_signature_compressed_unchecked(sig_comp)
|
||||
|
||||
proc benchSign*(iters: int) =
|
||||
var seckey: array[32, byte]
|
||||
for i in 1 ..< 32:
|
||||
seckey[i] = byte 42
|
||||
let (sk, pk) = demoKeyGen()
|
||||
let msg = "Mr F was here"
|
||||
|
||||
var pk: PublicKey
|
||||
var sk: SecretKey
|
||||
var sig: Signature
|
||||
|
||||
let ok = sk.deserialize_secret_key(seckey)
|
||||
doAssert ok == cttBLS_Success
|
||||
|
||||
bench("BLS signature", "BLS12_381 G2", iters):
|
||||
let status = sig.sign(sk, msg)
|
||||
doAssert status == cttBLS_Success
|
||||
|
||||
|
||||
proc benchVerify*(iters: int) =
|
||||
var seckey: array[32, byte]
|
||||
for i in 1 ..< 32:
|
||||
seckey[i] = byte 42
|
||||
let (sk, pk) = demoKeyGen()
|
||||
let msg = "Mr F was here"
|
||||
|
||||
var pk: PublicKey
|
||||
var sk: SecretKey
|
||||
var sig: Signature
|
||||
|
||||
let ok = sk.deserialize_secret_key(seckey)
|
||||
let ok = sig.sign(sk, msg)
|
||||
doAssert ok == cttBLS_Success
|
||||
|
||||
let ok2 = sig.sign(sk, msg)
|
||||
|
||||
let ok3 = pk.derive_public_key(sk)
|
||||
doAssert ok3 == cttBLS_Success
|
||||
|
||||
bench("BLS verification", "BLS12_381", iters):
|
||||
let valid = pk.verify(msg, sig)
|
||||
|
||||
proc benchFastAggregateVerify*(numKeys, iters: int) =
|
||||
## Verification of N pubkeys signing 1 message
|
||||
let msg = "Mr F was here"
|
||||
|
||||
var validators = newSeq[PublicKey](numKeys)
|
||||
var sigs = newSeq[Signature](numKeys)
|
||||
var aggSig: Signature
|
||||
|
||||
for i in 0 ..< numKeys:
|
||||
let (sk, pk) = demoKeyGen()
|
||||
validators[i] = pk
|
||||
let status = sigs[i].sign(sk, msg)
|
||||
doAssert status == cttBLS_Success
|
||||
|
||||
aggSig.aggregate_signatures(sigs)
|
||||
|
||||
bench("BLS agg verif of 1 msg by " & $numKeys & " pubkeys", "BLS12_381", iters):
|
||||
let valid = validators.fast_aggregate_verify(msg, aggSig)
|
||||
|
||||
proc benchVerifyMulti*(numSigs, iters: int) =
|
||||
## Verification of N pubkeys signing for N messages
|
||||
|
||||
var triplets: seq[tuple[pubkey: PublicKey, msg: array[32, byte], sig: Signature]]
|
||||
|
||||
var hashedMsg: array[32, byte]
|
||||
var sig: Signature
|
||||
|
||||
for i in 0 ..< numSigs:
|
||||
let (sk, pk) = demoKeyGen()
|
||||
sha256.hash(hashedMsg, "msg" & $i)
|
||||
let status = sig.sign(sk, hashedMsg)
|
||||
doAssert status == cttBLS_Success
|
||||
triplets.add (pk, hashedMsg, sig)
|
||||
|
||||
bench("BLS verif of " & $numSigs & " msgs by "& $numSigs & " pubkeys", "BLS12_381", iters):
|
||||
for i in 0 ..< triplets.len:
|
||||
let ok = triplets[i].pubkey.verify(triplets[i].msg, triplets[i].sig)
|
||||
doAssert ok == cttBLS_Success
|
||||
|
||||
proc benchVerifyBatched*(numSigs, iters: int) =
|
||||
## Verification of N pubkeys signing for N messages
|
||||
|
||||
var
|
||||
pubkeys: seq[PublicKey]
|
||||
messages: seq[array[32, byte]]
|
||||
signatures: seq[Signature]
|
||||
|
||||
var hashedMsg: array[32, byte]
|
||||
var sig: Signature
|
||||
|
||||
for i in 0 ..< numSigs:
|
||||
let (sk, pk) = demoKeyGen()
|
||||
sha256.hash(hashedMsg, "msg" & $i)
|
||||
let status = sig.sign(sk, hashedMsg)
|
||||
doAssert status == cttBLS_Success
|
||||
|
||||
pubkeys.add pk
|
||||
messages.add hashedMsg
|
||||
signatures.add sig
|
||||
|
||||
let secureBlindingBytes = sha256.hash("Mr F was here")
|
||||
|
||||
bench("BLS serial batch verify of " & $numSigs & " msgs by "& $numSigs & " pubkeys (with blinding)", "BLS12_381", iters):
|
||||
let ok = batch_verify(pubkeys, messages, signatures, secureBlindingBytes)
|
||||
doAssert ok == cttBLS_Success
|
||||
|
||||
const Iters = 1000
|
||||
|
||||
proc main() =
|
||||
@ -182,6 +203,23 @@ proc main() =
|
||||
benchSign(Iters)
|
||||
benchVerify(Iters)
|
||||
separator()
|
||||
benchFastAggregateVerify(numKeys = 128, iters = 10)
|
||||
separator()
|
||||
|
||||
# Simulate Block verification (at most 6 signatures per block)
|
||||
benchVerifyMulti(numSigs = 6, iters = 10)
|
||||
benchVerifyBatched(numSigs = 6, iters = 10)
|
||||
separator()
|
||||
|
||||
# Simulate 10 blocks verification
|
||||
benchVerifyMulti(numSigs = 60, iters = 10)
|
||||
benchVerifyBatched(numSigs = 60, iters = 10)
|
||||
separator()
|
||||
|
||||
# Simulate 30 blocks verification
|
||||
benchVerifyMulti(numSigs = 180, iters = 10)
|
||||
benchVerifyBatched(numSigs = 180, iters = 10)
|
||||
separator()
|
||||
|
||||
main()
|
||||
notes()
|
||||
|
@ -20,11 +20,11 @@ import
|
||||
../constantine/math/extension_fields,
|
||||
../constantine/math/ec_shortweierstrass,
|
||||
../constantine/math/constants/zoo_subgroups,
|
||||
../constantine/math/pairing/[
|
||||
cyclotomic_subgroup,
|
||||
../constantine/math/pairings/[
|
||||
cyclotomic_subgroups,
|
||||
lines_eval,
|
||||
pairing_bls12,
|
||||
pairing_bn
|
||||
pairings_bls12,
|
||||
pairings_bn
|
||||
],
|
||||
../constantine/math/constants/zoo_pairings,
|
||||
# Helpers
|
||||
|
@ -23,10 +23,10 @@ import
|
||||
ec_shortweierstrass_jacobian,
|
||||
ec_scalar_mul, ec_endomorphism_accel],
|
||||
../constantine/math/constants/zoo_subgroups,
|
||||
../constantine/math/pairing/[
|
||||
cyclotomic_subgroup,
|
||||
pairing_bls12,
|
||||
pairing_bn
|
||||
../constantine/math/pairings/[
|
||||
cyclotomic_subgroups,
|
||||
pairings_bls12,
|
||||
pairings_bn
|
||||
],
|
||||
../constantine/math/constants/zoo_pairings,
|
||||
../constantine/hashes,
|
||||
|
@ -57,7 +57,7 @@ export
|
||||
## already serve as proof-of-possession.
|
||||
|
||||
const DST = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"
|
||||
const ffi_prefix = "ctt_blssig_pop_on_bls12381_g2_"
|
||||
const ffi_prefix {.used.} = "ctt_blssig_pop_on_bls12381_g2_"
|
||||
|
||||
{.push raises: [].} # No exceptions allowed in core cryptographic operations
|
||||
# {.push cdecl, dynlib, exportc:ffi_prefix & "$1".} # TODO, C API
|
||||
@ -86,6 +86,7 @@ type
|
||||
cttBLS_ZeroSecretKey
|
||||
cttBLS_SecretKeyLargerThanCurveOrder
|
||||
cttBLS_ZeroLengthAggregation
|
||||
cttBLS_InconsistentLengthsOfInputs
|
||||
|
||||
# Comparisons
|
||||
# ------------------------------------------------------------------------------------------------
|
||||
@ -135,7 +136,7 @@ func validate_sig*(signature: Signature): CttBLSStatus =
|
||||
# ------------------------------------------------------------------------------------------------
|
||||
|
||||
## BLS12-381 serialization
|
||||
##
|
||||
##
|
||||
## 𝔽p elements are encoded in big-endian form. They occupy 48 bytes in this form.
|
||||
## 𝔽p2 elements are encoded in big-endian form, meaning that the 𝔽p2 element c0+c1u
|
||||
## is represented by the 𝔽p element c1 followed by the 𝔽p element c0.
|
||||
@ -144,9 +145,9 @@ func validate_sig*(signature: Signature): CttBLSStatus =
|
||||
## 𝔾1 and 𝔾2 elements can be encoded in uncompressed form (the x-coordinate followed by the y-coordinate) or in compressed form (just the x-coordinate).
|
||||
## 𝔾1 elements occupy 96 bytes in uncompressed form, and 48 bytes in compressed form.
|
||||
## 𝔾2 elements occupy 192 bytes in uncompressed form, and 96 bytes in compressed form.
|
||||
##
|
||||
##
|
||||
## The most-significant three bits of a 𝔾1 or 𝔾2 encoding should be masked away before the coordinate(s) are interpreted. These bits are used to unambiguously represent the underlying element:
|
||||
##
|
||||
##
|
||||
## The most significant bit, when set, indicates that the point is in compressed form. Otherwise, the point is in uncompressed form.
|
||||
## The second-most significant bit indicates that the point is at infinity. If this bit is set, the remaining bits of the group element’s encoding should be set to zero.
|
||||
## The third-most significant bit is set if (and only if) this point is in compressed form
|
||||
@ -154,7 +155,7 @@ func validate_sig*(signature: Signature): CttBLSStatus =
|
||||
##
|
||||
## - https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-04#appendix-A
|
||||
## - https://docs.rs/bls12_381/latest/bls12_381/notes/serialization/index.html
|
||||
## - https://github.com/zkcrypto/bls12_381/blob/0.6.0/src/notes/serialization.rs
|
||||
## - https://github.com/zkcrypto/bls12_381/blob/0.6.0/src/notes/serialization.rs
|
||||
|
||||
func serialize_secret_key*(dst: var array[32, byte], secret_key: SecretKey): CttBLSStatus =
|
||||
## Serialize a secret key
|
||||
@ -164,7 +165,7 @@ func serialize_secret_key*(dst: var array[32, byte], secret_key: SecretKey): Ctt
|
||||
|
||||
func serialize_public_key_compressed*(dst: var array[48, byte], public_key: PublicKey): CttBLSStatus =
|
||||
## Serialize a public key in compressed (Zcash) format
|
||||
##
|
||||
##
|
||||
## Returns cttBLS_Success if successful
|
||||
if public_key.raw.isInf().bool():
|
||||
for i in 0 ..< dst.len:
|
||||
@ -185,7 +186,7 @@ func serialize_public_key_compressed*(dst: var array[48, byte], public_key: Publ
|
||||
|
||||
func serialize_signature_compressed*(dst: var array[96, byte], signature: Signature): CttBLSStatus =
|
||||
## Serialize a signature in compressed (Zcash) format
|
||||
##
|
||||
##
|
||||
## Returns cttBLS_Success if successful
|
||||
if signature.raw.isInf().bool():
|
||||
for i in 0 ..< dst.len:
|
||||
@ -207,7 +208,7 @@ func serialize_signature_compressed*(dst: var array[96, byte], signature: Signat
|
||||
|
||||
func deserialize_secret_key*(dst: var SecretKey, src: array[32, byte]): CttBLSStatus =
|
||||
## deserialize a secret key
|
||||
##
|
||||
##
|
||||
## This is protected against side-channel unless your key is invalid.
|
||||
## In that case it will like whether it's all zeros or larger than the curve order.
|
||||
dst.raw.unmarshal(src, bigEndian)
|
||||
@ -219,14 +220,14 @@ func deserialize_secret_key*(dst: var SecretKey, src: array[32, byte]): CttBLSSt
|
||||
|
||||
func deserialize_public_key_compressed_unchecked*(dst: var PublicKey, src: array[48, byte]): CttBLSStatus =
|
||||
## Deserialize a public_key in compressed (Zcash) format.
|
||||
##
|
||||
##
|
||||
## Warning ⚠:
|
||||
## This procedure skips the very expensive subgroup checks.
|
||||
## Not checking subgroup exposes a protocol to small subgroup attacks.
|
||||
##
|
||||
##
|
||||
## Returns cttBLS_Success if successful
|
||||
|
||||
# src must have the compressed flag
|
||||
# src must have the compressed flag
|
||||
if (src[0] and byte 0b10000000) == byte 0:
|
||||
return cttBLS_InvalidEncoding
|
||||
|
||||
@ -261,7 +262,7 @@ func deserialize_public_key_compressed_unchecked*(dst: var PublicKey, src: array
|
||||
|
||||
func deserialize_public_key_compressed*(dst: var PublicKey, src: array[48, byte]): CttBLSStatus =
|
||||
## Deserialize a public_key in compressed (Zcash) format
|
||||
##
|
||||
##
|
||||
## Returns cttBLS_Success if successful
|
||||
|
||||
result = deserialize_public_key_compressed_unchecked(dst, src)
|
||||
@ -273,14 +274,14 @@ func deserialize_public_key_compressed*(dst: var PublicKey, src: array[48, byte]
|
||||
|
||||
func deserialize_signature_compressed_unchecked*(dst: var Signature, src: array[96, byte]): CttBLSStatus =
|
||||
## Deserialize a signature in compressed (Zcash) format.
|
||||
##
|
||||
##
|
||||
## Warning ⚠:
|
||||
## This procedure skips the very expensive subgroup checks.
|
||||
## Not checking subgroup exposes a protocol to small subgroup attacks.
|
||||
##
|
||||
##
|
||||
## Returns cttBLS_Success if successful
|
||||
|
||||
# src must have the compressed flag
|
||||
# src must have the compressed flag
|
||||
if (src[0] and byte 0b10000000) == byte 0:
|
||||
return cttBLS_InvalidEncoding
|
||||
|
||||
@ -320,13 +321,13 @@ func deserialize_signature_compressed_unchecked*(dst: var Signature, src: array[
|
||||
dst.raw.y.c0.toBig() >= Fp[BLS12_381].getPrimePlus1div2()
|
||||
else:
|
||||
dst.raw.y.c1.toBig() >= Fp[BLS12_381].getPrimePlus1div2()
|
||||
|
||||
|
||||
let srcIsLargest = SecretBool((src[0] shr 5) and byte 1)
|
||||
dst.raw.y.cneg(isLexicographicallyLargest xor srcIsLargest)
|
||||
|
||||
func deserialize_signature_compressed*(dst: var Signature, src: array[96, byte]): CttBLSStatus =
|
||||
## Deserialize a public_key in compressed (Zcash) format
|
||||
##
|
||||
##
|
||||
## Returns cttBLS_Success if successful
|
||||
|
||||
result = deserialize_signature_compressed_unchecked(dst, src)
|
||||
@ -341,7 +342,7 @@ func deserialize_signature_compressed*(dst: var Signature, src: array[96, byte])
|
||||
|
||||
func derive_public_key*(public_key: var PublicKey, secret_key: SecretKey): CttBLSStatus =
|
||||
## Derive the public key matching with a secret key
|
||||
##
|
||||
##
|
||||
## Secret protection:
|
||||
## - A valid secret key will only leak that it is valid.
|
||||
## - An invalid secret key will leak whether it's all zero or larger than the curve order.
|
||||
@ -358,18 +359,18 @@ func derive_public_key*(public_key: var PublicKey, secret_key: SecretKey): CttBL
|
||||
func sign*[T: byte|char](signature: var Signature, secret_key: SecretKey, message: openArray[T]): CttBLSStatus =
|
||||
## Produce a signature for the message under the specified secret key
|
||||
## Signature is on BLS12-381 G2 (and public key on G1)
|
||||
##
|
||||
##
|
||||
## For message domain separation purpose, the tag is `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_`
|
||||
##
|
||||
##
|
||||
## Input:
|
||||
## - A secret key
|
||||
## - A message
|
||||
##
|
||||
##
|
||||
## Output:
|
||||
## - `signature` is overwritten with `message` signed with `secretKey`
|
||||
## with the scheme
|
||||
## - A status code indicating success or if the secret key is invalid.
|
||||
##
|
||||
##
|
||||
## Secret protection:
|
||||
## - A valid secret key will only leak that it is valid.
|
||||
## - An invalid secret key will leak whether it's all zero or larger than the curve order.
|
||||
@ -377,7 +378,7 @@ func sign*[T: byte|char](signature: var Signature, secret_key: SecretKey, messag
|
||||
if status != cttBLS_Success:
|
||||
signature.raw.setInf()
|
||||
return status
|
||||
|
||||
|
||||
coreSign(signature.raw, secretKey.raw, message, sha256, 128, augmentation = "", DST)
|
||||
return cttBLS_Success
|
||||
|
||||
@ -385,18 +386,18 @@ func verify*[T: byte|char](public_key: PublicKey, message: openarray[T], signatu
|
||||
## Check that a signature is valid for a message
|
||||
## under the provided public key.
|
||||
## returns `true` if the signature is valid, `false` otherwise.
|
||||
##
|
||||
##
|
||||
## For message domain separation purpose, the tag is `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_`
|
||||
##
|
||||
##
|
||||
## Input:
|
||||
## - A public key initialized by one of the key derivation or deserialization procedure.
|
||||
## Or validated via validate_pubkey
|
||||
## - A message
|
||||
## - A signature initialized by one of the key derivation or deserialization procedure.
|
||||
## Or validated via validate_pubkey
|
||||
##
|
||||
##
|
||||
## In particular, the public key and signature are assumed to be on curve subgroup checked.
|
||||
|
||||
|
||||
# Deal with cases were pubkey or signature were mistakenly zero-init, due to a generic aggregation tentative for example
|
||||
if bool(public_key.raw.isInf() or signature.raw.isInf()):
|
||||
return cttBLS_PointAtInfinity
|
||||
@ -406,38 +407,151 @@ func verify*[T: byte|char](public_key: PublicKey, message: openarray[T], signatu
|
||||
return cttBLS_Success
|
||||
return cttBLS_VerificationFailure
|
||||
|
||||
func fast_aggregate_verify*[T: byte|char](public_keys: openArray[PublicKey], message: openarray[T], signature: Signature): CttBLSStatus =
|
||||
template unwrap[T: PublicKey|Signature](elems: openArray[T]): auto =
|
||||
# Unwrap collection of high-level type into collection of low-level type
|
||||
toOpenArray(cast[ptr UncheckedArray[typeof elems[0].raw]](elems[0].raw.unsafeAddr), elems.low, elems.high)
|
||||
|
||||
func aggregate_pubkeys*(aggregate_pubkey: var PublicKey, pubkeys: openArray[PublicKey]) =
|
||||
## Aggregate public keys into one
|
||||
## The individual public keys are assumed to be validated, either during deserialization
|
||||
## or by validate_pubkeys
|
||||
if pubkeys.len == 0:
|
||||
aggregate_pubkey.raw.setInf()
|
||||
return
|
||||
aggregate_pubkey.raw.aggregate(pubkeys.unwrap())
|
||||
|
||||
func aggregate_signatures*(aggregate_sig: var Signature, signatures: openArray[Signature]) =
|
||||
## Aggregate signatures into one
|
||||
## The individual signatures are assumed to be validated, either during deserialization
|
||||
## or by validate_signature
|
||||
if signatures.len == 0:
|
||||
aggregate_sig.raw.setInf()
|
||||
return
|
||||
aggregate_sig.raw.aggregate(signatures.unwrap())
|
||||
|
||||
func fast_aggregate_verify*[T: byte|char](pubkeys: openArray[PublicKey], message: openarray[T], aggregate_sig: Signature): CttBLSStatus =
|
||||
## Check that a signature is valid for a message
|
||||
## under the aggregate of provided public keys.
|
||||
## returns `true` if the signature is valid, `false` otherwise.
|
||||
##
|
||||
##
|
||||
## For message domain separation purpose, the tag is `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_`
|
||||
##
|
||||
##
|
||||
## Input:
|
||||
## - Public keys initialized by one of the key derivation or deserialization procedure.
|
||||
## Or validated via validate_pubkey
|
||||
## - A message
|
||||
## - A signature initialized by one of the key derivation or deserialization procedure.
|
||||
## Or validated via validate_pubkey
|
||||
##
|
||||
## Or validated via validate_sig
|
||||
##
|
||||
## In particular, the public keys and signature are assumed to be on curve subgroup checked.
|
||||
|
||||
if public_keys.len == 0:
|
||||
if pubkeys.len == 0:
|
||||
# IETF spec precondition
|
||||
return cttBLS_ZeroLengthAggregation
|
||||
|
||||
# Deal with cases were pubkey or signature were mistakenly zero-init, due to a generic aggregation tentative for example
|
||||
if signature.raw.isInf().bool:
|
||||
if aggregate_sig.raw.isInf().bool:
|
||||
return cttBLS_PointAtInfinity
|
||||
|
||||
for i in 0 ..< public_keys.len:
|
||||
if public_keys[i].raw.isInf().bool:
|
||||
for i in 0 ..< pubkeys.len:
|
||||
if pubkeys[i].raw.isInf().bool:
|
||||
return cttBLS_PointAtInfinity
|
||||
|
||||
|
||||
let verified = fastAggregateVerify(
|
||||
toOpenArray(cast[ptr UncheckedArray[typeof public_keys[0].raw]](public_keys[0].raw.unsafeAddr), public_keys.low, public_keys.high),
|
||||
message, signature.raw,
|
||||
sha256, 128, augmentation = "", DST)
|
||||
pubkeys.unwrap(),
|
||||
message, aggregate_sig.raw,
|
||||
sha256, 128, DST)
|
||||
if verified:
|
||||
return cttBLS_Success
|
||||
return cttBLS_VerificationFailure
|
||||
|
||||
func aggregate_verify*[M](pubkeys: openArray[PublicKey], messages: openarray[M], aggregate_sig: Signature): CttBLSStatus =
|
||||
## Verify the aggregated signature of multiple (pubkey, message) pairs
|
||||
## returns `true` if the signature is valid, `false` otherwise.
|
||||
##
|
||||
## For message domain separation purpose, the tag is `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_`
|
||||
##
|
||||
## Input:
|
||||
## - Public keys initialized by one of the key derivation or deserialization procedure.
|
||||
## Or validated via validate_pubkey
|
||||
## - Messages
|
||||
## - a signature initialized by one of the key derivation or deserialization procedure.
|
||||
## Or validated via validate_sig
|
||||
##
|
||||
## In particular, the public keys and signature are assumed to be on curve subgroup checked.
|
||||
##
|
||||
## To avoid splitting zeros and rogue keys attack:
|
||||
## 1. Public keys signing the same message MUST be aggregated and checked for 0 before calling BLSAggregateSigAccumulator.update()
|
||||
## 2. Augmentation or Proof of possessions must used for each public keys.
|
||||
|
||||
if pubkeys.len == 0:
|
||||
# IETF spec precondition
|
||||
return cttBLS_ZeroLengthAggregation
|
||||
|
||||
if pubkeys.len != messages.len:
|
||||
return cttBLS_InconsistentLengthsOfInputs
|
||||
|
||||
# Deal with cases were pubkey or signature were mistakenly zero-init, due to a generic aggregation tentative for example
|
||||
if aggregate_sig.raw.isInf().bool:
|
||||
return cttBLS_PointAtInfinity
|
||||
|
||||
for i in 0 ..< pubkeys.len:
|
||||
if pubkeys[i].raw.isInf().bool:
|
||||
return cttBLS_PointAtInfinity
|
||||
|
||||
let verified = aggregateVerify(
|
||||
pubkeys.unwrap(),
|
||||
messages, aggregate_sig.raw,
|
||||
sha256, 128, DST)
|
||||
if verified:
|
||||
return cttBLS_Success
|
||||
return cttBLS_VerificationFailure
|
||||
|
||||
func batch_verify*[M](pubkeys: openArray[PublicKey], messages: openarray[M], signatures: openArray[Signature], secureRandomBytes: array[32, byte]): CttBLSStatus =
|
||||
## Verify that all (pubkey, message, signature) triplets are valid
|
||||
## returns `true` if all signatures are valid, `false` if at least one is invalid.
|
||||
##
|
||||
## For message domain separation purpose, the tag is `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_`
|
||||
##
|
||||
## Input:
|
||||
## - Public keys initialized by one of the key derivation or deserialization procedure.
|
||||
## Or validated via validate_pubkey
|
||||
## - Messages
|
||||
## - Signatures initialized by one of the key derivation or deserialization procedure.
|
||||
## Or validated via validate_sig
|
||||
##
|
||||
## In particular, the public keys and signature are assumed to be on curve subgroup checked.
|
||||
##
|
||||
## To avoid splitting zeros and rogue keys attack:
|
||||
## 1. Cryptographically-secure random bytes must be provided.
|
||||
## 2. Augmentation or Proof of possessions must used for each public keys.
|
||||
##
|
||||
## The secureRandomBytes will serve as input not under the attacker control to foil potential splitting zeros inputs.
|
||||
## The scheme assumes that the attacker cannot
|
||||
## resubmit 2^64 times forged (publickey, message, signature) triplets
|
||||
## against the same `secureRandomBytes`
|
||||
|
||||
if pubkeys.len == 0:
|
||||
# IETF spec precondition
|
||||
return cttBLS_ZeroLengthAggregation
|
||||
|
||||
if pubkeys.len != messages.len or pubkeys.len != signatures.len:
|
||||
return cttBLS_InconsistentLengthsOfInputs
|
||||
|
||||
# Deal with cases were pubkey or signature were mistakenly zero-init, due to a generic aggregation tentative for example
|
||||
for i in 0 ..< pubkeys.len:
|
||||
if pubkeys[i].raw.isInf().bool:
|
||||
return cttBLS_PointAtInfinity
|
||||
|
||||
for i in 0 ..< signatures.len:
|
||||
if signatures[i].raw.isInf().bool:
|
||||
return cttBLS_PointAtInfinity
|
||||
|
||||
let verified = batchVerify(
|
||||
pubkeys.unwrap(),
|
||||
messages,
|
||||
signatures.unwrap(),
|
||||
sha256, 128, DST, secureRandomBytes)
|
||||
if verified:
|
||||
return cttBLS_Success
|
||||
return cttBLS_VerificationFailure
|
@ -18,10 +18,10 @@ import
|
||||
],
|
||||
./math/io/[io_bigints, io_fields],
|
||||
./math/isogenies/frobenius,
|
||||
./math/pairings,
|
||||
./math/pairing/[
|
||||
cyclotomic_subgroup,
|
||||
lines_eval
|
||||
./math/pairings/[
|
||||
cyclotomic_subgroups,
|
||||
lines_eval,
|
||||
pairings_generic
|
||||
],
|
||||
./math/constants/zoo_pairings,
|
||||
./hash_to_curve/hash_to_curve
|
||||
@ -202,19 +202,21 @@ export lines_eval.line_add
|
||||
export lines_eval.mul_by_line
|
||||
export lines_eval.mul_by_2_lines
|
||||
|
||||
export cyclotomic_subgroup.finalExpEasy
|
||||
export cyclotomic_subgroup.cyclotomic_inv
|
||||
export cyclotomic_subgroup.cyclotomic_square
|
||||
export cyclotomic_subgroup.cycl_sqr_repeated
|
||||
export cyclotomic_subgroup.cyclotomic_exp
|
||||
export cyclotomic_subgroup.isInCyclotomicSubgroup
|
||||
export cyclotomic_subgroups.finalExpEasy
|
||||
export cyclotomic_subgroups.cyclotomic_inv
|
||||
export cyclotomic_subgroups.cyclotomic_square
|
||||
export cyclotomic_subgroups.cycl_sqr_repeated
|
||||
export cyclotomic_subgroups.cyclotomic_exp
|
||||
export cyclotomic_subgroups.isInCyclotomicSubgroup
|
||||
|
||||
export zoo_pairings.cycl_exp_by_curve_param
|
||||
export zoo_pairings.cycl_exp_by_curve_param_div2
|
||||
export zoo_pairings.millerLoopAddchain
|
||||
export zoo_pairings.isInPairingSubgroup
|
||||
|
||||
export pairings.pairing
|
||||
export pairings_generic.pairing
|
||||
export pairings_generic.millerLoop
|
||||
export pairings_generic.finalExp
|
||||
|
||||
# Hashing to Elliptic Curve
|
||||
# ------------------------------------------------------------
|
||||
|
@ -12,7 +12,7 @@ import
|
||||
./math/[arithmetic, extension_fields],
|
||||
./math/arithmetic/limbs_montgomery,
|
||||
./math/ec_shortweierstrass,
|
||||
./math/pairing/[pairing_bn, miller_loops, cyclotomic_subgroup],
|
||||
./math/pairings/[pairings_bn, miller_loops, cyclotomic_subgroups],
|
||||
./math/constants/zoo_subgroups,
|
||||
./math/io/[io_bigints, io_fields]
|
||||
|
||||
@ -91,7 +91,7 @@ func eth_evm_ecadd*(
|
||||
## Elliptic Curve addition on BN254_Snarks
|
||||
## (also called alt_bn128 in Ethereum specs
|
||||
## and bn256 in Ethereum tests)
|
||||
##
|
||||
##
|
||||
## Name: ECADD
|
||||
##
|
||||
## Inputs:
|
||||
@ -109,7 +109,7 @@ func eth_evm_ecadd*(
|
||||
## cttEVM_Success
|
||||
## cttEVM_IntLargerThanModulus
|
||||
## cttEVM_PointNotOnCurve
|
||||
##
|
||||
##
|
||||
## Spec https://eips.ethereum.org/EIPS/eip-196
|
||||
|
||||
# Auto-pad with zero
|
||||
@ -166,7 +166,7 @@ func eth_evm_ecmul*(
|
||||
## cttEVM_Success
|
||||
## cttEVM_IntLargerThanModulus
|
||||
## cttEVM_PointNotOnCurve
|
||||
##
|
||||
##
|
||||
## Spec https://eips.ethereum.org/EIPS/eip-196
|
||||
|
||||
# Auto-pad with zero
|
||||
@ -288,7 +288,7 @@ func fromRawCoords(
|
||||
# Point on curve
|
||||
if not bool(isOnCurve(dst.x, dst.y, G2)):
|
||||
return cttEVM_PointNotOnCurve
|
||||
|
||||
|
||||
if not subgroupCheck(dst):
|
||||
return cttEVM_PointNotInSubgroup
|
||||
|
||||
@ -312,9 +312,9 @@ func eth_evm_ecpairing*(
|
||||
## cttEVM_IntLargerThanModulus
|
||||
## cttEVM_PointNotOnCurve
|
||||
## cttEVM_InvalidInputLength
|
||||
##
|
||||
##
|
||||
## Spec https://eips.ethereum.org/EIPS/eip-197
|
||||
|
||||
|
||||
let N = inputs.len div 192
|
||||
if inputs.len mod 192 != 0:
|
||||
return cttEVM_InvalidInputLength
|
||||
|
@ -114,7 +114,6 @@ func expandMessageXMD*[B1, B2, B3: byte|char, len_in_bytes: static int](
|
||||
doAssert output.len mod 32 == 0 # Assumed by copy optimization
|
||||
|
||||
let ell = ceilDiv(output.len.uint, DigestSize.uint)
|
||||
const zPad = default(array[BlockSize, byte])
|
||||
var l_i_b_str0 {.noInit.}: array[3, byte]
|
||||
l_i_b_str0.dumpRawInt(output.len.uint16, cursor = 0, bigEndian)
|
||||
l_i_b_str0[2] = 0
|
||||
|
@ -58,7 +58,7 @@ func hash*[DigestSize: static int, T: char|byte](
|
||||
func hash*[T: char|byte](
|
||||
HashKind: type CryptoHash,
|
||||
message: openarray[T],
|
||||
clearmem = false): array[HashKind.sizeInBytes, byte] {.noInit.} =
|
||||
clearmem = false): array[HashKind.digestSize, byte] {.noInit.} =
|
||||
## Produce a digest from a message
|
||||
HashKind.hash(result, message, clearMem)
|
||||
|
||||
|
@ -96,3 +96,12 @@ macro getEmbeddingDegree*(C: static Curve): untyped =
|
||||
macro getSexticTwist*(C: static Curve): untyped =
|
||||
## Returns if D-Twist or M-Twist
|
||||
result = bindSym($C & "_sexticTwist")
|
||||
|
||||
macro getGT*(C: static Curve): untyped =
|
||||
## Returns the GT extension field
|
||||
|
||||
template gt(embdegree: static int): untyped =
|
||||
`Fp embdegree`
|
||||
|
||||
result = quote do:
|
||||
`gt`(getEmbeddingDegree(Curve(`C`)))[Curve(`C`)]
|
||||
|
@ -12,8 +12,9 @@ import
|
||||
../io/io_bigints,
|
||||
../extension_fields,
|
||||
../elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_projective],
|
||||
../pairing/[cyclotomic_subgroup, miller_loops],
|
||||
../isogenies/frobenius
|
||||
../pairings/[cyclotomic_subgroups, miller_loops],
|
||||
../isogenies/frobenius,
|
||||
../../platforms/allocs
|
||||
|
||||
# Slow generic implementation
|
||||
# ------------------------------------------------------------
|
||||
@ -59,22 +60,23 @@ func millerLoopAddchain*(
|
||||
f.miller_accum_double_then_add(T, Q, P, 1) # 0b100001010000100011
|
||||
f.miller_accum_double_then_add(T, Q, P, 46, add = true) # 0b1000010100001000110000000000000000000000000000000000000000000001
|
||||
|
||||
func millerLoopAddchain*[N: static int](
|
||||
func millerLoopAddchain*(
|
||||
f: var Fp12[BLS12_377],
|
||||
Qs: array[N, ECP_ShortW_Aff[Fp2[BLS12_377], G2]],
|
||||
Ps: array[N, ECP_ShortW_Aff[Fp[BLS12_377], G1]]
|
||||
Qs: ptr UncheckedArray[ECP_ShortW_Aff[Fp2[BLS12_377], G2]],
|
||||
Ps: ptr UncheckedArray[ECP_ShortW_Aff[Fp[BLS12_377], G1]],
|
||||
N: int
|
||||
) =
|
||||
## Miller Loop for BLS12-377 curve
|
||||
## Computes f{u,Q}(P) with u the BLS curve parameter
|
||||
|
||||
var Ts {.noInit.}: array[N, ECP_ShortW_Prj[Fp2[BLS12_377], G2]]
|
||||
var Ts = allocStackArray(ECP_ShortW_Prj[Fp2[BLS12_377], G2], N)
|
||||
|
||||
f.miller_init_double_then_add(Ts, Qs, Ps, 5) # 0b100001
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, 2) # 0b10000101
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, 5) # 0b1000010100001
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, 4) # 0b10000101000010001
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, 1) # 0b100001010000100011
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, 46, add = true) # 0b1000010100001000110000000000000000000000000000000000000000000001
|
||||
f.miller_init_double_then_add( Ts, Qs, Ps, N, 5) # 0b100001
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, N, 2) # 0b10000101
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, N, 5) # 0b1000010100001
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, N, 4) # 0b10000101000010001
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, N, 1) # 0b100001010000100011
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, N, 46, add = true) # 0b1000010100001000110000000000000000000000000000000000000000000001
|
||||
|
||||
func cycl_exp_by_curve_param*(r: var Fp12[BLS12_377], a: Fp12[BLS12_377], invert = BLS12_377_pairing_ate_param_isNeg) =
|
||||
## f^x with x the curve parameter
|
@ -12,8 +12,9 @@ import
|
||||
../io/io_bigints,
|
||||
../extension_fields,
|
||||
../elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_projective],
|
||||
../pairing/[cyclotomic_subgroup, miller_loops],
|
||||
../isogenies/frobenius
|
||||
../pairings/[cyclotomic_subgroups, miller_loops],
|
||||
../isogenies/frobenius,
|
||||
../../platforms/allocs
|
||||
|
||||
# Slow generic implementation
|
||||
# ------------------------------------------------------------
|
||||
@ -60,33 +61,34 @@ func millerLoopAddchain*(
|
||||
# Negative AteParam, conjugation eliminated by final exponentiation
|
||||
# f.conj()
|
||||
|
||||
func millerLoopAddchain*[N: static int](
|
||||
func millerLoopAddchain*(
|
||||
f: var Fp12[BLS12_381],
|
||||
Qs: array[N, ECP_ShortW_Aff[Fp2[BLS12_381], G2]],
|
||||
Ps: array[N, ECP_ShortW_Aff[Fp[BLS12_381], G1]]
|
||||
Qs: ptr UncheckedArray[ECP_ShortW_Aff[Fp2[BLS12_381], G2]],
|
||||
Ps: ptr UncheckedArray[ECP_ShortW_Aff[Fp[BLS12_381], G1]],
|
||||
N: int
|
||||
) =
|
||||
## Generic Miller Loop for BLS12 curve
|
||||
## Computes f{u,Q}(P) with u the BLS curve parameter
|
||||
|
||||
var Ts {.noInit.}: array[N, ECP_ShortW_Prj[Fp2[BLS12_381], G2]]
|
||||
var Ts = allocStackArray(ECP_ShortW_Prj[Fp2[BLS12_381], G2], N)
|
||||
|
||||
# Ate param addition chain
|
||||
# Hex: 0xd201000000010000
|
||||
# Bin: 0b1101001000000001000000000000000000000000000000010000000000000000
|
||||
|
||||
f.miller_init_double_then_add(Ts, Qs, Ps, 1) # 0b11
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, 2) # 0b1101
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, 3) # 0b1101001
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, 9) # 0b1101001000000001
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, 32) # 0b110100100000000100000000000000000000000000000001
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, 16, add = false) # 0b1101001000000001000000000000000000000000000000010000000000000000
|
||||
f.miller_init_double_then_add( Ts, Qs, Ps, N, 1) # 0b11
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, N, 2) # 0b1101
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, N, 3) # 0b1101001
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, N, 9) # 0b1101001000000001
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, N, 32) # 0b110100100000000100000000000000000000000000000001
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, N, 16, add = false) # 0b1101001000000001000000000000000000000000000000010000000000000000
|
||||
|
||||
func cycl_exp_by_curve_param_div2*(
|
||||
r: var Fp12[BLS12_381], a: Fp12[BLS12_381],
|
||||
invert = BLS12_381_pairing_ate_param_isNeg) =
|
||||
## f^(x/2) with x the curve parameter
|
||||
## For BLS12_381 f^-0xd201000000010000 = 0b1101001000000001000000000000000000000000000000010000000000000000
|
||||
|
||||
|
||||
# Squarings accumulator
|
||||
var s{.noInit.}: Fp12[BLS12_381]
|
||||
|
@ -12,8 +12,9 @@ import
|
||||
../io/io_bigints,
|
||||
../extension_fields,
|
||||
../elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_projective],
|
||||
../pairing/[cyclotomic_subgroup, miller_loops],
|
||||
../isogenies/frobenius
|
||||
../pairings/[cyclotomic_subgroups, miller_loops],
|
||||
../isogenies/frobenius,
|
||||
../../platforms/allocs
|
||||
|
||||
# Slow generic implementation
|
||||
# ------------------------------------------------------------
|
||||
@ -59,20 +60,21 @@ func millerLoopAddchain*(
|
||||
# Ate pairing for BN curves needs adjustment after basic Miller loop
|
||||
f.millerCorrectionBN(T, Q, P, BN254_Nogami_pairing_ate_param_isNeg)
|
||||
|
||||
func millerLoopAddchain*[N: static int](
|
||||
func millerLoopAddchain*(
|
||||
f: var Fp12[BN254_Nogami],
|
||||
Qs: array[N, ECP_ShortW_Aff[Fp2[BN254_Nogami], G2]],
|
||||
Ps: array[N, ECP_ShortW_Aff[Fp[BN254_Nogami], G1]]
|
||||
Qs: ptr UncheckedArray[ECP_ShortW_Aff[Fp2[BN254_Nogami], G2]],
|
||||
Ps: ptr UncheckedArray[ECP_ShortW_Aff[Fp[BN254_Nogami], G1]],
|
||||
N: int
|
||||
) =
|
||||
## Miller Loop for BN254-Nogami curve
|
||||
## Computes f{6u+2,Q}(P) with u the BLS curve parameter
|
||||
var Ts {.noInit.}: array[N, ECP_ShortW_Prj[Fp2[BN254_Nogami], G2]]
|
||||
var Ts = allocStackArray(ECP_ShortW_Prj[Fp2[BN254_Nogami], G2], N)
|
||||
|
||||
f.miller_init_double_then_add(Ts, Qs, Ps, 1) # 0b11
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, 6) # 0b11000001
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, 1) # 0b110000011
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, 54) # 0b110000011000000000000000000000000000000000000000000000000000001
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, 2, add = false) # 0b11000001100000000000000000000000000000000000000000000000000000100
|
||||
f.miller_init_double_then_add( Ts, Qs, Ps, N, 1) # 0b11
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, N, 6) # 0b11000001
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, N, 1) # 0b110000011
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, N, 54) # 0b110000011000000000000000000000000000000000000000000000000000001
|
||||
f.miller_accum_double_then_add(Ts, Qs, Ps, N, 2, add = false) # 0b11000001100000000000000000000000000000000000000000000000000000100
|
||||
|
||||
# Negative AteParam
|
||||
f.conj()
|
||||
@ -105,6 +107,6 @@ func isInPairingSubgroup*(a: Fp12[BN254_Nogami]): SecretBool =
|
||||
t0 *= t1 # a^(3p²)
|
||||
t0.square() # a^(6p²)
|
||||
|
||||
t1.frobenius_map(a)
|
||||
t1.frobenius_map(a)
|
||||
|
||||
return t0 == t1
|
@ -11,7 +11,7 @@ import
|
||||
../config/curves,
|
||||
../io/io_bigints,
|
||||
../extension_fields,
|
||||
../pairing/cyclotomic_subgroup,
|
||||
../pairings/cyclotomic_subgroups,
|
||||
../isogenies/frobenius
|
||||
|
||||
# Slow generic implementation
|
||||
@ -115,6 +115,6 @@ func isInPairingSubgroup*(a: Fp12[BN254_Snarks]): SecretBool =
|
||||
t0 *= t1 # a^(3p²)
|
||||
t0.square() # a^(6p²)
|
||||
|
||||
t1.frobenius_map(a)
|
||||
t1.frobenius_map(a)
|
||||
|
||||
return t0 == t1
|
@ -11,7 +11,7 @@ import
|
||||
../config/curves,
|
||||
../io/io_bigints,
|
||||
../extension_fields,
|
||||
../pairing/cyclotomic_subgroup,
|
||||
../pairings/cyclotomic_subgroups,
|
||||
../isogenies/frobenius
|
||||
|
||||
# Slow generic implementation
|
@ -9,11 +9,11 @@
|
||||
import
|
||||
std/macros,
|
||||
../config/curves,
|
||||
./bls12_377_pairing,
|
||||
./bls12_381_pairing,
|
||||
./bn254_nogami_pairing,
|
||||
./bn254_snarks_pairing,
|
||||
./bw6_761_pairing
|
||||
./bls12_377_pairings,
|
||||
./bls12_381_pairings,
|
||||
./bn254_nogami_pairings,
|
||||
./bn254_snarks_pairings,
|
||||
./bw6_761_pairings
|
||||
|
||||
{.experimental: "dynamicBindSym".}
|
||||
|
||||
|
129
constantine/math/pairings/miller_accumulators.nim
Normal file
129
constantine/math/pairings/miller_accumulators.nim
Normal file
@ -0,0 +1,129 @@
|
||||
# Constantine
|
||||
# Copyright (c) 2018-2019 Status Research & Development GmbH
|
||||
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
|
||||
# Licensed and distributed under either of
|
||||
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
|
||||
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
../extension_fields,
|
||||
../elliptic/ec_shortweierstrass_affine,
|
||||
../arithmetic,
|
||||
./pairings_generic
|
||||
|
||||
{.push raises: [].} # No exceptions allowed in core cryptographic operations
|
||||
{.push checks: off.} # No defects due to array bound checking or signed integer overflow allowed
|
||||
|
||||
# ############################################################
|
||||
#
|
||||
# Miller Loop accumulators
|
||||
#
|
||||
# ############################################################
|
||||
|
||||
# Accumulators stores partial lines or Miller Loops results.
|
||||
# They allow supporting pairings in a streaming fashion
|
||||
# or to enable parallelization of multi-pairings.
|
||||
#
|
||||
# See ./multi-pairing.md for 2 approaches to a miller loop accumulator:
|
||||
#
|
||||
# - Software Implementation, Algorithm 11.2 & 11.3
|
||||
# Aranha, Dominguez Perez, A. Mrabet, Schwabe,
|
||||
# Guide to Pairing-Based Cryptography, 2015
|
||||
#
|
||||
# - Pairing Implementation Revisited
|
||||
# Mike Scott, 2019
|
||||
# https://eprint.iacr.org/2019/077.pdf
|
||||
#
|
||||
#
|
||||
# Aranha uses:
|
||||
# - 1 𝔽pᵏ accumulator `f` for Miller loop output
|
||||
# - N 𝔾2 accumulator `T`, with N our choice.
|
||||
# The Miller Loop can be batched on up to N pairings with this approach.
|
||||
#
|
||||
# Scott uses:
|
||||
# - M 𝔽pᵏ accumulator `f` for line functions, with M the number of bits in the ate param (68 for BLS12-381).
|
||||
# - 1 𝔾2 accumulator `T`
|
||||
# The Miller Loop can be batched on any amount of pairings
|
||||
#
|
||||
# Fp12 points are really large (576 bytes for BLS12-381), a projective G2 point is half that (288 bytes)
|
||||
# and we can choose N to be way less than 68.
|
||||
# So for compactness we take Aranha's approach.
|
||||
|
||||
const AccumMax = 8
|
||||
# Max buffer size before triggering a Miller Loop.
|
||||
# Assuming pairing costs 100, with 50 for Miller Loop and 50 for Final exponentiation.
|
||||
#
|
||||
# N unbatched pairings would cost N*100
|
||||
# N maximally batched pairings would cost N*50 + 50
|
||||
# N AccumMax batched pairings would cost N*50 + N/AccumMax*(Fpᵏ mul) + 50
|
||||
#
|
||||
# Fpᵏ mul costs 0.7% of a Miller Loop and so is negligeable.
|
||||
# By choosing AccumMax = 8, we amortized the cost to below 0.1% per pairing.
|
||||
|
||||
type MillerAccumulator*[FF1, FF2; FpK: ExtensionField] = object
|
||||
accum: FpK
|
||||
Ps: array[AccumMax, ECP_ShortW_Aff[FF1, G1]]
|
||||
Qs: array[AccumMax, ECP_ShortW_Aff[FF2, G2]]
|
||||
cur: uint32
|
||||
accOnce: bool
|
||||
|
||||
func init*(ctx: var MillerAccumulator) =
|
||||
ctx.cur = 0
|
||||
ctx.accOnce = false
|
||||
|
||||
func consumeBuffers[FF1, FF2, FpK](ctx: var MillerAccumulator[FF1, FF2, FpK]) =
|
||||
if ctx.cur == 0:
|
||||
return
|
||||
|
||||
var t{.noInit.}: FpK
|
||||
t.millerLoop(ctx.Qs.asUnchecked(), ctx.Ps.asUnchecked(), ctx.cur.int)
|
||||
if ctx.accOnce:
|
||||
ctx.accum *= t
|
||||
else:
|
||||
ctx.accum = t
|
||||
ctx.accOnce = true
|
||||
ctx.cur = 0
|
||||
|
||||
func update*[FF1, FF2, FpK](ctx: var MillerAccumulator[FF1, FF2, FpK], P: ECP_ShortW_Aff[FF1, G1], Q: ECP_ShortW_Aff[FF2, G2]): bool =
|
||||
## Aggregate another set for pairing
|
||||
## This returns `false` if P or Q are the infinity point
|
||||
##
|
||||
## ⚠️: This reveals if a point is infinity through timing side-channels
|
||||
|
||||
if P.isInf().bool or Q.isInf().bool:
|
||||
return false
|
||||
|
||||
if ctx.cur == AccumMax:
|
||||
ctx.consumeBuffers()
|
||||
|
||||
ctx.Ps[ctx.cur] = P
|
||||
ctx.Qs[ctx.cur] = Q
|
||||
ctx.cur += 1
|
||||
return true
|
||||
|
||||
func merge*(ctxDst: var MillerAccumulator, ctxSrc: MillerAccumulator) =
|
||||
## Merge ctxDst <- ctxDst + ctxSrc
|
||||
var dCur = ctxDst.cur
|
||||
var sCur = 0'u
|
||||
var itemsLeft = ctxSrc.cur
|
||||
|
||||
if dCur != 0 and dCur+itemsLeft >= AccumMax:
|
||||
# Previous partial update, fill the buffer and do one miller loop
|
||||
let free = AccumMax - dCur
|
||||
for i in 0 ..< free:
|
||||
ctxDst[dCur+i] = ctxSrc[i]
|
||||
ctxDst.consumeBuffers()
|
||||
dCur = 0
|
||||
sCur = free
|
||||
itemsLeft -= free
|
||||
|
||||
if itemsLeft != 0:
|
||||
# Store the tail
|
||||
for i in 0 ..< itemsLeft:
|
||||
ctxDst[dCur+i] = ctxSrc[sCur+i]
|
||||
|
||||
func finish*[FF1, FF2, FpK](ctx: var MillerAccumulator[FF1, FF2, FpK], multiMillerLoopResult: var Fpk) =
|
||||
## Output the accumulation of multiple Miller Loops
|
||||
ctx.consumeBuffers()
|
||||
multiMillerLoopResult = ctx.accum
|
@ -23,44 +23,44 @@ import
|
||||
# #
|
||||
# ############################################################
|
||||
|
||||
template basicMillerLoop*[FT, F1, F2](
|
||||
func basicMillerLoop*[FT, F1, F2](
|
||||
f: var FT,
|
||||
T: var ECP_ShortW_Prj[F2, G2],
|
||||
line: var Line[F2],
|
||||
T: var ECP_ShortW_Prj[F2, G2],
|
||||
P: ECP_ShortW_Aff[F1, G1],
|
||||
Q, nQ: ECP_ShortW_Aff[F2, G2],
|
||||
ate_param: untyped,
|
||||
ate_param_isNeg: untyped
|
||||
Q: ECP_ShortW_Aff[F2, G2],
|
||||
ate_param: auto,
|
||||
ate_param_isNeg: static bool
|
||||
) =
|
||||
## Basic Miller loop iterations
|
||||
mixin pairing # symbol from zoo_pairings
|
||||
|
||||
static:
|
||||
doAssert FT.C == F1.C
|
||||
doAssert FT.C == F2.C
|
||||
|
||||
f.setOne()
|
||||
var nQ {.noInit.}: ECP_ShortW_Aff[F2, G2]
|
||||
nQ.neg(Q)
|
||||
|
||||
template u: untyped = pairing(C, ate_param)
|
||||
var u3 = pairing(C, ate_param)
|
||||
template u: untyped = ate_param
|
||||
var u3 = ate_param
|
||||
u3 *= 3
|
||||
for i in countdown(u3.bits - 2, 1):
|
||||
square(f)
|
||||
line_double(line, T, P)
|
||||
mul_by_line(f, line)
|
||||
f.square()
|
||||
line.line_double(T, P)
|
||||
f.mul_by_line(line)
|
||||
|
||||
let naf = bit(u3, i).int8 - bit(u, i).int8 # This can throw exception
|
||||
let naf = u3.bit(i).int8 - u.bit(i).int8 # This can throw exception
|
||||
if naf == 1:
|
||||
line_add(line, T, Q, P)
|
||||
mul_by_line(f, line)
|
||||
line.line_add(T, Q, P)
|
||||
f.mul_by_line(line)
|
||||
elif naf == -1:
|
||||
line_add(line, T, nQ, P)
|
||||
mul_by_line(f, line)
|
||||
line.line_add(T, nQ, P)
|
||||
f.mul_by_line(line)
|
||||
|
||||
when pairing(C, ate_param_isNeg):
|
||||
when ate_param_isNeg:
|
||||
# In GT, x^-1 == conjugate(x)
|
||||
# Remark 7.1, chapter 7.1.1 of Guide to Pairing-Based Cryptography, El Mrabet, 2017
|
||||
conj(f)
|
||||
f.conj()
|
||||
|
||||
func millerCorrectionBN*[FT, F1, F2](
|
||||
f: var FT,
|
||||
@ -216,12 +216,13 @@ func miller_accum_double_then_add*[FT, F1, F2](
|
||||
# See `multi_pairing.md``
|
||||
# We implement Aranha approach
|
||||
|
||||
func double_jToN[N: static int, FT, F1, F2](
|
||||
func double_jToN[FT, F1, F2](
|
||||
f: var FT,
|
||||
j: static int,
|
||||
line0, line1: var Line[F2],
|
||||
Ts: var array[N, ECP_ShortW_Prj[F2, G2]],
|
||||
Ps: array[N, ECP_ShortW_Aff[F1, G1]]) =
|
||||
Ts: ptr UncheckedArray[ECP_ShortW_Prj[F2, G2]],
|
||||
Ps: ptr UncheckedArray[ECP_ShortW_Aff[F1, G1]],
|
||||
N: int) =
|
||||
## Doubling steps for pairings j to N
|
||||
|
||||
{.push checks: off.} # No OverflowError or IndexError allowed
|
||||
@ -234,19 +235,20 @@ func double_jToN[N: static int, FT, F1, F2](
|
||||
line1.line_double(Ts[i+1], Ps[i+1])
|
||||
f.mul_by_2_lines(line0, line1)
|
||||
|
||||
when (N and 1) == 1: # N >= 2 and N is odd, there is a leftover
|
||||
if (N and 1) == 1: # N >= 2 and N is odd, there is a leftover
|
||||
line0.line_double(Ts[N-1], Ps[N-1])
|
||||
f.mul_by_line(line0)
|
||||
|
||||
{.pop.}
|
||||
|
||||
func add_jToN[N: static int, FT, F1, F2](
|
||||
func add_jToN[FT, F1, F2](
|
||||
f: var FT,
|
||||
j: static int,
|
||||
line0, line1: var Line[F2],
|
||||
Ts: var array[N, ECP_ShortW_Prj[F2, G2]],
|
||||
Qs: array[N, ECP_ShortW_Aff[F2, G2]],
|
||||
Ps: array[N, ECP_ShortW_Aff[F1, G1]])=
|
||||
Ts: ptr UncheckedArray[ECP_ShortW_Prj[F2, G2]],
|
||||
Qs: ptr UncheckedArray[ECP_ShortW_Aff[F2, G2]],
|
||||
Ps: ptr UncheckedArray[ECP_ShortW_Aff[F1, G1]],
|
||||
N: int)=
|
||||
## Addition steps for pairings 0 to N
|
||||
|
||||
{.push checks: off.} # No OverflowError or IndexError allowed
|
||||
@ -259,24 +261,54 @@ func add_jToN[N: static int, FT, F1, F2](
|
||||
line1.line_add(Ts[i+1], Qs[i+1], Ps[i+1])
|
||||
f.mul_by_2_lines(line0, line1)
|
||||
|
||||
when (N and 1) == 1: # N >= 2 and N is odd, there is a leftover
|
||||
if (N and 1) == 1: # N >= 2 and N is odd, there is a leftover
|
||||
line0.line_add(Ts[N-1], Qs[N-1], Ps[N-1])
|
||||
f.mul_by_line(line0)
|
||||
|
||||
{.pop.}
|
||||
|
||||
template basicMillerLoop*[FT, F1, F2; N: static int](
|
||||
func add_jToN_negateQ[FT, F1, F2](
|
||||
f: var FT,
|
||||
Ts: var array[N, ECP_ShortW_Prj[F2, G2]],
|
||||
j: static int,
|
||||
line0, line1: var Line[F2],
|
||||
Ps: array[N, ECP_ShortW_Aff[F1, G1]],
|
||||
Qs, nQs: array[N, ECP_ShortW_Aff[F2, G2]],
|
||||
ate_param: untyped,
|
||||
ate_param_isNeg: untyped
|
||||
Ts: ptr UncheckedArray[ECP_ShortW_Prj[F2, G2]],
|
||||
Qs: ptr UncheckedArray[ECP_ShortW_Aff[F2, G2]],
|
||||
Ps: ptr UncheckedArray[ECP_ShortW_Aff[F1, G1]],
|
||||
N: int)=
|
||||
## Addition steps for pairings 0 to N
|
||||
|
||||
var nQ{.noInit.}: ECP_ShortW_Aff[F2, G2]
|
||||
|
||||
{.push checks: off.} # No OverflowError or IndexError allowed
|
||||
# Sparse merge 2 by 2, starting from 0
|
||||
for i in countup(j, N-1, 2):
|
||||
if i+1 >= N:
|
||||
break
|
||||
|
||||
nQ.neg(Qs[i])
|
||||
line0.line_add(Ts[i], nQ, Ps[i])
|
||||
nQ.neg(Qs[i+1])
|
||||
line1.line_add(Ts[i+1], nQ, Ps[i+1])
|
||||
f.mul_by_2_lines(line0, line1)
|
||||
|
||||
if (N and 1) == 1: # N >= 2 and N is odd, there is a leftover
|
||||
nQ.neg(Qs[N-1])
|
||||
line0.line_add(Ts[N-1], nQ, Ps[N-1])
|
||||
f.mul_by_line(line0)
|
||||
|
||||
{.pop.}
|
||||
|
||||
func basicMillerLoop*[FT, F1, F2](
|
||||
f: var FT,
|
||||
line0, line1: var Line[F2],
|
||||
Ts: ptr UncheckedArray[ECP_ShortW_Prj[F2, G2]],
|
||||
Ps: ptr UncheckedArray[ECP_ShortW_Aff[F1, G1]],
|
||||
Qs: ptr UncheckedArray[ECP_ShortW_Aff[F2, G2]],
|
||||
N: int,
|
||||
ate_param: auto,
|
||||
ate_param_isNeg: static bool
|
||||
) =
|
||||
## Basic Miller loop iterations
|
||||
# TODO: recompute nQ on-the-fly to save stack space
|
||||
mixin pairing # symbol from zoo_pairings
|
||||
|
||||
static:
|
||||
doAssert FT.C == F1.C
|
||||
@ -284,29 +316,30 @@ template basicMillerLoop*[FT, F1, F2; N: static int](
|
||||
|
||||
f.setOne()
|
||||
|
||||
template u: untyped = pairing(C, ate_param)
|
||||
var u3 = pairing(C, ate_param)
|
||||
template u: untyped = ate_param
|
||||
var u3 = ate_param
|
||||
u3 *= 3
|
||||
for i in countdown(u3.bits - 2, 1):
|
||||
f.square()
|
||||
double_jToN(f, j=0, line0, line1, Ts, Ps)
|
||||
f.double_jToN(j=0, line0, line1, Ts, Ps, N)
|
||||
|
||||
let naf = bit(u3, i).int8 - bit(u, i).int8 # This can throw exception
|
||||
let naf = u3.bit(i).int8 - u.bit(i).int8 # This can throw exception
|
||||
if naf == 1:
|
||||
add_jToN(f, j=0, line0, line1, Ts, Qs, Ps)
|
||||
f.add_jToN(j=0, line0, line1, Ts, Qs, Ps, N)
|
||||
elif naf == -1:
|
||||
add_jToN(f, j=0, line0, line1, Ts, nQs, Ps)
|
||||
f.add_jToN_negateQ(j=0, line0, line1, Ts, Qs, Ps, N)
|
||||
|
||||
when pairing(C, ate_param_isNeg):
|
||||
when ate_param_isNeg:
|
||||
# In GT, x^-1 == conjugate(x)
|
||||
# Remark 7.1, chapter 7.1.1 of Guide to Pairing-Based Cryptography, El Mrabet, 2017
|
||||
conj(f)
|
||||
f.conj()
|
||||
|
||||
func miller_init_double_then_add*[N: static int, FT, F1, F2](
|
||||
func miller_init_double_then_add*[FT, F1, F2](
|
||||
f: var FT,
|
||||
Ts: var array[N, ECP_ShortW_Prj[F2, G2]],
|
||||
Qs: array[N, ECP_ShortW_Aff[F2, G2]],
|
||||
Ps: array[N, ECP_ShortW_Aff[F1, G1]],
|
||||
Ts: ptr UncheckedArray[ECP_ShortW_Prj[F2, G2]],
|
||||
Qs: ptr UncheckedArray[ECP_ShortW_Aff[F2, G2]],
|
||||
Ps: ptr UncheckedArray[ECP_ShortW_Aff[F1, G1]],
|
||||
N: int,
|
||||
numDoublings: static int
|
||||
) =
|
||||
## Start a Miller Loop
|
||||
@ -329,42 +362,46 @@ func miller_init_double_then_add*[N: static int, FT, F1, F2](
|
||||
Ts[i].fromAffine(Qs[i])
|
||||
|
||||
line0.line_double(Ts[0], Ps[0])
|
||||
when N >= 2:
|
||||
if N >= 2:
|
||||
line1.line_double(Ts[1], Ps[1])
|
||||
f.prod_from_2_lines(line0, line1)
|
||||
f.double_jToN(j=2, line0, line1, Ts, Ps)
|
||||
f.double_jToN(j=2, line0, line1, Ts, Ps, N)
|
||||
|
||||
# Doubling steps: 0b10...00
|
||||
# ------------------------------------------------
|
||||
when numDoublings > 1: # Already did the MSB doubling
|
||||
when N == 1: # f = line0
|
||||
if N == 1: # f = line0
|
||||
f.prod_from_2_lines(line0, line0) # f.square()
|
||||
line0.line_double(Ts[0], Ps[0])
|
||||
f.mul_by_line(line0)
|
||||
for _ in 2 ..< numDoublings:
|
||||
f.square()
|
||||
f.double_jtoN(j=0, line0, line1, Ts, Ps)
|
||||
f.double_jtoN(j=0, line0, line1, Ts, Ps, N)
|
||||
else:
|
||||
for _ in 0 ..< numDoublings:
|
||||
f.square()
|
||||
f.double_jtoN(j=0, line0, line1, Ts, Ps)
|
||||
f.double_jtoN(j=0, line0, line1, Ts, Ps, N)
|
||||
|
||||
# Addition step: 0b10...01
|
||||
# ------------------------------------------------
|
||||
|
||||
when numDoublings == 1 and N == 1: # f = line0
|
||||
line1.line_add(Ts[0], Qs[0], Ps[0])
|
||||
f.prod_from_2_lines(line0, line1)
|
||||
when numDoublings == 1:
|
||||
if N == 1: # f = line0
|
||||
line1.line_add(Ts[0], Qs[0], Ps[0])
|
||||
f.prod_from_2_lines(line0, line1)
|
||||
else:
|
||||
f.add_jToN(j=0,line0, line1, Ts, Qs, Ps, N)
|
||||
else:
|
||||
f.add_jToN(j=0,line0, line1, Ts, Qs, Ps)
|
||||
f.add_jToN(j=0,line0, line1, Ts, Qs, Ps, N)
|
||||
|
||||
{.pop.} # No OverflowError or IndexError allowed
|
||||
|
||||
func miller_accum_double_then_add*[N: static int, FT, F1, F2](
|
||||
func miller_accum_double_then_add*[FT, F1, F2](
|
||||
f: var FT,
|
||||
Ts: var array[N, ECP_ShortW_Prj[F2, G2]],
|
||||
Qs: array[N, ECP_ShortW_Aff[F2, G2]],
|
||||
Ps: array[N, ECP_ShortW_Aff[F1, G1]],
|
||||
Ts: ptr UncheckedArray[ECP_ShortW_Prj[F2, G2]],
|
||||
Qs: ptr UncheckedArray[ECP_ShortW_Aff[F2, G2]],
|
||||
Ps: ptr UncheckedArray[ECP_ShortW_Aff[F1, G1]],
|
||||
N: int,
|
||||
numDoublings: int,
|
||||
add = true
|
||||
) =
|
||||
@ -376,7 +413,7 @@ func miller_accum_double_then_add*[N: static int, FT, F1, F2](
|
||||
var line0{.noInit.}, line1{.noinit.}: Line[F2]
|
||||
for _ in 0 ..< numDoublings:
|
||||
f.square()
|
||||
f.double_jtoN(j=0, line0, line1, Ts, Ps)
|
||||
f.double_jtoN(j=0, line0, line1, Ts, Ps, N)
|
||||
|
||||
if add:
|
||||
f.add_jToN(j=0, line0, line1, Ts, Qs, Ps)
|
||||
f.add_jToN(j=0, line0, line1, Ts, Qs, Ps, N)
|
@ -86,8 +86,8 @@ and then pairings of each tuple are directly merged on GT.
|
||||
Scott approach is fully "online"/"streaming",
|
||||
while Aranha's saves space.
|
||||
For BLS12_381,
|
||||
M = 68 hence we would need 68*12*48 = 39168 bytes (381-bit needs 48 bytes)
|
||||
G2 has size 3*2*48 = 288 bytes (3 proj coordinates on Fp2)
|
||||
M = 68 hence we would need 68\*12\*48 = 39168 bytes (381-bit needs 48 bytes)
|
||||
G2 has size 3\*2\*48 = 288 bytes (3 proj coordinates on Fp2)
|
||||
and while we can choose N to be anything (which can be 1 for single pairing or reverting to Scott approach).
|
||||
|
||||
In practice, "streaming pairings" are not used, pairings to compute are receive
|
@ -17,7 +17,7 @@ import
|
||||
../isogenies/frobenius,
|
||||
../constants/zoo_pairings,
|
||||
../arithmetic,
|
||||
./cyclotomic_subgroup,
|
||||
./cyclotomic_subgroups,
|
||||
./lines_eval,
|
||||
./miller_loops
|
||||
|
||||
@ -63,15 +63,13 @@ func millerLoopGenericBLS12*[C](
|
||||
var
|
||||
T {.noInit.}: ECP_ShortW_Prj[Fp2[C], G2]
|
||||
line {.noInit.}: Line[Fp2[C]]
|
||||
nQ{.noInit.}: typeof(Q)
|
||||
|
||||
T.fromAffine(Q)
|
||||
nQ.neg(Q)
|
||||
|
||||
basicMillerLoop(
|
||||
f, T, line,
|
||||
P, Q, nQ,
|
||||
ate_param, ate_param_isNeg
|
||||
f, line, T,
|
||||
P, Q,
|
||||
pairing(C, ate_param), pairing(C, ate_param_isNeg)
|
||||
)
|
||||
|
||||
func finalExpGeneric[C: static Curve](f: var Fp12[C]) =
|
||||
@ -169,6 +167,6 @@ func pairing_bls12*[N: static int, C](
|
||||
## Output:
|
||||
## The product of pairings
|
||||
## e(P₀, Q₀) * e(P₁, Q₁) * e(P₂, Q₂) * ... * e(Pₙ, Qₙ) ∈ Gt
|
||||
gt.millerLoopAddchain(Qs, Ps)
|
||||
gt.millerLoopAddchain(Qs.asUnchecked(), Ps.asUnchecked(), N)
|
||||
gt.finalExpEasy()
|
||||
gt.finalExpHard_BLS12()
|
@ -7,7 +7,7 @@
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
../../platforms/abstractions,
|
||||
../../platforms/[abstractions, allocs],
|
||||
../config/curves,
|
||||
../extension_fields,
|
||||
../elliptic/[
|
||||
@ -17,7 +17,7 @@ import
|
||||
../isogenies/frobenius,
|
||||
../constants/zoo_pairings,
|
||||
./lines_eval,
|
||||
./cyclotomic_subgroup,
|
||||
./cyclotomic_subgroups,
|
||||
./miller_loops
|
||||
|
||||
export zoo_pairings # generic sandwich https://github.com/nim-lang/Nim/issues/11225
|
||||
@ -55,19 +55,16 @@ func millerLoopGenericBN*[C](
|
||||
) {.meter.} =
|
||||
## Generic Miller Loop for BN curves
|
||||
## Computes f{6u+2,Q}(P) with u the BN curve parameter
|
||||
|
||||
var
|
||||
T {.noInit.}: ECP_ShortW_Prj[Fp2[C], G2]
|
||||
line {.noInit.}: Line[Fp2[C]]
|
||||
nQ{.noInit.}: typeof(Q)
|
||||
|
||||
T.fromAffine(Q)
|
||||
nQ.neg(Q)
|
||||
|
||||
basicMillerLoop(
|
||||
f, T, line,
|
||||
P, Q, nQ,
|
||||
ate_param, ate_param_isNeg
|
||||
f, line, T,
|
||||
P, Q,
|
||||
pairing(C, ate_param), pairing(C, ate_param_isNeg)
|
||||
)
|
||||
|
||||
# Ate pairing for BN curves needs adjustment after basic Miller loop
|
||||
@ -76,28 +73,25 @@ func millerLoopGenericBN*[C](
|
||||
pairing(C, ate_param_isNeg)
|
||||
)
|
||||
|
||||
func millerLoopGenericBN*[N: static int, C](
|
||||
func millerLoopGenericBN*[C](
|
||||
f: var Fp12[C],
|
||||
Ps: array[N, ECP_ShortW_Aff[Fp[C], G1]],
|
||||
Qs: array[N, ECP_ShortW_Aff[Fp2[C], G2]]
|
||||
Ps: ptr UncheckedArray[ECP_ShortW_Aff[Fp[C], G1]],
|
||||
Qs: ptr UncheckedArray[ECP_ShortW_Aff[Fp2[C], G2]],
|
||||
N: int
|
||||
) {.meter.} =
|
||||
## Generic Miller Loop for BN curves
|
||||
## Computes f{6u+2,Q}(P) with u the BN curve parameter
|
||||
|
||||
var
|
||||
Ts {.noInit.}: array[N, ECP_ShortW_Prj[Fp2[C], G2]]
|
||||
Ts = allocStackArray(ECP_ShortW_Prj[Fp2[C], G2], N)
|
||||
line0 {.noInit.}, line1 {.noInit.}: Line[Fp2[C]]
|
||||
nQs{.noInit.}: typeof(Qs)
|
||||
|
||||
for i in 0 ..< N:
|
||||
Ts[i].fromAffine(Qs[i])
|
||||
for i in 0 ..< N:
|
||||
nQs[i].neg(Qs[i])
|
||||
|
||||
basicMillerLoop(
|
||||
f, Ts, line0, line1,
|
||||
Ps, Qs, nQs,
|
||||
ate_param, ate_param_isNeg
|
||||
f, line0, line1, Ts,
|
||||
Ps, Qs, N,
|
||||
pairing(C, ate_param), pairing(C, ate_param_isNeg)
|
||||
)
|
||||
|
||||
# Ate pairing for BN curves needs adjustment after basic Miller loop
|
||||
@ -200,8 +194,8 @@ func pairing_bn*[N: static int, C](
|
||||
## The product of pairings
|
||||
## e(P₀, Q₀) * e(P₁, Q₁) * e(P₂, Q₂) * ... * e(Pₙ, Qₙ) ∈ Gt
|
||||
when C == BN254_Nogami:
|
||||
gt.millerLoopAddChain(Qs, Ps)
|
||||
gt.millerLoopAddChain(Qs.asUnchecked(), Ps.asUnchecked(), N)
|
||||
else:
|
||||
gt.millerLoopGenericBN(Ps, Qs)
|
||||
gt.millerLoopGenericBN(Ps.asUnchecked(), Qs.asUnchecked(), N)
|
||||
gt.finalExpEasy()
|
||||
gt.finalExpHard_BN()
|
@ -42,24 +42,22 @@ func millerLoopBW6_761_naive[C](
|
||||
var
|
||||
T {.noInit.}: ECP_ShortW_Prj[Fp[C], G2]
|
||||
line {.noInit.}: Line[Fp[C]]
|
||||
nQ{.noInit.}: typeof(Q)
|
||||
|
||||
T.fromAffine(Q)
|
||||
nQ.neg(Q)
|
||||
|
||||
basicMillerLoop(
|
||||
f, T, line,
|
||||
P, Q, nQ,
|
||||
ate_param_1_unopt, ate_param_1_unopt_isNeg
|
||||
f, line, T,
|
||||
P, Q,
|
||||
pairing(C, ate_param_1_unopt), pairing(C, ate_param_1_unopt_isNeg)
|
||||
)
|
||||
|
||||
var f2 {.noInit.}: typeof(f)
|
||||
T.fromAffine(Q)
|
||||
|
||||
basicMillerLoop(
|
||||
f2, T, line,
|
||||
P, Q, nQ,
|
||||
ate_param_2_unopt, ate_param_2_unopt_isNeg
|
||||
f2, line, T,
|
||||
P, Q,
|
||||
pairing(C, ate_param_2_unopt), pairing(C, ate_param_2_unopt_isNeg)
|
||||
)
|
||||
|
||||
let t = f2
|
@ -7,9 +7,11 @@
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
./config/curves,
|
||||
./pairing/[pairing_bn, pairing_bls12],
|
||||
./extension_fields
|
||||
../config/curves,
|
||||
./cyclotomic_subgroups,
|
||||
./pairings_bn, ./pairings_bls12,
|
||||
../extension_fields,
|
||||
../constants/zoo_pairings
|
||||
|
||||
func pairing*[C](gt: var Fp12[C], P, Q: auto) {.inline.} =
|
||||
when family(C) == BarretoNaehrig:
|
||||
@ -17,4 +19,19 @@ func pairing*[C](gt: var Fp12[C], P, Q: auto) {.inline.} =
|
||||
elif family(C) == BarretoLynnScott:
|
||||
pairing_bls12(gt, P, Q)
|
||||
else:
|
||||
{.error: "Pairing not implemented for " & $C.}
|
||||
{.error: "Pairing not implemented for " & $C.}
|
||||
|
||||
func millerLoop*[C](gt: var Fp12[C], P, Q: auto, n: int) {.inline.} =
|
||||
when C == BN254_Snarks:
|
||||
gt.millerLoopGenericBN(P, Q, n)
|
||||
else:
|
||||
gt.millerLoopAddchain(P, Q, n)
|
||||
|
||||
func finalExp*[C](gt: var Fp12[C]){.inline.} =
|
||||
gt.finalExpEasy()
|
||||
when family(C) == BarretoNaehrig:
|
||||
gt.finalExpHard_BN()
|
||||
elif family(C) == BarretoLynnScott:
|
||||
gt.finalExpHard_BLS12()
|
||||
else:
|
||||
{.error: "Final Exponentiation not implemented for " & $C.}
|
@ -7,10 +7,12 @@
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
../math/[ec_shortweierstrass, pairings],
|
||||
../math/[ec_shortweierstrass, extension_fields],
|
||||
../math/elliptic/ec_shortweierstrass_batch_ops,
|
||||
../math/pairings/[pairings_generic, miller_accumulators],
|
||||
../math/constants/zoo_generators,
|
||||
../hash_to_curve/hash_to_curve,
|
||||
../math/config/curves,
|
||||
../hash_to_curve/[hash_to_curve, h2c_hash_to_field],
|
||||
../hashes
|
||||
|
||||
# ############################################################
|
||||
@ -27,10 +29,8 @@ import
|
||||
# so tat the algorithms fit whether Pubkey and Sig are on G1 or G2
|
||||
# Actual protocols should expose publicly the full names SecretKey, PublicKey and Signature
|
||||
|
||||
|
||||
{.push inline.} # inline in the main public procs
|
||||
{.push raises: [].} # No exceptions allowed in core cryptographic operations
|
||||
|
||||
{.push checks: off.} # No defects due to array bound checking or signed integer overflow allowed
|
||||
|
||||
func derivePubkey*[Pubkey, SecKey](pubkey: var Pubkey, seckey: SecKey): bool =
|
||||
## Generates the public key associated with the input secret key.
|
||||
@ -39,7 +39,7 @@ func derivePubkey*[Pubkey, SecKey](pubkey: var Pubkey, seckey: SecKey): bool =
|
||||
## - false is secret key is invalid (SK == 0 or >= BLS12-381 curve order),
|
||||
## true otherwise
|
||||
## By construction no public API should ever instantiate
|
||||
## an invalid secretkey in the first place.
|
||||
## an invalid secretkey in the first place.
|
||||
const Group = Pubkey.G
|
||||
type Field = Pubkey.F
|
||||
const EC = Field.C
|
||||
@ -64,10 +64,10 @@ func coreSign*[B1, B2, B3: byte|char, Sig, SecKey](
|
||||
augmentation: openarray[B2],
|
||||
domainSepTag: openarray[B3]) =
|
||||
## Computes a signature for the message from the specified secret key.
|
||||
##
|
||||
##
|
||||
## Output:
|
||||
## - `signature` is overwritten with `message` signed with `secretKey`
|
||||
##
|
||||
##
|
||||
## Inputs:
|
||||
## - `Hash` a cryptographic hash function.
|
||||
## - `Hash` MAY be a Merkle-Damgaard hash function like SHA-2
|
||||
@ -85,7 +85,7 @@ func coreSign*[B1, B2, B3: byte|char, Sig, SecKey](
|
||||
## and `CoreVerify(PK, PK || message, signature)`
|
||||
## - `message` is the message to hash
|
||||
## - `domainSepTag` is the protocol domain separation tag (DST).
|
||||
|
||||
|
||||
type ECP_Jac = ECP_ShortW_Jac[Sig.F, Sig.G]
|
||||
|
||||
var sig {.noInit.}: ECP_Jac
|
||||
@ -128,37 +128,542 @@ func coreVerify*[B1, B2, B3: byte|char, Pubkey, Sig](
|
||||
|
||||
# ############################################################
|
||||
#
|
||||
# Aggregate verification
|
||||
# Aggregate and Batched Signature Verification
|
||||
# Accumulators
|
||||
#
|
||||
# ############################################################
|
||||
#
|
||||
# Terminology:
|
||||
#
|
||||
# - fastAggregateVerify:
|
||||
# Verify the aggregate of multiple signatures by multiple pubkeys
|
||||
# on the same message.
|
||||
# Verify the aggregate of multiple signatures on the same message by multiple pubkeys
|
||||
#
|
||||
# - aggregateVerify:
|
||||
# Verify the aggregate of multiple signatures by multiple (pubkey, message) pairs
|
||||
# Verify the aggregated signature of multiple (pubkey, message) pairs
|
||||
#
|
||||
# - batchVerify:
|
||||
# Verify that all (pubkey, message, signature) triplets are valid
|
||||
|
||||
func fastAggregateVerify*[B1, B2, B3: byte|char, Pubkey, Sig](
|
||||
# Aggregate Signatures
|
||||
# ------------------------------------------------------------
|
||||
|
||||
type
|
||||
BLSAggregateSigAccumulator*[H: CryptoHash, FF1, FF2; Fpk: ExtensionField; k: static int] = object
|
||||
## An accumulator for Aggregate BLS signature verification.
|
||||
## Note:
|
||||
## This is susceptible to "splitting-zero" attacks
|
||||
## - https://eprint.iacr.org/2021/323.pdf
|
||||
## - https://eprint.iacr.org/2021/377.pdf
|
||||
## To avoid splitting zeros and rogue keys attack:
|
||||
## 1. Public keys signing the same message MUST be aggregated and checked for 0 before calling BLSAggregateSigAccumulator.update()
|
||||
## 2. Augmentation or Proof of possessions must used for each public keys.
|
||||
|
||||
# An accumulator for the Miller loops
|
||||
millerAccum: MillerAccumulator[FF1, FF2, Fpk]
|
||||
|
||||
domainSepTag{.align: 64.}: array[255, byte] # Alignment to enable SIMD
|
||||
dst_len: uint8
|
||||
|
||||
func init*[T: char|byte](
|
||||
ctx: var BLSAggregateSigAccumulator, domainSepTag: openArray[T]) =
|
||||
## Initializes a BLS Aggregate Signature accumulator context.
|
||||
|
||||
type H = BLSAggregateSigAccumulator.H
|
||||
|
||||
ctx.millerAccum.init()
|
||||
|
||||
if domainSepTag.len > 255:
|
||||
var t {.noInit.}: array[H.digestSize(), byte]
|
||||
H.shortDomainSepTag(output = t, domainSepTag)
|
||||
copy(ctx.domainSepTag, dStart = 0,
|
||||
t, sStart = 0,
|
||||
H.digestSize())
|
||||
ctx.dst_len = uint8 H.digestSize()
|
||||
else:
|
||||
copy(ctx.domainSepTag, dStart = 0,
|
||||
domainSepTag, sStart = 0,
|
||||
domainSepTag.len)
|
||||
ctx.dst_len = uint8 domainSepTag.len
|
||||
for i in ctx.dst_len ..< ctx.domainSepTag.len:
|
||||
ctx.domainSepTag[i] = byte 0
|
||||
|
||||
func update*[T: char|byte, Pubkey: ECP_ShortW_Aff](
|
||||
ctx: var BLSAggregateSigAccumulator,
|
||||
pubkey: Pubkey,
|
||||
message: openArray[T]): bool =
|
||||
## Add a (public key, message) pair
|
||||
## to a BLS aggregate signature accumulator
|
||||
##
|
||||
## Assumes that the public key has been group checked
|
||||
##
|
||||
## Returns false if pubkey is the infinity point
|
||||
|
||||
const k = BLSAggregateSigAccumulator.k
|
||||
type H = BLSAggregateSigAccumulator.H
|
||||
|
||||
when Pubkey.G == G1:
|
||||
# Pubkey on G1, H(message) and Signature on G2
|
||||
type FF2 = BLSAggregateSigAccumulator.FF2
|
||||
var hmsgG2_aff {.noInit.}: ECP_ShortW_Aff[FF2, G2]
|
||||
H.hashToCurve(
|
||||
k, output = hmsgG2_aff,
|
||||
augmentation = "", message,
|
||||
ctx.domainSepTag.toOpenArray(0, ctx.dst_len.int - 1))
|
||||
|
||||
ctx.millerAccum.update(pubkey, hmsgG2_aff)
|
||||
|
||||
else:
|
||||
# Pubkey on G2, H(message) and Signature on G1
|
||||
type FF1 = BLSAggregateSigAccumulator.FF1
|
||||
var hmsgG1_aff {.noInit.}: ECP_ShortW_Aff[FF1, G1]
|
||||
H.hashToCurve(
|
||||
k, output = hmsgG1_aff,
|
||||
augmentation = "", message,
|
||||
ctx.domainSepTag.toOpenArray(0, ctx.dst_len.int - 1))
|
||||
|
||||
ctx.millerAccum.update(hmsgG1_aff, pubkey)
|
||||
|
||||
func merge*(ctxDst: var BLSAggregateSigAccumulator, ctxSrc: BLSAggregateSigAccumulator): bool =
|
||||
## Merge 2 BLS signature accumulators: ctxDst <- ctxDst + ctxSrc
|
||||
##
|
||||
## Returns false if they have inconsistent DomainSeparationTag and true otherwise.
|
||||
if ctxDst.dst_len != ctxSrc.dst_len:
|
||||
return false
|
||||
if not equalMem(ctxDst.domainSepTag.addr, ctxSrc.domainSepTag.addr, ctxDst.domainSepTag.len):
|
||||
return false
|
||||
|
||||
ctxDst.millerAccum.merge(ctxSrc.millerAccum)
|
||||
|
||||
func finalVerify*[F, G](ctx: var BLSAggregateSigAccumulator, aggregateSignature: ECP_ShortW_Aff[F, G]): bool =
|
||||
## Finish batch and/or aggregate signature verification and returns the final result.
|
||||
##
|
||||
## Returns false if nothing was accumulated
|
||||
## Rteturns false on verification failure
|
||||
|
||||
type FF1 = BLSAggregateSigAccumulator.FF1
|
||||
type FF2 = BLSAggregateSigAccumulator.FF2
|
||||
type Fpk = BLSAggregateSigAccumulator.Fpk
|
||||
|
||||
when G == G2:
|
||||
type PubKey = ECP_ShortW_Aff[FF1, G1]
|
||||
else:
|
||||
type PubKey = ECP_ShortW_Aff[FF2, G2]
|
||||
|
||||
var negG {.noInit.}: Pubkey
|
||||
negG.neg(Pubkey.F.C.getGenerator($Pubkey.G))
|
||||
|
||||
when G == G2:
|
||||
if not ctx.millerAccum.update(negG, aggregateSignature):
|
||||
return false
|
||||
else:
|
||||
if not ctx.millerAccum.update(aggregateSignature, negG):
|
||||
return false
|
||||
|
||||
var gt {.noinit.}: Fpk
|
||||
ctx.millerAccum.finish(gt)
|
||||
gt.finalExp()
|
||||
return gt.isOne().bool
|
||||
|
||||
# Batch Signatures
|
||||
# ------------------------------------------------------------
|
||||
|
||||
type
|
||||
BLSBatchSigAccumulator*[H: CryptoHash, FF1, FF2; Fpk: ExtensionField; SigAccum: ECP_ShortW_Jac, k: static int] = object
|
||||
## An accumulator for Batched BLS signature verification
|
||||
|
||||
# An accumulator for the Miller loops
|
||||
millerAccum: MillerAccumulator[FF1, FF2, Fpk]
|
||||
|
||||
# An accumulator for signatures:
|
||||
# signature verification is in the form
|
||||
# with PK a public key, H(𝔪) the hash of a message to sign, sig the signature
|
||||
#
|
||||
# e(PK, H(𝔪)).e(generator, sig) == 1
|
||||
#
|
||||
# For aggregate or batch verification
|
||||
# e(PK₀, H(𝔪₀)).e(-generator, sig₀).e(PK, H(𝔪₁)).e(-generator, sig₁) == 1
|
||||
#
|
||||
# Due to bilinearity of pairings:
|
||||
# e(PK₀, H(𝔪₀)).e(PK₁, H(𝔪₁)).e(-generator, sig₀+sig₁) == 1
|
||||
#
|
||||
# Hence we can divide cost of aggregate and batch verification by 2 if we aggregate signatures
|
||||
aggSig: SigAccum
|
||||
aggSigOnce: bool
|
||||
|
||||
domainSepTag{.align: 64.}: array[255, byte] # Alignment to enable SIMD
|
||||
dst_len: uint8
|
||||
|
||||
# This field holds a secure blinding scalar,
|
||||
# it does not use secret data but it is necessary
|
||||
# to have data not in the control of an attacker
|
||||
# to prevent forging valid aggregated signatures
|
||||
# from 2 invalid individual signatures using
|
||||
# the bilinearity property of pairings.
|
||||
# https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407/14
|
||||
#
|
||||
# Assuming blinding muls cost 60% of a pairing (worst case with 255-bit blinding)
|
||||
# verifying 3 signatures would have a base cost of 300
|
||||
# Batched single threaded the cost would be
|
||||
# 60*3 (blinding 255-bit) + 50 (Miller) + 50 (final exp) = 280
|
||||
#
|
||||
# With 64-bit blinding and ~20% overhead
|
||||
# (not 15% because no endomorphism acceleration with 64-bit)
|
||||
# 20*3 (blinding 64-bit) + 50 (Miller) + 50 (final exp) = 160
|
||||
#
|
||||
# If split on 2 cores, the critical path is
|
||||
# 20*2 (blinding 64-bit) + 50 (Miller) + 50 (final exp) = 140
|
||||
#
|
||||
# If split on 3 cores, the critical path is
|
||||
# 20*1 (blinding 64-bit) + 50 (Miller) + 50 (final exp) = 120
|
||||
secureBlinding{.align: 32.}: array[32, byte]
|
||||
|
||||
func hash[DigestSize: static int, T0, T1: char|byte](
|
||||
H: type CryptoHash, digest: var array[DigestSize, byte], input0: openArray[T0], input1: openArray[T1]) =
|
||||
|
||||
static: doAssert DigestSize == H.digestSize()
|
||||
|
||||
var h{.noInit.}: H
|
||||
h.init()
|
||||
h.update(input0)
|
||||
h.update(input1)
|
||||
h.finish(digest)
|
||||
|
||||
func init*[T0, T1: char|byte](
|
||||
ctx: var BLSBatchSigAccumulator, domainSepTag: openArray[T0],
|
||||
secureRandomBytes: array[32, byte], accumSepTag: openArray[T1]) =
|
||||
## Initializes a Batch BLS Signature accumulator context.
|
||||
##
|
||||
## This requires cryptographically secure random bytes
|
||||
## to defend against forged signatures that would not
|
||||
## verify individually but would verify while aggregated
|
||||
## https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407/14
|
||||
##
|
||||
## An optional accumulator separation tag can be added
|
||||
## so that from a single source of randomness
|
||||
## each accumulatpr is seeded with a different state.
|
||||
## This is useful in multithreaded context.
|
||||
|
||||
type H = BLSBatchSigAccumulator.H
|
||||
|
||||
ctx.millerAccum.init()
|
||||
ctx.aggSigOnce = false
|
||||
|
||||
if domainSepTag.len > 255:
|
||||
var t {.noInit.}: array[H.digestSize(), byte]
|
||||
H.shortDomainSepTag(output = t, domainSepTag)
|
||||
copy(ctx.domainSepTag, dStart = 0,
|
||||
t, sStart = 0,
|
||||
H.digestSize())
|
||||
ctx.dst_len = uint8 H.digestSize()
|
||||
else:
|
||||
copy(ctx.domainSepTag, dStart = 0,
|
||||
domainSepTag, sStart = 0,
|
||||
domainSepTag.len)
|
||||
ctx.dst_len = uint8 domainSepTag.len
|
||||
for i in ctx.dst_len ..< ctx.domainSepTag.len:
|
||||
ctx.domainSepTag[i] = byte 0
|
||||
|
||||
H.hash(ctx.secureBlinding, secureRandomBytes, accumSepTag)
|
||||
|
||||
iterator unpack(scalarByte: byte): bool =
|
||||
yield bool((scalarByte and 0b10000000) shr 7)
|
||||
yield bool((scalarByte and 0b01000000) shr 6)
|
||||
yield bool((scalarByte and 0b00100000) shr 5)
|
||||
yield bool((scalarByte and 0b00010000) shr 4)
|
||||
yield bool((scalarByte and 0b00001000) shr 3)
|
||||
yield bool((scalarByte and 0b00000100) shr 2)
|
||||
yield bool((scalarByte and 0b00000010) shr 1)
|
||||
yield bool( scalarByte and 0b00000001)
|
||||
|
||||
func scalarMul_doubleAdd_vartime[EC](
|
||||
P: var EC,
|
||||
scalarCanonical: openArray[byte],
|
||||
) =
|
||||
## **Variable-time** Elliptic Curve Scalar Multiplication
|
||||
##
|
||||
## P <- [k] P
|
||||
##
|
||||
## This uses the double-and-add algorithm
|
||||
## This is UNSAFE to use with secret data and is only intended for signature verification
|
||||
## to multiply by random blinding scalars.
|
||||
## Due to those scalars being 64-bit, window-method or endomorphism acceleration are slower
|
||||
## than double-and-add.
|
||||
##
|
||||
## This is highly VULNERABLE to timing attacks and power analysis attacks.
|
||||
var t0{.noInit.}, t1{.noInit.}: typeof(P)
|
||||
t0.setInf()
|
||||
t1.setInf()
|
||||
for scalarByte in scalarCanonical:
|
||||
for bit in unpack(scalarByte):
|
||||
t1.double(t0)
|
||||
if bit:
|
||||
t0.sum(t1, P)
|
||||
else:
|
||||
t0 = t1
|
||||
P = t0
|
||||
|
||||
func update*[T: char|byte, Pubkey, Sig: ECP_ShortW_Aff](
|
||||
ctx: var BLSBatchSigAccumulator,
|
||||
pubkey: Pubkey,
|
||||
message: openArray[T],
|
||||
signature: Sig): bool =
|
||||
## Add a (public key, message, signature) triplet
|
||||
## to a BLS signature accumulator
|
||||
##
|
||||
## Assumes that the public key and signature
|
||||
## have been group checked
|
||||
##
|
||||
## Returns false if pubkey or signatures are the infinity points
|
||||
|
||||
# The derivation of a secure scalar
|
||||
# MUST not output 0.
|
||||
# HKDF mod R for EIP2333 is suitable.
|
||||
# We can also consider using something
|
||||
# hardware-accelerated like AES.
|
||||
#
|
||||
# However the curve order r = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001
|
||||
# is 255 bits and 255-bit scalar mul on G2
|
||||
# costs 43% of a pairing and on G1 20%,
|
||||
# and we need to multiply both the signature
|
||||
# and the public key or message.
|
||||
# This blinding scheme would have a lot overhead
|
||||
# for single threaded.
|
||||
#
|
||||
# As we don't protect secret data here
|
||||
# and only want extra data not in possession of the attacker
|
||||
# we only use a 1..<2^64 random blinding factor.
|
||||
# We assume that the attacker cannot resubmit 2^64 times
|
||||
# forged public keys and signatures.
|
||||
# Discussion https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407
|
||||
|
||||
# We only use the first 8 bytes for blinding
|
||||
# but use the full 32 bytes to derive new random scalar
|
||||
|
||||
const k = BLSBatchSigAccumulator.k
|
||||
type H = BLSBatchSigAccumulator.H
|
||||
|
||||
while true: # Ensure we don't multiply by 0 for blinding
|
||||
H.hash(ctx.secureBlinding, ctx.secureBlinding)
|
||||
|
||||
var accum = byte 0
|
||||
for i in 0 ..< 8:
|
||||
accum = accum or ctx.secureBlinding[i]
|
||||
if accum != byte 0:
|
||||
break
|
||||
|
||||
when Pubkey.G == G1:
|
||||
# Pubkey on G1, H(message) and Signature on G2
|
||||
var pkG1_jac {.noInit.}: ECP_ShortW_Jac[Pubkey.F, Pubkey.G]
|
||||
var sigG2_jac {.noInit.}: ECP_ShortW_Jac[Sig.F, Sig.G]
|
||||
|
||||
pkG1_jac.fromAffine(pubkey)
|
||||
sigG2_jac.fromAffine(signature)
|
||||
|
||||
pkG1_jac.scalarMul_doubleAdd_vartime(ctx.secureBlinding.toOpenArray(0, 7))
|
||||
sigG2_jac.scalarMul_doubleAdd_vartime(ctx.secureBlinding.toOpenArray(0, 7))
|
||||
|
||||
if ctx.aggSigOnce == false:
|
||||
ctx.aggSig = sigG2_jac
|
||||
ctx.aggSigOnce = true
|
||||
else:
|
||||
ctx.aggSig += sigG2_jac
|
||||
|
||||
type FF1 = BLSBatchSigAccumulator.FF1
|
||||
var pkG1_aff {.noInit.}: ECP_ShortW_Aff[FF1, G1]
|
||||
pkG1_aff.affine(pkG1_jac)
|
||||
|
||||
type FF2 = BLSBatchSigAccumulator.FF2
|
||||
var hmsgG2_aff {.noInit.}: ECP_ShortW_Aff[FF2, G2]
|
||||
H.hashToCurve(
|
||||
k, output = hmsgG2_aff,
|
||||
augmentation = "", message,
|
||||
ctx.domainSepTag.toOpenArray(0, ctx.dst_len.int - 1))
|
||||
|
||||
ctx.millerAccum.update(pkG1_aff, hmsgG2_aff)
|
||||
|
||||
else:
|
||||
# Pubkey on G2, H(message) and Signature on G1
|
||||
var hmsgG1_jac {.noInit.}: ECP_ShortW_Jac[Sig.F, Sig.G]
|
||||
var sigG1_jac {.noInit.}: ECP_ShortW_Jac[Sig.F, Sig.G]
|
||||
|
||||
H.hashToCurve(
|
||||
k, output = hmsgG1_jac,
|
||||
augmentation = "", message,
|
||||
ctx.domainSepTag.toOpenArray(0, ctx.dst_len.int - 1))
|
||||
|
||||
sigG1_jac.fromAffine(signature)
|
||||
|
||||
hmsgG1_jac.scalarMul_doubleAdd_vartime(ctx.secureBlinding.toOpenArray(0, 7))
|
||||
sigG1_jac.scalarMul_doubleAdd_vartime(ctx.secureBlinding.toOpenArray(0, 7))
|
||||
|
||||
if ctx.aggSigOnce == false:
|
||||
ctx.aggSig = sigG1_jac
|
||||
ctx.aggSigOnce = true
|
||||
else:
|
||||
ctx.aggSig += sigG1_jac
|
||||
|
||||
type FF1 = BLSBatchSigAccumulator.FF1
|
||||
var hmsgG1_aff {.noInit.}: ECP_ShortW_Aff[FF1, G1]
|
||||
hmsgG1_aff.affine(hmsgG1_jac)
|
||||
ctx.millerAccum.update(hmsgG1_aff, pubkey)
|
||||
|
||||
func merge*(ctxDst: var BLSBatchSigAccumulator, ctxSrc: BLSBatchSigAccumulator): bool =
|
||||
## Merge 2 BLS signature accumulators: ctxDst <- ctxDst + ctxSrc
|
||||
##
|
||||
## Returns false if they have inconsistent DomainSeparationTag and true otherwise.
|
||||
if ctxDst.dst_len != ctxSrc.dst_len:
|
||||
return false
|
||||
if not equalMem(ctxDst.domainSepTag.addr, ctxSrc.domainSepTag.addr, ctxDst.domainSepTag.len):
|
||||
return false
|
||||
|
||||
ctxDst.millerAccum.merge(ctxSrc.millerAccum)
|
||||
|
||||
if ctxDst.aggSigOnce and ctxSrc.aggSigOnce:
|
||||
ctxDst.aggSig += ctxSrc.aggSig
|
||||
elif ctxSrc.aggSigOnce:
|
||||
ctxDst.aggSig = ctxSrc.aggSig
|
||||
ctxDst.aggSigOnce = true
|
||||
|
||||
BLSBatchSigAccumulator.H.hash(ctxDst.secureBlinding, ctxDst.secureBlinding, ctxSrc.secureBlinding)
|
||||
|
||||
func finalVerify*(ctx: var BLSBatchSigAccumulator): bool =
|
||||
## Finish batch and/or aggregate signature verification and returns the final result.
|
||||
##
|
||||
## Returns false if nothing was accumulated
|
||||
## Rteturns false on verification failure
|
||||
|
||||
if not ctx.aggSigOnce:
|
||||
return false
|
||||
|
||||
type FF1 = BLSBatchSigAccumulator.FF1
|
||||
type FF2 = BLSBatchSigAccumulator.FF2
|
||||
type Fpk = BLSBatchSigAccumulator.Fpk
|
||||
|
||||
when BLSBatchSigAccumulator.SigAccum.G == G2:
|
||||
type PubKey = ECP_ShortW_Aff[FF1, G1]
|
||||
else:
|
||||
type PubKey = ECP_ShortW_Aff[FF2, G2]
|
||||
|
||||
var negG {.noInit.}: Pubkey
|
||||
negG.neg(Pubkey.F.C.getGenerator($Pubkey.G))
|
||||
|
||||
var aggSig {.noInit.}: ctx.aggSig.typeof().affine()
|
||||
aggSig.affine(ctx.aggSig)
|
||||
|
||||
when BLSBatchSigAccumulator.SigAccum.G == G2:
|
||||
if not ctx.millerAccum.update(negG, aggSig):
|
||||
return false
|
||||
else:
|
||||
if not ctx.millerAccum.update(aggSig, negG):
|
||||
return false
|
||||
|
||||
var gt {.noinit.}: Fpk
|
||||
ctx.millerAccum.finish(gt)
|
||||
gt.finalExp()
|
||||
return gt.isOne().bool
|
||||
|
||||
# ############################################################
|
||||
#
|
||||
# Aggregate and Batched Signature Verification
|
||||
# end-to-end
|
||||
#
|
||||
# ############################################################
|
||||
|
||||
func aggregate*[T: ECP_ShortW_Aff](r: var T, points: openarray[T]) =
|
||||
## Aggregate pubkeys or signatures
|
||||
var accum {.noinit.}: ECP_ShortW_Jac[T.F, T.G]
|
||||
accum.sum_batch_vartime(points)
|
||||
r.affine(accum)
|
||||
|
||||
func fastAggregateVerify*[B1, B2: byte|char, Pubkey, Sig](
|
||||
pubkeys: openArray[Pubkey],
|
||||
message: openarray[B1],
|
||||
signature: Sig,
|
||||
aggregateSignature: Sig,
|
||||
H: type CryptoHash,
|
||||
k: static int,
|
||||
augmentation: openarray[B2],
|
||||
domainSepTag: openarray[B3]): bool =
|
||||
## Verify the aggregate of multiple signatures by multiple pubkeys
|
||||
## on the same message.
|
||||
|
||||
var accum {.noinit.}: ECP_ShortW_Jac[Pubkey.F, Pubkey.G]
|
||||
accum.sum_batch_vartime(pubkeys)
|
||||
domainSepTag: openarray[B2]): bool =
|
||||
## Verify the aggregate of multiple signatures on the same message by multiple pubkeys
|
||||
## Assumes pubkeys and sig have been checked for non-infinity and group-checked.
|
||||
|
||||
if pubkeys.len == 0:
|
||||
return false
|
||||
|
||||
var aggPubkey {.noinit.}: Pubkey
|
||||
aggPubkey.affine(accum)
|
||||
aggPubkey.aggregate(pubkeys)
|
||||
aggPubkey.coreVerify(message, aggregateSignature, H, k, augmentation = "", domainSepTag)
|
||||
|
||||
aggPubkey.coreVerify(message, signature, H, k, augmentation, domainSepTag)
|
||||
func aggregateVerify*[Msg; B: byte|char, Pubkey, Sig](
|
||||
pubkeys: openArray[Pubkey],
|
||||
messages: openArray[Msg],
|
||||
aggregateSignature: Sig,
|
||||
H: type CryptoHash,
|
||||
k: static int,
|
||||
domainSepTag: openarray[B]): bool =
|
||||
## Verify the aggregated signature of multiple (pubkey, message) pairs
|
||||
## Assumes pubkeys and the aggregated signature have been checked for non-infinity and group-checked.
|
||||
##
|
||||
## To avoid splitting zeros and rogue keys attack:
|
||||
## 1. Public keys signing the same message MUST be aggregated and checked for 0 before calling BLSAggregateSigAccumulator.update()
|
||||
## 2. Augmentation or Proof of possessions must used for each public keys.
|
||||
|
||||
if pubkeys.len == 0:
|
||||
return false
|
||||
|
||||
if pubkeys.len != messages.len:
|
||||
return false
|
||||
|
||||
type FF1 = Pubkey.F
|
||||
type FF2 = Sig.F
|
||||
type FpK = Sig.F.C.getGT()
|
||||
|
||||
var accum {.noinit.}: BLSAggregateSigAccumulator[H, FF1, FF2, Fpk, k]
|
||||
accum.init(domainSepTag)
|
||||
|
||||
for i in 0 ..< pubkeys.len:
|
||||
if not accum.update(pubkeys[i], messages[i]):
|
||||
return false
|
||||
|
||||
return accum.finalVerify(aggregateSignature)
|
||||
|
||||
func batchVerify*[Msg; B: byte|char, Pubkey, Sig](
|
||||
pubkeys: openArray[Pubkey],
|
||||
messages: openArray[Msg],
|
||||
signatures: openArray[Sig],
|
||||
H: type CryptoHash,
|
||||
k: static int,
|
||||
domainSepTag: openarray[B],
|
||||
secureRandomBytes: array[32, byte]): bool =
|
||||
## Verify that all (pubkey, message, signature) triplets are valid
|
||||
##
|
||||
## Returns false if there is at least one incorrect signature
|
||||
##
|
||||
## Assumes pubkeys and signatures have been checked for non-infinity and group-checked.
|
||||
##
|
||||
## This requires cryptographically-secure generated random bytes
|
||||
## for scalar blinding
|
||||
## to defend against forged signatures that would not
|
||||
## verify individually but would verify while aggregated.
|
||||
## I.e. we need an input that is not under the attacker control.
|
||||
##
|
||||
## The blinding scheme also assumes that the attacker cannot
|
||||
## resubmit 2^64 times forged (publickey, message, signature) triplets
|
||||
## against the same `secureRandomBytes`
|
||||
|
||||
if pubkeys.len == 0:
|
||||
return false
|
||||
|
||||
if pubkeys.len != messages.len or pubkeys.len != signatures.len:
|
||||
return false
|
||||
|
||||
type FF1 = Pubkey.F
|
||||
type FF2 = Sig.F
|
||||
type FpK = Sig.F.C.getGT()
|
||||
|
||||
var accum {.noinit.}: BLSBatchSigAccumulator[H, FF1, FF2, Fpk, ECP_ShortW_Jac[Sig.F, Sig.G], k]
|
||||
accum.init(domainSepTag, secureRandomBytes, accumSepTag = "serial")
|
||||
|
||||
for i in 0 ..< pubkeys.len:
|
||||
if not accum.update(pubkeys[i], messages[i], signatures[i]):
|
||||
return false
|
||||
|
||||
return accum.finalVerify()
|
||||
|
@ -13,7 +13,7 @@ import
|
||||
../constantine/math/[arithmetic, extension_fields],
|
||||
../constantine/math/elliptic/ec_shortweierstrass_projective,
|
||||
../constantine/math/constants/zoo_subgroups,
|
||||
../constantine/math/pairing/pairing_bls12,
|
||||
../constantine/math/pairings/pairings_bls12,
|
||||
# Helpers
|
||||
../helpers/prng_unsafe
|
||||
|
||||
|
@ -9,8 +9,8 @@ import
|
||||
ec_shortweierstrass_projective,
|
||||
],
|
||||
../../constantine/math/io/[io_fields, io_ec],
|
||||
../../constantine/math/pairing/[
|
||||
pairing_bls12,
|
||||
../../constantine/math/pairings/[
|
||||
pairings_bls12,
|
||||
miller_loops
|
||||
],
|
||||
# Research
|
||||
|
@ -8,10 +8,10 @@ import
|
||||
ec_shortweierstrass_projective,
|
||||
],
|
||||
../../constantine/math/io/[io_fields, io_ec],
|
||||
../../constantine/math/pairing/[
|
||||
../../constantine/math/pairings/[
|
||||
pairing_bls12,
|
||||
miller_loops,
|
||||
cyclotomic_subgroup
|
||||
cyclotomic_subgroups
|
||||
]
|
||||
|
||||
type
|
||||
|
@ -7,7 +7,7 @@
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
../../constantine/math/pairing/pairing_bls12,
|
||||
../../constantine/math/pairings/pairings_bls12,
|
||||
# Test utilities
|
||||
./t_pairing_template
|
||||
|
||||
|
@ -19,7 +19,7 @@ import
|
||||
ec_shortweierstrass_affine,
|
||||
ec_shortweierstrass_projective,
|
||||
ec_scalar_mul],
|
||||
../../constantine/math/pairing/lines_eval,
|
||||
../../constantine/math/pairings/lines_eval,
|
||||
# Test utilities
|
||||
../../helpers/[prng_unsafe, static_for]
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
../../constantine/math/pairing/pairing_bls12,
|
||||
../../constantine/math/pairings/pairings_bls12,
|
||||
# Test utilities
|
||||
./t_pairing_template
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
../../constantine/math/pairing/pairing_bls12,
|
||||
../../constantine/math/pairings/pairings_bls12,
|
||||
# Test utilities
|
||||
./t_pairing_template
|
||||
|
||||
|
@ -18,7 +18,7 @@ import
|
||||
../../constantine/math/elliptic/[
|
||||
ec_shortweierstrass_affine,
|
||||
ec_shortweierstrass_projective],
|
||||
../../constantine/math/pairing/lines_eval,
|
||||
../../constantine/math/pairings/lines_eval,
|
||||
# Test utilities
|
||||
../../helpers/[prng_unsafe, static_for]
|
||||
|
||||
|
@ -14,7 +14,7 @@ import
|
||||
../../constantine/math/[arithmetic, extension_fields, ec_shortweierstrass],
|
||||
../../constantine/math/io/io_extfields,
|
||||
../../constantine/math/config/curves,
|
||||
../../constantine/math/pairing/pairing_bls12,
|
||||
../../constantine/math/pairings/pairings_bls12,
|
||||
# Test utilities
|
||||
../../helpers/prng_unsafe
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
../../constantine/math/pairing/pairing_bls12,
|
||||
../../constantine/math/pairings/pairings_bls12,
|
||||
# Test utilities
|
||||
./t_pairing_template
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
../../constantine/math/pairing/pairing_bn,
|
||||
../../constantine/math/pairings/pairings_bn,
|
||||
# Test utilities
|
||||
./t_pairing_template
|
||||
|
||||
|
@ -14,7 +14,7 @@ import
|
||||
../../constantine/math/[arithmetic, extension_fields, ec_shortweierstrass],
|
||||
../../constantine/math/io/io_extfields,
|
||||
../../constantine/math/config/curves,
|
||||
../../constantine/math/pairing/pairing_bn,
|
||||
../../constantine/math/pairings/pairings_bn,
|
||||
# Test utilities
|
||||
../../helpers/prng_unsafe
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
../../constantine/math/pairing/pairing_bn,
|
||||
../../constantine/math/pairings/pairings_bn,
|
||||
# Test utilities
|
||||
./t_pairing_template
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
../../constantine/math/pairing/pairing_bn,
|
||||
../../constantine/math/pairings/pairings_bn,
|
||||
# Test utilities
|
||||
./t_pairing_template
|
||||
|
||||
|
@ -14,7 +14,7 @@ import
|
||||
../../constantine/math/[arithmetic, extension_fields, ec_shortweierstrass],
|
||||
../../constantine/math/io/io_extfields,
|
||||
../../constantine/math/config/curves,
|
||||
../../constantine/math/pairing/pairing_bn,
|
||||
../../constantine/math/pairings/pairings_bn,
|
||||
# Test utilities
|
||||
../../helpers/prng_unsafe
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
../../constantine/math/pairing/pairing_bn,
|
||||
../../constantine/math/pairings/pairings_bn,
|
||||
# Test utilities
|
||||
./t_pairing_template
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
../../constantine/math/pairing/pairing_bw6_761,
|
||||
../../constantine/math/pairings/pairings_bw6_761,
|
||||
# Test utilities
|
||||
./t_pairing_template
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
../../constantine/math/pairing/pairing_bw6_761,
|
||||
../../constantine/math/pairings/pairings_bw6_761,
|
||||
# Test utilities
|
||||
./t_pairing_template
|
||||
|
||||
|
@ -15,7 +15,7 @@ import
|
||||
../../constantine/math/extension_fields,
|
||||
../../constantine/math/config/curves,
|
||||
../../constantine/math/io/[io_bigints, io_extfields],
|
||||
../../constantine/math/pairing/cyclotomic_subgroup,
|
||||
../../constantine/math/pairings/cyclotomic_subgroups,
|
||||
../../constantine/math/isogenies/frobenius,
|
||||
# Test utilities
|
||||
../../helpers/[prng_unsafe, static_for]
|
||||
|
@ -15,7 +15,7 @@ import
|
||||
../../constantine/math/extension_fields,
|
||||
../../constantine/math/config/curves,
|
||||
../../constantine/math/io/io_extfields,
|
||||
../../constantine/math/pairing/lines_eval,
|
||||
../../constantine/math/pairings/lines_eval,
|
||||
# Test utilities
|
||||
../../helpers/[prng_unsafe, static_for]
|
||||
|
||||
|
@ -16,7 +16,7 @@ import
|
||||
../../constantine/math/config/curves,
|
||||
../../constantine/math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_projective],
|
||||
../../constantine/math/constants/[zoo_subgroups, zoo_pairings],
|
||||
../../constantine/math/pairing/cyclotomic_subgroup,
|
||||
../../constantine/math/pairings/cyclotomic_subgroups,
|
||||
../../constantine/math/io/io_extfields,
|
||||
|
||||
# Test utilities
|
||||
@ -27,7 +27,7 @@ export
|
||||
ec_shortweierstrass_affine, ec_shortweierstrass_projective,
|
||||
arithmetic, extension_fields,
|
||||
io_extfields,
|
||||
cyclotomic_subgroup,
|
||||
cyclotomic_subgroups,
|
||||
abstractions, curves
|
||||
|
||||
type
|
||||
|
@ -10,7 +10,8 @@ import
|
||||
std/[os, unittest, strutils],
|
||||
pkg/jsony,
|
||||
../constantine/blssig_pop_on_bls12381_g2,
|
||||
../constantine/math/io/io_bigints
|
||||
../constantine/math/io/io_bigints,
|
||||
../constantine/hashes
|
||||
|
||||
type
|
||||
# https://github.com/ethereum/bls12-381-tests/blob/master/formats/
|
||||
@ -50,6 +51,22 @@ type
|
||||
input: InputFastAggregateVerify
|
||||
output: bool
|
||||
|
||||
InputAggregateVerify = object
|
||||
pubkeys: seq[array[48, byte]]
|
||||
messages: seq[array[32, byte]]
|
||||
signature: array[96, byte]
|
||||
AggregateVerify_test = object
|
||||
input: InputAggregateVerify
|
||||
output: bool
|
||||
|
||||
InputBatchVerify = object
|
||||
pubkeys: seq[array[48, byte]]
|
||||
messages: seq[array[32, byte]]
|
||||
signatures: seq[array[96, byte]]
|
||||
BatchVerify_test = object
|
||||
input: InputBatchVerify
|
||||
output: bool
|
||||
|
||||
proc parseHook*[N: static int](src: string, pos: var int, value: var array[N, byte]) =
|
||||
var str: string
|
||||
parseHook(src, pos, str)
|
||||
@ -159,7 +176,7 @@ testGen(sign, testVector, Sign_test):
|
||||
var roundtrip{.noInit.}: array[96, byte]
|
||||
let sb_status = sig_bytes.serialize_signature_compressed(sig)
|
||||
let rt_status = roundtrip.serialize_signature_compressed(output)
|
||||
|
||||
|
||||
"\nResult signature differs from expected \n" &
|
||||
" computed: 0x" & $sig_bytes.toHex() & " (" & $sb_status & ")\n" &
|
||||
" roundtrip: 0x" & $roundtrip.toHex() & " (" & $rt_status & ")\n" &
|
||||
@ -191,10 +208,10 @@ testGen(verify, testVector, Verify_test):
|
||||
break testChecks
|
||||
|
||||
status = pubkey.verify(testVector.input.message, signature)
|
||||
|
||||
|
||||
let success = status == cttBLS_Success
|
||||
doAssert success == testVector.output, block:
|
||||
"\Verification differs from expected \n" &
|
||||
"Verification differs from expected \n" &
|
||||
" valid sig? " & $success & " (" & $status & ")\n" &
|
||||
" expected: " & $testVector.output
|
||||
|
||||
@ -223,17 +240,72 @@ testGen(fast_aggregate_verify, testVector, FastAggregateVerify_test):
|
||||
if status notin {cttBLS_Success, cttBLS_PointAtInfinity}:
|
||||
# For point at infinity, we want to make sure that "verify" itself handles them.
|
||||
break testChecks
|
||||
|
||||
|
||||
status = signature.deserialize_signature_compressed(testVector.input.signature)
|
||||
if status notin {cttBLS_Success, cttBLS_PointAtInfinity}:
|
||||
# For point at infinity, we want to make sure that "verify" itself handles them.
|
||||
break testChecks
|
||||
|
||||
status = pubkeys.fast_aggregate_verify(testVector.input.message, signature)
|
||||
|
||||
|
||||
let success = status == cttBLS_Success
|
||||
doAssert success == testVector.output, block:
|
||||
"\Verification differs from expected \n" &
|
||||
"Verification differs from expected \n" &
|
||||
" valid sig? " & $success & " (" & $status & ")\n" &
|
||||
" expected: " & $testVector.output
|
||||
|
||||
testGen(aggregate_verify, testVector, AggregateVerify_test):
|
||||
var
|
||||
pubkeys = newSeq[PublicKey](testVector.input.pubkeys.len)
|
||||
signature{.noInit.}: Signature
|
||||
status = cttBLS_VerificationFailure
|
||||
|
||||
block testChecks:
|
||||
for i in 0 ..< testVector.input.pubkeys.len:
|
||||
status = pubkeys[i].deserialize_public_key_compressed(testVector.input.pubkeys[i])
|
||||
if status notin {cttBLS_Success, cttBLS_PointAtInfinity}:
|
||||
# For point at infinity, we want to make sure that "verify" itself handles them.
|
||||
break testChecks
|
||||
|
||||
status = signature.deserialize_signature_compressed(testVector.input.signature)
|
||||
if status notin {cttBLS_Success, cttBLS_PointAtInfinity}:
|
||||
# For point at infinity, we want to make sure that "verify" itself handles them.
|
||||
break testChecks
|
||||
|
||||
status = pubkeys.aggregate_verify(testVector.input.messages, signature)
|
||||
|
||||
let success = status == cttBLS_Success
|
||||
doAssert success == testVector.output, block:
|
||||
"Verification differs from expected \n" &
|
||||
" valid sig? " & $success & " (" & $status & ")\n" &
|
||||
" expected: " & $testVector.output
|
||||
|
||||
testGen(batch_verify, testVector, BatchVerify_test):
|
||||
var
|
||||
pubkeys = newSeq[PublicKey](testVector.input.pubkeys.len)
|
||||
signatures = newSeq[Signature](testVector.input.signatures.len)
|
||||
status = cttBLS_VerificationFailure
|
||||
|
||||
block testChecks:
|
||||
for i in 0 ..< testVector.input.pubkeys.len:
|
||||
status = pubkeys[i].deserialize_public_key_compressed(testVector.input.pubkeys[i])
|
||||
if status notin {cttBLS_Success, cttBLS_PointAtInfinity}:
|
||||
# For point at infinity, we want to make sure that "verify" itself handles them.
|
||||
break testChecks
|
||||
|
||||
for i in 0 ..< testVector.input.signatures.len:
|
||||
status = signatures[i].deserialize_signature_compressed(testVector.input.signatures[i])
|
||||
if status notin {cttBLS_Success, cttBLS_PointAtInfinity}:
|
||||
# For point at infinity, we want to make sure that "verify" itself handles them.
|
||||
break testChecks
|
||||
|
||||
let randomBytes = sha256.hash("totally non-secure source of entropy")
|
||||
|
||||
status = pubkeys.batch_verify(testVector.input.messages, signatures, randomBytes)
|
||||
|
||||
let success = status == cttBLS_Success
|
||||
doAssert success == testVector.output, block:
|
||||
"Verification differs from expected \n" &
|
||||
" valid sig? " & $success & " (" & $status & ")\n" &
|
||||
" expected: " & $testVector.output
|
||||
|
||||
@ -247,4 +319,8 @@ suite "BLS signature on BLS12381G3 - ETH 2.0 test vectors":
|
||||
test "verify(PublicKey, message, Signature) -> bool":
|
||||
test_verify()
|
||||
test "fast_aggregate_verify(seq[PublicKey], message, Signature) -> bool":
|
||||
test_fast_aggregate_verify()
|
||||
test_fast_aggregate_verify()
|
||||
test "aggregate_verify(seq[PublicKey], seq[message], Signature) -> bool":
|
||||
test_aggregate_verify()
|
||||
test "batch_verify(seq[PublicKey], seq[message], seq[Signature]) -> bool":
|
||||
test_batch_verify()
|
Loading…
x
Reference in New Issue
Block a user