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:
parent
e8fa92a0c6
commit
5701a60143
310
secp256k1.nim
310
secp256k1.nim
@ -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".}
|
||||||
|
@ -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()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user