Last active
August 24, 2018 19:23
-
-
Save stanbar/1c0a5c67b5c6d9e7683bccd624237348 to your computer and use it in GitHub Desktop.
Simple kotlin blockchain implementation demo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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