mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-22 14:33:06 +01:00
Bouncy Castle Fallback (#1232)
* Added Bouncy Castle fallback to all Secp calls in ECKey.scala * Fixed bugs and made ExtKey support use of BouncyCastle * An attempt to add bouncy castle testing to CI * Responded to review * De-coupled libsecp256k1 uses from Bouncy Castle uses * Responded to review
This commit is contained in:
parent
552135dd68
commit
e0b234843d
6 changed files with 371 additions and 91 deletions
|
@ -0,0 +1,99 @@
|
|||
package org.bitcoins.core.crypto
|
||||
|
||||
import org.bitcoin.{NativeSecp256k1, Secp256k1Context}
|
||||
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 {
|
||||
|
||||
behavior of "CryptoLibraries"
|
||||
|
||||
override def withFixture(test: NoArgTest): Outcome = {
|
||||
if (Secp256k1Context.isEnabled) {
|
||||
super.withFixture(test)
|
||||
} else {
|
||||
logger.warn(s"Test ${test.name} skipped as Secp256k1 is not available.")
|
||||
Succeeded
|
||||
}
|
||||
}
|
||||
|
||||
it must "add private keys the same" in {
|
||||
forAll(CryptoGenerators.privateKey, CryptoGenerators.privateKey) {
|
||||
case (priv1, priv2) =>
|
||||
val sumWithBouncyCastle =
|
||||
BouncyCastleUtil.addNumbers(priv1.bytes, priv2.bytes)
|
||||
val sumWithSecp = NativeSecp256k1.privKeyTweakAdd(priv1.bytes.toArray,
|
||||
priv2.bytes.toArray)
|
||||
|
||||
val sumKeyWithBouncyCastle =
|
||||
ECPrivateKey(ByteVector(sumWithBouncyCastle.toByteArray))
|
||||
val sumKeyWithSecp = ECPrivateKey(ByteVector(sumWithSecp))
|
||||
|
||||
assert(sumKeyWithBouncyCastle == sumKeyWithSecp)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
assert(sumKey == sumKeyExpected)
|
||||
}
|
||||
}
|
||||
|
||||
it must "validate keys the same" in {
|
||||
val keyOrGarbageGen = Gen.oneOf(CryptoGenerators.publicKey.map(_.bytes),
|
||||
NumberGenerator.bytevector(33))
|
||||
forAll(keyOrGarbageGen) { bytes =>
|
||||
assert(
|
||||
ECPublicKey.isFullyValidWithBouncyCastle(bytes) ==
|
||||
ECPublicKey.isFullyValidWithSecp(bytes)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it must "decompress keys the same" in {
|
||||
forAll(CryptoGenerators.publicKey) { pubKey =>
|
||||
assert(pubKey.decompressedWithBouncyCastle == pubKey.decompressedWithSecp)
|
||||
}
|
||||
}
|
||||
|
||||
it must "compute public keys the same" in {
|
||||
forAll(CryptoGenerators.privateKey) { privKey =>
|
||||
assert(privKey.publicKeyWithBouncyCastle == privKey.publicKeyWithSecp)
|
||||
}
|
||||
}
|
||||
|
||||
it must "compute signatures the same" in {
|
||||
forAll(CryptoGenerators.privateKey, NumberGenerator.bytevector(32)) {
|
||||
case (privKey, bytes) =>
|
||||
assert(
|
||||
privKey.signWithBouncyCastle(bytes) == privKey.signWithSecp(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
it must "verify signatures the same" in {
|
||||
forAll(CryptoGenerators.privateKey,
|
||||
NumberGenerator.bytevector(32),
|
||||
CryptoGenerators.digitalSignature) {
|
||||
case (privKey, bytes, badSig) =>
|
||||
val sig = privKey.sign(bytes)
|
||||
val pubKey = privKey.publicKey
|
||||
assert(
|
||||
pubKey.verifyWithBouncyCastle(bytes, sig) == pubKey
|
||||
.verifyWithSecp(bytes, sig))
|
||||
assert(
|
||||
pubKey.verifyWithBouncyCastle(bytes, badSig) == pubKey
|
||||
.verifyWithSecp(bytes, badSig))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
package org.bitcoins.core.crypto
|
||||
|
||||
import org.bitcoin.NativeSecp256k1
|
||||
import org.bitcoin.{NativeSecp256k1, Secp256k1Context}
|
||||
import org.bitcoins.testkit.core.gen.CryptoGenerators
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
import scodec.bits._
|
||||
|
||||
import scala.concurrent.ExecutionContext
|
||||
|
||||
class ECPublicKeyTest extends BitcoinSUnitTest {
|
||||
|
||||
it must "be able to decompress keys" in {
|
||||
|
@ -37,16 +39,20 @@ class ECPublicKeyTest extends BitcoinSUnitTest {
|
|||
}
|
||||
}
|
||||
|
||||
it must "add keys correctly" 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.add(privKey.publicKey)
|
||||
it must "decompress keys correctly" in {
|
||||
forAll(CryptoGenerators.privateKey) { privKey =>
|
||||
val pubKey = privKey.publicKey
|
||||
|
||||
assert(sumKey == sumKeyExpected)
|
||||
assert(privKey.isCompressed)
|
||||
assert(pubKey.isCompressed)
|
||||
|
||||
val decompressedPrivKey =
|
||||
ECPrivateKey(privKey.bytes, isCompressed = false)(
|
||||
ExecutionContext.global)
|
||||
val decompressedPubKey = pubKey.decompressed
|
||||
|
||||
assert(decompressedPrivKey.publicKey == decompressedPubKey)
|
||||
assert(pubKey.bytes.tail == decompressedPubKey.bytes.splitAt(33)._1.tail)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
package org.bitcoins.core.crypto
|
||||
|
||||
import java.math.BigInteger
|
||||
|
||||
import org.bitcoins.core.util.BitcoinSUtil
|
||||
import org.bouncycastle.crypto.digests.SHA256Digest
|
||||
import org.bouncycastle.crypto.params.{
|
||||
ECPrivateKeyParameters,
|
||||
ECPublicKeyParameters
|
||||
}
|
||||
import org.bouncycastle.crypto.signers.{ECDSASigner, HMacDSAKCalculator}
|
||||
import org.bouncycastle.math.ec.{ECCurve, ECPoint}
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import scala.util.Try
|
||||
|
||||
object BouncyCastleUtil {
|
||||
|
||||
private val curve: ECCurve = CryptoParams.curve.getCurve
|
||||
private val G: ECPoint = CryptoParams.curve.getG
|
||||
private val N: BigInteger = CryptoParams.curve.getN
|
||||
|
||||
private def getBigInteger(bytes: ByteVector): BigInteger = {
|
||||
new BigInteger(1, bytes.toArray)
|
||||
}
|
||||
|
||||
def addNumbers(num1: ByteVector, num2: ByteVector): BigInteger = {
|
||||
val bigInteger1 = getBigInteger(num1)
|
||||
val bigInteger2 = getBigInteger(num2)
|
||||
|
||||
bigInteger1.add(bigInteger2).mod(N)
|
||||
}
|
||||
|
||||
def decodePoint(bytes: ByteVector): ECPoint = {
|
||||
curve.decodePoint(bytes.toArray)
|
||||
}
|
||||
|
||||
def validatePublicKey(bytes: ByteVector): Boolean = {
|
||||
Try(decodePoint(bytes))
|
||||
.map(_.getCurve == curve)
|
||||
.getOrElse(false)
|
||||
}
|
||||
|
||||
def decompressPublicKey(publicKey: ECPublicKey): ECPublicKey = {
|
||||
if (publicKey.isCompressed) {
|
||||
val point = decodePoint(publicKey.bytes)
|
||||
val decompressedBytes =
|
||||
ByteVector.fromHex("04").get ++
|
||||
ByteVector(point.getXCoord.getEncoded) ++
|
||||
ByteVector(point.getYCoord.getEncoded)
|
||||
ECPublicKey(decompressedBytes)
|
||||
} else publicKey
|
||||
}
|
||||
|
||||
def computePublicKey(privateKey: ECPrivateKey): ECPublicKey = {
|
||||
val priv = getBigInteger(privateKey.bytes)
|
||||
val point = G.multiply(priv)
|
||||
val pubBytes = ByteVector(point.getEncoded(privateKey.isCompressed))
|
||||
require(
|
||||
ECPublicKey.isFullyValid(pubBytes),
|
||||
s"Bouncy Castle failed to generate a valid public key, got: ${BitcoinSUtil
|
||||
.encodeHex(pubBytes)}")
|
||||
ECPublicKey(pubBytes)
|
||||
}
|
||||
|
||||
def sign(
|
||||
dataToSign: ByteVector,
|
||||
privateKey: ECPrivateKey): ECDigitalSignature = {
|
||||
val signer: ECDSASigner = new ECDSASigner(
|
||||
new HMacDSAKCalculator(new SHA256Digest()))
|
||||
val privKey: ECPrivateKeyParameters =
|
||||
new ECPrivateKeyParameters(getBigInteger(privateKey.bytes),
|
||||
CryptoParams.curve)
|
||||
signer.init(true, privKey)
|
||||
val components: Array[BigInteger] =
|
||||
signer.generateSignature(dataToSign.toArray)
|
||||
val (r, s) = (components(0), components(1))
|
||||
val signature = ECDigitalSignature(r, s)
|
||||
//make sure the signature follows BIP62's low-s value
|
||||
//https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#Low_S_values_in_signatures
|
||||
//bitcoinj implementation
|
||||
//https://github.com/bitcoinj/bitcoinj/blob/1e66b9a8e38d9ad425507bf5f34d64c5d3d23bb8/core/src/main/java/org/bitcoinj/core/ECKey.java#L551
|
||||
val signatureLowS = DERSignatureUtil.lowS(signature)
|
||||
require(
|
||||
signatureLowS.isDEREncoded,
|
||||
"We must create DER encoded signatures when signing a piece of data, got: " + signatureLowS)
|
||||
signatureLowS
|
||||
}
|
||||
|
||||
def verifyDigitalSignature(
|
||||
data: ByteVector,
|
||||
publicKey: ECPublicKey,
|
||||
signature: ECDigitalSignature): Boolean = {
|
||||
val resultTry = Try {
|
||||
val publicKeyParams =
|
||||
new ECPublicKeyParameters(decodePoint(publicKey.bytes),
|
||||
CryptoParams.curve)
|
||||
|
||||
val signer = new ECDSASigner
|
||||
signer.init(false, publicKeyParams)
|
||||
signature match {
|
||||
case EmptyDigitalSignature =>
|
||||
signer.verifySignature(data.toArray,
|
||||
java.math.BigInteger.valueOf(0),
|
||||
java.math.BigInteger.valueOf(0))
|
||||
case _: ECDigitalSignature =>
|
||||
val rBigInteger: BigInteger = new BigInteger(signature.r.toString())
|
||||
val sBigInteger: BigInteger = new BigInteger(signature.s.toString())
|
||||
signer.verifySignature(data.toArray, rBigInteger, sBigInteger)
|
||||
}
|
||||
}
|
||||
resultTry.getOrElse(false)
|
||||
}
|
||||
}
|
|
@ -3,19 +3,16 @@ package org.bitcoins.core.crypto
|
|||
import java.math.BigInteger
|
||||
import java.security.SecureRandom
|
||||
|
||||
import org.bitcoin.NativeSecp256k1
|
||||
import org.bitcoin.{NativeSecp256k1, Secp256k1Context}
|
||||
import org.bitcoins.core.config.{NetworkParameters, Networks}
|
||||
import org.bitcoins.core.protocol.NetworkElement
|
||||
import org.bitcoins.core.util.{BitcoinSUtil, _}
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair
|
||||
import org.bouncycastle.crypto.digests.SHA256Digest
|
||||
import org.bouncycastle.crypto.generators.ECKeyPairGenerator
|
||||
import org.bouncycastle.crypto.params.{
|
||||
ECKeyGenerationParameters,
|
||||
ECPrivateKeyParameters,
|
||||
ECPublicKeyParameters
|
||||
ECPrivateKeyParameters
|
||||
}
|
||||
import org.bouncycastle.crypto.signers.{ECDSASigner, HMacDSAKCalculator}
|
||||
import org.bouncycastle.math.ec.ECPoint
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
|
@ -49,53 +46,61 @@ sealed abstract class ECPrivateKey
|
|||
* @return the digital signature
|
||||
*/
|
||||
override def sign(dataToSign: ByteVector): ECDigitalSignature = {
|
||||
sign(dataToSign, Secp256k1Context.isEnabled)
|
||||
}
|
||||
|
||||
def sign(dataToSign: ByteVector, useSecp: Boolean): ECDigitalSignature = {
|
||||
require(dataToSign.length == 32 && bytes.length <= 32)
|
||||
if (useSecp) {
|
||||
signWithSecp(dataToSign)
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
|
||||
def sign(hash: HashDigest): ECDigitalSignature = sign(hash.bytes)
|
||||
|
||||
def signFuture(hash: HashDigest)(
|
||||
implicit ec: ExecutionContext): Future[ECDigitalSignature] =
|
||||
Future(sign(hash))
|
||||
|
||||
def signWithBouncyCastle(dataToSign: ByteVector): ECDigitalSignature = {
|
||||
val signer: ECDSASigner = new ECDSASigner(
|
||||
new HMacDSAKCalculator(new SHA256Digest()))
|
||||
val privKey: ECPrivateKeyParameters = new ECPrivateKeyParameters(
|
||||
new BigInteger(1, bytes.toArray),
|
||||
CryptoParams.curve)
|
||||
signer.init(true, privKey)
|
||||
val components: Array[BigInteger] =
|
||||
signer.generateSignature(dataToSign.toArray)
|
||||
val (r, s) = (components(0), components(1))
|
||||
val signature = ECDigitalSignature(r, s)
|
||||
//make sure the signature follows BIP62's low-s value
|
||||
//https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#Low_S_values_in_signatures
|
||||
//bitcoinj implementation
|
||||
//https://github.com/bitcoinj/bitcoinj/blob/1e66b9a8e38d9ad425507bf5f34d64c5d3d23bb8/core/src/main/java/org/bitcoinj/core/ECKey.java#L551
|
||||
val signatureLowS = DERSignatureUtil.lowS(signature)
|
||||
require(
|
||||
signatureLowS.isDEREncoded,
|
||||
"We must create DER encoded signatures when signing a piece of data, got: " + signatureLowS)
|
||||
signatureLowS
|
||||
}
|
||||
|
||||
/** Signifies if the this private key corresponds to a compressed public key */
|
||||
def isCompressed: Boolean
|
||||
|
||||
override def publicKey: ECPublicKey = publicKey(Secp256k1Context.isEnabled)
|
||||
|
||||
/** Derives the public for a the private key */
|
||||
def publicKey: ECPublicKey = {
|
||||
def publicKey(useSecp: Boolean): ECPublicKey = {
|
||||
if (useSecp) {
|
||||
publicKeyWithSecp
|
||||
} else {
|
||||
publicKeyWithBouncyCastle
|
||||
}
|
||||
}
|
||||
|
||||
def publicKeyWithSecp: ECPublicKey = {
|
||||
val pubKeyBytes: Array[Byte] =
|
||||
NativeSecp256k1.computePubkey(bytes.toArray, isCompressed)
|
||||
val pubBytes = ByteVector(pubKeyBytes)
|
||||
require(
|
||||
NativeSecp256k1.isValidPubKey(pubKeyBytes),
|
||||
ECPublicKey.isFullyValid(pubBytes),
|
||||
s"secp256k1 failed to generate a valid public key, got: ${BitcoinSUtil
|
||||
.encodeHex(ByteVector(pubKeyBytes))}"
|
||||
)
|
||||
ECPublicKey(ByteVector(pubKeyBytes))
|
||||
.encodeHex(pubBytes)}")
|
||||
ECPublicKey(pubBytes)
|
||||
}
|
||||
|
||||
def publicKeyWithBouncyCastle: ECPublicKey = {
|
||||
BouncyCastleUtil.computePublicKey(this)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -124,8 +129,14 @@ object ECPrivateKey extends Factory[ECPrivateKey] {
|
|||
isCompressed: Boolean,
|
||||
ec: ExecutionContext)
|
||||
extends ECPrivateKey {
|
||||
require(NativeSecp256k1.secKeyVerify(bytes.toArray),
|
||||
s"Invalid key according to secp256k1, hex: ${bytes.toHex}")
|
||||
if (Secp256k1Context.isEnabled) {
|
||||
require(NativeSecp256k1.secKeyVerify(bytes.toArray),
|
||||
s"Invalid key according to secp256k1, hex: ${bytes.toHex}")
|
||||
} else {
|
||||
require(CryptoParams.curve.getCurve
|
||||
.isValidFieldElement(new BigInteger(1, bytes.toArray)),
|
||||
s"Invalid key according to Bouncy Castle, hex: ${bytes.toHex}")
|
||||
}
|
||||
}
|
||||
|
||||
def apply(bytes: ByteVector, isCompressed: Boolean)(
|
||||
|
@ -274,9 +285,28 @@ sealed abstract class ECPublicKey extends BaseECKey {
|
|||
* [[org.bitcoins.core.crypto.ECPrivateKey ECPrivateKey]]'s corresponding
|
||||
* [[org.bitcoins.core.crypto.ECPublicKey ECPublicKey]]. */
|
||||
def verify(data: ByteVector, signature: ECDigitalSignature): Boolean = {
|
||||
val result = NativeSecp256k1.verify(data.toArray,
|
||||
signature.bytes.toArray,
|
||||
bytes.toArray)
|
||||
verify(data, signature, Secp256k1Context.isEnabled)
|
||||
}
|
||||
|
||||
def verify(
|
||||
data: ByteVector,
|
||||
signature: ECDigitalSignature,
|
||||
useSecp: Boolean): Boolean = {
|
||||
if (useSecp) {
|
||||
verifyWithSecp(data, signature)
|
||||
} else {
|
||||
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
|
||||
|
@ -284,68 +314,56 @@ sealed abstract class ECPublicKey extends BaseECKey {
|
|||
//bitcoin core implements this functionality here:
|
||||
//https://github.com/bitcoin/bitcoin/blob/master/src/pubkey.cpp#L16-L165
|
||||
//TODO: Implement functionality in Bitcoin Core linked above
|
||||
oldVerify(data, signature)
|
||||
verifyWithBouncyCastle(data, signature)
|
||||
} else result
|
||||
}
|
||||
|
||||
def verifyWithBouncyCastle(
|
||||
data: ByteVector,
|
||||
signature: ECDigitalSignature): Boolean = {
|
||||
BouncyCastleUtil.verifyDigitalSignature(data, this, signature)
|
||||
}
|
||||
|
||||
def verify(hex: String, signature: ECDigitalSignature): Boolean =
|
||||
verify(BitcoinSUtil.decodeHex(hex), signature)
|
||||
|
||||
override def toString = "ECPublicKey(" + hex + ")"
|
||||
|
||||
@deprecated(
|
||||
"Deprecated in favor of using verify functionality inside of secp256k1",
|
||||
"2/20/2017")
|
||||
private def oldVerify(
|
||||
data: ByteVector,
|
||||
signature: ECDigitalSignature): Boolean = {
|
||||
|
||||
/** The elliptic curve used by bitcoin. */
|
||||
def curve = CryptoParams.curve
|
||||
|
||||
/** This represents this public key in the bouncy castle library */
|
||||
def publicKeyParams =
|
||||
new ECPublicKeyParameters(curve.getCurve.decodePoint(bytes.toArray),
|
||||
curve)
|
||||
|
||||
val resultTry = Try {
|
||||
val signer = new ECDSASigner
|
||||
signer.init(false, publicKeyParams)
|
||||
signature match {
|
||||
case EmptyDigitalSignature =>
|
||||
signer.verifySignature(data.toArray,
|
||||
java.math.BigInteger.valueOf(0),
|
||||
java.math.BigInteger.valueOf(0))
|
||||
case _: ECDigitalSignature =>
|
||||
val rBigInteger: BigInteger = new BigInteger(signature.r.toString())
|
||||
val sBigInteger: BigInteger = new BigInteger(signature.s.toString())
|
||||
signer.verifySignature(data.toArray, rBigInteger, sBigInteger)
|
||||
}
|
||||
}
|
||||
resultTry.getOrElse(false)
|
||||
}
|
||||
|
||||
/** Checks if the [[org.bitcoins.core.crypto.ECPublicKey ECPublicKey]] is compressed */
|
||||
def isCompressed: Boolean = bytes.size == 33
|
||||
|
||||
/** Checks if the [[org.bitcoins.core.crypto.ECPublicKey ECPublicKey]] is valid according to secp256k1 */
|
||||
def isFullyValid = ECPublicKey.isFullyValid(bytes)
|
||||
def isFullyValid: Boolean = ECPublicKey.isFullyValid(bytes)
|
||||
|
||||
/** Returns the decompressed version of this [[org.bitcoins.core.crypto.ECPublicKey ECPublicKey]] */
|
||||
def decompressed: ECPublicKey = {
|
||||
def decompressed: ECPublicKey = decompressed(Secp256k1Context.isEnabled)
|
||||
|
||||
def decompressed(useSecp: Boolean): ECPublicKey = {
|
||||
if (useSecp) {
|
||||
decompressedWithSecp
|
||||
} else {
|
||||
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.core.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 = {
|
||||
CryptoParams.curve.getCurve.decodePoint(bytes.toArray)
|
||||
BouncyCastleUtil.decodePoint(bytes)
|
||||
}
|
||||
|
||||
/** Adds this ECPublicKey to another as points and returns the resulting ECPublicKey.
|
||||
|
@ -354,6 +372,10 @@ sealed abstract class ECPublicKey extends BaseECKey {
|
|||
* 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)
|
||||
|
@ -388,9 +410,26 @@ 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 =
|
||||
def isFullyValid(bytes: ByteVector): Boolean = {
|
||||
isFullyValid(bytes, Secp256k1Context.isEnabled)
|
||||
}
|
||||
|
||||
def isFullyValid(bytes: ByteVector, useSecp: Boolean): Boolean = {
|
||||
if (useSecp) {
|
||||
isFullyValidWithSecp(bytes)
|
||||
} else {
|
||||
isFullyValidWithBouncyCastle(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
def isFullyValidWithSecp(bytes: ByteVector): Boolean = {
|
||||
Try(NativeSecp256k1.isValidPubKey(bytes.toArray))
|
||||
.getOrElse(false) && isValid(bytes)
|
||||
}
|
||||
|
||||
def isFullyValidWithBouncyCastle(bytes: ByteVector): Boolean = {
|
||||
BouncyCastleUtil.validatePublicKey(bytes) && isValid(bytes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Mimics the CPubKey::IsValid function in Bitcoin core, this is a consensus rule
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package org.bitcoins.core.crypto
|
||||
|
||||
import org.bitcoin.NativeSecp256k1
|
||||
import org.bitcoin.{NativeSecp256k1, Secp256k1Context}
|
||||
import org.bitcoins.core.hd.{BIP32Node, BIP32Path}
|
||||
import org.bitcoins.core.number.{UInt32, UInt8}
|
||||
import org.bitcoins.core.protocol.NetworkElement
|
||||
|
@ -200,7 +200,12 @@ sealed abstract class ExtPrivateKey
|
|||
val (il, ir) = hmac.splitAt(32)
|
||||
//should be ECGroup addition
|
||||
//parse256(IL) + kpar (mod n)
|
||||
val tweak = NativeSecp256k1.privKeyTweakAdd(il.toArray, key.bytes.toArray)
|
||||
val tweak = if (Secp256k1Context.isEnabled) {
|
||||
NativeSecp256k1.privKeyTweakAdd(il.toArray, key.bytes.toArray)
|
||||
} else {
|
||||
val sum = BouncyCastleUtil.addNumbers(key.bytes, il)
|
||||
sum.toByteArray
|
||||
}
|
||||
val childKey = ECPrivateKey(ByteVector(tweak))
|
||||
val fp = CryptoUtil.sha256Hash160(key.publicKey.bytes).bytes.take(4)
|
||||
ExtPrivateKey(version, depth + UInt8.one, fp, idx, ChainCode(ir), childKey)
|
||||
|
@ -353,10 +358,15 @@ sealed abstract class ExtPublicKey extends ExtKey {
|
|||
val hmac = CryptoUtil.hmac512(chainCode.bytes, data)
|
||||
val (il, ir) = hmac.splitAt(32)
|
||||
val priv = ECPrivateKey(il)
|
||||
val tweaked = NativeSecp256k1.pubKeyTweakAdd(key.bytes.toArray,
|
||||
hmac.toArray,
|
||||
priv.isCompressed)
|
||||
val childPubKey = ECPublicKey(ByteVector(tweaked))
|
||||
val childPubKey = if (Secp256k1Context.isEnabled) {
|
||||
val tweaked = NativeSecp256k1.pubKeyTweakAdd(key.bytes.toArray,
|
||||
il.toArray,
|
||||
priv.isCompressed)
|
||||
ECPublicKey(ByteVector(tweaked))
|
||||
} else {
|
||||
val tweak = ECPrivateKey.fromBytes(il).publicKey
|
||||
key.add(tweak)
|
||||
}
|
||||
|
||||
//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,
|
||||
|
|
|
@ -45,8 +45,20 @@ public class Secp256k1Context {
|
|||
context = contextRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects whether or not the libsecp256k1 binaries were successfully
|
||||
* loaded in static initialization above. Useful in enabling a fallback
|
||||
* to Bouncy Castle implementations in the case of having no libsecp present.
|
||||
*/
|
||||
public static boolean isEnabled() {
|
||||
return enabled;
|
||||
String secpDisabled = System.getenv("DISABLE_SECP256K1");
|
||||
if (secpDisabled != null &&
|
||||
(secpDisabled.toLowerCase().equals("true") ||
|
||||
secpDisabled.equals("1"))) {
|
||||
return false;
|
||||
} else {
|
||||
return enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public static long getContext() {
|
||||
|
|
Loading…
Add table
Reference in a new issue