mirror of https://github.com/status-im/nim-eth.git
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:
parent
ac5bbe4d3d
commit
0b110f3287
|
@ -7,8 +7,12 @@
|
|||
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||
# MIT license (LICENSE-MIT)
|
||||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
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
|
||||
# Version 3 constants
|
||||
|
@ -21,31 +25,26 @@ const
|
|||
ScryptWorkFactor = 262_144
|
||||
|
||||
type
|
||||
KeyFileStatus* = enum
|
||||
Success, ## No Error
|
||||
RandomError, ## Random generator error
|
||||
UuidError, ## UUID generator error
|
||||
BufferOverrun, ## Supplied buffer is too small
|
||||
IncorrectDKLen, ## `dklen` parameter is 0 or more then MaxDKLen
|
||||
MalformedError, ## JSON has incorrect structure
|
||||
NotImplemented, ## Feature is not implemented
|
||||
NotSupported, ## Feature is not supported
|
||||
EmptyMac, ## `mac` parameter is zero length or not in
|
||||
## hexadecimal form
|
||||
EmptyCiphertext, ## `ciphertext` parameter is zero length or not in
|
||||
## hexadecimal format
|
||||
EmptySalt, ## `salt` parameter is zero length or not in
|
||||
## hexadecimal format
|
||||
EmptyIV, ## `cipherparams.iv` parameter is zero length or not in
|
||||
## hexadecimal format
|
||||
IncorrectIV, ## Size of IV vector is not equal to cipher block size
|
||||
PrfNotSupported, ## PRF algorithm for PBKDF2 is not supported
|
||||
KdfNotSupported, ## KDF algorithm is not supported
|
||||
CipherNotSupported, ## `cipher` parameter is not supported
|
||||
IncorrectMac, ## `mac` verification failed
|
||||
IncorrectPrivateKey, ## incorrect private key
|
||||
OsError, ## OS specific error
|
||||
JsonError ## JSON encoder/decoder error
|
||||
KeyFileError* = enum
|
||||
RandomError = "kf: Random generator error"
|
||||
UuidError = "kf: UUID generator error"
|
||||
BufferOverrun = "kf: Supplied buffer is too small"
|
||||
IncorrectDKLen = "kf: `dklen` parameter is 0 or more then MaxDKLen"
|
||||
MalformedError = "kf: JSON has incorrect structure"
|
||||
NotImplemented = "kf: Feature is not implemented"
|
||||
NotSupported = "kf: Feature is not supported"
|
||||
EmptyMac = "kf: `mac` parameter is zero length or not in hexadecimal form"
|
||||
EmptyCiphertext = "kf: `ciphertext` parameter is zero length or not in hexadecimal format"
|
||||
EmptySalt = "kf: `salt` parameter is zero length or not in hexadecimal format"
|
||||
EmptyIV = "kf: `cipherparams.iv` parameter is zero length or not in hexadecimal format"
|
||||
IncorrectIV = "kf: Size of IV vector is not equal to cipher block size"
|
||||
PrfNotSupported = "kf: PRF algorithm for PBKDF2 is not supported"
|
||||
KdfNotSupported = "kf: KDF algorithm is not supported"
|
||||
CipherNotSupported = "kf: `cipher` parameter is not supported"
|
||||
IncorrectMac = "kf: `mac` verification failed"
|
||||
IncorrectPrivateKey = "kf: incorrect private key"
|
||||
OsError = "kf: OS specific error"
|
||||
JsonError = "kf: JSON encoder/decoder error"
|
||||
|
||||
KdfKind* = enum
|
||||
PBKDF2, ## PBKDF2
|
||||
|
@ -60,6 +59,11 @@ type
|
|||
CipherNoSupport, ## Cipher not supported
|
||||
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
|
||||
SupportedHashes = [
|
||||
"sha224", "sha256", "sha384", "sha512",
|
||||
|
@ -109,103 +113,98 @@ proc deriveKey(password: string,
|
|||
salt: string,
|
||||
kdfkind: KdfKind,
|
||||
hashkind: HashKind,
|
||||
workfactor: int,
|
||||
output: var openarray[byte]): KeyFileStatus =
|
||||
if kdfkind == SCRYPT:
|
||||
return NotImplemented
|
||||
elif kdfkind == PBKDF2:
|
||||
workfactor: int): KfResult[array[DKLen, byte]] =
|
||||
if kdfkind == PBKDF2:
|
||||
var output: array[DKLen, byte]
|
||||
var c = if workfactor == 0: Pbkdf2WorkFactor else: workfactor
|
||||
case hashkind
|
||||
of HashSHA2_224:
|
||||
var ctx: HMAC[sha224]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
result = Success
|
||||
ok(output)
|
||||
of HashSHA2_256:
|
||||
var ctx: HMAC[sha256]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
result = Success
|
||||
ok(output)
|
||||
of HashSHA2_384:
|
||||
var ctx: HMAC[sha384]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
result = Success
|
||||
ok(output)
|
||||
of HashSHA2_512:
|
||||
var ctx: HMAC[sha512]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
result = Success
|
||||
ok(output)
|
||||
of HashKECCAK224:
|
||||
var ctx: HMAC[keccak224]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
result = Success
|
||||
ok(output)
|
||||
of HashKECCAK256:
|
||||
var ctx: HMAC[keccak256]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
result = Success
|
||||
ok(output)
|
||||
of HashKECCAK384:
|
||||
var ctx: HMAC[keccak384]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
result = Success
|
||||
ok(output)
|
||||
of HashKECCAK512:
|
||||
var ctx: HMAC[keccak512]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
result = Success
|
||||
ok(output)
|
||||
of HashSHA3_224:
|
||||
var ctx: HMAC[sha3_224]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
result = Success
|
||||
ok(output)
|
||||
of HashSHA3_256:
|
||||
var ctx: HMAC[sha3_256]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
result = Success
|
||||
ok(output)
|
||||
of HashSHA3_384:
|
||||
var ctx: HMAC[sha3_384]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
result = Success
|
||||
ok(output)
|
||||
of HashSHA3_512:
|
||||
var ctx: HMAC[sha3_512]
|
||||
discard ctx.pbkdf2(password, salt, c, output)
|
||||
result = Success
|
||||
ok(output)
|
||||
else:
|
||||
result = PrfNotSupported
|
||||
err(PrfNotSupported)
|
||||
else:
|
||||
err(NotImplemented)
|
||||
|
||||
proc encryptKey(seckey: PrivateKey,
|
||||
cryptkind: CryptKind,
|
||||
key: openarray[byte],
|
||||
iv: openarray[byte],
|
||||
crypttext: var openarray[byte]): KeyFileStatus =
|
||||
if len(crypttext) != KeyLength:
|
||||
return BufferOverrun
|
||||
iv: openarray[byte]): KfResult[array[KeyLength, byte]] =
|
||||
if cryptkind == AES128CTR:
|
||||
var crypttext: array[KeyLength, byte]
|
||||
var ctx: CTR[aes128]
|
||||
ctx.init(toOpenArray(key, 0, 15), iv)
|
||||
ctx.encrypt(seckey.toRaw(), crypttext)
|
||||
ctx.clear()
|
||||
result = Success
|
||||
ok(crypttext)
|
||||
else:
|
||||
result = NotImplemented
|
||||
err(NotImplemented)
|
||||
|
||||
proc decryptKey(ciphertext: openarray[byte],
|
||||
cryptkind: CryptKind,
|
||||
key: openarray[byte],
|
||||
iv: openarray[byte],
|
||||
plaintext: var openarray[byte]): KeyFileStatus =
|
||||
if len(ciphertext) != len(plaintext):
|
||||
return BufferOverrun
|
||||
iv: openarray[byte]): KfResult[array[KeyLength, byte]] =
|
||||
if cryptkind == AES128CTR:
|
||||
if len(iv) != aes128.sizeBlock:
|
||||
return IncorrectIV
|
||||
return err(IncorrectIV)
|
||||
var plaintext: array[KeyLength, byte]
|
||||
var ctx: CTR[aes128]
|
||||
ctx.init(toOpenArray(key, 0, 15), iv)
|
||||
ctx.decrypt(ciphertext, plaintext)
|
||||
ctx.clear()
|
||||
result = Success
|
||||
ok(plaintext)
|
||||
else:
|
||||
result = NotImplemented
|
||||
err(NotImplemented)
|
||||
|
||||
proc kdfParams(kdfkind: KdfKind, salt: string, workfactor: int,
|
||||
outjson: var JsonNode): KeyFileStatus =
|
||||
proc kdfParams(kdfkind: KdfKind, salt: string, workfactor: int): KfResult[JsonNode] =
|
||||
if kdfkind == SCRYPT:
|
||||
var wf = if workfactor == 0: ScryptWorkFactor else: workfactor
|
||||
outjson = %*
|
||||
let wf = if workfactor == 0: ScryptWorkFactor else: workfactor
|
||||
ok(%*
|
||||
{
|
||||
"dklen": DKLen,
|
||||
"n": wf,
|
||||
|
@ -213,19 +212,19 @@ proc kdfParams(kdfkind: KdfKind, salt: string, workfactor: int,
|
|||
"p": ScryptP,
|
||||
"salt": salt
|
||||
}
|
||||
result = Success
|
||||
)
|
||||
elif kdfkind == PBKDF2:
|
||||
var wf = if workfactor == 0: Pbkdf2WorkFactor else: workfactor
|
||||
outjson = %*
|
||||
let wf = if workfactor == 0: Pbkdf2WorkFactor else: workfactor
|
||||
ok(%*
|
||||
{
|
||||
"dklen": DKLen,
|
||||
"c": wf,
|
||||
"prf": "hmac-sha256",
|
||||
"salt": salt
|
||||
}
|
||||
result = Success
|
||||
)
|
||||
else:
|
||||
result = NotImplemented
|
||||
err(NotImplemented)
|
||||
|
||||
proc decodeHex(m: string): seq[byte] =
|
||||
if len(m) > 0:
|
||||
|
@ -254,11 +253,10 @@ proc compareMac(m1: openarray[byte], m2: openarray[byte]): bool =
|
|||
|
||||
proc createKeyFileJson*(seckey: PrivateKey,
|
||||
password: string,
|
||||
outjson: var JsonNode,
|
||||
version: int = 3,
|
||||
cryptkind: CryptKind = AES128CTR,
|
||||
kdfkind: KdfKind = PBKDF2,
|
||||
workfactor: int = 0): KeyFileStatus =
|
||||
workfactor: int = 0): KfResult[JsonNode] =
|
||||
## Create JSON object with keyfile structure.
|
||||
##
|
||||
## ``seckey`` - private key, which will be stored
|
||||
|
@ -270,30 +268,24 @@ proc createKeyFileJson*(seckey: PrivateKey,
|
|||
## ``kdfkind`` - algorithm for key deriviation function (default is PBKDF2)
|
||||
## ``workfactor`` - Key deriviation function work factor, 0 is to use
|
||||
## default workfactor.
|
||||
var res: KeyFileStatus
|
||||
var iv: array[aes128.sizeBlock, byte]
|
||||
var ciphertext: array[KeyLength, byte]
|
||||
var salt: array[SaltSize, byte]
|
||||
var saltstr = newString(SaltSize)
|
||||
var u: UUID
|
||||
if randomBytes(iv) != aes128.sizeBlock:
|
||||
return RandomError
|
||||
return err(RandomError)
|
||||
if randomBytes(salt) != SaltSize:
|
||||
return RandomError
|
||||
return err(RandomError)
|
||||
copyMem(addr saltstr[0], addr salt[0], SaltSize)
|
||||
if uuidGenerate(u) != 1:
|
||||
return UuidError
|
||||
if kdfkind != PBKDF2:
|
||||
return NotImplemented
|
||||
|
||||
var dkey = newSeq[byte](DKLen)
|
||||
res = deriveKey(password, saltstr, kdfkind, HashSHA2_256,
|
||||
workfactor, dkey)
|
||||
if res != Success:
|
||||
return res
|
||||
res = encryptKey(seckey, cryptkind, dkey, iv, ciphertext)
|
||||
if res != Success:
|
||||
return res
|
||||
let u = ? uuidGenerate().mapErrTo(UuidError)
|
||||
|
||||
if kdfkind != PBKDF2:
|
||||
return err(NotImplemented)
|
||||
|
||||
let
|
||||
dkey = ? deriveKey(password, saltstr, kdfkind, HashSHA2_256, workfactor)
|
||||
ciphertext = ? encryptKey(seckey, cryptkind, dkey, iv)
|
||||
|
||||
var ctx: keccak256
|
||||
ctx.init()
|
||||
ctx.update(toOpenArray(dkey, 16, 31))
|
||||
|
@ -301,14 +293,11 @@ proc createKeyFileJson*(seckey: PrivateKey,
|
|||
var mac = ctx.finish()
|
||||
ctx.clear()
|
||||
|
||||
var params: JsonNode
|
||||
res = kdfParams(kdfkind, toHex(salt, true), workfactor, params)
|
||||
if res != Success:
|
||||
return res
|
||||
let params = ? kdfParams(kdfkind, toHex(salt, true), workfactor)
|
||||
|
||||
outjson = %*
|
||||
ok(%*
|
||||
{
|
||||
"address": seckey.toPublicKey().tryGet().toAddress(false),
|
||||
"address": (? seckey.toPublicKey().mapErrTo(IncorrectPrivateKey)).toAddress(false),
|
||||
"crypto": {
|
||||
"cipher": $cryptkind,
|
||||
"cipherparams": {
|
||||
|
@ -322,34 +311,29 @@ proc createKeyFileJson*(seckey: PrivateKey,
|
|||
"id": $u,
|
||||
"version": version
|
||||
}
|
||||
result = Success
|
||||
)
|
||||
|
||||
proc decodeKeyFileJson*(j: JsonNode,
|
||||
password: string,
|
||||
seckey: var PrivateKey): KeyFileStatus =
|
||||
password: string): KfResult[PrivateKey] =
|
||||
## Decode private key into ``seckey`` from keyfile json object ``j`` using
|
||||
## password string ``password``.
|
||||
var
|
||||
res: KeyFileStatus
|
||||
plaintext: array[KeyLength, byte]
|
||||
|
||||
var crypto = j.getOrDefault("crypto")
|
||||
if isNil(crypto):
|
||||
return MalformedError
|
||||
return err(MalformedError)
|
||||
|
||||
var kdf = crypto.getOrDefault("kdf")
|
||||
if isNil(kdf):
|
||||
return MalformedError
|
||||
return err(MalformedError)
|
||||
|
||||
var cipherparams = crypto.getOrDefault("cipherparams")
|
||||
if isNil(cipherparams):
|
||||
return MalformedError
|
||||
return err(MalformedError)
|
||||
|
||||
if kdf.getStr() == "pbkdf2":
|
||||
var params = crypto.getOrDefault("kdfparams")
|
||||
|
||||
if isNil(params):
|
||||
return MalformedError
|
||||
return err(MalformedError)
|
||||
|
||||
var salt = decodeSalt(params.getOrDefault("salt").getStr())
|
||||
var ciphertext = decodeHex(crypto.getOrDefault("ciphertext").getStr())
|
||||
|
@ -358,29 +342,26 @@ proc decodeKeyFileJson*(j: JsonNode,
|
|||
var iv = decodeHex(cipherparams.getOrDefault("iv").getStr())
|
||||
|
||||
if len(salt) == 0:
|
||||
return EmptySalt
|
||||
return err(EmptySalt)
|
||||
if len(ciphertext) == 0:
|
||||
return EmptyCiphertext
|
||||
return err(EmptyCiphertext)
|
||||
if len(mactext) == 0:
|
||||
return EmptyMac
|
||||
return err(EmptyMac)
|
||||
if cryptkind == CipherNoSupport:
|
||||
return CipherNotSupported
|
||||
return err(CipherNotSupported)
|
||||
|
||||
var dklen = params.getOrDefault("dklen").getInt()
|
||||
var c = params.getOrDefault("c").getInt()
|
||||
var hash = getPrfHash(params.getOrDefault("prf").getStr())
|
||||
|
||||
if hash == HashNoSupport:
|
||||
return PrfNotSupported
|
||||
return err(PrfNotSupported)
|
||||
if dklen == 0 or dklen > MaxDKLen:
|
||||
return IncorrectDKLen
|
||||
return err(IncorrectDKLen)
|
||||
if len(ciphertext) != KeyLength:
|
||||
return IncorrectPrivateKey
|
||||
return err(IncorrectPrivateKey)
|
||||
|
||||
var dkey = newSeq[byte](dklen)
|
||||
res = deriveKey(password, salt, PBKDF2, hash, c, dkey)
|
||||
if res != Success:
|
||||
return res
|
||||
let dkey = ? deriveKey(password, salt, PBKDF2, hash, c)
|
||||
|
||||
var ctx: keccak256
|
||||
ctx.init()
|
||||
|
@ -388,51 +369,39 @@ proc decodeKeyFileJson*(j: JsonNode,
|
|||
ctx.update(ciphertext)
|
||||
var mac = ctx.finish()
|
||||
if not compareMac(mac.data, mactext):
|
||||
return IncorrectMac
|
||||
return err(IncorrectMac)
|
||||
|
||||
res = decryptKey(ciphertext, cryptkind, dkey, iv, plaintext)
|
||||
if res != Success:
|
||||
return res
|
||||
try:
|
||||
seckey = PrivateKey.fromRaw(plaintext).tryGet()
|
||||
except CatchableError:
|
||||
return IncorrectPrivateKey
|
||||
result = Success
|
||||
let plaintext = ? decryptKey(ciphertext, cryptkind, dkey, iv)
|
||||
|
||||
PrivateKey.fromRaw(plaintext).mapErrTo(IncorrectPrivateKey)
|
||||
else:
|
||||
return KdfNotSupported
|
||||
err(KdfNotSupported)
|
||||
|
||||
proc loadKeyFile*(pathname: string,
|
||||
password: string,
|
||||
seckey: var PrivateKey): KeyFileStatus =
|
||||
password: string): KfResult[PrivateKey] =
|
||||
## Load and decode private key ``seckey`` from file with pathname
|
||||
## ``pathname``, using password string ``password``.
|
||||
var data: JsonNode
|
||||
var stream = newFileStream(pathname)
|
||||
if isNil(stream):
|
||||
return OsError
|
||||
|
||||
try:
|
||||
data = parseFile(pathname)
|
||||
result = Success
|
||||
except CatchableError:
|
||||
result = JsonError
|
||||
finally:
|
||||
stream.close()
|
||||
data = json.parseFile(pathname)
|
||||
except JsonParsingError:
|
||||
return err(JsonError)
|
||||
except Exception: # json raises Exception
|
||||
return err(OsError)
|
||||
|
||||
if result == Success:
|
||||
result = decodeKeyFileJson(data, password, seckey)
|
||||
decodeKeyFileJson(data, password)
|
||||
|
||||
proc saveKeyFile*(pathname: string,
|
||||
jobject: JsonNode): KeyFileStatus =
|
||||
jobject: JsonNode): KfResult[void] =
|
||||
## Save JSON object ``jobject`` to file with pathname ``pathname``.
|
||||
var
|
||||
f: File
|
||||
if not f.open(pathname, fmWrite):
|
||||
return OsError
|
||||
return err(OsError)
|
||||
try:
|
||||
f.write($jobject)
|
||||
result = Success
|
||||
ok()
|
||||
except CatchableError:
|
||||
result = OsError
|
||||
err(OsError)
|
||||
finally:
|
||||
f.close()
|
||||
|
|
|
@ -16,40 +16,47 @@
|
|||
## - ``FreeBSD``, ``OpenBSD``, ``NetBSD``,
|
||||
## ``DragonflyBSD`` - using `uuid_create()`.
|
||||
|
||||
import nimcrypto/utils, stew/endians2
|
||||
{.push raises: [Defect].}
|
||||
|
||||
import stew/[byteutils, endians2, result]
|
||||
|
||||
from nimcrypto import stripSpaces
|
||||
|
||||
export result
|
||||
|
||||
type
|
||||
UUIDException = object of CatchableError
|
||||
|
||||
UUID* = object
|
||||
## Represents UUID object
|
||||
data*: array[16, byte]
|
||||
|
||||
proc raiseInvalidUuid() =
|
||||
raise newException(UUIDException, "Invalid UUID!")
|
||||
UuidResult*[T] = Result[T, cstring]
|
||||
|
||||
proc uuidFromString*(s: string): UUID =
|
||||
proc uuidFromString*(s: string): UuidResult[UUID] =
|
||||
## Convert string representation of UUID into UUID object.
|
||||
if len(s) != 36:
|
||||
raiseInvalidUuid()
|
||||
return err("uuid: length must be 36 bytes")
|
||||
for i in 0..<len(s):
|
||||
if s[i] notin {'A'..'F', '0'..'9', 'a'..'f', '-'}:
|
||||
raiseInvalidUuid()
|
||||
var d = fromHex(stripSpaces(s))
|
||||
return err("uuid: invalid characters")
|
||||
try:
|
||||
var d = hexToSeqByte(stripSpaces(s))
|
||||
var
|
||||
a = uint32.fromBytesBE(d.toOpenArray(0, 3))
|
||||
b = uint16.fromBytesBE(d.toOpenArray(4, 5))
|
||||
c = uint16.fromBytesBE(d.toOpenArray(6, 7))
|
||||
|
||||
copyMem(addr result.data[0], addr a, 4)
|
||||
copyMem(addr result.data[4], addr b, 2)
|
||||
copyMem(addr result.data[6], addr c, 2)
|
||||
copyMem(addr result.data[8], addr d[8], 8)
|
||||
var ret: UUID
|
||||
copyMem(addr ret.data[0], addr a, 4)
|
||||
copyMem(addr ret.data[4], addr b, 2)
|
||||
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.
|
||||
## You can use ``lowercase`` flag to specify letter case
|
||||
## of output string.
|
||||
## UUID are lowercase, per RFC4122
|
||||
result = newStringOfCap(38)
|
||||
var d: array[8, byte]
|
||||
var
|
||||
|
@ -60,22 +67,22 @@ proc uuidToString*(u: UUID, lowercase: bool = false): string =
|
|||
copyMem(addr d[4], addr b, 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(toHex(toOpenArray(d, 4, 5), lowercase))
|
||||
result.add(toHex(toOpenArray(d, 4, 5)))
|
||||
result.add("-")
|
||||
result.add(toHex(toOpenArray(d, 6, 7), lowercase))
|
||||
result.add(toHex(toOpenArray(d, 6, 7)))
|
||||
result.add("-")
|
||||
result.add(toHex(toOpenArray(u.data, 8, 9), lowercase))
|
||||
result.add(toHex(toOpenArray(u.data, 8, 9)))
|
||||
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.
|
||||
uuidToString(u, true)
|
||||
uuidToString(u)
|
||||
|
||||
when defined(nimdoc):
|
||||
proc uuidGenerate*(output: var UUID): int
|
||||
proc uuidGenerate*(): UuidResult[UUID]
|
||||
## Generates new unique UUID and store it to `output`.
|
||||
##
|
||||
## Return 1 on success, and 0 on failure
|
||||
|
@ -85,9 +92,10 @@ when defined(posix):
|
|||
proc uuidGenerateRandom(a: pointer)
|
||||
{.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))
|
||||
result = 1
|
||||
ok(output)
|
||||
|
||||
elif defined(freebsd) or defined(netbsd) or defined(openbsd) or
|
||||
defined(dragonflybsd):
|
||||
|
@ -95,13 +103,14 @@ when defined(posix):
|
|||
proc uuidCreate(a: pointer, s: ptr uint32)
|
||||
{.importc: "uuid_create", header: "uuid.h".}
|
||||
|
||||
proc uuidGenerate*(output: var UUID): int =
|
||||
proc uuidGenerate*(): UuidResult[UUID] =
|
||||
var status: uint32 = 0
|
||||
var output: UUID
|
||||
uuidCreate(cast[pointer](addr output), addr status)
|
||||
if status == 0:
|
||||
result = 1
|
||||
ok(output)
|
||||
else:
|
||||
result = 0
|
||||
err("uuid: uuid_create failed")
|
||||
|
||||
elif defined(linux) or defined(android):
|
||||
import posix, os, nimcrypto/sysrand
|
||||
|
@ -124,39 +133,44 @@ when defined(posix):
|
|||
break
|
||||
discard posix.close(fd)
|
||||
|
||||
proc uuidGenerate*(output: var UUID): int =
|
||||
result = 0
|
||||
proc uuidGenerate*(): UuidResult[UUID] =
|
||||
var buffer = newString(37)
|
||||
if uuidRead(buffer, 36) == 36:
|
||||
buffer.setLen(36)
|
||||
output = uuidFromString(buffer)
|
||||
result = 1
|
||||
uuidFromString(buffer)
|
||||
else:
|
||||
var output: UUID
|
||||
if randomBytes(output.data) == sizeof(output.data):
|
||||
result = 1
|
||||
ok(output)
|
||||
else:
|
||||
err("uuid: cannot get random bytes")
|
||||
|
||||
else:
|
||||
import nimcrypto/sysrand
|
||||
|
||||
proc uuidGenerate*(output: var UUID): int =
|
||||
proc uuidGenerate*(): UuidResult[UUID] =
|
||||
var output: UUID
|
||||
if randomBytes(output.data) == sizeof(output.data):
|
||||
result = 1
|
||||
ok(output)
|
||||
else:
|
||||
result = 0
|
||||
err("uuid: cannot get random bytes")
|
||||
|
||||
elif defined(windows):
|
||||
proc UuidCreate(p: pointer): int32
|
||||
{.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:
|
||||
return 1
|
||||
ok(output)
|
||||
else:
|
||||
return 0
|
||||
err("uuid: UuidCreate failed")
|
||||
elif not defined(nimdoc):
|
||||
import nimcrypto/sysrand
|
||||
|
||||
proc uuidGenerate*(output: var UUID): int =
|
||||
proc uuidGenerate*(): UuidResult[UUID] =
|
||||
var output: UUID
|
||||
if randomBytes(output.data) == sizeof(output.data):
|
||||
result = 1
|
||||
ok(output)
|
||||
else:
|
||||
result = 0
|
||||
err("uuid: cannot get random bytes")
|
||||
|
|
|
@ -74,7 +74,7 @@ proc processIncoming(server: StreamServer,
|
|||
node.peerPool.addPeer(peer)
|
||||
|
||||
proc listeningAddress*(node: EthereumNode): ENode =
|
||||
return initENode(node.keys.pubKey, node.address)
|
||||
node.toENode()
|
||||
|
||||
proc startListening*(node: EthereumNode) =
|
||||
# TODO allow binding to specific IP / IPv6 / etc
|
||||
|
|
459
eth/p2p/auth.nim
459
eth/p2p/auth.nim
|
@ -10,12 +10,16 @@
|
|||
|
||||
## This module implements Ethereum authentication
|
||||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
import eth/[keys, rlp], nimcrypto
|
||||
import ecies
|
||||
import stew/[byteutils, endians2]
|
||||
import stew/[byteutils, endians2, result]
|
||||
|
||||
export result
|
||||
|
||||
const
|
||||
SupportedRlpxVersion* = 4
|
||||
SupportedRlpxVersion* = 4'u8
|
||||
PlainAuthMessageV4Length* = 194
|
||||
AuthMessageV4Length* = 307
|
||||
PlainAuthMessageEIP8Length = 169
|
||||
|
@ -49,18 +53,17 @@ type
|
|||
Responder, ## `Handshake` owner is connection responder
|
||||
Eip8 ## Flag indicates that EIP-8 handshake is used
|
||||
|
||||
AuthStatus* = enum
|
||||
Success, ## Operation was successful
|
||||
RandomError, ## Could not obtain random data
|
||||
EcdhError, ## ECDH shared secret could not be calculated
|
||||
BufferOverrun, ## Buffer overrun error
|
||||
SignatureError, ## Signature could not be obtained
|
||||
EciesError, ## ECIES encryption/decryption error
|
||||
InvalidPubKey, ## Invalid public key
|
||||
InvalidAuth, ## Invalid Authentication message
|
||||
InvalidAck, ## Invalid Authentication ACK message
|
||||
RlpError, ## Error while decoding RLP stream
|
||||
IncompleteError ## Data incomplete error
|
||||
AuthError* = enum
|
||||
RandomError = "auth: could not obtain random data"
|
||||
EcdhError = "auth: ECDH shared secret could not be calculated"
|
||||
BufferOverrun = "auth: buffer overrun"
|
||||
SignatureError = "auth: signature could not be obtained"
|
||||
EciesError = "auth: ECIES encryption/decryption error"
|
||||
InvalidPubKey = "auth: invalid public key"
|
||||
InvalidAuth = "auth: invalid Authentication message"
|
||||
InvalidAck = "auth: invalid Authentication ACK message"
|
||||
RlpError = "auth: error while decoding RLP stream"
|
||||
IncompleteError = "auth: data incomplete"
|
||||
|
||||
Handshake* = object
|
||||
version*: uint8 ## protocol version
|
||||
|
@ -79,80 +82,94 @@ type
|
|||
egressMac*: keccak256
|
||||
ingressMac*: keccak256
|
||||
|
||||
AuthException* = object of CatchableError
|
||||
AuthResult*[T] = Result[T, AuthError]
|
||||
|
||||
template toa(a, b, c: untyped): untyped =
|
||||
toOpenArray((a), (b), (b) + (c) - 1)
|
||||
|
||||
proc sxor[T](a: var openarray[T], b: openarray[T]) {.inline.} =
|
||||
doAssert(len(a) == len(b))
|
||||
proc `xor`[N: static int](a, b: array[N, byte]): array[N, byte] =
|
||||
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},
|
||||
version: int = SupportedRlpxVersion): Handshake =
|
||||
proc mapErrTo[T, E](r: Result[T, E], v: static AuthError): AuthResult[T] =
|
||||
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.
|
||||
result.version = byte(version and 0xFF)
|
||||
result.flags = flags
|
||||
result.ephemeral = KeyPair.random().tryGet()
|
||||
|
||||
var
|
||||
initiatorNonce: Nonce
|
||||
responderNonce: Nonce
|
||||
expectedLength: int
|
||||
ephemeral = ? KeyPair.random().mapErrTo(RandomError)
|
||||
|
||||
if Initiator in flags:
|
||||
result.expectedLength = AckMessageV4Length
|
||||
if randomBytes(result.initiatorNonce) != len(result.initiatorNonce):
|
||||
raise newException(AuthException, "Could not obtain random data!")
|
||||
expectedLength = AckMessageV4Length
|
||||
if randomBytes(initiatorNonce) != len(initiatorNonce):
|
||||
return err(RandomError)
|
||||
else:
|
||||
result.expectedLength = AuthMessageV4Length
|
||||
if randomBytes(result.responderNonce) != len(result.responderNonce):
|
||||
raise newException(AuthException, "Could not obtain random data!")
|
||||
expectedLength = AuthMessageV4Length
|
||||
if randomBytes(responderNonce) != len(responderNonce):
|
||||
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,
|
||||
pubkey: PublicKey,
|
||||
output: var openarray[byte],
|
||||
outlen: var int,
|
||||
flag: int = 0,
|
||||
encrypt: bool = true): AuthStatus =
|
||||
flag: byte = 0,
|
||||
encrypt: bool = true): AuthResult[void] =
|
||||
## Create plain pre-EIP8 authentication message.
|
||||
var
|
||||
buffer: array[PlainAuthMessageV4Length, byte]
|
||||
flagb: byte
|
||||
header: ptr AuthMessageV4
|
||||
outlen = 0
|
||||
flagb = byte(flag)
|
||||
header = cast[ptr AuthMessageV4](addr buffer[0])
|
||||
var secret = ecdhRaw(h.host.seckey, pubkey)
|
||||
if secret.isErr:
|
||||
return(EcdhError)
|
||||
var xornonce = h.initiatorNonce
|
||||
xornonce.sxor(secret[].data)
|
||||
secret[].clear()
|
||||
let sig = sign(h.ephemeral.seckey, SkMessage(data: xornonce))
|
||||
if sig.isErr:
|
||||
return(SignatureError)
|
||||
let header = cast[ptr AuthMessageV4](addr buffer[0])
|
||||
|
||||
var secret = ? ecdhRaw(h.host.seckey, pubkey).mapErrTo(EcdhError)
|
||||
let xornonce = secret.data xor h.initiatorNonce
|
||||
|
||||
secret.clear()
|
||||
|
||||
let signature = ? sign(
|
||||
h.ephemeral.seckey, SkMessage(data: xornonce)).mapErrTo(SignatureError)
|
||||
|
||||
h.remoteHPubkey = pubkey
|
||||
header.signature = sig[].toRaw()
|
||||
header.signature = signature.toRaw()
|
||||
header.keyhash = keccak256.digest(h.ephemeral.pubkey.toRaw()).data
|
||||
header.pubkey = h.host.pubkey.toRaw()
|
||||
header.nonce = h.initiatorNonce
|
||||
header.flag = flagb
|
||||
header.flag = flag
|
||||
if encrypt:
|
||||
if len(output) < AuthMessageV4Length:
|
||||
return(BufferOverrun)
|
||||
if eciesEncrypt(buffer, output, h.remoteHPubkey) != EciesStatus.Success:
|
||||
return(EciesError)
|
||||
return err(BufferOverrun)
|
||||
if eciesEncrypt(buffer, output, h.remoteHPubkey).isErr:
|
||||
return err(EciesError)
|
||||
outlen = AuthMessageV4Length
|
||||
result = Success
|
||||
else:
|
||||
if len(output) < PlainAuthMessageV4Length:
|
||||
return(BufferOverrun)
|
||||
return err(BufferOverrun)
|
||||
copyMem(addr output[0], addr buffer[0], PlainAuthMessageV4Length)
|
||||
outlen = PlainAuthMessageV4Length
|
||||
result = Success
|
||||
|
||||
ok()
|
||||
|
||||
proc authMessageEIP8(h: var Handshake,
|
||||
pubkey: PublicKey,
|
||||
output: var openarray[byte],
|
||||
outlen: var int,
|
||||
flag: int = 0,
|
||||
encrypt: bool = true): AuthStatus =
|
||||
flag: byte = 0,
|
||||
encrypt: bool = true): AuthResult[void] =
|
||||
## Create EIP8 authentication message.
|
||||
var
|
||||
buffer: array[PlainAuthMessageMaxEIP8, byte]
|
||||
|
@ -160,17 +177,17 @@ proc authMessageEIP8(h: var Handshake,
|
|||
|
||||
doAssert(EIP8 in h.flags)
|
||||
outlen = 0
|
||||
var secret = ecdhRaw(h.host.seckey, pubkey)
|
||||
if secret.isErr:
|
||||
return(EcdhError)
|
||||
var xornonce = h.initiatorNonce
|
||||
xornonce.sxor(secret[].data)
|
||||
secret[].clear()
|
||||
var sig = sign(h.ephemeral.seckey, SkMessage(data: xornonce))
|
||||
if sig.isErr:
|
||||
return(SignatureError)
|
||||
var
|
||||
secret = ? ecdhRaw(h.host.seckey, pubkey).mapErrTo(EcdhError)
|
||||
xornonce = secret.data xor h.initiatorNonce
|
||||
|
||||
secret.clear()
|
||||
|
||||
let signature = ? sign(
|
||||
h.ephemeral.seckey, SkMessage(data: xornonce)).mapErrTo(SignatureError)
|
||||
|
||||
h.remoteHPubkey = pubkey
|
||||
var payload = rlp.encodeList(sig[].toRaw(),
|
||||
var payload = rlp.encodeList(signature.toRaw(),
|
||||
h.host.pubkey.toRaw(),
|
||||
h.initiatorNonce,
|
||||
[byte(h.version)])
|
||||
|
@ -178,65 +195,67 @@ proc authMessageEIP8(h: var Handshake,
|
|||
let pencsize = eciesEncryptedLength(len(payload))
|
||||
while true:
|
||||
if randomBytes(addr padsize, 1) != 1:
|
||||
return(RandomError)
|
||||
return err(RandomError)
|
||||
if int(padsize) > (AuthMessageV4Length - (pencsize + 2)):
|
||||
break
|
||||
# It is possible to make packet size constant by uncommenting this line
|
||||
# padsize = 24
|
||||
var wosize = pencsize + int(padsize)
|
||||
let wosize = pencsize + int(padsize)
|
||||
let fullsize = wosize + 2
|
||||
if randomBytes(toa(buffer, PlainAuthMessageEIP8Length,
|
||||
int(padsize))) != int(padsize):
|
||||
return(RandomError)
|
||||
return err(RandomError)
|
||||
if encrypt:
|
||||
copyMem(addr buffer[0], addr payload[0], len(payload))
|
||||
if len(output) < fullsize:
|
||||
return(BufferOverrun)
|
||||
return err(BufferOverrun)
|
||||
let wosizeBE = uint16(wosize).toBytesBE()
|
||||
output[0..<2] = wosizeBE
|
||||
if eciesEncrypt(toa(buffer, 0, len(payload) + int(padsize)),
|
||||
toa(output, 2, wosize), pubkey,
|
||||
toa(output, 0, 2)) != EciesStatus.Success:
|
||||
return(EciesError)
|
||||
toa(output, 0, 2)).isErr:
|
||||
return err(EciesError)
|
||||
outlen = fullsize
|
||||
else:
|
||||
let plainsize = len(payload) + int(padsize)
|
||||
if len(output) < plainsize:
|
||||
return(BufferOverrun)
|
||||
return err(BufferOverrun)
|
||||
copyMem(addr output[0], addr buffer[0], plainsize)
|
||||
outlen = plainsize
|
||||
result = Success
|
||||
|
||||
ok()
|
||||
|
||||
proc ackMessagePreEIP8(h: var Handshake,
|
||||
output: var openarray[byte],
|
||||
outlen: var int,
|
||||
flag: int = 0,
|
||||
encrypt: bool = true): AuthStatus =
|
||||
flag: byte = 0,
|
||||
encrypt: bool = true): AuthResult[void] =
|
||||
## Create plain pre-EIP8 authentication ack message.
|
||||
var buffer: array[PlainAckMessageV4Length, byte]
|
||||
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.nonce = h.responderNonce
|
||||
header.flag = byte(flag)
|
||||
header.flag = flag
|
||||
if encrypt:
|
||||
if len(output) < AckMessageV4Length:
|
||||
return(BufferOverrun)
|
||||
if eciesEncrypt(buffer, output, h.remoteHPubkey) != EciesStatus.Success:
|
||||
return(EciesError)
|
||||
return err(BufferOverrun)
|
||||
if eciesEncrypt(buffer, output, h.remoteHPubkey).isErr:
|
||||
return err(EciesError)
|
||||
outlen = AckMessageV4Length
|
||||
else:
|
||||
if len(output) < PlainAckMessageV4Length:
|
||||
return(BufferOverrun)
|
||||
return err(BufferOverrun)
|
||||
copyMem(addr output[0], addr buffer[0], PlainAckMessageV4Length)
|
||||
outlen = PlainAckMessageV4Length
|
||||
result = Success
|
||||
|
||||
ok()
|
||||
|
||||
proc ackMessageEIP8(h: var Handshake,
|
||||
output: var openarray[byte],
|
||||
outlen: var int,
|
||||
flag: int = 0,
|
||||
encrypt: bool = true): AuthStatus =
|
||||
flag: byte = 0,
|
||||
encrypt: bool = true): AuthResult[void] =
|
||||
## Create EIP8 authentication ack message.
|
||||
var
|
||||
buffer: array[PlainAckMessageMaxEIP8, byte]
|
||||
|
@ -250,34 +269,35 @@ proc ackMessageEIP8(h: var Handshake,
|
|||
let pencsize = eciesEncryptedLength(len(payload))
|
||||
while true:
|
||||
if randomBytes(addr padsize, 1) != 1:
|
||||
return(RandomError)
|
||||
return err(RandomError)
|
||||
if int(padsize) > (AckMessageV4Length - (pencsize + 2)):
|
||||
break
|
||||
# It is possible to make packet size constant by uncommenting this line
|
||||
# padsize = 0
|
||||
var wosize = pencsize + int(padsize)
|
||||
let wosize = pencsize + int(padsize)
|
||||
let fullsize = wosize + 2
|
||||
if int(padsize) > 0:
|
||||
if randomBytes(toa(buffer, PlainAckMessageEIP8Length,
|
||||
int(padsize))) != int(padsize):
|
||||
return(RandomError)
|
||||
return err(RandomError)
|
||||
copyMem(addr buffer[0], addr payload[0], len(payload))
|
||||
if encrypt:
|
||||
if len(output) < fullsize:
|
||||
return(BufferOverrun)
|
||||
return err(BufferOverrun)
|
||||
output[0..<2] = uint16(wosize).toBytesBE()
|
||||
if eciesEncrypt(toa(buffer, 0, len(payload) + int(padsize)),
|
||||
toa(output, 2, wosize), h.remoteHPubkey,
|
||||
toa(output, 0, 2)) != EciesStatus.Success:
|
||||
return(EciesError)
|
||||
toa(output, 0, 2)).isErr:
|
||||
return err(EciesError)
|
||||
outlen = fullsize
|
||||
else:
|
||||
let plainsize = len(payload) + int(padsize)
|
||||
if len(output) < plainsize:
|
||||
return(BufferOverrun)
|
||||
return err(BufferOverrun)
|
||||
copyMem(addr output[0], addr buffer[0], plainsize)
|
||||
outlen = plainsize
|
||||
result = Success
|
||||
|
||||
ok()
|
||||
|
||||
template authSize*(h: Handshake, encrypt: bool = true): int =
|
||||
## 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,
|
||||
output: var openarray[byte],
|
||||
outlen: var int, flag: int = 0,
|
||||
encrypt: bool = true): AuthStatus {.inline.} =
|
||||
outlen: var int, flag: byte = 0,
|
||||
encrypt: bool = true): AuthResult[void] =
|
||||
## Create new AuthMessage for specified `pubkey` and store it inside
|
||||
## of `output`, size of generated AuthMessage will stored in `outlen`.
|
||||
if EIP8 in h.flags:
|
||||
result = authMessageEIP8(h, pubkey, output, outlen, flag, encrypt)
|
||||
authMessageEIP8(h, pubkey, output, outlen, flag, encrypt)
|
||||
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],
|
||||
outlen: var int, flag: int = 0,
|
||||
encrypt: bool = true): AuthStatus =
|
||||
outlen: var int, flag: byte = 0,
|
||||
encrypt: bool = true): AuthResult[void] =
|
||||
## Create new AckMessage and store it inside of `output`, size of generated
|
||||
## AckMessage will stored in `outlen`.
|
||||
if EIP8 in h.flags:
|
||||
result = ackMessageEIP8(h, output, outlen, flag, encrypt)
|
||||
ackMessageEIP8(h, output, outlen, flag, encrypt)
|
||||
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.
|
||||
var
|
||||
buffer: array[PlainAuthMessageV4Length, byte]
|
||||
|
||||
doAssert(Responder in h.flags)
|
||||
if eciesDecrypt(m, buffer, h.host.seckey) != EciesStatus.Success:
|
||||
return(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
|
||||
if eciesDecrypt(m, buffer, h.host.seckey).isErr:
|
||||
return err(EciesError)
|
||||
|
||||
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.
|
||||
var
|
||||
nonce: Nonce
|
||||
|
@ -351,155 +368,138 @@ proc decodeAuthMessageEip8(h: var Handshake, m: openarray[byte]): AuthStatus =
|
|||
let size = uint16.fromBytesBE(m)
|
||||
h.expectedLength = int(size) + 2
|
||||
if h.expectedLength > len(m):
|
||||
return(IncompleteError)
|
||||
return err(IncompleteError)
|
||||
var buffer = newSeq[byte](eciesDecryptedLength(int(size)))
|
||||
if eciesDecrypt(toa(m, 2, int(size)), buffer, h.host.seckey,
|
||||
toa(m, 0, 2)) != EciesStatus.Success:
|
||||
return(EciesError)
|
||||
toa(m, 0, 2)).isErr:
|
||||
return err(EciesError)
|
||||
try:
|
||||
var reader = rlpFromBytes(buffer.toRange())
|
||||
if not reader.isList() or reader.listLen() < 4:
|
||||
return(InvalidAuth)
|
||||
return err(InvalidAuth)
|
||||
if reader.listElem(0).blobLen != RawSignatureSize:
|
||||
return(InvalidAuth)
|
||||
return err(InvalidAuth)
|
||||
if reader.listElem(1).blobLen != RawPublicKeySize:
|
||||
return(InvalidAuth)
|
||||
return err(InvalidAuth)
|
||||
if reader.listElem(2).blobLen != KeyLength:
|
||||
return(InvalidAuth)
|
||||
return err(InvalidAuth)
|
||||
if reader.listElem(3).blobLen != 1:
|
||||
return(InvalidAuth)
|
||||
return err(InvalidAuth)
|
||||
var signatureBr = reader.listElem(0).toBytes()
|
||||
var pubkeyBr = reader.listElem(1).toBytes()
|
||||
var nonceBr = reader.listElem(2).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.
|
||||
let size = uint16.fromBytesBE(m)
|
||||
|
||||
h.expectedLength = 2 + int(size)
|
||||
if h.expectedLength > len(m):
|
||||
return(IncompleteError)
|
||||
return err(IncompleteError)
|
||||
var buffer = newSeq[byte](eciesDecryptedLength(int(size)))
|
||||
if eciesDecrypt(toa(m, 2, int(size)), buffer, h.host.seckey,
|
||||
toa(m, 0, 2)) != EciesStatus.Success:
|
||||
return(EciesError)
|
||||
toa(m, 0, 2)).isErr:
|
||||
return err(EciesError)
|
||||
try:
|
||||
var reader = rlpFromBytes(buffer.toRange())
|
||||
if not reader.isList() or reader.listLen() < 3:
|
||||
return(InvalidAck)
|
||||
return err(InvalidAck)
|
||||
if reader.listElem(0).blobLen != RawPublicKeySize:
|
||||
return(InvalidAck)
|
||||
return err(InvalidAck)
|
||||
if reader.listElem(1).blobLen != KeyLength:
|
||||
return(InvalidAck)
|
||||
return err(InvalidAck)
|
||||
if reader.listElem(2).blobLen != 1:
|
||||
return(InvalidAck)
|
||||
return err(InvalidAck)
|
||||
let pubkeyBr = reader.listElem(0).toBytes()
|
||||
let nonceBr = reader.listElem(1).toBytes()
|
||||
let versionBr = reader.listElem(2).toBytes()
|
||||
let remoteEPubkey = PublicKey.fromRaw(pubkeyBr.toOpenArray())
|
||||
if remoteEPubkey.isErr:
|
||||
return(InvalidPubKey)
|
||||
h.remoteEPubkey = remoteEPubkey[]
|
||||
h.remoteEPubkey =
|
||||
? PublicKey.fromRaw(pubkeyBr.toOpenArray()).mapErrTo(InvalidPubKey)
|
||||
|
||||
copyMem(addr h.responderNonce[0], nonceBr.baseAddr, KeyLength)
|
||||
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.
|
||||
var
|
||||
buffer: array[PlainAckMessageV4Length, byte]
|
||||
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`.
|
||||
if len(input) < AuthMessageV4Length:
|
||||
result = 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)
|
||||
return err(IncompleteError)
|
||||
|
||||
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`.
|
||||
if len(input) < AckMessageV4Length:
|
||||
return(IncompleteError)
|
||||
elif len(input) == AckMessageV4Length:
|
||||
var res = h.decodeAckMessageV4(input)
|
||||
if res != Success:
|
||||
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)
|
||||
return err(IncompleteError)
|
||||
if len(input) == AckMessageV4Length:
|
||||
let res = h.decodeAckMessageV4(input)
|
||||
if res.isOk(): return res
|
||||
|
||||
proc getSecrets*(h: Handshake, authmsg: openarray[byte],
|
||||
ackmsg: openarray[byte],
|
||||
secret: var ConnectionSecret): AuthStatus =
|
||||
let res = h.decodeAckMessageEip8(input)
|
||||
if res.isOk(): h.flags.incl(EIP8)
|
||||
res
|
||||
|
||||
proc getSecrets*(
|
||||
h: Handshake, authmsg: openarray[byte],
|
||||
ackmsg: openarray[byte]): AuthResult[ConnectionSecret] =
|
||||
## Derive secrets from handshake `h` using encrypted AuthMessage `authmsg` and
|
||||
## encrypted AckMessage `ackmsg`.
|
||||
var
|
||||
ctx0: keccak256
|
||||
ctx1: keccak256
|
||||
mac1: MDigest[256]
|
||||
xornonce: Nonce
|
||||
secret: ConnectionSecret
|
||||
|
||||
# ecdhe-secret = ecdh.agree(ephemeral-privkey, remote-ephemeral-pubk)
|
||||
var shsec = ecdhRaw(h.ephemeral.seckey, h.remoteEPubkey)
|
||||
if shsec.isErr:
|
||||
return(EcdhError)
|
||||
var shsec = ? ecdhRaw(h.ephemeral.seckey, h.remoteEPubkey).mapErrTo(EcdhError)
|
||||
|
||||
# shared-secret = keccak(ecdhe-secret || keccak(nonce || initiator-nonce))
|
||||
ctx0.init()
|
||||
|
@ -508,36 +508,36 @@ proc getSecrets*(h: Handshake, authmsg: openarray[byte],
|
|||
ctx1.update(h.initiatorNonce)
|
||||
mac1 = ctx1.finish()
|
||||
ctx1.clear()
|
||||
ctx0.update(shsec[].data)
|
||||
ctx0.update(shsec.data)
|
||||
ctx0.update(mac1.data)
|
||||
mac1 = ctx0.finish()
|
||||
|
||||
# aes-secret = keccak(ecdhe-secret || shared-secret)
|
||||
ctx0.init()
|
||||
ctx0.update(shsec[].data)
|
||||
ctx0.update(shsec.data)
|
||||
ctx0.update(mac1.data)
|
||||
mac1 = ctx0.finish()
|
||||
|
||||
# mac-secret = keccak(ecdhe-secret || aes-secret)
|
||||
ctx0.init()
|
||||
ctx0.update(shsec[].data)
|
||||
ctx0.update(shsec.data)
|
||||
ctx0.update(mac1.data)
|
||||
secret.aesKey = mac1.data
|
||||
mac1 = ctx0.finish()
|
||||
secret.macKey = mac1.data
|
||||
|
||||
shsec[].clear()
|
||||
burnMem(shsec)
|
||||
|
||||
# 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.update(xornonce)
|
||||
ctx0.update(authmsg)
|
||||
|
||||
# ingress-mac = keccak256(mac-secret ^ initiator-nonce || auth-recvd-ack)
|
||||
xornonce = secret.macKey
|
||||
xornonce.sxor(h.initiatorNonce)
|
||||
xornonce = secret.macKey xor h.initiatorNonce
|
||||
|
||||
ctx1.init()
|
||||
ctx1.update(xornonce)
|
||||
ctx1.update(ackmsg)
|
||||
|
@ -552,4 +552,5 @@ proc getSecrets*(h: Handshake, authmsg: openarray[byte],
|
|||
|
||||
ctx0.clear()
|
||||
ctx1.clear()
|
||||
result = Success
|
||||
|
||||
ok(secret)
|
||||
|
|
|
@ -11,11 +11,12 @@
|
|||
import
|
||||
times,
|
||||
chronos, stint, nimcrypto, chronicles,
|
||||
eth/common/eth_types_json_serialization, eth/[keys, rlp],
|
||||
kademlia, enode
|
||||
eth/[keys, rlp],
|
||||
kademlia, enode,
|
||||
stew/result
|
||||
|
||||
export
|
||||
Node
|
||||
Node, result
|
||||
|
||||
logScope:
|
||||
topics = "discovery"
|
||||
|
@ -45,7 +46,8 @@ type
|
|||
|
||||
DiscProtocolError* = object of CatchableError
|
||||
|
||||
const MaxDgramSize = 1280
|
||||
DiscResult*[T] = Result[T, cstring]
|
||||
|
||||
const MinListLen: array[CommandId, int] = [4, 3, 2, 2]
|
||||
|
||||
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)
|
||||
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:
|
||||
msgHash.data[0 .. ^1] = msg.toOpenArray(0, msgHash.data.high)
|
||||
result = msgHash == keccak256.digest(msg.toOpenArray(MAC_SIZE, msg.high))
|
||||
var ret: MDigest[256]
|
||||
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 =
|
||||
if msg.len > HEAD_SIZE:
|
||||
let sig = Signature.fromRaw(msg.toOpenArray(MAC_SIZE, HEAD_SIZE))
|
||||
if sig.isOk():
|
||||
let pubkey = recover(sig[], msg.toOpenArray(HEAD_SIZE, msg.high))
|
||||
if pubkey.isOk():
|
||||
pk = pubkey[]
|
||||
return true
|
||||
proc recoverMsgPublicKey(msg: openArray[byte]): DiscResult[PublicKey] =
|
||||
if msg.len <= HEAD_SIZE:
|
||||
return err("disc: can't get public key")
|
||||
let sig = ? Signature.fromRaw(msg.toOpenArray(MAC_SIZE, HEAD_SIZE))
|
||||
recover(sig, msg.toOpenArray(HEAD_SIZE, msg.high))
|
||||
|
||||
proc unpack(msg: Bytes): tuple[cmdId: CommandId, payload: Bytes] =
|
||||
# Check against possible RangeError
|
||||
|
@ -231,17 +236,17 @@ proc expirationValid(cmdId: CommandId, rlpEncodedPayload: seq[byte]):
|
|||
proc receive*(d: DiscoveryProtocol, a: Address, msg: Bytes) {.gcsafe.} =
|
||||
## Can raise `DiscProtocolError` and all of `RlpError`
|
||||
# Note: export only needed for testing
|
||||
var msgHash: MDigest[256]
|
||||
if validateMsgHash(msg, msgHash):
|
||||
var remotePubkey: PublicKey
|
||||
if recoverMsgPublicKey(msg, remotePubkey):
|
||||
let msgHash = validateMsgHash(msg)
|
||||
if msgHash.isOk():
|
||||
let remotePubkey = recoverMsgPublicKey(msg)
|
||||
if remotePubkey.isOk:
|
||||
let (cmdId, payload) = unpack(msg)
|
||||
|
||||
if expirationValid(cmdId, payload):
|
||||
let node = newNode(remotePubkey, a)
|
||||
let node = newNode(remotePubkey[], a)
|
||||
case cmdId
|
||||
of cmdPing:
|
||||
d.recvPing(node, msgHash)
|
||||
d.recvPing(node, msgHash[])
|
||||
of cmdPong:
|
||||
d.recvPong(node, payload)
|
||||
of cmdNeighbours:
|
||||
|
@ -251,14 +256,13 @@ proc receive*(d: DiscoveryProtocol, a: Address, msg: Bytes) {.gcsafe.} =
|
|||
else:
|
||||
trace "Received msg already expired", cmdId, a
|
||||
else:
|
||||
error "Wrong public key from ", a
|
||||
notice "Wrong public key from ", a, err = remotePubkey.error
|
||||
else:
|
||||
error "Wrong msg mac from ", a
|
||||
notice "Wrong msg mac from ", a
|
||||
|
||||
proc processClient(transp: DatagramTransport,
|
||||
raddr: TransportAddress): Future[void] {.async, gcsafe.} =
|
||||
var proto = getUserData[DiscoveryProtocol](transp)
|
||||
var buf: seq[byte]
|
||||
try:
|
||||
# TODO: Maybe here better to use `peekMessage()` to avoid allocation,
|
||||
# but `Bytes` object is just a simple seq[byte], and `ByteRange` object
|
||||
|
@ -309,16 +313,14 @@ when isMainModule:
|
|||
|
||||
block:
|
||||
let m = hexToSeqByte"79664bff52ee17327b4a2d8f97d8fb32c9244d719e5038eb4f6b64da19ca6d271d659c3ad9ad7861a928ca85f8d8debfbe6b7ade26ad778f2ae2ba712567fcbd55bc09eb3e74a893d6b180370b266f6aaf3fe58a0ad95f7435bf3ddf1db940d20102f2cb842edbd4d182944382765da0ab56fb9e64a85a597e6bb27c656b4f1afb7e06b0fd4e41ccde6dba69a3c4a150845aaa4de2"
|
||||
var msgHash: MDigest[256]
|
||||
doAssert(validateMsgHash(m, msgHash))
|
||||
var remotePubkey: PublicKey
|
||||
doAssert(recoverMsgPublicKey(m, remotePubkey))
|
||||
discard validateMsgHash(m).expect("valid hash")
|
||||
var remotePubkey = recoverMsgPublicKey(m).expect("valid key")
|
||||
|
||||
let (cmdId, payload) = unpack(m)
|
||||
doAssert(payload == hexToSeqByte"f2cb842edbd4d182944382765da0ab56fb9e64a85a597e6bb27c656b4f1afb7e06b0fd4e41ccde6dba69a3c4a150845aaa4de2")
|
||||
doAssert(cmdId == cmdPong)
|
||||
doAssert(remotePubkey == PublicKey.fromHex(
|
||||
"78de8a0916848093c73790ead81d1928bec737d565119932b98c6b100d944b7a95e94f847f689fc723399d2e31129d182f7ef3863f2b4c820abbf3ab2722344d"))[]
|
||||
"78de8a0916848093c73790ead81d1928bec737d565119932b98c6b100d944b7a95e94f847f689fc723399d2e31129d182f7ef3863f2b4c820abbf3ab2722344d")[])
|
||||
|
||||
let privKey = PrivateKey.fromHex("a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a617")[]
|
||||
|
||||
|
@ -332,7 +334,7 @@ when isMainModule:
|
|||
|
||||
var bootnodes = newSeq[ENode]()
|
||||
for item in LOCAL_BOOTNODES:
|
||||
bootnodes.add(initENode(item))
|
||||
bootnodes.add(ENode.fromString(item)[])
|
||||
|
||||
let listenPort = Port(30310)
|
||||
var address = Address(udpPort: listenPort, tcpPort: listenPort)
|
||||
|
|
|
@ -2,6 +2,8 @@ import
|
|||
std/[tables, options], nimcrypto, stint, chronicles,
|
||||
types, node, enr, hkdf, ../enode, eth/[rlp, keys]
|
||||
|
||||
export keys
|
||||
|
||||
const
|
||||
idNoncePrefix = "discovery-id-nonce"
|
||||
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:
|
||||
resp.record = ln.record
|
||||
|
||||
let ephKey = PrivateKey.random().tryGet()
|
||||
let ephPubkey = ephKey.toPublicKey().tryGet().toRaw
|
||||
let ephKeys = KeyPair.random().tryGet()
|
||||
|
||||
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)
|
||||
|
||||
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 header = AuthHeader(auth: nonce, idNonce: challenge.idNonce,
|
||||
scheme: authSchemeName, ephemeralKey: ephPubkey, response: respEnc)
|
||||
scheme: authSchemeName, ephemeralKey: ephKeys.pubkey.toRaw, response: respEnc)
|
||||
rlp.encode(header)
|
||||
|
||||
proc `xor`[N: static[int], T](a, b: array[N, T]): array[N, T] =
|
||||
|
|
|
@ -6,6 +6,8 @@ import
|
|||
nimcrypto, stew/base64,
|
||||
eth/[rlp, keys], ../enode
|
||||
|
||||
export options
|
||||
|
||||
const
|
||||
maxEnrSize = 300
|
||||
minRlpListLen = 4 # for signature, seqId, "id" key, id
|
||||
|
@ -163,13 +165,12 @@ proc get*(r: Record, key: string, T: type): T =
|
|||
else:
|
||||
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
|
||||
if r.getField("secp256k1", pubkeyField) and pubkeyField.kind == kBytes:
|
||||
let pk = PublicKey.fromRaw(pubkeyField.bytes)
|
||||
if pk.isOk:
|
||||
pubKey = pk[]
|
||||
return true
|
||||
return some pk[]
|
||||
|
||||
proc tryGet*(r: Record, key: string, T: type): Option[T] =
|
||||
try:
|
||||
|
@ -197,12 +198,12 @@ proc toTypedRecord*(r: Record): Option[TypedRecord] =
|
|||
return some(tr)
|
||||
|
||||
proc verifySignatureV4(r: Record, sigData: openarray[byte], content: seq[byte]): bool =
|
||||
var publicKey: PublicKey
|
||||
if r.get(publicKey):
|
||||
let publicKey = r.get(PublicKey)
|
||||
if publicKey.isSome:
|
||||
let sig = SignatureNR.fromRaw(sigData)
|
||||
if sig.isOk:
|
||||
var h = keccak256.digest(content)
|
||||
return verify(sig[], h, publicKey)
|
||||
return verify(sig[], h, publicKey.get)
|
||||
|
||||
proc verifySignature(r: Record): bool =
|
||||
var rlp = rlpFromBytes(r.raw.toRange)
|
||||
|
|
|
@ -21,10 +21,10 @@ proc newNode*(enode: ENode, r: Record): Node =
|
|||
record: r)
|
||||
|
||||
proc newNode*(uriString: string): Node =
|
||||
newNode initENode(uriString)
|
||||
newNode ENode.fromString(uriString).tryGet()
|
||||
|
||||
proc newNode*(pk: PublicKey, address: Address): Node =
|
||||
newNode initENode(pk, address)
|
||||
newNode ENode(pubkey: pk, address: address)
|
||||
|
||||
proc newNode*(r: Record): Node =
|
||||
# TODO: Handle IPv6
|
||||
|
@ -48,7 +48,7 @@ proc newNode*(r: Record): Node =
|
|||
warn "Could not recover public key", err = pk.error
|
||||
return
|
||||
|
||||
result = newNode(initENode(pk[], a))
|
||||
result = newNode(ENode(pubkey: pk[], address: a))
|
||||
result.record = r
|
||||
|
||||
proc hash*(n: Node): hashes.Hash = hash(n.node.pubkey.toRaw)
|
||||
|
|
|
@ -480,7 +480,7 @@ proc newProtocol*(privKey: PrivateKey, db: Database,
|
|||
let
|
||||
a = Address(ip: externalIp.get(IPv4_any()),
|
||||
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)
|
||||
node = newNode(enode, enrRec)
|
||||
|
||||
|
|
|
@ -10,29 +10,37 @@
|
|||
|
||||
## This module implements ECIES method encryption/decryption.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
import eth/keys, nimcrypto/[rijndael, bcmode, hash, hmac, sysrand, sha2, utils]
|
||||
import stew/result
|
||||
|
||||
export result
|
||||
|
||||
const
|
||||
emptyMac* = array[0, byte]([])
|
||||
|
||||
type
|
||||
EciesException* = object of CatchableError
|
||||
EciesStatus* = enum
|
||||
Success, ## Operation was successful
|
||||
BufferOverrun, ## Output buffer size is too small
|
||||
RandomError, ## Could not obtain random data
|
||||
EcdhError, ## ECDH shared secret could not be calculated
|
||||
WrongHeader, ## ECIES header is incorrect
|
||||
IncorrectKey, ## Recovered public key is invalid
|
||||
IncorrectTag, ## ECIES tag verification failed
|
||||
IncompleteError ## Decryption needs more data
|
||||
EciesError* = enum
|
||||
BufferOverrun = "ecies: output buffer size is too small"
|
||||
RandomError = "ecies: could not obtain random data"
|
||||
EcdhError = "ecies: ECDH shared secret could not be calculated"
|
||||
WrongHeader = "ecies: header is incorrect"
|
||||
IncorrectKey = "ecies: recovered public key is invalid"
|
||||
IncorrectTag = "ecies: tag verification failed"
|
||||
IncompleteError = "ecies: decryption needs more data"
|
||||
|
||||
EciesHeader* = object {.packed.}
|
||||
EciesHeader* {.packed.} = object
|
||||
version*: byte
|
||||
pubkey*: array[RawPublicKeySize, byte]
|
||||
iv*: array[aes128.sizeBlock, 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 =
|
||||
## Return data overhead size for ECIES encrypted message
|
||||
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],
|
||||
pubkey: PublicKey,
|
||||
sharedmac: openarray[byte] = emptyMac): EciesStatus =
|
||||
sharedmac: openarray[byte] = emptyMac): EciesResult[void] =
|
||||
## Encrypt data with ECIES method using given public key `pubkey`.
|
||||
## ``input`` - input data
|
||||
## ``output`` - output data
|
||||
|
@ -99,33 +107,31 @@ proc eciesEncrypt*(input: openarray[byte], output: var openarray[byte],
|
|||
cipher: CTR[aes128]
|
||||
ctx: HMAC[sha256]
|
||||
iv: array[aes128.sizeBlock, byte]
|
||||
material: array[KeyLength, byte]
|
||||
|
||||
if len(output) < eciesEncryptedLength(len(input)):
|
||||
return(BufferOverrun)
|
||||
return err(BufferOverrun)
|
||||
if randomBytes(iv) != aes128.sizeBlock:
|
||||
return(RandomError)
|
||||
return err(RandomError)
|
||||
|
||||
var ephemeral = KeyPair.random()
|
||||
if ephemeral.isErr:
|
||||
return(RandomError)
|
||||
var
|
||||
ephemeral = ? KeyPair.random().mapErrTo(RandomError)
|
||||
secret = ? ecdhRaw(ephemeral.seckey, pubkey).mapErrTo(EcdhError)
|
||||
material = kdf(secret.data)
|
||||
|
||||
var secret = ecdhRaw(ephemeral[].seckey, pubkey)
|
||||
if secret.isErr:
|
||||
return(EcdhError)
|
||||
|
||||
material = kdf(secret[].data)
|
||||
burnMem(secret)
|
||||
clear(secret)
|
||||
|
||||
copyMem(addr encKey[0], addr material[0], aes128.sizeKey)
|
||||
|
||||
var macKey = sha256.digest(material, ostart = KeyLength div 2)
|
||||
burnMem(material)
|
||||
|
||||
var header = cast[ptr EciesHeader](addr output[0])
|
||||
header.version = 0x04
|
||||
header.pubkey = ephemeral[].pubkey.toRaw()
|
||||
header.pubkey = ephemeral.pubkey.toRaw()
|
||||
header.iv = iv
|
||||
|
||||
clear(ephemeral)
|
||||
|
||||
var so = eciesDataPos()
|
||||
var eo = so + len(input)
|
||||
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)
|
||||
ctx.clear()
|
||||
|
||||
result = Success
|
||||
ok()
|
||||
|
||||
proc eciesDecrypt*(input: openarray[byte],
|
||||
output: var openarray[byte],
|
||||
seckey: PrivateKey,
|
||||
sharedmac: openarray[byte] = emptyMac): EciesStatus =
|
||||
sharedmac: openarray[byte] = emptyMac): EciesResult[void] =
|
||||
## Decrypt data with ECIES method using given private key `seckey`.
|
||||
## ``input`` - input data
|
||||
## ``output`` - output data
|
||||
|
@ -165,24 +171,23 @@ proc eciesDecrypt*(input: openarray[byte],
|
|||
ctx: HMAC[sha256]
|
||||
|
||||
if len(input) <= 0:
|
||||
return(IncompleteError)
|
||||
return err(IncompleteError)
|
||||
|
||||
var header = cast[ptr EciesHeader](unsafeAddr input[0])
|
||||
if header.version != 0x04:
|
||||
return(WrongHeader)
|
||||
return err(WrongHeader)
|
||||
if len(input) <= eciesOverheadLength():
|
||||
return(IncompleteError)
|
||||
return err(IncompleteError)
|
||||
if len(input) - eciesOverheadLength() > len(output):
|
||||
return(BufferOverrun)
|
||||
let pubkey = PublicKey.fromRaw(header.pubkey)
|
||||
if pubkey.isErr:
|
||||
return(IncorrectKey)
|
||||
var secret = ecdhRaw(seckey, pubkey[])
|
||||
if secret.isErr:
|
||||
return(EcdhError)
|
||||
return err(BufferOverrun)
|
||||
|
||||
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)
|
||||
|
||||
copyMem(addr encKey[0], addr material[0], aes128.sizeKey)
|
||||
var macKey = sha256.digest(material, ostart = KeyLength div 2)
|
||||
burnMem(material)
|
||||
|
@ -198,7 +203,7 @@ proc eciesDecrypt*(input: openarray[byte],
|
|||
|
||||
if not equalMem(addr tag.data[0], unsafeAddr input[eciesMacPos(len(input))],
|
||||
sha256.sizeDigest):
|
||||
return(IncorrectTag)
|
||||
return err(IncorrectTag)
|
||||
|
||||
let datsize = eciesDecryptedLength(len(input))
|
||||
cipher.init(encKey, header.iv)
|
||||
|
@ -206,4 +211,5 @@ proc eciesDecrypt*(input: openarray[byte],
|
|||
cipher.decrypt(toOpenArray(input, eciesDataPos(),
|
||||
eciesDataPos() + datsize - 1), output)
|
||||
cipher.clear()
|
||||
result = Success
|
||||
|
||||
ok()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#
|
||||
# Ethereum P2P
|
||||
# (c) Copyright 2018
|
||||
# (c) Copyright 2018-2020
|
||||
# Status Research & Development GmbH
|
||||
#
|
||||
# Licensed under either of
|
||||
|
@ -8,22 +8,23 @@
|
|||
# MIT license (LICENSE-MIT)
|
||||
#
|
||||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
import uri, strutils, net
|
||||
import eth/keys
|
||||
|
||||
export keys
|
||||
|
||||
type
|
||||
ENodeStatus* = enum
|
||||
ENodeError* = enum
|
||||
## ENode status codes
|
||||
Success, ## Conversion operation succeed
|
||||
IncorrectNodeId, ## Incorrect public key supplied
|
||||
IncorrectScheme, ## Incorrect URI scheme supplied
|
||||
IncorrectIP, ## Incorrect IP address supplied
|
||||
IncorrectPort, ## Incorrect TCP port supplied
|
||||
IncorrectDiscPort, ## Incorrect UDP discovery port supplied
|
||||
IncorrectUri, ## Incorrect URI supplied
|
||||
IncompleteENode ## Incomplete ENODE object
|
||||
IncorrectNodeId = "enode: incorrect public key"
|
||||
IncorrectScheme = "enode: incorrect URI scheme"
|
||||
IncorrectIP = "enode: incorrect IP address"
|
||||
IncorrectPort = "enode: incorrect TCP port"
|
||||
IncorrectDiscPort = "enode: incorrect UDP discovery port"
|
||||
IncorrectUri = "enode: incorrect URI"
|
||||
IncompleteENode = "enode: incomplete ENODE object"
|
||||
|
||||
Address* = object
|
||||
## Network address object
|
||||
|
@ -36,25 +37,12 @@ type
|
|||
pubkey*: PublicKey ## Node public key
|
||||
address*: Address ## Node address
|
||||
|
||||
ENodeException* = object of CatchableError
|
||||
ENodeResult*[T] = Result[T, ENodeError]
|
||||
|
||||
proc raiseENodeError(status: ENodeStatus) =
|
||||
if status == IncorrectIP:
|
||||
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 mapErrTo[T, E](r: Result[T, E], v: static ENodeError): ENodeResult[T] =
|
||||
r.mapErr(proc (e: E): ENodeError = v)
|
||||
|
||||
proc initENode*(e: string, node: var ENode): ENodeStatus =
|
||||
proc fromString*(T: type ENode, e: string): ENodeResult[ENode] =
|
||||
## Initialize ENode ``node`` from URI string ``uri``.
|
||||
var
|
||||
uport: int = 0
|
||||
|
@ -62,83 +50,67 @@ proc initENode*(e: string, node: var ENode): ENodeStatus =
|
|||
uri: Uri = initUri()
|
||||
|
||||
if len(e) == 0:
|
||||
return IncorrectUri
|
||||
return err(IncorrectUri)
|
||||
|
||||
parseUri(e, uri)
|
||||
|
||||
if len(uri.scheme) == 0 or uri.scheme.toLowerAscii() != "enode":
|
||||
return IncorrectScheme
|
||||
return err(IncorrectScheme)
|
||||
|
||||
if len(uri.username) != 128:
|
||||
return IncorrectNodeId
|
||||
return err(IncorrectNodeId)
|
||||
|
||||
for i in uri.username:
|
||||
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:
|
||||
return IncorrectUri
|
||||
return err(IncorrectUri)
|
||||
|
||||
if len(uri.hostname) == 0:
|
||||
return IncorrectIP
|
||||
return err(IncorrectIP)
|
||||
|
||||
try:
|
||||
if len(uri.port) == 0:
|
||||
return IncorrectPort
|
||||
return err(IncorrectPort)
|
||||
tport = parseInt(uri.port)
|
||||
if tport <= 0 or tport > 65535:
|
||||
return IncorrectPort
|
||||
return err(IncorrectPort)
|
||||
except ValueError:
|
||||
return IncorrectPort
|
||||
return err(IncorrectPort)
|
||||
|
||||
if len(uri.query) > 0:
|
||||
if not uri.query.toLowerAscii().startsWith("discport="):
|
||||
return IncorrectDiscPort
|
||||
return err(IncorrectDiscPort)
|
||||
try:
|
||||
uport = parseInt(uri.query[9..^1])
|
||||
if uport <= 0 or uport > 65535:
|
||||
return IncorrectDiscPort
|
||||
return err(IncorrectDiscPort)
|
||||
except ValueError:
|
||||
return IncorrectDiscPort
|
||||
return err(IncorrectDiscPort)
|
||||
else:
|
||||
uport = tport
|
||||
|
||||
let pk = PublicKey.fromHex(uri.username)
|
||||
if pk.isErr:
|
||||
return IncorrectNodeId
|
||||
node.pubkey = pk[]
|
||||
|
||||
var ip: IpAddress
|
||||
try:
|
||||
node.address.ip = parseIpAddress(uri.hostname)
|
||||
ip = parseIpAddress(uri.hostname)
|
||||
except ValueError:
|
||||
zeroMem(addr node.pubkey, KeyLength * 2)
|
||||
return IncorrectIP
|
||||
return err(IncorrectIP)
|
||||
|
||||
node.address.tcpPort = Port(tport)
|
||||
node.address.udpPort = Port(uport)
|
||||
result = Success
|
||||
let pubkey = ? PublicKey.fromHex(uri.username).mapErrTo(IncorrectNodeId)
|
||||
|
||||
proc initENode*(uri: string): ENode {.inline.} =
|
||||
## Returns ENode object from URI string ``uri``.
|
||||
let res = initENode(uri, result)
|
||||
if res != Success:
|
||||
raiseENodeError(res)
|
||||
|
||||
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
|
||||
ok(ENode(
|
||||
pubkey: pubkey,
|
||||
address: Address(
|
||||
ip: ip,
|
||||
tcpPort: Port(tport),
|
||||
udpPort: Port(uport)
|
||||
)
|
||||
))
|
||||
|
||||
proc `$`*(n: ENode): string =
|
||||
## Returns string representation of ENode.
|
||||
var ipaddr: string
|
||||
if not isCorrect(n):
|
||||
raiseENodeError(IncompleteENode)
|
||||
if n.address.ip.family == IpAddressFamily.IPv4:
|
||||
ipaddr = $(n.address.ip)
|
||||
else:
|
||||
|
|
|
@ -55,12 +55,12 @@ proc toNodeId*(pk: PublicKey): NodeId =
|
|||
|
||||
proc newNode*(pk: PublicKey, address: Address): Node =
|
||||
result.new()
|
||||
result.node = initENode(pk, address)
|
||||
result.node = ENode(pubkey: pk, address: address)
|
||||
result.id = pk.toNodeId()
|
||||
|
||||
proc newNode*(uriString: string): Node =
|
||||
result.new()
|
||||
result.node = initENode(uriString)
|
||||
result.node = ENode.fromString(uriString)[]
|
||||
result.id = result.node.pubkey.toNodeId()
|
||||
|
||||
proc newNode*(enode: ENode): Node =
|
||||
|
|
|
@ -212,7 +212,5 @@ proc newMockPeer*(userConfigurator: proc (m: MockConf)): EthereumNode =
|
|||
return node
|
||||
|
||||
proc rlpxConnect*(node, otherNode: EthereumNode): Future[Peer] =
|
||||
let otherAsRemote = newNode(initENode(otherNode.keys.pubKey,
|
||||
otherNode.address))
|
||||
let otherAsRemote = newNode(otherNode.toENode())
|
||||
return rlpx.rlpxConnect(node, otherAsRemote)
|
||||
|
||||
|
|
|
@ -168,3 +168,5 @@ type
|
|||
|
||||
proc `$`*(peer: Peer): string = $peer.remote
|
||||
|
||||
proc toENode*(v: EthereumNode): ENode =
|
||||
ENode(pubkey: v.keys.pubkey, address: v.address)
|
||||
|
|
|
@ -390,7 +390,7 @@ proc recvMsg*(peer: Peer): Future[tuple[msgId: int, msgData: Rlp]] {.async.} =
|
|||
|
||||
var msgSize: int
|
||||
if decryptHeaderAndGetMsgSize(peer.secretsState,
|
||||
headerBytes, msgSize) != RlpxStatus.Success:
|
||||
headerBytes, msgSize).isErr():
|
||||
await peer.disconnectAndRaise(BreachOfProtocol,
|
||||
"Cannot decrypt RLPx frame header")
|
||||
|
||||
|
@ -414,7 +414,7 @@ proc recvMsg*(peer: Peer): Future[tuple[msgId: int, msgData: Rlp]] {.async.} =
|
|||
decryptedBytesCount = 0
|
||||
|
||||
if decryptBody(peer.secretsState, encryptedBytes, msgSize,
|
||||
decryptedBytes, decryptedBytesCount) != RlpxStatus.Success:
|
||||
decryptedBytes, decryptedBytesCount).isErr():
|
||||
await peer.disconnectAndRaise(BreachOfProtocol,
|
||||
"Cannot decrypt RLPx frame body")
|
||||
|
||||
|
@ -929,14 +929,9 @@ template `^`(arr): auto =
|
|||
# variable as an open array
|
||||
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],
|
||||
p: Peer) =
|
||||
var secrets: ConnectionSecret
|
||||
check hs.getSecrets(authMsg, ackMsg, secrets)
|
||||
var secrets = hs.getSecrets(authMsg, ackMsg).tryGet()
|
||||
initSecretState(secrets, p.secretsState)
|
||||
burnMem(secrets)
|
||||
|
||||
|
@ -975,12 +970,12 @@ proc rlpxConnect*(node: EthereumNode, remote: Node): Future[Peer] {.async.} =
|
|||
var ok = false
|
||||
try:
|
||||
result.transport = await connect(ta)
|
||||
var handshake = newHandshake({Initiator, EIP8}, int(node.baseProtocolVersion))
|
||||
handshake.host = node.keys
|
||||
var handshake = Handshake.tryInit(
|
||||
node.keys, {Initiator, EIP8}, node.baseProtocolVersion).tryGet()
|
||||
|
||||
var authMsg: array[AuthMessageMaxEIP8, byte]
|
||||
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)
|
||||
if res != authMsgLen:
|
||||
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))
|
||||
|
||||
var ret = handshake.decodeAckMessage(ackMsg)
|
||||
if ret == AuthStatus.IncompleteError:
|
||||
if ret.isErr and ret.error == AuthError.IncompleteError:
|
||||
ackMsg.setLen(handshake.expectedLength)
|
||||
await result.transport.readExactly(addr ackMsg[initialSize],
|
||||
len(ackMsg) - initialSize)
|
||||
ret = handshake.decodeAckMessage(ackMsg)
|
||||
check ret
|
||||
ret.tryGet() # for the raise!
|
||||
|
||||
node.checkSnappySupport(handshake, result)
|
||||
initSecretState(handshake, ^authMsg, ackMsg, result)
|
||||
|
@ -1062,8 +1057,7 @@ proc rlpxAccept*(node: EthereumNode,
|
|||
result.transport = transport
|
||||
result.network = node
|
||||
|
||||
var handshake = newHandshake({auth.Responder})
|
||||
handshake.host = node.keys
|
||||
var handshake = HandShake.tryInit(node.keys, {auth.Responder}).tryGet
|
||||
|
||||
var ok = false
|
||||
try:
|
||||
|
@ -1073,19 +1067,20 @@ proc rlpxAccept*(node: EthereumNode,
|
|||
authMsg.setLen(initialSize)
|
||||
await transport.readExactly(addr authMsg[0], len(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)
|
||||
await transport.readExactly(addr authMsg[initialSize],
|
||||
len(authMsg) - initialSize)
|
||||
ret = handshake.decodeAuthMessage(authMsg)
|
||||
check ret
|
||||
ret.tryGet() # for the raise!
|
||||
|
||||
node.checkSnappySupport(handshake, result)
|
||||
handshake.version = uint8(result.baseProtocolVersion)
|
||||
|
||||
var ackMsg: array[AckMessageMaxEIP8, byte]
|
||||
var ackMsgLen: int
|
||||
check handshake.ackMessage(ackMsg, ackMsgLen)
|
||||
handshake.ackMessage(ackMsg, ackMsgLen).tryGet()
|
||||
var res = await transport.write(addr ackMsg[0], ackMsgLen)
|
||||
if res != ackMsgLen:
|
||||
raisePeerDisconnected("Unexpected disconnect while authenticating",
|
||||
|
@ -1117,7 +1112,8 @@ proc rlpxAccept*(node: EthereumNode,
|
|||
let remote = transport.remoteAddress()
|
||||
let address = Address(ip: remote.address, tcpPort: 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,
|
||||
clientId = response.clientId
|
||||
|
|
|
@ -49,7 +49,7 @@ p2pProtocol Hive(version = hiveVersion,
|
|||
debug "Hive peer connected"
|
||||
|
||||
proc initProtocolState*(network: BzzNetwork, node: EthereumNode) {.gcsafe.} =
|
||||
network.thisENode = initENode(node.keys.pubkey, node.address)
|
||||
network.thisENode = node.toENode()
|
||||
|
||||
p2pProtocol Bzz(version = bzzVersion,
|
||||
rlpxName = "bzz",
|
||||
|
|
|
@ -266,8 +266,8 @@ p2pProtocol les(version = lesVersion,
|
|||
if signature.isNone:
|
||||
error "missing announce signature"
|
||||
return
|
||||
let sigMsg = rlp.encodeList(headHash, headNumber, headTotalDifficulty)
|
||||
let sig = Signature.fromRaw(signature.get).tryGet()
|
||||
let sigMsg = rlp.encodeList(headHash, headNumber, headTotalDifficulty)
|
||||
let signerKey = recover(sig, sigMsg).tryGet()
|
||||
if signerKey.toNodeId != peer.remote.id:
|
||||
error "invalid announce signature"
|
||||
|
|
|
@ -307,8 +307,8 @@ proc encode*(self: Payload): Option[Bytes] =
|
|||
if self.dst.isSome(): # Asymmetric key present - encryption requested
|
||||
var res = newSeq[byte](eciesEncryptedLength(plain.len))
|
||||
let err = eciesEncrypt(plain, res, self.dst.get())
|
||||
if err != EciesStatus.Success:
|
||||
notice "Encryption failed", err
|
||||
if err.isErr:
|
||||
notice "Encryption failed", err = err.error
|
||||
return
|
||||
return some(res)
|
||||
|
||||
|
@ -343,7 +343,7 @@ proc decode*(data: openarray[byte], dst = none[PrivateKey](),
|
|||
return
|
||||
|
||||
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
|
||||
return
|
||||
elif symKey.isSome():
|
||||
|
|
|
@ -10,9 +10,13 @@
|
|||
|
||||
## 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
|
||||
|
||||
export result
|
||||
|
||||
const
|
||||
RlpHeaderLength* = 16
|
||||
RlpMacLength* = 16
|
||||
|
@ -27,15 +31,16 @@ type
|
|||
emac*: keccak256
|
||||
imac*: keccak256
|
||||
|
||||
RlpxStatus* = enum
|
||||
Success, ## Operation was successful
|
||||
IncorrectMac, ## MAC verification failed
|
||||
BufferOverrun, ## Buffer overrun error
|
||||
IncompleteError, ## Data incomplete error
|
||||
IncorrectArgs ## Incorrect arguments
|
||||
RlpxError* = enum
|
||||
IncorrectMac = "rlpx: MAC verification failed"
|
||||
BufferOverrun = "rlpx: buffer overrun"
|
||||
IncompleteError = "rlpx: data incomplete"
|
||||
IncorrectArgs = "rlpx: incorrect arguments"
|
||||
|
||||
RlpxHeader* = array[16, byte]
|
||||
|
||||
RlpxResult*[T] = Result[T, RlpxError]
|
||||
|
||||
proc roundup16*(x: int): int {.inline.} =
|
||||
## Procedure aligns `x` to
|
||||
let rem = x and 15
|
||||
|
@ -76,7 +81,7 @@ template decryptedLength*(size: int): int =
|
|||
|
||||
proc encrypt*(c: var SecretState, header: 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
|
||||
## result into `output`.
|
||||
##
|
||||
|
@ -92,7 +97,7 @@ proc encrypt*(c: var SecretState, header: openarray[byte],
|
|||
let framePos = RlpHeaderLength + RlpMacLength
|
||||
let frameMacPos = RlpHeaderLength * 2 + frameLength
|
||||
if len(header) != RlpHeaderLength or len(frame) == 0 or length != len(output):
|
||||
return IncorrectArgs
|
||||
return err(IncorrectArgs)
|
||||
# header_ciphertext = self.aes_enc.update(header)
|
||||
c.aesenc.encrypt(header, toa(output, 0, RlpHeaderLength))
|
||||
# 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
|
||||
copyMem(addr output[headerMacPos], addr headerMac.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] =
|
||||
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
|
||||
result = newSeq[byte](encryptedLength(msg.len))
|
||||
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 =
|
||||
(int(a[0]) shl 16) or (int(a[1]) shl 8) or int(a[2])
|
||||
|
||||
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
|
||||
## result into `output`.
|
||||
##
|
||||
|
@ -169,9 +174,9 @@ proc decryptHeader*(c: var SecretState, data: openarray[byte],
|
|||
aes: array[RlpHeaderLength, byte]
|
||||
|
||||
if len(data) != RlpHeaderLength + RlpMacLength:
|
||||
return IncompleteError
|
||||
return err(IncompleteError)
|
||||
if len(output) < RlpHeaderLength:
|
||||
return IncorrectArgs
|
||||
return err(IncorrectArgs)
|
||||
# mac_secret = self.ingress_mac.digest()[:HEADER_LEN]
|
||||
tmpmac = c.imac
|
||||
var macsec = tmpmac.finish()
|
||||
|
@ -188,22 +193,22 @@ proc decryptHeader*(c: var SecretState, data: openarray[byte],
|
|||
let headerMacPos = RlpHeaderLength
|
||||
if not equalMem(cast[pointer](unsafeAddr data[headerMacPos]),
|
||||
cast[pointer](addr expectMac.data[0]), RlpMacLength):
|
||||
result = IncorrectMac
|
||||
result = err(IncorrectMac)
|
||||
else:
|
||||
# return self.aes_dec.update(header_ciphertext)
|
||||
c.aesdec.decrypt(toa(data, 0, RlpHeaderLength), output)
|
||||
result = Success
|
||||
result = ok()
|
||||
|
||||
proc decryptHeaderAndGetMsgSize*(c: var SecretState,
|
||||
encryptedHeader: openarray[byte],
|
||||
outSize: var int): RlpxStatus =
|
||||
outSize: var int): RlpxResult[void] =
|
||||
var decryptedHeader: RlpxHeader
|
||||
result = decryptHeader(c, encryptedHeader, decryptedHeader)
|
||||
if result == Success:
|
||||
if result.isOk():
|
||||
outSize = decryptedHeader.getBodySize
|
||||
|
||||
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
|
||||
## result into `output`.
|
||||
##
|
||||
|
@ -217,9 +222,9 @@ proc decryptBody*(c: var SecretState, data: openarray[byte], bodysize: int,
|
|||
outlen = 0
|
||||
let rsize = roundup16(bodysize)
|
||||
if len(data) < rsize + RlpMacLength:
|
||||
return IncompleteError
|
||||
return err(IncompleteError)
|
||||
if len(output) < rsize:
|
||||
return IncorrectArgs
|
||||
return err(IncorrectArgs)
|
||||
# self.ingress_mac.update(frame_ciphertext)
|
||||
c.imac.update(toa(data, 0, rsize))
|
||||
tmpmac = c.imac
|
||||
|
@ -235,8 +240,8 @@ proc decryptBody*(c: var SecretState, data: openarray[byte], bodysize: int,
|
|||
let bodyMacPos = rsize
|
||||
if not equalMem(cast[pointer](unsafeAddr data[bodyMacPos]),
|
||||
cast[pointer](addr expectMac.data[0]), RlpMacLength):
|
||||
result = IncorrectMac
|
||||
result = err(IncorrectMac)
|
||||
else:
|
||||
c.aesdec.decrypt(toa(data, 0, rsize), output)
|
||||
outlen = bodysize
|
||||
result = Success
|
||||
result = ok()
|
||||
|
|
|
@ -16,8 +16,7 @@ init:
|
|||
node2 = setupTestNode(eth, Whisper)
|
||||
|
||||
node2.startListening()
|
||||
peer = waitFor node1.rlpxConnect(newNode(initENode(node2.keys.pubKey,
|
||||
node2.address)))
|
||||
peer = waitFor node1.rlpxConnect(newNode(node2.toENode()))
|
||||
|
||||
test:
|
||||
aflLoop: # This appears to have unstable results with afl-clang-fast, probably
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||
# 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
|
||||
# https://github.com/ethereum/tests/blob/develop/KeyStoreTests/basic_tests.json
|
||||
|
@ -83,52 +83,45 @@ var TestVectors = [
|
|||
}
|
||||
]
|
||||
|
||||
var jobject: JsonNode
|
||||
|
||||
suite "KeyFile test suite":
|
||||
test "KeyStoreTests/basic_tests.json test1":
|
||||
var seckey: PrivateKey
|
||||
|
||||
var expectkey = PrivateKey.fromHex(TestVectors[0].getOrDefault("priv").getStr())[]
|
||||
check:
|
||||
let seckey =
|
||||
decodeKeyFileJson(TestVectors[0].getOrDefault("keyfile"),
|
||||
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())[]
|
||||
TestVectors[0].getOrDefault("password").getStr())[]
|
||||
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"),
|
||||
TestVectors[1].getOrDefault("password").getStr(),
|
||||
seckey) == KeyFileStatus.Success
|
||||
TestVectors[1].getOrDefault("password").getStr())[]
|
||||
check:
|
||||
seckey.toRaw == expectkey.toRaw
|
||||
test "KeyStoreTests/basic_tests.json evilnonce":
|
||||
var seckey: PrivateKey
|
||||
var expectkey = PrivateKey.fromHex(TestVectors[2].getOrDefault("priv").getStr())[]
|
||||
let seckey = decodeKeyFileJson(TestVectors[2].getOrDefault("keyfile"),
|
||||
TestVectors[2].getOrDefault("password").getStr())[]
|
||||
check:
|
||||
decodeKeyFileJson(TestVectors[2].getOrDefault("keyfile"),
|
||||
TestVectors[2].getOrDefault("password").getStr(),
|
||||
seckey) == KeyFileStatus.Success
|
||||
seckey.toRaw == expectkey.toRaw
|
||||
test "KeyStoreTests/basic_tests.json evilnonce with wrong password":
|
||||
var seckey: PrivateKey
|
||||
check:
|
||||
let seckey =
|
||||
decodeKeyFileJson(TestVectors[2].getOrDefault("keyfile"),
|
||||
"wrongpassword",
|
||||
seckey) == KeyFileStatus.IncorrectMac
|
||||
"wrongpassword")
|
||||
check:
|
||||
seckey.error == KeyFileError.IncorrectMac
|
||||
test "Create/Save/Load test":
|
||||
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:
|
||||
createKeyFileJson(seckey0, "randompassword",
|
||||
jobject) == KeyFileStatus.Success
|
||||
saveKeyFile("test.keyfile", jobject) == KeyFileStatus.Success
|
||||
loadKeyFile("test.keyfile", "randompassword",
|
||||
seckey1) == KeyFileStatus.Success
|
||||
seckey0.toRaw == seckey1.toRaw
|
||||
removeFile("test.keyfile")
|
||||
test "Load non-existent pathname test":
|
||||
var seckey: PrivateKey
|
||||
check:
|
||||
loadKeyFile("nonexistant.keyfile", "password",
|
||||
seckey) == KeyFileStatus.OsError
|
||||
loadKeyFile("nonexistant.keyfile", "password").error ==
|
||||
KeyFileError.OsError
|
||||
|
|
|
@ -7,17 +7,15 @@
|
|||
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||
# MIT license (LICENSE-MIT)
|
||||
|
||||
import eth/keyfile/uuid, strutils, unittest
|
||||
import eth/keyfile/uuid, unittest
|
||||
|
||||
suite "Cross-platform UUID test suite":
|
||||
test "Platform UUID check":
|
||||
var u: UUID
|
||||
check uuidGenerate(u) == 1
|
||||
check uuidGenerate().isOk
|
||||
|
||||
test "Conversion test":
|
||||
var u: UUID
|
||||
let u = uuidGenerate()[]
|
||||
check:
|
||||
uuidGenerate(u) == 1
|
||||
len($u) == 36
|
||||
$uuidFromString($u) == $u
|
||||
uuidToString(u, true) == $u
|
||||
uuidToString(u, false) == toUpperAscii($u)
|
||||
$uuidFromString($u)[] == $u
|
||||
uuidToString(u) == $u
|
||||
|
|
|
@ -7,10 +7,9 @@ import
|
|||
|
||||
var node = setupTestNode(Bzz, Hive)
|
||||
|
||||
var bzzENode: ENode
|
||||
let nodeId = "enode://10420addaa648ffcf09c4ba9df7ce876f276f77aae015bc9346487780c9c04862dc47cec17c86be10d4fb7d93f2cae3f8e702f94cb6dea5807bfedad218a53df@127.0.0.1:30399"
|
||||
discard initENode(nodeId, bzzENode)
|
||||
waitFor node.peerPool.connectToNode(newNode(bzzENode))
|
||||
let enode = ENode.fromString(nodeId)[]
|
||||
waitFor node.peerPool.connectToNode(newNode(enode))
|
||||
|
||||
doAssert node.peerPool.connectedNodes.len() == 1
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ proc setupBootNode*(): Future[ENode] {.async.} =
|
|||
bootNodeKey = KeyPair.random()[]
|
||||
bootNodeAddr = localAddress(30301)
|
||||
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 =
|
||||
let keys1 = KeyPair.random()[]
|
||||
|
|
|
@ -98,20 +98,15 @@ let topic = [byte 0x12, 0, 0, 0]
|
|||
if config.main:
|
||||
var bootnodes: seq[ENode] = @[]
|
||||
for nodeId in MainnetBootnodes:
|
||||
var bootnode: ENode
|
||||
discard initENode(nodeId, bootnode)
|
||||
bootnodes.add(bootnode)
|
||||
bootnodes.add(ENode.fromString(nodeId).expect("static nodes"))
|
||||
|
||||
asyncCheck node.connectToNetwork(bootnodes, true, true)
|
||||
# main network has mostly non SHH nodes, so we connect directly to SHH nodes
|
||||
for nodeId in WhisperNodes:
|
||||
var whisperENode: ENode
|
||||
discard initENode(nodeId, whisperENode)
|
||||
var whisperNode = newNode(whisperENode)
|
||||
var whisperNode = newNode(ENode.fromString(nodeId).expect("static nodes"))
|
||||
asyncCheck node.peerPool.connectToNode(whisperNode)
|
||||
else:
|
||||
var bootENode: ENode
|
||||
discard initENode(DockerBootNode, bootENode)
|
||||
let bootENode = ENode.fromString(DockerBootnode).expect("static node")
|
||||
waitFor node.connectToNetwork(@[bootENode], true, true)
|
||||
|
||||
if config.watch:
|
||||
|
|
|
@ -215,18 +215,20 @@ suite "Ethereum P2P handshake test suite":
|
|||
|
||||
block:
|
||||
proc newTestHandshake(flags: set[HandshakeFlag]): Handshake =
|
||||
result = newHandshake(flags)
|
||||
if Initiator in flags:
|
||||
result.host.seckey = PrivateKey.fromHex(testValue("initiator_private_key"))[]
|
||||
result.host.pubkey = result.host.seckey.toPublicKey()[]
|
||||
let pk = PrivateKey.fromHex(testValue("initiator_private_key"))[]
|
||||
let kp = KeyPair(seckey: pk, pubkey: pk.toPublicKey()[])
|
||||
result = Handshake.tryInit(kp, flags)[]
|
||||
|
||||
let epki = testValue("initiator_ephemeral_private_key")
|
||||
result.ephemeral.seckey = PrivateKey.fromHex(epki)[]
|
||||
result.ephemeral.pubkey = result.ephemeral.seckey.toPublicKey()[]
|
||||
let nonce = fromHex(stripSpaces(testValue("initiator_nonce")))
|
||||
result.initiatorNonce[0..^1] = nonce[0..^1]
|
||||
elif Responder in flags:
|
||||
result.host.seckey = PrivateKey.fromHex(testValue("receiver_private_key"))[]
|
||||
result.host.pubkey = result.host.seckey.toPublicKey()[]
|
||||
let pk = PrivateKey.fromHex(testValue("receiver_private_key"))[]
|
||||
let kp = KeyPair(seckey: pk, pubkey: pk.toPublicKey()[])
|
||||
result = Handshake.tryInit(kp, flags)[]
|
||||
let epkr = testValue("receiver_ephemeral_private_key")
|
||||
result.ephemeral.seckey = PrivateKey.fromHex(epkr)[]
|
||||
result.ephemeral.pubkey = result.ephemeral.seckey.toPublicKey()[]
|
||||
|
@ -238,9 +240,8 @@ suite "Ethereum P2P handshake test suite":
|
|||
var responder = newTestHandshake({Responder})
|
||||
var m0 = newSeq[byte](initiator.authSize(false))
|
||||
var k0 = 0
|
||||
check:
|
||||
initiator.authMessage(responder.host.pubkey,
|
||||
m0, k0, 0, false) == AuthStatus.Success
|
||||
initiator.authMessage(
|
||||
responder.host.pubkey, m0, k0, 0, false).expect("auth success")
|
||||
var expect1 = fromHex(stripSpaces(testValue("auth_plaintext")))
|
||||
var expect2 = fromHex(stripSpaces(pyevmAuth))
|
||||
check:
|
||||
|
@ -254,10 +255,11 @@ suite "Ethereum P2P handshake test suite":
|
|||
var k0 = 0
|
||||
let remoteEPubkey0 = initiator.ephemeral.pubkey
|
||||
let remoteHPubkey0 = initiator.host.pubkey
|
||||
|
||||
initiator.authMessage(
|
||||
responder.host.pubkey, m0, k0).expect("auth success")
|
||||
responder.decodeAuthMessage(m0).expect("decode success")
|
||||
check:
|
||||
initiator.authMessage(responder.host.pubkey,
|
||||
m0, k0) == AuthStatus.Success
|
||||
responder.decodeAuthMessage(m0) == AuthStatus.Success
|
||||
responder.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1]
|
||||
responder.remoteEPubkey == remoteEPubkey0
|
||||
responder.remoteHPubkey == remoteHPubkey0
|
||||
|
@ -270,11 +272,11 @@ suite "Ethereum P2P handshake test suite":
|
|||
var k0 = 0
|
||||
var k1 = 0
|
||||
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:
|
||||
initiator.authMessage(responder.host.pubkey,
|
||||
m0, k0) == AuthStatus.Success
|
||||
responder.decodeAuthMessage(m0) == AuthStatus.Success
|
||||
responder.ackMessage(m1, k1, 0, false) == AuthStatus.Success
|
||||
m1 == expect0
|
||||
responder.initiatorNonce == initiator.initiatorNonce
|
||||
|
||||
|
@ -285,12 +287,12 @@ suite "Ethereum P2P handshake test suite":
|
|||
var m1 = newSeq[byte](responder.ackSize())
|
||||
var k0 = 0
|
||||
var k1 = 0
|
||||
check:
|
||||
initiator.authMessage(responder.host.pubkey,
|
||||
m0, k0) == AuthStatus.Success
|
||||
responder.decodeAuthMessage(m0) == AuthStatus.Success
|
||||
responder.ackMessage(m1, k1) == AuthStatus.Success
|
||||
initiator.decodeAckMessage(m1) == AuthStatus.Success
|
||||
|
||||
initiator.authMessage(
|
||||
responder.host.pubkey, m0, k0).expect("auth success")
|
||||
responder.decodeAuthMessage(m0).expect("decode success")
|
||||
responder.ackMessage(m1, k1).expect("ack success")
|
||||
initiator.decodeAckMessage(m1).expect("decode success")
|
||||
let remoteEPubkey0 = responder.ephemeral.pubkey
|
||||
let remoteHPubkey0 = responder.host.pubkey
|
||||
check:
|
||||
|
@ -307,13 +309,12 @@ suite "Ethereum P2P handshake test suite":
|
|||
var tmac = fromHex(stripSpaces(testValue("mac_secret")))
|
||||
var temac = fromHex(stripSpaces(testValue("initial_egress_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:
|
||||
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.macKey == csecResponder.macKey
|
||||
taes[0..^1] == csecInitiator.aesKey[0..^1]
|
||||
|
@ -330,9 +331,11 @@ suite "Ethereum P2P handshake test suite":
|
|||
|
||||
block:
|
||||
proc newTestHandshake(flags: set[HandshakeFlag]): Handshake =
|
||||
result = newHandshake(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()[]
|
||||
let esec = testE8Value("initiator_ephemeral_private_key")
|
||||
result.ephemeral.seckey = PrivateKey.fromHex(esec)[]
|
||||
|
@ -340,8 +343,10 @@ suite "Ethereum P2P handshake test suite":
|
|||
let nonce = fromHex(stripSpaces(testE8Value("initiator_nonce")))
|
||||
result.initiatorNonce[0..^1] = nonce[0..^1]
|
||||
elif Responder in flags:
|
||||
result.host.seckey = PrivateKey.fromHex(testE8Value("receiver_private_key"))[]
|
||||
result.host.pubkey = result.host.seckey.toPublicKey()[]
|
||||
let pk = PrivateKey.fromHex(testE8Value("receiver_private_key"))[]
|
||||
let kp = KeyPair(seckey: pk, pubkey: pk.toPublicKey()[])
|
||||
result = Handshake.tryInit(kp, flags)[]
|
||||
|
||||
let esec = testE8Value("receiver_ephemeral_private_key")
|
||||
result.ephemeral.seckey = PrivateKey.fromHex(esec)[]
|
||||
result.ephemeral.pubkey = result.ephemeral.seckey.toPublicKey()[]
|
||||
|
@ -352,8 +357,8 @@ suite "Ethereum P2P handshake test suite":
|
|||
var initiator = newTestHandshake({Initiator})
|
||||
var responder = newTestHandshake({Responder})
|
||||
var m0 = fromHex(stripSpaces(testE8Value("auth_ciphertext_v4")))
|
||||
responder.decodeAuthMessage(m0).expect("decode success")
|
||||
check:
|
||||
responder.decodeAuthMessage(m0) == AuthStatus.Success
|
||||
responder.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1]
|
||||
let remoteEPubkey0 = initiator.ephemeral.pubkey
|
||||
let remoteHPubkey0 = initiator.host.pubkey
|
||||
|
@ -361,7 +366,7 @@ suite "Ethereum P2P handshake test suite":
|
|||
responder.remoteEPubkey == remoteEPubkey0
|
||||
responder.remoteHPubkey == remoteHPubkey0
|
||||
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
|
||||
check:
|
||||
initiator.remoteEPubkey == remoteEPubkey1
|
||||
|
@ -371,28 +376,27 @@ suite "Ethereum P2P handshake test suite":
|
|||
var initiator = newTestHandshake({Initiator})
|
||||
var responder = newTestHandshake({Responder})
|
||||
var m0 = fromHex(stripSpaces(testE8Value("auth_ciphertext_eip8")))
|
||||
responder.decodeAuthMessage(m0).expect("decode success")
|
||||
check:
|
||||
responder.decodeAuthMessage(m0) == AuthStatus.Success
|
||||
responder.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1]
|
||||
let remoteEPubkey0 = initiator.ephemeral.pubkey
|
||||
check responder.remoteEPubkey == remoteEPubkey0
|
||||
let remoteHPubkey0 = initiator.host.pubkey
|
||||
check responder.remoteHPubkey == remoteHPubkey0
|
||||
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
|
||||
check:
|
||||
initiator.remoteEPubkey == remoteEPubkey1
|
||||
initiator.responderNonce[0..^1] == responder.responderNonce[0..^1]
|
||||
var taes = fromHex(stripSpaces(testE8Value("auth2ack2_aes_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:
|
||||
int(initiator.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.macKey == csecResponder.macKey
|
||||
taes[0..^1] == csecInitiator.aesKey[0..^1]
|
||||
|
@ -407,8 +411,8 @@ suite "Ethereum P2P handshake test suite":
|
|||
var initiator = newTestHandshake({Initiator})
|
||||
var responder = newTestHandshake({Responder})
|
||||
var m0 = fromHex(stripSpaces(testE8Value("auth_ciphertext_eip8_3f")))
|
||||
responder.decodeAuthMessage(m0).expect("decode success")
|
||||
check:
|
||||
responder.decodeAuthMessage(m0) == AuthStatus.Success
|
||||
responder.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1]
|
||||
let remoteEPubkey0 = initiator.ephemeral.pubkey
|
||||
let remoteHPubkey0 = initiator.host.pubkey
|
||||
|
@ -416,7 +420,7 @@ suite "Ethereum P2P handshake test suite":
|
|||
responder.remoteEPubkey == remoteEPubkey0
|
||||
responder.remoteHPubkey == remoteHPubkey0
|
||||
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
|
||||
check:
|
||||
int(initiator.version) == 57
|
||||
|
@ -429,22 +433,20 @@ suite "Ethereum P2P handshake test suite":
|
|||
var initiator = newTestHandshake({Initiator, EIP8})
|
||||
var responder = newTestHandshake({Responder})
|
||||
var m0 = newSeq[byte](initiator.authSize())
|
||||
var csecInitiator: ConnectionSecret
|
||||
var csecResponder: ConnectionSecret
|
||||
var k0 = 0
|
||||
var k1 = 0
|
||||
check initiator.authMessage(responder.host.pubkey,
|
||||
m0, k0) == AuthStatus.Success
|
||||
initiator.authMessage(
|
||||
responder.host.pubkey, m0, k0).expect("auth success")
|
||||
m0.setLen(k0)
|
||||
check responder.decodeAuthMessage(m0) == AuthStatus.Success
|
||||
responder.decodeAuthMessage(m0).expect("decode success")
|
||||
check (EIP8 in responder.flags) == true
|
||||
var m1 = newSeq[byte](responder.ackSize())
|
||||
check responder.ackMessage(m1, k1) == AuthStatus.Success
|
||||
responder.ackMessage(m1, k1).expect("ack success")
|
||||
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:
|
||||
initiator.getSecrets(m0, m1, csecInitiator) == AuthStatus.Success
|
||||
responder.getSecrets(m0, m1, csecResponder) == AuthStatus.Success
|
||||
csecInitiator.aesKey == csecResponder.aesKey
|
||||
csecInitiator.macKey == csecResponder.macKey
|
||||
|
||||
|
@ -453,21 +455,19 @@ suite "Ethereum P2P handshake test suite":
|
|||
var initiator = newTestHandshake({Initiator})
|
||||
var responder = newTestHandshake({Responder})
|
||||
var m0 = newSeq[byte](initiator.authSize())
|
||||
var csecInitiator: ConnectionSecret
|
||||
var csecResponder: ConnectionSecret
|
||||
var k0 = 0
|
||||
var k1 = 0
|
||||
check initiator.authMessage(responder.host.pubkey,
|
||||
m0, k0) == AuthStatus.Success
|
||||
initiator.authMessage(
|
||||
responder.host.pubkey, m0, k0).expect("auth success")
|
||||
m0.setLen(k0)
|
||||
check responder.decodeAuthMessage(m0) == AuthStatus.Success
|
||||
responder.decodeAuthMessage(m0).expect("auth success")
|
||||
var m1 = newSeq[byte](responder.ackSize())
|
||||
check responder.ackMessage(m1, k1) == AuthStatus.Success
|
||||
responder.ackMessage(m1, k1).expect("ack success")
|
||||
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:
|
||||
initiator.getSecrets(m0, m1, csecInitiator) == AuthStatus.Success
|
||||
responder.getSecrets(m0, m1, csecResponder) == AuthStatus.Success
|
||||
csecInitiator.aesKey == csecResponder.aesKey
|
||||
csecInitiator.macKey == csecResponder.macKey
|
||||
|
|
|
@ -88,18 +88,19 @@ proc testValue(s: string): string =
|
|||
|
||||
suite "Ethereum RLPx encryption/decryption test suite":
|
||||
proc newTestHandshake(flags: set[HandshakeFlag]): Handshake =
|
||||
result = newHandshake(flags)
|
||||
if Initiator in flags:
|
||||
result.host.seckey = PrivateKey.fromHex(testValue("initiator_private_key"))[]
|
||||
result.host.pubkey = result.host.seckey.toPublicKey()[]
|
||||
let pk = PrivateKey.fromHex(testValue("initiator_private_key"))[]
|
||||
let kp = KeyPair(seckey: pk, pubkey: pk.toPublicKey()[])
|
||||
result = Handshake.tryInit(kp, flags)[]
|
||||
let epki = testValue("initiator_ephemeral_private_key")
|
||||
result.ephemeral.seckey = PrivateKey.fromHex(epki)[]
|
||||
result.ephemeral.pubkey = result.ephemeral.seckey.toPublicKey()[]
|
||||
let nonce = fromHex(stripSpaces(testValue("initiator_nonce")))
|
||||
result.initiatorNonce[0..^1] = nonce[0..^1]
|
||||
elif Responder in flags:
|
||||
result.host.seckey = PrivateKey.fromHex(testValue("receiver_private_key"))[]
|
||||
result.host.pubkey = result.host.seckey.toPublicKey()[]
|
||||
let pk = PrivateKey.fromHex(testValue("receiver_private_key"))[]
|
||||
let kp = KeyPair(seckey: pk, pubkey: pk.toPublicKey()[])
|
||||
result = Handshake.tryInit(kp, flags)[]
|
||||
let epkr = testValue("receiver_ephemeral_private_key")
|
||||
result.ephemeral.seckey = PrivateKey.fromHex(epkr)[]
|
||||
result.ephemeral.pubkey = result.ephemeral.seckey.toPublicKey()[]
|
||||
|
@ -111,15 +112,13 @@ suite "Ethereum RLPx encryption/decryption test suite":
|
|||
var responder = newTestHandshake({Responder})
|
||||
var authm = fromHex(stripSpaces(testValue("auth_ciphertext")))
|
||||
var ackm = fromHex(stripSpaces(testValue("authresp_ciphertext")))
|
||||
var csecInitiator: ConnectionSecret
|
||||
var csecResponder: ConnectionSecret
|
||||
var stateInitiator0, stateInitiator1: SecretState
|
||||
var stateResponder0, stateResponder1: SecretState
|
||||
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
|
||||
responder.decodeAuthMessage(authm).expect("success")
|
||||
initiator.decodeAckMessage(ackm).expect("success")
|
||||
|
||||
var csecInitiator = initiator.getSecrets(authm, ackm)[]
|
||||
var csecResponder = responder.getSecrets(authm, ackm)[]
|
||||
initSecretState(csecInitiator, stateInitiator0)
|
||||
initSecretState(csecResponder, stateResponder0)
|
||||
initSecretState(csecInitiator, stateInitiator1)
|
||||
|
@ -132,7 +131,7 @@ suite "Ethereum RLPx encryption/decryption test suite":
|
|||
|
||||
block:
|
||||
check stateResponder0.decryptHeader(toOpenArray(initiatorHello, 0, 31),
|
||||
header) == RlpxStatus.Success
|
||||
header).isOk()
|
||||
let bodysize = getBodySize(header)
|
||||
check bodysize == 79
|
||||
# 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:
|
||||
stateResponder0.decryptBody(
|
||||
toOpenArray(initiatorHello, 32, len(initiatorHello) - 1),
|
||||
getBodySize(header), body, decrsize) == RlpxStatus.Success
|
||||
getBodySize(header), body, decrsize).isOk()
|
||||
decrsize == 79
|
||||
body.setLen(decrsize)
|
||||
var hello = newSeq[byte](encryptedLength(bodysize))
|
||||
check:
|
||||
stateInitiator1.encrypt(header, body, hello) == RlpxStatus.Success
|
||||
stateInitiator1.encrypt(header, body, hello).isOk()
|
||||
hello == initiatorHello
|
||||
block:
|
||||
check stateInitiator0.decryptHeader(toOpenArray(responderHello, 0, 31),
|
||||
header) == RlpxStatus.Success
|
||||
header).isOk()
|
||||
let bodysize = getBodySize(header)
|
||||
check bodysize == 79
|
||||
# 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:
|
||||
stateInitiator0.decryptBody(
|
||||
toOpenArray(responderHello, 32, len(initiatorHello) - 1),
|
||||
getBodySize(header), body, decrsize) == RlpxStatus.Success
|
||||
getBodySize(header), body, decrsize).isOk()
|
||||
decrsize == 79
|
||||
body.setLen(decrsize)
|
||||
var hello = newSeq[byte](encryptedLength(bodysize))
|
||||
check:
|
||||
stateResponder1.encrypt(header, body, hello) == RlpxStatus.Success
|
||||
stateResponder1.encrypt(header, body, hello).isOk()
|
||||
hello == responderHello
|
||||
|
||||
test "Continuous stream of different lengths (1000 times)":
|
||||
var initiator = newTestHandshake({Initiator})
|
||||
var responder = newTestHandshake({Responder})
|
||||
var m0 = newSeq[byte](initiator.authSize())
|
||||
var csecInitiator: ConnectionSecret
|
||||
var csecResponder: ConnectionSecret
|
||||
var k0 = 0
|
||||
var k1 = 0
|
||||
check initiator.authMessage(responder.host.pubkey,
|
||||
m0, k0) == AuthStatus.Success
|
||||
m0, k0).isOk
|
||||
m0.setLen(k0)
|
||||
check responder.decodeAuthMessage(m0) == AuthStatus.Success
|
||||
check responder.decodeAuthMessage(m0).isOk
|
||||
var m1 = newSeq[byte](responder.ackSize())
|
||||
check responder.ackMessage(m1, k1) == AuthStatus.Success
|
||||
check responder.ackMessage(m1, k1).isOk
|
||||
m1.setLen(k1)
|
||||
check initiator.decodeAckMessage(m1) == AuthStatus.Success
|
||||
check initiator.decodeAckMessage(m1).isOk
|
||||
|
||||
check:
|
||||
initiator.getSecrets(m0, m1, csecInitiator) == AuthStatus.Success
|
||||
responder.getSecrets(m0, m1, csecResponder) == AuthStatus.Success
|
||||
var csecInitiator = initiator.getSecrets(m0, m1)[]
|
||||
var csecResponder = responder.getSecrets(m0, m1)[]
|
||||
var stateInitiator: SecretState
|
||||
var stateResponder: SecretState
|
||||
var iheader, rheader: array[16, byte]
|
||||
|
@ -207,9 +203,9 @@ suite "Ethereum RLPx encryption/decryption test suite":
|
|||
check:
|
||||
randomBytes(ibody) == len(ibody)
|
||||
stateInitiator.encrypt(iheader, ibody,
|
||||
encrypted) == RlpxStatus.Success
|
||||
encrypted).isOk()
|
||||
stateResponder.decryptHeader(toOpenArray(encrypted, 0, 31),
|
||||
rheader) == RlpxStatus.Success
|
||||
rheader).isOk()
|
||||
var length = getBodySize(rheader)
|
||||
check length == len(ibody)
|
||||
var rbody = newSeq[byte](decryptedLength(length))
|
||||
|
@ -217,7 +213,7 @@ suite "Ethereum RLPx encryption/decryption test suite":
|
|||
check:
|
||||
stateResponder.decryptBody(
|
||||
toOpenArray(encrypted, 32, len(encrypted) - 1),
|
||||
length, rbody, decrsize) == RlpxStatus.Success
|
||||
length, rbody, decrsize).isOk()
|
||||
decrsize == length
|
||||
rbody.setLen(decrsize)
|
||||
check:
|
||||
|
@ -235,9 +231,9 @@ suite "Ethereum RLPx encryption/decryption test suite":
|
|||
check:
|
||||
randomBytes(ibody) == len(ibody)
|
||||
stateResponder.encrypt(iheader, ibody,
|
||||
encrypted) == RlpxStatus.Success
|
||||
encrypted).isOk()
|
||||
stateInitiator.decryptHeader(toOpenArray(encrypted, 0, 31),
|
||||
rheader) == RlpxStatus.Success
|
||||
rheader).isOk()
|
||||
var length = getBodySize(rheader)
|
||||
check length == len(ibody)
|
||||
var rbody = newSeq[byte](decryptedLength(length))
|
||||
|
@ -245,7 +241,7 @@ suite "Ethereum RLPx encryption/decryption test suite":
|
|||
check:
|
||||
stateInitiator.decryptBody(
|
||||
toOpenArray(encrypted, 32, len(encrypted) - 1),
|
||||
length, rbody, decrsize) == RlpxStatus.Success
|
||||
length, rbody, decrsize).isOk()
|
||||
decrsize == length
|
||||
rbody.setLen(length)
|
||||
check:
|
||||
|
|
|
@ -22,7 +22,7 @@ proc test() {.async.} =
|
|||
bootNodeKey = PrivateKey.fromHex(
|
||||
"a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a617")[]
|
||||
bootNodeAddr = localAddress(20301)
|
||||
bootENode = initENode(bootNodeKey.toPublicKey()[], bootNodeAddr)
|
||||
bootENode = ENode(pubkey: bootNodeKey.toPublicKey()[], address: bootNodeAddr)
|
||||
bootNode = await startDiscoveryNode(bootNodeKey, bootNodeAddr, @[])
|
||||
|
||||
test "Discover nodes":
|
||||
|
|
|
@ -134,9 +134,9 @@ suite "Discovery v5 Cryptographic Primitives":
|
|||
let
|
||||
pub = PublicKey.fromHex(publicKey)[]
|
||||
priv = PrivateKey.fromHex(secretKey)[]
|
||||
|
||||
let eph = ecdhRawFull(priv, pub)
|
||||
check:
|
||||
eph.isOk()
|
||||
eph[].data == hexToSeqByte(sharedSecret)
|
||||
|
||||
test "Key Derivation":
|
||||
|
|
|
@ -71,14 +71,18 @@ suite "ECIES test suite":
|
|||
var shmac = [0x13'u8, 0x13'u8]
|
||||
var s = PrivateKey.random()[]
|
||||
var p = s.toPublicKey()[]
|
||||
|
||||
eciesEncrypt(plain, encr, p).expect("encryption should succeed")
|
||||
eciesDecrypt(encr, decr, s).expect("decryption should succeed")
|
||||
|
||||
check:
|
||||
# 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))
|
||||
# With additional mac data
|
||||
eciesEncrypt(plain, encr, p, shmac) == EciesStatus.Success
|
||||
eciesDecrypt(encr, decr, s, shmac) == EciesStatus.Success
|
||||
eciesEncrypt(plain, encr, p, shmac).expect("encryption should succeed")
|
||||
eciesDecrypt(encr, decr, s, shmac).expect("decryption should succeed")
|
||||
|
||||
check:
|
||||
equalMem(addr m[0], addr decr[0], len(m))
|
||||
|
||||
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 cipher = fromHex(stripSpaces(cipherText[i]))
|
||||
var expect = fromHex(stripSpaces(expectText[i]))
|
||||
|
||||
eciesDecrypt(cipher, data, s).expect("decryption should succeed")
|
||||
|
||||
check:
|
||||
eciesDecrypt(cipher, data, s) == EciesStatus.Success
|
||||
compare(data, expect) == true
|
||||
|
||||
test "ECIES/cpp-ethereum rlpx.cpp#L432-L459":
|
||||
|
@ -167,5 +173,5 @@ suite "ECIES test suite":
|
|||
var s = PrivateKey.fromHex(secretKeys[i])[]
|
||||
var cipher = fromHex(stripSpaces(cipherData[i]))
|
||||
check:
|
||||
eciesDecrypt(cipher, data, s) == EciesStatus.Success
|
||||
eciesDecrypt(cipher, data, s).isOk()
|
||||
compare(data, expectData[i]) == true
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||
# MIT license (LICENSE-MIT)
|
||||
|
||||
import unittest, net
|
||||
import unittest, net, options
|
||||
import eth/p2p/enode
|
||||
|
||||
suite "ENode":
|
||||
|
@ -28,26 +28,27 @@ suite "ENode":
|
|||
]
|
||||
|
||||
const results = [
|
||||
IncorrectScheme,
|
||||
IncorrectNodeId,
|
||||
IncorrectIP,
|
||||
IncorrectPort,
|
||||
IncorrectDiscPort,
|
||||
IncorrectScheme,
|
||||
IncorrectNodeId,
|
||||
IncorrectScheme,
|
||||
ENodeStatus.Success,
|
||||
ENodeStatus.Success,
|
||||
ENodeStatus.Success,
|
||||
ENodeStatus.Success
|
||||
some IncorrectScheme,
|
||||
some IncorrectNodeId,
|
||||
some IncorrectIP,
|
||||
some IncorrectPort,
|
||||
some IncorrectDiscPort,
|
||||
some IncorrectScheme,
|
||||
some IncorrectNodeId,
|
||||
some IncorrectScheme,
|
||||
none(ENodeError),
|
||||
none(ENodeError),
|
||||
none(ENodeError),
|
||||
none(ENodeError),
|
||||
]
|
||||
|
||||
for index in 0..<len(enodes):
|
||||
var node: ENode
|
||||
let res = initENode(enodes[index], node)
|
||||
check res == results[index]
|
||||
if res == ENodeStatus.Success:
|
||||
check enodes[index] == $node
|
||||
let res = ENode.fromString(enodes[index])
|
||||
check (results[index].isSome and res.error == results[index].get) or
|
||||
res.isOk
|
||||
|
||||
if res.isOk:
|
||||
check enodes[index] == $res[]
|
||||
|
||||
test "Custom validation tests":
|
||||
const enodes = [
|
||||
|
@ -67,35 +68,23 @@ suite "ENode":
|
|||
]
|
||||
|
||||
const results = [
|
||||
IncorrectIP,
|
||||
IncorrectIP,
|
||||
IncorrectIP,
|
||||
IncorrectIP,
|
||||
ENodeStatus.Success,
|
||||
IncorrectUri,
|
||||
IncorrectPort,
|
||||
IncorrectPort,
|
||||
IncorrectDiscPort,
|
||||
IncorrectDiscPort,
|
||||
IncorrectUri,
|
||||
IncorrectIP,
|
||||
IncorrectNodeId
|
||||
some IncorrectIP,
|
||||
some IncorrectIP,
|
||||
some IncorrectIP,
|
||||
some IncorrectIP,
|
||||
none(ENodeError),
|
||||
some IncorrectUri,
|
||||
some IncorrectPort,
|
||||
some IncorrectPort,
|
||||
some IncorrectDiscPort,
|
||||
some IncorrectDiscPort,
|
||||
some IncorrectUri,
|
||||
some IncorrectIP,
|
||||
some IncorrectNodeId
|
||||
]
|
||||
|
||||
for index in 0..<len(enodes):
|
||||
var node: ENode
|
||||
let res = initENode(enodes[index], node)
|
||||
check res == results[index]
|
||||
let res = ENode.fromString(enodes[index])
|
||||
|
||||
test "isCorrect() tests":
|
||||
var node: ENode
|
||||
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
|
||||
check (results[index].isSome and res.error == results[index].get) or
|
||||
res.isOk
|
||||
|
|
|
@ -58,8 +58,7 @@ suite "Testing protocol handlers":
|
|||
var node2 = setupTestNode(abc, xyz)
|
||||
|
||||
node2.startListening()
|
||||
let peer = await node1.rlpxConnect(newNode(initENode(node2.keys.pubKey,
|
||||
node2.address)))
|
||||
let peer = await node1.rlpxConnect(newNode(node2.toENode()))
|
||||
check:
|
||||
peer.isNil == false
|
||||
|
||||
|
@ -74,8 +73,7 @@ suite "Testing protocol handlers":
|
|||
var node1 = setupTestNode(hah)
|
||||
var node2 = setupTestNode(hah)
|
||||
node2.startListening()
|
||||
let peer = await node1.rlpxConnect(newNode(initENode(node2.keys.pubKey,
|
||||
node2.address)))
|
||||
let peer = await node1.rlpxConnect(newNode(node2.toENode()))
|
||||
check:
|
||||
peer.isNil == true
|
||||
# To check if the disconnection handler did not run
|
||||
|
|
|
@ -13,8 +13,7 @@ node1 = setupTestNode(eth, Whisper)
|
|||
node2 = setupTestNode(eth, Whisper)
|
||||
|
||||
node2.startListening()
|
||||
peer = waitFor node1.rlpxConnect(newNode(initENode(node2.keys.pubKey,
|
||||
node2.address)))
|
||||
peer = waitFor node1.rlpxConnect(newNode(node2.toENode()))
|
||||
|
||||
proc testThunk(payload: openArray[byte]) =
|
||||
var (msgId, msgData) = recvMsgMock(payload)
|
||||
|
|
|
@ -23,8 +23,7 @@ procSuite "Whisper connections":
|
|||
var node1 = setupTestNode(Whisper)
|
||||
var node2 = setupTestNode(Whisper)
|
||||
node2.startListening()
|
||||
waitFor node1.peerPool.connectToNode(newNode(initENode(node2.keys.pubKey,
|
||||
node2.address)))
|
||||
waitFor node1.peerPool.connectToNode(newNode(node2.toENode()))
|
||||
asyncTest "Two peers connected":
|
||||
check:
|
||||
node1.peerPool.connectedNodes.len() == 1
|
||||
|
@ -298,8 +297,7 @@ procSuite "Whisper connections":
|
|||
var ln1 = setupTestNode(Whisper)
|
||||
ln1.setLightNode(true)
|
||||
|
||||
await ln1.peerPool.connectToNode(newNode(initENode(node2.keys.pubKey,
|
||||
node2.address)))
|
||||
await ln1.peerPool.connectToNode(newNode(node2.toENode()))
|
||||
|
||||
let topic = [byte 0, 0, 0, 0]
|
||||
|
||||
|
@ -322,6 +320,5 @@ procSuite "Whisper connections":
|
|||
ln2.setLightNode(true)
|
||||
|
||||
ln2.startListening()
|
||||
let peer = await ln1.rlpxConnect(newNode(initENode(ln2.keys.pubKey,
|
||||
ln2.address)))
|
||||
let peer = await ln1.rlpxConnect(newNode(ln2.toENode()))
|
||||
check peer.isNil == true
|
||||
|
|
|
@ -26,8 +26,7 @@ procSuite "Waku - Whisper bridge tests":
|
|||
nodeWaku = setupTestNode(Waku)
|
||||
|
||||
nodeWakuWhisper.startListening()
|
||||
let bridgeNode = newNode(initENode(nodeWakuWhisper.keys.pubKey,
|
||||
nodeWakuWhisper.address))
|
||||
let bridgeNode = newNode(nodeWakuWhisper.toENode())
|
||||
nodeWakuWhisper.shareMessageQueue()
|
||||
|
||||
waitFor nodeWhisper.peerPool.connectToNode(bridgeNode)
|
||||
|
|
|
@ -48,9 +48,9 @@ suite "Waku connections":
|
|||
n3.startListening()
|
||||
|
||||
let
|
||||
p1 = await n2.rlpxConnect(newNode(initENode(n1.keys.pubKey, n1.address)))
|
||||
p2 = await n2.rlpxConnect(newNode(initENode(n3.keys.pubKey, n3.address)))
|
||||
p3 = await n4.rlpxConnect(newNode(initENode(n3.keys.pubKey, n3.address)))
|
||||
p1 = await n2.rlpxConnect(newNode(n1.toENode()))
|
||||
p2 = await n2.rlpxConnect(newNode(n3.toENode()))
|
||||
p3 = await n4.rlpxConnect(newNode(n3.toENode()))
|
||||
check:
|
||||
p1.isNil
|
||||
p2.isNil == false
|
||||
|
@ -70,8 +70,7 @@ suite "Waku connections":
|
|||
wakuTopicNode.protocolState(Waku).config.topics = some(@[topic1])
|
||||
|
||||
wakuNode.startListening()
|
||||
await wakuTopicNode.peerPool.connectToNode(newNode(
|
||||
initENode(wakuNode.keys.pubKey, wakuNode.address)))
|
||||
await wakuTopicNode.peerPool.connectToNode(newNode(wakuNode.toENode()))
|
||||
|
||||
# Update topic interest
|
||||
check:
|
||||
|
@ -93,8 +92,7 @@ suite "Waku connections":
|
|||
wakuNode = setupTestNode(Waku)
|
||||
|
||||
wakuNode.startListening()
|
||||
await wakuPowNode.peerPool.connectToNode(newNode(
|
||||
initENode(wakuNode.keys.pubKey, wakuNode.address)))
|
||||
await wakuPowNode.peerPool.connectToNode(newNode(wakuNode.toENode()))
|
||||
|
||||
# Update minimum pow
|
||||
await setPowRequirement(wakuPowNode, 1.0)
|
||||
|
@ -114,8 +112,7 @@ suite "Waku connections":
|
|||
wakuNode = setupTestNode(Waku)
|
||||
|
||||
wakuNode.startListening()
|
||||
await wakuLightNode.peerPool.connectToNode(newNode(
|
||||
initENode(wakuNode.keys.pubKey, wakuNode.address)))
|
||||
await wakuLightNode.peerPool.connectToNode(newNode(wakuNode.toENode()))
|
||||
|
||||
# Update minimum pow
|
||||
await setLightNode(wakuLightNode, true)
|
||||
|
@ -140,8 +137,7 @@ suite "Waku connections":
|
|||
discard await wakuBloomNode.setTopicInterest(topics)
|
||||
|
||||
wakuBloomNode.startListening()
|
||||
await wakuNode.peerPool.connectToNode(newNode(
|
||||
initENode(wakuBloomNode.keys.pubKey, wakuBloomNode.address)))
|
||||
await wakuNode.peerPool.connectToNode(newNode(wakuBloomNode.toENode()))
|
||||
|
||||
# Sanity check
|
||||
check:
|
||||
|
@ -188,8 +184,7 @@ suite "Waku connections":
|
|||
wakuTopicNode.protocolState(Waku).config.topics = some(@[topic1, topic2])
|
||||
|
||||
wakuNode.startListening()
|
||||
await wakuTopicNode.peerPool.connectToNode(newNode(
|
||||
initENode(wakuNode.keys.pubKey, wakuNode.address)))
|
||||
await wakuTopicNode.peerPool.connectToNode(newNode(wakuNode.toENode()))
|
||||
|
||||
let payload = repeat(byte 0, 10)
|
||||
check:
|
||||
|
@ -217,8 +212,7 @@ suite "Waku connections":
|
|||
wakuTopicNode.protocolState(Waku).config.bloom = some(toBloom([bloomTopic]))
|
||||
|
||||
wakuNode.startListening()
|
||||
await wakuTopicNode.peerPool.connectToNode(newNode(
|
||||
initENode(wakuNode.keys.pubKey, wakuNode.address)))
|
||||
await wakuTopicNode.peerPool.connectToNode(newNode(wakuNode.toENode()))
|
||||
|
||||
let payload = repeat(byte 0, 10)
|
||||
check:
|
||||
|
@ -235,8 +229,7 @@ suite "Waku connections":
|
|||
await ln.setLightNode(true)
|
||||
var fn = setupTestNode(Waku)
|
||||
fn.startListening()
|
||||
await ln.peerPool.connectToNode(newNode(initENode(fn.keys.pubKey,
|
||||
fn.address)))
|
||||
await ln.peerPool.connectToNode(newNode(fn.toENode()))
|
||||
|
||||
let topic = [byte 0, 0, 0, 0]
|
||||
|
||||
|
|
|
@ -16,9 +16,8 @@ procSuite "Waku Mail Client":
|
|||
var simpleServer = setupTestNode(Waku)
|
||||
|
||||
simpleServer.startListening()
|
||||
let simpleServerNode = newNode(initENode(simpleServer.keys.pubKey,
|
||||
simpleServer.address))
|
||||
let clientNode = newNode(initENode(client.keys.pubKey, client.address))
|
||||
let simpleServerNode = newNode(simpleServer.toENode())
|
||||
let clientNode = newNode(client.toENode())
|
||||
waitFor client.peerPool.connectToNode(simpleServerNode)
|
||||
require:
|
||||
waitFor simpleServer.waitForConnected().withTimeout(transmissionTimeout)
|
||||
|
|
Loading…
Reference in New Issue