2
0
mirror of synced 2025-02-23 10:58:15 +00:00

use requiredInit (#22)

* use requiredInit

Use requiredInit on keys - this simplifies error handling by providing
more compile-time guarantees through type.

Loophole: `clear` will leave an invalid key type in memory, not
guaranteed by type - it requires an explicit action to produce, so it's
somewhat better than the current situation where by default, keys are
invalid, but it's not watertight.

something like a `sink` would be needed which would have to guarantee
that `clear` is the last use of the instance.

* close requiresinit loophole

* remove clear for public stuff

* fix side effects

`secp256k1_context_no_precomp` is constant actually

* document nosideeffect

* document nosideeffect

* document fix
This commit is contained in:
Jacek Sieka 2020-06-22 16:08:21 +02:00 committed by GitHub
parent e8fa92a0c6
commit 5701a60143
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 184 additions and 184 deletions

View File

@ -25,6 +25,12 @@ export results
# in secp256k1_abi, exploiting some of its regulatities to make it slightly more # in secp256k1_abi, exploiting some of its regulatities to make it slightly more
# convenient to use from Nim # 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 hide raw pointer accesses and lengths behind nim types
# * We guarantee certain parameter properties, like not null and proper length, # * 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 # on the Nim side - in turn, we can rely on certain errors never happening in
@ -32,6 +38,8 @@ export results
# * Functions like "fromRaw/toRaw" are balanced and will always rountrip # * Functions like "fromRaw/toRaw" are balanced and will always rountrip
# * Functions like `fromRaw` are not called `init` because they may fail # * Functions like `fromRaw` are not called `init` because they may fail
# * No CatchableErrors # * No CatchableErrors
# * Where `secp256k1_context_no_precomp`, we surround the code with
# `{.noSideEffect.}` as the compiler cannot deduce that this is a constant
const const
SkRawSecretKeySize* = 32 # 256 div 8 SkRawSecretKeySize* = 32 # 256 div 8
@ -60,23 +68,26 @@ const
## ECDH-agreed raw key size ## ECDH-agreed raw key size
type type
SkPublicKey* = secp256k1_pubkey SkPublicKey* {.requiresInit.} = object
## Representation of public key. ## Representation of public key.
data: secp256k1_pubkey
SkSecretKey* = object SkSecretKey* {.requiresInit.} = object
## Representation of secret key. ## Representation of secret key.
data*: array[SkRawSecretKeySize, byte] data: array[SkRawSecretKeySize, byte]
SkKeyPair* = object SkKeyPair* = object
## Representation of private/public keys pair. ## Representation of private/public keys pair.
seckey*: SkSecretKey seckey*: SkSecretKey
pubkey*: SkPublicKey pubkey*: SkPublicKey
SkSignature* = secp256k1_ecdsa_signature SkSignature* {.requiresInit.} = object
## Representation of non-recoverable signature. ## Representation of non-recoverable signature.
data: secp256k1_ecdsa_signature
SkRecoverableSignature* = secp256k1_ecdsa_recoverable_signature SkRecoverableSignature* {.requiresInit.} = object
## Representation of recoverable signature. ## Representation of recoverable signature.
data: secp256k1_ecdsa_recoverable_signature
SkContext* = ref object SkContext* = ref object
## Representation of Secp256k1 context object. ## Representation of Secp256k1 context object.
@ -85,11 +96,11 @@ type
SkMessage* = MDigest[SkMessageSize * 8] SkMessage* = MDigest[SkMessageSize * 8]
## Message that can be signed or verified ## Message that can be signed or verified
SkEcdhSecret* = object SkEcdhSecret* {.requiresInit.} = object
## Representation of ECDH shared secret ## Representation of ECDH shared secret
data*: array[SkEdchSecretSize, byte] data*: array[SkEdchSecretSize, byte]
SkEcdhRawSecret* = object SkEcdhRawSecret* {.requiresInit.} = object
## Representation of ECDH shared secret, with leading `y` byte ## Representation of ECDH shared secret, with leading `y` byte
# (`y` is 0x02 when pubkey.y is even or 0x03 when odd) # (`y` is 0x02 when pubkey.y is even or 0x03 when odd)
data*: array[SkEcdhRawSecretSize, byte] data*: array[SkEcdhRawSecretSize, byte]
@ -104,15 +115,11 @@ var secpContext {.threadvar.}: SkContext
## Thread local variable which holds current context ## Thread local variable which holds current context
proc illegalCallback(message: cstring, data: pointer) {.cdecl, raises: [].} = proc illegalCallback(message: cstring, data: pointer) {.cdecl, raises: [].} =
# This is called for example when an invalid key is used - we'll simply # Internal panic - should never happen - all objects we pass into functions
# ignore and rely on the return value # are guaranteed valid per their type
# TODO it would be nice if a "constructor" could be used such that no invalid echo message
# keys can ever be created - this would remove the need for this kludge - echo getStackTrace()
# rust-secp256k1 for example operates under this principle. the quit 1
# 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 errorCallback(message: cstring, data: pointer) {.cdecl, raises: [].} = proc errorCallback(message: cstring, data: pointer) {.cdecl, raises: [].} =
# Internal panic - should never happen # Internal panic - should never happen
@ -123,7 +130,7 @@ proc errorCallback(message: cstring, data: pointer) {.cdecl, raises: [].} =
template ptr0(v: array|openArray): ptr cuchar = template ptr0(v: array|openArray): ptr cuchar =
cast[ptr cuchar](unsafeAddr v[0]) cast[ptr cuchar](unsafeAddr v[0])
proc shutdownLibsecp256k1(ctx: SkContext) = func shutdownLibsecp256k1(ctx: SkContext) =
# TODO: use destructor when finalizer are deprecated for destructors # TODO: use destructor when finalizer are deprecated for destructors
if not(isNil(ctx.context)): if not(isNil(ctx.context)):
secp256k1_context_destroy(ctx.context) secp256k1_context_destroy(ctx.context)
@ -140,62 +147,67 @@ proc newSkContext(): SkContext =
func getContext(): ptr secp256k1_context = func getContext(): ptr secp256k1_context =
## Get current `EccContext` ## Get current `EccContext`
{.noSideEffect.}: # TODO what problems will this cause? {.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): if isNil(secpContext):
secpContext = newSkContext() secpContext = newSkContext()
secpContext.context secpContext.context
proc fromHex*(T: type seq[byte], s: string): SkResult[T] = func fromHex(T: type seq[byte], s: string): SkResult[T] =
# TODO move this to some common location and return a general error? # TODO move this to some common location and return a general error?
try: try:
ok(hexToSeqByte(s)) ok(hexToSeqByte(s))
except CatchableError: except CatchableError:
err("secp: cannot parse hex string") err("secp: cannot parse hex string")
proc verify*(seckey: SkSecretKey): bool =
secp256k1_ec_seckey_verify(
secp256k1_context_no_precomp, seckey.data.ptr0) == 1
proc random*(T: type SkSecretKey): SkResult[T] = proc random*(T: type SkSecretKey): SkResult[T] =
## Generates new random private key. ## Generates new random private key.
var sk: T var data{.noinit.}: array[SkRawSecretKeySize, byte]
while randomBytes(sk.data) == SkRawSecretKeySize:
if sk.verify(): while randomBytes(data) == SkRawSecretKeySize:
return ok(sk) if secp256k1_ec_seckey_verify(secp256k1_context_no_precomp, data.ptr0) == 1:
return ok(T(data: data))
return err("secp: cannot get random bytes for key") return err("secp: cannot get random bytes for key")
proc fromRaw*(T: type SkSecretKey, data: openArray[byte]): SkResult[T] = func fromRaw*(T: type SkSecretKey, data: openArray[byte]): SkResult[T] =
## Load a valid private key, as created by `toRaw` ## Load a valid private key, as created by `toRaw`
if len(data) < SkRawSecretKeySize: if len(data) < SkRawSecretKeySize:
return err(static(&"secp: raw private key should be {SkRawSecretKeySize} bytes")) return err(static(&"secp: raw private key should be {SkRawSecretKeySize} bytes"))
{.noSideEffect.}: # secp256k1_context_no_precomp is actually const, see above
if secp256k1_ec_seckey_verify(secp256k1_context_no_precomp, data.ptr0) != 1: if secp256k1_ec_seckey_verify(secp256k1_context_no_precomp, data.ptr0) != 1:
return err("secp: invalid private key") return err("secp: invalid private key")
ok(T(data: toArray(32, data.toOpenArray(0, SkRawSecretKeySize - 1)))) ok(T(data: toArray(32, data.toOpenArray(0, SkRawSecretKeySize - 1))))
proc fromHex*(T: type SkSecretKey, data: string): SkResult[T] = func fromHex*(T: type SkSecretKey, data: string): SkResult[T] =
## Initialize Secp256k1 `private key` ``key`` from hexadecimal string ## Initialize Secp256k1 `private key` ``key`` from hexadecimal string
## representation ``data``. ## representation ``data``.
T.fromRaw(? seq[byte].fromHex(data)) T.fromRaw(? seq[byte].fromHex(data))
proc toRaw*(seckey: SkSecretKey): array[SkRawSecretKeySize, byte] = func toRaw*(seckey: SkSecretKey): array[SkRawSecretKeySize, byte] =
## Serialize Secp256k1 `private key` ``key`` to raw binary form ## Serialize Secp256k1 `private key` ``key`` to raw binary form
seckey.data seckey.data
proc toHex*(seckey: SkSecretKey): string = func toHex*(seckey: SkSecretKey): string =
toHex(toRaw(seckey)) toHex(toRaw(seckey))
proc toPublicKey*(key: SkSecretKey): SkResult[SkPublicKey] = func toPublicKey*(key: SkSecretKey): SkPublicKey =
## Calculate and return Secp256k1 `public key` from `private key` ``key``. ## Calculate and return Secp256k1 `public key` from `private key` ``key``.
var pubkey: SkPublicKey var pubkey {.noinit.}: secp256k1_pubkey
if secp256k1_ec_pubkey_create(getContext(), addr pubkey, key.data.ptr0) != 1: let res = secp256k1_ec_pubkey_create(
return err("secp: cannot create pubkey, private key invalid?") getContext(), addr pubkey, key.data.ptr0)
doAssert res == 1, "Valid private keys should always have a corresponding pub"
ok(pubkey) SkPublicKey(data: pubkey)
proc fromRaw*(T: type SkPublicKey, data: openArray[byte]): SkResult[T] = func fromRaw*(T: type SkPublicKey, data: openArray[byte]): SkResult[T] =
## Initialize Secp256k1 `public key` ``key`` from raw binary ## Initialize Secp256k1 `public key` ``key`` from raw binary
## representation ``data``, which may be compressed, uncompressed or hybrid ## representation ``data``, which may be compressed, uncompressed or hybrid
if len(data) < SkRawCompressedPublicKeySize: if len(data) < SkRawCompressedPublicKeySize:
@ -210,127 +222,139 @@ proc fromRaw*(T: type SkPublicKey, data: openArray[byte]): SkResult[T] =
else: else:
return err("secp: public key format not recognised") return err("secp: public key format not recognised")
var key: SkPublicKey var key {.noinit.}: secp256k1_pubkey
{.noSideEffect.}: # secp256k1_context_no_precomp is actually const, see above
if secp256k1_ec_pubkey_parse( if secp256k1_ec_pubkey_parse(
getContext(), addr key, data.ptr0, csize_t(length)) != 1: secp256k1_context_no_precomp, addr key, data.ptr0, csize_t(length)) != 1:
return err("secp: cannot parse public key") return err("secp: cannot parse public key")
ok(key) ok(SkPublicKey(data: key))
proc fromHex*(T: type SkPublicKey, data: string): SkResult[T] = func fromHex*(T: type SkPublicKey, data: string): SkResult[T] =
## Initialize Secp256k1 `public key` ``key`` from hexadecimal string ## Initialize Secp256k1 `public key` ``key`` from hexadecimal string
## representation ``data``. ## representation ``data``.
T.fromRaw(? seq[byte].fromHex(data)) T.fromRaw(? seq[byte].fromHex(data))
proc toRaw*(pubkey: SkPublicKey): array[SkRawPublicKeySize, byte] = func toRaw*(pubkey: SkPublicKey): array[SkRawPublicKeySize, byte] =
## Serialize Secp256k1 `public key` ``key`` to raw uncompressed form ## Serialize Secp256k1 `public key` ``key`` to raw uncompressed form
var length = csize_t(len(result)) var length = csize_t(len(result))
# Can't fail, per documentation
discard secp256k1_ec_pubkey_serialize(
getContext(), result.ptr0, addr length, unsafeAddr pubkey,
SECP256K1_EC_UNCOMPRESSED)
proc toHex*(pubkey: SkPublicKey): string = {.noSideEffect.}: # secp256k1_context_no_precomp is actually const, see above
let res = secp256k1_ec_pubkey_serialize(
secp256k1_context_no_precomp, result.ptr0, addr length,
unsafeAddr pubkey.data, SECP256K1_EC_UNCOMPRESSED)
doAssert res == 1, "Can't fail, per documentation"
func toHex*(pubkey: SkPublicKey): string =
toHex(toRaw(pubkey)) toHex(toRaw(pubkey))
proc toRawCompressed*(pubkey: SkPublicKey): array[SkRawCompressedPublicKeySize, byte] = func toRawCompressed*(pubkey: SkPublicKey): array[SkRawCompressedPublicKeySize, byte] =
## Serialize Secp256k1 `public key` ``key`` to raw compressed form ## Serialize Secp256k1 `public key` ``key`` to raw compressed form
var length = csize_t(len(result)) var length = csize_t(len(result))
# Can't fail, per documentation {.noSideEffect.}: # secp256k1_context_no_precomp is actually const, see above
discard secp256k1_ec_pubkey_serialize( let res = secp256k1_ec_pubkey_serialize(
getContext(), result.ptr0, addr length, unsafeAddr pubkey, secp256k1_context_no_precomp, result.ptr0, addr length,
SECP256K1_EC_COMPRESSED) unsafeAddr pubkey.data, SECP256K1_EC_COMPRESSED)
doAssert res == 1, "Can't fail, per documentation"
proc toHexCompressed*(pubkey: SkPublicKey): string = func toHexCompressed*(pubkey: SkPublicKey): string =
toHex(toRawCompressed(pubkey)) toHex(toRawCompressed(pubkey))
proc fromRaw*(T: type SkSignature, data: openArray[byte]): SkResult[T] = func fromRaw*(T: type SkSignature, data: openArray[byte]): SkResult[T] =
## Load compact signature from data ## Load compact signature from data
if data.len() < SkRawSignatureSize: if data.len() < SkRawSignatureSize:
return err(static(&"secp: signature must be {SkRawSignatureSize} bytes")) return err(static(&"secp: signature must be {SkRawSignatureSize} bytes"))
var sig: SkSignature var sig {.noinit.}: secp256k1_ecdsa_signature
{.noSideEffect.}: # secp256k1_context_no_precomp is actually const, see above
if secp256k1_ecdsa_signature_parse_compact( if secp256k1_ecdsa_signature_parse_compact(
getContext(), addr sig, data.ptr0) != 1: secp256k1_context_no_precomp, addr sig, data.ptr0) != 1:
return err("secp: cannot parse signaure") return err("secp: cannot parse signaure")
ok(sig) ok(T(data: sig))
proc fromDer*(T: type SkSignature, data: openarray[byte]): SkResult[T] = func fromDer*(T: type SkSignature, data: openarray[byte]): SkResult[T] =
## Initialize Secp256k1 `signature` ``sig`` from DER ## Initialize Secp256k1 `signature` ``sig`` from DER
## representation ``data``. ## representation ``data``.
if len(data) < 1: if len(data) < 1:
return err("secp: DER signature too short") return err("secp: DER signature too short")
var sig: T var sig {.noinit.}: secp256k1_ecdsa_signature
{.noSideEffect.}: # secp256k1_context_no_precomp is actually const, see above
if secp256k1_ecdsa_signature_parse_der( if secp256k1_ecdsa_signature_parse_der(
getContext(), addr sig, data.ptr0, csize_t(len(data))) != 1: secp256k1_context_no_precomp, addr sig, data.ptr0, csize_t(len(data))) != 1:
return err("secp: cannot parse DER signature") return err("secp: cannot parse DER signature")
ok(sig) ok(T(data: sig))
proc fromHex*(T: type SkSignature, data: string): SkResult[T] = func fromHex*(T: type SkSignature, data: string): SkResult[T] =
## Initialize Secp256k1 `signature` ``sig`` from hexadecimal string ## Initialize Secp256k1 `signature` ``sig`` from hexadecimal string
## representation ``data``. ## representation ``data``.
T.fromRaw(? seq[byte].fromHex(data)) T.fromRaw(? seq[byte].fromHex(data))
proc toRaw*(sig: SkSignature): array[SkRawSignatureSize, byte] = func toRaw*(sig: SkSignature): array[SkRawSignatureSize, byte] =
## Serialize signature to compact binary form ## Serialize signature to compact binary form
# Can't fail, per documentation {.noSideEffect.}: # secp256k1_context_no_precomp is actually const, see above
discard secp256k1_ecdsa_signature_serialize_compact( let res = secp256k1_ecdsa_signature_serialize_compact(
getContext(), result.ptr0, unsafeAddr sig) secp256k1_context_no_precomp, result.ptr0, unsafeAddr sig.data)
doAssert res == 1, "Can't fail, per documentation"
proc toDer*(sig: SkSignature, data: var openarray[byte]): int = func toDer*(sig: SkSignature, data: var openarray[byte]): int =
## Serialize Secp256k1 `signature` ``sig`` to raw binary form and store it ## Serialize Secp256k1 `signature` ``sig`` to raw binary form and store it
## to ``data``. ## to ``data``.
## ##
## Procedure returns number of bytes (octets) needed to store ## Returns number of bytes (octets) needed to store secp256k1 signature - if
## Secp256k1 signature. ## this is more than `data.len`, `data` is not written to.
let ctx = getContext()
var buffer: array[SkDerSignatureMaxSize, byte] var buffer: array[SkDerSignatureMaxSize, byte]
var plength = csize_t(len(buffer)) var plength = csize_t(len(buffer))
discard secp256k1_ecdsa_signature_serialize_der( {.noSideEffect.}: # secp256k1_context_no_precomp is actually const, see above
ctx, buffer.ptr0, addr plength, unsafeAddr sig) let res = secp256k1_ecdsa_signature_serialize_der(
secp256k1_context_no_precomp, buffer.ptr0, addr plength,
unsafeAddr sig.data)
doAssert res == 1, "Can't fail, per documentation"
result = int(plength) result = int(plength)
if len(data) >= result: if len(data) >= result:
copyMem(addr data[0], addr buffer[0], result) copyMem(addr data[0], addr buffer[0], result)
proc toDer*(sig: SkSignature): seq[byte] = func toDer*(sig: SkSignature): seq[byte] =
## Serialize Secp256k1 `signature` and return it. ## Serialize Secp256k1 `signature` and return it.
result = newSeq[byte](72) result = newSeq[byte](72)
let length = toDer(sig, result) let length = toDer(sig, result)
result.setLen(length) result.setLen(length)
proc toHex*(sig: SkSignature): string = func toHex*(sig: SkSignature): string =
toHex(toRaw(sig)) toHex(toRaw(sig))
proc fromRaw*(T: type SkRecoverableSignature, data: openArray[byte]): SkResult[T] = func fromRaw*(T: type SkRecoverableSignature, data: openArray[byte]): SkResult[T] =
if data.len() < SkRawRecoverableSignatureSize: if data.len() < SkRawRecoverableSignatureSize:
return err( return err(
static(&"secp: recoverable signature must be {SkRawRecoverableSignatureSize} bytes")) static(&"secp: recoverable signature must be {SkRawRecoverableSignatureSize} bytes"))
let recid = cint(data[64]) let recid = cint(data[64])
var sig: SkRecoverableSignature var sig {.noinit.}: secp256k1_ecdsa_recoverable_signature
{.noSideEffect.}: # secp256k1_context_no_precomp is actually const, see above
if secp256k1_ecdsa_recoverable_signature_parse_compact( if secp256k1_ecdsa_recoverable_signature_parse_compact(
getContext(), addr sig, data.ptr0, recid) != 1: secp256k1_context_no_precomp, addr sig, data.ptr0, recid) != 1:
return err("secp: invalid recoverable signature") return err("secp: invalid recoverable signature")
ok(sig) ok(T(data: sig))
proc fromHex*(T: type SkRecoverableSignature, data: string): SkResult[T] = func fromHex*(T: type SkRecoverableSignature, data: string): SkResult[T] =
## Initialize Secp256k1 `signature` ``sig`` from hexadecimal string ## Initialize Secp256k1 `signature` ``sig`` from hexadecimal string
## representation ``data``. ## representation ``data``.
T.fromRaw(? seq[byte].fromHex(data)) T.fromRaw(? seq[byte].fromHex(data))
proc toRaw*(sig: SkRecoverableSignature): array[SkRawRecoverableSignatureSize, byte] = func toRaw*(sig: SkRecoverableSignature): array[SkRawRecoverableSignatureSize, byte] =
## Converts recoverable signature to compact binary form ## Converts recoverable signature to compact binary form
var recid = cint(0) var recid = cint(0)
# Can't fail, per documentation {.noSideEffect.}: # secp256k1_context_no_precomp is actually const, see above
discard secp256k1_ecdsa_recoverable_signature_serialize_compact( let res = secp256k1_ecdsa_recoverable_signature_serialize_compact(
getContext(), result.ptr0, addr recid, unsafeAddr sig) secp256k1_context_no_precomp, result.ptr0, addr recid, unsafeAddr sig.data)
doAssert res == 1, "can't fail, per documentation"
result[64] = byte(recid) result[64] = byte(recid)
proc toHex*(sig: SkRecoverableSignature): string = func toHex*(sig: SkRecoverableSignature): string =
toHex(toRaw(sig)) toHex(toRaw(sig))
proc random*(T: type SkKeyPair): SkResult[T] = proc random*(T: type SkKeyPair): SkResult[T] =
@ -338,103 +362,105 @@ proc random*(T: type SkKeyPair): SkResult[T] =
let seckey = ? SkSecretKey.random() let seckey = ? SkSecretKey.random()
ok(T( ok(T(
seckey: seckey, seckey: seckey,
pubkey: seckey.toPublicKey().expect("random key should always be valid") pubkey: seckey.toPublicKey()
)) ))
proc `==`*(lhs, rhs: SkPublicKey): bool = func `==`*(lhs, rhs: SkPublicKey): bool =
## Compare Secp256k1 `public key` objects for equality. ## Compare Secp256k1 `public key` objects for equality.
lhs.toRaw() == rhs.toRaw() lhs.toRaw() == rhs.toRaw()
proc `==`*(lhs, rhs: SkSignature): bool = func `==`*(lhs, rhs: SkSignature): bool =
## Compare Secp256k1 `signature` objects for equality. ## Compare Secp256k1 `signature` objects for equality.
lhs.toRaw() == rhs.toRaw() lhs.toRaw() == rhs.toRaw()
proc `==`*(lhs, rhs: SkRecoverableSignature): bool = func `==`*(lhs, rhs: SkRecoverableSignature): bool =
## Compare Secp256k1 `recoverable signature` objects for equality. ## Compare Secp256k1 `recoverable signature` objects for equality.
lhs.toRaw() == rhs.toRaw() lhs.toRaw() == rhs.toRaw()
proc sign*(key: SkSecretKey, msg: SkMessage): SkResult[SkSignature] = func sign*(key: SkSecretKey, msg: SkMessage): SkSignature =
## Sign message `msg` using private key `key` and return signature object. ## Sign message `msg` using private key `key` and return signature object.
var sig: SkSignature var data {.noinit.}: secp256k1_ecdsa_signature
if secp256k1_ecdsa_sign( let res = secp256k1_ecdsa_sign(
getContext(), addr sig, msg.data.ptr0, key.data.ptr0, nil, nil) != 1: getContext(), addr data, msg.data.ptr0, key.data.ptr0, nil, nil)
return err("secp: cannot create signature, key invalid?") doAssert res == 1, "cannot create signature, key invalid?"
SkSignature(data: data)
ok(sig) func signRecoverable*(key: SkSecretKey, msg: SkMessage): SkRecoverableSignature =
proc signRecoverable*(key: SkSecretKey, msg: SkMessage): SkResult[SkRecoverableSignature] =
## Sign message `msg` using private key `key` and return signature object. ## Sign message `msg` using private key `key` and return signature object.
var sig: SkRecoverableSignature var data {.noinit.}: secp256k1_ecdsa_recoverable_signature
if secp256k1_ecdsa_sign_recoverable( let res = secp256k1_ecdsa_sign_recoverable(
getContext(), addr sig, msg.data.ptr0, key.data.ptr0, nil, nil) != 1: getContext(), addr data, msg.data.ptr0, key.data.ptr0, nil, nil)
return err("secp: cannot create recoverable signature, key invalid?") doAssert res == 1, "cannot create recoverable signature, key invalid?"
SkRecoverableSignature(data: data)
ok(sig) func verify*(sig: SkSignature, msg: SkMessage, key: SkPublicKey): bool =
proc verify*(sig: SkSignature, msg: SkMessage, key: SkPublicKey): bool =
secp256k1_ecdsa_verify( secp256k1_ecdsa_verify(
getContext(), unsafeAddr sig, msg.data.ptr0, unsafeAddr key) == 1 getContext(), unsafeAddr sig.data, msg.data.ptr0, unsafeAddr key.data) == 1
proc recover*(sig: SkRecoverableSignature, msg: SkMessage): SkResult[SkPublicKey] = func recover*(sig: SkRecoverableSignature, msg: SkMessage): SkResult[SkPublicKey] =
var pubkey: SkPublicKey var data {.noinit.}: secp256k1_pubkey
if secp256k1_ecdsa_recover( if secp256k1_ecdsa_recover(
getContext(), addr pubkey, unsafeAddr sig, msg.data.ptr0) != 1: getContext(), addr data, unsafeAddr sig.data, msg.data.ptr0) != 1:
return err("secp: cannot recover public key from signature") return err("secp: cannot recover public key from signature")
ok(pubkey) ok(SkPublicKey(data: data))
proc ecdh*(seckey: SkSecretKey, pubkey: SkPublicKey): SkResult[SkEcdhSecret] = func ecdh*(seckey: SkSecretKey, pubkey: SkPublicKey): SkEcdhSecret =
## Calculate ECDH shared secret. ## Calculate ECDH shared secret.
var secret: SkEcdhSecret var secret {.noinit.}: array[SkEdchSecretSize, byte]
if secp256k1_ecdh( {.noSideEffect.}: # secp256k1_context_no_precomp is actually const, see above
getContext(), secret.data.ptr0, unsafeAddr pubkey, seckey.data.ptr0) != 1: let res = secp256k1_ecdh(
return err("secp: cannot compute ECDH secret") secp256k1_context_no_precomp, secret.ptr0, unsafeAddr pubkey.data,
seckey.data.ptr0)
doAssert res == 1, "cannot compute ECDH secret, keys invalid?"
ok(secret) SkEcdhSecret(data: secret)
proc ecdhRaw*(seckey: SkSecretKey, pubkey: SkPublicKey): SkResult[SkEcdhRawSecret] = func ecdhRaw*(seckey: SkSecretKey, pubkey: SkPublicKey): SkEcdhRawSecret =
## Calculate ECDH shared secret, ethereum style ## Calculate ECDH shared secret, ethereum style
# TODO - deprecate: https://github.com/status-im/nim-eth/issues/222 # TODO - deprecate: https://github.com/status-im/nim-eth/issues/222
var secret: SkEcdhRawSecret var secret {.noinit.}: array[SkEcdhRawSecretSize, byte]
if secp256k1_ecdh_raw( {.noSideEffect.}: # secp256k1_context_no_precomp is actually const, see above
getContext(), secret.data.ptr0, unsafeAddr pubkey, seckey.data.ptr0) != 1: let res = secp256k1_ecdh_raw(
return err("Cannot compute raw ECDH secret") secp256k1_context_no_precomp, secret.ptr0, unsafeAddr pubkey.data,
seckey.data.ptr0)
doAssert res == 1, "cannot compute raw ECDH secret, keys invalid?"
ok(secret) SkEcdhRawSecret(data: secret)
proc clear*(v: var SkSecretKey) {.inline.} = func clear*(v: var SkSecretKey) =
## Wipe and clear memory of Secp256k1 `private key`. ## 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) burnMem(v.data)
proc clear*(v: var SkPublicKey) {.inline.} = func clear*(v: var SkEcdhSecret) =
## Wipe and clear memory of Secp256k1 `public key`. ## 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) burnMem(v.data)
proc clear*(v: var SkSignature) {.inline.} = func clear*(v: var SkEcdhRawSecret) =
## Wipe and clear memory of Secp256k1 `signature`. ## 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) burnMem(v.data)
proc clear*(v: var SkRecoverableSignature) {.inline.} = func `$`*(
## 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 = v: SkPublicKey | SkSecretKey | SkSignature | SkRecoverableSignature): string =
toHex(v) toHex(v)
proc fromBytes*(T: type SkMessage, data: openArray[byte]): SkResult[SkMessage] = func fromBytes*(T: type SkMessage, data: openArray[byte]): SkResult[SkMessage] =
if data.len() != SkMessageSize: if data.len() != SkMessageSize:
return err("Message must be 32 bytes") return err("Message must be 32 bytes")
ok(SkMessage(data: toArray(SkMessageSize, data))) ok(SkMessage(data: 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 SkSignature): T {.error: "loophole".}
proc default*(T: type SkRecoverableSignature): T {.error: "loophole".}
proc default*(T: type SkEcdhSecret): T {.error: "loophole".}
proc default*(T: type SkEcdhRawSecret): T {.error: "loophole".}

View File

@ -15,57 +15,31 @@ suite "secp256k1":
test "Key ops": test "Key ops":
let let
sk = SkSecretKey.random().expect("should get a key") sk = SkSecretKey.random().expect("should get a key")
pk = sk.toPublicKey().expect("valid private key gives valid public key") pk = sk.toPublicKey()
check: check:
sk.verify()
SkSecretKey.fromRaw(sk.toRaw())[].toHex() == sk.toHex() SkSecretKey.fromRaw(sk.toRaw())[].toHex() == sk.toHex()
SkSecretKey.fromHex(sk.toHex())[].toHex() == sk.toHex() SkSecretKey.fromHex(sk.toHex())[].toHex() == sk.toHex()
SkPublicKey.fromRaw(pk.toRaw())[].toHex() == pk.toHex() SkPublicKey.fromRaw(pk.toRaw())[].toHex() == pk.toHex()
SkPublicKey.fromRaw(pk.toRawCompressed())[].toHex() == pk.toHex() SkPublicKey.fromRaw(pk.toRawCompressed())[].toHex() == pk.toHex()
SkPublicKey.fromHex(pk.toHex())[].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": test "Signatures":
let let
sk = SkSecretKey.random()[] sk = SkSecretKey.random()[]
pk = sk.toPublicKey()[] pk = sk.toPublicKey()
badPk = SkPublicKey() otherPk = SkSecretKey.random()[].toPublicKey()
sig = sign(sk, msg0)[] sig = sign(sk, msg0)
sig2 = signRecoverable(sk, msg0)[] sig2 = signRecoverable(sk, msg0)
check: check:
verify(sig, msg0, pk) verify(sig, msg0, pk)
not verify(sig, msg0, badPk) not verify(sig, msg0, otherPk)
not verify(sig, msg1, pk) not verify(sig, msg1, pk)
recover(sig2, msg0)[] == pk recover(sig2, msg0)[] == pk
recover(sig2, msg1)[] != pk recover(sig2, msg1)[] != pk
SkSignature.fromDer(sig.toDer())[].toHex() == sig.toHex() 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": test "Message":
check: check:
SkMessage.fromBytes([]).isErr() SkMessage.fromBytes([]).isErr()