diff --git a/benchmarks/bench_blssig_on_bls12_381_g2.nim b/benchmarks/bench_blssig_on_bls12_381_g2.nim index f34eea1..1a990d5 100644 --- a/benchmarks/bench_blssig_on_bls12_381_g2.nim +++ b/benchmarks/bench_blssig_on_bls12_381_g2.nim @@ -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() diff --git a/benchmarks/bench_pairing_template.nim b/benchmarks/bench_pairing_template.nim index c1e55e5..4147c5f 100644 --- a/benchmarks/bench_pairing_template.nim +++ b/benchmarks/bench_pairing_template.nim @@ -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 diff --git a/benchmarks/bench_summary_template.nim b/benchmarks/bench_summary_template.nim index 32b6ec3..37b4f5f 100644 --- a/benchmarks/bench_summary_template.nim +++ b/benchmarks/bench_summary_template.nim @@ -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, diff --git a/constantine/blssig_pop_on_bls12381_g2.nim b/constantine/blssig_pop_on_bls12381_g2.nim index 680c35b..e197267 100644 --- a/constantine/blssig_pop_on_bls12381_g2.nim +++ b/constantine/blssig_pop_on_bls12381_g2.nim @@ -57,7 +57,7 @@ export ## already serve as proof-of-possession. const DST = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_" -const ffi_prefix = "ctt_blssig_pop_on_bls12381_g2_" +const ffi_prefix {.used.} = "ctt_blssig_pop_on_bls12381_g2_" {.push raises: [].} # No exceptions allowed in core cryptographic operations # {.push cdecl, dynlib, exportc:ffi_prefix & "$1".} # TODO, C API @@ -86,6 +86,7 @@ type cttBLS_ZeroSecretKey cttBLS_SecretKeyLargerThanCurveOrder cttBLS_ZeroLengthAggregation + cttBLS_InconsistentLengthsOfInputs # Comparisons # ------------------------------------------------------------------------------------------------ @@ -135,7 +136,7 @@ func validate_sig*(signature: Signature): CttBLSStatus = # ------------------------------------------------------------------------------------------------ ## BLS12-381 serialization -## +## ## 𝔽p elements are encoded in big-endian form. They occupy 48 bytes in this form. ## 𝔽p2​ elements are encoded in big-endian form, meaning that the 𝔽p2​ element c0+c1u ## is represented by the 𝔽p​ element c1​ followed by the 𝔽p element c0​. @@ -144,9 +145,9 @@ func validate_sig*(signature: Signature): CttBLSStatus = ## 𝔾1​ and 𝔾2​ elements can be encoded in uncompressed form (the x-coordinate followed by the y-coordinate) or in compressed form (just the x-coordinate). ## 𝔾1​ elements occupy 96 bytes in uncompressed form, and 48 bytes in compressed form. ## 𝔾2​ elements occupy 192 bytes in uncompressed form, and 96 bytes in compressed form. -## +## ## The most-significant three bits of a 𝔾1​ or 𝔾2​ encoding should be masked away before the coordinate(s) are interpreted. These bits are used to unambiguously represent the underlying element: -## +## ## The most significant bit, when set, indicates that the point is in compressed form. Otherwise, the point is in uncompressed form. ## The second-most significant bit indicates that the point is at infinity. If this bit is set, the remaining bits of the group element’s encoding should be set to zero. ## The third-most significant bit is set if (and only if) this point is in compressed form @@ -154,7 +155,7 @@ func validate_sig*(signature: Signature): CttBLSStatus = ## ## - https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-04#appendix-A ## - https://docs.rs/bls12_381/latest/bls12_381/notes/serialization/index.html -## - https://github.com/zkcrypto/bls12_381/blob/0.6.0/src/notes/serialization.rs +## - https://github.com/zkcrypto/bls12_381/blob/0.6.0/src/notes/serialization.rs func serialize_secret_key*(dst: var array[32, byte], secret_key: SecretKey): CttBLSStatus = ## Serialize a secret key @@ -164,7 +165,7 @@ func serialize_secret_key*(dst: var array[32, byte], secret_key: SecretKey): Ctt func serialize_public_key_compressed*(dst: var array[48, byte], public_key: PublicKey): CttBLSStatus = ## Serialize a public key in compressed (Zcash) format - ## + ## ## Returns cttBLS_Success if successful if public_key.raw.isInf().bool(): for i in 0 ..< dst.len: @@ -185,7 +186,7 @@ func serialize_public_key_compressed*(dst: var array[48, byte], public_key: Publ func serialize_signature_compressed*(dst: var array[96, byte], signature: Signature): CttBLSStatus = ## Serialize a signature in compressed (Zcash) format - ## + ## ## Returns cttBLS_Success if successful if signature.raw.isInf().bool(): for i in 0 ..< dst.len: @@ -207,7 +208,7 @@ func serialize_signature_compressed*(dst: var array[96, byte], signature: Signat func deserialize_secret_key*(dst: var SecretKey, src: array[32, byte]): CttBLSStatus = ## deserialize a secret key - ## + ## ## This is protected against side-channel unless your key is invalid. ## In that case it will like whether it's all zeros or larger than the curve order. dst.raw.unmarshal(src, bigEndian) @@ -219,14 +220,14 @@ func deserialize_secret_key*(dst: var SecretKey, src: array[32, byte]): CttBLSSt func deserialize_public_key_compressed_unchecked*(dst: var PublicKey, src: array[48, byte]): CttBLSStatus = ## Deserialize a public_key in compressed (Zcash) format. - ## + ## ## Warning ⚠: ## This procedure skips the very expensive subgroup checks. ## Not checking subgroup exposes a protocol to small subgroup attacks. - ## + ## ## Returns cttBLS_Success if successful - # src must have the compressed flag + # src must have the compressed flag if (src[0] and byte 0b10000000) == byte 0: return cttBLS_InvalidEncoding @@ -261,7 +262,7 @@ func deserialize_public_key_compressed_unchecked*(dst: var PublicKey, src: array func deserialize_public_key_compressed*(dst: var PublicKey, src: array[48, byte]): CttBLSStatus = ## Deserialize a public_key in compressed (Zcash) format - ## + ## ## Returns cttBLS_Success if successful result = deserialize_public_key_compressed_unchecked(dst, src) @@ -273,14 +274,14 @@ func deserialize_public_key_compressed*(dst: var PublicKey, src: array[48, byte] func deserialize_signature_compressed_unchecked*(dst: var Signature, src: array[96, byte]): CttBLSStatus = ## Deserialize a signature in compressed (Zcash) format. - ## + ## ## Warning ⚠: ## This procedure skips the very expensive subgroup checks. ## Not checking subgroup exposes a protocol to small subgroup attacks. - ## + ## ## Returns cttBLS_Success if successful - # src must have the compressed flag + # src must have the compressed flag if (src[0] and byte 0b10000000) == byte 0: return cttBLS_InvalidEncoding @@ -320,13 +321,13 @@ func deserialize_signature_compressed_unchecked*(dst: var Signature, src: array[ dst.raw.y.c0.toBig() >= Fp[BLS12_381].getPrimePlus1div2() else: dst.raw.y.c1.toBig() >= Fp[BLS12_381].getPrimePlus1div2() - + let srcIsLargest = SecretBool((src[0] shr 5) and byte 1) dst.raw.y.cneg(isLexicographicallyLargest xor srcIsLargest) func deserialize_signature_compressed*(dst: var Signature, src: array[96, byte]): CttBLSStatus = ## Deserialize a public_key in compressed (Zcash) format - ## + ## ## Returns cttBLS_Success if successful result = deserialize_signature_compressed_unchecked(dst, src) @@ -341,7 +342,7 @@ func deserialize_signature_compressed*(dst: var Signature, src: array[96, byte]) func derive_public_key*(public_key: var PublicKey, secret_key: SecretKey): CttBLSStatus = ## Derive the public key matching with a secret key - ## + ## ## Secret protection: ## - A valid secret key will only leak that it is valid. ## - An invalid secret key will leak whether it's all zero or larger than the curve order. @@ -358,18 +359,18 @@ func derive_public_key*(public_key: var PublicKey, secret_key: SecretKey): CttBL func sign*[T: byte|char](signature: var Signature, secret_key: SecretKey, message: openArray[T]): CttBLSStatus = ## Produce a signature for the message under the specified secret key ## Signature is on BLS12-381 G2 (and public key on G1) - ## + ## ## For message domain separation purpose, the tag is `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_` - ## + ## ## Input: ## - A secret key ## - A message - ## + ## ## Output: ## - `signature` is overwritten with `message` signed with `secretKey` ## with the scheme ## - A status code indicating success or if the secret key is invalid. - ## + ## ## Secret protection: ## - A valid secret key will only leak that it is valid. ## - An invalid secret key will leak whether it's all zero or larger than the curve order. @@ -377,7 +378,7 @@ func sign*[T: byte|char](signature: var Signature, secret_key: SecretKey, messag if status != cttBLS_Success: signature.raw.setInf() return status - + coreSign(signature.raw, secretKey.raw, message, sha256, 128, augmentation = "", DST) return cttBLS_Success @@ -385,18 +386,18 @@ func verify*[T: byte|char](public_key: PublicKey, message: openarray[T], signatu ## Check that a signature is valid for a message ## under the provided public key. ## returns `true` if the signature is valid, `false` otherwise. - ## + ## ## For message domain separation purpose, the tag is `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_` - ## + ## ## Input: ## - A public key initialized by one of the key derivation or deserialization procedure. ## Or validated via validate_pubkey ## - A message ## - A signature initialized by one of the key derivation or deserialization procedure. ## Or validated via validate_pubkey - ## + ## ## In particular, the public key and signature are assumed to be on curve subgroup checked. - + # Deal with cases were pubkey or signature were mistakenly zero-init, due to a generic aggregation tentative for example if bool(public_key.raw.isInf() or signature.raw.isInf()): return cttBLS_PointAtInfinity @@ -406,38 +407,151 @@ func verify*[T: byte|char](public_key: PublicKey, message: openarray[T], signatu return cttBLS_Success return cttBLS_VerificationFailure -func fast_aggregate_verify*[T: byte|char](public_keys: openArray[PublicKey], message: openarray[T], signature: Signature): CttBLSStatus = +template unwrap[T: PublicKey|Signature](elems: openArray[T]): auto = + # Unwrap collection of high-level type into collection of low-level type + toOpenArray(cast[ptr UncheckedArray[typeof elems[0].raw]](elems[0].raw.unsafeAddr), elems.low, elems.high) + +func aggregate_pubkeys*(aggregate_pubkey: var PublicKey, pubkeys: openArray[PublicKey]) = + ## Aggregate public keys into one + ## The individual public keys are assumed to be validated, either during deserialization + ## or by validate_pubkeys + if pubkeys.len == 0: + aggregate_pubkey.raw.setInf() + return + aggregate_pubkey.raw.aggregate(pubkeys.unwrap()) + +func aggregate_signatures*(aggregate_sig: var Signature, signatures: openArray[Signature]) = + ## Aggregate signatures into one + ## The individual signatures are assumed to be validated, either during deserialization + ## or by validate_signature + if signatures.len == 0: + aggregate_sig.raw.setInf() + return + aggregate_sig.raw.aggregate(signatures.unwrap()) + +func fast_aggregate_verify*[T: byte|char](pubkeys: openArray[PublicKey], message: openarray[T], aggregate_sig: Signature): CttBLSStatus = ## Check that a signature is valid for a message ## under the aggregate of provided public keys. ## returns `true` if the signature is valid, `false` otherwise. - ## + ## ## For message domain separation purpose, the tag is `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_` - ## + ## ## Input: ## - Public keys initialized by one of the key derivation or deserialization procedure. ## Or validated via validate_pubkey ## - A message ## - A signature initialized by one of the key derivation or deserialization procedure. - ## Or validated via validate_pubkey - ## + ## Or validated via validate_sig + ## ## In particular, the public keys and signature are assumed to be on curve subgroup checked. - if public_keys.len == 0: + if pubkeys.len == 0: # IETF spec precondition return cttBLS_ZeroLengthAggregation # Deal with cases were pubkey or signature were mistakenly zero-init, due to a generic aggregation tentative for example - if signature.raw.isInf().bool: + if aggregate_sig.raw.isInf().bool: return cttBLS_PointAtInfinity - for i in 0 ..< public_keys.len: - if public_keys[i].raw.isInf().bool: + for i in 0 ..< pubkeys.len: + if pubkeys[i].raw.isInf().bool: return cttBLS_PointAtInfinity - + let verified = fastAggregateVerify( - toOpenArray(cast[ptr UncheckedArray[typeof public_keys[0].raw]](public_keys[0].raw.unsafeAddr), public_keys.low, public_keys.high), - message, signature.raw, - sha256, 128, augmentation = "", DST) + pubkeys.unwrap(), + message, aggregate_sig.raw, + sha256, 128, DST) + if verified: + return cttBLS_Success + return cttBLS_VerificationFailure + +func aggregate_verify*[M](pubkeys: openArray[PublicKey], messages: openarray[M], aggregate_sig: Signature): CttBLSStatus = + ## Verify the aggregated signature of multiple (pubkey, message) pairs + ## returns `true` if the signature is valid, `false` otherwise. + ## + ## For message domain separation purpose, the tag is `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_` + ## + ## Input: + ## - Public keys initialized by one of the key derivation or deserialization procedure. + ## Or validated via validate_pubkey + ## - Messages + ## - a signature initialized by one of the key derivation or deserialization procedure. + ## Or validated via validate_sig + ## + ## In particular, the public keys and signature are assumed to be on curve subgroup checked. + ## + ## To avoid splitting zeros and rogue keys attack: + ## 1. Public keys signing the same message MUST be aggregated and checked for 0 before calling BLSAggregateSigAccumulator.update() + ## 2. Augmentation or Proof of possessions must used for each public keys. + + if pubkeys.len == 0: + # IETF spec precondition + return cttBLS_ZeroLengthAggregation + + if pubkeys.len != messages.len: + return cttBLS_InconsistentLengthsOfInputs + + # Deal with cases were pubkey or signature were mistakenly zero-init, due to a generic aggregation tentative for example + if aggregate_sig.raw.isInf().bool: + return cttBLS_PointAtInfinity + + for i in 0 ..< pubkeys.len: + if pubkeys[i].raw.isInf().bool: + return cttBLS_PointAtInfinity + + let verified = aggregateVerify( + pubkeys.unwrap(), + messages, aggregate_sig.raw, + sha256, 128, DST) + if verified: + return cttBLS_Success + return cttBLS_VerificationFailure + +func batch_verify*[M](pubkeys: openArray[PublicKey], messages: openarray[M], signatures: openArray[Signature], secureRandomBytes: array[32, byte]): CttBLSStatus = + ## Verify that all (pubkey, message, signature) triplets are valid + ## returns `true` if all signatures are valid, `false` if at least one is invalid. + ## + ## For message domain separation purpose, the tag is `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_` + ## + ## Input: + ## - Public keys initialized by one of the key derivation or deserialization procedure. + ## Or validated via validate_pubkey + ## - Messages + ## - Signatures initialized by one of the key derivation or deserialization procedure. + ## Or validated via validate_sig + ## + ## In particular, the public keys and signature are assumed to be on curve subgroup checked. + ## + ## To avoid splitting zeros and rogue keys attack: + ## 1. Cryptographically-secure random bytes must be provided. + ## 2. Augmentation or Proof of possessions must used for each public keys. + ## + ## The secureRandomBytes will serve as input not under the attacker control to foil potential splitting zeros inputs. + ## The scheme assumes that the attacker cannot + ## resubmit 2^64 times forged (publickey, message, signature) triplets + ## against the same `secureRandomBytes` + + if pubkeys.len == 0: + # IETF spec precondition + return cttBLS_ZeroLengthAggregation + + if pubkeys.len != messages.len or pubkeys.len != signatures.len: + return cttBLS_InconsistentLengthsOfInputs + + # Deal with cases were pubkey or signature were mistakenly zero-init, due to a generic aggregation tentative for example + for i in 0 ..< pubkeys.len: + if pubkeys[i].raw.isInf().bool: + return cttBLS_PointAtInfinity + + for i in 0 ..< signatures.len: + if signatures[i].raw.isInf().bool: + return cttBLS_PointAtInfinity + + let verified = batchVerify( + pubkeys.unwrap(), + messages, + signatures.unwrap(), + sha256, 128, DST, secureRandomBytes) if verified: return cttBLS_Success return cttBLS_VerificationFailure \ No newline at end of file diff --git a/constantine/curves_primitives.nim b/constantine/curves_primitives.nim index 740b641..644dd44 100644 --- a/constantine/curves_primitives.nim +++ b/constantine/curves_primitives.nim @@ -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 # ------------------------------------------------------------ diff --git a/constantine/ethereum_evm_precompiles.nim b/constantine/ethereum_evm_precompiles.nim index 8d9aa50..f1f0907 100644 --- a/constantine/ethereum_evm_precompiles.nim +++ b/constantine/ethereum_evm_precompiles.nim @@ -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 diff --git a/constantine/hash_to_curve/h2c_hash_to_field.nim b/constantine/hash_to_curve/h2c_hash_to_field.nim index 841552a..405f646 100644 --- a/constantine/hash_to_curve/h2c_hash_to_field.nim +++ b/constantine/hash_to_curve/h2c_hash_to_field.nim @@ -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 diff --git a/constantine/hashes.nim b/constantine/hashes.nim index d2986c4..1dec94a 100644 --- a/constantine/hashes.nim +++ b/constantine/hashes.nim @@ -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) diff --git a/constantine/math/config/curves_prop_curve.nim b/constantine/math/config/curves_prop_curve.nim index 8fdc7b7..1f66388 100644 --- a/constantine/math/config/curves_prop_curve.nim +++ b/constantine/math/config/curves_prop_curve.nim @@ -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`)] diff --git a/constantine/math/constants/bls12_377_pairing.nim b/constantine/math/constants/bls12_377_pairings.nim similarity index 83% rename from constantine/math/constants/bls12_377_pairing.nim rename to constantine/math/constants/bls12_377_pairings.nim index d59b000..07c2b07 100644 --- a/constantine/math/constants/bls12_377_pairing.nim +++ b/constantine/math/constants/bls12_377_pairings.nim @@ -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 diff --git a/constantine/math/constants/bls12_381_pairing.nim b/constantine/math/constants/bls12_381_pairings.nim similarity index 85% rename from constantine/math/constants/bls12_381_pairing.nim rename to constantine/math/constants/bls12_381_pairings.nim index b63f7c6..f6fcda2 100644 --- a/constantine/math/constants/bls12_381_pairing.nim +++ b/constantine/math/constants/bls12_381_pairings.nim @@ -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] diff --git a/constantine/math/constants/bn254_nogami_pairing.nim b/constantine/math/constants/bn254_nogami_pairings.nim similarity index 83% rename from constantine/math/constants/bn254_nogami_pairing.nim rename to constantine/math/constants/bn254_nogami_pairings.nim index f66ee92..00b30f0 100644 --- a/constantine/math/constants/bn254_nogami_pairing.nim +++ b/constantine/math/constants/bn254_nogami_pairings.nim @@ -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 \ No newline at end of file diff --git a/constantine/math/constants/bn254_snarks_pairing.nim b/constantine/math/constants/bn254_snarks_pairings.nim similarity index 98% rename from constantine/math/constants/bn254_snarks_pairing.nim rename to constantine/math/constants/bn254_snarks_pairings.nim index d0f12a4..035ef2b 100644 --- a/constantine/math/constants/bn254_snarks_pairing.nim +++ b/constantine/math/constants/bn254_snarks_pairings.nim @@ -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 \ No newline at end of file diff --git a/constantine/math/constants/bw6_761_pairing.nim b/constantine/math/constants/bw6_761_pairings.nim similarity index 99% rename from constantine/math/constants/bw6_761_pairing.nim rename to constantine/math/constants/bw6_761_pairings.nim index 03824fb..f82bef2 100644 --- a/constantine/math/constants/bw6_761_pairing.nim +++ b/constantine/math/constants/bw6_761_pairings.nim @@ -11,7 +11,7 @@ import ../config/curves, ../io/io_bigints, ../extension_fields, - ../pairing/cyclotomic_subgroup, + ../pairings/cyclotomic_subgroups, ../isogenies/frobenius # Slow generic implementation diff --git a/constantine/math/constants/zoo_pairings.nim b/constantine/math/constants/zoo_pairings.nim index cd97162..2b8d83b 100644 --- a/constantine/math/constants/zoo_pairings.nim +++ b/constantine/math/constants/zoo_pairings.nim @@ -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".} diff --git a/constantine/math/pairing/README.md b/constantine/math/pairings/README.md similarity index 100% rename from constantine/math/pairing/README.md rename to constantine/math/pairings/README.md diff --git a/constantine/math/pairing/cyclotomic_subgroup.nim b/constantine/math/pairings/cyclotomic_subgroups.nim similarity index 100% rename from constantine/math/pairing/cyclotomic_subgroup.nim rename to constantine/math/pairings/cyclotomic_subgroups.nim diff --git a/constantine/math/pairing/lines_eval.nim b/constantine/math/pairings/lines_eval.nim similarity index 100% rename from constantine/math/pairing/lines_eval.nim rename to constantine/math/pairings/lines_eval.nim diff --git a/constantine/math/pairings/miller_accumulators.nim b/constantine/math/pairings/miller_accumulators.nim new file mode 100644 index 0000000..995cb9b --- /dev/null +++ b/constantine/math/pairings/miller_accumulators.nim @@ -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 \ No newline at end of file diff --git a/constantine/math/pairing/miller_loops.nim b/constantine/math/pairings/miller_loops.nim similarity index 71% rename from constantine/math/pairing/miller_loops.nim rename to constantine/math/pairings/miller_loops.nim index bceb39e..fae3968 100644 --- a/constantine/math/pairing/miller_loops.nim +++ b/constantine/math/pairings/miller_loops.nim @@ -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) diff --git a/constantine/math/pairing/multi_pairing.md b/constantine/math/pairings/multi_pairings.md similarity index 96% rename from constantine/math/pairing/multi_pairing.md rename to constantine/math/pairings/multi_pairings.md index e0a726c..c111759 100644 --- a/constantine/math/pairing/multi_pairing.md +++ b/constantine/math/pairings/multi_pairings.md @@ -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 diff --git a/constantine/math/pairing/pairing_bls12.nim b/constantine/math/pairings/pairings_bls12.nim similarity index 96% rename from constantine/math/pairing/pairing_bls12.nim rename to constantine/math/pairings/pairings_bls12.nim index bbd3f67..3bd321c 100644 --- a/constantine/math/pairing/pairing_bls12.nim +++ b/constantine/math/pairings/pairings_bls12.nim @@ -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() diff --git a/constantine/math/pairing/pairing_bn.nim b/constantine/math/pairings/pairings_bn.nim similarity index 91% rename from constantine/math/pairing/pairing_bn.nim rename to constantine/math/pairings/pairings_bn.nim index 128ef4e..244fb9a 100644 --- a/constantine/math/pairing/pairing_bn.nim +++ b/constantine/math/pairings/pairings_bn.nim @@ -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() diff --git a/constantine/math/pairing/pairing_bw6_761.nim b/constantine/math/pairings/pairings_bw6_761.nim similarity index 95% rename from constantine/math/pairing/pairing_bw6_761.nim rename to constantine/math/pairings/pairings_bw6_761.nim index 7ee4a55..071874f 100644 --- a/constantine/math/pairing/pairing_bw6_761.nim +++ b/constantine/math/pairings/pairings_bw6_761.nim @@ -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 diff --git a/constantine/math/pairings.nim b/constantine/math/pairings/pairings_generic.nim similarity index 52% rename from constantine/math/pairings.nim rename to constantine/math/pairings/pairings_generic.nim index 1abc42f..291b5bd 100644 --- a/constantine/math/pairings.nim +++ b/constantine/math/pairings/pairings_generic.nim @@ -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.} \ No newline at end of file + {.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.} \ No newline at end of file diff --git a/constantine/signatures/bls_signatures.nim b/constantine/signatures/bls_signatures.nim index 69960cf..460bc91 100644 --- a/constantine/signatures/bls_signatures.nim +++ b/constantine/signatures/bls_signatures.nim @@ -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) \ No newline at end of file +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() diff --git a/metering/m_pairings.nim b/metering/m_pairings.nim index e999261..fff3f16 100644 --- a/metering/m_pairings.nim +++ b/metering/m_pairings.nim @@ -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 diff --git a/research/kzg_poly_commit/kzg_single_proofs.nim b/research/kzg_poly_commit/kzg_single_proofs.nim index 287e2d3..f9f16ab 100644 --- a/research/kzg_poly_commit/kzg_single_proofs.nim +++ b/research/kzg_poly_commit/kzg_single_proofs.nim @@ -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 diff --git a/research/kzg_poly_commit/polynomials.nim b/research/kzg_poly_commit/polynomials.nim index 214dfc3..d66c555 100644 --- a/research/kzg_poly_commit/polynomials.nim +++ b/research/kzg_poly_commit/polynomials.nim @@ -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 diff --git a/tests/math/t_pairing_bls12_377_gt_subgroup.nim b/tests/math/t_pairing_bls12_377_gt_subgroup.nim index 3e456c9..bf3e8f6 100644 --- a/tests/math/t_pairing_bls12_377_gt_subgroup.nim +++ b/tests/math/t_pairing_bls12_377_gt_subgroup.nim @@ -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 diff --git a/tests/math/t_pairing_bls12_377_line_functions.nim b/tests/math/t_pairing_bls12_377_line_functions.nim index 8af6c65..5301183 100644 --- a/tests/math/t_pairing_bls12_377_line_functions.nim +++ b/tests/math/t_pairing_bls12_377_line_functions.nim @@ -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] diff --git a/tests/math/t_pairing_bls12_377_optate.nim b/tests/math/t_pairing_bls12_377_optate.nim index fb9bb8a..6bffe1b 100644 --- a/tests/math/t_pairing_bls12_377_optate.nim +++ b/tests/math/t_pairing_bls12_377_optate.nim @@ -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 diff --git a/tests/math/t_pairing_bls12_381_gt_subgroup.nim b/tests/math/t_pairing_bls12_381_gt_subgroup.nim index f1a39b8..0095d85 100644 --- a/tests/math/t_pairing_bls12_381_gt_subgroup.nim +++ b/tests/math/t_pairing_bls12_381_gt_subgroup.nim @@ -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 diff --git a/tests/math/t_pairing_bls12_381_line_functions.nim b/tests/math/t_pairing_bls12_381_line_functions.nim index 318ed85..cc0fe98 100644 --- a/tests/math/t_pairing_bls12_381_line_functions.nim +++ b/tests/math/t_pairing_bls12_381_line_functions.nim @@ -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] diff --git a/tests/math/t_pairing_bls12_381_multi.nim b/tests/math/t_pairing_bls12_381_multi.nim index 39868de..d365f13 100644 --- a/tests/math/t_pairing_bls12_381_multi.nim +++ b/tests/math/t_pairing_bls12_381_multi.nim @@ -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 diff --git a/tests/math/t_pairing_bls12_381_optate.nim b/tests/math/t_pairing_bls12_381_optate.nim index 4cf4d41..b51602d 100644 --- a/tests/math/t_pairing_bls12_381_optate.nim +++ b/tests/math/t_pairing_bls12_381_optate.nim @@ -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 diff --git a/tests/math/t_pairing_bn254_nogami_gt_subgroup.nim b/tests/math/t_pairing_bn254_nogami_gt_subgroup.nim index 641df01..fd1700b 100644 --- a/tests/math/t_pairing_bn254_nogami_gt_subgroup.nim +++ b/tests/math/t_pairing_bn254_nogami_gt_subgroup.nim @@ -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 diff --git a/tests/math/t_pairing_bn254_nogami_multi.nim b/tests/math/t_pairing_bn254_nogami_multi.nim index 9377c17..b0bf59c 100644 --- a/tests/math/t_pairing_bn254_nogami_multi.nim +++ b/tests/math/t_pairing_bn254_nogami_multi.nim @@ -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 diff --git a/tests/math/t_pairing_bn254_nogami_optate.nim b/tests/math/t_pairing_bn254_nogami_optate.nim index d89b750..19212ba 100644 --- a/tests/math/t_pairing_bn254_nogami_optate.nim +++ b/tests/math/t_pairing_bn254_nogami_optate.nim @@ -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 diff --git a/tests/math/t_pairing_bn254_snarks_gt_subgroup.nim b/tests/math/t_pairing_bn254_snarks_gt_subgroup.nim index 84e33df..a058cd6 100644 --- a/tests/math/t_pairing_bn254_snarks_gt_subgroup.nim +++ b/tests/math/t_pairing_bn254_snarks_gt_subgroup.nim @@ -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 diff --git a/tests/math/t_pairing_bn254_snarks_multi.nim b/tests/math/t_pairing_bn254_snarks_multi.nim index da2daae..dc66821 100644 --- a/tests/math/t_pairing_bn254_snarks_multi.nim +++ b/tests/math/t_pairing_bn254_snarks_multi.nim @@ -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 diff --git a/tests/math/t_pairing_bn254_snarks_optate.nim b/tests/math/t_pairing_bn254_snarks_optate.nim index 764fd62..b401779 100644 --- a/tests/math/t_pairing_bn254_snarks_optate.nim +++ b/tests/math/t_pairing_bn254_snarks_optate.nim @@ -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 diff --git a/tests/math/t_pairing_bw6_761_gt_subgroup.nim b/tests/math/t_pairing_bw6_761_gt_subgroup.nim index 18cfdf6..0135ee2 100644 --- a/tests/math/t_pairing_bw6_761_gt_subgroup.nim +++ b/tests/math/t_pairing_bw6_761_gt_subgroup.nim @@ -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 diff --git a/tests/math/t_pairing_bw6_761_optate.nim b/tests/math/t_pairing_bw6_761_optate.nim index 435abab..e91e8df 100644 --- a/tests/math/t_pairing_bw6_761_optate.nim +++ b/tests/math/t_pairing_bw6_761_optate.nim @@ -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 diff --git a/tests/math/t_pairing_cyclotomic_subgroup.nim b/tests/math/t_pairing_cyclotomic_subgroup.nim index c0d1aa4..36184e5 100644 --- a/tests/math/t_pairing_cyclotomic_subgroup.nim +++ b/tests/math/t_pairing_cyclotomic_subgroup.nim @@ -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] diff --git a/tests/math/t_pairing_mul_fp12_by_lines.nim b/tests/math/t_pairing_mul_fp12_by_lines.nim index 1593b06..2174681 100644 --- a/tests/math/t_pairing_mul_fp12_by_lines.nim +++ b/tests/math/t_pairing_mul_fp12_by_lines.nim @@ -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] diff --git a/tests/math/t_pairing_template.nim b/tests/math/t_pairing_template.nim index 24b5b66..6a19be5 100644 --- a/tests/math/t_pairing_template.nim +++ b/tests/math/t_pairing_template.nim @@ -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 diff --git a/tests/t_blssig_pop_on_bls12381_g2.nim b/tests/t_blssig_pop_on_bls12381_g2.nim index 974ae4a..eb7f653 100644 --- a/tests/t_blssig_pop_on_bls12381_g2.nim +++ b/tests/t_blssig_pop_on_bls12381_g2.nim @@ -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() \ No newline at end of file + 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() \ No newline at end of file