163 lines
5.9 KiB
Nim
163 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
|
|
|
|
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)
|