Introduce more constants and type aliases

This commit is contained in:
kdeme 2020-02-27 13:45:12 +01:00
parent ad99b96e12
commit 1fab56f894
4 changed files with 76 additions and 61 deletions

View File

@ -4,11 +4,18 @@ import
const const
idNoncePrefix = "discovery-id-nonce" idNoncePrefix = "discovery-id-nonce"
gcmNonceSize* = 12
keyAgreementPrefix = "discovery v5 key agreement" keyAgreementPrefix = "discovery v5 key agreement"
authSchemeName* = "gcm" authSchemeName* = "gcm"
gcmNonceSize* = 12
gcmTagSize = 16
aesKeySize* = 128 div 8
tagSize* = 32 ## size of the tag where each message (except whoareyou) starts
## with
type type
AesKey = array[aesKeySize, byte]
PacketTag = array[tagSize, byte]
AuthResponse = object AuthResponse = object
version: int version: int
signature: array[64, byte] signature: array[64, byte]
@ -21,13 +28,13 @@ type
handshakes*: Table[string, Whoareyou] # TODO: Implement hash for NodeID handshakes*: Table[string, Whoareyou] # TODO: Implement hash for NodeID
HandshakeSecrets = object HandshakeSecrets = object
writeKey: array[16, byte] writeKey: AesKey
readKey: array[16, byte] readKey: AesKey
authRespKey: array[16, byte] authRespKey: AesKey
AuthHeader* = object AuthHeader* = object
auth*: array[12, byte] auth*: AuthTag
idNonce*: array[32, byte] idNonce*: IdNonce
scheme*: string scheme*: string
ephemeralKey*: array[64, byte] ephemeralKey*: array[64, byte]
response*: seq[byte] response*: seq[byte]
@ -39,9 +46,6 @@ type
HandshakeError, HandshakeError,
PacketError PacketError
const
gcmTagSize = 16
proc randomBytes*(v: var openarray[byte]) = proc randomBytes*(v: var openarray[byte]) =
if nimcrypto.randomBytes(v) != v.len: if nimcrypto.randomBytes(v) != v.len:
raise newException(RandomSourceDepleted, "Could not randomize bytes") 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 # 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) var res = cast[ptr UncheckedArray[byte]](addr result)
hkdf(sha256, eph.data, idNonce, info, toOpenArray(res, 0, sizeof(result) - 1)) 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: 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): array[32, byte] = proc packetTag(destNode, srcNode: NodeID): PacketTag =
let destId = destNode.toByteArrayBE() let destId = destNode.toByteArrayBE()
let srcId = srcNode.toByteArrayBE() let srcId = srcNode.toByteArrayBE()
let destidHash = sha256.digest(destId) let destidHash = sha256.digest(destId)
result = srcId xor destidHash.data 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] var nonce: array[gcmNonceSize, byte]
randomBytes(nonce) randomBytes(nonce)
var headEnc: seq[byte] var headEnc: seq[byte]
var writeKey: array[16, byte] var writeKey: AesKey
if challenge.isNil: if challenge.isNil:
headEnc = rlp.encode(nonce) 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 # 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. # 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)) headBuf.add(encryptGCM(writeKey, nonce, body, tag))
return (headBuf, nonce) 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] var dctx: GCM[aes128]
dctx.init(key, nonce, authData) dctx.init(key, nonce, authData)
result = newSeq[byte](ct.len - gcmTagSize) result = newSeq[byte](ct.len - gcmTagSize)
@ -224,13 +232,14 @@ proc decodeEncrypted*(c: var Codec,
fromId: NodeID, fromId: NodeID,
fromAddr: Address, fromAddr: Address,
input: seq[byte], input: seq[byte],
authTag: var array[12, byte], authTag: var AuthTag,
newNode: var Node, newNode: var Node,
packet: var Packet): DecodeStatus = packet: var Packet): DecodeStatus =
let input = input.toRange let input = input.toRange
var r = rlpFromBytes(input[32 .. ^1]) var r = rlpFromBytes(input[tagSize .. ^1])
var auth: AuthHeader var auth: AuthHeader
var readKey: array[16, byte]
var readKey: AesKey
logScope: sender = $fromAddr logScope: sender = $fromAddr
if r.isList: if r.isList:
@ -261,19 +270,19 @@ proc decodeEncrypted*(c: var Codec,
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
authTag = r.read(array[12, byte]) authTag = r.read(AuthTag)
auth.auth = authTag auth.auth = authTag
var writeKey: array[16, byte] var writeKey: array[aesKeySize, byte]
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 PacketError return PacketError
# doAssert(false, "TODO: HANDLE ME!") # doAssert(false, "TODO: HANDLE ME!")
let headSize = 32 + r.position let headSize = tagSize + r.position
let bodyEnc = input[headSize .. ^1] let bodyEnc = input[headSize .. ^1]
let body = decryptGCM(readKey, auth.auth, bodyEnc.toOpenArray, let body = decryptGCM(readKey, auth.auth, bodyEnc.toOpenArray,
input[0 .. 31].toOpenArray) input[0 .. tagSize - 1].toOpenArray)
if body.len > 1: if body.len > 1:
let status = decodePacketBody(body[0], body.toOpenArray(1, body.high), packet) let status = decodePacketBody(body[0], body.toOpenArray(1, body.high), packet)
if status == decodingSuccessful: if status == decodingSuccessful:

View File

@ -8,14 +8,29 @@ import nimcrypto except toHex
logScope: logScope:
topics = "discv5" 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 type
Protocol* = ref object Protocol* = ref object
transp: DatagramTransport transp: DatagramTransport
localNode*: Node localNode*: Node
privateKey: PrivateKey privateKey: PrivateKey
whoareyouMagic: array[32, byte] whoareyouMagic: array[magicSize, byte]
idHash: array[32, byte] idHash: array[32, byte]
pendingRequests: Table[array[12, byte], PendingRequest] pendingRequests: Table[AuthTag, PendingRequest]
db: Database db: Database
routingTable: RoutingTable routingTable: RoutingTable
codec*: Codec codec*: Codec
@ -27,18 +42,7 @@ type
node: Node node: Node
packet: seq[byte] packet: seq[byte]
const proc whoareyouMagic(toNode: NodeId): array[magicSize, byte] =
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] =
const prefix = "WHOAREYOU" const prefix = "WHOAREYOU"
var data: array[prefix.len + sizeof(toNode), byte] var data: array[prefix.len + sizeof(toNode), byte]
data[0 .. sizeof(toNode) - 1] = toNode.toByteArrayBE() 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 = proc isWhoAreYou(d: Protocol, msg: Bytes): bool =
if msg.len > d.whoareyouMagic.len: 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 = proc decodeWhoAreYou(d: Protocol, msg: Bytes): Whoareyou =
result = 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 trace "sending who are you", to = $toNode, toAddress = $address
let challenge = Whoareyou(authTag: authTag, recordSeq: 1) let challenge = Whoareyou(authTag: authTag, recordSeq: 1)
encoding.randomBytes(challenge.idNonce) 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) let (data, _) = d.codec.encodeEncrypted(toNode, encodePacket(packet, reqId), challenge = nil)
d.send(toNode, data) d.send(toNode, data)
const maxNodesPerPacket = 3
var packet: NodesPacket var packet: NodesPacket
packet.total = ceil(nodes.len / maxNodesPerPacket).uint32 packet.total = ceil(nodes.len / maxNodesPerPacket).uint32
@ -156,7 +158,7 @@ proc receive*(d: Protocol, a: Address, msg: Bytes) {.gcsafe,
EthKeysException, EthKeysException,
Secp256k1Exception, Secp256k1Exception,
].} = ].} =
if msg.len < 32: if msg.len < tagSize: # or magicSize, can be either
return # Invalid msg return # Invalid msg
# debug "Packet received: ", length = msg.len # debug "Packet received: ", length = msg.len
@ -175,12 +177,12 @@ proc receive*(d: Protocol, a: Address, msg: Bytes) {.gcsafe,
"due to randomness source depletion." "due to randomness source depletion."
else: else:
var tag: array[32, byte] var tag: array[tagSize, byte]
tag[0 .. ^1] = msg.toOpenArray(0, 31) tag[0 .. ^1] = msg.toOpenArray(0, tagSize - 1)
let senderData = tag xor d.idHash let senderData = tag xor d.idHash
let sender = readUintBE[256](senderData) let sender = readUintBE[256](senderData)
var authTag: array[12, byte] var authTag: AuthTag
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)
@ -278,8 +280,6 @@ proc lookup*(p: Protocol, target: NodeId): Future[seq[Node]] {.async.} =
for node in result: for node in result:
seen.incl(node.id) seen.incl(node.id)
const alpha = 3 # Kademlia concurrency factor
var pendingQueries = newSeqOfCap[Future[seq[Node]]](alpha) var pendingQueries = newSeqOfCap[Future[seq[Node]]](alpha)
while true: while true:

View File

@ -2,12 +2,18 @@ import
hashes, stint, hashes, stint,
../enode, enr ../enode, enr
const
authTagSize* = 12
idNonceSize* = 32
type type
NodeId* = UInt256 NodeId* = UInt256
AuthTag* = array[authTagSize, byte]
IdNonce* = array[idNonceSize, byte]
WhoareyouObj* = object WhoareyouObj* = object
authTag*: array[12, byte] authTag*: AuthTag
idNonce*: array[32, byte] idNonce*: IdNonce
recordSeq*: uint64 recordSeq*: uint64
Whoareyou* = ref WhoareyouObj Whoareyou* = ref WhoareyouObj

View File

@ -19,8 +19,8 @@ suite "Discovery v5 Packet Encodings":
randomPacketRlp = "0x01010101010101010101010101010101010101010101010101010101010101018c0202020202020202020202020404040404040404040404040404040404040404040404040404040404040404040404040404040404040404" randomPacketRlp = "0x01010101010101010101010101010101010101010101010101010101010101018c0202020202020202020202020404040404040404040404040404040404040404040404040404040404040404040404040404040404040404"
var data: seq[byte] var data: seq[byte]
data.add(hexToByteArray[32](tag)) data.add(hexToByteArray[tagSize](tag))
data.add(rlp.encode(hexToByteArray[12](authTag))) data.add(rlp.encode(hexToByteArray[authTagSize](authTag)))
data.add(hexToSeqByte(randomData)) data.add(hexToSeqByte(randomData))
check data == hexToSeqByte(randomPacketRlp) check data == hexToSeqByte(randomPacketRlp)
@ -35,8 +35,8 @@ suite "Discovery v5 Packet Encodings":
# expected output # expected output
whoareyouPacketRlp = "0x0101010101010101010101010101010101010101010101010101010101010101ef8c020202020202020202020202a0030303030303030303030303030303030303030303030303030303030303030301" whoareyouPacketRlp = "0x0101010101010101010101010101010101010101010101010101010101010101ef8c020202020202020202020202a0030303030303030303030303030303030303030303030303030303030303030301"
let challenge = Whoareyou(authTag: hexToByteArray[12](token), let challenge = Whoareyou(authTag: hexToByteArray[authTagSize](token),
idNonce: hexToByteArray[32](idNonce), idNonce: hexToByteArray[idNonceSize](idNonce),
recordSeq: enrSeq) recordSeq: enrSeq)
var data = hexToSeqByte(magic) var data = hexToSeqByte(magic)
data.add(rlp.encode(challenge[])) data.add(rlp.encode(challenge[]))
@ -55,8 +55,8 @@ suite "Discovery v5 Packet Encodings":
# expected output # expected output
authMessageRlp = "0x93a7400fa0d6a694ebc24d5cf570f65d04215b6ac00757875e3f3a5f42107903f8cc8c27b5af763c446acd2749fe8ea0e551b1c44264ab92bc0b3c9b26293e1ba4fed9128f3c3645301e8e119f179c658367636db840b35608c01ee67edff2cffa424b219940a81cf2fb9b66068b1cf96862a17d353e22524fbdcdebc609f85cbd58ebe7a872b01e24a3829b97dd5875e8ffbc4eea81b856570fbf23885c674867ab00320294a41732891457969a0f14d11c995668858b2ad731aa7836888020e2ccc6e0e5776d0d4bc4439161798565a4159aa8620992fb51dcb275c4f755c8b8030c82918898f1ac387f606852a5d12a2d94b8ccb3ba55558229867dc13bfa3648" authMessageRlp = "0x93a7400fa0d6a694ebc24d5cf570f65d04215b6ac00757875e3f3a5f42107903f8cc8c27b5af763c446acd2749fe8ea0e551b1c44264ab92bc0b3c9b26293e1ba4fed9128f3c3645301e8e119f179c658367636db840b35608c01ee67edff2cffa424b219940a81cf2fb9b66068b1cf96862a17d353e22524fbdcdebc609f85cbd58ebe7a872b01e24a3829b97dd5875e8ffbc4eea81b856570fbf23885c674867ab00320294a41732891457969a0f14d11c995668858b2ad731aa7836888020e2ccc6e0e5776d0d4bc4439161798565a4159aa8620992fb51dcb275c4f755c8b8030c82918898f1ac387f606852a5d12a2d94b8ccb3ba55558229867dc13bfa3648"
let authHeader = AuthHeader(auth: hexToByteArray[12](authTag), let authHeader = AuthHeader(auth: hexToByteArray[authTagSize](authTag),
idNonce: hexToByteArray[32](idNonce), idNonce: hexToByteArray[idNonceSize](idNonce),
scheme: authSchemeName, scheme: authSchemeName,
ephemeralKey: hexToByteArray[64](ephemeralPubkey), ephemeralKey: hexToByteArray[64](ephemeralPubkey),
response: hexToSeqByte(authRespCiphertext)) response: hexToSeqByte(authRespCiphertext))
@ -78,8 +78,8 @@ suite "Discovery v5 Packet Encodings":
messageRlp = "0x93a7400fa0d6a694ebc24d5cf570f65d04215b6ac00757875e3f3a5f421079038c27b5af763c446acd2749fe8ea5d12a2d94b8ccb3ba55558229867dc13bfa3648" messageRlp = "0x93a7400fa0d6a694ebc24d5cf570f65d04215b6ac00757875e3f3a5f421079038c27b5af763c446acd2749fe8ea5d12a2d94b8ccb3ba55558229867dc13bfa3648"
var data: seq[byte] var data: seq[byte]
data.add(hexToByteArray[32](tag)) data.add(hexToByteArray[tagSize](tag))
data.add(rlp.encode(hexToByteArray[12](authTag))) data.add(rlp.encode(hexToByteArray[authTagSize](authTag)))
data.add(hexToSeqByte(randomData)) data.add(hexToSeqByte(randomData))
check data == hexToSeqByte(messageRlp) check data == hexToSeqByte(messageRlp)
@ -168,7 +168,7 @@ suite "Discovery v5 Cryptographic Primitives":
let let
c = Codec(privKey: initPrivateKey(localSecretKey)) c = Codec(privKey: initPrivateKey(localSecretKey))
signature = signIDNonce(c, hexToByteArray[32](idNonce), signature = signIDNonce(c, hexToByteArray[idNonceSize](idNonce),
hexToByteArray[64](ephemeralKey)) hexToByteArray[64](ephemeralKey))
check signature.getRaw() == hexToByteArray[64](idNonceSig) check signature.getRaw() == hexToByteArray[64](idNonceSig)
@ -182,10 +182,10 @@ suite "Discovery v5 Cryptographic Primitives":
# expected output # expected output
messageCiphertext = "0xa5d12a2d94b8ccb3ba55558229867dc13bfa3648" messageCiphertext = "0xa5d12a2d94b8ccb3ba55558229867dc13bfa3648"
let encrypted = encryptGCM(hexToByteArray[16](encryptionKey), let encrypted = encryptGCM(hexToByteArray[aesKeySize](encryptionKey),
hexToByteArray[12](nonce), hexToByteArray[authTagSize](nonce),
hexToSeqByte(pt), hexToSeqByte(pt),
hexToByteArray[32](ad)) hexToByteArray[tagSize](ad))
check encrypted == hexToSeqByte(messageCiphertext) check encrypted == hexToSeqByte(messageCiphertext)
test "Authentication Header and Encrypted Message Generation": test "Authentication Header and Encrypted Message Generation":