Refactor crypto module to be compatible with Scala.js part 1 (#2719)

* Refactor crypto module to be compatible with Scala.js

* more changes

* some more changes

* abstract out Schnorr stuff

* abstract out adapter stuff

* cleanup

* some more cleanup

* fix build

* Removed references to ECPoint outside of .jvm scope

* remove references to ECPoint from the shared code

* cleanup

* remove cirlular dependencies

* more cleanup

* cleanup

* move SipHash to CryptoContext

* scaladoc

* scalafmt

Co-authored-by: nkohen <nadavk25@gmail.com>
This commit is contained in:
rorp 2021-02-27 03:58:20 -08:00 committed by GitHub
parent 7aa68998f1
commit c90f318fd7
44 changed files with 2305 additions and 648 deletions

View file

@ -23,4 +23,4 @@ jobs:
~/.bitcoin-s/binaries
key: ${{ runner.os }}-cache
- name: run tests
run: sbt ++2.12.12 downloadBitcoind coverage chainTest/test chain/coverageReport chain/coverageAggregate chain/coveralls nodeTest/test node/coverageReport node/coverageAggregate node/coveralls cryptoTest/test crypto/coverageReport crypto/coverageAggregate crypto/coveralls coreTest/test core/coverageReport core/coverageAggregate core/coveralls secp256k1jni/test zmq/test zmq/coverageReport zmq/coverageAggregate zmq/coveralls appCommonsTest/test appServerTest/test oracleServerTest/test
run: sbt ++2.12.12 downloadBitcoind coverage chainTest/test chain/coverageReport chain/coverageAggregate chain/coveralls nodeTest/test node/coverageReport node/coverageAggregate node/coveralls cryptoTest/test cryptoJVM/coverageReport cryptoJVM/coverageAggregate cryptoJVM/coveralls coreTest/test core/coverageReport core/coverageAggregate core/coveralls secp256k1jni/test zmq/test zmq/coverageReport zmq/coverageAggregate zmq/coveralls appCommonsTest/test appServerTest/test oracleServerTest/test

View file

@ -23,4 +23,4 @@ jobs:
~/.bitcoin-s/binaries
key: ${{ runner.os }}-cache
- name: run tests
run: sbt ++2.13.5 downloadBitcoind coverage chainTest/test chain/coverageReport chain/coverageAggregate chain/coveralls nodeTest/test node/coverageReport node/coverageAggregate node/coveralls cryptoTest/test crypto/coverageReport crypto/coverageAggregate crypto/coveralls coreTest/test core/coverageReport core/coverageAggregate core/coveralls secp256k1jni/test zmq/test zmq/coverageReport zmq/coverageAggregate zmq/coveralls appCommonsTest/test appServerTest/test oracleServerTest/test
run: sbt ++2.13.5 downloadBitcoind coverage chainTest/test chain/coverageReport chain/coverageAggregate chain/coveralls nodeTest/test node/coverageReport node/coverageAggregate node/coveralls cryptoTest/test cryptoJVM/coverageReport cryptoJVM/coverageAggregate cryptoJVM/coveralls coreTest/test core/coverageReport core/coverageAggregate core/coveralls secp256k1jni/test zmq/test zmq/coverageReport zmq/coverageAggregate zmq/coveralls appCommonsTest/test appServerTest/test oracleServerTest/test

View file

@ -1,3 +1,4 @@
import Deps.V
import com.typesafe.sbt.SbtGit.GitKeys._
import scala.util.Properties
@ -30,8 +31,39 @@ lazy val benchSettings: Seq[Def.SettingsDefinition] = {
}
import Projects._
lazy val crypto = project in file("crypto")
lazy val core = project in file("core") dependsOn crypto
lazy val commonJsSettings = {
Seq(
scalaJSLinkerConfig ~= {
_.withModuleKind(ModuleKind.CommonJSModule)
}
)
}
lazy val crypto = crossProject(JVMPlatform, JSPlatform)
.crossType(CrossType.Pure)
.settings(
name := "bitcoin-s-crypto"
)
.jvmSettings(
libraryDependencies ++= Seq(
"org.scala-js" %% "scalajs-stubs" % "1.0.0" % "provided",
"org.bouncycastle" % "bcprov-jdk15on" % V.bouncyCastle withSources () withJavadoc ()
)
)
.jsSettings(commonJsSettings)
.settings(
libraryDependencies ++= Seq(
"org.scodec" %%% "scodec-bits" % V.scodecV withSources () withJavadoc ()
)
)
.in(file("crypto"))
lazy val cryptoJS = crypto.js
lazy val cryptoJVM = crypto.jvm.dependsOn(secp256k1jni)
lazy val core = project in file("core") dependsOn cryptoJVM
lazy val bitcoindRpc = project
.in(file("bitcoind-rpc"))
@ -58,7 +90,8 @@ lazy val `bitcoin-s` = project
cliTest,
core,
coreTest,
crypto,
cryptoJVM,
cryptoJS,
cryptoTest,
dbCommons,
dbCommonsTest,
@ -97,7 +130,8 @@ lazy val `bitcoin-s` = project
cliTest,
core,
coreTest,
crypto,
cryptoJVM,
cryptoJS,
cryptoTest,
dbCommons,
dbCommonsTest,
@ -257,7 +291,7 @@ lazy val cryptoTest = project
name := "bitcoin-s-crypto-test"
)
.dependsOn(
crypto % testAndCompile,
cryptoJVM % testAndCompile,
testkit
)

View file

@ -2,6 +2,7 @@ package org.bitcoins.core.gcs
import org.bitcoins.core.number.{UInt64, UInt8}
import org.bitcoins.core.util.NumberUtil
import org.bitcoins.crypto.SipHashKey
import org.bitcoins.testkit.core.gen.NumberGenerator
import org.bitcoins.testkit.util.BitcoinSUnitTest
import org.scalacheck.Gen

View file

@ -2,7 +2,7 @@ package org.bitcoins.core.gcs
import org.bitcoins.core.number.UInt64
import org.bitcoins.core.protocol.CompactSizeUInt
import org.bitcoins.crypto.DoubleSha256Digest
import org.bitcoins.crypto.{DoubleSha256Digest, SipHashKey}
import org.bitcoins.testkit.core.gen.CryptoGenerators._
import org.bitcoins.testkit.core.gen.NumberGenerator
import org.bitcoins.testkit.util.BitcoinSAsyncTest

View file

@ -1,6 +1,5 @@
package org.bitcoins.core.crypto
import org.bitcoin.NativeSecp256k1
import org.bitcoins.core.hd.{BIP32Node, BIP32Path}
import org.bitcoins.core.number.{UInt32, UInt8}
import org.bitcoins.core.util._
@ -202,15 +201,7 @@ sealed abstract class ExtPrivateKey
val (il, ir) = hmac.splitAt(32)
//should be ECGroup addition
//parse256(IL) + kpar (mod n)
val tweak = CryptoContext.default match {
case CryptoContext.LibSecp256k1 =>
val tweakByteArr =
NativeSecp256k1.privKeyTweakAdd(il.toArray, key.bytes.toArray)
ByteVector(tweakByteArr)
case CryptoContext.BouncyCastle =>
val sum = key.fieldElement.add(FieldElement(il))
sum.bytes
}
val tweak = CryptoUtil.add(il, key)
val childKey = ECPrivateKey(tweak)
val fp = CryptoUtil.sha256Hash160(key.publicKey.bytes).bytes.take(4)
ExtPrivateKey(version, depth + UInt8.one, fp, idx, ChainCode(ir), childKey)
@ -476,16 +467,7 @@ sealed abstract class ExtPublicKey extends ExtKey {
val hmac = CryptoUtil.hmac512(chainCode.bytes, data)
val (il, ir) = hmac.splitAt(32)
val priv = ECPrivateKey(il)
val childPubKey = CryptoContext.default match {
case CryptoContext.LibSecp256k1 =>
val tweaked = NativeSecp256k1.pubKeyTweakAdd(key.bytes.toArray,
il.toArray,
priv.isCompressed)
ECPublicKey(ByteVector(tweaked))
case CryptoContext.BouncyCastle =>
val tweak = ECPrivateKey.fromBytes(il).publicKey
key.add(tweak)
}
val childPubKey = CryptoUtil.pubKeyTweakAdd(key, priv)
//we do not handle this case since it is impossible
//In case parse256(IL) n or Ki is the point at infinity, the resulting key is invalid,

View file

@ -6,7 +6,7 @@ import org.bitcoins.core.protocol.script.{EmptyScriptPubKey, ScriptPubKey}
import org.bitcoins.core.protocol.transaction.{Transaction, TransactionOutput}
import org.bitcoins.core.script.control.OP_RETURN
import org.bitcoins.core.util.BytesUtil
import org.bitcoins.crypto.DoubleSha256Digest
import org.bitcoins.crypto.{DoubleSha256Digest, SipHashKey}
import scodec.bits.ByteVector
object BlockFilter {

View file

@ -2,8 +2,7 @@ package org.bitcoins.core.gcs
import org.bitcoins.core.number.{UInt64, UInt8}
import org.bitcoins.core.protocol.CompactSizeUInt
import org.bouncycastle.crypto.macs.SipHash
import org.bouncycastle.crypto.params.KeyParameter
import org.bitcoins.crypto.{CryptoUtil, SipHashKey}
import scodec.bits.{BinStringSyntax, BitVector, ByteVector}
import scala.annotation.tailrec
@ -49,21 +48,7 @@ object GCS {
}
private def sipHash(item: ByteVector, key: SipHashKey): UInt64 = {
// https://github.com/bitcoin/bips/blob/master/bip-0158.mediawiki#hashing-data-objects
val sipHashCParam = 2
val sipHashDParam = 4
val sh = new SipHash(sipHashCParam, sipHashDParam)
val keyParam = new KeyParameter(key.bytes.toArray)
sh.init(keyParam)
val offset = 0
sh.update(item.toArray, offset, item.length.toInt)
val digest = sh.doFinal()
val digest = CryptoUtil.sipHash(item, key)
UInt64.fromHex(digest.toHexString)
}

View file

@ -6,7 +6,8 @@ import org.bitcoins.crypto.{
CryptoUtil,
DoubleSha256Digest,
DoubleSha256DigestBE,
NetworkElement
NetworkElement,
SipHashKey
}
import scodec.bits.{BitVector, ByteVector}

View file

@ -17,7 +17,8 @@ class BIP340Test extends BitcoinSUnitTest {
msg: ByteVector,
expectedSig: SchnorrDigitalSignature): Assertion = {
val secpSig = secKey.schnorrSign(msg, auxRand)
val bouncyCastleSig = BouncyCastleUtil.schnorrSign(msg, secKey, auxRand)
val bouncyCastleSig =
BouncycastleCryptoRuntime.schnorrSign(msg, secKey, auxRand)
assert(secpSig == expectedSig,
s"Test $index failed signing for libsecp256k1")
assert(bouncyCastleSig == expectedSig,
@ -33,7 +34,8 @@ class BIP340Test extends BitcoinSUnitTest {
expectedResult: Boolean,
comment: String): Assertion = {
val secpResult = pubKey.verify(msg, sig)
val bouncyCastleResult = BouncyCastleUtil.schnorrVerify(msg, pubKey, sig)
val bouncyCastleResult =
BouncycastleCryptoRuntime.schnorrVerify(msg, pubKey, sig)
assert(secpResult == expectedResult,
s"Test $index failed verification for libsecp256k1: $comment")
assert(bouncyCastleResult == expectedResult,

View file

@ -1,12 +1,9 @@
package org.bitcoins.crypto
import org.bitcoin.NativeSecp256k1
import org.bitcoins.crypto.CryptoContext.{BouncyCastle, LibSecp256k1}
import org.bitcoins.testkit.core.gen.{CryptoGenerators, NumberGenerator}
import org.bitcoins.testkit.util.BitcoinSUnitTest
import org.scalacheck.Gen
import org.scalatest.{Outcome, Succeeded}
import scodec.bits.ByteVector
class BouncyCastleSecp256k1Test extends BitcoinSUnitTest {
@ -18,7 +15,7 @@ class BouncyCastleSecp256k1Test extends BitcoinSUnitTest {
override def withFixture(test: NoArgTest): Outcome = {
CryptoContext.default match {
case CryptoContext.LibSecp256k1 => super.withFixture(test)
case CryptoContext.BouncyCastle =>
case CryptoContext.BouncyCastle | CryptoContext.BCrypto =>
logger.warn(s"Test ${test.name} skipped as Secp256k1 is not available.")
Succeeded
}
@ -27,19 +24,19 @@ class BouncyCastleSecp256k1Test extends BitcoinSUnitTest {
it must "add private keys the same" in {
forAll(CryptoGenerators.privateKey, CryptoGenerators.privateKey) {
case (priv1, priv2) =>
assert(priv1.addWithBouncyCastle(priv2) == priv1.addWithSecp(priv2))
assert(
BouncycastleCryptoRuntime
.add(priv1, priv2) == LibSecp256k1CryptoRuntime.add(priv1, priv2))
}
}
it must "add public keys the same" in {
forAll(CryptoGenerators.publicKey, CryptoGenerators.privateKey) {
case (pubKey, privKey) =>
val sumKeyBytes =
NativeSecp256k1.pubKeyTweakAdd(pubKey.bytes.toArray,
privKey.bytes.toArray,
true)
val sumKeyExpected = ECPublicKey.fromBytes(ByteVector(sumKeyBytes))
val sumKey = pubKey.addWithBouncyCastle(privKey.publicKey)
val sumKeyExpected =
LibSecp256k1CryptoRuntime.pubKeyTweakAdd(pubKey, privKey)
val sumKey =
BouncycastleCryptoRuntime.pubKeyTweakAdd(pubKey, privKey)
assert(sumKey == sumKeyExpected)
}
@ -49,18 +46,19 @@ class BouncyCastleSecp256k1Test extends BitcoinSUnitTest {
forAll(CryptoGenerators.publicKey, CryptoGenerators.fieldElement) {
case (pubKey, tweak) =>
assert(
pubKey.tweakMultiplyWithSecp(tweak) == pubKey
.tweakMultiplyWithBouncyCastle(tweak))
LibSecp256k1CryptoRuntime.tweakMultiply(pubKey, tweak) ==
BouncycastleCryptoRuntime.tweakMultiply(pubKey, tweak))
}
}
it must "validate keys the same" in {
val keyOrGarbageGen = Gen.oneOf(CryptoGenerators.publicKey.map(_.bytes),
NumberGenerator.bytevector(33))
val keyOrGarbageGen =
Gen.oneOf(CryptoGenerators.publicKey.map(_.bytes),
NumberGenerator.bytevector(33))
forAll(keyOrGarbageGen) { bytes =>
assert(
ECPublicKey.isFullyValid(bytes, context = BouncyCastle) ==
ECPublicKey.isFullyValid(bytes, context = LibSecp256k1)
LibSecp256k1CryptoRuntime.isValidPubKey(bytes) ==
BouncycastleCryptoRuntime.isValidPubKey(bytes)
)
}
}
@ -68,16 +66,18 @@ class BouncyCastleSecp256k1Test extends BitcoinSUnitTest {
it must "decompress keys the same" in {
forAll(CryptoGenerators.publicKey) { pubKey =>
assert(
pubKey.decompressed(context = BouncyCastle) == pubKey.decompressed(
context = LibSecp256k1))
LibSecp256k1CryptoRuntime.decompressed(pubKey) ==
BouncycastleCryptoRuntime.decompressed(pubKey)
)
}
}
it must "compute public keys the same" in {
forAll(CryptoGenerators.privateKey) { privKey =>
assert(
privKey.publicKey(context = BouncyCastle) == privKey.publicKey(
context = LibSecp256k1))
LibSecp256k1CryptoRuntime.publicKey(privKey) ==
BouncycastleCryptoRuntime.publicKey(privKey)
)
}
}
@ -85,8 +85,10 @@ class BouncyCastleSecp256k1Test extends BitcoinSUnitTest {
forAll(CryptoGenerators.privateKey, NumberGenerator.bytevector(32)) {
case (privKey, bytes) =>
assert(
privKey.sign(bytes, context = BouncyCastle) == privKey
.sign(bytes, context = LibSecp256k1))
LibSecp256k1CryptoRuntime.sign(
privKey,
bytes) == BouncycastleCryptoRuntime.sign(privKey, bytes)
)
}
}
@ -95,10 +97,13 @@ class BouncyCastleSecp256k1Test extends BitcoinSUnitTest {
NumberGenerator.bytevector(32),
NumberGenerator.bytevector(32)) { case (privKey, bytes, entropy) =>
assert(
privKey.signWithEntropy(bytes,
entropy,
context = BouncyCastle) == privKey
.signWithEntropy(bytes, entropy, context = LibSecp256k1))
LibSecp256k1CryptoRuntime.signWithEntropy(
privKey,
bytes,
entropy) == BouncycastleCryptoRuntime.signWithEntropy(privKey,
bytes,
entropy)
)
}
}
@ -109,11 +114,17 @@ class BouncyCastleSecp256k1Test extends BitcoinSUnitTest {
val sig = privKey.sign(bytes)
val pubKey = privKey.publicKey
assert(
pubKey.verify(bytes, sig, context = BouncyCastle) == pubKey
.verify(bytes, sig, context = LibSecp256k1))
LibSecp256k1CryptoRuntime.verify(
pubKey,
bytes,
sig) == BouncycastleCryptoRuntime.verify(pubKey, bytes, sig)
)
assert(
pubKey.verify(bytes, badSig, context = BouncyCastle) == pubKey
.verify(bytes, badSig, context = LibSecp256k1))
LibSecp256k1CryptoRuntime.verify(
pubKey,
bytes,
badSig) == BouncycastleCryptoRuntime.verify(pubKey, bytes, badSig)
)
}
}

View file

@ -97,7 +97,7 @@ class ECDigitalSignatureTest extends BitcoinSUnitTest {
it must "be able to generate valid signatures with bouncy castle" in {
forAll(CryptoGenerators.privateKey, CryptoGenerators.sha256Digest) {
case (privKey: ECPrivateKey, hash: Sha256Digest) =>
val sig = privKey.signWithBouncyCastle(hash.bytes)
val sig = BouncycastleCryptoRuntime.sign(privKey, hash.bytes)
val pubKey = privKey.publicKey
assert(pubKey.verify(hash, sig))

View file

@ -31,10 +31,14 @@ class ECPublicKeyTest extends BitcoinSUnitTest {
}
it must "have serialization symmetry from ECPublicKey -> ECPoint -> ECPublicKey" in {
forAll(CryptoGenerators.publicKey) { pubKey =>
val p = pubKey.toPoint
val pub2 = ECPublicKey.fromPoint(p, pubKey.isCompressed)
assert(pubKey == pub2)
CryptoContext.cryptoRuntime match {
case _: BouncycastleCryptoRuntime | _: LibSecp256k1CryptoRuntime =>
forAll(CryptoGenerators.publicKey) { pubKey =>
val p = BouncyCastleUtil.decodePoint(pubKey)
val pub2 = BouncyCastleUtil.decodePubKey(p, pubKey.isCompressed)
assert(pubKey == pub2)
}
case _ => succeed
}
}

View file

@ -0,0 +1,137 @@
package org.bitcoins.crypto
import scodec.bits.ByteVector
/** This is an implementation of [[CryptoRuntime]] that defaults to
* Bcrypto (https://github.com/bcoin-org/bcrypto) when possible.
*/
trait BCryptoCryptoRuntime extends CryptoRuntime {
override val cryptoContext: CryptoContext = CryptoContext.BCrypto
/** Generates a 32 byte private key */
override def freshPrivateKey: ECPrivateKey = ???
/** Converts a private key -> public key
*
* @param privateKey the private key we want the corresponding public key for
* @param isCompressed whether the returned public key should be compressed or not
*/
override def toPublicKey(
privateKey: ECPrivateKey,
isCompressed: Boolean): ECPublicKey = ???
override def ripeMd160(bytes: ByteVector): RipeMd160Digest = ???
override def sha256Hash160(bytes: ByteVector): Sha256Hash160Digest = ???
override def sha256(bytes: ByteVector): Sha256Digest = ???
override def sha1(bytes: ByteVector): Sha1Digest = ???
override def hmac512(key: ByteVector, data: ByteVector): ByteVector = ???
override def normalize(str: String): String = ???
/** Recover public keys from a signature and the message that was signed. This method will return 2 public keys, and the signature
* can be verified with both, but only one of them matches that private key that was used to generate the signature.
*
* @param signature signature
* @param message message that was signed
* @return a (pub1, pub2) tuple where pub1 and pub2 are candidates public keys. If you have the recovery id then use
* pub1 if the recovery id is even and pub2 if it is odd
*/
override def recoverPublicKey(
signature: ECDigitalSignature,
message: ByteVector): (ECPublicKey, ECPublicKey) = ???
override def publicKey(privateKey: ECPrivateKey): ECPublicKey = ???
override def sign(
privateKey: ECPrivateKey,
dataToSign: ByteVector): ECDigitalSignature = ???
override def signWithEntropy(
privateKey: ECPrivateKey,
bytes: ByteVector,
entropy: ByteVector): ECDigitalSignature = ???
override def secKeyVerify(privateKeybytes: ByteVector): Boolean = ???
override def verify(
publicKey: ECPublicKey,
data: ByteVector,
signature: ECDigitalSignature): Boolean = ???
override def decompressed(publicKey: ECPublicKey): ECPublicKey = ???
override def tweakMultiply(
publicKey: ECPublicKey,
tweak: FieldElement): ECPublicKey = ???
override def add(pk1: ECPrivateKey, pk2: ECPrivateKey): ECPrivateKey = ???
override def add(bytes: ByteVector, pk2: ECPrivateKey): ByteVector = ???
override def add(pk1: ECPublicKey, pk2: ECPublicKey): ECPublicKey = ???
override def pubKeyTweakAdd(
pubkey: ECPublicKey,
privkey: ECPrivateKey): ECPublicKey = ???
override def isValidPubKey(bytes: ByteVector): Boolean = ???
override def isFullyValidWithBouncyCastle(bytes: ByteVector): Boolean = ???
override def schnorrSign(
dataToSign: ByteVector,
privateKey: ECPrivateKey,
auxRand: ByteVector): SchnorrDigitalSignature = ???
override def schnorrSignWithNonce(
dataToSign: ByteVector,
privateKey: ECPrivateKey,
nonceKey: ECPrivateKey): SchnorrDigitalSignature = ???
override def schnorrVerify(
data: ByteVector,
schnorrPubKey: SchnorrPublicKey,
signature: SchnorrDigitalSignature): Boolean = ???
override def schnorrComputeSigPoint(
data: ByteVector,
nonce: SchnorrNonce,
pubKey: SchnorrPublicKey,
compressed: Boolean): ECPublicKey = ???
override def adaptorSign(
key: ECPrivateKey,
adaptorPoint: ECPublicKey,
msg: ByteVector): ECAdaptorSignature = ???
override def adaptorComplete(
key: ECPrivateKey,
adaptorSignature: ECAdaptorSignature): ECDigitalSignature = ???
override def extractAdaptorSecret(
signature: ECDigitalSignature,
adaptorSignature: ECAdaptorSignature,
key: ECPublicKey): ECPrivateKey = ???
override def adaptorVerify(
adaptorSignature: ECAdaptorSignature,
key: ECPublicKey,
msg: ByteVector,
adaptorPoint: ECPublicKey): Boolean = ???
override def decodeSignature(
signature: ECDigitalSignature): (scala.BigInt, scala.BigInt) = ???
override def isValidSignatureEncoding(
signature: ECDigitalSignature): Boolean = ???
override def isDEREncoded(signature: ECDigitalSignature): Boolean = ???
override def sipHash(item: ByteVector, key: SipHashKey): Long = ???
}
object BCryptoCryptoRuntime extends BCryptoCryptoRuntime

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,7 @@
package org.bitcoins.crypto
sealed protected trait CryptoRuntimeFactory {
def newCryptoRuntime: CryptoRuntime = BCryptoCryptoRuntime
}
protected object CryptoRuntimeFactory extends CryptoRuntimeFactory

View file

@ -0,0 +1,19 @@
package org.bitcoins.crypto
import scala.scalajs.js
import scala.scalajs.js.annotation._
/** Scala wrapper for
* https://github.com/bcoin-org/bcrypto/blob/master/lib/js/ecdsa.js
*/
@js.native
@JSImport("bcrypto/lib/js/ecdsa.js", JSImport.Default)
class ECDSA(
name: String = "SECP256K1",
hash: SHA256 = new SHA256,
xof: SHA256 = new SHA256,
pre: String = null)
extends js.Object {
def publicKeyCreate(key: Buffer, compressed: Boolean): Buffer = js.native
}

View file

@ -0,0 +1,17 @@
package org.bitcoins.crypto
import scala.scalajs.js
import scala.scalajs.js.annotation._
/** The scala js wrapper for
* https://github.com/bcoin-org/bcrypto/blob/master/lib/js/hash160.js
*/
@js.native
@JSImport("bcrypto/lib/js/hash160.js", JSImport.Default)
class Hash160() extends Hasher {
override def init(): Unit = js.native
override def update(bytes: Buffer): Unit = js.native
override def `final`(): Buffer = js.native
}

View file

@ -0,0 +1,12 @@
package org.bitcoins.crypto
import scala.scalajs.js
/** All hash classes in bcrypto have this structure */
trait Hasher extends js.Object {
def init(): Unit
def update(bytes: Buffer): Unit
def `final`(): Buffer
}

View file

@ -0,0 +1,16 @@
package org.bitcoins.crypto
import scala.scalajs.js
import scala.scalajs.js.annotation._
@js.native
@JSImport("bcrypto/lib/random.js", JSImport.Namespace)
object Random extends js.Object {
def randomBytes(n: Int): Buffer = js.native
}
@js.native
@JSImport("bcrypto/lib/random-browser.js", JSImport.Namespace)
object RandomBrowser extends js.Object {
def randomBytes(n: Int): Buffer = js.native
}

View file

@ -0,0 +1,16 @@
package org.bitcoins.crypto
import scala.scalajs.js
import scala.scalajs.js.annotation._
//the annotation specifies that Foobaz is a native JS class defined in the module "bar.js", and exported under the name "Foo".
@js.native
@JSImport("bcrypto/lib/js/ripemd160.js", JSImport.Default)
class RipeMd160() extends Hasher {
override def init(): Unit = js.native
override def update(bytes: Buffer): Unit = js.native
override def `final`(): Buffer = js.native
}

View file

@ -0,0 +1,17 @@
package org.bitcoins.crypto
import scala.scalajs.js
import scala.scalajs.js.annotation._
/** Scala wrapper for
* https://github.com/bcoin-org/bcrypto/blob/4db0feecde86bce71b0c33d31f7178fb14e7381f/lib/js/sha256.js#L54
*/
@js.native
@JSImport("bcrypto/lib/js/sha256.js", JSImport.Default)
class SHA256() extends Hasher {
override def init(): Unit = js.native
override def update(bytes: Buffer): Unit = js.native
override def `final`(): Buffer = js.native
}

View file

@ -1,7 +1,5 @@
package org.bitcoins.crypto
import java.math.BigInteger
import org.bouncycastle.crypto.digests.SHA256Digest
import org.bouncycastle.crypto.params.{
ECPrivateKeyParameters,
@ -11,7 +9,8 @@ import org.bouncycastle.crypto.signers.{ECDSASigner, HMacDSAKCalculator}
import org.bouncycastle.math.ec.{ECCurve, ECPoint}
import scodec.bits.ByteVector
import scala.util.{Failure, Success, Try}
import java.math.BigInteger
import scala.util.Try
object BouncyCastleUtil {
@ -23,14 +22,25 @@ object BouncyCastleUtil {
}
def pubKeyTweakMul(publicKey: ECPublicKey, tweak: ByteVector): ECPublicKey = {
val point = publicKey.toPoint.multiply(getBigInteger(tweak))
ECPublicKey.fromPoint(point, publicKey.isCompressed)
val point = decodePoint(publicKey).multiply(getBigInteger(tweak))
decodePubKey(point, publicKey.isCompressed)
}
def decodePoint(bytes: ByteVector): ECPoint = {
curve.decodePoint(bytes.toArray)
}
def decodePoint(pubKey: ECPublicKey): ECPoint = {
decodePoint(pubKey.bytes)
}
def decodePubKey(
point: ECPoint,
isCompressed: Boolean = true): ECPublicKey = {
val bytes = point.getEncoded(isCompressed)
ECPublicKey.fromBytes(ByteVector(bytes))
}
def validatePublicKey(bytes: ByteVector): Boolean = {
Try(decodePoint(bytes))
.map(_.getCurve == curve)
@ -38,8 +48,8 @@ object BouncyCastleUtil {
}
def pubKeyTweakMul(pubKey: ECPublicKey, tweak: FieldElement): ECPublicKey = {
val tweakedPoint = pubKey.toPoint.multiply(tweak.toBigInteger)
ECPublicKey.fromPoint(tweakedPoint, pubKey.isCompressed)
val tweakedPoint = decodePoint(pubKey).multiply(tweak.toBigInteger)
decodePubKey(tweakedPoint, pubKey.isCompressed)
}
def decompressPublicKey(publicKey: ECPublicKey): ECPublicKey = {
@ -144,81 +154,6 @@ object BouncyCastleUtil {
}
resultTry.getOrElse(false)
}
def schnorrSign(
dataToSign: ByteVector,
privateKey: ECPrivateKey,
auxRand: ByteVector): SchnorrDigitalSignature = {
val nonceKey =
SchnorrNonce.kFromBipSchnorr(privateKey, dataToSign, auxRand)
schnorrSignWithNonce(dataToSign, privateKey, nonceKey)
}
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 = CryptoUtil
.sha256SchnorrChallenge(
rx.bytes ++ privateKey.schnorrPublicKey.bytes ++ dataToSign)
.bytes
val challenge = x.multiply(FieldElement(e))
val sig = k.add(challenge)
SchnorrDigitalSignature(rx, sig)
}
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 = CryptoUtil
.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 = computedR.toPoint.getRawYCoord
yCoord != null && !yCoord.testBitZero() && computedR.schnorrNonce == rx
case Failure(_) => false
}
}
def schnorrComputeSigPoint(
data: ByteVector,
nonce: SchnorrNonce,
pubKey: SchnorrPublicKey,
compressed: Boolean): ECPublicKey = {
val eBytes = CryptoUtil
.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
}
}
}
object AdaptorStuff {
@ -230,7 +165,8 @@ object AdaptorStuff {
k: FieldElement,
r: ECPublicKey,
privateKey: ECPrivateKey): FieldElement = {
val rx = FieldElement(r.toPoint.getXCoord.toBigInteger)
val rx = FieldElement(
BouncyCastleUtil.decodePoint(r).getXCoord.toBigInteger)
val x = privateKey.fieldElement
val m = FieldElement(dataToSign)
val kInv = k.inverse
@ -243,7 +179,9 @@ object AdaptorStuff {
adaptorPoint: ECPublicKey,
dataToSign: ByteVector): ECAdaptorSignature = {
// Include dataToSign and adaptor in nonce derivation
val hash = CryptoUtil.sha256(dataToSign ++ serializePoint(adaptorPoint))
val hash =
BouncycastleCryptoRuntime.sha256(
dataToSign ++ serializePoint(adaptorPoint))
val k = DLEQStuff.dleqNonceFunc(hash.bytes,
privateKey.fieldElement,
"ECDSAAdaptorNon")
@ -361,7 +299,7 @@ object DLEQStuff {
fe: FieldElement,
algoName: String): FieldElement = {
val kBytes =
CryptoUtil.taggedSha256(fe.bytes ++ hash, algoName).bytes
BouncycastleCryptoRuntime.taggedSha256(fe.bytes ++ hash, algoName).bytes
FieldElement(kBytes)
}
@ -372,7 +310,7 @@ object DLEQStuff {
r2: ECPublicKey,
p1: ECPublicKey,
p2: ECPublicKey): ByteVector = {
CryptoUtil
BouncycastleCryptoRuntime
.taggedSha256(
serializePoint(adaptorPoint) ++ serializePoint(r1) ++ serializePoint(
r2) ++ serializePoint(p1) ++ serializePoint(p2),
@ -392,7 +330,7 @@ object DLEQStuff {
// hash(Y || fe*G || fe*Y)
val hash =
CryptoUtil
BouncycastleCryptoRuntime
.sha256(
serializePoint(adaptorPoint) ++ serializePoint(p1) ++ serializePoint(
p2))

View file

@ -0,0 +1,319 @@
package org.bitcoins.crypto
import org.bouncycastle.crypto.AsymmetricCipherKeyPair
import org.bouncycastle.crypto.digests.{RIPEMD160Digest, SHA512Digest}
import org.bouncycastle.crypto.generators.ECKeyPairGenerator
import org.bouncycastle.crypto.macs.{HMac, SipHash}
import org.bouncycastle.crypto.params.{
ECKeyGenerationParameters,
ECPrivateKeyParameters,
KeyParameter
}
import org.bouncycastle.math.ec.ECPoint
import scodec.bits.ByteVector
import java.math.BigInteger
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/)
* and [[java.security]].
*/
trait BouncycastleCryptoRuntime extends CryptoRuntime {
private[this] lazy val secureRandom = new SecureRandom()
override val cryptoContext: CryptoContext = CryptoContext.BouncyCastle
override def freshPrivateKey: ECPrivateKey = {
val generator: ECKeyPairGenerator = new ECKeyPairGenerator
val keyGenParams: ECKeyGenerationParameters =
new ECKeyGenerationParameters(CryptoParams.curve, secureRandom)
generator.init(keyGenParams)
val keypair: AsymmetricCipherKeyPair = generator.generateKeyPair
val privParams: ECPrivateKeyParameters =
keypair.getPrivate.asInstanceOf[ECPrivateKeyParameters]
val priv: BigInteger = privParams.getD
val bytes = ByteVector(priv.toByteArray)
ECPrivateKey.fromBytes(bytes)
}
/** @param x x coordinate
* @return a tuple (p1, p2) where p1 and p2 are points on the curve and p1.x = p2.x = x
* p1.y is even, p2.y is odd
*/
def recoverPoint(x: BigInteger): (ECPoint, ECPoint) = {
val bytes = ByteVector(x.toByteArray)
val bytes32 = if (bytes.length < 32) {
bytes.padLeft(32)
} else if (bytes.length == 32) {
bytes
} else if (bytes.length == 33 && bytes.head == 0.toByte) {
bytes.tail
} else {
throw new IllegalArgumentException(
s"Field element cannot have more than 32 bytes, got $bytes from $x")
}
(BouncyCastleUtil.decodePoint(ECPublicKey(0x02.toByte +: bytes32)),
BouncyCastleUtil.decodePoint(ECPublicKey(0x03.toByte +: bytes32)))
}
override def recoverPublicKey(
signature: ECDigitalSignature,
message: ByteVector): (ECPublicKey, ECPublicKey) = {
val curve = CryptoParams.curve
val (r, s) = (signature.r.bigInteger, signature.s.bigInteger)
val m = new BigInteger(1, message.toArray)
val (p1, p2) = recoverPoint(r)
val Q1 = p1
.multiply(s)
.subtract(curve.getG.multiply(m))
.multiply(r.modInverse(curve.getN))
val Q2 = p2
.multiply(s)
.subtract(curve.getG.multiply(m))
.multiply(r.modInverse(curve.getN))
val pub1 = BouncyCastleUtil.decodePubKey(Q1)
val pub2 = BouncyCastleUtil.decodePubKey(Q2)
(pub1, pub2)
}
override def hmac512(key: ByteVector, data: ByteVector): ByteVector = {
val hmac512 = new HMac(new SHA512Digest())
hmac512.init(new KeyParameter(key.toArray))
hmac512.update(data.toArray, 0, data.intSize.get)
val output = new Array[Byte](64)
hmac512.doFinal(output, 0)
ByteVector(output)
}
override def ripeMd160(bytes: ByteVector): RipeMd160Digest = {
//from this tutorial http://rosettacode.org/wiki/RIPEMD-160#Scala
val messageDigest = new RIPEMD160Digest
val raw = bytes.toArray
messageDigest.update(raw, 0, raw.length)
val out = Array.fill[Byte](messageDigest.getDigestSize)(0)
messageDigest.doFinal(out, 0)
RipeMd160Digest(ByteVector(out))
}
override def sha256(bytes: ByteVector): Sha256Digest = {
val hash = MessageDigest.getInstance("SHA-256").digest(bytes.toArray)
Sha256Digest(ByteVector(hash))
}
override def sha1(bytes: ByteVector): Sha1Digest = {
val hash = MessageDigest.getInstance("SHA-1").digest(bytes.toArray).toList
Sha1Digest(ByteVector(hash))
}
override def normalize(str: String): String = {
java.text.Normalizer.normalize(str, java.text.Normalizer.Form.NFC)
}
override def sha256Hash160(bytes: ByteVector): Sha256Hash160Digest = {
val hash = ripeMd160(sha256(bytes).bytes).bytes
Sha256Hash160Digest(hash)
}
override def toPublicKey(
privateKey: ECPrivateKey,
isCompressed: Boolean): ECPublicKey =
BouncyCastleUtil.computePublicKey(privateKey)
override def sign(
privateKey: ECPrivateKey,
dataToSign: ByteVector): ECDigitalSignature = {
BouncyCastleUtil.sign(dataToSign, privateKey)
}
override def signWithEntropy(
privateKey: ECPrivateKey,
bytes: ByteVector,
entropy: ByteVector): ECDigitalSignature =
BouncyCastleUtil.signWithEntropy(bytes, privateKey, entropy)
override def secKeyVerify(privateKeyBytes: ByteVector): Boolean =
CryptoParams.curve.getCurve
.isValidFieldElement(new BigInteger(1, privateKeyBytes.toArray))
override def verify(
publicKey: ECPublicKey,
data: ByteVector,
signature: ECDigitalSignature): Boolean =
BouncyCastleUtil.verifyDigitalSignature(data, publicKey, signature)
override def decompressed(publicKey: ECPublicKey): ECPublicKey =
BouncyCastleUtil.decompressPublicKey(publicKey)
override def publicKey(privateKey: ECPrivateKey): ECPublicKey =
BouncyCastleUtil.computePublicKey(privateKey)
override def tweakMultiply(
publicKey: ECPublicKey,
tweak: FieldElement): ECPublicKey =
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 = {
val sumPoint =
BouncyCastleUtil.decodePoint(pk1).add(BouncyCastleUtil.decodePoint(pk2))
BouncyCastleUtil.decodePubKey(sumPoint)
}
def pubKeyTweakAdd(
pubkey: ECPublicKey,
privkey: ECPrivateKey): ECPublicKey = {
val tweak = privkey.publicKey
pubkey.add(tweak)
}
override def isValidPubKey(bytes: ByteVector): Boolean =
BouncyCastleUtil.validatePublicKey(bytes)
override def isFullyValidWithBouncyCastle(bytes: ByteVector): Boolean =
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(
key: ECPrivateKey,
adaptorPoint: ECPublicKey,
msg: ByteVector): ECAdaptorSignature = {
AdaptorStuff.adaptorSign(key, adaptorPoint, msg)
}
override def adaptorComplete(
key: ECPrivateKey,
adaptorSignature: ECAdaptorSignature): ECDigitalSignature = {
AdaptorStuff.adaptorComplete(key, adaptorSignature.adaptedSig)
}
override def extractAdaptorSecret(
signature: ECDigitalSignature,
adaptorSignature: ECAdaptorSignature,
key: ECPublicKey): ECPrivateKey = {
AdaptorStuff.extractAdaptorSecret(signature, adaptorSignature, key)
}
override def adaptorVerify(
adaptorSignature: ECAdaptorSignature,
key: ECPublicKey,
msg: ByteVector,
adaptorPoint: ECPublicKey): Boolean =
AdaptorStuff.adaptorVerify(adaptorSignature, key, msg, adaptorPoint)
override def decodeSignature(
signature: ECDigitalSignature): (BigInt, BigInt) =
DERSignatureUtil.decodeSignature(signature)
override def isValidSignatureEncoding(
signature: ECDigitalSignature): Boolean =
DERSignatureUtil.isValidSignatureEncoding(signature)
override def isDEREncoded(signature: ECDigitalSignature): Boolean =
DERSignatureUtil.isDEREncoded(signature)
override def sipHash(item: ByteVector, key: SipHashKey): Long = {
val sipHashCParam = 2
val sipHashDParam = 4
val sh = new SipHash(sipHashCParam, sipHashDParam)
val keyParam = new KeyParameter(key.bytes.toArray)
sh.init(keyParam)
val offset = 0
sh.update(item.toArray, offset, item.length.toInt)
sh.doFinal()
}
}
object BouncycastleCryptoRuntime extends BouncycastleCryptoRuntime

View file

@ -1,11 +1,11 @@
package org.bitcoins.crypto
import java.math.BigInteger
import org.bouncycastle.asn1.sec.SECNamedCurves
import org.bouncycastle.asn1.x9.X9ECParameters
import org.bouncycastle.crypto.params.ECDomainParameters
import java.math.BigInteger
/** Created by chris on 3/29/16.
* This trait represents all of the default parameters for our elliptic curve
*/

View file

@ -0,0 +1,24 @@
package org.bitcoins.crypto
import org.bitcoin.Secp256k1Context
sealed protected trait CryptoRuntimeFactory {
def newCryptoRuntime: CryptoRuntime = {
val secpDisabled = System.getenv("DISABLE_SECP256K1")
if (
secpDisabled != null && (secpDisabled.toLowerCase == "true" || secpDisabled == "1")
) {
BouncycastleCryptoRuntime
} else {
if (Secp256k1Context.isEnabled) {
LibSecp256k1CryptoRuntime
} else {
BouncycastleCryptoRuntime
}
}
}
}
protected object CryptoRuntimeFactory extends CryptoRuntimeFactory

View file

@ -1,8 +1,5 @@
package org.bitcoins.crypto
import java.math.BigInteger
import java.security.SecureRandom
import org.bouncycastle.crypto.Digest
import org.bouncycastle.crypto.macs.HMac
import org.bouncycastle.crypto.params.KeyParameter
@ -10,6 +7,9 @@ import org.bouncycastle.crypto.signers.DSAKCalculator
import org.bouncycastle.util.{Arrays, BigIntegers}
import scodec.bits.ByteVector
import java.math.BigInteger
import java.security.SecureRandom
/** Entirely copied from [[org.bouncycastle.crypto.signers.HMacDSAKCalculator HMacDSAKCalculator]]
* with an added entropy parameter as well as two lines added adding the entropy to the hash.
*

View file

@ -0,0 +1,283 @@
package org.bitcoins.crypto
import org.bitcoin.NativeSecp256k1
import scodec.bits.ByteVector
/** This is an implementation of [[CryptoRuntime]] that defaults to libsecp256k1
* (https://github.com/bitcoin-core/secp256k1) when possible. All unsupported functions
* are delegated to [[BouncycastleCryptoRuntime]].
*/
trait LibSecp256k1CryptoRuntime extends CryptoRuntime {
override val cryptoContext: CryptoContext = CryptoContext.LibSecp256k1
override def freshPrivateKey: ECPrivateKey =
BouncycastleCryptoRuntime.freshPrivateKey
override def recoverPublicKey(
signature: ECDigitalSignature,
message: ByteVector): (ECPublicKey, ECPublicKey) =
BouncycastleCryptoRuntime.recoverPublicKey(signature, message)
override def hmac512(key: ByteVector, data: ByteVector): ByteVector =
BouncycastleCryptoRuntime.hmac512(key, data)
override def ripeMd160(bytes: ByteVector): RipeMd160Digest = {
BouncycastleCryptoRuntime.ripeMd160(bytes)
}
override def sha256(bytes: ByteVector): Sha256Digest = {
BouncycastleCryptoRuntime.sha256(bytes)
}
override def sha1(bytes: ByteVector): Sha1Digest = {
BouncycastleCryptoRuntime.sha1(bytes)
}
override def normalize(str: String): String = {
BouncycastleCryptoRuntime.normalize(str)
}
override def sha256Hash160(bytes: ByteVector): Sha256Hash160Digest = {
BouncycastleCryptoRuntime.sha256Hash160(bytes)
}
override def toPublicKey(
privateKey: ECPrivateKey,
isCompressed: Boolean): ECPublicKey = {
val pubKeyBytes: Array[Byte] =
NativeSecp256k1.computePubkey(privateKey.bytes.toArray, isCompressed)
val pubBytes = ByteVector(pubKeyBytes)
require(
ECPublicKey.isFullyValid(pubBytes),
s"secp256k1 failed to generate a valid public key, got: ${CryptoBytesUtil
.encodeHex(pubBytes)}")
ECPublicKey(pubBytes)
}
override def sign(
privateKey: ECPrivateKey,
dataToSign: ByteVector): ECDigitalSignature = {
val signature =
NativeSecp256k1.sign(dataToSign.toArray, privateKey.bytes.toArray)
ECDigitalSignature(ByteVector(signature))
}
override def signWithEntropy(
privateKey: ECPrivateKey,
bytes: ByteVector,
entropy: ByteVector): ECDigitalSignature = {
val sigBytes = NativeSecp256k1.signWithEntropy(bytes.toArray,
privateKey.bytes.toArray,
entropy.toArray)
ECDigitalSignature(ByteVector(sigBytes))
}
override def secKeyVerify(privateKeyBytes: ByteVector): Boolean =
NativeSecp256k1.secKeyVerify(privateKeyBytes.toArray)
override def verify(
publicKey: ECPublicKey,
data: ByteVector,
signature: ECDigitalSignature): Boolean = {
val result =
NativeSecp256k1.verify(data.toArray,
signature.bytes.toArray,
publicKey.bytes.toArray)
if (!result) {
//if signature verification fails with libsecp256k1 we need to use our old
//verification function from spongy castle, this is needed because early blockchain
//transactions can have weird non strict der encoded digital signatures
//bitcoin core implements this functionality here:
//https://github.com/bitcoin/bitcoin/blob/master/src/pubkey.cpp#L16-L165
BouncycastleCryptoRuntime.verify(publicKey, data, signature)
} else result
}
override def decompressed(publicKey: ECPublicKey): ECPublicKey = {
if (publicKey.isCompressed) {
val decompressed = NativeSecp256k1.decompress(publicKey.bytes.toArray)
ECPublicKey.fromBytes(ByteVector(decompressed))
} else publicKey
}
override def publicKey(privateKey: ECPrivateKey): ECPublicKey = {
val pubKeyBytes: Array[Byte] =
NativeSecp256k1.computePubkey(privateKey.bytes.toArray,
privateKey.isCompressed)
val pubBytes = ByteVector(pubKeyBytes)
require(
ECPublicKey.isFullyValid(pubBytes),
s"secp256k1 failed to generate a valid public key, got: ${CryptoBytesUtil
.encodeHex(pubBytes)}")
ECPublicKey(pubBytes)
}
override def tweakMultiply(
publicKey: ECPublicKey,
tweak: FieldElement): ECPublicKey = {
val mulBytes = NativeSecp256k1.pubKeyTweakMul(publicKey.bytes.toArray,
tweak.bytes.toArray,
publicKey.isCompressed)
ECPublicKey(ByteVector(mulBytes))
}
override def add(pk1: ECPrivateKey, pk2: ECPrivateKey): ECPrivateKey = {
val sumBytes =
NativeSecp256k1.privKeyTweakAdd(pk1.bytes.toArray, pk2.bytes.toArray)
ECPrivateKey(ByteVector(sumBytes))
}
override def add(pk1: ByteVector, pk2: ECPrivateKey): ByteVector = {
val sum =
NativeSecp256k1.privKeyTweakAdd(pk1.toArray, pk2.bytes.toArray)
ByteVector(sum)
}
override def add(pk1: ECPublicKey, pk2: ECPublicKey): ECPublicKey =
BouncycastleCryptoRuntime.add(pk1, pk2)
override def pubKeyTweakAdd(
pubkey: ECPublicKey,
privkey: ECPrivateKey): ECPublicKey = {
val tweaked = NativeSecp256k1.pubKeyTweakAdd(pubkey.bytes.toArray,
privkey.bytes.toArray,
privkey.isCompressed)
ECPublicKey(ByteVector(tweaked))
}
override def isValidPubKey(bytes: ByteVector): Boolean = {
try {
NativeSecp256k1.isValidPubKey(bytes.toArray)
} catch {
case scala.util.control.NonFatal(_) =>
false
}
}
override def isFullyValidWithBouncyCastle(bytes: ByteVector): Boolean =
bytes.nonEmpty && BouncycastleCryptoRuntime.isValidPubKey(bytes)
// TODO: add a native implementation
override def schnorrSign(
dataToSign: ByteVector,
privateKey: ECPrivateKey,
auxRand: ByteVector): SchnorrDigitalSignature = {
// val sigBytes =
// NativeSecp256k1.schnorrSign(dataToSign.toArray,
// privateKey.bytes.toArray,
// auxRand.toArray)
// SchnorrDigitalSignature(ByteVector(sigBytes))
BouncycastleCryptoRuntime.schnorrSign(dataToSign, privateKey, auxRand)
}
// TODO: add a native implementation
override def schnorrSignWithNonce(
dataToSign: ByteVector,
privateKey: ECPrivateKey,
nonceKey: ECPrivateKey): SchnorrDigitalSignature = {
// val sigBytes =
// NativeSecp256k1.schnorrSignWithNonce(dataToSign.toArray,
// privateKey.bytes.toArray,
// nonceKey.bytes.toArray)
// SchnorrDigitalSignature(ByteVector(sigBytes))
BouncycastleCryptoRuntime.schnorrSignWithNonce(dataToSign,
privateKey,
nonceKey)
}
// TODO: add a native implementation
override def schnorrVerify(
data: ByteVector,
schnorrPubKey: SchnorrPublicKey,
signature: SchnorrDigitalSignature): Boolean = {
BouncycastleCryptoRuntime.schnorrVerify(data, schnorrPubKey, signature)
}
// TODO: add a native implementation
override def schnorrComputeSigPoint(
data: ByteVector,
nonce: SchnorrNonce,
pubKey: SchnorrPublicKey,
compressed: Boolean): ECPublicKey = {
BouncycastleCryptoRuntime.schnorrComputeSigPoint(data,
nonce,
pubKey,
compressed)
}
// TODO: add a native implementation
override def adaptorSign(
key: ECPrivateKey,
adaptorPoint: ECPublicKey,
msg: ByteVector): ECAdaptorSignature = {
// val sigWithProof = NativeSecp256k1.adaptorSign(key.bytes.toArray,
// adaptorPoint.bytes.toArray,
// msg.toArray)
// ECAdaptorSignature(ByteVector(sigWithProof))
BouncycastleCryptoRuntime.adaptorSign(key, adaptorPoint, msg)
}
// TODO: add a native implementation
override def adaptorComplete(
key: ECPrivateKey,
adaptorSignature: ECAdaptorSignature): ECDigitalSignature = {
// val sigBytes =
// NativeSecp256k1.adaptorAdapt(key.bytes.toArray,
// adaptorSignature.adaptedSig.toArray)
// ECDigitalSignature.fromBytes(ByteVector(sigBytes))
BouncycastleCryptoRuntime.adaptorComplete(key, adaptorSignature)
}
// TODO: add a native implementation
override def extractAdaptorSecret(
signature: ECDigitalSignature,
adaptorSignature: ECAdaptorSignature,
key: ECPublicKey): ECPrivateKey = {
// val secretBytes = NativeSecp256k1.adaptorExtractSecret(
// signature.bytes.toArray,
// adaptorSignature.adaptedSig.toArray,
// key.bytes.toArray)
//
// ECPrivateKey(ByteVector(secretBytes))
BouncycastleCryptoRuntime.extractAdaptorSecret(signature,
adaptorSignature,
key)
}
// TODO: add a native implementation
override def adaptorVerify(
adaptorSignature: ECAdaptorSignature,
key: ECPublicKey,
msg: ByteVector,
adaptorPoint: ECPublicKey): Boolean = {
// NativeSecp256k1.adaptorVerify(adaptorSignature.adaptedSig.toArray,
// key.bytes.toArray,
// msg.toArray,
// adaptorPoint.bytes.toArray,
// adaptorSignature.dleqProof.toArray)
BouncycastleCryptoRuntime.adaptorVerify(adaptorSignature,
key,
msg,
adaptorPoint)
}
override def decodeSignature(
signature: ECDigitalSignature): (BigInt, BigInt) =
BouncycastleCryptoRuntime.decodeSignature(signature)
override def isValidSignatureEncoding(
signature: ECDigitalSignature): Boolean =
BouncycastleCryptoRuntime.isValidSignatureEncoding(signature)
override def isDEREncoded(signature: ECDigitalSignature): Boolean =
BouncycastleCryptoRuntime.isDEREncoded(signature)
override def sipHash(item: ByteVector, key: SipHashKey): Long =
BouncycastleCryptoRuntime.sipHash(item, key)
}
object LibSecp256k1CryptoRuntime extends LibSecp256k1CryptoRuntime

View file

@ -1,11 +1,10 @@
package org.bitcoins.crypto
import java.security.SecureRandom
import javax.crypto.spec.{IvParameterSpec, SecretKeySpec}
import javax.crypto.{BadPaddingException, Cipher, SecretKey}
import scodec.bits.ByteVector
import java.security.SecureRandom
import javax.crypto.spec.{IvParameterSpec, SecretKeySpec}
import javax.crypto.{BadPaddingException, Cipher, SecretKey}
import scala.util.{Failure, Success, Try}
/** Represents a encrypted cipher text with it's accompanying

View file

@ -1,7 +1,5 @@
package org.bitcoins.crypto
import org.bitcoin.Secp256k1Context
sealed trait CryptoContext
object CryptoContext {
@ -10,26 +8,10 @@ object CryptoContext {
case object BouncyCastle extends CryptoContext
def default: CryptoContext = {
val secpDisabled = System.getenv("DISABLE_SECP256K1")
if (
secpDisabled != null && (secpDisabled.toLowerCase == "true" || secpDisabled == "1")
) {
BouncyCastle
} else {
if (Secp256k1Context.isEnabled) {
LibSecp256k1
} else {
BouncyCastle
}
}
}
case object BCrypto extends CryptoContext
lazy val cryptoRuntime: CryptoRuntime = CryptoRuntimeFactory.newCryptoRuntime
lazy val default: CryptoContext = cryptoRuntime.cryptoContext
/** The platform specific cryptographic functions required to run bitcoin-s */
lazy val cryptoRuntime: CryptoRuntime = {
default match {
case LibSecp256k1 => JvmCryptoRuntime
case BouncyCastle => JvmCryptoRuntime
}
}
}

View file

@ -1,15 +1,14 @@
package org.bitcoins.crypto
import org.bouncycastle.math.ec.ECPoint
import scodec.bits.{BitVector, ByteVector}
import java.math.BigInteger
/** Trait that should be extended by specific runtimes like javascript
* or the JVM to support crypto functions needed for bitcoin-s
*/
trait CryptoRuntime {
val cryptoContext: CryptoContext
/** Generates a 32 byte private key */
def freshPrivateKey: ECPrivateKey
@ -80,7 +79,7 @@ trait CryptoRuntime {
}
def sha256DLCAttestation(str: String): Sha256Digest = {
sha256DLCAttestation(CryptoUtil.serializeForHash(str))
sha256DLCAttestation(serializeForHash(str))
}
// The tag "DLC/oracle/announcement/v0"
@ -95,12 +94,6 @@ trait CryptoRuntime {
sha256(dlcAnnouncementTagBytes ++ bytes)
}
/** @param x x coordinate
* @return a tuple (p1, p2) where p1 and p2 are points on the curve and p1.x = p2.x = x
* p1.y is even, p2.y is odd
*/
def recoverPoint(x: BigInteger): (ECPoint, ECPoint)
/** Recover public keys from a signature and the message that was signed. This method will return 2 public keys, and the signature
* can be verified with both, but only one of them matches that private key that was used to generate the signature.
*
@ -136,4 +129,86 @@ trait CryptoRuntime {
def sha256SchnorrNonce(bytes: ByteVector): Sha256Digest = {
sha256(schnorrNonceTagBytes ++ bytes)
}
def publicKey(privateKey: ECPrivateKey): ECPublicKey
def sign(privateKey: ECPrivateKey, dataToSign: ByteVector): ECDigitalSignature
def signWithEntropy(
privateKey: ECPrivateKey,
bytes: ByteVector,
entropy: ByteVector): ECDigitalSignature
def secKeyVerify(privateKeybytes: ByteVector): Boolean
def verify(
publicKey: ECPublicKey,
data: ByteVector,
signature: ECDigitalSignature): Boolean
def decompressed(publicKey: ECPublicKey): ECPublicKey
def tweakMultiply(publicKey: ECPublicKey, tweak: FieldElement): ECPublicKey
def add(pk1: ECPrivateKey, pk2: ECPrivateKey): ECPrivateKey
def add(bytes: ByteVector, pk2: ECPrivateKey): ByteVector
def add(pk1: ECPublicKey, pk2: ECPublicKey): ECPublicKey
def pubKeyTweakAdd(pubkey: ECPublicKey, privkey: ECPrivateKey): ECPublicKey
def isValidPubKey(bytes: ByteVector): Boolean
def isFullyValidWithBouncyCastle(bytes: ByteVector): Boolean
def schnorrSign(
dataToSign: ByteVector,
privateKey: ECPrivateKey,
auxRand: ByteVector): SchnorrDigitalSignature
def schnorrSignWithNonce(
dataToSign: ByteVector,
privateKey: ECPrivateKey,
nonceKey: ECPrivateKey): SchnorrDigitalSignature
def schnorrVerify(
data: ByteVector,
schnorrPubKey: SchnorrPublicKey,
signature: SchnorrDigitalSignature): Boolean
def schnorrComputeSigPoint(
data: ByteVector,
nonce: SchnorrNonce,
pubKey: SchnorrPublicKey,
compressed: Boolean): ECPublicKey
def adaptorSign(
key: ECPrivateKey,
adaptorPoint: ECPublicKey,
msg: ByteVector): ECAdaptorSignature
def adaptorComplete(
key: ECPrivateKey,
adaptorSignature: ECAdaptorSignature): ECDigitalSignature
def extractAdaptorSecret(
signature: ECDigitalSignature,
adaptorSignature: ECAdaptorSignature,
key: ECPublicKey): ECPrivateKey
def adaptorVerify(
adaptorSignature: ECAdaptorSignature,
key: ECPublicKey,
msg: ByteVector,
adaptorPoint: ECPublicKey): Boolean
def decodeSignature(signature: ECDigitalSignature): (BigInt, BigInt)
def isValidSignatureEncoding(signature: ECDigitalSignature): Boolean
def isDEREncoded(signature: ECDigitalSignature): Boolean
/** https://github.com/bitcoin/bips/blob/master/bip-0158.mediawiki#hashing-data-objects */
def sipHash(item: ByteVector, key: SipHashKey): Long
}

View file

@ -1,13 +1,10 @@
package org.bitcoins.crypto
import org.bouncycastle.math.ec.ECPoint
import scodec.bits.ByteVector
import java.math.BigInteger
/** Utility cryptographic functions
* This is a proxy for the underlying implementation of [[CryptoRuntime]]
* such as [[JvmCryptoRuntime]].
* such as [[LibSecp256k1CryptoRuntime]].
*
* This is necessary so that the core module doesn't need to be refactored
* to add support for multiple platforms, it can keep referencing CryptoUtil
@ -17,6 +14,8 @@ trait CryptoUtil extends CryptoRuntime {
/** The underlying runtime for the specific platform we are running on */
private lazy val cryptoRuntime: CryptoRuntime = CryptoContext.cryptoRuntime
override lazy val cryptoContext: CryptoContext = cryptoRuntime.cryptoContext
override def freshPrivateKey: ECPrivateKey = {
cryptoRuntime.freshPrivateKey
}
@ -41,7 +40,7 @@ trait CryptoUtil extends CryptoRuntime {
}
def doubleSHA256(str: String): DoubleSha256Digest = {
doubleSHA256(serializeForHash(str))
cryptoRuntime.doubleSHA256(serializeForHash(str))
}
/** Takes sha256(bytes). */
@ -50,7 +49,7 @@ trait CryptoUtil extends CryptoRuntime {
}
def taggedSha256(str: String, tag: String): Sha256Digest = {
taggedSha256(serializeForHash(str), tag)
cryptoRuntime.taggedSha256(serializeForHash(str), tag)
}
/** Performs SHA1(bytes). */
@ -59,7 +58,7 @@ trait CryptoUtil extends CryptoRuntime {
}
def sha1(str: String): Sha1Digest = {
sha1(serializeForHash(str))
cryptoRuntime.sha1(serializeForHash(str))
}
/** Performs RIPEMD160(bytes). */
@ -68,7 +67,7 @@ trait CryptoUtil extends CryptoRuntime {
}
def ripeMd160(str: String): RipeMd160Digest = {
ripeMd160(serializeForHash(str))
cryptoRuntime.ripeMd160(serializeForHash(str))
}
/** Calculates `HMAC-SHA512(key, data)`
@ -77,19 +76,125 @@ trait CryptoUtil extends CryptoRuntime {
cryptoRuntime.hmac512(key, data)
}
/** @param x x coordinate
* @return a tuple (p1, p2) where p1 and p2 are points on the curve and p1.x = p2.x = x
* p1.y is even, p2.y is odd
*/
def recoverPoint(x: BigInteger): (ECPoint, ECPoint) = {
cryptoRuntime.recoverPoint(x)
}
override def recoverPublicKey(
signature: ECDigitalSignature,
message: ByteVector): (ECPublicKey, ECPublicKey) = {
cryptoRuntime.recoverPublicKey(signature, message)
}
override def publicKey(privateKey: ECPrivateKey): ECPublicKey =
cryptoRuntime.publicKey(privateKey)
override def sign(
privateKey: ECPrivateKey,
dataToSign: ByteVector): ECDigitalSignature =
cryptoRuntime.sign(privateKey, dataToSign)
override def signWithEntropy(
privateKey: ECPrivateKey,
bytes: ByteVector,
entropy: ByteVector): ECDigitalSignature =
cryptoRuntime.signWithEntropy(privateKey, bytes, entropy)
override def secKeyVerify(privateKeybytes: ByteVector): Boolean =
cryptoRuntime.secKeyVerify(privateKeybytes)
override def verify(
publicKey: ECPublicKey,
data: ByteVector,
signature: ECDigitalSignature): Boolean =
cryptoRuntime.verify(publicKey, data, signature)
override def decompressed(publicKey: ECPublicKey): ECPublicKey =
cryptoRuntime.decompressed(publicKey)
override def tweakMultiply(
publicKey: ECPublicKey,
tweak: FieldElement): ECPublicKey =
cryptoRuntime.tweakMultiply(publicKey, tweak)
override def add(pk1: ECPrivateKey, pk2: ECPrivateKey): ECPrivateKey =
cryptoRuntime.add(pk1, pk2)
override def add(pk1: ByteVector, pk2: ECPrivateKey): ByteVector =
cryptoRuntime.add(pk1, pk2)
override def add(pk1: ECPublicKey, pk2: ECPublicKey): ECPublicKey =
cryptoRuntime.add(pk1, pk2)
override def pubKeyTweakAdd(
pubkey: ECPublicKey,
privkey: ECPrivateKey): ECPublicKey =
cryptoRuntime.pubKeyTweakAdd(pubkey, privkey)
override def isValidPubKey(bytes: ByteVector): Boolean =
cryptoRuntime.isValidPubKey(bytes)
override def isFullyValidWithBouncyCastle(bytes: ByteVector): Boolean =
cryptoRuntime.isFullyValidWithBouncyCastle(bytes)
override def schnorrSign(
dataToSign: ByteVector,
privateKey: ECPrivateKey,
auxRand: ByteVector): SchnorrDigitalSignature =
cryptoRuntime.schnorrSign(dataToSign, privateKey, auxRand)
override def schnorrSignWithNonce(
dataToSign: ByteVector,
privateKey: ECPrivateKey,
nonceKey: ECPrivateKey): SchnorrDigitalSignature =
cryptoRuntime.schnorrSignWithNonce(dataToSign, privateKey, nonceKey)
override def schnorrVerify(
data: ByteVector,
schnorrPubKey: SchnorrPublicKey,
signature: SchnorrDigitalSignature): Boolean =
cryptoRuntime.schnorrVerify(data, schnorrPubKey, signature)
override def schnorrComputeSigPoint(
data: ByteVector,
nonce: SchnorrNonce,
pubKey: SchnorrPublicKey,
compressed: Boolean): ECPublicKey =
cryptoRuntime.schnorrComputeSigPoint(data, nonce, pubKey, compressed)
override def adaptorSign(
key: ECPrivateKey,
adaptorPoint: ECPublicKey,
msg: ByteVector): ECAdaptorSignature =
cryptoRuntime.adaptorSign(key, adaptorPoint, msg)
override def adaptorComplete(
key: ECPrivateKey,
adaptorSignature: ECAdaptorSignature): ECDigitalSignature =
cryptoRuntime.adaptorComplete(key, adaptorSignature)
override def extractAdaptorSecret(
signature: ECDigitalSignature,
adaptorSignature: ECAdaptorSignature,
key: ECPublicKey): ECPrivateKey =
cryptoRuntime.extractAdaptorSecret(signature, adaptorSignature, key)
override def adaptorVerify(
adaptorSignature: ECAdaptorSignature,
key: ECPublicKey,
msg: ByteVector,
adaptorPoint: ECPublicKey): Boolean =
cryptoRuntime.adaptorVerify(adaptorSignature, key, msg, adaptorPoint)
override def decodeSignature(
signature: ECDigitalSignature): (BigInt, BigInt) =
cryptoRuntime.decodeSignature(signature)
override def isValidSignatureEncoding(
signature: ECDigitalSignature): Boolean =
cryptoRuntime.isValidSignatureEncoding(signature)
override def isDEREncoded(signature: ECDigitalSignature): Boolean =
cryptoRuntime.isDEREncoded(signature)
override def sipHash(item: ByteVector, key: SipHashKey): Long =
cryptoRuntime.sipHash(item, key)
}
object CryptoUtil extends CryptoUtil

View file

@ -20,9 +20,9 @@ case class ECAdaptorSignature(bytes: ByteVector) extends NetworkElement {
val dleqProofS: FieldElement = FieldElement(dleqProof.drop(33).take(32))
val dleqProofE: FieldElement = FieldElement(dleqProof.drop(65))
require(ECPublicKey.isFullyValidWithBouncyCastle(tweakedNonce.bytes),
require(CryptoUtil.isFullyValidWithBouncyCastle(tweakedNonce.bytes),
s"Tweaked nonce (R) must be a valid public key: $tweakedNonce")
require(ECPublicKey.isFullyValidWithBouncyCastle(untweakedNonce.bytes),
require(CryptoUtil.isFullyValidWithBouncyCastle(untweakedNonce.bytes),
s"Untweaked nonce (R') must be a valid public key: $untweakedNonce")
}

View file

@ -24,19 +24,19 @@ sealed abstract class ECDigitalSignature extends NetworkElement {
* https://crypto.stackexchange.com/questions/1795/how-can-i-convert-a-der-ecdsa-signature-to-asn-1
* @return boolean representing if the signature is a valid
*/
def isDEREncoded: Boolean = DERSignatureUtil.isDEREncoded(this)
def isDEREncoded: Boolean = CryptoUtil.isDEREncoded(this)
/** Checks if the signature is strictly der encoded as per BIP66
* [[https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki]]
*/
def isStrictEncoded: Boolean = DERSignatureUtil.isValidSignatureEncoding(this)
def isStrictEncoded: Boolean = CryptoUtil.isValidSignatureEncoding(this)
/** Decodes the digital signature into it's r and s points
* throws an exception if the given sequence of bytes is not a DER encoded signature
* @return the (r,s) values for the elliptic curve digital signature
*/
lazy val decodeSignature: (BigInt, BigInt) =
DERSignatureUtil.decodeSignature(this)
CryptoUtil.decodeSignature(this)
/** Represents the r value found in a elliptic curve digital signature */
def r: BigInt = {

View file

@ -1,11 +1,8 @@
package org.bitcoins.crypto
import java.math.BigInteger
import org.bitcoin.NativeSecp256k1
import org.bouncycastle.math.ec.ECPoint
import scodec.bits.ByteVector
import java.math.BigInteger
import scala.annotation.tailrec
import scala.concurrent.ExecutionContext.Implicits
import scala.concurrent.{ExecutionContext, Future}
@ -32,27 +29,7 @@ sealed abstract class ECPrivateKey
* @return the digital signature
*/
override def sign(dataToSign: ByteVector): ECDigitalSignature = {
sign(dataToSign, CryptoContext.default)
}
def sign(
dataToSign: ByteVector,
context: CryptoContext): ECDigitalSignature = {
require(dataToSign.length == 32 && bytes.length <= 32)
context match {
case CryptoContext.LibSecp256k1 => signWithSecp(dataToSign)
case CryptoContext.BouncyCastle => signWithBouncyCastle(dataToSign)
}
}
def signWithSecp(dataToSign: ByteVector): ECDigitalSignature = {
val signature =
NativeSecp256k1.sign(dataToSign.toArray, bytes.toArray)
ECDigitalSignature(ByteVector(signature))
}
def signWithBouncyCastle(dataToSign: ByteVector): ECDigitalSignature = {
BouncyCastleUtil.sign(dataToSign, this)
CryptoUtil.sign(this, dataToSign)
}
def sign(hash: HashDigest): ECDigitalSignature = sign(hash.bytes)
@ -64,34 +41,7 @@ sealed abstract class ECPrivateKey
override def signWithEntropy(
bytes: ByteVector,
entropy: ByteVector): ECDigitalSignature = {
signWithEntropy(bytes, entropy, CryptoContext.default)
}
def signWithEntropy(
bytes: ByteVector,
entropy: ByteVector,
context: CryptoContext): ECDigitalSignature = {
context match {
case CryptoContext.LibSecp256k1 => signWithEntropyWithSecp(bytes, entropy)
case CryptoContext.BouncyCastle =>
signWithEntropyWithBouncyCastle(bytes, entropy)
}
}
def signWithEntropyWithSecp(
bytes: ByteVector,
entropy: ByteVector): ECDigitalSignature = {
val sigBytes = NativeSecp256k1.signWithEntropy(bytes.toArray,
this.bytes.toArray,
entropy.toArray)
ECDigitalSignature(ByteVector(sigBytes))
}
def signWithEntropyWithBouncyCastle(
bytes: ByteVector,
entropy: ByteVector): ECDigitalSignature = {
BouncyCastleUtil.signWithEntropy(bytes, this, entropy)
CryptoUtil.signWithEntropy(this, bytes, entropy)
}
override def signWithEntropyFunction: (
@ -106,99 +56,27 @@ sealed abstract class ECPrivateKey
schnorrSign(dataToSign, auxRand)
}
// TODO: match on CryptoContext once secp version is added
def schnorrSign(
dataToSign: ByteVector,
auxRand: ByteVector): SchnorrDigitalSignature = {
schnorrSignWithBouncyCastle(dataToSign, auxRand)
CryptoUtil.schnorrSign(dataToSign, this, auxRand)
}
/*
def schnorrSignWithSecp(
dataToSign: ByteVector,
auxRand: ByteVector): SchnorrDigitalSignature = {
val sigBytes =
NativeSecp256k1.schnorrSign(dataToSign.toArray,
bytes.toArray,
auxRand.toArray)
SchnorrDigitalSignature(ByteVector(sigBytes))
}
*/
def schnorrSignWithBouncyCastle(
dataToSign: ByteVector,
auxRand: ByteVector): SchnorrDigitalSignature = {
BouncyCastleUtil.schnorrSign(dataToSign, this, auxRand)
}
// TODO: match on CryptoContext once secp version is added
def schnorrSignWithNonce(
dataToSign: ByteVector,
nonce: ECPrivateKey): SchnorrDigitalSignature = {
schnorrSignWithNonceWithBouncyCastle(dataToSign, nonce)
CryptoUtil.schnorrSignWithNonce(dataToSign, this, nonce)
}
/*
def schnorrSignWithNonceWithSecp(
dataToSign: ByteVector,
nonce: ECPrivateKey): SchnorrDigitalSignature = {
val sigBytes =
NativeSecp256k1.schnorrSignWithNonce(dataToSign.toArray,
bytes.toArray,
nonce.bytes.toArray)
SchnorrDigitalSignature(ByteVector(sigBytes))
}
*/
def schnorrSignWithNonceWithBouncyCastle(
dataToSign: ByteVector,
nonce: ECPrivateKey): SchnorrDigitalSignature = {
BouncyCastleUtil.schnorrSignWithNonce(dataToSign, this, nonce)
}
// TODO: match on CryptoContext once secp version is added
def adaptorSign(
adaptorPoint: ECPublicKey,
msg: ByteVector): ECAdaptorSignature = {
adaptorSignWithBouncyCastle(adaptorPoint, msg)
CryptoUtil.adaptorSign(this, adaptorPoint, msg)
}
/*
def adaptorSignWithSecp(
adaptorPoint: ECPublicKey,
msg: ByteVector): ECAdaptorSignature = {
val sigWithProof = NativeSecp256k1.adaptorSign(bytes.toArray,
adaptorPoint.bytes.toArray,
msg.toArray)
ECAdaptorSignature(ByteVector(sigWithProof))
}
*/
def adaptorSignWithBouncyCastle(
adaptorPoint: ECPublicKey,
msg: ByteVector): ECAdaptorSignature = {
AdaptorStuff.adaptorSign(this, adaptorPoint, msg)
}
// TODO: match on CryptoContext once secp version is added
def completeAdaptorSignature(
adaptorSignature: ECAdaptorSignature): ECDigitalSignature = {
completeAdaptorSignatureWithBouncyCastle(adaptorSignature)
}
/*
def completeAdaptorSignatureWithSecp(
adaptorSignature: ECAdaptorSignature): ECDigitalSignature = {
val sigBytes = NativeSecp256k1.adaptorAdapt(
bytes.toArray,
adaptorSignature.adaptedSig.toArray)
ECDigitalSignature.fromBytes(ByteVector(sigBytes))
}
*/
def completeAdaptorSignatureWithBouncyCastle(
adaptorSignature: ECAdaptorSignature): ECDigitalSignature = {
AdaptorStuff.adaptorComplete(this, adaptorSignature.adaptedSig)
CryptoUtil.adaptorComplete(this, adaptorSignature)
}
def completeAdaptorSignature(
@ -224,60 +102,25 @@ sealed abstract class ECPrivateKey
}
}
// CryptoParams.curve.getN
private val N: BigInteger = new BigInteger(
"115792089237316195423570985008687907852837564279074904382605163141518161494337")
def negate: ECPrivateKey = {
val negPrivKeyNum = CryptoParams.curve.getN
.subtract(new BigInteger(1, bytes.toArray))
val negPrivKeyNum = N.subtract(new BigInteger(1, bytes.toArray))
ECPrivateKey(ByteVector(negPrivKeyNum.toByteArray))
}
def add(other: ECPrivateKey): ECPrivateKey = {
add(other, CryptoContext.default)
}
def add(other: ECPrivateKey, context: CryptoContext): ECPrivateKey = {
context match {
case CryptoContext.LibSecp256k1 => addWithSecp(other)
case CryptoContext.BouncyCastle => addWithBouncyCastle(other)
}
}
def addWithSecp(other: ECPrivateKey): ECPrivateKey = {
val sumBytes =
NativeSecp256k1.privKeyTweakAdd(bytes.toArray, other.bytes.toArray)
ECPrivateKey(ByteVector(sumBytes))
}
def addWithBouncyCastle(other: ECPrivateKey): ECPrivateKey = {
fieldElement.add(other.fieldElement).toPrivateKey
CryptoUtil.add(this, other)
}
/** Signifies if the this private key corresponds to a compressed public key */
def isCompressed: Boolean
override def publicKey: ECPublicKey = publicKey(CryptoContext.default)
/** Derives the public for a the private key */
def publicKey(context: CryptoContext): ECPublicKey = {
context match {
case CryptoContext.LibSecp256k1 => publicKeyWithSecp
case CryptoContext.BouncyCastle => publicKeyWithBouncyCastle
}
}
def publicKeyWithSecp: ECPublicKey = {
val pubKeyBytes: Array[Byte] =
NativeSecp256k1.computePubkey(bytes.toArray, isCompressed)
val pubBytes = ByteVector(pubKeyBytes)
require(
ECPublicKey.isFullyValid(pubBytes),
s"secp256k1 failed to generate a valid public key, got: ${CryptoBytesUtil
.encodeHex(pubBytes)}")
ECPublicKey(pubBytes)
}
def publicKeyWithBouncyCastle: ECPublicKey = {
BouncyCastleUtil.computePublicKey(this)
}
override def publicKey: ECPublicKey =
CryptoUtil.publicKey(this)
def schnorrPublicKey: SchnorrPublicKey = {
SchnorrPublicKey(publicKey.bytes)
@ -299,15 +142,7 @@ object ECPrivateKey extends Factory[ECPrivateKey] {
isCompressed: Boolean,
ec: ExecutionContext)
extends ECPrivateKey {
CryptoContext.default match {
case CryptoContext.LibSecp256k1 =>
require(NativeSecp256k1.secKeyVerify(bytes.toArray),
s"Invalid key according to secp256k1, hex: ${bytes.toHex}")
case CryptoContext.BouncyCastle =>
require(CryptoParams.curve.getCurve
.isValidFieldElement(new BigInteger(1, bytes.toArray)),
s"Invalid key according to Bouncy Castle, hex: ${bytes.toHex}")
}
require(CryptoUtil.secKeyVerify(bytes), s"Invalid key, hex: ${bytes.toHex}")
}
def apply(bytes: ByteVector, isCompressed: Boolean)(implicit
@ -368,41 +203,7 @@ sealed abstract class ECPublicKey extends BaseECKey {
* [[org.bitcoins.crypto.ECPublicKey ECPublicKey]].
*/
def verify(data: ByteVector, signature: ECDigitalSignature): Boolean = {
verify(data, signature, CryptoContext.default)
}
def verify(
data: ByteVector,
signature: ECDigitalSignature,
context: CryptoContext): Boolean = {
context match {
case CryptoContext.LibSecp256k1 => verifyWithSecp(data, signature)
case CryptoContext.BouncyCastle => verifyWithBouncyCastle(data, signature)
}
}
def verifyWithSecp(
data: ByteVector,
signature: ECDigitalSignature): Boolean = {
val result =
NativeSecp256k1.verify(data.toArray,
signature.bytes.toArray,
bytes.toArray)
if (!result) {
//if signature verification fails with libsecp256k1 we need to use our old
//verification function from spongy castle, this is needed because early blockchain
//transactions can have weird non strict der encoded digital signatures
//bitcoin core implements this functionality here:
//https://github.com/bitcoin/bitcoin/blob/master/src/pubkey.cpp#L16-L165
verifyWithBouncyCastle(data, signature)
} else result
}
def verifyWithBouncyCastle(
data: ByteVector,
signature: ECDigitalSignature): Boolean = {
BouncyCastleUtil.verifyDigitalSignature(data, this, signature)
CryptoUtil.verify(this, data, signature)
}
def verify(hex: String, signature: ECDigitalSignature): Boolean =
@ -425,58 +226,17 @@ sealed abstract class ECPublicKey extends BaseECKey {
def schnorrNonce: SchnorrNonce = SchnorrNonce(bytes)
// TODO: match on CryptoContext once secp version is added
def adaptorVerify(
msg: ByteVector,
adaptorPoint: ECPublicKey,
adaptorSignature: ECAdaptorSignature): Boolean = {
adaptorVerifyWithBouncyCastle(msg, adaptorPoint, adaptorSignature)
CryptoUtil.adaptorVerify(adaptorSignature, this, msg, adaptorPoint)
}
/*
def adaptorVerifyWithSecp(
msg: ByteVector,
adaptorPoint: ECPublicKey,
adaptorSignature: ECAdaptorSignature): Boolean = {
NativeSecp256k1.adaptorVerify(adaptorSignature.adaptedSig.toArray,
bytes.toArray,
msg.toArray,
adaptorPoint.bytes.toArray,
adaptorSignature.dleqProof.toArray)
}
*/
def adaptorVerifyWithBouncyCastle(
msg: ByteVector,
adaptorPoint: ECPublicKey,
adaptorSignature: ECAdaptorSignature): Boolean = {
AdaptorStuff.adaptorVerify(adaptorSignature, this, msg, adaptorPoint)
}
// TODO: match on CryptoContext once secp version is added
def extractAdaptorSecret(
adaptorSignature: ECAdaptorSignature,
signature: ECDigitalSignature): ECPrivateKey = {
extractAdaptorSecretWithBouncyCastle(adaptorSignature, signature)
}
/*
def extractAdaptorSecretWithSecp(
adaptorSignature: ECAdaptorSignature,
signature: ECDigitalSignature): ECPrivateKey = {
val secretBytes = NativeSecp256k1.adaptorExtractSecret(
signature.bytes.toArray,
adaptorSignature.adaptedSig.toArray,
bytes.toArray)
ECPrivateKey(ByteVector(secretBytes))
}
*/
def extractAdaptorSecretWithBouncyCastle(
adaptorSignature: ECAdaptorSignature,
signature: ECDigitalSignature): ECPrivateKey = {
AdaptorStuff.extractAdaptorSecret(signature, adaptorSignature, this)
CryptoUtil.extractAdaptorSecret(signature, adaptorSignature, this)
}
override def toString: String = "ECPublicKey(" + hex + ")"
@ -488,72 +248,19 @@ sealed abstract class ECPublicKey extends BaseECKey {
def isFullyValid: Boolean = ECPublicKey.isFullyValid(bytes)
/** Returns the decompressed version of this [[org.bitcoins.crypto.ECPublicKey ECPublicKey]] */
def decompressed: ECPublicKey = decompressed(CryptoContext.default)
def decompressed(context: CryptoContext): ECPublicKey = {
context match {
case CryptoContext.LibSecp256k1 => decompressedWithSecp
case CryptoContext.BouncyCastle => decompressedWithBouncyCastle
}
}
def decompressedWithSecp: ECPublicKey = {
if (isCompressed) {
val decompressed = NativeSecp256k1.decompress(bytes.toArray)
ECPublicKey.fromBytes(ByteVector(decompressed))
} else this
}
def decompressedWithBouncyCastle: ECPublicKey = {
BouncyCastleUtil.decompressPublicKey(this)
}
/** Decodes a [[org.bitcoins.crypto.ECPublicKey ECPublicKey]] in bitcoin-s
* to a [[org.bouncycastle.math.ec.ECPoint ECPoint]] data structure that is internal to the
* bouncy castle library
* @return
*/
def toPoint: ECPoint = {
BouncyCastleUtil.decodePoint(bytes)
}
def decompressed: ECPublicKey =
CryptoUtil.decompressed(this)
/** Adds this ECPublicKey to another as points and returns the resulting ECPublicKey.
*
* Note: if this ever becomes a bottleneck, secp256k1_ec_pubkey_combine should
* get wrapped in NativeSecp256k1 to speed things up.
*/
def add(otherKey: ECPublicKey): ECPublicKey = {
addWithBouncyCastle(otherKey)
}
def addWithBouncyCastle(otherKey: ECPublicKey): ECPublicKey = {
val sumPoint = toPoint.add(otherKey.toPoint)
ECPublicKey.fromPoint(sumPoint)
}
def add(otherKey: ECPublicKey): ECPublicKey =
CryptoUtil.add(this, otherKey)
def tweakMultiply(tweak: FieldElement): ECPublicKey = {
tweakMultiply(tweak, CryptoContext.default)
}
def tweakMultiply(
tweak: FieldElement,
context: CryptoContext): ECPublicKey = {
context match {
case CryptoContext.LibSecp256k1 => tweakMultiplyWithSecp(tweak)
case CryptoContext.BouncyCastle => tweakMultiplyWithBouncyCastle(tweak)
}
}
def tweakMultiplyWithSecp(tweak: FieldElement): ECPublicKey = {
val mulBytes = NativeSecp256k1.pubKeyTweakMul(bytes.toArray,
tweak.bytes.toArray,
isCompressed)
ECPublicKey(ByteVector(mulBytes))
}
def tweakMultiplyWithBouncyCastle(tweak: FieldElement): ECPublicKey = {
BouncyCastleUtil.pubKeyTweakMul(this, tweak.bytes)
CryptoUtil.tweakMultiply(this, tweak)
}
}
@ -587,40 +294,11 @@ object ECPublicKey extends Factory[ECPublicKey] {
* Mimics this function in bitcoin core
* [[https://github.com/bitcoin/bitcoin/blob/27765b6403cece54320374b37afb01a0cfe571c3/src/pubkey.cpp#L207-L212]]
*/
def isFullyValid(bytes: ByteVector): Boolean = {
isFullyValid(bytes, CryptoContext.default)
}
def isFullyValid(bytes: ByteVector, context: CryptoContext): Boolean = {
context match {
case CryptoContext.LibSecp256k1 => isFullyValidWithSecp(bytes)
case CryptoContext.BouncyCastle => isFullyValidWithBouncyCastle(bytes)
}
}
def isFullyValidWithSecp(bytes: ByteVector): Boolean = {
try {
NativeSecp256k1.isValidPubKey(bytes.toArray) && isValid(bytes)
} catch {
case scala.util.control.NonFatal(_) =>
false
}
}
def isFullyValidWithBouncyCastle(bytes: ByteVector): Boolean = {
BouncyCastleUtil.validatePublicKey(bytes) && isValid(bytes)
}
def isFullyValid(bytes: ByteVector): Boolean =
isValid(bytes) && CryptoUtil.isValidPubKey(bytes)
/** Mimics the CPubKey::IsValid function in Bitcoin core, this is a consensus rule
* [[https://github.com/bitcoin/bitcoin/blob/27765b6403cece54320374b37afb01a0cfe571c3/src/pubkey.h#L158]]
*/
def isValid(bytes: ByteVector): Boolean = bytes.nonEmpty
/** Creates a [[org.bitcoins.crypto.ECPublicKey ECPublicKey]] from the
* [[org.bouncycastle.math.ec.ECPoint ECPoint]] data structure used internally inside of Bouncy Castle
*/
def fromPoint(p: ECPoint, isCompressed: Boolean = true): ECPublicKey = {
val bytes = p.getEncoded(isCompressed)
ECPublicKey.fromBytes(ByteVector(bytes))
}
}

View file

@ -1,10 +1,8 @@
package org.bitcoins.crypto
import java.math.BigInteger
import org.bouncycastle.math.ec.ECPoint
import scodec.bits.ByteVector
import java.math.BigInteger
import scala.util.Try
/** Represents integers modulo the secp256k1 field size: pow(2,256) - 0x1000003D1.
@ -52,8 +50,6 @@ case class FieldElement(bytes: ByteVector) extends NetworkElement {
FieldElement.negate(this)
}
def getPoint: ECPoint = FieldElement.computePoint(this)
def getPublicKey: ECPublicKey = toPrivateKey.publicKey
def inverse: FieldElement = FieldElement.computeInverse(this)
@ -92,8 +88,13 @@ object FieldElement extends Factory[FieldElement] {
val nMinusOne: FieldElement = FieldElement(
"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140")
private val G: ECPoint = CryptoParams.curve.getG
private val N: BigInteger = CryptoParams.curve.getN
// CryptoParams.curve.getG
private val G: ECPublicKey = ECPublicKey(
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")
// CryptoParams.curve.getN
private val N: BigInteger = new BigInteger(
"115792089237316195423570985008687907852837564279074904382605163141518161494337")
private def getBigInteger(bytes: ByteVector): BigInteger = {
new BigInteger(1, bytes.toArray)
@ -120,7 +121,7 @@ object FieldElement extends Factory[FieldElement] {
FieldElement(neg)
}
def computePoint(fe: FieldElement): ECPoint = G.multiply(fe.toBigInteger)
def computePoint(fe: FieldElement): ECPublicKey = G.tweakMultiply(fe)
/** Computes the inverse (mod M) of the input using the Euclidean Algorithm (log time)
* Cribbed from [[https://www.geeksforgeeks.org/multiplicative-inverse-under-modulo-m/]]

View file

@ -1,8 +1,9 @@
package org.bitcoins.crypto
import scodec.bits.ByteVector
import javax.crypto.spec.PBEKeySpec
import javax.crypto.{SecretKey, SecretKeyFactory}
import scodec.bits.ByteVector
/** @define keyStretch Derives the provided value and salt to a secret key
* using the PBKDF2 key derivation function

View file

@ -26,7 +26,8 @@ object SchnorrNonce extends Factory[SchnorrNonce] {
auxRand: ByteVector): ECPrivateKey = {
val privKeyForUse = privKey.schnorrKey
val randHash = CryptoUtil.sha256SchnorrAuxRand(auxRand).bytes
val randHash =
CryptoUtil.sha256SchnorrAuxRand(auxRand).bytes
val maskedKey = randHash.xor(privKeyForUse.bytes)
val nonceHash = CryptoUtil.sha256SchnorrNonce(

View file

@ -1,6 +1,5 @@
package org.bitcoins.crypto
import org.bitcoin.NativeSecp256k1
import scodec.bits.ByteVector
import scala.annotation.tailrec
@ -12,25 +11,8 @@ case class SchnorrPublicKey(bytes: ByteVector) extends NetworkElement {
require(Try(publicKey).isSuccess,
s"Schnorr public key must be a valid x coordinate, got $bytes")
// TODO: match on CryptoContext once secp version is added
def verify(data: ByteVector, signature: SchnorrDigitalSignature): Boolean = {
verifyWithBouncyCastle(data, signature)
}
/*
def verifyWithSecp(
data: ByteVector,
signature: SchnorrDigitalSignature): Boolean = {
NativeSecp256k1.schnorrVerify(signature.bytes.toArray,
data.toArray,
bytes.toArray)
}
*/
def verifyWithBouncyCastle(
data: ByteVector,
signature: SchnorrDigitalSignature): Boolean = {
BouncyCastleUtil.schnorrVerify(data, this, signature)
CryptoUtil.schnorrVerify(data, this, signature)
}
def computeSigPoint(data: ByteVector, nonce: SchnorrNonce): ECPublicKey = {
@ -67,44 +49,17 @@ case class SchnorrPublicKey(bytes: ByteVector) extends NetworkElement {
this.publicKey.tweakMultiply(aggHashes.fieldElement).add(aggNonces)
}
// TODO: match on CryptoContext once secp version is added
def computeSigPoint(
data: ByteVector,
nonce: SchnorrNonce,
compressed: Boolean): ECPublicKey = {
computeSigPointWithBouncyCastle(data, nonce, compressed)
}
/*
def computeSigPointWithSecp(
data: ByteVector,
nonce: SchnorrNonce,
compressed: Boolean = true): ECPublicKey = {
val sigPointBytes = NativeSecp256k1.schnorrComputeSigPoint(
data.toArray,
nonce.bytes.toArray,
bytes.toArray,
compressed)
ECPublicKey(ByteVector(sigPointBytes))
}
*/
def computeSigPointWithBouncyCastle(
data: ByteVector,
nonce: SchnorrNonce,
compressed: Boolean = true): ECPublicKey = {
BouncyCastleUtil.schnorrComputeSigPoint(data, nonce, this, compressed)
CryptoUtil.schnorrComputeSigPoint(data, nonce, this, compressed)
}
def publicKey: ECPublicKey = {
val pubKeyBytes = ByteVector.fromByte(2) ++ bytes
val validPubKey = CryptoContext.default match {
case CryptoContext.LibSecp256k1 =>
NativeSecp256k1.isValidPubKey(pubKeyBytes.toArray)
case CryptoContext.BouncyCastle =>
BouncyCastleUtil.validatePublicKey(pubKeyBytes)
}
val validPubKey = CryptoUtil.isValidPubKey(pubKeyBytes)
require(
validPubKey,

View file

@ -1,6 +1,5 @@
package org.bitcoins.core.gcs
package org.bitcoins.crypto
import org.bitcoins.crypto.NetworkElement
import scodec.bits.ByteVector
case class SipHashKey(bytes: ByteVector) extends NetworkElement {

View file

@ -74,12 +74,15 @@ Here is an example of calling bouncy castle methods in `ECKey`
```scala mdoc:to-string
val privKey = ECPrivateKey.freshPrivateKey
val publicKey = privKey.publicKeyWithBouncyCastle
// calls bouncy castle indirectly via CryptoContext
val publicKey = privKey.publicKey
val dataToSign = DoubleSha256Digest.empty
val signature = privKey.signWithBouncyCastle(dataToSign.bytes)
// calls bouncy castle indirectly via CryptoContext
val signature = privKey.sign(dataToSign.bytes)
val verified = publicKey.verifyWithBouncyCastle(dataToSign.bytes, signature)
// calls bouncy castle indirectly via CryptoContext
val verified = publicKey.verify(dataToSign.bytes, signature)
println(s"Verified with bouncy castle=${verified}")

View file

@ -36,3 +36,8 @@ addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.10.0-RC1")
//https://github.com/scalameta/sbt-native-image
addSbtPlugin("org.scalameta" % "sbt-native-image" % "0.3.0")
// Scala.js
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.0")
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0")

View file

@ -1,7 +1,6 @@
package org.bitcoins.testkit.core.gen
import org.bitcoins.core.crypto._
import org.bitcoins.core.gcs.SipHashKey
import org.bitcoins.core.number.{UInt64, UInt8}
import org.bitcoins.core.script.crypto.HashType
import org.bitcoins.{core, crypto}
@ -23,7 +22,8 @@ import org.bitcoins.crypto.{
SchnorrPublicKey,
Sha256Digest,
Sha256DigestBE,
Sha256Hash160Digest
Sha256Hash160Digest,
SipHashKey
}
import org.scalacheck.Gen
import scodec.bits.{BitVector, ByteVector}