discv5 encoding: First steps to move to result based error handling

This commit is contained in:
kdeme 2020-04-24 15:40:30 +02:00
parent 28e684ce80
commit 5dec5c5a9b
5 changed files with 150 additions and 133 deletions

View File

@ -1,9 +1,11 @@
import import
std/[tables, options], nimcrypto, stint, chronicles, std/[tables, options], nimcrypto, stint, chronicles, stew/results,
types, node, enr, hkdf, ../enode, eth/[rlp, keys] types, node, enr, hkdf, ../enode, eth/[rlp, keys]
export keys export keys
{.push raises: [Defect].}
const const
idNoncePrefix = "discovery-id-nonce" idNoncePrefix = "discovery-id-nonce"
keyAgreementPrefix = "discovery v5 key agreement" keyAgreementPrefix = "discovery v5 key agreement"
@ -14,7 +16,6 @@ const
## with ## with
type type
PacketTag* = array[tagSize, byte] PacketTag* = array[tagSize, byte]
AuthResponse = object AuthResponse = object
@ -40,23 +41,20 @@ type
ephemeralKey*: array[64, byte] ephemeralKey*: array[64, byte]
response*: seq[byte] response*: seq[byte]
RandomSourceDepleted* = object of CatchableError DecodeError* = enum
DecodeStatus* = enum
Success,
HandshakeError, HandshakeError,
PacketError, PacketError,
DecryptError DecryptError,
UnsupportedPacketType
proc randomBytes2*(v: var openarray[byte]) = DecodeResult*[T] = Result[T, DecodeError]
# TODO if this is called randomBytes it breaks calling the real randomBytes EncodeResult*[T] = Result[T, cstring]
# in other modules... sigh, nim modules and global namespaces...
# ideally, a new random library will take the place of both these proc's
# in as setting without exceptions for such low-level constructs..
if randomBytes(v) != v.len:
raise newException(RandomSourceDepleted, "Could not randomize bytes")
proc idNonceHash(nonce, ephkey: openarray[byte]): MDigest[256] = proc mapErrTo[T, E](r: Result[T, E], v: static DecodeError):
DecodeResult[T] {.raises:[].} =
r.mapErr(proc (e: E): DecodeError = v)
proc idNonceHash(nonce, ephkey: openarray[byte]): MDigest[256] {.raises:[].} =
var ctx: sha256 var ctx: sha256
ctx.init() ctx.init()
ctx.update(idNoncePrefix) ctx.update(idNoncePrefix)
@ -64,31 +62,27 @@ proc idNonceHash(nonce, ephkey: openarray[byte]): MDigest[256] =
ctx.update(ephkey) ctx.update(ephkey)
ctx.finish() ctx.finish()
proc signIDNonce*(c: Codec, idNonce, ephKey: openarray[byte]): SignatureNR = proc signIDNonce*(privKey: PrivateKey, idNonce, ephKey: openarray[byte]):
let sig = signNR(c.privKey, idNonceHash(idNonce, ephKey)) Result[SignatureNR, cstring] =
if sig.isErr: signNR(privKey, idNonceHash(idNonce, ephKey))
raise newException(CatchableError, "Could not sign idNonce")
sig[]
proc deriveKeys(n1, n2: NodeID, priv: PrivateKey, pub: PublicKey, proc deriveKeys(n1, n2: NodeID, priv: PrivateKey, pub: PublicKey,
idNonce: openarray[byte], result: var HandshakeSecrets) = idNonce: openarray[byte]): Result[HandshakeSecrets, cstring] =
let eph = ecdhRawFull(priv, pub) let eph = ? ecdhRawFull(priv, pub)
if eph.isErr:
raise newException(CatchableError, "ecdhRawFull failed")
# TODO: Unneeded allocation here
var info = newSeqOfCap[byte](idNoncePrefix.len + 32 * 2) var info = newSeqOfCap[byte](idNoncePrefix.len + 32 * 2)
for i, c in keyAgreementPrefix: info.add(byte(c)) for i, c in keyAgreementPrefix: info.add(byte(c))
info.add(n1.toByteArrayBE()) info.add(n1.toByteArrayBE())
info.add(n2.toByteArrayBE()) info.add(n2.toByteArrayBE())
# echo "EPH: ", eph.data.toHex, " idNonce: ", challenge.idNonce.toHex, "info: ", info.toHex var secrets: HandshakeSecrets
static: assert(sizeof(secrets) == aesKeySize * 3)
var res = cast[ptr UncheckedArray[byte]](addr secrets)
hkdf(sha256, eph.data, idNonce, info, toOpenArray(res, 0, sizeof(secrets) - 1))
ok(secrets)
static: assert(sizeof(result) == aesKeySize * 3) proc encryptGCM*(key, nonce, pt, authData: openarray[byte]):
var res = cast[ptr UncheckedArray[byte]](addr result) seq[byte] {.raises:[].} =
hkdf(sha256, eph[].data, idNonce, info, toOpenArray(res, 0, sizeof(result) - 1))
proc encryptGCM*(key, nonce, pt, authData: openarray[byte]): seq[byte] =
var ectx: GCM[aes128] var ectx: GCM[aes128]
ectx.init(key, nonce, authData) ectx.init(key, nonce, authData)
result = newSeq[byte](pt.len + gcmTagSize) result = newSeq[byte](pt.len + gcmTagSize)
@ -96,21 +90,26 @@ proc encryptGCM*(key, nonce, pt, authData: openarray[byte]): seq[byte] =
ectx.getTag(result.toOpenArray(pt.len, result.high)) ectx.getTag(result.toOpenArray(pt.len, result.high))
ectx.clear() ectx.clear()
proc makeAuthHeader(c: Codec, toId: NodeID, nonce: array[gcmNonceSize, byte], proc makeAuthHeader(c: Codec,
toId: NodeID,
nonce: array[gcmNonceSize, byte],
handshakeSecrets: var HandshakeSecrets, handshakeSecrets: var HandshakeSecrets,
challenge: Whoareyou): seq[byte] = challenge: Whoareyou):
EncodeResult[seq[byte]] =
var resp = AuthResponse(version: 5) var resp = AuthResponse(version: 5)
let ln = c.localNode let ln = c.localNode
# TODO: What goes over the wire now in case of no updated ENR?
if challenge.recordSeq < ln.record.seqNum: if challenge.recordSeq < ln.record.seqNum:
resp.record = ln.record resp.record = ln.record
let ephKeys = KeyPair.random().tryGet() let ephKeys = ? KeyPair.random()
let signature = ? signIDNonce(c.privKey, challenge.idNonce,
ephKeys.pubkey.toRaw)
resp.signature = signature.toRaw
resp.signature = c.signIDNonce(challenge.idNonce, ephKeys.pubkey.toRaw).toRaw handshakeSecrets = ? deriveKeys(ln.id, toId, ephKeys.seckey, challenge.pubKey,
challenge.idNonce)
deriveKeys(ln.id, toId, ephKeys.seckey, challenge.pubKey, challenge.idNonce,
handshakeSecrets)
let respRlp = rlp.encode(resp) let respRlp = rlp.encode(resp)
@ -118,17 +117,19 @@ proc makeAuthHeader(c: Codec, toId: NodeID, nonce: array[gcmNonceSize, byte],
let respEnc = encryptGCM(handshakeSecrets.authRespKey, zeroNonce, respRLP, []) let respEnc = encryptGCM(handshakeSecrets.authRespKey, zeroNonce, respRLP, [])
let header = AuthHeader(auth: nonce, idNonce: challenge.idNonce, let header = AuthHeader(auth: nonce, idNonce: challenge.idNonce,
scheme: authSchemeName, ephemeralKey: ephKeys.pubkey.toRaw, response: respEnc) scheme: authSchemeName, ephemeralKey: ephKeys.pubkey.toRaw,
rlp.encode(header) response: respEnc)
ok(rlp.encode(header))
proc `xor`[N: static[int], T](a, b: array[N, T]): array[N, T] = proc `xor`[N: static[int], T](a, b: array[N, T]): array[N, T] {.raises:[].} =
for i in 0 .. a.high: for i in 0 .. a.high:
result[i] = a[i] xor b[i] result[i] = a[i] xor b[i]
proc packetTag(destNode, srcNode: NodeID): PacketTag = proc packetTag(destNode, srcNode: NodeID): PacketTag {.raises:[].} =
let destId = destNode.toByteArrayBE() let
let srcId = srcNode.toByteArrayBE() destId = destNode.toByteArrayBE()
let destidHash = sha256.digest(destId) srcId = srcNode.toByteArrayBE()
destidHash = sha256.digest(destId)
result = srcId xor destidHash.data result = srcId xor destidHash.data
proc encodeEncrypted*(c: Codec, proc encodeEncrypted*(c: Codec,
@ -136,9 +137,11 @@ proc encodeEncrypted*(c: Codec,
toAddr: Address, toAddr: Address,
packetData: seq[byte], packetData: seq[byte],
challenge: Whoareyou): challenge: Whoareyou):
(seq[byte], array[gcmNonceSize, byte]) = EncodeResult[(seq[byte], array[gcmNonceSize, byte])] =
var nonce: array[gcmNonceSize, byte] var nonce: array[gcmNonceSize, byte]
randomBytes2(nonce) if randomBytes(nonce) != nonce.len:
return err("Could not randomize bytes")
var headEnc: seq[byte] var headEnc: seq[byte]
var writeKey: AesKey var writeKey: AesKey
@ -152,7 +155,7 @@ proc encodeEncrypted*(c: Codec,
discard c.db.loadKeys(toId, toAddr, readKey, writeKey) discard c.db.loadKeys(toId, toAddr, readKey, writeKey)
else: else:
var sec: HandshakeSecrets var sec: HandshakeSecrets
headEnc = c.makeAuthHeader(toId, nonce, sec, challenge) headEnc = ? c.makeAuthHeader(toId, nonce, sec, challenge)
writeKey = sec.writeKey writeKey = sec.writeKey
# TODO: is it safe to ignore the error here? # TODO: is it safe to ignore the error here?
@ -166,10 +169,10 @@ proc encodeEncrypted*(c: Codec,
headBuf.add(headEnc) headBuf.add(headEnc)
headBuf.add(encryptGCM(writeKey, nonce, body, tag)) headBuf.add(encryptGCM(writeKey, nonce, body, tag))
return (headBuf, nonce) ok((headBuf, nonce))
proc decryptGCM*(key: AesKey, nonce, ct, authData: openarray[byte]): proc decryptGCM*(key: AesKey, nonce, ct, authData: openarray[byte]):
Option[seq[byte]] = Option[seq[byte]] {.raises:[].} =
if ct.len <= gcmTagSize: if ct.len <= gcmTagSize:
debug "cipher is missing tag", len = ct.len debug "cipher is missing tag", len = ct.len
return return
@ -187,62 +190,66 @@ proc decryptGCM*(key: AesKey, nonce, ct, authData: openarray[byte]):
return some(res) return some(res)
type
DecodePacketResult = enum
decodingSuccessful
invalidPacketPayload
invalidPacketType
unsupportedPacketType
proc decodePacketBody(typ: byte, proc decodePacketBody(typ: byte,
body: openarray[byte], body: openarray[byte],
res: var Packet): DecodePacketResult = res: var Packet):
DecodeResult[void] {.raises:[Defect].} =
if typ < PacketKind.low.byte or typ > PacketKind.high.byte: if typ < PacketKind.low.byte or typ > PacketKind.high.byte:
return invalidPacketType return err(PacketError)
let kind = cast[PacketKind](typ) let kind = cast[PacketKind](typ)
res = Packet(kind: kind) res = Packet(kind: kind)
var rlp = rlpFromBytes(body) var rlp = rlpFromBytes(body)
if rlp.enterList: if rlp.enterList:
try:
res.reqId = rlp.read(RequestId) res.reqId = rlp.read(RequestId)
except RlpError:
return err(PacketError)
proc decode[T](rlp: var Rlp, v: var T) {.inline, nimcall.} = proc decode[T](rlp: var Rlp, v: var T)
{.inline, nimcall, raises:[RlpError, ValueError, Defect].} =
for k, v in v.fieldPairs: for k, v in v.fieldPairs:
v = rlp.read(typeof(v)) v = rlp.read(typeof(v))
try:
case kind case kind
of unused: return invalidPacketPayload of unused: return err(PacketError)
of ping: rlp.decode(res.ping) of ping: rlp.decode(res.ping)
of pong: rlp.decode(res.pong) of pong: rlp.decode(res.pong)
of findNode: rlp.decode(res.findNode) of findNode: rlp.decode(res.findNode)
of nodes: rlp.decode(res.nodes) of nodes: rlp.decode(res.nodes)
of regtopic, ticket, regconfirmation, topicquery: of regtopic, ticket, regconfirmation, topicquery:
# TODO Implement these packet types # TODO: Implement support for topic advertisement
return unsupportedPacketType return err(UnsupportedPacketType)
except RlpError, ValueError:
return err(PacketError)
return decodingSuccessful ok()
else: else:
return invalidPacketPayload err(PacketError)
proc decodeAuthResp(c: Codec, fromId: NodeId, head: AuthHeader, proc decodeAuthResp(c: Codec, fromId: NodeId, head: AuthHeader,
challenge: Whoareyou, secrets: var HandshakeSecrets, newNode: var Node): bool = challenge: Whoareyou, secrets: var HandshakeSecrets, newNode: var Node):
DecodeResult[void] {.raises:[Defect].} =
if head.scheme != authSchemeName: if head.scheme != authSchemeName:
warn "Unknown auth scheme" warn "Unknown auth scheme"
return false return err(HandshakeError)
var ephKey = PublicKey.fromRaw(head.ephemeralKey) let ephKey = ? PublicKey.fromRaw(head.ephemeralKey).mapErrTo(HandshakeError)
if ephKey.isErr:
return false
deriveKeys(fromId, c.localNode.id, c.privKey, ephKey[], challenge.idNonce, secrets = ? deriveKeys(fromId, c.localNode.id, c.privKey, ephKey,
secrets) challenge.idNonce).mapErrTo(HandshakeError)
var zeroNonce: array[gcmNonceSize, byte] var zeroNonce: array[gcmNonceSize, byte]
let respData = decryptGCM(secrets.authRespKey, zeroNonce, head.response, []) let respData = decryptGCM(secrets.authRespKey, zeroNonce, head.response, [])
if respData.isNone(): if respData.isNone():
return false return err(HandshakeError)
let authResp = rlp.decode(respData.get(), AuthResponse) var authResp: AuthResponse
try:
authResp = rlp.decode(respData.get(), AuthResponse)
except RlpError, ValueError:
return err(HandshakeError)
# TODO: # TODO:
# 1. Should allow for not having an ENR included, solved for now by sending # 1. Should allow for not having an ENR included, solved for now by sending
# whoareyou with always recordSeq of 0 # whoareyou with always recordSeq of 0
@ -252,8 +259,11 @@ proc decodeAuthResp(c: Codec, fromId: NodeId, head: AuthHeader,
# More TODO: # More TODO:
# This will also not work if ENR does not contain an IP address or if the # This will also not work if ENR does not contain an IP address or if the
# IP address is out of date and doesn't match current UDP end point # IP address is out of date and doesn't match current UDP end point
try:
newNode = newNode(authResp.record) newNode = newNode(authResp.record)
return true ok()
except KeyError, ValueError:
err(HandshakeError)
proc decodeEncrypted*(c: var Codec, proc decodeEncrypted*(c: var Codec,
fromId: NodeID, fromId: NodeID,
@ -261,7 +271,7 @@ proc decodeEncrypted*(c: var Codec,
input: openArray[byte], input: openArray[byte],
authTag: var AuthTag, authTag: var AuthTag,
newNode: var Node, newNode: var Node,
packet: var Packet): DecodeStatus = packet: var Packet): DecodeResult[void] =
var r = rlpFromBytes(input.toOpenArray(tagSize, input.high)) var r = rlpFromBytes(input.toOpenArray(tagSize, input.high))
var auth: AuthHeader var auth: AuthHeader
@ -270,23 +280,27 @@ proc decodeEncrypted*(c: var Codec,
if r.isList: if r.isList:
# Handshake - rlp list indicates auth-header # Handshake - rlp list indicates auth-header
try:
auth = r.read(AuthHeader) auth = r.read(AuthHeader)
except RlpError:
return err(PacketError)
authTag = auth.auth authTag = auth.auth
let key = HandShakeKey(nodeId: fromId, address: $fromAddr) let key = HandShakeKey(nodeId: fromId, address: $fromAddr)
let challenge = c.handshakes.getOrDefault(key) let challenge = c.handshakes.getOrDefault(key)
if challenge.isNil: if challenge.isNil:
trace "Decoding failed (no challenge)" trace "Decoding failed (no challenge)"
return HandshakeError return err(HandshakeError)
if auth.idNonce != challenge.idNonce: if auth.idNonce != challenge.idNonce:
trace "Decoding failed (different nonce)" trace "Decoding failed (different nonce)"
return HandshakeError return err(HandshakeError)
var sec: HandshakeSecrets var sec: HandshakeSecrets
if not c.decodeAuthResp(fromId, auth, challenge, sec, newNode): if c.decodeAuthResp(fromId, auth, challenge, sec, newNode).isErr:
trace "Decoding failed (bad auth)" trace "Decoding failed (bad auth)"
return HandshakeError return err(HandshakeError)
c.handshakes.del(key) c.handshakes.del(key)
# For an incoming handshake, we are not sure the address in the ENR is there # For an incoming handshake, we are not sure the address in the ENR is there
@ -299,16 +313,17 @@ proc decodeEncrypted*(c: var Codec,
# TODO: is it safe to ignore the error here? # TODO: is it safe to ignore the error here?
discard c.db.storeKeys(fromId, fromAddr, sec.readKey, sec.writeKey) discard c.db.storeKeys(fromId, fromAddr, sec.readKey, sec.writeKey)
readKey = sec.readKey readKey = sec.readKey
else: else:
# Message packet or random packet - rlp bytes (size 12) indicates auth-tag # Message packet or random packet - rlp bytes (size 12) indicates auth-tag
try:
authTag = r.read(AuthTag) authTag = r.read(AuthTag)
except RlpError:
return err(PacketError)
auth.auth = authTag auth.auth = authTag
var writeKey: AesKey var writeKey: AesKey
if not c.db.loadKeys(fromId, fromAddr, readKey, writeKey): if not c.db.loadKeys(fromId, fromAddr, readKey, writeKey):
trace "Decoding failed (no keys)" trace "Decoding failed (no keys)"
return DecryptError return err(DecryptError)
# doAssert(false, "TODO: HANDLE ME!")
let headSize = tagSize + r.position let headSize = tagSize + r.position
@ -318,31 +333,29 @@ proc decodeEncrypted*(c: var Codec,
input.toOpenArray(0, tagSize - 1)) input.toOpenArray(0, tagSize - 1))
if body.isNone(): if body.isNone():
discard c.db.deleteKeys(fromId, fromAddr) discard c.db.deleteKeys(fromId, fromAddr)
return DecryptError return err(DecryptError)
let packetData = body.get() let packetData = body.get()
if packetData.len > 1: if packetData.len > 1:
let status = decodePacketBody(packetData[0], decodePacketBody(packetData[0],
packetData.toOpenArray(1, packetData.high), packet) packetData.toOpenArray(1, packetData.high), packet)
if status == decodingSuccessful:
return Success
else: else:
debug "Failed to decode discovery packet", reason = status err(PacketError)
return PacketError
proc newRequestId*(): Result[RequestId, cstring] {.raises:[].} =
var id: RequestId
if randomBytes(addr id, sizeof(id)) != sizeof(id):
err("Could not randomize bytes")
else: else:
return PacketError ok(id)
proc newRequestId*(): RequestId = proc numFields(T: typedesc): int {.raises:[].} =
if randomBytes(addr result, sizeof(result)) != sizeof(result):
raise newException(RandomSourceDepleted, "Could not randomize bytes")
proc numFields(T: typedesc): int =
for k, v in fieldPairs(default(T)): inc result for k, v in fieldPairs(default(T)): inc result
proc encodePacket*[T: SomePacket](p: T, reqId: RequestId): seq[byte] = proc encodePacket*[T: SomePacket](p: T, reqId: RequestId):
seq[byte] {.raises:[].} =
result = newSeqOfCap[byte](64) result = newSeqOfCap[byte](64)
result.add(packetKind(T).ord) result.add(packetKind(T).ord)
# result.add(rlp.encode(p))
const sz = numFields(T) const sz = numFields(T)
var writer = initRlpList(sz + 1) var writer = initRlpList(sz + 1)
@ -350,6 +363,3 @@ proc encodePacket*[T: SomePacket](p: T, reqId: RequestId): seq[byte] =
for k, v in fieldPairs(p): for k, v in fieldPairs(p):
writer.append(v) writer.append(v)
result.add(writer.finish()) result.add(writer.finish())
proc encodePacket*[T: SomePacket](p: T): seq[byte] =
encodePacket(p, newRequestId())

View File

@ -1,9 +1,10 @@
import nimcrypto import nimcrypto
proc hkdf*(HashType: typedesc, secret, salt, info: openarray[byte], output: var openarray[byte]) = proc hkdf*(HashType: typedesc, ikm, salt, info: openarray[byte],
output: var openarray[byte]) =
var ctx: HMAC[HashType] var ctx: HMAC[HashType]
ctx.init(salt) ctx.init(salt)
ctx.update(secret) ctx.update(ikm)
let prk = ctx.finish().data let prk = ctx.finish().data
const hashLen = HashType.bits div 8 const hashLen = HashType.bits div 8

View File

@ -120,6 +120,8 @@ type
node: Node node: Node
packet: seq[byte] packet: seq[byte]
RandomSourceDepleted* = object of CatchableError
proc addNode*(d: Protocol, node: Node) = proc addNode*(d: Protocol, node: Node) =
discard d.routingTable.addNode(node) discard d.routingTable.addNode(node)
@ -182,7 +184,9 @@ proc decodeWhoAreYou(d: Protocol, msg: openArray[byte]): Whoareyou =
proc sendWhoareyou(d: Protocol, address: Address, toNode: NodeId, authTag: AuthTag) = proc sendWhoareyou(d: Protocol, address: Address, toNode: NodeId, authTag: AuthTag) =
trace "sending who are you", to = $toNode, toAddress = $address trace "sending who are you", to = $toNode, toAddress = $address
let challenge = Whoareyou(authTag: authTag, recordSeq: 0) let challenge = Whoareyou(authTag: authTag, recordSeq: 0)
encoding.randomBytes2(challenge.idNonce)
if randomBytes(challenge.idNonce) != challenge.idNonce.len:
raise newException(RandomSourceDepleted, "Could not randomize bytes")
# If there is already a handshake going on for this nodeid then we drop this # If there is already a handshake going on for this nodeid then we drop this
# new one. Handshake will get cleaned up after `handshakeTimeout`. # new one. Handshake will get cleaned up after `handshakeTimeout`.
# If instead overwriting the handshake would be allowed, the handshake timeout # If instead overwriting the handshake would be allowed, the handshake timeout
@ -208,7 +212,7 @@ proc sendNodes(d: Protocol, toId: NodeId, toAddr: Address, reqId: RequestId,
proc sendNodes(d: Protocol, toId: NodeId, toAddr: Address, proc sendNodes(d: Protocol, toId: NodeId, toAddr: Address,
packet: NodesPacket, reqId: RequestId) {.nimcall.} = packet: NodesPacket, reqId: RequestId) {.nimcall.} =
let (data, _) = d.codec.encodeEncrypted(toId, toAddr, let (data, _) = d.codec.encodeEncrypted(toId, toAddr,
encodePacket(packet, reqId), challenge = nil) encodePacket(packet, reqId), challenge = nil).tryGet()
d.send(toAddr, data) d.send(toAddr, data)
var packet: NodesPacket var packet: NodesPacket
@ -234,7 +238,7 @@ proc handlePing(d: Protocol, fromId: NodeId, fromAddr: Address,
pong.port = a.udpPort.uint16 pong.port = a.udpPort.uint16
let (data, _) = d.codec.encodeEncrypted(fromId, fromAddr, let (data, _) = d.codec.encodeEncrypted(fromId, fromAddr,
encodePacket(pong, reqId), challenge = nil) encodePacket(pong, reqId), challenge = nil).tryGet()
d.send(fromAddr, data) d.send(fromAddr, data)
proc handleFindNode(d: Protocol, fromId: NodeId, fromAddr: Address, proc handleFindNode(d: Protocol, fromId: NodeId, fromAddr: Address,
@ -270,9 +274,9 @@ proc receive*(d: Protocol, a: Address, msg: openArray[byte]) {.gcsafe,
whoareyou.pubKey = toNode.node.pubkey # TODO: Yeah, rather ugly this. whoareyou.pubKey = toNode.node.pubkey # TODO: Yeah, rather ugly this.
try: try:
let (data, _) = d.codec.encodeEncrypted(toNode.id, toNode.address, let (data, _) = d.codec.encodeEncrypted(toNode.id, toNode.address,
pr.packet, challenge = whoareyou) pr.packet, challenge = whoareyou).tryGet()
d.send(toNode, data) d.send(toNode, data)
except RandomSourceDepleted as err: except RandomSourceDepleted:
debug "Failed to respond to a who-you-are msg " & debug "Failed to respond to a who-you-are msg " &
"due to randomness source depletion." "due to randomness source depletion."
@ -286,7 +290,7 @@ proc receive*(d: Protocol, a: Address, msg: openArray[byte]) {.gcsafe,
var node: Node var node: Node
var packet: Packet var packet: Packet
let decoded = d.codec.decodeEncrypted(sender, a, msg, authTag, node, packet) let decoded = d.codec.decodeEncrypted(sender, a, msg, authTag, node, packet)
if decoded == DecodeStatus.Success: if decoded.isOk:
if not node.isNil: if not node.isNil:
# Not filling table with nodes without correct IP in the ENR # Not filling table with nodes without correct IP in the ENR
if a.ip == node.address.ip: if a.ip == node.address.ip:
@ -305,19 +309,20 @@ proc receive*(d: Protocol, a: Address, msg: openArray[byte]) {.gcsafe,
waiter.complete(packet.some) waiter.complete(packet.some)
else: else:
debug "TODO: handle packet: ", packet = packet.kind, origin = a debug "TODO: handle packet: ", packet = packet.kind, origin = a
elif decoded == DecodeStatus.DecryptError: elif decoded.error == DecodeError.DecryptError:
debug "Could not decrypt packet, respond with whoareyou", debug "Could not decrypt packet, respond with whoareyou",
localNode = $d.localNode, address = a localNode = $d.localNode, address = a
# only sendingWhoareyou in case it is a decryption failure # only sendingWhoareyou in case it is a decryption failure
d.sendWhoareyou(a, sender, authTag) d.sendWhoareyou(a, sender, authTag)
elif decoded == DecodeStatus.PacketError: elif decoded.error == DecodeError.UnsupportedPacketType:
# Still adding the node in case there is a packet error (could be # Still adding the node in case failure is because of unsupported packet.
# unsupported packet)
if not node.isNil: if not node.isNil:
if a.ip == node.address.ip: if a.ip == node.address.ip:
debug "Adding new node to routing table", node = $node, debug "Adding new node to routing table", node = $node,
localNode = $d.localNode localNode = $d.localNode
discard d.routingTable.addNode(node) discard d.routingTable.addNode(node)
# elif decoded.error == DecodeError.PacketError:
# Not adding this node as from our perspective it is sending rubbish.
proc processClient(transp: DatagramTransport, proc processClient(transp: DatagramTransport,
raddr: TransportAddress): Future[void] {.async, gcsafe.} = raddr: TransportAddress): Future[void] {.async, gcsafe.} =
@ -389,11 +394,11 @@ proc waitNodes(d: Protocol, fromNode: Node, reqId: RequestId): Future[seq[Node]]
proc sendPing(d: Protocol, toNode: Node): RequestId = proc sendPing(d: Protocol, toNode: Node): RequestId =
let let
reqId = newRequestId() reqId = newRequestId().tryGet()
ping = PingPacket(enrSeq: d.localNode.record.seqNum) ping = PingPacket(enrSeq: d.localNode.record.seqNum)
packet = encodePacket(ping, reqId) packet = encodePacket(ping, reqId)
(data, nonce) = d.codec.encodeEncrypted(toNode.id, toNode.address, packet, (data, nonce) = d.codec.encodeEncrypted(toNode.id, toNode.address, packet,
challenge = nil) challenge = nil).tryGet()
d.registerRequest(toNode, packet, nonce) d.registerRequest(toNode, packet, nonce)
d.send(toNode, data) d.send(toNode, data)
return reqId return reqId
@ -406,10 +411,10 @@ proc ping*(d: Protocol, toNode: Node): Future[Option[PongPacket]] {.async.} =
return some(resp.get().pong) return some(resp.get().pong)
proc sendFindNode(d: Protocol, toNode: Node, distance: uint32): RequestId = proc sendFindNode(d: Protocol, toNode: Node, distance: uint32): RequestId =
let reqId = newRequestId() let reqId = newRequestId().tryGet()
let packet = encodePacket(FindNodePacket(distance: distance), reqId) let packet = encodePacket(FindNodePacket(distance: distance), reqId)
let (data, nonce) = d.codec.encodeEncrypted(toNode.id, toNode.address, packet, let (data, nonce) = d.codec.encodeEncrypted(toNode.id, toNode.address, packet,
challenge = nil) challenge = nil).tryGet()
d.registerRequest(toNode, packet, nonce) d.registerRequest(toNode, packet, nonce)
d.send(toNode, data) d.send(toNode, data)

View File

@ -1,5 +1,5 @@
import import
unittest, chronos, sequtils, chronicles, tables, stint, unittest, chronos, sequtils, chronicles, tables, stint, nimcrypto,
eth/[keys, rlp], eth/p2p/enode, eth/trie/db, eth/[keys, rlp], eth/p2p/enode, eth/trie/db,
eth/p2p/discoveryv5/[discovery_db, enr, node, types, routing_table, encoding], eth/p2p/discoveryv5/[discovery_db, enr, node, types, routing_table, encoding],
eth/p2p/discoveryv5/protocol as discv5_protocol, eth/p2p/discoveryv5/protocol as discv5_protocol,
@ -26,8 +26,8 @@ proc randomPacket(tag: PacketTag): seq[byte] =
authTag: AuthTag authTag: AuthTag
msg: array[44, byte] msg: array[44, byte]
randomBytes2(authTag) require randomBytes(authTag) == authTag.len
randomBytes2(msg) require randomBytes(msg) == msg.len
result.add(tag) result.add(tag)
result.add(rlp.encode(authTag)) result.add(rlp.encode(authTag))
result.add(msg) result.add(msg)
@ -98,7 +98,7 @@ suite "Discovery v5 Tests":
let a = localAddress(20303) let a = localAddress(20303)
for i in 0 ..< 5: for i in 0 ..< 5:
randomBytes2(tag) require randomBytes(tag) == tag.len
node.receive(a, randomPacket(tag)) node.receive(a, randomPacket(tag))
# Checking different nodeIds but same address # Checking different nodeIds but same address

View File

@ -166,10 +166,11 @@ suite "Discovery v5 Cryptographic Primitives":
idNonceSig = "0xc5036e702a79902ad8aa147dabfe3958b523fd6fa36cc78e2889b912d682d8d35fdea142e141f690736d86f50b39746ba2d2fc510b46f82ee08f08fd55d133a4" idNonceSig = "0xc5036e702a79902ad8aa147dabfe3958b523fd6fa36cc78e2889b912d682d8d35fdea142e141f690736d86f50b39746ba2d2fc510b46f82ee08f08fd55d133a4"
let let
c = Codec(privKey: PrivateKey.fromHex(localSecretKey)[]) privKey = PrivateKey.fromHex(localSecretKey)[]
signature = signIDNonce(c, hexToByteArray[idNonceSize](idNonce), signature = signIDNonce(privKey, hexToByteArray[idNonceSize](idNonce),
hexToByteArray[64](ephemeralKey)) hexToByteArray[64](ephemeralKey))
check signature.toRaw() == hexToByteArray[64](idNonceSig) require signature.isOK()
check signature[].toRaw() == hexToByteArray[64](idNonceSig)
test "Encryption/Decryption": test "Encryption/Decryption":
const const