mirror of
https://github.com/status-im/nim-eth-keys.git
synced 2025-02-13 01:36:38 +00:00
Add message signing + tests
This commit is contained in:
parent
4ee4227eec
commit
ea6316d5ce
85
src/backend_libsecp256k1/ecdsa_recovery_wrapper.nim
Normal file
85
src/backend_libsecp256k1/ecdsa_recovery_wrapper.nim
Normal 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)
|
||||
#
|
@ -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.")
|
||||
|
@ -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]
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
)
|
||||
)
|
||||
|
@ -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
|
Loading…
x
Reference in New Issue
Block a user