Secp more refactor (#211)

* simplify some modules

* mark several modules with raises
* fix clearing of keys in auth.nim
* fix keyfile case dropping off
* fix keyfile stream storage
* uuid should be output in lowercase

* enode: simplify API
This commit is contained in:
Jacek Sieka 2020-04-06 18:24:15 +02:00 committed by GitHub
parent ac5bbe4d3d
commit 0b110f3287
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 783 additions and 856 deletions

View File

@ -7,8 +7,12 @@
# Apache License, version 2.0, (LICENSE-APACHEv2) # Apache License, version 2.0, (LICENSE-APACHEv2)
# MIT license (LICENSE-MIT) # MIT license (LICENSE-MIT)
{.push raises: [Defect].}
import nimcrypto/[bcmode, hmac, rijndael, pbkdf2, sha2, sysrand, utils, keccak], import nimcrypto/[bcmode, hmac, rijndael, pbkdf2, sha2, sysrand, utils, keccak],
eth/keys, json, uuid, os, strutils, streams eth/keys, json, uuid, strutils, stew/result
export result
const const
# Version 3 constants # Version 3 constants
@ -21,31 +25,26 @@ const
ScryptWorkFactor = 262_144 ScryptWorkFactor = 262_144
type type
KeyFileStatus* = enum KeyFileError* = enum
Success, ## No Error RandomError = "kf: Random generator error"
RandomError, ## Random generator error UuidError = "kf: UUID generator error"
UuidError, ## UUID generator error BufferOverrun = "kf: Supplied buffer is too small"
BufferOverrun, ## Supplied buffer is too small IncorrectDKLen = "kf: `dklen` parameter is 0 or more then MaxDKLen"
IncorrectDKLen, ## `dklen` parameter is 0 or more then MaxDKLen MalformedError = "kf: JSON has incorrect structure"
MalformedError, ## JSON has incorrect structure NotImplemented = "kf: Feature is not implemented"
NotImplemented, ## Feature is not implemented NotSupported = "kf: Feature is not supported"
NotSupported, ## Feature is not supported EmptyMac = "kf: `mac` parameter is zero length or not in hexadecimal form"
EmptyMac, ## `mac` parameter is zero length or not in EmptyCiphertext = "kf: `ciphertext` parameter is zero length or not in hexadecimal format"
## hexadecimal form EmptySalt = "kf: `salt` parameter is zero length or not in hexadecimal format"
EmptyCiphertext, ## `ciphertext` parameter is zero length or not in EmptyIV = "kf: `cipherparams.iv` parameter is zero length or not in hexadecimal format"
## hexadecimal format IncorrectIV = "kf: Size of IV vector is not equal to cipher block size"
EmptySalt, ## `salt` parameter is zero length or not in PrfNotSupported = "kf: PRF algorithm for PBKDF2 is not supported"
## hexadecimal format KdfNotSupported = "kf: KDF algorithm is not supported"
EmptyIV, ## `cipherparams.iv` parameter is zero length or not in CipherNotSupported = "kf: `cipher` parameter is not supported"
## hexadecimal format IncorrectMac = "kf: `mac` verification failed"
IncorrectIV, ## Size of IV vector is not equal to cipher block size IncorrectPrivateKey = "kf: incorrect private key"
PrfNotSupported, ## PRF algorithm for PBKDF2 is not supported OsError = "kf: OS specific error"
KdfNotSupported, ## KDF algorithm is not supported JsonError = "kf: JSON encoder/decoder error"
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 KdfKind* = enum
PBKDF2, ## PBKDF2 PBKDF2, ## PBKDF2
@ -60,6 +59,11 @@ type
CipherNoSupport, ## Cipher not supported CipherNoSupport, ## Cipher not supported
AES128CTR ## AES-128-CTR AES128CTR ## AES-128-CTR
KfResult*[T] = Result[T, KeyFileError]
proc mapErrTo[T, E](r: Result[T, E], v: static KeyFileError): KfResult[T] =
r.mapErr(proc (e: E): KeyFileError = v)
const const
SupportedHashes = [ SupportedHashes = [
"sha224", "sha256", "sha384", "sha512", "sha224", "sha256", "sha384", "sha512",
@ -109,103 +113,98 @@ proc deriveKey(password: string,
salt: string, salt: string,
kdfkind: KdfKind, kdfkind: KdfKind,
hashkind: HashKind, hashkind: HashKind,
workfactor: int, workfactor: int): KfResult[array[DKLen, byte]] =
output: var openarray[byte]): KeyFileStatus = if kdfkind == PBKDF2:
if kdfkind == SCRYPT: var output: array[DKLen, byte]
return NotImplemented
elif kdfkind == PBKDF2:
var c = if workfactor == 0: Pbkdf2WorkFactor else: workfactor var c = if workfactor == 0: Pbkdf2WorkFactor else: workfactor
case hashkind case hashkind
of HashSHA2_224: of HashSHA2_224:
var ctx: HMAC[sha224] var ctx: HMAC[sha224]
discard ctx.pbkdf2(password, salt, c, output) discard ctx.pbkdf2(password, salt, c, output)
result = Success ok(output)
of HashSHA2_256: of HashSHA2_256:
var ctx: HMAC[sha256] var ctx: HMAC[sha256]
discard ctx.pbkdf2(password, salt, c, output) discard ctx.pbkdf2(password, salt, c, output)
result = Success ok(output)
of HashSHA2_384: of HashSHA2_384:
var ctx: HMAC[sha384] var ctx: HMAC[sha384]
discard ctx.pbkdf2(password, salt, c, output) discard ctx.pbkdf2(password, salt, c, output)
result = Success ok(output)
of HashSHA2_512: of HashSHA2_512:
var ctx: HMAC[sha512] var ctx: HMAC[sha512]
discard ctx.pbkdf2(password, salt, c, output) discard ctx.pbkdf2(password, salt, c, output)
result = Success ok(output)
of HashKECCAK224: of HashKECCAK224:
var ctx: HMAC[keccak224] var ctx: HMAC[keccak224]
discard ctx.pbkdf2(password, salt, c, output) discard ctx.pbkdf2(password, salt, c, output)
result = Success ok(output)
of HashKECCAK256: of HashKECCAK256:
var ctx: HMAC[keccak256] var ctx: HMAC[keccak256]
discard ctx.pbkdf2(password, salt, c, output) discard ctx.pbkdf2(password, salt, c, output)
result = Success ok(output)
of HashKECCAK384: of HashKECCAK384:
var ctx: HMAC[keccak384] var ctx: HMAC[keccak384]
discard ctx.pbkdf2(password, salt, c, output) discard ctx.pbkdf2(password, salt, c, output)
result = Success ok(output)
of HashKECCAK512: of HashKECCAK512:
var ctx: HMAC[keccak512] var ctx: HMAC[keccak512]
discard ctx.pbkdf2(password, salt, c, output) discard ctx.pbkdf2(password, salt, c, output)
result = Success ok(output)
of HashSHA3_224: of HashSHA3_224:
var ctx: HMAC[sha3_224] var ctx: HMAC[sha3_224]
discard ctx.pbkdf2(password, salt, c, output) discard ctx.pbkdf2(password, salt, c, output)
result = Success ok(output)
of HashSHA3_256: of HashSHA3_256:
var ctx: HMAC[sha3_256] var ctx: HMAC[sha3_256]
discard ctx.pbkdf2(password, salt, c, output) discard ctx.pbkdf2(password, salt, c, output)
result = Success ok(output)
of HashSHA3_384: of HashSHA3_384:
var ctx: HMAC[sha3_384] var ctx: HMAC[sha3_384]
discard ctx.pbkdf2(password, salt, c, output) discard ctx.pbkdf2(password, salt, c, output)
result = Success ok(output)
of HashSHA3_512: of HashSHA3_512:
var ctx: HMAC[sha3_512] var ctx: HMAC[sha3_512]
discard ctx.pbkdf2(password, salt, c, output) discard ctx.pbkdf2(password, salt, c, output)
result = Success ok(output)
else: else:
result = PrfNotSupported err(PrfNotSupported)
else:
err(NotImplemented)
proc encryptKey(seckey: PrivateKey, proc encryptKey(seckey: PrivateKey,
cryptkind: CryptKind, cryptkind: CryptKind,
key: openarray[byte], key: openarray[byte],
iv: openarray[byte], iv: openarray[byte]): KfResult[array[KeyLength, byte]] =
crypttext: var openarray[byte]): KeyFileStatus =
if len(crypttext) != KeyLength:
return BufferOverrun
if cryptkind == AES128CTR: if cryptkind == AES128CTR:
var crypttext: array[KeyLength, byte]
var ctx: CTR[aes128] var ctx: CTR[aes128]
ctx.init(toOpenArray(key, 0, 15), iv) ctx.init(toOpenArray(key, 0, 15), iv)
ctx.encrypt(seckey.toRaw(), crypttext) ctx.encrypt(seckey.toRaw(), crypttext)
ctx.clear() ctx.clear()
result = Success ok(crypttext)
else: else:
result = NotImplemented err(NotImplemented)
proc decryptKey(ciphertext: openarray[byte], proc decryptKey(ciphertext: openarray[byte],
cryptkind: CryptKind, cryptkind: CryptKind,
key: openarray[byte], key: openarray[byte],
iv: openarray[byte], iv: openarray[byte]): KfResult[array[KeyLength, byte]] =
plaintext: var openarray[byte]): KeyFileStatus =
if len(ciphertext) != len(plaintext):
return BufferOverrun
if cryptkind == AES128CTR: if cryptkind == AES128CTR:
if len(iv) != aes128.sizeBlock: if len(iv) != aes128.sizeBlock:
return IncorrectIV return err(IncorrectIV)
var plaintext: array[KeyLength, byte]
var ctx: CTR[aes128] var ctx: CTR[aes128]
ctx.init(toOpenArray(key, 0, 15), iv) ctx.init(toOpenArray(key, 0, 15), iv)
ctx.decrypt(ciphertext, plaintext) ctx.decrypt(ciphertext, plaintext)
ctx.clear() ctx.clear()
result = Success ok(plaintext)
else: else:
result = NotImplemented err(NotImplemented)
proc kdfParams(kdfkind: KdfKind, salt: string, workfactor: int, proc kdfParams(kdfkind: KdfKind, salt: string, workfactor: int): KfResult[JsonNode] =
outjson: var JsonNode): KeyFileStatus =
if kdfkind == SCRYPT: if kdfkind == SCRYPT:
var wf = if workfactor == 0: ScryptWorkFactor else: workfactor let wf = if workfactor == 0: ScryptWorkFactor else: workfactor
outjson = %* ok(%*
{ {
"dklen": DKLen, "dklen": DKLen,
"n": wf, "n": wf,
@ -213,19 +212,19 @@ proc kdfParams(kdfkind: KdfKind, salt: string, workfactor: int,
"p": ScryptP, "p": ScryptP,
"salt": salt "salt": salt
} }
result = Success )
elif kdfkind == PBKDF2: elif kdfkind == PBKDF2:
var wf = if workfactor == 0: Pbkdf2WorkFactor else: workfactor let wf = if workfactor == 0: Pbkdf2WorkFactor else: workfactor
outjson = %* ok(%*
{ {
"dklen": DKLen, "dklen": DKLen,
"c": wf, "c": wf,
"prf": "hmac-sha256", "prf": "hmac-sha256",
"salt": salt "salt": salt
} }
result = Success )
else: else:
result = NotImplemented err(NotImplemented)
proc decodeHex(m: string): seq[byte] = proc decodeHex(m: string): seq[byte] =
if len(m) > 0: if len(m) > 0:
@ -254,11 +253,10 @@ proc compareMac(m1: openarray[byte], m2: openarray[byte]): bool =
proc createKeyFileJson*(seckey: PrivateKey, proc createKeyFileJson*(seckey: PrivateKey,
password: string, password: string,
outjson: var JsonNode,
version: int = 3, version: int = 3,
cryptkind: CryptKind = AES128CTR, cryptkind: CryptKind = AES128CTR,
kdfkind: KdfKind = PBKDF2, kdfkind: KdfKind = PBKDF2,
workfactor: int = 0): KeyFileStatus = workfactor: int = 0): KfResult[JsonNode] =
## Create JSON object with keyfile structure. ## Create JSON object with keyfile structure.
## ##
## ``seckey`` - private key, which will be stored ## ``seckey`` - private key, which will be stored
@ -270,30 +268,24 @@ proc createKeyFileJson*(seckey: PrivateKey,
## ``kdfkind`` - algorithm for key deriviation function (default is PBKDF2) ## ``kdfkind`` - algorithm for key deriviation function (default is PBKDF2)
## ``workfactor`` - Key deriviation function work factor, 0 is to use ## ``workfactor`` - Key deriviation function work factor, 0 is to use
## default workfactor. ## default workfactor.
var res: KeyFileStatus
var iv: array[aes128.sizeBlock, byte] var iv: array[aes128.sizeBlock, byte]
var ciphertext: array[KeyLength, byte]
var salt: array[SaltSize, byte] var salt: array[SaltSize, byte]
var saltstr = newString(SaltSize) var saltstr = newString(SaltSize)
var u: UUID
if randomBytes(iv) != aes128.sizeBlock: if randomBytes(iv) != aes128.sizeBlock:
return RandomError return err(RandomError)
if randomBytes(salt) != SaltSize: if randomBytes(salt) != SaltSize:
return RandomError return err(RandomError)
copyMem(addr saltstr[0], addr salt[0], SaltSize) copyMem(addr saltstr[0], addr salt[0], SaltSize)
if uuidGenerate(u) != 1:
return UuidError
if kdfkind != PBKDF2:
return NotImplemented
var dkey = newSeq[byte](DKLen) let u = ? uuidGenerate().mapErrTo(UuidError)
res = deriveKey(password, saltstr, kdfkind, HashSHA2_256,
workfactor, dkey) if kdfkind != PBKDF2:
if res != Success: return err(NotImplemented)
return res
res = encryptKey(seckey, cryptkind, dkey, iv, ciphertext) let
if res != Success: dkey = ? deriveKey(password, saltstr, kdfkind, HashSHA2_256, workfactor)
return res ciphertext = ? encryptKey(seckey, cryptkind, dkey, iv)
var ctx: keccak256 var ctx: keccak256
ctx.init() ctx.init()
ctx.update(toOpenArray(dkey, 16, 31)) ctx.update(toOpenArray(dkey, 16, 31))
@ -301,14 +293,11 @@ proc createKeyFileJson*(seckey: PrivateKey,
var mac = ctx.finish() var mac = ctx.finish()
ctx.clear() ctx.clear()
var params: JsonNode let params = ? kdfParams(kdfkind, toHex(salt, true), workfactor)
res = kdfParams(kdfkind, toHex(salt, true), workfactor, params)
if res != Success:
return res
outjson = %* ok(%*
{ {
"address": seckey.toPublicKey().tryGet().toAddress(false), "address": (? seckey.toPublicKey().mapErrTo(IncorrectPrivateKey)).toAddress(false),
"crypto": { "crypto": {
"cipher": $cryptkind, "cipher": $cryptkind,
"cipherparams": { "cipherparams": {
@ -322,34 +311,29 @@ proc createKeyFileJson*(seckey: PrivateKey,
"id": $u, "id": $u,
"version": version "version": version
} }
result = Success )
proc decodeKeyFileJson*(j: JsonNode, proc decodeKeyFileJson*(j: JsonNode,
password: string, password: string): KfResult[PrivateKey] =
seckey: var PrivateKey): KeyFileStatus =
## Decode private key into ``seckey`` from keyfile json object ``j`` using ## Decode private key into ``seckey`` from keyfile json object ``j`` using
## password string ``password``. ## password string ``password``.
var
res: KeyFileStatus
plaintext: array[KeyLength, byte]
var crypto = j.getOrDefault("crypto") var crypto = j.getOrDefault("crypto")
if isNil(crypto): if isNil(crypto):
return MalformedError return err(MalformedError)
var kdf = crypto.getOrDefault("kdf") var kdf = crypto.getOrDefault("kdf")
if isNil(kdf): if isNil(kdf):
return MalformedError return err(MalformedError)
var cipherparams = crypto.getOrDefault("cipherparams") var cipherparams = crypto.getOrDefault("cipherparams")
if isNil(cipherparams): if isNil(cipherparams):
return MalformedError return err(MalformedError)
if kdf.getStr() == "pbkdf2": if kdf.getStr() == "pbkdf2":
var params = crypto.getOrDefault("kdfparams") var params = crypto.getOrDefault("kdfparams")
if isNil(params): if isNil(params):
return MalformedError return err(MalformedError)
var salt = decodeSalt(params.getOrDefault("salt").getStr()) var salt = decodeSalt(params.getOrDefault("salt").getStr())
var ciphertext = decodeHex(crypto.getOrDefault("ciphertext").getStr()) var ciphertext = decodeHex(crypto.getOrDefault("ciphertext").getStr())
@ -358,29 +342,26 @@ proc decodeKeyFileJson*(j: JsonNode,
var iv = decodeHex(cipherparams.getOrDefault("iv").getStr()) var iv = decodeHex(cipherparams.getOrDefault("iv").getStr())
if len(salt) == 0: if len(salt) == 0:
return EmptySalt return err(EmptySalt)
if len(ciphertext) == 0: if len(ciphertext) == 0:
return EmptyCiphertext return err(EmptyCiphertext)
if len(mactext) == 0: if len(mactext) == 0:
return EmptyMac return err(EmptyMac)
if cryptkind == CipherNoSupport: if cryptkind == CipherNoSupport:
return CipherNotSupported return err(CipherNotSupported)
var dklen = params.getOrDefault("dklen").getInt() var dklen = params.getOrDefault("dklen").getInt()
var c = params.getOrDefault("c").getInt() var c = params.getOrDefault("c").getInt()
var hash = getPrfHash(params.getOrDefault("prf").getStr()) var hash = getPrfHash(params.getOrDefault("prf").getStr())
if hash == HashNoSupport: if hash == HashNoSupport:
return PrfNotSupported return err(PrfNotSupported)
if dklen == 0 or dklen > MaxDKLen: if dklen == 0 or dklen > MaxDKLen:
return IncorrectDKLen return err(IncorrectDKLen)
if len(ciphertext) != KeyLength: if len(ciphertext) != KeyLength:
return IncorrectPrivateKey return err(IncorrectPrivateKey)
var dkey = newSeq[byte](dklen) let dkey = ? deriveKey(password, salt, PBKDF2, hash, c)
res = deriveKey(password, salt, PBKDF2, hash, c, dkey)
if res != Success:
return res
var ctx: keccak256 var ctx: keccak256
ctx.init() ctx.init()
@ -388,51 +369,39 @@ proc decodeKeyFileJson*(j: JsonNode,
ctx.update(ciphertext) ctx.update(ciphertext)
var mac = ctx.finish() var mac = ctx.finish()
if not compareMac(mac.data, mactext): if not compareMac(mac.data, mactext):
return IncorrectMac return err(IncorrectMac)
res = decryptKey(ciphertext, cryptkind, dkey, iv, plaintext) let plaintext = ? decryptKey(ciphertext, cryptkind, dkey, iv)
if res != Success:
return res PrivateKey.fromRaw(plaintext).mapErrTo(IncorrectPrivateKey)
try:
seckey = PrivateKey.fromRaw(plaintext).tryGet()
except CatchableError:
return IncorrectPrivateKey
result = Success
else: else:
return KdfNotSupported err(KdfNotSupported)
proc loadKeyFile*(pathname: string, proc loadKeyFile*(pathname: string,
password: string, password: string): KfResult[PrivateKey] =
seckey: var PrivateKey): KeyFileStatus =
## Load and decode private key ``seckey`` from file with pathname ## Load and decode private key ``seckey`` from file with pathname
## ``pathname``, using password string ``password``. ## ``pathname``, using password string ``password``.
var data: JsonNode var data: JsonNode
var stream = newFileStream(pathname)
if isNil(stream):
return OsError
try: try:
data = parseFile(pathname) data = json.parseFile(pathname)
result = Success except JsonParsingError:
except CatchableError: return err(JsonError)
result = JsonError except Exception: # json raises Exception
finally: return err(OsError)
stream.close()
if result == Success: decodeKeyFileJson(data, password)
result = decodeKeyFileJson(data, password, seckey)
proc saveKeyFile*(pathname: string, proc saveKeyFile*(pathname: string,
jobject: JsonNode): KeyFileStatus = jobject: JsonNode): KfResult[void] =
## Save JSON object ``jobject`` to file with pathname ``pathname``. ## Save JSON object ``jobject`` to file with pathname ``pathname``.
var var
f: File f: File
if not f.open(pathname, fmWrite): if not f.open(pathname, fmWrite):
return OsError return err(OsError)
try: try:
f.write($jobject) f.write($jobject)
result = Success ok()
except CatchableError: except CatchableError:
result = OsError err(OsError)
finally: finally:
f.close() f.close()

View File

@ -16,40 +16,47 @@
## - ``FreeBSD``, ``OpenBSD``, ``NetBSD``, ## - ``FreeBSD``, ``OpenBSD``, ``NetBSD``,
## ``DragonflyBSD`` - using `uuid_create()`. ## ``DragonflyBSD`` - using `uuid_create()`.
import nimcrypto/utils, stew/endians2 {.push raises: [Defect].}
import stew/[byteutils, endians2, result]
from nimcrypto import stripSpaces
export result
type type
UUIDException = object of CatchableError
UUID* = object UUID* = object
## Represents UUID object ## Represents UUID object
data*: array[16, byte] data*: array[16, byte]
proc raiseInvalidUuid() = UuidResult*[T] = Result[T, cstring]
raise newException(UUIDException, "Invalid UUID!")
proc uuidFromString*(s: string): UUID = proc uuidFromString*(s: string): UuidResult[UUID] =
## Convert string representation of UUID into UUID object. ## Convert string representation of UUID into UUID object.
if len(s) != 36: if len(s) != 36:
raiseInvalidUuid() return err("uuid: length must be 36 bytes")
for i in 0..<len(s): for i in 0..<len(s):
if s[i] notin {'A'..'F', '0'..'9', 'a'..'f', '-'}: if s[i] notin {'A'..'F', '0'..'9', 'a'..'f', '-'}:
raiseInvalidUuid() return err("uuid: invalid characters")
var d = fromHex(stripSpaces(s)) try:
var var d = hexToSeqByte(stripSpaces(s))
a = uint32.fromBytesBE(d.toOpenArray(0, 3)) var
b = uint16.fromBytesBE(d.toOpenArray(4, 5)) a = uint32.fromBytesBE(d.toOpenArray(0, 3))
c = uint16.fromBytesBE(d.toOpenArray(6, 7)) b = uint16.fromBytesBE(d.toOpenArray(4, 5))
c = uint16.fromBytesBE(d.toOpenArray(6, 7))
copyMem(addr result.data[0], addr a, 4) var ret: UUID
copyMem(addr result.data[4], addr b, 2) copyMem(addr ret.data[0], addr a, 4)
copyMem(addr result.data[6], addr c, 2) copyMem(addr ret.data[4], addr b, 2)
copyMem(addr result.data[8], addr d[8], 8) copyMem(addr ret.data[6], addr c, 2)
copyMem(addr ret.data[8], addr d[8], 8)
ok(ret)
except CatchableError:
err("uuid: cannot parse hex string")
proc uuidToString*(u: UUID, lowercase: bool = false): string = proc uuidToString*(u: UUID): string =
## Convert UUID object into string representation. ## Convert UUID object into string representation.
## You can use ``lowercase`` flag to specify letter case ## UUID are lowercase, per RFC4122
## of output string.
result = newStringOfCap(38) result = newStringOfCap(38)
var d: array[8, byte] var d: array[8, byte]
var var
@ -60,22 +67,22 @@ proc uuidToString*(u: UUID, lowercase: bool = false): string =
copyMem(addr d[4], addr b, 2) copyMem(addr d[4], addr b, 2)
copyMem(addr d[6], addr c, 2) copyMem(addr d[6], addr c, 2)
result.add(toHex(toOpenArray(d, 0, 3), lowercase)) result.add(toHex(toOpenArray(d, 0, 3)))
result.add("-") result.add("-")
result.add(toHex(toOpenArray(d, 4, 5), lowercase)) result.add(toHex(toOpenArray(d, 4, 5)))
result.add("-") result.add("-")
result.add(toHex(toOpenArray(d, 6, 7), lowercase)) result.add(toHex(toOpenArray(d, 6, 7)))
result.add("-") result.add("-")
result.add(toHex(toOpenArray(u.data, 8, 9), lowercase)) result.add(toHex(toOpenArray(u.data, 8, 9)))
result.add("-") result.add("-")
result.add(toHex(toOpenArray(u.data, 10, 15), lowercase)) result.add(toHex(toOpenArray(u.data, 10, 15)))
proc `$`*(u: UUID): string {.inline.} = proc `$`*(u: UUID): string =
## Convert UUID object to lowercase string representation. ## Convert UUID object to lowercase string representation.
uuidToString(u, true) uuidToString(u)
when defined(nimdoc): when defined(nimdoc):
proc uuidGenerate*(output: var UUID): int proc uuidGenerate*(): UuidResult[UUID]
## Generates new unique UUID and store it to `output`. ## Generates new unique UUID and store it to `output`.
## ##
## Return 1 on success, and 0 on failure ## Return 1 on success, and 0 on failure
@ -85,9 +92,10 @@ when defined(posix):
proc uuidGenerateRandom(a: pointer) proc uuidGenerateRandom(a: pointer)
{.importc: "uuid_generate_random", header: "uuid/uuid.h".} {.importc: "uuid_generate_random", header: "uuid/uuid.h".}
proc uuidGenerate*(output: var UUID): int = proc uuidGenerate*(): UuidResult[UUID] =
var output: UUID
uuidGenerateRandom(cast[pointer](addr output)) uuidGenerateRandom(cast[pointer](addr output))
result = 1 ok(output)
elif defined(freebsd) or defined(netbsd) or defined(openbsd) or elif defined(freebsd) or defined(netbsd) or defined(openbsd) or
defined(dragonflybsd): defined(dragonflybsd):
@ -95,13 +103,14 @@ when defined(posix):
proc uuidCreate(a: pointer, s: ptr uint32) proc uuidCreate(a: pointer, s: ptr uint32)
{.importc: "uuid_create", header: "uuid.h".} {.importc: "uuid_create", header: "uuid.h".}
proc uuidGenerate*(output: var UUID): int = proc uuidGenerate*(): UuidResult[UUID] =
var status: uint32 = 0 var status: uint32 = 0
var output: UUID
uuidCreate(cast[pointer](addr output), addr status) uuidCreate(cast[pointer](addr output), addr status)
if status == 0: if status == 0:
result = 1 ok(output)
else: else:
result = 0 err("uuid: uuid_create failed")
elif defined(linux) or defined(android): elif defined(linux) or defined(android):
import posix, os, nimcrypto/sysrand import posix, os, nimcrypto/sysrand
@ -124,39 +133,44 @@ when defined(posix):
break break
discard posix.close(fd) discard posix.close(fd)
proc uuidGenerate*(output: var UUID): int = proc uuidGenerate*(): UuidResult[UUID] =
result = 0
var buffer = newString(37) var buffer = newString(37)
if uuidRead(buffer, 36) == 36: if uuidRead(buffer, 36) == 36:
buffer.setLen(36) buffer.setLen(36)
output = uuidFromString(buffer) uuidFromString(buffer)
result = 1
else: else:
var output: UUID
if randomBytes(output.data) == sizeof(output.data): if randomBytes(output.data) == sizeof(output.data):
result = 1 ok(output)
else:
err("uuid: cannot get random bytes")
else: else:
import nimcrypto/sysrand import nimcrypto/sysrand
proc uuidGenerate*(output: var UUID): int = proc uuidGenerate*(): UuidResult[UUID] =
var output: UUID
if randomBytes(output.data) == sizeof(output.data): if randomBytes(output.data) == sizeof(output.data):
result = 1 ok(output)
else: else:
result = 0 err("uuid: cannot get random bytes")
elif defined(windows): elif defined(windows):
proc UuidCreate(p: pointer): int32 proc UuidCreate(p: pointer): int32
{.stdcall, dynlib: "rpcrt4", importc: "UuidCreate".} {.stdcall, dynlib: "rpcrt4", importc: "UuidCreate".}
proc uuidGenerate*(output: var UUID): int = proc uuidGenerate*(): UuidResult[UUID] =
var output: UUID
if UuidCreate(cast[pointer](addr output)) == 0: if UuidCreate(cast[pointer](addr output)) == 0:
return 1 ok(output)
else: else:
return 0 err("uuid: UuidCreate failed")
elif not defined(nimdoc): elif not defined(nimdoc):
import nimcrypto/sysrand import nimcrypto/sysrand
proc uuidGenerate*(output: var UUID): int = proc uuidGenerate*(): UuidResult[UUID] =
var output: UUID
if randomBytes(output.data) == sizeof(output.data): if randomBytes(output.data) == sizeof(output.data):
result = 1 ok(output)
else: else:
result = 0 err("uuid: cannot get random bytes")

View File

@ -74,7 +74,7 @@ proc processIncoming(server: StreamServer,
node.peerPool.addPeer(peer) node.peerPool.addPeer(peer)
proc listeningAddress*(node: EthereumNode): ENode = proc listeningAddress*(node: EthereumNode): ENode =
return initENode(node.keys.pubKey, node.address) node.toENode()
proc startListening*(node: EthereumNode) = proc startListening*(node: EthereumNode) =
# TODO allow binding to specific IP / IPv6 / etc # TODO allow binding to specific IP / IPv6 / etc

View File

@ -10,12 +10,16 @@
## This module implements Ethereum authentication ## This module implements Ethereum authentication
{.push raises: [Defect].}
import eth/[keys, rlp], nimcrypto import eth/[keys, rlp], nimcrypto
import ecies import ecies
import stew/[byteutils, endians2] import stew/[byteutils, endians2, result]
export result
const const
SupportedRlpxVersion* = 4 SupportedRlpxVersion* = 4'u8
PlainAuthMessageV4Length* = 194 PlainAuthMessageV4Length* = 194
AuthMessageV4Length* = 307 AuthMessageV4Length* = 307
PlainAuthMessageEIP8Length = 169 PlainAuthMessageEIP8Length = 169
@ -49,18 +53,17 @@ type
Responder, ## `Handshake` owner is connection responder Responder, ## `Handshake` owner is connection responder
Eip8 ## Flag indicates that EIP-8 handshake is used Eip8 ## Flag indicates that EIP-8 handshake is used
AuthStatus* = enum AuthError* = enum
Success, ## Operation was successful RandomError = "auth: could not obtain random data"
RandomError, ## Could not obtain random data EcdhError = "auth: ECDH shared secret could not be calculated"
EcdhError, ## ECDH shared secret could not be calculated BufferOverrun = "auth: buffer overrun"
BufferOverrun, ## Buffer overrun error SignatureError = "auth: signature could not be obtained"
SignatureError, ## Signature could not be obtained EciesError = "auth: ECIES encryption/decryption error"
EciesError, ## ECIES encryption/decryption error InvalidPubKey = "auth: invalid public key"
InvalidPubKey, ## Invalid public key InvalidAuth = "auth: invalid Authentication message"
InvalidAuth, ## Invalid Authentication message InvalidAck = "auth: invalid Authentication ACK message"
InvalidAck, ## Invalid Authentication ACK message RlpError = "auth: error while decoding RLP stream"
RlpError, ## Error while decoding RLP stream IncompleteError = "auth: data incomplete"
IncompleteError ## Data incomplete error
Handshake* = object Handshake* = object
version*: uint8 ## protocol version version*: uint8 ## protocol version
@ -79,80 +82,94 @@ type
egressMac*: keccak256 egressMac*: keccak256
ingressMac*: keccak256 ingressMac*: keccak256
AuthException* = object of CatchableError AuthResult*[T] = Result[T, AuthError]
template toa(a, b, c: untyped): untyped = template toa(a, b, c: untyped): untyped =
toOpenArray((a), (b), (b) + (c) - 1) toOpenArray((a), (b), (b) + (c) - 1)
proc sxor[T](a: var openarray[T], b: openarray[T]) {.inline.} = proc `xor`[N: static int](a, b: array[N, byte]): array[N, byte] =
doAssert(len(a) == len(b))
for i in 0 ..< len(a): for i in 0 ..< len(a):
a[i] = a[i] xor b[i] result[i] = a[i] xor b[i]
proc newHandshake*(flags: set[HandshakeFlag] = {Initiator}, proc mapErrTo[T, E](r: Result[T, E], v: static AuthError): AuthResult[T] =
version: int = SupportedRlpxVersion): Handshake = r.mapErr(proc (e: E): AuthError = v)
proc tryInit*(
T: type Handshake, host: KeyPair, flags: set[HandshakeFlag] = {Initiator},
version: uint8 = SupportedRlpxVersion): AuthResult[T] =
## Create new `Handshake` object. ## Create new `Handshake` object.
result.version = byte(version and 0xFF)
result.flags = flags var
result.ephemeral = KeyPair.random().tryGet() initiatorNonce: Nonce
responderNonce: Nonce
expectedLength: int
ephemeral = ? KeyPair.random().mapErrTo(RandomError)
if Initiator in flags: if Initiator in flags:
result.expectedLength = AckMessageV4Length expectedLength = AckMessageV4Length
if randomBytes(result.initiatorNonce) != len(result.initiatorNonce): if randomBytes(initiatorNonce) != len(initiatorNonce):
raise newException(AuthException, "Could not obtain random data!") return err(RandomError)
else: else:
result.expectedLength = AuthMessageV4Length expectedLength = AuthMessageV4Length
if randomBytes(result.responderNonce) != len(result.responderNonce): if randomBytes(responderNonce) != len(responderNonce):
raise newException(AuthException, "Could not obtain random data!") return err(RandomError)
return ok(T(
version: version,
flags: flags,
host: host,
ephemeral: ephemeral,
initiatorNonce: initiatorNonce,
responderNonce: responderNonce,
expectedLength: expectedLength
))
proc authMessagePreEIP8(h: var Handshake, proc authMessagePreEIP8(h: var Handshake,
pubkey: PublicKey, pubkey: PublicKey,
output: var openarray[byte], output: var openarray[byte],
outlen: var int, outlen: var int,
flag: int = 0, flag: byte = 0,
encrypt: bool = true): AuthStatus = encrypt: bool = true): AuthResult[void] =
## Create plain pre-EIP8 authentication message. ## Create plain pre-EIP8 authentication message.
var var
buffer: array[PlainAuthMessageV4Length, byte] buffer: array[PlainAuthMessageV4Length, byte]
flagb: byte
header: ptr AuthMessageV4
outlen = 0 outlen = 0
flagb = byte(flag) let header = cast[ptr AuthMessageV4](addr buffer[0])
header = cast[ptr AuthMessageV4](addr buffer[0])
var secret = ecdhRaw(h.host.seckey, pubkey) var secret = ? ecdhRaw(h.host.seckey, pubkey).mapErrTo(EcdhError)
if secret.isErr: let xornonce = secret.data xor h.initiatorNonce
return(EcdhError)
var xornonce = h.initiatorNonce secret.clear()
xornonce.sxor(secret[].data)
secret[].clear() let signature = ? sign(
let sig = sign(h.ephemeral.seckey, SkMessage(data: xornonce)) h.ephemeral.seckey, SkMessage(data: xornonce)).mapErrTo(SignatureError)
if sig.isErr:
return(SignatureError)
h.remoteHPubkey = pubkey h.remoteHPubkey = pubkey
header.signature = sig[].toRaw() header.signature = signature.toRaw()
header.keyhash = keccak256.digest(h.ephemeral.pubkey.toRaw()).data header.keyhash = keccak256.digest(h.ephemeral.pubkey.toRaw()).data
header.pubkey = h.host.pubkey.toRaw() header.pubkey = h.host.pubkey.toRaw()
header.nonce = h.initiatorNonce header.nonce = h.initiatorNonce
header.flag = flagb header.flag = flag
if encrypt: if encrypt:
if len(output) < AuthMessageV4Length: if len(output) < AuthMessageV4Length:
return(BufferOverrun) return err(BufferOverrun)
if eciesEncrypt(buffer, output, h.remoteHPubkey) != EciesStatus.Success: if eciesEncrypt(buffer, output, h.remoteHPubkey).isErr:
return(EciesError) return err(EciesError)
outlen = AuthMessageV4Length outlen = AuthMessageV4Length
result = Success
else: else:
if len(output) < PlainAuthMessageV4Length: if len(output) < PlainAuthMessageV4Length:
return(BufferOverrun) return err(BufferOverrun)
copyMem(addr output[0], addr buffer[0], PlainAuthMessageV4Length) copyMem(addr output[0], addr buffer[0], PlainAuthMessageV4Length)
outlen = PlainAuthMessageV4Length outlen = PlainAuthMessageV4Length
result = Success
ok()
proc authMessageEIP8(h: var Handshake, proc authMessageEIP8(h: var Handshake,
pubkey: PublicKey, pubkey: PublicKey,
output: var openarray[byte], output: var openarray[byte],
outlen: var int, outlen: var int,
flag: int = 0, flag: byte = 0,
encrypt: bool = true): AuthStatus = encrypt: bool = true): AuthResult[void] =
## Create EIP8 authentication message. ## Create EIP8 authentication message.
var var
buffer: array[PlainAuthMessageMaxEIP8, byte] buffer: array[PlainAuthMessageMaxEIP8, byte]
@ -160,17 +177,17 @@ proc authMessageEIP8(h: var Handshake,
doAssert(EIP8 in h.flags) doAssert(EIP8 in h.flags)
outlen = 0 outlen = 0
var secret = ecdhRaw(h.host.seckey, pubkey) var
if secret.isErr: secret = ? ecdhRaw(h.host.seckey, pubkey).mapErrTo(EcdhError)
return(EcdhError) xornonce = secret.data xor h.initiatorNonce
var xornonce = h.initiatorNonce
xornonce.sxor(secret[].data) secret.clear()
secret[].clear()
var sig = sign(h.ephemeral.seckey, SkMessage(data: xornonce)) let signature = ? sign(
if sig.isErr: h.ephemeral.seckey, SkMessage(data: xornonce)).mapErrTo(SignatureError)
return(SignatureError)
h.remoteHPubkey = pubkey h.remoteHPubkey = pubkey
var payload = rlp.encodeList(sig[].toRaw(), var payload = rlp.encodeList(signature.toRaw(),
h.host.pubkey.toRaw(), h.host.pubkey.toRaw(),
h.initiatorNonce, h.initiatorNonce,
[byte(h.version)]) [byte(h.version)])
@ -178,65 +195,67 @@ proc authMessageEIP8(h: var Handshake,
let pencsize = eciesEncryptedLength(len(payload)) let pencsize = eciesEncryptedLength(len(payload))
while true: while true:
if randomBytes(addr padsize, 1) != 1: if randomBytes(addr padsize, 1) != 1:
return(RandomError) return err(RandomError)
if int(padsize) > (AuthMessageV4Length - (pencsize + 2)): if int(padsize) > (AuthMessageV4Length - (pencsize + 2)):
break break
# It is possible to make packet size constant by uncommenting this line # It is possible to make packet size constant by uncommenting this line
# padsize = 24 # padsize = 24
var wosize = pencsize + int(padsize) let wosize = pencsize + int(padsize)
let fullsize = wosize + 2 let fullsize = wosize + 2
if randomBytes(toa(buffer, PlainAuthMessageEIP8Length, if randomBytes(toa(buffer, PlainAuthMessageEIP8Length,
int(padsize))) != int(padsize): int(padsize))) != int(padsize):
return(RandomError) return err(RandomError)
if encrypt: if encrypt:
copyMem(addr buffer[0], addr payload[0], len(payload)) copyMem(addr buffer[0], addr payload[0], len(payload))
if len(output) < fullsize: if len(output) < fullsize:
return(BufferOverrun) return err(BufferOverrun)
let wosizeBE = uint16(wosize).toBytesBE() let wosizeBE = uint16(wosize).toBytesBE()
output[0..<2] = wosizeBE output[0..<2] = wosizeBE
if eciesEncrypt(toa(buffer, 0, len(payload) + int(padsize)), if eciesEncrypt(toa(buffer, 0, len(payload) + int(padsize)),
toa(output, 2, wosize), pubkey, toa(output, 2, wosize), pubkey,
toa(output, 0, 2)) != EciesStatus.Success: toa(output, 0, 2)).isErr:
return(EciesError) return err(EciesError)
outlen = fullsize outlen = fullsize
else: else:
let plainsize = len(payload) + int(padsize) let plainsize = len(payload) + int(padsize)
if len(output) < plainsize: if len(output) < plainsize:
return(BufferOverrun) return err(BufferOverrun)
copyMem(addr output[0], addr buffer[0], plainsize) copyMem(addr output[0], addr buffer[0], plainsize)
outlen = plainsize outlen = plainsize
result = Success
ok()
proc ackMessagePreEIP8(h: var Handshake, proc ackMessagePreEIP8(h: var Handshake,
output: var openarray[byte], output: var openarray[byte],
outlen: var int, outlen: var int,
flag: int = 0, flag: byte = 0,
encrypt: bool = true): AuthStatus = encrypt: bool = true): AuthResult[void] =
## Create plain pre-EIP8 authentication ack message. ## Create plain pre-EIP8 authentication ack message.
var buffer: array[PlainAckMessageV4Length, byte] var buffer: array[PlainAckMessageV4Length, byte]
outlen = 0 outlen = 0
var header = cast[ptr AckMessageV4](addr buffer[0]) let header = cast[ptr AckMessageV4](addr buffer[0])
header.pubkey = h.ephemeral.pubkey.toRaw() header.pubkey = h.ephemeral.pubkey.toRaw()
header.nonce = h.responderNonce header.nonce = h.responderNonce
header.flag = byte(flag) header.flag = flag
if encrypt: if encrypt:
if len(output) < AckMessageV4Length: if len(output) < AckMessageV4Length:
return(BufferOverrun) return err(BufferOverrun)
if eciesEncrypt(buffer, output, h.remoteHPubkey) != EciesStatus.Success: if eciesEncrypt(buffer, output, h.remoteHPubkey).isErr:
return(EciesError) return err(EciesError)
outlen = AckMessageV4Length outlen = AckMessageV4Length
else: else:
if len(output) < PlainAckMessageV4Length: if len(output) < PlainAckMessageV4Length:
return(BufferOverrun) return err(BufferOverrun)
copyMem(addr output[0], addr buffer[0], PlainAckMessageV4Length) copyMem(addr output[0], addr buffer[0], PlainAckMessageV4Length)
outlen = PlainAckMessageV4Length outlen = PlainAckMessageV4Length
result = Success
ok()
proc ackMessageEIP8(h: var Handshake, proc ackMessageEIP8(h: var Handshake,
output: var openarray[byte], output: var openarray[byte],
outlen: var int, outlen: var int,
flag: int = 0, flag: byte = 0,
encrypt: bool = true): AuthStatus = encrypt: bool = true): AuthResult[void] =
## Create EIP8 authentication ack message. ## Create EIP8 authentication ack message.
var var
buffer: array[PlainAckMessageMaxEIP8, byte] buffer: array[PlainAckMessageMaxEIP8, byte]
@ -250,34 +269,35 @@ proc ackMessageEIP8(h: var Handshake,
let pencsize = eciesEncryptedLength(len(payload)) let pencsize = eciesEncryptedLength(len(payload))
while true: while true:
if randomBytes(addr padsize, 1) != 1: if randomBytes(addr padsize, 1) != 1:
return(RandomError) return err(RandomError)
if int(padsize) > (AckMessageV4Length - (pencsize + 2)): if int(padsize) > (AckMessageV4Length - (pencsize + 2)):
break break
# It is possible to make packet size constant by uncommenting this line # It is possible to make packet size constant by uncommenting this line
# padsize = 0 # padsize = 0
var wosize = pencsize + int(padsize) let wosize = pencsize + int(padsize)
let fullsize = wosize + 2 let fullsize = wosize + 2
if int(padsize) > 0: if int(padsize) > 0:
if randomBytes(toa(buffer, PlainAckMessageEIP8Length, if randomBytes(toa(buffer, PlainAckMessageEIP8Length,
int(padsize))) != int(padsize): int(padsize))) != int(padsize):
return(RandomError) return err(RandomError)
copyMem(addr buffer[0], addr payload[0], len(payload)) copyMem(addr buffer[0], addr payload[0], len(payload))
if encrypt: if encrypt:
if len(output) < fullsize: if len(output) < fullsize:
return(BufferOverrun) return err(BufferOverrun)
output[0..<2] = uint16(wosize).toBytesBE() output[0..<2] = uint16(wosize).toBytesBE()
if eciesEncrypt(toa(buffer, 0, len(payload) + int(padsize)), if eciesEncrypt(toa(buffer, 0, len(payload) + int(padsize)),
toa(output, 2, wosize), h.remoteHPubkey, toa(output, 2, wosize), h.remoteHPubkey,
toa(output, 0, 2)) != EciesStatus.Success: toa(output, 0, 2)).isErr:
return(EciesError) return err(EciesError)
outlen = fullsize outlen = fullsize
else: else:
let plainsize = len(payload) + int(padsize) let plainsize = len(payload) + int(padsize)
if len(output) < plainsize: if len(output) < plainsize:
return(BufferOverrun) return err(BufferOverrun)
copyMem(addr output[0], addr buffer[0], plainsize) copyMem(addr output[0], addr buffer[0], plainsize)
outlen = plainsize outlen = plainsize
result = Success
ok()
template authSize*(h: Handshake, encrypt: bool = true): int = template authSize*(h: Handshake, encrypt: bool = true): int =
## Get number of bytes needed to store AuthMessage. ## Get number of bytes needed to store AuthMessage.
@ -295,55 +315,52 @@ template ackSize*(h: Handshake, encrypt: bool = true): int =
proc authMessage*(h: var Handshake, pubkey: PublicKey, proc authMessage*(h: var Handshake, pubkey: PublicKey,
output: var openarray[byte], output: var openarray[byte],
outlen: var int, flag: int = 0, outlen: var int, flag: byte = 0,
encrypt: bool = true): AuthStatus {.inline.} = encrypt: bool = true): AuthResult[void] =
## Create new AuthMessage for specified `pubkey` and store it inside ## Create new AuthMessage for specified `pubkey` and store it inside
## of `output`, size of generated AuthMessage will stored in `outlen`. ## of `output`, size of generated AuthMessage will stored in `outlen`.
if EIP8 in h.flags: if EIP8 in h.flags:
result = authMessageEIP8(h, pubkey, output, outlen, flag, encrypt) authMessageEIP8(h, pubkey, output, outlen, flag, encrypt)
else: else:
result = authMessagePreEIP8(h, pubkey, output, outlen, flag, encrypt) authMessagePreEIP8(h, pubkey, output, outlen, flag, encrypt)
proc ackMessage*(h: var Handshake, output: var openarray[byte], proc ackMessage*(h: var Handshake, output: var openarray[byte],
outlen: var int, flag: int = 0, outlen: var int, flag: byte = 0,
encrypt: bool = true): AuthStatus = encrypt: bool = true): AuthResult[void] =
## Create new AckMessage and store it inside of `output`, size of generated ## Create new AckMessage and store it inside of `output`, size of generated
## AckMessage will stored in `outlen`. ## AckMessage will stored in `outlen`.
if EIP8 in h.flags: if EIP8 in h.flags:
result = ackMessageEIP8(h, output, outlen, flag, encrypt) ackMessageEIP8(h, output, outlen, flag, encrypt)
else: else:
result = ackMessagePreEIP8(h, output, outlen, flag, encrypt) ackMessagePreEIP8(h, output, outlen, flag, encrypt)
proc decodeAuthMessageV4(h: var Handshake, m: openarray[byte]): AuthStatus = proc decodeAuthMessageV4(h: var Handshake, m: openarray[byte]): AuthResult[void] =
## Decodes V4 AuthMessage. ## Decodes V4 AuthMessage.
var var
buffer: array[PlainAuthMessageV4Length, byte] buffer: array[PlainAuthMessageV4Length, byte]
doAssert(Responder in h.flags) doAssert(Responder in h.flags)
if eciesDecrypt(m, buffer, h.host.seckey) != EciesStatus.Success: if eciesDecrypt(m, buffer, h.host.seckey).isErr:
return(EciesError) return err(EciesError)
var header = cast[ptr AuthMessageV4](addr buffer[0])
let pubkey = PublicKey.fromRaw(header.pubkey)
if pubkey.isErr:
return(InvalidPubKey)
var secret = ecdhRaw(h.host.seckey, pubkey[])
if secret.isErr:
return(EcdhError)
var xornonce = header.nonce
xornonce.sxor(secret[].data)
secret[].clear()
let sig = Signature.fromRaw(header.signature)
if sig.isErr:
return(SignatureError)
let remoteEPubkey = recover(sig[], SkMessage(data: xornonce))
if remoteEPubkey.isErr:
return(SignatureError)
h.remoteEPubkey = remoteEPubkey[]
h.initiatorNonce = header.nonce
h.remoteHPubkey = pubkey[]
result = Success
proc decodeAuthMessageEip8(h: var Handshake, m: openarray[byte]): AuthStatus = let
header = cast[ptr AuthMessageV4](addr buffer[0])
pubkey = ? PublicKey.fromRaw(header.pubkey).mapErrTo(InvalidPubKey)
signature = ? Signature.fromRaw(header.signature).mapErrTo(SignatureError)
var secret = ? ecdhRaw(h.host.seckey, pubkey).mapErrTo(EcdhError)
let xornonce = secret.data xor header.nonce
secret.clear()
h.remoteEPubkey =
? recover(signature, SkMessage(data: xornonce)).mapErrTo(SignatureError)
h.initiatorNonce = header.nonce
h.remoteHPubkey = pubkey
ok()
proc decodeAuthMessageEip8(h: var Handshake, m: openarray[byte]): AuthResult[void] =
## Decodes EIP-8 AuthMessage. ## Decodes EIP-8 AuthMessage.
var var
nonce: Nonce nonce: Nonce
@ -351,155 +368,138 @@ proc decodeAuthMessageEip8(h: var Handshake, m: openarray[byte]): AuthStatus =
let size = uint16.fromBytesBE(m) let size = uint16.fromBytesBE(m)
h.expectedLength = int(size) + 2 h.expectedLength = int(size) + 2
if h.expectedLength > len(m): if h.expectedLength > len(m):
return(IncompleteError) return err(IncompleteError)
var buffer = newSeq[byte](eciesDecryptedLength(int(size))) var buffer = newSeq[byte](eciesDecryptedLength(int(size)))
if eciesDecrypt(toa(m, 2, int(size)), buffer, h.host.seckey, if eciesDecrypt(toa(m, 2, int(size)), buffer, h.host.seckey,
toa(m, 0, 2)) != EciesStatus.Success: toa(m, 0, 2)).isErr:
return(EciesError) return err(EciesError)
try: try:
var reader = rlpFromBytes(buffer.toRange()) var reader = rlpFromBytes(buffer.toRange())
if not reader.isList() or reader.listLen() < 4: if not reader.isList() or reader.listLen() < 4:
return(InvalidAuth) return err(InvalidAuth)
if reader.listElem(0).blobLen != RawSignatureSize: if reader.listElem(0).blobLen != RawSignatureSize:
return(InvalidAuth) return err(InvalidAuth)
if reader.listElem(1).blobLen != RawPublicKeySize: if reader.listElem(1).blobLen != RawPublicKeySize:
return(InvalidAuth) return err(InvalidAuth)
if reader.listElem(2).blobLen != KeyLength: if reader.listElem(2).blobLen != KeyLength:
return(InvalidAuth) return err(InvalidAuth)
if reader.listElem(3).blobLen != 1: if reader.listElem(3).blobLen != 1:
return(InvalidAuth) return err(InvalidAuth)
var signatureBr = reader.listElem(0).toBytes() var signatureBr = reader.listElem(0).toBytes()
var pubkeyBr = reader.listElem(1).toBytes() var pubkeyBr = reader.listElem(1).toBytes()
var nonceBr = reader.listElem(2).toBytes() var nonceBr = reader.listElem(2).toBytes()
var versionBr = reader.listElem(3).toBytes() var versionBr = reader.listElem(3).toBytes()
let pubkey = PublicKey.fromRaw(pubkeyBr.toOpenArray())
if pubkey.isErr:
return(InvalidPubKey)
copyMem(addr nonce[0], nonceBr.baseAddr, KeyLength)
var secret = ecdhRaw(h.host.seckey, pubkey[])
if secret.isErr:
return(EcdhError)
var xornonce = nonce
xornonce.sxor(secret[].data)
secret[].clear()
let sig = Signature.fromRaw(signatureBr.toOpenArray())
if sig.isErr:
return(SignatureError)
let remoteEPubkey = recover(sig[], SkMessage(data: xornonce))
if remoteEPubkey.isErr:
return(SignatureError)
h.remoteEPubkey = remoteEPubkey[]
h.initiatorNonce = nonce
h.remoteHPubkey = pubkey[]
h.version = cast[ptr byte](versionBr.baseAddr)[]
result = Success
except CatchableError:
result = RlpError
proc decodeAckMessageEip8*(h: var Handshake, m: openarray[byte]): AuthStatus = let pubkey =
? PublicKey.fromRaw(pubkeyBr.toOpenArray()).mapErrTo(InvalidPubKey)
copyMem(addr nonce[0], nonceBr.baseAddr, KeyLength)
var secret = ? ecdhRaw(h.host.seckey, pubkey).mapErrTo(EcdhError)
let xornonce = nonce xor secret.data
secret.clear()
let signature =
? Signature.fromRaw(signatureBr.toOpenArray()).mapErrTo(SignatureError)
h.remoteEPubkey =
? recover(signature, SkMessage(data: xornonce)).mapErrTo(SignatureError)
h.initiatorNonce = nonce
h.remoteHPubkey = pubkey
h.version = cast[ptr byte](versionBr.baseAddr)[]
ok()
except CatchableError:
err(RlpError)
proc decodeAckMessageEip8*(h: var Handshake, m: openarray[byte]): AuthResult[void] =
## Decodes EIP-8 AckMessage. ## Decodes EIP-8 AckMessage.
let size = uint16.fromBytesBE(m) let size = uint16.fromBytesBE(m)
h.expectedLength = 2 + int(size) h.expectedLength = 2 + int(size)
if h.expectedLength > len(m): if h.expectedLength > len(m):
return(IncompleteError) return err(IncompleteError)
var buffer = newSeq[byte](eciesDecryptedLength(int(size))) var buffer = newSeq[byte](eciesDecryptedLength(int(size)))
if eciesDecrypt(toa(m, 2, int(size)), buffer, h.host.seckey, if eciesDecrypt(toa(m, 2, int(size)), buffer, h.host.seckey,
toa(m, 0, 2)) != EciesStatus.Success: toa(m, 0, 2)).isErr:
return(EciesError) return err(EciesError)
try: try:
var reader = rlpFromBytes(buffer.toRange()) var reader = rlpFromBytes(buffer.toRange())
if not reader.isList() or reader.listLen() < 3: if not reader.isList() or reader.listLen() < 3:
return(InvalidAck) return err(InvalidAck)
if reader.listElem(0).blobLen != RawPublicKeySize: if reader.listElem(0).blobLen != RawPublicKeySize:
return(InvalidAck) return err(InvalidAck)
if reader.listElem(1).blobLen != KeyLength: if reader.listElem(1).blobLen != KeyLength:
return(InvalidAck) return err(InvalidAck)
if reader.listElem(2).blobLen != 1: if reader.listElem(2).blobLen != 1:
return(InvalidAck) return err(InvalidAck)
let pubkeyBr = reader.listElem(0).toBytes() let pubkeyBr = reader.listElem(0).toBytes()
let nonceBr = reader.listElem(1).toBytes() let nonceBr = reader.listElem(1).toBytes()
let versionBr = reader.listElem(2).toBytes() let versionBr = reader.listElem(2).toBytes()
let remoteEPubkey = PublicKey.fromRaw(pubkeyBr.toOpenArray()) h.remoteEPubkey =
if remoteEPubkey.isErr: ? PublicKey.fromRaw(pubkeyBr.toOpenArray()).mapErrTo(InvalidPubKey)
return(InvalidPubKey)
h.remoteEPubkey = remoteEPubkey[]
copyMem(addr h.responderNonce[0], nonceBr.baseAddr, KeyLength) copyMem(addr h.responderNonce[0], nonceBr.baseAddr, KeyLength)
h.version = cast[ptr byte](versionBr.baseAddr)[] h.version = cast[ptr byte](versionBr.baseAddr)[]
result = Success
except CatchableError:
result = RlpError
proc decodeAckMessageV4(h: var Handshake, m: openarray[byte]): AuthStatus = ok()
except CatchableError:
err(RlpError)
proc decodeAckMessageV4(h: var Handshake, m: openarray[byte]): AuthResult[void] =
## Decodes V4 AckMessage. ## Decodes V4 AckMessage.
var var
buffer: array[PlainAckMessageV4Length, byte] buffer: array[PlainAckMessageV4Length, byte]
doAssert(Initiator in h.flags) doAssert(Initiator in h.flags)
if eciesDecrypt(m, buffer, h.host.seckey) != EciesStatus.Success:
return(EciesError)
var header = cast[ptr AckMessageV4](addr buffer[0])
let remoteEPubkey = PublicKey.fromRaw(header.pubkey)
if remoteEPubkey.isErr:
return(InvalidPubKey)
h.remoteEPubkey = remoteEPubkey[]
h.responderNonce = header.nonce
result = Success
proc decodeAuthMessage*(h: var Handshake, input: openarray[byte]): AuthStatus = if eciesDecrypt(m, buffer, h.host.seckey).isErr:
return err(EciesError)
var header = cast[ptr AckMessageV4](addr buffer[0])
h.remoteEPubkey = ? PublicKey.fromRaw(header.pubkey).mapErrTo(InvalidPubKey)
h.responderNonce = header.nonce
ok()
proc decodeAuthMessage*(h: var Handshake, input: openarray[byte]): AuthResult[void] =
## Decodes AuthMessage from `input`. ## Decodes AuthMessage from `input`.
if len(input) < AuthMessageV4Length: if len(input) < AuthMessageV4Length:
result = IncompleteError return err(IncompleteError)
elif len(input) == AuthMessageV4Length:
var res = h.decodeAuthMessageV4(input)
if res != Success:
res = h.decodeAuthMessageEip8(input)
if res != Success:
result = res
else:
h.flags.incl(EIP8)
result = Success
else:
result = Success
else:
result = h.decodeAuthMessageEip8(input)
if result == Success:
h.flags.incl(EIP8)
proc decodeAckMessage*(h: var Handshake, input: openarray[byte]): AuthStatus = if len(input) == AuthMessageV4Length:
let res = h.decodeAuthMessageV4(input)
if res.isOk(): return res
let res = h.decodeAuthMessageEip8(input)
if res.isOk():
h.flags.incl(EIP8)
res
proc decodeAckMessage*(h: var Handshake, input: openarray[byte]): AuthResult[void] =
## Decodes AckMessage from `input`. ## Decodes AckMessage from `input`.
if len(input) < AckMessageV4Length: if len(input) < AckMessageV4Length:
return(IncompleteError) return err(IncompleteError)
elif len(input) == AckMessageV4Length: if len(input) == AckMessageV4Length:
var res = h.decodeAckMessageV4(input) let res = h.decodeAckMessageV4(input)
if res != Success: if res.isOk(): return res
res = h.decodeAckMessageEip8(input)
if res != Success:
result = res
else:
h.flags.incl(EIP8)
result = Success
else:
result = Success
else:
result = h.decodeAckMessageEip8(input)
if result == Success:
h.flags.incl(EIP8)
proc getSecrets*(h: Handshake, authmsg: openarray[byte], let res = h.decodeAckMessageEip8(input)
ackmsg: openarray[byte], if res.isOk(): h.flags.incl(EIP8)
secret: var ConnectionSecret): AuthStatus = res
proc getSecrets*(
h: Handshake, authmsg: openarray[byte],
ackmsg: openarray[byte]): AuthResult[ConnectionSecret] =
## Derive secrets from handshake `h` using encrypted AuthMessage `authmsg` and ## Derive secrets from handshake `h` using encrypted AuthMessage `authmsg` and
## encrypted AckMessage `ackmsg`. ## encrypted AckMessage `ackmsg`.
var var
ctx0: keccak256 ctx0: keccak256
ctx1: keccak256 ctx1: keccak256
mac1: MDigest[256] mac1: MDigest[256]
xornonce: Nonce secret: ConnectionSecret
# ecdhe-secret = ecdh.agree(ephemeral-privkey, remote-ephemeral-pubk) # ecdhe-secret = ecdh.agree(ephemeral-privkey, remote-ephemeral-pubk)
var shsec = ecdhRaw(h.ephemeral.seckey, h.remoteEPubkey) var shsec = ? ecdhRaw(h.ephemeral.seckey, h.remoteEPubkey).mapErrTo(EcdhError)
if shsec.isErr:
return(EcdhError)
# shared-secret = keccak(ecdhe-secret || keccak(nonce || initiator-nonce)) # shared-secret = keccak(ecdhe-secret || keccak(nonce || initiator-nonce))
ctx0.init() ctx0.init()
@ -508,36 +508,36 @@ proc getSecrets*(h: Handshake, authmsg: openarray[byte],
ctx1.update(h.initiatorNonce) ctx1.update(h.initiatorNonce)
mac1 = ctx1.finish() mac1 = ctx1.finish()
ctx1.clear() ctx1.clear()
ctx0.update(shsec[].data) ctx0.update(shsec.data)
ctx0.update(mac1.data) ctx0.update(mac1.data)
mac1 = ctx0.finish() mac1 = ctx0.finish()
# aes-secret = keccak(ecdhe-secret || shared-secret) # aes-secret = keccak(ecdhe-secret || shared-secret)
ctx0.init() ctx0.init()
ctx0.update(shsec[].data) ctx0.update(shsec.data)
ctx0.update(mac1.data) ctx0.update(mac1.data)
mac1 = ctx0.finish() mac1 = ctx0.finish()
# mac-secret = keccak(ecdhe-secret || aes-secret) # mac-secret = keccak(ecdhe-secret || aes-secret)
ctx0.init() ctx0.init()
ctx0.update(shsec[].data) ctx0.update(shsec.data)
ctx0.update(mac1.data) ctx0.update(mac1.data)
secret.aesKey = mac1.data secret.aesKey = mac1.data
mac1 = ctx0.finish() mac1 = ctx0.finish()
secret.macKey = mac1.data secret.macKey = mac1.data
shsec[].clear() burnMem(shsec)
# egress-mac = keccak256(mac-secret ^ recipient-nonce || auth-sent-init) # egress-mac = keccak256(mac-secret ^ recipient-nonce || auth-sent-init)
xornonce = mac1.data
xornonce.sxor(h.responderNonce) var xornonce = mac1.data xor h.responderNonce
ctx0.init() ctx0.init()
ctx0.update(xornonce) ctx0.update(xornonce)
ctx0.update(authmsg) ctx0.update(authmsg)
# ingress-mac = keccak256(mac-secret ^ initiator-nonce || auth-recvd-ack) # ingress-mac = keccak256(mac-secret ^ initiator-nonce || auth-recvd-ack)
xornonce = secret.macKey xornonce = secret.macKey xor h.initiatorNonce
xornonce.sxor(h.initiatorNonce)
ctx1.init() ctx1.init()
ctx1.update(xornonce) ctx1.update(xornonce)
ctx1.update(ackmsg) ctx1.update(ackmsg)
@ -552,4 +552,5 @@ proc getSecrets*(h: Handshake, authmsg: openarray[byte],
ctx0.clear() ctx0.clear()
ctx1.clear() ctx1.clear()
result = Success
ok(secret)

View File

@ -11,11 +11,12 @@
import import
times, times,
chronos, stint, nimcrypto, chronicles, chronos, stint, nimcrypto, chronicles,
eth/common/eth_types_json_serialization, eth/[keys, rlp], eth/[keys, rlp],
kademlia, enode kademlia, enode,
stew/result
export export
Node Node, result
logScope: logScope:
topics = "discovery" topics = "discovery"
@ -45,7 +46,8 @@ type
DiscProtocolError* = object of CatchableError DiscProtocolError* = object of CatchableError
const MaxDgramSize = 1280 DiscResult*[T] = Result[T, cstring]
const MinListLen: array[CommandId, int] = [4, 3, 2, 2] const MinListLen: array[CommandId, int] = [4, 3, 2, 2]
proc append*(w: var RlpWriter, a: IpAddress) = proc append*(w: var RlpWriter, a: IpAddress) =
@ -71,19 +73,22 @@ proc pack(cmdId: CommandId, payload: BytesRange, pk: PrivateKey): Bytes =
let msgHash = keccak256.digest(signature & encodedData) let msgHash = keccak256.digest(signature & encodedData)
result = @(msgHash.data) & signature & encodedData result = @(msgHash.data) & signature & encodedData
proc validateMsgHash(msg: Bytes, msgHash: var MDigest[256]): bool = proc validateMsgHash(msg: Bytes): DiscResult[MDigest[256]] =
if msg.len > HEAD_SIZE: if msg.len > HEAD_SIZE:
msgHash.data[0 .. ^1] = msg.toOpenArray(0, msgHash.data.high) var ret: MDigest[256]
result = msgHash == keccak256.digest(msg.toOpenArray(MAC_SIZE, msg.high)) ret.data[0 .. ^1] = msg.toOpenArray(0, ret.data.high)
if ret == keccak256.digest(msg.toOpenArray(MAC_SIZE, msg.high)):
ok(ret)
else:
err("disc: invalid message hash")
else:
err("disc: msg missing hash")
proc recoverMsgPublicKey(msg: Bytes, pk: var PublicKey): bool = proc recoverMsgPublicKey(msg: openArray[byte]): DiscResult[PublicKey] =
if msg.len > HEAD_SIZE: if msg.len <= HEAD_SIZE:
let sig = Signature.fromRaw(msg.toOpenArray(MAC_SIZE, HEAD_SIZE)) return err("disc: can't get public key")
if sig.isOk(): let sig = ? Signature.fromRaw(msg.toOpenArray(MAC_SIZE, HEAD_SIZE))
let pubkey = recover(sig[], msg.toOpenArray(HEAD_SIZE, msg.high)) recover(sig, msg.toOpenArray(HEAD_SIZE, msg.high))
if pubkey.isOk():
pk = pubkey[]
return true
proc unpack(msg: Bytes): tuple[cmdId: CommandId, payload: Bytes] = proc unpack(msg: Bytes): tuple[cmdId: CommandId, payload: Bytes] =
# Check against possible RangeError # Check against possible RangeError
@ -231,17 +236,17 @@ proc expirationValid(cmdId: CommandId, rlpEncodedPayload: seq[byte]):
proc receive*(d: DiscoveryProtocol, a: Address, msg: Bytes) {.gcsafe.} = proc receive*(d: DiscoveryProtocol, a: Address, msg: Bytes) {.gcsafe.} =
## Can raise `DiscProtocolError` and all of `RlpError` ## Can raise `DiscProtocolError` and all of `RlpError`
# Note: export only needed for testing # Note: export only needed for testing
var msgHash: MDigest[256] let msgHash = validateMsgHash(msg)
if validateMsgHash(msg, msgHash): if msgHash.isOk():
var remotePubkey: PublicKey let remotePubkey = recoverMsgPublicKey(msg)
if recoverMsgPublicKey(msg, remotePubkey): if remotePubkey.isOk:
let (cmdId, payload) = unpack(msg) let (cmdId, payload) = unpack(msg)
if expirationValid(cmdId, payload): if expirationValid(cmdId, payload):
let node = newNode(remotePubkey, a) let node = newNode(remotePubkey[], a)
case cmdId case cmdId
of cmdPing: of cmdPing:
d.recvPing(node, msgHash) d.recvPing(node, msgHash[])
of cmdPong: of cmdPong:
d.recvPong(node, payload) d.recvPong(node, payload)
of cmdNeighbours: of cmdNeighbours:
@ -251,14 +256,13 @@ proc receive*(d: DiscoveryProtocol, a: Address, msg: Bytes) {.gcsafe.} =
else: else:
trace "Received msg already expired", cmdId, a trace "Received msg already expired", cmdId, a
else: else:
error "Wrong public key from ", a notice "Wrong public key from ", a, err = remotePubkey.error
else: else:
error "Wrong msg mac from ", a notice "Wrong msg mac from ", a
proc processClient(transp: DatagramTransport, proc processClient(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.async, gcsafe.} = raddr: TransportAddress): Future[void] {.async, gcsafe.} =
var proto = getUserData[DiscoveryProtocol](transp) var proto = getUserData[DiscoveryProtocol](transp)
var buf: seq[byte]
try: try:
# TODO: Maybe here better to use `peekMessage()` to avoid allocation, # TODO: Maybe here better to use `peekMessage()` to avoid allocation,
# but `Bytes` object is just a simple seq[byte], and `ByteRange` object # but `Bytes` object is just a simple seq[byte], and `ByteRange` object
@ -309,16 +313,14 @@ when isMainModule:
block: block:
let m = hexToSeqByte"79664bff52ee17327b4a2d8f97d8fb32c9244d719e5038eb4f6b64da19ca6d271d659c3ad9ad7861a928ca85f8d8debfbe6b7ade26ad778f2ae2ba712567fcbd55bc09eb3e74a893d6b180370b266f6aaf3fe58a0ad95f7435bf3ddf1db940d20102f2cb842edbd4d182944382765da0ab56fb9e64a85a597e6bb27c656b4f1afb7e06b0fd4e41ccde6dba69a3c4a150845aaa4de2" let m = hexToSeqByte"79664bff52ee17327b4a2d8f97d8fb32c9244d719e5038eb4f6b64da19ca6d271d659c3ad9ad7861a928ca85f8d8debfbe6b7ade26ad778f2ae2ba712567fcbd55bc09eb3e74a893d6b180370b266f6aaf3fe58a0ad95f7435bf3ddf1db940d20102f2cb842edbd4d182944382765da0ab56fb9e64a85a597e6bb27c656b4f1afb7e06b0fd4e41ccde6dba69a3c4a150845aaa4de2"
var msgHash: MDigest[256] discard validateMsgHash(m).expect("valid hash")
doAssert(validateMsgHash(m, msgHash)) var remotePubkey = recoverMsgPublicKey(m).expect("valid key")
var remotePubkey: PublicKey
doAssert(recoverMsgPublicKey(m, remotePubkey))
let (cmdId, payload) = unpack(m) let (cmdId, payload) = unpack(m)
doAssert(payload == hexToSeqByte"f2cb842edbd4d182944382765da0ab56fb9e64a85a597e6bb27c656b4f1afb7e06b0fd4e41ccde6dba69a3c4a150845aaa4de2") doAssert(payload == hexToSeqByte"f2cb842edbd4d182944382765da0ab56fb9e64a85a597e6bb27c656b4f1afb7e06b0fd4e41ccde6dba69a3c4a150845aaa4de2")
doAssert(cmdId == cmdPong) doAssert(cmdId == cmdPong)
doAssert(remotePubkey == PublicKey.fromHex( doAssert(remotePubkey == PublicKey.fromHex(
"78de8a0916848093c73790ead81d1928bec737d565119932b98c6b100d944b7a95e94f847f689fc723399d2e31129d182f7ef3863f2b4c820abbf3ab2722344d"))[] "78de8a0916848093c73790ead81d1928bec737d565119932b98c6b100d944b7a95e94f847f689fc723399d2e31129d182f7ef3863f2b4c820abbf3ab2722344d")[])
let privKey = PrivateKey.fromHex("a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a617")[] let privKey = PrivateKey.fromHex("a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a617")[]
@ -332,7 +334,7 @@ when isMainModule:
var bootnodes = newSeq[ENode]() var bootnodes = newSeq[ENode]()
for item in LOCAL_BOOTNODES: for item in LOCAL_BOOTNODES:
bootnodes.add(initENode(item)) bootnodes.add(ENode.fromString(item)[])
let listenPort = Port(30310) let listenPort = Port(30310)
var address = Address(udpPort: listenPort, tcpPort: listenPort) var address = Address(udpPort: listenPort, tcpPort: listenPort)

View File

@ -2,6 +2,8 @@ import
std/[tables, options], nimcrypto, stint, chronicles, std/[tables, options], nimcrypto, stint, chronicles,
types, node, enr, hkdf, ../enode, eth/[rlp, keys] types, node, enr, hkdf, ../enode, eth/[rlp, keys]
export keys
const const
idNoncePrefix = "discovery-id-nonce" idNoncePrefix = "discovery-id-nonce"
keyAgreementPrefix = "discovery v5 key agreement" keyAgreementPrefix = "discovery v5 key agreement"
@ -103,12 +105,11 @@ proc makeAuthHeader(c: Codec, toId: NodeID, nonce: array[gcmNonceSize, byte],
if challenge.recordSeq < ln.record.seqNum: if challenge.recordSeq < ln.record.seqNum:
resp.record = ln.record resp.record = ln.record
let ephKey = PrivateKey.random().tryGet() let ephKeys = KeyPair.random().tryGet()
let ephPubkey = ephKey.toPublicKey().tryGet().toRaw
resp.signature = c.signIDNonce(challenge.idNonce, ephPubkey).toRaw resp.signature = c.signIDNonce(challenge.idNonce, ephKeys.pubkey.toRaw).toRaw
deriveKeys(ln.id, toId, ephKey, challenge.pubKey, challenge.idNonce, deriveKeys(ln.id, toId, ephKeys.seckey, challenge.pubKey, challenge.idNonce,
handshakeSecrets) handshakeSecrets)
let respRlp = rlp.encode(resp) let respRlp = rlp.encode(resp)
@ -117,7 +118,7 @@ proc makeAuthHeader(c: Codec, toId: NodeID, nonce: array[gcmNonceSize, byte],
let respEnc = encryptGCM(handshakeSecrets.authRespKey, zeroNonce, respRLP, []) let respEnc = encryptGCM(handshakeSecrets.authRespKey, zeroNonce, respRLP, [])
let header = AuthHeader(auth: nonce, idNonce: challenge.idNonce, let header = AuthHeader(auth: nonce, idNonce: challenge.idNonce,
scheme: authSchemeName, ephemeralKey: ephPubkey, response: respEnc) scheme: authSchemeName, ephemeralKey: ephKeys.pubkey.toRaw, response: respEnc)
rlp.encode(header) rlp.encode(header)
proc `xor`[N: static[int], T](a, b: array[N, T]): array[N, T] = proc `xor`[N: static[int], T](a, b: array[N, T]): array[N, T] =

View File

@ -6,6 +6,8 @@ import
nimcrypto, stew/base64, nimcrypto, stew/base64,
eth/[rlp, keys], ../enode eth/[rlp, keys], ../enode
export options
const const
maxEnrSize = 300 maxEnrSize = 300
minRlpListLen = 4 # for signature, seqId, "id" key, id minRlpListLen = 4 # for signature, seqId, "id" key, id
@ -163,13 +165,12 @@ proc get*(r: Record, key: string, T: type): T =
else: else:
raise newException(KeyError, "Key not found in ENR: " & key) raise newException(KeyError, "Key not found in ENR: " & key)
proc get*(r: Record, pubKey: var PublicKey): bool = proc get*(r: Record, T: type PublicKey): Option[T] {.raises: [Defect].} =
var pubkeyField: Field var pubkeyField: Field
if r.getField("secp256k1", pubkeyField) and pubkeyField.kind == kBytes: if r.getField("secp256k1", pubkeyField) and pubkeyField.kind == kBytes:
let pk = PublicKey.fromRaw(pubkeyField.bytes) let pk = PublicKey.fromRaw(pubkeyField.bytes)
if pk.isOk: if pk.isOk:
pubKey = pk[] return some pk[]
return true
proc tryGet*(r: Record, key: string, T: type): Option[T] = proc tryGet*(r: Record, key: string, T: type): Option[T] =
try: try:
@ -197,12 +198,12 @@ proc toTypedRecord*(r: Record): Option[TypedRecord] =
return some(tr) return some(tr)
proc verifySignatureV4(r: Record, sigData: openarray[byte], content: seq[byte]): bool = proc verifySignatureV4(r: Record, sigData: openarray[byte], content: seq[byte]): bool =
var publicKey: PublicKey let publicKey = r.get(PublicKey)
if r.get(publicKey): if publicKey.isSome:
let sig = SignatureNR.fromRaw(sigData) let sig = SignatureNR.fromRaw(sigData)
if sig.isOk: if sig.isOk:
var h = keccak256.digest(content) var h = keccak256.digest(content)
return verify(sig[], h, publicKey) return verify(sig[], h, publicKey.get)
proc verifySignature(r: Record): bool = proc verifySignature(r: Record): bool =
var rlp = rlpFromBytes(r.raw.toRange) var rlp = rlpFromBytes(r.raw.toRange)

View File

@ -21,10 +21,10 @@ proc newNode*(enode: ENode, r: Record): Node =
record: r) record: r)
proc newNode*(uriString: string): Node = proc newNode*(uriString: string): Node =
newNode initENode(uriString) newNode ENode.fromString(uriString).tryGet()
proc newNode*(pk: PublicKey, address: Address): Node = proc newNode*(pk: PublicKey, address: Address): Node =
newNode initENode(pk, address) newNode ENode(pubkey: pk, address: address)
proc newNode*(r: Record): Node = proc newNode*(r: Record): Node =
# TODO: Handle IPv6 # TODO: Handle IPv6
@ -48,7 +48,7 @@ proc newNode*(r: Record): Node =
warn "Could not recover public key", err = pk.error warn "Could not recover public key", err = pk.error
return return
result = newNode(initENode(pk[], a)) result = newNode(ENode(pubkey: pk[], address: a))
result.record = r result.record = r
proc hash*(n: Node): hashes.Hash = hash(n.node.pubkey.toRaw) proc hash*(n: Node): hashes.Hash = hash(n.node.pubkey.toRaw)

View File

@ -480,7 +480,7 @@ proc newProtocol*(privKey: PrivateKey, db: Database,
let let
a = Address(ip: externalIp.get(IPv4_any()), a = Address(ip: externalIp.get(IPv4_any()),
tcpPort: tcpPort, udpPort: udpPort) tcpPort: tcpPort, udpPort: udpPort)
enode = initENode(privKey.toPublicKey().tryGet(), a) enode = ENode(pubkey: privKey.toPublicKey().tryGet(), address: a)
enrRec = enr.Record.init(1, privKey, externalIp, tcpPort, udpPort) enrRec = enr.Record.init(1, privKey, externalIp, tcpPort, udpPort)
node = newNode(enode, enrRec) node = newNode(enode, enrRec)

View File

@ -10,29 +10,37 @@
## This module implements ECIES method encryption/decryption. ## This module implements ECIES method encryption/decryption.
{.push raises: [Defect].}
import eth/keys, nimcrypto/[rijndael, bcmode, hash, hmac, sysrand, sha2, utils] import eth/keys, nimcrypto/[rijndael, bcmode, hash, hmac, sysrand, sha2, utils]
import stew/result
export result
const const
emptyMac* = array[0, byte]([]) emptyMac* = array[0, byte]([])
type type
EciesException* = object of CatchableError EciesError* = enum
EciesStatus* = enum BufferOverrun = "ecies: output buffer size is too small"
Success, ## Operation was successful RandomError = "ecies: could not obtain random data"
BufferOverrun, ## Output buffer size is too small EcdhError = "ecies: ECDH shared secret could not be calculated"
RandomError, ## Could not obtain random data WrongHeader = "ecies: header is incorrect"
EcdhError, ## ECDH shared secret could not be calculated IncorrectKey = "ecies: recovered public key is invalid"
WrongHeader, ## ECIES header is incorrect IncorrectTag = "ecies: tag verification failed"
IncorrectKey, ## Recovered public key is invalid IncompleteError = "ecies: decryption needs more data"
IncorrectTag, ## ECIES tag verification failed
IncompleteError ## Decryption needs more data
EciesHeader* = object {.packed.} EciesHeader* {.packed.} = object
version*: byte version*: byte
pubkey*: array[RawPublicKeySize, byte] pubkey*: array[RawPublicKeySize, byte]
iv*: array[aes128.sizeBlock, byte] iv*: array[aes128.sizeBlock, byte]
data*: byte data*: byte
EciesResult*[T] = Result[T, EciesError]
proc mapErrTo[T](r: SkResult[T], v: static EciesError): EciesResult[T] =
r.mapErr(proc (e: cstring): EciesError = v)
template eciesOverheadLength*(): int = template eciesOverheadLength*(): int =
## Return data overhead size for ECIES encrypted message ## Return data overhead size for ECIES encrypted message
1 + sizeof(PublicKey) + aes128.sizeBlock + sha256.sizeDigest 1 + sizeof(PublicKey) + aes128.sizeBlock + sha256.sizeDigest
@ -86,7 +94,7 @@ proc kdf*(data: openarray[byte]): array[KeyLength, byte] {.noInit.} =
proc eciesEncrypt*(input: openarray[byte], output: var openarray[byte], proc eciesEncrypt*(input: openarray[byte], output: var openarray[byte],
pubkey: PublicKey, pubkey: PublicKey,
sharedmac: openarray[byte] = emptyMac): EciesStatus = sharedmac: openarray[byte] = emptyMac): EciesResult[void] =
## Encrypt data with ECIES method using given public key `pubkey`. ## Encrypt data with ECIES method using given public key `pubkey`.
## ``input`` - input data ## ``input`` - input data
## ``output`` - output data ## ``output`` - output data
@ -99,33 +107,31 @@ proc eciesEncrypt*(input: openarray[byte], output: var openarray[byte],
cipher: CTR[aes128] cipher: CTR[aes128]
ctx: HMAC[sha256] ctx: HMAC[sha256]
iv: array[aes128.sizeBlock, byte] iv: array[aes128.sizeBlock, byte]
material: array[KeyLength, byte]
if len(output) < eciesEncryptedLength(len(input)): if len(output) < eciesEncryptedLength(len(input)):
return(BufferOverrun) return err(BufferOverrun)
if randomBytes(iv) != aes128.sizeBlock: if randomBytes(iv) != aes128.sizeBlock:
return(RandomError) return err(RandomError)
var ephemeral = KeyPair.random() var
if ephemeral.isErr: ephemeral = ? KeyPair.random().mapErrTo(RandomError)
return(RandomError) secret = ? ecdhRaw(ephemeral.seckey, pubkey).mapErrTo(EcdhError)
material = kdf(secret.data)
var secret = ecdhRaw(ephemeral[].seckey, pubkey) clear(secret)
if secret.isErr:
return(EcdhError)
material = kdf(secret[].data)
burnMem(secret)
copyMem(addr encKey[0], addr material[0], aes128.sizeKey) copyMem(addr encKey[0], addr material[0], aes128.sizeKey)
var macKey = sha256.digest(material, ostart = KeyLength div 2) var macKey = sha256.digest(material, ostart = KeyLength div 2)
burnMem(material) burnMem(material)
var header = cast[ptr EciesHeader](addr output[0]) var header = cast[ptr EciesHeader](addr output[0])
header.version = 0x04 header.version = 0x04
header.pubkey = ephemeral[].pubkey.toRaw() header.pubkey = ephemeral.pubkey.toRaw()
header.iv = iv header.iv = iv
clear(ephemeral)
var so = eciesDataPos() var so = eciesDataPos()
var eo = so + len(input) var eo = so + len(input)
cipher.init(encKey, iv) cipher.init(encKey, iv)
@ -146,12 +152,12 @@ proc eciesEncrypt*(input: openarray[byte], output: var openarray[byte],
copyMem(addr output[so], addr tag.data[0], sha256.sizeDigest) copyMem(addr output[so], addr tag.data[0], sha256.sizeDigest)
ctx.clear() ctx.clear()
result = Success ok()
proc eciesDecrypt*(input: openarray[byte], proc eciesDecrypt*(input: openarray[byte],
output: var openarray[byte], output: var openarray[byte],
seckey: PrivateKey, seckey: PrivateKey,
sharedmac: openarray[byte] = emptyMac): EciesStatus = sharedmac: openarray[byte] = emptyMac): EciesResult[void] =
## Decrypt data with ECIES method using given private key `seckey`. ## Decrypt data with ECIES method using given private key `seckey`.
## ``input`` - input data ## ``input`` - input data
## ``output`` - output data ## ``output`` - output data
@ -165,24 +171,23 @@ proc eciesDecrypt*(input: openarray[byte],
ctx: HMAC[sha256] ctx: HMAC[sha256]
if len(input) <= 0: if len(input) <= 0:
return(IncompleteError) return err(IncompleteError)
var header = cast[ptr EciesHeader](unsafeAddr input[0]) var header = cast[ptr EciesHeader](unsafeAddr input[0])
if header.version != 0x04: if header.version != 0x04:
return(WrongHeader) return err(WrongHeader)
if len(input) <= eciesOverheadLength(): if len(input) <= eciesOverheadLength():
return(IncompleteError) return err(IncompleteError)
if len(input) - eciesOverheadLength() > len(output): if len(input) - eciesOverheadLength() > len(output):
return(BufferOverrun) return err(BufferOverrun)
let pubkey = PublicKey.fromRaw(header.pubkey)
if pubkey.isErr:
return(IncorrectKey)
var secret = ecdhRaw(seckey, pubkey[])
if secret.isErr:
return(EcdhError)
var material = kdf(secret[].data) var
pubkey = ? PublicKey.fromRaw(header.pubkey).mapErrTo(IncorrectKey)
secret = ? ecdhRaw(seckey, pubkey).mapErrTo(EcdhError)
var material = kdf(secret.data)
burnMem(secret) burnMem(secret)
copyMem(addr encKey[0], addr material[0], aes128.sizeKey) copyMem(addr encKey[0], addr material[0], aes128.sizeKey)
var macKey = sha256.digest(material, ostart = KeyLength div 2) var macKey = sha256.digest(material, ostart = KeyLength div 2)
burnMem(material) burnMem(material)
@ -198,7 +203,7 @@ proc eciesDecrypt*(input: openarray[byte],
if not equalMem(addr tag.data[0], unsafeAddr input[eciesMacPos(len(input))], if not equalMem(addr tag.data[0], unsafeAddr input[eciesMacPos(len(input))],
sha256.sizeDigest): sha256.sizeDigest):
return(IncorrectTag) return err(IncorrectTag)
let datsize = eciesDecryptedLength(len(input)) let datsize = eciesDecryptedLength(len(input))
cipher.init(encKey, header.iv) cipher.init(encKey, header.iv)
@ -206,4 +211,5 @@ proc eciesDecrypt*(input: openarray[byte],
cipher.decrypt(toOpenArray(input, eciesDataPos(), cipher.decrypt(toOpenArray(input, eciesDataPos(),
eciesDataPos() + datsize - 1), output) eciesDataPos() + datsize - 1), output)
cipher.clear() cipher.clear()
result = Success
ok()

View File

@ -1,6 +1,6 @@
# #
# Ethereum P2P # Ethereum P2P
# (c) Copyright 2018 # (c) Copyright 2018-2020
# Status Research & Development GmbH # Status Research & Development GmbH
# #
# Licensed under either of # Licensed under either of
@ -8,22 +8,23 @@
# MIT license (LICENSE-MIT) # MIT license (LICENSE-MIT)
# #
{.push raises: [Defect].}
import uri, strutils, net import uri, strutils, net
import eth/keys import eth/keys
export keys export keys
type type
ENodeStatus* = enum ENodeError* = enum
## ENode status codes ## ENode status codes
Success, ## Conversion operation succeed IncorrectNodeId = "enode: incorrect public key"
IncorrectNodeId, ## Incorrect public key supplied IncorrectScheme = "enode: incorrect URI scheme"
IncorrectScheme, ## Incorrect URI scheme supplied IncorrectIP = "enode: incorrect IP address"
IncorrectIP, ## Incorrect IP address supplied IncorrectPort = "enode: incorrect TCP port"
IncorrectPort, ## Incorrect TCP port supplied IncorrectDiscPort = "enode: incorrect UDP discovery port"
IncorrectDiscPort, ## Incorrect UDP discovery port supplied IncorrectUri = "enode: incorrect URI"
IncorrectUri, ## Incorrect URI supplied IncompleteENode = "enode: incomplete ENODE object"
IncompleteENode ## Incomplete ENODE object
Address* = object Address* = object
## Network address object ## Network address object
@ -36,25 +37,12 @@ type
pubkey*: PublicKey ## Node public key pubkey*: PublicKey ## Node public key
address*: Address ## Node address address*: Address ## Node address
ENodeException* = object of CatchableError ENodeResult*[T] = Result[T, ENodeError]
proc raiseENodeError(status: ENodeStatus) = proc mapErrTo[T, E](r: Result[T, E], v: static ENodeError): ENodeResult[T] =
if status == IncorrectIP: r.mapErr(proc (e: E): ENodeError = v)
raise newException(ENodeException, "Incorrect IP address")
elif status == IncorrectPort:
raise newException(ENodeException, "Incorrect port number")
elif status == IncorrectDiscPort:
raise newException(ENodeException, "Incorrect discovery port number")
elif status == IncorrectUri:
raise newException(ENodeException, "Incorrect URI")
elif status == IncorrectScheme:
raise newException(ENodeException, "Incorrect scheme")
elif status == IncorrectNodeId:
raise newException(ENodeException, "Incorrect node id")
elif status == IncompleteENode:
raise newException(ENodeException, "Incomplete enode")
proc initENode*(e: string, node: var ENode): ENodeStatus = proc fromString*(T: type ENode, e: string): ENodeResult[ENode] =
## Initialize ENode ``node`` from URI string ``uri``. ## Initialize ENode ``node`` from URI string ``uri``.
var var
uport: int = 0 uport: int = 0
@ -62,83 +50,67 @@ proc initENode*(e: string, node: var ENode): ENodeStatus =
uri: Uri = initUri() uri: Uri = initUri()
if len(e) == 0: if len(e) == 0:
return IncorrectUri return err(IncorrectUri)
parseUri(e, uri) parseUri(e, uri)
if len(uri.scheme) == 0 or uri.scheme.toLowerAscii() != "enode": if len(uri.scheme) == 0 or uri.scheme.toLowerAscii() != "enode":
return IncorrectScheme return err(IncorrectScheme)
if len(uri.username) != 128: if len(uri.username) != 128:
return IncorrectNodeId return err(IncorrectNodeId)
for i in uri.username: for i in uri.username:
if i notin {'A'..'F', 'a'..'f', '0'..'9'}: if i notin {'A'..'F', 'a'..'f', '0'..'9'}:
return IncorrectNodeId return err(IncorrectNodeId)
if len(uri.password) != 0 or len(uri.path) != 0 or len(uri.anchor) != 0: if len(uri.password) != 0 or len(uri.path) != 0 or len(uri.anchor) != 0:
return IncorrectUri return err(IncorrectUri)
if len(uri.hostname) == 0: if len(uri.hostname) == 0:
return IncorrectIP return err(IncorrectIP)
try: try:
if len(uri.port) == 0: if len(uri.port) == 0:
return IncorrectPort return err(IncorrectPort)
tport = parseInt(uri.port) tport = parseInt(uri.port)
if tport <= 0 or tport > 65535: if tport <= 0 or tport > 65535:
return IncorrectPort return err(IncorrectPort)
except ValueError: except ValueError:
return IncorrectPort return err(IncorrectPort)
if len(uri.query) > 0: if len(uri.query) > 0:
if not uri.query.toLowerAscii().startsWith("discport="): if not uri.query.toLowerAscii().startsWith("discport="):
return IncorrectDiscPort return err(IncorrectDiscPort)
try: try:
uport = parseInt(uri.query[9..^1]) uport = parseInt(uri.query[9..^1])
if uport <= 0 or uport > 65535: if uport <= 0 or uport > 65535:
return IncorrectDiscPort return err(IncorrectDiscPort)
except ValueError: except ValueError:
return IncorrectDiscPort return err(IncorrectDiscPort)
else: else:
uport = tport uport = tport
let pk = PublicKey.fromHex(uri.username) var ip: IpAddress
if pk.isErr:
return IncorrectNodeId
node.pubkey = pk[]
try: try:
node.address.ip = parseIpAddress(uri.hostname) ip = parseIpAddress(uri.hostname)
except ValueError: except ValueError:
zeroMem(addr node.pubkey, KeyLength * 2) return err(IncorrectIP)
return IncorrectIP
node.address.tcpPort = Port(tport) let pubkey = ? PublicKey.fromHex(uri.username).mapErrTo(IncorrectNodeId)
node.address.udpPort = Port(uport)
result = Success
proc initENode*(uri: string): ENode {.inline.} = ok(ENode(
## Returns ENode object from URI string ``uri``. pubkey: pubkey,
let res = initENode(uri, result) address: Address(
if res != Success: ip: ip,
raiseENodeError(res) tcpPort: Port(tport),
udpPort: Port(uport)
proc initENode*(pubkey: PublicKey, address: Address): ENode {.inline.} = )
## Create ENode object from public key ``pubkey`` and ``address``. ))
result.pubkey = pubkey
result.address = address
proc isCorrect*(n: ENode): bool =
## Returns ``true`` if ENode ``n`` is properly filled.
var pk: PublicKey
n.pubkey != pk
proc `$`*(n: ENode): string = proc `$`*(n: ENode): string =
## Returns string representation of ENode. ## Returns string representation of ENode.
var ipaddr: string var ipaddr: string
if not isCorrect(n):
raiseENodeError(IncompleteENode)
if n.address.ip.family == IpAddressFamily.IPv4: if n.address.ip.family == IpAddressFamily.IPv4:
ipaddr = $(n.address.ip) ipaddr = $(n.address.ip)
else: else:

View File

@ -55,12 +55,12 @@ proc toNodeId*(pk: PublicKey): NodeId =
proc newNode*(pk: PublicKey, address: Address): Node = proc newNode*(pk: PublicKey, address: Address): Node =
result.new() result.new()
result.node = initENode(pk, address) result.node = ENode(pubkey: pk, address: address)
result.id = pk.toNodeId() result.id = pk.toNodeId()
proc newNode*(uriString: string): Node = proc newNode*(uriString: string): Node =
result.new() result.new()
result.node = initENode(uriString) result.node = ENode.fromString(uriString)[]
result.id = result.node.pubkey.toNodeId() result.id = result.node.pubkey.toNodeId()
proc newNode*(enode: ENode): Node = proc newNode*(enode: ENode): Node =

View File

@ -212,7 +212,5 @@ proc newMockPeer*(userConfigurator: proc (m: MockConf)): EthereumNode =
return node return node
proc rlpxConnect*(node, otherNode: EthereumNode): Future[Peer] = proc rlpxConnect*(node, otherNode: EthereumNode): Future[Peer] =
let otherAsRemote = newNode(initENode(otherNode.keys.pubKey, let otherAsRemote = newNode(otherNode.toENode())
otherNode.address))
return rlpx.rlpxConnect(node, otherAsRemote) return rlpx.rlpxConnect(node, otherAsRemote)

View File

@ -168,3 +168,5 @@ type
proc `$`*(peer: Peer): string = $peer.remote proc `$`*(peer: Peer): string = $peer.remote
proc toENode*(v: EthereumNode): ENode =
ENode(pubkey: v.keys.pubkey, address: v.address)

View File

@ -390,7 +390,7 @@ proc recvMsg*(peer: Peer): Future[tuple[msgId: int, msgData: Rlp]] {.async.} =
var msgSize: int var msgSize: int
if decryptHeaderAndGetMsgSize(peer.secretsState, if decryptHeaderAndGetMsgSize(peer.secretsState,
headerBytes, msgSize) != RlpxStatus.Success: headerBytes, msgSize).isErr():
await peer.disconnectAndRaise(BreachOfProtocol, await peer.disconnectAndRaise(BreachOfProtocol,
"Cannot decrypt RLPx frame header") "Cannot decrypt RLPx frame header")
@ -414,7 +414,7 @@ proc recvMsg*(peer: Peer): Future[tuple[msgId: int, msgData: Rlp]] {.async.} =
decryptedBytesCount = 0 decryptedBytesCount = 0
if decryptBody(peer.secretsState, encryptedBytes, msgSize, if decryptBody(peer.secretsState, encryptedBytes, msgSize,
decryptedBytes, decryptedBytesCount) != RlpxStatus.Success: decryptedBytes, decryptedBytesCount).isErr():
await peer.disconnectAndRaise(BreachOfProtocol, await peer.disconnectAndRaise(BreachOfProtocol,
"Cannot decrypt RLPx frame body") "Cannot decrypt RLPx frame body")
@ -929,14 +929,9 @@ template `^`(arr): auto =
# variable as an open array # variable as an open array
arr.toOpenArray(0, `arr Len` - 1) arr.toOpenArray(0, `arr Len` - 1)
proc check(status: AuthStatus) =
if status != AuthStatus.Success:
raise newException(CatchableError, "Error: " & $status)
proc initSecretState(hs: var Handshake, authMsg, ackMsg: openarray[byte], proc initSecretState(hs: var Handshake, authMsg, ackMsg: openarray[byte],
p: Peer) = p: Peer) =
var secrets: ConnectionSecret var secrets = hs.getSecrets(authMsg, ackMsg).tryGet()
check hs.getSecrets(authMsg, ackMsg, secrets)
initSecretState(secrets, p.secretsState) initSecretState(secrets, p.secretsState)
burnMem(secrets) burnMem(secrets)
@ -975,12 +970,12 @@ proc rlpxConnect*(node: EthereumNode, remote: Node): Future[Peer] {.async.} =
var ok = false var ok = false
try: try:
result.transport = await connect(ta) result.transport = await connect(ta)
var handshake = newHandshake({Initiator, EIP8}, int(node.baseProtocolVersion)) var handshake = Handshake.tryInit(
handshake.host = node.keys node.keys, {Initiator, EIP8}, node.baseProtocolVersion).tryGet()
var authMsg: array[AuthMessageMaxEIP8, byte] var authMsg: array[AuthMessageMaxEIP8, byte]
var authMsgLen = 0 var authMsgLen = 0
check authMessage(handshake, remote.node.pubkey, authMsg, authMsgLen) authMessage(handshake, remote.node.pubkey, authMsg, authMsgLen).tryGet()
var res = await result.transport.write(addr authMsg[0], authMsgLen) var res = await result.transport.write(addr authMsg[0], authMsgLen)
if res != authMsgLen: if res != authMsgLen:
raisePeerDisconnected("Unexpected disconnect while authenticating", raisePeerDisconnected("Unexpected disconnect while authenticating",
@ -993,12 +988,12 @@ proc rlpxConnect*(node: EthereumNode, remote: Node): Future[Peer] {.async.} =
await result.transport.readExactly(addr ackMsg[0], len(ackMsg)) await result.transport.readExactly(addr ackMsg[0], len(ackMsg))
var ret = handshake.decodeAckMessage(ackMsg) var ret = handshake.decodeAckMessage(ackMsg)
if ret == AuthStatus.IncompleteError: if ret.isErr and ret.error == AuthError.IncompleteError:
ackMsg.setLen(handshake.expectedLength) ackMsg.setLen(handshake.expectedLength)
await result.transport.readExactly(addr ackMsg[initialSize], await result.transport.readExactly(addr ackMsg[initialSize],
len(ackMsg) - initialSize) len(ackMsg) - initialSize)
ret = handshake.decodeAckMessage(ackMsg) ret = handshake.decodeAckMessage(ackMsg)
check ret ret.tryGet() # for the raise!
node.checkSnappySupport(handshake, result) node.checkSnappySupport(handshake, result)
initSecretState(handshake, ^authMsg, ackMsg, result) initSecretState(handshake, ^authMsg, ackMsg, result)
@ -1062,8 +1057,7 @@ proc rlpxAccept*(node: EthereumNode,
result.transport = transport result.transport = transport
result.network = node result.network = node
var handshake = newHandshake({auth.Responder}) var handshake = HandShake.tryInit(node.keys, {auth.Responder}).tryGet
handshake.host = node.keys
var ok = false var ok = false
try: try:
@ -1073,19 +1067,20 @@ proc rlpxAccept*(node: EthereumNode,
authMsg.setLen(initialSize) authMsg.setLen(initialSize)
await transport.readExactly(addr authMsg[0], len(authMsg)) await transport.readExactly(addr authMsg[0], len(authMsg))
var ret = handshake.decodeAuthMessage(authMsg) var ret = handshake.decodeAuthMessage(authMsg)
if ret == AuthStatus.IncompleteError: # Eip8 auth message is likely if ret.isErr and ret.error == AuthError.IncompleteError:
# Eip8 auth message is likely
authMsg.setLen(handshake.expectedLength) authMsg.setLen(handshake.expectedLength)
await transport.readExactly(addr authMsg[initialSize], await transport.readExactly(addr authMsg[initialSize],
len(authMsg) - initialSize) len(authMsg) - initialSize)
ret = handshake.decodeAuthMessage(authMsg) ret = handshake.decodeAuthMessage(authMsg)
check ret ret.tryGet() # for the raise!
node.checkSnappySupport(handshake, result) node.checkSnappySupport(handshake, result)
handshake.version = uint8(result.baseProtocolVersion) handshake.version = uint8(result.baseProtocolVersion)
var ackMsg: array[AckMessageMaxEIP8, byte] var ackMsg: array[AckMessageMaxEIP8, byte]
var ackMsgLen: int var ackMsgLen: int
check handshake.ackMessage(ackMsg, ackMsgLen) handshake.ackMessage(ackMsg, ackMsgLen).tryGet()
var res = await transport.write(addr ackMsg[0], ackMsgLen) var res = await transport.write(addr ackMsg[0], ackMsgLen)
if res != ackMsgLen: if res != ackMsgLen:
raisePeerDisconnected("Unexpected disconnect while authenticating", raisePeerDisconnected("Unexpected disconnect while authenticating",
@ -1117,7 +1112,8 @@ proc rlpxAccept*(node: EthereumNode,
let remote = transport.remoteAddress() let remote = transport.remoteAddress()
let address = Address(ip: remote.address, tcpPort: remote.port, let address = Address(ip: remote.address, tcpPort: remote.port,
udpPort: remote.port) udpPort: remote.port)
result.remote = newNode(initEnode(handshake.remoteHPubkey, address)) result.remote = newNode(
ENode(pubkey: handshake.remoteHPubkey, address: address))
trace "devp2p handshake completed", peer = result.remote, trace "devp2p handshake completed", peer = result.remote,
clientId = response.clientId clientId = response.clientId

View File

@ -49,7 +49,7 @@ p2pProtocol Hive(version = hiveVersion,
debug "Hive peer connected" debug "Hive peer connected"
proc initProtocolState*(network: BzzNetwork, node: EthereumNode) {.gcsafe.} = proc initProtocolState*(network: BzzNetwork, node: EthereumNode) {.gcsafe.} =
network.thisENode = initENode(node.keys.pubkey, node.address) network.thisENode = node.toENode()
p2pProtocol Bzz(version = bzzVersion, p2pProtocol Bzz(version = bzzVersion,
rlpxName = "bzz", rlpxName = "bzz",

View File

@ -266,8 +266,8 @@ p2pProtocol les(version = lesVersion,
if signature.isNone: if signature.isNone:
error "missing announce signature" error "missing announce signature"
return return
let sigMsg = rlp.encodeList(headHash, headNumber, headTotalDifficulty)
let sig = Signature.fromRaw(signature.get).tryGet() let sig = Signature.fromRaw(signature.get).tryGet()
let sigMsg = rlp.encodeList(headHash, headNumber, headTotalDifficulty)
let signerKey = recover(sig, sigMsg).tryGet() let signerKey = recover(sig, sigMsg).tryGet()
if signerKey.toNodeId != peer.remote.id: if signerKey.toNodeId != peer.remote.id:
error "invalid announce signature" error "invalid announce signature"

View File

@ -307,8 +307,8 @@ proc encode*(self: Payload): Option[Bytes] =
if self.dst.isSome(): # Asymmetric key present - encryption requested if self.dst.isSome(): # Asymmetric key present - encryption requested
var res = newSeq[byte](eciesEncryptedLength(plain.len)) var res = newSeq[byte](eciesEncryptedLength(plain.len))
let err = eciesEncrypt(plain, res, self.dst.get()) let err = eciesEncrypt(plain, res, self.dst.get())
if err != EciesStatus.Success: if err.isErr:
notice "Encryption failed", err notice "Encryption failed", err = err.error
return return
return some(res) return some(res)
@ -343,7 +343,7 @@ proc decode*(data: openarray[byte], dst = none[PrivateKey](),
return return
plain.setLen(eciesDecryptedLength(data.len)) plain.setLen(eciesDecryptedLength(data.len))
if eciesDecrypt(data, plain, dst.get()) != EciesStatus.Success: if eciesDecrypt(data, plain, dst.get()).isErr:
debug "Couldn't decrypt using asymmetric key", len = data.len debug "Couldn't decrypt using asymmetric key", len = data.len
return return
elif symKey.isSome(): elif symKey.isSome():

View File

@ -10,9 +10,13 @@
## This module implements RLPx cryptography ## This module implements RLPx cryptography
import stew/ranges/stackarrays, eth/rlp/types, nimcrypto {.push raises: [Defect].}
import stew/ranges/stackarrays, eth/rlp/types, nimcrypto, stew/result
from auth import ConnectionSecret from auth import ConnectionSecret
export result
const const
RlpHeaderLength* = 16 RlpHeaderLength* = 16
RlpMacLength* = 16 RlpMacLength* = 16
@ -27,15 +31,16 @@ type
emac*: keccak256 emac*: keccak256
imac*: keccak256 imac*: keccak256
RlpxStatus* = enum RlpxError* = enum
Success, ## Operation was successful IncorrectMac = "rlpx: MAC verification failed"
IncorrectMac, ## MAC verification failed BufferOverrun = "rlpx: buffer overrun"
BufferOverrun, ## Buffer overrun error IncompleteError = "rlpx: data incomplete"
IncompleteError, ## Data incomplete error IncorrectArgs = "rlpx: incorrect arguments"
IncorrectArgs ## Incorrect arguments
RlpxHeader* = array[16, byte] RlpxHeader* = array[16, byte]
RlpxResult*[T] = Result[T, RlpxError]
proc roundup16*(x: int): int {.inline.} = proc roundup16*(x: int): int {.inline.} =
## Procedure aligns `x` to ## Procedure aligns `x` to
let rem = x and 15 let rem = x and 15
@ -76,7 +81,7 @@ template decryptedLength*(size: int): int =
proc encrypt*(c: var SecretState, header: openarray[byte], proc encrypt*(c: var SecretState, header: openarray[byte],
frame: openarray[byte], frame: openarray[byte],
output: var openarray[byte]): RlpxStatus = output: var openarray[byte]): RlpxResult[void] =
## Encrypts `header` and `frame` using SecretState `c` context and store ## Encrypts `header` and `frame` using SecretState `c` context and store
## result into `output`. ## result into `output`.
## ##
@ -92,7 +97,7 @@ proc encrypt*(c: var SecretState, header: openarray[byte],
let framePos = RlpHeaderLength + RlpMacLength let framePos = RlpHeaderLength + RlpMacLength
let frameMacPos = RlpHeaderLength * 2 + frameLength let frameMacPos = RlpHeaderLength * 2 + frameLength
if len(header) != RlpHeaderLength or len(frame) == 0 or length != len(output): if len(header) != RlpHeaderLength or len(frame) == 0 or length != len(output):
return IncorrectArgs return err(IncorrectArgs)
# header_ciphertext = self.aes_enc.update(header) # header_ciphertext = self.aes_enc.update(header)
c.aesenc.encrypt(header, toa(output, 0, RlpHeaderLength)) c.aesenc.encrypt(header, toa(output, 0, RlpHeaderLength))
# mac_secret = self.egress_mac.digest()[:HEADER_LEN] # mac_secret = self.egress_mac.digest()[:HEADER_LEN]
@ -128,7 +133,7 @@ proc encrypt*(c: var SecretState, header: openarray[byte],
# return header_ciphertext + header_mac + frame_ciphertext + frame_mac # return header_ciphertext + header_mac + frame_ciphertext + frame_mac
copyMem(addr output[headerMacPos], addr headerMac.data[0], RlpHeaderLength) copyMem(addr output[headerMacPos], addr headerMac.data[0], RlpHeaderLength)
copyMem(addr output[frameMacPos], addr frameMac.data[0], RlpHeaderLength) copyMem(addr output[frameMacPos], addr frameMac.data[0], RlpHeaderLength)
result = Success ok()
proc encryptMsg*(msg: openarray[byte], secrets: var SecretState): seq[byte] = proc encryptMsg*(msg: openarray[byte], secrets: var SecretState): seq[byte] =
var header: RlpxHeader var header: RlpxHeader
@ -152,13 +157,13 @@ proc encryptMsg*(msg: openarray[byte], secrets: var SecretState): seq[byte] =
# This would be safer if we use a thread-local sequ for the temporary buffer # This would be safer if we use a thread-local sequ for the temporary buffer
result = newSeq[byte](encryptedLength(msg.len)) result = newSeq[byte](encryptedLength(msg.len))
let s = encrypt(secrets, header, msg, result) let s = encrypt(secrets, header, msg, result)
doAssert s == Success s.expect("always succeeds because we call with correct buffer")
proc getBodySize*(a: RlpxHeader): int = proc getBodySize*(a: RlpxHeader): int =
(int(a[0]) shl 16) or (int(a[1]) shl 8) or int(a[2]) (int(a[0]) shl 16) or (int(a[1]) shl 8) or int(a[2])
proc decryptHeader*(c: var SecretState, data: openarray[byte], proc decryptHeader*(c: var SecretState, data: openarray[byte],
output: var openarray[byte]): RlpxStatus = output: var openarray[byte]): RlpxResult[void] =
## Decrypts header `data` using SecretState `c` context and store ## Decrypts header `data` using SecretState `c` context and store
## result into `output`. ## result into `output`.
## ##
@ -169,9 +174,9 @@ proc decryptHeader*(c: var SecretState, data: openarray[byte],
aes: array[RlpHeaderLength, byte] aes: array[RlpHeaderLength, byte]
if len(data) != RlpHeaderLength + RlpMacLength: if len(data) != RlpHeaderLength + RlpMacLength:
return IncompleteError return err(IncompleteError)
if len(output) < RlpHeaderLength: if len(output) < RlpHeaderLength:
return IncorrectArgs return err(IncorrectArgs)
# mac_secret = self.ingress_mac.digest()[:HEADER_LEN] # mac_secret = self.ingress_mac.digest()[:HEADER_LEN]
tmpmac = c.imac tmpmac = c.imac
var macsec = tmpmac.finish() var macsec = tmpmac.finish()
@ -188,22 +193,22 @@ proc decryptHeader*(c: var SecretState, data: openarray[byte],
let headerMacPos = RlpHeaderLength let headerMacPos = RlpHeaderLength
if not equalMem(cast[pointer](unsafeAddr data[headerMacPos]), if not equalMem(cast[pointer](unsafeAddr data[headerMacPos]),
cast[pointer](addr expectMac.data[0]), RlpMacLength): cast[pointer](addr expectMac.data[0]), RlpMacLength):
result = IncorrectMac result = err(IncorrectMac)
else: else:
# return self.aes_dec.update(header_ciphertext) # return self.aes_dec.update(header_ciphertext)
c.aesdec.decrypt(toa(data, 0, RlpHeaderLength), output) c.aesdec.decrypt(toa(data, 0, RlpHeaderLength), output)
result = Success result = ok()
proc decryptHeaderAndGetMsgSize*(c: var SecretState, proc decryptHeaderAndGetMsgSize*(c: var SecretState,
encryptedHeader: openarray[byte], encryptedHeader: openarray[byte],
outSize: var int): RlpxStatus = outSize: var int): RlpxResult[void] =
var decryptedHeader: RlpxHeader var decryptedHeader: RlpxHeader
result = decryptHeader(c, encryptedHeader, decryptedHeader) result = decryptHeader(c, encryptedHeader, decryptedHeader)
if result == Success: if result.isOk():
outSize = decryptedHeader.getBodySize outSize = decryptedHeader.getBodySize
proc decryptBody*(c: var SecretState, data: openarray[byte], bodysize: int, proc decryptBody*(c: var SecretState, data: openarray[byte], bodysize: int,
output: var openarray[byte], outlen: var int): RlpxStatus = output: var openarray[byte], outlen: var int): RlpxResult[void] =
## Decrypts body `data` using SecretState `c` context and store ## Decrypts body `data` using SecretState `c` context and store
## result into `output`. ## result into `output`.
## ##
@ -217,9 +222,9 @@ proc decryptBody*(c: var SecretState, data: openarray[byte], bodysize: int,
outlen = 0 outlen = 0
let rsize = roundup16(bodysize) let rsize = roundup16(bodysize)
if len(data) < rsize + RlpMacLength: if len(data) < rsize + RlpMacLength:
return IncompleteError return err(IncompleteError)
if len(output) < rsize: if len(output) < rsize:
return IncorrectArgs return err(IncorrectArgs)
# self.ingress_mac.update(frame_ciphertext) # self.ingress_mac.update(frame_ciphertext)
c.imac.update(toa(data, 0, rsize)) c.imac.update(toa(data, 0, rsize))
tmpmac = c.imac tmpmac = c.imac
@ -235,8 +240,8 @@ proc decryptBody*(c: var SecretState, data: openarray[byte], bodysize: int,
let bodyMacPos = rsize let bodyMacPos = rsize
if not equalMem(cast[pointer](unsafeAddr data[bodyMacPos]), if not equalMem(cast[pointer](unsafeAddr data[bodyMacPos]),
cast[pointer](addr expectMac.data[0]), RlpMacLength): cast[pointer](addr expectMac.data[0]), RlpMacLength):
result = IncorrectMac result = err(IncorrectMac)
else: else:
c.aesdec.decrypt(toa(data, 0, rsize), output) c.aesdec.decrypt(toa(data, 0, rsize), output)
outlen = bodysize outlen = bodysize
result = Success result = ok()

View File

@ -31,4 +31,4 @@ test:
# These errors are also catched in `processClient` in discovery.nim # These errors are also catched in `processClient` in discovery.nim
# TODO: move them a layer down in discovery so we can do a cleaner test there? # TODO: move them a layer down in discovery so we can do a cleaner test there?
except RlpError, DiscProtocolError as e: except RlpError, DiscProtocolError as e:
debug "Receive failed", err = e.msg debug "Receive failed", err = e.msg

View File

@ -16,8 +16,7 @@ init:
node2 = setupTestNode(eth, Whisper) node2 = setupTestNode(eth, Whisper)
node2.startListening() node2.startListening()
peer = waitFor node1.rlpxConnect(newNode(initENode(node2.keys.pubKey, peer = waitFor node1.rlpxConnect(newNode(node2.toENode()))
node2.address)))
test: test:
aflLoop: # This appears to have unstable results with afl-clang-fast, probably aflLoop: # This appears to have unstable results with afl-clang-fast, probably

View File

@ -7,7 +7,7 @@
# Apache License, version 2.0, (LICENSE-APACHEv2) # Apache License, version 2.0, (LICENSE-APACHEv2)
# MIT license (LICENSE-MIT) # MIT license (LICENSE-MIT)
import eth/keys, eth/keyfile/[uuid, keyfile], json, strutils, os, unittest import eth/keys, eth/keyfile/[keyfile], json, os, unittest
# Test vectors copied from # Test vectors copied from
# https://github.com/ethereum/tests/blob/develop/KeyStoreTests/basic_tests.json # https://github.com/ethereum/tests/blob/develop/KeyStoreTests/basic_tests.json
@ -83,52 +83,45 @@ var TestVectors = [
} }
] ]
var jobject: JsonNode
suite "KeyFile test suite": suite "KeyFile test suite":
test "KeyStoreTests/basic_tests.json test1": test "KeyStoreTests/basic_tests.json test1":
var seckey: PrivateKey
var expectkey = PrivateKey.fromHex(TestVectors[0].getOrDefault("priv").getStr())[] var expectkey = PrivateKey.fromHex(TestVectors[0].getOrDefault("priv").getStr())[]
check: let seckey =
decodeKeyFileJson(TestVectors[0].getOrDefault("keyfile"), decodeKeyFileJson(TestVectors[0].getOrDefault("keyfile"),
TestVectors[0].getOrDefault("password").getStr(), TestVectors[0].getOrDefault("password").getStr())[]
seckey) == KeyFileStatus.Success
seckey.toRaw == expectkey.toRaw
test "KeyStoreTests/basic_tests.json python_generated_test_with_odd_iv":
var seckey: PrivateKey
var expectkey = PrivateKey.fromHex(TestVectors[1].getOrDefault("priv").getStr())[]
check: check:
seckey.toRaw() == expectkey.toRaw()
test "KeyStoreTests/basic_tests.json python_generated_test_with_odd_iv":
var expectkey = PrivateKey.fromHex(TestVectors[1].getOrDefault("priv").getStr())[]
let seckey =
decodeKeyFileJson(TestVectors[1].getOrDefault("keyfile"), decodeKeyFileJson(TestVectors[1].getOrDefault("keyfile"),
TestVectors[1].getOrDefault("password").getStr(), TestVectors[1].getOrDefault("password").getStr())[]
seckey) == KeyFileStatus.Success check:
seckey.toRaw == expectkey.toRaw seckey.toRaw == expectkey.toRaw
test "KeyStoreTests/basic_tests.json evilnonce": test "KeyStoreTests/basic_tests.json evilnonce":
var seckey: PrivateKey
var expectkey = PrivateKey.fromHex(TestVectors[2].getOrDefault("priv").getStr())[] var expectkey = PrivateKey.fromHex(TestVectors[2].getOrDefault("priv").getStr())[]
let seckey = decodeKeyFileJson(TestVectors[2].getOrDefault("keyfile"),
TestVectors[2].getOrDefault("password").getStr())[]
check: check:
decodeKeyFileJson(TestVectors[2].getOrDefault("keyfile"),
TestVectors[2].getOrDefault("password").getStr(),
seckey) == KeyFileStatus.Success
seckey.toRaw == expectkey.toRaw seckey.toRaw == expectkey.toRaw
test "KeyStoreTests/basic_tests.json evilnonce with wrong password": test "KeyStoreTests/basic_tests.json evilnonce with wrong password":
var seckey: PrivateKey let seckey =
check:
decodeKeyFileJson(TestVectors[2].getOrDefault("keyfile"), decodeKeyFileJson(TestVectors[2].getOrDefault("keyfile"),
"wrongpassword", "wrongpassword")
seckey) == KeyFileStatus.IncorrectMac check:
seckey.error == KeyFileError.IncorrectMac
test "Create/Save/Load test": test "Create/Save/Load test":
var seckey0 = PrivateKey.random()[] var seckey0 = PrivateKey.random()[]
var seckey1: PrivateKey let jobject = createKeyFileJson(seckey0, "randompassword")[]
check:
saveKeyFile("test.keyfile", jobject).isOk()
var seckey1 = loadKeyFile("test.keyfile", "randompassword")[]
check: check:
createKeyFileJson(seckey0, "randompassword",
jobject) == KeyFileStatus.Success
saveKeyFile("test.keyfile", jobject) == KeyFileStatus.Success
loadKeyFile("test.keyfile", "randompassword",
seckey1) == KeyFileStatus.Success
seckey0.toRaw == seckey1.toRaw seckey0.toRaw == seckey1.toRaw
removeFile("test.keyfile") removeFile("test.keyfile")
test "Load non-existent pathname test": test "Load non-existent pathname test":
var seckey: PrivateKey
check: check:
loadKeyFile("nonexistant.keyfile", "password", loadKeyFile("nonexistant.keyfile", "password").error ==
seckey) == KeyFileStatus.OsError KeyFileError.OsError

View File

@ -7,17 +7,15 @@
# Apache License, version 2.0, (LICENSE-APACHEv2) # Apache License, version 2.0, (LICENSE-APACHEv2)
# MIT license (LICENSE-MIT) # MIT license (LICENSE-MIT)
import eth/keyfile/uuid, strutils, unittest import eth/keyfile/uuid, unittest
suite "Cross-platform UUID test suite": suite "Cross-platform UUID test suite":
test "Platform UUID check": test "Platform UUID check":
var u: UUID check uuidGenerate().isOk
check uuidGenerate(u) == 1
test "Conversion test": test "Conversion test":
var u: UUID let u = uuidGenerate()[]
check: check:
uuidGenerate(u) == 1
len($u) == 36 len($u) == 36
$uuidFromString($u) == $u $uuidFromString($u)[] == $u
uuidToString(u, true) == $u uuidToString(u) == $u
uuidToString(u, false) == toUpperAscii($u)

View File

@ -7,10 +7,9 @@ import
var node = setupTestNode(Bzz, Hive) var node = setupTestNode(Bzz, Hive)
var bzzENode: ENode
let nodeId = "enode://10420addaa648ffcf09c4ba9df7ce876f276f77aae015bc9346487780c9c04862dc47cec17c86be10d4fb7d93f2cae3f8e702f94cb6dea5807bfedad218a53df@127.0.0.1:30399" let nodeId = "enode://10420addaa648ffcf09c4ba9df7ce876f276f77aae015bc9346487780c9c04862dc47cec17c86be10d4fb7d93f2cae3f8e702f94cb6dea5807bfedad218a53df@127.0.0.1:30399"
discard initENode(nodeId, bzzENode) let enode = ENode.fromString(nodeId)[]
waitFor node.peerPool.connectToNode(newNode(bzzENode)) waitFor node.peerPool.connectToNode(newNode(enode))
doAssert node.peerPool.connectedNodes.len() == 1 doAssert node.peerPool.connectedNodes.len() == 1

View File

@ -20,7 +20,7 @@ proc setupBootNode*(): Future[ENode] {.async.} =
bootNodeKey = KeyPair.random()[] bootNodeKey = KeyPair.random()[]
bootNodeAddr = localAddress(30301) bootNodeAddr = localAddress(30301)
bootNode = await startDiscoveryNode(bootNodeKey.seckey, bootNodeAddr, @[]) bootNode = await startDiscoveryNode(bootNodeKey.seckey, bootNodeAddr, @[])
result = initENode(bootNodeKey.pubkey, bootNodeAddr) result = ENode(pubkey: bootNodeKey.pubkey, address: bootNodeAddr)
proc setupTestNode*(capabilities: varargs[ProtocolInfo, `protocolInfo`]): EthereumNode = proc setupTestNode*(capabilities: varargs[ProtocolInfo, `protocolInfo`]): EthereumNode =
let keys1 = KeyPair.random()[] let keys1 = KeyPair.random()[]

View File

@ -98,20 +98,15 @@ let topic = [byte 0x12, 0, 0, 0]
if config.main: if config.main:
var bootnodes: seq[ENode] = @[] var bootnodes: seq[ENode] = @[]
for nodeId in MainnetBootnodes: for nodeId in MainnetBootnodes:
var bootnode: ENode bootnodes.add(ENode.fromString(nodeId).expect("static nodes"))
discard initENode(nodeId, bootnode)
bootnodes.add(bootnode)
asyncCheck node.connectToNetwork(bootnodes, true, true) asyncCheck node.connectToNetwork(bootnodes, true, true)
# main network has mostly non SHH nodes, so we connect directly to SHH nodes # main network has mostly non SHH nodes, so we connect directly to SHH nodes
for nodeId in WhisperNodes: for nodeId in WhisperNodes:
var whisperENode: ENode var whisperNode = newNode(ENode.fromString(nodeId).expect("static nodes"))
discard initENode(nodeId, whisperENode)
var whisperNode = newNode(whisperENode)
asyncCheck node.peerPool.connectToNode(whisperNode) asyncCheck node.peerPool.connectToNode(whisperNode)
else: else:
var bootENode: ENode let bootENode = ENode.fromString(DockerBootnode).expect("static node")
discard initENode(DockerBootNode, bootENode)
waitFor node.connectToNetwork(@[bootENode], true, true) waitFor node.connectToNetwork(@[bootENode], true, true)
if config.watch: if config.watch:

View File

@ -215,18 +215,20 @@ suite "Ethereum P2P handshake test suite":
block: block:
proc newTestHandshake(flags: set[HandshakeFlag]): Handshake = proc newTestHandshake(flags: set[HandshakeFlag]): Handshake =
result = newHandshake(flags)
if Initiator in flags: if Initiator in flags:
result.host.seckey = PrivateKey.fromHex(testValue("initiator_private_key"))[] let pk = PrivateKey.fromHex(testValue("initiator_private_key"))[]
result.host.pubkey = result.host.seckey.toPublicKey()[] let kp = KeyPair(seckey: pk, pubkey: pk.toPublicKey()[])
result = Handshake.tryInit(kp, flags)[]
let epki = testValue("initiator_ephemeral_private_key") let epki = testValue("initiator_ephemeral_private_key")
result.ephemeral.seckey = PrivateKey.fromHex(epki)[] result.ephemeral.seckey = PrivateKey.fromHex(epki)[]
result.ephemeral.pubkey = result.ephemeral.seckey.toPublicKey()[] result.ephemeral.pubkey = result.ephemeral.seckey.toPublicKey()[]
let nonce = fromHex(stripSpaces(testValue("initiator_nonce"))) let nonce = fromHex(stripSpaces(testValue("initiator_nonce")))
result.initiatorNonce[0..^1] = nonce[0..^1] result.initiatorNonce[0..^1] = nonce[0..^1]
elif Responder in flags: elif Responder in flags:
result.host.seckey = PrivateKey.fromHex(testValue("receiver_private_key"))[] let pk = PrivateKey.fromHex(testValue("receiver_private_key"))[]
result.host.pubkey = result.host.seckey.toPublicKey()[] let kp = KeyPair(seckey: pk, pubkey: pk.toPublicKey()[])
result = Handshake.tryInit(kp, flags)[]
let epkr = testValue("receiver_ephemeral_private_key") let epkr = testValue("receiver_ephemeral_private_key")
result.ephemeral.seckey = PrivateKey.fromHex(epkr)[] result.ephemeral.seckey = PrivateKey.fromHex(epkr)[]
result.ephemeral.pubkey = result.ephemeral.seckey.toPublicKey()[] result.ephemeral.pubkey = result.ephemeral.seckey.toPublicKey()[]
@ -238,9 +240,8 @@ suite "Ethereum P2P handshake test suite":
var responder = newTestHandshake({Responder}) var responder = newTestHandshake({Responder})
var m0 = newSeq[byte](initiator.authSize(false)) var m0 = newSeq[byte](initiator.authSize(false))
var k0 = 0 var k0 = 0
check: initiator.authMessage(
initiator.authMessage(responder.host.pubkey, responder.host.pubkey, m0, k0, 0, false).expect("auth success")
m0, k0, 0, false) == AuthStatus.Success
var expect1 = fromHex(stripSpaces(testValue("auth_plaintext"))) var expect1 = fromHex(stripSpaces(testValue("auth_plaintext")))
var expect2 = fromHex(stripSpaces(pyevmAuth)) var expect2 = fromHex(stripSpaces(pyevmAuth))
check: check:
@ -254,10 +255,11 @@ suite "Ethereum P2P handshake test suite":
var k0 = 0 var k0 = 0
let remoteEPubkey0 = initiator.ephemeral.pubkey let remoteEPubkey0 = initiator.ephemeral.pubkey
let remoteHPubkey0 = initiator.host.pubkey let remoteHPubkey0 = initiator.host.pubkey
initiator.authMessage(
responder.host.pubkey, m0, k0).expect("auth success")
responder.decodeAuthMessage(m0).expect("decode success")
check: check:
initiator.authMessage(responder.host.pubkey,
m0, k0) == AuthStatus.Success
responder.decodeAuthMessage(m0) == AuthStatus.Success
responder.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1] responder.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1]
responder.remoteEPubkey == remoteEPubkey0 responder.remoteEPubkey == remoteEPubkey0
responder.remoteHPubkey == remoteHPubkey0 responder.remoteHPubkey == remoteHPubkey0
@ -270,11 +272,11 @@ suite "Ethereum P2P handshake test suite":
var k0 = 0 var k0 = 0
var k1 = 0 var k1 = 0
var expect0 = fromHex(stripSpaces(testValue("authresp_plaintext"))) var expect0 = fromHex(stripSpaces(testValue("authresp_plaintext")))
initiator.authMessage(
responder.host.pubkey, m0, k0).expect("auth success")
responder.decodeAuthMessage(m0).expect("decode success")
responder.ackMessage(m1, k1, 0, false).expect("ack success")
check: check:
initiator.authMessage(responder.host.pubkey,
m0, k0) == AuthStatus.Success
responder.decodeAuthMessage(m0) == AuthStatus.Success
responder.ackMessage(m1, k1, 0, false) == AuthStatus.Success
m1 == expect0 m1 == expect0
responder.initiatorNonce == initiator.initiatorNonce responder.initiatorNonce == initiator.initiatorNonce
@ -285,12 +287,12 @@ suite "Ethereum P2P handshake test suite":
var m1 = newSeq[byte](responder.ackSize()) var m1 = newSeq[byte](responder.ackSize())
var k0 = 0 var k0 = 0
var k1 = 0 var k1 = 0
check:
initiator.authMessage(responder.host.pubkey, initiator.authMessage(
m0, k0) == AuthStatus.Success responder.host.pubkey, m0, k0).expect("auth success")
responder.decodeAuthMessage(m0) == AuthStatus.Success responder.decodeAuthMessage(m0).expect("decode success")
responder.ackMessage(m1, k1) == AuthStatus.Success responder.ackMessage(m1, k1).expect("ack success")
initiator.decodeAckMessage(m1) == AuthStatus.Success initiator.decodeAckMessage(m1).expect("decode success")
let remoteEPubkey0 = responder.ephemeral.pubkey let remoteEPubkey0 = responder.ephemeral.pubkey
let remoteHPubkey0 = responder.host.pubkey let remoteHPubkey0 = responder.host.pubkey
check: check:
@ -307,13 +309,12 @@ suite "Ethereum P2P handshake test suite":
var tmac = fromHex(stripSpaces(testValue("mac_secret"))) var tmac = fromHex(stripSpaces(testValue("mac_secret")))
var temac = fromHex(stripSpaces(testValue("initial_egress_MAC"))) var temac = fromHex(stripSpaces(testValue("initial_egress_MAC")))
var timac = fromHex(stripSpaces(testValue("initial_ingress_MAC"))) var timac = fromHex(stripSpaces(testValue("initial_ingress_MAC")))
var csecInitiator: ConnectionSecret
var csecResponder: ConnectionSecret responder.decodeAuthMessage(authm).expect("decode success")
initiator.decodeAckMessage(ackm).expect("ack success")
var csecInitiator = initiator.getSecrets(authm, ackm).expect("secrets success")
var csecResponder = responder.getSecrets(authm, ackm).expect("secrets success")
check: check:
responder.decodeAuthMessage(authm) == AuthStatus.Success
initiator.decodeAckMessage(ackm) == AuthStatus.Success
initiator.getSecrets(authm, ackm, csecInitiator) == AuthStatus.Success
responder.getSecrets(authm, ackm, csecResponder) == AuthStatus.Success
csecInitiator.aesKey == csecResponder.aesKey csecInitiator.aesKey == csecResponder.aesKey
csecInitiator.macKey == csecResponder.macKey csecInitiator.macKey == csecResponder.macKey
taes[0..^1] == csecInitiator.aesKey[0..^1] taes[0..^1] == csecInitiator.aesKey[0..^1]
@ -330,9 +331,11 @@ suite "Ethereum P2P handshake test suite":
block: block:
proc newTestHandshake(flags: set[HandshakeFlag]): Handshake = proc newTestHandshake(flags: set[HandshakeFlag]): Handshake =
result = newHandshake(flags)
if Initiator in flags: if Initiator in flags:
result.host.seckey = PrivateKey.fromHex(testE8Value("initiator_private_key"))[] let pk = PrivateKey.fromHex(testE8Value("initiator_private_key"))[]
let kp = KeyPair(seckey: pk, pubkey: pk.toPublicKey()[])
result = Handshake.tryInit(kp, flags)[]
result.host.pubkey = result.host.seckey.toPublicKey()[] result.host.pubkey = result.host.seckey.toPublicKey()[]
let esec = testE8Value("initiator_ephemeral_private_key") let esec = testE8Value("initiator_ephemeral_private_key")
result.ephemeral.seckey = PrivateKey.fromHex(esec)[] result.ephemeral.seckey = PrivateKey.fromHex(esec)[]
@ -340,8 +343,10 @@ suite "Ethereum P2P handshake test suite":
let nonce = fromHex(stripSpaces(testE8Value("initiator_nonce"))) let nonce = fromHex(stripSpaces(testE8Value("initiator_nonce")))
result.initiatorNonce[0..^1] = nonce[0..^1] result.initiatorNonce[0..^1] = nonce[0..^1]
elif Responder in flags: elif Responder in flags:
result.host.seckey = PrivateKey.fromHex(testE8Value("receiver_private_key"))[] let pk = PrivateKey.fromHex(testE8Value("receiver_private_key"))[]
result.host.pubkey = result.host.seckey.toPublicKey()[] let kp = KeyPair(seckey: pk, pubkey: pk.toPublicKey()[])
result = Handshake.tryInit(kp, flags)[]
let esec = testE8Value("receiver_ephemeral_private_key") let esec = testE8Value("receiver_ephemeral_private_key")
result.ephemeral.seckey = PrivateKey.fromHex(esec)[] result.ephemeral.seckey = PrivateKey.fromHex(esec)[]
result.ephemeral.pubkey = result.ephemeral.seckey.toPublicKey()[] result.ephemeral.pubkey = result.ephemeral.seckey.toPublicKey()[]
@ -352,8 +357,8 @@ suite "Ethereum P2P handshake test suite":
var initiator = newTestHandshake({Initiator}) var initiator = newTestHandshake({Initiator})
var responder = newTestHandshake({Responder}) var responder = newTestHandshake({Responder})
var m0 = fromHex(stripSpaces(testE8Value("auth_ciphertext_v4"))) var m0 = fromHex(stripSpaces(testE8Value("auth_ciphertext_v4")))
responder.decodeAuthMessage(m0).expect("decode success")
check: check:
responder.decodeAuthMessage(m0) == AuthStatus.Success
responder.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1] responder.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1]
let remoteEPubkey0 = initiator.ephemeral.pubkey let remoteEPubkey0 = initiator.ephemeral.pubkey
let remoteHPubkey0 = initiator.host.pubkey let remoteHPubkey0 = initiator.host.pubkey
@ -361,7 +366,7 @@ suite "Ethereum P2P handshake test suite":
responder.remoteEPubkey == remoteEPubkey0 responder.remoteEPubkey == remoteEPubkey0
responder.remoteHPubkey == remoteHPubkey0 responder.remoteHPubkey == remoteHPubkey0
var m1 = fromHex(stripSpaces(testE8Value("authack_ciphertext_v4"))) var m1 = fromHex(stripSpaces(testE8Value("authack_ciphertext_v4")))
check initiator.decodeAckMessage(m1) == AuthStatus.Success initiator.decodeAckMessage(m1).expect("decode success")
let remoteEPubkey1 = responder.ephemeral.pubkey let remoteEPubkey1 = responder.ephemeral.pubkey
check: check:
initiator.remoteEPubkey == remoteEPubkey1 initiator.remoteEPubkey == remoteEPubkey1
@ -371,28 +376,27 @@ suite "Ethereum P2P handshake test suite":
var initiator = newTestHandshake({Initiator}) var initiator = newTestHandshake({Initiator})
var responder = newTestHandshake({Responder}) var responder = newTestHandshake({Responder})
var m0 = fromHex(stripSpaces(testE8Value("auth_ciphertext_eip8"))) var m0 = fromHex(stripSpaces(testE8Value("auth_ciphertext_eip8")))
responder.decodeAuthMessage(m0).expect("decode success")
check: check:
responder.decodeAuthMessage(m0) == AuthStatus.Success
responder.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1] responder.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1]
let remoteEPubkey0 = initiator.ephemeral.pubkey let remoteEPubkey0 = initiator.ephemeral.pubkey
check responder.remoteEPubkey == remoteEPubkey0 check responder.remoteEPubkey == remoteEPubkey0
let remoteHPubkey0 = initiator.host.pubkey let remoteHPubkey0 = initiator.host.pubkey
check responder.remoteHPubkey == remoteHPubkey0 check responder.remoteHPubkey == remoteHPubkey0
var m1 = fromHex(stripSpaces(testE8Value("authack_ciphertext_eip8"))) var m1 = fromHex(stripSpaces(testE8Value("authack_ciphertext_eip8")))
check initiator.decodeAckMessage(m1) == AuthStatus.Success initiator.decodeAckMessage(m1).expect("decode success")
let remoteEPubkey1 = responder.ephemeral.pubkey let remoteEPubkey1 = responder.ephemeral.pubkey
check: check:
initiator.remoteEPubkey == remoteEPubkey1 initiator.remoteEPubkey == remoteEPubkey1
initiator.responderNonce[0..^1] == responder.responderNonce[0..^1] initiator.responderNonce[0..^1] == responder.responderNonce[0..^1]
var taes = fromHex(stripSpaces(testE8Value("auth2ack2_aes_secret"))) var taes = fromHex(stripSpaces(testE8Value("auth2ack2_aes_secret")))
var tmac = fromHex(stripSpaces(testE8Value("auth2ack2_mac_secret"))) var tmac = fromHex(stripSpaces(testE8Value("auth2ack2_mac_secret")))
var csecInitiator: ConnectionSecret
var csecResponder: ConnectionSecret var csecInitiator = initiator.getSecrets(m0, m1).expect("secrets")
var csecResponder = responder.getSecrets(m0, m1).expect("secrets")
check: check:
int(initiator.version) == 4 int(initiator.version) == 4
int(responder.version) == 4 int(responder.version) == 4
initiator.getSecrets(m0, m1, csecInitiator) == AuthStatus.Success
responder.getSecrets(m0, m1, csecResponder) == AuthStatus.Success
csecInitiator.aesKey == csecResponder.aesKey csecInitiator.aesKey == csecResponder.aesKey
csecInitiator.macKey == csecResponder.macKey csecInitiator.macKey == csecResponder.macKey
taes[0..^1] == csecInitiator.aesKey[0..^1] taes[0..^1] == csecInitiator.aesKey[0..^1]
@ -407,8 +411,8 @@ suite "Ethereum P2P handshake test suite":
var initiator = newTestHandshake({Initiator}) var initiator = newTestHandshake({Initiator})
var responder = newTestHandshake({Responder}) var responder = newTestHandshake({Responder})
var m0 = fromHex(stripSpaces(testE8Value("auth_ciphertext_eip8_3f"))) var m0 = fromHex(stripSpaces(testE8Value("auth_ciphertext_eip8_3f")))
responder.decodeAuthMessage(m0).expect("decode success")
check: check:
responder.decodeAuthMessage(m0) == AuthStatus.Success
responder.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1] responder.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1]
let remoteEPubkey0 = initiator.ephemeral.pubkey let remoteEPubkey0 = initiator.ephemeral.pubkey
let remoteHPubkey0 = initiator.host.pubkey let remoteHPubkey0 = initiator.host.pubkey
@ -416,7 +420,7 @@ suite "Ethereum P2P handshake test suite":
responder.remoteEPubkey == remoteEPubkey0 responder.remoteEPubkey == remoteEPubkey0
responder.remoteHPubkey == remoteHPubkey0 responder.remoteHPubkey == remoteHPubkey0
var m1 = fromHex(stripSpaces(testE8Value("authack_ciphertext_eip8_3f"))) var m1 = fromHex(stripSpaces(testE8Value("authack_ciphertext_eip8_3f")))
check initiator.decodeAckMessage(m1) == AuthStatus.Success initiator.decodeAckMessage(m1).expect("decode success")
let remoteEPubkey1 = responder.ephemeral.pubkey let remoteEPubkey1 = responder.ephemeral.pubkey
check: check:
int(initiator.version) == 57 int(initiator.version) == 57
@ -429,22 +433,20 @@ suite "Ethereum P2P handshake test suite":
var initiator = newTestHandshake({Initiator, EIP8}) var initiator = newTestHandshake({Initiator, EIP8})
var responder = newTestHandshake({Responder}) var responder = newTestHandshake({Responder})
var m0 = newSeq[byte](initiator.authSize()) var m0 = newSeq[byte](initiator.authSize())
var csecInitiator: ConnectionSecret
var csecResponder: ConnectionSecret
var k0 = 0 var k0 = 0
var k1 = 0 var k1 = 0
check initiator.authMessage(responder.host.pubkey, initiator.authMessage(
m0, k0) == AuthStatus.Success responder.host.pubkey, m0, k0).expect("auth success")
m0.setLen(k0) m0.setLen(k0)
check responder.decodeAuthMessage(m0) == AuthStatus.Success responder.decodeAuthMessage(m0).expect("decode success")
check (EIP8 in responder.flags) == true check (EIP8 in responder.flags) == true
var m1 = newSeq[byte](responder.ackSize()) var m1 = newSeq[byte](responder.ackSize())
check responder.ackMessage(m1, k1) == AuthStatus.Success responder.ackMessage(m1, k1).expect("ack success")
m1.setLen(k1) m1.setLen(k1)
check initiator.decodeAckMessage(m1) == AuthStatus.Success initiator.decodeAckMessage(m1).expect("decode success")
var csecInitiator = initiator.getSecrets(m0, m1).expect("secrets")
var csecResponder = responder.getSecrets(m0, m1).expect("secrets")
check: check:
initiator.getSecrets(m0, m1, csecInitiator) == AuthStatus.Success
responder.getSecrets(m0, m1, csecResponder) == AuthStatus.Success
csecInitiator.aesKey == csecResponder.aesKey csecInitiator.aesKey == csecResponder.aesKey
csecInitiator.macKey == csecResponder.macKey csecInitiator.macKey == csecResponder.macKey
@ -453,21 +455,19 @@ suite "Ethereum P2P handshake test suite":
var initiator = newTestHandshake({Initiator}) var initiator = newTestHandshake({Initiator})
var responder = newTestHandshake({Responder}) var responder = newTestHandshake({Responder})
var m0 = newSeq[byte](initiator.authSize()) var m0 = newSeq[byte](initiator.authSize())
var csecInitiator: ConnectionSecret
var csecResponder: ConnectionSecret
var k0 = 0 var k0 = 0
var k1 = 0 var k1 = 0
check initiator.authMessage(responder.host.pubkey, initiator.authMessage(
m0, k0) == AuthStatus.Success responder.host.pubkey, m0, k0).expect("auth success")
m0.setLen(k0) m0.setLen(k0)
check responder.decodeAuthMessage(m0) == AuthStatus.Success responder.decodeAuthMessage(m0).expect("auth success")
var m1 = newSeq[byte](responder.ackSize()) var m1 = newSeq[byte](responder.ackSize())
check responder.ackMessage(m1, k1) == AuthStatus.Success responder.ackMessage(m1, k1).expect("ack success")
m1.setLen(k1) m1.setLen(k1)
check initiator.decodeAckMessage(m1) == AuthStatus.Success initiator.decodeAckMessage(m1).expect("ack success")
var csecInitiator = initiator.getSecrets(m0, m1).expect("secrets")
var csecResponder = responder.getSecrets(m0, m1).expect("secrets")
check: check:
initiator.getSecrets(m0, m1, csecInitiator) == AuthStatus.Success
responder.getSecrets(m0, m1, csecResponder) == AuthStatus.Success
csecInitiator.aesKey == csecResponder.aesKey csecInitiator.aesKey == csecResponder.aesKey
csecInitiator.macKey == csecResponder.macKey csecInitiator.macKey == csecResponder.macKey

View File

@ -88,18 +88,19 @@ proc testValue(s: string): string =
suite "Ethereum RLPx encryption/decryption test suite": suite "Ethereum RLPx encryption/decryption test suite":
proc newTestHandshake(flags: set[HandshakeFlag]): Handshake = proc newTestHandshake(flags: set[HandshakeFlag]): Handshake =
result = newHandshake(flags)
if Initiator in flags: if Initiator in flags:
result.host.seckey = PrivateKey.fromHex(testValue("initiator_private_key"))[] let pk = PrivateKey.fromHex(testValue("initiator_private_key"))[]
result.host.pubkey = result.host.seckey.toPublicKey()[] let kp = KeyPair(seckey: pk, pubkey: pk.toPublicKey()[])
result = Handshake.tryInit(kp, flags)[]
let epki = testValue("initiator_ephemeral_private_key") let epki = testValue("initiator_ephemeral_private_key")
result.ephemeral.seckey = PrivateKey.fromHex(epki)[] result.ephemeral.seckey = PrivateKey.fromHex(epki)[]
result.ephemeral.pubkey = result.ephemeral.seckey.toPublicKey()[] result.ephemeral.pubkey = result.ephemeral.seckey.toPublicKey()[]
let nonce = fromHex(stripSpaces(testValue("initiator_nonce"))) let nonce = fromHex(stripSpaces(testValue("initiator_nonce")))
result.initiatorNonce[0..^1] = nonce[0..^1] result.initiatorNonce[0..^1] = nonce[0..^1]
elif Responder in flags: elif Responder in flags:
result.host.seckey = PrivateKey.fromHex(testValue("receiver_private_key"))[] let pk = PrivateKey.fromHex(testValue("receiver_private_key"))[]
result.host.pubkey = result.host.seckey.toPublicKey()[] let kp = KeyPair(seckey: pk, pubkey: pk.toPublicKey()[])
result = Handshake.tryInit(kp, flags)[]
let epkr = testValue("receiver_ephemeral_private_key") let epkr = testValue("receiver_ephemeral_private_key")
result.ephemeral.seckey = PrivateKey.fromHex(epkr)[] result.ephemeral.seckey = PrivateKey.fromHex(epkr)[]
result.ephemeral.pubkey = result.ephemeral.seckey.toPublicKey()[] result.ephemeral.pubkey = result.ephemeral.seckey.toPublicKey()[]
@ -111,15 +112,13 @@ suite "Ethereum RLPx encryption/decryption test suite":
var responder = newTestHandshake({Responder}) var responder = newTestHandshake({Responder})
var authm = fromHex(stripSpaces(testValue("auth_ciphertext"))) var authm = fromHex(stripSpaces(testValue("auth_ciphertext")))
var ackm = fromHex(stripSpaces(testValue("authresp_ciphertext"))) var ackm = fromHex(stripSpaces(testValue("authresp_ciphertext")))
var csecInitiator: ConnectionSecret
var csecResponder: ConnectionSecret
var stateInitiator0, stateInitiator1: SecretState var stateInitiator0, stateInitiator1: SecretState
var stateResponder0, stateResponder1: SecretState var stateResponder0, stateResponder1: SecretState
check: responder.decodeAuthMessage(authm).expect("success")
responder.decodeAuthMessage(authm) == AuthStatus.Success initiator.decodeAckMessage(ackm).expect("success")
initiator.decodeAckMessage(ackm) == AuthStatus.Success
initiator.getSecrets(authm, ackm, csecInitiator) == AuthStatus.Success var csecInitiator = initiator.getSecrets(authm, ackm)[]
responder.getSecrets(authm, ackm, csecResponder) == AuthStatus.Success var csecResponder = responder.getSecrets(authm, ackm)[]
initSecretState(csecInitiator, stateInitiator0) initSecretState(csecInitiator, stateInitiator0)
initSecretState(csecResponder, stateResponder0) initSecretState(csecResponder, stateResponder0)
initSecretState(csecInitiator, stateInitiator1) initSecretState(csecInitiator, stateInitiator1)
@ -132,7 +131,7 @@ suite "Ethereum RLPx encryption/decryption test suite":
block: block:
check stateResponder0.decryptHeader(toOpenArray(initiatorHello, 0, 31), check stateResponder0.decryptHeader(toOpenArray(initiatorHello, 0, 31),
header) == RlpxStatus.Success header).isOk()
let bodysize = getBodySize(header) let bodysize = getBodySize(header)
check bodysize == 79 check bodysize == 79
# we need body size to be rounded to 16 bytes boundary to properly # we need body size to be rounded to 16 bytes boundary to properly
@ -142,16 +141,16 @@ suite "Ethereum RLPx encryption/decryption test suite":
check: check:
stateResponder0.decryptBody( stateResponder0.decryptBody(
toOpenArray(initiatorHello, 32, len(initiatorHello) - 1), toOpenArray(initiatorHello, 32, len(initiatorHello) - 1),
getBodySize(header), body, decrsize) == RlpxStatus.Success getBodySize(header), body, decrsize).isOk()
decrsize == 79 decrsize == 79
body.setLen(decrsize) body.setLen(decrsize)
var hello = newSeq[byte](encryptedLength(bodysize)) var hello = newSeq[byte](encryptedLength(bodysize))
check: check:
stateInitiator1.encrypt(header, body, hello) == RlpxStatus.Success stateInitiator1.encrypt(header, body, hello).isOk()
hello == initiatorHello hello == initiatorHello
block: block:
check stateInitiator0.decryptHeader(toOpenArray(responderHello, 0, 31), check stateInitiator0.decryptHeader(toOpenArray(responderHello, 0, 31),
header) == RlpxStatus.Success header).isOk()
let bodysize = getBodySize(header) let bodysize = getBodySize(header)
check bodysize == 79 check bodysize == 79
# we need body size to be rounded to 16 bytes boundary to properly # we need body size to be rounded to 16 bytes boundary to properly
@ -161,34 +160,31 @@ suite "Ethereum RLPx encryption/decryption test suite":
check: check:
stateInitiator0.decryptBody( stateInitiator0.decryptBody(
toOpenArray(responderHello, 32, len(initiatorHello) - 1), toOpenArray(responderHello, 32, len(initiatorHello) - 1),
getBodySize(header), body, decrsize) == RlpxStatus.Success getBodySize(header), body, decrsize).isOk()
decrsize == 79 decrsize == 79
body.setLen(decrsize) body.setLen(decrsize)
var hello = newSeq[byte](encryptedLength(bodysize)) var hello = newSeq[byte](encryptedLength(bodysize))
check: check:
stateResponder1.encrypt(header, body, hello) == RlpxStatus.Success stateResponder1.encrypt(header, body, hello).isOk()
hello == responderHello hello == responderHello
test "Continuous stream of different lengths (1000 times)": test "Continuous stream of different lengths (1000 times)":
var initiator = newTestHandshake({Initiator}) var initiator = newTestHandshake({Initiator})
var responder = newTestHandshake({Responder}) var responder = newTestHandshake({Responder})
var m0 = newSeq[byte](initiator.authSize()) var m0 = newSeq[byte](initiator.authSize())
var csecInitiator: ConnectionSecret
var csecResponder: ConnectionSecret
var k0 = 0 var k0 = 0
var k1 = 0 var k1 = 0
check initiator.authMessage(responder.host.pubkey, check initiator.authMessage(responder.host.pubkey,
m0, k0) == AuthStatus.Success m0, k0).isOk
m0.setLen(k0) m0.setLen(k0)
check responder.decodeAuthMessage(m0) == AuthStatus.Success check responder.decodeAuthMessage(m0).isOk
var m1 = newSeq[byte](responder.ackSize()) var m1 = newSeq[byte](responder.ackSize())
check responder.ackMessage(m1, k1) == AuthStatus.Success check responder.ackMessage(m1, k1).isOk
m1.setLen(k1) m1.setLen(k1)
check initiator.decodeAckMessage(m1) == AuthStatus.Success check initiator.decodeAckMessage(m1).isOk
check: var csecInitiator = initiator.getSecrets(m0, m1)[]
initiator.getSecrets(m0, m1, csecInitiator) == AuthStatus.Success var csecResponder = responder.getSecrets(m0, m1)[]
responder.getSecrets(m0, m1, csecResponder) == AuthStatus.Success
var stateInitiator: SecretState var stateInitiator: SecretState
var stateResponder: SecretState var stateResponder: SecretState
var iheader, rheader: array[16, byte] var iheader, rheader: array[16, byte]
@ -207,9 +203,9 @@ suite "Ethereum RLPx encryption/decryption test suite":
check: check:
randomBytes(ibody) == len(ibody) randomBytes(ibody) == len(ibody)
stateInitiator.encrypt(iheader, ibody, stateInitiator.encrypt(iheader, ibody,
encrypted) == RlpxStatus.Success encrypted).isOk()
stateResponder.decryptHeader(toOpenArray(encrypted, 0, 31), stateResponder.decryptHeader(toOpenArray(encrypted, 0, 31),
rheader) == RlpxStatus.Success rheader).isOk()
var length = getBodySize(rheader) var length = getBodySize(rheader)
check length == len(ibody) check length == len(ibody)
var rbody = newSeq[byte](decryptedLength(length)) var rbody = newSeq[byte](decryptedLength(length))
@ -217,7 +213,7 @@ suite "Ethereum RLPx encryption/decryption test suite":
check: check:
stateResponder.decryptBody( stateResponder.decryptBody(
toOpenArray(encrypted, 32, len(encrypted) - 1), toOpenArray(encrypted, 32, len(encrypted) - 1),
length, rbody, decrsize) == RlpxStatus.Success length, rbody, decrsize).isOk()
decrsize == length decrsize == length
rbody.setLen(decrsize) rbody.setLen(decrsize)
check: check:
@ -235,9 +231,9 @@ suite "Ethereum RLPx encryption/decryption test suite":
check: check:
randomBytes(ibody) == len(ibody) randomBytes(ibody) == len(ibody)
stateResponder.encrypt(iheader, ibody, stateResponder.encrypt(iheader, ibody,
encrypted) == RlpxStatus.Success encrypted).isOk()
stateInitiator.decryptHeader(toOpenArray(encrypted, 0, 31), stateInitiator.decryptHeader(toOpenArray(encrypted, 0, 31),
rheader) == RlpxStatus.Success rheader).isOk()
var length = getBodySize(rheader) var length = getBodySize(rheader)
check length == len(ibody) check length == len(ibody)
var rbody = newSeq[byte](decryptedLength(length)) var rbody = newSeq[byte](decryptedLength(length))
@ -245,7 +241,7 @@ suite "Ethereum RLPx encryption/decryption test suite":
check: check:
stateInitiator.decryptBody( stateInitiator.decryptBody(
toOpenArray(encrypted, 32, len(encrypted) - 1), toOpenArray(encrypted, 32, len(encrypted) - 1),
length, rbody, decrsize) == RlpxStatus.Success length, rbody, decrsize).isOk()
decrsize == length decrsize == length
rbody.setLen(length) rbody.setLen(length)
check: check:

View File

@ -22,7 +22,7 @@ proc test() {.async.} =
bootNodeKey = PrivateKey.fromHex( bootNodeKey = PrivateKey.fromHex(
"a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a617")[] "a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a617")[]
bootNodeAddr = localAddress(20301) bootNodeAddr = localAddress(20301)
bootENode = initENode(bootNodeKey.toPublicKey()[], bootNodeAddr) bootENode = ENode(pubkey: bootNodeKey.toPublicKey()[], address: bootNodeAddr)
bootNode = await startDiscoveryNode(bootNodeKey, bootNodeAddr, @[]) bootNode = await startDiscoveryNode(bootNodeKey, bootNodeAddr, @[])
test "Discover nodes": test "Discover nodes":

View File

@ -134,9 +134,9 @@ suite "Discovery v5 Cryptographic Primitives":
let let
pub = PublicKey.fromHex(publicKey)[] pub = PublicKey.fromHex(publicKey)[]
priv = PrivateKey.fromHex(secretKey)[] priv = PrivateKey.fromHex(secretKey)[]
let eph = ecdhRawFull(priv, pub) let eph = ecdhRawFull(priv, pub)
check: check:
eph.isOk()
eph[].data == hexToSeqByte(sharedSecret) eph[].data == hexToSeqByte(sharedSecret)
test "Key Derivation": test "Key Derivation":

View File

@ -71,14 +71,18 @@ suite "ECIES test suite":
var shmac = [0x13'u8, 0x13'u8] var shmac = [0x13'u8, 0x13'u8]
var s = PrivateKey.random()[] var s = PrivateKey.random()[]
var p = s.toPublicKey()[] var p = s.toPublicKey()[]
eciesEncrypt(plain, encr, p).expect("encryption should succeed")
eciesDecrypt(encr, decr, s).expect("decryption should succeed")
check: check:
# Without additional mac data # Without additional mac data
eciesEncrypt(plain, encr, p) == EciesStatus.Success
eciesDecrypt(encr, decr, s) == EciesStatus.Success
equalMem(addr m[0], addr decr[0], len(m)) equalMem(addr m[0], addr decr[0], len(m))
# With additional mac data # With additional mac data
eciesEncrypt(plain, encr, p, shmac) == EciesStatus.Success eciesEncrypt(plain, encr, p, shmac).expect("encryption should succeed")
eciesDecrypt(encr, decr, s, shmac) == EciesStatus.Success eciesDecrypt(encr, decr, s, shmac).expect("decryption should succeed")
check:
equalMem(addr m[0], addr decr[0], len(m)) equalMem(addr m[0], addr decr[0], len(m))
test "ECIES/py-evm/cpp-ethereum test_ecies.py#L43/rlpx.cpp#L187": test "ECIES/py-evm/cpp-ethereum test_ecies.py#L43/rlpx.cpp#L187":
@ -126,8 +130,10 @@ suite "ECIES test suite":
var s = PrivateKey.fromHex(secretKeys[i])[] var s = PrivateKey.fromHex(secretKeys[i])[]
var cipher = fromHex(stripSpaces(cipherText[i])) var cipher = fromHex(stripSpaces(cipherText[i]))
var expect = fromHex(stripSpaces(expectText[i])) var expect = fromHex(stripSpaces(expectText[i]))
eciesDecrypt(cipher, data, s).expect("decryption should succeed")
check: check:
eciesDecrypt(cipher, data, s) == EciesStatus.Success
compare(data, expect) == true compare(data, expect) == true
test "ECIES/cpp-ethereum rlpx.cpp#L432-L459": test "ECIES/cpp-ethereum rlpx.cpp#L432-L459":
@ -167,5 +173,5 @@ suite "ECIES test suite":
var s = PrivateKey.fromHex(secretKeys[i])[] var s = PrivateKey.fromHex(secretKeys[i])[]
var cipher = fromHex(stripSpaces(cipherData[i])) var cipher = fromHex(stripSpaces(cipherData[i]))
check: check:
eciesDecrypt(cipher, data, s) == EciesStatus.Success eciesDecrypt(cipher, data, s).isOk()
compare(data, expectData[i]) == true compare(data, expectData[i]) == true

View File

@ -7,7 +7,7 @@
# Apache License, version 2.0, (LICENSE-APACHEv2) # Apache License, version 2.0, (LICENSE-APACHEv2)
# MIT license (LICENSE-MIT) # MIT license (LICENSE-MIT)
import unittest, net import unittest, net, options
import eth/p2p/enode import eth/p2p/enode
suite "ENode": suite "ENode":
@ -28,26 +28,27 @@ suite "ENode":
] ]
const results = [ const results = [
IncorrectScheme, some IncorrectScheme,
IncorrectNodeId, some IncorrectNodeId,
IncorrectIP, some IncorrectIP,
IncorrectPort, some IncorrectPort,
IncorrectDiscPort, some IncorrectDiscPort,
IncorrectScheme, some IncorrectScheme,
IncorrectNodeId, some IncorrectNodeId,
IncorrectScheme, some IncorrectScheme,
ENodeStatus.Success, none(ENodeError),
ENodeStatus.Success, none(ENodeError),
ENodeStatus.Success, none(ENodeError),
ENodeStatus.Success none(ENodeError),
] ]
for index in 0..<len(enodes): for index in 0..<len(enodes):
var node: ENode let res = ENode.fromString(enodes[index])
let res = initENode(enodes[index], node) check (results[index].isSome and res.error == results[index].get) or
check res == results[index] res.isOk
if res == ENodeStatus.Success:
check enodes[index] == $node if res.isOk:
check enodes[index] == $res[]
test "Custom validation tests": test "Custom validation tests":
const enodes = [ const enodes = [
@ -67,35 +68,23 @@ suite "ENode":
] ]
const results = [ const results = [
IncorrectIP, some IncorrectIP,
IncorrectIP, some IncorrectIP,
IncorrectIP, some IncorrectIP,
IncorrectIP, some IncorrectIP,
ENodeStatus.Success, none(ENodeError),
IncorrectUri, some IncorrectUri,
IncorrectPort, some IncorrectPort,
IncorrectPort, some IncorrectPort,
IncorrectDiscPort, some IncorrectDiscPort,
IncorrectDiscPort, some IncorrectDiscPort,
IncorrectUri, some IncorrectUri,
IncorrectIP, some IncorrectIP,
IncorrectNodeId some IncorrectNodeId
] ]
for index in 0..<len(enodes): for index in 0..<len(enodes):
var node: ENode let res = ENode.fromString(enodes[index])
let res = initENode(enodes[index], node)
check res == results[index]
test "isCorrect() tests": check (results[index].isSome and res.error == results[index].get) or
var node: ENode res.isOk
check isCorrect(node) == false
let ip = IpAddress(family:IpAddressFamily.IPv4)
node = Enode(address: Address(ip: ip))
check isCorrect(node) == false
node.address.tcpPort = Port(25)
check isCorrect(node) == false
node.address.udpPort = Port(25)
check isCorrect(node) == false
node.pubkey = PrivateKey.random()[].toPublicKey()[]
check isCorrect(node) == true

View File

@ -58,8 +58,7 @@ suite "Testing protocol handlers":
var node2 = setupTestNode(abc, xyz) var node2 = setupTestNode(abc, xyz)
node2.startListening() node2.startListening()
let peer = await node1.rlpxConnect(newNode(initENode(node2.keys.pubKey, let peer = await node1.rlpxConnect(newNode(node2.toENode()))
node2.address)))
check: check:
peer.isNil == false peer.isNil == false
@ -74,8 +73,7 @@ suite "Testing protocol handlers":
var node1 = setupTestNode(hah) var node1 = setupTestNode(hah)
var node2 = setupTestNode(hah) var node2 = setupTestNode(hah)
node2.startListening() node2.startListening()
let peer = await node1.rlpxConnect(newNode(initENode(node2.keys.pubKey, let peer = await node1.rlpxConnect(newNode(node2.toENode()))
node2.address)))
check: check:
peer.isNil == true peer.isNil == true
# To check if the disconnection handler did not run # To check if the disconnection handler did not run

View File

@ -13,8 +13,7 @@ node1 = setupTestNode(eth, Whisper)
node2 = setupTestNode(eth, Whisper) node2 = setupTestNode(eth, Whisper)
node2.startListening() node2.startListening()
peer = waitFor node1.rlpxConnect(newNode(initENode(node2.keys.pubKey, peer = waitFor node1.rlpxConnect(newNode(node2.toENode()))
node2.address)))
proc testThunk(payload: openArray[byte]) = proc testThunk(payload: openArray[byte]) =
var (msgId, msgData) = recvMsgMock(payload) var (msgId, msgData) = recvMsgMock(payload)

View File

@ -23,8 +23,7 @@ procSuite "Whisper connections":
var node1 = setupTestNode(Whisper) var node1 = setupTestNode(Whisper)
var node2 = setupTestNode(Whisper) var node2 = setupTestNode(Whisper)
node2.startListening() node2.startListening()
waitFor node1.peerPool.connectToNode(newNode(initENode(node2.keys.pubKey, waitFor node1.peerPool.connectToNode(newNode(node2.toENode()))
node2.address)))
asyncTest "Two peers connected": asyncTest "Two peers connected":
check: check:
node1.peerPool.connectedNodes.len() == 1 node1.peerPool.connectedNodes.len() == 1
@ -298,8 +297,7 @@ procSuite "Whisper connections":
var ln1 = setupTestNode(Whisper) var ln1 = setupTestNode(Whisper)
ln1.setLightNode(true) ln1.setLightNode(true)
await ln1.peerPool.connectToNode(newNode(initENode(node2.keys.pubKey, await ln1.peerPool.connectToNode(newNode(node2.toENode()))
node2.address)))
let topic = [byte 0, 0, 0, 0] let topic = [byte 0, 0, 0, 0]
@ -322,6 +320,5 @@ procSuite "Whisper connections":
ln2.setLightNode(true) ln2.setLightNode(true)
ln2.startListening() ln2.startListening()
let peer = await ln1.rlpxConnect(newNode(initENode(ln2.keys.pubKey, let peer = await ln1.rlpxConnect(newNode(ln2.toENode()))
ln2.address)))
check peer.isNil == true check peer.isNil == true

View File

@ -26,8 +26,7 @@ procSuite "Waku - Whisper bridge tests":
nodeWaku = setupTestNode(Waku) nodeWaku = setupTestNode(Waku)
nodeWakuWhisper.startListening() nodeWakuWhisper.startListening()
let bridgeNode = newNode(initENode(nodeWakuWhisper.keys.pubKey, let bridgeNode = newNode(nodeWakuWhisper.toENode())
nodeWakuWhisper.address))
nodeWakuWhisper.shareMessageQueue() nodeWakuWhisper.shareMessageQueue()
waitFor nodeWhisper.peerPool.connectToNode(bridgeNode) waitFor nodeWhisper.peerPool.connectToNode(bridgeNode)

View File

@ -48,9 +48,9 @@ suite "Waku connections":
n3.startListening() n3.startListening()
let let
p1 = await n2.rlpxConnect(newNode(initENode(n1.keys.pubKey, n1.address))) p1 = await n2.rlpxConnect(newNode(n1.toENode()))
p2 = await n2.rlpxConnect(newNode(initENode(n3.keys.pubKey, n3.address))) p2 = await n2.rlpxConnect(newNode(n3.toENode()))
p3 = await n4.rlpxConnect(newNode(initENode(n3.keys.pubKey, n3.address))) p3 = await n4.rlpxConnect(newNode(n3.toENode()))
check: check:
p1.isNil p1.isNil
p2.isNil == false p2.isNil == false
@ -70,8 +70,7 @@ suite "Waku connections":
wakuTopicNode.protocolState(Waku).config.topics = some(@[topic1]) wakuTopicNode.protocolState(Waku).config.topics = some(@[topic1])
wakuNode.startListening() wakuNode.startListening()
await wakuTopicNode.peerPool.connectToNode(newNode( await wakuTopicNode.peerPool.connectToNode(newNode(wakuNode.toENode()))
initENode(wakuNode.keys.pubKey, wakuNode.address)))
# Update topic interest # Update topic interest
check: check:
@ -93,8 +92,7 @@ suite "Waku connections":
wakuNode = setupTestNode(Waku) wakuNode = setupTestNode(Waku)
wakuNode.startListening() wakuNode.startListening()
await wakuPowNode.peerPool.connectToNode(newNode( await wakuPowNode.peerPool.connectToNode(newNode(wakuNode.toENode()))
initENode(wakuNode.keys.pubKey, wakuNode.address)))
# Update minimum pow # Update minimum pow
await setPowRequirement(wakuPowNode, 1.0) await setPowRequirement(wakuPowNode, 1.0)
@ -114,8 +112,7 @@ suite "Waku connections":
wakuNode = setupTestNode(Waku) wakuNode = setupTestNode(Waku)
wakuNode.startListening() wakuNode.startListening()
await wakuLightNode.peerPool.connectToNode(newNode( await wakuLightNode.peerPool.connectToNode(newNode(wakuNode.toENode()))
initENode(wakuNode.keys.pubKey, wakuNode.address)))
# Update minimum pow # Update minimum pow
await setLightNode(wakuLightNode, true) await setLightNode(wakuLightNode, true)
@ -140,8 +137,7 @@ suite "Waku connections":
discard await wakuBloomNode.setTopicInterest(topics) discard await wakuBloomNode.setTopicInterest(topics)
wakuBloomNode.startListening() wakuBloomNode.startListening()
await wakuNode.peerPool.connectToNode(newNode( await wakuNode.peerPool.connectToNode(newNode(wakuBloomNode.toENode()))
initENode(wakuBloomNode.keys.pubKey, wakuBloomNode.address)))
# Sanity check # Sanity check
check: check:
@ -188,8 +184,7 @@ suite "Waku connections":
wakuTopicNode.protocolState(Waku).config.topics = some(@[topic1, topic2]) wakuTopicNode.protocolState(Waku).config.topics = some(@[topic1, topic2])
wakuNode.startListening() wakuNode.startListening()
await wakuTopicNode.peerPool.connectToNode(newNode( await wakuTopicNode.peerPool.connectToNode(newNode(wakuNode.toENode()))
initENode(wakuNode.keys.pubKey, wakuNode.address)))
let payload = repeat(byte 0, 10) let payload = repeat(byte 0, 10)
check: check:
@ -217,8 +212,7 @@ suite "Waku connections":
wakuTopicNode.protocolState(Waku).config.bloom = some(toBloom([bloomTopic])) wakuTopicNode.protocolState(Waku).config.bloom = some(toBloom([bloomTopic]))
wakuNode.startListening() wakuNode.startListening()
await wakuTopicNode.peerPool.connectToNode(newNode( await wakuTopicNode.peerPool.connectToNode(newNode(wakuNode.toENode()))
initENode(wakuNode.keys.pubKey, wakuNode.address)))
let payload = repeat(byte 0, 10) let payload = repeat(byte 0, 10)
check: check:
@ -235,8 +229,7 @@ suite "Waku connections":
await ln.setLightNode(true) await ln.setLightNode(true)
var fn = setupTestNode(Waku) var fn = setupTestNode(Waku)
fn.startListening() fn.startListening()
await ln.peerPool.connectToNode(newNode(initENode(fn.keys.pubKey, await ln.peerPool.connectToNode(newNode(fn.toENode()))
fn.address)))
let topic = [byte 0, 0, 0, 0] let topic = [byte 0, 0, 0, 0]

View File

@ -16,9 +16,8 @@ procSuite "Waku Mail Client":
var simpleServer = setupTestNode(Waku) var simpleServer = setupTestNode(Waku)
simpleServer.startListening() simpleServer.startListening()
let simpleServerNode = newNode(initENode(simpleServer.keys.pubKey, let simpleServerNode = newNode(simpleServer.toENode())
simpleServer.address)) let clientNode = newNode(client.toENode())
let clientNode = newNode(initENode(client.keys.pubKey, client.address))
waitFor client.peerPool.connectToNode(simpleServerNode) waitFor client.peerPool.connectToNode(simpleServerNode)
require: require:
waitFor simpleServer.waitForConnected().withTimeout(transmissionTimeout) waitFor simpleServer.waitForConnected().withTimeout(transmissionTimeout)