mirror of
https://github.com/status-im/nim-eth-keys.git
synced 2025-02-08 15:34:40 +00:00
Libsecp256k1 backend (#2)
* Add safe hex, endianness and bytes conversion tools * comment typo endianess -> endianness * Add libsecp256k1 - private and public keygen and serialization + tests
This commit is contained in:
parent
33b9df4c83
commit
d3df9f5055
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,6 +1,9 @@
|
||||
nimcache/
|
||||
|
||||
# Executables shall be put in an ignored build/ directory
|
||||
# Ignore dynamic, static libs and libtool archive files
|
||||
build/
|
||||
*.so
|
||||
*.dylib
|
||||
*.dylib
|
||||
*.a
|
||||
*.la
|
@ -6,7 +6,8 @@ license = "MIT"
|
||||
srcDir = "src"
|
||||
|
||||
### Dependencies
|
||||
requires "nim >= 0.17.2", "keccak_tiny >= 0.1.0", "ttmath >= 0.1.0", "nimSHA2"
|
||||
|
||||
requires "nim >= 0.17.2", "keccak_tiny >= 0.1.0", "ttmath >= 0.1.0", "nimSHA2", "secp256k1"
|
||||
|
||||
proc test(name: string, lang: string = "cpp") =
|
||||
if not dirExists "build":
|
||||
|
53
src/backend_libsecp256k1/libsecp256k1.nim
Normal file
53
src/backend_libsecp256k1/libsecp256k1.nim
Normal file
@ -0,0 +1,53 @@
|
||||
import ../datatypes
|
||||
import secp256k1
|
||||
|
||||
const SECP256K1_CONTEXT_ALL = SECP256K1_CONTEXT_VERIFY or SECP256K1_CONTEXT_SIGN
|
||||
|
||||
let ctx = secp256k1_context_create(SECP256K1_CONTEXT_ALL)
|
||||
|
||||
{.experimental.}
|
||||
proc `=destroy`(ctx: ptr secp256k1_context) =
|
||||
if not ctx.isNil:
|
||||
ctx.secp256k1_context_destroy
|
||||
|
||||
type Serialized_PubKey = ByteArrayBE[65]
|
||||
# header 0x04 (uncompressed) + 128 hex char
|
||||
|
||||
proc asPtrPubKey(key: PublicKey): ptr secp256k1_pubkey =
|
||||
cast[ptr secp256k1_pubkey](unsafeAddr key.raw_key)
|
||||
|
||||
proc asPtrCuchar(key: PrivateKey): ptr cuchar =
|
||||
cast[ptr cuchar](unsafeAddr key.raw_key)
|
||||
|
||||
proc asPtrCuchar(key: Serialized_PubKey): ptr cuchar =
|
||||
cast[ptr cuchar](unsafeAddr key)
|
||||
|
||||
proc private_key_to_public_key*(key: PrivateKey): PublicKey {.noInit.}=
|
||||
|
||||
let valid:bool = bool secp256k1_ec_pubkey_create(
|
||||
ctx,
|
||||
result.asPtrPubKey,
|
||||
key.asPtrCuchar
|
||||
)
|
||||
|
||||
if not valid:
|
||||
raise newException(ValueError, "Private key is invalid")
|
||||
|
||||
proc serialize*(key: PublicKey): string =
|
||||
|
||||
var
|
||||
tmp{.noInit.}: Serialized_PubKey
|
||||
tmp_len: csize = 65
|
||||
|
||||
# Proc always return 1
|
||||
discard secp256k1_ec_pubkey_serialize(
|
||||
ctx,
|
||||
tmp.asPtrCuchar,
|
||||
addr tmp_len,
|
||||
key.asPtrPubKey,
|
||||
SECP256K1_EC_UNCOMPRESSED
|
||||
)
|
||||
|
||||
assert tmp_len == 65 # header 0x04 (uncompressed) + 128 hex char
|
||||
|
||||
result = tmp.toHex
|
@ -1,14 +1,15 @@
|
||||
# Copyright (c) 2018 Status Research & Development GmbH
|
||||
# Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT).
|
||||
|
||||
import strutils, ttmath
|
||||
import ttmath, ./private/lowlevel_types
|
||||
export lowlevel_types
|
||||
|
||||
type
|
||||
PublicKey* = object
|
||||
raw_key*: array[2, UInt256]
|
||||
raw_key*: ByteArrayBE[64]
|
||||
|
||||
PrivateKey* = object
|
||||
raw_key*: UInt256
|
||||
raw_key*: ByteArrayBE[32]
|
||||
public_key*: PublicKey
|
||||
|
||||
BaseKey* = PrivateKey|PublicKey
|
||||
|
@ -7,57 +7,50 @@
|
||||
# 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 ./datatypes,
|
||||
ttmath
|
||||
# import keccak_tiny
|
||||
|
||||
import ./backend_native/ecdsa
|
||||
when defined(backend_native):
|
||||
import ./backend_native/ecdsa
|
||||
else:
|
||||
import ./backend_libsecp256k1/libsecp256k1
|
||||
export libsecp256k1.serialize
|
||||
|
||||
# ################################
|
||||
# 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)
|
||||
proc initPrivateKey*(hexString: string): PrivateKey {.noInit.}=
|
||||
result.raw_key = hexToByteArrayBE[32](hexString)
|
||||
result.public_key = private_key_to_public_key(result)
|
||||
|
||||
# ################################
|
||||
# Hex
|
||||
proc toHex*(key: PrivateKey): string =
|
||||
result = key.raw_key.toHex
|
||||
# proc initPublicKey*(hexString: string): PublicKey {.noInit, noSideEffect.}=
|
||||
# result.raw_key = hexToByteArrayBE[64](hexString)
|
||||
|
||||
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)
|
||||
|
||||
# ################################
|
||||
# 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 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_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)
|
||||
|
||||
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)
|
||||
|
||||
# ################################
|
||||
# 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)
|
||||
|
||||
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
|
||||
# # ################################
|
||||
# # Signature interface is a duplicate of the public key interface
|
101
src/private/lowlevel_types.nim
Normal file
101
src/private/lowlevel_types.nim
Normal file
@ -0,0 +1,101 @@
|
||||
# Copyright (c) 2018 Status Research & Development GmbH
|
||||
# Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT).
|
||||
|
||||
import ttmath, strutils, strutils
|
||||
|
||||
# Note on endianness:
|
||||
# - UInt256 uses host endianness
|
||||
# - Libsecp256k1, Ethereum EVM expect Big Endian
|
||||
# https://github.com/ethereum/evmjit/issues/91
|
||||
# - Keccak expects least-significant byte first: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf
|
||||
# Appendix B.1 p37 and outputs a hash with the same endianness as input
|
||||
# http://www.dianacoman.com/2018/02/08/eucrypt-chapter-9-byte-order-and-bit-disorder-in-keccak/
|
||||
# https://www.reddit.com/r/crypto/comments/6287my/explanations_on_the_keccaksha3_paddingbyte/
|
||||
# Note: Since Nim's Keccak-Tiny only accepts string as input, endianness does not matter.
|
||||
|
||||
type ByteArrayBE*[N: static[int]] = distinct array[N, byte]
|
||||
## A byte array that stores bytes in big-endian order
|
||||
|
||||
proc `[]`*[N: static[int], I: Ordinal](ba: ByteArrayBE[N], i: I): byte {.noSideEffect.}=
|
||||
(array[N,byte])(ba)[i]
|
||||
|
||||
proc `[]=`*[N: static[int], I: Ordinal](ba: var ByteArrayBE[N], i: I, val: byte) {.noSideEffect.}=
|
||||
(array[N,byte])(ba)[i] = val
|
||||
|
||||
proc readUint256BE*(ba: ByteArrayBE[32]): UInt256 {.noSideEffect.}=
|
||||
## Convert a big-endian array of Bytes to an UInt256 (in native host endianness)
|
||||
const N = 32
|
||||
for i in 0 ..< N:
|
||||
result = result shl 8 or ba[i].u256
|
||||
|
||||
proc toByteArrayBE*(num: UInt256): ByteArrayBE[32] {.noSideEffect, noInit.}=
|
||||
## Convert an UInt256 (in native host endianness) to a big-endian byte array
|
||||
const N = 32
|
||||
for i in 0 ..< N:
|
||||
result[i] = byte getUInt(num shr uint((N-1-i) * 8))
|
||||
|
||||
proc readHexChar(c: char): byte {.noSideEffect.}=
|
||||
## Converts an hex char to a byte
|
||||
case c
|
||||
of '0'..'9': result = byte(ord(c) - ord('0'))
|
||||
of 'a'..'f': result = byte(ord(c) - ord('a') + 10)
|
||||
of 'A'..'F': result = byte(ord(c) - ord('A') + 10)
|
||||
else:
|
||||
raise newException(ValueError, $c & "is not a hexademical character")
|
||||
|
||||
proc hexToByteArrayBE*[N: static[int]](hexStr: string): ByteArrayBE[N] {.noSideEffect, noInit.}=
|
||||
## Read an hex string and store it in a Byte Array in Big-Endian order
|
||||
var i = 0
|
||||
if hexStr[i] == '0' and (hexStr[i+1] == 'x' or hexStr[i+1] == 'X'):
|
||||
inc(i, 2) # Ignore 0x and 0X prefix
|
||||
|
||||
assert hexStr.len - i == 2*N
|
||||
|
||||
while i < N:
|
||||
result[i] = hexStr[2*i].readHexChar shl 4 or hexStr[2*i+1].readHexChar
|
||||
inc(i)
|
||||
|
||||
proc hexToUInt256*(hexStr: string): UInt256 {.noSideEffect.}=
|
||||
## Read an hex string and store it in a UInt256
|
||||
const N = 32
|
||||
|
||||
var i = 0
|
||||
if hexStr[i] == '0' and (hexStr[i+1] == 'x' or hexStr[i+1] == 'X'):
|
||||
inc(i, 2) # Ignore 0x and 0X prefix
|
||||
|
||||
assert hexStr.len - i == 2*N
|
||||
|
||||
while i < 2*N:
|
||||
result = result shl 4 or hexStr[i].readHexChar.uint.u256
|
||||
inc(i)
|
||||
|
||||
proc toHex*(n: UInt256): string {.noSideEffect.}=
|
||||
## Convert uint256 to its hex representation
|
||||
## Output is in lowercase
|
||||
|
||||
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
|
||||
|
||||
proc toHex*[N: static[int]](ba: ByteArrayBE[N]): string {.noSideEffect.}=
|
||||
## Convert a big-endian byte-array to its hex representation
|
||||
## Output is in lowercase
|
||||
##
|
||||
## Warning ⚠: Do not use toHex for hex representation of Public Keys
|
||||
## Use the ``serialize`` proc:
|
||||
## - PublicKey is actually 2 separate numbers corresponding to coordinate on elliptic curve
|
||||
## - It is resistant against timing attack
|
||||
|
||||
const hexChars = "0123456789abcdef"
|
||||
|
||||
result = newString(2*N)
|
||||
for i in 0 ..< N:
|
||||
result[2*i] = hexChars[ba[i] shr 4 and 0xF]
|
||||
result[2*i+1] = hexChars[ba[i] and 0xF]
|
@ -1,4 +1,6 @@
|
||||
# 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
|
||||
import ./test_hex_bytes_conversion,
|
||||
./test_private_public_key_consistency
|
||||
#./test_key_and_signature_datastructures
|
||||
|
@ -28,14 +28,14 @@ import ttmath
|
||||
|
||||
type
|
||||
testKeySig* = object
|
||||
privkey*: PrivateKey
|
||||
pubkey*: PublicKey
|
||||
privkey*: string
|
||||
pubkey*: string
|
||||
raw_sig*: Signature
|
||||
|
||||
let
|
||||
alice* = testKeySig(
|
||||
privkey: initPrivateKey("9c0257114eb9399a2985f8e75dad7600c5d89fe3824ffa99ec1c3eb8bf3b0501"),
|
||||
pubkey: initPublicKey("5eed5fa3a67696c334762bb4823e585e2ee579aba3558d9955296d6c04541b426078dbd48d74af1fd0c72aa1a05147cf17be6b60bdbed6ba19b08ec28445b0ca"),
|
||||
privkey: "9c0257114eb9399a2985f8e75dad7600c5d89fe3824ffa99ec1c3eb8bf3b0501",
|
||||
pubkey: "5eed5fa3a67696c334762bb4823e585e2ee579aba3558d9955296d6c04541b426078dbd48d74af1fd0c72aa1a05147cf17be6b60bdbed6ba19b08ec28445b0ca",
|
||||
raw_sig: Signature(
|
||||
v: 1,
|
||||
r: "80536744857756143861726945576089915884233437828013729338039544043241440681784".u256,
|
||||
@ -44,8 +44,8 @@ let
|
||||
)
|
||||
|
||||
bob* = testKeySig(
|
||||
privkey: initPrivateKey("38e47a7b719dce63662aeaf43440326f551b8a7ee198cee35cb5d517f2d296a2"),
|
||||
pubkey: initPublicKey("347746ccb908e583927285fa4bd202f08e2f82f09c920233d89c47c79e48f937d049130e3d1c14cf7b21afefc057f71da73dec8e8ff74ff47dc6a574ccd5d570"),
|
||||
privkey: "38e47a7b719dce63662aeaf43440326f551b8a7ee198cee35cb5d517f2d296a2",
|
||||
pubkey: "347746ccb908e583927285fa4bd202f08e2f82f09c920233d89c47c79e48f937d049130e3d1c14cf7b21afefc057f71da73dec8e8ff74ff47dc6a574ccd5d570",
|
||||
raw_sig: Signature(
|
||||
v: 1,
|
||||
r: "41741612198399299636429810387160790514780876799439767175315078161978521003886".u256,
|
||||
@ -54,8 +54,8 @@ let
|
||||
)
|
||||
|
||||
eve* = testKeySig(
|
||||
privkey: initPrivateKey("876be0999ed9b7fc26f1b270903ef7b0c35291f89407903270fea611c85f515c"),
|
||||
pubkey: initPublicKey("c06641f0d04f64dba13eac9e52999f2d10a1ff0ca68975716b6583dee0318d91e7c2aed363ed22edeba2215b03f6237184833fd7d4ad65f75c2c1d5ea0abecc0"),
|
||||
privkey: "876be0999ed9b7fc26f1b270903ef7b0c35291f89407903270fea611c85f515c",
|
||||
pubkey: "c06641f0d04f64dba13eac9e52999f2d10a1ff0ca68975716b6583dee0318d91e7c2aed363ed22edeba2215b03f6237184833fd7d4ad65f75c2c1d5ea0abecc0",
|
||||
raw_sig: Signature(
|
||||
v: 0,
|
||||
r: "84467545608142925331782333363288012579669270632210954476013542647119929595395".u256,
|
||||
|
23
tests/test_hex_bytes_conversion.nim
Normal file
23
tests/test_hex_bytes_conversion.nim
Normal file
@ -0,0 +1,23 @@
|
||||
# Copyright (c) 2018 Status Research & Development GmbH
|
||||
# Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT).
|
||||
|
||||
import ../src/private/lowlevel_types
|
||||
import unittest, ttmath, strutils
|
||||
|
||||
|
||||
suite "Testing conversion functions: Hex, Bytes, Endianness":
|
||||
let
|
||||
SECPK1_N_HEX = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141".toLowerAscii
|
||||
SECPK1_N = "115792089237316195423570985008687907852837564279074904382605163141518161494337".u256
|
||||
|
||||
test "hex -> uint256":
|
||||
check: SECPK1_N_HEX.hexToUInt256 == SECPK1_N
|
||||
|
||||
test "uint256 -> hex":
|
||||
check: SECPK1_N.toHex == SECPK1_N_HEX
|
||||
|
||||
test "hex -> big-endian array -> uint256":
|
||||
check: hexToByteArrayBE[32](SECPK1_N_HEX).readUint256BE == SECPK1_N
|
||||
|
||||
test "uint256 -> big-endian array -> hex":
|
||||
check: SECPK1_N.toByteArrayBE.toHex == SECPK1_N_HEX
|
16
tests/test_private_public_key_consistency.nim
Normal file
16
tests/test_private_public_key_consistency.nim
Normal file
@ -0,0 +1,16 @@
|
||||
# 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
|
||||
|
||||
suite "Testing private -> public key conversion":
|
||||
test "Known private to known public keys (test data from Ethereum eth-keys)":
|
||||
for person in [alice, bob, eve]:
|
||||
let privkey = initPrivateKey(person.privkey)
|
||||
|
||||
let computed_pubkey = privkey.public_key.serialize
|
||||
|
||||
check: computed_pubkey == "04" & person.pubkey # Serialization prefixes uncompressed public keys with 04
|
Loading…
x
Reference in New Issue
Block a user