Skip to content

Commit

Permalink
Make NIP-42 fallback a requirement to NIP-43
Browse files Browse the repository at this point in the history
  • Loading branch information
arthurfranca committed Oct 12, 2023
1 parent fa6beee commit 9ba1db1
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 72 deletions.
2 changes: 1 addition & 1 deletion 04.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ let event = {

## Security Warning

This standard does not go anywhere near what is considered the state-of-the-art in encrypted communication between peers, and it leaks metadata in the events, therefore it must not be used for anything you really need to keep secret, and only with relays that use NIP-42 authentication to restrict who can fetch your `kind:4` events.
This standard does not go anywhere near what is considered the state-of-the-art in encrypted communication between peers, and it leaks metadata in the events, therefore it must not be used for anything you really need to keep secret, and only with relays that use NIP-42/43 authentication to restrict who can fetch your `kind:4` events.

## Client Implementation Warning

Expand Down
2 changes: 1 addition & 1 deletion 11.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ subject to the `max_message_length`, if defined.
- `min_pow_difficulty`: new events will require at least this difficulty of PoW,
based on [NIP-13](13.md), or they will be rejected by this server.

- `auth_required`: this relay requires [NIP-42](42.md) authentication
- `auth_required`: this relay requires [NIP-42](42.md)/[NIP-43](43.md) authentication
to happen before a new connection may perform any other action.
Even if set to False, authentication may be required for specific actions.

Expand Down
87 changes: 87 additions & 0 deletions 20.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,91 @@ Command Results

`final` `mandatory` `author:jb55`

<<<<<<< HEAD
Moved to [NIP-01](01.md).
=======
When submitting events to relays, clients currently have no way to know if an event was successfully committed to the database. This NIP introduces the concept of command results which are like NOTICE's except provide more information about if an event was accepted or rejected.

A command result is a JSON object with the following structure that is returned when an event is successfully saved to the database or rejected:

["OK", <event_id>, <true|false>, <message>]

Relays MUST return `true` when the event is a duplicate and has already been saved. The `message` SHOULD start with `duplicate:` in this case.

Relays MUST return `false` when the event was rejected and not saved.

The `message` SHOULD provide additional information as to why the command succeeded or failed.

The `message` SHOULD start with `blocked:` if the pubkey or network address has been blocked, banned, or is not on a whitelist.

The `message` SHOULD start with `invalid:` if the event is invalid or doesn't meet some specific criteria (created_at is too far off, id is wrong, signature is wrong, etc)

The `message` SHOULD start with `pow:` if the event doesn't meet some proof-of-work difficulty. The client MAY consult the relay metadata at this point to retrieve the required posting difficulty.

The `message` SHOULD start with `rate-limited:` if the event was rejected due to rate limiting techniques.

The `message` SHOULD start with `error:` if the event failed to save due to a server issue.

Ephemeral events are not acknowledged with OK responses, unless there is a failure.

If the event or `EVENT` command is malformed and could not be parsed, a NOTICE message SHOULD be used instead of a command result. This NIP only applies to non-malformed EVENT commands.


Examples
--------

Event successfully written to the database:

["OK", "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30", true, ""]

Event successfully written to the database because of a reason:

["OK", "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30", true, "pow: difficulty 25>=24"]

Event blocked due to ip filter

["OK", "b1a649ebe8...", false, "blocked: tor exit nodes not allowed"]

Event blocked due to pubkey ban

["OK", "b1a649ebe8...", false, "blocked: you are banned from posting here"]

Event blocked, pubkey not registered

["OK", "b1a649ebe8...", false, "blocked: please register your pubkey at https://my-expensive-relay.example.com"]

Event rejected, rate limited

["OK", "b1a649ebe8...", false, "rate-limited: slow down there chief"]

Event rejected, `created_at` too far off

["OK", "b1a649ebe8...", false, "invalid: event creation date is too far off from the current time. Is your system clock in sync?"]

Event rejected, insufficient proof-of-work difficulty

["OK", "b1a649ebe8...", false, "pow: difficulty 26 is less than 30"]

Event failed to save,

["OK", "b1a649ebe8...", false, "error: could not connect to the database"]



Client Handling
---------------

`messages` are meant for humans, with `reason:` prefixes so that clients can be slightly more intelligent with what to do with them. For example, with a `rate-limited:` reason the client may not show anything and simply try again with a longer timeout.

For the `pow:` prefix it may query relay metadata to get the updated difficulty requirement and try again in the background.

For the `invalid:` and `blocked:` prefix the client may wish to show these as styled error popups.

The prefixes include a colon so that the message can be cleanly separated from the prefix by taking everything after `:` and trimming it.


Future Extensions
-----------------

This proposal SHOULD be extended to support further commands in the future, such as REQ and AUTH. They are left out of this initial version to keep things simpler.
>>>>>>> 7908484 (Make NIP-42 fallback a requirement to NIP-43)
125 changes: 56 additions & 69 deletions 42.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,98 +4,85 @@ NIP-42
Authentication of clients to relays
-----------------------------------

`draft` `optional` `author:Semisol` `author:fiatjaf` `author:arthurfranca`
`draft` `optional` `author:Semisol` `author:fiatjaf`

This is a `client` to `relay` authentication procedure which is
fast and easy to implement because it's non-interactive.

Authentication happens on connection start, not
requiring message exchange between `client` and `relay`.
This NIP defines a way for clients to authenticate to relays by signing an ephemeral event.

## Motivation

A relay may want to require clients to authenticate to access restricted resources. For example,

- A relay may request payment or other forms of whitelisting to publish events.
It may choose to accept events with any pubkey value as long as they are published from an authenticated user;
- A relay may limit access to `kind: 4` DMs to only the parties involved in the chat exchange,
and for that it may require authentication before clients can query for that kind.
- A relay may limit subscriptions of any kind to paying users or users whitelisted through any other means,
and require authentication.

## How to Authenticate

The `client` must generate a `kind: 22242` ephemeral event with the current time as `created_at`
and the relay url as a `relay` tag.
Then it has to stringify the JSON event and [percent-encode](https://www.rfc-editor.org/rfc/rfc3986#page-12) it.
The resulting string is used as `authorization` query param when connecting to the relay.

### Javascript Example

```js
const relayUrl = 'wss://relay.example.com'
// add id and signature as usual
const jsonEvent = generateNostrEvent({
pubkey: "...",
created_at: Math.floor(Date.now() / 1000),
kind: 22242,
tags: [['relay', relayUrl]],
content: ""
})
const auth = window.encodeURIComponent(JSON.stringify(jsonEvent))
const ws = new WebSocket(`${relayUrl}?authorization=${auth}`)
ws.addEventListener('open', () => console.log('auth accepted'))
ws.addEventListener('close', () => console.log('disconnected') )
```

## Relay Handling

`Relays` that require users to authenticate before performing atleast one action, must authenticate user on connection request.
The authorization query param must be decoded and validated. A valid event must:
- be of `kind` `22242`;
- have `created_at` within a small time window relative to the current date (e.g. 60 seconds);
- have the `relay` tag url value with the same domain name as the relay.
- A relay may request payment or other forms of whitelisting to publish events -- this can naïvely be achieved by limiting publication
to events signed by the whitelisted key, but with this NIP they may choose to accept any events as long as they are published from an
authenticated user;
- A relay may limit access to `kind: 4` DMs to only the parties involved in the chat exchange, and for that it may require authentication
before clients can query for that kind.
- A relay may limit subscriptions of any kind to paying users or users whitelisted through any other means, and require authentication.

If the event is valid, its `pubkey` identifies the authenticated user.
## Definitions

The `relay` may end the socket right away if the user is not authorized to access the `relay`.
This NIP defines a new message, `AUTH`, which relays can send when they support authentication and clients can send to relays when they want
to authenticate. When sent by relays, the message is of the following form:

The `relay` should send a `NOTICE` or `OK` message (as per [NIP-20](20.md)) with a standard prefix `"restricted: "` (readable both by humans and machines)
when blocking authenticated user from accessing some resources.
```
["AUTH", <challenge-string>]
```

For example, if an already authenticated user A requests DMs not sent by him and meant to be read only by user B,
relay should send a `NOTICE` like this one:
And, when sent by clients, of the following form:

```
["NOTICE", "restricted: not authorized to access this user's DMs, please login with NIP-42"]
["AUTH", <signed-event-json>]
```

## Security Measures

The used protocol must be `wss` (WebSocket Secure).
The signed event is an ephemeral event not meant to be published or queried, it must be of `kind: 22242` and it should have at least two tags,
one for the relay URL and one for the challenge string as received from the relay.
Relays MUST exclude `kind: 22242` events from being broadcasted to any client.
`created_at` should be the current time. Example:

```json
{
"id": "...",
"pubkey": "...",
"created_at": 1669695536,
"kind": 22242,
"tags": [
["relay", "wss://relay.example.com/"],
["challenge", "challengestringhere"]
],
"content": "",
"sig": "..."
}
```

The authorization event `id` should be stored by the `relay` for the same above mentioned time window so that
if the same event `id` is used twice, the `relay` should reject the connection immediately and
should also disconnect the user that first used the event to authenticate.
In this case the `relay` may send a `NOTICE` message with `"restricted: "` prefix informing
the first user before disconnecting him, for example:
## Protocol flow

```
["NOTICE", "restricted: your client is misbehaving, authorization event can't be reused."]
```
At any moment the relay may send an `AUTH` message to the client containing a challenge. After receiving that the client may decide to
authenticate itself or not. The challenge is expected to be valid for the duration of the connection or until a next challenge is sent by
the relay.

## Backward Compatibility
The client may send an auth message right before performing an action for which it knows authentication will be required -- for example, right
before requesting `kind: 4` chat messages --, or it may do right on connection start or at some other moment it deems best. The authentication
is expected to last for the duration of the WebSocket connection.

`Relays` may wish to support the legacy flow (it was in effect from 16/jan/2023 to 1/jun/2023) if the `authorization` query param is absent. They should send to connecting `clients`, at any moment, an `AUTH` message containing a `challenge`, as follows:
Upon receiving a message from an unauthenticated user it can't fulfill without authentication, a relay may choose to notify the client. For
that it can use a `NOTICE` or `OK` message with a standard prefix `"restricted: "` that is readable both by humans and machines, for example:

```
["AUTH", <challenge-string>]
["NOTICE", "restricted: we can't serve DMs to unauthenticated users, does your client implement NIP-42?"]
```

`Clients` may then reply with an `AUTH` message containg a `kind` `22242` event, similar to the above mentioned one, **with** the same challenge as a tag, for example:
or it can return an `OK` message noting the reason an event was not written using the same prefix:

```
["AUTH", { "kind": 22242, ..., "tags": [["relay", "wss://relay.example.com/"], ["challenge", "challengestringhere"]] }]
["OK", <event-id>, false, "restricted: we do not accept events from unauthenticated users, please sign up at https://example.com/"]
```

`Relays` should validate the event as mentioned above, additionally checking if the `challenge` match the previously sent one.
## Signed Event Verification

To verify `AUTH` messages, relays must ensure:

- that the `kind` is `22242`;
- that the event `created_at` is close (e.g. within ~10 minutes) of the current time;
- that the `"challenge"` tag matches the challenge sent before;
- that the `"relay"` tag matches the relay URL:
- URL normalization techniques can be applied. For most cases just checking if the domain name is correct should be enough.
107 changes: 107 additions & 0 deletions 43.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
NIP-43
======

Fast Authentication
-------------------

`draft` `optional` `author:arthurfranca`

This is a `client` to `relay` authentication procedure which is
fast and easy to implement because it's non-interactive.

Authentication happens on connection start, not
requiring message exchange between `client` and `relay`.

For `relays` implementing this NIP, it must be offered **in addition to**
NIP-42 to maintain backward compatibility (follow [this section](#backward-compatibility)).

## Motivation

A relay may want to require clients to authenticate to access restricted resources. For example,

- A relay may request payment or other forms of whitelisting to publish events.
It may choose to accept events with any pubkey value as long as they are published from an authenticated user;
- A relay may limit access to `kind: 4` DMs to only the parties involved in the chat exchange,
and for that it may require authentication before clients can query for that kind.
- A relay may limit subscriptions of any kind to paying users or users whitelisted through any other means,
and require authentication.

## How to Authenticate

The `client` must generate a `kind: 22242` ephemeral event with the current time as `created_at`
and the relay url as a `relay` tag.
Then it has to stringify the JSON event and [percent-encode](https://www.rfc-editor.org/rfc/rfc3986#page-12) it.
The resulting string is used as `authorization` query param when connecting to the relay.

### Javascript Example

```js
const relayUrl = 'wss://relay.example.com'
// add id and signature as usual
const jsonEvent = generateNostrEvent({
pubkey: "...",
created_at: Math.floor(Date.now() / 1000),
kind: 22242,
tags: [['relay', relayUrl]],
content: ""
})
const auth = window.encodeURIComponent(JSON.stringify(jsonEvent))
const ws = new WebSocket(`${relayUrl}?authorization=${auth}`)
ws.addEventListener('open', () => console.log('auth accepted'))
ws.addEventListener('close', () => console.log('disconnected') )
```

## Relay Handling

`Relays` that require users to authenticate before performing atleast one action, must authenticate user on connection request.
The authorization query param must be decoded and validated. A valid event must:
- be of `kind` `22242`;
- have `created_at` within a small time window relative to the current date (e.g. 60 seconds);
- have the `relay` tag url value with the same domain name as the relay.

If the event is valid, its `pubkey` identifies the authenticated user.

If the user is not authorized to access the `relay`, instead of rejecting the connection right away, `relay` must fallback to NIP-42 following [this section](#backward-compatibility).

The `relay` should send a `NOTICE` or `OK` message (as per [NIP-20](20.md)) with a standard prefix `"restricted: "` (readable both by humans and machines)
when blocking authenticated user from accessing some resources.

For example, if an already authenticated user A requests DMs not sent by him and meant to be read only by user B,
relay should send a `NOTICE` like this one:

```
["NOTICE", "restricted: not authorized to access this user's DMs, please login with NIP-43 or NIP-42"]
```

## Security Measures

The used protocol must be `wss` (WebSocket Secure).

Although not required, the authorization event `id` should be stored by the `relay` for the same above mentioned time window so that
if the same event `id` is used twice, the `relay` should reject the connection immediately and
should also disconnect the user that first used the event to authenticate.
In this case the `relay` may send a `NOTICE` message with `"restricted: "` prefix informing
the first user before disconnecting him, for example:

```
["NOTICE", "restricted: your client is misbehaving, authorization event can't be reused."]
```

## Backward Compatibility

It is crucial that `relays` offer NIP-42 as a fallback mechanism for `clients` that don't intend implementing NIP-43.

If the `authorization` query param is **absent**, the `relay` should send to connecting `clients`,
at any moment (for example, upon connection establishment), an `AUTH` message containing a `challenge`, in accordance with NIP-42, as follows:

```
["AUTH", <challenge-string>]
```

`Clients` are then expected to reply with an `AUTH` message containg a `kind` `22242` event, similar to the above mentioned one, **with** the same challenge as a tag, for example:

```
["AUTH", { "kind": 22242, ..., "tags": [["relay", "wss://relay.example.com/"], ["challenge", "challengestringhere"]] }]
```

`Relays` should validate the event as mentioned above, additionally checking if the `challenge` match the previously sent one.
2 changes: 1 addition & 1 deletion 65.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ It is suggested that people spread their kind `10002` events to many relays, but

Authors may post events outside of the feed that they wish their followers to follow by posting them to relays outside of those listed in their "Relay List Metadata". For example, an author may want to reply to someone without all of their followers watching.

It is suggested that relays allow any user to write their own kind `10002` event (optionally with NIP-42 authentication to verify it is their own) even if they are not otherwise subscribed to the relay because
It is suggested that relays allow any user to write their own kind `10002` event (optionally with NIP-42/43 authentication to verify it is their own) even if they are not otherwise subscribed to the relay because

- finding where someone posts is rather important
- these events do not have content that needs management
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
- [NIP-39: External Identities in Profiles](39.md)
- [NIP-40: Expiration Timestamp](40.md)
- [NIP-42: Authentication of clients to relays](42.md)
- [NIP-43: Fast Authentication](43.md)
- [NIP-45: Counting results](45.md)
- [NIP-46: Nostr Connect](46.md)
- [NIP-47: Wallet Connect](47.md)
Expand Down

0 comments on commit 9ba1db1

Please sign in to comment.