Skip to content

Instantly share code, notes, and snippets.

@stanbar
Last active August 24, 2018 19:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stanbar/1c0a5c67b5c6d9e7683bccd624237348 to your computer and use it in GitHub Desktop.
Save stanbar/1c0a5c67b5c6d9e7683bccd624237348 to your computer and use it in GitHub Desktop.
Simple kotlin blockchain implementation demo
import java.math.BigDecimal
import java.math.BigInteger
import java.security.MessageDigest
import java.security.SecureRandom
import java.util.*
import java.util.concurrent.LinkedBlockingDeque
import kotlin.system.measureTimeMillis
val sha256: MessageDigest = MessageDigest.getInstance("SHA-256")
val rand: SecureRandom = SecureRandom.getInstanceStrong()
val me = ByteArray(32).also { rand.nextBytes(it) }
val alice = ByteArray(32).also { rand.nextBytes(it) }
val bob = ByteArray(32).also { rand.nextBytes(it) }
fun main(args: Array<String>) {
val blockchain = LinkedBlockingDeque<Block>()
val transaction1 = Transaction(me, bob, BigDecimal.ONE)
val transaction2 = Transaction(bob, alice, BigDecimal.ONE)
val transaction3 = Transaction(alice, me, BigDecimal.ONE)
val block = Block(mutableListOf(transaction1, transaction2, transaction3))
blockchain.push(block)
println(blockchain.joinToString())
for (difficultyBits in 1..32)
mine(block, difficultyBits)
}
data class Transaction(
val from: ByteArray,
val to: ByteArray,
val amount: BigDecimal
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Transaction
if (!Arrays.equals(from, other.from)) return false
if (!Arrays.equals(to, other.to)) return false
if (amount != other.amount) return false
return true
}
override fun hashCode(): Int {
return Arrays.hashCode(hash())
}
fun hash(): ByteArray {
return sha256.digest(from + to + amount.toPlainString().toByteArray())
}
override fun toString(): String {
val fromString = from.toHexString()
val to = to.toHexString()
val amountString = amount.toPlainString()
return """
from: $fromString
to: $to
amount: $amountString
""".trimIndent()
}
}
data class Block(val transactions: MutableList<Transaction>) {
fun toByteArray(): ByteArray {
val hashes = transactions.map { it.hash() }
return hashes.reduce { acc, bytes -> acc + bytes }
}
}
fun mine(block: Block, difficultyBits: Int) {
val coinbase = Transaction(ByteArray(0) { it.toByte() }, me, 25.0.toBigDecimal())
block.transactions.add(0, coinbase)
var hashResult = BigInteger.ZERO
var nonce = BigInteger.ZERO
println("Difficulty: ${1L shl difficultyBits} ($difficultyBits bits)")
println("Starting search...")
val time = measureTimeMillis {
val result = proofOfWork(block, difficultyBits)
hashResult = result.first
nonce = result.second
}
println("Hash is ${hashResult.toByteArray().toHexString()}")
println("Elapsed time: ${formatDurationInMillis(time)} seconds")
println("Hashes per second : ${nonce.toLong() / (time / 1000.0)}")
println()
}
fun proofOfWork(block: Block, difficultyBits: Int): Pair<BigInteger, BigInteger> {
val target = BigInteger.TWO.pow(256 - difficultyBits)
var nonce = BigInteger.ZERO
while (nonce < BigInteger.TWO.pow(32)) {
val hashResult = BigInteger(1, sha256.digest(block.toByteArray() + nonce.toByteArray()))
if (hashResult < target) {
return hashResult to nonce
}
nonce++
}
throw IllegalStateException("Difficulty too high")
}
fun formatDurationInMillis(durationInMillis: Long): String {
val millis = durationInMillis % 1000
val second = durationInMillis / 1000 % 60
val minute = durationInMillis / (1000 * 60) % 60
val hour = durationInMillis / (1000 * 60 * 60) % 24
return "%02d:%02d:%02d.%d".format(hour, minute, second, millis)
}
fun ByteArray.toHexString() = joinToString("") { Integer.toHexString(0xff and it.toInt()) }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment