mirror of
https://github.com/ACINQ/eclair.git
synced 2024-11-20 02:27:32 +01:00
electrum wallet: export xpub (#586)
This commit is contained in:
parent
7c12a7fecf
commit
d3244c8499
@ -33,6 +33,8 @@ class ElectrumEclairWallet(val wallet: ActorRef, chainHash: BinaryData)(implicit
|
||||
|
||||
override def getFinalAddress = (wallet ? GetCurrentReceiveAddress).mapTo[GetCurrentReceiveAddressResponse].map(_.address)
|
||||
|
||||
def getXpub: Future[GetXpubResponse] = (wallet ? GetXpub).mapTo[GetXpubResponse]
|
||||
|
||||
override def makeFundingTx(pubkeyScript: BinaryData, amount: Satoshi, feeRatePerKw: Long) = {
|
||||
val tx = Transaction(version = 2, txIn = Nil, txOut = TxOut(amount, pubkeyScript) :: Nil, lockTime = 0)
|
||||
(wallet ? CompleteTransaction(tx, feeRatePerKw)).mapTo[CompleteTransactionResponse].map(response => response match {
|
||||
|
@ -266,6 +266,11 @@ class ElectrumWallet(seed: BinaryData, client: ActorRef, params: ElectrumWallet.
|
||||
|
||||
case Event(GetData, data) => stay replying GetDataResponse(data)
|
||||
|
||||
case Event(GetXpub ,_) => {
|
||||
val (xpub, path) = computeXpub(master, chainHash)
|
||||
stay replying GetXpubResponse(xpub, path)
|
||||
}
|
||||
|
||||
case Event(ElectrumClient.BroadcastTransaction(tx), _) => stay replying ElectrumClient.BroadcastTransactionResponse(tx, Some(Error(-1, "wallet is not connected")))
|
||||
}
|
||||
|
||||
@ -294,6 +299,9 @@ object ElectrumWallet {
|
||||
case object GetBalance extends Request
|
||||
case class GetBalanceResponse(confirmed: Satoshi, unconfirmed: Satoshi) extends Response
|
||||
|
||||
case object GetXpub extends Request
|
||||
case class GetXpubResponse(xpub: String, path: String) extends Response
|
||||
|
||||
case object GetCurrentReceiveAddress extends Request
|
||||
case class GetCurrentReceiveAddressResponse(address: String) extends Response
|
||||
|
||||
@ -369,32 +377,43 @@ object ElectrumWallet {
|
||||
*/
|
||||
def computeScriptHashFromPublicKey(key: PublicKey): BinaryData = Crypto.sha256(Script.write(computePublicKeyScript(key))).reverse
|
||||
|
||||
def accountPath(chainHash: BinaryData) : List[Long] = chainHash match {
|
||||
case Block.RegtestGenesisBlock.hash | Block.TestnetGenesisBlock.hash => hardened(49) :: hardened(1) :: hardened(0) :: Nil
|
||||
case Block.LivenetGenesisBlock.hash => hardened(49) :: hardened(0) :: hardened(0) :: Nil
|
||||
}
|
||||
|
||||
/**
|
||||
* use BIP49 (and not BIP44) since we use p2sh-of-p2wpkh
|
||||
*
|
||||
* @param master master key
|
||||
* @return the BIP49 account key for this master key: m/49'/1'/0'/0 on testnet/regtest, m/49'/0'/0'/0 on mainnet
|
||||
*/
|
||||
def accountKey(master: ExtendedPrivateKey, chainHash: BinaryData) = chainHash match {
|
||||
case Block.RegtestGenesisBlock.hash | Block.TestnetGenesisBlock.hash =>
|
||||
DeterministicWallet.derivePrivateKey(master, hardened(49) :: hardened(1) :: hardened(0) :: 0L :: Nil)
|
||||
case Block.LivenetGenesisBlock.hash =>
|
||||
DeterministicWallet.derivePrivateKey(master, hardened(49) :: hardened(0) :: hardened(0) :: 0L :: Nil)
|
||||
}
|
||||
def accountKey(master: ExtendedPrivateKey, chainHash: BinaryData) = DeterministicWallet.derivePrivateKey(master, accountPath(chainHash) ::: 0L :: Nil)
|
||||
|
||||
|
||||
/**
|
||||
* Compute the wallet's xpub
|
||||
* @param master master key
|
||||
* @param chainHash chain hash
|
||||
* @return a (xpub, path) tuple where xpub is the encoded account public key, and path is the derivation path for the account key
|
||||
*/
|
||||
def computeXpub(master: ExtendedPrivateKey, chainHash: BinaryData) : (String, String) = {
|
||||
val xpub = DeterministicWallet.publicKey(DeterministicWallet.derivePrivateKey(master, accountPath(chainHash)))
|
||||
// we use the tpub/xpub prefix instead of upub/ypub because it is more widely understood
|
||||
val prefix = chainHash match {
|
||||
case Block.LivenetGenesisBlock.hash => DeterministicWallet.xpub
|
||||
case Block.RegtestGenesisBlock.hash | Block.TestnetGenesisBlock.hash => DeterministicWallet.tpub
|
||||
}
|
||||
(DeterministicWallet.encode(xpub, prefix), xpub.path.toString())
|
||||
}
|
||||
|
||||
/**
|
||||
* use BIP49 (and not BIP44) since we use p2sh-of-p2wpkh
|
||||
*
|
||||
* @param master master key
|
||||
* @return the BIP49 change key for this master key: m/49'/1'/0'/1 on testnet/regtest, m/49'/0'/0'/1 on mainnet
|
||||
*/
|
||||
def changeKey(master: ExtendedPrivateKey, chainHash: BinaryData) = chainHash match {
|
||||
case Block.RegtestGenesisBlock.hash | Block.TestnetGenesisBlock.hash =>
|
||||
DeterministicWallet.derivePrivateKey(master, hardened(49) :: hardened(1) :: hardened(0) :: 1L :: Nil)
|
||||
case Block.LivenetGenesisBlock.hash =>
|
||||
DeterministicWallet.derivePrivateKey(master, hardened(49) :: hardened(0) :: hardened(0) :: 1L :: Nil)
|
||||
}
|
||||
def changeKey(master: ExtendedPrivateKey, chainHash: BinaryData) = DeterministicWallet.derivePrivateKey(master, accountPath(chainHash) ::: 1L :: Nil)
|
||||
|
||||
def totalAmount(utxos: Seq[Utxo]): Satoshi = Satoshi(utxos.map(_.item.value).sum)
|
||||
|
||||
|
@ -20,7 +20,7 @@ import akka.actor.{Actor, ActorSystem, Props}
|
||||
import akka.testkit.{TestFSMRef, TestKit, TestProbe}
|
||||
import fr.acinq.bitcoin.{BinaryData, Block, MnemonicCode, Satoshi}
|
||||
import fr.acinq.eclair.blockchain.electrum.ElectrumClient.{ScriptHashSubscription, ScriptHashSubscriptionResponse}
|
||||
import fr.acinq.eclair.blockchain.electrum.ElectrumWallet.{NewWalletReceiveAddress, WalletEvent, WalletParameters, WalletReady}
|
||||
import fr.acinq.eclair.blockchain.electrum.ElectrumWallet._
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.FunSuiteLike
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
@ -63,12 +63,17 @@ class ElectrumWalletSimulatedClientSpec extends TestKit(ActorSystem("test")) wit
|
||||
awaitCond(wallet.stateName == ElectrumWallet.RUNNING)
|
||||
assert(listener.expectMsgType[WalletReady].timestamp == header1.timestamp)
|
||||
listener.expectMsgType[NewWalletReceiveAddress]
|
||||
listener.send(wallet, GetXpub)
|
||||
val GetXpubResponse(xpub, path) = listener.expectMsgType[GetXpubResponse]
|
||||
assert(xpub == "tpubDCY62b4okoTERzMurvrtoCMgkswfLufejmhwfShqAKDBN2PPNUWpwx72cvyt4R8enGstorHvXNGS8StbkAsPb7XSbYFER8Wo6zPf1Z6m9w4")
|
||||
assert(path == "m/49'/1'/0'")
|
||||
}
|
||||
|
||||
test("tell wallet is ready when a new block comes in, even if nothing else has changed") {
|
||||
sender.send(wallet, ElectrumClient.HeaderSubscriptionResponse(header2))
|
||||
assert(listener.expectMsgType[WalletReady].timestamp == header2.timestamp)
|
||||
listener.expectMsgType[NewWalletReceiveAddress]
|
||||
val NewWalletReceiveAddress(address) = listener.expectMsgType[NewWalletReceiveAddress]
|
||||
assert(address == "2NDjBqJugL3gCtjWTToDgaWWogq9nYuYw31")
|
||||
}
|
||||
|
||||
test("tell wallet is ready when it is reconnected, even if nothing has changed") {
|
||||
|
Loading…
Reference in New Issue
Block a user