Add message signing + tests

This commit is contained in:
mratsim 2018-02-14 19:50:36 +01:00
parent 4ee4227eec
commit ea6316d5ce
8 changed files with 179 additions and 42 deletions

View File

@ -0,0 +1,85 @@
# Copyright (c) 2018 Status Research & Development GmbH
# Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT).
# To delete when available in nim-libsecp256k1
# https://github.com/status-im/nim-secp256k1
{.deadCodeElim: on.}
import secp256k1
when defined(windows):
const Lib = "secp256k1.dll"
elif defined(macosx):
const Lib = "libsecp256k1(|.0).dylib"
else:
const Lib = "libsecp256k1.so(|.0)"
when defined(static_secp256k1):
{.pragma: secp, importc, cdecl.}
else:
{.pragma: secp, dynlib: Lib, importc, cdecl.}
type
secp256k1_ecdsa_recoverable_signature* = object
## Opaque data structured that holds a parsed ECDSA signature,
## supporting pubkey recovery.
## The exact representation of data inside is implementation defined and not
## guaranteed to be portable between different platforms or versions. It is
## however guaranteed to be 65 bytes in size, and can be safely copied/moved.
## If you need to convert to a format suitable for storage or transmission, use
## the secp256k1_ecdsa_signature_serialize_* and
## secp256k1_ecdsa_signature_parse_* functions.
## Furthermore, it is guaranteed that identical signatures (including their
## recoverability) will have identical representation, so they can be
## memcmp'ed.
data*: array[65, uint8]
proc secp256k1_ecdsa_sign_recoverable*(
ctx: ptr secp256k1_context;
sig: ptr secp256k1_ecdsa_recoverable_signature;
msg32: ptr cuchar;
seckey: ptr cuchar;
noncefp: secp256k1_nonce_function;
ndata: pointer): cint {.secp.}
## Create a recoverable ECDSA signature.
##
## Returns: 1: signature created
## 0: the nonce generation function failed, or the private key was invalid.
## Args: ctx: pointer to a context object, initialized for signing (cannot be NULL)
## Out: sig: pointer to an array where the signature will be placed (cannot be NULL)
## In: msg32: the 32-byte message hash being signed (cannot be NULL)
## seckey: pointer to a 32-byte secret key (cannot be NULL)
## noncefp:pointer to a nonce generation function. If NULL, secp256k1_nonce_function_default is used
## ndata: pointer to arbitrary data used by the nonce generation function (can be NULL)
##
proc secp256k1_ecdsa_recover*(
ctx: ptr secp256k1_context;
pubkey: ptr secp256k1_pubkey;
sig: ptr secp256k1_ecdsa_recoverable_signature;
msg32: ptr cuchar): cint {.secp.}
## Recover an ECDSA public key from a signature.
##
## Returns: 1: public key successfully recovered (which guarantees a correct signature).
## 0: otherwise.
## Args: ctx: pointer to a context object, initialized for verification (cannot be NULL)
## Out: pubkey: pointer to the recovered public key (cannot be NULL)
## In: sig: pointer to initialized signature that supports pubkey recovery (cannot be NULL)
## msg32: the 32-byte message hash assumed to be signed (cannot be NULL)
##
proc secp256k1_ecdsa_recoverable_signature_serialize_compact*(
ctx: ptr secp256k1_context;
output64: ptr cuchar;
recid: ptr cint;
sig: ptr secp256k1_ecdsa_recoverable_signature): cint {.secp.}
# Serialize an ECDSA signature in compact format (64 bytes + recovery id).
#
# Returns: 1
# Args: ctx: a secp256k1 context object
# Out: output64: a pointer to a 64-byte array of the compact signature (cannot be NULL)
# recid: a pointer to an integer to hold the recovery id (can be NULL).
# In: sig: a pointer to an initialized signature object (cannot be NULL)
#

View File

@ -1,5 +1,8 @@
import ../datatypes
import secp256k1
# Copyright (c) 2018 Status Research & Development GmbH
# Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT).
import ../datatypes, ./ecdsa_recovery_wrapper
import secp256k1, keccak_tiny
const SECP256K1_CONTEXT_ALL = SECP256K1_CONTEXT_VERIFY or SECP256K1_CONTEXT_SIGN
@ -10,8 +13,8 @@ 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
type
Serialized_PubKey = ByteArrayBE[65]
proc asPtrPubKey(key: PublicKey): ptr secp256k1_pubkey =
cast[ptr secp256k1_pubkey](unsafeAddr key.raw_key)
@ -22,19 +25,25 @@ proc asPtrCuchar(key: PrivateKey): ptr cuchar =
proc asPtrCuchar(key: Serialized_PubKey): ptr cuchar =
cast[ptr cuchar](unsafeAddr key)
proc private_key_to_public_key*(key: PrivateKey): PublicKey {.noInit.}=
proc asPtrCuchar(msg_hash: Hash[256]): ptr cuchar =
cast[ptr cuchar](unsafeAddr msg_hash)
let valid:bool = bool secp256k1_ec_pubkey_create(
proc asPtrRecoverableSignature(sig: Signature): ptr secp256k1_ecdsa_recoverable_signature =
cast[ptr secp256k1_ecdsa_recoverable_signature](unsafeAddr sig)
proc private_key_to_public_key*(key: PrivateKey): PublicKey {.noInit.}=
## Generates a public key from the private key
let success:bool = bool secp256k1_ec_pubkey_create(
ctx,
result.asPtrPubKey,
key.asPtrCuchar
)
if not valid:
if not success:
raise newException(ValueError, "Private key is invalid")
proc serialize*(key: PublicKey): string =
## Exports a publicKey to a hex string
var
tmp{.noInit.}: Serialized_PubKey
tmp_len: csize = 65
@ -51,3 +60,22 @@ proc serialize*(key: PublicKey): string =
assert tmp_len == 65 # header 0x04 (uncompressed) + 128 hex char
result = tmp.toHex
proc ecdsa_sign*(key: PrivateKey, msg_hash: Hash[256]): Signature {.noInit.}=
## Sign a message with a recoverable signature
## Input:
## - A message encoded with keccak_256
## Output:
## - A recoverable signature
let success:bool = bool secp256k1_ecdsa_sign_recoverable(
ctx,
result.asPtrRecoverableSignature,
msg_hash.asPtrCuchar,
key.asPtrCuchar,
nil, # Nonce function, default is RFC6979 (HMAC-SHA256)
nil # Arbitrary data for the nonce function
)
if not success:
raise newException(ValueError, "The nonce generation function failed, or the private key was invalid.")

View File

@ -1,8 +1,10 @@
# Copyright (c) 2018 Status Research & Development GmbH
# Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT).
import ttmath, ./private/lowlevel_types
export lowlevel_types
import ./private/lowlevel_types
import ttmath
export lowlevel_types, ttmath
type
PublicKey* = object
@ -14,7 +16,7 @@ type
BaseKey* = PrivateKey|PublicKey
Signature* = object
v*: range[0.uint8 .. 1.uint8]
Signature* {.packed.}= object
r*: UInt256
s*: UInt256
v*: range[0.byte .. 1.byte]

View File

@ -7,9 +7,9 @@
# Note: for now only a native pure Nim backend is supported
# In the future alternative, proven crypto backend will be added like libsecpk1
import ./datatypes,
ttmath
# import keccak_tiny
import ./datatypes
import keccak_tiny
when defined(backend_native):
import ./backend_native/ecdsa
@ -48,9 +48,12 @@ proc initPrivateKey*(hexString: string): PrivateKey {.noInit.}=
# 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_sign(key, message_hash)
proc sign_msg*(key: PrivateKey, message_hash: Hash[256]): Signature {.inline.} =
ecdsa_sign(key, message_hash)
# # ################################
# # Signature interface is a duplicate of the public key interface

View File

@ -19,6 +19,9 @@ type ByteArrayBE*[N: static[int]] = distinct array[N, byte]
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): var 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
@ -55,6 +58,19 @@ proc hexToByteArrayBE*[N: static[int]](hexStr: string): ByteArrayBE[N] {.noSideE
result[i] = hexStr[2*i].readHexChar shl 4 or hexStr[2*i+1].readHexChar
inc(i)
proc hexToSeqByteBE*(hexStr: string): seq[byte] {.noSideEffect.}=
## Read an hex string and store it in a sequence of bytes 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
let N = (hexStr.len - i) div 2
result = newSeq[byte](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

View File

@ -2,5 +2,5 @@
# Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT).
import ./test_hex_bytes_conversion,
./test_private_public_key_consistency
#./test_key_and_signature_datastructures
./test_private_public_key_consistency,
./test_key_and_signature_datastructures

View File

@ -1,9 +1,6 @@
# 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:
@ -26,39 +23,45 @@ import ttmath
# assert crypto.ecdsa_recover(msghash, sig) == pubkey
# """
import keccak_tiny
type
testKeySig* = object
privkey*: string
pubkey*: string
raw_sig*: Signature
raw_sig*: tuple[v: int, r, s: string]
let
MSG* = "message"
MSGHASH* = keccak256(MSG)
let
alice* = testKeySig(
privkey: "9c0257114eb9399a2985f8e75dad7600c5d89fe3824ffa99ec1c3eb8bf3b0501",
pubkey: "5eed5fa3a67696c334762bb4823e585e2ee579aba3558d9955296d6c04541b426078dbd48d74af1fd0c72aa1a05147cf17be6b60bdbed6ba19b08ec28445b0ca",
raw_sig: Signature(
raw_sig: (
v: 1,
r: "80536744857756143861726945576089915884233437828013729338039544043241440681784".u256,
s: "1902566422691403459035240420865094128779958320521066670269403689808757640701".u256
r: "80536744857756143861726945576089915884233437828013729338039544043241440681784",
s: "1902566422691403459035240420865094128779958320521066670269403689808757640701"
)
)
bob* = testKeySig(
privkey: "38e47a7b719dce63662aeaf43440326f551b8a7ee198cee35cb5d517f2d296a2",
pubkey: "347746ccb908e583927285fa4bd202f08e2f82f09c920233d89c47c79e48f937d049130e3d1c14cf7b21afefc057f71da73dec8e8ff74ff47dc6a574ccd5d570",
raw_sig: Signature(
raw_sig: (
v: 1,
r: "41741612198399299636429810387160790514780876799439767175315078161978521003886".u256,
s: "47545396818609319588074484786899049290652725314938191835667190243225814114102".u256
r: "41741612198399299636429810387160790514780876799439767175315078161978521003886",
s: "47545396818609319588074484786899049290652725314938191835667190243225814114102"
)
)
eve* = testKeySig(
privkey: "876be0999ed9b7fc26f1b270903ef7b0c35291f89407903270fea611c85f515c",
pubkey: "c06641f0d04f64dba13eac9e52999f2d10a1ff0ca68975716b6583dee0318d91e7c2aed363ed22edeba2215b03f6237184833fd7d4ad65f75c2c1d5ea0abecc0",
raw_sig: Signature(
raw_sig: (
v: 0,
r: "84467545608142925331782333363288012579669270632210954476013542647119929595395".u256,
s: "43529886636775750164425297556346136250671451061152161143648812009114516499167".u256
r: "84467545608142925331782333363288012579669270632210954476013542647119929595395",
s: "43529886636775750164425297556346136250671451061152161143648812009114516499167"
)
)

View File

@ -4,16 +4,16 @@
import ../src/eth_keys,
./config
import unittest, keccak_tiny
import unittest
let
MSG = "message"
MSGHASH = keccak256(MSG)
suite "Test key and signature datastructures":
test "Signing fromprivate key object":
suite "Test key and signature data structure":
test "Signing from private key object":
for person in [alice, bob, eve]:
let signature = person.privkey.sign_msg(MSG)
let
pk = initPrivateKey(person.privkey)
signature = pk.sign_msg(MSG)
check: verify_msg_hash(person.privkey.public_key, MSGHASH, signature)
check: signature.v == person.raw_sig.v
check: signature.r == person.raw_sig.r.u256
check: signature.s == person.raw_sig.s.u256