mirror of https://github.com/status-im/nim-eth.git
Update to support the latest discv5.1 specification
This commit is contained in:
parent
820a73f96f
commit
17ef0b25e0
|
@ -8,17 +8,19 @@ export keys
|
||||||
{.push raises: [Defect].}
|
{.push raises: [Defect].}
|
||||||
|
|
||||||
const
|
const
|
||||||
version: uint8 = 1
|
version: uint16 = 1
|
||||||
idNoncePrefix = "discovery-id-nonce"
|
idNoncePrefix = "discovery-id-nonce"
|
||||||
|
idSignatureText = "discovery v5 identity proof"
|
||||||
keyAgreementPrefix = "discovery v5 key agreement"
|
keyAgreementPrefix = "discovery v5 key agreement"
|
||||||
protocolIdStr = "discv5 "
|
protocolIdStr = "discv5"
|
||||||
protocolId = toBytes(protocolIdStr)
|
protocolId = toBytes(protocolIdStr)
|
||||||
gcmNonceSize* = 12
|
gcmNonceSize* = 12
|
||||||
idNonceSize* = 32
|
idNonceSize* = 16
|
||||||
gcmTagSize* = 16
|
gcmTagSize* = 16
|
||||||
ivSize = 16
|
ivSize* = 16
|
||||||
staticHeaderSize = protocolId.len + sizeof(NodeId) + 1 + 2
|
staticHeaderSize = protocolId.len + 2 + 2 + 1 + gcmNonceSize
|
||||||
authdataHeadSize = 1 + gcmNonceSize + 1 + 1
|
authdataHeadSize = sizeof(NodeId) + 1 + 1
|
||||||
|
whoareyouSize = ivSize + staticHeaderSize + idNonceSize + 8
|
||||||
|
|
||||||
type
|
type
|
||||||
AESGCMNonce* = array[gcmNonceSize, byte]
|
AESGCMNonce* = array[gcmNonceSize, byte]
|
||||||
|
@ -26,16 +28,17 @@ type
|
||||||
|
|
||||||
WhoareyouData* = object
|
WhoareyouData* = object
|
||||||
requestNonce*: AESGCMNonce
|
requestNonce*: AESGCMNonce
|
||||||
idNonce*: IdNonce
|
idNonce*: IdNonce # TODO: This data is also available in challengeData
|
||||||
recordSeq*: uint64
|
recordSeq*: uint64
|
||||||
|
challengeData*: seq[byte]
|
||||||
|
|
||||||
Challenge* = object
|
Challenge* = object
|
||||||
whoareyouData*: WhoareyouData
|
whoareyouData*: WhoareyouData
|
||||||
pubkey*: Option[PublicKey]
|
pubkey*: Option[PublicKey]
|
||||||
|
|
||||||
StaticHeader* = object
|
StaticHeader* = object
|
||||||
srcId: NodeId
|
|
||||||
flag: Flag
|
flag: Flag
|
||||||
|
nonce: AESGCMNonce
|
||||||
authdataSize: uint16
|
authdataSize: uint16
|
||||||
|
|
||||||
HandshakeSecrets* = object
|
HandshakeSecrets* = object
|
||||||
|
@ -52,13 +55,14 @@ type
|
||||||
of OrdinaryMessage:
|
of OrdinaryMessage:
|
||||||
messageOpt*: Option[Message]
|
messageOpt*: Option[Message]
|
||||||
requestNonce*: AESGCMNonce
|
requestNonce*: AESGCMNonce
|
||||||
|
srcId*: NodeId
|
||||||
of Whoareyou:
|
of Whoareyou:
|
||||||
whoareyou*: WhoareyouData
|
whoareyou*: WhoareyouData
|
||||||
of HandshakeMessage:
|
of HandshakeMessage:
|
||||||
message*: Message # In a handshake we expect to always be able to decrypt
|
message*: Message # In a handshake we expect to always be able to decrypt
|
||||||
# TODO record or node immediately?
|
# TODO record or node immediately?
|
||||||
node*: Option[Node]
|
node*: Option[Node]
|
||||||
srcId*: NodeId
|
srcIdHs*: NodeId
|
||||||
|
|
||||||
Codec* = object
|
Codec* = object
|
||||||
localNode*: Node
|
localNode*: Node
|
||||||
|
@ -79,21 +83,23 @@ proc mapErrTo[T, E](r: Result[T, E], v: static DecodeError):
|
||||||
DecodeResult[T] =
|
DecodeResult[T] =
|
||||||
r.mapErr(proc (e: E): DecodeError = v)
|
r.mapErr(proc (e: E): DecodeError = v)
|
||||||
|
|
||||||
proc idNonceHash(nonce, ephkey: openarray[byte]): MDigest[256] =
|
proc idNonceHash(challengeData, ephkey: openarray[byte], nodeId: NodeId):
|
||||||
|
MDigest[256] =
|
||||||
var ctx: sha256
|
var ctx: sha256
|
||||||
ctx.init()
|
ctx.init()
|
||||||
ctx.update(idNoncePrefix)
|
ctx.update(idSignatureText)
|
||||||
ctx.update(nonce)
|
ctx.update(challengeData)
|
||||||
ctx.update(ephkey)
|
ctx.update(ephkey)
|
||||||
|
ctx.update(nodeId.toByteArrayBE())
|
||||||
result = ctx.finish()
|
result = ctx.finish()
|
||||||
ctx.clear()
|
ctx.clear()
|
||||||
|
|
||||||
proc signIDNonce*(privKey: PrivateKey, idNonce, ephKey: openarray[byte]):
|
proc signIDNonce*(privKey: PrivateKey, challengeData,
|
||||||
SignatureNR =
|
ephKey: openarray[byte], nodeId: NodeId): SignatureNR =
|
||||||
signNR(privKey, SkMessage(idNonceHash(idNonce, ephKey).data))
|
signNR(privKey, SkMessage(idNonceHash(challengeData, ephKey, nodeId).data))
|
||||||
|
|
||||||
proc deriveKeys*(n1, n2: NodeID, priv: PrivateKey, pub: PublicKey,
|
proc deriveKeys*(n1, n2: NodeID, priv: PrivateKey, pub: PublicKey,
|
||||||
idNonce: openarray[byte]): HandshakeSecrets =
|
challengeData: openarray[byte]): HandshakeSecrets =
|
||||||
let eph = ecdhRawFull(priv, pub)
|
let eph = ecdhRawFull(priv, pub)
|
||||||
|
|
||||||
var info = newSeqOfCap[byte](keyAgreementPrefix.len + 32 * 2)
|
var info = newSeqOfCap[byte](keyAgreementPrefix.len + 32 * 2)
|
||||||
|
@ -104,7 +110,9 @@ proc deriveKeys*(n1, n2: NodeID, priv: PrivateKey, pub: PublicKey,
|
||||||
var secrets: HandshakeSecrets
|
var secrets: HandshakeSecrets
|
||||||
static: assert(sizeof(secrets) == aesKeySize * 2)
|
static: assert(sizeof(secrets) == aesKeySize * 2)
|
||||||
var res = cast[ptr UncheckedArray[byte]](addr secrets)
|
var res = cast[ptr UncheckedArray[byte]](addr secrets)
|
||||||
hkdf(sha256, eph.data, idNonce, info, toOpenArray(res, 0, sizeof(secrets) - 1))
|
|
||||||
|
hkdf(sha256, eph.data, challengeData, info,
|
||||||
|
toOpenArray(res, 0, sizeof(secrets) - 1))
|
||||||
secrets
|
secrets
|
||||||
|
|
||||||
proc encryptGCM*(key, nonce, pt, authData: openarray[byte]): seq[byte] =
|
proc encryptGCM*(key, nonce, pt, authData: openarray[byte]): seq[byte] =
|
||||||
|
@ -141,21 +149,29 @@ proc encryptHeader*(id: NodeId, iv, header: openarray[byte]): seq[byte] =
|
||||||
ectx.encrypt(header, result)
|
ectx.encrypt(header, result)
|
||||||
ectx.clear()
|
ectx.clear()
|
||||||
|
|
||||||
proc encodeStaticHeader*(srcId: NodeId, flag: Flag, authSize: int): seq[byte] =
|
proc hasHandshake*(c: Codec, key: HandShakeKey): bool =
|
||||||
|
c.handshakes.hasKey(key)
|
||||||
|
|
||||||
|
proc encodeStaticHeader*(flag: Flag, nonce: AESGCMNonce, authSize: int):
|
||||||
|
seq[byte] =
|
||||||
result.add(protocolId)
|
result.add(protocolId)
|
||||||
result.add(srcId.toByteArrayBE())
|
result.add(version.toBytesBE())
|
||||||
result.add(byte(flag))
|
result.add(byte(flag))
|
||||||
|
result.add(nonce)
|
||||||
# TODO: assert on authSize of > 2^16?
|
# TODO: assert on authSize of > 2^16?
|
||||||
result.add((uint16(authSize)).toBytesBE())
|
result.add((uint16(authSize)).toBytesBE())
|
||||||
|
|
||||||
proc encodeMessagePacket*(rng: var BrHmacDrbgContext, c: var Codec,
|
proc encodeMessagePacket*(rng: var BrHmacDrbgContext, c: var Codec,
|
||||||
toId: NodeID, toAddr: Address, message: openarray[byte]):
|
toId: NodeID, toAddr: Address, message: openarray[byte]):
|
||||||
(seq[byte], AESGCMNonce) =
|
(seq[byte], AESGCMNonce) =
|
||||||
var authdata: AESGCMNonce
|
var nonce: AESGCMNonce
|
||||||
brHmacDrbgGenerate(rng, authdata) # Random AESGCM nonce
|
brHmacDrbgGenerate(rng, nonce) # Random AESGCM nonce
|
||||||
|
var iv: array[ivSize, byte]
|
||||||
|
brHmacDrbgGenerate(rng, iv) # Random IV
|
||||||
|
|
||||||
# static-header
|
# static-header
|
||||||
let staticHeader = encodeStaticHeader(c.localNode.id, Flag.OrdinaryMessage,
|
let authdata = c.localNode.id.toByteArrayBE()
|
||||||
|
let staticHeader = encodeStaticHeader(Flag.OrdinaryMessage, nonce,
|
||||||
authdata.len())
|
authdata.len())
|
||||||
# header = static-header || authdata
|
# header = static-header || authdata
|
||||||
var header: seq[byte]
|
var header: seq[byte]
|
||||||
|
@ -166,20 +182,18 @@ proc encodeMessagePacket*(rng: var BrHmacDrbgContext, c: var Codec,
|
||||||
var messageEncrypted: seq[byte]
|
var messageEncrypted: seq[byte]
|
||||||
var writeKey, readKey: AesKey
|
var writeKey, readKey: AesKey
|
||||||
if c.sessions.load(toId, toAddr, readKey, writeKey):
|
if c.sessions.load(toId, toAddr, readKey, writeKey):
|
||||||
messageEncrypted = encryptGCM(writeKey, authdata, message, header)
|
messageEncrypted = encryptGCM(writeKey, nonce, message, @iv & header)
|
||||||
else:
|
else:
|
||||||
# We might not have the node's keys if the handshake hasn't been performed
|
# We might not have the node's keys if the handshake hasn't been performed
|
||||||
# yet. That's fine, we send a random-packet and we will be responded with
|
# yet. That's fine, we send a random-packet and we will be responded with
|
||||||
# a WHOAREYOU packet.
|
# a WHOAREYOU packet.
|
||||||
# TODO: What is minimum size of an encrypted message that we should provided
|
# TODO Minumum packet size is 63, we have here 16 + 23 + 32 = 71, so in theory
|
||||||
# here?
|
# we don't need to add random data? But then how do we know if decryption
|
||||||
var randomData: array[44, byte]
|
# fails. Empty message automatically means -> whoareyou?
|
||||||
|
var randomData: array[8, byte]
|
||||||
brHmacDrbgGenerate(rng, randomData)
|
brHmacDrbgGenerate(rng, randomData)
|
||||||
messageEncrypted.add(randomData)
|
messageEncrypted.add(randomData)
|
||||||
|
|
||||||
var iv: array[ivSize, byte]
|
|
||||||
brHmacDrbgGenerate(rng, iv) # Random IV
|
|
||||||
|
|
||||||
let maskedHeader = encryptHeader(toId, iv, header)
|
let maskedHeader = encryptHeader(toId, iv, header)
|
||||||
|
|
||||||
var packet: seq[byte]
|
var packet: seq[byte]
|
||||||
|
@ -187,20 +201,22 @@ proc encodeMessagePacket*(rng: var BrHmacDrbgContext, c: var Codec,
|
||||||
packet.add(maskedHeader)
|
packet.add(maskedHeader)
|
||||||
packet.add(messageEncrypted)
|
packet.add(messageEncrypted)
|
||||||
|
|
||||||
return (packet, authdata)
|
return (packet, nonce)
|
||||||
|
|
||||||
proc encodeWhoareyouPacket*(rng: var BrHmacDrbgContext, c: var Codec,
|
proc encodeWhoareyouPacket*(rng: var BrHmacDrbgContext, c: var Codec,
|
||||||
toId: NodeID, requestNonce: AESGCMNonce, idNonce: IdNonce, enrSeq: uint64):
|
toId: NodeID, toAddr: Address, requestNonce: AESGCMNonce, recordSeq: uint64,
|
||||||
seq[byte] =
|
pubkey: Option[PublicKey]): seq[byte] =
|
||||||
|
var idNonce: IdNonce
|
||||||
|
brHmacDrbgGenerate(rng, idNonce)
|
||||||
|
|
||||||
# authdata
|
# authdata
|
||||||
var authdata: seq[byte]
|
var authdata: seq[byte]
|
||||||
authdata.add(requestNonce)
|
|
||||||
authdata.add(idNonce)
|
authdata.add(idNonce)
|
||||||
authdata.add(enrSeq.tobytesBE)
|
authdata.add(recordSeq.tobytesBE)
|
||||||
|
|
||||||
# static-header
|
# static-header
|
||||||
let staticHeader = encodeStaticHeader(c.localNode.id, Flag.Whoareyou,
|
let staticHeader = encodeStaticHeader(Flag.Whoareyou, requestNonce,
|
||||||
authdata.len()) # authdata will always be 52 bytes
|
authdata.len())
|
||||||
|
|
||||||
# header = static-header || authdata
|
# header = static-header || authdata
|
||||||
var header: seq[byte]
|
var header: seq[byte]
|
||||||
|
@ -216,50 +232,60 @@ proc encodeWhoareyouPacket*(rng: var BrHmacDrbgContext, c: var Codec,
|
||||||
packet.add(iv)
|
packet.add(iv)
|
||||||
packet.add(maskedHeader)
|
packet.add(maskedHeader)
|
||||||
|
|
||||||
|
let
|
||||||
|
whoareyouData = WhoareyouData(
|
||||||
|
requestNonce: requestNonce,
|
||||||
|
idNonce: idNonce,
|
||||||
|
recordSeq: recordSeq,
|
||||||
|
challengeData: @iv & header)
|
||||||
|
challenge = Challenge(whoareyouData: whoareyouData, pubkey: pubkey)
|
||||||
|
key = HandShakeKey(nodeId: toId, address: $toAddr)
|
||||||
|
|
||||||
|
c.handshakes[key] = challenge
|
||||||
|
|
||||||
return packet
|
return packet
|
||||||
|
|
||||||
proc encodeHandshakePacket*(rng: var BrHmacDrbgContext, c: var Codec,
|
proc encodeHandshakePacket*(rng: var BrHmacDrbgContext, c: var Codec,
|
||||||
toId: NodeID, toAddr: Address, message: openarray[byte], idNonce: IdNonce,
|
toId: NodeID, toAddr: Address, message: openarray[byte],
|
||||||
enrSeq: uint64, pubkey: PublicKey): seq[byte] =
|
whoareyouData: WhoareyouData, pubkey: PublicKey): seq[byte] =
|
||||||
var header: seq[byte]
|
var header: seq[byte]
|
||||||
var nonce: AESGCMNonce
|
var nonce: AESGCMNonce
|
||||||
brHmacDrbgGenerate(rng, nonce)
|
brHmacDrbgGenerate(rng, nonce)
|
||||||
|
var iv: array[ivSize, byte]
|
||||||
|
brHmacDrbgGenerate(rng, iv) # Random IV
|
||||||
|
|
||||||
var authdata: seq[byte]
|
var authdata: seq[byte]
|
||||||
var authdataHead: seq[byte]
|
var authdataHead: seq[byte]
|
||||||
authdataHead.add(version)
|
|
||||||
authdataHead.add(nonce)
|
authdataHead.add(c.localNode.id.toByteArrayBE())
|
||||||
authdataHead.add(64'u8) # sig-size: 64
|
authdataHead.add(64'u8) # sig-size: 64
|
||||||
authdataHead.add(33'u8) # eph-key-size: 33
|
authdataHead.add(33'u8) # eph-key-size: 33
|
||||||
authdata.add(authdataHead)
|
authdata.add(authdataHead)
|
||||||
|
|
||||||
let ephKeys = KeyPair.random(rng)
|
let ephKeys = KeyPair.random(rng)
|
||||||
let signature = signIDNonce(c.privKey, idNonce,
|
let signature = signIDNonce(c.privKey, whoareyouData.challengeData,
|
||||||
ephKeys.pubkey.toRawCompressed())
|
ephKeys.pubkey.toRawCompressed(), toId)
|
||||||
|
|
||||||
authdata.add(signature.toRaw())
|
authdata.add(signature.toRaw())
|
||||||
# compressed pub key format (33 bytes)
|
# compressed pub key format (33 bytes)
|
||||||
authdata.add(ephKeys.pubkey.toRawCompressed())
|
authdata.add(ephKeys.pubkey.toRawCompressed())
|
||||||
|
|
||||||
# Add ENR of sequence number is newer
|
# Add ENR of sequence number is newer
|
||||||
if enrSeq < c.localNode.record.seqNum:
|
if whoareyouData.recordSeq < c.localNode.record.seqNum:
|
||||||
authdata.add(encode(c.localNode.record))
|
authdata.add(encode(c.localNode.record))
|
||||||
|
|
||||||
let secrets = deriveKeys(c.localNode.id, toId, ephKeys.seckey, pubkey,
|
let secrets = deriveKeys(c.localNode.id, toId, ephKeys.seckey, pubkey,
|
||||||
idNonce)
|
whoareyouData.challengeData)
|
||||||
|
|
||||||
# Header
|
# Header
|
||||||
let staticHeader = encodeStaticHeader(c.localNode.id, Flag.HandshakeMessage,
|
let staticHeader = encodeStaticHeader(Flag.HandshakeMessage, nonce,
|
||||||
authdata.len())
|
authdata.len())
|
||||||
|
|
||||||
header.add(staticHeader)
|
header.add(staticHeader)
|
||||||
header.add(authdata)
|
header.add(authdata)
|
||||||
|
|
||||||
c.sessions.store(toId, toAddr, secrets.readKey, secrets.writeKey)
|
c.sessions.store(toId, toAddr, secrets.readKey, secrets.writeKey)
|
||||||
let messageEncrypted = encryptGCM(secrets.writeKey, nonce, message, header)
|
let messageEncrypted = encryptGCM(secrets.writeKey, nonce, message, @iv & header)
|
||||||
|
|
||||||
var iv: array[ivSize, byte]
|
|
||||||
brHmacDrbgGenerate(rng, iv) # Random IV
|
|
||||||
|
|
||||||
let maskedHeader = encryptHeader(toId, iv, header)
|
let maskedHeader = encryptHeader(toId, iv, header)
|
||||||
|
|
||||||
|
@ -272,11 +298,8 @@ proc encodeHandshakePacket*(rng: var BrHmacDrbgContext, c: var Codec,
|
||||||
|
|
||||||
proc decodeHeader*(id: NodeId, iv, maskedHeader: openarray[byte]):
|
proc decodeHeader*(id: NodeId, iv, maskedHeader: openarray[byte]):
|
||||||
DecodeResult[(StaticHeader, seq[byte])] =
|
DecodeResult[(StaticHeader, seq[byte])] =
|
||||||
# Smallest header is staticHeader + gcm nonce for a ordinary message
|
# No need to check staticHeader size as that is included in minimum packet
|
||||||
let inputLen = maskedHeader.len
|
# size check in decodePacket
|
||||||
if inputLen < staticHeaderSize + gcmNonceSize:
|
|
||||||
return err(PacketError)
|
|
||||||
|
|
||||||
var ectx: CTR[aes128]
|
var ectx: CTR[aes128]
|
||||||
ectx.init(id.toByteArrayBE().toOpenArray(0, ivSize - 1), iv)
|
ectx.init(id.toByteArrayBE().toOpenArray(0, ivSize - 1), iv)
|
||||||
# Decrypt static-header part of the header
|
# Decrypt static-header part of the header
|
||||||
|
@ -287,15 +310,22 @@ proc decodeHeader*(id: NodeId, iv, maskedHeader: openarray[byte]):
|
||||||
if staticHeader.toOpenArray(0, protocolId.len - 1) != protocolId:
|
if staticHeader.toOpenArray(0, protocolId.len - 1) != protocolId:
|
||||||
return err(PacketError)
|
return err(PacketError)
|
||||||
|
|
||||||
let srcId = NodeId.fromBytesBE(staticHeader.toOpenArray(8, 39))
|
if uint16.fromBytesBE(staticHeader.toOpenArray(6, 7)) != version:
|
||||||
|
|
||||||
if staticHeader[40] < Flag.low.byte or staticHeader[40] > Flag.high.byte:
|
|
||||||
return err(PacketError)
|
return err(PacketError)
|
||||||
let flag = cast[Flag](staticHeader[40])
|
|
||||||
|
|
||||||
let authdataSize = uint16.fromBytesBE(staticHeader.toOpenArray(41, 42))
|
if staticHeader[8] < Flag.low.byte or staticHeader[8] > Flag.high.byte:
|
||||||
|
return err(PacketError)
|
||||||
|
let flag = cast[Flag](staticHeader[8])
|
||||||
|
|
||||||
|
var nonce: AESGCMNonce
|
||||||
|
copyMem(addr nonce[0], unsafeAddr staticHeader[9], gcmNonceSize)
|
||||||
|
|
||||||
|
let authdataSize = uint16.fromBytesBE(staticHeader.toOpenArray(21,
|
||||||
|
staticHeader.high))
|
||||||
|
|
||||||
# Input should have minimum size of staticHeader + provided authdata size
|
# Input should have minimum size of staticHeader + provided authdata size
|
||||||
if inputLen < staticHeaderSize + int(authdataSize):
|
# Can be larger as there can come a message after.
|
||||||
|
if maskedHeader.len < staticHeaderSize + int(authdataSize):
|
||||||
return err(PacketError)
|
return err(PacketError)
|
||||||
|
|
||||||
var authdata = newSeq[byte](int(authdataSize))
|
var authdata = newSeq[byte](int(authdataSize))
|
||||||
|
@ -303,7 +333,7 @@ proc decodeHeader*(id: NodeId, iv, maskedHeader: openarray[byte]):
|
||||||
staticHeaderSize + int(authdataSize) - 1), authdata)
|
staticHeaderSize + int(authdataSize) - 1), authdata)
|
||||||
ectx.clear()
|
ectx.clear()
|
||||||
|
|
||||||
ok((StaticHeader(srcId: srcId, flag: flag, authdataSize: authdataSize),
|
ok((StaticHeader(authdataSize: authdataSize, flag: flag, nonce: nonce),
|
||||||
staticHeader & authdata))
|
staticHeader & authdata))
|
||||||
|
|
||||||
proc decodeMessage*(body: openarray[byte]): DecodeResult[Message] =
|
proc decodeMessage*(body: openarray[byte]): DecodeResult[Message] =
|
||||||
|
@ -352,69 +382,65 @@ proc decodeMessage*(body: openarray[byte]): DecodeResult[Message] =
|
||||||
else:
|
else:
|
||||||
err(PacketError)
|
err(PacketError)
|
||||||
|
|
||||||
proc decodeMessagePacket(c: var Codec, fromAddr: Address, srcId: NodeId,
|
proc decodeMessagePacket(c: var Codec, fromAddr: Address, nonce: AESGCMNonce,
|
||||||
ct, header: openArray[byte]): DecodeResult[Packet] =
|
iv, header, ct: openArray[byte]): DecodeResult[Packet] =
|
||||||
# We now know the exact size that the header should be
|
# We now know the exact size that the header should be
|
||||||
if header.len != staticHeaderSize + gcmNonceSize:
|
if header.len != staticHeaderSize + sizeof(NodeId):
|
||||||
return err(PacketError)
|
return err(PacketError)
|
||||||
|
|
||||||
var nonce: AESGCMNonce
|
let srcId = NodeId.fromBytesBE(header.toOpenArray(staticHeaderSize,
|
||||||
copyMem(addr nonce[0], unsafeAddr header[staticHeaderSize], gcmNonceSize)
|
header.high))
|
||||||
|
|
||||||
var writeKey, readKey: AesKey
|
var writeKey, readKey: AesKey
|
||||||
if not c.sessions.load(srcId, fromAddr, readKey, writeKey):
|
if not c.sessions.load(srcId, fromAddr, readKey, writeKey):
|
||||||
# Don't consider this an error, simply haven't done a handshake yet or
|
# Don't consider this an error, simply haven't done a handshake yet or
|
||||||
# the session got removed.
|
# the session got removed.
|
||||||
trace "Decrypting failed (no keys)"
|
trace "Decrypting failed (no keys)"
|
||||||
return ok(Packet(flag: Flag.OrdinaryMessage, requestNonce: nonce,
|
return ok(Packet(flag: Flag.OrdinaryMessage, requestNonce: nonce,
|
||||||
srcId: srcId))
|
srcId: srcId))
|
||||||
|
|
||||||
let pt = decryptGCM(readKey, nonce, ct, header)
|
let pt = decryptGCM(readKey, nonce, ct, @iv & @header)
|
||||||
if pt.isNone():
|
if pt.isNone():
|
||||||
# Don't consider this an error, the session got probably removed at the
|
# Don't consider this an error, the session got probably removed at the
|
||||||
# peer's side.
|
# peer's side.
|
||||||
trace "Decrypting failed (invalid keys)"
|
trace "Decrypting failed (invalid keys)"
|
||||||
c.sessions.del(srcId, fromAddr)
|
c.sessions.del(srcId, fromAddr)
|
||||||
return ok(Packet(flag: Flag.OrdinaryMessage, requestNonce: nonce,
|
return ok(Packet(flag: Flag.OrdinaryMessage, requestNonce: nonce,
|
||||||
srcId: srcId))
|
srcId: srcId))
|
||||||
|
|
||||||
let message = ? decodeMessage(pt.get())
|
let message = ? decodeMessage(pt.get())
|
||||||
|
|
||||||
return ok(Packet(flag: Flag.OrdinaryMessage,
|
return ok(Packet(flag: Flag.OrdinaryMessage,
|
||||||
messageOpt: some(message), requestNonce: nonce, srcId: srcId))
|
messageOpt: some(message), requestNonce: nonce, srcId: srcId))
|
||||||
|
|
||||||
proc decodeWhoareyouPacket(c: var Codec, srcId: NodeId,
|
proc decodeWhoareyouPacket(c: var Codec, nonce: AESGCMNonce,
|
||||||
authdata: openArray[byte]): DecodeResult[Packet] =
|
iv, header: openArray[byte]): DecodeResult[Packet] =
|
||||||
# We now know the exact size that the authdata should be
|
# TODO improve this
|
||||||
if authdata.len != gcmNonceSize + idNonceSize + sizeof(uint64):
|
let authdata = header[staticHeaderSize..header.high()]
|
||||||
|
# We now know the exact size that the authdata should be
|
||||||
|
if authdata.len != idNonceSize + sizeof(uint64):
|
||||||
return err(PacketError)
|
return err(PacketError)
|
||||||
|
|
||||||
var requestNonce: AESGCMNonce
|
|
||||||
copyMem(addr requestNonce[0], unsafeAddr authdata[0], gcmNonceSize)
|
|
||||||
var idNonce: IdNonce
|
var idNonce: IdNonce
|
||||||
copyMem(addr idNonce[0], unsafeAddr authdata[gcmNonceSize], idNonceSize)
|
copyMem(addr idNonce[0], unsafeAddr authdata[0], idNonceSize)
|
||||||
let whoareyou = WhoareyouData(requestNonce: requestNonce, idNonce: idNonce,
|
let whoareyou = WhoareyouData(requestNonce: nonce, idNonce: idNonce,
|
||||||
recordSeq: uint64.fromBytesBE(
|
recordSeq: uint64.fromBytesBE(
|
||||||
authdata.toOpenArray(gcmNonceSize + idNonceSize, authdata.high)))
|
authdata.toOpenArray(idNonceSize, authdata.high)),
|
||||||
|
challengeData: @iv & @header)
|
||||||
|
|
||||||
return ok(Packet(flag: Flag.Whoareyou, whoareyou: whoareyou,
|
return ok(Packet(flag: Flag.Whoareyou, whoareyou: whoareyou))
|
||||||
srcId: srcId))
|
|
||||||
|
|
||||||
proc decodeHandshakePacket(c: var Codec, fromAddr: Address, srcId: NodeId,
|
proc decodeHandshakePacket(c: var Codec, fromAddr: Address, nonce: AESGCMNonce,
|
||||||
ct, header: openArray[byte]): DecodeResult[Packet] =
|
iv, header, ct: openArray[byte]): DecodeResult[Packet] =
|
||||||
# Checking if there is enough data to decode authdata-head
|
# Checking if there is enough data to decode authdata-head
|
||||||
if header.len <= staticHeaderSize + authdataHeadSize:
|
if header.len <= staticHeaderSize + authdataHeadSize:
|
||||||
return err(PacketError)
|
return err(PacketError)
|
||||||
|
|
||||||
# check version
|
|
||||||
let authData = header[staticHeaderSize..header.high()]
|
|
||||||
if uint8(authData[0]) != version:
|
|
||||||
return err(HandshakeError)
|
|
||||||
|
|
||||||
let
|
let
|
||||||
nonce = authdata[1..12]
|
authdata = header[staticHeaderSize..header.high()]
|
||||||
sigSize = uint8(authdata[13])
|
srcId = NodeId.fromBytesBE(authdata.toOpenArray(0, 31))
|
||||||
ephKeySize = uint8(authdata[14])
|
sigSize = uint8(authdata[32])
|
||||||
|
ephKeySize = uint8(authdata[33])
|
||||||
|
|
||||||
# If smaller, as it can be equal and bigger (in case it holds an enr)
|
# If smaller, as it can be equal and bigger (in case it holds an enr)
|
||||||
if header.len < staticHeaderSize + authdataHeadSize + int(sigSize) + int(ephKeySize):
|
if header.len < staticHeaderSize + authdataHeadSize + int(sigSize) + int(ephKeySize):
|
||||||
|
@ -469,17 +495,18 @@ proc decodeHandshakePacket(c: var Codec, fromAddr: Address, srcId: NodeId,
|
||||||
authdata.toOpenArray(authdataHeadSize,
|
authdata.toOpenArray(authdataHeadSize,
|
||||||
authdataHeadSize + int(sigSize) - 1)).mapErrTo(HandshakeError)
|
authdataHeadSize + int(sigSize) - 1)).mapErrTo(HandshakeError)
|
||||||
|
|
||||||
let h = idNonceHash(challenge.whoareyouData.idNonce, ephKeyRaw)
|
let h = idNonceHash(challenge.whoareyouData.challengeData, ephKeyRaw,
|
||||||
|
c.localNode.id)
|
||||||
if not verify(sig, SkMessage(h.data), pubkey):
|
if not verify(sig, SkMessage(h.data), pubkey):
|
||||||
return err(HandshakeError)
|
return err(HandshakeError)
|
||||||
|
|
||||||
# Do the key derivation step only after id-nonce-sig is verified!
|
# Do the key derivation step only after id-nonce-sig is verified!
|
||||||
var secrets = deriveKeys(srcId, c.localNode.id, c.privKey,
|
var secrets = deriveKeys(srcId, c.localNode.id, c.privKey,
|
||||||
ephKey, challenge.whoareyouData.idNonce)
|
ephKey, challenge.whoareyouData.challengeData)
|
||||||
|
|
||||||
swap(secrets.readKey, secrets.writeKey)
|
swap(secrets.readKey, secrets.writeKey)
|
||||||
|
|
||||||
let pt = decryptGCM(secrets.readKey, nonce, ct, header)
|
let pt = decryptGCM(secrets.readKey, nonce, ct, @iv & @header)
|
||||||
if pt.isNone():
|
if pt.isNone():
|
||||||
c.sessions.del(srcId, fromAddr)
|
c.sessions.del(srcId, fromAddr)
|
||||||
# Differently from an ordinary message, this is seen as an error as the
|
# Differently from an ordinary message, this is seen as an error as the
|
||||||
|
@ -490,20 +517,19 @@ proc decodeHandshakePacket(c: var Codec, fromAddr: Address, srcId: NodeId,
|
||||||
|
|
||||||
# Only store the session secrets in case decryption was successful and also
|
# Only store the session secrets in case decryption was successful and also
|
||||||
# in case the message can get decoded.
|
# in case the message can get decoded.
|
||||||
c.sessions.store(srcId, fromAddr, secrets.readKey,
|
c.sessions.store(srcId, fromAddr, secrets.readKey, secrets.writeKey)
|
||||||
secrets.writeKey)
|
|
||||||
|
|
||||||
return ok(Packet(flag: Flag.HandshakeMessage, message: message, srcId: srcId,
|
return ok(Packet(flag: Flag.HandshakeMessage, message: message,
|
||||||
node: newNode))
|
srcIdHs: srcId, node: newNode))
|
||||||
|
|
||||||
proc decodePacket*(c: var Codec, fromAddr: Address, input: openArray[byte]):
|
proc decodePacket*(c: var Codec, fromAddr: Address, input: openArray[byte]):
|
||||||
DecodeResult[Packet] =
|
DecodeResult[Packet] =
|
||||||
## Decode a packet. This can be a regular packet or a packet in response to a
|
## Decode a packet. This can be a regular packet or a packet in response to a
|
||||||
## WHOAREYOU packet. In case of the latter a `newNode` might be provided.
|
## WHOAREYOU packet. In case of the latter a `newNode` might be provided.
|
||||||
# TODO: First size check. Which size however?
|
# Smallest packet is Whoareyou packet so that is the minimum size
|
||||||
# IVSize + staticHeaderSize + 12 + ...? What is minimum message size?
|
if input.len() < whoareyouSize:
|
||||||
if input.len() <= ivSize + staticHeaderSize + gcmNonceSize:
|
|
||||||
return err(PacketError)
|
return err(PacketError)
|
||||||
|
|
||||||
# TODO: Just pass in the full input? Makes more sense perhaps..
|
# TODO: Just pass in the full input? Makes more sense perhaps..
|
||||||
let (staticHeader, header) = ? decodeHeader(c.localNode.id,
|
let (staticHeader, header) = ? decodeHeader(c.localNode.id,
|
||||||
input.toOpenArray(0, ivSize - 1), # IV
|
input.toOpenArray(0, ivSize - 1), # IV
|
||||||
|
@ -513,18 +539,20 @@ proc decodePacket*(c: var Codec, fromAddr: Address, input: openArray[byte]):
|
||||||
case staticHeader.flag
|
case staticHeader.flag
|
||||||
of OrdinaryMessage:
|
of OrdinaryMessage:
|
||||||
# TODO: Extra size check on ct data?
|
# TODO: Extra size check on ct data?
|
||||||
return decodeMessagePacket(c, fromAddr, staticHeader.srcId,
|
return decodeMessagePacket(c, fromAddr, staticHeader.nonce,
|
||||||
input.toOpenArray(ivSize + header.len, input.high), header)
|
input.toOpenArray(0, ivSize - 1), header,
|
||||||
|
input.toOpenArray(ivSize + header.len, input.high))
|
||||||
|
|
||||||
of Whoareyou:
|
of Whoareyou:
|
||||||
# Header size got checked in decode header
|
# Header size got checked in decode header
|
||||||
return decodeWhoareyouPacket(c, staticHeader.srcId,
|
return decodeWhoareyouPacket(c, staticHeader.nonce,
|
||||||
header.toOpenArray(staticHeaderSize, header.high()))
|
input.toOpenArray(0, ivSize - 1), header)
|
||||||
|
|
||||||
of HandshakeMessage:
|
of HandshakeMessage:
|
||||||
# TODO: Extra size check on ct data?
|
# TODO: Extra size check on ct data?
|
||||||
return decodeHandshakePacket(c, fromAddr, staticHeader.srcId,
|
return decodeHandshakePacket(c, fromAddr, staticHeader.nonce,
|
||||||
input.toOpenArray(ivSize + header.len, input.high), header)
|
input.toOpenArray(0, ivSize - 1), header,
|
||||||
|
input.toOpenArray(ivSize + header.len, input.high))
|
||||||
|
|
||||||
proc init*(T: type RequestId, rng: var BrHmacDrbgContext): T =
|
proc init*(T: type RequestId, rng: var BrHmacDrbgContext): T =
|
||||||
var buf: array[sizeof(T), byte]
|
var buf: array[sizeof(T), byte]
|
||||||
|
|
|
@ -316,29 +316,21 @@ proc handleMessage(d: Protocol, srcId: NodeId, fromAddr: Address,
|
||||||
|
|
||||||
proc sendWhoareyou(d: Protocol, toId: NodeId, a: Address,
|
proc sendWhoareyou(d: Protocol, toId: NodeId, a: Address,
|
||||||
requestNonce: AESGCMNonce, node: Option[Node]) {.raises: [Exception].} =
|
requestNonce: AESGCMNonce, node: Option[Node]) {.raises: [Exception].} =
|
||||||
var idNonce: IdNonce
|
let key = HandShakeKey(nodeId: toId, address: $a)
|
||||||
brHmacDrbgGenerate(d.rng[], idNonce)
|
if not d.codec.hasHandshake(key):
|
||||||
|
let
|
||||||
|
recordSeq = if node.isSome(): node.get().record.seqNum
|
||||||
|
else: 0
|
||||||
|
pubkey = if node.isSome(): some(node.get().pubkey)
|
||||||
|
else: none(PublicKey)
|
||||||
|
|
||||||
let
|
let data = encodeWhoareyouPacket(d.rng[], d.codec, toId, a, requestNonce,
|
||||||
recordSeq = if node.isSome(): node.get().record.seqNum
|
recordSeq, pubkey)
|
||||||
else: 0
|
|
||||||
whoareyouData = WhoareyouData(requestNonce: requestNonce,
|
|
||||||
idNonce: idNonce, recordSeq: recordSeq)
|
|
||||||
pubkey = if node.isSome(): some(node.get().pubkey)
|
|
||||||
else: none(PublicKey)
|
|
||||||
challenge = Challenge(whoareyouData: whoareyouData, pubkey: pubkey)
|
|
||||||
key = HandShakeKey(nodeId: toId, address: $a)
|
|
||||||
|
|
||||||
if not d.codec.handshakes.hasKeyOrPut(key, challenge):
|
|
||||||
# TODO: raises: [Exception], but it shouldn't.
|
|
||||||
sleepAsync(handshakeTimeout).addCallback() do(data: pointer):
|
sleepAsync(handshakeTimeout).addCallback() do(data: pointer):
|
||||||
# TODO: should we still provide cancellation in case handshake completes
|
# TODO: should we still provide cancellation in case handshake completes
|
||||||
# correctly?
|
# correctly?
|
||||||
d.codec.handshakes.del(key)
|
d.codec.handshakes.del(key)
|
||||||
|
|
||||||
let data = encodeWhoareyouPacket(d.rng[], d.codec, toId,
|
|
||||||
requestNonce, idNonce, recordSeq)
|
|
||||||
|
|
||||||
d.send(a, data)
|
d.send(a, data)
|
||||||
else:
|
else:
|
||||||
debug "Node with this id already has ongoing handshake, ignoring packet"
|
debug "Node with this id already has ongoing handshake, ignoring packet"
|
||||||
|
@ -374,15 +366,14 @@ proc receive*(d: Protocol, a: Address, packet: openArray[byte]) {.gcsafe,
|
||||||
# This is a node we previously contacted and thus must have an address.
|
# This is a node we previously contacted and thus must have an address.
|
||||||
doAssert(toNode.address.isSome())
|
doAssert(toNode.address.isSome())
|
||||||
let data = encodeHandshakePacket(d.rng[], d.codec, toNode.id,
|
let data = encodeHandshakePacket(d.rng[], d.codec, toNode.id,
|
||||||
toNode.address.get(), pr.message, packet.whoareyou.idNonce,
|
toNode.address.get(), pr.message, packet.whoareyou, toNode.pubkey)
|
||||||
packet.whoareyou.recordSeq, toNode.pubkey)
|
|
||||||
|
|
||||||
d.send(toNode, data)
|
d.send(toNode, data)
|
||||||
else:
|
else:
|
||||||
debug "Timed out or unrequested Whoareyou packet"
|
debug "Timed out or unrequested Whoareyou packet"
|
||||||
of HandshakeMessage:
|
of HandshakeMessage:
|
||||||
trace "Received handshake packet"
|
trace "Received handshake packet"
|
||||||
d.handleMessage(packet.srcId, a, packet.message)
|
d.handleMessage(packet.srcIdHs, a, packet.message)
|
||||||
# For a handshake message it is possible that we received an newer ENR.
|
# For a handshake message it is possible that we received an newer ENR.
|
||||||
# In that case we can add/update it to the routing table.
|
# In that case we can add/update it to the routing table.
|
||||||
if packet.node.isSome():
|
if packet.node.isSome():
|
||||||
|
|
|
@ -4,12 +4,9 @@ import
|
||||||
eth/[rlp, keys],
|
eth/[rlp, keys],
|
||||||
eth/p2p/discoveryv5/[typesv1, encodingv1, enr, node, sessions]
|
eth/p2p/discoveryv5/[typesv1, encodingv1, enr, node, sessions]
|
||||||
|
|
||||||
# According to test vectors:
|
|
||||||
# https://github.com/ethereum/devp2p/blob/master/discv5/discv5-wire-test-vectors.md
|
|
||||||
|
|
||||||
let rng = newRng()
|
let rng = newRng()
|
||||||
|
|
||||||
suite "Discovery v5 Protocol Message Encodings":
|
suite "Discovery v5.1 Protocol Message Encodings":
|
||||||
test "Ping Request":
|
test "Ping Request":
|
||||||
var p: PingMessage
|
var p: PingMessage
|
||||||
p.enrSeq = 1
|
p.enrSeq = 1
|
||||||
|
@ -47,11 +44,13 @@ suite "Discovery v5 Protocol Message Encodings":
|
||||||
var reqId: RequestId = 1
|
var reqId: RequestId = 1
|
||||||
check encodeMessage(p, reqId).toHex == "04f8f20101f8eef875b8401ce2991c64993d7c84c29a00bdc871917551c7d330fca2dd0d69c706596dc655448f030b98a77d4001fd46ae0112ce26d613c5a6a02a81a6223cd0c4edaa53280182696482763489736563703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138f875b840d7f1c39e376297f81d7297758c64cb37dcc5c3beea9f57f7ce9695d7d5a67553417d719539d6ae4b445946de4d99e680eb8063f29485b555d45b7df16a1850130182696482763489736563703235366b31a1030e2cb74241c0c4fc8e8166f1a79a05d5b0dd95813a74b094529f317d5c39d235"
|
check encodeMessage(p, reqId).toHex == "04f8f20101f8eef875b8401ce2991c64993d7c84c29a00bdc871917551c7d330fca2dd0d69c706596dc655448f030b98a77d4001fd46ae0112ce26d613c5a6a02a81a6223cd0c4edaa53280182696482763489736563703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138f875b840d7f1c39e376297f81d7297758c64cb37dcc5c3beea9f57f7ce9695d7d5a67553417d719539d6ae4b445946de4d99e680eb8063f29485b555d45b7df16a1850130182696482763489736563703235366b31a1030e2cb74241c0c4fc8e8166f1a79a05d5b0dd95813a74b094529f317d5c39d235"
|
||||||
|
|
||||||
suite "Discovery v5 Cryptographic Primitives":
|
# According to test vectors:
|
||||||
|
# https://github.com/fjl/devp2p/blob/discv5-v1-update/discv5/discv5-wire-test-vectors.md#cryptographic-primitives
|
||||||
|
suite "Discovery v5.1 Cryptographic Primitives Test Vectors":
|
||||||
test "ECDH":
|
test "ECDH":
|
||||||
const
|
const
|
||||||
# input
|
# input
|
||||||
publicKey = "0x9961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231503061ac4aaee666073d7e5bc2c80c3f5c5b500c1cb5fd0a76abbb6b675ad157"
|
publicKey = "0x039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231"
|
||||||
secretKey = "0xfb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736"
|
secretKey = "0xfb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736"
|
||||||
# expected output
|
# expected output
|
||||||
sharedSecret = "0x033b11a2a1f214567e1537ce5e509ffd9b21373247f2a3ff6841f4976f53165e7e"
|
sharedSecret = "0x033b11a2a1f214567e1537ce5e509ffd9b21373247f2a3ff6841f4976f53165e7e"
|
||||||
|
@ -59,41 +58,51 @@ suite "Discovery v5 Cryptographic Primitives":
|
||||||
let
|
let
|
||||||
pub = PublicKey.fromHex(publicKey)[]
|
pub = PublicKey.fromHex(publicKey)[]
|
||||||
priv = PrivateKey.fromHex(secretKey)[]
|
priv = PrivateKey.fromHex(secretKey)[]
|
||||||
let eph = ecdhRawFull(priv, pub)
|
eph = ecdhRawFull(priv, pub)
|
||||||
check:
|
check:
|
||||||
eph.data == hexToSeqByte(sharedSecret)
|
eph.data == hexToSeqByte(sharedSecret)
|
||||||
|
|
||||||
test "Key Derivation":
|
test "Key Derivation":
|
||||||
# const
|
const
|
||||||
# # input
|
# input
|
||||||
# secretKey = "0x02a77e3aa0c144ae7c0a3af73692b7d6e5b7a2fdc0eda16e8d5e6cb0d08e88dd04"
|
ephemeralKey = "0xfb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736"
|
||||||
# nodeIdA = "0xa448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7"
|
destPubkey = "0x0317931e6e0840220642f230037d285d122bc59063221ef3226b1f403ddc69ca91"
|
||||||
# nodeIdB = "0x885bba8dfeddd49855459df852ad5b63d13a3fae593f3f9fa7e317fd43651409"
|
nodeIdA = "0xaaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb"
|
||||||
# idNonce = "0x0101010101010101010101010101010101010101010101010101010101010101"
|
nodeIdB = "0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9"
|
||||||
# # expected output
|
challengeData = "0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000"
|
||||||
# initiatorKey = "0x238d8b50e4363cf603a48c6cc3542967"
|
# expected output
|
||||||
# recipientKey = "0xbebc0183484f7e7ca2ac32e3d72c8891"
|
initiatorKey = "0xdccc82d81bd610f4f76d3ebe97a40571"
|
||||||
# authRespKey = "0xe987ad9e414d5b4f9bfe4ff1e52f2fae"
|
recipientKey = "0xac74bb8773749920b0d3a8881c173ec5"
|
||||||
|
|
||||||
# Code doesn't allow to start from shared `secretKey`, but only from the
|
let secrets = deriveKeys(
|
||||||
# public and private key. Would require pulling `ecdhAgree` out of
|
NodeId.fromHex(nodeIdA),
|
||||||
# `deriveKeys`
|
NodeId.fromHex(nodeIdB),
|
||||||
skip()
|
PrivateKey.fromHex(ephemeralKey)[],
|
||||||
|
PublicKey.fromHex(destPubkey)[],
|
||||||
|
hexToSeqByte(challengeData))
|
||||||
|
|
||||||
|
check:
|
||||||
|
secrets.writeKey == hexToByteArray[aesKeySize](initiatorKey)
|
||||||
|
secrets.readKey == hexToByteArray[aesKeySize](recipientKey)
|
||||||
|
|
||||||
test "Nonce Signing":
|
test "Nonce Signing":
|
||||||
const
|
const
|
||||||
# input
|
# input
|
||||||
idNonce = "0xa77e3aa0c144ae7c0a3af73692b7d6e5b7a2fdc0eda16e8d5e6cb0d08e88dd04"
|
staticKey = "0xfb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736"
|
||||||
ephemeralKey = "0x9961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231503061ac4aaee666073d7e5bc2c80c3f5c5b500c1cb5fd0a76abbb6b675ad157"
|
challengeData = "0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000"
|
||||||
localSecretKey = "0xfb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736"
|
ephemeralPubkey = "0x039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231"
|
||||||
|
nodeIdB = "0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9"
|
||||||
# expected output
|
# expected output
|
||||||
idNonceSig = "0xc5036e702a79902ad8aa147dabfe3958b523fd6fa36cc78e2889b912d682d8d35fdea142e141f690736d86f50b39746ba2d2fc510b46f82ee08f08fd55d133a4"
|
idSignature = "0x94852a1e2318c4e5e9d422c98eaf19d1d90d876b29cd06ca7cb7546d0fff7b484fe86c09a064fe72bdbef73ba8e9c34df0cd2b53e9d65528c2c7f336d5dfc6e6"
|
||||||
|
|
||||||
let
|
let
|
||||||
privKey = PrivateKey.fromHex(localSecretKey)[]
|
privKey = PrivateKey.fromHex(staticKey)[]
|
||||||
signature = signIDNonce(privKey, hexToByteArray[idNonceSize](idNonce),
|
signature = signIDNonce(
|
||||||
hexToByteArray[64](ephemeralKey))
|
privKey,
|
||||||
check signature.toRaw() == hexToByteArray[64](idNonceSig)
|
hexToSeqByte(challengeData),
|
||||||
|
hexToSeqByte(ephemeralPubkey),
|
||||||
|
NodeId.fromHex(nodeIdB))
|
||||||
|
check signature.toRaw() == hexToByteArray[64](idSignature)
|
||||||
|
|
||||||
test "Encryption/Decryption":
|
test "Encryption/Decryption":
|
||||||
const
|
const
|
||||||
|
@ -111,14 +120,9 @@ suite "Discovery v5 Cryptographic Primitives":
|
||||||
hexToByteArray[32](ad))
|
hexToByteArray[32](ad))
|
||||||
check encrypted == hexToSeqByte(messageCiphertext)
|
check encrypted == hexToSeqByte(messageCiphertext)
|
||||||
|
|
||||||
test "Authentication Header and Encrypted Message Generation":
|
# According to test vectors:
|
||||||
# Can't work directly with the provided shared secret as keys are derived
|
# https://github.com/fjl/devp2p/blob/discv5-v1-update/discv5/discv5-wire-test-vectors.md#packet-encodings
|
||||||
# inside makeAuthHeader, and passed on one call up.
|
suite "Discovery v5.1 Packet Encodings Test Vectors":
|
||||||
# The encryption of the auth-resp-pt uses one of these keys, as does the
|
|
||||||
# encryption of the message itself. So the whole test depends on this.
|
|
||||||
skip()
|
|
||||||
|
|
||||||
suite "Discovery v5.1 Test Vectors":
|
|
||||||
const
|
const
|
||||||
nodeAKey = "0xeef77acb6c6a6eebc5b363a475ac583ec7eccdb42b6481424c60f59aa326547f"
|
nodeAKey = "0xeef77acb6c6a6eebc5b363a475ac583ec7eccdb42b6481424c60f59aa326547f"
|
||||||
nodeBKey = "0x66fb62bfbd66b9177a138c1e5cddbe4f7c30c343e94e68df8769459cb1cde628"
|
nodeBKey = "0x66fb62bfbd66b9177a138c1e5cddbe4f7c30c343e94e68df8769459cb1cde628"
|
||||||
|
@ -143,13 +147,45 @@ suite "Discovery v5.1 Test Vectors":
|
||||||
codecB = Codec(localNode: nodeB, privKey: privKeyB,
|
codecB = Codec(localNode: nodeB, privKey: privKeyB,
|
||||||
sessions: Sessions.init(5))
|
sessions: Sessions.init(5))
|
||||||
|
|
||||||
|
test "Ping Ordinary Message Packet":
|
||||||
|
const
|
||||||
|
readKey = "0x00000000000000000000000000000000"
|
||||||
|
pingReqId = 0x00000001'u64
|
||||||
|
pingEnrSeq = 2'u64
|
||||||
|
|
||||||
|
encodedPacket =
|
||||||
|
"00000000000000000000000000000000088b3d4342774649325f313964a39e55" &
|
||||||
|
"ea96c005ad52be8c7560413a7008f16c9e6d2f43bbea8814a546b7409ce783d3" &
|
||||||
|
"4c4f53245d08dab84102ed931f66d1492acb308fa1c6715b9d139b81acbdcc"
|
||||||
|
|
||||||
|
let dummyKey = "0x00000000000000000000000000000001" # of no importance
|
||||||
|
codecA.sessions.store(nodeB.id, nodeB.address.get(),
|
||||||
|
hexToByteArray[aesKeySize](dummyKey), hexToByteArray[aesKeySize](readKey))
|
||||||
|
codecB.sessions.store(nodeA.id, nodeA.address.get(),
|
||||||
|
hexToByteArray[aesKeySize](readKey), hexToByteArray[aesKeySize](dummyKey))
|
||||||
|
|
||||||
|
# Note: Noticed when comparing these test vectors that we encode reqId as
|
||||||
|
# integer while it seems the test vectors have it encoded as byte seq,
|
||||||
|
# meaning having potentially leading zeroes.
|
||||||
|
|
||||||
|
let decoded = codecB.decodePacket(nodeA.address.get(), hexToSeqByte(encodedPacket))
|
||||||
|
check:
|
||||||
|
decoded.isOK()
|
||||||
|
decoded.get().messageOpt.isSome()
|
||||||
|
decoded.get().messageOpt.get().reqId == pingReqId
|
||||||
|
decoded.get().messageOpt.get().kind == ping
|
||||||
|
decoded.get().messageOpt.get().ping.enrSeq == pingEnrSeq
|
||||||
|
|
||||||
test "Whoareyou Packet":
|
test "Whoareyou Packet":
|
||||||
const
|
const
|
||||||
|
whoareyouChallengeData = "0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000"
|
||||||
whoareyouRequestNonce = "0x0102030405060708090a0b0c"
|
whoareyouRequestNonce = "0x0102030405060708090a0b0c"
|
||||||
whoareyouIdNonce = "0x0102030405060708090a0b0c0d0e0f1000000000000000000000000000000000"
|
whoareyouIdNonce = "0x0102030405060708090a0b0c0d0e0f10"
|
||||||
whoareyouEnrSeq = 0
|
whoareyouEnrSeq = 0
|
||||||
|
|
||||||
encodedPacket = "0x00000000000000000000000000000000088b3d4342776668980a4adf72a8fcaa963f24b27a2f6bb44c7ed5ca10e87de130f94d2390b9853c3ecb9ad5e368892ec562137bf19c6d0a9191a5651c4f415117bdfa0c7ab86af62b7a9784eceb28008d03ede83bd1369631f9f3d8da0b45"
|
encodedPacket =
|
||||||
|
"00000000000000000000000000000000088b3d434277464933a1ccc59f5967ad" &
|
||||||
|
"1d6035f15e528627dde75cd68292f9e6c27d6b66c8100a873fcbaed4e16b8d"
|
||||||
|
|
||||||
let decoded = codecB.decodePacket(nodeA.address.get(),
|
let decoded = codecB.decodePacket(nodeA.address.get(),
|
||||||
hexToSeqByte(encodedPacket))
|
hexToSeqByte(encodedPacket))
|
||||||
|
@ -160,59 +196,35 @@ suite "Discovery v5.1 Test Vectors":
|
||||||
decoded.get().whoareyou.requestNonce == hexToByteArray[gcmNonceSize](whoareyouRequestNonce)
|
decoded.get().whoareyou.requestNonce == hexToByteArray[gcmNonceSize](whoareyouRequestNonce)
|
||||||
decoded.get().whoareyou.idNonce == hexToByteArray[idNonceSize](whoareyouIdNonce)
|
decoded.get().whoareyou.idNonce == hexToByteArray[idNonceSize](whoareyouIdNonce)
|
||||||
decoded.get().whoareyou.recordSeq == whoareyouEnrSeq
|
decoded.get().whoareyou.recordSeq == whoareyouEnrSeq
|
||||||
|
decoded.get().whoareyou.challengeData == hexToSeqByte(whoareyouChallengeData)
|
||||||
test "Ping Ordinary Message Packet":
|
|
||||||
const
|
|
||||||
# nonce = "0xffffffffffffffffffffffff"
|
|
||||||
readKey = "0x00000000000000000000000000000000"
|
|
||||||
pingReqId = 0x00000001'u64
|
|
||||||
pingEnrSeq = 2'u64
|
|
||||||
|
|
||||||
encodedPacket = "00000000000000000000000000000000088b3d4342776668980a4adf72a8fcaa963f24b27a2f6bb44c7ed5ca10e87de130f94d2390b9853c3fcba22b1e9472d43c9ae48d04689eb84102ed931f66d180cbb4219f369a24f4e6b24d7bdc2a04"
|
|
||||||
|
|
||||||
let dummyKey = "0x00000000000000000000000000000001" # of no importance
|
|
||||||
codecA.sessions.store(nodeB.id, nodeB.address.get(),
|
|
||||||
hexToByteArray[aesKeySize](dummyKey), hexToByteArray[aesKeySize](readKey))
|
|
||||||
codecB.sessions.store(nodeA.id, nodeA.address.get(),
|
|
||||||
hexToByteArray[aesKeySize](readKey), hexToByteArray[aesKeySize](dummyKey))
|
|
||||||
|
|
||||||
# Note: Noticed when comparing these test vectors that we encode reqId as
|
|
||||||
# integer while it seems the test vectors have it encoded as byte seq,
|
|
||||||
# meaning having potentially heaving leading zeroes.
|
|
||||||
|
|
||||||
let decoded = codecB.decodePacket(nodeA.address.get(), hexToSeqByte(encodedPacket))
|
|
||||||
check:
|
|
||||||
decoded.isOK()
|
|
||||||
decoded.get().messageOpt.isSome()
|
|
||||||
decoded.get().messageOpt.get().reqId == pingReqId
|
|
||||||
decoded.get().messageOpt.get().kind == ping
|
|
||||||
decoded.get().messageOpt.get().ping.enrSeq == pingEnrSeq
|
|
||||||
|
|
||||||
test "Ping Handshake Message Packet":
|
test "Ping Handshake Message Packet":
|
||||||
const
|
const
|
||||||
# srcNodeId = "0xaaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb"
|
|
||||||
# destNodeId = "0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9"
|
|
||||||
# nonce = "0xffffffffffffffffffffffff"
|
|
||||||
# readKey = "0x4917330b5aeb51650213f90d5f253c45"
|
|
||||||
|
|
||||||
pingReqId = 0x00000001'u64
|
pingReqId = 0x00000001'u64
|
||||||
pingEnrSeq = 1'u64
|
pingEnrSeq = 1'u64
|
||||||
#
|
#
|
||||||
# handshake inputs:
|
# handshake inputs:
|
||||||
#
|
#
|
||||||
|
whoareyouChallengeData = "0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000001"
|
||||||
whoareyouRequestNonce = "0x0102030405060708090a0b0c"
|
whoareyouRequestNonce = "0x0102030405060708090a0b0c"
|
||||||
whoareyouIdNonce = "0x0102030405060708090a0b0c0d0e0f1000000000000000000000000000000000"
|
whoareyouIdNonce = "0x0102030405060708090a0b0c0d0e0f10"
|
||||||
whoareyouEnrSeq = 1'u64
|
whoareyouEnrSeq = 1'u64
|
||||||
# ephemeralKey = "0x0288ef00023598499cb6c940146d050d2b1fb914198c327f76aad590bead68b6"
|
|
||||||
# ephemeralPubkey = "0x039a003ba6517b473fa0cd74aefe99dadfdb34627f90fec6362df85803908f53a5"
|
|
||||||
|
|
||||||
encodedPacket = "00000000000000000000000000000000088b3d4342776668980a4adf72a8fcaa963f24b27a2f6bb44c7ed5ca10e87de130f94d2390b9853c3dcbded51e9472d43c9ae48d04689ef4d3b340a9cb02d3f5cb5c73f266876372a497ef20dccc83eebcf61f61bc2bb13655118c2dddd4fa7f66210832e7c45c2af87b635121ae132057cce99aa7d2760b31390fea5142053c97feb5fc3f5d0ff3d71008a5b6724bbfc8c97746524e695129d2bd7fccc3d4569a69fd8a783849a117bd23ec5b5d02be0a0c57"
|
encodedPacket =
|
||||||
|
"00000000000000000000000000000000088b3d4342774649305f313964a39e55" &
|
||||||
|
"ea96c005ad521d8c7560413a7008f16c9e6d2f43bbea8814a546b7409ce783d3" &
|
||||||
|
"4c4f53245d08da4bb252012b2cba3f4f374a90a75cff91f142fa9be3e0a5f3ef" &
|
||||||
|
"268ccb9065aeecfd67a999e7fdc137e062b2ec4a0eb92947f0d9a74bfbf44dfb" &
|
||||||
|
"a776b21301f8b65efd5796706adff216ab862a9186875f9494150c4ae06fa4d1" &
|
||||||
|
"f0396c93f215fa4ef524f1eadf5f0f4126b79336671cbcf7a885b1f8bd2a5d83" &
|
||||||
|
"9cf8"
|
||||||
|
|
||||||
let
|
let
|
||||||
whoareyouData = WhoareyouData(
|
whoareyouData = WhoareyouData(
|
||||||
requestNonce: hexToByteArray[gcmNonceSize](whoareyouRequestNonce),
|
requestNonce: hexToByteArray[gcmNonceSize](whoareyouRequestNonce),
|
||||||
idNonce: hexToByteArray[idNonceSize](whoareyouIdNonce),
|
idNonce: hexToByteArray[idNonceSize](whoareyouIdNonce),
|
||||||
recordSeq: whoareyouEnrSeq)
|
recordSeq: whoareyouEnrSeq,
|
||||||
|
challengeData: hexToSeqByte(whoareyouChallengeData))
|
||||||
pubkey = some(privKeyA.toPublicKey())
|
pubkey = some(privKeyA.toPublicKey())
|
||||||
challenge = Challenge(whoareyouData: whoareyouData, pubkey: pubkey)
|
challenge = Challenge(whoareyouData: whoareyouData, pubkey: pubkey)
|
||||||
key = HandShakeKey(nodeId: nodeA.id, address: $(nodeA.address.get()))
|
key = HandShakeKey(nodeId: nodeA.id, address: $(nodeA.address.get()))
|
||||||
|
@ -222,43 +234,44 @@ suite "Discovery v5.1 Test Vectors":
|
||||||
let decoded = codecB.decodePacket(nodeA.address.get(),
|
let decoded = codecB.decodePacket(nodeA.address.get(),
|
||||||
hexToSeqByte(encodedPacket))
|
hexToSeqByte(encodedPacket))
|
||||||
|
|
||||||
skip()
|
check:
|
||||||
# TODO: This test fails at the deriveKeys step. The readkey is not the
|
decoded.isOk()
|
||||||
# expected value of above. Hardcoding that values makes decryption work.
|
decoded.get().message.reqId == pingReqId
|
||||||
# TBI.
|
decoded.get().message.kind == ping
|
||||||
|
decoded.get().message.ping.enrSeq == pingEnrSeq
|
||||||
# check:
|
decoded.get().node.isNone()
|
||||||
# decoded.isOk()
|
|
||||||
# decoded.get().message.reqId == pingReqId
|
|
||||||
# decoded.get().message.kind == ping
|
|
||||||
# decoded.get().message.ping.enrSeq == pingEnrSeq
|
|
||||||
# decoded.get().node.isNone()
|
|
||||||
|
|
||||||
test "Ping Handshake Message Packet with ENR":
|
test "Ping Handshake Message Packet with ENR":
|
||||||
const
|
const
|
||||||
# srcNodeId = "0xaaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb"
|
|
||||||
# destNodeId = "0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9"
|
|
||||||
# nonce = "0xffffffffffffffffffffffff"
|
|
||||||
# readKey = "0x4917330b5aeb51650213f90d5f253c45"
|
|
||||||
|
|
||||||
pingReqId = 0x00000001'u64
|
pingReqId = 0x00000001'u64
|
||||||
pingEnrSeq = 1'u64
|
pingEnrSeq = 1'u64
|
||||||
#
|
#
|
||||||
# handshake inputs:
|
# handshake inputs:
|
||||||
#
|
#
|
||||||
|
whoareyouChallengeData = "0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000"
|
||||||
whoareyouRequestNonce = "0x0102030405060708090a0b0c"
|
whoareyouRequestNonce = "0x0102030405060708090a0b0c"
|
||||||
whoareyouIdNonce = "0x0102030405060708090a0b0c0d0e0f1000000000000000000000000000000000"
|
whoareyouIdNonce = "0x0102030405060708090a0b0c0d0e0f10"
|
||||||
whoareyouEnrSeq = 0'u64
|
whoareyouEnrSeq = 0'u64
|
||||||
# ephemeralKey = "0x0288ef00023598499cb6c940146d050d2b1fb914198c327f76aad590bead68b6"
|
|
||||||
# ephemeralPubkey = "0x039a003ba6517b473fa0cd74aefe99dadfdb34627f90fec6362df85803908f53a5"
|
|
||||||
|
|
||||||
encodedPacket = "00000000000000000000000000000000088b3d4342776668980a4adf72a8fcaa963f24b27a2f6bb44c7ed5ca10e87de130f94d2390b9853c3dcaa0d51e9472d43c9ae48d04689ef4d3d2602a5e89ac340f9e81e722b1d7dac2578d520dd5bc6dc1e38ad3ab33012be1a5d259267a0947bf242219834c5702d1c694c0ceb4a6a27b5d68bd2c2e32e6cb9696706adff216ab862a9186875f9494150c4ae06fa4d1f0396c93f215fa4ef52417d9c40a31564e8d5f31a7f08c38045ff5e30d9661838b1eabee9f1e561120bcc4d9f2f9c839152b4ab970e029b2395b97e8c3aa8d3b497ee98a15e865bcd34effa8b83eb6396bca60ad8f0bff1e047e278454bc2b3d6404c12106a9d0b6107fc2383976fc05fbda2c954d402c28c8fb53a2b3a4b111c286ba2ac4ff880168323c6e97b01dbcbeef4f234e5849f75ab007217c919820aaa1c8a7926d3625917fccc3d4569a69fd8aca026be87afab8e8e645d1ee888992"
|
encodedPacket =
|
||||||
|
"00000000000000000000000000000000088b3d4342774649305f313964a39e55" &
|
||||||
|
"ea96c005ad539c8c7560413a7008f16c9e6d2f43bbea8814a546b7409ce783d3" &
|
||||||
|
"4c4f53245d08da4bb23698868350aaad22e3ab8dd034f548a1c43cd246be9856" &
|
||||||
|
"2fafa0a1fa86d8e7a3b95ae78cc2b988ded6a5b59eb83ad58097252188b902b2" &
|
||||||
|
"1481e30e5e285f19735796706adff216ab862a9186875f9494150c4ae06fa4d1" &
|
||||||
|
"f0396c93f215fa4ef524e0ed04c3c21e39b1868e1ca8105e585ec17315e755e6" &
|
||||||
|
"cfc4dd6cb7fd8e1a1f55e49b4b5eb024221482105346f3c82b15fdaae36a3bb1" &
|
||||||
|
"2a494683b4a3c7f2ae41306252fed84785e2bbff3b022812d0882f06978df84a" &
|
||||||
|
"80d443972213342d04b9048fc3b1d5fcb1df0f822152eced6da4d3f6df27e70e" &
|
||||||
|
"4539717307a0208cd208d65093ccab5aa596a34d7511401987662d8cf62b1394" &
|
||||||
|
"71"
|
||||||
|
|
||||||
let
|
let
|
||||||
whoareyouData = WhoareyouData(
|
whoareyouData = WhoareyouData(
|
||||||
requestNonce: hexToByteArray[gcmNonceSize](whoareyouRequestNonce),
|
requestNonce: hexToByteArray[gcmNonceSize](whoareyouRequestNonce),
|
||||||
idNonce: hexToByteArray[idNonceSize](whoareyouIdNonce),
|
idNonce: hexToByteArray[idNonceSize](whoareyouIdNonce),
|
||||||
recordSeq: whoareyouEnrSeq)
|
recordSeq: whoareyouEnrSeq,
|
||||||
|
challengeData: hexToSeqByte(whoareyouChallengeData))
|
||||||
pubkey = none(PublicKey)
|
pubkey = none(PublicKey)
|
||||||
challenge = Challenge(whoareyouData: whoareyouData, pubkey: pubkey)
|
challenge = Challenge(whoareyouData: whoareyouData, pubkey: pubkey)
|
||||||
key = HandShakeKey(nodeId: nodeA.id, address: $(nodeA.address.get()))
|
key = HandShakeKey(nodeId: nodeA.id, address: $(nodeA.address.get()))
|
||||||
|
@ -268,19 +281,14 @@ suite "Discovery v5.1 Test Vectors":
|
||||||
let decoded = codecB.decodePacket(nodeA.address.get(),
|
let decoded = codecB.decodePacket(nodeA.address.get(),
|
||||||
hexToSeqByte(encodedPacket))
|
hexToSeqByte(encodedPacket))
|
||||||
|
|
||||||
skip()
|
check:
|
||||||
# TODO: This test fails at the deriveKeys step. The readkey is not the
|
decoded.isOk()
|
||||||
# expected value of above. Hardcoding that values makes decryption work.
|
decoded.get().message.reqId == pingReqId
|
||||||
# TBI.
|
decoded.get().message.kind == ping
|
||||||
|
decoded.get().message.ping.enrSeq == pingEnrSeq
|
||||||
|
decoded.get().node.isSome()
|
||||||
|
|
||||||
# check:
|
suite "Discovery v5.1 Additional Encode/Decode":
|
||||||
# decoded.isOk()
|
|
||||||
# decoded.get().message.reqId == pingReqId
|
|
||||||
# decoded.get().message.kind == ping
|
|
||||||
# decoded.get().message.ping.enrSeq == pingEnrSeq
|
|
||||||
# decoded.get().node.isSome()
|
|
||||||
|
|
||||||
suite "Discovery v5.1 Additional":
|
|
||||||
test "Encryption/Decryption":
|
test "Encryption/Decryption":
|
||||||
let
|
let
|
||||||
encryptionKey = hexToByteArray[aesKeySize]("0x9f2d77db7004bf8a1a85107ac686990b")
|
encryptionKey = hexToByteArray[aesKeySize]("0x9f2d77db7004bf8a1a85107ac686990b")
|
||||||
|
@ -288,8 +296,9 @@ suite "Discovery v5.1 Additional":
|
||||||
ad = hexToByteArray[32]("0x93a7400fa0d6a694ebc24d5cf570f65d04215b6ac00757875e3f3a5f42107903")
|
ad = hexToByteArray[32]("0x93a7400fa0d6a694ebc24d5cf570f65d04215b6ac00757875e3f3a5f42107903")
|
||||||
pt = hexToSeqByte("0xa1")
|
pt = hexToSeqByte("0xa1")
|
||||||
|
|
||||||
let ct = encryptGCM(encryptionKey, nonce, pt, ad)
|
let
|
||||||
let decrypted = decryptGCM(encryptionKey, nonce, ct, ad)
|
ct = encryptGCM(encryptionKey, nonce, pt, ad)
|
||||||
|
decrypted = decryptGCM(encryptionKey, nonce, ct, ad)
|
||||||
|
|
||||||
check decrypted.get() == pt
|
check decrypted.get() == pt
|
||||||
|
|
||||||
|
@ -316,13 +325,15 @@ suite "Discovery v5.1 Additional":
|
||||||
check decryptGCM(encryptionKey, nonce, invalidCipher, ad).isNone()
|
check decryptGCM(encryptionKey, nonce, invalidCipher, ad).isNone()
|
||||||
|
|
||||||
test "Encrypt / Decrypt header":
|
test "Encrypt / Decrypt header":
|
||||||
|
var nonce: AESGCMNonce
|
||||||
|
brHmacDrbgGenerate(rng[], nonce)
|
||||||
let
|
let
|
||||||
privKey = PrivateKey.random(rng[])
|
privKey = PrivateKey.random(rng[])
|
||||||
nodeId = privKey.toPublicKey().toNodeId()
|
nodeId = privKey.toPublicKey().toNodeId()
|
||||||
authdata = [byte 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
|
authdata = newSeq[byte](32)
|
||||||
staticHeader = encodeStaticHeader(nodeId, Flag.OrdinaryMessage,
|
staticHeader = encodeStaticHeader(Flag.OrdinaryMessage, nonce,
|
||||||
authdata.len())
|
authdata.len())
|
||||||
header = @staticHeader & @authdata
|
header = staticHeader & authdata
|
||||||
|
|
||||||
var iv: array[128 div 8, byte]
|
var iv: array[128 div 8, byte]
|
||||||
brHmacDrbgGenerate(rng[], iv)
|
brHmacDrbgGenerate(rng[], iv)
|
||||||
|
@ -367,49 +378,47 @@ suite "Discovery v5.1 Additional":
|
||||||
decoded[].requestNonce == nonce
|
decoded[].requestNonce == nonce
|
||||||
|
|
||||||
test "Encode / Decode Whoareyou Packet":
|
test "Encode / Decode Whoareyou Packet":
|
||||||
var
|
var requestNonce: AESGCMNonce
|
||||||
requestNonce: AESGCMNonce
|
|
||||||
idNonce: IdNonce
|
|
||||||
brHmacDrbgGenerate(rng[], idNonce)
|
|
||||||
brHmacDrbgGenerate(rng[], requestNonce)
|
brHmacDrbgGenerate(rng[], requestNonce)
|
||||||
let recordSeq = 0'u64
|
let recordSeq = 0'u64
|
||||||
|
|
||||||
let data = encodeWhoareyouPacket(rng[], codecA, nodeB.id, requestNonce, idNonce,
|
let data = encodeWhoareyouPacket(rng[], codecA, nodeB.id,
|
||||||
recordSeq)
|
nodeB.address.get(), requestNonce, recordSeq, none(PublicKey))
|
||||||
|
|
||||||
let decoded = codecB.decodePacket(nodeA.address.get(), data)
|
let decoded = codecB.decodePacket(nodeA.address.get(), data)
|
||||||
|
|
||||||
|
let key = HandShakeKey(nodeId: nodeB.id, address: $nodeB.address.get())
|
||||||
|
var challenge: Challenge
|
||||||
|
|
||||||
check:
|
check:
|
||||||
|
codecA.handshakes.pop(key, challenge)
|
||||||
decoded.isOk()
|
decoded.isOk()
|
||||||
decoded[].flag == Flag.Whoareyou
|
decoded[].flag == Flag.Whoareyou
|
||||||
decoded[].whoareyou.requestNonce == requestNonce
|
decoded[].whoareyou.requestNonce == requestNonce
|
||||||
decoded[].whoareyou.idNonce == idNonce
|
decoded[].whoareyou.idNonce == challenge.whoareyouData.idNonce
|
||||||
decoded[].whoareyou.recordSeq == recordSeq
|
decoded[].whoareyou.recordSeq == recordSeq
|
||||||
|
|
||||||
test "Encode / Decode Handshake Message Packet":
|
test "Encode / Decode Handshake Message Packet":
|
||||||
var
|
var requestNonce: AESGCMNonce
|
||||||
requestNonce: AESGCMNonce
|
|
||||||
idNonce: IdNonce
|
|
||||||
brHmacDrbgGenerate(rng[], idNonce)
|
|
||||||
brHmacDrbgGenerate(rng[], requestNonce)
|
brHmacDrbgGenerate(rng[], requestNonce)
|
||||||
let recordSeq = 1'u64
|
|
||||||
|
|
||||||
let
|
let
|
||||||
|
recordSeq = 1'u64
|
||||||
m = PingMessage(enrSeq: 0)
|
m = PingMessage(enrSeq: 0)
|
||||||
reqId = RequestId.init(rng[])
|
reqId = RequestId.init(rng[])
|
||||||
message = encodeMessage(m, reqId)
|
message = encodeMessage(m, reqId)
|
||||||
let
|
|
||||||
whoareyouData = WhoareyouData(
|
|
||||||
requestNonce: requestNonce,
|
|
||||||
idNonce: idNonce,
|
|
||||||
recordSeq: recordSeq)
|
|
||||||
pubkey = some(privKeyA.toPublicKey())
|
pubkey = some(privKeyA.toPublicKey())
|
||||||
challenge = Challenge(whoareyouData: whoareyouData, pubkey: pubkey)
|
|
||||||
key = HandShakeKey(nodeId: nodeA.id, address: $(nodeA.address.get()))
|
|
||||||
|
|
||||||
check: not codecB.handshakes.hasKeyOrPut(key, challenge)
|
# Encode/decode whoareyou packet to get the handshake stored and the
|
||||||
|
# whoareyou data returned. It's either that or construct the header for the
|
||||||
|
# whoareyouData manually.
|
||||||
|
let
|
||||||
|
encodedDummy = encodeWhoareyouPacket(rng[], codecB, nodeA.id,
|
||||||
|
nodeA.address.get(), requestNonce, recordSeq, pubkey)
|
||||||
|
decodedDummy = codecA.decodePacket(nodeB.address.get(), encodedDummy)
|
||||||
|
|
||||||
let data = encodeHandshakePacket(rng[], codecA, nodeB.id,
|
let data = encodeHandshakePacket(rng[], codecA, nodeB.id,
|
||||||
nodeB.address.get(), message, idNonce, recordSeq, privKeyB.toPublicKey())
|
nodeB.address.get(), message, decodedDummy[].whoareyou,
|
||||||
|
privKeyB.toPublicKey())
|
||||||
|
|
||||||
let decoded = codecB.decodePacket(nodeA.address.get(), data)
|
let decoded = codecB.decodePacket(nodeA.address.get(), data)
|
||||||
|
|
||||||
|
@ -421,32 +430,28 @@ suite "Discovery v5.1 Additional":
|
||||||
decoded.get().node.isNone()
|
decoded.get().node.isNone()
|
||||||
|
|
||||||
test "Encode / Decode Handshake Message Packet with ENR":
|
test "Encode / Decode Handshake Message Packet with ENR":
|
||||||
var
|
var requestNonce: AESGCMNonce
|
||||||
requestNonce: AESGCMNonce
|
|
||||||
idNonce: IdNonce
|
|
||||||
brHmacDrbgGenerate(rng[], idNonce)
|
|
||||||
brHmacDrbgGenerate(rng[], requestNonce)
|
brHmacDrbgGenerate(rng[], requestNonce)
|
||||||
let
|
let
|
||||||
recordSeq = 0'u64
|
recordSeq = 0'u64
|
||||||
|
|
||||||
m = PingMessage(enrSeq: 0)
|
m = PingMessage(enrSeq: 0)
|
||||||
reqId = RequestId.init(rng[])
|
reqId = RequestId.init(rng[])
|
||||||
message = encodeMessage(m, reqId)
|
message = encodeMessage(m, reqId)
|
||||||
|
|
||||||
whoareyouData = WhoareyouData(requestNonce: requestNonce,
|
|
||||||
idNonce: idNonce, recordSeq: recordSeq)
|
|
||||||
pubkey = none(PublicKey)
|
pubkey = none(PublicKey)
|
||||||
challenge = Challenge(whoareyouData: whoareyouData, pubkey: pubkey)
|
|
||||||
key = HandShakeKey(nodeId: nodeA.id, address: $(nodeA.address.get()))
|
|
||||||
|
|
||||||
# Need to manually add the handshake, which would normally be done when
|
# Encode/decode whoareyou packet to get the handshake stored and the
|
||||||
# sending a whoareyou Packet.
|
# whoareyou data returned. It's either that or construct the header for the
|
||||||
check: not codecB.handshakes.hasKeyOrPut(key, challenge)
|
# whoareyouData manually.
|
||||||
|
let
|
||||||
|
encodedDummy = encodeWhoareyouPacket(rng[], codecB, nodeA.id,
|
||||||
|
nodeA.address.get(), requestNonce, recordSeq, pubkey)
|
||||||
|
decodedDummy = codecA.decodePacket(nodeB.address.get(), encodedDummy)
|
||||||
|
|
||||||
let data = encodeHandshakePacket(rng[], codecA, nodeB.id,
|
let encoded = encodeHandshakePacket(rng[], codecA, nodeB.id,
|
||||||
nodeB.address.get(), message, idNonce, recordSeq, privKeyB.toPublicKey())
|
nodeB.address.get(), message, decodedDummy[].whoareyou,
|
||||||
|
privKeyB.toPublicKey())
|
||||||
|
|
||||||
let decoded = codecB.decodePacket(nodeA.address.get(), data)
|
let decoded = codecB.decodePacket(nodeA.address.get(), encoded)
|
||||||
|
|
||||||
check:
|
check:
|
||||||
decoded.isOk()
|
decoded.isOk()
|
||||||
|
@ -462,7 +467,7 @@ suite "Discovery v5.1 Additional":
|
||||||
reqId = RequestId.init(rng[])
|
reqId = RequestId.init(rng[])
|
||||||
message = encodeMessage(m, reqId)
|
message = encodeMessage(m, reqId)
|
||||||
|
|
||||||
# Need to manually add the secrets the normally get negotiated in the
|
# Need to manually add the secrets that normally get negotiated in the
|
||||||
# handshake packet.
|
# handshake packet.
|
||||||
var secrets: HandshakeSecrets
|
var secrets: HandshakeSecrets
|
||||||
codecA.sessions.store(nodeB.id, nodeB.address.get(), secrets.readKey, secrets.writeKey)
|
codecA.sessions.store(nodeB.id, nodeB.address.get(), secrets.readKey, secrets.writeKey)
|
||||||
|
|
Loading…
Reference in New Issue