From ef89ccdb4f32514fab50c2a5f5bc129076307b55 Mon Sep 17 00:00:00 2001 From: Christian Moss Date: Sat, 25 Oct 2025 14:50:12 +0200 Subject: [PATCH 1/2] Add LUD-22: Lightning address request specification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces a new protocol for services to request a user's Lightning address without manual input. Particularly useful for games, kiosks, and interactive experiences where typing creates friction. Example use case: A kart racing game at a conference that streams sats to players as they collect coins - users simply scan a QR code instead of typing their Lightning address. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- 22.md | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 + 2 files changed, 191 insertions(+) create mode 100644 22.md diff --git a/22.md b/22.md new file mode 100644 index 0000000..7ca0827 --- /dev/null +++ b/22.md @@ -0,0 +1,189 @@ +LUD-22: `addressRequest` base spec. +======================================= + +`author: christianmoss` + +--- + +## Lightning Address Sharing + +A service can request a user's Lightning address without requiring manual text input. This is particularly useful for games, apps, and services that need to pay users (e.g., game rewards, referral bonuses, content creator payouts) where typing an address would create friction. + +The service displays a QR code or triggers a URL scheme, the user's wallet prompts for permission, and upon approval sends the Lightning address to the service's callback endpoint. + +### User story: + +**Conference Gaming Example**: You're at a Bitcoin conference and want to play a kart racing game that streams sats to your wallet as you collect coins on the track. + +**Current friction**: You must use a keyboard to manually enter your Lightning address into a UI the game developer built. This is slow, error-prone, and breaks immersion. + +**With LUD-22**: You simply scan a QR code displayed on the game screen with your phone, approve the request in your wallet, and start playing immediately. The game streams sats directly to your Lightning address as you collect coins - no typing required. + +### Use cases: +- Arcade/kiosk games at conferences that pay players in real-time +- Point-of-sale systems collecting customer Lightning addresses for refunds/rewards +- Interactive installations and experiences that reward participants +- Game developers rewarding players without requiring account creation +- Apps distributing payments to users +- Services onboarding users for payouts +- Any application needing a user's Lightning address without manual input + +### Server-side generation of addressRequest URL: + +When creating an `addressRequest` handler, `LN SERVICE` must include a `k1` query parameter consisting of randomly generated 32 bytes of data. An example is `https://yourapp.com/share-address?tag=addressRequest&k1=hex(32 bytes of random data)`. + +The `k1` serves two purposes: +1. **Request matching**: Allows the service to match the callback response to the specific user/session that initiated the request +2. **Spam prevention**: Prevents random POSTs to the callback endpoint + +`LN SERVICE` must maintain a cache of unused `k1` values and only accept callbacks with valid `k1` values from that cache. Used `k1` values should be removed after successful address submission to prevent replay attacks. + +### Server-side choice of subdomain: + +While not as critical as with authentication (LUD-04), `LN SERVICE` should still use clear, meaningful domain names since wallets will display the requesting domain to users for approval. For example, `rewards.yourgame.com` is clearer than `api-v2.yourgame.com`. + +### Wallet to service interaction flow: + +1. `LN WALLET` scans a QR code or receives a URL scheme intent and decodes the URL which contains: + - `tag` query parameter with value `addressRequest` + - `k1` (hex encoded 32 bytes) for request matching + - optional `callback` URL (if different from the base URL) + +2. If `callback` is not present in the initial URL, `LN WALLET` makes a GET request to the URL to retrieve details: + + ```JSON + { + "tag": "addressRequest", + "callback": "https://yourapp.com/receive-address", + "k1": "hex(32 bytes of random data)", + "metadata": "Share your Lightning address with AwesomeGame to receive rewards" + } + ``` + + The `metadata` field should be a human-readable explanation of why the address is being requested. + +3. `LN WALLET` displays a confirmation dialog which must include: + - The domain name extracted from the callback URL + - The `metadata` text explaining the request + - A clear indication that the Lightning address will be shared + - Approve/Deny options + +4. Once approved by user, `LN WALLET` makes a POST request to the `callback` URL with the following JSON body: + + ```JSON + { + "k1": "hex(32 bytes from request)", + "address": "user@wallet.com" + } + ``` + +5. `LN SERVICE` responds with JSON: + + ```JSON + {"status": "OK"} + ``` + + or + + ```JSON + {"status": "ERROR", "reason": "error details..."} + ``` + +### URL scheme format (same device): + +For same-device interactions, the URL can be formatted as: + +``` +lightning:addressRequest?callback=https://yourapp.com/receive&k1=hex(32 bytes)&metadata=Share%20address%20with%20AwesomeGame +``` + +Or services can implement deep linking to their own app: + +``` +yourapp://receive-address?address=user@wallet.com +``` + +### Encoding for QR codes: + +For QR code display (cross-device), the URL should be bech32-encoded following LUD-01: + +``` +lnurl1dp68gurn8ghj7um9wfmxjcm99e5k7... (encodes https://yourapp.com/share?tag=addressRequest&k1=...) +``` + +When used in QR codes, `LNURL` should be uppercase. + +### Security considerations: + +1. **Public information**: Lightning addresses are public identifiers meant to be shared. No sensitive authentication material is transmitted. + +2. **User consent**: Wallets MUST require explicit user approval before sharing the address. The dialog should clearly show which service is requesting the address. + +3. **k1 validation**: Services must validate that the `k1` in the callback matches an unused `k1` from their cache to prevent spam and replay attacks. + +4. **HTTPS required**: All callback URLs must use HTTPS (or http:// for Tor v2/v3 onion addresses). + +5. **Rate limiting**: Services should implement rate limiting on the callback endpoint to prevent abuse. + +6. **No authentication**: This protocol does NOT authenticate the user - it only collects their Lightning address. If authentication is needed, use LUD-04 instead. + +### Example implementation (JavaScript): + +```javascript +// Server-side: Generate addressRequest +const crypto = require('crypto'); + +function generateAddressRequest(sessionId) { + const k1 = crypto.randomBytes(32).toString('hex'); + + // Store k1 with session mapping + sessionCache.set(k1, { sessionId, timestamp: Date.now() }); + + return { + tag: "addressRequest", + callback: `https://yourgame.com/api/receive-address`, + k1: k1, + metadata: "Share your Lightning address with AwesomeGame to receive in-game rewards" + }; +} + +// Server-side: Handle callback +app.post('/api/receive-address', (req, res) => { + const { k1, address } = req.body; + + // Validate k1 + const session = sessionCache.get(k1); + if (!session) { + return res.json({ status: "ERROR", reason: "Invalid or expired request" }); + } + + // Validate address format (basic check) + if (!/^[\w\-\.]+@[\w\-\.]+\.\w+$/.test(address)) { + return res.json({ status: "ERROR", reason: "Invalid Lightning address format" }); + } + + // Store address with user session + userSessions.updateAddress(session.sessionId, address); + + // Remove used k1 + sessionCache.delete(k1); + + res.json({ status: "OK" }); +}); +``` + +### Dependencies: + +- **LUD-01**: Base LNURL encoding/decoding for QR code support +- **LUD-16**: Understanding Lightning address format (`user@domain.com`) + +### Differences from LUD-04 (auth): + +Unlike authentication, this protocol: +- Does NOT require signature generation/verification +- Does NOT derive domain-specific keys +- Does NOT authenticate the user's identity +- Only collects a public Lightning address +- Is much simpler to implement + +If you need authentication, use LUD-04. If you just need a payment destination, use LUD-22. diff --git a/README.md b/README.md index 5d4929f..8008582 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ These are all the individual documents describing each small piece of protocol t | [19][19] | Pay link discoverable from withdraw link. | [Blixt][blixt], [CoinCorner][coincorner], [OBW][obw], [LifPay][lifpay], [LNbits][lnbits] | | [20][20] | Long payment description for pay protocol. | [Alby][alby], [BitBanana][bitbanana], [Blixt][blixt], [Clams][clams], [cliché][cliche], [Mash][mash], [OneKey][onekey], [Phoenix][phoenix], [Piggy][piggy] | | [21][21] | `verify` LNURL-pay payments | [Alby][alby], [LifPay][lifpay], [Mutiny][mutiny], [Stacker.News][stacker.news], [Zaprite][zaprite] | +| [22][22] | Request user's Lightning address | _implementation pending_ | [alby]: https://github.com/getAlby/lightning-browser-extension [bipa]: https://bipa.app @@ -227,6 +228,7 @@ Tools for developers [19]: 19.md [20]: 20.md [21]: 21.md +[22]: 22.md Dependency Tree --------------- From 1f85243a057b9f7fac65d47b93bf8bb16a8241dc Mon Sep 17 00:00:00 2001 From: Christian Moss Date: Sun, 8 Mar 2026 15:47:16 +0000 Subject: [PATCH 2/2] Rename LUD-22 to LUD-23 and address review feedback - Renumber spec from LUD-22 to LUD-23 (LUD-22 reserved for currency denomination PR #251) - Switch wallet callback from POST+JSON to GET with query params to match existing LUD conventions - Rename `metadata` field to `description` to avoid collision with LUD-06 - Update address validation regex to LUD-16 compliant character set ([a-z0-9\-_.+]) Co-Authored-By: Claude Sonnet 4.6 --- 22.md => 23.md | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) rename 22.md => 23.md (84%) diff --git a/22.md b/23.md similarity index 84% rename from 22.md rename to 23.md index 7ca0827..fcf9f60 100644 --- a/22.md +++ b/23.md @@ -1,7 +1,7 @@ -LUD-22: `addressRequest` base spec. +LUD-23: `addressRequest` base spec. ======================================= -`author: christianmoss` +`author: Christian Moss aka Mandelduck` --- @@ -17,7 +17,7 @@ The service displays a QR code or triggers a URL scheme, the user's wallet promp **Current friction**: You must use a keyboard to manually enter your Lightning address into a UI the game developer built. This is slow, error-prone, and breaks immersion. -**With LUD-22**: You simply scan a QR code displayed on the game screen with your phone, approve the request in your wallet, and start playing immediately. The game streams sats directly to your Lightning address as you collect coins - no typing required. +**With LUD-23**: You simply scan a QR code displayed on the game screen with your phone, approve the request in your wallet, and start playing immediately. The game streams sats directly to your Lightning address as you collect coins - no typing required. ### Use cases: - Arcade/kiosk games at conferences that pay players in real-time @@ -34,7 +34,7 @@ When creating an `addressRequest` handler, `LN SERVICE` must include a `k1` quer The `k1` serves two purposes: 1. **Request matching**: Allows the service to match the callback response to the specific user/session that initiated the request -2. **Spam prevention**: Prevents random POSTs to the callback endpoint +2. **Spam prevention**: Prevents random requests to the callback endpoint `LN SERVICE` must maintain a cache of unused `k1` values and only accept callbacks with valid `k1` values from that cache. Used `k1` values should be removed after successful address submission to prevent replay attacks. @@ -56,25 +56,22 @@ While not as critical as with authentication (LUD-04), `LN SERVICE` should still "tag": "addressRequest", "callback": "https://yourapp.com/receive-address", "k1": "hex(32 bytes of random data)", - "metadata": "Share your Lightning address with AwesomeGame to receive rewards" + "description": "Share your Lightning address with AwesomeGame to receive rewards" } ``` - The `metadata` field should be a human-readable explanation of why the address is being requested. + The `description` field should be a human-readable explanation of why the address is being requested. 3. `LN WALLET` displays a confirmation dialog which must include: - The domain name extracted from the callback URL - - The `metadata` text explaining the request + - The `description` text explaining the request - A clear indication that the Lightning address will be shared - Approve/Deny options -4. Once approved by user, `LN WALLET` makes a POST request to the `callback` URL with the following JSON body: +4. Once approved by user, `LN WALLET` makes a GET request to the `callback` URL with the following query parameters: - ```JSON - { - "k1": "hex(32 bytes from request)", - "address": "user@wallet.com" - } + ``` + GET ?k1=&address= ``` 5. `LN SERVICE` responds with JSON: @@ -94,7 +91,7 @@ While not as critical as with authentication (LUD-04), `LN SERVICE` should still For same-device interactions, the URL can be formatted as: ``` -lightning:addressRequest?callback=https://yourapp.com/receive&k1=hex(32 bytes)&metadata=Share%20address%20with%20AwesomeGame +lightning:addressRequest?callback=https://yourapp.com/receive&k1=hex(32 bytes)&description=Share%20address%20with%20AwesomeGame ``` Or services can implement deep linking to their own app: @@ -143,13 +140,13 @@ function generateAddressRequest(sessionId) { tag: "addressRequest", callback: `https://yourgame.com/api/receive-address`, k1: k1, - metadata: "Share your Lightning address with AwesomeGame to receive in-game rewards" + description: "Share your Lightning address with AwesomeGame to receive in-game rewards" }; } // Server-side: Handle callback -app.post('/api/receive-address', (req, res) => { - const { k1, address } = req.body; +app.get('/api/receive-address', (req, res) => { + const { k1, address } = req.query; // Validate k1 const session = sessionCache.get(k1); @@ -157,8 +154,9 @@ app.post('/api/receive-address', (req, res) => { return res.json({ status: "ERROR", reason: "Invalid or expired request" }); } - // Validate address format (basic check) - if (!/^[\w\-\.]+@[\w\-\.]+\.\w+$/.test(address)) { + // Validate address format per LUD-16 (lowercase, [a-z0-9\-_.+] local part) + const [localPart, domain] = (address || '').split('@'); + if (!localPart || !domain || !/^[a-z0-9\-_.+]+$/.test(localPart)) { return res.json({ status: "ERROR", reason: "Invalid Lightning address format" }); } @@ -186,4 +184,4 @@ Unlike authentication, this protocol: - Only collects a public Lightning address - Is much simpler to implement -If you need authentication, use LUD-04. If you just need a payment destination, use LUD-22. +If you need authentication, use LUD-04. If you just need a payment destination, use LUD-23.