mirror of https://github.com/status-im/nim-eth.git
Moved eth-keyfile to eth
This commit is contained in:
parent
621293c8d2
commit
0d18ffac31
|
@ -0,0 +1,11 @@
|
|||
#
|
||||
# Ethereum KeyFile
|
||||
# (c) Copyright 2018
|
||||
# Status Research & Development GmbH
|
||||
#
|
||||
# Licensed under either of
|
||||
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||
# MIT license (LICENSE-MIT)
|
||||
|
||||
import keyfile/uuid, keyfile/keyfile
|
||||
export uuid, keyfile
|
|
@ -0,0 +1,438 @@
|
|||
#
|
||||
# Ethereum KeyFile
|
||||
# (c) Copyright 2018
|
||||
# Status Research & Development GmbH
|
||||
#
|
||||
# Licensed under either of
|
||||
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||
# MIT license (LICENSE-MIT)
|
||||
|
||||
import nimcrypto/[bcmode, hmac, rijndael, pbkdf2, sha2, sysrand, utils, keccak],
|
||||
eth/keys, json, uuid, os, strutils, streams
|
||||
|
||||
const
|
||||
# Version 3 constants
|
||||
SaltSize = 16
|
||||
DKLen = 32
|
||||
MaxDKLen = 128
|
||||
ScryptR = 1
|
||||
ScryptP = 8
|
||||
Pbkdf2WorkFactor = 1_000_000
|
||||
ScryptWorkFactor = 262_144
|
||||
|
||||
type
|
||||
KeyFileStatus* = enum
|
||||
Success, ## No Error
|
||||
RandomError, ## Random generator error
|
||||
UuidError, ## UUID generator error
|
||||
BufferOverrun, ## Supplied buffer is too small
|
||||
IncorrectDKLen, ## `dklen` parameter is 0 or more then MaxDKLen
|
||||
MalformedError, ## JSON has incorrect structure
|
||||
NotImplemented, ## Feature is not implemented
|
||||
NotSupported, ## Feature is not supported
|
||||
EmptyMac, ## `mac` parameter is zero length or not in
|
||||
## hexadecimal form
|
||||
EmptyCiphertext, ## `ciphertext` parameter is zero length or not in
|
||||
## hexadecimal format
|
||||
EmptySalt, ## `salt` parameter is zero length or not in
|
||||
## hexadecimal format
|
||||
EmptyIV, ## `cipherparams.iv` parameter is zero length or not in
|
||||
## hexadecimal format
|
||||
IncorrectIV, ## Size of IV vector is not equal to cipher block size
|
||||
PrfNotSupported, ## PRF algorithm for PBKDF2 is not supported
|
||||
KdfNotSupported, ## KDF algorithm is not supported
|
||||
CipherNotSupported, ## `cipher` parameter is not supported
|
||||
IncorrectMac, ## `mac` verification failed
|
||||
IncorrectPrivateKey, ## incorrect private key
|
||||
OsError, ## OS specific error
|
||||
JsonError ## JSON encoder/decoder error
|
||||
|
||||
KdfKind* = enum
|
||||
PBKDF2, ## PBKDF2
|
||||
SCRYPT ## SCRYPT
|
||||
|
||||
HashKind* = enum
|
||||
HashNoSupport, HashSHA2_224, HashSHA2_256, HashSHA2_384, HashSHA2_512,
|
||||
HashKECCAK224, HashKECCAK256, HashKECCAK384, HashKECCAK512,
|
||||
HashSHA3_224, HashSHA3_256, HashSHA3_384, HashSHA3_512
|
||||
|
||||
CryptKind* = enum
|
||||
CipherNoSupport, ## Cipher not supported
|
||||
AES128CTR ## AES-128-CTR
|
||||
|
||||
const
|
||||
SupportedHashes = [
|
||||
"sha224", "sha256", "sha384", "sha512",
|
||||
"keccak224", "keccak256", "keccak384", "keccak512",
|
||||
"sha3_224", "sha3_256", "sha3_384", "sha3_512"
|
||||
]
|
||||
|
||||
SupportedHashesKinds = [
|
||||
HashSHA2_224, HashSHA2_256, HashSHA2_384, HashSHA2_512,
|
||||
HashKECCAK224, HashKECCAK256, HashKECCAK384, HashKECCAK512,
|
||||
HashSHA3_224, HashSHA3_256, HashSHA3_384, HashSHA3_512
|
||||
]
|
||||
|
||||
proc `$`(k: KdfKind): string =
|
||||
case k
|
||||
of SCRYPT:
|
||||
result = "scrypt"
|
||||
else:
|
||||
result = "pbkdf2"
|
||||
|
||||
proc `$`(k: CryptKind): string =
|
||||
case k
|
||||
of AES128CTR:
|
||||
result = "aes-128-ctr"
|
||||
else:
|
||||
result = "aes-128-ctr"
|
||||
|
||||
proc getPrfHash(prf: string): HashKind =
|
||||
result = HashNoSupport
|
||||
let p = prf.toLowerAscii()
|
||||
if p.startsWith("hmac-"):
|
||||
var hash = p[5..^1]
|
||||
var res = SupportedHashes.find(hash)
|
||||
if res >= 0:
|
||||
result = SupportedHashesKinds[res]
|
||||
else:
|
||||
result = HashNoSupport
|
||||
|
||||
proc getCipher(c: string): CryptKind =
|
||||
var cl = c.toLowerAscii()
|
||||
if cl == "aes-128-ctr":
|
||||
result = AES128CTR
|
||||
else:
|
||||
result = CipherNoSupport
|
||||
|
||||
proc deriveKey(password: string,
|
||||
salt: string,
|
||||
kdfkind: KdfKind,
|
||||
hashkind: HashKind,
|
||||
workfactor: int,
|
||||
output: var openarray[byte]): KeyFileStatus =
|
||||
if kdfkind == SCRYPT:
|
||||
return NotImplemented
|
||||
elif kdfkind == PBKDF2:
|
||||
var c = if workfactor == 0: Pbkdf2WorkFactor else: workfactor
|
||||
case hashkind
|
||||
of HashSHA2_224:
|
||||
var ctx: HMAC[sha224]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
result = Success
|
||||
of HashSHA2_256:
|
||||
var ctx: HMAC[sha256]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
result = Success
|
||||
of HashSHA2_384:
|
||||
var ctx: HMAC[sha384]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
result = Success
|
||||
of HashSHA2_512:
|
||||
var ctx: HMAC[sha512]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
result = Success
|
||||
of HashKECCAK224:
|
||||
var ctx: HMAC[keccak224]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
result = Success
|
||||
of HashKECCAK256:
|
||||
var ctx: HMAC[keccak256]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
result = Success
|
||||
of HashKECCAK384:
|
||||
var ctx: HMAC[keccak384]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
result = Success
|
||||
of HashKECCAK512:
|
||||
var ctx: HMAC[keccak512]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
result = Success
|
||||
of HashSHA3_224:
|
||||
var ctx: HMAC[sha3_224]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
result = Success
|
||||
of HashSHA3_256:
|
||||
var ctx: HMAC[sha3_256]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
result = Success
|
||||
of HashSHA3_384:
|
||||
var ctx: HMAC[sha3_384]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
result = Success
|
||||
of HashSHA3_512:
|
||||
var ctx: HMAC[sha3_512]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
result = Success
|
||||
else:
|
||||
result = PrfNotSupported
|
||||
|
||||
proc encryptKey(seckey: PrivateKey,
|
||||
cryptkind: CryptKind,
|
||||
key: openarray[byte],
|
||||
iv: openarray[byte],
|
||||
crypttext: var openarray[byte]): KeyFileStatus =
|
||||
if len(crypttext) != KeyLength:
|
||||
return BufferOverrun
|
||||
if cryptkind == AES128CTR:
|
||||
var ctx: CTR[aes128]
|
||||
ctx.init(toOpenArray(key, 0, 15), iv)
|
||||
ctx.encrypt(seckey.data, crypttext)
|
||||
ctx.clear()
|
||||
result = Success
|
||||
else:
|
||||
result = NotImplemented
|
||||
|
||||
proc decryptKey(ciphertext: openarray[byte],
|
||||
cryptkind: CryptKind,
|
||||
key: openarray[byte],
|
||||
iv: openarray[byte],
|
||||
plaintext: var openarray[byte]): KeyFileStatus =
|
||||
if len(ciphertext) != len(plaintext):
|
||||
return BufferOverrun
|
||||
if cryptkind == AES128CTR:
|
||||
if len(iv) != aes128.sizeBlock:
|
||||
return IncorrectIV
|
||||
var ctx: CTR[aes128]
|
||||
ctx.init(toOpenArray(key, 0, 15), iv)
|
||||
ctx.decrypt(ciphertext, plaintext)
|
||||
ctx.clear()
|
||||
result = Success
|
||||
else:
|
||||
result = NotImplemented
|
||||
|
||||
proc kdfParams(kdfkind: KdfKind, salt: string, workfactor: int,
|
||||
outjson: var JsonNode): KeyFileStatus =
|
||||
if kdfkind == SCRYPT:
|
||||
var wf = if workfactor == 0: ScryptWorkFactor else: workfactor
|
||||
outjson = %*
|
||||
{
|
||||
"dklen": DKLen,
|
||||
"n": wf,
|
||||
"r": ScryptR,
|
||||
"p": ScryptP,
|
||||
"salt": salt
|
||||
}
|
||||
result = Success
|
||||
elif kdfkind == PBKDF2:
|
||||
var wf = if workfactor == 0: Pbkdf2WorkFactor else: workfactor
|
||||
outjson = %*
|
||||
{
|
||||
"dklen": DKLen,
|
||||
"c": wf,
|
||||
"prf": "hmac-sha256",
|
||||
"salt": salt
|
||||
}
|
||||
result = Success
|
||||
else:
|
||||
result = NotImplemented
|
||||
|
||||
proc decodeHex(m: string): seq[byte] =
|
||||
if len(m) > 0:
|
||||
try:
|
||||
result = fromHex(m)
|
||||
except:
|
||||
result = newSeq[byte]()
|
||||
else:
|
||||
result = newSeq[byte]()
|
||||
|
||||
proc decodeSalt(m: string): string =
|
||||
var sarr: seq[byte]
|
||||
if len(m) > 0:
|
||||
try:
|
||||
sarr = fromHex(m)
|
||||
result = newString(len(sarr))
|
||||
copyMem(addr result[0], addr sarr[0], len(sarr))
|
||||
except:
|
||||
result = ""
|
||||
else:
|
||||
result = ""
|
||||
|
||||
proc compareMac(m1: openarray[byte], m2: openarray[byte]): bool =
|
||||
if len(m1) == len(m2) and len(m1) > 0:
|
||||
result = equalMem(unsafeAddr m1[0], unsafeAddr m2[0], len(m1))
|
||||
|
||||
proc createKeyFileJson*(seckey: PrivateKey,
|
||||
password: string,
|
||||
outjson: var JsonNode,
|
||||
version: int = 3,
|
||||
cryptkind: CryptKind = AES128CTR,
|
||||
kdfkind: KdfKind = PBKDF2,
|
||||
workfactor: int = 0): KeyFileStatus =
|
||||
## Create JSON object with keyfile structure.
|
||||
##
|
||||
## ``seckey`` - private key, which will be stored
|
||||
## ``password`` - encryption password
|
||||
## ``outjson`` - result JSON object
|
||||
## ``version`` - version of keyfile format (default is 3)
|
||||
## ``cryptkind`` - algorithm for private key encryption
|
||||
## (default is AES128-CTR)
|
||||
## ``kdfkind`` - algorithm for key deriviation function (default is PBKDF2)
|
||||
## ``workfactor`` - Key deriviation function work factor, 0 is to use
|
||||
## default workfactor.
|
||||
var res: KeyFileStatus
|
||||
var iv: array[aes128.sizeBlock, byte]
|
||||
var ciphertext: array[KeyLength, byte]
|
||||
var salt: array[SaltSize, byte]
|
||||
var saltstr = newString(SaltSize)
|
||||
var u: UUID
|
||||
if randomBytes(iv) != aes128.sizeBlock:
|
||||
return RandomError
|
||||
if randomBytes(salt) != SaltSize:
|
||||
return RandomError
|
||||
copyMem(addr saltstr[0], addr salt[0], SaltSize)
|
||||
if uuidGenerate(u) != 1:
|
||||
return UuidError
|
||||
if kdfkind != PBKDF2:
|
||||
return NotImplemented
|
||||
|
||||
var dkey = newSeq[byte](DKLen)
|
||||
res = deriveKey(password, saltstr, kdfkind, HashSHA2_256,
|
||||
workfactor, dkey)
|
||||
if res != Success:
|
||||
return res
|
||||
res = encryptKey(seckey, cryptkind, dkey, iv, ciphertext)
|
||||
if res != Success:
|
||||
return res
|
||||
var ctx: keccak256
|
||||
ctx.init()
|
||||
ctx.update(toOpenArray(dkey, 16, 31))
|
||||
ctx.update(ciphertext)
|
||||
var mac = ctx.finish()
|
||||
ctx.clear()
|
||||
|
||||
var params: JsonNode
|
||||
res = kdfParams(kdfkind, toHex(salt, true), workfactor, params)
|
||||
if res != Success:
|
||||
return res
|
||||
|
||||
outjson = %*
|
||||
{
|
||||
"address": seckey.getPublicKey().toAddress(false),
|
||||
"crypto": {
|
||||
"cipher": $cryptkind,
|
||||
"cipherparams": {
|
||||
"iv": toHex(iv, true)
|
||||
},
|
||||
"ciphertext": toHex(ciphertext, true),
|
||||
"kdf": $kdfkind,
|
||||
"kdfparams": params,
|
||||
"mac": toHex(mac.data, true),
|
||||
},
|
||||
"id": $u,
|
||||
"version": version
|
||||
}
|
||||
result = Success
|
||||
|
||||
proc decodeKeyFileJson*(j: JsonNode,
|
||||
password: string,
|
||||
seckey: var PrivateKey): KeyFileStatus =
|
||||
## Decode private key into ``seckey`` from keyfile json object ``j`` using
|
||||
## password string ``password``.
|
||||
var
|
||||
res: KeyFileStatus
|
||||
plaintext: array[KeyLength, byte]
|
||||
|
||||
var crypto = j.getOrDefault("crypto")
|
||||
if isNil(crypto):
|
||||
return MalformedError
|
||||
|
||||
var kdf = crypto.getOrDefault("kdf")
|
||||
if isNil(kdf):
|
||||
return MalformedError
|
||||
|
||||
var cipherparams = crypto.getOrDefault("cipherparams")
|
||||
if isNil(cipherparams):
|
||||
return MalformedError
|
||||
|
||||
if kdf.getStr() == "pbkdf2":
|
||||
var params = crypto.getOrDefault("kdfparams")
|
||||
|
||||
if isNil(params):
|
||||
return MalformedError
|
||||
|
||||
var salt = decodeSalt(params.getOrDefault("salt").getStr())
|
||||
var ciphertext = decodeHex(crypto.getOrDefault("ciphertext").getStr())
|
||||
var mactext = decodeHex(crypto.getOrDefault("mac").getStr())
|
||||
var cryptkind = getCipher(crypto.getOrDefault("cipher").getStr())
|
||||
var iv = decodeHex(cipherparams.getOrDefault("iv").getStr())
|
||||
|
||||
if len(salt) == 0:
|
||||
return EmptySalt
|
||||
if len(ciphertext) == 0:
|
||||
return EmptyCiphertext
|
||||
if len(mactext) == 0:
|
||||
return EmptyMac
|
||||
if cryptkind == CipherNoSupport:
|
||||
return CipherNotSupported
|
||||
|
||||
var dklen = params.getOrDefault("dklen").getInt()
|
||||
var c = params.getOrDefault("c").getInt()
|
||||
var hash = getPrfHash(params.getOrDefault("prf").getStr())
|
||||
|
||||
if hash == HashNoSupport:
|
||||
return PrfNotSupported
|
||||
if dklen == 0 or dklen > MaxDKLen:
|
||||
return IncorrectDKLen
|
||||
if len(ciphertext) != KeyLength:
|
||||
return IncorrectPrivateKey
|
||||
|
||||
var dkey = newSeq[byte](dklen)
|
||||
res = deriveKey(password, salt, PBKDF2, hash, c, dkey)
|
||||
if res != Success:
|
||||
return res
|
||||
|
||||
var ctx: keccak256
|
||||
ctx.init()
|
||||
ctx.update(toOpenArray(dkey, 16, 31))
|
||||
ctx.update(ciphertext)
|
||||
var mac = ctx.finish()
|
||||
if not compareMac(mac.data, mactext):
|
||||
return IncorrectMac
|
||||
|
||||
res = decryptKey(ciphertext, cryptkind, dkey, iv, plaintext)
|
||||
if res != Success:
|
||||
return res
|
||||
try:
|
||||
seckey = initPrivateKey(plaintext)
|
||||
except:
|
||||
return IncorrectPrivateKey
|
||||
result = Success
|
||||
else:
|
||||
return KdfNotSupported
|
||||
|
||||
proc loadKeyFile*(pathname: string,
|
||||
password: string,
|
||||
seckey: var PrivateKey): KeyFileStatus =
|
||||
## Load and decode private key ``seckey`` from file with pathname
|
||||
## ``pathname``, using password string ``password``.
|
||||
var data: JsonNode
|
||||
var stream = newFileStream(pathname)
|
||||
if isNil(stream):
|
||||
return OsError
|
||||
|
||||
try:
|
||||
data = parseFile(pathname)
|
||||
result = Success
|
||||
except:
|
||||
result = JsonError
|
||||
finally:
|
||||
stream.close()
|
||||
|
||||
if result == Success:
|
||||
result = decodeKeyFileJson(data, password, seckey)
|
||||
|
||||
proc saveKeyFile*(pathname: string,
|
||||
jobject: JsonNode): KeyFileStatus =
|
||||
## Save JSON object ``jobject`` to file with pathname ``pathname``.
|
||||
var
|
||||
f: File
|
||||
if not f.open(pathname, fmWrite):
|
||||
return OsError
|
||||
try:
|
||||
f.write($jobject)
|
||||
result = Success
|
||||
except:
|
||||
result = OsError
|
||||
finally:
|
||||
f.close()
|
|
@ -0,0 +1,154 @@
|
|||
#
|
||||
# Ethereum KeyFile
|
||||
# (c) Copyright 2018
|
||||
# Status Research & Development GmbH
|
||||
#
|
||||
# Licensed under either of
|
||||
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||
# MIT license (LICENSE-MIT)
|
||||
|
||||
## This module implements interface to cross-platform UUID
|
||||
## generator.
|
||||
##
|
||||
## - ``Windows`` - using rpcrt4.dll's `UuidCreate()`.
|
||||
## - ``Linux`` and ``Android`` - using `/proc/sys/kernel/random/uuid`.
|
||||
## - ``MacOS`` and ``iOS`` - using `uuid_generate_random()`.
|
||||
## - ``FreeBSD``, ``OpenBSD``, ``NetBSD``,
|
||||
## ``DragonflyBSD`` - using `uuid_create()`.
|
||||
|
||||
{.deadCodeElim:on.}
|
||||
|
||||
import nimcrypto/utils, endians
|
||||
|
||||
type
|
||||
UUIDException = object of Exception
|
||||
|
||||
UUID* = object
|
||||
## Represents UUID object
|
||||
data*: array[16, byte]
|
||||
|
||||
proc raiseInvalidUuid() =
|
||||
raise newException(UUIDException, "Invalid UUID!")
|
||||
|
||||
proc uuidFromString*(s: string): UUID =
|
||||
## Convert string representation of UUID into UUID object.
|
||||
if len(s) != 36:
|
||||
raiseInvalidUuid()
|
||||
for i in 0..<len(s):
|
||||
if s[i] notin {'A'..'F', '0'..'9', 'a'..'f', '-'}:
|
||||
raiseInvalidUuid()
|
||||
var d = fromHex(stripSpaces(s))
|
||||
bigEndian32(addr result.data[0], addr d[0])
|
||||
bigEndian16(addr result.data[4], addr d[4])
|
||||
bigEndian16(addr result.data[6], addr d[6])
|
||||
copyMem(addr result.data[8], addr d[8], 8)
|
||||
|
||||
proc uuidToString*(u: UUID, lowercase: bool = false): string =
|
||||
## Convert UUID object into string representation.
|
||||
## You can use ``lowercase`` flag to specify letter case
|
||||
## of output string.
|
||||
result = newStringOfCap(38)
|
||||
var d: array[8, byte]
|
||||
bigEndian32(addr d[0], unsafeAddr u.data[0])
|
||||
bigEndian16(addr d[4], unsafeAddr u.data[4])
|
||||
bigEndian16(addr d[6], unsafeAddr u.data[6])
|
||||
result.add(toHex(toOpenArray(d, 0, 3), lowercase))
|
||||
result.add("-")
|
||||
result.add(toHex(toOpenArray(d, 4, 5), lowercase))
|
||||
result.add("-")
|
||||
result.add(toHex(toOpenArray(d, 6, 7), lowercase))
|
||||
result.add("-")
|
||||
result.add(toHex(toOpenArray(u.data, 8, 9), lowercase))
|
||||
result.add("-")
|
||||
result.add(toHex(toOpenArray(u.data, 10, 15), lowercase))
|
||||
|
||||
proc `$`*(u: UUID): string {.inline.} =
|
||||
## Convert UUID object to lowercase string representation.
|
||||
uuidToString(u, true)
|
||||
|
||||
when defined(nimdoc):
|
||||
proc uuidGenerate*(output: var UUID): int
|
||||
## Generates new unique UUID and store it to `output`.
|
||||
##
|
||||
## Return 1 on success, and 0 on failure
|
||||
|
||||
when defined(posix):
|
||||
when defined(macosx):
|
||||
proc uuidGenerateRandom(a: pointer)
|
||||
{.importc: "uuid_generate_random", header: "uuid/uuid.h".}
|
||||
|
||||
proc uuidGenerate*(output: var UUID): int =
|
||||
uuidGenerateRandom(cast[pointer](addr output))
|
||||
result = 1
|
||||
|
||||
elif defined(freebsd) or defined(netbsd) or defined(openbsd) or
|
||||
defined(dragonflybsd):
|
||||
|
||||
proc uuidCreate(a: pointer, s: ptr uint32)
|
||||
{.importc: "uuid_create", header: "uuid.h".}
|
||||
|
||||
proc uuidGenerate*(output: var UUID): int =
|
||||
var status: uint32 = 0
|
||||
uuidCreate(cast[pointer](addr output), addr status)
|
||||
if status == 0:
|
||||
result = 1
|
||||
else:
|
||||
result = 0
|
||||
|
||||
elif defined(linux) or defined(android):
|
||||
import posix, os, nimcrypto/sysrand
|
||||
|
||||
proc uuidRead(bytes: var string, length: int): int =
|
||||
result = -1
|
||||
let fd = posix.open("/proc/sys/kernel/random/uuid", posix.O_RDONLY)
|
||||
if fd != -1:
|
||||
result = 0
|
||||
while result < length:
|
||||
var p = cast[pointer](cast[uint](addr bytes[0]) + uint(result))
|
||||
var res = posix.read(fd, p, length - result)
|
||||
if res > 0:
|
||||
result += res
|
||||
elif res == 0:
|
||||
break
|
||||
else:
|
||||
if osLastError().int32 != EINTR:
|
||||
result = -1
|
||||
break
|
||||
discard posix.close(fd)
|
||||
|
||||
proc uuidGenerate*(output: var UUID): int =
|
||||
result = 0
|
||||
var buffer = newString(37)
|
||||
if uuidRead(buffer, 36) == 36:
|
||||
buffer.setLen(36)
|
||||
output = uuidFromString(buffer)
|
||||
result = 1
|
||||
else:
|
||||
if randomBytes(output.data) == sizeof(output.data):
|
||||
result = 1
|
||||
else:
|
||||
import nimcrypto/sysrand
|
||||
|
||||
proc uuidGenerate*(output: var UUID): int =
|
||||
if randomBytes(output.data) == sizeof(output.data):
|
||||
result = 1
|
||||
else:
|
||||
result = 0
|
||||
|
||||
elif defined(windows):
|
||||
proc UuidCreate(p: pointer): int32
|
||||
{.stdcall, dynlib: "rpcrt4", importc: "UuidCreate".}
|
||||
|
||||
proc uuidGenerate*(output: var UUID): int =
|
||||
if UuidCreate(cast[pointer](addr output)) == 0:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
elif not defined(nimdoc):
|
||||
import nimcrypto/sysrand
|
||||
|
||||
proc uuidGenerate*(output: var UUID): int =
|
||||
if randomBytes(output.data) == sizeof(output.data):
|
||||
result = 1
|
||||
else:
|
||||
result = 0
|
|
@ -0,0 +1,3 @@
|
|||
switch("path", "$projectDir/..")
|
||||
switch("threads", "on")
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
#
|
||||
# Ethereum KeyFile
|
||||
# (c) Copyright 2018
|
||||
# Status Research & Development GmbH
|
||||
#
|
||||
# Licensed under either of
|
||||
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||
# MIT license (LICENSE-MIT)
|
||||
|
||||
import eth_keys, eth_keyfile/[uuid, keyfile], json, strutils, os, unittest
|
||||
|
||||
# Test vectors copied from
|
||||
# https://github.com/ethereum/tests/blob/develop/KeyStoreTests/basic_tests.json
|
||||
|
||||
var TestVectors = [
|
||||
%*{
|
||||
"keyfile": {
|
||||
"crypto" : {
|
||||
"cipher" : "aes-128-ctr",
|
||||
"cipherparams" : {"iv" : "6087dab2f9fdbbfaddc31a909735c1e6"},
|
||||
"ciphertext" : "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46",
|
||||
"kdf" : "pbkdf2",
|
||||
"kdfparams" : {
|
||||
"c" : 262144,
|
||||
"dklen" : 32,
|
||||
"prf" : "hmac-sha256",
|
||||
"salt" : "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"
|
||||
},
|
||||
"mac" : "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2"
|
||||
},
|
||||
"id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6",
|
||||
"version" : 3
|
||||
},
|
||||
"name": "test1",
|
||||
"password": "testpassword",
|
||||
"priv": "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"
|
||||
},
|
||||
%*{
|
||||
"keyfile": {
|
||||
"version": 3,
|
||||
"crypto": {
|
||||
"ciphertext": "ee75456c006b1e468133c5d2a916bacd3cf515ced4d9b021b5c59978007d1e87",
|
||||
"version": 1,
|
||||
"kdf": "pbkdf2",
|
||||
"kdfparams": {
|
||||
"dklen": 32,
|
||||
"c": 262144,
|
||||
"prf": "hmac-sha256",
|
||||
"salt": "504490577620f64f43d73f29479c2cf0"
|
||||
},
|
||||
"mac": "196815708465de9af7504144a1360d08874fc3c30bb0e648ce88fbc36830d35d",
|
||||
"cipherparams": {"iv": "514ccc8c4fb3e60e5538e0cf1e27c233"},
|
||||
"cipher": "aes-128-ctr"
|
||||
},
|
||||
"id": "98d193c7-5174-4c7c-5345-c1daf95477b5"
|
||||
},
|
||||
"name": "python_generated_test_with_odd_iv",
|
||||
"password": "foo",
|
||||
"priv": "0101010101010101010101010101010101010101010101010101010101010101"
|
||||
},
|
||||
%*{
|
||||
"keyfile": {
|
||||
"version": 3,
|
||||
"crypto": {
|
||||
"ciphertext": "d69313b6470ac1942f75d72ebf8818a0d484ac78478a132ee081cd954d6bd7a9",
|
||||
"cipherparams": {"iv": "ffffffffffffffffffffffffffffffff"},
|
||||
"kdf": "pbkdf2",
|
||||
"kdfparams": {
|
||||
"dklen": 32,
|
||||
"c": 262144,
|
||||
"prf": "hmac-sha256",
|
||||
"salt": "c82ef14476014cbf438081a42709e2ed"
|
||||
},
|
||||
"mac": "cf6bfbcc77142a22c4a908784b4a16f1023a1d0e2aff404c20158fa4f1587177",
|
||||
"cipher": "aes-128-ctr",
|
||||
"version": 1
|
||||
},
|
||||
"id": "abb67040-8dbe-0dad-fc39-2b082ef0ee5f"
|
||||
},
|
||||
"name": "evilnonce",
|
||||
"password": "bar",
|
||||
"priv": "0202020202020202020202020202020202020202020202020202020202020202"
|
||||
}
|
||||
]
|
||||
|
||||
var jobject: JsonNode
|
||||
|
||||
suite "KeyFile test suite":
|
||||
test "KeyStoreTests/basic_tests.json test1":
|
||||
var seckey: PrivateKey
|
||||
var expectkey = initPrivateKey(TestVectors[0].getOrDefault("priv").getStr())
|
||||
check:
|
||||
decodeKeyFileJson(TestVectors[0].getOrDefault("keyfile"),
|
||||
TestVectors[0].getOrDefault("password").getStr(),
|
||||
seckey) == KeyFileStatus.Success
|
||||
seckey.data == expectkey.data
|
||||
test "KeyStoreTests/basic_tests.json python_generated_test_with_odd_iv":
|
||||
var seckey: PrivateKey
|
||||
var expectkey = initPrivateKey(TestVectors[1].getOrDefault("priv").getStr())
|
||||
check:
|
||||
decodeKeyFileJson(TestVectors[1].getOrDefault("keyfile"),
|
||||
TestVectors[1].getOrDefault("password").getStr(),
|
||||
seckey) == KeyFileStatus.Success
|
||||
seckey.data == expectkey.data
|
||||
test "KeyStoreTests/basic_tests.json evilnonce":
|
||||
var seckey: PrivateKey
|
||||
var expectkey = initPrivateKey(TestVectors[2].getOrDefault("priv").getStr())
|
||||
check:
|
||||
decodeKeyFileJson(TestVectors[2].getOrDefault("keyfile"),
|
||||
TestVectors[2].getOrDefault("password").getStr(),
|
||||
seckey) == KeyFileStatus.Success
|
||||
seckey.data == expectkey.data
|
||||
test "KeyStoreTests/basic_tests.json evilnonce with wrong password":
|
||||
var seckey: PrivateKey
|
||||
check:
|
||||
decodeKeyFileJson(TestVectors[2].getOrDefault("keyfile"),
|
||||
"wrongpassword",
|
||||
seckey) == KeyFileStatus.IncorrectMac
|
||||
test "Create/Save/Load test":
|
||||
var seckey0 = newPrivateKey()
|
||||
var seckey1: PrivateKey
|
||||
check:
|
||||
createKeyFileJson(seckey0, "randompassword",
|
||||
jobject) == KeyFileStatus.Success
|
||||
saveKeyFile("test.keyfile", jobject) == KeyFileStatus.Success
|
||||
loadKeyFile("test.keyfile", "randompassword",
|
||||
seckey1) == KeyFileStatus.Success
|
||||
seckey0.data == seckey1.data
|
||||
removeFile("test.keyfile")
|
||||
test "Load non-existent pathname test":
|
||||
var seckey: PrivateKey
|
||||
check:
|
||||
loadKeyFile("nonexistant.keyfile", "password",
|
||||
seckey) == KeyFileStatus.OsError
|
|
@ -0,0 +1,23 @@
|
|||
#
|
||||
# Ethereum KeyFile
|
||||
# (c) Copyright 2018
|
||||
# Status Research & Development GmbH
|
||||
#
|
||||
# Licensed under either of
|
||||
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||
# MIT license (LICENSE-MIT)
|
||||
|
||||
import eth_keyfile/uuid, strutils, unittest
|
||||
|
||||
suite "Cross-platform UUID test suite":
|
||||
test "Platform UUID check":
|
||||
var u: UUID
|
||||
check uuidGenerate(u) == 1
|
||||
test "Conversion test":
|
||||
var u: UUID
|
||||
check:
|
||||
uuidGenerate(u) == 1
|
||||
len($u) == 36
|
||||
$uuidFromString($u) == $u
|
||||
uuidToString(u, true) == $u
|
||||
uuidToString(u, false) == toUpperAscii($u)
|
Loading…
Reference in New Issue