325 lines
12 KiB
Nim
325 lines
12 KiB
Nim
|
#
|
||
|
# Ethereum P2P
|
||
|
# (c) Copyright 2018
|
||
|
# Status Research & Development GmbH
|
||
|
#
|
||
|
# See the file "LICENSE", included in this
|
||
|
# distribution, for details about the copyright.
|
||
|
#
|
||
|
|
||
|
## This module implements `libsecp256k1` ECC/ECDH functions
|
||
|
|
||
|
import secp256k1, hexdump, nimcrypto/sysrand, nimcrypto/utils
|
||
|
|
||
|
const
|
||
|
KeyLength* = 32
|
||
|
PublicKeyLength* = 64
|
||
|
SignatureLength* = 65
|
||
|
|
||
|
|
||
|
type
|
||
|
EccContext* = ref object of RootRef
|
||
|
context*: ptr secp256k1_context
|
||
|
error*: string
|
||
|
|
||
|
EccStatus* = enum
|
||
|
Success, ## Operation was successful
|
||
|
Error ## Operation failed
|
||
|
|
||
|
PublicKey* = secp256k1_pubkey
|
||
|
## Representation of public key
|
||
|
|
||
|
PrivateKey* = array[KeyLength, byte]
|
||
|
## Representation of secret key
|
||
|
|
||
|
SharedSecret* = array[KeyLength, byte]
|
||
|
## Representation of ECDH shared secret
|
||
|
|
||
|
Nonce* = array[KeyLength, byte]
|
||
|
## Representation of nonce
|
||
|
|
||
|
RawPublickey* = object
|
||
|
## Representation of serialized public key
|
||
|
header*: byte
|
||
|
data*: array[KeyLength * 2, byte]
|
||
|
|
||
|
KeyPair* = object
|
||
|
## Representation of private/public keys pair
|
||
|
seckey*: PrivateKey
|
||
|
pubkey*: PublicKey
|
||
|
|
||
|
Signature* = secp256k1_ecdsa_recoverable_signature
|
||
|
## Representation of signature
|
||
|
|
||
|
RawSignature* = object
|
||
|
## Representation of serialized signature
|
||
|
data*: array[KeyLength * 2 + 1, byte]
|
||
|
|
||
|
Secp256k1Exception* = object of Exception
|
||
|
## Exceptions generated by `libsecp256k1`
|
||
|
EccException* = object of Exception
|
||
|
## Exception generated by this module
|
||
|
|
||
|
var eccContext* {.threadvar.}: EccContext
|
||
|
## Thread local variable which holds current context
|
||
|
|
||
|
proc illegalCallback(message: cstring; data: pointer) {.cdecl.} =
|
||
|
let ctx = cast[EccContext](data)
|
||
|
ctx.error = $message
|
||
|
|
||
|
proc errorCallback(message: cstring, data: pointer) {.cdecl.} =
|
||
|
let ctx = cast[EccContext](data)
|
||
|
ctx.error = $message
|
||
|
|
||
|
proc newEccContext*(): EccContext =
|
||
|
## Create new `EccContext`.
|
||
|
result = new EccContext
|
||
|
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(eccContext):
|
||
|
eccContext = newEccContext()
|
||
|
result = eccContext.context
|
||
|
|
||
|
proc getEccContext*(): EccContext =
|
||
|
## Get current `EccContext`
|
||
|
if isNil(eccContext):
|
||
|
eccContext = newEccContext()
|
||
|
result = eccContext
|
||
|
|
||
|
template raiseSecp256k1Error*() =
|
||
|
## Raises `libsecp256k1` error as exception
|
||
|
let mctx = getEccContext()
|
||
|
if len(mctx.error) > 0:
|
||
|
var msg = mctx.error
|
||
|
mctx.error.setLen(0)
|
||
|
raise newException(Secp256k1Exception, msg)
|
||
|
|
||
|
proc eccErrorMsg*(): string =
|
||
|
let mctx = getEccContext()
|
||
|
result = mctx.error
|
||
|
|
||
|
proc setErrorMsg*(m: string) =
|
||
|
let mctx = getEccContext()
|
||
|
mctx.error = m
|
||
|
|
||
|
proc getRaw*(pubkey: PublicKey): RawPublickey =
|
||
|
## Converts public key `pubkey` to serialized form of `secp256k1_pubkey`.
|
||
|
var length = csize(sizeof(RawPublickey))
|
||
|
let ctx = getSecpContext()
|
||
|
if secp256k1_ec_pubkey_serialize(ctx, cast[ptr cuchar](addr result),
|
||
|
addr length, unsafeAddr pubkey,
|
||
|
SECP256K1_EC_UNCOMPRESSED) != 1:
|
||
|
raiseSecp256k1Error()
|
||
|
if length != 65:
|
||
|
raise newException(EccException, "Invalid public key length!")
|
||
|
if result.header != 0x04'u8:
|
||
|
raise newException(EccException, "Invalid public key header!")
|
||
|
|
||
|
proc getRaw*(s: Signature): RawSignature =
|
||
|
## Converts signature `s` to serialized form.
|
||
|
let ctx = getSecpContext()
|
||
|
var recid = cint(0)
|
||
|
if secp256k1_ecdsa_recoverable_signature_serialize_compact(
|
||
|
ctx, cast[ptr cuchar](unsafeAddr result), addr recid, unsafeAddr s) != 1:
|
||
|
raiseSecp256k1Error()
|
||
|
result.data[64] = uint8(recid)
|
||
|
|
||
|
proc signMessage*(seckey: PrivateKey, data: ptr byte, length: int,
|
||
|
sig: var Signature): EccStatus =
|
||
|
## Sign message pointed by `data` with size `length` and save signature to
|
||
|
## `sig`.
|
||
|
let ctx = getSecpContext()
|
||
|
if secp256k1_ecdsa_sign_recoverable(ctx, addr sig,
|
||
|
cast[ptr cuchar](data),
|
||
|
cast[ptr cuchar](unsafeAddr seckey[0]),
|
||
|
nil, nil) != 1:
|
||
|
return(Error)
|
||
|
return(Success)
|
||
|
|
||
|
proc signMessage*[T](seckey: PrivateKey, data: openarray[T],
|
||
|
sig: var Signature, ostart: int = 0,
|
||
|
ofinish: int = -1): EccStatus =
|
||
|
## Sign message ``data``[`soffset`..`eoffset`] and store result into `sig`.
|
||
|
let so = ostart
|
||
|
let eo = if ofinish == -1: (len(data) - 1) else: ofinish
|
||
|
let length = (eo - so + 1) * sizeof(T)
|
||
|
# We don't need to check `so` because compiler will do it for `data[so]`.
|
||
|
if eo >= len(data):
|
||
|
setErrorMsg("Index is out of bounds!")
|
||
|
return(Error)
|
||
|
if len(data) < KeyLength or length < KeyLength:
|
||
|
setErrorMsg("There no reason to sign this message!")
|
||
|
return(Error)
|
||
|
result = signMessage(seckey, cast[ptr byte](unsafeAddr data[so]),
|
||
|
length, sig)
|
||
|
|
||
|
proc recoverSignatureKey*(data: ptr byte, length: int, message: ptr byte,
|
||
|
pubkey: var PublicKey): EccStatus =
|
||
|
## Check signature and return public key from `data` with size `length` and
|
||
|
## `message`.
|
||
|
let ctx = getSecpContext()
|
||
|
var s: secp256k1_ecdsa_recoverable_signature
|
||
|
if length >= 65:
|
||
|
var recid = cint(cast[ptr UncheckedArray[byte]](data)[KeyLength * 2])
|
||
|
if secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, addr s,
|
||
|
cast[ptr cuchar](data),
|
||
|
recid) != 1:
|
||
|
return(Error)
|
||
|
|
||
|
if secp256k1_ecdsa_recover(ctx, addr pubkey, addr s,
|
||
|
cast[ptr cuchar](message)) != 1:
|
||
|
setErrorMsg("Message signature verification failed!")
|
||
|
return(Error)
|
||
|
return(Success)
|
||
|
else:
|
||
|
setErrorMsg("Incorrect signature size")
|
||
|
return(Error)
|
||
|
|
||
|
proc recoverSignatureKey*[A, B](data: openarray[A],
|
||
|
message: openarray[B],
|
||
|
pubkey: var PublicKey,
|
||
|
ostart: int = 0,
|
||
|
ofinish: int = -1): EccStatus =
|
||
|
## Check signature in ``data``[`soffset`..`eoffset`] and recover public key
|
||
|
## from signature to ``pubkey`` using message `message`.
|
||
|
if len(message) == 0:
|
||
|
setErrorMsg("Message could not be empty!")
|
||
|
return(Error)
|
||
|
let so = ostart
|
||
|
let eo = if ofinish == -1: (len(data) - 1) else: ofinish
|
||
|
let length = (eo - so + 1) * sizeof(A)
|
||
|
# We don't need to check `so` because compiler will do it for `data[so]`.
|
||
|
if eo > len(data):
|
||
|
setErrorMsg("Index is out of bounds!")
|
||
|
return(Error)
|
||
|
if length < sizeof(RawSignature) or len(data) < sizeof(RawSignature):
|
||
|
setErrorMsg("Invalid signature size!")
|
||
|
return(Error)
|
||
|
result = recoverSignatureKey(cast[ptr byte](unsafeAddr data[so]), length,
|
||
|
cast[ptr byte](unsafeAddr message[0]), pubkey)
|
||
|
|
||
|
proc ecdhAgree*(seckey: PrivateKey, pubkey: PublicKey,
|
||
|
secret: var SharedSecret): EccStatus =
|
||
|
## 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(Error)
|
||
|
copyMem(addr secret[0], addr res[1], KeyLength)
|
||
|
return(Success)
|
||
|
|
||
|
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[0])) != 1:
|
||
|
raiseSecp256k1Error()
|
||
|
|
||
|
|
||
|
proc recoverPublicKey*(data: ptr byte, length: int,
|
||
|
pubkey: var PublicKey): EccStatus =
|
||
|
## Unserialize public key from `data` pointer and size `length` and'
|
||
|
## set `pubkey`.
|
||
|
let ctx = getSecpContext()
|
||
|
if length < sizeof(PublicKey):
|
||
|
setErrorMsg("Invalid public key!")
|
||
|
return(Error)
|
||
|
var rawkey: RawPublickey
|
||
|
rawkey.header = 0x04 # mark key with COMPRESSED flag
|
||
|
copyMem(addr rawkey.data[0], data, len(rawkey.data))
|
||
|
if secp256k1_ec_pubkey_parse(ctx, addr pubkey,
|
||
|
cast[ptr cuchar](addr rawkey),
|
||
|
sizeof(RawPublickey)) != 1:
|
||
|
return(Error)
|
||
|
return(Success)
|
||
|
|
||
|
proc recoverPublicKey*[T](data: openarray[T], pubkey: var PublicKey,
|
||
|
ostart: int = 0, ofinish: int = -1, ): EccStatus =
|
||
|
## Unserialize public key from openarray[T] `data`, from position `ostart` to
|
||
|
## position `ofinish` and save it to `pubkey`.
|
||
|
let so = ostart
|
||
|
let eo = if ofinish == -1: (len(data) - 1) else: ofinish
|
||
|
let length = (eo - so + 1) * sizeof(T)
|
||
|
# We don't need to check `so` because compiler will do it for `data[so]`.
|
||
|
if eo > len(data):
|
||
|
setErrorMsg("Index is out of bounds!")
|
||
|
return(Error)
|
||
|
if length < sizeof(PublicKey) or len(data) < sizeof(PublicKey):
|
||
|
setErrorMsg("Invalid public key size!")
|
||
|
return(Error)
|
||
|
result = recoverPublicKey(cast[ptr byte](unsafeAddr data[so]), length,
|
||
|
pubkey)
|
||
|
|
||
|
proc newPrivateKey*(): PrivateKey =
|
||
|
## Generates new secret key.
|
||
|
let ctx = getSecpContext()
|
||
|
while true:
|
||
|
if randomBytes(addr result[0], KeyLength) == KeyLength:
|
||
|
if secp256k1_ec_seckey_verify(ctx, cast[ptr cuchar](addr result[0])) == 1:
|
||
|
break
|
||
|
|
||
|
proc newKeyPair*(): KeyPair =
|
||
|
## Generates new private and public key.
|
||
|
result.seckey = newPrivateKey()
|
||
|
result.pubkey = result.seckey.getPublicKey()
|
||
|
|
||
|
proc getPrivateKey*(hexstr: string): PrivateKey =
|
||
|
## Set secret key from hexadecimal string representation.
|
||
|
let ctx = getSecpContext()
|
||
|
var o = fromHex(stripSpaces(hexstr))
|
||
|
if len(o) < KeyLength:
|
||
|
raise newException(EccException, "Invalid private key!")
|
||
|
copyMem(addr result[0], unsafeAddr o[0], KeyLength)
|
||
|
if secp256k1_ec_seckey_verify(ctx, cast[ptr cuchar](addr result[0])) != 1:
|
||
|
raise newException(EccException, "Invalid private key!")
|
||
|
|
||
|
proc getPublicKey*(hexstr: string): PublicKey =
|
||
|
## Set public key from hexadecimal string representation.
|
||
|
var o = fromHex(stripSpaces(hexstr))
|
||
|
if recoverPublicKey(o, result) != Success:
|
||
|
raise newException(EccException, "Invalid public key!")
|
||
|
|
||
|
proc dump*(s: openarray[byte], c: string = ""): string =
|
||
|
## Return hexadecimal dump of array `s`.
|
||
|
result = if len(c) > 0: c & "=>\n" else: ""
|
||
|
if len(s) > 0:
|
||
|
result &= dumpHex(unsafeAddr s[0], len(s))
|
||
|
else:
|
||
|
result &= "[]"
|
||
|
|
||
|
proc dump*(s: PublicKey, c: string = ""): string =
|
||
|
## Return hexadecimal dump of public key `s`.
|
||
|
result = if len(c) > 0: c & "=>\n" else: ""
|
||
|
result &= dumpHex(unsafeAddr s.data[0], sizeof(secp256k1_pubkey))
|
||
|
|
||
|
proc dump*(s: RawSignature, c: string = ""): string =
|
||
|
## Return hexadecimal dump of serialized signature `s`.
|
||
|
result = if len(c) > 0: c & "=>\n" else: ""
|
||
|
result &= dumpHex(unsafeAddr s.data[0], sizeof(RawSignature))
|
||
|
|
||
|
proc dump*(s: RawPublickey, c: string = ""): string =
|
||
|
## Return hexadecimal dump of serialized public key `s`.
|
||
|
result = if len(c) > 0: c & "=>\n" else: ""
|
||
|
result &= dumpHex(unsafeAddr s, sizeof(RawSignature))
|
||
|
|
||
|
proc dump*(s: secp256k1_ecdsa_recoverable_signature, c: string = ""): string =
|
||
|
## Return hexadecimal dump of signature `s`.
|
||
|
result = if len(c) > 0: c & "=>\n" else: ""
|
||
|
result &= dumpHex(unsafeAddr s.data[0],
|
||
|
sizeof(secp256k1_ecdsa_recoverable_signature))
|
||
|
|
||
|
proc dump*(p: pointer, s: int, c: string = ""): string =
|
||
|
## Return hexadecimal dump of memory blob `p` and size `s`.
|
||
|
result = if len(c) > 0: c & "=>\n" else: ""
|
||
|
result &= dumpHex(p, s)
|