-
Notifications
You must be signed in to change notification settings - Fork 267
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for bech32m bitcoin wallets #2873
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,7 +18,7 @@ package fr.acinq.eclair.blockchain | |
|
||
import fr.acinq.bitcoin.psbt.Psbt | ||
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey | ||
import fr.acinq.bitcoin.scalacompat.{OutPoint, Satoshi, Transaction, TxId} | ||
import fr.acinq.bitcoin.scalacompat.{BlockHash, OutPoint, Satoshi, Script, Transaction, TxId, addressToPublicKeyScript} | ||
import fr.acinq.eclair.blockchain.fee.FeeratePerKw | ||
import scodec.bits.ByteVector | ||
|
||
|
@@ -28,6 +28,18 @@ import scala.concurrent.{ExecutionContext, Future} | |
* Created by PM on 06/07/2017. | ||
*/ | ||
|
||
sealed trait AddressType | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is confusing, this isn't really what we're interested in (bech32 vs bech32m is just the encoding, not really the type of address). What we want to do here is distinguish between segwit v0 p2wpkh addresses and segwit v1 p2tr (with the standard BIP86 // @formatter:off
sealed trait AddressType
object AddressType {
/** A segwit v0 p2wpkh address. */
case object P2WPKH extends AddressType { override def toString: String = "p2wpkh" }
/** A segwit v1 p2tr address that doesn't have any script path, as defined in BIP86. */
case object P2TR extends AddressType { override def toString: String = "p2tr" }
}
// @formatter:on |
||
|
||
object AddressType { | ||
case object Bech32 extends AddressType { | ||
override def toString: String = "bech32" | ||
} | ||
|
||
case object Bech32m extends AddressType { | ||
override def toString: String = "bech32m" | ||
} | ||
} | ||
|
||
/** This trait lets users fund lightning channels. */ | ||
trait OnChainChannelFunder { | ||
|
||
|
@@ -119,7 +131,7 @@ trait OnChainAddressGenerator { | |
/** | ||
* @param label used if implemented with bitcoin core, can be ignored by implementation | ||
*/ | ||
def getReceiveAddress(label: String = "")(implicit ec: ExecutionContext): Future[String] | ||
def getReceiveAddress(label: String = "", addressType_opt: Option[AddressType] = None)(implicit ec: ExecutionContext): Future[String] | ||
|
||
/** Generate a p2wpkh wallet address and return the corresponding public key. */ | ||
def getP2wpkhPubkey()(implicit ec: ExecutionContext): Future[PublicKey] | ||
|
@@ -132,6 +144,8 @@ trait OnchainPubkeyCache { | |
* @param renew applies after requesting the current pubkey, and is asynchronous | ||
*/ | ||
def getP2wpkhPubkey(renew: Boolean = true): PublicKey | ||
|
||
def getPubkeyScript(renew: Boolean = true): ByteVector | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is way too vague and confusing since in bitcoin terms a "scriptPubKey" can refer to a lot of things...and this trait is supposed to manage public keys directly. I think we should instead have: def getP2wpkhPubkey(renew: Boolean = true): PublicKey
def getP2trPubkey(renew: Boolean = true): PublicKey It's not the responsibility of this |
||
} | ||
|
||
/** This trait lets users check the wallet's on-chain balance. */ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,12 +21,11 @@ import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey | |
import fr.acinq.bitcoin.scalacompat._ | ||
import fr.acinq.bitcoin.{Bech32, Block, SigHash} | ||
import fr.acinq.eclair.ShortChannelId.coordinates | ||
import fr.acinq.eclair.blockchain.OnChainWallet | ||
import fr.acinq.eclair.blockchain.OnChainWallet.{FundTransactionResponse, MakeFundingTxResponse, OnChainBalance, ProcessPsbtResponse} | ||
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{GetTxWithMetaResponse, UtxoStatus, ValidateResult} | ||
import fr.acinq.eclair.blockchain.fee.{FeeratePerKB, FeeratePerKw} | ||
import fr.acinq.eclair.blockchain.{AddressType, OnChainWallet} | ||
import fr.acinq.eclair.crypto.keymanager.OnChainKeyManager | ||
import fr.acinq.eclair.json.SatoshiSerializer | ||
import fr.acinq.eclair.transactions.Transactions | ||
import fr.acinq.eclair.wire.protocol.ChannelAnnouncement | ||
import fr.acinq.eclair.{BlockHeight, TimestampSecond, TxCoordinates} | ||
|
@@ -550,10 +549,29 @@ class BitcoinCoreClient(val rpcClient: BitcoinJsonRPCClient, val onChainKeyManag | |
} | ||
} | ||
|
||
def getReceiveAddress(label: String)(implicit ec: ExecutionContext): Future[String] = for { | ||
JString(address) <- rpcClient.invoke("getnewaddress", label) | ||
_ <- extractPublicKey(address) | ||
} yield address | ||
private def verifyAddress(address: String)(implicit ec: ExecutionContext): Future[String] = { | ||
for { | ||
addressInfo <- rpcClient.invoke("getaddressinfo", address) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We shouldn't make this call if |
||
JString(keyPath) = addressInfo \ "hdkeypath" | ||
} yield { | ||
// check that when we manage private keys we can re-compute the address we got from bitcoin core | ||
onChainKeyManager_opt match { | ||
case Some(keyManager) => | ||
val (_, computed) = keyManager.derivePublicKey(DeterministicWallet.KeyPath(keyPath)) | ||
if (computed != address) return Future.failed(new RuntimeException("cannot recompute address generated by bitcoin core")) | ||
case None => () | ||
} | ||
address | ||
} | ||
} | ||
|
||
def getReceiveAddress(label: String, addressType_opt: Option[AddressType] = None)(implicit ec: ExecutionContext): Future[String] = { | ||
val params = List(label) ++ addressType_opt.map(_.toString).toList | ||
for { | ||
JString(address) <- rpcClient.invoke("getnewaddress", params: _*) | ||
_ <- verifyAddress(address) | ||
} yield address | ||
} | ||
|
||
def getP2wpkhPubkey()(implicit ec: ExecutionContext): Future[Crypto.PublicKey] = for { | ||
JString(address) <- rpcClient.invoke("getnewaddress", "", "bech32") | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,13 +16,13 @@ | |
|
||
package fr.acinq.eclair.channel.fsm | ||
|
||
import akka.actor.{ActorRef, FSM, Status} | ||
import akka.actor.FSM | ||
import fr.acinq.bitcoin.scalacompat.{ByteVector32, Script} | ||
import fr.acinq.eclair.Features | ||
import fr.acinq.eclair.channel._ | ||
import fr.acinq.eclair.db.PendingCommandsDb | ||
import fr.acinq.eclair.io.Peer | ||
import fr.acinq.eclair.wire.protocol.{HtlcSettlementMessage, LightningMessage, UpdateMessage} | ||
import fr.acinq.eclair.{Features, InitFeature} | ||
import scodec.bits.ByteVector | ||
|
||
import scala.concurrent.duration.DurationInt | ||
|
@@ -115,17 +115,21 @@ trait CommonHandlers { | |
upfrontShutdownScript | ||
} else { | ||
log.info("ignoring pre-generated shutdown script, because option_upfront_shutdown_script is disabled") | ||
generateFinalScriptPubKey() | ||
generateFinalScriptPubKey(data.commitments.params.localParams.initFeatures, data.commitments.params.remoteParams.initFeatures) | ||
} | ||
case None => | ||
// normal case: we don't pre-generate shutdown scripts | ||
generateFinalScriptPubKey() | ||
generateFinalScriptPubKey(data.commitments.params.localParams.initFeatures, data.commitments.params.remoteParams.initFeatures) | ||
} | ||
} | ||
|
||
private def generateFinalScriptPubKey(): ByteVector = { | ||
val finalPubKey = wallet.getP2wpkhPubkey() | ||
val finalScriptPubKey = Script.write(Script.pay2wpkh(finalPubKey)) | ||
private def generateFinalScriptPubKey(localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature]): ByteVector = { | ||
val allowAnySegwit = Features.canUseFeature(localFeatures, remoteFeatures, Features.ShutdownAnySegwit) | ||
val finalScriptPubKey = if (allowAnySegwit) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's confusing to only gate it on |
||
wallet.getPubkeyScript() | ||
} else { | ||
Script.write(Script.pay2wpkh(wallet.getP2wpkhPubkey())) | ||
} | ||
log.info(s"using finalScriptPubkey=$finalScriptPubKey") | ||
finalScriptPubKey | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shoudn't this now return an object containing the master pubkey for p2wpkh addresses AND the master pubkey for p2tr addresses?