Final version for review.

This commit is contained in:
cheatfate 2018-04-02 10:15:16 +03:00
parent b89d42b633
commit 59d65df3f4
2 changed files with 533 additions and 423 deletions

View File

@ -13,27 +13,31 @@ import endians
import ecc, ecies, rlp import ecc, ecies, rlp
import nimcrypto/sysrand, nimcrypto/hash, nimcrypto/utils, nimcrypto/hmac import nimcrypto/sysrand, nimcrypto/hash, nimcrypto/utils, nimcrypto/hmac
import nimcrypto/rijndael, nimcrypto/keccak, nimcrypto/sha2 import nimcrypto/rijndael, nimcrypto/keccak, nimcrypto/sha2
import hexdump
const const
SupportedRlpxVersion* = 4 SupportedRlpxVersion* = 4
# REVIEW: If these messages have fixed lenghts, they will be PlainAuthMessageV4Length* = 194
# better described by an object type (see my similar comments AuthMessageV4Length* = 307
# in the ecies module. PlainAuthMessageEIP8Length = 169
PlainAuthMessageLength* = 194 PlainAuthMessageMaxEIP8 = PlainAuthMessageEIP8Length + 255
PlainAuthAckMessageLength* = 97 AuthMessageEIP8Length* = 282 + 2
AuthMessageLength* = 307 AuthMessageMaxEIP8* = AuthMessageEIP8Length + 255
AuthAckMessageLength* = 210 PlainAckMessageV4Length* = 97
AckMessageV4Length* = 210
PlainAckMessageEIP8Length = 102
PlainAckMessageMaxEIP8 = PlainAckMessageEIP8Length + 255
AckMessageEIP8Length = 215 + 2
AckMessageMaxEIP8 = AckMessageEIP8Length + 255
type type
PlainAuthMessage* = object {.packed.} AuthMessageV4* = object {.packed.}
signature: RawSignature signature: RawSignature
keyhash: array[keccak256.sizeDigest, byte] keyhash: array[keccak256.sizeDigest, byte]
pubkey: PublicKey pubkey: PublicKey
nonce: array[keccak256.sizeDigest, byte] nonce: array[keccak256.sizeDigest, byte]
flag: byte flag: byte
PlainAuthAckMessage* = object {.packed.} AckMessageV4* = object {.packed.}
pubkey: PublicKey pubkey: PublicKey
nonce: array[keccak256.sizeDigest, byte] nonce: array[keccak256.sizeDigest, byte]
flag: byte flag: byte
@ -47,6 +51,7 @@ type
Success, ## Operation was successful Success, ## Operation was successful
RandomError, ## Could not obtain random data RandomError, ## Could not obtain random data
EcdhError, ## ECDH shared secret could not be calculated EcdhError, ## ECDH shared secret could not be calculated
BufferOverrun, ## Buffer overrun error
SignatureError, ## Signature could not be obtained SignatureError, ## Signature could not be obtained
EciesError, ## ECIES encryption/decryption error EciesError, ## ECIES encryption/decryption error
InvalidPubKey, ## Invalid public key InvalidPubKey, ## Invalid public key
@ -56,56 +61,37 @@ type
IncompleteError ## Data incomplete error IncompleteError ## Data incomplete error
Handshake* = object Handshake* = object
version: uint8 version*: uint8 ## protocol version
flags: set[HandshakeFlag] flags*: set[HandshakeFlag] ## handshake flags
host*: KeyPair host*: KeyPair ## host keypair
ephemeral*: KeyPair ephemeral*: KeyPair ## ephemeral host keypair
remoteHPubkey*: PublicKey remoteHPubkey*: PublicKey ## remote host public key
remoteEPubkey*: PublicKey remoteEPubkey*: PublicKey ## remote host ephemeral public key
initiatorNonce*: Nonce initiatorNonce*: Nonce ## initiator nonce
responderNonce*: Nonce responderNonce*: Nonce ## responder nonce
ConnectionSecret* = object ConnectionSecret* = object
# REVIEW: it would be nice if Nimcrypto defines distinct or
# alias types such as `aes256.key` instead of having to spell
# out the full array type everywhere.
aesKey*: array[aes256.sizeKey, byte] aesKey*: array[aes256.sizeKey, byte]
macKey*: array[KeyLength, byte] macKey*: array[KeyLength, byte]
egressMac*: array[keccak256.sizeDigest, byte] egressMac*: array[keccak256.sizeDigest, byte]
ingressMac*: array[keccak256.sizeDigest, byte] ingressMac*: array[keccak256.sizeDigest, byte]
# PlainAuthMessage* = array[PlainAuthMessageLength, byte]
# PlainAuthAckMessage* = array[PlainAuthAckMessageLength, byte]
AuthMessage* = array[AuthMessageLength, byte]
AuthAckMessage* = array[AuthAckMessageLength, byte]
AuthException* = object of Exception 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]) =
assert(len(a) == len(b)) assert(len(a) == len(b))
for i in 0 ..< len(a): for i in 0 ..< len(a):
a[i] = a[i] xor b[i] a[i] = a[i] xor b[i]
proc empty[T](v: openarray[T]): bool = proc newHandshake*(flags: set[HandshakeFlag] = {Initiator},
var r: T version: int = SupportedRlpxVersion): Handshake =
for item in v: ## Create new `Handshake` object.
r = r + item result.version = byte(version and 0xFF)
result = (r == T(0))
proc move[T](dst: var openarray[T], src: openarray[T],
dstx: int = 0, dsty: int = -1, srcx: int = 0, srcy: int = -1) =
let sx = if srcx < 0: (len(src) + srcx) else: srcx
let sy = if srcy < 0: (len(src) + srcy) else: srcy
let dx = if dstx < 0: (len(dst) + dstx) else: dstx
let dy = if dsty < 0: (len(dst) + dsty) else: dsty
assert(sy - sx == dy - dx)
moveMem(addr dst[dstx], unsafeAddr src[srcx], (dy - dx + 1) * sizeof(T))
proc newHandshake*(flags: set[HandshakeFlag] = {Initiator}): Handshake =
var p: ptr byte
result.flags = flags result.flags = flags
result.ephemeral = newKeyPair() result.ephemeral = newKeyPair()
if Initiator in flags: if Initiator in flags:
if randomBytes(result.initiatorNonce) != len(result.initiatorNonce): if randomBytes(result.initiatorNonce) != len(result.initiatorNonce):
raise newException(AuthException, "Could not obtain random data!") raise newException(AuthException, "Could not obtain random data!")
@ -113,240 +99,252 @@ proc newHandshake*(flags: set[HandshakeFlag] = {Initiator}): Handshake =
if randomBytes(result.responderNonce) != len(result.responderNonce): if randomBytes(result.responderNonce) != len(result.responderNonce):
raise newException(AuthException, "Could not obtain random data!") raise newException(AuthException, "Could not obtain random data!")
proc authMessagePreEIP8*(h: var Handshake, proc authMessagePreEIP8(h: var Handshake,
pubkey: PublicKey, pubkey: PublicKey,
output: var PlainAuthMessage, output: var openarray[byte],
flag: int = 0): AuthStatus = outlen: var int,
## Create plain preEIP8 authentication message. flag: int = 0,
var secret: SharedSecret encrypt: bool = true): AuthStatus =
var signature: Signature ## Create plain pre-EIP8 authentication message.
var flagb = byte(flag) var
secret: SharedSecret
signature: Signature
buffer: array[PlainAuthMessageV4Length, byte]
flagb: byte
header: ptr AuthMessageV4
outlen = 0
flagb = byte(flag)
header = cast[ptr AuthMessageV4](addr buffer[0])
if ecdhAgree(h.host.seckey, pubkey, secret) != EccStatus.Success: if ecdhAgree(h.host.seckey, pubkey, secret) != EccStatus.Success:
return(EcdhError) return(EcdhError)
var xornonce = h.initiatorNonce var xornonce = h.initiatorNonce
xornonce.sxor(secret) xornonce.sxor(secret)
if signMessage(h.ephemeral.seckey, xornonce, signature) != EccStatus.Success: if signMessage(h.ephemeral.seckey, xornonce, signature) != EccStatus.Success:
return(SignatureError) return(SignatureError)
h.remoteHPubkey = pubkey h.remoteHPubkey = pubkey
header.signature = signature.getRaw()
output.signature = signature.getRaw() header.keyhash = keccak256.digest(h.ephemeral.pubkey.getRaw().data).data
output.keyhash = keccak256.digest(h.ephemeral.pubkey.getRaw().data).data header.pubkey = cast[PublicKey](h.host.pubkey.getRaw().data)
output.pubkey = cast[PublicKey](h.host.pubkey.getRaw().data) header.nonce = h.initiatorNonce
output.nonce = h.initiatorNonce header.flag = flagb
output.flag = flagb if encrypt:
if len(output) < AuthMessageV4Length:
return(BufferOverrun)
if eciesEncrypt(buffer, output, h.remoteHPubkey) != EciesStatus.Success:
return(EciesError)
outlen = AuthMessageV4Length
result = Success
else:
if len(output) < PlainAuthMessageV4Length:
return(BufferOverrun)
copyMem(addr output[0], addr buffer[0], PlainAuthMessageV4Length)
outlen = PlainAuthMessageV4Length
result = Success
proc authAckMessagePreEIP8*(h: var Handshake, proc authMessageEIP8(h: var Handshake,
output: var PlainAuthAckMessage, pubkey: PublicKey,
flag: int = 0): AuthStatus = output: var openarray[byte],
output.pubkey = cast[PublicKey](h.ephemeral.pubkey.getRaw().data) outlen: var int,
output.nonce = h.responderNonce flag: int = 0,
output.flag = byte(flag) encrypt: bool = true): AuthStatus =
## Create EIP8 authentication message.
proc encryptAuthMessage*(input: ptr byte, inputlen: int, var
output: ptr byte, outputlen: int, secret: SharedSecret
pubkey: PublicKey, shmac: ptr byte = nil, signature: Signature
shlen: int = 0): AuthStatus = buffer: array[PlainAuthMessageMaxEIP8, byte]
result = Success padsize: byte
if eciesEncrypt(input, output, inputlen, outputlen,
pubkey, shmac, shlen) != EciesStatus.Success:
result = EciesError
proc encryptAuthMessage*(input: PlainAuthMessage,
output: var AuthMessage,
pubkey: PublicKey): AuthStatus =
result = Success
result = encryptAuthMessage(unsafeAddr input[0], PlainAuthMessageLength,
addr output[0], AuthMessageLength, pubkey)
proc decryptAuthMessage*(input: ptr byte, inputlen: int,
output: ptr byte, outputlen: int,
seckey: PrivateKey, shmac: ptr byte = nil,
shlen: int = 0): AuthStatus =
result = Success
if eciesDecrypt(input, output, inputlen, outputlen,
seckey, shmac, shlen) != EciesStatus.Success:
result = EciesError
proc decryptAuthMessage*(input: AuthMessage, output: var PlainAuthMessage,
seckey: PrivateKey): AuthStatus =
result = decryptAuthMessage(unsafeAddr input[0], AuthMessageLength,
addr output[0], PlainAuthMessageLength,
seckey)
proc encryptAuthAckMessage*(input: ptr byte, inputlen: int,
output: ptr byte, outputlen: int,
pubkey: PublicKey, shmac: ptr byte = nil,
shlen: int = 0): AuthStatus =
result = Success
if eciesEncrypt(input, output, inputlen, outputlen,
pubkey, shmac, shlen) != EciesStatus.Success:
result = EciesError
proc encryptAuthAckMessage*(input: PlainAuthAckMessage,
output: var AuthAckMessage,
pubkey: PublicKey): AuthStatus =
result = encryptAuthAckMessage(unsafeAddr input[0], PlainAuthAckMessageLength,
addr output[0], AuthAckMessageLength,
pubkey)
proc decryptAuthAckMessage*(input: ptr byte, inputlen: int,
output: ptr byte, outputlen: int,
seckey: PrivateKey, shmac: ptr byte = nil,
shlen: int = 0): AuthStatus =
result = Success
if eciesDecrypt(input, output, inputlen, outputlen,
seckey, shmac, shlen) != EciesStatus.Success:
result = EciesError
proc decryptAuthAckMessage*(input: AuthAckMessage,
output: var PlainAuthAckMessage,
seckey: PrivateKey): AuthStatus =
result = decryptAuthAckMessage(unsafeAddr input[0], AuthAckMessageLength,
addr output[0], PlainAuthAckMessageLength,
seckey)
proc decodePlainAuthMessage(h: var Handshake, m: PlainAuthMessage): AuthStatus =
var secret: SharedSecret
var nonce: array[32, byte]
var pubkey: PublicKey
copyMem(addr nonce[0], unsafeAddr m[161], KeyLength)
if recoverPublicKey(unsafeAddr m[97], sizeof(PublicKey),
pubkey) != EccStatus.Success:
return(InvalidPubKey)
assert(EIP8 in h.flags)
outlen = 0
if ecdhAgree(h.host.seckey, pubkey, secret) != EccStatus.Success: if ecdhAgree(h.host.seckey, pubkey, secret) != EccStatus.Success:
return(EcdhError) return(EcdhError)
var xornonce = h.initiatorNonce
var xornonce = nonce
xornonce.sxor(secret) xornonce.sxor(secret)
if signMessage(h.ephemeral.seckey, xornonce, signature) != EccStatus.Success:
return(SignatureError)
h.remoteHPubkey = pubkey
var payload = rlp.encodeList(signature.getRaw().data,
h.host.pubkey.getRaw().data,
h.initiatorNonce,
[byte(h.version)])
assert(len(payload) == PlainAuthMessageEIP8Length)
let pencsize = eciesEncryptedLength(len(payload))
while true:
if randomBytes(addr padsize, 1) != 1:
return(RandomError)
if int(padsize) > (AuthMessageV4Length - (pencsize + 2)):
break
# It is possible to make packet size constant by uncommenting this line
# padsize = 24
var wosize = pencsize + int(padsize)
let fullsize = wosize + 2
if randomBytes(toa(buffer, PlainAuthMessageEIP8Length,
int(padsize))) != int(padsize):
return(RandomError)
if encrypt:
copyMem(addr buffer[0], payload.baseAddr, len(payload))
if len(output) < fullsize:
return(BufferOverrun)
bigEndian16(addr output, addr wosize)
if eciesEncrypt(toa(buffer, 0, len(payload) + int(padsize)),
toa(output, 2, wosize), pubkey,
toa(output, 0, 2)) != EciesStatus.Success:
return(EciesError)
outlen = fullsize
else:
let plainsize = len(payload) + int(padsize)
if len(output) < plainsize:
return(BufferOverrun)
copyMem(addr output[0], addr buffer[0], plainsize)
outlen = plainsize
result = Success
if recoverSignatureKey(unsafeAddr m[0], SignatureLength, addr xornonce[0], proc ackMessagePreEIP8(h: var Handshake,
output: var openarray[byte],
outlen: var int,
flag: int = 0,
encrypt: bool = true): AuthStatus =
## Create plain pre-EIP8 authentication ack message.
var buffer: array[PlainAckMessageV4Length, byte]
outlen = 0
var header = cast[ptr AckMessageV4](addr buffer[0])
header.pubkey = cast[PublicKey](h.ephemeral.pubkey.getRaw().data)
header.nonce = h.responderNonce
header.flag = byte(flag)
if encrypt:
if len(output) < AckMessageV4Length:
return(BufferOverrun)
if eciesEncrypt(buffer, output, h.remoteHPubkey) != EciesStatus.Success:
return(EciesError)
outlen = AckMessageV4Length
else:
if len(output) < PlainAckMessageV4Length:
return(BufferOverrun)
copyMem(addr output[0], addr buffer[0], PlainAckMessageV4Length)
outlen = PlainAckMessageV4Length
result = Success
proc ackMessageEIP8(h: var Handshake,
output: var openarray[byte],
outlen: var int,
flag: int = 0,
encrypt: bool = true): AuthStatus =
## Create EIP8 authentication ack message.
var
buffer: array[PlainAckMessageMaxEIP8, byte]
padsize: byte
assert(EIP8 in h.flags)
var payload = rlp.encodeList(h.ephemeral.pubkey.getRaw().data,
h.responderNonce,
[byte(h.version)])
assert(len(payload) == PlainAckMessageEIP8Length)
outlen = 0
let pencsize = eciesEncryptedLength(len(payload))
while true:
if randomBytes(addr padsize, 1) != 1:
return(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 fullsize = wosize + 2
if int(padsize) > 0:
if randomBytes(toa(buffer, PlainAuthMessageEIP8Length,
int(padsize))) != int(padsize):
return(RandomError)
copyMem(addr buffer[0], payload.baseAddr, len(payload))
if encrypt:
if len(output) < fullsize:
return(BufferOverrun)
bigEndian16(addr output, addr wosize)
if eciesEncrypt(toa(buffer, 0, len(payload) + int(padsize)),
toa(output, 2, wosize), h.remoteHPubkey,
toa(output, 0, 2)) != EciesStatus.Success:
return(EciesError)
outlen = fullsize
else:
let plainsize = len(payload) + int(padsize)
if len(output) < plainsize:
return(BufferOverrun)
copyMem(addr output[0], addr buffer[0], plainsize)
outlen = plainsize
result = Success
template authSize*(h: Handshake, encrypt: bool = true): int =
## Get number of bytes needed to store AuthMessage.
if EIP8 in h.flags:
if encrypt: (AuthMessageMaxEIP8) else: (PlainAuthMessageMaxEIP8)
else:
if encrypt: (AuthMessageV4Length) else: (PlainAuthMessageV4Length)
template ackSize*(h: Handshake, encrypt: bool = true): int =
## Get number of bytes needed to store AckMessage.
if EIP8 in h.flags:
if encrypt: (AckMessageMaxEIP8) else: (PlainAckMessageMaxEIP8)
else:
if encrypt: (AckMessageV4Length) else: (PlainAckMessageV4Length)
proc authMessage*(h: var Handshake, pubkey: PublicKey,
output: var openarray[byte],
outlen: var int, flag: int = 0,
encrypt: bool = true): AuthStatus {.inline.} =
## 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)
else:
result = 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 =
## 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)
else:
result = ackMessagePreEIP8(h, output, outlen, flag, encrypt)
proc decodeAuthMessageV4(h: var Handshake, m: openarray[byte]): AuthStatus =
## Decodes V4 AuthMessage.
var
secret: SharedSecret
buffer: array[PlainAuthMessageV4Length, byte]
pubkey: PublicKey
assert(Responder in h.flags)
if eciesDecrypt(m, buffer, h.host.seckey) != EciesStatus.Success:
return(EciesError)
var header = cast[ptr AuthMessageV4](addr buffer[0])
if recoverPublicKey(header.pubkey.data, pubkey) != EccStatus.Success:
return(InvalidPubKey)
if ecdhAgree(h.host.seckey, pubkey, secret) != EccStatus.Success:
return(EcdhError)
var xornonce = header.nonce
xornonce.sxor(secret)
if recoverSignatureKey(header.signature.data, xornonce,
h.remoteEPubkey) != EccStatus.Success: h.remoteEPubkey) != EccStatus.Success:
return(SignatureError) return(SignatureError)
h.initiatorNonce = header.nonce
h.initiatorNonce = nonce
h.remoteHPubkey = pubkey h.remoteHPubkey = pubkey
result = Success result = Success
proc decodePlainAuthAckMessage*(h: var Handshake, proc decodeAuthMessageEip8(h: var Handshake, m: openarray[byte]): AuthStatus =
m: PlainAuthAckMessage): AuthStatus = ## Decodes EIP-8 AuthMessage.
if recoverPublicKey(m, h.remoteEPubkey, 0, 63) != EccStatus.Success:
return(InvalidPubKey)
h.responderNonce[0..31] = m[64..95]
result = Success
proc getSecrets*(h: var Handshake,
msg: ptr byte, msglen: int,
ack: ptr byte, acklen: int,
secret: var ConnectionSecret): AuthStatus =
var
shsec: SharedSecret
ctx0: keccak256
ctx1: keccak256
digest: array[keccak256.sizeDigest, byte]
mac1: array[keccak256.sizeDigest, byte]
mac2: array[keccak256.sizeDigest, byte]
xornonce: Nonce
# ecdhe-secret = ecdh.agree(ephemeral-privkey, remote-ephemeral-pubk)
if ecdhAgree(h.ephemeral.seckey, h.remoteEPubkey, shsec) != EccStatus.Success:
return(EcdhError)
# shared-secret = keccak(ecdhe-secret || keccak(nonce || initiator-nonce))
ctx0.init()
ctx1.init()
ctx1.update(addr h.responderNonce[0], uint(len(h.responderNonce)))
ctx1.update(addr h.initiatorNonce[0], uint(len(h.initiatorNonce)))
digest = ctx1.finish().data
ctx1.init() # clean keccak256 context
ctx0.update(addr shsec[0], uint(sizeof(SharedSecret)))
ctx0.update(addr digest[0], uint(keccak256.sizeDigest))
digest = ctx0.finish().data
# aes-secret = keccak(ecdhe-secret || shared-secret)
ctx0.init()
ctx0.update(addr shsec[0], uint(sizeof(SharedSecret)))
ctx0.update(addr digest[0], uint(keccak256.sizeDigest))
secret.aesKey = ctx0.finish().data
# mac-secret = keccak(ecdhe-secret || aes-secret)
ctx0.init()
ctx0.update(addr shsec[0], uint(sizeof(SharedSecret)))
ctx0.update(addr secret.aesKey[0], uint(keccak256.sizeDigest))
secret.macKey = ctx0.finish().data
zeroMem(addr shsec[0], sizeof(SharedSecret)) # clean ecdhe-secret
# egress-mac = keccak256(mac-secret ^ recipient-nonce || auth-sent-init)
xornonce = secret.macKey
xornonce.sxor(h.responderNonce)
ctx0.init()
ctx0.update(addr xornonce[0], uint(sizeof(Nonce)))
ctx0.update(msg, uint(msglen))
mac1 = ctx0.finish().data
# ingress-mac = keccak256(mac-secret ^ initiator-nonce || auth-recvd-ack)
xornonce = secret.macKey
xornonce.sxor(h.initiatorNonce)
ctx0.init()
ctx0.update(addr xornonce[0], uint(sizeof(Nonce)))
ctx0.update(ack, uint(acklen))
mac2 = ctx0.finish().data
ctx0.init() # clean keccak256 context
zeroMem(addr xornonce[0], sizeof(Nonce)) # clean xornonce
if Initiator in h.flags:
secret.egressMac = mac1
secret.ingressMac = mac2
else:
secret.ingressMac = mac1
secret.egressMac = mac2
zeroMem(addr mac1[0], keccak256.sizeDigest) # clean temporary mac1
zeroMem(addr mac2[0], keccak256.sizeDigest) # clean temporary mac2
result = Success
proc getSecrets*(h: var Handshake, msg: AuthMessage, ack: AuthAckMessage,
secret: var ConnectionSecret): AuthStatus =
result = getSecrets(h, unsafeAddr msg[0], AuthMessageLength,
unsafeAddr ack[0], AuthAckMessageLength,
secret)
proc decodeAuthEip8Message*(h: var Handshake, msg: ptr byte,
msglen: int): AuthStatus =
var var
pubkey: PublicKey pubkey: PublicKey
nonce: Nonce nonce: Nonce
size: uint16 size: uint16
secret: SharedSecret secret: SharedSecret
if msglen < 2: bigEndian16(addr size, unsafeAddr m[0])
return(InvalidAuth) if 2 + int(size) > len(m):
bigEndian16(addr size, msg) return(IncompleteError)
var buffer = newSeq[byte](eciesDecryptedLength(int(size)))
if (2 + int(size)) > msglen: if eciesDecrypt(toa(m, 2, int(size)), buffer, h.host.seckey,
return(InvalidAuth) toa(m, 0, 2)) != EciesStatus.Success:
# Maximum `size` value is 65535 bytes
var outlen = eciesDecryptedLength(int(size))
var output = newSeq[byte](outlen)
var input = cast[ptr UncheckedArray[byte]](msg)
if decryptAuthMessage(addr input[2], int(size), addr output[0],
outlen, h.host.seckey,
addr input[0], 2) != Success:
return(EciesError) return(EciesError)
try: try:
var reader = rlpFromBytes(output.toRange()) var reader = rlpFromBytes(buffer.toRange())
if not reader.isList() or reader.listLen() < 4: if not reader.isList() or reader.listLen() < 4:
return(InvalidAuth) return(InvalidAuth)
if reader.listElem(0).blobLen != SignatureLength: if reader.listElem(0).blobLen != SignatureLength:
@ -357,56 +355,42 @@ proc decodeAuthEip8Message*(h: var Handshake, msg: ptr byte,
return(InvalidAuth) return(InvalidAuth)
if reader.listElem(3).blobLen != 1: if reader.listElem(3).blobLen != 1:
return(InvalidAuth) return(InvalidAuth)
var signatureBr = reader.listElem(0).toBytes() var signatureBr = reader.listElem(0).toBytes()
var pubkeyBr = reader.listElem(1).toBytes() var pubkeyBr = reader.listElem(1).toBytes()
var nonceBr = reader.listElem(2).toBytes() var nonceBr = reader.listElem(2).toBytes()
var versionBr = reader.listElem(3).toBytes() var versionBr = reader.listElem(3).toBytes()
if recoverPublicKey(pubkeyBr.baseAddr, PublicKeyLength, if recoverPublicKey(pubkeyBr.baseAddr, PublicKeyLength,
pubkey) != EccStatus.Success: pubkey) != EccStatus.Success:
return(InvalidPubKey) return(InvalidPubKey)
copyMem(addr nonce[0], nonceBr.baseAddr, KeyLength) copyMem(addr nonce[0], nonceBr.baseAddr, KeyLength)
if ecdhAgree(h.host.seckey, pubkey, secret) != EccStatus.Success: if ecdhAgree(h.host.seckey, pubkey, secret) != EccStatus.Success:
return(EcdhError) return(EcdhError)
var xornonce = nonce var xornonce = nonce
xornonce.sxor(secret) xornonce.sxor(secret)
if recoverSignatureKey(signatureBr.baseAddr, SignatureLength, if recoverSignatureKey(signatureBr.baseAddr, SignatureLength,
addr xornonce[0], addr xornonce[0],
h.remoteEPubkey) != EccStatus.Success: h.remoteEPubkey) != EccStatus.Success:
return(SignatureError) return(SignatureError)
h.initiatorNonce = nonce h.initiatorNonce = nonce
h.remoteHPubkey = pubkey h.remoteHPubkey = pubkey
h.version = cast[ptr byte](versionBr.baseAddr)[] h.version = cast[ptr byte](versionBr.baseAddr)[]
result = Success result = Success
except: except:
return(RlpError) result = RlpError
proc decodeAuthAckEip8Message(h: var Handshake, msg: ptr byte, proc decodeAckMessageEip8*(h: var Handshake, m: openarray[byte]): AuthStatus =
msglen: int): AuthStatus = ## Decodes EIP-8 AckMessage.
var size: uint16 var size: uint16
if msglen < 2: assert(len(m) > 2)
bigEndian16(addr size, unsafeAddr m[0])
if 2 + int(size) > len(m):
return(IncompleteError) return(IncompleteError)
bigEndian16(addr size, msg) var buffer = newSeq[byte](eciesDecryptedLength(int(size)))
if eciesDecrypt(toa(m, 2, int(size)), buffer, h.host.seckey,
if (2 + int(size)) > msglen: toa(m, 0, 2)) != EciesStatus.Success:
return(IncompleteError)
# Maximum `size` value is 65535 bytes
var outlen = eciesDecryptedLength(int(size))
var output = newSeq[byte](outlen)
var input = cast[ptr UncheckedArray[byte]](msg)
if decryptAuthMessage(addr input[2], int(size), addr output[0],
outlen, h.host.seckey,
addr input[0], 2) != Success:
return(EciesError) return(EciesError)
try: try:
var reader = rlpFromBytes(output.toRange()) var reader = rlpFromBytes(buffer.toRange())
if not reader.isList() or reader.listLen() < 3: if not reader.isList() or reader.listLen() < 3:
return(InvalidAck) return(InvalidAck)
if reader.listElem(0).blobLen != PublicKeyLength: if reader.listElem(0).blobLen != PublicKeyLength:
@ -418,7 +402,6 @@ proc decodeAuthAckEip8Message(h: var Handshake, msg: ptr byte,
let pubkeyBr = reader.listElem(0).toBytes() let pubkeyBr = reader.listElem(0).toBytes()
let nonceBr = reader.listElem(1).toBytes() let nonceBr = reader.listElem(1).toBytes()
let versionBr = reader.listElem(2).toBytes() let versionBr = reader.listElem(2).toBytes()
if recoverPublicKey(pubkeyBr.baseAddr, PublicKeyLength, if recoverPublicKey(pubkeyBr.baseAddr, PublicKeyLength,
h.remoteEPubkey) != EccStatus.Success: h.remoteEPubkey) != EccStatus.Success:
return(InvalidPubKey) return(InvalidPubKey)
@ -426,43 +409,128 @@ proc decodeAuthAckEip8Message(h: var Handshake, msg: ptr byte,
h.version = cast[ptr byte](versionBr.baseAddr)[] h.version = cast[ptr byte](versionBr.baseAddr)[]
result = Success result = Success
except: except:
return(RlpError) result = RlpError
proc decodeAuthMessage*(h: var Handshake, msg: ptr byte, proc decodeAckMessageV4(h: var Handshake, m: openarray[byte]): AuthStatus =
msglen: int): AuthStatus = ## Decodes V4 AckMessage.
if msglen < AuthMessageLength: var
return(IncompleteError) buffer: array[PlainAckMessageV4Length, byte]
elif msglen == AuthMessageLength: assert(Initiator in h.flags)
# Decoding plain authentication message if eciesDecrypt(m, buffer, h.host.seckey) != EciesStatus.Success:
var plain: PlainAuthMessage return(EciesError)
result = decryptAuthMessage(msg, msglen, addr plain[0], var header = cast[ptr AckMessageV4](addr buffer[0])
sizeof(PlainAuthMessage), h.host.seckey) if recoverPublicKey(header.pubkey.data, h.remoteEPubkey) != EccStatus.Success:
if result == Success: return(InvalidPubKey)
result = decodePlainAuthMessage(h, plain) h.responderNonce = header.nonce
result = Success
proc decodeAuthMessage*(h: var Handshake, input: openarray[byte]): AuthStatus =
## Decodes AuthMessage from `input`.
if len(input) < AuthMessageV4Length:
result = IncompleteError
elif len(input) == AuthMessageV4Length:
let res = h.decodeAuthMessageV4(input)
if res != Success:
if h.decodeAuthMessageEip8(input) != Success:
result = res
else:
h.flags.incl(EIP8)
result = Success
else:
result = Success
else: else:
# Decoding EIP-8 authentication message result = h.decodeAuthMessageEip8(input)
result = decodeAuthEip8Message(h, msg, msglen)
if result == Success: if result == Success:
h.flags.incl(EIP8) h.flags.incl(EIP8)
proc decodeAckMessage*(h: var Handshake, msg: ptr byte, proc decodeAckMessage*(h: var Handshake, input: openarray[byte]): AuthStatus =
msglen: int): AuthStatus = ## Decodes AckMessage from `input`.
if msglen < AuthAckMessageLength: if len(input) < AckMessageV4Length:
return(IncompleteError) return(IncompleteError)
elif msglen == AuthAckMessageLength: elif len(input) == AckMessageV4Length:
# Decoding plain authentication ACK message let res = h.decodeAckMessageV4(input)
var plain: PlainAuthAckMessage if res != Success:
result = decryptAuthAckMessage(msg, msglen, addr plain[0], if h.decodeAckMessageEip8(input) != Success:
PlainAuthAckMessageLength, result = res
h.host.seckey) else:
if result == Success: h.flags.incl(EIP8)
result = decodePlainAuthAckMessage(h, plain) result = Success
else:
result = Success
else: else:
# Decoding EIP-8 ACK authentication message result = h.decodeAckMessageEip8(input)
result = decodeAuthAckEip8Message(h, msg, msglen) if result == Success:
h.flags.incl(EIP8)
proc decodeAuthMessage*(h: var Handshake, msg: openarray[byte]): AuthStatus = proc getSecrets*(h: Handshake, authmsg: openarray[byte],
result = decodeAuthMessage(h, unsafeAddr msg[0], len(msg)) ackmsg: openarray[byte],
secret: var ConnectionSecret): AuthStatus =
## Derive secrets from handshake `h` using encrypted AuthMessage `authmsg` and
## encrypted AckMessage `ackmsg`.
var
shsec: SharedSecret
ctx0: keccak256
ctx1: keccak256
mac1: MDigest[256]
mac2: MDigest[256]
xornonce: Nonce
proc decodeAckMessage*(h: var Handshake, msg: openarray[byte]): AuthStatus = # ecdhe-secret = ecdh.agree(ephemeral-privkey, remote-ephemeral-pubk)
result = decodeAckMessage(h, unsafeAddr msg[0], len(msg)) if ecdhAgree(h.ephemeral.seckey, h.remoteEPubkey, shsec) != EccStatus.Success:
return(EcdhError)
# shared-secret = keccak(ecdhe-secret || keccak(nonce || initiator-nonce))
ctx0.init()
ctx1.init()
ctx1.update(h.responderNonce)
ctx1.update(h.initiatorNonce)
mac1 = ctx1.finish()
ctx1.clear()
ctx0.update(shsec)
ctx0.update(mac1.data)
mac1 = ctx0.finish()
# aes-secret = keccak(ecdhe-secret || shared-secret)
ctx0.init()
ctx0.update(shsec)
ctx0.update(mac1.data)
mac1 = ctx0.finish()
# mac-secret = keccak(ecdhe-secret || aes-secret)
ctx0.init()
ctx0.update(shsec)
ctx0.update(mac1.data)
secret.aesKey = mac1.data
mac1 = ctx0.finish()
secret.macKey = mac1.data
burnMem(shsec)
# egress-mac = keccak256(mac-secret ^ recipient-nonce || auth-sent-init)
xornonce = mac1.data
xornonce.sxor(h.responderNonce)
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
if Initiator in h.flags:
secret.egressMac = mac1.data
secret.ingressMac = mac2.data
else:
secret.ingressMac = mac1.data
secret.egressMac = mac2.data
burnMem(mac1)
burnMem(mac2)
result = Success

View File

@ -213,96 +213,106 @@ proc testE8Value(s: string): string =
suite "Ethereum P2P handshake test suite": suite "Ethereum P2P handshake test suite":
block: block:
var initiator: Handshake proc newTestHandshake(flags: set[HandshakeFlag]): Handshake =
var receiver: Handshake result = newHandshake(flags)
var m0, dm0: PlainAuthMessage if Initiator in flags:
var em0: AuthMessage result.host.seckey = getPrivateKey(testValue("initiator_private_key"))
result.host.pubkey = result.host.seckey.getPublicKey()
initiator = newHandshake({Initiator}) let epki = testValue("initiator_ephemeral_private_key")
receiver = newHandshake({Responder}) result.ephemeral.seckey = getPrivateKey(epki)
initiator.host.seckey = getPrivateKey(testValue("initiator_private_key")) result.ephemeral.pubkey = result.ephemeral.seckey.getPublicKey()
initiator.host.pubkey = initiator.host.seckey.getPublicKey() let nonce = fromHex(stripSpaces(testValue("initiator_nonce")))
var epki = testValue("initiator_ephemeral_private_key") result.initiatorNonce[0..^1] = nonce[0..^1]
initiator.ephemeral.seckey = getPrivateKey(epki) elif Responder in flags:
initiator.ephemeral.pubkey = initiator.ephemeral.seckey.getPublicKey() result.host.seckey = getPrivateKey(testValue("receiver_private_key"))
receiver.host.seckey = getPrivateKey(testValue("receiver_private_key")) result.host.pubkey = result.host.seckey.getPublicKey()
receiver.host.pubkey = receiver.host.seckey.getPublicKey() let epkr = testValue("receiver_ephemeral_private_key")
var epkr = testValue("receiver_ephemeral_private_key") result.ephemeral.seckey = getPrivateKey(epkr)
receiver.ephemeral.seckey = getPrivateKey(epkr) result.ephemeral.pubkey = result.ephemeral.seckey.getPublicKey()
receiver.ephemeral.pubkey = receiver.ephemeral.seckey.getPublicKey() let nonce = fromHex(stripSpaces(testValue("receiver_nonce")))
var n0 = fromHex(stripSpaces(testValue("initiator_nonce"))) result.responderNonce[0..^1] = nonce[0..^1]
initiator.initiatorNonce[0..^1] = n0[0..^1]
var n1 = fromHex(stripSpaces(testValue("receiver_nonce")))
receiver.responderNonce[0..^1] = n1[0..^1]
test "Create plain auth message": test "Create plain auth message":
check authMessage(initiator, receiver.host.pubkey, var initiator = newTestHandshake({Initiator})
m0) == AuthStatus.Success var responder = newTestHandshake({Responder})
var m1 = fromHex(stripSpaces(testValue("auth_plaintext"))) var m0 = newSeq[byte](initiator.authSize(false))
var m2 = fromHex(stripSpaces(pyevmAuth)) var k0 = 0
check: check:
m0[65..^1] == m1[65..^1] initiator.authMessage(responder.host.pubkey,
m0[0..^1] == m2[0..^1] m0, k0, 0, false) == AuthStatus.Success
var expect1 = fromHex(stripSpaces(testValue("auth_plaintext")))
test "Auth message encrypt/decrypt": var expect2 = fromHex(stripSpaces(pyevmAuth))
# Check that encrypting and decrypting the auth_init gets us the orig msg.
check: check:
encryptAuthMessage(m0, em0, receiver.host.pubkey) == AuthStatus.Success m0[65..^1] == expect1[65..^1]
decryptAuthMessage(em0, dm0, receiver.host.seckey) == AuthStatus.Success m0[0..^1] == expect2[0..^1]
m0[0..^1] == dm0[0..^1]
test "Auth message decode": test "Auth message decoding":
# Check that the responder correctly decodes the auth msg. var initiator = newTestHandshake({Initiator})
check receiver.decodeAuthMessage(em0) == AuthStatus.Success var responder = newTestHandshake({Responder})
var m0 = newSeq[byte](initiator.authSize())
var k0 = 0
let remoteEPubkey0 = initiator.ephemeral.pubkey.data let remoteEPubkey0 = initiator.ephemeral.pubkey.data
let remoteHPubkey0 = initiator.host.pubkey.data let remoteHPubkey0 = initiator.host.pubkey.data
check: check:
receiver.initiatorNonce[0..^1] == n0[0..^1] initiator.authMessage(responder.host.pubkey,
receiver.remoteEPubkey.data[0..^1] == remoteEPubkey0[0..^1] m0, k0) == AuthStatus.Success
receiver.remoteHPubkey.data[0..^1] == remoteHPubkey0[0..^1] responder.decodeAuthMessage(m0) == AuthStatus.Success
responder.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1]
responder.remoteEPubkey.data[0..^1] == remoteEPubkey0[0..^1]
responder.remoteHPubkey.data[0..^1] == remoteHPubkey0[0..^1]
var k0: PlainAuthAckMessage test "ACK message expectation":
var ek0: AuthAckMessage var initiator = newTestHandshake({Initiator})
var responder = newTestHandshake({Responder})
test "Auth ACK expectation": var m0 = newSeq[byte](initiator.authSize())
# Check that the auth_ack msg generated by the responder is what we var m1 = newSeq[byte](responder.ackSize(false))
# expect. var k0 = 0
check receiver.authAckMessage(k0) == AuthStatus.Success var k1 = 0
var ac0 = fromHex(stripSpaces(testValue("authresp_plaintext"))) var expect0 = fromHex(stripSpaces(testValue("authresp_plaintext")))
check: check:
k0[0..^1] == ac0[0..^1] initiator.authMessage(responder.host.pubkey,
receiver.initiatorNonce[0..^1] == n0[0..^1] m0, k0) == AuthStatus.Success
encryptAuthAckMessage(k0, ek0, responder.decodeAuthMessage(m0) == AuthStatus.Success
receiver.remoteHPubkey) == AuthStatus.Success responder.ackMessage(m1, k1, 0, false) == AuthStatus.Success
m1 == expect0
responder.initiatorNonce == initiator.initiatorNonce
test "Initiator decode Auth ACK message": test "ACK message decoding":
# Check if initiator correctly decodes the auth ack msg. var initiator = newTestHandshake({Initiator})
check initiator.decodeAckMessage(ek0) == AuthStatus.Success var responder = newTestHandshake({Responder})
let remoteEPubkey1 = receiver.ephemeral.pubkey.data var m0 = newSeq[byte](initiator.authSize())
let remoteHPubkey1 = receiver.host.pubkey.data var m1 = newSeq[byte](responder.ackSize())
var k0 = 0
var k1 = 0
check: check:
initiator.remoteEPubkey.data[0..^1] == remoteEPubkey1[0..^1] initiator.authMessage(responder.host.pubkey,
initiator.remoteHPubkey.data[0..^1] == remoteHPubkey1[0..^1] m0, k0) == AuthStatus.Success
initiator.responderNonce[0..^1] == n1[0..^1] responder.decodeAuthMessage(m0) == AuthStatus.Success
responder.ackMessage(m1, k1) == AuthStatus.Success
initiator.decodeAckMessage(m1) == AuthStatus.Success
let remoteEPubkey0 = responder.ephemeral.pubkey.data
let remoteHPubkey0 = responder.host.pubkey.data
check:
initiator.remoteEPubkey.data[0..^1] == remoteEPubkey0[0..^1]
initiator.remoteHPubkey.data[0..^1] == remoteHPubkey0[0..^1]
initiator.responderNonce == responder.responderNonce
test "Check derived secrets": test "Check derived secrets":
# Check that the secrets derived from ephemeral key agreements match var initiator = newTestHandshake({Initiator})
# the expected values. var responder = newTestHandshake({Responder})
var authm = fromHex(stripSpaces(testValue("auth_ciphertext"))) var authm = fromHex(stripSpaces(testValue("auth_ciphertext")))
var ackm = fromHex(stripSpaces(testValue("authresp_ciphertext"))) var ackm = fromHex(stripSpaces(testValue("authresp_ciphertext")))
var taes = fromHex(stripSpaces(testValue("aes_secret"))) var taes = fromHex(stripSpaces(testValue("aes_secret")))
var tmac = fromHex(stripSpaces(testValue("mac_secret"))) var tmac = fromHex(stripSpaces(testValue("mac_secret")))
var temac = fromHex(stripSpaces(testValue("initial_egress_MAC"))) var temac = fromHex(stripSpaces(testValue("initial_egress_MAC")))
var timac = fromHex(stripSpaces(testValue("initial_ingress_MAC"))) var timac = fromHex(stripSpaces(testValue("initial_ingress_MAC")))
var csecInitiator: ConnectionSecret var csecInitiator: ConnectionSecret
var csecResponder: ConnectionSecret var csecResponder: ConnectionSecret
check: check:
initiator.getSecrets(addr authm[0], len(authm), addr ackm[0], responder.decodeAuthMessage(authm) == AuthStatus.Success
len(ackm), csecInitiator) == AuthStatus.Success initiator.decodeAckMessage(ackm) == AuthStatus.Success
receiver.getSecrets(addr authm[0], len(authm), addr ackm[0], initiator.getSecrets(authm, ackm, csecInitiator) == AuthStatus.Success
len(ackm), csecResponder) == AuthStatus.Success responder.getSecrets(authm, ackm, csecResponder) == AuthStatus.Success
csecInitiator.aesKey == csecResponder.aesKey csecInitiator.aesKey == csecResponder.aesKey
csecInitiator.macKey == csecResponder.macKey csecInitiator.macKey == csecResponder.macKey
taes[0..^1] == csecInitiator.aesKey[0..^1] taes[0..^1] == csecInitiator.aesKey[0..^1]
@ -322,7 +332,7 @@ suite "Ethereum P2P handshake test suite":
result.ephemeral.seckey = getPrivateKey(esec) result.ephemeral.seckey = getPrivateKey(esec)
result.ephemeral.pubkey = result.ephemeral.seckey.getPublicKey() result.ephemeral.pubkey = result.ephemeral.seckey.getPublicKey()
let nonce = fromHex(stripSpaces(testE8Value("initiator_nonce"))) let nonce = fromHex(stripSpaces(testE8Value("initiator_nonce")))
result.initiatorNonce[0..(KeyLength - 1)] = nonce[0..(KeyLength - 1)] result.initiatorNonce[0..^1] = nonce[0..^1]
elif Responder in flags: elif Responder in flags:
result.host.seckey = getPrivateKey(testE8Value("receiver_private_key")) result.host.seckey = getPrivateKey(testE8Value("receiver_private_key"))
result.host.pubkey = result.host.seckey.getPublicKey() result.host.pubkey = result.host.seckey.getPublicKey()
@ -330,67 +340,53 @@ suite "Ethereum P2P handshake test suite":
result.ephemeral.seckey = getPrivateKey(esec) result.ephemeral.seckey = getPrivateKey(esec)
result.ephemeral.pubkey = result.ephemeral.seckey.getPublicKey() result.ephemeral.pubkey = result.ephemeral.seckey.getPublicKey()
let nonce = fromHex(stripSpaces(testE8Value("receiver_nonce"))) let nonce = fromHex(stripSpaces(testE8Value("receiver_nonce")))
result.responderNonce[0..(KeyLength - 1)] = nonce[0..(KeyLength - 1)] result.responderNonce[0..^1] = nonce[0..^1]
test "AUTH/ACK v4 test vectors": # auth/ack v4 test "AUTH/ACK v4 test vectors": # auth/ack v4
var initiator = newTestHandshake({Initiator}) var initiator = newTestHandshake({Initiator})
var receiver = newTestHandshake({Responder}) var responder = newTestHandshake({Responder})
# Check that the responder correctly decodes the auth msg.
var m0 = fromHex(stripSpaces(testE8Value("auth_ciphertext_v4"))) var m0 = fromHex(stripSpaces(testE8Value("auth_ciphertext_v4")))
check: check:
receiver.decodeAuthMessage(m0) == AuthStatus.Success responder.decodeAuthMessage(m0) == AuthStatus.Success
receiver.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1] responder.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1]
let remoteEPubkey0 = initiator.ephemeral.pubkey.data let remoteEPubkey0 = initiator.ephemeral.pubkey.data
check receiver.remoteEPubkey.data[0..^1] == remoteEPubkey0[0..^1]
let remoteHPubkey0 = initiator.host.pubkey.data let remoteHPubkey0 = initiator.host.pubkey.data
check receiver.remoteHPubkey.data[0..^1] == remoteHPubkey0[0..^1] check:
responder.remoteEPubkey.data[0..^1] == remoteEPubkey0[0..^1]
# Check that the initiator correctly decodes the auth ack msg. responder.remoteHPubkey.data[0..^1] == remoteHPubkey0[0..^1]
var m1 = fromHex(stripSpaces(testE8Value("authack_ciphertext_v4"))) var m1 = fromHex(stripSpaces(testE8Value("authack_ciphertext_v4")))
check initiator.decodeAckMessage(m1) == AuthStatus.Success check initiator.decodeAckMessage(m1) == AuthStatus.Success
let remoteEPubkey1 = responder.ephemeral.pubkey.data
let remoteEPubkey1 = receiver.ephemeral.pubkey.data
check: check:
initiator.remoteEPubkey.data[0..^1] == remoteEPubkey1[0..^1] initiator.remoteEPubkey.data[0..^1] == remoteEPubkey1[0..^1]
initiator.responderNonce[0..^1] == receiver.responderNonce[0..^1] initiator.responderNonce[0..^1] == responder.responderNonce[0..^1]
test "AUTH/ACK EIP-8 test vectors": test "AUTH/ACK EIP-8 test vectors":
var initiator = newTestHandshake({Initiator}) var initiator = newTestHandshake({Initiator})
var receiver = newTestHandshake({Responder}) var responder = newTestHandshake({Responder})
# Check that the responder correctly decodes the auth msg.
var m0 = fromHex(stripSpaces(testE8Value("auth_ciphertext_eip8"))) var m0 = fromHex(stripSpaces(testE8Value("auth_ciphertext_eip8")))
check: check:
receiver.decodeAuthMessage(m0) == AuthStatus.Success responder.decodeAuthMessage(m0) == AuthStatus.Success
receiver.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1] responder.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1]
let remoteEPubkey0 = initiator.ephemeral.pubkey.data let remoteEPubkey0 = initiator.ephemeral.pubkey.data
check receiver.remoteEPubkey.data[0..^1] == remoteEPubkey0[0..^1] check responder.remoteEPubkey.data[0..^1] == remoteEPubkey0[0..^1]
let remoteHPubkey0 = initiator.host.pubkey.data let remoteHPubkey0 = initiator.host.pubkey.data
check receiver.remoteHPubkey.data[0..^1] == remoteHPubkey0[0..^1] check responder.remoteHPubkey.data[0..^1] == remoteHPubkey0[0..^1]
# Check that the initiator correctly decodes the auth ack msg.
var m1 = fromHex(stripSpaces(testE8Value("authack_ciphertext_eip8"))) var m1 = fromHex(stripSpaces(testE8Value("authack_ciphertext_eip8")))
check initiator.decodeAckMessage(m1) == AuthStatus.Success check initiator.decodeAckMessage(m1) == AuthStatus.Success
let remoteEPubkey1 = responder.ephemeral.pubkey.data
let remoteEPubkey1 = receiver.ephemeral.pubkey.data
check: check:
initiator.remoteEPubkey.data[0..^1] == remoteEPubkey1[0..^1] initiator.remoteEPubkey.data[0..^1] == remoteEPubkey1[0..^1]
initiator.responderNonce[0..^1] == receiver.responderNonce[0..^1] initiator.responderNonce[0..^1] == responder.responderNonce[0..^1]
# Check that the secrets derived from ephemeral key agreements match
# the expected values.
var taes = fromHex(stripSpaces(testE8Value("auth2ack2_aes_secret"))) var taes = fromHex(stripSpaces(testE8Value("auth2ack2_aes_secret")))
var tmac = fromHex(stripSpaces(testE8Value("auth2ack2_mac_secret"))) var tmac = fromHex(stripSpaces(testE8Value("auth2ack2_mac_secret")))
var csecInitiator: ConnectionSecret var csecInitiator: ConnectionSecret
var csecResponder: ConnectionSecret var csecResponder: ConnectionSecret
check: check:
initiator.getSecrets(addr m0[0], len(m0), addr m1[0], int(initiator.version) == 4
len(m1), csecInitiator) == AuthStatus.Success int(responder.version) == 4
receiver.getSecrets(addr m0[0], len(m0), addr m1[0], initiator.getSecrets(m0, m1, csecInitiator) == AuthStatus.Success
len(m1), csecResponder) == AuthStatus.Success responder.getSecrets(m0, m1, csecResponder) == AuthStatus.Success
csecInitiator.aesKey == csecResponder.aesKey csecInitiator.aesKey == csecResponder.aesKey
csecInitiator.macKey == csecResponder.macKey csecInitiator.macKey == csecResponder.macKey
taes[0..^1] == csecInitiator.aesKey[0..^1] taes[0..^1] == csecInitiator.aesKey[0..^1]
@ -398,23 +394,69 @@ suite "Ethereum P2P handshake test suite":
test "AUTH/ACK EIP-8 with additional fields test vectors": test "AUTH/ACK EIP-8 with additional fields test vectors":
var initiator = newTestHandshake({Initiator}) var initiator = newTestHandshake({Initiator})
var receiver = newTestHandshake({Responder}) var responder = newTestHandshake({Responder})
# Check that the responder correctly decodes the auth msg.
var m0 = fromHex(stripSpaces(testE8Value("auth_ciphertext_eip8_3f"))) var m0 = fromHex(stripSpaces(testE8Value("auth_ciphertext_eip8_3f")))
check: check:
receiver.decodeAuthMessage(m0) == AuthStatus.Success responder.decodeAuthMessage(m0) == AuthStatus.Success
receiver.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1] responder.initiatorNonce[0..^1] == initiator.initiatorNonce[0..^1]
let remoteEPubkey0 = initiator.ephemeral.pubkey.data let remoteEPubkey0 = initiator.ephemeral.pubkey.data
check receiver.remoteEPubkey.data[0..^1] == remoteEPubkey0[0..^1]
let remoteHPubkey0 = initiator.host.pubkey.data let remoteHPubkey0 = initiator.host.pubkey.data
check receiver.remoteHPubkey.data[0..^1] == remoteHPubkey0[0..^1] check:
responder.remoteEPubkey.data[0..^1] == remoteEPubkey0[0..^1]
# Check that the initiator correctly decodes the auth ack msg. responder.remoteHPubkey.data[0..^1] == remoteHPubkey0[0..^1]
var m1 = fromHex(stripSpaces(testE8Value("authack_ciphertext_eip8_3f"))) var m1 = fromHex(stripSpaces(testE8Value("authack_ciphertext_eip8_3f")))
check initiator.decodeAckMessage(m1) == AuthStatus.Success check initiator.decodeAckMessage(m1) == AuthStatus.Success
let remoteEPubkey1 = responder.ephemeral.pubkey.data
let remoteEPubkey1 = receiver.ephemeral.pubkey.data
check: check:
int(initiator.version) == 57
int(responder.version) == 56
initiator.remoteEPubkey.data[0..^1] == remoteEPubkey1[0..^1] initiator.remoteEPubkey.data[0..^1] == remoteEPubkey1[0..^1]
initiator.responderNonce[0..^1] == receiver.responderNonce[0..^1] initiator.responderNonce[0..^1] == responder.responderNonce[0..^1]
test "100 AUTH/ACK EIP-8 handshakes":
for i in 1..100:
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
m0.setLen(k0)
check responder.decodeAuthMessage(m0) == AuthStatus.Success
check (EIP8 in responder.flags) == true
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
csecInitiator.aesKey == csecResponder.aesKey
csecInitiator.macKey == csecResponder.macKey
test "100 AUTH/ACK V4 handshakes":
for i in 1..100:
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
csecInitiator.aesKey == csecResponder.aesKey
csecInitiator.macKey == csecResponder.macKey