All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fr.acinq.bitcoin.scalacompat.Crypto.scala Maven / Gradle / Ivy

package fr.acinq.bitcoin.scalacompat

import fr.acinq.bitcoin
import fr.acinq.bitcoin.scalacompat.KotlinUtils._
import scodec.bits.ByteVector

object Crypto {
  /**
   * A bitcoin private key.
   * A private key is valid if it is not 0 and less than the secp256k1 curve order when interpreted as an integer (most significant byte first).
   * The probability of choosing a 32-byte string uniformly at random which is an invalid private key is negligible, so this condition is not checked by default.
   * However, if you receive a private key from an external, untrusted source, you should call `isValid()` before actually using it.
   */
  case class PrivateKey(priv: bitcoin.PrivateKey) {
    val value: ByteVector32 = priv.value

    def add(that: PrivateKey): PrivateKey = PrivateKey(this.priv plus that.priv)

    def subtract(that: PrivateKey): PrivateKey = PrivateKey(this.priv minus that.priv)

    def multiply(that: PrivateKey): PrivateKey = PrivateKey(this.priv times that.priv)

    def +(that: PrivateKey): PrivateKey = add(that)

    def -(that: PrivateKey): PrivateKey = subtract(that)

    def *(that: PrivateKey): PrivateKey = multiply(that)

    def isZero: Boolean = priv.value == bitcoin.ByteVector32.Zeroes

    def isValid: Boolean = priv.isValid

    def publicKey: PublicKey = PublicKey(priv.publicKey())

    /**
     * @param prefix Private key prefix
     * @return the private key in Base58 (WIF) compressed format
     */
    def toBase58(prefix: Byte): String = priv.toBase58(prefix)

    def toHex: String = priv.toHex

    override def toString = priv.toString
  }

  object PrivateKey {
    def apply(data: ByteVector): PrivateKey = PrivateKey(new bitcoin.PrivateKey(data.toArray))

    /**
     * @param data serialized private key in bitcoin format
     * @return the de-serialized key
     */
    def fromBin(data: ByteVector): (PrivateKey, Boolean) = {
      val compressed = data.length match {
        case 32 => false
        case 33 if data.last == 1.toByte => true
      }
      (PrivateKey(data), compressed)
    }

    def fromBase58(value: String, prefix: Byte): (PrivateKey, Boolean) = {
      val p = bitcoin.PrivateKey.fromBase58(value, prefix)
      (p.getFirst, p.getSecond)
    }
  }

  /**
   * A bitcoin public key (in compressed form).
   * A public key is valid if it represents a point on the secp256k1 curve.
   * The validity of this public key is not checked by default, because when you create a public key from a private key it will always be valid.
   * However, if you receive a public key from an external, untrusted source, you should call `isValid()` before actually using it.
   */
  case class PublicKey(pub: bitcoin.PublicKey) {
    val value: ByteVector = pub.value

    def hash160: ByteVector = ByteVector.view(pub.hash160())

    def isValid: Boolean = pub.isValid

    def add(that: PublicKey): PublicKey = PublicKey(this.pub plus that.pub)

    def add(that: PrivateKey): PublicKey = PublicKey(this.pub plus that.priv.publicKey())

    def subtract(that: PublicKey): PublicKey = PublicKey(this.pub minus that.pub)

    def multiply(that: PrivateKey): PublicKey = PublicKey(this.pub times that.priv)

    def +(that: PublicKey): PublicKey = add(that)

    def -(that: PublicKey): PublicKey = subtract(that)

    def *(that: PrivateKey): PublicKey = multiply(that)

    def toUncompressedBin: ByteVector = ByteVector.view(pub.toUncompressedBin)

    def toHex: String = pub.toHex

    override def toString = pub.toString
  }

  object PublicKey {
    /**
     * @param raw        serialized value of this public key (a point)
     * @param checkValid indicates whether or not we check that this is a valid public key; this should be used
     *                   carefully for optimization purposes
     */
    def apply(raw: ByteVector, checkValid: Boolean = true): PublicKey = fromBin(raw, checkValid)

    def fromBin(input: ByteVector, checkValid: Boolean = true): PublicKey = {
      require(isPubKeyValidLax(input))
      require(!checkValid || Crypto.isPubKeyValidStrict(input), "public key is invalid")
      PublicKey(new bitcoin.PublicKey(bitcoin.PublicKey.compress(input.toArray)))
    }
  }

  /**
   * Computes ecdh using secp256k1's variant: sha256(priv * pub serialized in compressed format)
   *
   * @param priv private value
   * @param pub  public value
   * @return ecdh(priv, pub) as computed by libsecp256k1
   */
  def ecdh(priv: PrivateKey, pub: PublicKey): ByteVector32 = ByteVector32(ByteVector.view(bitcoin.Crypto.ecdh(priv.priv, pub.pub)))

  def hmac512(key: ByteVector, data: ByteVector): ByteVector = ByteVector.view(bitcoin.Crypto.hmac512(key.toArray, data.toArray))

  def sha256(x: ByteVector): ByteVector32 = ByteVector32(ByteVector.view(bitcoin.Crypto.sha256(x)))

  def ripemd160(x: ByteVector): ByteVector = ByteVector.view(bitcoin.Crypto.ripemd160(x))

  /**
   * 160 bits bitcoin hash, used mostly for address encoding
   * hash160(input) = RIPEMD160(SHA256(input))
   *
   * @param input array of byte
   * @return the 160 bits BTC hash of input
   */
  def hash160(input: ByteVector): ByteVector = ByteVector.view(bitcoin.Crypto.hash160(input.toArray))

  /**
   * 256 bits bitcoin hash
   * hash256(input) = SHA256(SHA256(input))
   *
   * @param input array of byte
   * @return the 256 bits BTC hash of input
   */
  def hash256(input: ByteVector): ByteVector32 = ByteVector32(ByteVector.view(bitcoin.Crypto.hash256(input.toArray)))

  def isDERSignature(sig: ByteVector): Boolean = bitcoin.Crypto.isDERSignature(sig.toArray)

  def isLowDERSignature(sig: ByteVector): Boolean = bitcoin.Crypto.isLowDERSignature(sig.toArray)

  def checkSignatureEncoding(sig: ByteVector, flags: Int): Boolean = bitcoin.Crypto.checkSignatureEncoding(sig.toArray, flags)

  def checkPubKeyEncoding(key: ByteVector, flags: Int, sigVersion: Int): Boolean = bitcoin.Crypto.checkPubKeyEncoding(key.toArray, flags, sigVersion)

  /**
   * @param key serialized public key
   * @return true if the key is valid. Please not that this performs very basic tests and does not check that the
   *         point represented by this key is actually valid.
   */
  def isPubKeyValidLax(key: ByteVector): Boolean = key.length match {
    case 65 if key(0) == 4 || key(0) == 6 || key(0) == 7 => true
    case 33 if key(0) == 2 || key(0) == 3 => true
    case _ => false
  }

  /**
   * @param key serialized public key
   * @return true if the key is valid. This check is much more expensive than its lax version since here we check that
   *         the public key is a valid point on the secp256k1 curve
   */
  def isPubKeyValidStrict(key: ByteVector): Boolean = isPubKeyValidLax(key) && bitcoin.Crypto.isPubKeyValid(key.toArray)

  def isPubKeyCompressedOrUncompressed(key: ByteVector): Boolean = bitcoin.Crypto.isPubKeyCompressedOrUncompressed(key.toArray)

  def isPubKeyCompressed(key: ByteVector): Boolean = bitcoin.Crypto.isPubKeyCompressed(key.toArray)

  def isDefinedHashTypeSignature(sig: ByteVector): Boolean = bitcoin.Crypto.isDefinedHashTypeSignature(sig.toArray)

  def compact2der(signature: ByteVector64): ByteVector = bitcoin.Crypto.compact2der(signature)

  def der2compact(signature: ByteVector): ByteVector64 = bitcoin.Crypto.der2compact(signature.toArray)

  /**
   * @param data      data
   * @param signature signature
   * @param publicKey public key
   * @return true is signature is valid for this data with this public key
   */
  def verifySignature(data: ByteVector, signature: ByteVector64, publicKey: PublicKey): Boolean = bitcoin.Crypto.verifySignature(data.toArray, signature, publicKey.pub)

  /**
   * @param privateKey private key
   * @return the corresponding public key
   */
  def publicKeyFromPrivateKey(privateKey: ByteVector): PublicKey = PrivateKey(privateKey).publicKey

  /**
   * Sign data with a private key, using RCF6979 deterministic signatures
   *
   * @param data       data to sign
   * @param privateKey private key. If you are using bitcoin "compressed" private keys make sure to only use the first 32 bytes of
   *                   the key (there is an extra "1" appended to the key)
   * @return a signature in compact format (64 bytes)
   */
  def sign(data: Array[Byte], privateKey: PrivateKey): ByteVector64 = bitcoin.Crypto.sign(data, privateKey.priv)

  def sign(data: ByteVector, privateKey: PrivateKey): ByteVector64 = sign(data.toArray, privateKey)

  /**
   * 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 recovered public key
   */
  def recoverPublicKey(signature: ByteVector64, message: ByteVector, recoveryId: Int): PublicKey = PublicKey(bitcoin.Crypto.recoverPublicKey(signature, message.toArray, recoveryId))

  def recoverPublicKey(signature: ByteVector64, message: ByteVector): (PublicKey, PublicKey) = {
    val p = bitcoin.Crypto.recoverPublicKey(signature, message.toArray)
    (PublicKey(p.getFirst), PublicKey(p.getSecond))
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy