mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-20 22:13:14 +01:00
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
This commit is contained in:
parent
bcade41326
commit
a1bdbda039
5 changed files with 324 additions and 184 deletions
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
139
crypto/src/main/scala/org/bitcoins/crypto/CryptoRuntime.scala
Normal file
139
crypto/src/main/scala/org/bitcoins/crypto/CryptoRuntime.scala
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
139
crypto/src/main/scala/org/bitcoins/crypto/JvmCryptoRuntime.scala
Normal file
139
crypto/src/main/scala/org/bitcoins/crypto/JvmCryptoRuntime.scala
Normal file
|
@ -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
|
Loading…
Add table
Reference in a new issue