diff --git a/README.md b/README.md index 5504fc1..a54841d 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,9 @@ A reimplementation in pure Nim of [eth-keys](https://github.com/ethereum/eth-keys), the common API for Ethereum key operations. +# Experimental + +Warning ⚠: current native backend is a proof of concept, not suitable for production use: + - Future versions will use libsecp256k1 as a cryptographic backend, a proven crypto library. + +DO NOT USE for production \ No newline at end of file diff --git a/nim-eth-keys.nimble b/eth_keys.nimble similarity index 60% rename from nim-eth-keys.nimble rename to eth_keys.nimble index f5d0865..c2ac065 100644 --- a/nim-eth-keys.nimble +++ b/eth_keys.nimble @@ -1,4 +1,4 @@ -packageName = "eth-keys" +packageName = "eth_keys" version = "0.0.1" author = "Status Research & Development GmbH" description = "A reimplementation in pure Nim of eth-keys, the common API for Ethereum key operations." @@ -6,14 +6,17 @@ license = "MIT" srcDir = "src" ### Dependencies -requires "nim >= 0.17.2" +requires "nim >= 0.17.2", "keccak_tiny >= 0.1.0", "ttmath >= 0.1.0", "nimSHA2" -proc test(name: string, lang: string = "c") = +proc test(name: string, lang: string = "cpp") = if not dirExists "build": - mkDir "bin" + mkDir "build" if not dirExists "nimcache": mkDir "nimcache" --run --nimcache: "nimcache" switch("out", ("./build/" & name)) - setCommand lang, "tests/" & name & ".nim" \ No newline at end of file + setCommand lang, "tests/" & name & ".nim" + +task test, "Run all tests": + test "all_tests" \ No newline at end of file diff --git a/src/backend_native/README.md b/src/backend_native/README.md new file mode 100644 index 0000000..a7cde7a --- /dev/null +++ b/src/backend_native/README.md @@ -0,0 +1,6 @@ +# Experimental + +Warning ⚠: this is a proof of concept, not suitable for production use: + - Future versions will use libsecp256k1 as a cryptographic backend, a proven crypto library. + +DO NOT USE for production \ No newline at end of file diff --git a/src/backend_native/constants.nim b/src/backend_native/constants.nim new file mode 100644 index 0000000..54b4738 --- /dev/null +++ b/src/backend_native/constants.nim @@ -0,0 +1,17 @@ +# Copyright (c) 2018 Status Research & Development GmbH +# Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). + +# SECPK1N + +import ttmath + +let + # TODO: Compile-Time Evaluation of those contants + # cf: https://en.bitcoin.it/wiki/Secp256k1 + SECPK1_P* = "115792089237316195423570985008687907853269984665640564039457584007908834671663".u256 + SECPK1_N* = "115792089237316195423570985008687907852837564279074904382605163141518161494337".u256 + SECPK1_A* = 0.u256 + SECPK1_B* = 7.u256 + SECPK1_Gx* = "55066263022277343669578718895168534326250603453777594175500187360389116729240".u256 + SECPK1_Gy* = "32670510020758816978083085130507043184471273380659243275938904335757337482424".u256 + SECPK1_G* = [SECPK1_Gx, SECPK1_Gy] diff --git a/src/backend_native/ecdsa.nim b/src/backend_native/ecdsa.nim new file mode 100644 index 0000000..d315a5a --- /dev/null +++ b/src/backend_native/ecdsa.nim @@ -0,0 +1,93 @@ +# Copyright (c) 2018 Status Research & Development GmbH +# Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). + +import ../datatypes, ../private/[array_utils, casting], + ./jacobian, ./mod_arithmetic, ./hmac, ./constants + +import ttmath, keccak_tiny, strutils, + nimsha2 # TODO: For SHA-256, use OpenSSL instead? (see https://rosettacode.org/wiki/SHA-256#Nim) + +proc private_key_to_public_key*(key: PrivateKey): PublicKey {.noInit.}= + # TODO: allow to switch implementation based on backend + + if key.raw_key >= SECPK1_N: # TODO use ranged type + raise newException(ValueError, "Invalid private key") + + result.raw_key = fast_multiply(SECPK1_G, key.raw_key) + +proc ecdsa_raw_verify*(msg_hash: Hash[256], vrs: Signature, key: PublicKey): bool = + let + w = invmod(vrs.s, SECPK1_N) + z = msg_hash.toUInt256 + + u1 = mulmod(z, w, SECPK1_N) + u2 = mulmod(vrs.r, w, SECPK1_N) + xy = fast_add( + fast_multiply(SECPK1_G, u1), + fast_multiply(key.raw_key, u2) + ) + result = vrs.r == xy[0] and vrs.r.isOdd and vrs.s.isOdd + +proc deterministic_generate_k(msg_hash: Hash[256], key: PrivateKey): UInt256 = + const + v_0 = initArray[32, byte](0x01'u8) + k_0 = initArray[32, byte](0x00'u8) + + let + # TODO: avoid heap allocation + k_1 = k_0.hmac_sha256(@v_0 & @[0x00.byte] & @(toByteArray(key.raw_key)) & @(msg_hash.data)) + v_1 = cast[array[32, byte]](k_1.hmac_sha256(@v_0)) + k_2 = k_1.hmac_sha256(@v_1 & @[0x01.byte] & @(toByteArray(key.raw_key)) & @(msg_hash.data)) + v_2 = k_2.hmac_sha256(@v_1) + + kb = k_2.hmac_sha256(@v_2) + + result = kb.toUInt256 + +proc ecdsa_raw_sign*(msg_hash: Hash[256], key: PrivateKey): Signature = + modulo(SECPK1_N): + let + z = msg_hash.toUInt256 + k = deterministic_generate_k(msg_hash, key) + + ry = fast_multiply(SECPK1_G, k) + s_raw = invmod(k, SECPK1_N) * (z + ry[0] * key.raw_key) + + result.v = uint8 getUint `xor`( + ry[1] mod 2.u256, + if s_raw * 2.u256 < SECPK1_N: 0.u256 else: 1.u256 + ) + result.s = if s_raw * 2.u256 < SECPK1_N: s_raw + else: SECPK1_N - s_raw + result.r = ry[0] + +proc ecdsa_raw_recover*(msg_hash: Hash[256], vrs: Signature): PublicKey {.noInit.} = + modulo(SECPK1_P): + let + x = vrs.r + xcubedaxb = x * x * x + SECPK1_A * x + SECPK1_B + beta = pow(xcubedaxb, (SECPK1_P + 1.u256) div 4.u256) + y = if vrs.v == 0 xor beta.isEven: beta # TODO: precedence rule + else: SECPK1_P - beta + # If xcubedaxb is not a quadratic residue, then r cannot be the x coord + # for a point on the curve, and so the sig is invalid + + if xcubedaxb - y * y != 0.u256 or + not (vrs.r mod SECPK1_N == 1.u256) or + not (vrs.s mod SECPK1_N == 1.u256): + raise newException(ValueError, "Bad signature") + + let + z = msg_hash.toUInt256 + Gz = jacobian_multiply( + [SECPK1_Gx, SECPK1_Gy,1.u256], + submod(SECPK1_N, z, SECPK1_N) + ) + XY = jacobian_multiply( + [SECPK1_Gx, SECPK1_Gy,1.u256], + vrs.s + ) + Qr = jacobian_add(Gz, XY) + Q = jacobian_multiply(Qr, invmod(vrs.r, SECPK1_N)) + + result.raw_key = from_jacobian(Q) \ No newline at end of file diff --git a/src/backend_native/hmac.nim b/src/backend_native/hmac.nim new file mode 100644 index 0000000..6b9a8da --- /dev/null +++ b/src/backend_native/hmac.nim @@ -0,0 +1,40 @@ +# Copyright (c) 2018 Status Research & Development GmbH +# Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). + +# Nim Implementation of HMAC +# https://tools.ietf.org/html/rfc2104.html + +import ../private/array_utils +import nimsha2 # TODO: For SHA-256, use OpenSSL instead? (see https://rosettacode.org/wiki/SHA-256#Nim) + +proc hmac_sha256*[N: static[int]](key: array[N, byte|char], + data: string|seq[byte|char]): SHA256Digest = + # Note: due to https://github.com/nim-lang/Nim/issues/7208 + # blockSize cannot be a compile-time parameter with a default value + const + opad: byte = 0x5c + ipad: byte = 0x36 + blockSize = 64 + + var k, k_ipad{.noInit.}, k_opad{.noInit.}: array[blockSize, byte] + + when N > blockSize: + k[0 ..< 32] = key.computeSHA256 + else: + k[0 ..< N] = cast[array[N, byte]](key) + + for i in 0 ..< blockSize: + k_ipad[i] = k[i] xor ipad + k_opad[i] = k[i] xor opad + + result = computeSHA256($k_opad & $computeSHA256($k_ipad & $data)) + + +when isMainModule: + # From https://en.wikipedia.org/wiki/Hash-based_message_authentication_code + let + key = ['k','e','y'] + data = "The quick brown fox jumps over the lazy dog" + + import strutils + doAssert hmac_sha256(key, data).toHex == "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8".toUpperAscii \ No newline at end of file diff --git a/src/backend_native/jacobian.nim b/src/backend_native/jacobian.nim new file mode 100644 index 0000000..777c430 --- /dev/null +++ b/src/backend_native/jacobian.nim @@ -0,0 +1,77 @@ +# Copyright (c) 2018 Status Research & Development GmbH +# Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). + +import ./constants, ./mod_arithmetic +import ttmath + +proc to_jacobian(p: array[2, UInt256]): array[3, UInt256] {.noInit.}= + [p[0], p[1], 1.u256] + +proc jacobian_double(p: array[3, UInt256]): array[3, UInt256] {.noInit.}= + if p[1] == 0.u256: + return [0.u256, 0.u256, 0.u256] + + modulo(SECPK1_P): + let + ysq = p[1] ** 2.u256 + S = 4.u256 * p[0] * ysq + M = 3.u256 * (p[0] ** 2.u256) + SECPK1_A * (p[2] ** 4.u256) + nx = M ** 2.u256 - 2.u256 * S + ny = M * (S - nx) - 8.u256 * (ysq ** 2.u256) + nz = 2.u256 * p[1] * p[2] + + result = [nx, ny, nz] + +proc jacobian_add*(p, q: array[3, UInt256]): array[3, UInt256] {.noInit.}= + if p[1] == 0.u256: + return q + if q[1] == 0.u256: + return p + + modulo(SECPK1_P): + let + U1 = p[0] * (q[2] ** 2.u256) + U2 = q[0] * (p[2] ** 2.u256) + S1 = p[1] * (q[2] ** 2.u256) + S2 = q[1] * (p[2] ** 2.u256) + + if U1 == U2: + if S1 == S2: + return [0.u256, 0.u256, 1.u256] + return jacobian_double(p) + + modulo(SECPK1_P): + let + H = U2 - U1 + R = S2 - S1 + H2 = H * H + H3 = H * H2 + U1H2 = U1 * H2 + nx = R ** 2.u256 - H3 - 2.u256 * U1H2 + ny = R * (U1H2 - nx) - S1 * H3 + nz = H * p[2] * q[2] + + result = [nx, ny, nz] + +proc from_jacobian*(p: array[3, UInt256]): array[2, UInt256] = + let z = invmod(p[2], SECPK1_P) + modulo(SECPK1_P): + result = [p[0] * (z ** 2.u256), p[1] * (z ** 3.u256)] + +proc jacobian_multiply*(a: array[3, UInt256], n: UInt256): array[3, UInt256] = + if a[1] == 0.u256 or n == 0.u256: + return [0.u256, 0.u256, 1.u256] + elif n == 1.u256: + return a + elif n >= SECPK1_N: # note n cannot be < 0 in Nim + return jacobian_multiply(a, n mod SECPK1_N) + elif n.isEven: + return jacobian_double jacobian_multiply(a, n div 2.u256) + else: # n.isOdd + return jacobian_add(jacobian_double jacobian_multiply(a, n div 2.u256), a) + +proc fast_multiply*(a: array[2, UInt256], n: UInt256): array[2,UInt256] = + return from_jacobian jacobian_multiply(a.to_jacobian, n) + +proc fast_add*(a, b: array[2, UInt256]): array[2, UInt256] = + return from_jacobian jacobian_add(a.to_jacobian, b.to_jacobian) \ No newline at end of file diff --git a/src/backend_native/mod_arithmetic.nim b/src/backend_native/mod_arithmetic.nim new file mode 100644 index 0000000..bf8903c --- /dev/null +++ b/src/backend_native/mod_arithmetic.nim @@ -0,0 +1,158 @@ +# Copyright (c) 2018 Status Research & Development GmbH +# Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). + +import ttmath + +proc isEven*(a: UInt256): bool = + (a and 1.u256) == 0.u256 + +proc isOdd*(a: UInt256): bool = + (a and 1.u256) != 0.u256 + +proc addmod*(a, b, m: UInt256): UInt256 = + ## Modular addition + + let a_m = if a < m: a + else: a mod m + if b == 0.u256: + return a_m + let b_m = if b < m: b + else: b mod m + + # We don't do a + b to avoid overflows + # But we know that m at least is inferior to biggest UInt256 + + let b_from_m = m - b_m + if a_m >= b_from_m: + return a_m - b_from_m + return m - b_from_m + a_m + +proc submod*(a, b, m: UInt256): UInt256 = + ## Modular substraction + + let a_m = if a < m: a + else: a mod m + if b == 0.u256: + return a_m + let b_m = if b < m: b + else: b mod m + + # We don't do a - b to avoid overflows + + if a_m >= b_m: + return a_m - b_m + return m - b_m + a_m + +proc doublemod(a, m: UInt256): UInt256 {.inline.}= + ## double a modulo m. assume a < m + result = a + if a >= m - a: + result -= m + result += a + +proc mulmod*(a, b, m: UInt256): UInt256 = + ## Modular multiplication + + var a_m = if a < m: a + else: a mod m + var b_m = if b < m: b + else: b mod m + + if b_m > a_m: + swap(a_m, b_m) + while b_m > 0.u256: + if b_m.isOdd: + result = addmod(result, a_m, m) + a_m = doublemod(a_m, m) + b_m = b_m shr 1 + +proc expmod*(base, exponent, m: UInt256): UInt256 = + ## Modular exponentiation + + # Formula from applied Cryptography by Bruce Schneier + # function modular_pow(base, exponent, modulus) + # result := 1 + # while exponent > 0 + # if (exponent mod 2 == 1): + # result := (result * base) mod modulus + # exponent := exponent >> 1 + # base = (base * base) mod modulus + # return result + + result = 1.u256 # (exp 0 = 1) + + var e = exponent + var b = base + + while e > 0.u256: + if isOdd e: + result = mulmod(result, b, m) + e = e shr 1 # e div 2 + b = mulmod(b,b,m) + +proc invmod*(a, m: UInt256): UInt256 = + ## Modular multiplication inverse + ## Input: + ## - 2 positive integers a and m + ## Result: + ## - An integer z that solves `az ≡ 1 mod m` + # Adapted from Knuth, The Art of Computer Programming, Vol2 p342 + # and Menezes, Handbook of Applied Cryptography (HAC), p610 + # to avoid requiring signed integers + # http://cacr.uwaterloo.ca/hac/about/chap14.pdf + + # Starting from the binary extended GCD formula (Bezout identity), + # `ax + by = gcd(x,y)` + # with input x,y and outputs a, b, gcd + # We assume a and m are coprimes, i.e. gcd is 1, otherwise no inverse + # `ax + my = 1` + # `ax + my ≡ 1 mod m` + # `ax ≡ 1 mod m`` + # Meaning we can use the Extended Euclid Algorithm + # `ax + by` with + # a = a, x = result, b = m, y = 0 + + var + a = a + x = 1.u256 + b = m + y = 0.u256 + oddIter = true # instead of requiring signed int, we keep track of even/odd iterations which would be in negative + + while b != 0.u256: + let + q = a div b + r = a mod b + t = x + q * y + x = y; y = t; a = b; b = r + oddIter = not oddIter + + if a != 1.u256: + # a now holds the gcd(a, m) and should equal 1 + raise newException(ValueError, "No modular inverse exists") + + if oddIter: + return x + return m - x + +template modulo*(modulus: UInt256, body: untyped): untyped = + # `+`, `*`, `**` and pow will be replaced by their modular version + template `+`(a, b: UInt256): UInt256 = + addmod(a, b, `modulus`) + template `-`(a, b: UInt256): UInt256 = + submod(a, b, `modulus`) + template `*`(a, b: UInt256): UInt256 = + mulmod(a, b, `modulus`) + template `**`(a, b: UInt256): UInt256 = + expmod(a, b, `modulus`) + template pow(a, b: UInt256): UInt256 = + expmod(a, b, `modulus`) + body + +when isMainModule: + # https://www.khanacademy.org/computing/computer-science/cryptography/modarithmetic/a/fast-modular-exponentiation + assert expmod(5.u256, 117.u256, 19.u256) == 1.u256 + assert expmod(3.u256, 1993.u256, 17.u256) == 14.u256 + + assert invmod(42.u256, 2017.u256) == 1969.u256 + assert invmod(271.u256, 383.u256) == 106.u256 # Handbook of Applied Cryptography p610 \ No newline at end of file diff --git a/src/datatypes.nim b/src/datatypes.nim new file mode 100644 index 0000000..44e5e6b --- /dev/null +++ b/src/datatypes.nim @@ -0,0 +1,19 @@ +# Copyright (c) 2018 Status Research & Development GmbH +# Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). + +import strutils, ttmath + +type + PublicKey* = object + raw_key*: array[2, UInt256] + + PrivateKey* = object + raw_key*: UInt256 + public_key*: PublicKey + + BaseKey* = PrivateKey|PublicKey + + Signature* = object + v*: range[0.uint8 .. 1.uint8] + r*: UInt256 + s*: UInt256 diff --git a/src/datatypes_interface.nim b/src/datatypes_interface.nim new file mode 100644 index 0000000..3df6fe0 --- /dev/null +++ b/src/datatypes_interface.nim @@ -0,0 +1,63 @@ +# Copyright (c) 2018 Status Research & Development GmbH +# Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). + +# In Nim this must be in a separate files from datatypes to avoid recursive dependencies +# between datatypes <-> ecdsa + +# Note: for now only a native pure Nim backend is supported +# In the future alternative, proven crypto backend will be added like libsecpk1 + +import ./private/hex, ./datatypes +import keccak_tiny, ttmath + +import ./backend_native/ecdsa + +# ################################ +# Initialization + +proc initPublicKey*(hexString: string): PublicKey = + assert hexString.len == 128 + result.raw_key[0] = hexToUInt256(hexString[0..<64]) + result.raw_key[1] = hexToUInt256(hexString[64..<128]) + +proc initPrivateKey*(hexString: string): PrivateKey = + assert hexString.len == 64 + result.raw_key = hexToUInt256(hexString) + result.public_key = private_key_to_public_key(result) + +# ################################ +# Hex +proc toHex*(key: PrivateKey): string = + result = key.raw_key.toHex + +proc toHex*(key: PublicKey): string = + result = key.raw_key[0].toHex + result.add key.raw_key[1].toHex + +# ################################ +# Public key interface +proc recover_pubkey_from_msg_hash*(message_hash: Hash[256], sig: Signature): PublicKey {.inline.}= + ecdsa_raw_recover(message_hash, sig) + +proc recover_pubkey_from_msg*(message: string, sig: Signature): PublicKey {.inline.}= + let message_hash = keccak_256(message) + result = recover_pubkey_from_msg_hash(message_hash, sig) + +proc verify_msg_hash*(key: PublicKey, message_hash: Hash[256], sig: Signature): bool {.inline.}= + key == ecdsa_raw_recover(message_hash, sig) + +proc verify_msg*(key: PublicKey, message: string, sig: Signature): bool {.inline.} = + let message_hash = keccak_256(message) + key == ecdsa_raw_recover(message_hash, sig) + +# ################################ +# Private key interface +proc sign_msg_hash*(key: PrivateKey, message_hash: Hash[256]): Signature {.inline.}= + ecdsa_raw_sign(message_hash, key) + +proc sign_msg*(key: PrivateKey, message: string): Signature {.inline.} = + let message_hash = keccak_256(message) + ecdsa_raw_sign(message_hash, key) + +# ################################ +# Signature interface is a duplicate of the public key interface \ No newline at end of file diff --git a/src/eth_keys.nim b/src/eth_keys.nim new file mode 100644 index 0000000..ac98243 --- /dev/null +++ b/src/eth_keys.nim @@ -0,0 +1,13 @@ +# Copyright (c) 2018 Status Research & Development GmbH +# Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). + +import ./datatypes, + ./datatypes_interface + + +export datatypes, + datatypes_interface + + +import ttmath +export ttmath \ No newline at end of file diff --git a/src/private/array_utils.nim b/src/private/array_utils.nim new file mode 100644 index 0000000..2a9b606 --- /dev/null +++ b/src/private/array_utils.nim @@ -0,0 +1,18 @@ +# Copyright (c) 2018 Status Research & Development GmbH +# Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). + +import algorithm + +proc initArray*[N: static[int], T](value: T): array[N, T] {.noInit.}= + result.fill(value) + +proc `$`*[N:static[int]](a: array[N, byte]): string = + $(cast[array[N, char]](a)) + +proc `&`*[N1, N2: static[int], T]( + a: array[N1, T], + b: array[N2, T] + ): array[N1 + N2, T] = + ## Array concatenation + result[0 ..< N1] = a + result[N1 ..< N2] = b \ No newline at end of file diff --git a/src/private/casting.nim b/src/private/casting.nim new file mode 100644 index 0000000..80651eb --- /dev/null +++ b/src/private/casting.nim @@ -0,0 +1,12 @@ +# Copyright (c) 2018 Status Research & Development GmbH +# Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). + +import ttmath, keccak_tiny, nimsha2 + +# We can't use Nim cast :/ to avoid copy + +proc toUint256*[T: Hash[256]|array[32, byte|char]](hash: T): UInt256 = + copyMem(addr result, unsafeAddr hash, 32) + +proc toByteArray*(num: UInt256): array[32, byte] = + copyMem(addr result, unsafeAddr num, 32) \ No newline at end of file diff --git a/src/private/hex.nim b/src/private/hex.nim new file mode 100644 index 0000000..c77a096 --- /dev/null +++ b/src/private/hex.nim @@ -0,0 +1,48 @@ +# Copyright (c) 2018 Status Research & Development GmbH +# Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). + +import ttmath, strutils, ./casting + +proc readHexChar(c: char): int = + case c + of '0'..'9': result = ord(c) - ord('0') + of 'a'..'f': result = ord(c) - ord('a') + 10 + of 'A'..'F': result = ord(c) - ord('A') + 10 + else: + raise newException(ValueError, $c & "is not a hexademical character") + +proc hexToByteArray*[N: static[int]](hexStr: string): array[N, byte] {.noSideEffect.}= + var i = 0 + if hexStr[i] == '0' and (hexStr[i+1] == 'x' or hexStr[i+1] == 'X'): + # Ignore 0x and OX + inc(i, 2) + assert hexStr.len - i == 2*N + + while i < N: + result[i] = byte(readHexChar(hexStr[2*i]) shl 4 or readHexChar(hexStr[2*i+1])) + inc(i) + +proc hexToUInt256*(hexStr: string): UInt256 {.noSideEffect.}= + const N = 32 + + var i = 0 + if hexStr[i] == '0' and (hexStr[i+1] == 'x' or hexStr[i+1] == 'X'): + # Ignore 0x and OX + inc(i, 2) + assert hexStr.len - i == 2*N + + while i < 2*N: + result = result shl 4 or readHexChar(hexStr[i]).uint.u256 + inc(i) + +proc toHex*(n: UInt256): string = + var rem = n # reminder to encode + + const + N = 32 # nb of bytes in n + hexChars = "0123456789abcdef" + + result = newString(2*N) + for i in countdown(2*N - 1, 0): + result[i] = hexChars[(rem and 0xF.u256).getUInt.int] + rem = rem shr 4 \ No newline at end of file diff --git a/tests/all_tests.nim b/tests/all_tests.nim new file mode 100644 index 0000000..38d0805 --- /dev/null +++ b/tests/all_tests.nim @@ -0,0 +1,4 @@ +# Copyright (c) 2018 Status Research & Development GmbH +# Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). + +import ./test_key_and_signature_datastructures \ No newline at end of file diff --git a/tests/config.nim b/tests/config.nim new file mode 100644 index 0000000..22dac39 --- /dev/null +++ b/tests/config.nim @@ -0,0 +1,64 @@ +# Copyright (c) 2018 Status Research & Development GmbH +# Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). + +import ../src/eth_keys +import ttmath + +# This is a sample of signatures generated with a known-good implementation of the ECDSA +# algorithm, which we use to test our ECC backends. If necessary, it can be generated from scratch +# with the following code: +# +# """python +# from devp2p import crypto +# from eth_utils import encode_hex +# msg = b'message' +# msghash = crypto.sha3(b'message') +# for secret in ['alice', 'bob', 'eve']: +# print("'{}': dict(".format(secret)) +# privkey = crypto.mk_privkey(secret) +# pubkey = crypto.privtopub(privkey) +# print(" privkey='{}',".format(encode_hex(privkey))) +# print(" pubkey='{}',".format(encode_hex(crypto.privtopub(privkey)))) +# ecc = crypto.ECCx(raw_privkey=privkey) +# sig = ecc.sign(msghash) +# print(" sig='{}',".format(encode_hex(sig))) +# print(" raw_sig='{}')".format(crypto._decode_sig(sig))) +# assert crypto.ecdsa_recover(msghash, sig) == pubkey +# """ + +type + testKeySig* = object + privkey*: PrivateKey + pubkey*: PublicKey + raw_sig*: Signature + +let + alice* = testKeySig( + privkey: initPrivateKey("9c0257114eb9399a2985f8e75dad7600c5d89fe3824ffa99ec1c3eb8bf3b0501"), + pubkey: initPublicKey("5eed5fa3a67696c334762bb4823e585e2ee579aba3558d9955296d6c04541b426078dbd48d74af1fd0c72aa1a05147cf17be6b60bdbed6ba19b08ec28445b0ca"), + raw_sig: Signature( + v: 1, + r: "80536744857756143861726945576089915884233437828013729338039544043241440681784".u256, + s: "1902566422691403459035240420865094128779958320521066670269403689808757640701".u256 + ) + ) + + bob* = testKeySig( + privkey: initPrivateKey("38e47a7b719dce63662aeaf43440326f551b8a7ee198cee35cb5d517f2d296a2"), + pubkey: initPublicKey("347746ccb908e583927285fa4bd202f08e2f82f09c920233d89c47c79e48f937d049130e3d1c14cf7b21afefc057f71da73dec8e8ff74ff47dc6a574ccd5d570"), + raw_sig: Signature( + v: 1, + r: "41741612198399299636429810387160790514780876799439767175315078161978521003886".u256, + s: "47545396818609319588074484786899049290652725314938191835667190243225814114102".u256 + ) + ) + + eve* = testKeySig( + privkey: initPrivateKey("876be0999ed9b7fc26f1b270903ef7b0c35291f89407903270fea611c85f515c"), + pubkey: initPublicKey("c06641f0d04f64dba13eac9e52999f2d10a1ff0ca68975716b6583dee0318d91e7c2aed363ed22edeba2215b03f6237184833fd7d4ad65f75c2c1d5ea0abecc0"), + raw_sig: Signature( + v: 0, + r: "84467545608142925331782333363288012579669270632210954476013542647119929595395".u256, + s: "43529886636775750164425297556346136250671451061152161143648812009114516499167".u256 + ) + ) diff --git a/tests/test_key_and_signature_datastructures.nim b/tests/test_key_and_signature_datastructures.nim new file mode 100644 index 0000000..d383667 --- /dev/null +++ b/tests/test_key_and_signature_datastructures.nim @@ -0,0 +1,19 @@ +# Copyright (c) 2018 Status Research & Development GmbH +# Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). + +import ../src/eth_keys, + ./config + +import unittest, keccak_tiny + +let + MSG = "message" + MSGHASH = keccak256(MSG) + +suite "Test key and signature datastructures": + test "Signing fromprivate key object": + + for person in [alice, bob, eve]: + let signature = person.privkey.sign_msg(MSG) + + check: verify_msg_hash(person.privkey.public_key, MSGHASH, signature) \ No newline at end of file