From c07b9f445783b520db20fcc69b140adb09dca6b4 Mon Sep 17 00:00:00 2001 From: cheatfate Date: Wed, 18 Apr 2018 09:33:48 +0300 Subject: [PATCH] RLPx encryption/decryption module with tests. --- ethp2p.nimble | 4 +- ethp2p/auth.nim | 31 +++--- ethp2p/rlpxcrypt.nim | 203 ++++++++++++++++++++++++++++++++++ tests/testauth.nim | 16 ++- tests/testcrypt.nim | 258 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 487 insertions(+), 25 deletions(-) create mode 100644 ethp2p/rlpxcrypt.nim create mode 100644 tests/testcrypt.nim diff --git a/ethp2p.nimble b/ethp2p.nimble index c1b1ab4..48f2426 100644 --- a/ethp2p.nimble +++ b/ethp2p.nimble @@ -9,7 +9,7 @@ skipDirs = @["tests", "Nim"] requires "nim > 0.18.0", "rlp >= 1.0.1", - "nimcrypto >= 0.1.0", + "nimcrypto >= 0.3.0", "secp256k1 >= 0.1.0", "eth_keys", "ranges", @@ -21,5 +21,5 @@ proc runTest(name: string, lang = "c") = exec "nim " & lang & " -r tests/" & nam task test, "Runs the test suite": runTest "testecies" runTest "testauth" - + runTest "testcrypt" runTest("tdiscovery", "cpp") diff --git a/ethp2p/auth.nim b/ethp2p/auth.nim index 9530e5b..66036fe 100644 --- a/ethp2p/auth.nim +++ b/ethp2p/auth.nim @@ -75,15 +75,15 @@ type ConnectionSecret* = object aesKey*: array[aes256.sizeKey, byte] macKey*: array[KeyLength, byte] - egressMac*: array[keccak256.sizeDigest, byte] - ingressMac*: array[keccak256.sizeDigest, byte] + egressMac*: keccak256 + ingressMac*: keccak256 AuthException* = object of Exception template toa(a, b, c: untyped): untyped = toOpenArray((a), (b), (b) + (c) - 1) -proc sxor[T](a: var openarray[T], b: openarray[T]) = +proc sxor[T](a: var openarray[T], b: openarray[T]) {.inline.} = assert(len(a) == len(b)) for i in 0 ..< len(a): a[i] = a[i] xor b[i] @@ -476,7 +476,6 @@ proc getSecrets*(h: Handshake, authmsg: openarray[byte], ctx0: keccak256 ctx1: keccak256 mac1: MDigest[256] - mac2: MDigest[256] xornonce: Nonce # ecdhe-secret = ecdh.agree(ephemeral-privkey, remote-ephemeral-pubk) @@ -516,26 +515,22 @@ proc getSecrets*(h: Handshake, authmsg: openarray[byte], ctx0.init() ctx0.update(xornonce) ctx0.update(authmsg) - mac1 = ctx0.finish() # ingress-mac = keccak256(mac-secret ^ initiator-nonce || auth-recvd-ack) xornonce = secret.macKey xornonce.sxor(h.initiatorNonce) - ctx0.init() - ctx0.update(xornonce) - ctx0.update(ackmsg) - mac2 = ctx0.finish() - - ctx0.init() # clean keccak256 context - zeroMem(addr xornonce[0], sizeof(Nonce)) # clean xornonce + ctx1.init() + ctx1.update(xornonce) + ctx1.update(ackmsg) + burnMem(xornonce) if Initiator in h.flags: - secret.egressMac = mac1.data - secret.ingressMac = mac2.data + secret.egressMac = ctx0 + secret.ingressMac = ctx1 else: - secret.ingressMac = mac1.data - secret.egressMac = mac2.data + secret.ingressMac = ctx0 + secret.egressMac = ctx1 - burnMem(mac1) - burnMem(mac2) + ctx0.clear() + ctx1.clear() result = Success diff --git a/ethp2p/rlpxcrypt.nim b/ethp2p/rlpxcrypt.nim new file mode 100644 index 0000000..13a1364 --- /dev/null +++ b/ethp2p/rlpxcrypt.nim @@ -0,0 +1,203 @@ +# +# Ethereum P2P +# (c) Copyright 2018 +# Status Research & Development GmbH +# +# See the file "LICENSE", included in this +# distribution, for details about the copyright. +# + +## This module implements RLPx cryptography + +import nimcrypto/rijndael, nimcrypto/bcmode, nimcrypto/keccak, + nimcrypto/utils +from auth import ConnectionSecret + +const + RlpHeaderLength* = 16 + RlpMacLength* = 16 + +type + SecretState* = object + ## Object represents current encryption/decryption context. + aesenc*: CTR[aes256] + aesdec*: CTR[aes256] + macenc*: ECB[aes256] + 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 + +proc roundup16*(x: int): int {.inline.} = + ## Procedure aligns `x` to + let rem = x and 15 + if rem != 0: + result = x + 16 - rem + else: + result = x + +template toa(a, b, c: untyped): untyped = + toOpenArray((a), (b), (b) + (c) - 1) + +proc sxor[T](a: var openarray[T], b: openarray[T]) {.inline.} = + assert(len(a) == len(b)) + for i in 0 ..< len(a): + a[i] = a[i] xor b[i] + +proc initSecretState*(secrets: ConnectionSecret, context: var SecretState) = + ## Initialized `context` with values from `secrets`. + + # FIXME: Yes, the encryption is insecure, + # see: https://github.com/ethereum/devp2p/issues/32 + # https://github.com/ethereum/py-evm/blob/master/p2p/peer.py#L159-L160 + var iv: array[context.aesenc.sizeBlock, byte] + context.aesenc.init(secrets.aesKey, iv) + context.aesdec = context.aesenc + context.macenc.init(secrets.macKey) + context.emac = secrets.egressMac + context.imac = secrets.ingressMac + +template encryptedLength*(size: int): int = + ## Returns size of encrypted message for frame with length `size`. + RlpHeaderLength + roundup16(size) + 2 * RlpMacLength + +template decryptedLength*(size: int): int = + ## Returns size of decrypted message for body with length `size`. + roundup16(size) + +proc encrypt*(c: var SecretState, header: openarray[byte], + frame: openarray[byte], + output: var openarray[byte]): RlpxStatus = + ## Encrypts `header` and `frame` using SecretState `c` context and store + ## result into `output`. + ## + ## `header` must be exactly `RlpHeaderLength` length. + ## `frame` must not be zero length. + ## `output` must be at least `encryptedLength(len(frame))` length. + var + tmpmac: keccak256 + aes: array[RlpHeaderLength, byte] + let length = encryptedLength(len(frame)) + let frameLength = roundup16(len(frame)) + let headerMacPos = RlpHeaderLength + let framePos = RlpHeaderLength + RlpMacLength + let frameMacPos = RlpHeaderLength * 2 + frameLength + if len(header) != RlpHeaderLength or len(frame) == 0 or length > len(output): + return IncorrectArgs + # header_ciphertext = self.aes_enc.update(header) + c.aesenc.encrypt(header, toa(output, 0, RlpHeaderLength)) + # mac_secret = self.egress_mac.digest()[:HEADER_LEN] + tmpmac = c.emac + var macsec = tmpmac.finish() + # self.egress_mac.update(sxor(self.mac_enc(mac_secret), header_ciphertext)) + c.macenc.encrypt(toa(macsec.data, 0, RlpHeaderLength), aes) + sxor(aes, toa(output, 0, RlpHeaderLength)) + c.emac.update(aes) + burnMem(aes) + # header_mac = self.egress_mac.digest()[:HEADER_LEN] + tmpmac = c.emac + var headerMac = tmpmac.finish() + # frame_ciphertext = self.aes_enc.update(frame) + copyMem(addr output[framePos], unsafeAddr frame[0], len(frame)) + c.aesenc.encrypt(toa(output, 32, frameLength), toa(output, 32, frameLength)) + # self.egress_mac.update(frame_ciphertext) + c.emac.update(toa(output, 32, frameLength)) + # fmac_seed = self.egress_mac.digest()[:HEADER_LEN] + tmpmac = c.emac + var seed = tmpmac.finish() + # mac_secret = self.egress_mac.digest()[:HEADER_LEN] + macsec = seed + # self.egress_mac.update(sxor(self.mac_enc(mac_secret), fmac_seed)) + c.macenc.encrypt(toa(macsec.data, 0, RlpHeaderLength), aes) + sxor(aes, toa(seed.data, 0, RlpHeaderLength)) + c.emac.update(aes) + burnMem(aes) + # frame_mac = self.egress_mac.digest()[:HEADER_LEN] + tmpmac = c.emac + var frameMac = tmpmac.finish() + tmpmac.clear() + # 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 + +proc decryptHeader*(c: var SecretState, data: openarray[byte], + output: var openarray[byte]): RlpxStatus = + ## Decrypts header `data` using SecretState `c` context and store + ## result into `output`. + ## + ## `header` must be exactly `RlpHeaderLength + RlpMacLength` length. + ## `output` must be at least `RlpHeaderLength` length. + var + tmpmac: keccak256 + aes: array[RlpHeaderLength, byte] + + if len(data) != RlpHeaderLength + RlpMacLength: + return IncompleteError + if len(output) < RlpHeaderLength: + return IncorrectArgs + # mac_secret = self.ingress_mac.digest()[:HEADER_LEN] + tmpmac = c.imac + var macsec = tmpmac.finish() + # aes = self.mac_enc(mac_secret)[:HEADER_LEN] + c.macenc.encrypt(toa(macsec.data, 0, RlpHeaderLength), aes) + # self.ingress_mac.update(sxor(aes, header_ciphertext)) + sxor(aes, toa(data, 0, RlpHeaderLength)) + c.imac.update(aes) + burnMem(aes) + # expected_header_mac = self.ingress_mac.digest()[:HEADER_LEN] + tmpmac = c.imac + var expectMac = tmpmac.finish() + # if not bytes_eq(expected_header_mac, header_mac): + let headerMacPos = RlpHeaderLength + if not equalMem(cast[pointer](unsafeAddr data[headerMacPos]), + cast[pointer](addr expectMac.data[0]), RlpMacLength): + result = IncorrectMac + else: + # return self.aes_dec.update(header_ciphertext) + c.aesdec.decrypt(toa(data, 0, RlpHeaderLength), output) + result = Success + +proc decryptBody*(c: var SecretState, data: openarray[byte], bodysize: int, + output: var openarray[byte], outlen: var int): RlpxStatus = + ## Decrypts body `data` using SecretState `c` context and store + ## result into `output`. + ## + ## `data` must be at least `roundup16(bodysize) + RlpMacLength` length. + ## `output` must be at least `roundup16(bodysize)` length. + ## + ## On success completion `outlen` will hold actual size of decrypted body. + var + tmpmac: keccak256 + aes: array[RlpHeaderLength, byte] + outlen = 0 + let rsize = roundup16(bodysize) + if len(data) < rsize + RlpMacLength: + return IncompleteError + if len(output) < rsize: + return IncorrectArgs + # self.ingress_mac.update(frame_ciphertext) + c.imac.update(toa(data, 0, rsize)) + tmpmac = c.imac + # fmac_seed = self.ingress_mac.digest()[:MAC_LEN] + var seed = tmpmac.finish() + # self.ingress_mac.update(sxor(self.mac_enc(fmac_seed), fmac_seed)) + c.macenc.encrypt(toa(seed.data, 0, RlpHeaderLength), aes) + sxor(aes, toa(seed.data, 0, RlpHeaderLength)) + c.imac.update(aes) + # expected_frame_mac = self.ingress_mac.digest()[:MAC_LEN] + tmpmac = c.imac + var expectMac = tmpmac.finish() + let bodyMacPos = rsize + if not equalMem(cast[pointer](unsafeAddr data[bodyMacPos]), + cast[pointer](addr expectMac.data[0]), RlpMacLength): + result = IncorrectMac + else: + c.aesdec.decrypt(toa(data, 0, rsize), output) + outlen = bodysize + result = Success diff --git a/tests/testauth.nim b/tests/testauth.nim index f97e9c2..cc890b0 100644 --- a/tests/testauth.nim +++ b/tests/testauth.nim @@ -8,7 +8,8 @@ # import unittest -import eth_keys, ethp2p/auth, nimcrypto/utils +import eth_keys, ethp2p/auth, nimcrypto/utils, nimcrypto/keccak, + ethp2p/hexdump # This was generated by `print` actual auth message generated by # https://github.com/ethereum/py-evm/blob/master/tests/p2p/test_auth.py @@ -317,10 +318,15 @@ suite "Ethereum P2P handshake test suite": csecInitiator.macKey == csecResponder.macKey taes[0..^1] == csecInitiator.aesKey[0..^1] tmac[0..^1] == csecInitiator.macKey[0..^1] - csecInitiator.egressMac[0..^1] == temac[0..^1] - csecInitiator.ingressMac[0..^1] == timac[0..^1] - csecResponder.egressMac[0..^1] == timac[0..^1] - csecResponder.ingressMac[0..^1] == temac[0..^1] + let iemac = csecInitiator.egressMac.finish() + let iimac = csecInitiator.ingressMac.finish() + let remac = csecResponder.egressMac.finish() + let rimac = csecResponder.ingressMac.finish() + check: + iemac.data[0..^1] == temac[0..^1] + iimac.data[0..^1] == timac[0..^1] + remac.data[0..^1] == timac[0..^1] + rimac.data[0..^1] == temac[0..^1] block: proc newTestHandshake(flags: set[HandshakeFlag]): Handshake = diff --git a/tests/testcrypt.nim b/tests/testcrypt.nim new file mode 100644 index 0000000..da1e7e9 --- /dev/null +++ b/tests/testcrypt.nim @@ -0,0 +1,258 @@ +# +# Ethereum P2P +# (c) Copyright 2018 +# Status Research & Development GmbH +# +# See the file "LICENSE", included in this +# distribution, for details about the copyright. +# + +import unittest +import eth_keys, ethp2p/auth, ethp2p/rlpxcrypt, nimcrypto/utils, ethp2p/hexdump +import nimcrypto/sysrand, nimcrypto/keccak + +const data = [ + ("initiator_private_key", + "5e173f6ac3c669587538e7727cf19b782a4f2fda07c1eaa662c593e5e85e3051"), + ("receiver_private_key", + "c45f950382d542169ea207959ee0220ec1491755abe405cd7498d6b16adb6df8"), + ("initiator_ephemeral_private_key", + "19c2185f4f40634926ebed3af09070ca9e029f2edd5fae6253074896205f5f6c"), + ("receiver_ephemeral_private_key", + "d25688cf0ab10afa1a0e2dba7853ed5f1e5bf1c631757ed4e103b593ff3f5620"), + ("auth_plaintext", + """884c36f7ae6b406637c1f61b2f57e1d2cab813d24c6559aaf843c3f48962f32f + 46662c066d39669b7b2e3ba14781477417600e7728399278b1b5d801a519aa57 + 0034fdb5419558137e0d44cd13d319afe5629eeccb47fd9dfe55cc6089426e46 + cc762dd8a0636e07a54b31169eba0c7a20a1ac1ef68596f1f283b5c676bae406 + 4abfcce24799d09f67e392632d3ffdc12e3d6430dcb0ea19c318343ffa7aae74 + d4cd26fecb93657d1cd9e9eaf4f8be720b56dd1d39f190c4e1c6b7ec66f077bb + 1100"""), + ("authresp_plaintext", + """802b052f8b066640bba94a4fc39d63815c377fced6fcb84d27f791c9921ddf3e + 9bf0108e298f490812847109cbd778fae393e80323fd643209841a3b7f110397 + f37ec61d84cea03dcc5e8385db93248584e8af4b4d1c832d8c7453c0089687a7 + 00"""), + ("auth_ciphertext", + """04a0274c5951e32132e7f088c9bdfdc76c9d91f0dc6078e848f8e3361193dbdc + 43b94351ea3d89e4ff33ddcefbc80070498824857f499656c4f79bbd97b6c51a + 514251d69fd1785ef8764bd1d262a883f780964cce6a14ff206daf1206aa073a + 2d35ce2697ebf3514225bef186631b2fd2316a4b7bcdefec8d75a1025ba2c540 + 4a34e7795e1dd4bc01c6113ece07b0df13b69d3ba654a36e35e69ff9d482d88d + 2f0228e7d96fe11dccbb465a1831c7d4ad3a026924b182fc2bdfe016a6944312 + 021da5cc459713b13b86a686cf34d6fe6615020e4acf26bf0d5b7579ba813e77 + 23eb95b3cef9942f01a58bd61baee7c9bdd438956b426a4ffe238e61746a8c93 + d5e10680617c82e48d706ac4953f5e1c4c4f7d013c87d34a06626f498f34576d + c017fdd3d581e83cfd26cf125b6d2bda1f1d56"""), + ("authresp_ciphertext", + """049934a7b2d7f9af8fd9db941d9da281ac9381b5740e1f64f7092f3588d4f87f + 5ce55191a6653e5e80c1c5dd538169aa123e70dc6ffc5af1827e546c0e958e42 + dad355bcc1fcb9cdf2cf47ff524d2ad98cbf275e661bf4cf00960e74b5956b79 + 9771334f426df007350b46049adb21a6e78ab1408d5e6ccde6fb5e69f0f4c92b + b9c725c02f99fa72b9cdc8dd53cff089e0e73317f61cc5abf6152513cb7d833f + 09d2851603919bf0fbe44d79a09245c6e8338eb502083dc84b846f2fee1cc310 + d2cc8b1b9334728f97220bb799376233e113"""), + ("ecdhe_shared_secret", + "e3f407f83fc012470c26a93fdff534100f2c6f736439ce0ca90e9914f7d1c381"), + ("initiator_nonce", + "cd26fecb93657d1cd9e9eaf4f8be720b56dd1d39f190c4e1c6b7ec66f077bb11"), + ("receiver_nonce", + "f37ec61d84cea03dcc5e8385db93248584e8af4b4d1c832d8c7453c0089687a7"), + ("aes_secret", + "c0458fa97a5230830e05f4f20b7c755c1d4e54b1ce5cf43260bb191eef4e418d"), + ("mac_secret", + "48c938884d5067a1598272fcddaa4b833cd5e7d92e8228c0ecdfabbe68aef7f1"), + ("token", + "3f9ec2592d1554852b1f54d228f042ed0a9310ea86d038dc2b401ba8cd7fdac4"), + ("initial_egress_MAC", + "09771e93b1a6109e97074cbe2d2b0cf3d3878efafe68f53c41bb60c0ec49097e"), + ("initial_ingress_MAC", + "75823d96e23136c89666ee025fb21a432be906512b3dd4a3049e898adb433847"), + ("initiator_hello_packet", + """6ef23fcf1cec7312df623f9ae701e63b550cdb8517fefd8dd398fc2acd1d935e + 6e0434a2b96769078477637347b7b01924fff9ff1c06df2f804df3b0402bbb9f + 87365b3c6856b45e1e2b6470986813c3816a71bff9d69dd297a5dbd935ab578f + 6e5d7e93e4506a44f307c332d95e8a4b102585fd8ef9fc9e3e055537a5cec2e9"""), + ("receiver_hello_packet", + """6ef23fcf1cec7312df623f9ae701e63be36a1cdd1b19179146019984f3625d4a + 6e0434a2b96769050577657247b7b02bc6c314470eca7e3ef650b98c83e9d7dd + 4830b3f718ff562349aead2530a8d28a8484604f92e5fced2c6183f304344ab0 + e7c301a0c05559f4c25db65e36820b4b909a226171a60ac6cb7beea09376d6d8""") +] + +proc testValue(s: string): string = + for item in data: + if item[0] == s: + result = item[1] + break + +template getBodySize(a: openarray[byte]): int = + (int(a[0]) shl 16) or (int(a[1]) shl 8) or int(a[2]) + +suite "Ethereum RLPx encryption/decryption test suite": + proc newTestHandshake(flags: set[HandshakeFlag]): Handshake = + result = newHandshake(flags) + if Initiator in flags: + result.host.seckey = initPrivateKey(testValue("initiator_private_key")) + result.host.pubkey = result.host.seckey.getPublicKey() + let epki = testValue("initiator_ephemeral_private_key") + result.ephemeral.seckey = initPrivateKey(epki) + result.ephemeral.pubkey = result.ephemeral.seckey.getPublicKey() + let nonce = fromHex(stripSpaces(testValue("initiator_nonce"))) + result.initiatorNonce[0..^1] = nonce[0..^1] + elif Responder in flags: + result.host.seckey = initPrivateKey(testValue("receiver_private_key")) + result.host.pubkey = result.host.seckey.getPublicKey() + let epkr = testValue("receiver_ephemeral_private_key") + result.ephemeral.seckey = initPrivateKey(epkr) + result.ephemeral.pubkey = result.ephemeral.seckey.getPublicKey() + let nonce = fromHex(stripSpaces(testValue("receiver_nonce"))) + result.responderNonce[0..^1] = nonce[0..^1] + + test "Encrypt/Decrypt Hello packet test vectors": + var initiator = newTestHandshake({Initiator}) + 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 + initSecretState(csecInitiator, stateInitiator0) + initSecretState(csecResponder, stateResponder0) + initSecretState(csecInitiator, stateInitiator1) + initSecretState(csecResponder, stateResponder1) + var packet0 = testValue("initiator_hello_packet") + var initiatorHello = fromHex(stripSpaces(packet0)) + var packet1 = testValue("receiver_hello_packet") + var responderHello = fromHex(stripSpaces(packet1)) + var header: array[RlpHeaderLength, byte] + + block: + check stateResponder0.decryptHeader(toOpenArray(initiatorHello, 0, 31), + header) == RlpxStatus.Success + let bodysize = getBodySize(header) + check bodysize == 79 + # we need body size to be rounded to 16 bytes boundary to properly + # encrypt/decrypt it. + var body = newSeq[byte](decryptedLength(bodysize)) + var decrsize = 0 + check: + stateResponder0.decryptBody( + toOpenArray(initiatorHello, 32, len(initiatorHello) - 1), + getBodySize(header), body, decrsize) == RlpxStatus.Success + decrsize == 79 + body.setLen(decrsize) + var hello = newSeq[byte](encryptedLength(bodysize)) + check: + stateInitiator1.encrypt(header, body, hello) == RlpxStatus.Success + hello == initiatorHello + block: + check stateInitiator0.decryptHeader(toOpenArray(responderHello, 0, 31), + header) == RlpxStatus.Success + let bodysize = getBodySize(header) + check bodysize == 79 + # we need body size to be rounded to 16 bytes boundary to properly + # encrypt/decrypt it. + var body = newSeq[byte](decryptedLength(bodysize)) + var decrsize = 0 + check: + stateInitiator0.decryptBody( + toOpenArray(responderHello, 32, len(initiatorHello) - 1), + getBodySize(header), body, decrsize) == RlpxStatus.Success + decrsize == 79 + body.setLen(decrsize) + var hello = newSeq[byte](encryptedLength(bodysize)) + check: + stateResponder1.encrypt(header, body, hello) == RlpxStatus.Success + 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.setLen(k0) + check responder.decodeAuthMessage(m0) == AuthStatus.Success + var m1 = newSeq[byte](responder.ackSize()) + check responder.ackMessage(m1, k1) == AuthStatus.Success + m1.setLen(k1) + check initiator.decodeAckMessage(m1) == AuthStatus.Success + + check: + initiator.getSecrets(m0, m1, csecInitiator) == AuthStatus.Success + responder.getSecrets(m0, m1, csecResponder) == AuthStatus.Success + var stateInitiator: SecretState + var stateResponder: SecretState + var iheader, rheader: array[16, byte] + initSecretState(csecInitiator, stateInitiator) + initSecretState(csecResponder, stateResponder) + burnMem(iheader) + burnMem(rheader) + for i in 1..1000: + # initiator -> responder + block: + var ibody = newSeq[byte](i) + var encrypted = newSeq[byte](encryptedLength(len(ibody))) + iheader[0] = byte((len(ibody) shr 16) and 0xFF) + iheader[1] = byte((len(ibody) shr 8) and 0xFF) + iheader[2] = byte(len(ibody) and 0xFF) + check: + randomBytes(ibody) == len(ibody) + stateInitiator.encrypt(iheader, ibody, + encrypted) == RlpxStatus.Success + stateResponder.decryptHeader(toOpenArray(encrypted, 0, 31), + rheader) == RlpxStatus.Success + var length = getBodySize(rheader) + check length == len(ibody) + var rbody = newSeq[byte](decryptedLength(length)) + var decrsize = 0 + check: + stateResponder.decryptBody( + toOpenArray(encrypted, 32, len(encrypted) - 1), + length, rbody, decrsize) == RlpxStatus.Success + decrsize == length + rbody.setLen(decrsize) + check: + iheader == rheader + ibody == rbody + burnMem(iheader) + burnMem(rheader) + # responder -> initiator + block: + var ibody = newSeq[byte](i * 3) + var encrypted = newSeq[byte](encryptedLength(len(ibody))) + iheader[0] = byte((len(ibody) shr 16) and 0xFF) + iheader[1] = byte((len(ibody) shr 8) and 0xFF) + iheader[2] = byte(len(ibody) and 0xFF) + check: + randomBytes(ibody) == len(ibody) + stateResponder.encrypt(iheader, ibody, + encrypted) == RlpxStatus.Success + stateInitiator.decryptHeader(toOpenArray(encrypted, 0, 31), + rheader) == RlpxStatus.Success + var length = getBodySize(rheader) + check length == len(ibody) + var rbody = newSeq[byte](decryptedLength(length)) + var decrsize = 0 + check: + stateInitiator.decryptBody( + toOpenArray(encrypted, 32, len(encrypted) - 1), + length, rbody, decrsize) == RlpxStatus.Success + decrsize == length + rbody.setLen(length) + check: + iheader == rheader + ibody == rbody + burnMem(iheader) + burnMem(rheader)