From 188f3e710c30f85bee620e58eb23fe2184a838e0 Mon Sep 17 00:00:00 2001 From: Mamy Ratsimbazafy Date: Mon, 23 Jan 2023 01:54:40 +0100 Subject: [PATCH] add fast_aggregate_verify --- constantine/blssig_pop_on_bls12381_g2.nim | 37 ++++++++++ constantine/signatures/bls_signatures.nim | 46 ++++++++++-- tests/t_blssig_pop_on_bls12381_g2.nim | 85 ++++++++++++++++------- 3 files changed, 140 insertions(+), 28 deletions(-) diff --git a/constantine/blssig_pop_on_bls12381_g2.nim b/constantine/blssig_pop_on_bls12381_g2.nim index 0b7ac04..680c35b 100644 --- a/constantine/blssig_pop_on_bls12381_g2.nim +++ b/constantine/blssig_pop_on_bls12381_g2.nim @@ -85,6 +85,7 @@ type cttBLS_PointNotInSubgroup cttBLS_ZeroSecretKey cttBLS_SecretKeyLargerThanCurveOrder + cttBLS_ZeroLengthAggregation # Comparisons # ------------------------------------------------------------------------------------------------ @@ -401,6 +402,42 @@ func verify*[T: byte|char](public_key: PublicKey, message: openarray[T], signatu return cttBLS_PointAtInfinity let verified = coreVerify(public_key.raw, message, signature.raw, sha256, 128, augmentation = "", DST) + if verified: + return cttBLS_Success + return cttBLS_VerificationFailure + +func fast_aggregate_verify*[T: byte|char](public_keys: openArray[PublicKey], message: openarray[T], signature: 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 + ## + ## In particular, the public keys and signature are assumed to be on curve subgroup checked. + + if public_keys.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: + return cttBLS_PointAtInfinity + + for i in 0 ..< public_keys.len: + if public_keys[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) if verified: return cttBLS_Success return cttBLS_VerificationFailure \ No newline at end of file diff --git a/constantine/signatures/bls_signatures.nim b/constantine/signatures/bls_signatures.nim index bd374dc..69960cf 100644 --- a/constantine/signatures/bls_signatures.nim +++ b/constantine/signatures/bls_signatures.nim @@ -8,6 +8,7 @@ import ../math/[ec_shortweierstrass, pairings], + ../math/elliptic/ec_shortweierstrass_batch_ops, ../math/constants/zoo_generators, ../hash_to_curve/hash_to_curve, ../hashes @@ -94,7 +95,7 @@ func coreSign*[B1, B2, B3: byte|char, Sig, SecKey]( signature.affine(sig) func coreVerify*[B1, B2, B3: byte|char, Pubkey, Sig]( - pubKey: Pubkey, + pubkey: Pubkey, message: openarray[B1], signature: Sig, H: type CryptoHash, @@ -119,8 +120,45 @@ func coreVerify*[B1, B2, B3: byte|char, Pubkey, Sig]( # e(PK, H(msg))*e(sig, -G) == 1 when Sig.G == G2: - pairing(gt, [pubKey, negG], [Q, signature]) + pairing(gt, [pubkey, negG], [Q, signature]) else: - pairing(gt, [Q, signature], [pubKey, negG]) + pairing(gt, [Q, signature], [pubkey, negG]) - return gt.isOne().bool() \ No newline at end of file + return gt.isOne().bool() + +# ############################################################ +# +# Aggregate verification +# +# ############################################################ +# +# Terminology: +# +# - fastAggregateVerify: +# Verify the aggregate of multiple signatures by multiple pubkeys +# on the same message. +# +# - aggregateVerify: +# Verify the aggregate of multiple signatures by multiple (pubkey, message) pairs +# +# - batchVerify: +# Verify that all (pubkey, message, signature) triplets are valid + +func fastAggregateVerify*[B1, B2, B3: byte|char, Pubkey, Sig]( + pubkeys: openArray[Pubkey], + message: openarray[B1], + signature: 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) + + var aggPubkey {.noinit.}: Pubkey + aggPubkey.affine(accum) + + aggPubkey.coreVerify(message, signature, H, k, augmentation, domainSepTag) \ No newline at end of file diff --git a/tests/t_blssig_pop_on_bls12381_g2.nim b/tests/t_blssig_pop_on_bls12381_g2.nim index c7cdf37..974ae4a 100644 --- a/tests/t_blssig_pop_on_bls12381_g2.nim +++ b/tests/t_blssig_pop_on_bls12381_g2.nim @@ -13,13 +13,16 @@ import ../constantine/math/io/io_bigints type + # https://github.com/ethereum/bls12-381-tests/blob/master/formats/ + PubkeyField = object pubkey: array[48, byte] - SignatureField =object - signature: array[96, byte] DeserG1_test = object input: PubkeyField output: bool + + SignatureField =object + signature: array[96, byte] DeserG2_test = object input: SignatureField output: bool @@ -27,7 +30,6 @@ type InputSign = object privkey: array[32, byte] message: array[32, byte] - Sign_test = object input: InputSign output: array[96, byte] @@ -36,11 +38,18 @@ type pubkey: array[48, byte] message: array[32, byte] signature: array[96, byte] - Verify_test = object input: InputVerify output: bool + InputFastAggregateVerify = object + pubkeys: seq[array[48, byte]] + message: array[32, byte] + signature: array[96, byte] + FastAggregateVerify_test = object + input: InputFastAggregateVerify + output: bool + proc parseHook*[N: static int](src: string, pos: var int, value: var array[N, byte]) = var str: string parseHook(src, pos, str) @@ -73,7 +82,7 @@ template testGen*(name, testData, TestType, body: untyped): untyped = var count = 0 # Need to fail if walkDir doesn't return anything var skipped = 0 for dir, file in walkTests(astToStr(name), skipped): - stdout.write(" " & astToStr(name) & " test: " & alignLeft(file, 60)) + stdout.write(" " & astToStr(name) & " test: " & alignLeft(file, 70)) let testFile = readFile(dir/file) let testData = testFile.fromJson(TestType) @@ -169,7 +178,7 @@ testGen(verify, testVector, Verify_test): var pubkey{.noInit.}: PublicKey signature{.noInit.}: Signature - status = cttBLS_Success + status = cttBLS_VerificationFailure block testChecks: status = pubkey.deserialize_public_key_compressed(testVector.input.pubkey) @@ -181,26 +190,52 @@ testGen(verify, testVector, Verify_test): # For point at infinity, we want to make sure that "verify" itself handles them. break testChecks - status = pubkey.verify(testVector.input.message, signature) - let success = status == cttBLS_Success - doAssert success == testVector.output, block: - "\Verification differs from expected \n" & - " valid sig? " & $success & " (" & $status & ")\n" & - " expected: " & $testVector.output + + let success = status == cttBLS_Success + doAssert success == testVector.output, block: + "\Verification differs from expected \n" & + " valid sig? " & $success & " (" & $status & ")\n" & + " expected: " & $testVector.output - if success: # Extra codec testing - block: - var output{.noInit.}: array[48, byte] - let s = output.serialize_public_key_compressed(pubkey) - doAssert s == cttBLS_Success - doAssert output == testVector.input.pubkey + if success: # Extra codec testing + block: + var output{.noInit.}: array[48, byte] + let s = output.serialize_public_key_compressed(pubkey) + doAssert s == cttBLS_Success + doAssert output == testVector.input.pubkey - block: - var output{.noInit.}: array[96, byte] - let s = output.serialize_signature_compressed(signature) - doAssert s == cttBLS_Success - doAssert output == testVector.input.signature + block: + var output{.noInit.}: array[96, byte] + let s = output.serialize_signature_compressed(signature) + doAssert s == cttBLS_Success + doAssert output == testVector.input.signature + +testGen(fast_aggregate_verify, testVector, FastAggregateVerify_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.fast_aggregate_verify(testVector.input.message, signature) + + let success = status == cttBLS_Success + doAssert success == testVector.output, block: + "\Verification differs from expected \n" & + " valid sig? " & $success & " (" & $status & ")\n" & + " expected: " & $testVector.output suite "BLS signature on BLS12381G3 - ETH 2.0 test vectors": test "Deserialization_G1(PublicKey) -> bool": @@ -210,4 +245,6 @@ suite "BLS signature on BLS12381G3 - ETH 2.0 test vectors": test "sign(SecretKey, message) -> Signature": test_sign() test "verify(PublicKey, message, Signature) -> bool": - test_verify() \ No newline at end of file + test_verify() + test "fast_aggregate_verify(seq[PublicKey], message, Signature) -> bool": + test_fast_aggregate_verify() \ No newline at end of file