nim-eth-keys/eth_keys.nim

165 lines
5.9 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)
#
import nimcrypto/hash, nimcrypto/keccak
{.deadCodeElim:on.}
type
EthKeysStatus* = enum
Success, ## Operation was successful
Error ## Operation failed
EthKeysException* = object of Exception
## Exception generated by this module
when not defined(native):
include eth_keys/libsecp256k1
proc ekErrorMsg*(): string {.inline.} =
## Return current error message.
result = libsecp256k1ErrorMsg()
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 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 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 signMessage*(seckey: PrivateKey, data: string): Signature {.inline.} =
## Sign message of arbitrary length `data` using private key `seckey`.
signMessage(seckey, cast[seq[byte]](data))
proc signMessage*(seckey: PrivateKey,
hash: MDigest[256]): Signature {.inline.} =
## Sign 256bit `hash` using private key `seckey`.
result = signMessage(seckey, hash.data)
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
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)
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 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 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 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 toAddress*(pubkey: PublicKey, with0x = true): string =
## Convert public key to hexadecimal string address.
var hash = keccak256.digest(pubkey.getRaw())
result = if with0x: "0x" else: ""
result.add(toHex(toOpenArray(hash.data, 12, len(hash.data) - 1), true))
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 hash2 = keccak256.digest(hhash1)
var hhash2 = toHex(hash2.data, true)
for i in 0..<len(hhash1):
if hhash2[i] >= '0' and hhash2[i] <= '7':
result.add(hhash1[i])
else:
if hhash1[i] >= '0' and hhash1[i] <= '9':
result.add(hhash1[i])
else:
let ch = chr(ord(hhash1[i]) - ord('a') + ord('A'))
result.add(ch)
proc validateChecksumAddress*(a: string): bool =
## Validate checksumable mixed-case address (EIP-55).
var address = ""
var check = "0x"
if len(a) != 42:
return false
if a[0] != '0' and a[1] != 'x':
return false
for i in 2..41:
let ch = a[i]
if ch in {'0'..'9'} or ch in {'a'..'f'}:
address &= ch
elif ch in {'A'..'F'}:
address &= chr(ord(ch) - ord('A') + ord('a'))
else:
return false
var hash = keccak256.digest(address)
var hexhash = toHex(hash.data, true)
for i in 0..<len(address):
if hexhash[i] >= '0' and hexhash[i] <= '7':
check.add(address[i])
else:
if address[i] >= '0' and address[i] <= '9':
check.add(address[i])
else:
let ch = chr(ord(address[i]) - ord('a') + ord('A'))
check.add(ch)
result = (check == a)
proc toCanonicalAddress*(pubkey: PublicKey): array[20, byte] =
## Convert public key to canonical address.
var hash = keccak256.digest(pubkey.getRaw())
copyMem(addr result[0], addr hash.data[12], 20)
proc `$`*(pubkey: PublicKey): string =
## Convert public key to hexadecimal string representation.
result = toHex(pubkey.getRaw(), true)
proc `$`*(sig: Signature): string =
## Convert signature to hexadecimal string representation.
result = toHex(sig.getRaw(), true)
proc `$`*(seckey: PrivateKey): string =
## Convert private key to hexadecimal string representation
result = toHex(seckey.data, true)