From f43a97c96e0cf5b577e62f383198816f721a252a Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sun, 8 Dec 2024 23:58:55 +0100 Subject: [PATCH 01/52] [zoo] add generator for secp256k1 --- .../named/constants/secp256k1_generators.nim | 26 +++++++++++++++++++ constantine/named/zoo_generators.nim | 3 ++- 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 constantine/named/constants/secp256k1_generators.nim diff --git a/constantine/named/constants/secp256k1_generators.nim b/constantine/named/constants/secp256k1_generators.nim new file mode 100644 index 000000000..efd17908b --- /dev/null +++ b/constantine/named/constants/secp256k1_generators.nim @@ -0,0 +1,26 @@ +# Constantine +# Copyright (c) 2018-2019 Status Research & Development GmbH +# Copyright (c) 2020-Present Mamy André-Ratsimbazafy +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +import + constantine/named/algebras, + constantine/math/elliptic/ec_shortweierstrass_affine, + constantine/math/io/[io_fields, io_extfields] + +{.used.} + +# Generators +# ----------------------------------------------------------------- +# https://www.secg.org/sec2-v2.pdf page 9 (13 of PDF), sec. 2.4.1 + +# The group G_1 (== G) is defined on the curve Y^2 = X^3 + 7 over the field F_p +# with p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 +# with generator: +const Secp256k1_generator_G1* = EC_ShortW_Aff[Fp[Secp256k1], G1]( + x: Fp[Secp256k1].fromHex"0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", + y: Fp[Secp256k1].fromHex"0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8" +) diff --git a/constantine/named/zoo_generators.nim b/constantine/named/zoo_generators.nim index d462bf6af..d858d9a19 100644 --- a/constantine/named/zoo_generators.nim +++ b/constantine/named/zoo_generators.nim @@ -12,7 +12,8 @@ import ./constants/bls12_381_generators, ./constants/bn254_snarks_generators, ./constants/bandersnatch_generators, - ./constants/banderwagon_generators + ./constants/banderwagon_generators, + ./constants/secp256k1_generators {.experimental: "dynamicbindsym".} From 6434eba37d05a58a480868461f79a3d995b81665 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sun, 8 Dec 2024 23:59:15 +0100 Subject: [PATCH 02/52] [ECDSA] add initial ECDSA signing / verifying implementation --- constantine/ecdsa/ecdsa.nim | 351 ++++++++++++++++++++++++++++++++++++ 1 file changed, 351 insertions(+) create mode 100644 constantine/ecdsa/ecdsa.nim diff --git a/constantine/ecdsa/ecdsa.nim b/constantine/ecdsa/ecdsa.nim new file mode 100644 index 000000000..34950121a --- /dev/null +++ b/constantine/ecdsa/ecdsa.nim @@ -0,0 +1,351 @@ +import + ../hashes/h_sha256, + ../named/algebras, + ../math/io/[io_bigints, io_fields, io_ec], + ../math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul], + ../math/arithmetic, + ../platforms/abstractions, + ../serialization/codecs, # for fromHex and (in the future) base64 encoding + ../mac/mac_hmac # for deterministic nonce generation via RFC 6979 + ../named/zoo_generators, # for generator + ../csprngs/sysrand + +import std / macros # for `update` convenience helper + +type + ## Decides the type of sampler we use for the nonce. By default + ## a simple uniform random sampler. Alternatively a deterministic + ## sampler based on message hash and private key. + NonceSampler* = enum + nsRandom, ## pure uniform random sampling + nsRfc6979 ## deterministic according to RFC 6979 + +# For easier readibility, define the curve and generator +# as globals in this file +const C = Secp256k1 +const G = Secp256k1.getGenerator("G1") + +proc hashMessage(message: string): array[32, byte] = + # Hash a given message + var h {.noinit.}: sha256 + h.init() + h.update(message) + h.finish(result) + +proc toBytes(x: Fr[C] | Fp[C]): array[32, byte] = + let bi = x.toBig() + discard result.marshal(bi, bigEndian) + +proc toDER(r, s: Fr[C]): seq[byte] = + ## Converts the given signature `(r, s)` into a signature in + ## ASN.1 DER encoding. + ## + ## Note that the implementation is not written for efficiency + ## and should be viewed as a convenience tool for the time being. + # Convert signature to DER format + result = @[byte(0x30)] # sequence marker + + # Convert r and s to big-endian bytes + var rBytes = @(r.toBytes()) + var sBytes = @(s.toBytes()) + + # Add padding if needed (if high bit is set) + if (rBytes[0] and 0x80) != 0: + rBytes = @[byte(0)] & rBytes + if (sBytes[0] and 0x80) != 0: + sBytes = @[byte(0)] & sBytes + + # Add integer markers and lengths + let rEncoded = @[byte(0x02), byte(rBytes.len)] & rBytes + let sEncoded = @[byte(0x02), byte(sBytes.len)] & sBytes + + # Total length + let totalLen = rEncoded.len + sEncoded.len + result.add(byte(totalLen)) + + # Add r and s encodings + result.add(rEncoded) + result.add(sEncoded) + +func fromDigest(dst: var Fr[C], src: array[32, byte]): bool {.discardable.} = + ## Convert a SHA256 digest to an element in the scalar field `Fr[Secp256k1]`. + ## The proc returns a boolean indicating whether the data in `src` is + ## smaller than the field modulus. It is discardable, because in some + ## use cases this is fine (e.g. constructing a field element from a hash), + ## but invalid in the nonce generation following RFC6979. + var scalar {.noInit.}: BigInt[256] + scalar.unmarshal(src, bigEndian) + # `true` if smaller than modulus + result = bool(scalar < Fr[C].getModulus()) + dst.fromBig(scalar) + +proc randomFieldElement[FF](): FF = + ## random element in ~Fp[T]/Fr[T]~ + let m = FF.getModulus() + var b: matchingBigInt(FF.Name) + + while b.isZero().bool or (b > m).bool: + ## XXX: raise / what else to do if `sysrand` call fails? + doAssert b.limbs.sysrand() + + result.fromBig(b) + +proc arrayWith[N: static int](val: byte): array[N, byte] = + for i in 0 ..< N: + result[i] = val + +macro update[T](hmac: var HMAC[T], args: varargs[untyped]): untyped = + ## Mini helper to allow HMAC to act on multiple arguments in succession + result = newStmtList() + for arg in args: + result.add quote do: + `hmac`.update(`arg`) + +template round(hmac, input, output: typed, args: varargs[untyped]): untyped = + ## Perform a full 'round' of HMAC. Pre-shared secret is `input`, the + ## result will be stored in `output`. All `args` are fed into the HMAC + ## in the order they are given. + hmac.init(input) + hmac.update(args) + hmac.finish(output) + +proc nonceRfc6979(msgHash, privateKey: Fr[C]): Fr[C] = + ## Generate deterministic nonce according to RFC 6979. + ## + ## Spec: + ## https://datatracker.ietf.org/doc/html/rfc6979#section-3.2 + # Step a: `h1 = H(m)` hash message (already done, input is hash), convert to array of bytes + let msgHashBytes = msgHash.toBytes() + # Piece of step d: Conversion of the private key to a byte array. + # No need for `bits2octets`, because the private key is already a valid + # scalar in the field `Fr[C]` and thus < p-1 (`bits2octets` converts + # `r` bytes to a BigInt, reduces modulo prime order `p` and converts to + # a byte array). + let privKeyBytes = privateKey.toBytes() + + # Initial values + # Step b: `V = 0x01 0x01 0x01 ... 0x01` + var v = arrayWith[32](byte 0x01) + # Step c: `K = 0x00 0x00 0x00 ... 0x00` + var k = arrayWith[32](byte 0x00) + + # Create HMAC contexts + var hmac {.noinit.}: HMAC[sha256] + + # Step d: `K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1))` + hmac.round(k, k, v, [byte 0x00], privKeyBytes, msgHashBytes) + # Step e: `V = HMAC_K(V)` + hmac.round(k, v, v) + # Step f: `K = HMAC_K(V || 0x01 || int2octets(x) || bits2octets(h1))` + hmac.round(k, k, v, [byte 0x01], privKeyBytes, msgHashBytes) + # Step g: `V = HMAC_K(V)` + hmac.round(k, v, v) + # Step h: Loop until valid nonce found + while true: + # Step h.1 (init T to zero) and h.2: + # `V = HMAC_K(V)` + # `T = T || V` + # We do not need to accumulate a `T`, because we use SHA256 as a hash + # function (256 bits) and Secp256k1 as a curve (also 256 big int). + hmac.round(k, v, v) # v becomes T + + # Step h.3: `k = bits2int(T)` + var candidate: Fr[C] + # `fromDigest` returns `false` if the array is larger than the field modulus, + # important for uniform sampling in valid range `[1, q-1]`! + let smaller = candidate.fromDigest(v) + + if not bool(candidate.isZero()) and smaller: + return candidate + + # Step h.3 failure state: + # `K = HMAC_K(V || 0x00)` + # `V = HMAC_K(V)` + # Try again if invalid + hmac.round(k, k, v, [byte 0x00]) + hmac.round(k, v, v) + +proc generateNonce(kind: NonceSampler, msgHash, privateKey: Fr[C]): Fr[C] = + case kind + of nsRandom: randomFieldElement[Fr[C]]() + of nsRfc6979: nonceRfc6979(msgHash, privateKey) + +proc signMessage*(message: string, privateKey: Fr[C], + nonceSampler: NonceSampler = nsRandom): tuple[r, s: Fr[C]] = + ## Sign a given `message` using the `privateKey`. + ## + ## By default we use a purely random nonce (uniform random number), + ## but passing `nonceSampler = nsRfc6979` uses RFC 6979 to compute + ## a deterministic nonce (and thus deterministic signature) given + ## the message and private key as base. + # 1. hash the message in big endian order + let h = hashMessage(message) + var message_hash: Fr[C] + message_hash.fromDigest(h) + + # loop until we found a valid (non zero) signature + while true: + # Generate random nonce + var k = generateNonce(nonceSampler, message_hash, privateKey) + + # Calculate r (x-coordinate of kG) + # `r = k·G (mod n)` + let r_point = k * G + # get x coordinate of the point `r` *in affine coordinates* + let rx = r_point.getAffine().x # element of Fp + ## XXX: smarter way for this? + let r = Fr[C].fromBig(rx.toBig()) + + if bool(r.isZero()): + continue # try again + + # Calculate s + # `s = (k⁻¹ · (h + r · p)) (mod n)` + # with `h`: message hash as `Fr[C]` (if we didn't use SHA256 w/ 32 byte output + # we'd need to truncate to N bits for N being bits in modulo `n`) + k.inv() + var s = (k * (message_hash + r * privateKey)) + # get inversion of `s` for 'lower-s normalization' + var sneg = s # inversion of `s` + sneg.neg() # q - s + # conditionally assign result based on BigInt comparison + let mask = s.toBig() > sneg.toBig() # if true, `s` is in upper half, need `sneg` + ccopy(s, sneg, mask) + + if bool(s == zero): + continue # try again + + return (r: r, s: s) + +proc verifySignature*( + message: string, + signature: tuple[r, s: Fr[C]], + publicKey: EC_ShortW_Aff[Fp[C], G1] +): bool = + ## Verify a given `signature` for a `message` using the given `publicKey`. + # 1. Hash the message (same as in signing) + let h = hashMessage(message) + var e: Fr[C] + e.fromDigest(h) + + # 2. Compute w = s⁻¹ + var w = signature.s + w.inv() # w = s⁻¹ + + # 3. Compute u₁ = ew and u₂ = rw + let u1 = e * w + let u2 = signature.r * w + + # 4. Compute u₁G + u₂Q + let point1 = u1 * G + let point2 = u2 * publicKey + let R = point1 + point2 + + # 5. Get x coordinate and convert to Fr (like in signing) + let x = R.getAffine().x + let r_computed = Fr[C].fromBig(x.toBig()) + + # 6. Verify r_computed equals provided r + result = bool(r_computed == signature.r) + +proc getPrivateKey*(): Fr[C] = + ## Generate a new private key using a cryptographic random number generator. + result = randomFieldElement[Fr[C]]() + +proc toPemPrivateKey(privateKey: Fr[C]): seq[byte] = + # Start with SEQUENCE + result = @[byte(0x30)] + + # Version (always 1) + let version = @[byte(0x02), byte(1), byte(1)] + + # Private key as octet string + let privKeyBytes = privateKey.toBytes() + let privKeyEncoded = @[byte(0x04), byte(privKeyBytes.len)] & @privKeyBytes + + # Parameters (secp256k1 OID: 1.3.132.0.10) + let parameters = @[byte(0xA0), byte(7), byte(6), byte(5), + byte(0x2B), byte(0x81), byte(0x04), byte(0x00), byte(0x0A)] + + # Combine all parts + let contents = version & privKeyEncoded & parameters + result.add(byte(contents.len)) + result.add(contents) + +proc toPemPublicKey(publicKey: EC_ShortW_Aff[Fp[C], G1]): seq[byte] = + # Start with SEQUENCE + result = @[byte(0x30)] + + # Algorithm identifier + let algoId = @[ + byte(0x30), byte(0x10), # SEQUENCE + byte(0x06), byte(0x07), # OID for EC + byte(0x2A), byte(0x86), byte(0x48), # 1.2.840.10045.2.1 + byte(0xCE), byte(0x3D), byte(0x02), byte(0x01), + byte(0x06), byte(0x05), # OID for secp256k1 + byte(0x2B), byte(0x81), byte(0x04), byte(0x00), byte(0x0A) # 1.3.132.0.10 + ] + + # Public key as bit string + let pubKeyBytes = @[ + byte(0x00), # DER BIT STRING: number of unused bits (always 0 for keys) + byte(0x04) # SEC1: uncompressed point format marker + ] & @(publicKey.x.toBytes()) & @(publicKey.y.toBytes()) # x & y coordinates + + let pubKeyEncoded = @[byte(0x03), byte(pubKeyBytes.len)] & pubKeyBytes + + # Combine all parts + let contents = algoId & pubKeyEncoded + result.add(byte(contents.len)) + result.add(contents) + +## NOTE: +## The below procs / code is currently "unsuited" for Constantine in the sense that +## it currently still contains stdlib dependencies. Most of those are trivial, with the +## exception of a base64 encoder. +## Having a ANS1.DER encoder (and maybe decoder in the future) for SEC1 private and +## public keys would be nice to have in CTT, I think (at least for the curves that +## we support for the related operations; secp256k1 at the moment). + +## XXX: Might also need to replace this by header / tail approach to avoid +## stdlib `%`! +import std / [strutils, base64, math] +const PrivateKeyTmpl = """-----BEGIN EC PRIVATE KEY----- +$# +-----END EC PRIVATE KEY----- +""" +const PublicKeyTmpl = """-----BEGIN PUBLIC KEY----- +$# +-----END PUBLIC KEY----- +""" + +proc wrap(s: string, maxLineWidth = 64): string = + ## Wrap the given string at `maxLineWidth` over multiple lines + let lines = s.len.ceilDiv maxLineWidth + result = newStringOfCap(s.len + lines) + for i in 0 ..< lines: + let frm = i * maxLineWidth + let to = min(s.len, (i+1) * maxLineWidth) + result.add s[frm ..< to] + if i < lines-1: + result.add "\n" + +proc toPemFile*(publicKey: EC_ShortW_Aff[Fp[C], G1]): string = + ## Convert a given private key to data in PEM format following SEC1 + # 1. Convert public key to ASN.1 DER + let derB = publicKey.toPemPublicKey() + # 2. Encode bytes in base64 + let der64 = derB.encode().wrap() + # 3. Wrap in begin/end public key template + result = PublicKeyTmpl % [der64] + +proc toPemFile*(privateKey: Fr[C]): string = + ## XXX: For now using `std/base64` but will need to write base64 encoder + ## & add tests for CTT base64 decoder! + ## Convert a given private key to data in PEM format following SEC1 + # 1. Convert private key to ASN.1 DER encoding + let derB = toPemPrivateKey(privateKey) + # 2. Encode bytes in base64 + let der64 = derB.encode().wrap() + # 3. Wrap in begin/end private key template + result = PrivateKeyTmpl % [der64] From e8540b615b3f5594c780da52216fa36f7a6d4dbd Mon Sep 17 00:00:00 2001 From: Vindaar Date: Thu, 12 Dec 2024 19:36:50 +0100 Subject: [PATCH 03/52] [ecdsa] fix imports --- constantine/ecdsa/ecdsa.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/constantine/ecdsa/ecdsa.nim b/constantine/ecdsa/ecdsa.nim index 34950121a..dd5a9fd0a 100644 --- a/constantine/ecdsa/ecdsa.nim +++ b/constantine/ecdsa/ecdsa.nim @@ -6,7 +6,7 @@ import ../math/arithmetic, ../platforms/abstractions, ../serialization/codecs, # for fromHex and (in the future) base64 encoding - ../mac/mac_hmac # for deterministic nonce generation via RFC 6979 + ../mac/mac_hmac, # for deterministic nonce generation via RFC 6979 ../named/zoo_generators, # for generator ../csprngs/sysrand From 41d502d504294e21bead6fe701a23c369d7c7491 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Thu, 12 Dec 2024 19:37:18 +0100 Subject: [PATCH 04/52] [ecdsa] export Secp256k1 as `C` for convenience (We might want to take this out before we merge?) --- constantine/ecdsa/ecdsa.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/constantine/ecdsa/ecdsa.nim b/constantine/ecdsa/ecdsa.nim index dd5a9fd0a..efa0a2330 100644 --- a/constantine/ecdsa/ecdsa.nim +++ b/constantine/ecdsa/ecdsa.nim @@ -22,7 +22,7 @@ type # For easier readibility, define the curve and generator # as globals in this file -const C = Secp256k1 +const C* = Secp256k1 const G = Secp256k1.getGenerator("G1") proc hashMessage(message: string): array[32, byte] = From 2760af446457a45aca26d212de84d72b3bce0f23 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Thu, 12 Dec 2024 19:37:48 +0100 Subject: [PATCH 05/52] [ecdsa] export `toDER` proc --- constantine/ecdsa/ecdsa.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/constantine/ecdsa/ecdsa.nim b/constantine/ecdsa/ecdsa.nim index efa0a2330..67fb9cc4a 100644 --- a/constantine/ecdsa/ecdsa.nim +++ b/constantine/ecdsa/ecdsa.nim @@ -36,9 +36,9 @@ proc toBytes(x: Fr[C] | Fp[C]): array[32, byte] = let bi = x.toBig() discard result.marshal(bi, bigEndian) -proc toDER(r, s: Fr[C]): seq[byte] = +proc toDER*(r, s: Fr[C]): seq[byte] = ## Converts the given signature `(r, s)` into a signature in - ## ASN.1 DER encoding. + ## ASN.1 DER encoding following SEC1. ## ## Note that the implementation is not written for efficiency ## and should be viewed as a convenience tool for the time being. From 9f31c38afdee3544e42d1554273171ae8ac7add9 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Thu, 12 Dec 2024 19:38:05 +0100 Subject: [PATCH 06/52] [ecdsa] use `isZero` instead of old zero comparison --- constantine/ecdsa/ecdsa.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/constantine/ecdsa/ecdsa.nim b/constantine/ecdsa/ecdsa.nim index 67fb9cc4a..80650f339 100644 --- a/constantine/ecdsa/ecdsa.nim +++ b/constantine/ecdsa/ecdsa.nim @@ -212,7 +212,7 @@ proc signMessage*(message: string, privateKey: Fr[C], let mask = s.toBig() > sneg.toBig() # if true, `s` is in upper half, need `sneg` ccopy(s, sneg, mask) - if bool(s == zero): + if bool(s.isZero()): continue # try again return (r: r, s: s) From 5862d43fdf84d4ff223f962dda18e68081d92324 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Thu, 12 Dec 2024 19:38:18 +0100 Subject: [PATCH 07/52] [ecdsa] rename private key generator & add private -> public key --- constantine/ecdsa/ecdsa.nim | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/constantine/ecdsa/ecdsa.nim b/constantine/ecdsa/ecdsa.nim index 80650f339..797efc3ac 100644 --- a/constantine/ecdsa/ecdsa.nim +++ b/constantine/ecdsa/ecdsa.nim @@ -248,10 +248,15 @@ proc verifySignature*( # 6. Verify r_computed equals provided r result = bool(r_computed == signature.r) -proc getPrivateKey*(): Fr[C] = +proc generatePrivateKey*(): Fr[C] = ## Generate a new private key using a cryptographic random number generator. result = randomFieldElement[Fr[C]]() +proc getPublicKey*(pk: Fr[C]): EC_ShortW_Aff[Fp[C], G1] = + ## Derives the public key from a given private key, + ## `privateKey · G` in affine coordinates. + result = (pk * G).getAffine() + proc toPemPrivateKey(privateKey: Fr[C]): seq[byte] = # Start with SEQUENCE result = @[byte(0x30)] From 4fd34e2f6d3e6b4cba32011a90baa89f30ff09f5 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Thu, 12 Dec 2024 19:38:43 +0100 Subject: [PATCH 08/52] [tests] add test cases for ECDSA signature verification Adds a test vector generator and a test case (and test vectors) for verification of OpenSSL generated signatures. --- tests/ecdsa/generate_signatures.nim | 127 +++ tests/ecdsa/t_ecdsa_verify_openssl.nim | 60 ++ .../ecdsa_openssl_signatures_fixed_msg.json | 82 ++ .../ecdsa_openssl_signatures_random.json | 802 ++++++++++++++++++ 4 files changed, 1071 insertions(+) create mode 100644 tests/ecdsa/generate_signatures.nim create mode 100644 tests/ecdsa/t_ecdsa_verify_openssl.nim create mode 100644 tests/ecdsa/testVectors/ecdsa_openssl_signatures_fixed_msg.json create mode 100644 tests/ecdsa/testVectors/ecdsa_openssl_signatures_random.json diff --git a/tests/ecdsa/generate_signatures.nim b/tests/ecdsa/generate_signatures.nim new file mode 100644 index 000000000..096a6578f --- /dev/null +++ b/tests/ecdsa/generate_signatures.nim @@ -0,0 +1,127 @@ +##[ +This is a helper program to generate ECDSA signatures using OpenSSL as a +set of test vectors for our implementation. + +We generate test vectors following these cases: +- same message, different nonces -> different signature +- random message, random nonces + +Further, generate signatures using Constantine, which we verify +with OpenSSL. +]## + +import + constantine/csprngs/sysrand, + constantine/ecdsa/ecdsa, + constantine/named/algebras, + constantine/math/io/[io_bigints, io_fields, io_ec], + constantine/serialization/codecs + +import + std / [os, osproc, strutils, strformat, json] + +type + TestVector = object + message: string # A hex string, which is fed as-is into OpenSSL, not the raw bytes incl 0x prefix + privateKey: string + publicKeyX: string + publicKeyY: string + r: string + s: string + +proc generateMessage(len: int): string = + ## Returns a randomly generated message of `len` bytes as a + ## string of hex bytes. + let len = min(1024, len) # maximum length, to fit into our array + var buf: array[1024, byte] + doAssert sysrand(buf) + # Convert raw bytes to hex + result = buf.toOpenArray[:byte](0, len - 1).toHex() + +proc toHex(s: string): string = + result = s.toOpenArrayByte(0, s.len-1).toHex() + +proc toNormalizedHex(s: string): string = + ## Takes a string of raw bytes, removes additional empty bytes + ## (length of 33 bytes) or adds bytes (length < 32 bytes) and + ## then converts them to a hex string. + var s = s + if s.len == 33: + doAssert s[0] == '\0', "No, got: " & $s[0] + s = s[1 ..< s.len] + let toAdd = 32 - s.len + if toAdd < 0: + raiseAssert "Invalid input of length: " & $s.len & ": " & s + let prefix = repeat('\0', toAdd) + s = prefix & s + doAssert s.len == 32 + result = s.toOpenArrayByte(0, s.len-1).toHex() + +proc parseSignature(derSig: string): tuple[r, s: string] = + ## Parses a signature given in raw ASN.1 DER (SEC1) + ## raw bytes to the individual `r` and `s` elements. + ## The elements `r` and `s` are returned as hex strings. + ## + ## Note: the `r` or `s` values are 33 bytes long, if the leading + ## bit is `1` to clarify that the number is positive (a prefix + ## `0` byte is added). In our case we just parse 32 or 33 bytes, + ## because we don't care about a leading zero byte. + doAssert derSig[0] == '\48' # SEQUENCE + doAssert derSig[1] in {'\67', '\68', '\69', '\70'} # 68-70 bytes long (depending on 0, 1, 2 zero prefixes) + doAssert derSig[2] == '\02' # INTEGER tag + let lenX = ord(derSig[3]) + doAssert lenX <= 33, "Found length: " & $lenX # Length of integer, 32 or 33 bytes + let toX = 4 + lenX + let r = derSig[4 ..< toX] + doAssert derSig[toX] == '\02' # INTEGER tag + let lenY = ord(derSig[toX + 1]) + doAssert lenY <= 33, "Found length: " & $lenX # Length of integer, 32 or 33 bytes + let toY = toX + 2 + lenY + let s = derSig[toX + 2 ..< toY] + doAssert toY == derSig.len + # Convert raw byte strings to hex strings. + result = (r: r.toNormalizedHex(), s: s.toNormalizedHex()) + +proc generateSignatures(num: int, msg = ""): seq[TestVector] = + ## Generates `num` signatures. + result = newSeq[TestVector](num) + let dir = getTempDir() + # temp filename for private key PEM file + let privKeyFile = dir / "private_key.pem" + let sigFile = dir / "message.sig" + for i in 0 ..< num: + let msg = if msg.len > 0: msg else: generateMessage(64) # 64 byte long messages + let privKey = generatePrivateKey() + let pubKey = getPublicKey(privKey) + + # convert private key to a PEM file and write as temp + writeFile(privKeyFile, toPemFile(privKey)) + + # NOTE: We treat the *hex string* as the message, not the raw bytes, + # including the `0x` prefix! + let cmd = &"echo -n '{msg}' | openssl dgst -sha256 -sign {privKeyFile} -out {sigFile}" + let (res, error) = execCmdEx(cmd) + + # extract raw signature + let (r, s) = sigFile.readFile.parseSignature + let vec = TestVector(message: msg, + privateKey: privKey.toHex(), + publicKeyX: pubKey.x.toHex(), + publicKeyY: pubKey.y.toHex(), + r: r, + s: s) + result[i] = vec + + # sanity check here that our data is actually good. Sign + # and verify with CTT & verify just parsed OpenSSL sig + let (rCTT, sCTT) = msg.signMessage(privKey) + doAssert verifySignature(msg, (r: rCTT, s: sCTT), pubKey) + doAssert verifySignature(msg, (r: Fr[C].fromHex(r), s: Fr[C].fromHex(s)), pubKey) + +# 1. generate 100 signatures with random messages, private keys and random nonces +let vecs1 = generateSignatures(100) +# 2. generate 10 signatures for the same message +let vecs2 = generateSignatures(10, "Hello, Constantine!") + +writeFile("testVectors/ecdsa_openssl_signatures_random.json", (% vecs1).pretty()) +writeFile("testVectors/ecdsa_openssl_signatures_fixed_msg.json", (% vecs2).pretty()) diff --git a/tests/ecdsa/t_ecdsa_verify_openssl.nim b/tests/ecdsa/t_ecdsa_verify_openssl.nim new file mode 100644 index 000000000..44ec67bbc --- /dev/null +++ b/tests/ecdsa/t_ecdsa_verify_openssl.nim @@ -0,0 +1,60 @@ +##[ +This test case verifies signatures generated by OpenSSL in the +`generate_signatures.nim` script. +]## + +import + constantine/csprngs/sysrand, + constantine/ecdsa/ecdsa, + constantine/named/algebras, + constantine/math/elliptic/[ec_shortweierstrass_affine], + constantine/math/io/[io_bigints, io_fields, io_ec], + constantine/serialization/codecs + +import + std / [os, osproc, strformat, json, unittest] + +type + TestVector = object + message: string # A hex string, which is fed as-is into OpenSSL, not the raw bytes incl 0x prefix + privateKey: string + publicKeyX: string + publicKeyY: string + r: string + s: string + + TestVectorCTT = object + message: string + privateKey: Fr[C] + publicKey: EC_ShortW_Aff[Fp[C], G1] + r: Fr[C] + s: Fr[C] + +proc parseSignatureFile(f: string): seq[TestVector] = + result = f.readFile.parseJson.to(seq[TestVector]) + +proc parseTestVector(vec: TestVector): TestVectorCTT = + result = TestVectorCTT( + message: vec.message, + privateKey: Fr[C].fromHex(vec.privateKey), + publicKey: EC_ShortW_Aff[Fp[C], G1].fromHex(vec.publicKeyX, vec.publicKeyY), + r: Fr[C].fromHex(vec.r), + s: Fr[C].fromHex(vec.s)) + + +suite "ECDSA over secp256k1": + test "Verify OpenSSL generated signatures from a fixed message (different nonces)": + let vecs = parseSignatureFile("testVectors/ecdsa_openssl_signatures_fixed_msg.json") + + for vec in vecs: + let vctt = vec.parseTestVector() + # verify the signature + check verifySignature(vctt.message, (r: vctt.r, s: vctt.s), vctt.publicKey) + + test "Verify OpenSSL generated signatures for different messages": + let vecs = parseSignatureFile("testVectors/ecdsa_openssl_signatures_random.json") + + for vec in vecs: + let vctt = vec.parseTestVector() + # verify the signature + check verifySignature(vctt.message, (r: vctt.r, s: vctt.s), vctt.publicKey) diff --git a/tests/ecdsa/testVectors/ecdsa_openssl_signatures_fixed_msg.json b/tests/ecdsa/testVectors/ecdsa_openssl_signatures_fixed_msg.json new file mode 100644 index 000000000..cca681dfb --- /dev/null +++ b/tests/ecdsa/testVectors/ecdsa_openssl_signatures_fixed_msg.json @@ -0,0 +1,82 @@ +[ + { + "message": "Hello, Constantine!", + "privateKey": "0x256bd879af387ca45f9164b1d4882f68570e0e9cedae60a5185a405b4aeb4697", + "publicKeyX": "0x45a41d181c6dff19b6275a4639cde0ae581f4fb2ccbd82663fe9ff9ad6896f3e", + "publicKeyY": "0x31039cb9d47f142d2f1a7a928329b89d94040f23668f70772664259decbdb017", + "r": "0xed77f05cd805bab09c214f9c913c776d0afe3db8e63d1dcb83819828d61ac5b7", + "s": "0xa3ea2322400e727256ea054648ac88e819b28ae6839728b53081225dad8ffb48" + }, + { + "message": "Hello, Constantine!", + "privateKey": "0x2d8f7346f506f998e2cddaeb930821d5cec2425f1b9f8f9a9cf2fe6d2d12f7e7", + "publicKeyX": "0xa852f5361011655444fa29e0b00322d7df3b00279d5bd87c3a9208b4d08bebd8", + "publicKeyY": "0xc4ef7ab0b5b0fe064eefa7f61607d6eaab3fac906a701826efe46b9e7c8958a4", + "r": "0x994e37598cc2e01abf006159d36dafeae859c1341aaff98a6a253e150536fe30", + "s": "0xa032c45d726c75ebf50606900ef363fa485d6be29a9dca6a89e50ec10378e2d2" + }, + { + "message": "Hello, Constantine!", + "privateKey": "0x22a8c89261fe9adcfbf19579bb3ea69267d04580ea7efbe545ca8a85bc360c0a", + "publicKeyX": "0xea6587e76c9a5eb035a9628288540babefd6a2e3e1daafe93741f0e3d3c1f8c0", + "publicKeyY": "0x9fda7e50aff389e652c95fb0d53bc11937f285e5b56f1d59aba2a86509a3603a", + "r": "0x0b1077451d938aa42eecdd53b3380edd2680e01e5ebad0bff5a62a2701d10fcd", + "s": "0x6eb29bb61d2a9d8097b18aecf1a6c0007c4a1bd9b21f8f3e98e58a32279e9827" + }, + { + "message": "Hello, Constantine!", + "privateKey": "0x1fc6c933b4f47451cc09db8071792ef5feda71113d1c5809d5e0d5044dfe9c31", + "publicKeyX": "0xc0ab9dbed4534b090865432b1ddcea4844ef504aedd69a66afe80fa2a4c41ab4", + "publicKeyY": "0xd8db593e93eb432f226c433acb9f1476a3dcf0dc7be2ce03f9e8496c3d0214b3", + "r": "0x263fe33a9faf349a8fb2278a4c895411752a1157cf75d4122e25f159cac5787e", + "s": "0x3b9fc9b21fe5e1acaaab5bbb13dde0a32420e032316cab0738a3c9ef5add4ddc" + }, + { + "message": "Hello, Constantine!", + "privateKey": "0xddba67e7d19fd7e23a3fcba09fdac0d8ef04abdd0709b68e4160a0adbbd0714d", + "publicKeyX": "0x57c8ff99412c0bea729c77820fd695ccd5aae174d5a77bb01bb388b5f9a29339", + "publicKeyY": "0x36319a8016c96866b301ac23fb7dd194e2513c8154b84e7de62703672f4589bd", + "r": "0xfc01b7c9c9ef3c876cafe18850eb6f19915e48762432ba1f8bc0d89a467b2225", + "s": "0x308f8e317f4bb38b41abeaafc19e46a392c1a0f8a2d3da73bfb3243ade59f114" + }, + { + "message": "Hello, Constantine!", + "privateKey": "0x75061b24004f09bc201197d3dfbdd31702aa851d423028d174b02ac9c6ee0e6b", + "publicKeyX": "0xdc56f5ba2301ff776006935d446aca77be29c7ecf80b371db72576a0d105bc4a", + "publicKeyY": "0x0369ae2f661f2142477dafd4c60fc79e88a986311f78c5ab31a7257504e0ede8", + "r": "0xa7f2e685681f74ccb2ea58dcdde123b40e980c1703900200d34fa45a816900db", + "s": "0x8e22971d7270db3e70ddc2897a7f087548c1f1d77a65517714866b4f4da95b78" + }, + { + "message": "Hello, Constantine!", + "privateKey": "0xbac0b31a6416866f012652846f958646afc253e4491465b6b405fd1ea2d00f7e", + "publicKeyX": "0x5ac12a3d7d10a4efcf8d39b104d9427a97d4ee92f117670d30c710ad2619926f", + "publicKeyY": "0xe00c161d99c692e918e9d88d644664bfcdd5043e23d64b7542c191fb67852c7f", + "r": "0x4639db7be1b2fb9cd438fa0193e8f9b7f547fa73a21a0fed3e5570d4bc4f79a5", + "s": "0x8f0763583c1cefd1a94ea3ae9d567529a3a6e9039d5e2c8c90fe3757a7015ea8" + }, + { + "message": "Hello, Constantine!", + "privateKey": "0x27bc1016ca234abb27a5ed4d155294221d0ca091567022a6610440291930bc9d", + "publicKeyX": "0xfc0fb22385d2f44c0baf3ebb1e7ecd1d9c906a439a0d00b226f7a204cc1c0d0d", + "publicKeyY": "0x2db3f2b91a27f5c8b4d1b693b6d84540f6659e2f1a737db752806907b58ecec9", + "r": "0xfbc73650d98de44d17fc8a441105318c8eb580b82deca2c1ce1584b5d9580758", + "s": "0x3c10dee8c45d5dad238d97b724556d66bc1ec9deeda784fddfac2e96a0640f29" + }, + { + "message": "Hello, Constantine!", + "privateKey": "0x9191e0d56713ab93454cb0f3f8a9881d0a53f6da6c285b452e4d5f4847c64b84", + "publicKeyX": "0x4f6df1f0acf0fd68b74a4d41920718ab84fed5e0642c663407245be1c7914629", + "publicKeyY": "0xb0532e2807c075335b27c93b3218178669e4b3705b4d784493df9784bdbdfeb4", + "r": "0x503198ef113526713e6b18628742ba19ad8817e32b6dd1890b3606e9e382da8e", + "s": "0x915f30acb77e07c6923c34482e09e6a5f34c0cf30c8ecc4dbb81fa066029a94e" + }, + { + "message": "Hello, Constantine!", + "privateKey": "0x99a913bb0e8a74da4edca08307c15b0b81d8fad41825fe6fc2dce721e0dcd617", + "publicKeyX": "0x53e22946172e0a73d61733c563377d8d8c8fe8d3446b79cd6883e25410715882", + "publicKeyY": "0xadcbcbbe249cd0e786ba6d374b64c66b32d7a917eb48c4dd3e1556054dc95430", + "r": "0x5e1ce1b3c21f6d616fecd54718356a704e89855da529f9163ff68ae2425ee607", + "s": "0xcf228448c3327191e1dcbd8da97f22f432c9d4b74335d8271d53753bbbcbe2d3" + } +] \ No newline at end of file diff --git a/tests/ecdsa/testVectors/ecdsa_openssl_signatures_random.json b/tests/ecdsa/testVectors/ecdsa_openssl_signatures_random.json new file mode 100644 index 000000000..f5aaad2a6 --- /dev/null +++ b/tests/ecdsa/testVectors/ecdsa_openssl_signatures_random.json @@ -0,0 +1,802 @@ +[ + { + "message": "0x367cc3d190e540ff9cd4d735bac4fa67b857db2825e0fc6d3853ce38667e3a288a2792cb18cc8c03058390f827505b0d8a6b1ac46ce339d3772736446e26a381", + "privateKey": "0x13c0331fbbf9b07fb5465efb83712cca5bcf78433bd7fcc41162738e31f500c6", + "publicKeyX": "0xe63412da39d91668bea8974674235fe7d309f13b4fa4d65c05792f92a4b1d1f2", + "publicKeyY": "0x878ea9cde512c28a07fbf99c519ae0c264543eff2b456db0e4f404ee11783742", + "r": "0x7b0b48e29f15fabc9d9c44bded82de8e2655be839140f86292ca5dff9be1ad07", + "s": "0x6ff1e32a4e12bc69ab48d36e5572eddf7f0281f7a879fc94b5059d8b630baec0" + }, + { + "message": "0xf2a67da53d543760eb054441489bf0f6e45991326cf91fcfdd2b85b1503c3a9504412b925d4941cd74f21cc9bcb8acfc6d39b274896bd237d68dc799f013fd5d", + "privateKey": "0x9eaa29cbda0f1045c8b631411b55ccef3c15df776edf91e7826346f0c2230064", + "publicKeyX": "0xa3712ccab8462673c0678f4dc733b5cb9051632de977684a8c3e94c1bdc10e33", + "publicKeyY": "0x2293af0ea3836d337eb83c9daa8ecf22bf94ceb618724d3fb043082044b4a971", + "r": "0xfc8157cba0c8851ae7093fca9c6a33e1df8bead01e0ebb517a2a2c472322ae9e", + "s": "0x8de92c2e2f537870e71d80f6842359fa52680a341db2bb2e48c5941cebf56b7d" + }, + { + "message": "0xd7e4e1306dd9ac397ed41d9775bd6769430f1827280b42cbe7c54bed785deb47bd4c5b54eef76906c75eec4d3665dc05d4b7d40ccaa73baa464342117d8cf330", + "privateKey": "0x5cf63abfb24b5c736a60f5c616e34fe1b3c48d30e6b0d68e28fa1f7453dac2e8", + "publicKeyX": "0xdf3da0770e33c11a9405a8bcf878d39bd0312ca22f9e07ad3f28446935871678", + "publicKeyY": "0x99c3a77bdc3ceeef5845a8d536262b3e144ee01df4d125922985f1e3c787e50b", + "r": "0xc4b6762fd01d007ba147bddd0e065e0d5fb830a729e2371e9cc58f6dcb316626", + "s": "0xcf9324eb930a3c9c22e6ec2e5d1e0a0ec36c7b76327f3687b4058110068f05ee" + }, + { + "message": "0xa65e4f75b3ed99bf3f0c80b14832fd7204628c650835ae1a3e1c70c3e5bb3b995153b447a31b5a0e9218de2f77025d2dc94903272a94c3774146eaa797d38f2f", + "privateKey": "0x0084b8cb18ee7e3ec638c62fa75135d828193537e0dd582e1070d69d8c60fb7f", + "publicKeyX": "0xa4ab4354894a73d534e143f267cc6d3b957b7e13843e5338d69e8113609b3af6", + "publicKeyY": "0xb76d6655504a55c1fed48e894807962f827a1adaa40e01ee065f9bc884d03583", + "r": "0x4838d2686541e6c1d38c2359853f4af2a7b8a31bc89d7521879ce7e2c6bdefd3", + "s": "0x97608cda0f8fa16f8e44e39c901a2d989b2053b1d7e97ff8ea38191cc73d57ab" + }, + { + "message": "0xe15ac423b2873f3849ecf692f117d00c2e7671309888cde84e961dfce2de55c0684fd54bb0bfa79aa15f5c33f8f24196a89e73e7d87f6b2a5f88d89591833729", + "privateKey": "0xd509afc7fd7fd30ba02fb8bb21a8ed9130821df8df3f69ae1fa401b21b17cf58", + "publicKeyX": "0x6a65a1048de25997f5c6ed4cb4a45ee48cb73e4da5836bfdeda0fbb182973f42", + "publicKeyY": "0x78e4fde5c2325c5ffe30d4db7aab153af936efe559e0225436169e0b4ba323a7", + "r": "0x30c801c0bad5b937ca652d64ec9e007453ad34b78d2d66f0028265892f78eac0", + "s": "0x549ac6bf4e0e6dc21c1bc903473a7b60c8bd788bbec9b07942f2badc4cc1d759" + }, + { + "message": "0xba726418e5b1588e6570579e35b4fbf549fa8e43fa892b6358e86b4486729757ac8fb4aa8eb72504ce1fc996ea242b0c3dc9e3e00ed512cd1f360764b0bb97df", + "privateKey": "0xd553c549cca33b3ae4277705698ebbd7d9bc882205e151f215a863b1d2508591", + "publicKeyX": "0x506344bb6653c4732310f6f760447393d8fa9481854bfffecce100d3b6416304", + "publicKeyY": "0x835528d40fc82d5c92285e6a99e73743b838f2067f65ea666ac35cd2ccc59f01", + "r": "0xf17ada026075c60ad2545cf658a175698b8d2fbff303b8e1a0d7628d428d3da6", + "s": "0x8e8ab2b7cc349d994aa02e202ee14a471ef0982b1d5ff05c934ae78c98b87243" + }, + { + "message": "0xd37fb73aca3b670b8256a2d52881f26c0a0c7115d6218be4f21f898d09f55491383352db1b508cef68e09a1bc5ba6d359f8abcd7df09d308cbfa9787be18a03d", + "privateKey": "0x3cc765df0c7e1c2dfd980e91acae4f38d9d7db114daa9b1200aeefb6401c0767", + "publicKeyX": "0x1cf5e56581b425087285975be03a7031dd4274deb66adc059047c1eba1b1851b", + "publicKeyY": "0x86b24e8b2ff877fc1f332d42fae5aba61566afd722649f95fcc2061078ea8ec5", + "r": "0x94a2c50874a9cfa4f0d94d9230bfda8b4b89d8a9aefaec5f0159d5af8debb5c4", + "s": "0x2f734bed280eca292fb33439c96caf354cd7f0b3d1c56f63a23593cdeefed263" + }, + { + "message": "0x9262e4521d8c70583045a6c1080ebb5cc259284c30d48cb660fe6805825d6e36ca49f0ac32b1d8123e5979b8e3027fdb9d313581ac6386920377cfde06604576", + "privateKey": "0xf5b232ee4a965efb2b2fbb0ad7961ea49af41e453934bed618b1247f0c812c5b", + "publicKeyX": "0x51a85cb912a06ab465eae370f26dc3546f7a9061d81215202e5576499a9975f4", + "publicKeyY": "0x26940e5de9d6e8907283e80f605edbe0bdab33aa284208b4ff97bd59fd6f19d4", + "r": "0x595face4049da761574ec2c47e6da72d0d7dfc52e9f4f0532769b4e6673ed9a5", + "s": "0x9d385f34c94f262d50b655058967cd568812eaeae24aa3de244926989eef9ff4" + }, + { + "message": "0xc2a4129e68511ed9f401144ad80ed1c2601d62d597848be00e5ff825a98fa7e9687277a02c79b2e0d1a0d2efc7170a97576721870d58f5a01fee18e8be7c6d8d", + "privateKey": "0xda809319d6fddefbfa1e90ed9d896234248561b66cab7c9d263cb865a96575b5", + "publicKeyX": "0x88b3f391acc1aa80858a90ab4cbb478fbfccafe6e1fcb891cc96836dfe44dc2d", + "publicKeyY": "0xfb885b73013f73cc95e2a85ded6932214b5f0782e86677ddf91b169b520538b5", + "r": "0xf7f58c255b446404957170643b391a6c3a64d4d7ce4aafc0be231be7938c7b4a", + "s": "0x44d62cae251555dc820d0207fdc9886142f75aeb1476663f88bd4e523182c6cc" + }, + { + "message": "0x09f2413bcfcb13b14b9649f1da5df5faa258c1d0b4642ba5cfe91df85d53cd461f61ed8b58c945972e8e7997166790beb7e7094915f4c0c17ad3826b44123e8a", + "privateKey": "0x2bd81ffdf104470e134a504c190cf66a7e7f1592a3710c29e28a0d6d58e6773d", + "publicKeyX": "0xea6d3d224a0813d3972ba20d2ac4bca67098495296813dda89eedc332f57cc9a", + "publicKeyY": "0x635e7a7b64c60b4f3a6cf164f0ca02857e4ed0f02487d6496dd4c8443aab677c", + "r": "0xb02153a485ca591c7522b94394a8c0955ebf9f14d57fecd4ed9e173ab487acd4", + "s": "0x261fb9f410553fa6deabe5e5dc471f80b891a6e5ff1d315755f0a477d7027383" + }, + { + "message": "0xe7fb72332c946102980f1914a72d03734d38defa8c8be772b3dbfaabdfa964660093a50a6822a85d4449ca69c0c59eaa7955b1118cf0d1c88f4cb4865d7ff227", + "privateKey": "0xb4431765fc32428b6754a30f5002662f03d6650a65333a196b315309010462d5", + "publicKeyX": "0x03fe900d02d236243865cc9c160716e74314abbaf2bb145cd17d4f118e92fa33", + "publicKeyY": "0x96a7c5ced3876eb7d184418d941bcec78ef5996323eab1e2c2fca031a38bac37", + "r": "0x6111657c010708da3632c63768e9483d027a4fb48f647c75ad3132ae10885b52", + "s": "0xe35cc7c28b295e0c4a8a36de3311af5bab18d2251a96c3b35d32f00d0d1312e5" + }, + { + "message": "0x4f5b088f418f92a91ebc048e69eaf360059fd0fbab4076066c08a509387061ef0db7ea6e9323df29461c5a360a48ff4b616898560dbec038ea5c627dea4f6395", + "privateKey": "0xd22c61f4cbe78c78a3c2c687a22c8632db2adcec0cb81ceb05ece51f4e3e41cc", + "publicKeyX": "0x05c164901da4af12d3543776a7e7a7b4082382748cea5525be8ab0e13cbd7611", + "publicKeyY": "0xe13441356a0ac62813633d455359fd42e80b2b782b786bcaaf59c42a284acfea", + "r": "0x31186a5d59486054d6d78b1559302bc5f2468f36f3e8dbe6b01fe6a761ab3aaf", + "s": "0x0b1027f5c1b08eccc58d87f357be1e246293d6e2d3c56c49032d1cc63a8786bc" + }, + { + "message": "0xadf1b5a06f70e9cf9e50f6f45c70771cd1539ab4efdaf7421485e7aa1baeaec48ba6f86708132723467aac5b64eb11b71c1cfd3668133adbb8742b9bf7c633b2", + "privateKey": "0xa2e6cdd434abb3df65a62cc49d5f9bf65a8735b3294bfa2cf34e7eacc8337017", + "publicKeyX": "0xf7b49acb8b929ba301f18e1dd000796682f7579d301330bf194a89c422e0bd98", + "publicKeyY": "0x50c8252c4076e440064a51e5a58f4454240ce74614a7ece5a621b313fad06afe", + "r": "0xf1dfc02e86485b39b7fee0a05f2cafc2202c8e788b541f5813f789e1fe7c616b", + "s": "0x84e25b64321404a973c0f0dd73f562a3e4720d34045797f0cb1526093430339c" + }, + { + "message": "0x39d8e58df4449b5e5beb8f9989775fb1ea43fd570ab9b3452dac8647b861be1bd02a467a67cb0d033af6570eae8278fe8c2f7dca669a6382dd8e8f75b53346ab", + "privateKey": "0x05e725e5ca96d80bca776ffd9f6fbd66eb4e0a2159d9b522d67f80c4c45e871e", + "publicKeyX": "0x6b256d9de0c9a007ae5db768759b8e6d3f080286adb9bc74b312ac5398d0f9c6", + "publicKeyY": "0x34ec36d04299120ad89ea44d772a2b553741102a414d6a431c307ca59c73694c", + "r": "0x1f54f5ba26c1b8dd01ecfda26ec358cf21a086ed7cdb0d2fd310b5dd537de5fe", + "s": "0xd6bf4efc158840da9d568d74acfdd466c232b712baa259f8c91700e99c4a28c7" + }, + { + "message": "0x8981d7434e80f2b6f2fcd8bd8e68ba722f2f31861010a489ee127f1b665e8e7855220287c6c206a5f6fea200cf02601d3b3e6fe700762df5fbad6f9d501e07b2", + "privateKey": "0x71f43a82b3e92af2d402b2b17bef41e42a5ea028552d6fd387e192cb89608c49", + "publicKeyX": "0x754c2ea5e1c61f7187851f36e65eb171a10777a225714f6e8d37dbc24626b68c", + "publicKeyY": "0xb6169217dde71db4f13975bcad514011c95ca79ec51a6d3b064969bf43573e42", + "r": "0x92b6777a1a6fb5aa503795bd536eda24b9d1006fc07ba9171488dda4e5c1583e", + "s": "0x7f734f58f86ca8ff00990eccc266ca50e266f048a5bc7086f737832a9f6e6f7c" + }, + { + "message": "0x320c23efe48c013e2106be66250584769d88ba82ff2a8f31b8d10163ca47adb1eabad572d780af49e82d3418bd65ce42a0c18fdd7d7390b8e89df96c7c1e983c", + "privateKey": "0x374648903ea600876c543999e95a549a7fee6d90e0b9c47e9badf31a7325b5af", + "publicKeyX": "0xfa4177af4941d5c53e16985fb1bfbb5fa248e593c2ad50cd0d72eaddf8338e47", + "publicKeyY": "0xbe8e24d09f5f13db5073c0aa834630cf0b82eb4d8936de039918325fc78b76b1", + "r": "0x38f67445e705f7492ec0b47b3361ff8aa154a6d74d1478189f5ecb29a10a8e6f", + "s": "0xae199885b1ff7ffcd01aa14efe60a4d4a4dd9006f4de8e6e509822fec511d709" + }, + { + "message": "0x69d31f6a63eba7275c726256fd0c1c5b13bef09b25f054197ea7eac7f8d1999711f9c2bb2ce136da4b35092e1d3b32b8432cdb377c18ad93fdd4c949d6677399", + "privateKey": "0x61a6bb8f2fed69e870e879466698ba81034646b834a62d356fc500acee7c9545", + "publicKeyX": "0x495e84bdaa1cfb70b4e766c29a255bd4b965b79d0af04daaf35e0abe27533c42", + "publicKeyY": "0x887eae3eab388eead62f59d85535aa8e70b0e37c2603b59b0433ea46188feab4", + "r": "0x310e5f8e3b51ed2b6a855aba86b850b130bfcc1e53d05deb52c3308dc95bec03", + "s": "0xdd4c468ee38a3451c18fdb28e66adf7fd99a69d0245eaf9c1f411268d09ffd47" + }, + { + "message": "0x516c6596547fc6511407983eea91bf42f35df7a5ad90e5acb733604d180bef3c8afaf3694a97147e46a2097bd3bc2a73a537e2f2c712523e2fce68f1efd97be4", + "privateKey": "0x3abad4b34a0ecd809a11653188137541af3ea1df54ed7631f3275606869f0837", + "publicKeyX": "0xf14208ccd67d49a701e4d8c3401cb7534bb1bbccf90d590e73fa91ce3b3ee9d9", + "publicKeyY": "0x0667968417797f1f8e67744843b1c500b680c38ffec06d51f1aa3dee0766c5b9", + "r": "0x14d11440212b32a882f34614311cfd772908b36ae8cccc4058ae526664286da4", + "s": "0x1e052c7862feb39c187c92f65353c051e4e5b998fadc1bb638660b102c54db61" + }, + { + "message": "0x78a36be651eab81417c49a3a9ff2676e25b4629ac094bc5a4e5eb130a16c3af46f597f4b3deb5e741f616717ddb741bb9224839f45dd8e93edbcc36ec4620fd9", + "privateKey": "0x58b22b71eca3336dc85ba377023fe385561d013a7a35e1efb8b4b61c35af7284", + "publicKeyX": "0x19be6ace209a349b68f36f2465088bee24ce9a5eb61bfc611ebe2073ca61a0c2", + "publicKeyY": "0xc34fe2957e8649826e4cf648c88bf72397ba24808064f48db7097e550a370c94", + "r": "0x8e59921ce83c88dae1314d1ce8689383712cbd32e017c4961720924220f4ebd5", + "s": "0xf49250d1eed8721b65205793d1b48344af9ae13a492204be8b88387ae6eb6cd6" + }, + { + "message": "0x4f9b9aa175ccea10916c83db01161ef7e787f7dffc99b4a82ebbb85f1c77691afc6dcac4a419ca2adc81e62e8a69d32292f4b14ce5adac41d929e6f2f877ec1b", + "privateKey": "0x02ee525339b56cdf153fecddf64128cdba3fa7dd6aaa63ece7529a829c81bc6a", + "publicKeyX": "0x2c36185aae2f890e15c1b6e3179cfdaae4f88a460321b1807cbf9dca62c0e168", + "publicKeyY": "0x327072c7cd2e9e7a0fc51fb01235fff2628cacd9907c54f2704a3e12a726412d", + "r": "0x63568f9958b3166dcc2dc25d830c53ad4d885105b03e3125d063cad5da701105", + "s": "0xac99a2a76d93331cb65e344d3ea86568323a73de805ce59e44c15d8f3e4fdec1" + }, + { + "message": "0xa3f6651eb9917a0e54e60b233eccf8627caa692bf81a670bd63ebbe40efbd272fc19f63a0d7f694d9cb98cab407e6920caade6a304c85b9503e6fe18dee42a57", + "privateKey": "0x41c1c9c47b244fa3c1f779cbc807933f89861dfb777459d3932025eb9b9efe46", + "publicKeyX": "0x9ecead233404b6a51d3ca8f0603cdbd7472967fec7814b02391e9faecc6f9940", + "publicKeyY": "0xf978569d0f1beffb77dd2a25dc4e92c9ef819f1d5c472d552f993b0ce4575ba9", + "r": "0xcd43c01e9a120d831f986c7c76bd0c7eb1bf93e17aadf81fca74b9e2850a7359", + "s": "0x7cfd029f91d339bdeacf0bdf172c8f3df74bb6611e6e5bdb36e7c013f9e6c8ce" + }, + { + "message": "0xe87a992762b23f4bc9a001f6d4939ee039f86b9617ba7161125875daea45cd21690c5290df5322d76d80fc61d263c20a64e5457e49db3334e6600b5effa0f170", + "privateKey": "0x969b2020c0055f6c3ba3ec1a155608bc0b35d02a5c8d1fa2b8de9b4fddbe5578", + "publicKeyX": "0x241a60fae000a190f2bd547030b5ed67ec6536ca3f585ae291fbbdde263d9c99", + "publicKeyY": "0x657773fac8e4bea88722ea26e3ac08b4364d4134e12b9b6dd6dac96bb305bc05", + "r": "0xacb2dcf4452e40a98d8d18bf192cb5dea3f02024d6a565c874f633edcdd7f88e", + "s": "0x7965479dba3b7da7b5b55c26ed999d04167554511611e9271b844bc3d84f84dd" + }, + { + "message": "0x7f1f3cb82a787eeb1cf24b23814ef1c54256bc69ed0f4c83b30206120cb9cc8d9f32765af76052bc402422a4a2683aedd8a6015cc31704f8e9c5d76ed92c0919", + "privateKey": "0xa6c10e06ac04fec7cd40ded4906831409a8f6a6eba09dde5dc8b9f255f3c7a17", + "publicKeyX": "0x97d3cbd5e81c1dcd874124d801e6b1061f6b37c485f9dab37bb5b73bc08af0ce", + "publicKeyY": "0x2aa44553890cde0fde7daeec843a18f8a16af8f576bcd2a131b7002429e73726", + "r": "0x3b8d18c1cb35ff8ebd1ea395c7741bfb0fd84ab9f9afac33d351d6e75a0fcb28", + "s": "0xff60aa1481001a35b20e6c8fef306a7654f3d4a19e8d77ef679c794b94d029c0" + }, + { + "message": "0x311739e98549a5851764101c48f1fa2a41ba8856f05a84d9c18755a3963062b983dcbc840ff7d241c8ac999b7a6727588a58f0980c8d61c63639fe83c43b6c1c", + "privateKey": "0xf589afb763baa4f81ac97949308d00a19c0cb6d98e92add052f74957782fcb8e", + "publicKeyX": "0xadf831e3d4d9992f2429233732e86f68b294a10732be9a8c0bf2c363218602b0", + "publicKeyY": "0xd6c5cbb33deee7c24ffa70fd1667c98b56ac78c20c40fafb39ede58bdbff6903", + "r": "0x1c183bf8a561ea02119238331dfb51f1c39a298f4d2ce7955b285fcd6e67601d", + "s": "0x2178ecaaf8ab06a3ba19e4f1e57b98bafba3c9121ca5e4934d97122f7e9e34a8" + }, + { + "message": "0x8f31b0094d95fb142d17f9be80af8b3d46dd385fb2568ce4e0469f08745a13b53cf45bf962d8ffa172d99d499e7227d4da9c34acc628fc1fa72934ed2caeb14c", + "privateKey": "0x51c9bca691e1287ea80e5718c9429d6f74157dc006a04f36ff21ced3d2098de6", + "publicKeyX": "0x4134b6635a0d5e7bbad48b7a15442c99323615e0bead8fa70939c434f0b7a250", + "publicKeyY": "0xf7be90ef0644c8188afb737fce0eec41a53e9f660f1c05139b8901ddb08ed94d", + "r": "0x72de31951def5999bb4a46372b99add5a7c41db583c830340850c179740e1959", + "s": "0x2e07a078e8af2bc82b45da89eae50859386740ff5815b13311b139ec3bb6b76a" + }, + { + "message": "0x807c6a48d26b1b2414a1f8cd2c23237b8faceeccc227752323877611d90cbe19de6cb597487be4dd95e7a4eabf3f84b62862a0a42550d32f2696f42c18e0520e", + "privateKey": "0xd7f4b43174665d1a119ea8601483a8551a1a56a137b5af4a481e3f7e1adba521", + "publicKeyX": "0xcca0c8b0ce293d87cfb975936942dafa4007f2f9007c1cde22e4df3e85e0e765", + "publicKeyY": "0x78f260785506116c3d6f00dba9bf537df5e3d9106dc85c92a6557e17d1d0c2a1", + "r": "0xf6e0cfd45563f984820dc3e4efa9054ad8ea134090cabb214cfbff79e076b9d2", + "s": "0x4f7c063fda729937d86bec915f2722afdb666c422edabfb0845abf2d8d554f2e" + }, + { + "message": "0xf69d76e2214104d1d8be8ce692706702f725bb0a70a4f0983f84e5803c6f4ce2167c37f56cc85c82b562caac798b55e5f682ae1e7a6cd01b761686d93004d925", + "privateKey": "0xaed4fc636d3795d16b7f4b68826429bed1ce84b0fe13853877e8276d11c3f421", + "publicKeyX": "0x113ec1d7e040a6b4d935ffae708141bbab8a238d57169c6875c591100f58167f", + "publicKeyY": "0x1b68c3d6bd699b2d28da5fff9a4d9881e64b980b0a37833dbf5cb67a9941f78a", + "r": "0x52dabdb1e6f38968e98d4a57a30338d2c8e630426e6823a8531a78d74ec0ff6f", + "s": "0xc9b883ebd89ea41deca9576116097952d698a289b3d381cfd2243a8d2343afb1" + }, + { + "message": "0x34ec44b7097fff8a5a393ee9bb15d5dfd1cc5347ba26cf9c5f6b4cb80c6708d0b29e49cf3cd6c2a726cb9f5ec0df398dec91023118552acad1ad8ccaa55d8a18", + "privateKey": "0x916acaa2e9f5e5a07e75ace85d0e7ffabaf351a696896fc0270daa05a81311a1", + "publicKeyX": "0x91dc1b3b49e60c6f78b129ba830bc52e6e947ea9ab23deb72678a3e46b4e7855", + "publicKeyY": "0xfb05cba4d4c5d47bf92c5da7561e520080c36dd6025b1f6c2f8389507a306de2", + "r": "0x849c4cf0bf447e2444033e271b4bfd11266a3d8d93b913b8c450cb51aba4d5dd", + "s": "0x3467ebfd7ef2c9fe30d67dfacd91c998537f12495142fb16cfaf096080c6b7ff" + }, + { + "message": "0x7ea2c8c5dbc0079d06c62a6507c608728d11343d57181df11d93f40a536590f65dd98bb19c2a99494b318671c2f3ab5109949c5648f00d078e9995f903797b9e", + "privateKey": "0x3e2a78a03bfd2fabf488f7755b986bf03148d19d6ad9921e14636416e340e802", + "publicKeyX": "0x65f321d22b68581d3bfa3d992529c7af38c3ef00f59056a0231c4b580ae51715", + "publicKeyY": "0xe8dc35778c0ccf1e2c14282b4a163381c093d3e216b0b9eed050d55ce35c6c70", + "r": "0x2ee2d16fa86a6ec196b48988b1ad5fa791a1c6f4dbfbc008dc3a99fef9fa2b42", + "s": "0xa399767f281091a3f82bfc64cc77af48c6fa5753375433125229e9bc94774926" + }, + { + "message": "0xd36ddd0241fa4fe915f20883ee8c23e06516f5de4d8092e58c7375c0f609abb8f829bb9706b4f6da32e568209fdaa9ccb2ba86681519ba98d8350c934e454209", + "privateKey": "0xac7a5c58453d490e690aa372f97ce291fe64dba2d4e1af85451a37a3055ed5c2", + "publicKeyX": "0xd66ada616a9830c89440ad8795b4f67c376b451fc0ac3811583c4b560c0595e7", + "publicKeyY": "0x111cbac11569f7ec4abe52656c88dba8d190c1c19dac51512d042e6664419515", + "r": "0xb32659d2fce52a9f70d47abdb58c4d0734e30ccba71e9d88d422781dfc5b4876", + "s": "0xf21ae730979f7685899e611b310d107fdeea9b7f5e9a507da6307c47fa5fc6de" + }, + { + "message": "0x12c13bd83d1aef5a51a8ede0b5e0d69f8e8594ad526b80a2f48d5f118583f841e72f1afb199fc4a77c0a53ab373928445c2d2ddac8652059ee55dd1a144a44fc", + "privateKey": "0x727d6f6834a3cc28fe10dc4c1932f45a07948ba46d8827b8d9fa23b261a6c385", + "publicKeyX": "0x48ff806ad7a819e422170fb42d8e6162a800e149b9cc17899a8155418176f135", + "publicKeyY": "0xdefb2da725dc2fe323f28c622b4f5d0e1ef3d8d186be9c36a90de90458e329d5", + "r": "0x82ad0ef6dc388594919677a1fe356d4848205a2bc51fba0c95501c6c37bb2fec", + "s": "0xc30c1524f6549a9e626f096c8824d0c70266e63ac0d9a3da871f11532a8bcc28" + }, + { + "message": "0x4eda5782ccffe3f841385abad1605f1b1d73e9100f18238f38bd75109e20782985bce6c22267230c9dee5475521dfcfd4437eccc41838643dc5e3618c048f6da", + "privateKey": "0x8813172f4627998309a56f36ef7b76b73ebf208bfc90e87995ce3d40b18cba8d", + "publicKeyX": "0x56b7bd6459214decb305ed887c68fe942bb2874b91388e80272386c624499b2a", + "publicKeyY": "0xd6f7beb5d34ef7167da6b25a6f1ffd4cc5a717c99cd7af823a40cee4c5b0f7fa", + "r": "0xacdb91576852aab0c922a074159fb707142f7d4236d509831ea4c8085fcf254e", + "s": "0xb5b61811744b1f05b06d2530b5837c4e099b34aa55cb1fa8bfad1cdac9a383a9" + }, + { + "message": "0x4836b9ed3ea6592478389007bdc605fb8fecccf90906390a2a4ded075a56893923034960e72e86835f6b07f0bebdb69fec10226cc3b2db1915bbdfdf2ed09f07", + "privateKey": "0x901a62f5232583b1d33329128f49ccee4b81fb835b0454cda04d1f610fcab221", + "publicKeyX": "0xd3c4c094aa130e48a9e87dd6f36303a29772047916264637e9cb6e1aa447b579", + "publicKeyY": "0xe889142b9109791a7f366c7a320b826390cc1a8a92315053aef273502d80d884", + "r": "0x5ba58178f46495d6ab3ed2cb85c45deb268b5365ced22e05f3269de8c18b47cc", + "s": "0xc953874c1765bd123a0f39860ccfe6f20e6b0700a621be040c52fbf2decd5daf" + }, + { + "message": "0x647c01801585e7e18bd1e57aebad3ad0f17b1b55080635bbf07fb3b8dab0c8dcbb7345c5fa500f2f0aeeed72ec003e8465805d4a39f0e57ff3d40deac3ba526f", + "privateKey": "0xc059e6c9530bdedd825f92858848fcd970a18af20d0210f47b33e89336944f98", + "publicKeyX": "0x95f65000a75c653201d25b0f3d557da180e6ecf1e9c8d0c018aeb3aefe647585", + "publicKeyY": "0xfa09647cd5bba01549ad9d2d34e058f575f684640be7793f2997af976c4d4719", + "r": "0x0dc3202738a48898311642ab8ec0c3681ade924a7eca136e6b7bc75fc3054479", + "s": "0x565196c911dc5c6cecc451698a929408c0dda7d76134af8f2049b810808385e0" + }, + { + "message": "0xebe64a5b629c8f4d3f45ed58f3abed022075dddb307c7c887641737f93353313b7e2757691520133d2a655b8c9ce19629b7345441ce1a72f1afa5ac3d1fdcc93", + "privateKey": "0xb7fff2b4e1ca23a4f5cd4bc82b6b5c489dec218b52ac74894a5621d1b5702cd6", + "publicKeyX": "0xb5f74451a83e24567ce9b57ff6402e9b838d47e87f4367bd37ffca7ff90876cc", + "publicKeyY": "0x46b9c80d8fd496bb16e6a51610b6fbd26bd8dc679596ee5649a27f73360797ce", + "r": "0x2719a3b289dfaa5ddfa754c17de5277eb26ea35022c03df7476978ea03fafbb1", + "s": "0x3c841095dd8d20e8e4dc17991122e85e8783fd4bdd188d8eec20072b20291a02" + }, + { + "message": "0x41af80d164275ffff83c3132065c0944d1712ea8bdda3276d2eebcd6c0f2fcdbc00823b5cc648e296364d6ceaf99f651cbe25fe55f19f6f660e244d4cf08d250", + "privateKey": "0xed2f91ee00e127521b9138775968dbca2607437f947e45d553fb0c173990682c", + "publicKeyX": "0x64bd1de5633600b9bde796747ff029447275ab848362e643d61048e17defbcab", + "publicKeyY": "0xfeb9a03be24a68a26ec247ebbde78881bfd189e127d61f866b277a825aba5c5f", + "r": "0xfeedd45d46315b3aaecd2cb9517459e6109d7c599a92de361295acd1dde43430", + "s": "0x854c47ded15e5cecf6dd50b59923945da17e90a9784b7bbb0c0b4ef4bd8fc40a" + }, + { + "message": "0x06ce4c213d04629a175a469c8457ad72a9f1e5b4ac93d8a09b24c06f3409bbe34a61add1097079800eb4541dca44adf2524e1eb76cfe7371d7432a1009469147", + "privateKey": "0x692c7d8918825632b33c328af98a032d12973843c818e52c1e4cdf087bd4937f", + "publicKeyX": "0xd5613c9214f9c11b1ea6b4968a9917631ab0cbb686821e133da1ec129c2e9ea3", + "publicKeyY": "0x225b52752b7d034b1e13a31d69a4f3f5bb46b11f06419690a528b13e6f34c230", + "r": "0xca7767848448c05947ef418027292407f3e55ebb106f8df5d32f9025840b1a84", + "s": "0xce264058615d2ec9702e67d90ce3778f760a291cfa70d7aea6ea9ff0abee9fef" + }, + { + "message": "0xfb047343c29b1b341ba61ad8c253f4df5acb15a2dddee9c9a6df8343eb2977b0e710498280224b2ac28bbe3f35c7b4c0fb605b4cf513842d0cff3ecedfb53682", + "privateKey": "0x968d90bc5c29ebaabb8871b8b206fe696cf8f5fb9682c08318c04982150920a2", + "publicKeyX": "0x107adac17fd406f01060756a46aa21c0ae9e735dba7aa739e3a5ebb9ea3050c0", + "publicKeyY": "0x7ac9014f3117efc49d60f12a571345c63158a0417376cebc05e4f00ceaa63ca8", + "r": "0xd86bf3c645747382d69472d736a46c86203a4e95de3983956268d9cfe45e259f", + "s": "0x6a27b70afa85983af1414323e3d71b8a95e9e239527c5cb9b0fb17058aa2a59b" + }, + { + "message": "0x0ea7bf84f53a54b91fabdfa5c7897f3c6cfb243b3ac233e3a950d0c033a9c58c994c67a633cc3ac77d1297a0e28450ec9280d6c5af937b2f6c45853171d604e0", + "privateKey": "0xd30dde66834330a979530251d8487ba0c36ac1083307138a7756c86fa088f050", + "publicKeyX": "0x40fc50d63ec1bb349271652500668374112b5d7df7b91ff52bc80eef3e6e6ffe", + "publicKeyY": "0xf801d9a9ed69ac005c7428ff7845eb11a18e2f524e970849673e6558984d77dc", + "r": "0xd6afb57eba31b0b735fb703a4b991a3fd2552f8bca9c75c09e426e0b20364fbf", + "s": "0x39baf1f84f2421ea259fc237574a18b5c86c392ce4324b6b44bf98a31da3cda7" + }, + { + "message": "0x19ed5f6ad6a0d6710029cc8fa3c2b0ca84420eeba37643398af519f4500dd7a5939edbba12a5ee95d96545f5ad5fc6cb9877478de5ee63bf4d3665065a942dd0", + "privateKey": "0xc276a420ad97351c98f7da5c54b0b8f6160329f8b1f672f12d4bfcf04d0f2526", + "publicKeyX": "0x1592494c24e61002b2d33977d802f0da3bbf8ce46d61224d2d69580c8938932f", + "publicKeyY": "0x5221af2d4594dd918d9d1966183ba149b902a0aa3bf2383e3d52c48275dc7ba2", + "r": "0x553ad794c23bfa3db688ac8f780ac3a9930a27be8db3cfa1d50b5a3fcd7b4a0d", + "s": "0xe1b6b9a6da805670516e39792c0a6e5ca701d511b658e0a9cf5be9c0fcf10c5f" + }, + { + "message": "0xe1b38b9ca51a1f2086d47e6c09bd802375cd0c6c4aecfe9d192fadffa9e2cd472195aa9b537103a0394a75c4dad0fcbd6d2b1ccfb2ffbb105be87a7fa29687f3", + "privateKey": "0x64e41dc387f569b14d04b4d747ab9d5639966764856d62395d49e5d3b41aeb7f", + "publicKeyX": "0xf1c5cf1a517b8caa07e1d4b011fb96df038038fc49abd0408e3ca69457333cad", + "publicKeyY": "0xc5dcc8a0d3b94391ecf06ae0115597245dd2333b039c2ac6caac9984a5a4a056", + "r": "0x41c2c3647c7e1c525f50e34827fda8217aa14e3eb681b1ea1346f72fb61493d2", + "s": "0xc5984fa0a9ed366490cfb58176b6e6e552f75cc93756d95b1b52910cddea4d9d" + }, + { + "message": "0x70fe30b2e960e88a65c67f41dc3f6ccc6914b050cb6accef4f77d38f232c4a1f581e1ab70153a4d142fcf60cd70f51daa1555f89c5d97ed51209d3d98c26e8ba", + "privateKey": "0x2fc269dec13281bb1f784ca1562a29c7c7dcc96b123982186f071a52d36d1071", + "publicKeyX": "0x7fdb4e27d697b4e35f73404f47ed88c62fabb078804a00d78b472fa385cf7457", + "publicKeyY": "0x1422f87e6f5f29b1c937ab48a00ada4a40711f389a520d7d7a4af88926e44b2d", + "r": "0x96865f7dc4a9820b8698f595f43f1d051508a9d6b9067140c938641fceeeb255", + "s": "0x6f75befd473215870eecbf968b594712f3ce7d60da1d7f39d9f4a5e85820625a" + }, + { + "message": "0x7e2f7fdd5b29deac9395471acaa62537ef5314e05e6a4834cd2a5be073afdd0ecddd66bb2a2ff786b7eff9431bab1f28d1ec7e9bc93727b4b66e1d20d1717bcb", + "privateKey": "0xfb35c2a214fe3466198be0b4376b5fee596848fd364f65a25814921533d60c04", + "publicKeyX": "0x8e48695001758a83d459490cb291d2c4d36c507e58e734a3f8b5a29017c52c11", + "publicKeyY": "0xbb1aa31ff7e4b8a94d295f36ceb38bfc6cecc041c9358348775deecbdd51d28e", + "r": "0x5909a5cfea6257b69ac05aa03b144fd0bc71c10f3337a5e325e8cc6a3ef16025", + "s": "0x426294e2fedf1ff14ea15b9313a04250a3407143bf28f29a0271994b8d7743a5" + }, + { + "message": "0xf01bc05f9fc498c344f9444d6d7e3ce2386d9318056ca464e6c65472456ae1803478f8c8344623282ea66586d5815cdb6bade8a5cbabcd4a90ac65afc42c1ab0", + "privateKey": "0x3492eac0f24fd1a96b8d55f52aab3c0805d47d2621e40ace54d2483bd59593a8", + "publicKeyX": "0xad20b80911a7116fff6f938a9cca4d8fcd48055bda3865bcb4f9f194fce5af89", + "publicKeyY": "0x614c429664d14c64974208756ecaf35d67f8a5da4fd9b10e7e38dab3a822a8f8", + "r": "0x567aadcabca6b7dc79298ef4fbdeb58199bd6aeab0621d3b182e154071556263", + "s": "0xe1050d5353c91d928317a299b48577eda8f564af0c165002a73bffba071b8023" + }, + { + "message": "0xdec73837a169c79a01c39ca8cce13520b50d48b8e919761035dafe3f5b33befb87ba67400cfcd630de58cdbc0f504309cb6ca9d80e7fcc8de408dd0377123d05", + "privateKey": "0x9d825eb0fac5a2722b2766e21bda4999c32fc740493f166224e8df5c5dad7776", + "publicKeyX": "0x0d3d5a42f86bd0ff0600b269764e5d5083a0064cacccd87000d003ef580ffc62", + "publicKeyY": "0x5908b2e00bb0d6a20d1d1972d0233aaae0609f5427dd41689d22f457e4cd4f70", + "r": "0x20741dae439c1f124bb4787c462c9d605588d8778660750e5a404b60de99a534", + "s": "0x7198f22c911c5a529450a945c708d631a6ae74f63be2df223b00e1cb4313b9e5" + }, + { + "message": "0x1892b0b0c15e186515d2acd6f3fcc43ef6caed56e33283ca512df8d3495fc0e571f1bdfa11537889518c081bed87fca4375e4b7fc0269eefc9d4219dd612b215", + "privateKey": "0x6f6cd7f51c2a8e488aae79f6ba2e393d9018bcc2b8f65fd5c45ef9b39a46628e", + "publicKeyX": "0xce8c332830ce374413b15df098d40ee8321ec379a683683a68872e03919c80c8", + "publicKeyY": "0xaa498307f3e087f65728d0a6057015aa2568a27e409344dbd90218cb0b6ab973", + "r": "0xef9f0589959d4655551daa0d608f29b809cd38a584998a8312b9648c42da62a8", + "s": "0x6feb3be7145ac7e2e62a7a26714637cbc9ec89724d69981d0138396ce1c452e1" + }, + { + "message": "0x6945af3214f8ab123abb9c897f01205912d6d91f40d38943d2d3418c3f535331c8adb1cd154c97b26671d9ed8bbc21d1a660dec74586ea19180eb82dfcb1bf99", + "privateKey": "0x1aef666cee4689c89711c33c046e76f156b2f8bcd59d6b97c220d5d55eaea3f8", + "publicKeyX": "0x0efa80b4fdf63686fe33ef46c60ac6339a31927ef23f67202eb999711e70be25", + "publicKeyY": "0x84281d5b95aabdd1a384d9a9c9c4d9143cbc950629cbf00d3e593bcf97cd125b", + "r": "0x8df0a563ff3ccf66f663acf3608549083d25fee739a3e21a2a20f2844cc6edc1", + "s": "0x89d3cd01c9f35e63225ce17c851b402b17f4a95675e7af4cf2b291c993e8c18a" + }, + { + "message": "0x3850b75141bd9521a146c8e7f8f810a23dcd5ec368ff5936f4a89e51e1cf899eb6a105ea2a82959df09cf9506b6d7095f3dca6baf954cf273bc09c7288d1657a", + "privateKey": "0x372cfabda5876df6e151f570a00d7b7318519a195225c040fd12cad41f0b1fe3", + "publicKeyX": "0xe18655f48c89caea57cefe19c1b24e9a1acb8e72f80771c6265175f4a2c81891", + "publicKeyY": "0x5848bfaacc06beed3f3172e1f5fd7ca21378f61d14a3272bd7750d64e7147e03", + "r": "0x565cda8cef5d1253f23e73a6b1e8273cbff0f4ed6b205354e1f6ead1550944cd", + "s": "0x86d2d49c47820ce9ba022076099006e340a461f667d24a57d94388f33e908030" + }, + { + "message": "0x6761ca2e940ac3f11493e73ff79c707402574fe25fa44375c4bd7ddf38896f2ee1c2f20397b17594a879d66002ca37683cf34577a4c17c9e57a4f1f8a442ba68", + "privateKey": "0x308a44c56df375ef61596a10b589c92db7343c207aadf10e8776357d2d8caa79", + "publicKeyX": "0x717e5aaed49cf2f9f425cbabd710cc1ebfc137b7ab85e20f24c94d7ef40c2238", + "publicKeyY": "0x12500d0db0e0ea471f36d79d4dc27cb2fb8961d06ba25b247338d80202789967", + "r": "0x223e03ec811511f0d3b68431f7fdf54dba4fb317943482325deeaf7cdfb636e6", + "s": "0xdb6111a8379d661827dd09edec052a2ca0d6293839f5368aa712912cd1792d20" + }, + { + "message": "0xc9855736a81046f374e58873ca7de720ac735d21055d626a44cf51744d7a03016314871900cefa8108d72afc73fc00d478461cb63dd8ff7b5ff2f20895cb1eee", + "privateKey": "0x77b0d1f9b545dfd04801d99fff9244502f58b870625344bc80e788254398fff4", + "publicKeyX": "0xccf6919ef61d6c5450bcc457b3d5cfdd6f63839732082bd741630b46dc5413c5", + "publicKeyY": "0x57da4ddebb81b205c1ca5270670fb621a20a37820d6201b878531d669634448e", + "r": "0xe8a9450bbc7ff09d13184cda38e4c297f1b505f5d0984dfa4e582717a2f4421f", + "s": "0x423d8119dcf4a06da1d76c7155a41ddee6504dcbdfa6b6595944ca0d9c9b3d0d" + }, + { + "message": "0xdffd4d6ef719645de648a4a4b9a79e350785981d51b7f04d892fe5147aea27469a7315dbe7dd218d3d457a9ca8ff0592109d94b3c4f4f7bf418d1f8634efc486", + "privateKey": "0xf975dce03b6183ace499d25e8682bf451f1b334ee4fc27b7660ad99093afff96", + "publicKeyX": "0x63460a3aa9b19046544a034d50bdeff5e168f0f4accd744013a173fdd76885ad", + "publicKeyY": "0x6111492eda4276a756a31287c8059ce6e91f167336cee4b760fc5f290d9a720d", + "r": "0x784b72951fb2f9c3ba506bb5ad9108c60f5bdbeacad8eb9d1bac64a52d2580ac", + "s": "0xcb1f1a7075fd90f04206329cd0b72173d63af315f658fdf9f6412706576cf6a6" + }, + { + "message": "0xe005e4215c76a1ea8acf33dd550bb7d9c8cf13be66754247b9f7257af37985d11d30b83d572ba720f0caf989afa9a1a4b4e7ebf87bafa6a40525df87902ca4b0", + "privateKey": "0xca5286ffa0cb9965fe9e2470152b73c35ccdf9a38cb3a60b4122c205be1bc9ea", + "publicKeyX": "0xc271037bd8560241eb8d0f50cb8f5ec6945abd92f8ad32275d2de08d46b09738", + "publicKeyY": "0xd949ef4451ae3cb26ac8ce2ccb63fad742b6d801b47e8e0738f75c9d4e21f77c", + "r": "0x67fce94e74151c3ab9e7985cb72e11ea4b9c4c101ab0ab4cc71f02a3095a23ce", + "s": "0xaac3e3ba9c2159c76c387db226bb56b77f6d0ae6f8545b05f6f38a5ccb2d7eef" + }, + { + "message": "0x2ab096f12b61fddec134091c712510945228791ae09b9edd3c12edf984ef6fe30086d544c5fe2e9320b04e756cecc61544fe5112df3271f84247effb7c710c87", + "privateKey": "0xed903b4ea78ee1151b9c893e30a1f48d3e52804cfeaa84e09d9c82012c442d1e", + "publicKeyX": "0x19d293eb70903363a9404ed07472f4fab4f8dbbe6cce3e8d525d0305cecf2622", + "publicKeyY": "0x2f1b58c33cae95c8d71e352548758d6019736a60b2b24fdb1d0526ec65ccbd15", + "r": "0x7c4eb9294c03192515d071faba8fcd821a0e3c5b30a9ef98b4a18f1f48386e45", + "s": "0x9fa06a6f522040ca12bd5e5c30a5ec50aecb45015b0c91f6f8ad7570fd69251a" + }, + { + "message": "0xf67b271948d8697653dcd041b3de186016416946bd13dcab880cd35f58348a16893aee0b22776c324b6473ff409f1fe110f3d8938fefbb034f83446ab5baeb7b", + "privateKey": "0x3b5eb3316b63cd9f4419c3fa38bfae5970bf2c5eb480f444bd2e4c811f98358a", + "publicKeyX": "0x4dca62cf2aa1b1aa6063f31d6db28dc6f223f068b44caab6bc007f94ad845abf", + "publicKeyY": "0x3acf3364e6ca9720f9f55bbd425931dec12af07eb47044eb3ed07beee99c0b84", + "r": "0xda42cca18f0087163bba3aa4cc05e21c42b1d1f2a61b154c60f9769e921ae60b", + "s": "0xa8f5da60cc974be2fc09589ed77db3236888f45bb490b2fc131e48a2ed3e5d47" + }, + { + "message": "0x93567d9ed491fe521dc2399a07845c3bf4bdcc99eb98342322480778d2b2cf14736dac4d8305eeb552573ffb022f9664a2dbfd64d91f60541edd01303b82351f", + "privateKey": "0xac7fe19dbc41228b09ea2d17c6948659d2e7f37bf98dd267b0688d3bb7c10c9e", + "publicKeyX": "0xc750df0f1295c280950e7931e1db3edbbbbf1a9567fbc3c9996dfb7f0329ef82", + "publicKeyY": "0x480f6484e25633273d59fd932c4781ed7faad0b186b812dd9346613fc0565911", + "r": "0xac38fa645ebc50ca8800dcf2de6a8906cd66375c296190898442b463d0c29ad3", + "s": "0x459934aaca63efe5ccf3e137ed9f52d4e4a9928d1043dba989424c32b7f6a317" + }, + { + "message": "0x9b1a94374f5477ac7565fb5b0c82d2ce2452410abb0636516697d3a1b16598e6bc303f90d205979f189806bd737d92362d94644e7e58af5a88ccb3ad71f02fa0", + "privateKey": "0x77264890f2776d27c421815e1f07d17075fc8577fd76a46f5b40c2ca325eb733", + "publicKeyX": "0x09281bd5492a89e0909956c6b1da5a582eb5b54dc80c36753d9d74abda2c37b8", + "publicKeyY": "0xba2da49dc74a5e0f1efcb57863f876a430013d1f29ff22deec641f49fcfea6d4", + "r": "0x9225a21db876dec8c5eee5185558b760ced4575a61b70c2c290e7a74edf2395b", + "s": "0xf44784188dc2c0e74224217a17f0f5904f678e3afe9389dbeeb90362a58cb6c3" + }, + { + "message": "0xcaa375cbc4e6e9dd66f1463872f8fdb241d87718f5850c62f0083a8d51707ab25d185090ed30438831dab348af2e4980595a7af56ff857ee89a8cfddb965c793", + "privateKey": "0xac26e82e4fa7c72d147da1a9dfd78fc1c243145f31327a5c0a20cd8ce2786282", + "publicKeyX": "0x602b82d2650c1b15ce3ff1918bff0f60866a34763338df54d089520b5a1febb7", + "publicKeyY": "0x8ada5b9c05579953aca1c54c79ce723f46a6967d3d37e8ba2616022aa0f90907", + "r": "0x17c07911dbccfbacc2162387a2da581e598cb5015f220d78fe03edd10ce6227b", + "s": "0x6ec22b7602cb02a96c8553bc19fa5b3326657a6960f44d0a066f83432937c2aa" + }, + { + "message": "0xfaf33d7e2e47a9e9e425cc11e4bdc571973fd570978c596d7efd6acfefe287f2505d5d0a41886c6bd8dbb097732c6093bb9b9c6e7fe3b25043039fdc455ec385", + "privateKey": "0x6e6ace5dcc1cb31375d4ebbe5699b3a955c8144f5b4584ea303e8a81b22a6648", + "publicKeyX": "0x4c45230aecdf177622c954bf0f4a52c7c70c94cb2fdefabde7004dc3ade9683d", + "publicKeyY": "0xd801f4877ebe306c834f00eae858d7c7e6ed64af4735573bf8f8ff61c67d166e", + "r": "0x632b866b79bfb1644caf571b7368e604a77af013aed08b0b17c2272eddc6031b", + "s": "0xe33026982e484db41a927d82947afb4be77c6e8b5a0ca3278e51380e47d01ecc" + }, + { + "message": "0x1166754eb943080d8eafe5e1a36ca5d83e40e8f29ac6f3ca1d285ff99f61c82544d37e7be489b15a39f08cc924d2587d28c48d1e4e47da146397e7f46491cd4c", + "privateKey": "0x020cc373b4b0a34d9df6f63fd7db548617b1df2b52405562d13610b1a8b2d3dd", + "publicKeyX": "0x3e5d7a28d7831113374173582393da9cdb385e49d6a599cefc2e7a1993ac701f", + "publicKeyY": "0xffd494cf354e3ae916b7f9e270adbf3daac1fe6512e04d1f8e36841410f007ca", + "r": "0xe882f35946ebe223f7f0b03fe95d4482499ebf4cc8d6b4d403d4ace163492097", + "s": "0x14221753a41571eeea8feae514eef10ff358f0ebd9bde28c30172319b3b665ad" + }, + { + "message": "0xe9272f3000df1c79f05233700b9d76597d419b223bf714fbbd70483035450db9f4f5e992a54a9ee4c0d85eb3768be1b18ab87c1df0600f9de10f4d652b0a0d28", + "privateKey": "0x5fd1195b7a9a78bdf5d1e8b48f9865008d039124877d77296f587b50feb8bfb9", + "publicKeyX": "0x115ddd6b3ccfbeb51490dad7eef1d68b4a19c4232c36bbc14982f7afc2c3d2e7", + "publicKeyY": "0xf2f2ca83212728aa6ec62dbf60926847755c388096a4ceadd78b60d77f527216", + "r": "0x37f269456612a27041ee7127281e97d0678177b5fd72dd2cc4f84253b038610e", + "s": "0x61aeb96e26bfd43e5b7412065b7c548f44d365f2cb419699137ecd73de5ce19a" + }, + { + "message": "0x70cc643098b9b61b6ebcfb2bbc1ed2442ae2a4a087b75874cae8a14d7c1f1fb3ca8a8048253068a6c9132d1f38440e887cff32bb04344871492d3b3fa5b21b4c", + "privateKey": "0x9b677ae0cdcacdceb4205baddf8e95e62d6ddbc7647d23ef74938b8bb7c32cbd", + "publicKeyX": "0x927c979a5bffc86d1d88fa607511580bc7d4f7fefbe2a6cd14e2a6193d78735e", + "publicKeyY": "0x9cf021238d91b69526caa4c6be9af6544918af518d871b9abd55127bf4f597da", + "r": "0x51408f51be1f2236a0d661f328c2ca8c58ac813dbab7bfef629c6028e75aed6c", + "s": "0x2f84076cbe3017fe0aea8892e566a27432e9ef0c8b1a37c56a5824232fb73917" + }, + { + "message": "0x296d85882fe09ecab2ff73c276fae2d20e4e8f1bfb9cf2237ea17cac7abd8ba93dfb97ab729afc2d1307f79e56989a7491754c8de6fc39ed50cf855796d4ae0b", + "privateKey": "0x947bb80ff3fbc2629e52562d89437247afab6cae36b3f1a4d226a8bb838d2883", + "publicKeyX": "0x6440924d060bbeef52bf73a64a6caed0b4a213cca7c44c8ecad219fd9ec16e2e", + "publicKeyY": "0x5a60e163cf5e1a24c7f3fa9be8a45f5407d5faf39ee9470f8ef4ca993616a5bf", + "r": "0xcf1a53604bf5ae550f667c96c3978360f603606e637df215355c87e2626fe8e4", + "s": "0x18ada0bb1eac62b7d49baee6127e012c6f58e31b5888da19448719063b608b28" + }, + { + "message": "0x4f330f45ad5cd7d241482f7a9d6c35c92dff97bb299dd49932cac07884da642ce5744d549705259643df6a30876c5ac5450e69cb8595f8f5bfca3d23ff3c075a", + "privateKey": "0x660b17557aecbb80e38751bfdb417930543613ed9fbb7d872ba22466c6b4ccfe", + "publicKeyX": "0x403aea3400fe0b7b25c0756a2bcadaccf1c3c1f66f088fd865e5b5b2e9f2cf91", + "publicKeyY": "0x0c959c056658b067c930a223d05103696c5c61b973df803767ab4dc527be126c", + "r": "0x5a6aa38d3dc4cb03f8dd2ce9eba7925d19904306c98879f5aee1bf43efeda885", + "s": "0x197fbd99059d0a36cb53dbb20c41172510a41ae5bc2bbad3f0007b790b59e2bb" + }, + { + "message": "0x2a42766a79930707d84350ee78a7094a9c92b63f95884e9e67653f5a6e473e7aeca7dde2f5b060d51cd56a076c4106bfcee8e5bce8c2a737db6a681aee8e42ae", + "privateKey": "0xcce10e37b62fc27e57eff3c426c98c5157e263b5f15c1f5062debdc8c50f03b4", + "publicKeyX": "0xee5f2ac18d064e2ad74cf82ec3d07692a2d033882298a681de10a53e5c491221", + "publicKeyY": "0x052c4ac2dfe3a1e87ad1aae6153e315487a765abbdedab21be3e8829dcf5667a", + "r": "0xb152f4ca417613f0a8769186956b1a2a536e40ad37550f805834907043484949", + "s": "0xf06213187489745c10e4a08555eff07f60f6d638419f3e8c8e906bd90bca48ee" + }, + { + "message": "0xdd4c6d79ae996b7f363b08944ae08365a52bc4161c9750a712995cef4779b690174296b8b3dda607eaced41760c43e67eab368f0d099db5b0699656db9ca7b4b", + "privateKey": "0x924f413b1c2b4c408f2d3b87270453f1ffb127466345321b6766813014403284", + "publicKeyX": "0x1fa82f10bf039d7daa93116c835f018e9f07ffb46e2f03a266005692f95935d8", + "publicKeyY": "0x03f4cf2d377113eeb59c3ba418581e7bdd5e9381a5df4395a666dbfd7dbc55f1", + "r": "0x86d6cdb57ea35f7c131544e40e83ff623a5ed62889ec798ccc6bca52e583fbe2", + "s": "0x776c544d71acb13024d1d3cc112d9ade91cb43f0b69069e077986223e85eb5ad" + }, + { + "message": "0x03473b24ab45dc9f1dc1555c0658c11b821dc40eccd13e67f3755eff78fa4a09eaf2e4734589af23adca9de08b22643ca4723ead40fe5adc2c603ecba52e01e4", + "privateKey": "0x177b3de456aeb5360a185c4313b466a7b38923983c07b05ceb6634977ff5568b", + "publicKeyX": "0x6e51534b5c84257b3f9c0ee261eb9930e1863656ddd3cfa1ced455e8acfa3cce", + "publicKeyY": "0xf31be82ba1de3be284da9237a78a79271f3b86213fda3e3aac671842e1bcab80", + "r": "0x8416b0ccb08bc97fcc70fa0ec81cac2320c3679a29a11a11cef2f5989def7582", + "s": "0x02b76cf1856d8c079a85a4a8bf398d5c96256c312fb29ccf3a9a75a82fddcd08" + }, + { + "message": "0xc6e65107796a7b69de2a643a5cf92d651d0358a1bbf9127812ecbb4650c5bafc66ac087e9e99a47250fd2e6226d413ee0bb5d03e66bd8f1198c0dfb3b02ba5b0", + "privateKey": "0x638da6a10777382be1a60f46599149a75e952eb9727d6f238883170729b2d785", + "publicKeyX": "0xe9858c125d88dc02336d7e5a6703511cd680918ebd04c7069904f23ce9c6af58", + "publicKeyY": "0x56f414cd2479f318ea758ee60f30125ce0b3f2d61f8389792767310c9494e7df", + "r": "0x90c6869fd2388fe0095c8969189d7c081d34516194b04d5cdbc9355670d3901c", + "s": "0x42e248bdc99b14d7b7955146f60ddebe81226c6f5d019805f590ee69cc19b0d6" + }, + { + "message": "0x3f7f6907af583b2d409a79312e4da4761da9f49dc18ff9ae5ea29bc86d45727e9bc21056b95a8d96e6f58007b7b4b385a30c21412606658ab60d5e0c42bc7fdc", + "privateKey": "0x6fb2fb37d2bca85518a428223babb2135d4226c2e2ec6a563f9d7ba935a5432b", + "publicKeyX": "0x5f71a53b45156b9003e1c5f52fbff490be2cbe2bff82ea715f6111ac78a8aa66", + "publicKeyY": "0x7c533b0fb58e3e978e52b570205d38496fe1c10f0cb0183e7b90f57ad92b99c7", + "r": "0x1b2de6c777682c5c09a877eb122bc628a297b098eaadc07dbf2bdf6ca67adcde", + "s": "0xa4c8e7b8ad3d83494568c5b227c7b5379c9b7ddef4a05c98497ecbbd119ab019" + }, + { + "message": "0x76ff344265f92857b11e25039b97a4b7d94a6c8209f4304e4c39f6c955663b7d7ec9e3beaeb1a2b92625190e96df22b4291e132535f66f6fa097d1e81bbab466", + "privateKey": "0x323e51081eeaf4638a746a9ed5c7eaf40ac8b787254e47455410e0a4a4c44956", + "publicKeyX": "0xe8d4853842b469e6c7ed3739e6b013b77e86ff66aecbcdd62010482b9ca049e4", + "publicKeyY": "0xa911d73cf93bec22d5b3587a3464db7415baea9ce307cdb3d7f6e1db7ed61068", + "r": "0x95b769b4464677df3ab1c69c785ad6ca7703225b24775d7b14f2ab6ab7b4c3c3", + "s": "0x0654cb60a8f904db678201ddac678ad312476b8ec0218199a217bc777f9a9f51" + }, + { + "message": "0x94b4345e9f004eb8ae96da2bd1f0356eeef249c8e275e78c27031da1eda3a9a4f2940052bb4619a7e5351f45d5b9d06d46226b9a647fe6f9eca5fe24c251d071", + "privateKey": "0x9f0e62e34a7243cc69ed908ddd98e35c0fac27f526c6583ac150339f58b536e3", + "publicKeyX": "0x4d500bf1b2a5834a79f427ecf63744bcebe329214a12a856151f58e3233628ca", + "publicKeyY": "0x918bcd4db20af18050e7e7718622680e4b18d8efd74589ebe1df342b2b6a7499", + "r": "0x35a21213ab758fa4d91bf3cbd35d845c2fa7c881dc83839f16ab66416def57b6", + "s": "0xde7acd6f179f6745de70f34698446fdc21ffc2c5c1b724217311182bf10bef25" + }, + { + "message": "0xde9524eebec68fd89c64883b78ca95a747b35690032c036119c39630623b7da15fb78cab54ceb9f88f466063b17f6fdbb1f7d10b626ab0ee34e55ab0bf362eac", + "privateKey": "0x533a1a73c5e5e3f2ccc5d61d338290088713d17c8f0d997aeecdb47e2443b9a8", + "publicKeyX": "0x63749c55916c2a0eb9ffa3950e0587febcde3ed860b0cb77cc43eca00bc51184", + "publicKeyY": "0x31d8bab66124605b4f3b477c6347402c4bcd3c24cbe9ee4c44848ab89b1812ba", + "r": "0xcb187e4bc648164adb8d1e999edfcdb9d710b81d8fa025347efc8961f2a5dca8", + "s": "0x8619dba0531e9d6be761da9cd7cae1711fb8a9bb6d6a394f6b963d311b2ffe20" + }, + { + "message": "0xf4f12716022a697f369447f4b8753e4b2a4f5d551379d067a78ef21c2821a2bd3fad2761b3f25205135247fc2b4b0b45026f838948ded35dcc9d5ad24b51cd3c", + "privateKey": "0xd0f5843f4963a11bc7910f9dc2e6b95bfc09a104b4be20e232b2aaaa0ef45209", + "publicKeyX": "0xe7ac2266a0aef49715a6d610ac39e50bf8f7174e3c169c911c20f7a0d9c5ba24", + "publicKeyY": "0x8eaa8784c1cd5c07b6b9ea2007b348baac6a6caff75e476e85107f832c1815de", + "r": "0x41ab877cce8620078dbee25fa4a7ae49a4eb570b374ee72251632a781e56874d", + "s": "0x1ae1044d948c56f7d83a9729a82e6a4ae53dc6ed6cfc17dae367b975d7fdd068" + }, + { + "message": "0x31a31029d10c07bc5f59c0d2fe1518ea5ffa7ad757b16099ff4e5b4bcc04ee0a62670d3bcf1c825c2e3b5b615cd5854af289b18d9c8b041a938b5caa470e32fd", + "privateKey": "0x90ffdeaaf96092d04bf141fc6d50e05909017eee71ad984c6eccdb5b5e6766b7", + "publicKeyX": "0x9197f038213d4766efac6638ef3bb4b30f37882ef09d068ccd5a496dad30c5f3", + "publicKeyY": "0x1f6d30912ec61f279bb890906de11e1085bdfcf64c5bf5a857e48f78a96a486f", + "r": "0xd1296024c9c98ed0011d3549db27f68f6c668c6b83252c0d6e2d514300d1d5c5", + "s": "0x1a587fb59c364037236920904d8f6490f26e9eee3c0b16725d5d0d6f3a66a342" + }, + { + "message": "0xcc50f956e3cb266ce5caa48de0d3e8c7507679e432e1d18b55b67ebeb582dddbc2e313c7127e56f8014911a9e132f212d86f5a43e40f23a7a1ba0dc6f466972e", + "privateKey": "0x65ba469a66b840a46eab6ae33a6355f360217ee6388a8f2be898b3b13403c6a6", + "publicKeyX": "0x5500eb7d078b84f62eaaf793680240f7d7c8f7bee2a2698a508f873f4dec9c11", + "publicKeyY": "0xbab65bf3b6b226a02cdbf327dc4b452a1dfcdf4d16d79b0562087d855685aa68", + "r": "0x66f4da7d354287e2c0b2829dfa32baf3457fa22bb70091d3d161ac00d15c677a", + "s": "0x784c1686603308fc3a8e44d13d16666f766f67334fe387620f644f320f5833bd" + }, + { + "message": "0x959a7b25474b9e8dc3c39a71ec5860ad29aad9e3e66cc846d67bee059abc3d8ad964161426291c275972fad02a636aa7971146378de827535646520afffb1a16", + "privateKey": "0x0bb1b4602df53001801a09afdff75d23166931e3b6dbe0224b02787ea81174ca", + "publicKeyX": "0xdee7aceb255f1a59645d22dc62ac120733cfb8cadda021604db04c2eddd34245", + "publicKeyY": "0x315f885c20e0369132b82a54c6c57dd156d18b05dfd350355008953f3023aea9", + "r": "0x4c1091af7b9eeb16dc85ed33dbc97196d9cd3d7ac1d926e92ec503165f69c97f", + "s": "0xd355763f0e2c6ee80c9742c0a606f45f4610ab61b5566168e059d470a9602c38" + }, + { + "message": "0xc0302aa5c44ab5e702fbc9c24a317744724c0ec0ab40cf47bf0215ffad90b2f8ba285a303571105989e3fbb398020378aee39d60f663fc0f2c87b63c449953fd", + "privateKey": "0x9bd89678b93347dbd2016501889e81aa4dc0a8edddec2f0c619f9e675f4b8ca4", + "publicKeyX": "0x05b9c39f63b1bf70a5690ededa3ad0d002364da909088820c6b23b3fa054bb88", + "publicKeyY": "0xea9638c127e1e9365c0229ccd036be77d14d825e69885ebfe91851991748918d", + "r": "0xc9cefea318d09382a7cd26af4da7854da0895018d80e70eb0e0a8985cd2d0068", + "s": "0xcb3106d9345a1f36c9578235bcc65f035609049f2d92fe0de87d0fb4c2692dd4" + }, + { + "message": "0xee67c073c32b8909fea183362051820953331684cc6051471dd51f590a82480461deed4e7580587d97db9f5f5aac89df4a9b39a5438cd919b7b96fba7593968a", + "privateKey": "0x9f4b7c6efc8a62cae4c9028a92bb40fc1b06f0b3347766f87fc47cd176a827e4", + "publicKeyX": "0xe4771b6b3309f291622fb9dd0f076de9f7d83128313041b47c8456c2b73d1559", + "publicKeyY": "0xd53db97cd8af1ec59869029230403b4ed96ce36a82b6ee2553776dbb85c046f8", + "r": "0x9ec27b1af10a587beebb668ede3bc05d8f9a2a4f48949e1b0bbbb96244dbaa63", + "s": "0x20c4beb3b2fe8b4d26eb5f7cacd8703b9ef6a79365f6d85fcaf81fb0f6f21e14" + }, + { + "message": "0x97253d6f28557b4c1454a2cc4ad91e80bd96c5c696b05dd5326714ed369c8f2e276568e8ae4f63cda85bf20f74c59decef3934ba28062cdb97f6def002f55feb", + "privateKey": "0x72a8a4e4f476db9a13964f7034b964bbb52d8d1d55166a0c94b504ca5ee076cd", + "publicKeyX": "0xc791ce496ca0ee7362915315a640d07a55760c3044eef8a0025a66620698793d", + "publicKeyY": "0x1a4abb4260ab1baa7cc240e95ebb506fb57d128c4cf089d2f6c8a361bd5419fe", + "r": "0x32ec021a1fd60e9aafabc4367afc105249ef789ae82133a82a19aee7ec7d384e", + "s": "0x5b9acb6bcfdf457ffd06d06ce5170ede38f8a133075a0314e17ece58279c2584" + }, + { + "message": "0x87c432c2e5077118902907ac18c5d4648cf06803c89c3ec332ec518a01a4c5c918b5b6f3ade94ff3ac1ae9abd129994ecc2bc0647dcf3777cf5682949eb5cd13", + "privateKey": "0x838f38c064258fa7b6cb64c5dd1939104c5316f8e6bef8b3d9253ed01349984d", + "publicKeyX": "0x5d1a973b0ed60c271884196658e5d6ba6aee09ec0854e2705336ff56d298a434", + "publicKeyY": "0x5ddb2511afa851941f460757e6691034b68f9c6e8fe7440679ad857963c2a60a", + "r": "0xabc2f9ee94a2136addae98931e5aab20a06e17a9c81dd6e5dff7eadf25d6fe4d", + "s": "0x76ab4a1a05d5024a7568f2f2fcb394b1a145a8610d23ed3f34d2dfd7adbda49a" + }, + { + "message": "0x9e5e9612a939a87c19c8fe15e5ec8a218f4acc592bc1eb8fd4569498dc5477973d3227545fbc62ec5ef4eb3577a5f6c0b7ab63dc26c905c0c87c21b03d084ef5", + "privateKey": "0x52d547244d0c707b35b2fcb80814e82024fab656afd6f8c8bf843363bdbf3029", + "publicKeyX": "0xaa88cc2a5f66a454f8c0d7271b1f2579a16773fc54542f4c59ff0cb3aba0213c", + "publicKeyY": "0x8df5e9696813e23f861d10ad8e4fc132acab02e0a31bbffb695de98a4d86c132", + "r": "0x4e29b3f89b80b590e205d38eb4724db2db79e5f29c7cb745aabfeb5884913e0d", + "s": "0x41cc45b26b1143995ba043954b11417f29653e45f86feb61d91a9ddf62b5f403" + }, + { + "message": "0x672927413db7f847c49d9c0eb185162be2c376092202523a0a7286faa987aa2deafad3bd38be1e7647b836d07c18c4478364d2ff9f73ca89ccace5b2c99b8a23", + "privateKey": "0x5a7bf156ccfa99c138d2898719ed4e481ca95ca27e835ad5faa4160dd838a4ba", + "publicKeyX": "0xa2eb501e108dc413ea21e61405da9ec014371dc88cc19c80ba91d55b19b52ea3", + "publicKeyY": "0xf65feb28addbde31004c62ad717cc47cb0dcd8349f9e785f46f69506f1af2fb9", + "r": "0xa5dbe34ed8ac2cecfe74df4541949960f1b24db29f48903536ee5a0e351dc709", + "s": "0x79726bda19124cff2b032e942f0fada54df9a5e1f86131c3a1c1551d7684c9fc" + }, + { + "message": "0xb9a3d42cd4c7a44cc2dda51f0fe7ee6ddcdf4bbbd2ceaf11c6c9a1468562f486177fd44727dbd3f23bedc3777095418643eb2d245199b67d3cf16685095b21dd", + "privateKey": "0x47d544eea41d2d09fcc17f18de7e466ef62af8aa754c3928ba21d38f4cace4b3", + "publicKeyX": "0xe64b8dc3489f5a67b08dc7ddb5410c045df012a7c1c698e0153f44622dea449a", + "publicKeyY": "0x569d9fb8aa848f201b5257f380fda72e721239d22fdebbaa676adab067d4fc7a", + "r": "0x0b925863d8cc307f83a09f7c9cf0744a84aa4ad648c69c042c13b64b5a7acd0b", + "s": "0x3d65e68ec91daed6f894416e959b134352b144d588c09529b40506e007908b1e" + }, + { + "message": "0x7660ee3683f34612665c87aa095a93ada157b4f7d70b16178c4c25d38e758a4ef1d9d9ddea7bdfaccba7f686d37bfc4bfde0a6392777fbfb2886359a25cfc818", + "privateKey": "0xf6d77da3b471cdf595ec8b0a830abf6c607cfbe24f187a6e2eb04c011df45765", + "publicKeyX": "0xb829fdc7222a1e2cf238dfc479fc5924ef0938c921a483eb6dedaf648d8379e3", + "publicKeyY": "0x1f11f88223cae84fb74b1bd7217515733e894c58f48356f5d9886ae475534a26", + "r": "0x12c0fd9b479d914065f932a75e3eef4e1afacf778c820bf27a41eeade4adff8c", + "s": "0x10a53c322c92a32df2bdd04287fda86b9193581a82ffeb45e1e287bc265ff8d3" + }, + { + "message": "0x8f9d663162d55ab7e45b3a9c8cd54bc7ba9fdc6ca9938b473afb71fc013990957f9fbcc251bfc3a3dc8c1affb05bb3127197259a1dfda1edf70b074a7bf78c0e", + "privateKey": "0x98f6d7fd1dabb500e4a81fbd39825ae4c1617eaf0d11d19518ffae812fdaecfc", + "publicKeyX": "0xcb778a516abb450f42ec27f7840c4d8b9745e3b1117042d4bb38eb3e25f99d5d", + "publicKeyY": "0xe596788f4a0f1ba82132012a4987424c9f0ba48fa1144e7a6fa3f3d3960a9074", + "r": "0x4a79a9b9249dcb68a5e0b3e6562b064b6f87a5fe31f9c32d573dc3ae96410b70", + "s": "0xdd87c01fc913aa3f71914cc29df40e14455b9802912e8cd20a7b71d54c1f2379" + }, + { + "message": "0xc5b9dfc7c64e8f55027246c6b48e2c515390a5b600fe61f82176500135c4b91746c2f53af3c3e92ad4b0653b57f4e36003a8d69a765d8940f82f1db3dd613fbb", + "privateKey": "0x9adf364ef94c584604d7fef247568c1255c16e3122320af40b2a91aced96a687", + "publicKeyX": "0xa3895f2baa13db8a5cc918a5be05f8aae9dde30749c9daf087496fcbef54aafd", + "publicKeyY": "0x0bb3ede09cd8d745cf1f5d97b17b7ef5fde49570f47da199ff0af99002e3bd95", + "r": "0xcc74ad34466288c00b39f3c47623ab3c014a0da1245b8cafa1066f01d0c7d3aa", + "s": "0x3dbc6740c1b614a78eaf24f6b6037e10e519b483f98de754b1b55a6e6c3364bb" + }, + { + "message": "0xd70f01d1d9999901e9c57ae27188fbbcdfa120a18caa40f605983fb73371903d58a5812d5fcde8841cdda5d779f3a84234403aaf0c435f15e92a458d82f5bd84", + "privateKey": "0x4cfe8c3cd8e3c3dd441f94e18009533f08c0bab46d17a452ff6b43790d95b99b", + "publicKeyX": "0x0807adae8ab65f658fc0e2defed1a24d4f1022ed9c7e278141c6b8f1857198ed", + "publicKeyY": "0xee3300df3f9aac17e75e8b70f80a46a8b6166aecc85f509cf7ea93a81b50c2e7", + "r": "0x09fd9fb94166ffeb50fb56541980a67b4cd0854497393ad30277a3689a4071a5", + "s": "0x1d4edf0b0a8064fe3f3775047f71c9e1eb281a6549c3c4d855a95af132c34527" + }, + { + "message": "0x436e3532bc214df3cec93cff8c37f8580be160f24abd659f6bdf85fd496455d95dbf39a165eb37505d798e3fcfa1f811e77c702887a8ef6151439b15453e869a", + "privateKey": "0x1efede4140f1f429f73593937788de56e744073b0f877d5ab3c88ef9f6e64191", + "publicKeyX": "0x51d695528c04b14a22bf4f539f386070a99a0fcd13651f2eafc8a19339ae3654", + "publicKeyY": "0xe33603e25c6dca148f4712dfa03be15074513bfd1b4c19b569e063786143df38", + "r": "0xbadbd8ff0e2003a19ce3ac6f8da4b3250019b5a49ceee6f13e10433cf36607bb", + "s": "0xad2fe6ca0c02502b3a38999091e14f203471858553460a691bf73a5e66f3d3ef" + }, + { + "message": "0x2049b4e9b0b7472b39edd06e14aab2ad234349f6ad73d37e2efc3ad26eead28e7f205267012133a605bc63e24b7d7b645b024eb4b72dcd00615f7706eff6ec13", + "privateKey": "0x04bcef2a79e6546dbf54f93daf43ed250b900a4280d0e422dc08e6443d8d04f6", + "publicKeyX": "0x5f5db2fcbc1a8927167566146680f2bd753e468ff368226e365c0cfa05b43e54", + "publicKeyY": "0x12c9555f7230fc221539327c8d444a077552456e62a6935c53630cbb29873921", + "r": "0x465e600bdf1c0fbd0b1330feec591273589d4154691fd5693488d5d2d85a43b5", + "s": "0xc2cb30b18f24954060c3a3d11110e6c95e8d0d797854bf318762f474ca41e0b3" + }, + { + "message": "0x3cdeaa216dccec8181c07a2b9c828b3f184fb8868af04356c4c0de791e922929d930b6fed9bb4dd430ba768ab0b85be8889193b53388c360732012e10e1877d9", + "privateKey": "0xd724b6f976e0faeeedc767c0f854f115118770b5e7cb7259031d7a87dca60b14", + "publicKeyX": "0xc2512b047c4578bbddcbcf849800d5ea8176d5d696ddca4f87ca8edee5d24196", + "publicKeyY": "0x72eacda14b42ce369edfac74511f321af001b0083a8fc645d2cb03949a506b80", + "r": "0x1ece0c3dd8e3899bf4f3ec0e056eb11608f2b21f68747933adf7c09af8e1e265", + "s": "0x403dfb6515abc9853aa129a734d3efd89264e7bf9612eda083d8d7a315a3449e" + }, + { + "message": "0xbb443489142471e4952d5af24b76c7dd8da389293c2ef488d9ea8922d52caddb0825dab3c6e7766c98d2f1304b3373556bd28b2c00ad89d5c109607adc352772", + "privateKey": "0xa10913ed4c6d362efcdeba63f747128e8126f9167f3cc1cce723c70e6e66a799", + "publicKeyX": "0xc06e0a02014f79d2ed3c04761ac60a2f766cfba69742c7a0045229ab88420339", + "publicKeyY": "0xbadc48458da06e4816f8e592027b39e43a824471c31740093c14e2d2c2159d86", + "r": "0x87c523eb21c95d1d5eeb02c79dd9eeb823d28919bae8365b73f954275cf1b070", + "s": "0x1020f7d61ea4e10096da38edab2e3cf12e2af5a43ef431183ca8b57c84b11298" + }, + { + "message": "0xc79470aad9a65d57f6617040141ef4dfe51ec2e31091a4c5cc3035bb352c9d7887cadb7ea3b8fc3171eb2033c315f79166aba288db6f4e3399b579cb9cec4873", + "privateKey": "0xb9d22e83ae514f23e7128ea2738e27d84441e07c8237d61f7c52f0844af1474d", + "publicKeyX": "0xb587e485e36b5f55514b4355a7109f7bddfcc243fc04016e436e0b67ca59cd55", + "publicKeyY": "0x4ba9c42a1ef72017aa6b6e44e42460da5f369e8a416de7e2edeb44f01e0d06f3", + "r": "0xab9f79fd27f3b878e0c2e49cdfe07695184dae67de29f5c4d0fcc403a41841da", + "s": "0x7e183b015fc6eaf12c5ea56b8afae186fe6decd713c9591612573dfe204f5b2e" + }, + { + "message": "0x40c08101a056bb54c2a9220bd8826b371dd6ce01953c9194ee149a62e70edd914df6264d44108f995818169c57cf96d324a6059c5f026d6b95c0f11f15afe8e1", + "privateKey": "0x0efe34919adb2dec0d6bb400674a53206d409498365d7213b7a813aea93c82b7", + "publicKeyX": "0xdfb1e4952635f480bdac9fa3cc447c19da986794864dc2efdb16e5e8974c1d10", + "publicKeyY": "0x1d4ad26ab602bcfa16a42929dc8b7c8b866d77c5629dc95edfcdf1f9e7a55f4f", + "r": "0xe4ee8b6cf5fe4c34c2f6c09a8a8ebd859d86fb8c4fc7b0abc24cbecf0e7e900b", + "s": "0x87c9b55ea4d9b0ba47674aaa39f511464460ce9730585d517397d91ea371ad1f" + }, + { + "message": "0xb76c73fef307f2cda625ed5ee301957d7f7e395b0fff0f0316e4ebcf4dccbe61a8ffae916807364026dc89c3b0ccb6ba5d8325afe2c30f5fa1517dd6932187ac", + "privateKey": "0x04b6e9ab1d495a441c05a638c717eaf9edb133326ec0dfa6e63654d09913cb59", + "publicKeyX": "0x6b48bbac449551c8145ab3bd691ec017feb1967aa38fa6cadc2c591f2d75988a", + "publicKeyY": "0xcedeeb469c74e8b8961efc31d224d8cebe6afb742b7bb7e0d3886f9461e63095", + "r": "0x5333f2358cdcc38ebf37ff9d409c0b30ec33cb2916c041657cefa129de89f44d", + "s": "0xb872d4b4e4ceb3a767f44690e4cf98293f50f9ab195dc424ff014d16fdacdfc0" + }, + { + "message": "0x95c48e3f3e6b00eb88c65ad2c7687a2c7fe3f6b81d52b6458a426a9963bb24e7cafd112b7ca7342d0afae1ad1106d1e456b358af525d83e28e8b4b3227e2a39b", + "privateKey": "0x51df3ca11f2b858c450a9269d9e5e91e4221b1253a06a3c252c8b3ee364f4a20", + "publicKeyX": "0x9906ac8acfd8454af8296e733c8207507ece05823978888abcb6fe2f97c0b78e", + "publicKeyY": "0x7e1bf8ac80e92a961d9fa42f643046de3e5b5e30fc42d5f7ecc8050752753c81", + "r": "0x98675ff69c8fa590315471862e8ef92cab2b1e0bccb046ed4889d94eb201161a", + "s": "0x0250c38660376c8fab3c06d4d817b275ee516b9b9067683b69031ea1c6bff927" + }, + { + "message": "0x0633af7687f2b4a46fa25725ce6dcb8e72b8d702504f20499721c3456680a60403f96e4e8a3eee7c4f56bafe4e3a90ad14906cbcbff8b8fdae45c52c56254f0d", + "privateKey": "0xf59ccb86079ac799d90a37af59197b45e1008c39f53a6c594a0a4f968395673e", + "publicKeyX": "0x89964f6cdc562bcc577ed261ac2ecf0c17a7ab84b98876638dba7e8d11130e8e", + "publicKeyY": "0x9856d27a4aabca88c5a6dd780c84c4dca14cc62f46c577adcb0c798d553f4cdf", + "r": "0x00d2f3460a294a5ab77241d67b759f03d96532ba99afc416d812a03b6b69cfdb", + "s": "0x78a8f4ec94ff6f918bfa36b4346226c671492924964c269ac81805851ea7c41a" + }, + { + "message": "0xb5fec56816360d29f244f9eb60830932189ce476cce7a37f919936772d71b64c354267ba6ebe11307b836c4be0b7ea06002d82fd1199c15ca2b71478895793cc", + "privateKey": "0x74430f9a5fe3cd365bfcf0741325d14288196a6f352176e059cb19a0fe395082", + "publicKeyX": "0x65309581a64047d72103d78d96a2ccabfb832f3763b1d7e34e88c9a8b80b7a81", + "publicKeyY": "0xbc07fa5b60f58ee956cb11210c19c16e4de3aa7f274dca69c24a8df6f060de18", + "r": "0x462ce44fd3af252d39cdd77616cd82ef63f645becf2f2651d7f3afab86c41fb9", + "s": "0x832fedae72e9ea1545d64698445bd83a297b3297e5245fe99eba1c9e72c0b522" + }, + { + "message": "0xfb6cabb3ef2bc555e1877d3e4271c9ea6676a21ddbedb8e19f9503dbcb9d4a0588d7d8830e621ecab91f28239e34bbd0c0f9a9a8baaae03d343690921142ed04", + "privateKey": "0xa3b3716f5abceb2c0a20473bb3f2ffbad75edb89c604d8ca68b59487f46c7ac7", + "publicKeyX": "0x2f5aefb3c29db2c381745c5088dca1dc494162c7575edbaf07c1b82528672823", + "publicKeyY": "0xe619636863f3486d34733541450733829606dd84cec3385b47d8bfe655f6d39a", + "r": "0x361ec469e640290b3d867587a465ed955826ef3aa46a6a171a6a9d5681a7db2c", + "s": "0x32af7e72fb8a1a30feaf6b6e50802f14f606ad8aafd07346a33bdfd790479585" + }, + { + "message": "0x0c3810263290b571151f69115af63c3a3396a3cd95c0101b51e98921d719869b525a5d17ab8ad77f60e782d416834d92574cf0e8a0becf26d600f4eed94abc75", + "privateKey": "0x2ed3c95e657cea435777aa6f9e413938d7725161fd102f1113b45956e70d1df3", + "publicKeyX": "0x0f3c08b1bb79b9507dcd50f48f93f06e51212fb549ecd4d7b004053a15511091", + "publicKeyY": "0x623081ea26fad543873751f147e84cf535156efdf972699de0465eeb30db69f1", + "r": "0xb4070b007cd021cc61cc91c6c9a4cb59d8a2a6d9f2f5ff74d661d985f60af0cc", + "s": "0x82a2e41f84ca352ff39b223fc7615e04c68990b5e9524f6f9813f6ecc101e82a" + }, + { + "message": "0x19fa9f79cec471d6e77664e8496e1291e2ce955751a51bbf35b424d82ddf7aa6dfb4c7ff2173280c7a1f92c9a6645de9cf9806a25eac983e0ff567dd3bdd8536", + "privateKey": "0xc6695470d9d1e3e7e97e2e6253d8875c5d263cca22d106ab097e9c7a4320a8c7", + "publicKeyX": "0xd94b868e9cfef94e46daf1fd78dd5811f6723c70f932f3cbe82e258c07f0d1e4", + "publicKeyY": "0x9bc37091c57f9a7072d58191edbaf4ff44568de843bb158f7f11aa966f8e4612", + "r": "0x56d945d2ff488f6bdd0bb4a53cecb611c38a8d4d47b63b7afa02cec880f33ae3", + "s": "0xf4398d4054c9ca8a97f93dbcd68e0eddd7b9a23022b42fb3eba17af753bbaa78" + }, + { + "message": "0x2c074a429fd318dda6cace0cf4a9e14c0a07c1b8d346b3be1aec5c162956214a9cc51c44138b07b2931181eac0841ac18a7531c60548f731bb4fb106461193db", + "privateKey": "0xe58d06b750e3efa6116f7a69683880c557df1fa0efae6e9018f6c21e911c86b7", + "publicKeyX": "0xca8b11be65f6dac4c12f6addba82f216ef8a2fd26edd2946f6919d0356c1d9ce", + "publicKeyY": "0x8d6b6432112b1dcf682f754d0c3d48e5e0d991e18de99a8b3fb6f0aa7d51a9da", + "r": "0x36b0ba98d6e646c67e64c2a0e482042043975510baa3864fc8ee0102d7ab8e8e", + "s": "0xadbe22e7ff3ee3b17129c4fcfb3a33d46a771962e368b9803b13daa1faece7b0" + } +] \ No newline at end of file From a4ca057f1c2890683586c9a0f83b88feab1706b3 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 21 Dec 2024 13:13:27 +0100 Subject: [PATCH 09/52] [ecdsa] handle some `.noinit.` cases --- constantine/ecdsa/ecdsa.nim | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/constantine/ecdsa/ecdsa.nim b/constantine/ecdsa/ecdsa.nim index 797efc3ac..db38c29fe 100644 --- a/constantine/ecdsa/ecdsa.nim +++ b/constantine/ecdsa/ecdsa.nim @@ -90,7 +90,7 @@ proc randomFieldElement[FF](): FF = result.fromBig(b) -proc arrayWith[N: static int](val: byte): array[N, byte] = +proc arrayWith[N: static int](val: byte): array[N, byte] {.noinit.} = for i in 0 ..< N: result[i] = val @@ -109,7 +109,7 @@ template round(hmac, input, output: typed, args: varargs[untyped]): untyped = hmac.update(args) hmac.finish(output) -proc nonceRfc6979(msgHash, privateKey: Fr[C]): Fr[C] = +proc nonceRfc6979(msgHash, privateKey: Fr[C]): Fr[C] {.noinit.} = ## Generate deterministic nonce according to RFC 6979. ## ## Spec: @@ -150,7 +150,7 @@ proc nonceRfc6979(msgHash, privateKey: Fr[C]): Fr[C] = hmac.round(k, v, v) # v becomes T # Step h.3: `k = bits2int(T)` - var candidate: Fr[C] + var candidate {.noinit.}: Fr[C] # `fromDigest` returns `false` if the array is larger than the field modulus, # important for uniform sampling in valid range `[1, q-1]`! let smaller = candidate.fromDigest(v) @@ -165,13 +165,13 @@ proc nonceRfc6979(msgHash, privateKey: Fr[C]): Fr[C] = hmac.round(k, k, v, [byte 0x00]) hmac.round(k, v, v) -proc generateNonce(kind: NonceSampler, msgHash, privateKey: Fr[C]): Fr[C] = +proc generateNonce(kind: NonceSampler, msgHash, privateKey: Fr[C]): Fr[C] {.noinit.} = case kind of nsRandom: randomFieldElement[Fr[C]]() of nsRfc6979: nonceRfc6979(msgHash, privateKey) proc signMessage*(message: string, privateKey: Fr[C], - nonceSampler: NonceSampler = nsRandom): tuple[r, s: Fr[C]] = + nonceSampler: NonceSampler = nsRandom): tuple[r, s: Fr[C]] {.noinit.} = ## Sign a given `message` using the `privateKey`. ## ## By default we use a purely random nonce (uniform random number), @@ -225,7 +225,7 @@ proc verifySignature*( ## Verify a given `signature` for a `message` using the given `publicKey`. # 1. Hash the message (same as in signing) let h = hashMessage(message) - var e: Fr[C] + var e {.noinit.}: Fr[C] e.fromDigest(h) # 2. Compute w = s⁻¹ @@ -248,11 +248,11 @@ proc verifySignature*( # 6. Verify r_computed equals provided r result = bool(r_computed == signature.r) -proc generatePrivateKey*(): Fr[C] = +proc generatePrivateKey*(): Fr[C] {.noinit.} = ## Generate a new private key using a cryptographic random number generator. result = randomFieldElement[Fr[C]]() -proc getPublicKey*(pk: Fr[C]): EC_ShortW_Aff[Fp[C], G1] = +proc getPublicKey*(pk: Fr[C]): EC_ShortW_Aff[Fp[C], G1] {.noinit.} = ## Derives the public key from a given private key, ## `privateKey · G` in affine coordinates. result = (pk * G).getAffine() From 22bd57b5dc5392a8b9e517e5d283d73a892faed9 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 21 Dec 2024 13:15:01 +0100 Subject: [PATCH 10/52] [ecdsa] turn `toBytes`, `arrayWith` into in-place procedures --- constantine/ecdsa/ecdsa.nim | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/constantine/ecdsa/ecdsa.nim b/constantine/ecdsa/ecdsa.nim index db38c29fe..a12031e72 100644 --- a/constantine/ecdsa/ecdsa.nim +++ b/constantine/ecdsa/ecdsa.nim @@ -32,9 +32,8 @@ proc hashMessage(message: string): array[32, byte] = h.update(message) h.finish(result) -proc toBytes(x: Fr[C] | Fp[C]): array[32, byte] = - let bi = x.toBig() - discard result.marshal(bi, bigEndian) +proc toBytes(res: var array[32, byte], x: Fr[C] | Fp[C]) = + discard res.marshal(x.toBig(), bigEndian) proc toDER*(r, s: Fr[C]): seq[byte] = ## Converts the given signature `(r, s)` into a signature in @@ -45,9 +44,14 @@ proc toDER*(r, s: Fr[C]): seq[byte] = # Convert signature to DER format result = @[byte(0x30)] # sequence marker + template toByteSeq(x: Fr[C]): untyped = + var a: array[32, byte] + a.toBytes(x) + @a + # Convert r and s to big-endian bytes - var rBytes = @(r.toBytes()) - var sBytes = @(s.toBytes()) + var rBytes = r.toByteSeq() + var sBytes = s.toByteSeq() # Add padding if needed (if high bit is set) if (rBytes[0] and 0x80) != 0: @@ -90,9 +94,9 @@ proc randomFieldElement[FF](): FF = result.fromBig(b) -proc arrayWith[N: static int](val: byte): array[N, byte] {.noinit.} = +proc arrayWith[N: static int](res: var array[N, byte], val: byte) = for i in 0 ..< N: - result[i] = val + res[i] = val macro update[T](hmac: var HMAC[T], args: varargs[untyped]): untyped = ## Mini helper to allow HMAC to act on multiple arguments in succession @@ -115,19 +119,21 @@ proc nonceRfc6979(msgHash, privateKey: Fr[C]): Fr[C] {.noinit.} = ## Spec: ## https://datatracker.ietf.org/doc/html/rfc6979#section-3.2 # Step a: `h1 = H(m)` hash message (already done, input is hash), convert to array of bytes - let msgHashBytes = msgHash.toBytes() + var msgHashBytes {.noinit.}: array[32, byte] + msgHashBytes.toBytes(msgHash) # Piece of step d: Conversion of the private key to a byte array. # No need for `bits2octets`, because the private key is already a valid # scalar in the field `Fr[C]` and thus < p-1 (`bits2octets` converts # `r` bytes to a BigInt, reduces modulo prime order `p` and converts to # a byte array). - let privKeyBytes = privateKey.toBytes() + var privKeyBytes {.noinit.}: array[32, byte] + privKeyBytes.toBytes(privateKey) # Initial values # Step b: `V = 0x01 0x01 0x01 ... 0x01` - var v = arrayWith[32](byte 0x01) + var v: array[32, byte]; v.arrayWith(byte 0x01) # Step c: `K = 0x00 0x00 0x00 ... 0x00` - var k = arrayWith[32](byte 0x00) + var k: array[32, byte]; k.arrayWith(byte 0x00) # Create HMAC contexts var hmac {.noinit.}: HMAC[sha256] From 4e17514c43d0d5597efa9d1f4e66ef6569f36c1e Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 21 Dec 2024 13:15:27 +0100 Subject: [PATCH 11/52] [ecdsa] clean up comment about Fp -> Fr conversion --- constantine/ecdsa/ecdsa.nim | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/constantine/ecdsa/ecdsa.nim b/constantine/ecdsa/ecdsa.nim index a12031e72..000b7674b 100644 --- a/constantine/ecdsa/ecdsa.nim +++ b/constantine/ecdsa/ecdsa.nim @@ -198,9 +198,8 @@ proc signMessage*(message: string, privateKey: Fr[C], # `r = k·G (mod n)` let r_point = k * G # get x coordinate of the point `r` *in affine coordinates* - let rx = r_point.getAffine().x # element of Fp - ## XXX: smarter way for this? - let r = Fr[C].fromBig(rx.toBig()) + let rx = r_point.getAffine().x # element of `Fp` + let r = Fr[C].fromBig(rx.toBig()) # convert to `Fr` if bool(r.isZero()): continue # try again @@ -247,7 +246,7 @@ proc verifySignature*( let point2 = u2 * publicKey let R = point1 + point2 - # 5. Get x coordinate and convert to Fr (like in signing) + # 5. Get x coordinate (in `Fp`) and convert to `Fr` (like in signing) let x = R.getAffine().x let r_computed = Fr[C].fromBig(x.toBig()) From 46d8f928921be798dc6fcab08f194460330c5359 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 21 Dec 2024 13:25:51 +0100 Subject: [PATCH 12/52] [ecdsa] replace toPemPrivateKey/PublicKey by in-place array variants --- constantine/ecdsa/ecdsa.nim | 56 +++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/constantine/ecdsa/ecdsa.nim b/constantine/ecdsa/ecdsa.nim index 000b7674b..b5c3da24c 100644 --- a/constantine/ecdsa/ecdsa.nim +++ b/constantine/ecdsa/ecdsa.nim @@ -262,32 +262,37 @@ proc getPublicKey*(pk: Fr[C]): EC_ShortW_Aff[Fp[C], G1] {.noinit.} = ## `privateKey · G` in affine coordinates. result = (pk * G).getAffine() -proc toPemPrivateKey(privateKey: Fr[C]): seq[byte] = +template toOA(x: openArray[byte]): untyped = toOpenArray[byte](x, 0, x.len - 1) + +proc toPemPrivateKey(res: var array[48, byte], privateKey: Fr[C]) = # Start with SEQUENCE - result = @[byte(0x30)] + res.rawCopy(0, toOA [byte(0x30), byte(0x2E)], 0, 2) # Version (always 1) - let version = @[byte(0x02), byte(1), byte(1)] + res.rawCopy(2, toOA [byte(0x02), 1, 1], 0, 3) + # Private key as octet string - let privKeyBytes = privateKey.toBytes() - let privKeyEncoded = @[byte(0x04), byte(privKeyBytes.len)] & @privKeyBytes + var privKeyBytes {.noinit.}: array[32, byte] + privKeyBytes.toBytes(privateKey) + + res.rawCopy(5, toOA [byte(0x04), byte(privKeyBytes.len)], 0, 2) + res.rawCopy(7, toOA privKeyBytes, 0, 32) # Parameters (secp256k1 OID: 1.3.132.0.10) - let parameters = @[byte(0xA0), byte(7), byte(6), byte(5), - byte(0x2B), byte(0x81), byte(0x04), byte(0x00), byte(0x0A)] + const Secp256k1Oid = [byte(0xA0), byte(7), byte(6), byte(5), + byte(0x2B), byte(0x81), byte(0x04), byte(0x00), byte(0x0A)] + res.rawCopy(39, toOA Secp256k1Oid, 0, 9) - # Combine all parts - let contents = version & privKeyEncoded & parameters - result.add(byte(contents.len)) - result.add(contents) +proc toPemPrivateKey(privateKey: Fr[C]): array[48, byte] = + result.toPemPrivateKey(privateKey) -proc toPemPublicKey(publicKey: EC_ShortW_Aff[Fp[C], G1]): seq[byte] = +proc toPemPublicKey(res: var array[88, byte], publicKey: EC_ShortW_Aff[Fp[C], G1]) = # Start with SEQUENCE - result = @[byte(0x30)] + res.rawCopy(0, toOA [byte(0x30), byte(0x58)], 0, 2) # Algorithm identifier - let algoId = @[ + const algoId = [ byte(0x30), byte(0x10), # SEQUENCE byte(0x06), byte(0x07), # OID for EC byte(0x2A), byte(0x86), byte(0x48), # 1.2.840.10045.2.1 @@ -296,18 +301,27 @@ proc toPemPublicKey(publicKey: EC_ShortW_Aff[Fp[C], G1]): seq[byte] = byte(0x2B), byte(0x81), byte(0x04), byte(0x00), byte(0x0A) # 1.3.132.0.10 ] + res.rawCopy(2, toOA algoId, 0, 18) + # Public key as bit string - let pubKeyBytes = @[ + const encoding = [byte(0x03), byte(0x42)] # 2+32+32 prefix & coordinates + const prefix = [ byte(0x00), # DER BIT STRING: number of unused bits (always 0 for keys) byte(0x04) # SEC1: uncompressed point format marker - ] & @(publicKey.x.toBytes()) & @(publicKey.y.toBytes()) # x & y coordinates + ] + + template toByteArray(x: Fp[C] | Fr[C]): untyped = + var a: array[32, byte] + a.toBytes(x) + a - let pubKeyEncoded = @[byte(0x03), byte(pubKeyBytes.len)] & pubKeyBytes + res.rawCopy(20, toOA encoding, 0, 2) + res.rawCopy(22, toOA prefix, 0, 2) + res.rawCopy(24, toOA publicKey.x.toByteArray(), 0, 32) + res.rawCopy(56, toOA publicKey.y.toByteArray(), 0, 32) - # Combine all parts - let contents = algoId & pubKeyEncoded - result.add(byte(contents.len)) - result.add(contents) +proc toPemPublicKey(publicKey: EC_ShortW_Aff[Fp[C], G1]): array[88, byte] = + result.toPemPublicKey(publicKey) ## NOTE: ## The below procs / code is currently "unsuited" for Constantine in the sense that From 7164f0785667e2d8e5c6cd9b320d87526b205287 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 21 Dec 2024 19:44:09 +0100 Subject: [PATCH 13/52] [ecdsa] replace `toDER` by non allocating variant Inspired by Bitcoin's implementation: https://github.com/bitcoin-core/secp256k1/blob/f79f46c70386c693ff4e7aef0b9e7923ba284e56/src/ecdsa_impl.h#L171-L193 Essentially, we just have a 72 byte buffer and only fill it up to `len` bytes. --- constantine/ecdsa/ecdsa.nim | 95 ++++++++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 28 deletions(-) diff --git a/constantine/ecdsa/ecdsa.nim b/constantine/ecdsa/ecdsa.nim index b5c3da24c..a19a1d594 100644 --- a/constantine/ecdsa/ecdsa.nim +++ b/constantine/ecdsa/ecdsa.nim @@ -20,6 +20,14 @@ type nsRandom, ## pure uniform random sampling nsRfc6979 ## deterministic according to RFC 6979 + ## Helper type for ASN.1 DER signatures to avoid allocation. + ## Has a `data` buffer of 72 bytes (maximum possible size for + ## a signature) and `len` of actually used data. + ## `data[0 ..< len]` is the actual signature. + DERSignature* = object + data*: array[72, byte] # Max size: 6 bytes overhead + 33 bytes each for r,s + len*: int # Actual length used + # For easier readibility, define the curve and generator # as globals in this file const C* = Secp256k1 @@ -35,41 +43,72 @@ proc hashMessage(message: string): array[32, byte] = proc toBytes(res: var array[32, byte], x: Fr[C] | Fp[C]) = discard res.marshal(x.toBig(), bigEndian) -proc toDER*(r, s: Fr[C]): seq[byte] = - ## Converts the given signature `(r, s)` into a signature in - ## ASN.1 DER encoding following SEC1. +proc toDER*(derSig: var DERSignature, r, s: Fr[C]) = + ## Converts signature (r,s) to DER format without allocation. + ## Max size is 72 bytes: 6 bytes overhead + up to 33 bytes each for r,s. + ## 6 byte 'overhead' for: + ## - `0x30` byte SEQUENCE designator + ## - total length of the array + ## - integer type designator `0x02` (before `r` and `s`) + ## - length of `r` and `s` ## - ## Note that the implementation is not written for efficiency - ## and should be viewed as a convenience tool for the time being. - # Convert signature to DER format - result = @[byte(0x30)] # sequence marker + ## Implementation follows ideas of Bitcoin's secp256k1 implementation: + ## https://github.com/bitcoin-core/secp256k1/blob/f79f46c70386c693ff4e7aef0b9e7923ba284e56/src/ecdsa_impl.h#L171-L193 + + template toByteArray(x: Fr[C]): untyped = + ## Convert to a 33 byte array. Leading zero byte required if + ## first real byte (idx 1) highest bit set (> 0x80). + var a: array[33, byte] + discard toOpenArray[byte](a, 1, 32).marshal(x.toBig(), bigEndian) + a - template toByteSeq(x: Fr[C]): untyped = - var a: array[32, byte] - a.toBytes(x) - @a + # 1. Prepare the data & determine required sizes + + # Convert r,s to big-endian bytes + var rBytes = r.toByteArray() + var sBytes = s.toByteArray() + var rLen = 33 + var sLen = 33 + + # Skip leading zeros but ensure high bit constraint + var rPos = 0 + while rLen > 1 and rBytes[rPos] == 0 and (rBytes[rPos+1] < 0x80.byte): + dec rLen + inc rPos + var sPos = 0 + while sLen > 1 and sBytes[sPos] == 0 and (sBytes[sPos+1] < 0x80.byte): + dec sLen + inc sPos + + # Set total length + derSig.len = 6 + rLen + sLen + + + # 2. Write the actual data + + template setInc(val: byte): untyped = + # Set `val` at `pos` and increase `pos` + derSig.data[pos] = val + inc pos - # Convert r and s to big-endian bytes - var rBytes = r.toByteSeq() - var sBytes = s.toByteSeq() + # Write DER structure + var pos = 0 + setInc 0x30 # sequence + setInc (4 + rLen + sLen).byte # total length + setInc 0x02 # integer + setInc rLen.byte # length of `r` - # Add padding if needed (if high bit is set) - if (rBytes[0] and 0x80) != 0: - rBytes = @[byte(0)] & rBytes - if (sBytes[0] and 0x80) != 0: - sBytes = @[byte(0)] & sBytes + # Write `r` bytes in valid region + derSig.data.rawCopy(pos, rBytes, rPos, rLen) + inc pos, rLen - # Add integer markers and lengths - let rEncoded = @[byte(0x02), byte(rBytes.len)] & rBytes - let sEncoded = @[byte(0x02), byte(sBytes.len)] & sBytes + setInc 0x02 # integer + setInc sLen.byte # length of `s` - # Total length - let totalLen = rEncoded.len + sEncoded.len - result.add(byte(totalLen)) + derSig.data.rawCopy(pos, sBytes, sPos, sLen) + inc pos, sLen - # Add r and s encodings - result.add(rEncoded) - result.add(sEncoded) + assert derSig.len == pos func fromDigest(dst: var Fr[C], src: array[32, byte]): bool {.discardable.} = ## Convert a SHA256 digest to an element in the scalar field `Fr[Secp256k1]`. From 890b1850d47436ab8f89cef56cbbad1ba810545d Mon Sep 17 00:00:00 2001 From: Vindaar Date: Mon, 23 Dec 2024 22:29:17 +0100 Subject: [PATCH 14/52] [ecdsa] replace out-of-place arithmetic by in-place --- constantine/ecdsa/ecdsa.nim | 34 +- tests/ecdsa/generate_signatures.nim | 19 +- .../ecdsa_openssl_signatures_fixed_msg.json | 100 +- .../ecdsa_openssl_signatures_random.json | 876 ++---------------- 4 files changed, 167 insertions(+), 862 deletions(-) diff --git a/constantine/ecdsa/ecdsa.nim b/constantine/ecdsa/ecdsa.nim index a19a1d594..50adc0569 100644 --- a/constantine/ecdsa/ecdsa.nim +++ b/constantine/ecdsa/ecdsa.nim @@ -2,8 +2,8 @@ import ../hashes/h_sha256, ../named/algebras, ../math/io/[io_bigints, io_fields, io_ec], - ../math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul], - ../math/arithmetic, + ../math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul], + ../math/[arithmetic, ec_shortweierstrass], ../platforms/abstractions, ../serialization/codecs, # for fromHex and (in the future) base64 encoding ../mac/mac_hmac, # for deterministic nonce generation via RFC 6979 @@ -229,15 +229,17 @@ proc signMessage*(message: string, privateKey: Fr[C], message_hash.fromDigest(h) # loop until we found a valid (non zero) signature + while true: # Generate random nonce var k = generateNonce(nonceSampler, message_hash, privateKey) + var R {.noinit.}: EC_ShortW_Jac[Fp[C], G1] # Calculate r (x-coordinate of kG) # `r = k·G (mod n)` - let r_point = k * G + R.scalarMul(k, G) # get x coordinate of the point `r` *in affine coordinates* - let rx = r_point.getAffine().x # element of `Fp` + let rx = R.getAffine().x let r = Fr[C].fromBig(rx.toBig()) # convert to `Fr` if bool(r.isZero()): @@ -247,8 +249,11 @@ proc signMessage*(message: string, privateKey: Fr[C], # `s = (k⁻¹ · (h + r · p)) (mod n)` # with `h`: message hash as `Fr[C]` (if we didn't use SHA256 w/ 32 byte output # we'd need to truncate to N bits for N being bits in modulo `n`) - k.inv() - var s = (k * (message_hash + r * privateKey)) + var s {.noinit.}: Fr[C] + s.prod(r, privateKey) # `r * privateKey` + s += message_hash # `message_hash + r * privateKey` + k.inv() # `k := k⁻¹` + s *= k # `k⁻¹ * (message_hash + r * privateKey)` # get inversion of `s` for 'lower-s normalization' var sneg = s # inversion of `s` sneg.neg() # q - s @@ -277,13 +282,20 @@ proc verifySignature*( w.inv() # w = s⁻¹ # 3. Compute u₁ = ew and u₂ = rw - let u1 = e * w - let u2 = signature.r * w + var + u1 {.noinit.}: Fr[C] + u2 {.noinit.}: Fr[C] + u1.prod(e, w) + u2.prod(signature.r, w) # 4. Compute u₁G + u₂Q - let point1 = u1 * G - let point2 = u2 * publicKey - let R = point1 + point2 + var + point1 {.noinit.}: EC_ShortW_Jac[Fp[C], G1] + point2 {.noinit.}: EC_ShortW_Jac[Fp[C], G1] + point1.scalarMul(u1, G) + point2.scalarMul(u2, publicKey) + var R {.noinit.}: EC_ShortW_Jac[Fp[C], G1] + R.sum(point1, point2) # 5. Get x coordinate (in `Fp`) and convert to `Fr` (like in signing) let x = R.getAffine().x diff --git a/tests/ecdsa/generate_signatures.nim b/tests/ecdsa/generate_signatures.nim index 096a6578f..a47ec2c88 100644 --- a/tests/ecdsa/generate_signatures.nim +++ b/tests/ecdsa/generate_signatures.nim @@ -67,6 +67,7 @@ proc parseSignature(derSig: string): tuple[r, s: string] = ## `0` byte is added). In our case we just parse 32 or 33 bytes, ## because we don't care about a leading zero byte. doAssert derSig[0] == '\48' # SEQUENCE + ## XXX: replace by maximum length 70! Can be anything larger than 2 really (1 for r and s) doAssert derSig[1] in {'\67', '\68', '\69', '\70'} # 68-70 bytes long (depending on 0, 1, 2 zero prefixes) doAssert derSig[2] == '\02' # INTEGER tag let lenX = ord(derSig[3]) @@ -97,6 +98,8 @@ proc generateSignatures(num: int, msg = ""): seq[TestVector] = # convert private key to a PEM file and write as temp writeFile(privKeyFile, toPemFile(privKey)) + discard toPemFile(pubKey) + # NOTE: We treat the *hex string* as the message, not the raw bytes, # including the `0x` prefix! let cmd = &"echo -n '{msg}' | openssl dgst -sha256 -sign {privKeyFile} -out {sigFile}" @@ -118,10 +121,20 @@ proc generateSignatures(num: int, msg = ""): seq[TestVector] = doAssert verifySignature(msg, (r: rCTT, s: sCTT), pubKey) doAssert verifySignature(msg, (r: Fr[C].fromHex(r), s: Fr[C].fromHex(s)), pubKey) + #let rOS = Fr[C].fromHex(r) + #let sOS = Fr[C].fromHex(s) + #echo "SEQ based: ", toDERSeq(rOS, sOS) + #var ds: DERSignature; toDER(ds, rOS, sOS) + #echo "ARR based: ", @(ds.data) + # + #doAssert toDERSeq(rOS, sOS) == @(ds.data)[0 ..< ds.len] + + + # 1. generate 100 signatures with random messages, private keys and random nonces let vecs1 = generateSignatures(100) # 2. generate 10 signatures for the same message let vecs2 = generateSignatures(10, "Hello, Constantine!") - -writeFile("testVectors/ecdsa_openssl_signatures_random.json", (% vecs1).pretty()) -writeFile("testVectors/ecdsa_openssl_signatures_fixed_msg.json", (% vecs2).pretty()) +# +#writeFile("testVectors/ecdsa_openssl_signatures_random.json", (% vecs1).pretty()) +#writeFile("testVectors/ecdsa_openssl_signatures_fixed_msg.json", (% vecs2).pretty()) diff --git a/tests/ecdsa/testVectors/ecdsa_openssl_signatures_fixed_msg.json b/tests/ecdsa/testVectors/ecdsa_openssl_signatures_fixed_msg.json index cca681dfb..88c12f758 100644 --- a/tests/ecdsa/testVectors/ecdsa_openssl_signatures_fixed_msg.json +++ b/tests/ecdsa/testVectors/ecdsa_openssl_signatures_fixed_msg.json @@ -1,82 +1,82 @@ [ { "message": "Hello, Constantine!", - "privateKey": "0x256bd879af387ca45f9164b1d4882f68570e0e9cedae60a5185a405b4aeb4697", - "publicKeyX": "0x45a41d181c6dff19b6275a4639cde0ae581f4fb2ccbd82663fe9ff9ad6896f3e", - "publicKeyY": "0x31039cb9d47f142d2f1a7a928329b89d94040f23668f70772664259decbdb017", - "r": "0xed77f05cd805bab09c214f9c913c776d0afe3db8e63d1dcb83819828d61ac5b7", - "s": "0xa3ea2322400e727256ea054648ac88e819b28ae6839728b53081225dad8ffb48" + "privateKey": "0xd870b08067bff928072d5db736f4ee10dca7d6b0263285b3b2aa7ba870ac68b7", + "publicKeyX": "0xa1b52f56e9db283d258e0ce8e0a799bd2d8d124c7619ebbe13d3f3acf18d36d4", + "publicKeyY": "0x309994064311a7b62158409bd83126fb9622b0ea8460741d5f1265bbff4f1738", + "r": "0xb45f7409a3a8d71b6653a748845971e07f471f92850d8ccfbc4910e5edcba47f", + "s": "0x29d5e9a9c32e87de8429487f2c0cd1e1837d2c108c61d4e93a64fd7eedf1d0c3" }, { "message": "Hello, Constantine!", - "privateKey": "0x2d8f7346f506f998e2cddaeb930821d5cec2425f1b9f8f9a9cf2fe6d2d12f7e7", - "publicKeyX": "0xa852f5361011655444fa29e0b00322d7df3b00279d5bd87c3a9208b4d08bebd8", - "publicKeyY": "0xc4ef7ab0b5b0fe064eefa7f61607d6eaab3fac906a701826efe46b9e7c8958a4", - "r": "0x994e37598cc2e01abf006159d36dafeae859c1341aaff98a6a253e150536fe30", - "s": "0xa032c45d726c75ebf50606900ef363fa485d6be29a9dca6a89e50ec10378e2d2" + "privateKey": "0x70fa0674be3646b8131c77252db73fb1f31d70aebb9b5540b84b752e5da22c04", + "publicKeyX": "0x94fd65a886c4dc154fe66765144773c8fc1ef34d61df500217cb1789997cd449", + "publicKeyY": "0x2c75da7ccf7b5f58846aa81ddd27796b41e2ce2a43e1c705d0b09f28854dc501", + "r": "0x56eb4aca12897a5375bfa159e36454f29fc12da57720af8413cb545e68001a3a", + "s": "0x57abfcbee64b40fdf1fb242d4c565c93882630f9bc4ef984fdc6bf24fdaf794d" }, { "message": "Hello, Constantine!", - "privateKey": "0x22a8c89261fe9adcfbf19579bb3ea69267d04580ea7efbe545ca8a85bc360c0a", - "publicKeyX": "0xea6587e76c9a5eb035a9628288540babefd6a2e3e1daafe93741f0e3d3c1f8c0", - "publicKeyY": "0x9fda7e50aff389e652c95fb0d53bc11937f285e5b56f1d59aba2a86509a3603a", - "r": "0x0b1077451d938aa42eecdd53b3380edd2680e01e5ebad0bff5a62a2701d10fcd", - "s": "0x6eb29bb61d2a9d8097b18aecf1a6c0007c4a1bd9b21f8f3e98e58a32279e9827" + "privateKey": "0x5fa75feb2ad1cb8eb0cb68420be1ed858f1bd5645f49be1df45fb84b8a8c84ae", + "publicKeyX": "0x327de4e42a8ef3e8cd85504cf420b619eddbaad6f3a4b9dae2b8cb980c1c703f", + "publicKeyY": "0xaefae9f952f6ec0f3035c6363c0049fa3232e1d72f9efde730c3f7a3488851a6", + "r": "0x6169580de3706434e170d1bcbc84ed91b987162d6b824093a6a6cb959dba1353", + "s": "0x1e73538ba74aedd070e2074edfa93b3e65d7183aed503c81e95ab500748204cb" }, { "message": "Hello, Constantine!", - "privateKey": "0x1fc6c933b4f47451cc09db8071792ef5feda71113d1c5809d5e0d5044dfe9c31", - "publicKeyX": "0xc0ab9dbed4534b090865432b1ddcea4844ef504aedd69a66afe80fa2a4c41ab4", - "publicKeyY": "0xd8db593e93eb432f226c433acb9f1476a3dcf0dc7be2ce03f9e8496c3d0214b3", - "r": "0x263fe33a9faf349a8fb2278a4c895411752a1157cf75d4122e25f159cac5787e", - "s": "0x3b9fc9b21fe5e1acaaab5bbb13dde0a32420e032316cab0738a3c9ef5add4ddc" + "privateKey": "0x57e40ab85efb7101d1005547cb3795925dfcad811cd2119875969de4c00ed916", + "publicKeyX": "0x6f83e1ec2ba63d09cf8d7fc1be938b99da7e044323db5756c6bd9618feddd89e", + "publicKeyY": "0xff96cb711565b3c930604a44c3b5b60d60831caf7de0329917ff75c8e7c28b16", + "r": "0x0b5144c1361adf5252aec22b14001a85efa77246e923bc8240a4f88f92ef9e1b", + "s": "0xce829a84b8e0de0490fcd5b486f2fd1413c57a7c3fde435738a913891309fd14" }, { "message": "Hello, Constantine!", - "privateKey": "0xddba67e7d19fd7e23a3fcba09fdac0d8ef04abdd0709b68e4160a0adbbd0714d", - "publicKeyX": "0x57c8ff99412c0bea729c77820fd695ccd5aae174d5a77bb01bb388b5f9a29339", - "publicKeyY": "0x36319a8016c96866b301ac23fb7dd194e2513c8154b84e7de62703672f4589bd", - "r": "0xfc01b7c9c9ef3c876cafe18850eb6f19915e48762432ba1f8bc0d89a467b2225", - "s": "0x308f8e317f4bb38b41abeaafc19e46a392c1a0f8a2d3da73bfb3243ade59f114" + "privateKey": "0xc00b5891b26db9c959873ccaa50c4ac230f39e019720f63bdeab1a4f46ce92ef", + "publicKeyX": "0x18ab3244bdad4a3df274a226881b5bd778d5cea61673baa615cd45a49b2d92d3", + "publicKeyY": "0x041fab03d07fd03b7721d00e329d3ed42190049bd7f682413cf0b052bf9ae698", + "r": "0x744343729ae1702d58ac95b6daa9a8492de85e6635cd57b57263666216c98217", + "s": "0xd7efc920f5e281171b533d97cb00385fadfb556c65acc83cada6ec5d68808f30" }, { "message": "Hello, Constantine!", - "privateKey": "0x75061b24004f09bc201197d3dfbdd31702aa851d423028d174b02ac9c6ee0e6b", - "publicKeyX": "0xdc56f5ba2301ff776006935d446aca77be29c7ecf80b371db72576a0d105bc4a", - "publicKeyY": "0x0369ae2f661f2142477dafd4c60fc79e88a986311f78c5ab31a7257504e0ede8", - "r": "0xa7f2e685681f74ccb2ea58dcdde123b40e980c1703900200d34fa45a816900db", - "s": "0x8e22971d7270db3e70ddc2897a7f087548c1f1d77a65517714866b4f4da95b78" + "privateKey": "0xa80c027c34383a150bb9d3af3f0173bbd5f057abd6fe0800f038adc2ba9c4dce", + "publicKeyX": "0x18b645616ba143d50aa8170532abb823d12b0f584977d95e22e364f1366ac464", + "publicKeyY": "0x1f4e357fc9f67a87745c13736a08266c337bf421c66a8cdd4fab3a32cc9fa4c1", + "r": "0x33d77c4b859695704f4a968b3bb27a1539625cda84d6ece8f3ecfccf80e8f696", + "s": "0xf6078d0c6b935c49c173aae1a183143906cd39a298fd446e1db15f8296d4261d" }, { "message": "Hello, Constantine!", - "privateKey": "0xbac0b31a6416866f012652846f958646afc253e4491465b6b405fd1ea2d00f7e", - "publicKeyX": "0x5ac12a3d7d10a4efcf8d39b104d9427a97d4ee92f117670d30c710ad2619926f", - "publicKeyY": "0xe00c161d99c692e918e9d88d644664bfcdd5043e23d64b7542c191fb67852c7f", - "r": "0x4639db7be1b2fb9cd438fa0193e8f9b7f547fa73a21a0fed3e5570d4bc4f79a5", - "s": "0x8f0763583c1cefd1a94ea3ae9d567529a3a6e9039d5e2c8c90fe3757a7015ea8" + "privateKey": "0xeb71a9f41b2719356150f413f820a0599374abf334d89294af01ddc299c07bb7", + "publicKeyX": "0xc9bb54ee9254ac7026c805994f7133ca2a130f54742deb9a6cd8fb1ba97a5144", + "publicKeyY": "0x0deff37ec02919d55dab1eb6e8c7308d2c82db6a5b48d5c32c6731a95bde9bcb", + "r": "0x7f3b99bd471518ab5d6fdeef52cb4a259edbfa2c8c4a12013ff4a813aa3e3e3a", + "s": "0x04be53956348dcb5ab8e94467a57fd36eb2763e97ba0e3c8d55f4efdc90210f4" }, { "message": "Hello, Constantine!", - "privateKey": "0x27bc1016ca234abb27a5ed4d155294221d0ca091567022a6610440291930bc9d", - "publicKeyX": "0xfc0fb22385d2f44c0baf3ebb1e7ecd1d9c906a439a0d00b226f7a204cc1c0d0d", - "publicKeyY": "0x2db3f2b91a27f5c8b4d1b693b6d84540f6659e2f1a737db752806907b58ecec9", - "r": "0xfbc73650d98de44d17fc8a441105318c8eb580b82deca2c1ce1584b5d9580758", - "s": "0x3c10dee8c45d5dad238d97b724556d66bc1ec9deeda784fddfac2e96a0640f29" + "privateKey": "0x80afb2cd864ccef46f6a05b218adeb09d9872ea0b7aa13415c1e5130a450d263", + "publicKeyX": "0x441a439fccdab65a6011f67e7ba61b7412dbd21fa581f5cfaa195e85c1b8b33f", + "publicKeyY": "0x7a822352ede3f6f3e80c4352b3a1b6df603270f0a9f66c68a726f03bff71a688", + "r": "0x580c07808a05f9caf470a86d548636a20bbcd51b0f1cea4197593a8defb68c3e", + "s": "0x8aa1a672d5bff22f89a9e4aa115bd9f22d60a600ef77a7c0eafa1512904d9492" }, { "message": "Hello, Constantine!", - "privateKey": "0x9191e0d56713ab93454cb0f3f8a9881d0a53f6da6c285b452e4d5f4847c64b84", - "publicKeyX": "0x4f6df1f0acf0fd68b74a4d41920718ab84fed5e0642c663407245be1c7914629", - "publicKeyY": "0xb0532e2807c075335b27c93b3218178669e4b3705b4d784493df9784bdbdfeb4", - "r": "0x503198ef113526713e6b18628742ba19ad8817e32b6dd1890b3606e9e382da8e", - "s": "0x915f30acb77e07c6923c34482e09e6a5f34c0cf30c8ecc4dbb81fa066029a94e" + "privateKey": "0xf44cf5a639dbbfad459f96f41efd278fb0cd0f385e9cf5744034ccb7dd442432", + "publicKeyX": "0x591a1857d64e9f170a521aaeebe402129d9ec73e3d3fa5c73cb23a7c56408e47", + "publicKeyY": "0x26901c0fe84af420ae4d220ba4d047983c510a08dbbe3cfd761a337c2023c9ee", + "r": "0xeddb895f06f84defef824b26a3a7ba8ec06622163a53b80e5e5177bae6e50575", + "s": "0x10781156896627231d5bc145bb302c1192710a35c850524c24644dea166e6619" }, { "message": "Hello, Constantine!", - "privateKey": "0x99a913bb0e8a74da4edca08307c15b0b81d8fad41825fe6fc2dce721e0dcd617", - "publicKeyX": "0x53e22946172e0a73d61733c563377d8d8c8fe8d3446b79cd6883e25410715882", - "publicKeyY": "0xadcbcbbe249cd0e786ba6d374b64c66b32d7a917eb48c4dd3e1556054dc95430", - "r": "0x5e1ce1b3c21f6d616fecd54718356a704e89855da529f9163ff68ae2425ee607", - "s": "0xcf228448c3327191e1dcbd8da97f22f432c9d4b74335d8271d53753bbbcbe2d3" + "privateKey": "0xc46b018e8c8f5cf6530ef737d9bf0be23f248d82f5d09f9274d052ca780d4e4e", + "publicKeyX": "0x6e1f4bf4aa07da28576d7fe69e2ee81db4e5117b124a0c0145fa6dbe2171a110", + "publicKeyY": "0xff71a9da404b5fa27f666aedbaf0d5c291b0be973838c27b539b4a217dab178d", + "r": "0xd1a44141336513a981593a3dd1158cee5311973e5015a17f30cf2c27544df050", + "s": "0x10f33ce3c8b463c7b0d84016172a24540929d9d95e9ee22e4cc2365e177be753" } ] \ No newline at end of file diff --git a/tests/ecdsa/testVectors/ecdsa_openssl_signatures_random.json b/tests/ecdsa/testVectors/ecdsa_openssl_signatures_random.json index f5aaad2a6..7cada4753 100644 --- a/tests/ecdsa/testVectors/ecdsa_openssl_signatures_random.json +++ b/tests/ecdsa/testVectors/ecdsa_openssl_signatures_random.json @@ -1,802 +1,82 @@ [ { - "message": "0x367cc3d190e540ff9cd4d735bac4fa67b857db2825e0fc6d3853ce38667e3a288a2792cb18cc8c03058390f827505b0d8a6b1ac46ce339d3772736446e26a381", - "privateKey": "0x13c0331fbbf9b07fb5465efb83712cca5bcf78433bd7fcc41162738e31f500c6", - "publicKeyX": "0xe63412da39d91668bea8974674235fe7d309f13b4fa4d65c05792f92a4b1d1f2", - "publicKeyY": "0x878ea9cde512c28a07fbf99c519ae0c264543eff2b456db0e4f404ee11783742", - "r": "0x7b0b48e29f15fabc9d9c44bded82de8e2655be839140f86292ca5dff9be1ad07", - "s": "0x6ff1e32a4e12bc69ab48d36e5572eddf7f0281f7a879fc94b5059d8b630baec0" - }, - { - "message": "0xf2a67da53d543760eb054441489bf0f6e45991326cf91fcfdd2b85b1503c3a9504412b925d4941cd74f21cc9bcb8acfc6d39b274896bd237d68dc799f013fd5d", - "privateKey": "0x9eaa29cbda0f1045c8b631411b55ccef3c15df776edf91e7826346f0c2230064", - "publicKeyX": "0xa3712ccab8462673c0678f4dc733b5cb9051632de977684a8c3e94c1bdc10e33", - "publicKeyY": "0x2293af0ea3836d337eb83c9daa8ecf22bf94ceb618724d3fb043082044b4a971", - "r": "0xfc8157cba0c8851ae7093fca9c6a33e1df8bead01e0ebb517a2a2c472322ae9e", - "s": "0x8de92c2e2f537870e71d80f6842359fa52680a341db2bb2e48c5941cebf56b7d" - }, - { - "message": "0xd7e4e1306dd9ac397ed41d9775bd6769430f1827280b42cbe7c54bed785deb47bd4c5b54eef76906c75eec4d3665dc05d4b7d40ccaa73baa464342117d8cf330", - "privateKey": "0x5cf63abfb24b5c736a60f5c616e34fe1b3c48d30e6b0d68e28fa1f7453dac2e8", - "publicKeyX": "0xdf3da0770e33c11a9405a8bcf878d39bd0312ca22f9e07ad3f28446935871678", - "publicKeyY": "0x99c3a77bdc3ceeef5845a8d536262b3e144ee01df4d125922985f1e3c787e50b", - "r": "0xc4b6762fd01d007ba147bddd0e065e0d5fb830a729e2371e9cc58f6dcb316626", - "s": "0xcf9324eb930a3c9c22e6ec2e5d1e0a0ec36c7b76327f3687b4058110068f05ee" - }, - { - "message": "0xa65e4f75b3ed99bf3f0c80b14832fd7204628c650835ae1a3e1c70c3e5bb3b995153b447a31b5a0e9218de2f77025d2dc94903272a94c3774146eaa797d38f2f", - "privateKey": "0x0084b8cb18ee7e3ec638c62fa75135d828193537e0dd582e1070d69d8c60fb7f", - "publicKeyX": "0xa4ab4354894a73d534e143f267cc6d3b957b7e13843e5338d69e8113609b3af6", - "publicKeyY": "0xb76d6655504a55c1fed48e894807962f827a1adaa40e01ee065f9bc884d03583", - "r": "0x4838d2686541e6c1d38c2359853f4af2a7b8a31bc89d7521879ce7e2c6bdefd3", - "s": "0x97608cda0f8fa16f8e44e39c901a2d989b2053b1d7e97ff8ea38191cc73d57ab" - }, - { - "message": "0xe15ac423b2873f3849ecf692f117d00c2e7671309888cde84e961dfce2de55c0684fd54bb0bfa79aa15f5c33f8f24196a89e73e7d87f6b2a5f88d89591833729", - "privateKey": "0xd509afc7fd7fd30ba02fb8bb21a8ed9130821df8df3f69ae1fa401b21b17cf58", - "publicKeyX": "0x6a65a1048de25997f5c6ed4cb4a45ee48cb73e4da5836bfdeda0fbb182973f42", - "publicKeyY": "0x78e4fde5c2325c5ffe30d4db7aab153af936efe559e0225436169e0b4ba323a7", - "r": "0x30c801c0bad5b937ca652d64ec9e007453ad34b78d2d66f0028265892f78eac0", - "s": "0x549ac6bf4e0e6dc21c1bc903473a7b60c8bd788bbec9b07942f2badc4cc1d759" - }, - { - "message": "0xba726418e5b1588e6570579e35b4fbf549fa8e43fa892b6358e86b4486729757ac8fb4aa8eb72504ce1fc996ea242b0c3dc9e3e00ed512cd1f360764b0bb97df", - "privateKey": "0xd553c549cca33b3ae4277705698ebbd7d9bc882205e151f215a863b1d2508591", - "publicKeyX": "0x506344bb6653c4732310f6f760447393d8fa9481854bfffecce100d3b6416304", - "publicKeyY": "0x835528d40fc82d5c92285e6a99e73743b838f2067f65ea666ac35cd2ccc59f01", - "r": "0xf17ada026075c60ad2545cf658a175698b8d2fbff303b8e1a0d7628d428d3da6", - "s": "0x8e8ab2b7cc349d994aa02e202ee14a471ef0982b1d5ff05c934ae78c98b87243" - }, - { - "message": "0xd37fb73aca3b670b8256a2d52881f26c0a0c7115d6218be4f21f898d09f55491383352db1b508cef68e09a1bc5ba6d359f8abcd7df09d308cbfa9787be18a03d", - "privateKey": "0x3cc765df0c7e1c2dfd980e91acae4f38d9d7db114daa9b1200aeefb6401c0767", - "publicKeyX": "0x1cf5e56581b425087285975be03a7031dd4274deb66adc059047c1eba1b1851b", - "publicKeyY": "0x86b24e8b2ff877fc1f332d42fae5aba61566afd722649f95fcc2061078ea8ec5", - "r": "0x94a2c50874a9cfa4f0d94d9230bfda8b4b89d8a9aefaec5f0159d5af8debb5c4", - "s": "0x2f734bed280eca292fb33439c96caf354cd7f0b3d1c56f63a23593cdeefed263" - }, - { - "message": "0x9262e4521d8c70583045a6c1080ebb5cc259284c30d48cb660fe6805825d6e36ca49f0ac32b1d8123e5979b8e3027fdb9d313581ac6386920377cfde06604576", - "privateKey": "0xf5b232ee4a965efb2b2fbb0ad7961ea49af41e453934bed618b1247f0c812c5b", - "publicKeyX": "0x51a85cb912a06ab465eae370f26dc3546f7a9061d81215202e5576499a9975f4", - "publicKeyY": "0x26940e5de9d6e8907283e80f605edbe0bdab33aa284208b4ff97bd59fd6f19d4", - "r": "0x595face4049da761574ec2c47e6da72d0d7dfc52e9f4f0532769b4e6673ed9a5", - "s": "0x9d385f34c94f262d50b655058967cd568812eaeae24aa3de244926989eef9ff4" - }, - { - "message": "0xc2a4129e68511ed9f401144ad80ed1c2601d62d597848be00e5ff825a98fa7e9687277a02c79b2e0d1a0d2efc7170a97576721870d58f5a01fee18e8be7c6d8d", - "privateKey": "0xda809319d6fddefbfa1e90ed9d896234248561b66cab7c9d263cb865a96575b5", - "publicKeyX": "0x88b3f391acc1aa80858a90ab4cbb478fbfccafe6e1fcb891cc96836dfe44dc2d", - "publicKeyY": "0xfb885b73013f73cc95e2a85ded6932214b5f0782e86677ddf91b169b520538b5", - "r": "0xf7f58c255b446404957170643b391a6c3a64d4d7ce4aafc0be231be7938c7b4a", - "s": "0x44d62cae251555dc820d0207fdc9886142f75aeb1476663f88bd4e523182c6cc" - }, - { - "message": "0x09f2413bcfcb13b14b9649f1da5df5faa258c1d0b4642ba5cfe91df85d53cd461f61ed8b58c945972e8e7997166790beb7e7094915f4c0c17ad3826b44123e8a", - "privateKey": "0x2bd81ffdf104470e134a504c190cf66a7e7f1592a3710c29e28a0d6d58e6773d", - "publicKeyX": "0xea6d3d224a0813d3972ba20d2ac4bca67098495296813dda89eedc332f57cc9a", - "publicKeyY": "0x635e7a7b64c60b4f3a6cf164f0ca02857e4ed0f02487d6496dd4c8443aab677c", - "r": "0xb02153a485ca591c7522b94394a8c0955ebf9f14d57fecd4ed9e173ab487acd4", - "s": "0x261fb9f410553fa6deabe5e5dc471f80b891a6e5ff1d315755f0a477d7027383" - }, - { - "message": "0xe7fb72332c946102980f1914a72d03734d38defa8c8be772b3dbfaabdfa964660093a50a6822a85d4449ca69c0c59eaa7955b1118cf0d1c88f4cb4865d7ff227", - "privateKey": "0xb4431765fc32428b6754a30f5002662f03d6650a65333a196b315309010462d5", - "publicKeyX": "0x03fe900d02d236243865cc9c160716e74314abbaf2bb145cd17d4f118e92fa33", - "publicKeyY": "0x96a7c5ced3876eb7d184418d941bcec78ef5996323eab1e2c2fca031a38bac37", - "r": "0x6111657c010708da3632c63768e9483d027a4fb48f647c75ad3132ae10885b52", - "s": "0xe35cc7c28b295e0c4a8a36de3311af5bab18d2251a96c3b35d32f00d0d1312e5" - }, - { - "message": "0x4f5b088f418f92a91ebc048e69eaf360059fd0fbab4076066c08a509387061ef0db7ea6e9323df29461c5a360a48ff4b616898560dbec038ea5c627dea4f6395", - "privateKey": "0xd22c61f4cbe78c78a3c2c687a22c8632db2adcec0cb81ceb05ece51f4e3e41cc", - "publicKeyX": "0x05c164901da4af12d3543776a7e7a7b4082382748cea5525be8ab0e13cbd7611", - "publicKeyY": "0xe13441356a0ac62813633d455359fd42e80b2b782b786bcaaf59c42a284acfea", - "r": "0x31186a5d59486054d6d78b1559302bc5f2468f36f3e8dbe6b01fe6a761ab3aaf", - "s": "0x0b1027f5c1b08eccc58d87f357be1e246293d6e2d3c56c49032d1cc63a8786bc" - }, - { - "message": "0xadf1b5a06f70e9cf9e50f6f45c70771cd1539ab4efdaf7421485e7aa1baeaec48ba6f86708132723467aac5b64eb11b71c1cfd3668133adbb8742b9bf7c633b2", - "privateKey": "0xa2e6cdd434abb3df65a62cc49d5f9bf65a8735b3294bfa2cf34e7eacc8337017", - "publicKeyX": "0xf7b49acb8b929ba301f18e1dd000796682f7579d301330bf194a89c422e0bd98", - "publicKeyY": "0x50c8252c4076e440064a51e5a58f4454240ce74614a7ece5a621b313fad06afe", - "r": "0xf1dfc02e86485b39b7fee0a05f2cafc2202c8e788b541f5813f789e1fe7c616b", - "s": "0x84e25b64321404a973c0f0dd73f562a3e4720d34045797f0cb1526093430339c" - }, - { - "message": "0x39d8e58df4449b5e5beb8f9989775fb1ea43fd570ab9b3452dac8647b861be1bd02a467a67cb0d033af6570eae8278fe8c2f7dca669a6382dd8e8f75b53346ab", - "privateKey": "0x05e725e5ca96d80bca776ffd9f6fbd66eb4e0a2159d9b522d67f80c4c45e871e", - "publicKeyX": "0x6b256d9de0c9a007ae5db768759b8e6d3f080286adb9bc74b312ac5398d0f9c6", - "publicKeyY": "0x34ec36d04299120ad89ea44d772a2b553741102a414d6a431c307ca59c73694c", - "r": "0x1f54f5ba26c1b8dd01ecfda26ec358cf21a086ed7cdb0d2fd310b5dd537de5fe", - "s": "0xd6bf4efc158840da9d568d74acfdd466c232b712baa259f8c91700e99c4a28c7" - }, - { - "message": "0x8981d7434e80f2b6f2fcd8bd8e68ba722f2f31861010a489ee127f1b665e8e7855220287c6c206a5f6fea200cf02601d3b3e6fe700762df5fbad6f9d501e07b2", - "privateKey": "0x71f43a82b3e92af2d402b2b17bef41e42a5ea028552d6fd387e192cb89608c49", - "publicKeyX": "0x754c2ea5e1c61f7187851f36e65eb171a10777a225714f6e8d37dbc24626b68c", - "publicKeyY": "0xb6169217dde71db4f13975bcad514011c95ca79ec51a6d3b064969bf43573e42", - "r": "0x92b6777a1a6fb5aa503795bd536eda24b9d1006fc07ba9171488dda4e5c1583e", - "s": "0x7f734f58f86ca8ff00990eccc266ca50e266f048a5bc7086f737832a9f6e6f7c" - }, - { - "message": "0x320c23efe48c013e2106be66250584769d88ba82ff2a8f31b8d10163ca47adb1eabad572d780af49e82d3418bd65ce42a0c18fdd7d7390b8e89df96c7c1e983c", - "privateKey": "0x374648903ea600876c543999e95a549a7fee6d90e0b9c47e9badf31a7325b5af", - "publicKeyX": "0xfa4177af4941d5c53e16985fb1bfbb5fa248e593c2ad50cd0d72eaddf8338e47", - "publicKeyY": "0xbe8e24d09f5f13db5073c0aa834630cf0b82eb4d8936de039918325fc78b76b1", - "r": "0x38f67445e705f7492ec0b47b3361ff8aa154a6d74d1478189f5ecb29a10a8e6f", - "s": "0xae199885b1ff7ffcd01aa14efe60a4d4a4dd9006f4de8e6e509822fec511d709" - }, - { - "message": "0x69d31f6a63eba7275c726256fd0c1c5b13bef09b25f054197ea7eac7f8d1999711f9c2bb2ce136da4b35092e1d3b32b8432cdb377c18ad93fdd4c949d6677399", - "privateKey": "0x61a6bb8f2fed69e870e879466698ba81034646b834a62d356fc500acee7c9545", - "publicKeyX": "0x495e84bdaa1cfb70b4e766c29a255bd4b965b79d0af04daaf35e0abe27533c42", - "publicKeyY": "0x887eae3eab388eead62f59d85535aa8e70b0e37c2603b59b0433ea46188feab4", - "r": "0x310e5f8e3b51ed2b6a855aba86b850b130bfcc1e53d05deb52c3308dc95bec03", - "s": "0xdd4c468ee38a3451c18fdb28e66adf7fd99a69d0245eaf9c1f411268d09ffd47" - }, - { - "message": "0x516c6596547fc6511407983eea91bf42f35df7a5ad90e5acb733604d180bef3c8afaf3694a97147e46a2097bd3bc2a73a537e2f2c712523e2fce68f1efd97be4", - "privateKey": "0x3abad4b34a0ecd809a11653188137541af3ea1df54ed7631f3275606869f0837", - "publicKeyX": "0xf14208ccd67d49a701e4d8c3401cb7534bb1bbccf90d590e73fa91ce3b3ee9d9", - "publicKeyY": "0x0667968417797f1f8e67744843b1c500b680c38ffec06d51f1aa3dee0766c5b9", - "r": "0x14d11440212b32a882f34614311cfd772908b36ae8cccc4058ae526664286da4", - "s": "0x1e052c7862feb39c187c92f65353c051e4e5b998fadc1bb638660b102c54db61" - }, - { - "message": "0x78a36be651eab81417c49a3a9ff2676e25b4629ac094bc5a4e5eb130a16c3af46f597f4b3deb5e741f616717ddb741bb9224839f45dd8e93edbcc36ec4620fd9", - "privateKey": "0x58b22b71eca3336dc85ba377023fe385561d013a7a35e1efb8b4b61c35af7284", - "publicKeyX": "0x19be6ace209a349b68f36f2465088bee24ce9a5eb61bfc611ebe2073ca61a0c2", - "publicKeyY": "0xc34fe2957e8649826e4cf648c88bf72397ba24808064f48db7097e550a370c94", - "r": "0x8e59921ce83c88dae1314d1ce8689383712cbd32e017c4961720924220f4ebd5", - "s": "0xf49250d1eed8721b65205793d1b48344af9ae13a492204be8b88387ae6eb6cd6" - }, - { - "message": "0x4f9b9aa175ccea10916c83db01161ef7e787f7dffc99b4a82ebbb85f1c77691afc6dcac4a419ca2adc81e62e8a69d32292f4b14ce5adac41d929e6f2f877ec1b", - "privateKey": "0x02ee525339b56cdf153fecddf64128cdba3fa7dd6aaa63ece7529a829c81bc6a", - "publicKeyX": "0x2c36185aae2f890e15c1b6e3179cfdaae4f88a460321b1807cbf9dca62c0e168", - "publicKeyY": "0x327072c7cd2e9e7a0fc51fb01235fff2628cacd9907c54f2704a3e12a726412d", - "r": "0x63568f9958b3166dcc2dc25d830c53ad4d885105b03e3125d063cad5da701105", - "s": "0xac99a2a76d93331cb65e344d3ea86568323a73de805ce59e44c15d8f3e4fdec1" - }, - { - "message": "0xa3f6651eb9917a0e54e60b233eccf8627caa692bf81a670bd63ebbe40efbd272fc19f63a0d7f694d9cb98cab407e6920caade6a304c85b9503e6fe18dee42a57", - "privateKey": "0x41c1c9c47b244fa3c1f779cbc807933f89861dfb777459d3932025eb9b9efe46", - "publicKeyX": "0x9ecead233404b6a51d3ca8f0603cdbd7472967fec7814b02391e9faecc6f9940", - "publicKeyY": "0xf978569d0f1beffb77dd2a25dc4e92c9ef819f1d5c472d552f993b0ce4575ba9", - "r": "0xcd43c01e9a120d831f986c7c76bd0c7eb1bf93e17aadf81fca74b9e2850a7359", - "s": "0x7cfd029f91d339bdeacf0bdf172c8f3df74bb6611e6e5bdb36e7c013f9e6c8ce" - }, - { - "message": "0xe87a992762b23f4bc9a001f6d4939ee039f86b9617ba7161125875daea45cd21690c5290df5322d76d80fc61d263c20a64e5457e49db3334e6600b5effa0f170", - "privateKey": "0x969b2020c0055f6c3ba3ec1a155608bc0b35d02a5c8d1fa2b8de9b4fddbe5578", - "publicKeyX": "0x241a60fae000a190f2bd547030b5ed67ec6536ca3f585ae291fbbdde263d9c99", - "publicKeyY": "0x657773fac8e4bea88722ea26e3ac08b4364d4134e12b9b6dd6dac96bb305bc05", - "r": "0xacb2dcf4452e40a98d8d18bf192cb5dea3f02024d6a565c874f633edcdd7f88e", - "s": "0x7965479dba3b7da7b5b55c26ed999d04167554511611e9271b844bc3d84f84dd" - }, - { - "message": "0x7f1f3cb82a787eeb1cf24b23814ef1c54256bc69ed0f4c83b30206120cb9cc8d9f32765af76052bc402422a4a2683aedd8a6015cc31704f8e9c5d76ed92c0919", - "privateKey": "0xa6c10e06ac04fec7cd40ded4906831409a8f6a6eba09dde5dc8b9f255f3c7a17", - "publicKeyX": "0x97d3cbd5e81c1dcd874124d801e6b1061f6b37c485f9dab37bb5b73bc08af0ce", - "publicKeyY": "0x2aa44553890cde0fde7daeec843a18f8a16af8f576bcd2a131b7002429e73726", - "r": "0x3b8d18c1cb35ff8ebd1ea395c7741bfb0fd84ab9f9afac33d351d6e75a0fcb28", - "s": "0xff60aa1481001a35b20e6c8fef306a7654f3d4a19e8d77ef679c794b94d029c0" - }, - { - "message": "0x311739e98549a5851764101c48f1fa2a41ba8856f05a84d9c18755a3963062b983dcbc840ff7d241c8ac999b7a6727588a58f0980c8d61c63639fe83c43b6c1c", - "privateKey": "0xf589afb763baa4f81ac97949308d00a19c0cb6d98e92add052f74957782fcb8e", - "publicKeyX": "0xadf831e3d4d9992f2429233732e86f68b294a10732be9a8c0bf2c363218602b0", - "publicKeyY": "0xd6c5cbb33deee7c24ffa70fd1667c98b56ac78c20c40fafb39ede58bdbff6903", - "r": "0x1c183bf8a561ea02119238331dfb51f1c39a298f4d2ce7955b285fcd6e67601d", - "s": "0x2178ecaaf8ab06a3ba19e4f1e57b98bafba3c9121ca5e4934d97122f7e9e34a8" - }, - { - "message": "0x8f31b0094d95fb142d17f9be80af8b3d46dd385fb2568ce4e0469f08745a13b53cf45bf962d8ffa172d99d499e7227d4da9c34acc628fc1fa72934ed2caeb14c", - "privateKey": "0x51c9bca691e1287ea80e5718c9429d6f74157dc006a04f36ff21ced3d2098de6", - "publicKeyX": "0x4134b6635a0d5e7bbad48b7a15442c99323615e0bead8fa70939c434f0b7a250", - "publicKeyY": "0xf7be90ef0644c8188afb737fce0eec41a53e9f660f1c05139b8901ddb08ed94d", - "r": "0x72de31951def5999bb4a46372b99add5a7c41db583c830340850c179740e1959", - "s": "0x2e07a078e8af2bc82b45da89eae50859386740ff5815b13311b139ec3bb6b76a" - }, - { - "message": "0x807c6a48d26b1b2414a1f8cd2c23237b8faceeccc227752323877611d90cbe19de6cb597487be4dd95e7a4eabf3f84b62862a0a42550d32f2696f42c18e0520e", - "privateKey": "0xd7f4b43174665d1a119ea8601483a8551a1a56a137b5af4a481e3f7e1adba521", - "publicKeyX": "0xcca0c8b0ce293d87cfb975936942dafa4007f2f9007c1cde22e4df3e85e0e765", - "publicKeyY": "0x78f260785506116c3d6f00dba9bf537df5e3d9106dc85c92a6557e17d1d0c2a1", - "r": "0xf6e0cfd45563f984820dc3e4efa9054ad8ea134090cabb214cfbff79e076b9d2", - "s": "0x4f7c063fda729937d86bec915f2722afdb666c422edabfb0845abf2d8d554f2e" - }, - { - "message": "0xf69d76e2214104d1d8be8ce692706702f725bb0a70a4f0983f84e5803c6f4ce2167c37f56cc85c82b562caac798b55e5f682ae1e7a6cd01b761686d93004d925", - "privateKey": "0xaed4fc636d3795d16b7f4b68826429bed1ce84b0fe13853877e8276d11c3f421", - "publicKeyX": "0x113ec1d7e040a6b4d935ffae708141bbab8a238d57169c6875c591100f58167f", - "publicKeyY": "0x1b68c3d6bd699b2d28da5fff9a4d9881e64b980b0a37833dbf5cb67a9941f78a", - "r": "0x52dabdb1e6f38968e98d4a57a30338d2c8e630426e6823a8531a78d74ec0ff6f", - "s": "0xc9b883ebd89ea41deca9576116097952d698a289b3d381cfd2243a8d2343afb1" - }, - { - "message": "0x34ec44b7097fff8a5a393ee9bb15d5dfd1cc5347ba26cf9c5f6b4cb80c6708d0b29e49cf3cd6c2a726cb9f5ec0df398dec91023118552acad1ad8ccaa55d8a18", - "privateKey": "0x916acaa2e9f5e5a07e75ace85d0e7ffabaf351a696896fc0270daa05a81311a1", - "publicKeyX": "0x91dc1b3b49e60c6f78b129ba830bc52e6e947ea9ab23deb72678a3e46b4e7855", - "publicKeyY": "0xfb05cba4d4c5d47bf92c5da7561e520080c36dd6025b1f6c2f8389507a306de2", - "r": "0x849c4cf0bf447e2444033e271b4bfd11266a3d8d93b913b8c450cb51aba4d5dd", - "s": "0x3467ebfd7ef2c9fe30d67dfacd91c998537f12495142fb16cfaf096080c6b7ff" - }, - { - "message": "0x7ea2c8c5dbc0079d06c62a6507c608728d11343d57181df11d93f40a536590f65dd98bb19c2a99494b318671c2f3ab5109949c5648f00d078e9995f903797b9e", - "privateKey": "0x3e2a78a03bfd2fabf488f7755b986bf03148d19d6ad9921e14636416e340e802", - "publicKeyX": "0x65f321d22b68581d3bfa3d992529c7af38c3ef00f59056a0231c4b580ae51715", - "publicKeyY": "0xe8dc35778c0ccf1e2c14282b4a163381c093d3e216b0b9eed050d55ce35c6c70", - "r": "0x2ee2d16fa86a6ec196b48988b1ad5fa791a1c6f4dbfbc008dc3a99fef9fa2b42", - "s": "0xa399767f281091a3f82bfc64cc77af48c6fa5753375433125229e9bc94774926" - }, - { - "message": "0xd36ddd0241fa4fe915f20883ee8c23e06516f5de4d8092e58c7375c0f609abb8f829bb9706b4f6da32e568209fdaa9ccb2ba86681519ba98d8350c934e454209", - "privateKey": "0xac7a5c58453d490e690aa372f97ce291fe64dba2d4e1af85451a37a3055ed5c2", - "publicKeyX": "0xd66ada616a9830c89440ad8795b4f67c376b451fc0ac3811583c4b560c0595e7", - "publicKeyY": "0x111cbac11569f7ec4abe52656c88dba8d190c1c19dac51512d042e6664419515", - "r": "0xb32659d2fce52a9f70d47abdb58c4d0734e30ccba71e9d88d422781dfc5b4876", - "s": "0xf21ae730979f7685899e611b310d107fdeea9b7f5e9a507da6307c47fa5fc6de" - }, - { - "message": "0x12c13bd83d1aef5a51a8ede0b5e0d69f8e8594ad526b80a2f48d5f118583f841e72f1afb199fc4a77c0a53ab373928445c2d2ddac8652059ee55dd1a144a44fc", - "privateKey": "0x727d6f6834a3cc28fe10dc4c1932f45a07948ba46d8827b8d9fa23b261a6c385", - "publicKeyX": "0x48ff806ad7a819e422170fb42d8e6162a800e149b9cc17899a8155418176f135", - "publicKeyY": "0xdefb2da725dc2fe323f28c622b4f5d0e1ef3d8d186be9c36a90de90458e329d5", - "r": "0x82ad0ef6dc388594919677a1fe356d4848205a2bc51fba0c95501c6c37bb2fec", - "s": "0xc30c1524f6549a9e626f096c8824d0c70266e63ac0d9a3da871f11532a8bcc28" - }, - { - "message": "0x4eda5782ccffe3f841385abad1605f1b1d73e9100f18238f38bd75109e20782985bce6c22267230c9dee5475521dfcfd4437eccc41838643dc5e3618c048f6da", - "privateKey": "0x8813172f4627998309a56f36ef7b76b73ebf208bfc90e87995ce3d40b18cba8d", - "publicKeyX": "0x56b7bd6459214decb305ed887c68fe942bb2874b91388e80272386c624499b2a", - "publicKeyY": "0xd6f7beb5d34ef7167da6b25a6f1ffd4cc5a717c99cd7af823a40cee4c5b0f7fa", - "r": "0xacdb91576852aab0c922a074159fb707142f7d4236d509831ea4c8085fcf254e", - "s": "0xb5b61811744b1f05b06d2530b5837c4e099b34aa55cb1fa8bfad1cdac9a383a9" - }, - { - "message": "0x4836b9ed3ea6592478389007bdc605fb8fecccf90906390a2a4ded075a56893923034960e72e86835f6b07f0bebdb69fec10226cc3b2db1915bbdfdf2ed09f07", - "privateKey": "0x901a62f5232583b1d33329128f49ccee4b81fb835b0454cda04d1f610fcab221", - "publicKeyX": "0xd3c4c094aa130e48a9e87dd6f36303a29772047916264637e9cb6e1aa447b579", - "publicKeyY": "0xe889142b9109791a7f366c7a320b826390cc1a8a92315053aef273502d80d884", - "r": "0x5ba58178f46495d6ab3ed2cb85c45deb268b5365ced22e05f3269de8c18b47cc", - "s": "0xc953874c1765bd123a0f39860ccfe6f20e6b0700a621be040c52fbf2decd5daf" - }, - { - "message": "0x647c01801585e7e18bd1e57aebad3ad0f17b1b55080635bbf07fb3b8dab0c8dcbb7345c5fa500f2f0aeeed72ec003e8465805d4a39f0e57ff3d40deac3ba526f", - "privateKey": "0xc059e6c9530bdedd825f92858848fcd970a18af20d0210f47b33e89336944f98", - "publicKeyX": "0x95f65000a75c653201d25b0f3d557da180e6ecf1e9c8d0c018aeb3aefe647585", - "publicKeyY": "0xfa09647cd5bba01549ad9d2d34e058f575f684640be7793f2997af976c4d4719", - "r": "0x0dc3202738a48898311642ab8ec0c3681ade924a7eca136e6b7bc75fc3054479", - "s": "0x565196c911dc5c6cecc451698a929408c0dda7d76134af8f2049b810808385e0" - }, - { - "message": "0xebe64a5b629c8f4d3f45ed58f3abed022075dddb307c7c887641737f93353313b7e2757691520133d2a655b8c9ce19629b7345441ce1a72f1afa5ac3d1fdcc93", - "privateKey": "0xb7fff2b4e1ca23a4f5cd4bc82b6b5c489dec218b52ac74894a5621d1b5702cd6", - "publicKeyX": "0xb5f74451a83e24567ce9b57ff6402e9b838d47e87f4367bd37ffca7ff90876cc", - "publicKeyY": "0x46b9c80d8fd496bb16e6a51610b6fbd26bd8dc679596ee5649a27f73360797ce", - "r": "0x2719a3b289dfaa5ddfa754c17de5277eb26ea35022c03df7476978ea03fafbb1", - "s": "0x3c841095dd8d20e8e4dc17991122e85e8783fd4bdd188d8eec20072b20291a02" - }, - { - "message": "0x41af80d164275ffff83c3132065c0944d1712ea8bdda3276d2eebcd6c0f2fcdbc00823b5cc648e296364d6ceaf99f651cbe25fe55f19f6f660e244d4cf08d250", - "privateKey": "0xed2f91ee00e127521b9138775968dbca2607437f947e45d553fb0c173990682c", - "publicKeyX": "0x64bd1de5633600b9bde796747ff029447275ab848362e643d61048e17defbcab", - "publicKeyY": "0xfeb9a03be24a68a26ec247ebbde78881bfd189e127d61f866b277a825aba5c5f", - "r": "0xfeedd45d46315b3aaecd2cb9517459e6109d7c599a92de361295acd1dde43430", - "s": "0x854c47ded15e5cecf6dd50b59923945da17e90a9784b7bbb0c0b4ef4bd8fc40a" - }, - { - "message": "0x06ce4c213d04629a175a469c8457ad72a9f1e5b4ac93d8a09b24c06f3409bbe34a61add1097079800eb4541dca44adf2524e1eb76cfe7371d7432a1009469147", - "privateKey": "0x692c7d8918825632b33c328af98a032d12973843c818e52c1e4cdf087bd4937f", - "publicKeyX": "0xd5613c9214f9c11b1ea6b4968a9917631ab0cbb686821e133da1ec129c2e9ea3", - "publicKeyY": "0x225b52752b7d034b1e13a31d69a4f3f5bb46b11f06419690a528b13e6f34c230", - "r": "0xca7767848448c05947ef418027292407f3e55ebb106f8df5d32f9025840b1a84", - "s": "0xce264058615d2ec9702e67d90ce3778f760a291cfa70d7aea6ea9ff0abee9fef" - }, - { - "message": "0xfb047343c29b1b341ba61ad8c253f4df5acb15a2dddee9c9a6df8343eb2977b0e710498280224b2ac28bbe3f35c7b4c0fb605b4cf513842d0cff3ecedfb53682", - "privateKey": "0x968d90bc5c29ebaabb8871b8b206fe696cf8f5fb9682c08318c04982150920a2", - "publicKeyX": "0x107adac17fd406f01060756a46aa21c0ae9e735dba7aa739e3a5ebb9ea3050c0", - "publicKeyY": "0x7ac9014f3117efc49d60f12a571345c63158a0417376cebc05e4f00ceaa63ca8", - "r": "0xd86bf3c645747382d69472d736a46c86203a4e95de3983956268d9cfe45e259f", - "s": "0x6a27b70afa85983af1414323e3d71b8a95e9e239527c5cb9b0fb17058aa2a59b" - }, - { - "message": "0x0ea7bf84f53a54b91fabdfa5c7897f3c6cfb243b3ac233e3a950d0c033a9c58c994c67a633cc3ac77d1297a0e28450ec9280d6c5af937b2f6c45853171d604e0", - "privateKey": "0xd30dde66834330a979530251d8487ba0c36ac1083307138a7756c86fa088f050", - "publicKeyX": "0x40fc50d63ec1bb349271652500668374112b5d7df7b91ff52bc80eef3e6e6ffe", - "publicKeyY": "0xf801d9a9ed69ac005c7428ff7845eb11a18e2f524e970849673e6558984d77dc", - "r": "0xd6afb57eba31b0b735fb703a4b991a3fd2552f8bca9c75c09e426e0b20364fbf", - "s": "0x39baf1f84f2421ea259fc237574a18b5c86c392ce4324b6b44bf98a31da3cda7" - }, - { - "message": "0x19ed5f6ad6a0d6710029cc8fa3c2b0ca84420eeba37643398af519f4500dd7a5939edbba12a5ee95d96545f5ad5fc6cb9877478de5ee63bf4d3665065a942dd0", - "privateKey": "0xc276a420ad97351c98f7da5c54b0b8f6160329f8b1f672f12d4bfcf04d0f2526", - "publicKeyX": "0x1592494c24e61002b2d33977d802f0da3bbf8ce46d61224d2d69580c8938932f", - "publicKeyY": "0x5221af2d4594dd918d9d1966183ba149b902a0aa3bf2383e3d52c48275dc7ba2", - "r": "0x553ad794c23bfa3db688ac8f780ac3a9930a27be8db3cfa1d50b5a3fcd7b4a0d", - "s": "0xe1b6b9a6da805670516e39792c0a6e5ca701d511b658e0a9cf5be9c0fcf10c5f" - }, - { - "message": "0xe1b38b9ca51a1f2086d47e6c09bd802375cd0c6c4aecfe9d192fadffa9e2cd472195aa9b537103a0394a75c4dad0fcbd6d2b1ccfb2ffbb105be87a7fa29687f3", - "privateKey": "0x64e41dc387f569b14d04b4d747ab9d5639966764856d62395d49e5d3b41aeb7f", - "publicKeyX": "0xf1c5cf1a517b8caa07e1d4b011fb96df038038fc49abd0408e3ca69457333cad", - "publicKeyY": "0xc5dcc8a0d3b94391ecf06ae0115597245dd2333b039c2ac6caac9984a5a4a056", - "r": "0x41c2c3647c7e1c525f50e34827fda8217aa14e3eb681b1ea1346f72fb61493d2", - "s": "0xc5984fa0a9ed366490cfb58176b6e6e552f75cc93756d95b1b52910cddea4d9d" - }, - { - "message": "0x70fe30b2e960e88a65c67f41dc3f6ccc6914b050cb6accef4f77d38f232c4a1f581e1ab70153a4d142fcf60cd70f51daa1555f89c5d97ed51209d3d98c26e8ba", - "privateKey": "0x2fc269dec13281bb1f784ca1562a29c7c7dcc96b123982186f071a52d36d1071", - "publicKeyX": "0x7fdb4e27d697b4e35f73404f47ed88c62fabb078804a00d78b472fa385cf7457", - "publicKeyY": "0x1422f87e6f5f29b1c937ab48a00ada4a40711f389a520d7d7a4af88926e44b2d", - "r": "0x96865f7dc4a9820b8698f595f43f1d051508a9d6b9067140c938641fceeeb255", - "s": "0x6f75befd473215870eecbf968b594712f3ce7d60da1d7f39d9f4a5e85820625a" - }, - { - "message": "0x7e2f7fdd5b29deac9395471acaa62537ef5314e05e6a4834cd2a5be073afdd0ecddd66bb2a2ff786b7eff9431bab1f28d1ec7e9bc93727b4b66e1d20d1717bcb", - "privateKey": "0xfb35c2a214fe3466198be0b4376b5fee596848fd364f65a25814921533d60c04", - "publicKeyX": "0x8e48695001758a83d459490cb291d2c4d36c507e58e734a3f8b5a29017c52c11", - "publicKeyY": "0xbb1aa31ff7e4b8a94d295f36ceb38bfc6cecc041c9358348775deecbdd51d28e", - "r": "0x5909a5cfea6257b69ac05aa03b144fd0bc71c10f3337a5e325e8cc6a3ef16025", - "s": "0x426294e2fedf1ff14ea15b9313a04250a3407143bf28f29a0271994b8d7743a5" - }, - { - "message": "0xf01bc05f9fc498c344f9444d6d7e3ce2386d9318056ca464e6c65472456ae1803478f8c8344623282ea66586d5815cdb6bade8a5cbabcd4a90ac65afc42c1ab0", - "privateKey": "0x3492eac0f24fd1a96b8d55f52aab3c0805d47d2621e40ace54d2483bd59593a8", - "publicKeyX": "0xad20b80911a7116fff6f938a9cca4d8fcd48055bda3865bcb4f9f194fce5af89", - "publicKeyY": "0x614c429664d14c64974208756ecaf35d67f8a5da4fd9b10e7e38dab3a822a8f8", - "r": "0x567aadcabca6b7dc79298ef4fbdeb58199bd6aeab0621d3b182e154071556263", - "s": "0xe1050d5353c91d928317a299b48577eda8f564af0c165002a73bffba071b8023" - }, - { - "message": "0xdec73837a169c79a01c39ca8cce13520b50d48b8e919761035dafe3f5b33befb87ba67400cfcd630de58cdbc0f504309cb6ca9d80e7fcc8de408dd0377123d05", - "privateKey": "0x9d825eb0fac5a2722b2766e21bda4999c32fc740493f166224e8df5c5dad7776", - "publicKeyX": "0x0d3d5a42f86bd0ff0600b269764e5d5083a0064cacccd87000d003ef580ffc62", - "publicKeyY": "0x5908b2e00bb0d6a20d1d1972d0233aaae0609f5427dd41689d22f457e4cd4f70", - "r": "0x20741dae439c1f124bb4787c462c9d605588d8778660750e5a404b60de99a534", - "s": "0x7198f22c911c5a529450a945c708d631a6ae74f63be2df223b00e1cb4313b9e5" - }, - { - "message": "0x1892b0b0c15e186515d2acd6f3fcc43ef6caed56e33283ca512df8d3495fc0e571f1bdfa11537889518c081bed87fca4375e4b7fc0269eefc9d4219dd612b215", - "privateKey": "0x6f6cd7f51c2a8e488aae79f6ba2e393d9018bcc2b8f65fd5c45ef9b39a46628e", - "publicKeyX": "0xce8c332830ce374413b15df098d40ee8321ec379a683683a68872e03919c80c8", - "publicKeyY": "0xaa498307f3e087f65728d0a6057015aa2568a27e409344dbd90218cb0b6ab973", - "r": "0xef9f0589959d4655551daa0d608f29b809cd38a584998a8312b9648c42da62a8", - "s": "0x6feb3be7145ac7e2e62a7a26714637cbc9ec89724d69981d0138396ce1c452e1" - }, - { - "message": "0x6945af3214f8ab123abb9c897f01205912d6d91f40d38943d2d3418c3f535331c8adb1cd154c97b26671d9ed8bbc21d1a660dec74586ea19180eb82dfcb1bf99", - "privateKey": "0x1aef666cee4689c89711c33c046e76f156b2f8bcd59d6b97c220d5d55eaea3f8", - "publicKeyX": "0x0efa80b4fdf63686fe33ef46c60ac6339a31927ef23f67202eb999711e70be25", - "publicKeyY": "0x84281d5b95aabdd1a384d9a9c9c4d9143cbc950629cbf00d3e593bcf97cd125b", - "r": "0x8df0a563ff3ccf66f663acf3608549083d25fee739a3e21a2a20f2844cc6edc1", - "s": "0x89d3cd01c9f35e63225ce17c851b402b17f4a95675e7af4cf2b291c993e8c18a" - }, - { - "message": "0x3850b75141bd9521a146c8e7f8f810a23dcd5ec368ff5936f4a89e51e1cf899eb6a105ea2a82959df09cf9506b6d7095f3dca6baf954cf273bc09c7288d1657a", - "privateKey": "0x372cfabda5876df6e151f570a00d7b7318519a195225c040fd12cad41f0b1fe3", - "publicKeyX": "0xe18655f48c89caea57cefe19c1b24e9a1acb8e72f80771c6265175f4a2c81891", - "publicKeyY": "0x5848bfaacc06beed3f3172e1f5fd7ca21378f61d14a3272bd7750d64e7147e03", - "r": "0x565cda8cef5d1253f23e73a6b1e8273cbff0f4ed6b205354e1f6ead1550944cd", - "s": "0x86d2d49c47820ce9ba022076099006e340a461f667d24a57d94388f33e908030" - }, - { - "message": "0x6761ca2e940ac3f11493e73ff79c707402574fe25fa44375c4bd7ddf38896f2ee1c2f20397b17594a879d66002ca37683cf34577a4c17c9e57a4f1f8a442ba68", - "privateKey": "0x308a44c56df375ef61596a10b589c92db7343c207aadf10e8776357d2d8caa79", - "publicKeyX": "0x717e5aaed49cf2f9f425cbabd710cc1ebfc137b7ab85e20f24c94d7ef40c2238", - "publicKeyY": "0x12500d0db0e0ea471f36d79d4dc27cb2fb8961d06ba25b247338d80202789967", - "r": "0x223e03ec811511f0d3b68431f7fdf54dba4fb317943482325deeaf7cdfb636e6", - "s": "0xdb6111a8379d661827dd09edec052a2ca0d6293839f5368aa712912cd1792d20" - }, - { - "message": "0xc9855736a81046f374e58873ca7de720ac735d21055d626a44cf51744d7a03016314871900cefa8108d72afc73fc00d478461cb63dd8ff7b5ff2f20895cb1eee", - "privateKey": "0x77b0d1f9b545dfd04801d99fff9244502f58b870625344bc80e788254398fff4", - "publicKeyX": "0xccf6919ef61d6c5450bcc457b3d5cfdd6f63839732082bd741630b46dc5413c5", - "publicKeyY": "0x57da4ddebb81b205c1ca5270670fb621a20a37820d6201b878531d669634448e", - "r": "0xe8a9450bbc7ff09d13184cda38e4c297f1b505f5d0984dfa4e582717a2f4421f", - "s": "0x423d8119dcf4a06da1d76c7155a41ddee6504dcbdfa6b6595944ca0d9c9b3d0d" - }, - { - "message": "0xdffd4d6ef719645de648a4a4b9a79e350785981d51b7f04d892fe5147aea27469a7315dbe7dd218d3d457a9ca8ff0592109d94b3c4f4f7bf418d1f8634efc486", - "privateKey": "0xf975dce03b6183ace499d25e8682bf451f1b334ee4fc27b7660ad99093afff96", - "publicKeyX": "0x63460a3aa9b19046544a034d50bdeff5e168f0f4accd744013a173fdd76885ad", - "publicKeyY": "0x6111492eda4276a756a31287c8059ce6e91f167336cee4b760fc5f290d9a720d", - "r": "0x784b72951fb2f9c3ba506bb5ad9108c60f5bdbeacad8eb9d1bac64a52d2580ac", - "s": "0xcb1f1a7075fd90f04206329cd0b72173d63af315f658fdf9f6412706576cf6a6" - }, - { - "message": "0xe005e4215c76a1ea8acf33dd550bb7d9c8cf13be66754247b9f7257af37985d11d30b83d572ba720f0caf989afa9a1a4b4e7ebf87bafa6a40525df87902ca4b0", - "privateKey": "0xca5286ffa0cb9965fe9e2470152b73c35ccdf9a38cb3a60b4122c205be1bc9ea", - "publicKeyX": "0xc271037bd8560241eb8d0f50cb8f5ec6945abd92f8ad32275d2de08d46b09738", - "publicKeyY": "0xd949ef4451ae3cb26ac8ce2ccb63fad742b6d801b47e8e0738f75c9d4e21f77c", - "r": "0x67fce94e74151c3ab9e7985cb72e11ea4b9c4c101ab0ab4cc71f02a3095a23ce", - "s": "0xaac3e3ba9c2159c76c387db226bb56b77f6d0ae6f8545b05f6f38a5ccb2d7eef" - }, - { - "message": "0x2ab096f12b61fddec134091c712510945228791ae09b9edd3c12edf984ef6fe30086d544c5fe2e9320b04e756cecc61544fe5112df3271f84247effb7c710c87", - "privateKey": "0xed903b4ea78ee1151b9c893e30a1f48d3e52804cfeaa84e09d9c82012c442d1e", - "publicKeyX": "0x19d293eb70903363a9404ed07472f4fab4f8dbbe6cce3e8d525d0305cecf2622", - "publicKeyY": "0x2f1b58c33cae95c8d71e352548758d6019736a60b2b24fdb1d0526ec65ccbd15", - "r": "0x7c4eb9294c03192515d071faba8fcd821a0e3c5b30a9ef98b4a18f1f48386e45", - "s": "0x9fa06a6f522040ca12bd5e5c30a5ec50aecb45015b0c91f6f8ad7570fd69251a" - }, - { - "message": "0xf67b271948d8697653dcd041b3de186016416946bd13dcab880cd35f58348a16893aee0b22776c324b6473ff409f1fe110f3d8938fefbb034f83446ab5baeb7b", - "privateKey": "0x3b5eb3316b63cd9f4419c3fa38bfae5970bf2c5eb480f444bd2e4c811f98358a", - "publicKeyX": "0x4dca62cf2aa1b1aa6063f31d6db28dc6f223f068b44caab6bc007f94ad845abf", - "publicKeyY": "0x3acf3364e6ca9720f9f55bbd425931dec12af07eb47044eb3ed07beee99c0b84", - "r": "0xda42cca18f0087163bba3aa4cc05e21c42b1d1f2a61b154c60f9769e921ae60b", - "s": "0xa8f5da60cc974be2fc09589ed77db3236888f45bb490b2fc131e48a2ed3e5d47" - }, - { - "message": "0x93567d9ed491fe521dc2399a07845c3bf4bdcc99eb98342322480778d2b2cf14736dac4d8305eeb552573ffb022f9664a2dbfd64d91f60541edd01303b82351f", - "privateKey": "0xac7fe19dbc41228b09ea2d17c6948659d2e7f37bf98dd267b0688d3bb7c10c9e", - "publicKeyX": "0xc750df0f1295c280950e7931e1db3edbbbbf1a9567fbc3c9996dfb7f0329ef82", - "publicKeyY": "0x480f6484e25633273d59fd932c4781ed7faad0b186b812dd9346613fc0565911", - "r": "0xac38fa645ebc50ca8800dcf2de6a8906cd66375c296190898442b463d0c29ad3", - "s": "0x459934aaca63efe5ccf3e137ed9f52d4e4a9928d1043dba989424c32b7f6a317" - }, - { - "message": "0x9b1a94374f5477ac7565fb5b0c82d2ce2452410abb0636516697d3a1b16598e6bc303f90d205979f189806bd737d92362d94644e7e58af5a88ccb3ad71f02fa0", - "privateKey": "0x77264890f2776d27c421815e1f07d17075fc8577fd76a46f5b40c2ca325eb733", - "publicKeyX": "0x09281bd5492a89e0909956c6b1da5a582eb5b54dc80c36753d9d74abda2c37b8", - "publicKeyY": "0xba2da49dc74a5e0f1efcb57863f876a430013d1f29ff22deec641f49fcfea6d4", - "r": "0x9225a21db876dec8c5eee5185558b760ced4575a61b70c2c290e7a74edf2395b", - "s": "0xf44784188dc2c0e74224217a17f0f5904f678e3afe9389dbeeb90362a58cb6c3" - }, - { - "message": "0xcaa375cbc4e6e9dd66f1463872f8fdb241d87718f5850c62f0083a8d51707ab25d185090ed30438831dab348af2e4980595a7af56ff857ee89a8cfddb965c793", - "privateKey": "0xac26e82e4fa7c72d147da1a9dfd78fc1c243145f31327a5c0a20cd8ce2786282", - "publicKeyX": "0x602b82d2650c1b15ce3ff1918bff0f60866a34763338df54d089520b5a1febb7", - "publicKeyY": "0x8ada5b9c05579953aca1c54c79ce723f46a6967d3d37e8ba2616022aa0f90907", - "r": "0x17c07911dbccfbacc2162387a2da581e598cb5015f220d78fe03edd10ce6227b", - "s": "0x6ec22b7602cb02a96c8553bc19fa5b3326657a6960f44d0a066f83432937c2aa" - }, - { - "message": "0xfaf33d7e2e47a9e9e425cc11e4bdc571973fd570978c596d7efd6acfefe287f2505d5d0a41886c6bd8dbb097732c6093bb9b9c6e7fe3b25043039fdc455ec385", - "privateKey": "0x6e6ace5dcc1cb31375d4ebbe5699b3a955c8144f5b4584ea303e8a81b22a6648", - "publicKeyX": "0x4c45230aecdf177622c954bf0f4a52c7c70c94cb2fdefabde7004dc3ade9683d", - "publicKeyY": "0xd801f4877ebe306c834f00eae858d7c7e6ed64af4735573bf8f8ff61c67d166e", - "r": "0x632b866b79bfb1644caf571b7368e604a77af013aed08b0b17c2272eddc6031b", - "s": "0xe33026982e484db41a927d82947afb4be77c6e8b5a0ca3278e51380e47d01ecc" - }, - { - "message": "0x1166754eb943080d8eafe5e1a36ca5d83e40e8f29ac6f3ca1d285ff99f61c82544d37e7be489b15a39f08cc924d2587d28c48d1e4e47da146397e7f46491cd4c", - "privateKey": "0x020cc373b4b0a34d9df6f63fd7db548617b1df2b52405562d13610b1a8b2d3dd", - "publicKeyX": "0x3e5d7a28d7831113374173582393da9cdb385e49d6a599cefc2e7a1993ac701f", - "publicKeyY": "0xffd494cf354e3ae916b7f9e270adbf3daac1fe6512e04d1f8e36841410f007ca", - "r": "0xe882f35946ebe223f7f0b03fe95d4482499ebf4cc8d6b4d403d4ace163492097", - "s": "0x14221753a41571eeea8feae514eef10ff358f0ebd9bde28c30172319b3b665ad" - }, - { - "message": "0xe9272f3000df1c79f05233700b9d76597d419b223bf714fbbd70483035450db9f4f5e992a54a9ee4c0d85eb3768be1b18ab87c1df0600f9de10f4d652b0a0d28", - "privateKey": "0x5fd1195b7a9a78bdf5d1e8b48f9865008d039124877d77296f587b50feb8bfb9", - "publicKeyX": "0x115ddd6b3ccfbeb51490dad7eef1d68b4a19c4232c36bbc14982f7afc2c3d2e7", - "publicKeyY": "0xf2f2ca83212728aa6ec62dbf60926847755c388096a4ceadd78b60d77f527216", - "r": "0x37f269456612a27041ee7127281e97d0678177b5fd72dd2cc4f84253b038610e", - "s": "0x61aeb96e26bfd43e5b7412065b7c548f44d365f2cb419699137ecd73de5ce19a" - }, - { - "message": "0x70cc643098b9b61b6ebcfb2bbc1ed2442ae2a4a087b75874cae8a14d7c1f1fb3ca8a8048253068a6c9132d1f38440e887cff32bb04344871492d3b3fa5b21b4c", - "privateKey": "0x9b677ae0cdcacdceb4205baddf8e95e62d6ddbc7647d23ef74938b8bb7c32cbd", - "publicKeyX": "0x927c979a5bffc86d1d88fa607511580bc7d4f7fefbe2a6cd14e2a6193d78735e", - "publicKeyY": "0x9cf021238d91b69526caa4c6be9af6544918af518d871b9abd55127bf4f597da", - "r": "0x51408f51be1f2236a0d661f328c2ca8c58ac813dbab7bfef629c6028e75aed6c", - "s": "0x2f84076cbe3017fe0aea8892e566a27432e9ef0c8b1a37c56a5824232fb73917" - }, - { - "message": "0x296d85882fe09ecab2ff73c276fae2d20e4e8f1bfb9cf2237ea17cac7abd8ba93dfb97ab729afc2d1307f79e56989a7491754c8de6fc39ed50cf855796d4ae0b", - "privateKey": "0x947bb80ff3fbc2629e52562d89437247afab6cae36b3f1a4d226a8bb838d2883", - "publicKeyX": "0x6440924d060bbeef52bf73a64a6caed0b4a213cca7c44c8ecad219fd9ec16e2e", - "publicKeyY": "0x5a60e163cf5e1a24c7f3fa9be8a45f5407d5faf39ee9470f8ef4ca993616a5bf", - "r": "0xcf1a53604bf5ae550f667c96c3978360f603606e637df215355c87e2626fe8e4", - "s": "0x18ada0bb1eac62b7d49baee6127e012c6f58e31b5888da19448719063b608b28" - }, - { - "message": "0x4f330f45ad5cd7d241482f7a9d6c35c92dff97bb299dd49932cac07884da642ce5744d549705259643df6a30876c5ac5450e69cb8595f8f5bfca3d23ff3c075a", - "privateKey": "0x660b17557aecbb80e38751bfdb417930543613ed9fbb7d872ba22466c6b4ccfe", - "publicKeyX": "0x403aea3400fe0b7b25c0756a2bcadaccf1c3c1f66f088fd865e5b5b2e9f2cf91", - "publicKeyY": "0x0c959c056658b067c930a223d05103696c5c61b973df803767ab4dc527be126c", - "r": "0x5a6aa38d3dc4cb03f8dd2ce9eba7925d19904306c98879f5aee1bf43efeda885", - "s": "0x197fbd99059d0a36cb53dbb20c41172510a41ae5bc2bbad3f0007b790b59e2bb" - }, - { - "message": "0x2a42766a79930707d84350ee78a7094a9c92b63f95884e9e67653f5a6e473e7aeca7dde2f5b060d51cd56a076c4106bfcee8e5bce8c2a737db6a681aee8e42ae", - "privateKey": "0xcce10e37b62fc27e57eff3c426c98c5157e263b5f15c1f5062debdc8c50f03b4", - "publicKeyX": "0xee5f2ac18d064e2ad74cf82ec3d07692a2d033882298a681de10a53e5c491221", - "publicKeyY": "0x052c4ac2dfe3a1e87ad1aae6153e315487a765abbdedab21be3e8829dcf5667a", - "r": "0xb152f4ca417613f0a8769186956b1a2a536e40ad37550f805834907043484949", - "s": "0xf06213187489745c10e4a08555eff07f60f6d638419f3e8c8e906bd90bca48ee" - }, - { - "message": "0xdd4c6d79ae996b7f363b08944ae08365a52bc4161c9750a712995cef4779b690174296b8b3dda607eaced41760c43e67eab368f0d099db5b0699656db9ca7b4b", - "privateKey": "0x924f413b1c2b4c408f2d3b87270453f1ffb127466345321b6766813014403284", - "publicKeyX": "0x1fa82f10bf039d7daa93116c835f018e9f07ffb46e2f03a266005692f95935d8", - "publicKeyY": "0x03f4cf2d377113eeb59c3ba418581e7bdd5e9381a5df4395a666dbfd7dbc55f1", - "r": "0x86d6cdb57ea35f7c131544e40e83ff623a5ed62889ec798ccc6bca52e583fbe2", - "s": "0x776c544d71acb13024d1d3cc112d9ade91cb43f0b69069e077986223e85eb5ad" - }, - { - "message": "0x03473b24ab45dc9f1dc1555c0658c11b821dc40eccd13e67f3755eff78fa4a09eaf2e4734589af23adca9de08b22643ca4723ead40fe5adc2c603ecba52e01e4", - "privateKey": "0x177b3de456aeb5360a185c4313b466a7b38923983c07b05ceb6634977ff5568b", - "publicKeyX": "0x6e51534b5c84257b3f9c0ee261eb9930e1863656ddd3cfa1ced455e8acfa3cce", - "publicKeyY": "0xf31be82ba1de3be284da9237a78a79271f3b86213fda3e3aac671842e1bcab80", - "r": "0x8416b0ccb08bc97fcc70fa0ec81cac2320c3679a29a11a11cef2f5989def7582", - "s": "0x02b76cf1856d8c079a85a4a8bf398d5c96256c312fb29ccf3a9a75a82fddcd08" - }, - { - "message": "0xc6e65107796a7b69de2a643a5cf92d651d0358a1bbf9127812ecbb4650c5bafc66ac087e9e99a47250fd2e6226d413ee0bb5d03e66bd8f1198c0dfb3b02ba5b0", - "privateKey": "0x638da6a10777382be1a60f46599149a75e952eb9727d6f238883170729b2d785", - "publicKeyX": "0xe9858c125d88dc02336d7e5a6703511cd680918ebd04c7069904f23ce9c6af58", - "publicKeyY": "0x56f414cd2479f318ea758ee60f30125ce0b3f2d61f8389792767310c9494e7df", - "r": "0x90c6869fd2388fe0095c8969189d7c081d34516194b04d5cdbc9355670d3901c", - "s": "0x42e248bdc99b14d7b7955146f60ddebe81226c6f5d019805f590ee69cc19b0d6" - }, - { - "message": "0x3f7f6907af583b2d409a79312e4da4761da9f49dc18ff9ae5ea29bc86d45727e9bc21056b95a8d96e6f58007b7b4b385a30c21412606658ab60d5e0c42bc7fdc", - "privateKey": "0x6fb2fb37d2bca85518a428223babb2135d4226c2e2ec6a563f9d7ba935a5432b", - "publicKeyX": "0x5f71a53b45156b9003e1c5f52fbff490be2cbe2bff82ea715f6111ac78a8aa66", - "publicKeyY": "0x7c533b0fb58e3e978e52b570205d38496fe1c10f0cb0183e7b90f57ad92b99c7", - "r": "0x1b2de6c777682c5c09a877eb122bc628a297b098eaadc07dbf2bdf6ca67adcde", - "s": "0xa4c8e7b8ad3d83494568c5b227c7b5379c9b7ddef4a05c98497ecbbd119ab019" - }, - { - "message": "0x76ff344265f92857b11e25039b97a4b7d94a6c8209f4304e4c39f6c955663b7d7ec9e3beaeb1a2b92625190e96df22b4291e132535f66f6fa097d1e81bbab466", - "privateKey": "0x323e51081eeaf4638a746a9ed5c7eaf40ac8b787254e47455410e0a4a4c44956", - "publicKeyX": "0xe8d4853842b469e6c7ed3739e6b013b77e86ff66aecbcdd62010482b9ca049e4", - "publicKeyY": "0xa911d73cf93bec22d5b3587a3464db7415baea9ce307cdb3d7f6e1db7ed61068", - "r": "0x95b769b4464677df3ab1c69c785ad6ca7703225b24775d7b14f2ab6ab7b4c3c3", - "s": "0x0654cb60a8f904db678201ddac678ad312476b8ec0218199a217bc777f9a9f51" - }, - { - "message": "0x94b4345e9f004eb8ae96da2bd1f0356eeef249c8e275e78c27031da1eda3a9a4f2940052bb4619a7e5351f45d5b9d06d46226b9a647fe6f9eca5fe24c251d071", - "privateKey": "0x9f0e62e34a7243cc69ed908ddd98e35c0fac27f526c6583ac150339f58b536e3", - "publicKeyX": "0x4d500bf1b2a5834a79f427ecf63744bcebe329214a12a856151f58e3233628ca", - "publicKeyY": "0x918bcd4db20af18050e7e7718622680e4b18d8efd74589ebe1df342b2b6a7499", - "r": "0x35a21213ab758fa4d91bf3cbd35d845c2fa7c881dc83839f16ab66416def57b6", - "s": "0xde7acd6f179f6745de70f34698446fdc21ffc2c5c1b724217311182bf10bef25" - }, - { - "message": "0xde9524eebec68fd89c64883b78ca95a747b35690032c036119c39630623b7da15fb78cab54ceb9f88f466063b17f6fdbb1f7d10b626ab0ee34e55ab0bf362eac", - "privateKey": "0x533a1a73c5e5e3f2ccc5d61d338290088713d17c8f0d997aeecdb47e2443b9a8", - "publicKeyX": "0x63749c55916c2a0eb9ffa3950e0587febcde3ed860b0cb77cc43eca00bc51184", - "publicKeyY": "0x31d8bab66124605b4f3b477c6347402c4bcd3c24cbe9ee4c44848ab89b1812ba", - "r": "0xcb187e4bc648164adb8d1e999edfcdb9d710b81d8fa025347efc8961f2a5dca8", - "s": "0x8619dba0531e9d6be761da9cd7cae1711fb8a9bb6d6a394f6b963d311b2ffe20" - }, - { - "message": "0xf4f12716022a697f369447f4b8753e4b2a4f5d551379d067a78ef21c2821a2bd3fad2761b3f25205135247fc2b4b0b45026f838948ded35dcc9d5ad24b51cd3c", - "privateKey": "0xd0f5843f4963a11bc7910f9dc2e6b95bfc09a104b4be20e232b2aaaa0ef45209", - "publicKeyX": "0xe7ac2266a0aef49715a6d610ac39e50bf8f7174e3c169c911c20f7a0d9c5ba24", - "publicKeyY": "0x8eaa8784c1cd5c07b6b9ea2007b348baac6a6caff75e476e85107f832c1815de", - "r": "0x41ab877cce8620078dbee25fa4a7ae49a4eb570b374ee72251632a781e56874d", - "s": "0x1ae1044d948c56f7d83a9729a82e6a4ae53dc6ed6cfc17dae367b975d7fdd068" - }, - { - "message": "0x31a31029d10c07bc5f59c0d2fe1518ea5ffa7ad757b16099ff4e5b4bcc04ee0a62670d3bcf1c825c2e3b5b615cd5854af289b18d9c8b041a938b5caa470e32fd", - "privateKey": "0x90ffdeaaf96092d04bf141fc6d50e05909017eee71ad984c6eccdb5b5e6766b7", - "publicKeyX": "0x9197f038213d4766efac6638ef3bb4b30f37882ef09d068ccd5a496dad30c5f3", - "publicKeyY": "0x1f6d30912ec61f279bb890906de11e1085bdfcf64c5bf5a857e48f78a96a486f", - "r": "0xd1296024c9c98ed0011d3549db27f68f6c668c6b83252c0d6e2d514300d1d5c5", - "s": "0x1a587fb59c364037236920904d8f6490f26e9eee3c0b16725d5d0d6f3a66a342" - }, - { - "message": "0xcc50f956e3cb266ce5caa48de0d3e8c7507679e432e1d18b55b67ebeb582dddbc2e313c7127e56f8014911a9e132f212d86f5a43e40f23a7a1ba0dc6f466972e", - "privateKey": "0x65ba469a66b840a46eab6ae33a6355f360217ee6388a8f2be898b3b13403c6a6", - "publicKeyX": "0x5500eb7d078b84f62eaaf793680240f7d7c8f7bee2a2698a508f873f4dec9c11", - "publicKeyY": "0xbab65bf3b6b226a02cdbf327dc4b452a1dfcdf4d16d79b0562087d855685aa68", - "r": "0x66f4da7d354287e2c0b2829dfa32baf3457fa22bb70091d3d161ac00d15c677a", - "s": "0x784c1686603308fc3a8e44d13d16666f766f67334fe387620f644f320f5833bd" - }, - { - "message": "0x959a7b25474b9e8dc3c39a71ec5860ad29aad9e3e66cc846d67bee059abc3d8ad964161426291c275972fad02a636aa7971146378de827535646520afffb1a16", - "privateKey": "0x0bb1b4602df53001801a09afdff75d23166931e3b6dbe0224b02787ea81174ca", - "publicKeyX": "0xdee7aceb255f1a59645d22dc62ac120733cfb8cadda021604db04c2eddd34245", - "publicKeyY": "0x315f885c20e0369132b82a54c6c57dd156d18b05dfd350355008953f3023aea9", - "r": "0x4c1091af7b9eeb16dc85ed33dbc97196d9cd3d7ac1d926e92ec503165f69c97f", - "s": "0xd355763f0e2c6ee80c9742c0a606f45f4610ab61b5566168e059d470a9602c38" - }, - { - "message": "0xc0302aa5c44ab5e702fbc9c24a317744724c0ec0ab40cf47bf0215ffad90b2f8ba285a303571105989e3fbb398020378aee39d60f663fc0f2c87b63c449953fd", - "privateKey": "0x9bd89678b93347dbd2016501889e81aa4dc0a8edddec2f0c619f9e675f4b8ca4", - "publicKeyX": "0x05b9c39f63b1bf70a5690ededa3ad0d002364da909088820c6b23b3fa054bb88", - "publicKeyY": "0xea9638c127e1e9365c0229ccd036be77d14d825e69885ebfe91851991748918d", - "r": "0xc9cefea318d09382a7cd26af4da7854da0895018d80e70eb0e0a8985cd2d0068", - "s": "0xcb3106d9345a1f36c9578235bcc65f035609049f2d92fe0de87d0fb4c2692dd4" - }, - { - "message": "0xee67c073c32b8909fea183362051820953331684cc6051471dd51f590a82480461deed4e7580587d97db9f5f5aac89df4a9b39a5438cd919b7b96fba7593968a", - "privateKey": "0x9f4b7c6efc8a62cae4c9028a92bb40fc1b06f0b3347766f87fc47cd176a827e4", - "publicKeyX": "0xe4771b6b3309f291622fb9dd0f076de9f7d83128313041b47c8456c2b73d1559", - "publicKeyY": "0xd53db97cd8af1ec59869029230403b4ed96ce36a82b6ee2553776dbb85c046f8", - "r": "0x9ec27b1af10a587beebb668ede3bc05d8f9a2a4f48949e1b0bbbb96244dbaa63", - "s": "0x20c4beb3b2fe8b4d26eb5f7cacd8703b9ef6a79365f6d85fcaf81fb0f6f21e14" - }, - { - "message": "0x97253d6f28557b4c1454a2cc4ad91e80bd96c5c696b05dd5326714ed369c8f2e276568e8ae4f63cda85bf20f74c59decef3934ba28062cdb97f6def002f55feb", - "privateKey": "0x72a8a4e4f476db9a13964f7034b964bbb52d8d1d55166a0c94b504ca5ee076cd", - "publicKeyX": "0xc791ce496ca0ee7362915315a640d07a55760c3044eef8a0025a66620698793d", - "publicKeyY": "0x1a4abb4260ab1baa7cc240e95ebb506fb57d128c4cf089d2f6c8a361bd5419fe", - "r": "0x32ec021a1fd60e9aafabc4367afc105249ef789ae82133a82a19aee7ec7d384e", - "s": "0x5b9acb6bcfdf457ffd06d06ce5170ede38f8a133075a0314e17ece58279c2584" - }, - { - "message": "0x87c432c2e5077118902907ac18c5d4648cf06803c89c3ec332ec518a01a4c5c918b5b6f3ade94ff3ac1ae9abd129994ecc2bc0647dcf3777cf5682949eb5cd13", - "privateKey": "0x838f38c064258fa7b6cb64c5dd1939104c5316f8e6bef8b3d9253ed01349984d", - "publicKeyX": "0x5d1a973b0ed60c271884196658e5d6ba6aee09ec0854e2705336ff56d298a434", - "publicKeyY": "0x5ddb2511afa851941f460757e6691034b68f9c6e8fe7440679ad857963c2a60a", - "r": "0xabc2f9ee94a2136addae98931e5aab20a06e17a9c81dd6e5dff7eadf25d6fe4d", - "s": "0x76ab4a1a05d5024a7568f2f2fcb394b1a145a8610d23ed3f34d2dfd7adbda49a" - }, - { - "message": "0x9e5e9612a939a87c19c8fe15e5ec8a218f4acc592bc1eb8fd4569498dc5477973d3227545fbc62ec5ef4eb3577a5f6c0b7ab63dc26c905c0c87c21b03d084ef5", - "privateKey": "0x52d547244d0c707b35b2fcb80814e82024fab656afd6f8c8bf843363bdbf3029", - "publicKeyX": "0xaa88cc2a5f66a454f8c0d7271b1f2579a16773fc54542f4c59ff0cb3aba0213c", - "publicKeyY": "0x8df5e9696813e23f861d10ad8e4fc132acab02e0a31bbffb695de98a4d86c132", - "r": "0x4e29b3f89b80b590e205d38eb4724db2db79e5f29c7cb745aabfeb5884913e0d", - "s": "0x41cc45b26b1143995ba043954b11417f29653e45f86feb61d91a9ddf62b5f403" - }, - { - "message": "0x672927413db7f847c49d9c0eb185162be2c376092202523a0a7286faa987aa2deafad3bd38be1e7647b836d07c18c4478364d2ff9f73ca89ccace5b2c99b8a23", - "privateKey": "0x5a7bf156ccfa99c138d2898719ed4e481ca95ca27e835ad5faa4160dd838a4ba", - "publicKeyX": "0xa2eb501e108dc413ea21e61405da9ec014371dc88cc19c80ba91d55b19b52ea3", - "publicKeyY": "0xf65feb28addbde31004c62ad717cc47cb0dcd8349f9e785f46f69506f1af2fb9", - "r": "0xa5dbe34ed8ac2cecfe74df4541949960f1b24db29f48903536ee5a0e351dc709", - "s": "0x79726bda19124cff2b032e942f0fada54df9a5e1f86131c3a1c1551d7684c9fc" - }, - { - "message": "0xb9a3d42cd4c7a44cc2dda51f0fe7ee6ddcdf4bbbd2ceaf11c6c9a1468562f486177fd44727dbd3f23bedc3777095418643eb2d245199b67d3cf16685095b21dd", - "privateKey": "0x47d544eea41d2d09fcc17f18de7e466ef62af8aa754c3928ba21d38f4cace4b3", - "publicKeyX": "0xe64b8dc3489f5a67b08dc7ddb5410c045df012a7c1c698e0153f44622dea449a", - "publicKeyY": "0x569d9fb8aa848f201b5257f380fda72e721239d22fdebbaa676adab067d4fc7a", - "r": "0x0b925863d8cc307f83a09f7c9cf0744a84aa4ad648c69c042c13b64b5a7acd0b", - "s": "0x3d65e68ec91daed6f894416e959b134352b144d588c09529b40506e007908b1e" - }, - { - "message": "0x7660ee3683f34612665c87aa095a93ada157b4f7d70b16178c4c25d38e758a4ef1d9d9ddea7bdfaccba7f686d37bfc4bfde0a6392777fbfb2886359a25cfc818", - "privateKey": "0xf6d77da3b471cdf595ec8b0a830abf6c607cfbe24f187a6e2eb04c011df45765", - "publicKeyX": "0xb829fdc7222a1e2cf238dfc479fc5924ef0938c921a483eb6dedaf648d8379e3", - "publicKeyY": "0x1f11f88223cae84fb74b1bd7217515733e894c58f48356f5d9886ae475534a26", - "r": "0x12c0fd9b479d914065f932a75e3eef4e1afacf778c820bf27a41eeade4adff8c", - "s": "0x10a53c322c92a32df2bdd04287fda86b9193581a82ffeb45e1e287bc265ff8d3" - }, - { - "message": "0x8f9d663162d55ab7e45b3a9c8cd54bc7ba9fdc6ca9938b473afb71fc013990957f9fbcc251bfc3a3dc8c1affb05bb3127197259a1dfda1edf70b074a7bf78c0e", - "privateKey": "0x98f6d7fd1dabb500e4a81fbd39825ae4c1617eaf0d11d19518ffae812fdaecfc", - "publicKeyX": "0xcb778a516abb450f42ec27f7840c4d8b9745e3b1117042d4bb38eb3e25f99d5d", - "publicKeyY": "0xe596788f4a0f1ba82132012a4987424c9f0ba48fa1144e7a6fa3f3d3960a9074", - "r": "0x4a79a9b9249dcb68a5e0b3e6562b064b6f87a5fe31f9c32d573dc3ae96410b70", - "s": "0xdd87c01fc913aa3f71914cc29df40e14455b9802912e8cd20a7b71d54c1f2379" - }, - { - "message": "0xc5b9dfc7c64e8f55027246c6b48e2c515390a5b600fe61f82176500135c4b91746c2f53af3c3e92ad4b0653b57f4e36003a8d69a765d8940f82f1db3dd613fbb", - "privateKey": "0x9adf364ef94c584604d7fef247568c1255c16e3122320af40b2a91aced96a687", - "publicKeyX": "0xa3895f2baa13db8a5cc918a5be05f8aae9dde30749c9daf087496fcbef54aafd", - "publicKeyY": "0x0bb3ede09cd8d745cf1f5d97b17b7ef5fde49570f47da199ff0af99002e3bd95", - "r": "0xcc74ad34466288c00b39f3c47623ab3c014a0da1245b8cafa1066f01d0c7d3aa", - "s": "0x3dbc6740c1b614a78eaf24f6b6037e10e519b483f98de754b1b55a6e6c3364bb" - }, - { - "message": "0xd70f01d1d9999901e9c57ae27188fbbcdfa120a18caa40f605983fb73371903d58a5812d5fcde8841cdda5d779f3a84234403aaf0c435f15e92a458d82f5bd84", - "privateKey": "0x4cfe8c3cd8e3c3dd441f94e18009533f08c0bab46d17a452ff6b43790d95b99b", - "publicKeyX": "0x0807adae8ab65f658fc0e2defed1a24d4f1022ed9c7e278141c6b8f1857198ed", - "publicKeyY": "0xee3300df3f9aac17e75e8b70f80a46a8b6166aecc85f509cf7ea93a81b50c2e7", - "r": "0x09fd9fb94166ffeb50fb56541980a67b4cd0854497393ad30277a3689a4071a5", - "s": "0x1d4edf0b0a8064fe3f3775047f71c9e1eb281a6549c3c4d855a95af132c34527" - }, - { - "message": "0x436e3532bc214df3cec93cff8c37f8580be160f24abd659f6bdf85fd496455d95dbf39a165eb37505d798e3fcfa1f811e77c702887a8ef6151439b15453e869a", - "privateKey": "0x1efede4140f1f429f73593937788de56e744073b0f877d5ab3c88ef9f6e64191", - "publicKeyX": "0x51d695528c04b14a22bf4f539f386070a99a0fcd13651f2eafc8a19339ae3654", - "publicKeyY": "0xe33603e25c6dca148f4712dfa03be15074513bfd1b4c19b569e063786143df38", - "r": "0xbadbd8ff0e2003a19ce3ac6f8da4b3250019b5a49ceee6f13e10433cf36607bb", - "s": "0xad2fe6ca0c02502b3a38999091e14f203471858553460a691bf73a5e66f3d3ef" - }, - { - "message": "0x2049b4e9b0b7472b39edd06e14aab2ad234349f6ad73d37e2efc3ad26eead28e7f205267012133a605bc63e24b7d7b645b024eb4b72dcd00615f7706eff6ec13", - "privateKey": "0x04bcef2a79e6546dbf54f93daf43ed250b900a4280d0e422dc08e6443d8d04f6", - "publicKeyX": "0x5f5db2fcbc1a8927167566146680f2bd753e468ff368226e365c0cfa05b43e54", - "publicKeyY": "0x12c9555f7230fc221539327c8d444a077552456e62a6935c53630cbb29873921", - "r": "0x465e600bdf1c0fbd0b1330feec591273589d4154691fd5693488d5d2d85a43b5", - "s": "0xc2cb30b18f24954060c3a3d11110e6c95e8d0d797854bf318762f474ca41e0b3" - }, - { - "message": "0x3cdeaa216dccec8181c07a2b9c828b3f184fb8868af04356c4c0de791e922929d930b6fed9bb4dd430ba768ab0b85be8889193b53388c360732012e10e1877d9", - "privateKey": "0xd724b6f976e0faeeedc767c0f854f115118770b5e7cb7259031d7a87dca60b14", - "publicKeyX": "0xc2512b047c4578bbddcbcf849800d5ea8176d5d696ddca4f87ca8edee5d24196", - "publicKeyY": "0x72eacda14b42ce369edfac74511f321af001b0083a8fc645d2cb03949a506b80", - "r": "0x1ece0c3dd8e3899bf4f3ec0e056eb11608f2b21f68747933adf7c09af8e1e265", - "s": "0x403dfb6515abc9853aa129a734d3efd89264e7bf9612eda083d8d7a315a3449e" - }, - { - "message": "0xbb443489142471e4952d5af24b76c7dd8da389293c2ef488d9ea8922d52caddb0825dab3c6e7766c98d2f1304b3373556bd28b2c00ad89d5c109607adc352772", - "privateKey": "0xa10913ed4c6d362efcdeba63f747128e8126f9167f3cc1cce723c70e6e66a799", - "publicKeyX": "0xc06e0a02014f79d2ed3c04761ac60a2f766cfba69742c7a0045229ab88420339", - "publicKeyY": "0xbadc48458da06e4816f8e592027b39e43a824471c31740093c14e2d2c2159d86", - "r": "0x87c523eb21c95d1d5eeb02c79dd9eeb823d28919bae8365b73f954275cf1b070", - "s": "0x1020f7d61ea4e10096da38edab2e3cf12e2af5a43ef431183ca8b57c84b11298" - }, - { - "message": "0xc79470aad9a65d57f6617040141ef4dfe51ec2e31091a4c5cc3035bb352c9d7887cadb7ea3b8fc3171eb2033c315f79166aba288db6f4e3399b579cb9cec4873", - "privateKey": "0xb9d22e83ae514f23e7128ea2738e27d84441e07c8237d61f7c52f0844af1474d", - "publicKeyX": "0xb587e485e36b5f55514b4355a7109f7bddfcc243fc04016e436e0b67ca59cd55", - "publicKeyY": "0x4ba9c42a1ef72017aa6b6e44e42460da5f369e8a416de7e2edeb44f01e0d06f3", - "r": "0xab9f79fd27f3b878e0c2e49cdfe07695184dae67de29f5c4d0fcc403a41841da", - "s": "0x7e183b015fc6eaf12c5ea56b8afae186fe6decd713c9591612573dfe204f5b2e" - }, - { - "message": "0x40c08101a056bb54c2a9220bd8826b371dd6ce01953c9194ee149a62e70edd914df6264d44108f995818169c57cf96d324a6059c5f026d6b95c0f11f15afe8e1", - "privateKey": "0x0efe34919adb2dec0d6bb400674a53206d409498365d7213b7a813aea93c82b7", - "publicKeyX": "0xdfb1e4952635f480bdac9fa3cc447c19da986794864dc2efdb16e5e8974c1d10", - "publicKeyY": "0x1d4ad26ab602bcfa16a42929dc8b7c8b866d77c5629dc95edfcdf1f9e7a55f4f", - "r": "0xe4ee8b6cf5fe4c34c2f6c09a8a8ebd859d86fb8c4fc7b0abc24cbecf0e7e900b", - "s": "0x87c9b55ea4d9b0ba47674aaa39f511464460ce9730585d517397d91ea371ad1f" - }, - { - "message": "0xb76c73fef307f2cda625ed5ee301957d7f7e395b0fff0f0316e4ebcf4dccbe61a8ffae916807364026dc89c3b0ccb6ba5d8325afe2c30f5fa1517dd6932187ac", - "privateKey": "0x04b6e9ab1d495a441c05a638c717eaf9edb133326ec0dfa6e63654d09913cb59", - "publicKeyX": "0x6b48bbac449551c8145ab3bd691ec017feb1967aa38fa6cadc2c591f2d75988a", - "publicKeyY": "0xcedeeb469c74e8b8961efc31d224d8cebe6afb742b7bb7e0d3886f9461e63095", - "r": "0x5333f2358cdcc38ebf37ff9d409c0b30ec33cb2916c041657cefa129de89f44d", - "s": "0xb872d4b4e4ceb3a767f44690e4cf98293f50f9ab195dc424ff014d16fdacdfc0" - }, - { - "message": "0x95c48e3f3e6b00eb88c65ad2c7687a2c7fe3f6b81d52b6458a426a9963bb24e7cafd112b7ca7342d0afae1ad1106d1e456b358af525d83e28e8b4b3227e2a39b", - "privateKey": "0x51df3ca11f2b858c450a9269d9e5e91e4221b1253a06a3c252c8b3ee364f4a20", - "publicKeyX": "0x9906ac8acfd8454af8296e733c8207507ece05823978888abcb6fe2f97c0b78e", - "publicKeyY": "0x7e1bf8ac80e92a961d9fa42f643046de3e5b5e30fc42d5f7ecc8050752753c81", - "r": "0x98675ff69c8fa590315471862e8ef92cab2b1e0bccb046ed4889d94eb201161a", - "s": "0x0250c38660376c8fab3c06d4d817b275ee516b9b9067683b69031ea1c6bff927" - }, - { - "message": "0x0633af7687f2b4a46fa25725ce6dcb8e72b8d702504f20499721c3456680a60403f96e4e8a3eee7c4f56bafe4e3a90ad14906cbcbff8b8fdae45c52c56254f0d", - "privateKey": "0xf59ccb86079ac799d90a37af59197b45e1008c39f53a6c594a0a4f968395673e", - "publicKeyX": "0x89964f6cdc562bcc577ed261ac2ecf0c17a7ab84b98876638dba7e8d11130e8e", - "publicKeyY": "0x9856d27a4aabca88c5a6dd780c84c4dca14cc62f46c577adcb0c798d553f4cdf", - "r": "0x00d2f3460a294a5ab77241d67b759f03d96532ba99afc416d812a03b6b69cfdb", - "s": "0x78a8f4ec94ff6f918bfa36b4346226c671492924964c269ac81805851ea7c41a" - }, - { - "message": "0xb5fec56816360d29f244f9eb60830932189ce476cce7a37f919936772d71b64c354267ba6ebe11307b836c4be0b7ea06002d82fd1199c15ca2b71478895793cc", - "privateKey": "0x74430f9a5fe3cd365bfcf0741325d14288196a6f352176e059cb19a0fe395082", - "publicKeyX": "0x65309581a64047d72103d78d96a2ccabfb832f3763b1d7e34e88c9a8b80b7a81", - "publicKeyY": "0xbc07fa5b60f58ee956cb11210c19c16e4de3aa7f274dca69c24a8df6f060de18", - "r": "0x462ce44fd3af252d39cdd77616cd82ef63f645becf2f2651d7f3afab86c41fb9", - "s": "0x832fedae72e9ea1545d64698445bd83a297b3297e5245fe99eba1c9e72c0b522" - }, - { - "message": "0xfb6cabb3ef2bc555e1877d3e4271c9ea6676a21ddbedb8e19f9503dbcb9d4a0588d7d8830e621ecab91f28239e34bbd0c0f9a9a8baaae03d343690921142ed04", - "privateKey": "0xa3b3716f5abceb2c0a20473bb3f2ffbad75edb89c604d8ca68b59487f46c7ac7", - "publicKeyX": "0x2f5aefb3c29db2c381745c5088dca1dc494162c7575edbaf07c1b82528672823", - "publicKeyY": "0xe619636863f3486d34733541450733829606dd84cec3385b47d8bfe655f6d39a", - "r": "0x361ec469e640290b3d867587a465ed955826ef3aa46a6a171a6a9d5681a7db2c", - "s": "0x32af7e72fb8a1a30feaf6b6e50802f14f606ad8aafd07346a33bdfd790479585" - }, - { - "message": "0x0c3810263290b571151f69115af63c3a3396a3cd95c0101b51e98921d719869b525a5d17ab8ad77f60e782d416834d92574cf0e8a0becf26d600f4eed94abc75", - "privateKey": "0x2ed3c95e657cea435777aa6f9e413938d7725161fd102f1113b45956e70d1df3", - "publicKeyX": "0x0f3c08b1bb79b9507dcd50f48f93f06e51212fb549ecd4d7b004053a15511091", - "publicKeyY": "0x623081ea26fad543873751f147e84cf535156efdf972699de0465eeb30db69f1", - "r": "0xb4070b007cd021cc61cc91c6c9a4cb59d8a2a6d9f2f5ff74d661d985f60af0cc", - "s": "0x82a2e41f84ca352ff39b223fc7615e04c68990b5e9524f6f9813f6ecc101e82a" - }, - { - "message": "0x19fa9f79cec471d6e77664e8496e1291e2ce955751a51bbf35b424d82ddf7aa6dfb4c7ff2173280c7a1f92c9a6645de9cf9806a25eac983e0ff567dd3bdd8536", - "privateKey": "0xc6695470d9d1e3e7e97e2e6253d8875c5d263cca22d106ab097e9c7a4320a8c7", - "publicKeyX": "0xd94b868e9cfef94e46daf1fd78dd5811f6723c70f932f3cbe82e258c07f0d1e4", - "publicKeyY": "0x9bc37091c57f9a7072d58191edbaf4ff44568de843bb158f7f11aa966f8e4612", - "r": "0x56d945d2ff488f6bdd0bb4a53cecb611c38a8d4d47b63b7afa02cec880f33ae3", - "s": "0xf4398d4054c9ca8a97f93dbcd68e0eddd7b9a23022b42fb3eba17af753bbaa78" - }, - { - "message": "0x2c074a429fd318dda6cace0cf4a9e14c0a07c1b8d346b3be1aec5c162956214a9cc51c44138b07b2931181eac0841ac18a7531c60548f731bb4fb106461193db", - "privateKey": "0xe58d06b750e3efa6116f7a69683880c557df1fa0efae6e9018f6c21e911c86b7", - "publicKeyX": "0xca8b11be65f6dac4c12f6addba82f216ef8a2fd26edd2946f6919d0356c1d9ce", - "publicKeyY": "0x8d6b6432112b1dcf682f754d0c3d48e5e0d991e18de99a8b3fb6f0aa7d51a9da", - "r": "0x36b0ba98d6e646c67e64c2a0e482042043975510baa3864fc8ee0102d7ab8e8e", - "s": "0xadbe22e7ff3ee3b17129c4fcfb3a33d46a771962e368b9803b13daa1faece7b0" + "message": "0xfe52b2ab39dfd3c4a7dfe5effb2854dfb24d8526f4757d3f4ec38c0b62bd9d67545a509514ac417475bfe0e91a4b507cafdd78628d0b6808a463766f1eb78e44", + "privateKey": "0x1e4ea7373e42eed690de4be8bb48a13bc01bd03a75ba0878186ab4a2a728adb7", + "publicKeyX": "0x5d429f0d12adc0c56b711f8e286e792b882239e42fe5b974181d3658f75fb4f3", + "publicKeyY": "0x3ca22980eeb399f0a329d78ed12a712f2a2a00f221708f9a130649a1f9858f09", + "r": "0xa26112ca9338df8ff6d0e998de7915cd84bd3fb97ef3e3b0de1834cbd8adfdde", + "s": "0x27e31e3d102711596b7b9a67e97305f3acc68c74574276cbdb77715baa65573f" + }, + { + "message": "0x5054b2f096bb49b5bac390317cbea58949d1191106c7ee95afe15e7ddf75e370b8dd6402136f8cf57354cb436daca8764f55a50e0395b749781d9b93f843a002", + "privateKey": "0xfa1c2932283157a402bc34b30cbadbf6fb7b79b75436db1c374ebc5cbebc3147", + "publicKeyX": "0x02dba20d3820b71eb43abd98ce850e683c89ae29632416458c894340038fe021", + "publicKeyY": "0x5096c7b6c0872df51c88dfbb0f38b8667c5e366b0b583ad396234f6a6982c4d0", + "r": "0x281bd54bbdabaad3c4b2b68851295f2dc977f3fd30ca829ef1ef3e4a1a54e56a", + "s": "0x9c5a17fcec8669c28e28035d895297597ebe65d03d9799ae542d7d66eedbe6e7" + }, + { + "message": "0xd6b861a37f578e7b09b9c977442820d266e165af194f9951c44cb7e987aeef6be6d1ae403d939bee7af1398f5dcd3815a5bbaa49449119d232e6ab937109f813", + "privateKey": "0x2f54506b998ae0e52eb268b970db8b2bdc428e2eb768b3f4ffc9e52841d1491e", + "publicKeyX": "0x4a112e2facdfc9a65b1597db891982c9aea3c4af8ce9d5df55d8bebed193f16a", + "publicKeyY": "0x1a34f2a99dc0c2229dd991c2bc87207149f5fa9ccfa4f8445a70441febf64467", + "r": "0xfa9bff7e59f8fcca764c7ed86d9bcb80b8840ec4400d20f9387f18a5d628a094", + "s": "0x97aacbd6635fe7e64f416cd7b9f43d989d159b460e7d9c3095827f29e3154dee" + }, + { + "message": "0xd024025928f7e220e2e1d6b2fdfad56b2723e0eba117123072b1fc109669f2095beebbb2c6865c1e3012eec263b72a432c2eea2fbf1931c4f2db3356cc41476d", + "privateKey": "0x76f3bab8464e4c9cfe87a86ece5e0b411e844cf58659ac651636a087c528ee23", + "publicKeyX": "0x399ce4c030e49bd8fa0b18b88b0816879be37199e1f50ec6bd73e4c0ef0e9343", + "publicKeyY": "0xa288024f3faa9b94c8f8d976b984ee16379cf088d475b9cfe5a1f4c19d07e01a", + "r": "0x748f2583358a2e35268594923d7b140bb640cf244a77aa5c3efa5036010df559", + "s": "0x979cb37664c1463fa03a5c54d91bc4fb3044a6c7f7260498d4b519897f375dd1" + }, + { + "message": "0x7a71953949ba14aa3665680ca65d562550c1256d145f89f68b52a785b2d0bb2d998d7dc3c8461cb40b4ed38b8d760ddbcc78c06b05d20d03a8c704e2c1ff0270", + "privateKey": "0x1f90e9dcd295fc109ef356ef18a31608311250dafde28c7848d17098df26cd14", + "publicKeyX": "0xd338f82376f01300d3752331bc0e8d13b7bd826daea96ea29652d6490ba263db", + "publicKeyY": "0xc28f05a61e1e3a5e116ed48d0e5a3a5b660ffab162f48356baeed71e67686666", + "r": "0x6868b21822dcc5028809272e8f2aceafd722cbc96899335f700621c6945da63d", + "s": "0x3b35bf34cf2289daac213b104c96475af0b4dcdf1fcec74427c02e98496c55d0" + }, + { + "message": "0x91ab27df1123fba2fe16138257f05f84962b7cf4b0425270b9cc6d34df69758fd292b7948fe62ae8749e54da29e801d550c727af5ff94774789e2d3aed79fe52", + "privateKey": "0xec4e6e0bbdbe36f3061bef6812684984658e765fb95d119048eaff488f832a93", + "publicKeyX": "0x94d8d157fede42032915f9d04941a6455d55d4babe6ef5129d09c4157a8b0c2f", + "publicKeyY": "0xcd6c4655eb76dfe748ad485500e43ae22203f6e37d90c93e226650e069406fa5", + "r": "0xabd4d6dba22cfe2b2520bed0d98d121be4423cb88a60bd039f19613cf5b003f8", + "s": "0xc03081c4932b0505b34ae7e257bd7c53a38e4a511fddf14ccbb98cc22042119b" + }, + { + "message": "0xa300c3bcf1bc934d96871ef28fe6623c459273a3b9621901d011139083e4354b2587df863797fcb9e21ee1c0791e41da8e1f6e8f3e00f16addb025bdab7417d7", + "privateKey": "0xad735cf44e7c06a8c439d2c46801cdb0051e6bb495fa366de546856e490882de", + "publicKeyX": "0x24febef55f052fc0e400897678a0a891901e3698eca75502b95e8b744428e3d6", + "publicKeyY": "0xffa7e1d4ff68709152c982aa34f2f62cc4e01082074d99e2218abedb21d4d098", + "r": "0xfc569a4bcf52829c5ea33232370881254cd24e599edff26c2781edc1c2917fa6", + "s": "0x555e11289903a4a19190662190241a5ca878952c66849a8b3ff8246ee9e6416a" + }, + { + "message": "0x7ab56041fd35cad9aaae0fd130535eaa6a74882796aef9d1345232279ea6bac886ea0857a6699dddc85151af812064e52f2da76b874083dd3bb09d44f9eb47ed", + "privateKey": "0xa40d8e0ccb6b1ffc71caaee8e43676e9face256c5e1794add742ede9109da7d9", + "publicKeyX": "0x968c30541677e7c15ba96a9f8c1e254735f8cf8ea6bfec371fb29fb7f29c27f4", + "publicKeyY": "0xb3e45d2b54e1ecf227fd4f2f7a84fdf295405d8f47882fa90cf444f6dda64027", + "r": "0x80abbc5b3a885eabfb673f1d5d018105ee5d5693c84def974099afb84f6d4abf", + "s": "0xe81594ab8e92f21b710e9e76a055b5cbd36f9c06f91b58217b1177503f78c95e" + }, + { + "message": "0x29b9a9aa9aac294b44a814d4bc149cad709da4aaf5a88898ce532863b71d23c28f83b725ff813cc47223f89096b764686056adfdff75f41c6bd29995fb7479d0", + "privateKey": "0x1d6908c325fe28869c58ce64bd3fe8eddf746f9b4a0c4b23d544cfcb72a61c4d", + "publicKeyX": "0x464b953403d97b23cc1507512c365caff719781850460e7c6e69a2045769edd3", + "publicKeyY": "0x34c5918f09387445af0c75f7871c81300b286b552d093a73fc5887617eb06348", + "r": "0x1f675443729b8c277d79626871c880a0f494dec3a2d94de44d16da9cd6c17ec8", + "s": "0x240e40acc71966ec216d56b699ed3c175801c83b9303e5807b8deb2089df2555" + }, + { + "message": "0x786a1d5d644784e29f404ea9a20a2bc71dd6e063c9c50e54a45001a6f348d020aa2a5baaf14a3c4c1da3be0ac2b2dd2a020fee772c89d36a8187221b98099958", + "privateKey": "0x6cc3e9f0961402f41396d1be9b4381c18aa8f03131ccc73e48e32441c8c4020a", + "publicKeyX": "0xa82d1cb026f45f8ba3213259b5885ab1564d7e708e4720126e1d04f230efc9dc", + "publicKeyY": "0x652debc599cb697f6fe35f9aae0547bd926647a11b4d0f546341cece2aeaf72e", + "r": "0x618859f6b20fcc1cb331d8ea9ddaeb2118ba69615494f7732b7467c91030baa5", + "s": "0xfaccdfacd2b0a1610b5e4b0341c72088d9b35601b6a7e319d4a28773ffd5e603" } ] \ No newline at end of file From 50de116c2796c304128bb1a9f46e3b806f7b05ad Mon Sep 17 00:00:00 2001 From: Vindaar Date: Mon, 23 Dec 2024 22:40:08 +0100 Subject: [PATCH 15/52] [ecdsa] move ECDSA implementation to ~signatures~ directory --- constantine/{ecdsa => signatures}/ecdsa.nim | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename constantine/{ecdsa => signatures}/ecdsa.nim (100%) diff --git a/constantine/ecdsa/ecdsa.nim b/constantine/signatures/ecdsa.nim similarity index 100% rename from constantine/ecdsa/ecdsa.nim rename to constantine/signatures/ecdsa.nim From 931044d836dae1bfc97b49f23d96ca382a172fc1 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 24 Dec 2024 11:45:20 +0100 Subject: [PATCH 16/52] [ecdsa] remove dependence on explicit SHA256 hash function --- constantine/signatures/ecdsa.nim | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/constantine/signatures/ecdsa.nim b/constantine/signatures/ecdsa.nim index 50adc0569..e204823cd 100644 --- a/constantine/signatures/ecdsa.nim +++ b/constantine/signatures/ecdsa.nim @@ -1,5 +1,5 @@ import - ../hashes/h_sha256, + ../hashes, ../named/algebras, ../math/io/[io_bigints, io_fields, io_ec], ../math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul], @@ -33,12 +33,6 @@ type const C* = Secp256k1 const G = Secp256k1.getGenerator("G1") -proc hashMessage(message: string): array[32, byte] = - # Hash a given message - var h {.noinit.}: sha256 - h.init() - h.update(message) - h.finish(result) proc toBytes(res: var array[32, byte], x: Fr[C] | Fp[C]) = discard res.marshal(x.toBig(), bigEndian) @@ -224,9 +218,11 @@ proc signMessage*(message: string, privateKey: Fr[C], ## a deterministic nonce (and thus deterministic signature) given ## the message and private key as base. # 1. hash the message in big endian order - let h = hashMessage(message) - var message_hash: Fr[C] - message_hash.fromDigest(h) + var dgst {.noinit.}: array[H.digestSize, byte] + H.hash(dgst, message) + var message_hash: Fr[Name] + # if `dgst` uses more bytes than + message_hash.fromDigest(dgst, truncateInput = true) # loop until we found a valid (non zero) signature @@ -273,9 +269,10 @@ proc verifySignature*( ): bool = ## Verify a given `signature` for a `message` using the given `publicKey`. # 1. Hash the message (same as in signing) - let h = hashMessage(message) - var e {.noinit.}: Fr[C] - e.fromDigest(h) + var dgst {.noinit.}: array[H.digestSize, byte] + H.hash(dgst, message) + var e {.noinit.}: Fr[Name] + e.fromDigest(dgst, truncateInput = true) # 2. Compute w = s⁻¹ var w = signature.s From fe8a8aa79f956a0ee902087fa9a634dab64f16d0 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 24 Dec 2024 11:46:38 +0100 Subject: [PATCH 17/52] [ecdsa] make DERSignature generic under curve by having static size The size is now a static argument of the type, depending on the curve. `DERSigSize` can be used to calculate the size required for the given curve. --- constantine/signatures/ecdsa.nim | 37 +++++++++++++++++++------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/constantine/signatures/ecdsa.nim b/constantine/signatures/ecdsa.nim index e204823cd..f35021016 100644 --- a/constantine/signatures/ecdsa.nim +++ b/constantine/signatures/ecdsa.nim @@ -22,24 +22,27 @@ type ## Helper type for ASN.1 DER signatures to avoid allocation. ## Has a `data` buffer of 72 bytes (maximum possible size for - ## a signature) and `len` of actually used data. + ## a signature for `secp256k1`) and `len` of actually used data. ## `data[0 ..< len]` is the actual signature. - DERSignature* = object - data*: array[72, byte] # Max size: 6 bytes overhead + 33 bytes each for r,s + DERSignature*[N: static int] = object + data*: array[N, byte] # Max size: 6 bytes overhead + 33 bytes each for r,s len*: int # Actual length used # For easier readibility, define the curve and generator # as globals in this file const C* = Secp256k1 const G = Secp256k1.getGenerator("G1") +template DERSigSize*(Name: static Algebra): int = + 6 + 2 * (Fr[Name].bits.ceilDiv_vartime(sizeof(pointer)) + 1) proc toBytes(res: var array[32, byte], x: Fr[C] | Fp[C]) = discard res.marshal(x.toBig(), bigEndian) -proc toDER*(derSig: var DERSignature, r, s: Fr[C]) = +proc toDER*[Name: static Algebra; N: static int](derSig: var DERSignature[N], r, s: Fr[Name]) = ## Converts signature (r,s) to DER format without allocation. - ## Max size is 72 bytes: 6 bytes overhead + up to 33 bytes each for r,s. + ## Max size is 72 bytes (for Secp256k1 or any curve with 32 byte scalars in `Fr`): + ## 6 bytes overhead + up to 32+1 bytes each for r,s. ## 6 byte 'overhead' for: ## - `0x30` byte SEQUENCE designator ## - total length of the array @@ -49,11 +52,14 @@ proc toDER*(derSig: var DERSignature, r, s: Fr[C]) = ## Implementation follows ideas of Bitcoin's secp256k1 implementation: ## https://github.com/bitcoin-core/secp256k1/blob/f79f46c70386c693ff4e7aef0b9e7923ba284e56/src/ecdsa_impl.h#L171-L193 - template toByteArray(x: Fr[C]): untyped = + const WordSize = sizeof(BaseType) + const N = Fr[Name].bits.ceilDiv_vartime(WordSize) # 32 for `secp256k1` + + template toByteArray(x: Fr[Name]): untyped = ## Convert to a 33 byte array. Leading zero byte required if ## first real byte (idx 1) highest bit set (> 0x80). - var a: array[33, byte] - discard toOpenArray[byte](a, 1, 32).marshal(x.toBig(), bigEndian) + var a: array[N+1, byte] + discard toOpenArray[byte](a, 1, N).marshal(x.toBig(), bigEndian) a # 1. Prepare the data & determine required sizes @@ -61,8 +67,8 @@ proc toDER*(derSig: var DERSignature, r, s: Fr[C]) = # Convert r,s to big-endian bytes var rBytes = r.toByteArray() var sBytes = s.toByteArray() - var rLen = 33 - var sLen = 33 + var rLen = N + 1 + var sLen = N + 1 # Skip leading zeros but ensure high bit constraint var rPos = 0 @@ -79,26 +85,27 @@ proc toDER*(derSig: var DERSignature, r, s: Fr[C]) = # 2. Write the actual data - + var pos = 0 template setInc(val: byte): untyped = # Set `val` at `pos` and increase `pos` derSig.data[pos] = val inc pos - # Write DER structure - var pos = 0 + # Write DER structure, global setInc 0x30 # sequence setInc (4 + rLen + sLen).byte # total length + + # `r` prefix setInc 0x02 # integer setInc rLen.byte # length of `r` - # Write `r` bytes in valid region derSig.data.rawCopy(pos, rBytes, rPos, rLen) inc pos, rLen + # `s` prefix setInc 0x02 # integer setInc sLen.byte # length of `s` - + # Write `s` bytes in valid region derSig.data.rawCopy(pos, sBytes, sPos, sLen) inc pos, sLen From bec1536da446c7164d44b2f11fff6a39df9f8038 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 24 Dec 2024 11:53:14 +0100 Subject: [PATCH 18/52] [ecdsa] turn more procs generic over curve and hash function --- constantine/signatures/ecdsa.nim | 38 ++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/constantine/signatures/ecdsa.nim b/constantine/signatures/ecdsa.nim index f35021016..6eb3249eb 100644 --- a/constantine/signatures/ecdsa.nim +++ b/constantine/signatures/ecdsa.nim @@ -35,8 +35,7 @@ const G = Secp256k1.getGenerator("G1") template DERSigSize*(Name: static Algebra): int = 6 + 2 * (Fr[Name].bits.ceilDiv_vartime(sizeof(pointer)) + 1) - -proc toBytes(res: var array[32, byte], x: Fr[C] | Fp[C]) = +proc toBytes[Name: static Algebra; N: static int](res: var array[N, byte], x: FF[Name]) = discard res.marshal(x.toBig(), bigEndian) proc toDER*[Name: static Algebra; N: static int](derSig: var DERSignature[N], r, s: Fr[Name]) = @@ -111,16 +110,16 @@ proc toDER*[Name: static Algebra; N: static int](derSig: var DERSignature[N], r, assert derSig.len == pos -func fromDigest(dst: var Fr[C], src: array[32, byte]): bool {.discardable.} = - ## Convert a SHA256 digest to an element in the scalar field `Fr[Secp256k1]`. +func fromDigest[Name: static Algebra; N: static int](dst: var Fr[Name], src: array[N, byte]): bool {.discardable.} = + ## Convert a hash function digest to an element in the scalar field `Fr[Name]`. ## The proc returns a boolean indicating whether the data in `src` is ## smaller than the field modulus. It is discardable, because in some ## use cases this is fine (e.g. constructing a field element from a hash), ## but invalid in the nonce generation following RFC6979. - var scalar {.noInit.}: BigInt[256] + var scalar {.noInit.}: matchingOrderBigInt(Name) scalar.unmarshal(src, bigEndian) # `true` if smaller than modulus - result = bool(scalar < Fr[C].getModulus()) + result = bool(scalar < Fr[Name].getModulus()) dst.fromBig(scalar) proc randomFieldElement[FF](): FF = @@ -153,30 +152,36 @@ template round(hmac, input, output: typed, args: varargs[untyped]): untyped = hmac.update(args) hmac.finish(output) -proc nonceRfc6979(msgHash, privateKey: Fr[C]): Fr[C] {.noinit.} = +proc nonceRfc6979[Name: static Algebra]( + msgHash, privateKey: Fr[Name], + H: type CryptoHash): Fr[Name] {.noinit.} = ## Generate deterministic nonce according to RFC 6979. ## ## Spec: ## https://datatracker.ietf.org/doc/html/rfc6979#section-3.2 + + const WordSize = sizeof(BaseType) + const N = Fr[Name].bits.ceilDiv_vartime(WordSize) + # Step a: `h1 = H(m)` hash message (already done, input is hash), convert to array of bytes - var msgHashBytes {.noinit.}: array[32, byte] + var msgHashBytes {.noinit.}: array[N, byte] msgHashBytes.toBytes(msgHash) # Piece of step d: Conversion of the private key to a byte array. # No need for `bits2octets`, because the private key is already a valid # scalar in the field `Fr[C]` and thus < p-1 (`bits2octets` converts # `r` bytes to a BigInt, reduces modulo prime order `p` and converts to # a byte array). - var privKeyBytes {.noinit.}: array[32, byte] + var privKeyBytes {.noinit.}: array[N, byte] privKeyBytes.toBytes(privateKey) # Initial values # Step b: `V = 0x01 0x01 0x01 ... 0x01` - var v: array[32, byte]; v.arrayWith(byte 0x01) + var v: array[N, byte]; v.arrayWith(byte 0x01) # Step c: `K = 0x00 0x00 0x00 ... 0x00` - var k: array[32, byte]; k.arrayWith(byte 0x00) + var k: array[N, byte]; k.arrayWith(byte 0x00) # Create HMAC contexts - var hmac {.noinit.}: HMAC[sha256] + var hmac {.noinit.}: HMAC[H] # Step d: `K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1))` hmac.round(k, k, v, [byte 0x00], privKeyBytes, msgHashBytes) @@ -196,7 +201,7 @@ proc nonceRfc6979(msgHash, privateKey: Fr[C]): Fr[C] {.noinit.} = hmac.round(k, v, v) # v becomes T # Step h.3: `k = bits2int(T)` - var candidate {.noinit.}: Fr[C] + var candidate {.noinit.}: Fr[Name] # `fromDigest` returns `false` if the array is larger than the field modulus, # important for uniform sampling in valid range `[1, q-1]`! let smaller = candidate.fromDigest(v) @@ -211,11 +216,10 @@ proc nonceRfc6979(msgHash, privateKey: Fr[C]): Fr[C] {.noinit.} = hmac.round(k, k, v, [byte 0x00]) hmac.round(k, v, v) -proc generateNonce(kind: NonceSampler, msgHash, privateKey: Fr[C]): Fr[C] {.noinit.} = +proc generateNonce[Name: static Algebra]( + kind: NonceSampler, msgHash, privateKey: Fr[Name], + H: type CryptoHash): Fr[Name] {.noinit.} = case kind - of nsRandom: randomFieldElement[Fr[C]]() - of nsRfc6979: nonceRfc6979(msgHash, privateKey) - proc signMessage*(message: string, privateKey: Fr[C], nonceSampler: NonceSampler = nsRandom): tuple[r, s: Fr[C]] {.noinit.} = ## Sign a given `message` using the `privateKey`. From 34442fa4dc2a790a727140acf075f720d6982af7 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 24 Dec 2024 11:54:42 +0100 Subject: [PATCH 19/52] [ecdsa] replace sign/verify API by one matching BLS signatures --- constantine/signatures/ecdsa.nim | 95 ++++++++++++++++++++++++-------- 1 file changed, 71 insertions(+), 24 deletions(-) diff --git a/constantine/signatures/ecdsa.nim b/constantine/signatures/ecdsa.nim index 6eb3249eb..cd399cc48 100644 --- a/constantine/signatures/ecdsa.nim +++ b/constantine/signatures/ecdsa.nim @@ -4,7 +4,7 @@ import ../math/io/[io_bigints, io_fields, io_ec], ../math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul], ../math/[arithmetic, ec_shortweierstrass], - ../platforms/abstractions, + ../platforms/[abstractions, views], ../serialization/codecs, # for fromHex and (in the future) base64 encoding ../mac/mac_hmac, # for deterministic nonce generation via RFC 6979 ../named/zoo_generators, # for generator @@ -220,9 +220,16 @@ proc generateNonce[Name: static Algebra]( kind: NonceSampler, msgHash, privateKey: Fr[Name], H: type CryptoHash): Fr[Name] {.noinit.} = case kind -proc signMessage*(message: string, privateKey: Fr[C], - nonceSampler: NonceSampler = nsRandom): tuple[r, s: Fr[C]] {.noinit.} = - ## Sign a given `message` using the `privateKey`. + of nsRandom: randomFieldElement[Fr[Name]]() + of nsRfc6979: nonceRfc6979(msgHash, privateKey, H) + +proc signImpl[Name: static Algebra; Sig]( + sig: var Sig, + secretKey: Fr[Name], + message: openArray[byte], + H: type CryptoHash, + nonceSampler: NonceSampler = nsRandom) = + ## Sign a given `message` using the `secretKey`. ## ## By default we use a purely random nonce (uniform random number), ## but passing `nonceSampler = nsRfc6979` uses RFC 6979 to compute @@ -235,19 +242,21 @@ proc signMessage*(message: string, privateKey: Fr[C], # if `dgst` uses more bytes than message_hash.fromDigest(dgst, truncateInput = true) - # loop until we found a valid (non zero) signature + # Generator of the curve + const G = Name.getGenerator($G1) + # loop until we found a valid (non zero) signature while true: # Generate random nonce - var k = generateNonce(nonceSampler, message_hash, privateKey) + var k = generateNonce(nonceSampler, message_hash, secretKey, H) - var R {.noinit.}: EC_ShortW_Jac[Fp[C], G1] + var R {.noinit.}: EC_ShortW_Jac[Fp[Name], G1] # Calculate r (x-coordinate of kG) # `r = k·G (mod n)` R.scalarMul(k, G) # get x coordinate of the point `r` *in affine coordinates* let rx = R.getAffine().x - let r = Fr[C].fromBig(rx.toBig()) # convert to `Fr` + let r = Fr[Name].fromBig(rx.toBig()) # convert to `Fr` if bool(r.isZero()): continue # try again @@ -256,11 +265,11 @@ proc signMessage*(message: string, privateKey: Fr[C], # `s = (k⁻¹ · (h + r · p)) (mod n)` # with `h`: message hash as `Fr[C]` (if we didn't use SHA256 w/ 32 byte output # we'd need to truncate to N bits for N being bits in modulo `n`) - var s {.noinit.}: Fr[C] - s.prod(r, privateKey) # `r * privateKey` - s += message_hash # `message_hash + r * privateKey` + var s {.noinit.}: Fr[Name] + s.prod(r, secretKey) # `r * secretKey` + s += message_hash # `message_hash + r * secretKey` k.inv() # `k := k⁻¹` - s *= k # `k⁻¹ * (message_hash + r * privateKey)` + s *= k # `k⁻¹ * (message_hash + r * secretKey)` # get inversion of `s` for 'lower-s normalization' var sneg = s # inversion of `s` sneg.neg() # q - s @@ -271,12 +280,37 @@ proc signMessage*(message: string, privateKey: Fr[C], if bool(s.isZero()): continue # try again - return (r: r, s: s) - -proc verifySignature*( - message: string, - signature: tuple[r, s: Fr[C]], - publicKey: EC_ShortW_Aff[Fp[C], G1] + # Set output and return + sig.r = r + sig.s = s + return + +proc coreSign*[Sig, SecKey]( + signature: var Sig, + secretKey: SecKey, + message: openArray[byte], + H: type CryptoHash, + nonceSampler: NonceSampler = nsRandom) {.genCharAPI.} = + ## Computes a signature for the message from the specified secret key. + ## + ## Output: + ## - `signature` is overwritten with `message` signed with `secretKey` + ## + ## Inputs: + ## - `Hash` a cryptographic hash function. + ## - `Hash` MAY be `sha256` + ## - `Hash` MAY be `keccak` + ## - Otherwise, H MUST be a hash function that has been proved + ## indifferentiable from a random oracle [MRH04] under a reasonable + ## cryptographic assumption. + ## - `message` is the message to hash + signature.signImpl(secretKey, message, H, nonceSampler) + +proc verifyImpl[Name: static Algebra; Sig]( + publicKey: EC_ShortW_Aff[Fp[Name], G1], + signature: Sig, # tuple[r, s: Fr[Name]], + message: openArray[byte], + H: type CryptoHash, ): bool = ## Verify a given `signature` for a `message` using the given `publicKey`. # 1. Hash the message (same as in signing) @@ -291,27 +325,40 @@ proc verifySignature*( # 3. Compute u₁ = ew and u₂ = rw var - u1 {.noinit.}: Fr[C] - u2 {.noinit.}: Fr[C] + u1 {.noinit.}: Fr[Name] + u2 {.noinit.}: Fr[Name] u1.prod(e, w) u2.prod(signature.r, w) # 4. Compute u₁G + u₂Q var - point1 {.noinit.}: EC_ShortW_Jac[Fp[C], G1] - point2 {.noinit.}: EC_ShortW_Jac[Fp[C], G1] + point1 {.noinit.}: EC_ShortW_Jac[Fp[Name], G1] + point2 {.noinit.}: EC_ShortW_Jac[Fp[Name], G1] + # Generator of the curve + const G = publicKey.F.Name.getGenerator($publicKey.G) point1.scalarMul(u1, G) point2.scalarMul(u2, publicKey) - var R {.noinit.}: EC_ShortW_Jac[Fp[C], G1] + var R {.noinit.}: EC_ShortW_Jac[Fp[Name], G1] R.sum(point1, point2) # 5. Get x coordinate (in `Fp`) and convert to `Fr` (like in signing) let x = R.getAffine().x - let r_computed = Fr[C].fromBig(x.toBig()) + let r_computed = Fr[Name].fromBig(x.toBig()) # 6. Verify r_computed equals provided r result = bool(r_computed == signature.r) +func coreVerify*[Pubkey, Sig]( + pubkey: Pubkey, + message: openarray[byte], + signature: Sig, + H: type CryptoHash): bool {.genCharAPI.} = + ## Check that a signature is valid + ## for a message under the provided public key + ## This assumes that the PublicKey and Signatures + ## have been pre-checked for non-infinity and being in the correct subgroup + ## (likely on deserialization) + result = pubKey.verifyImpl(signature, message, H) proc generatePrivateKey*(): Fr[C] {.noinit.} = ## Generate a new private key using a cryptographic random number generator. result = randomFieldElement[Fr[C]]() From 06f7a5f1f533ed3d3151dcce0576918971fdf70c Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 24 Dec 2024 11:55:41 +0100 Subject: [PATCH 20/52] [ecdsa] remove global curve & generator constants --- constantine/signatures/ecdsa.nim | 4 ---- 1 file changed, 4 deletions(-) diff --git a/constantine/signatures/ecdsa.nim b/constantine/signatures/ecdsa.nim index cd399cc48..2570bed78 100644 --- a/constantine/signatures/ecdsa.nim +++ b/constantine/signatures/ecdsa.nim @@ -28,10 +28,6 @@ type data*: array[N, byte] # Max size: 6 bytes overhead + 33 bytes each for r,s len*: int # Actual length used -# For easier readibility, define the curve and generator -# as globals in this file -const C* = Secp256k1 -const G = Secp256k1.getGenerator("G1") template DERSigSize*(Name: static Algebra): int = 6 + 2 * (Fr[Name].bits.ceilDiv_vartime(sizeof(pointer)) + 1) From ad1640373614b32438323d3101dd38621427020b Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 24 Dec 2024 11:56:32 +0100 Subject: [PATCH 21/52] [ecdsa] correctly handle truncation of digests > Fr BigInts --- constantine/signatures/ecdsa.nim | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/constantine/signatures/ecdsa.nim b/constantine/signatures/ecdsa.nim index 2570bed78..6f4f7f398 100644 --- a/constantine/signatures/ecdsa.nim +++ b/constantine/signatures/ecdsa.nim @@ -106,13 +106,39 @@ proc toDER*[Name: static Algebra; N: static int](derSig: var DERSignature[N], r, assert derSig.len == pos -func fromDigest[Name: static Algebra; N: static int](dst: var Fr[Name], src: array[N, byte]): bool {.discardable.} = +func fromDigest[Name: static Algebra; N: static int]( + dst: var Fr[Name], src: array[N, byte], + truncateInput: static bool): bool {.discardable.} = ## Convert a hash function digest to an element in the scalar field `Fr[Name]`. ## The proc returns a boolean indicating whether the data in `src` is ## smaller than the field modulus. It is discardable, because in some ## use cases this is fine (e.g. constructing a field element from a hash), ## but invalid in the nonce generation following RFC6979. + ## + ## The `truncateInput` argument handles how `src` arrays larger than the BigInt + ## underlying `Fr[Name]` are handled. If it is `false` we will simply throw + ## an assertion error on `unmarshal` (used in RFC6979 nonce generation where + ## the array size cannot be larger than `Fr[Name]`). If it is `true`, we truncate + ## the digest array to the left most bits of up to the number of bits underlying + ## the BigInt of `Fr[Name]` following SEC1v2 [0] (page 45, 5.1-5.4). + ## + ## [0]: https://www.secg.org/sec1-v2.pdf var scalar {.noInit.}: matchingOrderBigInt(Name) + when truncateInput: # for signature & verification + # If the `src` array is larger than the BigInt underlying `Fr[Name]`, need + # to truncate the `src`. + const WordSize = sizeof(BaseType) + const FrBytes = Fr[Name].bits.ceildiv_vartime(WordSize) + # effectively: `scalar ~ array[0 ..< scalar.len]` + scalar.unmarshal(toOpenArray[byte](src, 0, FrBytes-1), bigEndian) + # Now still need to right shift potential individual bits. + # e.g. 381 bit BigInt fits into 384 bit (48 bytes), so need to + # right shift 3 bits to truncate correctly. + const toShift = FrBytes * WordSize - Fr[Name].bits + when toShift > 0: + scalar.shiftRight(toShift) + else: # for RFC 6979 nonce sampling. If larger than modulus, sample again + scalar.unmarshal(src, bigEndian) scalar.unmarshal(src, bigEndian) # `true` if smaller than modulus result = bool(scalar < Fr[Name].getModulus()) @@ -200,7 +226,7 @@ proc nonceRfc6979[Name: static Algebra]( var candidate {.noinit.}: Fr[Name] # `fromDigest` returns `false` if the array is larger than the field modulus, # important for uniform sampling in valid range `[1, q-1]`! - let smaller = candidate.fromDigest(v) + let smaller = candidate.fromDigest(v, truncateInput = false) # do not truncate! if not bool(candidate.isZero()) and smaller: return candidate From e9174e60a118520158373279174a235349c5df3a Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 24 Dec 2024 12:03:40 +0100 Subject: [PATCH 22/52] create file for common signature ops, `derivePubkey` for ECDSA & BLS Also cleans up the imports of the ECDSA file and adds the copyright header --- constantine/signatures/bls_signatures.nim | 19 +++--------- .../signatures/common_signature_ops.nim | 26 ++++++++++++++++ constantine/signatures/ecdsa.nim | 31 +++++++++++++------ 3 files changed, 51 insertions(+), 25 deletions(-) create mode 100644 constantine/signatures/common_signature_ops.nim diff --git a/constantine/signatures/bls_signatures.nim b/constantine/signatures/bls_signatures.nim index 153f54e1f..91fd28143 100644 --- a/constantine/signatures/bls_signatures.nim +++ b/constantine/signatures/bls_signatures.nim @@ -15,7 +15,10 @@ import constantine/named/algebras, constantine/hash_to_curve/[hash_to_curve, h2c_hash_to_field], constantine/hashes, - constantine/platforms/views + constantine/platforms/views, + constantine/signatures/common_signature_ops # for `derivePubkey` + +export common_signature_ops # ############################################################ # @@ -34,20 +37,6 @@ import {.push raises: [].} # No exceptions allowed in core cryptographic operations {.push checks: off.} # No defects due to array bound checking or signed integer overflow allowed -func derivePubkey*[Pubkey, SecKey](pubkey: var Pubkey, seckey: SecKey) = - ## Generates the public key associated with the input secret key. - ## - ## The secret key MUST be in range (0, curve order) - ## 0 is INVALID - const Group = Pubkey.G - type Field = Pubkey.F - const EC = Field.Name - - var pk {.noInit.}: EC_ShortW_Jac[Field, Group] - pk.setGenerator() - pk.scalarMul(seckey) - pubkey.affine(pk) - func coreSign*[Sig, SecKey]( signature: var Sig, secretKey: SecKey, diff --git a/constantine/signatures/common_signature_ops.nim b/constantine/signatures/common_signature_ops.nim new file mode 100644 index 000000000..6b5cb1f7c --- /dev/null +++ b/constantine/signatures/common_signature_ops.nim @@ -0,0 +1,26 @@ +# Constantine +# Copyright (c) 2018-2019 Status Research & Development GmbH +# Copyright (c) 2020-Present Mamy André-Ratsimbazafy +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +import + constantine/math/[ec_shortweierstrass], + constantine/named/zoo_generators, + constantine/named/algebras + +func derivePubkey*[Pubkey, SecKey](pubkey: var Pubkey, seckey: SecKey) = + ## Generates the public key associated with the input secret key. + ## + ## The secret key MUST be in range (0, curve order) + ## 0 is INVALID + const Group = Pubkey.G + type Field = Pubkey.F + const EC = Field.Name + + var pk {.noInit.}: EC_ShortW_Jac[Field, Group] + pk.setGenerator() + pk.scalarMul(seckey) + pubkey.affine(pk) diff --git a/constantine/signatures/ecdsa.nim b/constantine/signatures/ecdsa.nim index 6f4f7f398..906d0edf8 100644 --- a/constantine/signatures/ecdsa.nim +++ b/constantine/signatures/ecdsa.nim @@ -1,17 +1,28 @@ +# Constantine +# Copyright (c) 2018-2019 Status Research & Development GmbH +# Copyright (c) 2020-Present Mamy André-Ratsimbazafy +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + import - ../hashes, - ../named/algebras, - ../math/io/[io_bigints, io_fields, io_ec], - ../math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul], - ../math/[arithmetic, ec_shortweierstrass], - ../platforms/[abstractions, views], - ../serialization/codecs, # for fromHex and (in the future) base64 encoding - ../mac/mac_hmac, # for deterministic nonce generation via RFC 6979 - ../named/zoo_generators, # for generator - ../csprngs/sysrand + constantine/hashes, + constantine/named/algebras, + constantine/math/io/[io_bigints, io_fields, io_ec], + constantine/math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul], + constantine/math/[arithmetic, ec_shortweierstrass], + constantine/platforms/[abstractions, views], + constantine/serialization/codecs, # for fromHex and (in the future) base64 encoding + constantine/mac/mac_hmac, # for deterministic nonce generation via RFC 6979 + constantine/named/zoo_generators, # for generator + constantine/csprngs/sysrand, + constantine/signatures/common_signature_ops # for `derivePubkey` import std / macros # for `update` convenience helper +export common_signature_ops + type ## Decides the type of sampler we use for the nonce. By default ## a simple uniform random sampler. Alternatively a deterministic From 4d72a6bc268f09c140cd7b0e8c6ffb0946dd17fe Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 24 Dec 2024 12:12:39 +0100 Subject: [PATCH 23/52] create file specifically for ECDSA over secp256k1 --- constantine/ecdsa_secp256k1.nim | 180 +++++++++++++++++++++++++ constantine/signatures/ecdsa.nim | 120 ----------------- tests/ecdsa/generate_signatures.nim | 3 +- tests/ecdsa/t_ecdsa_verify_openssl.nim | 3 +- 4 files changed, 184 insertions(+), 122 deletions(-) create mode 100644 constantine/ecdsa_secp256k1.nim diff --git a/constantine/ecdsa_secp256k1.nim b/constantine/ecdsa_secp256k1.nim new file mode 100644 index 000000000..b3f3a4ccf --- /dev/null +++ b/constantine/ecdsa_secp256k1.nim @@ -0,0 +1,180 @@ +# Constantine +# Copyright (c) 2018-2019 Status Research & Development GmbH +# Copyright (c) 2020-Present Mamy André-Ratsimbazafy +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +import + constantine/signatures/ecdsa, + constantine/hashes/h_sha256, + constantine/named/algebras, + constantine/math/io/[io_bigints, io_fields, io_ec], + constantine/math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul], + constantine/math/[arithmetic, ec_shortweierstrass], + constantine/platforms/[abstractions, views], + constantine/serialization/codecs, # for fromHex and (in the future) base64 encoding + constantine/named/zoo_generators, # for generator + constantine/csprngs/sysrand + +export ecdsa ## XXX: shouldn't be needed once serialization is in submodules + +## XXX: Move this as API in `constantine/ecdsa_secp256k1.nim` +# For easier readibility, define the curve and generator +# as globals in this file +const C* = Secp256k1 + +## XXX: Still need to adjust secp256k1 specific API & tests +proc signMessage*(message: string, secretKey: Fr[C], + nonceSampler: NonceSampler = nsRandom): tuple[r, s: Fr[C]] = + ## WARNING: Convenience for development + result.coreSign(secretKey, message.toOpenArrayByte(0, message.len-1), sha256, nonceSampler) + +proc verifySignature*( + message: string, + signature: tuple[r, s: Fr[C]], + publicKey: EC_ShortW_Aff[Fp[C], G1] +): bool = + ## WARNING: Convenience for development + result = publicKey.coreVerify(message.toOpenArrayByte(0, message.len-1), signature, sha256) + +proc randomFieldElement[FF](): FF = + ## random element in ~Fp[T]/Fr[T]~ + let m = FF.getModulus() + var b: matchingBigInt(FF.Name) + + while b.isZero().bool or (b > m).bool: + ## XXX: raise / what else to do if `sysrand` call fails? + doAssert b.limbs.sysrand() + + result.fromBig(b) + +proc generatePrivateKey*(): Fr[C] {.noinit.} = + ## Generate a new private key using a cryptographic random number generator. + result = randomFieldElement[Fr[C]]() + +proc getPublicKey*(secKey: Fr[C]): EC_ShortW_Aff[Fp[C], G1] {.noinit.} = + result.derivePubkey(secKey) + + +## XXX: move to serialization submodule + +template toOA(x: openArray[byte]): untyped = toOpenArray[byte](x, 0, x.len - 1) + +proc toBytes[Name: static Algebra; N: static int](res: var array[N, byte], x: FF[Name]) = + discard res.marshal(x.toBig(), bigEndian) + +proc toPemPrivateKey(res: var array[48, byte], privateKey: Fr[C]) = + # Start with SEQUENCE + res.rawCopy(0, toOA [byte(0x30), byte(0x2E)], 0, 2) ## XXX: Calc size + + # Version (always 1) + res.rawCopy(2, toOA [byte(0x02), 1, 1], 0, 3) + + + # Private key as octet string + var privKeyBytes {.noinit.}: array[32, byte] ## XXX: array size + privKeyBytes.toBytes(privateKey) + + res.rawCopy(5, toOA [byte(0x04), byte(privKeyBytes.len)], 0, 2) + res.rawCopy(7, toOA privKeyBytes, 0, 32) ## XXX: array size + + ## XXX: OID for curve! + # Parameters (secp256k1 OID: 1.3.132.0.10) + const Secp256k1Oid = [byte(0xA0), byte(7), byte(6), byte(5), + byte(0x2B), byte(0x81), byte(0x04), byte(0x00), byte(0x0A)] + res.rawCopy(39, toOA Secp256k1Oid, 0, 9) + +proc toPemPrivateKey(privateKey: Fr[C]): array[48, byte] = + result.toPemPrivateKey(privateKey) + +proc toPemPublicKey(res: var array[88, byte], publicKey: EC_ShortW_Aff[Fp[C], G1]) = ## XXX: array size + # Start with SEQUENCE + res.rawCopy(0, toOA [byte(0x30), byte(0x58)], 0, 2) ## XXX: ADjust total length! + + ## XXX: OID for curve! + # Algorithm identifier + const algoId = [ + byte(0x30), byte(0x10), # SEQUENCE + byte(0x06), byte(0x07), # OID for EC + byte(0x2A), byte(0x86), byte(0x48), # 1.2.840.10045.2.1 + byte(0xCE), byte(0x3D), byte(0x02), byte(0x01), + byte(0x06), byte(0x05), # OID for secp256k1 + byte(0x2B), byte(0x81), byte(0x04), byte(0x00), byte(0x0A) # 1.3.132.0.10 + ] + + res.rawCopy(2, toOA algoId, 0, 18) + + # Public key as bit string + ## XXX: adjust length + const encoding = [byte(0x03), byte(0x42)] # 2+32+32 prefix & coordinates + const prefix = [ + byte(0x00), # DER BIT STRING: number of unused bits (always 0 for keys) + byte(0x04) # SEC1: uncompressed point format marker + ] + + template toByteArray(x: Fp[C] | Fr[C]): untyped = + var a: array[32, byte] ## XXX: array size + a.toBytes(x) + a + + ## XXX: copy indices & sizes! + res.rawCopy(20, toOA encoding, 0, 2) + res.rawCopy(22, toOA prefix, 0, 2) + res.rawCopy(24, toOA publicKey.x.toByteArray(), 0, 32) + res.rawCopy(56, toOA publicKey.y.toByteArray(), 0, 32) + +proc toPemPublicKey(publicKey: EC_ShortW_Aff[Fp[C], G1]): array[88, byte] = + result.toPemPublicKey(publicKey) + +## NOTE: +## The below procs / code is currently "unsuited" for Constantine in the sense that +## it currently still contains stdlib dependencies. Most of those are trivial, with the +## exception of a base64 encoder. +## Having a ANS1.DER encoder (and maybe decoder in the future) for SEC1 private and +## public keys would be nice to have in CTT, I think (at least for the curves that +## we support for the related operations; secp256k1 at the moment). + +## XXX: Might also need to replace this by header / tail approach to avoid +## stdlib `%`! +import std / [strutils, base64, math] +const PrivateKeyTmpl = """-----BEGIN EC PRIVATE KEY----- +$# +-----END EC PRIVATE KEY----- +""" +const PublicKeyTmpl = """-----BEGIN PUBLIC KEY----- +$# +-----END PUBLIC KEY----- +""" + +proc wrap(s: string, maxLineWidth = 64): string = + ## Wrap the given string at `maxLineWidth` over multiple lines + let lines = s.len.ceilDiv maxLineWidth + result = newStringOfCap(s.len + lines) + for i in 0 ..< lines: + let frm = i * maxLineWidth + let to = min(s.len, (i+1) * maxLineWidth) + result.add s[frm ..< to] + if i < lines-1: + result.add "\n" + +proc toPemFile*(publicKey: EC_ShortW_Aff[Fp[C], G1]): string = + ## Convert a given private key to data in PEM format following SEC1 + # 1. Convert public key to ASN.1 DER + let derB = publicKey.toPemPublicKey() + # 2. Encode bytes in base64 + let der64 = derB.encode().wrap() + # 3. Wrap in begin/end public key template + result = PublicKeyTmpl % [der64] + +proc toPemFile*(privateKey: Fr[C]): string = + ## XXX: For now using `std/base64` but will need to write base64 encoder + ## & add tests for CTT base64 decoder! + ## Convert a given private key to data in PEM format following SEC1 + # 1. Convert private key to ASN.1 DER encoding + let derB = toPemPrivateKey(privateKey) + # 2. Encode bytes in base64 + let der64 = derB.encode().wrap() + # 3. Wrap in begin/end private key template + result = PrivateKeyTmpl % [der64] diff --git a/constantine/signatures/ecdsa.nim b/constantine/signatures/ecdsa.nim index 906d0edf8..10b5c0100 100644 --- a/constantine/signatures/ecdsa.nim +++ b/constantine/signatures/ecdsa.nim @@ -392,123 +392,3 @@ func coreVerify*[Pubkey, Sig]( ## have been pre-checked for non-infinity and being in the correct subgroup ## (likely on deserialization) result = pubKey.verifyImpl(signature, message, H) -proc generatePrivateKey*(): Fr[C] {.noinit.} = - ## Generate a new private key using a cryptographic random number generator. - result = randomFieldElement[Fr[C]]() - -proc getPublicKey*(pk: Fr[C]): EC_ShortW_Aff[Fp[C], G1] {.noinit.} = - ## Derives the public key from a given private key, - ## `privateKey · G` in affine coordinates. - result = (pk * G).getAffine() - -template toOA(x: openArray[byte]): untyped = toOpenArray[byte](x, 0, x.len - 1) - -proc toPemPrivateKey(res: var array[48, byte], privateKey: Fr[C]) = - # Start with SEQUENCE - res.rawCopy(0, toOA [byte(0x30), byte(0x2E)], 0, 2) - - # Version (always 1) - res.rawCopy(2, toOA [byte(0x02), 1, 1], 0, 3) - - - # Private key as octet string - var privKeyBytes {.noinit.}: array[32, byte] - privKeyBytes.toBytes(privateKey) - - res.rawCopy(5, toOA [byte(0x04), byte(privKeyBytes.len)], 0, 2) - res.rawCopy(7, toOA privKeyBytes, 0, 32) - - # Parameters (secp256k1 OID: 1.3.132.0.10) - const Secp256k1Oid = [byte(0xA0), byte(7), byte(6), byte(5), - byte(0x2B), byte(0x81), byte(0x04), byte(0x00), byte(0x0A)] - res.rawCopy(39, toOA Secp256k1Oid, 0, 9) - -proc toPemPrivateKey(privateKey: Fr[C]): array[48, byte] = - result.toPemPrivateKey(privateKey) - -proc toPemPublicKey(res: var array[88, byte], publicKey: EC_ShortW_Aff[Fp[C], G1]) = - # Start with SEQUENCE - res.rawCopy(0, toOA [byte(0x30), byte(0x58)], 0, 2) - - # Algorithm identifier - const algoId = [ - byte(0x30), byte(0x10), # SEQUENCE - byte(0x06), byte(0x07), # OID for EC - byte(0x2A), byte(0x86), byte(0x48), # 1.2.840.10045.2.1 - byte(0xCE), byte(0x3D), byte(0x02), byte(0x01), - byte(0x06), byte(0x05), # OID for secp256k1 - byte(0x2B), byte(0x81), byte(0x04), byte(0x00), byte(0x0A) # 1.3.132.0.10 - ] - - res.rawCopy(2, toOA algoId, 0, 18) - - # Public key as bit string - const encoding = [byte(0x03), byte(0x42)] # 2+32+32 prefix & coordinates - const prefix = [ - byte(0x00), # DER BIT STRING: number of unused bits (always 0 for keys) - byte(0x04) # SEC1: uncompressed point format marker - ] - - template toByteArray(x: Fp[C] | Fr[C]): untyped = - var a: array[32, byte] - a.toBytes(x) - a - - res.rawCopy(20, toOA encoding, 0, 2) - res.rawCopy(22, toOA prefix, 0, 2) - res.rawCopy(24, toOA publicKey.x.toByteArray(), 0, 32) - res.rawCopy(56, toOA publicKey.y.toByteArray(), 0, 32) - -proc toPemPublicKey(publicKey: EC_ShortW_Aff[Fp[C], G1]): array[88, byte] = - result.toPemPublicKey(publicKey) - -## NOTE: -## The below procs / code is currently "unsuited" for Constantine in the sense that -## it currently still contains stdlib dependencies. Most of those are trivial, with the -## exception of a base64 encoder. -## Having a ANS1.DER encoder (and maybe decoder in the future) for SEC1 private and -## public keys would be nice to have in CTT, I think (at least for the curves that -## we support for the related operations; secp256k1 at the moment). - -## XXX: Might also need to replace this by header / tail approach to avoid -## stdlib `%`! -import std / [strutils, base64, math] -const PrivateKeyTmpl = """-----BEGIN EC PRIVATE KEY----- -$# ------END EC PRIVATE KEY----- -""" -const PublicKeyTmpl = """-----BEGIN PUBLIC KEY----- -$# ------END PUBLIC KEY----- -""" - -proc wrap(s: string, maxLineWidth = 64): string = - ## Wrap the given string at `maxLineWidth` over multiple lines - let lines = s.len.ceilDiv maxLineWidth - result = newStringOfCap(s.len + lines) - for i in 0 ..< lines: - let frm = i * maxLineWidth - let to = min(s.len, (i+1) * maxLineWidth) - result.add s[frm ..< to] - if i < lines-1: - result.add "\n" - -proc toPemFile*(publicKey: EC_ShortW_Aff[Fp[C], G1]): string = - ## Convert a given private key to data in PEM format following SEC1 - # 1. Convert public key to ASN.1 DER - let derB = publicKey.toPemPublicKey() - # 2. Encode bytes in base64 - let der64 = derB.encode().wrap() - # 3. Wrap in begin/end public key template - result = PublicKeyTmpl % [der64] - -proc toPemFile*(privateKey: Fr[C]): string = - ## XXX: For now using `std/base64` but will need to write base64 encoder - ## & add tests for CTT base64 decoder! - ## Convert a given private key to data in PEM format following SEC1 - # 1. Convert private key to ASN.1 DER encoding - let derB = toPemPrivateKey(privateKey) - # 2. Encode bytes in base64 - let der64 = derB.encode().wrap() - # 3. Wrap in begin/end private key template - result = PrivateKeyTmpl % [der64] diff --git a/tests/ecdsa/generate_signatures.nim b/tests/ecdsa/generate_signatures.nim index a47ec2c88..a691bc72f 100644 --- a/tests/ecdsa/generate_signatures.nim +++ b/tests/ecdsa/generate_signatures.nim @@ -12,7 +12,8 @@ with OpenSSL. import constantine/csprngs/sysrand, - constantine/ecdsa/ecdsa, + #constantine/signatures/ecdsa, + constantine/ecdsa_secp256k1, constantine/named/algebras, constantine/math/io/[io_bigints, io_fields, io_ec], constantine/serialization/codecs diff --git a/tests/ecdsa/t_ecdsa_verify_openssl.nim b/tests/ecdsa/t_ecdsa_verify_openssl.nim index 44ec67bbc..0ce7b4f8b 100644 --- a/tests/ecdsa/t_ecdsa_verify_openssl.nim +++ b/tests/ecdsa/t_ecdsa_verify_openssl.nim @@ -5,7 +5,8 @@ This test case verifies signatures generated by OpenSSL in the import constantine/csprngs/sysrand, - constantine/ecdsa/ecdsa, + #constantine/signatures/ecdsa, + constantine/ecdsa_secp256k1, constantine/named/algebras, constantine/math/elliptic/[ec_shortweierstrass_affine], constantine/math/io/[io_bigints, io_fields, io_ec], From a8ecd5949f0a9698b6e5c46b43f288d0d492c058 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Thu, 26 Dec 2024 20:06:28 +0100 Subject: [PATCH 24/52] [ecdsa] add `fromDER` to split DER encoded signature back into r, s arrays Written for OpenSSL interop for test suite (to be moved to serialization with the other related procs). --- constantine/signatures/ecdsa.nim | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/constantine/signatures/ecdsa.nim b/constantine/signatures/ecdsa.nim index 10b5c0100..bcdcbfb61 100644 --- a/constantine/signatures/ecdsa.nim +++ b/constantine/signatures/ecdsa.nim @@ -117,6 +117,58 @@ proc toDER*[Name: static Algebra; N: static int](derSig: var DERSignature[N], r, assert derSig.len == pos +proc fromRawDER*(r, s: var array[32, byte], sig: openArray[byte]): bool = + ## Extracts the `r` and `s` values from a given DER signature. + ## + ## Returns `true` if the input is a valid DER encoded signature + ## for `secp256k1` (or any curve with 32 byte scalars). + var pos = 0 + + template checkInc(val: untyped): untyped = + if pos > sig.high or sig[pos] != val: + # Invalid signature + return false + inc pos + template readInc(val: untyped): untyped = + if pos > sig.high: + return false + val = sig[pos] + inc pos + + checkInc(0x30) # SEQUENCE + var totalLen: byte; readInc(totalLen) + + template parseElement(el: var array[32, byte]): untyped = + var eLen: byte; readInc(eLen) # len of `r` + if pos + eLen.int > sig.len: # would need more data than available + return false + # read `r` into *last* `rLen` bytes + var eStart = el.len - eLen.int + if eStart < 0: # indicates prefix 0 due to first byte >= 0x80 (highest bit set) + doAssert eLen == 33 + inc pos # skip first byte + eStart = 0 # start from 0 in `el` + dec eLen # decrease eLen by 1 + el.rawCopy(eStart, sig, pos, eLen.int) + inc pos, eLen.int + + # `r` + checkInc(0x02) # INTEGER + parseElement(r) + + # `s` + checkInc(0x02) # INTEGER + parseElement(s) + + assert pos == totalLen.int, "Pos = " & $pos & ", totalLen = " & $totalLen + + result = true + +proc fromDER*(r, s: var array[32, byte], derSig: DERSignature) = + ## Splits a given `DERSignature` back into the `r` and `s` elements as + ## raw byte arrays. + fromRawDER(r, s, derSig.data) + func fromDigest[Name: static Algebra; N: static int]( dst: var Fr[Name], src: array[N, byte], truncateInput: static bool): bool {.discardable.} = From 828189cf7877b0eefd596be5abdb769c1104e5d9 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Thu, 26 Dec 2024 20:36:27 +0100 Subject: [PATCH 25/52] [tests] add OpenSSL wrapper intended for test cases --- tests/ecdsa/openssl_wrapper.nim | 137 ++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 tests/ecdsa/openssl_wrapper.nim diff --git a/tests/ecdsa/openssl_wrapper.nim b/tests/ecdsa/openssl_wrapper.nim new file mode 100644 index 000000000..61ae1df2d --- /dev/null +++ b/tests/ecdsa/openssl_wrapper.nim @@ -0,0 +1,137 @@ +# Deal with platform mess +# -------------------------------------------------------------------- +when defined(windows): + when sizeof(int) == 8: + const DLLSSLName* = "(libssl-1_1-x64|ssleay64|libssl64).dll" + else: + const DLLSSLName* = "(libssl-1_1|ssleay32|libssl32).dll" +else: + when defined(macosx) or defined(macos) or defined(ios): + const versions = "(.1.1|.38|.39|.41|.43|.44|.45|.46|.47|.48|.10|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|)" + else: + const versions = "(.1.1|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|.48|.47|.46|.45|.44|.43|.41|.39|.38|.10|)" + + when defined(macosx) or defined(macos) or defined(ios): + const DLLSSLName* = "libssl" & versions & ".dylib" + elif defined(genode): + const DLLSSLName* = "libssl.lib.so" + else: + const DLLSSLName* = "libssl.so" & versions + +# OpenSSL wrapper +# -------------------------------------------------------------------- + +# OpenSSL removed direct use of their SHA256 function. https://github.com/openssl/openssl/commit/4d49b68504cc494e552bce8e0b82ec8b501d5abe +# It isn't accessible anymore in Windows CI on Github Action. +# But the new API isn't expose on Linux :/ + +# TODO: fix Windows +when not defined(windows): + proc SHA256[T: byte|char]( + msg: openarray[T], + digest: ptr array[32, byte] = nil + ): ptr array[32, byte] {.noconv, dynlib: DLLSSLName, importc.} + + # proc EVP_Q_digest[T: byte|char]( + # ossl_libctx: pointer, + # algoName: cstring, + # propq: cstring, + # data: openArray[T], + # digest: var array[32, byte], + # size: ptr uint): int32 {.noconv, dynlib: DLLSSLName, importc.} + + proc SHA256_OpenSSL[T: byte|char]( + digest: var array[32, byte], + s: openArray[T]) = + discard SHA256(s, digest.addr) + # discard EVP_Q_digest(nil, "SHA256", nil, s, digest, nil) + +type + BIGNUM_Obj* = object + EC_KEY_Obj* = object + EC_GROUP_Obj* = object + + EVP_PKEY_Obj* = object + EVP_MD_CTX_Obj* = object + EVP_PKEY_CTX_Obj* = object + + EVP_PKEY* = ptr EVP_PKEY_Obj + EVP_MD_CTX* = ptr EVP_MD_CTX_Obj + EVP_PKEY_CTX* = ptr EVP_PKEY_CTX_Obj + +type + BIGNUM* = ptr BIGNUM_Obj + EC_KEY* = ptr EC_KEY_Obj + EC_GROUP* = ptr EC_GROUP_Obj + +## Push the pragmas to clean up the code a bit +{.push noconv, importc, dynlib: DLLSSLName.} +proc EVP_MD_CTX_new*(): EVP_MD_CTX +proc EVP_MD_CTX_free*(ctx: EVP_MD_CTX) + +proc EVP_sha256*(): pointer + +proc EVP_DigestSignInit*(ctx: EVP_MD_CTX, + pctx: ptr EVP_PKEY_CTX, + typ: pointer, + e: pointer, + pkey: EVP_PKEY): cint + +proc EVP_DigestSign*(ctx: EVP_MD_CTX, + sig: ptr byte, + siglen: ptr uint, + tbs: ptr byte, + tbslen: uint): cint + +proc BN_bin2bn*(s: ptr byte, len: cint, ret: BIGNUM): BIGNUM +proc BN_new*(): BIGNUM +proc BN_free*(bn: BIGNUM) + +proc EC_KEY_new_by_curve_name*(nid: cint): EC_KEY +proc EC_KEY_set_private_key*(key: EC_KEY, priv: BIGNUM): cint + +proc EC_KEY_get0_group*(key: EC_KEY): EC_GROUP +proc EC_KEY_generate_key*(key: EC_KEY): cint + +proc EVP_PKEY_new*(): EVP_PKEY + +## NOTE: This is _also_ now outdated and one should use the `EVP_PKEY_fromdata` function +## in theory: +## https://docs.openssl.org/master/man3/EVP_PKEY_fromdata/ +proc EVP_PKEY_set1_EC_KEY*(pkey: EVP_PKEY, key: EC_KEY): cint +{.pop.} + +proc initPrivateKeyOpenSSL*(pkey: var EVP_PKEY, rawKey: openArray[byte]) = + ## Initializes an OpenSSL private key of `EVP_PKEY` type from a given + ## raw private key in bytes. + let bn = BN_new() + discard BN_bin2bn(unsafeAddr rawKey[0], rawKey.len.cint, bn) + + let eckey = EC_KEY_new_by_curve_name(714) # NID_secp256k1 + discard EC_KEY_set_private_key(eckey, bn) + + pkey = EVP_PKEY_new() + discard EVP_PKEY_set1_EC_KEY(pkey, eckey) + + BN_free(bn) + +proc signMessageOpenSSL*(sig: var array[72, byte], msg: openArray[byte], key: EVP_PKEY) = + ## Sign a message with OpenSSL and return the resulting DER encoded signature in `sig`. + let ctx = EVP_MD_CTX_new() + var pctx: EVP_PKEY_CTX + + if EVP_DigestSignInit(ctx, addr pctx, EVP_sha256(), nil, key) <= 0: + raise newException(Exception, "Signing init failed") + + # Get required signature length + var sigLen: uint + if EVP_DigestSign(ctx, nil, addr sigLen, nil, 0.uint) <= 0: + raise newException(Exception, "Getting sig length failed") + + doAssert sigLen.int == 72 + + if EVP_DigestSign(ctx, addr sig[0], addr sigLen, + unsafeAddr msg[0], msg.len.uint) <= 0: + raise newException(Exception, "Signing failed") + + EVP_MD_CTX_free(ctx) From 722fa37bb757f1732c3faa54d603bacc4d50f84f Mon Sep 17 00:00:00 2001 From: Vindaar Date: Thu, 26 Dec 2024 20:36:45 +0100 Subject: [PATCH 26/52] [tests] first step towards OpenSSL tests Next is to merge this into the actual test case and avoid the JSON intermediate stage altogether. --- tests/ecdsa/generate_signatures.nim | 76 ++++++++--------------------- 1 file changed, 21 insertions(+), 55 deletions(-) diff --git a/tests/ecdsa/generate_signatures.nim b/tests/ecdsa/generate_signatures.nim index a691bc72f..95bc1a169 100644 --- a/tests/ecdsa/generate_signatures.nim +++ b/tests/ecdsa/generate_signatures.nim @@ -42,72 +42,38 @@ proc generateMessage(len: int): string = proc toHex(s: string): string = result = s.toOpenArrayByte(0, s.len-1).toHex() -proc toNormalizedHex(s: string): string = - ## Takes a string of raw bytes, removes additional empty bytes - ## (length of 33 bytes) or adds bytes (length < 32 bytes) and - ## then converts them to a hex string. - var s = s - if s.len == 33: - doAssert s[0] == '\0', "No, got: " & $s[0] - s = s[1 ..< s.len] - let toAdd = 32 - s.len - if toAdd < 0: - raiseAssert "Invalid input of length: " & $s.len & ": " & s - let prefix = repeat('\0', toAdd) - s = prefix & s - doAssert s.len == 32 - result = s.toOpenArrayByte(0, s.len-1).toHex() +import ./openssl_wrapper +import constantine/math/arithmetic/finite_fields -proc parseSignature(derSig: string): tuple[r, s: string] = - ## Parses a signature given in raw ASN.1 DER (SEC1) - ## raw bytes to the individual `r` and `s` elements. - ## The elements `r` and `s` are returned as hex strings. - ## - ## Note: the `r` or `s` values are 33 bytes long, if the leading - ## bit is `1` to clarify that the number is positive (a prefix - ## `0` byte is added). In our case we just parse 32 or 33 bytes, - ## because we don't care about a leading zero byte. - doAssert derSig[0] == '\48' # SEQUENCE - ## XXX: replace by maximum length 70! Can be anything larger than 2 really (1 for r and s) - doAssert derSig[1] in {'\67', '\68', '\69', '\70'} # 68-70 bytes long (depending on 0, 1, 2 zero prefixes) - doAssert derSig[2] == '\02' # INTEGER tag - let lenX = ord(derSig[3]) - doAssert lenX <= 33, "Found length: " & $lenX # Length of integer, 32 or 33 bytes - let toX = 4 + lenX - let r = derSig[4 ..< toX] - doAssert derSig[toX] == '\02' # INTEGER tag - let lenY = ord(derSig[toX + 1]) - doAssert lenY <= 33, "Found length: " & $lenX # Length of integer, 32 or 33 bytes - let toY = toX + 2 + lenY - let s = derSig[toX + 2 ..< toY] - doAssert toY == derSig.len - # Convert raw byte strings to hex strings. - result = (r: r.toNormalizedHex(), s: s.toNormalizedHex()) +proc toBytes[Name: static Algebra; N: static int](res: var array[N, byte], x: FF[Name]) = + discard res.marshal(x.toBig(), bigEndian) proc generateSignatures(num: int, msg = ""): seq[TestVector] = ## Generates `num` signatures. result = newSeq[TestVector](num) let dir = getTempDir() # temp filename for private key PEM file - let privKeyFile = dir / "private_key.pem" - let sigFile = dir / "message.sig" for i in 0 ..< num: let msg = if msg.len > 0: msg else: generateMessage(64) # 64 byte long messages let privKey = generatePrivateKey() let pubKey = getPublicKey(privKey) - # convert private key to a PEM file and write as temp - writeFile(privKeyFile, toPemFile(privKey)) - - discard toPemFile(pubKey) - - # NOTE: We treat the *hex string* as the message, not the raw bytes, - # including the `0x` prefix! - let cmd = &"echo -n '{msg}' | openssl dgst -sha256 -sign {privKeyFile} -out {sigFile}" - let (res, error) = execCmdEx(cmd) + # Get bytes of private key & initialize an OpenSSL key + var skBytes: array[32, byte] + skBytes.toBytes(privKey) + var osSecKey: EVP_PKEY + osSecKey.initPrivateKeyOpenSSL(skBytes) + + # Sign the message using OpenSSL + var osSig: array[72, byte] + osSig.signMessageOpenSSL(msg.toOpenArrayByte(0, msg.len-1), osSecKey) + # Destructure the DER encoded signature into two arrays + var rOSL: array[32, byte] + var sOSL: array[32, byte] + # And turn into hex strings + doAssert fromRawDER(rOSL, sOSL, osSig), "Deconstructing DER signature from OpenSSL failed: " & $osSig + let (r, s) = (rOSL.toHex(), sOSL.toHex()) - # extract raw signature - let (r, s) = sigFile.readFile.parseSignature let vec = TestVector(message: msg, privateKey: privKey.toHex(), publicKeyX: pubKey.x.toHex(), @@ -137,5 +103,5 @@ let vecs1 = generateSignatures(100) # 2. generate 10 signatures for the same message let vecs2 = generateSignatures(10, "Hello, Constantine!") # -#writeFile("testVectors/ecdsa_openssl_signatures_random.json", (% vecs1).pretty()) -#writeFile("testVectors/ecdsa_openssl_signatures_fixed_msg.json", (% vecs2).pretty()) +writeFile("testVectors/ecdsa_openssl_signatures_random.json", (% vecs1).pretty()) +writeFile("testVectors/ecdsa_openssl_signatures_fixed_msg.json", (% vecs2).pretty()) From 64130a15e02351cae9c4cb6fc1023bcad9e504e4 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Fri, 27 Dec 2024 18:49:54 +0100 Subject: [PATCH 27/52] [tests] fully avoid JSON intermediary files for ECDSA tests --- tests/ecdsa/generate_signatures.nim | 81 +++++++++--------- tests/ecdsa/t_ecdsa_verify_openssl.nim | 61 -------------- .../ecdsa_openssl_signatures_fixed_msg.json | 82 ------------------- .../ecdsa_openssl_signatures_random.json | 82 ------------------- 4 files changed, 37 insertions(+), 269 deletions(-) delete mode 100644 tests/ecdsa/t_ecdsa_verify_openssl.nim delete mode 100644 tests/ecdsa/testVectors/ecdsa_openssl_signatures_fixed_msg.json delete mode 100644 tests/ecdsa/testVectors/ecdsa_openssl_signatures_random.json diff --git a/tests/ecdsa/generate_signatures.nim b/tests/ecdsa/generate_signatures.nim index 95bc1a169..8249141b7 100644 --- a/tests/ecdsa/generate_signatures.nim +++ b/tests/ecdsa/generate_signatures.nim @@ -12,23 +12,17 @@ with OpenSSL. import constantine/csprngs/sysrand, - #constantine/signatures/ecdsa, constantine/ecdsa_secp256k1, constantine/named/algebras, constantine/math/io/[io_bigints, io_fields, io_ec], - constantine/serialization/codecs + constantine/serialization/codecs, + constantine/math/arithmetic/finite_fields, + constantine/platforms/abstractions -import - std / [os, osproc, strutils, strformat, json] +import ./openssl_wrapper -type - TestVector = object - message: string # A hex string, which is fed as-is into OpenSSL, not the raw bytes incl 0x prefix - privateKey: string - publicKeyX: string - publicKeyY: string - r: string - s: string +import + std / [os, strutils, strformat, unittest] proc generateMessage(len: int): string = ## Returns a randomly generated message of `len` bytes as a @@ -42,17 +36,14 @@ proc generateMessage(len: int): string = proc toHex(s: string): string = result = s.toOpenArrayByte(0, s.len-1).toHex() -import ./openssl_wrapper -import constantine/math/arithmetic/finite_fields - proc toBytes[Name: static Algebra; N: static int](res: var array[N, byte], x: FF[Name]) = discard res.marshal(x.toBig(), bigEndian) -proc generateSignatures(num: int, msg = ""): seq[TestVector] = - ## Generates `num` signatures. - result = newSeq[TestVector](num) - let dir = getTempDir() - # temp filename for private key PEM file +proc signAndVerify(num: int, msg = "", nonceSampler = nsRandom) = + ## Generates `num` signatures and verify them against OpenSSL. + ## + ## If `msg` is given, use a fixed message. Otherwise will generate a message with + ## a length up to 1024 bytes. for i in 0 ..< num: let msg = if msg.len > 0: msg else: generateMessage(64) # 64 byte long messages let privKey = generatePrivateKey() @@ -71,37 +62,39 @@ proc generateSignatures(num: int, msg = ""): seq[TestVector] = var rOSL: array[32, byte] var sOSL: array[32, byte] # And turn into hex strings - doAssert fromRawDER(rOSL, sOSL, osSig), "Deconstructing DER signature from OpenSSL failed: " & $osSig + check fromRawDER(rOSL, sOSL, osSig) let (r, s) = (rOSL.toHex(), sOSL.toHex()) - let vec = TestVector(message: msg, - privateKey: privKey.toHex(), - publicKeyX: pubKey.x.toHex(), - publicKeyY: pubKey.y.toHex(), - r: r, - s: s) - result[i] = vec - # sanity check here that our data is actually good. Sign # and verify with CTT & verify just parsed OpenSSL sig let (rCTT, sCTT) = msg.signMessage(privKey) - doAssert verifySignature(msg, (r: rCTT, s: sCTT), pubKey) - doAssert verifySignature(msg, (r: Fr[C].fromHex(r), s: Fr[C].fromHex(s)), pubKey) + check verifySignature(msg, (r: rCTT, s: sCTT), pubKey) + check verifySignature(msg, (r: Fr[C].fromHex(r), s: Fr[C].fromHex(s)), pubKey) + +proc signRfc6979(msg: string, num = 10) = + ## Signs the given message with a randomly generated private key `num` times + ## using deterministic nonce generation and verifies the signature comes out + ## identical each time. + + var derSig: DERSignature[DERSigSize(Secp256k1)] + + let privKey = generatePrivateKey() + let (r, s) = msg.signMessage(privKey, nonceSampler = nsRfc6979) + for i in 0 ..< num: + let (r2, s2) = msg.signMessage(privKey, nonceSampler = nsRfc6979) + check bool(r == r2) + check bool(s == s2) + - #let rOS = Fr[C].fromHex(r) - #let sOS = Fr[C].fromHex(s) - #echo "SEQ based: ", toDERSeq(rOS, sOS) - #var ds: DERSignature; toDER(ds, rOS, sOS) - #echo "ARR based: ", @(ds.data) - # - #doAssert toDERSeq(rOS, sOS) == @(ds.data)[0 ..< ds.len] +suite "ECDSA over secp256k1": + test "Verify OpenSSL generated signatures from a fixed message (different nonces)": + signAndVerify(100, "Hello, Constantine!") # fixed message + test "Verify OpenSSL generated signatures for different messages": + signAndVerify(100) # randomly generated message + test "Verify deterministic nonce generation via RFC6979 yields deterministic signatures": + signRfc6979("Hello, Constantine!") + signRfc6979("Foobar is 42") -# 1. generate 100 signatures with random messages, private keys and random nonces -let vecs1 = generateSignatures(100) -# 2. generate 10 signatures for the same message -let vecs2 = generateSignatures(10, "Hello, Constantine!") # -writeFile("testVectors/ecdsa_openssl_signatures_random.json", (% vecs1).pretty()) -writeFile("testVectors/ecdsa_openssl_signatures_fixed_msg.json", (% vecs2).pretty()) diff --git a/tests/ecdsa/t_ecdsa_verify_openssl.nim b/tests/ecdsa/t_ecdsa_verify_openssl.nim deleted file mode 100644 index 0ce7b4f8b..000000000 --- a/tests/ecdsa/t_ecdsa_verify_openssl.nim +++ /dev/null @@ -1,61 +0,0 @@ -##[ -This test case verifies signatures generated by OpenSSL in the -`generate_signatures.nim` script. -]## - -import - constantine/csprngs/sysrand, - #constantine/signatures/ecdsa, - constantine/ecdsa_secp256k1, - constantine/named/algebras, - constantine/math/elliptic/[ec_shortweierstrass_affine], - constantine/math/io/[io_bigints, io_fields, io_ec], - constantine/serialization/codecs - -import - std / [os, osproc, strformat, json, unittest] - -type - TestVector = object - message: string # A hex string, which is fed as-is into OpenSSL, not the raw bytes incl 0x prefix - privateKey: string - publicKeyX: string - publicKeyY: string - r: string - s: string - - TestVectorCTT = object - message: string - privateKey: Fr[C] - publicKey: EC_ShortW_Aff[Fp[C], G1] - r: Fr[C] - s: Fr[C] - -proc parseSignatureFile(f: string): seq[TestVector] = - result = f.readFile.parseJson.to(seq[TestVector]) - -proc parseTestVector(vec: TestVector): TestVectorCTT = - result = TestVectorCTT( - message: vec.message, - privateKey: Fr[C].fromHex(vec.privateKey), - publicKey: EC_ShortW_Aff[Fp[C], G1].fromHex(vec.publicKeyX, vec.publicKeyY), - r: Fr[C].fromHex(vec.r), - s: Fr[C].fromHex(vec.s)) - - -suite "ECDSA over secp256k1": - test "Verify OpenSSL generated signatures from a fixed message (different nonces)": - let vecs = parseSignatureFile("testVectors/ecdsa_openssl_signatures_fixed_msg.json") - - for vec in vecs: - let vctt = vec.parseTestVector() - # verify the signature - check verifySignature(vctt.message, (r: vctt.r, s: vctt.s), vctt.publicKey) - - test "Verify OpenSSL generated signatures for different messages": - let vecs = parseSignatureFile("testVectors/ecdsa_openssl_signatures_random.json") - - for vec in vecs: - let vctt = vec.parseTestVector() - # verify the signature - check verifySignature(vctt.message, (r: vctt.r, s: vctt.s), vctt.publicKey) diff --git a/tests/ecdsa/testVectors/ecdsa_openssl_signatures_fixed_msg.json b/tests/ecdsa/testVectors/ecdsa_openssl_signatures_fixed_msg.json deleted file mode 100644 index 88c12f758..000000000 --- a/tests/ecdsa/testVectors/ecdsa_openssl_signatures_fixed_msg.json +++ /dev/null @@ -1,82 +0,0 @@ -[ - { - "message": "Hello, Constantine!", - "privateKey": "0xd870b08067bff928072d5db736f4ee10dca7d6b0263285b3b2aa7ba870ac68b7", - "publicKeyX": "0xa1b52f56e9db283d258e0ce8e0a799bd2d8d124c7619ebbe13d3f3acf18d36d4", - "publicKeyY": "0x309994064311a7b62158409bd83126fb9622b0ea8460741d5f1265bbff4f1738", - "r": "0xb45f7409a3a8d71b6653a748845971e07f471f92850d8ccfbc4910e5edcba47f", - "s": "0x29d5e9a9c32e87de8429487f2c0cd1e1837d2c108c61d4e93a64fd7eedf1d0c3" - }, - { - "message": "Hello, Constantine!", - "privateKey": "0x70fa0674be3646b8131c77252db73fb1f31d70aebb9b5540b84b752e5da22c04", - "publicKeyX": "0x94fd65a886c4dc154fe66765144773c8fc1ef34d61df500217cb1789997cd449", - "publicKeyY": "0x2c75da7ccf7b5f58846aa81ddd27796b41e2ce2a43e1c705d0b09f28854dc501", - "r": "0x56eb4aca12897a5375bfa159e36454f29fc12da57720af8413cb545e68001a3a", - "s": "0x57abfcbee64b40fdf1fb242d4c565c93882630f9bc4ef984fdc6bf24fdaf794d" - }, - { - "message": "Hello, Constantine!", - "privateKey": "0x5fa75feb2ad1cb8eb0cb68420be1ed858f1bd5645f49be1df45fb84b8a8c84ae", - "publicKeyX": "0x327de4e42a8ef3e8cd85504cf420b619eddbaad6f3a4b9dae2b8cb980c1c703f", - "publicKeyY": "0xaefae9f952f6ec0f3035c6363c0049fa3232e1d72f9efde730c3f7a3488851a6", - "r": "0x6169580de3706434e170d1bcbc84ed91b987162d6b824093a6a6cb959dba1353", - "s": "0x1e73538ba74aedd070e2074edfa93b3e65d7183aed503c81e95ab500748204cb" - }, - { - "message": "Hello, Constantine!", - "privateKey": "0x57e40ab85efb7101d1005547cb3795925dfcad811cd2119875969de4c00ed916", - "publicKeyX": "0x6f83e1ec2ba63d09cf8d7fc1be938b99da7e044323db5756c6bd9618feddd89e", - "publicKeyY": "0xff96cb711565b3c930604a44c3b5b60d60831caf7de0329917ff75c8e7c28b16", - "r": "0x0b5144c1361adf5252aec22b14001a85efa77246e923bc8240a4f88f92ef9e1b", - "s": "0xce829a84b8e0de0490fcd5b486f2fd1413c57a7c3fde435738a913891309fd14" - }, - { - "message": "Hello, Constantine!", - "privateKey": "0xc00b5891b26db9c959873ccaa50c4ac230f39e019720f63bdeab1a4f46ce92ef", - "publicKeyX": "0x18ab3244bdad4a3df274a226881b5bd778d5cea61673baa615cd45a49b2d92d3", - "publicKeyY": "0x041fab03d07fd03b7721d00e329d3ed42190049bd7f682413cf0b052bf9ae698", - "r": "0x744343729ae1702d58ac95b6daa9a8492de85e6635cd57b57263666216c98217", - "s": "0xd7efc920f5e281171b533d97cb00385fadfb556c65acc83cada6ec5d68808f30" - }, - { - "message": "Hello, Constantine!", - "privateKey": "0xa80c027c34383a150bb9d3af3f0173bbd5f057abd6fe0800f038adc2ba9c4dce", - "publicKeyX": "0x18b645616ba143d50aa8170532abb823d12b0f584977d95e22e364f1366ac464", - "publicKeyY": "0x1f4e357fc9f67a87745c13736a08266c337bf421c66a8cdd4fab3a32cc9fa4c1", - "r": "0x33d77c4b859695704f4a968b3bb27a1539625cda84d6ece8f3ecfccf80e8f696", - "s": "0xf6078d0c6b935c49c173aae1a183143906cd39a298fd446e1db15f8296d4261d" - }, - { - "message": "Hello, Constantine!", - "privateKey": "0xeb71a9f41b2719356150f413f820a0599374abf334d89294af01ddc299c07bb7", - "publicKeyX": "0xc9bb54ee9254ac7026c805994f7133ca2a130f54742deb9a6cd8fb1ba97a5144", - "publicKeyY": "0x0deff37ec02919d55dab1eb6e8c7308d2c82db6a5b48d5c32c6731a95bde9bcb", - "r": "0x7f3b99bd471518ab5d6fdeef52cb4a259edbfa2c8c4a12013ff4a813aa3e3e3a", - "s": "0x04be53956348dcb5ab8e94467a57fd36eb2763e97ba0e3c8d55f4efdc90210f4" - }, - { - "message": "Hello, Constantine!", - "privateKey": "0x80afb2cd864ccef46f6a05b218adeb09d9872ea0b7aa13415c1e5130a450d263", - "publicKeyX": "0x441a439fccdab65a6011f67e7ba61b7412dbd21fa581f5cfaa195e85c1b8b33f", - "publicKeyY": "0x7a822352ede3f6f3e80c4352b3a1b6df603270f0a9f66c68a726f03bff71a688", - "r": "0x580c07808a05f9caf470a86d548636a20bbcd51b0f1cea4197593a8defb68c3e", - "s": "0x8aa1a672d5bff22f89a9e4aa115bd9f22d60a600ef77a7c0eafa1512904d9492" - }, - { - "message": "Hello, Constantine!", - "privateKey": "0xf44cf5a639dbbfad459f96f41efd278fb0cd0f385e9cf5744034ccb7dd442432", - "publicKeyX": "0x591a1857d64e9f170a521aaeebe402129d9ec73e3d3fa5c73cb23a7c56408e47", - "publicKeyY": "0x26901c0fe84af420ae4d220ba4d047983c510a08dbbe3cfd761a337c2023c9ee", - "r": "0xeddb895f06f84defef824b26a3a7ba8ec06622163a53b80e5e5177bae6e50575", - "s": "0x10781156896627231d5bc145bb302c1192710a35c850524c24644dea166e6619" - }, - { - "message": "Hello, Constantine!", - "privateKey": "0xc46b018e8c8f5cf6530ef737d9bf0be23f248d82f5d09f9274d052ca780d4e4e", - "publicKeyX": "0x6e1f4bf4aa07da28576d7fe69e2ee81db4e5117b124a0c0145fa6dbe2171a110", - "publicKeyY": "0xff71a9da404b5fa27f666aedbaf0d5c291b0be973838c27b539b4a217dab178d", - "r": "0xd1a44141336513a981593a3dd1158cee5311973e5015a17f30cf2c27544df050", - "s": "0x10f33ce3c8b463c7b0d84016172a24540929d9d95e9ee22e4cc2365e177be753" - } -] \ No newline at end of file diff --git a/tests/ecdsa/testVectors/ecdsa_openssl_signatures_random.json b/tests/ecdsa/testVectors/ecdsa_openssl_signatures_random.json deleted file mode 100644 index 7cada4753..000000000 --- a/tests/ecdsa/testVectors/ecdsa_openssl_signatures_random.json +++ /dev/null @@ -1,82 +0,0 @@ -[ - { - "message": "0xfe52b2ab39dfd3c4a7dfe5effb2854dfb24d8526f4757d3f4ec38c0b62bd9d67545a509514ac417475bfe0e91a4b507cafdd78628d0b6808a463766f1eb78e44", - "privateKey": "0x1e4ea7373e42eed690de4be8bb48a13bc01bd03a75ba0878186ab4a2a728adb7", - "publicKeyX": "0x5d429f0d12adc0c56b711f8e286e792b882239e42fe5b974181d3658f75fb4f3", - "publicKeyY": "0x3ca22980eeb399f0a329d78ed12a712f2a2a00f221708f9a130649a1f9858f09", - "r": "0xa26112ca9338df8ff6d0e998de7915cd84bd3fb97ef3e3b0de1834cbd8adfdde", - "s": "0x27e31e3d102711596b7b9a67e97305f3acc68c74574276cbdb77715baa65573f" - }, - { - "message": "0x5054b2f096bb49b5bac390317cbea58949d1191106c7ee95afe15e7ddf75e370b8dd6402136f8cf57354cb436daca8764f55a50e0395b749781d9b93f843a002", - "privateKey": "0xfa1c2932283157a402bc34b30cbadbf6fb7b79b75436db1c374ebc5cbebc3147", - "publicKeyX": "0x02dba20d3820b71eb43abd98ce850e683c89ae29632416458c894340038fe021", - "publicKeyY": "0x5096c7b6c0872df51c88dfbb0f38b8667c5e366b0b583ad396234f6a6982c4d0", - "r": "0x281bd54bbdabaad3c4b2b68851295f2dc977f3fd30ca829ef1ef3e4a1a54e56a", - "s": "0x9c5a17fcec8669c28e28035d895297597ebe65d03d9799ae542d7d66eedbe6e7" - }, - { - "message": "0xd6b861a37f578e7b09b9c977442820d266e165af194f9951c44cb7e987aeef6be6d1ae403d939bee7af1398f5dcd3815a5bbaa49449119d232e6ab937109f813", - "privateKey": "0x2f54506b998ae0e52eb268b970db8b2bdc428e2eb768b3f4ffc9e52841d1491e", - "publicKeyX": "0x4a112e2facdfc9a65b1597db891982c9aea3c4af8ce9d5df55d8bebed193f16a", - "publicKeyY": "0x1a34f2a99dc0c2229dd991c2bc87207149f5fa9ccfa4f8445a70441febf64467", - "r": "0xfa9bff7e59f8fcca764c7ed86d9bcb80b8840ec4400d20f9387f18a5d628a094", - "s": "0x97aacbd6635fe7e64f416cd7b9f43d989d159b460e7d9c3095827f29e3154dee" - }, - { - "message": "0xd024025928f7e220e2e1d6b2fdfad56b2723e0eba117123072b1fc109669f2095beebbb2c6865c1e3012eec263b72a432c2eea2fbf1931c4f2db3356cc41476d", - "privateKey": "0x76f3bab8464e4c9cfe87a86ece5e0b411e844cf58659ac651636a087c528ee23", - "publicKeyX": "0x399ce4c030e49bd8fa0b18b88b0816879be37199e1f50ec6bd73e4c0ef0e9343", - "publicKeyY": "0xa288024f3faa9b94c8f8d976b984ee16379cf088d475b9cfe5a1f4c19d07e01a", - "r": "0x748f2583358a2e35268594923d7b140bb640cf244a77aa5c3efa5036010df559", - "s": "0x979cb37664c1463fa03a5c54d91bc4fb3044a6c7f7260498d4b519897f375dd1" - }, - { - "message": "0x7a71953949ba14aa3665680ca65d562550c1256d145f89f68b52a785b2d0bb2d998d7dc3c8461cb40b4ed38b8d760ddbcc78c06b05d20d03a8c704e2c1ff0270", - "privateKey": "0x1f90e9dcd295fc109ef356ef18a31608311250dafde28c7848d17098df26cd14", - "publicKeyX": "0xd338f82376f01300d3752331bc0e8d13b7bd826daea96ea29652d6490ba263db", - "publicKeyY": "0xc28f05a61e1e3a5e116ed48d0e5a3a5b660ffab162f48356baeed71e67686666", - "r": "0x6868b21822dcc5028809272e8f2aceafd722cbc96899335f700621c6945da63d", - "s": "0x3b35bf34cf2289daac213b104c96475af0b4dcdf1fcec74427c02e98496c55d0" - }, - { - "message": "0x91ab27df1123fba2fe16138257f05f84962b7cf4b0425270b9cc6d34df69758fd292b7948fe62ae8749e54da29e801d550c727af5ff94774789e2d3aed79fe52", - "privateKey": "0xec4e6e0bbdbe36f3061bef6812684984658e765fb95d119048eaff488f832a93", - "publicKeyX": "0x94d8d157fede42032915f9d04941a6455d55d4babe6ef5129d09c4157a8b0c2f", - "publicKeyY": "0xcd6c4655eb76dfe748ad485500e43ae22203f6e37d90c93e226650e069406fa5", - "r": "0xabd4d6dba22cfe2b2520bed0d98d121be4423cb88a60bd039f19613cf5b003f8", - "s": "0xc03081c4932b0505b34ae7e257bd7c53a38e4a511fddf14ccbb98cc22042119b" - }, - { - "message": "0xa300c3bcf1bc934d96871ef28fe6623c459273a3b9621901d011139083e4354b2587df863797fcb9e21ee1c0791e41da8e1f6e8f3e00f16addb025bdab7417d7", - "privateKey": "0xad735cf44e7c06a8c439d2c46801cdb0051e6bb495fa366de546856e490882de", - "publicKeyX": "0x24febef55f052fc0e400897678a0a891901e3698eca75502b95e8b744428e3d6", - "publicKeyY": "0xffa7e1d4ff68709152c982aa34f2f62cc4e01082074d99e2218abedb21d4d098", - "r": "0xfc569a4bcf52829c5ea33232370881254cd24e599edff26c2781edc1c2917fa6", - "s": "0x555e11289903a4a19190662190241a5ca878952c66849a8b3ff8246ee9e6416a" - }, - { - "message": "0x7ab56041fd35cad9aaae0fd130535eaa6a74882796aef9d1345232279ea6bac886ea0857a6699dddc85151af812064e52f2da76b874083dd3bb09d44f9eb47ed", - "privateKey": "0xa40d8e0ccb6b1ffc71caaee8e43676e9face256c5e1794add742ede9109da7d9", - "publicKeyX": "0x968c30541677e7c15ba96a9f8c1e254735f8cf8ea6bfec371fb29fb7f29c27f4", - "publicKeyY": "0xb3e45d2b54e1ecf227fd4f2f7a84fdf295405d8f47882fa90cf444f6dda64027", - "r": "0x80abbc5b3a885eabfb673f1d5d018105ee5d5693c84def974099afb84f6d4abf", - "s": "0xe81594ab8e92f21b710e9e76a055b5cbd36f9c06f91b58217b1177503f78c95e" - }, - { - "message": "0x29b9a9aa9aac294b44a814d4bc149cad709da4aaf5a88898ce532863b71d23c28f83b725ff813cc47223f89096b764686056adfdff75f41c6bd29995fb7479d0", - "privateKey": "0x1d6908c325fe28869c58ce64bd3fe8eddf746f9b4a0c4b23d544cfcb72a61c4d", - "publicKeyX": "0x464b953403d97b23cc1507512c365caff719781850460e7c6e69a2045769edd3", - "publicKeyY": "0x34c5918f09387445af0c75f7871c81300b286b552d093a73fc5887617eb06348", - "r": "0x1f675443729b8c277d79626871c880a0f494dec3a2d94de44d16da9cd6c17ec8", - "s": "0x240e40acc71966ec216d56b699ed3c175801c83b9303e5807b8deb2089df2555" - }, - { - "message": "0x786a1d5d644784e29f404ea9a20a2bc71dd6e063c9c50e54a45001a6f348d020aa2a5baaf14a3c4c1da3be0ac2b2dd2a020fee772c89d36a8187221b98099958", - "privateKey": "0x6cc3e9f0961402f41396d1be9b4381c18aa8f03131ccc73e48e32441c8c4020a", - "publicKeyX": "0xa82d1cb026f45f8ba3213259b5885ab1564d7e708e4720126e1d04f230efc9dc", - "publicKeyY": "0x652debc599cb697f6fe35f9aae0547bd926647a11b4d0f546341cece2aeaf72e", - "r": "0x618859f6b20fcc1cb331d8ea9ddaeb2118ba69615494f7732b7467c91030baa5", - "s": "0xfaccdfacd2b0a1610b5e4b0341c72088d9b35601b6a7e319d4a28773ffd5e603" - } -] \ No newline at end of file From e9387e8f6e3a2bc5a2f87a8708290ccedf671556 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Fri, 27 Dec 2024 18:50:30 +0100 Subject: [PATCH 28/52] [tests] rename file back to test case name, add DERSigSize tests --- ...natures.nim => t_ecdsa_verify_openssl.nim} | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) rename tests/ecdsa/{generate_signatures.nim => t_ecdsa_verify_openssl.nim} (71%) diff --git a/tests/ecdsa/generate_signatures.nim b/tests/ecdsa/t_ecdsa_verify_openssl.nim similarity index 71% rename from tests/ecdsa/generate_signatures.nim rename to tests/ecdsa/t_ecdsa_verify_openssl.nim index 8249141b7..3fb594a97 100644 --- a/tests/ecdsa/generate_signatures.nim +++ b/tests/ecdsa/t_ecdsa_verify_openssl.nim @@ -85,6 +85,20 @@ proc signRfc6979(msg: string, num = 10) = check bool(r == r2) check bool(s == s2) +suite "General ECDSA related tests": + test "DERSigSize correctly computes maximum size of DER encoded signature": + # Check that `DerSigSize` correctly computes the maximum DER encoded signature + # based on the size of the scalar + check DerSigSize(Secp256k1) == 72 # 256 bit subgroup order -> 32 byte scalars + check DerSigSize(P256) == 72 # 256 bit subgroup order + check DerSigSize(Edwards25519) == 72 # 253 bits subgroup order, fits 256 bit BigInt + check DerSigSize(BLS12_381) == 72 # not commonly used, but larger modulo but *same* subgroup order + check DerSigSize(P224) == 64 # 224 bit subgroup order -> 28 byte scalars + when sizeof(BaseType) == 8: + check DerSigSize(BW6_761) == 104 # not commonly used, but larger modulo with *larger* subgroup order + # 377 bit subgroup order -> 384 BigInt -> 48 byte scalars + elif sizeof(BaseType) == 4: + check DerSigSize(BW6_761) == 96 # 377 bit subgroup -> 380 bit BigInt -> 44 byte scalars suite "ECDSA over secp256k1": test "Verify OpenSSL generated signatures from a fixed message (different nonces)": @@ -97,4 +111,17 @@ suite "ECDSA over secp256k1": signRfc6979("Hello, Constantine!") signRfc6979("Foobar is 42") + +# let rOS = Fr[C].fromHex(r) +# let sOS = Fr[C].fromHex(s) +# #echo "SEQ based: ", toDERSeq(rOS, sOS) +# echo "rOS = ", rOS.toHex(), " and ", r +# echo "sOS = ", sOS.toHex(), " and ", s +# const N = DERSigSize(C) +# echo "N = ", N +# var ds: DERSignature[N]; toDER(ds, rOS, sOS) +# echo "ARR based: ", @(ds.data) +# # +# #doAssert toDERSeq(rOS, sOS) == @(ds.data)[0 ..< ds.len] +# # From 0d24f6a5d1f1bda5c2c851dd31fd6593b216302b Mon Sep 17 00:00:00 2001 From: Vindaar Date: Fri, 27 Dec 2024 19:25:28 +0100 Subject: [PATCH 29/52] [tests] also test our DER encoder --- tests/ecdsa/t_ecdsa_verify_openssl.nim | 29 +++++++++++--------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/tests/ecdsa/t_ecdsa_verify_openssl.nim b/tests/ecdsa/t_ecdsa_verify_openssl.nim index 3fb594a97..89782b818 100644 --- a/tests/ecdsa/t_ecdsa_verify_openssl.nim +++ b/tests/ecdsa/t_ecdsa_verify_openssl.nim @@ -44,6 +44,9 @@ proc signAndVerify(num: int, msg = "", nonceSampler = nsRandom) = ## ## If `msg` is given, use a fixed message. Otherwise will generate a message with ## a length up to 1024 bytes. + ## + ## As a side effect it also verifies our `fromDER` parser and as an additional + ## sanity check our `toDER` converter. for i in 0 ..< num: let msg = if msg.len > 0: msg else: generateMessage(64) # 64 byte long messages let privKey = generatePrivateKey() @@ -69,13 +72,20 @@ proc signAndVerify(num: int, msg = "", nonceSampler = nsRandom) = # and verify with CTT & verify just parsed OpenSSL sig let (rCTT, sCTT) = msg.signMessage(privKey) check verifySignature(msg, (r: rCTT, s: sCTT), pubKey) - check verifySignature(msg, (r: Fr[C].fromHex(r), s: Fr[C].fromHex(s)), pubKey) + let (rOslFr, sOslFr) = (Fr[C].fromHex(r), Fr[C].fromHex(s)) + check verifySignature(msg, (r: rOslFr, s: sOslFr), pubKey) + + # Now verify that we can generate a DER signature again from the OpenSSL + # data and it is equivalent + var derSig: DERSignature[DERSigSize(Secp256k1)] + derSig.toDER(rOslFr, sOslFr) + check derSig.data == osSig + proc signRfc6979(msg: string, num = 10) = ## Signs the given message with a randomly generated private key `num` times ## using deterministic nonce generation and verifies the signature comes out ## identical each time. - var derSig: DERSignature[DERSigSize(Secp256k1)] let privKey = generatePrivateKey() @@ -110,18 +120,3 @@ suite "ECDSA over secp256k1": test "Verify deterministic nonce generation via RFC6979 yields deterministic signatures": signRfc6979("Hello, Constantine!") signRfc6979("Foobar is 42") - - -# let rOS = Fr[C].fromHex(r) -# let sOS = Fr[C].fromHex(s) -# #echo "SEQ based: ", toDERSeq(rOS, sOS) -# echo "rOS = ", rOS.toHex(), " and ", r -# echo "sOS = ", sOS.toHex(), " and ", s -# const N = DERSigSize(C) -# echo "N = ", N -# var ds: DERSignature[N]; toDER(ds, rOS, sOS) -# echo "ARR based: ", @(ds.data) -# # -# #doAssert toDERSeq(rOS, sOS) == @(ds.data)[0 ..< ds.len] -# -# From 5229551d69afb50632cd6a4031b1c850f489fae6 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 28 Dec 2024 12:22:02 +0100 Subject: [PATCH 30/52] [tests] extend OpenSSL wrapper for required functionality --- tests/ecdsa/openssl_wrapper.nim | 38 ++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/tests/ecdsa/openssl_wrapper.nim b/tests/ecdsa/openssl_wrapper.nim index 61ae1df2d..a5be82681 100644 --- a/tests/ecdsa/openssl_wrapper.nim +++ b/tests/ecdsa/openssl_wrapper.nim @@ -2,9 +2,9 @@ # -------------------------------------------------------------------- when defined(windows): when sizeof(int) == 8: - const DLLSSLName* = "(libssl-1_1-x64|ssleay64|libssl64).dll" + const DLL_SSL_Name* = "(libssl-1_1-x64|ssleay64|libssl64).dll" else: - const DLLSSLName* = "(libssl-1_1|ssleay32|libssl32).dll" + const DLL_SSL_Name* = "(libssl-1_1|ssleay32|libssl32).dll" else: when defined(macosx) or defined(macos) or defined(ios): const versions = "(.1.1|.38|.39|.41|.43|.44|.45|.46|.47|.48|.10|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|)" @@ -12,11 +12,11 @@ else: const versions = "(.1.1|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|.48|.47|.46|.45|.44|.43|.41|.39|.38|.10|)" when defined(macosx) or defined(macos) or defined(ios): - const DLLSSLName* = "libssl" & versions & ".dylib" + const DLL_SSL_Name* = "libssl" & versions & ".dylib" elif defined(genode): - const DLLSSLName* = "libssl.lib.so" + const DLL_SSL_Name* = "libssl.lib.so" else: - const DLLSSLName* = "libssl.so" & versions + const DLL_SSL_Name* = "libssl.so" & versions # OpenSSL wrapper # -------------------------------------------------------------------- @@ -30,7 +30,7 @@ when not defined(windows): proc SHA256[T: byte|char]( msg: openarray[T], digest: ptr array[32, byte] = nil - ): ptr array[32, byte] {.noconv, dynlib: DLLSSLName, importc.} + ): ptr array[32, byte] {.noconv, dynlib: DLL_SSL_Name, importc.} # proc EVP_Q_digest[T: byte|char]( # ossl_libctx: pointer, @@ -38,9 +38,9 @@ when not defined(windows): # propq: cstring, # data: openArray[T], # digest: var array[32, byte], - # size: ptr uint): int32 {.noconv, dynlib: DLLSSLName, importc.} + # size: ptr uint): int32 {.noconv, dynlib: DLL_SSL_Name, importc.} - proc SHA256_OpenSSL[T: byte|char]( + proc SHA256_OpenSSL*[T: byte|char]( digest: var array[32, byte], s: openArray[T]) = discard SHA256(s, digest.addr) @@ -59,13 +59,28 @@ type EVP_MD_CTX* = ptr EVP_MD_CTX_Obj EVP_PKEY_CTX* = ptr EVP_PKEY_CTX_Obj -type BIGNUM* = ptr BIGNUM_Obj EC_KEY* = ptr EC_KEY_Obj EC_GROUP* = ptr EC_GROUP_Obj + BIO_Obj* = object + BIO* = ptr BIO_Obj + + OSSL_ENCODER_CTX_Obj* = object + OSSL_ENCODER_CTX* = ptr OSSL_ENCODER_CTX_Obj + + OSSL_LIB_CTX_Obj* = object + OSSL_PROVIDER_Obj* = object + OSSL_LIB_CTX* = ptr OSSL_LIB_CTX_Obj + OSSL_PROVIDER* = ptr OSSL_PROVIDER_Obj + OSSL_PARAM_Obj* = object + OSSL_PARAM* = ptr OSSL_PARAM_Obj + + OSSL_PARAM_BLD_Obj* = object + OSSL_PARAM_BLD* = ptr OSSL_PARAM_BLD_Obj + ## Push the pragmas to clean up the code a bit -{.push noconv, importc, dynlib: DLLSSLName.} +{.push noconv, importc, dynlib: DLL_SSL_Name.} proc EVP_MD_CTX_new*(): EVP_MD_CTX proc EVP_MD_CTX_free*(ctx: EVP_MD_CTX) @@ -99,6 +114,9 @@ proc EVP_PKEY_new*(): EVP_PKEY ## in theory: ## https://docs.openssl.org/master/man3/EVP_PKEY_fromdata/ proc EVP_PKEY_set1_EC_KEY*(pkey: EVP_PKEY, key: EC_KEY): cint + +proc BIO_new_file*(filename: cstring, mode: cstring): BIO +proc BIO_free*(bio: BIO): cint {.pop.} proc initPrivateKeyOpenSSL*(pkey: var EVP_PKEY, rawKey: openArray[byte]) = From 1d05da4efdc3ac774ed2edcf5983aab070016aae Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 28 Dec 2024 12:22:28 +0100 Subject: [PATCH 31/52] [tests] move openssl wrapper to root of tests to share between tests --- tests/{ecdsa => }/openssl_wrapper.nim | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{ecdsa => }/openssl_wrapper.nim (100%) diff --git a/tests/ecdsa/openssl_wrapper.nim b/tests/openssl_wrapper.nim similarity index 100% rename from tests/ecdsa/openssl_wrapper.nim rename to tests/openssl_wrapper.nim From c0b380693109ffa058311caff0a043c0de18798f Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 28 Dec 2024 12:23:06 +0100 Subject: [PATCH 32/52] [tests] add test case to verify PEM file writer --- tests/ecdsa/t_ecdsa_verify_openssl.nim | 61 ++++++++++++++++++++------ 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/tests/ecdsa/t_ecdsa_verify_openssl.nim b/tests/ecdsa/t_ecdsa_verify_openssl.nim index 89782b818..8944ea99f 100644 --- a/tests/ecdsa/t_ecdsa_verify_openssl.nim +++ b/tests/ecdsa/t_ecdsa_verify_openssl.nim @@ -22,7 +22,7 @@ import import ./openssl_wrapper import - std / [os, strutils, strformat, unittest] + std / [os, osproc, strutils, strformat, unittest] proc generateMessage(len: int): string = ## Returns a randomly generated message of `len` bytes as a @@ -49,12 +49,12 @@ proc signAndVerify(num: int, msg = "", nonceSampler = nsRandom) = ## sanity check our `toDER` converter. for i in 0 ..< num: let msg = if msg.len > 0: msg else: generateMessage(64) # 64 byte long messages - let privKey = generatePrivateKey() - let pubKey = getPublicKey(privKey) + let secKey = generatePrivateKey() + let pubKey = getPublicKey(secKey) # Get bytes of private key & initialize an OpenSSL key var skBytes: array[32, byte] - skBytes.toBytes(privKey) + skBytes.toBytes(secKey) var osSecKey: EVP_PKEY osSecKey.initPrivateKeyOpenSSL(skBytes) @@ -67,20 +67,50 @@ proc signAndVerify(num: int, msg = "", nonceSampler = nsRandom) = # And turn into hex strings check fromRawDER(rOSL, sOSL, osSig) let (r, s) = (rOSL.toHex(), sOSL.toHex()) - - # sanity check here that our data is actually good. Sign - # and verify with CTT & verify just parsed OpenSSL sig - let (rCTT, sCTT) = msg.signMessage(privKey) - check verifySignature(msg, (r: rCTT, s: sCTT), pubKey) + # Convert to scalar and verify signature let (rOslFr, sOslFr) = (Fr[C].fromHex(r), Fr[C].fromHex(s)) check verifySignature(msg, (r: rOslFr, s: sOslFr), pubKey) + # Now also sign with CTT and verify + let (rCTT, sCTT) = msg.signMessage(secKey) + check verifySignature(msg, (r: rCTT, s: sCTT), pubKey) - # Now verify that we can generate a DER signature again from the OpenSSL - # data and it is equivalent + # Verify that we can generate a DER signature again from the OpenSSL + # data and it is equivalent to original var derSig: DERSignature[DERSigSize(Secp256k1)] derSig.toDER(rOslFr, sOslFr) check derSig.data == osSig +proc verifyPemWriter(num: int, msg = "") = + ## We verify our PEM writers in a bit of a roundabout way. + ## + ## TODO: Ideally we would simply write a given raw private and public key + ## using the C API of OpenSSL and compare writing the same key using + ## our serialization logic. + let dir = getTempDir() + # temp filename for private key PEM file + let pubKeyFile = dir / "public_key.pem" + let secKeyFile = dir / "private_key.pem" + let sigFile = dir / "msg.sig" + for i in 0 ..< num: + let msg = if msg.len > 0: msg else: generateMessage(64) # 64 byte long messages + let secKey = generatePrivateKey() + let pubKey = getPublicKey(secKey) + + writeFile(secKeyFile, toPemFile(secKey)) + writeFile(pubKeyFile, toPemFile(pubKey)) + + # Write a PEM file for public and private key using CTT and use it + # to sign and verify a message. + # NOTE: I tried using OpenSSL's C API, but couldn't get it to work + # 1. Sign using the private key and message + let sign = &"echo -n '{msg}' | openssl dgst -sha256 -sign {secKeyFile} -out {sigFile}" + let (resS, errS) = execCmdEx(sign) + check errS == 0 + + # 2. Verify using public key + let verify = &"echo -n '{msg}' | openssl dgst -sha256 -verify {pubKeyFile} -signature {sigFile}" + let (resV, errV) = execCmdEx(verify) + check errV == 0 proc signRfc6979(msg: string, num = 10) = ## Signs the given message with a randomly generated private key `num` times @@ -88,10 +118,10 @@ proc signRfc6979(msg: string, num = 10) = ## identical each time. var derSig: DERSignature[DERSigSize(Secp256k1)] - let privKey = generatePrivateKey() - let (r, s) = msg.signMessage(privKey, nonceSampler = nsRfc6979) + let secKey = generatePrivateKey() + let (r, s) = msg.signMessage(secKey, nonceSampler = nsRfc6979) for i in 0 ..< num: - let (r2, s2) = msg.signMessage(privKey, nonceSampler = nsRfc6979) + let (r2, s2) = msg.signMessage(secKey, nonceSampler = nsRfc6979) check bool(r == r2) check bool(s == s2) @@ -120,3 +150,6 @@ suite "ECDSA over secp256k1": test "Verify deterministic nonce generation via RFC6979 yields deterministic signatures": signRfc6979("Hello, Constantine!") signRfc6979("Foobar is 42") + + test "Verify PEM file serialization for public and private keys": + verifyPemWriter(100) From 87bc88744cfe49d2e968f618e5163a871fbe2932 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 28 Dec 2024 12:23:29 +0100 Subject: [PATCH 33/52] [ecdsa] clean up and fix PEM file writers The total length of the public key was wrong. Needs to be 86 bytes and not 88. I counted the SEQUENCE and total length bytes by accident. --- constantine/ecdsa_secp256k1.nim | 73 +++++++++++++++++---------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/constantine/ecdsa_secp256k1.nim b/constantine/ecdsa_secp256k1.nim index b3f3a4ccf..6c4f77750 100644 --- a/constantine/ecdsa_secp256k1.nim +++ b/constantine/ecdsa_secp256k1.nim @@ -60,40 +60,45 @@ proc getPublicKey*(secKey: Fr[C]): EC_ShortW_Aff[Fp[C], G1] {.noinit.} = ## XXX: move to serialization submodule -template toOA(x: openArray[byte]): untyped = toOpenArray[byte](x, 0, x.len - 1) - proc toBytes[Name: static Algebra; N: static int](res: var array[N, byte], x: FF[Name]) = discard res.marshal(x.toBig(), bigEndian) proc toPemPrivateKey(res: var array[48, byte], privateKey: Fr[C]) = + ## Encodes a private key as ASN.1 DER encoded private keys. + ## + ## See: https://www.secg.org/sec1-v2.pdf appendix C.4 + ## + ## TODO: Adjust to support different curves. # Start with SEQUENCE - res.rawCopy(0, toOA [byte(0x30), byte(0x2E)], 0, 2) ## XXX: Calc size + res.rawCopy(0, [byte(0x30), byte(0x2E)], 0, 2) # Version (always 1) - res.rawCopy(2, toOA [byte(0x02), 1, 1], 0, 3) - + res.rawCopy(2, [byte(0x02), 1, 1], 0, 3) # Private key as octet string - var privKeyBytes {.noinit.}: array[32, byte] ## XXX: array size - privKeyBytes.toBytes(privateKey) + var secKeyBytes {.noinit.}: array[32, byte] + secKeyBytes.toBytes(privateKey) - res.rawCopy(5, toOA [byte(0x04), byte(privKeyBytes.len)], 0, 2) - res.rawCopy(7, toOA privKeyBytes, 0, 32) ## XXX: array size + res.rawCopy(5, [byte(0x04), byte(secKeyBytes.len)], 0, 2) + res.rawCopy(7, secKeyBytes, 0, 32) ## XXX: array size - ## XXX: OID for curve! # Parameters (secp256k1 OID: 1.3.132.0.10) const Secp256k1Oid = [byte(0xA0), byte(7), byte(6), byte(5), byte(0x2B), byte(0x81), byte(0x04), byte(0x00), byte(0x0A)] - res.rawCopy(39, toOA Secp256k1Oid, 0, 9) + res.rawCopy(39, Secp256k1Oid, 0, 9) proc toPemPrivateKey(privateKey: Fr[C]): array[48, byte] = result.toPemPrivateKey(privateKey) -proc toPemPublicKey(res: var array[88, byte], publicKey: EC_ShortW_Aff[Fp[C], G1]) = ## XXX: array size +proc toPemPublicKey(res: var array[88, byte], publicKey: EC_ShortW_Aff[Fp[C], G1]) = + ## Encodes a public key as ASN.1 DER encoded public keys. + ## + ## See: https://www.secg.org/sec1-v2.pdf appendix C.3 + ## + ## TODO: Adjust to support different curves. # Start with SEQUENCE - res.rawCopy(0, toOA [byte(0x30), byte(0x58)], 0, 2) ## XXX: ADjust total length! + res.rawCopy(0, [byte(0x30), byte(0x56)], 0, 2) - ## XXX: OID for curve! # Algorithm identifier const algoId = [ byte(0x30), byte(0x10), # SEQUENCE @@ -104,26 +109,24 @@ proc toPemPublicKey(res: var array[88, byte], publicKey: EC_ShortW_Aff[Fp[C], G1 byte(0x2B), byte(0x81), byte(0x04), byte(0x00), byte(0x0A) # 1.3.132.0.10 ] - res.rawCopy(2, toOA algoId, 0, 18) + res.rawCopy(2, algoId, 0, algoId.len) # algoId.len == 18 # Public key as bit string - ## XXX: adjust length - const encoding = [byte(0x03), byte(0x42)] # 2+32+32 prefix & coordinates + const encoding = [byte(0x03), byte(0x42)] # [BIT-STRING, 2+32+32 prefix & coordinates] const prefix = [ byte(0x00), # DER BIT STRING: number of unused bits (always 0 for keys) byte(0x04) # SEC1: uncompressed point format marker ] template toByteArray(x: Fp[C] | Fr[C]): untyped = - var a: array[32, byte] ## XXX: array size + var a: array[32, byte] a.toBytes(x) a - ## XXX: copy indices & sizes! - res.rawCopy(20, toOA encoding, 0, 2) - res.rawCopy(22, toOA prefix, 0, 2) - res.rawCopy(24, toOA publicKey.x.toByteArray(), 0, 32) - res.rawCopy(56, toOA publicKey.y.toByteArray(), 0, 32) + res.rawCopy(20, encoding, 0, 2) + res.rawCopy(22, prefix, 0, 2) + res.rawCopy(24, publicKey.x.toByteArray(), 0, 32) + res.rawCopy(56, publicKey.y.toByteArray(), 0, 32) proc toPemPublicKey(publicKey: EC_ShortW_Aff[Fp[C], G1]): array[88, byte] = result.toPemPublicKey(publicKey) @@ -136,17 +139,7 @@ proc toPemPublicKey(publicKey: EC_ShortW_Aff[Fp[C], G1]): array[88, byte] = ## public keys would be nice to have in CTT, I think (at least for the curves that ## we support for the related operations; secp256k1 at the moment). -## XXX: Might also need to replace this by header / tail approach to avoid -## stdlib `%`! import std / [strutils, base64, math] -const PrivateKeyTmpl = """-----BEGIN EC PRIVATE KEY----- -$# ------END EC PRIVATE KEY----- -""" -const PublicKeyTmpl = """-----BEGIN PUBLIC KEY----- -$# ------END PUBLIC KEY----- -""" proc wrap(s: string, maxLineWidth = 64): string = ## Wrap the given string at `maxLineWidth` over multiple lines @@ -161,20 +154,30 @@ proc wrap(s: string, maxLineWidth = 64): string = proc toPemFile*(publicKey: EC_ShortW_Aff[Fp[C], G1]): string = ## Convert a given private key to data in PEM format following SEC1 + ## + ## RFC 7468 describes the textual encoding of these files: + ## https://www.rfc-editor.org/rfc/rfc7468#section-10 # 1. Convert public key to ASN.1 DER let derB = publicKey.toPemPublicKey() # 2. Encode bytes in base64 let der64 = derB.encode().wrap() # 3. Wrap in begin/end public key template - result = PublicKeyTmpl % [der64] + result = "-----BEGIN PUBLIC KEY-----\n" + result.add der64 & "\n" + result.add "-----END PUBLIC KEY-----\n" proc toPemFile*(privateKey: Fr[C]): string = ## XXX: For now using `std/base64` but will need to write base64 encoder ## & add tests for CTT base64 decoder! ## Convert a given private key to data in PEM format following SEC1 + ## + ## RFC 7468 describes the textual encoding of these files: + ## https://www.rfc-editor.org/rfc/rfc7468#section-13 # 1. Convert private key to ASN.1 DER encoding let derB = toPemPrivateKey(privateKey) # 2. Encode bytes in base64 let der64 = derB.encode().wrap() # 3. Wrap in begin/end private key template - result = PrivateKeyTmpl % [der64] + result = "-----BEGIN EC PRIVATE KEY-----\n" + result.add der64 & "\n" + result.add "-----END EC PRIVATE KEY-----\n" From 8000567bef447ce74e3b5715fdb19841f41191f1 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 28 Dec 2024 12:24:04 +0100 Subject: [PATCH 34/52] [tests] [bench] use shared OpenSSL wrapper where appropriate --- benchmarks/bench_h_sha256.nim | 35 ++----------------------- tests/t_hash_sha256_vs_openssl.nim | 42 +----------------------------- 2 files changed, 3 insertions(+), 74 deletions(-) diff --git a/benchmarks/bench_h_sha256.nim b/benchmarks/bench_h_sha256.nim index c912bf8c4..25c6100cf 100644 --- a/benchmarks/bench_h_sha256.nim +++ b/benchmarks/bench_h_sha256.nim @@ -5,40 +5,9 @@ import helpers/prng_unsafe, ./bench_blueprint -proc separator*() = separator(69) - -# Deal with platform mess -# -------------------------------------------------------------------- -when defined(windows): - when sizeof(int) == 8: - const DLLSSLName* = "(libssl-1_1-x64|ssleay64|libssl64).dll" - else: - const DLLSSLName* = "(libssl-1_1|ssleay32|libssl32).dll" -else: - when defined(macosx) or defined(macos) or defined(ios): - const versions = "(.1.1|.38|.39|.41|.43|.44|.45|.46|.47|.48|.10|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|)" - else: - const versions = "(.1.1|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|.48|.47|.46|.45|.44|.43|.41|.39|.38|.10|)" +import ../tests/openssl_wrapper - when defined(macosx) or defined(macos) or defined(ios): - const DLLSSLName* = "libssl" & versions & ".dylib" - elif defined(genode): - const DLLSSLName* = "libssl.lib.so" - else: - const DLLSSLName* = "libssl.so" & versions - -# OpenSSL wrapper -# -------------------------------------------------------------------- - -proc SHA256[T: byte|char]( - msg: openarray[T], - digest: ptr array[32, byte] = nil - ): ptr array[32, byte] {.noconv, dynlib: DLLSSLName, importc.} - -proc SHA256_OpenSSL[T: byte|char]( - digest: var array[32, byte], - s: openarray[T]) = - discard SHA256(s, digest.addr) +proc separator*() = separator(69) # -------------------------------------------------------------------- diff --git a/tests/t_hash_sha256_vs_openssl.nim b/tests/t_hash_sha256_vs_openssl.nim index 6c0025b14..ec632d19d 100644 --- a/tests/t_hash_sha256_vs_openssl.nim +++ b/tests/t_hash_sha256_vs_openssl.nim @@ -5,47 +5,7 @@ import # Helpers helpers/prng_unsafe -# Deal with platform mess -# -------------------------------------------------------------------- -when defined(windows): - when sizeof(int) == 8: - const DLLSSLName* = "(libssl-1_1-x64|ssleay64|libssl64).dll" - else: - const DLLSSLName* = "(libssl-1_1|ssleay32|libssl32).dll" -else: - when defined(macosx) or defined(macos) or defined(ios): - const versions = "(.1.1|.38|.39|.41|.43|.44|.45|.46|.47|.48|.10|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|)" - else: - const versions = "(.1.1|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|.48|.47|.46|.45|.44|.43|.41|.39|.38|.10|)" - - when defined(macosx) or defined(macos) or defined(ios): - const DLLSSLName* = "libssl" & versions & ".dylib" - elif defined(genode): - const DLLSSLName* = "libssl.lib.so" - else: - const DLLSSLName* = "libssl.so" & versions - -# OpenSSL wrapper -# -------------------------------------------------------------------- - -# OpenSSL removed direct use of their SHA256 function. https://github.com/openssl/openssl/commit/4d49b68504cc494e552bce8e0b82ec8b501d5abe -# It isn't accessible anymore in Windows CI on Github Action. -# But the new API EVP_Q_digest isn't accesible either -# TODO: fix Windows -when not defined(windows): - proc EVP_Q_digest[T: byte|char]( - ossl_libctx: pointer, - algoName: cstring, - propq: cstring, - data: openArray[T], - digest: var array[32, byte], - size: ptr uint): int32 {.noconv, dynlib: DLLSSLName, importc.} - - proc SHA256_OpenSSL[T: byte|char]( - digest: var array[32, byte], - s: openArray[T]) = - # discard SHA256(s, digest.addr) - discard EVP_Q_digest(nil, "SHA256", nil, s, digest, nil) +import ./openssl_wrapper # Test cases # -------------------------------------------------------------------- From 04ce1c81c7e45daa3f904c13e98252af8892fb1b Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 28 Dec 2024 12:39:33 +0100 Subject: [PATCH 35/52] [codecs] move serialization logic to ecdsa secp256k1 submodule --- constantine/ecdsa_secp256k1.nim | 125 -------------- .../serialization/codecs_ecdsa_secp256k1.nim | 160 ++++++++++++++++++ tests/ecdsa/t_ecdsa_verify_openssl.nim | 4 +- 3 files changed, 162 insertions(+), 127 deletions(-) create mode 100644 constantine/serialization/codecs_ecdsa_secp256k1.nim diff --git a/constantine/ecdsa_secp256k1.nim b/constantine/ecdsa_secp256k1.nim index 6c4f77750..7a17ac989 100644 --- a/constantine/ecdsa_secp256k1.nim +++ b/constantine/ecdsa_secp256k1.nim @@ -56,128 +56,3 @@ proc generatePrivateKey*(): Fr[C] {.noinit.} = proc getPublicKey*(secKey: Fr[C]): EC_ShortW_Aff[Fp[C], G1] {.noinit.} = result.derivePubkey(secKey) - - -## XXX: move to serialization submodule - -proc toBytes[Name: static Algebra; N: static int](res: var array[N, byte], x: FF[Name]) = - discard res.marshal(x.toBig(), bigEndian) - -proc toPemPrivateKey(res: var array[48, byte], privateKey: Fr[C]) = - ## Encodes a private key as ASN.1 DER encoded private keys. - ## - ## See: https://www.secg.org/sec1-v2.pdf appendix C.4 - ## - ## TODO: Adjust to support different curves. - # Start with SEQUENCE - res.rawCopy(0, [byte(0x30), byte(0x2E)], 0, 2) - - # Version (always 1) - res.rawCopy(2, [byte(0x02), 1, 1], 0, 3) - - # Private key as octet string - var secKeyBytes {.noinit.}: array[32, byte] - secKeyBytes.toBytes(privateKey) - - res.rawCopy(5, [byte(0x04), byte(secKeyBytes.len)], 0, 2) - res.rawCopy(7, secKeyBytes, 0, 32) ## XXX: array size - - # Parameters (secp256k1 OID: 1.3.132.0.10) - const Secp256k1Oid = [byte(0xA0), byte(7), byte(6), byte(5), - byte(0x2B), byte(0x81), byte(0x04), byte(0x00), byte(0x0A)] - res.rawCopy(39, Secp256k1Oid, 0, 9) - -proc toPemPrivateKey(privateKey: Fr[C]): array[48, byte] = - result.toPemPrivateKey(privateKey) - -proc toPemPublicKey(res: var array[88, byte], publicKey: EC_ShortW_Aff[Fp[C], G1]) = - ## Encodes a public key as ASN.1 DER encoded public keys. - ## - ## See: https://www.secg.org/sec1-v2.pdf appendix C.3 - ## - ## TODO: Adjust to support different curves. - # Start with SEQUENCE - res.rawCopy(0, [byte(0x30), byte(0x56)], 0, 2) - - # Algorithm identifier - const algoId = [ - byte(0x30), byte(0x10), # SEQUENCE - byte(0x06), byte(0x07), # OID for EC - byte(0x2A), byte(0x86), byte(0x48), # 1.2.840.10045.2.1 - byte(0xCE), byte(0x3D), byte(0x02), byte(0x01), - byte(0x06), byte(0x05), # OID for secp256k1 - byte(0x2B), byte(0x81), byte(0x04), byte(0x00), byte(0x0A) # 1.3.132.0.10 - ] - - res.rawCopy(2, algoId, 0, algoId.len) # algoId.len == 18 - - # Public key as bit string - const encoding = [byte(0x03), byte(0x42)] # [BIT-STRING, 2+32+32 prefix & coordinates] - const prefix = [ - byte(0x00), # DER BIT STRING: number of unused bits (always 0 for keys) - byte(0x04) # SEC1: uncompressed point format marker - ] - - template toByteArray(x: Fp[C] | Fr[C]): untyped = - var a: array[32, byte] - a.toBytes(x) - a - - res.rawCopy(20, encoding, 0, 2) - res.rawCopy(22, prefix, 0, 2) - res.rawCopy(24, publicKey.x.toByteArray(), 0, 32) - res.rawCopy(56, publicKey.y.toByteArray(), 0, 32) - -proc toPemPublicKey(publicKey: EC_ShortW_Aff[Fp[C], G1]): array[88, byte] = - result.toPemPublicKey(publicKey) - -## NOTE: -## The below procs / code is currently "unsuited" for Constantine in the sense that -## it currently still contains stdlib dependencies. Most of those are trivial, with the -## exception of a base64 encoder. -## Having a ANS1.DER encoder (and maybe decoder in the future) for SEC1 private and -## public keys would be nice to have in CTT, I think (at least for the curves that -## we support for the related operations; secp256k1 at the moment). - -import std / [strutils, base64, math] - -proc wrap(s: string, maxLineWidth = 64): string = - ## Wrap the given string at `maxLineWidth` over multiple lines - let lines = s.len.ceilDiv maxLineWidth - result = newStringOfCap(s.len + lines) - for i in 0 ..< lines: - let frm = i * maxLineWidth - let to = min(s.len, (i+1) * maxLineWidth) - result.add s[frm ..< to] - if i < lines-1: - result.add "\n" - -proc toPemFile*(publicKey: EC_ShortW_Aff[Fp[C], G1]): string = - ## Convert a given private key to data in PEM format following SEC1 - ## - ## RFC 7468 describes the textual encoding of these files: - ## https://www.rfc-editor.org/rfc/rfc7468#section-10 - # 1. Convert public key to ASN.1 DER - let derB = publicKey.toPemPublicKey() - # 2. Encode bytes in base64 - let der64 = derB.encode().wrap() - # 3. Wrap in begin/end public key template - result = "-----BEGIN PUBLIC KEY-----\n" - result.add der64 & "\n" - result.add "-----END PUBLIC KEY-----\n" - -proc toPemFile*(privateKey: Fr[C]): string = - ## XXX: For now using `std/base64` but will need to write base64 encoder - ## & add tests for CTT base64 decoder! - ## Convert a given private key to data in PEM format following SEC1 - ## - ## RFC 7468 describes the textual encoding of these files: - ## https://www.rfc-editor.org/rfc/rfc7468#section-13 - # 1. Convert private key to ASN.1 DER encoding - let derB = toPemPrivateKey(privateKey) - # 2. Encode bytes in base64 - let der64 = derB.encode().wrap() - # 3. Wrap in begin/end private key template - result = "-----BEGIN EC PRIVATE KEY-----\n" - result.add der64 & "\n" - result.add "-----END EC PRIVATE KEY-----\n" diff --git a/constantine/serialization/codecs_ecdsa_secp256k1.nim b/constantine/serialization/codecs_ecdsa_secp256k1.nim new file mode 100644 index 000000000..99e09cf6a --- /dev/null +++ b/constantine/serialization/codecs_ecdsa_secp256k1.nim @@ -0,0 +1,160 @@ +# Constantine +# Copyright (c) 2018-2019 Status Research & Development GmbH +# Copyright (c) 2020-Present Mamy André-Ratsimbazafy +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +##[ + +Performs serialization of ECDSA public and private keys over Secp256k1 +in ASN.1 DER encoded PEM files following SEC1: + +https://www.secg.org/sec1-v2.pdf + +(See appendix C) + +Further, RFC 7468 + +https://www.rfc-editor.org/rfc/rfc7468 + +describes the PEM file format itself. + +The code below could be made generic under the curve relatively easily. +That requires to adjust the array sizes & length bytes for the scalar fields +/ elliptic curve points in use and needs the OIDs for the curves. + + +*NOTE*: Currently this relies on the Nim stdlib `base64` encoder. + +TODO: Write custom `base64` encoder and add tests for existing `base64` +decoder in `codecs.nim` +]## + +import + constantine/named/algebras, + constantine/platforms/primitives, + constantine/math/arithmetic/finite_fields, + constantine/math/elliptic/ec_shortweierstrass_affine, + constantine/math/io/io_bigints + +import std / [strutils, base64, math] + +const C = Secp256k1 + +proc toBytes[Name: static Algebra; N: static int](res: var array[N, byte], x: FF[Name]) = + discard res.marshal(x.toBig(), bigEndian) + +proc toPemPrivateKey*(res: var array[48, byte], privateKey: Fr[C]) = + ## Encodes a private key as ASN.1 DER encoded private keys. + ## + ## See: https://www.secg.org/sec1-v2.pdf appendix C.4 + ## + ## TODO: Adjust to support different curves. + # Start with SEQUENCE + res.rawCopy(0, [byte(0x30), byte(0x2E)], 0, 2) + + # Version (always 1) + res.rawCopy(2, [byte(0x02), 1, 1], 0, 3) + + # Private key as octet string + var secKeyBytes {.noinit.}: array[32, byte] + secKeyBytes.toBytes(privateKey) + + res.rawCopy(5, [byte(0x04), byte(secKeyBytes.len)], 0, 2) + res.rawCopy(7, secKeyBytes, 0, 32) ## XXX: array size + + # Parameters (secp256k1 OID: 1.3.132.0.10) + const Secp256k1Oid = [byte(0xA0), byte(7), byte(6), byte(5), + byte(0x2B), byte(0x81), byte(0x04), byte(0x00), byte(0x0A)] + res.rawCopy(39, Secp256k1Oid, 0, 9) + +proc toPemPublicKey*(res: var array[88, byte], publicKey: EC_ShortW_Aff[Fp[C], G1]) = + ## Encodes a public key as ASN.1 DER encoded public keys. + ## + ## See: https://www.secg.org/sec1-v2.pdf appendix C.3 + ## + ## TODO: Adjust to support different curves. + # Start with SEQUENCE + res.rawCopy(0, [byte(0x30), byte(0x56)], 0, 2) + + # Algorithm identifier + const algoId = [ + byte(0x30), byte(0x10), # SEQUENCE + byte(0x06), byte(0x07), # OID for EC + byte(0x2A), byte(0x86), byte(0x48), # 1.2.840.10045.2.1 + byte(0xCE), byte(0x3D), byte(0x02), byte(0x01), + byte(0x06), byte(0x05), # OID for secp256k1 + byte(0x2B), byte(0x81), byte(0x04), byte(0x00), byte(0x0A) # 1.3.132.0.10 + ] + + res.rawCopy(2, algoId, 0, algoId.len) # algoId.len == 18 + + # Public key as bit string + const encoding = [byte(0x03), byte(0x42)] # [BIT-STRING, 2+32+32 prefix & coordinates] + const prefix = [ + byte(0x00), # DER BIT STRING: number of unused bits (always 0 for keys) + byte(0x04) # SEC1: uncompressed point format marker + ] + + template toByteArray(x: Fp[C] | Fr[C]): untyped = + var a: array[32, byte] + a.toBytes(x) + a + + res.rawCopy(20, encoding, 0, 2) + res.rawCopy(22, prefix, 0, 2) + res.rawCopy(24, publicKey.x.toByteArray(), 0, 32) + res.rawCopy(56, publicKey.y.toByteArray(), 0, 32) + +## NOTE: +## The below procs / code is currently "unsuited" for Constantine in the sense that +## it currently still contains stdlib dependencies. Most of those are trivial, with the +## exception of a base64 encoder. +## Having a ANS1.DER encoder (and maybe decoder in the future) for SEC1 private and +## public keys would be nice to have in CTT, I think (at least for the curves that +## we support for the related operations; secp256k1 at the moment). + +proc wrap(s: string, maxLineWidth = 64): string = + ## Wrap the given string at `maxLineWidth` over multiple lines + let lines = s.len.ceilDiv maxLineWidth + result = newStringOfCap(s.len + lines) + for i in 0 ..< lines: + let frm = i * maxLineWidth + let to = min(s.len, (i+1) * maxLineWidth) + result.add s[frm ..< to] + if i < lines-1: + result.add "\n" + +proc toPemFile*(publicKey: EC_ShortW_Aff[Fp[C], G1]): string = + ## Convert a given private key to data in PEM format following SEC1 + ## + ## RFC 7468 describes the textual encoding of these files: + ## https://www.rfc-editor.org/rfc/rfc7468#section-10 + # 1. Convert public key to ASN.1 DER + var derB: array[88, byte] + derB.toPemPublicKey(publicKey) + # 2. Encode bytes in base64 + let der64 = derB.encode().wrap() + # 3. Wrap in begin/end public key template + result = "-----BEGIN PUBLIC KEY-----\n" + result.add der64 & "\n" + result.add "-----END PUBLIC KEY-----\n" + +proc toPemFile*(privateKey: Fr[C]): string = + ## XXX: For now using `std/base64` but will need to write base64 encoder + ## & add tests for CTT base64 decoder! + ## Convert a given private key to data in PEM format following SEC1 + ## + ## RFC 7468 describes the textual encoding of these files: + ## https://www.rfc-editor.org/rfc/rfc7468#section-13 + # 1. Convert private key to ASN.1 DER encoding + var derB {.noinit.}: array[48, byte] + derB.toPemPrivateKey(privateKey) + # 2. Encode bytes in base64 + let der64 = derB.encode().wrap() + # 3. Wrap in begin/end private key template + result = "-----BEGIN EC PRIVATE KEY-----\n" + result.add der64 & "\n" + result.add "-----END EC PRIVATE KEY-----\n" diff --git a/tests/ecdsa/t_ecdsa_verify_openssl.nim b/tests/ecdsa/t_ecdsa_verify_openssl.nim index 8944ea99f..381ec0ba2 100644 --- a/tests/ecdsa/t_ecdsa_verify_openssl.nim +++ b/tests/ecdsa/t_ecdsa_verify_openssl.nim @@ -15,11 +15,11 @@ import constantine/ecdsa_secp256k1, constantine/named/algebras, constantine/math/io/[io_bigints, io_fields, io_ec], - constantine/serialization/codecs, + constantine/serialization/[codecs, codecs_ecdsa_secp256k1], constantine/math/arithmetic/finite_fields, constantine/platforms/abstractions -import ./openssl_wrapper +import ../openssl_wrapper import std / [os, osproc, strutils, strformat, unittest] From 0c5195fbd0b3c8d372c7cf77cf20064bde971289 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 28 Dec 2024 13:22:16 +0100 Subject: [PATCH 36/52] [codecs] move DER signature serialization to codecs_ecdsa submodule --- constantine/serialization/codecs_ecdsa.nim | 176 +++++++++++++++++++++ constantine/signatures/ecdsa.nim | 135 ---------------- tests/ecdsa/t_ecdsa_verify_openssl.nim | 2 +- 3 files changed, 177 insertions(+), 136 deletions(-) create mode 100644 constantine/serialization/codecs_ecdsa.nim diff --git a/constantine/serialization/codecs_ecdsa.nim b/constantine/serialization/codecs_ecdsa.nim new file mode 100644 index 000000000..207a37537 --- /dev/null +++ b/constantine/serialization/codecs_ecdsa.nim @@ -0,0 +1,176 @@ +# Constantine +# Copyright (c) 2018-2019 Status Research & Development GmbH +# Copyright (c) 2020-Present Mamy André-Ratsimbazafy +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +##[ + +Performs (de-)serialization of ECDSA signatures into ASN.1 DER encoded +data following SEC1: + +https://www.secg.org/sec1-v2.pdf + +In contrast to `codecs_ecdsa_secp256k1.nim` this file is generic under the choice +of elliptic curve. +]## + +#import +# constantine/named/algebras, +# constantine/platforms/primitives, +# constantine/platforms/abstractions, +# constantine/math/arithmetic/finite_fields, +# constantine/math/elliptic/ec_shortweierstrass_affine, +# constantine/math/io/io_bigints + +import + constantine/hashes, + constantine/named/algebras, + constantine/math/io/[io_bigints, io_fields, io_ec], + constantine/math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul], + constantine/math/[arithmetic, ec_shortweierstrass], + constantine/platforms/[abstractions, views], + constantine/serialization/codecs, # for fromHex and (in the future) base64 encoding + constantine/mac/mac_hmac, # for deterministic nonce generation via RFC 6979 + constantine/named/zoo_generators, # for generator + constantine/csprngs/sysrand, + constantine/signatures/common_signature_ops # for `derivePubkey` + +type + ## Helper type for ASN.1 DER signatures to avoid allocation. + ## Has a `data` buffer of 72 bytes (maximum possible size for + ## a signature for `secp256k1`) and `len` of actually used data. + ## `data[0 ..< len]` is the actual signature. + DERSignature*[N: static int] = object + data*: array[N, byte] # Max size: 6 bytes overhead + 33 bytes each for r,s + len*: int # Actual length used + +template DERSigSize*(Name: static Algebra): int = + 6 + 2 * (Fr[Name].bits.ceilDiv_vartime(sizeof(pointer)) + 1) + +proc toDER*[Name: static Algebra; N: static int](derSig: var DERSignature[N], r, s: Fr[Name]) = + ## Converts signature (r,s) to DER format without allocation. + ## Max size is 72 bytes (for Secp256k1 or any curve with 32 byte scalars in `Fr`): + ## 6 bytes overhead + up to 32+1 bytes each for r,s. + ## 6 byte 'overhead' for: + ## - `0x30` byte SEQUENCE designator + ## - total length of the array + ## - integer type designator `0x02` (before `r` and `s`) + ## - length of `r` and `s` + ## + ## Implementation follows ideas of Bitcoin's secp256k1 implementation: + ## https://github.com/bitcoin-core/secp256k1/blob/f79f46c70386c693ff4e7aef0b9e7923ba284e56/src/ecdsa_impl.h#L171-L193 + + const WordSize = sizeof(BaseType) + const N = Fr[Name].bits.ceilDiv_vartime(WordSize) # 32 for `secp256k1` + + template toByteArray(x: Fr[Name]): untyped = + ## Convert to a 33 byte array. Leading zero byte required if + ## first real byte (idx 1) highest bit set (> 0x80). + var a: array[N+1, byte] + discard toOpenArray[byte](a, 1, N).marshal(x.toBig(), bigEndian) + a + + # 1. Prepare the data & determine required sizes + + # Convert r,s to big-endian bytes + var rBytes = r.toByteArray() + var sBytes = s.toByteArray() + var rLen = N + 1 + var sLen = N + 1 + + # Skip leading zeros but ensure high bit constraint + var rPos = 0 + while rLen > 1 and rBytes[rPos] == 0 and (rBytes[rPos+1] < 0x80.byte): + dec rLen + inc rPos + var sPos = 0 + while sLen > 1 and sBytes[sPos] == 0 and (sBytes[sPos+1] < 0x80.byte): + dec sLen + inc sPos + + # Set total length + derSig.len = 6 + rLen + sLen + + + # 2. Write the actual data + var pos = 0 + template setInc(val: byte): untyped = + # Set `val` at `pos` and increase `pos` + derSig.data[pos] = val + inc pos + + # Write DER structure, global + setInc 0x30 # sequence + setInc (4 + rLen + sLen).byte # total length + + # `r` prefix + setInc 0x02 # integer + setInc rLen.byte # length of `r` + # Write `r` bytes in valid region + derSig.data.rawCopy(pos, rBytes, rPos, rLen) + inc pos, rLen + + # `s` prefix + setInc 0x02 # integer + setInc sLen.byte # length of `s` + # Write `s` bytes in valid region + derSig.data.rawCopy(pos, sBytes, sPos, sLen) + inc pos, sLen + + assert derSig.len == pos + +proc fromRawDER*(r, s: var array[32, byte], sig: openArray[byte]): bool = + ## Extracts the `r` and `s` values from a given DER signature. + ## + ## Returns `true` if the input is a valid DER encoded signature + ## for `secp256k1` (or any curve with 32 byte scalars). + var pos = 0 + + template checkInc(val: untyped): untyped = + if pos > sig.high or sig[pos] != val: + # Invalid signature + return false + inc pos + template readInc(val: untyped): untyped = + if pos > sig.high: + return false + val = sig[pos] + inc pos + + checkInc(0x30) # SEQUENCE + var totalLen: byte; readInc(totalLen) + + template parseElement(el: var array[32, byte]): untyped = + var eLen: byte; readInc(eLen) # len of `r` + if pos + eLen.int > sig.len: # would need more data than available + return false + # read `r` into *last* `rLen` bytes + var eStart = el.len - eLen.int + if eStart < 0: # indicates prefix 0 due to first byte >= 0x80 (highest bit set) + doAssert eLen == 33 + inc pos # skip first byte + eStart = 0 # start from 0 in `el` + dec eLen # decrease eLen by 1 + el.rawCopy(eStart, sig, pos, eLen.int) + inc pos, eLen.int + + # `r` + checkInc(0x02) # INTEGER + parseElement(r) + + # `s` + checkInc(0x02) # INTEGER + parseElement(s) + + # NOTE: `totalLen` does not include the prefix [0x30, totalLen] 2 bytes. Hence -2. + assert pos - 2 == totalLen.int, "Pos = " & $pos & ", totalLen = " & $totalLen + + result = true + +proc fromDER*(r, s: var array[32, byte], derSig: DERSignature) = + ## Splits a given `DERSignature` back into the `r` and `s` elements as + ## raw byte arrays. + fromRawDER(r, s, derSig.data) diff --git a/constantine/signatures/ecdsa.nim b/constantine/signatures/ecdsa.nim index bcdcbfb61..de0c8e909 100644 --- a/constantine/signatures/ecdsa.nim +++ b/constantine/signatures/ecdsa.nim @@ -31,144 +31,9 @@ type nsRandom, ## pure uniform random sampling nsRfc6979 ## deterministic according to RFC 6979 - ## Helper type for ASN.1 DER signatures to avoid allocation. - ## Has a `data` buffer of 72 bytes (maximum possible size for - ## a signature for `secp256k1`) and `len` of actually used data. - ## `data[0 ..< len]` is the actual signature. - DERSignature*[N: static int] = object - data*: array[N, byte] # Max size: 6 bytes overhead + 33 bytes each for r,s - len*: int # Actual length used - -template DERSigSize*(Name: static Algebra): int = - 6 + 2 * (Fr[Name].bits.ceilDiv_vartime(sizeof(pointer)) + 1) - proc toBytes[Name: static Algebra; N: static int](res: var array[N, byte], x: FF[Name]) = discard res.marshal(x.toBig(), bigEndian) -proc toDER*[Name: static Algebra; N: static int](derSig: var DERSignature[N], r, s: Fr[Name]) = - ## Converts signature (r,s) to DER format without allocation. - ## Max size is 72 bytes (for Secp256k1 or any curve with 32 byte scalars in `Fr`): - ## 6 bytes overhead + up to 32+1 bytes each for r,s. - ## 6 byte 'overhead' for: - ## - `0x30` byte SEQUENCE designator - ## - total length of the array - ## - integer type designator `0x02` (before `r` and `s`) - ## - length of `r` and `s` - ## - ## Implementation follows ideas of Bitcoin's secp256k1 implementation: - ## https://github.com/bitcoin-core/secp256k1/blob/f79f46c70386c693ff4e7aef0b9e7923ba284e56/src/ecdsa_impl.h#L171-L193 - - const WordSize = sizeof(BaseType) - const N = Fr[Name].bits.ceilDiv_vartime(WordSize) # 32 for `secp256k1` - - template toByteArray(x: Fr[Name]): untyped = - ## Convert to a 33 byte array. Leading zero byte required if - ## first real byte (idx 1) highest bit set (> 0x80). - var a: array[N+1, byte] - discard toOpenArray[byte](a, 1, N).marshal(x.toBig(), bigEndian) - a - - # 1. Prepare the data & determine required sizes - - # Convert r,s to big-endian bytes - var rBytes = r.toByteArray() - var sBytes = s.toByteArray() - var rLen = N + 1 - var sLen = N + 1 - - # Skip leading zeros but ensure high bit constraint - var rPos = 0 - while rLen > 1 and rBytes[rPos] == 0 and (rBytes[rPos+1] < 0x80.byte): - dec rLen - inc rPos - var sPos = 0 - while sLen > 1 and sBytes[sPos] == 0 and (sBytes[sPos+1] < 0x80.byte): - dec sLen - inc sPos - - # Set total length - derSig.len = 6 + rLen + sLen - - - # 2. Write the actual data - var pos = 0 - template setInc(val: byte): untyped = - # Set `val` at `pos` and increase `pos` - derSig.data[pos] = val - inc pos - - # Write DER structure, global - setInc 0x30 # sequence - setInc (4 + rLen + sLen).byte # total length - - # `r` prefix - setInc 0x02 # integer - setInc rLen.byte # length of `r` - # Write `r` bytes in valid region - derSig.data.rawCopy(pos, rBytes, rPos, rLen) - inc pos, rLen - - # `s` prefix - setInc 0x02 # integer - setInc sLen.byte # length of `s` - # Write `s` bytes in valid region - derSig.data.rawCopy(pos, sBytes, sPos, sLen) - inc pos, sLen - - assert derSig.len == pos - -proc fromRawDER*(r, s: var array[32, byte], sig: openArray[byte]): bool = - ## Extracts the `r` and `s` values from a given DER signature. - ## - ## Returns `true` if the input is a valid DER encoded signature - ## for `secp256k1` (or any curve with 32 byte scalars). - var pos = 0 - - template checkInc(val: untyped): untyped = - if pos > sig.high or sig[pos] != val: - # Invalid signature - return false - inc pos - template readInc(val: untyped): untyped = - if pos > sig.high: - return false - val = sig[pos] - inc pos - - checkInc(0x30) # SEQUENCE - var totalLen: byte; readInc(totalLen) - - template parseElement(el: var array[32, byte]): untyped = - var eLen: byte; readInc(eLen) # len of `r` - if pos + eLen.int > sig.len: # would need more data than available - return false - # read `r` into *last* `rLen` bytes - var eStart = el.len - eLen.int - if eStart < 0: # indicates prefix 0 due to first byte >= 0x80 (highest bit set) - doAssert eLen == 33 - inc pos # skip first byte - eStart = 0 # start from 0 in `el` - dec eLen # decrease eLen by 1 - el.rawCopy(eStart, sig, pos, eLen.int) - inc pos, eLen.int - - # `r` - checkInc(0x02) # INTEGER - parseElement(r) - - # `s` - checkInc(0x02) # INTEGER - parseElement(s) - - assert pos == totalLen.int, "Pos = " & $pos & ", totalLen = " & $totalLen - - result = true - -proc fromDER*(r, s: var array[32, byte], derSig: DERSignature) = - ## Splits a given `DERSignature` back into the `r` and `s` elements as - ## raw byte arrays. - fromRawDER(r, s, derSig.data) - func fromDigest[Name: static Algebra; N: static int]( dst: var Fr[Name], src: array[N, byte], truncateInput: static bool): bool {.discardable.} = diff --git a/tests/ecdsa/t_ecdsa_verify_openssl.nim b/tests/ecdsa/t_ecdsa_verify_openssl.nim index 381ec0ba2..2e9e08f7a 100644 --- a/tests/ecdsa/t_ecdsa_verify_openssl.nim +++ b/tests/ecdsa/t_ecdsa_verify_openssl.nim @@ -15,7 +15,7 @@ import constantine/ecdsa_secp256k1, constantine/named/algebras, constantine/math/io/[io_bigints, io_fields, io_ec], - constantine/serialization/[codecs, codecs_ecdsa_secp256k1], + constantine/serialization/[codecs, codecs_ecdsa, codecs_ecdsa_secp256k1], constantine/math/arithmetic/finite_fields, constantine/platforms/abstractions From 89688bab07df3de9722a83134dafc93a3ee4e76e Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 28 Dec 2024 13:54:25 +0100 Subject: [PATCH 37/52] [ecdsa] adjust ECDSA secp256k1 API & test cases --- constantine/ecdsa_secp256k1.nim | 73 +++++++++++-------- .../serialization/codecs_ecdsa_secp256k1.nim | 15 ++-- tests/ecdsa/t_ecdsa_verify_openssl.nim | 52 ++++++++++--- 3 files changed, 92 insertions(+), 48 deletions(-) diff --git a/constantine/ecdsa_secp256k1.nim b/constantine/ecdsa_secp256k1.nim index 7a17ac989..54cc5cebc 100644 --- a/constantine/ecdsa_secp256k1.nim +++ b/constantine/ecdsa_secp256k1.nim @@ -7,6 +7,7 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import + constantine/zoo_exports, constantine/signatures/ecdsa, constantine/hashes/h_sha256, constantine/named/algebras, @@ -18,41 +19,53 @@ import constantine/named/zoo_generators, # for generator constantine/csprngs/sysrand -export ecdsa ## XXX: shouldn't be needed once serialization is in submodules +export NonceSampler -## XXX: Move this as API in `constantine/ecdsa_secp256k1.nim` -# For easier readibility, define the curve and generator -# as globals in this file -const C* = Secp256k1 +const prefix_ffi = "ctt_ecdsa_secp256k1_" +type + SecretKey* {.byref, exportc: prefix_ffi & "seckey".} = object + ## A Secp256k1 secret key + raw*: Fr[Secp256k1] -## XXX: Still need to adjust secp256k1 specific API & tests -proc signMessage*(message: string, secretKey: Fr[C], - nonceSampler: NonceSampler = nsRandom): tuple[r, s: Fr[C]] = - ## WARNING: Convenience for development - result.coreSign(secretKey, message.toOpenArrayByte(0, message.len-1), sha256, nonceSampler) + PublicKey* {.byref, exportc: prefix_ffi & "pubkey".} = object + ## A Secp256k1 public key for ECDSA signatures + raw*: EC_ShortW_Aff[Fp[Secp256k1], G1] -proc verifySignature*( - message: string, - signature: tuple[r, s: Fr[C]], - publicKey: EC_ShortW_Aff[Fp[C], G1] -): bool = - ## WARNING: Convenience for development - result = publicKey.coreVerify(message.toOpenArrayByte(0, message.len-1), signature, sha256) + Signature* {.byref, exportc: prefix_ffi & "signature".} = object + ## A Secp256k1 signature for ECDSA signatures + r: Fr[Secp256k1] + s: Fr[Secp256k1] -proc randomFieldElement[FF](): FF = - ## random element in ~Fp[T]/Fr[T]~ - let m = FF.getModulus() - var b: matchingBigInt(FF.Name) +func pubkey_is_zero*(pubkey: PublicKey): bool {.libPrefix: prefix_ffi.} = + ## Returns true if input is 0 + bool(pubkey.raw.isNeutral()) - while b.isZero().bool or (b > m).bool: - ## XXX: raise / what else to do if `sysrand` call fails? - doAssert b.limbs.sysrand() +func pubkeys_are_equal*(a, b: PublicKey): bool {.libPrefix: prefix_ffi.} = + ## Returns true if inputs are equal + bool(a.raw == b.raw) - result.fromBig(b) +func signatures_are_equal*(a, b: Signature): bool {.libPrefix: prefix_ffi.} = + ## Returns true if inputs are equal + bool(a.r == b.r and a.s == b.s) -proc generatePrivateKey*(): Fr[C] {.noinit.} = - ## Generate a new private key using a cryptographic random number generator. - result = randomFieldElement[Fr[C]]() +proc sign*(sig: var Signature, + secretKey: SecretKey, + message: openArray[byte], + nonceSampler: NonceSampler = nsRandom) {.libPrefix: prefix_ffi, genCharAPI.} = + ## Sign `message` using `secretKey` and store the signature in `sig`. The nonce + ## will either be randomly sampled `nsRandom` or deterministically calculated according + ## to RFC6979 (`nsRfc6979`) + sig.coreSign(secretKey.raw, message, sha256, nonceSampler) -proc getPublicKey*(secKey: Fr[C]): EC_ShortW_Aff[Fp[C], G1] {.noinit.} = - result.derivePubkey(secKey) +proc verify*( + publicKey: PublicKey, + message: openArray[byte], + signature: Signature +): bool {.libPrefix: prefix_ffi, genCharAPI.} = + result = publicKey.raw.coreVerify(message, signature, sha256) + +func derive_pubkey*(public_key: var PublicKey, secret_key: SecretKey) {.libPrefix: prefix_ffi.} = + ## Derive the public key matching with a secret key + ## + ## The secret_key MUST be validated + public_key.raw.derivePubkey(secret_key.raw) diff --git a/constantine/serialization/codecs_ecdsa_secp256k1.nim b/constantine/serialization/codecs_ecdsa_secp256k1.nim index 99e09cf6a..9be68f4d9 100644 --- a/constantine/serialization/codecs_ecdsa_secp256k1.nim +++ b/constantine/serialization/codecs_ecdsa_secp256k1.nim @@ -37,9 +37,10 @@ import constantine/platforms/primitives, constantine/math/arithmetic/finite_fields, constantine/math/elliptic/ec_shortweierstrass_affine, - constantine/math/io/io_bigints + constantine/math/io/io_bigints, + constantine/ecdsa_secp256k1 -import std / [strutils, base64, math] +import std / [strutils, base64, math, importutils] const C = Secp256k1 @@ -127,14 +128,15 @@ proc wrap(s: string, maxLineWidth = 64): string = if i < lines-1: result.add "\n" -proc toPemFile*(publicKey: EC_ShortW_Aff[Fp[C], G1]): string = +proc toPemFile*(publicKey: PublicKey): string = ## Convert a given private key to data in PEM format following SEC1 ## ## RFC 7468 describes the textual encoding of these files: ## https://www.rfc-editor.org/rfc/rfc7468#section-10 # 1. Convert public key to ASN.1 DER var derB: array[88, byte] - derB.toPemPublicKey(publicKey) + privateAccess(PublicKey) # for `raw` access + derB.toPemPublicKey(publicKey.raw) # 2. Encode bytes in base64 let der64 = derB.encode().wrap() # 3. Wrap in begin/end public key template @@ -142,7 +144,7 @@ proc toPemFile*(publicKey: EC_ShortW_Aff[Fp[C], G1]): string = result.add der64 & "\n" result.add "-----END PUBLIC KEY-----\n" -proc toPemFile*(privateKey: Fr[C]): string = +proc toPemFile*(privateKey: SecretKey): string = ## XXX: For now using `std/base64` but will need to write base64 encoder ## & add tests for CTT base64 decoder! ## Convert a given private key to data in PEM format following SEC1 @@ -151,7 +153,8 @@ proc toPemFile*(privateKey: Fr[C]): string = ## https://www.rfc-editor.org/rfc/rfc7468#section-13 # 1. Convert private key to ASN.1 DER encoding var derB {.noinit.}: array[48, byte] - derB.toPemPrivateKey(privateKey) + privateAccess(SecretKey) # for `raw` access + derB.toPemPrivateKey(privateKey.raw) # 2. Encode bytes in base64 let der64 = derB.encode().wrap() # 3. Wrap in begin/end private key template diff --git a/tests/ecdsa/t_ecdsa_verify_openssl.nim b/tests/ecdsa/t_ecdsa_verify_openssl.nim index 2e9e08f7a..9759b0695 100644 --- a/tests/ecdsa/t_ecdsa_verify_openssl.nim +++ b/tests/ecdsa/t_ecdsa_verify_openssl.nim @@ -12,17 +12,35 @@ with OpenSSL. import constantine/csprngs/sysrand, - constantine/ecdsa_secp256k1, constantine/named/algebras, constantine/math/io/[io_bigints, io_fields, io_ec], constantine/serialization/[codecs, codecs_ecdsa, codecs_ecdsa_secp256k1], - constantine/math/arithmetic/finite_fields, - constantine/platforms/abstractions + constantine/math/arithmetic/[bigints, finite_fields], + constantine/platforms/abstractions, + constantine/ecdsa_secp256k1 import ../openssl_wrapper import - std / [os, osproc, strutils, strformat, unittest] + std / [os, osproc, strutils, strformat, unittest, importutils] + +const C = Secp256k1 + +proc randomFieldElement[FF](): FF = + ## random element in ~Fp[T]/Fr[T]~ + let m = FF.getModulus() + var b: matchingBigInt(FF.Name) + + while b.isZero().bool or (b > m).bool: + ## XXX: raise / what else to do if `sysrand` call fails? + doAssert b.limbs.sysrand() + + result.fromBig(b) + +proc generatePrivateKey(): SecretKey {.noinit.} = + ## Generate a new private key using a cryptographic random number generator. + privateAccess(SecretKey) + result = SecretKey(raw: randomFieldElement[Fr[C]]()) proc generateMessage(len: int): string = ## Returns a randomly generated message of `len` bytes as a @@ -39,6 +57,11 @@ proc toHex(s: string): string = proc toBytes[Name: static Algebra; N: static int](res: var array[N, byte], x: FF[Name]) = discard res.marshal(x.toBig(), bigEndian) +func getPublicKey(secKey: SecretKey): PublicKey {.noinit.} = + result.derive_pubkey(secKey) + +template toOA(x: string): untyped = toOpenArrayByte(x, 0, x.len-1) + proc signAndVerify(num: int, msg = "", nonceSampler = nsRandom) = ## Generates `num` signatures and verify them against OpenSSL. ## @@ -54,7 +77,8 @@ proc signAndVerify(num: int, msg = "", nonceSampler = nsRandom) = # Get bytes of private key & initialize an OpenSSL key var skBytes: array[32, byte] - skBytes.toBytes(secKey) + privateAccess(SecretKey) # access to `raw` + skBytes.toBytes(secKey.raw) var osSecKey: EVP_PKEY osSecKey.initPrivateKeyOpenSSL(skBytes) @@ -69,10 +93,13 @@ proc signAndVerify(num: int, msg = "", nonceSampler = nsRandom) = let (r, s) = (rOSL.toHex(), sOSL.toHex()) # Convert to scalar and verify signature let (rOslFr, sOslFr) = (Fr[C].fromHex(r), Fr[C].fromHex(s)) - check verifySignature(msg, (r: rOslFr, s: sOslFr), pubKey) + privateAccess(Signature) # make `r`, `s` accessible in scope + let sigOsl = Signature(r: rOslFr, s: sOslFr) + check pubKey.verify(toOA msg, sigOsl) # Now also sign with CTT and verify - let (rCTT, sCTT) = msg.signMessage(secKey) - check verifySignature(msg, (r: rCTT, s: sCTT), pubKey) + var sigCTT {.noinit.}: Signature + sigCTT.sign(secKey, toOA msg) + check pubKey.verify(toOA msg, sigCTT) # Verify that we can generate a DER signature again from the OpenSSL # data and it is equivalent to original @@ -119,11 +146,12 @@ proc signRfc6979(msg: string, num = 10) = var derSig: DERSignature[DERSigSize(Secp256k1)] let secKey = generatePrivateKey() - let (r, s) = msg.signMessage(secKey, nonceSampler = nsRfc6979) + var sig {.noinit.}: Signature + sig.sign(secKey, toOA msg, nonceSampler = nsRfc6979) for i in 0 ..< num: - let (r2, s2) = msg.signMessage(secKey, nonceSampler = nsRfc6979) - check bool(r == r2) - check bool(s == s2) + var sig2 {.noinit.}: Signature + sig2.sign(secKey, toOA msg, nonceSampler = nsRfc6979) + check signatures_are_equal(sig, sig2) suite "General ECDSA related tests": test "DERSigSize correctly computes maximum size of DER encoded signature": From 5011fe326483b29d8c0922140bcf95d9ae4b31b5 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 28 Dec 2024 13:56:37 +0100 Subject: [PATCH 38/52] [ecdsa] add mini docstring for `verify` --- constantine/ecdsa_secp256k1.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/constantine/ecdsa_secp256k1.nim b/constantine/ecdsa_secp256k1.nim index 54cc5cebc..7860f2947 100644 --- a/constantine/ecdsa_secp256k1.nim +++ b/constantine/ecdsa_secp256k1.nim @@ -62,6 +62,7 @@ proc verify*( message: openArray[byte], signature: Signature ): bool {.libPrefix: prefix_ffi, genCharAPI.} = + ## Verify `signature` using `publicKey` for `message`. result = publicKey.raw.coreVerify(message, signature, sha256) func derive_pubkey*(public_key: var PublicKey, secret_key: SecretKey) {.libPrefix: prefix_ffi.} = From fa5a5ebb8dad3f64dc7f4016d994095631eaa080 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 28 Dec 2024 14:04:38 +0100 Subject: [PATCH 39/52] [codecs] clean up imports in `codecs_ecdsa.nim` --- constantine/serialization/codecs_ecdsa.nim | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/constantine/serialization/codecs_ecdsa.nim b/constantine/serialization/codecs_ecdsa.nim index 207a37537..363fcdfdc 100644 --- a/constantine/serialization/codecs_ecdsa.nim +++ b/constantine/serialization/codecs_ecdsa.nim @@ -17,26 +17,12 @@ In contrast to `codecs_ecdsa_secp256k1.nim` this file is generic under the choic of elliptic curve. ]## -#import -# constantine/named/algebras, -# constantine/platforms/primitives, -# constantine/platforms/abstractions, -# constantine/math/arithmetic/finite_fields, -# constantine/math/elliptic/ec_shortweierstrass_affine, -# constantine/math/io/io_bigints - import - constantine/hashes, constantine/named/algebras, - constantine/math/io/[io_bigints, io_fields, io_ec], - constantine/math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul], - constantine/math/[arithmetic, ec_shortweierstrass], + constantine/math/io/[io_bigints, io_fields], + constantine/math/elliptic/[ec_shortweierstrass_affine], constantine/platforms/[abstractions, views], - constantine/serialization/codecs, # for fromHex and (in the future) base64 encoding - constantine/mac/mac_hmac, # for deterministic nonce generation via RFC 6979 - constantine/named/zoo_generators, # for generator - constantine/csprngs/sysrand, - constantine/signatures/common_signature_ops # for `derivePubkey` + constantine/serialization/codecs # for fromHex and (in the future) base64 encoding type ## Helper type for ASN.1 DER signatures to avoid allocation. From 2f6a897979ac15fa7d15424a26503994e6008fe0 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 28 Dec 2024 14:06:20 +0100 Subject: [PATCH 40/52] [ecdsa] clean up imports of `ecdsa_secp256k1.nim` --- constantine/ecdsa_secp256k1.nim | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/constantine/ecdsa_secp256k1.nim b/constantine/ecdsa_secp256k1.nim index 7860f2947..0deb04f72 100644 --- a/constantine/ecdsa_secp256k1.nim +++ b/constantine/ecdsa_secp256k1.nim @@ -11,13 +11,9 @@ import constantine/signatures/ecdsa, constantine/hashes/h_sha256, constantine/named/algebras, - constantine/math/io/[io_bigints, io_fields, io_ec], - constantine/math/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_jacobian, ec_scalar_mul, ec_multi_scalar_mul], + constantine/math/elliptic/[ec_shortweierstrass_affine], constantine/math/[arithmetic, ec_shortweierstrass], - constantine/platforms/[abstractions, views], - constantine/serialization/codecs, # for fromHex and (in the future) base64 encoding - constantine/named/zoo_generators, # for generator - constantine/csprngs/sysrand + constantine/platforms/[abstractions, views] export NonceSampler From 2693aec31d2a9d874c4827e3a7d618e53f82c073 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Mon, 30 Dec 2024 14:29:14 +0100 Subject: [PATCH 41/52] [ecdsa] do not export `raw` field in ecdsa_secp256k1 --- constantine/ecdsa_secp256k1.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/constantine/ecdsa_secp256k1.nim b/constantine/ecdsa_secp256k1.nim index 0deb04f72..bc28b188a 100644 --- a/constantine/ecdsa_secp256k1.nim +++ b/constantine/ecdsa_secp256k1.nim @@ -21,11 +21,11 @@ const prefix_ffi = "ctt_ecdsa_secp256k1_" type SecretKey* {.byref, exportc: prefix_ffi & "seckey".} = object ## A Secp256k1 secret key - raw*: Fr[Secp256k1] + raw: Fr[Secp256k1] PublicKey* {.byref, exportc: prefix_ffi & "pubkey".} = object ## A Secp256k1 public key for ECDSA signatures - raw*: EC_ShortW_Aff[Fp[Secp256k1], G1] + raw: EC_ShortW_Aff[Fp[Secp256k1], G1] Signature* {.byref, exportc: prefix_ffi & "signature".} = object ## A Secp256k1 signature for ECDSA signatures From 1b44a8f923485819192c0bea4419216db0203401 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Mon, 30 Dec 2024 20:53:09 +0100 Subject: [PATCH 42/52] [CI] fix CI failures by including OpenSSL wrapper instead of import Not sure what's happening here. Never seen that with any C wrappers. --- benchmarks/bench_h_sha256.nim | 9 ++++++++- tests/t_hash_sha256_vs_openssl.nim | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/benchmarks/bench_h_sha256.nim b/benchmarks/bench_h_sha256.nim index 25c6100cf..c385d83fd 100644 --- a/benchmarks/bench_h_sha256.nim +++ b/benchmarks/bench_h_sha256.nim @@ -5,7 +5,14 @@ import helpers/prng_unsafe, ./bench_blueprint -import ../tests/openssl_wrapper +## NOTE: For a reason that evades me at the moment, if we only `import` +## the wrapper, we get a linker error of the form: +## +## @mopenssl_wrapper.nim.c:(.text+0x110): undefined reference to `Dl_1073742356_' +## /usr/bin/ld: warning: creating DT_TEXTREL in a PIE +## +## So for the moment, we just include the wrapper. +include ../tests/openssl_wrapper proc separator*() = separator(69) diff --git a/tests/t_hash_sha256_vs_openssl.nim b/tests/t_hash_sha256_vs_openssl.nim index ec632d19d..8df742b92 100644 --- a/tests/t_hash_sha256_vs_openssl.nim +++ b/tests/t_hash_sha256_vs_openssl.nim @@ -5,7 +5,14 @@ import # Helpers helpers/prng_unsafe -import ./openssl_wrapper +## NOTE: For a reason that evades me at the moment, if we only `import` +## the wrapper, we get a linker error of the form: +## +## @mopenssl_wrapper.nim.c:(.text+0x110): undefined reference to `Dl_1073742356_' +## /usr/bin/ld: warning: creating DT_TEXTREL in a PIE +## +## So for the moment, we just include the wrapper. +include ./openssl_wrapper # Test cases # -------------------------------------------------------------------- From c2c39af26560567218a66ff59a771d5875bc2073 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 31 Dec 2024 09:46:08 +0100 Subject: [PATCH 43/52] [bench] disable OpenSSL bench for sha256 on windows API not currently available in the OpenSSL version on GH actions --- benchmarks/bench_h_sha256.nim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/benchmarks/bench_h_sha256.nim b/benchmarks/bench_h_sha256.nim index c385d83fd..dbb9740db 100644 --- a/benchmarks/bench_h_sha256.nim +++ b/benchmarks/bench_h_sha256.nim @@ -56,6 +56,7 @@ when isMainModule: let msg = rng.random_byte_seq(s) let iters = int(target_cycles div (s.int64 * worst_cycles_per_bytes)) benchSHA256_constantine(msg, $s & "B", iters) - benchSHA256_openssl(msg, $s & "B", iters) + when not defined(windows): # not available on Windows in GH actions atm + benchSHA256_openssl(msg, $s & "B", iters) main() From fa9e0ab9f14bad9d4170609201a8f7924024e7a3 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 31 Dec 2024 09:46:32 +0100 Subject: [PATCH 44/52] [nimble] add ECDSA signature test to nimble task --- constantine.nimble | 3 +++ 1 file changed, 3 insertions(+) diff --git a/constantine.nimble b/constantine.nimble index 82c0be0b7..ecf0fcea4 100644 --- a/constantine.nimble +++ b/constantine.nimble @@ -607,6 +607,9 @@ const testDesc: seq[tuple[path: string, useGMP: bool]] = @[ ("tests/t_ethereum_verkle_primitives.nim", false), ("tests/t_ethereum_verkle_ipa_primitives.nim", false), + # Signatures + ("tests/ecdsa/t_ecdsa_verify_openssl.nim", false), + # Proof systems # ---------------------------------------------------------- ("tests/proof_systems/t_r1cs_parser.nim", false), From 4cac2ec95cb0b67914f9b739dae6b5b27ac003ad Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 31 Dec 2024 12:25:14 +0100 Subject: [PATCH 45/52] [ecdsa] replace brainfart using pointer size for bits in byte ... --- constantine/serialization/codecs_ecdsa.nim | 5 ++--- constantine/signatures/ecdsa.nim | 10 +++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/constantine/serialization/codecs_ecdsa.nim b/constantine/serialization/codecs_ecdsa.nim index 363fcdfdc..479dc6097 100644 --- a/constantine/serialization/codecs_ecdsa.nim +++ b/constantine/serialization/codecs_ecdsa.nim @@ -48,9 +48,8 @@ proc toDER*[Name: static Algebra; N: static int](derSig: var DERSignature[N], r, ## ## Implementation follows ideas of Bitcoin's secp256k1 implementation: ## https://github.com/bitcoin-core/secp256k1/blob/f79f46c70386c693ff4e7aef0b9e7923ba284e56/src/ecdsa_impl.h#L171-L193 - - const WordSize = sizeof(BaseType) - const N = Fr[Name].bits.ceilDiv_vartime(WordSize) # 32 for `secp256k1` + const OctetWidth = 8 + const N = Fr[Name].bits.ceilDiv_vartime(OctetWidth) # 32 for `secp256k1` template toByteArray(x: Fr[Name]): untyped = ## Convert to a 33 byte array. Leading zero byte required if diff --git a/constantine/signatures/ecdsa.nim b/constantine/signatures/ecdsa.nim index de0c8e909..f36128993 100644 --- a/constantine/signatures/ecdsa.nim +++ b/constantine/signatures/ecdsa.nim @@ -55,14 +55,14 @@ func fromDigest[Name: static Algebra; N: static int]( when truncateInput: # for signature & verification # If the `src` array is larger than the BigInt underlying `Fr[Name]`, need # to truncate the `src`. - const WordSize = sizeof(BaseType) - const FrBytes = Fr[Name].bits.ceildiv_vartime(WordSize) + const OctetWidth = 8 + const FrBytes = Fr[Name].bits.ceildiv_vartime(OctetWidth) # effectively: `scalar ~ array[0 ..< scalar.len]` scalar.unmarshal(toOpenArray[byte](src, 0, FrBytes-1), bigEndian) # Now still need to right shift potential individual bits. # e.g. 381 bit BigInt fits into 384 bit (48 bytes), so need to # right shift 3 bits to truncate correctly. - const toShift = FrBytes * WordSize - Fr[Name].bits + const toShift = FrBytes * OctetWidth - Fr[Name].bits when toShift > 0: scalar.shiftRight(toShift) else: # for RFC 6979 nonce sampling. If larger than modulus, sample again @@ -110,8 +110,8 @@ proc nonceRfc6979[Name: static Algebra]( ## Spec: ## https://datatracker.ietf.org/doc/html/rfc6979#section-3.2 - const WordSize = sizeof(BaseType) - const N = Fr[Name].bits.ceilDiv_vartime(WordSize) + const OctetWidth = 8 + const N = Fr[Name].bits.ceilDiv_vartime(OctetWidth) # Step a: `h1 = H(m)` hash message (already done, input is hash), convert to array of bytes var msgHashBytes {.noinit.}: array[N, byte] From 30285ff95ac0bd968da54992457f902625c290e3 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 31 Dec 2024 20:05:29 +0100 Subject: [PATCH 46/52] [ecdsa] fix final related brainfart :) --- constantine/serialization/codecs_ecdsa.nim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/constantine/serialization/codecs_ecdsa.nim b/constantine/serialization/codecs_ecdsa.nim index 479dc6097..b117267c5 100644 --- a/constantine/serialization/codecs_ecdsa.nim +++ b/constantine/serialization/codecs_ecdsa.nim @@ -34,7 +34,8 @@ type len*: int # Actual length used template DERSigSize*(Name: static Algebra): int = - 6 + 2 * (Fr[Name].bits.ceilDiv_vartime(sizeof(pointer)) + 1) + const OctetWidth = 8 + 6 + 2 * (Fr[Name].bits.ceilDiv_vartime(OctetWidth) + 1) proc toDER*[Name: static Algebra; N: static int](derSig: var DERSignature[N], r, s: Fr[Name]) = ## Converts signature (r,s) to DER format without allocation. From 901597e3cbe6fc224cc2159b102106cae97b53e0 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Tue, 31 Dec 2024 20:48:25 +0100 Subject: [PATCH 47/52] =?UTF-8?q?[tests]=20when=20the=20brainfart=20infect?= =?UTF-8?q?s=20the=20test=20cases=20too!=20=F0=9F=A4=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/ecdsa/t_ecdsa_verify_openssl.nim | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/ecdsa/t_ecdsa_verify_openssl.nim b/tests/ecdsa/t_ecdsa_verify_openssl.nim index 9759b0695..297a9e45d 100644 --- a/tests/ecdsa/t_ecdsa_verify_openssl.nim +++ b/tests/ecdsa/t_ecdsa_verify_openssl.nim @@ -162,11 +162,8 @@ suite "General ECDSA related tests": check DerSigSize(Edwards25519) == 72 # 253 bits subgroup order, fits 256 bit BigInt check DerSigSize(BLS12_381) == 72 # not commonly used, but larger modulo but *same* subgroup order check DerSigSize(P224) == 64 # 224 bit subgroup order -> 28 byte scalars - when sizeof(BaseType) == 8: - check DerSigSize(BW6_761) == 104 # not commonly used, but larger modulo with *larger* subgroup order - # 377 bit subgroup order -> 384 BigInt -> 48 byte scalars - elif sizeof(BaseType) == 4: - check DerSigSize(BW6_761) == 96 # 377 bit subgroup -> 380 bit BigInt -> 44 byte scalars + check DerSigSize(BW6_761) == 104 # not commonly used, but larger modulo with *larger* subgroup order + # 377 bit subgroup order -> 384 BigInt -> 48 byte scalars suite "ECDSA over secp256k1": test "Verify OpenSSL generated signatures from a fixed message (different nonces)": From 6c09fe11f1f51948b5f466e79adab505679e78e8 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 1 Jan 2025 17:55:52 +0100 Subject: [PATCH 48/52] replace DERSig* by DerSig* --- constantine/serialization/codecs_ecdsa.nim | 10 +++++----- tests/ecdsa/t_ecdsa_verify_openssl.nim | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/constantine/serialization/codecs_ecdsa.nim b/constantine/serialization/codecs_ecdsa.nim index b117267c5..a804c6f57 100644 --- a/constantine/serialization/codecs_ecdsa.nim +++ b/constantine/serialization/codecs_ecdsa.nim @@ -29,15 +29,15 @@ type ## Has a `data` buffer of 72 bytes (maximum possible size for ## a signature for `secp256k1`) and `len` of actually used data. ## `data[0 ..< len]` is the actual signature. - DERSignature*[N: static int] = object + DerSignature*[N: static int] = object data*: array[N, byte] # Max size: 6 bytes overhead + 33 bytes each for r,s len*: int # Actual length used -template DERSigSize*(Name: static Algebra): int = +template DerSigSize*(Name: static Algebra): int = const OctetWidth = 8 6 + 2 * (Fr[Name].bits.ceilDiv_vartime(OctetWidth) + 1) -proc toDER*[Name: static Algebra; N: static int](derSig: var DERSignature[N], r, s: Fr[Name]) = +proc toDER*[Name: static Algebra; N: static int](derSig: var DerSignature[N], r, s: Fr[Name]) = ## Converts signature (r,s) to DER format without allocation. ## Max size is 72 bytes (for Secp256k1 or any curve with 32 byte scalars in `Fr`): ## 6 bytes overhead + up to 32+1 bytes each for r,s. @@ -156,7 +156,7 @@ proc fromRawDER*(r, s: var array[32, byte], sig: openArray[byte]): bool = result = true -proc fromDER*(r, s: var array[32, byte], derSig: DERSignature) = - ## Splits a given `DERSignature` back into the `r` and `s` elements as +proc fromDER*(r, s: var array[32, byte], derSig: DerSignature) = + ## Splits a given `DerSignature` back into the `r` and `s` elements as ## raw byte arrays. fromRawDER(r, s, derSig.data) diff --git a/tests/ecdsa/t_ecdsa_verify_openssl.nim b/tests/ecdsa/t_ecdsa_verify_openssl.nim index 297a9e45d..6cafc3858 100644 --- a/tests/ecdsa/t_ecdsa_verify_openssl.nim +++ b/tests/ecdsa/t_ecdsa_verify_openssl.nim @@ -103,7 +103,7 @@ proc signAndVerify(num: int, msg = "", nonceSampler = nsRandom) = # Verify that we can generate a DER signature again from the OpenSSL # data and it is equivalent to original - var derSig: DERSignature[DERSigSize(Secp256k1)] + var derSig: DerSignature[DerSigSize(Secp256k1)] derSig.toDER(rOslFr, sOslFr) check derSig.data == osSig @@ -143,7 +143,7 @@ proc signRfc6979(msg: string, num = 10) = ## Signs the given message with a randomly generated private key `num` times ## using deterministic nonce generation and verifies the signature comes out ## identical each time. - var derSig: DERSignature[DERSigSize(Secp256k1)] + var derSig: DerSignature[DerSigSize(Secp256k1)] let secKey = generatePrivateKey() var sig {.noinit.}: Signature From 0bbc8397bff3318764a0bdcef3a7901f32aecb0f Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 1 Jan 2025 18:00:51 +0100 Subject: [PATCH 49/52] replace `toPemFile` by simply `toPem` --- constantine/serialization/codecs_ecdsa_secp256k1.nim | 4 ++-- tests/ecdsa/t_ecdsa_verify_openssl.nim | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/constantine/serialization/codecs_ecdsa_secp256k1.nim b/constantine/serialization/codecs_ecdsa_secp256k1.nim index 9be68f4d9..c92f84b5e 100644 --- a/constantine/serialization/codecs_ecdsa_secp256k1.nim +++ b/constantine/serialization/codecs_ecdsa_secp256k1.nim @@ -128,7 +128,7 @@ proc wrap(s: string, maxLineWidth = 64): string = if i < lines-1: result.add "\n" -proc toPemFile*(publicKey: PublicKey): string = +proc toPem*(publicKey: PublicKey): string = ## Convert a given private key to data in PEM format following SEC1 ## ## RFC 7468 describes the textual encoding of these files: @@ -144,7 +144,7 @@ proc toPemFile*(publicKey: PublicKey): string = result.add der64 & "\n" result.add "-----END PUBLIC KEY-----\n" -proc toPemFile*(privateKey: SecretKey): string = +proc toPem*(privateKey: SecretKey): string = ## XXX: For now using `std/base64` but will need to write base64 encoder ## & add tests for CTT base64 decoder! ## Convert a given private key to data in PEM format following SEC1 diff --git a/tests/ecdsa/t_ecdsa_verify_openssl.nim b/tests/ecdsa/t_ecdsa_verify_openssl.nim index 6cafc3858..ceeca053b 100644 --- a/tests/ecdsa/t_ecdsa_verify_openssl.nim +++ b/tests/ecdsa/t_ecdsa_verify_openssl.nim @@ -123,8 +123,8 @@ proc verifyPemWriter(num: int, msg = "") = let secKey = generatePrivateKey() let pubKey = getPublicKey(secKey) - writeFile(secKeyFile, toPemFile(secKey)) - writeFile(pubKeyFile, toPemFile(pubKey)) + writeFile(secKeyFile, toPem(secKey)) + writeFile(pubKeyFile, toPem(pubKey)) # Write a PEM file for public and private key using CTT and use it # to sign and verify a message. From 20057e089d22e99347ed104314effefee17ac9db Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 1 Jan 2025 18:02:42 +0100 Subject: [PATCH 50/52] rename `common_signature_ops` to `ecc_sig_ops` to clarify that these are only for EC based signatures. --- constantine/signatures/bls_signatures.nim | 4 ++-- .../signatures/{common_signature_ops.nim => ecc_sig_ops.nim} | 0 constantine/signatures/ecdsa.nim | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename constantine/signatures/{common_signature_ops.nim => ecc_sig_ops.nim} (100%) diff --git a/constantine/signatures/bls_signatures.nim b/constantine/signatures/bls_signatures.nim index 91fd28143..1a3ecf2f6 100644 --- a/constantine/signatures/bls_signatures.nim +++ b/constantine/signatures/bls_signatures.nim @@ -16,9 +16,9 @@ import constantine/hash_to_curve/[hash_to_curve, h2c_hash_to_field], constantine/hashes, constantine/platforms/views, - constantine/signatures/common_signature_ops # for `derivePubkey` + constantine/signatures/ecc_sig_ops # for `derivePubkey` -export common_signature_ops +export ecc_sig_ops # ############################################################ # diff --git a/constantine/signatures/common_signature_ops.nim b/constantine/signatures/ecc_sig_ops.nim similarity index 100% rename from constantine/signatures/common_signature_ops.nim rename to constantine/signatures/ecc_sig_ops.nim diff --git a/constantine/signatures/ecdsa.nim b/constantine/signatures/ecdsa.nim index f36128993..63248ab77 100644 --- a/constantine/signatures/ecdsa.nim +++ b/constantine/signatures/ecdsa.nim @@ -17,11 +17,11 @@ import constantine/mac/mac_hmac, # for deterministic nonce generation via RFC 6979 constantine/named/zoo_generators, # for generator constantine/csprngs/sysrand, - constantine/signatures/common_signature_ops # for `derivePubkey` + constantine/signatures/ecc_sig_ops # for `derivePubkey` import std / macros # for `update` convenience helper -export common_signature_ops +export ecc_sig_ops type ## Decides the type of sampler we use for the nonce. By default From 9642ca6938108d470fb2b7afd21a4289fef9ed69 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 1 Jan 2025 18:05:23 +0100 Subject: [PATCH 51/52] [tests] disable ECDSA test for Windows --- tests/ecdsa/t_ecdsa_verify_openssl.nim | 234 +++++++++++++------------ 1 file changed, 119 insertions(+), 115 deletions(-) diff --git a/tests/ecdsa/t_ecdsa_verify_openssl.nim b/tests/ecdsa/t_ecdsa_verify_openssl.nim index ceeca053b..8f52066a4 100644 --- a/tests/ecdsa/t_ecdsa_verify_openssl.nim +++ b/tests/ecdsa/t_ecdsa_verify_openssl.nim @@ -19,7 +19,10 @@ import constantine/platforms/abstractions, constantine/ecdsa_secp256k1 -import ../openssl_wrapper +when not defined(windows): + # Windows (at least in GH actions CI) does not provide, among others `BN_new` + # so we disable this test for Windows for the time being. + import ../openssl_wrapper import std / [os, osproc, strutils, strformat, unittest, importutils] @@ -62,119 +65,120 @@ func getPublicKey(secKey: SecretKey): PublicKey {.noinit.} = template toOA(x: string): untyped = toOpenArrayByte(x, 0, x.len-1) -proc signAndVerify(num: int, msg = "", nonceSampler = nsRandom) = - ## Generates `num` signatures and verify them against OpenSSL. - ## - ## If `msg` is given, use a fixed message. Otherwise will generate a message with - ## a length up to 1024 bytes. - ## - ## As a side effect it also verifies our `fromDER` parser and as an additional - ## sanity check our `toDER` converter. - for i in 0 ..< num: - let msg = if msg.len > 0: msg else: generateMessage(64) # 64 byte long messages - let secKey = generatePrivateKey() - let pubKey = getPublicKey(secKey) - - # Get bytes of private key & initialize an OpenSSL key - var skBytes: array[32, byte] - privateAccess(SecretKey) # access to `raw` - skBytes.toBytes(secKey.raw) - var osSecKey: EVP_PKEY - osSecKey.initPrivateKeyOpenSSL(skBytes) - - # Sign the message using OpenSSL - var osSig: array[72, byte] - osSig.signMessageOpenSSL(msg.toOpenArrayByte(0, msg.len-1), osSecKey) - # Destructure the DER encoded signature into two arrays - var rOSL: array[32, byte] - var sOSL: array[32, byte] - # And turn into hex strings - check fromRawDER(rOSL, sOSL, osSig) - let (r, s) = (rOSL.toHex(), sOSL.toHex()) - # Convert to scalar and verify signature - let (rOslFr, sOslFr) = (Fr[C].fromHex(r), Fr[C].fromHex(s)) - privateAccess(Signature) # make `r`, `s` accessible in scope - let sigOsl = Signature(r: rOslFr, s: sOslFr) - check pubKey.verify(toOA msg, sigOsl) - # Now also sign with CTT and verify - var sigCTT {.noinit.}: Signature - sigCTT.sign(secKey, toOA msg) - check pubKey.verify(toOA msg, sigCTT) - - # Verify that we can generate a DER signature again from the OpenSSL - # data and it is equivalent to original +when not defined(windows): # see above + proc signAndVerify(num: int, msg = "", nonceSampler = nsRandom) = + ## Generates `num` signatures and verify them against OpenSSL. + ## + ## If `msg` is given, use a fixed message. Otherwise will generate a message with + ## a length up to 1024 bytes. + ## + ## As a side effect it also verifies our `fromDER` parser and as an additional + ## sanity check our `toDER` converter. + for i in 0 ..< num: + let msg = if msg.len > 0: msg else: generateMessage(64) # 64 byte long messages + let secKey = generatePrivateKey() + let pubKey = getPublicKey(secKey) + + # Get bytes of private key & initialize an OpenSSL key + var skBytes: array[32, byte] + privateAccess(SecretKey) # access to `raw` + skBytes.toBytes(secKey.raw) + var osSecKey: EVP_PKEY + osSecKey.initPrivateKeyOpenSSL(skBytes) + + # Sign the message using OpenSSL + var osSig: array[72, byte] + osSig.signMessageOpenSSL(msg.toOpenArrayByte(0, msg.len-1), osSecKey) + # Destructure the DER encoded signature into two arrays + var rOSL: array[32, byte] + var sOSL: array[32, byte] + # And turn into hex strings + check fromRawDER(rOSL, sOSL, osSig) + let (r, s) = (rOSL.toHex(), sOSL.toHex()) + # Convert to scalar and verify signature + let (rOslFr, sOslFr) = (Fr[C].fromHex(r), Fr[C].fromHex(s)) + privateAccess(Signature) # make `r`, `s` accessible in scope + let sigOsl = Signature(r: rOslFr, s: sOslFr) + check pubKey.verify(toOA msg, sigOsl) + # Now also sign with CTT and verify + var sigCTT {.noinit.}: Signature + sigCTT.sign(secKey, toOA msg) + check pubKey.verify(toOA msg, sigCTT) + + # Verify that we can generate a DER signature again from the OpenSSL + # data and it is equivalent to original + var derSig: DerSignature[DerSigSize(Secp256k1)] + derSig.toDER(rOslFr, sOslFr) + check derSig.data == osSig + + proc verifyPemWriter(num: int, msg = "") = + ## We verify our PEM writers in a bit of a roundabout way. + ## + ## TODO: Ideally we would simply write a given raw private and public key + ## using the C API of OpenSSL and compare writing the same key using + ## our serialization logic. + let dir = getTempDir() + # temp filename for private key PEM file + let pubKeyFile = dir / "public_key.pem" + let secKeyFile = dir / "private_key.pem" + let sigFile = dir / "msg.sig" + for i in 0 ..< num: + let msg = if msg.len > 0: msg else: generateMessage(64) # 64 byte long messages + let secKey = generatePrivateKey() + let pubKey = getPublicKey(secKey) + + writeFile(secKeyFile, toPem(secKey)) + writeFile(pubKeyFile, toPem(pubKey)) + + # Write a PEM file for public and private key using CTT and use it + # to sign and verify a message. + # NOTE: I tried using OpenSSL's C API, but couldn't get it to work + # 1. Sign using the private key and message + let sign = &"echo -n '{msg}' | openssl dgst -sha256 -sign {secKeyFile} -out {sigFile}" + let (resS, errS) = execCmdEx(sign) + check errS == 0 + + # 2. Verify using public key + let verify = &"echo -n '{msg}' | openssl dgst -sha256 -verify {pubKeyFile} -signature {sigFile}" + let (resV, errV) = execCmdEx(verify) + check errV == 0 + + proc signRfc6979(msg: string, num = 10) = + ## Signs the given message with a randomly generated private key `num` times + ## using deterministic nonce generation and verifies the signature comes out + ## identical each time. var derSig: DerSignature[DerSigSize(Secp256k1)] - derSig.toDER(rOslFr, sOslFr) - check derSig.data == osSig - -proc verifyPemWriter(num: int, msg = "") = - ## We verify our PEM writers in a bit of a roundabout way. - ## - ## TODO: Ideally we would simply write a given raw private and public key - ## using the C API of OpenSSL and compare writing the same key using - ## our serialization logic. - let dir = getTempDir() - # temp filename for private key PEM file - let pubKeyFile = dir / "public_key.pem" - let secKeyFile = dir / "private_key.pem" - let sigFile = dir / "msg.sig" - for i in 0 ..< num: - let msg = if msg.len > 0: msg else: generateMessage(64) # 64 byte long messages + let secKey = generatePrivateKey() - let pubKey = getPublicKey(secKey) - - writeFile(secKeyFile, toPem(secKey)) - writeFile(pubKeyFile, toPem(pubKey)) - - # Write a PEM file for public and private key using CTT and use it - # to sign and verify a message. - # NOTE: I tried using OpenSSL's C API, but couldn't get it to work - # 1. Sign using the private key and message - let sign = &"echo -n '{msg}' | openssl dgst -sha256 -sign {secKeyFile} -out {sigFile}" - let (resS, errS) = execCmdEx(sign) - check errS == 0 - - # 2. Verify using public key - let verify = &"echo -n '{msg}' | openssl dgst -sha256 -verify {pubKeyFile} -signature {sigFile}" - let (resV, errV) = execCmdEx(verify) - check errV == 0 - -proc signRfc6979(msg: string, num = 10) = - ## Signs the given message with a randomly generated private key `num` times - ## using deterministic nonce generation and verifies the signature comes out - ## identical each time. - var derSig: DerSignature[DerSigSize(Secp256k1)] - - let secKey = generatePrivateKey() - var sig {.noinit.}: Signature - sig.sign(secKey, toOA msg, nonceSampler = nsRfc6979) - for i in 0 ..< num: - var sig2 {.noinit.}: Signature - sig2.sign(secKey, toOA msg, nonceSampler = nsRfc6979) - check signatures_are_equal(sig, sig2) - -suite "General ECDSA related tests": - test "DERSigSize correctly computes maximum size of DER encoded signature": - # Check that `DerSigSize` correctly computes the maximum DER encoded signature - # based on the size of the scalar - check DerSigSize(Secp256k1) == 72 # 256 bit subgroup order -> 32 byte scalars - check DerSigSize(P256) == 72 # 256 bit subgroup order - check DerSigSize(Edwards25519) == 72 # 253 bits subgroup order, fits 256 bit BigInt - check DerSigSize(BLS12_381) == 72 # not commonly used, but larger modulo but *same* subgroup order - check DerSigSize(P224) == 64 # 224 bit subgroup order -> 28 byte scalars - check DerSigSize(BW6_761) == 104 # not commonly used, but larger modulo with *larger* subgroup order - # 377 bit subgroup order -> 384 BigInt -> 48 byte scalars - -suite "ECDSA over secp256k1": - test "Verify OpenSSL generated signatures from a fixed message (different nonces)": - signAndVerify(100, "Hello, Constantine!") # fixed message - - test "Verify OpenSSL generated signatures for different messages": - signAndVerify(100) # randomly generated message - - test "Verify deterministic nonce generation via RFC6979 yields deterministic signatures": - signRfc6979("Hello, Constantine!") - signRfc6979("Foobar is 42") - - test "Verify PEM file serialization for public and private keys": - verifyPemWriter(100) + var sig {.noinit.}: Signature + sig.sign(secKey, toOA msg, nonceSampler = nsRfc6979) + for i in 0 ..< num: + var sig2 {.noinit.}: Signature + sig2.sign(secKey, toOA msg, nonceSampler = nsRfc6979) + check signatures_are_equal(sig, sig2) + + suite "General ECDSA related tests": + test "DERSigSize correctly computes maximum size of DER encoded signature": + # Check that `DerSigSize` correctly computes the maximum DER encoded signature + # based on the size of the scalar + check DerSigSize(Secp256k1) == 72 # 256 bit subgroup order -> 32 byte scalars + check DerSigSize(P256) == 72 # 256 bit subgroup order + check DerSigSize(Edwards25519) == 72 # 253 bits subgroup order, fits 256 bit BigInt + check DerSigSize(BLS12_381) == 72 # not commonly used, but larger modulo but *same* subgroup order + check DerSigSize(P224) == 64 # 224 bit subgroup order -> 28 byte scalars + check DerSigSize(BW6_761) == 104 # not commonly used, but larger modulo with *larger* subgroup order + # 377 bit subgroup order -> 384 BigInt -> 48 byte scalars + + suite "ECDSA over secp256k1": + test "Verify OpenSSL generated signatures from a fixed message (different nonces)": + signAndVerify(100, "Hello, Constantine!") # fixed message + + test "Verify OpenSSL generated signatures for different messages": + signAndVerify(100) # randomly generated message + + test "Verify deterministic nonce generation via RFC6979 yields deterministic signatures": + signRfc6979("Hello, Constantine!") + signRfc6979("Foobar is 42") + + test "Verify PEM file serialization for public and private keys": + verifyPemWriter(100) From 2fd2bb230c79389185c696972d80a1354e08c2dc Mon Sep 17 00:00:00 2001 From: Vindaar Date: Sat, 4 Jan 2025 17:51:29 +0100 Subject: [PATCH 52/52] [ecdsa] avoid awkward arrayWith declaration & call --- constantine/signatures/ecdsa.nim | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/constantine/signatures/ecdsa.nim b/constantine/signatures/ecdsa.nim index 63248ab77..088c8845c 100644 --- a/constantine/signatures/ecdsa.nim +++ b/constantine/signatures/ecdsa.nim @@ -83,9 +83,9 @@ proc randomFieldElement[FF](): FF = result.fromBig(b) -proc arrayWith[N: static int](res: var array[N, byte], val: byte) = +proc byteArrayWith(N: static int, val: byte): array[N, byte] {.noinit, inline.} = for i in 0 ..< N: - res[i] = val + result[i] = val macro update[T](hmac: var HMAC[T], args: varargs[untyped]): untyped = ## Mini helper to allow HMAC to act on multiple arguments in succession @@ -126,9 +126,9 @@ proc nonceRfc6979[Name: static Algebra]( # Initial values # Step b: `V = 0x01 0x01 0x01 ... 0x01` - var v: array[N, byte]; v.arrayWith(byte 0x01) + var v = byteArrayWith(N, byte 0x01) # Step c: `K = 0x00 0x00 0x00 ... 0x00` - var k: array[N, byte]; k.arrayWith(byte 0x00) + var k = byteArrayWith(N, byte 0x00) # Create HMAC contexts var hmac {.noinit.}: HMAC[H]