nim-eth/eth/keys/libsecp256k1.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)