diff --git a/eth/keys.nim b/eth/keys.nim index dc03690..8645b58 100644 --- a/eth/keys.nim +++ b/eth/keys.nim @@ -6,105 +6,95 @@ # - MIT license (LICENSE-MIT) # -import nimcrypto/hash, nimcrypto/keccak +# This module contains adaptations of the general secp interface to help make +# working with keys and signatures as they appear in Ethereum in particular: +# +# * Public keys as serialized in uncompressed format without the initial byte +# * Shared secrets are serialized in raw format without the intial byte + +import + nimcrypto/hash, nimcrypto/keccak, ./keys/secp, + stew/[byteutils, objects, result], strformat + +export secp, result + +const + KeyLength* = SkEcdhRawSecretSize - 1 + ## Shared secret key length without format marker + RawPublicKeySize* = SkRawPublicKeySize - 1 + ## Size of uncompressed public key without format marker (0x04) + RawSignatureSize* = SkRawRecoverableSignatureSize type - EthKeysStatus* = enum - Success, ## Operation was successful - Error ## Operation failed + PrivateKey* = distinct SkSecretKey - EthKeysException* = object of CatchableError - ## Exception generated by this module + PublicKey* = distinct SkPublicKey + ## Public key that's serialized to raw format without 0x04 marker + Signature* = distinct SkRecoverableSignature + ## Ethereum uses recoverable signatures allowing some space savings + SignatureNR* = distinct SkSignature + ## ...but ENR uses non-recoverable signatures! -when not defined(native): - include eth/keys/libsecp256k1 + SharedSecretFull* = SkEcdhRawSecret + SharedSecret* = object + data*: array[KeyLength, byte] - proc ekErrorMsg*(): string {.inline.} = - ## Return current error message. - result = libsecp256k1ErrorMsg() + KeyPair* = object + seckey*: PrivateKey + pubkey*: PublicKey -proc isZeroKey*(seckey: PrivateKey): bool = - ## Check if private key `seckey` contains only 0 bytes. - result = true - for i in seckey.data: - if i != byte(0): - result = false +proc toPublicKey*(seckey: PrivateKey): SkResult[PublicKey] = + SkSecretKey(seckey).toPublicKey().mapConvert(PublicKey) -proc isZeroKey*(pubkey: PublicKey): bool = - ## Check if public key `pubkey` contains only 0 bytes. - result = true - for i in pubkey.data: - if i != byte(0): - result = false +proc fromRaw*(T: type PrivateKey, data: openArray[byte]): SkResult[PrivateKey] = + SkSecretKey.fromRaw(data).mapConvert(PrivateKey) -proc signMessage*(seckey: PrivateKey, - data: openarray[byte]): Signature {.inline.} = - ## Sign message of arbitrary length `data` using private key `seckey`. - let hash = keccak256.digest(data) - if signRawMessage(hash.data, seckey, result) != EthKeysStatus.Success: - raise newException(EthKeysException, ekErrorMsg()) +proc fromHex*(T: type PrivateKey, data: string): SkResult[PrivateKey] = + SkSecretKey.fromHex(data).mapConvert(PrivateKey) -proc signMessage*(seckey: PrivateKey, data: string): Signature {.inline.} = - ## Sign message of arbitrary length `data` using private key `seckey`. - signMessage(seckey, cast[seq[byte]](data)) +proc fromRaw*(T: type PublicKey, data: openArray[byte]): SkResult[T] = + if data.len() == SkRawCompressedPubKeySize: + return SkPublicKey.fromRaw(data).mapConvert(PublicKey) -proc signMessage*(seckey: PrivateKey, - hash: MDigest[256]): Signature {.inline.} = - ## Sign 256bit `hash` using private key `seckey`. - result = signMessage(seckey, hash.data) + if len(data) < SkRawPublicKeySize - 1: + return err(&"keys: raw eth public key should be {SkRawPublicKeySize - 1} bytes") -proc verifyMessage*(data: openarray[byte], message: openarray[byte]): bool = - ## Verify binary data blob `data` has properly signed message `message`. - var pubkey: PublicKey - if recoverSignatureKey(data, message, pubkey) == EthKeysStatus.Success: - result = true - else: - result = false + var d: array[SkRawPublicKeySize, byte] + d[0] = 0x04'u8 + copyMem(addr d[1], unsafeAddr data[0], 64) -proc verifyMessage*(data: openarray[byte], - hash: MDigest[256]): bool {.inline.} = - ## Verify binary data blob `data` has properly signed hash `hash`. - result = verifyMessage(data, hash.data) + SkPublicKey.fromRaw(d).mapConvert(PublicKey) -proc recoverKeyFromMessage*(data: openarray[byte], - hash: MDigest[256]): PublicKey {.inline.} = - ## Recover public key from signed binary blob `data` using 256bit hash `hash`. - if recoverSignatureKey(data, hash.data, result) != EthKeysStatus.Success: - raise newException(EthKeysException, ekErrorMsg()) +proc fromHex*(T: type PublicKey, data: string): SkResult[PublicKey] = + try: + # TODO strip string? + T.fromRaw(hexToSeqByte(data)) + except CatchableError: + err("keys: cannot parse eth public key") -proc recoverKeyFromMessage*(data: openarray[byte], - message: string): PublicKey {.inline.} = - ## Recover public key from signed binary blob `data` using `message`. - var hash = keccak256.digest(message) - if recoverSignatureKey(data, hash.data, result) != EthKeysStatus.Success: - raise newException(EthKeysException, ekErrorMsg()) +proc random*(t: type KeyPair): SkResult[KeyPair] = + let tmp = ?SkKeypair.random() + ok(KeyPair(seckey: PrivateKey(tmp.seckey), pubkey: PublicKey(tmp.pubkey))) -proc recoverKeyFromSignature*(signature: Signature, - message: string): PublicKey {.inline.} = - ## Recover public key from signature `signature` using `message`. - var hash = keccak256.digest(message) - if recoverSignatureKey(signature, hash.data, result) != EthKeysStatus.Success: - raise newException(EthKeysException, ekErrorMsg()) +proc toRaw*(pubkey: PublicKey): array[64, byte] = + let tmp = SkPublicKey(pubkey).toRaw() + copyMem(addr result[0], unsafeAddr tmp[1], 64) -proc recoverKeyFromSignature*(signature: Signature, - hash: MDigest[256]): PublicKey {.inline.} = - ## Recover public key from signature `signature` using `message`. - if recoverSignatureKey(signature, hash.data, result) != EthKeysStatus.Success: - raise newException(EthKeysException, ekErrorMsg()) +proc toRawCompressed*(pubkey: PublicKey): array[33, byte] {.borrow.} proc toAddress*(pubkey: PublicKey, with0x = true): string = ## Convert public key to hexadecimal string address. - var hash = keccak256.digest(pubkey.getRaw()) + var hash = keccak256.digest(pubkey.toRaw()) result = if with0x: "0x" else: "" - result.add(toHex(toOpenArray(hash.data, 12, len(hash.data) - 1), true)) + result.add(toHex(toOpenArray(hash.data, 12, len(hash.data) - 1))) proc toChecksumAddress*(pubkey: PublicKey, with0x = true): string = ## Convert public key to checksumable mixed-case address (EIP-55). result = if with0x: "0x" else: "" - var hash1 = keccak256.digest(pubkey.getRaw()) - var hhash1 = toHex(toOpenArray(hash1.data, 12, len(hash1.data) - 1), true) + var hash1 = keccak256.digest(pubkey.toRaw()) + var hhash1 = toHex(toOpenArray(hash1.data, 12, len(hash1.data) - 1)) var hash2 = keccak256.digest(hhash1) - var hhash2 = toHex(hash2.data, true) + var hhash2 = toHex(hash2.data) for i in 0..= '0' and hhash2[i] <= '7': result.add(hhash1[i]) @@ -132,7 +122,7 @@ proc validateChecksumAddress*(a: string): bool = else: return false var hash = keccak256.digest(address) - var hexhash = toHex(hash.data, true) + var hexhash = toHex(hash.data) for i in 0..= '0' and hexhash[i] <= '7': check.add(address[i]) @@ -144,19 +134,293 @@ proc validateChecksumAddress*(a: string): bool = check.add(ch) result = (check == a) -proc toCanonicalAddress*(pubkey: PublicKey): array[20, byte] = +func toCanonicalAddress*(pubkey: PublicKey): array[20, byte] = ## Convert public key to canonical address. - var hash = keccak256.digest(pubkey.getRaw()) + var hash = keccak256.digest(pubkey.toRaw()) copyMem(addr result[0], addr hash.data[12], 20) -proc `$`*(pubkey: PublicKey): string = +func `$`*(pubkey: PublicKey): string = ## Convert public key to hexadecimal string representation. - result = toHex(pubkey.getRaw(), true) + toHex(pubkey.toRaw()) -proc `$`*(sig: Signature): string = +func `$`*(sig: Signature): string = ## Convert signature to hexadecimal string representation. - result = toHex(sig.getRaw(), true) + toHex(SkRecoverableSignature(sig).toRaw()) -proc `$`*(seckey: PrivateKey): string = +func `$`*(seckey: PrivateKey): string = ## Convert private key to hexadecimal string representation - result = toHex(seckey.data, true) + toHex(SkSecretKey(seckey).toRaw()) + +proc `==`*(lhs, rhs: PublicKey): bool {.borrow.} + +proc random*(T: type PrivateKey): SkResult[PrivateKey] = + SkSecretKey.random().mapConvert(PrivateKey) + +proc toRaw*(key: PrivateKey): array[SkRawSecretKeySize, byte] {.borrow.} + +# Backwards compat - the functions in here are deprecated and should be moved +# reimplemented using functions that return Result instead! + +from nimcrypto/utils import stripSpaces + +type + EthKeysException* {.deprecated.} = object of CatchableError + Secp256k1Exception* {.deprecated.} = object of CatchableError + + EthKeysStatus* {.deprecated.} = enum + Success + Error + +template data*(pubkey: PublicKey): auto = + SkPublicKey(pubkey).data + +template data*(seckey: PrivateKey): auto = + SkSecretKey(seckey).data + +template data*(sig: Signature): auto = + SkRecoverableSignature(sig).data + +proc isZeroKey*(seckey: PrivateKey): bool {.deprecated.} = + ## Check if private key `seckey` contains only 0 bytes. + # TODO this is a weird check - better would be to check if the key is valid! + result = true + for i in seckey.data: # constant time, loop all bytes always + if i != byte(0): + result = false + +proc isZeroKey*(pubkey: PublicKey): bool {.deprecated.} = + ## Check if public key `pubkey` contains only 0 bytes. + # TODO this is a weird check - better would be to check if the key is valid! + result = true + for i in pubkey.data: # constant time, loop all bytes always + if i != byte(0): + result = false + +proc newPrivateKey*(): PrivateKey {.deprecated: "random".} = + let key = PrivateKey.random() + if key.isErr: + raise newException(Secp256k1Exception, $key.error) + key[] + +proc newKeyPair*(): KeyPair {.deprecated: "random".} = + let kp = KeyPair.random() + if kp.isErr: + raise newException(Secp256k1Exception, $kp.error) + kp[] + +proc getPublicKey*(seckey: PrivateKey): PublicKey {.deprecated: "toPublicKey".} = + let key = seckey.toPublicKey() + if key.isErr: + raise newException(Secp256k1Exception, "invalid private key") + PublicKey(key[]) + +proc ecdhAgree*( + seckey: PrivateKey, pubkey: PublicKey, + s: var SharedSecret): EthKeysStatus {.deprecated.} = + let v = ecdhRaw( + SkSecretKey(seckey), SkPublicKey(pubkey)).map proc(v: auto): SharedSecret = + copyMem(addr result.data[0], unsafeAddr(v.data[1]), sizeof(result)) + + if v.isOk(): + s = v[] + return Success + return Error + +proc getRaw*( + pubkey: PublicKey): array[RawPublicKeySize, byte] {.deprecated: "toRaw".} = + pubkey.toRaw() + +proc getRawCompressed*( + pubkey: PublicKey): array[SkRawCompressedPubKeySize, byte] {. + deprecated: "toRawCompressed".} = + pubkey.toRawCompressed() + +proc recoverPublicKey*( + data: openArray[byte], pubkey: var PublicKey): EthKeysStatus {. + deprecated: "fromRaw".} = + let v = PublicKey.fromRaw(data) + if v.isOk(): + pubkey = v[] + return Success + + return Error + +proc signRawMessage*(data: openarray[byte], seckey: PrivateKey, + signature: var Signature): EthKeysStatus {.deprecated.} = + if len(data) != SkMessageSize: + return Error + let sig = signRecoverable( + SkSecretKey(seckey), SkMessage(data: toArray(32, data.toOpenArray(0, 31)))) + if sig.isOk(): + signature = Signature(sig[]) + return Success + + return Error + +proc signRawMessage*(data: openarray[byte], seckey: PrivateKey, + signature: var SignatureNR): EthKeysStatus {.deprecated.} = + ## Sign message `data` of `KeyLength` size using private key `seckey` and + ## store result into `signature`. + let length = len(data) + if length != KeyLength: + return(EthKeysStatus.Error) + let sig = sign( + SkSecretKey(seckey), SkMessage(data: toArray(32, data.toOpenArray(0, 31)))) + if sig.isOk(): + signature = SignatureNR(sig[]) + return Success + + return Error + +proc signMessage*(seckey: PrivateKey, + data: openarray[byte]): Signature {.deprecated.} = + let hash = keccak256.digest(data) + if signRawMessage(hash.data, seckey, result) != EthKeysStatus.Success: + raise newException(EthKeysException, "signature failed") + +proc getRaw*( + s: SignatureNR): array[SkRawSignatureSize, byte] {.deprecated: "toRaw".} = + ## Converts signature `s` to serialized form. + SkSignature(s).toRaw() + +proc getRaw*( + s: Signature): array[SkRawRecoverableSignatureSize, byte] {. + deprecated: "toRaw".} = + ## Converts signature `s` to serialized form. + SkRecoverableSignature(s).toRaw() + +proc recoverSignatureKey*(signature: Signature, + msg: openarray[byte], + pubkey: var PublicKey): EthKeysStatus {.deprecated.} = + if len(msg) < SkMessageSize: + return Error + let pk = recover( + SkRecoverableSignature(signature), + SkMessage(data: toArray(32, msg.toOpenArray(0, 31)))) + if pk.isErr(): return Error + + pubkey = PublicKey(pk[]) + return Success + +proc recoverSignatureKey*(data: openarray[byte], + msg: openarray[byte], + pubkey: var PublicKey): EthKeysStatus {.deprecated.} = + let signature = SkRecoverableSignature.fromRaw(data) + if signature.isErr(): return Error + + if len(msg) < SkMessageSize: + return Error + let pk = recover( + SkRecoverableSignature(signature[]), + SkMessage(data: toArray(32, msg.toOpenArray(0, 31)))) + if pk.isErr(): return Error + + pubkey = PublicKey(pk[]) + return Success + +proc initPrivateKey*( + data: openArray[byte]): PrivateKey {.deprecated: "PrivateKey.fromRaw".} = + let res = PrivateKey.fromRaw(data) + if res.isOk(): + return res[] + + raise (ref EthKeysException)(msg: $res.error) + +proc initPrivateKey*( + data: string): PrivateKey {.deprecated: "PrivateKey.fromHex".} = + let res = PrivateKey.fromHex(stripSpaces(data)) + if res.isOk(): + return res[] + + raise (ref EthKeysException)(msg: $res.error) + +proc initPublicKey*( + hexstr: string): PublicKey {.deprecated: "PublicKey.fromHex".} = + let pk = PublicKey.fromHex(stripSpaces(hexstr)) + if pk.isOk(): return pk[] + + raise newException(EthKeysException, $pk.error) + +proc initPublicKey*(data: openarray[byte]): PublicKey {.deprecated.} = + let pk = PublicKey.fromRaw(data) + if pk.isOk(): return pk[] + + raise newException(EthKeysException, $pk.error) + +proc signMessage*(seckey: PrivateKey, data: string): Signature {.deprecated.} = + signMessage(seckey, cast[seq[byte]](data)) + +proc toKeyPair*(key: PrivateKey): KeyPair {.deprecated.} = + KeyPair(seckey: key, pubkey: key.getPublicKey()) + +proc initSignature*(data: openArray[byte]): Signature {.deprecated.} = + let sig = SkRecoverableSignature.fromRaw(data) + if sig.isOk(): return Signature(sig[]) + + raise newException(EthKeysException, $sig.error) + +proc initSignature*(hexstr: string): Signature {.deprecated.} = + let sig = SkRecoverableSignature.fromHex(stripSpaces(hexstr)) + if sig.isOk(): return Signature(sig[]) + + raise newException(EthKeysException, $sig.error) + +proc recoverSignature*(data: openarray[byte], + signature: var Signature): EthKeysStatus {.deprecated.} = + ## Deprecated, use `parseCompact` instead + if data.len < RawSignatureSize: + return(EthKeysStatus.Error) + let sig = SkRecoverableSignature.fromRaw(data) + if sig.isErr(): + return Error + signature = Signature(sig[]) + return Success + +proc recoverKeyFromSignature*(signature: Signature, + hash: MDigest[256]): PublicKey {.deprecated.} = + ## Recover public key from signature `signature` using `message`. + let key = recover(SkRecoverableSignature(signature), hash) + if key.isOk(): + return PublicKey(key[]) + raise newException(EthKeysException, $key.error) + +proc recoverKeyFromSignature*( + signature: Signature, + message: openArray[byte]): PublicKey {.deprecated.} = + let hash = keccak256.digest(message) + recoverKeyFromSignature(signature, hash) + +proc recoverKeyFromSignature*( + signature: Signature, data: string): PublicKey {.deprecated.} = + recoverKeyFromSignature(signature, cast[seq[byte]](data)) + +proc parseCompact*( + signature: var SignatureNR, + data: openarray[byte]): EthKeysStatus {.deprecated.} = + let sig = SkSignature.fromRaw(data) + if sig.isErr(): + return Error + + signature = SignatureNR(sig[]) + return Success + +proc verifySignatureRaw*( + signature: SignatureNR, message: openarray[byte], + publicKey: PublicKey): EthKeysStatus {.deprecated.} = + ## Verify `signature` using original `message` (32 bytes) and `publicKey`. + if verify( + SkSignature(signature), + SkMessage(data: toArray(32, message.toOpenArray(0, 31))), + SkPublicKey(publicKey)): + return Success + + return Error + +proc ecdhAgree*( + seckey: PrivateKey, pubkey: PublicKey, + s: var SharedSecretFull): EthKeysStatus {.deprecated.} = + let v = ecdhRaw(SkSecretKey(seckey), SkPublicKey(pubkey)) + if v.isOk(): + s = SharedSecretFull(v[]) + return Success + return Error diff --git a/eth/keys/libsecp256k1.nim b/eth/keys/libsecp256k1.nim deleted file mode 100644 index 1175f0a..0000000 --- a/eth/keys/libsecp256k1.nim +++ /dev/null @@ -1,414 +0,0 @@ -# -# Nim Ethereum Keys (nim-eth-keys) -# Copyright (c) 2018 Status Research & Development GmbH -# Licensed under either of -# - Apache License, version 2.0, (LICENSE-APACHEv2) -# - MIT license (LICENSE-MIT) -# - -## This is libsecp256k1 backend. - -import secp256k1, nimcrypto/sysrand, nimcrypto/utils - -const - KeyLength* = 256 div 8 - CompressedPubKeyLength* = 33 - RawSignatureNRSize* = KeyLength * 2 # Non-recoverable signature - RawSignatureSize* = RawSignatureNRSize + 1 # Recoverable - RawPublicKeySize* = KeyLength * 2 - InvalidPrivateKey = "Invalid private key!" - InvalidPublicKey = "Invalid public key!" - InvalidSignature = "Invalid signature!" - VerificationFailed = "Signature verification has been failed!" - MessageSizeError = "Size of message to sign must be KeyLength bytes!" - -type - PublicKey* = secp256k1_pubkey - ## Representation of public key - - PrivateKey* = object - ## Representation of secret key - data*: array[KeyLength, byte] - - SharedSecret* = object - ## Representation of ECDH shared secret - data*: array[KeyLength, byte] - - SharedSecretFull* = object - ## Representation of ECDH shared secret, with leading `y` byte - # (`y` is 0x02 when pubkey.y is even or 0x03 when odd) - data*: array[1 + KeyLength, byte] - - KeyPair* = object - ## Representation of private/public keys pair - seckey*: PrivateKey - pubkey*: PublicKey - - Signature* = secp256k1_ecdsa_recoverable_signature - ## Representation of signature - - SignatureNR* = secp256k1_ecdsa_signature - ## Representation of non-recoverable signature - - Secp256k1Exception* = object of CatchableError - ## Exceptions generated by `libsecp256k1` - - EthKeysContext = ref object - context: ptr secp256k1_context - error: string - -var ekContext {.threadvar.}: EthKeysContext - ## Thread local variable which holds current context - -## -## Private procedures interface -## - -proc illegalCallback(message: cstring; data: pointer) {.cdecl.} = - let ctx = cast[EthKeysContext](data) - ctx.error = $message - -proc errorCallback(message: cstring, data: pointer) {.cdecl.} = - let ctx = cast[EthKeysContext](data) - ctx.error = $message - -proc shutdownLibsecp256k1(ekContext: EthKeysContext) = - # TODO: use destructor when finalizer are deprecated for destructors - if not isNil(ekContext.context): - secp256k1_context_destroy(ekContext.context) - -proc newEthKeysContext(): EthKeysContext = - ## Create new `EthKeysContext`. - new(result, shutdownLibsecp256k1) - let flags = cuint(SECP256K1_CONTEXT_VERIFY or SECP256K1_CONTEXT_SIGN) - result.context = secp256k1_context_create(flags) - secp256k1_context_set_illegal_callback(result.context, illegalCallback, - cast[pointer](result)) - secp256k1_context_set_error_callback(result.context, errorCallback, - cast[pointer](result)) - result.error = "" - -proc getSecpContext(): ptr secp256k1_context = - ## Get current `secp256k1_context` - if isNil(ekContext): - ekContext = newEthKeysContext() - result = ekContext.context - -proc getContext(): EthKeysContext = - ## Get current `EccContext` - if isNil(ekContext): - ekContext = newEthKeysContext() - result = ekContext - -template raiseSecp256k1Error() = - ## Raises `libsecp256k1` error as exception - let mctx = getContext() - if len(mctx.error) > 0: - var msg = mctx.error - mctx.error.setLen(0) - raise newException(Secp256k1Exception, msg) - -proc libsecp256k1ErrorMsg(): string = - let mctx = getContext() - result = mctx.error - -proc setErrorMsg(m: string) = - let mctx = getContext() - mctx.error = m - -## -## Public procedures interface -## - -proc newPrivateKey*(): PrivateKey = - ## Generates new private key. - let ctx = getSecpContext() - while true: - if randomBytes(result.data) == KeyLength: - if secp256k1_ec_seckey_verify(ctx, cast[ptr cuchar](addr result)) == 1: - break - -proc getPublicKey*(seckey: PrivateKey): PublicKey = - ## Return public key for private key `seckey`. - let ctx = getSecpContext() - if secp256k1_ec_pubkey_create(ctx, addr result, - cast[ptr cuchar](unsafeAddr seckey)) != 1: - raiseSecp256k1Error() - -proc toKeyPair*(key: PrivateKey): KeyPair = - KeyPair(seckey: key, pubkey: key.getPublicKey()) - -proc newKeyPair*(): KeyPair = - ## Generates new private and public key. - result.seckey = newPrivateKey() - result.pubkey = result.seckey.getPublicKey() - -proc initPrivateKey*(hexstr: string): PrivateKey = - ## Create new private key from hexadecimal string representation. - let ctx = getSecpContext() - var o = fromHex(stripSpaces(hexstr)) - if len(o) < KeyLength: - raise newException(EthKeysException, InvalidPrivateKey) - copyMem(addr result, addr o[0], KeyLength) - if secp256k1_ec_seckey_verify(ctx, cast[ptr cuchar](addr result)) != 1: - raise newException(EthKeysException, InvalidPrivateKey) - -proc initPrivateKey*(data: openarray[byte]): PrivateKey = - ## Create new private key from binary data blob. - let ctx = getSecpContext() - if len(data) < KeyLength: - raise newException(EthKeysException, InvalidPrivateKey) - copyMem(addr result, unsafeAddr data[0], KeyLength) - if secp256k1_ec_seckey_verify(ctx, cast[ptr cuchar](addr result)) != 1: - raise newException(EthKeysException, InvalidPrivateKey) - -proc recoverPublicKey*(data: openarray[byte], - pubkey: var PublicKey): EthKeysStatus = - ## Unserialize public key from `data`. - let ctx = getSecpContext() - let length = len(data) - if length >= RawPublicKeySize: - var rawkey: array[RawPublicKeySize + 1, byte] - rawkey[0] = 0x04'u8 # mark key with UNCOMPRESSED flag - copyMem(addr rawkey[1], unsafeAddr data[0], RawPublicKeySize) - if secp256k1_ec_pubkey_parse(ctx, addr pubkey, - cast[ptr cuchar](addr rawkey), - RawPublicKeySize + 1) != 1: - return(EthKeysStatus.Error) - elif length == CompressedPubKeyLength: - # Compressed format - if secp256k1_ec_pubkey_parse(ctx, addr pubkey, - cast[ptr cuchar](unsafeAddr data), - length) != 1: - return(EthKeysStatus.Error) - else: - setErrorMsg(InvalidPublicKey) - return(EthKeysStatus.Error) - - result = EthKeysStatus.Success - -proc parseCompact*(signature: var SignatureNR, data: openarray[byte]): EthKeysStatus = - let ctx = getSecpContext() - let length = len(data) - if length == RawSignatureNRSize: - if secp256k1_ecdsa_signature_parse_compact(ctx, addr signature, - cast[ptr cuchar](unsafeAddr data[0])) != 1: - return(EthKeysStatus.Error) - else: - setErrorMsg(InvalidSignature) - return(EthKeysStatus.Error) - result = EthKeysStatus.Success - -proc parseCompact*(signature: var Signature, data: openarray[byte]): EthKeysStatus = - ## Unserialize signature from `data`. - let ctx = getSecpContext() - let length = len(data) - if length != RawSignatureSize: - setErrorMsg(InvalidSignature) - return(EthKeysStatus.Error) - var recid = cint(data[KeyLength * 2]) - if secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, addr signature, - cast[ptr cuchar](unsafeAddr data[0]), - recid) != 1: - return(EthKeysStatus.Error) - result = EthKeysStatus.Success - -proc recoverSignature*(data: openarray[byte], - signature: var Signature): EthKeysStatus {.deprecated.} = - ## Deprecated, use `parseCompact` instead - if data.len < RawSignatureSize: - setErrorMsg(InvalidSignature) - return(EthKeysStatus.Error) - signature.parseCompact(data.toOpenArray(0, RawSignatureSize - 1)) - -proc initPublicKey*(hexstr: string): PublicKey = - ## Create new public key from hexadecimal string representation. - var o = fromHex(stripSpaces(hexstr)) - if recoverPublicKey(o, result) != EthKeysStatus.Success: - raise newException(EthKeysException, InvalidPublicKey) - -proc initPublicKey*(data: openarray[byte]): PublicKey = - ## Create new public key from binary data blob. - if recoverPublicKey(data, result) != EthKeysStatus.Success: - raise newException(EthKeysException, InvalidPublicKey) - -proc initSignature*(hexstr: string): Signature = - ## Create new signature from hexadecimal string representation. - var o = fromHex(stripSpaces(hexstr)) - if recoverSignature(o, result) != EthKeysStatus.Success: - raise newException(EthKeysException, libsecp256k1ErrorMsg()) - -proc initSignature*(data: openarray[byte]): Signature = - ## Create new signature from 'data'. - if recoverSignature(data, result) != EthKeysStatus.Success: - raise newException(EthKeysException, libsecp256k1ErrorMsg()) - -proc ecdhAgree*(seckey: PrivateKey, pubkey: PublicKey, - secret: var SharedSecretFull): EthKeysStatus = - ## Calculate ECDH shared secret. - let ctx = getSecpContext() - if secp256k1_ecdh_raw(ctx, cast[ptr cuchar](addr secret.data), - unsafeAddr pubkey, - cast[ptr cuchar](unsafeAddr seckey)) != 1: - return(EthKeysStatus.Error) - return(EthKeysStatus.Success) - -proc ecdhAgree*(seckey: PrivateKey, pubkey: PublicKey, - secret: var SharedSecret): EthKeysStatus = - ## Calculate ECDH shared secret. - var res: array[KeyLength + 1, byte] - let ctx = getSecpContext() - if secp256k1_ecdh_raw(ctx, cast[ptr cuchar](addr res), - unsafeAddr pubkey, - cast[ptr cuchar](unsafeAddr seckey)) != 1: - return(EthKeysStatus.Error) - copyMem(addr secret, addr res[1], KeyLength) - return(EthKeysStatus.Success) - -proc toRaw*(pubkey: PublicKey, data: var openarray[byte], compressed = false) = - ## Converts public key `pubkey` to serialized form and store it in `data`. - if compressed: - var length = len(data) - doAssert(length >= CompressedPubKeyLength) - let ctx = getSecpContext() - if secp256k1_ec_pubkey_serialize(ctx, cast[ptr cuchar](addr data[0]), - addr length, unsafeAddr pubkey, - SECP256K1_EC_COMPRESSED) != 1: - raiseSecp256k1Error() - else: - var key: array[RawPublicKeySize + 1, byte] - doAssert(len(data) >= RawPublicKeySize) - var length = csize(sizeof(key)) - let ctx = getSecpContext() - if secp256k1_ec_pubkey_serialize(ctx, cast[ptr cuchar](addr key), - addr length, unsafeAddr pubkey, - SECP256K1_EC_UNCOMPRESSED) != 1: - raiseSecp256k1Error() - doAssert(length == RawPublicKeySize + 1) - doAssert(key[0] == 0x04'u8) - copyMem(addr data[0], addr key[1], RawPublicKeySize) - -proc getRaw*(pubkey: PublicKey): array[RawPublicKeySize, byte] {.noinit, inline.} = - ## Converts public key `pubkey` to serialized form. - pubkey.toRaw(result) - -proc `==`*(lhs, rhs: PublicKey): bool = - lhs.getRaw == rhs.getRaw - -proc getRawCompressed*(pubkey: PublicKey): array[CompressedPubKeyLength, byte] {.noinit, inline.} = - ## Converts public key `pubkey` to serialized form. - pubkey.toRaw(result, true) - -proc toRaw*(s: Signature, data: var openarray[byte]) = - ## Converts signature `s` to serialized form and store it in `data`. - let ctx = getSecpContext() - var recid = cint(0) - doAssert(len(data) >= RawSignatureSize) - if secp256k1_ecdsa_recoverable_signature_serialize_compact( - ctx, cast[ptr cuchar](addr data[0]), addr recid, unsafeAddr s) != 1: - raiseSecp256k1Error() - data[64] = uint8(recid) - -proc getRaw*(s: Signature): array[RawSignatureSize, byte] {.noinit.} = - ## Converts signature `s` to serialized form. - s.toRaw(result) - -proc toRaw*(s: SignatureNR, data: var openarray[byte]) = - ## Converts signature `s` to serialized form and store it in `data`. - doAssert(len(data) == RawSignatureNRSize) - let ctx = getSecpContext() - if secp256k1_ecdsa_signature_serialize_compact(ctx, cast[ptr cuchar](addr data[0]), - unsafeAddr s) != 1: - raiseSecp256k1Error() - -proc getRaw*(s: SignatureNR): array[RawSignatureNRSize, byte] {.noinit.} = - ## Converts signature `s` to serialized form. - s.toRaw(result) - -proc recoverSignatureKey*(data: openarray[byte], - msg: openarray[byte], - pubkey: var PublicKey): EthKeysStatus = - ## Perform check on digitally signed `data` using original message `msg` and - ## recover public key to `pubkey` on success. - let ctx = getSecpContext() - let length = len(data) - if len(msg) < KeyLength: - setErrorMsg(MessageSizeError) - return(EthKeysStatus.Error) - if length < RawSignatureSize: - setErrorMsg(InvalidSignature) - return(EthKeysStatus.Error) - var recid = cint(data[KeyLength * 2]) - var s: secp256k1_ecdsa_recoverable_signature - if secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, addr s, - cast[ptr cuchar](unsafeAddr data[0]), - recid) != 1: - return(EthKeysStatus.Error) - if secp256k1_ecdsa_recover(ctx, addr pubkey, addr s, - cast[ptr cuchar](msg)) != 1: - setErrorMsg(VerificationFailed) - return(EthKeysStatus.Error) - result = EthKeysStatus.Success - -proc recoverSignatureKey*(signature: Signature, - msg: openarray[byte], - pubkey: var PublicKey): EthKeysStatus = - ## Perform check of `signature` using original message `msg` and - ## recover public key to `pubkey` on success. - let ctx = getSecpContext() - if len(msg) < KeyLength: - setErrorMsg(MessageSizeError) - return(EthKeysStatus.Error) - if secp256k1_ecdsa_recover(ctx, addr pubkey, unsafeAddr signature, - cast[ptr cuchar](msg)) != 1: - setErrorMsg(VerificationFailed) - return(EthKeysStatus.Error) - result = EthKeysStatus.Success - -proc signRawMessage*(data: openarray[byte], seckey: PrivateKey, - signature: var Signature): EthKeysStatus = - ## Sign message `data` of `KeyLength` size using private key `seckey` and - ## store result into `signature`. - let ctx = getSecpContext() - let length = len(data) - if length != KeyLength: - setErrorMsg(MessageSizeError) - return(EthKeysStatus.Error) - if secp256k1_ecdsa_sign_recoverable(ctx, addr signature, - cast[ptr cuchar](unsafeAddr data[0]), - cast[ptr cuchar](unsafeAddr seckey), - nil, nil) != 1: - return(EthKeysStatus.Error) - return(EthKeysStatus.Success) - -proc signRawMessage*(data: openarray[byte], seckey: PrivateKey, - signature: var SignatureNR): EthKeysStatus = - ## Sign message `data` of `KeyLength` size using private key `seckey` and - ## store result into `signature`. - let ctx = getSecpContext() - let length = len(data) - if length != KeyLength: - setErrorMsg(MessageSizeError) - return(EthKeysStatus.Error) - if secp256k1_ecdsa_sign(ctx, addr signature, - cast[ptr cuchar](unsafeAddr data[0]), - cast[ptr cuchar](unsafeAddr seckey), - nil, nil) != 1: - return(EthKeysStatus.Error) - return(EthKeysStatus.Success) - -proc verifySignatureRaw*(signature: SignatureNR, message: openarray[byte], - publicKey: PublicKey): EthKeysStatus = - ## Verify `signature` using original `message` (32 bytes) and `publicKey`. - let ctx = getSecpContext() - if len(message) != KeyLength: - setErrorMsg(MessageSizeError) - return(EthKeysStatus.Error) - - if secp256k1_ecdsa_verify(ctx, unsafeAddr signature, - cast[ptr cuchar](unsafeAddr message[0]), - unsafeAddr publicKey) != 1: - setErrorMsg(VerificationFailed) - return(EthKeysStatus.Error) - - return(EthKeysStatus.Success) diff --git a/eth/keys/secp.nim b/eth/keys/secp.nim new file mode 100644 index 0000000..7baca00 --- /dev/null +++ b/eth/keys/secp.nim @@ -0,0 +1,415 @@ +## Copyright (c) 2018-2020 Status Research & Development GmbH +## Licensed under either of +## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +## * MIT license ([LICENSE-MIT](LICENSE-MIT)) +## at your option. +## This file may not be copied, modified, or distributed except according to +## those terms. +## + +import + strformat, + secp256k1, + stew/[byteutils, objects, result], + nimcrypto/[hash, sysrand] + +from nimcrypto/utils import burnMem + +export result + +{.push raises: [Defect].} + +# Implementation notes +# +# The goal of this wrapper is to create a thin later on top of the API presented +# in libsecp256k1, exploiting some of its regulatities to make it slightly more +# convenient to use from Nim +# +# * We hide raw pointer accesses and lengths behind nim types +# * We guarantee certain parameter properties, like not null and proper length, +# on the Nim side - in turn, we can rely on certain errors never happening in +# libsecp256k1, so we can skip checking for them +# * Functions like "fromRaw/toRaw" are balanced and will always rountrip +# * Functions like `fromRaw` are not called `init` because they may fail +# * Exception-free + +const + SkRawSecretKeySize* = 32 # 256 div 8 + ## Size of private key in octets (bytes) + SkRawSignatureSize* = 64 + ## Compact serialized non-recoverable signature + SkDerSignatureMaxSize* = 72 + ## Max bytes in DER encoding + + SkRawRecoverableSignatureSize* = 65 + ## Size of recoverable signature in octets (bytes) + + SkRawPublicKeySize* = 65 + ## Size of uncompressed public key in octets (bytes) + + SkRawCompressedPubKeySize* = 33 + ## Size of compressed public key in octets (bytes) + + SkMessageSize* = 32 + ## Size of message that can be signed + + SkEdchSecretSize* = 32 + ## ECDH-agreed key size + SkEcdhRawSecretSize* = 33 + ## ECDH-agreed raw key size + +type + SkPublicKey* = secp256k1_pubkey + ## Representation of public key. + + SkSecretKey* = object + ## Representation of secret key. + data*: array[SkRawSecretKeySize, byte] + + SkKeyPair* = object + ## Representation of private/public keys pair. + seckey*: SkSecretKey + pubkey*: SkPublicKey + + SkSignature* = secp256k1_ecdsa_signature + ## Representation of non-recoverable signature. + + SkRecoverableSignature* = secp256k1_ecdsa_recoverable_signature + ## Representation of recoverable signature. + + SkContext* = ref object + ## Representation of Secp256k1 context object. + context: ptr secp256k1_context + + SkMessage* = MDigest[SkMessageSize * 8] + ## Message that can be signed or verified + + SkEcdhSecret* = object + ## Representation of ECDH shared secret + data*: array[SkEdchSecretSize, byte] + + SkEcdhRawSecret* = 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] + + SkResult*[T] = result.Result[T, cstring] + +## +## Private procedures interface +## + +var secpContext {.threadvar.}: SkContext + ## Thread local variable which holds current context + +proc illegalCallback(message: cstring, data: pointer) {.cdecl.} = + # This should never happen because we check all parameters before passing + # them to secp + echo message + echo getStackTrace() + quit 1 + +proc errorCallback(message: cstring, data: pointer) {.cdecl.} = + # Internal panic - should never happen + echo message + echo getStackTrace() + quit 1 + +template ptr0(v: array|openArray): ptr cuchar = + cast[ptr cuchar](unsafeAddr v[0]) + +proc shutdownLibsecp256k1(ctx: SkContext) = + # TODO: use destructor when finalizer are deprecated for destructors + if not(isNil(ctx.context)): + secp256k1_context_destroy(ctx.context) + +proc newSkContext(): SkContext = + ## Create new Secp256k1 context object. + new(result, shutdownLibsecp256k1) + let flags = cuint(SECP256K1_CONTEXT_VERIFY or SECP256K1_CONTEXT_SIGN) + result.context = secp256k1_context_create(flags) + secp256k1_context_set_illegal_callback(result.context, illegalCallback, + cast[pointer](result)) + secp256k1_context_set_error_callback(result.context, errorCallback, + cast[pointer](result)) + +func getContext(): ptr secp256k1_context = + ## Get current `EccContext` + {.noSideEffect.}: # TODO what problems will this cause? + if isNil(secpContext): + secpContext = newSkContext() + secpContext.context + +proc random*(T: type SkSecretKey): SkResult[T] = + ## Generates new random private key. + let ctx = getContext() + var sk: T + while randomBytes(sk.data) == SkRawSecretKeySize: + if secp256k1_ec_seckey_verify(ctx, sk.data.ptr0) == 1: + return ok(sk) + + return err("secp: cannot get random bytes for key") + +proc 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(getContext(), 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[SkSecretKey] = + ## Initialize Secp256k1 `private key` ``key`` from hexadecimal string + ## representation ``data``. + try: + # TODO strip string? + T.fromRaw(hexToSeqByte(data)) + except CatchableError: + err("secp: cannot parse private key") + +proc toRaw*(seckey: SkSecretKey): array[SkRawSecretKeySize, byte] = + ## Serialize Secp256k1 `private key` ``key`` to raw binary form + seckey.data + +proc toPublicKey*(key: SkSecretKey): SkResult[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?") + + ok(pubkey) + +proc 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) < 1: + return err(static( + &"secp: public key must be {SkRawCompressedPubKeySize} or {SkRawPublicKeySize} bytes")) + + var length: int + if data[0] == 0x02'u8 or data[0] == 0x03'u8: + length = min(len(data), SkRawCompressedPubKeySize) + elif data[0] == 0x04'u8 or data[0] == 0x06'u8 or data[0] == 0x07'u8: + length = min(len(data), SkRawPublicKeySize) + else: + return err("secp: public key format not recognised") + + var key: SkPublicKey + if secp256k1_ec_pubkey_parse( + getContext(), addr key, data.ptr0, length) != 1: + return err("secp: cannot parse public key") + + ok(key) + +proc fromHex*(T: type SkPublicKey, data: string): SkResult[T] = + ## Initialize Secp256k1 `public key` ``key`` from hexadecimal string + ## representation ``data``. + try: + # TODO strip string? + T.fromRaw(hexToSeqByte(data)) + except CatchableError: + err("secp: cannot parse public key") + +proc toRaw*(pubkey: SkPublicKey): array[SkRawPublicKeySize, byte] = + ## Serialize Secp256k1 `public key` ``key`` to raw uncompressed form + var length = csize(len(result)) + # Can't fail, per documentation + discard secp256k1_ec_pubkey_serialize( + getContext(), result.ptr0, addr length, unsafeAddr pubkey, + SECP256K1_EC_UNCOMPRESSED) + +proc toRawCompressed*(key: SkPublicKey): array[SkRawCompressedPubKeySize, byte] = + ## Serialize Secp256k1 `public key` ``key`` to raw compressed form + var length = csize(len(result)) + # Can't fail, per documentation + discard secp256k1_ec_pubkey_serialize( + getContext(), result.ptr0, addr length, unsafeAddr key, + SECP256K1_EC_COMPRESSED) + +proc fromRaw*(T: type SkSignature, data: openArray[byte]): SkResult[T] = + ## Load compact signature from data + if data.len() < SkRawSignatureSize: + return err(static(&"secp: signature must be {SkRawSignatureSize} bytes")) + + var sig: SkSignature + if secp256k1_ecdsa_signature_parse_compact( + getContext(), addr sig, data.ptr0) != 1: + return err("secp: cannot parse signaure") + + ok(sig) + +proc 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().context, addr sig, data.ptr0, csize(len(data))) != 1: + return err("secp: cannot parse DER signature") + + ok(sig) + +proc fromHex*(T: type SkSignature, data: string): SkResult[T] = + ## Initialize Secp256k1 `signature` ``sig`` from hexadecimal string + ## representation ``data``. + try: + # TODO strip string? + T.fromRaw(hexToSeqByte(data)) + except CatchableError: + err("secp: cannot parse signature") + +proc toRaw*(sig: SkSignature): array[SkRawSignatureSize, byte] = + ## Serialize signature to compact binary form + # Can't fail, per documentation + discard secp256k1_ecdsa_signature_serialize_compact( + getContext(), result.ptr0, unsafeAddr sig) + +proc toDer*(sig: SkSignature, data: var openarray[byte]): int = + ## Serialize Secp256k1 `signature` ``sig`` to raw binary form and store it + ## to ``data``. + ## + ## Procedure returns number of bytes (octets) needed to store + ## Secp256k1 signature. + let ctx = getContext() + var buffer: array[SkDerSignatureMaxSize, byte] + var plength = csize(len(buffer)) + discard secp256k1_ecdsa_signature_serialize_der( + ctx, buffer.ptr0, addr plength, unsafeAddr sig) + result = plength + if len(data) >= plength: + copyMem(addr data[0], addr buffer[0], plength) + +proc toDer*(sig: SkSignature): seq[byte] = + ## Serialize Secp256k1 `signature` and return it. + result = newSeq[byte](72) + let length = toDer(sig, result) + result.setLen(length) + +proc 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") + + ok(sig) + +proc fromHex*(T: type SkRecoverableSignature, data: string): SkResult[T] = + ## Initialize Secp256k1 `signature` ``sig`` from hexadecimal string + ## representation ``data``. + try: + # TODO strip string? + T.fromRaw(hexToSeqByte(data)) + except CatchableError: + err("secp: cannot parse recoverable signature") + +proc 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) + result[64] = byte(recid) + +proc random*(T: type SkKeyPair): SkResult[T] = + ## Generates new random key pair. + let seckey = ? SkSecretKey.random() + ok(T( + seckey: seckey, + pubkey: seckey.toPublicKey().expect("random key should always be valid") + )) + +proc `==`*(lhs, rhs: SkPublicKey): bool = + ## Compare Secp256k1 `public key` objects for equality. + lhs.toRaw() == rhs.toRaw() + +proc `==`*(lhs, rhs: SkSignature): bool = + ## Compare Secp256k1 `signature` objects for equality. + lhs.toRaw() == rhs.toRaw() + +proc `==`*(lhs, rhs: SkRecoverableSignature): bool = + ## Compare Secp256k1 `recoverable signature` objects for equality. + lhs.toRaw() == rhs.toRaw() + +proc sign*(key: SkSecretKey, msg: SkMessage): SkResult[SkSignature] = + ## Sign message `msg` using private key `key` and return signature object. + var sig: SkSignature + if secp256k1_ecdsa_sign( + getContext(), addr sig, msg.data.ptr0, key.data.ptr0, nil, nil) != 1: + return err("secp: cannot create signature, key invalid?") + + ok(sig) + +proc signRecoverable*(key: SkSecretKey, msg: SkMessage): SkResult[SkRecoverableSignature] = + ## Sign message `msg` using private key `key` and return signature object. + var sig: SkRecoverableSignature + if secp256k1_ecdsa_sign_recoverable( + getContext(), addr sig, msg.data.ptr0, key.data.ptr0, nil, nil) != 1: + return err("secp: cannot create recoverable signature, key invalid?") + + ok(sig) + +proc verify*(sig: SkSignature, msg: SkMessage, key: SkPublicKey): bool = + secp256k1_ecdsa_verify( + getContext(), unsafeAddr sig, msg.data.ptr0, unsafeAddr key) == 1 + +proc recover*(sig: SkRecoverableSignature, msg: SkMessage): SkResult[SkPublicKey] = + var pubkey: SkPublicKey + if secp256k1_ecdsa_recover( + getContext(), addr pubkey, unsafeAddr sig, msg.data.ptr0) != 1: + return err("secp: cannot recover public key from signature") + + ok(pubkey) + +proc ecdh*(seckey: SkSecretKey, pubkey: SkPublicKey): SkResult[SkEcdhSecret] = + ## Calculate ECDH shared secret. + var secret: SkEcdhSecret + if secp256k1_ecdh( + getContext(), secret.data.ptr0, unsafeAddr pubkey, seckey.data.ptr0) != 1: + return err("secp: cannot compute ECDH secret") + + ok(secret) + +proc ecdhRaw*(seckey: SkSecretKey, pubkey: SkPublicKey): SkResult[SkEcdhRawSecret] = + ## Calculate ECDH shared secret. + var secret: SkEcdhRawSecret + if secp256k1_ecdh_raw( + getContext(), secret.data.ptr0, unsafeAddr pubkey, seckey.data.ptr0) != 1: + return err("Cannot compute raw ECDH secret") + + ok(secret) + +proc clear*(v: var SkSecretKey) {.inline.} = + ## Wipe and clear memory of Secp256k1 `private key`. + burnMem(v.data) + +proc clear*(v: var SkPublicKey) {.inline.} = + ## Wipe and clear memory of Secp256k1 `public key`. + burnMem(v.data) + +proc clear*(v: var SkSignature) {.inline.} = + ## Wipe and clear memory of Secp256k1 `signature`. + burnMem(v.data) + +proc clear*(v: var SkRecoverableSignature) {.inline.} = + ## Wipe and clear memory of Secp256k1 `signature`. + burnMem(v.data) + +proc clear*(v: var SkKeyPair) {.inline.} = + ## Wipe and clear memory of Secp256k1 `key pair`. + v.seckey.clear() + v.pubkey.clear() + +proc clear*(v: var SkEcdhSecret) = + burnMem(v.data) + +proc clear*(v: var SkEcdhRawSecret) = + burnMem(v.data) diff --git a/eth/p2p/discovery.nim b/eth/p2p/discovery.nim index 9451345..f0d2877 100644 --- a/eth/p2p/discovery.nim +++ b/eth/p2p/discovery.nim @@ -341,6 +341,7 @@ when isMainModule: discovery.open() proc test() {.async.} = - await discovery.bootstrap() + {.gcsafe.}: + await discovery.bootstrap() waitFor test() diff --git a/eth/p2p/enode.nim b/eth/p2p/enode.nim index 66e0f5a..beec9a9 100644 --- a/eth/p2p/enode.nim +++ b/eth/p2p/enode.nim @@ -11,6 +11,8 @@ import uri, strutils, net import eth/keys +export keys + type ENodeStatus* = enum ## ENode status codes diff --git a/tests/keys/test_keys.nim b/tests/keys/test_keys.nim index 1debe31..380ed5e 100644 --- a/tests/keys/test_keys.nim +++ b/tests/keys/test_keys.nim @@ -70,12 +70,6 @@ suite "ECC/ECDSA/ECDHE tests suite": check: $signature == $expectSignature - test "test_signing_from_private_key_obj": - var s = initPrivateKey(pkbytes) - var signature = s.signMessage(message) - var mhash = keccak256.digest(message) - check verifyMessage(signature.data, mhash) == true - test "test_recover_from_signature_obj": var s = initPrivateKey(pkbytes) var mhash = keccak256.digest(message) @@ -162,7 +156,7 @@ suite "ECC/ECDSA/ECDHE tests suite": let expect = fromHex(stripSpaces(sharedSecrets[i])) check: ecdhAgree(s, p, secret) == EthKeysStatus.Success - compare(expect, secret.data) == true + expect == secret.data test "ECDHE/cpp-ethereum crypto.cpp#L394": # ECDHE test vectors @@ -175,7 +169,7 @@ suite "ECC/ECDSA/ECDHE tests suite": let expect = fromHex(stripSpaces(expectm)) check: ecdhAgree(s, p, secret) == EthKeysStatus.Success - compare(expect, secret.data) == true + expect == secret.data test "ECDHE/cpp-ethereum rlpx.cpp#L425": # ECDHE test vectors diff --git a/tests/p2p/test_enode.nim b/tests/p2p/test_enode.nim index af96b70..a3ba56f 100644 --- a/tests/p2p/test_enode.nim +++ b/tests/p2p/test_enode.nim @@ -71,7 +71,7 @@ suite "ENode": IncorrectIP, IncorrectIP, IncorrectIP, - Success, + ENodeStatus.Success, IncorrectUri, IncorrectPort, IncorrectPort,