mirror of https://github.com/status-im/nim-eth.git
412 lines
15 KiB
Nim
412 lines
15 KiB
Nim
#
|
|
# 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 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)
|