From f997aaaab87e62abfb4f89da0e9e8fad1ad6f4b5 Mon Sep 17 00:00:00 2001 From: fluency03 Date: Sat, 12 May 2018 23:27:13 +0200 Subject: [PATCH] add tests for Wallet and Merkle --- .../blockchain/core/KeyContainer.scala | 9 ++- .../fluency03/blockchain/core/Merkle.scala | 2 +- .../fluency03/blockchain/core/Peer.scala | 5 +- .../fluency03/blockchain/core/Wallet.scala | 14 +++- .../blockchain/crypto/Secp256k1.scala | 10 ++- .../blockchain/core/KeyContainerTest.scala | 4 ++ .../blockchain/core/MerkleTest.scala | 69 ++++++++++++++++++- .../blockchain/core/WalletTest.scala | 30 +++++++- 8 files changed, 132 insertions(+), 11 deletions(-) diff --git a/src/main/scala/com/github/fluency03/blockchain/core/KeyContainer.scala b/src/main/scala/com/github/fluency03/blockchain/core/KeyContainer.scala index 1f550a9..542ccb9 100644 --- a/src/main/scala/com/github/fluency03/blockchain/core/KeyContainer.scala +++ b/src/main/scala/com/github/fluency03/blockchain/core/KeyContainer.scala @@ -1,7 +1,7 @@ package com.github.fluency03.blockchain package core -import java.security.KeyPair +import java.security._ import com.github.fluency03.blockchain.core.KeyContainer.balanceOfKey import com.github.fluency03.blockchain.core.Transaction.signTxIn @@ -11,7 +11,7 @@ import scala.collection.mutable case class KeyContainer() { - private[this] val keyPair: KeyPair = Secp256k1.generateKeyPair() + val keyPair: KeyPair = Secp256k1.generateKeyPair() // TODO (Chang): change it to actual address (which is a Base58) of a PublicKey lazy val address: HexString = keyPair.getPublic.toHex @@ -20,6 +20,11 @@ case class KeyContainer() { def balance(uTxOs: mutable.Map[Outpoint, TxOut]): Long = balanceOfKey(this, uTxOs) + def sign(data: Bytes): Bytes = Secp256k1.sign(data, keyPair.getPrivate) + + def verify(data: Bytes, signature: Bytes): Boolean = + Secp256k1.verify(data, keyPair.getPublic, signature) + def sign(txId: String, txIn: TxIn, uTxOs: mutable.Map[Outpoint, TxOut]): Option[TxIn] = signTxIn(txId, txIn, keyPair, uTxOs) diff --git a/src/main/scala/com/github/fluency03/blockchain/core/Merkle.scala b/src/main/scala/com/github/fluency03/blockchain/core/Merkle.scala index ab7a3c9..20ff3e1 100644 --- a/src/main/scala/com/github/fluency03/blockchain/core/Merkle.scala +++ b/src/main/scala/com/github/fluency03/blockchain/core/Merkle.scala @@ -23,7 +23,7 @@ object Merkle { @tailrec def hashViaMerklePath(init: String, path: Seq[String], index: Int): String = - if (path.isEmpty) init + if (path.isEmpty) init.toLowerCase() else { val newHash = if (index % 2 == 0) SHA256.hashAll(init, path.head) diff --git a/src/main/scala/com/github/fluency03/blockchain/core/Peer.scala b/src/main/scala/com/github/fluency03/blockchain/core/Peer.scala index dac19ec..efa35e1 100644 --- a/src/main/scala/com/github/fluency03/blockchain/core/Peer.scala +++ b/src/main/scala/com/github/fluency03/blockchain/core/Peer.scala @@ -1,4 +1,5 @@ -package com.github.fluency03.blockchain.core +package com.github.fluency03.blockchain +package core case class PeerSimple(name: String) -case class Peer(name: String, publicKeys: Set[String]) +case class Peer(name: String, publicKeys: Set[HexString]) diff --git a/src/main/scala/com/github/fluency03/blockchain/core/Wallet.scala b/src/main/scala/com/github/fluency03/blockchain/core/Wallet.scala index fbe7036..76d6b83 100644 --- a/src/main/scala/com/github/fluency03/blockchain/core/Wallet.scala +++ b/src/main/scala/com/github/fluency03/blockchain/core/Wallet.scala @@ -6,6 +6,8 @@ import scala.collection.mutable trait Wallet { + def size(): Int + def getKey(str: String): Option[KeyContainer] def newKey(): KeyContainer @@ -17,9 +19,14 @@ trait Wallet { case class RandomWallet() extends Wallet { - private[this] val keys = mutable.Map.empty[String, KeyContainer] + val keys: mutable.Map[String, KeyContainer] = { + val initKeys = mutable.Map.empty[String, KeyContainer] + val aNewKey = KeyContainer() + initKeys += (aNewKey.publicKeyHex -> aNewKey) + initKeys + } - override def getKey(str: String): Option[KeyContainer] = keys.get(str) + override def getKey(hash: String): Option[KeyContainer] = keys.get(hash) override def newKey(): KeyContainer = { val aNewKey = KeyContainer() @@ -30,6 +37,7 @@ case class RandomWallet() extends Wallet { override def balance(uTxOs: mutable.Map[Outpoint, TxOut]): Long = keys.values.map(k => balanceOfKey(k, uTxOs)).sum + override def size(): Int = keys.size } @@ -43,4 +51,6 @@ case class SeededWallet() extends Wallet { override def balance(uTxOs: mutable.Map[Outpoint, TxOut]): Long = ??? + override def size(): Int = ??? + } diff --git a/src/main/scala/com/github/fluency03/blockchain/crypto/Secp256k1.scala b/src/main/scala/com/github/fluency03/blockchain/crypto/Secp256k1.scala index 9474271..555c480 100644 --- a/src/main/scala/com/github/fluency03/blockchain/crypto/Secp256k1.scala +++ b/src/main/scala/com/github/fluency03/blockchain/crypto/Secp256k1.scala @@ -24,9 +24,12 @@ object Secp256k1 { val keySpec: PKCS8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKey) val keyFactory: KeyFactory = KeyFactory.getInstance(KEY_ALGORITHM) val key: PrivateKey = keyFactory.generatePrivate(keySpec) + sign(data, key) + } + def sign(data: Bytes, privateKey: PrivateKey): Bytes = { val sig: Signature = Signature.getInstance(KEY_ALGORITHM, KEY_PROVIDER) - sig.initSign(key, new SecureRandom) + sig.initSign(privateKey, new SecureRandom) sig.update(data) sig.sign() } @@ -35,9 +38,12 @@ object Secp256k1 { val keySpec: X509EncodedKeySpec = new X509EncodedKeySpec(publicKey) val keyFactory: KeyFactory = KeyFactory.getInstance(KEY_ALGORITHM) val key: PublicKey = keyFactory.generatePublic(keySpec) + verify(data, key, signature) + } + def verify(data: Bytes, publicKey: PublicKey, signature: Bytes): Boolean = { val sig: Signature = Signature.getInstance(KEY_ALGORITHM, KEY_PROVIDER) - sig.initVerify(key) + sig.initVerify(publicKey) sig.update(data) sig.verify(signature) } diff --git a/src/test/scala/com/github/fluency03/blockchain/core/KeyContainerTest.scala b/src/test/scala/com/github/fluency03/blockchain/core/KeyContainerTest.scala index aaaa4bb..4b2e3c3 100644 --- a/src/test/scala/com/github/fluency03/blockchain/core/KeyContainerTest.scala +++ b/src/test/scala/com/github/fluency03/blockchain/core/KeyContainerTest.scala @@ -37,6 +37,10 @@ class KeyContainerTest extends FlatSpec with Matchers { val signedTxIn = kc.sign(id, txIn, uTxOs) signedTxIn shouldEqual Some(TxIn(Outpoint("def0", 0), signedTxIn.get.signature)) + + kc.verify("abc".getBytes, kc.sign("abc".getBytes)) shouldEqual true + kc.verify("".getBytes, kc.sign("abc".getBytes)) shouldEqual false + kc.verify("123".getBytes, kc.sign("123".getBytes)) shouldEqual true } diff --git a/src/test/scala/com/github/fluency03/blockchain/core/MerkleTest.scala b/src/test/scala/com/github/fluency03/blockchain/core/MerkleTest.scala index 122f6a3..7cff845 100644 --- a/src/test/scala/com/github/fluency03/blockchain/core/MerkleTest.scala +++ b/src/test/scala/com/github/fluency03/blockchain/core/MerkleTest.scala @@ -21,7 +21,7 @@ class MerkleTest extends FlatSpec with Matchers { computeRoot(List(t)) shouldEqual t.id } - "A Merkle Tree" should "have a valid root hash." in { + it should "be able to compute valid root hash." in { val h1 = "41ef4bb0b23661e66301aac36066912dac037827b4ae63a7b1165a5aa93ed4eb" val h2 = "000031bee3fa033f2d69ae7d0d9f565bf3a235452ccf8a5edffb78cfbcdd7137" val h12 = SHA256.hashAll(h1, h2) @@ -41,4 +41,71 @@ class MerkleTest extends FlatSpec with Matchers { computeRoot(List(t1, t2, t3)) shouldEqual SHA256.hashAll(th12, th33) } + it should "be able to compute root hash via merkle path and verify it." in { + hashViaMerklePath( + "D97A21CF46FD5AFB0BF9EA4237BC4BF5C84E8B47D38D1EEE2BBEB5C0F8A1C625", + Seq.empty[HexString], + 0) shouldEqual + "D97A21CF46FD5AFB0BF9EA4237BC4BF5C84E8B47D38D1EEE2BBEB5C0F8A1C625".toLowerCase() + + hashViaMerklePath( + "D97A21CF46FD5AFB0BF9EA4237BC4BF5C84E8B47D38D1EEE2BBEB5C0F8A1C625", + Seq("AE1E670BDBF8AB984F412E6102C369AECA2CED933A1DE74712CCDA5EDAF4EE57"), + 0) shouldEqual + "e2fbeaa16b00fb4ba139c62158971612aa8cddf6163082c74fa74ebb5004c10b" + + hashViaMerklePath( + "D97A21CF46FD5AFB0BF9EA4237BC4BF5C84E8B47D38D1EEE2BBEB5C0F8A1C625", + Seq( + "AE1E670BDBF8AB984F412E6102C369AECA2CED933A1DE74712CCDA5EDAF4EE57", + "EFC2B3DB87FF4F00C79DFA8F732A23C0E18587A73A839B7710234583CDD03DB9"), + 2) shouldEqual + "dce52948923f07840400d52cc5deb037c8cef400a2e97699146a291112477ce0" + + hashViaMerklePath( + "e2fbeaa16b00fb4ba139c62158971612aa8cddf6163082c74fa74ebb5004c10b", + Seq("EFC2B3DB87FF4F00C79DFA8F732A23C0E18587A73A839B7710234583CDD03DB9"), + 1) shouldEqual + "dce52948923f07840400d52cc5deb037c8cef400a2e97699146a291112477ce0" + + hashViaMerklePath( + "D97A21CF46FD5AFB0BF9EA4237BC4BF5C84E8B47D38D1EEE2BBEB5C0F8A1C625", + Seq( + "AE1E670BDBF8AB984F412E6102C369AECA2CED933A1DE74712CCDA5EDAF4EE57", + "EFC2B3DB87FF4F00C79DFA8F732A23C0E18587A73A839B7710234583CDD03DB9", + "F1B6FE8FC2AB800E6D76EE975A002D3E67A60B51A62085A07289505B8D03F149"), + 6) shouldEqual + hashViaMerklePath( + "dce52948923f07840400d52cc5deb037c8cef400a2e97699146a291112477ce0", + Seq("F1B6FE8FC2AB800E6D76EE975A002D3E67A60B51A62085A07289505B8D03F149"), + 1) + + hashViaMerklePath( + "D97A21CF46FD5AFB0BF9EA4237BC4BF5C84E8B47D38D1EEE2BBEB5C0F8A1C625", + Seq( + "AE1E670BDBF8AB984F412E6102C369AECA2CED933A1DE74712CCDA5EDAF4EE57", + "EFC2B3DB87FF4F00C79DFA8F732A23C0E18587A73A839B7710234583CDD03DB9", + "F1B6FE8FC2AB800E6D76EE975A002D3E67A60B51A62085A07289505B8D03F149", + "E827331B1FE7A2689FBC23D14CD21317C699596CBCA222182A489322ECE1FA74"), + 6) shouldEqual + hashViaMerklePath( + hashViaMerklePath( + "dce52948923f07840400d52cc5deb037c8cef400a2e97699146a291112477ce0", + Seq("F1B6FE8FC2AB800E6D76EE975A002D3E67A60B51A62085A07289505B8D03F149"), + 1), + Seq("E827331B1FE7A2689FBC23D14CD21317C699596CBCA222182A489322ECE1FA74"), + 0) + + verifySimplified( + "D97A21CF46FD5AFB0BF9EA4237BC4BF5C84E8B47D38D1EEE2BBEB5C0F8A1C625", + "79f56ece2f6f9082bf36c6f131bbe85ac4a3f0c5a07527e29f19e78c5bc281f8", + Seq( + "AE1E670BDBF8AB984F412E6102C369AECA2CED933A1DE74712CCDA5EDAF4EE57", + "EFC2B3DB87FF4F00C79DFA8F732A23C0E18587A73A839B7710234583CDD03DB9", + "F1B6FE8FC2AB800E6D76EE975A002D3E67A60B51A62085A07289505B8D03F149", + "E827331B1FE7A2689FBC23D14CD21317C699596CBCA222182A489322ECE1FA74"), + 6) shouldEqual true + + } + } diff --git a/src/test/scala/com/github/fluency03/blockchain/core/WalletTest.scala b/src/test/scala/com/github/fluency03/blockchain/core/WalletTest.scala index 4d3c721..cf4aad8 100644 --- a/src/test/scala/com/github/fluency03/blockchain/core/WalletTest.scala +++ b/src/test/scala/com/github/fluency03/blockchain/core/WalletTest.scala @@ -2,10 +2,38 @@ package com.github.fluency03.blockchain.core import org.scalatest.{FlatSpec, Matchers} +import scala.collection.mutable + class WalletTest extends FlatSpec with Matchers { - "Wallet" should "do something." in { + "RandomWallet" should "maintain a set of keys in random way." in { + val wallet = RandomWallet() + wallet shouldBe a[Wallet] + wallet.size() shouldEqual 1 + val kc1 = wallet.keys.values.head + + wallet.getKey(kc1.publicKeyHex) shouldEqual Some(kc1) + wallet.getKey("") shouldEqual None + + val kc2 = wallet.newKey() + kc2 shouldBe a[KeyContainer] + wallet.size() shouldEqual 2 + wallet.getKey(kc2.publicKeyHex) shouldEqual Some(kc2) + + val uTxOs: mutable.Map[Outpoint, TxOut] = mutable.Map.empty[Outpoint, TxOut] + wallet.balance(uTxOs) shouldEqual 0 + + uTxOs += (Outpoint("def0", 0) -> TxOut(kc1.publicKeyHex, 40)) + uTxOs += (Outpoint("def0", 1) -> TxOut("abc4", 40)) + wallet.balance(uTxOs) shouldEqual 40 + kc1.balance(uTxOs) shouldEqual 40 + kc2.balance(uTxOs) shouldEqual 0 + + uTxOs += (Outpoint("def0", 2) -> TxOut(kc2.publicKeyHex, 40)) + wallet.balance(uTxOs) shouldEqual 80 + kc1.balance(uTxOs) shouldEqual 40 + kc2.balance(uTxOs) shouldEqual 40 } }