2018-04-11 15:04:27 +02:00

161 lines
5.4 KiB
Nim

# Nim Eth-keys
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
#
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
#
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import ../datatypes, ../private/conversion_bytes
import secp256k1, nimcrypto
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 = array[65, byte]
proc asPtrPubKey(key: PublicKey): ptr secp256k1_pubkey =
cast[ptr secp256k1_pubkey](unsafeAddr key)
proc asPtrCuchar(key: PrivateKey): ptr cuchar =
cast[ptr cuchar](unsafeAddr key)
proc asPtrCuchar(key: Serialized_PubKey): ptr cuchar =
cast[ptr cuchar](unsafeAddr key)
proc asPtrCuchar(msg_hash: MDigest[256]): ptr cuchar =
cast[ptr cuchar](unsafeAddr msg_hash)
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 success:
raise newException(ValueError, "Private key is invalid")
proc serialize*(s: Signature, output: var openarray[byte], fromIdx: int = 0) =
## Serialize an ECDSA signature in compact format, 65 bytes long
## (64 bytes + recovery id). The output is written starting from `fromIdx`.
assert(output.len - fromIdx >= 65)
var v: cint
discard secp256k1_ecdsa_recoverable_signature_serialize_compact(ctx,
cast[ptr cuchar](addr output[fromIdx]), addr v, s.asPtrRecoverableSignature)
output[fromIdx + 64] = byte(v)
proc parseSignature*(data: openarray[byte], fromIdx: int = 0): Signature =
## Parse a compact ECDSA signature. Bytes [fromIdx .. fromIdx + 63] of `data`
## should contain the signature, byte [fromIdx + 64] should contain the recovery id.
assert(data.len - fromIdx >= 65)
if secp256k1_ecdsa_recoverable_signature_parse_compact(ctx,
result.asPtrRecoverableSignature,
cast[ptr cuchar](unsafeAddr data[fromIdx]),
cint(data[fromIdx + 64])) != 1:
raise newException(ValueError, "Signature data is invalid")
proc serialize*(key: PublicKey, output: var openarray[byte], addPrefix = false) =
## Exports a publicKey to `output` buffer so that it can be
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
if addPrefix:
assert(output.len >= 65)
copyMem(addr output[0], addr tmp[0], 65)
else:
assert(output.len >= 64)
copyMem(addr output[0], addr tmp[1], 64) # Skip the 0x04 prefix
proc toString*(key: PublicKey): string =
var data: array[64, byte]
key.serialize(data)
result = data.toHex
proc toStringWithPrefix*(key: PublicKey): string =
var data: array[65, byte]
key.serialize(data, true)
result = data.toHex
proc serialize*(key: PublicKey): string {.deprecated.} = key.toStringWithPrefix()
proc parsePublicKeyWithPrefix(data: openarray[byte], result: var PublicKey) =
## Parse a variable-length public key into the PublicKey object
if secp256k1_ec_pubkey_parse(ctx, result.asPtrPubKey, cast[ptr cuchar](unsafeAddr data[0]), data.len.csize) != 1:
raise newException(Exception, "Could not parse public key")
proc parsePublicKey*(data: openarray[byte]): PublicKey =
## Parse a variable-length public key into the PublicKey object
case data.len
of 65:
parsePublicKeyWithPrefix(data, result)
of 64:
var tmpData: Serialized_PubKey
copyMem(addr tmpData[1], unsafeAddr data[0], 64)
tmpData[0] = 0x04
parsePublicKeyWithPrefix(tmpData, result)
else: # TODO: Support other lengths
raise newException(Exception, "Wrong public key length")
proc ecdsa_sign*(key: PrivateKey, msg_hash: MDigest[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.")
proc ecdsa_recover*(msg_hash: MDigest[256], sig: Signature): PublicKey =
## Recover the Public Key from the message hash and the signature
let success: bool = bool secp256k1_ecdsa_recover(
ctx,
result.asPtrPubKey,
sig.asPtrRecoverableSignature,
msg_hash.asPtrCuchar
)
if not success:
raise newException(ValueError, "Failed to recover public key. Is the signature correct?")
proc `$`*(key: PublicKey): string {.inline.} = key.toString()
proc `$`*(s: Signature): string =
var data: array[65, byte]
s.serialize(data)
data.toHex()