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:
Mamy Ratsimbazafy 2023-01-27 00:42:12 +01:00 committed by GitHub
parent 7c01affe24
commit ff8c26c1fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 1274 additions and 352 deletions

View File

@ -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()

View File

@ -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

View File

@ -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,

View File

@ -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 elements 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

View File

@ -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
# ------------------------------------------------------------

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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`)]

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -11,7 +11,7 @@ import
../config/curves,
../io/io_bigints,
../extension_fields,
../pairing/cyclotomic_subgroup,
../pairings/cyclotomic_subgroups,
../isogenies/frobenius
# Slow generic implementation

View File

@ -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".}

View 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

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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.}

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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]

View File

@ -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

View File

@ -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()