author: chill117
A service may authorize other applications to create LNURLs without direct interaction between the authorized application and the service. This can allow offline hardware devices to generate LNURLs for a service that supports the signing scheme described in this document.
The service must keep a list of authorized keys so that it can check whether a request was signed by an authorized key. The authorized keys list can be empty - in this case no signed requests will be accepted. An authorization key should include a secret key, the encoding of the secret key, and a unique identifier. An example authorization key as a JSON object:
{
"id": "935e30a7",
"key": "e31b5c188346f3a83a7e698486bee48522eed378847126d78dbc030093ea14c7",
"encoding": "hex"
}
Possible values for "encoding"
:
"base64"
- Base64 encoded"hex"
- Hexadecimal encoded""
(empty-string) - Unencoded / plaintext (utf8)
The "id"
should be a unique identifier so that an individual authorization key may be found by this value.
An authorization key is to be provided to the authorized application so that it can generate signed URLs.
These steps are to be done by an authorized application.
- Build the base URL in the same way that your service would normally less the
k1
or secret value. For example, if your service generates withdraw links like https://example.com/lnurl?tag=withdraw&amount=5¤cy=EUR&k1=0aa4a2285fb16207865c87c28bb0d78f42248f3077aba917c2f55b6be51e5e3d wherek1
is the secret that makes the link unique and acts as a secret (or password) which grants access to the URL. Thek1
can be left out here because we are using signing to authorize the creation of the URL in the server. So the example URL here would be as follows: - Add the authorization key's identifier to the URL's query string as follows:
- Generate a unique, random nonce and add it to the query string:
- https://example.com/lnurl?tag=withdraw&amount=5¤cy=EUR&id=935e30a7&nonce=d2e3c794
- 32 bits of random data is probably sufficient entropy to avoid collisions
- Sort the query string by key (alphabetically). The above example would result in the following:
amount=5¤cy=EUR&id=935e30a7&nonce=d2e3c794&tag=withdraw
- The sorted query string will be referred to as the "payload".
- Note that both the keys and values in the query string must be URL-encoded. The following characters should be unescaped:
A-Z a-z 0-9 - _ . ! ~ * ' ( )
. See encodeURIComponent for more details.
- Note that both the keys and values in the query string must be URL-encoded. The following characters should be unescaped:
- Sign the payload using the authorization key secret. Signatures are generated using HMAC-SHA256, where the authorization key secret is the key and the payload is the message.
- Add the signature to the query string. Example:
amount=5¤cy=EUR&id=935e30a7&nonce=d2e3c794&tag=withdraw&signature=80224eed83e03acd0e44760f42b3a7157f549d04cf0160574246e9a87ff9bf8f
- Build the full URL with the signed query string. Example:
These steps are to be done by the service when it receives a signed LNURL request:
- Given the following example request URL:
- Separate the query string from the rest of the request URL. Example:
amount=5¤cy=EUR&id=935e30a7&nonce=d2e3c794&tag=withdraw&signature=80224eed83e03acd0e44760f42b3a7157f549d04cf0160574246e9a87ff9bf8f
- Remove the signature from the query string. Example:
amount=5¤cy=EUR&id=935e30a7&nonce=d2e3c794&tag=withdraw
- Sort the query string by key (alphabetically) in the case that it is not already. Example:
amount=5¤cy=EUR&id=935e30a7&nonce=d2e3c794&tag=withdraw
- The sorted query string will be referred to as the "payload".
- Use the
id
in the query string to find the authorization key in the service's list of authorization keys.- If the authorization key is not found, then fail the request.
- If the authorization key is found, then continue with the signature check.
- Sign the payload using the authorization key secret that was found in the previous step. Signatures are generated using HMAC-SHA256, where the authorization key secret is the key and the payload is the message.
- Check the signature from the query string against the signature generated by the service.
- If the signature does not match, then fail the request.
- If the signature matches, then continue with the service's normal LNURL flow.
Optional steps to generate a deterministic value to be used as the k1
or identifier of an LNURL record:
- Concatenate the
id
andsignature
values together. Example:935e30a7-80224eed83e03acd0e44760f42b3a7157f549d04cf0160574246e9a87ff9bf8f
- Hash the result of the previous step using SHA256. Example:
e3c99bc67a12b3cc90cdc9a2604564fea3e54c8529f3fc5166fb92e0f7f5a3f0
- The resulting hash can be used as the
k1
value or unique, deterministic identifier for the signed request.- This can allow your service to prevent or limit the re-use of signed URLs.
The below test vectors are formatted as a JSON array:
[
{
"authorizationKey": {
"id": "935e30a7",
"key": "e31b5c188346f3a83a7e698486bee48522eed378847126d78dbc030093ea14c7",
"encoding": "hex"
},
"payload": "amount=5¤cy=EUR&id=935e30a7&nonce=d2e3c794&tag=withdraw",
"signature": "80224eed83e03acd0e44760f42b3a7157f549d04cf0160574246e9a87ff9bf8f"
},
{
"authorizationKey": {
"id": "4155710c",
"key": "bGAzwLUv1ivWOtARN3pcLV8ry1gdaaAPn2n6wdrKiuY=",
"encoding": "base64"
},
"payload": "amount=5¤cy=EUR&id=4155710c&nonce=d2e3c794&tag=withdraw",
"signature": "5709dbc00362abbf7ad4da05d9058992b969a3a0c8d771c9310d1ab4738a278e"
},
{
"authorizationKey": {
"id": "123",
"key": "a plaintext secret",
"encoding": ""
},
"payload": "amount=5¤cy=EUR&id=123&nonce=d2e3c794&tag=withdraw",
"signature": "abbd793e08b1fff85ff684639dd0283037a7cfd99b5af8e19fbff8dfb31397dd"
}
]
Test vectors generated with the following code to be run with nodejs:
const crypto = require('crypto');
const querystring = require('querystring');
const authorizationKeys = [
{
id: '935e30a7',
key: 'e31b5c188346f3a83a7e698486bee48522eed378847126d78dbc030093ea14c7',
encoding: 'hex',
},
{
id: '4155710c',
key: 'bGAzwLUv1ivWOtARN3pcLV8ry1gdaaAPn2n6wdrKiuY=',
encoding: 'base64',
},
{
id: '123',
key: 'a plaintext secret',
encoding: '',
},
];
const createSignature = function(key, encoding, payload) {
encoding = encoding || 'utf8';
return crypto.createHmac('sha256', Buffer.from(key, encoding)).update(payload).digest('hex');
};
const preparePayload = function(query) {
let sortedQuery = Object.create(null);
// Sort the query object by key (alphabetically).
for (const key of Object.keys(query).sort()) {
sortedQuery[key] = query[key];
}
return querystring.stringify(sortedQuery);
};
const testVectors = authorizationKeys.map(authorizationKey => {
const { id, key, encoding } = authorizationKey;
let query = {
amount: 5,
currency: 'EUR',
nonce: 'd2e3c794',
tag: 'withdraw',
id,
};
const payload = preparePayload(query);
const signature = createSignature(key, encoding, payload);
return {
authorizationKey,
payload,
signature,
};
});
console.log(JSON.stringify(testVectors, null, 4));