Conversation
|
Thanks for working on this! One of the things we liked about NWA was the ability for our service to use one single pubkey (instead of one per user) and instead rely on the secret in the connection string to identify and link a user's wallet. It looks like this proposal would require using a unique pubkey per user as the pubkey has become the only identifier of the new connection. Is that correct? |
No, the wallet service can use a single wallet service key for all its connections if it wants. The only change here is that the client's secret is created client-side (and the corresponding pubkey is given to the wallet service) rather than the wallet service generating the client's key and requiring the user to copy the secret from the service into the client. In the Nostr flow, the client subscribes to an info event that has a Note: Using a single wallet service key for all connections is simpler but unfortunately it leaks metadata (anyone who knows the wallet service pubkey can filter events by that key to see all activity through that wallet service). For this reason in Alby Hub we now generate unique wallet service keys per connection. |
Just to be clear - I'm talking about our NWC service (not a "wallet" but a "client" or "service"). How could we identify which wallet is associated with which user if our supplied pubkey isn't unique? When we wanted to onboard a user with NWA, we would generate a unique secret to include in the URI and associate this secret with the user on our backend. When we received the 33194 event sent from the new wallet pubkey it included the secret so we could identify which of our users created this connection. The wallet service was still creating a unique pubkey per connection but nostr.wine was not.
Not if your service uses a properly authenticated relay with access control. inbox.nostr.wine supports this by default. Events can be written by anyone tagging our NWC service pubkey but only our key can request and read those events. This setup becomes untenable if we need to manage authentications and queries for one pubkey per user. |
|
Another subtle but important difference from NWA: When we (a client or other non-wallet service) pass a relay parameter in the URI, is the wallet service expected to listen on this relay for requests in perpetuity? With NWA the 33194 event broadcasted to the relay included in the client generated URI included an optional relay field for the wallet service to effectively override which relay would be used to listen for requests. Our service would then connect to the designated wallet relay to verify the presence of a 13194 info event for the corresponding wallet pubkey. Are your wallet services prepared to connect to any number of 3rd party relays? Do they support NIP-42 AUTH? |
Now nostr.wine as the "client" must generate the unique key instead of the wallet service. You just need to associate the key you generate with a particular user. (And only the corresponding public key of this unique key is given to the wallet service. You as the client will keep the secret and use it for NWC communication). Does that make sense?
This is still possible in this proposal. The wallet service can provide its own preferred relay in the Unfortunately from Alby Hub's side, we currently only support 1 relay at a time, which the user can configure via an environment variable. We do not support NIP-42 AUTH. |
Yes, I understand your proposal but this doesn't support one of the main NWA features we used which was the ability to use a single pubkey on the client side.
Will the wallet service always send the 13194 to the relay in the client provided URI? If not, how would the client know where to look to find the 13194? How does that work with a 1 relay at a time restriction? I don't think you should force the wallet service to obey the client relay request but you must send the 13194 to the relay in the URI otherwise the client does not know which relay to use. |
Ah, I see. But I don't think this works because the wallet service uses the pubkey of standard NIP-47 requests to know which app connection it is. Did you try creating two connections with the same pubkey for the same wallet service?
For Alby Hub because it's self hosted and not publicly accessible, we use Alby Go as the user-facing NWA interface which communicates with Alby Hub over nostr to initiate the connection. Therefore, I think Alby Go can listen to the hub's info event and then re-broadcast it to the client-provided relay without Alby Hub having to be connected to it. (Note: we haven't done this yet) |
I'm confused trying to understand what you mean by this. The wallet service knows who we are talking to because we are p-tagging the unique wallet connection pubkey (the one that sent the 13194 info event) with the request. NWA worked perfectly with the setup I'm describing. All we need to do is add an optional secret to the client generated URI and then have the 13194 event include that secret if it was passed. This would allow us to have a similar flow to NWA without the extra event.
If your wallet service can't drop off the 13194 on the relay included in the client provided URI, then this proposal doesn't work at all currently. @asoltys does coinos support this correctly? I think there is some disconnect because everyone always thinks of NWC as nostr clients on one side and wallet services on the other. There is a third participant and that is subscription services like nostr.wine. We have different requirements and goals. We aren't trying to manage a user's wallet, we are just trying to send them payment invoices when they are due. Reducing friction for services like us to implement NWC is important if we ever want it to be used beyond zaps. |
I don't think this is a third participant, nostr.wine is just another client. A client is any type of app or service that wants some access to the user's wallet, even if it's receive-only access. A NWC client is not necessarily a typical nostr client. It can be anything - a PoS machine, a recurring payment service, a wallet interface, a game, ... |
|
Have you read through the old NWA PR and the comments? I would highly recommend you do so because it would help us get on the same page. There is actually a LOT of confusion about what a "client" is in that thread. I feel like Ben did a really good job of explaining why all the fields were necessary and the objectives of it. We also talked about "client" key management and why some services may not want to create one pubkey per connection. We likely won't implement a NWC solution that requires us to store one private key per connection. It's not worth the added complexity on the relay REQ side alone. There are also security gains from not having to store and access potentially thousands of private keys (also mentioned in the NWA discussion). |
We don't allow the client to provide a relay. As the wallet provider we always use our own relay and inform the client of it in a postmessage that gets sent back when the user authorizes the connection. I agree using an AUTH'd relay would be a good idea to not leak events/metadata for the connection. Our relay is public at this time. I'm not very familiar with relay authentication in general yet so open to suggestions on how we could improve there. I'm running strfry with near stock configuration for relay.coinos.io |
ekzyis
left a comment
There was a problem hiding this comment.
Looks good! It's just unfortunate that as-is, a 1-click flow from desktop web to a mobile NWC wallet without a server (like cashu.me) doesn't seem to be possible but I don't know how you want to contact the mobile NWC wallet without communicating some relay to it beforehand.
@ekzyis in this current proposal the flow is that a QR code would be shown on the desktop web that the user would have to scan using cashu.me, and then cashu.me would include its preferred relay in the broadcasted info event to the client's relay. I believe this is completely possible, where do you see the issue? |
We also do not run an AUTH'd relay. I think if AUTH is added it's important to not require that wallet connections be tied to user identity (I guess it can be done by using the standard NIP-47 client and wallet service keys to sign these messages, but isn't in then kind of redundant to force them to sign additional messages?). Our current solution is to use unique keys both for the client and wallet service per connection, and some minor constraints on our relay to ensure events cannot be queried without providing a filter with one of these keys (either querying by author, or a specific |
Yes, I read the comments. I understand right now you can subscribe to a single pubkey and listen to events from all different wallet service keys. This is indeed simpler for your usecase. We run multiple services which subscribe to many keys on relays so have experience with what you mention above. One of our demo apps which does scheduled subscription payments I believe is very similar to the model nostr.wine could use to charge users for relay usage on a monthly basis: https://zapplanner.albylabs.com/ However, I would like to explain the other side of what your suggested change would mean:
@nostr-wine please let me know if I miss something here. |
One of the comments is about why a secret (outside of client generated pubkey) is necessary for the client initiated nostr flow regardless of whether or not the client uses a unique key per connection. Since the wallet service never shares their pubkey with the client app out of band, what happens if multiple 13194 events are posted within a few ms tagging the same client pubkey? Should the client app always assume the first received event is the legitimate one? What if they have the same timestamp? An attacker could monitor for these events (on your unauthenticated NWC relays) and immediately create duplicate 13194s. There is no way for the client app to know with certainty which event is from the wallet service without a shared secret. The p tag is public. Pubkeys are NOT secret and we should not call them secrets.
Not right now - this only worked with the original NWA proposal and Mutiny. Without a functional nostr 1 click flow we are not using NWC at all currently.
This is a lot easier to do on unprotected relays. We would like NWC to work on authenticated relays too. There are already several functional relays that provide this service for nostr DMs (you can learn more about the architecture here: https://docs.nostr.wine/inbox/readme - we even had added 33194 events for NWA). If the service needs to authenticate for every single pubkey it is monitoring and then open individual REQs for each it would be impractical with just a few thousand wallet connections.
Actually it does not need to be unique per connection, just per wallet user. We would never (as a service) want to create more than one active connection at a time with the same wallet user anyway. With that being said, we can't imagine any wallet service would actually want to reuse keys. The privacy issues are a lot more relevant on the wallet side. You wouldn't want a random outside observer to be able to track a single user's wallet interactions. On the flip side, I don't see any issue If everyone can see that nostr.wine is sending NWC events to a bunch of random pubkeys. |
Oh, you are right, I didn't consider the QR code. I guess it can count as a 1-click flow. I was thinking of "1-click" more in terms of "the client opens the wallet" (no context switch), but with the QR code flow, the user has to manually open their wallet. But it's okay, just wanted to mention and as mentioned, I wouldn't know how to improve this anyway. |
I think it makes more sense that we add some basic recommendations for relay protection for wallet service relay runners to prevent against allowing all their notes to be crawled (e.g. how Alby's relay does it). If you wish to use a random public relay sure you are at a very small risk of this attack, but there's almost no incentive to do this attack... |
@ekzyis you could have a native lightning wallet app on your desktop which registers the EDIT: it can also technically be done with browser extensions (maybe the Alby Extension will be able to handle these links at sometime in the future) |
We think this is much more complicated, open ended, and unlikely to be properly enforced. Not all relay implementations support these type of restrictions. Including a secret is simple and only relevant for those updating their implementations to support this new "1-click" flow. We need to move on to other things so we'll step aside from here. If others are happy with it as is don't let our opinions get in the way! |
|
|
||
| - `pubkey` Required. The corresponding pubkey of the secret generated by the **client**. | ||
|
|
||
| The **wallet service** MAY ignore all the below optional parameters. |
There was a problem hiding this comment.
I think it'd be good to reference an optional NIP-68 client ID here for some basic phishing protection.
There was a problem hiding this comment.
In the long term I think it's a good idea but I am unsure if it will be adopted in the short-mid term due to much more additional work on all sides (wallet service having to fetch the event and calculate WoT, client must handle a redirect uri, developer required to publish this event).
Should it be a NIP-19 nevent so that it also contains the relay? Do you have suggestions on the field name?
|
|
||
| The client MAY generate its own secret and co-ordinate only public keys with the wallet service so that secrets are never exposed. | ||
|
|
||
| ### HTTP Confirmation Page |
There was a problem hiding this comment.
I'm really glad we're talking about standardizing this HTTP-based flow. The experience you guys have had with alby for a while is a much better UX than the copy-paste flow IMO and works especially nicely for custodial wallets.
My main worry here is that from a security standpoint, this gets very close to OAuth, but without the guarantees that OAuth has around phishing prevention, csrf, token rotation, etc. OAuth is really battle-tested and while it can be painful, trying to re-implement OAuth is a very common security foot-gun. When building out UMA Auth we spent quite a while in security review with 3P security experts and they insisted on simply using OAuth for the connection handshake when there's a wallet available over HTTP.
I do quite like that this method never has the secret leave the client application. I'd initially tried to hold that property with UMA Auth as well, but it was much trickier to get key rotation correct that way. I still think it might be doable, but doing without user interaction is awkward. Eventually I resigned myself to the idea of the wallet being the source of truth on key state, etc.
For reference:
There was a problem hiding this comment.
These are all valid points.
I think the simplicity of NWC is a big reason it has gained adoption and we should not take this lightly. I think the implications of OAuth also push hosted wallets further from self-hosted ones, which currently in this proposal I have tried to keep as aligned as possible.
@asoltys I would be interested to hear your perspective.
| - `isolated` Optional. The makes an isolated app connection / sub-wallet with its own balance and only access to its own transaction list. e.g. `&isolated=true`. | ||
| - `metadata` Optional. Url encoded, JSON-serialized metadata that describes the app connection. | ||
|
|
||
| The **user** MUST be presented with a confirmation page to be able to review and approve or decline the connection. |
There was a problem hiding this comment.
Any thoughts on how this happens? For UMA auth, we use the user's UMA (or lightning address) fetch a configuration document similar to the OIDC discovery doc. From there, we just read the authorization_endpoint field to decide what url to open. It's really similar to how oauth works in this regard and allows the user to just type a lightning address to open the right endpoint (or detect that this method won't work).
See https://docs.uma.me/uma-auth/uma-auth-client/client-manual-implementation#oauth-20-exchange
There was a problem hiding this comment.
Our current solution is to have a list of wallets to pick from (e.g. Bitcoin Connect has a list of wallets it supports and knows what flows to use, and in the http case what url to use).
I think there are pros and cons of both approaches though.
|
Are we doing this? Can we clear all debates over this and move to either implement/merge or close it? |
|
@vitorpamplona currently:
We hope to see more adoption but I think it's a good start. |
|
Thanks for all of this. I've reviewed this with a goal of implementing this for https://rizful.com/ I understand how this works and how we could modify Rizful to incorporate this. But: The "stakes are high" here -- a flaw in this flow, which led to the loss of user funds, could tarnish the reputation of NWC going forward. My reservation is the same as @jklein24 .... Oauth is complicated, and I don't personally understand it very well.... I think before implementation, we need an Oauth security expert to review this. Specifically I look at this sample url, used to make a new connection.... ... And my grug-brain says "This is too simple. Oauth is like 10x more complicated than this. Oauth must be so much more complicated for a reason." I know Block has a lot of security experts, I wonder if they would lend us one to take a look at this? |
|
I have a vague idea of an attack on this which is not technical but relies on user deception. Currently, if I want to connect Rizful and Primal, I need to proactively copy the NWC code from Rizful, and paste it into Primal. I've got to do a lot of active thinking, and I'm pretty unlikely to have a "fake Primal App" on my phone (or web browser), into which I could be tricked into pasting my NWC string. Looking at this proposal, here, we see:
So this means that the CLIENT is specifying a
So to combat this, rizful.com could refuse to display the ... BUT -- when I think of all time times that I have connected apps on the internet -- like, connecting Google to X, or two banks together, or anything like that.... a central part of the security is that they are GUARANTEEING to me that I am connecting to the app or service that i think I am connecting to.... Now, of course, Nostr doesn't use centralized identity... but shouldn't we somehow verify in the This could be accomplished I think with DNS records? This is like an SSL/TLS thing, where we just need to verify that the request from the client is coming from the same domain that it is claiming to come from? (But... these requests will be coming from apps on people's phones mostly, so this won't work? ) Or is some sort of nostr-native way better? Or am I being paranoid ....and this is not needed at all in everyone's opinion? |
FWIW, this type of phishing attack is exactly the type of thing we were worried about in designing the UMA Auth flow, which is why we just went with a more standard OAuth scheme with dynamic nostr-driven client app registration. See the docs here |
|
Right, it's even more effective in the context of phishing, because then it's like "I got an email from Primal and they want to send me 1000 sats! Sure I'll connect Rizful to Primal!" |
|
Yeah, @jklein24 's NIP-68 proposal is of course much more secure than just a name and icon. I just think it adds a lot of extra complexity that could be done later when we have more apps and users actually using this connection flow. (wallet service having to fetch the event and calculate WoT, client must handle a redirect uri, developer required to publish this event, user must have an adequate WOT of mutual followers who follow this app developer's nostr account to know that that app is trustworthy...). |
|
@rolznz great points. We would like to add the @jklein24 proposed workflow (or something like it, if someone comes up with something more simple... ) at the point that there is a client that supports it. We're nervous about the https version because I think if there is an exploit, the NWC service is likely to take the blame. ALSO... I know this is not NOSTR-native, but I still think a custom DNS record could be a simple solution... isn't this exactly the kind of thing that custom DNS records are used for? (I'm not sure) .... I wonder if usage of a custom DNS record could be added to your https spec..... |
|
|
||
| #### Example HTTP confirmation URL | ||
|
|
||
| `https://example.com/connections/new?pubkey=b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4` |
There was a problem hiding this comment.
Wouldn't it make sense to standardize this endpoint and use a Well-known URI, similar to LNURL?
Afaict, the endpoint for Coinos was hardcoded to /apps/new in Alby and I am not sure how I should have known that without looking into Alby's or Coinos' implementation.
There was a problem hiding this comment.
That sounds good to me.
No you are not, I share your concerns
I think "could be done later" is a flawed assumption. It will be MUCH harder to fix this connection flow later if a severe vulnerability was found "when we have more apps and users actually using this connection flow." I will implement this as-is on SN to create Coinos wallets without leaving SN but it should not be seen as an endorsement of the current spec. I just want to play around with it first and see if/how I can break it. |
|
I think the safest way for a nostr client application to get an NWC code (and lightning address) from a NWC wallet is to require the user to take affirmative action to enter a code into into the nostr client application, and then nostr client application then uses that code to get the secrets. With this in mind, we have created this demo client application which holds a user's hand from Signup --> Get NWC Code & Lightning address. It uses a "token exchange" security model. https://github.com/MegalithicBTC/rizful-integration-demo This demo flow handles:
Our Rizful implementation requires that a user sign up for Rizful with a email/password ... you can see our rationale in the ReadMe - I would not be surprised if many/most nostr developers didn't like this and would rather that user's funds be secured only by their ... But the main point is that a user must take affirmative action to enter the 2FA into their client application, which I think might be better security practice than any "automatic" flow. |
|
Obviously there is a server-side implementation to this flow. It's not in this public repo, but maybe it's obvious how it works (I'm not sure.) One of the key security features is that the 2FA-like code is time-limited, in our case we are limiting it to three minutes, after which it becomes invalid. |
|
@MegalithicBTC it's not 100% automatic as the user should be presented with a confirmation screen to view the app's permissions and either accept or deny the connection. (This is the HTTP flow, but it's also the same with the NWA flow) I guess the 2FA code is a bit better because it can only be exchanged for a NWC secret once. But still I think a big part of this proposal is to avoid the need to copy-paste. |
|
Right. In my mind "the need to copy-paste" is an important security feature, similar to how 2FA codes work... if the user pastes the code into an app or website, then the user is saying "I have identified this app/website as one that I want to give the ability to make NWC payments on my behalf." Then if, two weeks later, the user approaches Rizful and says "my wallet was drained, what happened??" We can always say -- "When you pasted the code we gave you, did you paste it into a app or website that you trust?" But you're definitely correct that it adds friction. |
|
FYI -- This onboarding flow ( https://github.com/MegalithicBTC/rizful-integration-demo ) is now integrated in jumble.social -- you can see what it looks like from the perspective of a user here: https://docs.megalithic.me/using-rizful/use-jumble -- see "The Easy Way". |
I agree with @rolznz here:
Not directly copying the secret but a code with expiration to share them makes it more secure, but it seems to solve more for better security than for better UX. It's still better UX, but not enough. I think there's a way to keep the original flow and still have similar security guarantees. Afaict, we only need to make sure the wallet can verify that the request actually comes from a service so it can provide security guarantees to the user when approving or denying the request (see below for terminology). After talking to @rolznz yesterday, I came to the following idea, maybe you can tell me what is wrong with it. It's inspired by SSO where you first need to register with OAuth providers to get a For cases like Zappy Bird where there is no backend that can keep secrets (single-page apps), a Terminology
Steps
1.1 Registration here means that the service requests a client_id+client_secret that the wallet will associate with the service and the service will associate with the wallet. 1.2. The wallet MUST verify authenticity of request. This can be done via any secure, authenticated channel and must most likely be done manually by a human. Examples for secure, authenticated channels are Signal, nostr DMs etc. A wallet can also provide a simple contact form for this because it is the wallet's responsibility to authenticate requests. 1.3.
2.1. 2.2. Not sure yet which keys are used here, could be nostr, but maybe doesn't have to be 2.3. 2.4. 2.5. Here the service can make sure it doesn’t sign blindly but only if it’s actually a user of the service. This can use any authentication scheme like JWTs. 2.6. 2.7. 2.8. This step makes sure that 2.10. 2.11. 2.12. In this step, the wallet can now be sure the request comes from the service because the service signed the request. 2.13. If approved, Additional context
( I think this actually goes back to what @MegalithicBTC mentioned here at August 20:
I think registering a redirect URL could accomplishy exactly that! The wallet could use DNS records like TXT to verify the authenticity of the registration request. Sorry for the wallet of text. I hope this made sense. Let me know what you think! Is this too complicated? Is this not as secure as I think it is? |
|
@ekzyis if I understand correctly you are basically suggesting something similar to OAuth, right? one downside I see of your version is it relies on a server for the app itself. Most NWC apps today are client-side. I also don't think expecting apps to register with every wallet is scalable. Jeremy's NIP-68 would mean developers only need to register their app once (by posting it to nostr). |
| ```javascript | ||
| window.dispatchEvent(new CustomEvent("nwc:success", { | ||
| detail: { | ||
| relayUrl, |
There was a problem hiding this comment.
relayUrl is not a good name considering there can be multiple relays
No, you can register a redirect URL with the wallet, see this in my comment above:
Yes, it's not great for apps, but it's great for wallets to be able to show their users something they can actually trust. Also, how many wallets are there that support this? So far, it's only Coinos right? Don't you already have to add a new button in your app for each new wallet? In the process of adding the button, you could reach out to the wallet provider to register, no? I agree with you that this is the main downside though. But I think it's worth it for users and wallet providers. I haven't had the time yet to implement a proof of concept of this, but I'm planning to.
Thanks, I didn't know about it, will check it out! |
Yeah, coinos and Alby Cloud currently. If your app manually maintains a list of wallets, yes. But I don't think that's a great solution either. 🤔 Our solution so far for this is that apps would use a package like Bitcoin connect, which maintains a registry of wallets. (and there could be similar libraries built for different platforms/environments). |
|
|
||
| The **user** opens the URL in the **wallet service** by scanning a QR code, handling a deeplink or pasting in a URI, and MUST be presented with a confirmation page. | ||
|
|
||
| If the **user** approves the request, a new connection should be created for the **client**'s public key (specified in the base path of the authorization URI). Once the connection is created, the NIP-47 info event MUST be broadcasted to the relays specified in the authorization URI, and the info event MUST include a `p` tag containing the public key of the **client** this info is for, so that the **client** can discover the public key of the **wallet service**. The `p` tag MAY contain the recommended relay url of the **wallet service**. If provided, the recommended relay url MUST be used. |
There was a problem hiding this comment.
for multiple relay support, is it possible to support multiple recommended relay URLs here?

This PR introduces two "1-click" connection flows for setting up initial NWC connections. Rather than having to copy-paste a connection string, the user is presented with an authorization page which they can approve or decline. The secret is generated locally and never leaves the client.
Both flows are also implemented in Alby JS SDK and Bitcoin Connect.
This can be tested in some apps:
Also implemented by: