|
| 1 | +# Custodial accounts |
| 2 | + |
| 3 | +This document describes how the "accounts" feature of LiT can be used to create |
| 4 | +custodial user accounts with their own balances on an existing `lnd` node. |
| 5 | + |
| 6 | +## What is an account? |
| 7 | + |
| 8 | +An account in the LiT context is a virtual construct that provides restricted |
| 9 | +access to an existing `lnd`/LiT node. An account has a virtual (off-chain only) |
| 10 | +balance in satoshis and an optional expiration. That allows a node operator to |
| 11 | +give someone else (or some client application, see [Use cases](#use-cases)) |
| 12 | +restricted access to their node with the ability to only spend up to a certain |
| 13 | +amount of the node's channel balance. |
| 14 | + |
| 15 | +NOTE: An account's balance is purely virtual. If an account is created with an |
| 16 | +initial balance higher than the node's actual overall channel balance, that is |
| 17 | +equivalent to fractional reserve banking. Therefore, the user accepting an |
| 18 | +account restricted access enters a trust relationship with the node operator |
| 19 | +that the promised balance of the account is actually spendable. |
| 20 | + |
| 21 | +## How do accounts work? |
| 22 | + |
| 23 | +The accounts systems is made possible thanks to the power of |
| 24 | +[macaroons](https://github.com/lightningnetwork/lnd/blob/master/docs/macaroons.md) |
| 25 | +and the [RPC middleware |
| 26 | +interceptor](https://github.com/lightningnetwork/lnd/blob/master/lnrpc/lightning.proto#L558) |
| 27 | +of `lnd`. |
| 28 | + |
| 29 | +**What does that mean?** |
| 30 | + |
| 31 | +It means a node operator can give another user or client application access to |
| 32 | +their node through the default gRPC interface of `lnd`, which makes this access |
| 33 | +mode fully compatible with any remote (or local) user interfaces or |
| 34 | +apps/browser plugins, as well as any [LNC (Lightning Node |
| 35 | +Connect)](https://github.com/lightninglabs/lightning-node-connect) connection. |
| 36 | + |
| 37 | +What features and balance the restricted user has access to is **solely |
| 38 | +controlled by the macaroon that is given to the user**. So a user that wants to |
| 39 | +get restricted access to a node will receive a macaroon that is bound to an |
| 40 | +account that is defined in LiT. Because of the cryptographic setup of macaroons, |
| 41 | +that restriction cannot be removed from the macaroon by the user without |
| 42 | +invalidating the macaroon itself. Therefore, any user/application using such a |
| 43 | +restricted macaroon will trigger special rules in the RPC middleware interceptor |
| 44 | +mentioned above. See [the features](#features) section below to find out what |
| 45 | +those rules are. |
| 46 | + |
| 47 | +## Features |
| 48 | + |
| 49 | +When an account-restricted macaroon is used, the RPC middleware interceptor |
| 50 | +enforces the following rules on the RPC interface: |
| 51 | + |
| 52 | +* Any payment made by a custodial/restricted user account is deducted from an |
| 53 | + account's virtual balance (the full amount, including off-chain routing fees). |
| 54 | +* If a payment (or the sum of multiple in-flight payments) exceeds the account's |
| 55 | + virtual balance, it is denied. |
| 56 | +* The on-chain balance of any RPC responses such as the `WalletBalance` RPC is |
| 57 | + always shown as `0`. A custodial/restricted user shouldn't be able to see what |
| 58 | + on-chain balance is available to the node operator as an account can only |
| 59 | + spend off-chain balances anyway. |
| 60 | +* The off-chain balance (e.g. the response returned by the `ChannelBalance` RPC) |
| 61 | + always reflects the account's virtual balance and not the node's overall |
| 62 | + channel balance (and any remote balances are always shown as `0`). |
| 63 | +* The list of active/pending/closed channels is always returned empty. The |
| 64 | + custodial/restricted user should not need to care (or even know) about |
| 65 | + channels and their internal workings. |
| 66 | +* The list of payments and invoices is filtered to only return payments/invoices |
| 67 | + created or paid by the account. |
| 68 | +* Invoices created by an account are mapped to that account. If/when such a |
| 69 | + mapped invoice is paid, the amount is credited to that account's virtual |
| 70 | + balance. |
| 71 | + |
| 72 | +## Use cases |
| 73 | + |
| 74 | +The following (definitely non-exhaustive) list of use cases is made possible by |
| 75 | +the accounts system: |
| 76 | + - The "Uncle Jim" model: The tech-savvy person of the family (e.g. "Uncle Jim") |
| 77 | + operates a Lightning node. He manages the liquidity of the node and provides |
| 78 | + the capital for the channels. He can onboard his family members by creating |
| 79 | + an account, locking a macaroon to that account and then scanning a QR code |
| 80 | + with an app like [Zeus](https://github.com/ZeusLN/zeus) on the family |
| 81 | + member's smartphone. |
| 82 | + - The "spend up to a certain amount automatically" model: A web user has a |
| 83 | + browser extension like [Alby](https://getalby.com/) installed and wants to |
| 84 | + allow that extension to pay invoices for paywalls automatically up to a |
| 85 | + certain amount per month. That amount could be enforced by the account so the |
| 86 | + browser extension doesn't have to keep track of its spending actions. And an |
| 87 | + account can be shared between extensions installed in different browsers. |
| 88 | + - The "allowance" model: A parent wants to give their child their allowance in |
| 89 | + Lightning satoshis. They create an account over the allowance amount and top |
| 90 | + up the account each week/month. |
| 91 | + |
| 92 | +## HOWTO |
| 93 | + |
| 94 | +This section describes how an account can be created and used. |
| 95 | + |
| 96 | +### Create the account |
| 97 | + |
| 98 | +The first thing that needs to be done is to create the account with its initial |
| 99 | +balance (and an optional expiry). This **needs to be done by the node |
| 100 | +operator**, meaning access to the `lit.macaroon` is required. |
| 101 | + |
| 102 | +Example: |
| 103 | +```shell |
| 104 | +$ litcli accounts create 50000 --save_to /tmp/accounts.macaroon |
| 105 | + |
| 106 | +{ |
| 107 | + "account": { |
| 108 | + "id": "d64dbc31b28edf66", |
| 109 | + "initial_balance": "50000", |
| 110 | + "current_balance": "50000", |
| 111 | + "last_update": "1652353332", |
| 112 | + "expiration_date": "0" |
| 113 | + }, |
| 114 | + "macaroon": "020103........." |
| 115 | +} |
| 116 | +Account macaroon saved to /tmp/accounts.macaroon |
| 117 | +``` |
| 118 | + |
| 119 | +This created a new account (ID `d64dbc31b28edf66`) with an initial balance of |
| 120 | +50k satoshis and no expiration. A new macaroon was baked that contains the |
| 121 | +correct permissions and is locked to that account. The macaroon file was stored |
| 122 | +under `/tmp/accounts.macaroon` in this example. |
| 123 | + |
| 124 | +### Use the macaroon |
| 125 | + |
| 126 | +This step is done by the user/app that should be given the restricted access. An |
| 127 | +example could be to create a QR code with a tool like |
| 128 | +[`lndconnect`](https://github.com/LN-Zap/lndconnect) that can be scanned by |
| 129 | +mobile apps to connect to the node. Or some browser extensions require the user |
| 130 | +to upload the macaroon to the browser. |
| 131 | + |
| 132 | +**It's absolutely crucial to use the macaroon generated in the previous step |
| 133 | +here** to make sure the restrictions are applied. |
| 134 | + |
| 135 | +The permissions and restrictions of a macaroon can always be inspected by: |
| 136 | +```shell |
| 137 | +$ lncli printmacaroon --macaroon_file /tmp/accounts.macaroon |
| 138 | + |
| 139 | +{ |
| 140 | + "version": 2, |
| 141 | + "location": "lnd", |
| 142 | + "root_key_id": "0", |
| 143 | + "permissions": [ |
| 144 | + "info:read", |
| 145 | + "invoices:read", |
| 146 | + "invoices:write", |
| 147 | + "offchain:read", |
| 148 | + "offchain:write", |
| 149 | + "onchain:read" |
| 150 | + ], |
| 151 | + "caveats": [ |
| 152 | + "lnd-custom account d64dbc31b28edf66" |
| 153 | + ] |
| 154 | +} |
| 155 | +``` |
| 156 | + |
| 157 | +The important part is the `lnd-custom account ...` part in the `caveats` |
| 158 | +section. |
| 159 | + |
| 160 | +Example of using `lncli` to check the account balance (assuming integrated `lnd` |
| 161 | +mode, adjust RPC server/port and TLS cert for remote mode): |
| 162 | +```shell |
| 163 | +$ lncli --macaroonpath=/tmp/accounts.macaroon channelbalance |
| 164 | + |
| 165 | +{ |
| 166 | + "balance": "5000", |
| 167 | + "pending_open_balance": "0", |
| 168 | + "local_balance": { |
| 169 | + "sat": "5000", |
| 170 | + "msat": "5000000" |
| 171 | + }, |
| 172 | + "remote_balance": { |
| 173 | + "sat": "0", |
| 174 | + "msat": "0" |
| 175 | + } |
| 176 | + ... |
| 177 | +} |
| 178 | +``` |
0 commit comments