674 lines
27 KiB
Nim
674 lines
27 KiB
Nim
## Copyright (c) 2018-2023 Status Research & Development GmbH
|
|
## 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: [].}
|
|
|
|
import
|
|
strformat, typetraits,
|
|
stew/[byteutils, objects, results, ctops, ptrops],
|
|
./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
|
|
#
|
|
# * Types like keys and signatures are guaranteed to hold valid values which
|
|
# simplifies reasoning about errors
|
|
# * An exception is keys that have been cleared - these are no longer valid
|
|
# to be passed as arguments to functions
|
|
# * TODO a sink that makes the compiler guarantee that `clear` is the last
|
|
# thing called on the instance
|
|
# * 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
|
|
# * Where `secp256k1_context_no_precomp`, we surround the code with
|
|
# `{.noSideEffect.}` as the compiler cannot deduce that this is a constant
|
|
|
|
const
|
|
SkRawSecretKeySize* = 32 # 256 div 8
|
|
## Size of private key in octets (bytes)
|
|
SkRawSignatureSize* = 64
|
|
## Compact serialized non-recoverable signature
|
|
SkDerSignatureMaxSize* = 72
|
|
## Max bytes in DER encoding
|
|
|
|
SkRawRecoverableSignatureSize* = 65
|
|
## Size of recoverable signature in octets (bytes)
|
|
|
|
SkRawSchnorrSignatureSize* = 64
|
|
## Size of Schnorr signature in octets (bytes)
|
|
|
|
SkRawPublicKeySize* = 65
|
|
## Size of uncompressed public key in octets (bytes)
|
|
|
|
SkRawCompressedPublicKeySize* = 33
|
|
## Size of compressed public key in octets (bytes)
|
|
|
|
SkRawXOnlyPublicKeySize* = 32
|
|
## Size of x-only public key in octets (bytes)
|
|
|
|
SkMessageSize* = 32
|
|
## Size of message that can be signed
|
|
|
|
SkEcdhSecretSize* = 32
|
|
## ECDH-agreed key size
|
|
|
|
type
|
|
SkPublicKey* {.requiresInit.} = object
|
|
## Representation of public key.
|
|
data: secp256k1_pubkey
|
|
|
|
SkXOnlyPublicKey* {.requiresInit.} = object
|
|
## Representation of public key that only reveals the x-coordinate.
|
|
data: secp256k1_xonly_pubkey
|
|
|
|
SkSecretKey* {.requiresInit.} = object
|
|
## Representation of secret key.
|
|
data: array[SkRawSecretKeySize, byte]
|
|
|
|
SkKeyPair* = object
|
|
## Representation of private/public keys pair.
|
|
seckey*: SkSecretKey
|
|
pubkey*: SkPublicKey
|
|
|
|
SkSignature* {.requiresInit.} = object
|
|
## Representation of non-recoverable signature.
|
|
data: secp256k1_ecdsa_signature
|
|
|
|
SkRecoverableSignature* {.requiresInit.} = object
|
|
## Representation of recoverable signature.
|
|
data: secp256k1_ecdsa_recoverable_signature
|
|
|
|
SkSchnorrSignature* {.requiresInit.} = object
|
|
## Representation of a Schnorr signature.
|
|
data: array[SkRawSchnorrSignatureSize, byte]
|
|
|
|
SkContext = object
|
|
## Representation of Secp256k1 context object.
|
|
context: ptr secp256k1_context
|
|
|
|
SkMessage* = distinct array[SkMessageSize, byte]
|
|
## Message that can be signed or verified
|
|
|
|
SkEcdhSecret* {.requiresInit.} = object
|
|
## Representation of ECDH shared secret
|
|
data*: array[SkEcdhSecretSize, byte]
|
|
|
|
SkEcdhHashFunc* = secp256k1_ecdh_hash_function
|
|
|
|
SkResult*[T] = Result[T, cstring]
|
|
|
|
##
|
|
## Private procedures interface
|
|
##
|
|
|
|
var secpContext {.threadvar.}: SkContext
|
|
## Thread local variable which holds current context
|
|
|
|
proc illegalCallback(message: cstring, data: pointer) {.cdecl, raises: [].} =
|
|
# Internal panic - should never happen - all objects we pass into functions
|
|
# are guaranteed valid per their type
|
|
echo message
|
|
echo getStackTrace()
|
|
quit 1
|
|
|
|
proc errorCallback(message: cstring, data: pointer) {.cdecl, raises: [].} =
|
|
# Internal panic - should never happen
|
|
echo message
|
|
echo getStackTrace()
|
|
quit 1
|
|
|
|
template baseAddr(v: SkMessage): ptr byte =
|
|
baseAddr(distinctBase(v))
|
|
|
|
proc releaseThread*(T: type SkContext): T =
|
|
if not isNil(secpContext.context):
|
|
secp256k1_context_destroy(secpContext.context)
|
|
secpContext.context = nil
|
|
|
|
proc init(T: type SkContext): T =
|
|
## Create new Secp256k1 context object - when no longer needed, it should be
|
|
## destroyed
|
|
|
|
# TODO We _should_ release the context on thread shutdown but there's no
|
|
# reliable way to do that short of doing it manually, which the code is
|
|
# not really prepared for - unfortunately, nim finalizers are broken:
|
|
# https://github.com/nim-lang/Nim/issues/4851
|
|
# A workaround is to call SkContext.releaseThread() on thread end - this
|
|
# will become a no-op when the issue is fixed
|
|
let flags = cuint(SECP256K1_CONTEXT_VERIFY or SECP256K1_CONTEXT_SIGN)
|
|
result.context = secp256k1_context_create(flags)
|
|
secp256k1_context_set_illegal_callback(
|
|
result.context, illegalCallback, nil)
|
|
secp256k1_context_set_error_callback(
|
|
result.context, errorCallback, nil)
|
|
|
|
func getContext(): ptr secp256k1_context =
|
|
## Get current `EccContext`
|
|
{.noSideEffect.}:
|
|
# TODO modifying the secp context here is a side effect, but not
|
|
# necessarily an observable one, since the modification is done to
|
|
# a thread-local variable that is only updated from within here.
|
|
# Technically, it should be possible to precompute a static context
|
|
# at compile time and use that instead, which would turn this into
|
|
# a truly side-effect-free function, instead of an as-if-free one.
|
|
if isNil(secpContext.context):
|
|
secpContext = SkContext.init()
|
|
secpContext.context
|
|
|
|
func fromHex*(T: type seq[byte], s: string): SkResult[T] =
|
|
# TODO move this to some common location and return a general error?
|
|
try:
|
|
ok(hexToSeqByte(s))
|
|
except CatchableError:
|
|
err("secp: cannot parse hex string")
|
|
|
|
type
|
|
Rng* = proc(data: var openArray[byte]): bool {.raises: [Defect], gcsafe.}
|
|
## A function that fills data with random bytes from a cryptographically
|
|
## secure source or returns false
|
|
|
|
FoolproofRng* = proc(data: var openArray[byte]) {.raises: [Defect], gcsafe.}
|
|
## The world will run out of fools before this RNG fails!
|
|
|
|
proc random*(T: type SkSecretKey, rng: Rng): SkResult[T] =
|
|
## Generates new random private key - a cryptographically secure RNG should be
|
|
## used - see nimcrypto or bearssl for good RNG's.
|
|
##
|
|
## The random number generator in the Nim standard library `random` module is
|
|
## not cryptographically secure.
|
|
##
|
|
## This function may fail to generate a valid key if the RNG fails. In the
|
|
## current version, the random number generation will be called in a loop
|
|
## which may be vulnerable to timing attacks. Generate your keys elsewhere
|
|
## if this is a issue.
|
|
var data{.noinit.}: array[SkRawSecretKeySize, byte]
|
|
|
|
while rng(data):
|
|
if secp256k1_ec_seckey_verify(secp256k1_context_no_precomp, data.baseAddr) == 1:
|
|
return ok(T(data: data))
|
|
|
|
return err("secp: cannot get random bytes for key")
|
|
|
|
proc random*(T: type SkSecretKey, rng: FoolproofRng): T =
|
|
## Generates new random private key - a cryptographically secure RNG should be
|
|
## used - see nimcrypto or bearssl for good RNG's.
|
|
##
|
|
## The random number generator in the Nim standard library `random` module is
|
|
## not cryptographically secure.
|
|
##
|
|
## This function may fail to generate a valid key if the RNG fails, in which
|
|
## case it will raise a Defect.
|
|
##
|
|
## In the current version, the random number generation will be called in a
|
|
## loop which may be vulnerable to timing attacks. Generate your keys
|
|
## elsewhere if this is a issue.
|
|
var data{.noinit.}: array[SkRawSecretKeySize, byte]
|
|
|
|
for _ in 0..1000*1000:
|
|
rng(data)
|
|
if secp256k1_ec_seckey_verify(secp256k1_context_no_precomp, data.baseAddr) == 1:
|
|
return T(data: data)
|
|
|
|
result = T(data: default(array[32, byte])) # Silence compiler
|
|
# All-zeroes all the time for example will break this function
|
|
raiseAssert "RNG not giving random enough bytes, can't create valid key"
|
|
|
|
func fromRaw*(T: type SkSecretKey, data: openArray[byte]): SkResult[T] =
|
|
## Load a valid private key, as created by `toRaw`
|
|
if len(data) < SkRawSecretKeySize:
|
|
return err(static(&"secp: raw private key should be {SkRawSecretKeySize} bytes"))
|
|
|
|
if secp256k1_ec_seckey_verify(secp256k1_context_no_precomp, data.baseAddr) != 1:
|
|
return err("secp: invalid private key")
|
|
|
|
ok(T(data: toArray(32, data.toOpenArray(0, SkRawSecretKeySize - 1))))
|
|
|
|
func fromHex*(T: type SkSecretKey, data: string): SkResult[T] =
|
|
## Initialize Secp256k1 `private key` ``key`` from hexadecimal string
|
|
## representation ``data``.
|
|
T.fromRaw(? seq[byte].fromHex(data))
|
|
|
|
func toRaw*(seckey: SkSecretKey): array[SkRawSecretKeySize, byte] =
|
|
## Serialize Secp256k1 `private key` ``key`` to raw binary form
|
|
seckey.data
|
|
|
|
func toHex*(seckey: SkSecretKey): string =
|
|
toHex(toRaw(seckey))
|
|
|
|
func toPublicKey*(key: SkSecretKey): SkPublicKey =
|
|
## Calculate and return Secp256k1 `public key` from `private key` ``key``.
|
|
var pubkey {.noinit.}: secp256k1_pubkey
|
|
let res = secp256k1_ec_pubkey_create(
|
|
getContext(), addr pubkey, key.data.baseAddr)
|
|
doAssert res == 1, "Valid private keys should always have a corresponding pub"
|
|
|
|
SkPublicKey(data: pubkey)
|
|
|
|
func fromRaw*(T: type SkPublicKey, data: openArray[byte]): SkResult[T] =
|
|
## Initialize Secp256k1 `public key` ``key`` from raw binary
|
|
## 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"))
|
|
|
|
var length: int
|
|
if data[0] == 0x02'u8 or data[0] == 0x03'u8:
|
|
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")
|
|
|
|
var key {.noinit.}: secp256k1_pubkey
|
|
if secp256k1_ec_pubkey_parse(
|
|
secp256k1_context_no_precomp, addr key, data.baseAddr, csize_t(length)) != 1:
|
|
return err("secp: cannot parse public key")
|
|
|
|
ok(SkPublicKey(data: key))
|
|
|
|
func fromHex*(T: type SkPublicKey, data: string): SkResult[T] =
|
|
## Initialize Secp256k1 `public key` ``key`` from hexadecimal string
|
|
## representation ``data``.
|
|
T.fromRaw(? seq[byte].fromHex(data))
|
|
|
|
func toRaw*(pubkey: SkPublicKey): array[SkRawPublicKeySize, byte] =
|
|
## Serialize Secp256k1 `public key` ``key`` to raw uncompressed form
|
|
var length = csize_t(len(result))
|
|
let res = secp256k1_ec_pubkey_serialize(
|
|
secp256k1_context_no_precomp, result.baseAddr, addr length,
|
|
unsafeAddr pubkey.data, SECP256K1_EC_UNCOMPRESSED)
|
|
doAssert res == 1, "Can't fail, per documentation"
|
|
|
|
func toHex*(pubkey: SkPublicKey): string =
|
|
toHex(toRaw(pubkey))
|
|
|
|
func toRawCompressed*(pubkey: SkPublicKey): array[SkRawCompressedPublicKeySize, byte] =
|
|
## Serialize Secp256k1 `public key` ``key`` to raw compressed form
|
|
var length = csize_t(len(result))
|
|
let res = secp256k1_ec_pubkey_serialize(
|
|
secp256k1_context_no_precomp, result.baseAddr, addr length,
|
|
unsafeAddr pubkey.data, SECP256K1_EC_COMPRESSED)
|
|
doAssert res == 1, "Can't fail, per documentation"
|
|
|
|
func toHexCompressed*(pubkey: SkPublicKey): string =
|
|
toHex(toRawCompressed(pubkey))
|
|
|
|
func toXOnly*(pk: SkPublicKey): SkXOnlyPublicKey =
|
|
## Gets a pubkey that reveals only the x-coordinate on the curve.
|
|
var data {.noinit.}: secp256k1_xonly_pubkey
|
|
let res = secp256k1_xonly_pubkey_from_pubkey(
|
|
secp256k1_context_no_precomp, addr data, nil, unsafeAddr pk.data)
|
|
doAssert res == 1, "cannot get xonly pubkey from pubkey, key invalid?"
|
|
|
|
SkXOnlyPublicKey(data: data)
|
|
|
|
func fromRaw*(T: type SkXOnlyPublicKey, data: openArray[byte]): SkResult[T] =
|
|
## Initialize Secp256k1 `x-only public key` ``key`` from raw binary
|
|
## representation ``data``.
|
|
if len(data) != SkRawXOnlyPublicKeySize:
|
|
return err(static(
|
|
&"secp: x-only public key must be {SkRawXOnlyPublicKeySize} bytes"))
|
|
|
|
var key {.noinit.}: secp256k1_xonly_pubkey
|
|
if secp256k1_xonly_pubkey_parse(
|
|
secp256k1_context_no_precomp, addr key, data.baseAddr) != 1:
|
|
return err("secp: cannot parse x-only public key")
|
|
|
|
ok(SkXOnlyPublicKey(data: key))
|
|
|
|
func fromHex*(T: type SkXOnlyPublicKey, data: string): SkResult[T] =
|
|
## Initialize Secp256k1 `x-only public key` ``key`` from hexadecimal string
|
|
## representation ``data``.
|
|
T.fromRaw(? seq[byte].fromHex(data))
|
|
|
|
func toRaw*(pubkey: SkXOnlyPublicKey): array[SkRawXOnlyPublicKeySize, byte] =
|
|
## Serialize Secp256k1 `x-only public key` ``key`` to raw form.
|
|
let res = secp256k1_xonly_pubkey_serialize(
|
|
secp256k1_context_no_precomp, result.baseAddr, unsafeAddr pubkey.data)
|
|
doAssert res == 1, "Can't fail, per documentation"
|
|
|
|
func toHex*(pubkey: SkXOnlyPublicKey): string =
|
|
toHex(toRaw(pubkey))
|
|
|
|
func 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"))
|
|
|
|
var sig {.noinit.}: secp256k1_ecdsa_signature
|
|
if secp256k1_ecdsa_signature_parse_compact(
|
|
secp256k1_context_no_precomp, addr sig, data.baseAddr) != 1:
|
|
return err("secp: cannot parse signaure")
|
|
|
|
ok(T(data: sig))
|
|
|
|
func fromDer*(T: type SkSignature, data: openArray[byte]): SkResult[T] =
|
|
## Initialize Secp256k1 `signature` ``sig`` from DER
|
|
## representation ``data``.
|
|
if len(data) < 1:
|
|
return err("secp: DER signature too short")
|
|
|
|
var sig {.noinit.}: secp256k1_ecdsa_signature
|
|
if secp256k1_ecdsa_signature_parse_der(
|
|
secp256k1_context_no_precomp, addr sig, data.baseAddr, csize_t(len(data))) != 1:
|
|
return err("secp: cannot parse DER signature")
|
|
|
|
ok(T(data: sig))
|
|
|
|
func fromHex*(T: type SkSignature, data: string): SkResult[T] =
|
|
## Initialize Secp256k1 `signature` ``sig`` from hexadecimal string
|
|
## representation ``data``.
|
|
T.fromRaw(? seq[byte].fromHex(data))
|
|
|
|
func toRaw*(sig: SkSignature): array[SkRawSignatureSize, byte] =
|
|
## Serialize signature to compact binary form
|
|
let res = secp256k1_ecdsa_signature_serialize_compact(
|
|
secp256k1_context_no_precomp, result.baseAddr, unsafeAddr sig.data)
|
|
doAssert res == 1, "Can't fail, per documentation"
|
|
|
|
func toDer*(sig: SkSignature, data: var openArray[byte]): int =
|
|
## Serialize Secp256k1 `signature` ``sig`` to raw binary form and store it
|
|
## to ``data``.
|
|
##
|
|
## Returns number of bytes (octets) needed to store secp256k1 signature - if
|
|
## this is more than `data.len`, `data` is not written to.
|
|
var buffer: array[SkDerSignatureMaxSize, byte]
|
|
var plength = csize_t(len(buffer))
|
|
let res = secp256k1_ecdsa_signature_serialize_der(
|
|
secp256k1_context_no_precomp, buffer.baseAddr, addr plength,
|
|
unsafeAddr sig.data)
|
|
doAssert res == 1, "Can't fail, per documentation"
|
|
result = int(plength)
|
|
if len(data) >= result:
|
|
copyMem(addr data[0], addr buffer[0], result)
|
|
|
|
func toDer*(sig: SkSignature): seq[byte] =
|
|
## Serialize Secp256k1 `signature` and return it.
|
|
result = newSeq[byte](72)
|
|
let length = toDer(sig, result)
|
|
result.setLen(length)
|
|
|
|
func toHex*(sig: SkSignature): string =
|
|
toHex(toRaw(sig))
|
|
|
|
func fromRaw*(T: type SkRecoverableSignature, data: openArray[byte]): SkResult[T] =
|
|
if data.len() < SkRawRecoverableSignatureSize:
|
|
return err(
|
|
static(&"secp: recoverable signature must be {SkRawRecoverableSignatureSize} bytes"))
|
|
|
|
let recid = cint(data[64])
|
|
if recid < 0 or recid > 3:
|
|
return err("secp: recoverable signature's recid must be >= 0 and <= 3")
|
|
|
|
var sig {.noinit.}: secp256k1_ecdsa_recoverable_signature
|
|
if secp256k1_ecdsa_recoverable_signature_parse_compact(
|
|
secp256k1_context_no_precomp, addr sig, data.baseAddr, recid) != 1:
|
|
return err("secp: invalid recoverable signature")
|
|
|
|
ok(T(data: sig))
|
|
|
|
func fromHex*(T: type SkRecoverableSignature, data: string): SkResult[T] =
|
|
## Initialize Secp256k1 `signature` ``sig`` from hexadecimal string
|
|
## representation ``data``.
|
|
T.fromRaw(? seq[byte].fromHex(data))
|
|
|
|
func toRaw*(sig: SkRecoverableSignature): array[SkRawRecoverableSignatureSize, byte] =
|
|
## Converts recoverable signature to compact binary form
|
|
var recid = cint(0)
|
|
let res = secp256k1_ecdsa_recoverable_signature_serialize_compact(
|
|
secp256k1_context_no_precomp, result.baseAddr, addr recid, unsafeAddr sig.data)
|
|
doAssert res == 1, "Can't fail, per documentation"
|
|
|
|
result[64] = byte(recid)
|
|
|
|
func toHex*(sig: SkRecoverableSignature): string =
|
|
toHex(toRaw(sig))
|
|
|
|
func fromRaw*(T: type SkSchnorrSignature, data: openArray[byte]): SkResult[T] =
|
|
## Load Schnorr `signature` from data as created by `toRaw`.
|
|
if len(data) < SkRawSchnorrSignatureSize:
|
|
return err(static(&"secp: raw schnorr signature should be {SkRawSchnorrSignatureSize} bytes"))
|
|
|
|
ok(T(data: toArray(64, data.toOpenArray(0, SkRawSchnorrSignatureSize - 1))))
|
|
|
|
func fromHex*(T: type SkSchnorrSignature, data: string): SkResult[T] =
|
|
## Initialize Schnorr `signature` from hexadecimal string representation ``data``.
|
|
T.fromRaw(? seq[byte].fromHex(data))
|
|
|
|
func toRaw*(sig: SkSchnorrSignature): array[SkRawSchnorrSignatureSize, byte] =
|
|
## Serialize Schnorr `signature` ``sig`` to raw binary form.
|
|
sig.data
|
|
|
|
func toHex*(sig: SkSchnorrSignature): string =
|
|
toHex(toRaw(sig))
|
|
|
|
proc random*(T: type SkKeyPair, rng: Rng): SkResult[T] =
|
|
## Generates new random key pair.
|
|
let seckey = ? SkSecretKey.random(rng)
|
|
ok(T(
|
|
seckey: seckey,
|
|
pubkey: seckey.toPublicKey()
|
|
))
|
|
|
|
proc random*(T: type SkKeyPair, rng: FoolproofRng): T =
|
|
## Generates new random key pair.
|
|
let seckey = SkSecretKey.random(rng)
|
|
T(
|
|
seckey: seckey,
|
|
pubkey: seckey.toPublicKey()
|
|
)
|
|
|
|
func `==`*(lhs, rhs: SkPublicKey): bool =
|
|
## Compare Secp256k1 `public key` objects for equality.
|
|
CT.isEqual(lhs.toRaw(), rhs.toRaw())
|
|
|
|
func `==`*(lhs, rhs: SkSignature): bool =
|
|
## Compare Secp256k1 `signature` objects for equality.
|
|
CT.isEqual(lhs.toRaw(), rhs.toRaw())
|
|
|
|
func `==`*(lhs, rhs: SkXOnlyPublicKey): bool =
|
|
## Compare Secp256k1 `x-only public key` objects for equality.
|
|
CT.isEqual(lhs.toRaw(), rhs.toRaw())
|
|
|
|
func `==`*(lhs, rhs: SkRecoverableSignature): bool =
|
|
## Compare Secp256k1 `recoverable signature` objects for equality.
|
|
CT.isEqual(lhs.toRaw(), rhs.toRaw())
|
|
|
|
func `==`*(lhs, rhs: SkSchnorrSignature): bool =
|
|
## Compare Schnorr signature objects for equality.
|
|
CT.isEqual(lhs.toRaw(), rhs.toRaw())
|
|
|
|
func sign*(key: SkSecretKey, msg: SkMessage): SkSignature =
|
|
## Sign message `msg` using private key `key` and return signature object.
|
|
## It is recommended that `msg` is produced by hashing the input data to
|
|
## a 32-byte hash, like sha256.
|
|
var data {.noinit.}: secp256k1_ecdsa_signature
|
|
let res = secp256k1_ecdsa_sign(
|
|
getContext(), addr data, msg.baseAddr, key.data.baseAddr, nil, nil)
|
|
doAssert res == 1, "cannot create signature, key invalid?"
|
|
SkSignature(data: data)
|
|
|
|
func signRecoverable*(key: SkSecretKey, msg: SkMessage): SkRecoverableSignature =
|
|
## Sign message `msg` using private key `key` and return signature object.
|
|
var data {.noinit.}: secp256k1_ecdsa_recoverable_signature
|
|
let res = secp256k1_ecdsa_sign_recoverable(
|
|
getContext(), addr data, msg.baseAddr, key.data.baseAddr, nil, nil)
|
|
doAssert res == 1, "cannot create recoverable signature, key invalid?"
|
|
SkRecoverableSignature(data: data)
|
|
|
|
template signSchnorrImpl(signMsg: untyped): untyped =
|
|
var kp {.noinit, inject.}: secp256k1_keypair
|
|
let res = secp256k1_keypair_create(
|
|
getContext(), addr kp, key.data.baseAddr)
|
|
doAssert res == 1, "cannot create keypair, key invalid?"
|
|
|
|
var data {.noinit, inject.}: array[SkRawSchnorrSignatureSize, byte]
|
|
let res2 = signMsg
|
|
doAssert res2 == 1, "cannot create signature, key invalid?"
|
|
SkSchnorrSignature(data: data)
|
|
|
|
func signSchnorr*(key: SkSecretKey, msg: SkMessage, randbytes: Opt[array[32, byte]]): SkSchnorrSignature =
|
|
## Sign message `msg` using private key `key` with the Schnorr signature algorithm and return signature object.
|
|
## `randbytes` should be an array of 32 freshly generated random bytes.
|
|
let aux_rand32 = if randbytes.isSome: randbytes[].baseAddr else: nil
|
|
signSchnorrImpl(
|
|
secp256k1_schnorrsig_sign32(
|
|
getContext(), data.baseAddr, msg.baseAddr, addr kp, aux_rand32))
|
|
|
|
func signSchnorr*(key: SkSecretKey, msg: openArray[byte], randbytes: Opt[array[32, byte]]): SkSchnorrSignature =
|
|
## Sign message `msg` using private key `key` with the Schnorr signature algorithm and return signature object.
|
|
## `randbytes` should be an array of 32 freshly generated random bytes.
|
|
let aux_rand32 = if randbytes.isSome: randbytes[].baseAddr else: nil
|
|
let extraparams = secp256k1_schnorrsig_extraparams(magic: SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC, noncefp: nil, ndata: aux_rand32)
|
|
signSchnorrImpl(
|
|
secp256k1_schnorrsig_sign_custom(
|
|
getContext(), data.baseAddr, msg.baseAddr, csize_t msg.len, addr kp, unsafeAddr extraparams))
|
|
|
|
template signSchnorrRngImpl(): untyped =
|
|
var randbytes: array[32, byte]
|
|
if rng(randbytes):
|
|
return ok(signSchnorr(key, msg, Opt.some randbytes))
|
|
return err("secp: cannot get random bytes for signature")
|
|
|
|
proc signSchnorr*(key: SkSecretKey, msg: SkMessage, rng: Rng): SkResult[SkSchnorrSignature] {.inline.} =
|
|
## Sign message `msg` using private key `key` with the Schnorr signature algorithm and return signature object.
|
|
## Uses ``rng`` to generate 32-bytes of random data for signature generation.
|
|
signSchnorrRngImpl()
|
|
|
|
proc signSchnorr*(key: SkSecretKey, msg: openArray[byte], rng: Rng): SkResult[SkSchnorrSignature] {.inline.} =
|
|
## Sign message `msg` using private key `key` with the Schnorr signature algorithm and return signature object.
|
|
## Uses ``rng`` to generate 32-bytes of random data for signature generation.
|
|
signSchnorrRngImpl()
|
|
|
|
template signSchnorrFoolproofRngImpl(): untyped =
|
|
var randbytes: array[32, byte]
|
|
rng(randbytes)
|
|
return signSchnorr(key, msg, Opt.some randbytes)
|
|
|
|
proc signSchnorr*(key: SkSecretKey, msg: SkMessage, rng: FoolproofRng): SkSchnorrSignature {.inline.} =
|
|
## Sign message `msg` using private key `key` with the Schnorr signature algorithm and return signature object.
|
|
## Uses ``rng`` to generate 32-bytes of random data for signature generation.
|
|
signSchnorrFoolproofRngImpl()
|
|
|
|
proc signSchnorr*(key: SkSecretKey, msg: openArray[byte], rng: FoolproofRng): SkSchnorrSignature {.inline.} =
|
|
## Sign message `msg` using private key `key` with the Schnorr signature algorithm and return signature object.
|
|
## Uses ``rng`` to generate 32-bytes of random data for signature generation.
|
|
signSchnorrFoolproofRngImpl()
|
|
|
|
func verify*(sig: SkSignature, msg: SkMessage, key: SkPublicKey): bool =
|
|
secp256k1_ecdsa_verify(
|
|
getContext(), unsafeAddr sig.data, msg.baseAddr, unsafeAddr key.data) == 1
|
|
|
|
func verify*(sig: SkSchnorrSignature, msg: SkMessage, pubkey: SkXOnlyPublicKey): bool =
|
|
secp256k1_schnorrsig_verify(
|
|
getContext(), unsafeAddr sig.data[0], msg.baseAddr, csize_t SkMessageSize, unsafeAddr pubkey.data) == 1
|
|
|
|
func verify*(sig: SkSchnorrSignature, msg: openArray[byte], pubkey: SkXOnlyPublicKey): bool =
|
|
secp256k1_schnorrsig_verify(
|
|
getContext(), unsafeAddr sig.data[0], msg.baseAddr, csize_t msg.len, unsafeAddr pubkey.data) == 1
|
|
|
|
template verify*(sig: SkSchnorrSignature, msg: SkMessage, pubkey: SkPublicKey): bool =
|
|
verify(sig, msg, pubkey.toXOnly)
|
|
|
|
template verify*(sig: SkSchnorrSignature, msg: openArray[byte], pubkey: SkPublicKey): bool =
|
|
verify(sig, msg, pubkey.toXOnly)
|
|
|
|
func recover*(sig: SkRecoverableSignature, msg: SkMessage): SkResult[SkPublicKey] =
|
|
var data {.noinit.}: secp256k1_pubkey
|
|
if secp256k1_ecdsa_recover(
|
|
getContext(), addr data, unsafeAddr sig.data, msg.baseAddr) != 1:
|
|
return err("secp: cannot recover public key from signature")
|
|
|
|
ok(SkPublicKey(data: data))
|
|
|
|
func ecdh*(seckey: SkSecretKey, pubkey: SkPublicKey): SkEcdhSecret =
|
|
## Calculate ECDH shared secret.
|
|
## Default hash function and `requiresInit` should prevent this function
|
|
## from failing.
|
|
var secret {.noinit.}: array[SkEcdhSecretSize, byte]
|
|
let res = secp256k1_ecdh(
|
|
secp256k1_context_no_precomp, secret.baseAddr, unsafeAddr pubkey.data,
|
|
seckey.data.baseAddr)
|
|
doAssert res == 1, "cannot compute ECDH secret, keys invalid?"
|
|
|
|
SkEcdhSecret(data: secret)
|
|
|
|
func ecdh*[N: static[int]](seckey: SkSecretKey, pubkey: SkPublicKey,
|
|
hashfn: SkEcdhHashFunc, data: pointer): SkResult[array[N, byte]] =
|
|
## Calculate ECDH shared secret using custom hash function.
|
|
## This function may fail if the custom hash function return zero
|
|
## although other inputs have been initialized properly.
|
|
var secret {.noinit.}: array[N, byte]
|
|
if secp256k1_ecdh(
|
|
secp256k1_context_no_precomp, secret.baseAddr, unsafeAddr pubkey.data,
|
|
seckey.data.baseAddr, hashfn, data) != 1:
|
|
return err("cannot compute ECDH secret, keys invalid?")
|
|
|
|
ok(secret)
|
|
|
|
func clear*(v: var SkSecretKey) =
|
|
## Wipe and clear memory of Secp256k1 `private key`.
|
|
## After calling this function, the key is invalid and using it elsewhere will
|
|
## result in undefined behaviour or Defect
|
|
burnMem(v.data)
|
|
|
|
func clear*(v: var SkEcdhSecret) =
|
|
## Wipe and clear memory of ECDH `shared secret`.
|
|
## After calling this function, the key is invalid and using it elsewhere will
|
|
## result in undefined behaviour or Defect
|
|
burnMem(v.data)
|
|
|
|
func `$`*(
|
|
v: SkPublicKey | SkSecretKey | SkXOnlyPublicKey | SkSignature | SkRecoverableSignature | SkSchnorrSignature): string =
|
|
toHex(v)
|
|
|
|
func fromBytes*(T: type SkMessage, data: openArray[byte]): SkResult[SkMessage] =
|
|
if data.len() != SkMessageSize:
|
|
return err("Message must be 32 bytes")
|
|
|
|
ok(SkMessage(toArray(SkMessageSize, data)))
|
|
|
|
# Close `requiresInit` loophole
|
|
# TODO replace `requiresInit` with a pragma that does the expected thing
|
|
proc default*(T: type SkPublicKey): T {.error: "loophole".}
|
|
proc default*(T: type SkSecretKey): T {.error: "loophole".}
|
|
proc default*(T: type SkXOnlyPublicKey): T {.error: "loophole".}
|
|
proc default*(T: type SkSignature): T {.error: "loophole".}
|
|
proc default*(T: type SkRecoverableSignature): T {.error: "loophole".}
|
|
proc default*(T: type SkSchnorrSignature): T {.error: "loophole".}
|
|
proc default*(T: type SkEcdhSecret): T {.error: "loophole".}
|
|
|
|
func tweakAdd*(secretKey: var SkSecretKey, tweak: openArray[byte]): SkResult[void] =
|
|
let res = secp256k1_ec_privkey_tweak_add(
|
|
secp256k1_context_no_precomp, secretKey.data.baseAddr, tweak.baseAddr)
|
|
if res != 1:
|
|
err("Tweak out of range, or invalid private key")
|
|
else:
|
|
ok()
|
|
|
|
func tweakMul*(secretKey: var SkSecretKey, tweak: openArray[byte]): SkResult[void] =
|
|
let res = secp256k1_ec_privkey_tweak_mul(
|
|
secp256k1_context_no_precomp, secretKey.data.baseAddr, tweak.baseAddr)
|
|
if res != 1:
|
|
err("Tweak out of range, or equal to zero")
|
|
else:
|
|
ok()
|
|
|