Skip to content

Latest commit

 

History

History
99 lines (73 loc) · 5.51 KB

18.md

File metadata and controls

99 lines (73 loc) · 5.51 KB

LUD-18: Payer identity in payRequest protocol.

author: akumaigorodski author: hampus_s discussion: https://t.me/lnurl/14436 author: fiatjaf author: dpad85 discussion: https://t.me/lnurl/24003 discussion: https://github.com/fiatjaf/lnurl-rfc/pull/101


The idea here is that the payer can identify itself when paying. The scope varies from free-form names (or LUD-16 internet identifiers) useful for loose identification in comments or similar things, to cryptographic keys for proof of payment, to authentication keys for future login into a SERVICE's website after a payment and other use cases.

Aside from that, the payer ids are also committed to the invoice by means of the descriptionHash property, which ensures strong cryptographic guarantees for proof of payment.

1. payerData record

If SERVICE wants to get one or more types of payer identities from WALLET then it MUST alter its JSON response to the first callback to include a payerData field, as follows (notice that the payerData record below has a bunch of fields only for completion, an actual response will likely contain just a subset of these):

 {
   "callback": String,
   "maxSendable": number,
   "minSendable": number,
   "metadata": string,
+  "payerData": {
+    "name": { "mandatory": boolean },
+    "pubkey": { "mandatory": boolean },
+    "identifier": { "mandatory": boolean },
+    "email": { "mandatory": boolean },
+    "auth": {
+       "mandatory": boolean,
+       "k1": string // hex encoded 32 bytes of challenge
+    },
+    ...other fields may be negotiated
+  },
   "tag": "payRequest",
 }

Notice that just including the payer id kind ("name", "pubkey" etc.) in the payerData record is enough to signal acceptance of that kind.

2. Specifying payer identity before sending a payment

In response to seeing a payerData record in the initial response from SERVICE, WALLET attaches a payerdata query parameter to LNURL-PAY callback with value set to a JSON object:

- <callback><?|&>amount=<milliSatoshi>
+ <callback><?|&>amount=<milliSatoshi>&payerdata=<urlencode({json object})>

The JSON object MUST be of the following format (notice that these fields are shown only for completion, in practice it will likely contain just a subset of these):

{
  "name": string, // free form string
  "pubkey": string, // hex(<randomly generated secp256k1 pubkey>),
  "auth": {
    "key": string, // hex(<linkingKey>)
    "k1": string, // same as received from service on section 1
    "sig": string, // following LUD-04: hex(sign(hexToBytes(<k1>), <linkingPrivKey>))
  },
  "email": string,
  "identifier": string,
  ...other fields may be included if supported by wallet and requested by service
}

Each key in this JSON object should correspond to a requested payerdata from the payerData record received from SERVICE.

WALLET CAN send any of the payer id kinds if they are listed in the payerData record. But if any is marked as "mandatory": true then WALLET MUST send or otherwise do not proceed with the payment flow.

WALLET SHOULD NOT send payer identity types omitted in payerData record, none at all if record is not present.

3. Committing payer to the invoice

If SERVICE requests (section 1) and WALLET sends a payerdata record in the callback (section 2), the payer ids MUST be committed to the metadata before the invoice is created.

SERVICE MUST append it to the metadata as it was received (after url-decoding), as in the following example:

 [["text/plain", "description"], ["image/png;base64", "AAA=="]]
+[["text/plain", "description"], ["image/png;base64", "AAA=="]]{<identity records>}

After doing that, SERVICE proceeds to hash the metadata and include that hash in the generated BOLT11 invoice as the descriptionHash field.

On its own side, WALLET MUST do the same thing before hashing the metadata and checking its hashed value against the descriptionHash field of the invoice received from SERVICE.

Even if WALLET sends more payerdata fields than requested by SERVICE, SERVICE MUST still append everything as received.

WALLET MUST NOT expect SERVICE to append the identity records to the metadata if SERVICE hadn't included the payerData record in the first response, as that would mean SERVICE doesn't understand LUD-18 at all.

Pseudocode example for calculating descriptionHash with payerdata

originalServiceMetadata = '[["text/plain", "description"], ["image/png;base64", "AAA=="]]'
urlEncodedPayerIds = '%7B%22name%22%3A%22bob%22%2C%22auth%22%3A%7B%22key%22%3A%2202c9323d02fc164f89c8f688dbfba8aad69a96fa8f6253ba8cce2c6f1546073fa3%22%2C%22sig%22%3A%222afd21794e2a801d0d516584ceebe1a24ed8991dd5ec708259aeaee5c0d2d1437542b689ee5d39e619a01a257142d49c18a4af3088c46ce87e2d941a1bcc7210%22%7D%2C%22identifier%22%3A%22bob%40bob.com%22%2C%22pubkey%22%3A%2203ee58475055820fbfa52e356a8920f62f8316129c39369dbdde3e5d0198a9e315%22%7D'
payerData = urldecode(urlEncodedPayerIds)
descriptionToBeHashed = metadata + payerData
descriptionToBeHashed == '[["text/plain", "description"], ["image/png;base64", "AAA=="]]{"name":"bob","auth":{"key":"02c9323d02fc164f89c8f688dbfba8aad69a96fa8f6253ba8cce2c6f1546073fa3","sig":"2afd21794e2a801d0d516584ceebe1a24ed8991dd5ec708259aeaee5c0d2d1437542b689ee5d39e619a01a257142d49c18a4af3088c46ce87e2d941a1bcc7210"},"identifier":"[email protected]","pubkey":"03ee58475055820fbfa52e356a8920f62f8316129c39369dbdde3e5d0198a9e315"}'
descriptionHash = sha256(utf8(descriptionToBeHashed))