From a1bdbda039afc67ae5b31a21246f571e4382fc8f Mon Sep 17 00:00:00 2001 From: Chris Stewart Date: Fri, 12 Feb 2021 15:18:42 -0600 Subject: [PATCH] CryptoRuntime abstraction (#2658) * Add CryptoRuntime, extend it with CryptoUtil * Remove direct usages of CryptoUtil in the core project, use CryptoTrait.cryptoRuntime * Add JvmCryptoRuntime * Take ben's suggestion so we don't need to modify anyting in core, h/t to ben * Refactor ECPrivateKey.freshPrivateKey to use CryptoUtil.freshPrivateKey * Remove CryptoTrait as it is no longer necessary --- .../org/bitcoins/crypto/CryptoContext.scala | 8 + .../org/bitcoins/crypto/CryptoRuntime.scala | 139 ++++++++++++ .../org/bitcoins/crypto/CryptoUtil.scala | 202 ++++-------------- .../scala/org/bitcoins/crypto/ECKey.scala | 20 +- .../bitcoins/crypto/JvmCryptoRuntime.scala | 139 ++++++++++++ 5 files changed, 324 insertions(+), 184 deletions(-) create mode 100644 crypto/src/main/scala/org/bitcoins/crypto/CryptoRuntime.scala create mode 100644 crypto/src/main/scala/org/bitcoins/crypto/JvmCryptoRuntime.scala diff --git a/crypto/src/main/scala/org/bitcoins/crypto/CryptoContext.scala b/crypto/src/main/scala/org/bitcoins/crypto/CryptoContext.scala index 8915a3875f..7cde2fef66 100644 --- a/crypto/src/main/scala/org/bitcoins/crypto/CryptoContext.scala +++ b/crypto/src/main/scala/org/bitcoins/crypto/CryptoContext.scala @@ -24,4 +24,12 @@ object CryptoContext { } } } + + /** The platform specific cryptographic functions required to run bitcoin-s */ + lazy val cryptoRuntime: CryptoRuntime = { + default match { + case LibSecp256k1 => JvmCryptoRuntime + case BouncyCastle => JvmCryptoRuntime + } + } } diff --git a/crypto/src/main/scala/org/bitcoins/crypto/CryptoRuntime.scala b/crypto/src/main/scala/org/bitcoins/crypto/CryptoRuntime.scala new file mode 100644 index 0000000000..e8b4b55b1e --- /dev/null +++ b/crypto/src/main/scala/org/bitcoins/crypto/CryptoRuntime.scala @@ -0,0 +1,139 @@ +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 { + + /** Generates a 32 byte private key */ + 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 + */ + def toPublicKey(privateKey: ECPrivateKey, isCompressed: Boolean): ECPublicKey + + def ripeMd160(bytes: ByteVector): RipeMd160Digest + + def sha256Hash160(bytes: ByteVector): Sha256Hash160Digest + + def sha256(bytes: ByteVector): Sha256Digest + + def sha256(str: String): Sha256Digest = { + sha256(serializeForHash(str)) + } + + def sha256(bitVector: BitVector): Sha256Digest = { + sha256(bitVector.toByteVector) + } + + def taggedSha256(bytes: ByteVector, tag: String): Sha256Digest = { + val tagHash = sha256(tag) + val tagBytes = tagHash.bytes ++ tagHash.bytes + sha256(tagBytes ++ bytes) + } + + /** Performs sha256(sha256(bytes)). */ + def doubleSHA256(bytes: ByteVector): DoubleSha256Digest = { + val hash: ByteVector = sha256(sha256(bytes).bytes).bytes + DoubleSha256Digest(hash) + } + + def sha1(bytes: ByteVector): Sha1Digest + + def hmac512(key: ByteVector, data: ByteVector): ByteVector + + def normalize(str: String): String + + def serializeForHash(str: String): ByteVector = { + ByteVector(normalize(str).getBytes("UTF-8")) + } + + // The tag "BIP0340/challenge" + private lazy val schnorrChallengeTagBytes = { + ByteVector + .fromValidHex( + "7bb52d7a9fef58323eb1bf7a407db382d2f3f2d81bb1224f49fe518f6d48d37c7bb52d7a9fef58323eb1bf7a407db382d2f3f2d81bb1224f49fe518f6d48d37c" + ) + } + + // The tag "DLC/oracle/attestation/v0" + private val dlcAttestationTagBytes = { + ByteVector + .fromValidHex( + "0c2fa46216e6e460e5e3f78555b102c5ac6aecabbfb82b430cf36cdfe04421790c2fa46216e6e460e5e3f78555b102c5ac6aecabbfb82b430cf36cdfe0442179" + ) + } + + def sha256SchnorrChallenge(bytes: ByteVector): Sha256Digest = { + sha256(schnorrChallengeTagBytes ++ bytes) + } + + def sha256DLCAttestation(bytes: ByteVector): Sha256Digest = { + sha256(dlcAttestationTagBytes ++ bytes) + } + + def sha256DLCAttestation(str: String): Sha256Digest = { + sha256DLCAttestation(CryptoUtil.serializeForHash(str)) + } + + // The tag "DLC/oracle/announcement/v0" + private val dlcAnnouncementTagBytes = { + ByteVector + .fromValidHex( + "6378871e8c99d480fff016e178a371e7e058445eff3023fe158f05aa185ed0e16378871e8c99d480fff016e178a371e7e058445eff3023fe158f05aa185ed0e1" + ) + } + + def sha256DLCAnnouncement(bytes: ByteVector): Sha256Digest = { + 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. + * + * @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 + */ + def recoverPublicKey( + signature: ECDigitalSignature, + message: ByteVector): (ECPublicKey, ECPublicKey) + + // The tag "BIP0340/aux" + private val schnorrAuxTagBytes = { + ByteVector + .fromValidHex( + "f1ef4e5ec063cada6d94cafa9d987ea069265839ecc11f972d77a52ed8c1cc90f1ef4e5ec063cada6d94cafa9d987ea069265839ecc11f972d77a52ed8c1cc90" + ) + } + + def sha256SchnorrAuxRand(bytes: ByteVector): Sha256Digest = { + sha256(schnorrAuxTagBytes ++ bytes) + } + + // The tag "BIP0340/nonce" + private val schnorrNonceTagBytes = { + ByteVector + .fromValidHex( + "07497734a79bcb355b9b8c7d034f121cf434d73ef72dda19870061fb52bfeb2f07497734a79bcb355b9b8c7d034f121cf434d73ef72dda19870061fb52bfeb2f" + ) + } + + def sha256SchnorrNonce(bytes: ByteVector): Sha256Digest = { + sha256(schnorrNonceTagBytes ++ bytes) + } +} diff --git a/crypto/src/main/scala/org/bitcoins/crypto/CryptoUtil.scala b/crypto/src/main/scala/org/bitcoins/crypto/CryptoUtil.scala index a8a88ad09f..deeb2d30eb 100644 --- a/crypto/src/main/scala/org/bitcoins/crypto/CryptoUtil.scala +++ b/crypto/src/main/scala/org/bitcoins/crypto/CryptoUtil.scala @@ -1,40 +1,43 @@ package org.bitcoins.crypto -import java.math.BigInteger -import java.security.MessageDigest - -import org.bouncycastle.crypto.digests.{RIPEMD160Digest, SHA512Digest} -import org.bouncycastle.crypto.macs.HMac -import org.bouncycastle.crypto.params.KeyParameter import org.bouncycastle.math.ec.ECPoint -import scodec.bits.{BitVector, ByteVector} +import scodec.bits.ByteVector + +import java.math.BigInteger /** Utility cryptographic functions + * This is a proxy for the underlying implementation of [[CryptoRuntime]] + * such as [[JvmCryptoRuntime]]. + * + * 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 */ -trait CryptoUtil { +trait CryptoUtil extends CryptoRuntime { - def normalize(str: String): String = { - java.text.Normalizer.normalize(str, java.text.Normalizer.Form.NFC) + /** The underlying runtime for the specific platform we are running on */ + private lazy val cryptoRuntime: CryptoRuntime = CryptoContext.cryptoRuntime + + override def freshPrivateKey: ECPrivateKey = { + cryptoRuntime.freshPrivateKey } - def serializeForHash(str: String): ByteVector = { - ByteVector(normalize(str).getBytes("UTF-8")) + override def toPublicKey( + privateKey: ECPrivateKey, + isCompressed: Boolean): ECPublicKey = { + cryptoRuntime.toPublicKey(privateKey, isCompressed) + } + + override def normalize(str: String): String = { + cryptoRuntime.normalize(str) } /** Does the following computation: RIPEMD160(SHA256(hex)). */ - def sha256Hash160(bytes: ByteVector): Sha256Hash160Digest = { - val hash = ripeMd160(sha256(bytes).bytes).bytes - Sha256Hash160Digest(hash) + override def sha256Hash160(bytes: ByteVector): Sha256Hash160Digest = { + cryptoRuntime.sha256Hash160(bytes) } def sha256Hash160(str: String): Sha256Hash160Digest = { - sha256Hash160(serializeForHash(str)) - } - - /** Performs sha256(sha256(bytes)). */ - def doubleSHA256(bytes: ByteVector): DoubleSha256Digest = { - val hash: ByteVector = sha256(sha256(bytes).bytes).bytes - DoubleSha256Digest(hash) + cryptoRuntime.sha256Hash160(serializeForHash(str)) } def doubleSHA256(str: String): DoubleSha256Digest = { @@ -42,98 +45,17 @@ trait CryptoUtil { } /** Takes sha256(bytes). */ - def sha256(bytes: ByteVector): Sha256Digest = { - val hash = MessageDigest.getInstance("SHA-256").digest(bytes.toArray) - Sha256Digest(ByteVector(hash)) - } - - /** Takes sha256(bits). */ - def sha256(bits: BitVector): Sha256Digest = { - sha256(bits.toByteVector) - } - - def sha256(str: String): Sha256Digest = { - sha256(serializeForHash(str)) - } - - def taggedSha256(bytes: ByteVector, tag: String): Sha256Digest = { - val tagHash = sha256(tag) - val tagBytes = tagHash.bytes ++ tagHash.bytes - sha256(tagBytes ++ bytes) + override def sha256(bytes: ByteVector): Sha256Digest = { + cryptoRuntime.sha256(bytes) } def taggedSha256(str: String, tag: String): Sha256Digest = { taggedSha256(serializeForHash(str), tag) } - // The tag "BIP0340/challenge" - private val schnorrChallengeTagBytes = { - ByteVector - .fromValidHex( - "7bb52d7a9fef58323eb1bf7a407db382d2f3f2d81bb1224f49fe518f6d48d37c7bb52d7a9fef58323eb1bf7a407db382d2f3f2d81bb1224f49fe518f6d48d37c" - ) - } - - def sha256SchnorrChallenge(bytes: ByteVector): Sha256Digest = { - sha256(schnorrChallengeTagBytes ++ bytes) - } - - // The tag "BIP0340/nonce" - private val schnorrNonceTagBytes = { - ByteVector - .fromValidHex( - "07497734a79bcb355b9b8c7d034f121cf434d73ef72dda19870061fb52bfeb2f07497734a79bcb355b9b8c7d034f121cf434d73ef72dda19870061fb52bfeb2f" - ) - } - - def sha256SchnorrNonce(bytes: ByteVector): Sha256Digest = { - sha256(schnorrNonceTagBytes ++ bytes) - } - - // The tag "BIP0340/aux" - private val schnorrAuxTagBytes = { - ByteVector - .fromValidHex( - "f1ef4e5ec063cada6d94cafa9d987ea069265839ecc11f972d77a52ed8c1cc90f1ef4e5ec063cada6d94cafa9d987ea069265839ecc11f972d77a52ed8c1cc90" - ) - } - - def sha256SchnorrAuxRand(bytes: ByteVector): Sha256Digest = { - sha256(schnorrAuxTagBytes ++ bytes) - } - - // The tag "DLC/oracle/attestation/v0" - private val dlcAttestationTagBytes = { - ByteVector - .fromValidHex( - "0c2fa46216e6e460e5e3f78555b102c5ac6aecabbfb82b430cf36cdfe04421790c2fa46216e6e460e5e3f78555b102c5ac6aecabbfb82b430cf36cdfe0442179" - ) - } - - def sha256DLCAttestation(bytes: ByteVector): Sha256Digest = { - sha256(dlcAttestationTagBytes ++ bytes) - } - - def sha256DLCAttestation(str: String): Sha256Digest = { - sha256DLCAttestation(CryptoUtil.serializeForHash(str)) - } - - // The tag "DLC/oracle/announcement/v0" - private val dlcAnnouncementTagBytes = { - ByteVector - .fromValidHex( - "6378871e8c99d480fff016e178a371e7e058445eff3023fe158f05aa185ed0e16378871e8c99d480fff016e178a371e7e058445eff3023fe158f05aa185ed0e1" - ) - } - - def sha256DLCAnnouncement(bytes: ByteVector): Sha256Digest = { - sha256(dlcAnnouncementTagBytes ++ bytes) - } - /** Performs SHA1(bytes). */ - def sha1(bytes: ByteVector): Sha1Digest = { - val hash = MessageDigest.getInstance("SHA-1").digest(bytes.toArray).toList - Sha1Digest(ByteVector(hash)) + override def sha1(bytes: ByteVector): Sha1Digest = { + cryptoRuntime.sha1(bytes) } def sha1(str: String): Sha1Digest = { @@ -141,14 +63,8 @@ trait CryptoUtil { } /** Performs RIPEMD160(bytes). */ - 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 ripeMd160(bytes: ByteVector): RipeMd160Digest = { + cryptoRuntime.ripeMd160(bytes) } def ripeMd160(str: String): RipeMd160Digest = { @@ -157,13 +73,8 @@ trait CryptoUtil { /** Calculates `HMAC-SHA512(key, data)` */ - 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 hmac512(key: ByteVector, data: ByteVector): ByteVector = { + cryptoRuntime.hmac512(key, data) } /** @param x x coordinate @@ -171,54 +82,13 @@ trait CryptoUtil { * 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") - } - - (ECPublicKey(0x02.toByte +: bytes32).toPoint, - ECPublicKey(0x03.toByte +: bytes32).toPoint) + cryptoRuntime.recoverPoint(x) } - /** 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 - */ - def recoverPublicKey( + 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 = ECPublicKey.fromPoint(Q1) - val pub2 = ECPublicKey.fromPoint(Q2) - (pub1, pub2) + cryptoRuntime.recoverPublicKey(signature, message) } } diff --git a/crypto/src/main/scala/org/bitcoins/crypto/ECKey.scala b/crypto/src/main/scala/org/bitcoins/crypto/ECKey.scala index aa11ffb489..ba80bbea68 100644 --- a/crypto/src/main/scala/org/bitcoins/crypto/ECKey.scala +++ b/crypto/src/main/scala/org/bitcoins/crypto/ECKey.scala @@ -1,15 +1,8 @@ package org.bitcoins.crypto import java.math.BigInteger -import java.security.SecureRandom import org.bitcoin.NativeSecp256k1 -import org.bouncycastle.crypto.AsymmetricCipherKeyPair -import org.bouncycastle.crypto.generators.ECKeyPairGenerator -import org.bouncycastle.crypto.params.{ - ECKeyGenerationParameters, - ECPrivateKeyParameters -} import org.bouncycastle.math.ec.ECPoint import scodec.bits.ByteVector @@ -358,17 +351,8 @@ object ECPrivateKey extends Factory[ECPrivateKey] { def freshPrivateKey: ECPrivateKey = freshPrivateKey(true) def freshPrivateKey(isCompressed: Boolean): ECPrivateKey = { - val secureRandom = new SecureRandom - 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, isCompressed) + val priv = CryptoUtil.freshPrivateKey + ECPrivateKey.fromBytes(priv.bytes, isCompressed) } } diff --git a/crypto/src/main/scala/org/bitcoins/crypto/JvmCryptoRuntime.scala b/crypto/src/main/scala/org/bitcoins/crypto/JvmCryptoRuntime.scala new file mode 100644 index 0000000000..274dd7379e --- /dev/null +++ b/crypto/src/main/scala/org/bitcoins/crypto/JvmCryptoRuntime.scala @@ -0,0 +1,139 @@ +package org.bitcoins.crypto + +import org.bitcoin.NativeSecp256k1 +import org.bouncycastle.crypto.AsymmetricCipherKeyPair +import org.bouncycastle.crypto.digests.{RIPEMD160Digest, SHA512Digest} +import org.bouncycastle.crypto.generators.ECKeyPairGenerator +import org.bouncycastle.crypto.macs.HMac +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} + +trait JvmCryptoRuntime extends CryptoRuntime { + private[this] lazy val secureRandom = new SecureRandom() + + 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 + */ + override 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") + } + + (ECPublicKey(0x02.toByte +: bytes32).toPoint, + ECPublicKey(0x03.toByte +: bytes32).toPoint) + } + + 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 = ECPublicKey.fromPoint(Q1) + val pub2 = ECPublicKey.fromPoint(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 = { + CryptoContext.default match { + case CryptoContext.BouncyCastle => + BouncyCastleUtil.computePublicKey(privateKey) + case CryptoContext.LibSecp256k1 => + 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) + } + } +} + +object JvmCryptoRuntime extends JvmCryptoRuntime