# # 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..= '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..= '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)