mirror of https://github.com/status-im/nim-eth.git
refactor secp interface (#203)
* avoid mix of exceptions and return codes * introduce result * use deprecated compatibility API to avoid big-bang refactoring * loosely based on nim-libp2p secp as well as the rust-secp256k1 wrapper * oh, boy does our secp256k1 wrapper need updating - we're _far_ behind...
This commit is contained in:
parent
5dc0a533b0
commit
02d661503f
430
eth/keys.nim
430
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..<len(hhash1):
|
||||
if hhash2[i] >= '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..<len(address):
|
||||
if hexhash[i] >= '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
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -341,6 +341,7 @@ when isMainModule:
|
|||
discovery.open()
|
||||
|
||||
proc test() {.async.} =
|
||||
{.gcsafe.}:
|
||||
await discovery.bootstrap()
|
||||
|
||||
waitFor test()
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
import uri, strutils, net
|
||||
import eth/keys
|
||||
|
||||
export keys
|
||||
|
||||
type
|
||||
ENodeStatus* = enum
|
||||
## ENode status codes
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -71,7 +71,7 @@ suite "ENode":
|
|||
IncorrectIP,
|
||||
IncorrectIP,
|
||||
IncorrectIP,
|
||||
Success,
|
||||
ENodeStatus.Success,
|
||||
IncorrectUri,
|
||||
IncorrectPort,
|
||||
IncorrectPort,
|
||||
|
|
Loading…
Reference in New Issue