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:
Mamy Ratsimbazafy 2018-02-14 16:24:29 +01:00 committed by GitHub
parent 33b9df4c83
commit d3df9f5055
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 247 additions and 54 deletions

5
.gitignore vendored
View File

@ -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

View File

@ -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":

View 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

View File

@ -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

View File

@ -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

View 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]

View File

@ -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

View File

@ -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,

View 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

View 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