add convenience api (#21)

fixes #19
This commit is contained in:
Jacek Sieka 2020-04-17 07:43:30 +02:00 committed by GitHub
parent 5af866754b
commit 306289244c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 794 additions and 273 deletions

View File

@ -7,7 +7,8 @@
![Stability: experimental](https://img.shields.io/badge/stability-experimental-orange.svg) ![Stability: experimental](https://img.shields.io/badge/stability-experimental-orange.svg)
# Introduction # Introduction
This library is a wrapper for Bitcoin's [secp256k1](https://github.com/bitcoin-core/secp256k1) library.
This library is a wrapper for Bitcoin's [secp256k1](https://github.com/bitcoin-core/secp256k1) library. Two interfaces are exposed - `secp256k1` which thinly wraps the raw C interface found in `secp256k1_abi`. The thin wrapper is recommended.
# Installation # Installation

View File

@ -1,322 +1,440 @@
import strutils ## Copyright (c) 2018-2020 Status Research & Development GmbH
from os import DirSep, quoteShell ## Licensed under either of
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
## at your option.
## This file may not be copied, modified, or distributed except according to
## those terms.
##
{.push raises: [Defect].}
import
strformat,
stew/[byteutils, objects, results],
nimcrypto/[hash, sysrand],
./secp256k1_abi
from nimcrypto/utils import burnMem
export results
# Implementation notes
#
# The goal of this wrapper is to create a thin layer on top of the API presented
# in secp256k1_abi, exploiting some of its regulatities to make it slightly more
# convenient to use from Nim
#
# * We hide raw pointer accesses and lengths behind nim types
# * We guarantee certain parameter properties, like not null and proper length,
# on the Nim side - in turn, we can rely on certain errors never happening in
# libsecp256k1, so we can skip checking for them
# * Functions like "fromRaw/toRaw" are balanced and will always rountrip
# * Functions like `fromRaw` are not called `init` because they may fail
# * No CatchableErrors
const const
wrapperPath = currentSourcePath.rsplit(DirSep, 1)[0] & DirSep & SkRawSecretKeySize* = 32 # 256 div 8
"secp256k1_wrapper" ## Size of private key in octets (bytes)
internalPath = wrapperPath & DirSep & "secp256k1" SkRawSignatureSize* = 64
srcPath = internalPath & DirSep & "src" ## Compact serialized non-recoverable signature
secpSrc = srcPath & DirSep & "secp256k1.c" SkDerSignatureMaxSize* = 72
## Max bytes in DER encoding
{.passC: "-I" & quoteShell(wrapperPath).} SkRawRecoverableSignatureSize* = 65
{.passC: "-I" & quoteShell(internalPath).} ## Size of recoverable signature in octets (bytes)
{.passC: "-I" & quoteShell(srcPath).}
{.passC: "-DHAVE_CONFIG_H".}
when defined(gcc) or defined(clang): SkRawPublicKeySize* = 65
{.passC: "-DHAVE_BUILTIN_EXPECT"} ## Size of uncompressed public key in octets (bytes)
{.compile: secpSrc.} SkRawCompressedPublicKeySize* = 33
## Size of compressed public key in octets (bytes)
{.pragma: secp, importc, cdecl, raises: [].} SkMessageSize* = 32
## Size of message that can be signed
SkEdchSecretSize* = 32
## ECDH-agreed key size
SkEcdhRawSecretSize* = 33
## ECDH-agreed raw key size
type type
secp256k1_pubkey* = object SkPublicKey* = secp256k1_pubkey
data*: array[64, uint8] ## Representation of public key.
secp256k1_ecdsa_signature* = object SkSecretKey* = object
data*: array[64, uint8] ## Representation of secret key.
data*: array[SkRawSecretKeySize, byte]
secp256k1_nonce_function* = proc (nonce32: ptr cuchar; msg32: ptr cuchar; SkKeyPair* = object
key32: ptr cuchar; algo16: ptr cuchar; data: pointer; ## Representation of private/public keys pair.
attempt: cuint): cint {.cdecl, raises: [].} seckey*: SkSecretKey
secp256k1_error_function* = proc (message: cstring; data: pointer) {.cdecl, raises: [].} pubkey*: SkPublicKey
secp256k1_ecdh_hash_function* = proc (output: ptr cuchar, SkSignature* = secp256k1_ecdsa_signature
x32, y32: ptr cuchar, ## Representation of non-recoverable signature.
data: pointer) {.cdecl, raises: [].}
secp256k1_context* = object SkRecoverableSignature* = secp256k1_ecdsa_recoverable_signature
secp256k1_scratch_space* = object ## Representation of recoverable signature.
const SkContext* = ref object
SECP256K1_FLAGS_TYPE_MASK* = ((1 shl 8) - 1) ## Representation of Secp256k1 context object.
SECP256K1_FLAGS_TYPE_CONTEXT* = (1 shl 0) context: ptr secp256k1_context
SECP256K1_FLAGS_TYPE_COMPRESSION* = (1 shl 1)
## * The higher bits contain the actual data. Do not use directly. SkMessage* = MDigest[SkMessageSize * 8]
SECP256K1_FLAGS_BIT_CONTEXT_VERIFY* = (1 shl 8) ## Message that can be signed or verified
SECP256K1_FLAGS_BIT_CONTEXT_SIGN* = (1 shl 9)
SECP256K1_FLAGS_BIT_CONTEXT_DECLASSIFY* = (1 shl 10)
SECP256K1_FLAGS_BIT_COMPRESSION* = (1 shl 8)
## * Flags to pass to secp256k1_context_create. SkEcdhSecret* = object
SECP256K1_CONTEXT_VERIFY* = ( ## Representation of ECDH shared secret
SECP256K1_FLAGS_TYPE_CONTEXT or SECP256K1_FLAGS_BIT_CONTEXT_VERIFY) data*: array[SkEdchSecretSize, byte]
SECP256K1_CONTEXT_SIGN* = (
SECP256K1_FLAGS_TYPE_CONTEXT or SECP256K1_FLAGS_BIT_CONTEXT_SIGN)
SECP256K1_CONTEXT_DECLASSIFY* = (
SECP256K1_FLAGS_TYPE_CONTEXT or SECP256K1_FLAGS_BIT_CONTEXT_DECLASSIFY
)
SECP256K1_CONTEXT_NONE* = (SECP256K1_FLAGS_TYPE_CONTEXT)
## * Flag to pass to secp256k1_ec_pubkey_serialize and secp256k1_ec_privkey_export. SkEcdhRawSecret* = object
SECP256K1_EC_COMPRESSED* = ( ## Representation of ECDH shared secret, with leading `y` byte
SECP256K1_FLAGS_TYPE_COMPRESSION or SECP256K1_FLAGS_BIT_COMPRESSION) # (`y` is 0x02 when pubkey.y is even or 0x03 when odd)
SECP256K1_EC_UNCOMPRESSED* = (SECP256K1_FLAGS_TYPE_COMPRESSION) data*: array[SkEcdhRawSecretSize, byte]
## * Prefix byte used to tag various encoded curvepoints for specific purposes SkResult*[T] = Result[T, cstring]
SECP256K1_TAG_PUBKEY_EVEN* = 0x00000002
SECP256K1_TAG_PUBKEY_ODD* = 0x00000003
SECP256K1_TAG_PUBKEY_UNCOMPRESSED* = 0x00000004
SECP256K1_TAG_PUBKEY_HYBRID_EVEN* = 0x00000006
SECP256K1_TAG_PUBKEY_HYBRID_ODD* = 0x00000007
var secp256k1_context_no_precomp_imp {. ##
importc: "secp256k1_context_no_precomp".}: ptr secp256k1_context ## Private procedures interface
let secp256k1_context_no_precomp* = secp256k1_context_no_precomp_imp ##
var secp256k1_ecdh_hash_function_default_imp {. var secpContext {.threadvar.}: SkContext
importc: "secp256k1_ecdh_hash_function_default".}: secp256k1_ecdh_hash_function ## Thread local variable which holds current context
let secp256k1_ecdh_hash_function_default* =
secp256k1_ecdh_hash_function_default_imp
proc secp256k1_context_create*( proc illegalCallback(message: cstring, data: pointer) {.cdecl, raises: [].} =
flags: cuint): ptr secp256k1_context {.secp.} # This is called for example when an invalid key is used - we'll simply
# ignore and rely on the return value
# TODO it would be nice if a "constructor" could be used such that no invalid
# keys can ever be created - this would remove the need for this kludge -
# rust-secp256k1 for example operates under this principle. the
# alternative would be to pre-validate keys before every function call
# but that seems expensive given that libsecp itself already does this
# check
discard
proc secp256k1_context_clone*( proc errorCallback(message: cstring, data: pointer) {.cdecl, raises: [].} =
ctx: ptr secp256k1_context): ptr secp256k1_context {.secp.} # Internal panic - should never happen
echo message
echo getStackTrace()
quit 1
proc secp256k1_context_destroy*( template ptr0(v: array|openArray): ptr cuchar =
ctx: ptr secp256k1_context) {.secp.} cast[ptr cuchar](unsafeAddr v[0])
proc secp256k1_context_set_illegal_callback*( proc shutdownLibsecp256k1(ctx: SkContext) =
ctx: ptr secp256k1_context; # TODO: use destructor when finalizer are deprecated for destructors
fun: secp256k1_error_function; if not(isNil(ctx.context)):
data: pointer) {.secp.} secp256k1_context_destroy(ctx.context)
proc secp256k1_context_set_error_callback*( proc newSkContext(): SkContext =
ctx: ptr secp256k1_context; ## Create new Secp256k1 context object.
fun: secp256k1_error_function; new(result, shutdownLibsecp256k1)
data: pointer) {.secp.} let flags = cuint(SECP256K1_CONTEXT_VERIFY or SECP256K1_CONTEXT_SIGN)
result.context = secp256k1_context_create(flags)
secp256k1_context_set_illegal_callback(
result.context, illegalCallback, cast[pointer](result))
secp256k1_context_set_error_callback(
result.context, errorCallback, cast[pointer](result))
proc secp256k1_scratch_space_create*( func getContext(): ptr secp256k1_context =
ctx: ptr secp256k1_context; ## Get current `EccContext`
size: csize_t): ptr secp256k1_scratch_space {.secp.} {.noSideEffect.}: # TODO what problems will this cause?
if isNil(secpContext):
secpContext = newSkContext()
secpContext.context
proc secp256k1_scratch_space_destroy*( proc fromHex*(T: type seq[byte], s: string): SkResult[T] =
ctx: ptr secp256k1_context; # TODO move this to some common location and return a general error?
scratch: ptr secp256k1_scratch_space) {.secp.} try:
ok(hexToSeqByte(s))
except CatchableError:
err("secp: cannot parse hex string")
proc secp256k1_ec_pubkey_parse*( proc verify*(seckey: SkSecretKey): bool =
ctx: ptr secp256k1_context; secp256k1_ec_seckey_verify(
pubkey: ptr secp256k1_pubkey; secp256k1_context_no_precomp, seckey.data.ptr0) == 1
input: ptr cuchar;
inputlen: csize_t): cint {.secp.}
proc secp256k1_ec_pubkey_serialize*( proc random*(T: type SkSecretKey): SkResult[T] =
ctx: ptr secp256k1_context; ## Generates new random private key.
output: ptr cuchar; var sk: T
outputlen: ptr csize_t; while randomBytes(sk.data) == SkRawSecretKeySize:
pubkey: ptr secp256k1_pubkey; if sk.verify():
flags: cuint): cint {.secp.} return ok(sk)
proc secp256k1_ecdsa_signature_parse_compact*( return err("secp: cannot get random bytes for key")
ctx: ptr secp256k1_context;
sig: ptr secp256k1_ecdsa_signature;
input64: ptr cuchar): cint {.secp.}
proc secp256k1_ecdsa_signature_parse_der*( proc fromRaw*(T: type SkSecretKey, data: openArray[byte]): SkResult[T] =
ctx: ptr secp256k1_context; ## Load a valid private key, as created by `toRaw`
sig: ptr secp256k1_ecdsa_signature; if len(data) < SkRawSecretKeySize:
input: ptr cuchar; return err(static(&"secp: raw private key should be {SkRawSecretKeySize} bytes"))
inputlen: csize_t): cint {.secp.}
proc secp256k1_ecdsa_signature_serialize_der*( if secp256k1_ec_seckey_verify(secp256k1_context_no_precomp, data.ptr0) != 1:
ctx: ptr secp256k1_context; return err("secp: invalid private key")
output: ptr cuchar;
outputlen: ptr csize_t;
sig: ptr secp256k1_ecdsa_signature): cint {.secp.}
proc secp256k1_ecdsa_signature_serialize_compact*( ok(T(data: toArray(32, data.toOpenArray(0, SkRawSecretKeySize - 1))))
ctx: ptr secp256k1_context;
output64: ptr cuchar;
sig: ptr secp256k1_ecdsa_signature): cint {.secp.}
proc secp256k1_ecdsa_verify*( proc fromHex*(T: type SkSecretKey, data: string): SkResult[T] =
ctx: ptr secp256k1_context; ## Initialize Secp256k1 `private key` ``key`` from hexadecimal string
sig: ptr secp256k1_ecdsa_signature; ## representation ``data``.
msg32: ptr cuchar; T.fromRaw(? seq[byte].fromHex(data))
pubkey: ptr secp256k1_pubkey): cint {.secp.}
proc secp256k1_ecdsa_signature_normalize*( proc toRaw*(seckey: SkSecretKey): array[SkRawSecretKeySize, byte] =
ctx: ptr secp256k1_context; ## Serialize Secp256k1 `private key` ``key`` to raw binary form
sigout: ptr secp256k1_ecdsa_signature; seckey.data
sigin: ptr secp256k1_ecdsa_signature): cint {.secp.}
proc secp256k1_ecdsa_sign*( proc toHex*(seckey: SkSecretKey): string =
ctx: ptr secp256k1_context; toHex(toRaw(seckey))
sig: ptr secp256k1_ecdsa_signature;
msg32: ptr cuchar;
seckey: ptr cuchar;
noncefp: secp256k1_nonce_function;
ndata: pointer): cint {.secp.}
proc secp256k1_ec_seckey_verify*( proc toPublicKey*(key: SkSecretKey): SkResult[SkPublicKey] =
ctx: ptr secp256k1_context; ## Calculate and return Secp256k1 `public key` from `private key` ``key``.
seckey: ptr cuchar): cint {.secp.} var pubkey: SkPublicKey
if secp256k1_ec_pubkey_create(getContext(), addr pubkey, key.data.ptr0) != 1:
return err("secp: cannot create pubkey, private key invalid?")
proc secp256k1_ec_pubkey_create*( ok(pubkey)
ctx: ptr secp256k1_context;
pubkey: ptr secp256k1_pubkey;
seckey: ptr cuchar): cint {.secp.}
proc secp256k1_ec_privkey_negate*( proc fromRaw*(T: type SkPublicKey, data: openArray[byte]): SkResult[T] =
ctx: ptr secp256k1_context; ## Initialize Secp256k1 `public key` ``key`` from raw binary
seckey: ptr cuchar): cint {.secp.} ## representation ``data``, which may be compressed, uncompressed or hybrid
if len(data) < SkRawCompressedPublicKeySize:
return err(static(
&"secp: public key must be {SkRawCompressedPublicKeySize} or {SkRawPublicKeySize} bytes"))
proc secp256k1_ec_pubkey_negate*( var length: int
ctx: ptr secp256k1_context; if data[0] == 0x02'u8 or data[0] == 0x03'u8:
pubkey: ptr secp256k1_pubkey): cint {.secp.} length = min(len(data), SkRawCompressedPublicKeySize)
elif data[0] == 0x04'u8 or data[0] == 0x06'u8 or data[0] == 0x07'u8:
length = min(len(data), SkRawPublicKeySize)
else:
return err("secp: public key format not recognised")
proc secp256k1_ec_privkey_tweak_add*( var key: SkPublicKey
ctx: ptr secp256k1_context; if secp256k1_ec_pubkey_parse(
seckey: ptr cuchar; getContext(), addr key, data.ptr0, csize_t(length)) != 1:
tweak: ptr cuchar): cint {.secp.} return err("secp: cannot parse public key")
proc secp256k1_ec_pubkey_tweak_add*( ok(key)
ctx: ptr secp256k1_context;
pubkey: ptr secp256k1_pubkey;
tweak: ptr cuchar): cint {.secp.}
proc secp256k1_ec_privkey_tweak_mul*( proc fromHex*(T: type SkPublicKey, data: string): SkResult[T] =
ctx: ptr secp256k1_context; ## Initialize Secp256k1 `public key` ``key`` from hexadecimal string
seckey: ptr cuchar; ## representation ``data``.
tweak: ptr cuchar): cint {.secp.} T.fromRaw(? seq[byte].fromHex(data))
proc secp256k1_ec_pubkey_tweak_mul*( proc toRaw*(pubkey: SkPublicKey): array[SkRawPublicKeySize, byte] =
ctx: ptr secp256k1_context; ## Serialize Secp256k1 `public key` ``key`` to raw uncompressed form
pubkey: ptr secp256k1_pubkey; var length = csize_t(len(result))
tweak: ptr cuchar): cint {.secp.} # Can't fail, per documentation
discard secp256k1_ec_pubkey_serialize(
getContext(), result.ptr0, addr length, unsafeAddr pubkey,
SECP256K1_EC_UNCOMPRESSED)
proc secp256k1_context_randomize*( proc toHex*(pubkey: SkPublicKey): string =
ctx: ptr secp256k1_context; toHex(toRaw(pubkey))
seed32: ptr cuchar): cint {.secp.}
proc secp256k1_ec_pubkey_combine*( proc toRawCompressed*(pubkey: SkPublicKey): array[SkRawCompressedPublicKeySize, byte] =
ctx: ptr secp256k1_context; ## Serialize Secp256k1 `public key` ``key`` to raw compressed form
output: ptr secp256k1_pubkey; var length = csize_t(len(result))
ins: ptr ptr secp256k1_pubkey; # Can't fail, per documentation
n: csize_t): cint {.secp.} discard secp256k1_ec_pubkey_serialize(
getContext(), result.ptr0, addr length, unsafeAddr pubkey,
SECP256K1_EC_COMPRESSED)
var secp256k1_nonce_function_rfc6979*: secp256k1_nonce_function proc toHexCompressed*(pubkey: SkPublicKey): string =
var secp256k1_nonce_function_default*: secp256k1_nonce_function toHex(toRawCompressed(pubkey))
## Recovery interface follows proc fromRaw*(T: type SkSignature, data: openArray[byte]): SkResult[T] =
## Load compact signature from data
if data.len() < SkRawSignatureSize:
return err(static(&"secp: signature must be {SkRawSignatureSize} bytes"))
type var sig: SkSignature
secp256k1_ecdsa_recoverable_signature* = object if secp256k1_ecdsa_signature_parse_compact(
## Opaque data structured that holds a parsed ECDSA signature, getContext(), addr sig, data.ptr0) != 1:
## supporting pubkey recovery. return err("secp: cannot parse signaure")
## 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*( ok(sig)
ctx: ptr secp256k1_context;
sig: ptr secp256k1_ecdsa_recoverable_signature; proc fromDer*(T: type SkSignature, data: openarray[byte]): SkResult[T] =
msg32: ptr cuchar; ## Initialize Secp256k1 `signature` ``sig`` from DER
seckey: ptr cuchar; ## representation ``data``.
noncefp: secp256k1_nonce_function; if len(data) < 1:
ndata: pointer): cint {.secp.} return err("secp: DER signature too short")
## Create a recoverable ECDSA signature.
## var sig: T
## Returns: 1: signature created if secp256k1_ecdsa_signature_parse_der(
## 0: the nonce generation function failed, or the private key was invalid. getContext(), addr sig, data.ptr0, csize_t(len(data))) != 1:
## Args: ctx: pointer to a context object, initialized for signing (cannot be NULL) return err("secp: cannot parse DER signature")
## 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) ok(sig)
## 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 proc fromHex*(T: type SkSignature, data: string): SkResult[T] =
## ndata: pointer to arbitrary data used by the nonce generation function (can be NULL) ## Initialize Secp256k1 `signature` ``sig`` from hexadecimal string
## representation ``data``.
T.fromRaw(? seq[byte].fromHex(data))
proc toRaw*(sig: SkSignature): array[SkRawSignatureSize, byte] =
## Serialize signature to compact binary form
# Can't fail, per documentation
discard secp256k1_ecdsa_signature_serialize_compact(
getContext(), result.ptr0, unsafeAddr sig)
proc toDer*(sig: SkSignature, data: var openarray[byte]): int =
## Serialize Secp256k1 `signature` ``sig`` to raw binary form and store it
## to ``data``.
## ##
## Procedure returns number of bytes (octets) needed to store
## Secp256k1 signature.
let ctx = getContext()
var buffer: array[SkDerSignatureMaxSize, byte]
var plength = csize_t(len(buffer))
discard secp256k1_ecdsa_signature_serialize_der(
ctx, buffer.ptr0, addr plength, unsafeAddr sig)
result = int(plength)
if len(data) >= result:
copyMem(addr data[0], addr buffer[0], result)
proc secp256k1_ecdsa_recover*( proc toDer*(sig: SkSignature): seq[byte] =
ctx: ptr secp256k1_context; ## Serialize Secp256k1 `signature` and return it.
pubkey: ptr secp256k1_pubkey; result = newSeq[byte](72)
sig: ptr secp256k1_ecdsa_recoverable_signature; let length = toDer(sig, result)
msg32: ptr cuchar): cint {.secp.} result.setLen(length)
## 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*( proc toHex*(sig: SkSignature): string =
ctx: ptr secp256k1_context; toHex(toRaw(sig))
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)
##
proc secp256k1_ecdsa_recoverable_signature_parse_compact*( proc fromRaw*(T: type SkRecoverableSignature, data: openArray[byte]): SkResult[T] =
ctx: ptr secp256k1_context; if data.len() < SkRawRecoverableSignatureSize:
sig: ptr secp256k1_ecdsa_recoverable_signature; return err(
input64: ptr cuchar, recid: cint): cint {.secp.} static(&"secp: recoverable signature must be {SkRawRecoverableSignatureSize} bytes"))
proc secp256k1_ecdh*(ctx: ptr secp256k1_context; output32: ptr cuchar; let recid = cint(data[64])
pubkey: ptr secp256k1_pubkey; var sig: SkRecoverableSignature
privkey: ptr cuchar, if secp256k1_ecdsa_recoverable_signature_parse_compact(
hashfp: secp256k1_ecdh_hash_function, getContext(), addr sig, data.ptr0, recid) != 1:
data: pointer return err("secp: invalid recoverable signature")
): cint {.secp.}
## Compute an EC Diffie-Hellman secret in constant time
## Returns: 1: exponentiation was successful
## 0: scalar was invalid (zero or overflow)
## Args: ctx: pointer to a context object (cannot be NULL)
## Out: result: a 32-byte array which will be populated by an ECDH
## secret computed from the point and scalar
## In: pubkey: a pointer to a secp256k1_pubkey containing an
## initialized public key
## privkey: a 32-byte scalar with which to multiply the point
##
template secp256k1_ecdh*(ctx: ptr secp256k1_context; output32: ptr cuchar; ok(sig)
pubkey: ptr secp256k1_pubkey;
privkey: ptr cuchar
): cint =
secp256k1_ecdh(ctx, output32, pubkey, privkey,
secp256k1_ecdh_hash_function_default, nil)
proc secp256k1_ecdh_raw*(ctx: ptr secp256k1_context; output32: ptr cuchar; proc fromHex*(T: type SkRecoverableSignature, data: string): SkResult[T] =
pubkey: ptr secp256k1_pubkey; ## Initialize Secp256k1 `signature` ``sig`` from hexadecimal string
input32: ptr cuchar): cint {.secp.} ## representation ``data``.
## Compute an EC Diffie-Hellman secret in constant time T.fromRaw(? seq[byte].fromHex(data))
## Returns: 1: exponentiation was successful
## 0: scalar was invalid (zero or overflow) proc toRaw*(sig: SkRecoverableSignature): array[SkRawRecoverableSignatureSize, byte] =
## Args: ctx: pointer to a context object (cannot be NULL) ## Converts recoverable signature to compact binary form
## Out: result: a 33-byte array which will be populated by an ECDH var recid = cint(0)
## secret computed from the point and scalar in form # Can't fail, per documentation
## of compressed point discard secp256k1_ecdsa_recoverable_signature_serialize_compact(
## In: pubkey: a pointer to a secp256k1_pubkey containing an getContext(), result.ptr0, addr recid, unsafeAddr sig)
## initialized public key result[64] = byte(recid)
## privkey: a 32-byte scalar with which to multiply the point
## proc toHex*(sig: SkRecoverableSignature): string =
toHex(toRaw(sig))
proc random*(T: type SkKeyPair): SkResult[T] =
## Generates new random key pair.
let seckey = ? SkSecretKey.random()
ok(T(
seckey: seckey,
pubkey: seckey.toPublicKey().expect("random key should always be valid")
))
proc `==`*(lhs, rhs: SkPublicKey): bool =
## Compare Secp256k1 `public key` objects for equality.
lhs.toRaw() == rhs.toRaw()
proc `==`*(lhs, rhs: SkSignature): bool =
## Compare Secp256k1 `signature` objects for equality.
lhs.toRaw() == rhs.toRaw()
proc `==`*(lhs, rhs: SkRecoverableSignature): bool =
## Compare Secp256k1 `recoverable signature` objects for equality.
lhs.toRaw() == rhs.toRaw()
proc sign*(key: SkSecretKey, msg: SkMessage): SkResult[SkSignature] =
## Sign message `msg` using private key `key` and return signature object.
var sig: SkSignature
if secp256k1_ecdsa_sign(
getContext(), addr sig, msg.data.ptr0, key.data.ptr0, nil, nil) != 1:
return err("secp: cannot create signature, key invalid?")
ok(sig)
proc signRecoverable*(key: SkSecretKey, msg: SkMessage): SkResult[SkRecoverableSignature] =
## Sign message `msg` using private key `key` and return signature object.
var sig: SkRecoverableSignature
if secp256k1_ecdsa_sign_recoverable(
getContext(), addr sig, msg.data.ptr0, key.data.ptr0, nil, nil) != 1:
return err("secp: cannot create recoverable signature, key invalid?")
ok(sig)
proc verify*(sig: SkSignature, msg: SkMessage, key: SkPublicKey): bool =
secp256k1_ecdsa_verify(
getContext(), unsafeAddr sig, msg.data.ptr0, unsafeAddr key) == 1
proc recover*(sig: SkRecoverableSignature, msg: SkMessage): SkResult[SkPublicKey] =
var pubkey: SkPublicKey
if secp256k1_ecdsa_recover(
getContext(), addr pubkey, unsafeAddr sig, msg.data.ptr0) != 1:
return err("secp: cannot recover public key from signature")
ok(pubkey)
proc ecdh*(seckey: SkSecretKey, pubkey: SkPublicKey): SkResult[SkEcdhSecret] =
## Calculate ECDH shared secret.
var secret: SkEcdhSecret
if secp256k1_ecdh(
getContext(), secret.data.ptr0, unsafeAddr pubkey, seckey.data.ptr0) != 1:
return err("secp: cannot compute ECDH secret")
ok(secret)
proc ecdhRaw*(seckey: SkSecretKey, pubkey: SkPublicKey): SkResult[SkEcdhRawSecret] =
## Calculate ECDH shared secret, ethereum style
# TODO - deprecate: https://github.com/status-im/nim-eth/issues/222
var secret: SkEcdhRawSecret
if secp256k1_ecdh_raw(
getContext(), secret.data.ptr0, unsafeAddr pubkey, seckey.data.ptr0) != 1:
return err("Cannot compute raw ECDH secret")
ok(secret)
proc clear*(v: var SkSecretKey) {.inline.} =
## Wipe and clear memory of Secp256k1 `private key`.
burnMem(v.data)
proc clear*(v: var SkPublicKey) {.inline.} =
## Wipe and clear memory of Secp256k1 `public key`.
burnMem(v.data)
proc clear*(v: var SkSignature) {.inline.} =
## Wipe and clear memory of Secp256k1 `signature`.
burnMem(v.data)
proc clear*(v: var SkRecoverableSignature) {.inline.} =
## Wipe and clear memory of Secp256k1 `signature`.
burnMem(v.data)
proc clear*(v: var SkKeyPair) {.inline.} =
## Wipe and clear memory of Secp256k1 `key pair`.
v.seckey.clear()
v.pubkey.clear()
proc clear*(v: var SkEcdhSecret) =
burnMem(v.data)
proc clear*(v: var SkEcdhRawSecret) =
burnMem(v.data)
proc `$`*(
v: SkPublicKey | SkSecretKey | SkSignature | SkRecoverableSignature): string =
toHex(v)
proc fromBytes*(T: type SkMessage, data: openArray[byte]): SkResult[SkMessage] =
if data.len() != SkMessageSize:
return err("Message must be 32 bytes")
ok(SkMessage(data: toArray(SkMessageSize, data)))

View File

@ -1,13 +1,15 @@
mode = ScriptMode.Verbose mode = ScriptMode.Verbose
packageName = "secp256k1" packageName = "secp256k1"
version = "0.1.2" version = "0.2.0"
author = "Status Research & Development GmbH" author = "Status Research & Development GmbH"
description = "A wrapper for the libsecp256k1 C library" description = "A wrapper for the libsecp256k1 C library"
license = "Apache License 2.0" license = "Apache License 2.0"
installDirs = @["secp256k1_wrapper"] installDirs = @["secp256k1_wrapper"]
requires "nim >= 0.18.0" requires "nim >= 1.2.0"
requires "stew"
requires "nimcrypto"
proc test(name: string, lang: string = "c") = proc test(name: string, lang: string = "c") =
if not dirExists "build": if not dirExists "build":
@ -17,5 +19,5 @@ proc test(name: string, lang: string = "c") =
switch("out", ("./build/" & name)) switch("out", ("./build/" & name))
setCommand lang, "tests/" & name & ".nim" setCommand lang, "tests/" & name & ".nim"
task test, "Run Proof-of-Work tests (without mining)": task test, "Tests":
test "test1" test "all_tests"

322
secp256k1_abi.nim Normal file
View File

@ -0,0 +1,322 @@
import strutils
from os import DirSep, quoteShell
const
wrapperPath = currentSourcePath.rsplit(DirSep, 1)[0] & DirSep &
"secp256k1_wrapper"
internalPath = wrapperPath & DirSep & "secp256k1"
srcPath = internalPath & DirSep & "src"
secpSrc = srcPath & DirSep & "secp256k1.c"
{.passC: "-I" & quoteShell(wrapperPath).}
{.passC: "-I" & quoteShell(internalPath).}
{.passC: "-I" & quoteShell(srcPath).}
{.passC: "-DHAVE_CONFIG_H".}
when defined(gcc) or defined(clang):
{.passC: "-DHAVE_BUILTIN_EXPECT"}
{.compile: secpSrc.}
{.pragma: secp, importc, cdecl, raises: [].}
type
secp256k1_pubkey* = object
data*: array[64, uint8]
secp256k1_ecdsa_signature* = object
data*: array[64, uint8]
secp256k1_nonce_function* = proc (nonce32: ptr cuchar; msg32: ptr cuchar;
key32: ptr cuchar; algo16: ptr cuchar; data: pointer;
attempt: cuint): cint {.cdecl, raises: [].}
secp256k1_error_function* = proc (message: cstring; data: pointer) {.cdecl, raises: [].}
secp256k1_ecdh_hash_function* = proc (output: ptr cuchar,
x32, y32: ptr cuchar,
data: pointer) {.cdecl, raises: [].}
secp256k1_context* = object
secp256k1_scratch_space* = object
const
SECP256K1_FLAGS_TYPE_MASK* = ((1 shl 8) - 1)
SECP256K1_FLAGS_TYPE_CONTEXT* = (1 shl 0)
SECP256K1_FLAGS_TYPE_COMPRESSION* = (1 shl 1)
## * The higher bits contain the actual data. Do not use directly.
SECP256K1_FLAGS_BIT_CONTEXT_VERIFY* = (1 shl 8)
SECP256K1_FLAGS_BIT_CONTEXT_SIGN* = (1 shl 9)
SECP256K1_FLAGS_BIT_CONTEXT_DECLASSIFY* = (1 shl 10)
SECP256K1_FLAGS_BIT_COMPRESSION* = (1 shl 8)
## * Flags to pass to secp256k1_context_create.
SECP256K1_CONTEXT_VERIFY* = (
SECP256K1_FLAGS_TYPE_CONTEXT or SECP256K1_FLAGS_BIT_CONTEXT_VERIFY)
SECP256K1_CONTEXT_SIGN* = (
SECP256K1_FLAGS_TYPE_CONTEXT or SECP256K1_FLAGS_BIT_CONTEXT_SIGN)
SECP256K1_CONTEXT_DECLASSIFY* = (
SECP256K1_FLAGS_TYPE_CONTEXT or SECP256K1_FLAGS_BIT_CONTEXT_DECLASSIFY
)
SECP256K1_CONTEXT_NONE* = (SECP256K1_FLAGS_TYPE_CONTEXT)
## * Flag to pass to secp256k1_ec_pubkey_serialize and secp256k1_ec_privkey_export.
SECP256K1_EC_COMPRESSED* = (
SECP256K1_FLAGS_TYPE_COMPRESSION or SECP256K1_FLAGS_BIT_COMPRESSION)
SECP256K1_EC_UNCOMPRESSED* = (SECP256K1_FLAGS_TYPE_COMPRESSION)
## * Prefix byte used to tag various encoded curvepoints for specific purposes
SECP256K1_TAG_PUBKEY_EVEN* = 0x00000002
SECP256K1_TAG_PUBKEY_ODD* = 0x00000003
SECP256K1_TAG_PUBKEY_UNCOMPRESSED* = 0x00000004
SECP256K1_TAG_PUBKEY_HYBRID_EVEN* = 0x00000006
SECP256K1_TAG_PUBKEY_HYBRID_ODD* = 0x00000007
var secp256k1_context_no_precomp_imp {.
importc: "secp256k1_context_no_precomp".}: ptr secp256k1_context
let secp256k1_context_no_precomp* = secp256k1_context_no_precomp_imp
var secp256k1_ecdh_hash_function_default_imp {.
importc: "secp256k1_ecdh_hash_function_default".}: secp256k1_ecdh_hash_function
let secp256k1_ecdh_hash_function_default* =
secp256k1_ecdh_hash_function_default_imp
proc secp256k1_context_create*(
flags: cuint): ptr secp256k1_context {.secp.}
proc secp256k1_context_clone*(
ctx: ptr secp256k1_context): ptr secp256k1_context {.secp.}
proc secp256k1_context_destroy*(
ctx: ptr secp256k1_context) {.secp.}
proc secp256k1_context_set_illegal_callback*(
ctx: ptr secp256k1_context;
fun: secp256k1_error_function;
data: pointer) {.secp.}
proc secp256k1_context_set_error_callback*(
ctx: ptr secp256k1_context;
fun: secp256k1_error_function;
data: pointer) {.secp.}
proc secp256k1_scratch_space_create*(
ctx: ptr secp256k1_context;
size: csize_t): ptr secp256k1_scratch_space {.secp.}
proc secp256k1_scratch_space_destroy*(
ctx: ptr secp256k1_context;
scratch: ptr secp256k1_scratch_space) {.secp.}
proc secp256k1_ec_pubkey_parse*(
ctx: ptr secp256k1_context;
pubkey: ptr secp256k1_pubkey;
input: ptr cuchar;
inputlen: csize_t): cint {.secp.}
proc secp256k1_ec_pubkey_serialize*(
ctx: ptr secp256k1_context;
output: ptr cuchar;
outputlen: ptr csize_t;
pubkey: ptr secp256k1_pubkey;
flags: cuint): cint {.secp.}
proc secp256k1_ecdsa_signature_parse_compact*(
ctx: ptr secp256k1_context;
sig: ptr secp256k1_ecdsa_signature;
input64: ptr cuchar): cint {.secp.}
proc secp256k1_ecdsa_signature_parse_der*(
ctx: ptr secp256k1_context;
sig: ptr secp256k1_ecdsa_signature;
input: ptr cuchar;
inputlen: csize_t): cint {.secp.}
proc secp256k1_ecdsa_signature_serialize_der*(
ctx: ptr secp256k1_context;
output: ptr cuchar;
outputlen: ptr csize_t;
sig: ptr secp256k1_ecdsa_signature): cint {.secp.}
proc secp256k1_ecdsa_signature_serialize_compact*(
ctx: ptr secp256k1_context;
output64: ptr cuchar;
sig: ptr secp256k1_ecdsa_signature): cint {.secp.}
proc secp256k1_ecdsa_verify*(
ctx: ptr secp256k1_context;
sig: ptr secp256k1_ecdsa_signature;
msg32: ptr cuchar;
pubkey: ptr secp256k1_pubkey): cint {.secp.}
proc secp256k1_ecdsa_signature_normalize*(
ctx: ptr secp256k1_context;
sigout: ptr secp256k1_ecdsa_signature;
sigin: ptr secp256k1_ecdsa_signature): cint {.secp.}
proc secp256k1_ecdsa_sign*(
ctx: ptr secp256k1_context;
sig: ptr secp256k1_ecdsa_signature;
msg32: ptr cuchar;
seckey: ptr cuchar;
noncefp: secp256k1_nonce_function;
ndata: pointer): cint {.secp.}
proc secp256k1_ec_seckey_verify*(
ctx: ptr secp256k1_context;
seckey: ptr cuchar): cint {.secp.}
proc secp256k1_ec_pubkey_create*(
ctx: ptr secp256k1_context;
pubkey: ptr secp256k1_pubkey;
seckey: ptr cuchar): cint {.secp.}
proc secp256k1_ec_privkey_negate*(
ctx: ptr secp256k1_context;
seckey: ptr cuchar): cint {.secp.}
proc secp256k1_ec_pubkey_negate*(
ctx: ptr secp256k1_context;
pubkey: ptr secp256k1_pubkey): cint {.secp.}
proc secp256k1_ec_privkey_tweak_add*(
ctx: ptr secp256k1_context;
seckey: ptr cuchar;
tweak: ptr cuchar): cint {.secp.}
proc secp256k1_ec_pubkey_tweak_add*(
ctx: ptr secp256k1_context;
pubkey: ptr secp256k1_pubkey;
tweak: ptr cuchar): cint {.secp.}
proc secp256k1_ec_privkey_tweak_mul*(
ctx: ptr secp256k1_context;
seckey: ptr cuchar;
tweak: ptr cuchar): cint {.secp.}
proc secp256k1_ec_pubkey_tweak_mul*(
ctx: ptr secp256k1_context;
pubkey: ptr secp256k1_pubkey;
tweak: ptr cuchar): cint {.secp.}
proc secp256k1_context_randomize*(
ctx: ptr secp256k1_context;
seed32: ptr cuchar): cint {.secp.}
proc secp256k1_ec_pubkey_combine*(
ctx: ptr secp256k1_context;
output: ptr secp256k1_pubkey;
ins: ptr ptr secp256k1_pubkey;
n: csize_t): cint {.secp.}
var secp256k1_nonce_function_rfc6979*: secp256k1_nonce_function
var secp256k1_nonce_function_default*: secp256k1_nonce_function
## Recovery interface follows
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)
##
proc secp256k1_ecdsa_recoverable_signature_parse_compact*(
ctx: ptr secp256k1_context;
sig: ptr secp256k1_ecdsa_recoverable_signature;
input64: ptr cuchar, recid: cint): cint {.secp.}
proc secp256k1_ecdh*(ctx: ptr secp256k1_context; output32: ptr cuchar;
pubkey: ptr secp256k1_pubkey;
privkey: ptr cuchar,
hashfp: secp256k1_ecdh_hash_function,
data: pointer
): cint {.secp.}
## Compute an EC Diffie-Hellman secret in constant time
## Returns: 1: exponentiation was successful
## 0: scalar was invalid (zero or overflow)
## Args: ctx: pointer to a context object (cannot be NULL)
## Out: result: a 32-byte array which will be populated by an ECDH
## secret computed from the point and scalar
## In: pubkey: a pointer to a secp256k1_pubkey containing an
## initialized public key
## privkey: a 32-byte scalar with which to multiply the point
##
template secp256k1_ecdh*(ctx: ptr secp256k1_context; output32: ptr cuchar;
pubkey: ptr secp256k1_pubkey;
privkey: ptr cuchar
): cint =
secp256k1_ecdh(ctx, output32, pubkey, privkey,
secp256k1_ecdh_hash_function_default, nil)
proc secp256k1_ecdh_raw*(ctx: ptr secp256k1_context; output32: ptr cuchar;
pubkey: ptr secp256k1_pubkey;
input32: ptr cuchar): cint {.secp.}
## Compute an EC Diffie-Hellman secret in constant time
## Returns: 1: exponentiation was successful
## 0: scalar was invalid (zero or overflow)
## Args: ctx: pointer to a context object (cannot be NULL)
## Out: result: a 33-byte array which will be populated by an ECDH
## secret computed from the point and scalar in form
## of compressed point
## In: pubkey: a pointer to a secp256k1_pubkey containing an
## initialized public key
## privkey: a 32-byte scalar with which to multiply the point
##

3
tests/all_tests.nim Normal file
View File

@ -0,0 +1,3 @@
import
./test_secp256k1_abi,
./test_secp256k1

73
tests/test_secp256k1.nim Normal file
View File

@ -0,0 +1,73 @@
import ../secp256k1, unittest
{.used.}
const
msg0 = SkMessage()
msg1 = SkMessage(data: [
1'u8, 0, 0, 0, 0, 0, 0, 0,
1'u8, 0, 0, 0, 0, 0, 0, 0,
1'u8, 0, 0, 0, 0, 0, 0, 0,
1'u8, 0, 0, 0, 0, 0, 0, 0,
])
suite "secp256k1":
test "Key ops":
let
sk = SkSecretKey.random().expect("should get a key")
pk = sk.toPublicKey().expect("valid private key gives valid public key")
check:
sk.verify()
SkSecretKey.fromRaw(sk.toRaw())[].toHex() == sk.toHex()
SkSecretKey.fromHex(sk.toHex())[].toHex() == sk.toHex()
SkPublicKey.fromRaw(pk.toRaw())[].toHex() == pk.toHex()
SkPublicKey.fromRaw(pk.toRawCompressed())[].toHex() == pk.toHex()
SkPublicKey.fromHex(pk.toHex())[].toHex() == pk.toHex()
test "Invalid secret key ops":
let
sk = SkSecretKey()
check:
not sk.verify()
sk.toPublicKey().isErr()
sign(sk, msg0).isErr()
signRecoverable(sk, msg0).isErr()
ecdh(sk, SkPublicKey()).isErr()
ecdhRaw(sk, SkPublicKey()).isErr()
test "Signatures":
let
sk = SkSecretKey.random()[]
pk = sk.toPublicKey()[]
badPk = SkPublicKey()
sig = sign(sk, msg0)[]
sig2 = signRecoverable(sk, msg0)[]
check:
verify(sig, msg0, pk)
not verify(sig, msg0, badPk)
not verify(sig, msg1, pk)
recover(sig2, msg0)[] == pk
recover(sig2, msg1)[] != pk
SkSignature.fromDer(sig.toDer())[].toHex() == sig.toHex()
test "Bad signatures":
let
sk = SkSecretKey.random()[]
pk = sk.toPublicKey()[]
badPk = SkPublicKey()
badSig = SkSignature()
badSig2 = SkRecoverableSignature()
check:
not verify(badSig, msg0, pk)
not verify(badSig, msg0, badPk)
recover(badSig2, msg0).isErr
test "Message":
check:
SkMessage.fromBytes([]).isErr()
SkMessage.fromBytes([0'u8]).isErr()
SkMessage.fromBytes(msg0.data).isOk()

View File

@ -1,6 +1,8 @@
import ../secp256k1, unittest import ../secp256k1_abi, unittest
suite "Test1": {.used.}
suite "ABI tests":
test "Context should be created and destroyed": test "Context should be created and destroyed":
let ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN or SECP256K1_CONTEXT_VERIFY) let ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN or SECP256K1_CONTEXT_VERIFY)
check ctx != nil check ctx != nil