[WIP] add native ECDSA backend implementation (⚠ to be debugged)
* Add jacobian primitives * Add ECDSA algos * Implement ECDSA, HMAC crypto (to be cleaned up) * [WIP] test suite * Fix arrayOfBytes <-> UInt256 casting issue * ecdsa_raw_sign: Fix shadowing result which lead to implicit object field construction requires a .partial object * Fix casting + remove tests covered by ranged type * Fix toHex conversion and add first test (failing) * Fix modular inversion for unsigned ints * Add modulo template * Public key generation bug still to hunt.
This commit is contained in:
parent
326e44dd17
commit
33b9df4c83
|
@ -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
|
|
@ -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"
|
||||
|
||||
task test, "Run all tests":
|
||||
test "all_tests"
|
|
@ -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
|
|
@ -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]
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
)
|
||||
)
|
|
@ -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)
|
Loading…
Reference in New Issue