Skip to content

Commit

Permalink
add Base58 and refactor SHA256
Browse files Browse the repository at this point in the history
  • Loading branch information
fluency03 committed May 6, 2018
1 parent 2328e94 commit 4510611
Show file tree
Hide file tree
Showing 14 changed files with 236 additions and 43 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ libraryDependencies ++= {
"org.scalaz" %% "scalaz-core" % scalazVersion,
"org.json4s" %% "json4s-native" % json4sVersion,
"org.json4s" %% "json4s-jackson" % json4sVersion,
"org.bouncycastle" % "bcprov-jdk15on" % "1.59"
"org.bouncycastle" % "bcprov-jdk15on" % "1.59"
)
} ++ httpDependencies ++ testDependencies

Expand Down
50 changes: 50 additions & 0 deletions src/main/scala/com/github/fluency03/blockchain/Base58.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.github.fluency03.blockchain

import org.bouncycastle.util.encoders.Hex

import scala.annotation.tailrec

object Base58 {

lazy val ALPHABET: Array[Char] = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray
private val ENCODED_ZERO = ALPHABET(0)

def encodeString(str: String): String = encode(str.getBytes)

def encodeHex(hex: String): String = encode(hex.hex2Bytes)

def encode(bytes: Bytes): String = {
@tailrec
def buildBase58(res: String, bi: BigInt): String =
if (bi <= 0) res
else buildBase58(ALPHABET((bi % 58).toInt) + res, bi / 58)

@tailrec
def confirmZeroByte(res: String, bytes: Array[Byte], idx: Int): String =
if (bytes(idx) != 0 || idx >= bytes.length) res
else confirmZeroByte(ENCODED_ZERO + res, bytes, idx + 1)

confirmZeroByte(buildBase58("", bytes.toBigInt), bytes, 0)
}

def decode(str: String): Bytes = {
@tailrec
def restoreBigInt(chars: Array[Char], bi: BigInt, idx: Int): BigInt =
if (idx >= chars.length) bi
else {
val i: Int = ALPHABET.zipWithIndex.find(t => t._1 == chars(idx)).map(_._2).get
restoreBigInt(chars, bi * 58 + i, idx + 1)
}

val bi = restoreBigInt(str.toCharArray, 0, 0)
Hex.decode(bi.toString(16))
}

def decodeToHex(str: String): String = new String(decode(str))

def checkEncodeHex(str: String): String = encodeString(str + str.hex2Bytes.toSha256Digest.toSha256.substring(0, 8))

def checkEncode(bytes: Bytes): String = encode(bytes ++ bytes.toSha256Digest.toSha256Digest.slice(0, 4))


}
15 changes: 13 additions & 2 deletions src/main/scala/com/github/fluency03/blockchain/Crypto.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,21 @@ object Crypto {
KeyFactory.getInstance(KEY_ALGORITHM)
.generatePrivate(new ECPrivateKeySpec(new BigInteger(hex, 16), ecSpec))

def publicKeyToHex(publicKey: PublicKey): String =
publicKey.asInstanceOf[ECPublicKey].getQ.getEncoded(false).toHex
def publicKeyToHex(publicKey: PublicKey): String = publicKeyToBytes(publicKey).toHex

def privateKeyToHex(privateKey: PrivateKey): String =
privateKey.asInstanceOf[ECPrivateKey].getD.toString(16)

def publicKeyToBytes(publicKey: PublicKey): Bytes =
publicKey.asInstanceOf[ECPublicKey].getQ.getEncoded(false)

def privateKeyToBytes(privateKey: PrivateKey): Bytes =
privateKey.asInstanceOf[ECPrivateKey].getD.toByteArray

def publicKeyToAddress(publicKey: String, networkBytes: String = "00"): String =
Base58.checkEncode(networkBytes.hex2Bytes ++ publicKey.hex2Bytes.toHash160Bytes)




}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.github.fluency03.blockchain

object RIPEMD160 {

}
31 changes: 31 additions & 0 deletions src/main/scala/com/github/fluency03/blockchain/SHA256.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.github.fluency03.blockchain

import java.security.MessageDigest

object SHA256 {

/**
* Generate SHA256 Hash from a input String.
* https://gist.github.com/navicore/6234040bbfce3aa58f866db314c07c15
*/
def hash(text: String) : String = hash(text.getBytes)

/**
* Generate SHA256 Hash from a input Array of Byte.
*/
def hash(bytes: Bytes) : String = String.format("%064x",
new java.math.BigInteger(1, hashToDigest(bytes)))

/**
* Generate digest from a input Array of Byte.
*/
def hashToDigest(bytes: Bytes): Bytes =
MessageDigest.getInstance("SHA-256").digest(bytes)

/**
* Calculate the hash of concatenation a Seq of Strings.
*/
def hashAll(strings: String*): String = hash(strings mkString "")


}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,6 @@ object BlockHeader {
timestamp: Long,
difficulty: Int,
nonce: Int): String =
sha256Of(index.toString, previousHash, data, merkleHash, timestamp.toString, difficulty.toString, nonce.toString)
SHA256.hashAll(index.toString, previousHash, data, merkleHash, timestamp.toString, difficulty.toString, nonce.toString)

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ object Merkle {
case 0 => ZERO64
case 1 => hashes.head
case n if n % 2 != 0 => computeRootOfHashes(hashes :+ hashes.last) // append last element again
case _ => computeRootOfHashes(hashes.grouped(2).map { a => sha256Of(a(0), a(1)) } .toList)
case _ => computeRootOfHashes(hashes.grouped(2).map { a => SHA256.hashAll(a(0), a(1)) } .toList)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ object Transaction {
hashOfTransaction(cbTx) == cbTx.id

// hash of transaction
def hashOfTransaction(tx: Transaction): String = sha256Of(
def hashOfTransaction(tx: Transaction): String = SHA256.hashAll(
tx.txIns.map(tx => tx.previousOut.id + tx.previousOut.index).mkString,
tx.txOuts.map(tx => tx.address + tx.amount).mkString,
tx.timestamp.toString)

def hashOfTransaction(txIns: Seq[TxIn], txOuts: Seq[TxOut], timestamp: Long): String = sha256Of(
def hashOfTransaction(txIns: Seq[TxIn], txOuts: Seq[TxOut], timestamp: Long): String = SHA256.hashAll(
txIns.map(tx => tx.previousOut.id + tx.previousOut.index).mkString,
txOuts.map(tx => tx.address + tx.amount).mkString,
timestamp.toString)
Expand Down
54 changes: 31 additions & 23 deletions src/main/scala/com/github/fluency03/blockchain/package.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package com.github.fluency03

import java.nio.charset.Charset
import java.security.{MessageDigest, PrivateKey, PublicKey}
import java.security.{PrivateKey, PublicKey}
import java.time.Instant

import com.github.fluency03.blockchain.Crypto.{privateKeyToHex, publicKeyToHex}
import com.github.fluency03.blockchain.core.{Peer, PeerSimple, TxIn, TxOut}
import org.bouncycastle.crypto.digests.RIPEMD160Digest
import org.bouncycastle.util.encoders.{Base64, Hex}
import org.json4s.native.Serialization
import org.json4s.{Formats, NoTypeHints}
Expand Down Expand Up @@ -33,15 +34,24 @@ package object blockchain {

implicit class StringImplicit(val str: String) {
def hex2Long: Long = java.lang.Long.parseLong(str, 16)
def hex2BigInt: BigInt = BigInt(str, 16)
def hex2Bytes: Array[Byte] = Hex.decode(str)
def hex2Binary: String = binaryOfHex(str)
def toBase64: String = base64Of(str.getBytes("UTF-8"))
def toSha256: String = sha256HashOf(str)
def toBase64: String = base64Of(str.getBytes)
def toSha256: String = SHA256.hash(str)
def toRipemd160Of: String = ripemd160Of(str)
}

implicit class BytesImplicit(val bytes: Bytes) {
def toHex: String = Hex.toHexString(bytes)
def toBigInt: BigInt = BigInt(bytes)
def toBase64: String = base64Of(bytes)
def toSha256: String = SHA256.hash(bytes)
def toSha256Digest: Bytes = SHA256.hashToDigest(bytes)
def toRipemd160Of: String = ripemd160Of(bytes)
def toRipemd160ODigest: Bytes = ripemd160ODigestOf(bytes)
def toHash160: String = hash160Of(bytes)
def toHash160Bytes: Bytes = hash160BytesOf(bytes)
}

implicit class PublicKeyImplicit(val publicKey: PublicKey) {
Expand All @@ -57,25 +67,6 @@ package object blockchain {
}


/**
* Generate SHA256 Hash from a input String.
* https://gist.github.com/navicore/6234040bbfce3aa58f866db314c07c15
*/
def sha256HashOf(text: String) : String = String.format("%064x",
new java.math.BigInteger(1, digestOf(text)))

/**
* Generate digest from a input String.
* https://gist.github.com/navicore/6234040bbfce3aa58f866db314c07c15
*/
def digestOf(text: String): Bytes =
MessageDigest.getInstance("SHA-256").digest(text.getBytes("UTF-8"))

/**
* Calculate the hash of concatenation a Seq of Strings.
*/
def sha256Of(strings: String*): String = sha256HashOf(strings mkString "")

/**
* Return the current timestamp in Unix Epoch Time.
*/
Expand All @@ -99,7 +90,7 @@ package object blockchain {
/**
* Encode a String to Base64.
*/
def base64Of(text: String): String = Base64.toBase64String(text.getBytes("UTF-8"))
def base64Of(text: String): String = Base64.toBase64String(text.getBytes)

/**
* Encode an Array of Bytes String to Base64.
Expand All @@ -112,5 +103,22 @@ package object blockchain {
def fromBase64(base64: String): String = new String(Base64.decode(base64), "UTF-8")


def ripemd160Of(str: String): String = ripemd160Of(str.getBytes)

def ripemd160Of(bytes: Bytes): String = ripemd160ODigestOf(bytes).map("%02x".format(_)).mkString

def ripemd160ODigestOf(bytes: Bytes): Bytes = {
val (raw, messageDigest) = (bytes, new RIPEMD160Digest())
messageDigest.update(raw, 0, raw.length)
val out = Array.fill[Byte](messageDigest.getDigestSize)(0)
messageDigest.doFinal(out, 0)
out
}

def hash160Of(bytes: Bytes): String = ripemd160Of(SHA256.hashToDigest(bytes))

def hash160BytesOf(bytes: Bytes): Bytes = ripemd160ODigestOf(SHA256.hashToDigest(bytes))



}
44 changes: 44 additions & 0 deletions src/test/scala/com/github/fluency03/blockchain/Base58Test.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.github.fluency03.blockchain

import org.scalatest.{FlatSpec, Matchers}

class Base58Test extends FlatSpec with Matchers {

"Base58" should "encode String to Base58 and decode it back to original." in {
Base58.encodeString("abc") shouldEqual "ZiCa"
Base58.encodeString("bitcoin") shouldEqual "4jJc4sAwPs"
Base58.encodeString("blockchain") shouldEqual "6XidGdGeMggztM"
Base58.encodeString("00F5F2D624CFB5C3F66D06123D0829D1C9CEBF770E2C13A798") shouldEqual
"bSMTi3tDLFwyLC26U3SB8ctp7Y4iCcGpXxztHUWQSeo4tXV6p7WABrsxVa4tB7n8e8iT"

Base58.decodeToHex("ZiCa") shouldEqual "abc"
Base58.decodeToHex("4jJc4sAwPs") shouldEqual "bitcoin"
Base58.decodeToHex("6XidGdGeMggztM") shouldEqual "blockchain"

Base58.encodeString("04b4d653fcbb4b96000c99343f23b08a44fa306031e0587f9e657ab4a254112" +
"9368d7d9bb05cd8afbdf7705a6540d98028236965553f91bf1c5b4f70073f55b55d") shouldEqual
"2f6iufmY2PoZhwnZkWwvNYmN6A3G4dH8TSDH1Y5FKpC7yCxoJfqStHLBmkUYrwkekaYUttiAwYWCtioTWJn1s" +
"mMSGtMwsyLqmSLpQbLrTpSrMNjKcNzL1viBDBkFuJEM3KMtPAx2g2hVLeBFDP79iaqHvwuVyu2zaViPVeLjWtRpWzu4sM"

Base58.decodeToHex("2f6iufmY2PoZhwnZkWwvNYmN6A3G4dH8TSDH1Y5FKpC7yCxoJfqStHLBmkUYrwkekaYUttiAwYWCtioTWJn1s" +
"mMSGtMwsyLqmSLpQbLrTpSrMNjKcNzL1viBDBkFuJEM3KMtPAx2g2hVLeBFDP79iaqHvwuVyu2zaViPVeLjWtRpWzu4sM") shouldEqual
"04b4d653fcbb4b96000c99343f23b08a44fa306031e0587f9e657ab4a254112" +
"9368d7d9bb05cd8afbdf7705a6540d98028236965553f91bf1c5b4f70073f55b55d"

ripemd160Of("61956bf4e271df1cd88a9a7828a59c88eb7ea13c176c4d03355ac27129760673") shouldEqual
"352b0b6bd7284755d5c685fb7793c9f4d672c5ff"
ripemd160Of("abcd") shouldEqual "2e7e536fd487deaa943fda5522d917bdb9011b7a"
ripemd160Of("205575f4f33a39ff47f569613a694c6321d6cdd7") shouldEqual "bd4e962413308b4a6689aa0e7cff5e419391c3db"
ripemd160Of("bitcoin") shouldEqual "5891bf40b0b0e8e19f524bdc2e842d012264624b"
ripemd160Of("blockchain") shouldEqual "5c403af45cae136a79eea3c7e9f79c3dd049776b"

Crypto.publicKeyToAddress("04B4D653FCBB4B96000C99343F23B08A44FA306031E0587F9E657AB" +
"4A2541129368D7D9BB05CD8AFBDF7705A6540D98028236965553F91BF1C5B4F70073F55B55D") shouldEqual
"1DU8Hi1sbHTpEP9vViBEkEw6noeUrgKkJH"

Base58.encodeHex("0088C2D2FA846282C870A76CADECBE45C4ACD72BB655DA1216") shouldEqual
"1DU8Hi1sbHTpEP9vViBEkEw6noeUrgKkJH"

}

}
12 changes: 12 additions & 0 deletions src/test/scala/com/github/fluency03/blockchain/RIPEMD160Test.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.github.fluency03.blockchain

import org.scalatest.{FlatSpec, Matchers}

class RIPEMD160Test extends FlatSpec with Matchers {

"RIPEMD160" should "encode String to RIPEMD160 and decode it back to original." in {


}

}
39 changes: 39 additions & 0 deletions src/test/scala/com/github/fluency03/blockchain/SHA256Test.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.github.fluency03.blockchain

import org.scalatest.{FlatSpec, Matchers}

class SHA256Test extends FlatSpec with Matchers {

"SHA256" should "encode String to SHA256 and decode it back to original." in {
SHA256.hash("open sesame") shouldEqual "41ef4bb0b23661e66301aac36066912dac037827b4ae63a7b1165a5aa93ed4eb"
SHA256.hashAll("open", " ", "sesame") shouldEqual "41ef4bb0b23661e66301aac36066912dac037827b4ae63a7b1165a5aa93ed4eb"
SHA256.hash("") shouldEqual "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
SHA256.hash("0000000000000000000000000000000000000000000000000000000000000") shouldEqual "a738b0b5c122d30af5b9da1c63c5d590a31aeafa7de1723ee9b5e3a11c9def35"

SHA256.hash(("04b4d653fcbb4b96000c99343f23b08a44fa306031e0587f9e657ab4a25411" +
"29368d7d9bb05cd8afbdf7705a6540d98028236965553f91bf1c5b4f70073f55b55d").hex2Bytes) shouldEqual
"173BDED8F2A2069C193E63EA30DC8FD20E815EC3642B9C24AD7002C03D1BFB9B".toLowerCase

SHA256.hashToDigest(("04b4d653fcbb4b96000c99343f23b08a44fa306031e0587f9e657ab4a25411" +
"29368d7d9bb05cd8afbdf7705a6540d98028236965553f91bf1c5b4f70073f55b55d").hex2Bytes) shouldEqual
"173BDED8F2A2069C193E63EA30DC8FD20E815EC3642B9C24AD7002C03D1BFB9B".hex2Bytes

SHA256.hash("0088C2D2FA846282C870A76CADECBE45C4ACD72BB6".hex2Bytes) shouldEqual
"1F87490FC565C795595563D56412A0100CD1F29FFB60A3779789FE0C018C6164".toLowerCase


SHA256.hash("1F87490FC565C795595563D56412A0100CD1F29FFB60A3779789FE0C018C6164".hex2Bytes) shouldEqual
"55DA1216A5EF5BAE605B543A5A9CE2AC8A8FA1781AA037F35DE3F2222BAD8127".toLowerCase

ripemd160Of("173BDED8F2A2069C193E63EA30DC8FD20E815EC3642B9C24AD7002C03D1BFB9B".hex2Bytes) shouldEqual
"88C2D2FA846282C870A76CADECBE45C4ACD72BB6".toLowerCase

hash160BytesOf(("04b4d653fcbb4b96000c99343f23b08a44fa306031e0587f9e657ab4a25411" +
"29368d7d9bb05cd8afbdf7705a6540d98028236965553f91bf1c5b4f70073f55b55d").hex2Bytes) shouldEqual
"88C2D2FA846282C870A76CADECBE45C4ACD72BB6".hex2Bytes

SHA256.hashToDigest("00".hex2Bytes ++ "88C2D2FA846282C870A76CADECBE45C4ACD72BB6".hex2Bytes) shouldEqual
"1F87490FC565C795595563D56412A0100CD1F29FFB60A3779789FE0C018C6164".hex2Bytes
}

}
7 changes: 0 additions & 7 deletions src/test/scala/com/github/fluency03/blockchain/UtilTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,6 @@ import org.scalatest.{FlatSpec, Matchers}

class UtilTest extends FlatSpec with Matchers with MockFactory {

"hashOf" should "convert a String to SHA256 hash." in {
sha256Of("open sesame") shouldEqual "41ef4bb0b23661e66301aac36066912dac037827b4ae63a7b1165a5aa93ed4eb"
sha256Of("open", " ", "sesame") shouldEqual "41ef4bb0b23661e66301aac36066912dac037827b4ae63a7b1165a5aa93ed4eb"
sha256Of("") shouldEqual "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
sha256Of("0000000000000000000000000000000000000000000000000000000000000") shouldEqual "a738b0b5c122d30af5b9da1c63c5d590a31aeafa7de1723ee9b5e3a11c9def35"
}

"getCurrentTimestamp" should "be able to get current Unix epoch time." in {
val t1 = getCurrentTimestamp
val t2 = getCurrentTimestamp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,21 @@ class MerkleTest extends FlatSpec with Matchers {
"A Merkle Tree" should "have a valid root hash." in {
val h1 = "41ef4bb0b23661e66301aac36066912dac037827b4ae63a7b1165a5aa93ed4eb"
val h2 = "000031bee3fa033f2d69ae7d0d9f565bf3a235452ccf8a5edffb78cfbcdd7137"
val h12 = sha256Of(h1, h2)
val h12 = SHA256.hashAll(h1, h2)
computeRootOfHashes(List(h1, h2)) shouldEqual h12

val h3 = "000031beekdjnvj2310i0i0c4i3jomo1m2km10ijodsjco1edffb78cfbcdd7137"
val h33 = sha256Of(h3, h3)
computeRootOfHashes(List(h1, h2, h3)) shouldEqual sha256Of(h12, h33)
val h33 = SHA256.hashAll(h3, h3)
computeRootOfHashes(List(h1, h2, h3)) shouldEqual SHA256.hashAll(h12, h33)

val t1 = createCoinbaseTx(1, genesisMiner, genesisTimestamp)
val t2 = createCoinbaseTx(2, genesisMiner, genesisTimestamp)
val th12 = sha256Of(t1.id, t2.id)
val th12 = SHA256.hashAll(t1.id, t2.id)
computeRoot(List(t1, t2)) shouldEqual th12

val t3 = createCoinbaseTx(3, genesisMiner, genesisTimestamp)
val th33 = sha256Of(t3.id, t3.id)
computeRoot(List(t1, t2, t3)) shouldEqual sha256Of(th12, th33)
val th33 = SHA256.hashAll(t3.id, t3.id)
computeRoot(List(t1, t2, t3)) shouldEqual SHA256.hashAll(th12, th33)
}

}

0 comments on commit 4510611

Please sign in to comment.