Skip to content
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

[NIP-47] Add versioning and migrate to NIP-44. #1531

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 131 additions & 21 deletions 47.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ This NIP describes a way for clients to access a remote lightning wallet through

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.

5. The **wallet service** may send encrypted notifications (kind 23196) of wallet events (such as a received payment) to the **client**.
5. The **wallet service** may send encrypted notifications (kind 23197 or 23196) of wallet events (such as a received payment) to the **client**.

## Events

There are four event kinds:
- `NIP-47 info event`: 13194
- `NIP-47 request`: 23194
- `NIP-47 response`: 23195
- `NIP-47 notification event`: 23196
- `NIP-47 notification event`: 23197 (23196 in v0)

### Info Event

Expand All @@ -43,34 +43,69 @@ The content should be a plaintext string with the supported capabilities space-s

If the **wallet service** supports notifications, the info event SHOULD contain a `notifications` tag with the supported notification types space-separated, eg. `payment_received payment_sent`.

It should also contain supported versions as described in the [Versioning](#versioning) section. For example:

```jsonc
{
"kind": 13194,
"tags": [
["v", "1.3 0.0"], // List of supported versions as described in the Versioning section.
["notifications", "payment_received payment_sent"]
// ...
],
"content": "pay_invoice get_balance make_invoice lookup_invoice list_transactions get_info notifications",
// ...
}
```

### Request and Response Events

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.
Optionally, a request can have an `expiration` tag that has a unix timestamp in seconds. If the request is received after this timestamp, it should be ignored.

The content of requests and responses is encrypted with [NIP04](04.md), and is a JSON-RPCish object with a semi-fixed structure:
The content of requests and responses is encrypted with [NIP44](44.md), and is a JSON-RPCish object with a semi-fixed structure.

Request:
```jsonc
**Important note for backwards-compatibility:** v0 of the protocol used [NIP04](04.md). If a **wallet service** or client app does not include the `v` tag in the
`info` or request events, it should be assumed that the connection is using v0 of the protocol and NIP04 should be used for encryption. See the [Versioning](#versioning) section for more information.

Example request:
```js
{
"method": "pay_invoice", // method, string
"params": { // params, object
"invoice": "lnbc50n1..." // command-related data
}
"kind" 23194,
"tags": [
["v", "1.2"],
["p", "03..." ] // public key of the wallet service.
// ...
],
"content": nip44_encrypt({
"method": "pay_invoice", // method, string
"params": { // params, object
"invoice": "lnbc50n1..." // command-related data
}
}),
}
```

Response:
```jsonc
Example response:
```js
{
"result_type": "pay_invoice", //indicates the structure of the result field
"error": { //object, non-null in case of error
"code": "UNAUTHORIZED", //string error code, see below
"message": "human readable error message"
},
"result": { // result, object. null in case of error.
"preimage": "0123456789abcdef..." // command-related data
}
"kind" 23195,
"tags": [
["p", "03..." ] // public key of the requesting client app
["e", "1234"] // id of the request event this is responding to
// ...
],
"content": nip44_encrypt({
"result_type": "pay_invoice", //indicates the structure of the result field
"error": { //object, non-null in case of error
"code": "UNAUTHORIZED", //string error code, see below
"message": "human readable error message"
},
"result": { // result, object. null in case of error.
"preimage": "0123456789abcdef..." // command-related data
}
})
// ...
}
```

Expand All @@ -80,9 +115,9 @@ If the command was successful, the `error` field must be null.

### Notification Events

The notification event SHOULD contain one `p` tag, the public key of the **user**.
The notification event is a kind 23197 event (23196 in v0) SHOULD contain one `p` tag, the public key of the **user**.

The content of notifications 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 content of notifications is encrypted with [NIP44](https://github.com/nostr-protocol/nips/blob/master/44.md) (or NIP-04 in v0), and is a JSON-RPCish object with a semi-fixed structure:

```jsonc
{
Expand All @@ -93,6 +128,7 @@ The content of notifications is encrypted with [NIP04](https://github.com/nostr-
}
```

_Note on backwards-compatibility:_ If a **wallet service** supports both v0 (or no version tag) and v1+ of the protocol, it should publish both notification events for each notification - kind 23196 encrypted with NIP-04 for v0, and kind 23197 encrypted with NIP-44 for v1. It is up to the **client** to decide which event to listen to based on its supported version and declared supported versions of the **wallet service** in the `info` event.

### Error codes
- `RATE_LIMITED`: The client is sending commands too fast. It should retry in a few seconds.
Expand All @@ -102,6 +138,7 @@ The content of notifications is encrypted with [NIP04](https://github.com/nostr-
- `RESTRICTED`: This public key is not allowed to do this operation.
- `UNAUTHORIZED`: This public key has no wallet connected.
- `INTERNAL`: An internal error.
- `UNSUPPORTED_VERSION`: The version of the request is not supported by the wallet service.
- `OTHER`: Other error.

## Nostr Wallet Connect URI
Expand Down Expand Up @@ -488,6 +525,78 @@ Notification:
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.

## Versioning

Versioning for NIP-47 allows the protocol to evolve and even make breaking changes while maintaining backwards-compatibility where needed. Versions are represented as `<major>.<minor>` (e.g. 1.3).
Major version bumps imply breaking changes that are incompatible with the previous major version. Minor version bumps, however, only include non-breaking feature additions or improvements which
can maintain full compatibility with the previous minor version.

**Note:** Version 0.0 or absence of a version specification implies the version of NIP-47 prior to adding the versioning system. This is the protocol as defined at commit hash `32b9f3f`.

Versioning works as follows.

1. The **wallet service** includes a `v` tag in the `info` event. This tag contains a space-separated list of versions that the **wallet service** supports.
2. The **client application** includes a `v` tag in each request event. This tag contains the highest version that the **client application** supports and is compatible with the **wallet service**.

### Info event

First, the **wallet service** adds a `v` tag to its `info` event containing a list of versions it supports. This list should be a space-separated list in decreasing order with the format
`<major>.<minor>`. There should be one entry in the list for each major version supported by the wallet, where the minor version is the highest minor version for that major version. For example,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about patch versions? I think clients would also be interested in knowing if the service has the latest patches.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My take was the same as @bumi mentioned with regards to patch versions. If they don't really change compatibility, and versions don't bump often, I think patch versions will add more complexity then benefit.

if a wallet service supports version 1.0-1.3 and version 0.0, their `v` tag in the `info` event might look something like:

```jsonc
{
"kind": 13194,
"tags": [
["v", "1.3 0.0"], // List of supported versions as described in the Versioning section.
// ...
],
"content": "pay_invoice get_balance make_invoice lookup_invoice list_transactions get_info",
// ...
}
```

When a **client application** establishes a connection, it should read the info event and look for the v tag.

**Absence of this tag implies that the wallet only supports version 0.**

If the `v` tag is present, the **client application** will choose the highest version supported by both itself, and the **wallet service**. Note that minor version bumps are non-breaking. For example,
if the client application supports a highest version of 1.2, and the wallet published the info event shown above, it would select version 1.2 since the wallet’s support for v1.3 implies support for v1.2.

### Request events

When a **client application** sends a request event, it should include a `v` tag with the version it is using. This tag should contain the highest version that the **client application** supports
and is compatible with the **wallet service**. The version MUST be supported by the **wallet service** as indicated by the info event. For example, if the client application supports version 1.2,
the request event might look like:

```jsonc
{
"kind": 23194,
"tags": [
["v", "1.2"], // Version tag
// ...
],
// ...
}
```

If the **wallet service** does not support the specified version, it will return an `UNSUPPORTED_VERSION` error. Absence of the `v` tag indicates use of version 0.

### Notification events

As described above in the [Notifications](#notifications) section, if a **wallet service** supports both v0 and v1+ of the protocol, it should publish two notification events for each notification - kind 23196 encrypted with NIP-04 for v0, and kind 23197 encrypted with NIP-44 for v1. If the **wallet service** only supports v1+, it should only publish kind 23197 events.

The **client** should check the `v` tag in the `info` event to determine which version of the protocol the **wallet service** supports, and listen to the appropriate notification event. Clients that support both v0 and v1+ can choose to only listen to 23197 events for simplicity.

### Changelog

- **0.0**:
- Initial version using the protocol as defined at commit hash `32b9f3f`. Uses NIP-04 for encryption and kind 23196 for notifications.
- **1.0**
- Adds versioning system.
- Uses NIP-44 for encryption.
- Moves to kind-23197 notification events, encrypted with NIP-44.

## 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.

Expand All @@ -502,6 +611,7 @@ This NIP does not specify any requirements on the type of relays used. However,
"created_at": 1713883677,
"kind": 13194,
"tags": [
[ "v", "1.3 0.0"],
[
"notifications",
"payment_received payment_sent"
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
| `22242` | Client Authentication | [42](42.md) |
| `23194` | Wallet Request | [47](47.md) |
| `23195` | Wallet Response | [47](47.md) |
| `23196` | NWC Notification (v0) | [47](47.md) |
| `23197` | NWC Notification (v1+) | [47](47.md) |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it also make sense to propose v as a standardized tag (further down in this file)?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually considered this, but decided to avoid the larger discussion on a general version tag or implications in nostr events more broadly than in nwc. That being said, maybe it's worth listing here to be explicit. @vitorpamplona any thoughts on adding the v tag here as a standardized tag?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to stay out of it since NIP-47 seems to be the only place using it. Let's see if other nips decide to adopt first.

| `24133` | Nostr Connect | [46](46.md) |
| `24242` | Blobs stored on mediaservers | [Blossom][blossom] |
| `27235` | HTTP Auth | [98](98.md) |
Expand Down