mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-03 18:47:38 +01:00
Schnorr js (#2805)
* Schnorr sigs for Scala.js * fix build * put BIP340 test vectors in a shared space * remove teskit dependency, fix point edge cases * fix build * add unit tests for point addition * scaladoc * cleanup * respond to the comments * Fix usage of BitcoinSLogger Co-authored-by: christewart <stewart.chris1234@gmail.com>
This commit is contained in:
parent
c6c4e83e9e
commit
911fca5825
44 changed files with 1042 additions and 392 deletions
|
@ -373,10 +373,7 @@ lazy val cryptoTest = crossProject(JVMPlatform, JSPlatform)
|
||||||
name := "bitcoin-s-crypto-test",
|
name := "bitcoin-s-crypto-test",
|
||||||
libraryDependencies ++= Deps.cryptoTest.value
|
libraryDependencies ++= Deps.cryptoTest.value
|
||||||
)
|
)
|
||||||
.dependsOn(
|
.dependsOn(crypto)
|
||||||
crypto,
|
|
||||||
testkitCore
|
|
||||||
)
|
|
||||||
|
|
||||||
lazy val cryptoTestJVM = cryptoTest.jvm
|
lazy val cryptoTestJVM = cryptoTest.jvm
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
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.testkitcore.util.BitcoinSUnitTest
|
||||||
|
|
||||||
|
class WIFEncodingTest extends BitcoinSUnitTest {
|
||||||
|
|
||||||
|
it must "determine if a private key corresponds to a compressed public key or not" in {
|
||||||
|
val compressedKey = "L1RrrnXkcKut5DEMwtDthjwRcTTwED36thyL1DebVrKuwvohjMNi"
|
||||||
|
val uncompressedKey = "93DVKyFYwSN6wEo3E2fCrFPUp17FtrtNi2Lf7n4G3garFb16CRj"
|
||||||
|
ECPrivateKeyUtil.isCompressed(compressedKey) must be(true)
|
||||||
|
ECPrivateKeyUtil.isCompressed(uncompressedKey) must be(false)
|
||||||
|
}
|
||||||
|
it must "serialize a private key to WIF and then be able to deserialize it" in {
|
||||||
|
|
||||||
|
val hex = "2cecbfb72f8d5146d7fe7e5a3f80402c6dd688652c332dff2e44618d2d3372"
|
||||||
|
val privKey = ECPrivateKey(hex)
|
||||||
|
val wif = ECPrivateKeyUtil.toWIF(privKey, TestNet3)
|
||||||
|
val privKeyFromWIF = ECPrivateKeyUtil.fromWIFToPrivateKey(wif)
|
||||||
|
privKeyFromWIF must be(privKey)
|
||||||
|
|
||||||
|
val privKeyDecompressed = ECPrivateKey.fromHex(hex, isCompressed = false)
|
||||||
|
val wifDecompressed = ECPrivateKeyUtil.toWIF(privKeyDecompressed, TestNet3)
|
||||||
|
val privKeyDecompressedFromWIF =
|
||||||
|
ECPrivateKeyUtil.fromWIFToPrivateKey(wifDecompressed)
|
||||||
|
privKeyDecompressedFromWIF must be(privKeyDecompressed)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 wif = ECPrivateKeyUtil.toWIF(privKey, TestNet3)
|
||||||
|
val privKeyFromWIF = ECPrivateKeyUtil.fromWIFToPrivateKey(wif)
|
||||||
|
privKeyFromWIF must be(privKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
it must "correctly decode a private key from WIF" in {
|
||||||
|
val privateKey = ECPrivateKeyUtil.fromWIFToPrivateKey(
|
||||||
|
"cTPg4Zc5Jis2EZXy3NXShgbn487GWBTapbU63BerLDZM3w2hQSjC")
|
||||||
|
//derived hex on bitcore's playground
|
||||||
|
privateKey.hex must be(
|
||||||
|
"ad59fb6aadf617fb0f93469741fcd9a9f48700f1d1f465ddc0f26fa7f7bfa1ac")
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
|
"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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it must "fail to parse unknown WIF networks" in {
|
||||||
|
// Litecoin privkey
|
||||||
|
val wif = "6uSDaezGtedUbYk4F9CNVXbDWw9DuEuw7czU596t1CzmeAJ77P8"
|
||||||
|
assert(ECPrivateKeyUtil.parseNetworkFromWIF(wif).isFailure)
|
||||||
|
}
|
||||||
|
|
||||||
|
it must "fail to parse non-WIF strings" in {
|
||||||
|
assert(ECPrivateKeyUtil.parseNetworkFromWIF("hello there").isFailure)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,20 +1,19 @@
|
||||||
package org.bitcoins.core.util
|
package org.bitcoins.core.util
|
||||||
|
|
||||||
import java.math.BigInteger
|
|
||||||
|
|
||||||
import org.bitcoins.core.number._
|
import org.bitcoins.core.number._
|
||||||
import org.bitcoins.core.protocol.blockchain.BlockHeader
|
import org.bitcoins.core.protocol.blockchain.BlockHeader
|
||||||
import org.bitcoins.core.protocol.blockchain.BlockHeader.TargetDifficultyHelper
|
import org.bitcoins.core.protocol.blockchain.BlockHeader.TargetDifficultyHelper
|
||||||
import org.bitcoins.crypto.FieldElement
|
import org.bitcoins.crypto.CryptoNumberUtil
|
||||||
import scodec.bits.{BitVector, ByteVector}
|
import scodec.bits.{BitVector, ByteVector}
|
||||||
|
|
||||||
|
import java.math.BigInteger
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
import scala.math.BigInt
|
import scala.math.BigInt
|
||||||
import scala.util.{Failure, Success, Try}
|
import scala.util.{Failure, Success, Try}
|
||||||
|
|
||||||
/** Created by chris on 2/8/16.
|
/** Created by chris on 2/8/16.
|
||||||
*/
|
*/
|
||||||
sealed abstract class NumberUtil {
|
sealed abstract class NumberUtil extends CryptoNumberUtil {
|
||||||
|
|
||||||
/** Takes 2^^num. */
|
/** Takes 2^^num. */
|
||||||
def pow2(exponent: Int): BigInt = {
|
def pow2(exponent: Int): BigInt = {
|
||||||
|
@ -24,43 +23,6 @@ sealed abstract class NumberUtil {
|
||||||
BigInt(1) << exponent
|
BigInt(1) << exponent
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Converts a sequence of bytes to a **big endian** unsigned integer */
|
|
||||||
def toUnsignedInt(bytes: ByteVector): BigInt = {
|
|
||||||
toUnsignedInt(bytes.toArray)
|
|
||||||
}
|
|
||||||
|
|
||||||
def uintToFieldElement(bytes: ByteVector): FieldElement = {
|
|
||||||
FieldElement(toUnsignedInt(bytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Converts a sequence of bytes to a **big endian** unsigned integer */
|
|
||||||
def toUnsignedInt(bytes: Array[Byte]): BigInt = {
|
|
||||||
BigInt(new BigInteger(1, bytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Takes a hex string and parses it to a [[scala.math.BigInt BigInt]]. */
|
|
||||||
def toBigInt(hex: String): BigInt = toBigInt(BytesUtil.decodeHex(hex))
|
|
||||||
|
|
||||||
/** Converts a sequence of bytes to twos complement signed number. */
|
|
||||||
def toBigInt(bytes: ByteVector): BigInt = {
|
|
||||||
//BigInt interprets the number as an unsigned number then applies the given
|
|
||||||
//sign in front of that number, therefore if we have a negative number we need to invert it
|
|
||||||
//since twos complement is an inverted number representation for negative numbers
|
|
||||||
//see [[https://en.wikipedia.org/wiki/Two%27s_complement]]
|
|
||||||
if (bytes.isEmpty) BigInt(0)
|
|
||||||
//check if sign bit is set
|
|
||||||
else if ((0x80.toByte & bytes.head) != 0) {
|
|
||||||
val invertedBytes = bytes.tail.map(b => (b ^ 0xff.toByte).toByte)
|
|
||||||
val firstByteInverted = (bytes.head ^ 0xff.toByte).toByte
|
|
||||||
val num = firstByteInverted +: invertedBytes
|
|
||||||
BigInt(-1, num.toArray) - 1
|
|
||||||
} else {
|
|
||||||
val firstBitOff = (0x7f & bytes.head).toByte
|
|
||||||
val num = firstBitOff +: bytes.tail
|
|
||||||
BigInt(num.toArray)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Converts a sequence of [[scala.Byte Byte]] to a [[scala.Int Int]]. */
|
/** Converts a sequence of [[scala.Byte Byte]] to a [[scala.Int Int]]. */
|
||||||
def toInt(bytes: ByteVector): Int = toBigInt(bytes).toInt
|
def toInt(bytes: ByteVector): Int = toBigInt(bytes).toInt
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
|
class BCryptoECDigitalSignatureTest extends BitcoinSCryptoTest {
|
||||||
|
|
||||||
|
behavior of "BCryptoECDigitalSignatureTest"
|
||||||
|
|
||||||
|
it must "be able to generate valid signatures with bcrypto" in {
|
||||||
|
forAll(CryptoGenerators.privateKey, CryptoGenerators.sha256Digest) {
|
||||||
|
case (privKey: ECPrivateKey, hash: Sha256Digest) =>
|
||||||
|
val sig = BCryptoCryptoRuntime.sign(privKey, hash.bytes)
|
||||||
|
val pubKey = privKey.publicKey
|
||||||
|
|
||||||
|
assert(pubKey.verify(hash, sig))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,8 @@
|
||||||
package org.bitcoins.crypto
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
import org.scalatest.flatspec.AnyFlatSpec
|
|
||||||
import org.scalatest.matchers.must.Matchers
|
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
class HashingTest extends AnyFlatSpec with Matchers {
|
class HashingTest extends BitcoinSCryptoTest {
|
||||||
|
|
||||||
private lazy val lines = Vector(
|
private lazy val lines = Vector(
|
||||||
// rnd,sha1(rnd),sha256(rnd),ripeMd160(rnd),sha256Hash160(rnd),hmac(rnd,sha256)
|
// rnd,sha1(rnd),sha256(rnd),ripeMd160(rnd),sha256Hash160(rnd),hmac(rnd,sha256)
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
package org.bitcoins.crypto
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
import org.scalatest.flatspec.AnyFlatSpec
|
import scodec.bits.ByteVector
|
||||||
import org.scalatest.matchers.must.Matchers
|
|
||||||
|
|
||||||
class KeysTest extends AnyFlatSpec with Matchers {
|
class KeysTest extends BitcoinSCryptoTest {
|
||||||
|
|
||||||
it must "generate keys" in {
|
it must "generate keys" in {
|
||||||
val privkey = ECPrivateKey.freshPrivateKey
|
val privkey = ECPrivateKey.freshPrivateKey
|
||||||
|
@ -18,4 +17,62 @@ class KeysTest extends AnyFlatSpec with Matchers {
|
||||||
assert(!BCryptoCryptoRuntime.isValidPubKey(privkey.bytes))
|
assert(!BCryptoCryptoRuntime.isValidPubKey(privkey.bytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it must "be able to compress/decompress public keys" in {
|
||||||
|
val privkey = ECPrivateKey.freshPrivateKey
|
||||||
|
assert(BCryptoCryptoRuntime.secKeyVerify(privkey.bytes))
|
||||||
|
assert(privkey.isCompressed)
|
||||||
|
|
||||||
|
val pubkey = BCryptoCryptoRuntime.toPublicKey(privkey, isCompressed = false)
|
||||||
|
assert(BCryptoCryptoRuntime.isValidPubKey(pubkey.bytes))
|
||||||
|
assert(!pubkey.isCompressed)
|
||||||
|
|
||||||
|
val compressed = privkey.publicKey
|
||||||
|
assert(BCryptoCryptoRuntime.isValidPubKey(compressed.bytes))
|
||||||
|
assert(compressed.isCompressed)
|
||||||
|
|
||||||
|
val converted = ECPublicKey.fromBytes(
|
||||||
|
BCryptoCryptoRuntime.publicKeyConvert(pubkey.bytes, compressed = true))
|
||||||
|
assert(BCryptoCryptoRuntime.isValidPubKey(converted.bytes))
|
||||||
|
assert(converted.isCompressed)
|
||||||
|
|
||||||
|
val decompressed = ECPublicKey.fromBytes(
|
||||||
|
BCryptoCryptoRuntime.publicKeyConvert(compressed.bytes,
|
||||||
|
compressed = false))
|
||||||
|
assert(BCryptoCryptoRuntime.isValidPubKey(decompressed.bytes))
|
||||||
|
assert(!decompressed.isCompressed)
|
||||||
|
|
||||||
|
assert(pubkey.bytes != converted.bytes)
|
||||||
|
assert(compressed.bytes == converted.bytes)
|
||||||
|
assert(compressed.bytes != decompressed.bytes)
|
||||||
|
assert(pubkey.bytes == decompressed.bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val inf = ECPublicKey.fromHex("00")
|
||||||
|
|
||||||
|
it must "be able to add infinity points" in {
|
||||||
|
val privkey = ECPrivateKey.freshPrivateKey
|
||||||
|
val pubkey1 = privkey.publicKey
|
||||||
|
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 res1 = BCryptoCryptoRuntime.add(pubkey1, pubkey2)
|
||||||
|
|
||||||
|
assert(res1 == inf)
|
||||||
|
|
||||||
|
val decompressedPubkey1 = ECPublicKey.fromBytes(
|
||||||
|
BCryptoCryptoRuntime.publicKeyConvert(pubkey1.bytes, compressed = false))
|
||||||
|
|
||||||
|
val decompressedPubkey2 = ECPublicKey.fromBytes(
|
||||||
|
BCryptoCryptoRuntime.publicKeyConvert(pubkey2.bytes, compressed = false))
|
||||||
|
|
||||||
|
val res2 =
|
||||||
|
BCryptoCryptoRuntime.add(decompressedPubkey1, decompressedPubkey2)
|
||||||
|
|
||||||
|
assert(res2 == inf)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
package org.bitcoins.crypto
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
import org.scalatest.flatspec.AnyFlatSpec
|
class RandomTest extends BitcoinSCryptoTest {
|
||||||
import org.scalatest.matchers.must.Matchers
|
|
||||||
|
|
||||||
class RandomTest extends AnyFlatSpec with Matchers {
|
|
||||||
|
|
||||||
it should "generate random bytes" in {
|
it should "generate random bytes" in {
|
||||||
val rnd = 1.to(16).map(_ => BCryptoCryptoRuntime.randomBytes(32))
|
val rnd = 1.to(16).map(_ => BCryptoCryptoRuntime.randomBytes(32))
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
|
import org.scalatest.Assertion
|
||||||
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
|
import scala.util.{Failure, Success, Try}
|
||||||
|
|
||||||
|
class SigningTest extends BitcoinSCryptoTest {
|
||||||
|
|
||||||
|
it must "be able to sign and verify signatures" in {
|
||||||
|
val privkey = ECPrivateKey.freshPrivateKey
|
||||||
|
val pubkey = privkey.publicKey
|
||||||
|
val msg = BCryptoCryptoRuntime.randomBytes(32)
|
||||||
|
val sig = privkey.sign(msg)
|
||||||
|
assert(pubkey.verify(msg, sig))
|
||||||
|
}
|
||||||
|
|
||||||
|
it must "be able to sign and verify Schnorr signatures" in {
|
||||||
|
val privkey = ECPrivateKey.freshPrivateKey
|
||||||
|
val pubkey = privkey.publicKey
|
||||||
|
val msg = BCryptoCryptoRuntime.randomBytes(32)
|
||||||
|
val sig = privkey.schnorrSign(msg)
|
||||||
|
assert(pubkey.schnorrVerify(msg, sig))
|
||||||
|
}
|
||||||
|
|
||||||
|
it must "pass the BIP 340 test-vectors with bcrypto" in {
|
||||||
|
BIP340TestVectors.vectors.foreach {
|
||||||
|
case (index, secKeyOpt, pubKey, auxRandOpt, msg, sig, result, comment) =>
|
||||||
|
test(index = index,
|
||||||
|
secKeyOpt = secKeyOpt,
|
||||||
|
pubKey = pubKey,
|
||||||
|
auxRandOpt = auxRandOpt,
|
||||||
|
msg = msg,
|
||||||
|
sig = sig,
|
||||||
|
result = result,
|
||||||
|
comment = comment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def test(
|
||||||
|
index: Int,
|
||||||
|
secKeyOpt: Option[String],
|
||||||
|
pubKey: String,
|
||||||
|
auxRandOpt: Option[String],
|
||||||
|
msg: String,
|
||||||
|
sig: String,
|
||||||
|
result: Boolean,
|
||||||
|
comment: String): Assertion = {
|
||||||
|
val pkT = Try(SchnorrPublicKey(pubKey))
|
||||||
|
val msgBytes = ByteVector.fromHex(msg).get
|
||||||
|
val schnorrSigT = Try(SchnorrDigitalSignature(sig))
|
||||||
|
|
||||||
|
(pkT, schnorrSigT) match {
|
||||||
|
case (Success(pk), Success(schnorrSig)) =>
|
||||||
|
(secKeyOpt, auxRandOpt) match {
|
||||||
|
case (Some(secKeyStr), Some(auxRandStr)) =>
|
||||||
|
val secKey = ECPrivateKey(secKeyStr)
|
||||||
|
assert(secKey.schnorrPublicKey == pk)
|
||||||
|
val auxRand = ByteVector.fromHex(auxRandStr).get
|
||||||
|
testSign(index, secKey, auxRand, msgBytes, schnorrSig)
|
||||||
|
case _ => ()
|
||||||
|
}
|
||||||
|
|
||||||
|
testVerify(index, pk, msgBytes, schnorrSig, result, comment)
|
||||||
|
case (Failure(_), _) |
|
||||||
|
(_, Failure(_)) => // Must be verify only test resulting in false
|
||||||
|
assert(secKeyOpt.isEmpty)
|
||||||
|
assert(auxRandOpt.isEmpty)
|
||||||
|
assert(!result, s"Test $index failed to parse signature: $comment")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def testSign(
|
||||||
|
index: Int,
|
||||||
|
secKey: ECPrivateKey,
|
||||||
|
auxRand: ByteVector,
|
||||||
|
msg: ByteVector,
|
||||||
|
expectedSig: SchnorrDigitalSignature): Assertion = {
|
||||||
|
val bcryptoSig =
|
||||||
|
BCryptoCryptoRuntime.schnorrSign(msg, secKey, auxRand)
|
||||||
|
assert(bcryptoSig == expectedSig,
|
||||||
|
s"Test $index failed signing for Bouncy Castle")
|
||||||
|
}
|
||||||
|
|
||||||
|
def testVerify(
|
||||||
|
index: Int,
|
||||||
|
pubKey: SchnorrPublicKey,
|
||||||
|
msg: ByteVector,
|
||||||
|
sig: SchnorrDigitalSignature,
|
||||||
|
expectedResult: Boolean,
|
||||||
|
comment: String): Assertion = {
|
||||||
|
val bcryptoResult =
|
||||||
|
BCryptoCryptoRuntime.schnorrVerify(msg, pubKey, sig)
|
||||||
|
assert(bcryptoResult == expectedResult,
|
||||||
|
s"Test $index failed verification for Bouncy Castle: $comment")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,12 +1,10 @@
|
||||||
package org.bitcoins.crypto
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
import org.bitcoins.testkitcore.gen.{CryptoGenerators, NumberGenerator}
|
|
||||||
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
|
|
||||||
import org.scalacheck.Gen
|
import org.scalacheck.Gen
|
||||||
import org.scalatest.compatible.Assertion
|
import org.scalatest.compatible.Assertion
|
||||||
import scodec.bits.{ByteVector, HexStringSyntax}
|
import scodec.bits.{ByteVector, HexStringSyntax}
|
||||||
|
|
||||||
class AesCryptTest extends BitcoinSUnitTest {
|
class AesCryptTest extends BitcoinSCryptoTest {
|
||||||
behavior of "AesEncrypt"
|
behavior of "AesEncrypt"
|
||||||
|
|
||||||
val password = AesPassword.fromNonEmptyString("PASSWORD")
|
val password = AesPassword.fromNonEmptyString("PASSWORD")
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
package org.bitcoins.crypto
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
|
|
||||||
import org.scalatest.Assertion
|
import org.scalatest.Assertion
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
import scala.util.{Failure, Success, Try}
|
import scala.util.{Failure, Success, Try}
|
||||||
|
|
||||||
/** Tests from https://github.com/sipa/bips/blob/bip-taproot/bip-0340/test-vectors.csv */
|
/** Tests from https://github.com/sipa/bips/blob/bip-taproot/bip-0340/test-vectors.csv */
|
||||||
class BouncyCastleBIP340Test extends BitcoinSUnitTest {
|
class BouncyCastleBIP340Test extends BitcoinSCryptoTest {
|
||||||
behavior of "Schnorr Signing"
|
behavior of "Schnorr Signing"
|
||||||
|
|
||||||
def testSign(
|
def testSign(
|
||||||
|
@ -76,35 +75,9 @@ class BouncyCastleBIP340Test extends BitcoinSUnitTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def toOpt(str: String): Option[String] = {
|
|
||||||
if (str.isEmpty) {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
it must "pass the BIP 340 test-vectors with both secp256k1 bindings and bouncy castle" in {
|
it must "pass the BIP 340 test-vectors with both secp256k1 bindings and bouncy castle" in {
|
||||||
val bufferedSource =
|
BIP340TestVectors.vectors.foreach {
|
||||||
io.Source.fromURL(getClass.getResource("/bip340-test-vectors.csv"))
|
case (index, secKeyOpt, pubKey, auxRandOpt, msg, sig, result, comment) =>
|
||||||
try {
|
|
||||||
val lines = bufferedSource.getLines()
|
|
||||||
val _ = lines.next()
|
|
||||||
for (line <- bufferedSource.getLines()) {
|
|
||||||
val testVec = line.split(",").map(_.trim)
|
|
||||||
val index = testVec.head.toInt
|
|
||||||
val secKeyOpt = toOpt(testVec(1))
|
|
||||||
val pubKey = testVec(2)
|
|
||||||
val auxRandOpt = toOpt(testVec(3))
|
|
||||||
val msg = testVec(4)
|
|
||||||
val sig = testVec(5)
|
|
||||||
val result = testVec(6).toBoolean
|
|
||||||
val comment = if (testVec.length > 7) {
|
|
||||||
testVec(7)
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
|
|
||||||
test(index = index,
|
test(index = index,
|
||||||
secKeyOpt = secKeyOpt,
|
secKeyOpt = secKeyOpt,
|
||||||
pubKey = pubKey,
|
pubKey = pubKey,
|
||||||
|
@ -113,9 +86,6 @@ class BouncyCastleBIP340Test extends BitcoinSUnitTest {
|
||||||
sig = sig,
|
sig = sig,
|
||||||
result = result,
|
result = result,
|
||||||
comment = comment)
|
comment = comment)
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
bufferedSource.close()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
package org.bitcoins.crypto
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
import org.bitcoins.testkitcore.gen.CryptoGenerators
|
class BouncyCastleECDigitalSignatureTest extends BitcoinSCryptoTest {
|
||||||
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
|
|
||||||
|
|
||||||
class BouncyCastleECDigitalSignatureTest extends BitcoinSUnitTest {
|
|
||||||
|
|
||||||
behavior of "BouncyCastleECDigitalSignatureTest"
|
behavior of "BouncyCastleECDigitalSignatureTest"
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
package org.bitcoins.crypto
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
import org.bitcoins.testkitcore.gen.{CryptoGenerators, NumberGenerator}
|
|
||||||
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
|
|
||||||
import org.scalacheck.Gen
|
import org.scalacheck.Gen
|
||||||
import org.scalatest.{Outcome, Succeeded}
|
import org.scalatest.{Outcome, Succeeded}
|
||||||
|
|
||||||
class BouncyCastleSecp256k1Test extends BitcoinSUnitTest {
|
class BouncyCastleSecp256k1Test extends BitcoinSCryptoTest {
|
||||||
|
|
||||||
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
|
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
|
||||||
generatorDrivenConfigNewCode
|
generatorDrivenConfigNewCode
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
package org.bitcoins.crypto
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
import org.bitcoins.testkitcore.gen.CryptoGenerators
|
|
||||||
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
|
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
/** Created by chris on 3/22/16.
|
/** Created by chris on 3/22/16.
|
||||||
*/
|
*/
|
||||||
class ECDigitalSignatureTest extends BitcoinSUnitTest {
|
class ECDigitalSignatureTest extends BitcoinSCryptoTest {
|
||||||
|
|
||||||
implicit override val generatorDrivenConfig = generatorDrivenConfigNewCode
|
implicit override val generatorDrivenConfig = generatorDrivenConfigNewCode
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,6 @@
|
||||||
package org.bitcoins.crypto
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
import org.bitcoins.core.config.{MainNet, RegTest, SigNet, TestNet3}
|
class ECPrivateKeyTest extends BitcoinSCryptoTest {
|
||||||
import org.bitcoins.core.crypto.ECPrivateKeyUtil
|
|
||||||
import org.bitcoins.testkitcore.gen.{
|
|
||||||
ChainParamsGenerator,
|
|
||||||
CryptoGenerators,
|
|
||||||
NumberGenerator
|
|
||||||
}
|
|
||||||
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
|
|
||||||
|
|
||||||
class ECPrivateKeyTest extends BitcoinSUnitTest {
|
|
||||||
it must "create a private key from its hex representation" in {
|
it must "create a private key from its hex representation" in {
|
||||||
val privateKeyHex =
|
val privateKeyHex =
|
||||||
"180cb41c7c600be951b5d3d0a7334acc7506173875834f7a6c4c786a28fcbb19"
|
"180cb41c7c600be951b5d3d0a7334acc7506173875834f7a6c4c786a28fcbb19"
|
||||||
|
@ -17,70 +8,10 @@ class ECPrivateKeyTest extends BitcoinSUnitTest {
|
||||||
key.hex must be(privateKeyHex)
|
key.hex must be(privateKeyHex)
|
||||||
}
|
}
|
||||||
|
|
||||||
it must "determine if a private key corresponds to a compressed public key or not" in {
|
|
||||||
val compressedKey = "L1RrrnXkcKut5DEMwtDthjwRcTTwED36thyL1DebVrKuwvohjMNi"
|
|
||||||
val uncompressedKey = "93DVKyFYwSN6wEo3E2fCrFPUp17FtrtNi2Lf7n4G3garFb16CRj"
|
|
||||||
ECPrivateKeyUtil.isCompressed(compressedKey) must be(true)
|
|
||||||
ECPrivateKeyUtil.isCompressed(uncompressedKey) must be(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
it must "create a fresh private key" in {
|
it must "create a fresh private key" in {
|
||||||
ECPrivateKey() must not equal (ECPrivateKey())
|
ECPrivateKey() must not equal (ECPrivateKey())
|
||||||
}
|
}
|
||||||
|
|
||||||
it must "serialize a private key to WIF and then be able to deserialize it" in {
|
|
||||||
|
|
||||||
val hex = "2cecbfb72f8d5146d7fe7e5a3f80402c6dd688652c332dff2e44618d2d3372"
|
|
||||||
val privKey = ECPrivateKey(hex)
|
|
||||||
val wif = ECPrivateKeyUtil.toWIF(privKey, TestNet3)
|
|
||||||
val privKeyFromWIF = ECPrivateKeyUtil.fromWIFToPrivateKey(wif)
|
|
||||||
privKeyFromWIF must be(privKey)
|
|
||||||
|
|
||||||
val privKeyDecompressed = ECPrivateKey.fromHex(hex, isCompressed = false)
|
|
||||||
val wifDecompressed = ECPrivateKeyUtil.toWIF(privKeyDecompressed, TestNet3)
|
|
||||||
val privKeyDecompressedFromWIF =
|
|
||||||
ECPrivateKeyUtil.fromWIFToPrivateKey(wifDecompressed)
|
|
||||||
privKeyDecompressedFromWIF must be(privKeyDecompressed)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 wif = ECPrivateKeyUtil.toWIF(privKey, TestNet3)
|
|
||||||
val privKeyFromWIF = ECPrivateKeyUtil.fromWIFToPrivateKey(wif)
|
|
||||||
privKeyFromWIF must be(privKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
it must "correctly decode a private key from WIF" in {
|
|
||||||
val privateKey = ECPrivateKeyUtil.fromWIFToPrivateKey(
|
|
||||||
"cTPg4Zc5Jis2EZXy3NXShgbn487GWBTapbU63BerLDZM3w2hQSjC")
|
|
||||||
//derived hex on bitcore's playground
|
|
||||||
privateKey.hex must be(
|
|
||||||
"ad59fb6aadf617fb0f93469741fcd9a9f48700f1d1f465ddc0f26fa7f7bfa1ac")
|
|
||||||
}
|
|
||||||
|
|
||||||
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(
|
|
||||||
"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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
it must "have serialization symmetry" in {
|
it must "have serialization symmetry" in {
|
||||||
forAll(CryptoGenerators.privateKey) { privKey =>
|
forAll(CryptoGenerators.privateKey) { privKey =>
|
||||||
assert(ECPrivateKey(privKey.hex) == privKey)
|
assert(ECPrivateKey(privKey.hex) == privKey)
|
||||||
|
@ -95,16 +26,6 @@ class ECPrivateKeyTest extends BitcoinSUnitTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
it must "fail to parse unknown WIF networks" in {
|
|
||||||
// Litecoin privkey
|
|
||||||
val wif = "6uSDaezGtedUbYk4F9CNVXbDWw9DuEuw7czU596t1CzmeAJ77P8"
|
|
||||||
assert(ECPrivateKeyUtil.parseNetworkFromWIF(wif).isFailure)
|
|
||||||
}
|
|
||||||
|
|
||||||
it must "fail to parse non-WIF strings" in {
|
|
||||||
assert(ECPrivateKeyUtil.parseNetworkFromWIF("hello there").isFailure)
|
|
||||||
}
|
|
||||||
|
|
||||||
it must "not serialize a ECPrivateKey toString" in {
|
it must "not serialize a ECPrivateKey toString" in {
|
||||||
ECPrivateKey().toString must be("Masked(ECPrivateKeyImpl)")
|
ECPrivateKey().toString must be("Masked(ECPrivateKeyImpl)")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
package org.bitcoins.crypto
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
import org.bitcoins.testkitcore.gen.CryptoGenerators
|
|
||||||
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
|
|
||||||
import scodec.bits._
|
import scodec.bits._
|
||||||
|
|
||||||
import scala.concurrent.ExecutionContext
|
import scala.concurrent.ExecutionContext
|
||||||
|
|
||||||
class ECPublicKeyTest extends BitcoinSUnitTest {
|
class ECPublicKeyTest extends BitcoinSCryptoTest {
|
||||||
|
|
||||||
it must "be able to decompress keys" in {
|
it must "be able to decompress keys" in {
|
||||||
val uncompressed =
|
val uncompressed =
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
package org.bitcoins.crypto
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
import org.bitcoins.testkitcore.gen.CryptoGenerators
|
|
||||||
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
|
|
||||||
|
|
||||||
/** Public key tests specific to the JVM */
|
/** Public key tests specific to the JVM */
|
||||||
class JvmECPublicKeyTest extends BitcoinSUnitTest {
|
class JvmECPublicKeyTest extends BitcoinSCryptoTest {
|
||||||
|
|
||||||
behavior of "JVMECPublicKeytest"
|
behavior of "JVMECPublicKeytest"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
|
class SignWithEntropyTest extends BitcoinSCryptoAsyncTest {
|
||||||
|
|
||||||
|
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
|
||||||
|
generatorDrivenConfigNewCode
|
||||||
|
|
||||||
|
//ECPrivateKey implements the sign interface
|
||||||
|
//so just use it for testing purposes
|
||||||
|
val privKey: Sign = ECPrivateKey.freshPrivateKey
|
||||||
|
val pubKey: ECPublicKey = privKey.publicKey
|
||||||
|
|
||||||
|
behavior of "SignWithEntropy"
|
||||||
|
|
||||||
|
it must "sign arbitrary pieces of data with arbitrary entropy correctly" in {
|
||||||
|
forAllAsync(CryptoGenerators.sha256Digest, CryptoGenerators.sha256Digest) {
|
||||||
|
case (hash, entropy) =>
|
||||||
|
val sigF = privKey.signWithEntropyFunction(hash.bytes, entropy.bytes)
|
||||||
|
|
||||||
|
sigF.map { sig =>
|
||||||
|
assert(pubKey.verify(hash.bytes, sig))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
|
object BIP340TestVectors {
|
||||||
|
|
||||||
|
val vectors: Vector[
|
||||||
|
(
|
||||||
|
Int,
|
||||||
|
Option[String],
|
||||||
|
String,
|
||||||
|
Option[String],
|
||||||
|
String,
|
||||||
|
String,
|
||||||
|
Boolean,
|
||||||
|
String)] = Vector(
|
||||||
|
"index,secret key,public key,aux_rand,message,signature,verification result,comment",
|
||||||
|
"0,0000000000000000000000000000000000000000000000000000000000000003,F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9,0000000000000000000000000000000000000000000000000000000000000000,0000000000000000000000000000000000000000000000000000000000000000,E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0,TRUE,",
|
||||||
|
"1,B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,0000000000000000000000000000000000000000000000000000000000000001,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A,TRUE,",
|
||||||
|
"2,C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9,DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8,C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906,7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C,5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7,TRUE,",
|
||||||
|
"3,0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710,25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3,TRUE,test fails if msg is reduced modulo p or n",
|
||||||
|
"4,,D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9,,4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703,00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B07D28308D7F4,TRUE,",
|
||||||
|
"5,,EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,public key not on the curve",
|
||||||
|
"6,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A14602975563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAFA34B1AC553E2,FALSE,has_even_y(R) is false",
|
||||||
|
"7,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA5134FCCDB2BD,FALSE,negated message",
|
||||||
|
"8,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834FF0D0C2E6DA6,FALSE,negated s value",
|
||||||
|
"9,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,0000000000000000000000000000000000000000000000000000000000000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C6425BD186051,FALSE,sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 0",
|
||||||
|
"10,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,00000000000000000000000000000000000000000000000000000000000000017615FBAF5AE28864013C099742DEADB4DBA87F11AC6754F93780D5A1837CF197,FALSE,sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 1",
|
||||||
|
"11,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,sig[0:32] is not an X coordinate on the curve",
|
||||||
|
"12,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,sig[0:32] is equal to field size",
|
||||||
|
"13,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141,FALSE,sig[32:64] is equal to curve order",
|
||||||
|
"14,,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,public key is not a valid X coordinate because it exceeds the field size"
|
||||||
|
).tail.map { line =>
|
||||||
|
val testVec = line.split(",").map(_.trim)
|
||||||
|
val index = testVec.head.toInt
|
||||||
|
val secKeyOpt = toOpt(testVec(1))
|
||||||
|
val pubKey = testVec(2)
|
||||||
|
val auxRandOpt = toOpt(testVec(3))
|
||||||
|
val msg = testVec(4)
|
||||||
|
val sig = testVec(5)
|
||||||
|
val result = testVec(6).toBoolean
|
||||||
|
val comment = if (testVec.length > 7) {
|
||||||
|
testVec(7)
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
(index, secKeyOpt, pubKey, auxRandOpt, msg, sig, result, comment)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def toOpt(str: String): Option[String] = {
|
||||||
|
if (str.isEmpty) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
|
import org.scalacheck.Gen
|
||||||
|
import org.scalactic.anyvals.PosInt
|
||||||
|
import org.scalatest.flatspec.{AnyFlatSpec, AsyncFlatSpec}
|
||||||
|
import org.scalatest.matchers.must.Matchers
|
||||||
|
import org.scalatest.{Assertion, BeforeAndAfter, BeforeAndAfterAll, Succeeded}
|
||||||
|
import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
|
||||||
|
|
||||||
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
|
|
||||||
|
trait BitcoinSCryptoTest
|
||||||
|
extends AnyFlatSpec
|
||||||
|
with BeforeAndAfter
|
||||||
|
with BeforeAndAfterAll
|
||||||
|
with Matchers
|
||||||
|
with ScalaCheckPropertyChecks {
|
||||||
|
|
||||||
|
implicit def executionContext: ExecutionContext =
|
||||||
|
scala.concurrent.ExecutionContext.global
|
||||||
|
|
||||||
|
def generatorDrivenConfigNewCode: PropertyCheckConfiguration = {
|
||||||
|
customGenDrivenConfig(BitcoinSCryptoTest.NEW_CODE_EXECUTIONS)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the generator driven tests to perform the given amount of execs */
|
||||||
|
def customGenDrivenConfig(executions: Int): PropertyCheckConfiguration = {
|
||||||
|
PropertyCheckConfiguration(
|
||||||
|
minSuccessful = PosInt.from(executions).get,
|
||||||
|
minSize = PosInt.from(executions).get,
|
||||||
|
workers = 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
trait BitcoinSCryptoAsyncTest
|
||||||
|
extends AsyncFlatSpec
|
||||||
|
with BeforeAndAfter
|
||||||
|
with BeforeAndAfterAll
|
||||||
|
with Matchers
|
||||||
|
with ScalaCheckPropertyChecks {
|
||||||
|
|
||||||
|
implicit override def executionContext =
|
||||||
|
scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
|
||||||
|
def generatorDrivenConfigNewCode: PropertyCheckConfiguration = {
|
||||||
|
customGenDrivenConfig(BitcoinSCryptoTest.NEW_CODE_EXECUTIONS)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the generator driven tests to perform the given amount of execs */
|
||||||
|
def customGenDrivenConfig(executions: Int): PropertyCheckConfiguration = {
|
||||||
|
PropertyCheckConfiguration(
|
||||||
|
minSuccessful = PosInt.from(executions).get,
|
||||||
|
minSize = PosInt.from(executions).get,
|
||||||
|
workers = 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
def forAllAsync[A](gen: Gen[A])(
|
||||||
|
func: A => Future[Assertion]): Future[Assertion] = {
|
||||||
|
|
||||||
|
val samples = 1
|
||||||
|
.to(generatorDrivenConfig.minSize)
|
||||||
|
.map(_ => gen.sample)
|
||||||
|
.toVector
|
||||||
|
.flatten
|
||||||
|
|
||||||
|
val testRunsF = Future.sequence(samples.map(func))
|
||||||
|
|
||||||
|
checkRunResults(testRunsF)
|
||||||
|
}
|
||||||
|
|
||||||
|
def forAllAsync[A, B](genA: Gen[A], genB: Gen[B])(
|
||||||
|
func: (A, B) => Future[Assertion]): Future[Assertion] = {
|
||||||
|
|
||||||
|
val samples = 1
|
||||||
|
.to(generatorDrivenConfig.minSize)
|
||||||
|
.map(_ => (genA.sample, genB.sample))
|
||||||
|
.toVector
|
||||||
|
.collect { case (Some(a), Some(b)) =>
|
||||||
|
(a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
val testRunsF = Future.sequence(samples.map(x => func(x._1, x._2)))
|
||||||
|
|
||||||
|
checkRunResults(testRunsF)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def checkRunResults(testRunsF: Future[Vector[Assertion]]) = {
|
||||||
|
for {
|
||||||
|
testRuns <- testRunsF
|
||||||
|
} yield {
|
||||||
|
val succeeded = testRuns.filter(_ == Succeeded)
|
||||||
|
val failed = testRuns.filterNot(_ == Succeeded)
|
||||||
|
if (succeeded.size < generatorDrivenConfig.minSuccessful) {
|
||||||
|
failed.headOption.getOrElse(fail())
|
||||||
|
} else {
|
||||||
|
succeed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object BitcoinSCryptoTest {
|
||||||
|
|
||||||
|
/** The number of times new code
|
||||||
|
* should be executed in a property based test
|
||||||
|
*/
|
||||||
|
val NEW_CODE_EXECUTIONS = 100
|
||||||
|
|
||||||
|
/** The number of times old code should be executed
|
||||||
|
* in a property based test
|
||||||
|
*/
|
||||||
|
val OLD_CODE_EXECUTIONS = 20
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,175 @@
|
||||||
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
|
import org.bitcoins.crypto
|
||||||
|
import org.scalacheck.Gen
|
||||||
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
|
/** Created by chris on 6/22/16.
|
||||||
|
*/
|
||||||
|
sealed abstract class CryptoGenerators {
|
||||||
|
|
||||||
|
def privateKey: Gen[ECPrivateKey] = Gen.delay(ECPrivateKey())
|
||||||
|
|
||||||
|
def fieldElement: Gen[FieldElement] = privateKey.map(_.fieldElement)
|
||||||
|
|
||||||
|
def smallFieldElement: Gen[FieldElement] =
|
||||||
|
NumberGenerator
|
||||||
|
.bytevector(30)
|
||||||
|
.map(bytes => FieldElement(ByteVector.fill(2)(0) ++ bytes))
|
||||||
|
|
||||||
|
def reallySmallFieldElement: Gen[FieldElement] =
|
||||||
|
NumberGenerator
|
||||||
|
.bytevector(15)
|
||||||
|
.map(bytes => FieldElement(ByteVector.fill(17)(0) ++ bytes))
|
||||||
|
|
||||||
|
def largeFieldElement: Gen[FieldElement] =
|
||||||
|
NumberGenerator
|
||||||
|
.bytevector(30)
|
||||||
|
.map(bytes => FieldElement(ByteVector.fill(2)(Byte.MinValue) ++ bytes))
|
||||||
|
|
||||||
|
def nonZeroFieldElement: Gen[FieldElement] =
|
||||||
|
nonZeroPrivKey.map(_.fieldElement)
|
||||||
|
|
||||||
|
/** Generates a random non-zero private key */
|
||||||
|
def nonZeroPrivKey: Gen[ECPrivateKey] =
|
||||||
|
privateKey.filter(_.bytes.toArray.exists(_ != 0.toByte))
|
||||||
|
|
||||||
|
def schnorrNonce: Gen[SchnorrNonce] =
|
||||||
|
nonZeroPrivKey.map(_.publicKey.bytes.tail).map(SchnorrNonce.fromBytes)
|
||||||
|
|
||||||
|
def schnorrPublicKey: Gen[SchnorrPublicKey] =
|
||||||
|
publicKey.map(_.schnorrPublicKey)
|
||||||
|
|
||||||
|
/** Generate a sequence of private keys
|
||||||
|
* @param num maximum number of keys to generate
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
def privateKeySeq(num: Int): Gen[Seq[ECPrivateKey]] =
|
||||||
|
Gen.listOfN(num, privateKey)
|
||||||
|
|
||||||
|
/** Generates a sequence of private keys, and determines an amount of 'required' private keys
|
||||||
|
* that a transaction needs to be signed with
|
||||||
|
* @param num the maximum number of keys to generate
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
def privateKeySeqWithRequiredSigs(num: Int): Gen[(Seq[ECPrivateKey], Int)] = {
|
||||||
|
if (num <= 0) {
|
||||||
|
Gen.const((Nil, 0))
|
||||||
|
} else {
|
||||||
|
val privateKeys = privateKeySeq(num)
|
||||||
|
for {
|
||||||
|
keys <- privateKeys
|
||||||
|
requiredSigs <- Gen.choose(0, keys.size - 1)
|
||||||
|
} yield (keys, requiredSigs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generates a random number of private keys less than 15.
|
||||||
|
* Also generates a random 'requiredSigs' number that a transaction needs to be signed with
|
||||||
|
*/
|
||||||
|
def privateKeySeqWithRequiredSigs: Gen[(Seq[ECPrivateKey], Int)] =
|
||||||
|
for {
|
||||||
|
num <- Gen.choose(0, 15)
|
||||||
|
keysAndRequiredSigs <- privateKeySeqWithRequiredSigs(num)
|
||||||
|
} yield keysAndRequiredSigs
|
||||||
|
|
||||||
|
/** A generator with 7 or less private keys -- useful for creating smaller scripts */
|
||||||
|
def smallPrivateKeySeqWithRequiredSigs: Gen[(Seq[ECPrivateKey], Int)] =
|
||||||
|
for {
|
||||||
|
num <- Gen.choose(0, 7)
|
||||||
|
keysAndRequiredSigs <- privateKeySeqWithRequiredSigs(num)
|
||||||
|
} yield keysAndRequiredSigs
|
||||||
|
|
||||||
|
/** Generates a random public key */
|
||||||
|
def publicKey: Gen[ECPublicKey] =
|
||||||
|
for {
|
||||||
|
privKey <- privateKey
|
||||||
|
} yield privKey.publicKey
|
||||||
|
|
||||||
|
/** Generates a random digital signature */
|
||||||
|
def digitalSignature: Gen[ECDigitalSignature] =
|
||||||
|
for {
|
||||||
|
privKey <- privateKey
|
||||||
|
hash <- CryptoGenerators.doubleSha256Digest
|
||||||
|
} yield privKey.sign(hash)
|
||||||
|
|
||||||
|
def schnorrDigitalSignature: Gen[SchnorrDigitalSignature] = {
|
||||||
|
for {
|
||||||
|
privKey <- privateKey
|
||||||
|
hash <- CryptoGenerators.doubleSha256Digest
|
||||||
|
} yield privKey.schnorrSign(hash.bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
def adaptorSignature: Gen[ECAdaptorSignature] = {
|
||||||
|
for {
|
||||||
|
tweakedNonce <- publicKey
|
||||||
|
untweakedNonce <- publicKey
|
||||||
|
adaptedS <- fieldElement
|
||||||
|
proofS <- fieldElement
|
||||||
|
proofE <- fieldElement
|
||||||
|
} yield {
|
||||||
|
ECAdaptorSignature(tweakedNonce, adaptedS, untweakedNonce, proofS, proofE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def sha256Digest: Gen[Sha256Digest] =
|
||||||
|
for {
|
||||||
|
bytes <- NumberGenerator.bytevector
|
||||||
|
digest = CryptoUtil.sha256(bytes)
|
||||||
|
} yield digest
|
||||||
|
|
||||||
|
def sha256DigestBE: Gen[Sha256DigestBE] = {
|
||||||
|
sha256Digest.map(_.flip)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generates a random [[DoubleSha256Digest DoubleSha256Digest]] */
|
||||||
|
def doubleSha256Digest: Gen[DoubleSha256Digest] =
|
||||||
|
for {
|
||||||
|
key <- privateKey
|
||||||
|
digest = CryptoUtil.doubleSHA256(key.bytes)
|
||||||
|
} yield digest
|
||||||
|
|
||||||
|
def doubleSha256DigestBE: Gen[DoubleSha256DigestBE] = {
|
||||||
|
doubleSha256Digest.map(_.flip)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generates a sequence of [[DoubleSha256Digest DoubleSha256Digest]]
|
||||||
|
* @param num the number of digets to generate
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
def doubleSha256DigestSeq(num: Int): Gen[Seq[DoubleSha256Digest]] =
|
||||||
|
Gen.listOfN(num, doubleSha256Digest)
|
||||||
|
|
||||||
|
/** Generates a random [[Sha256Hash160Digest Sha256Hash160Digest]] */
|
||||||
|
def sha256Hash160Digest: Gen[Sha256Hash160Digest] =
|
||||||
|
for {
|
||||||
|
pubKey <- publicKey
|
||||||
|
hash = CryptoUtil.sha256Hash160(pubKey.bytes)
|
||||||
|
} yield hash
|
||||||
|
|
||||||
|
def aesKey128Bit: Gen[AesKey] = Gen.delay(AesKey.get128Bit())
|
||||||
|
def aesKey192Bit: Gen[AesKey] = Gen.delay(AesKey.get192Bit())
|
||||||
|
def aesKey256Bit: Gen[AesKey] = Gen.delay(AesKey.get256Bit())
|
||||||
|
|
||||||
|
def aesKey: Gen[AesKey] =
|
||||||
|
Gen.oneOf(aesKey128Bit, aesKey192Bit, aesKey256Bit)
|
||||||
|
|
||||||
|
def aesPassword: Gen[AesPassword] =
|
||||||
|
Gen.alphaStr.suchThat(_.nonEmpty).map(AesPassword.fromNonEmptyString(_))
|
||||||
|
|
||||||
|
def aesIV: Gen[AesIV] = Gen.delay(AesIV.random)
|
||||||
|
|
||||||
|
def aesEncryptedData: Gen[AesEncryptedData] =
|
||||||
|
for {
|
||||||
|
cipher <- NumberGenerator.bytevector.suchThat(_.nonEmpty)
|
||||||
|
iv <- aesIV
|
||||||
|
} yield crypto.AesEncryptedData(cipherText = cipher, iv)
|
||||||
|
|
||||||
|
def genKey: Gen[SipHashKey] =
|
||||||
|
Gen
|
||||||
|
.listOfN(16, NumberGenerator.byte)
|
||||||
|
.map(ByteVector(_))
|
||||||
|
.map(SipHashKey(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
object CryptoGenerators extends CryptoGenerators
|
|
@ -1,19 +1,11 @@
|
||||||
package org.bitcoins.crypto
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
import org.bitcoins.core.util.BytesUtil
|
|
||||||
import org.bitcoins.testkitcore.gen.{CryptoGenerators, NumberGenerator}
|
|
||||||
import org.scalacheck.Gen
|
import org.scalacheck.Gen
|
||||||
import org.scalatest.flatspec.AnyFlatSpec
|
|
||||||
import org.scalatest.matchers.must.Matchers
|
|
||||||
import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
|
|
||||||
import scodec.bits._
|
import scodec.bits._
|
||||||
|
|
||||||
/** Created by chris on 1/26/16.
|
/** Created by chris on 1/26/16.
|
||||||
*/
|
*/
|
||||||
class CryptoUtilTest
|
class CryptoUtilTest extends BitcoinSCryptoTest {
|
||||||
extends AnyFlatSpec
|
|
||||||
with Matchers
|
|
||||||
with ScalaCheckPropertyChecks {
|
|
||||||
|
|
||||||
"CryptoUtil" must "perform a SHA-1 hash" in {
|
"CryptoUtil" must "perform a SHA-1 hash" in {
|
||||||
val hash = CryptoUtil.sha1(hex"")
|
val hash = CryptoUtil.sha1(hex"")
|
||||||
|
@ -49,7 +41,7 @@ class CryptoUtilTest
|
||||||
|
|
||||||
it must "perform a single SHA256 hash on a bit vector" in {
|
it must "perform a single SHA256 hash on a bit vector" in {
|
||||||
val binary = bin"010001101110010001101110"
|
val binary = bin"010001101110010001101110"
|
||||||
val strBytes = BytesUtil.decodeHex(binary.toHex)
|
val strBytes = CryptoBytesUtil.decodeHex(binary.toHex)
|
||||||
|
|
||||||
val shaStrBytes = CryptoUtil.sha256(strBytes)
|
val shaStrBytes = CryptoUtil.sha256(strBytes)
|
||||||
val shaBinary = CryptoUtil.sha256(binary)
|
val shaBinary = CryptoUtil.sha256(binary)
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
package org.bitcoins.crypto
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
import org.bitcoins.core.util.NumberUtil
|
|
||||||
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
|
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
|
import scala.math.BigInt
|
||||||
|
|
||||||
/** Created by chris on 3/23/16.
|
/** Created by chris on 3/23/16.
|
||||||
*/
|
*/
|
||||||
class DERSignatureUtilTest extends BitcoinSUnitTest {
|
class DERSignatureUtilTest extends BitcoinSCryptoTest {
|
||||||
|
|
||||||
val p2shSignature = ECDigitalSignature(
|
val p2shSignature = ECDigitalSignature(
|
||||||
"304402205b7d2c2f177ae76cfbbf14d589c113b0b35db753d305d5562dd0b61cbf366cfb02202e56f93c4f08a27f986cd424ffc48a462c3202c4902104d4d0ff98ed28f4bf8001")
|
"304402205b7d2c2f177ae76cfbbf14d589c113b0b35db753d305d5562dd0b61cbf366cfb02202e56f93c4f08a27f986cd424ffc48a462c3202c4902104d4d0ff98ed28f4bf8001")
|
||||||
|
@ -37,30 +37,30 @@ class DERSignatureUtilTest extends BitcoinSUnitTest {
|
||||||
it must "retrieve the (r,s) values for a p2sh signature in bitcoin" in {
|
it must "retrieve the (r,s) values for a p2sh signature in bitcoin" in {
|
||||||
val (r, s) = DERSignatureUtil.decodeSignature(p2shSignature)
|
val (r, s) = DERSignatureUtil.decodeSignature(p2shSignature)
|
||||||
r must be(
|
r must be(
|
||||||
NumberUtil.toBigInt(
|
CryptoNumberUtil.toBigInt(
|
||||||
"5b7d2c2f177ae76cfbbf14d589c113b0b35db753d305d5562dd0b61cbf366cfb"))
|
"5b7d2c2f177ae76cfbbf14d589c113b0b35db753d305d5562dd0b61cbf366cfb"))
|
||||||
s must be(
|
s must be(
|
||||||
NumberUtil.toBigInt(
|
CryptoNumberUtil.toBigInt(
|
||||||
"2e56f93c4f08a27f986cd424ffc48a462c3202c4902104d4d0ff98ed28f4bf80"))
|
"2e56f93c4f08a27f986cd424ffc48a462c3202c4902104d4d0ff98ed28f4bf80"))
|
||||||
}
|
}
|
||||||
|
|
||||||
it must "retrieve the (r,s) values for a p2pkh signature in bitcoin" in {
|
it must "retrieve the (r,s) values for a p2pkh signature in bitcoin" in {
|
||||||
val (r, s) = DERSignatureUtil.decodeSignature(p2pkhSignature)
|
val (r, s) = DERSignatureUtil.decodeSignature(p2pkhSignature)
|
||||||
r must be(
|
r must be(
|
||||||
NumberUtil.toBigInt(
|
CryptoNumberUtil.toBigInt(
|
||||||
"16ffdbb7c57634903c5e018fcfc48d59f4e37dc4bc3bbc9ba4e6ee39150bca03"))
|
"16ffdbb7c57634903c5e018fcfc48d59f4e37dc4bc3bbc9ba4e6ee39150bca03"))
|
||||||
s must be(
|
s must be(
|
||||||
NumberUtil.toBigInt(
|
CryptoNumberUtil.toBigInt(
|
||||||
"119c2241a931819bc1a75d3596e4029d803d1cd6de123bf8a1a1a2c3665e1fac"))
|
"119c2241a931819bc1a75d3596e4029d803d1cd6de123bf8a1a1a2c3665e1fac"))
|
||||||
}
|
}
|
||||||
|
|
||||||
it must "retrieve the (r,s) values from a p2pk signature in bitcoin" in {
|
it must "retrieve the (r,s) values from a p2pk signature in bitcoin" in {
|
||||||
val (r, s) = DERSignatureUtil.decodeSignature(p2pkSignature)
|
val (r, s) = DERSignatureUtil.decodeSignature(p2pkSignature)
|
||||||
r must be(
|
r must be(
|
||||||
NumberUtil.toBigInt(
|
CryptoNumberUtil.toBigInt(
|
||||||
"0a5c6163f07b8d3b013c4d1d6dba25e780b39658d79ba37af7057a3b7f15ffa1"))
|
"0a5c6163f07b8d3b013c4d1d6dba25e780b39658d79ba37af7057a3b7f15ffa1"))
|
||||||
s must be(
|
s must be(
|
||||||
NumberUtil.toBigInt(
|
CryptoNumberUtil.toBigInt(
|
||||||
"1fd9b4eaa9943f734928b99a83592c2e7bf342ea2680f6a2bb705167966b7420"))
|
"1fd9b4eaa9943f734928b99a83592c2e7bf342ea2680f6a2bb705167966b7420"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
package org.bitcoins.crypto
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
import org.bitcoins.testkitcore.gen.CryptoGenerators
|
class FieldElementTest extends BitcoinSCryptoTest {
|
||||||
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
|
|
||||||
|
|
||||||
class FieldElementTest extends BitcoinSUnitTest {
|
|
||||||
|
|
||||||
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
|
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
|
||||||
generatorDrivenConfigNewCode
|
generatorDrivenConfigNewCode
|
|
@ -1,10 +1,8 @@
|
||||||
package org.bitcoins.crypto
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
import org.bitcoins.testkitcore.gen.{CryptoGenerators, NumberGenerator}
|
|
||||||
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
|
|
||||||
import scodec.bits._
|
import scodec.bits._
|
||||||
|
|
||||||
class HashDigestTest extends BitcoinSUnitTest {
|
class HashDigestTest extends BitcoinSCryptoTest {
|
||||||
behavior of "DoubleSha256Digest"
|
behavior of "DoubleSha256Digest"
|
||||||
|
|
||||||
it must "be constructable from 32 bytes" in {
|
it must "be constructable from 32 bytes" in {
|
|
@ -0,0 +1,84 @@
|
||||||
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
|
import org.scalacheck.Arbitrary.arbitrary
|
||||||
|
import org.scalacheck.Gen
|
||||||
|
import scodec.bits.{BitVector, ByteVector}
|
||||||
|
|
||||||
|
/** Created by chris on 6/16/16.
|
||||||
|
*/
|
||||||
|
trait NumberGenerator {
|
||||||
|
|
||||||
|
def positiveShort: Gen[Short] = {
|
||||||
|
Gen.chooseNum[Short](0, Short.MaxValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a generator that generates positive long numbers */
|
||||||
|
def positiveLongs: Gen[Long] = Gen.choose(0, Long.MaxValue)
|
||||||
|
|
||||||
|
/** Integers between 0 and Int.MaxValue
|
||||||
|
*/
|
||||||
|
val positiveInts: Gen[Int] = Gen.choose(0, Int.MaxValue)
|
||||||
|
|
||||||
|
/** Integers between Int.MinValue and -1
|
||||||
|
*/
|
||||||
|
val negativeInts: Gen[Int] = Gen.choose(Int.MinValue, -1)
|
||||||
|
|
||||||
|
/** Random integers
|
||||||
|
*/
|
||||||
|
val ints: Gen[Int] = Gen.choose(Int.MinValue, Int.MaxValue)
|
||||||
|
|
||||||
|
/** Creates a generator for positive longs without the number zero */
|
||||||
|
def positiveLongsNoZero: Gen[Long] = Gen.choose(1, Long.MaxValue)
|
||||||
|
|
||||||
|
/** Creates a number generator that generates negative long numbers */
|
||||||
|
def negativeLongs: Gen[Long] = Gen.choose(Long.MinValue, -1)
|
||||||
|
|
||||||
|
/** Chooses a BigInt in the ranges of 0 <= bigInt < 2^^64 */
|
||||||
|
def bigInts: Gen[BigInt] =
|
||||||
|
Gen
|
||||||
|
.chooseNum(Long.MinValue, Long.MaxValue)
|
||||||
|
.map(x => BigInt(x) + BigInt(2).pow(63))
|
||||||
|
|
||||||
|
def positiveBigInts: Gen[BigInt] = bigInts.filter(_ >= 0)
|
||||||
|
|
||||||
|
def bigIntsUInt64Range: Gen[BigInt] =
|
||||||
|
positiveBigInts.filter(_ < (BigInt(1) << 64))
|
||||||
|
|
||||||
|
/** Generates an arbitrary [[scala.Byte Byte]] in Scala */
|
||||||
|
def byte: Gen[Byte] = arbitrary[Byte]
|
||||||
|
|
||||||
|
/** Generates an arbitrary [[scodec.bits.ByteVector ByteVector]] */
|
||||||
|
def bytevector: Gen[ByteVector] = Gen.listOf(byte).map(ByteVector(_))
|
||||||
|
|
||||||
|
def bytevector(length: Int): Gen[ByteVector] =
|
||||||
|
Gen.listOfN(length, byte).map(ByteVector(_))
|
||||||
|
|
||||||
|
/** Generates a 100 byte sequence */
|
||||||
|
def bytes: Gen[List[Byte]] =
|
||||||
|
for {
|
||||||
|
num <- Gen.choose(0, 100)
|
||||||
|
b <- bytes(num)
|
||||||
|
} yield b
|
||||||
|
|
||||||
|
/** Generates the number of bytes specified by num
|
||||||
|
* @param num
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
def bytes(num: Int): Gen[List[Byte]] = Gen.listOfN(num, byte)
|
||||||
|
|
||||||
|
/** Generates a random boolean */
|
||||||
|
def bool: Gen[Boolean] =
|
||||||
|
for {
|
||||||
|
num <- Gen.choose(0, 1)
|
||||||
|
} yield num == 1
|
||||||
|
|
||||||
|
/** Generates a bit vector */
|
||||||
|
def bitVector: Gen[BitVector] =
|
||||||
|
for {
|
||||||
|
n <- Gen.choose(0, 100)
|
||||||
|
vector <- Gen.listOfN(n, bool)
|
||||||
|
} yield BitVector.bits(vector)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object NumberGenerator extends NumberGenerator
|
|
@ -1,10 +1,6 @@
|
||||||
package org.bitcoins.crypto
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
import org.bitcoins.core.util.NumberUtil
|
class SchnorrDigitalSignatureTest extends BitcoinSCryptoTest {
|
||||||
import org.bitcoins.testkitcore.gen.{CryptoGenerators, NumberGenerator}
|
|
||||||
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
|
|
||||||
|
|
||||||
class SchnorrDigitalSignatureTest extends BitcoinSUnitTest {
|
|
||||||
|
|
||||||
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
|
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
|
||||||
generatorDrivenConfigNewCode
|
generatorDrivenConfigNewCode
|
||||||
|
@ -109,8 +105,8 @@ class SchnorrDigitalSignatureTest extends BitcoinSUnitTest {
|
||||||
sig2.rx.bytes ++ privKey.schnorrPublicKey.bytes ++ message2
|
sig2.rx.bytes ++ privKey.schnorrPublicKey.bytes ++ message2
|
||||||
val e2Bytes = CryptoUtil.sha256SchnorrChallenge(bytesToHash2).bytes
|
val e2Bytes = CryptoUtil.sha256SchnorrChallenge(bytesToHash2).bytes
|
||||||
|
|
||||||
val e1 = NumberUtil.uintToFieldElement(e1Bytes)
|
val e1 = CryptoNumberUtil.uintToFieldElement(e1Bytes)
|
||||||
val e2 = NumberUtil.uintToFieldElement(e2Bytes)
|
val e2 = CryptoNumberUtil.uintToFieldElement(e2Bytes)
|
||||||
|
|
||||||
val k = nonce.nonceKey.fieldElement
|
val k = nonce.nonceKey.fieldElement
|
||||||
val x = privKey.schnorrKey.fieldElement
|
val x = privKey.schnorrKey.fieldElement
|
|
@ -1,9 +1,6 @@
|
||||||
package org.bitcoins.crypto
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
import org.bitcoins.testkitcore.gen.CryptoGenerators
|
class SchnorrNonceTest extends BitcoinSCryptoTest {
|
||||||
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
|
|
||||||
|
|
||||||
class SchnorrNonceTest extends BitcoinSUnitTest {
|
|
||||||
|
|
||||||
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
|
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
|
||||||
generatorDrivenConfigNewCode
|
generatorDrivenConfigNewCode
|
|
@ -1,9 +1,6 @@
|
||||||
package org.bitcoins.crypto
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
import org.bitcoins.testkitcore.gen.CryptoGenerators
|
class SchnorrPublicKeyTest extends BitcoinSCryptoTest {
|
||||||
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
|
|
||||||
|
|
||||||
class SchnorrPublicKeyTest extends BitcoinSUnitTest {
|
|
||||||
|
|
||||||
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
|
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
|
||||||
generatorDrivenConfigNewCode
|
generatorDrivenConfigNewCode
|
|
@ -1,9 +1,6 @@
|
||||||
package org.bitcoins.crypto
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
import org.bitcoins.testkitcore.gen.CryptoGenerators
|
class SignTest extends BitcoinSCryptoAsyncTest {
|
||||||
import org.bitcoins.testkitcore.util.BitcoinSJvmTest
|
|
||||||
|
|
||||||
class SignTest extends BitcoinSJvmTest {
|
|
||||||
|
|
||||||
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
|
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
|
||||||
generatorDrivenConfigNewCode
|
generatorDrivenConfigNewCode
|
||||||
|
@ -25,17 +22,6 @@ class SignTest extends BitcoinSJvmTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
it must "sign arbitrary pieces of data with arbitrary entropy correctly" in {
|
|
||||||
forAllAsync(CryptoGenerators.sha256Digest, CryptoGenerators.sha256Digest) {
|
|
||||||
case (hash, entropy) =>
|
|
||||||
val sigF = privKey.signWithEntropyFunction(hash.bytes, entropy.bytes)
|
|
||||||
|
|
||||||
sigF.map { sig =>
|
|
||||||
assert(pubKey.verify(hash.bytes, sig))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
it must "sign arbitrary data correctly with low R values" in {
|
it must "sign arbitrary data correctly with low R values" in {
|
||||||
forAllAsync(CryptoGenerators.sha256Digest) { hash =>
|
forAllAsync(CryptoGenerators.sha256Digest) { hash =>
|
||||||
val bytes = hash.bytes
|
val bytes = hash.bytes
|
|
@ -4,9 +4,9 @@ import scodec.bits.ByteVector
|
||||||
|
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import scala.scalajs.js
|
import scala.scalajs.js
|
||||||
import scala.scalajs.js.typedarray._
|
|
||||||
import scala.scalajs.js.JSStringOps._
|
import scala.scalajs.js.JSStringOps._
|
||||||
import scala.scalajs.js.UnicodeNormalizationForm
|
import scala.scalajs.js.typedarray._
|
||||||
|
import scala.scalajs.js.{JavaScriptException, UnicodeNormalizationForm}
|
||||||
|
|
||||||
/** This is an implementation of [[CryptoRuntime]] that defaults to
|
/** This is an implementation of [[CryptoRuntime]] that defaults to
|
||||||
* Bcrypto (https://github.com/bcoin-org/bcrypto) when possible.
|
* Bcrypto (https://github.com/bcoin-org/bcrypto) when possible.
|
||||||
|
@ -23,7 +23,9 @@ trait BCryptoCryptoRuntime extends CryptoRuntime {
|
||||||
private lazy val sha1 = new SHA1
|
private lazy val sha1 = new SHA1
|
||||||
private lazy val sha256 = SHA256Factory.create()
|
private lazy val sha256 = SHA256Factory.create()
|
||||||
private lazy val hmac = SHA512.hmac.apply().asInstanceOf[HMAC]
|
private lazy val hmac = SHA512.hmac.apply().asInstanceOf[HMAC]
|
||||||
private lazy val ecdsa = new ECDSA("SECP256K1", sha256, sha256, null)
|
|
||||||
|
private lazy val ecdsa =
|
||||||
|
new ECDSA("SECP256K1", sha256, js.constructorOf[SHA256], null)
|
||||||
|
|
||||||
private lazy val randomBytesFunc: Int => ByteVector =
|
private lazy val randomBytesFunc: Int => ByteVector =
|
||||||
try {
|
try {
|
||||||
|
@ -149,49 +151,57 @@ trait BCryptoCryptoRuntime extends CryptoRuntime {
|
||||||
|
|
||||||
override def tweakMultiply(
|
override def tweakMultiply(
|
||||||
publicKey: ECPublicKey,
|
publicKey: ECPublicKey,
|
||||||
tweak: FieldElement): ECPublicKey = ???
|
tweak: FieldElement): ECPublicKey = {
|
||||||
|
val keyBuffer = ecdsa.publicKeyTweakMul(publicKey.bytes, tweak.bytes, true)
|
||||||
|
ECPublicKey.fromBytes(keyBuffer)
|
||||||
|
}
|
||||||
|
|
||||||
override def add(pk1: ECPrivateKey, pk2: ECPrivateKey): ECPrivateKey = ???
|
def publicKeyConvert(buffer: ByteVector, compressed: Boolean): ByteVector =
|
||||||
|
publicKeyConvert(toNodeBuffer(buffer), compressed)
|
||||||
|
|
||||||
override def add(bytes: ByteVector, pk2: ECPrivateKey): ByteVector = ???
|
def publicKeyConvert(buffer: Buffer, compressed: Boolean): Buffer =
|
||||||
|
ecdsa.publicKeyConvert(buffer, compressed)
|
||||||
|
|
||||||
override def add(pk1: ECPublicKey, pk2: ECPublicKey): ECPublicKey = ???
|
override def add(pk1: ECPublicKey, pk2: ECPublicKey): ECPublicKey = {
|
||||||
|
try {
|
||||||
|
val keyBuffer =
|
||||||
|
ecdsa.publicKeyCombine(js.Array(pk1.bytes, pk2.bytes), true)
|
||||||
|
ECPublicKey.fromBytes(keyBuffer)
|
||||||
|
} catch {
|
||||||
|
case ex: JavaScriptException =>
|
||||||
|
// check for infinity
|
||||||
|
val k1: Buffer =
|
||||||
|
if (pk1.isCompressed) pk1.bytes
|
||||||
|
else publicKeyConvert(pk1.bytes, compressed = true)
|
||||||
|
|
||||||
|
val k2: Buffer =
|
||||||
|
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.fromHex("00")
|
||||||
|
} else {
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override def pubKeyTweakAdd(
|
override def pubKeyTweakAdd(
|
||||||
pubkey: ECPublicKey,
|
pubkey: ECPublicKey,
|
||||||
privkey: ECPrivateKey): ECPublicKey = ???
|
privkey: ECPrivateKey): ECPublicKey = {
|
||||||
|
val keyBuffer = ecdsa.publicKeyTweakAdd(pubkey.bytes, privkey.bytes, true)
|
||||||
|
ECPublicKey.fromBytes(keyBuffer)
|
||||||
|
}
|
||||||
|
|
||||||
override def isValidPubKey(bytes: ByteVector): Boolean =
|
override def isValidPubKey(bytes: ByteVector): Boolean =
|
||||||
ecdsa.publicKeyVerify(bytes)
|
ecdsa.publicKeyVerify(bytes)
|
||||||
|
|
||||||
override def isFullyValidWithBouncyCastle(bytes: ByteVector): Boolean = ???
|
override def isFullyValidWithBouncyCastle(bytes: ByteVector): Boolean = ???
|
||||||
|
|
||||||
override def schnorrSign(
|
|
||||||
dataToSign: ByteVector,
|
|
||||||
privateKey: ECPrivateKey,
|
|
||||||
auxRand: ByteVector): SchnorrDigitalSignature = {
|
|
||||||
val buffer = ecdsa.schnorrSign(dataToSign, privateKey.bytes) //, auxRand)
|
|
||||||
SchnorrDigitalSignature.fromBytes(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
override def schnorrSignWithNonce(
|
|
||||||
dataToSign: ByteVector,
|
|
||||||
privateKey: ECPrivateKey,
|
|
||||||
nonceKey: ECPrivateKey): SchnorrDigitalSignature = ???
|
|
||||||
|
|
||||||
override def schnorrVerify(
|
|
||||||
data: ByteVector,
|
|
||||||
schnorrPubKey: SchnorrPublicKey,
|
|
||||||
signature: SchnorrDigitalSignature): Boolean = {
|
|
||||||
ecdsa.schnorrVerify(data, signature.bytes, schnorrPubKey.bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
override def schnorrComputeSigPoint(
|
|
||||||
data: ByteVector,
|
|
||||||
nonce: SchnorrNonce,
|
|
||||||
pubKey: SchnorrPublicKey,
|
|
||||||
compressed: Boolean): ECPublicKey = ???
|
|
||||||
|
|
||||||
override def adaptorSign(
|
override def adaptorSign(
|
||||||
key: ECPrivateKey,
|
key: ECPrivateKey,
|
||||||
adaptorPoint: ECPublicKey,
|
adaptorPoint: ECPublicKey,
|
||||||
|
@ -252,6 +262,22 @@ trait BCryptoCryptoRuntime extends CryptoRuntime {
|
||||||
s"Need $len bytes for buffer -> bytevector conversion")
|
s"Need $len bytes for buffer -> bytevector conversion")
|
||||||
ByteVector(accum.map(_.toByte))
|
ByteVector(accum.map(_.toByte))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override def decodePoint(bytes: ByteVector): ECPoint = {
|
||||||
|
if (bytes.size == 1 && bytes(0) == 0x00) {
|
||||||
|
ECPointInfinity
|
||||||
|
} else {
|
||||||
|
val decoded = ecdsa.curve
|
||||||
|
.applyDynamic("decodePoint")(toNodeBuffer(bytes))
|
||||||
|
.asInstanceOf[Point]
|
||||||
|
|
||||||
|
if (decoded.isInfinity())
|
||||||
|
ECPointInfinity
|
||||||
|
else
|
||||||
|
ECPoint(new BigInteger(decoded.getX().toString()),
|
||||||
|
new BigInteger(decoded.getY().toString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object BCryptoCryptoRuntime extends BCryptoCryptoRuntime
|
object BCryptoCryptoRuntime extends BCryptoCryptoRuntime
|
||||||
|
|
|
@ -11,7 +11,7 @@ import scala.scalajs.js.annotation._
|
||||||
class ECDSA(
|
class ECDSA(
|
||||||
name: String = "SECP256K1",
|
name: String = "SECP256K1",
|
||||||
hash: SHA256 = new SHA256,
|
hash: SHA256 = new SHA256,
|
||||||
xof: SHA256 = new SHA256,
|
xof: js.Dynamic = js.constructorOf[SHA256],
|
||||||
pre: String = null)
|
pre: String = null)
|
||||||
extends js.Object {
|
extends js.Object {
|
||||||
|
|
||||||
|
@ -19,10 +19,24 @@ class ECDSA(
|
||||||
|
|
||||||
def privateKeyVerify(key: Buffer): Boolean = js.native
|
def privateKeyVerify(key: Buffer): Boolean = js.native
|
||||||
|
|
||||||
|
def privateKeyTweakMul(key: Buffer, tweak: Buffer): Buffer =
|
||||||
|
js.native
|
||||||
|
|
||||||
def publicKeyCreate(key: Buffer, compressed: Boolean): Buffer = js.native
|
def publicKeyCreate(key: Buffer, compressed: Boolean): Buffer = js.native
|
||||||
|
|
||||||
def publicKeyVerify(key: Buffer): Boolean = js.native
|
def publicKeyVerify(key: Buffer): Boolean = js.native
|
||||||
|
|
||||||
|
def publicKeyConvert(key: Buffer, compressed: Boolean): Buffer = js.native
|
||||||
|
|
||||||
|
def publicKeyTweakMul(key: Buffer, tweak: Buffer, compress: Boolean): Buffer =
|
||||||
|
js.native
|
||||||
|
|
||||||
|
def publicKeyTweakAdd(key: Buffer, tweak: Buffer, compress: Boolean): Buffer =
|
||||||
|
js.native
|
||||||
|
|
||||||
|
def publicKeyCombine(keys: js.Array[Buffer], compress: Boolean): Buffer =
|
||||||
|
js.native
|
||||||
|
|
||||||
def sign(msg: Buffer, key: Buffer): Buffer = js.native
|
def sign(msg: Buffer, key: Buffer): Buffer = js.native
|
||||||
|
|
||||||
def verify(msg: Buffer, sig: Buffer, key: Buffer): Boolean = js.native
|
def verify(msg: Buffer, sig: Buffer, key: Buffer): Boolean = js.native
|
||||||
|
@ -33,10 +47,5 @@ class ECDSA(
|
||||||
param: Byte,
|
param: Byte,
|
||||||
compress: Boolean): Buffer = js.native
|
compress: Boolean): Buffer = js.native
|
||||||
|
|
||||||
var schnorr: Schnorr = js.native
|
val curve: js.Dynamic = js.native
|
||||||
|
|
||||||
def schnorrSign(msg: Buffer, key: Buffer): Buffer = js.native
|
|
||||||
|
|
||||||
def schnorrVerify(msg: Buffer, sig: Buffer, key: Buffer): Boolean =
|
|
||||||
js.native
|
|
||||||
}
|
}
|
||||||
|
|
12
crypto/.js/src/main/scala/org/bitcoins/crypto/Point.scala
Normal file
12
crypto/.js/src/main/scala/org/bitcoins/crypto/Point.scala
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
|
import scala.scalajs.js
|
||||||
|
import scala.scalajs.js.annotation.JSImport
|
||||||
|
|
||||||
|
@js.native
|
||||||
|
@JSImport("bcrypto/lib/js/elliptic.js", JSImport.Default)
|
||||||
|
class Point extends js.Object {
|
||||||
|
def getX(): js.BigInt = js.native
|
||||||
|
def getY(): js.BigInt = js.native
|
||||||
|
def isInfinity(): Boolean = js.native
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ import org.bouncycastle.crypto.params.{
|
||||||
ECPublicKeyParameters
|
ECPublicKeyParameters
|
||||||
}
|
}
|
||||||
import org.bouncycastle.crypto.signers.{ECDSASigner, HMacDSAKCalculator}
|
import org.bouncycastle.crypto.signers.{ECDSASigner, HMacDSAKCalculator}
|
||||||
import org.bouncycastle.math.ec.{ECCurve, ECPoint}
|
import org.bouncycastle.math.ec.ECCurve
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
|
@ -15,7 +15,7 @@ import scala.util.Try
|
||||||
object BouncyCastleUtil {
|
object BouncyCastleUtil {
|
||||||
|
|
||||||
private val curve: ECCurve = BouncyCastleCryptoParams.curve.getCurve
|
private val curve: ECCurve = BouncyCastleCryptoParams.curve.getCurve
|
||||||
private val G: ECPoint = BouncyCastleCryptoParams.curve.getG
|
private val G = BouncyCastleCryptoParams.curve.getG
|
||||||
|
|
||||||
private def getBigInteger(bytes: ByteVector): BigInteger = {
|
private def getBigInteger(bytes: ByteVector): BigInteger = {
|
||||||
new BigInteger(1, bytes.toArray)
|
new BigInteger(1, bytes.toArray)
|
||||||
|
@ -26,16 +26,18 @@ object BouncyCastleUtil {
|
||||||
decodePubKey(point, publicKey.isCompressed)
|
decodePubKey(point, publicKey.isCompressed)
|
||||||
}
|
}
|
||||||
|
|
||||||
def decodePoint(bytes: ByteVector): ECPoint = {
|
private[crypto] def decodePoint(
|
||||||
|
bytes: ByteVector): org.bouncycastle.math.ec.ECPoint = {
|
||||||
curve.decodePoint(bytes.toArray)
|
curve.decodePoint(bytes.toArray)
|
||||||
}
|
}
|
||||||
|
|
||||||
def decodePoint(pubKey: ECPublicKey): ECPoint = {
|
private[crypto] def decodePoint(
|
||||||
|
pubKey: ECPublicKey): org.bouncycastle.math.ec.ECPoint = {
|
||||||
decodePoint(pubKey.bytes)
|
decodePoint(pubKey.bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
def decodePubKey(
|
private[crypto] def decodePubKey(
|
||||||
point: ECPoint,
|
point: org.bouncycastle.math.ec.ECPoint,
|
||||||
isCompressed: Boolean = true): ECPublicKey = {
|
isCompressed: Boolean = true): ECPublicKey = {
|
||||||
val bytes = point.getEncoded(isCompressed)
|
val bytes = point.getEncoded(isCompressed)
|
||||||
ECPublicKey.fromBytes(ByteVector(bytes))
|
ECPublicKey.fromBytes(ByteVector(bytes))
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.bitcoins.crypto
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
|
import org.bitcoins.crypto
|
||||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair
|
import org.bouncycastle.crypto.AsymmetricCipherKeyPair
|
||||||
import org.bouncycastle.crypto.digests.{RIPEMD160Digest, SHA512Digest}
|
import org.bouncycastle.crypto.digests.{RIPEMD160Digest, SHA512Digest}
|
||||||
import org.bouncycastle.crypto.generators.ECKeyPairGenerator
|
import org.bouncycastle.crypto.generators.ECKeyPairGenerator
|
||||||
|
@ -9,12 +10,10 @@ import org.bouncycastle.crypto.params.{
|
||||||
ECPrivateKeyParameters,
|
ECPrivateKeyParameters,
|
||||||
KeyParameter
|
KeyParameter
|
||||||
}
|
}
|
||||||
import org.bouncycastle.math.ec.ECPoint
|
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.security.{MessageDigest, SecureRandom}
|
import java.security.{MessageDigest, SecureRandom}
|
||||||
import scala.util.{Failure, Success, Try}
|
|
||||||
|
|
||||||
/** This is an implementation of [[CryptoRuntime]] that defaults to Bouncy Castle (https://bouncycastle.org/)
|
/** This is an implementation of [[CryptoRuntime]] that defaults to Bouncy Castle (https://bouncycastle.org/)
|
||||||
* and [[java.security]].
|
* and [[java.security]].
|
||||||
|
@ -42,7 +41,9 @@ trait BouncycastleCryptoRuntime extends CryptoRuntime {
|
||||||
* @return a tuple (p1, p2) where p1 and p2 are points on the curve and p1.x = p2.x = x
|
* @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
|
* p1.y is even, p2.y is odd
|
||||||
*/
|
*/
|
||||||
def recoverPoint(x: BigInteger): (ECPoint, ECPoint) = {
|
def recoverPoint(x: BigInteger): (
|
||||||
|
org.bouncycastle.math.ec.ECPoint,
|
||||||
|
org.bouncycastle.math.ec.ECPoint) = {
|
||||||
val bytes = ByteVector(x.toByteArray)
|
val bytes = ByteVector(x.toByteArray)
|
||||||
|
|
||||||
val bytes32 = if (bytes.length < 32) {
|
val bytes32 = if (bytes.length < 32) {
|
||||||
|
@ -150,9 +151,6 @@ trait BouncycastleCryptoRuntime extends CryptoRuntime {
|
||||||
signature: ECDigitalSignature): Boolean =
|
signature: ECDigitalSignature): Boolean =
|
||||||
BouncyCastleUtil.verifyDigitalSignature(data, publicKey, signature)
|
BouncyCastleUtil.verifyDigitalSignature(data, publicKey, signature)
|
||||||
|
|
||||||
override def decompressed(publicKey: ECPublicKey): ECPublicKey =
|
|
||||||
BouncyCastleUtil.decompressPublicKey(publicKey)
|
|
||||||
|
|
||||||
override def publicKey(privateKey: ECPrivateKey): ECPublicKey =
|
override def publicKey(privateKey: ECPrivateKey): ECPublicKey =
|
||||||
BouncyCastleUtil.computePublicKey(privateKey)
|
BouncyCastleUtil.computePublicKey(privateKey)
|
||||||
|
|
||||||
|
@ -161,18 +159,10 @@ trait BouncycastleCryptoRuntime extends CryptoRuntime {
|
||||||
tweak: FieldElement): ECPublicKey =
|
tweak: FieldElement): ECPublicKey =
|
||||||
BouncyCastleUtil.pubKeyTweakMul(publicKey, tweak.bytes)
|
BouncyCastleUtil.pubKeyTweakMul(publicKey, tweak.bytes)
|
||||||
|
|
||||||
override def add(pk1: ECPrivateKey, pk2: ECPrivateKey): ECPrivateKey =
|
|
||||||
pk1.fieldElement.add(pk2.fieldElement).toPrivateKey
|
|
||||||
|
|
||||||
override def add(pk1: ByteVector, pk2: ECPrivateKey): ByteVector = {
|
|
||||||
val sum = pk2.fieldElement.add(FieldElement(pk1))
|
|
||||||
sum.bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
override def add(pk1: ECPublicKey, pk2: ECPublicKey): ECPublicKey = {
|
override def add(pk1: ECPublicKey, pk2: ECPublicKey): ECPublicKey = {
|
||||||
val sumPoint =
|
val p1 = BouncyCastleUtil.decodePoint(pk1)
|
||||||
BouncyCastleUtil.decodePoint(pk1).add(BouncyCastleUtil.decodePoint(pk2))
|
val p2 = BouncyCastleUtil.decodePoint(pk2)
|
||||||
|
val sumPoint = p1.add(p2)
|
||||||
BouncyCastleUtil.decodePubKey(sumPoint)
|
BouncyCastleUtil.decodePubKey(sumPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,77 +179,6 @@ trait BouncycastleCryptoRuntime extends CryptoRuntime {
|
||||||
override def isFullyValidWithBouncyCastle(bytes: ByteVector): Boolean =
|
override def isFullyValidWithBouncyCastle(bytes: ByteVector): Boolean =
|
||||||
bytes.nonEmpty && isValidPubKey(bytes)
|
bytes.nonEmpty && isValidPubKey(bytes)
|
||||||
|
|
||||||
override def schnorrSign(
|
|
||||||
dataToSign: ByteVector,
|
|
||||||
privateKey: ECPrivateKey,
|
|
||||||
auxRand: ByteVector): SchnorrDigitalSignature = {
|
|
||||||
val nonceKey =
|
|
||||||
SchnorrNonce.kFromBipSchnorr(privateKey, dataToSign, auxRand)
|
|
||||||
|
|
||||||
schnorrSignWithNonce(dataToSign, privateKey, nonceKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
override def schnorrSignWithNonce(
|
|
||||||
dataToSign: ByteVector,
|
|
||||||
privateKey: ECPrivateKey,
|
|
||||||
nonceKey: ECPrivateKey): SchnorrDigitalSignature = {
|
|
||||||
val rx = nonceKey.schnorrNonce
|
|
||||||
val k = nonceKey.nonceKey.fieldElement
|
|
||||||
val x = privateKey.schnorrKey.fieldElement
|
|
||||||
val e = sha256SchnorrChallenge(
|
|
||||||
rx.bytes ++ privateKey.schnorrPublicKey.bytes ++ dataToSign).bytes
|
|
||||||
|
|
||||||
val challenge = x.multiply(FieldElement(e))
|
|
||||||
val sig = k.add(challenge)
|
|
||||||
|
|
||||||
SchnorrDigitalSignature(rx, sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
override def schnorrVerify(
|
|
||||||
data: ByteVector,
|
|
||||||
schnorrPubKey: SchnorrPublicKey,
|
|
||||||
signature: SchnorrDigitalSignature): Boolean = {
|
|
||||||
val rx = signature.rx
|
|
||||||
val sT = Try(signature.sig.toPrivateKey)
|
|
||||||
|
|
||||||
sT match {
|
|
||||||
case Success(s) =>
|
|
||||||
val eBytes = sha256SchnorrChallenge(
|
|
||||||
rx.bytes ++ schnorrPubKey.bytes ++ data).bytes
|
|
||||||
|
|
||||||
val e = FieldElement(eBytes)
|
|
||||||
val negE = e.negate
|
|
||||||
|
|
||||||
val sigPoint = s.publicKey
|
|
||||||
val challengePoint = schnorrPubKey.publicKey.tweakMultiply(negE)
|
|
||||||
val computedR = challengePoint.add(sigPoint)
|
|
||||||
val yCoord = BouncyCastleUtil.decodePoint(computedR).getRawYCoord
|
|
||||||
|
|
||||||
yCoord != null && !yCoord.testBitZero() && computedR.schnorrNonce == rx
|
|
||||||
case Failure(_) => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override def schnorrComputeSigPoint(
|
|
||||||
data: ByteVector,
|
|
||||||
nonce: SchnorrNonce,
|
|
||||||
pubKey: SchnorrPublicKey,
|
|
||||||
compressed: Boolean): ECPublicKey = {
|
|
||||||
val eBytes = sha256SchnorrChallenge(
|
|
||||||
nonce.bytes ++ pubKey.bytes ++ data).bytes
|
|
||||||
|
|
||||||
val e = FieldElement(eBytes)
|
|
||||||
|
|
||||||
val compressedSigPoint =
|
|
||||||
nonce.publicKey.add(pubKey.publicKey.tweakMultiply(e))
|
|
||||||
|
|
||||||
if (compressed) {
|
|
||||||
compressedSigPoint
|
|
||||||
} else {
|
|
||||||
compressedSigPoint.decompressed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override def adaptorSign(
|
override def adaptorSign(
|
||||||
key: ECPrivateKey,
|
key: ECPrivateKey,
|
||||||
adaptorPoint: ECPublicKey,
|
adaptorPoint: ECPublicKey,
|
||||||
|
@ -311,6 +230,15 @@ trait BouncycastleCryptoRuntime extends CryptoRuntime {
|
||||||
sh.doFinal()
|
sh.doFinal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override def decodePoint(bytes: ByteVector): crypto.ECPoint = {
|
||||||
|
val decoded = BouncyCastleUtil.decodePoint(bytes)
|
||||||
|
|
||||||
|
if (decoded.isInfinity)
|
||||||
|
crypto.ECPointInfinity
|
||||||
|
else
|
||||||
|
crypto.ECPoint(decoded.getRawXCoord.getEncoded,
|
||||||
|
decoded.getRawYCoord.getEncoded)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object BouncycastleCryptoRuntime extends BouncycastleCryptoRuntime
|
object BouncycastleCryptoRuntime extends BouncycastleCryptoRuntime
|
||||||
|
|
|
@ -274,6 +274,9 @@ trait LibSecp256k1CryptoRuntime extends CryptoRuntime {
|
||||||
|
|
||||||
override def sipHash(item: ByteVector, key: SipHashKey): Long =
|
override def sipHash(item: ByteVector, key: SipHashKey): Long =
|
||||||
BouncycastleCryptoRuntime.sipHash(item, key)
|
BouncycastleCryptoRuntime.sipHash(item, key)
|
||||||
|
|
||||||
|
override def decodePoint(bytes: ByteVector): ECPoint =
|
||||||
|
BouncycastleCryptoRuntime.decodePoint(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
object LibSecp256k1CryptoRuntime extends LibSecp256k1CryptoRuntime
|
object LibSecp256k1CryptoRuntime extends LibSecp256k1CryptoRuntime
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
package org.bitcoins.crypto
|
||||||
|
|
||||||
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
|
import java.math.BigInteger
|
||||||
|
import scala.math.BigInt
|
||||||
|
|
||||||
|
trait CryptoNumberUtil {
|
||||||
|
|
||||||
|
/** Converts a sequence of bytes to a **big endian** unsigned integer */
|
||||||
|
def toUnsignedInt(bytes: ByteVector): BigInt = {
|
||||||
|
toUnsignedInt(bytes.toArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Converts a sequence of bytes to a **big endian** unsigned integer */
|
||||||
|
def toUnsignedInt(bytes: Array[Byte]): BigInt = {
|
||||||
|
BigInt(new BigInteger(1, bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
def uintToFieldElement(bytes: ByteVector): FieldElement = {
|
||||||
|
FieldElement(toUnsignedInt(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Takes a hex string and parses it to a [[scala.math.BigInt BigInt]]. */
|
||||||
|
def toBigInt(hex: String): BigInt = toBigInt(CryptoBytesUtil.decodeHex(hex))
|
||||||
|
|
||||||
|
/** Converts a sequence of bytes to twos complement signed number. */
|
||||||
|
def toBigInt(bytes: ByteVector): BigInt = {
|
||||||
|
//BigInt interprets the number as an unsigned number then applies the given
|
||||||
|
//sign in front of that number, therefore if we have a negative number we need to invert it
|
||||||
|
//since twos complement is an inverted number representation for negative numbers
|
||||||
|
//see [[https://en.wikipedia.org/wiki/Two%27s_complement]]
|
||||||
|
if (bytes.isEmpty) BigInt(0)
|
||||||
|
//check if sign bit is set
|
||||||
|
else if ((0x80.toByte & bytes.head) != 0) {
|
||||||
|
val invertedBytes = bytes.tail.map(b => (b ^ 0xff.toByte).toByte)
|
||||||
|
val firstByteInverted = (bytes.head ^ 0xff.toByte).toByte
|
||||||
|
val num = firstByteInverted +: invertedBytes
|
||||||
|
BigInt(-1, num.toArray) - 1
|
||||||
|
} else {
|
||||||
|
val firstBitOff = (0x7f & bytes.head).toByte
|
||||||
|
val num = firstBitOff +: bytes.tail
|
||||||
|
BigInt(num.toArray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object CryptoNumberUtil extends CryptoNumberUtil
|
|
@ -2,6 +2,8 @@ package org.bitcoins.crypto
|
||||||
|
|
||||||
import scodec.bits.{BitVector, ByteVector}
|
import scodec.bits.{BitVector, ByteVector}
|
||||||
|
|
||||||
|
import scala.util.{Failure, Success, Try}
|
||||||
|
|
||||||
/** Trait that should be extended by specific runtimes like javascript
|
/** Trait that should be extended by specific runtimes like javascript
|
||||||
* or the JVM to support crypto functions needed for bitcoin-s
|
* or the JVM to support crypto functions needed for bitcoin-s
|
||||||
*/
|
*/
|
||||||
|
@ -146,13 +148,29 @@ trait CryptoRuntime {
|
||||||
data: ByteVector,
|
data: ByteVector,
|
||||||
signature: ECDigitalSignature): Boolean
|
signature: ECDigitalSignature): Boolean
|
||||||
|
|
||||||
def decompressed(publicKey: ECPublicKey): ECPublicKey
|
def decompressed(publicKey: ECPublicKey): ECPublicKey = {
|
||||||
|
if (publicKey.isCompressed) {
|
||||||
|
decodePoint(publicKey.bytes) match {
|
||||||
|
case ECPointInfinity => ECPublicKey.fromHex("00")
|
||||||
|
case point: ECPointImpl =>
|
||||||
|
val decompressedBytes =
|
||||||
|
ByteVector.fromHex("04").get ++
|
||||||
|
point.x.bytes ++
|
||||||
|
point.y.bytes
|
||||||
|
ECPublicKey(decompressedBytes)
|
||||||
|
}
|
||||||
|
} else publicKey
|
||||||
|
}
|
||||||
|
|
||||||
def tweakMultiply(publicKey: ECPublicKey, tweak: FieldElement): ECPublicKey
|
def tweakMultiply(publicKey: ECPublicKey, tweak: FieldElement): ECPublicKey
|
||||||
|
|
||||||
def add(pk1: ECPrivateKey, pk2: ECPrivateKey): ECPrivateKey
|
def add(pk1: ECPrivateKey, pk2: ECPrivateKey): ECPrivateKey =
|
||||||
|
pk1.fieldElement.add(pk2.fieldElement).toPrivateKey
|
||||||
|
|
||||||
def add(bytes: ByteVector, pk2: ECPrivateKey): ByteVector
|
def add(pk1: ByteVector, pk2: ECPrivateKey): ByteVector = {
|
||||||
|
val sum = pk2.fieldElement.add(FieldElement(pk1))
|
||||||
|
sum.bytes
|
||||||
|
}
|
||||||
|
|
||||||
def add(pk1: ECPublicKey, pk2: ECPublicKey): ECPublicKey
|
def add(pk1: ECPublicKey, pk2: ECPublicKey): ECPublicKey
|
||||||
|
|
||||||
|
@ -162,26 +180,84 @@ trait CryptoRuntime {
|
||||||
|
|
||||||
def isFullyValidWithBouncyCastle(bytes: ByteVector): Boolean
|
def isFullyValidWithBouncyCastle(bytes: ByteVector): Boolean
|
||||||
|
|
||||||
|
def decodePoint(bytes: ByteVector): ECPoint
|
||||||
|
|
||||||
|
def decodePoint(pubKey: ECPublicKey): ECPoint = {
|
||||||
|
decodePoint(pubKey.bytes)
|
||||||
|
}
|
||||||
|
|
||||||
def schnorrSign(
|
def schnorrSign(
|
||||||
dataToSign: ByteVector,
|
dataToSign: ByteVector,
|
||||||
privateKey: ECPrivateKey,
|
privateKey: ECPrivateKey,
|
||||||
auxRand: ByteVector): SchnorrDigitalSignature
|
auxRand: ByteVector): SchnorrDigitalSignature = {
|
||||||
|
val nonceKey =
|
||||||
|
SchnorrNonce.kFromBipSchnorr(privateKey, dataToSign, auxRand)
|
||||||
|
|
||||||
|
schnorrSignWithNonce(dataToSign, privateKey, nonceKey)
|
||||||
|
}
|
||||||
|
|
||||||
def schnorrSignWithNonce(
|
def schnorrSignWithNonce(
|
||||||
dataToSign: ByteVector,
|
dataToSign: ByteVector,
|
||||||
privateKey: ECPrivateKey,
|
privateKey: ECPrivateKey,
|
||||||
nonceKey: ECPrivateKey): SchnorrDigitalSignature
|
nonceKey: ECPrivateKey): SchnorrDigitalSignature = {
|
||||||
|
val rx = nonceKey.schnorrNonce
|
||||||
|
val k = nonceKey.nonceKey.fieldElement
|
||||||
|
val x = privateKey.schnorrKey.fieldElement
|
||||||
|
val e = sha256SchnorrChallenge(
|
||||||
|
rx.bytes ++ privateKey.schnorrPublicKey.bytes ++ dataToSign).bytes
|
||||||
|
|
||||||
|
val challenge = x.multiply(FieldElement(e))
|
||||||
|
val sig = k.add(challenge)
|
||||||
|
|
||||||
|
SchnorrDigitalSignature(rx, sig)
|
||||||
|
}
|
||||||
|
|
||||||
def schnorrVerify(
|
def schnorrVerify(
|
||||||
data: ByteVector,
|
data: ByteVector,
|
||||||
schnorrPubKey: SchnorrPublicKey,
|
schnorrPubKey: SchnorrPublicKey,
|
||||||
signature: SchnorrDigitalSignature): Boolean
|
signature: SchnorrDigitalSignature): Boolean = {
|
||||||
|
val rx = signature.rx
|
||||||
|
val sT = Try(signature.sig.toPrivateKey)
|
||||||
|
|
||||||
|
sT match {
|
||||||
|
case Success(s) =>
|
||||||
|
val eBytes = sha256SchnorrChallenge(
|
||||||
|
rx.bytes ++ schnorrPubKey.bytes ++ data).bytes
|
||||||
|
|
||||||
|
val e = FieldElement(eBytes)
|
||||||
|
val negE = e.negate
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
case Failure(_) => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def schnorrComputeSigPoint(
|
def schnorrComputeSigPoint(
|
||||||
data: ByteVector,
|
data: ByteVector,
|
||||||
nonce: SchnorrNonce,
|
nonce: SchnorrNonce,
|
||||||
pubKey: SchnorrPublicKey,
|
pubKey: SchnorrPublicKey,
|
||||||
compressed: Boolean): ECPublicKey
|
compressed: Boolean): ECPublicKey = {
|
||||||
|
val eBytes = sha256SchnorrChallenge(
|
||||||
|
nonce.bytes ++ pubKey.bytes ++ data).bytes
|
||||||
|
|
||||||
|
val e = FieldElement(eBytes)
|
||||||
|
|
||||||
|
val compressedSigPoint =
|
||||||
|
nonce.publicKey.add(pubKey.publicKey.tweakMultiply(e))
|
||||||
|
|
||||||
|
if (compressed) {
|
||||||
|
compressedSigPoint
|
||||||
|
} else {
|
||||||
|
compressedSigPoint.decompressed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def adaptorSign(
|
def adaptorSign(
|
||||||
key: ECPrivateKey,
|
key: ECPrivateKey,
|
||||||
|
|
|
@ -195,6 +195,9 @@ trait CryptoUtil extends CryptoRuntime {
|
||||||
|
|
||||||
override def sipHash(item: ByteVector, key: SipHashKey): Long =
|
override def sipHash(item: ByteVector, key: SipHashKey): Long =
|
||||||
cryptoRuntime.sipHash(item, key)
|
cryptoRuntime.sipHash(item, key)
|
||||||
|
|
||||||
|
override def decodePoint(bytes: ByteVector): ECPoint =
|
||||||
|
cryptoRuntime.decodePoint(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
object CryptoUtil extends CryptoUtil
|
object CryptoUtil extends CryptoUtil
|
||||||
|
|
37
crypto/src/main/scala/org/bitcoins/crypto/ECPoint.scala
Normal file
37
crypto/src/main/scala/org/bitcoins/crypto/ECPoint.scala
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
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))
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
import Deps.{Compile, Test}
|
||||||
import sbt._
|
import sbt._
|
||||||
import org.portablescala.sbtplatformdeps.PlatformDepsPlugin.autoImport._
|
import org.portablescala.sbtplatformdeps.PlatformDepsPlugin.autoImport._
|
||||||
|
|
||||||
|
@ -268,8 +269,7 @@ object Deps {
|
||||||
def crypto: Def.Initialize[Seq[ModuleID]] = {
|
def crypto: Def.Initialize[Seq[ModuleID]] = {
|
||||||
Def.setting {
|
Def.setting {
|
||||||
List(
|
List(
|
||||||
Compile.scodec.value,
|
Compile.scodec.value
|
||||||
Test.scalaTest.value
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -292,7 +292,8 @@ object Deps {
|
||||||
def cryptoTest = Def.setting {
|
def cryptoTest = Def.setting {
|
||||||
List(
|
List(
|
||||||
Test.scalaTest.value,
|
Test.scalaTest.value,
|
||||||
Test.scalacheck.value
|
Test.scalacheck.value,
|
||||||
|
Compile.scalaTestPlus.value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,11 @@ import org.bitcoins.core.config.{NetworkParameters, RegTest}
|
||||||
import org.bitcoins.core.protocol.blockchain.ChainParams
|
import org.bitcoins.core.protocol.blockchain.ChainParams
|
||||||
import org.scalacheck.{Gen, Shrink}
|
import org.scalacheck.{Gen, Shrink}
|
||||||
import org.scalactic.anyvals.PosInt
|
import org.scalactic.anyvals.PosInt
|
||||||
|
import org.scalatest._
|
||||||
import org.scalatest.concurrent.AsyncTimeLimitedTests
|
import org.scalatest.concurrent.AsyncTimeLimitedTests
|
||||||
import org.scalatest.flatspec.AsyncFlatSpec
|
import org.scalatest.flatspec.AsyncFlatSpec
|
||||||
import org.scalatest.matchers.must.Matchers
|
import org.scalatest.matchers.must.Matchers
|
||||||
import org.scalatest.time.Span
|
import org.scalatest.time.Span
|
||||||
import org.scalatest._
|
|
||||||
import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
|
import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
|
||||||
|
|
||||||
import scala.annotation.nowarn
|
import scala.annotation.nowarn
|
||||||
|
|
|
@ -31,7 +31,6 @@ import org.bitcoins.testkit.node.fixture.{
|
||||||
SpvNodeConnectedWithBitcoind,
|
SpvNodeConnectedWithBitcoind,
|
||||||
SpvNodeConnectedWithBitcoindV19
|
SpvNodeConnectedWithBitcoindV19
|
||||||
}
|
}
|
||||||
|
|
||||||
import org.bitcoins.testkit.wallet.{BitcoinSWalletTest, WalletWithBitcoindRpc}
|
import org.bitcoins.testkit.wallet.{BitcoinSWalletTest, WalletWithBitcoindRpc}
|
||||||
import org.bitcoins.testkitcore.node.P2PMessageTestUtil
|
import org.bitcoins.testkitcore.node.P2PMessageTestUtil
|
||||||
import org.bitcoins.wallet.WalletCallbacks
|
import org.bitcoins.wallet.WalletCallbacks
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package org.bitcoins.testkit.util
|
package org.bitcoins.testkit.util
|
||||||
|
|
||||||
|
import grizzled.slf4j.Logging
|
||||||
|
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import org.bitcoins.rpc.client.common.BitcoindRpcClient
|
import org.bitcoins.rpc.client.common.BitcoindRpcClient
|
||||||
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
|
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
|
||||||
|
@ -7,7 +9,7 @@ import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
import scala.concurrent.{Await, Future}
|
import scala.concurrent.{Await, Future}
|
||||||
|
|
||||||
abstract class BitcoindRpcTest extends BitcoinSAsyncTest {
|
abstract class BitcoindRpcTest extends BitcoinSAsyncTest with Logging {
|
||||||
|
|
||||||
private val dirExists =
|
private val dirExists =
|
||||||
Files.exists(BitcoindRpcTestClient.sbtBinaryDirectory)
|
Files.exists(BitcoindRpcTestClient.sbtBinaryDirectory)
|
||||||
|
|
Loading…
Add table
Reference in a new issue