Skip to content

Commit

Permalink
Consolidate internal backend API and implement #90
Browse files Browse the repository at this point in the history
  • Loading branch information
mratsim committed Dec 6, 2020
1 parent 4a0f354 commit 83b549c
Show file tree
Hide file tree
Showing 11 changed files with 1,242 additions and 20 deletions.
6 changes: 3 additions & 3 deletions blscurve.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
16 changes: 4 additions & 12 deletions blscurve/bls_backend.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
29 changes: 29 additions & 0 deletions blscurve/bls_public_exports.nim
Original file line number Diff line number Diff line change
@@ -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
253 changes: 253 additions & 0 deletions blscurve/bls_sig_min_pubkey.nim
Original file line number Diff line number Diff line change
@@ -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

ctx.init(DST)
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)
Loading

0 comments on commit 83b549c

Please sign in to comment.