From 5701a60143e8ff1e4885262797abf6875f340b51 Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Mon, 22 Jun 2020 16:08:21 +0200 Subject: [PATCH] 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 --- secp256k1.nim | 330 +++++++++++++++++++++------------------ tests/test_secp256k1.nim | 38 +---- 2 files changed, 184 insertions(+), 184 deletions(-) diff --git a/secp256k1.nim b/secp256k1.nim index b0d8da6..c061e73 100644 --- a/secp256k1.nim +++ b/secp256k1.nim @@ -25,6 +25,12 @@ export results # 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 @@ -32,6 +38,8 @@ export results # * 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 @@ -60,23 +68,26 @@ const ## ECDH-agreed raw key size type - SkPublicKey* = secp256k1_pubkey + SkPublicKey* {.requiresInit.} = object ## Representation of public key. + data: secp256k1_pubkey - SkSecretKey* = object + SkSecretKey* {.requiresInit.} = object ## Representation of secret key. - data*: array[SkRawSecretKeySize, byte] + data: array[SkRawSecretKeySize, byte] SkKeyPair* = object ## Representation of private/public keys pair. seckey*: SkSecretKey pubkey*: SkPublicKey - SkSignature* = secp256k1_ecdsa_signature + SkSignature* {.requiresInit.} = object ## Representation of non-recoverable signature. + data: secp256k1_ecdsa_signature - SkRecoverableSignature* = secp256k1_ecdsa_recoverable_signature + SkRecoverableSignature* {.requiresInit.} = object ## Representation of recoverable signature. + data: secp256k1_ecdsa_recoverable_signature SkContext* = ref object ## Representation of Secp256k1 context object. @@ -85,11 +96,11 @@ type SkMessage* = MDigest[SkMessageSize * 8] ## Message that can be signed or verified - SkEcdhSecret* = object + SkEcdhSecret* {.requiresInit.} = object ## Representation of ECDH shared secret data*: array[SkEdchSecretSize, byte] - SkEcdhRawSecret* = object + SkEcdhRawSecret* {.requiresInit.} = object ## Representation of ECDH shared secret, with leading `y` byte # (`y` is 0x02 when pubkey.y is even or 0x03 when odd) data*: array[SkEcdhRawSecretSize, byte] @@ -104,15 +115,11 @@ var secpContext {.threadvar.}: SkContext ## Thread local variable which holds current context proc illegalCallback(message: cstring, data: pointer) {.cdecl, raises: [].} = - # 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 + # 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 @@ -123,7 +130,7 @@ proc errorCallback(message: cstring, data: pointer) {.cdecl, raises: [].} = template ptr0(v: array|openArray): ptr cuchar = cast[ptr cuchar](unsafeAddr v[0]) -proc shutdownLibsecp256k1(ctx: SkContext) = +func shutdownLibsecp256k1(ctx: SkContext) = # TODO: use destructor when finalizer are deprecated for destructors if not(isNil(ctx.context)): secp256k1_context_destroy(ctx.context) @@ -140,62 +147,67 @@ proc newSkContext(): SkContext = func getContext(): ptr secp256k1_context = ## 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): secpContext = newSkContext() 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? try: ok(hexToSeqByte(s)) except CatchableError: 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] = ## Generates new random private key. - var sk: T - while randomBytes(sk.data) == SkRawSecretKeySize: - if sk.verify(): - return ok(sk) + var data{.noinit.}: array[SkRawSecretKeySize, byte] + + while randomBytes(data) == SkRawSecretKeySize: + 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") -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` 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.ptr0) != 1: - return err("secp: invalid private key") + {.noSideEffect.}: # secp256k1_context_no_precomp is actually const, see above + if secp256k1_ec_seckey_verify(secp256k1_context_no_precomp, data.ptr0) != 1: + return err("secp: invalid private key") 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 ## representation ``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 seckey.data -proc toHex*(seckey: SkSecretKey): string = +func toHex*(seckey: SkSecretKey): string = toHex(toRaw(seckey)) -proc toPublicKey*(key: SkSecretKey): SkResult[SkPublicKey] = +func toPublicKey*(key: SkSecretKey): SkPublicKey = ## Calculate and return Secp256k1 `public key` from `private key` ``key``. - var pubkey: SkPublicKey - if secp256k1_ec_pubkey_create(getContext(), addr pubkey, key.data.ptr0) != 1: - return err("secp: cannot create pubkey, private key invalid?") + var pubkey {.noinit.}: secp256k1_pubkey + let res = secp256k1_ec_pubkey_create( + 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 ## representation ``data``, which may be compressed, uncompressed or hybrid if len(data) < SkRawCompressedPublicKeySize: @@ -210,127 +222,139 @@ proc fromRaw*(T: type SkPublicKey, data: openArray[byte]): SkResult[T] = else: return err("secp: public key format not recognised") - var key: SkPublicKey - if secp256k1_ec_pubkey_parse( - getContext(), addr key, data.ptr0, csize_t(length)) != 1: - return err("secp: cannot parse public key") + var key {.noinit.}: secp256k1_pubkey + {.noSideEffect.}: # secp256k1_context_no_precomp is actually const, see above + if secp256k1_ec_pubkey_parse( + secp256k1_context_no_precomp, addr key, data.ptr0, csize_t(length)) != 1: + 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 ## representation ``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 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)) -proc toRawCompressed*(pubkey: SkPublicKey): array[SkRawCompressedPublicKeySize, byte] = +func toRawCompressed*(pubkey: SkPublicKey): array[SkRawCompressedPublicKeySize, byte] = ## Serialize Secp256k1 `public key` ``key`` to raw compressed form 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_COMPRESSED) + {.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_COMPRESSED) + doAssert res == 1, "Can't fail, per documentation" -proc toHexCompressed*(pubkey: SkPublicKey): string = +func toHexCompressed*(pubkey: SkPublicKey): string = 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 if data.len() < SkRawSignatureSize: return err(static(&"secp: signature must be {SkRawSignatureSize} bytes")) - var sig: SkSignature - if secp256k1_ecdsa_signature_parse_compact( - getContext(), addr sig, data.ptr0) != 1: - return err("secp: cannot parse signaure") + var sig {.noinit.}: secp256k1_ecdsa_signature + {.noSideEffect.}: # secp256k1_context_no_precomp is actually const, see above + if secp256k1_ecdsa_signature_parse_compact( + secp256k1_context_no_precomp, addr sig, data.ptr0) != 1: + 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 ## representation ``data``. if len(data) < 1: return err("secp: DER signature too short") - var sig: T - if secp256k1_ecdsa_signature_parse_der( - getContext(), addr sig, data.ptr0, csize_t(len(data))) != 1: - return err("secp: cannot parse DER signature") + var sig {.noinit.}: secp256k1_ecdsa_signature + {.noSideEffect.}: # secp256k1_context_no_precomp is actually const, see above + if secp256k1_ecdsa_signature_parse_der( + secp256k1_context_no_precomp, addr sig, data.ptr0, csize_t(len(data))) != 1: + 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 ## representation ``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 - # Can't fail, per documentation - discard secp256k1_ecdsa_signature_serialize_compact( - getContext(), result.ptr0, unsafeAddr sig) + {.noSideEffect.}: # secp256k1_context_no_precomp is actually const, see above + let res = secp256k1_ecdsa_signature_serialize_compact( + 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 ## to ``data``. ## - ## Procedure returns number of bytes (octets) needed to store - ## Secp256k1 signature. - let ctx = getContext() + ## 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)) - discard secp256k1_ecdsa_signature_serialize_der( - ctx, buffer.ptr0, addr plength, unsafeAddr sig) + {.noSideEffect.}: # secp256k1_context_no_precomp is actually const, see above + 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) if len(data) >= 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. result = newSeq[byte](72) let length = toDer(sig, result) result.setLen(length) -proc toHex*(sig: SkSignature): string = +func toHex*(sig: SkSignature): string = 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: return err( static(&"secp: recoverable signature must be {SkRawRecoverableSignatureSize} bytes")) let recid = cint(data[64]) - var sig: SkRecoverableSignature - if secp256k1_ecdsa_recoverable_signature_parse_compact( - getContext(), addr sig, data.ptr0, recid) != 1: - return err("secp: invalid recoverable signature") + var sig {.noinit.}: secp256k1_ecdsa_recoverable_signature + {.noSideEffect.}: # secp256k1_context_no_precomp is actually const, see above + if secp256k1_ecdsa_recoverable_signature_parse_compact( + secp256k1_context_no_precomp, addr sig, data.ptr0, recid) != 1: + 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 ## representation ``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 var recid = cint(0) - # Can't fail, per documentation - discard secp256k1_ecdsa_recoverable_signature_serialize_compact( - getContext(), result.ptr0, addr recid, unsafeAddr sig) + {.noSideEffect.}: # secp256k1_context_no_precomp is actually const, see above + let res = secp256k1_ecdsa_recoverable_signature_serialize_compact( + secp256k1_context_no_precomp, result.ptr0, addr recid, unsafeAddr sig.data) + doAssert res == 1, "can't fail, per documentation" + result[64] = byte(recid) -proc toHex*(sig: SkRecoverableSignature): string = +func toHex*(sig: SkRecoverableSignature): string = toHex(toRaw(sig)) proc random*(T: type SkKeyPair): SkResult[T] = @@ -338,103 +362,105 @@ proc random*(T: type SkKeyPair): SkResult[T] = let seckey = ? SkSecretKey.random() ok(T( 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. lhs.toRaw() == rhs.toRaw() -proc `==`*(lhs, rhs: SkSignature): bool = +func `==`*(lhs, rhs: SkSignature): bool = ## Compare Secp256k1 `signature` objects for equality. lhs.toRaw() == rhs.toRaw() -proc `==`*(lhs, rhs: SkRecoverableSignature): bool = +func `==`*(lhs, rhs: SkRecoverableSignature): bool = ## Compare Secp256k1 `recoverable signature` objects for equality. 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. - 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?") + var data {.noinit.}: secp256k1_ecdsa_signature + let res = secp256k1_ecdsa_sign( + getContext(), addr data, msg.data.ptr0, key.data.ptr0, nil, nil) + doAssert res == 1, "cannot create signature, key invalid?" + SkSignature(data: data) - ok(sig) - -proc signRecoverable*(key: SkSecretKey, msg: SkMessage): SkResult[SkRecoverableSignature] = +func signRecoverable*(key: SkSecretKey, msg: SkMessage): 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?") + var data {.noinit.}: secp256k1_ecdsa_recoverable_signature + let res = secp256k1_ecdsa_sign_recoverable( + getContext(), addr data, msg.data.ptr0, key.data.ptr0, nil, nil) + doAssert res == 1, "cannot create recoverable signature, key invalid?" + SkRecoverableSignature(data: data) - ok(sig) - -proc verify*(sig: SkSignature, msg: SkMessage, key: SkPublicKey): bool = +func verify*(sig: SkSignature, msg: SkMessage, key: SkPublicKey): bool = 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] = - var pubkey: SkPublicKey +func recover*(sig: SkRecoverableSignature, msg: SkMessage): SkResult[SkPublicKey] = + var data {.noinit.}: secp256k1_pubkey 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") - 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. - var secret: SkEcdhSecret - if secp256k1_ecdh( - getContext(), secret.data.ptr0, unsafeAddr pubkey, seckey.data.ptr0) != 1: - return err("secp: cannot compute ECDH secret") + var secret {.noinit.}: array[SkEdchSecretSize, byte] + {.noSideEffect.}: # secp256k1_context_no_precomp is actually const, see above + let res = secp256k1_ecdh( + 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 # 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") + var secret {.noinit.}: array[SkEcdhRawSecretSize, byte] + {.noSideEffect.}: # secp256k1_context_no_precomp is actually const, see above + let res = secp256k1_ecdh_raw( + 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`. + ## After calling this function, the key is invalid and using it elsewhere will + ## result in undefined behaviour or Defect burnMem(v.data) -proc clear*(v: var SkPublicKey) {.inline.} = - ## Wipe and clear memory of Secp256k1 `public key`. +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) -proc clear*(v: var SkSignature) {.inline.} = - ## Wipe and clear memory of Secp256k1 `signature`. +func clear*(v: var SkEcdhRawSecret) = + ## 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) -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 `$`*( +func `$`*( v: SkPublicKey | SkSecretKey | SkSignature | SkRecoverableSignature): string = 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: return err("Message must be 32 bytes") 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".} diff --git a/tests/test_secp256k1.nim b/tests/test_secp256k1.nim index 799388e..0cb3eaa 100644 --- a/tests/test_secp256k1.nim +++ b/tests/test_secp256k1.nim @@ -15,57 +15,31 @@ 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") + pk = sk.toPublicKey() 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)[] + pk = sk.toPublicKey() + otherPk = SkSecretKey.random()[].toPublicKey() + sig = sign(sk, msg0) + sig2 = signRecoverable(sk, msg0) check: verify(sig, msg0, pk) - not verify(sig, msg0, badPk) + not verify(sig, msg0, otherPk) 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()