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-103: Onion Routed Direct Messages #499

Open
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

threeseries
Copy link

This NIP should help improve privacy with respect to kind 4 events on nostr.

@threeseries threeseries marked this pull request as draft May 5, 2023 05:43
@threeseries threeseries marked this pull request as ready for review May 5, 2023 13:43
@v0l
Copy link
Member

v0l commented May 5, 2023

Same as #468

@fiatjaf fiatjaf closed this May 5, 2023
@threeseries threeseries mentioned this pull request May 9, 2023
@Giszmo
Copy link
Member

Giszmo commented May 9, 2023

Going by what @threeseries said here, this PR was closed prematurely. I like the idea. No time to look into details now but ... reopening for now. Ping me if I didn't review by tomorrow, please.

@Giszmo Giszmo reopened this May 9, 2023
@threeseries
Copy link
Author

Going by what @threeseries said here, this PR was closed prematurely. I like the idea. No time to look into details now but ... reopening for now. Ping me if I didn't review by tomorrow, please.

Thanks @Giszmo. I went ahead and updated the NIP to clarify that the encrypting key can be one's own.

@Giszmo
Copy link
Member

Giszmo commented May 9, 2023

Ok, read your nip.

Can you re-word it all, avoiding the "another key pair" or "random"? These are things many don't want on nostr as to prevent spam.

I would call it "Onion Routed DMs" and use ephemeral messages where possible.

Allow for more than one hop.

Consider paying the routing bots.

Consider routing bot discoverability.

Routing bot advertisement:

  • kind 14174 for bots to advertise their offering
  • DMs could contain "nuts" for bots along the way. @callebtc any ideas here? bot advertisement could advertise a fee.
  • Clients could filter out bots that were not recently updated
  • Sybil attacks are an issue to be solved later. Bots could commit to staked BTC or whatever. In the beginning, known bots from the community could run the service.

DMs:

  • Using nip-4's encryption is problematic as it does not allow the kind of onion routing you had in mind - the recipient needs to know the sender's pubkey to decrypt the message but as that is ideally not known to the intermediary on the last leg, you can't take that route. But as the recipient has to be ready to receive DMs of this kind, we could signal this readiness by adding an RSA public key to the bot and recipient profile. This key would ideally be deterministically derived from the users nsec.
  • Send onions as ephemeral kind 20174 events.
  • Send the core as non-ephemeral kind 174.
  • As Carol receives the core from Bot, she needs to learn the real origin in the encrypted part, so the content could be an RSA encrypted nip-4 DM but I would avoid it being a valid event as you wouldn't want some clients to store them on relays in the decrypted form.

I like onion routing. I do not like single-hop "onion" routing as it replaces the trusted relay of other proposals with a trusted bot.
I working solution would be more involved than what you currently have but if others think this is interesting, I might co-author something more workable.

@threeseries
Copy link
Author

@Giszmo Interesting, I really like the idea of using something like RSA keys to avoid needing to leak one's own public key at every hop. Do you know offhand how such an RSA key derivation would work based on Schnorr keys?

Regarding the point about the inner event being a valid kind 4 event, I am thinking this may not be much of an issue since the client should never be retransmitting such a message once received and unwrapped (I would consider that a bug or malicious behavior on the part of the client).

@threeseries
Copy link
Author

@Giszmo I've updated the NIP to focus on onion routing and using RSA keys, let me know if you think there are some more changes needed. I left out some of the details on things like bot payment because I wanted to try to keep this simple for now.

@threeseries threeseries changed the title NIP-103: Encrypted direct message envelopes NIP-103: Onion Routed Direct Messages May 11, 2023
103.md Outdated Show resolved Hide resolved
103.md Outdated

# Intermediate hops

Intermediate nodes can be one of two types: always-online bots that exist solely to perform onion-routing, or ordinary users who have opted into forwarding messages for others (this also provides plausible deniability to the users themselves who are participating in forwarding). In the former case it may be desirable to use kind 20174 to make tracing more difficult, however there needs to be a way for bots to signal that they're online to ensure that such a message will be received. Hence it may be useful to have an ephemeral "heartbeat" event for sending these types of signals.
Copy link
Member

Choose a reason for hiding this comment

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

In my proposal I suggested to update a replaceable event regularly. A bot that updated an hour ago is probably online. A heartbeat would have to be every minute or the user firing up his client would have wait a long time before knowing an online bot is online.

You could also probe by sending a message to yourself.

Copy link
Author

Choose a reason for hiding this comment

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

If it's ephemeral I don't see a huge problem with firing one every few minutes. I like the idea of probing as well, and it seems like that could be a useful tool in some situations.

An issue I see with updating a replaceable event is there'd be fuzziness over how to interpret the timestamp, and knowing how far in the past the event truly was. With an ephemeral event you'd know it was just sent because otherwise you wouldn't have received it.

Copy link
Member

Choose a reason for hiding this comment

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

I'm arguing for lower relay load here. Updating the replaceable event is way more expensive than an ephemeral event but the latter you would need once per minute I would say and I consider the replaceable event to be preferable.

There is network, CPU and storage. 60 ephemeral events would be maybe x60 the network, x20 the CPU and x0 the storage. Not sure what will be the bottleneck.

Copy link
Author

Choose a reason for hiding this comment

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

If we prefer replaceable to ephemeral how about reusing kind 0 again? The content could contain a last_updated_at field or something like that which could be used to signal online status (and also just general metadata freshness which seems useful outside of this NIP).


# RSA keys

RSA keys should be derived deterministically from the user's nsec. They should also be advertised in the metadata of a pubkey for any account that can perform onion routing.
Copy link
Member

Choose a reason for hiding this comment

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

I'm afraid you have to be more prescriptive of how this should work. While profiles must expose the public RSA key, clients must know how to independently derive it from the Schnorr key. Some

entropy = sha256(npub+"RSA")
rsa = getRsaKeyPair(entropy)

but you'd have to research this yourself for some real-world code example.

Also you have to prescribe how and where to store the RSA pubkey. In the profile? In a new replaceable event?

Copy link
Author

Choose a reason for hiding this comment

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

This might take me some time as I'm not a cryptographer, but I'll give it a shot. Could PGP also work for this? I'm thinking the easiest way to advertise would probably be to put the public key in a kind 0 metadata event.

However the key derviation works, it'd be nice if it enabled a way of verifying whether an RSA or PGP public key actually corresponded to the user's npub (i.e., the private key was derived from the associated nsec).

Copy link
Member

Choose a reason for hiding this comment

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

Nah, the key derivation is not compatible in a sense that an RSA pub key wouldn't have anything to do with the Schnorr pubkey. The determinism is in my opinion only important, so a user doesn't have to backup yet another key. As the event is signed with the Schnorr key though, you can be certain that the RSA key was approved.

Copy link
Author

@threeseries threeseries May 12, 2023

Choose a reason for hiding this comment

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

Apparently for RSA it's recommended to have 2048 bit keys based on the prime numbers p and q. One way we could derive them deterministically from a nostr secret key would be to (for example) repeatedly hash the key and stretch it into a 2048 bit number. Then split this into two 1024 pieces and keep incrementing until we hit prime numbers for each. Once we have q and q there are a few more steps that could be implemented by any software: https://en.wikipedia.org/wiki/RSA_(cryptosystem).

Here's some Python code for doing the first part, although it is extremely slow due to the primality test:

from math import sqrt
import hashlib


def is_prime(n: int) -> bool:
    if n <= 1:
        return False
    test = 2
    sqrt_n = sqrt(n)
    while test <= sqrt_n:
        if n % test == 0:
            return False
        test += 1
    return True


result = ""
hex_string = "0487a6b310c7bc874c075001e0aaf492d5d1aae1bfa12cae40fb18497a079027"
for _ in range(8):
    result += hex_string
    m = hashlib.sha256()
    m.update(bytearray.fromhex(hex_string))
    m.digest()
    hex_string = m.hexdigest()

p = int(result[:256], 16)
q = int(result[256:], 16)

while not is_prime(p):
    p += 1
while not is_prime(q):
    q += 1

print(f"p: {p}, q: {q}")

Do you think this might work? Again I don't know much about cryptography and would want to have it reviewed by someone who does.

Copy link
Member

Choose a reason for hiding this comment

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

Not a cryptographer. If RSA is done by brute-forcing a prime number in the right range, yes, this could work and should not make things slower than any other tool that would work with RSA. Now if we should store the RSA private key in a nip-4 DM style enrypted event I don't know. That would take away the requirement for it to be deterministic.

threeseries and others added 2 commits May 11, 2023 15:10
Co-authored-by: Leo Wandersleb <leo@leowandersleb.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants