From 6630f5e5a050877c95e27a344b0ebad1c123553c Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Tue, 5 Dec 2023 22:27:41 +0700 Subject: [PATCH 1/4] feat: remove browserify, crypto-js and source maps --- README.md | 23 +++++++++++++++++------ package.json | 12 ++++-------- setupJest.ts | 11 +++++++++-- src/invoice.test.ts | 11 +++++++++++ src/invoice.ts | 8 ++++---- src/lightning-address.ts | 8 ++++---- src/utils/hex.ts | 5 +++++ src/utils/lnurl.test.ts | 4 ++-- src/utils/lnurl.ts | 12 ++++++------ src/utils/nostr.ts | 9 ++++----- src/utils/sha256.ts | 18 ++++++++++++++++++ src/window.js | 3 --- tsconfig.json | 4 ++-- yarn.lock | 10 ---------- 14 files changed, 86 insertions(+), 52 deletions(-) create mode 100644 src/utils/hex.ts create mode 100644 src/utils/sha256.ts delete mode 100644 src/window.js diff --git a/README.md b/README.md index 3bcc11e..0705023 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,20 @@ yarn add @getalby/lightning-tools or for use without any build tools: -``` -// lightning-tools now available at window.lightningTools - +```html + ``` **This library relies on a global `fetch()` function which will work in [browsers](https://caniuse.com/?search=fetch) and node v18 or newer.** (In older versions you have to use a polyfill.) @@ -179,7 +190,7 @@ import { fetchWithL402 } from "@getalby/lightning-tools"; await fetchWithL402( "https://lsat-weather-api.getalby.repl.co/kigali", {}, - { store: window.localStorage }, + { store: window.localStorage } ) .then((res) => res.json()) .then(console.log); @@ -198,7 +209,7 @@ const nwc = new webln.NostrWebLNProvider({ await fetchWithL402( "https://lsat-weather-api.getalby.repl.co/kigali", {}, - { webln: nwc }, + { webln: nwc } ) .then((res) => res.json()) .then(console.log); @@ -211,7 +222,7 @@ import { l402 } from "@getalby/lightning-tools"; await l402.fetchWithL402( "https://lsat-weather-api.getalby.repl.co/kigali", {}, - { store: new l402.storage.NoStorage() }, + { store: new l402.storage.NoStorage() } ); ``` diff --git a/package.json b/package.json index 97fa43d..363fd88 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@getalby/lightning-tools", - "version": "4.2.1", + "version": "5.0.0", "description": "Collection of helpful building blocks and tools to develop Bitcoin Lightning web apps", "type": "module", "source": "src/index.ts", @@ -36,21 +36,16 @@ "tsc:compile": "tsc --noEmit", "format": "prettier --check '**/*.(md|json)' 'src/**/*.(js|ts)' 'examples/**/*.js'", "format:fix": "prettier --loglevel silent --write '**/*.(md|json)' 'src/**/*.(js|ts)' 'examples/**/*.js'", - "build:browser": "cp src/window.js dist && browserify dist/window.js > dist/index.browser.js", "test": "jest", "clean": "rm -rf dist", - "build": "microbundle && yarn build:browser", + "build": "microbundle --no-sourcemap", "dev": "microbundle watch", "prepare": "husky install" }, - "dependencies": { - "crypto-js": "^4.1.1", - "light-bolt11-decoder": "^3.0.0" - }, + "dependencies": {}, "devDependencies": { "@commitlint/cli": "^17.7.1", "@commitlint/config-conventional": "^17.7.0", - "@types/crypto-js": "^4.1.1", "@types/jest": "^29.4.0", "@types/node": "^20.8.2", "@typescript-eslint/eslint-plugin": "^6.4.0", @@ -62,6 +57,7 @@ "husky": "^8.0.3", "jest": "^29.5.0", "jest-fetch-mock": "^3.0.3", + "light-bolt11-decoder": "^3.0.0", "lint-staged": "^14.0.0", "microbundle": "^0.15.1", "nostr-tools": "^1.17.0", diff --git a/setupJest.ts b/setupJest.ts index 79e0bb2..02c85dd 100644 --- a/setupJest.ts +++ b/setupJest.ts @@ -1,3 +1,10 @@ -import jestFetchMock from 'jest-fetch-mock'; +import jestFetchMock from "jest-fetch-mock"; +import crypto from "crypto"; -jestFetchMock.enableMocks(); \ No newline at end of file +jestFetchMock.enableMocks(); + +Object.defineProperty(globalThis, "crypto", { + value: { + subtle: crypto.webcrypto.subtle, + }, +}); diff --git a/src/invoice.test.ts b/src/invoice.test.ts index b20d9a8..adbc249 100644 --- a/src/invoice.test.ts +++ b/src/invoice.test.ts @@ -27,4 +27,15 @@ describe("Invoice", () => { const decodedInvoice = new Invoice({ pr: paymentRequestWithMemo }); expect(decodedInvoice.description).toBe("Test memo"); }); + + test("validate preimage", async () => { + const decodedInvoice = new Invoice({ + pr: "lnbc120n1p3ecwp5pp5z8n0tzytydn57x6q0kqgfearewkx6kdh90svrkrc64azwy9jpnfqdq4f35kw6r5wdshgueqw35hqcqzpgxqyz5vqsp535pwwk083jvpnf87nl3mr4ext8q5f576s57cds72nvu7fpr037nq9qyyssqtq40wszjzs0vpaka2uckjf4xs2fu24f4vp9eev8r230m6epcp2kxdg8xztlw89p2kzkdpadujuflv6f8avgw3jhnvcxjkegdtydd95sp8hwns5", + }); + expect( + await decodedInvoice.validatePreimage( + "dedbef581d83342848d99c02519053f01856add237f94437bc9bbec7bd6f6e55", + ), + ).toBe(true); + }); }); diff --git a/src/invoice.ts b/src/invoice.ts index 8ad900f..ae8e4dc 100644 --- a/src/invoice.ts +++ b/src/invoice.ts @@ -1,7 +1,7 @@ import { decodeInvoice } from "./utils/invoice"; -import Hex from "crypto-js/enc-hex.js"; -import sha256 from "crypto-js/sha256.js"; import { InvoiceArgs } from "./types"; +import { sha256 } from "./utils/sha256"; +import { fromHexString } from "./utils/hex"; export default class Invoice { paymentRequest: string; @@ -44,11 +44,11 @@ export default class Invoice { } } - validatePreimage(preimage: string): boolean { + async validatePreimage(preimage: string): Promise { if (!preimage || !this.paymentHash) return false; try { - const preimageHash = sha256(Hex.parse(preimage)).toString(Hex); + const preimageHash = await sha256(fromHexString(preimage)); return this.paymentHash === preimageHash; } catch { return false; diff --git a/src/lightning-address.ts b/src/lightning-address.ts index 3d019c4..3409af7 100644 --- a/src/lightning-address.ts +++ b/src/lightning-address.ts @@ -75,7 +75,7 @@ export default class LightningAddress { ); const json = await result.json(); - this.parseResponse(json.lnurlp, json.keysend, json.nostr); + await this.parseResponse(json.lnurlp, json.keysend, json.nostr); } async fetchWithoutProxy() { @@ -99,7 +99,7 @@ export default class LightningAddress { nostrData = await nostrResult.json(); } - this.parseResponse(lnurlData, keysendData, nostrData); + await this.parseResponse(lnurlData, keysendData, nostrData); } lnurlpUrl() { @@ -249,13 +249,13 @@ export default class LightningAddress { return response; } - private parseResponse( + private async parseResponse( lnurlpData: LnUrlRawData | undefined, keysendData: KeySendRawData | undefined, nostrData: NostrResponse | undefined, ) { if (lnurlpData) { - this.lnurlpData = parseLnUrlPayResponse(lnurlpData); + this.lnurlpData = await parseLnUrlPayResponse(lnurlpData); } if (keysendData) { this.keysendData = parseKeysendResponse(keysendData); diff --git a/src/utils/hex.ts b/src/utils/hex.ts new file mode 100644 index 0000000..2151dd1 --- /dev/null +++ b/src/utils/hex.ts @@ -0,0 +1,5 @@ +// from https://stackoverflow.com/a/50868276 +export const fromHexString = (hexString: string) => + Uint8Array.from( + hexString.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16)), + ); diff --git a/src/utils/lnurl.test.ts b/src/utils/lnurl.test.ts index ca94013..632a2eb 100644 --- a/src/utils/lnurl.test.ts +++ b/src/utils/lnurl.test.ts @@ -27,7 +27,7 @@ describe("isUrl", () => { }); describe("parseLnUrlPayResponse", () => { - test("min/max must be in millisats", () => { + test("min/max must be in millisats", async () => { const response = { status: "OK", tag: "payRequest", @@ -42,7 +42,7 @@ describe("parseLnUrlPayResponse", () => { "79f00d3f5a19ec806189fcab03c1be4ff81d18ee4f653c88fac41fe03570f432", allowsNostr: true, }; - const parsed = parseLnUrlPayResponse(response); + const parsed = await parseLnUrlPayResponse(response); expect(parsed.min).toBe(1000); expect(parsed.max).toBe(11000000000); }); diff --git a/src/utils/lnurl.ts b/src/utils/lnurl.ts index 47e5b91..0fcc2f2 100644 --- a/src/utils/lnurl.ts +++ b/src/utils/lnurl.ts @@ -1,11 +1,9 @@ -import Hex from "crypto-js/enc-hex.js"; -import sha256 from "crypto-js/sha256.js"; - import type { LUD18ServicePayerData, LnUrlPayResponse, LnUrlRawData, } from "../types"; +import { sha256 } from "./sha256"; const URL_REGEX = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)/; @@ -30,7 +28,9 @@ export const isValidAmount = ({ const TAG_PAY_REQUEST = "payRequest"; // From: https://github.com/dolcalmi/lnurl-pay/blob/main/src/request-pay-service-params.ts -export const parseLnUrlPayResponse = (data: LnUrlRawData): LnUrlPayResponse => { +export const parseLnUrlPayResponse = async ( + data: LnUrlRawData, +): Promise => { if (data.tag !== TAG_PAY_REQUEST) throw new Error("Invalid pay service params"); @@ -45,10 +45,10 @@ export const parseLnUrlPayResponse = (data: LnUrlRawData): LnUrlPayResponse => { let metadataHash: string; try { metadata = JSON.parse(data.metadata + ""); - metadataHash = sha256(data.metadata + "").toString(Hex); + metadataHash = await sha256(data.metadata + ""); } catch { metadata = []; - metadataHash = sha256("[]").toString(Hex); + metadataHash = await sha256("[]"); } let image = ""; diff --git a/src/utils/nostr.ts b/src/utils/nostr.ts index f806b60..97cf0e7 100644 --- a/src/utils/nostr.ts +++ b/src/utils/nostr.ts @@ -1,6 +1,5 @@ -import Hex from "crypto-js/enc-hex.js"; -import sha256 from "crypto-js/sha256.js"; import { Event, NostrResponse, ZapArgs, ZapOptions } from "../types"; +import { sha256 } from "./sha256"; export async function generateZapEvent( { satoshi, comment, p, e, relays }: ZapArgs, @@ -32,7 +31,7 @@ export async function generateZapEvent( content: comment ?? "", }; - nostrEvent.id = getEventHash(nostrEvent); + nostrEvent.id = await getEventHash(nostrEvent); return await nostr.signEvent(nostrEvent); } @@ -69,8 +68,8 @@ export function serializeEvent(evt: Event): string { ]); } -export function getEventHash(event: Event): string { - return sha256(serializeEvent(event)).toString(Hex); +export function getEventHash(event: Event): Promise { + return sha256(serializeEvent(event)); } export function parseNostrResponse( diff --git a/src/utils/sha256.ts b/src/utils/sha256.ts new file mode 100644 index 0000000..83c9753 --- /dev/null +++ b/src/utils/sha256.ts @@ -0,0 +1,18 @@ +// from https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest +export async function sha256(message: string | Uint8Array) { + // encode as UTF-8 + const msgBuffer = + typeof message === "string" ? new TextEncoder().encode(message) : message; + + // hash the message + const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer); + + // convert ArrayBuffer to Array + const hashArray = Array.from(new Uint8Array(hashBuffer)); + + // convert bytes to hex string + const hashHex = hashArray + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); + return hashHex; +} diff --git a/src/window.js b/src/window.js deleted file mode 100644 index 915186d..0000000 --- a/src/window.js +++ /dev/null @@ -1,3 +0,0 @@ -// assign alby-tools exports to global window object (for index.browser.js) -// @ts-ignore this file is created at build time -window["lightningTools"] = require("./index.cjs"); diff --git a/tsconfig.json b/tsconfig.json index 576273a..10683f9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,13 +6,13 @@ "skipLibCheck": true, "checkJs": true, "allowJs": true, - "declarationMap": true, + "declarationMap": false, "declaration": true, "allowSyntheticDefaultImports": true, "target": "es2020", "rootDir": "./src", "moduleResolution": "node", - "sourceMap": true, + "sourceMap": false, "noImplicitAny": false }, "include": ["src/**/*"], diff --git a/yarn.lock b/yarn.lock index 0d94405..1dcf19d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1899,11 +1899,6 @@ dependencies: "@babel/types" "^7.3.0" -"@types/crypto-js@^4.1.1": - version "4.1.1" - resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-4.1.1.tgz#602859584cecc91894eb23a4892f38cfa927890d" - integrity sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA== - "@types/estree@*": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" @@ -3066,11 +3061,6 @@ crypto-browserify@^3.0.0: randombytes "^2.0.0" randomfill "^1.0.3" -crypto-js@^4.1.1: - version "4.2.0" - resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631" - integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== - css-declaration-sorter@^6.3.0: version "6.3.1" resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz#be5e1d71b7a992433fb1c542c7a1b835e45682ec" From eb60ea6b186dbb7646e3361e81a766d1c4c86770 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Wed, 6 Dec 2023 12:20:00 +0700 Subject: [PATCH 2/4] fix: crypto usage in nodejs --- README.md | 3 +-- examples/request-invoice.js | 11 +++++++++++ package.json | 2 +- src/utils/sha256.ts | 5 +++++ tsconfig.json | 1 + 5 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 examples/request-invoice.js diff --git a/README.md b/README.md index 0705023..8c8320d 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,7 @@ or for use without any build tools: ```html