Skip to content

Commit

Permalink
Routines for working with threshold signatures
Browse files Browse the repository at this point in the history
Other changes:

Cosmetic fixes bringing the project closer to the
Status style guide.
  • Loading branch information
zah committed May 9, 2022
1 parent d1a0a21 commit 1d42842
Show file tree
Hide file tree
Showing 19 changed files with 461 additions and 81 deletions.
1 change: 0 additions & 1 deletion benchmarks/bench_sha256.nim
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,4 @@ when isMainModule:
benchSHA256_nimcrypto(msg5MB, "5MB", 16)
benchSHA256_blst(msg5MB, "5MB", 16)


main()
2 changes: 1 addition & 1 deletion benchmarks/platforms/x86.nim
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ proc cpuName*(): string =
cpuID(eaxi = 0x80000002'i32, ecxi = 0),
cpuID(eaxi = 0x80000003'i32, ecxi = 0),
cpuID(eaxi = 0x80000004'i32, ecxi = 0)])
result = $cast[cstring](addr leaves[0])
$cast[cstring](addr leaves[0])

# Counting cycles
# -------------------------------------------------------
Expand Down
3 changes: 3 additions & 0 deletions blscurve.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ task test, "Run all tests":
# Internal SHA256
test "-d:BLS_FORCE_BACKEND=blst", "tests/blst_sha256.nim"

# Key spliting and recovery
test "-d:BLS_FORCE_BACKEND=blst", "tests/secret_sharing.nim"

when (defined(windows) and sizeof(pointer) == 4):
# Eth2 vectors without batch verify
test "-d:BLS_FORCE_BACKEND=blst", "tests/eth2_vectors.nim"
Expand Down
4 changes: 2 additions & 2 deletions blscurve/bls_backend.nim
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ else:
const BLS_BACKEND* = Miracl

when BLS_BACKEND == BLST:
import ./blst/blst_min_pubkey_sig_core
export blst_min_pubkey_sig_core
import ./blst/[blst_min_pubkey_sig_core, blst_recovery]
export blst_min_pubkey_sig_core, blst_recovery
else:
import ./miracl/miracl_min_pubkey_sig_core
export miracl_min_pubkey_sig_core
14 changes: 11 additions & 3 deletions blscurve/bls_public_exports.nim
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
# This file may not be copied, modified, or distributed except according to
# those terms.

import bls_backend
import
bls_backend

export
BLS_BACKEND, BlsBackendKind,
Expand All @@ -21,15 +22,22 @@ export

# TODO - MIRACL implementation
when BLS_BACKEND == BLST:
export exportUncompressed
export
exportUncompressed,
ID, recover, genSecretShare, fromUint32, add

import bls_sig_min_pubkey

export
sign,
verify, aggregateVerify, fastAggregateVerify
verify,
aggregateVerify,
fastAggregateVerify

when BLS_BACKEND == BLST:
import ./blst/blst_recovery
export blst_recovery

import ./blst/sha256_abi
export sha256_abi

Expand Down
10 changes: 5 additions & 5 deletions blscurve/bls_sig_min_pubkey.nim
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func popProve*(secretKey: SecretKey): ProofOfPossession =
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)
popProve(secretKey, pubkey)

func popVerify*(publicKey: PublicKey, proof: ProofOfPossession): bool =
## Verify if the proof-of-possession is valid for the public key
Expand All @@ -71,7 +71,7 @@ func popVerify*(publicKey: PublicKey, proof: ProofOfPossession): bool =
# 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)
coreVerifyNoGroupCheck(publicKey, pk, proof, DST_POP)

func sign*[T: byte|char](secretKey: SecretKey, message: openArray[T]): Signature =
## Computes a signature
Expand Down Expand Up @@ -134,7 +134,7 @@ func aggregateVerify*(
##
## 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
# Note: we can't have openArray of openArrays until openArrays are first-class value types
if publicKeys.len != proofs.len or publicKeys.len != messages.len:
return false
if not(publicKeys.len >= 1):
Expand All @@ -161,7 +161,7 @@ func 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.
# Note: we can't have openArray of openarrays until openarrays are first-class value types
# 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):
Expand All @@ -185,7 +185,7 @@ func aggregateVerify*[T: string or seq[byte]](
## 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
# Note: we can't have tuple of openArrays until openArrays are first-class value types
if not(publicKey_msg_pairs.len >= 1):
# Spec precondition
return false
Expand Down
6 changes: 3 additions & 3 deletions blscurve/blst/bls_sig_io.nim
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func toHex*(
var bytes{.noinit.}: array[size, byte]
bytes.blst_p2_compress(obj.point)

result = bytes.toHex()
bytes.toHex()

func fromBytes*(
obj: var (Signature|ProofOfPossession),
Expand Down Expand Up @@ -95,8 +95,8 @@ func fromBytes*(
# 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()
if result:
result = bool obj.point.blst_p1_affine_in_g1()

func fromBytesKnownOnCurve*(
obj: var PublicKey,
Expand Down
36 changes: 22 additions & 14 deletions blscurve/blst/blst_min_pubkey_sig_core.nim
Original file line number Diff line number Diff line change
Expand Up @@ -96,17 +96,9 @@ func `==`*(a, b: SecretKey): bool {.error: "Comparing secret keys is not allowed
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
)
)
bool blst_p1_affine_is_equal(a.point, b.point)
else:
result = bool(
blst_p2_affine_is_equal(
a.point, b.point
)
)
bool blst_p2_affine_is_equal(a.point, b.point)

# IO
# ----------------------------------------------------------------------
Expand Down Expand Up @@ -242,7 +234,7 @@ func coreVerify*[T: byte|char](
domainSepTag: static string): bool {.inline.} =
## Check that a signature (or proof-of-possession) is valid
## for a message (or serialized publickey) under the provided public key
result = BLST_SUCCESS == blst_core_verify_pk_in_g1(
BLST_SUCCESS == blst_core_verify_pk_in_g1(
publicKey.point,
sig_or_proof.point,
hash_or_encode = kHash,
Expand Down Expand Up @@ -277,7 +269,7 @@ func coreVerifyNoGroupCheck*[T: byte|char](
return false

ctx.blst_pairing_commit()
result = bool ctx.blst_pairing_finalverify(nil)
bool ctx.blst_pairing_finalverify(nil)

# Core aggregate operations
# Aggregate Batch of (Publickeys, Messages, Signatures)
Expand Down Expand Up @@ -312,7 +304,7 @@ func update*[T: char|byte](
ctx: var ContextCoreAggregateVerify,
publicKey: PublicKey,
message: openArray[T]): bool {.inline.} =
result = BLST_SUCCESS == ctx.c.blst_pairing_chk_n_aggr_pk_in_g1(
BLST_SUCCESS == ctx.c.blst_pairing_chk_n_aggr_pk_in_g1(
publicKey.point.unsafeAddr,
pk_grpchk = false, # Already grouped checked
signature = nil,
Expand All @@ -331,7 +323,7 @@ func commit(ctx: var ContextCoreAggregateVerify) {.inline.} =

func finalVerify(ctx: var ContextCoreAggregateVerify): bool {.inline.} =
## Verify a whole batch of (PublicKey, message, Signature) triplets.
result = bool ctx.c.blst_pairing_finalverify(nil)
bool ctx.c.blst_pairing_finalverify(nil)

func finish*(ctx: var ContextCoreAggregateVerify, signature: Signature or AggregateSignature): bool =
# Implementation strategy
Expand Down Expand Up @@ -562,3 +554,19 @@ func merge*(
func finalVerify*(ctx: var ContextMultiAggregateVerify): bool {.inline.} =
## Verify a whole batch of (PublicKey, message, Signature) triplets.
result = bool ctx.c.blst_pairing_finalverify(nil)

func getScalar*(sk: SecretKey): blst_scalar =
return sk.scalar

func fromFr*(t: typedesc[SecretKey], pt: blst_fr): SecretKey =
var transformed: blst_scalar
transformed.blst_scalar_from_fr(pt)
SecretKey(scalar: transformed)

func getPoint*(sig: Signature): blst_p2_affine =
return sig.point

func fromP2*(s: typedesc[Signature], pt: blst_p2): Signature =
var transformed: blst_p2_affine
transformed.blst_p2_to_affine(pt)
Signature(point: transformed)
146 changes: 146 additions & 0 deletions blscurve/blst/blst_recovery.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import
sequtils,
stew/[results, objects],
./blst_lowlevel,
./blst_min_pubkey_sig_core

export
results

type
ID* = object
## A point on X axis used for key and signature recovery
point: blst_scalar

func fromUint32*(T: type ID, value: array[8, uint32]): T =
result.point.blst_scalar_from_uint32(value)

func `/`(a: blst_fr, b: blst_fr): blst_fr =
var t: blst_fr
blst_fr_eucl_inverse(t, b)
result.blst_fr_mul(a, t)

func toScalar(a: blst_fr): blst_scalar =
result.blst_scalar_from_fr(a)

func `*=`(a: var blst_fr, b: blst_fr) =
a.blst_fr_mul(a, b)

func `*`(a: blst_fr, b: blst_fr): blst_fr=
result.blst_fr_mul(a, b)

func `-`(a: blst_fr, b: blst_fr): blst_fr=
result.blst_fr_sub(a, b)

func `+=`(a: var blst_fr, b: blst_fr) =
a.blst_fr_add(a, b)

func `+`(a: blst_fr, b: blst_fr): blst_fr =
result.blst_fr_add(a, b)

func `*=`(a: var blst_p2; s: blst_fr) =
a.blst_p2_mult(a, s.toScalar(), 255)

func `*`(a: blst_p2; s: blst_fr): blst_p2=
result.blst_p2_mult(a, s.toScalar(), 255)

func `+=`(a: var blst_p2; b: blst_p2) =
a.blst_p2_add(a, b)

func toFr(sk: SecretKey): blst_fr =
result.blst_fr_from_scalar(sk.getScalar)

func toFr(id: ID): blst_fr =
result.blst_fr_from_scalar(id.point)

func toP2(s: Signature): blst_p2 =
result.blst_p2_from_affine(s.getPoint)

func add*(a: SecretKey, b: SecretKey): SecretKey =
var r: blst_fr
blst_fr_add(r, a.toFr, b.toFr)
SecretKey.fromFr(r)

func evaluatePolynomial(cfs: openArray[blst_fr], x: blst_fr): blst_fr =
let count = len(cfs)
if count == 0:
return blst_fr()

if count == 1:
return cfs[0]

# Horner's method
# We will calculate a0 + X(a1 + X(a2 + ..X(an-1 + Xan))
var y = cfs[count - 1]
for i in 2 .. count:
y = y * x + cfs[count - i]

return y

func lagrangeInterpolation[T, U](yVec: openArray[T], xVec: openArray[U]): Result[T, cstring] =
let k = len(xVec)
if k == 0 or k != len(yVec):
return err "invalid inputs"

if k == 1:
return ok yVec[0]

# We calculate L(0) so we can simplify
# (X - X0) .. (X - Xj-1) * (X - Xj+1) .. (X - Xk) to just X0 * X1 .. Xk
# Later we can divide by Xi for each basis polynomial li(0)
var a = xVec[0]
for i in 1 ..< k:
a *= xVec[i]

if a.isZeroMemory:
return err "zero secret share id"

var r: T
for i in 0 ..< k:
var b = xVec[i]
for j in 0 ..< k:
if j != i:
let v = xVec[j] - xVec[i]
if v.isZeroMemory:
return err "duplicate secret share id"
b *= v
# Ith basis polynomial for X = 0
let li0 = (a / b)
r += yVec[i] * li0

ok(r)

func genSecretShare*(mask: openArray[SecretKey], id: ID): SecretKey =
## https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing
##
## In Shamir's secret sharing, a secret is encoded as a n-degree polynomial
## F where the secret value is equal to F(0). Here, F(0) is provided as mask[0].
##
## A key share is generated by evaluating the polynomial in the `id` point.
## Later we can use at least N of these points to recover the original secret.
##
## Furthermore, if we sign some message M with at least K of the secret key
## shares we can restore from them the signature of the same message signed
## with original secret key.
##
## For a more gentle introductiont to Shamir's secret sharing, see also:
##
## https://github.com/dashpay/dips/blob/master/dip-0006/bls_m-of-n_threshold_scheme_and_dkg.md
## https://medium.com/toruslabs/what-distributed-key-generation-is-866adc79620
let cfs = mask.mapIt(it.toFr)
SecretKey.fromFr evaluatePolynomial(cfs, id.toFr)

func recover*(secrets: openArray[SecretKey],
ids: openArray[ID]): Result[SecretKey, cstring] =
## Recover original secret key from N points generated by genSecretShare
let ys = secrets.mapIt(it.toFr)
let xs = ids.mapIt(it.toFr)
ok SecretKey.fromFr(? lagrangeInterpolation(ys, xs))

func recover*(signs: openArray[Signature],
ids: openArray[ID]): Result[Signature, cstring] =
## Recover signature from the original secret key from N signatures
## produced by at least N different secret shares generated by genSecretShare
let ys = signs.mapIt(it.toP2)
let xs = ids.mapIt(it.toFr)
ok Signature.fromP2(? lagrangeInterpolation(ys, xs))
2 changes: 1 addition & 1 deletion blscurve/eth2_keygen/hkdf_mod_r_blst.nim
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,4 @@ func keyGen*(ikm: openArray[byte], publicKey: var PublicKey, secretKey: var Secr

# The cast is a workaround for private field access
cast[ptr blst_scalar](secretKey.addr)[].blst_keygen(ikm, info = "")
result = publicKey.publicFromSecret(secretKey)
publicKey.publicFromSecret(secretKey)
2 changes: 1 addition & 1 deletion blscurve/eth2_keygen/hkdf_mod_r_miracl.nim
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ 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()
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
Expand Down
Loading

0 comments on commit 1d42842

Please sign in to comment.