Skip to content

Commit

Permalink
Add bip32 derivation path to the funding PSBT that will be signed by …
Browse files Browse the repository at this point in the history
…bitcoin core
  • Loading branch information
sstone committed Aug 16, 2021
1 parent 46dfff6 commit 44dbaf3
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package fr.acinq.eclair.blockchain

import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin.DeterministicWallet.ExtendedPublicKey
import fr.acinq.bitcoin.{Psbt, Satoshi, Transaction}
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import scodec.bits.ByteVector
Expand All @@ -36,7 +37,9 @@ trait EclairWallet {
def getReceiveAddress(label: String = ""): Future[String]

def getReceivePubkey(receiveAddress: Option[String] = None): Future[PublicKey]


def makeFundingTx(localFundingKey: ExtendedPublicKey, remoteFundingKey: PublicKey, amount: Satoshi, feeRatePerKw: FeeratePerKw): Future[MakeFundingTxResponse]

def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: FeeratePerKw): Future[MakeFundingTxResponse]

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@
package fr.acinq.eclair.blockchain.bitcoind

import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin.DeterministicWallet.ExtendedPublicKey
import fr.acinq.bitcoin._
import fr.acinq.eclair.addressToPublicKeyScript
import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.blockchain.bitcoind.rpc.ExtendedBitcoinClient.{FundPsbtOptions, FundPsbtResponse, FundTransactionOptions, FundTransactionResponse, ProcessPsbtResponse, SignTransactionResponse, toSatoshi}
import fr.acinq.eclair.blockchain.bitcoind.rpc.{BitcoinJsonRPCClient, ExtendedBitcoinClient}
import fr.acinq.eclair.blockchain.fee.{FeeratePerKB, FeeratePerKw}
import fr.acinq.eclair.transactions.Transactions
import fr.acinq.eclair.transactions.{Scripts, Transactions}
import grizzled.slf4j.Logging
import org.json4s.JsonAST._
import scodec.bits.ByteVector
Expand Down Expand Up @@ -180,6 +181,38 @@ class BitcoinCoreWallet(chainHash: ByteVector32, rpcClient: BitcoinJsonRPCClient
JString(rawKey) <- rpcClient.invoke("getaddressinfo", address).map(_ \ "pubkey")
} yield PublicKey(ByteVector.fromValidHex(rawKey))

override def makeFundingTx(localFundingKey: ExtendedPublicKey, remoteFundingKey: PublicKey, amount: Satoshi, feeRatePerKw: FeeratePerKw): Future[MakeFundingTxResponse] = {
val hrp = chainHash match {
case Block.RegtestGenesisBlock.hash => "bcrt"
case Block.TestnetGenesisBlock.hash => "tb"
case Block.LivenetGenesisBlock.hash => "bc"
case _ => return Future.failed(new IllegalArgumentException(s"invalid chain hash ${chainHash}"))
}
val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingKey.publicKey, remoteFundingKey)))
val fundingAddress = Script.parse(fundingPubkeyScript) match {
case OP_0 :: OP_PUSHDATA(data, _) :: Nil if data.size == 20 || data.size == 32 => Bech32.encodeWitnessAddress(hrp, 0, data)
case _ => return Future.failed(new IllegalArgumentException("invalid pubkey script"))
}

for {
// we ask bitcoin core to create and fund the funding tx
actualFeeRate <- getMinFeerate(feeRatePerKw)
FundPsbtResponse(psbt, fee, Some(changePos)) <- bitcoinClient.fundPsbt(Map(fundingAddress -> amount), 0, FundPsbtOptions(FeeratePerKw(actualFeeRate), lockUtxos = true))

output = psbt.outputs(1 - changePos).copy(derivationPaths = Map(localFundingKey.publicKey -> Psbt.KeyPathWithMaster(localFundingKey.parent, localFundingKey.path)))
psbt1 = psbt.copy(outputs = psbt.outputs.updated(1 - changePos, output))

// now let's sign the funding tx
ProcessPsbtResponse(signedPsbt, true) <- signPsbtOrUnlock(psbt1)
Success(fundingTx) = signedPsbt.extract()
// there will probably be a change output, so we need to find which output is ours
outputIndex <- Transactions.findPubKeyScriptIndex(fundingTx, fundingPubkeyScript) match {
case Right(outputIndex) => Future.successful(outputIndex)
case Left(skipped) => Future.failed(new RuntimeException(skipped.toString))
}
_ = logger.debug(s"created funding txid=${fundingTx.txid} outputIndex=$outputIndex fee=${fee}")
} yield MakeFundingTxResponse(signedPsbt, outputIndex, fee)
}

override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feerate: FeeratePerKw): Future[MakeFundingTxResponse] = {
val hrp = chainHash match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ object ExtendedBitcoinClient {
}

case class FundPsbtResponse(psbt: Psbt, fee: Satoshi, changePosition: Option[Int]) {
val amountIn: Satoshi = fee + psbt.computeFees().get
val amountIn: Satoshi = psbt.computeFees().get + psbt.global.tx.txOut.map(_.amount).sum
}

case class PreviousTx(txid: ByteVector32, vout: Long, scriptPubKey: String, redeemScript: String, witnessScript: String, amount: BigDecimal)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -413,8 +413,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
shutdownScript = remoteShutdownScript)
log.debug("remote params: {}", remoteParams)
val localFundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath)
val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey.publicKey, remoteParams.fundingPubKey)))
wallet.makeFundingTx(fundingPubkeyScript, fundingSatoshis, fundingTxFeeratePerKw).pipeTo(self)
wallet.makeFundingTx(localFundingPubkey, remoteParams.fundingPubKey, fundingSatoshis, fundingTxFeeratePerKw).pipeTo(self)
goto(WAIT_FOR_FUNDING_INTERNAL) using DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, accept.firstPerCommitmentPoint, channelConfig, channelFeatures, open)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
package fr.acinq.eclair.blockchain

import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin.{ByteVector32, Crypto, OutPoint, Psbt, Satoshi, SatoshiLong, Script, Transaction, TxIn, TxOut}
import fr.acinq.bitcoin.{ByteVector32, Crypto, DeterministicWallet, OutPoint, Psbt, Satoshi, SatoshiLong, Script, Transaction, TxIn, TxOut}
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import fr.acinq.eclair.randomKey
import fr.acinq.eclair.transactions.Scripts
import scodec.bits._

import scala.concurrent.Future
Expand All @@ -41,6 +42,9 @@ class TestWallet extends EclairWallet {
override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: FeeratePerKw): Future[MakeFundingTxResponse] =
Future.successful(TestWallet.makeDummyFundingTx(pubkeyScript, amount))

override def makeFundingTx(localFundingKey: DeterministicWallet.ExtendedPublicKey, remoteFundingKey: PublicKey, amount: Satoshi, feeRatePerKw: FeeratePerKw): Future[MakeFundingTxResponse] =
makeFundingTx(Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingKey.publicKey, remoteFundingKey))), amount, feeRatePerKw)

override def commit(tx: Transaction): Future[Boolean] = Future.successful(true)

override def rollback(tx: Transaction): Future[Boolean] = {
Expand Down

0 comments on commit 44dbaf3

Please sign in to comment.