From 0b110f3287f26e03f5e7ac4c9e7f0103456895c0 Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Mon, 6 Apr 2020 18:24:15 +0200 Subject: [PATCH] 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 --- eth/keyfile/keyfile.nim | 251 +++++----- eth/keyfile/uuid.nim | 108 +++-- eth/p2p.nim | 2 +- eth/p2p/auth.nim | 459 +++++++++--------- eth/p2p/discovery.nim | 62 +-- eth/p2p/discoveryv5/encoding.nim | 11 +- eth/p2p/discoveryv5/enr.nim | 13 +- eth/p2p/discoveryv5/node.nim | 6 +- eth/p2p/discoveryv5/protocol.nim | 2 +- eth/p2p/ecies.nim | 86 ++-- eth/p2p/enode.nim | 106 ++-- eth/p2p/kademlia.nim | 4 +- eth/p2p/mock_peers.nim | 4 +- eth/p2p/private/p2p_types.nim | 2 + eth/p2p/rlpx.nim | 34 +- eth/p2p/rlpx_protocols/bzz_protocol.nim | 2 +- eth/p2p/rlpx_protocols/les_protocol.nim | 2 +- .../rlpx_protocols/whisper/whisper_types.nim | 6 +- eth/p2p/rlpxcrypt.nim | 51 +- tests/fuzzing/discovery/fuzz.nim | 2 +- tests/fuzzing/rlpx/thunk.nim | 3 +- tests/keyfile/test_keyfile.nim | 53 +- tests/keyfile/test_uuid.nim | 14 +- tests/p2p/bzz_basic_client.nim | 5 +- tests/p2p/p2p_test_helper.nim | 2 +- tests/p2p/shh_basic_client.nim | 11 +- tests/p2p/test_auth.nim | 118 ++--- tests/p2p/test_crypt.nim | 62 ++- tests/p2p/test_discovery.nim | 2 +- tests/p2p/test_discv5_encoding.nim | 2 +- tests/p2p/test_ecies.nim | 20 +- tests/p2p/test_enode.nim | 81 ++-- tests/p2p/test_protocol_handlers.nim | 6 +- tests/p2p/test_rlpx_thunk.nim | 3 +- tests/p2p/test_shh_connect.nim | 9 +- tests/p2p/test_waku_bridge.nim | 3 +- tests/p2p/test_waku_connect.nim | 27 +- tests/p2p/test_waku_mail.nim | 5 +- 38 files changed, 783 insertions(+), 856 deletions(-) diff --git a/eth/keyfile/keyfile.nim b/eth/keyfile/keyfile.nim index 02ad720..a179f63 100644 --- a/eth/keyfile/keyfile.nim +++ b/eth/keyfile/keyfile.nim @@ -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() diff --git a/eth/keyfile/uuid.nim b/eth/keyfile/uuid.nim index 87da4de..f267126 100644 --- a/eth/keyfile/uuid.nim +++ b/eth/keyfile/uuid.nim @@ -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.. (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) diff --git a/eth/p2p/discovery.nim b/eth/p2p/discovery.nim index 08de202..2ebeb53 100644 --- a/eth/p2p/discovery.nim +++ b/eth/p2p/discovery.nim @@ -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) diff --git a/eth/p2p/discoveryv5/encoding.nim b/eth/p2p/discoveryv5/encoding.nim index d8d5be6..a049f93 100644 --- a/eth/p2p/discoveryv5/encoding.nim +++ b/eth/p2p/discoveryv5/encoding.nim @@ -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] = diff --git a/eth/p2p/discoveryv5/enr.nim b/eth/p2p/discoveryv5/enr.nim index 7242e44..5239cfd 100644 --- a/eth/p2p/discoveryv5/enr.nim +++ b/eth/p2p/discoveryv5/enr.nim @@ -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) diff --git a/eth/p2p/discoveryv5/node.nim b/eth/p2p/discoveryv5/node.nim index 6495049..76c2c7e 100644 --- a/eth/p2p/discoveryv5/node.nim +++ b/eth/p2p/discoveryv5/node.nim @@ -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) diff --git a/eth/p2p/discoveryv5/protocol.nim b/eth/p2p/discoveryv5/protocol.nim index fa56753..1c3a8b3 100644 --- a/eth/p2p/discoveryv5/protocol.nim +++ b/eth/p2p/discoveryv5/protocol.nim @@ -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) diff --git a/eth/p2p/ecies.nim b/eth/p2p/ecies.nim index 3186e2b..4f9d423 100644 --- a/eth/p2p/ecies.nim +++ b/eth/p2p/ecies.nim @@ -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() diff --git a/eth/p2p/enode.nim b/eth/p2p/enode.nim index 10a0646..6181bec 100644 --- a/eth/p2p/enode.nim +++ b/eth/p2p/enode.nim @@ -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: diff --git a/eth/p2p/kademlia.nim b/eth/p2p/kademlia.nim index 2e29b2d..7b29428 100644 --- a/eth/p2p/kademlia.nim +++ b/eth/p2p/kademlia.nim @@ -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 = diff --git a/eth/p2p/mock_peers.nim b/eth/p2p/mock_peers.nim index 86b5901..a6cc825 100644 --- a/eth/p2p/mock_peers.nim +++ b/eth/p2p/mock_peers.nim @@ -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) - diff --git a/eth/p2p/private/p2p_types.nim b/eth/p2p/private/p2p_types.nim index 947c127..a160938 100644 --- a/eth/p2p/private/p2p_types.nim +++ b/eth/p2p/private/p2p_types.nim @@ -168,3 +168,5 @@ type proc `$`*(peer: Peer): string = $peer.remote +proc toENode*(v: EthereumNode): ENode = + ENode(pubkey: v.keys.pubkey, address: v.address) diff --git a/eth/p2p/rlpx.nim b/eth/p2p/rlpx.nim index f56924c..03370b1 100644 --- a/eth/p2p/rlpx.nim +++ b/eth/p2p/rlpx.nim @@ -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 diff --git a/eth/p2p/rlpx_protocols/bzz_protocol.nim b/eth/p2p/rlpx_protocols/bzz_protocol.nim index c4375fa..4a29bd0 100644 --- a/eth/p2p/rlpx_protocols/bzz_protocol.nim +++ b/eth/p2p/rlpx_protocols/bzz_protocol.nim @@ -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", diff --git a/eth/p2p/rlpx_protocols/les_protocol.nim b/eth/p2p/rlpx_protocols/les_protocol.nim index 7ae4687..4f9f6f5 100644 --- a/eth/p2p/rlpx_protocols/les_protocol.nim +++ b/eth/p2p/rlpx_protocols/les_protocol.nim @@ -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" diff --git a/eth/p2p/rlpx_protocols/whisper/whisper_types.nim b/eth/p2p/rlpx_protocols/whisper/whisper_types.nim index b07353d..0fa81e9 100644 --- a/eth/p2p/rlpx_protocols/whisper/whisper_types.nim +++ b/eth/p2p/rlpx_protocols/whisper/whisper_types.nim @@ -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(): diff --git a/eth/p2p/rlpxcrypt.nim b/eth/p2p/rlpxcrypt.nim index 4c56742..f158e40 100644 --- a/eth/p2p/rlpxcrypt.nim +++ b/eth/p2p/rlpxcrypt.nim @@ -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() diff --git a/tests/fuzzing/discovery/fuzz.nim b/tests/fuzzing/discovery/fuzz.nim index f219dff..ba577a0 100644 --- a/tests/fuzzing/discovery/fuzz.nim +++ b/tests/fuzzing/discovery/fuzz.nim @@ -31,4 +31,4 @@ test: # These errors are also catched in `processClient` in discovery.nim # TODO: move them a layer down in discovery so we can do a cleaner test there? except RlpError, DiscProtocolError as e: - debug "Receive failed", err = e.msg \ No newline at end of file + debug "Receive failed", err = e.msg diff --git a/tests/fuzzing/rlpx/thunk.nim b/tests/fuzzing/rlpx/thunk.nim index fb2afc1..cef6e72 100644 --- a/tests/fuzzing/rlpx/thunk.nim +++ b/tests/fuzzing/rlpx/thunk.nim @@ -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 diff --git a/tests/keyfile/test_keyfile.nim b/tests/keyfile/test_keyfile.nim index a894c9b..3131b2b 100644 --- a/tests/keyfile/test_keyfile.nim +++ b/tests/keyfile/test_keyfile.nim @@ -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 diff --git a/tests/keyfile/test_uuid.nim b/tests/keyfile/test_uuid.nim index f2f013a..005678a 100644 --- a/tests/keyfile/test_uuid.nim +++ b/tests/keyfile/test_uuid.nim @@ -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 diff --git a/tests/p2p/bzz_basic_client.nim b/tests/p2p/bzz_basic_client.nim index 6160391..bb49922 100644 --- a/tests/p2p/bzz_basic_client.nim +++ b/tests/p2p/bzz_basic_client.nim @@ -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 diff --git a/tests/p2p/p2p_test_helper.nim b/tests/p2p/p2p_test_helper.nim index 5b5d029..54ffb59 100644 --- a/tests/p2p/p2p_test_helper.nim +++ b/tests/p2p/p2p_test_helper.nim @@ -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()[] diff --git a/tests/p2p/shh_basic_client.nim b/tests/p2p/shh_basic_client.nim index 2cb19c8..8f384b5 100644 --- a/tests/p2p/shh_basic_client.nim +++ b/tests/p2p/shh_basic_client.nim @@ -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: diff --git a/tests/p2p/test_auth.nim b/tests/p2p/test_auth.nim index 35329a8..9e110ba 100644 --- a/tests/p2p/test_auth.nim +++ b/tests/p2p/test_auth.nim @@ -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 diff --git a/tests/p2p/test_crypt.nim b/tests/p2p/test_crypt.nim index 09ddc52..7bd6856 100644 --- a/tests/p2p/test_crypt.nim +++ b/tests/p2p/test_crypt.nim @@ -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: diff --git a/tests/p2p/test_discovery.nim b/tests/p2p/test_discovery.nim index 3c3f8ab..1b1e73d 100644 --- a/tests/p2p/test_discovery.nim +++ b/tests/p2p/test_discovery.nim @@ -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": diff --git a/tests/p2p/test_discv5_encoding.nim b/tests/p2p/test_discv5_encoding.nim index e82958e..8cb69f6 100644 --- a/tests/p2p/test_discv5_encoding.nim +++ b/tests/p2p/test_discv5_encoding.nim @@ -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": diff --git a/tests/p2p/test_ecies.nim b/tests/p2p/test_ecies.nim index d0b0d33..34b4725 100644 --- a/tests/p2p/test_ecies.nim +++ b/tests/p2p/test_ecies.nim @@ -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 + # With additional mac data + 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 diff --git a/tests/p2p/test_enode.nim b/tests/p2p/test_enode.nim index e64f93f..d1a29fa 100644 --- a/tests/p2p/test_enode.nim +++ b/tests/p2p/test_enode.nim @@ -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..