This is the first in a series of articles on how to implement a Kademlia DHT in Kotlin.

We will be following the specification documents at:-

Creating the globally unique NodeId

The first item to implement will be the NodeID:-

NodeIDs are binary numbers of length B = 160 bits. In basic Kademlia, each node chooses its own ID by some unspecified quasi-random procedure. It is important that nodeIDs be uniformly distributed; the network design relies upon this.

Because 160 bits = 20 bytes we will therefore create a ByteArray that will hold 20 random bytes:-

1
ByteArray(20).map { ThreadLocalRandom.current().nextInt(257).toByte() }

Note:ThreadLocalRandom.current().nextInt(257) will return us a random Int value between 0 and 256 which will be in the range -128 to 127 when converted to a signed Byte.

Next, we will create an extension method on ByteArray which will easily allow us to return a hex encoded string:-

1
2
fun ByteArray.toHexString(): String = map
{ String.format("%02x", (it.toInt() and 0xFF)) }.joinToString("")

Once we have a hex encoded string at some point we will need convert it back into a ByteArray. That can be done with the following code:-

1
2
3
4
5
6
7
8
9
private fun hexDigit(c: Char): Int = Character.digit(c, 16)
fun String.hexToByteArray(): ByteArray {
val data = ByteArray(length / 2)
var i = 0
while (i < length)
data[i / 2] = hexDigit(this[i++]).shl(4).or(hexDigit(this[i++])).toByte()
return data
}

Creating The Key

Data being stored in or retrieved from a Kademlia network must also have a key of length B. These keys should also be uniformly distributed. There are several ways to guarantee this; the most common is to take a hash, such as the 160 bit SHA1 digest, of the value.

1
2
3
4
fun ByteArray.sha1(): String {
val md = MessageDigest.getInstance("SHA-1")
return md.digest(this).map { String.format("%02x", it) }.joinToString("")
}