nim-eth-p2p/tests/testcrypt.nim

256 lines
11 KiB
Nim

#
# 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
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
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)