Skip to content

Commit

Permalink
feat: add support of keyauth:// + error handling on decode
Browse files Browse the repository at this point in the history
  • Loading branch information
Dolu89 committed Jan 26, 2023
1 parent e89dc60 commit 7f621a9
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 21 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zerologin/lnurl",
"version": "0.1.1",
"version": "0.1.2",
"description": "LNURL library written in TS",
"private": false,
"type": "module",
Expand Down
28 changes: 28 additions & 0 deletions src/decode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { bech32 } from "bech32";
import { limit } from "./lnurl-bech32";

export const decodeBech32 = (lnurl: string): string => {
const protocol = 'lightning:'
if (lnurl.toLowerCase().includes(protocol)) {
lnurl = lnurl.toLowerCase().split('lightning:')[1]
}

const decoded = bech32.decode(lnurl, limit);
const decodedString = Buffer.from(bech32.fromWords(decoded.words)).toString("utf8");
return decodedString
}

export const decodeKeyauth = (keyauth: string): string => {
const isTorRegex = /^keyauth?:\/\/[a-z0-9]*\.onion(\/|$)/i;

let decodedString = keyauth

const protocol = 'keyauth://'
if (isTorRegex.test(keyauth)) {
decodedString = keyauth.replace(protocol, 'http://')
} else {
decodedString = keyauth.replace(protocol, 'https://')
}

return decodedString
}
39 changes: 19 additions & 20 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
import { bech32 } from "bech32";
import { decodeBech32, decodeKeyauth } from "./decode.js";
import { LnurlDecoded } from "./interfaces/LnurlDecoded.js";
import { limit, prefix } from "./lnurl-bech32.js";
import { getLnurlObject, getRootDomain } from "./utils.js";

const limit = 1023;
const prefix = "lnurl";

interface LnurlDecoded {
decoded: string,
domain: string,
tag: string,
k1: string,
action: string,
}

const decode = (lnurl: string): LnurlDecoded => {
const protocol = 'lightning:'
if (lnurl.toLowerCase().includes(protocol)) {
lnurl = lnurl.toLowerCase().split('lightning:')[1]
let decodedString: string | null = null

if (lnurl.toLowerCase().startsWith('lightning:') || lnurl.toLowerCase().startsWith('lnurl1')) {
decodedString = decodeBech32(lnurl)
} else if (lnurl.toLowerCase().startsWith('keyauth://')) {
decodedString = decodeKeyauth(lnurl)
} else {
throw new Error(`URL not valid ('${lnurl}')`);
}

const decoded = bech32.decode(lnurl, limit);
const decodedString = Buffer.from(bech32.fromWords(decoded.words)).toString("utf8");

const split = decodedString.split('?')
if (split.length !== 2) {
Expand All @@ -32,9 +26,14 @@ const decode = (lnurl: string): LnurlDecoded => {
return { decoded: decodedString, domain, tag: getLnurlObject(lnurlObject, "tag"), k1: getLnurlObject(lnurlObject, "k1"), action: getLnurlObject(lnurlObject, "action") };
};

const encode = (unencoded: string): string => {
let words = bech32.toWords(Buffer.from(unencoded, "utf8"));
return bech32.encode(prefix, words, limit).toUpperCase();
const encode = (unencoded: string, keyauth: boolean = false): string => {
if (keyauth) {
return unencoded.replace('http://', 'keyauth://').replace('https://', 'keyauth://');
}
else {
let words = bech32.toWords(Buffer.from(unencoded, "utf8"));
return bech32.encode(prefix, words, limit).toUpperCase();
}
};

export { decode, encode };
7 changes: 7 additions & 0 deletions src/interfaces/LnurlDecoded.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface LnurlDecoded {
decoded: string,
domain: string,
tag: string,
k1: string,
action: string,
}
2 changes: 2 additions & 0 deletions src/lnurl-bech32.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const limit = 1023;
export const prefix = "lnurl";
59 changes: 59 additions & 0 deletions tests/lnurl.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,26 @@ test("can encode", ({ expect }) => {
expect(encoded).toEqual(expected);
});

test("can encode to keyauth from clearnet", ({ expect }) => {
const unencoded =
"https://service.com/lnurl/auth?tag=login&k1=e1459d4e1c2e545a89d8dfb7e5cf6ca90a2d8f6ed217722ee3258cbbc6206281&hmac=cae3ce867242f32a738ac6d0c60eeefd18df0618c6c0ab7eb4d833ddd584a161";
const encoded = encode(unencoded, true);

const expected =
"keyauth://service.com/lnurl/auth?tag=login&k1=e1459d4e1c2e545a89d8dfb7e5cf6ca90a2d8f6ed217722ee3258cbbc6206281&hmac=cae3ce867242f32a738ac6d0c60eeefd18df0618c6c0ab7eb4d833ddd584a161";
expect(encoded).toEqual(expected);
});

test("can encode to keyauth from onion", ({ expect }) => {
const unencoded =
"http://service.onion/lnurl/auth?tag=login&k1=e1459d4e1c2e545a89d8dfb7e5cf6ca90a2d8f6ed217722ee3258cbbc6206281&hmac=cae3ce867242f32a738ac6d0c60eeefd18df0618c6c0ab7eb4d833ddd584a161";
const encoded = encode(unencoded, true);

const expected =
"keyauth://service.onion/lnurl/auth?tag=login&k1=e1459d4e1c2e545a89d8dfb7e5cf6ca90a2d8f6ed217722ee3258cbbc6206281&hmac=cae3ce867242f32a738ac6d0c60eeefd18df0618c6c0ab7eb4d833ddd584a161";
expect(encoded).toEqual(expected);
});

test("can decode without protocol", ({ expect }) => {
const encoded =
"lnurl1dp68gurn8ghj7um9wfmxjcm99e3k7mf0d3h82unv9ash2arg8a6xzeead3hkw6twye4nz0t9xy6r2wtyx3jnzcejv56ngdtp8qukgwryve3rwef4vdnrvcmp8ycxzvny8pnrvetyxgcnwdejxfjk2vejx5uxxcnzvvmryvpkxgurzfngd4skx0trv9jnxcm98qmrwv35xfnrxvnpxuensctrxejrqcekxpjk2etxvscnserxxqmrzwrrxe3nqctzxajkydry8qenxeryvs6nsdrpxymrzajxakz";
Expand Down Expand Up @@ -74,3 +94,42 @@ test("can decode without protocol UPPERCASE", ({ expect }) => {
};
expect(decoded).toEqual(expected);
});

test("can decode keyauth:// protocol clearnet", ({ expect }) => {
const encoded =
"keyauth://service.com/lnurl/auth?tag=login&k1=e1459d4e1c2e545a89d8dfb7e5cf6ca90a2d8f6ed217722ee3258cbbc6206281&hmac=cae3ce867242f32a738ac6d0c60eeefd18df0618c6c0ab7eb4d833ddd584a161";
const decoded = decode(encoded);

const expected =
{
decoded: "https://service.com/lnurl/auth?tag=login&k1=e1459d4e1c2e545a89d8dfb7e5cf6ca90a2d8f6ed217722ee3258cbbc6206281&hmac=cae3ce867242f32a738ac6d0c60eeefd18df0618c6c0ab7eb4d833ddd584a161",
domain: "service.com",
action: "",
k1: "e1459d4e1c2e545a89d8dfb7e5cf6ca90a2d8f6ed217722ee3258cbbc6206281",
tag: "login"
};
expect(decoded).toEqual(expected);
});

test("can decode keyauth:// protocol onion", ({ expect }) => {
const encoded =
"keyauth://service.onion/lnurl/auth?tag=login&k1=e1459d4e1c2e545a89d8dfb7e5cf6ca90a2d8f6ed217722ee3258cbbc6206281&hmac=cae3ce867242f32a738ac6d0c60eeefd18df0618c6c0ab7eb4d833ddd584a161";
const decoded = decode(encoded);

const expected =
{
decoded: "http://service.onion/lnurl/auth?tag=login&k1=e1459d4e1c2e545a89d8dfb7e5cf6ca90a2d8f6ed217722ee3258cbbc6206281&hmac=cae3ce867242f32a738ac6d0c60eeefd18df0618c6c0ab7eb4d833ddd584a161",
domain: "service.onion",
action: "",
k1: "e1459d4e1c2e545a89d8dfb7e5cf6ca90a2d8f6ed217722ee3258cbbc6206281",
tag: "login"
};
expect(decoded).toEqual(expected);
});

test("throw exception when invalid string is passed", ({ expect }) => {
const encoded =
"invalidprotocol:invalidstring";

expect(() => { decode(encoded) }).toThrowError();
});

0 comments on commit 7f621a9

Please sign in to comment.