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

fr.acinq.bitcoin.DeterministicWallet.scala Maven / Gradle / Ivy

package fr.acinq.bitcoin

import java.io.{ByteArrayInputStream, ByteArrayOutputStream, OutputStream}
import java.math.BigInteger
import java.nio.ByteOrder

import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.Protocol._
import scodec.bits.ByteVector

/**
 * see https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
 */
object DeterministicWallet {

  case class KeyPath(path: Seq[Long]) {
    def lastChildNumber: Long = if (path.isEmpty) 0L else path.last

    def derive(number: Long) = KeyPath(path :+ number)

    override def toString = path.map(KeyPath.childNumberToString).foldLeft("m")(_ + "/" + _)
  }

  object KeyPath {
    val Root = KeyPath(Nil)

    /**
     *
     * @param path key path. A list of integers separated by a `/`. May start with "/" or "m/". A single quote appended
     *             at the end means use the hardened version of the ley index (example: m/44'/0'/0'/0)
     * @return a KeyPath instance
     */
    def apply(path: String): KeyPath = {
      def toNumber(value: String): Long = if (value.last == '\'') hardened(value.dropRight(1).toLong) else value.toLong

      val path1 = path.stripPrefix("m").stripPrefix("/")
      if (path1.isEmpty) KeyPath.Root else new KeyPath(path1.split('/').map(toNumber).toSeq)
    }

    def childNumberToString(childNumber: Long) = if (isHardened(childNumber)) (childNumber - hardenedKeyIndex).toString + "'" else childNumber.toString
  }

  implicit def keypath2longseq(input: KeyPath): Seq[Long] = input.path

  implicit def longseq2keypath(input: Seq[Long]): KeyPath = KeyPath(input)

  val hardenedKeyIndex = 0x80000000L

  def hardened(index: Long): Long = hardenedKeyIndex + index

  def isHardened(index: Long): Boolean = index >= hardenedKeyIndex

  case class ExtendedPrivateKey(secretkeybytes: ByteVector32, chaincode: ByteVector32, depth: Int, path: KeyPath, parent: Long) {

    def privateKey: PrivateKey = PrivateKey(secretkeybytes)

    def publicKey: PublicKey = privateKey.publicKey
  }

  object ExtendedPrivateKey {
    def decode(input: String, parentPath: KeyPath = KeyPath.Root): (Int, ExtendedPrivateKey) = {
      val (prefix, bin) = Base58Check.decodeWithIntPrefix(input)
      val bis = new ByteArrayInputStream(bin.toArray)
      val depth = Protocol.uint8(bis)
      val parent = Protocol.uint32(bis, ByteOrder.BIG_ENDIAN)
      val childNumber = Protocol.uint32(bis, ByteOrder.BIG_ENDIAN)
      val chaincode = ByteVector32(Protocol.bytes(bis, 32))
      require(bis.read() == 0)
      val secretkeybytes = ByteVector32(Protocol.bytes(bis, 32))
      (prefix, ExtendedPrivateKey(secretkeybytes, chaincode, depth, parentPath.derive(childNumber), parent))
    }
  }

  def encode(input: ExtendedPrivateKey, prefix: Int): String = {
    val out = new ByteArrayOutputStream()
    writeUInt8(input.depth, out)
    writeUInt32(input.parent.toInt, out, ByteOrder.BIG_ENDIAN)
    writeUInt32(input.path.lastChildNumber.toInt, out, ByteOrder.BIG_ENDIAN)
    out.write(input.chaincode.toArray)
    out.write(0)
    out.write(input.secretkeybytes.toArray)
    val buffer = ByteVector.view(out.toByteArray)
    Base58Check.encode(prefix, buffer)
  }

  case class ExtendedPublicKey(publickeybytes: ByteVector, chaincode: ByteVector32, depth: Int, path: KeyPath, parent: Long) {
    require(publickeybytes.length == 33)
    require(chaincode.length == 32)

    def publicKey: PublicKey = PublicKey(publickeybytes)
  }

  object ExtendedPublicKey {
    def decode(input: String, parentPath: KeyPath = KeyPath.Root): (Int, ExtendedPublicKey) = {
      val (prefix, bin) = Base58Check.decodeWithIntPrefix(input)
      val bis = new ByteArrayInputStream(bin.toArray)
      val depth = Protocol.uint8(bis)
      val parent = Protocol.uint32(bis, ByteOrder.BIG_ENDIAN)
      val childNumber = Protocol.uint32(bis, ByteOrder.BIG_ENDIAN)
      val chaincode = ByteVector32(Protocol.bytes(bis, 32))
      val publickeybytes = Protocol.bytes(bis, 33)
      (prefix.toInt, ExtendedPublicKey(publickeybytes, chaincode, depth, parentPath.derive(childNumber), parent))
    }
  }

  def encode(input: ExtendedPublicKey, prefix: Int): String = {
    val out = new ByteArrayOutputStream()
    write(input, out)
    val buffer = ByteVector.view(out.toByteArray)
    Base58Check.encode(prefix, buffer)
  }

  def write(input: ExtendedPublicKey, output: OutputStream): Unit = {
    writeUInt8(input.depth, output)
    writeUInt32(input.parent.toInt, output, ByteOrder.BIG_ENDIAN)
    writeUInt32(input.path.lastChildNumber.toInt, output, ByteOrder.BIG_ENDIAN)
    writeBytes(input.chaincode.toArray, output)
    writeBytes(input.publickeybytes.toArray, output)
  }

  /**
   *
   * @param seed random seed
   * @return a "master" private key
   */
  def generate(seed: ByteVector): ExtendedPrivateKey = {
    val I = Crypto.hmac512(ByteVector.view("Bitcoin seed".getBytes("UTF-8")), seed)
    val IL = ByteVector32(I.take(32))
    val IR = ByteVector32(I.takeRight(32))
    ExtendedPrivateKey(IL, IR, depth = 0, path = List.empty[Long], parent = 0L)
  }

  /**
   *
   * @param input extended private key
   * @return the public key for this private key
   */
  def publicKey(input: ExtendedPrivateKey): ExtendedPublicKey = {
    ExtendedPublicKey(input.publicKey.value, input.chaincode, depth = input.depth, path = input.path, parent = input.parent)
  }

  /**
   *
   * @param input extended public key
   * @return the fingerprint for this public key
   */
  def fingerprint(input: ExtendedPublicKey): Long = uint32(new ByteArrayInputStream(Crypto.hash160(input.publickeybytes).take(4).reverse.toArray))

  /**
   *
   * @param input extended private key
   * @return the fingerprint for this private key (which is based on the corresponding public key)
   */
  def fingerprint(input: ExtendedPrivateKey): Long = fingerprint(publicKey(input))

  /**
   *
   * @param parent extended private key
   * @param index  index of the child key
   * @return the derived private key at the specified index
   */
  def derivePrivateKey(parent: ExtendedPrivateKey, index: Long): ExtendedPrivateKey = {
    val I = if (isHardened(index)) {
      val buffer = 0.toByte +: parent.secretkeybytes
      Crypto.hmac512(parent.chaincode, buffer ++ writeUInt32(index.toInt, ByteOrder.BIG_ENDIAN))
    } else {
      val pub = publicKey(parent).publickeybytes
      Crypto.hmac512(parent.chaincode, pub ++ writeUInt32(index.toInt, ByteOrder.BIG_ENDIAN))
    }
    val IL = ByteVector32(I.take(32))
    val IR = ByteVector32(I.takeRight(32))
    val p = new BigInteger(1, IL.toArray)
    if (p.compareTo(Crypto.curve.getN) >= 0) {
      throw new RuntimeException("cannot generated child private key")
    }

    val key = PrivateKey(IL).add(parent.privateKey)
    if (key.isZero) {
      throw new RuntimeException("cannot generated child private key")
    }
    val buffer = key.value
    ExtendedPrivateKey(buffer, chaincode = IR, depth = parent.depth + 1, path = parent.path.derive(index), parent = fingerprint(parent))
  }

  /**
   *
   * @param parent extended public key
   * @param index  index of the child key
   * @return the derived public key at the specified index
   */
  def derivePublicKey(parent: ExtendedPublicKey, index: Long): ExtendedPublicKey = {
    require(!isHardened(index), "Cannot derive public keys from public hardened keys")

    val I = Crypto.hmac512(parent.chaincode, parent.publickeybytes ++ writeUInt32(index.toInt, ByteOrder.BIG_ENDIAN))
    val IL = ByteVector32(I.take(32))
    val IR = ByteVector32(I.takeRight(32))
    val p = new BigInteger(1, IL.toArray)
    if (p.compareTo(Crypto.curve.getN) >= 0) {
      throw new RuntimeException("cannot generated child public key")
    }
    val Ki = PrivateKey(p).publicKey.add(parent.publicKey)
    if (Ki.ecpoint.isInfinity) {
      throw new RuntimeException("cannot generated child public key")
    }
    val buffer = Ki.value
    ExtendedPublicKey(buffer, chaincode = IR, depth = parent.depth + 1, path = parent.path.derive(index), parent = fingerprint(parent))
  }

  def derivePrivateKey(parent: ExtendedPrivateKey, chain: Seq[Long]): ExtendedPrivateKey = chain.foldLeft(parent)(derivePrivateKey)

  def derivePrivateKey(parent: ExtendedPrivateKey, keyPath: KeyPath): ExtendedPrivateKey = derivePrivateKey(parent, keyPath.path)

  def derivePublicKey(parent: ExtendedPublicKey, chain: Seq[Long]): ExtendedPublicKey = chain.foldLeft(parent)(derivePublicKey)

  def derivePublicKey(parent: ExtendedPublicKey, keyPath: KeyPath): ExtendedPublicKey = derivePublicKey(parent, keyPath.path)

  // p2pkh mainnet
  val xprv = 0x0488ade4
  val xpub = 0x0488b21e

  // p2sh-of-p2wpkh mainnet
  val yprv = 0x049d7878
  val ypub = 0x049d7cb2

  // p2wpkh mainnet
  val zprv = 0x04b2430c
  val zpub = 0x04b24746

  // p2pkh testnet
  val tprv = 0x04358394
  val tpub = 0x043587cf

  // p2sh-of-p2wpkh testnet
  val uprv = 0x044a4e28
  val upub = 0x044a5262

  // p2wpkh testnet
  val vprv = 0x045f18bc
  val vpub = 0x045f1cf6
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy