diff --git a/blscurve.nim b/blscurve.nim index edde112..386499e 100644 --- a/blscurve.nim +++ b/blscurve.nim @@ -7,9 +7,9 @@ # This file may not be copied, modified, or distributed except according to # those terms. -const BLS_ETH2_SPEC* = "v1.0.0-rc0" +const BLS_ETH2_SPEC* = "v1.0.0" import - blscurve/bls_backend, + blscurve/bls_public_exports, blscurve/keygen_eip2333 -export bls_backend, keygen_eip2333 +export bls_public_exports, keygen_eip2333 diff --git a/blscurve/bls_backend.nim b/blscurve/bls_backend.nim index dedd04a..6b6a810 100644 --- a/blscurve/bls_backend.nim +++ b/blscurve/bls_backend.nim @@ -45,16 +45,8 @@ else: const BLS_BACKEND* = Miracl when BLS_BACKEND == BLST: - import ./blst/bls_sig_min_pubkey_size_pop, ./blst/sha256_abi - export bls_sig_min_pubkey_size_pop, sha256_abi + import ./blst/blst_min_pubkey_sig_core + export blst_min_pubkey_sig_core else: - import - ./miracl/bls_signature_scheme - export - SecretKey, PublicKey, Signature, ProofOfPossession, - AggregateSignature, - `==`, - init, aggregate, finish, aggregateAll, - sign, verify, aggregateVerify, fastAggregateVerify, - publicFromSecret, isZero, - fromHex, fromBytes, toHex, serialize, exportRaw + import ./miracl/miracl_min_pubkey_sig_core + export miracl_min_pubkey_sig_core diff --git a/blscurve/bls_public_exports.nim b/blscurve/bls_public_exports.nim new file mode 100644 index 0000000..3b8ef72 --- /dev/null +++ b/blscurve/bls_public_exports.nim @@ -0,0 +1,29 @@ +# Nim-BLSCurve +# Copyright (c) 2018-Present Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) +# at your option. +# This file may not be copied, modified, or distributed except according to +# those terms. + +import bls_backend + +export + BLS_BACKEND, BlsBackendKind, + SecretKey, PublicKey, Signature, ProofOfPossession, + AggregateSignature, AggregatePublicKey, + `==`, + init, aggregate, finish, aggregateAll, + publicFromSecret, + fromHex, fromBytes, toHex, serialize, exportRaw + +import bls_sig_min_pubkey + +export + sign, + verify, aggregateVerify, fastAggregateVerify + +when BLS_BACKEND == BLST: + import ./blst/sha256_abi + export sha256_abi diff --git a/blscurve/bls_sig_min_pubkey.nim b/blscurve/bls_sig_min_pubkey.nim new file mode 100644 index 0000000..e040610 --- /dev/null +++ b/blscurve/bls_sig_min_pubkey.nim @@ -0,0 +1,253 @@ +# Nim-BLST +# Copyright (c) 2020 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) +# at your option. +# This file may not be copied, modified, or distributed except according to +# those terms. + +import bls_backend + +# Public API +# ---------------------------------------------------------------------- +# +# There are 3 BLS schemes that differ in handling rogue key attacks +# - basic: requires message signed by an aggregate signature to be distinct +# - message augmentation: signatures are generated over the concatenation of public key and the message +# enforcing message signed by different public key to be distinct +# - proof of possession: a separate public key called proof-of-possession is used to allow signing +# on the same message while defending against rogue key attacks +# with respective ID / domain separation tag: +# - BLS_SIG_BLS12381G2-SHA256-SSWU-RO-_NUL_ +# - BLS_SIG_BLS12381G2-SHA256-SSWU-RO-_AUG_ +# - BLS_SIG_BLS12381G2-SHA256-SSWU-RO-_POP_ +# - POP tag: BLS_POP_BLS12381G2-SHA256-SSWU-RO-_POP_ +# +# We implement the proof-of-possession scheme +# Compared to the spec API are modified +# to enforce usage of the proof-of-posession (as recommended) + +const DST = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_" +const DST_POP = "BLS_POP_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_" + +func popProve*(secretKey: SecretKey, publicKey: PublicKey): ProofOfPossession = + ## Generate a proof of possession for the public/secret keypair + # 1. xP = SK * P + # 2. PK = point_to_pubkey(xP) + # 3. Q = hash_pubkey_to_point(PK) + # 4. R = SK * Q + # 5. proof = point_to_signature(R) + # 6. return proof + var pk{.noInit.}: array[48, byte] + pk.rawFromPublic(publicKey) # 2. Convert to raw bytes compressed form + result.coreSign(secretKey, pk, DST_POP) # 3-4. hash_to_curve and multiply by secret key + +func popProve*(secretKey: SecretKey): ProofOfPossession = + ## Generate a proof of possession for the public key associated with the input secret key + ## Note: this internally recomputes the public key, an overload that doesn't is available. + # 1. xP = SK * P + # 2. PK = point_to_pubkey(xP) + # 3. Q = hash_pubkey_to_point(PK) + # 4. R = SK * Q + # 5. proof = point_to_signature(R) + # 6. return proof + var pubkey {.noInit.}: PublicKey + let ok {.used.} = pubkey.publicFromSecret(secretKey) + assert ok, "The secret key is INVALID, it should be initialized non-zero with keyGen or derive_child_secretKey" + result = popProve(secretKey, pubkey) + +func popVerify*(publicKey: PublicKey, proof: ProofOfPossession): bool = + ## Verify if the proof-of-possession is valid for the public key + ## returns true if valid or false if invalid + # 1. R = signature_to_point(proof) + # 2. If R is INVALID, return INVALID + # 3. If signature_subgroup_check(R) is INVALID, return INVALID + # 4. If KeyValidate(PK) is INVALID, return INVALID + # 5. xP = pubkey_to_point(PK) + # 6. Q = hash_pubkey_to_point(PK) + # 7. C1 = pairing(Q, xP) + # 8. C2 = pairing(R, P) + # 9. If C1 == C2, return VALID, else return INVALID + var pk{.noInit.}: array[48, byte] + pk.rawFromPublic(publicKey) + result = coreVerifyNoGroupCheck(publicKey, pk, proof, DST_POP) + +func sign*[T: byte|char](secretKey: SecretKey, message: openarray[T]): Signature = + ## Computes a signature + ## from a secret key and a message + ## + ## The SecretKey MUST be directly created via + ## `keyGen` or `derive_child_secretKey` + ## or deserialized from `fromBytes` or `fromHex`. + ## This ensures the precondition that it's not a zero key. + result.coreSign(secretKey, message, DST) + +func verify*[T: byte|char]( + publicKey: PublicKey, + proof: ProofOfPossession, + message: openarray[T], + signature: Signature) : bool = + ## Check that a signature is valid for a message + ## under the provided public key. + ## returns `true` if the signature is valid, `false` otherwise. + ## + ## The proof-of-possession MUST be verified before calling this function. + ## It is recommended to use the overload that accepts a proof-of-possession + ## to enforce correct usage. + ## + ## The PublicKey MUST be directly created via + ## `publicFromPrivate` + ## or deserialized from `fromBytes` or `fromHex`. + ## This ensures the precondition that it's not a zero key + ## and that is has been subgroup checked + if not publicKey.popVerify(proof): + return false + return publicKey.coreVerifyNoGroupCheck(message, signature, DST) + +func verify*[T: byte|char]( + publicKey: PublicKey, + message: openarray[T], + signature: Signature) : bool = + ## Check that a signature is valid for a message + ## under the provided public key. + ## returns `true` if the signature is valid, `false` otherwise. + ## + ## The proof-of-possession MUST be verified before calling this function. + ## It is recommended to use the overload that accepts a proof-of-possession + ## to enforce correct usage. + ## + ## The PublicKey MUST be directly created via + ## `publicFromPrivate` + ## or deserialized from `fromBytes` or `fromHex`. + ## This ensures the precondition that it's not a zero key + ## and that is has been subgroup checked + return publicKey.coreVerifyNoGroupCheck(message, signature, DST) + +func aggregateVerify*( + publicKeys: openarray[PublicKey], + proofs: openarray[ProofOfPossession], + messages: openarray[string or seq[byte]], + signature: Signature): bool {.noInline.} = + ## Check that an aggregated signature over several (publickey, message) pairs + ## returns `true` if the signature is valid, `false` otherwise. + ## + ## Compared to the IETF spec API, it is modified to + ## enforce proper usage of the proof-of-possessions + # Note: we can't have openarray of openarrays until openarrays are first-class value types + if publicKeys.len != proofs.len or publicKeys != messages.len: + return false + if not(publicKeys.len >= 1): + return false + + var ctx{.noInit.}: ContextCoreAggregateVerify[DST] + + ctx.init() + for i in 0 ..< publicKeys.len: + if not publicKeys[i].popVerify(proofs[i]): + return false + if not ctx.update(publicKeys[i], messages[i]): + return false + return ctx.finish(signature) + +func aggregateVerify*( + publicKeys: openarray[PublicKey], + messages: openarray[string or seq[byte]], + signature: Signature): bool = + ## Check that an aggregated signature over several (publickey, message) pairs + ## returns `true` if the signature is valid, `false` otherwise. + ## + ## The proof-of-possession MUST be verified before calling this function. + ## It is recommended to use the overload that accepts a proof-of-possession + ## to enforce correct usage. + # Note: we can't have openarray of openarrays until openarrays are first-class value types + if publicKeys.len != messages.len: + return false + if not(publicKeys.len >= 1): + return false + + var ctx{.noInit.}: ContextCoreAggregateVerify[DST] + + ctx.init() + for i in 0 ..< publicKeys.len: + if not ctx.update(publicKeys[i], messages[i]): + return false + return ctx.finish(signature) + +func aggregateVerify*[T: string or seq[byte]]( + publicKey_msg_pairs: openarray[tuple[publicKey: PublicKey, message: T]], + signature: Signature): bool = + ## Check that an aggregated signature over several (publickey, message) pairs + ## returns `true` if the signature is valid, `false` otherwise. + ## + ## The proof-of-possession MUST be verified before calling this function. + ## It is recommended to use the overload that accepts a proof-of-possession + ## to enforce correct usage. + # Note: we can't have tuple of openarrays until openarrays are first-class value types + if not(publicKey_msg_pairs.len >= 1): + return false + + var ctx{.noInit.}: ContextCoreAggregateVerify[DST] + + ctx.init() + for i in 0 ..< publicKey_msg_pairs.len: + if not ctx.update(publicKey_msg_pairs[i].publicKey, publicKey_msg_pairs[i].message): + return false + return ctx.finish(signature) + +func fastAggregateVerify*[T: byte|char]( + publicKeys: openarray[PublicKey], + proofs: openarray[ProofOfPossession], + message: openarray[T], + signature: Signature + ): bool = + ## Verify the aggregate of multiple signatures on the same message + ## This function is faster than AggregateVerify + ## Compared to the IETF spec API, it is modified to + ## enforce proper usage of the proof-of-posession + # 1. aggregate = pubkey_to_point(PK_1) + # 2. for i in 2, ..., n: + # 3. next = pubkey_to_point(PK_i) + # 4. aggregate = aggregate + next + # 5. PK = point_to_pubkey(aggregate) + # 6. return CoreVerify(PK, message, signature) + if publicKeys.len == 0: + return false + if not publicKeys[0].popVerify(proofs[0]): + return false + var aggPK {.noInit.}: AggregatePublicKey + aggPK.init(publicKeys[0]) + for i in 1 ..< publicKeys.len: + if not publicKeys[i].popVerify(proofs[i]): + return false + # We assume that the PublicKey is in on curve, in the proper subgroup + aggPK.aggregate(publicKeys[i]) + + var aggAffine{.noInit.}: PublicKey + aggAffine.finish(aggAffine) + return coreVerifyNoGroupCheck(aggAffine, message, signature, DST) + +func fastAggregateVerify*[T: byte|char]( + publicKeys: openarray[PublicKey], + message: openarray[T], + signature: Signature + ): bool = + ## Verify the aggregate of multiple signatures on the same message + ## This function is faster than AggregateVerify + ## + ## The proof-of-possession MUST be verified before calling this function. + ## It is recommended to use the overload that accepts a proof-of-possession + ## to enforce correct usage. + # 1. aggregate = pubkey_to_point(PK_1) + # 2. for i in 2, ..., n: + # 3. next = pubkey_to_point(PK_i) + # 4. aggregate = aggregate + next + # 5. PK = point_to_pubkey(aggregate) + # 6. return CoreVerify(PK, message, signature) + if publicKeys.len == 0: + return false + + var aggAffine{.noInit.}: PublicKey + if not aggAffine.aggregateAll(publicKeys): + return false + return coreVerifyNoGroupCheck(aggAffine, message, signature, DST) diff --git a/blscurve/blst/bls_sig_io.nim b/blscurve/blst/bls_sig_io.nim new file mode 100644 index 0000000..b00f74b --- /dev/null +++ b/blscurve/blst/bls_sig_io.nim @@ -0,0 +1,160 @@ +# Nim-BLSCurve +# Copyright (c) 2018 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) +# at your option. +# This file may not be copied, modified, or distributed except according to +# those terms. + +# Implementation of IO routines to serialize to and from +# the types defined in +# - https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-00#section-5.5 +# - https://github.com/cfrg/draft-irtf-cfrg-bls-signature + +# This file should be included to have access to private fields +# It is kept separated as it does not fall under the IETF BLS specification + +func toHex*( + obj: SecretKey|PublicKey|Signature|ProofOfPossession|AggregateSignature, + ): string = + ## Return the hex representation of a BLS signature scheme object + ## They are serialized in compressed form + when obj is SecretKey: + const size = 32 + var bytes{.noInit.}: array[size, byte] + bytes.blst_bendian_from_scalar(obj.scalar) + elif obj is PublicKey: + const size = 48 + var bytes{.noInit.}: array[size, byte] + bytes.blst_p1_affine_compress(obj.point) + elif obj is (Signature or ProofOfPossession): + const size = 96 + var bytes{.noInit.}: array[size, byte] + bytes.blst_p2_affine_compress(obj.point) + elif obj is AggregateSignature: + const size = 96 + var bytes{.noInit.}: array[size, byte] + bytes.blst_p2_compress(obj.point) + + result = bytes.toHex() + +func fromBytes*( + obj: var (Signature|ProofOfPossession), + raw: openarray[byte] or array[96, byte] + ): bool {.inline.} = + ## Initialize a BLS signature scheme object from + ## its raw bytes representation. + ## Returns true on success and false otherwise + const L = 96 + when raw is array: + result = obj.point.blst_p2_uncompress(raw) == BLST_SUCCESS + else: + if raw.len != L: + return false + let pa = cast[ptr array[L, byte]](raw[0].unsafeAddr) + result = obj.point.blst_p2_uncompress(pa[]) == BLST_SUCCESS + # Infinity signatures are allowed if we receive an empty aggregated signature + if result: + result = bool obj.point.blst_p2_affine_in_g2() + +func fromBytes*( + obj: var PublicKey, + raw: openarray[byte] or array[48, byte] + ): bool {.inline.} = + ## Initialize a BLS signature scheme object from + ## its raw bytes representation. + ## Returns true on success and false otherwise + const L = 48 + when raw is array: + result = obj.point.blst_p1_uncompress(raw) == BLST_SUCCESS + else: + if raw.len != L: + return false + let pa = cast[ptr array[L, byte]](raw[0].unsafeAddr) + result = obj.point.blst_p1_uncompress(pa[]) == BLST_SUCCESS + # Infinity public keys are not allowed + if result: + result = not bool obj.point.blst_p1_affine_is_inf() + if result: + result = bool obj.point.blst_p1_affine_in_g1() + +func fromBytes*( + obj: var SecretKey, + raw: openarray[byte] or array[32, byte] + ): bool {.inline.} = + ## Initialize a BLS secret key from + ## its raw bytes representation. + ## Returns true on success and false otherwise + const L = 32 + when raw is array: + obj.scalar.blst_scalar_from_bendian(raw) + else: + if raw.len != 32: + return false + let pa = cast[ptr array[L, byte]](raw[0].unsafeAddr) + obj.scalar.blst_scalar_from_bendian(pa[]) + if obj.vec_is_zero(): + return false + if not obj.scalar.blst_sk_check().bool: + return false + return true + +func fromHex*( + obj: var (SecretKey|PublicKey|Signature|ProofOfPossession), + hexStr: string + ): bool {.inline.} = + ## Initialize a BLS signature scheme object from + ## its hex raw bytes representation. + ## Returns true on a success and false otherwise + when obj is SecretKey: + const size = 32 + elif obj is PublicKey: + const size = 48 + elif obj is (Signature or ProofOfPossession): + const size = 96 + + try: + let bytes = hexToPaddedByteArray[size](hexStr) + return obj.fromBytes(bytes) + except: + return false + +func serialize*( + dst: var array[32, byte], + obj: SecretKey): bool {.inline.} = + ## Serialize the input `obj` in raw binary form and write it + ## in `dst`. + ## Returns `true` if the export is succesful, `false` otherwise + blst_bendian_from_scalar(dst, obj.scalar) + return true + +func serialize*( + dst: var array[48, byte], + obj: PublicKey): bool {.inline.} = + ## Serialize the input `obj` in raw binary form and write it + ## in `dst`. + ## Returns `true` if the export is succesful, `false` otherwise + blst_p1_affine_compress(dst, obj.point) + return true + +func serialize*( + dst: var array[96, byte], + obj: Signature|ProofOfPossession): bool {.inline.} = + ## Serialize the input `obj` in raw binary form and write it + ## in `dst`. + ## Returns `true` if the export is succesful, `false` otherwise + blst_p2_affine_compress(dst, obj.point) + return true + +func exportRaw*(secretKey: SecretKey): array[32, byte] {.inline.}= + ## Serialize a secret key into its raw binary representation + discard result.serialize(secretKey) + +func exportRaw*(publicKey: PublicKey): array[48, byte] {.inline.}= + ## Serialize a public key into its raw binary representation + discard result.serialize(publicKey) + +func exportRaw*(signature: Signature): array[96, byte] {.inline.}= + ## Serialize a signature into its raw binary representation + discard result.serialize(signature) diff --git a/blscurve/blst/bls_sig_min_pubkey_size_pop.nim b/blscurve/blst/bls_sig_min_pubkey_size_pop.nim deleted file mode 100644 index 2bacd3f..0000000 --- a/blscurve/blst/bls_sig_min_pubkey_size_pop.nim +++ /dev/null @@ -1,714 +0,0 @@ -# Nim-BLST -# Copyright (c) 2020 Status Research & Development GmbH -# Licensed under either of -# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) -# * MIT license ([LICENSE-MIT](LICENSE-MIT)) -# at your option. -# This file may not be copied, modified, or distributed except according to -# those terms. - -# https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-02 -# -# Variant Minimal-pubkey-size: -# public keys are points in G1, signatures are -# points in G2. -# Implementations using signature aggregation SHOULD use this -# approach, since the size of (PK_1, ..., PK_n, signature) is -# dominated by the public keys even for small n. - -# We expose the same API as MIRACL -# -# Design: -# - We check public keys and signatures at deserialization -# - non-zero -# - in the correct subgroup -# The primitives called assume that input are already subgroup-checked -# and so do not call "KeyValidate" again in verification procs. - -import - # Status libraries - stew/byteutils, - # Internals - ./blst_lowlevel - -# TODO: Consider keeping the compressed keys/signatures in memory -# to divide mem usage by 2 -# i.e. use the staging "pk2" variants like -# - blst_sk_to_pk2_in_g1 -# - blst_sign_pk2_in_g1 - -type - SecretKey* = object - ## A secret key in the BLS (Boneh-Lynn-Shacham) signature scheme. - ## This secret key SHOULD be protected against: - ## - side-channel attacks: - ## implementation must perform exactly the same memory access - ## and execute the same step. In other words it should run in constant time. - ## Furthermore, retrieval of secret key data has been done by reading - ## voltage and power usage on embedded devices - ## - memory dumps: - ## core dumps in case of program crash could leak the data - ## - root attaching to process: - ## a root process like a debugger could attach and read the secret key - ## - key remaining in memory: - ## if the key is not securely erased from memory, it could be accessed - ## - ## Long-term storage of this key also requires adequate protection. - ## - ## At the moment, the nim-blscurve library does not guarantee such protections - scalar: blst_scalar - - PublicKey* = object - ## A public key in the BLS (Boneh-Lynn-Shacham) signature scheme. - point: blst_p1_affine - - # We split Signature and AggregateSignature? - # This saves 1/3 of size as well as signature - # can be affine (2 coordinates) while aggregate has to be jacobian/projective (3 coordinates) - Signature* = object - ## A digital signature of a message using the BLS (Boneh-Lynn-Shacham) signature scheme. - point: blst_p2_affine - - AggregateSignature* = object - ## An aggregated Signature - point: blst_p2 - - ProofOfPossession* = object - ## A separate public key in the Proof-of-Possession BLS signature variant scheme - point: blst_p2_affine - -func `==`*(a, b: SecretKey): bool {.error: "Comparing secret keys is not allowed".} - ## Disallow comparing secret keys. It would require constant-time comparison, - ## and it doesn't make sense anyway. - -func `==`*(a, b: PublicKey or Signature or ProofOfPossession): bool {.inline.} = - ## Check if 2 BLS signature scheme objects are equal - when a.point is blst_p1_affine: - result = bool( - blst_p1_affine_is_equal( - a.point, b.point - ) - ) - else: - result = bool( - blst_p2_affine_is_equal( - a.point, b.point - ) - ) - -# IO -# ---------------------------------------------------------------------- -# Serialization / Deserialization - -func toHex*( - obj: SecretKey|PublicKey|Signature|ProofOfPossession|AggregateSignature, - ): string = - ## Return the hex representation of a BLS signature scheme object - ## They are serialized in compressed form - when obj is SecretKey: - const size = 32 - var bytes{.noInit.}: array[size, byte] - bytes.blst_bendian_from_scalar(obj.scalar) - elif obj is PublicKey: - const size = 48 - var bytes{.noInit.}: array[size, byte] - bytes.blst_p1_affine_compress(obj.point) - elif obj is (Signature or ProofOfPossession): - const size = 96 - var bytes{.noInit.}: array[size, byte] - bytes.blst_p2_affine_compress(obj.point) - elif obj is AggregateSignature: - const size = 96 - var bytes{.noInit.}: array[size, byte] - bytes.blst_p2_compress(obj.point) - - result = bytes.toHex() - -func fromBytes*( - obj: var (Signature|ProofOfPossession), - raw: openarray[byte] or array[96, byte] - ): bool {.inline.} = - ## Initialize a BLS signature scheme object from - ## its raw bytes representation. - ## Returns true on success and false otherwise - const L = 96 - when raw is array: - result = obj.point.blst_p2_uncompress(raw) == BLST_SUCCESS - else: - if raw.len != L: - return false - let pa = cast[ptr array[L, byte]](raw[0].unsafeAddr) - result = obj.point.blst_p2_uncompress(pa[]) == BLST_SUCCESS - # Infinity signatures are allowed if we receive an empty aggregated signature - if result: - result = bool obj.point.blst_p2_affine_in_g2() - -func fromBytes*( - obj: var PublicKey, - raw: openarray[byte] or array[48, byte] - ): bool {.inline.} = - ## Initialize a BLS signature scheme object from - ## its raw bytes representation. - ## Returns true on success and false otherwise - const L = 48 - when raw is array: - result = obj.point.blst_p1_uncompress(raw) == BLST_SUCCESS - else: - if raw.len != L: - return false - let pa = cast[ptr array[L, byte]](raw[0].unsafeAddr) - result = obj.point.blst_p1_uncompress(pa[]) == BLST_SUCCESS - # Infinity public keys are not allowed - if result: - result = not bool obj.point.blst_p1_affine_is_inf() - if result: - result = bool obj.point.blst_p1_affine_in_g1() - -func fromBytes*( - obj: var SecretKey, - raw: openarray[byte] or array[32, byte] - ): bool {.inline.} = - ## Initialize a BLS secret key from - ## its raw bytes representation. - ## Returns true on success and false otherwise - const L = 32 - when raw is array: - obj.scalar.blst_scalar_from_bendian(raw) - else: - if raw.len != 32: - return false - let pa = cast[ptr array[L, byte]](raw[0].unsafeAddr) - obj.scalar.blst_scalar_from_bendian(pa[]) - if obj.vec_is_zero(): - return false - if not obj.scalar.blst_sk_check().bool: - return false - return true - -func fromHex*( - obj: var (SecretKey|PublicKey|Signature|ProofOfPossession), - hexStr: string - ): bool {.inline.} = - ## Initialize a BLS signature scheme object from - ## its hex raw bytes representation. - ## Returns true on a success and false otherwise - when obj is SecretKey: - const size = 32 - elif obj is PublicKey: - const size = 48 - elif obj is (Signature or ProofOfPossession): - const size = 96 - - try: - let bytes = hexToPaddedByteArray[size](hexStr) - return obj.fromBytes(bytes) - except: - return false - -func serialize*( - dst: var array[32, byte], - obj: SecretKey): bool {.inline.} = - ## Serialize the input `obj` in raw binary form and write it - ## in `dst`. - ## Returns `true` if the export is succesful, `false` otherwise - blst_bendian_from_scalar(dst, obj.scalar) - return true - -func serialize*( - dst: var array[48, byte], - obj: PublicKey): bool {.inline.} = - ## Serialize the input `obj` in raw binary form and write it - ## in `dst`. - ## Returns `true` if the export is succesful, `false` otherwise - blst_p1_affine_compress(dst, obj.point) - return true - -func serialize*( - dst: var array[96, byte], - obj: Signature|ProofOfPossession): bool {.inline.} = - ## Serialize the input `obj` in raw binary form and write it - ## in `dst`. - ## Returns `true` if the export is succesful, `false` otherwise - blst_p2_affine_compress(dst, obj.point) - return true - -func exportRaw*(secretKey: SecretKey): array[32, byte] {.inline.}= - ## Serialize a secret key into its raw binary representation - discard result.serialize(secretKey) - -func exportRaw*(publicKey: PublicKey): array[48, byte] {.inline.}= - ## Serialize a public key into its raw binary representation - discard result.serialize(publicKey) - -func exportRaw*(signature: Signature): array[96, byte] {.inline.}= - ## Serialize a signature into its raw binary representation - discard result.serialize(signature) - -# Primitives -# ---------------------------------------------------------------------- - -func publicFromSecret*(pubkey: var PublicKey, seckey: SecretKey): bool = - ## Generates a public key from a secret key - ## Generates a public key from a secret key - ## This requires some -O3 compiler optimizations to be off - ## as such {.passC: "-fno-tree-vectorize".} - ## is automatically added to the compiler flags in blst_lowlevel - if seckey.vec_is_zero(): - return false - var pk {.noInit.}: blst_p1 - pk.blst_sk_to_pk_in_g1(seckey.scalar) - pubkey.point.blst_p1_to_affine(pk) - return true - -# Aggregate -# ---------------------------------------------------------------------- - -func init*(agg: var AggregateSignature, sig: Signature) {.inline.} = - ## Initialize an aggregate signature with a signature - agg.point.blst_p2_from_affine(sig.point) - -func aggregate*(agg: var AggregateSignature, sig: Signature) {.inline.} = - ## Aggregates signature ``sig`` into ``agg`` - # Precondition n >= 1 is respected - agg.point.blst_p2_add_or_double_affine( - agg.point, - sig.point - ) - -proc aggregate*(agg: var AggregateSignature, sigs: openarray[Signature]) = - ## Aggregates an array of signatures `sigs` into a signature `sig` - # Precondition n >= 1 is respected even if sigs.len == 0 - for s in sigs: - agg.point.blst_p2_add_or_double_affine( - agg.point, - s.point - ) - -proc finish*(sig: var Signature, agg: AggregateSignature) {.inline.} = - ## Canonicalize the AggregateSignature into a Signature - sig.point.blst_p2_to_affine(agg.point) - -proc aggregateAll*(dst: var Signature, sigs: openarray[Signature]): bool = - ## Returns the aggregate signature of ``sigs[0.. GT) - # to get the equivalent check that is more efficient to implement - # (e'(pubkey1, msg1) e'(pubkey2, msg2) ... e'(pubkeyN, msgN) e'(-P1, sig))^x == 1 - # The generator P1 is on G1 which is cheaper to negate than the signature - - # We add the signature to the pairing context - # via `blst_pairing_aggregate_pk_in_g1` - # instead of via `blst_aggregated_in_g2` + `blst_pairing_finalverify` - # to save one Miller loop - # as both `blst_pairing_commit` and `blst_pairing_finalverify(non-nil)` - # use a Miller loop internally and Miller loops are **very** costly. - - when signature is Signature: - result = BLST_SUCCESS == ctx.c.blst_pairing_chk_n_aggr_pk_in_g1( - PK = nil, - pk_grpchk = false, # Already grouped checked - signature.point.unsafeAddr, - sig_grpchk = false, # Already grouped checked - msg = "", - aug = "" - ) - elif signature is AggregateSignature: - block: - var sig{.noInit.}: blst_p2_affine - sig.blst_p2_to_affine(signature.point) - result = BLST_SUCCESS == ctx.c.blst_pairing_chk_n_aggr_pk_in_g1( - PK = nil, - pk_grpchk = false, # Already grouped checked - sig.point.unsafeAddr, - sig_grpchk = false, # Already grouped checked - msg = "", - aug = "" - ) - else: - {.error: "Unreachable".} - - if not result: return - - ctx.c.blst_pairing_commit() - result = bool ctx.c.blst_pairing_finalverify(nil) - -# Public API -# ---------------------------------------------------------------------- -# -# There are 3 BLS schemes that differ in handling rogue key attacks -# - basic: requires message signed by an aggregate signature to be distinct -# - message augmentation: signatures are generated over the concatenation of public key and the message -# enforcing message signed by different public key to be distinct -# - proof of possession: a separate public key called proof-of-possession is used to allow signing -# on the same message while defending against rogue key attacks -# with respective ID / domain separation tag: -# - BLS_SIG_BLS12381G2-SHA256-SSWU-RO-_NUL_ -# - BLS_SIG_BLS12381G2-SHA256-SSWU-RO-_AUG_ -# - BLS_SIG_BLS12381G2-SHA256-SSWU-RO-_POP_ -# - POP tag: BLS_POP_BLS12381G2-SHA256-SSWU-RO-_POP_ -# -# We implement the proof-of-possession scheme -# Compared to the spec API are modified -# to enforce usage of the proof-of-posession (as recommended) - -const DST = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_" -const DST_POP = "BLS_POP_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_" - -func popProve*(secretKey: SecretKey, publicKey: PublicKey): ProofOfPossession = - ## Generate a proof of possession for the public/secret keypair - # 1. xP = SK * P - # 2. PK = point_to_pubkey(xP) - # 3. Q = hash_pubkey_to_point(PK) - # 4. R = SK * Q - # 5. proof = point_to_signature(R) - # 6. return proof - var pk{.noInit.}: array[48, byte] - pk.blst_p1_affine_compress(publicKey.point) # 2. Convert to raw bytes compressed form - result.coreSign(secretKey, pk, DST_POP) # 3-4. hash_to_curve and multiply by secret key - -func popProve*(secretKey: SecretKey): ProofOfPossession = - ## Generate a proof of possession for the public key associated with the input secret key - ## Note: this internally recomputes the public key, an overload that doesn't is available. - # 1. xP = SK * P - # 2. PK = point_to_pubkey(xP) - # 3. Q = hash_pubkey_to_point(PK) - # 4. R = SK * Q - # 5. proof = point_to_signature(R) - # 6. return proof - var pubkey {.noInit.}: PublicKey - let ok {.used.} = pubkey.publicFromSecret(secretKey) - assert ok, "The secret key is INVALID, it should be initialized non-zero with keyGen or derive_child_secretKey" - result = popProve(secretKey, pubkey) - -func popVerify*(publicKey: PublicKey, proof: ProofOfPossession): bool = - ## Verify if the proof-of-possession is valid for the public key - ## returns true if valid or false if invalid - # 1. R = signature_to_point(proof) - # 2. If R is INVALID, return INVALID - # 3. If signature_subgroup_check(R) is INVALID, return INVALID - # 4. If KeyValidate(PK) is INVALID, return INVALID - # 5. xP = pubkey_to_point(PK) - # 6. Q = hash_pubkey_to_point(PK) - # 7. C1 = pairing(Q, xP) - # 8. C2 = pairing(R, P) - # 9. If C1 == C2, return VALID, else return INVALID - var pk{.noInit.}: array[48, byte] - pk.blst_p1_affine_compress(publicKey.point) - result = coreVerifyNoGroupCheck(publicKey, pk, proof, DST_POP) - -func sign*[T: byte|char](secretKey: SecretKey, message: openarray[T]): Signature = - ## Computes a signature - ## from a secret key and a message - result.coreSign(secretKey, message, DST) - -func verify*[T: byte|char]( - publicKey: PublicKey, - proof: ProofOfPossession, - message: openarray[T], - signature: Signature) : bool = - ## Check that a signature is valid for a message - ## under the provided public key. - ## returns `true` if the signature is valid, `false` otherwise. - ## - ## Compared to the IETF spec API, it is modified to - ## enforce proper usage of the proof-of-possession - if not publicKey.popVerify(proof): - return false - return publicKey.coreVerifyNoGroupCheck(message, signature, DST) - -func verify*[T: byte|char]( - publicKey: PublicKey, - message: openarray[T], - signature: Signature) : bool = - ## Check that a signature is valid for a message - ## under the provided public key. - ## returns `true` if the signature is valid, `false` otherwise. - ## - ## The proof-of-possession MUST be verified before calling this function. - ## It is recommended to use the overload that accepts a proof-of-possession - ## to enforce correct usage. - return publicKey.coreVerifyNoGroupCheck(message, signature, DST) - -func aggregateVerify*( - publicKeys: openarray[PublicKey], - proofs: openarray[ProofOfPossession], - messages: openarray[string or seq[byte]], - signature: Signature): bool {.noInline.} = - ## Check that an aggregated signature over several (publickey, message) pairs - ## returns `true` if the signature is valid, `false` otherwise. - ## - ## Compared to the IETF spec API, it is modified to - ## enforce proper usage of the proof-of-possessions - # Note: we can't have openarray of openarrays until openarrays are first-class value types - if publicKeys.len != proofs.len or publicKeys != messages.len: - return false - if not(publicKeys.len >= 1): - return false - - var ctx{.noInit.}: ContextCoreAggregateVerify - - ctx.init(DST) - for i in 0 ..< publicKeys.len: - if not publicKeys[i].popVerify(proofs[i]): - return false - ctx.update(publicKeys[i], messages[i]) - return ctx.finish(signature) - -func aggregateVerify*( - publicKeys: openarray[PublicKey], - messages: openarray[string or seq[byte]], - signature: Signature): bool = - ## Check that an aggregated signature over several (publickey, message) pairs - ## returns `true` if the signature is valid, `false` otherwise. - ## - ## The proof-of-possession MUST be verified before calling this function. - ## It is recommended to use the overload that accepts a proof-of-possession - ## to enforce correct usage. - # Note: we can't have openarray of openarrays until openarrays are first-class value types - if publicKeys.len != messages.len: - return false - if not(publicKeys.len >= 1): - return false - - var ctx{.noInit.}: ContextCoreAggregateVerify - - ctx.init(DST) - for i in 0 ..< publicKeys.len: - result = ctx.update(publicKeys[i], messages[i]) - if not result: - return - return ctx.finish(signature) - -func aggregateVerify*[T: string or seq[byte]]( - publicKey_msg_pairs: openarray[tuple[publicKey: PublicKey, message: T]], - signature: Signature): bool = - ## Check that an aggregated signature over several (publickey, message) pairs - ## returns `true` if the signature is valid, `false` otherwise. - ## - ## The proof-of-possession MUST be verified before calling this function. - ## It is recommended to use the overload that accepts a proof-of-possession - ## to enforce correct usage. - # Note: we can't have tuple of openarrays until openarrays are first-class value types - if not(publicKey_msg_pairs.len >= 1): - return false - - var ctx{.noInit.}: ContextCoreAggregateVerify - - ctx.init(DST) - for i in 0 ..< publicKey_msg_pairs.len: - result = ctx.update(publicKey_msg_pairs[i].publicKey, publicKey_msg_pairs[i].message) - if not result: - return - return ctx.finish(signature) - -func fastAggregateVerify*[T: byte|char]( - publicKeys: openarray[PublicKey], - proofs: openarray[ProofOfPossession], - message: openarray[T], - signature: Signature - ): bool = - ## Verify the aggregate of multiple signatures on the same message - ## This function is faster than AggregateVerify - ## Compared to the IETF spec API, it is modified to - ## enforce proper usage of the proof-of-posession - # 1. aggregate = pubkey_to_point(PK_1) - # 2. for i in 2, ..., n: - # 3. next = pubkey_to_point(PK_i) - # 4. aggregate = aggregate + next - # 5. PK = point_to_pubkey(aggregate) - # 6. return CoreVerify(PK, message, signature) - if publicKeys.len == 0: - return false - if not publicKeys[0].popVerify(proofs[0]): - return false - var aggregate {.noInit.}: blst_p1 - aggregate.blst_p1_from_affine(publicKeys[0].point) - for i in 1 ..< publicKeys.len: - if not publicKeys[i].popVerify(proofs[i]): - return false - # We assume that the PublicKey is in on curve, in the proper subgroup - aggregate.blst_p1_add_or_double_affine(publicKeys[i].point) - - var aggAffine{.noInit.}: PublicKey - aggAffine.point.blst_p1_to_affine(aggregate) - return coreVerifyNoGroupCheck(aggAffine, message, signature, DST) - -func fastAggregateVerify*[T: byte|char]( - publicKeys: openarray[PublicKey], - message: openarray[T], - signature: Signature - ): bool = - ## Verify the aggregate of multiple signatures on the same message - ## This function is faster than AggregateVerify - ## - ## The proof-of-possession MUST be verified before calling this function. - ## It is recommended to use the overload that accepts a proof-of-possession - ## to enforce correct usage. - # 1. aggregate = pubkey_to_point(PK_1) - # 2. for i in 2, ..., n: - # 3. next = pubkey_to_point(PK_i) - # 4. aggregate = aggregate + next - # 5. PK = point_to_pubkey(aggregate) - # 6. return CoreVerify(PK, message, signature) - if publicKeys.len == 0: - return false - var aggregate {.noInit.}: blst_p1 - aggregate.blst_p1_from_affine(publicKeys[0].point) - for i in 1 ..< publicKeys.len: - # We assume that the PublicKey is in on curve, in the proper subgroup - aggregate.blst_p1_add_or_double_affine(aggregate, publicKeys[i].point) - - var aggAffine{.noInit.}: PublicKey - aggAffine.point.blst_p1_to_affine(aggregate) - return coreVerifyNoGroupCheck(aggAffine, message, signature, DST) diff --git a/blscurve/blst/blst_min_pubkey_sig_core.nim b/blscurve/blst/blst_min_pubkey_sig_core.nim new file mode 100644 index 0000000..4dfeb1e --- /dev/null +++ b/blscurve/blst/blst_min_pubkey_sig_core.nim @@ -0,0 +1,380 @@ +# Nim-BLST +# Copyright (c) 2020 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) +# at your option. +# This file may not be copied, modified, or distributed except according to +# those terms. + +# https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-02 +# +# Variant Minimal-pubkey-size: +# public keys are points in G1, signatures are +# points in G2. +# Implementations using signature aggregation SHOULD use this +# approach, since the size of (PK_1, ..., PK_n, signature) is +# dominated by the public keys even for small n. + +# We expose the same API as MIRACL +# +# Design: +# - We check public keys and signatures at deserialization +# - non-zero +# - in the correct subgroup +# The primitives called assume that input are already subgroup-checked +# and so do not call "KeyValidate" again in verification procs. + +# Core verification +import + # Status libraries + stew/byteutils, + # Internals + ./blst_lowlevel + +# TODO: Consider keeping the compressed keys/signatures in memory +# to divide mem usage by 2 +# i.e. use the staging "pk2" variants like +# - blst_sk_to_pk2_in_g1 +# - blst_sign_pk2_in_g1 + +type + SecretKey* = object + ## A secret key in the BLS (Boneh-Lynn-Shacham) signature scheme. + ## This secret key SHOULD be protected against: + ## - side-channel attacks: + ## implementation must perform exactly the same memory access + ## and execute the same step. In other words it should run in constant time. + ## Furthermore, retrieval of secret key data has been done by reading + ## voltage and power usage on embedded devices + ## - memory dumps: + ## core dumps in case of program crash could leak the data + ## - root attaching to process: + ## a root process like a debugger could attach and read the secret key + ## - key remaining in memory: + ## if the key is not securely erased from memory, it could be accessed + ## + ## Long-term storage of this key also requires adequate protection. + ## + ## At the moment, the nim-blscurve library does not guarantee such protections + scalar: blst_scalar + + PublicKey* = object + ## A public key in the BLS (Boneh-Lynn-Shacham) signature scheme. + point: blst_p1_affine + + # We split Signature and AggregateSignature? + # This saves 1/3 of size as well as signature + # can be affine (2 coordinates) while aggregate has to be jacobian/projective (3 coordinates) + Signature* = object + ## A digital signature of a message using the BLS (Boneh-Lynn-Shacham) signature scheme. + point: blst_p2_affine + + AggregateSignature* = object + ## An aggregated Signature + point: blst_p2 + + AggregatePublicKey* = object + ## An aggregated Public Key + point: blst_p1 + + ProofOfPossession* = object + ## A separate public key in the Proof-of-Possession BLS signature variant scheme + point: blst_p2_affine + +func `==`*(a, b: SecretKey): bool {.error: "Comparing secret keys is not allowed".} + ## Disallow comparing secret keys. It would require constant-time comparison, + ## and it doesn't make sense anyway. + +func `==`*(a, b: PublicKey or Signature or ProofOfPossession): bool {.inline.} = + ## Check if 2 BLS signature scheme objects are equal + when a.point is blst_p1_affine: + result = bool( + blst_p1_affine_is_equal( + a.point, b.point + ) + ) + else: + result = bool( + blst_p2_affine_is_equal( + a.point, b.point + ) + ) + +# IO +# ---------------------------------------------------------------------- +# Serialization / Deserialization +# As I/O routines are not part of the specifications, they are implemented +# in a separate file. The file is included instead of imported to +# access private fields +# +# PublicKeys are infinity checked and subgroup checked on deserialization +# Signatures are subgroup-checked on deserialization + +include ./bls_sig_io + +# Primitives +# ---------------------------------------------------------------------- + +func publicFromSecret*(pubkey: var PublicKey, seckey: SecretKey): bool = + ## Generates a public key from a secret key + ## This requires some -O3 compiler optimizations to be off + ## as such {.passC: "-fno-tree-vectorize".} + ## is automatically added to the compiler flags in blst_lowlevel + if seckey.vec_is_zero(): + return false + var pk {.noInit.}: blst_p1 + pk.blst_sk_to_pk_in_g1(seckey.scalar) + pubkey.point.blst_p1_to_affine(pk) + return true + +func rawFromPublic*(raw: var array[48, byte], pubkey: PublicKey) {.inline.} = + ## Dump a public key to raw bytes compressed form + raw.blst_p1_affine_compress(pubkey.point) + +# Aggregate +# ---------------------------------------------------------------------- + +template genAggregatorProcedures( + Aggregate: typedesc[AggregateSignature or AggregatePublicKey], + BaseType: typedesc[Signature or PublicKey], + p1_or_p2: untyped + ): untyped = + func init*(agg: var Aggregate, elem: BaseType) {.inline.} = + ## Initialize an aggregate signature or public key + agg.point.`blst _ p1_or_p2 _ from_affine`(elem.point) + + func aggregate*(agg: var Aggregate, elem: BaseType) {.inline.} = + ## Aggregates an element ``elem`` into ``agg`` + # Precondition n >= 1 is respected + agg.point.`blst _ p1_or_p2 _ add_or_double_affine`( + agg.point, + elem.point + ) + + proc aggregate*(agg: var Aggregate, elems: openarray[BaseType]) = + ## Aggregates an array of elements `elems` into `agg` + # Precondition n >= 1 is respected even if elems.len == 0 + for e in elems: + agg.point.`blst _ p1_or_p2 _ add_or_double_affine`( + agg.point, + e.point + ) + + proc finish*(dst: var BaseType, src: Aggregate) {.inline.} = + ## Canonicalize the Aggregate into a BaseType element + dst.point.`blst _ p1_or_p2 _ to_affine`(src.point) + + proc aggregateAll*(dst: var BaseType, elems: openarray[BaseType]): bool = + ## Returns the aggregate of ``elems[0.. GT) + # to get the equivalent check that is more efficient to implement + # (e'(pubkey1, msg1) e'(pubkey2, msg2) ... e'(pubkeyN, msgN) e'(-P1, sig))^x == 1 + # The generator P1 is on G1 which is cheaper to negate than the signature + + # We add the signature to the pairing context + # via `blst_pairing_aggregate_pk_in_g1` + # instead of via `blst_aggregated_in_g2` + `blst_pairing_finalverify` + # to save one Miller loop + # as both `blst_pairing_commit` and `blst_pairing_finalverify(non-nil)` + # use a Miller loop internally and Miller loops are **very** costly. + + when signature is Signature: + result = BLST_SUCCESS == ctx.c.blst_pairing_chk_n_aggr_pk_in_g1( + PK = nil, + pk_grpchk = false, # Already grouped checked + signature.point.unsafeAddr, + sig_grpchk = false, # Already grouped checked + msg = "", + aug = "" + ) + elif signature is AggregateSignature: + block: + var sig{.noInit.}: blst_p2_affine + sig.blst_p2_to_affine(signature.point) + result = BLST_SUCCESS == ctx.c.blst_pairing_chk_n_aggr_pk_in_g1( + PK = nil, + pk_grpchk = false, # Already grouped checked + sig.point.unsafeAddr, + sig_grpchk = false, # Already grouped checked + msg = "", + aug = "" + ) + else: + {.error: "Unreachable".} + + if not result: return + + ctx.commit() + result = bool ctx.finalVerify() diff --git a/blscurve/eth2_keygen/hkdf_mod_r_blst.nim b/blscurve/eth2_keygen/hkdf_mod_r_blst.nim index d1f8dd5..a5c1705 100644 --- a/blscurve/eth2_keygen/hkdf_mod_r_blst.nim +++ b/blscurve/eth2_keygen/hkdf_mod_r_blst.nim @@ -80,7 +80,9 @@ func mul_mont_sparse_256( # ---------------------------------------------------------------------- -func hkdf_mod_r*(secretKey: var SecretKey, ikm: openArray[byte], key_info: string): bool = +func hkdf_mod_r*[T: char|byte]( + secretKey: var SecretKey, + ikm: openArray[byte], key_info: openarray[T]): bool = ## Ethereum 2 EIP-2333, extracts this from the BLS signature schemes # 1. salt = "BLS-SIG-KEYGEN-SALT-" # 2. SK = 0 @@ -124,8 +126,6 @@ func hkdf_mod_r*(secretKey: var SecretKey, ikm: openArray[byte], key_info: strin else: return true - - func keyGen*(ikm: openarray[byte], publicKey: var PublicKey, secretKey: var SecretKey): bool = ## Generate a (public key, secret key) pair ## from the input keying material `ikm` diff --git a/blscurve/eth2_keygen/hkdf_mod_r_miracl.nim b/blscurve/eth2_keygen/hkdf_mod_r_miracl.nim index dfb840a..78336a9 100644 --- a/blscurve/eth2_keygen/hkdf_mod_r_miracl.nim +++ b/blscurve/eth2_keygen/hkdf_mod_r_miracl.nim @@ -18,6 +18,12 @@ import ../miracl/[common, milagro], ./hkdf +func isZero(seckey: SecretKey): bool {.inline.} = + ## Returns true if the secret key is zero + ## Those are invalid + # The cast is a workaround for private field access + result = cast[ptr BIG_384](seckey.unsafeAddr)[].isZilch() + func hkdf_mod_r*(secretKey: var SecretKey, ikm: openArray[byte], key_info: string): bool = ## Ethereum 2 EIP-2333, extracts this from the BLS signature schemes # 1. PRK = HKDF-Extract("BLS-SIG-KEYGEN-SALT-", IKM) diff --git a/blscurve/miracl/bls_sig_io.nim b/blscurve/miracl/bls_sig_io.nim index a75ae48..ef4d094 100644 --- a/blscurve/miracl/bls_sig_io.nim +++ b/blscurve/miracl/bls_sig_io.nim @@ -33,7 +33,9 @@ func fromHex*[T: SecretKey|PublicKey|Signature|ProofOfPossession]( when obj is PublicKey: # KeyValidate if obj.point.isInf(): - result = false + return false + if not subgroupCheck(obj.point): + return false func fromBytes*[T: SecretKey|PublicKey|Signature|ProofOfPossession]( obj: var T, diff --git a/blscurve/miracl/bls_signature_scheme.nim b/blscurve/miracl/bls_signature_scheme.nim deleted file mode 100644 index 883728a..0000000 --- a/blscurve/miracl/bls_signature_scheme.nim +++ /dev/null @@ -1,578 +0,0 @@ -# Nim-BLSCurve -# Copyright (c) 2018 Status Research & Development GmbH -# Licensed under either of -# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) -# * MIT license ([LICENSE-MIT](LICENSE-MIT)) -# at your option. -# This file may not be copied, modified, or distributed except according to -# those terms. - -# Implementation of BLS signature scheme (Boneh-Lynn-Shacham) -# following IETF standardization -# Target Ethereum 2.0 specification after v0.10. -# -# Specification: -# - https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-02 -# - https://github.com/cfrg/draft-irtf-cfrg-bls-signature -# -# Ethereum 2.0 specification targets minimul-pubkey-size -# so public keys are on curve subgroup G1 -# and signatures are on curve subgroup G2 -# -# We reuse the IETF types and procedure names -# Cipher suite ID: BLS_SIG_BLS12381G2-SHA256-SSWU-RO-_NUL_ -# -# Draft changes: https://tools.ietf.org/rfcdiff?url1=https://tools.ietf.org/id/draft-irtf-cfrg-bls-signature-00.txt&url2=https://tools.ietf.org/id/draft-irtf-cfrg-bls-signature-02.txt - -{.push raises: [Defect].} - -import - # third-party - nimcrypto/[hmac, sha2], - stew/endians2, - # internal - ./milagro, ./common - -import ./hash_to_curve - -# Public Types -# ---------------------------------------------------------------------- - -type - SecretKey* = object - ## A secret key in the BLS (Boneh-Lynn-Shacham) signature scheme. - ## - ## This SecretKey is non-zero by construction. - ## - ## This secret key SHOULD be protected against: - ## - side-channel attacks: - ## implementation must perform exactly the same memory access - ## and execute the same step. In other words it should run in constant time. - ## Furthermore, retrieval of secret key data has been done by reading - ## voltage and power usage on embedded devices - ## - memory dumps: - ## core dumps in case of program crash could leak the data - ## - root attaching to process: - ## a root process like a debugger could attach and read the secret key - ## - key remaining in memory: - ## if the key is not securely erased from memory, it could be accessed - ## - ## Long-term storage of this key also requires adequate protection. - ## - ## At the moment, the nim-blscurve library does not guarantee such protections - intVal: BIG_384 - - PublicKey* = object - ## A public key in the BLS (Boneh-Lynn-Shacham) signature scheme. - point: GroupG1 - - Signature* = object - ## A digital signature of a message using the BLS (Boneh-Lynn-Shacham) signature scheme. - point: GroupG2 - - ProofOfPossession* = object - ## A separate public key in the Proof-of-Possession BLS signature variant scheme - point: GroupG2 - - AggregateSignature*{.borrow:`.`.} = distinct Signature - ## An aggregated Signature. - ## With MIRACL backend, there is no bit-level - ## difference from a normal signature - -func `==`*(a, b: SecretKey): bool {.error: "Comparing secret keys is not allowed".} - ## Disallow comparing secret keys. It would require constant-time comparison, - ## and it doesn't make sense anyway. - -func `==`*(a, b: PublicKey or Signature or ProofOfPossession): bool {.inline.} = - ## Check if 2 BLS signature scheme objects are equal - return a.point == b.point - -# IO -# ---------------------------------------------------------------------- -# Serialization / Deserialization -# As I/O routines are not part of the specifications, they are implemented -# in a separate file. The file is included instead of imported to -# access private fields - -include ./bls_sig_io - -# Primitives -# ---------------------------------------------------------------------- -func subgroupCheck*(P: GroupG1 or GroupG2): bool = - ## Checks that a point `P` - ## is actually in the subgroup G1/G2 of the BLS Curve - var rP = P - {.noSideEffect.}: - rP.mul(CURVE_Order) - result = rP.isInf() - -func isZero*(seckey: SecretKey): bool = - ## Returns true if the secret key is zero - ## Those are invalid - result = seckey.intVal.iszilch() - -func publicFromSecret*(pubkey: var PublicKey, seckey: SecretKey): bool = - ## Generates a public key from a secret key - ## Inputs: - ## - SK, a secret integer such that 1 <= SK < r. - ## - ## Outputs: - ## - PK, a public key encoded as an octet string. - ## - ## Returns: - ## - false is secret key is invalid (SK == 0), true otherwise - ## - ## Side-channel/Constant-time considerations: - ## The SK content is not revealed unless its value - ## is exactly 0 - # - # Procedure: - # 1. xP = SK * P - # 2. PK = point_to_pubkey(xP) - # 3. return PK - - - # Always != 0: - # keyGen, deriveChild_secretKey, fromHex, fromBytes guarantee that. - if seckey.isZero(): - return false - pubkey.point = generator1() - pubkey.point.mul(secKey.intVal) - return true - -# Aggregate -# ---------------------------------------------------------------------- -# 2.8. Aggregate (BLSv4) -# -# The Aggregate algorithm aggregates multiple signatures into one. -# signature = Aggregate((signature_1, ..., signature_n)) -# -# Inputs: -# - signature_1, ..., signature_n, octet strings output by -# either CoreSign or Aggregate. -# -# Outputs: -# - signature, an octet string encoding a aggregated signature -# that combines all inputs; or INVALID. -# -# Precondition: n >= 1, otherwise return INVALID. -# -# Procedure: -# 1. aggregate = signature_to_point(signature_1) -# 2. If aggregate is INVALID, return INVALID -# 3. for i in 2, ..., n: -# 4. next = signature_to_point(signature_i) -# 5. If next is INVALID, return INVALID -# 6. aggregate = aggregate + next -# 7. signature = point_to_signature(aggregate) -# -# Comments: -# - This does not require signatures to be non-zero - -func init*(agg: var AggregateSignature, sig: Signature) {.inline.} = - ## Initialize an aggregate signature with a signature - agg = AggregateSignature(sig) - -proc aggregate*(agg: var AggregateSignature, sig: Signature) {.inline.} = - ## Returns the aggregate signature of ``sig1`` + ``sig2``. - # Precondition n >= 1 is respected - agg.point.add(sig.point) - -proc aggregate*(agg: var AggregateSignature, sigs: openarray[Signature]) = - ## Returns the aggregate signature of ``sig1`` + ``sigs[0..= 1 is respected even if sigs.len == 0 - for s in sigs: - agg.aggregate(s) - -proc finish*(sig: var Signature, agg: AggregateSignature) {.inline.} = - ## Canonicalize the AggregateSignature into a Signature - sig = Signature(agg) - -proc aggregateAll*(dst: var Signature, sigs: openarray[Signature]): bool = - ## Returns the aggregate signature of ``sigs[0.. GT - # and pairing(R, P) := e(P, R) - - # 3. If signature_subgroup_check(R) is INVALID, return INVALID - if not subgroupCheck(sig_or_proof.point): - return false - # 4. If KeyValidate(PK) is INVALID, return INVALID - # We assumes PK is not 0 - if not subgroupCheck(publicKey.point): - return false - let Q = hashToG2(message, domainSepTag) - - # pairing(Q, xP) == pairing(R, P) - return multiPairing( - Q, publicKey.point, - sig_or_proof.point, generator1() - ) - -type - ContextCoreAggregateVerify = object - # Streaming API for Aggregate verification to handle both SoA and AoS data layout - # Spec - # Precondition: n >= 1, otherwise return INVALID. - # Procedure: - # 1. R = signature_to_point(signature) - # 2. If R is INVALID, return INVALID - # 3. If signature_subgroup_check(R) is INVALID, return INVALID - # 4. C1 = 1 (the identity element in GT) - # 5. for i in 1, ..., n: - # 6. If KeyValidate(PK_i) is INVALID, return INVALID - # 7. xP = pubkey_to_point(PK_i) - # 8. Q = hash_to_point(message_i) - # 9. C1 = C1 * pairing(Q, xP) - # 10. C2 = pairing(R, P) - # 11. If C1 == C2, return VALID, else return INVALID - C1: array[AteBitsCount, FP12_BLS12381] - -func init(ctx: var ContextCoreAggregateVerify) = - ## initialize an aggregate verification context - PAIR_BLS12381_initmp(addr ctx.C1[0]) # C1 = 1 (identity element) - -template `&`(point: GroupG1 or GroupG2): untyped = unsafeAddr point - -func update[T: char|byte]( - ctx: var ContextCoreAggregateVerify, - publicKey: PublicKey, - message: openarray[T], - domainSepTag: static string): bool = - if not subgroupCheck(publicKey.point): - return false - let Q = hashToG2(message, domainSepTag) # Q = hash_to_point(message_i) - PAIR_BLS12381_another(addr ctx.C1[0], &Q, &publicKey.point) # C1 = C1 * pairing(Q, xP) - return true - -func finish(ctx: var ContextCoreAggregateVerify, signature: Signature): bool = - # Implementation strategy - # ----------------------- - # We are checking that - # e(pubkey1, msg1) e(pubkey2, msg2) ... e(pubkeyN, msgN) == e(P1, sig) - # with P1 the generator point for G1 - # For x' = (q^12 - 1)/r - # - q the BLS12-381 field modulus: 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab - # - r the BLS12-381 subgroup size: 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 - # - # constructed from x = -0xd201000000010000 - # - q = (x - 1)² ((x⁴ - x² + 1) / 3) + x - # - r = (x⁴ - x² + 1) - # - # we have the following equivalence by removing the final exponentiation - # in the optimal ate pairing, and denoting e'(_, _) the pairing without final exponentiation - # (e'(pubkey1, msg1) e'(pubkey2, msg2) ... e'(pubkeyN, msgN))^x == e'(P1, sig)^x - # - # We multiply by the inverse in group GT (e(G1, G2) -> GT) - # to get the equivalent check that is more efficient to implement - # (e'(pubkey1, msg1) e'(pubkey2, msg2) ... e'(pubkeyN, msgN) e'(-P1, sig))^x == 1 - # The generator P1 is on G1 which is cheaper to negate than the signature - - # Accumulate the multiplicative inverse of C2 into C1 - let nP1 = neg(generator1()) - PAIR_BLS12381_another(addr ctx.C1[0], &signature.point, &nP1) - # Optimal Ate Pairing - var v: FP12_BLS12381 - PAIR_BLS12381_miller(addr v, addr ctx.C1[0]) - PAIR_BLS12381_fexp(addr v) - - if FP12_BLS12381_isunity(addr v) == 1: - return true - return false - -# Public API -# ---------------------------------------------------------------------- -# -# There are 3 BLS schemes that differ in handling rogue key attacks -# - basic: requires message signed by an aggregate signature to be distinct -# - message augmentation: signatures are generated over the concatenation of public key and the message -# enforcing message signed by different public key to be distinct -# - proof of possession: a separate public key called proof-of-possession is used to allow signing -# on the same message while defending against rogue key attacks -# -# We implement the proof-of-possession scheme -# Compared to the spec API are modified -# to enforce usage of the proof-of-posession (as recommended) - -const DST = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_" -const DST_POP = "BLS_POP_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_" - -func popProve*(secretKey: SecretKey, publicKey: PublicKey): ProofOfPossession = - ## Generate a proof of possession for the public/secret keypair - # 1. xP = SK * P - # 2. PK = point_to_pubkey(xP) - # 3. Q = hash_pubkey_to_point(PK) - # 4. R = SK * Q - # 5. proof = point_to_signature(R) - # 6. return proof - let pk = publicKey.point.getBytes() # 2. Convert to raw bytes compressed form - result.point = secretKey.coreSign(pk, DST_POP) # 3-4. hash_to_curve and multiply by secret key - -func popProve*(secretKey: SecretKey): ProofOfPossession = - ## Generate a proof of possession for the public key associated with the input secret key - ## Note: this internally recomputes the public key, an overload that doesn't is available. - # 1. xP = SK * P - # 2. PK = point_to_pubkey(xP) - # 3. Q = hash_pubkey_to_point(PK) - # 4. R = SK * Q - # 5. proof = point_to_signature(R) - # 6. return proof - var pubkey {.noInit.}: PublicKey - let ok {.used.} = pubkey.publicFromSecret(secretKey) - assert ok, "The secret key is INVALID, it should be initialized non-zero with keyGen or derive_child_secretKey" - result = popProve(secretKey, pubkey) - -func popVerify*(publicKey: PublicKey, proof: ProofOfPossession): bool = - ## Verify if the proof-of-possession is valid for the public key - ## returns true if valid or false if invalid - # 1. R = signature_to_point(proof) - # 2. If R is INVALID, return INVALID - # 3. If signature_subgroup_check(R) is INVALID, return INVALID - # 4. If KeyValidate(PK) is INVALID, return INVALID - # 5. xP = pubkey_to_point(PK) - # 6. Q = hash_pubkey_to_point(PK) - # 7. C1 = pairing(Q, xP) - # 8. C2 = pairing(R, P) - # 9. If C1 == C2, return VALID, else return INVALID - result = coreVerify(publicKey, publicKey.point.getBytes(), proof, DST_POP) - -func sign*[T: byte|char](secretKey: SecretKey, message: openarray[T]): Signature = - ## Computes a signature - ## from a secret key and a message - ## - ## The SecretKey MUST be directly created via - ## `keyGen` or `derive_child_secretKey` - ## or deserialized from `fromBytes` or `fromHex`. - ## This ensures the precondition that it's not a zero key. - result.point = secretKey.coreSign(message, DST) - -func verify*[T: byte|char]( - publicKey: PublicKey, - proof: ProofOfPossession, - message: openarray[T], - signature: Signature) : bool = - ## Check that a signature is valid for a message - ## under the provided public key. - ## returns `true` if the signature is valid, `false` otherwise. - ## - ## Compared to the IETF spec API, it is modified to - ## enforce proper usage of the proof-of-possession - ## - ## The PublicKey MUST be directly created via - ## `publicFromPrivate` - ## or deserialized from `fromBytes` or `fromHex`. - ## This ensures the precondition that it's not a zero key. - if not publicKey.popVerify(proof): - return false - return publicKey.coreVerify(message, signature, DST) - -func verify*[T: byte|char]( - publicKey: PublicKey, - message: openarray[T], - signature: Signature) : bool = - ## Check that a signature is valid for a message - ## under the provided public key. - ## returns `true` if the signature is valid, `false` otherwise. - ## - ## The proof-of-possession MUST be verified before calling this function. - ## It is recommended to use the overload that accepts a proof-of-possession - ## to enforce correct usage. - ## - ## The PublicKey MUST be directly created via - ## `publicFromPrivate` - ## or deserialized from `fromBytes` or `fromHex`. - ## This ensures the precondition that it's not a zero key. - return publicKey.coreVerify(message, signature, DST) - -func aggregateVerify*( - publicKeys: openarray[PublicKey], - proofs: openarray[ProofOfPossession], - messages: openarray[string or seq[byte]], - signature: Signature): bool = - ## Check that an aggregated signature over several (publickey, message) pairs - ## returns `true` if the signature is valid, `false` otherwise. - ## - ## Compared to the IETF spec API, it is modified to - ## enforce proper usage of the proof-of-possessions - # Note: we can't have openarray of openarrays until openarrays are first-class value types - if publicKeys.len != proofs.len or publicKeys != messages.len: - return false - if not(publicKeys.len >= 1): - return false - - var ctx: ContextCoreAggregateVerify - ctx.init() - for i in 0 ..< publicKeys.len: - if not publicKeys[i].popVerify(proofs[i]): - return false - if not ctx.update(publicKeys[i], messages[i], DST): - return false - return ctx.finish(signature) - -func aggregateVerify*( - publicKeys: openarray[PublicKey], - messages: openarray[string or seq[byte]], - signature: Signature): bool = - ## Check that an aggregated signature over several (publickey, message) pairs - ## returns `true` if the signature is valid, `false` otherwise. - ## - ## The proof-of-possession MUST be verified before calling this function. - ## It is recommended to use the overload that accepts a proof-of-possession - ## to enforce correct usage. - # Note: we can't have openarray of openarrays until openarrays are first-class value types - if publicKeys.len != messages.len: - return false - if not(publicKeys.len >= 1): - return false - - var ctx: ContextCoreAggregateVerify - ctx.init() - for i in 0 ..< publicKeys.len: - if not ctx.update(publicKeys[i], messages[i], DST): - return false - return ctx.finish(signature) - -func aggregateVerify*[T: string or seq[byte]]( - publicKey_msg_pairs: openarray[tuple[publicKey: PublicKey, message: T]], - signature: Signature): bool = - ## Check that an aggregated signature over several (publickey, message) pairs - ## returns `true` if the signature is valid, `false` otherwise. - ## - ## The proof-of-possession MUST be verified before calling this function. - ## It is recommended to use the overload that accepts a proof-of-possession - ## to enforce correct usage. - # Note: we can't have tuple of openarrays until openarrays are first-class value types - if not(publicKey_msg_pairs.len >= 1): - return false - var ctx: ContextCoreAggregateVerify - ctx.init() - for i in 0 ..< publicKey_msg_pairs.len: - if not ctx.update(publicKey_msg_pairs[i].publicKey, publicKey_msg_pairs[i].message, DST): - return false - return ctx.finish(signature) - -func fastAggregateVerify*[T: byte|char]( - publicKeys: openarray[PublicKey], - proofs: openarray[ProofOfPossession], - message: openarray[T], - signature: Signature - ): bool = - ## Verify the aggregate of multiple signatures on the same message - ## This function is faster than AggregateVerify - ## Compared to the IETF spec API, it is modified to - ## enforce proper usage of the proof-of-posession - # 1. aggregate = pubkey_to_point(PK_1) - # 2. for i in 2, ..., n: - # 3. next = pubkey_to_point(PK_i) - # 4. aggregate = aggregate + next - # 5. PK = point_to_pubkey(aggregate) - # 6. return CoreVerify(PK, message, signature) - if publicKeys.len == 0: - return false - if not publicKeys[0].popVerify(proofs[0]): - return false - var aggregate = publicKeys[0] - for i in 1 ..< publicKeys.len: - if not publicKeys[i].popVerify(proofs[i]): - return false - aggregate.point.add(publicKeys[i].point) - return coreVerify(aggregate, message, signature, DST) - -func fastAggregateVerify*[T: byte|char]( - publicKeys: openarray[PublicKey], - message: openarray[T], - signature: Signature - ): bool = - ## Verify the aggregate of multiple signatures on the same message - ## This function is faster than AggregateVerify - ## - ## The proof-of-possession MUST be verified before calling this function. - ## It is recommended to use the overload that accepts a proof-of-possession - ## to enforce correct usage. - # 1. aggregate = pubkey_to_point(PK_1) - # 2. for i in 2, ..., n: - # 3. next = pubkey_to_point(PK_i) - # 4. aggregate = aggregate + next - # 5. PK = point_to_pubkey(aggregate) - # 6. return CoreVerify(PK, message, signature) - if publicKeys.len == 0: - return false - var aggregate = publicKeys[0] - for i in 1 ..< publicKeys.len: - aggregate.point.add(publicKeys[i].point) - return coreVerify(aggregate, message, signature, DST) diff --git a/blscurve/miracl/miracl_min_pubkey_sig_core.nim b/blscurve/miracl/miracl_min_pubkey_sig_core.nim new file mode 100644 index 0000000..153473f --- /dev/null +++ b/blscurve/miracl/miracl_min_pubkey_sig_core.nim @@ -0,0 +1,393 @@ +# Nim-BLSCurve +# Copyright (c) 2018 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) +# at your option. +# This file may not be copied, modified, or distributed except according to +# those terms. + +# Implementation of BLS signature scheme (Boneh-Lynn-Shacham) +# following IETF standardization +# Target Ethereum 2.0 specification after v0.10. +# +# Specification: +# - https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-02 +# - https://github.com/cfrg/draft-irtf-cfrg-bls-signature +# +# Ethereum 2.0 specification targets minimul-pubkey-size +# so public keys are on curve subgroup G1 +# and signatures are on curve subgroup G2 +# +# We reuse the IETF types and procedure names +# Cipher suite ID: BLS_SIG_BLS12381G2-SHA256-SSWU-RO-_NUL_ +# +# Draft changes: https://tools.ietf.org/rfcdiff?url1=https://tools.ietf.org/id/draft-irtf-cfrg-bls-signature-00.txt&url2=https://tools.ietf.org/id/draft-irtf-cfrg-bls-signature-02.txt + +{.push raises: [Defect].} + +import + # third-party + nimcrypto/[hmac, sha2], + stew/endians2, + # internal + ./milagro, ./common + +import ./hash_to_curve + +# Public Types +# ---------------------------------------------------------------------- + +type + SecretKey* = object + ## A secret key in the BLS (Boneh-Lynn-Shacham) signature scheme. + ## + ## This SecretKey is non-zero by construction. + ## + ## This secret key SHOULD be protected against: + ## - side-channel attacks: + ## implementation must perform exactly the same memory access + ## and execute the same step. In other words it should run in constant time. + ## Furthermore, retrieval of secret key data has been done by reading + ## voltage and power usage on embedded devices + ## - memory dumps: + ## core dumps in case of program crash could leak the data + ## - root attaching to process: + ## a root process like a debugger could attach and read the secret key + ## - key remaining in memory: + ## if the key is not securely erased from memory, it could be accessed + ## + ## Long-term storage of this key also requires adequate protection. + ## + ## At the moment, the nim-blscurve library does not guarantee such protections + intVal: BIG_384 + + PublicKey* = object + ## A public key in the BLS (Boneh-Lynn-Shacham) signature scheme. + point: GroupG1 + + Signature* = object + ## A digital signature of a message using the BLS (Boneh-Lynn-Shacham) signature scheme. + point: GroupG2 + + ProofOfPossession* = object + ## A separate public key in the Proof-of-Possession BLS signature variant scheme + point: GroupG2 + + AggregateSignature*{.borrow:`.`.} = distinct Signature + ## An aggregated Signature. + ## With MIRACL backend, there is no bit-level + ## difference from a normal signature + + AggregatePublicKey*{.borrow:`.`.} = distinct PublicKey + ## An aggregated Public Key + ## With MIRACL backend, there is no bit-level + ## difference from a normal PublicKey + +func `==`*(a, b: SecretKey): bool {.error: "Comparing secret keys is not allowed".} + ## Disallow comparing secret keys. It would require constant-time comparison, + ## and it doesn't make sense anyway. + +func `==`*(a, b: PublicKey or Signature or ProofOfPossession): bool {.inline.} = + ## Check if 2 BLS signature scheme objects are equal + return a.point == b.point + +# Primitives +# ---------------------------------------------------------------------- +func subgroupCheck*(P: GroupG1 or GroupG2): bool = + ## Checks that a point `P` + ## is actually in the subgroup G1/G2 of the BLS Curve + var rP = P + {.noSideEffect.}: + rP.mul(CURVE_Order) + result = rP.isInf() + +func publicFromSecret*(pubkey: var PublicKey, seckey: SecretKey): bool = + ## Generates a public key from a secret key + ## Inputs: + ## - SK, a secret integer such that 1 <= SK < r. + ## + ## Outputs: + ## - PK, a public key encoded as an octet string. + ## + ## Returns: + ## - false is secret key is invalid (SK == 0), true otherwise + ## + ## Side-channel/Constant-time considerations: + ## The SK content is not revealed unless its value + ## is exactly 0 + # + # Procedure: + # 1. xP = SK * P + # 2. PK = point_to_pubkey(xP) + # 3. return PK + + + # Always != 0: + # keyGen, deriveChild_secretKey, fromHex, fromBytes guarantee that. + if seckey.intVal.isZilch(): + return false + pubkey.point = generator1() + pubkey.point.mul(secKey.intVal) + return true + +func rawFromPublic*(raw: var array[48, byte], pubkey: PublicKey) {.inline.} = + ## Dump a public key to raw bytes compressed form + raw = pubkey.point.getBytes() + +# IO +# ---------------------------------------------------------------------- +# Serialization / Deserialization +# As I/O routines are not part of the specifications, they are implemented +# in a separate file. The file is included instead of imported to +# access private fields +# +# PublicKeys are infinity checked and subgroup checked on deserialization +# Signatures are subgroup-checked on deserialization + +include ./bls_sig_io + +# Aggregate +# ---------------------------------------------------------------------- +# 2.8. Aggregate (BLSv4) +# +# The Aggregate algorithm aggregates multiple signatures into one. +# signature = Aggregate((signature_1, ..., signature_n)) +# +# Inputs: +# - signature_1, ..., signature_n, octet strings output by +# either CoreSign or Aggregate. +# +# Outputs: +# - signature, an octet string encoding a aggregated signature +# that combines all inputs; or INVALID. +# +# Precondition: n >= 1, otherwise return INVALID. +# +# Procedure: +# 1. aggregate = signature_to_point(signature_1) +# 2. If aggregate is INVALID, return INVALID +# 3. for i in 2, ..., n: +# 4. next = signature_to_point(signature_i) +# 5. If next is INVALID, return INVALID +# 6. aggregate = aggregate + next +# 7. signature = point_to_signature(aggregate) +# +# Comments: +# - This does not require signatures to be non-zero + +template genAggregatorProcedures( + Aggregate: typedesc[AggregateSignature or AggregatePublicKey], + BaseType: typedesc[Signature or PublicKey] + ): untyped = + + func init*(agg: var Aggregate, elem: BaseType) {.inline.} = + ## Initialize an aggregate signature or public key + agg = Aggregate(elem) + + proc aggregate*(agg: var Aggregate, elem: BaseType) {.inline.} = + ## Aggregates an element ``elem`` into ``agg`` + # Precondition n >= 1 is respected + agg.point.add(elem.point) + + proc aggregate*(agg: var Aggregate, elems: openarray[BaseType]) = + ## Aggregates an array of elements `elems` into `agg` + # Precondition n >= 1 is respected even if sigs.len == 0 + for e in elems: + agg.aggregate(e) + + proc finish*(dst: var BaseType, src: Aggregate) {.inline.} = + ## Canonicalize the Aggregate into a BaseType element + dst = BaseType(src) + + proc aggregateAll*(dst: var BaseType, elems: openarray[BaseType]): bool = + ## Returns the aggregate signature of ``elems[0.. GT + # and pairing(R, P) := e(P, R) + + # 3. If signature_subgroup_check(R) is INVALID, return INVALID + if not subgroupCheck(sig_or_proof.point): + return false + # 4. If KeyValidate(PK) is INVALID, return INVALID + if publicKey.point.isInf(): + return false + if not subgroupCheck(publicKey.point): + return false + let Q = hashToG2(message, domainSepTag) + + # pairing(Q, xP) == pairing(R, P) + return multiPairing( + Q, publicKey.point, + sig_or_proof.point, generator1() + ) + +func coreVerifyNoGroupCheck*[T: byte|char]( + publicKey: PublicKey, + message: openarray[T], + sig_or_proof: Signature or ProofOfPossession, + domainSepTag: static string): bool = + ## Check that a signature (or proof-of-possession) is valid + ## for a message (or serialized publickey) under the provided public key + ## + ## PublicKey MUST be non-zero + ## `publicFromSecret`, `fromHex`, `fromBytes` ensure that. + ## + ## Assumes that infinity pubkey and subgroup checks were done + ## for example at deserialization + let Q = hashToG2(message, domainSepTag) + + # pairing(Q, xP) == pairing(R, P) + return multiPairing( + Q, publicKey.point, + sig_or_proof.point, generator1() + ) + +type + ContextCoreAggregateVerify*[DomainSepTag: static string] = object + # Streaming API for Aggregate verification to handle both SoA and AoS data layout + # Spec + # Precondition: n >= 1, otherwise return INVALID. + # Procedure: + # 1. R = signature_to_point(signature) + # 2. If R is INVALID, return INVALID + # 3. If signature_subgroup_check(R) is INVALID, return INVALID + # 4. C1 = 1 (the identity element in GT) + # 5. for i in 1, ..., n: + # 6. If KeyValidate(PK_i) is INVALID, return INVALID + # 7. xP = pubkey_to_point(PK_i) + # 8. Q = hash_to_point(message_i) + # 9. C1 = C1 * pairing(Q, xP) + # 10. C2 = pairing(R, P) + # 11. If C1 == C2, return VALID, else return INVALID + C1: array[AteBitsCount, FP12_BLS12381] + +func init*(ctx: var ContextCoreAggregateVerify) {.inline.} = + ## initialize an aggregate verification context + PAIR_BLS12381_initmp(addr ctx.C1[0]) + +template `&`(point: GroupG1 or GroupG2): untyped = unsafeAddr point + +func update*[T: char|byte]( + ctx: var ContextCoreAggregateVerify, + publicKey: PublicKey, + message: openarray[T]): bool = + if not subgroupCheck(publicKey.point): + return false + let Q = hashToG2(message, ctx.DomainSepTag) # Q = hash_to_point(message_i) + PAIR_BLS12381_another(addr ctx.C1[0], &Q, &publicKey.point) # C1 = C1 * pairing(Q, xP) + return true + +func finish*(ctx: var ContextCoreAggregateVerify, signature: Signature): bool = + # Implementation strategy + # ----------------------- + # We are checking that + # e(pubkey1, msg1) e(pubkey2, msg2) ... e(pubkeyN, msgN) == e(P1, sig) + # with P1 the generator point for G1 + # For x' = (q^12 - 1)/r + # - q the BLS12-381 field modulus: 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab + # - r the BLS12-381 subgroup size: 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 + # + # constructed from x = -0xd201000000010000 + # - q = (x - 1)² ((x⁴ - x² + 1) / 3) + x + # - r = (x⁴ - x² + 1) + # + # we have the following equivalence by removing the final exponentiation + # in the optimal ate pairing, and denoting e'(_, _) the pairing without final exponentiation + # (e'(pubkey1, msg1) e'(pubkey2, msg2) ... e'(pubkeyN, msgN))^x == e'(P1, sig)^x + # + # We multiply by the inverse in group GT (e(G1, G2) -> GT) + # to get the equivalent check that is more efficient to implement + # (e'(pubkey1, msg1) e'(pubkey2, msg2) ... e'(pubkeyN, msgN) e'(-P1, sig))^x == 1 + # The generator P1 is on G1 which is cheaper to negate than the signature + + # Accumulate the multiplicative inverse of C2 into C1 + let nP1 = neg(generator1()) + PAIR_BLS12381_another(addr ctx.C1[0], &signature.point, &nP1) + # Optimal Ate Pairing + var v: FP12_BLS12381 + PAIR_BLS12381_miller(addr v, addr ctx.C1[0]) + PAIR_BLS12381_fexp(addr v) + + if FP12_BLS12381_isunity(addr v) == 1: + return true + return false diff --git a/tests/blst_sha256.nim b/tests/blst_sha256.nim index f4e5e09..91d2e0a 100644 --- a/tests/blst_sha256.nim +++ b/tests/blst_sha256.nim @@ -4,7 +4,7 @@ import # Status nimcrypto/[sha2, hash], # BLSCurve - ../blscurve/bls_backend + ../blscurve/bls_public_exports static: doAssert BLS_BACKEND == BLST