Uncle Bob's Observations from the field

This commit is contained in:
Robert C. Martin 2023-05-23 11:41:45 -05:00
parent 1c916953c1
commit dffa67287e

95
47.md
View File

@ -14,16 +14,7 @@ This NIP describes a way for clients to access a remote Lightning wallet through
* **client**: Nostr app on any platform that wants to pay Lightning invoices.
* **user**: The person using the **client**, and want's to connect their wallet app to their **client**.
* **wallet service**: Nostr app that typically runs on an always-on computer (eg. in the cloud or on a Raspberry Pi). This app has access to the APIs of the wallets it serves.
## Theory of Operation
1. **Users** who which to use this NIP to send lightning payments to other nostr users must first acquire a special "connection" URI from their NIP-47 compliant wallet application. The wallet application may provide this URI using a QR screen, or a pasteable string, or some other means.
2. The **user** should then copy this URI into their **client(s)** by pasting, or scanning the QR, etc. The **client(s)** should save this URI and use it later whenever the **user** makes a payment. The **client** should then request an `info` (13194) event from the relay(s) specified in the URI. The **wallet service** will have sent that event to those relays earlier, and the relays will hold it as a replaceable event.
3. When the **user** initiates a payment their nostr **client** create a `pay_invoice` request, encrypts it using a token from the URI, and sends it (kind 23194) to the relay(s) specified in the connection URI. The **wallet service** will be listening on those relays and will decrypt the request and then contact the **user's** wallet application to send the payment. The **wallet service** will know how to talk to the wallet application because the connection URI specified relay(s) that have access to the wallet app API.
4. Once the payment is complete the **wallet service** will send an encrypted `response` (kind 23195) to the **user** over the relay(s) in the URI.
* **wallet service**: Nostr app that typically runs on an always-on computer (eg. in the cloud or on a Raspberry Pi). It interacts with the client as a NIP-01 relay. This app has access to the APIs of the wallets it serves.
## Events
@ -32,12 +23,40 @@ There are three event kinds:
- `NIP-47 request`: 23194
- `NIP-47 response`: 23195
The info event should be a replaceable event that is published by the **wallet service** on the relay to indicate which commands it supports. The content should be
a plaintext string with the supported commands, space-seperated, eg. `pay_invoice get_balance`. Only the `pay_invoice` command is described in this NIP, but other commands might be defined in different NIPs.
## Theory of Operation
1. **Users** who which to use this NIP to send lightning payments to other nostr users must first acquire a special "connection" URI from their NIP-47 compliant wallet application. The wallet application may provide this URI using a QR screen, or a pasteable string, or some other means. The format of this URI, as observed from the _alby_ wallet, is:
> * `nostrwalletconnect://<connection-id>?relay=<relay-url>&secret=<secret>&lud16=<lud16>` See: _Nostr Wallet Connect URI_ below.
> * The important data in this URI are the `<connection-id>`, `<relay>`, and `<secret>`. The `<lud16>` is optional and may be used by the **client** to update the `lud16` field of the user's profile.
> * The `<secret>` is a nostr private key. The corresponding public key (`secret-pubkey`) should be caclulated by the **client** for use later.
> * Note: It's not clear to me (UB) why this information was packed into a URI rather than a json string. That seems a bit strange. Perhaps it is because the string is meant to be communicated over a QR code; and perhaps there are character limitations with QR. I don't know.
2. The **user** should copy this URI into their **client(s)** by pasting, or scanning the QR, etc. The **client(s)** should use the information within it whenever the **user** makes a payment. The **client** should subscribe to an `info` (13194) event from the relay(s) specified in the URI. The **wallet service** will have sent that event to those relays earlier, and the relays will hold it as a replaceable event.
> * The suscription should be of the form: `["REQ","<subscription>",{"kinds":[13194],"authors":["<connection-id>"]}]`
> * My observation has been that the relay will provide a single event followed by an `EOSE`.
> * The `content` of the event should contain the string `pay_invoice`, signifying that this connection can be used for making payments.
3. When the **user** initiates a payment their nostr **client** uses the `lud16` or `lud06` fields of the recipient's profile to create an invoice, as in NIP 57. The client then creates a `pay_invoice` request, encrypts the `content`, and sends it (kind 23194) to the relay(s) specified in the connection URI. The **wallet service** will be listening on those relays and will decrypt the request and then contact the **user's** wallet application to send the payment. The **wallet service** will know how to talk to the wallet application because the connection URI specified relay(s) that have access to the wallet app API.
> * Before sending the request, the client should subscribe to recieve the kind:23195 response authored by `<connection-id>`.
> * `["REQ","<subscription>",{"kinds":[23195],"authors":["<connection-id>"]}]`
> * The `pubkey` field of the request should be the `secret-pubkey`.
> * The request should be signed by the `<secret>` private key.
> * The NIP-04 encryption of the `content` should use the `<secret>` private key and the `<connection-id>` from the URI.
4. Once the payment is complete the **wallet service** will send an encrypted `response` (kind 23195) to the **user** over the relay(s) in the URI.
> * Many 23195 events may be recieved. The **client** should wait for the one with a `p` tag equal to the `secret-pubkey`.
> * The `pubkey` field of this event will be the `<connection-id>`.
> * The `content` of this event should be decoded using the `<secret>` private key and the `<connection-id>`.
Both the request and response events SHOULD contain one `p` tag, containing the public key of the **wallet service** if this is a request, and the public key of the **user** if this is a response. The response event SHOULD contain an `e` tag with the id of the request event it is responding to.
## Event Descriptions
The content of requests and responses is encrypted with [NIP04](https://github.com/nostr-protocol/nips/blob/master/04.md), and is a JSON-RPCish object with a semi-fixed structure:
The info event (13194) should be a replaceable event that is published by the **wallet service** on the relay to indicate which commands it supports. The content should be a plaintext string with the supported commands, space-seperated, eg. `pay_invoice get_balance`. Only the `pay_invoice` command is described in this NIP, but other commands might be defined in different NIPs.
* The request (23194) event SHOULD contain one `p` tag, containing the `<connection-id>` from the URI.
* The response (23195) event SHOULD contain one `p` tag containing the `secret-pubkey` derived from the `<secret>` private key.
* The response event MAY contain an `e` tag with the id of the request event it is responding to.
* Note: The alby response does not contain this `e` key. See below.
The content of requests and responses is a JSON-RPCish object with a semi-fixed structure shown below. It is encrypted with [NIP04](https://github.com/nostr-protocol/nips/blob/master/04.md), using the `<secret>` private key and the `<connection-id>` from the URI.
Request:
```jsonc
@ -63,9 +82,9 @@ Response:
}
```
The `result_type` field MUST contain the name of the method that this event is responding to.
The `error` field MUST contain a `message` field with a human readable error message and a `code` field with the error code if the command was not succesful.
If the command was succesful, the `error` field must be null.
* The `result_type` field MUST contain the name of the method that this event is responding to.
* If the request was successful there should be no `error` field.
* If the request was not successful the `error` field MUST contain a `message` field with a human readable error message and a `code` field with the error code.
### Error codes
- `RATE_LIMITED`: The client is sending commands too fast. It should retry in a few seconds.
@ -80,17 +99,18 @@ If the command was succesful, the `error` field must be null.
## Nostr Wallet Connect URI
**client** discovers **wallet service** by scanning a QR code, handling a deeplink or pasting in a URI.
The **wallet service** generates this connection URI with protocol `nostr+walletconnect:` and base path it's hex-encoded `pubkey` with the following query string parameters:
The **wallet service** generates this connection URI with protocol `nostr+walletconnect:` and base path it's hex-encoded `<connection-id>` with the following query string parameters:
- `relay` Required. URL of the relay where the **wallet service** is connected and will be listening for events. May be more than one.
- `relay` Required. (Example: `wss://relay.getalby.com/v1`) URL of the relay where the **wallet service** is connected and will be listening for events. May be more than one.
>Note: It's not clear why there might be more than one relay, nor what procedure clients might use to take advantage of that.
- `secret` Required. 32-byte randomly generated hex encoded string. The **client** MUST use this to sign events and encrypt payloads when communicating with the **wallet service**.
- Authorization does not require passing keys back and forth.
- The user can have different keys for different applications. Keys can be revoked and created at will and have arbitrary constraints (eg. budgets).
- The key is harder to leak since it is not shown to the user and backed up.
- It improves privacy because the user's main key would not be linked to their payments.
- `lud16` Recommended. A lightning address that clients can use to automatically setup the `lud16` field on the user's profile if they have none configured.
- `lud16` Recommended. (Example: `unclebob@getalby.com`) A lightning address that clients can use to automatically setup the `lud16` field on the user's profile if they have none configured.
The **client** should then store this connection and use it when the user wants to perform actions like paying an invoice. Due to this NIP using ephemeral events, it is recommended to pick relays that do not close connections on inactivity to not drop events.
The **client** should use this connection when the user wants to perform actions like paying an invoice. Due to this NIP using ephemeral events, it is recommended to pick relays that do not close connections on inactivity to not drop events.
### Example connection string
```sh
@ -128,10 +148,35 @@ Errors:
## Example pay invoice flow
0. The user scans the QR code generated by the **wallet service** with their **client** application, they follow a `nostr+walletconnect:` deeplink or configure the connection details manually.
1. **client** sends an event to the **wallet service** service with kind `23194`. The content is a `pay_invoice` request. The private key is the secret from the connection string above.
2. **wallet service** verifies that the author's key is authorized to perform the payment, decrypts the payload and sends the payment.
3. **wallet service** responds to the event by sending an event with kind `23195` and content being a response either containing an error message or a preimage.
1. The user scans the QR code generated by the **wallet service** with their **client** application, they follow a `nostr+walletconnect:` deeplink or configure the connection details manually.
1. The **client** sends a subscription request to the **wallet service** relay for the (13194) info event.
1. The **wallet service** relay responds with the info event and an EOSE.
1. **client** sends an event to the **wallet service** service with kind `23194`. The content is a `pay_invoice` request. The private key is the `secret` from the connection string above. The `pubkey` is the `secret-pubkey` derived from the `secret`. The `content` is encrypted using the `secret` and the `<connection-id>`.
* Before sending the request, the **client** should subscribe to recieve the `23195` response from the **wallet service** relay.
1. **wallet service** verifies that the author's key is authorized to perform the payment, decrypts the payload and sends the payment.
1. **wallet service** responds to the event by sending an event with kind `23195` and `p` tag with `secret-pubkey`.
* The encrypted content will be a response containing either an error message or a preimage.
* ignore the preimage if you like.
## Using a dedicated relay
This NIP does not specify any requirements on the type of relays used. However, if the user is using a custodial service it might make sense to use a relay that is hosted by the custodial service. The relay may then enforce authentication to prevent metadata leaks. Not depending on a 3rd party relay would also improve reliability in this case.
## Observations from the field.
The following is an example of the flow of messages between the `more-speech` client and the `getalby.com` wallet connect relay.
<table>
<tr> <th>more-speech-&gt;</th><th>&lt;-alby</th></tr>
<tr><td>["REQ","&lt;subscription&gt;",{"kinds":[13194],"authors":["&lt;connection-id&gt;"]}]</td></tr>
<tr><td></td><td>["EVENT" "&lt;subscription&gt;" {"kind":,13194,"content":,"pay_invoice"}]</td></tr>
<tr><td></td><td>["EOSE" "&lt;subscription&gt;"}]</td></tr>
<tr><td>["REQ","&lt;subscription&gt;",{"kinds":[23195],"authors":["&lt;connection-id&gt;"]}]</td></tr>
<tr><td>["EVENT",{"kind":23194,"pubkey":,"&lt;secret-pubkey&gt;","content":,&lt;encrypted request&gt;}]</td></tr>
<tr><td></td><td>...["EVENT" "&lt;subscription&gt;" {"kind":,23195,"tags":[["p",&lt;???&gt;]],"content":,&lt;???&gt;}]</td></tr>
<tr><td></td><td>["EVENT" "&lt;subscription&gt;" {"kind":,23195,"tags":[["p",&lt;secret-pubkey&gt;]],"content":,&lt;encrypted response&gt;}]</td></tr>
<tr><td></td><td>...["EVENT" "&lt;subscription&gt;" {"kind":,23195,"tags":[["p",&lt;???&gt;]],"content":,&lt;???&gt;}]</td></tr>
<tr><td></td><td>["EOSE" "&lt;subscription&gt;"}]</td></tr>
<tr><td>
</table>