From 78f4dfb8c66eedc9c5b758ae5880274bf74571d2 Mon Sep 17 00:00:00 2001 From: Nadav Kohen Date: Thu, 6 May 2021 13:19:52 -0500 Subject: [PATCH] Pubkey Refactor (#2936) * Removed ExecutionContext from ECKey * Refactored ECPublicKey to remove compression state and introduced ECPublicKeyBytes to handle cases where serialization of input is important * Fixed the rest of bitcoin-s so that it passes all tests * Made all ECKeys into case classes * Successfully added isFullyValid invariant to ECPublicKey! * Fixed docs * Added scaladocs and fixed a RpcPsbtResult bug * Reject private keys of length < 32 and fix WIF parsing bug --- .../jsonmodels/SerializedTransaction.scala | 4 +- .../commons/jsonmodels/bitcoind/RpcOpts.scala | 4 +- .../jsonmodels/bitcoind/RpcPsbtResult.scala | 4 +- .../commons/serializers/JsonReaders.scala | 7 + .../commons/serializers/JsonSerializers.scala | 6 + .../bitcoins/rpc/common/MessageRpcTest.scala | 3 +- .../rpc/common/MultiWalletRpcTest.scala | 10 +- .../bitcoins/rpc/common/WalletRpcTest.scala | 25 +- .../rpc/v16/BitcoindV16RpcClientTest.scala | 9 +- .../rpc/v17/BitcoindV17RpcClientTest.scala | 5 +- .../bitcoins/rpc/client/common/Client.scala | 16 +- .../rpc/client/common/MessageRpc.scala | 4 +- .../rpc/client/common/WalletRpc.scala | 6 +- .../bitcoins/core/bloom/BloomFilterTest.scala | 6 +- .../TransactionSignatureCheckerTest.scala | 18 +- .../TransactionSignatureCreatorTest.scala | 10 +- .../core/crypto/WIFEncodingTest.scala | 57 +++- .../org/bitcoins/core/hd/HDPathTest.scala | 18 +- .../blockchain/MerkleBlockTests.scala | 4 +- .../MultiSignatureScriptPubKeyTest.scala | 11 +- .../script/P2PKScriptPubKeyTest.scala | 7 +- .../script/P2SHScriptSignatureTest.scala | 6 +- .../script/P2WPKHWitnessSPKV0Test.scala | 4 +- .../script/P2WSHWitnessSPKV0Test.scala | 20 +- .../org/bitcoins/core/psbt/PSBTUnitTest.scala | 24 +- .../core/util/BitcoinScriptUtilTest.scala | 17 +- .../core/crypto/ECPrivateKeyUtil.scala | 12 +- .../crypto/TransactionSignatureChecker.scala | 18 +- .../core/protocol/ln/node/NodeId.scala | 3 - .../core/protocol/ln/routing/LnRoute.scala | 3 - .../core/protocol/script/ScriptPubKey.scala | 60 ++-- .../protocol/script/ScriptSignature.scala | 31 +- .../core/protocol/script/ScriptWitness.scala | 20 +- .../org/bitcoins/core/psbt/PSBTMap.scala | 8 +- .../org/bitcoins/core/psbt/PSBTRecord.scala | 8 +- .../script/crypto/CryptoInterpreter.scala | 7 +- .../core/util/BitcoinScriptUtil.scala | 14 +- .../bitcoins/core/wallet/signer/Signer.scala | 4 +- .../bitcoins/core/wallet/utxo/InputInfo.scala | 18 +- .../bitcoins/crypto/ECPrivateKeyTest.scala | 2 +- .../org/bitcoins/crypto/ECPublicKeyTest.scala | 95 +++--- .../crypto/BCryptoCryptoRuntime.scala | 60 ++-- .../bitcoins/crypto/BouncyCastleUtil.scala | 18 +- .../crypto/BouncycastleCryptoRuntime.scala | 23 +- .../crypto/LibSecp256k1CryptoRuntime.scala | 64 ++-- .../org/bitcoins/crypto/AdaptorUtil.scala | 15 +- .../org/bitcoins/crypto/CryptoRuntime.scala | 66 ++-- .../org/bitcoins/crypto/CryptoUtil.scala | 17 +- .../scala/org/bitcoins/crypto/DLEQUtil.scala | 6 +- .../bitcoins/crypto/ECAdaptorSignature.scala | 2 +- .../scala/org/bitcoins/crypto/ECKey.scala | 298 ++++++++++++------ .../scala/org/bitcoins/crypto/ECPoint.scala | 37 --- .../scala/org/bitcoins/crypto/SecpPoint.scala | 63 ++++ docs/core/psbts.md | 4 +- .../util/TransactionTestUtil.scala | 6 +- .../org/bitcoins/wallet/WalletUnitTest.scala | 12 +- .../scala/org/bitcoins/wallet/Wallet.scala | 5 +- 57 files changed, 811 insertions(+), 493 deletions(-) delete mode 100644 crypto/src/main/scala/org/bitcoins/crypto/ECPoint.scala create mode 100644 crypto/src/main/scala/org/bitcoins/crypto/SecpPoint.scala diff --git a/app-commons/src/main/scala/org/bitcoins/commons/jsonmodels/SerializedTransaction.scala b/app-commons/src/main/scala/org/bitcoins/commons/jsonmodels/SerializedTransaction.scala index a980764815..8b7aa4320c 100644 --- a/app-commons/src/main/scala/org/bitcoins/commons/jsonmodels/SerializedTransaction.scala +++ b/app-commons/src/main/scala/org/bitcoins/commons/jsonmodels/SerializedTransaction.scala @@ -12,7 +12,7 @@ import org.bitcoins.core.script.constant.{ import org.bitcoins.crypto.{ DoubleSha256DigestBE, ECDigitalSignature, - ECPublicKey + ECPublicKeyBytes } import play.api.libs.json._ import scodec.bits.ByteVector @@ -43,7 +43,7 @@ case class SerializedTransactionWitness( hex: String, scriptType: Option[String], script: Option[Vector[ScriptToken]], - pubKey: Option[ECPublicKey], + pubKey: Option[ECPublicKeyBytes], signature: Option[ECDigitalSignature], stack: Option[Vector[ByteVector]]) diff --git a/app-commons/src/main/scala/org/bitcoins/commons/jsonmodels/bitcoind/RpcOpts.scala b/app-commons/src/main/scala/org/bitcoins/commons/jsonmodels/bitcoind/RpcOpts.scala index 5b5436c38b..42edc950f6 100644 --- a/app-commons/src/main/scala/org/bitcoins/commons/jsonmodels/bitcoind/RpcOpts.scala +++ b/app-commons/src/main/scala/org/bitcoins/commons/jsonmodels/bitcoind/RpcOpts.scala @@ -9,7 +9,7 @@ import org.bitcoins.core.protocol.transaction.{ TransactionOutPoint } import org.bitcoins.commons.serializers.JsonWriters._ -import org.bitcoins.crypto.{DoubleSha256DigestBE, ECPrivateKey} +import org.bitcoins.crypto.{DoubleSha256DigestBE, ECPrivateKeyBytes} import play.api.libs.json.{Json, Writes} import ujson.{Num, Str, Value} @@ -108,7 +108,7 @@ object RpcOpts { timestamp: UInt32, redeemscript: Option[ScriptPubKey] = None, pubkeys: Option[Vector[ScriptPubKey]] = None, - keys: Option[Vector[ECPrivateKey]] = None, + keys: Option[Vector[ECPrivateKeyBytes]] = None, internal: Option[Boolean] = None, watchonly: Option[Boolean] = None, label: Option[String] = None) diff --git a/app-commons/src/main/scala/org/bitcoins/commons/jsonmodels/bitcoind/RpcPsbtResult.scala b/app-commons/src/main/scala/org/bitcoins/commons/jsonmodels/bitcoind/RpcPsbtResult.scala index 045a40e7d1..6cfe78930b 100644 --- a/app-commons/src/main/scala/org/bitcoins/commons/jsonmodels/bitcoind/RpcPsbtResult.scala +++ b/app-commons/src/main/scala/org/bitcoins/commons/jsonmodels/bitcoind/RpcPsbtResult.scala @@ -7,7 +7,7 @@ import org.bitcoins.core.protocol.transaction.Transaction import org.bitcoins.core.psbt.PSBT import org.bitcoins.core.script.ScriptType import org.bitcoins.core.script.crypto.HashType -import org.bitcoins.crypto.{ECDigitalSignature, ECPublicKey} +import org.bitcoins.crypto.{ECDigitalSignature, ECPublicKey, ECPublicKeyBytes} sealed abstract class RpcPsbtResult @@ -89,7 +89,7 @@ final case class AnalyzePsbtInput( ) extends RpcPsbtResult final case class PsbtMissingData( - pubkeys: Option[Vector[ECPublicKey]], + pubkeys: Option[Vector[ECPublicKeyBytes]], signatures: Option[Vector[ECDigitalSignature]], redeemscript: Option[RpcPsbtScript], witnessscript: Option[RpcPsbtScript] diff --git a/app-commons/src/main/scala/org/bitcoins/commons/serializers/JsonReaders.scala b/app-commons/src/main/scala/org/bitcoins/commons/serializers/JsonReaders.scala index f3c841698a..2237919972 100644 --- a/app-commons/src/main/scala/org/bitcoins/commons/serializers/JsonReaders.scala +++ b/app-commons/src/main/scala/org/bitcoins/commons/serializers/JsonReaders.scala @@ -302,6 +302,13 @@ object JsonReaders { SerializerUtil.processJsString[ECPublicKey](ECPublicKey.fromHex)(json) } + implicit object ECPublicKeyBytesReads extends Reads[ECPublicKeyBytes] { + + override def reads(json: JsValue): JsResult[ECPublicKeyBytes] = + SerializerUtil.processJsString[ECPublicKeyBytes]( + ECPublicKeyBytes.fromHex)(json) + } + implicit object SchnorrPublicKeyReads extends Reads[SchnorrPublicKey] { override def reads(json: JsValue): JsResult[SchnorrPublicKey] = diff --git a/app-commons/src/main/scala/org/bitcoins/commons/serializers/JsonSerializers.scala b/app-commons/src/main/scala/org/bitcoins/commons/serializers/JsonSerializers.scala index b609720cc7..d72473c766 100644 --- a/app-commons/src/main/scala/org/bitcoins/commons/serializers/JsonSerializers.scala +++ b/app-commons/src/main/scala/org/bitcoins/commons/serializers/JsonSerializers.scala @@ -68,6 +68,9 @@ object JsonSerializers { implicit val sha256Hash160DigestReads: Reads[Sha256Hash160Digest] = Sha256Hash160DigestReads implicit val eCPublicKeyReads: Reads[ECPublicKey] = ECPublicKeyReads + + implicit val eCPublicKeyBytesReads: Reads[ECPublicKeyBytes] = + ECPublicKeyBytesReads implicit val p2PKHAddressReads: Reads[P2PKHAddress] = P2PKHAddressReads implicit val p2SHAddressReads: Reads[P2SHAddress] = P2SHAddressReads @@ -631,6 +634,9 @@ object JsonSerializers { implicit val ecPublicKeyWrites: Writes[ECPublicKey] = Writes[ECPublicKey](pubKey => JsString(pubKey.hex)) + implicit val ecPublicKeyBytesWrites: Writes[ECPublicKeyBytes] = + Writes[ECPublicKeyBytes](pubKey => JsString(pubKey.hex)) + implicit val scriptTokenWrites: Writes[ScriptToken] = Writes[ScriptToken](token => JsString(tokenToString(token))) diff --git a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/MessageRpcTest.scala b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/MessageRpcTest.scala index 922a9a7957..f904cb52aa 100644 --- a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/MessageRpcTest.scala +++ b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/MessageRpcTest.scala @@ -37,7 +37,8 @@ class MessageRpcTest extends BitcoindRpcTest { for { client <- clientF - signature <- client.signMessageWithPrivKey(privKey, message) + signature <- client.signMessageWithPrivKey(privKey.toPrivateKeyBytes(), + message) validity <- client.verifyMessage(address, signature, message) } yield assert(validity) } diff --git a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/MultiWalletRpcTest.scala b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/MultiWalletRpcTest.scala index c303e5d447..54402588f8 100644 --- a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/MultiWalletRpcTest.scala +++ b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/MultiWalletRpcTest.scala @@ -287,7 +287,7 @@ class MultiWalletRpcTest extends BitcoindFixturesCachedPairV19 { val address = P2PKHAddress(publicKey, networkParam) for { - _ <- client.importPrivKey(ecPrivateKey, + _ <- client.importPrivKey(ecPrivateKey.toPrivateKeyBytes(), rescan = false, walletNameOpt = Some(walletName)) key <- client.dumpPrivKey(address, Some(walletName)) @@ -297,11 +297,15 @@ class MultiWalletRpcTest extends BitcoindFixturesCachedPairV19 { client.getDaemon.datadir.getAbsolutePath + "/wallet_dump.dat", Some(walletName)) } yield { - assert(key == ecPrivateKey) + assert(key.toPrivateKey == ecPrivateKey) val reader = new Scanner(result.filename) var found = false while (reader.hasNext) { - if (reader.next == ECPrivateKeyUtil.toWIF(ecPrivateKey, networkParam)) { + if ( + reader.next == ECPrivateKeyUtil.toWIF( + ecPrivateKey.toPrivateKeyBytes(), + networkParam) + ) { found = true } } diff --git a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/WalletRpcTest.scala b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/WalletRpcTest.scala index ab8a54906a..423692157a 100644 --- a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/WalletRpcTest.scala +++ b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/WalletRpcTest.scala @@ -399,18 +399,23 @@ class WalletRpcTest extends BitcoindFixturesCachedPairV21 { val address = P2PKHAddress(publicKey, networkParam) for { - _ <- client.importPrivKey(ecPrivateKey, rescan = false) + _ <- client.importPrivKey(ecPrivateKey.toPrivateKeyBytes(), + rescan = false) key <- client.dumpPrivKey(address) result <- client .dumpWallet( client.getDaemon.datadir.getAbsolutePath + "/wallet_dump.dat") } yield { - assert(key == ecPrivateKey) + assert(key.toPrivateKey == ecPrivateKey) val reader = new Scanner(result.filename) var found = false while (reader.hasNext) { - if (reader.next == ECPrivateKeyUtil.toWIF(ecPrivateKey, networkParam)) { + if ( + reader.next == ECPrivateKeyUtil.toWIF( + ecPrivateKey.toPrivateKeyBytes(), + networkParam) + ) { found = true } } @@ -580,13 +585,15 @@ class WalletRpcTest extends BitcoindFixturesCachedPairV21 { BitcoinAddress.fromScriptPubKey(output.scriptPubKey, RegTest)) } yield { val partialSig = BitcoinSigner.signSingle( - ECSignatureParams( - P2WPKHV0InputInfo(outPoint, output.value, privKey.publicKey), - prevTx, - privKey, - HashType.sigHashAll), + ECSignatureParams(P2WPKHV0InputInfo(outPoint, + output.value, + privKey.toPrivateKey.publicKey), + prevTx, + privKey.toPrivateKey, + HashType.sigHashAll), transaction, - isDummySignature = false) + isDummySignature = false + ) signedTx match { case btx: NonWitnessTransaction => diff --git a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v16/BitcoindV16RpcClientTest.scala b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v16/BitcoindV16RpcClientTest.scala index bc97c93004..89d91357f4 100644 --- a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v16/BitcoindV16RpcClientTest.scala +++ b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v16/BitcoindV16RpcClientTest.scala @@ -12,7 +12,7 @@ import org.bitcoins.core.protocol.transaction.{ TransactionInput, TransactionOutPoint } -import org.bitcoins.crypto.{DoubleSha256DigestBE, ECPrivateKey} +import org.bitcoins.crypto.{DoubleSha256DigestBE, ECPrivateKeyBytes} import org.bitcoins.rpc.client.common.BitcoindVersion import org.bitcoins.testkit.rpc.{ BitcoindFixturesCachedPairV16, @@ -85,7 +85,7 @@ class BitcoindV16RpcClientTest extends BitcoindFixturesCachedPairV16 { it should "be able to sign a raw transaction with private keys" in { nodePair: FixtureParam => val client = nodePair.node1 - val privkeys: List[ECPrivateKey] = + val privkeys: List[ECPrivateKeyBytes] = List("cUeKHd5orzT3mz8P9pxyREHfsWtVfgsfDjiZZBcjUBAaGk1BTj7N", "cVKpPfVKSJxKqVpE9awvXNWuLHCa5j5tiE7K6zbUSptFpTEtiFrA") .map(ECPrivateKeyUtil.fromWIFToPrivateKey) @@ -121,7 +121,10 @@ class BitcoindV16RpcClientTest extends BitcoindFixturesCachedPairV16 { for { rawTx <- client.createRawTransaction(inputs, outputs) - signed <- client.signRawTransaction(rawTx, utxoDeps, privkeys.toVector) + signed <- client.signRawTransaction( + rawTx, + utxoDeps, + privkeys.toVector.map(_.toPrivateKey)) } yield assert(signed.complete) } diff --git a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v17/BitcoindV17RpcClientTest.scala b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v17/BitcoindV17RpcClientTest.scala index ceb435036e..bc64c9ec4a 100644 --- a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v17/BitcoindV17RpcClientTest.scala +++ b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/v17/BitcoindV17RpcClientTest.scala @@ -109,7 +109,10 @@ class BitcoindV17RpcClientTest extends BitcoindFixturesCachedPairV17 { for { rawTx <- client.createRawTransaction(inputs, outputs) signed <- - client.signRawTransactionWithKey(rawTx, privkeys.toVector, utxoDeps) + client.signRawTransactionWithKey( + rawTx, + privkeys.toVector.map(_.toPrivateKey), + utxoDeps) } yield assert(signed.complete) } diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/Client.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/Client.scala index 950b89fe0e..6ee423086f 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/Client.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/Client.scala @@ -14,7 +14,7 @@ import org.bitcoins.commons.serializers.JsonSerializers._ import org.bitcoins.core.config._ import org.bitcoins.core.crypto.ECPrivateKeyUtil import org.bitcoins.core.util.StartStopAsync -import org.bitcoins.crypto.ECPrivateKey +import org.bitcoins.crypto.{ECPrivateKey, ECPrivateKeyBytes} import org.bitcoins.rpc.BitcoindException import org.bitcoins.rpc.config.BitcoindAuthCredentials.{ CookieBased, @@ -78,11 +78,23 @@ trait Client implicit object ECPrivateKeyWrites extends Writes[ECPrivateKey] { override def writes(o: ECPrivateKey): JsValue = - JsString(ECPrivateKeyUtil.toWIF(o, network)) + JsString(ECPrivateKeyUtil.toWIF(o.toPrivateKeyBytes(), network)) } implicit val eCPrivateKeyWrites: Writes[ECPrivateKey] = ECPrivateKeyWrites + /** This is here (and not in JsonWrriters) + * so that the implicit network val is accessible + */ + implicit object ECPrivateKeyBytesWrites extends Writes[ECPrivateKeyBytes] { + + override def writes(o: ECPrivateKeyBytes): JsValue = + JsString(ECPrivateKeyUtil.toWIF(o, network)) + } + + implicit val eCPrivateKeyBytesWrites: Writes[ECPrivateKeyBytes] = + ECPrivateKeyBytesWrites + implicit val importMultiAddressWrites: Writes[RpcOpts.ImportMultiAddress] = Json.writes[RpcOpts.ImportMultiAddress] diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/MessageRpc.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/MessageRpc.scala index 7c98f945da..2630f5e23d 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/MessageRpc.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/MessageRpc.scala @@ -2,7 +2,7 @@ package org.bitcoins.rpc.client.common import org.bitcoins.core.crypto.ECPrivateKeyUtil import org.bitcoins.core.protocol.P2PKHAddress -import org.bitcoins.crypto.ECPrivateKey +import org.bitcoins.crypto.ECPrivateKeyBytes import play.api.libs.json.JsString import scala.concurrent.Future @@ -18,7 +18,7 @@ trait MessageRpc { self: Client => } def signMessageWithPrivKey( - key: ECPrivateKey, + key: ECPrivateKeyBytes, message: String): Future[String] = { bitcoindCall[String]( "signmessagewithprivkey", diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/WalletRpc.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/WalletRpc.scala index 0594ca758d..e26454a379 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/WalletRpc.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/WalletRpc.scala @@ -18,7 +18,7 @@ import org.bitcoins.core.script.crypto.HashType import org.bitcoins.crypto.{ DoubleSha256Digest, DoubleSha256DigestBE, - ECPrivateKey, + ECPrivateKeyBytes, ECPublicKey } import org.bitcoins.rpc.client.common.BitcoindVersion._ @@ -41,7 +41,7 @@ trait WalletRpc { self: Client => def dumpPrivKey( address: BitcoinAddress, - walletNameOpt: Option[String] = None): Future[ECPrivateKey] = { + walletNameOpt: Option[String] = None): Future[ECPrivateKeyBytes] = { bitcoindCall[String]("dumpprivkey", List(JsString(address.value)), uriExtensionOpt = walletNameOpt.map(walletExtension)) @@ -197,7 +197,7 @@ trait WalletRpc { self: Client => } def importPrivKey( - key: ECPrivateKey, + key: ECPrivateKeyBytes, account: String = "", rescan: Boolean = true, walletNameOpt: Option[String] = None): Future[Unit] = { diff --git a/core-test/src/test/scala/org/bitcoins/core/bloom/BloomFilterTest.scala b/core-test/src/test/scala/org/bitcoins/core/bloom/BloomFilterTest.scala index c5aa18c345..b6e85f253c 100644 --- a/core-test/src/test/scala/org/bitcoins/core/bloom/BloomFilterTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/bloom/BloomFilterTest.scala @@ -72,7 +72,7 @@ class BloomFilterTest extends BitcoinSUnitTest { "5Kg1gnAjaLfKiwhhPpGS3QfRg2m6awQvaj98JCZBZQ5SuS2F15C") assert( privKey.hex == "f49addfd726a59abde172c86452f5f73038a02f4415878dc14934175e8418aff") - val pubKey = privKey.publicKey + val pubKey = privKey.publicKeyBytes val filter1 = filter.insert(pubKey.bytes) //hex is from bitcoin core filter1.hex must be("0302c12b080000000000000001") @@ -133,7 +133,7 @@ class BloomFilterTest extends BitcoinSUnitTest { val filter6 = BloomFilter(10, 0.000001, UInt32.zero, BloomUpdateAll) //insert the pubkey of spendingTx in the bloom filter - val pubKey = ECPublicKey( + val pubKey = ECPublicKeyBytes( "046d11fee51b0e60666d5049a9101a72741df480b96ee26488a4d3466b95c9a40ac5eeef87e10a5cd336c19a84565f80fa6c547957b7700ff4dfbdefe76036c339") val filter7 = filter6.insert(pubKey.bytes) filter7.isRelevant(creditingTx) must be(true) @@ -204,7 +204,7 @@ class BloomFilterTest extends BitcoinSUnitTest { val block = Block( "0100000075616236cc2126035fadb38deb65b9102cc2c41c09cdf29fc051906800000000fe7d5e12ef0ff901f6050211249919b1c0653771832b3a80c66cea42847f0ae1d4d26e49ffff001d00f0a4410401000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0804ffff001d029105ffffffff0100f2052a010000004341046d8709a041d34357697dfcb30a9d05900a6294078012bf3bb09c6f9b525f1d16d5503d7905db1ada9501446ea00728668fc5719aa80be2fdfc8a858a4dbdd4fbac00000000010000000255605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d28350000000049483045022100aa46504baa86df8a33b1192b1b9367b4d729dc41e389f2c04f3e5c7f0559aae702205e82253a54bf5c4f65b7428551554b2045167d6d206dfe6a2e198127d3f7df1501ffffffff55605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835010000004847304402202329484c35fa9d6bb32a55a70c0982f606ce0e3634b69006138683bcd12cbb6602200c28feb1e2555c3210f1dddb299738b4ff8bbe9667b68cb8764b5ac17b7adf0001ffffffff0200e1f505000000004341046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac00180d8f000000004341044a656f065871a353f216ca26cef8dde2f03e8c16202d2e8ad769f02032cb86a5eb5e56842e92e19141d60a01928f8dd2c875a390f67c1f6c94cfc617c0ea45afac0000000001000000025f9a06d3acdceb56be1bfeaa3e8a25e62d182fa24fefe899d1c17f1dad4c2028000000004847304402205d6058484157235b06028c30736c15613a28bdb768ee628094ca8b0030d4d6eb0220328789c9a2ec27ddaec0ad5ef58efded42e6ea17c2e1ce838f3d6913f5e95db601ffffffff5f9a06d3acdceb56be1bfeaa3e8a25e62d182fa24fefe899d1c17f1dad4c2028010000004a493046022100c45af050d3cea806cedd0ab22520c53ebe63b987b8954146cdca42487b84bdd6022100b9b027716a6b59e640da50a864d6dd8a0ef24c76ce62391fa3eabaf4d2886d2d01ffffffff0200e1f505000000004341046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac00180d8f000000004341046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac000000000100000002e2274e5fea1bf29d963914bd301aa63b64daaf8a3e88f119b5046ca5738a0f6b0000000048473044022016e7a727a061ea2254a6c358376aaa617ac537eb836c77d646ebda4c748aac8b0220192ce28bf9f2c06a6467e6531e27648d2b3e2e2bae85159c9242939840295ba501ffffffffe2274e5fea1bf29d963914bd301aa63b64daaf8a3e88f119b5046ca5738a0f6b010000004a493046022100b7a1a755588d4190118936e15cd217d133b0e4a53c3c15924010d5648d8925c9022100aaef031874db2114f2d869ac2de4ae53908fbfea5b2b1862e181626bb9005c9f01ffffffff0200e1f505000000004341044a656f065871a353f216ca26cef8dde2f03e8c16202d2e8ad769f02032cb86a5eb5e56842e92e19141d60a01928f8dd2c875a390f67c1f6c94cfc617c0ea45afac00180d8f000000004341046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac00000000") val txs = block.transactions - val pubKey = ECPublicKey( + val pubKey = ECPublicKeyBytes( "044a656f065871a353f216ca26cef8dde2f03e8c16202d2e8ad769f02032cb86a5eb5e56842e92e19141d60a01928f8dd2c875a390f67c1f6c94cfc617c0ea45af") val filter = BloomFilter(1, 0.00001, UInt32.zero, BloomUpdateNone).insert(pubKey.bytes) diff --git a/core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCheckerTest.scala b/core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCheckerTest.scala index e602bdf796..9e878cc897 100644 --- a/core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCheckerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCheckerTest.scala @@ -8,7 +8,7 @@ import org.bitcoins.core.protocol.script.{ } import org.bitcoins.core.protocol.transaction._ import org.bitcoins.core.script.constant.ScriptToken -import org.bitcoins.crypto.{ECDigitalSignature, ECPublicKey} +import org.bitcoins.crypto.{ECDigitalSignature, ECPublicKey, ECPublicKeyBytes} import org.bitcoins.testkitcore.util.BitcoinSUnitTest /** Created by chris on 2/29/16. @@ -27,7 +27,7 @@ class TransactionSignatureCheckerTest extends BitcoinSUnitTest { val p2pkOutput: TransactionOutput = TransactionOutput( "00f2052a0100000043410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac") - val p2pkPubKey: ECPublicKey = ECPublicKey( + val p2pkPubKey: ECPublicKeyBytes = ECPublicKeyBytes( "0311db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c") val p2pkSig: ECDigitalSignature = ECDigitalSignature( @@ -55,7 +55,7 @@ class TransactionSignatureCheckerTest extends BitcoinSUnitTest { val p2pkhOutput: TransactionOutput = TransactionOutput( "00000000000000001976a914cd0385f813ec73f8fc340b7069daf566878a0d6b88ac") - val p2pkhPubKey: ECPublicKey = ECPublicKey( + val p2pkhPubKey: ECPublicKeyBytes = ECPublicKeyBytes( "02a01aaa27b468ec3fb2ae0c2a9fa1d5dce9b79b35062178f479156d8daa6c0e50") val p2pkhSig: ECDigitalSignature = ECDigitalSignature( @@ -86,10 +86,10 @@ class TransactionSignatureCheckerTest extends BitcoinSUnitTest { MultiSignatureScriptPubKey( "47522102895a52495c4c370d50e6bef622ff28d87eec2df00c546b8921b6d07e844bfb9c210283fe2cf10b7dba0d635b3e408532183e27cd43adc11e125027107c095a2bfbc552ae") - val p2shMultiPubKey1: ECPublicKey = ECPublicKey( + val p2shMultiPubKey1: ECPublicKeyBytes = ECPublicKeyBytes( "02895a52495c4c370d50e6bef622ff28d87eec2df00c546b8921b6d07e844bfb9c") - val p2shMultiPubKey2: ECPublicKey = ECPublicKey( + val p2shMultiPubKey2: ECPublicKeyBytes = ECPublicKeyBytes( "0283fe2cf10b7dba0d635b3e408532183e27cd43adc11e125027107c095a2bfbc5") val p2shMultiSig1: ECDigitalSignature = ECDigitalSignature( @@ -176,7 +176,7 @@ class TransactionSignatureCheckerTest extends BitcoinSUnitTest { val p2shwpkhRedeemScript: ScriptPubKey = ScriptPubKey("16001422fe81d0b50f7a6407d4280e1603c10f5fc3fac8") - val p2shwpkhPubKey: ECPublicKey = ECPublicKey( + val p2shwpkhPubKey: ECPublicKeyBytes = ECPublicKeyBytes( "029ca7faef43714b34508589f31394e0b0b6ab24dd4e440eaa03e72841d48e50c5") val p2shwpkhSig: ECDigitalSignature = ECDigitalSignature( @@ -211,10 +211,10 @@ class TransactionSignatureCheckerTest extends BitcoinSUnitTest { MultiSignatureScriptPubKey( "6952210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae") - val p2wshPubKey1: ECPublicKey = ECPublicKey( + val p2wshPubKey1: ECPublicKeyBytes = ECPublicKeyBytes( "0375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c") - val p2wshPubKey2: ECPublicKey = ECPublicKey( + val p2wshPubKey2: ECPublicKeyBytes = ECPublicKeyBytes( "03a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff") val p2wshSig1: ECDigitalSignature = ECDigitalSignature( @@ -261,7 +261,7 @@ class TransactionSignatureCheckerTest extends BitcoinSUnitTest { } // Negative Test Cases - val incorrectPubKey: ECPublicKey = ECPublicKey.freshPublicKey + val incorrectPubKey: ECPublicKeyBytes = ECPublicKeyBytes.freshPublicKey val incorrectOutput: EmptyTransactionOutput.type = EmptyTransactionOutput val incorrectTx: Transaction = EmptyTransaction val incorrectInputIndex: UInt32 = UInt32(100) diff --git a/core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorTest.scala b/core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorTest.scala index 06eba876bd..d91d9f7c21 100644 --- a/core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorTest.scala @@ -53,7 +53,7 @@ class TransactionSignatureCreatorTest extends BitcoinSJvmTest { "cTPg4Zc5Jis2EZXy3NXShgbn487GWBTapbU63BerLDZM3w2hQSjC") val txSignature = TransactionSignatureCreator.createSig(txSignatureComponent, - privateKey, + privateKey.toPrivateKey, HashType.sigHashAll) txSignature.r must be(expectedSig.r) txSignature.s must be(expectedSig.s) @@ -81,7 +81,7 @@ class TransactionSignatureCreatorTest extends BitcoinSJvmTest { "cTTh7jNtZhg3vHTjvYK8zcHkLfsMAS8iqL7pfZ6eVAVHHF8fN1qy") val txSignature = TransactionSignatureCreator.createSig(txSignatureComponent, - privateKey, + privateKey.toPrivateKey, HashType.sigHashAll) txSignature.r must be(expectedSig.r) txSignature.s must be(expectedSig.s) @@ -97,8 +97,10 @@ class TransactionSignatureCreatorTest extends BitcoinSJvmTest { val transaction = Transaction(rawTx) val prevTransaction = Transaction( "0100000001e4dbac0d73f4e3a9e99e70596a5f81b35a75f95b0474d051fbfd9dc249a5b67e000000006a4730440220486f112aee12997f6e484754d53d5c2158c18cc6d1d3f13aefcdf0ed19c47b290220136133d934d9e79a57408166c39fbce38e217ea9d417cabc20744134f04f06960121021f8cb5c3d611cf24dd665adff3fd540e4c155a05adaa6b672bfa7897c126d9b6feffffff0293d22a00000000001976a914cd0385f813ec73f8fc340b7069daf566878a0d6b88ac40420f000000000017a91480f7a6c14a8407da3546b4abfc3086876ca9a0668700000000") - val privateKey = ECPrivateKeyUtil.fromWIFToPrivateKey( - "cTTh7jNtZhg3vHTjvYK8zcHkLfsMAS8iqL7pfZ6eVAVHHF8fN1qy") + val privateKey = ECPrivateKeyUtil + .fromWIFToPrivateKey( + "cTTh7jNtZhg3vHTjvYK8zcHkLfsMAS8iqL7pfZ6eVAVHHF8fN1qy") + .toPrivateKey val inputInfo = P2PKHInputInfo(TransactionOutPoint(prevTransaction.txId, UInt32.zero), diff --git a/core-test/src/test/scala/org/bitcoins/core/crypto/WIFEncodingTest.scala b/core-test/src/test/scala/org/bitcoins/core/crypto/WIFEncodingTest.scala index 8a43438af1..33923dc2a8 100644 --- a/core-test/src/test/scala/org/bitcoins/core/crypto/WIFEncodingTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/crypto/WIFEncodingTest.scala @@ -1,8 +1,12 @@ package org.bitcoins.core.crypto import org.bitcoins.core.config.{MainNet, RegTest, SigNet, TestNet3} -import org.bitcoins.crypto.ECPrivateKey -import org.bitcoins.testkitcore.gen.{ChainParamsGenerator, CryptoGenerators} +import org.bitcoins.crypto.{ECPrivateKey, ECPrivateKeyBytes} +import org.bitcoins.testkitcore.gen.{ + ChainParamsGenerator, + CryptoGenerators, + NumberGenerator +} import org.bitcoins.testkitcore.util.BitcoinSUnitTest class WIFEncodingTest extends BitcoinSUnitTest { @@ -15,13 +19,14 @@ class WIFEncodingTest extends BitcoinSUnitTest { } it must "serialize a private key to WIF and then be able to deserialize it" in { - val hex = "2cecbfb72f8d5146d7fe7e5a3f80402c6dd688652c332dff2e44618d2d3372" - val privKey = ECPrivateKey(hex) + val hex = "002cecbfb72f8d5146d7fe7e5a3f80402c6dd688652c332dff2e44618d2d3372" + val privKey = ECPrivateKeyBytes(hex) val wif = ECPrivateKeyUtil.toWIF(privKey, TestNet3) val privKeyFromWIF = ECPrivateKeyUtil.fromWIFToPrivateKey(wif) privKeyFromWIF must be(privKey) - val privKeyDecompressed = ECPrivateKey.fromHex(hex, isCompressed = false) + val privKeyDecompressed = + ECPrivateKey.fromHex(hex).toPrivateKeyBytes(isCompressed = false) val wifDecompressed = ECPrivateKeyUtil.toWIF(privKeyDecompressed, TestNet3) val privKeyDecompressedFromWIF = ECPrivateKeyUtil.fromWIFToPrivateKey(wifDecompressed) @@ -31,7 +36,7 @@ class WIFEncodingTest extends BitcoinSUnitTest { it must "serialize a private key to WIF when the private key is prefixed with 0 bytes" in { val hex = "00fc391adf4d6063a16a2e38b14d2be10133c4dacd4348b49d23ee0ce5ff4f1701" - val privKey = ECPrivateKey(hex) + val privKey = ECPrivateKeyBytes(hex) val wif = ECPrivateKeyUtil.toWIF(privKey, TestNet3) val privKeyFromWIF = ECPrivateKeyUtil.fromWIFToPrivateKey(wif) privKeyFromWIF must be(privKey) @@ -48,24 +53,42 @@ class WIFEncodingTest extends BitcoinSUnitTest { it must "decode a WIF private key corresponding to uncompressed public key" in { val wif = "5Kg1gnAjaLfKiwhhPpGS3QfRg2m6awQvaj98JCZBZQ5SuS2F15C" val privKey = ECPrivateKeyUtil.fromWIFToPrivateKey(wif) - privKey.publicKey.hex must be( + privKey.publicKeyBytes.hex must be( "045b81f0017e2091e2edcd5eecf10d5bdd120a5514cb3ee65b8447ec18bfc4575c6d5bf415e54e03b1067934a0f0ba76b01c6b9ab227142ee1d543764b69d901e0") } it must "have serialization symmetry for WIF format" in { - forAll(CryptoGenerators.privateKey, ChainParamsGenerator.networkParams) { - (privKey, network) => - val wif = ECPrivateKeyUtil.toWIF(privKey, network) - network match { - case MainNet => - assert(ECPrivateKeyUtil.parseNetworkFromWIF(wif).get == network) - case TestNet3 | RegTest | SigNet => - assert(ECPrivateKeyUtil.parseNetworkFromWIF(wif).get == TestNet3) - } - assert(ECPrivateKeyUtil.fromWIFToPrivateKey(wif) == privKey) + forAll(CryptoGenerators.privateKey, + ChainParamsGenerator.networkParams, + NumberGenerator.bool) { (privKey, network, compressed) => + val wif = + ECPrivateKeyUtil.toWIF(privKey.toPrivateKeyBytes(compressed), network) + assert(ECPrivateKeyUtil.isCompressed(wif) == compressed) + network match { + case MainNet => + assert(ECPrivateKeyUtil.parseNetworkFromWIF(wif).get == MainNet) + case TestNet3 | RegTest | SigNet => + assert(ECPrivateKeyUtil.parseNetworkFromWIF(wif).get == TestNet3) + } + assert( + ECPrivateKeyUtil.fromWIFToPrivateKey(wif) == privKey.toPrivateKeyBytes( + compressed)) } } + it must "have serialization symmetry for WIF format when private key ends in 0x01" in { + val privKey = ECPrivateKey( + "710ed6c96012015f02e352cddd6f5a5b32499f2926ac7752b57d93b38be8c701") + val wif = + ECPrivateKeyUtil.toWIF(privKey.toPrivateKeyBytes(isCompressed = false), + TestNet3) + assert(!ECPrivateKeyUtil.isCompressed(wif)) + assert(ECPrivateKeyUtil.parseNetworkFromWIF(wif).get == TestNet3) + assert( + ECPrivateKeyUtil.fromWIFToPrivateKey(wif) == privKey + .toPrivateKeyBytes(isCompressed = false)) + } + it must "fail to parse unknown WIF networks" in { // Litecoin privkey val wif = "6uSDaezGtedUbYk4F9CNVXbDWw9DuEuw7czU596t1CzmeAJ77P8" diff --git a/core-test/src/test/scala/org/bitcoins/core/hd/HDPathTest.scala b/core-test/src/test/scala/org/bitcoins/core/hd/HDPathTest.scala index 137f05a5f3..b73e20d492 100644 --- a/core-test/src/test/scala/org/bitcoins/core/hd/HDPathTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/hd/HDPathTest.scala @@ -387,8 +387,10 @@ class HDPathTest extends BitcoinSUnitTest { val spk = P2WPKHWitnessSPKV0(derivedPub) val address = Bech32Address(spk, MainNet) - val expectedPriv = ECPrivateKeyUtil.fromWIFToPrivateKey( - "KyZpNDKnfs94vbrwhJneDi77V6jF64PWPF8x5cdJb8ifgg2DUc9d") + val expectedPriv = ECPrivateKeyUtil + .fromWIFToPrivateKey( + "KyZpNDKnfs94vbrwhJneDi77V6jF64PWPF8x5cdJb8ifgg2DUc9d") + .toPrivateKey val expectedPub = ECPublicKey( hex"0330d54fd0dd420a6e5f8d3624f5f3482cae350f79d5f0753bf5beef9c2d91af3c") val expectedAddress = @@ -407,8 +409,10 @@ class HDPathTest extends BitcoinSUnitTest { val spk = P2WPKHWitnessSPKV0(derivedPub) val address = Bech32Address(spk, MainNet) - val expectedPriv = ECPrivateKeyUtil.fromWIFToPrivateKey( - "Kxpf5b8p3qX56DKEe5NqWbNUP9MnqoRFzZwHRtsFqhzuvUJsYZCy") + val expectedPriv = ECPrivateKeyUtil + .fromWIFToPrivateKey( + "Kxpf5b8p3qX56DKEe5NqWbNUP9MnqoRFzZwHRtsFqhzuvUJsYZCy") + .toPrivateKey val expectedPub = ECPublicKey( hex"03e775fd51f0dfb8cd865d9ff1cca2a158cf651fe997fdc9fee9c1d3b5e995ea77") val expectedAddress = @@ -427,8 +431,10 @@ class HDPathTest extends BitcoinSUnitTest { val spk = P2WPKHWitnessSPKV0(derivedPub) val address = Bech32Address(spk, MainNet) - val expectedPriv = ECPrivateKeyUtil.fromWIFToPrivateKey( - "KxuoxufJL5csa1Wieb2kp29VNdn92Us8CoaUG3aGtPtcF3AzeXvF") + val expectedPriv = ECPrivateKeyUtil + .fromWIFToPrivateKey( + "KxuoxufJL5csa1Wieb2kp29VNdn92Us8CoaUG3aGtPtcF3AzeXvF") + .toPrivateKey val expectedPub = ECPublicKey( hex"03025324888e429ab8e3dbaf1f7802648b9cd01e9b418485c5fa4c1b9b5700e1a6") val expectedAddress = diff --git a/core-test/src/test/scala/org/bitcoins/core/protocol/blockchain/MerkleBlockTests.scala b/core-test/src/test/scala/org/bitcoins/core/protocol/blockchain/MerkleBlockTests.scala index 3f4a498a09..03726faacd 100644 --- a/core-test/src/test/scala/org/bitcoins/core/protocol/blockchain/MerkleBlockTests.scala +++ b/core-test/src/test/scala/org/bitcoins/core/protocol/blockchain/MerkleBlockTests.scala @@ -4,7 +4,7 @@ import org.bitcoins.core.bloom._ import org.bitcoins.core.number.UInt32 import org.bitcoins.core.protocol.transaction.TransactionOutPoint import org.bitcoins.core.util.BytesUtil -import org.bitcoins.crypto.{DoubleSha256Digest, ECPublicKey} +import org.bitcoins.crypto.{DoubleSha256Digest, ECPublicKeyBytes} import org.bitcoins.testkitcore.util.BitcoinSUnitTest /** Created by chris on 8/9/16. @@ -136,7 +136,7 @@ class MerkleBlockTests extends BitcoinSUnitTest { // Match an output from the second transaction (the pubkey for address 1DZTzaBHUDM7T3QvUKBz4qXMRpkg8jsfB5) // This should not match the third transaction though it spends the output matched // It will match the fourth transaction, which has another pay-to-pubkey output to the same address - val pubKey = ECPublicKey( + val pubKey = ECPublicKeyBytes( "044a656f065871a353f216ca26cef8dde2f03e8c16202d2e8ad769f02032cb86a5eb5e56842e92e19141d60a01928f8dd2c875a390f67c1f6c94cfc617c0ea45af") val filterWithPubKey = filter.insert(pubKey.bytes) diff --git a/core-test/src/test/scala/org/bitcoins/core/protocol/script/MultiSignatureScriptPubKeyTest.scala b/core-test/src/test/scala/org/bitcoins/core/protocol/script/MultiSignatureScriptPubKeyTest.scala index 7bc87c104b..b4e912159c 100644 --- a/core-test/src/test/scala/org/bitcoins/core/protocol/script/MultiSignatureScriptPubKeyTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/protocol/script/MultiSignatureScriptPubKeyTest.scala @@ -1,8 +1,7 @@ package org.bitcoins.core.protocol.script -import org.bitcoins.crypto.ECPublicKey -import org.bitcoins.testkitcore.util.TestUtil -import org.bitcoins.testkitcore.util.BitcoinSUnitTest +import org.bitcoins.crypto.ECPublicKeyBytes +import org.bitcoins.testkitcore.util.{BitcoinSUnitTest, TestUtil} /** Created by chris on 3/8/16. */ @@ -36,11 +35,11 @@ class MultiSignatureScriptPubKeyTest extends BitcoinSUnitTest { multiSigScriptPubKey.publicKeys must be( Seq( - ECPublicKey( + ECPublicKeyBytes( "025878e270211662a27181cf4d6ad4d2cf0e69a98a3815c086f587c7e9388d8718"), - ECPublicKey( + ECPublicKeyBytes( "03fc85980e3fac1f3d8a5c3223c3ef5bffc1bd42d2cc42add8c3899cc66e7f1906"), - ECPublicKey( + ECPublicKeyBytes( "0215b5bd050869166a70a7341b4f216e268b7c6c7504576dcea2cce7d11cc9a35f") )) diff --git a/core-test/src/test/scala/org/bitcoins/core/protocol/script/P2PKScriptPubKeyTest.scala b/core-test/src/test/scala/org/bitcoins/core/protocol/script/P2PKScriptPubKeyTest.scala index d0b9d2c829..fea088177b 100644 --- a/core-test/src/test/scala/org/bitcoins/core/protocol/script/P2PKScriptPubKeyTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/protocol/script/P2PKScriptPubKeyTest.scala @@ -1,8 +1,7 @@ package org.bitcoins.core.protocol.script -import org.bitcoins.crypto.ECPublicKey -import org.bitcoins.testkitcore.util.TestUtil -import org.bitcoins.testkitcore.util.BitcoinSUnitTest +import org.bitcoins.crypto.ECPublicKeyBytes +import org.bitcoins.testkitcore.util.{BitcoinSUnitTest, TestUtil} /** Created by chris on 4/1/16. */ @@ -15,7 +14,7 @@ class P2PKScriptPubKeyTest extends BitcoinSUnitTest { throw new RuntimeException("should have been p2pk script pub key") } - p2pkScriptPubKey.publicKey must be(ECPublicKey( + p2pkScriptPubKey.publicKey must be(ECPublicKeyBytes( "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8")) } } diff --git a/core-test/src/test/scala/org/bitcoins/core/protocol/script/P2SHScriptSignatureTest.scala b/core-test/src/test/scala/org/bitcoins/core/protocol/script/P2SHScriptSignatureTest.scala index 053ad7e22d..91bb834c4c 100644 --- a/core-test/src/test/scala/org/bitcoins/core/protocol/script/P2SHScriptSignatureTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/protocol/script/P2SHScriptSignatureTest.scala @@ -5,7 +5,7 @@ import org.bitcoins.core.script.constant.{ OP_0, ScriptConstant } -import org.bitcoins.crypto.ECPublicKey +import org.bitcoins.crypto.ECPublicKeyBytes import org.bitcoins.testkitcore.util.{BitcoinSJvmTest, TestUtil} /** Created by chris on 3/8/16. @@ -21,9 +21,9 @@ class P2SHScriptSignatureTest extends BitcoinSJvmTest { } p2shScriptSig.publicKeys must be( Seq( - ECPublicKey( + ECPublicKeyBytes( "0369d26ebd086523384a0f89f293d4c327a65fa73332d8efd1097cb35231295b83"), - ECPublicKey( + ECPublicKeyBytes( "02480863e5c4a4e9763f5380c44fcfe6a3b7787397076cf9ea1049303a9d34f721") )) diff --git a/core-test/src/test/scala/org/bitcoins/core/protocol/script/P2WPKHWitnessSPKV0Test.scala b/core-test/src/test/scala/org/bitcoins/core/protocol/script/P2WPKHWitnessSPKV0Test.scala index dec6dee05a..620a9f1bd9 100644 --- a/core-test/src/test/scala/org/bitcoins/core/protocol/script/P2WPKHWitnessSPKV0Test.scala +++ b/core-test/src/test/scala/org/bitcoins/core/protocol/script/P2WPKHWitnessSPKV0Test.scala @@ -1,12 +1,12 @@ package org.bitcoins.core.protocol.script -import org.bitcoins.crypto.ECPrivateKey +import org.bitcoins.crypto.ECPrivateKeyBytes import org.bitcoins.testkitcore.util.BitcoinSUnitTest class P2WPKHWitnessSPKV0Test extends BitcoinSUnitTest { "P2WPKHWitnessSPKV0" must "fail to be created with an uncompressed public key" in { - val uncompressed = ECPrivateKey(false).publicKey + val uncompressed = ECPrivateKeyBytes.freshPrivateKey(false).publicKeyBytes intercept[IllegalArgumentException] { P2WPKHWitnessSPKV0(uncompressed) } diff --git a/core-test/src/test/scala/org/bitcoins/core/protocol/script/P2WSHWitnessSPKV0Test.scala b/core-test/src/test/scala/org/bitcoins/core/protocol/script/P2WSHWitnessSPKV0Test.scala index 51faf4e310..90e15028cc 100644 --- a/core-test/src/test/scala/org/bitcoins/core/protocol/script/P2WSHWitnessSPKV0Test.scala +++ b/core-test/src/test/scala/org/bitcoins/core/protocol/script/P2WSHWitnessSPKV0Test.scala @@ -1,13 +1,23 @@ package org.bitcoins.core.protocol.script -import org.bitcoins.core.script.constant.ScriptNumber -import org.bitcoins.crypto.ECPrivateKey +import org.bitcoins.core.script.constant.{OP_1, ScriptConstant, ScriptNumber} +import org.bitcoins.core.script.crypto.{OP_CHECKMULTISIG, OP_CHECKSIG} +import org.bitcoins.core.util.BitcoinScriptUtil +import org.bitcoins.crypto.ECPrivateKeyBytes import org.bitcoins.testkitcore.util.BitcoinSUnitTest class P2WSHWitnessSPKV0Test extends BitcoinSUnitTest { - val uncompressed = ECPrivateKey(false).publicKey - val p2pk = P2PKScriptPubKey(uncompressed) - val multisig = MultiSignatureScriptPubKey(1, Vector(uncompressed)) + val uncompressed = ECPrivateKeyBytes.freshPrivateKey(false).publicKeyBytes + val pushOps = BitcoinScriptUtil.calculatePushOp(uncompressed.bytes) + val asmP2PK = pushOps ++ Seq(ScriptConstant(uncompressed.bytes), OP_CHECKSIG) + val p2pk = P2PKScriptPubKey(asmP2PK) + + val asmMultisig = + Seq(OP_1) ++ pushOps ++ Seq(ScriptConstant(uncompressed.bytes), + OP_1, + OP_CHECKMULTISIG) + val multisig = MultiSignatureScriptPubKey.fromAsm(asmMultisig) + "P2WPKHWitnessSPKV0" must "fail to be created with an uncompressed public key" in { intercept[IllegalArgumentException] { P2WSHWitnessSPKV0(p2pk) diff --git a/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTUnitTest.scala b/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTUnitTest.scala index efa7eb86b1..caa0ea83cc 100644 --- a/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTUnitTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTUnitTest.scala @@ -339,10 +339,14 @@ class PSBTUnitTest extends BitcoinSUnitTest { assert(unsignedPsbt.nextRole == PSBTRole.SignerPSBTRole) - val privKey0 = ECPrivateKeyUtil.fromWIFToPrivateKey( - "cP53pDbR5WtAD8dYAW9hhTjuvvTVaEiQBdrz9XPrgLBeRFiyCbQr") - val privKey1 = ECPrivateKeyUtil.fromWIFToPrivateKey( - "cR6SXDoyfQrcp4piaiHE97Rsgta9mNhGTen9XeonVgwsh4iSgw6d") + val privKey0 = ECPrivateKeyUtil + .fromWIFToPrivateKey( + "cP53pDbR5WtAD8dYAW9hhTjuvvTVaEiQBdrz9XPrgLBeRFiyCbQr") + .toPrivateKey + val privKey1 = ECPrivateKeyUtil + .fromWIFToPrivateKey( + "cR6SXDoyfQrcp4piaiHE97Rsgta9mNhGTen9XeonVgwsh4iSgw6d") + .toPrivateKey // BCrypto does not use low r signing val (expectedPsbt0, expectedPsbt1) = CryptoUtil.cryptoContext match { @@ -361,11 +365,15 @@ class PSBTUnitTest extends BitcoinSUnitTest { (psbt0, psbt1) } - val privKey2 = ECPrivateKeyUtil.fromWIFToPrivateKey( - "cT7J9YpCwY3AVRFSjN6ukeEeWY6mhpbJPxRaDaP5QTdygQRxP9Au") + val privKey2 = ECPrivateKeyUtil + .fromWIFToPrivateKey( + "cT7J9YpCwY3AVRFSjN6ukeEeWY6mhpbJPxRaDaP5QTdygQRxP9Au") + .toPrivateKey - val privKey3 = ECPrivateKeyUtil.fromWIFToPrivateKey( - "cNBc3SWUip9PPm1GjRoLEJT6T41iNzCYtD7qro84FMnM5zEqeJsE") + val privKey3 = ECPrivateKeyUtil + .fromWIFToPrivateKey( + "cNBc3SWUip9PPm1GjRoLEJT6T41iNzCYtD7qro84FMnM5zEqeJsE") + .toPrivateKey val expectedPubKeyHashes = Vector(privKey0, privKey1, privKey2, privKey3).map { key => diff --git a/core-test/src/test/scala/org/bitcoins/core/util/BitcoinScriptUtilTest.scala b/core-test/src/test/scala/org/bitcoins/core/util/BitcoinScriptUtilTest.scala index 61d252c41f..9c1ec7b2fc 100644 --- a/core-test/src/test/scala/org/bitcoins/core/util/BitcoinScriptUtilTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/util/BitcoinScriptUtilTest.scala @@ -8,10 +8,9 @@ import org.bitcoins.core.script.flag.ScriptVerifyWitnessPubKeyType import org.bitcoins.core.script.locktime.OP_CHECKLOCKTIMEVERIFY import org.bitcoins.core.script.reserved.{OP_NOP, OP_RESERVED} import org.bitcoins.core.script.result.ScriptErrorWitnessPubKeyType -import org.bitcoins.crypto.{ECPrivateKey, ECPublicKey} +import org.bitcoins.crypto.{ECPrivateKeyBytes, ECPublicKeyBytes} import org.bitcoins.testkitcore.gen.ScriptGenerators -import org.bitcoins.testkitcore.util.TestUtil -import org.bitcoins.testkitcore.util.BitcoinSUnitTest +import org.bitcoins.testkitcore.util.{BitcoinSUnitTest, TestUtil} import scodec.bits.ByteVector /** Created by chris on 3/2/16. @@ -36,7 +35,7 @@ class BitcoinScriptUtilTest extends BitcoinSUnitTest { // https://en.bitcoin.it/wiki/Genesis_block it must "filter out non-data from the genesis coinbase transaction" in { - val genesisPK = ECPublicKey.fromHex( + val genesisPK = ECPublicKeyBytes.fromHex( "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f") val output = TransactionOutput.fromHex( "00f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac") @@ -272,7 +271,7 @@ class BitcoinScriptUtilTest extends BitcoinSUnitTest { it must "check a public key's encoding" in { //pubkeys must be compressed or uncompressed or else that are not validly encoded - val key = ECPublicKey("00") + val key = ECPublicKeyBytes("00") val program = TestUtil.testProgramExecutionInProgress BitcoinScriptUtil.checkPubKeyEncoding(key, program) must be(false) } @@ -300,16 +299,16 @@ class BitcoinScriptUtilTest extends BitcoinSUnitTest { } it must "determine if a segwit pubkey is compressed" in { - val key = ECPrivateKey(false) - val pubKey = key.publicKey + val key = ECPrivateKeyBytes.freshPrivateKey(false) + val pubKey = key.publicKeyBytes val flags = Seq(ScriptVerifyWitnessPubKeyType) BitcoinScriptUtil.isValidPubKeyEncoding(pubKey, SigVersionWitnessV0, flags) must be( Some(ScriptErrorWitnessPubKeyType)) - val key2 = ECPrivateKey(false) - val pubKey2 = key2.publicKey + val key2 = ECPrivateKeyBytes.freshPrivateKey(false) + val pubKey2 = key2.publicKeyBytes BitcoinScriptUtil.isValidPubKeyEncoding(pubKey2, SigVersionBase, flags) must be(None) diff --git a/core/src/main/scala/org/bitcoins/core/crypto/ECPrivateKeyUtil.scala b/core/src/main/scala/org/bitcoins/core/crypto/ECPrivateKeyUtil.scala index ce7e139fec..3a53c9c4fb 100644 --- a/core/src/main/scala/org/bitcoins/core/crypto/ECPrivateKeyUtil.scala +++ b/core/src/main/scala/org/bitcoins/core/crypto/ECPrivateKeyUtil.scala @@ -2,17 +2,17 @@ package org.bitcoins.core.crypto import org.bitcoins.core.config.{NetworkParameters, Networks} import org.bitcoins.core.util.{Base58, BytesUtil} -import org.bitcoins.crypto.{CryptoUtil, ECPrivateKey} +import org.bitcoins.crypto.{CryptoUtil, ECPrivateKeyBytes} import scodec.bits.ByteVector import scala.util.{Failure, Success, Try} object ECPrivateKeyUtil { - /** Converts a [[org.bitcoins.crypto.ECPrivateKey ECPrivateKey]] to + /** Converts a [[org.bitcoins.crypto.ECPrivateKeyBytes ECPrivateKey]] to * [[https://en.bitcoin.it/wiki/Wallet_import_format WIF]] */ - def toWIF(privKey: ECPrivateKey, network: NetworkParameters): String = { + def toWIF(privKey: ECPrivateKeyBytes, network: NetworkParameters): String = { val networkByte = network.privateKey //append 1 byte to the end of the priv key byte representation if we need a compressed pub key val fullBytes = @@ -31,10 +31,10 @@ object ECPrivateKeyUtil { * @param WIF Wallet Import Format. Encoded in Base58 * @return */ - def fromWIFToPrivateKey(WIF: String): ECPrivateKey = { + def fromWIFToPrivateKey(WIF: String): ECPrivateKeyBytes = { val isCompressed = ECPrivateKeyUtil.isCompressed(WIF) val privateKeyBytes = trimFunction(WIF) - ECPrivateKey.fromBytes(privateKeyBytes, isCompressed) + ECPrivateKeyBytes(privateKeyBytes, isCompressed) } /** Takes in WIF private key as a sequence of bytes and determines if it corresponds to a compressed public key. @@ -49,7 +49,7 @@ object ECPrivateKeyUtil { val validCompressedBytesInHex: Seq[String] = validCompressedBytes.map(b => BytesUtil.encodeHex(b)) val firstByteHex = BytesUtil.encodeHex(bytes.head) - if (validCompressedBytesInHex.contains(firstByteHex)) + if (validCompressedBytesInHex.contains(firstByteHex) && bytes.length == 38) bytes(bytes.length - 5) == 0x01.toByte else false } diff --git a/core/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureChecker.scala b/core/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureChecker.scala index 53769e7b84..fcd5e4d424 100644 --- a/core/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureChecker.scala +++ b/core/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureChecker.scala @@ -9,7 +9,12 @@ import org.bitcoins.core.script.crypto._ import org.bitcoins.core.script.flag.{ScriptFlag, ScriptFlagUtil} import org.bitcoins.core.script.result.ScriptErrorWitnessPubKeyType import org.bitcoins.core.util.BitcoinScriptUtil -import org.bitcoins.crypto.{DERSignatureUtil, ECDigitalSignature, ECPublicKey} +import org.bitcoins.crypto.{ + DERSignatureUtil, + ECDigitalSignature, + ECPublicKey, + ECPublicKeyBytes +} import scodec.bits.ByteVector import scala.annotation.tailrec @@ -20,6 +25,13 @@ import scala.annotation.tailrec */ trait TransactionSignatureChecker { + def checkSignature( + txSignatureComponent: TxSigComponent, + pubKeyBytes: ECPublicKeyBytes, + signature: ECDigitalSignature): TransactionSignatureCheckerResult = + checkSignature(txSignatureComponent, + PartialSignature(pubKeyBytes, signature)) + def checkSignature( txSignatureComponent: TxSigComponent, pubKey: ECPublicKey, @@ -58,7 +70,7 @@ trait TransactionSignatureChecker { def checkSignature( txSignatureComponent: TxSigComponent, script: Seq[ScriptToken], - pubKey: ECPublicKey, + pubKey: ECPublicKeyBytes, signature: ECDigitalSignature, flags: Seq[ScriptFlag] = Policy.standardFlags): TransactionSignatureCheckerResult = { @@ -147,7 +159,7 @@ trait TransactionSignatureChecker { txSignatureComponent: TxSigComponent, script: Seq[ScriptToken], sigs: List[ECDigitalSignature], - pubKeys: List[ECPublicKey], + pubKeys: List[ECPublicKeyBytes], flags: Seq[ScriptFlag], requiredSigs: Long): TransactionSignatureCheckerResult = { require(requiredSigs >= 0, diff --git a/core/src/main/scala/org/bitcoins/core/protocol/ln/node/NodeId.scala b/core/src/main/scala/org/bitcoins/core/protocol/ln/node/NodeId.scala index 2b44731efa..58e6a28e73 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/ln/node/NodeId.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/ln/node/NodeId.scala @@ -9,9 +9,6 @@ import scodec.bits.ByteVector * 33 byte compressed secp256k1 public key. */ case class NodeId(pubKey: ECPublicKey) extends NetworkElement { - require( - pubKey.isCompressed, - s"Cannot create a nodeId from a public key that was not compressed ${pubKey.hex}") override def toString: String = pubKey.hex diff --git a/core/src/main/scala/org/bitcoins/core/protocol/ln/routing/LnRoute.scala b/core/src/main/scala/org/bitcoins/core/protocol/ln/routing/LnRoute.scala index cc8dda1792..7eede5bbf5 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/ln/routing/LnRoute.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/ln/routing/LnRoute.scala @@ -25,9 +25,6 @@ case class LnRoute( cltvExpiryDelta: Short) extends NetworkElement { - require(pubkey.isCompressed, - s"Can only use a compressed public key in routing") - override def bytes: ByteVector = { val cltvExpiryDeltaHex = BytesUtil.encodeHex(cltvExpiryDelta) diff --git a/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptPubKey.scala b/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptPubKey.scala index 357b915c2d..34ca0d022f 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptPubKey.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptPubKey.scala @@ -154,11 +154,11 @@ sealed trait MultiSignatureScriptPubKey extends RawScriptPubKey { } /** Returns the public keys encoded into the `scriptPubKey` */ - def publicKeys: Seq[ECPublicKey] = { + def publicKeys: Seq[ECPublicKeyBytes] = { asm .filter(_.isInstanceOf[ScriptConstant]) .slice(1, maxSigs + 1) - .map(key => ECPublicKey(key.hex)) + .map(key => ECPublicKeyBytes(key.bytes)) } override def toString = s"multi($requiredSigs,${publicKeys.mkString(",")})" @@ -343,8 +343,8 @@ object P2SHScriptPubKey extends ScriptFactory[P2SHScriptPubKey] { */ sealed trait P2PKScriptPubKey extends RawScriptPubKey { - def publicKey: ECPublicKey = - ECPublicKey(BitcoinScriptUtil.filterPushOps(asm).head.bytes) + def publicKey: ECPublicKeyBytes = + ECPublicKeyBytes(BitcoinScriptUtil.filterPushOps(asm).head.bytes) override def toString = s"pk(${publicKey.hex})" @@ -382,6 +382,21 @@ object P2PKScriptPubKey extends ScriptFactory[P2PKScriptPubKey] { case _ => false } + /** Builds a P2PKScriptPubKey from a specific branch of a P2PKWithTimeout. + * Useful for when decomposing a script into spending branches. + */ + private[core] def fromP2PKWithTimeout( + p2pkWithTimeout: P2PKWithTimeoutScriptPubKey, + timeoutBranch: Boolean): P2PKScriptPubKey = { + val pubKeyBytes = + if (timeoutBranch) p2pkWithTimeout.timeoutPubKey + else p2pkWithTimeout.pubKey + val pushOps = BitcoinScriptUtil.calculatePushOp(pubKeyBytes.bytes) + val asm = pushOps ++ Seq(ScriptConstant(pubKeyBytes.bytes), OP_CHECKSIG) + + P2PKScriptPubKey(asm) + } + } sealed trait LockTimeScriptPubKey extends RawScriptPubKey { @@ -896,8 +911,8 @@ object NonStandardNotIfConditionalScriptPubKey */ sealed trait P2PKWithTimeoutScriptPubKey extends RawScriptPubKey { - lazy val pubKey: ECPublicKey = - ECPublicKey.fromBytes(asm(2).bytes) + lazy val pubKey: ECPublicKeyBytes = + ECPublicKeyBytes(asm(2).bytes) private lazy val smallCSVOpt: Option[Long] = { asm(4) match { @@ -912,10 +927,10 @@ sealed trait P2PKWithTimeoutScriptPubKey extends RawScriptPubKey { .getOrElse(ScriptNumber(asm(5).bytes)) } - lazy val timeoutPubKey: ECPublicKey = { + lazy val timeoutPubKey: ECPublicKeyBytes = { smallCSVOpt match { - case Some(_) => ECPublicKey.fromBytes(asm(8).bytes) - case None => ECPublicKey.fromBytes(asm(9).bytes) + case Some(_) => ECPublicKeyBytes(asm(8).bytes) + case None => ECPublicKeyBytes(asm(9).bytes) } } } @@ -968,24 +983,28 @@ object P2PKWithTimeoutScriptPubKey } if (asm.length == requiredSize) { - val pubKey = ECPublicKey.fromBytes(asm(2).bytes) + val pubKeyTry = Try(ECPublicKey.fromBytes(asm(2).bytes)) val lockTimeTry = smallCSVOpt match { case Some(num) => Success(ScriptNumber(num)) case None => Try(ScriptNumber.fromBytes(asm(5).bytes)) } - val timeoutPubKey = smallCSVOpt match { - case Some(_) => ECPublicKey.fromBytes(asm(8).bytes) - case None => ECPublicKey.fromBytes(asm(9).bytes) + val timeoutPubKeyTry = Try { + smallCSVOpt match { + case Some(_) => ECPublicKey.fromBytes(asm(8).bytes) + case None => ECPublicKey.fromBytes(asm(9).bytes) + } } - lockTimeTry match { - case Success(lockTime) => + (pubKeyTry, lockTimeTry, timeoutPubKeyTry) match { + case (Failure(_), _, _) => false + case (_, Failure(_), _) => false + case (_, _, Failure(_)) => false + case (Success(pubKey), Success(lockTime), Success(timeoutPubKey)) => asm == P2PKWithTimeoutScriptPubKey(pubKey, lockTime, timeoutPubKey).asm - case Failure(_) => false } } else { false @@ -1258,12 +1277,17 @@ object P2WPKHWitnessSPKV0 extends ScriptFactory[P2WPKHWitnessSPKV0] { fromAsm(Seq(OP_0) ++ pushop ++ Seq(ScriptConstant(hash.bytes))) } - /** Creates a P2WPKH witness script pubkey */ - def apply(pubKey: ECPublicKey): P2WPKHWitnessSPKV0 = { + /** Builds a P2WPKH SPK from raw ECPublicKeyBytes (unsafe). */ + private[core] def apply(pubKey: ECPublicKeyBytes): P2WPKHWitnessSPKV0 = { //https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki#restrictions-on-public-key-type require( pubKey.isCompressed, s"Public key must be compressed to be used in a segwit script, see BIP143") + P2WPKHWitnessSPKV0(pubKey.toPublicKey) + } + + /** Creates a P2WPKH witness script pubkey */ + def apply(pubKey: ECPublicKey): P2WPKHWitnessSPKV0 = { val hash = CryptoUtil.sha256Hash160(pubKey.bytes) val pushop = BitcoinScriptUtil.calculatePushOp(hash.bytes) fromAsm(Seq(OP_0) ++ pushop ++ Seq(ScriptConstant(hash.bytes))) diff --git a/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptSignature.scala b/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptSignature.scala index d9cd244c60..5c03e49076 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptSignature.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptSignature.scala @@ -4,7 +4,7 @@ import org.bitcoins.core.script.constant._ import org.bitcoins.core.serializers.script.ScriptParser import org.bitcoins.core.util._ import org.bitcoins.core.wallet.utxo.ConditionalPath -import org.bitcoins.crypto.{ECDigitalSignature, ECPublicKey} +import org.bitcoins.crypto.{ECDigitalSignature, ECPublicKey, ECPublicKeyBytes} import scala.annotation.tailrec import scala.util.Try @@ -79,7 +79,7 @@ sealed trait P2PKHScriptSignature extends ScriptSignature { def signature: ECDigitalSignature = signatures.head /** Gives us the public key inside of a p2pkh script signature */ - def publicKey: ECPublicKey = ECPublicKey(asm.last.bytes) + def publicKey: ECPublicKeyBytes = ECPublicKeyBytes(asm.last.bytes) override def signatures: Seq[ECDigitalSignature] = { Seq(ECDigitalSignature(asm(1).hex)) @@ -103,20 +103,27 @@ object P2PKHScriptSignature extends ScriptFactory[P2PKHScriptSignature] { ) } + /** Builds a P2PKH ScriptSig from a signature and raw ECPublicKeyBytes (may be invalid). */ + private[core] def apply( + signature: ECDigitalSignature, + pubKeyBytes: ECPublicKeyBytes): P2PKHScriptSignature = { + val signatureBytesToPushOntoStack = + BitcoinScriptUtil.calculatePushOp(signature.bytes) + val pubKeyBytesToPushOntoStack = + BitcoinScriptUtil.calculatePushOp(pubKeyBytes.bytes) + val asm: Seq[ScriptToken] = + signatureBytesToPushOntoStack ++ Seq(ScriptConstant(signature.bytes)) ++ + pubKeyBytesToPushOntoStack ++ Seq(ScriptConstant(pubKeyBytes.bytes)) + fromAsm(asm) + } + /** Builds a script signature from a digital signature and a public key * this is a pay to public key hash script sig */ def apply( signature: ECDigitalSignature, pubKey: ECPublicKey): P2PKHScriptSignature = { - val signatureBytesToPushOntoStack = - BitcoinScriptUtil.calculatePushOp(signature.bytes) - val pubKeyBytesToPushOntoStack = - BitcoinScriptUtil.calculatePushOp(pubKey.bytes) - val asm: Seq[ScriptToken] = - signatureBytesToPushOntoStack ++ Seq(ScriptConstant(signature.hex)) ++ - pubKeyBytesToPushOntoStack ++ Seq(ScriptConstant(pubKey.hex)) - fromAsm(asm) + P2PKHScriptSignature(signature, pubKey.toPublicKeyBytes()) } /** Determines if the given asm matches a [[P2PKHScriptSignature]] */ @@ -180,11 +187,11 @@ sealed trait P2SHScriptSignature extends ScriptSignature { } /** Returns the public keys for the p2sh scriptSignature */ - def publicKeys: Seq[ECPublicKey] = { + def publicKeys: Seq[ECPublicKeyBytes] = { val pubKeys: Seq[ScriptToken] = redeemScript.asm .filter(_.isInstanceOf[ScriptConstant]) .filterNot(_.isInstanceOf[ScriptNumberOperation]) - pubKeys.map(k => ECPublicKey(k.hex)) + pubKeys.map(k => ECPublicKeyBytes(k.bytes)) } /** The digital signatures inside of the scriptSig */ diff --git a/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptWitness.scala b/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptWitness.scala index c22420f198..3973b7b597 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptWitness.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptWitness.scala @@ -10,6 +10,7 @@ import org.bitcoins.core.util.{BitcoinScriptUtil, BytesUtil} import org.bitcoins.crypto.{ ECDigitalSignature, ECPublicKey, + ECPublicKeyBytes, EmptyDigitalSignature, Factory, NetworkElement @@ -43,7 +44,7 @@ sealed abstract class ScriptWitnessV0 extends ScriptWitness { * Format: */ sealed abstract class P2WPKHWitnessV0 extends ScriptWitnessV0 { - def pubKey: ECPublicKey = ECPublicKey(stack.head) + def pubKey: ECPublicKeyBytes = ECPublicKeyBytes(stack.head) def signature: ECDigitalSignature = stack(1) match { @@ -63,14 +64,25 @@ object P2WPKHWitnessV0 { private def apply(stack: Seq[ByteVector]): P2WPKHWitnessV0 = P2WPKHWitnessV0Impl(stack) + private[bitcoins] def apply( + pubKeyBytes: ECPublicKeyBytes): P2WPKHWitnessV0 = { + P2WPKHWitnessV0(pubKeyBytes, EmptyDigitalSignature) + } + def apply(pubKey: ECPublicKey): P2WPKHWitnessV0 = { P2WPKHWitnessV0(pubKey, EmptyDigitalSignature) } + private[bitcoins] def apply( + publicKeyBytes: ECPublicKeyBytes, + signature: ECDigitalSignature): P2WPKHWitnessV0 = { + P2WPKHWitnessV0(Seq(publicKeyBytes.bytes, signature.bytes)) + } + def apply( publicKey: ECPublicKey, signature: ECDigitalSignature): P2WPKHWitnessV0 = { - P2WPKHWitnessV0(Seq(publicKey.bytes, signature.bytes)) + P2WPKHWitnessV0(publicKey.toPublicKeyBytes(), signature) } def fromP2PKHScriptSig(scriptSig: ScriptSignature): P2WPKHWitnessV0 = @@ -176,11 +188,11 @@ object ScriptWitness extends Factory[ScriptWitness] { if (stack.isEmpty) { EmptyScriptWitness } else if (isPubKey && stack.size == 2) { - val pubKey = ECPublicKey(stack.head) + val pubKey = ECPublicKeyBytes(stack.head) val sig = ECDigitalSignature(stack(1)) P2WPKHWitnessV0(pubKey, sig) } else if (isPubKey && stack.size == 1) { - val pubKey = ECPublicKey(stack.head) + val pubKey = ECPublicKeyBytes(stack.head) P2WPKHWitnessV0(pubKey) } else { //wont match a Vector if I don't convert to list diff --git a/core/src/main/scala/org/bitcoins/core/psbt/PSBTMap.scala b/core/src/main/scala/org/bitcoins/core/psbt/PSBTMap.scala index 0e1e86184f..aa4db058bd 100644 --- a/core/src/main/scala/org/bitcoins/core/psbt/PSBTMap.scala +++ b/core/src/main/scala/org/bitcoins/core/psbt/PSBTMap.scala @@ -503,10 +503,14 @@ case class InputPSBTMap(elements: Vector[InputPSBTRecord]) addLeaves(conditional.trueSPK, path :+ true) addLeaves(conditional.falseSPK, path :+ false) case p2pkWithTimeout: P2PKWithTimeoutScriptPubKey => - addLeaves(P2PKScriptPubKey(p2pkWithTimeout.pubKey), path :+ true) + addLeaves(P2PKScriptPubKey.fromP2PKWithTimeout(p2pkWithTimeout, + timeoutBranch = + false), + path :+ true) val timeoutSPK = CLTVScriptPubKey( p2pkWithTimeout.lockTime, - P2PKScriptPubKey(p2pkWithTimeout.timeoutPubKey)) + P2PKScriptPubKey.fromP2PKWithTimeout(p2pkWithTimeout, + timeoutBranch = true)) addLeaves(timeoutSPK, path :+ false) case cltv: CLTVScriptPubKey => addLeaves(cltv.nestedScriptPubKey, path) diff --git a/core/src/main/scala/org/bitcoins/core/psbt/PSBTRecord.scala b/core/src/main/scala/org/bitcoins/core/psbt/PSBTRecord.scala index 81598aaa79..1d8e00b4e7 100644 --- a/core/src/main/scala/org/bitcoins/core/psbt/PSBTRecord.scala +++ b/core/src/main/scala/org/bitcoins/core/psbt/PSBTRecord.scala @@ -157,7 +157,7 @@ object InputPSBTRecord extends Factory[InputPSBTRecord] { } case class PartialSignature( - pubKey: ECPublicKey, + pubKey: ECPublicKeyBytes, signature: ECDigitalSignature) extends InputPSBTRecord { require(pubKey.byteSize == 33, @@ -172,6 +172,12 @@ object InputPSBTRecord extends Factory[InputPSBTRecord] { object PartialSignature extends Factory[PartialSignature] { + def apply( + pubKey: ECPublicKey, + signature: ECDigitalSignature): PartialSignature = { + PartialSignature(pubKey.toPublicKeyBytes(), signature) + } + def dummyPartialSig( pubKey: ECPublicKey = ECPublicKey.freshPublicKey): PartialSignature = { PartialSignature(pubKey, DummyECDigitalSignature) diff --git a/core/src/main/scala/org/bitcoins/core/script/crypto/CryptoInterpreter.scala b/core/src/main/scala/org/bitcoins/core/script/crypto/CryptoInterpreter.scala index 47a93d6695..e4dac75054 100644 --- a/core/src/main/scala/org/bitcoins/core/script/crypto/CryptoInterpreter.scala +++ b/core/src/main/scala/org/bitcoins/core/script/crypto/CryptoInterpreter.scala @@ -14,7 +14,7 @@ import org.bitcoins.core.util.BitcoinScriptUtil import org.bitcoins.crypto.{ CryptoUtil, ECDigitalSignature, - ECPublicKey, + ECPublicKeyBytes, HashDigest } import scodec.bits.ByteVector @@ -77,7 +77,7 @@ sealed abstract class CryptoInterpreter { if (program.stack.size < 2) { program.failExecution(ScriptErrorInvalidStackOperation) } else { - val pubKey = ECPublicKey(program.stack.head.bytes) + val pubKey = ECPublicKeyBytes(program.stack.head.bytes) val signature = ECDigitalSignature(program.stack.tail.head.bytes) val flags = program.flags val restOfStack = program.stack.tail.tail @@ -182,7 +182,8 @@ sealed abstract class CryptoInterpreter { program.stack.tail .slice(nPossibleSignatures.toInt, program.stack.tail.size)) - val pubKeys = pubKeysScriptTokens.map(key => ECPublicKey(key.bytes)) + val pubKeys = + pubKeysScriptTokens.map(key => ECPublicKeyBytes(key.bytes)) //+1 is for the fact that we have the # of sigs + the script token indicating the # of sigs val signaturesScriptTokens = program.stack.tail.slice( diff --git a/core/src/main/scala/org/bitcoins/core/util/BitcoinScriptUtil.scala b/core/src/main/scala/org/bitcoins/core/util/BitcoinScriptUtil.scala index 40777feda0..3d8822247f 100644 --- a/core/src/main/scala/org/bitcoins/core/util/BitcoinScriptUtil.scala +++ b/core/src/main/scala/org/bitcoins/core/util/BitcoinScriptUtil.scala @@ -35,7 +35,7 @@ import org.bitcoins.core.script.{ } import org.bitcoins.core.serializers.script.ScriptParser import org.bitcoins.core.wallet.utxo._ -import org.bitcoins.crypto.{ECDigitalSignature, ECPublicKey} +import org.bitcoins.crypto.{ECDigitalSignature, ECPublicKeyBytes} import scodec.bits.ByteVector import scala.annotation.tailrec @@ -308,11 +308,13 @@ trait BitcoinScriptUtil { * [[https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp#L202]]. */ def checkPubKeyEncoding( - key: ECPublicKey, + key: ECPublicKeyBytes, program: ExecutionInProgressScriptProgram): Boolean = checkPubKeyEncoding(key, program.flags) - def checkPubKeyEncoding(key: ECPublicKey, flags: Seq[ScriptFlag]): Boolean = { + def checkPubKeyEncoding( + key: ECPublicKeyBytes, + flags: Seq[ScriptFlag]): Boolean = { if ( ScriptFlagUtil.requireStrictEncoding(flags) && !isCompressedOrUncompressedPubKey(key) @@ -325,7 +327,7 @@ trait BitcoinScriptUtil { * @param key the public key that is being checked * @return true if the key is compressed/uncompressed otherwise false */ - def isCompressedOrUncompressedPubKey(key: ECPublicKey): Boolean = { + def isCompressedOrUncompressedPubKey(key: ECPublicKeyBytes): Boolean = { if (key.bytes.size < 33) { // Non-canonical public key: too short return false @@ -345,7 +347,7 @@ trait BitcoinScriptUtil { } /** Checks if the given public key is a compressed public key */ - def isCompressedPubKey(key: ECPublicKey): Boolean = { + def isCompressedPubKey(key: ECPublicKeyBytes): Boolean = { (key.bytes.size == 33) && (key.bytes.head == 0x02 || key.bytes.head == 0x03) } @@ -361,7 +363,7 @@ trait BitcoinScriptUtil { * [[https://github.com/bitcoin/bitcoin/blob/528472111b4965b1a99c4bcf08ac5ec93d87f10f/src/script/interpreter.cpp#L214-L223]] */ def isValidPubKeyEncoding( - pubKey: ECPublicKey, + pubKey: ECPublicKeyBytes, sigVersion: SignatureVersion, flags: Seq[ScriptFlag]): Option[ScriptError] = { if ( diff --git a/core/src/main/scala/org/bitcoins/core/wallet/signer/Signer.scala b/core/src/main/scala/org/bitcoins/core/wallet/signer/Signer.scala index 4fa08cc433..1b79de695d 100644 --- a/core/src/main/scala/org/bitcoins/core/wallet/signer/Signer.scala +++ b/core/src/main/scala/org/bitcoins/core/wallet/signer/Signer.scala @@ -256,7 +256,7 @@ object BitcoinSigner extends SignerUtils { psbt .inputMaps(inputIndex) .partialSignatures - .exists(_.pubKey == signer.publicKey) + .exists(_.pubKey.toPublicKey == signer.publicKey) ) { throw new IllegalArgumentException( "Input has already been signed with this key") @@ -331,7 +331,7 @@ sealed abstract class RawSingleKeyBitcoinSigner[-InputType <: RawInputInfo] signSingle(spendingInfo.toSingle(0), unsignedTx, isDummySignature) val scriptSig = - keyAndSigToScriptSig(partialSignature.pubKey, + keyAndSigToScriptSig(partialSignature.pubKey.toPublicKey, partialSignature.signature, spendingInfoToSatisfy) diff --git a/core/src/main/scala/org/bitcoins/core/wallet/utxo/InputInfo.scala b/core/src/main/scala/org/bitcoins/core/wallet/utxo/InputInfo.scala index 7bcb3631e1..907c5f2929 100644 --- a/core/src/main/scala/org/bitcoins/core/wallet/utxo/InputInfo.scala +++ b/core/src/main/scala/org/bitcoins/core/wallet/utxo/InputInfo.scala @@ -10,6 +10,7 @@ import org.bitcoins.core.script.crypto.HashType import org.bitcoins.core.util.{BitcoinScriptUtil, BytesUtil} import org.bitcoins.crypto.{ ECPublicKey, + ECPublicKeyBytes, LowRDummyECDigitalSignature, NetworkElement, Sign @@ -352,8 +353,10 @@ object RawInputInfo { case p2pk: P2PKScriptPubKey => P2PKInputInfo(outPoint, amount, p2pk) case p2pkh: P2PKHScriptPubKey => - hashPreImages.collectFirst { case pubKey: ECPublicKey => - pubKey + hashPreImages.collectFirst { + case pubKey: ECPublicKey => + pubKey + case pubKeyBytes: ECPublicKeyBytes => pubKeyBytes.toPublicKey } match { case None => throw new IllegalArgumentException( @@ -418,7 +421,8 @@ case class P2PKInputInfo( override def conditionalPath: ConditionalPath = ConditionalPath.NoCondition - override def pubKeys: Vector[ECPublicKey] = Vector(scriptPubKey.publicKey) + override def pubKeys: Vector[ECPublicKey] = Vector( + scriptPubKey.publicKey.toPublicKey) override def requiredSigs: Int = 1 } @@ -454,7 +458,8 @@ case class P2PKWithTimeoutInputInfo( } override def pubKeys: Vector[ECPublicKey] = - Vector(scriptPubKey.pubKey, scriptPubKey.timeoutPubKey) + Vector(scriptPubKey.pubKey.toPublicKey, + scriptPubKey.timeoutPubKey.toPublicKey) override def requiredSigs: Int = 1 } @@ -468,7 +473,8 @@ case class MultiSignatureInputInfo( override def conditionalPath: ConditionalPath = ConditionalPath.NoCondition - override def pubKeys: Vector[ECPublicKey] = scriptPubKey.publicKeys.toVector + override def pubKeys: Vector[ECPublicKey] = + scriptPubKey.publicKeys.map(_.toPublicKey).toVector override def requiredSigs: Int = scriptPubKey.requiredSigs } @@ -545,7 +551,7 @@ object SegwitV0NativeInputInfo { Vector.empty): SegwitV0NativeInputInfo = { scriptWitness match { case p2wpkh: P2WPKHWitnessV0 => - P2WPKHV0InputInfo(outPoint, amount, p2wpkh.pubKey) + P2WPKHV0InputInfo(outPoint, amount, p2wpkh.pubKey.toPublicKey) case p2wsh: P2WSHWitnessV0 => P2WSHV0InputInfo(outPoint, amount, diff --git a/crypto-test/src/test/scala/org/bitcoins/crypto/ECPrivateKeyTest.scala b/crypto-test/src/test/scala/org/bitcoins/crypto/ECPrivateKeyTest.scala index 7039a99a4d..0d7ad97434 100644 --- a/crypto-test/src/test/scala/org/bitcoins/crypto/ECPrivateKeyTest.scala +++ b/crypto-test/src/test/scala/org/bitcoins/crypto/ECPrivateKeyTest.scala @@ -27,7 +27,7 @@ class ECPrivateKeyTest extends BitcoinSCryptoTest { } it must "not serialize a ECPrivateKey toString" in { - ECPrivateKey().toString must be("Masked(ECPrivateKeyImpl)") + ECPrivateKey().toString must be("Masked(ECPrivateKey)") } it must "successfully negate itself" in { diff --git a/crypto-test/src/test/scala/org/bitcoins/crypto/ECPublicKeyTest.scala b/crypto-test/src/test/scala/org/bitcoins/crypto/ECPublicKeyTest.scala index 812d4d52dd..5fd4473618 100644 --- a/crypto-test/src/test/scala/org/bitcoins/crypto/ECPublicKeyTest.scala +++ b/crypto-test/src/test/scala/org/bitcoins/crypto/ECPublicKeyTest.scala @@ -2,8 +2,6 @@ package org.bitcoins.crypto import scodec.bits._ -import scala.concurrent.ExecutionContext - class ECPublicKeyTest extends BitcoinSCryptoTest { it must "be able to decompress keys" in { @@ -30,17 +28,17 @@ class ECPublicKeyTest extends BitcoinSCryptoTest { it must "decompress keys correctly" in { forAll(CryptoGenerators.privateKey) { privKey => - val pubKey = privKey.publicKey + val privKeyBytes = ECPrivateKeyBytes(privKey.bytes) + val pubKey = privKeyBytes.publicKeyBytes - assert(privKey.isCompressed) + assert(privKeyBytes.isCompressed) assert(pubKey.isCompressed) val decompressedPrivKey = - ECPrivateKey(privKey.bytes, isCompressed = false)( - ExecutionContext.global) + ECPrivateKeyBytes(privKey.bytes, isCompressed = false) val decompressedPubKey = pubKey.decompressed - assert(decompressedPrivKey.publicKey == decompressedPubKey) + assert(decompressedPrivKey.publicKeyBytes == decompressedPubKey) assert(pubKey.bytes.tail == decompressedPubKey.bytes.splitAt(33)._1.tail) } } @@ -53,27 +51,25 @@ class ECPublicKeyTest extends BitcoinSCryptoTest { } it must "be able to compress/decompress public keys" in { - val privkey = ECPrivateKey.freshPrivateKey + val privkey = ECPrivateKeyBytes.freshPrivateKey assert(CryptoUtil.secKeyVerify(privkey.bytes)) assert(privkey.isCompressed) val notCompressedKey = - ECPrivateKey(bytes = privkey.bytes, isCompressed = false) - val pubkey = CryptoUtil.toPublicKey(notCompressedKey) + ECPrivateKeyBytes(bytes = privkey.bytes, isCompressed = false) + val pubkey = notCompressedKey.publicKeyBytes assert(CryptoUtil.isValidPubKey(pubkey.bytes)) assert(!pubkey.isCompressed) - val compressed = privkey.publicKey + val compressed = privkey.publicKeyBytes assert(CryptoUtil.isValidPubKey(compressed.bytes)) assert(compressed.isCompressed) - val converted = - CryptoUtil.publicKeyConvert(pubkey, compressed = true) + val converted = pubkey.compressed assert(CryptoUtil.isValidPubKey(converted.bytes)) assert(converted.isCompressed) - val decompressed = - CryptoUtil.publicKeyConvert(compressed, compressed = false) + val decompressed = compressed.decompressed assert(CryptoUtil.isValidPubKey(decompressed.bytes)) assert(!decompressed.isCompressed) @@ -84,8 +80,7 @@ class ECPublicKeyTest extends BitcoinSCryptoTest { } it must "not be able to add opposite public keys" in { - val privkey = ECPrivateKey.freshPrivateKey - val pubkey1 = privkey.publicKey + val pubkey1 = ECPublicKey.freshPublicKey val firstByte: Byte = if (pubkey1.bytes.head == 0x02) 0x03 else if (pubkey1.bytes.head == 0x03) 0x02 @@ -93,21 +88,37 @@ class ECPublicKeyTest extends BitcoinSCryptoTest { val pubkey2 = ECPublicKey.fromBytes(ByteVector(firstByte) ++ pubkey1.bytes.tail) - assertThrows[Exception] { - val sumKey = CryptoUtil.add(pubkey1, pubkey2) - if (sumKey == ECPublicKey.infinity) fail() - } + assertThrows[Exception](CryptoUtil.add(pubkey1, pubkey2)) + assertThrows[Exception]( + CryptoUtil.add(pubkey1.compressed, pubkey2.compressed)) + assertThrows[Exception]( + CryptoUtil.add(pubkey1.decompressed, pubkey2.decompressed)) + } - val decompressedPubkey1 = - CryptoUtil.publicKeyConvert(pubkey1, compressed = false) + it must "be able to add multiple public keys together with sub-sums of 0x00" in { + val pubkey1 = ECPublicKey.freshPublicKey + val firstByte: Byte = + if (pubkey1.bytes.head == 0x02) 0x03 + else if (pubkey1.bytes.head == 0x03) 0x02 + else pubkey1.bytes.head + val pubkey2 = + ECPublicKey.fromBytes(ByteVector(firstByte) ++ pubkey1.bytes.tail) + val pubkey3 = ECPublicKey.freshPublicKey + val firstByte2: Byte = + if (pubkey3.bytes.head == 0x02) 0x03 + else if (pubkey3.bytes.head == 0x03) 0x02 + else pubkey3.bytes.head + val pubkey4 = + ECPublicKey.fromBytes(ByteVector(firstByte2) ++ pubkey3.bytes.tail) - val decompressedPubkey2 = - CryptoUtil.publicKeyConvert(pubkey2, compressed = false) - - assertThrows[Exception] { - val sumKey = CryptoUtil.add(decompressedPubkey1, decompressedPubkey2) - if (sumKey == ECPublicKey.infinity) fail() - } + assert( + CryptoUtil.combinePubKeys(Vector(pubkey1, pubkey2, pubkey3)) == pubkey3) + assert( + CryptoUtil.combinePubKeys(Vector(pubkey3, pubkey1, pubkey2)) == pubkey3) + assertThrows[Exception]( + CryptoUtil.combinePubKeys(Vector(pubkey1, pubkey2, pubkey3, pubkey4))) + assertThrows[Exception]( + CryptoUtil.combinePubKeys(Vector(pubkey1, pubkey3, pubkey2, pubkey4))) } it must "correctly compress keys" in { @@ -116,16 +127,22 @@ class ECPublicKeyTest extends BitcoinSCryptoTest { val pubKeyCompressed = pubKey.compressed val pubKeyDecompressed = pubKey.decompressed - if (privKey.isCompressed) { - assert(pubKey == pubKeyCompressed) - } else { - assert(pubKey == pubKeyDecompressed) - } + assert(!pubKey.isCompressed) + assert(pubKeyCompressed.isFullyValid) + assert(pubKeyDecompressed.isFullyValid) - assert(pubKeyCompressed.decompressed == pubKeyDecompressed) - assert(pubKeyCompressed.compressed == pubKeyCompressed) - assert(pubKeyDecompressed.compressed == pubKeyCompressed) - assert(pubKeyDecompressed.decompressed == pubKeyDecompressed) + assert(pubKeyCompressed == pubKeyDecompressed) + assert(!pubKeyCompressed.decompressed.isCompressed) + assert(pubKeyCompressed.compressed.isCompressed) + assert(pubKeyDecompressed.compressed.isCompressed) + assert(!pubKeyDecompressed.decompressed.isCompressed) + assert(pubKeyCompressed == pubKey) + assert(pubKeyDecompressed == pubKey) + assert( + pubKeyCompressed.bytes.tail == pubKeyDecompressed.decompressedBytes + .splitAt(33) + ._1 + .tail) } } diff --git a/crypto/.js/src/main/scala/org/bitcoins/crypto/BCryptoCryptoRuntime.scala b/crypto/.js/src/main/scala/org/bitcoins/crypto/BCryptoCryptoRuntime.scala index ed770f698c..9ca0d03aac 100644 --- a/crypto/.js/src/main/scala/org/bitcoins/crypto/BCryptoCryptoRuntime.scala +++ b/crypto/.js/src/main/scala/org/bitcoins/crypto/BCryptoCryptoRuntime.scala @@ -63,8 +63,7 @@ trait BCryptoCryptoRuntime extends CryptoRuntime { override def toPublicKey(privateKey: ECPrivateKey): ECPublicKey = { val buffer = CryptoJsUtil.toNodeBuffer(privateKey.bytes) val pubKeyBuffer = - SECP256k1.publicKeyCreate(key = buffer, - compressed = privateKey.isCompressed) + SECP256k1.publicKeyCreate(key = buffer, compressed = false) val privKeyByteVec = CryptoJsUtil.toByteVector(pubKeyBuffer) ECPublicKey.fromBytes(privKeyByteVec) } @@ -151,8 +150,7 @@ trait BCryptoCryptoRuntime extends CryptoRuntime { override def publicKey(privateKey: ECPrivateKey): ECPublicKey = { val buffer = CryptoJsUtil.toNodeBuffer(privateKey.bytes) - val bufferPubKey = - SECP256k1.publicKeyCreate(buffer, privateKey.isCompressed) + val bufferPubKey = SECP256k1.publicKeyCreate(buffer, compressed = false) val byteVec = CryptoJsUtil.toByteVector(bufferPubKey) ECPublicKey.fromBytes(byteVec) } @@ -178,7 +176,7 @@ trait BCryptoCryptoRuntime extends CryptoRuntime { } override def verify( - publicKey: ECPublicKey, + publicKey: PublicKey[_], data: ByteVector, signature: ECDigitalSignature): Boolean = { val dataBuffer = CryptoJsUtil.toNodeBuffer(data) @@ -190,7 +188,7 @@ trait BCryptoCryptoRuntime extends CryptoRuntime { override def tweakMultiply( publicKey: ECPublicKey, tweak: FieldElement): ECPublicKey = { - val pubKeyBuffer = CryptoJsUtil.toNodeBuffer(publicKey.bytes) + val pubKeyBuffer = CryptoJsUtil.toNodeBuffer(publicKey.decompressedBytes) val tweakBuffer = CryptoJsUtil.toNodeBuffer(tweak.bytes) val keyBuffer = SECP256k1.publicKeyTweakMul(pubKeyBuffer, tweakBuffer, compress = true) @@ -198,23 +196,9 @@ trait BCryptoCryptoRuntime extends CryptoRuntime { ECPublicKey.fromBytes(keyByteVec) } - def publicKeyConvert(buffer: ByteVector, compressed: Boolean): ByteVector = { - val pubKeyBuffer = - publicKeyConvert(CryptoJsUtil.toNodeBuffer(buffer), compressed) - CryptoJsUtil.toByteVector(pubKeyBuffer) - } - - def publicKeyConvert(buffer: Buffer, compressed: Boolean): Buffer = - SECP256k1.publicKeyConvert(buffer, compressed) - - override def publicKeyConvert( - key: ECPublicKey, - compressed: Boolean): ECPublicKey = - ECPublicKey(publicKeyConvert(key.bytes, compressed)) - override def add(pk1: ECPublicKey, pk2: ECPublicKey): ECPublicKey = { - val pk1Buffer = CryptoJsUtil.toNodeBuffer(pk1.bytes) - val pk2Buffer = CryptoJsUtil.toNodeBuffer(pk2.bytes) + val pk1Buffer = CryptoJsUtil.toNodeBuffer(pk1.decompressedBytes) + val pk2Buffer = CryptoJsUtil.toNodeBuffer(pk2.decompressedBytes) try { val keyBuffer = SECP256k1.publicKeyCombine(js.Array(pk1Buffer, pk2Buffer), @@ -223,21 +207,13 @@ trait BCryptoCryptoRuntime extends CryptoRuntime { ECPublicKey.fromBytes(keyBytes) } catch { case ex: JavaScriptException => + val k1: ByteVector = pk1.bytes + val k2: ByteVector = pk2.bytes + // check for infinity - val k1: ByteVector = - if (pk1.isCompressed) pk1.bytes - else publicKeyConvert(pk1.bytes, compressed = true) - - val k2: ByteVector = - if (pk2.isCompressed) pk2.bytes - else publicKeyConvert(pk2.bytes, compressed = true) - - if ( - ((k1.head == 0x02 && k2.head == 0x03) || - (k1.head == 0x03 && k2.head == 0x02)) && - k1.tail == k2.tail - ) { - ECPublicKey.infinity + if ((k1.head ^ k2.head) == 0x01 && k1.tail == k2.tail) { + throw new IllegalArgumentException( + s"Invalid public key sum, got 0x00 = $pk1 + $pk2") } else { throw ex } @@ -247,7 +223,7 @@ trait BCryptoCryptoRuntime extends CryptoRuntime { override def pubKeyTweakAdd( pubkey: ECPublicKey, privkey: ECPrivateKey): ECPublicKey = { - val pubKeyBuffer = CryptoJsUtil.toNodeBuffer(pubkey.bytes) + val pubKeyBuffer = CryptoJsUtil.toNodeBuffer(pubkey.decompressedBytes) val privKeyBuffer = CryptoJsUtil.toNodeBuffer(privkey.bytes) val keyBuffer = SECP256k1.publicKeyTweakAdd(pubKeyBuffer, privKeyBuffer, compress = true) @@ -269,19 +245,19 @@ trait BCryptoCryptoRuntime extends CryptoRuntime { hi | lo } - override def decodePoint(bytes: ByteVector): ECPoint = { + override def decodePoint(bytes: ByteVector): SecpPoint = { if (bytes.size == 1 && bytes(0) == 0x00) { - ECPointInfinity + SecpPointInfinity } else { val decoded = SECP256k1.curve .applyDynamic("decodePoint")(CryptoJsUtil.toNodeBuffer(bytes)) .asInstanceOf[Point] if (decoded.isInfinity()) - ECPointInfinity + SecpPointInfinity else - ECPoint(new BigInteger(decoded.getX().toString()), - new BigInteger(decoded.getY().toString())) + SecpPoint(new BigInteger(decoded.getX().toString()), + new BigInteger(decoded.getY().toString())) } } diff --git a/crypto/.jvm/src/main/scala/org/bitcoins/crypto/BouncyCastleUtil.scala b/crypto/.jvm/src/main/scala/org/bitcoins/crypto/BouncyCastleUtil.scala index 0aad3a26c9..07a4597947 100644 --- a/crypto/.jvm/src/main/scala/org/bitcoins/crypto/BouncyCastleUtil.scala +++ b/crypto/.jvm/src/main/scala/org/bitcoins/crypto/BouncyCastleUtil.scala @@ -6,7 +6,7 @@ import org.bouncycastle.crypto.params.{ ECPublicKeyParameters } import org.bouncycastle.crypto.signers.{ECDSASigner, HMacDSAKCalculator} -import org.bouncycastle.math.ec.ECCurve +import org.bouncycastle.math.ec.{ECCurve, ECPoint} import scodec.bits.ByteVector import java.math.BigInteger @@ -26,25 +26,23 @@ object BouncyCastleUtil { decodePubKey(point, publicKey.isCompressed) } - private[crypto] def decodePoint( - bytes: ByteVector): org.bouncycastle.math.ec.ECPoint = { + private[crypto] def decodePoint(bytes: ByteVector): ECPoint = { curve.decodePoint(bytes.toArray) } - private[crypto] def decodePoint( - pubKey: ECPublicKey): org.bouncycastle.math.ec.ECPoint = { - decodePoint(pubKey.bytes) + private[crypto] def decodePoint(pubKey: ECPublicKey): ECPoint = { + decodePoint(pubKey.decompressedBytes) } private[crypto] def decodePubKey( - point: org.bouncycastle.math.ec.ECPoint, + point: ECPoint, isCompressed: Boolean = true): ECPublicKey = { val bytes = point.getEncoded(isCompressed) ECPublicKey.fromBytes(ByteVector(bytes)) } def validatePublicKey(bytes: ByteVector): Boolean = { - Try(decodePoint(bytes)) + bytes != ByteVector(0x00) && Try(decodePoint(bytes)) .map(_.getCurve == curve) .getOrElse(false) } @@ -68,7 +66,7 @@ object BouncyCastleUtil { def computePublicKey(privateKey: ECPrivateKey): ECPublicKey = { val priv = getBigInteger(privateKey.bytes) val point = G.multiply(priv) - val pubBytes = ByteVector(point.getEncoded(privateKey.isCompressed)) + val pubBytes = ByteVector(point.getEncoded(false)) require( ECPublicKey.isFullyValid(pubBytes), s"Bouncy Castle failed to generate a valid public key, got: ${CryptoBytesUtil @@ -149,7 +147,7 @@ object BouncyCastleUtil { def verifyDigitalSignature( data: ByteVector, - publicKey: ECPublicKey, + publicKey: PublicKey[_], signature: ECDigitalSignature): Boolean = { val resultTry = Try { val publicKeyParams = diff --git a/crypto/.jvm/src/main/scala/org/bitcoins/crypto/BouncycastleCryptoRuntime.scala b/crypto/.jvm/src/main/scala/org/bitcoins/crypto/BouncycastleCryptoRuntime.scala index d83d921ee7..2b32dc12be 100644 --- a/crypto/.jvm/src/main/scala/org/bitcoins/crypto/BouncycastleCryptoRuntime.scala +++ b/crypto/.jvm/src/main/scala/org/bitcoins/crypto/BouncycastleCryptoRuntime.scala @@ -1,6 +1,5 @@ package org.bitcoins.crypto -import org.bitcoins.crypto import org.bouncycastle.crypto.AsymmetricCipherKeyPair import org.bouncycastle.crypto.digests.{RIPEMD160Digest, SHA512Digest} import org.bouncycastle.crypto.generators.ECKeyPairGenerator @@ -10,6 +9,7 @@ import org.bouncycastle.crypto.params.{ ECPrivateKeyParameters, KeyParameter } +import org.bouncycastle.math.ec.ECPoint import scodec.bits.ByteVector import java.math.BigInteger @@ -34,16 +34,14 @@ trait BouncycastleCryptoRuntime extends CryptoRuntime { keypair.getPrivate.asInstanceOf[ECPrivateKeyParameters] val priv: BigInteger = privParams.getD val bytes = ByteVector(priv.toByteArray) - ECPrivateKey.fromBytes(bytes) + ECPrivateKey.fromBytes(bytes.padLeft(33)) } /** @param x x coordinate * @return a tuple (p1, p2) where p1 and p2 are points on the curve and p1.x = p2.x = x * p1.y is even, p2.y is odd */ - def recoverPoint(x: BigInteger): ( - org.bouncycastle.math.ec.ECPoint, - org.bouncycastle.math.ec.ECPoint) = { + def recoverPoint(x: BigInteger): (ECPoint, ECPoint) = { val bytes = ByteVector(x.toByteArray) val bytes32 = if (bytes.length < 32) { @@ -148,7 +146,7 @@ trait BouncycastleCryptoRuntime extends CryptoRuntime { } override def verify( - publicKey: ECPublicKey, + publicKey: PublicKey[_], data: ByteVector, signature: ECDigitalSignature): Boolean = BouncyCastleUtil.verifyDigitalSignature(data, publicKey, signature) @@ -156,11 +154,6 @@ trait BouncycastleCryptoRuntime extends CryptoRuntime { override def publicKey(privateKey: ECPrivateKey): ECPublicKey = BouncyCastleUtil.computePublicKey(privateKey) - override def publicKeyConvert( - key: ECPublicKey, - compressed: Boolean): ECPublicKey = - BouncyCastleUtil.publicKeyConvert(key, compressed) - override def tweakMultiply( publicKey: ECPublicKey, tweak: FieldElement): ECPublicKey = @@ -200,14 +193,14 @@ trait BouncycastleCryptoRuntime extends CryptoRuntime { sh.doFinal() } - override def decodePoint(bytes: ByteVector): crypto.ECPoint = { + override def decodePoint(bytes: ByteVector): SecpPoint = { val decoded = BouncyCastleUtil.decodePoint(bytes) if (decoded.isInfinity) - crypto.ECPointInfinity + SecpPointInfinity else - crypto.ECPoint(decoded.getRawXCoord.getEncoded, - decoded.getRawYCoord.getEncoded) + SecpPoint(decoded.getRawXCoord.getEncoded, + decoded.getRawYCoord.getEncoded) } override def pbkdf2WithSha512( diff --git a/crypto/.jvm/src/main/scala/org/bitcoins/crypto/LibSecp256k1CryptoRuntime.scala b/crypto/.jvm/src/main/scala/org/bitcoins/crypto/LibSecp256k1CryptoRuntime.scala index 8c8987b9ec..7c83641ec2 100644 --- a/crypto/.jvm/src/main/scala/org/bitcoins/crypto/LibSecp256k1CryptoRuntime.scala +++ b/crypto/.jvm/src/main/scala/org/bitcoins/crypto/LibSecp256k1CryptoRuntime.scala @@ -44,8 +44,7 @@ trait LibSecp256k1CryptoRuntime extends CryptoRuntime { override def toPublicKey(privateKey: ECPrivateKey): ECPublicKey = { val pubKeyBytes: Array[Byte] = - NativeSecp256k1.computePubkey(privateKey.bytes.toArray, - privateKey.isCompressed) + NativeSecp256k1.computePubkey(privateKey.bytes.toArray, false) val pubBytes = ByteVector(pubKeyBytes) require( ECPublicKey.isFullyValid(pubBytes), @@ -77,7 +76,7 @@ trait LibSecp256k1CryptoRuntime extends CryptoRuntime { NativeSecp256k1.secKeyVerify(privateKeyBytes.toArray) override def verify( - publicKey: ECPublicKey, + publicKey: PublicKey[_], data: ByteVector, signature: ECDigitalSignature): Boolean = { val result = @@ -96,17 +95,16 @@ trait LibSecp256k1CryptoRuntime extends CryptoRuntime { } else result } - override def decompressed(publicKey: ECPublicKey): ECPublicKey = { + override def decompressed[PK <: PublicKey[PK]](publicKey: PK): PK = { if (publicKey.isCompressed) { val decompressed = NativeSecp256k1.decompress(publicKey.bytes.toArray) - ECPublicKey.fromBytes(ByteVector(decompressed)) + publicKey.fromBytes(ByteVector(decompressed)) } else publicKey } override def publicKey(privateKey: ECPrivateKey): ECPublicKey = { val pubKeyBytes: Array[Byte] = - NativeSecp256k1.computePubkey(privateKey.bytes.toArray, - privateKey.isCompressed) + NativeSecp256k1.computePubkey(privateKey.bytes.toArray, false) val pubBytes = ByteVector(pubKeyBytes) require( ECPublicKey.isFullyValid(pubBytes), @@ -115,17 +113,13 @@ trait LibSecp256k1CryptoRuntime extends CryptoRuntime { ECPublicKey(pubBytes) } - override def publicKeyConvert( - key: ECPublicKey, - compressed: Boolean): ECPublicKey = - BouncycastleCryptoRuntime.publicKeyConvert(key, compressed) - override def tweakMultiply( publicKey: ECPublicKey, tweak: FieldElement): ECPublicKey = { - val mulBytes = NativeSecp256k1.pubKeyTweakMul(publicKey.bytes.toArray, - tweak.bytes.toArray, - publicKey.isCompressed) + val mulBytes = NativeSecp256k1.pubKeyTweakMul( + publicKey.decompressedBytes.toArray, + tweak.bytes.toArray, + false) ECPublicKey(ByteVector(mulBytes)) } @@ -142,8 +136,16 @@ trait LibSecp256k1CryptoRuntime extends CryptoRuntime { } override def add(pk1: ECPublicKey, pk2: ECPublicKey): ECPublicKey = { - val summands = Array(pk1.bytes.toArray, pk2.bytes.toArray) - val sumKey = NativeSecp256k1.pubKeyCombine(summands, pk1.isCompressed) + val summands = + Array(pk1.decompressedBytes.toArray, pk2.decompressedBytes.toArray) + val sumKey = NativeSecp256k1.pubKeyCombine(summands, false) + + ECPublicKey(ByteVector(sumKey)) + } + + override def combinePubKeys(pubKeys: Vector[ECPublicKey]): ECPublicKey = { + val summands = pubKeys.map(_.decompressedBytes.toArray).toArray + val sumKey = NativeSecp256k1.pubKeyCombine(summands, false) ECPublicKey(ByteVector(sumKey)) } @@ -151,9 +153,10 @@ trait LibSecp256k1CryptoRuntime extends CryptoRuntime { override def pubKeyTweakAdd( pubkey: ECPublicKey, privkey: ECPrivateKey): ECPublicKey = { - val tweaked = NativeSecp256k1.pubKeyTweakAdd(pubkey.bytes.toArray, - privkey.bytes.toArray, - privkey.isCompressed) + val tweaked = NativeSecp256k1.pubKeyTweakAdd( + pubkey.decompressedBytes.toArray, + privkey.bytes.toArray, + false) ECPublicKey(ByteVector(tweaked)) } @@ -214,10 +217,11 @@ trait LibSecp256k1CryptoRuntime extends CryptoRuntime { adaptorPoint: ECPublicKey, msg: ByteVector, auxRand: ByteVector): ECAdaptorSignature = { - val sig = NativeSecp256k1.adaptorSign(key.bytes.toArray, - adaptorPoint.bytes.toArray, - msg.toArray, - auxRand.toArray) + val sig = NativeSecp256k1.adaptorSign( + key.bytes.toArray, + adaptorPoint.decompressedBytes.toArray, + msg.toArray, + auxRand.toArray) ECAdaptorSignature(ByteVector(sig)) } @@ -237,7 +241,7 @@ trait LibSecp256k1CryptoRuntime extends CryptoRuntime { val secretBytes = NativeSecp256k1.adaptorExtractSecret( signature.bytes.toArray, adaptorSignature.bytes.toArray, - key.bytes.toArray) + key.decompressedBytes.toArray) ECPrivateKey(ByteVector(secretBytes)) } @@ -248,9 +252,9 @@ trait LibSecp256k1CryptoRuntime extends CryptoRuntime { msg: ByteVector, adaptorPoint: ECPublicKey): Boolean = { NativeSecp256k1.adaptorVerify(adaptorSignature.bytes.toArray, - key.bytes.toArray, + key.decompressedBytes.toArray, msg.toArray, - adaptorPoint.bytes.toArray) + adaptorPoint.decompressedBytes.toArray) } override def isValidSignatureEncoding( @@ -263,16 +267,16 @@ trait LibSecp256k1CryptoRuntime extends CryptoRuntime { override def sipHash(item: ByteVector, key: SipHashKey): Long = BouncycastleCryptoRuntime.sipHash(item, key) - override def decodePoint(bytes: ByteVector): ECPoint = { + override def decodePoint(bytes: ByteVector): SecpPoint = { val infinityPt: ByteVector = ByteVector.fromByte(0x00) if (bytes == infinityPt) { - ECPointInfinity + SecpPointInfinity } else { val pointBytes = NativeSecp256k1.decompress(bytes.toArray) val xBytes = pointBytes.tail.take(32) val yBytes = pointBytes.takeRight(32) - ECPoint(xBytes, yBytes) + SecpPoint(xBytes, yBytes) } } diff --git a/crypto/src/main/scala/org/bitcoins/crypto/AdaptorUtil.scala b/crypto/src/main/scala/org/bitcoins/crypto/AdaptorUtil.scala index 262c0bce10..d5fdb48fce 100644 --- a/crypto/src/main/scala/org/bitcoins/crypto/AdaptorUtil.scala +++ b/crypto/src/main/scala/org/bitcoins/crypto/AdaptorUtil.scala @@ -35,7 +35,7 @@ object AdaptorUtil { val randHash = CryptoUtil.sha256ECDSAAdaptorAux(auxRand).bytes val maskedKey = randHash.xor(privKey.bytes) - val bytesToHash = maskedKey ++ adaptorPoint.compressed.bytes ++ message + val bytesToHash = maskedKey ++ adaptorPoint.bytes ++ message val nonceHash = algoName match { case "DLEQ" => CryptoUtil.sha256DLEQ(bytesToHash) case "ECDSAadaptor/non" => CryptoUtil.sha256ECDSAAdaptorNonce(bytesToHash) @@ -55,10 +55,10 @@ object AdaptorUtil { r: ECPublicKey, privateKey: ECPrivateKey): FieldElement = { CryptoUtil.decodePoint(r) match { - case ECPointInfinity => + case SecpPointInfinity => throw new IllegalArgumentException( - s"Invalid point, got=$ECPointInfinity") - case point: ECPointImpl => + s"Invalid point, got=$SecpPointInfinity") + case point: SecpPointFinite => val rx = FieldElement(point.x.toBigInteger) val x = privateKey.fieldElement val m = FieldElement(dataToSign) @@ -106,7 +106,7 @@ object AdaptorUtil { val untweakedPoint = m.getPublicKey.add(pubKey.tweakMultiply(rx)).tweakMultiply(s.inverse) - FieldElement(untweakedPoint.compressed.bytes.tail) + FieldElement(untweakedPoint.bytes.tail) } /** https://github.com/discreetlogcontracts/dlcspecs/blob/d01595b70269d4204b05510d19bba6a4f4fcff23/ECDSA-adaptor.md#encryption-verification */ @@ -163,9 +163,8 @@ object AdaptorUtil { val secretOrNeg = adaptorSig.adaptedS.multInv(FieldElement(sig.s)) - require( - secretOrNeg.getPublicKey.compressed.bytes.tail == adaptor.compressed.bytes.tail, - s"Invalid inputs: $sig, $adaptorSig, and $adaptor") + require(secretOrNeg.getPublicKey.bytes.tail == adaptor.bytes.tail, + s"Invalid inputs: $sig, $adaptorSig, and $adaptor") if (secretOrNeg.getPublicKey == adaptor) { secretOrNeg.toPrivateKey diff --git a/crypto/src/main/scala/org/bitcoins/crypto/CryptoRuntime.scala b/crypto/src/main/scala/org/bitcoins/crypto/CryptoRuntime.scala index 4a832031bb..a04b79ad93 100644 --- a/crypto/src/main/scala/org/bitcoins/crypto/CryptoRuntime.scala +++ b/crypto/src/main/scala/org/bitcoins/crypto/CryptoRuntime.scala @@ -170,11 +170,6 @@ trait CryptoRuntime { def publicKey(privateKey: ECPrivateKey): ECPublicKey - /** Converts the given public key from its current representation to compressed/not compressed - * depending upon how [[compressed]] is set - */ - def publicKeyConvert(key: ECPublicKey, compressed: Boolean): ECPublicKey - def sign(privateKey: ECPrivateKey, dataToSign: ByteVector): ECDigitalSignature def signWithEntropy( @@ -185,20 +180,20 @@ trait CryptoRuntime { def secKeyVerify(privateKeybytes: ByteVector): Boolean def verify( - publicKey: ECPublicKey, + publicKey: PublicKey[_], data: ByteVector, signature: ECDigitalSignature): Boolean - def decompressed(publicKey: ECPublicKey): ECPublicKey = { + def decompressed[PK <: PublicKey[PK]](publicKey: PK): PK = { if (publicKey.isCompressed) { decodePoint(publicKey.bytes) match { - case ECPointInfinity => ECPublicKey.fromHex("00") - case point: ECPointImpl => + case SecpPointInfinity => publicKey.fromHex("00") + case point: SecpPointFinite => val decompressedBytes = ByteVector.fromHex("04").get ++ point.x.bytes ++ point.y.bytes - ECPublicKey(decompressedBytes) + publicKey.fromBytes(decompressedBytes) } } else publicKey } @@ -213,16 +208,50 @@ trait CryptoRuntime { sum.bytes } + /** Adds two SecpPoints together and correctly handles the point at infinity (0x00). */ + def add(point1: SecpPoint, point2: SecpPoint): SecpPoint = { + (point1, point2) match { + case (SecpPointInfinity, p) => p + case (p, SecpPointInfinity) => p + case (p1: SecpPointFinite, p2: SecpPointFinite) => + val pk1 = p1.toPublicKey + val pk2 = p2.toPublicKey + + if ( + (pk1.bytes.head ^ pk2.bytes.head) == 0x01 && pk1.bytes.tail == pk2.bytes.tail + ) { + SecpPointInfinity + } else { + add(pk1, pk2).toPoint + } + } + } + + /** Adds two public keys together, failing if the sum is 0x00 (the point at infinity). */ def add(pk1: ECPublicKey, pk2: ECPublicKey): ECPublicKey + /** Adds a Vector of public keys together, failing only if the total sum is 0x00 + * (the point at infinity), but still succeeding if sub-sums are 0x00. + */ + def combinePubKeys(pubKeys: Vector[ECPublicKey]): ECPublicKey = { + val summandPoints = pubKeys.map(_.toPoint) + val sumPoint = summandPoints.reduce[SecpPoint](add(_, _)) + sumPoint match { + case SecpPointInfinity => + throw new IllegalArgumentException( + "Sum result was 0x00, an invalid public key.") + case p: SecpPointFinite => p.toPublicKey + } + } + def pubKeyTweakAdd(pubkey: ECPublicKey, privkey: ECPrivateKey): ECPublicKey def isValidPubKey(bytes: ByteVector): Boolean - def decodePoint(bytes: ByteVector): ECPoint + def decodePoint(bytes: ByteVector): SecpPoint - def decodePoint(pubKey: ECPublicKey): ECPoint = { - decodePoint(pubKey.bytes) + def decodePoint(pubKey: ECPublicKey): SecpPoint = { + decodePoint(pubKey.decompressedBytes) } def schnorrSign( @@ -268,11 +297,12 @@ trait CryptoRuntime { val sigPoint = s.publicKey val challengePoint = schnorrPubKey.publicKey.tweakMultiply(negE) - val computedR = challengePoint.add(sigPoint) - decodePoint(computedR) match { - case ECPointInfinity => false - case ECPointImpl(_, yCoord) => - !yCoord.toBigInteger.testBit(0) && computedR.schnorrNonce == rx + val computedR = challengePoint.toPoint.add(sigPoint.toPoint) + computedR match { + case SecpPointInfinity => false + case point: SecpPointFinite => + !point.y.toBigInteger.testBit( + 0) && point.toPublicKey.schnorrNonce == rx } case Failure(_) => false } diff --git a/crypto/src/main/scala/org/bitcoins/crypto/CryptoUtil.scala b/crypto/src/main/scala/org/bitcoins/crypto/CryptoUtil.scala index 815d495eb0..21acc1e9a0 100644 --- a/crypto/src/main/scala/org/bitcoins/crypto/CryptoUtil.scala +++ b/crypto/src/main/scala/org/bitcoins/crypto/CryptoUtil.scala @@ -83,11 +83,6 @@ trait CryptoUtil extends CryptoRuntime { override def publicKey(privateKey: ECPrivateKey): ECPublicKey = cryptoRuntime.publicKey(privateKey) - override def publicKeyConvert( - key: ECPublicKey, - compressed: Boolean): ECPublicKey = - cryptoRuntime.publicKeyConvert(key, compressed) - override def sign( privateKey: ECPrivateKey, dataToSign: ByteVector): ECDigitalSignature = @@ -103,12 +98,12 @@ trait CryptoUtil extends CryptoRuntime { cryptoRuntime.secKeyVerify(privateKeybytes) override def verify( - publicKey: ECPublicKey, + publicKey: PublicKey[_], data: ByteVector, signature: ECDigitalSignature): Boolean = cryptoRuntime.verify(publicKey, data, signature) - override def decompressed(publicKey: ECPublicKey): ECPublicKey = + override def decompressed[PK <: PublicKey[PK]](publicKey: PK): PK = cryptoRuntime.decompressed(publicKey) override def tweakMultiply( @@ -122,9 +117,15 @@ trait CryptoUtil extends CryptoRuntime { override def add(pk1: ByteVector, pk2: ECPrivateKey): ByteVector = cryptoRuntime.add(pk1, pk2) + override def add(point1: SecpPoint, point2: SecpPoint): SecpPoint = + cryptoRuntime.add(point1, point2) + override def add(pk1: ECPublicKey, pk2: ECPublicKey): ECPublicKey = cryptoRuntime.add(pk1, pk2) + override def combinePubKeys(pubKeys: Vector[ECPublicKey]): ECPublicKey = + cryptoRuntime.combinePubKeys(pubKeys) + override def pubKeyTweakAdd( pubkey: ECPublicKey, privkey: ECPrivateKey): ECPublicKey = @@ -197,7 +198,7 @@ trait CryptoUtil extends CryptoRuntime { override def sipHash(item: ByteVector, key: SipHashKey): Long = cryptoRuntime.sipHash(item, key) - override def decodePoint(bytes: ByteVector): ECPoint = + override def decodePoint(bytes: ByteVector): SecpPoint = cryptoRuntime.decodePoint(bytes) override def randomBytes(n: Int): ByteVector = cryptoRuntime.randomBytes(n) diff --git a/crypto/src/main/scala/org/bitcoins/crypto/DLEQUtil.scala b/crypto/src/main/scala/org/bitcoins/crypto/DLEQUtil.scala index c8dd5d323d..5cccdcbcbe 100644 --- a/crypto/src/main/scala/org/bitcoins/crypto/DLEQUtil.scala +++ b/crypto/src/main/scala/org/bitcoins/crypto/DLEQUtil.scala @@ -45,7 +45,7 @@ object DLEQUtil { tweakedPoint: ECPublicKey, auxRand: ByteVector): FieldElement = { val hash = CryptoUtil - .sha256(point.compressed.bytes ++ tweakedPoint.compressed.bytes) + .sha256(point.bytes ++ tweakedPoint.bytes) .bytes AdaptorUtil.adaptorNonce(hash, @@ -66,8 +66,8 @@ object DLEQUtil { p2: ECPublicKey): ByteVector = { CryptoUtil .sha256DLEQ( - p1.compressed.bytes ++ adaptorPoint.compressed.bytes ++ - p2.compressed.bytes ++ r1.compressed.bytes ++ r2.compressed.bytes) + p1.bytes ++ adaptorPoint.bytes ++ + p2.bytes ++ r1.bytes ++ r2.bytes) .bytes } diff --git a/crypto/src/main/scala/org/bitcoins/crypto/ECAdaptorSignature.scala b/crypto/src/main/scala/org/bitcoins/crypto/ECAdaptorSignature.scala index 42e4204f7c..94d8eeef8f 100644 --- a/crypto/src/main/scala/org/bitcoins/crypto/ECAdaptorSignature.scala +++ b/crypto/src/main/scala/org/bitcoins/crypto/ECAdaptorSignature.scala @@ -38,7 +38,7 @@ object ECAdaptorSignature extends Factory[ECAdaptorSignature] { dleqProofE: FieldElement, dleqProofS: FieldElement): ECAdaptorSignature = { fromBytes( - tweakedNonce.compressed.bytes ++ untweakedNonce.compressed.bytes ++ + tweakedNonce.bytes ++ untweakedNonce.bytes ++ adaptedS.bytes ++ dleqProofE.bytes ++ dleqProofS.bytes ) } diff --git a/crypto/src/main/scala/org/bitcoins/crypto/ECKey.scala b/crypto/src/main/scala/org/bitcoins/crypto/ECKey.scala index 866c6b2917..6b06a075ca 100644 --- a/crypto/src/main/scala/org/bitcoins/crypto/ECKey.scala +++ b/crypto/src/main/scala/org/bitcoins/crypto/ECKey.scala @@ -3,19 +3,143 @@ package org.bitcoins.crypto import scodec.bits.ByteVector import java.math.BigInteger -import scala.annotation.tailrec -import scala.concurrent.ExecutionContext + +/** Represents the raw bytes which are meant to represent an ECKey without deserializing. */ +sealed trait ECKeyBytes extends NetworkElement + +/** Represents a serialization sensitive ECPrivateKey (such as is used in WIF). */ +case class ECPrivateKeyBytes(bytes: ByteVector, isCompressed: Boolean = true) + extends ECKeyBytes + with MaskedToString { + val toPrivateKey: ECPrivateKey = ECPrivateKey(bytes) + + /** Returns the raw ECPublicKeyBytes serialized using isCompressed. */ + def publicKeyBytes: ECPublicKeyBytes = { + val pubKey = toPrivateKey.publicKey + if (isCompressed) { + ECPublicKeyBytes(pubKey.bytes) + } else { + ECPublicKeyBytes(pubKey.decompressedBytes) + } + } + + override def toStringSensitive: String = s"ECPrivateKeyBytes($hex)" +} + +object ECPrivateKeyBytes extends Factory[ECPrivateKeyBytes] { + + override def fromBytes(bytes: ByteVector): ECPrivateKeyBytes = { + val modifiedBytes = ECPrivateKey.fromBytes(bytes).bytes + + new ECPrivateKeyBytes(modifiedBytes) + } + + def freshPrivateKey(isCompressed: Boolean): ECPrivateKeyBytes = { + CryptoUtil.freshPrivateKey.toPrivateKeyBytes(isCompressed) + } + + def freshPrivateKey: ECPrivateKeyBytes = { + CryptoUtil.freshPrivateKey.toPrivateKeyBytes() + } +} + +/** Represents any type which wraps public key bytes which can be used for ECDSA verification. + * Should always be instantiated with class X extends PublicKey[X]. + */ +sealed trait PublicKey[PK <: PublicKey[PK]] extends NetworkElement { + + /** The fromBytes function for the PK type. */ + private[crypto] def fromBytes(bytes: ByteVector): PK + + private[crypto] def fromHex(hex: String): PK = { + fromBytes(CryptoBytesUtil.decodeHex(hex)) + } + + /** Returns this but as a PK. */ + private def thisAsPK: PK = { + this.asInstanceOf[PK] + } + + def verify(hash: HashDigest, signature: ECDigitalSignature): Boolean = + verify(hash.bytes, signature) + + /** Verifies if a given piece of data is signed by the + * [[org.bitcoins.crypto.ECPrivateKey ECPrivateKey]]'s corresponding + * [[org.bitcoins.crypto.ECPublicKey ECPublicKey]]. + */ + def verify(data: ByteVector, signature: ECDigitalSignature): Boolean = { + CryptoUtil.verify(this, data, signature) + } + + def verify(hex: String, signature: ECDigitalSignature): Boolean = + verify(CryptoBytesUtil.decodeHex(hex), signature) + + /** Returns true if the underlying bytes being wrapped are compressed */ + def isCompressed: Boolean = bytes.size == 33 + + /** Returns true if the underlying bytes being wrapped are valid according to secp256k1 */ + def isFullyValid: Boolean = ECPublicKey.isFullyValid(bytes) + + /** Returns the decompressed version of this PublicKey */ + lazy val decompressed: PK = { + if (isCompressed) { + CryptoUtil.decompressed(thisAsPK) + } else thisAsPK + } + + /** Returns the compressed version of this PublicKey */ + lazy val compressed: PK = { + if (isCompressed || bytes == ByteVector.fromByte(0x00)) { + thisAsPK + } else { + val key = if (bytes.length == 65) this else decompressed + val (x, y) = key.bytes.tail.splitAt(32) + val leadByte = if (FieldElement(y).isEven) 2.toByte else 3.toByte + fromBytes(x.+:(leadByte)) + } + } +} + +/** Wraps raw ECPublicKey bytes without doing any validation or deserialization (may be invalid). */ +case class ECPublicKeyBytes(bytes: ByteVector) + extends ECKeyBytes + with PublicKey[ECPublicKeyBytes] { + + /** Parse these bytes into the bitcoin-s internal public key type. */ + def toPublicKey: ECPublicKey = ECPublicKey(bytes) + + override private[crypto] def fromBytes(bytes: ByteVector): ECPublicKeyBytes = + ECPublicKeyBytes(bytes) +} + +object ECPublicKeyBytes extends Factory[ECPublicKeyBytes] { + + override def fromBytes(bytes: ByteVector): ECPublicKeyBytes = { + new ECPublicKeyBytes(bytes) + } + + def freshPublicKey: ECPublicKeyBytes = { + ECPrivateKeyBytes.freshPrivateKey.publicKeyBytes + } +} /** Created by chris on 2/16/16. + * Represents a fully parsed and validated ECDSA private or public key. */ sealed abstract class BaseECKey extends NetworkElement /** Created by chris on 2/16/16. + * A valid deserialized private key. + * + * Note that there is no notion of compressed vs. decompressed + * as there is in Wallet Import Format (WIF), if dealing with + * external wallets then ECPrivateKeyBytes may be needed. */ -sealed abstract class ECPrivateKey +case class ECPrivateKey(bytes: ByteVector) extends BaseECKey with AdaptorSign with MaskedToString { + require(CryptoUtil.secKeyVerify(bytes), s"Invalid key, hex: ${bytes.toHex}") /** Signs a given sequence of bytes with the signingKey * @param dataToSign the bytes to be signed @@ -91,16 +215,13 @@ sealed abstract class ECPrivateKey def negate: ECPrivateKey = { val negPrivKeyNum = N.subtract(new BigInteger(1, bytes.toArray)) - ECPrivateKey(ByteVector(negPrivKeyNum.toByteArray)) + ECPrivateKey(ByteVector(negPrivKeyNum.toByteArray).padLeft(33)) } def add(other: ECPrivateKey): ECPrivateKey = { CryptoUtil.add(this, other) } - /** Signifies if the this private key corresponds to a compressed public key */ - def isCompressed: Boolean - /** Derives the public for a the private key */ override def publicKey: ECPublicKey = CryptoUtil.publicKey(this) @@ -115,83 +236,61 @@ sealed abstract class ECPrivateKey def fieldElement: FieldElement = FieldElement(bytes) - override def toStringSensitive: String = s"ECPrivateKey($hex,$isCompressed)" + override def toStringSensitive: String = s"ECPrivateKey($hex)" + + def toPrivateKeyBytes(isCompressed: Boolean = true): ECPrivateKeyBytes = { + ECPrivateKeyBytes(bytes, isCompressed) + } } object ECPrivateKey extends Factory[ECPrivateKey] { - private case class ECPrivateKeyImpl( - override val bytes: ByteVector, - isCompressed: Boolean, - ec: ExecutionContext) - extends ECPrivateKey { - require(CryptoUtil.secKeyVerify(bytes), s"Invalid key, hex: ${bytes.toHex}") - } - - def apply(bytes: ByteVector, isCompressed: Boolean)(implicit - ec: ExecutionContext): ECPrivateKey = { - ECPrivateKeyImpl(bytes, isCompressed, ec) - } - - override def fromBytes(bytes: ByteVector): ECPrivateKey = - fromBytes(bytes, isCompressed = true) - - @tailrec - def fromBytes(bytes: ByteVector, isCompressed: Boolean): ECPrivateKey = { - + override def fromBytes(bytes: ByteVector): ECPrivateKey = { if (bytes.size == 32) - ECPrivateKeyImpl(bytes, isCompressed, ExecutionContext.global) - else if (bytes.size < 32) { - //means we need to pad the private key with 0 bytes so we have 32 bytes - ECPrivateKey.fromBytes(bytes.padLeft(32), isCompressed) - } //this is for the case when java serialies a BigInteger to 33 bytes to hold the signed num representation - else if (bytes.size == 33) - ECPrivateKey.fromBytes(bytes.slice(1, 33), isCompressed) - else + new ECPrivateKey(bytes) + else if (bytes.size == 33 && bytes.head == 0x00) { + new ECPrivateKey(bytes.slice(1, 33)) + } else throw new IllegalArgumentException( - "Private keys cannot be greater than 33 bytes in size, got: " + + "Private keys must be 32 in size, got: " + CryptoBytesUtil.encodeHex(bytes) + " which is of size: " + bytes.size) } - def fromHex(hex: String, isCompressed: Boolean): ECPrivateKey = - fromBytes(CryptoBytesUtil.decodeHex(hex), isCompressed) - def fromFieldElement(fieldElement: FieldElement): ECPrivateKey = { fieldElement.toPrivateKey } /** Generates a fresh [[org.bitcoins.crypto.ECPrivateKey ECPrivateKey]] that has not been used before. */ - def apply(): ECPrivateKey = ECPrivateKey(true) - - def apply(isCompressed: Boolean): ECPrivateKey = freshPrivateKey(isCompressed) + def apply(): ECPrivateKey = ECPrivateKey.freshPrivateKey /** Generates a fresh [[org.bitcoins.crypto.ECPrivateKey ECPrivateKey]] that has not been used before. */ - def freshPrivateKey: ECPrivateKey = freshPrivateKey(true) - - def freshPrivateKey(isCompressed: Boolean): ECPrivateKey = { - val priv = CryptoUtil.freshPrivateKey - ECPrivateKey.fromBytes(priv.bytes, isCompressed) - } + def freshPrivateKey: ECPrivateKey = CryptoUtil.freshPrivateKey } /** Created by chris on 2/16/16. + * A valid deserialized ECDSA public key. + * + * This class wraps some underlying _bytes but after checking that these _bytes are valid, + * all serializations (compressed and decompressed) of this public key are (lazily) computed + * where the decompressed version is used internally for computation and the compressed version + * is provided by the NetworkElement::bytes member. + * + * Note that 0x00 is not a valid ECPublicKey but is a valid SecpPoint meaning that if you are + * doing computations on public key (points) that may have intermediate 0x00 values, then you + * should convert using toPoint, do computation, and then convert back toPublicKey in the end. */ -sealed abstract class ECPublicKey extends BaseECKey { +case class ECPublicKey(private val _bytes: ByteVector) + extends BaseECKey + with PublicKey[ECPublicKey] { + require(isFullyValid, s"Invalid public key: ${_bytes}") - def verify(hash: HashDigest, signature: ECDigitalSignature): Boolean = - verify(hash.bytes, signature) + /** Converts this public key into the raw underlying point on secp256k1 for computation. */ + def toPoint: SecpPointFinite = SecpPoint.fromPublicKey(this) - /** Verifies if a given piece of data is signed by the - * [[org.bitcoins.crypto.ECPrivateKey ECPrivateKey]]'s corresponding - * [[org.bitcoins.crypto.ECPublicKey ECPublicKey]]. - */ - def verify(data: ByteVector, signature: ECDigitalSignature): Boolean = { - CryptoUtil.verify(this, data, signature) + override private[crypto] def fromBytes(bytes: ByteVector): ECPublicKey = { + ECPublicKey.fromBytes(bytes) } - def verify(hex: String, signature: ECDigitalSignature): Boolean = - verify(CryptoBytesUtil.decodeHex(hex), signature) - def schnorrVerify( data: ByteVector, signature: SchnorrDigitalSignature): Boolean = { @@ -200,9 +299,8 @@ sealed abstract class ECPublicKey extends BaseECKey { def schnorrComputePoint( data: ByteVector, - nonce: SchnorrNonce, - compressed: Boolean = isCompressed): ECPublicKey = { - schnorrPublicKey.computeSigPoint(data, nonce, compressed) + nonce: SchnorrNonce): ECPublicKey = { + schnorrPublicKey.computeSigPoint(data, nonce) } def schnorrPublicKey: SchnorrPublicKey = SchnorrPublicKey(bytes) @@ -224,31 +322,56 @@ sealed abstract class ECPublicKey extends BaseECKey { override def toString: String = "ECPublicKey(" + hex + ")" - /** Checks if the [[org.bitcoins.crypto.ECPublicKey ECPublicKey]] is compressed */ - def isCompressed: Boolean = bytes.size == 33 + /** Returns true only if the underlying wrapped _bytes are compressed */ + override def isCompressed: Boolean = _bytes.size == 33 - /** Checks if the [[org.bitcoins.crypto.ECPublicKey ECPublicKey]] is valid according to secp256k1 */ - def isFullyValid: Boolean = ECPublicKey.isFullyValid(bytes) + /** @inheritdoc */ + override def isFullyValid: Boolean = ECPublicKey.isFullyValid(_bytes) - /** Returns the decompressed version of this [[org.bitcoins.crypto.ECPublicKey ECPublicKey]] */ - def decompressed: ECPublicKey = - CryptoUtil.decompressed(this) - - def compressed: ECPublicKey = { - if (isCompressed || bytes == ByteVector.fromByte(0x00)) { + /** Returns this same ECPublicKey wrapping the underlying compressed _bytes. + * This function doesn't really have any use, don't use it probably. + */ + override lazy val compressed: ECPublicKey = { + if (isCompressed || _bytes == ByteVector.fromByte(0x00)) { this } else { - val key = if (bytes.length == 65) this else decompressed - val (x, y) = key.bytes.tail.splitAt(32) + val key = if (_bytes.length == 65) this else decompressed + val (x, y) = key._bytes.tail.splitAt(32) val leadByte = if (FieldElement(y).isEven) 2.toByte else 3.toByte ECPublicKey(x.+:(leadByte)) } } + /** Returns the compressed representation of this ECPublicKey */ + override def bytes: ByteVector = { + compressed._bytes + } + + /** Returns the decompressed representation of this ECPublicKey */ + def decompressedBytes: ByteVector = { + decompressed._bytes + } + + def decompressedHex: String = { + decompressedBytes.toHex + } + + /** Converts this ECPublicKey to raw ECPublicKeyBytes using the specified serialization. */ + def toPublicKeyBytes(isCompressed: Boolean = true): ECPublicKeyBytes = { + val bs = if (isCompressed) bytes else decompressedBytes + + ECPublicKeyBytes(bs) + } + + override def equals(obj: Any): Boolean = { + obj match { + case pubKey: ECPublicKey => bytes == pubKey.bytes + case _ => false + } + } + /** Adds this ECPublicKey to another as points and returns the resulting ECPublicKey. - * - * Note: if this ever becomes a bottleneck, secp256k1_ec_pubkey_combine should - * get wrapped in NativeSecp256k1 to speed things up. + * If you are adding more than two points together use CryptoUtil.combinePubKeys instead. */ def add(otherKey: ECPublicKey): ECPublicKey = CryptoUtil.add(this, otherKey) @@ -260,28 +383,15 @@ sealed abstract class ECPublicKey extends BaseECKey { object ECPublicKey extends Factory[ECPublicKey] { - private case class ECPublicKeyImpl( - override val bytes: ByteVector, - ec: ExecutionContext) - extends ECPublicKey { - //unfortunately we cannot place ANY invariants here - //because of old transactions on the blockchain that have weirdly formatted public keys. Look at example in script_tests.json - //https://github.com/bitcoin/bitcoin/blob/master/src/test/data/script_tests.json#L457 - //bitcoin core only checks CPubKey::IsValid() - //this means we can have public keys with only one byte i.e. 0x00 or no bytes. - //Eventually we would like this to be CPubKey::IsFullyValid() but since we are remaining backwards compatible - //we cannot do this. If there ever is a hard fork this would be a good thing to add. - } - override def fromBytes(bytes: ByteVector): ECPublicKey = { - ECPublicKeyImpl(bytes, ExecutionContext.global) + new ECPublicKey(bytes) } def apply(): ECPublicKey = freshPublicKey - val dummy: ECPublicKey = FieldElement.one.getPublicKey + def apply(point: SecpPointFinite): ECPublicKey = point.toPublicKey - val infinity: ECPublicKey = ECPublicKey.fromBytes(ByteVector(0x00)) + val dummy: ECPublicKey = FieldElement.one.getPublicKey /** Generates a fresh [[org.bitcoins.crypto.ECPublicKey ECPublicKey]] that has not been used before. */ def freshPublicKey: ECPublicKey = ECPrivateKey.freshPrivateKey.publicKey diff --git a/crypto/src/main/scala/org/bitcoins/crypto/ECPoint.scala b/crypto/src/main/scala/org/bitcoins/crypto/ECPoint.scala deleted file mode 100644 index a10dd35a77..0000000000 --- a/crypto/src/main/scala/org/bitcoins/crypto/ECPoint.scala +++ /dev/null @@ -1,37 +0,0 @@ -package org.bitcoins.crypto - -import scodec.bits.ByteVector - -import java.math.BigInteger - -/** Represents a point on secp256k1 elliptic curve. - */ -sealed trait ECPoint - -/** The infinity point. - */ -case object ECPointInfinity extends ECPoint - -/** A point on an elliptic curve. - * @param x - * @param y - */ -case class ECPointImpl(x: FieldElement, y: FieldElement) extends ECPoint - -object ECPoint { - - def apply(x: ByteVector, y: ByteVector): ECPoint = - ECPointImpl(FieldElement.fromBytes(x), FieldElement.fromBytes(y)) - - def apply(x: Array[Byte], y: Array[Byte]): ECPoint = - ECPointImpl(FieldElement.fromByteArray(x), FieldElement.fromByteArray(y)) - - def apply(x: BigInteger, y: BigInteger): ECPoint = - ECPointImpl(FieldElement(x), FieldElement(y)) - - def apply(x: BigInt, y: BigInt): ECPoint = - ECPointImpl(FieldElement(x), FieldElement(y)) - - def apply(x: String, y: String): ECPoint = - ECPointImpl(FieldElement.fromHex(x), FieldElement.fromHex(y)) -} diff --git a/crypto/src/main/scala/org/bitcoins/crypto/SecpPoint.scala b/crypto/src/main/scala/org/bitcoins/crypto/SecpPoint.scala new file mode 100644 index 0000000000..01162bfa84 --- /dev/null +++ b/crypto/src/main/scala/org/bitcoins/crypto/SecpPoint.scala @@ -0,0 +1,63 @@ +package org.bitcoins.crypto + +import scodec.bits.ByteVector + +import java.math.BigInteger + +/** Represents a point on the secp256k1 elliptic curve. */ +sealed trait SecpPoint extends NetworkElement { + + /** Returns the group sum of this point and the input. */ + def add(point: SecpPoint): SecpPoint = { + CryptoUtil.add(this, point) + } +} + +/** The point at infinity, this is the secp256k1 group identity element meaning + * p + 0x00 = 0x00 + p = p for any point p and + * p + (-p) = 0x00. + * + * Note that this does not correspond to a valid ECPublicKey just like + * FieldElement.zero does not correspond to a valid private key (and in fact + * 0x00 = FieldElement.zero*G). + */ +case object SecpPointInfinity extends SecpPoint { + override val bytes: ByteVector = ByteVector(0x00) +} + +/** A non-identity point, (x, y), on the secp256k1 elliptic curve. + */ +case class SecpPointFinite(x: FieldElement, y: FieldElement) extends SecpPoint { + + override def bytes: ByteVector = { + ByteVector(0x04) ++ x.bytes ++ y.bytes + } + + def toPublicKey: ECPublicKey = { + ECPublicKey(bytes) + } +} + +object SecpPoint { + + def fromPublicKey(key: ECPublicKey): SecpPointFinite = { + val (x, y) = key.decompressedBytes.tail.splitAt(32) + SecpPointFinite(FieldElement.fromBytes(x), FieldElement.fromBytes(y)) + } + + def apply(x: ByteVector, y: ByteVector): SecpPointFinite = + SecpPointFinite(FieldElement.fromBytes(x), FieldElement.fromBytes(y)) + + def apply(x: Array[Byte], y: Array[Byte]): SecpPointFinite = + SecpPointFinite(FieldElement.fromByteArray(x), + FieldElement.fromByteArray(y)) + + def apply(x: BigInteger, y: BigInteger): SecpPointFinite = + SecpPointFinite(FieldElement(x), FieldElement(y)) + + def apply(x: BigInt, y: BigInt): SecpPointFinite = + SecpPointFinite(FieldElement(x), FieldElement(y)) + + def apply(x: String, y: String): SecpPointFinite = + SecpPointFinite(FieldElement.fromHex(x), FieldElement.fromHex(y)) +} diff --git a/docs/core/psbts.md b/docs/core/psbts.md index ec66ad9fd6..f7f4223bfc 100644 --- a/docs/core/psbts.md +++ b/docs/core/psbts.md @@ -102,10 +102,10 @@ val psbtWithSigHashFlags = psbtWithUpdatedSecondInput // correctly in an application // Here we use the relevant private keys to sign the first input val privKey0 = ECPrivateKeyUtil.fromWIFToPrivateKey( - "cP53pDbR5WtAD8dYAW9hhTjuvvTVaEiQBdrz9XPrgLBeRFiyCbQr") + "cP53pDbR5WtAD8dYAW9hhTjuvvTVaEiQBdrz9XPrgLBeRFiyCbQr").toPrivateKey val privKey1 = ECPrivateKeyUtil.fromWIFToPrivateKey( - "cR6SXDoyfQrcp4piaiHE97Rsgta9mNhGTen9XeonVgwsh4iSgw6d") + "cR6SXDoyfQrcp4piaiHE97Rsgta9mNhGTen9XeonVgwsh4iSgw6d").toPrivateKey val psbtFirstSig = psbtWithSigHashFlags diff --git a/testkit-core/src/main/scala/org/bitcoins/testkitcore/util/TransactionTestUtil.scala b/testkit-core/src/main/scala/org/bitcoins/testkitcore/util/TransactionTestUtil.scala index 7bff822e32..039f2e5a04 100644 --- a/testkit-core/src/main/scala/org/bitcoins/testkitcore/util/TransactionTestUtil.scala +++ b/testkit-core/src/main/scala/org/bitcoins/testkitcore/util/TransactionTestUtil.scala @@ -6,7 +6,7 @@ import org.bitcoins.core.number.{Int32, UInt32} import org.bitcoins.core.protocol.script._ import org.bitcoins.core.protocol.transaction._ import org.bitcoins.core.psbt.PSBT -import org.bitcoins.crypto.{DoubleSha256Digest, ECPublicKey} +import org.bitcoins.crypto.{DoubleSha256Digest, ECPublicKeyBytes} /** Created by chris on 2/12/16. */ @@ -136,7 +136,7 @@ trait TransactionTestUtil { Transaction, Int, ScriptPubKey, - Seq[ECPublicKey]) = { + Seq[ECPublicKeyBytes]) = { val key1 = ECPrivateKeyUtil.fromWIFToPrivateKey( "cVLwRLTvz3BxDAWkvS3yzT9pUcTCup7kQnfT2smRjvmmm1wAP6QT") val key2 = ECPrivateKeyUtil.fromWIFToPrivateKey( @@ -146,7 +146,7 @@ trait TransactionTestUtil { (signedMultiSignatureTx, 0, multiSignatureScriptPubKey, - Seq(key1.publicKey, key2.publicKey, key3.publicKey)) + Seq(key1.publicKeyBytes, key2.publicKeyBytes, key3.publicKeyBytes)) } /** Returns a p2sh transaction with its corresponding crediting output */ diff --git a/wallet-test/src/test/scala/org/bitcoins/wallet/WalletUnitTest.scala b/wallet-test/src/test/scala/org/bitcoins/wallet/WalletUnitTest.scala index 5be5983f60..5b9b736a8c 100644 --- a/wallet-test/src/test/scala/org/bitcoins/wallet/WalletUnitTest.scala +++ b/wallet-test/src/test/scala/org/bitcoins/wallet/WalletUnitTest.scala @@ -214,7 +214,8 @@ class WalletUnitTest extends BitcoinSWalletTest { } yield { assert(signed != psbt) assert( - signed.inputMaps.head.partialSignatures.exists(_.pubKey == walletKey)) + signed.inputMaps.head.partialSignatures + .exists(_.pubKey.toPublicKey == walletKey)) } } @@ -236,7 +237,8 @@ class WalletUnitTest extends BitcoinSWalletTest { } yield { assert(signed != psbt) assert( - signed.inputMaps.head.partialSignatures.exists(_.pubKey == walletKey)) + signed.inputMaps.head.partialSignatures + .exists(_.pubKey.toPublicKey == walletKey)) } } @@ -258,7 +260,8 @@ class WalletUnitTest extends BitcoinSWalletTest { } yield { assert(signed != psbt) assert( - signed.inputMaps.head.partialSignatures.exists(_.pubKey == walletKey)) + signed.inputMaps.head.partialSignatures + .exists(_.pubKey.toPublicKey == walletKey)) } } @@ -281,7 +284,8 @@ class WalletUnitTest extends BitcoinSWalletTest { } yield { assert(signed != psbt) assert( - signed.inputMaps.head.partialSignatures.exists(_.pubKey == walletKey)) + signed.inputMaps.head.partialSignatures + .exists(_.pubKey.toPublicKey == walletKey)) } } diff --git a/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala b/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala index 760443a858..5077eebb4e 100644 --- a/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala +++ b/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala @@ -822,7 +822,10 @@ abstract class Wallet keyPaths.foldLeft(withData) { (accum, hdPath) => val sign = keyManager.toSign(hdPath) // Only sign if that key doesn't have a signature yet - if (!input.partialSignatures.exists(_.pubKey == sign.publicKey)) { + if ( + !input.partialSignatures.exists( + _.pubKey.toPublicKey == sign.publicKey) + ) { logger.debug( s"Signing input $index with key ${sign.publicKey.hex}") accum.sign(index, sign)