From 7061fb0e6fc18dcecc3cbde9610170cefbdea4c1 Mon Sep 17 00:00:00 2001 From: Andrew Camilleri Date: Wed, 1 Mar 2023 15:53:19 +0100 Subject: [PATCH 1/6] LNURL Over Nostr --- XX.md | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 XX.md diff --git a/XX.md b/XX.md new file mode 100644 index 0000000..1a0adc8 --- /dev/null +++ b/XX.md @@ -0,0 +1,82 @@ +LUD-XX: LNURL Over Nostr. +============================================== + +`author: kukks` + +--- + +## Using LNURL over Nostr + +Many opponents of LNURL always point to the DNS requirement of LNURL as its biggest weakness, as it makes wallets require a publicly accessible http endpoint, often resulting in centralzied custodial solutions offering dominanely offering LNURL support. + +This LUD presents an alternative, where neither `SERVICE` nor `WALLET` require even an http server, by utilizing [Nostr](https://github.com/nostr-protocol). + +By utilizing [NIP-04](https://github.com/nostr-protocol/nips/blob/master/04.md)), [NIP-19](https://github.com/nostr-protocol/nips/blob/master/19.md)), [NIP-21](https://github.com/nostr-protocol/nips/blob/master/21.md)), and [NIP-33](https://github.com/nostr-protocol/nips/blob/master/33.md)), we can broadcast lnurl parameters and have its callbacks trigger the respective specification. + + ### Various NIP19 scenarios + + NIP19 offers various note options that can point to public keys (`nprofile`), specific events (`nevent`), or parameters of a specific event (`naddr`). All 3 can be used to encode sufficient data to form a communication line for LNURL LUD-01 to function. We will also prefix with the uri `nostr:` scheme from NIP-21 to stay uri compliant within LNURL. + +#### Nprofile example +When using `nprofile` for LNURL, it is assumed that the pubkey is exclusive for this lnurl usage. + +* Generate a new key for nostr: `b7226fb958fc69906a20ee5029bb15500f92f47136a5a4fff653a1186cc4ef3b` +* Derive its public key: `fce2b7c9aa3019e65e808f47fe8cf99ae465103737f294a0444e6c5b53dee0c7` +* Create nprofile (specifying `wss://r.x.com` as a relay hint) `nprofile1qqs0ec4hex4rqx0xt6qg73l73nue4er9zqmn0u555pzyumzm200wp3cpp4mhxue69uhhytnc9e3k7mgj4um4e` +* Create nip21 uri : `nostr:nprofile1qqs0ec4hex4rqx0xt6qg73l73nue4er9zqmn0u555pzyumzm200wp3cpp4mhxue69uhhytnc9e3k7mgj4um4e` +* Encode it in bech32 as per LUD01 `lnurl1dehhxarj8fh8qun0ve5kcef3w9chxvr9vv6xsetcx3e8z7ps0p6rvut8xuekcdende6k2dr9wguh5utddcc82df4x4c857t4d4ax6v3sxpmhqvmrwpcrgmtg0p6k2d3ew45xs7t5de3njefndvmk6em2x36k6dr9ynmp4m` +* or as a url as per LUD17 `lnurlp:nprofile1qqs0ec4hex4rqx0xt6qg73l73nue4er9zqmn0u555pzyumzm200wp3cpp4mhxue69uhhytnc9e3k7mgj4um4e` + +You can now advertise this LNURL. + +* Wallet scans/loads LNURL +* Wallet connects to the relay specified in nprofile +* Wallet creates a new key for nostr and send a NIP4 to pubkey specified in nprofile, with empty content or a randomly generated string (to discuss) +* Wallet watches for a reply to event id created in previous step from nprofile pubkey +* Service creates a NIP4 event replying to wallet nostr event, with json parameters. In this case +``` +{ + "callback": "nostr:nprofile1qqs0ec4hex4rqx0xt6qg73l73nue4er9zqmn0u555pzyumzm200wp3cpp4mhxue69uhhytnc9e3k7mgj4um4e", + "metadata": "[[\"text/plain\",\"hello world\"]]", + "tag": "payRequest", + "minSendable": 1000, + "maxSendable": 1000000, + "commentAllowed": 200 +} +``` +Please note that the `callback` property is optional in this variant. If specified that next step is addressed to it, else it is addressed to service nostr event itself. +* Wallet detects the event, reads parameters, and generates the query string parameters in the same format as usual, meaning `amount=1010&comment=whatever` +* Wallet creates a new nostr event, with the query string parameters as its content. and replies to either the wallet nostr event, or to the callback nostr note definition. This callback helps prevent metadata leakage of public lnurl usage. +* Service detects event, and send the lnurl response json back in NIP4 as a reply. +* Wallet handles it as per lnurl specs + +#### NEvent example +* Service creates a nostr event, where its content is the lnurl json, with event id being `c0e9e10110659220b25f83961d297194e596d2188943153918663d4904c478c7` +* Services encodes it to nevent, `nevent1qqsvp60pqygxty3qkf0c89sa99cefevk6gvgjsc48yvxv02fqnz833cpp4mhxue69uhhytnc9e3k7mgalulyu` +* Create nip21 uri : `nostr:nevent1qqsvp60pqygxty3qkf0c89sa99cefevk6gvgjsc48yvxv02fqnz833cpp4mhxue69uhhytnc9e3k7mgalulyu` +* Encode to lnurl bech32 or LUD17 +* Wallet scans/loads LNURL +* Wallet connects to the relay specified in nevent +* Wallet creates a new key for nostr +* Wallet scans for event with id from nevent +* Wallet detects the event, reads parameters, and generates the query string parameters in the same format as usual, meaning `amount=1010&comment=whatever` +* Wallet creates a new nostr event, with the query string parameters as its content. and replies to either the wallet nostr event, or to the callback nostr note definition. This callback helps prevent metadata leakage of public lnurl usage. +* Service detects event, and send the lnurl response json back in NIP4 as a reply. +* Wallet handles it as per lnurl specs + +#### NAddr example + +Same as nevent, except that scanning for event requires using kind, author, and `d` identifier. + + +### Npub can also be used using the identical flow of `nprofile`, though no relay can be provided to guide the wallet where to connect to and would be less likely to find the event. + +### Callback property + +As mentioned, the `callback` (or other similar properties found for example in lnurl-auth) property is optional, and if left out, the callback mechanism is to tag the event that gave the parameters. +If however it is included, it could be set to an `nevent`, `nprofile`, `naddr`, `npub`. If the note had a public key, it would tag it in the event, if it had an event, it would tag it. This allows the Service to decouple the lnurl callback events, which works well when the lnurl is of npub or nprofile and reduces possible analysis of usage metrics. + + +### To Discuss +* Replace NIP4 with an ephemeral event? +* Should we reduce the options as many duplicate functionality? To what? From 695d2364d395978122e64b66fb43461211b63431 Mon Sep 17 00:00:00 2001 From: Andrew Camilleri Date: Wed, 1 Mar 2023 15:56:50 +0100 Subject: [PATCH 2/6] Update XX.md --- XX.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XX.md b/XX.md index 1a0adc8..8162da1 100644 --- a/XX.md +++ b/XX.md @@ -66,7 +66,7 @@ Please note that the `callback` property is optional in this variant. If specifi #### NAddr example -Same as nevent, except that scanning for event requires using kind, author, and `d` identifier. +Same as nevent, except that scanning for event requires using kind, author, and `d` identifier. The benefits of using naddr over nevent, is that it allows you to update the parameters without needing to issue a new lnurl ### Npub can also be used using the identical flow of `nprofile`, though no relay can be provided to guide the wallet where to connect to and would be less likely to find the event. From 3d50ddbed0d51fa4f71580c759ae9dc8314b10ae Mon Sep 17 00:00:00 2001 From: Andrew Camilleri Date: Wed, 1 Mar 2023 16:00:21 +0100 Subject: [PATCH 3/6] Update XX.md --- XX.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/XX.md b/XX.md index 8162da1..ee098ea 100644 --- a/XX.md +++ b/XX.md @@ -7,15 +7,15 @@ LUD-XX: LNURL Over Nostr. ## Using LNURL over Nostr -Many opponents of LNURL always point to the DNS requirement of LNURL as its biggest weakness, as it makes wallets require a publicly accessible http endpoint, often resulting in centralzied custodial solutions offering dominanely offering LNURL support. +Many opponents of LNURL always point to the DNS requirement of LNURL as its biggest weakness, as it makes wallets require a publicly accessible HTTP endpoint, often resulting in centralized custodial solutions offering dominantly offering LNURL support. -This LUD presents an alternative, where neither `SERVICE` nor `WALLET` require even an http server, by utilizing [Nostr](https://github.com/nostr-protocol). +This LUD presents an alternative, where neither `SERVICE` nor `WALLET` requires even an HTTP server, using [Nostr](https://github.com/nostr-protocol). By utilizing [NIP-04](https://github.com/nostr-protocol/nips/blob/master/04.md)), [NIP-19](https://github.com/nostr-protocol/nips/blob/master/19.md)), [NIP-21](https://github.com/nostr-protocol/nips/blob/master/21.md)), and [NIP-33](https://github.com/nostr-protocol/nips/blob/master/33.md)), we can broadcast lnurl parameters and have its callbacks trigger the respective specification. ### Various NIP19 scenarios - NIP19 offers various note options that can point to public keys (`nprofile`), specific events (`nevent`), or parameters of a specific event (`naddr`). All 3 can be used to encode sufficient data to form a communication line for LNURL LUD-01 to function. We will also prefix with the uri `nostr:` scheme from NIP-21 to stay uri compliant within LNURL. + NIP19 offers various note options that can point to public keys (`nprofile`), specific events (`nevent`), or parameters of a particular event (`naddr`). All 3 can be used to encode sufficient data to form a communication line for LNURL LUD-01 to function. We will also prefix with the uri `nostr:` scheme from NIP-21 to stay uri compliant within LNURL. #### Nprofile example When using `nprofile` for LNURL, it is assumed that the pubkey is exclusive for this lnurl usage. @@ -61,7 +61,7 @@ Please note that the `callback` property is optional in this variant. If specifi * Wallet scans for event with id from nevent * Wallet detects the event, reads parameters, and generates the query string parameters in the same format as usual, meaning `amount=1010&comment=whatever` * Wallet creates a new nostr event, with the query string parameters as its content. and replies to either the wallet nostr event, or to the callback nostr note definition. This callback helps prevent metadata leakage of public lnurl usage. -* Service detects event, and send the lnurl response json back in NIP4 as a reply. +* Service detects event and sends the lnurl response json back in NIP4 as a reply. * Wallet handles it as per lnurl specs #### NAddr example From 86513e34690c793d1c4e912dfd9eeef3e9138f1d Mon Sep 17 00:00:00 2001 From: Andrew Camilleri Date: Thu, 2 Mar 2023 12:24:30 +0100 Subject: [PATCH 4/6] simplify --- XX.md | 102 ++++++++++++++++++++++++++-------------------------------- 1 file changed, 45 insertions(+), 57 deletions(-) diff --git a/XX.md b/XX.md index ee098ea..215bb91 100644 --- a/XX.md +++ b/XX.md @@ -1,82 +1,70 @@ -LUD-XX: LNURL Over Nostr. -============================================== +LUD-XX: LNURL Over Nostr -`author: kukks` +Author: kukks --- -## Using LNURL over Nostr +## Introduction -Many opponents of LNURL always point to the DNS requirement of LNURL as its biggest weakness, as it makes wallets require a publicly accessible HTTP endpoint, often resulting in centralized custodial solutions offering dominantly offering LNURL support. +LNURL has a requirement for DNS, which some see as a major weakness, as it can lead to centralized solutions. This LUD offers an alternative to LNURL by using [Nostr](https://github.com/nostr-protocol/nostr), which does not require an HTTP server. -This LUD presents an alternative, where neither `SERVICE` nor `WALLET` requires even an HTTP server, using [Nostr](https://github.com/nostr-protocol). +## LNURL over Nostr -By utilizing [NIP-04](https://github.com/nostr-protocol/nips/blob/master/04.md)), [NIP-19](https://github.com/nostr-protocol/nips/blob/master/19.md)), [NIP-21](https://github.com/nostr-protocol/nips/blob/master/21.md)), and [NIP-33](https://github.com/nostr-protocol/nips/blob/master/33.md)), we can broadcast lnurl parameters and have its callbacks trigger the respective specification. +By using Nostr [NIP-04](https://github.com/nostr-protocol/nips/blob/master/04.md), [NIP-19](https://github.com/nostr-protocol/nips/blob/master/19.md), [NIP-21](https://github.com/nostr-protocol/nips/blob/master/21.md), it is possible to broadcast LNURL parameters and trigger the relevant specifications. This can be done without requiring an HTTP server for either the service or wallet. - ### Various NIP19 scenarios - - NIP19 offers various note options that can point to public keys (`nprofile`), specific events (`nevent`), or parameters of a particular event (`naddr`). All 3 can be used to encode sufficient data to form a communication line for LNURL LUD-01 to function. We will also prefix with the uri `nostr:` scheme from NIP-21 to stay uri compliant within LNURL. +### Using NIP-19 for LNURL -#### Nprofile example -When using `nprofile` for LNURL, it is assumed that the pubkey is exclusive for this lnurl usage. +NIP-19 has several options, but for this proposal, we will focus on "nprofile," which includes a public key and relay hints. This is sufficient to establish a communication line for LNURL [LUD-01](01.md) to function. -* Generate a new key for nostr: `b7226fb958fc69906a20ee5029bb15500f92f47136a5a4fff653a1186cc4ef3b` -* Derive its public key: `fce2b7c9aa3019e65e808f47fe8cf99ae465103737f294a0444e6c5b53dee0c7` -* Create nprofile (specifying `wss://r.x.com` as a relay hint) `nprofile1qqs0ec4hex4rqx0xt6qg73l73nue4er9zqmn0u555pzyumzm200wp3cpp4mhxue69uhhytnc9e3k7mgj4um4e` -* Create nip21 uri : `nostr:nprofile1qqs0ec4hex4rqx0xt6qg73l73nue4er9zqmn0u555pzyumzm200wp3cpp4mhxue69uhhytnc9e3k7mgj4um4e` -* Encode it in bech32 as per LUD01 `lnurl1dehhxarj8fh8qun0ve5kcef3w9chxvr9vv6xsetcx3e8z7ps0p6rvut8xuekcdende6k2dr9wguh5utddcc82df4x4c857t4d4ax6v3sxpmhqvmrwpcrgmtg0p6k2d3ew45xs7t5de3njefndvmk6em2x36k6dr9ynmp4m` -* or as a url as per LUD17 `lnurlp:nprofile1qqs0ec4hex4rqx0xt6qg73l73nue4er9zqmn0u555pzyumzm200wp3cpp4mhxue69uhhytnc9e3k7mgj4um4e` +#### Example of Nprofile + +When using nprofile for LNURL, it is assumed that the public key is exclusive to this LNURL usage. Here are the steps to create a nprofile: + +1. Generate a new key for Nostr: `b7226fb958fc69906a20ee5029bb15500f92f47136a5a4fff653a1186cc4ef3b`. +2. Derive its public key: `fce2b7c9aa3019e65e808f47fe8cf99ae465103737f294a0444e6c5b53dee0c7`. +3. Create nprofile specifying `wss://r.x.com` as a relay hint: `nprofile1qqs0ec4hex4rqx0xt6qg73l73nue4er9zqmn0u555pzyumzm200wp3cpp4mhxue69uhhytnc9e3k7mgj4um4e`. +4. Create a NIP-21 URI: `nostr:nprofile1qqs0ec4hex4rqx0xt6qg73l73nue4er9zqmn0u555pzyumzm200wp3cpp4mhxue69uhhytnc9e3k7mgj4um4e`. +5. Encode it in bech32 as per [LUD-01](01.md) `lnurl1dehhxarj8fh8qun0ve5kcef3w9chxvr9vv6xsetcx3e8z7ps0p6rvut8xuekcdende6k2dr9wguh5utddcc82df4x4c857t4d4ax6v3sxpmhqvmrwpcrgmtg0p6k2d3ew45xs7t5de3njefndvmk6em2x36k6dr9ynmp4m` +6. Alternatively, use a URL as per [LUD-17](17.md): `lnurlp:nprofile1qqs0ec4hex4rqx0xt6qg73l73nue4er9zqmn0u555pzyumzm200wp3cpp4mhxue69uhhytnc9e3k7mgj4um4e`. You can now advertise this LNURL. -* Wallet scans/loads LNURL -* Wallet connects to the relay specified in nprofile -* Wallet creates a new key for nostr and send a NIP4 to pubkey specified in nprofile, with empty content or a randomly generated string (to discuss) -* Wallet watches for a reply to event id created in previous step from nprofile pubkey -* Service creates a NIP4 event replying to wallet nostr event, with json parameters. In this case -``` +### The Flow + +1. The wallet scans/loads the LNURL. +2. The wallet connects to the relay specified in nprofile. +3. The wallet creates a new key for Nostr and sends a NIP-4 to the public key specified in nprofile, with empty content or a randomly generated string. +4. The wallet watches for a reply to the event ID created in the previous step from the nprofile public key. +5. The service creates a NIP-4 event replying to the wallet Nostr event, with JSON parameters. For example: +```json { - "callback": "nostr:nprofile1qqs0ec4hex4rqx0xt6qg73l73nue4er9zqmn0u555pzyumzm200wp3cpp4mhxue69uhhytnc9e3k7mgj4um4e", - "metadata": "[[\"text/plain\",\"hello world\"]]", - "tag": "payRequest", - "minSendable": 1000, - "maxSendable": 1000000, - "commentAllowed": 200 +"callback": "nostr:nprofile1qqs0ec4hex4rqx0xt6qg73l73nue4er9zqmn0u555pzyumzm200wp3cpp4mhxue69uhhytnc9e3k7mgj4um4e", +"metadata": "[["text/plain","hello world"]]", +"tag": "payRequest", +"minSendable": 1000, +"maxSendable": 1000000, +"commentAllowed": 200 } ``` -Please note that the `callback` property is optional in this variant. If specified that next step is addressed to it, else it is addressed to service nostr event itself. -* Wallet detects the event, reads parameters, and generates the query string parameters in the same format as usual, meaning `amount=1010&comment=whatever` -* Wallet creates a new nostr event, with the query string parameters as its content. and replies to either the wallet nostr event, or to the callback nostr note definition. This callback helps prevent metadata leakage of public lnurl usage. -* Service detects event, and send the lnurl response json back in NIP4 as a reply. -* Wallet handles it as per lnurl specs +Note that the `callback` property is optional in this variant. If specified, the next step is addressed to it; otherwise, it is addressed to the same Nostr LNURL. +6. The wallet detects the event, reads the parameters, and generates the query string parameters in the same format as usual. For example: `amount=1010&comment=whatever` +7. The wallet creates a new NIP-4 Nostr event with the query string parameters as its content and replies to either the wallet Nostr event or the callback Nostr note definition. This callback helps prevent metadata leakage of public LNURL usage. Tagging the previous event from the service is also optional and helps reduce the metadata leakage. +8. The service detects the event and sends the LNURL response JSON back in a NIP-4 reply. +9. The wallet handles it as per LNURL specs. -#### NEvent example -* Service creates a nostr event, where its content is the lnurl json, with event id being `c0e9e10110659220b25f83961d297194e596d2188943153918663d4904c478c7` -* Services encodes it to nevent, `nevent1qqsvp60pqygxty3qkf0c89sa99cefevk6gvgjsc48yvxv02fqnz833cpp4mhxue69uhhytnc9e3k7mgalulyu` -* Create nip21 uri : `nostr:nevent1qqsvp60pqygxty3qkf0c89sa99cefevk6gvgjsc48yvxv02fqnz833cpp4mhxue69uhhytnc9e3k7mgalulyu` -* Encode to lnurl bech32 or LUD17 -* Wallet scans/loads LNURL -* Wallet connects to the relay specified in nevent -* Wallet creates a new key for nostr -* Wallet scans for event with id from nevent -* Wallet detects the event, reads parameters, and generates the query string parameters in the same format as usual, meaning `amount=1010&comment=whatever` -* Wallet creates a new nostr event, with the query string parameters as its content. and replies to either the wallet nostr event, or to the callback nostr note definition. This callback helps prevent metadata leakage of public lnurl usage. -* Service detects event and sends the lnurl response json back in NIP4 as a reply. -* Wallet handles it as per lnurl specs +### Using Npub -#### NAddr example +Npub can also be used using the same flow as nprofile, but without providing a relay to guide the wallet where to connect. Supporting only one note reduces overall complexity. -Same as nevent, except that scanning for event requires using kind, author, and `d` identifier. The benefits of using naddr over nevent, is that it allows you to update the parameters without needing to issue a new lnurl +### The Callback Property +The `callback` (or other similar properties found in lnurl-auth) property is optional. If left out, the callback property is the original nostr lnurl URI. If included, it could be set to an `nprofile` note. This allows the service to decouple the LNURL callback events, which works well when the LNURL is of npub or nprofile and reduces possible analysis of usage metrics. -### Npub can also be used using the identical flow of `nprofile`, though no relay can be provided to guide the wallet where to connect to and would be less likely to find the event. +### Asynchronous -### Callback property +As Nostr is asynchronous in nature, lnurl protocols also become asynchronous. When an lnurl "provider" is offline, a lnurl "taker" may still send his request. Once the provider is back online, they can query all events relating to the lnurl pubkey that were broadcast since they last processed. -As mentioned, the `callback` (or other similar properties found for example in lnurl-auth) property is optional, and if left out, the callback mechanism is to tag the event that gave the parameters. -If however it is included, it could be set to an `nevent`, `nprofile`, `naddr`, `npub`. If the note had a public key, it would tag it in the event, if it had an event, it would tag it. This allows the Service to decouple the lnurl callback events, which works well when the lnurl is of npub or nprofile and reduces possible analysis of usage metrics. +## To Discuss +Should we replace NIP-4 with an ephemeral event? (this voids the async functionality mentioned above) -### To Discuss -* Replace NIP4 with an ephemeral event? -* Should we reduce the options as many duplicate functionality? To what? From a7ccc5d82c0cc0cd9c1d834994fd6cc0c22f8e2c Mon Sep 17 00:00:00 2001 From: Andrew Camilleri Date: Thu, 2 Mar 2023 12:26:00 +0100 Subject: [PATCH 5/6] Update XX.md --- XX.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XX.md b/XX.md index 215bb91..d779544 100644 --- a/XX.md +++ b/XX.md @@ -39,7 +39,7 @@ You can now advertise this LNURL. ```json { "callback": "nostr:nprofile1qqs0ec4hex4rqx0xt6qg73l73nue4er9zqmn0u555pzyumzm200wp3cpp4mhxue69uhhytnc9e3k7mgj4um4e", -"metadata": "[["text/plain","hello world"]]", +"metadata": "[[\"text/plain\",\"hello world\"]]", "tag": "payRequest", "minSendable": 1000, "maxSendable": 1000000, From 1fdf83fa567874524c8508ecadcb0544dc8761d9 Mon Sep 17 00:00:00 2001 From: Andrew Camilleri Date: Sun, 12 Apr 2026 13:54:09 -0400 Subject: [PATCH 6/6] update LNURL-over-Nostr spec: NIP-17, naddr, security - Replace NIP-04 with NIP-17 (Gift Wrap / NIP-59 / NIP-44) for all DM exchanges - Add naddr (parameterized replaceable event) as primary addressing mode, allowing wallets to fetch LNURL params directly from the relay without a DM round trip to the service - Keep nprofile for session-specific types (login, withdraw with unique k1) - Add LUD-17 scheme parsing rules for nostr: URIs - Add error handling, timeout guidance, and security considerations - Remove "To Discuss" section (NIP-17 resolves ephemeral-vs-async tradeoff) --- XX.md | 152 +++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 113 insertions(+), 39 deletions(-) diff --git a/XX.md b/XX.md index d779544..1e13f82 100644 --- a/XX.md +++ b/XX.md @@ -6,65 +6,139 @@ Author: kukks ## Introduction -LNURL has a requirement for DNS, which some see as a major weakness, as it can lead to centralized solutions. This LUD offers an alternative to LNURL by using [Nostr](https://github.com/nostr-protocol/nostr), which does not require an HTTP server. +LNURL requires DNS and HTTPS, which creates a dependency on centralized infrastructure. This LUD offers an alternative transport for LNURL using [Nostr](https://github.com/nostr-protocol/nostr), removing the need for an HTTP server on either side. -## LNURL over Nostr +## Prerequisites -By using Nostr [NIP-04](https://github.com/nostr-protocol/nips/blob/master/04.md), [NIP-19](https://github.com/nostr-protocol/nips/blob/master/19.md), [NIP-21](https://github.com/nostr-protocol/nips/blob/master/21.md), it is possible to broadcast LNURL parameters and trigger the relevant specifications. This can be done without requiring an HTTP server for either the service or wallet. +This specification uses: +- [NIP-17](https://github.com/nostr-protocol/nips/blob/master/17.md) — Private Direct Messages (Gift Wrap) +- [NIP-19](https://github.com/nostr-protocol/nips/blob/master/19.md) — bech32-encoded entities (`naddr`, `nprofile`) +- [NIP-21](https://github.com/nostr-protocol/nips/blob/master/21.md) — `nostr:` URI scheme +- [NIP-33](https://github.com/nostr-protocol/nips/blob/master/33.md) — Parameterized Replaceable Events +- [NIP-44](https://github.com/nostr-protocol/nips/blob/master/44.md) — Versioned Encryption +- [NIP-59](https://github.com/nostr-protocol/nips/blob/master/59.md) — Gift Wrap -### Using NIP-19 for LNURL +## Addressing -NIP-19 has several options, but for this proposal, we will focus on "nprofile," which includes a public key and relay hints. This is sufficient to establish a communication line for LNURL [LUD-01](01.md) to function. +Two NIP-19 addressing modes are supported. The wallet detects which mode is in use from the NIP-19 prefix in the `nostr:` URI. -#### Example of Nprofile +### `naddr` — Public LNURL Parameters (recommended) -When using nprofile for LNURL, it is assumed that the public key is exclusive to this LNURL usage. Here are the steps to create a nprofile: +The service publishes its LNURL parameters as a **parameterized replaceable event** ([NIP-33](https://github.com/nostr-protocol/nips/blob/master/33.md)). The `naddr` encodes a coordinate (kind + pubkey + `d` tag + relay hints) that always resolves to the latest version of that event. -1. Generate a new key for Nostr: `b7226fb958fc69906a20ee5029bb15500f92f47136a5a4fff653a1186cc4ef3b`. -2. Derive its public key: `fce2b7c9aa3019e65e808f47fe8cf99ae465103737f294a0444e6c5b53dee0c7`. -3. Create nprofile specifying `wss://r.x.com` as a relay hint: `nprofile1qqs0ec4hex4rqx0xt6qg73l73nue4er9zqmn0u555pzyumzm200wp3cpp4mhxue69uhhytnc9e3k7mgj4um4e`. -4. Create a NIP-21 URI: `nostr:nprofile1qqs0ec4hex4rqx0xt6qg73l73nue4er9zqmn0u555pzyumzm200wp3cpp4mhxue69uhhytnc9e3k7mgj4um4e`. -5. Encode it in bech32 as per [LUD-01](01.md) `lnurl1dehhxarj8fh8qun0ve5kcef3w9chxvr9vv6xsetcx3e8z7ps0p6rvut8xuekcdende6k2dr9wguh5utddcc82df4x4c857t4d4ax6v3sxpmhqvmrwpcrgmtg0p6k2d3ew45xs7t5de3njefndvmk6em2x36k6dr9ynmp4m` -6. Alternatively, use a URL as per [LUD-17](17.md): `lnurlp:nprofile1qqs0ec4hex4rqx0xt6qg73l73nue4er9zqmn0u555pzyumzm200wp3cpp4mhxue69uhhytnc9e3k7mgj4um4e`. +This is the recommended mode for LNURL types with static parameters (`payRequest`, `channelRequest`) because: +- The wallet fetches parameters directly from the relay — no round trip to the service. +- The service can update parameters at any time by publishing a replacement event. +- The `naddr` itself never changes. +- The relay serves the event instantly, even if the service is offline. -You can now advertise this LNURL. +#### LNURL Parameter Event -### The Flow +The service publishes a Kind **31120** parameterized replaceable event: -1. The wallet scans/loads the LNURL. -2. The wallet connects to the relay specified in nprofile. -3. The wallet creates a new key for Nostr and sends a NIP-4 to the public key specified in nprofile, with empty content or a randomly generated string. -4. The wallet watches for a reply to the event ID created in the previous step from the nprofile public key. -5. The service creates a NIP-4 event replying to the wallet Nostr event, with JSON parameters. For example: ```json { -"callback": "nostr:nprofile1qqs0ec4hex4rqx0xt6qg73l73nue4er9zqmn0u555pzyumzm200wp3cpp4mhxue69uhhytnc9e3k7mgj4um4e", -"metadata": "[[\"text/plain\",\"hello world\"]]", -"tag": "payRequest", -"minSendable": 1000, -"maxSendable": 1000000, -"commentAllowed": 200 + "kind": 31120, + "pubkey": "", + "created_at": 1234567890, + "tags": [["d", "payRequest"]], + "content": "{\"tag\":\"payRequest\",\"minSendable\":1000,\"maxSendable\":1000000,\"metadata\":\"[[\\\"text/plain\\\",\\\"hello world\\\"]]\",\"commentAllowed\":200}", + "sig": "..." } ``` -Note that the `callback` property is optional in this variant. If specified, the next step is addressed to it; otherwise, it is addressed to the same Nostr LNURL. -6. The wallet detects the event, reads the parameters, and generates the query string parameters in the same format as usual. For example: `amount=1010&comment=whatever` -7. The wallet creates a new NIP-4 Nostr event with the query string parameters as its content and replies to either the wallet Nostr event or the callback Nostr note definition. This callback helps prevent metadata leakage of public LNURL usage. Tagging the previous event from the service is also optional and helps reduce the metadata leakage. -8. The service detects the event and sends the LNURL response JSON back in a NIP-4 reply. -9. The wallet handles it as per LNURL specs. -### Using Npub +The `d` tag MUST match the LNURL tag value (e.g. `payRequest`, `withdrawRequest`, `channelRequest`). The `content` field contains the JSON LNURL response exactly as it would appear over HTTP, with one difference: the `callback` property is optional (see [Callback](#the-callback-property)). -Npub can also be used using the same flow as nprofile, but without providing a relay to guide the wallet where to connect. Supporting only one note reduces overall complexity. +#### Constructing an `naddr` LNURL -### The Callback Property +1. Generate a new Nostr key pair dedicated to this LNURL endpoint. +2. Publish the Kind 31120 event to one or more relays. +3. Construct the `naddr`: kind `31120`, pubkey, `d` tag (e.g. `payRequest`), relay hints. +4. Create a [NIP-21](https://github.com/nostr-protocol/nips/blob/master/21.md) URI: `nostr:`. +5. Encode as bech32 per [LUD-01](01.md) or as a [LUD-17](17.md) scheme URI. -The `callback` (or other similar properties found in lnurl-auth) property is optional. If left out, the callback property is the original nostr lnurl URI. If included, it could be set to an `nprofile` note. This allows the service to decouple the LNURL callback events, which works well when the LNURL is of npub or nprofile and reduces possible analysis of usage metrics. +#### Example -### Asynchronous +Encoded as bech32 LNURL: +``` +lnurl1dp68gurn8ghj7... +``` + +Or as LUD-17 scheme: +``` +lnurlp:naddr1qvzqqqr4gupzp... +``` + +### `nprofile` — Interactive DM Flow + +For LNURL types that require per-session parameters (e.g. `login` with a unique `k1` challenge), the service uses `nprofile` addressing. The wallet must perform a [NIP-17](https://github.com/nostr-protocol/nips/blob/master/17.md) DM exchange to obtain parameters. + +The `nprofile` encodes a pubkey + relay hints. It is assumed that the public key is dedicated to this LNURL endpoint. + +## Flows + +### Direct Fetch (`naddr`) + +1. The wallet scans/loads the LNURL and parses the `naddr`. +2. The wallet connects to a relay from the `naddr` relay hints. +3. The wallet sends a `REQ` for the parameterized replaceable event: + ```json + {"kinds": [31120], "authors": [""], "#d": ["payRequest"]} + ``` +4. The relay returns the event. The wallet parses `content` as LNURL parameters. +5. The wallet constructs the callback query string as usual. For example: `amount=1010&comment=whatever` +6. The wallet creates a fresh ephemeral Nostr key and sends a [NIP-17](https://github.com/nostr-protocol/nips/blob/master/17.md) Gift Wrapped DM (Kind 14 inside Seal inside Gift Wrap) to the service pubkey, with the query string as content. +7. The wallet subscribes to Kind 1059 (Gift Wrap) events addressed to its ephemeral pubkey: + ```json + {"kinds": [1059], "#p": [""]} + ``` +8. The service unwraps the Gift Wrap, processes the request, and sends the LNURL response JSON back as a NIP-17 DM to the wallet's ephemeral key. +9. The wallet unwraps the response and handles it per the relevant LUD specification. + +### Interactive Flow (`nprofile`) + +1. The wallet scans/loads the LNURL and parses the `nprofile`. +2. The wallet connects to a relay from the `nprofile` relay hints. +3. The wallet creates a fresh ephemeral Nostr key and sends a NIP-17 DM to the service pubkey with empty content (or the LNURL `tag` if known from the encoding, e.g. `tag=login`). +4. The wallet subscribes to Kind 1059 (Gift Wrap) events addressed to its ephemeral pubkey: + ```json + {"kinds": [1059], "#p": [""]} + ``` +5. The service unwraps the request, generates session-specific parameters (e.g. a `k1` challenge), and sends them back as a NIP-17 DM. +6. The wallet unwraps the response and reads the LNURL parameters. +7. Subsequent callback steps follow the same NIP-17 exchange as steps 5–9 in the Direct Fetch flow. + +## The Callback Property + +The `callback` property is **optional** in LNURL-over-Nostr responses. If omitted, the wallet sends callback requests to the service pubkey from the original `naddr` or `nprofile`. + +If specified, `callback` SHOULD be a `nostr:nprofile1...` URI. This allows the service to decouple the advertised LNURL identity from the callback identity, reducing correlation of usage across different payers. + +## LUD-17 Scheme Parsing + +When a [LUD-17](17.md) scheme URI has a host starting with `naddr1` or `nprofile1`, the wallet MUST treat it as a `nostr:` URI rather than converting to `https:`. For example: + +``` +lnurlp:naddr1qvzqqqr4gupzp... → nostr:naddr1qvzqqqr4gupzp... +``` + +## Asynchronous Delivery + +Nostr is asynchronous by nature. NIP-17 Gift Wrap events are stored by relays, enabling fully asynchronous LNURL flows: + +- With `naddr`, the wallet can fetch parameters at any time — the relay has the event cached regardless of the service's availability. +- A wallet can send a callback request even if the service is offline. The service processes it when it comes back online. +- Services SHOULD periodically query their relays for unprocessed Gift Wrap events addressed to their LNURL pubkey. -As Nostr is asynchronous in nature, lnurl protocols also become asynchronous. When an lnurl "provider" is offline, a lnurl "taker" may still send his request. Once the provider is back online, they can query all events relating to the lnurl pubkey that were broadcast since they last processed. +## Error Handling -## To Discuss +- If the service cannot process a request, it SHOULD reply with a standard LNURL error response: `{"status": "ERROR", "reason": "..."}`. +- If no response is received within a reasonable timeout (RECOMMENDED: 30 seconds for interactive flows), the wallet SHOULD inform the user and MAY retry. +- If a relay from the hints is unreachable, the wallet SHOULD try other relays listed in the `naddr`/`nprofile`. -Should we replace NIP-4 with an ephemeral event? (this voids the async functionality mentioned above) +## Security Considerations +- **Key isolation**: The Nostr key used for an LNURL endpoint SHOULD be dedicated to that purpose — not the user's main Nostr identity. +- **Ephemeral wallet keys**: The wallet MUST generate a fresh key for each LNURL interaction to prevent correlation across uses. +- **Replay protection**: Services SHOULD reject requests with `created_at` timestamps more than 10 minutes in the past. +- **NIP-17 privacy**: Gift Wrap (NIP-59) hides the sender's identity from relays via an ephemeral key on the outer wrap and randomizes timestamps. Only the recipient can unwrap the message.