NIP-46: Nostr Connect 🔌 connect your Nostr app with remote signing devices
Hello frens!
Having to enter your private key on each Nostr web app sucks. Using a browser extension is a great advancement, although most people don't bother to install extensions or do not even use a desktop, just a mobile phone.
I propose Nostr Connect, a protocol to allow apps to connect with remote signing devices through Nostr, to ask for things such as "hey sign this Nostr event" or "hey sign for me this bitcoin transaction" or anything else the remote device is capable to understand.
Demo
https://user-images.githubusercontent.com/3596602/211113925-4ef54996-8de4-46c6-ab25-341118c1b592.mp4
NIP
https://github.com/nostr-connect/nips/blob/nostr-connect/46.md
Key points
- There is no need for relayers & client to understand anything new since we propose to use kind 4 encrypted messages between the app and the signing device.
- All remote signers should implement at least
get_public_keyandsign_eventmethods, but are free to respond to anything else
Implementations
Looks like this is a duplicate of https://github.com/nostr-protocol/nips/pull/85
Looks like this is a duplicate of #85
does not look like a duplicate to me. This is generic signing over nostr messaging. Can be used to sign nostr events and much more. Also uses existing semantic.
Example: I could make e Git plugin that uses this to sign commits, or implement this in a bitcoin wallet to sign transactions
Looks like this is a duplicate of #85
It looks like to me a tiny subset, rather than a duplicate.
On the specific of only signing a nostr event, they achieve the same, but Nostr Connect it's more for bi-directional message passing between an external app and your wallet/signer for potentially anything.
If https://github.com/nostr-protocol/nips/pull/85 gets implemented, it can be used by the sign_event method
This seems to be closely related to https://github.com/nostr-protocol/nips/issues/154. We should find a way to merge both somehow.
May not be possible, but we could at least use the same formats/fields perhaps?
These are actually quite different things I think, see discussion here: https://twitter.com/lukechilds/status/1611904044536152065
Potentially we could share the URI handler though? I'm also not sure that makes sense since these seem like two different actions, but maybe the kind of apps that handle one would also be able to handle tho other so maybe it does make sense.
#154 uses nostrsign:nevent1<base58 encoded event> to ask whichever app has registered a nostrsign handler to sign the provided event. If the event is of the new authentication_challenge kind then it will show some anti phishing UI elements to explain you're logging in to an app and send the signed result to the callback URI specified within the note.
This PR uses nostr://connect?target=<pubkey>&relay=<relay>&metadata={"url": "example.com","name": "Example"} to bootstrap a bi-directional network connection via Nostr DMs to be used for arbitrary communication or signing.
I don't think there is actually much overlap, although they do seem similar at a high level.
Potentially both proposals could use nostrconnect: and depending on if the value is an authentication_challenge note or a connection string the app tries to either sign the note and execute the callback or complete the Nostr Connect DM handshake. That does not seem like a good idea to me personally, it feels like we're overloading a single handler to do to very different things. Also I don't see the benefit to doing two very different things with one handler. I do see the benefit of doing two different things with two handlers, since that allows for one app to handle Nostr Connect and a different app to handle Login with Nostr. Also the nostrsign handler is a very generic handler that just asks the native Nostr signing app to sign the note. It could be useful for other things outside of #154.
Does anyone see any areas of functional overlap that I've missed? @tiero is my understanding of what Nostr Connect is doing accurate?
I agree that reusing the same handler is not a good idea.
Does anyone see any areas of functional overlap that I've missed? @tiero is my understanding of what Nostr Connect is doing accurate?
What you say it's correct, not much overlap indeed in terms of protocols, but as functionality if you have an app doing the connect 99% time is capable (and most likely is willing to) to allow user to login with it.
Therefore would be useful to add this capability to the same library or SDK (ie. "Connect SDK") so it can be abstracted for web devs at the "application level" instead of forcing to merge the two protocols now.
As after-tought: what if the challenge/response authentication becomes a nostr connect standard method like an authenticate RPC? The app sends a challenge, the Signer app sign it so App can later recover it and let you in.
(bear with me, I only skimmed over your PR, so I may say not correct things, but will read it more thoroughly later on. So don't take it as "do my thing, instead of yours" 🙏 )
As after-tought: what if the challenge/response authentication becomes a nostr connect standard method like an authenticate RPC?
I've had some discussions with @fiatjaf where we kinda went through the same thought process. There are some issues with relying on networking through Nostr for auth IMO. They were discussed in #154 (and more indepth privately) here's an excerpt:
One issue you could have with a DM based approach is going through a relay. I really like how my above approach does not rely on Nostr for networking, only for signing, it makes auth much more reliable.
Since Nostr only kinda half guarantees that stuff ends up where it should, it seems not ideal to rely on going through relays for something critical like auth. For example what if I have my client configured so low PoW DMs are ignored. I wouldn't see your DM if you don't add enough PoW. Even if you add PoW what if my client is configured to ~$1 PoW per DM, can you justify paying that to log a user in? Or what if my main relay goes down and you can't find me, I can't authenticate with any services until it comes back online.
Note that I'm not saying it's a bad idea in the context of your proposal. If you have two remote things behind NAT and you want them to talk to each other, I think it can make sense. But if you have someone who is already on your website and you just want a signature from them, I think involving Nostr relays introduces a lot of complexity and fragility without any benefits over a simple HTTP POST.
I wonder if there is some overlap but the other way round. Instead of boostrapping Login with Nostr over Nostr Connect, you bootstrap Nostr Connect over Login with Nostr. Because Login with Nostr is just about proving who you are. Nostr Connect is (I think) proving who you are and then establishing an bidirectional communication channel for RPC.
(bear with me, I only skimmed over your PR, so I may say not correct things, but will read it more thoroughly later on. So don't take it as "do my thing, instead of yours" 🙏 )
Same goes for you! lol
I think involving Nostr relays introduces a lot of complexity and fragility
Agreed . I do still think the two should be seen as "one" single thing to be integrated by "Signer" apps (ie. Damus, Nostrum, Blixt, Marina etc..)
If you have two remote things behind NAT and you want them to talk to each other, I think it can make sense
You got it right! It could be a replacement for API served via tor hidden services (for NAT traversal, not privacy)
Nostr Connect is (I think) proving who you are
Nostr Connect has been designed with web apps (but also CLI, extensions etc..) that do not have any backend at all, so we had little interest in proving who the "Signer" is to a third party, rather than just having a way to use Nostr as transport for comms between your own devices. (The browser creates an ephemeral key, but it's basically always yourself).
I think would make perfect sense for advanced Signer apps to derive a new keypair for each app they connect to (but not forced to), to shield their public identity (although the get_public_key RPC should return the main identity eventually, thinking of chat or social media apps)
I do think that if a web app has a backend and wants you to auth within them, using a dedicated protocol is best here. ie. I can log in with a third-party app using Nostr identity AND I want to connect my different devices so they can communicate, without having the private key leaving the signer mobile app
To wrap up, I do really see a lot of overlaps in terms of high-level functionalities, but really hard to merge the two protocols as is. What about having a "common" SDK? Happy to have you in the nostr-connect org and using it as a common workspace for "all things auth" (just a suggestion here)
Nostr Connect has been designed with web apps (but also CLI, extensions etc..) that do not have any backend at all, so we had little interest in proving who the "Signer" is to a third party, rather than just having a way to use Nostr as transport for comms between your own devices. (The browser creates an ephemeral key, but it's basically always yourself).
Ahh of course, that makes sense, thanks for the clarification.
To wrap up, I do really see a lot of overlaps in terms of high-level functionalities, but really hard to merge the two protocols as is. What about having a "common" SDK? Happy to have you in the nostr-connect org and using it as a common workspace for "all things auth" (just a suggestion here)
Thanks! I'll let you know if I have anything useful to contribute there. In the meantime I'll keep thinking on any ways we can share as much logic as possible between these protocols.
Since the time of everyone is limited here a TL;DR of the current status @Semisol @fiatjaf @Cameri
Changes
Nostr Connect URIhas becomenostrconnect://<pubkey>?relay=<relay>&metadata=<metadata>- mandatory URL dropped in metadata, to give less sense of "certifified" data
- agreement to try to give a common SDK that allows app devs to add both "Login With Nostr" (for apps that has a backend) and Connect two apps (app w/o backend & remote signer)
Pending ACKs:
- Suggesting user to double check in signer the App pubkey with an emoji/number sequence
- Removed the
All others subsequentdelegateRequests will be ACKed automaticallyfrom the Delegate flow
I still think Nostr login and Nostr connect could be the same thing, but I haven't had time to actually think about it and prototype it or even write about it.
A URL like nostrconnect:tlvEncoded(pubkey, relay-url, method, other params), in which method could be "login" or "sign-event" or "connect", and flexible to add more stuff later.
For websites, the relay URL could be the website's own fake relay that would only support talking to a specific client.
The website or app requesting the signature could provide its own metadata through the relay, such that the signer app would be able to show some nice metadata that isn't phishable since it comes from a signed event.
I think providing all the details in the URL directly and making two proposals into one might have a huge benefit in complexity reduction and adoption. I also think the JSON-RPC interface using Nostr events as network sockets is very ugly, but this is just my personal bitterness maybe.
The website or app requesting the signature could provide its own metadata through the relay, such that the signer app would be able to show some nice metadata that isn't phishable since it comes from a signed event
I don't see how "isn't phishable" for app that do not have any backend: the app (usually running on your browser) generates a random keypair, that is only used to receive messages back from the signer app (as long is kept in local storage or browser session). It's more a pubkey of the session, rather than a permanent identity of the web app.
I think providing all the details in the URL directly and making two proposals into one might have a huge benefit in complexity reduction and adoption
Both me and @lukechilds tried hard to fit things together, but I see the very little overlap. Do you think having a common SDK/library would not be enough for adoption? I am really with you on this if at protocol level could be possible, but fail to see how. Login with Nostr is to auth into websites that have a backend and some sort of user data in their DB.
I also think the JSON-RPC interface using Nostr events as network sockets is very ugly
We can really do whatever we want actually, being encrypted payloads sent over between your own devices (I also don't like JSON-RPC per se, thats why I called it JSONRPC-ish)
But in the end, if you want to generalize bi-directional communication that goes beyond of nostr specific stuff (signing bitcoin transactions, making general schnorr signatures, making SSH logins etc..), you will end up with a concept of request-response anyway
- App: "is this message a response to a request I sent to the Signer already?" 👉 you need some sort of ID to bind together requests and responses.
- Signer: "is this a request for a handler the Signer's developer defined for me?" 👉 you need some way to agree on a defined method and params so the handler understands, and since Signers devs are free to implement any methods/API they want, this way is kept open and fixed in stone in this spec.
A URL like nostrconnect:tlvEncoded(pubkey, relay-url, method, other params), in which method could be "login" or "sign-event" or "connect", and flexible to add more stuff later.
mmm but this means for each new "request" the app needs to generate a new URL and user must scan/click again with the signer app? It breaks the very original purpose of "connecting" Signer & App together (connect once, communicate back & forth via Nostr)
One of the methods could be connect, but I imagine there will be cases in which the app will not want this "connect" interface. For these, it's a burden to have to support all the messages passing back and forth for establishing connection and so on.
Or at least that's what I'm thinking, but I didn't think very too deeply.
What do you think?
in which the app
Wait, when you say app you mean the web (most of the times) app (ie. the one that generates the ephemeral keypair) or the Signer app? (ie. holds user's Nostr private key)
Because if you mean the former, there is no connect request being sent from the app to the signer, because is done via scanning QRCode/clicking URI. This for two reasons:
- Signer's pubkey is not public knowledge
- Signer app cannot "spammed" of connect requests unless he manually scans/clicks a "Connect URL" link and starts the dance
After the Signer has knowledge of the App pubkey through the "Connect URL", he sends an "ACK" message, along with his pubkey (it can be a new one derived for the specific app for example for the mean of communicating only).
From that point on the App is "enabled" to send messages (ie. requests) for the methods we are trying to standardize: get_public_key, sign_event, get_relays, etc... which is exactly what NIP07-enabled browser extensions to do
Concept ACK.
I strongly dislike it using kind:4 even though the whole nip isn't even explicit about which kind it's using. Just that it's kind:4 encryption.
I don't see the need for persistence of these events. For every "like" we would have two events, each of which is bigger than the "like" itself, persisted like it was a DM the relays should hopefully delete after all the other events?
Concept ACK.
I strongly dislike it using kind:4 even though the whole nip isn't even explicit about which kind it's using. Just that it's kind:4 encryption.
I don't see the need for persistence of these events. For every "like" we would have two events, each of which is bigger than the "like" itself, persisted like it was a DM the relays should hopefully delete after all the other events?
I ended up chosing 24133 as kind so we make the messages ephemeral. If Signers apps want to offer any kind of activity log they must offer persistency by other means, still not part of the NIP anyway (ie. sending a nip04 DM to themself?)
Do you see something else missing to graduate the NIP to be ready for merge? @fiatjaf @Semisol @Giszmo
ACK LGTM.
The Readme needs editing.
Can we make this so there is JavaScript library that provides a window.nostr object that works exactly like NIP-07?
Can we make this so there is JavaScript library that provides a
window.nostrobject that works exactly like NIP-07?
the current "connect" SDK has these methods indeed https://github.com/nostr-connect/connect#start-making-requests which are very similar to what browser extensions will be injected in visiting a website.
So you may want a library to inject in the window.nostr object using the above methods instead? I guess the advantage is to not have web apps to change code?
If yes, I fail to see how this is a "seamless" integration anyway, for the nature of the machinery needed: those methods relay on an instance/object that has secretKey and relay set in the constructor
ie.
const connect = new Connect({ secretKey: sk, relay: 'wss://nostr.vulpem.com' });
Plus it gives events, which are not defined as semantic in window.nostr afaik.
Also since nostr web apps must show a QRCode/link anyway, they would end-up installing @nostr-connect/connect no? At that point, you are forced to branch in your app logic, between using a browser extension or nostr connect remote signer.
Let me know If I understood correctly your goal
I think nos2x should figure out how to delegate signing to the nostr-connect supporting mobile app.
The Readme needs editing.
done
For those who want to test it out:
iOS TestFligth https://testflight.apple.com/join/JIbjvN2p Android APK https://expo.dev/accounts/tiero/projects/nostrum/builds/79ca2ec4-524e-49de-a7ec-fe8d58090720
Playground Web App to Connect to https://nostr-connect.github.io/connect/
Can we make this so there is JavaScript library that provides a
window.nostrobject that works exactly like NIP-07?
Tracking the issue here. https://github.com/nostr-connect/connect/issues/5
The integration is NOT for free tho, you still need to
- instantiate
Connectand listen for response globally - attach listeners for
connect&disconnectevents, to mutate the UI - Show a Connect URI qrcode/link
After the pairing is done, we can override window.nostr object for mapping to getPublicKey and signEvent
I am working on something similar with encrypted event channels.
Basically you can use a shared key (and tag hint) to establish e2e encrypted channels.
The format I use is key@relay-address for passing around connection strings. You could append a query string as well.
The event channel uses a json-rpc interface and event emitters on both ends to simulate a virtual socket.
From there it is easy to pass rpc commands back and forth. The remote client could publish events directly.
The virtual socket is pretty much invisible and works like a group event emitter, so you could have a VPN setup with multiple devices.
Nostr-Emitter does this out of the box, but a dedicated reference client for desktop would be nice. Maybe an electron app that simply manages a whitelist for keys that can forward events for publishing.
I am working on something similar with encrypted event channels.
Thanks @cmdruid great work! Not clear to me what's your end goal: swapping "NostrRPC" for your "NostrEmitter" or was it more to "let me know"?
I don't like that it doubles the load on the relays by requiring two events -- one to get the signed event and then the new event itself. Relays are already under pressure from the added traffic. It being ephemeral sounds great unless you know that "deleting" records from large databases isn't a true delete in most cases. The db reclaims space that has to be reused (if possible) later, fragmenting the table. I honestly don't know what a better solution would be than passing the request through Nostr itself, I just don't like the idea of doubling load on the relays.