From 93ba3ba628b6a0254c34e041625e35fc4f98b79c Mon Sep 17 00:00:00 2001 From: Arc <33088785+arcbtc@users.noreply.github.com> Date: Thu, 11 Sep 2025 05:15:23 +0100 Subject: [PATCH] Revert "Improve configuration form for readability and UX" --- .github/workflows/main.yml | 16 +- .github/workflows/release.yml | 16 +- .github/workflows/test.yml | 2 +- __init__.py | 2 +- migrations.py | 7 +- nwcp.py | 3 +- static/js/admin.js | 100 ++++--- static/js/index.js | 355 +++++++++++------------ static/js/noble-secp256k1.min.js | 479 +++++++++++++++---------------- templates/nwcprovider/admin.html | 47 +-- templates/nwcprovider/index.html | 4 +- tests/integration/test_all.py | 33 +-- 12 files changed, 512 insertions(+), 552 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c428ddd..ed44344 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,7 +1,7 @@ on: push: tags: - - 'v[0-9]+.[0-9]+.[0-9]+' + - "v[0-9]+.[0-9]+.[0-9]+" jobs: release: @@ -22,7 +22,7 @@ jobs: with: token: ${{ secrets.EXT_GITHUB }} repository: lnbits/lnbits-extensions - path: './lnbits-extensions' + path: "./lnbits-extensions" - name: setup git user run: | @@ -31,12 +31,12 @@ jobs: - name: Create pull request in extensions repo env: GH_TOKEN: ${{ secrets.EXT_GITHUB }} - repo_name: '${{ github.event.repository.name }}' - tag: '${{ github.ref_name }}' - branch: 'update-${{ github.event.repository.name }}-${{ github.ref_name }}' - title: '[UPDATE] ${{ github.event.repository.name }} to ${{ github.ref_name }}' - body: 'https://github.com/lnbits/${{ github.event.repository.name }}/releases/${{ github.ref_name }}' - archive: 'https://github.com/lnbits/${{ github.event.repository.name }}/archive/refs/tags/${{ github.ref_name }}.zip' + repo_name: "${{ github.event.repository.name }}" + tag: "${{ github.ref_name }}" + branch: "update-${{ github.event.repository.name }}-${{ github.ref_name }}" + title: "[UPDATE] ${{ github.event.repository.name }} to ${{ github.ref_name }}" + body: "https://github.com/lnbits/${{ github.event.repository.name }}/releases/${{ github.ref_name }}" + archive: "https://github.com/lnbits/${{ github.event.repository.name }}/archive/refs/tags/${{ github.ref_name }}.zip" run: | cd lnbits-extensions git checkout -b $branch diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c428ddd..ed44344 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,7 +1,7 @@ on: push: tags: - - 'v[0-9]+.[0-9]+.[0-9]+' + - "v[0-9]+.[0-9]+.[0-9]+" jobs: release: @@ -22,7 +22,7 @@ jobs: with: token: ${{ secrets.EXT_GITHUB }} repository: lnbits/lnbits-extensions - path: './lnbits-extensions' + path: "./lnbits-extensions" - name: setup git user run: | @@ -31,12 +31,12 @@ jobs: - name: Create pull request in extensions repo env: GH_TOKEN: ${{ secrets.EXT_GITHUB }} - repo_name: '${{ github.event.repository.name }}' - tag: '${{ github.ref_name }}' - branch: 'update-${{ github.event.repository.name }}-${{ github.ref_name }}' - title: '[UPDATE] ${{ github.event.repository.name }} to ${{ github.ref_name }}' - body: 'https://github.com/lnbits/${{ github.event.repository.name }}/releases/${{ github.ref_name }}' - archive: 'https://github.com/lnbits/${{ github.event.repository.name }}/archive/refs/tags/${{ github.ref_name }}.zip' + repo_name: "${{ github.event.repository.name }}" + tag: "${{ github.ref_name }}" + branch: "update-${{ github.event.repository.name }}-${{ github.ref_name }}" + title: "[UPDATE] ${{ github.event.repository.name }} to ${{ github.ref_name }}" + body: "https://github.com/lnbits/${{ github.event.repository.name }}/releases/${{ github.ref_name }}" + archive: "https://github.com/lnbits/${{ github.event.repository.name }}/archive/refs/tags/${{ github.ref_name }}.zip" run: | cd lnbits-extensions git checkout -b $branch diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1537439..e720631 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: '3.9' + python-version: "3.9" - name: Install dependencies run: | diff --git a/__init__.py b/__init__.py index 3cfa20c..86e59d7 100644 --- a/__init__.py +++ b/__init__.py @@ -45,7 +45,7 @@ def nwcprovider_start(): __all__ = [ "db", "nwcprovider_ext", - "nwcprovider_start", "nwcprovider_static_files", + "nwcprovider_start", "nwcprovider_stop", ] diff --git a/migrations.py b/migrations.py index da5fdd8..fd12650 100644 --- a/migrations.py +++ b/migrations.py @@ -87,13 +87,12 @@ async def m004_default_config2(db): Default config """ await db.execute( - """ + """ INSERT INTO nwcprovider.config (key, value) VALUES ('relay_alias', :value) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value; """, - {"value": ""}, - ) - + {"value": ""}, + ) async def m005_key_last_used(db): """ diff --git a/nwcp.py b/nwcp.py index ded5999..4a5023a 100644 --- a/nwcp.py +++ b/nwcp.py @@ -4,8 +4,7 @@ import hashlib import json import random import time -from collections.abc import Awaitable -from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from typing import Any, Awaitable, Callable, Dict, List, Optional, Tuple, Union import secp256k1 import websockets.client as websockets diff --git a/static/js/admin.js b/static/js/admin.js index 3e88718..afacb7e 100644 --- a/static/js/admin.js +++ b/static/js/admin.js @@ -1,81 +1,87 @@ window.app = Vue.createApp({ - el: '#vue', + el: "#vue", mixins: [windowMixin], - delimiters: ['${', '}'], + delimiters: ["${", "}"], data: function () { return { - config: {}, + entries: [], columns: [ { - name: 'key', + name: "key", required: true, - label: 'Key', - align: 'left', - field: row => row.key, - sortable: true + label: "Key", + align: "left", + field: (row) => row.key, + sortable: true, }, { - name: 'value', + name: "value", required: true, - label: 'Value', - align: 'left', - field: row => row.value, - sortable: true - } - ] - } + label: "Value", + align: "left", + field: (row) => row.value, + sortable: true, + }, + ], + }; }, methods: { fetchConfig() { - this.config = {} + this.entries = []; LNbits.api .request( - 'GET', - '/nwcprovider/api/v1/config', - this.g.user.wallets[0].adminkey + "GET", + "/nwcprovider/api/v1/config", + this.g.user.wallets[0].adminkey, ) - .then(response => { - this.config = response.data - console.log('Config fetched:', this.config) + .then((response) => { + const newEntries = []; + for (const [key, value] of Object.entries(response.data)) { + newEntries.push({ + key: key, + value: value, + }); + } + this.entries.length = 0; + this.entries.push(...newEntries); }) .catch(function (error) { - console.error('Error fetching config:', error) - }) + console.error("Error fetching config:", error); + }); }, async saveConfig() { - const data = {} - for (const [key, value] of Object.entries(this.config)) { - data[key] = value + const data = {}; + for (const entry of this.entries) { + data[entry.key] = entry.value; } - console.log('Saving config:', data) try { const response = await LNbits.api.request( - 'POST', - '/nwcprovider/api/v1/config', + "POST", + "/nwcprovider/api/v1/config", this.g.user.wallets[0].adminkey, - data - ) + data, + ); Quasar.Notify.create({ - type: 'positive', - message: 'Config saved!' - }) + type: "positive", + message: "Config saved!", + }); Quasar.Notify.create({ - type: 'warning', + type: "warning", message: - 'You need to restart the server for the changes to take effect!' - }) + "You need to restart the server for the changes to take effect!", + }); } catch (error) { Quasar.Notify.create({ - type: 'negative', - message: 'Error saving config: ' + String(error) - }) - console.error('Error saving config:', error) + type: "negative", + message: "Error saving config: " + String(error), + }); + console.error("Error saving config:", error); } - } + }, }, created: function () { - this.fetchConfig() - } -}) + this.fetchConfig(); + }, +}); diff --git a/static/js/index.js b/static/js/index.js index d593469..e8a4ec1 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -1,7 +1,7 @@ window.app = Vue.createApp({ - el: '#vue', + el: "#vue", mixins: [windowMixin], - delimiters: ['${', '}'], + delimiters: ["${", "}"], data: function () { return { selectedWallet: null, @@ -10,244 +10,244 @@ window.app = Vue.createApp({ nwcsTable: { columns: [ { - name: 'description', - align: 'left', - label: 'Description', - field: 'description' + name: "description", + align: "left", + label: "Description", + field: "description", }, - {name: 'status', align: 'left', label: 'Status', field: 'status'}, + { name: "status", align: "left", label: "Status", field: "status" }, { - name: 'last_used', - align: 'left', - label: 'Last used', - field: 'last_used' + name: "last_used", + align: "left", + label: "Last used", + field: "last_used", }, { - name: 'created_at', - align: 'left', - label: 'Created', - field: 'created_at' + name: "created_at", + align: "left", + label: "Created", + field: "created_at", }, { - name: 'expires_at', - align: 'left', - label: 'Expires', - field: 'expires_at' - } + name: "expires_at", + align: "left", + label: "Expires", + field: "expires_at", + }, ], pagination: { - rowsPerPage: 10 - } + rowsPerPage: 10, + }, }, connectDialog: { show: false, - data: {} + data: {}, }, pairingDialog: { show: false, data: { - pairingUrl: '' - } + pairingUrl: "", + }, }, pairingQrDialog: { show: false, data: { - pairingUrl: '' - } + pairingUrl: "", + }, }, connectionInfoDialog: { show: false, - data: {} - } - } + data: {}, + }, + }; }, methods: { showConnectDialog() { - const wallet = this.getWallet() + const wallet = this.getWallet(); if (!wallet) { Quasar.Notify.create({ - type: 'negative', - message: 'Please select a wallet first' - }) - return + type: "negative", + message: "Please select a wallet first", + }); + return; } else { - this.connectDialog.show = true + this.connectDialog.show = true; } }, openConnectionInfoDialog(data) { - this.connectionInfoDialog.data = data - this.connectionInfoDialog.show = true + this.connectionInfoDialog.data = data; + this.connectionInfoDialog.show = true; }, closeConnectionInfoDialog() { - this.connectionInfoDialog.show = false + this.connectionInfoDialog.show = false; }, openPairingUrl() { - const url = this.pairingDialog.data.pairingUrl - if (url) window.open(url, '_blank') + const url = this.pairingDialog.data.pairingUrl; + if (url) window.open(url, "_blank"); }, go(url) { - window.open(url, '_blank') + window.open(url, "_blank"); }, async copyPairingUrl() { - const url = this.pairingDialog.data.pairingUrl + const url = this.pairingDialog.data.pairingUrl; if (url) { try { - await navigator.clipboard.writeText(url) + await navigator.clipboard.writeText(url); Quasar.Notify.create({ - type: 'positive', - message: 'URL copied to clipboard' - }) + type: "positive", + message: "URL copied to clipboard", + }); } catch (err) { Quasar.Notify.create({ - type: 'negative', - message: 'Failed to copy URL.' - }) + type: "negative", + message: "Failed to copy URL.", + }); } } }, showPairingQR() { - this.pairingQrDialog.data.pairingUrl = this.pairingDialog.data.pairingUrl - this.pairingQrDialog.show = true + this.pairingQrDialog.data.pairingUrl = this.pairingDialog.data.pairingUrl; + this.pairingQrDialog.show = true; }, closePairingQrDialog() { - this.pairingQrDialog.show = false + this.pairingQrDialog.show = false; }, loadConnectDialogData() { this.connectDialog.data = { - description: '', + description: "", expires_at: Date.now() + 1000 * 60 * 60 * 24 * 7, neverExpires: true, permissions: [], - budgets: [] - } + budgets: [], + }; for (const permission of this.nodePermissions) { this.connectDialog.data.permissions.push({ key: permission.key, name: permission.name, - value: permission.value - }) + value: permission.value, + }); } }, deleteBudget(index) { - this.connectDialog.data.budgets.splice(index, 1) + this.connectDialog.data.budgets.splice(index, 1); }, addBudget() { this.connectDialog.data.budgets.push({ budget_sats: 1000, used_budget_sats: 0, created_at: new Date(new Date().setHours(0, 0, 0, 0)).getTime() / 1000, - expiration: 'never' - }) + expiration: "never", + }); }, closeConnectDialog() { - this.connectDialog.show = false - this.loadConnectDialogData() + this.connectDialog.show = false; + this.loadConnectDialogData(); }, getWallet: function () { - let wallet = undefined + let wallet = undefined; for (let i = 0; i < this.g.user.wallets.length; i++) { if (this.g.user.wallets[i].id == this.selectedWallet) { - wallet = this.g.user.wallets[i] - break + wallet = this.g.user.wallets[i]; + break; } } - return wallet + return wallet; }, async generateKeyPair() { while (!window.NobleSecp256k1) { - await new Promise(resolve => setTimeout(resolve, 1)) + await new Promise((resolve) => setTimeout(resolve, 1)); } - const privKeyBytes = window.NobleSecp256k1.utils.randomPrivateKey() - const pubKeyBytes = window.NobleSecp256k1.getPublicKey(privKeyBytes) + const privKeyBytes = window.NobleSecp256k1.utils.randomPrivateKey(); + const pubKeyBytes = window.NobleSecp256k1.getPublicKey(privKeyBytes); const out = { privKeyBytes: privKeyBytes, pubKeyBytes: pubKeyBytes, privKey: window.NobleSecp256k1.etc.bytesToHex(privKeyBytes), - pubKey: window.NobleSecp256k1.etc.bytesToHex(pubKeyBytes.slice(1)) - } - return out + pubKey: window.NobleSecp256k1.etc.bytesToHex(pubKeyBytes.slice(1)), + }; + return out; }, deleteNWC: async function (pubkey) { Quasar.Dialog.create({ - title: 'Confirm Deletion', - message: 'Are you sure you want to delete this connection?', + title: "Confirm Deletion", + message: "Are you sure you want to delete this connection?", cancel: true, - persistent: true + persistent: true, }) .onOk(async () => { try { - const wallet = this.getWallet() + const wallet = this.getWallet(); const response = await LNbits.api.request( - 'DELETE', + "DELETE", `/nwcprovider/api/v1/nwc/${pubkey}`, - wallet.adminkey - ) - this.loadNwcs() + wallet.adminkey, + ); + this.loadNwcs(); Quasar.Notify.create({ - type: 'positive', - message: 'Deleted successfully' - }) + type: "positive", + message: "Deleted successfully", + }); } catch (error) { - LNbits.utils.notifyApiError(error) + LNbits.utils.notifyApiError(error); } }) .onCancel(() => { // User canceled the operation - }) + }); }, loadNwcs: async function () { - const wallet = this.getWallet() + const wallet = this.getWallet(); if (!wallet) { - this.nwcs = [] - return + this.nwcs = []; + return; } try { const response = await LNbits.api.request( - 'GET', - '/nwcprovider/api/v1/nwc?include_expired=true&calculate_spent_budget=true', - wallet.adminkey - ) - this.nwcs = response.data + "GET", + "/nwcprovider/api/v1/nwc?include_expired=true&calculate_spent_budget=true", + wallet.adminkey, + ); + this.nwcs = response.data; } catch (error) { - this.nwcs = [] + this.nwcs = []; } try { const response = await LNbits.api.request( - 'GET', - '/nwcprovider/api/v1/permissions', - wallet.adminkey - ) - const permissions = [] + "GET", + "/nwcprovider/api/v1/permissions", + wallet.adminkey, + ); + const permissions = []; for (const [key, value] of Object.entries(response.data)) { permissions.push({ key: key, name: value.name, - value: value.default - }) + value: value.default, + }); } - this.nodePermissions = permissions + this.nodePermissions = permissions; } catch (error) { - Lnbits.utils.notifyApiError(error) + Lnbits.utils.notifyApiError(error); } - this.loadConnectDialogData() - const newTableEntries = [] + this.loadConnectDialogData(); + const newTableEntries = []; for (const nwc of this.nwcs) { const t = Quasar.date.formatDate( new Date(nwc.data.created_at * 1000), - 'YYYY-MM-DD HH:mm' - ) + "YYYY-MM-DD HH:mm", + ); const e = nwc.data.expires_at > 0 ? Quasar.date.formatDate( new Date(nwc.data.expires_at * 1000), - 'YYYY-MM-DD HH:mm' + "YYYY-MM-DD HH:mm", ) - : 'Never' + : "Never"; const l = Quasar.date.formatDate( new Date(nwc.data.last_used * 1000), - 'YYYY-MM-DD HH:mm' - ) + "YYYY-MM-DD HH:mm", + ); const nwcTableEntry = { description: nwc.data.description, created_at: t, @@ -256,130 +256,131 @@ window.app = Vue.createApp({ pubkey: nwc.data.pubkey, permissions: nwc.data.permissions, budgets: [], - status: 'Active' - } + status: "Active", + }; if ( nwc.data.expires_at > 0 && nwc.data.expires_at < new Date().getTime() / 1000 ) { - nwcTableEntry.status = 'Expired' + nwcTableEntry.status = "Expired"; } for (const budget of nwc.budgets) { const createdAt = Quasar.date.formatDate( new Date(budget.created_at * 1000), - 'YYYY-MM-DD HH:mm' - ) - let refreshWindow = budget.refresh_window + "YYYY-MM-DD HH:mm", + ); + let refreshWindow = budget.refresh_window; if (refreshWindow <= 0) { - refreshWindow = 'Never' + refreshWindow = "Never"; } else if (refreshWindow == 60 * 60 * 24) { - refreshWindow = 'Daily' + refreshWindow = "Daily"; } else if (refreshWindow == 60 * 60 * 24 * 7) { - refreshWindow = 'Weekly' + refreshWindow = "Weekly"; } else if (refreshWindow == 60 * 60 * 24 * 30) { - refreshWindow = 'Monthly' + refreshWindow = "Monthly"; } else if (refreshWindow == 60 * 60 * 24 * 365) { - refreshWindow = 'Yearly' + refreshWindow = "Yearly"; } nwcTableEntry.budgets.push({ budget_sats: budget.budget_msats / 1000, used_budget_sats: budget.used_budget_msats / 1000, created_at: createdAt, - refresh_window: refreshWindow - }) + refresh_window: refreshWindow, + }); } - newTableEntries.push(nwcTableEntry) + newTableEntries.push(nwcTableEntry); } - this.nwcEntries = newTableEntries + this.nwcEntries = newTableEntries; }, closePairingDialog() { - this.pairingDialog.show = false + this.pairingDialog.show = false; }, async showPairingDialog(secret) { let response = await LNbits.api.request( - 'GET', - '/nwcprovider/api/v1/pairing/{SECRET}' - ) - response = response.data - response = response.replace('{SECRET}', secret) - this.pairingDialog.data.pairingUrl = response - this.pairingDialog.show = true + "GET", + "/nwcprovider/api/v1/pairing/{SECRET}", + ); + response = response.data; + response = response.replace("{SECRET}", secret); + this.pairingDialog.data.pairingUrl = response; + this.pairingDialog.show = true; }, async confirmConnectDialog() { - const keyPair = await this.generateKeyPair() + const keyPair = await this.generateKeyPair(); // timestamp - let expires_at = 0 + let expires_at = 0; if (!this.connectDialog.data.neverExpires) { expires_at = - new Date(this.connectDialog.data.expires_at).getTime() / 1000 + new Date(this.connectDialog.data.expires_at).getTime() / 1000; } const data = { permissions: [], description: this.connectDialog.data.description, expires_at: expires_at, - budgets: [] - } + budgets: [], + }; for (const permission of this.connectDialog.data.permissions) { - if (permission.value) data.permissions.push(permission.key) + if (permission.value) data.permissions.push(permission.key); } for (const budget of this.connectDialog.data.budgets) { - const budget_msats = budget.budget_sats * 1000 - let refresh_window = 0 + const budget_msats = budget.budget_sats * 1000; + let refresh_window = 0; switch (budget.expiry) { - case 'Daily': - refresh_window = 60 * 60 * 24 - break - case 'Weekly': - refresh_window = 60 * 60 * 24 * 7 - break - case 'Monthly': - refresh_window = 60 * 60 * 24 * 30 - break - case 'Yearly': - refresh_window = 60 * 60 * 24 * 365 - break - case 'Never': - refresh_window = 0 - break + case "Daily": + refresh_window = 60 * 60 * 24; + break; + case "Weekly": + refresh_window = 60 * 60 * 24 * 7; + break; + case "Monthly": + refresh_window = 60 * 60 * 24 * 30; + break; + case "Yearly": + refresh_window = 60 * 60 * 24 * 365; + break; + case "Never": + refresh_window = 0; + break; } data.budgets.push({ budget_msats: budget_msats, refresh_window: refresh_window, - created_at: new Date(new Date().setHours(0, 0, 0, 0)).getTime() / 1000 - }) + created_at: + new Date(new Date().setHours(0, 0, 0, 0)).getTime() / 1000, + }); } - const wallet = this.getWallet() + const wallet = this.getWallet(); try { const response = await LNbits.api.request( - 'PUT', - '/nwcprovider/api/v1/nwc/' + keyPair.pubKey, + "PUT", + "/nwcprovider/api/v1/nwc/" + keyPair.pubKey, wallet.adminkey, - data - ) - this.closeConnectDialog() + data, + ); + this.closeConnectDialog(); if ( !response.data || !response.data.data || !response.data.data.pubkey ) { - LNbits.utils.notifyApiError('Error creating nwc pairing') - return + LNbits.utils.notifyApiError("Error creating nwc pairing"); + return; } - this.showPairingDialog(keyPair.privKey) + this.showPairingDialog(keyPair.privKey); } catch (error) { - LNbits.utils.notifyApiError(error) + LNbits.utils.notifyApiError(error); } - this.loadNwcs() - } + this.loadNwcs(); + }, }, created: function () { - this.loadNwcs() + this.loadNwcs(); }, watch: { selectedWallet(newValue, oldValue) { - this.loadNwcs() - } - } -}) + this.loadNwcs(); + }, + }, +}); diff --git a/static/js/noble-secp256k1.min.js b/static/js/noble-secp256k1.min.js index c996cd2..af328c8 100644 --- a/static/js/noble-secp256k1.min.js +++ b/static/js/noble-secp256k1.min.js @@ -4,93 +4,93 @@ const B256 = 2n ** 256n, N = B256 - 0x14551231950b75fc4402da1732fc9bebfn, Gx = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798n, Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8n, - CURVE = {p: P, n: N, a: 0n, b: 7n, Gx, Gy}, + CURVE = { p: P, n: N, a: 0n, b: 7n, Gx, Gy }, fLen = 32, - crv = t => mod(mod(t * t) * t + CURVE.b), - err = (t = '') => { - throw new Error(t) + crv = (t) => mod(mod(t * t) * t + CURVE.b), + err = (t = "") => { + throw new Error(t); }, - big = t => 'bigint' == typeof t, - str = t => 'string' == typeof t, - fe = t => big(t) && 0n < t && t < P, - ge = t => big(t) && 0n < t && t < N, - isu8 = t => + big = (t) => "bigint" == typeof t, + str = (t) => "string" == typeof t, + fe = (t) => big(t) && 0n < t && t < P, + ge = (t) => big(t) && 0n < t && t < N, + isu8 = (t) => t instanceof Uint8Array || - (null != t && 'object' == typeof t && 'Uint8Array' === t.constructor.name), + (null != t && "object" == typeof t && "Uint8Array" === t.constructor.name), au8 = (t, n) => - !isu8(t) || ('number' == typeof n && n > 0 && t.length !== n) - ? err('Uint8Array expected') + !isu8(t) || ("number" == typeof n && n > 0 && t.length !== n) + ? err("Uint8Array expected") : t, - u8n = t => new Uint8Array(t), + u8n = (t) => new Uint8Array(t), toU8 = (t, n) => au8(str(t) ? h2b(t) : u8n(au8(t)), n), mod = (t, n = P) => { - let e = t % n - return e >= 0n ? e : n + e + let e = t % n; + return e >= 0n ? e : n + e; }, - isPoint = t => (t instanceof Point ? t : err('Point expected')) + isPoint = (t) => (t instanceof Point ? t : err("Point expected")); class Point { constructor(t, n, e) { - ;((this.px = t), (this.py = n), (this.pz = e)) + (this.px = t), (this.py = n), (this.pz = e); } static fromAffine(t) { - return 0n === t.x && 0n === t.y ? Point.ZERO : new Point(t.x, t.y, 1n) + return 0n === t.x && 0n === t.y ? Point.ZERO : new Point(t.x, t.y, 1n); } static fromHex(t) { - let n + let n; const e = (t = toU8(t))[0], r = t.subarray(1), o = slcNum(r, 0, 32), - i = t.length + i = t.length; if (33 === i && [2, 3].includes(e)) { - fe(o) || err('Point hex invalid: x not FE') - let t = sqrt(crv(o)) - ;(!(1 & ~e) !== (1n === (1n & t)) && (t = mod(-t)), - (n = new Point(o, t, 1n))) + fe(o) || err("Point hex invalid: x not FE"); + let t = sqrt(crv(o)); + !(1 & ~e) !== (1n === (1n & t)) && (t = mod(-t)), + (n = new Point(o, t, 1n)); } return ( 65 === i && 4 === e && (n = new Point(o, slcNum(r, 32, 64), 1n)), - n ? n.ok() : err('Point is not on curve') - ) + n ? n.ok() : err("Point is not on curve") + ); } static fromPrivateKey(t) { - return G.mul(toPriv(t)) + return G.mul(toPriv(t)); } get x() { - return this.aff().x + return this.aff().x; } get y() { - return this.aff().y + return this.aff().y; } equals(t) { - const {px: n, py: e, pz: r} = this, - {px: o, py: i, pz: s} = isPoint(t), + const { px: n, py: e, pz: r } = this, + { px: o, py: i, pz: s } = isPoint(t), a = mod(n * s), c = mod(o * r), d = mod(e * s), - u = mod(i * r) - return a === c && d === u + u = mod(i * r); + return a === c && d === u; } negate() { - return new Point(this.px, mod(-this.py), this.pz) + return new Point(this.px, mod(-this.py), this.pz); } double() { - return this.add(this) + return this.add(this); } add(t) { - const {px: n, py: e, pz: r} = this, - {px: o, py: i, pz: s} = isPoint(t), - {a, b: c} = CURVE + const { px: n, py: e, pz: r } = this, + { px: o, py: i, pz: s } = isPoint(t), + { a, b: c } = CURVE; let d = 0n, u = 0n, - m = 0n - const h = mod(3n * c) + m = 0n; + const h = mod(3n * c); let l = mod(n * o), y = mod(e * i), f = mod(r * s), p = mod(n + e), - b = mod(o + i) - ;((p = mod(p * b)), (b = mod(l + y)), (p = mod(p - b)), (b = mod(n + r))) - let g = mod(o + s) + b = mod(o + i); + (p = mod(p * b)), (b = mod(l + y)), (p = mod(p - b)), (b = mod(n + r)); + let g = mod(o + s); return ( (b = mod(b * g)), (g = mod(l + f)), @@ -123,301 +123,300 @@ class Point { (m = mod(g * m)), (m = mod(m + l)), new Point(d, u, m) - ) + ); } mul(t, n = !0) { - if (!n && 0n === t) return I - if ((ge(t) || err('invalid scalar'), this.equals(G))) return wNAF(t).p + if (!n && 0n === t) return I; + if ((ge(t) || err("invalid scalar"), this.equals(G))) return wNAF(t).p; let e = I, - r = G + r = G; for (let o = this; t > 0n; o = o.double(), t >>= 1n) - 1n & t ? (e = e.add(o)) : n && (r = r.add(o)) - return e + 1n & t ? (e = e.add(o)) : n && (r = r.add(o)); + return e; } mulAddQUns(t, n, e) { - return this.mul(n, !1).add(t.mul(e, !1)).ok() + return this.mul(n, !1).add(t.mul(e, !1)).ok(); } toAffine() { - const {px: t, py: n, pz: e} = this - if (this.equals(I)) return {x: 0n, y: 0n} - if (1n === e) return {x: t, y: n} - const r = inv(e) + const { px: t, py: n, pz: e } = this; + if (this.equals(I)) return { x: 0n, y: 0n }; + if (1n === e) return { x: t, y: n }; + const r = inv(e); return ( - 1n !== mod(e * r) && err('invalid inverse'), - {x: mod(t * r), y: mod(n * r)} - ) + 1n !== mod(e * r) && err("invalid inverse"), + { x: mod(t * r), y: mod(n * r) } + ); } assertValidity() { - const {x: t, y: n} = this.aff() + const { x: t, y: n } = this.aff(); return ( - (fe(t) && fe(n)) || err('Point invalid: x or y'), - mod(n * n) === crv(t) ? this : err('Point invalid: not on curve') - ) + (fe(t) && fe(n)) || err("Point invalid: x or y"), + mod(n * n) === crv(t) ? this : err("Point invalid: not on curve") + ); } multiply(t) { - return this.mul(t) + return this.mul(t); } aff() { - return this.toAffine() + return this.toAffine(); } ok() { - return this.assertValidity() + return this.assertValidity(); } toHex(t = !0) { - const {x: n, y: e} = this.aff() + const { x: n, y: e } = this.aff(); return ( - (t ? (0n === (1n & e) ? '02' : '03') : '04') + n2h(n) + (t ? '' : n2h(e)) - ) + (t ? (0n === (1n & e) ? "02" : "03") : "04") + n2h(n) + (t ? "" : n2h(e)) + ); } toRawBytes(t = !0) { - return h2b(this.toHex(t)) + return h2b(this.toHex(t)); } } -;((Point.BASE = new Point(Gx, Gy, 1n)), (Point.ZERO = new Point(0n, 1n, 0n))) -const {BASE: G, ZERO: I} = Point, - padh = (t, n) => t.toString(16).padStart(n, '0'), - b2h = t => +(Point.BASE = new Point(Gx, Gy, 1n)), (Point.ZERO = new Point(0n, 1n, 0n)); +const { BASE: G, ZERO: I } = Point, + padh = (t, n) => t.toString(16).padStart(n, "0"), + b2h = (t) => Array.from(t) - .map(t => padh(t, 2)) - .join(''), - h2b = t => { - const n = t.length - ;(!str(t) || n % 2) && err('hex invalid 1') - const e = u8n(n / 2) + .map((t) => padh(t, 2)) + .join(""), + h2b = (t) => { + const n = t.length; + (!str(t) || n % 2) && err("hex invalid 1"); + const e = u8n(n / 2); for (let n = 0; n < e.length; n++) { const r = 2 * n, o = t.slice(r, r + 2), - i = Number.parseInt(o, 16) - ;((Number.isNaN(i) || i < 0) && err('hex invalid 2'), (e[n] = i)) + i = Number.parseInt(o, 16); + (Number.isNaN(i) || i < 0) && err("hex invalid 2"), (e[n] = i); } - return e + return e; }, - b2n = t => BigInt('0x' + (b2h(t) || '0')), + b2n = (t) => BigInt("0x" + (b2h(t) || "0")), slcNum = (t, n, e) => b2n(t.slice(n, e)), - n2b = t => - big(t) && t >= 0n && t < B256 ? h2b(padh(t, 64)) : err('bigint expected'), - n2h = t => b2h(n2b(t)), + n2b = (t) => + big(t) && t >= 0n && t < B256 ? h2b(padh(t, 64)) : err("bigint expected"), + n2h = (t) => b2h(n2b(t)), concatB = (...t) => { - const n = u8n(t.reduce((t, n) => t + au8(n).length, 0)) - let e = 0 + const n = u8n(t.reduce((t, n) => t + au8(n).length, 0)); + let e = 0; return ( - t.forEach(t => { - ;(n.set(t, e), (e += t.length)) + t.forEach((t) => { + n.set(t, e), (e += t.length); }), n - ) + ); }, inv = (t, n = P) => { - ;(0n === t || n <= 0n) && err('no inverse n=' + t + ' mod=' + n) + (0n === t || n <= 0n) && err("no inverse n=" + t + " mod=" + n); let e = mod(t, n), r = n, o = 0n, i = 1n, s = 1n, - a = 0n + a = 0n; for (; 0n !== e; ) { const t = r / e, n = r % e, c = o - s * t, - d = i - a * t - ;((r = e), (e = n), (o = s), (i = a), (s = c), (a = d)) + d = i - a * t; + (r = e), (e = n), (o = s), (i = a), (s = c), (a = d); } - return 1n === r ? mod(o, n) : err('no inverse') + return 1n === r ? mod(o, n) : err("no inverse"); }, - sqrt = t => { - let n = 1n + sqrt = (t) => { + let n = 1n; for (let e = t, r = (P + 1n) / 4n; r > 0n; r >>= 1n) - (1n & r && (n = (n * e) % P), (e = (e * e) % P)) - return mod(n * n) === t ? n : err('sqrt invalid') + 1n & r && (n = (n * e) % P), (e = (e * e) % P); + return mod(n * n) === t ? n : err("sqrt invalid"); }, - toPriv = t => ( + toPriv = (t) => ( big(t) || (t = b2n(toU8(t, 32))), - ge(t) ? t : err('private key out of range') + ge(t) ? t : err("private key out of range") ), - moreThanHalfN = t => t > N >> 1n, - getPublicKey = (t, n = !0) => Point.fromPrivateKey(t).toRawBytes(n) + moreThanHalfN = (t) => t > N >> 1n, + getPublicKey = (t, n = !0) => Point.fromPrivateKey(t).toRawBytes(n); class Signature { constructor(t, n, e) { - ;((this.r = t), (this.s = n), (this.recovery = e), this.assertValidity()) + (this.r = t), (this.s = n), (this.recovery = e), this.assertValidity(); } static fromCompact(t) { return ( - (t = toU8(t, 64)), - new Signature(slcNum(t, 0, 32), slcNum(t, 32, 64)) - ) + (t = toU8(t, 64)), new Signature(slcNum(t, 0, 32), slcNum(t, 32, 64)) + ); } assertValidity() { - return ge(this.r) && ge(this.s) ? this : err() + return ge(this.r) && ge(this.s) ? this : err(); } addRecoveryBit(t) { - return new Signature(this.r, this.s, t) + return new Signature(this.r, this.s, t); } hasHighS() { - return moreThanHalfN(this.s) + return moreThanHalfN(this.s); } normalizeS() { return this.hasHighS() ? new Signature(this.r, mod(this.s, N), this.recovery) - : this + : this; } recoverPublicKey(t) { - const {r: n, s: e, recovery: r} = this - ;[0, 1, 2, 3].includes(r) || err('recovery id invalid') + const { r: n, s: e, recovery: r } = this; + [0, 1, 2, 3].includes(r) || err("recovery id invalid"); const o = bits2int_modN(toU8(t, 32)), - i = 2 === r || 3 === r ? n + N : n - i >= P && err('q.x invalid') - const s = 1 & r ? '03' : '02', + i = 2 === r || 3 === r ? n + N : n; + i >= P && err("q.x invalid"); + const s = 1 & r ? "03" : "02", a = Point.fromHex(s + n2h(i)), c = inv(i, N), d = mod(-o * c, N), - u = mod(e * c, N) - return G.mulAddQUns(a, d, u) + u = mod(e * c, N); + return G.mulAddQUns(a, d, u); } toCompactRawBytes() { - return h2b(this.toCompactHex()) + return h2b(this.toCompactHex()); } toCompactHex() { - return n2h(this.r) + n2h(this.s) + return n2h(this.r) + n2h(this.s); } } -const bits2int = t => { +const bits2int = (t) => { const n = 8 * t.length - 256, - e = b2n(t) - return n > 0 ? e >> BigInt(n) : e + e = b2n(t); + return n > 0 ? e >> BigInt(n) : e; }, - bits2int_modN = t => mod(bits2int(t), N), - i2o = t => n2b(t), + bits2int_modN = (t) => mod(bits2int(t), N), + i2o = (t) => n2b(t), cr = () => - 'object' == typeof globalThis && 'crypto' in globalThis + "object" == typeof globalThis && "crypto" in globalThis ? globalThis.crypto - : void 0 -let _hmacSync -const optS = {lowS: !0}, - optV = {lowS: !0}, + : void 0; +let _hmacSync; +const optS = { lowS: !0 }, + optV = { lowS: !0 }, prepSig = (t, n, e = optS) => { - ;['der', 'recovered', 'canonical'].some(t => t in e) && - err('sign() legacy options not supported') - let {lowS: r} = e - null == r && (r = !0) + ["der", "recovered", "canonical"].some((t) => t in e) && + err("sign() legacy options not supported"); + let { lowS: r } = e; + null == r && (r = !0); const o = bits2int_modN(toU8(t)), i = i2o(o), s = toPriv(n), - a = [i2o(s), i] - let c = e.extraEntropy + a = [i2o(s), i]; + let c = e.extraEntropy; if (c) { - !0 === c && (c = etc.randomBytes(32)) - const t = toU8(c) - ;(32 !== t.length && err(), a.push(t)) + !0 === c && (c = etc.randomBytes(32)); + const t = toU8(c); + 32 !== t.length && err(), a.push(t); } - const d = o + const d = o; return { seed: concatB(...a), - k2sig: t => { - const n = bits2int(t) - if (!ge(n)) return + k2sig: (t) => { + const n = bits2int(t); + if (!ge(n)) return; const e = inv(n, N), o = G.mul(n).aff(), - i = mod(o.x, N) - if (0n === i) return - const a = mod(e * mod(d + mod(s * i, N), N), N) - if (0n === a) return + i = mod(o.x, N); + if (0n === i) return; + const a = mod(e * mod(d + mod(s * i, N), N), N); + if (0n === a) return; let c = a, - u = (o.x === i ? 0 : 2) | Number(1n & o.y) + u = (o.x === i ? 0 : 2) | Number(1n & o.y); return ( r && moreThanHalfN(a) && ((c = mod(-a, N)), (u ^= 1)), new Signature(i, c, u) - ) - } - } - } + ); + }, + }; + }; function hmacDrbg(t) { let n = u8n(32), e = u8n(32), - r = 0 + r = 0; const o = () => { - ;(n.fill(1), e.fill(0), (r = 0)) + n.fill(1), e.fill(0), (r = 0); }, - i = 'drbg: tried 1000 values' + i = "drbg: tried 1000 values"; if (t) { const t = (...t) => etc.hmacSha256Async(e, n, ...t), s = async (r = u8n()) => { - ;((e = await t(u8n([0]), r)), + (e = await t(u8n([0]), r)), (n = await t()), - 0 !== r.length && ((e = await t(u8n([1]), r)), (n = await t()))) + 0 !== r.length && ((e = await t(u8n([1]), r)), (n = await t())); }, - a = async () => (r++ >= 1e3 && err(i), (n = await t()), n) + a = async () => (r++ >= 1e3 && err(i), (n = await t()), n); return async (t, n) => { - let e - for (o(), await s(t); !(e = n(await a())); ) await s() - return (o(), e) - } + let e; + for (o(), await s(t); !(e = n(await a())); ) await s(); + return o(), e; + }; } { const t = (...t) => { - const r = _hmacSync - return (r || err('etc.hmacSha256Sync not set'), r(e, n, ...t)) + const r = _hmacSync; + return r || err("etc.hmacSha256Sync not set"), r(e, n, ...t); }, s = (r = u8n()) => { - ;((e = t(u8n([0]), r)), + (e = t(u8n([0]), r)), (n = t()), - 0 !== r.length && ((e = t(u8n([1]), r)), (n = t()))) + 0 !== r.length && ((e = t(u8n([1]), r)), (n = t())); }, - a = () => (r++ >= 1e3 && err(i), (n = t()), n) + a = () => (r++ >= 1e3 && err(i), (n = t()), n); return (t, n) => { - let e - for (o(), s(t); !(e = n(a())); ) s() - return (o(), e) - } + let e; + for (o(), s(t); !(e = n(a())); ) s(); + return o(), e; + }; } } const signAsync = async (t, n, e = optS) => { - const {seed: r, k2sig: o} = prepSig(t, n, e) - return hmacDrbg(!0)(r, o) + const { seed: r, k2sig: o } = prepSig(t, n, e); + return hmacDrbg(!0)(r, o); }, sign = (t, n, e = optS) => { - const {seed: r, k2sig: o} = prepSig(t, n, e) - return hmacDrbg(!1)(r, o) + const { seed: r, k2sig: o } = prepSig(t, n, e); + return hmacDrbg(!1)(r, o); }, verify = (t, n, e, r = optV) => { let o, i, s, - {lowS: a} = r - ;(null == a && (a = !0), - 'strict' in r && err('verify() legacy options not supported')) - const c = t && 'object' == typeof t && 'r' in t - c || 64 === toU8(t).length || err('signature must be 64 bytes') + { lowS: a } = r; + null == a && (a = !0), + "strict" in r && err("verify() legacy options not supported"); + const c = t && "object" == typeof t && "r" in t; + c || 64 === toU8(t).length || err("signature must be 64 bytes"); try { - ;((o = c + (o = c ? new Signature(t.r, t.s).assertValidity() : Signature.fromCompact(t)), (i = bits2int_modN(toU8(n))), - (s = e instanceof Point ? e.ok() : Point.fromHex(e))) + (s = e instanceof Point ? e.ok() : Point.fromHex(e)); } catch (t) { - return !1 + return !1; } - if (!o) return !1 - const {r: d, s: u} = o - if (a && moreThanHalfN(u)) return !1 - let m + if (!o) return !1; + const { r: d, s: u } = o; + if (a && moreThanHalfN(u)) return !1; + let m; try { const t = inv(u, N), n = mod(i * t, N), - e = mod(d * t, N) - m = G.mulAddQUns(s, n, e).aff() + e = mod(d * t, N); + m = G.mulAddQUns(s, n, e).aff(); } catch (t) { - return !1 + return !1; } - if (!m) return !1 - return mod(m.x, N) === d + if (!m) return !1; + return mod(m.x, N) === d; }, getSharedSecret = (t, n, e = !0) => Point.fromHex(n).mul(toPriv(t)).toRawBytes(e), - hashToPrivateKey = t => { - ;((t = toU8(t)).length < 40 || t.length > 1024) && - err('expected proper params') - const n = mod(b2n(t), N - 1n) + 1n - return n2b(n) + hashToPrivateKey = (t) => { + ((t = toU8(t)).length < 40 || t.length > 1024) && + err("expected proper params"); + const n = mod(b2n(t), N - 1n) + 1n; + return n2b(n); }, etc = { hexToBytes: h2b, @@ -429,84 +428,84 @@ const signAsync = async (t, n, e = optS) => { invert: inv, hmacSha256Async: async (t, ...n) => { const e = cr(), - r = e && e.subtle - if (!r) return err('etc.hmacSha256Async not set') + r = e && e.subtle; + if (!r) return err("etc.hmacSha256Async not set"); const o = await r.importKey( - 'raw', + "raw", t, - {name: 'HMAC', hash: {name: 'SHA-256'}}, + { name: "HMAC", hash: { name: "SHA-256" } }, !1, - ['sign'] - ) - return u8n(await r.sign('HMAC', o, concatB(...n))) + ["sign"], + ); + return u8n(await r.sign("HMAC", o, concatB(...n))); }, hmacSha256Sync: _hmacSync, hashToPrivateKey, randomBytes: (t = 32) => { - const n = cr() + const n = cr(); return ( (n && n.getRandomValues) || - err('crypto.getRandomValues must be defined'), + err("crypto.getRandomValues must be defined"), n.getRandomValues(u8n(t)) - ) - } + ); + }, }, utils = { normPrivateKeyToScalar: toPriv, - isValidPrivateKey: t => { + isValidPrivateKey: (t) => { try { - return !!toPriv(t) + return !!toPriv(t); } catch (t) { - return !1 + return !1; } }, randomPrivateKey: () => hashToPrivateKey(etc.randomBytes(48)), - precompute: (t = 8, n = G) => (n.multiply(3n), n) - } + precompute: (t = 8, n = G) => (n.multiply(3n), n), + }; Object.defineProperties(etc, { hmacSha256Sync: { configurable: !1, get: () => _hmacSync, set(t) { - _hmacSync || (_hmacSync = t) - } - } -}) + _hmacSync || (_hmacSync = t); + }, + }, +}); const W = 8, precompute = () => { - const t = [] + const t = []; let n = G, - e = n + e = n; for (let r = 0; r < 33; r++) { - ;((e = n), t.push(e)) - for (let r = 1; r < 128; r++) ((e = e.add(n)), t.push(e)) - n = e.double() + (e = n), t.push(e); + for (let r = 1; r < 128; r++) (e = e.add(n)), t.push(e); + n = e.double(); } - return t - } -let Gpows -const wNAF = t => { + return t; + }; +let Gpows; +const wNAF = (t) => { const n = Gpows || (Gpows = precompute()), e = (t, n) => { - let e = n.negate() - return t ? e : n - } + let e = n.negate(); + return t ? e : n; + }; let r = I, - o = G + o = G; const i = BigInt(255), - s = BigInt(8) + s = BigInt(8); for (let a = 0; a < 33; a++) { - const c = 128 * a - let d = Number(t & i) - ;((t >>= s), d > 128 && ((d -= 256), (t += 1n))) + const c = 128 * a; + let d = Number(t & i); + (t >>= s), d > 128 && ((d -= 256), (t += 1n)); const u = c, m = c + Math.abs(d) - 1, h = a % 2 != 0, - l = d < 0 - 0 === d ? (o = o.add(e(h, n[u]))) : (r = r.add(e(l, n[m]))) + l = d < 0; + 0 === d ? (o = o.add(e(h, n[u]))) : (r = r.add(e(l, n[m]))); } - return {p: r, f: o} -} + return { p: r, f: o }; +}; export { getPublicKey, sign, @@ -517,5 +516,5 @@ export { etc, utils, Point as ProjectivePoint, - Signature -} + Signature, +}; diff --git a/templates/nwcprovider/admin.html b/templates/nwcprovider/admin.html index 81576b4..171a6d0 100644 --- a/templates/nwcprovider/admin.html +++ b/templates/nwcprovider/admin.html @@ -6,56 +6,21 @@