From 4694b177cb6be21a944cb9cb0fa844632020ed13 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Thu, 5 May 2022 01:41:09 +0300 Subject: [PATCH] Routines for working with threshold signatures Other changes: Cosmetic fixes bringing the project closer to the Status style guide. --- benchmarks/bench_sha256.nim | 1 - benchmarks/platforms/x86.nim | 2 +- blscurve.nimble | 3 + blscurve/bls_backend.nim | 4 +- blscurve/bls_public_exports.nim | 14 +- blscurve/bls_sig_min_pubkey.nim | 10 +- blscurve/blst/bls_sig_io.nim | 6 +- blscurve/blst/blst_min_pubkey_sig_core.nim | 36 +-- blscurve/blst/blst_recovery.nim | 139 +++++++++++ blscurve/eth2_keygen/hkdf_mod_r_blst.nim | 2 +- blscurve/eth2_keygen/hkdf_mod_r_miracl.nim | 2 +- blscurve/miracl/bls_sig_io.nim | 10 +- blscurve/miracl/common.nim | 72 +++--- blscurve/miracl/hash_to_curve.nim | 3 +- .../miracl/miracl_min_pubkey_sig_core.nim | 2 +- tests/eip2333_key_derivation.nim | 2 +- tests/eth2_vectors.nim | 4 +- tests/secret_sharing.nim | 221 ++++++++++++++++++ tests/test_locator.nim | 2 +- 19 files changed, 454 insertions(+), 81 deletions(-) create mode 100644 blscurve/blst/blst_recovery.nim create mode 100644 tests/secret_sharing.nim diff --git a/benchmarks/bench_sha256.nim b/benchmarks/bench_sha256.nim index 1c789c1..2fc713e 100644 --- a/benchmarks/bench_sha256.nim +++ b/benchmarks/bench_sha256.nim @@ -37,5 +37,4 @@ when isMainModule: benchSHA256_nimcrypto(msg5MB, "5MB", 16) benchSHA256_blst(msg5MB, "5MB", 16) - main() diff --git a/benchmarks/platforms/x86.nim b/benchmarks/platforms/x86.nim index 866afba..1783b02 100644 --- a/benchmarks/platforms/x86.nim +++ b/benchmarks/platforms/x86.nim @@ -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 # ------------------------------------------------------- diff --git a/blscurve.nimble b/blscurve.nimble index 3842527..8dd89d5 100644 --- a/blscurve.nimble +++ b/blscurve.nimble @@ -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" diff --git a/blscurve/bls_backend.nim b/blscurve/bls_backend.nim index 2060ff5..22135e7 100644 --- a/blscurve/bls_backend.nim +++ b/blscurve/bls_backend.nim @@ -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 diff --git a/blscurve/bls_public_exports.nim b/blscurve/bls_public_exports.nim index 9e3d26e..a39ae8e 100644 --- a/blscurve/bls_public_exports.nim +++ b/blscurve/bls_public_exports.nim @@ -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, @@ -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 diff --git a/blscurve/bls_sig_min_pubkey.nim b/blscurve/bls_sig_min_pubkey.nim index 94f2c75..2757a9a 100644 --- a/blscurve/bls_sig_min_pubkey.nim +++ b/blscurve/bls_sig_min_pubkey.nim @@ -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 @@ -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 @@ -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): @@ -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): @@ -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 diff --git a/blscurve/blst/bls_sig_io.nim b/blscurve/blst/bls_sig_io.nim index 324af82..c6a0184 100644 --- a/blscurve/blst/bls_sig_io.nim +++ b/blscurve/blst/bls_sig_io.nim @@ -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), @@ -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, diff --git a/blscurve/blst/blst_min_pubkey_sig_core.nim b/blscurve/blst/blst_min_pubkey_sig_core.nim index 1c00dd1..4857aee 100644 --- a/blscurve/blst/blst_min_pubkey_sig_core.nim +++ b/blscurve/blst/blst_min_pubkey_sig_core.nim @@ -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 # ---------------------------------------------------------------------- @@ -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, @@ -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) @@ -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, @@ -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 @@ -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) diff --git a/blscurve/blst/blst_recovery.nim b/blscurve/blst/blst_recovery.nim new file mode 100644 index 0000000..42e6fac --- /dev/null +++ b/blscurve/blst/blst_recovery.nim @@ -0,0 +1,139 @@ +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 = + ## 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. + 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)) diff --git a/blscurve/eth2_keygen/hkdf_mod_r_blst.nim b/blscurve/eth2_keygen/hkdf_mod_r_blst.nim index 367cdeb..a0e0688 100644 --- a/blscurve/eth2_keygen/hkdf_mod_r_blst.nim +++ b/blscurve/eth2_keygen/hkdf_mod_r_blst.nim @@ -187,4 +187,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) diff --git a/blscurve/eth2_keygen/hkdf_mod_r_miracl.nim b/blscurve/eth2_keygen/hkdf_mod_r_miracl.nim index c231b83..24472e0 100644 --- a/blscurve/eth2_keygen/hkdf_mod_r_miracl.nim +++ b/blscurve/eth2_keygen/hkdf_mod_r_miracl.nim @@ -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 diff --git a/blscurve/miracl/bls_sig_io.nim b/blscurve/miracl/bls_sig_io.nim index 8eba237..5e3c234 100644 --- a/blscurve/miracl/bls_sig_io.nim +++ b/blscurve/miracl/bls_sig_io.nim @@ -76,15 +76,15 @@ func fromBytesKnownOnCurve*[T: PublicKey|Signature|ProofOfPossession]( ## The point is known to be on curve: ## - Public key are not checked for infinity points ## - PublicKey, Signature, Proof of possessions are not subgroup checked - result = obj.point.fromBytes(raw) + obj.point.fromBytes(raw) func toHex*(obj: SecretKey|PublicKey|Signature|ProofOfPossession): string {.inline.} = ## Return the hex representation of a BLS signature scheme object ## Signature and Proof-of-posessions are serialized in compressed form when obj is SecretKey: - result = obj.intVal.toHex() + obj.intVal.toHex() else: - result = obj.point.toHex() + obj.point.toHex() func serialize*( dst: var openArray[byte], @@ -94,9 +94,9 @@ func serialize*( ## Returns `true` if the export is successful, `false` otherwise when obj is SecretKey: # TODO: Test if dst is 32 bytes instead of 48 bytes - result = obj.intVal.toBytes(dst) + obj.intVal.toBytes(dst) else: - result = obj.point.toBytes(dst) + obj.point.toBytes(dst) const RawSecretKeySize = MODBYTES_384 # TODO should be 32 diff --git a/blscurve/miracl/common.nim b/blscurve/miracl/common.nim index 40be814..b15f349 100644 --- a/blscurve/miracl/common.nim +++ b/blscurve/miracl/common.nim @@ -77,7 +77,7 @@ proc zero*(a: var BIG_384) {.inline.} = proc bitsCount*(a: BIG_384): int {.inline.} = ## Returns number of bits in big integer ``a``. - result = BIG_384_nbits(a) + BIG_384_nbits(a) proc copy*(dst: var BIG_384, src: BIG_384) {.inline.} = ## Copy value if big integer ``src`` to ``dst``. @@ -134,7 +134,7 @@ proc sqr*(x: FP2_BLS12381): FP2_BLS12381 {.inline.} = func isSquare*(a: FP2_BLS12381): bool {.inline.} = ## Returns true if ``a`` is a square in the FP2 extension field - result = FP2_BLS12381_qr(unsafeAddr a) == 1 + FP2_BLS12381_qr(unsafeAddr a) == 1 proc sqrt*(a: var FP2_BLS12381, b: FP2_BLS12381): bool {.inline.} = ## ``a ≡ sqrt(b) (mod q)`` @@ -160,15 +160,15 @@ proc cmp*(a: BIG_384, b: BIG_384): int {.inline.} = ## Compares two big integers, inputs must be normalized externally ## ## Returns ``-1`` if ``a < b``, ``0`` if ``a == b``, ``1`` if ``a > b`` - result = int(BIG_384_comp(a, b)) + int BIG_384_comp(a, b) proc iszilch*(a: BIG_384): bool {.inline.} = ## Returns ``true`` if ``a`` is zero. - result = bool(BIG_384_iszilch(a)) + bool BIG_384_iszilch(a) proc iszilch*(a: FP_BLS12381): bool {.inline.} = ## Returns ``true`` if ``a`` is zero. - result = (FP_BLS12381_iszilch(unsafeAddr a) == 1) + FP_BLS12381_iszilch(unsafeAddr a) == 1 proc cmp*(a: FP_BLS12381, b: FP_BLS12381): int {.inline.} = ## Compares two FP field members @@ -177,7 +177,7 @@ proc cmp*(a: FP_BLS12381, b: FP_BLS12381): int {.inline.} = var ab, bb: BIG_384 FP_BLS12381_redc(ab, unsafeAddr a) FP_BLS12381_redc(bb, unsafeAddr b) - result = cmp(ab, bb) + cmp(ab, bb) proc cmp*(a: FP2_BLS12381, b: FP2_BLS12381): int {.inline.} = ## Compares two FP2 field members. @@ -228,12 +228,12 @@ proc inf*(a: var ECP_BLS12381) {.inline.} = proc isinf*(a: ECP_BLS12381): bool {.inline.} = ## Check if ``a`` is infinite. var tmp = a - result = (ECP_BLS12381_isinf(addr tmp) != 0) + ECP_BLS12381_isinf(addr tmp) != 0 proc isinf*(a: ECP2_BLS12381): bool {.inline.} = ## Check if ``a`` is infinite. var tmp = a - result = (ECP2_BLS12381_isinf(addr tmp) != 0) + ECP2_BLS12381_isinf(addr tmp) != 0 proc inv*(a: FP2_BLS12381): FP2_BLS12381 {.inline.} = ## Returns the reciprocal copy of ``a`` @@ -246,7 +246,7 @@ proc rhs*(x: FP2_BLS12381): FP2_BLS12381 {.inline.} = proc iszilch*(a: FP2_BLS12381): bool {.inline.} = ## Returns ``true`` if ``a`` is zero. - result = (FP2_BLS12381_iszilch(unsafeAddr a) == 1) + FP2_BLS12381_iszilch(unsafeAddr a) == 1 proc cmov*(a: var FP2_BLS12381, b: FP2_BLS12381, c: bool) {.inline.} = ## Conditional copy of FP2 element (without branching) @@ -267,17 +267,17 @@ proc parity*(a: FP2_BLS12381): int {.inline.} = ## Returns parity for ``a``. var t: BIG_384 FP_BLS12381_redc(t, unsafeAddr a.a) - result = int(BIG_384_parity(t)) + int BIG_384_parity(t) proc parity*(a: FP_BLS12381): int {.inline.} = ## Returns parity for ``a``. var t: BIG_384 FP_BLS12381_redc(t, unsafeAddr a) - result = int(BIG_384_parity(t)) + int BIG_384_parity(t) proc parity*(a: BIG_384): int {.inline.} = ## Returns parity for ``a``. - result = int(BIG_384_parity(a)) + int BIG_384_parity(a) func inf*(a: var ECP2_BLS12381) {.inline.} = ## Makes point ``a`` infinite. @@ -322,31 +322,31 @@ proc mul*(a: var ECP_BLS12381, b: BIG_384) {.inline.} = proc get*(a: ECP2_BLS12381, x, y: var FP2_BLS12381): int {.inline.} = ## Get coordinates ``x`` and ``y`` from point ``a``. - result = int(ECP2_BLS12381_get(addr x, addr y, unsafeAddr a)) + int ECP2_BLS12381_get(addr x, addr y, unsafeAddr a) proc get*(a: ECP_BLS12381, x, y: var BIG_384): int {.inline.} = ## Get coordinates ``x`` and ``y`` from point ``a``. - result = int(ECP_BLS12381_get(x, y, unsafeAddr a)) + int ECP_BLS12381_get(x, y, unsafeAddr a) proc `==`*(a, b: ECP_BLS12381): bool {.inline.} = ## Compare points ``a`` and ``b`` in ECP Group. - result = (ECP_BLS12381_equals(unsafeAddr a, unsafeAddr b) == 1) + ECP_BLS12381_equals(unsafeAddr a, unsafeAddr b) == 1 proc `==`*(a, b: ECP2_BLS12381): bool {.inline.} = ## Compare points ``a`` and ``b`` in ECP2 Group. - result = (ECP2_BLS12381_equals(unsafeAddr a, unsafeAddr b) == 1) + ECP2_BLS12381_equals(unsafeAddr a, unsafeAddr b) == 1 proc `==`*(a, b: FP_BLS12381): bool {.inline.} = ## Compare field elements over FP. - result = (FP_BLS12381_equals(unsafeAddr a, unsafeAddr b) == 1) + FP_BLS12381_equals(unsafeAddr a, unsafeAddr b) == 1 proc `==`*(a, b: FP2_BLS12381): bool {.inline.} = ## Compare field elements over FP2. - result = (FP2_BLS12381_equals(unsafeAddr a, unsafeAddr b) == 1) + FP2_BLS12381_equals(unsafeAddr a, unsafeAddr b) == 1 proc `==`*(a, b: FP12_BLS12381): bool {.inline.} = ## Compare field elements over FP12. - result = (FP12_BLS12381_equals(unsafeAddr a, unsafeAddr b) == 1) + FP12_BLS12381_equals(unsafeAddr a, unsafeAddr b) == 1 proc `$`*(a: BIG_384): string = ## Returns string hexadecimal representation of big integer ``a``. @@ -372,7 +372,7 @@ proc `$`*(r: FP_BLS12381): string = ## Return string representation of ``FP_BLS12381``. var c: BIG_384 FP_BLS12381_redc(c, unsafeAddr r) - result = $c + $c proc `$`*(w: FP2_BLS12381): string = ## Return string representation of ``FP2_BLS12381``. @@ -497,9 +497,9 @@ proc generator2*(): ECP2_BLS12381 {.inline.} = proc isOnCurve*(x, y: FP_BLS12381 or FP2_BLS12381): bool = ## Returns ``true`` if point is on curve or points to infinite. if x.iszilch() and y.iszilch(): - result = true + true else: - result = (sqr(y) == rhs(x)) + sqr(y) == rhs(x) proc toBytes*(a: BIG_384, res: var openArray[byte]): bool = ## Serialize big integer ``a`` to ``res``. Length of ``res`` array @@ -515,9 +515,9 @@ proc toBytes*(a: BIG_384, res: var openArray[byte]): bool = for i in countdown(MODBYTES_384 - 1, 0): res[i] = byte(c[0] and 0xFF) discard BIG_384_fshr(c, 8) - result = true + true else: - result = false + false proc getBytes*(a: BIG_384): array[MODBYTES_384, byte] = ## Serialize big integer ``a`` and return array of bytes. @@ -536,7 +536,7 @@ func fromBytes*(res: var BIG_384, a: openArray[byte]): bool = for i in 0.. 0: res[0] = res[0] or (1'u8 shl 5) - result = true + true else: - result = false + false proc getBytes*(point: ECP2_BLS12381): array[MODBYTES_384 * 2, byte] = ## Serialize ECP2(G2) point ``point`` and return array of bytes. @@ -670,7 +670,7 @@ proc toBytes*(point: ECP_BLS12381, res: var openArray[byte]): bool = if parity == -1: zeroMem(addr res[0], MODBYTES_384) res[0] = res[0] or (1'u8 shl 7) or (1'u8 shl 6) - result = true + true else: var ny = nres(y) var negy = ny.neg() @@ -679,9 +679,9 @@ proc toBytes*(point: ECP_BLS12381, res: var openArray[byte]): bool = if cmp(ny, negy) > 0: res[0] = res[0] or (1'u8 shl 5) res[0] = res[0] or (1'u8 shl 7) - result = true + true else: - result = false + false proc getBytes*(point: ECP_BLS12381): array[MODBYTES_384, byte] = ## Serialize ECP(G1) point ``point`` and return array of bytes. @@ -755,9 +755,7 @@ proc doublePairing*(pointG2_1: GroupG2, pointG1_1: GroupG1, PAIR_BLS12381_double_ate(addr v, unsafeAddr pointG2_1, addr npoint, unsafeAddr pointG2_2, unsafeAddr pointG1_2) PAIR_BLS12381_fexp(addr v) - - if FP12_BLS12381_isunity(addr v) == 1: - result = true + FP12_BLS12381_isunity(addr v) == 1 proc multiPairing*(pointG2_1: GroupG2, pointG1_1: GroupG1, pointG2_2: GroupG2, pointG1_2: GroupG1): bool = @@ -770,6 +768,4 @@ proc multiPairing*(pointG2_1: GroupG2, pointG1_1: GroupG1, PAIR_BLS12381_another(addr r[0], unsafeAddr pointG2_2, unsafeAddr pointG1_2) PAIR_BLS12381_miller(addr v, addr r[0]) PAIR_BLS12381_fexp(addr v) - - if FP12_BLS12381_isunity(addr v) == 1: - result = true + FP12_BLS12381_isunity(addr v) == 1 diff --git a/blscurve/miracl/hash_to_curve.nim b/blscurve/miracl/hash_to_curve.nim index 492f359..3b1cb10 100644 --- a/blscurve/miracl/hash_to_curve.nim +++ b/blscurve/miracl/hash_to_curve.nim @@ -50,7 +50,6 @@ func dstToDSTprime(dst: string): seq[byte] = for ch in dst: result.add byte(ch) result.add byte(dst.len) - result func expandMessageXMD[B: byte|char, len_in_bytes: static int]( H: typedesc, @@ -427,7 +426,7 @@ func mapToCurveG2*(u: FP2_BLS12381): ECP2_BLS12381 = # Hash to a curve isogenous to G2 BLS12-381 let pointPrime = mapToIsoCurveSimpleSWU_G2(u) # 3-isogeny map P'(x', y') to G2 with coordinate P(x, y) - result = isogeny_map_G2(pointPrime.x, pointPrime.y) + isogeny_map_G2(pointPrime.x, pointPrime.y) func clearCofactor*(P: var ECP2_BLS12381) = ## From any point on the elliptic curve of G2 of BLS12-381 diff --git a/blscurve/miracl/miracl_min_pubkey_sig_core.nim b/blscurve/miracl/miracl_min_pubkey_sig_core.nim index 2520a12..0d716fb 100644 --- a/blscurve/miracl/miracl_min_pubkey_sig_core.nim +++ b/blscurve/miracl/miracl_min_pubkey_sig_core.nim @@ -104,7 +104,7 @@ func subgroupCheck*(P: GroupG1 or GroupG2): bool = var rP = P {.noSideEffect.}: rP.mul(CURVE_Order) - result = rP.isinf() + rP.isinf() func publicFromSecret*(pubkey: var PublicKey, seckey: SecretKey): bool = ## Generates a public key from a secret key diff --git a/tests/eip2333_key_derivation.nim b/tests/eip2333_key_derivation.nim index bab8231..a36a28d 100644 --- a/tests/eip2333_key_derivation.nim +++ b/tests/eip2333_key_derivation.nim @@ -9,7 +9,7 @@ import # Standard library - unittest, + std/unittest, # Status libraries stew/byteutils, stint, # Public API diff --git a/tests/eth2_vectors.nim b/tests/eth2_vectors.nim index 88da118..8c24127 100644 --- a/tests/eth2_vectors.nim +++ b/tests/eth2_vectors.nim @@ -12,7 +12,7 @@ import # Standard library - json, strutils, os, unittest, + std/[json, strutils, os, unittest], # Status libraries stew/byteutils, # Public API @@ -367,7 +367,7 @@ when BLS_BACKEND == BLST and compileOption("threads"): var batch: seq[SignatureSet] - proc hash[T: byte|char](message: openArray[T]): array[32, byte] {.noinit.}= + proc hash[T: byte|char](message: openArray[T]): array[32, byte] {.noinit.} = result.bls_sha256_digest(message) proc asArray[T: byte|char](message: openArray[T]): array[32, byte] {.noinit.}= diff --git a/tests/secret_sharing.nim b/tests/secret_sharing.nim new file mode 100644 index 0000000..01cefd7 --- /dev/null +++ b/tests/secret_sharing.nim @@ -0,0 +1,221 @@ +# 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 + std/[unittest, sequtils], + stew/endians2, + # Public API + ../blscurve + +template wrappedTest(desc: string, body: untyped): untyped = + ## Wrap test in a proc to avoid having globals everywhere + ## ballooning the test BSS space usage + ## properly test destructors/GC/try-finally, ... + ## aliasing + ## and optimizations (that don't apply to globals) + test desc: + proc wTest() = + body + wTest() + +type + SecretShare = object + secret: SecretKey + id: ID + + SignsShare = object + sign: Signature + id: ID + +proc keyGen(seed: uint64): tuple[pubkey: PublicKey, seckey: SecretKey] = + var ikm: array[32, byte] + ikm[0 ..< 8] = seed.toBytesLE + let ok = ikm.keyGen(result.pubkey, result.seckey) + doAssert ok + +proc blsIdFromUint32(x: uint32) : ID = + var a: array[8, uint32] = [uint32 0, 0, 0, 0, 0, 0, 0, x] + ID.fromUint32(a) + +proc generateSecretShares(sk: SecretKey, k: int, n: int): seq[SecretShare] = + doAssert k <= n + var originPts: seq[SecretKey] + originPts.add(sk) + for i in 1 ..< k: + originPts.add(keyGen(uint64(42 + i)).seckey) + + for i in uint32(0) ..< uint32(n): + # id must not be zero + let id = blsIdFromUint32(i + 1) + let secret = genSecretShare(originPts, id) + result.add(SecretShare(secret: secret, id: id)) + +proc rekeySecretShares(shares: openArray[SecretShare], k: int): seq[SecretShare] = + doAssert k <= shares.len + var originPts: seq[SecretKey] + # generates a new random polynomial with constant term zero + # Note: The polynomial must be from the same degree as the original one + originPts.add(SecretKey()) + for i in 1 ..< k: + originPts.add(keyGen(uint64(42 + i * 21)).seckey) + + for old in shares: + let secret = genSecretShare(originPts, old.id) + let newSecret = add(old.secret, secret) + result.add(SecretShare(secret: newSecret, id: old.id)) + +proc secrets(shares: openArray[SecretShare]): seq[SecretKey] = + shares.mapIt(it.secret) + +proc ids(shares: openArray[SecretShare]): seq[ID] = + shares.mapIt(it.id) + +proc signs(shares: openArray[SignsShare]): seq[Signature] = + shares.mapIt(it.sign) + +proc ids(shares: openArray[SignsShare]): seq[ID] = + shares.mapIt(it.id) + +proc sign(shares: openArray[SecretShare], data: openArray[byte]): seq[SignsShare] = + for share in items(shares): + result.add(SignsShare(sign: share.secret.sign(data), id: share.id)) + +proc testKeyRecover(origin: SecretKey, shares: openArray[SecretShare]) = + let k = recover(shares.secrets, shares.ids).expect("valid shares") + doAssert origin.toHex() == k.toHex() + +proc testFailKeyRecover(origin: SecretKey, shares: openArray[SecretShare]) = + doAssert recover(shares.secrets, shares.ids).isErr + +proc testWrongKeyRecovery(origin: SecretKey, shares: openArray[SecretShare]) = + let k = recover(shares.secrets, shares.ids).expect("valid shares") + doAssert not (origin.toHex() == k.toHex()) + +proc testSignRecover(pk: PublicKey, + msg: array[8, byte], + shares: openArray[SignsShare]) = + let s = recover(shares.signs, shares.ids).expect("valid shares") + doAssert pk.verify(msg, s) + +proc testFailSignRecover(pk: PublicKey, msg: array[8, byte], shares: openArray[SignsShare]) = + let s = recover(shares.signs, shares.ids).expect("valid shares") + doAssert not pk.verify(msg, s) + +suite "Shamir's Secret Sharing": + var sk: SecretKey + discard sk.fromHex("1b500388741efd98239a9b3a689721a89a92e8b209aabb10fb7dc3f844976dc2") + + var pk: PublicKey + discard pk.publicFromSecret(sk) + + var msg: array[8, byte] = [byte 0,1,2,3,4,5,6,7] + var pts: array[2, SecretKey] = [sk, keyGen(84)[1]] + + wrappedTest "secret keys reconsturction 0/0": + let shares = generateSecretShares(sk, 0, 0) + check len(shares) == 0 + + wrappedTest "secret keys reconsturction 1/1": + let shares = generateSecretShares(sk, 1, 1) + check len(shares) == 1 + check shares[0].secret.toHex() == sk.toHex() + testKeyRecover(sk, shares) + + wrappedTest "secret keys reconsturction n/n": + let n = 3 + let k = n + let shares = generateSecretShares(sk, k, n) + check len(shares) == n + + for k in items(shares.secrets): + check not (k.toHex() == sk.toHex()) + + testKeyRecover(sk, shares) + + testWrongKeyRecovery(sk, [shares[0], shares[1]]) + testWrongKeyRecovery(sk, [shares[0], shares[2]]) + testWrongKeyRecovery(sk, [shares[1], shares[2]]) + testFailKeyRecover(sk, [shares[0], shares[1], shares[1]]) + + + wrappedTest "secret keys reconsturction k/n": + const n = 3 + const k = 2 + let shares = generateSecretShares(sk, k, n) + check len(shares) == n + + testKeyRecover(sk, [shares[0], shares[1]]) + testKeyRecover(sk, [shares[0], shares[2]]) + testKeyRecover(sk, [shares[1], shares[2]]) + + testKeyRecover(sk, shares) + + testWrongKeyRecovery(sk, [shares[0]]) + testWrongKeyRecovery(sk, [shares[1]]) + testWrongKeyRecovery(sk, [shares[2]]) + + wrappedTest "signatures reconstuction 1/1": + let shares = generateSecretShares(sk, 1, 1) + let signs = shares.sign(msg) + check len(signs) == 1 + check sk.sign(msg) == signs[0].sign + testSignRecover(pk, msg, signs) + + wrappedTest "signatures reconstuction n/n": + const n = 3 + const k = n + + let shares = generateSecretShares(sk, k, n) + check len(shares) == n + + let signs = shares.sign(msg) + + testSignRecover(pk, msg, signs) + + testFailSignRecover(pk, msg, [signs[0], signs[1]]) + testFailSignRecover(pk, msg, [signs[0], signs[2]]) + testFailSignRecover(pk, msg, [signs[1], signs[2]]) + + wrappedTest "signatures reconstuction k/n": + const n = 3 + const k = 2 + + let shares = generateSecretShares(sk, k, n) + check len(shares) == n + + let signs = shares.sign(msg) + + testSignRecover(pk, msg, [signs[0], signs[1]]) + testSignRecover(pk, msg, [signs[0], signs[2]]) + testSignRecover(pk, msg, [signs[1], signs[2]]) + + testSignRecover(pk, msg, signs) + testSignRecover(pk, msg, [signs[2], signs[1], signs[0]]) + + testFailSignRecover(pk, msg, [signs[0]]) + testFailSignRecover(pk, msg, [signs[1]]) + testFailSignRecover(pk, msg, [signs[2]]) + + wrappedTest "rekeying k/n": + const n = 3 + const k = 2 + + let shares = generateSecretShares(sk, k, n) + check len(shares) == n + + let signs = shares.sign(msg) + + testSignRecover(pk, msg, signs) + + let newShares = rekeySecretShares(shares, k) + + check len(newShares) == n + + let newSigns = newShares.sign(msg) + testSignRecover(pk, msg, newSigns) diff --git a/tests/test_locator.nim b/tests/test_locator.nim index 871c98e..e46b788 100644 --- a/tests/test_locator.nim +++ b/tests/test_locator.nim @@ -7,7 +7,7 @@ export const ETH2_DIR = currentSourcePath.rsplit(DirSep, 1)[0] / "ef-bls12381-vectors-v0.1.1" proc parseTest*(file: string): JsonNode = - result = json.parseFile(file) + json.parseFile(file) const SkippedTests = [ # For genericity, requires successful deserialization of infinity G1 points,