From 1fab56f894ce8dac01823698da95610950ebbf7f Mon Sep 17 00:00:00 2001 From: kdeme Date: Thu, 27 Feb 2020 13:45:12 +0100 Subject: [PATCH] Introduce more constants and type aliases --- eth/p2p/discoveryv5/encoding.nim | 53 +++++++++++++++++------------- eth/p2p/discoveryv5/protocol.nim | 50 ++++++++++++++-------------- eth/p2p/discoveryv5/types.nim | 10 ++++-- tests/p2p/test_discv5_encoding.nim | 24 +++++++------- 4 files changed, 76 insertions(+), 61 deletions(-) diff --git a/eth/p2p/discoveryv5/encoding.nim b/eth/p2p/discoveryv5/encoding.nim index 2bd3a35..373a3ac 100644 --- a/eth/p2p/discoveryv5/encoding.nim +++ b/eth/p2p/discoveryv5/encoding.nim @@ -4,11 +4,18 @@ import const idNoncePrefix = "discovery-id-nonce" - gcmNonceSize* = 12 keyAgreementPrefix = "discovery v5 key agreement" authSchemeName* = "gcm" + gcmNonceSize* = 12 + gcmTagSize = 16 + aesKeySize* = 128 div 8 + tagSize* = 32 ## size of the tag where each message (except whoareyou) starts + ## with type + AesKey = array[aesKeySize, byte] + PacketTag = array[tagSize, byte] + AuthResponse = object version: int signature: array[64, byte] @@ -21,13 +28,13 @@ type handshakes*: Table[string, Whoareyou] # TODO: Implement hash for NodeID HandshakeSecrets = object - writeKey: array[16, byte] - readKey: array[16, byte] - authRespKey: array[16, byte] + writeKey: AesKey + readKey: AesKey + authRespKey: AesKey AuthHeader* = object - auth*: array[12, byte] - idNonce*: array[32, byte] + auth*: AuthTag + idNonce*: IdNonce scheme*: string ephemeralKey*: array[64, byte] response*: seq[byte] @@ -39,9 +46,6 @@ type HandshakeError, PacketError -const - gcmTagSize = 16 - proc randomBytes*(v: var openarray[byte]) = if nimcrypto.randomBytes(v) != v.len: raise newException(RandomSourceDepleted, "Could not randomize bytes") @@ -72,7 +76,7 @@ proc deriveKeys(n1, n2: NodeID, priv: PrivateKey, pub: PublicKey, # echo "EPH: ", eph.data.toHex, " idNonce: ", challenge.idNonce.toHex, "info: ", info.toHex - static: assert(sizeof(result) == 16 * 3) + static: assert(sizeof(result) == aesKeySize * 3) var res = cast[ptr UncheckedArray[byte]](addr result) hkdf(sha256, eph.data, idNonce, info, toOpenArray(res, 0, sizeof(result) - 1)) @@ -114,22 +118,26 @@ proc `xor`[N: static[int], T](a, b: array[N, T]): array[N, T] = for i in 0 .. a.high: result[i] = a[i] xor b[i] -proc packetTag(destNode, srcNode: NodeID): array[32, byte] = +proc packetTag(destNode, srcNode: NodeID): PacketTag = let destId = destNode.toByteArrayBE() let srcId = srcNode.toByteArrayBE() let destidHash = sha256.digest(destId) result = srcId xor destidHash.data -proc encodeEncrypted*(c: Codec, toNode: Node, packetData: seq[byte], challenge: Whoareyou): (seq[byte], array[gcmNonceSize, byte]) = +proc encodeEncrypted*(c: Codec, + toNode: Node, + packetData: seq[byte], + challenge: Whoareyou): + (seq[byte], array[gcmNonceSize, byte]) = var nonce: array[gcmNonceSize, byte] randomBytes(nonce) var headEnc: seq[byte] - var writeKey: array[16, byte] + var writeKey: AesKey if challenge.isNil: headEnc = rlp.encode(nonce) - var readKey: array[16, byte] + var readKey: AesKey # We might not have the node's keys if the handshake hasn't been performed # yet. That's fine, we will be responded with whoareyou. @@ -152,7 +160,7 @@ proc encodeEncrypted*(c: Codec, toNode: Node, packetData: seq[byte], challenge: headBuf.add(encryptGCM(writeKey, nonce, body, tag)) return (headBuf, nonce) -proc decryptGCM(key: array[16, byte], nonce, ct, authData: openarray[byte]): seq[byte] = +proc decryptGCM(key: AesKey, nonce, ct, authData: openarray[byte]): seq[byte] = var dctx: GCM[aes128] dctx.init(key, nonce, authData) result = newSeq[byte](ct.len - gcmTagSize) @@ -224,13 +232,14 @@ proc decodeEncrypted*(c: var Codec, fromId: NodeID, fromAddr: Address, input: seq[byte], - authTag: var array[12, byte], + authTag: var AuthTag, newNode: var Node, packet: var Packet): DecodeStatus = let input = input.toRange - var r = rlpFromBytes(input[32 .. ^1]) + var r = rlpFromBytes(input[tagSize .. ^1]) var auth: AuthHeader - var readKey: array[16, byte] + + var readKey: AesKey logScope: sender = $fromAddr if r.isList: @@ -261,19 +270,19 @@ proc decodeEncrypted*(c: var Codec, else: # Message packet or random packet - rlp bytes (size 12) indicates auth-tag - authTag = r.read(array[12, byte]) + authTag = r.read(AuthTag) auth.auth = authTag - var writeKey: array[16, byte] + var writeKey: array[aesKeySize, byte] if not c.db.loadKeys(fromId, fromAddr, readKey, writeKey): trace "Decoding failed (no keys)" return PacketError # doAssert(false, "TODO: HANDLE ME!") - let headSize = 32 + r.position + let headSize = tagSize + r.position let bodyEnc = input[headSize .. ^1] let body = decryptGCM(readKey, auth.auth, bodyEnc.toOpenArray, - input[0 .. 31].toOpenArray) + input[0 .. tagSize - 1].toOpenArray) if body.len > 1: let status = decodePacketBody(body[0], body.toOpenArray(1, body.high), packet) if status == decodingSuccessful: diff --git a/eth/p2p/discoveryv5/protocol.nim b/eth/p2p/discoveryv5/protocol.nim index c4d2e5d..4979f1a 100644 --- a/eth/p2p/discoveryv5/protocol.nim +++ b/eth/p2p/discoveryv5/protocol.nim @@ -8,14 +8,29 @@ import nimcrypto except toHex logScope: topics = "discv5" +const + alpha = 3 ## Kademlia concurrency factor + lookupRequestLimit = 3 + findNodeResultLimit = 15 # applies in FINDNODE handler + maxNodesPerPacket = 3 + lookupInterval = 60.seconds ## Interval of launching a random lookup to + ## populate the routing table. go-ethereum seems to do 3 runs every 30 + ## minutes. Trinity starts one every minute. + handshakeTimeout* = 2.seconds ## timeout for the reply on the + ## whoareyou message + responseTimeout* = 2.seconds ## timeout for the response of a request-response + ## call + magicSize = 32 ## size of the magic which is the start of the whoareyou + ## message + type Protocol* = ref object transp: DatagramTransport localNode*: Node privateKey: PrivateKey - whoareyouMagic: array[32, byte] + whoareyouMagic: array[magicSize, byte] idHash: array[32, byte] - pendingRequests: Table[array[12, byte], PendingRequest] + pendingRequests: Table[AuthTag, PendingRequest] db: Database routingTable: RoutingTable codec*: Codec @@ -27,18 +42,7 @@ type node: Node packet: seq[byte] -const - lookupRequestLimit = 3 - findNodeResultLimit = 15 # applies in FINDNODE handler - lookupInterval = 60.seconds ## Interval of launching a random lookup to - ## populate the routing table. go-ethereum seems to do 3 runs every 30 - ## minutes. Trinity starts one every minute. - handshakeTimeout* = 2.seconds ## timeout for the reply on the - ## whoareyou message - responseTimeout* = 2.seconds ## timeout for the response of a request-response - ## call - -proc whoareyouMagic(toNode: NodeId): array[32, byte] = +proc whoareyouMagic(toNode: NodeId): array[magicSize, byte] = const prefix = "WHOAREYOU" var data: array[prefix.len + sizeof(toNode), byte] data[0 .. sizeof(toNode) - 1] = toNode.toByteArrayBE() @@ -80,13 +84,13 @@ proc `xor`[N: static[int], T](a, b: array[N, T]): array[N, T] = proc isWhoAreYou(d: Protocol, msg: Bytes): bool = if msg.len > d.whoareyouMagic.len: - result = d.whoareyouMagic == msg.toOpenArray(0, 31) + result = d.whoareyouMagic == msg.toOpenArray(0, magicSize - 1) proc decodeWhoAreYou(d: Protocol, msg: Bytes): Whoareyou = result = Whoareyou() - result[] = rlp.decode(msg.toRange[32 .. ^1], WhoareyouObj) + result[] = rlp.decode(msg.toRange[magicSize .. ^1], WhoareyouObj) -proc sendWhoareyou(d: Protocol, address: Address, toNode: NodeId, authTag: array[12, byte]) = +proc sendWhoareyou(d: Protocol, address: Address, toNode: NodeId, authTag: AuthTag) = trace "sending who are you", to = $toNode, toAddress = $address let challenge = Whoareyou(authTag: authTag, recordSeq: 1) encoding.randomBytes(challenge.idNonce) @@ -111,8 +115,6 @@ proc sendNodes(d: Protocol, toNode: Node, reqId: RequestId, nodes: openarray[Nod let (data, _) = d.codec.encodeEncrypted(toNode, encodePacket(packet, reqId), challenge = nil) d.send(toNode, data) - const maxNodesPerPacket = 3 - var packet: NodesPacket packet.total = ceil(nodes.len / maxNodesPerPacket).uint32 @@ -156,7 +158,7 @@ proc receive*(d: Protocol, a: Address, msg: Bytes) {.gcsafe, EthKeysException, Secp256k1Exception, ].} = - if msg.len < 32: + if msg.len < tagSize: # or magicSize, can be either return # Invalid msg # debug "Packet received: ", length = msg.len @@ -175,12 +177,12 @@ proc receive*(d: Protocol, a: Address, msg: Bytes) {.gcsafe, "due to randomness source depletion." else: - var tag: array[32, byte] - tag[0 .. ^1] = msg.toOpenArray(0, 31) + var tag: array[tagSize, byte] + tag[0 .. ^1] = msg.toOpenArray(0, tagSize - 1) let senderData = tag xor d.idHash let sender = readUintBE[256](senderData) - var authTag: array[12, byte] + var authTag: AuthTag var node: Node var packet: Packet let decoded = d.codec.decodeEncrypted(sender, a, msg, authTag, node, packet) @@ -278,8 +280,6 @@ proc lookup*(p: Protocol, target: NodeId): Future[seq[Node]] {.async.} = for node in result: seen.incl(node.id) - const alpha = 3 # Kademlia concurrency factor - var pendingQueries = newSeqOfCap[Future[seq[Node]]](alpha) while true: diff --git a/eth/p2p/discoveryv5/types.nim b/eth/p2p/discoveryv5/types.nim index 41d0720..f56ea21 100644 --- a/eth/p2p/discoveryv5/types.nim +++ b/eth/p2p/discoveryv5/types.nim @@ -2,12 +2,18 @@ import hashes, stint, ../enode, enr +const + authTagSize* = 12 + idNonceSize* = 32 + type NodeId* = UInt256 + AuthTag* = array[authTagSize, byte] + IdNonce* = array[idNonceSize, byte] WhoareyouObj* = object - authTag*: array[12, byte] - idNonce*: array[32, byte] + authTag*: AuthTag + idNonce*: IdNonce recordSeq*: uint64 Whoareyou* = ref WhoareyouObj diff --git a/tests/p2p/test_discv5_encoding.nim b/tests/p2p/test_discv5_encoding.nim index b102c5c..5a7bfaa 100644 --- a/tests/p2p/test_discv5_encoding.nim +++ b/tests/p2p/test_discv5_encoding.nim @@ -19,8 +19,8 @@ suite "Discovery v5 Packet Encodings": randomPacketRlp = "0x01010101010101010101010101010101010101010101010101010101010101018c0202020202020202020202020404040404040404040404040404040404040404040404040404040404040404040404040404040404040404" var data: seq[byte] - data.add(hexToByteArray[32](tag)) - data.add(rlp.encode(hexToByteArray[12](authTag))) + data.add(hexToByteArray[tagSize](tag)) + data.add(rlp.encode(hexToByteArray[authTagSize](authTag))) data.add(hexToSeqByte(randomData)) check data == hexToSeqByte(randomPacketRlp) @@ -35,8 +35,8 @@ suite "Discovery v5 Packet Encodings": # expected output whoareyouPacketRlp = "0x0101010101010101010101010101010101010101010101010101010101010101ef8c020202020202020202020202a0030303030303030303030303030303030303030303030303030303030303030301" - let challenge = Whoareyou(authTag: hexToByteArray[12](token), - idNonce: hexToByteArray[32](idNonce), + let challenge = Whoareyou(authTag: hexToByteArray[authTagSize](token), + idNonce: hexToByteArray[idNonceSize](idNonce), recordSeq: enrSeq) var data = hexToSeqByte(magic) data.add(rlp.encode(challenge[])) @@ -55,8 +55,8 @@ suite "Discovery v5 Packet Encodings": # expected output authMessageRlp = "0x93a7400fa0d6a694ebc24d5cf570f65d04215b6ac00757875e3f3a5f42107903f8cc8c27b5af763c446acd2749fe8ea0e551b1c44264ab92bc0b3c9b26293e1ba4fed9128f3c3645301e8e119f179c658367636db840b35608c01ee67edff2cffa424b219940a81cf2fb9b66068b1cf96862a17d353e22524fbdcdebc609f85cbd58ebe7a872b01e24a3829b97dd5875e8ffbc4eea81b856570fbf23885c674867ab00320294a41732891457969a0f14d11c995668858b2ad731aa7836888020e2ccc6e0e5776d0d4bc4439161798565a4159aa8620992fb51dcb275c4f755c8b8030c82918898f1ac387f606852a5d12a2d94b8ccb3ba55558229867dc13bfa3648" - let authHeader = AuthHeader(auth: hexToByteArray[12](authTag), - idNonce: hexToByteArray[32](idNonce), + let authHeader = AuthHeader(auth: hexToByteArray[authTagSize](authTag), + idNonce: hexToByteArray[idNonceSize](idNonce), scheme: authSchemeName, ephemeralKey: hexToByteArray[64](ephemeralPubkey), response: hexToSeqByte(authRespCiphertext)) @@ -78,8 +78,8 @@ suite "Discovery v5 Packet Encodings": messageRlp = "0x93a7400fa0d6a694ebc24d5cf570f65d04215b6ac00757875e3f3a5f421079038c27b5af763c446acd2749fe8ea5d12a2d94b8ccb3ba55558229867dc13bfa3648" var data: seq[byte] - data.add(hexToByteArray[32](tag)) - data.add(rlp.encode(hexToByteArray[12](authTag))) + data.add(hexToByteArray[tagSize](tag)) + data.add(rlp.encode(hexToByteArray[authTagSize](authTag))) data.add(hexToSeqByte(randomData)) check data == hexToSeqByte(messageRlp) @@ -168,7 +168,7 @@ suite "Discovery v5 Cryptographic Primitives": let c = Codec(privKey: initPrivateKey(localSecretKey)) - signature = signIDNonce(c, hexToByteArray[32](idNonce), + signature = signIDNonce(c, hexToByteArray[idNonceSize](idNonce), hexToByteArray[64](ephemeralKey)) check signature.getRaw() == hexToByteArray[64](idNonceSig) @@ -182,10 +182,10 @@ suite "Discovery v5 Cryptographic Primitives": # expected output messageCiphertext = "0xa5d12a2d94b8ccb3ba55558229867dc13bfa3648" - let encrypted = encryptGCM(hexToByteArray[16](encryptionKey), - hexToByteArray[12](nonce), + let encrypted = encryptGCM(hexToByteArray[aesKeySize](encryptionKey), + hexToByteArray[authTagSize](nonce), hexToSeqByte(pt), - hexToByteArray[32](ad)) + hexToByteArray[tagSize](ad)) check encrypted == hexToSeqByte(messageCiphertext) test "Authentication Header and Encrypted Message Generation":