Wilmer Paulino
3 years ago
1 changed files with 290 additions and 0 deletions
@ -0,0 +1,290 @@
|
||||
# Overview |
||||
|
||||
This document serves as an introductory point for users interested in reducing |
||||
their hot-wallet risks, allowing them to maintain on-chain funds outside of |
||||
`lnd` but still be able to manage them within `lnd`. As of `v0.13.0-beta`, `lnd` |
||||
is able to import BIP-0049 and BIP-0084 extended public keys either at the |
||||
account path (`m/purpose'/coin_type'/account'`) or at the address index path |
||||
(`m/purpose'/coin_type'/account'/change/address_index`) as watch-only through |
||||
the `WalletKit` APIs. |
||||
|
||||
Note that in order to follow the rest of this document and/or use the |
||||
`WalletKit` APIs, users will need to obtain a `lnd` build compiled with the |
||||
`walletrpc` tag. Our release builds already include this tag by default, so this |
||||
would only be necessary when compiling from source. |
||||
|
||||
# `lnd`'s Default Wallet Accounts |
||||
|
||||
Upon initializing `lnd`, a wallet is created with four default accounts: |
||||
|
||||
* A custom BIP-0049 account (more on this later) to generate NP2WKH external |
||||
addresses. |
||||
* A BIP-0084 account to generate P2WKH external and change addresses. |
||||
* A catch-all BIP-0049 account where all imported BIP-0049 address keys (NP2WKH |
||||
addresses) exist within. |
||||
* A catch-all BIP-0084 account where all imported BIP-0049 address keys (P2WKH |
||||
addresses) exist within. |
||||
|
||||
Prior to `v0.13.0-beta`, these accounts were abstracted away from users. As part |
||||
of the key import feature, they are now exposed through the new `WalletKit` RPCs |
||||
(`ListAccounts`, `ImportAccount`, `ImportPublicKey`) and the `lncli wallet |
||||
accounts` command. |
||||
|
||||
```shell |
||||
$ lncli wallet accounts |
||||
NAME: |
||||
lncli wallet accounts - Interact with wallet accounts. |
||||
|
||||
USAGE: |
||||
lncli wallet accounts command [command options] [arguments...] |
||||
|
||||
COMMANDS: |
||||
list Retrieve information of existing on-chain wallet accounts. |
||||
import Import an on-chain account into the wallet through its extended public key. |
||||
import-pubkey Import a public key as watch-only into the wallet. |
||||
|
||||
OPTIONS: |
||||
--help, -h show help |
||||
``` |
||||
|
||||
## Account Details |
||||
|
||||
Before interacting with the new set of APIs, users will want to become familiar |
||||
with how wallet accounts are represented within `lnd`. The |
||||
`WalletKit.ListAccounts` RPC or `lncli wallet accounts list` command can be used |
||||
to retrieve the details of accounts. |
||||
|
||||
```shell |
||||
$ lncli wallet accounts list |
||||
{ |
||||
"accounts": [ |
||||
{ |
||||
"name": "default", |
||||
"address_type": "HYBRID_NESTED_WITNESS_PUBKEY_HASH", |
||||
"extended_public_key": "upub5EbJZz2tYCpPFgDAMDnXpTeLs5EMNJAfyzRKQuUiTugSaJDjnDdk9vNcENzpw1FnxkerNW7jLuBeoxmcGMtopGExmaWqrMB7wRgU8tExTMz", |
||||
"master_key_fingerprint": null, |
||||
"derivation_path": "m/49'/0'/0'", |
||||
"external_key_count": 0, |
||||
"internal_key_count": 0, |
||||
"watch_only": false |
||||
}, |
||||
{ |
||||
"name": "default", |
||||
"address_type": "WITNESS_PUBKEY_HASH", |
||||
"extended_public_key": "vpub5Z9beF6NYCrHeDmKC38tM3xXMDFFSARa9sdHRPChEMGqtxiELfZB8hm6FwBpBvfPpX2HGG8edYVV9Wupe43PEJJhhfnz1egtQNNaDXyYExn", |
||||
"master_key_fingerprint": null, |
||||
"derivation_path": "m/84'/0'/0'", |
||||
"external_key_count": 0, |
||||
"internal_key_count": 0, |
||||
"watch_only": false |
||||
} |
||||
] |
||||
} |
||||
``` |
||||
|
||||
There's a lot to unpack in the response above, so let's cover each account field |
||||
in detail. As mentioned above, four default accounts should exist, though only |
||||
two are shown in the output. The catch-all imported accounts are hidden by |
||||
default until a key has been imported into them. |
||||
|
||||
* `name`: Each account has a name it can be identified by. `lnd`'s default |
||||
spendable accounts have the name "default". The default catch-all imported |
||||
accounts have the name "imported". |
||||
* `extended_public_key`: The BIP-0044 extended public key for the account. Any |
||||
addresses generated for the account are derived from this key. Each key has a |
||||
version prefix that identifies the chain and derivation scheme being used. At |
||||
the time of writing, `lnd` supports the following versions: |
||||
* `xpub/tpub`: The commonly used version prefix originally intended for |
||||
BIP-0032 mainnet/testnet extended keys. Since `lnd` does not support |
||||
BIP-0032 extended keys, this version serves as a catch-all for the other |
||||
versions. |
||||
* `ypub/upub`: The version prefix for BIP-0049 mainnet/testnet extended keys. |
||||
* `zpub/vpub`: The version prefix for BIP-0084 mainnet/testnet extended keys. |
||||
* `address_type`: The type of addresses the account can derive. There are three |
||||
supported address types: |
||||
* `WITNESS_PUBKEY_HASH`: The standard derivation scheme for BIP-0084 with |
||||
P2WKH for external and change addresses. |
||||
* `NESTED_WITNESS_PUBKEY_HASH`: The standard derivation scheme for BIP-0049 |
||||
with P2WKH for external and change addresses. |
||||
* `HYBRID_NESTED_WITNESS_PUBKEY_HASH` A custom derivation scheme for BIP-0049 |
||||
used by `lnd` where NP2WKH is used for external addresses and P2WKH for |
||||
change addresses. |
||||
* `master_key_fingerprint`: The 4 byte fingerprint of the master key |
||||
corresponding to the account. This is usually required by hardware |
||||
wallet/external signers to identify the proper signing key. |
||||
* `derivation_path`: The BIP-0044 derivation path used on the master key to |
||||
obtain the account key. |
||||
* `external_key_count`: The number of external addresses generated. |
||||
* `internal_key_count`: The number of change addresses generated. |
||||
* `watch_only`: Whether the wallet has private key information for the account. |
||||
This is always true for `lnd`'s default wallet accounts. |
||||
|
||||
# Key Import |
||||
|
||||
An existing limitation to the key import APIs is that events (deposits/spends) |
||||
for imported keys, including those derived from an imported account, will only |
||||
be detected by lnd if they happen after the import. Rescans to detect past |
||||
events are currently not supported, but will come at a later time. |
||||
|
||||
## Account Key Import |
||||
|
||||
The `WalletKit.ImportAccount` RPC and `lncli wallet accounts import` command can |
||||
be used to import an account. At the time of writing, importing an account has |
||||
the following request parameters: |
||||
|
||||
* `name` (required): A name to identify the imported account with. |
||||
* `extended_public_key` (required): A public key that corresponds to a wallet account |
||||
represented as an extended key. It must conform to a derivation path of the |
||||
form `m/purpose'/coin_type'/account'`. |
||||
* `master_key_fingerprint` (optional): The fingerprint of the root key (also |
||||
known as the key with derivation path m/) from which the account public key |
||||
was derived from. This may be required by some hardware wallets for proper |
||||
identification and signing. |
||||
* `address_type` (optional): An address type is only required when the extended |
||||
account public key has a legacy version (xpub, tpub, etc.), such that the |
||||
wallet cannot detect what address scheme it belongs to. |
||||
* `dry_run` (optional): Whether a dry run should be attempted when importing the |
||||
account. This serves as a way to confirm whether the account is being imported |
||||
correctly by returning the first N addresses for the external and internal |
||||
branches of the account. If these addresses match as expected, then it should |
||||
be safe to import the account as is. |
||||
|
||||
For the sake of simplicity, we'll present an example with two `lnd` nodes Alice |
||||
and Bob, where Alice acts as a signer _only_, and Bob manages Alice's on-chain |
||||
BIP-0084 account by crafting transactions and watching/spending addresses. Since |
||||
Alice will only act as a signer, we'll want to import her BIP-0084 account into |
||||
Bob's node, which will require knowledge of Alice's extended public key. |
||||
|
||||
Alice's BIP-0084 extended public key can be obtained as follows. |
||||
|
||||
```shell |
||||
$ lncli-alice wallet accounts list --name=default --address_type=p2wkh |
||||
{ |
||||
"accounts": [ |
||||
{ |
||||
"name": "default", |
||||
"address_type": "WITNESS_PUBKEY_HASH", |
||||
"extended_public_key": "vpub5Z9beF6NYCrHeDmKC38tM3xXMDFFSARa9sdHRPChEMGqtxiELfZB8hm6FwBpBvfPpX2HGG8edYVV9Wupe43PEJJhhfnz1egtQNNaDXyYExn", |
||||
"master_key_fingerprint": null, |
||||
"derivation_path": "m/84'/0'/0'", |
||||
"external_key_count": 0, |
||||
"internal_key_count": 0, |
||||
"watch_only": false |
||||
} |
||||
] |
||||
} |
||||
``` |
||||
|
||||
Bob can then import the account with the following command: |
||||
|
||||
```shell |
||||
$ lncli-bob wallet accounts import vpub5Z9beF6NYCrHeDmKC38tM3xXMDFFSARa9sdHRPChEMGqtxiELfZB8hm6FwBpBvfPpX2HGG8edYVV9Wupe43PEJJhhfnz1egtQNNaDXyYExn alice |
||||
``` |
||||
|
||||
Before Bob imports the account, they may want to confirm the account is being |
||||
imported using the correct derivation scheme. This can be done with the dry run |
||||
request parameter. When a dry run is done, the response will include the usual |
||||
account details, as well as the first 5 external and change addresses, which can |
||||
be used to confirm they match with what the account owner expects. |
||||
|
||||
```shell |
||||
$ lncli-bob wallet accounts import vpub5Z9beF6NYCrHeDmKC38tM3xXMDFFSARa9sdHRPChEMGqtxiELfZB8hm6FwBpBvfPpX2HGG8edYVV9Wupe43PEJJhhfnz1egtQNNaDXyYExn alice --dry_run |
||||
{ |
||||
"account": { |
||||
"name": "alice", |
||||
"address_type": "WITNESS_PUBKEY_HASH", |
||||
"extended_public_key": "vpub5Z9beF6NYCrHeDmKC38tM3xXMDFFSARa9sdHRPChEMGqtxiELfZB8hm6FwBpBvfPpX2HGG8edYVV9Wupe43PEJJhhfnz1egtQNNaDXyYExn", |
||||
"master_key_fingerprint": null, |
||||
"derivation_path": "m/84'/0'/0'", |
||||
"external_key_count": 0, |
||||
"internal_key_count": 0, |
||||
"watch_only": true |
||||
}, |
||||
"dry_run_external_addrs": [ |
||||
"bcrt1q8zdjz2q92eh7jw9ah3upf2u9553226gq79el5l", |
||||
"bcrt1qmx2m4ngd2el0rmmcu0mz453yzzl3aq9mag0l79", |
||||
"bcrt1q904yve7yvt2t3v0s5r7rueweh4jjr3enfgam8w", |
||||
"bcrt1qa7k20jwfvsep8x0dx4jfu9xm0tlwaa8wrrgl77", |
||||
"bcrt1qzypxx35cfsl24mslqextetuc5m8vvadlqp20d8" |
||||
], |
||||
"dry_run_internal_addrs": [ |
||||
"bcrt1qlstwh8ecy7szfw7k6rllc4ajkg6922xjwj6a23", |
||||
"bcrt1qdrz9glz4ld7uyxwv3jz2anx4k9pe3zm86hpy9g", |
||||
"bcrt1qfdu6tfhs85q20tf48nhtx0kjgr0t2j25apm90t", |
||||
"bcrt1qkmysm9wlnhyyc4uhfaxyafj6q3e3ujcnh97cqc", |
||||
"bcrt1qw8hhmdg3atfp7dcwjtysq4kcmnh07kjy2rd2ay" |
||||
] |
||||
} |
||||
``` |
||||
|
||||
Once Bob has confirmed the correct account derivation scheme is being used, the |
||||
account can be imported without the dry run parameter. |
||||
|
||||
```shell |
||||
$ lncli-bob wallet accounts import vpub5Z9beF6NYCrHeDmKC38tM3xXMDFFSARa9sdHRPChEMGqtxiELfZB8hm6FwBpBvfPpX2HGG8edYVV9Wupe43PEJJhhfnz1egtQNNaDXyYExn alice |
||||
{ |
||||
"account": { |
||||
"name": "alice", |
||||
"address_type": "WITNESS_PUBKEY_HASH", |
||||
"extended_public_key": "vpub5Z9beF6NYCrHeDmKC38tM3xXMDFFSARa9sdHRPChEMGqtxiELfZB8hm6FwBpBvfPpX2HGG8edYVV9Wupe43PEJJhhfnz1egtQNNaDXyYExn", |
||||
"master_key_fingerprint": null, |
||||
"derivation_path": "m/84'/0'/0'", |
||||
"external_key_count": 0, |
||||
"internal_key_count": 0, |
||||
"watch_only": true |
||||
} |
||||
} |
||||
``` |
||||
|
||||
### Generating Addresses from an Imported Account |
||||
|
||||
External addresses from an imported account can be generated through the |
||||
existing `Lightning.NewAddress` RPC and `lncli newaddress` command, as they now |
||||
take an additional optional parameter to specify which account the address |
||||
should be derived from. |
||||
|
||||
Following the example above, Bob is able to generate an external address for an |
||||
incoming deposit as follows: |
||||
|
||||
```shell |
||||
$ lncli-bob newaddress p2wkh --account=alice |
||||
{ |
||||
"address": "bcrt1q8zdjz2q92eh7jw9ah3upf2u9553226gq79el5l" |
||||
} |
||||
``` |
||||
|
||||
Change addresses cannot be generated on demand, they are generated automatically |
||||
when a transaction is crafted that requires a change output. |
||||
|
||||
### Crafting Transactions through PSBTs from an Imported Account |
||||
|
||||
Assuming a deposit of 1 tBTC was made to the address above |
||||
(`bcrt1q8zdjz2q92eh7jw9ah3upf2u9553226gq79el5l`), Bob should be able to craft a |
||||
transaction spending their new UTXO. Since Bob is unable to sign the transaction |
||||
themselves, they'll use PSBTs to craft the transaction, and provide it to Alice |
||||
to sign. |
||||
|
||||
```shell |
||||
$ lncli-bob wallet psbt fund --account=alice --outputs="{\"bcrt1qpjqr663tylcksysa4u76xvremee9k8af3pqd5h\": 500000}" --sat_per_vbyte=1 |
||||
{ |
||||
"psbt": "cHNidP8BAHECAAAAAYDHzEGcDW4Qf+gVbIgWpG2PVSUY6aZ3xUGk/3Ia/XnJAAAAAAD/////AiChBwAAAAAAFgAUDIA9aisn8WgSHa89ozB53nJbH6lWNf4pAQAAABYAFPwW6584J6Aku9bQ//xXsrI0VSjSAAAAAAABAKgCAAAAAAEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////A1oBAf////8CAPIFKgEAAAAWABQ4myEoBVZv6Ti9vHgUq4WlIqVpAAAAAAAAAAAAJmokqiGp7eL2HD9x0d79P6mZ36NpU3VcaQaJeZlitIvr2DaXToz5ASAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAR8A8gUqAQAAABYAFDibISgFVm/pOL28eBSrhaUipWkAAQMEAQAAACIGArbCQ3C0eTrSeuEokWjN7ty25lSzNxiClZL3tnbmlDG6GAAAAABUAACAAAAAgAAAAIAAAAAAAAAAAAAAAA==", |
||||
"change_output_index": 1, |
||||
"locks": [ |
||||
{ |
||||
"id": "ede19a92ed321a4705f8a1cccc1d4f6182545d4bb4fae08bd5937831b7e38f98", |
||||
"outpoint": "c979fd1a72ffa441c577a6e91825558f6da416886c15e87f106e0d9c41ccc780:0", |
||||
"expiration": 1621632493 |
||||
} |
||||
] |
||||
} |
||||
``` |
||||
|
||||
The PSBT can then be provided to Alice to sign: |
||||
|
||||
```shell |
||||
$ lncli-alice wallet psbt finalize --funded_psbt="cHNidP8BAHECAAAAAYDHzEGcDW4Qf+gVbIgWpG2PVSUY6aZ3xUGk/3Ia/XnJAAAAAAD/////AiChBwAAAAAAFgAUDIA9aisn8WgSHa89ozB53nJbH6lWNf4pAQAAABYAFPwW6584J6Aku9bQ//xXsrI0VSjSAAAAAAABAKgCAAAAAAEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////A1oBAf////8CAPIFKgEAAAAWABQ4myEoBVZv6Ti9vHgUq4WlIqVpAAAAAAAAAAAAJmokqiGp7eL2HD9x0d79P6mZ36NpU3VcaQaJeZlitIvr2DaXToz5ASAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAR8A8gUqAQAAABYAFDibISgFVm/pOL28eBSrhaUipWkAAQMEAQAAACIGArbCQ3C0eTrSeuEokWjN7ty25lSzNxiClZL3tnbmlDG6GAAAAABUAACAAAAAgAAAAIAAAAAAAAAAAAAAAA==" |
||||
{ |
||||
"psbt": "cHNidP8BAHECAAAAAYDHzEGcDW4Qf+gVbIgWpG2PVSUY6aZ3xUGk/3Ia/XnJAAAAAAD/////AiChBwAAAAAAFgAUDIA9aisn8WgSHa89ozB53nJbH6lWNf4pAQAAABYAFPwW6584J6Aku9bQ//xXsrI0VSjSAAAAAAABAKgCAAAAAAEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////A1oBAf////8CAPIFKgEAAAAWABQ4myEoBVZv6Ti9vHgUq4WlIqVpAAAAAAAAAAAAJmokqiGp7eL2HD9x0d79P6mZ36NpU3VcaQaJeZlitIvr2DaXToz5ASAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAR8A8gUqAQAAABYAFDibISgFVm/pOL28eBSrhaUipWkAAQhsAkgwRQIhALZOShGB8ATptNZFQ/R2h+2haZVoyBF7cW+GFp07ZbUNAiBzXYNYd5qS8BLQJDhEzW3VgxFhg9uRYedyhHEK1BVstwEhArbCQ3C0eTrSeuEokWjN7ty25lSzNxiClZL3tnbmlDG6AAAA", |
||||
"final_tx": "0200000000010180c7cc419c0d6e107fe8156c8816a46d8f552518e9a677c541a4ff721afd79c90000000000ffffffff0220a10700000000001600140c803d6a2b27f168121daf3da33079de725b1fa95635fe2901000000160014fc16eb9f3827a024bbd6d0fffc57b2b2345528d202483045022100b64e4a1181f004e9b4d64543f47687eda1699568c8117b716f86169d3b65b50d0220735d8358779a92f012d0243844cd6dd583116183db9161e77284710ad4156cb7012102b6c24370b4793ad27ae1289168cdeedcb6e654b33718829592f7b676e69431ba00000000" |
||||
} |
||||
``` |
Loading…
Reference in new issue