From 88c906fd340ff78fb8a5bd4e9fbad579b0ac0ec7 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Tue, 21 Jul 2026 20:56:01 -0400 Subject: [PATCH 1/8] Add BitLogin as the primary sign-in method (gated, unstable) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The login modal now shows -- a portable Nostr identity behind a login name and password -- as the default sign-in path, styled with bitvid's own brand tokens (accent-strong, surfaces, radii). The existing NIP-07 extension, nsec, and NIP-46 remote-signer options still work exactly as before, just collapsed under a new "Other login options" fold instead of a flat "Available methods" grid; nip07 no longer carries the stale "Recommended" eyebrow now that BitLogin has that role. BitLogin isn't a single button-click-then-await-a-promise flow like the other providers -- it's a whole multi-screen sign-in/create/recover UI -- so it gets a permanent, statically-declared widget in components/login-modal.html (js/ui/bitloginModalIntegration.js relocates it, never destroys and recreates it, since removal tears down its crypto Web Worker) instead of a grid button. It still goes through the exact same session-establishment path as every other signer: a new "bitlogin" provider (js/services/authProviders/bitlogin.js) lets AuthService resolve and activate it via the normal requestLogin -> handleAuthLogin path, while providersForModal excludes it from the grid so it isn't rendered twice. Two things this needed beyond the wiring itself: - js/app.js is one line from its lint/file-size ceiling, so the only change there is renaming an import binding (providers -> providersForModal), a net-zero line change. The actual widget/event wiring lives in a new, unconstrained module instead. - The login modal's HTML (including the permanent widget element) loads asynchronously after bootstrap runs, so wireBitloginLogin() waits via MutationObserver rather than assuming a specific call order. Vendored via scripts/build-bitlogin-widget.mjs (mirrors the existing Bitcoin Connect/Blossom vendor steps): BitLogin isn't published to npm, so the widget build is copied verbatim from the sibling ../bitlogin checkout into committed vendor/bitlogin/, then lazy-imported (like Bitcoin Connect) so its ~350KB never weighs on visitors who never open the login modal. Gated behind FEATURE_BITLOGIN (config/instance-config.js), default on for this unstable-branch experiment per AGENTS.md §1 -- reset to false before this reaches main until verified end-to-end against production relays. Verified: npm run lint (clean), the full auth/login test suite (43/43, including new coverage for the provider + adapter), and a manual Chrome pass against the built dist/ -- widget renders themed correctly, "Other login options" still shows the original four methods unchanged. --- components/login-modal.html | 55 +- config/instance-config.js | 13 + css/tailwind.source.css | 23 + js/app.js | 2 +- js/config.js | 2 + js/constants.js | 10 + js/nostr/adapters/bitloginAdapter.js | 38 + js/services/authProviders/bitlogin.js | 85 + js/services/authProviders/index.js | 16 +- js/services/authProviders/nip07.js | 1 - js/ui/applicationBootstrap.js | 2 + js/ui/bitloginModalIntegration.js | 116 + scripts/build-bitlogin-widget.mjs | 68 + tests/auth-providers-bitlogin.test.mjs | 146 + vendor/bitlogin.LICENSE | 21 + vendor/bitlogin.integrity.json | 18 + vendor/bitlogin/bitlogin-shared-QIBe5Omw.js | 10837 ++++++++++++++++++ vendor/bitlogin/bitlogin.js | 1253 ++ vendor/bitlogin/cryptoWorker.js | 3446 ++++++ 19 files changed, 16133 insertions(+), 19 deletions(-) create mode 100644 js/nostr/adapters/bitloginAdapter.js create mode 100644 js/services/authProviders/bitlogin.js create mode 100644 js/ui/bitloginModalIntegration.js create mode 100644 scripts/build-bitlogin-widget.mjs create mode 100644 tests/auth-providers-bitlogin.test.mjs create mode 100644 vendor/bitlogin.LICENSE create mode 100644 vendor/bitlogin.integrity.json create mode 100644 vendor/bitlogin/bitlogin-shared-QIBe5Omw.js create mode 100644 vendor/bitlogin/bitlogin.js create mode 100644 vendor/bitlogin/cryptoWorker.js diff --git a/components/login-modal.html b/components/login-modal.html index 6ec0e560..f23494b2 100644 --- a/components/login-modal.html +++ b/components/login-modal.html @@ -47,27 +47,50 @@ >

- Choose how you'd like to sign in. Connect a browser extension, link a - remote signer, or work directly with your keys when you need to. + Sign in with a login name and password that works the same on every + BitLogin site, or use one of the other options below.

-
-- never + written by an innerHTML re-render (this markup never changes after + the page loads), so the element is only ever hidden/shown, never + destroyed and recreated. Removing it from the DOM tears down its + crypto Web Worker (see BitLogin's README). + --> +
-
-

Available methods

-

- Pick the option that matches how you manage your Nostr identity. -

-
+ +
- -
-
+
+ + Other login options + +
+
+

Available methods

+

+ Pick the option that matches how you manage your Nostr identity. +

+
+ + +
+
+
diff --git a/config/instance-config.js b/config/instance-config.js index dec628cf..183a3b90 100644 --- a/config/instance-config.js +++ b/config/instance-config.js @@ -276,6 +276,19 @@ export const FEATURE_AUDIO_INGEST = false; */ export const FEATURE_BITCOIN_CONNECT = true; +/** + * Whether the login modal offers BitLogin (a portable Nostr identity behind a + * login name/password, https://github.com/PR0M3TH3AN/bitlogin) as the primary + * sign-in method, with the NIP-07 extension, nsec, and NIP-46 options + * collapsed under "Other login options" (js/ui/bitloginModalIntegration.js). + * The vendored widget bundle (vendor/bitlogin/, scripts/build-bitlogin-widget.mjs) + * only lazy-loads once the login modal's HTML is present in the DOM. + * + * Unstable-branch experiment (AGENTS.md §1) -- reset to `false` before this + * reaches main until verified end-to-end against production relays. + */ +export const FEATURE_BITLOGIN = true; + /** * Whether this deployment offers Blossom (nostr-native blob storage) as a * first-class upload provider alongside Cloudflare R2 / generic S3 / Backblaze B2 diff --git a/css/tailwind.source.css b/css/tailwind.source.css index 258b6acb..d52ec7d6 100644 --- a/css/tailwind.source.css +++ b/css/tailwind.source.css @@ -880,6 +880,29 @@ position: relative; } + /* Maps the widget's own theming hooks onto bitvid's design + tokens (components/login-modal.html), so it reads as part of this site + rather than a foreign embed, and follows the light/dark theme toggle + automatically since every token below is itself theme-aware. */ + bitlogin-auth { + display: block; + --bl-accent: var(--color-accent-strong); + --bl-accent-hover: var(--color-accent); + --bl-accent-fg: rgb(255 255 255); + --bl-bg: var(--surface-panel); + --bl-fg: var(--text-primary); + --bl-muted: var(--text-muted); + --bl-border: var(--border-default); + --bl-input-bg: var(--surface-sunken); + --bl-danger: var(--status-danger); + --bl-danger-bg: var(--status-danger-surface); + --bl-warn: var(--status-warning); + --bl-warn-bg: var(--status-warning-surface); + --bl-radius: var(--radius-lg); + --bl-font-family: inherit; + --bl-max-width: none; + } + .btn-ghost[data-state="active"], .btn-ghost[aria-pressed="true"] { @apply border-transparent bg-primary text-white shadow-md; diff --git a/js/app.js b/js/app.js index 1b3dccb3..f07987e1 100644 --- a/js/app.js +++ b/js/app.js @@ -67,7 +67,7 @@ import { normalizeVideoNotePayload, } from "./services/videoNotePayload.js"; import getAuthProvider, { - providers as authProviders, + providersForModal as authProviders, } from "./services/authProviders/index.js"; import hashtagPreferences, { HASHTAG_PREFERENCES_EVENTS, diff --git a/js/config.js b/js/config.js index 1cc67536..07639a27 100644 --- a/js/config.js +++ b/js/config.js @@ -27,6 +27,7 @@ import { FEATURE_NIP71_INGEST, FEATURE_AUDIO_INGEST, FEATURE_BITCOIN_CONNECT, + FEATURE_BITLOGIN, FEATURE_BLOSSOM_STORAGE, FEATURE_BLOSSOM_TORRENT_METADATA, CARD_LIVENESS_POLICY, @@ -127,6 +128,7 @@ export { ALLOW_NSFW_CONTENT }; export { FEATURE_NIP71_INGEST }; export { FEATURE_AUDIO_INGEST }; export { FEATURE_BITCOIN_CONNECT }; +export { FEATURE_BITLOGIN }; export { FEATURE_BLOSSOM_STORAGE }; export { FEATURE_BLOSSOM_TORRENT_METADATA }; export { CARD_LIVENESS_POLICY }; diff --git a/js/constants.js b/js/constants.js index 68c23835..3a0a15a3 100644 --- a/js/constants.js +++ b/js/constants.js @@ -9,6 +9,7 @@ import { FEATURE_NIP71_INGEST as CONFIG_FEATURE_NIP71_INGEST, FEATURE_AUDIO_INGEST as CONFIG_FEATURE_AUDIO_INGEST, FEATURE_BITCOIN_CONNECT as CONFIG_FEATURE_BITCOIN_CONNECT, + FEATURE_BITLOGIN as CONFIG_FEATURE_BITLOGIN, FEATURE_BLOSSOM_STORAGE as CONFIG_FEATURE_BLOSSOM_STORAGE, FEATURE_BLOSSOM_TORRENT_METADATA as CONFIG_FEATURE_BLOSSOM_TORRENT_METADATA, CARD_LIVENESS_POLICY as CONFIG_CARD_LIVENESS_POLICY, @@ -205,6 +206,9 @@ const DEFAULT_FLAGS = Object.freeze({ // Bitcoin Connect NWC "Connect wallet" flow (docs/bitcoin-connect-plan.md, #61). // Default OFF; only an explicit `true` in the instance config enables it. FEATURE_BITCOIN_CONNECT: CONFIG_FEATURE_BITCOIN_CONNECT === true, + // BitLogin primary sign-in method (js/ui/bitloginModalIntegration.js). + // Default OFF; only an explicit `true` in the instance config enables it. + FEATURE_BITLOGIN: CONFIG_FEATURE_BITLOGIN === true, // Blossom (nostr-native blob storage) provider (docs/blossom-plan.md, #30). // Default OFF; only an explicit `true` in the instance config enables it. FEATURE_BLOSSOM_STORAGE: CONFIG_FEATURE_BLOSSOM_STORAGE === true, @@ -241,6 +245,7 @@ const runtimeFlags = (() => { FEATURE_NIP71_INGEST: DEFAULT_FLAGS.FEATURE_NIP71_INGEST, FEATURE_AUDIO_INGEST: DEFAULT_FLAGS.FEATURE_AUDIO_INGEST, FEATURE_BITCOIN_CONNECT: DEFAULT_FLAGS.FEATURE_BITCOIN_CONNECT, + FEATURE_BITLOGIN: DEFAULT_FLAGS.FEATURE_BITLOGIN, FEATURE_BLOSSOM_STORAGE: DEFAULT_FLAGS.FEATURE_BLOSSOM_STORAGE, FEATURE_BLOSSOM_TORRENT_METADATA: DEFAULT_FLAGS.FEATURE_BLOSSOM_TORRENT_METADATA, @@ -299,6 +304,11 @@ export let FEATURE_BITCOIN_CONNECT = coerceBoolean( DEFAULT_FLAGS.FEATURE_BITCOIN_CONNECT ); +export let FEATURE_BITLOGIN = coerceBoolean( + runtimeFlags.FEATURE_BITLOGIN, + DEFAULT_FLAGS.FEATURE_BITLOGIN +); + export let FEATURE_BLOSSOM_STORAGE = coerceBoolean( runtimeFlags.FEATURE_BLOSSOM_STORAGE, DEFAULT_FLAGS.FEATURE_BLOSSOM_STORAGE diff --git a/js/nostr/adapters/bitloginAdapter.js b/js/nostr/adapters/bitloginAdapter.js new file mode 100644 index 00000000..167c747a --- /dev/null +++ b/js/nostr/adapters/bitloginAdapter.js @@ -0,0 +1,38 @@ +// js/nostr/adapters/bitloginAdapter.js +// +// Wraps a live element (already signed in) as a signer matching +// the same shape as nip07Adapter/nsecAdapter/nip46Adapter. Never touches +// window.nostr and never sees the private key -- every call is proxied through +// the element's own crypto Web Worker (see BitLogin's README, "Embedding +// BitLogin" -> element-scoped methods). + +export function createBitloginAdapter(widgetElement, pubkey) { + const signer = { + type: "bitlogin", + pubkey: typeof pubkey === "string" ? pubkey : "", + metadata: async () => null, + relays: async () => null, + signEvent: async (event) => widgetElement.signEvent(event), + nip44Encrypt: async (peerPubkey, plaintext) => + widgetElement.nip44Encrypt(peerPubkey, plaintext), + nip44Decrypt: async (peerPubkey, payload) => + widgetElement.nip44Decrypt(peerPubkey, payload), + requestPermissions: async () => ({ ok: true }), + destroy: async () => { + try { + await widgetElement.logout(); + } catch (error) { + // The element is permanently mounted and reused for the next sign-in; + // a failed logout here shouldn't block switching to another signer. + } + }, + canSign: () => true, + capabilities: { + sign: true, + nip44: true, + nip04: false, + }, + }; + + return signer; +} diff --git a/js/services/authProviders/bitlogin.js b/js/services/authProviders/bitlogin.js new file mode 100644 index 00000000..91c5b0a1 --- /dev/null +++ b/js/services/authProviders/bitlogin.js @@ -0,0 +1,85 @@ +import { accessControl } from "../../accessControl.js"; + +const PROVIDER_ID = "bitlogin"; +const PROVIDER_LABEL = "BitLogin"; +const PROVIDER_DESCRIPTION = + "A portable Nostr identity behind a familiar login name and password."; +const PROVIDER_CAPABILITIES = Object.freeze([ + Object.freeze({ + id: "signing", + label: "Signs Nostr events", + variant: "info", + }), + Object.freeze({ + id: "portable", + label: "Works on every BitLogin site", + variant: "neutral", + }), +]); +const PROVIDER_BUTTON = Object.freeze({ + variant: "primary", +}); +const PROVIDER_MESSAGES = Object.freeze({ + loading: "Signing in with BitLogin…", + slow: "Still waiting on BitLogin…", + error: "Failed to sign in with BitLogin. Please try again.", +}); + +// Set by the widget's own "bitlogin-login" listener (js/ui/applicationBootstrap.js) +// right before it calls authService.requestLogin({ providerId: "bitlogin" }): by +// the time that reaches this provider's login(), the widget has already run its +// own multi-screen sign-in/create/recover flow and produced a pubkey + signer. +// This provider exists so that result goes through the exact same session- +// establishment path (AuthService.login -> handleAuthLogin) as every other +// signer, without BitLogin also rendering as a redundant button in the login +// grid -- see providersForModal in ./index.js. +let pendingResult = null; + +export function setPendingBitloginResult(result) { + pendingResult = result && typeof result === "object" ? result : null; +} + +export default { + id: PROVIDER_ID, + label: PROVIDER_LABEL, + description: PROVIDER_DESCRIPTION, + eyebrow: "Recommended", + tone: "accent", + badgeVariant: "info", + capabilities: PROVIDER_CAPABILITIES, + button: PROVIDER_BUTTON, + messages: PROVIDER_MESSAGES, + errorMessage: PROVIDER_MESSAGES.error, + async login({ nostrClient } = {}) { + const result = pendingResult; + pendingResult = null; + + if (!result || typeof result.pubkey !== "string" || !result.pubkey.trim()) { + const error = new Error("BitLogin sign-in hasn't completed yet."); + error.code = "provider-unavailable"; + throw error; + } + + const pubkey = result.pubkey.trim(); + + if (!accessControl.canAccess(pubkey)) { + if (accessControl.isBlacklisted(pubkey)) { + throw new Error("Your account has been blocked on this platform."); + } + throw new Error("Access restricted to admins and moderators users only."); + } + + if ( + nostrClient?.signerManager && + typeof nostrClient.signerManager.setActiveSigner === "function" + ) { + nostrClient.signerManager.setActiveSigner(result.signer); + } + + return { + authType: PROVIDER_ID, + pubkey, + signer: result.signer, + }; + }, +}; diff --git a/js/services/authProviders/index.js b/js/services/authProviders/index.js index 1cb0f65a..63c37823 100644 --- a/js/services/authProviders/index.js +++ b/js/services/authProviders/index.js @@ -2,6 +2,7 @@ import nip07 from "./nip07.js"; import nip46 from "./nip46.js"; import nsec from "./nsec.js"; import generate from "./generate.js"; +import bitlogin from "./bitlogin.js"; function normalizeProviderId(provider, fallbackId) { if (provider && typeof provider.id === "string") { @@ -39,7 +40,7 @@ function normalizeProviderMetadata(provider, fallbackId) { return Object.freeze({ id, label, description, badgeVariant }); } -const providerList = [nip07, nip46, nsec, generate].filter(Boolean); +const providerList = [nip07, nip46, nsec, generate, bitlogin].filter(Boolean); const providerMap = {}; const metadataMap = {}; @@ -97,6 +98,19 @@ builtInMetadata.forEach((entry) => { export const providers = Object.freeze({ ...providerMap }); export const providerMetadata = Object.freeze({ ...metadataMap }); +// BitLogin gets its own permanent, always-visible widget in the login modal +// (components/login-modal.html) rather than a grid button -- it isn't a single +// button-click-then-await-a-promise flow like the others, it's a whole +// multi-screen sign-in/create/recover UI. Excluding it here keeps the modal's +// provider grid ("Other login options") free of a redundant, do-nothing button +// for it, while `providers.bitlogin` above still resolves normally for +// AuthService.requestLogin({ providerId: "bitlogin" }). +export const providersForModal = Object.freeze( + Object.fromEntries( + Object.entries(providerMap).filter(([id]) => id !== "bitlogin"), + ), +); + const unknownMetadataCache = new Map(); function getFallbackMetadata(providerId) { diff --git a/js/services/authProviders/nip07.js b/js/services/authProviders/nip07.js index 1d425d2d..818eba00 100644 --- a/js/services/authProviders/nip07.js +++ b/js/services/authProviders/nip07.js @@ -49,7 +49,6 @@ export default { id: PROVIDER_ID, label: PROVIDER_LABEL, description: PROVIDER_DESCRIPTION, - eyebrow: "Recommended", tone: "accent", badgeVariant: "info", capabilities: PROVIDER_CAPABILITIES, diff --git a/js/ui/applicationBootstrap.js b/js/ui/applicationBootstrap.js index ebd96633..639b32ee 100644 --- a/js/ui/applicationBootstrap.js +++ b/js/ui/applicationBootstrap.js @@ -41,6 +41,7 @@ import ProfileIdentityController from "./profileIdentityController.js"; import VideoListViewController from "./videoListViewController.js"; import ProfileModalController from "./profileModalController.js"; import LoginModalController from "./loginModalController.js"; +import { wireBitloginLogin } from "./bitloginModalIntegration.js"; import { VideoListView } from "./views/VideoListView.js"; import MoreMenuController from "./moreMenuController.js"; import VideoSettingsMenuController from "./videoSettingsMenuController.js"; @@ -429,6 +430,7 @@ export default class ApplicationBootstrap { authProviders: this.services.authProviders || authProviders, getAuthProvider: this.services.getAuthProvider || getAuthProvider, }); + wireBitloginLogin(app); app.authEventUnsubscribes.push( app.authService.on("auth:login", (detail) => { const maybePromise = app.handleAuthLogin(detail); diff --git a/js/ui/bitloginModalIntegration.js b/js/ui/bitloginModalIntegration.js new file mode 100644 index 00000000..c71ba074 --- /dev/null +++ b/js/ui/bitloginModalIntegration.js @@ -0,0 +1,116 @@ +// js/ui/bitloginModalIntegration.js +// +// Wires the permanent element -- declared +// once, statically, in components/login-modal.html, never written by an +// innerHTML re-render -- into the exact same session-establishment path every +// other sign-in method uses (AuthService.requestLogin -> handleAuthLogin). +// BitLogin isn't a single button-click-then-await-a-promise flow like the +// other providers, it's a whole multi-screen sign-in/create/recover UI, so it +// gets its own widget in the modal instead of a grid button (see +// providersForModal in js/services/authProviders/index.js) and its own small +// integration module here rather than special-casing LoginModalController. +import { devLogger } from "../utils/logger.js"; +import { closeStaticModal } from "./components/staticModalAccessibility.js"; +import { setModalState as setGlobalModalState } from "../state/appState.js"; +import { createBitloginAdapter } from "../nostr/adapters/bitloginAdapter.js"; +import { setPendingBitloginResult } from "../services/authProviders/bitlogin.js"; +import { FEATURE_BITLOGIN } from "../constants.js"; + +// Vendored, pinned BitLogin widget bundle (scripts/build-bitlogin-widget.mjs). +// Lazy-imported so its ~350KB never weighs on the main bundle for visitors who +// never open the login modal, mirroring the existing Bitcoin Connect vendor +// step (js/ui/profileModal/ProfileWalletController.js). A plain dynamic +// import is enough: the bundle's only job is the customElements.define("bitlogin-auth") +// side effect, which retroactively upgrades the already-present +// tag in the modal's static HTML once it resolves. +const BITLOGIN_WIDGET_BUNDLE_URL = "../../vendor/bitlogin/bitlogin.js"; + +let widgetLoadPromise = null; + +function loadBitloginWidgetOnce() { + if (!widgetLoadPromise) { + widgetLoadPromise = import(BITLOGIN_WIDGET_BUNDLE_URL).catch((error) => { + widgetLoadPromise = null; + throw error; + }); + } + return widgetLoadPromise; +} + +function closeLoginModal() { + const modal = document.getElementById("loginModal"); + if (modal && closeStaticModal(modal)) { + setGlobalModalState("login", false); + } +} + +function attachWidget(app, widget) { + const mount = document.getElementById("bitloginMount"); + if (!FEATURE_BITLOGIN) { + // Unstable-branch experiment (AGENTS.md §1, config/instance-config.js) -- + // leave the permanent, statically-declared markup in place but never load + // the vendored bundle or show the (otherwise-empty, unupgraded) element. + if (mount instanceof HTMLElement) { + mount.hidden = true; + } + return; + } + if (mount instanceof HTMLElement) { + mount.hidden = false; + } + + loadBitloginWidgetOnce().catch((error) => { + devLogger.warn("[BitLogin] Failed to load the widget bundle:", error); + }); + + widget.addEventListener("bitlogin-login", async (event) => { + const pubkey = + event?.detail && typeof event.detail.publicKey === "string" + ? event.detail.publicKey.trim() + : ""; + if (!pubkey) { + return; + } + + setPendingBitloginResult({ + pubkey, + signer: createBitloginAdapter(widget, pubkey), + }); + + try { + await app.authService.requestLogin({ providerId: "bitlogin" }); + closeLoginModal(); + } catch (error) { + devLogger.warn("[BitLogin] Sign-in failed to apply:", error); + } + }); +} + +/** + * Call once during app bootstrap, after `app.authService` exists. The login + * modal's HTML (including the permanent #bitloginWidget element) loads + * asynchronously (js/index.js bootstrapInterface()) and isn't guaranteed to be + * in the DOM yet at this point, so this waits for it rather than requiring a + * specific call order between the two. + * @param {{ authService: { requestLogin: Function } }} app + */ +export function wireBitloginLogin(app) { + if (!app?.authService) { + return; + } + + const existing = document.getElementById("bitloginWidget"); + if (existing instanceof HTMLElement) { + attachWidget(app, existing); + return; + } + + const observer = new MutationObserver(() => { + const widget = document.getElementById("bitloginWidget"); + if (widget instanceof HTMLElement) { + observer.disconnect(); + attachWidget(app, widget); + } + }); + observer.observe(document.body, { childList: true, subtree: true }); +} diff --git a/scripts/build-bitlogin-widget.mjs b/scripts/build-bitlogin-widget.mjs new file mode 100644 index 00000000..8377a4c2 --- /dev/null +++ b/scripts/build-bitlogin-widget.mjs @@ -0,0 +1,68 @@ +// Vendor the sibling @bitlogin/widget build into a pinned, same-origin bundle. +// +// bitvid's CSP is script-src 'self' (plus a couple of pinned CDN fallbacks for +// nostr-tools) -- the same reasoning as the existing bitcoin-connect/blossom-sdk +// vendor steps. Unlike those, BitLogin ships its own Vite-built ESM output +// directly (bitlogin.js + cryptoWorker.js + a shared chunk they both import), +// so this script copies that output verbatim instead of re-bundling it with +// esbuild -- the worker's relative self-URL resolution depends on the three +// files staying exactly as built and sitting side by side (see BitRoad's and +// BitUnlock's identical vendor steps for this same reasoning). +// +// node scripts/build-bitlogin-widget.mjs +// BITLOGIN_ENTRY=/path/to/bitlogin node scripts/build-bitlogin-widget.mjs + +import { createHash } from "node:crypto"; +import { copyFile, mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises"; +import { existsSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { execSync } from "node:child_process"; + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const bitloginRoot = process.env.BITLOGIN_ENTRY + ? path.resolve(process.env.BITLOGIN_ENTRY) + : path.resolve(repoRoot, "../bitlogin"); +const widgetDir = path.join(bitloginRoot, "packages/widget"); +const widgetDist = path.join(widgetDir, "dist"); +const vendorDir = path.join(repoRoot, "vendor/bitlogin"); +const integrityFile = path.join(repoRoot, "vendor/bitlogin.integrity.json"); + +const packageJson = JSON.parse(await readFile(path.join(widgetDir, "package.json"), "utf8")); +if (packageJson.name !== "@bitlogin/widget") { + throw new Error("BITLOGIN_ENTRY must point to a BitLogin checkout (packages/widget/package.json not found there)."); +} + +if (!existsSync(widgetDist)) { + console.log("[build-bitlogin-widget] dist missing, building @bitlogin/core then @bitlogin/widget first..."); + execSync("npm run build -w @bitlogin/core", { cwd: bitloginRoot, stdio: "inherit" }); + execSync("npm run build -w @bitlogin/widget", { cwd: bitloginRoot, stdio: "inherit" }); +} + +await rm(vendorDir, { recursive: true, force: true }); +await mkdir(vendorDir, { recursive: true }); + +const distEntries = (await readdir(widgetDist)).filter((name) => name.endsWith(".js")); +if (!distEntries.includes("bitlogin.js") || !distEntries.includes("cryptoWorker.js")) { + throw new Error("Expected bitlogin.js and cryptoWorker.js in @bitlogin/widget dist -- did the build layout change?"); +} + +const files = {}; +for (const name of distEntries) { + await copyFile(path.join(widgetDist, name), path.join(vendorDir, name)); + const bytes = await readFile(path.join(widgetDist, name)); + files[name] = { sha256: createHash("sha256").update(bytes).digest("hex"), bytes: bytes.byteLength }; +} + +await writeFile( + integrityFile, + `${JSON.stringify({ package: packageJson.name, version: packageJson.version, files }, null, 2)}\n`, +); + +try { + await copyFile(path.join(bitloginRoot, "LICENSE"), path.join(repoRoot, "vendor/bitlogin.LICENSE")); +} catch (error) { + console.warn("[build-bitlogin-widget] could not copy LICENSE:", error.message); +} + +console.log("[build-bitlogin-widget] wrote", path.relative(repoRoot, vendorDir), `(${distEntries.length} files)`); diff --git a/tests/auth-providers-bitlogin.test.mjs b/tests/auth-providers-bitlogin.test.mjs new file mode 100644 index 00000000..938f781e --- /dev/null +++ b/tests/auth-providers-bitlogin.test.mjs @@ -0,0 +1,146 @@ +import "./test-helpers/setup-localstorage.mjs"; +import assert from "node:assert/strict"; +import { describe, it, beforeEach, afterEach } from "node:test"; + +import { + providers, + providersForModal, +} from "../js/services/authProviders/index.js"; +import bitloginProvider, { + setPendingBitloginResult, +} from "../js/services/authProviders/bitlogin.js"; +import { createBitloginAdapter } from "../js/nostr/adapters/bitloginAdapter.js"; +import { accessControl } from "../js/accessControl.js"; + +const HEX_PUBKEY = "a".repeat(64); + +describe("BitLogin auth provider", () => { + const originalCanAccess = accessControl.canAccess; + const originalIsBlacklisted = accessControl.isBlacklisted; + + afterEach(() => { + setPendingBitloginResult(null); + accessControl.canAccess = originalCanAccess; + accessControl.isBlacklisted = originalIsBlacklisted; + }); + + it("registers in the resolvable provider registry but not the modal grid", () => { + // AuthService.resolveAuthProvider() must be able to find "bitlogin" ... + assert.equal(providers.bitlogin, bitloginProvider); + // ... but the login modal's grid (LoginModalController) must never render + // a redundant button for it -- BitLogin gets its own permanent widget in + // components/login-modal.html instead. + assert.equal(providersForModal.bitlogin, undefined); + assert.equal(providersForModal.nip07, providers.nip07); + }); + + it("refuses to log in before the widget has actually produced a pubkey", async () => { + accessControl.canAccess = () => true; + setPendingBitloginResult(null); + + await assert.rejects( + () => bitloginProvider.login({ nostrClient: {} }), + /hasn't completed yet/i, + ); + }); + + it("activates the signer and resolves once the widget's result is pending", async () => { + accessControl.canAccess = () => true; + let activated = null; + const fakeSigner = { type: "bitlogin", pubkey: HEX_PUBKEY }; + const fakeNostrClient = { + signerManager: { + setActiveSigner: (signer) => { + activated = signer; + }, + }, + }; + + setPendingBitloginResult({ pubkey: HEX_PUBKEY, signer: fakeSigner }); + + const result = await bitloginProvider.login({ nostrClient: fakeNostrClient }); + + assert.equal(result.authType, "bitlogin"); + assert.equal(result.pubkey, HEX_PUBKEY); + assert.equal(result.signer, fakeSigner); + assert.equal(activated, fakeSigner); + }); + + it("consumes the pending result exactly once (no replay across calls)", async () => { + accessControl.canAccess = () => true; + const fakeNostrClient = { signerManager: { setActiveSigner: () => {} } }; + setPendingBitloginResult({ pubkey: HEX_PUBKEY, signer: {} }); + + await bitloginProvider.login({ nostrClient: fakeNostrClient }); + + await assert.rejects( + () => bitloginProvider.login({ nostrClient: fakeNostrClient }), + /hasn't completed yet/i, + ); + }); + + it("never activates a signer for a blacklisted pubkey", async () => { + accessControl.canAccess = () => false; + accessControl.isBlacklisted = () => true; + let activated = null; + const fakeNostrClient = { + signerManager: { + setActiveSigner: (signer) => { + activated = signer; + }, + }, + }; + + setPendingBitloginResult({ pubkey: HEX_PUBKEY, signer: { type: "bitlogin" } }); + + await assert.rejects( + () => bitloginProvider.login({ nostrClient: fakeNostrClient }), + /blocked/i, + ); + assert.equal(activated, null); + }); +}); + +describe("createBitloginAdapter", () => { + it("delegates signing and NIP-44 calls to the live widget element, never window.nostr", async () => { + const calls = []; + const fakeWidget = { + signEvent: async (event) => { + calls.push(["signEvent", event]); + return { ...event, id: "signed" }; + }, + nip44Encrypt: async (peer, plaintext) => { + calls.push(["nip44Encrypt", peer, plaintext]); + return "ciphertext"; + }, + nip44Decrypt: async (peer, payload) => { + calls.push(["nip44Decrypt", peer, payload]); + return "plaintext"; + }, + logout: async () => { + calls.push(["logout"]); + }, + }; + + const adapter = createBitloginAdapter(fakeWidget, HEX_PUBKEY); + + assert.equal(adapter.type, "bitlogin"); + assert.equal(adapter.pubkey, HEX_PUBKEY); + assert.equal(adapter.canSign(), true); + assert.deepEqual(adapter.capabilities, { sign: true, nip44: true, nip04: false }); + + const signed = await adapter.signEvent({ kind: 1, content: "hi" }); + assert.equal(signed.id, "signed"); + + await adapter.nip44Encrypt("peer-pubkey", "hello"); + await adapter.nip44Decrypt("peer-pubkey", "ciphertext"); + await adapter.destroy(); + + assert.deepEqual(calls, [ + ["signEvent", { kind: 1, content: "hi" }], + ["nip44Encrypt", "peer-pubkey", "hello"], + ["nip44Decrypt", "peer-pubkey", "ciphertext"], + ["logout"], + ]); + }); +}); diff --git a/vendor/bitlogin.LICENSE b/vendor/bitlogin.LICENSE new file mode 100644 index 00000000..3e927039 --- /dev/null +++ b/vendor/bitlogin.LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 PR0M3TH3AN + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/vendor/bitlogin.integrity.json b/vendor/bitlogin.integrity.json new file mode 100644 index 00000000..23dd37c9 --- /dev/null +++ b/vendor/bitlogin.integrity.json @@ -0,0 +1,18 @@ +{ + "package": "@bitlogin/widget", + "version": "0.1.0", + "files": { + "bitlogin-shared-QIBe5Omw.js": { + "sha256": "7ce4ed0a5c3c7f744e864f55745f7fa78482cbfa19cd30eb551f67a415f55191", + "bytes": 288275 + }, + "bitlogin.js": { + "sha256": "7bbe34092ff5c110d947ac4cec7500cd32824868e51118387566896660a31228", + "bytes": 65009 + }, + "cryptoWorker.js": { + "sha256": "31e2b32cbbb8a4223d6664d383b6de49a173406e14a409a4cf192be53ed9dd74", + "bytes": 64803 + } + } +} diff --git a/vendor/bitlogin/bitlogin-shared-QIBe5Omw.js b/vendor/bitlogin/bitlogin-shared-QIBe5Omw.js new file mode 100644 index 00000000..8f88c18b --- /dev/null +++ b/vendor/bitlogin/bitlogin-shared-QIBe5Omw.js @@ -0,0 +1,10837 @@ +function Kr(e) { + return e && e.__esModule && Object.prototype.hasOwnProperty.call(e, "default") ? e.default : e; +} +var yt, or; +function En() { + return or || (or = 1, yt = { + 11111: "abacus", + 11112: "abdomen", + 11113: "abdominal", + 11114: "abide", + 11115: "abiding", + 11116: "ability", + 11121: "ablaze", + 11122: "able", + 11123: "abnormal", + 11124: "abrasion", + 11125: "abrasive", + 11126: "abreast", + 11131: "abridge", + 11132: "abroad", + 11133: "abruptly", + 11134: "absence", + 11135: "absentee", + 11136: "absently", + 11141: "absinthe", + 11142: "absolute", + 11143: "absolve", + 11144: "abstain", + 11145: "abstract", + 11146: "absurd", + 11151: "accent", + 11152: "acclaim", + 11153: "acclimate", + 11154: "accompany", + 11155: "account", + 11156: "accuracy", + 11161: "accurate", + 11162: "accustom", + 11163: "acetone", + 11164: "achiness", + 11165: "aching", + 11166: "acid", + 11211: "acorn", + 11212: "acquaint", + 11213: "acquire", + 11214: "acre", + 11215: "acrobat", + 11216: "acronym", + 11221: "acting", + 11222: "action", + 11223: "activate", + 11224: "activator", + 11225: "active", + 11226: "activism", + 11231: "activist", + 11232: "activity", + 11233: "actress", + 11234: "acts", + 11235: "acutely", + 11236: "acuteness", + 11241: "aeration", + 11242: "aerobics", + 11243: "aerosol", + 11244: "aerospace", + 11245: "afar", + 11246: "affair", + 11251: "affected", + 11252: "affecting", + 11253: "affection", + 11254: "affidavit", + 11255: "affiliate", + 11256: "affirm", + 11261: "affix", + 11262: "afflicted", + 11263: "affluent", + 11264: "afford", + 11265: "affront", + 11266: "aflame", + 11311: "afloat", + 11312: "aflutter", + 11313: "afoot", + 11314: "afraid", + 11315: "afterglow", + 11316: "afterlife", + 11321: "aftermath", + 11322: "aftermost", + 11323: "afternoon", + 11324: "aged", + 11325: "ageless", + 11326: "agency", + 11331: "agenda", + 11332: "agent", + 11333: "aggregate", + 11334: "aghast", + 11335: "agile", + 11336: "agility", + 11341: "aging", + 11342: "agnostic", + 11343: "agonize", + 11344: "agonizing", + 11345: "agony", + 11346: "agreeable", + 11351: "agreeably", + 11352: "agreed", + 11353: "agreeing", + 11354: "agreement", + 11355: "aground", + 11356: "ahead", + 11361: "ahoy", + 11362: "aide", + 11363: "aids", + 11364: "aim", + 11365: "ajar", + 11366: "alabaster", + 11411: "alarm", + 11412: "albatross", + 11413: "album", + 11414: "alfalfa", + 11415: "algebra", + 11416: "algorithm", + 11421: "alias", + 11422: "alibi", + 11423: "alienable", + 11424: "alienate", + 11425: "aliens", + 11426: "alike", + 11431: "alive", + 11432: "alkaline", + 11433: "alkalize", + 11434: "almanac", + 11435: "almighty", + 11436: "almost", + 11441: "aloe", + 11442: "aloft", + 11443: "aloha", + 11444: "alone", + 11445: "alongside", + 11446: "aloof", + 11451: "alphabet", + 11452: "alright", + 11453: "although", + 11454: "altitude", + 11455: "alto", + 11456: "aluminum", + 11461: "alumni", + 11462: "always", + 11463: "amaretto", + 11464: "amaze", + 11465: "amazingly", + 11466: "amber", + 11511: "ambiance", + 11512: "ambiguity", + 11513: "ambiguous", + 11514: "ambition", + 11515: "ambitious", + 11516: "ambulance", + 11521: "ambush", + 11522: "amendable", + 11523: "amendment", + 11524: "amends", + 11525: "amenity", + 11526: "amiable", + 11531: "amicably", + 11532: "amid", + 11533: "amigo", + 11534: "amino", + 11535: "amiss", + 11536: "ammonia", + 11541: "ammonium", + 11542: "amnesty", + 11543: "amniotic", + 11544: "among", + 11545: "amount", + 11546: "amperage", + 11551: "ample", + 11552: "amplifier", + 11553: "amplify", + 11554: "amply", + 11555: "amuck", + 11556: "amulet", + 11561: "amusable", + 11562: "amused", + 11563: "amusement", + 11564: "amuser", + 11565: "amusing", + 11566: "anaconda", + 11611: "anaerobic", + 11612: "anagram", + 11613: "anatomist", + 11614: "anatomy", + 11615: "anchor", + 11616: "anchovy", + 11621: "ancient", + 11622: "android", + 11623: "anemia", + 11624: "anemic", + 11625: "aneurism", + 11626: "anew", + 11631: "angelfish", + 11632: "angelic", + 11633: "anger", + 11634: "angled", + 11635: "angler", + 11636: "angles", + 11641: "angling", + 11642: "angrily", + 11643: "angriness", + 11644: "anguished", + 11645: "angular", + 11646: "animal", + 11651: "animate", + 11652: "animating", + 11653: "animation", + 11654: "animator", + 11655: "anime", + 11656: "animosity", + 11661: "ankle", + 11662: "annex", + 11663: "annotate", + 11664: "announcer", + 11665: "annoying", + 11666: "annually", + 12111: "annuity", + 12112: "anointer", + 12113: "another", + 12114: "answering", + 12115: "antacid", + 12116: "antarctic", + 12121: "anteater", + 12122: "antelope", + 12123: "antennae", + 12124: "anthem", + 12125: "anthill", + 12126: "anthology", + 12131: "antibody", + 12132: "antics", + 12133: "antidote", + 12134: "antihero", + 12135: "antiquely", + 12136: "antiques", + 12141: "antiquity", + 12142: "antirust", + 12143: "antitoxic", + 12144: "antitrust", + 12145: "antiviral", + 12146: "antivirus", + 12151: "antler", + 12152: "antonym", + 12153: "antsy", + 12154: "anvil", + 12155: "anybody", + 12156: "anyhow", + 12161: "anymore", + 12162: "anyone", + 12163: "anyplace", + 12164: "anything", + 12165: "anytime", + 12166: "anyway", + 12211: "anywhere", + 12212: "aorta", + 12213: "apache", + 12214: "apostle", + 12215: "appealing", + 12216: "appear", + 12221: "appease", + 12222: "appeasing", + 12223: "appendage", + 12224: "appendix", + 12225: "appetite", + 12226: "appetizer", + 12231: "applaud", + 12232: "applause", + 12233: "apple", + 12234: "appliance", + 12235: "applicant", + 12236: "applied", + 12241: "apply", + 12242: "appointee", + 12243: "appraisal", + 12244: "appraiser", + 12245: "apprehend", + 12246: "approach", + 12251: "approval", + 12252: "approve", + 12253: "apricot", + 12254: "april", + 12255: "apron", + 12256: "aptitude", + 12261: "aptly", + 12262: "aqua", + 12263: "aqueduct", + 12264: "arbitrary", + 12265: "arbitrate", + 12266: "ardently", + 12311: "area", + 12312: "arena", + 12313: "arguable", + 12314: "arguably", + 12315: "argue", + 12316: "arise", + 12321: "armadillo", + 12322: "armband", + 12323: "armchair", + 12324: "armed", + 12325: "armful", + 12326: "armhole", + 12331: "arming", + 12332: "armless", + 12333: "armoire", + 12334: "armored", + 12335: "armory", + 12336: "armrest", + 12341: "army", + 12342: "aroma", + 12343: "arose", + 12344: "around", + 12345: "arousal", + 12346: "arrange", + 12351: "array", + 12352: "arrest", + 12353: "arrival", + 12354: "arrive", + 12355: "arrogance", + 12356: "arrogant", + 12361: "arson", + 12362: "art", + 12363: "ascend", + 12364: "ascension", + 12365: "ascent", + 12366: "ascertain", + 12411: "ashamed", + 12412: "ashen", + 12413: "ashes", + 12414: "ashy", + 12415: "aside", + 12416: "askew", + 12421: "asleep", + 12422: "asparagus", + 12423: "aspect", + 12424: "aspirate", + 12425: "aspire", + 12426: "aspirin", + 12431: "astonish", + 12432: "astound", + 12433: "astride", + 12434: "astrology", + 12435: "astronaut", + 12436: "astronomy", + 12441: "astute", + 12442: "atlantic", + 12443: "atlas", + 12444: "atom", + 12445: "atonable", + 12446: "atop", + 12451: "atrium", + 12452: "atrocious", + 12453: "atrophy", + 12454: "attach", + 12455: "attain", + 12456: "attempt", + 12461: "attendant", + 12462: "attendee", + 12463: "attention", + 12464: "attentive", + 12465: "attest", + 12466: "attic", + 12511: "attire", + 12512: "attitude", + 12513: "attractor", + 12514: "attribute", + 12515: "atypical", + 12516: "auction", + 12521: "audacious", + 12522: "audacity", + 12523: "audible", + 12524: "audibly", + 12525: "audience", + 12526: "audio", + 12531: "audition", + 12532: "augmented", + 12533: "august", + 12534: "authentic", + 12535: "author", + 12536: "autism", + 12541: "autistic", + 12542: "autograph", + 12543: "automaker", + 12544: "automated", + 12545: "automatic", + 12546: "autopilot", + 12551: "available", + 12552: "avalanche", + 12553: "avatar", + 12554: "avenge", + 12555: "avenging", + 12556: "avenue", + 12561: "average", + 12562: "aversion", + 12563: "avert", + 12564: "aviation", + 12565: "aviator", + 12566: "avid", + 12611: "avoid", + 12612: "await", + 12613: "awaken", + 12614: "award", + 12615: "aware", + 12616: "awhile", + 12621: "awkward", + 12622: "awning", + 12623: "awoke", + 12624: "awry", + 12625: "axis", + 12626: "babble", + 12631: "babbling", + 12632: "babied", + 12633: "baboon", + 12634: "backache", + 12635: "backboard", + 12636: "backboned", + 12641: "backdrop", + 12642: "backed", + 12643: "backer", + 12644: "backfield", + 12645: "backfire", + 12646: "backhand", + 12651: "backing", + 12652: "backlands", + 12653: "backlash", + 12654: "backless", + 12655: "backlight", + 12656: "backlit", + 12661: "backlog", + 12662: "backpack", + 12663: "backpedal", + 12664: "backrest", + 12665: "backroom", + 12666: "backshift", + 13111: "backside", + 13112: "backslid", + 13113: "backspace", + 13114: "backspin", + 13115: "backstab", + 13116: "backstage", + 13121: "backtalk", + 13122: "backtrack", + 13123: "backup", + 13124: "backward", + 13125: "backwash", + 13126: "backwater", + 13131: "backyard", + 13132: "bacon", + 13133: "bacteria", + 13134: "bacterium", + 13135: "badass", + 13136: "badge", + 13141: "badland", + 13142: "badly", + 13143: "badness", + 13144: "baffle", + 13145: "baffling", + 13146: "bagel", + 13151: "bagful", + 13152: "baggage", + 13153: "bagged", + 13154: "baggie", + 13155: "bagginess", + 13156: "bagging", + 13161: "baggy", + 13162: "bagpipe", + 13163: "baguette", + 13164: "baked", + 13165: "bakery", + 13166: "bakeshop", + 13211: "baking", + 13212: "balance", + 13213: "balancing", + 13214: "balcony", + 13215: "balmy", + 13216: "balsamic", + 13221: "bamboo", + 13222: "banana", + 13223: "banish", + 13224: "banister", + 13225: "banjo", + 13226: "bankable", + 13231: "bankbook", + 13232: "banked", + 13233: "banker", + 13234: "banking", + 13235: "banknote", + 13236: "bankroll", + 13241: "banner", + 13242: "bannister", + 13243: "banshee", + 13244: "banter", + 13245: "barbecue", + 13246: "barbed", + 13251: "barbell", + 13252: "barber", + 13253: "barcode", + 13254: "barge", + 13255: "bargraph", + 13256: "barista", + 13261: "baritone", + 13262: "barley", + 13263: "barmaid", + 13264: "barman", + 13265: "barn", + 13266: "barometer", + 13311: "barrack", + 13312: "barracuda", + 13313: "barrel", + 13314: "barrette", + 13315: "barricade", + 13316: "barrier", + 13321: "barstool", + 13322: "bartender", + 13323: "barterer", + 13324: "bash", + 13325: "basically", + 13326: "basics", + 13331: "basil", + 13332: "basin", + 13333: "basis", + 13334: "basket", + 13335: "batboy", + 13336: "batch", + 13341: "bath", + 13342: "baton", + 13343: "bats", + 13344: "battalion", + 13345: "battered", + 13346: "battering", + 13351: "battery", + 13352: "batting", + 13353: "battle", + 13354: "bauble", + 13355: "bazooka", + 13356: "blabber", + 13361: "bladder", + 13362: "blade", + 13363: "blah", + 13364: "blame", + 13365: "blaming", + 13366: "blanching", + 13411: "blandness", + 13412: "blank", + 13413: "blaspheme", + 13414: "blasphemy", + 13415: "blast", + 13416: "blatancy", + 13421: "blatantly", + 13422: "blazer", + 13423: "blazing", + 13424: "bleach", + 13425: "bleak", + 13426: "bleep", + 13431: "blemish", + 13432: "blend", + 13433: "bless", + 13434: "blighted", + 13435: "blimp", + 13436: "bling", + 13441: "blinked", + 13442: "blinker", + 13443: "blinking", + 13444: "blinks", + 13445: "blip", + 13446: "blissful", + 13451: "blitz", + 13452: "blizzard", + 13453: "bloated", + 13454: "bloating", + 13455: "blob", + 13456: "blog", + 13461: "bloomers", + 13462: "blooming", + 13463: "blooper", + 13464: "blot", + 13465: "blouse", + 13466: "blubber", + 13511: "bluff", + 13512: "bluish", + 13513: "blunderer", + 13514: "blunt", + 13515: "blurb", + 13516: "blurred", + 13521: "blurry", + 13522: "blurt", + 13523: "blush", + 13524: "blustery", + 13525: "boaster", + 13526: "boastful", + 13531: "boasting", + 13532: "boat", + 13533: "bobbed", + 13534: "bobbing", + 13535: "bobble", + 13536: "bobcat", + 13541: "bobsled", + 13542: "bobtail", + 13543: "bodacious", + 13544: "body", + 13545: "bogged", + 13546: "boggle", + 13551: "bogus", + 13552: "boil", + 13553: "bok", + 13554: "bolster", + 13555: "bolt", + 13556: "bonanza", + 13561: "bonded", + 13562: "bonding", + 13563: "bondless", + 13564: "boned", + 13565: "bonehead", + 13566: "boneless", + 13611: "bonelike", + 13612: "boney", + 13613: "bonfire", + 13614: "bonnet", + 13615: "bonsai", + 13616: "bonus", + 13621: "bony", + 13622: "boogeyman", + 13623: "boogieman", + 13624: "book", + 13625: "boondocks", + 13626: "booted", + 13631: "booth", + 13632: "bootie", + 13633: "booting", + 13634: "bootlace", + 13635: "bootleg", + 13636: "boots", + 13641: "boozy", + 13642: "borax", + 13643: "boring", + 13644: "borough", + 13645: "borrower", + 13646: "borrowing", + 13651: "boss", + 13652: "botanical", + 13653: "botanist", + 13654: "botany", + 13655: "botch", + 13656: "both", + 13661: "bottle", + 13662: "bottling", + 13663: "bottom", + 13664: "bounce", + 13665: "bouncing", + 13666: "bouncy", + 14111: "bounding", + 14112: "boundless", + 14113: "bountiful", + 14114: "bovine", + 14115: "boxcar", + 14116: "boxer", + 14121: "boxing", + 14122: "boxlike", + 14123: "boxy", + 14124: "breach", + 14125: "breath", + 14126: "breeches", + 14131: "breeching", + 14132: "breeder", + 14133: "breeding", + 14134: "breeze", + 14135: "breezy", + 14136: "brethren", + 14141: "brewery", + 14142: "brewing", + 14143: "briar", + 14144: "bribe", + 14145: "brick", + 14146: "bride", + 14151: "bridged", + 14152: "brigade", + 14153: "bright", + 14154: "brilliant", + 14155: "brim", + 14156: "bring", + 14161: "brink", + 14162: "brisket", + 14163: "briskly", + 14164: "briskness", + 14165: "bristle", + 14166: "brittle", + 14211: "broadband", + 14212: "broadcast", + 14213: "broaden", + 14214: "broadly", + 14215: "broadness", + 14216: "broadside", + 14221: "broadways", + 14222: "broiler", + 14223: "broiling", + 14224: "broken", + 14225: "broker", + 14226: "bronchial", + 14231: "bronco", + 14232: "bronze", + 14233: "bronzing", + 14234: "brook", + 14235: "broom", + 14236: "brought", + 14241: "browbeat", + 14242: "brownnose", + 14243: "browse", + 14244: "browsing", + 14245: "bruising", + 14246: "brunch", + 14251: "brunette", + 14252: "brunt", + 14253: "brush", + 14254: "brussels", + 14255: "brute", + 14256: "brutishly", + 14261: "bubble", + 14262: "bubbling", + 14263: "bubbly", + 14264: "buccaneer", + 14265: "bucked", + 14266: "bucket", + 14311: "buckle", + 14312: "buckshot", + 14313: "buckskin", + 14314: "bucktooth", + 14315: "buckwheat", + 14316: "buddhism", + 14321: "buddhist", + 14322: "budding", + 14323: "buddy", + 14324: "budget", + 14325: "buffalo", + 14326: "buffed", + 14331: "buffer", + 14332: "buffing", + 14333: "buffoon", + 14334: "buggy", + 14335: "bulb", + 14336: "bulge", + 14341: "bulginess", + 14342: "bulgur", + 14343: "bulk", + 14344: "bulldog", + 14345: "bulldozer", + 14346: "bullfight", + 14351: "bullfrog", + 14352: "bullhorn", + 14353: "bullion", + 14354: "bullish", + 14355: "bullpen", + 14356: "bullring", + 14361: "bullseye", + 14362: "bullwhip", + 14363: "bully", + 14364: "bunch", + 14365: "bundle", + 14366: "bungee", + 14411: "bunion", + 14412: "bunkbed", + 14413: "bunkhouse", + 14414: "bunkmate", + 14415: "bunny", + 14416: "bunt", + 14421: "busboy", + 14422: "bush", + 14423: "busily", + 14424: "busload", + 14425: "bust", + 14426: "busybody", + 14431: "buzz", + 14432: "cabana", + 14433: "cabbage", + 14434: "cabbie", + 14435: "cabdriver", + 14436: "cable", + 14441: "caboose", + 14442: "cache", + 14443: "cackle", + 14444: "cacti", + 14445: "cactus", + 14446: "caddie", + 14451: "caddy", + 14452: "cadet", + 14453: "cadillac", + 14454: "cadmium", + 14455: "cage", + 14456: "cahoots", + 14461: "cake", + 14462: "calamari", + 14463: "calamity", + 14464: "calcium", + 14465: "calculate", + 14466: "calculus", + 14511: "caliber", + 14512: "calibrate", + 14513: "calm", + 14514: "caloric", + 14515: "calorie", + 14516: "calzone", + 14521: "camcorder", + 14522: "cameo", + 14523: "camera", + 14524: "camisole", + 14525: "camper", + 14526: "campfire", + 14531: "camping", + 14532: "campsite", + 14533: "campus", + 14534: "canal", + 14535: "canary", + 14536: "cancel", + 14541: "candied", + 14542: "candle", + 14543: "candy", + 14544: "cane", + 14545: "canine", + 14546: "canister", + 14551: "cannabis", + 14552: "canned", + 14553: "canning", + 14554: "cannon", + 14555: "cannot", + 14556: "canola", + 14561: "canon", + 14562: "canopener", + 14563: "canopy", + 14564: "canteen", + 14565: "canyon", + 14566: "capable", + 14611: "capably", + 14612: "capacity", + 14613: "cape", + 14614: "capillary", + 14615: "capital", + 14616: "capitol", + 14621: "capped", + 14622: "capricorn", + 14623: "capsize", + 14624: "capsule", + 14625: "caption", + 14626: "captivate", + 14631: "captive", + 14632: "captivity", + 14633: "capture", + 14634: "caramel", + 14635: "carat", + 14636: "caravan", + 14641: "carbon", + 14642: "cardboard", + 14643: "carded", + 14644: "cardiac", + 14645: "cardigan", + 14646: "cardinal", + 14651: "cardstock", + 14652: "carefully", + 14653: "caregiver", + 14654: "careless", + 14655: "caress", + 14656: "caretaker", + 14661: "cargo", + 14662: "caring", + 14663: "carless", + 14664: "carload", + 14665: "carmaker", + 14666: "carnage", + 15111: "carnation", + 15112: "carnival", + 15113: "carnivore", + 15114: "carol", + 15115: "carpenter", + 15116: "carpentry", + 15121: "carpool", + 15122: "carport", + 15123: "carried", + 15124: "carrot", + 15125: "carrousel", + 15126: "carry", + 15131: "cartel", + 15132: "cartload", + 15133: "carton", + 15134: "cartoon", + 15135: "cartridge", + 15136: "cartwheel", + 15141: "carve", + 15142: "carving", + 15143: "carwash", + 15144: "cascade", + 15145: "case", + 15146: "cash", + 15151: "casing", + 15152: "casino", + 15153: "casket", + 15154: "cassette", + 15155: "casually", + 15156: "casualty", + 15161: "catacomb", + 15162: "catalog", + 15163: "catalyst", + 15164: "catalyze", + 15165: "catapult", + 15166: "cataract", + 15211: "catatonic", + 15212: "catcall", + 15213: "catchable", + 15214: "catcher", + 15215: "catching", + 15216: "catchy", + 15221: "caterer", + 15222: "catering", + 15223: "catfight", + 15224: "catfish", + 15225: "cathedral", + 15226: "cathouse", + 15231: "catlike", + 15232: "catnap", + 15233: "catnip", + 15234: "catsup", + 15235: "cattail", + 15236: "cattishly", + 15241: "cattle", + 15242: "catty", + 15243: "catwalk", + 15244: "caucasian", + 15245: "caucus", + 15246: "causal", + 15251: "causation", + 15252: "cause", + 15253: "causing", + 15254: "cauterize", + 15255: "caution", + 15256: "cautious", + 15261: "cavalier", + 15262: "cavalry", + 15263: "caviar", + 15264: "cavity", + 15265: "cedar", + 15266: "celery", + 15311: "celestial", + 15312: "celibacy", + 15313: "celibate", + 15314: "celtic", + 15315: "cement", + 15316: "census", + 15321: "ceramics", + 15322: "ceremony", + 15323: "certainly", + 15324: "certainty", + 15325: "certified", + 15326: "certify", + 15331: "cesarean", + 15332: "cesspool", + 15333: "chafe", + 15334: "chaffing", + 15335: "chain", + 15336: "chair", + 15341: "chalice", + 15342: "challenge", + 15343: "chamber", + 15344: "chamomile", + 15345: "champion", + 15346: "chance", + 15351: "change", + 15352: "channel", + 15353: "chant", + 15354: "chaos", + 15355: "chaperone", + 15356: "chaplain", + 15361: "chapped", + 15362: "chaps", + 15363: "chapter", + 15364: "character", + 15365: "charbroil", + 15366: "charcoal", + 15411: "charger", + 15412: "charging", + 15413: "chariot", + 15414: "charity", + 15415: "charm", + 15416: "charred", + 15421: "charter", + 15422: "charting", + 15423: "chase", + 15424: "chasing", + 15425: "chaste", + 15426: "chastise", + 15431: "chastity", + 15432: "chatroom", + 15433: "chatter", + 15434: "chatting", + 15435: "chatty", + 15436: "cheating", + 15441: "cheddar", + 15442: "cheek", + 15443: "cheer", + 15444: "cheese", + 15445: "cheesy", + 15446: "chef", + 15451: "chemicals", + 15452: "chemist", + 15453: "chemo", + 15454: "cherisher", + 15455: "cherub", + 15456: "chess", + 15461: "chest", + 15462: "chevron", + 15463: "chevy", + 15464: "chewable", + 15465: "chewer", + 15466: "chewing", + 15511: "chewy", + 15512: "chief", + 15513: "chihuahua", + 15514: "childcare", + 15515: "childhood", + 15516: "childish", + 15521: "childless", + 15522: "childlike", + 15523: "chili", + 15524: "chill", + 15525: "chimp", + 15526: "chip", + 15531: "chirping", + 15532: "chirpy", + 15533: "chitchat", + 15534: "chivalry", + 15535: "chive", + 15536: "chloride", + 15541: "chlorine", + 15542: "choice", + 15543: "chokehold", + 15544: "choking", + 15545: "chomp", + 15546: "chooser", + 15551: "choosing", + 15552: "choosy", + 15553: "chop", + 15554: "chosen", + 15555: "chowder", + 15556: "chowtime", + 15561: "chrome", + 15562: "chubby", + 15563: "chuck", + 15564: "chug", + 15565: "chummy", + 15566: "chump", + 15611: "chunk", + 15612: "churn", + 15613: "chute", + 15614: "cider", + 15615: "cilantro", + 15616: "cinch", + 15621: "cinema", + 15622: "cinnamon", + 15623: "circle", + 15624: "circling", + 15625: "circular", + 15626: "circulate", + 15631: "circus", + 15632: "citable", + 15633: "citadel", + 15634: "citation", + 15635: "citizen", + 15636: "citric", + 15641: "citrus", + 15642: "city", + 15643: "civic", + 15644: "civil", + 15645: "clad", + 15646: "claim", + 15651: "clambake", + 15652: "clammy", + 15653: "clamor", + 15654: "clamp", + 15655: "clamshell", + 15656: "clang", + 15661: "clanking", + 15662: "clapped", + 15663: "clapper", + 15664: "clapping", + 15665: "clarify", + 15666: "clarinet", + 16111: "clarity", + 16112: "clash", + 16113: "clasp", + 16114: "class", + 16115: "clatter", + 16116: "clause", + 16121: "clavicle", + 16122: "claw", + 16123: "clay", + 16124: "clean", + 16125: "clear", + 16126: "cleat", + 16131: "cleaver", + 16132: "cleft", + 16133: "clench", + 16134: "clergyman", + 16135: "clerical", + 16136: "clerk", + 16141: "clever", + 16142: "clicker", + 16143: "client", + 16144: "climate", + 16145: "climatic", + 16146: "cling", + 16151: "clinic", + 16152: "clinking", + 16153: "clip", + 16154: "clique", + 16155: "cloak", + 16156: "clobber", + 16161: "clock", + 16162: "clone", + 16163: "cloning", + 16164: "closable", + 16165: "closure", + 16166: "clothes", + 16211: "clothing", + 16212: "cloud", + 16213: "clover", + 16214: "clubbed", + 16215: "clubbing", + 16216: "clubhouse", + 16221: "clump", + 16222: "clumsily", + 16223: "clumsy", + 16224: "clunky", + 16225: "clustered", + 16226: "clutch", + 16231: "clutter", + 16232: "coach", + 16233: "coagulant", + 16234: "coastal", + 16235: "coaster", + 16236: "coasting", + 16241: "coastland", + 16242: "coastline", + 16243: "coat", + 16244: "coauthor", + 16245: "cobalt", + 16246: "cobbler", + 16251: "cobweb", + 16252: "cocoa", + 16253: "coconut", + 16254: "cod", + 16255: "coeditor", + 16256: "coerce", + 16261: "coexist", + 16262: "coffee", + 16263: "cofounder", + 16264: "cognition", + 16265: "cognitive", + 16266: "cogwheel", + 16311: "coherence", + 16312: "coherent", + 16313: "cohesive", + 16314: "coil", + 16315: "coke", + 16316: "cola", + 16321: "cold", + 16322: "coleslaw", + 16323: "coliseum", + 16324: "collage", + 16325: "collapse", + 16326: "collar", + 16331: "collected", + 16332: "collector", + 16333: "collide", + 16334: "collie", + 16335: "collision", + 16336: "colonial", + 16341: "colonist", + 16342: "colonize", + 16343: "colony", + 16344: "colossal", + 16345: "colt", + 16346: "coma", + 16351: "come", + 16352: "comfort", + 16353: "comfy", + 16354: "comic", + 16355: "coming", + 16356: "comma", + 16361: "commence", + 16362: "commend", + 16363: "comment", + 16364: "commerce", + 16365: "commode", + 16366: "commodity", + 16411: "commodore", + 16412: "common", + 16413: "commotion", + 16414: "commute", + 16415: "commuting", + 16416: "compacted", + 16421: "compacter", + 16422: "compactly", + 16423: "compactor", + 16424: "companion", + 16425: "company", + 16426: "compare", + 16431: "compel", + 16432: "compile", + 16433: "comply", + 16434: "component", + 16435: "composed", + 16436: "composer", + 16441: "composite", + 16442: "compost", + 16443: "composure", + 16444: "compound", + 16445: "compress", + 16446: "comprised", + 16451: "computer", + 16452: "computing", + 16453: "comrade", + 16454: "concave", + 16455: "conceal", + 16456: "conceded", + 16461: "concept", + 16462: "concerned", + 16463: "concert", + 16464: "conch", + 16465: "concierge", + 16466: "concise", + 16511: "conclude", + 16512: "concrete", + 16513: "concur", + 16514: "condense", + 16515: "condiment", + 16516: "condition", + 16521: "condone", + 16522: "conducive", + 16523: "conductor", + 16524: "conduit", + 16525: "cone", + 16526: "confess", + 16531: "confetti", + 16532: "confidant", + 16533: "confident", + 16534: "confider", + 16535: "confiding", + 16536: "configure", + 16541: "confined", + 16542: "confining", + 16543: "confirm", + 16544: "conflict", + 16545: "conform", + 16546: "confound", + 16551: "confront", + 16552: "confused", + 16553: "confusing", + 16554: "confusion", + 16555: "congenial", + 16556: "congested", + 16561: "congrats", + 16562: "congress", + 16563: "conical", + 16564: "conjoined", + 16565: "conjure", + 16566: "conjuror", + 16611: "connected", + 16612: "connector", + 16613: "consensus", + 16614: "consent", + 16615: "console", + 16616: "consoling", + 16621: "consonant", + 16622: "constable", + 16623: "constant", + 16624: "constrain", + 16625: "constrict", + 16626: "construct", + 16631: "consult", + 16632: "consumer", + 16633: "consuming", + 16634: "contact", + 16635: "container", + 16636: "contempt", + 16641: "contend", + 16642: "contented", + 16643: "contently", + 16644: "contents", + 16645: "contest", + 16646: "context", + 16651: "contort", + 16652: "contour", + 16653: "contrite", + 16654: "control", + 16655: "contusion", + 16656: "convene", + 16661: "convent", + 16662: "copartner", + 16663: "cope", + 16664: "copied", + 16665: "copier", + 16666: "copilot", + 21111: "coping", + 21112: "copious", + 21113: "copper", + 21114: "copy", + 21115: "coral", + 21116: "cork", + 21121: "cornball", + 21122: "cornbread", + 21123: "corncob", + 21124: "cornea", + 21125: "corned", + 21126: "corner", + 21131: "cornfield", + 21132: "cornflake", + 21133: "cornhusk", + 21134: "cornmeal", + 21135: "cornstalk", + 21136: "corny", + 21141: "coronary", + 21142: "coroner", + 21143: "corporal", + 21144: "corporate", + 21145: "corral", + 21146: "correct", + 21151: "corridor", + 21152: "corrode", + 21153: "corroding", + 21154: "corrosive", + 21155: "corsage", + 21156: "corset", + 21161: "cortex", + 21162: "cosigner", + 21163: "cosmetics", + 21164: "cosmic", + 21165: "cosmos", + 21166: "cosponsor", + 21211: "cost", + 21212: "cottage", + 21213: "cotton", + 21214: "couch", + 21215: "cough", + 21216: "could", + 21221: "countable", + 21222: "countdown", + 21223: "counting", + 21224: "countless", + 21225: "country", + 21226: "county", + 21231: "courier", + 21232: "covenant", + 21233: "cover", + 21234: "coveted", + 21235: "coveting", + 21236: "coyness", + 21241: "cozily", + 21242: "coziness", + 21243: "cozy", + 21244: "crabbing", + 21245: "crabgrass", + 21246: "crablike", + 21251: "crabmeat", + 21252: "cradle", + 21253: "cradling", + 21254: "crafter", + 21255: "craftily", + 21256: "craftsman", + 21261: "craftwork", + 21262: "crafty", + 21263: "cramp", + 21264: "cranberry", + 21265: "crane", + 21266: "cranial", + 21311: "cranium", + 21312: "crank", + 21313: "crate", + 21314: "crave", + 21315: "craving", + 21316: "crawfish", + 21321: "crawlers", + 21322: "crawling", + 21323: "crayfish", + 21324: "crayon", + 21325: "crazed", + 21326: "crazily", + 21331: "craziness", + 21332: "crazy", + 21333: "creamed", + 21334: "creamer", + 21335: "creamlike", + 21336: "crease", + 21341: "creasing", + 21342: "creatable", + 21343: "create", + 21344: "creation", + 21345: "creative", + 21346: "creature", + 21351: "credible", + 21352: "credibly", + 21353: "credit", + 21354: "creed", + 21355: "creme", + 21356: "creole", + 21361: "crepe", + 21362: "crept", + 21363: "crescent", + 21364: "crested", + 21365: "cresting", + 21366: "crestless", + 21411: "crevice", + 21412: "crewless", + 21413: "crewman", + 21414: "crewmate", + 21415: "crib", + 21416: "cricket", + 21421: "cried", + 21422: "crier", + 21423: "crimp", + 21424: "crimson", + 21425: "cringe", + 21426: "cringing", + 21431: "crinkle", + 21432: "crinkly", + 21433: "crisped", + 21434: "crisping", + 21435: "crisply", + 21436: "crispness", + 21441: "crispy", + 21442: "criteria", + 21443: "critter", + 21444: "croak", + 21445: "crock", + 21446: "crook", + 21451: "croon", + 21452: "crop", + 21453: "cross", + 21454: "crouch", + 21455: "crouton", + 21456: "crowbar", + 21461: "crowd", + 21462: "crown", + 21463: "crucial", + 21464: "crudely", + 21465: "crudeness", + 21466: "cruelly", + 21511: "cruelness", + 21512: "cruelty", + 21513: "crumb", + 21514: "crummiest", + 21515: "crummy", + 21516: "crumpet", + 21521: "crumpled", + 21522: "cruncher", + 21523: "crunching", + 21524: "crunchy", + 21525: "crusader", + 21526: "crushable", + 21531: "crushed", + 21532: "crusher", + 21533: "crushing", + 21534: "crust", + 21535: "crux", + 21536: "crying", + 21541: "cryptic", + 21542: "crystal", + 21543: "cubbyhole", + 21544: "cube", + 21545: "cubical", + 21546: "cubicle", + 21551: "cucumber", + 21552: "cuddle", + 21553: "cuddly", + 21554: "cufflink", + 21555: "culinary", + 21556: "culminate", + 21561: "culpable", + 21562: "culprit", + 21563: "cultivate", + 21564: "cultural", + 21565: "culture", + 21566: "cupbearer", + 21611: "cupcake", + 21612: "cupid", + 21613: "cupped", + 21614: "cupping", + 21615: "curable", + 21616: "curator", + 21621: "curdle", + 21622: "cure", + 21623: "curfew", + 21624: "curing", + 21625: "curled", + 21626: "curler", + 21631: "curliness", + 21632: "curling", + 21633: "curly", + 21634: "curry", + 21635: "curse", + 21636: "cursive", + 21641: "cursor", + 21642: "curtain", + 21643: "curtly", + 21644: "curtsy", + 21645: "curvature", + 21646: "curve", + 21651: "curvy", + 21652: "cushy", + 21653: "cusp", + 21654: "cussed", + 21655: "custard", + 21656: "custodian", + 21661: "custody", + 21662: "customary", + 21663: "customer", + 21664: "customize", + 21665: "customs", + 21666: "cut", + 22111: "cycle", + 22112: "cyclic", + 22113: "cycling", + 22114: "cyclist", + 22115: "cylinder", + 22116: "cymbal", + 22121: "cytoplasm", + 22122: "cytoplast", + 22123: "dab", + 22124: "dad", + 22125: "daffodil", + 22126: "dagger", + 22131: "daily", + 22132: "daintily", + 22133: "dainty", + 22134: "dairy", + 22135: "daisy", + 22136: "dallying", + 22141: "dance", + 22142: "dancing", + 22143: "dandelion", + 22144: "dander", + 22145: "dandruff", + 22146: "dandy", + 22151: "danger", + 22152: "dangle", + 22153: "dangling", + 22154: "daredevil", + 22155: "dares", + 22156: "daringly", + 22161: "darkened", + 22162: "darkening", + 22163: "darkish", + 22164: "darkness", + 22165: "darkroom", + 22166: "darling", + 22211: "darn", + 22212: "dart", + 22213: "darwinism", + 22214: "dash", + 22215: "dastardly", + 22216: "data", + 22221: "datebook", + 22222: "dating", + 22223: "daughter", + 22224: "daunting", + 22225: "dawdler", + 22226: "dawn", + 22231: "daybed", + 22232: "daybreak", + 22233: "daycare", + 22234: "daydream", + 22235: "daylight", + 22236: "daylong", + 22241: "dayroom", + 22242: "daytime", + 22243: "dazzler", + 22244: "dazzling", + 22245: "deacon", + 22246: "deafening", + 22251: "deafness", + 22252: "dealer", + 22253: "dealing", + 22254: "dealmaker", + 22255: "dealt", + 22256: "dean", + 22261: "debatable", + 22262: "debate", + 22263: "debating", + 22264: "debit", + 22265: "debrief", + 22266: "debtless", + 22311: "debtor", + 22312: "debug", + 22313: "debunk", + 22314: "decade", + 22315: "decaf", + 22316: "decal", + 22321: "decathlon", + 22322: "decay", + 22323: "deceased", + 22324: "deceit", + 22325: "deceiver", + 22326: "deceiving", + 22331: "december", + 22332: "decency", + 22333: "decent", + 22334: "deception", + 22335: "deceptive", + 22336: "decibel", + 22341: "decidable", + 22342: "decimal", + 22343: "decimeter", + 22344: "decipher", + 22345: "deck", + 22346: "declared", + 22351: "decline", + 22352: "decode", + 22353: "decompose", + 22354: "decorated", + 22355: "decorator", + 22356: "decoy", + 22361: "decrease", + 22362: "decree", + 22363: "dedicate", + 22364: "dedicator", + 22365: "deduce", + 22366: "deduct", + 22411: "deed", + 22412: "deem", + 22413: "deepen", + 22414: "deeply", + 22415: "deepness", + 22416: "deface", + 22421: "defacing", + 22422: "defame", + 22423: "default", + 22424: "defeat", + 22425: "defection", + 22426: "defective", + 22431: "defendant", + 22432: "defender", + 22433: "defense", + 22434: "defensive", + 22435: "deferral", + 22436: "deferred", + 22441: "defiance", + 22442: "defiant", + 22443: "defile", + 22444: "defiling", + 22445: "define", + 22446: "definite", + 22451: "deflate", + 22452: "deflation", + 22453: "deflator", + 22454: "deflected", + 22455: "deflector", + 22456: "defog", + 22461: "deforest", + 22462: "defraud", + 22463: "defrost", + 22464: "deftly", + 22465: "defuse", + 22466: "defy", + 22511: "degraded", + 22512: "degrading", + 22513: "degrease", + 22514: "degree", + 22515: "dehydrate", + 22516: "deity", + 22521: "dejected", + 22522: "delay", + 22523: "delegate", + 22524: "delegator", + 22525: "delete", + 22526: "deletion", + 22531: "delicacy", + 22532: "delicate", + 22533: "delicious", + 22534: "delighted", + 22535: "delirious", + 22536: "delirium", + 22541: "deliverer", + 22542: "delivery", + 22543: "delouse", + 22544: "delta", + 22545: "deluge", + 22546: "delusion", + 22551: "deluxe", + 22552: "demanding", + 22553: "demeaning", + 22554: "demeanor", + 22555: "demise", + 22556: "democracy", + 22561: "democrat", + 22562: "demote", + 22563: "demotion", + 22564: "demystify", + 22565: "denatured", + 22566: "deniable", + 22611: "denial", + 22612: "denim", + 22613: "denote", + 22614: "dense", + 22615: "density", + 22616: "dental", + 22621: "dentist", + 22622: "denture", + 22623: "deny", + 22624: "deodorant", + 22625: "deodorize", + 22626: "departed", + 22631: "departure", + 22632: "depict", + 22633: "deplete", + 22634: "depletion", + 22635: "deplored", + 22636: "deploy", + 22641: "deport", + 22642: "depose", + 22643: "depraved", + 22644: "depravity", + 22645: "deprecate", + 22646: "depress", + 22651: "deprive", + 22652: "depth", + 22653: "deputize", + 22654: "deputy", + 22655: "derail", + 22656: "deranged", + 22661: "derby", + 22662: "derived", + 22663: "desecrate", + 22664: "deserve", + 22665: "deserving", + 22666: "designate", + 23111: "designed", + 23112: "designer", + 23113: "designing", + 23114: "deskbound", + 23115: "desktop", + 23116: "deskwork", + 23121: "desolate", + 23122: "despair", + 23123: "despise", + 23124: "despite", + 23125: "destiny", + 23126: "destitute", + 23131: "destruct", + 23132: "detached", + 23133: "detail", + 23134: "detection", + 23135: "detective", + 23136: "detector", + 23141: "detention", + 23142: "detergent", + 23143: "detest", + 23144: "detonate", + 23145: "detonator", + 23146: "detoxify", + 23151: "detract", + 23152: "deuce", + 23153: "devalue", + 23154: "deviancy", + 23155: "deviant", + 23156: "deviate", + 23161: "deviation", + 23162: "deviator", + 23163: "device", + 23164: "devious", + 23165: "devotedly", + 23166: "devotee", + 23211: "devotion", + 23212: "devourer", + 23213: "devouring", + 23214: "devoutly", + 23215: "dexterity", + 23216: "dexterous", + 23221: "diabetes", + 23222: "diabetic", + 23223: "diabolic", + 23224: "diagnoses", + 23225: "diagnosis", + 23226: "diagram", + 23231: "dial", + 23232: "diameter", + 23233: "diaper", + 23234: "diaphragm", + 23235: "diary", + 23236: "dice", + 23241: "dicing", + 23242: "dictate", + 23243: "dictation", + 23244: "dictator", + 23245: "difficult", + 23246: "diffused", + 23251: "diffuser", + 23252: "diffusion", + 23253: "diffusive", + 23254: "dig", + 23255: "dilation", + 23256: "diligence", + 23261: "diligent", + 23262: "dill", + 23263: "dilute", + 23264: "dime", + 23265: "diminish", + 23266: "dimly", + 23311: "dimmed", + 23312: "dimmer", + 23313: "dimness", + 23314: "dimple", + 23315: "diner", + 23316: "dingbat", + 23321: "dinghy", + 23322: "dinginess", + 23323: "dingo", + 23324: "dingy", + 23325: "dining", + 23326: "dinner", + 23331: "diocese", + 23332: "dioxide", + 23333: "diploma", + 23334: "dipped", + 23335: "dipper", + 23336: "dipping", + 23341: "directed", + 23342: "direction", + 23343: "directive", + 23344: "directly", + 23345: "directory", + 23346: "direness", + 23351: "dirtiness", + 23352: "disabled", + 23353: "disagree", + 23354: "disallow", + 23355: "disarm", + 23356: "disarray", + 23361: "disaster", + 23362: "disband", + 23363: "disbelief", + 23364: "disburse", + 23365: "discard", + 23366: "discern", + 23411: "discharge", + 23412: "disclose", + 23413: "discolor", + 23414: "discount", + 23415: "discourse", + 23416: "discover", + 23421: "discuss", + 23422: "disdain", + 23423: "disengage", + 23424: "disfigure", + 23425: "disgrace", + 23426: "dish", + 23431: "disinfect", + 23432: "disjoin", + 23433: "disk", + 23434: "dislike", + 23435: "disliking", + 23436: "dislocate", + 23441: "dislodge", + 23442: "disloyal", + 23443: "dismantle", + 23444: "dismay", + 23445: "dismiss", + 23446: "dismount", + 23451: "disobey", + 23452: "disorder", + 23453: "disown", + 23454: "disparate", + 23455: "disparity", + 23456: "dispatch", + 23461: "dispense", + 23462: "dispersal", + 23463: "dispersed", + 23464: "disperser", + 23465: "displace", + 23466: "display", + 23511: "displease", + 23512: "disposal", + 23513: "dispose", + 23514: "disprove", + 23515: "dispute", + 23516: "disregard", + 23521: "disrupt", + 23522: "dissuade", + 23523: "distance", + 23524: "distant", + 23525: "distaste", + 23526: "distill", + 23531: "distinct", + 23532: "distort", + 23533: "distract", + 23534: "distress", + 23535: "district", + 23536: "distrust", + 23541: "ditch", + 23542: "ditto", + 23543: "ditzy", + 23544: "dividable", + 23545: "divided", + 23546: "dividend", + 23551: "dividers", + 23552: "dividing", + 23553: "divinely", + 23554: "diving", + 23555: "divinity", + 23556: "divisible", + 23561: "divisibly", + 23562: "division", + 23563: "divisive", + 23564: "divorcee", + 23565: "dizziness", + 23566: "dizzy", + 23611: "doable", + 23612: "docile", + 23613: "dock", + 23614: "doctrine", + 23615: "document", + 23616: "dodge", + 23621: "dodgy", + 23622: "doily", + 23623: "doing", + 23624: "dole", + 23625: "dollar", + 23626: "dollhouse", + 23631: "dollop", + 23632: "dolly", + 23633: "dolphin", + 23634: "domain", + 23635: "domelike", + 23636: "domestic", + 23641: "dominion", + 23642: "dominoes", + 23643: "donated", + 23644: "donation", + 23645: "donator", + 23646: "donor", + 23651: "donut", + 23652: "doodle", + 23653: "doorbell", + 23654: "doorframe", + 23655: "doorknob", + 23656: "doorman", + 23661: "doormat", + 23662: "doornail", + 23663: "doorpost", + 23664: "doorstep", + 23665: "doorstop", + 23666: "doorway", + 24111: "doozy", + 24112: "dork", + 24113: "dormitory", + 24114: "dorsal", + 24115: "dosage", + 24116: "dose", + 24121: "dotted", + 24122: "doubling", + 24123: "douche", + 24124: "dove", + 24125: "down", + 24126: "dowry", + 24131: "doze", + 24132: "drab", + 24133: "dragging", + 24134: "dragonfly", + 24135: "dragonish", + 24136: "dragster", + 24141: "drainable", + 24142: "drainage", + 24143: "drained", + 24144: "drainer", + 24145: "drainpipe", + 24146: "dramatic", + 24151: "dramatize", + 24152: "drank", + 24153: "drapery", + 24154: "drastic", + 24155: "draw", + 24156: "dreaded", + 24161: "dreadful", + 24162: "dreadlock", + 24163: "dreamboat", + 24164: "dreamily", + 24165: "dreamland", + 24166: "dreamless", + 24211: "dreamlike", + 24212: "dreamt", + 24213: "dreamy", + 24214: "drearily", + 24215: "dreary", + 24216: "drench", + 24221: "dress", + 24222: "drew", + 24223: "dribble", + 24224: "dried", + 24225: "drier", + 24226: "drift", + 24231: "driller", + 24232: "drilling", + 24233: "drinkable", + 24234: "drinking", + 24235: "dripping", + 24236: "drippy", + 24241: "drivable", + 24242: "driven", + 24243: "driver", + 24244: "driveway", + 24245: "driving", + 24246: "drizzle", + 24251: "drizzly", + 24252: "drone", + 24253: "drool", + 24254: "droop", + 24255: "drop-down", + 24256: "dropbox", + 24261: "dropkick", + 24262: "droplet", + 24263: "dropout", + 24264: "dropper", + 24265: "drove", + 24266: "drown", + 24311: "drowsily", + 24312: "drudge", + 24313: "drum", + 24314: "dry", + 24315: "dubbed", + 24316: "dubiously", + 24321: "duchess", + 24322: "duckbill", + 24323: "ducking", + 24324: "duckling", + 24325: "ducktail", + 24326: "ducky", + 24331: "duct", + 24332: "dude", + 24333: "duffel", + 24334: "dugout", + 24335: "duh", + 24336: "duke", + 24341: "duller", + 24342: "dullness", + 24343: "duly", + 24344: "dumping", + 24345: "dumpling", + 24346: "dumpster", + 24351: "duo", + 24352: "dupe", + 24353: "duplex", + 24354: "duplicate", + 24355: "duplicity", + 24356: "durable", + 24361: "durably", + 24362: "duration", + 24363: "duress", + 24364: "during", + 24365: "dusk", + 24366: "dust", + 24411: "dutiful", + 24412: "duty", + 24413: "duvet", + 24414: "dwarf", + 24415: "dweeb", + 24416: "dwelled", + 24421: "dweller", + 24422: "dwelling", + 24423: "dwindle", + 24424: "dwindling", + 24425: "dynamic", + 24426: "dynamite", + 24431: "dynasty", + 24432: "dyslexia", + 24433: "dyslexic", + 24434: "each", + 24435: "eagle", + 24436: "earache", + 24441: "eardrum", + 24442: "earflap", + 24443: "earful", + 24444: "earlobe", + 24445: "early", + 24446: "earmark", + 24451: "earmuff", + 24452: "earphone", + 24453: "earpiece", + 24454: "earplugs", + 24455: "earring", + 24456: "earshot", + 24461: "earthen", + 24462: "earthlike", + 24463: "earthling", + 24464: "earthly", + 24465: "earthworm", + 24466: "earthy", + 24511: "earwig", + 24512: "easeful", + 24513: "easel", + 24514: "easiest", + 24515: "easily", + 24516: "easiness", + 24521: "easing", + 24522: "eastbound", + 24523: "eastcoast", + 24524: "easter", + 24525: "eastward", + 24526: "eatable", + 24531: "eaten", + 24532: "eatery", + 24533: "eating", + 24534: "eats", + 24535: "ebay", + 24536: "ebony", + 24541: "ebook", + 24542: "ecard", + 24543: "eccentric", + 24544: "echo", + 24545: "eclair", + 24546: "eclipse", + 24551: "ecologist", + 24552: "ecology", + 24553: "economic", + 24554: "economist", + 24555: "economy", + 24556: "ecosphere", + 24561: "ecosystem", + 24562: "edge", + 24563: "edginess", + 24564: "edging", + 24565: "edgy", + 24566: "edition", + 24611: "editor", + 24612: "educated", + 24613: "education", + 24614: "educator", + 24615: "eel", + 24616: "effective", + 24621: "effects", + 24622: "efficient", + 24623: "effort", + 24624: "eggbeater", + 24625: "egging", + 24626: "eggnog", + 24631: "eggplant", + 24632: "eggshell", + 24633: "egomaniac", + 24634: "egotism", + 24635: "egotistic", + 24636: "either", + 24641: "eject", + 24642: "elaborate", + 24643: "elastic", + 24644: "elated", + 24645: "elbow", + 24646: "eldercare", + 24651: "elderly", + 24652: "eldest", + 24653: "electable", + 24654: "election", + 24655: "elective", + 24656: "elephant", + 24661: "elevate", + 24662: "elevating", + 24663: "elevation", + 24664: "elevator", + 24665: "eleven", + 24666: "elf", + 25111: "eligible", + 25112: "eligibly", + 25113: "eliminate", + 25114: "elite", + 25115: "elitism", + 25116: "elixir", + 25121: "elk", + 25122: "ellipse", + 25123: "elliptic", + 25124: "elm", + 25125: "elongated", + 25126: "elope", + 25131: "eloquence", + 25132: "eloquent", + 25133: "elsewhere", + 25134: "elude", + 25135: "elusive", + 25136: "elves", + 25141: "email", + 25142: "embargo", + 25143: "embark", + 25144: "embassy", + 25145: "embattled", + 25146: "embellish", + 25151: "ember", + 25152: "embezzle", + 25153: "emblaze", + 25154: "emblem", + 25155: "embody", + 25156: "embolism", + 25161: "emboss", + 25162: "embroider", + 25163: "emcee", + 25164: "emerald", + 25165: "emergency", + 25166: "emission", + 25211: "emit", + 25212: "emote", + 25213: "emoticon", + 25214: "emotion", + 25215: "empathic", + 25216: "empathy", + 25221: "emperor", + 25222: "emphases", + 25223: "emphasis", + 25224: "emphasize", + 25225: "emphatic", + 25226: "empirical", + 25231: "employed", + 25232: "employee", + 25233: "employer", + 25234: "emporium", + 25235: "empower", + 25236: "emptier", + 25241: "emptiness", + 25242: "empty", + 25243: "emu", + 25244: "enable", + 25245: "enactment", + 25246: "enamel", + 25251: "enchanted", + 25252: "enchilada", + 25253: "encircle", + 25254: "enclose", + 25255: "enclosure", + 25256: "encode", + 25261: "encore", + 25262: "encounter", + 25263: "encourage", + 25264: "encroach", + 25265: "encrust", + 25266: "encrypt", + 25311: "endanger", + 25312: "endeared", + 25313: "endearing", + 25314: "ended", + 25315: "ending", + 25316: "endless", + 25321: "endnote", + 25322: "endocrine", + 25323: "endorphin", + 25324: "endorse", + 25325: "endowment", + 25326: "endpoint", + 25331: "endurable", + 25332: "endurance", + 25333: "enduring", + 25334: "energetic", + 25335: "energize", + 25336: "energy", + 25341: "enforced", + 25342: "enforcer", + 25343: "engaged", + 25344: "engaging", + 25345: "engine", + 25346: "engorge", + 25351: "engraved", + 25352: "engraver", + 25353: "engraving", + 25354: "engross", + 25355: "engulf", + 25356: "enhance", + 25361: "enigmatic", + 25362: "enjoyable", + 25363: "enjoyably", + 25364: "enjoyer", + 25365: "enjoying", + 25366: "enjoyment", + 25411: "enlarged", + 25412: "enlarging", + 25413: "enlighten", + 25414: "enlisted", + 25415: "enquirer", + 25416: "enrage", + 25421: "enrich", + 25422: "enroll", + 25423: "enslave", + 25424: "ensnare", + 25425: "ensure", + 25426: "entail", + 25431: "entangled", + 25432: "entering", + 25433: "entertain", + 25434: "enticing", + 25435: "entire", + 25436: "entitle", + 25441: "entity", + 25442: "entomb", + 25443: "entourage", + 25444: "entrap", + 25445: "entree", + 25446: "entrench", + 25451: "entrust", + 25452: "entryway", + 25453: "entwine", + 25454: "enunciate", + 25455: "envelope", + 25456: "enviable", + 25461: "enviably", + 25462: "envious", + 25463: "envision", + 25464: "envoy", + 25465: "envy", + 25466: "enzyme", + 25511: "epic", + 25512: "epidemic", + 25513: "epidermal", + 25514: "epidermis", + 25515: "epidural", + 25516: "epilepsy", + 25521: "epileptic", + 25522: "epilogue", + 25523: "epiphany", + 25524: "episode", + 25525: "equal", + 25526: "equate", + 25531: "equation", + 25532: "equator", + 25533: "equinox", + 25534: "equipment", + 25535: "equity", + 25536: "equivocal", + 25541: "eradicate", + 25542: "erasable", + 25543: "erased", + 25544: "eraser", + 25545: "erasure", + 25546: "ergonomic", + 25551: "errand", + 25552: "errant", + 25553: "erratic", + 25554: "error", + 25555: "erupt", + 25556: "escalate", + 25561: "escalator", + 25562: "escapable", + 25563: "escapade", + 25564: "escapist", + 25565: "escargot", + 25566: "eskimo", + 25611: "esophagus", + 25612: "espionage", + 25613: "espresso", + 25614: "esquire", + 25615: "essay", + 25616: "essence", + 25621: "essential", + 25622: "establish", + 25623: "estate", + 25624: "esteemed", + 25625: "estimate", + 25626: "estimator", + 25631: "estranged", + 25632: "estrogen", + 25633: "etching", + 25634: "eternal", + 25635: "eternity", + 25636: "ethanol", + 25641: "ether", + 25642: "ethically", + 25643: "ethics", + 25644: "euphemism", + 25645: "evacuate", + 25646: "evacuee", + 25651: "evade", + 25652: "evaluate", + 25653: "evaluator", + 25654: "evaporate", + 25655: "evasion", + 25656: "evasive", + 25661: "even", + 25662: "everglade", + 25663: "evergreen", + 25664: "everybody", + 25665: "everyday", + 25666: "everyone", + 26111: "evict", + 26112: "evidence", + 26113: "evident", + 26114: "evil", + 26115: "evoke", + 26116: "evolution", + 26121: "evolve", + 26122: "exact", + 26123: "exalted", + 26124: "example", + 26125: "excavate", + 26126: "excavator", + 26131: "exceeding", + 26132: "exception", + 26133: "excess", + 26134: "exchange", + 26135: "excitable", + 26136: "exciting", + 26141: "exclaim", + 26142: "exclude", + 26143: "excluding", + 26144: "exclusion", + 26145: "exclusive", + 26146: "excretion", + 26151: "excretory", + 26152: "excursion", + 26153: "excusable", + 26154: "excusably", + 26155: "excuse", + 26156: "exemplary", + 26161: "exemplify", + 26162: "exemption", + 26163: "exerciser", + 26164: "exert", + 26165: "exes", + 26166: "exfoliate", + 26211: "exhale", + 26212: "exhaust", + 26213: "exhume", + 26214: "exile", + 26215: "existing", + 26216: "exit", + 26221: "exodus", + 26222: "exonerate", + 26223: "exorcism", + 26224: "exorcist", + 26225: "expand", + 26226: "expanse", + 26231: "expansion", + 26232: "expansive", + 26233: "expectant", + 26234: "expedited", + 26235: "expediter", + 26236: "expel", + 26241: "expend", + 26242: "expenses", + 26243: "expensive", + 26244: "expert", + 26245: "expire", + 26246: "expiring", + 26251: "explain", + 26252: "expletive", + 26253: "explicit", + 26254: "explode", + 26255: "exploit", + 26256: "explore", + 26261: "exploring", + 26262: "exponent", + 26263: "exporter", + 26264: "exposable", + 26265: "expose", + 26266: "exposure", + 26311: "express", + 26312: "expulsion", + 26313: "exquisite", + 26314: "extended", + 26315: "extending", + 26316: "extent", + 26321: "extenuate", + 26322: "exterior", + 26323: "external", + 26324: "extinct", + 26325: "extortion", + 26326: "extradite", + 26331: "extras", + 26332: "extrovert", + 26333: "extrude", + 26334: "extruding", + 26335: "exuberant", + 26336: "fable", + 26341: "fabric", + 26342: "fabulous", + 26343: "facebook", + 26344: "facecloth", + 26345: "facedown", + 26346: "faceless", + 26351: "facelift", + 26352: "faceplate", + 26353: "faceted", + 26354: "facial", + 26355: "facility", + 26356: "facing", + 26361: "facsimile", + 26362: "faction", + 26363: "factoid", + 26364: "factor", + 26365: "factsheet", + 26366: "factual", + 26411: "faculty", + 26412: "fade", + 26413: "fading", + 26414: "failing", + 26415: "falcon", + 26416: "fall", + 26421: "false", + 26422: "falsify", + 26423: "fame", + 26424: "familiar", + 26425: "family", + 26426: "famine", + 26431: "famished", + 26432: "fanatic", + 26433: "fancied", + 26434: "fanciness", + 26435: "fancy", + 26436: "fanfare", + 26441: "fang", + 26442: "fanning", + 26443: "fantasize", + 26444: "fantastic", + 26445: "fantasy", + 26446: "fascism", + 26451: "fastball", + 26452: "faster", + 26453: "fasting", + 26454: "fastness", + 26455: "faucet", + 26456: "favorable", + 26461: "favorably", + 26462: "favored", + 26463: "favoring", + 26464: "favorite", + 26465: "fax", + 26466: "feast", + 26511: "federal", + 26512: "fedora", + 26513: "feeble", + 26514: "feed", + 26515: "feel", + 26516: "feisty", + 26521: "feline", + 26522: "felt-tip", + 26523: "feminine", + 26524: "feminism", + 26525: "feminist", + 26526: "feminize", + 26531: "femur", + 26532: "fence", + 26533: "fencing", + 26534: "fender", + 26535: "ferment", + 26536: "fernlike", + 26541: "ferocious", + 26542: "ferocity", + 26543: "ferret", + 26544: "ferris", + 26545: "ferry", + 26546: "fervor", + 26551: "fester", + 26552: "festival", + 26553: "festive", + 26554: "festivity", + 26555: "fetal", + 26556: "fetch", + 26561: "fever", + 26562: "fiber", + 26563: "fiction", + 26564: "fiddle", + 26565: "fiddling", + 26566: "fidelity", + 26611: "fidgeting", + 26612: "fidgety", + 26613: "fifteen", + 26614: "fifth", + 26615: "fiftieth", + 26616: "fifty", + 26621: "figment", + 26622: "figure", + 26623: "figurine", + 26624: "filing", + 26625: "filled", + 26626: "filler", + 26631: "filling", + 26632: "film", + 26633: "filter", + 26634: "filth", + 26635: "filtrate", + 26636: "finale", + 26641: "finalist", + 26642: "finalize", + 26643: "finally", + 26644: "finance", + 26645: "financial", + 26646: "finch", + 26651: "fineness", + 26652: "finer", + 26653: "finicky", + 26654: "finished", + 26655: "finisher", + 26656: "finishing", + 26661: "finite", + 26662: "finless", + 26663: "finlike", + 26664: "fiscally", + 26665: "fit", + 26666: "five", + 31111: "flaccid", + 31112: "flagman", + 31113: "flagpole", + 31114: "flagship", + 31115: "flagstick", + 31116: "flagstone", + 31121: "flail", + 31122: "flakily", + 31123: "flaky", + 31124: "flame", + 31125: "flammable", + 31126: "flanked", + 31131: "flanking", + 31132: "flannels", + 31133: "flap", + 31134: "flaring", + 31135: "flashback", + 31136: "flashbulb", + 31141: "flashcard", + 31142: "flashily", + 31143: "flashing", + 31144: "flashy", + 31145: "flask", + 31146: "flatbed", + 31151: "flatfoot", + 31152: "flatly", + 31153: "flatness", + 31154: "flatten", + 31155: "flattered", + 31156: "flatterer", + 31161: "flattery", + 31162: "flattop", + 31163: "flatware", + 31164: "flatworm", + 31165: "flavored", + 31166: "flavorful", + 31211: "flavoring", + 31212: "flaxseed", + 31213: "fled", + 31214: "fleshed", + 31215: "fleshy", + 31216: "flick", + 31221: "flier", + 31222: "flight", + 31223: "flinch", + 31224: "fling", + 31225: "flint", + 31226: "flip", + 31231: "flirt", + 31232: "float", + 31233: "flock", + 31234: "flogging", + 31235: "flop", + 31236: "floral", + 31241: "florist", + 31242: "floss", + 31243: "flounder", + 31244: "flyable", + 31245: "flyaway", + 31246: "flyer", + 31251: "flying", + 31252: "flyover", + 31253: "flypaper", + 31254: "foam", + 31255: "foe", + 31256: "fog", + 31261: "foil", + 31262: "folic", + 31263: "folk", + 31264: "follicle", + 31265: "follow", + 31266: "fondling", + 31311: "fondly", + 31312: "fondness", + 31313: "fondue", + 31314: "font", + 31315: "food", + 31316: "fool", + 31321: "footage", + 31322: "football", + 31323: "footbath", + 31324: "footboard", + 31325: "footer", + 31326: "footgear", + 31331: "foothill", + 31332: "foothold", + 31333: "footing", + 31334: "footless", + 31335: "footman", + 31336: "footnote", + 31341: "footpad", + 31342: "footpath", + 31343: "footprint", + 31344: "footrest", + 31345: "footsie", + 31346: "footsore", + 31351: "footwear", + 31352: "footwork", + 31353: "fossil", + 31354: "foster", + 31355: "founder", + 31356: "founding", + 31361: "fountain", + 31362: "fox", + 31363: "foyer", + 31364: "fraction", + 31365: "fracture", + 31366: "fragile", + 31411: "fragility", + 31412: "fragment", + 31413: "fragrance", + 31414: "fragrant", + 31415: "frail", + 31416: "frame", + 31421: "framing", + 31422: "frantic", + 31423: "fraternal", + 31424: "frayed", + 31425: "fraying", + 31426: "frays", + 31431: "freckled", + 31432: "freckles", + 31433: "freebase", + 31434: "freebee", + 31435: "freebie", + 31436: "freedom", + 31441: "freefall", + 31442: "freehand", + 31443: "freeing", + 31444: "freeload", + 31445: "freely", + 31446: "freemason", + 31451: "freeness", + 31452: "freestyle", + 31453: "freeware", + 31454: "freeway", + 31455: "freewill", + 31456: "freezable", + 31461: "freezing", + 31462: "freight", + 31463: "french", + 31464: "frenzied", + 31465: "frenzy", + 31466: "frequency", + 31511: "frequent", + 31512: "fresh", + 31513: "fretful", + 31514: "fretted", + 31515: "friction", + 31516: "friday", + 31521: "fridge", + 31522: "fried", + 31523: "friend", + 31524: "frighten", + 31525: "frightful", + 31526: "frigidity", + 31531: "frigidly", + 31532: "frill", + 31533: "fringe", + 31534: "frisbee", + 31535: "frisk", + 31536: "fritter", + 31541: "frivolous", + 31542: "frolic", + 31543: "from", + 31544: "front", + 31545: "frostbite", + 31546: "frosted", + 31551: "frostily", + 31552: "frosting", + 31553: "frostlike", + 31554: "frosty", + 31555: "froth", + 31556: "frown", + 31561: "frozen", + 31562: "fructose", + 31563: "frugality", + 31564: "frugally", + 31565: "fruit", + 31566: "frustrate", + 31611: "frying", + 31612: "gab", + 31613: "gaffe", + 31614: "gag", + 31615: "gainfully", + 31616: "gaining", + 31621: "gains", + 31622: "gala", + 31623: "gallantly", + 31624: "galleria", + 31625: "gallery", + 31626: "galley", + 31631: "gallon", + 31632: "gallows", + 31633: "gallstone", + 31634: "galore", + 31635: "galvanize", + 31636: "gambling", + 31641: "game", + 31642: "gaming", + 31643: "gamma", + 31644: "gander", + 31645: "gangly", + 31646: "gangrene", + 31651: "gangway", + 31652: "gap", + 31653: "garage", + 31654: "garbage", + 31655: "garden", + 31656: "gargle", + 31661: "garland", + 31662: "garlic", + 31663: "garment", + 31664: "garnet", + 31665: "garnish", + 31666: "garter", + 32111: "gas", + 32112: "gatherer", + 32113: "gathering", + 32114: "gating", + 32115: "gauging", + 32116: "gauntlet", + 32121: "gauze", + 32122: "gave", + 32123: "gawk", + 32124: "gazing", + 32125: "gear", + 32126: "gecko", + 32131: "geek", + 32132: "geiger", + 32133: "gem", + 32134: "gender", + 32135: "generic", + 32136: "generous", + 32141: "genetics", + 32142: "genre", + 32143: "gentile", + 32144: "gentleman", + 32145: "gently", + 32146: "gents", + 32151: "geography", + 32152: "geologic", + 32153: "geologist", + 32154: "geology", + 32155: "geometric", + 32156: "geometry", + 32161: "geranium", + 32162: "gerbil", + 32163: "geriatric", + 32164: "germicide", + 32165: "germinate", + 32166: "germless", + 32211: "germproof", + 32212: "gestate", + 32213: "gestation", + 32214: "gesture", + 32215: "getaway", + 32216: "getting", + 32221: "getup", + 32222: "giant", + 32223: "gibberish", + 32224: "giblet", + 32225: "giddily", + 32226: "giddiness", + 32231: "giddy", + 32232: "gift", + 32233: "gigabyte", + 32234: "gigahertz", + 32235: "gigantic", + 32236: "giggle", + 32241: "giggling", + 32242: "giggly", + 32243: "gigolo", + 32244: "gilled", + 32245: "gills", + 32246: "gimmick", + 32251: "girdle", + 32252: "giveaway", + 32253: "given", + 32254: "giver", + 32255: "giving", + 32256: "gizmo", + 32261: "gizzard", + 32262: "glacial", + 32263: "glacier", + 32264: "glade", + 32265: "gladiator", + 32266: "gladly", + 32311: "glamorous", + 32312: "glamour", + 32313: "glance", + 32314: "glancing", + 32315: "glandular", + 32316: "glare", + 32321: "glaring", + 32322: "glass", + 32323: "glaucoma", + 32324: "glazing", + 32325: "gleaming", + 32326: "gleeful", + 32331: "glider", + 32332: "gliding", + 32333: "glimmer", + 32334: "glimpse", + 32335: "glisten", + 32336: "glitch", + 32341: "glitter", + 32342: "glitzy", + 32343: "gloater", + 32344: "gloating", + 32345: "gloomily", + 32346: "gloomy", + 32351: "glorified", + 32352: "glorifier", + 32353: "glorify", + 32354: "glorious", + 32355: "glory", + 32356: "gloss", + 32361: "glove", + 32362: "glowing", + 32363: "glowworm", + 32364: "glucose", + 32365: "glue", + 32366: "gluten", + 32411: "glutinous", + 32412: "glutton", + 32413: "gnarly", + 32414: "gnat", + 32415: "goal", + 32416: "goatskin", + 32421: "goes", + 32422: "goggles", + 32423: "going", + 32424: "goldfish", + 32425: "goldmine", + 32426: "goldsmith", + 32431: "golf", + 32432: "goliath", + 32433: "gonad", + 32434: "gondola", + 32435: "gone", + 32436: "gong", + 32441: "good", + 32442: "gooey", + 32443: "goofball", + 32444: "goofiness", + 32445: "goofy", + 32446: "google", + 32451: "goon", + 32452: "gopher", + 32453: "gore", + 32454: "gorged", + 32455: "gorgeous", + 32456: "gory", + 32461: "gosling", + 32462: "gossip", + 32463: "gothic", + 32464: "gotten", + 32465: "gout", + 32466: "gown", + 32511: "grab", + 32512: "graceful", + 32513: "graceless", + 32514: "gracious", + 32515: "gradation", + 32516: "graded", + 32521: "grader", + 32522: "gradient", + 32523: "grading", + 32524: "gradually", + 32525: "graduate", + 32526: "graffiti", + 32531: "grafted", + 32532: "grafting", + 32533: "grain", + 32534: "granddad", + 32535: "grandkid", + 32536: "grandly", + 32541: "grandma", + 32542: "grandpa", + 32543: "grandson", + 32544: "granite", + 32545: "granny", + 32546: "granola", + 32551: "grant", + 32552: "granular", + 32553: "grape", + 32554: "graph", + 32555: "grapple", + 32556: "grappling", + 32561: "grasp", + 32562: "grass", + 32563: "gratified", + 32564: "gratify", + 32565: "grating", + 32566: "gratitude", + 32611: "gratuity", + 32612: "gravel", + 32613: "graveness", + 32614: "graves", + 32615: "graveyard", + 32616: "gravitate", + 32621: "gravity", + 32622: "gravy", + 32623: "gray", + 32624: "grazing", + 32625: "greasily", + 32626: "greedily", + 32631: "greedless", + 32632: "greedy", + 32633: "green", + 32634: "greeter", + 32635: "greeting", + 32636: "grew", + 32641: "greyhound", + 32642: "grid", + 32643: "grief", + 32644: "grievance", + 32645: "grieving", + 32646: "grievous", + 32651: "grill", + 32652: "grimace", + 32653: "grimacing", + 32654: "grime", + 32655: "griminess", + 32656: "grimy", + 32661: "grinch", + 32662: "grinning", + 32663: "grip", + 32664: "gristle", + 32665: "grit", + 32666: "groggily", + 33111: "groggy", + 33112: "groin", + 33113: "groom", + 33114: "groove", + 33115: "grooving", + 33116: "groovy", + 33121: "grope", + 33122: "ground", + 33123: "grouped", + 33124: "grout", + 33125: "grove", + 33126: "grower", + 33131: "growing", + 33132: "growl", + 33133: "grub", + 33134: "grudge", + 33135: "grudging", + 33136: "grueling", + 33141: "gruffly", + 33142: "grumble", + 33143: "grumbling", + 33144: "grumbly", + 33145: "grumpily", + 33146: "grunge", + 33151: "grunt", + 33152: "guacamole", + 33153: "guidable", + 33154: "guidance", + 33155: "guide", + 33156: "guiding", + 33161: "guileless", + 33162: "guise", + 33163: "gulf", + 33164: "gullible", + 33165: "gully", + 33166: "gulp", + 33211: "gumball", + 33212: "gumdrop", + 33213: "gumminess", + 33214: "gumming", + 33215: "gummy", + 33216: "gurgle", + 33221: "gurgling", + 33222: "guru", + 33223: "gush", + 33224: "gusto", + 33225: "gusty", + 33226: "gutless", + 33231: "guts", + 33232: "gutter", + 33233: "guy", + 33234: "guzzler", + 33235: "gyration", + 33236: "habitable", + 33241: "habitant", + 33242: "habitat", + 33243: "habitual", + 33244: "hacked", + 33245: "hacker", + 33246: "hacking", + 33251: "hacksaw", + 33252: "had", + 33253: "haggler", + 33254: "haiku", + 33255: "half", + 33256: "halogen", + 33261: "halt", + 33262: "halved", + 33263: "halves", + 33264: "hamburger", + 33265: "hamlet", + 33266: "hammock", + 33311: "hamper", + 33312: "hamster", + 33313: "hamstring", + 33314: "handbag", + 33315: "handball", + 33316: "handbook", + 33321: "handbrake", + 33322: "handcart", + 33323: "handclap", + 33324: "handclasp", + 33325: "handcraft", + 33326: "handcuff", + 33331: "handed", + 33332: "handful", + 33333: "handgrip", + 33334: "handgun", + 33335: "handheld", + 33336: "handiness", + 33341: "handiwork", + 33342: "handlebar", + 33343: "handled", + 33344: "handler", + 33345: "handling", + 33346: "handmade", + 33351: "handoff", + 33352: "handpick", + 33353: "handprint", + 33354: "handrail", + 33355: "handsaw", + 33356: "handset", + 33361: "handsfree", + 33362: "handshake", + 33363: "handstand", + 33364: "handwash", + 33365: "handwork", + 33366: "handwoven", + 33411: "handwrite", + 33412: "handyman", + 33413: "hangnail", + 33414: "hangout", + 33415: "hangover", + 33416: "hangup", + 33421: "hankering", + 33422: "hankie", + 33423: "hanky", + 33424: "haphazard", + 33425: "happening", + 33426: "happier", + 33431: "happiest", + 33432: "happily", + 33433: "happiness", + 33434: "happy", + 33435: "harbor", + 33436: "hardcopy", + 33441: "hardcore", + 33442: "hardcover", + 33443: "harddisk", + 33444: "hardened", + 33445: "hardener", + 33446: "hardening", + 33451: "hardhat", + 33452: "hardhead", + 33453: "hardiness", + 33454: "hardly", + 33455: "hardness", + 33456: "hardship", + 33461: "hardware", + 33462: "hardwired", + 33463: "hardwood", + 33464: "hardy", + 33465: "harmful", + 33466: "harmless", + 33511: "harmonica", + 33512: "harmonics", + 33513: "harmonize", + 33514: "harmony", + 33515: "harness", + 33516: "harpist", + 33521: "harsh", + 33522: "harvest", + 33523: "hash", + 33524: "hassle", + 33525: "haste", + 33526: "hastily", + 33531: "hastiness", + 33532: "hasty", + 33533: "hatbox", + 33534: "hatchback", + 33535: "hatchery", + 33536: "hatchet", + 33541: "hatching", + 33542: "hatchling", + 33543: "hate", + 33544: "hatless", + 33545: "hatred", + 33546: "haunt", + 33551: "haven", + 33552: "hazard", + 33553: "hazelnut", + 33554: "hazily", + 33555: "haziness", + 33556: "hazing", + 33561: "hazy", + 33562: "headache", + 33563: "headband", + 33564: "headboard", + 33565: "headcount", + 33566: "headdress", + 33611: "headed", + 33612: "header", + 33613: "headfirst", + 33614: "headgear", + 33615: "heading", + 33616: "headlamp", + 33621: "headless", + 33622: "headlock", + 33623: "headphone", + 33624: "headpiece", + 33625: "headrest", + 33626: "headroom", + 33631: "headscarf", + 33632: "headset", + 33633: "headsman", + 33634: "headstand", + 33635: "headstone", + 33636: "headway", + 33641: "headwear", + 33642: "heap", + 33643: "heat", + 33644: "heave", + 33645: "heavily", + 33646: "heaviness", + 33651: "heaving", + 33652: "hedge", + 33653: "hedging", + 33654: "heftiness", + 33655: "hefty", + 33656: "helium", + 33661: "helmet", + 33662: "helper", + 33663: "helpful", + 33664: "helping", + 33665: "helpless", + 33666: "helpline", + 34111: "hemlock", + 34112: "hemstitch", + 34113: "hence", + 34114: "henchman", + 34115: "henna", + 34116: "herald", + 34121: "herbal", + 34122: "herbicide", + 34123: "herbs", + 34124: "heritage", + 34125: "hermit", + 34126: "heroics", + 34131: "heroism", + 34132: "herring", + 34133: "herself", + 34134: "hertz", + 34135: "hesitancy", + 34136: "hesitant", + 34141: "hesitate", + 34142: "hexagon", + 34143: "hexagram", + 34144: "hubcap", + 34145: "huddle", + 34146: "huddling", + 34151: "huff", + 34152: "hug", + 34153: "hula", + 34154: "hulk", + 34155: "hull", + 34156: "human", + 34161: "humble", + 34162: "humbling", + 34163: "humbly", + 34164: "humid", + 34165: "humiliate", + 34166: "humility", + 34211: "humming", + 34212: "hummus", + 34213: "humongous", + 34214: "humorist", + 34215: "humorless", + 34216: "humorous", + 34221: "humpback", + 34222: "humped", + 34223: "humvee", + 34224: "hunchback", + 34225: "hundredth", + 34226: "hunger", + 34231: "hungrily", + 34232: "hungry", + 34233: "hunk", + 34234: "hunter", + 34235: "hunting", + 34236: "huntress", + 34241: "huntsman", + 34242: "hurdle", + 34243: "hurled", + 34244: "hurler", + 34245: "hurling", + 34246: "hurray", + 34251: "hurricane", + 34252: "hurried", + 34253: "hurry", + 34254: "hurt", + 34255: "husband", + 34256: "hush", + 34261: "husked", + 34262: "huskiness", + 34263: "hut", + 34264: "hybrid", + 34265: "hydrant", + 34266: "hydrated", + 34311: "hydration", + 34312: "hydrogen", + 34313: "hydroxide", + 34314: "hyperlink", + 34315: "hypertext", + 34316: "hyphen", + 34321: "hypnoses", + 34322: "hypnosis", + 34323: "hypnotic", + 34324: "hypnotism", + 34325: "hypnotist", + 34326: "hypnotize", + 34331: "hypocrisy", + 34332: "hypocrite", + 34333: "ibuprofen", + 34334: "ice", + 34335: "iciness", + 34336: "icing", + 34341: "icky", + 34342: "icon", + 34343: "icy", + 34344: "idealism", + 34345: "idealist", + 34346: "idealize", + 34351: "ideally", + 34352: "idealness", + 34353: "identical", + 34354: "identify", + 34355: "identity", + 34356: "ideology", + 34361: "idiocy", + 34362: "idiom", + 34363: "idly", + 34364: "igloo", + 34365: "ignition", + 34366: "ignore", + 34411: "iguana", + 34412: "illicitly", + 34413: "illusion", + 34414: "illusive", + 34415: "image", + 34416: "imaginary", + 34421: "imagines", + 34422: "imaging", + 34423: "imbecile", + 34424: "imitate", + 34425: "imitation", + 34426: "immature", + 34431: "immerse", + 34432: "immersion", + 34433: "imminent", + 34434: "immobile", + 34435: "immodest", + 34436: "immorally", + 34441: "immortal", + 34442: "immovable", + 34443: "immovably", + 34444: "immunity", + 34445: "immunize", + 34446: "impaired", + 34451: "impale", + 34452: "impart", + 34453: "impatient", + 34454: "impeach", + 34455: "impeding", + 34456: "impending", + 34461: "imperfect", + 34462: "imperial", + 34463: "impish", + 34464: "implant", + 34465: "implement", + 34466: "implicate", + 34511: "implicit", + 34512: "implode", + 34513: "implosion", + 34514: "implosive", + 34515: "imply", + 34516: "impolite", + 34521: "important", + 34522: "importer", + 34523: "impose", + 34524: "imposing", + 34525: "impotence", + 34526: "impotency", + 34531: "impotent", + 34532: "impound", + 34533: "imprecise", + 34534: "imprint", + 34535: "imprison", + 34536: "impromptu", + 34541: "improper", + 34542: "improve", + 34543: "improving", + 34544: "improvise", + 34545: "imprudent", + 34546: "impulse", + 34551: "impulsive", + 34552: "impure", + 34553: "impurity", + 34554: "iodine", + 34555: "iodize", + 34556: "ion", + 34561: "ipad", + 34562: "iphone", + 34563: "ipod", + 34564: "irate", + 34565: "irk", + 34566: "iron", + 34611: "irregular", + 34612: "irrigate", + 34613: "irritable", + 34614: "irritably", + 34615: "irritant", + 34616: "irritate", + 34621: "islamic", + 34622: "islamist", + 34623: "isolated", + 34624: "isolating", + 34625: "isolation", + 34626: "isotope", + 34631: "issue", + 34632: "issuing", + 34633: "italicize", + 34634: "italics", + 34635: "item", + 34636: "itinerary", + 34641: "itunes", + 34642: "ivory", + 34643: "ivy", + 34644: "jab", + 34645: "jackal", + 34646: "jacket", + 34651: "jackknife", + 34652: "jackpot", + 34653: "jailbird", + 34654: "jailbreak", + 34655: "jailer", + 34656: "jailhouse", + 34661: "jalapeno", + 34662: "jam", + 34663: "janitor", + 34664: "january", + 34665: "jargon", + 34666: "jarring", + 35111: "jasmine", + 35112: "jaundice", + 35113: "jaunt", + 35114: "java", + 35115: "jawed", + 35116: "jawless", + 35121: "jawline", + 35122: "jaws", + 35123: "jaybird", + 35124: "jaywalker", + 35125: "jazz", + 35126: "jeep", + 35131: "jeeringly", + 35132: "jellied", + 35133: "jelly", + 35134: "jersey", + 35135: "jester", + 35136: "jet", + 35141: "jiffy", + 35142: "jigsaw", + 35143: "jimmy", + 35144: "jingle", + 35145: "jingling", + 35146: "jinx", + 35151: "jitters", + 35152: "jittery", + 35153: "job", + 35154: "jockey", + 35155: "jockstrap", + 35156: "jogger", + 35161: "jogging", + 35162: "john", + 35163: "joining", + 35164: "jokester", + 35165: "jokingly", + 35166: "jolliness", + 35211: "jolly", + 35212: "jolt", + 35213: "jot", + 35214: "jovial", + 35215: "joyfully", + 35216: "joylessly", + 35221: "joyous", + 35222: "joyride", + 35223: "joystick", + 35224: "jubilance", + 35225: "jubilant", + 35226: "judge", + 35231: "judgingly", + 35232: "judicial", + 35233: "judiciary", + 35234: "judo", + 35235: "juggle", + 35236: "juggling", + 35241: "jugular", + 35242: "juice", + 35243: "juiciness", + 35244: "juicy", + 35245: "jujitsu", + 35246: "jukebox", + 35251: "july", + 35252: "jumble", + 35253: "jumbo", + 35254: "jump", + 35255: "junction", + 35256: "juncture", + 35261: "june", + 35262: "junior", + 35263: "juniper", + 35264: "junkie", + 35265: "junkman", + 35266: "junkyard", + 35311: "jurist", + 35312: "juror", + 35313: "jury", + 35314: "justice", + 35315: "justifier", + 35316: "justify", + 35321: "justly", + 35322: "justness", + 35323: "juvenile", + 35324: "kabob", + 35325: "kangaroo", + 35326: "karaoke", + 35331: "karate", + 35332: "karma", + 35333: "kebab", + 35334: "keenly", + 35335: "keenness", + 35336: "keep", + 35341: "keg", + 35342: "kelp", + 35343: "kennel", + 35344: "kept", + 35345: "kerchief", + 35346: "kerosene", + 35351: "kettle", + 35352: "kick", + 35353: "kiln", + 35354: "kilobyte", + 35355: "kilogram", + 35356: "kilometer", + 35361: "kilowatt", + 35362: "kilt", + 35363: "kimono", + 35364: "kindle", + 35365: "kindling", + 35366: "kindly", + 35411: "kindness", + 35412: "kindred", + 35413: "kinetic", + 35414: "kinfolk", + 35415: "king", + 35416: "kinship", + 35421: "kinsman", + 35422: "kinswoman", + 35423: "kissable", + 35424: "kisser", + 35425: "kissing", + 35426: "kitchen", + 35431: "kite", + 35432: "kitten", + 35433: "kitty", + 35434: "kiwi", + 35435: "kleenex", + 35436: "knapsack", + 35441: "knee", + 35442: "knelt", + 35443: "knickers", + 35444: "knoll", + 35445: "koala", + 35446: "kooky", + 35451: "kosher", + 35452: "krypton", + 35453: "kudos", + 35454: "kung", + 35455: "labored", + 35456: "laborer", + 35461: "laboring", + 35462: "laborious", + 35463: "labrador", + 35464: "ladder", + 35465: "ladies", + 35466: "ladle", + 35511: "ladybug", + 35512: "ladylike", + 35513: "lagged", + 35514: "lagging", + 35515: "lagoon", + 35516: "lair", + 35521: "lake", + 35522: "lance", + 35523: "landed", + 35524: "landfall", + 35525: "landfill", + 35526: "landing", + 35531: "landlady", + 35532: "landless", + 35533: "landline", + 35534: "landlord", + 35535: "landmark", + 35536: "landmass", + 35541: "landmine", + 35542: "landowner", + 35543: "landscape", + 35544: "landside", + 35545: "landslide", + 35546: "language", + 35551: "lankiness", + 35552: "lanky", + 35553: "lantern", + 35554: "lapdog", + 35555: "lapel", + 35556: "lapped", + 35561: "lapping", + 35562: "laptop", + 35563: "lard", + 35564: "large", + 35565: "lark", + 35566: "lash", + 35611: "lasso", + 35612: "last", + 35613: "latch", + 35614: "late", + 35615: "lather", + 35616: "latitude", + 35621: "latrine", + 35622: "latter", + 35623: "latticed", + 35624: "launch", + 35625: "launder", + 35626: "laundry", + 35631: "laurel", + 35632: "lavender", + 35633: "lavish", + 35634: "laxative", + 35635: "lazily", + 35636: "laziness", + 35641: "lazy", + 35642: "lecturer", + 35643: "left", + 35644: "legacy", + 35645: "legal", + 35646: "legend", + 35651: "legged", + 35652: "leggings", + 35653: "legible", + 35654: "legibly", + 35655: "legislate", + 35656: "lego", + 35661: "legroom", + 35662: "legume", + 35663: "legwarmer", + 35664: "legwork", + 35665: "lemon", + 35666: "lend", + 36111: "length", + 36112: "lens", + 36113: "lent", + 36114: "leotard", + 36115: "lesser", + 36116: "letdown", + 36121: "lethargic", + 36122: "lethargy", + 36123: "letter", + 36124: "lettuce", + 36125: "level", + 36126: "leverage", + 36131: "levers", + 36132: "levitate", + 36133: "levitator", + 36134: "liability", + 36135: "liable", + 36136: "liberty", + 36141: "librarian", + 36142: "library", + 36143: "licking", + 36144: "licorice", + 36145: "lid", + 36146: "life", + 36151: "lifter", + 36152: "lifting", + 36153: "liftoff", + 36154: "ligament", + 36155: "likely", + 36156: "likeness", + 36161: "likewise", + 36162: "liking", + 36163: "lilac", + 36164: "lilly", + 36165: "lily", + 36166: "limb", + 36211: "limeade", + 36212: "limelight", + 36213: "limes", + 36214: "limit", + 36215: "limping", + 36216: "limpness", + 36221: "line", + 36222: "lingo", + 36223: "linguini", + 36224: "linguist", + 36225: "lining", + 36226: "linked", + 36231: "linoleum", + 36232: "linseed", + 36233: "lint", + 36234: "lion", + 36235: "lip", + 36236: "liquefy", + 36241: "liqueur", + 36242: "liquid", + 36243: "lisp", + 36244: "list", + 36245: "litigate", + 36246: "litigator", + 36251: "litmus", + 36252: "litter", + 36253: "little", + 36254: "livable", + 36255: "lived", + 36256: "lively", + 36261: "liver", + 36262: "livestock", + 36263: "lividly", + 36264: "living", + 36265: "lizard", + 36266: "lubricant", + 36311: "lubricate", + 36312: "lucid", + 36313: "luckily", + 36314: "luckiness", + 36315: "luckless", + 36316: "lucrative", + 36321: "ludicrous", + 36322: "lugged", + 36323: "lukewarm", + 36324: "lullaby", + 36325: "lumber", + 36326: "luminance", + 36331: "luminous", + 36332: "lumpiness", + 36333: "lumping", + 36334: "lumpish", + 36335: "lunacy", + 36336: "lunar", + 36341: "lunchbox", + 36342: "luncheon", + 36343: "lunchroom", + 36344: "lunchtime", + 36345: "lung", + 36346: "lurch", + 36351: "lure", + 36352: "luridness", + 36353: "lurk", + 36354: "lushly", + 36355: "lushness", + 36356: "luster", + 36361: "lustfully", + 36362: "lustily", + 36363: "lustiness", + 36364: "lustrous", + 36365: "lusty", + 36366: "luxurious", + 36411: "luxury", + 36412: "lying", + 36413: "lyrically", + 36414: "lyricism", + 36415: "lyricist", + 36416: "lyrics", + 36421: "macarena", + 36422: "macaroni", + 36423: "macaw", + 36424: "mace", + 36425: "machine", + 36426: "machinist", + 36431: "magazine", + 36432: "magenta", + 36433: "maggot", + 36434: "magical", + 36435: "magician", + 36436: "magma", + 36441: "magnesium", + 36442: "magnetic", + 36443: "magnetism", + 36444: "magnetize", + 36445: "magnifier", + 36446: "magnify", + 36451: "magnitude", + 36452: "magnolia", + 36453: "mahogany", + 36454: "maimed", + 36455: "majestic", + 36456: "majesty", + 36461: "majorette", + 36462: "majority", + 36463: "makeover", + 36464: "maker", + 36465: "makeshift", + 36466: "making", + 36511: "malformed", + 36512: "malt", + 36513: "mama", + 36514: "mammal", + 36515: "mammary", + 36516: "mammogram", + 36521: "manager", + 36522: "managing", + 36523: "manatee", + 36524: "mandarin", + 36525: "mandate", + 36526: "mandatory", + 36531: "mandolin", + 36532: "manger", + 36533: "mangle", + 36534: "mango", + 36535: "mangy", + 36536: "manhandle", + 36541: "manhole", + 36542: "manhood", + 36543: "manhunt", + 36544: "manicotti", + 36545: "manicure", + 36546: "manifesto", + 36551: "manila", + 36552: "mankind", + 36553: "manlike", + 36554: "manliness", + 36555: "manly", + 36556: "manmade", + 36561: "manned", + 36562: "mannish", + 36563: "manor", + 36564: "manpower", + 36565: "mantis", + 36566: "mantra", + 36611: "manual", + 36612: "many", + 36613: "map", + 36614: "marathon", + 36615: "marauding", + 36616: "marbled", + 36621: "marbles", + 36622: "marbling", + 36623: "march", + 36624: "mardi", + 36625: "margarine", + 36626: "margarita", + 36631: "margin", + 36632: "marigold", + 36633: "marina", + 36634: "marine", + 36635: "marital", + 36636: "maritime", + 36641: "marlin", + 36642: "marmalade", + 36643: "maroon", + 36644: "married", + 36645: "marrow", + 36646: "marry", + 36651: "marshland", + 36652: "marshy", + 36653: "marsupial", + 36654: "marvelous", + 36655: "marxism", + 36656: "mascot", + 36661: "masculine", + 36662: "mashed", + 36663: "mashing", + 36664: "massager", + 36665: "masses", + 36666: "massive", + 41111: "mastiff", + 41112: "matador", + 41113: "matchbook", + 41114: "matchbox", + 41115: "matcher", + 41116: "matching", + 41121: "matchless", + 41122: "material", + 41123: "maternal", + 41124: "maternity", + 41125: "math", + 41126: "mating", + 41131: "matriarch", + 41132: "matrimony", + 41133: "matrix", + 41134: "matron", + 41135: "matted", + 41136: "matter", + 41141: "maturely", + 41142: "maturing", + 41143: "maturity", + 41144: "mauve", + 41145: "maverick", + 41146: "maximize", + 41151: "maximum", + 41152: "maybe", + 41153: "mayday", + 41154: "mayflower", + 41155: "moaner", + 41156: "moaning", + 41161: "mobile", + 41162: "mobility", + 41163: "mobilize", + 41164: "mobster", + 41165: "mocha", + 41166: "mocker", + 41211: "mockup", + 41212: "modified", + 41213: "modify", + 41214: "modular", + 41215: "modulator", + 41216: "module", + 41221: "moisten", + 41222: "moistness", + 41223: "moisture", + 41224: "molar", + 41225: "molasses", + 41226: "mold", + 41231: "molecular", + 41232: "molecule", + 41233: "molehill", + 41234: "mollusk", + 41235: "mom", + 41236: "monastery", + 41241: "monday", + 41242: "monetary", + 41243: "monetize", + 41244: "moneybags", + 41245: "moneyless", + 41246: "moneywise", + 41251: "mongoose", + 41252: "mongrel", + 41253: "monitor", + 41254: "monkhood", + 41255: "monogamy", + 41256: "monogram", + 41261: "monologue", + 41262: "monopoly", + 41263: "monorail", + 41264: "monotone", + 41265: "monotype", + 41266: "monoxide", + 41311: "monsieur", + 41312: "monsoon", + 41313: "monstrous", + 41314: "monthly", + 41315: "monument", + 41316: "moocher", + 41321: "moodiness", + 41322: "moody", + 41323: "mooing", + 41324: "moonbeam", + 41325: "mooned", + 41326: "moonlight", + 41331: "moonlike", + 41332: "moonlit", + 41333: "moonrise", + 41334: "moonscape", + 41335: "moonshine", + 41336: "moonstone", + 41341: "moonwalk", + 41342: "mop", + 41343: "morale", + 41344: "morality", + 41345: "morally", + 41346: "morbidity", + 41351: "morbidly", + 41352: "morphine", + 41353: "morphing", + 41354: "morse", + 41355: "mortality", + 41356: "mortally", + 41361: "mortician", + 41362: "mortified", + 41363: "mortify", + 41364: "mortuary", + 41365: "mosaic", + 41366: "mossy", + 41411: "most", + 41412: "mothball", + 41413: "mothproof", + 41414: "motion", + 41415: "motivate", + 41416: "motivator", + 41421: "motive", + 41422: "motocross", + 41423: "motor", + 41424: "motto", + 41425: "mountable", + 41426: "mountain", + 41431: "mounted", + 41432: "mounting", + 41433: "mourner", + 41434: "mournful", + 41435: "mouse", + 41436: "mousiness", + 41441: "moustache", + 41442: "mousy", + 41443: "mouth", + 41444: "movable", + 41445: "move", + 41446: "movie", + 41451: "moving", + 41452: "mower", + 41453: "mowing", + 41454: "much", + 41455: "muck", + 41456: "mud", + 41461: "mug", + 41462: "mulberry", + 41463: "mulch", + 41464: "mule", + 41465: "mulled", + 41466: "mullets", + 41511: "multiple", + 41512: "multiply", + 41513: "multitask", + 41514: "multitude", + 41515: "mumble", + 41516: "mumbling", + 41521: "mumbo", + 41522: "mummified", + 41523: "mummify", + 41524: "mummy", + 41525: "mumps", + 41526: "munchkin", + 41531: "mundane", + 41532: "municipal", + 41533: "muppet", + 41534: "mural", + 41535: "murkiness", + 41536: "murky", + 41541: "murmuring", + 41542: "muscular", + 41543: "museum", + 41544: "mushily", + 41545: "mushiness", + 41546: "mushroom", + 41551: "mushy", + 41552: "music", + 41553: "musket", + 41554: "muskiness", + 41555: "musky", + 41556: "mustang", + 41561: "mustard", + 41562: "muster", + 41563: "mustiness", + 41564: "musty", + 41565: "mutable", + 41566: "mutate", + 41611: "mutation", + 41612: "mute", + 41613: "mutilated", + 41614: "mutilator", + 41615: "mutiny", + 41616: "mutt", + 41621: "mutual", + 41622: "muzzle", + 41623: "myself", + 41624: "myspace", + 41625: "mystified", + 41626: "mystify", + 41631: "myth", + 41632: "nacho", + 41633: "nag", + 41634: "nail", + 41635: "name", + 41636: "naming", + 41641: "nanny", + 41642: "nanometer", + 41643: "nape", + 41644: "napkin", + 41645: "napped", + 41646: "napping", + 41651: "nappy", + 41652: "narrow", + 41653: "nastily", + 41654: "nastiness", + 41655: "national", + 41656: "native", + 41661: "nativity", + 41662: "natural", + 41663: "nature", + 41664: "naturist", + 41665: "nautical", + 41666: "navigate", + 42111: "navigator", + 42112: "navy", + 42113: "nearby", + 42114: "nearest", + 42115: "nearly", + 42116: "nearness", + 42121: "neatly", + 42122: "neatness", + 42123: "nebula", + 42124: "nebulizer", + 42125: "nectar", + 42126: "negate", + 42131: "negation", + 42132: "negative", + 42133: "neglector", + 42134: "negligee", + 42135: "negligent", + 42136: "negotiate", + 42141: "nemeses", + 42142: "nemesis", + 42143: "neon", + 42144: "nephew", + 42145: "nerd", + 42146: "nervous", + 42151: "nervy", + 42152: "nest", + 42153: "net", + 42154: "neurology", + 42155: "neuron", + 42156: "neurosis", + 42161: "neurotic", + 42162: "neuter", + 42163: "neutron", + 42164: "never", + 42165: "next", + 42166: "nibble", + 42211: "nickname", + 42212: "nicotine", + 42213: "niece", + 42214: "nifty", + 42215: "nimble", + 42216: "nimbly", + 42221: "nineteen", + 42222: "ninetieth", + 42223: "ninja", + 42224: "nintendo", + 42225: "ninth", + 42226: "nuclear", + 42231: "nuclei", + 42232: "nucleus", + 42233: "nugget", + 42234: "nullify", + 42235: "number", + 42236: "numbing", + 42241: "numbly", + 42242: "numbness", + 42243: "numeral", + 42244: "numerate", + 42245: "numerator", + 42246: "numeric", + 42251: "numerous", + 42252: "nuptials", + 42253: "nursery", + 42254: "nursing", + 42255: "nurture", + 42256: "nutcase", + 42261: "nutlike", + 42262: "nutmeg", + 42263: "nutrient", + 42264: "nutshell", + 42265: "nuttiness", + 42266: "nutty", + 42311: "nuzzle", + 42312: "nylon", + 42313: "oaf", + 42314: "oak", + 42315: "oasis", + 42316: "oat", + 42321: "obedience", + 42322: "obedient", + 42323: "obituary", + 42324: "object", + 42325: "obligate", + 42326: "obliged", + 42331: "oblivion", + 42332: "oblivious", + 42333: "oblong", + 42334: "obnoxious", + 42335: "oboe", + 42336: "obscure", + 42341: "obscurity", + 42342: "observant", + 42343: "observer", + 42344: "observing", + 42345: "obsessed", + 42346: "obsession", + 42351: "obsessive", + 42352: "obsolete", + 42353: "obstacle", + 42354: "obstinate", + 42355: "obstruct", + 42356: "obtain", + 42361: "obtrusive", + 42362: "obtuse", + 42363: "obvious", + 42364: "occultist", + 42365: "occupancy", + 42366: "occupant", + 42411: "occupier", + 42412: "occupy", + 42413: "ocean", + 42414: "ocelot", + 42415: "octagon", + 42416: "octane", + 42421: "october", + 42422: "octopus", + 42423: "ogle", + 42424: "oil", + 42425: "oink", + 42426: "ointment", + 42431: "okay", + 42432: "old", + 42433: "olive", + 42434: "olympics", + 42435: "omega", + 42436: "omen", + 42441: "ominous", + 42442: "omission", + 42443: "omit", + 42444: "omnivore", + 42445: "onboard", + 42446: "oncoming", + 42451: "ongoing", + 42452: "onion", + 42453: "online", + 42454: "onlooker", + 42455: "only", + 42456: "onscreen", + 42461: "onset", + 42462: "onshore", + 42463: "onslaught", + 42464: "onstage", + 42465: "onto", + 42466: "onward", + 42511: "onyx", + 42512: "oops", + 42513: "ooze", + 42514: "oozy", + 42515: "opacity", + 42516: "opal", + 42521: "open", + 42522: "operable", + 42523: "operate", + 42524: "operating", + 42525: "operation", + 42526: "operative", + 42531: "operator", + 42532: "opium", + 42533: "opossum", + 42534: "opponent", + 42535: "oppose", + 42536: "opposing", + 42541: "opposite", + 42542: "oppressed", + 42543: "oppressor", + 42544: "opt", + 42545: "opulently", + 42546: "osmosis", + 42551: "other", + 42552: "otter", + 42553: "ouch", + 42554: "ought", + 42555: "ounce", + 42556: "outage", + 42561: "outback", + 42562: "outbid", + 42563: "outboard", + 42564: "outbound", + 42565: "outbreak", + 42566: "outburst", + 42611: "outcast", + 42612: "outclass", + 42613: "outcome", + 42614: "outdated", + 42615: "outdoors", + 42616: "outer", + 42621: "outfield", + 42622: "outfit", + 42623: "outflank", + 42624: "outgoing", + 42625: "outgrow", + 42626: "outhouse", + 42631: "outing", + 42632: "outlast", + 42633: "outlet", + 42634: "outline", + 42635: "outlook", + 42636: "outlying", + 42641: "outmatch", + 42642: "outmost", + 42643: "outnumber", + 42644: "outplayed", + 42645: "outpost", + 42646: "outpour", + 42651: "output", + 42652: "outrage", + 42653: "outrank", + 42654: "outreach", + 42655: "outright", + 42656: "outscore", + 42661: "outsell", + 42662: "outshine", + 42663: "outshoot", + 42664: "outsider", + 42665: "outskirts", + 42666: "outsmart", + 43111: "outsource", + 43112: "outspoken", + 43113: "outtakes", + 43114: "outthink", + 43115: "outward", + 43116: "outweigh", + 43121: "outwit", + 43122: "oval", + 43123: "ovary", + 43124: "oven", + 43125: "overact", + 43126: "overall", + 43131: "overarch", + 43132: "overbid", + 43133: "overbill", + 43134: "overbite", + 43135: "overblown", + 43136: "overboard", + 43141: "overbook", + 43142: "overbuilt", + 43143: "overcast", + 43144: "overcoat", + 43145: "overcome", + 43146: "overcook", + 43151: "overcrowd", + 43152: "overdraft", + 43153: "overdrawn", + 43154: "overdress", + 43155: "overdrive", + 43156: "overdue", + 43161: "overeager", + 43162: "overeater", + 43163: "overexert", + 43164: "overfed", + 43165: "overfeed", + 43166: "overfill", + 43211: "overflow", + 43212: "overfull", + 43213: "overgrown", + 43214: "overhand", + 43215: "overhang", + 43216: "overhaul", + 43221: "overhead", + 43222: "overhear", + 43223: "overheat", + 43224: "overhung", + 43225: "overjoyed", + 43226: "overkill", + 43231: "overlabor", + 43232: "overlaid", + 43233: "overlap", + 43234: "overlay", + 43235: "overload", + 43236: "overlook", + 43241: "overlord", + 43242: "overlying", + 43243: "overnight", + 43244: "overpass", + 43245: "overpay", + 43246: "overplant", + 43251: "overplay", + 43252: "overpower", + 43253: "overprice", + 43254: "overrate", + 43255: "overreach", + 43256: "overreact", + 43261: "override", + 43262: "overripe", + 43263: "overrule", + 43264: "overrun", + 43265: "overshoot", + 43266: "overshot", + 43311: "oversight", + 43312: "oversized", + 43313: "oversleep", + 43314: "oversold", + 43315: "overspend", + 43316: "overstate", + 43321: "overstay", + 43322: "overstep", + 43323: "overstock", + 43324: "overstuff", + 43325: "oversweet", + 43326: "overtake", + 43331: "overthrow", + 43332: "overtime", + 43333: "overtly", + 43334: "overtone", + 43335: "overture", + 43336: "overturn", + 43341: "overuse", + 43342: "overvalue", + 43343: "overview", + 43344: "overwrite", + 43345: "owl", + 43346: "oxford", + 43351: "oxidant", + 43352: "oxidation", + 43353: "oxidize", + 43354: "oxidizing", + 43355: "oxygen", + 43356: "oxymoron", + 43361: "oyster", + 43362: "ozone", + 43363: "paced", + 43364: "pacemaker", + 43365: "pacific", + 43366: "pacifier", + 43411: "pacifism", + 43412: "pacifist", + 43413: "pacify", + 43414: "padded", + 43415: "padding", + 43416: "paddle", + 43421: "paddling", + 43422: "padlock", + 43423: "pagan", + 43424: "pager", + 43425: "paging", + 43426: "pajamas", + 43431: "palace", + 43432: "palatable", + 43433: "palm", + 43434: "palpable", + 43435: "palpitate", + 43436: "paltry", + 43441: "pampered", + 43442: "pamperer", + 43443: "pampers", + 43444: "pamphlet", + 43445: "panama", + 43446: "pancake", + 43451: "pancreas", + 43452: "panda", + 43453: "pandemic", + 43454: "pang", + 43455: "panhandle", + 43456: "panic", + 43461: "panning", + 43462: "panorama", + 43463: "panoramic", + 43464: "panther", + 43465: "pantomime", + 43466: "pantry", + 43511: "pants", + 43512: "pantyhose", + 43513: "paparazzi", + 43514: "papaya", + 43515: "paper", + 43516: "paprika", + 43521: "papyrus", + 43522: "parabola", + 43523: "parachute", + 43524: "parade", + 43525: "paradox", + 43526: "paragraph", + 43531: "parakeet", + 43532: "paralegal", + 43533: "paralyses", + 43534: "paralysis", + 43535: "paralyze", + 43536: "paramedic", + 43541: "parameter", + 43542: "paramount", + 43543: "parasail", + 43544: "parasite", + 43545: "parasitic", + 43546: "parcel", + 43551: "parched", + 43552: "parchment", + 43553: "pardon", + 43554: "parish", + 43555: "parka", + 43556: "parking", + 43561: "parkway", + 43562: "parlor", + 43563: "parmesan", + 43564: "parole", + 43565: "parrot", + 43566: "parsley", + 43611: "parsnip", + 43612: "partake", + 43613: "parted", + 43614: "parting", + 43615: "partition", + 43616: "partly", + 43621: "partner", + 43622: "partridge", + 43623: "party", + 43624: "passable", + 43625: "passably", + 43626: "passage", + 43631: "passcode", + 43632: "passenger", + 43633: "passerby", + 43634: "passing", + 43635: "passion", + 43636: "passive", + 43641: "passivism", + 43642: "passover", + 43643: "passport", + 43644: "password", + 43645: "pasta", + 43646: "pasted", + 43651: "pastel", + 43652: "pastime", + 43653: "pastor", + 43654: "pastrami", + 43655: "pasture", + 43656: "pasty", + 43661: "patchwork", + 43662: "patchy", + 43663: "paternal", + 43664: "paternity", + 43665: "path", + 43666: "patience", + 44111: "patient", + 44112: "patio", + 44113: "patriarch", + 44114: "patriot", + 44115: "patrol", + 44116: "patronage", + 44121: "patronize", + 44122: "pauper", + 44123: "pavement", + 44124: "paver", + 44125: "pavestone", + 44126: "pavilion", + 44131: "paving", + 44132: "pawing", + 44133: "payable", + 44134: "payback", + 44135: "paycheck", + 44136: "payday", + 44141: "payee", + 44142: "payer", + 44143: "paying", + 44144: "payment", + 44145: "payphone", + 44146: "payroll", + 44151: "pebble", + 44152: "pebbly", + 44153: "pecan", + 44154: "pectin", + 44155: "peculiar", + 44156: "peddling", + 44161: "pediatric", + 44162: "pedicure", + 44163: "pedigree", + 44164: "pedometer", + 44165: "pegboard", + 44166: "pelican", + 44211: "pellet", + 44212: "pelt", + 44213: "pelvis", + 44214: "penalize", + 44215: "penalty", + 44216: "pencil", + 44221: "pendant", + 44222: "pending", + 44223: "penholder", + 44224: "penknife", + 44225: "pennant", + 44226: "penniless", + 44231: "penny", + 44232: "penpal", + 44233: "pension", + 44234: "pentagon", + 44235: "pentagram", + 44236: "pep", + 44241: "perceive", + 44242: "percent", + 44243: "perch", + 44244: "percolate", + 44245: "perennial", + 44246: "perfected", + 44251: "perfectly", + 44252: "perfume", + 44253: "periscope", + 44254: "perish", + 44255: "perjurer", + 44256: "perjury", + 44261: "perkiness", + 44262: "perky", + 44263: "perm", + 44264: "peroxide", + 44265: "perpetual", + 44266: "perplexed", + 44311: "persecute", + 44312: "persevere", + 44313: "persuaded", + 44314: "persuader", + 44315: "pesky", + 44316: "peso", + 44321: "pessimism", + 44322: "pessimist", + 44323: "pester", + 44324: "pesticide", + 44325: "petal", + 44326: "petite", + 44331: "petition", + 44332: "petri", + 44333: "petroleum", + 44334: "petted", + 44335: "petticoat", + 44336: "pettiness", + 44341: "petty", + 44342: "petunia", + 44343: "phantom", + 44344: "phobia", + 44345: "phoenix", + 44346: "phonebook", + 44351: "phoney", + 44352: "phonics", + 44353: "phoniness", + 44354: "phony", + 44355: "phosphate", + 44356: "photo", + 44361: "phrase", + 44362: "phrasing", + 44363: "placard", + 44364: "placate", + 44365: "placidly", + 44366: "plank", + 44411: "planner", + 44412: "plant", + 44413: "plasma", + 44414: "plaster", + 44415: "plastic", + 44416: "plated", + 44421: "platform", + 44422: "plating", + 44423: "platinum", + 44424: "platonic", + 44425: "platter", + 44426: "platypus", + 44431: "plausible", + 44432: "plausibly", + 44433: "playable", + 44434: "playback", + 44435: "player", + 44436: "playful", + 44441: "playgroup", + 44442: "playhouse", + 44443: "playing", + 44444: "playlist", + 44445: "playmaker", + 44446: "playmate", + 44451: "playoff", + 44452: "playpen", + 44453: "playroom", + 44454: "playset", + 44455: "plaything", + 44456: "playtime", + 44461: "plaza", + 44462: "pleading", + 44463: "pleat", + 44464: "pledge", + 44465: "plentiful", + 44466: "plenty", + 44511: "plethora", + 44512: "plexiglas", + 44513: "pliable", + 44514: "plod", + 44515: "plop", + 44516: "plot", + 44521: "plow", + 44522: "ploy", + 44523: "pluck", + 44524: "plug", + 44525: "plunder", + 44526: "plunging", + 44531: "plural", + 44532: "plus", + 44533: "plutonium", + 44534: "plywood", + 44535: "poach", + 44536: "pod", + 44541: "poem", + 44542: "poet", + 44543: "pogo", + 44544: "pointed", + 44545: "pointer", + 44546: "pointing", + 44551: "pointless", + 44552: "pointy", + 44553: "poise", + 44554: "poison", + 44555: "poker", + 44556: "poking", + 44561: "polar", + 44562: "police", + 44563: "policy", + 44564: "polio", + 44565: "polish", + 44566: "politely", + 44611: "polka", + 44612: "polo", + 44613: "polyester", + 44614: "polygon", + 44615: "polygraph", + 44616: "polymer", + 44621: "poncho", + 44622: "pond", + 44623: "pony", + 44624: "popcorn", + 44625: "pope", + 44626: "poplar", + 44631: "popper", + 44632: "poppy", + 44633: "popsicle", + 44634: "populace", + 44635: "popular", + 44636: "populate", + 44641: "porcupine", + 44642: "pork", + 44643: "porous", + 44644: "porridge", + 44645: "portable", + 44646: "portal", + 44651: "portfolio", + 44652: "porthole", + 44653: "portion", + 44654: "portly", + 44655: "portside", + 44656: "poser", + 44661: "posh", + 44662: "posing", + 44663: "possible", + 44664: "possibly", + 44665: "possum", + 44666: "postage", + 45111: "postal", + 45112: "postbox", + 45113: "postcard", + 45114: "posted", + 45115: "poster", + 45116: "posting", + 45121: "postnasal", + 45122: "posture", + 45123: "postwar", + 45124: "pouch", + 45125: "pounce", + 45126: "pouncing", + 45131: "pound", + 45132: "pouring", + 45133: "pout", + 45134: "powdered", + 45135: "powdering", + 45136: "powdery", + 45141: "power", + 45142: "powwow", + 45143: "pox", + 45144: "praising", + 45145: "prance", + 45146: "prancing", + 45151: "pranker", + 45152: "prankish", + 45153: "prankster", + 45154: "prayer", + 45155: "praying", + 45156: "preacher", + 45161: "preaching", + 45162: "preachy", + 45163: "preamble", + 45164: "precinct", + 45165: "precise", + 45166: "precision", + 45211: "precook", + 45212: "precut", + 45213: "predator", + 45214: "predefine", + 45215: "predict", + 45216: "preface", + 45221: "prefix", + 45222: "preflight", + 45223: "preformed", + 45224: "pregame", + 45225: "pregnancy", + 45226: "pregnant", + 45231: "preheated", + 45232: "prelaunch", + 45233: "prelaw", + 45234: "prelude", + 45235: "premiere", + 45236: "premises", + 45241: "premium", + 45242: "prenatal", + 45243: "preoccupy", + 45244: "preorder", + 45245: "prepaid", + 45246: "prepay", + 45251: "preplan", + 45252: "preppy", + 45253: "preschool", + 45254: "prescribe", + 45255: "preseason", + 45256: "preset", + 45261: "preshow", + 45262: "president", + 45263: "presoak", + 45264: "press", + 45265: "presume", + 45266: "presuming", + 45311: "preteen", + 45312: "pretended", + 45313: "pretender", + 45314: "pretense", + 45315: "pretext", + 45316: "pretty", + 45321: "pretzel", + 45322: "prevail", + 45323: "prevalent", + 45324: "prevent", + 45325: "preview", + 45326: "previous", + 45331: "prewar", + 45332: "prewashed", + 45333: "prideful", + 45334: "pried", + 45335: "primal", + 45336: "primarily", + 45341: "primary", + 45342: "primate", + 45343: "primer", + 45344: "primp", + 45345: "princess", + 45346: "print", + 45351: "prior", + 45352: "prism", + 45353: "prison", + 45354: "prissy", + 45355: "pristine", + 45356: "privacy", + 45361: "private", + 45362: "privatize", + 45363: "prize", + 45364: "proactive", + 45365: "probable", + 45366: "probably", + 45411: "probation", + 45412: "probe", + 45413: "probing", + 45414: "probiotic", + 45415: "problem", + 45416: "procedure", + 45421: "process", + 45422: "proclaim", + 45423: "procreate", + 45424: "procurer", + 45425: "prodigal", + 45426: "prodigy", + 45431: "produce", + 45432: "product", + 45433: "profane", + 45434: "profanity", + 45435: "professed", + 45436: "professor", + 45441: "profile", + 45442: "profound", + 45443: "profusely", + 45444: "progeny", + 45445: "prognosis", + 45446: "program", + 45451: "progress", + 45452: "projector", + 45453: "prologue", + 45454: "prolonged", + 45455: "promenade", + 45456: "prominent", + 45461: "promoter", + 45462: "promotion", + 45463: "prompter", + 45464: "promptly", + 45465: "prone", + 45466: "prong", + 45511: "pronounce", + 45512: "pronto", + 45513: "proofing", + 45514: "proofread", + 45515: "proofs", + 45516: "propeller", + 45521: "properly", + 45522: "property", + 45523: "proponent", + 45524: "proposal", + 45525: "propose", + 45526: "props", + 45531: "prorate", + 45532: "protector", + 45533: "protegee", + 45534: "proton", + 45535: "prototype", + 45536: "protozoan", + 45541: "protract", + 45542: "protrude", + 45543: "proud", + 45544: "provable", + 45545: "proved", + 45546: "proven", + 45551: "provided", + 45552: "provider", + 45553: "providing", + 45554: "province", + 45555: "proving", + 45556: "provoke", + 45561: "provoking", + 45562: "provolone", + 45563: "prowess", + 45564: "prowler", + 45565: "prowling", + 45566: "proximity", + 45611: "proxy", + 45612: "prozac", + 45613: "prude", + 45614: "prudishly", + 45615: "prune", + 45616: "pruning", + 45621: "pry", + 45622: "psychic", + 45623: "public", + 45624: "publisher", + 45625: "pucker", + 45626: "pueblo", + 45631: "pug", + 45632: "pull", + 45633: "pulmonary", + 45634: "pulp", + 45635: "pulsate", + 45636: "pulse", + 45641: "pulverize", + 45642: "puma", + 45643: "pumice", + 45644: "pummel", + 45645: "punch", + 45646: "punctual", + 45651: "punctuate", + 45652: "punctured", + 45653: "pungent", + 45654: "punisher", + 45655: "punk", + 45656: "pupil", + 45661: "puppet", + 45662: "puppy", + 45663: "purchase", + 45664: "pureblood", + 45665: "purebred", + 45666: "purely", + 46111: "pureness", + 46112: "purgatory", + 46113: "purge", + 46114: "purging", + 46115: "purifier", + 46116: "purify", + 46121: "purist", + 46122: "puritan", + 46123: "purity", + 46124: "purple", + 46125: "purplish", + 46126: "purposely", + 46131: "purr", + 46132: "purse", + 46133: "pursuable", + 46134: "pursuant", + 46135: "pursuit", + 46136: "purveyor", + 46141: "pushcart", + 46142: "pushchair", + 46143: "pusher", + 46144: "pushiness", + 46145: "pushing", + 46146: "pushover", + 46151: "pushpin", + 46152: "pushup", + 46153: "pushy", + 46154: "putdown", + 46155: "putt", + 46156: "puzzle", + 46161: "puzzling", + 46162: "pyramid", + 46163: "pyromania", + 46164: "python", + 46165: "quack", + 46166: "quadrant", + 46211: "quail", + 46212: "quaintly", + 46213: "quake", + 46214: "quaking", + 46215: "qualified", + 46216: "qualifier", + 46221: "qualify", + 46222: "quality", + 46223: "qualm", + 46224: "quantum", + 46225: "quarrel", + 46226: "quarry", + 46231: "quartered", + 46232: "quarterly", + 46233: "quarters", + 46234: "quartet", + 46235: "quench", + 46236: "query", + 46241: "quicken", + 46242: "quickly", + 46243: "quickness", + 46244: "quicksand", + 46245: "quickstep", + 46246: "quiet", + 46251: "quill", + 46252: "quilt", + 46253: "quintet", + 46254: "quintuple", + 46255: "quirk", + 46256: "quit", + 46261: "quiver", + 46262: "quizzical", + 46263: "quotable", + 46264: "quotation", + 46265: "quote", + 46266: "rabid", + 46311: "race", + 46312: "racing", + 46313: "racism", + 46314: "rack", + 46315: "racoon", + 46316: "radar", + 46321: "radial", + 46322: "radiance", + 46323: "radiantly", + 46324: "radiated", + 46325: "radiation", + 46326: "radiator", + 46331: "radio", + 46332: "radish", + 46333: "raffle", + 46334: "raft", + 46335: "rage", + 46336: "ragged", + 46341: "raging", + 46342: "ragweed", + 46343: "raider", + 46344: "railcar", + 46345: "railing", + 46346: "railroad", + 46351: "railway", + 46352: "raisin", + 46353: "rake", + 46354: "raking", + 46355: "rally", + 46356: "ramble", + 46361: "rambling", + 46362: "ramp", + 46363: "ramrod", + 46364: "ranch", + 46365: "rancidity", + 46366: "random", + 46411: "ranged", + 46412: "ranger", + 46413: "ranging", + 46414: "ranked", + 46415: "ranking", + 46416: "ransack", + 46421: "ranting", + 46422: "rants", + 46423: "rare", + 46424: "rarity", + 46425: "rascal", + 46426: "rash", + 46431: "rasping", + 46432: "ravage", + 46433: "raven", + 46434: "ravine", + 46435: "raving", + 46436: "ravioli", + 46441: "ravishing", + 46442: "reabsorb", + 46443: "reach", + 46444: "reacquire", + 46445: "reaction", + 46446: "reactive", + 46451: "reactor", + 46452: "reaffirm", + 46453: "ream", + 46454: "reanalyze", + 46455: "reappear", + 46456: "reapply", + 46461: "reappoint", + 46462: "reapprove", + 46463: "rearrange", + 46464: "rearview", + 46465: "reason", + 46466: "reassign", + 46511: "reassure", + 46512: "reattach", + 46513: "reawake", + 46514: "rebalance", + 46515: "rebate", + 46516: "rebel", + 46521: "rebirth", + 46522: "reboot", + 46523: "reborn", + 46524: "rebound", + 46525: "rebuff", + 46526: "rebuild", + 46531: "rebuilt", + 46532: "reburial", + 46533: "rebuttal", + 46534: "recall", + 46535: "recant", + 46536: "recapture", + 46541: "recast", + 46542: "recede", + 46543: "recent", + 46544: "recess", + 46545: "recharger", + 46546: "recipient", + 46551: "recital", + 46552: "recite", + 46553: "reckless", + 46554: "reclaim", + 46555: "recliner", + 46556: "reclining", + 46561: "recluse", + 46562: "reclusive", + 46563: "recognize", + 46564: "recoil", + 46565: "recollect", + 46566: "recolor", + 46611: "reconcile", + 46612: "reconfirm", + 46613: "reconvene", + 46614: "recopy", + 46615: "record", + 46616: "recount", + 46621: "recoup", + 46622: "recovery", + 46623: "recreate", + 46624: "rectal", + 46625: "rectangle", + 46626: "rectified", + 46631: "rectify", + 46632: "recycled", + 46633: "recycler", + 46634: "recycling", + 46635: "reemerge", + 46636: "reenact", + 46641: "reenter", + 46642: "reentry", + 46643: "reexamine", + 46644: "referable", + 46645: "referee", + 46646: "reference", + 46651: "refill", + 46652: "refinance", + 46653: "refined", + 46654: "refinery", + 46655: "refining", + 46656: "refinish", + 46661: "reflected", + 46662: "reflector", + 46663: "reflex", + 46664: "reflux", + 46665: "refocus", + 46666: "refold", + 51111: "reforest", + 51112: "reformat", + 51113: "reformed", + 51114: "reformer", + 51115: "reformist", + 51116: "refract", + 51121: "refrain", + 51122: "refreeze", + 51123: "refresh", + 51124: "refried", + 51125: "refueling", + 51126: "refund", + 51131: "refurbish", + 51132: "refurnish", + 51133: "refusal", + 51134: "refuse", + 51135: "refusing", + 51136: "refutable", + 51141: "refute", + 51142: "regain", + 51143: "regalia", + 51144: "regally", + 51145: "reggae", + 51146: "regime", + 51151: "region", + 51152: "register", + 51153: "registrar", + 51154: "registry", + 51155: "regress", + 51156: "regretful", + 51161: "regroup", + 51162: "regular", + 51163: "regulate", + 51164: "regulator", + 51165: "rehab", + 51166: "reheat", + 51211: "rehire", + 51212: "rehydrate", + 51213: "reimburse", + 51214: "reissue", + 51215: "reiterate", + 51216: "rejoice", + 51221: "rejoicing", + 51222: "rejoin", + 51223: "rekindle", + 51224: "relapse", + 51225: "relapsing", + 51226: "relatable", + 51231: "related", + 51232: "relation", + 51233: "relative", + 51234: "relax", + 51235: "relay", + 51236: "relearn", + 51241: "release", + 51242: "relenting", + 51243: "reliable", + 51244: "reliably", + 51245: "reliance", + 51246: "reliant", + 51251: "relic", + 51252: "relieve", + 51253: "relieving", + 51254: "relight", + 51255: "relish", + 51256: "relive", + 51261: "reload", + 51262: "relocate", + 51263: "relock", + 51264: "reluctant", + 51265: "rely", + 51266: "remake", + 51311: "remark", + 51312: "remarry", + 51313: "rematch", + 51314: "remedial", + 51315: "remedy", + 51316: "remember", + 51321: "reminder", + 51322: "remindful", + 51323: "remission", + 51324: "remix", + 51325: "remnant", + 51326: "remodeler", + 51331: "remold", + 51332: "remorse", + 51333: "remote", + 51334: "removable", + 51335: "removal", + 51336: "removed", + 51341: "remover", + 51342: "removing", + 51343: "rename", + 51344: "renderer", + 51345: "rendering", + 51346: "rendition", + 51351: "renegade", + 51352: "renewable", + 51353: "renewably", + 51354: "renewal", + 51355: "renewed", + 51356: "renounce", + 51361: "renovate", + 51362: "renovator", + 51363: "rentable", + 51364: "rental", + 51365: "rented", + 51366: "renter", + 51411: "reoccupy", + 51412: "reoccur", + 51413: "reopen", + 51414: "reorder", + 51415: "repackage", + 51416: "repacking", + 51421: "repaint", + 51422: "repair", + 51423: "repave", + 51424: "repaying", + 51425: "repayment", + 51426: "repeal", + 51431: "repeated", + 51432: "repeater", + 51433: "repent", + 51434: "rephrase", + 51435: "replace", + 51436: "replay", + 51441: "replica", + 51442: "reply", + 51443: "reporter", + 51444: "repose", + 51445: "repossess", + 51446: "repost", + 51451: "repressed", + 51452: "reprimand", + 51453: "reprint", + 51454: "reprise", + 51455: "reproach", + 51456: "reprocess", + 51461: "reproduce", + 51462: "reprogram", + 51463: "reps", + 51464: "reptile", + 51465: "reptilian", + 51466: "repugnant", + 51511: "repulsion", + 51512: "repulsive", + 51513: "repurpose", + 51514: "reputable", + 51515: "reputably", + 51516: "request", + 51521: "require", + 51522: "requisite", + 51523: "reroute", + 51524: "rerun", + 51525: "resale", + 51526: "resample", + 51531: "rescuer", + 51532: "reseal", + 51533: "research", + 51534: "reselect", + 51535: "reseller", + 51536: "resemble", + 51541: "resend", + 51542: "resent", + 51543: "reset", + 51544: "reshape", + 51545: "reshoot", + 51546: "reshuffle", + 51551: "residence", + 51552: "residency", + 51553: "resident", + 51554: "residual", + 51555: "residue", + 51556: "resigned", + 51561: "resilient", + 51562: "resistant", + 51563: "resisting", + 51564: "resize", + 51565: "resolute", + 51566: "resolved", + 51611: "resonant", + 51612: "resonate", + 51613: "resort", + 51614: "resource", + 51615: "respect", + 51616: "resubmit", + 51621: "result", + 51622: "resume", + 51623: "resupply", + 51624: "resurface", + 51625: "resurrect", + 51626: "retail", + 51631: "retainer", + 51632: "retaining", + 51633: "retake", + 51634: "retaliate", + 51635: "retention", + 51636: "rethink", + 51641: "retinal", + 51642: "retired", + 51643: "retiree", + 51644: "retiring", + 51645: "retold", + 51646: "retool", + 51651: "retorted", + 51652: "retouch", + 51653: "retrace", + 51654: "retract", + 51655: "retrain", + 51656: "retread", + 51661: "retreat", + 51662: "retrial", + 51663: "retrieval", + 51664: "retriever", + 51665: "retry", + 51666: "return", + 52111: "retying", + 52112: "retype", + 52113: "reunion", + 52114: "reunite", + 52115: "reusable", + 52116: "reuse", + 52121: "reveal", + 52122: "reveler", + 52123: "revenge", + 52124: "revenue", + 52125: "reverb", + 52126: "revered", + 52131: "reverence", + 52132: "reverend", + 52133: "reversal", + 52134: "reverse", + 52135: "reversing", + 52136: "reversion", + 52141: "revert", + 52142: "revisable", + 52143: "revise", + 52144: "revision", + 52145: "revisit", + 52146: "revivable", + 52151: "revival", + 52152: "reviver", + 52153: "reviving", + 52154: "revocable", + 52155: "revoke", + 52156: "revolt", + 52161: "revolver", + 52162: "revolving", + 52163: "reward", + 52164: "rewash", + 52165: "rewind", + 52166: "rewire", + 52211: "reword", + 52212: "rework", + 52213: "rewrap", + 52214: "rewrite", + 52215: "rhyme", + 52216: "ribbon", + 52221: "ribcage", + 52222: "rice", + 52223: "riches", + 52224: "richly", + 52225: "richness", + 52226: "rickety", + 52231: "ricotta", + 52232: "riddance", + 52233: "ridden", + 52234: "ride", + 52235: "riding", + 52236: "rifling", + 52241: "rift", + 52242: "rigging", + 52243: "rigid", + 52244: "rigor", + 52245: "rimless", + 52246: "rimmed", + 52251: "rind", + 52252: "rink", + 52253: "rinse", + 52254: "rinsing", + 52255: "riot", + 52256: "ripcord", + 52261: "ripeness", + 52262: "ripening", + 52263: "ripping", + 52264: "ripple", + 52265: "rippling", + 52266: "riptide", + 52311: "rise", + 52312: "rising", + 52313: "risk", + 52314: "risotto", + 52315: "ritalin", + 52316: "ritzy", + 52321: "rival", + 52322: "riverbank", + 52323: "riverbed", + 52324: "riverboat", + 52325: "riverside", + 52326: "riveter", + 52331: "riveting", + 52332: "roamer", + 52333: "roaming", + 52334: "roast", + 52335: "robbing", + 52336: "robe", + 52341: "robin", + 52342: "robotics", + 52343: "robust", + 52344: "rockband", + 52345: "rocker", + 52346: "rocket", + 52351: "rockfish", + 52352: "rockiness", + 52353: "rocking", + 52354: "rocklike", + 52355: "rockslide", + 52356: "rockstar", + 52361: "rocky", + 52362: "rogue", + 52363: "roman", + 52364: "romp", + 52365: "rope", + 52366: "roping", + 52411: "roster", + 52412: "rosy", + 52413: "rotten", + 52414: "rotting", + 52415: "rotunda", + 52416: "roulette", + 52421: "rounding", + 52422: "roundish", + 52423: "roundness", + 52424: "roundup", + 52425: "roundworm", + 52426: "routine", + 52431: "routing", + 52432: "rover", + 52433: "roving", + 52434: "royal", + 52435: "rubbed", + 52436: "rubber", + 52441: "rubbing", + 52442: "rubble", + 52443: "rubdown", + 52444: "ruby", + 52445: "ruckus", + 52446: "rudder", + 52451: "rug", + 52452: "ruined", + 52453: "rule", + 52454: "rumble", + 52455: "rumbling", + 52456: "rummage", + 52461: "rumor", + 52462: "runaround", + 52463: "rundown", + 52464: "runner", + 52465: "running", + 52466: "runny", + 52511: "runt", + 52512: "runway", + 52513: "rupture", + 52514: "rural", + 52515: "ruse", + 52516: "rush", + 52521: "rust", + 52522: "rut", + 52523: "sabbath", + 52524: "sabotage", + 52525: "sacrament", + 52526: "sacred", + 52531: "sacrifice", + 52532: "sadden", + 52533: "saddlebag", + 52534: "saddled", + 52535: "saddling", + 52536: "sadly", + 52541: "sadness", + 52542: "safari", + 52543: "safeguard", + 52544: "safehouse", + 52545: "safely", + 52546: "safeness", + 52551: "saffron", + 52552: "saga", + 52553: "sage", + 52554: "sagging", + 52555: "saggy", + 52556: "said", + 52561: "saint", + 52562: "sake", + 52563: "salad", + 52564: "salami", + 52565: "salaried", + 52566: "salary", + 52611: "saline", + 52612: "salon", + 52613: "saloon", + 52614: "salsa", + 52615: "salt", + 52616: "salutary", + 52621: "salute", + 52622: "salvage", + 52623: "salvaging", + 52624: "salvation", + 52625: "same", + 52626: "sample", + 52631: "sampling", + 52632: "sanction", + 52633: "sanctity", + 52634: "sanctuary", + 52635: "sandal", + 52636: "sandbag", + 52641: "sandbank", + 52642: "sandbar", + 52643: "sandblast", + 52644: "sandbox", + 52645: "sanded", + 52646: "sandfish", + 52651: "sanding", + 52652: "sandlot", + 52653: "sandpaper", + 52654: "sandpit", + 52655: "sandstone", + 52656: "sandstorm", + 52661: "sandworm", + 52662: "sandy", + 52663: "sanitary", + 52664: "sanitizer", + 52665: "sank", + 52666: "santa", + 53111: "sapling", + 53112: "sappiness", + 53113: "sappy", + 53114: "sarcasm", + 53115: "sarcastic", + 53116: "sardine", + 53121: "sash", + 53122: "sasquatch", + 53123: "sassy", + 53124: "satchel", + 53125: "satiable", + 53126: "satin", + 53131: "satirical", + 53132: "satisfied", + 53133: "satisfy", + 53134: "saturate", + 53135: "saturday", + 53136: "sauciness", + 53141: "saucy", + 53142: "sauna", + 53143: "savage", + 53144: "savanna", + 53145: "saved", + 53146: "savings", + 53151: "savior", + 53152: "savor", + 53153: "saxophone", + 53154: "say", + 53155: "scabbed", + 53156: "scabby", + 53161: "scalded", + 53162: "scalding", + 53163: "scale", + 53164: "scaling", + 53165: "scallion", + 53166: "scallop", + 53211: "scalping", + 53212: "scam", + 53213: "scandal", + 53214: "scanner", + 53215: "scanning", + 53216: "scant", + 53221: "scapegoat", + 53222: "scarce", + 53223: "scarcity", + 53224: "scarecrow", + 53225: "scared", + 53226: "scarf", + 53231: "scarily", + 53232: "scariness", + 53233: "scarring", + 53234: "scary", + 53235: "scavenger", + 53236: "scenic", + 53241: "schedule", + 53242: "schematic", + 53243: "scheme", + 53244: "scheming", + 53245: "schilling", + 53246: "schnapps", + 53251: "scholar", + 53252: "science", + 53253: "scientist", + 53254: "scion", + 53255: "scoff", + 53256: "scolding", + 53261: "scone", + 53262: "scoop", + 53263: "scooter", + 53264: "scope", + 53265: "scorch", + 53266: "scorebook", + 53311: "scorecard", + 53312: "scored", + 53313: "scoreless", + 53314: "scorer", + 53315: "scoring", + 53316: "scorn", + 53321: "scorpion", + 53322: "scotch", + 53323: "scoundrel", + 53324: "scoured", + 53325: "scouring", + 53326: "scouting", + 53331: "scouts", + 53332: "scowling", + 53333: "scrabble", + 53334: "scraggly", + 53335: "scrambled", + 53336: "scrambler", + 53341: "scrap", + 53342: "scratch", + 53343: "scrawny", + 53344: "screen", + 53345: "scribble", + 53346: "scribe", + 53351: "scribing", + 53352: "scrimmage", + 53353: "script", + 53354: "scroll", + 53355: "scrooge", + 53356: "scrounger", + 53361: "scrubbed", + 53362: "scrubber", + 53363: "scruffy", + 53364: "scrunch", + 53365: "scrutiny", + 53366: "scuba", + 53411: "scuff", + 53412: "sculptor", + 53413: "sculpture", + 53414: "scurvy", + 53415: "scuttle", + 53416: "secluded", + 53421: "secluding", + 53422: "seclusion", + 53423: "second", + 53424: "secrecy", + 53425: "secret", + 53426: "sectional", + 53431: "sector", + 53432: "secular", + 53433: "securely", + 53434: "security", + 53435: "sedan", + 53436: "sedate", + 53441: "sedation", + 53442: "sedative", + 53443: "sediment", + 53444: "seduce", + 53445: "seducing", + 53446: "segment", + 53451: "seismic", + 53452: "seizing", + 53453: "seldom", + 53454: "selected", + 53455: "selection", + 53456: "selective", + 53461: "selector", + 53462: "self", + 53463: "seltzer", + 53464: "semantic", + 53465: "semester", + 53466: "semicolon", + 53511: "semifinal", + 53512: "seminar", + 53513: "semisoft", + 53514: "semisweet", + 53515: "senate", + 53516: "senator", + 53521: "send", + 53522: "senior", + 53523: "senorita", + 53524: "sensation", + 53525: "sensitive", + 53526: "sensitize", + 53531: "sensually", + 53532: "sensuous", + 53533: "sepia", + 53534: "september", + 53535: "septic", + 53536: "septum", + 53541: "sequel", + 53542: "sequence", + 53543: "sequester", + 53544: "series", + 53545: "sermon", + 53546: "serotonin", + 53551: "serpent", + 53552: "serrated", + 53553: "serve", + 53554: "service", + 53555: "serving", + 53556: "sesame", + 53561: "sessions", + 53562: "setback", + 53563: "setting", + 53564: "settle", + 53565: "settling", + 53566: "setup", + 53611: "sevenfold", + 53612: "seventeen", + 53613: "seventh", + 53614: "seventy", + 53615: "severity", + 53616: "shabby", + 53621: "shack", + 53622: "shaded", + 53623: "shadily", + 53624: "shadiness", + 53625: "shading", + 53626: "shadow", + 53631: "shady", + 53632: "shaft", + 53633: "shakable", + 53634: "shakily", + 53635: "shakiness", + 53636: "shaking", + 53641: "shaky", + 53642: "shale", + 53643: "shallot", + 53644: "shallow", + 53645: "shame", + 53646: "shampoo", + 53651: "shamrock", + 53652: "shank", + 53653: "shanty", + 53654: "shape", + 53655: "shaping", + 53656: "share", + 53661: "sharpener", + 53662: "sharper", + 53663: "sharpie", + 53664: "sharply", + 53665: "sharpness", + 53666: "shawl", + 54111: "sheath", + 54112: "shed", + 54113: "sheep", + 54114: "sheet", + 54115: "shelf", + 54116: "shell", + 54121: "shelter", + 54122: "shelve", + 54123: "shelving", + 54124: "sherry", + 54125: "shield", + 54126: "shifter", + 54131: "shifting", + 54132: "shiftless", + 54133: "shifty", + 54134: "shimmer", + 54135: "shimmy", + 54136: "shindig", + 54141: "shine", + 54142: "shingle", + 54143: "shininess", + 54144: "shining", + 54145: "shiny", + 54146: "ship", + 54151: "shirt", + 54152: "shivering", + 54153: "shock", + 54154: "shone", + 54155: "shoplift", + 54156: "shopper", + 54161: "shopping", + 54162: "shoptalk", + 54163: "shore", + 54164: "shortage", + 54165: "shortcake", + 54166: "shortcut", + 54211: "shorten", + 54212: "shorter", + 54213: "shorthand", + 54214: "shortlist", + 54215: "shortly", + 54216: "shortness", + 54221: "shorts", + 54222: "shortwave", + 54223: "shorty", + 54224: "shout", + 54225: "shove", + 54226: "showbiz", + 54231: "showcase", + 54232: "showdown", + 54233: "shower", + 54234: "showgirl", + 54235: "showing", + 54236: "showman", + 54241: "shown", + 54242: "showoff", + 54243: "showpiece", + 54244: "showplace", + 54245: "showroom", + 54246: "showy", + 54251: "shrank", + 54252: "shrapnel", + 54253: "shredder", + 54254: "shredding", + 54255: "shrewdly", + 54256: "shriek", + 54261: "shrill", + 54262: "shrimp", + 54263: "shrine", + 54264: "shrink", + 54265: "shrivel", + 54266: "shrouded", + 54311: "shrubbery", + 54312: "shrubs", + 54313: "shrug", + 54314: "shrunk", + 54315: "shucking", + 54316: "shudder", + 54321: "shuffle", + 54322: "shuffling", + 54323: "shun", + 54324: "shush", + 54325: "shut", + 54326: "shy", + 54331: "siamese", + 54332: "siberian", + 54333: "sibling", + 54334: "siding", + 54335: "sierra", + 54336: "siesta", + 54341: "sift", + 54342: "sighing", + 54343: "silenced", + 54344: "silencer", + 54345: "silent", + 54346: "silica", + 54351: "silicon", + 54352: "silk", + 54353: "silliness", + 54354: "silly", + 54355: "silo", + 54356: "silt", + 54361: "silver", + 54362: "similarly", + 54363: "simile", + 54364: "simmering", + 54365: "simple", + 54366: "simplify", + 54411: "simply", + 54412: "sincere", + 54413: "sincerity", + 54414: "singer", + 54415: "singing", + 54416: "single", + 54421: "singular", + 54422: "sinister", + 54423: "sinless", + 54424: "sinner", + 54425: "sinuous", + 54426: "sip", + 54431: "siren", + 54432: "sister", + 54433: "sitcom", + 54434: "sitter", + 54435: "sitting", + 54436: "situated", + 54441: "situation", + 54442: "sixfold", + 54443: "sixteen", + 54444: "sixth", + 54445: "sixties", + 54446: "sixtieth", + 54451: "sixtyfold", + 54452: "sizable", + 54453: "sizably", + 54454: "size", + 54455: "sizing", + 54456: "sizzle", + 54461: "sizzling", + 54462: "skater", + 54463: "skating", + 54464: "skedaddle", + 54465: "skeletal", + 54466: "skeleton", + 54511: "skeptic", + 54512: "sketch", + 54513: "skewed", + 54514: "skewer", + 54515: "skid", + 54516: "skied", + 54521: "skier", + 54522: "skies", + 54523: "skiing", + 54524: "skilled", + 54525: "skillet", + 54526: "skillful", + 54531: "skimmed", + 54532: "skimmer", + 54533: "skimming", + 54534: "skimpily", + 54535: "skincare", + 54536: "skinhead", + 54541: "skinless", + 54542: "skinning", + 54543: "skinny", + 54544: "skintight", + 54545: "skipper", + 54546: "skipping", + 54551: "skirmish", + 54552: "skirt", + 54553: "skittle", + 54554: "skydiver", + 54555: "skylight", + 54556: "skyline", + 54561: "skype", + 54562: "skyrocket", + 54563: "skyward", + 54564: "slab", + 54565: "slacked", + 54566: "slacker", + 54611: "slacking", + 54612: "slackness", + 54613: "slacks", + 54614: "slain", + 54615: "slam", + 54616: "slander", + 54621: "slang", + 54622: "slapping", + 54623: "slapstick", + 54624: "slashed", + 54625: "slashing", + 54626: "slate", + 54631: "slather", + 54632: "slaw", + 54633: "sled", + 54634: "sleek", + 54635: "sleep", + 54636: "sleet", + 54641: "sleeve", + 54642: "slept", + 54643: "sliceable", + 54644: "sliced", + 54645: "slicer", + 54646: "slicing", + 54651: "slick", + 54652: "slider", + 54653: "slideshow", + 54654: "sliding", + 54655: "slighted", + 54656: "slighting", + 54661: "slightly", + 54662: "slimness", + 54663: "slimy", + 54664: "slinging", + 54665: "slingshot", + 54666: "slinky", + 55111: "slip", + 55112: "slit", + 55113: "sliver", + 55114: "slobbery", + 55115: "slogan", + 55116: "sloped", + 55121: "sloping", + 55122: "sloppily", + 55123: "sloppy", + 55124: "slot", + 55125: "slouching", + 55126: "slouchy", + 55131: "sludge", + 55132: "slug", + 55133: "slum", + 55134: "slurp", + 55135: "slush", + 55136: "sly", + 55141: "small", + 55142: "smartly", + 55143: "smartness", + 55144: "smasher", + 55145: "smashing", + 55146: "smashup", + 55151: "smell", + 55152: "smelting", + 55153: "smile", + 55154: "smilingly", + 55155: "smirk", + 55156: "smite", + 55161: "smith", + 55162: "smitten", + 55163: "smock", + 55164: "smog", + 55165: "smoked", + 55166: "smokeless", + 55211: "smokiness", + 55212: "smoking", + 55213: "smoky", + 55214: "smolder", + 55215: "smooth", + 55216: "smother", + 55221: "smudge", + 55222: "smudgy", + 55223: "smuggler", + 55224: "smuggling", + 55225: "smugly", + 55226: "smugness", + 55231: "snack", + 55232: "snagged", + 55233: "snaking", + 55234: "snap", + 55235: "snare", + 55236: "snarl", + 55241: "snazzy", + 55242: "sneak", + 55243: "sneer", + 55244: "sneeze", + 55245: "sneezing", + 55246: "snide", + 55251: "sniff", + 55252: "snippet", + 55253: "snipping", + 55254: "snitch", + 55255: "snooper", + 55256: "snooze", + 55261: "snore", + 55262: "snoring", + 55263: "snorkel", + 55264: "snort", + 55265: "snout", + 55266: "snowbird", + 55311: "snowboard", + 55312: "snowbound", + 55313: "snowcap", + 55314: "snowdrift", + 55315: "snowdrop", + 55316: "snowfall", + 55321: "snowfield", + 55322: "snowflake", + 55323: "snowiness", + 55324: "snowless", + 55325: "snowman", + 55326: "snowplow", + 55331: "snowshoe", + 55332: "snowstorm", + 55333: "snowsuit", + 55334: "snowy", + 55335: "snub", + 55336: "snuff", + 55341: "snuggle", + 55342: "snugly", + 55343: "snugness", + 55344: "speak", + 55345: "spearfish", + 55346: "spearhead", + 55351: "spearman", + 55352: "spearmint", + 55353: "species", + 55354: "specimen", + 55355: "specked", + 55356: "speckled", + 55361: "specks", + 55362: "spectacle", + 55363: "spectator", + 55364: "spectrum", + 55365: "speculate", + 55366: "speech", + 55411: "speed", + 55412: "spellbind", + 55413: "speller", + 55414: "spelling", + 55415: "spendable", + 55416: "spender", + 55421: "spending", + 55422: "spent", + 55423: "spew", + 55424: "sphere", + 55425: "spherical", + 55426: "sphinx", + 55431: "spider", + 55432: "spied", + 55433: "spiffy", + 55434: "spill", + 55435: "spilt", + 55436: "spinach", + 55441: "spinal", + 55442: "spindle", + 55443: "spinner", + 55444: "spinning", + 55445: "spinout", + 55446: "spinster", + 55451: "spiny", + 55452: "spiral", + 55453: "spirited", + 55454: "spiritism", + 55455: "spirits", + 55456: "spiritual", + 55461: "splashed", + 55462: "splashing", + 55463: "splashy", + 55464: "splatter", + 55465: "spleen", + 55466: "splendid", + 55511: "splendor", + 55512: "splice", + 55513: "splicing", + 55514: "splinter", + 55515: "splotchy", + 55516: "splurge", + 55521: "spoilage", + 55522: "spoiled", + 55523: "spoiler", + 55524: "spoiling", + 55525: "spoils", + 55526: "spoken", + 55531: "spokesman", + 55532: "sponge", + 55533: "spongy", + 55534: "sponsor", + 55535: "spoof", + 55536: "spookily", + 55541: "spooky", + 55542: "spool", + 55543: "spoon", + 55544: "spore", + 55545: "sporting", + 55546: "sports", + 55551: "sporty", + 55552: "spotless", + 55553: "spotlight", + 55554: "spotted", + 55555: "spotter", + 55556: "spotting", + 55561: "spotty", + 55562: "spousal", + 55563: "spouse", + 55564: "spout", + 55565: "sprain", + 55566: "sprang", + 55611: "sprawl", + 55612: "spray", + 55613: "spree", + 55614: "sprig", + 55615: "spring", + 55616: "sprinkled", + 55621: "sprinkler", + 55622: "sprint", + 55623: "sprite", + 55624: "sprout", + 55625: "spruce", + 55626: "sprung", + 55631: "spry", + 55632: "spud", + 55633: "spur", + 55634: "sputter", + 55635: "spyglass", + 55636: "squabble", + 55641: "squad", + 55642: "squall", + 55643: "squander", + 55644: "squash", + 55645: "squatted", + 55646: "squatter", + 55651: "squatting", + 55652: "squeak", + 55653: "squealer", + 55654: "squealing", + 55655: "squeamish", + 55656: "squeegee", + 55661: "squeeze", + 55662: "squeezing", + 55663: "squid", + 55664: "squiggle", + 55665: "squiggly", + 55666: "squint", + 56111: "squire", + 56112: "squirt", + 56113: "squishier", + 56114: "squishy", + 56115: "stability", + 56116: "stabilize", + 56121: "stable", + 56122: "stack", + 56123: "stadium", + 56124: "staff", + 56125: "stage", + 56126: "staging", + 56131: "stagnant", + 56132: "stagnate", + 56133: "stainable", + 56134: "stained", + 56135: "staining", + 56136: "stainless", + 56141: "stalemate", + 56142: "staleness", + 56143: "stalling", + 56144: "stallion", + 56145: "stamina", + 56146: "stammer", + 56151: "stamp", + 56152: "stand", + 56153: "stank", + 56154: "staple", + 56155: "stapling", + 56156: "starboard", + 56161: "starch", + 56162: "stardom", + 56163: "stardust", + 56164: "starfish", + 56165: "stargazer", + 56166: "staring", + 56211: "stark", + 56212: "starless", + 56213: "starlet", + 56214: "starlight", + 56215: "starlit", + 56216: "starring", + 56221: "starry", + 56222: "starship", + 56223: "starter", + 56224: "starting", + 56225: "startle", + 56226: "startling", + 56231: "startup", + 56232: "starved", + 56233: "starving", + 56234: "stash", + 56235: "state", + 56236: "static", + 56241: "statistic", + 56242: "statue", + 56243: "stature", + 56244: "status", + 56245: "statute", + 56246: "statutory", + 56251: "staunch", + 56252: "stays", + 56253: "steadfast", + 56254: "steadier", + 56255: "steadily", + 56256: "steadying", + 56261: "steam", + 56262: "steed", + 56263: "steep", + 56264: "steerable", + 56265: "steering", + 56266: "steersman", + 56311: "stegosaur", + 56312: "stellar", + 56313: "stem", + 56314: "stench", + 56315: "stencil", + 56316: "step", + 56321: "stereo", + 56322: "sterile", + 56323: "sterility", + 56324: "sterilize", + 56325: "sterling", + 56326: "sternness", + 56331: "sternum", + 56332: "stew", + 56333: "stick", + 56334: "stiffen", + 56335: "stiffly", + 56336: "stiffness", + 56341: "stifle", + 56342: "stifling", + 56343: "stillness", + 56344: "stilt", + 56345: "stimulant", + 56346: "stimulate", + 56351: "stimuli", + 56352: "stimulus", + 56353: "stinger", + 56354: "stingily", + 56355: "stinging", + 56356: "stingray", + 56361: "stingy", + 56362: "stinking", + 56363: "stinky", + 56364: "stipend", + 56365: "stipulate", + 56366: "stir", + 56411: "stitch", + 56412: "stock", + 56413: "stoic", + 56414: "stoke", + 56415: "stole", + 56416: "stomp", + 56421: "stonewall", + 56422: "stoneware", + 56423: "stonework", + 56424: "stoning", + 56425: "stony", + 56426: "stood", + 56431: "stooge", + 56432: "stool", + 56433: "stoop", + 56434: "stoplight", + 56435: "stoppable", + 56436: "stoppage", + 56441: "stopped", + 56442: "stopper", + 56443: "stopping", + 56444: "stopwatch", + 56445: "storable", + 56446: "storage", + 56451: "storeroom", + 56452: "storewide", + 56453: "storm", + 56454: "stout", + 56455: "stove", + 56456: "stowaway", + 56461: "stowing", + 56462: "straddle", + 56463: "straggler", + 56464: "strained", + 56465: "strainer", + 56466: "straining", + 56511: "strangely", + 56512: "stranger", + 56513: "strangle", + 56514: "strategic", + 56515: "strategy", + 56516: "stratus", + 56521: "straw", + 56522: "stray", + 56523: "streak", + 56524: "stream", + 56525: "street", + 56526: "strength", + 56531: "strenuous", + 56532: "strep", + 56533: "stress", + 56534: "stretch", + 56535: "strewn", + 56536: "stricken", + 56541: "strict", + 56542: "stride", + 56543: "strife", + 56544: "strike", + 56545: "striking", + 56546: "strive", + 56551: "striving", + 56552: "strobe", + 56553: "strode", + 56554: "stroller", + 56555: "strongbox", + 56556: "strongly", + 56561: "strongman", + 56562: "struck", + 56563: "structure", + 56564: "strudel", + 56565: "struggle", + 56566: "strum", + 56611: "strung", + 56612: "strut", + 56613: "stubbed", + 56614: "stubble", + 56615: "stubbly", + 56616: "stubborn", + 56621: "stucco", + 56622: "stuck", + 56623: "student", + 56624: "studied", + 56625: "studio", + 56626: "study", + 56631: "stuffed", + 56632: "stuffing", + 56633: "stuffy", + 56634: "stumble", + 56635: "stumbling", + 56636: "stump", + 56641: "stung", + 56642: "stunned", + 56643: "stunner", + 56644: "stunning", + 56645: "stunt", + 56646: "stupor", + 56651: "sturdily", + 56652: "sturdy", + 56653: "styling", + 56654: "stylishly", + 56655: "stylist", + 56656: "stylized", + 56661: "stylus", + 56662: "suave", + 56663: "subarctic", + 56664: "subatomic", + 56665: "subdivide", + 56666: "subdued", + 61111: "subduing", + 61112: "subfloor", + 61113: "subgroup", + 61114: "subheader", + 61115: "subject", + 61116: "sublease", + 61121: "sublet", + 61122: "sublevel", + 61123: "sublime", + 61124: "submarine", + 61125: "submerge", + 61126: "submersed", + 61131: "submitter", + 61132: "subpanel", + 61133: "subpar", + 61134: "subplot", + 61135: "subprime", + 61136: "subscribe", + 61141: "subscript", + 61142: "subsector", + 61143: "subside", + 61144: "subsiding", + 61145: "subsidize", + 61146: "subsidy", + 61151: "subsoil", + 61152: "subsonic", + 61153: "substance", + 61154: "subsystem", + 61155: "subtext", + 61156: "subtitle", + 61161: "subtly", + 61162: "subtotal", + 61163: "subtract", + 61164: "subtype", + 61165: "suburb", + 61166: "subway", + 61211: "subwoofer", + 61212: "subzero", + 61213: "succulent", + 61214: "such", + 61215: "suction", + 61216: "sudden", + 61221: "sudoku", + 61222: "suds", + 61223: "sufferer", + 61224: "suffering", + 61225: "suffice", + 61226: "suffix", + 61231: "suffocate", + 61232: "suffrage", + 61233: "sugar", + 61234: "suggest", + 61235: "suing", + 61236: "suitable", + 61241: "suitably", + 61242: "suitcase", + 61243: "suitor", + 61244: "sulfate", + 61245: "sulfide", + 61246: "sulfite", + 61251: "sulfur", + 61252: "sulk", + 61253: "sullen", + 61254: "sulphate", + 61255: "sulphuric", + 61256: "sultry", + 61261: "superbowl", + 61262: "superglue", + 61263: "superhero", + 61264: "superior", + 61265: "superjet", + 61266: "superman", + 61311: "supermom", + 61312: "supernova", + 61313: "supervise", + 61314: "supper", + 61315: "supplier", + 61316: "supply", + 61321: "support", + 61322: "supremacy", + 61323: "supreme", + 61324: "surcharge", + 61325: "surely", + 61326: "sureness", + 61331: "surface", + 61332: "surfacing", + 61333: "surfboard", + 61334: "surfer", + 61335: "surgery", + 61336: "surgical", + 61341: "surging", + 61342: "surname", + 61343: "surpass", + 61344: "surplus", + 61345: "surprise", + 61346: "surreal", + 61351: "surrender", + 61352: "surrogate", + 61353: "surround", + 61354: "survey", + 61355: "survival", + 61356: "survive", + 61361: "surviving", + 61362: "survivor", + 61363: "sushi", + 61364: "suspect", + 61365: "suspend", + 61366: "suspense", + 61411: "sustained", + 61412: "sustainer", + 61413: "swab", + 61414: "swaddling", + 61415: "swagger", + 61416: "swampland", + 61421: "swan", + 61422: "swapping", + 61423: "swarm", + 61424: "sway", + 61425: "swear", + 61426: "sweat", + 61431: "sweep", + 61432: "swell", + 61433: "swept", + 61434: "swerve", + 61435: "swifter", + 61436: "swiftly", + 61441: "swiftness", + 61442: "swimmable", + 61443: "swimmer", + 61444: "swimming", + 61445: "swimsuit", + 61446: "swimwear", + 61451: "swinger", + 61452: "swinging", + 61453: "swipe", + 61454: "swirl", + 61455: "switch", + 61456: "swivel", + 61461: "swizzle", + 61462: "swooned", + 61463: "swoop", + 61464: "swoosh", + 61465: "swore", + 61466: "sworn", + 61511: "swung", + 61512: "sycamore", + 61513: "sympathy", + 61514: "symphonic", + 61515: "symphony", + 61516: "symptom", + 61521: "synapse", + 61522: "syndrome", + 61523: "synergy", + 61524: "synopses", + 61525: "synopsis", + 61526: "synthesis", + 61531: "synthetic", + 61532: "syrup", + 61533: "system", + 61534: "t-shirt", + 61535: "tabasco", + 61536: "tabby", + 61541: "tableful", + 61542: "tables", + 61543: "tablet", + 61544: "tableware", + 61545: "tabloid", + 61546: "tackiness", + 61551: "tacking", + 61552: "tackle", + 61553: "tackling", + 61554: "tacky", + 61555: "taco", + 61556: "tactful", + 61561: "tactical", + 61562: "tactics", + 61563: "tactile", + 61564: "tactless", + 61565: "tadpole", + 61566: "taekwondo", + 61611: "tag", + 61612: "tainted", + 61613: "take", + 61614: "taking", + 61615: "talcum", + 61616: "talisman", + 61621: "tall", + 61622: "talon", + 61623: "tamale", + 61624: "tameness", + 61625: "tamer", + 61626: "tamper", + 61631: "tank", + 61632: "tanned", + 61633: "tannery", + 61634: "tanning", + 61635: "tantrum", + 61636: "tapeless", + 61641: "tapered", + 61642: "tapering", + 61643: "tapestry", + 61644: "tapioca", + 61645: "tapping", + 61646: "taps", + 61651: "tarantula", + 61652: "target", + 61653: "tarmac", + 61654: "tarnish", + 61655: "tarot", + 61656: "tartar", + 61661: "tartly", + 61662: "tartness", + 61663: "task", + 61664: "tassel", + 61665: "taste", + 61666: "tastiness", + 62111: "tasting", + 62112: "tasty", + 62113: "tattered", + 62114: "tattle", + 62115: "tattling", + 62116: "tattoo", + 62121: "taunt", + 62122: "tavern", + 62123: "thank", + 62124: "that", + 62125: "thaw", + 62126: "theater", + 62131: "theatrics", + 62132: "thee", + 62133: "theft", + 62134: "theme", + 62135: "theology", + 62136: "theorize", + 62141: "thermal", + 62142: "thermos", + 62143: "thesaurus", + 62144: "these", + 62145: "thesis", + 62146: "thespian", + 62151: "thicken", + 62152: "thicket", + 62153: "thickness", + 62154: "thieving", + 62155: "thievish", + 62156: "thigh", + 62161: "thimble", + 62162: "thing", + 62163: "think", + 62164: "thinly", + 62165: "thinner", + 62166: "thinness", + 62211: "thinning", + 62212: "thirstily", + 62213: "thirsting", + 62214: "thirsty", + 62215: "thirteen", + 62216: "thirty", + 62221: "thong", + 62222: "thorn", + 62223: "those", + 62224: "thousand", + 62225: "thrash", + 62226: "thread", + 62231: "threaten", + 62232: "threefold", + 62233: "thrift", + 62234: "thrill", + 62235: "thrive", + 62236: "thriving", + 62241: "throat", + 62242: "throbbing", + 62243: "throng", + 62244: "throttle", + 62245: "throwaway", + 62246: "throwback", + 62251: "thrower", + 62252: "throwing", + 62253: "thud", + 62254: "thumb", + 62255: "thumping", + 62256: "thursday", + 62261: "thus", + 62262: "thwarting", + 62263: "thyself", + 62264: "tiara", + 62265: "tibia", + 62266: "tidal", + 62311: "tidbit", + 62312: "tidiness", + 62313: "tidings", + 62314: "tidy", + 62315: "tiger", + 62316: "tighten", + 62321: "tightly", + 62322: "tightness", + 62323: "tightrope", + 62324: "tightwad", + 62325: "tigress", + 62326: "tile", + 62331: "tiling", + 62332: "till", + 62333: "tilt", + 62334: "timid", + 62335: "timing", + 62336: "timothy", + 62341: "tinderbox", + 62342: "tinfoil", + 62343: "tingle", + 62344: "tingling", + 62345: "tingly", + 62346: "tinker", + 62351: "tinkling", + 62352: "tinsel", + 62353: "tinsmith", + 62354: "tint", + 62355: "tinwork", + 62356: "tiny", + 62361: "tipoff", + 62362: "tipped", + 62363: "tipper", + 62364: "tipping", + 62365: "tiptoeing", + 62366: "tiptop", + 62411: "tiring", + 62412: "tissue", + 62413: "trace", + 62414: "tracing", + 62415: "track", + 62416: "traction", + 62421: "tractor", + 62422: "trade", + 62423: "trading", + 62424: "tradition", + 62425: "traffic", + 62426: "tragedy", + 62431: "trailing", + 62432: "trailside", + 62433: "train", + 62434: "traitor", + 62435: "trance", + 62436: "tranquil", + 62441: "transfer", + 62442: "transform", + 62443: "translate", + 62444: "transpire", + 62445: "transport", + 62446: "transpose", + 62451: "trapdoor", + 62452: "trapeze", + 62453: "trapezoid", + 62454: "trapped", + 62455: "trapper", + 62456: "trapping", + 62461: "traps", + 62462: "trash", + 62463: "travel", + 62464: "traverse", + 62465: "travesty", + 62466: "tray", + 62511: "treachery", + 62512: "treading", + 62513: "treadmill", + 62514: "treason", + 62515: "treat", + 62516: "treble", + 62521: "tree", + 62522: "trekker", + 62523: "tremble", + 62524: "trembling", + 62525: "tremor", + 62526: "trench", + 62531: "trend", + 62532: "trespass", + 62533: "triage", + 62534: "trial", + 62535: "triangle", + 62536: "tribesman", + 62541: "tribunal", + 62542: "tribune", + 62543: "tributary", + 62544: "tribute", + 62545: "triceps", + 62546: "trickery", + 62551: "trickily", + 62552: "tricking", + 62553: "trickle", + 62554: "trickster", + 62555: "tricky", + 62556: "tricolor", + 62561: "tricycle", + 62562: "trident", + 62563: "tried", + 62564: "trifle", + 62565: "trifocals", + 62566: "trillion", + 62611: "trilogy", + 62612: "trimester", + 62613: "trimmer", + 62614: "trimming", + 62615: "trimness", + 62616: "trinity", + 62621: "trio", + 62622: "tripod", + 62623: "tripping", + 62624: "triumph", + 62625: "trivial", + 62626: "trodden", + 62631: "trolling", + 62632: "trombone", + 62633: "trophy", + 62634: "tropical", + 62635: "tropics", + 62636: "trouble", + 62641: "troubling", + 62642: "trough", + 62643: "trousers", + 62644: "trout", + 62645: "trowel", + 62646: "truce", + 62651: "truck", + 62652: "truffle", + 62653: "trump", + 62654: "trunks", + 62655: "trustable", + 62656: "trustee", + 62661: "trustful", + 62662: "trusting", + 62663: "trustless", + 62664: "truth", + 62665: "try", + 62666: "tubby", + 63111: "tubeless", + 63112: "tubular", + 63113: "tucking", + 63114: "tuesday", + 63115: "tug", + 63116: "tuition", + 63121: "tulip", + 63122: "tumble", + 63123: "tumbling", + 63124: "tummy", + 63125: "turban", + 63126: "turbine", + 63131: "turbofan", + 63132: "turbojet", + 63133: "turbulent", + 63134: "turf", + 63135: "turkey", + 63136: "turmoil", + 63141: "turret", + 63142: "turtle", + 63143: "tusk", + 63144: "tutor", + 63145: "tutu", + 63146: "tux", + 63151: "tweak", + 63152: "tweed", + 63153: "tweet", + 63154: "tweezers", + 63155: "twelve", + 63156: "twentieth", + 63161: "twenty", + 63162: "twerp", + 63163: "twice", + 63164: "twiddle", + 63165: "twiddling", + 63166: "twig", + 63211: "twilight", + 63212: "twine", + 63213: "twins", + 63214: "twirl", + 63215: "twistable", + 63216: "twisted", + 63221: "twister", + 63222: "twisting", + 63223: "twisty", + 63224: "twitch", + 63225: "twitter", + 63226: "tycoon", + 63231: "tying", + 63232: "tyke", + 63233: "udder", + 63234: "ultimate", + 63235: "ultimatum", + 63236: "ultra", + 63241: "umbilical", + 63242: "umbrella", + 63243: "umpire", + 63244: "unabashed", + 63245: "unable", + 63246: "unadorned", + 63251: "unadvised", + 63252: "unafraid", + 63253: "unaired", + 63254: "unaligned", + 63255: "unaltered", + 63256: "unarmored", + 63261: "unashamed", + 63262: "unaudited", + 63263: "unawake", + 63264: "unaware", + 63265: "unbaked", + 63266: "unbalance", + 63311: "unbeaten", + 63312: "unbend", + 63313: "unbent", + 63314: "unbiased", + 63315: "unbitten", + 63316: "unblended", + 63321: "unblessed", + 63322: "unblock", + 63323: "unbolted", + 63324: "unbounded", + 63325: "unboxed", + 63326: "unbraided", + 63331: "unbridle", + 63332: "unbroken", + 63333: "unbuckled", + 63334: "unbundle", + 63335: "unburned", + 63336: "unbutton", + 63341: "uncanny", + 63342: "uncapped", + 63343: "uncaring", + 63344: "uncertain", + 63345: "unchain", + 63346: "unchanged", + 63351: "uncharted", + 63352: "uncheck", + 63353: "uncivil", + 63354: "unclad", + 63355: "unclaimed", + 63356: "unclamped", + 63361: "unclasp", + 63362: "uncle", + 63363: "unclip", + 63364: "uncloak", + 63365: "unclog", + 63366: "unclothed", + 63411: "uncoated", + 63412: "uncoiled", + 63413: "uncolored", + 63414: "uncombed", + 63415: "uncommon", + 63416: "uncooked", + 63421: "uncork", + 63422: "uncorrupt", + 63423: "uncounted", + 63424: "uncouple", + 63425: "uncouth", + 63426: "uncover", + 63431: "uncross", + 63432: "uncrown", + 63433: "uncrushed", + 63434: "uncured", + 63435: "uncurious", + 63436: "uncurled", + 63441: "uncut", + 63442: "undamaged", + 63443: "undated", + 63444: "undaunted", + 63445: "undead", + 63446: "undecided", + 63451: "undefined", + 63452: "underage", + 63453: "underarm", + 63454: "undercoat", + 63455: "undercook", + 63456: "undercut", + 63461: "underdog", + 63462: "underdone", + 63463: "underfed", + 63464: "underfeed", + 63465: "underfoot", + 63466: "undergo", + 63511: "undergrad", + 63512: "underhand", + 63513: "underline", + 63514: "underling", + 63515: "undermine", + 63516: "undermost", + 63521: "underpaid", + 63522: "underpass", + 63523: "underpay", + 63524: "underrate", + 63525: "undertake", + 63526: "undertone", + 63531: "undertook", + 63532: "undertow", + 63533: "underuse", + 63534: "underwear", + 63535: "underwent", + 63536: "underwire", + 63541: "undesired", + 63542: "undiluted", + 63543: "undivided", + 63544: "undocked", + 63545: "undoing", + 63546: "undone", + 63551: "undrafted", + 63552: "undress", + 63553: "undrilled", + 63554: "undusted", + 63555: "undying", + 63556: "unearned", + 63561: "unearth", + 63562: "unease", + 63563: "uneasily", + 63564: "uneasy", + 63565: "uneatable", + 63566: "uneaten", + 63611: "unedited", + 63612: "unelected", + 63613: "unending", + 63614: "unengaged", + 63615: "unenvied", + 63616: "unequal", + 63621: "unethical", + 63622: "uneven", + 63623: "unexpired", + 63624: "unexposed", + 63625: "unfailing", + 63626: "unfair", + 63631: "unfasten", + 63632: "unfazed", + 63633: "unfeeling", + 63634: "unfiled", + 63635: "unfilled", + 63636: "unfitted", + 63641: "unfitting", + 63642: "unfixable", + 63643: "unfixed", + 63644: "unflawed", + 63645: "unfocused", + 63646: "unfold", + 63651: "unfounded", + 63652: "unframed", + 63653: "unfreeze", + 63654: "unfrosted", + 63655: "unfrozen", + 63656: "unfunded", + 63661: "unglazed", + 63662: "ungloved", + 63663: "unglue", + 63664: "ungodly", + 63665: "ungraded", + 63666: "ungreased", + 64111: "unguarded", + 64112: "unguided", + 64113: "unhappily", + 64114: "unhappy", + 64115: "unharmed", + 64116: "unhealthy", + 64121: "unheard", + 64122: "unhearing", + 64123: "unheated", + 64124: "unhelpful", + 64125: "unhidden", + 64126: "unhinge", + 64131: "unhitched", + 64132: "unholy", + 64133: "unhook", + 64134: "unicorn", + 64135: "unicycle", + 64136: "unified", + 64141: "unifier", + 64142: "uniformed", + 64143: "uniformly", + 64144: "unify", + 64145: "unimpeded", + 64146: "uninjured", + 64151: "uninstall", + 64152: "uninsured", + 64153: "uninvited", + 64154: "union", + 64155: "uniquely", + 64156: "unisexual", + 64161: "unison", + 64162: "unissued", + 64163: "unit", + 64164: "universal", + 64165: "universe", + 64166: "unjustly", + 64211: "unkempt", + 64212: "unkind", + 64213: "unknotted", + 64214: "unknowing", + 64215: "unknown", + 64216: "unlaced", + 64221: "unlatch", + 64222: "unlawful", + 64223: "unleaded", + 64224: "unlearned", + 64225: "unleash", + 64226: "unless", + 64231: "unleveled", + 64232: "unlighted", + 64233: "unlikable", + 64234: "unlimited", + 64235: "unlined", + 64236: "unlinked", + 64241: "unlisted", + 64242: "unlit", + 64243: "unlivable", + 64244: "unloaded", + 64245: "unloader", + 64246: "unlocked", + 64251: "unlocking", + 64252: "unlovable", + 64253: "unloved", + 64254: "unlovely", + 64255: "unloving", + 64256: "unluckily", + 64261: "unlucky", + 64262: "unmade", + 64263: "unmanaged", + 64264: "unmanned", + 64265: "unmapped", + 64266: "unmarked", + 64311: "unmasked", + 64312: "unmasking", + 64313: "unmatched", + 64314: "unmindful", + 64315: "unmixable", + 64316: "unmixed", + 64321: "unmolded", + 64322: "unmoral", + 64323: "unmovable", + 64324: "unmoved", + 64325: "unmoving", + 64326: "unnamable", + 64331: "unnamed", + 64332: "unnatural", + 64333: "unneeded", + 64334: "unnerve", + 64335: "unnerving", + 64336: "unnoticed", + 64341: "unopened", + 64342: "unopposed", + 64343: "unpack", + 64344: "unpadded", + 64345: "unpaid", + 64346: "unpainted", + 64351: "unpaired", + 64352: "unpaved", + 64353: "unpeeled", + 64354: "unpicked", + 64355: "unpiloted", + 64356: "unpinned", + 64361: "unplanned", + 64362: "unplanted", + 64363: "unpleased", + 64364: "unpledged", + 64365: "unplowed", + 64366: "unplug", + 64411: "unpopular", + 64412: "unproven", + 64413: "unquote", + 64414: "unranked", + 64415: "unrated", + 64416: "unraveled", + 64421: "unreached", + 64422: "unread", + 64423: "unreal", + 64424: "unreeling", + 64425: "unrefined", + 64426: "unrelated", + 64431: "unrented", + 64432: "unrest", + 64433: "unretired", + 64434: "unrevised", + 64435: "unrigged", + 64436: "unripe", + 64441: "unrivaled", + 64442: "unroasted", + 64443: "unrobed", + 64444: "unroll", + 64445: "unruffled", + 64446: "unruly", + 64451: "unrushed", + 64452: "unsaddle", + 64453: "unsafe", + 64454: "unsaid", + 64455: "unsalted", + 64456: "unsaved", + 64461: "unsavory", + 64462: "unscathed", + 64463: "unscented", + 64464: "unscrew", + 64465: "unsealed", + 64466: "unseated", + 64511: "unsecured", + 64512: "unseeing", + 64513: "unseemly", + 64514: "unseen", + 64515: "unselect", + 64516: "unselfish", + 64521: "unsent", + 64522: "unsettled", + 64523: "unshackle", + 64524: "unshaken", + 64525: "unshaved", + 64526: "unshaven", + 64531: "unsheathe", + 64532: "unshipped", + 64533: "unsightly", + 64534: "unsigned", + 64535: "unskilled", + 64536: "unsliced", + 64541: "unsmooth", + 64542: "unsnap", + 64543: "unsocial", + 64544: "unsoiled", + 64545: "unsold", + 64546: "unsolved", + 64551: "unsorted", + 64552: "unspoiled", + 64553: "unspoken", + 64554: "unstable", + 64555: "unstaffed", + 64556: "unstamped", + 64561: "unsteady", + 64562: "unsterile", + 64563: "unstirred", + 64564: "unstitch", + 64565: "unstopped", + 64566: "unstuck", + 64611: "unstuffed", + 64612: "unstylish", + 64613: "unsubtle", + 64614: "unsubtly", + 64615: "unsuited", + 64616: "unsure", + 64621: "unsworn", + 64622: "untagged", + 64623: "untainted", + 64624: "untaken", + 64625: "untamed", + 64626: "untangled", + 64631: "untapped", + 64632: "untaxed", + 64633: "unthawed", + 64634: "unthread", + 64635: "untidy", + 64636: "untie", + 64641: "until", + 64642: "untimed", + 64643: "untimely", + 64644: "untitled", + 64645: "untoasted", + 64646: "untold", + 64651: "untouched", + 64652: "untracked", + 64653: "untrained", + 64654: "untreated", + 64655: "untried", + 64656: "untrimmed", + 64661: "untrue", + 64662: "untruth", + 64663: "unturned", + 64664: "untwist", + 64665: "untying", + 64666: "unusable", + 65111: "unused", + 65112: "unusual", + 65113: "unvalued", + 65114: "unvaried", + 65115: "unvarying", + 65116: "unveiled", + 65121: "unveiling", + 65122: "unvented", + 65123: "unviable", + 65124: "unvisited", + 65125: "unvocal", + 65126: "unwanted", + 65131: "unwarlike", + 65132: "unwary", + 65133: "unwashed", + 65134: "unwatched", + 65135: "unweave", + 65136: "unwed", + 65141: "unwelcome", + 65142: "unwell", + 65143: "unwieldy", + 65144: "unwilling", + 65145: "unwind", + 65146: "unwired", + 65151: "unwitting", + 65152: "unwomanly", + 65153: "unworldly", + 65154: "unworn", + 65155: "unworried", + 65156: "unworthy", + 65161: "unwound", + 65162: "unwoven", + 65163: "unwrapped", + 65164: "unwritten", + 65165: "unzip", + 65166: "upbeat", + 65211: "upchuck", + 65212: "upcoming", + 65213: "upcountry", + 65214: "update", + 65215: "upfront", + 65216: "upgrade", + 65221: "upheaval", + 65222: "upheld", + 65223: "uphill", + 65224: "uphold", + 65225: "uplifted", + 65226: "uplifting", + 65231: "upload", + 65232: "upon", + 65233: "upper", + 65234: "upright", + 65235: "uprising", + 65236: "upriver", + 65241: "uproar", + 65242: "uproot", + 65243: "upscale", + 65244: "upside", + 65245: "upstage", + 65246: "upstairs", + 65251: "upstart", + 65252: "upstate", + 65253: "upstream", + 65254: "upstroke", + 65255: "upswing", + 65256: "uptake", + 65261: "uptight", + 65262: "uptown", + 65263: "upturned", + 65264: "upward", + 65265: "upwind", + 65266: "uranium", + 65311: "urban", + 65312: "urchin", + 65313: "urethane", + 65314: "urgency", + 65315: "urgent", + 65316: "urging", + 65321: "urologist", + 65322: "urology", + 65323: "usable", + 65324: "usage", + 65325: "useable", + 65326: "used", + 65331: "uselessly", + 65332: "user", + 65333: "usher", + 65334: "usual", + 65335: "utensil", + 65336: "utility", + 65341: "utilize", + 65342: "utmost", + 65343: "utopia", + 65344: "utter", + 65345: "vacancy", + 65346: "vacant", + 65351: "vacate", + 65352: "vacation", + 65353: "vagabond", + 65354: "vagrancy", + 65355: "vagrantly", + 65356: "vaguely", + 65361: "vagueness", + 65362: "valiant", + 65363: "valid", + 65364: "valium", + 65365: "valley", + 65366: "valuables", + 65411: "value", + 65412: "vanilla", + 65413: "vanish", + 65414: "vanity", + 65415: "vanquish", + 65416: "vantage", + 65421: "vaporizer", + 65422: "variable", + 65423: "variably", + 65424: "varied", + 65425: "variety", + 65426: "various", + 65431: "varmint", + 65432: "varnish", + 65433: "varsity", + 65434: "varying", + 65435: "vascular", + 65436: "vaseline", + 65441: "vastly", + 65442: "vastness", + 65443: "veal", + 65444: "vegan", + 65445: "veggie", + 65446: "vehicular", + 65451: "velcro", + 65452: "velocity", + 65453: "velvet", + 65454: "vendetta", + 65455: "vending", + 65456: "vendor", + 65461: "veneering", + 65462: "vengeful", + 65463: "venomous", + 65464: "ventricle", + 65465: "venture", + 65466: "venue", + 65511: "venus", + 65512: "verbalize", + 65513: "verbally", + 65514: "verbose", + 65515: "verdict", + 65516: "verify", + 65521: "verse", + 65522: "version", + 65523: "versus", + 65524: "vertebrae", + 65525: "vertical", + 65526: "vertigo", + 65531: "very", + 65532: "vessel", + 65533: "vest", + 65534: "veteran", + 65535: "veto", + 65536: "vexingly", + 65541: "viability", + 65542: "viable", + 65543: "vibes", + 65544: "vice", + 65545: "vicinity", + 65546: "victory", + 65551: "video", + 65552: "viewable", + 65553: "viewer", + 65554: "viewing", + 65555: "viewless", + 65556: "viewpoint", + 65561: "vigorous", + 65562: "village", + 65563: "villain", + 65564: "vindicate", + 65565: "vineyard", + 65566: "vintage", + 65611: "violate", + 65612: "violation", + 65613: "violator", + 65614: "violet", + 65615: "violin", + 65616: "viper", + 65621: "viral", + 65622: "virtual", + 65623: "virtuous", + 65624: "virus", + 65625: "visa", + 65626: "viscosity", + 65631: "viscous", + 65632: "viselike", + 65633: "visible", + 65634: "visibly", + 65635: "vision", + 65636: "visiting", + 65641: "visitor", + 65642: "visor", + 65643: "vista", + 65644: "vitality", + 65645: "vitalize", + 65646: "vitally", + 65651: "vitamins", + 65652: "vivacious", + 65653: "vividly", + 65654: "vividness", + 65655: "vixen", + 65656: "vocalist", + 65661: "vocalize", + 65662: "vocally", + 65663: "vocation", + 65664: "voice", + 65665: "voicing", + 65666: "void", + 66111: "volatile", + 66112: "volley", + 66113: "voltage", + 66114: "volumes", + 66115: "voter", + 66116: "voting", + 66121: "voucher", + 66122: "vowed", + 66123: "vowel", + 66124: "voyage", + 66125: "wackiness", + 66126: "wad", + 66131: "wafer", + 66132: "waffle", + 66133: "waged", + 66134: "wager", + 66135: "wages", + 66136: "waggle", + 66141: "wagon", + 66142: "wake", + 66143: "waking", + 66144: "walk", + 66145: "walmart", + 66146: "walnut", + 66151: "walrus", + 66152: "waltz", + 66153: "wand", + 66154: "wannabe", + 66155: "wanted", + 66156: "wanting", + 66161: "wasabi", + 66162: "washable", + 66163: "washbasin", + 66164: "washboard", + 66165: "washbowl", + 66166: "washcloth", + 66211: "washday", + 66212: "washed", + 66213: "washer", + 66214: "washhouse", + 66215: "washing", + 66216: "washout", + 66221: "washroom", + 66222: "washstand", + 66223: "washtub", + 66224: "wasp", + 66225: "wasting", + 66226: "watch", + 66231: "water", + 66232: "waviness", + 66233: "waving", + 66234: "wavy", + 66235: "whacking", + 66236: "whacky", + 66241: "wham", + 66242: "wharf", + 66243: "wheat", + 66244: "whenever", + 66245: "whiff", + 66246: "whimsical", + 66251: "whinny", + 66252: "whiny", + 66253: "whisking", + 66254: "whoever", + 66255: "whole", + 66256: "whomever", + 66261: "whoopee", + 66262: "whooping", + 66263: "whoops", + 66264: "why", + 66265: "wick", + 66266: "widely", + 66311: "widen", + 66312: "widget", + 66313: "widow", + 66314: "width", + 66315: "wieldable", + 66316: "wielder", + 66321: "wife", + 66322: "wifi", + 66323: "wikipedia", + 66324: "wildcard", + 66325: "wildcat", + 66326: "wilder", + 66331: "wildfire", + 66332: "wildfowl", + 66333: "wildland", + 66334: "wildlife", + 66335: "wildly", + 66336: "wildness", + 66341: "willed", + 66342: "willfully", + 66343: "willing", + 66344: "willow", + 66345: "willpower", + 66346: "wilt", + 66351: "wimp", + 66352: "wince", + 66353: "wincing", + 66354: "wind", + 66355: "wing", + 66356: "winking", + 66361: "winner", + 66362: "winnings", + 66363: "winter", + 66364: "wipe", + 66365: "wired", + 66366: "wireless", + 66411: "wiring", + 66412: "wiry", + 66413: "wisdom", + 66414: "wise", + 66415: "wish", + 66416: "wisplike", + 66421: "wispy", + 66422: "wistful", + 66423: "wizard", + 66424: "wobble", + 66425: "wobbling", + 66426: "wobbly", + 66431: "wok", + 66432: "wolf", + 66433: "wolverine", + 66434: "womanhood", + 66435: "womankind", + 66436: "womanless", + 66441: "womanlike", + 66442: "womanly", + 66443: "womb", + 66444: "woof", + 66445: "wooing", + 66446: "wool", + 66451: "woozy", + 66452: "word", + 66453: "work", + 66454: "worried", + 66455: "worrier", + 66456: "worrisome", + 66461: "worry", + 66462: "worsening", + 66463: "worshiper", + 66464: "worst", + 66465: "wound", + 66466: "woven", + 66511: "wow", + 66512: "wrangle", + 66513: "wrath", + 66514: "wreath", + 66515: "wreckage", + 66516: "wrecker", + 66521: "wrecking", + 66522: "wrench", + 66523: "wriggle", + 66524: "wriggly", + 66525: "wrinkle", + 66526: "wrinkly", + 66531: "wrist", + 66532: "writing", + 66533: "written", + 66534: "wrongdoer", + 66535: "wronged", + 66536: "wrongful", + 66541: "wrongly", + 66542: "wrongness", + 66543: "wrought", + 66544: "xbox", + 66545: "xerox", + 66546: "yahoo", + 66551: "yam", + 66552: "yanking", + 66553: "yapping", + 66554: "yard", + 66555: "yarn", + 66556: "yeah", + 66561: "yearbook", + 66562: "yearling", + 66563: "yearly", + 66564: "yearning", + 66565: "yeast", + 66566: "yelling", + 66611: "yelp", + 66612: "yen", + 66613: "yesterday", + 66614: "yiddish", + 66615: "yield", + 66616: "yin", + 66621: "yippee", + 66622: "yo-yo", + 66623: "yodel", + 66624: "yoga", + 66625: "yogurt", + 66626: "yonder", + 66631: "yoyo", + 66632: "yummy", + 66633: "zap", + 66634: "zealous", + 66635: "zebra", + 66636: "zen", + 66641: "zeppelin", + 66642: "zero", + 66643: "zestfully", + 66644: "zesty", + 66645: "zigzagged", + 66646: "zipfile", + 66651: "zipping", + 66652: "zippy", + 66653: "zips", + 66654: "zit", + 66655: "zodiac", + 66656: "zombie", + 66661: "zone", + 66662: "zoning", + 66663: "zookeeper", + 66664: "zoologist", + 66665: "zoology", + 66666: "zoom" + }), yt; +} +var xn = En(); +const sr = /* @__PURE__ */ Kr(xn), tt = Object.keys(sr).sort().map((e) => sr[e]); +if (tt.length !== 7776) + throw new Error(`EFF long wordlist must contain 7776 entries, found ${tt.length}.`); +function y1() { + return tt; +} +const b1 = Math.log2(tt.length); +function Dn() { + const e = globalThis.crypto; + if (!e || typeof e.getRandomValues != "function") + throw new Error("No cryptographically secure random source available in this environment."); + return e; +} +function qe(e) { + const t = new Uint8Array(e); + return Dn().getRandomValues(t), t; +} +function w1() { + return qe(16); +} +function C1() { + return qe(12); +} +function k1() { + return qe(16); +} +function v1(e) { + if (!Number.isInteger(e) || e <= 0 || e > 4294967296) + throw new Error("randomUniformInt: maxExclusive must be an integer in (0, 2^32]."); + const t = e <= 256 ? 1 : e <= 65536 ? 2 : 4, n = 256 ** t, r = n - n % e; + for (; ; ) { + const i = qe(t); + let a = 0; + for (const o of i) + a = a * 256 + o; + if (a < r) + return a % e; + } +} +const ve = typeof globalThis == "object" && "crypto" in globalThis ? globalThis.crypto : void 0; +/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */ +function At(e) { + return e instanceof Uint8Array || ArrayBuffer.isView(e) && e.constructor.name === "Uint8Array"; +} +function rt(e) { + if (!Number.isSafeInteger(e) || e < 0) + throw new Error("positive integer expected, got " + e); +} +function pe(e, ...t) { + if (!At(e)) + throw new Error("Uint8Array expected"); + if (t.length > 0 && !t.includes(e.length)) + throw new Error("Uint8Array expected of length " + t + ", got length=" + e.length); +} +function ht(e) { + if (typeof e != "function" || typeof e.create != "function") + throw new Error("Hash should be wrapped by utils.createHasher"); + rt(e.outputLen), rt(e.blockLen); +} +function nt(e, t = !0) { + if (e.destroyed) + throw new Error("Hash instance has been destroyed"); + if (t && e.finished) + throw new Error("Hash#digest() has already been called"); +} +function Sn(e, t) { + pe(e); + const n = t.outputLen; + if (e.length < n) + throw new Error("digestInto() expects output buffer of length at least " + n); +} +function Ce(...e) { + for (let t = 0; t < e.length; t++) + e[t].fill(0); +} +function bt(e) { + return new DataView(e.buffer, e.byteOffset, e.byteLength); +} +function re(e, t) { + return e << 32 - t | e >>> t; +} +const Or = /* @ts-ignore */ typeof Uint8Array.from([]).toHex == "function" && typeof Uint8Array.fromHex == "function", Fn = /* @__PURE__ */ Array.from({ length: 256 }, (e, t) => t.toString(16).padStart(2, "0")); +function Ee(e) { + if (pe(e), Or) + return e.toHex(); + let t = ""; + for (let n = 0; n < e.length; n++) + t += Fn[e[n]]; + return t; +} +const ie = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 }; +function lr(e) { + if (e >= ie._0 && e <= ie._9) + return e - ie._0; + if (e >= ie.A && e <= ie.F) + return e - (ie.A - 10); + if (e >= ie.a && e <= ie.f) + return e - (ie.a - 10); +} +function it(e) { + if (typeof e != "string") + throw new Error("hex string expected, got " + typeof e); + if (Or) + return Uint8Array.fromHex(e); + const t = e.length, n = t / 2; + if (t % 2) + throw new Error("hex string expected, got unpadded hex of length " + t); + const r = new Uint8Array(n); + for (let i = 0, a = 0; i < n; i++, a += 2) { + const o = lr(e.charCodeAt(a)), s = lr(e.charCodeAt(a + 1)); + if (o === void 0 || s === void 0) { + const l = e[a] + e[a + 1]; + throw new Error('hex string expected, got non-hex character "' + l + '" at index ' + a); + } + r[i] = o * 16 + s; + } + return r; +} +const Un = async () => { +}; +async function B1(e, t, n) { + let r = Date.now(); + for (let i = 0; i < e; i++) { + n(i); + const a = Date.now() - r; + a >= 0 && a < t || (await Un(), r += a); + } +} +function qt(e) { + if (typeof e != "string") + throw new Error("string expected"); + return new Uint8Array(new TextEncoder().encode(e)); +} +function Me(e) { + return typeof e == "string" && (e = qt(e)), pe(e), e; +} +function Q1(e) { + return typeof e == "string" && (e = qt(e)), pe(e), e; +} +function ne(...e) { + let t = 0; + for (let r = 0; r < e.length; r++) { + const i = e[r]; + pe(i), t += i.length; + } + const n = new Uint8Array(t); + for (let r = 0, i = 0; r < e.length; r++) { + const a = e[r]; + n.set(a, i), i += a.length; + } + return n; +} +function E1(e, t) { + if (t !== void 0 && {}.toString.call(t) !== "[object Object]") + throw new Error("options should be object or undefined"); + return Object.assign(e, t); +} +class Vr { +} +function zr(e) { + const t = (r) => e().update(Me(r)).digest(), n = e(); + return t.outputLen = n.outputLen, t.blockLen = n.blockLen, t.create = () => e(), t; +} +function ft(e = 32) { + if (ve && typeof ve.getRandomValues == "function") + return ve.getRandomValues(new Uint8Array(e)); + if (ve && typeof ve.randomBytes == "function") + return Uint8Array.from(ve.randomBytes(e)); + throw new Error("crypto.getRandomValues must be defined"); +} +function Gn(e, t, n, r) { + if (typeof e.setBigUint64 == "function") + return e.setBigUint64(t, n, r); + const i = BigInt(32), a = BigInt(4294967295), o = Number(n >> i & a), s = Number(n & a), l = r ? 4 : 0, u = r ? 0 : 4; + e.setUint32(t + l, o, r), e.setUint32(t + u, s, r); +} +function Hn(e, t, n) { + return e & t ^ ~e & n; +} +function Nn(e, t, n) { + return e & t ^ e & n ^ t & n; +} +class Jr extends Vr { + constructor(t, n, r, i) { + super(), this.finished = !1, this.length = 0, this.pos = 0, this.destroyed = !1, this.blockLen = t, this.outputLen = n, this.padOffset = r, this.isLE = i, this.buffer = new Uint8Array(t), this.view = bt(this.buffer); + } + update(t) { + nt(this), t = Me(t), pe(t); + const { view: n, buffer: r, blockLen: i } = this, a = t.length; + for (let o = 0; o < a; ) { + const s = Math.min(i - this.pos, a - o); + if (s === i) { + const l = bt(t); + for (; i <= a - o; o += i) + this.process(l, o); + continue; + } + r.set(t.subarray(o, o + s), this.pos), this.pos += s, o += s, this.pos === i && (this.process(n, 0), this.pos = 0); + } + return this.length += t.length, this.roundClean(), this; + } + digestInto(t) { + nt(this), Sn(t, this), this.finished = !0; + const { buffer: n, view: r, blockLen: i, isLE: a } = this; + let { pos: o } = this; + n[o++] = 128, Ce(this.buffer.subarray(o)), this.padOffset > i - o && (this.process(r, 0), o = 0); + for (let g = o; g < i; g++) + n[g] = 0; + Gn(r, i - 8, BigInt(this.length * 8), a), this.process(r, 0); + const s = bt(t), l = this.outputLen; + if (l % 4) + throw new Error("_sha2: outputLen should be aligned to 32bit"); + const u = l / 4, h = this.get(); + if (u > h.length) + throw new Error("_sha2: outputLen bigger than state"); + for (let g = 0; g < u; g++) + s.setUint32(4 * g, h[g], a); + } + digest() { + const { buffer: t, outputLen: n } = this; + this.digestInto(t); + const r = t.slice(0, n); + return this.destroy(), r; + } + _cloneInto(t) { + t || (t = new this.constructor()), t.set(...this.get()); + const { blockLen: n, buffer: r, length: i, finished: a, destroyed: o, pos: s } = this; + return t.destroyed = o, t.finished = a, t.length = i, t.pos = s, i % n && t.buffer.set(r), t; + } + clone() { + return this._cloneInto(); + } +} +const de = /* @__PURE__ */ Uint32Array.from([ + 1779033703, + 3144134277, + 1013904242, + 2773480762, + 1359893119, + 2600822924, + 528734635, + 1541459225 +]), _ = /* @__PURE__ */ Uint32Array.from([ + 1779033703, + 4089235720, + 3144134277, + 2227873595, + 1013904242, + 4271175723, + 2773480762, + 1595750129, + 1359893119, + 2917565137, + 2600822924, + 725511199, + 528734635, + 4215389547, + 1541459225, + 327033209 +]), Pe = /* @__PURE__ */ BigInt(2 ** 32 - 1), cr = /* @__PURE__ */ BigInt(32); +function Kn(e, t = !1) { + return t ? { h: Number(e & Pe), l: Number(e >> cr & Pe) } : { h: Number(e >> cr & Pe) | 0, l: Number(e & Pe) | 0 }; +} +function On(e, t = !1) { + const n = e.length; + let r = new Uint32Array(n), i = new Uint32Array(n); + for (let a = 0; a < n; a++) { + const { h: o, l: s } = Kn(e[a], t); + [r[a], i[a]] = [o, s]; + } + return [r, i]; +} +const ur = (e, t, n) => e >>> n, dr = (e, t, n) => e << 32 - n | t >>> n, Be = (e, t, n) => e >>> n | t << 32 - n, Qe = (e, t, n) => e << 32 - n | t >>> n, Le = (e, t, n) => e << 64 - n | t >>> n - 32, Te = (e, t, n) => e >>> n - 32 | t << 64 - n; +function ae(e, t, n, r) { + const i = (t >>> 0) + (r >>> 0); + return { h: e + n + (i / 2 ** 32 | 0) | 0, l: i | 0 }; +} +const Vn = (e, t, n) => (e >>> 0) + (t >>> 0) + (n >>> 0), zn = (e, t, n, r) => t + n + r + (e / 2 ** 32 | 0) | 0, Jn = (e, t, n, r) => (e >>> 0) + (t >>> 0) + (n >>> 0) + (r >>> 0), Yn = (e, t, n, r, i) => t + n + r + i + (e / 2 ** 32 | 0) | 0, Mn = (e, t, n, r, i) => (e >>> 0) + (t >>> 0) + (n >>> 0) + (r >>> 0) + (i >>> 0), qn = (e, t, n, r, i, a) => t + n + r + i + a + (e / 2 ** 32 | 0) | 0, jn = /* @__PURE__ */ Uint32Array.from([ + 1116352408, + 1899447441, + 3049323471, + 3921009573, + 961987163, + 1508970993, + 2453635748, + 2870763221, + 3624381080, + 310598401, + 607225278, + 1426881987, + 1925078388, + 2162078206, + 2614888103, + 3248222580, + 3835390401, + 4022224774, + 264347078, + 604807628, + 770255983, + 1249150122, + 1555081692, + 1996064986, + 2554220882, + 2821834349, + 2952996808, + 3210313671, + 3336571891, + 3584528711, + 113926993, + 338241895, + 666307205, + 773529912, + 1294757372, + 1396182291, + 1695183700, + 1986661051, + 2177026350, + 2456956037, + 2730485921, + 2820302411, + 3259730800, + 3345764771, + 3516065817, + 3600352804, + 4094571909, + 275423344, + 430227734, + 506948616, + 659060556, + 883997877, + 958139571, + 1322822218, + 1537002063, + 1747873779, + 1955562222, + 2024104815, + 2227730452, + 2361852424, + 2428436474, + 2756734187, + 3204031479, + 3329325298 +]), ge = /* @__PURE__ */ new Uint32Array(64); +class Rn extends Jr { + constructor(t = 32) { + super(64, t, 8, !1), this.A = de[0] | 0, this.B = de[1] | 0, this.C = de[2] | 0, this.D = de[3] | 0, this.E = de[4] | 0, this.F = de[5] | 0, this.G = de[6] | 0, this.H = de[7] | 0; + } + get() { + const { A: t, B: n, C: r, D: i, E: a, F: o, G: s, H: l } = this; + return [t, n, r, i, a, o, s, l]; + } + // prettier-ignore + set(t, n, r, i, a, o, s, l) { + this.A = t | 0, this.B = n | 0, this.C = r | 0, this.D = i | 0, this.E = a | 0, this.F = o | 0, this.G = s | 0, this.H = l | 0; + } + process(t, n) { + for (let g = 0; g < 16; g++, n += 4) + ge[g] = t.getUint32(n, !1); + for (let g = 16; g < 64; g++) { + const c = ge[g - 15], d = ge[g - 2], I = re(c, 7) ^ re(c, 18) ^ c >>> 3, m = re(d, 17) ^ re(d, 19) ^ d >>> 10; + ge[g] = m + ge[g - 7] + I + ge[g - 16] | 0; + } + let { A: r, B: i, C: a, D: o, E: s, F: l, G: u, H: h } = this; + for (let g = 0; g < 64; g++) { + const c = re(s, 6) ^ re(s, 11) ^ re(s, 25), d = h + c + Hn(s, l, u) + jn[g] + ge[g] | 0, m = (re(r, 2) ^ re(r, 13) ^ re(r, 22)) + Nn(r, i, a) | 0; + h = u, u = l, l = s, s = o + d | 0, o = a, a = i, i = r, r = d + m | 0; + } + r = r + this.A | 0, i = i + this.B | 0, a = a + this.C | 0, o = o + this.D | 0, s = s + this.E | 0, l = l + this.F | 0, u = u + this.G | 0, h = h + this.H | 0, this.set(r, i, a, o, s, l, u, h); + } + roundClean() { + Ce(ge); + } + destroy() { + this.set(0, 0, 0, 0, 0, 0, 0, 0), Ce(this.buffer); + } +} +const Yr = On([ + "0x428a2f98d728ae22", + "0x7137449123ef65cd", + "0xb5c0fbcfec4d3b2f", + "0xe9b5dba58189dbbc", + "0x3956c25bf348b538", + "0x59f111f1b605d019", + "0x923f82a4af194f9b", + "0xab1c5ed5da6d8118", + "0xd807aa98a3030242", + "0x12835b0145706fbe", + "0x243185be4ee4b28c", + "0x550c7dc3d5ffb4e2", + "0x72be5d74f27b896f", + "0x80deb1fe3b1696b1", + "0x9bdc06a725c71235", + "0xc19bf174cf692694", + "0xe49b69c19ef14ad2", + "0xefbe4786384f25e3", + "0x0fc19dc68b8cd5b5", + "0x240ca1cc77ac9c65", + "0x2de92c6f592b0275", + "0x4a7484aa6ea6e483", + "0x5cb0a9dcbd41fbd4", + "0x76f988da831153b5", + "0x983e5152ee66dfab", + "0xa831c66d2db43210", + "0xb00327c898fb213f", + "0xbf597fc7beef0ee4", + "0xc6e00bf33da88fc2", + "0xd5a79147930aa725", + "0x06ca6351e003826f", + "0x142929670a0e6e70", + "0x27b70a8546d22ffc", + "0x2e1b21385c26c926", + "0x4d2c6dfc5ac42aed", + "0x53380d139d95b3df", + "0x650a73548baf63de", + "0x766a0abb3c77b2a8", + "0x81c2c92e47edaee6", + "0x92722c851482353b", + "0xa2bfe8a14cf10364", + "0xa81a664bbc423001", + "0xc24b8b70d0f89791", + "0xc76c51a30654be30", + "0xd192e819d6ef5218", + "0xd69906245565a910", + "0xf40e35855771202a", + "0x106aa07032bbd1b8", + "0x19a4c116b8d2d0c8", + "0x1e376c085141ab53", + "0x2748774cdf8eeb99", + "0x34b0bcb5e19b48a8", + "0x391c0cb3c5c95a63", + "0x4ed8aa4ae3418acb", + "0x5b9cca4f7763e373", + "0x682e6ff3d6b2b8a3", + "0x748f82ee5defb2fc", + "0x78a5636f43172f60", + "0x84c87814a1f0ab72", + "0x8cc702081a6439ec", + "0x90befffa23631e28", + "0xa4506cebde82bde9", + "0xbef9a3f7b2c67915", + "0xc67178f2e372532b", + "0xca273eceea26619c", + "0xd186b8c721c0c207", + "0xeada7dd6cde0eb1e", + "0xf57d4f7fee6ed178", + "0x06f067aa72176fba", + "0x0a637dc5a2c898a6", + "0x113f9804bef90dae", + "0x1b710b35131c471b", + "0x28db77f523047d84", + "0x32caab7b40c72493", + "0x3c9ebe0a15c9bebc", + "0x431d67c49c100d4c", + "0x4cc5d4becb3e42b6", + "0x597f299cfc657e2a", + "0x5fcb6fab3ad6faec", + "0x6c44198c4a475817" +].map((e) => BigInt(e))), _n = Yr[0], Pn = Yr[1], Ae = /* @__PURE__ */ new Uint32Array(80), he = /* @__PURE__ */ new Uint32Array(80); +class Ln extends Jr { + constructor(t = 64) { + super(128, t, 16, !1), this.Ah = _[0] | 0, this.Al = _[1] | 0, this.Bh = _[2] | 0, this.Bl = _[3] | 0, this.Ch = _[4] | 0, this.Cl = _[5] | 0, this.Dh = _[6] | 0, this.Dl = _[7] | 0, this.Eh = _[8] | 0, this.El = _[9] | 0, this.Fh = _[10] | 0, this.Fl = _[11] | 0, this.Gh = _[12] | 0, this.Gl = _[13] | 0, this.Hh = _[14] | 0, this.Hl = _[15] | 0; + } + // prettier-ignore + get() { + const { Ah: t, Al: n, Bh: r, Bl: i, Ch: a, Cl: o, Dh: s, Dl: l, Eh: u, El: h, Fh: g, Fl: c, Gh: d, Gl: I, Hh: m, Hl: B } = this; + return [t, n, r, i, a, o, s, l, u, h, g, c, d, I, m, B]; + } + // prettier-ignore + set(t, n, r, i, a, o, s, l, u, h, g, c, d, I, m, B) { + this.Ah = t | 0, this.Al = n | 0, this.Bh = r | 0, this.Bl = i | 0, this.Ch = a | 0, this.Cl = o | 0, this.Dh = s | 0, this.Dl = l | 0, this.Eh = u | 0, this.El = h | 0, this.Fh = g | 0, this.Fl = c | 0, this.Gh = d | 0, this.Gl = I | 0, this.Hh = m | 0, this.Hl = B | 0; + } + process(t, n) { + for (let b = 0; b < 16; b++, n += 4) + Ae[b] = t.getUint32(n), he[b] = t.getUint32(n += 4); + for (let b = 16; b < 80; b++) { + const N = Ae[b - 15] | 0, K = he[b - 15] | 0, C = Be(N, K, 1) ^ Be(N, K, 8) ^ ur(N, K, 7), D = Qe(N, K, 1) ^ Qe(N, K, 8) ^ dr(N, K, 7), G = Ae[b - 2] | 0, v = he[b - 2] | 0, P = Be(G, v, 19) ^ Le(G, v, 61) ^ ur(G, v, 6), Z = Qe(G, v, 19) ^ Te(G, v, 61) ^ dr(G, v, 6), U = Jn(D, Z, he[b - 7], he[b - 16]), p = Yn(U, C, P, Ae[b - 7], Ae[b - 16]); + Ae[b] = p | 0, he[b] = U | 0; + } + let { Ah: r, Al: i, Bh: a, Bl: o, Ch: s, Cl: l, Dh: u, Dl: h, Eh: g, El: c, Fh: d, Fl: I, Gh: m, Gl: B, Hh: S, Hl: j } = this; + for (let b = 0; b < 80; b++) { + const N = Be(g, c, 14) ^ Be(g, c, 18) ^ Le(g, c, 41), K = Qe(g, c, 14) ^ Qe(g, c, 18) ^ Te(g, c, 41), C = g & d ^ ~g & m, D = c & I ^ ~c & B, G = Mn(j, K, D, Pn[b], he[b]), v = qn(G, S, N, C, _n[b], Ae[b]), P = G | 0, Z = Be(r, i, 28) ^ Le(r, i, 34) ^ Le(r, i, 39), U = Qe(r, i, 28) ^ Te(r, i, 34) ^ Te(r, i, 39), p = r & a ^ r & s ^ a & s, f = i & o ^ i & l ^ o & l; + S = m | 0, j = B | 0, m = d | 0, B = I | 0, d = g | 0, I = c | 0, { h: g, l: c } = ae(u | 0, h | 0, v | 0, P | 0), u = s | 0, h = l | 0, s = a | 0, l = o | 0, a = r | 0, o = i | 0; + const A = Vn(P, U, f); + r = zn(A, v, Z, p), i = A | 0; + } + ({ h: r, l: i } = ae(this.Ah | 0, this.Al | 0, r | 0, i | 0)), { h: a, l: o } = ae(this.Bh | 0, this.Bl | 0, a | 0, o | 0), { h: s, l } = ae(this.Ch | 0, this.Cl | 0, s | 0, l | 0), { h: u, l: h } = ae(this.Dh | 0, this.Dl | 0, u | 0, h | 0), { h: g, l: c } = ae(this.Eh | 0, this.El | 0, g | 0, c | 0), { h: d, l: I } = ae(this.Fh | 0, this.Fl | 0, d | 0, I | 0), { h: m, l: B } = ae(this.Gh | 0, this.Gl | 0, m | 0, B | 0), { h: S, l: j } = ae(this.Hh | 0, this.Hl | 0, S | 0, j | 0), this.set(r, i, a, o, s, l, u, h, g, c, d, I, m, B, S, j); + } + roundClean() { + Ce(Ae, he); + } + destroy() { + Ce(this.buffer), this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + } +} +const fe = /* @__PURE__ */ zr(() => new Rn()), x1 = /* @__PURE__ */ zr(() => new Ln()); +class Mr extends Vr { + constructor(t, n) { + super(), this.finished = !1, this.destroyed = !1, ht(t); + const r = Me(n); + if (this.iHash = t.create(), typeof this.iHash.update != "function") + throw new Error("Expected instance of class which extends utils.Hash"); + this.blockLen = this.iHash.blockLen, this.outputLen = this.iHash.outputLen; + const i = this.blockLen, a = new Uint8Array(i); + a.set(r.length > i ? t.create().update(r).digest() : r); + for (let o = 0; o < a.length; o++) + a[o] ^= 54; + this.iHash.update(a), this.oHash = t.create(); + for (let o = 0; o < a.length; o++) + a[o] ^= 106; + this.oHash.update(a), Ce(a); + } + update(t) { + return nt(this), this.iHash.update(t), this; + } + digestInto(t) { + nt(this), pe(t, this.outputLen), this.finished = !0, this.iHash.digestInto(t), this.oHash.update(t), this.oHash.digestInto(t), this.destroy(); + } + digest() { + const t = new Uint8Array(this.oHash.outputLen); + return this.digestInto(t), t; + } + _cloneInto(t) { + t || (t = Object.create(Object.getPrototypeOf(this), {})); + const { oHash: n, iHash: r, finished: i, destroyed: a, blockLen: o, outputLen: s } = this; + return t = t, t.finished = i, t.destroyed = a, t.blockLen = o, t.outputLen = s, t.oHash = n._cloneInto(t.oHash), t.iHash = r._cloneInto(t.iHash), t; + } + clone() { + return this._cloneInto(); + } + destroy() { + this.destroyed = !0, this.oHash.destroy(), this.iHash.destroy(); + } +} +const pt = (e, t, n) => new Mr(e, t).update(n).digest(); +pt.create = (e, t) => new Mr(e, t); +function Tn(e, t, n) { + return ht(e), n === void 0 && (n = new Uint8Array(e.outputLen)), pt(e, Me(n), Me(t)); +} +const wt = /* @__PURE__ */ Uint8Array.from([0]), gr = /* @__PURE__ */ Uint8Array.of(); +function Xn(e, t, n, r = 32) { + ht(e), rt(r); + const i = e.outputLen; + if (r > 255 * i) + throw new Error("Length should be <= 255*HashLen"); + const a = Math.ceil(r / i); + n === void 0 && (n = gr); + const o = new Uint8Array(a * i), s = pt.create(e, t), l = s._cloneInto(), u = new Uint8Array(s.outputLen); + for (let h = 0; h < a; h++) + wt[0] = h + 1, l.update(h === 0 ? gr : u).update(n).update(wt).digestInto(u), o.set(u, i * h), s._cloneInto(l); + return s.destroy(), l.destroy(), Ce(u, wt), o.slice(0, r); +} +function jt(e) { + let t = ""; + for (const n of e) + t += n.toString(16).padStart(2, "0"); + return t; +} +function Je(e) { + if (e.length % 2 !== 0) + throw new Error("Invalid hex string length."); + const t = new Uint8Array(e.length / 2); + for (let n = 0; n < t.length; n++) + t[n] = parseInt(e.slice(n * 2, n * 2 + 2), 16); + return t; +} +const ee = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; +function D1(e) { + let t = "", n = 0; + for (; n + 2 < e.length; n += 3) { + const i = e[n], a = e[n + 1], o = e[n + 2]; + t += ee[i >> 2], t += ee[(i & 3) << 4 | a >> 4], t += ee[(a & 15) << 2 | o >> 6], t += ee[o & 63]; + } + const r = e.length - n; + if (r === 1) { + const i = e[n]; + t += ee[i >> 2], t += ee[(i & 3) << 4]; + } else if (r === 2) { + const i = e[n], a = e[n + 1]; + t += ee[i >> 2], t += ee[(i & 3) << 4 | a >> 4], t += ee[(a & 15) << 2]; + } + return t; +} +const Zn = (() => { + const e = {}; + for (let t = 0; t < ee.length; t++) + e[ee[t]] = t; + return e; +})(); +function S1(e) { + const t = e.replace(/=+$/u, ""), n = []; + let r = 0, i = 0; + for (const a of t) { + const o = Zn[a]; + if (o === void 0) + throw new Error("Invalid base64url character."); + r = r << 6 | o, i += 6, i >= 8 && (i -= 8, n.push(r >> i & 255)); + } + return new Uint8Array(n); +} +function De(e) { + return new TextEncoder().encode(e); +} +function F1(e) { + return new TextDecoder("utf-8", { fatal: !0 }).decode(e); +} +function qr(...e) { + const t = e.reduce((i, a) => i + a.length, 0), n = new Uint8Array(t); + let r = 0; + for (const i of e) + n.set(i, r), r += i.length; + return n; +} +function U1(e, t) { + if (e.length !== t.length) + return !1; + let n = 0; + for (let r = 0; r < e.length; r++) + n |= e[r] ^ t[r]; + return n === 0; +} +function jr(e, t) { + return Tn(fe, t, e); +} +function Rt(e, t, n) { + const r = typeof t == "string" ? De(t) : t; + return Xn(fe, e, r, n); +} +function Rr(e) { + return fe(De(e)); +} +/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ +const _t = /* @__PURE__ */ BigInt(0), Gt = /* @__PURE__ */ BigInt(1); +function at(e, t = "") { + if (typeof e != "boolean") { + const n = t && `"${t}"`; + throw new Error(n + "expected boolean, got type=" + typeof e); + } + return e; +} +function Ie(e, t, n = "") { + const r = At(e), i = e?.length, a = t !== void 0; + if (!r || a && i !== t) { + const o = n && `"${n}" `, s = a ? ` of length ${t}` : "", l = r ? `length=${i}` : `type=${typeof e}`; + throw new Error(o + "expected Uint8Array" + s + ", got " + l); + } + return e; +} +function Xe(e) { + const t = e.toString(16); + return t.length & 1 ? "0" + t : t; +} +function _r(e) { + if (typeof e != "string") + throw new Error("hex string expected, got " + typeof e); + return e === "" ? _t : BigInt("0x" + e); +} +function Ge(e) { + return _r(Ee(e)); +} +function Pr(e) { + return pe(e), _r(Ee(Uint8Array.from(e).reverse())); +} +function It(e, t) { + return it(e.toString(16).padStart(t * 2, "0")); +} +function Lr(e, t) { + return It(e, t).reverse(); +} +function q(e, t, n) { + let r; + if (typeof t == "string") + try { + r = it(t); + } catch (a) { + throw new Error(e + " must be hex string or Uint8Array, cause: " + a); + } + else if (At(t)) + r = Uint8Array.from(t); + else + throw new Error(e + " must be hex string or Uint8Array"); + const i = r.length; + if (typeof n == "number" && i !== n) + throw new Error(e + " of length " + n + " expected, got " + i); + return r; +} +const Ct = (e) => typeof e == "bigint" && _t <= e; +function Ht(e, t, n) { + return Ct(e) && Ct(t) && Ct(n) && t <= e && e < n; +} +function $n(e, t, n, r) { + if (!Ht(t, n, r)) + throw new Error("expected valid " + e + ": " + n + " <= n < " + r + ", got " + t); +} +function Tr(e) { + let t; + for (t = 0; e > _t; e >>= Gt, t += 1) + ; + return t; +} +const je = (e) => (Gt << BigInt(e)) - Gt; +function Wn(e, t, n) { + if (typeof e != "number" || e < 2) + throw new Error("hashLen must be a number"); + if (typeof t != "number" || t < 2) + throw new Error("qByteLen must be a number"); + if (typeof n != "function") + throw new Error("hmacFn must be a function"); + const r = (d) => new Uint8Array(d), i = (d) => Uint8Array.of(d); + let a = r(e), o = r(e), s = 0; + const l = () => { + a.fill(1), o.fill(0), s = 0; + }, u = (...d) => n(o, a, ...d), h = (d = r(0)) => { + o = u(i(0), d), a = u(), d.length !== 0 && (o = u(i(1), d), a = u()); + }, g = () => { + if (s++ >= 1e3) + throw new Error("drbg: tried 1000 values"); + let d = 0; + const I = []; + for (; d < t; ) { + a = u(); + const m = a.slice(); + I.push(m), d += a.length; + } + return ne(...I); + }; + return (d, I) => { + l(), h(d); + let m; + for (; !(m = I(g())); ) + h(); + return l(), m; + }; +} +function Pt(e, t, n = {}) { + if (!e || typeof e != "object") + throw new Error("expected valid options object"); + function r(i, a, o) { + const s = e[i]; + if (o && s === void 0) + return; + const l = typeof s; + if (l !== a || s === null) + throw new Error(`param "${i}" is invalid: expected ${a}, got ${l}`); + } + Object.entries(t).forEach(([i, a]) => r(i, a, !1)), Object.entries(n).forEach(([i, a]) => r(i, a, !0)); +} +function Ar(e) { + const t = /* @__PURE__ */ new WeakMap(); + return (n, ...r) => { + const i = t.get(n); + if (i !== void 0) + return i; + const a = e(n, ...r); + return t.set(n, a), a; + }; +} +/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ +const X = BigInt(0), L = BigInt(1), me = /* @__PURE__ */ BigInt(2), Xr = /* @__PURE__ */ BigInt(3), Zr = /* @__PURE__ */ BigInt(4), $r = /* @__PURE__ */ BigInt(5), ei = /* @__PURE__ */ BigInt(7), Wr = /* @__PURE__ */ BigInt(8), ti = /* @__PURE__ */ BigInt(9), en = /* @__PURE__ */ BigInt(16); +function W(e, t) { + const n = e % t; + return n >= X ? n : t + n; +} +function $(e, t, n) { + let r = e; + for (; t-- > X; ) + r *= r, r %= n; + return r; +} +function hr(e, t) { + if (e === X) + throw new Error("invert: expected non-zero number"); + if (t <= X) + throw new Error("invert: expected positive modulus, got " + t); + let n = W(e, t), r = t, i = X, a = L; + for (; n !== X; ) { + const s = r / n, l = r % n, u = i - a * s; + r = n, n = l, i = a, a = u; + } + if (r !== L) + throw new Error("invert: does not exist"); + return W(i, t); +} +function Lt(e, t, n) { + if (!e.eql(e.sqr(t), n)) + throw new Error("Cannot find square root"); +} +function tn(e, t) { + const n = (e.ORDER + L) / Zr, r = e.pow(t, n); + return Lt(e, r, t), r; +} +function ri(e, t) { + const n = (e.ORDER - $r) / Wr, r = e.mul(t, me), i = e.pow(r, n), a = e.mul(t, i), o = e.mul(e.mul(a, me), i), s = e.mul(a, e.sub(o, e.ONE)); + return Lt(e, s, t), s; +} +function ni(e) { + const t = Re(e), n = rn(e), r = n(t, t.neg(t.ONE)), i = n(t, r), a = n(t, t.neg(r)), o = (e + ei) / en; + return (s, l) => { + let u = s.pow(l, o), h = s.mul(u, r); + const g = s.mul(u, i), c = s.mul(u, a), d = s.eql(s.sqr(h), l), I = s.eql(s.sqr(g), l); + u = s.cmov(u, h, d), h = s.cmov(c, g, I); + const m = s.eql(s.sqr(h), l), B = s.cmov(u, h, m); + return Lt(s, B, l), B; + }; +} +function rn(e) { + if (e < Xr) + throw new Error("sqrt is not defined for small field"); + let t = e - L, n = 0; + for (; t % me === X; ) + t /= me, n++; + let r = me; + const i = Re(e); + for (; fr(i, r) === 1; ) + if (r++ > 1e3) + throw new Error("Cannot find square root: probably non-prime P"); + if (n === 1) + return tn; + let a = i.pow(r, t); + const o = (t + L) / me; + return function(l, u) { + if (l.is0(u)) + return u; + if (fr(l, u) !== 1) + throw new Error("Cannot find square root"); + let h = n, g = l.mul(l.ONE, a), c = l.pow(u, t), d = l.pow(u, o); + for (; !l.eql(c, l.ONE); ) { + if (l.is0(c)) + return l.ZERO; + let I = 1, m = l.sqr(c); + for (; !l.eql(m, l.ONE); ) + if (I++, m = l.sqr(m), I === h) + throw new Error("Cannot find square root"); + const B = L << BigInt(h - I - 1), S = l.pow(g, B); + h = I, g = l.sqr(S), c = l.mul(c, g), d = l.mul(d, S); + } + return d; + }; +} +function ii(e) { + return e % Zr === Xr ? tn : e % Wr === $r ? ri : e % en === ti ? ni(e) : rn(e); +} +const ai = [ + "create", + "isValid", + "is0", + "neg", + "inv", + "sqrt", + "sqr", + "eql", + "add", + "sub", + "mul", + "pow", + "div", + "addN", + "subN", + "mulN", + "sqrN" +]; +function oi(e) { + const t = { + ORDER: "bigint", + MASK: "bigint", + BYTES: "number", + BITS: "number" + }, n = ai.reduce((r, i) => (r[i] = "function", r), t); + return Pt(e, n), e; +} +function si(e, t, n) { + if (n < X) + throw new Error("invalid exponent, negatives unsupported"); + if (n === X) + return e.ONE; + if (n === L) + return t; + let r = e.ONE, i = t; + for (; n > X; ) + n & L && (r = e.mul(r, i)), i = e.sqr(i), n >>= L; + return r; +} +function nn(e, t, n = !1) { + const r = new Array(t.length).fill(n ? e.ZERO : void 0), i = t.reduce((o, s, l) => e.is0(s) ? o : (r[l] = o, e.mul(o, s)), e.ONE), a = e.inv(i); + return t.reduceRight((o, s, l) => e.is0(s) ? o : (r[l] = e.mul(o, r[l]), e.mul(o, s)), a), r; +} +function fr(e, t) { + const n = (e.ORDER - L) / me, r = e.pow(t, n), i = e.eql(r, e.ONE), a = e.eql(r, e.ZERO), o = e.eql(r, e.neg(e.ONE)); + if (!i && !a && !o) + throw new Error("invalid Legendre symbol result"); + return i ? 1 : a ? 0 : -1; +} +function an(e, t) { + t !== void 0 && rt(t); + const n = t !== void 0 ? t : e.toString(2).length, r = Math.ceil(n / 8); + return { nBitLength: n, nByteLength: r }; +} +function Re(e, t, n = !1, r = {}) { + if (e <= X) + throw new Error("invalid field: expected ORDER > 0, got " + e); + let i, a, o = !1, s; + if (typeof t == "object" && t != null) { + if (r.sqrt || n) + throw new Error("cannot specify opts in two arguments"); + const c = t; + c.BITS && (i = c.BITS), c.sqrt && (a = c.sqrt), typeof c.isLE == "boolean" && (n = c.isLE), typeof c.modFromBytes == "boolean" && (o = c.modFromBytes), s = c.allowedLengths; + } else + typeof t == "number" && (i = t), r.sqrt && (a = r.sqrt); + const { nBitLength: l, nByteLength: u } = an(e, i); + if (u > 2048) + throw new Error("invalid field: expected ORDER of <= 2048 bytes"); + let h; + const g = Object.freeze({ + ORDER: e, + isLE: n, + BITS: l, + BYTES: u, + MASK: je(l), + ZERO: X, + ONE: L, + allowedLengths: s, + create: (c) => W(c, e), + isValid: (c) => { + if (typeof c != "bigint") + throw new Error("invalid field element: expected bigint, got " + typeof c); + return X <= c && c < e; + }, + is0: (c) => c === X, + // is valid and invertible + isValidNot0: (c) => !g.is0(c) && g.isValid(c), + isOdd: (c) => (c & L) === L, + neg: (c) => W(-c, e), + eql: (c, d) => c === d, + sqr: (c) => W(c * c, e), + add: (c, d) => W(c + d, e), + sub: (c, d) => W(c - d, e), + mul: (c, d) => W(c * d, e), + pow: (c, d) => si(g, c, d), + div: (c, d) => W(c * hr(d, e), e), + // Same as above, but doesn't normalize + sqrN: (c) => c * c, + addN: (c, d) => c + d, + subN: (c, d) => c - d, + mulN: (c, d) => c * d, + inv: (c) => hr(c, e), + sqrt: a || ((c) => (h || (h = ii(e)), h(g, c))), + toBytes: (c) => n ? Lr(c, u) : It(c, u), + fromBytes: (c, d = !0) => { + if (s) { + if (!s.includes(c.length) || c.length > u) + throw new Error("Field.fromBytes: expected " + s + " bytes, got " + c.length); + const m = new Uint8Array(u); + m.set(c, n ? 0 : m.length - c.length), c = m; + } + if (c.length !== u) + throw new Error("Field.fromBytes: expected " + u + " bytes, got " + c.length); + let I = n ? Pr(c) : Ge(c); + if (o && (I = W(I, e)), !d && !g.isValid(I)) + throw new Error("invalid field element: outside of range 0..ORDER"); + return I; + }, + // TODO: we don't need it here, move out to separate fn + invertBatch: (c) => nn(g, c), + // We can't move this out because Fp6, Fp12 implement it + // and it's unclear what to return in there. + cmov: (c, d, I) => I ? d : c + }); + return Object.freeze(g); +} +function on(e) { + if (typeof e != "bigint") + throw new Error("field order must be bigint"); + const t = e.toString(2).length; + return Math.ceil(t / 8); +} +function sn(e) { + const t = on(e); + return t + Math.ceil(t / 2); +} +function ln(e, t, n = !1) { + const r = e.length, i = on(t), a = sn(t); + if (r < 16 || r < a || r > 1024) + throw new Error("expected " + a + "-1024 bytes of input, got " + r); + const o = n ? Pr(e) : Ge(e), s = W(o, t - L) + L; + return n ? Lr(s, i) : It(s, i); +} +/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ +const Se = BigInt(0), ye = BigInt(1); +function ot(e, t) { + const n = t.negate(); + return e ? n : t; +} +function kt(e, t) { + const n = nn(e.Fp, t.map((r) => r.Z)); + return t.map((r, i) => e.fromAffine(r.toAffine(n[i]))); +} +function cn(e, t) { + if (!Number.isSafeInteger(e) || e <= 0 || e > t) + throw new Error("invalid window size, expected [1.." + t + "], got W=" + e); +} +function vt(e, t) { + cn(e, t); + const n = Math.ceil(t / e) + 1, r = 2 ** (e - 1), i = 2 ** e, a = je(e), o = BigInt(e); + return { windows: n, windowSize: r, mask: a, maxNumber: i, shiftBy: o }; +} +function pr(e, t, n) { + const { windowSize: r, mask: i, maxNumber: a, shiftBy: o } = n; + let s = Number(e & i), l = e >> o; + s > r && (s -= a, l += ye); + const u = t * r, h = u + Math.abs(s) - 1, g = s === 0, c = s < 0, d = t % 2 !== 0; + return { nextN: l, offset: h, isZero: g, isNeg: c, isNegF: d, offsetF: u }; +} +function li(e, t) { + if (!Array.isArray(e)) + throw new Error("array expected"); + e.forEach((n, r) => { + if (!(n instanceof t)) + throw new Error("invalid point at index " + r); + }); +} +function ci(e, t) { + if (!Array.isArray(e)) + throw new Error("array of scalars expected"); + e.forEach((n, r) => { + if (!t.isValid(n)) + throw new Error("invalid scalar at index " + r); + }); +} +const Bt = /* @__PURE__ */ new WeakMap(), un = /* @__PURE__ */ new WeakMap(); +function Qt(e) { + return un.get(e) || 1; +} +function Ir(e) { + if (e !== Se) + throw new Error("invalid wNAF"); +} +class ui { + // Parametrized with a given Point class (not individual point) + constructor(t, n) { + this.BASE = t.BASE, this.ZERO = t.ZERO, this.Fn = t.Fn, this.bits = n; + } + // non-const time multiplication ladder + _unsafeLadder(t, n, r = this.ZERO) { + let i = t; + for (; n > Se; ) + n & ye && (r = r.add(i)), i = i.double(), n >>= ye; + return r; + } + /** + * Creates a wNAF precomputation window. Used for caching. + * Default window size is set by `utils.precompute()` and is equal to 8. + * Number of precomputed points depends on the curve size: + * 2^(𝑊−1) * (Math.ceil(𝑛 / 𝑊) + 1), where: + * - 𝑊 is the window size + * - 𝑛 is the bitlength of the curve order. + * For a 256-bit curve and window size 8, the number of precomputed points is 128 * 33 = 4224. + * @param point Point instance + * @param W window size + * @returns precomputed point tables flattened to a single array + */ + precomputeWindow(t, n) { + const { windows: r, windowSize: i } = vt(n, this.bits), a = []; + let o = t, s = o; + for (let l = 0; l < r; l++) { + s = o, a.push(s); + for (let u = 1; u < i; u++) + s = s.add(o), a.push(s); + o = s.double(); + } + return a; + } + /** + * Implements ec multiplication using precomputed tables and w-ary non-adjacent form. + * More compact implementation: + * https://github.com/paulmillr/noble-secp256k1/blob/47cb1669b6e506ad66b35fe7d76132ae97465da2/index.ts#L502-L541 + * @returns real and fake (for const-time) points + */ + wNAF(t, n, r) { + if (!this.Fn.isValid(r)) + throw new Error("invalid scalar"); + let i = this.ZERO, a = this.BASE; + const o = vt(t, this.bits); + for (let s = 0; s < o.windows; s++) { + const { nextN: l, offset: u, isZero: h, isNeg: g, isNegF: c, offsetF: d } = pr(r, s, o); + r = l, h ? a = a.add(ot(c, n[d])) : i = i.add(ot(g, n[u])); + } + return Ir(r), { p: i, f: a }; + } + /** + * Implements ec unsafe (non const-time) multiplication using precomputed tables and w-ary non-adjacent form. + * @param acc accumulator point to add result of multiplication + * @returns point + */ + wNAFUnsafe(t, n, r, i = this.ZERO) { + const a = vt(t, this.bits); + for (let o = 0; o < a.windows && r !== Se; o++) { + const { nextN: s, offset: l, isZero: u, isNeg: h } = pr(r, o, a); + if (r = s, !u) { + const g = n[l]; + i = i.add(h ? g.negate() : g); + } + } + return Ir(r), i; + } + getPrecomputes(t, n, r) { + let i = Bt.get(n); + return i || (i = this.precomputeWindow(n, t), t !== 1 && (typeof r == "function" && (i = r(i)), Bt.set(n, i))), i; + } + cached(t, n, r) { + const i = Qt(t); + return this.wNAF(i, this.getPrecomputes(i, t, r), n); + } + unsafe(t, n, r, i) { + const a = Qt(t); + return a === 1 ? this._unsafeLadder(t, n, i) : this.wNAFUnsafe(a, this.getPrecomputes(a, t, r), n, i); + } + // We calculate precomputes for elliptic curve point multiplication + // using windowed method. This specifies window size and + // stores precomputed values. Usually only base point would be precomputed. + createCache(t, n) { + cn(n, this.bits), un.set(t, n), Bt.delete(t); + } + hasCache(t) { + return Qt(t) !== 1; + } +} +function di(e, t, n, r) { + let i = t, a = e.ZERO, o = e.ZERO; + for (; n > Se || r > Se; ) + n & ye && (a = a.add(i)), r & ye && (o = o.add(i)), i = i.double(), n >>= ye, r >>= ye; + return { p1: a, p2: o }; +} +function gi(e, t, n, r) { + li(n, e), ci(r, t); + const i = n.length, a = r.length; + if (i !== a) + throw new Error("arrays of points and scalars must have equal length"); + const o = e.ZERO, s = Tr(BigInt(i)); + let l = 1; + s > 12 ? l = s - 3 : s > 4 ? l = s - 2 : s > 0 && (l = 2); + const u = je(l), h = new Array(Number(u) + 1).fill(o), g = Math.floor((t.BITS - 1) / l) * l; + let c = o; + for (let d = g; d >= 0; d -= l) { + h.fill(o); + for (let m = 0; m < a; m++) { + const B = r[m], S = Number(B >> BigInt(d) & u); + h[S] = h[S].add(n[m]); + } + let I = o; + for (let m = h.length - 1, B = o; m > 0; m--) + B = B.add(h[m]), I = I.add(B); + if (c = c.add(I), d !== 0) + for (let m = 0; m < l; m++) + c = c.double(); + } + return c; +} +function mr(e, t, n) { + if (t) { + if (t.ORDER !== e) + throw new Error("Field.ORDER must match order: Fp == p, Fn == n"); + return oi(t), t; + } else + return Re(e, { isLE: n }); +} +function Ai(e, t, n = {}, r) { + if (r === void 0 && (r = e === "edwards"), !t || typeof t != "object") + throw new Error(`expected valid ${e} CURVE object`); + for (const l of ["p", "n", "h"]) { + const u = t[l]; + if (!(typeof u == "bigint" && u > Se)) + throw new Error(`CURVE.${l} must be positive bigint`); + } + const i = mr(t.p, n.Fp, r), a = mr(t.n, n.Fn, r), s = ["Gx", "Gy", "a", "b"]; + for (const l of s) + if (!i.isValid(t[l])) + throw new Error(`CURVE.${l} must be valid field element of CURVE.Fp`); + return t = Object.freeze(Object.assign({}, t)), { CURVE: t, Fp: i, Fn: a }; +} +/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ +const yr = (e, t) => (e + (e >= 0 ? t : -t) / dn) / t; +function hi(e, t, n) { + const [[r, i], [a, o]] = t, s = yr(o * e, n), l = yr(-i * e, n); + let u = e - s * r - l * a, h = -s * i - l * o; + const g = u < ce, c = h < ce; + g && (u = -u), c && (h = -h); + const d = je(Math.ceil(Tr(n) / 2)) + xe; + if (u < ce || u >= d || h < ce || h >= d) + throw new Error("splitScalar (endomorphism): failed, k=" + e); + return { k1neg: g, k1: u, k2neg: c, k2: h }; +} +function Nt(e) { + if (!["compact", "recovered", "der"].includes(e)) + throw new Error('Signature format must be "compact", "recovered", or "der"'); + return e; +} +function Et(e, t) { + const n = {}; + for (let r of Object.keys(t)) + n[r] = e[r] === void 0 ? t[r] : e[r]; + return at(n.lowS, "lowS"), at(n.prehash, "prehash"), n.format !== void 0 && Nt(n.format), n; +} +class fi extends Error { + constructor(t = "") { + super(t); + } +} +const le = { + // asn.1 DER encoding utils + Err: fi, + // Basic building block is TLV (Tag-Length-Value) + _tlv: { + encode: (e, t) => { + const { Err: n } = le; + if (e < 0 || e > 256) + throw new n("tlv.encode: wrong tag"); + if (t.length & 1) + throw new n("tlv.encode: unpadded data"); + const r = t.length / 2, i = Xe(r); + if (i.length / 2 & 128) + throw new n("tlv.encode: long form length too big"); + const a = r > 127 ? Xe(i.length / 2 | 128) : ""; + return Xe(e) + a + i + t; + }, + // v - value, l - left bytes (unparsed) + decode(e, t) { + const { Err: n } = le; + let r = 0; + if (e < 0 || e > 256) + throw new n("tlv.encode: wrong tag"); + if (t.length < 2 || t[r++] !== e) + throw new n("tlv.decode: wrong tlv"); + const i = t[r++], a = !!(i & 128); + let o = 0; + if (!a) + o = i; + else { + const l = i & 127; + if (!l) + throw new n("tlv.decode(long): indefinite length not supported"); + if (l > 4) + throw new n("tlv.decode(long): byte length is too big"); + const u = t.subarray(r, r + l); + if (u.length !== l) + throw new n("tlv.decode: length bytes not complete"); + if (u[0] === 0) + throw new n("tlv.decode(long): zero leftmost byte"); + for (const h of u) + o = o << 8 | h; + if (r += l, o < 128) + throw new n("tlv.decode(long): not minimal encoding"); + } + const s = t.subarray(r, r + o); + if (s.length !== o) + throw new n("tlv.decode: wrong value length"); + return { v: s, l: t.subarray(r + o) }; + } + }, + // https://crypto.stackexchange.com/a/57734 Leftmost bit of first byte is 'negative' flag, + // since we always use positive integers here. It must always be empty: + // - add zero byte if exists + // - if next byte doesn't have a flag, leading zero is not allowed (minimal encoding) + _int: { + encode(e) { + const { Err: t } = le; + if (e < ce) + throw new t("integer: negative integers are not allowed"); + let n = Xe(e); + if (Number.parseInt(n[0], 16) & 8 && (n = "00" + n), n.length & 1) + throw new t("unexpected DER parsing assertion: unpadded hex"); + return n; + }, + decode(e) { + const { Err: t } = le; + if (e[0] & 128) + throw new t("invalid signature integer: negative"); + if (e[0] === 0 && !(e[1] & 128)) + throw new t("invalid signature integer: unnecessary leading zero"); + return Ge(e); + } + }, + toSig(e) { + const { Err: t, _int: n, _tlv: r } = le, i = q("signature", e), { v: a, l: o } = r.decode(48, i); + if (o.length) + throw new t("invalid signature: left bytes after parsing"); + const { v: s, l } = r.decode(2, a), { v: u, l: h } = r.decode(2, l); + if (h.length) + throw new t("invalid signature: left bytes after parsing"); + return { r: n.decode(s), s: n.decode(u) }; + }, + hexFromSig(e) { + const { _tlv: t, _int: n } = le, r = t.encode(2, n.encode(e.r)), i = t.encode(2, n.encode(e.s)), a = r + i; + return t.encode(48, a); + } +}, ce = BigInt(0), xe = BigInt(1), dn = BigInt(2), Ze = BigInt(3), pi = BigInt(4); +function be(e, t) { + const { BYTES: n } = e; + let r; + if (typeof t == "bigint") + r = t; + else { + let i = q("private key", t); + try { + r = e.fromBytes(i); + } catch { + throw new Error(`invalid private key: expected ui8a of size ${n}, got ${typeof t}`); + } + } + if (!e.isValidNot0(r)) + throw new Error("invalid private key: out of range [1..N-1]"); + return r; +} +function Ii(e, t = {}) { + const n = Ai("weierstrass", e, t), { Fp: r, Fn: i } = n; + let a = n.CURVE; + const { h: o, n: s } = a; + Pt(t, {}, { + allowInfinityPoint: "boolean", + clearCofactor: "function", + isTorsionFree: "function", + fromBytes: "function", + toBytes: "function", + endo: "object", + wrapPrivateKey: "boolean" + }); + const { endo: l } = t; + if (l && (!r.is0(a.a) || typeof l.beta != "bigint" || !Array.isArray(l.basises))) + throw new Error('invalid endo: expected "beta": bigint and "basises": array'); + const u = An(r, i); + function h() { + if (!r.isOdd) + throw new Error("compression is not supported: Field does not have .isOdd()"); + } + function g(U, p, f) { + const { x: A, y } = p.toAffine(), w = r.toBytes(A); + if (at(f, "isCompressed"), f) { + h(); + const E = !r.isOdd(y); + return ne(gn(E), w); + } else + return ne(Uint8Array.of(4), w, r.toBytes(y)); + } + function c(U) { + Ie(U, void 0, "Point"); + const { publicKey: p, publicKeyUncompressed: f } = u, A = U.length, y = U[0], w = U.subarray(1); + if (A === p && (y === 2 || y === 3)) { + const E = r.fromBytes(w); + if (!r.isValid(E)) + throw new Error("bad point: is not on curve, wrong x"); + const Q = m(E); + let k; + try { + k = r.sqrt(Q); + } catch (Y) { + const O = Y instanceof Error ? ": " + Y.message : ""; + throw new Error("bad point: is not on curve, sqrt error" + O); + } + h(); + const x = r.isOdd(k); + return (y & 1) === 1 !== x && (k = r.neg(k)), { x: E, y: k }; + } else if (A === f && y === 4) { + const E = r.BYTES, Q = r.fromBytes(w.subarray(0, E)), k = r.fromBytes(w.subarray(E, E * 2)); + if (!B(Q, k)) + throw new Error("bad point: is not on curve"); + return { x: Q, y: k }; + } else + throw new Error(`bad point: got length ${A}, expected compressed=${p} or uncompressed=${f}`); + } + const d = t.toBytes || g, I = t.fromBytes || c; + function m(U) { + const p = r.sqr(U), f = r.mul(p, U); + return r.add(r.add(f, r.mul(U, a.a)), a.b); + } + function B(U, p) { + const f = r.sqr(p), A = m(U); + return r.eql(f, A); + } + if (!B(a.Gx, a.Gy)) + throw new Error("bad curve params: generator point"); + const S = r.mul(r.pow(a.a, Ze), pi), j = r.mul(r.sqr(a.b), BigInt(27)); + if (r.is0(r.add(S, j))) + throw new Error("bad curve params: a or b"); + function b(U, p, f = !1) { + if (!r.isValid(p) || f && r.is0(p)) + throw new Error(`bad point coordinate ${U}`); + return p; + } + function N(U) { + if (!(U instanceof v)) + throw new Error("ProjectivePoint expected"); + } + function K(U) { + if (!l || !l.basises) + throw new Error("no endo"); + return hi(U, l.basises, i.ORDER); + } + const C = Ar((U, p) => { + const { X: f, Y: A, Z: y } = U; + if (r.eql(y, r.ONE)) + return { x: f, y: A }; + const w = U.is0(); + p == null && (p = w ? r.ONE : r.inv(y)); + const E = r.mul(f, p), Q = r.mul(A, p), k = r.mul(y, p); + if (w) + return { x: r.ZERO, y: r.ZERO }; + if (!r.eql(k, r.ONE)) + throw new Error("invZ was invalid"); + return { x: E, y: Q }; + }), D = Ar((U) => { + if (U.is0()) { + if (t.allowInfinityPoint && !r.is0(U.Y)) + return; + throw new Error("bad point: ZERO"); + } + const { x: p, y: f } = U.toAffine(); + if (!r.isValid(p) || !r.isValid(f)) + throw new Error("bad point: x or y not field elements"); + if (!B(p, f)) + throw new Error("bad point: equation left != right"); + if (!U.isTorsionFree()) + throw new Error("bad point: not in prime-order subgroup"); + return !0; + }); + function G(U, p, f, A, y) { + return f = new v(r.mul(f.X, U), f.Y, f.Z), p = ot(A, p), f = ot(y, f), p.add(f); + } + class v { + /** Does NOT validate if the point is valid. Use `.assertValidity()`. */ + constructor(p, f, A) { + this.X = b("x", p), this.Y = b("y", f, !0), this.Z = b("z", A), Object.freeze(this); + } + static CURVE() { + return a; + } + /** Does NOT validate if the point is valid. Use `.assertValidity()`. */ + static fromAffine(p) { + const { x: f, y: A } = p || {}; + if (!p || !r.isValid(f) || !r.isValid(A)) + throw new Error("invalid affine point"); + if (p instanceof v) + throw new Error("projective point not allowed"); + return r.is0(f) && r.is0(A) ? v.ZERO : new v(f, A, r.ONE); + } + static fromBytes(p) { + const f = v.fromAffine(I(Ie(p, void 0, "point"))); + return f.assertValidity(), f; + } + static fromHex(p) { + return v.fromBytes(q("pointHex", p)); + } + get x() { + return this.toAffine().x; + } + get y() { + return this.toAffine().y; + } + /** + * + * @param windowSize + * @param isLazy true will defer table computation until the first multiplication + * @returns + */ + precompute(p = 8, f = !0) { + return Z.createCache(this, p), f || this.multiply(Ze), this; + } + // TODO: return `this` + /** A point on curve is valid if it conforms to equation. */ + assertValidity() { + D(this); + } + hasEvenY() { + const { y: p } = this.toAffine(); + if (!r.isOdd) + throw new Error("Field doesn't support isOdd"); + return !r.isOdd(p); + } + /** Compare one point to another. */ + equals(p) { + N(p); + const { X: f, Y: A, Z: y } = this, { X: w, Y: E, Z: Q } = p, k = r.eql(r.mul(f, Q), r.mul(w, y)), x = r.eql(r.mul(A, Q), r.mul(E, y)); + return k && x; + } + /** Flips point to one corresponding to (x, -y) in Affine coordinates. */ + negate() { + return new v(this.X, r.neg(this.Y), this.Z); + } + // Renes-Costello-Batina exception-free doubling formula. + // There is 30% faster Jacobian formula, but it is not complete. + // https://eprint.iacr.org/2015/1060, algorithm 3 + // Cost: 8M + 3S + 3*a + 2*b3 + 15add. + double() { + const { a: p, b: f } = a, A = r.mul(f, Ze), { X: y, Y: w, Z: E } = this; + let Q = r.ZERO, k = r.ZERO, x = r.ZERO, F = r.mul(y, y), Y = r.mul(w, w), O = r.mul(E, E), H = r.mul(y, w); + return H = r.add(H, H), x = r.mul(y, E), x = r.add(x, x), Q = r.mul(p, x), k = r.mul(A, O), k = r.add(Q, k), Q = r.sub(Y, k), k = r.add(Y, k), k = r.mul(Q, k), Q = r.mul(H, Q), x = r.mul(A, x), O = r.mul(p, O), H = r.sub(F, O), H = r.mul(p, H), H = r.add(H, x), x = r.add(F, F), F = r.add(x, F), F = r.add(F, O), F = r.mul(F, H), k = r.add(k, F), O = r.mul(w, E), O = r.add(O, O), F = r.mul(O, H), Q = r.sub(Q, F), x = r.mul(O, Y), x = r.add(x, x), x = r.add(x, x), new v(Q, k, x); + } + // Renes-Costello-Batina exception-free addition formula. + // There is 30% faster Jacobian formula, but it is not complete. + // https://eprint.iacr.org/2015/1060, algorithm 1 + // Cost: 12M + 0S + 3*a + 3*b3 + 23add. + add(p) { + N(p); + const { X: f, Y: A, Z: y } = this, { X: w, Y: E, Z: Q } = p; + let k = r.ZERO, x = r.ZERO, F = r.ZERO; + const Y = a.a, O = r.mul(a.b, Ze); + let H = r.mul(f, w), z = r.mul(A, E), M = r.mul(y, Q), T = r.add(f, A), J = r.add(w, E); + T = r.mul(T, J), J = r.add(H, z), T = r.sub(T, J), J = r.add(f, y); + let R = r.add(w, Q); + return J = r.mul(J, R), R = r.add(H, M), J = r.sub(J, R), R = r.add(A, y), k = r.add(E, Q), R = r.mul(R, k), k = r.add(z, M), R = r.sub(R, k), F = r.mul(Y, J), k = r.mul(O, M), F = r.add(k, F), k = r.sub(z, F), F = r.add(z, F), x = r.mul(k, F), z = r.add(H, H), z = r.add(z, H), M = r.mul(Y, M), J = r.mul(O, J), z = r.add(z, M), M = r.sub(H, M), M = r.mul(Y, M), J = r.add(J, M), H = r.mul(z, J), x = r.add(x, H), H = r.mul(R, J), k = r.mul(T, k), k = r.sub(k, H), H = r.mul(T, z), F = r.mul(R, F), F = r.add(F, H), new v(k, x, F); + } + subtract(p) { + return this.add(p.negate()); + } + is0() { + return this.equals(v.ZERO); + } + /** + * Constant time multiplication. + * Uses wNAF method. Windowed method may be 10% faster, + * but takes 2x longer to generate and consumes 2x memory. + * Uses precomputes when available. + * Uses endomorphism for Koblitz curves. + * @param scalar by which the point would be multiplied + * @returns New point + */ + multiply(p) { + const { endo: f } = t; + if (!i.isValidNot0(p)) + throw new Error("invalid scalar: out of range"); + let A, y; + const w = (E) => Z.cached(this, E, (Q) => kt(v, Q)); + if (f) { + const { k1neg: E, k1: Q, k2neg: k, k2: x } = K(p), { p: F, f: Y } = w(Q), { p: O, f: H } = w(x); + y = Y.add(H), A = G(f.beta, F, O, E, k); + } else { + const { p: E, f: Q } = w(p); + A = E, y = Q; + } + return kt(v, [A, y])[0]; + } + /** + * Non-constant-time multiplication. Uses double-and-add algorithm. + * It's faster, but should only be used when you don't care about + * an exposed secret key e.g. sig verification, which works over *public* keys. + */ + multiplyUnsafe(p) { + const { endo: f } = t, A = this; + if (!i.isValid(p)) + throw new Error("invalid scalar: out of range"); + if (p === ce || A.is0()) + return v.ZERO; + if (p === xe) + return A; + if (Z.hasCache(this)) + return this.multiply(p); + if (f) { + const { k1neg: y, k1: w, k2neg: E, k2: Q } = K(p), { p1: k, p2: x } = di(v, A, w, Q); + return G(f.beta, k, x, y, E); + } else + return Z.unsafe(A, p); + } + multiplyAndAddUnsafe(p, f, A) { + const y = this.multiplyUnsafe(f).add(p.multiplyUnsafe(A)); + return y.is0() ? void 0 : y; + } + /** + * Converts Projective point to affine (x, y) coordinates. + * @param invertedZ Z^-1 (inverted zero) - optional, precomputation is useful for invertBatch + */ + toAffine(p) { + return C(this, p); + } + /** + * Checks whether Point is free of torsion elements (is in prime subgroup). + * Always torsion-free for cofactor=1 curves. + */ + isTorsionFree() { + const { isTorsionFree: p } = t; + return o === xe ? !0 : p ? p(v, this) : Z.unsafe(this, s).is0(); + } + clearCofactor() { + const { clearCofactor: p } = t; + return o === xe ? this : p ? p(v, this) : this.multiplyUnsafe(o); + } + isSmallOrder() { + return this.multiplyUnsafe(o).is0(); + } + toBytes(p = !0) { + return at(p, "isCompressed"), this.assertValidity(), d(v, this, p); + } + toHex(p = !0) { + return Ee(this.toBytes(p)); + } + toString() { + return ``; + } + // TODO: remove + get px() { + return this.X; + } + get py() { + return this.X; + } + get pz() { + return this.Z; + } + toRawBytes(p = !0) { + return this.toBytes(p); + } + _setWindowSize(p) { + this.precompute(p); + } + static normalizeZ(p) { + return kt(v, p); + } + static msm(p, f) { + return gi(v, i, p, f); + } + static fromPrivateKey(p) { + return v.BASE.multiply(be(i, p)); + } + } + v.BASE = new v(a.Gx, a.Gy, r.ONE), v.ZERO = new v(r.ZERO, r.ONE, r.ZERO), v.Fp = r, v.Fn = i; + const P = i.BITS, Z = new ui(v, t.endo ? Math.ceil(P / 2) : P); + return v.BASE.precompute(8), v; +} +function gn(e) { + return Uint8Array.of(e ? 2 : 3); +} +function An(e, t) { + return { + secretKey: t.BYTES, + publicKey: 1 + e.BYTES, + publicKeyUncompressed: 1 + 2 * e.BYTES, + publicKeyHasPrefix: !0, + signature: 2 * t.BYTES + }; +} +function mi(e, t = {}) { + const { Fn: n } = e, r = t.randomBytes || ft, i = Object.assign(An(e.Fp, n), { seed: sn(n.ORDER) }); + function a(d) { + try { + return !!be(n, d); + } catch { + return !1; + } + } + function o(d, I) { + const { publicKey: m, publicKeyUncompressed: B } = i; + try { + const S = d.length; + return I === !0 && S !== m || I === !1 && S !== B ? !1 : !!e.fromBytes(d); + } catch { + return !1; + } + } + function s(d = r(i.seed)) { + return ln(Ie(d, i.seed, "seed"), n.ORDER); + } + function l(d, I = !0) { + return e.BASE.multiply(be(n, d)).toBytes(I); + } + function u(d) { + const I = s(d); + return { secretKey: I, publicKey: l(I) }; + } + function h(d) { + if (typeof d == "bigint") + return !1; + if (d instanceof e) + return !0; + const { secretKey: I, publicKey: m, publicKeyUncompressed: B } = i; + if (n.allowedLengths || I === m) + return; + const S = q("key", d).length; + return S === m || S === B; + } + function g(d, I, m = !0) { + if (h(d) === !0) + throw new Error("first arg must be private key"); + if (h(I) === !1) + throw new Error("second arg must be public key"); + const B = be(n, d); + return e.fromHex(I).multiply(B).toBytes(m); + } + return Object.freeze({ getPublicKey: l, getSharedSecret: g, keygen: u, Point: e, utils: { + isValidSecretKey: a, + isValidPublicKey: o, + randomSecretKey: s, + // TODO: remove + isValidPrivateKey: a, + randomPrivateKey: s, + normPrivateKeyToScalar: (d) => be(n, d), + precompute(d = 8, I = e.BASE) { + return I.precompute(d, !1); + } + }, lengths: i }); +} +function yi(e, t, n = {}) { + ht(t), Pt(n, {}, { + hmac: "function", + lowS: "boolean", + randomBytes: "function", + bits2int: "function", + bits2int_modN: "function" + }); + const r = n.randomBytes || ft, i = n.hmac || ((f, ...A) => pt(t, f, ne(...A))), { Fp: a, Fn: o } = e, { ORDER: s, BITS: l } = o, { keygen: u, getPublicKey: h, getSharedSecret: g, utils: c, lengths: d } = mi(e, n), I = { + prehash: !1, + lowS: typeof n.lowS == "boolean" ? n.lowS : !1, + format: void 0, + //'compact' as ECDSASigFormat, + extraEntropy: !1 + }, m = "compact"; + function B(f) { + const A = s >> xe; + return f > A; + } + function S(f, A) { + if (!o.isValidNot0(A)) + throw new Error(`invalid signature ${f}: out of range 1..Point.Fn.ORDER`); + return A; + } + function j(f, A) { + Nt(A); + const y = d.signature, w = A === "compact" ? y : A === "recovered" ? y + 1 : void 0; + return Ie(f, w, `${A} signature`); + } + class b { + constructor(A, y, w) { + this.r = S("r", A), this.s = S("s", y), w != null && (this.recovery = w), Object.freeze(this); + } + static fromBytes(A, y = m) { + j(A, y); + let w; + if (y === "der") { + const { r: x, s: F } = le.toSig(Ie(A)); + return new b(x, F); + } + y === "recovered" && (w = A[0], y = "compact", A = A.subarray(1)); + const E = o.BYTES, Q = A.subarray(0, E), k = A.subarray(E, E * 2); + return new b(o.fromBytes(Q), o.fromBytes(k), w); + } + static fromHex(A, y) { + return this.fromBytes(it(A), y); + } + addRecoveryBit(A) { + return new b(this.r, this.s, A); + } + recoverPublicKey(A) { + const y = a.ORDER, { r: w, s: E, recovery: Q } = this; + if (Q == null || ![0, 1, 2, 3].includes(Q)) + throw new Error("recovery id invalid"); + if (s * dn < y && Q > 1) + throw new Error("recovery id is ambiguous for h>1 curve"); + const x = Q === 2 || Q === 3 ? w + s : w; + if (!a.isValid(x)) + throw new Error("recovery id 2 or 3 invalid"); + const F = a.toBytes(x), Y = e.fromBytes(ne(gn((Q & 1) === 0), F)), O = o.inv(x), H = K(q("msgHash", A)), z = o.create(-H * O), M = o.create(E * O), T = e.BASE.multiplyUnsafe(z).add(Y.multiplyUnsafe(M)); + if (T.is0()) + throw new Error("point at infinify"); + return T.assertValidity(), T; + } + // Signatures should be low-s, to prevent malleability. + hasHighS() { + return B(this.s); + } + toBytes(A = m) { + if (Nt(A), A === "der") + return it(le.hexFromSig(this)); + const y = o.toBytes(this.r), w = o.toBytes(this.s); + if (A === "recovered") { + if (this.recovery == null) + throw new Error("recovery bit must be present"); + return ne(Uint8Array.of(this.recovery), y, w); + } + return ne(y, w); + } + toHex(A) { + return Ee(this.toBytes(A)); + } + // TODO: remove + assertValidity() { + } + static fromCompact(A) { + return b.fromBytes(q("sig", A), "compact"); + } + static fromDER(A) { + return b.fromBytes(q("sig", A), "der"); + } + normalizeS() { + return this.hasHighS() ? new b(this.r, o.neg(this.s), this.recovery) : this; + } + toDERRawBytes() { + return this.toBytes("der"); + } + toDERHex() { + return Ee(this.toBytes("der")); + } + toCompactRawBytes() { + return this.toBytes("compact"); + } + toCompactHex() { + return Ee(this.toBytes("compact")); + } + } + const N = n.bits2int || function(A) { + if (A.length > 8192) + throw new Error("input is too large"); + const y = Ge(A), w = A.length * 8 - l; + return w > 0 ? y >> BigInt(w) : y; + }, K = n.bits2int_modN || function(A) { + return o.create(N(A)); + }, C = je(l); + function D(f) { + return $n("num < 2^" + l, f, ce, C), o.toBytes(f); + } + function G(f, A) { + return Ie(f, void 0, "message"), A ? Ie(t(f), void 0, "prehashed message") : f; + } + function v(f, A, y) { + if (["recovered", "canonical"].some((z) => z in y)) + throw new Error("sign() legacy options not supported"); + const { lowS: w, prehash: E, extraEntropy: Q } = Et(y, I); + f = G(f, E); + const k = K(f), x = be(o, A), F = [D(x), D(k)]; + if (Q != null && Q !== !1) { + const z = Q === !0 ? r(d.secretKey) : Q; + F.push(q("extraEntropy", z)); + } + const Y = ne(...F), O = k; + function H(z) { + const M = N(z); + if (!o.isValidNot0(M)) + return; + const T = o.inv(M), J = e.BASE.multiply(M).toAffine(), R = o.create(J.x); + if (R === ce) + return; + const _e = o.create(T * o.create(O + R * x)); + if (_e === ce) + return; + let ir = (J.x === R ? 0 : 2) | Number(J.y & xe), ar = _e; + return w && B(_e) && (ar = o.neg(_e), ir ^= 1), new b(R, ar, ir); + } + return { seed: Y, k2sig: H }; + } + function P(f, A, y = {}) { + f = q("message", f); + const { seed: w, k2sig: E } = v(f, A, y); + return Wn(t.outputLen, o.BYTES, i)(w, E); + } + function Z(f) { + let A; + const y = typeof f == "string" || At(f), w = !y && f !== null && typeof f == "object" && typeof f.r == "bigint" && typeof f.s == "bigint"; + if (!y && !w) + throw new Error("invalid signature, expected Uint8Array, hex string or Signature instance"); + if (w) + A = new b(f.r, f.s); + else if (y) { + try { + A = b.fromBytes(q("sig", f), "der"); + } catch (E) { + if (!(E instanceof le.Err)) + throw E; + } + if (!A) + try { + A = b.fromBytes(q("sig", f), "compact"); + } catch { + return !1; + } + } + return A || !1; + } + function U(f, A, y, w = {}) { + const { lowS: E, prehash: Q, format: k } = Et(w, I); + if (y = q("publicKey", y), A = G(q("message", A), Q), "strict" in w) + throw new Error("options.strict was renamed to lowS"); + const x = k === void 0 ? Z(f) : b.fromBytes(q("sig", f), k); + if (x === !1) + return !1; + try { + const F = e.fromBytes(y); + if (E && x.hasHighS()) + return !1; + const { r: Y, s: O } = x, H = K(A), z = o.inv(O), M = o.create(H * z), T = o.create(Y * z), J = e.BASE.multiplyUnsafe(M).add(F.multiplyUnsafe(T)); + return J.is0() ? !1 : o.create(J.x) === Y; + } catch { + return !1; + } + } + function p(f, A, y = {}) { + const { prehash: w } = Et(y, I); + return A = G(A, w), b.fromBytes(f, "recovered").recoverPublicKey(A).toBytes(); + } + return Object.freeze({ + keygen: u, + getPublicKey: h, + getSharedSecret: g, + utils: c, + lengths: d, + Point: e, + sign: P, + verify: U, + recoverPublicKey: p, + Signature: b, + hash: t + }); +} +function bi(e) { + const t = { + a: e.a, + b: e.b, + p: e.Fp.ORDER, + n: e.n, + h: e.h, + Gx: e.Gx, + Gy: e.Gy + }, n = e.Fp; + let r = e.allowedPrivateKeyLengths ? Array.from(new Set(e.allowedPrivateKeyLengths.map((o) => Math.ceil(o / 2)))) : void 0; + const i = Re(t.n, { + BITS: e.nBitLength, + allowedLengths: r, + modFromBytes: e.wrapPrivateKey + }), a = { + Fp: n, + Fn: i, + allowInfinityPoint: e.allowInfinityPoint, + endo: e.endo, + isTorsionFree: e.isTorsionFree, + clearCofactor: e.clearCofactor, + fromBytes: e.fromBytes, + toBytes: e.toBytes + }; + return { CURVE: t, curveOpts: a }; +} +function wi(e) { + const { CURVE: t, curveOpts: n } = bi(e), r = { + hmac: e.hmac, + randomBytes: e.randomBytes, + lowS: e.lowS, + bits2int: e.bits2int, + bits2int_modN: e.bits2int_modN + }; + return { CURVE: t, curveOpts: n, hash: e.hash, ecdsaOpts: r }; +} +function Ci(e, t) { + const n = t.Point; + return Object.assign({}, t, { + ProjectivePoint: n, + CURVE: Object.assign({}, e, an(n.Fn.ORDER, n.Fn.BITS)) + }); +} +function ki(e) { + const { CURVE: t, curveOpts: n, hash: r, ecdsaOpts: i } = wi(e), a = Ii(t, n), o = yi(a, r, i); + return Ci(e, o); +} +/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ +function vi(e, t) { + const n = (r) => ki({ ...e, hash: r }); + return { ...n(t), create: n }; +} +/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ +const Fe = { + p: BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"), + n: BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"), + h: BigInt(1), + a: BigInt(0), + b: BigInt(7), + Gx: BigInt("0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + Gy: BigInt("0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8") +}, Bi = { + beta: BigInt("0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee"), + basises: [ + [BigInt("0x3086d221a7d46bcde86c90e49284eb15"), -BigInt("0xe4437ed6010e88286f547fa90abfe4c3")], + [BigInt("0x114ca50f7a8e2f3f657c1108d9d44cfd8"), BigInt("0x3086d221a7d46bcde86c90e49284eb15")] + ] +}, Qi = /* @__PURE__ */ BigInt(0), br = /* @__PURE__ */ BigInt(1), Kt = /* @__PURE__ */ BigInt(2); +function Ei(e) { + const t = Fe.p, n = BigInt(3), r = BigInt(6), i = BigInt(11), a = BigInt(22), o = BigInt(23), s = BigInt(44), l = BigInt(88), u = e * e * e % t, h = u * u * e % t, g = $(h, n, t) * h % t, c = $(g, n, t) * h % t, d = $(c, Kt, t) * u % t, I = $(d, i, t) * d % t, m = $(I, a, t) * I % t, B = $(m, s, t) * m % t, S = $(B, l, t) * B % t, j = $(S, s, t) * m % t, b = $(j, n, t) * h % t, N = $(b, o, t) * I % t, K = $(N, r, t) * u % t, C = $(K, Kt, t); + if (!st.eql(st.sqr(C), e)) + throw new Error("Cannot find square root"); + return C; +} +const st = Re(Fe.p, { sqrt: Ei }), mt = vi({ ...Fe, Fp: st, lowS: !0, endo: Bi }, fe), wr = {}; +function lt(e, ...t) { + let n = wr[e]; + if (n === void 0) { + const r = fe(qt(e)); + n = ne(r, r), wr[e] = n; + } + return fe(ne(n, ...t)); +} +const Tt = (e) => e.toBytes(!0).slice(1), He = mt.Point, Xt = (e) => e % Kt === Qi; +function Ot(e) { + const { Fn: t, BASE: n } = He, r = be(t, e), i = n.multiply(r); + return { scalar: Xt(i.y) ? r : t.neg(r), bytes: Tt(i) }; +} +function hn(e) { + const t = st; + if (!t.isValidNot0(e)) + throw new Error("invalid x: Fail if x ≥ p"); + const n = t.create(e * e), r = t.create(n * e + BigInt(7)); + let i = t.sqrt(r); + Xt(i) || (i = t.neg(i)); + const a = He.fromAffine({ x: e, y: i }); + return a.assertValidity(), a; +} +const Ye = Ge; +function fn(...e) { + return He.Fn.create(Ye(lt("BIP0340/challenge", ...e))); +} +function Cr(e) { + return Ot(e).bytes; +} +function xi(e, t, n = ft(32)) { + const { Fn: r } = He, i = q("message", e), { bytes: a, scalar: o } = Ot(t), s = q("auxRand", n, 32), l = r.toBytes(o ^ Ye(lt("BIP0340/aux", s))), u = lt("BIP0340/nonce", l, a, i), { bytes: h, scalar: g } = Ot(u), c = fn(h, a, i), d = new Uint8Array(64); + if (d.set(h, 0), d.set(r.toBytes(r.create(g + c * o)), 32), !pn(d, i, a)) + throw new Error("sign: Invalid signature produced"); + return d; +} +function pn(e, t, n) { + const { Fn: r, BASE: i } = He, a = q("signature", e, 64), o = q("message", t), s = q("publicKey", n, 32); + try { + const l = hn(Ye(s)), u = Ye(a.subarray(0, 32)); + if (!Ht(u, br, Fe.p)) + return !1; + const h = Ye(a.subarray(32, 64)); + if (!Ht(h, br, Fe.n)) + return !1; + const g = fn(r.toBytes(u), Tt(l), o), c = i.multiplyUnsafe(h).add(l.multiplyUnsafe(r.neg(g))), { x: d, y: I } = c.toAffine(); + return !(c.is0() || !Xt(I) || d !== u); + } catch { + return !1; + } +} +const Zt = /* @__PURE__ */ (() => { + const n = (i = ft(48)) => ln(i, Fe.n); + mt.utils.randomSecretKey; + function r(i) { + const a = n(i); + return { secretKey: a, publicKey: Cr(a) }; + } + return { + keygen: r, + getPublicKey: Cr, + sign: xi, + verify: pn, + Point: He, + utils: { + randomSecretKey: n, + randomPrivateKey: n, + taggedHash: lt, + // TODO: remove + lift_x: hn, + pointToBytes: Tt, + numberToBytesBE: It, + bytesToNumberBE: Ge, + mod: W + }, + lengths: { + secretKey: 32, + publicKey: 32, + publicKeyHasPrefix: !1, + signature: 64, + seed: 48 + } + }; +})(), Di = mt.CURVE.n; +function Si(e) { + let t = 0n; + for (const n of e) + t = t << 8n | BigInt(n); + return t; +} +function Fi(e) { + const t = new Uint8Array(32); + let n = e; + for (let r = 31; r >= 0; r--) + t[r] = Number(n & 0xffn), n >>= 8n; + return t; +} +function In(e, t) { + const n = De(t); + for (let r = 0; r <= 255; r++) { + const i = qr(n, new Uint8Array([0, r])), a = Rt(e, i, 32), o = Si(a); + if (o >= 1n && o < Di) + return { scalar: Fi(o), counter: r }; + } + throw new Error("ScalarExpand: exhausted 256 counters without a valid scalar (should not happen in practice)."); +} +/*! + * hash-wasm (https://www.npmjs.com/package/hash-wasm) + * (c) Dani Biro + * @license MIT + */ +function we(e, t, n, r) { + function i(a) { + return a instanceof n ? a : new n(function(o) { + o(a); + }); + } + return new (n || (n = Promise))(function(a, o) { + function s(h) { + try { + u(r.next(h)); + } catch (g) { + o(g); + } + } + function l(h) { + try { + u(r.throw(h)); + } catch (g) { + o(g); + } + } + function u(h) { + h.done ? a(h.value) : i(h.value).then(s, l); + } + u((r = r.apply(e, [])).next()); + }); +} +class V { + constructor() { + this.mutex = Promise.resolve(); + } + lock() { + let t = () => { + }; + return this.mutex = this.mutex.then(() => new Promise(t)), new Promise((n) => { + t = n; + }); + } + dispatch(t) { + return we(this, void 0, void 0, function* () { + const n = yield this.lock(); + try { + return yield Promise.resolve(t()); + } finally { + n(); + } + }); + } +} +var xt; +function Ui() { + return typeof globalThis < "u" ? globalThis : typeof self < "u" ? self : typeof window < "u" ? window : global; +} +const Vt = Ui(), Dt = (xt = Vt.Buffer) !== null && xt !== void 0 ? xt : null, Gi = Vt.TextEncoder ? new Vt.TextEncoder() : null; +function mn(e, t) { + return (e & 15) + (e >> 6 | e >> 3 & 8) << 4 | (t & 15) + (t >> 6 | t >> 3 & 8); +} +function yn(e, t) { + const n = t.length >> 1; + for (let r = 0; r < n; r++) { + const i = r << 1; + e[r] = mn(t.charCodeAt(i), t.charCodeAt(i + 1)); + } +} +function Hi(e, t) { + if (e.length !== t.length * 2) + return !1; + for (let n = 0; n < t.length; n++) { + const r = n << 1; + if (t[n] !== mn(e.charCodeAt(r), e.charCodeAt(r + 1))) + return !1; + } + return !0; +} +const kr = 87, vr = 48; +function zt(e, t, n) { + let r = 0; + for (let i = 0; i < n; i++) { + let a = t[i] >>> 4; + e[r++] = a > 9 ? a + kr : a + vr, a = t[i] & 15, e[r++] = a > 9 ? a + kr : a + vr; + } + return String.fromCharCode.apply(null, e); +} +const ue = Dt !== null ? (e) => { + if (typeof e == "string") { + const t = Dt.from(e, "utf8"); + return new Uint8Array(t.buffer, t.byteOffset, t.length); + } + if (Dt.isBuffer(e)) + return new Uint8Array(e.buffer, e.byteOffset, e.length); + if (ArrayBuffer.isView(e)) + return new Uint8Array(e.buffer, e.byteOffset, e.byteLength); + throw new Error("Invalid data type!"); +} : (e) => { + if (typeof e == "string") + return Gi.encode(e); + if (ArrayBuffer.isView(e)) + return new Uint8Array(e.buffer, e.byteOffset, e.byteLength); + throw new Error("Invalid data type!"); +}, te = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", ze = new Uint8Array(256); +for (let e = 0; e < te.length; e++) + ze[te.charCodeAt(e)] = e; +function Br(e, t = !0) { + const n = e.length, r = n % 3, i = [], a = n - r; + for (let o = 0; o < a; o += 3) { + const s = (e[o] << 16 & 16711680) + (e[o + 1] << 8 & 65280) + (e[o + 2] & 255), l = te.charAt(s >> 18 & 63) + te.charAt(s >> 12 & 63) + te.charAt(s >> 6 & 63) + te.charAt(s & 63); + i.push(l); + } + if (r === 1) { + const o = e[n - 1], s = te.charAt(o >> 2), l = te.charAt(o << 4 & 63); + i.push(`${s}${l}`), t && i.push("=="); + } else if (r === 2) { + const o = (e[n - 2] << 8) + e[n - 1], s = te.charAt(o >> 10), l = te.charAt(o >> 4 & 63), u = te.charAt(o << 2 & 63); + i.push(`${s}${l}${u}`), t && i.push("="); + } + return i.join(""); +} +function Ni(e) { + let t = Math.floor(e.length * 0.75); + const n = e.length; + return e[n - 1] === "=" && (t -= 1, e[n - 2] === "=" && (t -= 1)), t; +} +function Ki(e) { + const t = Ni(e), n = e.length, r = new Uint8Array(t); + let i = 0; + for (let a = 0; a < n; a += 4) { + const o = ze[e.charCodeAt(a)], s = ze[e.charCodeAt(a + 1)], l = ze[e.charCodeAt(a + 2)], u = ze[e.charCodeAt(a + 3)]; + r[i] = o << 2 | s >> 4, i += 1, r[i] = (s & 15) << 4 | l >> 2, i += 1, r[i] = (l & 3) << 6 | u & 63, i += 1; + } + return r; +} +const $e = 16 * 1024, Ke = 4, Oi = new V(), St = /* @__PURE__ */ new Map(); +function bn(e, t) { + return we(this, void 0, void 0, function* () { + let n = null, r = null, i = !1; + if (typeof WebAssembly > "u") + throw new Error("WebAssembly is not supported in this environment!"); + const a = (C, D = 0) => { + r.set(C, D); + }, o = () => r, s = () => n.exports, l = (C) => { + n.exports.Hash_SetMemorySize(C); + const D = n.exports.Hash_GetBuffer(), G = n.exports.memory.buffer; + r = new Uint8Array(G, D, C); + }, u = () => new DataView(n.exports.memory.buffer).getUint32(n.exports.STATE_SIZE, !0), h = Oi.dispatch(() => we(this, void 0, void 0, function* () { + if (!St.has(e.name)) { + const D = Ki(e.data), G = WebAssembly.compile(D); + St.set(e.name, G); + } + const C = yield St.get(e.name); + n = yield WebAssembly.instantiate(C, { + // env: { + // emscripten_memcpy_big: (dest, src, num) => { + // const memoryBuffer = wasmInstance.exports.memory.buffer; + // const memView = new Uint8Array(memoryBuffer, 0); + // memView.set(memView.subarray(src, src + num), dest); + // }, + // print_memory: (offset, len) => { + // const memoryBuffer = wasmInstance.exports.memory.buffer; + // const memView = new Uint8Array(memoryBuffer, 0); + // console.log('print_int32', memView.subarray(offset, offset + len)); + // }, + // }, + }); + })), g = () => we(this, void 0, void 0, function* () { + n || (yield h); + const C = n.exports.Hash_GetBuffer(), D = n.exports.memory.buffer; + r = new Uint8Array(D, C, $e); + }), c = (C = null) => { + i = !0, n.exports.Hash_Init(C); + }, d = (C) => { + let D = 0; + for (; D < C.length; ) { + const G = C.subarray(D, D + $e); + D += G.length, r.set(G), n.exports.Hash_Update(G.length); + } + }, I = (C) => { + if (!i) + throw new Error("update() called before init()"); + const D = ue(C); + d(D); + }, m = new Uint8Array(t * 2), B = (C, D = null) => { + if (!i) + throw new Error("digest() called before init()"); + return i = !1, n.exports.Hash_Final(D), C === "binary" ? r.slice(0, t) : zt(m, r, t); + }, S = () => { + if (!i) + throw new Error("save() can only be called after init() and before digest()"); + const C = n.exports.Hash_GetState(), D = u(), G = n.exports.memory.buffer, v = new Uint8Array(G, C, D), P = new Uint8Array(Ke + D); + return yn(P, e.hash), P.set(v, Ke), P; + }, j = (C) => { + if (!(C instanceof Uint8Array)) + throw new Error("load() expects an Uint8Array generated by save()"); + const D = n.exports.Hash_GetState(), G = u(), v = Ke + G, P = n.exports.memory.buffer; + if (C.length !== v) + throw new Error(`Bad state length (expected ${v} bytes, got ${C.length})`); + if (!Hi(e.hash, C.subarray(0, Ke))) + throw new Error("This state was written by an incompatible hash implementation"); + const Z = C.subarray(Ke); + new Uint8Array(P, D, G).set(Z), i = !0; + }, b = (C) => typeof C == "string" ? C.length < $e / 4 : C.byteLength < $e; + let N = b; + switch (e.name) { + case "argon2": + case "scrypt": + N = () => !0; + break; + case "blake2b": + case "blake2s": + N = (C, D) => D <= 512 && b(C); + break; + case "blake3": + N = (C, D) => D === 0 && b(C); + break; + case "xxhash64": + // cannot simplify + case "xxhash3": + case "xxhash128": + case "crc64": + N = () => !1; + break; + } + const K = (C, D = null, G = null) => { + if (!N(C, D)) + return c(D), I(C), B("hex", G); + const v = ue(C); + return r.set(v), n.exports.Hash_Calculate(v.length, D, G), zt(m, r, t); + }; + return yield g(), { + getMemory: o, + writeMemory: a, + getExports: s, + setMemorySize: l, + init: c, + update: I, + digest: B, + save: S, + load: j, + calculate: K, + hashLength: t + }; + }); +} +new V(); +var Vi = "argon2", zi = "AGFzbQEAAAABKQVgAX8Bf2AAAX9gEH9/f39/f39/f39/f39/f38AYAR/f39/AGACf38AAwYFAAECAwQFBgEBAoCAAgYIAX8BQZCoBAsHQQQGbWVtb3J5AgASSGFzaF9TZXRNZW1vcnlTaXplAAAOSGFzaF9HZXRCdWZmZXIAAQ5IYXNoX0NhbGN1bGF0ZQAECvEyBVgBAn9BACEBAkAgAEEAKAKICCICRg0AAkAgACACayIAQRB2IABBgIB8cSAASWoiAEAAQX9HDQBB/wHADwtBACEBQQBBACkDiAggAEEQdK18NwOICAsgAcALcAECfwJAQQAoAoAIIgANAEEAPwBBEHQiADYCgAhBACgCiAgiAUGAgCBGDQACQEGAgCAgAWsiAEEQdiAAQYCAfHEgAElqIgBAAEF/Rw0AQQAPC0EAQQApA4gIIABBEHStfDcDiAhBACgCgAghAAsgAAvcDgECfiAAIAQpAwAiECAAKQMAIhF8IBFCAYZC/v///x+DIBBC/////w+DfnwiEDcDACAMIBAgDCkDAIVCIIkiEDcDACAIIBAgCCkDACIRfCARQgGGQv7///8fgyAQQv////8Pg358IhA3AwAgBCAQIAQpAwCFQiiJIhA3AwAgACAQIAApAwAiEXwgEEL/////D4MgEUIBhkL+////H4N+fCIQNwMAIAwgECAMKQMAhUIwiSIQNwMAIAggECAIKQMAIhF8IBBC/////w+DIBFCAYZC/v///x+DfnwiEDcDACAEIBAgBCkDAIVCAYk3AwAgASAFKQMAIhAgASkDACIRfCARQgGGQv7///8fgyAQQv////8Pg358IhA3AwAgDSAQIA0pAwCFQiCJIhA3AwAgCSAQIAkpAwAiEXwgEUIBhkL+////H4MgEEL/////D4N+fCIQNwMAIAUgECAFKQMAhUIoiSIQNwMAIAEgECABKQMAIhF8IBBC/////w+DIBFCAYZC/v///x+DfnwiEDcDACANIBAgDSkDAIVCMIkiEDcDACAJIBAgCSkDACIRfCAQQv////8PgyARQgGGQv7///8fg358IhA3AwAgBSAQIAUpAwCFQgGJNwMAIAIgBikDACIQIAIpAwAiEXwgEUIBhkL+////H4MgEEL/////D4N+fCIQNwMAIA4gECAOKQMAhUIgiSIQNwMAIAogECAKKQMAIhF8IBFCAYZC/v///x+DIBBC/////w+DfnwiEDcDACAGIBAgBikDAIVCKIkiEDcDACACIBAgAikDACIRfCAQQv////8PgyARQgGGQv7///8fg358IhA3AwAgDiAQIA4pAwCFQjCJIhA3AwAgCiAQIAopAwAiEXwgEEL/////D4MgEUIBhkL+////H4N+fCIQNwMAIAYgECAGKQMAhUIBiTcDACADIAcpAwAiECADKQMAIhF8IBFCAYZC/v///x+DIBBC/////w+DfnwiEDcDACAPIBAgDykDAIVCIIkiEDcDACALIBAgCykDACIRfCARQgGGQv7///8fgyAQQv////8Pg358IhA3AwAgByAQIAcpAwCFQiiJIhA3AwAgAyAQIAMpAwAiEXwgEEL/////D4MgEUIBhkL+////H4N+fCIQNwMAIA8gECAPKQMAhUIwiSIQNwMAIAsgECALKQMAIhF8IBBC/////w+DIBFCAYZC/v///x+DfnwiEDcDACAHIBAgBykDAIVCAYk3AwAgACAFKQMAIhAgACkDACIRfCARQgGGQv7///8fgyAQQv////8Pg358IhA3AwAgDyAQIA8pAwCFQiCJIhA3AwAgCiAQIAopAwAiEXwgEUIBhkL+////H4MgEEL/////D4N+fCIQNwMAIAUgECAFKQMAhUIoiSIQNwMAIAAgECAAKQMAIhF8IBBC/////w+DIBFCAYZC/v///x+DfnwiEDcDACAPIBAgDykDAIVCMIkiEDcDACAKIBAgCikDACIRfCAQQv////8PgyARQgGGQv7///8fg358IhA3AwAgBSAQIAUpAwCFQgGJNwMAIAEgBikDACIQIAEpAwAiEXwgEUIBhkL+////H4MgEEL/////D4N+fCIQNwMAIAwgECAMKQMAhUIgiSIQNwMAIAsgECALKQMAIhF8IBFCAYZC/v///x+DIBBC/////w+DfnwiEDcDACAGIBAgBikDAIVCKIkiEDcDACABIBAgASkDACIRfCAQQv////8PgyARQgGGQv7///8fg358IhA3AwAgDCAQIAwpAwCFQjCJIhA3AwAgCyAQIAspAwAiEXwgEEL/////D4MgEUIBhkL+////H4N+fCIQNwMAIAYgECAGKQMAhUIBiTcDACACIAcpAwAiECACKQMAIhF8IBFCAYZC/v///x+DIBBC/////w+DfnwiEDcDACANIBAgDSkDAIVCIIkiEDcDACAIIBAgCCkDACIRfCARQgGGQv7///8fgyAQQv////8Pg358IhA3AwAgByAQIAcpAwCFQiiJIhA3AwAgAiAQIAIpAwAiEXwgEEL/////D4MgEUIBhkL+////H4N+fCIQNwMAIA0gECANKQMAhUIwiSIQNwMAIAggECAIKQMAIhF8IBBC/////w+DIBFCAYZC/v///x+DfnwiEDcDACAHIBAgBykDAIVCAYk3AwAgAyAEKQMAIhAgAykDACIRfCARQgGGQv7///8fgyAQQv////8Pg358IhA3AwAgDiAQIA4pAwCFQiCJIhA3AwAgCSAQIAkpAwAiEXwgEUIBhkL+////H4MgEEL/////D4N+fCIQNwMAIAQgECAEKQMAhUIoiSIQNwMAIAMgECADKQMAIhF8IBBC/////w+DIBFCAYZC/v///x+DfnwiEDcDACAOIBAgDikDAIVCMIkiEDcDACAJIBAgCSkDACIRfCAQQv////8PgyARQgGGQv7///8fg358IhA3AwAgBCAQIAQpAwCFQgGJNwMAC98aAQN/QQAhBEEAIAIpAwAgASkDAIU3A5AIQQAgAikDCCABKQMIhTcDmAhBACACKQMQIAEpAxCFNwOgCEEAIAIpAxggASkDGIU3A6gIQQAgAikDICABKQMghTcDsAhBACACKQMoIAEpAyiFNwO4CEEAIAIpAzAgASkDMIU3A8AIQQAgAikDOCABKQM4hTcDyAhBACACKQNAIAEpA0CFNwPQCEEAIAIpA0ggASkDSIU3A9gIQQAgAikDUCABKQNQhTcD4AhBACACKQNYIAEpA1iFNwPoCEEAIAIpA2AgASkDYIU3A/AIQQAgAikDaCABKQNohTcD+AhBACACKQNwIAEpA3CFNwOACUEAIAIpA3ggASkDeIU3A4gJQQAgAikDgAEgASkDgAGFNwOQCUEAIAIpA4gBIAEpA4gBhTcDmAlBACACKQOQASABKQOQAYU3A6AJQQAgAikDmAEgASkDmAGFNwOoCUEAIAIpA6ABIAEpA6ABhTcDsAlBACACKQOoASABKQOoAYU3A7gJQQAgAikDsAEgASkDsAGFNwPACUEAIAIpA7gBIAEpA7gBhTcDyAlBACACKQPAASABKQPAAYU3A9AJQQAgAikDyAEgASkDyAGFNwPYCUEAIAIpA9ABIAEpA9ABhTcD4AlBACACKQPYASABKQPYAYU3A+gJQQAgAikD4AEgASkD4AGFNwPwCUEAIAIpA+gBIAEpA+gBhTcD+AlBACACKQPwASABKQPwAYU3A4AKQQAgAikD+AEgASkD+AGFNwOICkEAIAIpA4ACIAEpA4AChTcDkApBACACKQOIAiABKQOIAoU3A5gKQQAgAikDkAIgASkDkAKFNwOgCkEAIAIpA5gCIAEpA5gChTcDqApBACACKQOgAiABKQOgAoU3A7AKQQAgAikDqAIgASkDqAKFNwO4CkEAIAIpA7ACIAEpA7AChTcDwApBACACKQO4AiABKQO4AoU3A8gKQQAgAikDwAIgASkDwAKFNwPQCkEAIAIpA8gCIAEpA8gChTcD2ApBACACKQPQAiABKQPQAoU3A+AKQQAgAikD2AIgASkD2AKFNwPoCkEAIAIpA+ACIAEpA+AChTcD8ApBACACKQPoAiABKQPoAoU3A/gKQQAgAikD8AIgASkD8AKFNwOAC0EAIAIpA/gCIAEpA/gChTcDiAtBACACKQOAAyABKQOAA4U3A5ALQQAgAikDiAMgASkDiAOFNwOYC0EAIAIpA5ADIAEpA5ADhTcDoAtBACACKQOYAyABKQOYA4U3A6gLQQAgAikDoAMgASkDoAOFNwOwC0EAIAIpA6gDIAEpA6gDhTcDuAtBACACKQOwAyABKQOwA4U3A8ALQQAgAikDuAMgASkDuAOFNwPIC0EAIAIpA8ADIAEpA8ADhTcD0AtBACACKQPIAyABKQPIA4U3A9gLQQAgAikD0AMgASkD0AOFNwPgC0EAIAIpA9gDIAEpA9gDhTcD6AtBACACKQPgAyABKQPgA4U3A/ALQQAgAikD6AMgASkD6AOFNwP4C0EAIAIpA/ADIAEpA/ADhTcDgAxBACACKQP4AyABKQP4A4U3A4gMQQAgAikDgAQgASkDgASFNwOQDEEAIAIpA4gEIAEpA4gEhTcDmAxBACACKQOQBCABKQOQBIU3A6AMQQAgAikDmAQgASkDmASFNwOoDEEAIAIpA6AEIAEpA6AEhTcDsAxBACACKQOoBCABKQOoBIU3A7gMQQAgAikDsAQgASkDsASFNwPADEEAIAIpA7gEIAEpA7gEhTcDyAxBACACKQPABCABKQPABIU3A9AMQQAgAikDyAQgASkDyASFNwPYDEEAIAIpA9AEIAEpA9AEhTcD4AxBACACKQPYBCABKQPYBIU3A+gMQQAgAikD4AQgASkD4ASFNwPwDEEAIAIpA+gEIAEpA+gEhTcD+AxBACACKQPwBCABKQPwBIU3A4ANQQAgAikD+AQgASkD+ASFNwOIDUEAIAIpA4AFIAEpA4AFhTcDkA1BACACKQOIBSABKQOIBYU3A5gNQQAgAikDkAUgASkDkAWFNwOgDUEAIAIpA5gFIAEpA5gFhTcDqA1BACACKQOgBSABKQOgBYU3A7ANQQAgAikDqAUgASkDqAWFNwO4DUEAIAIpA7AFIAEpA7AFhTcDwA1BACACKQO4BSABKQO4BYU3A8gNQQAgAikDwAUgASkDwAWFNwPQDUEAIAIpA8gFIAEpA8gFhTcD2A1BACACKQPQBSABKQPQBYU3A+ANQQAgAikD2AUgASkD2AWFNwPoDUEAIAIpA+AFIAEpA+AFhTcD8A1BACACKQPoBSABKQPoBYU3A/gNQQAgAikD8AUgASkD8AWFNwOADkEAIAIpA/gFIAEpA/gFhTcDiA5BACACKQOABiABKQOABoU3A5AOQQAgAikDiAYgASkDiAaFNwOYDkEAIAIpA5AGIAEpA5AGhTcDoA5BACACKQOYBiABKQOYBoU3A6gOQQAgAikDoAYgASkDoAaFNwOwDkEAIAIpA6gGIAEpA6gGhTcDuA5BACACKQOwBiABKQOwBoU3A8AOQQAgAikDuAYgASkDuAaFNwPIDkEAIAIpA8AGIAEpA8AGhTcD0A5BACACKQPIBiABKQPIBoU3A9gOQQAgAikD0AYgASkD0AaFNwPgDkEAIAIpA9gGIAEpA9gGhTcD6A5BACACKQPgBiABKQPgBoU3A/AOQQAgAikD6AYgASkD6AaFNwP4DkEAIAIpA/AGIAEpA/AGhTcDgA9BACACKQP4BiABKQP4BoU3A4gPQQAgAikDgAcgASkDgAeFNwOQD0EAIAIpA4gHIAEpA4gHhTcDmA9BACACKQOQByABKQOQB4U3A6APQQAgAikDmAcgASkDmAeFNwOoD0EAIAIpA6AHIAEpA6AHhTcDsA9BACACKQOoByABKQOoB4U3A7gPQQAgAikDsAcgASkDsAeFNwPAD0EAIAIpA7gHIAEpA7gHhTcDyA9BACACKQPAByABKQPAB4U3A9APQQAgAikDyAcgASkDyAeFNwPYD0EAIAIpA9AHIAEpA9AHhTcD4A9BACACKQPYByABKQPYB4U3A+gPQQAgAikD4AcgASkD4AeFNwPwD0EAIAIpA+gHIAEpA+gHhTcD+A9BACACKQPwByABKQPwB4U3A4AQQQAgAikD+AcgASkD+AeFNwOIEEGQCEGYCEGgCEGoCEGwCEG4CEHACEHICEHQCEHYCEHgCEHoCEHwCEH4CEGACUGICRACQZAJQZgJQaAJQagJQbAJQbgJQcAJQcgJQdAJQdgJQeAJQegJQfAJQfgJQYAKQYgKEAJBkApBmApBoApBqApBsApBuApBwApByApB0ApB2ApB4ApB6ApB8ApB+ApBgAtBiAsQAkGQC0GYC0GgC0GoC0GwC0G4C0HAC0HIC0HQC0HYC0HgC0HoC0HwC0H4C0GADEGIDBACQZAMQZgMQaAMQagMQbAMQbgMQcAMQcgMQdAMQdgMQeAMQegMQfAMQfgMQYANQYgNEAJBkA1BmA1BoA1BqA1BsA1BuA1BwA1ByA1B0A1B2A1B4A1B6A1B8A1B+A1BgA5BiA4QAkGQDkGYDkGgDkGoDkGwDkG4DkHADkHIDkHQDkHYDkHgDkHoDkHwDkH4DkGAD0GIDxACQZAPQZgPQaAPQagPQbAPQbgPQcAPQcgPQdAPQdgPQeAPQegPQfAPQfgPQYAQQYgQEAJBkAhBmAhBkAlBmAlBkApBmApBkAtBmAtBkAxBmAxBkA1BmA1BkA5BmA5BkA9BmA8QAkGgCEGoCEGgCUGoCUGgCkGoCkGgC0GoC0GgDEGoDEGgDUGoDUGgDkGoDkGgD0GoDxACQbAIQbgIQbAJQbgJQbAKQbgKQbALQbgLQbAMQbgMQbANQbgNQbAOQbgOQbAPQbgPEAJBwAhByAhBwAlByAlBwApByApBwAtByAtBwAxByAxBwA1ByA1BwA5ByA5BwA9ByA8QAkHQCEHYCEHQCUHYCUHQCkHYCkHQC0HYC0HQDEHYDEHQDUHYDUHQDkHYDkHQD0HYDxACQeAIQegIQeAJQegJQeAKQegKQeALQegLQeAMQegMQeANQegNQeAOQegOQeAPQegPEAJB8AhB+AhB8AlB+AlB8ApB+ApB8AtB+AtB8AxB+AxB8A1B+A1B8A5B+A5B8A9B+A8QAkGACUGICUGACkGICkGAC0GIC0GADEGIDEGADUGIDUGADkGIDkGAD0GID0GAEEGIEBACAkACQCADRQ0AA0AgACAEaiIDIAIgBGoiBSkDACABIARqIgYpAwCFIARBkAhqKQMAhSADKQMAhTcDACADQQhqIgMgBUEIaikDACAGQQhqKQMAhSAEQZgIaikDAIUgAykDAIU3AwAgBEEQaiIEQYAIRw0ADAILC0EAIQQDQCAAIARqIgMgAiAEaiIFKQMAIAEgBGoiBikDAIUgBEGQCGopAwCFNwMAIANBCGogBUEIaikDACAGQQhqKQMAhSAEQZgIaikDAIU3AwAgBEEQaiIEQYAIRw0ACwsL5QcMBX8BfgR/An4BfwF+AX8Bfgd/AX4DfwF+AkBBACgCgAgiAiABQQp0aiIDKAIIIAFHDQAgAygCDCEEIAMoAgAhBUEAIAMoAhQiBq03A7gQQQAgBK0iBzcDsBBBACAFIAEgBUECdG4iCGwiCUECdK03A6gQAkACQAJAAkAgBEUNAEF/IQogBUUNASAIQQNsIQsgCEECdCIErSEMIAWtIQ0gBkF/akECSSEOQgAhDwNAQQAgDzcDkBAgD6chEEIAIRFBACEBA0BBACARNwOgECAPIBGEUCIDIA5xIRIgBkEBRiAPUCITIAZBAkYgEUICVHFxciEUQX8gAUEBakEDcSAIbEF/aiATGyEVIAEgEHIhFiABIAhsIRcgA0EBdCEYQgAhGQNAQQBCADcDwBBBACAZNwOYECAYIQECQCASRQ0AQQBCATcDwBBBkBhBkBBBkCBBABADQZAYQZAYQZAgQQAQA0ECIQELAkAgASAITw0AIAQgGaciGmwgF2ogAWohAwNAIANBACAEIAEbQQAgEVAiGxtqQX9qIRwCQAJAIBQNAEEAKAKACCICIBxBCnQiHGohCgwBCwJAIAFB/wBxIgINAEEAQQApA8AQQgF8NwPAEEGQGEGQEEGQIEEAEANBkBhBkBhBkCBBABADCyAcQQp0IRwgAkEDdEGQGGohCkEAKAKACCECCyACIANBCnRqIAIgHGogAiAKKQMAIh1CIIinIAVwIBogFhsiHCAEbCABIAFBACAZIBytUSIcGyIKIBsbIBdqIAogC2ogExsgAUUgHHJrIhsgFWqtIB1C/////w+DIh0gHX5CIIggG61+QiCIfSAMgqdqQQp0akEBEAMgA0EBaiEDIAggAUEBaiIBRw0ACwsgGUIBfCIZIA1SDQALIBFCAXwiEachASARQgRSDQALIA9CAXwiDyAHUg0AC0EAKAKACCECCyAJQQx0QYB4aiEXIAVBf2oiCkUNAgwBC0EAQgM3A6AQQQAgBEF/aq03A5AQQYB4IRcLIAIgF2ohGyAIQQx0IQhBACEcA0AgCCAcQQFqIhxsQYB4aiEEQQAhAQNAIBsgAWoiAyADKQMAIAIgBCABamopAwCFNwMAIANBCGoiAyADKQMAIAIgBCABQQhyamopAwCFNwMAIAFBCGohAyABQRBqIQEgA0H4B0kNAAsgHCAKRw0ACwsgAiAXaiEbQXghAQNAIAIgAWoiA0EIaiAbIAFqIgRBCGopAwA3AwAgA0EQaiAEQRBqKQMANwMAIANBGGogBEEYaikDADcDACADQSBqIARBIGopAwA3AwAgAUEgaiIBQfgHSQ0ACwsL", Ji = "e4cdc523", Yi = { + name: Vi, + data: zi, + hash: Ji +}, Mi = "blake2b", qi = "AGFzbQEAAAABEQRgAAF/YAJ/fwBgAX8AYAAAAwoJAAECAwECAgABBQQBAQICBg4CfwFBsIsFC38AQYAICwdwCAZtZW1vcnkCAA5IYXNoX0dldEJ1ZmZlcgAACkhhc2hfRmluYWwAAwlIYXNoX0luaXQABQtIYXNoX1VwZGF0ZQAGDUhhc2hfR2V0U3RhdGUABw5IYXNoX0NhbGN1bGF0ZQAIClNUQVRFX1NJWkUDAQrTOAkFAEGACQvrAgIFfwF+AkAgAUEBSA0AAkACQAJAIAFBgAFBACgC4IoBIgJrIgNKDQAgASEEDAELQQBBADYC4IoBAkAgAkH/AEoNACACQeCJAWohBSAAIQRBACEGA0AgBSAELQAAOgAAIARBAWohBCAFQQFqIQUgAyAGQQFqIgZB/wFxSg0ACwtBAEEAKQPAiQEiB0KAAXw3A8CJAUEAQQApA8iJASAHQv9+Vq18NwPIiQFB4IkBEAIgACADaiEAAkAgASADayIEQYEBSA0AIAIgAWohBQNAQQBBACkDwIkBIgdCgAF8NwPAiQFBAEEAKQPIiQEgB0L/flatfDcDyIkBIAAQAiAAQYABaiEAIAVBgH9qIgVBgAJLDQALIAVBgH9qIQQMAQsgBEEATA0BC0EAIQUDQCAFQQAoAuCKAWpB4IkBaiAAIAVqLQAAOgAAIAQgBUEBaiIFQf8BcUoNAAsLQQBBACgC4IoBIARqNgLgigELC78uASR+QQBBACkD0IkBQQApA7CJASIBQQApA5CJAXwgACkDICICfCIDhULr+obav7X2wR+FQiCJIgRCq/DT9K/uvLc8fCIFIAGFQiiJIgYgA3wgACkDKCIBfCIHIASFQjCJIgggBXwiCSAGhUIBiSIKQQApA8iJAUEAKQOoiQEiBEEAKQOIiQF8IAApAxAiA3wiBYVCn9j52cKR2oKbf4VCIIkiC0K7zqqm2NDrs7t/fCIMIASFQiiJIg0gBXwgACkDGCIEfCIOfCAAKQNQIgV8Ig9BACkDwIkBQQApA6CJASIQQQApA4CJASIRfCAAKQMAIgZ8IhKFQtGFmu/6z5SH0QCFQiCJIhNCiJLznf/M+YTqAHwiFCAQhUIoiSIVIBJ8IAApAwgiEHwiFiAThUIwiSIXhUIgiSIYQQApA9iJAUEAKQO4iQEiE0EAKQOYiQF8IAApAzAiEnwiGYVC+cL4m5Gjs/DbAIVCIIkiGkLx7fT4paf9p6V/fCIbIBOFQiiJIhwgGXwgACkDOCITfCIZIBqFQjCJIhogG3wiG3wiHSAKhUIoiSIeIA98IAApA1giCnwiDyAYhUIwiSIYIB18Ih0gDiALhUIwiSIOIAx8Ih8gDYVCAYkiDCAWfCAAKQNAIgt8Ig0gGoVCIIkiFiAJfCIaIAyFQiiJIiAgDXwgACkDSCIJfCIhIBaFQjCJIhYgGyAchUIBiSIMIAd8IAApA2AiB3wiDSAOhUIgiSIOIBcgFHwiFHwiFyAMhUIoiSIbIA18IAApA2giDHwiHCAOhUIwiSIOIBd8IhcgG4VCAYkiGyAZIBQgFYVCAYkiFHwgACkDcCINfCIVIAiFQiCJIhkgH3wiHyAUhUIoiSIUIBV8IAApA3giCHwiFXwgDHwiIoVCIIkiI3wiJCAbhUIoiSIbICJ8IBJ8IiIgFyAYIBUgGYVCMIkiFSAffCIZIBSFQgGJIhQgIXwgDXwiH4VCIIkiGHwiFyAUhUIoiSIUIB98IAV8Ih8gGIVCMIkiGCAXfCIXIBSFQgGJIhR8IAF8IiEgFiAafCIWIBUgHSAehUIBiSIaIBx8IAl8IhyFQiCJIhV8Ih0gGoVCKIkiGiAcfCAIfCIcIBWFQjCJIhWFQiCJIh4gGSAOIBYgIIVCAYkiFiAPfCACfCIPhUIgiSIOfCIZIBaFQiiJIhYgD3wgC3wiDyAOhUIwiSIOIBl8Ihl8IiAgFIVCKIkiFCAhfCAEfCIhIB6FQjCJIh4gIHwiICAiICOFQjCJIiIgJHwiIyAbhUIBiSIbIBx8IAp8IhwgDoVCIIkiDiAXfCIXIBuFQiiJIhsgHHwgE3wiHCAOhUIwiSIOIBkgFoVCAYkiFiAffCAQfCIZICKFQiCJIh8gFSAdfCIVfCIdIBaFQiiJIhYgGXwgB3wiGSAfhUIwiSIfIB18Ih0gFoVCAYkiFiAVIBqFQgGJIhUgD3wgBnwiDyAYhUIgiSIYICN8IhogFYVCKIkiFSAPfCADfCIPfCAHfCIihUIgiSIjfCIkIBaFQiiJIhYgInwgBnwiIiAjhUIwiSIjICR8IiQgFoVCAYkiFiAOIBd8Ig4gDyAYhUIwiSIPICAgFIVCAYkiFCAZfCAKfCIXhUIgiSIYfCIZIBSFQiiJIhQgF3wgC3wiF3wgBXwiICAPIBp8Ig8gHyAOIBuFQgGJIg4gIXwgCHwiGoVCIIkiG3wiHyAOhUIoiSIOIBp8IAx8IhogG4VCMIkiG4VCIIkiISAdIB4gDyAVhUIBiSIPIBx8IAF8IhWFQiCJIhx8Ih0gD4VCKIkiDyAVfCADfCIVIByFQjCJIhwgHXwiHXwiHiAWhUIoiSIWICB8IA18IiAgIYVCMIkiISAefCIeIBogFyAYhUIwiSIXIBl8IhggFIVCAYkiFHwgCXwiGSAchUIgiSIaICR8IhwgFIVCKIkiFCAZfCACfCIZIBqFQjCJIhogHSAPhUIBiSIPICJ8IAR8Ih0gF4VCIIkiFyAbIB98Iht8Ih8gD4VCKIkiDyAdfCASfCIdIBeFQjCJIhcgH3wiHyAPhUIBiSIPIBsgDoVCAYkiDiAVfCATfCIVICOFQiCJIhsgGHwiGCAOhUIoiSIOIBV8IBB8IhV8IAx8IiKFQiCJIiN8IiQgD4VCKIkiDyAifCAHfCIiICOFQjCJIiMgJHwiJCAPhUIBiSIPIBogHHwiGiAVIBuFQjCJIhUgHiAWhUIBiSIWIB18IAR8IhuFQiCJIhx8Ih0gFoVCKIkiFiAbfCAQfCIbfCABfCIeIBUgGHwiFSAXIBogFIVCAYkiFCAgfCATfCIYhUIgiSIXfCIaIBSFQiiJIhQgGHwgCXwiGCAXhUIwiSIXhUIgiSIgIB8gISAVIA6FQgGJIg4gGXwgCnwiFYVCIIkiGXwiHyAOhUIoiSIOIBV8IA18IhUgGYVCMIkiGSAffCIffCIhIA+FQiiJIg8gHnwgBXwiHiAghUIwiSIgICF8IiEgGyAchUIwiSIbIB18IhwgFoVCAYkiFiAYfCADfCIYIBmFQiCJIhkgJHwiHSAWhUIoiSIWIBh8IBJ8IhggGYVCMIkiGSAfIA6FQgGJIg4gInwgAnwiHyAbhUIgiSIbIBcgGnwiF3wiGiAOhUIoiSIOIB98IAZ8Ih8gG4VCMIkiGyAafCIaIA6FQgGJIg4gFSAXIBSFQgGJIhR8IAh8IhUgI4VCIIkiFyAcfCIcIBSFQiiJIhQgFXwgC3wiFXwgBXwiIoVCIIkiI3wiJCAOhUIoiSIOICJ8IAh8IiIgGiAgIBUgF4VCMIkiFSAcfCIXIBSFQgGJIhQgGHwgCXwiGIVCIIkiHHwiGiAUhUIoiSIUIBh8IAZ8IhggHIVCMIkiHCAafCIaIBSFQgGJIhR8IAR8IiAgGSAdfCIZIBUgISAPhUIBiSIPIB98IAN8Ih2FQiCJIhV8Ih8gD4VCKIkiDyAdfCACfCIdIBWFQjCJIhWFQiCJIiEgFyAbIBkgFoVCAYkiFiAefCABfCIZhUIgiSIbfCIXIBaFQiiJIhYgGXwgE3wiGSAbhUIwiSIbIBd8Ihd8Ih4gFIVCKIkiFCAgfCAMfCIgICGFQjCJIiEgHnwiHiAiICOFQjCJIiIgJHwiIyAOhUIBiSIOIB18IBJ8Ih0gG4VCIIkiGyAafCIaIA6FQiiJIg4gHXwgC3wiHSAbhUIwiSIbIBcgFoVCAYkiFiAYfCANfCIXICKFQiCJIhggFSAffCIVfCIfIBaFQiiJIhYgF3wgEHwiFyAYhUIwiSIYIB98Ih8gFoVCAYkiFiAVIA+FQgGJIg8gGXwgCnwiFSAchUIgiSIZICN8IhwgD4VCKIkiDyAVfCAHfCIVfCASfCIihUIgiSIjfCIkIBaFQiiJIhYgInwgBXwiIiAjhUIwiSIjICR8IiQgFoVCAYkiFiAbIBp8IhogFSAZhUIwiSIVIB4gFIVCAYkiFCAXfCADfCIXhUIgiSIZfCIbIBSFQiiJIhQgF3wgB3wiF3wgAnwiHiAVIBx8IhUgGCAaIA6FQgGJIg4gIHwgC3wiGoVCIIkiGHwiHCAOhUIoiSIOIBp8IAR8IhogGIVCMIkiGIVCIIkiICAfICEgFSAPhUIBiSIPIB18IAZ8IhWFQiCJIh18Ih8gD4VCKIkiDyAVfCAKfCIVIB2FQjCJIh0gH3wiH3wiISAWhUIoiSIWIB58IAx8Ih4gIIVCMIkiICAhfCIhIBogFyAZhUIwiSIXIBt8IhkgFIVCAYkiFHwgEHwiGiAdhUIgiSIbICR8Ih0gFIVCKIkiFCAafCAJfCIaIBuFQjCJIhsgHyAPhUIBiSIPICJ8IBN8Ih8gF4VCIIkiFyAYIBx8Ihh8IhwgD4VCKIkiDyAffCABfCIfIBeFQjCJIhcgHHwiHCAPhUIBiSIPIBggDoVCAYkiDiAVfCAIfCIVICOFQiCJIhggGXwiGSAOhUIoiSIOIBV8IA18IhV8IA18IiKFQiCJIiN8IiQgD4VCKIkiDyAifCAMfCIiICOFQjCJIiMgJHwiJCAPhUIBiSIPIBsgHXwiGyAVIBiFQjCJIhUgISAWhUIBiSIWIB98IBB8IhiFQiCJIh18Ih8gFoVCKIkiFiAYfCAIfCIYfCASfCIhIBUgGXwiFSAXIBsgFIVCAYkiFCAefCAHfCIZhUIgiSIXfCIbIBSFQiiJIhQgGXwgAXwiGSAXhUIwiSIXhUIgiSIeIBwgICAVIA6FQgGJIg4gGnwgAnwiFYVCIIkiGnwiHCAOhUIoiSIOIBV8IAV8IhUgGoVCMIkiGiAcfCIcfCIgIA+FQiiJIg8gIXwgBHwiISAehUIwiSIeICB8IiAgGCAdhUIwiSIYIB98Ih0gFoVCAYkiFiAZfCAGfCIZIBqFQiCJIhogJHwiHyAWhUIoiSIWIBl8IBN8IhkgGoVCMIkiGiAcIA6FQgGJIg4gInwgCXwiHCAYhUIgiSIYIBcgG3wiF3wiGyAOhUIoiSIOIBx8IAN8IhwgGIVCMIkiGCAbfCIbIA6FQgGJIg4gFSAXIBSFQgGJIhR8IAt8IhUgI4VCIIkiFyAdfCIdIBSFQiiJIhQgFXwgCnwiFXwgBHwiIoVCIIkiI3wiJCAOhUIoiSIOICJ8IAl8IiIgGyAeIBUgF4VCMIkiFSAdfCIXIBSFQgGJIhQgGXwgDHwiGYVCIIkiHXwiGyAUhUIoiSIUIBl8IAp8IhkgHYVCMIkiHSAbfCIbIBSFQgGJIhR8IAN8Ih4gGiAffCIaIBUgICAPhUIBiSIPIBx8IAd8IhyFQiCJIhV8Ih8gD4VCKIkiDyAcfCAQfCIcIBWFQjCJIhWFQiCJIiAgFyAYIBogFoVCAYkiFiAhfCATfCIahUIgiSIYfCIXIBaFQiiJIhYgGnwgDXwiGiAYhUIwiSIYIBd8Ihd8IiEgFIVCKIkiFCAefCAFfCIeICCFQjCJIiAgIXwiISAiICOFQjCJIiIgJHwiIyAOhUIBiSIOIBx8IAt8IhwgGIVCIIkiGCAbfCIbIA6FQiiJIg4gHHwgEnwiHCAYhUIwiSIYIBcgFoVCAYkiFiAZfCABfCIXICKFQiCJIhkgFSAffCIVfCIfIBaFQiiJIhYgF3wgBnwiFyAZhUIwiSIZIB98Ih8gFoVCAYkiFiAVIA+FQgGJIg8gGnwgCHwiFSAdhUIgiSIaICN8Ih0gD4VCKIkiDyAVfCACfCIVfCANfCIihUIgiSIjfCIkIBaFQiiJIhYgInwgCXwiIiAjhUIwiSIjICR8IiQgFoVCAYkiFiAYIBt8IhggFSAahUIwiSIVICEgFIVCAYkiFCAXfCASfCIXhUIgiSIafCIbIBSFQiiJIhQgF3wgCHwiF3wgB3wiISAVIB18IhUgGSAYIA6FQgGJIg4gHnwgBnwiGIVCIIkiGXwiHSAOhUIoiSIOIBh8IAt8IhggGYVCMIkiGYVCIIkiHiAfICAgFSAPhUIBiSIPIBx8IAp8IhWFQiCJIhx8Ih8gD4VCKIkiDyAVfCAEfCIVIByFQjCJIhwgH3wiH3wiICAWhUIoiSIWICF8IAN8IiEgHoVCMIkiHiAgfCIgIBggFyAahUIwiSIXIBt8IhogFIVCAYkiFHwgBXwiGCAchUIgiSIbICR8IhwgFIVCKIkiFCAYfCABfCIYIBuFQjCJIhsgHyAPhUIBiSIPICJ8IAx8Ih8gF4VCIIkiFyAZIB18Ihl8Ih0gD4VCKIkiDyAffCATfCIfIBeFQjCJIhcgHXwiHSAPhUIBiSIPIBkgDoVCAYkiDiAVfCAQfCIVICOFQiCJIhkgGnwiGiAOhUIoiSIOIBV8IAJ8IhV8IBN8IiKFQiCJIiN8IiQgD4VCKIkiDyAifCASfCIiICOFQjCJIiMgJHwiJCAPhUIBiSIPIBsgHHwiGyAVIBmFQjCJIhUgICAWhUIBiSIWIB98IAt8IhmFQiCJIhx8Ih8gFoVCKIkiFiAZfCACfCIZfCAJfCIgIBUgGnwiFSAXIBsgFIVCAYkiFCAhfCAFfCIahUIgiSIXfCIbIBSFQiiJIhQgGnwgA3wiGiAXhUIwiSIXhUIgiSIhIB0gHiAVIA6FQgGJIg4gGHwgEHwiFYVCIIkiGHwiHSAOhUIoiSIOIBV8IAF8IhUgGIVCMIkiGCAdfCIdfCIeIA+FQiiJIg8gIHwgDXwiICAhhUIwiSIhIB58Ih4gGSAchUIwiSIZIB98IhwgFoVCAYkiFiAafCAIfCIaIBiFQiCJIhggJHwiHyAWhUIoiSIWIBp8IAp8IhogGIVCMIkiGCAdIA6FQgGJIg4gInwgBHwiHSAZhUIgiSIZIBcgG3wiF3wiGyAOhUIoiSIOIB18IAd8Ih0gGYVCMIkiGSAbfCIbIA6FQgGJIg4gFSAXIBSFQgGJIhR8IAx8IhUgI4VCIIkiFyAcfCIcIBSFQiiJIhQgFXwgBnwiFXwgEnwiIoVCIIkiI3wiJCAOhUIoiSIOICJ8IBN8IiIgGyAhIBUgF4VCMIkiFSAcfCIXIBSFQgGJIhQgGnwgBnwiGoVCIIkiHHwiGyAUhUIoiSIUIBp8IBB8IhogHIVCMIkiHCAbfCIbIBSFQgGJIhR8IA18IiEgGCAffCIYIBUgHiAPhUIBiSIPIB18IAJ8Ih2FQiCJIhV8Ih4gD4VCKIkiDyAdfCABfCIdIBWFQjCJIhWFQiCJIh8gFyAZIBggFoVCAYkiFiAgfCADfCIYhUIgiSIZfCIXIBaFQiiJIhYgGHwgBHwiGCAZhUIwiSIZIBd8Ihd8IiAgFIVCKIkiFCAhfCAIfCIhIB+FQjCJIh8gIHwiICAiICOFQjCJIiIgJHwiIyAOhUIBiSIOIB18IAd8Ih0gGYVCIIkiGSAbfCIbIA6FQiiJIg4gHXwgDHwiHSAZhUIwiSIZIBcgFoVCAYkiFiAafCALfCIXICKFQiCJIhogFSAefCIVfCIeIBaFQiiJIhYgF3wgCXwiFyAahUIwiSIaIB58Ih4gFoVCAYkiFiAVIA+FQgGJIg8gGHwgBXwiFSAchUIgiSIYICN8IhwgD4VCKIkiDyAVfCAKfCIVfCACfCIChUIgiSIifCIjIBaFQiiJIhYgAnwgC3wiAiAihUIwiSILICN8IiIgFoVCAYkiFiAZIBt8IhkgFSAYhUIwiSIVICAgFIVCAYkiFCAXfCANfCINhUIgiSIXfCIYIBSFQiiJIhQgDXwgBXwiBXwgEHwiECAVIBx8Ig0gGiAZIA6FQgGJIg4gIXwgDHwiDIVCIIkiFXwiGSAOhUIoiSIOIAx8IBJ8IhIgFYVCMIkiDIVCIIkiFSAeIB8gDSAPhUIBiSINIB18IAl8IgmFQiCJIg98IhogDYVCKIkiDSAJfCAIfCIJIA+FQjCJIgggGnwiD3wiGiAWhUIoiSIWIBB8IAd8IhAgEYUgDCAZfCIHIA6FQgGJIgwgCXwgCnwiCiALhUIgiSILIAUgF4VCMIkiBSAYfCIJfCIOIAyFQiiJIgwgCnwgE3wiEyALhUIwiSIKIA58IguFNwOAiQFBACADIAYgDyANhUIBiSINIAJ8fCICIAWFQiCJIgUgB3wiBiANhUIoiSIHIAJ8fCICQQApA4iJAYUgBCABIBIgCSAUhUIBiSIDfHwiASAIhUIgiSISICJ8IgkgA4VCKIkiAyABfHwiASAShUIwiSIEIAl8IhKFNwOIiQFBACATQQApA5CJAYUgECAVhUIwiSIQIBp8IhOFNwOQiQFBACABQQApA5iJAYUgAiAFhUIwiSICIAZ8IgGFNwOYiQFBACASIAOFQgGJQQApA6CJAYUgAoU3A6CJAUEAIBMgFoVCAYlBACkDqIkBhSAKhTcDqIkBQQAgASAHhUIBiUEAKQOwiQGFIASFNwOwiQFBACALIAyFQgGJQQApA7iJAYUgEIU3A7iJAQvdAgUBfwF+AX8BfgJ/IwBBwABrIgAkAAJAQQApA9CJAUIAUg0AQQBBACkDwIkBIgFBACgC4IoBIgKsfCIDNwPAiQFBAEEAKQPIiQEgAyABVK18NwPIiQECQEEALQDoigFFDQBBAEJ/NwPYiQELQQBCfzcD0IkBAkAgAkH/AEoNAEEAIQQDQCACIARqQeCJAWpBADoAACAEQQFqIgRBgAFBACgC4IoBIgJrSA0ACwtB4IkBEAIgAEEAKQOAiQE3AwAgAEEAKQOIiQE3AwggAEEAKQOQiQE3AxAgAEEAKQOYiQE3AxggAEEAKQOgiQE3AyAgAEEAKQOoiQE3AyggAEEAKQOwiQE3AzAgAEEAKQO4iQE3AzhBACgC5IoBIgVBAUgNAEEAIQRBACECA0AgBEGACWogACAEai0AADoAACAEQQFqIQQgBSACQQFqIgJB/wFxSg0ACwsgAEHAAGokAAv9AwMBfwF+AX8jAEGAAWsiAiQAQQBBgQI7AfKKAUEAIAE6APGKAUEAIAA6APCKAUGQfiEAA0AgAEGAiwFqQgA3AAAgAEH4igFqQgA3AAAgAEHwigFqQgA3AAAgAEEYaiIADQALQQAhAEEAQQApA/CKASIDQoiS853/zPmE6gCFNwOAiQFBAEEAKQP4igFCu86qptjQ67O7f4U3A4iJAUEAQQApA4CLAUKr8NP0r+68tzyFNwOQiQFBAEEAKQOIiwFC8e30+KWn/aelf4U3A5iJAUEAQQApA5CLAULRhZrv+s+Uh9EAhTcDoIkBQQBBACkDmIsBQp/Y+dnCkdqCm3+FNwOoiQFBAEEAKQOgiwFC6/qG2r+19sEfhTcDsIkBQQBBACkDqIsBQvnC+JuRo7Pw2wCFNwO4iQFBACADp0H/AXE2AuSKAQJAIAFBAUgNACACQgA3A3ggAkIANwNwIAJCADcDaCACQgA3A2AgAkIANwNYIAJCADcDUCACQgA3A0ggAkIANwNAIAJCADcDOCACQgA3AzAgAkIANwMoIAJCADcDICACQgA3AxggAkIANwMQIAJCADcDCCACQgA3AwBBACEEA0AgAiAAaiAAQYAJai0AADoAACAAQQFqIQAgBEEBaiIEQf8BcSABSA0ACyACQYABEAELIAJBgAFqJAALEgAgAEEDdkH/P3EgAEEQdhAECwkAQYAJIAAQAQsGAEGAiQELGwAgAUEDdkH/P3EgAUEQdhAEQYAJIAAQARADCwsLAQBBgAgLBPAAAAA=", ji = "c6f286e6", Ri = { + name: Mi, + data: qi, + hash: ji +}; +new V(); +function Qr(e) { + return !Number.isInteger(e) || e < 8 || e > 512 || e % 8 !== 0 ? new Error("Invalid variant! Valid values: 8, 16, ..., 512") : null; +} +function _i(e, t) { + return e | t << 16; +} +function Jt(e = 512, t = null) { + if (Qr(e)) + return Promise.reject(Qr(e)); + let n = null, r = e; + if (t !== null) { + if (n = ue(t), n.length > 64) + return Promise.reject(new Error("Max key length is 64 bytes")); + r = _i(e, n.length); + } + const i = e / 8; + return bn(Ri, i).then((a) => { + r > 512 && a.writeMemory(n), a.init(r); + const o = { + init: r > 512 ? () => (a.writeMemory(n), a.init(r), o) : () => (a.init(r), o), + update: (s) => (a.update(s), o), + // biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type + digest: (s) => a.digest(s), + save: () => a.save(), + load: (s) => (a.load(s), o), + blockSize: 128, + digestSize: i + }; + return o; + }); +} +function Pi(e, t, n) { + const r = [ + `m=${t.memorySize}`, + `t=${t.iterations}`, + `p=${t.parallelism}` + ].join(","); + return `$argon2${t.hashType}$v=19$${r}$${Br(e, !1)}$${Br(n, !1)}`; +} +const Er = new DataView(new ArrayBuffer(4)); +function se(e) { + return Er.setInt32(0, e, !0), new Uint8Array(Er.buffer); +} +function Ft(e, t, n) { + return we(this, void 0, void 0, function* () { + if (n <= 64) { + const l = yield Jt(n * 8); + return l.update(se(n)), l.update(t), l.digest("binary"); + } + const r = Math.ceil(n / 32) - 2, i = new Uint8Array(n); + e.init(), e.update(se(n)), e.update(t); + let a = e.digest("binary"); + i.set(a.subarray(0, 32), 0); + for (let l = 1; l < r; l++) + e.init(), e.update(a), a = e.digest("binary"), i.set(a.subarray(0, 32), l * 32); + const o = n - 32 * r; + let s; + return o === 64 ? (s = e, s.init()) : s = yield Jt(o * 8), s.update(a), a = s.digest("binary"), i.set(a.subarray(0, o), r * 32), i; + }); +} +function Li(e) { + switch (e) { + case "d": + return 0; + case "i": + return 1; + default: + return 2; + } +} +function Ti(e) { + return we(this, void 0, void 0, function* () { + var t; + const { parallelism: n, iterations: r, hashLength: i } = e, a = ue(e.password), o = ue(e.salt), s = 19, l = Li(e.hashType), { memorySize: u } = e, h = ue((t = e.secret) !== null && t !== void 0 ? t : ""), [g, c] = yield Promise.all([ + bn(Yi, 1024), + Jt(512) + ]); + g.setMemorySize(u * 1024 + 1024); + const d = new Uint8Array(24), I = new DataView(d.buffer); + I.setInt32(0, n, !0), I.setInt32(4, i, !0), I.setInt32(8, u, !0), I.setInt32(12, r, !0), I.setInt32(16, s, !0), I.setInt32(20, l, !0), g.writeMemory(d, u * 1024), c.init(), c.update(d), c.update(se(a.length)), c.update(a), c.update(se(o.length)), c.update(o), c.update(se(h.length)), c.update(h), c.update(se(0)); + const B = Math.floor(u / (n * 4)) * 4, S = new Uint8Array(72), j = c.digest("binary"); + S.set(j); + for (let K = 0; K < n; K++) { + S.set(se(0), 64), S.set(se(K), 68); + let C = K * B, D = yield Ft(c, S, 1024); + g.writeMemory(D, C * 1024), C += 1, S.set(se(1), 64), D = yield Ft(c, S, 1024), g.writeMemory(D, C * 1024); + } + const b = new Uint8Array(1024); + yn(b, g.calculate(new Uint8Array([]), u)); + const N = yield Ft(c, b, i); + if (e.outputType === "hex") { + const K = new Uint8Array(i * 2); + return zt(K, N, i); + } + return e.outputType === "encoded" ? Pi(o, e, N) : N; + }); +} +const Xi = (e) => { + var t; + if (!e || typeof e != "object") + throw new Error("Invalid options parameter. It requires an object."); + if (!e.password) + throw new Error("Password must be specified"); + if (e.password = ue(e.password), e.password.length < 1) + throw new Error("Password must be specified"); + if (!e.salt) + throw new Error("Salt must be specified"); + if (e.salt = ue(e.salt), e.salt.length < 8) + throw new Error("Salt should be at least 8 bytes long"); + if (e.secret = ue((t = e.secret) !== null && t !== void 0 ? t : ""), !Number.isInteger(e.iterations) || e.iterations < 1) + throw new Error("Iterations should be a positive number"); + if (!Number.isInteger(e.parallelism) || e.parallelism < 1) + throw new Error("Parallelism should be a positive number"); + if (!Number.isInteger(e.hashLength) || e.hashLength < 4) + throw new Error("Hash length should be at least 4 bytes."); + if (!Number.isInteger(e.memorySize)) + throw new Error("Memory size should be specified."); + if (e.memorySize < 8 * e.parallelism) + throw new Error("Memory size should be at least 8 * parallelism."); + if (e.outputType === void 0 && (e.outputType = "hex"), !["hex", "binary", "encoded"].includes(e.outputType)) + throw new Error(`Insupported output type ${e.outputType}. Valid values: ['hex', 'binary', 'encoded']`); +}; +function Zi(e) { + return we(this, void 0, void 0, function* () { + return Xi(e), Ti(Object.assign(Object.assign({}, e), { hashType: "id" })); + }); +} +new V(); +new V(); +new V(); +new V(); +new V(); +new V(); +new V(); +new V(); +new V(); +new V(); +new V(); +new V(); +new V(); +new V(); +new V(); +new V(); +new V(); +new V(); +new V(); +new V(); +const We = { + id: "bitlogin-argon2id-v1", + memoryKiB: 64 * 1024, + iterations: 3, + parallelism: 1, + hashLength: 32 +}; +async function $i(e, t) { + if (t.length < 8) + throw new Error("Argon2id salt must be at least 8 bytes."); + const n = await Zi({ + password: e, + salt: t, + parallelism: We.parallelism, + iterations: We.iterations, + memorySize: We.memoryKiB, + hashLength: We.hashLength, + outputType: "binary" + }); + return n instanceof Uint8Array ? n : new Uint8Array(n); +} +function Wi(e) { + return new TextEncoder().encode(e.normalize("NFKC")); +} +class Oe extends Error { + constructor(t) { + super(t), this.name = "LoginNameError"; + } +} +const xr = 3, Dr = 32, e1 = /^[\x00-\x7F]*$/u, t1 = /^[a-z0-9._-]+$/u, r1 = /[._-]{2,}/u, Sr = /[a-z0-9]/u; +function n1(e) { + const t = e.trim().toLowerCase(); + if (!e1.test(t)) + throw new Oe("Login name must contain ASCII characters only."); + if (t.length < xr || t.length > Dr) + throw new Oe(`Login name must be between ${xr} and ${Dr} characters.`); + if (!t1.test(t)) + throw new Oe("Login name may only contain a-z, 0-9, '.', '_', and '-'."); + const n = t[0], r = t[t.length - 1]; + if (!Sr.test(n) || !Sr.test(r)) + throw new Oe("Login name must not begin or end with punctuation."); + if (r1.test(t)) + throw new Oe("Login name must not contain consecutive punctuation."); + return t; +} +function G1(e) { + try { + return n1(e), !0; + } catch { + return !1; + } +} +function i1(e) { + return fe(qr(De("bitlogin/password-salt/v1"), new Uint8Array([0]), De(e))).slice(0, 16); +} +async function H1(e, t) { + const n = i1(t), r = Wi(e), i = await $i(r, n), a = jr(Rr("bitlogin/password-root/v1"), i), o = In(a, "bitlogin/password-locator-signing/v1"), s = Rt(a, "bitlogin/password-capsule-encryption/v1", 32); + return { locatorPrivateKey: o.scalar, capsuleKey: s }; +} +function N1(e) { + const t = jr(Rr("bitlogin/recovery-root/v1"), e), n = In(t, "bitlogin/recovery-signing/v1"), r = Rt(t, "bitlogin/recovery-capsule-encryption/v1", 32); + return { recoveryPrivateKey: n.scalar, capsuleKey: r }; +} +const a1 = mt.CURVE.n; +function o1(e) { + if (e.length !== 32) + return !1; + let t = 0n; + for (const n of e) + t = t << 8n | BigInt(n); + return t >= 1n && t < a1; +} +function K1() { + for (; ; ) { + const e = qe(32); + if (o1(e)) + return e; + } +} +function s1(e) { + return Zt.getPublicKey(e); +} +function O1(e) { + return jt(s1(e)); +} +function l1(e, t) { + return Zt.sign(e, t); +} +function c1(e, t, n) { + try { + return Zt.verify(e, t, n); + } catch { + return !1; + } +} +function u1(e) { + return JSON.stringify([0, e.pubkey, e.created_at, e.kind, e.tags, e.content]); +} +function wn(e) { + return jt(fe(De(u1(e)))); +} +function V1(e, t) { + const n = wn(e), r = jt(l1(Je(n), t)); + return { ...e, id: n, sig: r }; +} +function Fr(e) { + if (!/^[0-9a-f]{64}$/u.test(e.id) || !/^[0-9a-f]{64}$/u.test(e.pubkey) || !/^[0-9a-f]{128}$/u.test(e.sig) || wn(e) !== e.id) + return !1; + try { + return c1(Je(e.sig), Je(e.id), Je(e.pubkey)); + } catch { + return !1; + } +} +const z1 = 0, J1 = 5, Y1 = 10002, M1 = 10050, q1 = 22242, j1 = 30078, R1 = "bitlogin:password:v1", _1 = "bitlogin:recovery:v1", P1 = "bitlogin.credential.v1", L1 = "bitlogin.recovery.v1", Cn = "bitlogin.recovery-export.v3"; +var Ut, Ur; +function d1() { + return Ur || (Ur = 1, Ut = function e(t) { + if (typeof t == "number" && isNaN(t)) + throw new Error("NaN is not allowed"); + if (typeof t == "number" && !isFinite(t)) + throw new Error("Infinity is not allowed"); + return t === null || typeof t != "object" ? JSON.stringify(t) : t.toJSON instanceof Function ? e(t.toJSON()) : Array.isArray(t) ? `[${t.reduce((i, a, o) => `${i}${o === 0 ? "" : ","}${e(a === void 0 || typeof a == "symbol" ? null : a)}`, "")}]` : `{${Object.keys(t).sort().reduce((r, i) => { + if (t[i] === void 0 || typeof t[i] == "symbol") + return r; + const a = r.length === 0 ? "" : ","; + return `${r}${a}${e(i)}:${e(t[i])}`; + }, "")}}`; + }), Ut; +} +var g1 = d1(); +const T1 = /* @__PURE__ */ Kr(g1); +/*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) */ +function Ue(e) { + return e instanceof Uint8Array || ArrayBuffer.isView(e) && e.constructor.name === "Uint8Array"; +} +function A1(e, ...t) { + if (!Ue(e)) + throw new Error("Uint8Array expected"); + if (t.length > 0 && !t.includes(e.length)) + throw new Error("Uint8Array expected of length " + t + ", got length=" + e.length); +} +function kn(e, t) { + return Array.isArray(t) ? t.length === 0 ? !0 : e ? t.every((n) => typeof n == "string") : t.every((n) => Number.isSafeInteger(n)) : !1; +} +function vn(e) { + if (typeof e != "function") + throw new Error("function expected"); + return !0; +} +function ke(e, t) { + if (typeof t != "string") + throw new Error(`${e}: string expected`); + return !0; +} +function Ne(e) { + if (!Number.isSafeInteger(e)) + throw new Error(`invalid integer: ${e}`); +} +function ct(e) { + if (!Array.isArray(e)) + throw new Error("array expected"); +} +function ut(e, t) { + if (!kn(!0, t)) + throw new Error(`${e}: array of strings expected`); +} +function $t(e, t) { + if (!kn(!1, t)) + throw new Error(`${e}: array of numbers expected`); +} +// @__NO_SIDE_EFFECTS__ +function Wt(...e) { + const t = (a) => a, n = (a, o) => (s) => a(o(s)), r = e.map((a) => a.encode).reduceRight(n, t), i = e.map((a) => a.decode).reduce(n, t); + return { encode: r, decode: i }; +} +// @__NO_SIDE_EFFECTS__ +function er(e) { + const t = typeof e == "string" ? e.split("") : e, n = t.length; + ut("alphabet", t); + const r = new Map(t.map((i, a) => [i, a])); + return { + encode: (i) => (ct(i), i.map((a) => { + if (!Number.isSafeInteger(a) || a < 0 || a >= n) + throw new Error(`alphabet.encode: digit index outside alphabet "${a}". Allowed: ${e}`); + return t[a]; + })), + decode: (i) => (ct(i), i.map((a) => { + ke("alphabet.decode", a); + const o = r.get(a); + if (o === void 0) + throw new Error(`Unknown letter: "${a}". Allowed: ${e}`); + return o; + })) + }; +} +// @__NO_SIDE_EFFECTS__ +function tr(e = "") { + return ke("join", e), { + encode: (t) => (ut("join.decode", t), t.join(e)), + decode: (t) => (ke("join.decode", t), t.split(e)) + }; +} +// @__NO_SIDE_EFFECTS__ +function Bn(e, t = "=") { + return Ne(e), ke("padding", t), { + encode(n) { + for (ut("padding.encode", n); n.length * e % 8; ) + n.push(t); + return n; + }, + decode(n) { + ut("padding.decode", n); + let r = n.length; + if (r * e % 8) + throw new Error("padding: invalid, string should have whole number of bytes"); + for (; r > 0 && n[r - 1] === t; r--) + if ((r - 1) * e % 8 === 0) + throw new Error("padding: invalid, string has too much padding"); + return n.slice(0, r); + } + }; +} +function Yt(e, t, n) { + if (t < 2) + throw new Error(`convertRadix: invalid from=${t}, base cannot be less than 2`); + if (n < 2) + throw new Error(`convertRadix: invalid to=${n}, base cannot be less than 2`); + if (ct(e), !e.length) + return []; + let r = 0; + const i = [], a = Array.from(e, (s) => { + if (Ne(s), s < 0 || s >= t) + throw new Error(`invalid integer: ${s}`); + return s; + }), o = a.length; + for (; ; ) { + let s = 0, l = !0; + for (let u = r; u < o; u++) { + const h = a[u], g = t * s, c = g + h; + if (!Number.isSafeInteger(c) || g / t !== s || c - h !== g) + throw new Error("convertRadix: carry overflow"); + const d = c / n; + s = c % n; + const I = Math.floor(d); + if (a[u] = I, !Number.isSafeInteger(I) || I * n + s !== c) + throw new Error("convertRadix: carry overflow"); + if (l) + I ? l = !1 : r = u; + else continue; + } + if (i.push(s), l) + break; + } + for (let s = 0; s < e.length - 1 && e[s] === 0; s++) + i.push(0); + return i.reverse(); +} +const Qn = (e, t) => t === 0 ? e : Qn(t, e % t), dt = /* @__NO_SIDE_EFFECTS__ */ (e, t) => e + (t - Qn(e, t)), et = /* @__PURE__ */ (() => { + let e = []; + for (let t = 0; t < 40; t++) + e.push(2 ** t); + return e; +})(); +function gt(e, t, n, r) { + if (ct(e), t <= 0 || t > 32) + throw new Error(`convertRadix2: wrong from=${t}`); + if (n <= 0 || n > 32) + throw new Error(`convertRadix2: wrong to=${n}`); + if (/* @__PURE__ */ dt(t, n) > 32) + throw new Error(`convertRadix2: carry overflow from=${t} to=${n} carryBits=${/* @__PURE__ */ dt(t, n)}`); + let i = 0, a = 0; + const o = et[t], s = et[n] - 1, l = []; + for (const u of e) { + if (Ne(u), u >= o) + throw new Error(`convertRadix2: invalid data word=${u} from=${t}`); + if (i = i << t | u, a + t > 32) + throw new Error(`convertRadix2: carry overflow pos=${a} from=${t}`); + for (a += t; a >= n; a -= n) + l.push((i >> a - n & s) >>> 0); + const h = et[a]; + if (h === void 0) + throw new Error("invalid carry"); + i &= h - 1; + } + if (i = i << n - a & s, !r && a >= t) + throw new Error("Excess padding"); + if (!r && i > 0) + throw new Error(`Non-zero padding: ${i}`); + return r && a > 0 && l.push(i >>> 0), l; +} +// @__NO_SIDE_EFFECTS__ +function h1(e) { + Ne(e); + const t = 2 ** 8; + return { + encode: (n) => { + if (!Ue(n)) + throw new Error("radix.encode input should be Uint8Array"); + return Yt(Array.from(n), t, e); + }, + decode: (n) => ($t("radix.decode", n), Uint8Array.from(Yt(n, e, t))) + }; +} +// @__NO_SIDE_EFFECTS__ +function rr(e, t = !1) { + if (Ne(e), e <= 0 || e > 32) + throw new Error("radix2: bits should be in (0..32]"); + if (/* @__PURE__ */ dt(8, e) > 32 || /* @__PURE__ */ dt(e, 8) > 32) + throw new Error("radix2: carry overflow"); + return { + encode: (n) => { + if (!Ue(n)) + throw new Error("radix2.encode input should be Uint8Array"); + return gt(Array.from(n), 8, e, !t); + }, + decode: (n) => ($t("radix2.decode", n), Uint8Array.from(gt(n, e, 8, t))) + }; +} +function Gr(e) { + return vn(e), function(...t) { + try { + return e.apply(null, t); + } catch { + } + }; +} +function f1(e, t) { + return Ne(e), vn(t), { + encode(n) { + if (!Ue(n)) + throw new Error("checksum.encode: input should be Uint8Array"); + const r = t(n).slice(0, e), i = new Uint8Array(n.length + e); + return i.set(n), i.set(r, n.length), i; + }, + decode(n) { + if (!Ue(n)) + throw new Error("checksum.decode: input should be Uint8Array"); + const r = n.slice(0, -e), i = n.slice(-e), a = t(r).slice(0, e); + for (let o = 0; o < e; o++) + if (a[o] !== i[o]) + throw new Error("Invalid checksum"); + return r; + } + }; +} +const X1 = { + alphabet: er, + chain: Wt, + checksum: f1, + convertRadix: Yt, + convertRadix2: gt, + radix: h1, + radix2: rr, + join: tr, + padding: Bn +}, p1 = typeof Uint8Array.from([]).toBase64 == "function" && typeof Uint8Array.fromBase64 == "function", I1 = (e, t) => { + ke("base64", e); + const n = /^[A-Za-z0-9=+/]+$/, r = "base64"; + if (e.length > 0 && !n.test(e)) + throw new Error("invalid base64"); + return Uint8Array.fromBase64(e, { alphabet: r, lastChunkHandling: "strict" }); +}, Z1 = p1 ? { + encode(e) { + return A1(e), e.toBase64(); + }, + decode(e) { + return I1(e); + } +} : /* @__PURE__ */ Wt(/* @__PURE__ */ rr(6), /* @__PURE__ */ er("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), /* @__PURE__ */ Bn(6), /* @__PURE__ */ tr("")), Mt = /* @__PURE__ */ Wt(/* @__PURE__ */ er("qpzry9x8gf2tvdw0s3jn54khce6mua7l"), /* @__PURE__ */ tr("")), Hr = [996825010, 642813549, 513874426, 1027748829, 705979059]; +function Ve(e) { + const t = e >> 25; + let n = (e & 33554431) << 5; + for (let r = 0; r < Hr.length; r++) + (t >> r & 1) === 1 && (n ^= Hr[r]); + return n; +} +function Nr(e, t, n = 1) { + const r = e.length; + let i = 1; + for (let a = 0; a < r; a++) { + const o = e.charCodeAt(a); + if (o < 33 || o > 126) + throw new Error(`Invalid prefix (${e})`); + i = Ve(i) ^ o >> 5; + } + i = Ve(i); + for (let a = 0; a < r; a++) + i = Ve(i) ^ e.charCodeAt(a) & 31; + for (let a of t) + i = Ve(i) ^ a; + for (let a = 0; a < 6; a++) + i = Ve(i); + return i ^= n, Mt.encode(gt([i % et[30]], 30, 5, !1)); +} +// @__NO_SIDE_EFFECTS__ +function m1(e) { + const t = e === "bech32" ? 1 : 734539939, n = /* @__PURE__ */ rr(5), r = n.decode, i = n.encode, a = Gr(r); + function o(g, c, d = 90) { + ke("bech32.encode prefix", g), Ue(c) && (c = Array.from(c)), $t("bech32.encode", c); + const I = g.length; + if (I === 0) + throw new TypeError(`Invalid prefix length ${I}`); + const m = I + 7 + c.length; + if (d !== !1 && m > d) + throw new TypeError(`Length ${m} exceeds limit ${d}`); + const B = g.toLowerCase(), S = Nr(B, c, t); + return `${B}1${Mt.encode(c)}${S}`; + } + function s(g, c = 90) { + ke("bech32.decode input", g); + const d = g.length; + if (d < 8 || c !== !1 && d > c) + throw new TypeError(`invalid string length: ${d} (${g}). Expected (8..${c})`); + const I = g.toLowerCase(); + if (g !== I && g !== g.toUpperCase()) + throw new Error("String must be lowercase or uppercase"); + const m = I.lastIndexOf("1"); + if (m === 0 || m === -1) + throw new Error('Letter "1" must be present between prefix and data only'); + const B = I.slice(0, m), S = I.slice(m + 1); + if (S.length < 6) + throw new Error("Data must be at least 6 characters long"); + const j = Mt.decode(S).slice(0, -6), b = Nr(B, j, t); + if (!S.endsWith(b)) + throw new Error(`Invalid checksum in ${g}: expected "${b}"`); + return { prefix: B, words: j }; + } + const l = Gr(s); + function u(g) { + const { prefix: c, words: d } = s(g, !1); + return { prefix: c, words: d, bytes: r(d) }; + } + function h(g, c) { + return o(g, i(c)); + } + return { + encode: o, + decode: s, + encodeFromBytes: h, + decodeToBytes: u, + decodeUnsafe: l, + fromWords: r, + fromWordsUnsafe: a, + toWords: i + }; +} +const nr = /* @__PURE__ */ m1("bech32"); +function $1(e) { + return nr.encodeFromBytes("npub", Je(e)); +} +function W1(e) { + return nr.encodeFromBytes("nsec", e); +} +function e2(e) { + const { prefix: t, bytes: n } = nr.decodeToBytes(e); + if (t !== "nsec" || n.length !== 32) + throw new Error("Not a valid nsec."); + return n; +} +function t2(e) { + return { + schema: Cn, + recovery_public_key: e.recoveryPublicKeyHex, + vault_relays: e.vaultRelayUrls, + recovery_capsule_events: e.recoveryCapsuleEvents, + relay_list_events: e.relayListEvents, + created_at: e.now ?? Math.floor(Date.now() / 1e3) + }; +} +class oe extends Error { + constructor(t) { + super(t), this.name = "RecoveryExportParseError"; + } +} +function r2(e) { + if (typeof e != "object" || e === null) + throw new oe("Recovery export must be a JSON object."); + const t = e; + if (t.schema !== Cn) + throw new oe(`Unsupported recovery export schema: ${String(t.schema)}`); + if (typeof t.recovery_public_key != "string" || !/^[0-9a-f]{64}$/u.test(t.recovery_public_key)) + throw new oe("recovery_public_key must be lowercase 64-char hex."); + if (!Array.isArray(t.vault_relays)) + throw new oe("vault_relays must be an array."); + if (!Array.isArray(t.recovery_capsule_events) || t.recovery_capsule_events.length === 0) + throw new oe("recovery_capsule_events must be a non-empty array."); + for (const n of t.recovery_capsule_events) + if (!Fr(n)) + throw new oe("An embedded recovery_capsule_event has an invalid id or signature."); + if (!Array.isArray(t.relay_list_events)) + throw new oe("relay_list_events must be an array."); + for (const n of t.relay_list_events) + if (!Fr(n)) + throw new oe("An embedded relay_list_event has an invalid id or signature."); + if (typeof t.created_at != "number") + throw new oe("created_at must be a number."); + return t; +} +export { + jr as $, + rt as A, + Q1 as B, + pt as C, + R1 as D, + b1 as E, + Ce as F, + x1 as G, + pe as H, + X1 as I, + fe as J, + q1 as K, + We as L, + e2 as M, + Je as N, + n1 as O, + H1 as P, + w1 as Q, + oe as R, + L1 as S, + N1 as T, + K1 as U, + k1 as V, + J1 as W, + qe as X, + Z1 as Y, + qr as Z, + mt as _, + $1 as a, + U1 as a0, + Rt as a1, + t2 as a2, + W1 as a3, + z1 as b, + wn as c, + Y1 as d, + y1 as e, + M1 as f, + O1 as g, + j1 as h, + G1 as i, + T1 as j, + C1 as k, + D1 as l, + S1 as m, + F1 as n, + o1 as o, + r2 as p, + P1 as q, + v1 as r, + V1 as s, + _1 as t, + De as u, + Fr as v, + bt as w, + B1 as x, + ht as y, + E1 as z +}; diff --git a/vendor/bitlogin/bitlogin.js b/vendor/bitlogin/bitlogin.js new file mode 100644 index 00000000..ff55bb27 --- /dev/null +++ b/vendor/bitlogin/bitlogin.js @@ -0,0 +1,1253 @@ +import { r as y, e as w, E as v, p as Q, R as k, i as h, a as u } from "./bitlogin-shared-QIBe5Omw.js"; +const x = 6, p = 64; +function c(a = x) { + const e = w(), t = []; + for (let r = 0; r < a; r++) + t.push(e[y(e.length)]); + const s = a * v; + if (s < p) + throw new Error(`Passphrase of ${a} words provides only ~${s.toFixed(1)} bits; must exceed ${p}.`); + return { kind: "passphrase", secret: t.join(" "), entropyBits: s }; +} +const b = 64, m = 12, P = /* @__PURE__ */ new Set([ + "password", + "password1", + "password123", + "123456", + "123456789", + "12345678", + "qwerty", + "qwerty123", + "letmein", + "welcome", + "monkey", + "dragon", + "master", + "abc123", + "iloveyou", + "admin", + "login", + "starwars", + "sunshine", + "princess", + "football", + "baseball", + "trustno1", + "000000", + "111111", + "123123", + "1234567890", + "changeme", + "passw0rd", + "correcthorsebatterystaple" +]); +function L(a) { + let e = 0; + return /[a-z]/u.test(a) && (e += 26), /[A-Z]/u.test(a) && (e += 26), /[0-9]/u.test(a) && (e += 10), /[^a-zA-Z0-9]/u.test(a) && (e += 33), e || 1; +} +function C(a) { + return a.length * Math.log2(L(a)); +} +function S(a) { + if (/^(.)\1*$/u.test(a) || /(.)\1{2,}/u.test(a)) + return !0; + const e = a.toLowerCase(), t = ["0123456789", "abcdefghijklmnopqrstuvwxyz"]; + for (const s of t) + for (let r = 0; r + 4 <= s.length; r++) { + const o = s.slice(r, r + 4), n = [...o].reverse().join(""); + if (e.includes(o) || e.includes(n)) + return !0; + } + return !1; +} +function f(a, e) { + const t = C(a); + return a.length < m ? { ok: !1, entropyBits: t, reason: `Must be at least ${m} characters.` } : P.has(a.toLowerCase()) ? { ok: !1, entropyBits: t, reason: "This is on a list of extremely common passwords." } : e.length >= 3 && a.toLowerCase().includes(e) ? { ok: !1, entropyBits: t, reason: "Must not contain your login name." } : S(a) ? { ok: !1, entropyBits: t, reason: "Too predictable (repeated characters or a simple sequence)." } : t < b ? { + ok: !1, + entropyBits: t, + reason: `Estimated entropy (~${t.toFixed(0)} bits) is below the required ${b}. Use a longer password or mix character types.` + } : { ok: !0, entropyBits: t }; +} +class E { + worker; + pending = /* @__PURE__ */ new Map(); + counter = 0; + constructor() { + const e = ["cryptoWorker", ".js"].join(""), t = new URL(e, import.meta.url); + this.worker = new Worker(t, { type: "module" }), this.worker.addEventListener("message", (s) => { + const r = s.data, o = this.pending.get(r.id); + if (o) + if (this.pending.delete(r.id), r.ok) + o.resolve(r.result); + else { + const n = new Error(r.error); + n.name = r.errorName ?? "Error", o.reject(n); + } + }); + } + call(e, t) { + const s = `${Date.now().toString(36)}-${(this.counter++).toString(36)}`; + return new Promise((r, o) => { + this.pending.set(s, { resolve: r, reject: o }); + const n = { id: s, action: e, payload: t }; + this.worker.postMessage(n); + }); + } + configure(e) { + return this.call("configure", e); + } + register(e) { + return this.call("register", e); + } + previewImportKey(e) { + return this.call("previewImportKey", e); + } + login(e) { + return this.call("login", e); + } + recover(e) { + return this.call("recover", e); + } + completeRecovery(e) { + return this.call("completeRecovery", e); + } + changePassword(e) { + return this.call("changePassword", e); + } + publishProfileAndRelayLists(e) { + return this.call("publishProfileAndRelayLists", e); + } + getPublicKey() { + return this.call("getPublicKey", {}); + } + signEvent(e) { + return this.call("signEvent", e); + } + nip44Encrypt(e) { + return this.call("nip44Encrypt", e); + } + nip44Decrypt(e) { + return this.call("nip44Decrypt", e); + } + exportIdentity() { + return this.call("exportIdentity", {}); + } + buildRecoveryExport() { + return this.call("buildRecoveryExport", {}); + } + repairReplicas() { + return this.call("repairReplicas", {}); + } + getSessionStatus() { + return this.call("getSessionStatus", {}); + } + logout() { + return this.call("logout", {}); + } + terminate() { + this.worker.terminate(); + } +} +const N = "BitLogin: no identity is unlocked yet. Add to the page and let the user sign in, or call it programmatically before invoking window.nostr."; +function d(a) { + return a.catch((e) => { + throw e.message.includes("No identity is unlocked") ? new Error(N) : e; + }); +} +function $(a, e) { + return { + async getPublicKey() { + const { publicKey: t } = await d(a.getPublicKey()); + return t; + }, + async signEvent(t) { + return d(a.signEvent(t)); + }, + async getRelays() { + const t = {}; + for (const s of e()) t[s] = { read: !0, write: !0 }; + return t; + }, + nip44: { + async encrypt(t, s) { + const { ciphertext: r } = await d(a.nip44Encrypt({ peerPublicKey: t, plaintext: s })); + return r; + }, + async decrypt(t, s) { + const { plaintext: r } = await d(a.nip44Decrypt({ peerPublicKey: t, payload: s })); + return r; + } + }, + _bitlogin: !0 + }; +} +function R(a) { + const e = a.getAttribute("vault-relays"), t = a.getAttribute("discovery-relays"); + return { + vaultRelayUrls: e ? e.split(",").map((s) => s.trim()).filter(Boolean) : void 0, + discoveryRelayUrls: t ? t.split(",").map((s) => s.trim()).filter(Boolean) : void 0 + }; +} +const M = ( + /* css */ + ` +:host { + --bl-accent: #6d28d9; + --bl-accent-hover: #5b21b6; + --bl-accent-fg: white; + --bl-bg: #ffffff; + --bl-fg: #16151a; + --bl-muted: #6b7280; + --bl-border: #e5e7eb; + --bl-input-bg: #f9fafb; + --bl-danger: #b91c1c; + --bl-danger-bg: #fef2f2; + --bl-warn: #92400e; + --bl-warn-bg: #fffbeb; + --bl-radius: 14px; + /* Caps the widget's own width; a host page that wants it to fill a wider + container (rather than stay a fixed-width card) overrides this to a + larger value or "none". */ + --bl-max-width: 380px; + --bl-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + all: initial; + display: inline-block; + width: 100%; + max-width: var(--bl-max-width); + font-family: var(--bl-font-family); + color-scheme: light dark; +} +@media (prefers-color-scheme: dark) { + :host { + --bl-bg: #17151f; + --bl-fg: #f4f2f8; + --bl-muted: #9a94ab; + --bl-border: #322d40; + --bl-input-bg: #201d2b; + --bl-danger: #fca5a5; + --bl-danger-bg: #3a1414; + --bl-warn: #fcd34d; + --bl-warn-bg: #3a2a0a; + } +} +:host([data-theme="dark"]) { + --bl-bg: #17151f; + --bl-fg: #f4f2f8; + --bl-muted: #9a94ab; + --bl-border: #322d40; + --bl-input-bg: #201d2b; +} +:host([data-theme="light"]) { + --bl-bg: #ffffff; + --bl-fg: #16151a; + --bl-muted: #6b7280; + --bl-border: #e5e7eb; + --bl-input-bg: #f9fafb; +} +* { box-sizing: border-box; } +.card { + position: relative; + width: 100%; + background: var(--bl-bg); + color: var(--bl-fg); + border: 1px solid var(--bl-border); + border-radius: var(--bl-radius); + padding: 24px; + font-size: 14px; + line-height: 1.5; +} +h2 { + font-size: 18px; + font-weight: 600; + margin: 0 0 4px; +} +/* One fixed SVG (icon + wordmark, same file as the marketing site's assets/lockup.svg) + with the relative size and spacing between them baked in -- only the overall height + is ever set here, so the lockup can never end up looking different in one place than + another the way independently-sized icon + text elements could drift apart. */ +.brand-lockup { + display: block; + height: 20px; + width: auto; + margin: 0 0 6px; + color: var(--bl-fg); +} +/* Brief confirmation stamp shown over the destination screen right after a real + security-relevant success (see flashSuccess() in element.ts) -- the same brass ring + and checkmark as the brand mark, so a successful sign-in/create/recover/rotate reads + as "sealed," not just a silent screen change. Never blocks interaction, and fades out + on its own; the JS timer that removes it entirely doesn't depend on the animation. */ +.success-overlay { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 12px; + background: var(--bl-bg); + border-radius: var(--bl-radius); + pointer-events: none; + animation: bl-overlay-fade-out 0.4s ease 0.7s both; +} +.success-stamp svg { + width: 56px; + height: 56px; + animation: bl-stamp-in 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) both; +} +.success-label { + margin: 0; + font-size: 14px; + font-weight: 600; + color: var(--bl-fg); + opacity: 0; + animation: bl-label-in 0.3s ease 0.35s both; +} +@keyframes bl-stamp-in { + 0% { transform: scale(0.5); opacity: 0; } + 60% { transform: scale(1.15); opacity: 1; } + 100% { transform: scale(1); opacity: 1; } +} +@keyframes bl-label-in { + to { opacity: 1; } +} +@keyframes bl-overlay-fade-out { + 0% { opacity: 1; } + 100% { opacity: 0; } +} +@media (prefers-reduced-motion: reduce) { + .success-overlay { animation: none; } + .success-stamp svg { animation: none; } + .success-label { animation: none; opacity: 1; } +} +p.sub { + color: var(--bl-muted); + margin: 0 0 18px; + font-size: 13px; +} +label { + display: block; + font-size: 13px; + font-weight: 500; + margin: 14px 0 6px; +} +input[type="text"], input[type="password"] { + width: 100%; + padding: 10px 12px; + border-radius: 9px; + border: 1px solid var(--bl-border); + background: var(--bl-input-bg); + color: var(--bl-fg); + font-size: 14px; + font-family: inherit; +} +input:focus { outline: 2px solid var(--bl-accent); outline-offset: 1px; } +.credential-box { + background: var(--bl-input-bg); + border: 1px solid var(--bl-border); + border-radius: 9px; + padding: 12px; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 13.5px; + word-break: break-word; + margin: 6px 0 2px; + user-select: all; +} +.phrase-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 6px; + margin: 10px 0; +} +.phrase-word { + background: var(--bl-input-bg); + border: 1px solid var(--bl-border); + border-radius: 7px; + padding: 6px 8px; + font-size: 12.5px; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + display: flex; + gap: 4px; +} +.phrase-word span { color: var(--bl-muted); } +button { + font-family: inherit; + font-size: 14px; + font-weight: 600; + border-radius: 9px; + border: none; + padding: 11px 14px; + cursor: pointer; + width: 100%; + margin-top: 14px; +} +button.primary { background: var(--bl-accent); color: var(--bl-accent-fg); } +button.primary:hover { background: var(--bl-accent-hover); } +button.primary:disabled { opacity: 0.55; cursor: default; } +button.secondary { background: transparent; color: var(--bl-fg); border: 1px solid var(--bl-border); } +button.secondary:hover { background: var(--bl-input-bg); } +button.link { + display: block; + background: none; + color: var(--bl-accent); + width: auto; + padding: 0; + margin: 12px 0 0; + font-weight: 500; + font-size: 13px; +} +.row { display: flex; gap: 8px; } +.row > * { flex: 1; } +.notice { + border-radius: 9px; + padding: 10px 12px; + font-size: 13px; + margin: 12px 0; +} +.notice.warn { background: var(--bl-warn-bg); color: var(--bl-warn); } +.notice.error { background: var(--bl-danger-bg); color: var(--bl-danger); } +.notice.info { background: var(--bl-input-bg); color: var(--bl-fg); border: 1px solid var(--bl-border); } +.pubkey { + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 12px; + color: var(--bl-muted); + word-break: break-all; +} +.divider { border-top: 1px solid var(--bl-border); margin: 18px 0; } +.small { font-size: 12px; color: var(--bl-muted); } +.checkbox-row { display: flex; align-items: flex-start; gap: 8px; margin: 14px 0; font-size: 13px; } +.checkbox-row input { margin-top: 3px; } +.spinner { + display: inline-block; + width: 14px; height: 14px; + border: 2px solid rgba(255,255,255,0.4); + border-top-color: white; + border-radius: 50%; + animation: bl-spin 0.7s linear infinite; + vertical-align: -2px; + margin-right: 6px; +} +@keyframes bl-spin { to { transform: rotate(360deg); } } +` +); +class F extends HTMLElement { + root; + worker; + vaultRelayUrls = []; + discoveryRelayUrls = []; + installedProvider = null; + screen = "welcome"; + busy = !1; + errorMessage; + loginName = ""; + generatedCredential = ""; + savedCheckbox = !1; + recoveryPhrase = ""; + confirmSlots = []; + // Manual password opt-in (§9.3). Off by default; the generated passphrase remains the + // recommendation. manualPasswordFeedback is updated on every keystroke WITHOUT a full + // re-render (see onInput) so the field never loses focus while the user is typing. + // manualPasswordDraft/manualPasswordConfirmDraft re-populate the fields' value attribute + // across a validation-failure re-render, so a rejected password isn't silently wiped, + // forcing the user to retype it (and re-check "I saved it") from scratch. + manualPasswordMode = !1; + manualPasswordFeedback = null; + manualPasswordDraft = ""; + manualPasswordConfirmDraft = ""; + // Import flow (§SF10). importKey holds the pasted nsec/hex only until registration completes. + importKey = ""; + importPreviewNpub = ""; + recoverPhraseInput = ""; + recoveredPreview = null; + newCredentialAfterRecovery = ""; + // Optional recovery-export-file fallback (§19.5) for when live relays can't be reached. + // Never a substitute for the phrase -- the file never contains it or any phrase-derived key. + offlineExportFile = null; + offlineExportFileNotice; + session = null; + sessionWarnings = []; + lastSignedEventJson = ""; + exportedNsec = ""; + changePasswordNewCredential = ""; + // Brief animated brass-seal stamp (matching the widget's own brand mark) shown once over + // the destination screen right after a real security-relevant success -- see flashSuccess(). + pendingSuccessLabel = null; + successDismissTimer = null; + // Rollback confirmation (§16.2 step 6). A RollbackDetectedError from either login or + // password-change means this device has already seen a newer credential generation than the + // one just read -- most likely a rotated-away password being replayed from a relay that never + // processed its tombstone. Rather than a passive dashboard banner shown after the fact (which + // let an old, "revoked" password fully unlock a session), this blocks BEFORE claiming the + // signer or dispatching bitlogin-login, and requires an explicit second step to proceed. + pendingRollback = null; + rollbackMessage = ""; + constructor() { + super(), this.root = this.attachShadow({ mode: "open" }), this.worker = new E(); + } + connectedCallback() { + const e = R(this); + if (this.vaultRelayUrls = e.vaultRelayUrls ?? [], this.discoveryRelayUrls = e.discoveryRelayUrls ?? [], this.worker.configure({ vaultRelayUrls: this.vaultRelayUrls, discoveryRelayUrls: this.discoveryRelayUrls }), this.root.addEventListener("click", (t) => this.onClick(t)), this.root.addEventListener("submit", (t) => this.onSubmit(t)), this.root.addEventListener("input", (t) => this.onInput(t)), this.root.addEventListener("change", (t) => void this.onFileChange(t)), this.render(), this.installedProvider = $(this.worker, () => this.vaultRelayUrls), !window.nostr) + try { + window.nostr = this.installedProvider; + } catch { + } + } + disconnectedCallback() { + this.successDismissTimer !== null && clearTimeout(this.successDismissTimer), this.releaseSigner(), this.worker.terminate(); + } + // ---- Public API mirroring window.nostr, scoped to this element instance ---- + async getPublicKey() { + return (await this.worker.getPublicKey()).publicKey; + } + async signEvent(e) { + return this.worker.signEvent(e); + } + /** + * Element-scoped NIP-44 encryption, matching getPublicKey/signEvent above. A host page + * embedding multiple signing methods (its own extension detection, another widget) should + * prefer this over reaching through `window.nostr.nip44` -- window.nostr is a single global + * slot that whichever provider signed in last currently owns, so a host holding a direct + * reference to ITS OWN `` element can talk to it deterministically instead of + * racing other providers for that slot. + */ + async nip44Encrypt(e, t) { + return (await this.worker.nip44Encrypt({ peerPublicKey: e, plaintext: t })).ciphertext; + } + async nip44Decrypt(e, t) { + return (await this.worker.nip44Decrypt({ peerPublicKey: e, payload: t })).plaintext; + } + async logout() { + await this.worker.logout(), this.session = null, this.releaseSigner(), this.dispatchEvent(new CustomEvent("bitlogin-logout")), this.goto("welcome"); + } + /** + * (Re)installs this element's own provider as window.nostr, taking over from whatever is + * currently there (an extension, a different BitLogin instance, or nothing). Called + * automatically whenever a user completes sign-in through this widget — that's an + * explicit signal they want BitLogin active. Also public: a host page offering several + * signing methods can call it directly (e.g. `document.querySelector('bitlogin-auth').claimSigner()`) + * when the user re-selects BitLogin from its own method picker, without a page reload. + * + * Best-effort: some NIP-07 extensions install window.nostr as a non-configurable, + * non-writable property specifically to prevent another script from overwriting it, and + * the plain assignment below throws a TypeError in that case ("Cannot assign to read only + * property 'nostr'..."). This is never fatal to BitLogin's own session -- a host page + * holding a reference to this element (getPublicKey/signEvent/nip44Encrypt/nip44Decrypt + * above) never depends on window.nostr at all -- so the failure is caught and reported + * through the returned boolean and the event detail rather than thrown, and every caller + * below proceeds to complete sign-in regardless. Only a host page that reads window.nostr + * directly (instead of talking to this element) is actually affected, and only for as long + * as the other extension holds the slot. + */ + claimSigner() { + try { + return window.nostr = this.installedProvider, this.dispatchEvent(new CustomEvent("bitlogin-signer-claimed", { detail: { windowNostrClaimed: !0 } })), !0; + } catch (e) { + const t = e instanceof Error ? e.message : String(e); + return this.dispatchEvent(new CustomEvent("bitlogin-signer-claimed", { detail: { windowNostrClaimed: !1, error: t } })), !1; + } + } + /** + * Surfaces a claimSigner() failure on the dashboard itself (not just the event detail), + * using the same sessionWarnings/renderWarnings mechanism already shown for rollback and + * relay-disagreement warnings -- so a user isn't left wondering why some other app still + * seems to be using a different signer. + */ + noteSignerClaim(e) { + e || (this.sessionWarnings = [ + ...this.sessionWarnings, + "Another Nostr signer (browser extension) is active in this browser and couldn't be replaced. You're signed in to BitLogin, but any app that reads window.nostr directly may still use that other signer instead of this one." + ]); + } + /** + * Releases window.nostr back to undefined, but only if it's still this element's own + * provider — never clobbers a different signing method a host page (or another widget + * instance) may have since taken over. Called automatically on logout and on removal from + * the DOM, so a page offering multiple signing methods (an extension, a NIP-46 bunker, + * BitLogin) can let a user switch away from BitLogin without a full page reload — before + * this existed, window.nostr stayed pointed at a signed-out BitLogin provider forever. + * Returns whether it actually released anything. Best-effort for the same reason as + * claimSigner above: a property some other extension made non-configurable can also fail + * to `delete`, and that must not block logout either. + */ + releaseSigner() { + const e = window; + if (this.installedProvider && e.nostr === this.installedProvider) { + try { + delete e.nostr; + } catch { + return !1; + } + return this.dispatchEvent(new CustomEvent("bitlogin-signer-released")), !0; + } + return !1; + } + goto(e) { + this.screen = e, this.errorMessage = void 0, this.render(); + } + setBusy(e) { + this.busy = e, this.render(); + } + fail(e) { + this.errorMessage = e instanceof Error ? e.message : String(e), this.busy = !1, this.render(); + } + field(e) { + return this.root.querySelector(`[name="${e}"]`)?.value ?? ""; + } + /** + * Shared manual-password field markup (§9.3), reused on every screen that lets a user set + * their own password (initial creation, phrase recovery, and password rotation) so the + * option isn't only available at signup. + */ + renderManualPasswordFields() { + return ` + + +
+ + +
+ `; + } + /** + * Reads and validates the manual-password fields against a caller-supplied login name. + * Always stashes what was typed into manualPasswordDraft/manualPasswordConfirmDraft so a + * validation-failure re-render can restore it (see renderManualPasswordFields). + */ + readManualPassword(e) { + const t = this.field("manualPassword"), s = this.field("manualPasswordConfirm"); + this.manualPasswordDraft = t, this.manualPasswordConfirmDraft = s; + const r = f(t, e); + return r.ok ? t !== s ? { password: "", error: "Passwords do not match. Please re-enter both." } : { password: t } : { password: "", error: `Password not accepted: ${r.reason}` }; + } + /** + * Explicitly asks the browser to offer saving this credential via the Credential + * Management API (Chromium-based browsers only — Firefox and Safari never implemented + * `PasswordCredential`). This is the primary fix for password-manager integration here, + * not just a nice-to-have: the generated-password screen never puts the password into a + * real `` for a browser to observe being filled (it's shown as text), and even on + * the login screen — which does use real, correctly-`autocomplete`d inputs — save/autofill + * heuristics are unreliable inside a shadow root, and this widget's JS-driven submit + * (`preventDefault`, no real form POST) doesn't produce the passive signal most heuristics + * expect anyway. An explicit `navigator.credentials.store()` call sidesteps all of that. + * Best-effort only: never blocks or fails the surrounding flow. + */ + async offerToSaveCredential(e, t) { + try { + const s = window.PasswordCredential; + if (!s || !navigator.credentials?.store) return; + const r = new s({ id: e, password: t, name: e }); + await navigator.credentials.store(r); + } catch { + } + } + async onClick(e) { + const t = e.target.closest("[data-action]"); + if (!t) return; + switch (t.dataset.action) { + case "goto-create": + this.loginName = "", this.importKey = "", this.importPreviewNpub = "", this.goto("create-name"); + return; + case "goto-import": + this.importKey = "", this.importPreviewNpub = "", this.goto("import-key"); + return; + case "preview-import": + return this.handlePreviewImport(); + case "import-continue": + return this.handleImportContinue(); + case "goto-login": + this.goto("login"); + return; + case "goto-recover": + this.offlineExportFile = null, this.offlineExportFileNotice = void 0, this.goto("recover-phrase"); + return; + case "goto-welcome": + this.pendingRollback = null, this.goto("welcome"); + return; + case "goto-dashboard": + this.goto("dashboard"); + return; + case "goto-verify-phrase": + this.goto("verify-phrase"); + return; + case "goto-confirm-phrase": + this.goto("confirm-phrase"); + return; + case "goto-change-password": + this.changePasswordNewCredential = c().secret, this.manualPasswordMode = !1, this.manualPasswordFeedback = null, this.manualPasswordDraft = "", this.manualPasswordConfirmDraft = "", this.goto("change-password"); + return; + case "goto-export": + this.goto("export"); + return; + case "regenerate-credential": + this.generatedCredential = c().secret, this.savedCheckbox = !1, this.render(); + return; + case "toggle-manual-password": + this.manualPasswordMode = !this.manualPasswordMode, this.manualPasswordFeedback = null, this.manualPasswordDraft = "", this.manualPasswordConfirmDraft = "", this.savedCheckbox = !1, this.render(); + return; + case "copy-credential": { + const r = this.root.querySelector("#credential-box"), o = t, n = o.textContent ?? "Copy", l = (g) => { + o.textContent = g, setTimeout(() => { + o.isConnected && (o.textContent = n); + }, 2e3); + }; + if (!r || !navigator.clipboard?.writeText) { + l("Copy not available — select the text manually"); + return; + } + navigator.clipboard.writeText(r.textContent ?? "").then( + () => l("Copied"), + () => l("Copy failed — select the text manually") + ); + return; + } + case "download-recovery-export": + return this.handleDownloadRecoveryExport(); + case "sign-test-event": + return this.handleSignTestEvent(); + case "reveal-nsec": + return this.handleRevealNsec(); + case "logout": + return this.logout(); + case "rollback-retry": { + const r = this.pendingRollback?.kind; + this.pendingRollback = null, this.goto(r === "change-password" ? "change-password" : "login"); + return; + } + case "rollback-continue": + return this.handleRollbackContinue(); + default: + return; + } + } + async onSubmit(e) { + e.preventDefault(); + const s = e.target.dataset.form; + try { + switch (s) { + case "import-key": + return await this.handlePreviewImport(); + case "create-name": + return this.handleCreateNameSubmit(); + case "create-credential": + return await this.handleCreateCredentialSubmit(); + case "verify-phrase": + return this.handleVerifyPhraseSubmit(); + case "login": + return await this.handleLoginSubmit(); + case "recover-phrase": + return await this.handleRecoverPhraseSubmit(); + case "recover-new-credentials": + return await this.handleRecoverNewCredentialsSubmit(); + case "change-password": + return await this.handleChangePasswordSubmit(); + default: + return; + } + } catch (r) { + this.fail(r); + } + } + /** + * Live entropy/strength and match feedback for the manual-password fields, updated by + * DIRECTLY patching feedback elements' text — never calling this.render() — so neither + * input loses focus or cursor position while the user is typing (§9.3). + */ + onInput(e) { + const t = e.target; + if (!(t instanceof HTMLInputElement) || t.name !== "manualPassword" && t.name !== "manualPasswordConfirm") return; + const s = this.root.querySelector('input[name="manualPassword"]')?.value ?? "", r = this.root.querySelector('input[name="manualPasswordConfirm"]')?.value ?? ""; + this.manualPasswordFeedback = s ? f(s, this.loginName) : null; + const o = this.root.querySelector("#manual-password-feedback"); + if (o) + if (!this.manualPasswordFeedback) + o.textContent = "", o.className = "notice info"; + else { + const l = this.manualPasswordFeedback.entropyBits.toFixed(0); + this.manualPasswordFeedback.ok ? (o.textContent = `Looks good (~${l} bits estimated).`, o.className = "notice info") : (o.textContent = `${this.manualPasswordFeedback.reason} (~${l} bits estimated)`, o.className = "notice warn"); + } + const n = this.root.querySelector("#manual-password-match-feedback"); + n && (r ? r === s ? (n.textContent = "Passwords match.", n.className = "notice info") : (n.textContent = "Passwords do not match.", n.className = "notice warn") : (n.textContent = "", n.className = "notice info")); + } + /** + * Reads and validates an optional recovery-export file (§19.5) for the recover-phrase + * screen. Purely a relay-outage fallback -- the file never contains the phrase or any + * phrase-derived key, so it's ignored entirely unless the user also enters their phrase. + */ + async onFileChange(e) { + const t = e.target; + if (!(t instanceof HTMLInputElement) || t.name !== "offlineExportFile" || t.type !== "file") return; + const s = t.files?.[0]; + if (!s) { + this.offlineExportFile = null, this.offlineExportFileNotice = void 0, this.render(); + return; + } + try { + const r = await s.text(); + this.offlineExportFile = Q(JSON.parse(r)), this.offlineExportFileNotice = `Loaded recovery export from ${new Date(this.offlineExportFile.created_at * 1e3).toLocaleString()}.`; + } catch (r) { + this.offlineExportFile = null, this.offlineExportFileNotice = r instanceof k || r instanceof SyntaxError ? `Couldn't read that file: ${r.message}` : `Couldn't read that file: ${String(r)}`; + } + this.render(); + } + async handlePreviewImport() { + const e = this.field("importKey").trim(); + if (!e) { + this.errorMessage = "Paste your nsec or hex private key first.", this.render(); + return; + } + this.setBusy(!0); + try { + const t = await this.worker.previewImportKey({ nsecOrHex: e }); + this.importKey = e, this.importPreviewNpub = t.npub, this.busy = !1, this.render(); + } catch (t) { + this.importKey = "", this.importPreviewNpub = "", this.fail(t); + } + } + handleImportContinue() { + if (!this.importKey || !this.importPreviewNpub) { + this.errorMessage = "Check your key before continuing.", this.render(); + return; + } + this.loginName = "", this.goto("create-name"); + } + handleCreateNameSubmit() { + const e = this.field("loginName").trim().toLowerCase(); + if (!h(e)) { + this.errorMessage = "Login name must be 3-32 characters: a-z, 0-9, '.', '_', '-', and not start/end with punctuation.", this.render(); + return; + } + this.loginName = e, this.generatedCredential = c().secret, this.savedCheckbox = !1, this.manualPasswordMode = !1, this.manualPasswordFeedback = null, this.manualPasswordDraft = "", this.manualPasswordConfirmDraft = "", this.goto("create-credential"); + } + async handleCreateCredentialSubmit() { + this.savedCheckbox = this.root.querySelector("#saved-check")?.checked ?? !1; + let e = this.generatedCredential; + if (this.manualPasswordMode) { + const { password: o, error: n } = this.readManualPassword(this.loginName); + if (n) { + this.errorMessage = n, this.render(); + return; + } + e = o; + } + if (!this.savedCheckbox) { + this.errorMessage = this.manualPasswordMode ? "Please confirm you've saved your password (or written it down) before continuing." : "Please confirm you saved your password before continuing.", this.render(); + return; + } + this.setBusy(!0); + const t = await this.worker.register({ + loginName: this.loginName, + password: e, + importKey: this.importKey || void 0 + }); + this.importKey = "", this.importPreviewNpub = "", this.recoveryPhrase = t.recoveryPhrase; + const s = this.recoveryPhrase.split(" "), r = T(s.length, 3); + this.confirmSlots = r.map((o) => ({ index: o, value: "" })), this.session = { publicKey: t.everydayPublicKey, npub: u(t.everydayPublicKey), accountId: t.accountId }, this.busy = !1, this.goto("confirm-phrase"), this.worker.publishProfileAndRelayLists({ + name: this.loginName, + generalRelays: this.vaultRelayUrls, + dmRelays: this.vaultRelayUrls + }), this.offerToSaveCredential(this.loginName, e); + } + handleVerifyPhraseSubmit() { + const e = this.recoveryPhrase.split(" "); + for (const t of this.confirmSlots) + if (this.field(`confirm-${t.index}`).trim().toLowerCase() !== e[t.index]) { + this.errorMessage = `Word #${t.index + 1} doesn't match. Please check your saved phrase and try again.`, this.render(); + return; + } + this.sessionWarnings = [], this.noteSignerClaim(this.claimSigner()), this.dispatchEvent(new CustomEvent("bitlogin-login", { detail: { publicKey: this.session?.publicKey } })), this.flashSuccess("dashboard", "Account created"); + } + async handleLoginSubmit() { + const e = this.field("loginName").trim().toLowerCase(), t = this.field("password"); + await this.attemptLogin(e, t); + } + /** + * Shared by the login form and the "continue anyway" rollback-confirmation step so both + * paths grant a session identically -- claimSigner() and the bitlogin-login event only ever + * fire once a RollbackDetectedError (if any) has been resolved one way or the other. + */ + async attemptLogin(e, t, s = !1) { + this.setBusy(!0); + try { + const r = await this.worker.login({ loginName: e, password: t, acknowledgeRollback: s }); + this.loginName = e, this.session = { publicKey: r.everydayPublicKey, npub: u(r.everydayPublicKey), accountId: r.accountId }, this.sessionWarnings = [r.rollbackWarning, r.relayDisagreementWarning].filter((o) => !!o), this.busy = !1, this.noteSignerClaim(this.claimSigner()), this.dispatchEvent(new CustomEvent("bitlogin-login", { detail: { publicKey: r.everydayPublicKey } })), this.flashSuccess("dashboard", "Signed in"), this.offerToSaveCredential(e, t); + } catch (r) { + if (r instanceof Error && r.name === "RollbackDetectedError") { + this.pendingRollback = { kind: "login", loginName: e, password: t }, this.rollbackMessage = r.message, this.busy = !1, this.goto("rollback-confirm"); + return; + } + this.fail(r); + } + } + async handleRollbackContinue() { + const e = this.pendingRollback; + if (this.pendingRollback = null, !e) { + this.goto("welcome"); + return; + } + e.kind === "login" ? await this.attemptLogin(e.loginName, e.password, !0) : await this.attemptChangePassword(e.oldPassword, e.newPassword, !0); + } + async handleRecoverPhraseSubmit() { + const e = this.field("phrase").trim(); + this.setBusy(!0); + const t = await this.worker.recover({ phrase: e, offlineExportFile: this.offlineExportFile ?? void 0 }); + this.recoverPhraseInput = e, this.recoveredPreview = { + generalRelaysCount: t.generalRelays.length, + dmRelaysCount: t.dmRelays.length, + chainWarning: t.chainWarning + }, this.session = { publicKey: t.everydayPublicKey, npub: u(t.everydayPublicKey), accountId: t.accountId }, this.newCredentialAfterRecovery = c().secret, this.manualPasswordMode = !1, this.manualPasswordFeedback = null, this.manualPasswordDraft = "", this.manualPasswordConfirmDraft = "", this.busy = !1, this.goto("recover-new-credentials"); + } + async handleRecoverNewCredentialsSubmit() { + const e = this.field("newLoginName").trim().toLowerCase(); + if (!h(e)) { + this.errorMessage = "Login name must be 3-32 characters: a-z, 0-9, '.', '_', '-', and not start/end with punctuation.", this.render(); + return; + } + let t = this.newCredentialAfterRecovery; + if (this.manualPasswordMode) { + const { password: s, error: r } = this.readManualPassword(e); + if (r) { + this.errorMessage = r, this.render(); + return; + } + t = s; + } + this.setBusy(!0), await this.worker.completeRecovery({ newLoginName: e, newPassword: t }), this.loginName = e, this.busy = !1, this.sessionWarnings = [], this.noteSignerClaim(this.claimSigner()), this.dispatchEvent(new CustomEvent("bitlogin-login", { detail: { publicKey: this.session?.publicKey } })), this.flashSuccess("dashboard", "Account recovered"), this.offerToSaveCredential(e, t); + } + async handleChangePasswordSubmit() { + const e = this.field("oldPassword"); + let t = this.changePasswordNewCredential; + if (this.manualPasswordMode) { + const { password: s, error: r } = this.readManualPassword(this.loginName); + if (r) { + this.errorMessage = r, this.render(); + return; + } + t = s; + } + await this.attemptChangePassword(e, t); + } + /** Shared by the rotation form and the "continue anyway" rollback-confirmation step; see attemptLogin. */ + async attemptChangePassword(e, t, s = !1) { + this.setBusy(!0); + try { + await this.worker.changePassword({ + loginName: this.loginName, + oldPassword: e, + newPassword: t, + acknowledgeRollback: s + }), this.busy = !1, this.sessionWarnings = [], this.noteSignerClaim(this.claimSigner()), this.flashSuccess("dashboard", "Password updated"), this.offerToSaveCredential(this.loginName, t); + } catch (r) { + if (r instanceof Error && r.name === "RollbackDetectedError") { + this.pendingRollback = { kind: "change-password", oldPassword: e, newPassword: t }, this.rollbackMessage = r.message, this.busy = !1, this.goto("rollback-confirm"); + return; + } + this.fail(r); + } + } + async handleDownloadRecoveryExport() { + const e = await this.worker.buildRecoveryExport(), t = new Blob([JSON.stringify(e, null, 2)], { type: "application/json" }), s = URL.createObjectURL(t), r = document.createElement("a"); + r.href = s, r.download = "bitlogin-recovery-export.json", r.click(), URL.revokeObjectURL(s); + } + async handleSignTestEvent() { + const e = await this.worker.signEvent({ kind: 1, content: `Hello from BitLogin at ${(/* @__PURE__ */ new Date()).toISOString()}` }); + this.lastSignedEventJson = JSON.stringify(e, null, 2), this.render(); + } + async handleRevealNsec() { + const { nsec: e } = await this.worker.exportIdentity(); + this.exportedNsec = e, this.render(); + } + render() { + const e = this.pendingSuccessLabel ? this.renderSuccessOverlay(this.pendingSuccessLabel) : ""; + this.root.innerHTML = `
${this.renderScreen()}${e}
`; + } + renderSuccessOverlay(e) { + return ` +
+ + + +

${i(e)}

+
+ `; + } + /** + * Navigates to `screen` and briefly stamps it with the same brass-ring-and-checkmark mark + * used in the logo, for the handful of moments that are a genuine security-relevant success + * -- signing in, creating an account, recovering one, or rotating a password -- rather than + * every validation step or navigation. Purely decorative: `pointer-events: none` (see + * styles.ts) means it never blocks interacting with the screen underneath, and a JS timer + * (not just the CSS animation) guarantees it's removed even if the animation can't run. + */ + flashSuccess(e, t) { + this.goto(e), this.successDismissTimer !== null && clearTimeout(this.successDismissTimer), this.pendingSuccessLabel = t, this.render(), this.successDismissTimer = setTimeout(() => { + this.pendingSuccessLabel = null, this.successDismissTimer = null, this.root.querySelector("[data-success-overlay]")?.remove(); + }, 1100); + } + renderError() { + return this.errorMessage ? `
${i(this.errorMessage)}
` : ""; + } + renderWarnings() { + return this.sessionWarnings.map((e) => `
${i(e)}
`).join(""); + } + /** The widget's own brand lockup (mark + wordmark), inlined as vector paths rather than + * relying on the host page having loaded any particular font -- shown once, on the + * welcome screen, as the widget's one branding moment. */ + renderBrandLockup() { + return ' '; + } + renderScreen() { + switch (this.screen) { + case "welcome": + return ` + ${this.renderBrandLockup()} +

A portable Nostr identity with a familiar login name and password.

+ ${this.renderError()} + + + + + `; + case "import-key": { + const e = !!this.importPreviewNpub; + return ` +

Import an existing Nostr key

+

Wrap a Nostr identity you already control in a BitLogin login name and password. Your key never changes — you just get a friendlier way in.

+
Pasting a private key into any web page is risky. Only do this on a BitLogin build you trust, and clear your clipboard afterward. BitLogin can't secure copies of this key that already exist elsewhere.
+ ${this.renderError()} +
+ + + +
+ ${e ? `
This key's public identity:
+
${i(this.importPreviewNpub)}
+ ` : ""} + + `; + } + case "create-name": + return ` +

${this.importKey ? "Set up your login" : "Create your BitLogin"}

+

${this.importKey ? "Choose a login name for your imported identity. It's a convenience, not a secret." : "Choose a login name. It's a convenience, not a secret (it contributes no security)."}

+ ${this.renderError()} +
+ + + +
+ + `; + case "create-credential": { + const e = this.busy ? `${this.importKey ? "Importing…" : "Creating account…"}` : this.importKey ? "Import account" : "Create account"; + return this.manualPasswordMode ? ` +

Choose your own password

+

Not recommended: a downloadable encrypted file can be guessed against forever, offline, with no rate limit. BitLogin can only run basic checks here — not a breach-database lookup — so weak or reused passwords are still your risk to carry.

+
Offline guessing against this password can never be fully prevented. The generated passphrase remains the safer default.
+ ${this.renderError()} +
+ ${this.renderManualPasswordFields()} + + +
+ + ` : ` +

Your generated password

+

BitLogin generates your password because no server can rate-limit guesses against a downloadable encrypted file.

+
${i(this.generatedCredential)}
+ + + ${this.renderError()} +
+ + + +
+ + `; + } + case "confirm-phrase": + return ` +

Save your recovery phrase

+

These 12 words can recover your identity if you forget your password or lose all your devices. We cannot recover these words for you.

+

Do not enter a Bitcoin or other cryptocurrency-wallet recovery phrase into BitLogin. This is a BitLogin-only phrase.

+
+ ${this.recoveryPhrase.split(" ").map((e, t) => `
${t + 1}.${i(e)}
`).join("")} +
+ ${this.renderError()} + + `; + case "verify-phrase": + return ` +

Verify your recovery phrase

+

Your phrase is hidden now so you can confirm you actually saved it. Enter the requested words below.

+ ${this.renderError()} +
+ ${this.confirmSlots.map( + (e) => ` + + + ` + ).join("")} + +
+ + `; + case "login": + return ` +

Sign in

+ ${this.renderError()} +
+ + + + + +
+ + + `; + case "recover-phrase": + return ` +

Recover with phrase

+

Enter your 12-word BitLogin recovery phrase.

+

Do not enter a Bitcoin or other cryptocurrency-wallet recovery phrase into BitLogin.

+ ${this.renderError()} +
+ + + + +

Only needed if relays are unreachable -- the phrase above is still required either way. The file alone can never recover an account by itself.

+ ${this.offlineExportFileNotice ? `
${i(this.offlineExportFileNotice)}
` : ""} + +
+ + `; + case "recover-new-credentials": { + const e = this.recoveredPreview?.chainWarning ? `
${i(this.recoveredPreview.chainWarning)}
` : "", t = this.busy ? 'Finishing recovery…' : "Finish recovery"; + return ` +

Identity recovered

+

Found your account. Restored ${this.recoveredPreview?.generalRelaysCount ?? 0} general relay(s) and ${this.recoveredPreview?.dmRelaysCount ?? 0} DM relay(s) from your public events.

+ ${e} +

Now set a new login name and password.

+ ${this.manualPasswordMode ? '
Offline guessing against this password can never be fully prevented. The generated passphrase remains the safer default.
' : `
${i(this.newCredentialAfterRecovery)}
`} + ${this.renderError()} +
+ + + ${this.manualPasswordMode ? this.renderManualPasswordFields() : ""} + +
+ + `; + } + case "dashboard": + return ` +

Signed in

+ ${this.renderWarnings()} +

${this.session?.npub ?? ""}

+ ${this.renderError()} + + ${this.lastSignedEventJson ? `
${i(this.lastSignedEventJson)}
` : ""} +
+ + + + `; + case "rollback-confirm": + return ` +

This looks like a stale or revoked credential

+
${i(this.rollbackMessage)}
+

This usually means the password just entered was rotated away in an earlier session on this device, and a relay hasn't caught up with that change. If you rotated your password, use the new one instead. Only continue if you're confident this is relay lag, not a stale credential.

+ + + + `; + case "change-password": + return ` +

Rotate password

+

Your old password's capsule will be tombstoned and a deletion request issued. This does not erase copies an attacker may already have downloaded, and a relay that hasn't processed the deletion may keep serving the old password's capsule until a device that has already seen the new generation refuses it.

+ ${this.manualPasswordMode ? '
Offline guessing against this password can never be fully prevented. The generated passphrase remains the safer default.
' : `
${i(this.changePasswordNewCredential)}
`} + ${this.renderError()} +
+ + + ${this.manualPasswordMode ? this.renderManualPasswordFields() : ""} + +
+ + + `; + case "export": + return ` +

Export identity

+

Your public identity (npub) is safe to share. Your private key (nsec) is not.

+ +
${i(this.session?.npub ?? "")}
+ + ${this.renderError()} + + ${this.exportedNsec ? `
Never share this. Anyone with it controls your identity.
${i( + this.exportedNsec + )}
` : ""} + + `; + default: + return ""; + } + } +} +function T(a, e) { + const t = Array.from({ length: a }, (r, o) => o), s = []; + for (let r = 0; r < e && t.length > 0; r++) { + const o = Math.floor(Math.random() * t.length); + s.push(t.splice(o, 1)[0]); + } + return s.sort((r, o) => r - o); +} +function i(a) { + return a.replace(/[&<>"']/gu, (e) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[e]); +} +customElements.get("bitlogin-auth") || customElements.define("bitlogin-auth", F); +window.bitlogin = { + version: "0.1.0", + isActiveSigner() { + return window.nostr?._bitlogin === !0; + }, + releaseSigner() { + const a = window; + return a.nostr?._bitlogin === !0 ? (delete a.nostr, window.dispatchEvent(new CustomEvent("bitlogin-signer-released")), !0) : !1; + } +}; +export { + F as BitLoginAuthElement, + E as WorkerClient, + $ as createNip07Provider +}; diff --git a/vendor/bitlogin/cryptoWorker.js b/vendor/bitlogin/cryptoWorker.js new file mode 100644 index 00000000..5895b0af --- /dev/null +++ b/vendor/bitlogin/cryptoWorker.js @@ -0,0 +1,3446 @@ +import { v as ee, g as E, s as W, c as Ot, K as Dt, b as Je, d as Ce, f as Le, h as ae, u as ye, j as qt, k as zt, l as X, m as ne, n as Ye, S as Ne, o as Te, q as we, D as ie, t as be, w as jt, x as $t, y as Gt, z as Vt, A as _e, B as ze, C as Qe, F as Bt, G as Wt, H as Ht, I as he, J as Xe, L as Ft, M as Jt, N as Ze, O as ke, P as ce, Q as Yt, T as et, U as Qt, V as Xt, W as Zt, X as er, Y as tt, Z as Ie, _ as tr, $ as rr, a0 as nr, a1 as or, a2 as ar, a as je, a3 as ir } from "./bitlogin-shared-QIBe5Omw.js"; +class oe extends Error { + reason; + constructor(t) { + super("Account not found or credentials incorrect."), this.name = "AccountNotFoundError", this.reason = t; + } +} +class Z extends Error { + constructor(t) { + super(t), this.name = "RegistrationFailedError"; + } +} +class rt extends Error { + constructor(t = "An account already exists with this login name and password. Sign in instead, or choose different credentials.") { + super(t), this.name = "AccountAlreadyExistsError"; + } +} +class nt extends Error { + constructor(t) { + super(t), this.name = "RecoveryFailedError"; + } +} +class ot extends Error { + seenGeneration; + capsuleGeneration; + constructor(t, r) { + super(`This credential capsule reports generation ${r}, but this device has already seen generation ${t}. Refusing to log in with older, possibly-revoked credentials.`), this.name = "RollbackDetectedError", this.seenGeneration = t, this.capsuleGeneration = r; + } +} +function at(e, t) { + const r = t ?? Math.floor(Date.now() / 1e3); + return e == null ? r : Math.max(r, e + 1); +} +const sr = { generation: -1, recoveryGeneration: -1 }; +function it(e) { + return `bitlogin:hwm:${e}`; +} +async function Se(e, t) { + const r = await e.get(it(t)); + return r ? JSON.parse(r) : sr; +} +async function st(e, t, r) { + const n = await Se(e, t), o = { + generation: Math.max(n.generation, r.generation ?? -1), + recoveryGeneration: Math.max(n.recoveryGeneration, r.recoveryGeneration ?? -1) + }; + return await e.set(it(t), JSON.stringify(o)), o; +} +function cr() { + const e = globalThis.WebSocket; + if (!e) + throw new Error("No global WebSocket implementation is available in this environment."); + return e; +} +class lr { + url; + ws = null; + connectPromise = null; + subs = /* @__PURE__ */ new Map(); + pendingPublishes = /* @__PURE__ */ new Map(); + authPrivateKey; + connectTimeoutMs; + authenticated = !1; + constructor(t, r = {}) { + this.url = t, this.authPrivateKey = r.authPrivateKey, this.connectTimeoutMs = r.connectTimeoutMs ?? 8e3; + } + async connect() { + return this.connectPromise ? this.connectPromise : (this.connectPromise = new Promise((t, r) => { + const n = cr(), o = new n(this.url); + this.ws = o; + const a = setTimeout(() => { + r(new Error(`Timed out connecting to relay ${this.url}`)); + }, this.connectTimeoutMs); + o.addEventListener("open", () => { + clearTimeout(a), t(); + }), o.addEventListener("error", () => { + clearTimeout(a), r(new Error(`WebSocket error connecting to relay ${this.url}`)); + }), o.addEventListener("close", () => { + this.connectPromise = null; + }), o.addEventListener("message", (i) => { + this.handleMessage(String(i.data)); + }); + }), this.connectPromise); + } + close() { + this.ws?.close(), this.ws = null, this.connectPromise = null, this.subs.clear(), this.pendingPublishes.clear(); + } + send(t) { + if (!this.ws) + throw new Error("Not connected to relay."); + this.ws.send(JSON.stringify(t)); + } + handleMessage(t) { + let r; + try { + r = JSON.parse(t); + } catch { + return; + } + if (!Array.isArray(r) || typeof r[0] != "string") + return; + const [n, ...o] = r; + if (n === "EVENT") { + const [a, i] = o, s = this.subs.get(a); + s && ee(i) && s.events.push(i); + return; + } + if (n === "EOSE") { + const [a] = o; + this.subs.get(a)?.onEose(); + return; + } + if (n === "OK") { + const [a, i, s] = o; + this.pendingPublishes.get(a)?.({ ok: i, message: s ?? "" }), this.pendingPublishes.delete(a); + return; + } + if (n === "AUTH") { + const [a] = o; + this.respondToAuthChallenge(a); + return; + } + } + async respondToAuthChallenge(t) { + if (!this.authPrivateKey) + return; + const r = E(this.authPrivateKey), n = W({ + pubkey: r, + created_at: Math.floor(Date.now() / 1e3), + kind: Dt, + tags: [ + ["relay", this.url], + ["challenge", t] + ], + content: "" + }, this.authPrivateKey); + this.authenticated = !0, this.send(["AUTH", n]); + } + isAuthenticated() { + return this.authenticated; + } + async publish(t, r = 8e3) { + return await this.connect(), new Promise((n) => { + const o = setTimeout(() => { + this.pendingPublishes.delete(t.id), n({ ok: !1, message: "timeout: no OK received from relay" }); + }, r); + this.pendingPublishes.set(t.id, (a) => { + clearTimeout(o), n(a); + }), this.send(["EVENT", t]); + }); + } + async queryOnce(t, r = 8e3) { + await this.connect(); + const n = Ot({ + pubkey: "0".repeat(64), + created_at: Date.now(), + kind: 0, + tags: [], + content: JSON.stringify(t) + Math.random() + }).slice(0, 16); + return new Promise((o) => { + const a = [], i = () => { + clearTimeout(s), this.subs.delete(n), this.send(["CLOSE", n]), o(a); + }, s = setTimeout(i, r); + this.subs.set(n, { events: a, onEose: i }), this.send(["REQ", n, t]); + }); + } +} +class C { + connections = /* @__PURE__ */ new Map(); + constructor(t, r = {}) { + for (const n of new Set(t)) + this.connections.set(n, new lr(n, r)); + } + get relayUrls() { + return [...this.connections.keys()]; + } + /** Queries every configured relay and waits for a quorum of responses (or all timeouts) before returning (§16.2). */ + async queryQuorum(t, r = 8e3) { + const n = [...this.connections.entries()], o = await Promise.all(n.map(async ([s, c]) => { + try { + const d = await c.queryOnce(t, r); + return { relayUrl: s, events: d, responded: !0 }; + } catch (d) { + return { relayUrl: s, events: [], responded: !1, error: d.message }; + } + })), a = o.filter((s) => s.responded).length, i = o.length; + return { + outcomes: o, + quorumMet: a >= Math.ceil(i / 2), + respondedCount: a, + totalCount: i + }; + } + /** Publishes an event to every configured relay, best-effort (§15.6, §24.4). */ + async publishAll(t, r = 8e3) { + const n = [...this.connections.entries()]; + return Promise.all(n.map(async ([o, a]) => { + try { + const i = await a.publish(t, r); + return { relayUrl: o, result: i }; + } catch (i) { + return { relayUrl: o, result: { ok: !1, message: i.message } }; + } + })); + } + closeAll() { + for (const t of this.connections.values()) + t.close(); + } +} +function F(e) { + return e.filter((t) => t.result.ok).length; +} +function ur(e, t, r) { + return W({ pubkey: E(e), created_at: r, kind: Je, tags: [], content: JSON.stringify(t) }, e); +} +function dr(e, t, r) { + const n = t.map((o) => { + const a = ["r", o.url]; + return o.read && !o.write && a.push("read"), o.write && !o.read && a.push("write"), a; + }); + return W({ pubkey: E(e), created_at: r, kind: Ce, tags: n, content: "" }, e); +} +function yr(e, t, r) { + return W({ + pubkey: E(e), + created_at: r, + kind: Le, + tags: t.map((n) => ["relay", n]), + content: "" + }, e); +} +function hr(e) { + return e.tags.filter((t) => t[0] === "r" && t[1]).map((t) => t[1]); +} +function pr(e) { + return e.tags.filter((t) => t[0] === "relay" && t[1]).map((t) => t[1]); +} +async function vr(e) { + const t = E(e.everydayPrivateKey), r = Math.floor(Date.now() / 1e3), n = [.../* @__PURE__ */ new Set([...e.generalRelays, ...e.dmRelays, ...e.discoveryRelays])], o = new C(n), [a, i, s] = await Promise.all([ + o.queryQuorum({ authors: [t], kinds: [Je] }), + o.queryQuorum({ authors: [t], kinds: [Ce] }), + o.queryQuorum({ authors: [t], kinds: [Le] }) + ]); + o.closeAll(); + const c = a.outcomes.some((u) => u.events.length > 0), d = i.outcomes.some((u) => u.events.length > 0), h = s.outcomes.some((u) => u.events.length > 0), m = new C(n); + let p = null, g = null, v = null; + const f = []; + if (!c && (e.name || e.about || e.picture)) { + const u = ur(e.everydayPrivateKey, { name: e.name, about: e.about, picture: e.picture }, r); + f.push(m.publishAll(u).then((y) => void (p = y))); + } + if (!d) { + const u = dr(e.everydayPrivateKey, e.generalRelays.map((y) => ({ url: y, read: !0, write: !0 })), r); + f.push(m.publishAll(u).then((y) => void (g = y))); + } + if (!h) { + const u = yr(e.everydayPrivateKey, e.dmRelays, r); + f.push(m.publishAll(u).then((y) => void (v = y))); + } + return await Promise.all(f), m.closeAll(), { + profilePublished: p !== null && F(p) > 0, + relayListAcknowledgedCount: g !== null ? F(g) : 0, + dmRelayListAcknowledgedCount: v !== null ? F(v) : 0, + profileSkippedExisting: c, + relayListSkippedExisting: d, + dmRelayListSkippedExisting: h + }; +} +async function le(e, t, r) { + const n = r.minAcks ?? 2, o = r.minReadbacks ?? 2, a = await e.publishAll(t, r.timeoutMs), i = F(a), c = (await e.queryQuorum({ kinds: [ae], authors: [t.pubkey], "#d": [r.dTag], limit: 5 }, r.timeoutMs)).outcomes.filter((d) => d.events.some((h) => h.id === t.id)).length; + return { + acknowledgedCount: i, + readbackVerifiedCount: c, + success: i >= n && c >= o + }; +} +function gr(e) { + const t = qt(e); + if (t === void 0) + throw new Error("Value is not JSON-serializable for canonicalization."); + return t; +} +function fr(e) { + return ye(gr(e)); +} +const ve = [1024, 2048, 4096], Y = 4; +function mr(e) { + const t = Y + e.length, r = ve.find((a) => a >= t); + if (r === void 0) + throw new Error(`Payload of ${e.length} bytes exceeds the largest padding bucket (${ve[ve.length - 1]} bytes minus ${Y}-byte length prefix).`); + const n = new Uint8Array(r); + return new DataView(n.buffer).setUint32(0, e.length, !1), n.set(e, Y), n; +} +function wr(e) { + if (!ve.includes(e.length)) + throw new Error(`Padded plaintext length ${e.length} does not match a known bucket.`); + const r = new DataView(e.buffer, e.byteOffset, e.byteLength).getUint32(0, !1); + if (r > e.length - Y) + throw new Error("Declared payload length exceeds the padded bucket size."); + const n = e.slice(Y, Y + r), o = e.slice(Y + r); + for (const a of o) + if (a !== 0) + throw new Error("Padding bytes are not all zero; capsule plaintext is malformed."); + return n; +} +function Me() { + const e = globalThis.crypto; + if (!e || !e.subtle) + throw new Error("WebCrypto SubtleCrypto is not available in this environment."); + return e; +} +async function ct(e) { + if (e.length !== 32) + throw new Error("AES-256-GCM key must be exactly 32 bytes."); + return Me().subtle.importKey("raw", e, "AES-GCM", !1, ["encrypt", "decrypt"]); +} +async function br(e, t, r) { + const n = zt(), o = await ct(e), a = await Me().subtle.encrypt({ name: "AES-GCM", iv: n, additionalData: r, tagLength: 128 }, o, t); + return { nonce: n, ciphertext: new Uint8Array(a) }; +} +async function kr(e, t, r, n) { + const o = await ct(e); + try { + const a = await Me().subtle.decrypt({ name: "AES-GCM", iv: t, additionalData: n, tagLength: 128 }, o, r); + return new Uint8Array(a); + } catch { + throw new Error("AES-256-GCM authentication failed: capsule is corrupted, tampered, or the wrong key was used."); + } +} +function lt(e) { + return ye(`bitlogin|password-capsule|v1|${e}|30078|bitlogin:password:v1`); +} +function ut(e) { + return ye(`bitlogin|recovery-capsule|v1|${e}|30078|bitlogin:recovery:v1`); +} +async function dt(e, t, r) { + const n = fr(e), o = mr(n), a = await br(t, o, r); + return { + version: 1, + algorithm: "aes-256-gcm", + nonce: X(a.nonce), + ciphertext: X(a.ciphertext) + }; +} +async function yt(e, t, r) { + if (e.version !== 1 || e.algorithm !== "aes-256-gcm") + throw new Error(`Unsupported capsule envelope version/algorithm: ${e.version}/${e.algorithm}`); + const n = ne(e.nonce), o = ne(e.ciphertext), a = await kr(t, n, o, r), i = wr(a); + return JSON.parse(Ye(i)); +} +class Ue extends Error { + constructor(t) { + super(t), this.name = "CapsuleValidationError"; + } +} +const _r = /* @__PURE__ */ new Set(["wss:", "ws:"]), Er = 1e6, Pr = /^[0-9a-f]{64}$/u; +function P(e, t) { + if (!e) + throw new Ue(t); +} +function ge(e) { + return typeof e == "string" && Pr.test(e); +} +function ht(e) { + P(Array.isArray(e), "vault_relay_hints must be an array (§12.4.7)."); + for (const t of e) { + P(typeof t == "string", "Each relay hint must be a string (§12.4.7)."); + let r; + try { + r = new URL(t); + } catch { + throw new Ue(`Invalid relay URL: ${String(t)} (§12.4.7)`); + } + P(_r.has(r.protocol), `Relay URL uses a disallowed scheme: ${t} (§12.4.7)`); + } +} +function pt(e) { + P(typeof e == "string", "account_id must be a string (§12.4.2)."); + let t; + try { + t = ne(e); + } catch { + throw new Ue("account_id is not valid base64url (§12.4.2)."); + } + P(t.length === 16, "account_id must decode to exactly 128 bits (§12.4.2)."); +} +function vt(e, t) { + P(typeof e == "string", "operational_private_key must be a string (§12.4.3)."); + const r = ne(e); + P(r.length === 32, "operational_private_key must be exactly 32 bytes (§12.4.3)."), P(Te(r), "operational_private_key is not a valid secp256k1 scalar (§12.4.3)."), P(ge(t), "operational_public_key must be lowercase 64-char hex (§12.4.4)."); + const n = E(r); + P(n === t, "operational_public_key does not match the derived public key (§12.4.4)."); +} +function gt(e, t) { + P(Number.isInteger(e) && e >= 0 && e <= Er, `${t} is out of supported bounds (§12.4.8).`); +} +function Kr(e) { + P(e.schema === we, `Unsupported or unknown schema: ${String(e.schema)} (§12.4.1)`), pt(e.account_id), gt(e.generation, "generation"), vt(e.operational_private_key, e.operational_public_key), P(ge(e.recovery_public_key), "recovery_public_key must be lowercase 64-char hex (§12.4.5)."), ht(e.vault_relay_hints); + const t = e.recovery_capsule_event; + P(!!t && typeof t == "object", "recovery_capsule_event must be present (§12.4.6)."), P(ee(t), "Embedded recovery_capsule_event has an invalid event id or signature (§12.4.6)."), P(t.pubkey === e.recovery_public_key, "Embedded recovery_capsule_event author does not match recovery_public_key (§12.4.5)."); +} +function Rr(e) { + P(e.schema === Ne, `Unsupported or unknown schema: ${String(e.schema)} (§12.4.1)`), pt(e.account_id), gt(e.recovery_generation, "recovery_generation"), P(e.previous_recovery_event_id === null || ge(e.previous_recovery_event_id), "previous_recovery_event_id must be null or lowercase 64-char hex (§12.3)."), vt(e.operational_private_key, e.operational_public_key), P(ge(e.recovery_public_key), "recovery_public_key must be lowercase 64-char hex."), ht(e.vault_relay_hints); +} +function xr(e) { + const t = new Map(e.map((n) => [n.recoveryGeneration, n])), r = [...e].sort((n, o) => n.recoveryGeneration - o.recoveryGeneration); + for (let n = 1; n < r.length; n++) { + const o = r[n], a = t.get(o.recoveryGeneration - 1); + if (o.previousRecoveryEventId === null) + return { consistent: !1, warning: `Generation ${o.recoveryGeneration} has a null previous-event link but is not the first generation.` }; + if (a && o.previousRecoveryEventId !== a.eventId) + return { + consistent: !1, + warning: `Recovery generation chain is broken between generation ${a.recoveryGeneration} and ${o.recoveryGeneration}: possible replay or relay misbehavior.` + }; + } + return { consistent: !0 }; +} +async function Oe(e) { + const t = E(e.locatorPrivateKey), r = await dt(e.payload, e.capsuleKey, lt(t)); + return W({ + pubkey: t, + created_at: e.payload.created_at, + kind: ae, + tags: [["d", ie]], + content: JSON.stringify(r) + }, e.locatorPrivateKey); +} +function Ar(e) { + const t = E(e.oldLocatorPrivateKey); + return W({ + pubkey: t, + created_at: e.createdAt, + kind: ae, + tags: [["d", ie]], + content: "" + }, e.oldLocatorPrivateKey); +} +async function Cr(e, t) { + if (!ee(e)) + throw new Error("Credential capsule event has an invalid id or signature."); + const r = JSON.parse(e.content), n = await yt(r, t, lt(e.pubkey)); + return Kr(n), n; +} +async function ft(e) { + const t = E(e.recoveryPrivateKey), r = await dt(e.payload, e.capsuleKey, ut(t)); + return W({ + pubkey: t, + created_at: e.payload.created_at, + kind: ae, + tags: [["d", be]], + content: JSON.stringify(r) + }, e.recoveryPrivateKey); +} +async function mt(e, t) { + if (!ee(e)) + throw new Error("Recovery capsule event has an invalid id or signature."); + const r = JSON.parse(e.content), n = await yt(r, t, ut(e.pubkey)); + return Rr(n), n; +} +function Lr(e) { + const t = /* @__PURE__ */ new Map(); + for (const r of e) + ee(r) && t.set(r.id, r); + return [...t.values()].sort((r, n) => n.created_at - r.created_at); +} +async function wt(e, t, r, n, o) { + const a = await e.queryQuorum({ kinds: [ae], authors: [t], "#d": [r], limit: 5 }, o), i = a.outcomes.flatMap((p) => p.events), s = Lr(i), c = []; + for (const p of s) + try { + const g = await n(p); + c.push({ event: p, payload: g }); + } catch (g) { + c.push({ event: p, payload: null, error: g.message }); + } + const d = c.find((p) => p.payload !== null) ?? null, h = a.outcomes.filter((p) => p.responded && p.events.length > 0).map((p) => p.events.slice().sort((g, v) => v.created_at - g.created_at)[0].id), m = new Set(h).size > 1; + return { + quorumMet: a.quorumMet, + respondedCount: a.respondedCount, + totalCount: a.totalCount, + candidates: c, + best: d, + relayDisagreement: m + }; +} +async function fe(e, t, r, n = 8e3) { + return wt(e, t, ie, (o) => Cr(o, r), n); +} +async function Nr(e, t, r, n = 8e3) { + return wt(e, t, be, (o) => mt(o, r), n); +} +function Tr(e) { + const t = e.filter((r) => r.payload !== null); + return t.length < 2 ? { consistent: !0 } : xr(t.map((r) => ({ + eventId: r.event.id, + recoveryGeneration: r.payload.recovery_generation, + previousRecoveryEventId: r.payload.previous_recovery_event_id + }))); +} +async function Ir(e, t, r, n) { + const [o, a] = await Promise.all([ + e.publishAll(t, n), + e.publishAll(r, n) + ]); + return { + credentialAcknowledgedCount: F(o), + recoveryAcknowledgedCount: F(a), + relaysTried: e.relayUrls.length + }; +} +const bt = `abandon +ability +able +about +above +absent +absorb +abstract +absurd +abuse +access +accident +account +accuse +achieve +acid +acoustic +acquire +across +act +action +actor +actress +actual +adapt +add +addict +address +adjust +admit +adult +advance +advice +aerobic +affair +afford +afraid +again +age +agent +agree +ahead +aim +air +airport +aisle +alarm +album +alcohol +alert +alien +all +alley +allow +almost +alone +alpha +already +also +alter +always +amateur +amazing +among +amount +amused +analyst +anchor +ancient +anger +angle +angry +animal +ankle +announce +annual +another +answer +antenna +antique +anxiety +any +apart +apology +appear +apple +approve +april +arch +arctic +area +arena +argue +arm +armed +armor +army +around +arrange +arrest +arrive +arrow +art +artefact +artist +artwork +ask +aspect +assault +asset +assist +assume +asthma +athlete +atom +attack +attend +attitude +attract +auction +audit +august +aunt +author +auto +autumn +average +avocado +avoid +awake +aware +away +awesome +awful +awkward +axis +baby +bachelor +bacon +badge +bag +balance +balcony +ball +bamboo +banana +banner +bar +barely +bargain +barrel +base +basic +basket +battle +beach +bean +beauty +because +become +beef +before +begin +behave +behind +believe +below +belt +bench +benefit +best +betray +better +between +beyond +bicycle +bid +bike +bind +biology +bird +birth +bitter +black +blade +blame +blanket +blast +bleak +bless +blind +blood +blossom +blouse +blue +blur +blush +board +boat +body +boil +bomb +bone +bonus +book +boost +border +boring +borrow +boss +bottom +bounce +box +boy +bracket +brain +brand +brass +brave +bread +breeze +brick +bridge +brief +bright +bring +brisk +broccoli +broken +bronze +broom +brother +brown +brush +bubble +buddy +budget +buffalo +build +bulb +bulk +bullet +bundle +bunker +burden +burger +burst +bus +business +busy +butter +buyer +buzz +cabbage +cabin +cable +cactus +cage +cake +call +calm +camera +camp +can +canal +cancel +candy +cannon +canoe +canvas +canyon +capable +capital +captain +car +carbon +card +cargo +carpet +carry +cart +case +cash +casino +castle +casual +cat +catalog +catch +category +cattle +caught +cause +caution +cave +ceiling +celery +cement +census +century +cereal +certain +chair +chalk +champion +change +chaos +chapter +charge +chase +chat +cheap +check +cheese +chef +cherry +chest +chicken +chief +child +chimney +choice +choose +chronic +chuckle +chunk +churn +cigar +cinnamon +circle +citizen +city +civil +claim +clap +clarify +claw +clay +clean +clerk +clever +click +client +cliff +climb +clinic +clip +clock +clog +close +cloth +cloud +clown +club +clump +cluster +clutch +coach +coast +coconut +code +coffee +coil +coin +collect +color +column +combine +come +comfort +comic +common +company +concert +conduct +confirm +congress +connect +consider +control +convince +cook +cool +copper +copy +coral +core +corn +correct +cost +cotton +couch +country +couple +course +cousin +cover +coyote +crack +cradle +craft +cram +crane +crash +crater +crawl +crazy +cream +credit +creek +crew +cricket +crime +crisp +critic +crop +cross +crouch +crowd +crucial +cruel +cruise +crumble +crunch +crush +cry +crystal +cube +culture +cup +cupboard +curious +current +curtain +curve +cushion +custom +cute +cycle +dad +damage +damp +dance +danger +daring +dash +daughter +dawn +day +deal +debate +debris +decade +december +decide +decline +decorate +decrease +deer +defense +define +defy +degree +delay +deliver +demand +demise +denial +dentist +deny +depart +depend +deposit +depth +deputy +derive +describe +desert +design +desk +despair +destroy +detail +detect +develop +device +devote +diagram +dial +diamond +diary +dice +diesel +diet +differ +digital +dignity +dilemma +dinner +dinosaur +direct +dirt +disagree +discover +disease +dish +dismiss +disorder +display +distance +divert +divide +divorce +dizzy +doctor +document +dog +doll +dolphin +domain +donate +donkey +donor +door +dose +double +dove +draft +dragon +drama +drastic +draw +dream +dress +drift +drill +drink +drip +drive +drop +drum +dry +duck +dumb +dune +during +dust +dutch +duty +dwarf +dynamic +eager +eagle +early +earn +earth +easily +east +easy +echo +ecology +economy +edge +edit +educate +effort +egg +eight +either +elbow +elder +electric +elegant +element +elephant +elevator +elite +else +embark +embody +embrace +emerge +emotion +employ +empower +empty +enable +enact +end +endless +endorse +enemy +energy +enforce +engage +engine +enhance +enjoy +enlist +enough +enrich +enroll +ensure +enter +entire +entry +envelope +episode +equal +equip +era +erase +erode +erosion +error +erupt +escape +essay +essence +estate +eternal +ethics +evidence +evil +evoke +evolve +exact +example +excess +exchange +excite +exclude +excuse +execute +exercise +exhaust +exhibit +exile +exist +exit +exotic +expand +expect +expire +explain +expose +express +extend +extra +eye +eyebrow +fabric +face +faculty +fade +faint +faith +fall +false +fame +family +famous +fan +fancy +fantasy +farm +fashion +fat +fatal +father +fatigue +fault +favorite +feature +february +federal +fee +feed +feel +female +fence +festival +fetch +fever +few +fiber +fiction +field +figure +file +film +filter +final +find +fine +finger +finish +fire +firm +first +fiscal +fish +fit +fitness +fix +flag +flame +flash +flat +flavor +flee +flight +flip +float +flock +floor +flower +fluid +flush +fly +foam +focus +fog +foil +fold +follow +food +foot +force +forest +forget +fork +fortune +forum +forward +fossil +foster +found +fox +fragile +frame +frequent +fresh +friend +fringe +frog +front +frost +frown +frozen +fruit +fuel +fun +funny +furnace +fury +future +gadget +gain +galaxy +gallery +game +gap +garage +garbage +garden +garlic +garment +gas +gasp +gate +gather +gauge +gaze +general +genius +genre +gentle +genuine +gesture +ghost +giant +gift +giggle +ginger +giraffe +girl +give +glad +glance +glare +glass +glide +glimpse +globe +gloom +glory +glove +glow +glue +goat +goddess +gold +good +goose +gorilla +gospel +gossip +govern +gown +grab +grace +grain +grant +grape +grass +gravity +great +green +grid +grief +grit +grocery +group +grow +grunt +guard +guess +guide +guilt +guitar +gun +gym +habit +hair +half +hammer +hamster +hand +happy +harbor +hard +harsh +harvest +hat +have +hawk +hazard +head +health +heart +heavy +hedgehog +height +hello +helmet +help +hen +hero +hidden +high +hill +hint +hip +hire +history +hobby +hockey +hold +hole +holiday +hollow +home +honey +hood +hope +horn +horror +horse +hospital +host +hotel +hour +hover +hub +huge +human +humble +humor +hundred +hungry +hunt +hurdle +hurry +hurt +husband +hybrid +ice +icon +idea +identify +idle +ignore +ill +illegal +illness +image +imitate +immense +immune +impact +impose +improve +impulse +inch +include +income +increase +index +indicate +indoor +industry +infant +inflict +inform +inhale +inherit +initial +inject +injury +inmate +inner +innocent +input +inquiry +insane +insect +inside +inspire +install +intact +interest +into +invest +invite +involve +iron +island +isolate +issue +item +ivory +jacket +jaguar +jar +jazz +jealous +jeans +jelly +jewel +job +join +joke +journey +joy +judge +juice +jump +jungle +junior +junk +just +kangaroo +keen +keep +ketchup +key +kick +kid +kidney +kind +kingdom +kiss +kit +kitchen +kite +kitten +kiwi +knee +knife +knock +know +lab +label +labor +ladder +lady +lake +lamp +language +laptop +large +later +latin +laugh +laundry +lava +law +lawn +lawsuit +layer +lazy +leader +leaf +learn +leave +lecture +left +leg +legal +legend +leisure +lemon +lend +length +lens +leopard +lesson +letter +level +liar +liberty +library +license +life +lift +light +like +limb +limit +link +lion +liquid +list +little +live +lizard +load +loan +lobster +local +lock +logic +lonely +long +loop +lottery +loud +lounge +love +loyal +lucky +luggage +lumber +lunar +lunch +luxury +lyrics +machine +mad +magic +magnet +maid +mail +main +major +make +mammal +man +manage +mandate +mango +mansion +manual +maple +marble +march +margin +marine +market +marriage +mask +mass +master +match +material +math +matrix +matter +maximum +maze +meadow +mean +measure +meat +mechanic +medal +media +melody +melt +member +memory +mention +menu +mercy +merge +merit +merry +mesh +message +metal +method +middle +midnight +milk +million +mimic +mind +minimum +minor +minute +miracle +mirror +misery +miss +mistake +mix +mixed +mixture +mobile +model +modify +mom +moment +monitor +monkey +monster +month +moon +moral +more +morning +mosquito +mother +motion +motor +mountain +mouse +move +movie +much +muffin +mule +multiply +muscle +museum +mushroom +music +must +mutual +myself +mystery +myth +naive +name +napkin +narrow +nasty +nation +nature +near +neck +need +negative +neglect +neither +nephew +nerve +nest +net +network +neutral +never +news +next +nice +night +noble +noise +nominee +noodle +normal +north +nose +notable +note +nothing +notice +novel +now +nuclear +number +nurse +nut +oak +obey +object +oblige +obscure +observe +obtain +obvious +occur +ocean +october +odor +off +offer +office +often +oil +okay +old +olive +olympic +omit +once +one +onion +online +only +open +opera +opinion +oppose +option +orange +orbit +orchard +order +ordinary +organ +orient +original +orphan +ostrich +other +outdoor +outer +output +outside +oval +oven +over +own +owner +oxygen +oyster +ozone +pact +paddle +page +pair +palace +palm +panda +panel +panic +panther +paper +parade +parent +park +parrot +party +pass +patch +path +patient +patrol +pattern +pause +pave +payment +peace +peanut +pear +peasant +pelican +pen +penalty +pencil +people +pepper +perfect +permit +person +pet +phone +photo +phrase +physical +piano +picnic +picture +piece +pig +pigeon +pill +pilot +pink +pioneer +pipe +pistol +pitch +pizza +place +planet +plastic +plate +play +please +pledge +pluck +plug +plunge +poem +poet +point +polar +pole +police +pond +pony +pool +popular +portion +position +possible +post +potato +pottery +poverty +powder +power +practice +praise +predict +prefer +prepare +present +pretty +prevent +price +pride +primary +print +priority +prison +private +prize +problem +process +produce +profit +program +project +promote +proof +property +prosper +protect +proud +provide +public +pudding +pull +pulp +pulse +pumpkin +punch +pupil +puppy +purchase +purity +purpose +purse +push +put +puzzle +pyramid +quality +quantum +quarter +question +quick +quit +quiz +quote +rabbit +raccoon +race +rack +radar +radio +rail +rain +raise +rally +ramp +ranch +random +range +rapid +rare +rate +rather +raven +raw +razor +ready +real +reason +rebel +rebuild +recall +receive +recipe +record +recycle +reduce +reflect +reform +refuse +region +regret +regular +reject +relax +release +relief +rely +remain +remember +remind +remove +render +renew +rent +reopen +repair +repeat +replace +report +require +rescue +resemble +resist +resource +response +result +retire +retreat +return +reunion +reveal +review +reward +rhythm +rib +ribbon +rice +rich +ride +ridge +rifle +right +rigid +ring +riot +ripple +risk +ritual +rival +river +road +roast +robot +robust +rocket +romance +roof +rookie +room +rose +rotate +rough +round +route +royal +rubber +rude +rug +rule +run +runway +rural +sad +saddle +sadness +safe +sail +salad +salmon +salon +salt +salute +same +sample +sand +satisfy +satoshi +sauce +sausage +save +say +scale +scan +scare +scatter +scene +scheme +school +science +scissors +scorpion +scout +scrap +screen +script +scrub +sea +search +season +seat +second +secret +section +security +seed +seek +segment +select +sell +seminar +senior +sense +sentence +series +service +session +settle +setup +seven +shadow +shaft +shallow +share +shed +shell +sheriff +shield +shift +shine +ship +shiver +shock +shoe +shoot +shop +short +shoulder +shove +shrimp +shrug +shuffle +shy +sibling +sick +side +siege +sight +sign +silent +silk +silly +silver +similar +simple +since +sing +siren +sister +situate +six +size +skate +sketch +ski +skill +skin +skirt +skull +slab +slam +sleep +slender +slice +slide +slight +slim +slogan +slot +slow +slush +small +smart +smile +smoke +smooth +snack +snake +snap +sniff +snow +soap +soccer +social +sock +soda +soft +solar +soldier +solid +solution +solve +someone +song +soon +sorry +sort +soul +sound +soup +source +south +space +spare +spatial +spawn +speak +special +speed +spell +spend +sphere +spice +spider +spike +spin +spirit +split +spoil +sponsor +spoon +sport +spot +spray +spread +spring +spy +square +squeeze +squirrel +stable +stadium +staff +stage +stairs +stamp +stand +start +state +stay +steak +steel +stem +step +stereo +stick +still +sting +stock +stomach +stone +stool +story +stove +strategy +street +strike +strong +struggle +student +stuff +stumble +style +subject +submit +subway +success +such +sudden +suffer +sugar +suggest +suit +summer +sun +sunny +sunset +super +supply +supreme +sure +surface +surge +surprise +surround +survey +suspect +sustain +swallow +swamp +swap +swarm +swear +sweet +swift +swim +swing +switch +sword +symbol +symptom +syrup +system +table +tackle +tag +tail +talent +talk +tank +tape +target +task +taste +tattoo +taxi +teach +team +tell +ten +tenant +tennis +tent +term +test +text +thank +that +theme +then +theory +there +they +thing +this +thought +three +thrive +throw +thumb +thunder +ticket +tide +tiger +tilt +timber +time +tiny +tip +tired +tissue +title +toast +tobacco +today +toddler +toe +together +toilet +token +tomato +tomorrow +tone +tongue +tonight +tool +tooth +top +topic +topple +torch +tornado +tortoise +toss +total +tourist +toward +tower +town +toy +track +trade +traffic +tragic +train +transfer +trap +trash +travel +tray +treat +tree +trend +trial +tribe +trick +trigger +trim +trip +trophy +trouble +truck +true +truly +trumpet +trust +truth +try +tube +tuition +tumble +tuna +tunnel +turkey +turn +turtle +twelve +twenty +twice +twin +twist +two +type +typical +ugly +umbrella +unable +unaware +uncle +uncover +under +undo +unfair +unfold +unhappy +uniform +unique +unit +universe +unknown +unlock +until +unusual +unveil +update +upgrade +uphold +upon +upper +upset +urban +urge +usage +use +used +useful +useless +usual +utility +vacant +vacuum +vague +valid +valley +valve +van +vanish +vapor +various +vast +vault +vehicle +velvet +vendor +venture +venue +verb +verify +version +very +vessel +veteran +viable +vibrant +vicious +victory +video +view +village +vintage +violin +virtual +virus +visa +visit +visual +vital +vivid +vocal +voice +void +volcano +volume +vote +voyage +wage +wagon +wait +walk +wall +walnut +want +warfare +warm +warrior +wash +wasp +waste +water +wave +way +wealth +weapon +wear +weasel +weather +web +wedding +weekend +weird +welcome +west +wet +whale +what +wheat +wheel +when +where +whip +whisper +wide +width +wife +wild +will +win +window +wine +wing +wink +winner +winter +wire +wisdom +wise +wish +witness +wolf +woman +wonder +wood +wool +word +work +world +worry +worth +wrap +wreck +wrestle +wrist +write +wrong +yard +year +yellow +you +young +youth +zebra +zero +zone +zoo`.split(` +`); +function Sr(e, t, r, n) { + Gt(e); + const o = Vt({ dkLen: 32, asyncTick: 10 }, n), { c: a, dkLen: i, asyncTick: s } = o; + if (_e(a), _e(i), _e(s), a < 1) + throw new Error("iterations (c) should be >= 1"); + const c = ze(t), d = ze(r), h = new Uint8Array(i), m = Qe.create(e, c), p = m._cloneInto().update(d); + return { c: a, dkLen: i, asyncTick: s, DK: h, PRF: m, PRFSalt: p }; +} +function Mr(e, t, r, n, o) { + return e.destroy(), t.destroy(), n && n.destroy(), Bt(o), r; +} +async function Ur(e, t, r, n) { + const { c: o, dkLen: a, asyncTick: i, DK: s, PRF: c, PRFSalt: d } = Sr(e, t, r, n); + let h; + const m = new Uint8Array(4), p = jt(m), g = new Uint8Array(c.outputLen); + for (let v = 1, f = 0; f < a; v++, f += c.outputLen) { + const u = s.subarray(f, f + c.outputLen); + p.setInt32(0, v, !1), (h = d._cloneInto(h)).update(m).digestInto(g), u.set(g.subarray(0, u.length)), await $t(o - 1, i, () => { + c._cloneInto(h).update(g).digestInto(g); + for (let y = 0; y < u.length; y++) + u[y] ^= g[y]; + }); + } + return Mr(c, d, s, h, g); +} +/*! scure-bip39 - MIT License (c) 2022 Patricio Palladino, Paul Miller (paulmillr.com) */ +const Or = (e) => e[0] === "あいこくしん"; +function kt(e) { + if (typeof e != "string") + throw new TypeError("invalid mnemonic type: " + typeof e); + return e.normalize("NFKD"); +} +function _t(e) { + const t = kt(e), r = t.split(" "); + if (![12, 15, 18, 21, 24].includes(r.length)) + throw new Error("Invalid mnemonic"); + return { nfkd: t, words: r }; +} +function Et(e) { + Ht(e, 16, 20, 24, 28, 32); +} +const Dr = (e) => { + const t = 8 - e.length / 4; + return new Uint8Array([Xe(e)[0] >> t << t]); +}; +function Pt(e) { + if (!Array.isArray(e) || e.length !== 2048 || typeof e[0] != "string") + throw new Error("Wordlist: expected array of 2048 strings"); + return e.forEach((t) => { + if (typeof t != "string") + throw new Error("wordlist: non-string element: " + t); + }), he.chain(he.checksum(1, Dr), he.radix2(11, !0), he.alphabet(e)); +} +function qr(e, t) { + const { words: r } = _t(e), n = Pt(t).decode(r); + return Et(n), n; +} +function zr(e, t) { + return Et(e), Pt(t).encode(e).join(Or(t) ? " " : " "); +} +function jr(e, t) { + try { + qr(e, t); + } catch { + return !1; + } + return !0; +} +const $r = (e) => kt("mnemonic" + e); +function Gr(e, t = "") { + return Ur(Wt, _t(e).nfkd, $r(t), { c: 2048, dkLen: 64 }); +} +function Vr(e) { + if (e.length !== 16) + throw new Error("Recovery phrase entropy must be exactly 128 bits (16 bytes)."); + return zr(e, bt); +} +function Br(e) { + try { + return jr(Kt(e), bt); + } catch { + return !1; + } +} +function Kt(e) { + return e.trim().normalize("NFKD").split(/\s+/u).join(" "); +} +async function Rt(e) { + return Gr(Kt(e), ""); +} +const De = Ft.id, ue = "aes-256-gcm-v1", de = "bitlogin-bip39-hkdf-v1"; +async function xt(e) { + const t = ke(e.loginName), r = e.now ?? Math.floor(Date.now() / 1e3), { locatorPrivateKey: n, capsuleKey: o } = await ce(e.password, t), a = E(n), i = new C(e.vaultRelayUrls, { authPrivateKey: n }); + let s; + try { + s = await fe(i, a, o, e.timeoutMs); + } finally { + i.closeAll(); + } + if (!s.quorumMet) + throw new Z("Couldn't verify this login name and password aren't already registered. Please retry, or add more vault relays."); + if (s.candidates.length > 0) + throw new rt(); + const c = Vr(Yt()), d = await Rt(c), { recoveryPrivateKey: h, capsuleKey: m } = et(d), p = E(h), g = e.everydayPrivateKey !== void 0; + if (g && !Te(e.everydayPrivateKey)) + throw new Z("The provided key is not a valid secp256k1 private key."); + const v = g ? e.everydayPrivateKey : Qt(), f = E(v), u = X(Xt()), y = { + schema: Ne, + account_id: u, + recovery_generation: 0, + previous_recovery_event_id: null, + operational_private_key: X(v), + operational_public_key: f, + recovery_public_key: p, + created_at: r, + vault_relay_hints: e.vaultRelayUrls, + protocol: { capsule_encryption: ue, recovery_derivation: de } + }, k = await ft({ + recoveryPrivateKey: h, + capsuleKey: m, + payload: y + }), b = { + schema: we, + account_id: u, + generation: 0, + operational_private_key: X(v), + operational_public_key: f, + recovery_public_key: p, + recovery_capsule_event: k, + created_at: r, + vault_relay_hints: e.vaultRelayUrls, + protocol: { + password_kdf: De, + capsule_encryption: ue, + recovery_derivation: de + } + }, K = await Oe({ + locatorPrivateKey: n, + capsuleKey: o, + payload: b + }), G = new C(e.vaultRelayUrls, { authPrivateKey: h }), _ = await le(G, k, { + dTag: be, + minAcks: e.minAcknowledgements, + timeoutMs: e.timeoutMs + }); + G.closeAll(); + const R = new C(e.vaultRelayUrls, { authPrivateKey: n }), x = await le(R, K, { + dTag: ie, + minAcks: e.minAcknowledgements, + timeoutMs: e.timeoutMs + }); + if (R.closeAll(), !_.success || !x.success) + throw new Z("Registration did not reach the required relay acknowledgement and readback quorum. Please retry, or add more vault relays."); + return { + normalizedLoginName: t, + recoveryPhrase: c, + everydayPrivateKey: v, + everydayPublicKey: f, + recoveryPublicKey: p, + locatorPublicKey: a, + accountId: u, + imported: g, + credentialEvent: K, + recoveryEvent: k, + credentialPublish: x, + recoveryPublish: _ + }; +} +async function Wr(e) { + const { nsecOrHex: t, ...r } = e, n = At(t); + return xt({ ...r, everydayPrivateKey: n }); +} +function At(e) { + const t = e.trim(); + let r; + if (t.startsWith("nsec1")) + r = Jt(t); + else if (/^[0-9a-fA-F]{64}$/u.test(t)) + r = Ze(t.toLowerCase()); + else + throw new Z("Enter a valid nsec (nsec1…) or a 64-character hex private key."); + if (!Te(r)) + throw new Z("The provided key is not a valid secp256k1 private key."); + return r; +} +class Ct { + map = /* @__PURE__ */ new Map(); + async get(t) { + return this.map.get(t); + } + async set(t, r) { + this.map.set(t, r); + } + async delete(t) { + this.map.delete(t); + } +} +async function Hr(e) { + const t = ke(e.loginName), { locatorPrivateKey: r, capsuleKey: n } = await ce(e.password, t), o = E(r), a = new C(e.vaultRelayUrls, { authPrivateKey: r }); + try { + const i = await fe(a, o, n, e.timeoutMs); + if (!i.quorumMet) + throw new oe("quorum-not-met"); + if (!i.best) + throw new oe(i.candidates.length > 0 ? "no-valid-candidate" : "no-matching-event"); + const s = i.best.payload, c = e.store ?? new Ct(), d = await Se(c, s.operational_public_key), h = s.generation < d.generation; + if (h && !e.acknowledgeRollback) + throw new ot(d.generation, s.generation); + const m = h ? `This device previously saw credential generation ${d.generation}, but the accepted capsule is generation ${s.generation}. Relays may be serving stale data, or an old capsule is being replayed.` : void 0; + await st(c, s.operational_public_key, { generation: s.generation }); + const p = i.relayDisagreement ? 'Configured relays returned different credential capsules as "latest" for this account. Some relays may be stale, censored, or malicious.' : void 0; + return { + everydayPrivateKey: ne(s.operational_private_key), + everydayPublicKey: s.operational_public_key, + recoveryPublicKey: s.recovery_public_key, + accountId: s.account_id, + generation: s.generation, + credentialEvent: i.best.event, + recoveryCapsuleEvent: s.recovery_capsule_event, + rollbackWarning: m, + relayDisagreementWarning: p + }; + } finally { + a.closeAll(); + } +} +function $e(e) { + return e.filter((t) => ee(t)).sort((t, r) => r.created_at - t.created_at)[0]; +} +async function Fr(e) { + if (!Br(e.phrase)) + throw new nt("This does not look like a valid 12-word BitLogin recovery phrase."); + const t = await Rt(e.phrase), { recoveryPrivateKey: r, capsuleKey: n } = et(t), o = E(r), a = new C(e.vaultRelayUrls, { authPrivateKey: r }); + let i; + try { + i = await Nr(a, o, n, e.timeoutMs); + } finally { + a.closeAll(); + } + if (e.offlineRecoveryCapsuleEvents?.length) { + const f = []; + for (const b of e.offlineRecoveryCapsuleEvents) + if (ee(b)) + try { + f.push({ event: b, payload: await mt(b, n) }); + } catch (K) { + f.push({ event: b, payload: null, error: K.message }); + } + const u = /* @__PURE__ */ new Map(); + for (const b of [...i.candidates, ...f]) + u.set(b.event.id, b); + const y = [...u.values()].sort((b, K) => K.event.created_at - b.event.created_at), k = y.find((b) => b.payload !== null) ?? null; + i = { ...i, candidates: y, best: k, quorumMet: i.quorumMet || k !== null }; + } + if (!i.quorumMet) + throw new oe("quorum-not-met"); + if (!i.best) + throw new oe(i.candidates.length > 0 ? "no-valid-candidate" : "no-matching-event"); + const s = i.best.payload, c = Tr(i.candidates), d = ne(s.operational_private_key), h = s.operational_public_key, m = [.../* @__PURE__ */ new Set([...e.discoveryRelayUrls, ...s.vault_relay_hints])], p = new C(m); + let g = [], v = []; + try { + const f = await p.queryQuorum({ kinds: [Ce], authors: [h], limit: 5 }, e.timeoutMs), u = $e(f.outcomes.flatMap((b) => b.events)); + u && (g = hr(u)); + const y = await p.queryQuorum({ kinds: [Le], authors: [h], limit: 5 }, e.timeoutMs), k = $e(y.outcomes.flatMap((b) => b.events)); + k && (v = pr(k)); + } finally { + p.closeAll(); + } + return { + everydayPrivateKey: d, + everydayPublicKey: h, + recoveryPrivateKey: r, + recoveryPublicKey: o, + recoveryCapsuleKey: n, + accountId: s.account_id, + currentRecoveryEvent: i.best.event, + currentRecoveryPayload: s, + generalRelays: g, + dmRelays: v, + chainWarning: c.consistent ? void 0 : c.warning + }; +} +async function Jr(e) { + const t = e.now ?? Math.floor(Date.now() / 1e3), r = ke(e.newLoginName), { recovered: n } = e, o = { + schema: Ne, + account_id: n.accountId, + recovery_generation: n.currentRecoveryPayload.recovery_generation + 1, + previous_recovery_event_id: n.currentRecoveryEvent.id, + operational_private_key: X(n.everydayPrivateKey), + operational_public_key: n.everydayPublicKey, + recovery_public_key: n.recoveryPublicKey, + created_at: at(n.currentRecoveryEvent.created_at, t), + vault_relay_hints: e.vaultRelayUrls, + protocol: { capsule_encryption: ue, recovery_derivation: de } + }, a = await ft({ + recoveryPrivateKey: n.recoveryPrivateKey, + capsuleKey: n.recoveryCapsuleKey, + payload: o + }), i = new C(e.vaultRelayUrls, { authPrivateKey: n.recoveryPrivateKey }), s = await le(i, a, { + dTag: be, + minAcks: e.minAcknowledgements, + timeoutMs: e.timeoutMs + }); + i.closeAll(); + const { locatorPrivateKey: c, capsuleKey: d } = await ce(e.newPassword, r), h = E(c), m = { + schema: we, + account_id: n.accountId, + generation: 0, + operational_private_key: X(n.everydayPrivateKey), + operational_public_key: n.everydayPublicKey, + recovery_public_key: n.recoveryPublicKey, + recovery_capsule_event: a, + created_at: t, + vault_relay_hints: e.vaultRelayUrls, + protocol: { + password_kdf: De, + capsule_encryption: ue, + recovery_derivation: de + } + }, p = await Oe({ locatorPrivateKey: c, capsuleKey: d, payload: m }), g = new C(e.vaultRelayUrls, { authPrivateKey: c }), v = await le(g, p, { + dTag: ie, + minAcks: e.minAcknowledgements, + timeoutMs: e.timeoutMs + }); + if (g.closeAll(), !s.success || !v.success) + throw new nt("Could not publish the refreshed recovery and credential capsules to enough relays. Please retry."); + return { normalizedLoginName: r, locatorPublicKey: h, credentialEvent: p, refreshedRecoveryEvent: a, credentialPublish: v, recoveryPublish: s }; +} +function Yr(e) { + const t = E(e.privateKey); + return W({ + pubkey: t, + created_at: e.createdAt, + kind: Zt, + tags: [ + ["e", e.eventIdToDelete], + ["k", String(e.deletedEventKind)] + ], + content: e.reason ?? "" + }, e.privateKey); +} +async function Qr(e) { + const t = e.now ?? Math.floor(Date.now() / 1e3), r = ke(e.loginName), n = await ce(e.oldPassword, r), o = E(n.locatorPrivateKey), a = new C(e.vaultRelayUrls, { authPrivateKey: n.locatorPrivateKey }); + let i; + try { + i = await fe(a, o, n.capsuleKey, e.timeoutMs); + } finally { + a.closeAll(); + } + if (!i.quorumMet) + throw new oe("quorum-not-met"); + if (!i.best) + throw new oe(i.candidates.length > 0 ? "no-valid-candidate" : "no-matching-event"); + const s = i.best.payload, c = i.best.event, d = e.store ?? new Ct(), h = await Se(d, s.operational_public_key); + if (s.generation < h.generation && !e.acknowledgeRollback) + throw new ot(h.generation, s.generation); + const m = await ce(e.newPassword, r), p = E(m.locatorPrivateKey), g = new C(e.vaultRelayUrls, { authPrivateKey: m.locatorPrivateKey }); + let v; + try { + v = await fe(g, p, m.capsuleKey, e.timeoutMs); + } finally { + g.closeAll(); + } + if (!v.quorumMet) + throw new Z("Couldn't verify the new password isn't already registered under this login name. Please retry, or add more vault relays."); + if (v.candidates.length > 0) + throw new rt("Another account is already registered with this login name and the new password you chose. Pick a different new password."); + const f = s.generation + 1, u = { + schema: we, + account_id: s.account_id, + generation: f, + operational_private_key: s.operational_private_key, + operational_public_key: s.operational_public_key, + recovery_public_key: s.recovery_public_key, + recovery_capsule_event: s.recovery_capsule_event, + created_at: t, + vault_relay_hints: e.vaultRelayUrls, + protocol: { + password_kdf: De, + capsule_encryption: ue, + recovery_derivation: de + } + }, y = await Oe({ + locatorPrivateKey: m.locatorPrivateKey, + capsuleKey: m.capsuleKey, + payload: u + }), k = new C(e.vaultRelayUrls, { authPrivateKey: m.locatorPrivateKey }), b = await le(k, y, { + dTag: ie, + minAcks: e.minAcknowledgements, + timeoutMs: e.timeoutMs + }); + k.closeAll(); + const K = Ar({ + oldLocatorPrivateKey: n.locatorPrivateKey, + createdAt: at(c.created_at, t) + }), G = Yr({ + privateKey: n.locatorPrivateKey, + eventIdToDelete: c.id, + deletedEventKind: ae, + createdAt: t + }), _ = new C(e.vaultRelayUrls, { authPrivateKey: n.locatorPrivateKey }), [R, x] = await Promise.all([ + _.publishAll(K, e.timeoutMs), + _.publishAll(G, e.timeoutMs) + ]); + if (_.closeAll(), !b.success) + throw new Z("The new credential capsule did not reach the required relay acknowledgement and readback quorum. Please retry."); + return await st(d, s.operational_public_key, { generation: f }), { + normalizedLoginName: r, + oldLocatorPublicKey: o, + newLocatorPublicKey: p, + newGeneration: f, + recoveryPublicKey: s.recovery_public_key, + recoveryCapsuleEvent: s.recovery_capsule_event, + newCredentialEvent: y, + tombstoneEvent: K, + deletionRequestEvent: G, + newCredentialPublish: b, + tombstoneAcknowledgedCount: F(R), + deletionAcknowledgedCount: F(x) + }; +} +/*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */ +function Xr(e) { + return e instanceof Uint8Array || ArrayBuffer.isView(e) && e.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in e && e.BYTES_PER_ELEMENT === 1; +} +function Ge(e) { + if (typeof e != "boolean") + throw new TypeError(`boolean expected, not ${e}`); +} +function Ee(e) { + if (typeof e != "number") + throw new TypeError("number expected, got " + typeof e); + if (!Number.isSafeInteger(e) || e < 0) + throw new RangeError("positive integer expected, got " + e); +} +function te(e, t, r = "") { + const n = Xr(e), o = e?.length, a = t !== void 0; + if (!n || a && o !== t) { + const i = r && `"${r}" `, s = a ? ` of length ${t}` : "", c = n ? `length=${o}` : `type=${typeof e}`, d = i + "expected Uint8Array" + s + ", got " + c; + throw n ? new RangeError(d) : new TypeError(d); + } + return e; +} +function B(e) { + return new Uint32Array(e.buffer, e.byteOffset, Math.floor(e.byteLength / 4)); +} +function Ve(...e) { + for (let t = 0; t < e.length; t++) + e[t].fill(0); +} +const re = new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68, Zr = (e) => e << 24 & 4278190080 | e << 8 & 16711680 | e >>> 8 & 65280 | e >>> 24 & 255, en = (e) => { + for (let t = 0; t < e.length; t++) + e[t] = Zr(e[t]); + return e; +}, Q = re ? (e) => e : en; +function tn(e, t) { + if (t == null || typeof t != "object") + throw new Error("options must be defined"); + return Object.assign(e, t); +} +function rn(e, t, r = !0) { + if (t === void 0) + return new Uint8Array(e); + if (te(t, void 0, "output"), t.length !== e) + throw new Error('"output" expected Uint8Array of length ' + e + ", got: " + t.length); + if (r && !me(t)) + throw new Error("invalid output, must be aligned"); + return t; +} +function me(e) { + return e.byteOffset % 4 === 0; +} +function Be(e) { + return Uint8Array.from(te(e)); +} +const Lt = (e) => Uint8Array.from(e.split(""), (t) => t.charCodeAt(0)), nn = Q(B(Lt("expand 16-byte k"))), on = Q(B(Lt("expand 32-byte k"))); +function w(e, t) { + return e << t | e >>> 32 - t; +} +const se = 64, an = 16, Re = 2 ** 32 - 1, We = /* @__PURE__ */ Uint32Array.of(); +function sn(e, t, r, n, o, a, i, s) { + const c = o.length, d = new Uint8Array(se), h = B(d), m = re && me(o) && me(a), p = m ? B(o) : We, g = m ? B(a) : We; + if (!re) { + for (let v = 0; v < c; i++) { + if (e(t, r, n, h, i, s), Q(h), i >= Re) + throw new Error("arx: counter overflow"); + const f = Math.min(se, c - v); + for (let u = 0, y; u < f; u++) + y = v + u, a[y] = o[y] ^ d[u]; + v += f; + } + return; + } + for (let v = 0; v < c; i++) { + if (e(t, r, n, h, i, s), i >= Re) + throw new Error("arx: counter overflow"); + const f = Math.min(se, c - v); + if (m && f === se) { + const u = v / 4; + if (v % 4 !== 0) + throw new Error("arx: invalid block position"); + for (let y = 0, k; y < an; y++) + k = u + y, g[k] = p[k] ^ h[y]; + v += se; + continue; + } + for (let u = 0, y; u < f; u++) + y = v + u, a[y] = o[y] ^ d[u]; + v += f; + } +} +function cn(e, t) { + const { allowShortKeys: r, extendNonceFn: n, counterLength: o, counterRight: a, rounds: i } = tn({ allowShortKeys: !1, counterLength: 8, counterRight: !1, rounds: 20 }, t); + if (typeof e != "function") + throw new Error("core must be a function"); + return Ee(o), Ee(i), Ge(a), Ge(r), (s, c, d, h, m = 0) => { + te(s, void 0, "key"), te(c, void 0, "nonce"), te(d, void 0, "data"); + const p = d.length; + if (h = rn(p, h, !1), Ee(m), m < 0 || m >= Re) + throw new Error("arx: counter overflow"); + const g = []; + let v = s.length, f, u; + if (v === 32) + g.push(f = Be(s)), u = on; + else if (v === 16 && r) + f = new Uint8Array(32), f.set(s), f.set(s, 16), u = nn, g.push(f); + else + throw te(s, 32, "arx key"), new Error("invalid key size"); + (!re || !me(c)) && g.push(c = Be(c)); + let y = B(f); + if (n) { + if (c.length !== 24) + throw new Error("arx: extended nonce must be 24 bytes"); + const K = c.subarray(0, 16); + if (re) + n(u, y, B(K), y); + else { + const G = Q(Uint32Array.from(u)); + n(G, y, B(K), y), Ve(G), Q(y); + } + c = c.subarray(16); + } else re || Q(y); + const k = 16 - o; + if (k !== c.length) + throw new Error(`arx: nonce must be ${k} or 16 bytes`); + if (k !== 12) { + const K = new Uint8Array(12); + K.set(c, a ? 0 : 12 - c.length), c = K, g.push(c); + } + const b = Q(B(c)); + try { + return sn(e, u, y, b, d, h, m, i), h; + } finally { + Ve(...g); + } + }; +} +function ln(e, t, r, n, o, a = 20) { + let i = e[0], s = e[1], c = e[2], d = e[3], h = t[0], m = t[1], p = t[2], g = t[3], v = t[4], f = t[5], u = t[6], y = t[7], k = o, b = r[0], K = r[1], G = r[2], _ = i, R = s, x = c, L = d, N = h, T = m, I = p, S = g, M = v, U = f, O = u, D = y, q = k, z = b, j = K, $ = G; + for (let qe = 0; qe < a; qe += 2) + _ = _ + N | 0, q = w(q ^ _, 16), M = M + q | 0, N = w(N ^ M, 12), _ = _ + N | 0, q = w(q ^ _, 8), M = M + q | 0, N = w(N ^ M, 7), R = R + T | 0, z = w(z ^ R, 16), U = U + z | 0, T = w(T ^ U, 12), R = R + T | 0, z = w(z ^ R, 8), U = U + z | 0, T = w(T ^ U, 7), x = x + I | 0, j = w(j ^ x, 16), O = O + j | 0, I = w(I ^ O, 12), x = x + I | 0, j = w(j ^ x, 8), O = O + j | 0, I = w(I ^ O, 7), L = L + S | 0, $ = w($ ^ L, 16), D = D + $ | 0, S = w(S ^ D, 12), L = L + S | 0, $ = w($ ^ L, 8), D = D + $ | 0, S = w(S ^ D, 7), _ = _ + T | 0, $ = w($ ^ _, 16), O = O + $ | 0, T = w(T ^ O, 12), _ = _ + T | 0, $ = w($ ^ _, 8), O = O + $ | 0, T = w(T ^ O, 7), R = R + I | 0, q = w(q ^ R, 16), D = D + q | 0, I = w(I ^ D, 12), R = R + I | 0, q = w(q ^ R, 8), D = D + q | 0, I = w(I ^ D, 7), x = x + S | 0, z = w(z ^ x, 16), M = M + z | 0, S = w(S ^ M, 12), x = x + S | 0, z = w(z ^ x, 8), M = M + z | 0, S = w(S ^ M, 7), L = L + N | 0, j = w(j ^ L, 16), U = U + j | 0, N = w(N ^ U, 12), L = L + N | 0, j = w(j ^ L, 8), U = U + j | 0, N = w(N ^ U, 7); + let A = 0; + n[A++] = i + _ | 0, n[A++] = s + R | 0, n[A++] = c + x | 0, n[A++] = d + L | 0, n[A++] = h + N | 0, n[A++] = m + T | 0, n[A++] = p + I | 0, n[A++] = g + S | 0, n[A++] = v + M | 0, n[A++] = f + U | 0, n[A++] = u + O | 0, n[A++] = y + D | 0, n[A++] = k + q | 0, n[A++] = b + z | 0, n[A++] = K + j | 0, n[A++] = G + $ | 0; +} +const Nt = /* @__PURE__ */ cn(ln, { + counterRight: !1, + counterLength: 4, + allowShortKeys: !1 +}), Tt = 2, xe = 1, It = 65536, Ae = 4294967295; +function He(e, t) { + const r = Ie(new Uint8Array([2]), Ze(t)), o = tr.getSharedSecret(e, r, !0).slice(1, 33); + return rr(ye("nip44-v2"), o); +} +function St(e, t) { + const r = or(e, t, 76); + return { + chachaKey: r.slice(0, 32), + chachaNonce: r.slice(32, 44), + hmacKey: r.slice(44, 76) + }; +} +function Mt(e) { + if (e <= 32) + return 32; + const t = 2 ** Math.floor(Math.log2(e - 1) + 1), r = t <= 256 ? 32 : t / 8; + return r * (Math.floor((e - 1) / r) + 1); +} +function un(e) { + if (e < It) { + const r = new Uint8Array(2); + return new DataView(r.buffer).setUint16(0, e, !1), r; + } + const t = new Uint8Array(6); + return new DataView(t.buffer).setUint32(2, e, !1), t; +} +function dn(e) { + const t = e.length; + if (t < xe || t > Ae) + throw new Error(`NIP-44 plaintext length must be between ${xe} and ${Ae} bytes.`); + const r = un(t), n = Mt(t), o = new Uint8Array(r.length + n); + return o.set(r, 0), o.set(e, r.length), o; +} +function yn(e) { + const t = new DataView(e.buffer, e.byteOffset, e.byteLength), r = t.getUint16(0, !1); + let n, o; + if (r === 0) { + if (n = t.getUint32(2, !1), o = 6, n < It) + throw new Error("NIP-44 payload has inconsistent padding."); + } else + n = r, o = 2; + if (n < xe || n > Ae || e.length !== o + Mt(n)) + throw new Error("NIP-44 payload has inconsistent padding."); + return e.slice(o, o + n); +} +function Ut(e, t, r) { + return Qe(Xe, e, Ie(t, r)); +} +function hn(e, t, r) { + const n = er(32), { chachaKey: o, chachaNonce: a, hmacKey: i } = St(e, n), s = dn(ye(t)), c = Nt(o, a, s), d = Ut(i, n, c); + return tt.encode(Ie(new Uint8Array([Tt]), n, c, d)); +} +function pn(e, t) { + const r = tt.decode(t); + if (r[0] !== Tt) + throw new Error(`Unsupported NIP-44 version: ${r[0]}`); + const n = r.slice(1, 33), o = r.slice(r.length - 32), a = r.slice(33, r.length - 32), { chachaKey: i, chachaNonce: s, hmacKey: c } = St(e, n), d = Ut(c, n, a); + if (!nr(o, d)) + throw new Error("NIP-44 MAC verification failed: payload is corrupted, tampered, or uses the wrong key."); + const h = Nt(i, s, a); + return Ye(yn(h)); +} +function vn(...e) { + for (const t of e) + t && t.fill(0); +} +class Pe { + privateKey; + publicKeyHex; + destroyed = !1; + constructor(t) { + this.privateKey = t, this.publicKeyHex = E(t); + } + assertAlive() { + if (this.destroyed) + throw new Error("This signer has been destroyed (session locked or logged out)."); + } + getPublicKey() { + return this.assertAlive(), this.publicKeyHex; + } + signEvent(t) { + return this.assertAlive(), W({ + pubkey: this.publicKeyHex, + created_at: t.created_at ?? Math.floor(Date.now() / 1e3), + kind: t.kind, + tags: t.tags ?? [], + content: t.content + }, this.privateKey); + } + nip44Encrypt(t, r) { + return this.assertAlive(), hn(He(this.privateKey, t), r); + } + nip44Decrypt(t, r) { + return this.assertAlive(), pn(He(this.privateKey, t), r); + } + /** Best-practical secret wipe (§11.10, §21.4): overwrites the private key buffer in place. */ + destroy() { + vn(this.privateKey), this.destroyed = !0; + } +} +const gn = [ + "wss://relay.damus.io", + "wss://nos.lol", + "wss://relay.nostr.band" +], fn = [ + "wss://purplepag.es", + "wss://relay.nostr.band" +], mn = "bitlogin", H = "kv", wn = 1; +function bn() { + return new Promise((e, t) => { + const r = indexedDB.open(mn, wn); + r.onupgradeneeded = () => { + const n = r.result; + n.objectStoreNames.contains(H) || n.createObjectStore(H); + }, r.onsuccess = () => e(r.result), r.onerror = () => t(r.error); + }); +} +class kn { + dbPromise = null; + db() { + return this.dbPromise || (this.dbPromise = bn()), this.dbPromise; + } + async get(t) { + const r = await this.db(); + return new Promise((n, o) => { + const i = r.transaction(H, "readonly").objectStore(H).get(t); + i.onsuccess = () => n(i.result), i.onerror = () => o(i.error); + }); + } + async set(t, r) { + const n = await this.db(); + return new Promise((o, a) => { + const i = n.transaction(H, "readwrite"); + i.objectStore(H).put(r, t), i.oncomplete = () => o(), i.onerror = () => a(i.error); + }); + } + async delete(t) { + const r = await this.db(); + return new Promise((n, o) => { + const a = r.transaction(H, "readwrite"); + a.objectStore(H).delete(t), a.oncomplete = () => n(), a.onerror = () => o(a.error); + }); + } +} +const l = { + signer: null, + everydayPrivateKey: null, + accountId: null, + recoveryPublicKey: null, + activeCredentialEvent: null, + activeRecoveryEvent: null, + pendingRecovery: null +}; +let V = [...gn], Ke = [...fn]; +const Fe = new kn(); +function J() { + if (!l.signer || !l.everydayPrivateKey) + throw new Error("No identity is unlocked in this session."); + return { signer: l.signer, everydayPrivateKey: l.everydayPrivateKey }; +} +function pe() { + l.signer?.destroy(), l.everydayPrivateKey && l.everydayPrivateKey.fill(0), l.signer = null, l.everydayPrivateKey = null, l.accountId = null, l.recoveryPublicKey = null, l.activeCredentialEvent = null, l.activeRecoveryEvent = null, l.pendingRecovery && (l.pendingRecovery.recoveryPrivateKey.fill(0), l.pendingRecovery.everydayPrivateKey.fill(0), l.pendingRecovery = null); +} +async function _n(e, t) { + switch (e) { + case "configure": { + const r = t; + return r.vaultRelayUrls?.length && (V = r.vaultRelayUrls), r.discoveryRelayUrls?.length && (Ke = r.discoveryRelayUrls), {}; + } + case "register": { + const r = t, n = r.importKey ? await Wr({ nsecOrHex: r.importKey, loginName: r.loginName, password: r.password, vaultRelayUrls: V }) : await xt({ loginName: r.loginName, password: r.password, vaultRelayUrls: V }); + return pe(), l.signer = new Pe(n.everydayPrivateKey), l.everydayPrivateKey = n.everydayPrivateKey, l.accountId = n.accountId, l.recoveryPublicKey = n.recoveryPublicKey, l.activeCredentialEvent = n.credentialEvent, l.activeRecoveryEvent = n.recoveryEvent, { + recoveryPhrase: n.recoveryPhrase, + everydayPublicKey: n.everydayPublicKey, + recoveryPublicKey: n.recoveryPublicKey, + accountId: n.accountId, + imported: n.imported, + credentialEventId: n.credentialEvent.id, + recoveryEventId: n.recoveryEvent.id + }; + } + case "previewImportKey": { + const n = At(t.nsecOrHex), o = E(n), a = { everydayPublicKey: o, npub: je(o) }; + return n.fill(0), a; + } + case "login": { + const r = t, n = await Hr({ + loginName: r.loginName, + password: r.password, + vaultRelayUrls: V, + store: Fe, + acknowledgeRollback: r.acknowledgeRollback + }); + return pe(), l.signer = new Pe(n.everydayPrivateKey), l.everydayPrivateKey = n.everydayPrivateKey, l.accountId = n.accountId, l.recoveryPublicKey = n.recoveryPublicKey, l.activeCredentialEvent = n.credentialEvent, l.activeRecoveryEvent = n.recoveryCapsuleEvent, { + everydayPublicKey: n.everydayPublicKey, + accountId: n.accountId, + generation: n.generation, + rollbackWarning: n.rollbackWarning, + relayDisagreementWarning: n.relayDisagreementWarning + }; + } + case "recover": { + const r = t, n = await Fr({ + phrase: r.phrase, + vaultRelayUrls: V, + discoveryRelayUrls: Ke, + offlineRecoveryCapsuleEvents: r.offlineExportFile?.recovery_capsule_events + }); + return pe(), l.pendingRecovery = n, l.signer = new Pe(n.everydayPrivateKey), l.everydayPrivateKey = n.everydayPrivateKey, l.accountId = n.accountId, l.recoveryPublicKey = n.recoveryPublicKey, l.activeRecoveryEvent = n.currentRecoveryEvent, { + everydayPublicKey: n.everydayPublicKey, + accountId: n.accountId, + generalRelays: n.generalRelays, + dmRelays: n.dmRelays, + chainWarning: n.chainWarning + }; + } + case "completeRecovery": { + const r = t; + if (!l.pendingRecovery) throw new Error("No recovery is in progress in this session."); + const n = await Jr({ + recovered: l.pendingRecovery, + newLoginName: r.newLoginName, + newPassword: r.newPassword, + vaultRelayUrls: V + }); + return l.activeCredentialEvent = n.credentialEvent, l.activeRecoveryEvent = n.refreshedRecoveryEvent, l.pendingRecovery.recoveryPrivateKey.fill(0), l.pendingRecovery = null, { + locatorPublicKey: n.locatorPublicKey, + credentialEventId: n.credentialEvent.id, + refreshedRecoveryEventId: n.refreshedRecoveryEvent.id + }; + } + case "changePassword": { + const r = t, n = await Qr({ + loginName: r.loginName, + oldPassword: r.oldPassword, + newPassword: r.newPassword, + vaultRelayUrls: V, + store: Fe, + acknowledgeRollback: r.acknowledgeRollback + }); + return l.activeCredentialEvent = n.newCredentialEvent, l.recoveryPublicKey = n.recoveryPublicKey, l.activeRecoveryEvent = n.recoveryCapsuleEvent, { + newLocatorPublicKey: n.newLocatorPublicKey, + newGeneration: n.newGeneration, + tombstoneAcknowledgedCount: n.tombstoneAcknowledgedCount, + deletionAcknowledgedCount: n.deletionAcknowledgedCount + }; + } + case "publishProfileAndRelayLists": { + const r = t, { everydayPrivateKey: n } = J(); + return vr({ + everydayPrivateKey: n, + name: r.name, + about: r.about, + picture: r.picture, + generalRelays: r.generalRelays, + dmRelays: r.dmRelays, + discoveryRelays: Ke + }); + } + case "getPublicKey": { + const { signer: r } = J(); + return { publicKey: r.getPublicKey() }; + } + case "signEvent": { + const { signer: r } = J(), n = t; + return r.signEvent({ kind: n.kind, tags: n.tags, content: n.content, created_at: n.created_at }); + } + case "nip44Encrypt": { + const { signer: r } = J(), n = t; + return { ciphertext: r.nip44Encrypt(n.peerPublicKey, n.plaintext) }; + } + case "nip44Decrypt": { + const { signer: r } = J(), n = t; + return { plaintext: r.nip44Decrypt(n.peerPublicKey, n.payload) }; + } + case "exportIdentity": { + const { everydayPrivateKey: r, signer: n } = J(); + return { nsec: ir(r), npub: je(n.getPublicKey()) }; + } + case "buildRecoveryExport": { + if (J(), !l.recoveryPublicKey || !l.activeRecoveryEvent) + throw new Error("No recovery capsule is known in this session yet."); + return ar({ + recoveryPublicKeyHex: l.recoveryPublicKey, + vaultRelayUrls: V, + recoveryCapsuleEvents: [l.activeRecoveryEvent], + relayListEvents: [] + }); + } + case "repairReplicas": { + if (!l.activeCredentialEvent || !l.activeRecoveryEvent) + throw new Error("No active capsule events are known in this session yet."); + const r = new C(V), n = await Ir(r, l.activeCredentialEvent, l.activeRecoveryEvent); + return r.closeAll(), n; + } + case "getSessionStatus": + return { unlocked: !!l.signer, everydayPublicKey: l.signer?.getPublicKey() }; + case "logout": + return pe(), {}; + default: + throw new Error(`Unknown worker action: ${e}`); + } +} +self.addEventListener("message", (e) => { + const { id: t, action: r, payload: n } = e.data; + _n(r, n).then( + (o) => { + const a = { id: t, ok: !0, result: o }; + self.postMessage(a); + }, + (o) => { + const a = o instanceof Error ? o : new Error(String(o)), i = { id: t, ok: !1, error: a.message, errorName: a.name }; + self.postMessage(i); + } + ); +}); From ca7b711b8ae2e775805b89a9314a51c3138b1992 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Tue, 21 Jul 2026 21:44:40 -0400 Subject: [PATCH 2/8] Widen pinned BitLogin relay set for redundancy against a single dead relay relay.nostr.band (shared by both relay roles pinned here) is currently unreachable, and BitLogin's publishAndVerify() enforces a fixed ack/readback floor of 2 regardless of relay count -- so pinning to only 3 vault relays left zero margin the moment one went down, surfacing as a registration/login quorum failure. Widen the vault-relays/discovery-relays attributes in components/login-modal.html to match bitlogin's own updated built-in defaults, and re-vendor the widget bundle (see bitlogin's CHANGELOG.md 2026-07-21 entry). No CSP change needed -- this branch's connect-src already allows wss: broadly. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01DY9UMTuF43Ekc2U4E1eDwX --- components/login-modal.html | 4 ++-- vendor/bitlogin.integrity.json | 4 ++-- vendor/bitlogin/cryptoWorker.js | 7 +++++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/components/login-modal.html b/components/login-modal.html index f23494b2..4bda5112 100644 --- a/components/login-modal.html +++ b/components/login-modal.html @@ -65,8 +65,8 @@ >
diff --git a/vendor/bitlogin.integrity.json b/vendor/bitlogin.integrity.json index 23dd37c9..e443f727 100644 --- a/vendor/bitlogin.integrity.json +++ b/vendor/bitlogin.integrity.json @@ -11,8 +11,8 @@ "bytes": 65009 }, "cryptoWorker.js": { - "sha256": "31e2b32cbbb8a4223d6664d383b6de49a173406e14a409a4cf192be53ed9dd74", - "bytes": 64803 + "sha256": "061d4db248e5a789f61c505dfbc3fe073b6373b3c9a590c331144840e7b654b0", + "bytes": 64890 } } } diff --git a/vendor/bitlogin/cryptoWorker.js b/vendor/bitlogin/cryptoWorker.js index 5895b0af..b5c81626 100644 --- a/vendor/bitlogin/cryptoWorker.js +++ b/vendor/bitlogin/cryptoWorker.js @@ -3231,10 +3231,13 @@ class Pe { const gn = [ "wss://relay.damus.io", "wss://nos.lol", - "wss://relay.nostr.band" + "wss://relay.nostr.band", + "wss://nostr.wine", + "wss://relay.snort.social" ], fn = [ "wss://purplepag.es", - "wss://relay.nostr.band" + "wss://relay.nostr.band", + "wss://nostr-pub.wellorder.net" ], mn = "bitlogin", H = "kv", wn = 1; function bn() { return new Promise((e, t) => { From f2d390ff18aa18e34d1db3a15e3fb2cd46be4d4a Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Tue, 21 Jul 2026 23:32:27 -0400 Subject: [PATCH 3/8] Wire up NIP-04 in the BitLogin adapter, flip capabilities.nip04 to true bitlogin's widget now implements NIP-04 (nip04Encrypt/nip04Decrypt), closing the one gap left relative to a real NIP-07 extension. bitvid's own nip07Adapter/nsecAdapter/nip46Adapter all already wire this up and report it via signerCapabilities.nip04 -- BitLogin's adapter was the one exception, hardcoded to nip04: false because the widget genuinely didn't support it before. js/nostr/client.js's DM encrypt/decrypt path only offers NIP-04 to a caller when that capability flag is true, so a BitLogin-signed-in user previously had no working legacy-DM path at all. Re-vendored the widget bundle (bitlogin's CHANGELOG.md, 2026-07-21 entry, third one) and updated the one test that hardcoded the old (now-incorrect) capabilities shape. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01DY9UMTuF43Ekc2U4E1eDwX --- js/nostr/adapters/bitloginAdapter.js | 6 +- tests/auth-providers-bitlogin.test.mjs | 18 +- vendor/bitlogin.integrity.json | 8 +- vendor/bitlogin/bitlogin.js | 117 +- vendor/bitlogin/cryptoWorker.js | 1739 ++++++++++++++---------- 5 files changed, 1095 insertions(+), 793 deletions(-) diff --git a/js/nostr/adapters/bitloginAdapter.js b/js/nostr/adapters/bitloginAdapter.js index 167c747a..b76eaa82 100644 --- a/js/nostr/adapters/bitloginAdapter.js +++ b/js/nostr/adapters/bitloginAdapter.js @@ -17,6 +17,10 @@ export function createBitloginAdapter(widgetElement, pubkey) { widgetElement.nip44Encrypt(peerPubkey, plaintext), nip44Decrypt: async (peerPubkey, payload) => widgetElement.nip44Decrypt(peerPubkey, payload), + nip04Encrypt: async (peerPubkey, plaintext) => + widgetElement.nip04Encrypt(peerPubkey, plaintext), + nip04Decrypt: async (peerPubkey, payload) => + widgetElement.nip04Decrypt(peerPubkey, payload), requestPermissions: async () => ({ ok: true }), destroy: async () => { try { @@ -30,7 +34,7 @@ export function createBitloginAdapter(widgetElement, pubkey) { capabilities: { sign: true, nip44: true, - nip04: false, + nip04: true, }, }; diff --git a/tests/auth-providers-bitlogin.test.mjs b/tests/auth-providers-bitlogin.test.mjs index 938f781e..93beb6f8 100644 --- a/tests/auth-providers-bitlogin.test.mjs +++ b/tests/auth-providers-bitlogin.test.mjs @@ -102,7 +102,7 @@ describe("BitLogin auth provider", () => { }); describe("createBitloginAdapter", () => { - it("delegates signing and NIP-44 calls to the live widget element, never window.nostr", async () => { + it("delegates signing and NIP-44/NIP-04 calls to the live widget element, never window.nostr", async () => { const calls = []; const fakeWidget = { signEvent: async (event) => { @@ -117,6 +117,14 @@ describe("createBitloginAdapter", () => { calls.push(["nip44Decrypt", peer, payload]); return "plaintext"; }, + nip04Encrypt: async (peer, plaintext) => { + calls.push(["nip04Encrypt", peer, plaintext]); + return "legacy-ciphertext"; + }, + nip04Decrypt: async (peer, payload) => { + calls.push(["nip04Decrypt", peer, payload]); + return "legacy-plaintext"; + }, logout: async () => { calls.push(["logout"]); }, @@ -127,19 +135,25 @@ describe("createBitloginAdapter", () => { assert.equal(adapter.type, "bitlogin"); assert.equal(adapter.pubkey, HEX_PUBKEY); assert.equal(adapter.canSign(), true); - assert.deepEqual(adapter.capabilities, { sign: true, nip44: true, nip04: false }); + // NIP-04 is real, load-bearing capability info: js/nostr/client.js only offers a + // nip04Encrypt/Decrypt code path to callers when signerCapabilities.nip04 is true. + assert.deepEqual(adapter.capabilities, { sign: true, nip44: true, nip04: true }); const signed = await adapter.signEvent({ kind: 1, content: "hi" }); assert.equal(signed.id, "signed"); await adapter.nip44Encrypt("peer-pubkey", "hello"); await adapter.nip44Decrypt("peer-pubkey", "ciphertext"); + await adapter.nip04Encrypt("peer-pubkey", "hello"); + await adapter.nip04Decrypt("peer-pubkey", "legacy-ciphertext"); await adapter.destroy(); assert.deepEqual(calls, [ ["signEvent", { kind: 1, content: "hi" }], ["nip44Encrypt", "peer-pubkey", "hello"], ["nip44Decrypt", "peer-pubkey", "ciphertext"], + ["nip04Encrypt", "peer-pubkey", "hello"], + ["nip04Decrypt", "peer-pubkey", "legacy-ciphertext"], ["logout"], ]); }); diff --git a/vendor/bitlogin.integrity.json b/vendor/bitlogin.integrity.json index e443f727..d0702bc6 100644 --- a/vendor/bitlogin.integrity.json +++ b/vendor/bitlogin.integrity.json @@ -7,12 +7,12 @@ "bytes": 288275 }, "bitlogin.js": { - "sha256": "7bbe34092ff5c110d947ac4cec7500cd32824868e51118387566896660a31228", - "bytes": 65009 + "sha256": "539253ea709e9554f3dcff872beff40c2ca847b3564f53885b8fd89a7a2747d1", + "bytes": 66045 }, "cryptoWorker.js": { - "sha256": "061d4db248e5a789f61c505dfbc3fe073b6373b3c9a590c331144840e7b654b0", - "bytes": 64890 + "sha256": "06a7aaf0be2ff4e80b46f609b868aa4ccfbd402cf0604031500316830ff2f528", + "bytes": 73937 } } } diff --git a/vendor/bitlogin/bitlogin.js b/vendor/bitlogin/bitlogin.js index ff55bb27..bf87e4a7 100644 --- a/vendor/bitlogin/bitlogin.js +++ b/vendor/bitlogin/bitlogin.js @@ -1,6 +1,6 @@ import { r as y, e as w, E as v, p as Q, R as k, i as h, a as u } from "./bitlogin-shared-QIBe5Omw.js"; const x = 6, p = 64; -function c(a = x) { +function d(a = x) { const e = w(), t = []; for (let r = 0; r < a; r++) t.push(e[y(e.length)]); @@ -54,8 +54,8 @@ function S(a) { const e = a.toLowerCase(), t = ["0123456789", "abcdefghijklmnopqrstuvwxyz"]; for (const s of t) for (let r = 0; r + 4 <= s.length; r++) { - const o = s.slice(r, r + 4), n = [...o].reverse().join(""); - if (e.includes(o) || e.includes(n)) + const n = s.slice(r, r + 4), o = [...n].reverse().join(""); + if (e.includes(n) || e.includes(o)) return !0; } return !1; @@ -75,22 +75,22 @@ class E { constructor() { const e = ["cryptoWorker", ".js"].join(""), t = new URL(e, import.meta.url); this.worker = new Worker(t, { type: "module" }), this.worker.addEventListener("message", (s) => { - const r = s.data, o = this.pending.get(r.id); - if (o) + const r = s.data, n = this.pending.get(r.id); + if (n) if (this.pending.delete(r.id), r.ok) - o.resolve(r.result); + n.resolve(r.result); else { - const n = new Error(r.error); - n.name = r.errorName ?? "Error", o.reject(n); + const o = new Error(r.error); + o.name = r.errorName ?? "Error", n.reject(o); } }); } call(e, t) { const s = `${Date.now().toString(36)}-${(this.counter++).toString(36)}`; - return new Promise((r, o) => { - this.pending.set(s, { resolve: r, reject: o }); - const n = { id: s, action: e, payload: t }; - this.worker.postMessage(n); + return new Promise((r, n) => { + this.pending.set(s, { resolve: r, reject: n }); + const o = { id: s, action: e, payload: t }; + this.worker.postMessage(o); }); } configure(e) { @@ -129,6 +129,12 @@ class E { nip44Decrypt(e) { return this.call("nip44Decrypt", e); } + nip04Encrypt(e) { + return this.call("nip04Encrypt", e); + } + nip04Decrypt(e) { + return this.call("nip04Decrypt", e); + } exportIdentity() { return this.call("exportIdentity", {}); } @@ -149,7 +155,7 @@ class E { } } const N = "BitLogin: no identity is unlocked yet. Add to the page and let the user sign in, or call it programmatically before invoking window.nostr."; -function d(a) { +function c(a) { return a.catch((e) => { throw e.message.includes("No identity is unlocked") ? new Error(N) : e; }); @@ -157,11 +163,11 @@ function d(a) { function $(a, e) { return { async getPublicKey() { - const { publicKey: t } = await d(a.getPublicKey()); + const { publicKey: t } = await c(a.getPublicKey()); return t; }, async signEvent(t) { - return d(a.signEvent(t)); + return c(a.signEvent(t)); }, async getRelays() { const t = {}; @@ -170,11 +176,21 @@ function $(a, e) { }, nip44: { async encrypt(t, s) { - const { ciphertext: r } = await d(a.nip44Encrypt({ peerPublicKey: t, plaintext: s })); + const { ciphertext: r } = await c(a.nip44Encrypt({ peerPublicKey: t, plaintext: s })); return r; }, async decrypt(t, s) { - const { plaintext: r } = await d(a.nip44Decrypt({ peerPublicKey: t, payload: s })); + const { plaintext: r } = await c(a.nip44Decrypt({ peerPublicKey: t, payload: s })); + return r; + } + }, + nip04: { + async encrypt(t, s) { + const { ciphertext: r } = await c(a.nip04Encrypt({ peerPublicKey: t, plaintext: s })); + return r; + }, + async decrypt(t, s) { + const { plaintext: r } = await c(a.nip04Decrypt({ peerPublicKey: t, payload: s })); return r; } }, @@ -482,7 +498,9 @@ class F extends HTMLElement { pendingRollback = null; rollbackMessage = ""; constructor() { - super(), this.root = this.attachShadow({ mode: "open" }), this.worker = new E(); + super(), this.root = this.attachShadow({ mode: "open" }); + const e = new CSSStyleSheet(); + e.replaceSync(M), this.root.adoptedStyleSheets = [e], this.worker = new E(); } connectedCallback() { const e = R(this); @@ -516,6 +534,17 @@ class F extends HTMLElement { async nip44Decrypt(e, t) { return (await this.worker.nip44Decrypt({ peerPublicKey: e, payload: t })).plaintext; } + /** + * Legacy relative to nip44Encrypt/nip44Decrypt above, but still what a real NIP-07 + * extension exposes as window.nostr.nip04 -- implemented for drop-in parity with sites + * (or their older DM code paths) that still expect NIP-04 rather than NIP-44. + */ + async nip04Encrypt(e, t) { + return (await this.worker.nip04Encrypt({ peerPublicKey: e, plaintext: t })).ciphertext; + } + async nip04Decrypt(e, t) { + return (await this.worker.nip04Decrypt({ peerPublicKey: e, payload: t })).plaintext; + } async logout() { await this.worker.logout(), this.session = null, this.releaseSigner(), this.dispatchEvent(new CustomEvent("bitlogin-logout")), this.goto("welcome"); } @@ -673,21 +702,21 @@ class F extends HTMLElement { this.goto("confirm-phrase"); return; case "goto-change-password": - this.changePasswordNewCredential = c().secret, this.manualPasswordMode = !1, this.manualPasswordFeedback = null, this.manualPasswordDraft = "", this.manualPasswordConfirmDraft = "", this.goto("change-password"); + this.changePasswordNewCredential = d().secret, this.manualPasswordMode = !1, this.manualPasswordFeedback = null, this.manualPasswordDraft = "", this.manualPasswordConfirmDraft = "", this.goto("change-password"); return; case "goto-export": this.goto("export"); return; case "regenerate-credential": - this.generatedCredential = c().secret, this.savedCheckbox = !1, this.render(); + this.generatedCredential = d().secret, this.savedCheckbox = !1, this.render(); return; case "toggle-manual-password": this.manualPasswordMode = !this.manualPasswordMode, this.manualPasswordFeedback = null, this.manualPasswordDraft = "", this.manualPasswordConfirmDraft = "", this.savedCheckbox = !1, this.render(); return; case "copy-credential": { - const r = this.root.querySelector("#credential-box"), o = t, n = o.textContent ?? "Copy", l = (g) => { - o.textContent = g, setTimeout(() => { - o.isConnected && (o.textContent = n); + const r = this.root.querySelector("#credential-box"), n = t, o = n.textContent ?? "Copy", l = (g) => { + n.textContent = g, setTimeout(() => { + n.isConnected && (n.textContent = o); }, 2e3); }; if (!r || !navigator.clipboard?.writeText) { @@ -757,16 +786,16 @@ class F extends HTMLElement { if (!(t instanceof HTMLInputElement) || t.name !== "manualPassword" && t.name !== "manualPasswordConfirm") return; const s = this.root.querySelector('input[name="manualPassword"]')?.value ?? "", r = this.root.querySelector('input[name="manualPasswordConfirm"]')?.value ?? ""; this.manualPasswordFeedback = s ? f(s, this.loginName) : null; - const o = this.root.querySelector("#manual-password-feedback"); - if (o) + const n = this.root.querySelector("#manual-password-feedback"); + if (n) if (!this.manualPasswordFeedback) - o.textContent = "", o.className = "notice info"; + n.textContent = "", n.className = "notice info"; else { const l = this.manualPasswordFeedback.entropyBits.toFixed(0); - this.manualPasswordFeedback.ok ? (o.textContent = `Looks good (~${l} bits estimated).`, o.className = "notice info") : (o.textContent = `${this.manualPasswordFeedback.reason} (~${l} bits estimated)`, o.className = "notice warn"); + this.manualPasswordFeedback.ok ? (n.textContent = `Looks good (~${l} bits estimated).`, n.className = "notice info") : (n.textContent = `${this.manualPasswordFeedback.reason} (~${l} bits estimated)`, n.className = "notice warn"); } - const n = this.root.querySelector("#manual-password-match-feedback"); - n && (r ? r === s ? (n.textContent = "Passwords match.", n.className = "notice info") : (n.textContent = "Passwords do not match.", n.className = "notice warn") : (n.textContent = "", n.className = "notice info")); + const o = this.root.querySelector("#manual-password-match-feedback"); + o && (r ? r === s ? (o.textContent = "Passwords match.", o.className = "notice info") : (o.textContent = "Passwords do not match.", o.className = "notice warn") : (o.textContent = "", o.className = "notice info")); } /** * Reads and validates an optional recovery-export file (§19.5) for the recover-phrase @@ -816,18 +845,18 @@ class F extends HTMLElement { this.errorMessage = "Login name must be 3-32 characters: a-z, 0-9, '.', '_', '-', and not start/end with punctuation.", this.render(); return; } - this.loginName = e, this.generatedCredential = c().secret, this.savedCheckbox = !1, this.manualPasswordMode = !1, this.manualPasswordFeedback = null, this.manualPasswordDraft = "", this.manualPasswordConfirmDraft = "", this.goto("create-credential"); + this.loginName = e, this.generatedCredential = d().secret, this.savedCheckbox = !1, this.manualPasswordMode = !1, this.manualPasswordFeedback = null, this.manualPasswordDraft = "", this.manualPasswordConfirmDraft = "", this.goto("create-credential"); } async handleCreateCredentialSubmit() { this.savedCheckbox = this.root.querySelector("#saved-check")?.checked ?? !1; let e = this.generatedCredential; if (this.manualPasswordMode) { - const { password: o, error: n } = this.readManualPassword(this.loginName); - if (n) { - this.errorMessage = n, this.render(); + const { password: n, error: o } = this.readManualPassword(this.loginName); + if (o) { + this.errorMessage = o, this.render(); return; } - e = o; + e = n; } if (!this.savedCheckbox) { this.errorMessage = this.manualPasswordMode ? "Please confirm you've saved your password (or written it down) before continuing." : "Please confirm you saved your password before continuing.", this.render(); @@ -840,8 +869,8 @@ class F extends HTMLElement { importKey: this.importKey || void 0 }); this.importKey = "", this.importPreviewNpub = "", this.recoveryPhrase = t.recoveryPhrase; - const s = this.recoveryPhrase.split(" "), r = T(s.length, 3); - this.confirmSlots = r.map((o) => ({ index: o, value: "" })), this.session = { publicKey: t.everydayPublicKey, npub: u(t.everydayPublicKey), accountId: t.accountId }, this.busy = !1, this.goto("confirm-phrase"), this.worker.publishProfileAndRelayLists({ + const s = this.recoveryPhrase.split(" "), r = D(s.length, 3); + this.confirmSlots = r.map((n) => ({ index: n, value: "" })), this.session = { publicKey: t.everydayPublicKey, npub: u(t.everydayPublicKey), accountId: t.accountId }, this.busy = !1, this.goto("confirm-phrase"), this.worker.publishProfileAndRelayLists({ name: this.loginName, generalRelays: this.vaultRelayUrls, dmRelays: this.vaultRelayUrls @@ -869,7 +898,7 @@ class F extends HTMLElement { this.setBusy(!0); try { const r = await this.worker.login({ loginName: e, password: t, acknowledgeRollback: s }); - this.loginName = e, this.session = { publicKey: r.everydayPublicKey, npub: u(r.everydayPublicKey), accountId: r.accountId }, this.sessionWarnings = [r.rollbackWarning, r.relayDisagreementWarning].filter((o) => !!o), this.busy = !1, this.noteSignerClaim(this.claimSigner()), this.dispatchEvent(new CustomEvent("bitlogin-login", { detail: { publicKey: r.everydayPublicKey } })), this.flashSuccess("dashboard", "Signed in"), this.offerToSaveCredential(e, t); + this.loginName = e, this.session = { publicKey: r.everydayPublicKey, npub: u(r.everydayPublicKey), accountId: r.accountId }, this.sessionWarnings = [r.rollbackWarning, r.relayDisagreementWarning].filter((n) => !!n), this.busy = !1, this.noteSignerClaim(this.claimSigner()), this.dispatchEvent(new CustomEvent("bitlogin-login", { detail: { publicKey: r.everydayPublicKey } })), this.flashSuccess("dashboard", "Signed in"), this.offerToSaveCredential(e, t); } catch (r) { if (r instanceof Error && r.name === "RollbackDetectedError") { this.pendingRollback = { kind: "login", loginName: e, password: t }, this.rollbackMessage = r.message, this.busy = !1, this.goto("rollback-confirm"); @@ -894,7 +923,7 @@ class F extends HTMLElement { generalRelaysCount: t.generalRelays.length, dmRelaysCount: t.dmRelays.length, chainWarning: t.chainWarning - }, this.session = { publicKey: t.everydayPublicKey, npub: u(t.everydayPublicKey), accountId: t.accountId }, this.newCredentialAfterRecovery = c().secret, this.manualPasswordMode = !1, this.manualPasswordFeedback = null, this.manualPasswordDraft = "", this.manualPasswordConfirmDraft = "", this.busy = !1, this.goto("recover-new-credentials"); + }, this.session = { publicKey: t.everydayPublicKey, npub: u(t.everydayPublicKey), accountId: t.accountId }, this.newCredentialAfterRecovery = d().secret, this.manualPasswordMode = !1, this.manualPasswordFeedback = null, this.manualPasswordDraft = "", this.manualPasswordConfirmDraft = "", this.busy = !1, this.goto("recover-new-credentials"); } async handleRecoverNewCredentialsSubmit() { const e = this.field("newLoginName").trim().toLowerCase(); @@ -958,7 +987,7 @@ class F extends HTMLElement { } render() { const e = this.pendingSuccessLabel ? this.renderSuccessOverlay(this.pendingSuccessLabel) : ""; - this.root.innerHTML = `
${this.renderScreen()}${e}
`; + this.root.innerHTML = `
${this.renderScreen()}${e}
`; } renderSuccessOverlay(e) { return ` @@ -1224,13 +1253,13 @@ class F extends HTMLElement { } } } -function T(a, e) { - const t = Array.from({ length: a }, (r, o) => o), s = []; +function D(a, e) { + const t = Array.from({ length: a }, (r, n) => n), s = []; for (let r = 0; r < e && t.length > 0; r++) { - const o = Math.floor(Math.random() * t.length); - s.push(t.splice(o, 1)[0]); + const n = Math.floor(Math.random() * t.length); + s.push(t.splice(n, 1)[0]); } - return s.sort((r, o) => r - o); + return s.sort((r, n) => r - n); } function i(a) { return a.replace(/[&<>"']/gu, (e) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[e]); diff --git a/vendor/bitlogin/cryptoWorker.js b/vendor/bitlogin/cryptoWorker.js index b5c81626..d41fb05b 100644 --- a/vendor/bitlogin/cryptoWorker.js +++ b/vendor/bitlogin/cryptoWorker.js @@ -1,58 +1,58 @@ -import { v as ee, g as E, s as W, c as Ot, K as Dt, b as Je, d as Ce, f as Le, h as ae, u as ye, j as qt, k as zt, l as X, m as ne, n as Ye, S as Ne, o as Te, q as we, D as ie, t as be, w as jt, x as $t, y as Gt, z as Vt, A as _e, B as ze, C as Qe, F as Bt, G as Wt, H as Ht, I as he, J as Xe, L as Ft, M as Jt, N as Ze, O as ke, P as ce, Q as Yt, T as et, U as Qt, V as Xt, W as Zt, X as er, Y as tt, Z as Ie, _ as tr, $ as rr, a0 as nr, a1 as or, a2 as ar, a as je, a3 as ir } from "./bitlogin-shared-QIBe5Omw.js"; -class oe extends Error { +import { v as ce, g as R, s as X, c as tn, K as nn, b as ct, d as qe, f as ze, h as he, u as pe, j as rn, k as on, l as oe, m as ue, n as je, S as $e, o as Be, q as Re, D as fe, t as xe, w as an, x as sn, y as cn, z as ln, A as Le, B as et, C as lt, F as un, G as dn, H as yn, I as ke, J as ut, L as hn, M as pn, N as Ge, O as Ae, P as ge, Q as fn, T as dt, U as vn, V as gn, W as wn, X as yt, Y as de, Z as Ce, _ as ht, $ as mn, a0 as bn, a1 as kn, a2 as En, a as tt, a3 as _n } from "./bitlogin-shared-QIBe5Omw.js"; +class ye extends Error { reason; constructor(t) { super("Account not found or credentials incorrect."), this.name = "AccountNotFoundError", this.reason = t; } } -class Z extends Error { +class ae extends Error { constructor(t) { super(t), this.name = "RegistrationFailedError"; } } -class rt extends Error { +class pt extends Error { constructor(t = "An account already exists with this login name and password. Sign in instead, or choose different credentials.") { super(t), this.name = "AccountAlreadyExistsError"; } } -class nt extends Error { +class ft extends Error { constructor(t) { super(t), this.name = "RecoveryFailedError"; } } -class ot extends Error { +class vt extends Error { seenGeneration; capsuleGeneration; - constructor(t, r) { - super(`This credential capsule reports generation ${r}, but this device has already seen generation ${t}. Refusing to log in with older, possibly-revoked credentials.`), this.name = "RollbackDetectedError", this.seenGeneration = t, this.capsuleGeneration = r; + constructor(t, n) { + super(`This credential capsule reports generation ${n}, but this device has already seen generation ${t}. Refusing to log in with older, possibly-revoked credentials.`), this.name = "RollbackDetectedError", this.seenGeneration = t, this.capsuleGeneration = n; } } -function at(e, t) { - const r = t ?? Math.floor(Date.now() / 1e3); - return e == null ? r : Math.max(r, e + 1); +function gt(e, t) { + const n = t ?? Math.floor(Date.now() / 1e3); + return e == null ? n : Math.max(n, e + 1); } -const sr = { generation: -1, recoveryGeneration: -1 }; -function it(e) { +const Pn = { generation: -1, recoveryGeneration: -1 }; +function wt(e) { return `bitlogin:hwm:${e}`; } -async function Se(e, t) { - const r = await e.get(it(t)); - return r ? JSON.parse(r) : sr; +async function Ve(e, t) { + const n = await e.get(wt(t)); + return n ? JSON.parse(n) : Pn; } -async function st(e, t, r) { - const n = await Se(e, t), o = { - generation: Math.max(n.generation, r.generation ?? -1), - recoveryGeneration: Math.max(n.recoveryGeneration, r.recoveryGeneration ?? -1) +async function mt(e, t, n) { + const r = await Ve(e, t), o = { + generation: Math.max(r.generation, n.generation ?? -1), + recoveryGeneration: Math.max(r.recoveryGeneration, n.recoveryGeneration ?? -1) }; - return await e.set(it(t), JSON.stringify(o)), o; + return await e.set(wt(t), JSON.stringify(o)), o; } -function cr() { +function Kn() { const e = globalThis.WebSocket; if (!e) throw new Error("No global WebSocket implementation is available in this environment."); return e; } -class lr { +class Rn { url; ws = null; connectPromise = null; @@ -61,20 +61,20 @@ class lr { authPrivateKey; connectTimeoutMs; authenticated = !1; - constructor(t, r = {}) { - this.url = t, this.authPrivateKey = r.authPrivateKey, this.connectTimeoutMs = r.connectTimeoutMs ?? 8e3; + constructor(t, n = {}) { + this.url = t, this.authPrivateKey = n.authPrivateKey, this.connectTimeoutMs = n.connectTimeoutMs ?? 8e3; } async connect() { - return this.connectPromise ? this.connectPromise : (this.connectPromise = new Promise((t, r) => { - const n = cr(), o = new n(this.url); + return this.connectPromise ? this.connectPromise : (this.connectPromise = new Promise((t, n) => { + const r = Kn(), o = new r(this.url); this.ws = o; const a = setTimeout(() => { - r(new Error(`Timed out connecting to relay ${this.url}`)); + n(new Error(`Timed out connecting to relay ${this.url}`)); }, this.connectTimeoutMs); o.addEventListener("open", () => { clearTimeout(a), t(); }), o.addEventListener("error", () => { - clearTimeout(a), r(new Error(`WebSocket error connecting to relay ${this.url}`)); + clearTimeout(a), n(new Error(`WebSocket error connecting to relay ${this.url}`)); }), o.addEventListener("close", () => { this.connectPromise = null; }), o.addEventListener("message", (i) => { @@ -91,31 +91,31 @@ class lr { this.ws.send(JSON.stringify(t)); } handleMessage(t) { - let r; + let n; try { - r = JSON.parse(t); + n = JSON.parse(t); } catch { return; } - if (!Array.isArray(r) || typeof r[0] != "string") + if (!Array.isArray(n) || typeof n[0] != "string") return; - const [n, ...o] = r; - if (n === "EVENT") { - const [a, i] = o, s = this.subs.get(a); - s && ee(i) && s.events.push(i); + const [r, ...o] = n; + if (r === "EVENT") { + const [a, i] = o, c = this.subs.get(a); + c && ce(i) && c.events.push(i); return; } - if (n === "EOSE") { + if (r === "EOSE") { const [a] = o; this.subs.get(a)?.onEose(); return; } - if (n === "OK") { - const [a, i, s] = o; - this.pendingPublishes.get(a)?.({ ok: i, message: s ?? "" }), this.pendingPublishes.delete(a); + if (r === "OK") { + const [a, i, c] = o; + this.pendingPublishes.get(a)?.({ ok: i, message: c ?? "" }), this.pendingPublishes.delete(a); return; } - if (n === "AUTH") { + if (r === "AUTH") { const [a] = o; this.respondToAuthChallenge(a); return; @@ -124,34 +124,34 @@ class lr { async respondToAuthChallenge(t) { if (!this.authPrivateKey) return; - const r = E(this.authPrivateKey), n = W({ - pubkey: r, + const n = R(this.authPrivateKey), r = X({ + pubkey: n, created_at: Math.floor(Date.now() / 1e3), - kind: Dt, + kind: nn, tags: [ ["relay", this.url], ["challenge", t] ], content: "" }, this.authPrivateKey); - this.authenticated = !0, this.send(["AUTH", n]); + this.authenticated = !0, this.send(["AUTH", r]); } isAuthenticated() { return this.authenticated; } - async publish(t, r = 8e3) { - return await this.connect(), new Promise((n) => { + async publish(t, n = 8e3) { + return await this.connect(), new Promise((r) => { const o = setTimeout(() => { - this.pendingPublishes.delete(t.id), n({ ok: !1, message: "timeout: no OK received from relay" }); - }, r); + this.pendingPublishes.delete(t.id), r({ ok: !1, message: "timeout: no OK received from relay" }); + }, n); this.pendingPublishes.set(t.id, (a) => { - clearTimeout(o), n(a); + clearTimeout(o), r(a); }), this.send(["EVENT", t]); }); } - async queryOnce(t, r = 8e3) { + async queryOnce(t, n = 8e3) { await this.connect(); - const n = Ot({ + const r = tn({ pubkey: "0".repeat(64), created_at: Date.now(), kind: 0, @@ -160,31 +160,31 @@ class lr { }).slice(0, 16); return new Promise((o) => { const a = [], i = () => { - clearTimeout(s), this.subs.delete(n), this.send(["CLOSE", n]), o(a); - }, s = setTimeout(i, r); - this.subs.set(n, { events: a, onEose: i }), this.send(["REQ", n, t]); + clearTimeout(c), this.subs.delete(r), this.send(["CLOSE", r]), o(a); + }, c = setTimeout(i, n); + this.subs.set(r, { events: a, onEose: i }), this.send(["REQ", r, t]); }); } } -class C { +class S { connections = /* @__PURE__ */ new Map(); - constructor(t, r = {}) { - for (const n of new Set(t)) - this.connections.set(n, new lr(n, r)); + constructor(t, n = {}) { + for (const r of new Set(t)) + this.connections.set(r, new Rn(r, n)); } get relayUrls() { return [...this.connections.keys()]; } /** Queries every configured relay and waits for a quorum of responses (or all timeouts) before returning (§16.2). */ - async queryQuorum(t, r = 8e3) { - const n = [...this.connections.entries()], o = await Promise.all(n.map(async ([s, c]) => { + async queryQuorum(t, n = 8e3) { + const r = [...this.connections.entries()], o = await Promise.all(r.map(async ([c, s]) => { try { - const d = await c.queryOnce(t, r); - return { relayUrl: s, events: d, responded: !0 }; - } catch (d) { - return { relayUrl: s, events: [], responded: !1, error: d.message }; + const l = await s.queryOnce(t, n); + return { relayUrl: c, events: l, responded: !0 }; + } catch (l) { + return { relayUrl: c, events: [], responded: !1, error: l.message }; } - })), a = o.filter((s) => s.responded).length, i = o.length; + })), a = o.filter((c) => c.responded).length, i = o.length; return { outcomes: o, quorumMet: a >= Math.ceil(i / 2), @@ -193,11 +193,11 @@ class C { }; } /** Publishes an event to every configured relay, best-effort (§15.6, §24.4). */ - async publishAll(t, r = 8e3) { - const n = [...this.connections.entries()]; - return Promise.all(n.map(async ([o, a]) => { + async publishAll(t, n = 8e3) { + const r = [...this.connections.entries()]; + return Promise.all(r.map(async ([o, a]) => { try { - const i = await a.publish(t, r); + const i = await a.publish(t, n); return { relayUrl: o, result: i }; } catch (i) { return { relayUrl: o, result: { ok: !1, message: i.message } }; @@ -209,205 +209,205 @@ class C { t.close(); } } -function F(e) { +function te(e) { return e.filter((t) => t.result.ok).length; } -function ur(e, t, r) { - return W({ pubkey: E(e), created_at: r, kind: Je, tags: [], content: JSON.stringify(t) }, e); +function xn(e, t, n) { + return X({ pubkey: R(e), created_at: n, kind: ct, tags: [], content: JSON.stringify(t) }, e); } -function dr(e, t, r) { - const n = t.map((o) => { +function An(e, t, n) { + const r = t.map((o) => { const a = ["r", o.url]; return o.read && !o.write && a.push("read"), o.write && !o.read && a.push("write"), a; }); - return W({ pubkey: E(e), created_at: r, kind: Ce, tags: n, content: "" }, e); + return X({ pubkey: R(e), created_at: n, kind: qe, tags: r, content: "" }, e); } -function yr(e, t, r) { - return W({ - pubkey: E(e), - created_at: r, - kind: Le, - tags: t.map((n) => ["relay", n]), +function Cn(e, t, n) { + return X({ + pubkey: R(e), + created_at: n, + kind: ze, + tags: t.map((r) => ["relay", r]), content: "" }, e); } -function hr(e) { +function Ln(e) { return e.tags.filter((t) => t[0] === "r" && t[1]).map((t) => t[1]); } -function pr(e) { +function Tn(e) { return e.tags.filter((t) => t[0] === "relay" && t[1]).map((t) => t[1]); } -async function vr(e) { - const t = E(e.everydayPrivateKey), r = Math.floor(Date.now() / 1e3), n = [.../* @__PURE__ */ new Set([...e.generalRelays, ...e.dmRelays, ...e.discoveryRelays])], o = new C(n), [a, i, s] = await Promise.all([ - o.queryQuorum({ authors: [t], kinds: [Je] }), - o.queryQuorum({ authors: [t], kinds: [Ce] }), - o.queryQuorum({ authors: [t], kinds: [Le] }) +async function Nn(e) { + const t = R(e.everydayPrivateKey), n = Math.floor(Date.now() / 1e3), r = [.../* @__PURE__ */ new Set([...e.generalRelays, ...e.dmRelays, ...e.discoveryRelays])], o = new S(r), [a, i, c] = await Promise.all([ + o.queryQuorum({ authors: [t], kinds: [ct] }), + o.queryQuorum({ authors: [t], kinds: [qe] }), + o.queryQuorum({ authors: [t], kinds: [ze] }) ]); o.closeAll(); - const c = a.outcomes.some((u) => u.events.length > 0), d = i.outcomes.some((u) => u.events.length > 0), h = s.outcomes.some((u) => u.events.length > 0), m = new C(n); - let p = null, g = null, v = null; + const s = a.outcomes.some((h) => h.events.length > 0), l = i.outcomes.some((h) => h.events.length > 0), u = c.outcomes.some((h) => h.events.length > 0), d = new S(r); + let v = null, g = null, p = null; const f = []; - if (!c && (e.name || e.about || e.picture)) { - const u = ur(e.everydayPrivateKey, { name: e.name, about: e.about, picture: e.picture }, r); - f.push(m.publishAll(u).then((y) => void (p = y))); + if (!s && (e.name || e.about || e.picture)) { + const h = xn(e.everydayPrivateKey, { name: e.name, about: e.about, picture: e.picture }, n); + f.push(d.publishAll(h).then((y) => void (v = y))); } - if (!d) { - const u = dr(e.everydayPrivateKey, e.generalRelays.map((y) => ({ url: y, read: !0, write: !0 })), r); - f.push(m.publishAll(u).then((y) => void (g = y))); + if (!l) { + const h = An(e.everydayPrivateKey, e.generalRelays.map((y) => ({ url: y, read: !0, write: !0 })), n); + f.push(d.publishAll(h).then((y) => void (g = y))); } - if (!h) { - const u = yr(e.everydayPrivateKey, e.dmRelays, r); - f.push(m.publishAll(u).then((y) => void (v = y))); + if (!u) { + const h = Cn(e.everydayPrivateKey, e.dmRelays, n); + f.push(d.publishAll(h).then((y) => void (p = y))); } - return await Promise.all(f), m.closeAll(), { - profilePublished: p !== null && F(p) > 0, - relayListAcknowledgedCount: g !== null ? F(g) : 0, - dmRelayListAcknowledgedCount: v !== null ? F(v) : 0, - profileSkippedExisting: c, - relayListSkippedExisting: d, - dmRelayListSkippedExisting: h + return await Promise.all(f), d.closeAll(), { + profilePublished: v !== null && te(v) > 0, + relayListAcknowledgedCount: g !== null ? te(g) : 0, + dmRelayListAcknowledgedCount: p !== null ? te(p) : 0, + profileSkippedExisting: s, + relayListSkippedExisting: l, + dmRelayListSkippedExisting: u }; } -async function le(e, t, r) { - const n = r.minAcks ?? 2, o = r.minReadbacks ?? 2, a = await e.publishAll(t, r.timeoutMs), i = F(a), c = (await e.queryQuorum({ kinds: [ae], authors: [t.pubkey], "#d": [r.dTag], limit: 5 }, r.timeoutMs)).outcomes.filter((d) => d.events.some((h) => h.id === t.id)).length; +async function we(e, t, n) { + const r = n.minAcks ?? 2, o = n.minReadbacks ?? 2, a = await e.publishAll(t, n.timeoutMs), i = te(a), s = (await e.queryQuorum({ kinds: [he], authors: [t.pubkey], "#d": [n.dTag], limit: 5 }, n.timeoutMs)).outcomes.filter((l) => l.events.some((u) => u.id === t.id)).length; return { acknowledgedCount: i, - readbackVerifiedCount: c, - success: i >= n && c >= o + readbackVerifiedCount: s, + success: i >= r && s >= o }; } -function gr(e) { - const t = qt(e); +function Sn(e) { + const t = rn(e); if (t === void 0) throw new Error("Value is not JSON-serializable for canonicalization."); return t; } -function fr(e) { - return ye(gr(e)); +function In(e) { + return pe(Sn(e)); } -const ve = [1024, 2048, 4096], Y = 4; -function mr(e) { - const t = Y + e.length, r = ve.find((a) => a >= t); - if (r === void 0) - throw new Error(`Payload of ${e.length} bytes exceeds the largest padding bucket (${ve[ve.length - 1]} bytes minus ${Y}-byte length prefix).`); - const n = new Uint8Array(r); - return new DataView(n.buffer).setUint32(0, e.length, !1), n.set(e, Y), n; +const _e = [1024, 2048, 4096], re = 4; +function Mn(e) { + const t = re + e.length, n = _e.find((a) => a >= t); + if (n === void 0) + throw new Error(`Payload of ${e.length} bytes exceeds the largest padding bucket (${_e[_e.length - 1]} bytes minus ${re}-byte length prefix).`); + const r = new Uint8Array(n); + return new DataView(r.buffer).setUint32(0, e.length, !1), r.set(e, re), r; } -function wr(e) { - if (!ve.includes(e.length)) +function Un(e) { + if (!_e.includes(e.length)) throw new Error(`Padded plaintext length ${e.length} does not match a known bucket.`); - const r = new DataView(e.buffer, e.byteOffset, e.byteLength).getUint32(0, !1); - if (r > e.length - Y) + const n = new DataView(e.buffer, e.byteOffset, e.byteLength).getUint32(0, !1); + if (n > e.length - re) throw new Error("Declared payload length exceeds the padded bucket size."); - const n = e.slice(Y, Y + r), o = e.slice(Y + r); + const r = e.slice(re, re + n), o = e.slice(re + n); for (const a of o) if (a !== 0) throw new Error("Padding bytes are not all zero; capsule plaintext is malformed."); - return n; + return r; } -function Me() { +function We() { const e = globalThis.crypto; if (!e || !e.subtle) throw new Error("WebCrypto SubtleCrypto is not available in this environment."); return e; } -async function ct(e) { +async function bt(e) { if (e.length !== 32) throw new Error("AES-256-GCM key must be exactly 32 bytes."); - return Me().subtle.importKey("raw", e, "AES-GCM", !1, ["encrypt", "decrypt"]); + return We().subtle.importKey("raw", e, "AES-GCM", !1, ["encrypt", "decrypt"]); } -async function br(e, t, r) { - const n = zt(), o = await ct(e), a = await Me().subtle.encrypt({ name: "AES-GCM", iv: n, additionalData: r, tagLength: 128 }, o, t); - return { nonce: n, ciphertext: new Uint8Array(a) }; +async function On(e, t, n) { + const r = on(), o = await bt(e), a = await We().subtle.encrypt({ name: "AES-GCM", iv: r, additionalData: n, tagLength: 128 }, o, t); + return { nonce: r, ciphertext: new Uint8Array(a) }; } -async function kr(e, t, r, n) { - const o = await ct(e); +async function Dn(e, t, n, r) { + const o = await bt(e); try { - const a = await Me().subtle.decrypt({ name: "AES-GCM", iv: t, additionalData: n, tagLength: 128 }, o, r); + const a = await We().subtle.decrypt({ name: "AES-GCM", iv: t, additionalData: r, tagLength: 128 }, o, n); return new Uint8Array(a); } catch { throw new Error("AES-256-GCM authentication failed: capsule is corrupted, tampered, or the wrong key was used."); } } -function lt(e) { - return ye(`bitlogin|password-capsule|v1|${e}|30078|bitlogin:password:v1`); +function kt(e) { + return pe(`bitlogin|password-capsule|v1|${e}|30078|bitlogin:password:v1`); } -function ut(e) { - return ye(`bitlogin|recovery-capsule|v1|${e}|30078|bitlogin:recovery:v1`); +function Et(e) { + return pe(`bitlogin|recovery-capsule|v1|${e}|30078|bitlogin:recovery:v1`); } -async function dt(e, t, r) { - const n = fr(e), o = mr(n), a = await br(t, o, r); +async function _t(e, t, n) { + const r = In(e), o = Mn(r), a = await On(t, o, n); return { version: 1, algorithm: "aes-256-gcm", - nonce: X(a.nonce), - ciphertext: X(a.ciphertext) + nonce: oe(a.nonce), + ciphertext: oe(a.ciphertext) }; } -async function yt(e, t, r) { +async function Pt(e, t, n) { if (e.version !== 1 || e.algorithm !== "aes-256-gcm") throw new Error(`Unsupported capsule envelope version/algorithm: ${e.version}/${e.algorithm}`); - const n = ne(e.nonce), o = ne(e.ciphertext), a = await kr(t, n, o, r), i = wr(a); - return JSON.parse(Ye(i)); + const r = ue(e.nonce), o = ue(e.ciphertext), a = await Dn(t, r, o, n), i = Un(a); + return JSON.parse(je(i)); } -class Ue extends Error { +class He extends Error { constructor(t) { super(t), this.name = "CapsuleValidationError"; } } -const _r = /* @__PURE__ */ new Set(["wss:", "ws:"]), Er = 1e6, Pr = /^[0-9a-f]{64}$/u; -function P(e, t) { +const qn = /* @__PURE__ */ new Set(["wss:", "ws:"]), zn = 1e6, jn = /^[0-9a-f]{64}$/u; +function x(e, t) { if (!e) - throw new Ue(t); + throw new He(t); } -function ge(e) { - return typeof e == "string" && Pr.test(e); +function Pe(e) { + return typeof e == "string" && jn.test(e); } -function ht(e) { - P(Array.isArray(e), "vault_relay_hints must be an array (§12.4.7)."); +function Kt(e) { + x(Array.isArray(e), "vault_relay_hints must be an array (§12.4.7)."); for (const t of e) { - P(typeof t == "string", "Each relay hint must be a string (§12.4.7)."); - let r; + x(typeof t == "string", "Each relay hint must be a string (§12.4.7)."); + let n; try { - r = new URL(t); + n = new URL(t); } catch { - throw new Ue(`Invalid relay URL: ${String(t)} (§12.4.7)`); + throw new He(`Invalid relay URL: ${String(t)} (§12.4.7)`); } - P(_r.has(r.protocol), `Relay URL uses a disallowed scheme: ${t} (§12.4.7)`); + x(qn.has(n.protocol), `Relay URL uses a disallowed scheme: ${t} (§12.4.7)`); } } -function pt(e) { - P(typeof e == "string", "account_id must be a string (§12.4.2)."); +function Rt(e) { + x(typeof e == "string", "account_id must be a string (§12.4.2)."); let t; try { - t = ne(e); + t = ue(e); } catch { - throw new Ue("account_id is not valid base64url (§12.4.2)."); + throw new He("account_id is not valid base64url (§12.4.2)."); } - P(t.length === 16, "account_id must decode to exactly 128 bits (§12.4.2)."); + x(t.length === 16, "account_id must decode to exactly 128 bits (§12.4.2)."); } -function vt(e, t) { - P(typeof e == "string", "operational_private_key must be a string (§12.4.3)."); - const r = ne(e); - P(r.length === 32, "operational_private_key must be exactly 32 bytes (§12.4.3)."), P(Te(r), "operational_private_key is not a valid secp256k1 scalar (§12.4.3)."), P(ge(t), "operational_public_key must be lowercase 64-char hex (§12.4.4)."); - const n = E(r); - P(n === t, "operational_public_key does not match the derived public key (§12.4.4)."); +function xt(e, t) { + x(typeof e == "string", "operational_private_key must be a string (§12.4.3)."); + const n = ue(e); + x(n.length === 32, "operational_private_key must be exactly 32 bytes (§12.4.3)."), x(Be(n), "operational_private_key is not a valid secp256k1 scalar (§12.4.3)."), x(Pe(t), "operational_public_key must be lowercase 64-char hex (§12.4.4)."); + const r = R(n); + x(r === t, "operational_public_key does not match the derived public key (§12.4.4)."); } -function gt(e, t) { - P(Number.isInteger(e) && e >= 0 && e <= Er, `${t} is out of supported bounds (§12.4.8).`); +function At(e, t) { + x(Number.isInteger(e) && e >= 0 && e <= zn, `${t} is out of supported bounds (§12.4.8).`); } -function Kr(e) { - P(e.schema === we, `Unsupported or unknown schema: ${String(e.schema)} (§12.4.1)`), pt(e.account_id), gt(e.generation, "generation"), vt(e.operational_private_key, e.operational_public_key), P(ge(e.recovery_public_key), "recovery_public_key must be lowercase 64-char hex (§12.4.5)."), ht(e.vault_relay_hints); +function $n(e) { + x(e.schema === Re, `Unsupported or unknown schema: ${String(e.schema)} (§12.4.1)`), Rt(e.account_id), At(e.generation, "generation"), xt(e.operational_private_key, e.operational_public_key), x(Pe(e.recovery_public_key), "recovery_public_key must be lowercase 64-char hex (§12.4.5)."), Kt(e.vault_relay_hints); const t = e.recovery_capsule_event; - P(!!t && typeof t == "object", "recovery_capsule_event must be present (§12.4.6)."), P(ee(t), "Embedded recovery_capsule_event has an invalid event id or signature (§12.4.6)."), P(t.pubkey === e.recovery_public_key, "Embedded recovery_capsule_event author does not match recovery_public_key (§12.4.5)."); + x(!!t && typeof t == "object", "recovery_capsule_event must be present (§12.4.6)."), x(ce(t), "Embedded recovery_capsule_event has an invalid event id or signature (§12.4.6)."), x(t.pubkey === e.recovery_public_key, "Embedded recovery_capsule_event author does not match recovery_public_key (§12.4.5)."); } -function Rr(e) { - P(e.schema === Ne, `Unsupported or unknown schema: ${String(e.schema)} (§12.4.1)`), pt(e.account_id), gt(e.recovery_generation, "recovery_generation"), P(e.previous_recovery_event_id === null || ge(e.previous_recovery_event_id), "previous_recovery_event_id must be null or lowercase 64-char hex (§12.3)."), vt(e.operational_private_key, e.operational_public_key), P(ge(e.recovery_public_key), "recovery_public_key must be lowercase 64-char hex."), ht(e.vault_relay_hints); +function Bn(e) { + x(e.schema === $e, `Unsupported or unknown schema: ${String(e.schema)} (§12.4.1)`), Rt(e.account_id), At(e.recovery_generation, "recovery_generation"), x(e.previous_recovery_event_id === null || Pe(e.previous_recovery_event_id), "previous_recovery_event_id must be null or lowercase 64-char hex (§12.3)."), xt(e.operational_private_key, e.operational_public_key), x(Pe(e.recovery_public_key), "recovery_public_key must be lowercase 64-char hex."), Kt(e.vault_relay_hints); } -function xr(e) { - const t = new Map(e.map((n) => [n.recoveryGeneration, n])), r = [...e].sort((n, o) => n.recoveryGeneration - o.recoveryGeneration); - for (let n = 1; n < r.length; n++) { - const o = r[n], a = t.get(o.recoveryGeneration - 1); +function Gn(e) { + const t = new Map(e.map((r) => [r.recoveryGeneration, r])), n = [...e].sort((r, o) => r.recoveryGeneration - o.recoveryGeneration); + for (let r = 1; r < n.length; r++) { + const o = n[r], a = t.get(o.recoveryGeneration - 1); if (o.previousRecoveryEventId === null) return { consistent: !1, warning: `Generation ${o.recoveryGeneration} has a null previous-event link but is not the first generation.` }; if (a && o.previousRecoveryEventId !== a.eventId) @@ -418,99 +418,99 @@ function xr(e) { } return { consistent: !0 }; } -async function Oe(e) { - const t = E(e.locatorPrivateKey), r = await dt(e.payload, e.capsuleKey, lt(t)); - return W({ +async function Fe(e) { + const t = R(e.locatorPrivateKey), n = await _t(e.payload, e.capsuleKey, kt(t)); + return X({ pubkey: t, created_at: e.payload.created_at, - kind: ae, - tags: [["d", ie]], - content: JSON.stringify(r) + kind: he, + tags: [["d", fe]], + content: JSON.stringify(n) }, e.locatorPrivateKey); } -function Ar(e) { - const t = E(e.oldLocatorPrivateKey); - return W({ +function Vn(e) { + const t = R(e.oldLocatorPrivateKey); + return X({ pubkey: t, created_at: e.createdAt, - kind: ae, - tags: [["d", ie]], + kind: he, + tags: [["d", fe]], content: "" }, e.oldLocatorPrivateKey); } -async function Cr(e, t) { - if (!ee(e)) +async function Wn(e, t) { + if (!ce(e)) throw new Error("Credential capsule event has an invalid id or signature."); - const r = JSON.parse(e.content), n = await yt(r, t, lt(e.pubkey)); - return Kr(n), n; + const n = JSON.parse(e.content), r = await Pt(n, t, kt(e.pubkey)); + return $n(r), r; } -async function ft(e) { - const t = E(e.recoveryPrivateKey), r = await dt(e.payload, e.capsuleKey, ut(t)); - return W({ +async function Ct(e) { + const t = R(e.recoveryPrivateKey), n = await _t(e.payload, e.capsuleKey, Et(t)); + return X({ pubkey: t, created_at: e.payload.created_at, - kind: ae, - tags: [["d", be]], - content: JSON.stringify(r) + kind: he, + tags: [["d", xe]], + content: JSON.stringify(n) }, e.recoveryPrivateKey); } -async function mt(e, t) { - if (!ee(e)) +async function Lt(e, t) { + if (!ce(e)) throw new Error("Recovery capsule event has an invalid id or signature."); - const r = JSON.parse(e.content), n = await yt(r, t, ut(e.pubkey)); - return Rr(n), n; + const n = JSON.parse(e.content), r = await Pt(n, t, Et(e.pubkey)); + return Bn(r), r; } -function Lr(e) { +function Hn(e) { const t = /* @__PURE__ */ new Map(); - for (const r of e) - ee(r) && t.set(r.id, r); - return [...t.values()].sort((r, n) => n.created_at - r.created_at); + for (const n of e) + ce(n) && t.set(n.id, n); + return [...t.values()].sort((n, r) => r.created_at - n.created_at); } -async function wt(e, t, r, n, o) { - const a = await e.queryQuorum({ kinds: [ae], authors: [t], "#d": [r], limit: 5 }, o), i = a.outcomes.flatMap((p) => p.events), s = Lr(i), c = []; - for (const p of s) +async function Tt(e, t, n, r, o) { + const a = await e.queryQuorum({ kinds: [he], authors: [t], "#d": [n], limit: 5 }, o), i = a.outcomes.flatMap((v) => v.events), c = Hn(i), s = []; + for (const v of c) try { - const g = await n(p); - c.push({ event: p, payload: g }); + const g = await r(v); + s.push({ event: v, payload: g }); } catch (g) { - c.push({ event: p, payload: null, error: g.message }); + s.push({ event: v, payload: null, error: g.message }); } - const d = c.find((p) => p.payload !== null) ?? null, h = a.outcomes.filter((p) => p.responded && p.events.length > 0).map((p) => p.events.slice().sort((g, v) => v.created_at - g.created_at)[0].id), m = new Set(h).size > 1; + const l = s.find((v) => v.payload !== null) ?? null, u = a.outcomes.filter((v) => v.responded && v.events.length > 0).map((v) => v.events.slice().sort((g, p) => p.created_at - g.created_at)[0].id), d = new Set(u).size > 1; return { quorumMet: a.quorumMet, respondedCount: a.respondedCount, totalCount: a.totalCount, - candidates: c, - best: d, - relayDisagreement: m + candidates: s, + best: l, + relayDisagreement: d }; } -async function fe(e, t, r, n = 8e3) { - return wt(e, t, ie, (o) => Cr(o, r), n); +async function Ke(e, t, n, r = 8e3) { + return Tt(e, t, fe, (o) => Wn(o, n), r); } -async function Nr(e, t, r, n = 8e3) { - return wt(e, t, be, (o) => mt(o, r), n); +async function Fn(e, t, n, r = 8e3) { + return Tt(e, t, xe, (o) => Lt(o, n), r); } -function Tr(e) { - const t = e.filter((r) => r.payload !== null); - return t.length < 2 ? { consistent: !0 } : xr(t.map((r) => ({ - eventId: r.event.id, - recoveryGeneration: r.payload.recovery_generation, - previousRecoveryEventId: r.payload.previous_recovery_event_id +function Jn(e) { + const t = e.filter((n) => n.payload !== null); + return t.length < 2 ? { consistent: !0 } : Gn(t.map((n) => ({ + eventId: n.event.id, + recoveryGeneration: n.payload.recovery_generation, + previousRecoveryEventId: n.payload.previous_recovery_event_id }))); } -async function Ir(e, t, r, n) { +async function Yn(e, t, n, r) { const [o, a] = await Promise.all([ - e.publishAll(t, n), - e.publishAll(r, n) + e.publishAll(t, r), + e.publishAll(n, r) ]); return { - credentialAcknowledgedCount: F(o), - recoveryAcknowledgedCount: F(a), + credentialAcknowledgedCount: te(o), + recoveryAcknowledgedCount: te(a), relaysTried: e.relayUrls.length }; } -const bt = `abandon +const Nt = `abandon ability able about @@ -2559,340 +2559,340 @@ zero zone zoo`.split(` `); -function Sr(e, t, r, n) { - Gt(e); - const o = Vt({ dkLen: 32, asyncTick: 10 }, n), { c: a, dkLen: i, asyncTick: s } = o; - if (_e(a), _e(i), _e(s), a < 1) +function Qn(e, t, n, r) { + cn(e); + const o = ln({ dkLen: 32, asyncTick: 10 }, r), { c: a, dkLen: i, asyncTick: c } = o; + if (Le(a), Le(i), Le(c), a < 1) throw new Error("iterations (c) should be >= 1"); - const c = ze(t), d = ze(r), h = new Uint8Array(i), m = Qe.create(e, c), p = m._cloneInto().update(d); - return { c: a, dkLen: i, asyncTick: s, DK: h, PRF: m, PRFSalt: p }; + const s = et(t), l = et(n), u = new Uint8Array(i), d = lt.create(e, s), v = d._cloneInto().update(l); + return { c: a, dkLen: i, asyncTick: c, DK: u, PRF: d, PRFSalt: v }; } -function Mr(e, t, r, n, o) { - return e.destroy(), t.destroy(), n && n.destroy(), Bt(o), r; +function Xn(e, t, n, r, o) { + return e.destroy(), t.destroy(), r && r.destroy(), un(o), n; } -async function Ur(e, t, r, n) { - const { c: o, dkLen: a, asyncTick: i, DK: s, PRF: c, PRFSalt: d } = Sr(e, t, r, n); - let h; - const m = new Uint8Array(4), p = jt(m), g = new Uint8Array(c.outputLen); - for (let v = 1, f = 0; f < a; v++, f += c.outputLen) { - const u = s.subarray(f, f + c.outputLen); - p.setInt32(0, v, !1), (h = d._cloneInto(h)).update(m).digestInto(g), u.set(g.subarray(0, u.length)), await $t(o - 1, i, () => { - c._cloneInto(h).update(g).digestInto(g); - for (let y = 0; y < u.length; y++) - u[y] ^= g[y]; +async function Zn(e, t, n, r) { + const { c: o, dkLen: a, asyncTick: i, DK: c, PRF: s, PRFSalt: l } = Qn(e, t, n, r); + let u; + const d = new Uint8Array(4), v = an(d), g = new Uint8Array(s.outputLen); + for (let p = 1, f = 0; f < a; p++, f += s.outputLen) { + const h = c.subarray(f, f + s.outputLen); + v.setInt32(0, p, !1), (u = l._cloneInto(u)).update(d).digestInto(g), h.set(g.subarray(0, h.length)), await sn(o - 1, i, () => { + s._cloneInto(u).update(g).digestInto(g); + for (let y = 0; y < h.length; y++) + h[y] ^= g[y]; }); } - return Mr(c, d, s, h, g); + return Xn(s, l, c, u, g); } /*! scure-bip39 - MIT License (c) 2022 Patricio Palladino, Paul Miller (paulmillr.com) */ -const Or = (e) => e[0] === "あいこくしん"; -function kt(e) { +const er = (e) => e[0] === "あいこくしん"; +function St(e) { if (typeof e != "string") throw new TypeError("invalid mnemonic type: " + typeof e); return e.normalize("NFKD"); } -function _t(e) { - const t = kt(e), r = t.split(" "); - if (![12, 15, 18, 21, 24].includes(r.length)) +function It(e) { + const t = St(e), n = t.split(" "); + if (![12, 15, 18, 21, 24].includes(n.length)) throw new Error("Invalid mnemonic"); - return { nfkd: t, words: r }; + return { nfkd: t, words: n }; } -function Et(e) { - Ht(e, 16, 20, 24, 28, 32); +function Mt(e) { + yn(e, 16, 20, 24, 28, 32); } -const Dr = (e) => { +const tr = (e) => { const t = 8 - e.length / 4; - return new Uint8Array([Xe(e)[0] >> t << t]); + return new Uint8Array([ut(e)[0] >> t << t]); }; -function Pt(e) { +function Ut(e) { if (!Array.isArray(e) || e.length !== 2048 || typeof e[0] != "string") throw new Error("Wordlist: expected array of 2048 strings"); return e.forEach((t) => { if (typeof t != "string") throw new Error("wordlist: non-string element: " + t); - }), he.chain(he.checksum(1, Dr), he.radix2(11, !0), he.alphabet(e)); + }), ke.chain(ke.checksum(1, tr), ke.radix2(11, !0), ke.alphabet(e)); } -function qr(e, t) { - const { words: r } = _t(e), n = Pt(t).decode(r); - return Et(n), n; +function nr(e, t) { + const { words: n } = It(e), r = Ut(t).decode(n); + return Mt(r), r; } -function zr(e, t) { - return Et(e), Pt(t).encode(e).join(Or(t) ? " " : " "); +function rr(e, t) { + return Mt(e), Ut(t).encode(e).join(er(t) ? " " : " "); } -function jr(e, t) { +function or(e, t) { try { - qr(e, t); + nr(e, t); } catch { return !1; } return !0; } -const $r = (e) => kt("mnemonic" + e); -function Gr(e, t = "") { - return Ur(Wt, _t(e).nfkd, $r(t), { c: 2048, dkLen: 64 }); +const ar = (e) => St("mnemonic" + e); +function ir(e, t = "") { + return Zn(dn, It(e).nfkd, ar(t), { c: 2048, dkLen: 64 }); } -function Vr(e) { +function sr(e) { if (e.length !== 16) throw new Error("Recovery phrase entropy must be exactly 128 bits (16 bytes)."); - return zr(e, bt); + return rr(e, Nt); } -function Br(e) { +function cr(e) { try { - return jr(Kt(e), bt); + return or(Ot(e), Nt); } catch { return !1; } } -function Kt(e) { +function Ot(e) { return e.trim().normalize("NFKD").split(/\s+/u).join(" "); } -async function Rt(e) { - return Gr(Kt(e), ""); +async function Dt(e) { + return ir(Ot(e), ""); } -const De = Ft.id, ue = "aes-256-gcm-v1", de = "bitlogin-bip39-hkdf-v1"; -async function xt(e) { - const t = ke(e.loginName), r = e.now ?? Math.floor(Date.now() / 1e3), { locatorPrivateKey: n, capsuleKey: o } = await ce(e.password, t), a = E(n), i = new C(e.vaultRelayUrls, { authPrivateKey: n }); - let s; +const Je = hn.id, me = "aes-256-gcm-v1", be = "bitlogin-bip39-hkdf-v1"; +async function qt(e) { + const t = Ae(e.loginName), n = e.now ?? Math.floor(Date.now() / 1e3), { locatorPrivateKey: r, capsuleKey: o } = await ge(e.password, t), a = R(r), i = new S(e.vaultRelayUrls, { authPrivateKey: r }); + let c; try { - s = await fe(i, a, o, e.timeoutMs); + c = await Ke(i, a, o, e.timeoutMs); } finally { i.closeAll(); } - if (!s.quorumMet) - throw new Z("Couldn't verify this login name and password aren't already registered. Please retry, or add more vault relays."); - if (s.candidates.length > 0) - throw new rt(); - const c = Vr(Yt()), d = await Rt(c), { recoveryPrivateKey: h, capsuleKey: m } = et(d), p = E(h), g = e.everydayPrivateKey !== void 0; - if (g && !Te(e.everydayPrivateKey)) - throw new Z("The provided key is not a valid secp256k1 private key."); - const v = g ? e.everydayPrivateKey : Qt(), f = E(v), u = X(Xt()), y = { - schema: Ne, - account_id: u, + if (!c.quorumMet) + throw new ae("Couldn't verify this login name and password aren't already registered. Please retry, or add more vault relays."); + if (c.candidates.length > 0) + throw new pt(); + const s = sr(fn()), l = await Dt(s), { recoveryPrivateKey: u, capsuleKey: d } = dt(l), v = R(u), g = e.everydayPrivateKey !== void 0; + if (g && !Be(e.everydayPrivateKey)) + throw new ae("The provided key is not a valid secp256k1 private key."); + const p = g ? e.everydayPrivateKey : vn(), f = R(p), h = oe(gn()), y = { + schema: $e, + account_id: h, recovery_generation: 0, previous_recovery_event_id: null, - operational_private_key: X(v), + operational_private_key: oe(p), operational_public_key: f, - recovery_public_key: p, - created_at: r, + recovery_public_key: v, + created_at: n, vault_relay_hints: e.vaultRelayUrls, - protocol: { capsule_encryption: ue, recovery_derivation: de } - }, k = await ft({ - recoveryPrivateKey: h, - capsuleKey: m, + protocol: { capsule_encryption: me, recovery_derivation: be } + }, m = await Ct({ + recoveryPrivateKey: u, + capsuleKey: d, payload: y }), b = { - schema: we, - account_id: u, + schema: Re, + account_id: h, generation: 0, - operational_private_key: X(v), + operational_private_key: oe(p), operational_public_key: f, - recovery_public_key: p, - recovery_capsule_event: k, - created_at: r, + recovery_public_key: v, + recovery_capsule_event: m, + created_at: n, vault_relay_hints: e.vaultRelayUrls, protocol: { - password_kdf: De, - capsule_encryption: ue, - recovery_derivation: de + password_kdf: Je, + capsule_encryption: me, + recovery_derivation: be } - }, K = await Oe({ - locatorPrivateKey: n, + }, _ = await Fe({ + locatorPrivateKey: r, capsuleKey: o, payload: b - }), G = new C(e.vaultRelayUrls, { authPrivateKey: h }), _ = await le(G, k, { - dTag: be, + }), I = new S(e.vaultRelayUrls, { authPrivateKey: u }), E = await we(I, m, { + dTag: xe, minAcks: e.minAcknowledgements, timeoutMs: e.timeoutMs }); - G.closeAll(); - const R = new C(e.vaultRelayUrls, { authPrivateKey: n }), x = await le(R, K, { - dTag: ie, + I.closeAll(); + const P = new S(e.vaultRelayUrls, { authPrivateKey: r }), K = await we(P, _, { + dTag: fe, minAcks: e.minAcknowledgements, timeoutMs: e.timeoutMs }); - if (R.closeAll(), !_.success || !x.success) - throw new Z("Registration did not reach the required relay acknowledgement and readback quorum. Please retry, or add more vault relays."); + if (P.closeAll(), !E.success || !K.success) + throw new ae("Registration did not reach the required relay acknowledgement and readback quorum. Please retry, or add more vault relays."); return { normalizedLoginName: t, - recoveryPhrase: c, - everydayPrivateKey: v, + recoveryPhrase: s, + everydayPrivateKey: p, everydayPublicKey: f, - recoveryPublicKey: p, + recoveryPublicKey: v, locatorPublicKey: a, - accountId: u, + accountId: h, imported: g, - credentialEvent: K, - recoveryEvent: k, - credentialPublish: x, - recoveryPublish: _ + credentialEvent: _, + recoveryEvent: m, + credentialPublish: K, + recoveryPublish: E }; } -async function Wr(e) { - const { nsecOrHex: t, ...r } = e, n = At(t); - return xt({ ...r, everydayPrivateKey: n }); +async function lr(e) { + const { nsecOrHex: t, ...n } = e, r = zt(t); + return qt({ ...n, everydayPrivateKey: r }); } -function At(e) { +function zt(e) { const t = e.trim(); - let r; + let n; if (t.startsWith("nsec1")) - r = Jt(t); + n = pn(t); else if (/^[0-9a-fA-F]{64}$/u.test(t)) - r = Ze(t.toLowerCase()); + n = Ge(t.toLowerCase()); else - throw new Z("Enter a valid nsec (nsec1…) or a 64-character hex private key."); - if (!Te(r)) - throw new Z("The provided key is not a valid secp256k1 private key."); - return r; + throw new ae("Enter a valid nsec (nsec1…) or a 64-character hex private key."); + if (!Be(n)) + throw new ae("The provided key is not a valid secp256k1 private key."); + return n; } -class Ct { +class jt { map = /* @__PURE__ */ new Map(); async get(t) { return this.map.get(t); } - async set(t, r) { - this.map.set(t, r); + async set(t, n) { + this.map.set(t, n); } async delete(t) { this.map.delete(t); } } -async function Hr(e) { - const t = ke(e.loginName), { locatorPrivateKey: r, capsuleKey: n } = await ce(e.password, t), o = E(r), a = new C(e.vaultRelayUrls, { authPrivateKey: r }); +async function ur(e) { + const t = Ae(e.loginName), { locatorPrivateKey: n, capsuleKey: r } = await ge(e.password, t), o = R(n), a = new S(e.vaultRelayUrls, { authPrivateKey: n }); try { - const i = await fe(a, o, n, e.timeoutMs); + const i = await Ke(a, o, r, e.timeoutMs); if (!i.quorumMet) - throw new oe("quorum-not-met"); + throw new ye("quorum-not-met"); if (!i.best) - throw new oe(i.candidates.length > 0 ? "no-valid-candidate" : "no-matching-event"); - const s = i.best.payload, c = e.store ?? new Ct(), d = await Se(c, s.operational_public_key), h = s.generation < d.generation; - if (h && !e.acknowledgeRollback) - throw new ot(d.generation, s.generation); - const m = h ? `This device previously saw credential generation ${d.generation}, but the accepted capsule is generation ${s.generation}. Relays may be serving stale data, or an old capsule is being replayed.` : void 0; - await st(c, s.operational_public_key, { generation: s.generation }); - const p = i.relayDisagreement ? 'Configured relays returned different credential capsules as "latest" for this account. Some relays may be stale, censored, or malicious.' : void 0; + throw new ye(i.candidates.length > 0 ? "no-valid-candidate" : "no-matching-event"); + const c = i.best.payload, s = e.store ?? new jt(), l = await Ve(s, c.operational_public_key), u = c.generation < l.generation; + if (u && !e.acknowledgeRollback) + throw new vt(l.generation, c.generation); + const d = u ? `This device previously saw credential generation ${l.generation}, but the accepted capsule is generation ${c.generation}. Relays may be serving stale data, or an old capsule is being replayed.` : void 0; + await mt(s, c.operational_public_key, { generation: c.generation }); + const v = i.relayDisagreement ? 'Configured relays returned different credential capsules as "latest" for this account. Some relays may be stale, censored, or malicious.' : void 0; return { - everydayPrivateKey: ne(s.operational_private_key), - everydayPublicKey: s.operational_public_key, - recoveryPublicKey: s.recovery_public_key, - accountId: s.account_id, - generation: s.generation, + everydayPrivateKey: ue(c.operational_private_key), + everydayPublicKey: c.operational_public_key, + recoveryPublicKey: c.recovery_public_key, + accountId: c.account_id, + generation: c.generation, credentialEvent: i.best.event, - recoveryCapsuleEvent: s.recovery_capsule_event, - rollbackWarning: m, - relayDisagreementWarning: p + recoveryCapsuleEvent: c.recovery_capsule_event, + rollbackWarning: d, + relayDisagreementWarning: v }; } finally { a.closeAll(); } } -function $e(e) { - return e.filter((t) => ee(t)).sort((t, r) => r.created_at - t.created_at)[0]; +function nt(e) { + return e.filter((t) => ce(t)).sort((t, n) => n.created_at - t.created_at)[0]; } -async function Fr(e) { - if (!Br(e.phrase)) - throw new nt("This does not look like a valid 12-word BitLogin recovery phrase."); - const t = await Rt(e.phrase), { recoveryPrivateKey: r, capsuleKey: n } = et(t), o = E(r), a = new C(e.vaultRelayUrls, { authPrivateKey: r }); +async function dr(e) { + if (!cr(e.phrase)) + throw new ft("This does not look like a valid 12-word BitLogin recovery phrase."); + const t = await Dt(e.phrase), { recoveryPrivateKey: n, capsuleKey: r } = dt(t), o = R(n), a = new S(e.vaultRelayUrls, { authPrivateKey: n }); let i; try { - i = await Nr(a, o, n, e.timeoutMs); + i = await Fn(a, o, r, e.timeoutMs); } finally { a.closeAll(); } if (e.offlineRecoveryCapsuleEvents?.length) { const f = []; for (const b of e.offlineRecoveryCapsuleEvents) - if (ee(b)) + if (ce(b)) try { - f.push({ event: b, payload: await mt(b, n) }); - } catch (K) { - f.push({ event: b, payload: null, error: K.message }); + f.push({ event: b, payload: await Lt(b, r) }); + } catch (_) { + f.push({ event: b, payload: null, error: _.message }); } - const u = /* @__PURE__ */ new Map(); + const h = /* @__PURE__ */ new Map(); for (const b of [...i.candidates, ...f]) - u.set(b.event.id, b); - const y = [...u.values()].sort((b, K) => K.event.created_at - b.event.created_at), k = y.find((b) => b.payload !== null) ?? null; - i = { ...i, candidates: y, best: k, quorumMet: i.quorumMet || k !== null }; + h.set(b.event.id, b); + const y = [...h.values()].sort((b, _) => _.event.created_at - b.event.created_at), m = y.find((b) => b.payload !== null) ?? null; + i = { ...i, candidates: y, best: m, quorumMet: i.quorumMet || m !== null }; } if (!i.quorumMet) - throw new oe("quorum-not-met"); + throw new ye("quorum-not-met"); if (!i.best) - throw new oe(i.candidates.length > 0 ? "no-valid-candidate" : "no-matching-event"); - const s = i.best.payload, c = Tr(i.candidates), d = ne(s.operational_private_key), h = s.operational_public_key, m = [.../* @__PURE__ */ new Set([...e.discoveryRelayUrls, ...s.vault_relay_hints])], p = new C(m); - let g = [], v = []; + throw new ye(i.candidates.length > 0 ? "no-valid-candidate" : "no-matching-event"); + const c = i.best.payload, s = Jn(i.candidates), l = ue(c.operational_private_key), u = c.operational_public_key, d = [.../* @__PURE__ */ new Set([...e.discoveryRelayUrls, ...c.vault_relay_hints])], v = new S(d); + let g = [], p = []; try { - const f = await p.queryQuorum({ kinds: [Ce], authors: [h], limit: 5 }, e.timeoutMs), u = $e(f.outcomes.flatMap((b) => b.events)); - u && (g = hr(u)); - const y = await p.queryQuorum({ kinds: [Le], authors: [h], limit: 5 }, e.timeoutMs), k = $e(y.outcomes.flatMap((b) => b.events)); - k && (v = pr(k)); + const f = await v.queryQuorum({ kinds: [qe], authors: [u], limit: 5 }, e.timeoutMs), h = nt(f.outcomes.flatMap((b) => b.events)); + h && (g = Ln(h)); + const y = await v.queryQuorum({ kinds: [ze], authors: [u], limit: 5 }, e.timeoutMs), m = nt(y.outcomes.flatMap((b) => b.events)); + m && (p = Tn(m)); } finally { - p.closeAll(); + v.closeAll(); } return { - everydayPrivateKey: d, - everydayPublicKey: h, - recoveryPrivateKey: r, + everydayPrivateKey: l, + everydayPublicKey: u, + recoveryPrivateKey: n, recoveryPublicKey: o, - recoveryCapsuleKey: n, - accountId: s.account_id, + recoveryCapsuleKey: r, + accountId: c.account_id, currentRecoveryEvent: i.best.event, - currentRecoveryPayload: s, + currentRecoveryPayload: c, generalRelays: g, - dmRelays: v, - chainWarning: c.consistent ? void 0 : c.warning + dmRelays: p, + chainWarning: s.consistent ? void 0 : s.warning }; } -async function Jr(e) { - const t = e.now ?? Math.floor(Date.now() / 1e3), r = ke(e.newLoginName), { recovered: n } = e, o = { - schema: Ne, - account_id: n.accountId, - recovery_generation: n.currentRecoveryPayload.recovery_generation + 1, - previous_recovery_event_id: n.currentRecoveryEvent.id, - operational_private_key: X(n.everydayPrivateKey), - operational_public_key: n.everydayPublicKey, - recovery_public_key: n.recoveryPublicKey, - created_at: at(n.currentRecoveryEvent.created_at, t), +async function yr(e) { + const t = e.now ?? Math.floor(Date.now() / 1e3), n = Ae(e.newLoginName), { recovered: r } = e, o = { + schema: $e, + account_id: r.accountId, + recovery_generation: r.currentRecoveryPayload.recovery_generation + 1, + previous_recovery_event_id: r.currentRecoveryEvent.id, + operational_private_key: oe(r.everydayPrivateKey), + operational_public_key: r.everydayPublicKey, + recovery_public_key: r.recoveryPublicKey, + created_at: gt(r.currentRecoveryEvent.created_at, t), vault_relay_hints: e.vaultRelayUrls, - protocol: { capsule_encryption: ue, recovery_derivation: de } - }, a = await ft({ - recoveryPrivateKey: n.recoveryPrivateKey, - capsuleKey: n.recoveryCapsuleKey, + protocol: { capsule_encryption: me, recovery_derivation: be } + }, a = await Ct({ + recoveryPrivateKey: r.recoveryPrivateKey, + capsuleKey: r.recoveryCapsuleKey, payload: o - }), i = new C(e.vaultRelayUrls, { authPrivateKey: n.recoveryPrivateKey }), s = await le(i, a, { - dTag: be, + }), i = new S(e.vaultRelayUrls, { authPrivateKey: r.recoveryPrivateKey }), c = await we(i, a, { + dTag: xe, minAcks: e.minAcknowledgements, timeoutMs: e.timeoutMs }); i.closeAll(); - const { locatorPrivateKey: c, capsuleKey: d } = await ce(e.newPassword, r), h = E(c), m = { - schema: we, - account_id: n.accountId, + const { locatorPrivateKey: s, capsuleKey: l } = await ge(e.newPassword, n), u = R(s), d = { + schema: Re, + account_id: r.accountId, generation: 0, - operational_private_key: X(n.everydayPrivateKey), - operational_public_key: n.everydayPublicKey, - recovery_public_key: n.recoveryPublicKey, + operational_private_key: oe(r.everydayPrivateKey), + operational_public_key: r.everydayPublicKey, + recovery_public_key: r.recoveryPublicKey, recovery_capsule_event: a, created_at: t, vault_relay_hints: e.vaultRelayUrls, protocol: { - password_kdf: De, - capsule_encryption: ue, - recovery_derivation: de + password_kdf: Je, + capsule_encryption: me, + recovery_derivation: be } - }, p = await Oe({ locatorPrivateKey: c, capsuleKey: d, payload: m }), g = new C(e.vaultRelayUrls, { authPrivateKey: c }), v = await le(g, p, { - dTag: ie, + }, v = await Fe({ locatorPrivateKey: s, capsuleKey: l, payload: d }), g = new S(e.vaultRelayUrls, { authPrivateKey: s }), p = await we(g, v, { + dTag: fe, minAcks: e.minAcknowledgements, timeoutMs: e.timeoutMs }); - if (g.closeAll(), !s.success || !v.success) - throw new nt("Could not publish the refreshed recovery and credential capsules to enough relays. Please retry."); - return { normalizedLoginName: r, locatorPublicKey: h, credentialEvent: p, refreshedRecoveryEvent: a, credentialPublish: v, recoveryPublish: s }; + if (g.closeAll(), !c.success || !p.success) + throw new ft("Could not publish the refreshed recovery and credential capsules to enough relays. Please retry."); + return { normalizedLoginName: n, locatorPublicKey: u, credentialEvent: v, refreshedRecoveryEvent: a, credentialPublish: p, recoveryPublish: c }; } -function Yr(e) { - const t = E(e.privateKey); - return W({ +function hr(e) { + const t = R(e.privateKey); + return X({ pubkey: t, created_at: e.createdAt, - kind: Zt, + kind: wn, tags: [ ["e", e.eventIdToDelete], ["k", String(e.deletedEventKind)] @@ -2900,306 +2900,545 @@ function Yr(e) { content: e.reason ?? "" }, e.privateKey); } -async function Qr(e) { - const t = e.now ?? Math.floor(Date.now() / 1e3), r = ke(e.loginName), n = await ce(e.oldPassword, r), o = E(n.locatorPrivateKey), a = new C(e.vaultRelayUrls, { authPrivateKey: n.locatorPrivateKey }); +async function pr(e) { + const t = e.now ?? Math.floor(Date.now() / 1e3), n = Ae(e.loginName), r = await ge(e.oldPassword, n), o = R(r.locatorPrivateKey), a = new S(e.vaultRelayUrls, { authPrivateKey: r.locatorPrivateKey }); let i; try { - i = await fe(a, o, n.capsuleKey, e.timeoutMs); + i = await Ke(a, o, r.capsuleKey, e.timeoutMs); } finally { a.closeAll(); } if (!i.quorumMet) - throw new oe("quorum-not-met"); + throw new ye("quorum-not-met"); if (!i.best) - throw new oe(i.candidates.length > 0 ? "no-valid-candidate" : "no-matching-event"); - const s = i.best.payload, c = i.best.event, d = e.store ?? new Ct(), h = await Se(d, s.operational_public_key); - if (s.generation < h.generation && !e.acknowledgeRollback) - throw new ot(h.generation, s.generation); - const m = await ce(e.newPassword, r), p = E(m.locatorPrivateKey), g = new C(e.vaultRelayUrls, { authPrivateKey: m.locatorPrivateKey }); - let v; + throw new ye(i.candidates.length > 0 ? "no-valid-candidate" : "no-matching-event"); + const c = i.best.payload, s = i.best.event, l = e.store ?? new jt(), u = await Ve(l, c.operational_public_key); + if (c.generation < u.generation && !e.acknowledgeRollback) + throw new vt(u.generation, c.generation); + const d = await ge(e.newPassword, n), v = R(d.locatorPrivateKey), g = new S(e.vaultRelayUrls, { authPrivateKey: d.locatorPrivateKey }); + let p; try { - v = await fe(g, p, m.capsuleKey, e.timeoutMs); + p = await Ke(g, v, d.capsuleKey, e.timeoutMs); } finally { g.closeAll(); } - if (!v.quorumMet) - throw new Z("Couldn't verify the new password isn't already registered under this login name. Please retry, or add more vault relays."); - if (v.candidates.length > 0) - throw new rt("Another account is already registered with this login name and the new password you chose. Pick a different new password."); - const f = s.generation + 1, u = { - schema: we, - account_id: s.account_id, + if (!p.quorumMet) + throw new ae("Couldn't verify the new password isn't already registered under this login name. Please retry, or add more vault relays."); + if (p.candidates.length > 0) + throw new pt("Another account is already registered with this login name and the new password you chose. Pick a different new password."); + const f = c.generation + 1, h = { + schema: Re, + account_id: c.account_id, generation: f, - operational_private_key: s.operational_private_key, - operational_public_key: s.operational_public_key, - recovery_public_key: s.recovery_public_key, - recovery_capsule_event: s.recovery_capsule_event, + operational_private_key: c.operational_private_key, + operational_public_key: c.operational_public_key, + recovery_public_key: c.recovery_public_key, + recovery_capsule_event: c.recovery_capsule_event, created_at: t, vault_relay_hints: e.vaultRelayUrls, protocol: { - password_kdf: De, - capsule_encryption: ue, - recovery_derivation: de + password_kdf: Je, + capsule_encryption: me, + recovery_derivation: be } - }, y = await Oe({ - locatorPrivateKey: m.locatorPrivateKey, - capsuleKey: m.capsuleKey, - payload: u - }), k = new C(e.vaultRelayUrls, { authPrivateKey: m.locatorPrivateKey }), b = await le(k, y, { - dTag: ie, + }, y = await Fe({ + locatorPrivateKey: d.locatorPrivateKey, + capsuleKey: d.capsuleKey, + payload: h + }), m = new S(e.vaultRelayUrls, { authPrivateKey: d.locatorPrivateKey }), b = await we(m, y, { + dTag: fe, minAcks: e.minAcknowledgements, timeoutMs: e.timeoutMs }); - k.closeAll(); - const K = Ar({ - oldLocatorPrivateKey: n.locatorPrivateKey, - createdAt: at(c.created_at, t) - }), G = Yr({ - privateKey: n.locatorPrivateKey, - eventIdToDelete: c.id, - deletedEventKind: ae, + m.closeAll(); + const _ = Vn({ + oldLocatorPrivateKey: r.locatorPrivateKey, + createdAt: gt(s.created_at, t) + }), I = hr({ + privateKey: r.locatorPrivateKey, + eventIdToDelete: s.id, + deletedEventKind: he, createdAt: t - }), _ = new C(e.vaultRelayUrls, { authPrivateKey: n.locatorPrivateKey }), [R, x] = await Promise.all([ - _.publishAll(K, e.timeoutMs), - _.publishAll(G, e.timeoutMs) + }), E = new S(e.vaultRelayUrls, { authPrivateKey: r.locatorPrivateKey }), [P, K] = await Promise.all([ + E.publishAll(_, e.timeoutMs), + E.publishAll(I, e.timeoutMs) ]); - if (_.closeAll(), !b.success) - throw new Z("The new credential capsule did not reach the required relay acknowledgement and readback quorum. Please retry."); - return await st(d, s.operational_public_key, { generation: f }), { - normalizedLoginName: r, + if (E.closeAll(), !b.success) + throw new ae("The new credential capsule did not reach the required relay acknowledgement and readback quorum. Please retry."); + return await mt(l, c.operational_public_key, { generation: f }), { + normalizedLoginName: n, oldLocatorPublicKey: o, - newLocatorPublicKey: p, + newLocatorPublicKey: v, newGeneration: f, - recoveryPublicKey: s.recovery_public_key, - recoveryCapsuleEvent: s.recovery_capsule_event, + recoveryPublicKey: c.recovery_public_key, + recoveryCapsuleEvent: c.recovery_capsule_event, newCredentialEvent: y, - tombstoneEvent: K, - deletionRequestEvent: G, + tombstoneEvent: _, + deletionRequestEvent: I, newCredentialPublish: b, - tombstoneAcknowledgedCount: F(R), - deletionAcknowledgedCount: F(x) + tombstoneAcknowledgedCount: te(P), + deletionAcknowledgedCount: te(K) }; } /*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */ -function Xr(e) { +function fr(e) { return e instanceof Uint8Array || ArrayBuffer.isView(e) && e.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in e && e.BYTES_PER_ELEMENT === 1; } -function Ge(e) { +function rt(e) { if (typeof e != "boolean") throw new TypeError(`boolean expected, not ${e}`); } -function Ee(e) { +function Te(e) { if (typeof e != "number") throw new TypeError("number expected, got " + typeof e); if (!Number.isSafeInteger(e) || e < 0) throw new RangeError("positive integer expected, got " + e); } -function te(e, t, r = "") { - const n = Xr(e), o = e?.length, a = t !== void 0; - if (!n || a && o !== t) { - const i = r && `"${r}" `, s = a ? ` of length ${t}` : "", c = n ? `length=${o}` : `type=${typeof e}`, d = i + "expected Uint8Array" + s + ", got " + c; - throw n ? new RangeError(d) : new TypeError(d); +function T(e, t, n = "") { + const r = fr(e), o = e?.length, a = t !== void 0; + if (!r || a && o !== t) { + const i = n && `"${n}" `, c = a ? ` of length ${t}` : "", s = r ? `length=${o}` : `type=${typeof e}`, l = i + "expected Uint8Array" + c + ", got " + s; + throw r ? new RangeError(l) : new TypeError(l); } return e; } -function B(e) { +function A(e) { return new Uint32Array(e.buffer, e.byteOffset, Math.floor(e.byteLength / 4)); } -function Ve(...e) { +function se(...e) { for (let t = 0; t < e.length; t++) e[t].fill(0); } -const re = new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68, Zr = (e) => e << 24 & 4278190080 | e << 8 & 16711680 | e >>> 8 & 65280 | e >>> 24 & 255, en = (e) => { +const H = new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68, vr = (e) => e << 24 & 4278190080 | e << 8 & 16711680 | e >>> 8 & 65280 | e >>> 24 & 255, gr = (e) => { for (let t = 0; t < e.length; t++) - e[t] = Zr(e[t]); + e[t] = vr(e[t]); return e; -}, Q = re ? (e) => e : en; -function tn(e, t) { +}, N = H ? (e) => e : gr; +function wr(e, t) { + return !e.byteLength || !t.byteLength ? !1 : e.buffer === t.buffer && // best we can do, may fail with an obscure Proxy + e.byteOffset < t.byteOffset + t.byteLength && // a starts before b end + t.byteOffset < e.byteOffset + e.byteLength; +} +function $t(e, t) { + if (wr(e, t) && e.byteOffset < t.byteOffset) + throw new Error("complex overlap of input and output is not supported"); +} +function mr(e, t) { if (t == null || typeof t != "object") throw new Error("options must be defined"); return Object.assign(e, t); } -function rn(e, t, r = !0) { +const br = /* @__NO_SIDE_EFFECTS__ */ (e, t) => { + function n(r, ...o) { + if (T(r, void 0, "key"), e.nonceLength !== void 0) { + const u = o[0]; + T(u, e.varSizeNonce ? void 0 : e.nonceLength, "nonce"); + } + const a = e.tagLength; + a && o[1] !== void 0 && T(o[1], void 0, "AAD"); + const i = t(r, ...o), c = (u, d) => { + if (d !== void 0) { + if (u !== 2) + throw new Error("cipher output not supported"); + T(d, void 0, "output"); + } + }; + let s = !1; + return { + encrypt(u, d) { + if (s) + throw new Error("cannot encrypt() twice with same key + nonce"); + return s = !0, T(u), c(i.encrypt.length, d), i.encrypt(u, d); + }, + decrypt(u, d) { + if (T(u), a && u.length < a) + throw new Error('"ciphertext" expected length bigger than tagLength=' + a); + return c(i.decrypt.length, d), i.decrypt(u, d); + } + }; + } + return Object.assign(n, e), n; +}; +function Ye(e, t, n = !0) { if (t === void 0) return new Uint8Array(e); - if (te(t, void 0, "output"), t.length !== e) + if (T(t, void 0, "output"), t.length !== e) throw new Error('"output" expected Uint8Array of length ' + e + ", got: " + t.length); - if (r && !me(t)) + if (n && !Q(t)) throw new Error("invalid output, must be aligned"); return t; } -function me(e) { +function Q(e) { return e.byteOffset % 4 === 0; } -function Be(e) { - return Uint8Array.from(te(e)); +function ie(e) { + return Uint8Array.from(T(e)); } -const Lt = (e) => Uint8Array.from(e.split(""), (t) => t.charCodeAt(0)), nn = Q(B(Lt("expand 16-byte k"))), on = Q(B(Lt("expand 32-byte k"))); -function w(e, t) { +const Bt = (e) => Uint8Array.from(e.split(""), (t) => t.charCodeAt(0)), kr = N(A(Bt("expand 16-byte k"))), Er = N(A(Bt("expand 32-byte k"))); +function k(e, t) { return e << t | e >>> 32 - t; } -const se = 64, an = 16, Re = 2 ** 32 - 1, We = /* @__PURE__ */ Uint32Array.of(); -function sn(e, t, r, n, o, a, i, s) { - const c = o.length, d = new Uint8Array(se), h = B(d), m = re && me(o) && me(a), p = m ? B(o) : We, g = m ? B(a) : We; - if (!re) { - for (let v = 0; v < c; i++) { - if (e(t, r, n, h, i, s), Q(h), i >= Re) +const ve = 64, _r = 16, Me = 2 ** 32 - 1, ot = /* @__PURE__ */ Uint32Array.of(); +function Pr(e, t, n, r, o, a, i, c) { + const s = o.length, l = new Uint8Array(ve), u = A(l), d = H && Q(o) && Q(a), v = d ? A(o) : ot, g = d ? A(a) : ot; + if (!H) { + for (let p = 0; p < s; i++) { + if (e(t, n, r, u, i, c), N(u), i >= Me) throw new Error("arx: counter overflow"); - const f = Math.min(se, c - v); - for (let u = 0, y; u < f; u++) - y = v + u, a[y] = o[y] ^ d[u]; - v += f; + const f = Math.min(ve, s - p); + for (let h = 0, y; h < f; h++) + y = p + h, a[y] = o[y] ^ l[h]; + p += f; } return; } - for (let v = 0; v < c; i++) { - if (e(t, r, n, h, i, s), i >= Re) + for (let p = 0; p < s; i++) { + if (e(t, n, r, u, i, c), i >= Me) throw new Error("arx: counter overflow"); - const f = Math.min(se, c - v); - if (m && f === se) { - const u = v / 4; - if (v % 4 !== 0) + const f = Math.min(ve, s - p); + if (d && f === ve) { + const h = p / 4; + if (p % 4 !== 0) throw new Error("arx: invalid block position"); - for (let y = 0, k; y < an; y++) - k = u + y, g[k] = p[k] ^ h[y]; - v += se; + for (let y = 0, m; y < _r; y++) + m = h + y, g[m] = v[m] ^ u[y]; + p += ve; continue; } - for (let u = 0, y; u < f; u++) - y = v + u, a[y] = o[y] ^ d[u]; - v += f; + for (let h = 0, y; h < f; h++) + y = p + h, a[y] = o[y] ^ l[h]; + p += f; } } -function cn(e, t) { - const { allowShortKeys: r, extendNonceFn: n, counterLength: o, counterRight: a, rounds: i } = tn({ allowShortKeys: !1, counterLength: 8, counterRight: !1, rounds: 20 }, t); +function Kr(e, t) { + const { allowShortKeys: n, extendNonceFn: r, counterLength: o, counterRight: a, rounds: i } = mr({ allowShortKeys: !1, counterLength: 8, counterRight: !1, rounds: 20 }, t); if (typeof e != "function") throw new Error("core must be a function"); - return Ee(o), Ee(i), Ge(a), Ge(r), (s, c, d, h, m = 0) => { - te(s, void 0, "key"), te(c, void 0, "nonce"), te(d, void 0, "data"); - const p = d.length; - if (h = rn(p, h, !1), Ee(m), m < 0 || m >= Re) + return Te(o), Te(i), rt(a), rt(n), (c, s, l, u, d = 0) => { + T(c, void 0, "key"), T(s, void 0, "nonce"), T(l, void 0, "data"); + const v = l.length; + if (u = Ye(v, u, !1), Te(d), d < 0 || d >= Me) throw new Error("arx: counter overflow"); const g = []; - let v = s.length, f, u; - if (v === 32) - g.push(f = Be(s)), u = on; - else if (v === 16 && r) - f = new Uint8Array(32), f.set(s), f.set(s, 16), u = nn, g.push(f); + let p = c.length, f, h; + if (p === 32) + g.push(f = ie(c)), h = Er; + else if (p === 16 && n) + f = new Uint8Array(32), f.set(c), f.set(c, 16), h = kr, g.push(f); else - throw te(s, 32, "arx key"), new Error("invalid key size"); - (!re || !me(c)) && g.push(c = Be(c)); - let y = B(f); - if (n) { - if (c.length !== 24) + throw T(c, 32, "arx key"), new Error("invalid key size"); + (!H || !Q(s)) && g.push(s = ie(s)); + let y = A(f); + if (r) { + if (s.length !== 24) throw new Error("arx: extended nonce must be 24 bytes"); - const K = c.subarray(0, 16); - if (re) - n(u, y, B(K), y); + const _ = s.subarray(0, 16); + if (H) + r(h, y, A(_), y); else { - const G = Q(Uint32Array.from(u)); - n(G, y, B(K), y), Ve(G), Q(y); + const I = N(Uint32Array.from(h)); + r(I, y, A(_), y), se(I), N(y); } - c = c.subarray(16); - } else re || Q(y); - const k = 16 - o; - if (k !== c.length) - throw new Error(`arx: nonce must be ${k} or 16 bytes`); - if (k !== 12) { - const K = new Uint8Array(12); - K.set(c, a ? 0 : 12 - c.length), c = K, g.push(c); + s = s.subarray(16); + } else H || N(y); + const m = 16 - o; + if (m !== s.length) + throw new Error(`arx: nonce must be ${m} or 16 bytes`); + if (m !== 12) { + const _ = new Uint8Array(12); + _.set(s, a ? 0 : 12 - s.length), s = _, g.push(s); } - const b = Q(B(c)); + const b = N(A(s)); try { - return sn(e, u, y, b, d, h, m, i), h; + return Pr(e, h, y, b, l, u, d, i), u; } finally { - Ve(...g); + se(...g); } }; } -function ln(e, t, r, n, o, a = 20) { - let i = e[0], s = e[1], c = e[2], d = e[3], h = t[0], m = t[1], p = t[2], g = t[3], v = t[4], f = t[5], u = t[6], y = t[7], k = o, b = r[0], K = r[1], G = r[2], _ = i, R = s, x = c, L = d, N = h, T = m, I = p, S = g, M = v, U = f, O = u, D = y, q = k, z = b, j = K, $ = G; - for (let qe = 0; qe < a; qe += 2) - _ = _ + N | 0, q = w(q ^ _, 16), M = M + q | 0, N = w(N ^ M, 12), _ = _ + N | 0, q = w(q ^ _, 8), M = M + q | 0, N = w(N ^ M, 7), R = R + T | 0, z = w(z ^ R, 16), U = U + z | 0, T = w(T ^ U, 12), R = R + T | 0, z = w(z ^ R, 8), U = U + z | 0, T = w(T ^ U, 7), x = x + I | 0, j = w(j ^ x, 16), O = O + j | 0, I = w(I ^ O, 12), x = x + I | 0, j = w(j ^ x, 8), O = O + j | 0, I = w(I ^ O, 7), L = L + S | 0, $ = w($ ^ L, 16), D = D + $ | 0, S = w(S ^ D, 12), L = L + S | 0, $ = w($ ^ L, 8), D = D + $ | 0, S = w(S ^ D, 7), _ = _ + T | 0, $ = w($ ^ _, 16), O = O + $ | 0, T = w(T ^ O, 12), _ = _ + T | 0, $ = w($ ^ _, 8), O = O + $ | 0, T = w(T ^ O, 7), R = R + I | 0, q = w(q ^ R, 16), D = D + q | 0, I = w(I ^ D, 12), R = R + I | 0, q = w(q ^ R, 8), D = D + q | 0, I = w(I ^ D, 7), x = x + S | 0, z = w(z ^ x, 16), M = M + z | 0, S = w(S ^ M, 12), x = x + S | 0, z = w(z ^ x, 8), M = M + z | 0, S = w(S ^ M, 7), L = L + N | 0, j = w(j ^ L, 16), U = U + j | 0, N = w(N ^ U, 12), L = L + N | 0, j = w(j ^ L, 8), U = U + j | 0, N = w(N ^ U, 7); - let A = 0; - n[A++] = i + _ | 0, n[A++] = s + R | 0, n[A++] = c + x | 0, n[A++] = d + L | 0, n[A++] = h + N | 0, n[A++] = m + T | 0, n[A++] = p + I | 0, n[A++] = g + S | 0, n[A++] = v + M | 0, n[A++] = f + U | 0, n[A++] = u + O | 0, n[A++] = y + D | 0, n[A++] = k + q | 0, n[A++] = b + z | 0, n[A++] = K + j | 0, n[A++] = G + $ | 0; +function Rr(e, t, n, r, o, a = 20) { + let i = e[0], c = e[1], s = e[2], l = e[3], u = t[0], d = t[1], v = t[2], g = t[3], p = t[4], f = t[5], h = t[6], y = t[7], m = o, b = n[0], _ = n[1], I = n[2], E = i, P = c, K = s, L = l, M = u, U = d, O = v, D = g, q = p, z = f, j = h, $ = y, B = m, G = b, V = _, W = I; + for (let Ze = 0; Ze < a; Ze += 2) + E = E + M | 0, B = k(B ^ E, 16), q = q + B | 0, M = k(M ^ q, 12), E = E + M | 0, B = k(B ^ E, 8), q = q + B | 0, M = k(M ^ q, 7), P = P + U | 0, G = k(G ^ P, 16), z = z + G | 0, U = k(U ^ z, 12), P = P + U | 0, G = k(G ^ P, 8), z = z + G | 0, U = k(U ^ z, 7), K = K + O | 0, V = k(V ^ K, 16), j = j + V | 0, O = k(O ^ j, 12), K = K + O | 0, V = k(V ^ K, 8), j = j + V | 0, O = k(O ^ j, 7), L = L + D | 0, W = k(W ^ L, 16), $ = $ + W | 0, D = k(D ^ $, 12), L = L + D | 0, W = k(W ^ L, 8), $ = $ + W | 0, D = k(D ^ $, 7), E = E + U | 0, W = k(W ^ E, 16), j = j + W | 0, U = k(U ^ j, 12), E = E + U | 0, W = k(W ^ E, 8), j = j + W | 0, U = k(U ^ j, 7), P = P + O | 0, B = k(B ^ P, 16), $ = $ + B | 0, O = k(O ^ $, 12), P = P + O | 0, B = k(B ^ P, 8), $ = $ + B | 0, O = k(O ^ $, 7), K = K + D | 0, G = k(G ^ K, 16), q = q + G | 0, D = k(D ^ q, 12), K = K + D | 0, G = k(G ^ K, 8), q = q + G | 0, D = k(D ^ q, 7), L = L + M | 0, V = k(V ^ L, 16), z = z + V | 0, M = k(M ^ z, 12), L = L + M | 0, V = k(V ^ L, 8), z = z + V | 0, M = k(M ^ z, 7); + let C = 0; + r[C++] = i + E | 0, r[C++] = c + P | 0, r[C++] = s + K | 0, r[C++] = l + L | 0, r[C++] = u + M | 0, r[C++] = d + U | 0, r[C++] = v + O | 0, r[C++] = g + D | 0, r[C++] = p + q | 0, r[C++] = f + z | 0, r[C++] = h + j | 0, r[C++] = y + $ | 0, r[C++] = m + B | 0, r[C++] = b + G | 0, r[C++] = _ + V | 0, r[C++] = I + W | 0; } -const Nt = /* @__PURE__ */ cn(ln, { +const Gt = /* @__PURE__ */ Kr(Rr, { counterRight: !1, counterLength: 4, allowShortKeys: !1 -}), Tt = 2, xe = 1, It = 65536, Ae = 4294967295; -function He(e, t) { - const r = Ie(new Uint8Array([2]), Ze(t)), o = tr.getSharedSecret(e, r, !0).slice(1, 33); - return rr(ye("nip44-v2"), o); +}), Vt = 2, Ue = 1, Wt = 65536, Oe = 4294967295; +function at(e, t) { + const n = Ce(new Uint8Array([2]), Ge(t)), o = ht.getSharedSecret(e, n, !0).slice(1, 33); + return mn(pe("nip44-v2"), o); } -function St(e, t) { - const r = or(e, t, 76); +function Ht(e, t) { + const n = kn(e, t, 76); return { - chachaKey: r.slice(0, 32), - chachaNonce: r.slice(32, 44), - hmacKey: r.slice(44, 76) + chachaKey: n.slice(0, 32), + chachaNonce: n.slice(32, 44), + hmacKey: n.slice(44, 76) }; } -function Mt(e) { +function Ft(e) { if (e <= 32) return 32; - const t = 2 ** Math.floor(Math.log2(e - 1) + 1), r = t <= 256 ? 32 : t / 8; - return r * (Math.floor((e - 1) / r) + 1); + const t = 2 ** Math.floor(Math.log2(e - 1) + 1), n = t <= 256 ? 32 : t / 8; + return n * (Math.floor((e - 1) / n) + 1); } -function un(e) { - if (e < It) { - const r = new Uint8Array(2); - return new DataView(r.buffer).setUint16(0, e, !1), r; +function xr(e) { + if (e < Wt) { + const n = new Uint8Array(2); + return new DataView(n.buffer).setUint16(0, e, !1), n; } const t = new Uint8Array(6); return new DataView(t.buffer).setUint32(2, e, !1), t; } -function dn(e) { +function Ar(e) { const t = e.length; - if (t < xe || t > Ae) - throw new Error(`NIP-44 plaintext length must be between ${xe} and ${Ae} bytes.`); - const r = un(t), n = Mt(t), o = new Uint8Array(r.length + n); - return o.set(r, 0), o.set(e, r.length), o; + if (t < Ue || t > Oe) + throw new Error(`NIP-44 plaintext length must be between ${Ue} and ${Oe} bytes.`); + const n = xr(t), r = Ft(t), o = new Uint8Array(n.length + r); + return o.set(n, 0), o.set(e, n.length), o; } -function yn(e) { - const t = new DataView(e.buffer, e.byteOffset, e.byteLength), r = t.getUint16(0, !1); - let n, o; - if (r === 0) { - if (n = t.getUint32(2, !1), o = 6, n < It) +function Cr(e) { + const t = new DataView(e.buffer, e.byteOffset, e.byteLength), n = t.getUint16(0, !1); + let r, o; + if (n === 0) { + if (r = t.getUint32(2, !1), o = 6, r < Wt) throw new Error("NIP-44 payload has inconsistent padding."); } else - n = r, o = 2; - if (n < xe || n > Ae || e.length !== o + Mt(n)) + r = n, o = 2; + if (r < Ue || r > Oe || e.length !== o + Ft(r)) throw new Error("NIP-44 payload has inconsistent padding."); - return e.slice(o, o + n); + return e.slice(o, o + r); } -function Ut(e, t, r) { - return Qe(Xe, e, Ie(t, r)); +function Jt(e, t, n) { + return lt(ut, e, Ce(t, n)); } -function hn(e, t, r) { - const n = er(32), { chachaKey: o, chachaNonce: a, hmacKey: i } = St(e, n), s = dn(ye(t)), c = Nt(o, a, s), d = Ut(i, n, c); - return tt.encode(Ie(new Uint8Array([Tt]), n, c, d)); +function Lr(e, t, n) { + const r = yt(32), { chachaKey: o, chachaNonce: a, hmacKey: i } = Ht(e, r), c = Ar(pe(t)), s = Gt(o, a, c), l = Jt(i, r, s); + return de.encode(Ce(new Uint8Array([Vt]), r, s, l)); } -function pn(e, t) { - const r = tt.decode(t); - if (r[0] !== Tt) - throw new Error(`Unsupported NIP-44 version: ${r[0]}`); - const n = r.slice(1, 33), o = r.slice(r.length - 32), a = r.slice(33, r.length - 32), { chachaKey: i, chachaNonce: s, hmacKey: c } = St(e, n), d = Ut(c, n, a); - if (!nr(o, d)) +function Tr(e, t) { + const n = de.decode(t); + if (n[0] !== Vt) + throw new Error(`Unsupported NIP-44 version: ${n[0]}`); + const r = n.slice(1, 33), o = n.slice(n.length - 32), a = n.slice(33, n.length - 32), { chachaKey: i, chachaNonce: c, hmacKey: s } = Ht(e, r), l = Jt(s, r, a); + if (!bn(o, l)) throw new Error("NIP-44 MAC verification failed: payload is corrupted, tampered, or uses the wrong key."); - const h = Nt(i, s, a); - return Ye(yn(h)); + const u = Gt(i, c, a); + return je(Cr(u)); } -function vn(...e) { +const ne = 16, Nr = 283; +function Sr(e) { + if (![16, 24, 32].includes(e.length)) + throw new Error('"aes key" expected Uint8Array of length 16/24/32, got length=' + e.length); +} +function Qe(e) { + return e << 1 ^ Nr & -(e >> 7); +} +function le(e, t) { + let n = 0; + for (; t > 0; t >>= 1) + n ^= e & -(t & 1), e = Qe(e); + return n; +} +const De = /* @__PURE__ */ (() => { + const e = new Uint8Array(256); + for (let n = 0, r = 1; n < 256; n++, r ^= Qe(r)) + e[n] = r; + const t = new Uint8Array(256); + t[0] = 99; + for (let n = 0; n < 255; n++) { + let r = e[255 - n]; + r |= r << 8, t[e[n]] = (r ^ r >> 4 ^ r >> 5 ^ r >> 6 ^ r >> 7 ^ 99) & 255; + } + return se(e), t; +})(), Ir = /* @__PURE__ */ De.map((e, t) => De.indexOf(t)), Mr = (e) => e << 24 | e >>> 8, Ne = (e) => e << 8 | e >>> 24; +function Yt(e, t) { + if (e.length !== 256) + throw new Error("Wrong sbox length"); + const n = new Uint32Array(256).map((l, u) => t(e[u])), r = n.map(Ne), o = r.map(Ne), a = o.map(Ne), i = new Uint32Array(256 * 256), c = new Uint32Array(256 * 256), s = new Uint16Array(256 * 256); + for (let l = 0; l < 256; l++) + for (let u = 0; u < 256; u++) { + const d = l * 256 + u; + i[d] = n[l] ^ r[u], c[d] = o[l] ^ a[u], s[d] = e[l] << 8 | e[u]; + } + return { sbox: e, sbox2: s, T0: n, T1: r, T2: o, T3: a, T01: i, T23: c }; +} +const Xe = /* @__PURE__ */ Yt(De, (e) => le(e, 3) << 24 | e << 16 | e << 8 | le(e, 2)), Qt = /* @__PURE__ */ Yt(Ir, (e) => le(e, 11) << 24 | le(e, 13) << 16 | le(e, 9) << 8 | le(e, 14)), Ur = /* @__PURE__ */ (() => { + const e = new Uint8Array(16); + for (let t = 0, n = 1; t < 16; t++, n = Qe(n)) + e[t] = n; + return e; +})(); +function Xt(e) { + T(e); + const t = e.length; + Sr(e); + const { sbox2: n } = Xe, r = []; + (!H || !Q(e)) && r.push(e = ie(e)); + const o = N(A(e)), a = o.length, i = (s) => F(n, s, s, s, s), c = new Uint32Array(t + 28); + c.set(o); + for (let s = a; s < c.length; s++) { + let l = c[s - 1]; + s % a === 0 ? l = i(Mr(l)) ^ Ur[s / a - 1] : a > 6 && s % a === 4 && (l = i(l)), c[s] = c[s - a] ^ l; + } + return se(...r), c; +} +function Or(e) { + const t = Xt(e), n = t.slice(), r = t.length, { sbox2: o } = Xe, { T0: a, T1: i, T2: c, T3: s } = Qt; + for (let l = 0; l < r; l += 4) + for (let u = 0; u < 4; u++) + n[l + u] = t[r - l - 4 + u]; + se(t); + for (let l = 4; l < r - 4; l++) { + const u = n[l], d = F(o, u, u, u, u); + n[l] = a[d & 255] ^ i[d >>> 8 & 255] ^ c[d >>> 16 & 255] ^ s[d >>> 24]; + } + return n; +} +function ee(e, t, n, r, o, a) { + return e[n << 8 & 65280 | r >>> 8 & 255] ^ t[o >>> 8 & 65280 | a >>> 24 & 255]; +} +function F(e, t, n, r, o) { + return e[t & 255 | n & 65280] | e[r >>> 16 & 255 | o >>> 16 & 65280] << 16; +} +function it(e, t, n, r, o) { + const { sbox2: a, T01: i, T23: c } = Xe; + let s = 0; + t ^= e[s++], n ^= e[s++], r ^= e[s++], o ^= e[s++]; + const l = e.length / 4 - 2; + for (let p = 0; p < l; p++) { + const f = e[s++] ^ ee(i, c, t, n, r, o), h = e[s++] ^ ee(i, c, n, r, o, t), y = e[s++] ^ ee(i, c, r, o, t, n), m = e[s++] ^ ee(i, c, o, t, n, r); + t = f, n = h, r = y, o = m; + } + const u = e[s++] ^ F(a, t, n, r, o), d = e[s++] ^ F(a, n, r, o, t), v = e[s++] ^ F(a, r, o, t, n), g = e[s++] ^ F(a, o, t, n, r); + return { s0: u, s1: d, s2: v, s3: g }; +} +function Dr(e, t, n, r, o) { + const { sbox2: a, T01: i, T23: c } = Qt; + let s = 0; + t ^= e[s++], n ^= e[s++], r ^= e[s++], o ^= e[s++]; + const l = e.length / 4 - 2; + for (let p = 0; p < l; p++) { + const f = e[s++] ^ ee(i, c, t, o, r, n), h = e[s++] ^ ee(i, c, n, t, o, r), y = e[s++] ^ ee(i, c, r, n, t, o), m = e[s++] ^ ee(i, c, o, r, n, t); + t = f, n = h, r = y, o = m; + } + const u = e[s++] ^ F(a, t, o, r, n), d = e[s++] ^ F(a, n, t, o, r), v = e[s++] ^ F(a, r, n, t, o), g = e[s++] ^ F(a, o, r, n, t); + return { s0: u, s1: d, s2: v, s3: g }; +} +function qr(e) { + if (T(e), e.length % ne !== 0) + throw new Error("aes-(cbc/ecb).decrypt ciphertext should consist of blocks with size " + ne); +} +function zr(e, t, n) { + T(e); + let r = e.length; + const o = r % ne; + if (!t && o !== 0) + throw new Error("aec/(cbc-ecb): unpadded plaintext with disabled padding"); + if (t) { + let c = ne - o; + c || (c = ne), r = r + c; + } + n = Ye(r, n), $t(e, n), (!H || !Q(e)) && (e = ie(e)); + const a = A(e); + N(a); + const i = A(n); + return { b: a, o: i, out: n }; +} +function jr(e, t) { + if (!t) + return e; + const n = e.length; + if (n === 0) + throw new Error("aes/pkcs7: empty ciphertext not allowed"); + const r = e[n - 1]; + let o = 1; + o &= r - 1 >>> 31 ^ 1, o &= 16 - r >>> 31 ^ 1; + for (let a = 0; a < 16; a++) { + const i = a - r >>> 31, c = (e[n - 1 - a] ^ r) === 0 ? 1 : 0; + o &= c | i ^ 1; + } + if (!o) + throw new Error("aes/pkcs7: wrong padding"); + return e.subarray(0, n - r); +} +function $r(e) { + const t = new Uint8Array(16), n = A(t); + t.set(e); + const r = ne - e.length; + for (let o = ne - r; o < ne; o++) + t[o] = r; + return n; +} +const Zt = /* @__PURE__ */ br({ blockSize: 16, nonceLength: 16 }, function(t, n, r = {}) { + const o = !r.disablePadding; + return { + encrypt(a, i) { + const c = Xt(t), { b: s, o: l, out: u } = zr(a, o, i); + let d = n; + const v = [c]; + (!H || !Q(d)) && v.push(d = ie(d)); + const g = A(d); + N(g); + let p = g[0], f = g[1], h = g[2], y = g[3], m = 0; + for (; m + 4 <= s.length; ) + p ^= s[m + 0], f ^= s[m + 1], h ^= s[m + 2], y ^= s[m + 3], { s0: p, s1: f, s2: h, s3: y } = it(c, p, f, h, y), l[m++] = p, l[m++] = f, l[m++] = h, l[m++] = y; + if (o) { + const b = $r(a.subarray(m * 4)); + N(b), p ^= b[0], f ^= b[1], h ^= b[2], y ^= b[3], { s0: p, s1: f, s2: h, s3: y } = it(c, p, f, h, y), l[m++] = p, l[m++] = f, l[m++] = h, l[m++] = y; + } + return N(l), se(...v), u; + }, + decrypt(a, i) { + qr(a); + const c = Or(t); + let s = n; + const l = [c]; + (!H || !Q(s)) && l.push(s = ie(s)); + const u = A(s); + N(u), i = Ye(a.length, i), $t(a, i), (!H || !Q(a)) && l.push(a = ie(a)); + const d = A(a), v = A(i); + N(d); + let g = u[0], p = u[1], f = u[2], h = u[3]; + for (let y = 0; y + 4 <= d.length; ) { + const m = g, b = p, _ = f, I = h; + g = d[y + 0], p = d[y + 1], f = d[y + 2], h = d[y + 3]; + const { s0: E, s1: P, s2: K, s3: L } = Dr(c, g, p, f, h); + v[y++] = E ^ m, v[y++] = P ^ b, v[y++] = K ^ _, v[y++] = L ^ I; + } + return N(v), se(...l), jr(i, o); + } + }; +}); +function en(e, t) { + const n = Ce(new Uint8Array([2]), Ge(t)); + return ht.getSharedSecret(e, n, !0).slice(1, 33); +} +function Br(e, t, n, r) { + const o = en(e, t), a = yt(16), i = Zt(o, a).encrypt(pe(n)); + return `${de.encode(i)}?iv=${de.encode(a)}`; +} +function Gr(e, t, n) { + const r = n.indexOf("?iv="); + if (r === -1) + throw new Error('NIP-04 payload is missing its "?iv=" suffix.'); + const o = de.decode(n.slice(0, r)), a = de.decode(n.slice(r + 4)), i = en(e, t), c = Zt(i, a).decrypt(o); + return je(c); +} +function Vr(...e) { for (const t of e) t && t.fill(0); } -class Pe { +class Se { privateKey; publicKeyHex; destroyed = !1; constructor(t) { - this.privateKey = t, this.publicKeyHex = E(t); + this.privateKey = t, this.publicKeyHex = R(t); } assertAlive() { if (this.destroyed) @@ -3209,7 +3448,7 @@ class Pe { return this.assertAlive(), this.publicKeyHex; } signEvent(t) { - return this.assertAlive(), W({ + return this.assertAlive(), X({ pubkey: this.publicKeyHex, created_at: t.created_at ?? Math.floor(Date.now() / 1e3), kind: t.kind, @@ -3217,65 +3456,73 @@ class Pe { content: t.content }, this.privateKey); } - nip44Encrypt(t, r) { - return this.assertAlive(), hn(He(this.privateKey, t), r); + nip44Encrypt(t, n) { + return this.assertAlive(), Lr(at(this.privateKey, t), n); } - nip44Decrypt(t, r) { - return this.assertAlive(), pn(He(this.privateKey, t), r); + nip44Decrypt(t, n) { + return this.assertAlive(), Tr(at(this.privateKey, t), n); + } + /** Legacy relative to nip44Encrypt above, but still what a real NIP-07 extension exposes as + * window.nostr.nip04.encrypt -- implemented for drop-in parity. */ + nip04Encrypt(t, n) { + return this.assertAlive(), Br(this.privateKey, t, n); + } + nip04Decrypt(t, n) { + return this.assertAlive(), Gr(this.privateKey, t, n); } /** Best-practical secret wipe (§11.10, §21.4): overwrites the private key buffer in place. */ destroy() { - vn(this.privateKey), this.destroyed = !0; + Vr(this.privateKey), this.destroyed = !0; } } -const gn = [ +const Wr = [ "wss://relay.damus.io", "wss://nos.lol", "wss://relay.nostr.band", "wss://nostr.wine", "wss://relay.snort.social" -], fn = [ +], Hr = [ "wss://purplepag.es", "wss://relay.nostr.band", "wss://nostr-pub.wellorder.net" -], mn = "bitlogin", H = "kv", wn = 1; -function bn() { +], Fr = "bitlogin", Z = "kv", Jr = 1; +function Yr() { return new Promise((e, t) => { - const r = indexedDB.open(mn, wn); - r.onupgradeneeded = () => { - const n = r.result; - n.objectStoreNames.contains(H) || n.createObjectStore(H); - }, r.onsuccess = () => e(r.result), r.onerror = () => t(r.error); + const n = indexedDB.open(Fr, Jr); + n.onupgradeneeded = () => { + const r = n.result; + r.objectStoreNames.contains(Z) || r.createObjectStore(Z); + }, n.onsuccess = () => e(n.result), n.onerror = () => t(n.error); }); } -class kn { +class Qr { dbPromise = null; db() { - return this.dbPromise || (this.dbPromise = bn()), this.dbPromise; + return this.dbPromise || (this.dbPromise = Yr()), this.dbPromise; } async get(t) { - const r = await this.db(); - return new Promise((n, o) => { - const i = r.transaction(H, "readonly").objectStore(H).get(t); - i.onsuccess = () => n(i.result), i.onerror = () => o(i.error); + const n = await this.db(); + return new Promise((r, o) => { + const i = n.transaction(Z, "readonly").objectStore(Z).get(t); + i.onsuccess = () => r(i.result), i.onerror = () => o(i.error); }); } - async set(t, r) { - const n = await this.db(); + async set(t, n) { + const r = await this.db(); return new Promise((o, a) => { - const i = n.transaction(H, "readwrite"); - i.objectStore(H).put(r, t), i.oncomplete = () => o(), i.onerror = () => a(i.error); + const i = r.transaction(Z, "readwrite"); + i.objectStore(Z).put(n, t), i.oncomplete = () => o(), i.onerror = () => a(i.error); }); } async delete(t) { - const r = await this.db(); - return new Promise((n, o) => { - const a = r.transaction(H, "readwrite"); - a.objectStore(H).delete(t), a.oncomplete = () => n(), a.onerror = () => o(a.error); + const n = await this.db(); + return new Promise((r, o) => { + const a = n.transaction(Z, "readwrite"); + a.objectStore(Z).delete(t), a.oncomplete = () => r(), a.onerror = () => o(a.error); }); } } -const l = { +const w = { signer: null, everydayPrivateKey: null, accountId: null, @@ -3284,159 +3531,167 @@ const l = { activeRecoveryEvent: null, pendingRecovery: null }; -let V = [...gn], Ke = [...fn]; -const Fe = new kn(); -function J() { - if (!l.signer || !l.everydayPrivateKey) +let J = [...Wr], Ie = [...Hr]; +const st = new Qr(); +function Y() { + if (!w.signer || !w.everydayPrivateKey) throw new Error("No identity is unlocked in this session."); - return { signer: l.signer, everydayPrivateKey: l.everydayPrivateKey }; + return { signer: w.signer, everydayPrivateKey: w.everydayPrivateKey }; } -function pe() { - l.signer?.destroy(), l.everydayPrivateKey && l.everydayPrivateKey.fill(0), l.signer = null, l.everydayPrivateKey = null, l.accountId = null, l.recoveryPublicKey = null, l.activeCredentialEvent = null, l.activeRecoveryEvent = null, l.pendingRecovery && (l.pendingRecovery.recoveryPrivateKey.fill(0), l.pendingRecovery.everydayPrivateKey.fill(0), l.pendingRecovery = null); +function Ee() { + w.signer?.destroy(), w.everydayPrivateKey && w.everydayPrivateKey.fill(0), w.signer = null, w.everydayPrivateKey = null, w.accountId = null, w.recoveryPublicKey = null, w.activeCredentialEvent = null, w.activeRecoveryEvent = null, w.pendingRecovery && (w.pendingRecovery.recoveryPrivateKey.fill(0), w.pendingRecovery.everydayPrivateKey.fill(0), w.pendingRecovery = null); } -async function _n(e, t) { +async function Xr(e, t) { switch (e) { case "configure": { - const r = t; - return r.vaultRelayUrls?.length && (V = r.vaultRelayUrls), r.discoveryRelayUrls?.length && (Ke = r.discoveryRelayUrls), {}; + const n = t; + return n.vaultRelayUrls?.length && (J = n.vaultRelayUrls), n.discoveryRelayUrls?.length && (Ie = n.discoveryRelayUrls), {}; } case "register": { - const r = t, n = r.importKey ? await Wr({ nsecOrHex: r.importKey, loginName: r.loginName, password: r.password, vaultRelayUrls: V }) : await xt({ loginName: r.loginName, password: r.password, vaultRelayUrls: V }); - return pe(), l.signer = new Pe(n.everydayPrivateKey), l.everydayPrivateKey = n.everydayPrivateKey, l.accountId = n.accountId, l.recoveryPublicKey = n.recoveryPublicKey, l.activeCredentialEvent = n.credentialEvent, l.activeRecoveryEvent = n.recoveryEvent, { - recoveryPhrase: n.recoveryPhrase, - everydayPublicKey: n.everydayPublicKey, - recoveryPublicKey: n.recoveryPublicKey, - accountId: n.accountId, - imported: n.imported, - credentialEventId: n.credentialEvent.id, - recoveryEventId: n.recoveryEvent.id + const n = t, r = n.importKey ? await lr({ nsecOrHex: n.importKey, loginName: n.loginName, password: n.password, vaultRelayUrls: J }) : await qt({ loginName: n.loginName, password: n.password, vaultRelayUrls: J }); + return Ee(), w.signer = new Se(r.everydayPrivateKey), w.everydayPrivateKey = r.everydayPrivateKey, w.accountId = r.accountId, w.recoveryPublicKey = r.recoveryPublicKey, w.activeCredentialEvent = r.credentialEvent, w.activeRecoveryEvent = r.recoveryEvent, { + recoveryPhrase: r.recoveryPhrase, + everydayPublicKey: r.everydayPublicKey, + recoveryPublicKey: r.recoveryPublicKey, + accountId: r.accountId, + imported: r.imported, + credentialEventId: r.credentialEvent.id, + recoveryEventId: r.recoveryEvent.id }; } case "previewImportKey": { - const n = At(t.nsecOrHex), o = E(n), a = { everydayPublicKey: o, npub: je(o) }; - return n.fill(0), a; + const r = zt(t.nsecOrHex), o = R(r), a = { everydayPublicKey: o, npub: tt(o) }; + return r.fill(0), a; } case "login": { - const r = t, n = await Hr({ - loginName: r.loginName, - password: r.password, - vaultRelayUrls: V, - store: Fe, - acknowledgeRollback: r.acknowledgeRollback + const n = t, r = await ur({ + loginName: n.loginName, + password: n.password, + vaultRelayUrls: J, + store: st, + acknowledgeRollback: n.acknowledgeRollback }); - return pe(), l.signer = new Pe(n.everydayPrivateKey), l.everydayPrivateKey = n.everydayPrivateKey, l.accountId = n.accountId, l.recoveryPublicKey = n.recoveryPublicKey, l.activeCredentialEvent = n.credentialEvent, l.activeRecoveryEvent = n.recoveryCapsuleEvent, { - everydayPublicKey: n.everydayPublicKey, - accountId: n.accountId, - generation: n.generation, - rollbackWarning: n.rollbackWarning, - relayDisagreementWarning: n.relayDisagreementWarning + return Ee(), w.signer = new Se(r.everydayPrivateKey), w.everydayPrivateKey = r.everydayPrivateKey, w.accountId = r.accountId, w.recoveryPublicKey = r.recoveryPublicKey, w.activeCredentialEvent = r.credentialEvent, w.activeRecoveryEvent = r.recoveryCapsuleEvent, { + everydayPublicKey: r.everydayPublicKey, + accountId: r.accountId, + generation: r.generation, + rollbackWarning: r.rollbackWarning, + relayDisagreementWarning: r.relayDisagreementWarning }; } case "recover": { - const r = t, n = await Fr({ - phrase: r.phrase, - vaultRelayUrls: V, - discoveryRelayUrls: Ke, - offlineRecoveryCapsuleEvents: r.offlineExportFile?.recovery_capsule_events + const n = t, r = await dr({ + phrase: n.phrase, + vaultRelayUrls: J, + discoveryRelayUrls: Ie, + offlineRecoveryCapsuleEvents: n.offlineExportFile?.recovery_capsule_events }); - return pe(), l.pendingRecovery = n, l.signer = new Pe(n.everydayPrivateKey), l.everydayPrivateKey = n.everydayPrivateKey, l.accountId = n.accountId, l.recoveryPublicKey = n.recoveryPublicKey, l.activeRecoveryEvent = n.currentRecoveryEvent, { - everydayPublicKey: n.everydayPublicKey, - accountId: n.accountId, - generalRelays: n.generalRelays, - dmRelays: n.dmRelays, - chainWarning: n.chainWarning + return Ee(), w.pendingRecovery = r, w.signer = new Se(r.everydayPrivateKey), w.everydayPrivateKey = r.everydayPrivateKey, w.accountId = r.accountId, w.recoveryPublicKey = r.recoveryPublicKey, w.activeRecoveryEvent = r.currentRecoveryEvent, { + everydayPublicKey: r.everydayPublicKey, + accountId: r.accountId, + generalRelays: r.generalRelays, + dmRelays: r.dmRelays, + chainWarning: r.chainWarning }; } case "completeRecovery": { - const r = t; - if (!l.pendingRecovery) throw new Error("No recovery is in progress in this session."); - const n = await Jr({ - recovered: l.pendingRecovery, - newLoginName: r.newLoginName, - newPassword: r.newPassword, - vaultRelayUrls: V + const n = t; + if (!w.pendingRecovery) throw new Error("No recovery is in progress in this session."); + const r = await yr({ + recovered: w.pendingRecovery, + newLoginName: n.newLoginName, + newPassword: n.newPassword, + vaultRelayUrls: J }); - return l.activeCredentialEvent = n.credentialEvent, l.activeRecoveryEvent = n.refreshedRecoveryEvent, l.pendingRecovery.recoveryPrivateKey.fill(0), l.pendingRecovery = null, { - locatorPublicKey: n.locatorPublicKey, - credentialEventId: n.credentialEvent.id, - refreshedRecoveryEventId: n.refreshedRecoveryEvent.id + return w.activeCredentialEvent = r.credentialEvent, w.activeRecoveryEvent = r.refreshedRecoveryEvent, w.pendingRecovery.recoveryPrivateKey.fill(0), w.pendingRecovery = null, { + locatorPublicKey: r.locatorPublicKey, + credentialEventId: r.credentialEvent.id, + refreshedRecoveryEventId: r.refreshedRecoveryEvent.id }; } case "changePassword": { - const r = t, n = await Qr({ - loginName: r.loginName, - oldPassword: r.oldPassword, - newPassword: r.newPassword, - vaultRelayUrls: V, - store: Fe, - acknowledgeRollback: r.acknowledgeRollback + const n = t, r = await pr({ + loginName: n.loginName, + oldPassword: n.oldPassword, + newPassword: n.newPassword, + vaultRelayUrls: J, + store: st, + acknowledgeRollback: n.acknowledgeRollback }); - return l.activeCredentialEvent = n.newCredentialEvent, l.recoveryPublicKey = n.recoveryPublicKey, l.activeRecoveryEvent = n.recoveryCapsuleEvent, { - newLocatorPublicKey: n.newLocatorPublicKey, - newGeneration: n.newGeneration, - tombstoneAcknowledgedCount: n.tombstoneAcknowledgedCount, - deletionAcknowledgedCount: n.deletionAcknowledgedCount + return w.activeCredentialEvent = r.newCredentialEvent, w.recoveryPublicKey = r.recoveryPublicKey, w.activeRecoveryEvent = r.recoveryCapsuleEvent, { + newLocatorPublicKey: r.newLocatorPublicKey, + newGeneration: r.newGeneration, + tombstoneAcknowledgedCount: r.tombstoneAcknowledgedCount, + deletionAcknowledgedCount: r.deletionAcknowledgedCount }; } case "publishProfileAndRelayLists": { - const r = t, { everydayPrivateKey: n } = J(); - return vr({ - everydayPrivateKey: n, - name: r.name, - about: r.about, - picture: r.picture, - generalRelays: r.generalRelays, - dmRelays: r.dmRelays, - discoveryRelays: Ke + const n = t, { everydayPrivateKey: r } = Y(); + return Nn({ + everydayPrivateKey: r, + name: n.name, + about: n.about, + picture: n.picture, + generalRelays: n.generalRelays, + dmRelays: n.dmRelays, + discoveryRelays: Ie }); } case "getPublicKey": { - const { signer: r } = J(); - return { publicKey: r.getPublicKey() }; + const { signer: n } = Y(); + return { publicKey: n.getPublicKey() }; } case "signEvent": { - const { signer: r } = J(), n = t; - return r.signEvent({ kind: n.kind, tags: n.tags, content: n.content, created_at: n.created_at }); + const { signer: n } = Y(), r = t; + return n.signEvent({ kind: r.kind, tags: r.tags, content: r.content, created_at: r.created_at }); } case "nip44Encrypt": { - const { signer: r } = J(), n = t; - return { ciphertext: r.nip44Encrypt(n.peerPublicKey, n.plaintext) }; + const { signer: n } = Y(), r = t; + return { ciphertext: n.nip44Encrypt(r.peerPublicKey, r.plaintext) }; } case "nip44Decrypt": { - const { signer: r } = J(), n = t; - return { plaintext: r.nip44Decrypt(n.peerPublicKey, n.payload) }; + const { signer: n } = Y(), r = t; + return { plaintext: n.nip44Decrypt(r.peerPublicKey, r.payload) }; + } + case "nip04Encrypt": { + const { signer: n } = Y(), r = t; + return { ciphertext: n.nip04Encrypt(r.peerPublicKey, r.plaintext) }; + } + case "nip04Decrypt": { + const { signer: n } = Y(), r = t; + return { plaintext: n.nip04Decrypt(r.peerPublicKey, r.payload) }; } case "exportIdentity": { - const { everydayPrivateKey: r, signer: n } = J(); - return { nsec: ir(r), npub: je(n.getPublicKey()) }; + const { everydayPrivateKey: n, signer: r } = Y(); + return { nsec: _n(n), npub: tt(r.getPublicKey()) }; } case "buildRecoveryExport": { - if (J(), !l.recoveryPublicKey || !l.activeRecoveryEvent) + if (Y(), !w.recoveryPublicKey || !w.activeRecoveryEvent) throw new Error("No recovery capsule is known in this session yet."); - return ar({ - recoveryPublicKeyHex: l.recoveryPublicKey, - vaultRelayUrls: V, - recoveryCapsuleEvents: [l.activeRecoveryEvent], + return En({ + recoveryPublicKeyHex: w.recoveryPublicKey, + vaultRelayUrls: J, + recoveryCapsuleEvents: [w.activeRecoveryEvent], relayListEvents: [] }); } case "repairReplicas": { - if (!l.activeCredentialEvent || !l.activeRecoveryEvent) + if (!w.activeCredentialEvent || !w.activeRecoveryEvent) throw new Error("No active capsule events are known in this session yet."); - const r = new C(V), n = await Ir(r, l.activeCredentialEvent, l.activeRecoveryEvent); - return r.closeAll(), n; + const n = new S(J), r = await Yn(n, w.activeCredentialEvent, w.activeRecoveryEvent); + return n.closeAll(), r; } case "getSessionStatus": - return { unlocked: !!l.signer, everydayPublicKey: l.signer?.getPublicKey() }; + return { unlocked: !!w.signer, everydayPublicKey: w.signer?.getPublicKey() }; case "logout": - return pe(), {}; + return Ee(), {}; default: throw new Error(`Unknown worker action: ${e}`); } } self.addEventListener("message", (e) => { - const { id: t, action: r, payload: n } = e.data; - _n(r, n).then( + const { id: t, action: n, payload: r } = e.data; + Xr(n, r).then( (o) => { const a = { id: t, ok: !0, result: o }; self.postMessage(a); From 4d1116530b216a79e20d7b86ef51e46d7b07a07e Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Wed, 22 Jul 2026 00:19:43 -0400 Subject: [PATCH 4/8] Surface BitLogin sign-in rejections instead of silently swallowing them devLogger.warn is a no-op outside dev mode (IS_DEV_MODE = false on this deployment), so when AuthService.requestLogin() rejected a BitLogin sign-in -- e.g. the invite-only access-control check ("Access restricted to admins and moderators users only.") -- nothing ever told the user why. The widget itself doesn't know bitvid rejected it, so it kept showing its own "Signed in" screen, and the login modal never closed, looking exactly like a hung/broken close button. On rejection: route the error through app.handleLoginModalError (the same visible-banner path every other sign-in method already uses) and reset the widget via widget.logout() so it doesn't keep claiming a session bitvid never actually granted. Also added a WeakSet guard so wireBitloginLogin() can never attach two "bitlogin-login" listeners to the same element, regardless of how attachWidget() ends up getting invoked -- cheap insurance, no observed bug currently triggers it. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01DY9UMTuF43Ekc2U4E1eDwX --- js/ui/bitloginModalIntegration.js | 45 +++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/js/ui/bitloginModalIntegration.js b/js/ui/bitloginModalIntegration.js index c71ba074..99e3391e 100644 --- a/js/ui/bitloginModalIntegration.js +++ b/js/ui/bitloginModalIntegration.js @@ -9,11 +9,12 @@ // gets its own widget in the modal instead of a grid button (see // providersForModal in js/services/authProviders/index.js) and its own small // integration module here rather than special-casing LoginModalController. -import { devLogger } from "../utils/logger.js"; +import { devLogger, userLogger } from "../utils/logger.js"; import { closeStaticModal } from "./components/staticModalAccessibility.js"; import { setModalState as setGlobalModalState } from "../state/appState.js"; import { createBitloginAdapter } from "../nostr/adapters/bitloginAdapter.js"; import { setPendingBitloginResult } from "../services/authProviders/bitlogin.js"; +import bitloginProvider from "../services/authProviders/bitlogin.js"; import { FEATURE_BITLOGIN } from "../constants.js"; // Vendored, pinned BitLogin widget bundle (scripts/build-bitlogin-widget.mjs). @@ -44,6 +45,13 @@ function closeLoginModal() { } } +// Cheap insurance against wireBitloginLogin() ever running twice for the same +// element (e.g. a future bootstrap change that calls it more than once): the +// same instance should only ever get one "bitlogin-login" +// listener, since two would each race the shared, single-read `pendingResult` +// and each independently call requestLogin() for one real sign-in. +const wiredWidgets = new WeakSet(); + function attachWidget(app, widget) { const mount = document.getElementById("bitloginMount"); if (!FEATURE_BITLOGIN) { @@ -59,6 +67,11 @@ function attachWidget(app, widget) { mount.hidden = false; } + if (wiredWidgets.has(widget)) { + return; + } + wiredWidgets.add(widget); + loadBitloginWidgetOnce().catch((error) => { devLogger.warn("[BitLogin] Failed to load the widget bundle:", error); }); @@ -81,7 +94,35 @@ function attachWidget(app, widget) { await app.authService.requestLogin({ providerId: "bitlogin" }); closeLoginModal(); } catch (error) { - devLogger.warn("[BitLogin] Sign-in failed to apply:", error); + // bitvid rejecting the sign-in (e.g. the invite-only access-control + // check) leaves the widget itself still showing its own "Signed in" + // screen -- BitLogin succeeded on its own terms, bitvid just didn't + // accept the identity. Reset the widget and surface the real reason + // through the same error-banner path every other sign-in method uses + // (devLogger.warn is a silent no-op outside dev mode -- see + // IS_DEV_MODE in config/instance-config.js -- so relying on it here + // meant a rejected user saw no feedback at all). + userLogger.warn("[BitLogin] Sign-in failed to apply:", error); + const maybePromise = app?.handleLoginModalError?.({ + error, + provider: bitloginProvider, + }); + if (maybePromise && typeof maybePromise.then === "function") { + maybePromise.catch((handlerError) => { + devLogger.warn( + "[BitLogin] handleLoginModalError threw:", + handlerError, + ); + }); + } + try { + await widget.logout(); + } catch (logoutError) { + devLogger.warn( + "[BitLogin] Failed to reset widget after a rejected sign-in:", + logoutError, + ); + } } }); } From f25c2ac2422ce67be197c1afb32276073b923b66 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Wed, 22 Jul 2026 07:50:06 -0400 Subject: [PATCH 5/8] Re-vendor bitlogin widget: local session persistence Pulls in bitlogin's session-persistence feature -- after a successful sign-in, the everyday private key is cached in IndexedDB for this origin, so a page reload restores the session instead of asking for the login name + password again. No changes needed in bitloginModalIntegration.js: the existing "bitlogin-login" listener fires the same way whether the sign-in was just typed or silently restored. See bitlogin's CHANGELOG.md (2026-07-22 entry). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01DY9UMTuF43Ekc2U4E1eDwX --- vendor/bitlogin.integrity.json | 14 +- ...IBe5Omw.js => bitlogin-shared-n51Pos3V.js} | 5 +- vendor/bitlogin/bitlogin.js | 61 +- vendor/bitlogin/cryptoWorker.js | 1115 +++++++++-------- 4 files changed, 640 insertions(+), 555 deletions(-) rename vendor/bitlogin/{bitlogin-shared-QIBe5Omw.js => bitlogin-shared-n51Pos3V.js} (99%) diff --git a/vendor/bitlogin.integrity.json b/vendor/bitlogin.integrity.json index d0702bc6..98e083d0 100644 --- a/vendor/bitlogin.integrity.json +++ b/vendor/bitlogin.integrity.json @@ -2,17 +2,17 @@ "package": "@bitlogin/widget", "version": "0.1.0", "files": { - "bitlogin-shared-QIBe5Omw.js": { - "sha256": "7ce4ed0a5c3c7f744e864f55745f7fa78482cbfa19cd30eb551f67a415f55191", - "bytes": 288275 + "bitlogin-shared-n51Pos3V.js": { + "sha256": "bf7ee691d519f3f9fed7fe1d0955389ea33d0b6de5282432d4815c82cdffe25e", + "bytes": 288287 }, "bitlogin.js": { - "sha256": "539253ea709e9554f3dcff872beff40c2ca847b3564f53885b8fd89a7a2747d1", - "bytes": 66045 + "sha256": "927646a94a1bb0fff4a0080778f2fc58be60107f1e4e1250369074f980f19ac4", + "bytes": 67232 }, "cryptoWorker.js": { - "sha256": "06a7aaf0be2ff4e80b46f609b868aa4ccfbd402cf0604031500316830ff2f528", - "bytes": 73937 + "sha256": "ba5ded0d533f394e02b5a1a66949f6c5c17e3af1ab83db34dc7b4c3c48506a0c", + "bytes": 76304 } } } diff --git a/vendor/bitlogin/bitlogin-shared-QIBe5Omw.js b/vendor/bitlogin/bitlogin-shared-n51Pos3V.js similarity index 99% rename from vendor/bitlogin/bitlogin-shared-QIBe5Omw.js rename to vendor/bitlogin/bitlogin-shared-n51Pos3V.js index 8f88c18b..fcc5bc40 100644 --- a/vendor/bitlogin/bitlogin-shared-QIBe5Omw.js +++ b/vendor/bitlogin/bitlogin-shared-n51Pos3V.js @@ -10807,8 +10807,9 @@ export { $1 as a, U1 as a0, Rt as a1, - t2 as a2, - W1 as a3, + jt as a2, + t2 as a3, + W1 as a4, z1 as b, wn as c, Y1 as d, diff --git a/vendor/bitlogin/bitlogin.js b/vendor/bitlogin/bitlogin.js index bf87e4a7..8d25b38e 100644 --- a/vendor/bitlogin/bitlogin.js +++ b/vendor/bitlogin/bitlogin.js @@ -1,6 +1,6 @@ -import { r as y, e as w, E as v, p as Q, R as k, i as h, a as u } from "./bitlogin-shared-QIBe5Omw.js"; +import { r as y, e as w, E as v, p as Q, R as k, i as h, a as d } from "./bitlogin-shared-n51Pos3V.js"; const x = 6, p = 64; -function d(a = x) { +function u(a = x) { const e = w(), t = []; for (let r = 0; r < a; r++) t.push(e[y(e.length)]); @@ -45,10 +45,10 @@ function L(a) { let e = 0; return /[a-z]/u.test(a) && (e += 26), /[A-Z]/u.test(a) && (e += 26), /[0-9]/u.test(a) && (e += 10), /[^a-zA-Z0-9]/u.test(a) && (e += 33), e || 1; } -function C(a) { +function S(a) { return a.length * Math.log2(L(a)); } -function S(a) { +function C(a) { if (/^(.)\1*$/u.test(a) || /(.)\1{2,}/u.test(a)) return !0; const e = a.toLowerCase(), t = ["0123456789", "abcdefghijklmnopqrstuvwxyz"]; @@ -60,9 +60,9 @@ function S(a) { } return !1; } -function f(a, e) { - const t = C(a); - return a.length < m ? { ok: !1, entropyBits: t, reason: `Must be at least ${m} characters.` } : P.has(a.toLowerCase()) ? { ok: !1, entropyBits: t, reason: "This is on a list of extremely common passwords." } : e.length >= 3 && a.toLowerCase().includes(e) ? { ok: !1, entropyBits: t, reason: "Must not contain your login name." } : S(a) ? { ok: !1, entropyBits: t, reason: "Too predictable (repeated characters or a simple sequence)." } : t < b ? { +function g(a, e) { + const t = S(a); + return a.length < m ? { ok: !1, entropyBits: t, reason: `Must be at least ${m} characters.` } : P.has(a.toLowerCase()) ? { ok: !1, entropyBits: t, reason: "This is on a list of extremely common passwords." } : e.length >= 3 && a.toLowerCase().includes(e) ? { ok: !1, entropyBits: t, reason: "Must not contain your login name." } : C(a) ? { ok: !1, entropyBits: t, reason: "Too predictable (repeated characters or a simple sequence)." } : t < b ? { ok: !1, entropyBits: t, reason: `Estimated entropy (~${t.toFixed(0)} bits) is below the required ${b}. Use a longer password or mix character types.` @@ -147,6 +147,9 @@ class E { getSessionStatus() { return this.call("getSessionStatus", {}); } + restoreSession() { + return this.call("restoreSession", {}); + } logout() { return this.call("logout", {}); } @@ -504,7 +507,7 @@ class F extends HTMLElement { } connectedCallback() { const e = R(this); - if (this.vaultRelayUrls = e.vaultRelayUrls ?? [], this.discoveryRelayUrls = e.discoveryRelayUrls ?? [], this.worker.configure({ vaultRelayUrls: this.vaultRelayUrls, discoveryRelayUrls: this.discoveryRelayUrls }), this.root.addEventListener("click", (t) => this.onClick(t)), this.root.addEventListener("submit", (t) => this.onSubmit(t)), this.root.addEventListener("input", (t) => this.onInput(t)), this.root.addEventListener("change", (t) => void this.onFileChange(t)), this.render(), this.installedProvider = $(this.worker, () => this.vaultRelayUrls), !window.nostr) + if (this.vaultRelayUrls = e.vaultRelayUrls ?? [], this.discoveryRelayUrls = e.discoveryRelayUrls ?? [], this.worker.configure({ vaultRelayUrls: this.vaultRelayUrls, discoveryRelayUrls: this.discoveryRelayUrls }).then(() => this.tryRestoreSession()), this.root.addEventListener("click", (t) => this.onClick(t)), this.root.addEventListener("submit", (t) => this.onSubmit(t)), this.root.addEventListener("input", (t) => this.onInput(t)), this.root.addEventListener("change", (t) => void this.onFileChange(t)), this.render(), this.installedProvider = $(this.worker, () => this.vaultRelayUrls), !window.nostr) try { window.nostr = this.installedProvider; } catch { @@ -645,7 +648,7 @@ class F extends HTMLElement { readManualPassword(e) { const t = this.field("manualPassword"), s = this.field("manualPasswordConfirm"); this.manualPasswordDraft = t, this.manualPasswordConfirmDraft = s; - const r = f(t, e); + const r = g(t, e); return r.ok ? t !== s ? { password: "", error: "Passwords do not match. Please re-enter both." } : { password: t } : { password: "", error: `Password not accepted: ${r.reason}` }; } /** @@ -702,20 +705,20 @@ class F extends HTMLElement { this.goto("confirm-phrase"); return; case "goto-change-password": - this.changePasswordNewCredential = d().secret, this.manualPasswordMode = !1, this.manualPasswordFeedback = null, this.manualPasswordDraft = "", this.manualPasswordConfirmDraft = "", this.goto("change-password"); + this.changePasswordNewCredential = u().secret, this.manualPasswordMode = !1, this.manualPasswordFeedback = null, this.manualPasswordDraft = "", this.manualPasswordConfirmDraft = "", this.goto("change-password"); return; case "goto-export": this.goto("export"); return; case "regenerate-credential": - this.generatedCredential = d().secret, this.savedCheckbox = !1, this.render(); + this.generatedCredential = u().secret, this.savedCheckbox = !1, this.render(); return; case "toggle-manual-password": this.manualPasswordMode = !this.manualPasswordMode, this.manualPasswordFeedback = null, this.manualPasswordDraft = "", this.manualPasswordConfirmDraft = "", this.savedCheckbox = !1, this.render(); return; case "copy-credential": { - const r = this.root.querySelector("#credential-box"), n = t, o = n.textContent ?? "Copy", l = (g) => { - n.textContent = g, setTimeout(() => { + const r = this.root.querySelector("#credential-box"), n = t, o = n.textContent ?? "Copy", l = (f) => { + n.textContent = f, setTimeout(() => { n.isConnected && (n.textContent = o); }, 2e3); }; @@ -785,7 +788,7 @@ class F extends HTMLElement { const t = e.target; if (!(t instanceof HTMLInputElement) || t.name !== "manualPassword" && t.name !== "manualPasswordConfirm") return; const s = this.root.querySelector('input[name="manualPassword"]')?.value ?? "", r = this.root.querySelector('input[name="manualPasswordConfirm"]')?.value ?? ""; - this.manualPasswordFeedback = s ? f(s, this.loginName) : null; + this.manualPasswordFeedback = s ? g(s, this.loginName) : null; const n = this.root.querySelector("#manual-password-feedback"); if (n) if (!this.manualPasswordFeedback) @@ -845,7 +848,7 @@ class F extends HTMLElement { this.errorMessage = "Login name must be 3-32 characters: a-z, 0-9, '.', '_', '-', and not start/end with punctuation.", this.render(); return; } - this.loginName = e, this.generatedCredential = d().secret, this.savedCheckbox = !1, this.manualPasswordMode = !1, this.manualPasswordFeedback = null, this.manualPasswordDraft = "", this.manualPasswordConfirmDraft = "", this.goto("create-credential"); + this.loginName = e, this.generatedCredential = u().secret, this.savedCheckbox = !1, this.manualPasswordMode = !1, this.manualPasswordFeedback = null, this.manualPasswordDraft = "", this.manualPasswordConfirmDraft = "", this.goto("create-credential"); } async handleCreateCredentialSubmit() { this.savedCheckbox = this.root.querySelector("#saved-check")?.checked ?? !1; @@ -870,7 +873,7 @@ class F extends HTMLElement { }); this.importKey = "", this.importPreviewNpub = "", this.recoveryPhrase = t.recoveryPhrase; const s = this.recoveryPhrase.split(" "), r = D(s.length, 3); - this.confirmSlots = r.map((n) => ({ index: n, value: "" })), this.session = { publicKey: t.everydayPublicKey, npub: u(t.everydayPublicKey), accountId: t.accountId }, this.busy = !1, this.goto("confirm-phrase"), this.worker.publishProfileAndRelayLists({ + this.confirmSlots = r.map((n) => ({ index: n, value: "" })), this.session = { publicKey: t.everydayPublicKey, npub: d(t.everydayPublicKey), accountId: t.accountId }, this.busy = !1, this.goto("confirm-phrase"), this.worker.publishProfileAndRelayLists({ name: this.loginName, generalRelays: this.vaultRelayUrls, dmRelays: this.vaultRelayUrls @@ -889,6 +892,28 @@ class F extends HTMLElement { const e = this.field("loginName").trim().toLowerCase(), t = this.field("password"); await this.attemptLogin(e, t); } + /** + * Called once per connectedCallback, right after "configure" -- restores whatever + * a prior login/register/rotate cached locally (§21), so a page reload lands + * straight on the dashboard instead of asking for the login name + password + * again. Silent by design: no flashSuccess() stamp (that's reserved for a + * deliberate action the user just took) and no offerToSaveCredential (there's + * no password in hand to save). If the welcome screen already rendered by the + * time this resolves, goto() just re-renders over it -- a brief flash, not a + * correctness issue. + */ + async tryRestoreSession() { + try { + const e = await this.worker.restoreSession(); + if (!e.restored || !e.everydayPublicKey) return; + this.session = { + publicKey: e.everydayPublicKey, + npub: d(e.everydayPublicKey), + accountId: e.accountId + }, this.noteSignerClaim(this.claimSigner()), this.dispatchEvent(new CustomEvent("bitlogin-login", { detail: { publicKey: e.everydayPublicKey } })), this.goto("dashboard"); + } catch { + } + } /** * Shared by the login form and the "continue anyway" rollback-confirmation step so both * paths grant a session identically -- claimSigner() and the bitlogin-login event only ever @@ -898,7 +923,7 @@ class F extends HTMLElement { this.setBusy(!0); try { const r = await this.worker.login({ loginName: e, password: t, acknowledgeRollback: s }); - this.loginName = e, this.session = { publicKey: r.everydayPublicKey, npub: u(r.everydayPublicKey), accountId: r.accountId }, this.sessionWarnings = [r.rollbackWarning, r.relayDisagreementWarning].filter((n) => !!n), this.busy = !1, this.noteSignerClaim(this.claimSigner()), this.dispatchEvent(new CustomEvent("bitlogin-login", { detail: { publicKey: r.everydayPublicKey } })), this.flashSuccess("dashboard", "Signed in"), this.offerToSaveCredential(e, t); + this.loginName = e, this.session = { publicKey: r.everydayPublicKey, npub: d(r.everydayPublicKey), accountId: r.accountId }, this.sessionWarnings = [r.rollbackWarning, r.relayDisagreementWarning].filter((n) => !!n), this.busy = !1, this.noteSignerClaim(this.claimSigner()), this.dispatchEvent(new CustomEvent("bitlogin-login", { detail: { publicKey: r.everydayPublicKey } })), this.flashSuccess("dashboard", "Signed in"), this.offerToSaveCredential(e, t); } catch (r) { if (r instanceof Error && r.name === "RollbackDetectedError") { this.pendingRollback = { kind: "login", loginName: e, password: t }, this.rollbackMessage = r.message, this.busy = !1, this.goto("rollback-confirm"); @@ -923,7 +948,7 @@ class F extends HTMLElement { generalRelaysCount: t.generalRelays.length, dmRelaysCount: t.dmRelays.length, chainWarning: t.chainWarning - }, this.session = { publicKey: t.everydayPublicKey, npub: u(t.everydayPublicKey), accountId: t.accountId }, this.newCredentialAfterRecovery = d().secret, this.manualPasswordMode = !1, this.manualPasswordFeedback = null, this.manualPasswordDraft = "", this.manualPasswordConfirmDraft = "", this.busy = !1, this.goto("recover-new-credentials"); + }, this.session = { publicKey: t.everydayPublicKey, npub: d(t.everydayPublicKey), accountId: t.accountId }, this.newCredentialAfterRecovery = u().secret, this.manualPasswordMode = !1, this.manualPasswordFeedback = null, this.manualPasswordDraft = "", this.manualPasswordConfirmDraft = "", this.busy = !1, this.goto("recover-new-credentials"); } async handleRecoverNewCredentialsSubmit() { const e = this.field("newLoginName").trim().toLowerCase(); diff --git a/vendor/bitlogin/cryptoWorker.js b/vendor/bitlogin/cryptoWorker.js index d41fb05b..96f5fe3e 100644 --- a/vendor/bitlogin/cryptoWorker.js +++ b/vendor/bitlogin/cryptoWorker.js @@ -1,4 +1,4 @@ -import { v as ce, g as R, s as X, c as tn, K as nn, b as ct, d as qe, f as ze, h as he, u as pe, j as rn, k as on, l as oe, m as ue, n as je, S as $e, o as Be, q as Re, D as fe, t as xe, w as an, x as sn, y as cn, z as ln, A as Le, B as et, C as lt, F as un, G as dn, H as yn, I as ke, J as ut, L as hn, M as pn, N as Ge, O as Ae, P as ge, Q as fn, T as dt, U as vn, V as gn, W as wn, X as yt, Y as de, Z as Ce, _ as ht, $ as mn, a0 as bn, a1 as kn, a2 as En, a as tt, a3 as _n } from "./bitlogin-shared-QIBe5Omw.js"; +import { v as ce, g as R, s as X, K as rn, c as on, b as ut, d as $e, f as Be, h as he, u as pe, j as an, k as sn, l as oe, m as ue, n as Ge, S as Ve, o as He, q as Ce, D as ve, t as Le, w as cn, x as ln, y as un, z as dn, A as Ie, B as rt, C as dt, F as yn, G as hn, H as pn, I as Pe, J as yt, L as vn, M as fn, N as Se, O as Ne, P as be, Q as gn, T as ht, U as wn, V as bn, W as mn, X as pt, Y as de, Z as Te, _ as vt, $ as kn, a0 as En, a1 as Pn, a2 as _n, a3 as Kn, a as ot, a4 as Rn } from "./bitlogin-shared-n51Pos3V.js"; class ye extends Error { reason; constructor(t) { @@ -10,49 +10,49 @@ class ae extends Error { super(t), this.name = "RegistrationFailedError"; } } -class pt extends Error { +class ft extends Error { constructor(t = "An account already exists with this login name and password. Sign in instead, or choose different credentials.") { super(t), this.name = "AccountAlreadyExistsError"; } } -class ft extends Error { +class gt extends Error { constructor(t) { super(t), this.name = "RecoveryFailedError"; } } -class vt extends Error { +class wt extends Error { seenGeneration; capsuleGeneration; constructor(t, n) { super(`This credential capsule reports generation ${n}, but this device has already seen generation ${t}. Refusing to log in with older, possibly-revoked credentials.`), this.name = "RollbackDetectedError", this.seenGeneration = t, this.capsuleGeneration = n; } } -function gt(e, t) { +function bt(e, t) { const n = t ?? Math.floor(Date.now() / 1e3); return e == null ? n : Math.max(n, e + 1); } -const Pn = { generation: -1, recoveryGeneration: -1 }; -function wt(e) { +const xn = { generation: -1, recoveryGeneration: -1 }; +function mt(e) { return `bitlogin:hwm:${e}`; } -async function Ve(e, t) { - const n = await e.get(wt(t)); - return n ? JSON.parse(n) : Pn; +async function We(e, t) { + const n = await e.get(mt(t)); + return n ? JSON.parse(n) : xn; } -async function mt(e, t, n) { - const r = await Ve(e, t), o = { +async function kt(e, t, n) { + const r = await We(e, t), o = { generation: Math.max(r.generation, n.generation ?? -1), recoveryGeneration: Math.max(r.recoveryGeneration, n.recoveryGeneration ?? -1) }; - return await e.set(wt(t), JSON.stringify(o)), o; + return await e.set(mt(t), JSON.stringify(o)), o; } -function Kn() { +function An() { const e = globalThis.WebSocket; if (!e) throw new Error("No global WebSocket implementation is available in this environment."); return e; } -class Rn { +class Cn { url; ws = null; connectPromise = null; @@ -66,7 +66,7 @@ class Rn { } async connect() { return this.connectPromise ? this.connectPromise : (this.connectPromise = new Promise((t, n) => { - const r = Kn(), o = new r(this.url); + const r = An(), o = new r(this.url); this.ws = o; const a = setTimeout(() => { n(new Error(`Timed out connecting to relay ${this.url}`)); @@ -127,7 +127,7 @@ class Rn { const n = R(this.authPrivateKey), r = X({ pubkey: n, created_at: Math.floor(Date.now() / 1e3), - kind: nn, + kind: rn, tags: [ ["relay", this.url], ["challenge", t] @@ -151,7 +151,7 @@ class Rn { } async queryOnce(t, n = 8e3) { await this.connect(); - const r = tn({ + const r = on({ pubkey: "0".repeat(64), created_at: Date.now(), kind: 0, @@ -166,11 +166,11 @@ class Rn { }); } } -class S { +class T { connections = /* @__PURE__ */ new Map(); constructor(t, n = {}) { for (const r of new Set(t)) - this.connections.set(r, new Rn(r, n)); + this.connections.set(r, new Cn(r, n)); } get relayUrls() { return [...this.connections.keys()]; @@ -212,63 +212,63 @@ class S { function te(e) { return e.filter((t) => t.result.ok).length; } -function xn(e, t, n) { - return X({ pubkey: R(e), created_at: n, kind: ct, tags: [], content: JSON.stringify(t) }, e); +function Ln(e, t, n) { + return X({ pubkey: R(e), created_at: n, kind: ut, tags: [], content: JSON.stringify(t) }, e); } -function An(e, t, n) { +function Sn(e, t, n) { const r = t.map((o) => { const a = ["r", o.url]; return o.read && !o.write && a.push("read"), o.write && !o.read && a.push("write"), a; }); - return X({ pubkey: R(e), created_at: n, kind: qe, tags: r, content: "" }, e); + return X({ pubkey: R(e), created_at: n, kind: $e, tags: r, content: "" }, e); } -function Cn(e, t, n) { +function Nn(e, t, n) { return X({ pubkey: R(e), created_at: n, - kind: ze, + kind: Be, tags: t.map((r) => ["relay", r]), content: "" }, e); } -function Ln(e) { +function Tn(e) { return e.tags.filter((t) => t[0] === "r" && t[1]).map((t) => t[1]); } -function Tn(e) { +function In(e) { return e.tags.filter((t) => t[0] === "relay" && t[1]).map((t) => t[1]); } -async function Nn(e) { - const t = R(e.everydayPrivateKey), n = Math.floor(Date.now() / 1e3), r = [.../* @__PURE__ */ new Set([...e.generalRelays, ...e.dmRelays, ...e.discoveryRelays])], o = new S(r), [a, i, c] = await Promise.all([ - o.queryQuorum({ authors: [t], kinds: [ct] }), - o.queryQuorum({ authors: [t], kinds: [qe] }), - o.queryQuorum({ authors: [t], kinds: [ze] }) +async function Mn(e) { + const t = R(e.everydayPrivateKey), n = Math.floor(Date.now() / 1e3), r = [.../* @__PURE__ */ new Set([...e.generalRelays, ...e.dmRelays, ...e.discoveryRelays])], o = new T(r), [a, i, c] = await Promise.all([ + o.queryQuorum({ authors: [t], kinds: [ut] }), + o.queryQuorum({ authors: [t], kinds: [$e] }), + o.queryQuorum({ authors: [t], kinds: [Be] }) ]); o.closeAll(); - const s = a.outcomes.some((h) => h.events.length > 0), l = i.outcomes.some((h) => h.events.length > 0), u = c.outcomes.some((h) => h.events.length > 0), d = new S(r); - let v = null, g = null, p = null; + const s = a.outcomes.some((p) => p.events.length > 0), l = i.outcomes.some((p) => p.events.length > 0), u = c.outcomes.some((p) => p.events.length > 0), d = new T(r); + let g = null, w = null, v = null; const f = []; if (!s && (e.name || e.about || e.picture)) { - const h = xn(e.everydayPrivateKey, { name: e.name, about: e.about, picture: e.picture }, n); - f.push(d.publishAll(h).then((y) => void (v = y))); + const p = Ln(e.everydayPrivateKey, { name: e.name, about: e.about, picture: e.picture }, n); + f.push(d.publishAll(p).then((h) => void (g = h))); } if (!l) { - const h = An(e.everydayPrivateKey, e.generalRelays.map((y) => ({ url: y, read: !0, write: !0 })), n); - f.push(d.publishAll(h).then((y) => void (g = y))); + const p = Sn(e.everydayPrivateKey, e.generalRelays.map((h) => ({ url: h, read: !0, write: !0 })), n); + f.push(d.publishAll(p).then((h) => void (w = h))); } if (!u) { - const h = Cn(e.everydayPrivateKey, e.dmRelays, n); - f.push(d.publishAll(h).then((y) => void (p = y))); + const p = Nn(e.everydayPrivateKey, e.dmRelays, n); + f.push(d.publishAll(p).then((h) => void (v = h))); } return await Promise.all(f), d.closeAll(), { - profilePublished: v !== null && te(v) > 0, - relayListAcknowledgedCount: g !== null ? te(g) : 0, - dmRelayListAcknowledgedCount: p !== null ? te(p) : 0, + profilePublished: g !== null && te(g) > 0, + relayListAcknowledgedCount: w !== null ? te(w) : 0, + dmRelayListAcknowledgedCount: v !== null ? te(v) : 0, profileSkippedExisting: s, relayListSkippedExisting: l, dmRelayListSkippedExisting: u }; } -async function we(e, t, n) { +async function me(e, t, n) { const r = n.minAcks ?? 2, o = n.minReadbacks ?? 2, a = await e.publishAll(t, n.timeoutMs), i = te(a), s = (await e.queryQuorum({ kinds: [he], authors: [t.pubkey], "#d": [n.dTag], limit: 5 }, n.timeoutMs)).outcomes.filter((l) => l.events.some((u) => u.id === t.id)).length; return { acknowledgedCount: i, @@ -276,25 +276,25 @@ async function we(e, t, n) { success: i >= r && s >= o }; } -function Sn(e) { - const t = rn(e); +function Un(e) { + const t = an(e); if (t === void 0) throw new Error("Value is not JSON-serializable for canonicalization."); return t; } -function In(e) { - return pe(Sn(e)); +function On(e) { + return pe(Un(e)); } -const _e = [1024, 2048, 4096], re = 4; -function Mn(e) { - const t = re + e.length, n = _e.find((a) => a >= t); +const Re = [1024, 2048, 4096], re = 4; +function Dn(e) { + const t = re + e.length, n = Re.find((a) => a >= t); if (n === void 0) - throw new Error(`Payload of ${e.length} bytes exceeds the largest padding bucket (${_e[_e.length - 1]} bytes minus ${re}-byte length prefix).`); + throw new Error(`Payload of ${e.length} bytes exceeds the largest padding bucket (${Re[Re.length - 1]} bytes minus ${re}-byte length prefix).`); const r = new Uint8Array(n); return new DataView(r.buffer).setUint32(0, e.length, !1), r.set(e, re), r; } -function Un(e) { - if (!_e.includes(e.length)) +function qn(e) { + if (!Re.includes(e.length)) throw new Error(`Padded plaintext length ${e.length} does not match a known bucket.`); const n = new DataView(e.buffer, e.byteOffset, e.byteLength).getUint32(0, !1); if (n > e.length - re) @@ -305,38 +305,38 @@ function Un(e) { throw new Error("Padding bytes are not all zero; capsule plaintext is malformed."); return r; } -function We() { +function Fe() { const e = globalThis.crypto; if (!e || !e.subtle) throw new Error("WebCrypto SubtleCrypto is not available in this environment."); return e; } -async function bt(e) { +async function Et(e) { if (e.length !== 32) throw new Error("AES-256-GCM key must be exactly 32 bytes."); - return We().subtle.importKey("raw", e, "AES-GCM", !1, ["encrypt", "decrypt"]); + return Fe().subtle.importKey("raw", e, "AES-GCM", !1, ["encrypt", "decrypt"]); } -async function On(e, t, n) { - const r = on(), o = await bt(e), a = await We().subtle.encrypt({ name: "AES-GCM", iv: r, additionalData: n, tagLength: 128 }, o, t); +async function zn(e, t, n) { + const r = sn(), o = await Et(e), a = await Fe().subtle.encrypt({ name: "AES-GCM", iv: r, additionalData: n, tagLength: 128 }, o, t); return { nonce: r, ciphertext: new Uint8Array(a) }; } -async function Dn(e, t, n, r) { - const o = await bt(e); +async function jn(e, t, n, r) { + const o = await Et(e); try { - const a = await We().subtle.decrypt({ name: "AES-GCM", iv: t, additionalData: r, tagLength: 128 }, o, n); + const a = await Fe().subtle.decrypt({ name: "AES-GCM", iv: t, additionalData: r, tagLength: 128 }, o, n); return new Uint8Array(a); } catch { throw new Error("AES-256-GCM authentication failed: capsule is corrupted, tampered, or the wrong key was used."); } } -function kt(e) { +function Pt(e) { return pe(`bitlogin|password-capsule|v1|${e}|30078|bitlogin:password:v1`); } -function Et(e) { +function _t(e) { return pe(`bitlogin|recovery-capsule|v1|${e}|30078|bitlogin:recovery:v1`); } -async function _t(e, t, n) { - const r = In(e), o = Mn(r), a = await On(t, o, n); +async function Kt(e, t, n) { + const r = On(e), o = Dn(r), a = await zn(t, o, n); return { version: 1, algorithm: "aes-256-gcm", @@ -344,26 +344,26 @@ async function _t(e, t, n) { ciphertext: oe(a.ciphertext) }; } -async function Pt(e, t, n) { +async function Rt(e, t, n) { if (e.version !== 1 || e.algorithm !== "aes-256-gcm") throw new Error(`Unsupported capsule envelope version/algorithm: ${e.version}/${e.algorithm}`); - const r = ue(e.nonce), o = ue(e.ciphertext), a = await Dn(t, r, o, n), i = Un(a); - return JSON.parse(je(i)); + const r = ue(e.nonce), o = ue(e.ciphertext), a = await jn(t, r, o, n), i = qn(a); + return JSON.parse(Ge(i)); } -class He extends Error { +class Je extends Error { constructor(t) { super(t), this.name = "CapsuleValidationError"; } } -const qn = /* @__PURE__ */ new Set(["wss:", "ws:"]), zn = 1e6, jn = /^[0-9a-f]{64}$/u; +const $n = /* @__PURE__ */ new Set(["wss:", "ws:"]), Bn = 1e6, Gn = /^[0-9a-f]{64}$/u; function x(e, t) { if (!e) - throw new He(t); + throw new Je(t); } -function Pe(e) { - return typeof e == "string" && jn.test(e); +function xe(e) { + return typeof e == "string" && Gn.test(e); } -function Kt(e) { +function xt(e) { x(Array.isArray(e), "vault_relay_hints must be an array (§12.4.7)."); for (const t of e) { x(typeof t == "string", "Each relay hint must be a string (§12.4.7)."); @@ -371,40 +371,40 @@ function Kt(e) { try { n = new URL(t); } catch { - throw new He(`Invalid relay URL: ${String(t)} (§12.4.7)`); + throw new Je(`Invalid relay URL: ${String(t)} (§12.4.7)`); } - x(qn.has(n.protocol), `Relay URL uses a disallowed scheme: ${t} (§12.4.7)`); + x($n.has(n.protocol), `Relay URL uses a disallowed scheme: ${t} (§12.4.7)`); } } -function Rt(e) { +function At(e) { x(typeof e == "string", "account_id must be a string (§12.4.2)."); let t; try { t = ue(e); } catch { - throw new He("account_id is not valid base64url (§12.4.2)."); + throw new Je("account_id is not valid base64url (§12.4.2)."); } x(t.length === 16, "account_id must decode to exactly 128 bits (§12.4.2)."); } -function xt(e, t) { +function Ct(e, t) { x(typeof e == "string", "operational_private_key must be a string (§12.4.3)."); const n = ue(e); - x(n.length === 32, "operational_private_key must be exactly 32 bytes (§12.4.3)."), x(Be(n), "operational_private_key is not a valid secp256k1 scalar (§12.4.3)."), x(Pe(t), "operational_public_key must be lowercase 64-char hex (§12.4.4)."); + x(n.length === 32, "operational_private_key must be exactly 32 bytes (§12.4.3)."), x(He(n), "operational_private_key is not a valid secp256k1 scalar (§12.4.3)."), x(xe(t), "operational_public_key must be lowercase 64-char hex (§12.4.4)."); const r = R(n); x(r === t, "operational_public_key does not match the derived public key (§12.4.4)."); } -function At(e, t) { - x(Number.isInteger(e) && e >= 0 && e <= zn, `${t} is out of supported bounds (§12.4.8).`); +function Lt(e, t) { + x(Number.isInteger(e) && e >= 0 && e <= Bn, `${t} is out of supported bounds (§12.4.8).`); } -function $n(e) { - x(e.schema === Re, `Unsupported or unknown schema: ${String(e.schema)} (§12.4.1)`), Rt(e.account_id), At(e.generation, "generation"), xt(e.operational_private_key, e.operational_public_key), x(Pe(e.recovery_public_key), "recovery_public_key must be lowercase 64-char hex (§12.4.5)."), Kt(e.vault_relay_hints); +function Vn(e) { + x(e.schema === Ce, `Unsupported or unknown schema: ${String(e.schema)} (§12.4.1)`), At(e.account_id), Lt(e.generation, "generation"), Ct(e.operational_private_key, e.operational_public_key), x(xe(e.recovery_public_key), "recovery_public_key must be lowercase 64-char hex (§12.4.5)."), xt(e.vault_relay_hints); const t = e.recovery_capsule_event; x(!!t && typeof t == "object", "recovery_capsule_event must be present (§12.4.6)."), x(ce(t), "Embedded recovery_capsule_event has an invalid event id or signature (§12.4.6)."), x(t.pubkey === e.recovery_public_key, "Embedded recovery_capsule_event author does not match recovery_public_key (§12.4.5)."); } -function Bn(e) { - x(e.schema === $e, `Unsupported or unknown schema: ${String(e.schema)} (§12.4.1)`), Rt(e.account_id), At(e.recovery_generation, "recovery_generation"), x(e.previous_recovery_event_id === null || Pe(e.previous_recovery_event_id), "previous_recovery_event_id must be null or lowercase 64-char hex (§12.3)."), xt(e.operational_private_key, e.operational_public_key), x(Pe(e.recovery_public_key), "recovery_public_key must be lowercase 64-char hex."), Kt(e.vault_relay_hints); +function Hn(e) { + x(e.schema === Ve, `Unsupported or unknown schema: ${String(e.schema)} (§12.4.1)`), At(e.account_id), Lt(e.recovery_generation, "recovery_generation"), x(e.previous_recovery_event_id === null || xe(e.previous_recovery_event_id), "previous_recovery_event_id must be null or lowercase 64-char hex (§12.3)."), Ct(e.operational_private_key, e.operational_public_key), x(xe(e.recovery_public_key), "recovery_public_key must be lowercase 64-char hex."), xt(e.vault_relay_hints); } -function Gn(e) { +function Wn(e) { const t = new Map(e.map((r) => [r.recoveryGeneration, r])), n = [...e].sort((r, o) => r.recoveryGeneration - o.recoveryGeneration); for (let r = 1; r < n.length; r++) { const o = n[r], a = t.get(o.recoveryGeneration - 1); @@ -418,64 +418,64 @@ function Gn(e) { } return { consistent: !0 }; } -async function Fe(e) { - const t = R(e.locatorPrivateKey), n = await _t(e.payload, e.capsuleKey, kt(t)); +async function Ye(e) { + const t = R(e.locatorPrivateKey), n = await Kt(e.payload, e.capsuleKey, Pt(t)); return X({ pubkey: t, created_at: e.payload.created_at, kind: he, - tags: [["d", fe]], + tags: [["d", ve]], content: JSON.stringify(n) }, e.locatorPrivateKey); } -function Vn(e) { +function Fn(e) { const t = R(e.oldLocatorPrivateKey); return X({ pubkey: t, created_at: e.createdAt, kind: he, - tags: [["d", fe]], + tags: [["d", ve]], content: "" }, e.oldLocatorPrivateKey); } -async function Wn(e, t) { +async function Jn(e, t) { if (!ce(e)) throw new Error("Credential capsule event has an invalid id or signature."); - const n = JSON.parse(e.content), r = await Pt(n, t, kt(e.pubkey)); - return $n(r), r; + const n = JSON.parse(e.content), r = await Rt(n, t, Pt(e.pubkey)); + return Vn(r), r; } -async function Ct(e) { - const t = R(e.recoveryPrivateKey), n = await _t(e.payload, e.capsuleKey, Et(t)); +async function St(e) { + const t = R(e.recoveryPrivateKey), n = await Kt(e.payload, e.capsuleKey, _t(t)); return X({ pubkey: t, created_at: e.payload.created_at, kind: he, - tags: [["d", xe]], + tags: [["d", Le]], content: JSON.stringify(n) }, e.recoveryPrivateKey); } -async function Lt(e, t) { +async function Nt(e, t) { if (!ce(e)) throw new Error("Recovery capsule event has an invalid id or signature."); - const n = JSON.parse(e.content), r = await Pt(n, t, Et(e.pubkey)); - return Bn(r), r; + const n = JSON.parse(e.content), r = await Rt(n, t, _t(e.pubkey)); + return Hn(r), r; } -function Hn(e) { +function Yn(e) { const t = /* @__PURE__ */ new Map(); for (const n of e) ce(n) && t.set(n.id, n); return [...t.values()].sort((n, r) => r.created_at - n.created_at); } async function Tt(e, t, n, r, o) { - const a = await e.queryQuorum({ kinds: [he], authors: [t], "#d": [n], limit: 5 }, o), i = a.outcomes.flatMap((v) => v.events), c = Hn(i), s = []; - for (const v of c) + const a = await e.queryQuorum({ kinds: [he], authors: [t], "#d": [n], limit: 5 }, o), i = a.outcomes.flatMap((g) => g.events), c = Yn(i), s = []; + for (const g of c) try { - const g = await r(v); - s.push({ event: v, payload: g }); - } catch (g) { - s.push({ event: v, payload: null, error: g.message }); + const w = await r(g); + s.push({ event: g, payload: w }); + } catch (w) { + s.push({ event: g, payload: null, error: w.message }); } - const l = s.find((v) => v.payload !== null) ?? null, u = a.outcomes.filter((v) => v.responded && v.events.length > 0).map((v) => v.events.slice().sort((g, p) => p.created_at - g.created_at)[0].id), d = new Set(u).size > 1; + const l = s.find((g) => g.payload !== null) ?? null, u = a.outcomes.filter((g) => g.responded && g.events.length > 0).map((g) => g.events.slice().sort((w, v) => v.created_at - w.created_at)[0].id), d = new Set(u).size > 1; return { quorumMet: a.quorumMet, respondedCount: a.respondedCount, @@ -485,21 +485,21 @@ async function Tt(e, t, n, r, o) { relayDisagreement: d }; } -async function Ke(e, t, n, r = 8e3) { - return Tt(e, t, fe, (o) => Wn(o, n), r); +async function Ae(e, t, n, r = 8e3) { + return Tt(e, t, ve, (o) => Jn(o, n), r); } -async function Fn(e, t, n, r = 8e3) { - return Tt(e, t, xe, (o) => Lt(o, n), r); +async function Qn(e, t, n, r = 8e3) { + return Tt(e, t, Le, (o) => Nt(o, n), r); } -function Jn(e) { +function Xn(e) { const t = e.filter((n) => n.payload !== null); - return t.length < 2 ? { consistent: !0 } : Gn(t.map((n) => ({ + return t.length < 2 ? { consistent: !0 } : Wn(t.map((n) => ({ eventId: n.event.id, recoveryGeneration: n.payload.recovery_generation, previousRecoveryEventId: n.payload.previous_recovery_event_id }))); } -async function Yn(e, t, n, r) { +async function Zn(e, t, n, r) { const [o, a] = await Promise.all([ e.publishAll(t, r), e.publishAll(n, r) @@ -510,7 +510,7 @@ async function Yn(e, t, n, r) { relaysTried: e.relayUrls.length }; } -const Nt = `abandon +const It = `abandon ability able about @@ -2559,192 +2559,192 @@ zero zone zoo`.split(` `); -function Qn(e, t, n, r) { - cn(e); - const o = ln({ dkLen: 32, asyncTick: 10 }, r), { c: a, dkLen: i, asyncTick: c } = o; - if (Le(a), Le(i), Le(c), a < 1) +function er(e, t, n, r) { + un(e); + const o = dn({ dkLen: 32, asyncTick: 10 }, r), { c: a, dkLen: i, asyncTick: c } = o; + if (Ie(a), Ie(i), Ie(c), a < 1) throw new Error("iterations (c) should be >= 1"); - const s = et(t), l = et(n), u = new Uint8Array(i), d = lt.create(e, s), v = d._cloneInto().update(l); - return { c: a, dkLen: i, asyncTick: c, DK: u, PRF: d, PRFSalt: v }; + const s = rt(t), l = rt(n), u = new Uint8Array(i), d = dt.create(e, s), g = d._cloneInto().update(l); + return { c: a, dkLen: i, asyncTick: c, DK: u, PRF: d, PRFSalt: g }; } -function Xn(e, t, n, r, o) { - return e.destroy(), t.destroy(), r && r.destroy(), un(o), n; +function tr(e, t, n, r, o) { + return e.destroy(), t.destroy(), r && r.destroy(), yn(o), n; } -async function Zn(e, t, n, r) { - const { c: o, dkLen: a, asyncTick: i, DK: c, PRF: s, PRFSalt: l } = Qn(e, t, n, r); +async function nr(e, t, n, r) { + const { c: o, dkLen: a, asyncTick: i, DK: c, PRF: s, PRFSalt: l } = er(e, t, n, r); let u; - const d = new Uint8Array(4), v = an(d), g = new Uint8Array(s.outputLen); - for (let p = 1, f = 0; f < a; p++, f += s.outputLen) { - const h = c.subarray(f, f + s.outputLen); - v.setInt32(0, p, !1), (u = l._cloneInto(u)).update(d).digestInto(g), h.set(g.subarray(0, h.length)), await sn(o - 1, i, () => { - s._cloneInto(u).update(g).digestInto(g); - for (let y = 0; y < h.length; y++) - h[y] ^= g[y]; + const d = new Uint8Array(4), g = cn(d), w = new Uint8Array(s.outputLen); + for (let v = 1, f = 0; f < a; v++, f += s.outputLen) { + const p = c.subarray(f, f + s.outputLen); + g.setInt32(0, v, !1), (u = l._cloneInto(u)).update(d).digestInto(w), p.set(w.subarray(0, p.length)), await ln(o - 1, i, () => { + s._cloneInto(u).update(w).digestInto(w); + for (let h = 0; h < p.length; h++) + p[h] ^= w[h]; }); } - return Xn(s, l, c, u, g); + return tr(s, l, c, u, w); } /*! scure-bip39 - MIT License (c) 2022 Patricio Palladino, Paul Miller (paulmillr.com) */ -const er = (e) => e[0] === "あいこくしん"; -function St(e) { +const rr = (e) => e[0] === "あいこくしん"; +function Mt(e) { if (typeof e != "string") throw new TypeError("invalid mnemonic type: " + typeof e); return e.normalize("NFKD"); } -function It(e) { - const t = St(e), n = t.split(" "); +function Ut(e) { + const t = Mt(e), n = t.split(" "); if (![12, 15, 18, 21, 24].includes(n.length)) throw new Error("Invalid mnemonic"); return { nfkd: t, words: n }; } -function Mt(e) { - yn(e, 16, 20, 24, 28, 32); +function Ot(e) { + pn(e, 16, 20, 24, 28, 32); } -const tr = (e) => { +const or = (e) => { const t = 8 - e.length / 4; - return new Uint8Array([ut(e)[0] >> t << t]); + return new Uint8Array([yt(e)[0] >> t << t]); }; -function Ut(e) { +function Dt(e) { if (!Array.isArray(e) || e.length !== 2048 || typeof e[0] != "string") throw new Error("Wordlist: expected array of 2048 strings"); return e.forEach((t) => { if (typeof t != "string") throw new Error("wordlist: non-string element: " + t); - }), ke.chain(ke.checksum(1, tr), ke.radix2(11, !0), ke.alphabet(e)); + }), Pe.chain(Pe.checksum(1, or), Pe.radix2(11, !0), Pe.alphabet(e)); } -function nr(e, t) { - const { words: n } = It(e), r = Ut(t).decode(n); - return Mt(r), r; +function ar(e, t) { + const { words: n } = Ut(e), r = Dt(t).decode(n); + return Ot(r), r; } -function rr(e, t) { - return Mt(e), Ut(t).encode(e).join(er(t) ? " " : " "); +function ir(e, t) { + return Ot(e), Dt(t).encode(e).join(rr(t) ? " " : " "); } -function or(e, t) { +function sr(e, t) { try { - nr(e, t); + ar(e, t); } catch { return !1; } return !0; } -const ar = (e) => St("mnemonic" + e); -function ir(e, t = "") { - return Zn(dn, It(e).nfkd, ar(t), { c: 2048, dkLen: 64 }); +const cr = (e) => Mt("mnemonic" + e); +function lr(e, t = "") { + return nr(hn, Ut(e).nfkd, cr(t), { c: 2048, dkLen: 64 }); } -function sr(e) { +function ur(e) { if (e.length !== 16) throw new Error("Recovery phrase entropy must be exactly 128 bits (16 bytes)."); - return rr(e, Nt); + return ir(e, It); } -function cr(e) { +function dr(e) { try { - return or(Ot(e), Nt); + return sr(qt(e), It); } catch { return !1; } } -function Ot(e) { +function qt(e) { return e.trim().normalize("NFKD").split(/\s+/u).join(" "); } -async function Dt(e) { - return ir(Ot(e), ""); +async function zt(e) { + return lr(qt(e), ""); } -const Je = hn.id, me = "aes-256-gcm-v1", be = "bitlogin-bip39-hkdf-v1"; -async function qt(e) { - const t = Ae(e.loginName), n = e.now ?? Math.floor(Date.now() / 1e3), { locatorPrivateKey: r, capsuleKey: o } = await ge(e.password, t), a = R(r), i = new S(e.vaultRelayUrls, { authPrivateKey: r }); +const Qe = vn.id, ke = "aes-256-gcm-v1", Ee = "bitlogin-bip39-hkdf-v1"; +async function jt(e) { + const t = Ne(e.loginName), n = e.now ?? Math.floor(Date.now() / 1e3), { locatorPrivateKey: r, capsuleKey: o } = await be(e.password, t), a = R(r), i = new T(e.vaultRelayUrls, { authPrivateKey: r }); let c; try { - c = await Ke(i, a, o, e.timeoutMs); + c = await Ae(i, a, o, e.timeoutMs); } finally { i.closeAll(); } if (!c.quorumMet) throw new ae("Couldn't verify this login name and password aren't already registered. Please retry, or add more vault relays."); if (c.candidates.length > 0) - throw new pt(); - const s = sr(fn()), l = await Dt(s), { recoveryPrivateKey: u, capsuleKey: d } = dt(l), v = R(u), g = e.everydayPrivateKey !== void 0; - if (g && !Be(e.everydayPrivateKey)) + throw new ft(); + const s = ur(gn()), l = await zt(s), { recoveryPrivateKey: u, capsuleKey: d } = ht(l), g = R(u), w = e.everydayPrivateKey !== void 0; + if (w && !He(e.everydayPrivateKey)) throw new ae("The provided key is not a valid secp256k1 private key."); - const p = g ? e.everydayPrivateKey : vn(), f = R(p), h = oe(gn()), y = { - schema: $e, - account_id: h, + const v = w ? e.everydayPrivateKey : wn(), f = R(v), p = oe(bn()), h = { + schema: Ve, + account_id: p, recovery_generation: 0, previous_recovery_event_id: null, - operational_private_key: oe(p), + operational_private_key: oe(v), operational_public_key: f, - recovery_public_key: v, + recovery_public_key: g, created_at: n, vault_relay_hints: e.vaultRelayUrls, - protocol: { capsule_encryption: me, recovery_derivation: be } - }, m = await Ct({ + protocol: { capsule_encryption: ke, recovery_derivation: Ee } + }, b = await St({ recoveryPrivateKey: u, capsuleKey: d, - payload: y - }), b = { - schema: Re, - account_id: h, + payload: h + }), m = { + schema: Ce, + account_id: p, generation: 0, - operational_private_key: oe(p), + operational_private_key: oe(v), operational_public_key: f, - recovery_public_key: v, - recovery_capsule_event: m, + recovery_public_key: g, + recovery_capsule_event: b, created_at: n, vault_relay_hints: e.vaultRelayUrls, protocol: { - password_kdf: Je, - capsule_encryption: me, - recovery_derivation: be + password_kdf: Qe, + capsule_encryption: ke, + recovery_derivation: Ee } - }, _ = await Fe({ + }, P = await Ye({ locatorPrivateKey: r, capsuleKey: o, - payload: b - }), I = new S(e.vaultRelayUrls, { authPrivateKey: u }), E = await we(I, m, { - dTag: xe, + payload: m + }), I = new T(e.vaultRelayUrls, { authPrivateKey: u }), E = await me(I, b, { + dTag: Le, minAcks: e.minAcknowledgements, timeoutMs: e.timeoutMs }); I.closeAll(); - const P = new S(e.vaultRelayUrls, { authPrivateKey: r }), K = await we(P, _, { - dTag: fe, + const _ = new T(e.vaultRelayUrls, { authPrivateKey: r }), K = await me(_, P, { + dTag: ve, minAcks: e.minAcknowledgements, timeoutMs: e.timeoutMs }); - if (P.closeAll(), !E.success || !K.success) + if (_.closeAll(), !E.success || !K.success) throw new ae("Registration did not reach the required relay acknowledgement and readback quorum. Please retry, or add more vault relays."); return { normalizedLoginName: t, recoveryPhrase: s, - everydayPrivateKey: p, + everydayPrivateKey: v, everydayPublicKey: f, - recoveryPublicKey: v, + recoveryPublicKey: g, locatorPublicKey: a, - accountId: h, - imported: g, - credentialEvent: _, - recoveryEvent: m, + accountId: p, + imported: w, + credentialEvent: P, + recoveryEvent: b, credentialPublish: K, recoveryPublish: E }; } -async function lr(e) { - const { nsecOrHex: t, ...n } = e, r = zt(t); - return qt({ ...n, everydayPrivateKey: r }); +async function yr(e) { + const { nsecOrHex: t, ...n } = e, r = $t(t); + return jt({ ...n, everydayPrivateKey: r }); } -function zt(e) { +function $t(e) { const t = e.trim(); let n; if (t.startsWith("nsec1")) - n = pn(t); + n = fn(t); else if (/^[0-9a-fA-F]{64}$/u.test(t)) - n = Ge(t.toLowerCase()); + n = Se(t.toLowerCase()); else throw new ae("Enter a valid nsec (nsec1…) or a 64-character hex private key."); - if (!Be(n)) + if (!He(n)) throw new ae("The provided key is not a valid secp256k1 private key."); return n; } -class jt { +class Bt { map = /* @__PURE__ */ new Map(); async get(t) { return this.map.get(t); @@ -2756,20 +2756,20 @@ class jt { this.map.delete(t); } } -async function ur(e) { - const t = Ae(e.loginName), { locatorPrivateKey: n, capsuleKey: r } = await ge(e.password, t), o = R(n), a = new S(e.vaultRelayUrls, { authPrivateKey: n }); +async function hr(e) { + const t = Ne(e.loginName), { locatorPrivateKey: n, capsuleKey: r } = await be(e.password, t), o = R(n), a = new T(e.vaultRelayUrls, { authPrivateKey: n }); try { - const i = await Ke(a, o, r, e.timeoutMs); + const i = await Ae(a, o, r, e.timeoutMs); if (!i.quorumMet) throw new ye("quorum-not-met"); if (!i.best) throw new ye(i.candidates.length > 0 ? "no-valid-candidate" : "no-matching-event"); - const c = i.best.payload, s = e.store ?? new jt(), l = await Ve(s, c.operational_public_key), u = c.generation < l.generation; + const c = i.best.payload, s = e.store ?? new Bt(), l = await We(s, c.operational_public_key), u = c.generation < l.generation; if (u && !e.acknowledgeRollback) - throw new vt(l.generation, c.generation); + throw new wt(l.generation, c.generation); const d = u ? `This device previously saw credential generation ${l.generation}, but the accepted capsule is generation ${c.generation}. Relays may be serving stale data, or an old capsule is being replayed.` : void 0; - await mt(s, c.operational_public_key, { generation: c.generation }); - const v = i.relayDisagreement ? 'Configured relays returned different credential capsules as "latest" for this account. Some relays may be stale, censored, or malicious.' : void 0; + await kt(s, c.operational_public_key, { generation: c.generation }); + const g = i.relayDisagreement ? 'Configured relays returned different credential capsules as "latest" for this account. Some relays may be stale, censored, or malicious.' : void 0; return { everydayPrivateKey: ue(c.operational_private_key), everydayPublicKey: c.operational_public_key, @@ -2779,53 +2779,53 @@ async function ur(e) { credentialEvent: i.best.event, recoveryCapsuleEvent: c.recovery_capsule_event, rollbackWarning: d, - relayDisagreementWarning: v + relayDisagreementWarning: g }; } finally { a.closeAll(); } } -function nt(e) { +function at(e) { return e.filter((t) => ce(t)).sort((t, n) => n.created_at - t.created_at)[0]; } -async function dr(e) { - if (!cr(e.phrase)) - throw new ft("This does not look like a valid 12-word BitLogin recovery phrase."); - const t = await Dt(e.phrase), { recoveryPrivateKey: n, capsuleKey: r } = dt(t), o = R(n), a = new S(e.vaultRelayUrls, { authPrivateKey: n }); +async function pr(e) { + if (!dr(e.phrase)) + throw new gt("This does not look like a valid 12-word BitLogin recovery phrase."); + const t = await zt(e.phrase), { recoveryPrivateKey: n, capsuleKey: r } = ht(t), o = R(n), a = new T(e.vaultRelayUrls, { authPrivateKey: n }); let i; try { - i = await Fn(a, o, r, e.timeoutMs); + i = await Qn(a, o, r, e.timeoutMs); } finally { a.closeAll(); } if (e.offlineRecoveryCapsuleEvents?.length) { const f = []; - for (const b of e.offlineRecoveryCapsuleEvents) - if (ce(b)) + for (const m of e.offlineRecoveryCapsuleEvents) + if (ce(m)) try { - f.push({ event: b, payload: await Lt(b, r) }); - } catch (_) { - f.push({ event: b, payload: null, error: _.message }); + f.push({ event: m, payload: await Nt(m, r) }); + } catch (P) { + f.push({ event: m, payload: null, error: P.message }); } - const h = /* @__PURE__ */ new Map(); - for (const b of [...i.candidates, ...f]) - h.set(b.event.id, b); - const y = [...h.values()].sort((b, _) => _.event.created_at - b.event.created_at), m = y.find((b) => b.payload !== null) ?? null; - i = { ...i, candidates: y, best: m, quorumMet: i.quorumMet || m !== null }; + const p = /* @__PURE__ */ new Map(); + for (const m of [...i.candidates, ...f]) + p.set(m.event.id, m); + const h = [...p.values()].sort((m, P) => P.event.created_at - m.event.created_at), b = h.find((m) => m.payload !== null) ?? null; + i = { ...i, candidates: h, best: b, quorumMet: i.quorumMet || b !== null }; } if (!i.quorumMet) throw new ye("quorum-not-met"); if (!i.best) throw new ye(i.candidates.length > 0 ? "no-valid-candidate" : "no-matching-event"); - const c = i.best.payload, s = Jn(i.candidates), l = ue(c.operational_private_key), u = c.operational_public_key, d = [.../* @__PURE__ */ new Set([...e.discoveryRelayUrls, ...c.vault_relay_hints])], v = new S(d); - let g = [], p = []; + const c = i.best.payload, s = Xn(i.candidates), l = ue(c.operational_private_key), u = c.operational_public_key, d = [.../* @__PURE__ */ new Set([...e.discoveryRelayUrls, ...c.vault_relay_hints])], g = new T(d); + let w = [], v = []; try { - const f = await v.queryQuorum({ kinds: [qe], authors: [u], limit: 5 }, e.timeoutMs), h = nt(f.outcomes.flatMap((b) => b.events)); - h && (g = Ln(h)); - const y = await v.queryQuorum({ kinds: [ze], authors: [u], limit: 5 }, e.timeoutMs), m = nt(y.outcomes.flatMap((b) => b.events)); - m && (p = Tn(m)); + const f = await g.queryQuorum({ kinds: [$e], authors: [u], limit: 5 }, e.timeoutMs), p = at(f.outcomes.flatMap((m) => m.events)); + p && (w = Tn(p)); + const h = await g.queryQuorum({ kinds: [Be], authors: [u], limit: 5 }, e.timeoutMs), b = at(h.outcomes.flatMap((m) => m.events)); + b && (v = In(b)); } finally { - v.closeAll(); + g.closeAll(); } return { everydayPrivateKey: l, @@ -2836,35 +2836,35 @@ async function dr(e) { accountId: c.account_id, currentRecoveryEvent: i.best.event, currentRecoveryPayload: c, - generalRelays: g, - dmRelays: p, + generalRelays: w, + dmRelays: v, chainWarning: s.consistent ? void 0 : s.warning }; } -async function yr(e) { - const t = e.now ?? Math.floor(Date.now() / 1e3), n = Ae(e.newLoginName), { recovered: r } = e, o = { - schema: $e, +async function vr(e) { + const t = e.now ?? Math.floor(Date.now() / 1e3), n = Ne(e.newLoginName), { recovered: r } = e, o = { + schema: Ve, account_id: r.accountId, recovery_generation: r.currentRecoveryPayload.recovery_generation + 1, previous_recovery_event_id: r.currentRecoveryEvent.id, operational_private_key: oe(r.everydayPrivateKey), operational_public_key: r.everydayPublicKey, recovery_public_key: r.recoveryPublicKey, - created_at: gt(r.currentRecoveryEvent.created_at, t), + created_at: bt(r.currentRecoveryEvent.created_at, t), vault_relay_hints: e.vaultRelayUrls, - protocol: { capsule_encryption: me, recovery_derivation: be } - }, a = await Ct({ + protocol: { capsule_encryption: ke, recovery_derivation: Ee } + }, a = await St({ recoveryPrivateKey: r.recoveryPrivateKey, capsuleKey: r.recoveryCapsuleKey, payload: o - }), i = new S(e.vaultRelayUrls, { authPrivateKey: r.recoveryPrivateKey }), c = await we(i, a, { - dTag: xe, + }), i = new T(e.vaultRelayUrls, { authPrivateKey: r.recoveryPrivateKey }), c = await me(i, a, { + dTag: Le, minAcks: e.minAcknowledgements, timeoutMs: e.timeoutMs }); i.closeAll(); - const { locatorPrivateKey: s, capsuleKey: l } = await ge(e.newPassword, n), u = R(s), d = { - schema: Re, + const { locatorPrivateKey: s, capsuleKey: l } = await be(e.newPassword, n), u = R(s), d = { + schema: Ce, account_id: r.accountId, generation: 0, operational_private_key: oe(r.everydayPrivateKey), @@ -2874,25 +2874,25 @@ async function yr(e) { created_at: t, vault_relay_hints: e.vaultRelayUrls, protocol: { - password_kdf: Je, - capsule_encryption: me, - recovery_derivation: be + password_kdf: Qe, + capsule_encryption: ke, + recovery_derivation: Ee } - }, v = await Fe({ locatorPrivateKey: s, capsuleKey: l, payload: d }), g = new S(e.vaultRelayUrls, { authPrivateKey: s }), p = await we(g, v, { - dTag: fe, + }, g = await Ye({ locatorPrivateKey: s, capsuleKey: l, payload: d }), w = new T(e.vaultRelayUrls, { authPrivateKey: s }), v = await me(w, g, { + dTag: ve, minAcks: e.minAcknowledgements, timeoutMs: e.timeoutMs }); - if (g.closeAll(), !c.success || !p.success) - throw new ft("Could not publish the refreshed recovery and credential capsules to enough relays. Please retry."); - return { normalizedLoginName: n, locatorPublicKey: u, credentialEvent: v, refreshedRecoveryEvent: a, credentialPublish: p, recoveryPublish: c }; + if (w.closeAll(), !c.success || !v.success) + throw new gt("Could not publish the refreshed recovery and credential capsules to enough relays. Please retry."); + return { normalizedLoginName: n, locatorPublicKey: u, credentialEvent: g, refreshedRecoveryEvent: a, credentialPublish: v, recoveryPublish: c }; } -function hr(e) { +function fr(e) { const t = R(e.privateKey); return X({ pubkey: t, created_at: e.createdAt, - kind: wn, + kind: mn, tags: [ ["e", e.eventIdToDelete], ["k", String(e.deletedEventKind)] @@ -2900,11 +2900,11 @@ function hr(e) { content: e.reason ?? "" }, e.privateKey); } -async function pr(e) { - const t = e.now ?? Math.floor(Date.now() / 1e3), n = Ae(e.loginName), r = await ge(e.oldPassword, n), o = R(r.locatorPrivateKey), a = new S(e.vaultRelayUrls, { authPrivateKey: r.locatorPrivateKey }); +async function gr(e) { + const t = e.now ?? Math.floor(Date.now() / 1e3), n = Ne(e.loginName), r = await be(e.oldPassword, n), o = R(r.locatorPrivateKey), a = new T(e.vaultRelayUrls, { authPrivateKey: r.locatorPrivateKey }); let i; try { - i = await Ke(a, o, r.capsuleKey, e.timeoutMs); + i = await Ae(a, o, r.capsuleKey, e.timeoutMs); } finally { a.closeAll(); } @@ -2912,22 +2912,22 @@ async function pr(e) { throw new ye("quorum-not-met"); if (!i.best) throw new ye(i.candidates.length > 0 ? "no-valid-candidate" : "no-matching-event"); - const c = i.best.payload, s = i.best.event, l = e.store ?? new jt(), u = await Ve(l, c.operational_public_key); + const c = i.best.payload, s = i.best.event, l = e.store ?? new Bt(), u = await We(l, c.operational_public_key); if (c.generation < u.generation && !e.acknowledgeRollback) - throw new vt(u.generation, c.generation); - const d = await ge(e.newPassword, n), v = R(d.locatorPrivateKey), g = new S(e.vaultRelayUrls, { authPrivateKey: d.locatorPrivateKey }); - let p; + throw new wt(u.generation, c.generation); + const d = await be(e.newPassword, n), g = R(d.locatorPrivateKey), w = new T(e.vaultRelayUrls, { authPrivateKey: d.locatorPrivateKey }); + let v; try { - p = await Ke(g, v, d.capsuleKey, e.timeoutMs); + v = await Ae(w, g, d.capsuleKey, e.timeoutMs); } finally { - g.closeAll(); + w.closeAll(); } - if (!p.quorumMet) + if (!v.quorumMet) throw new ae("Couldn't verify the new password isn't already registered under this login name. Please retry, or add more vault relays."); - if (p.candidates.length > 0) - throw new pt("Another account is already registered with this login name and the new password you chose. Pick a different new password."); - const f = c.generation + 1, h = { - schema: Re, + if (v.candidates.length > 0) + throw new ft("Another account is already registered with this login name and the new password you chose. Pick a different new password."); + const f = c.generation + 1, p = { + schema: Ce, account_id: c.account_id, generation: f, operational_private_key: c.operational_private_key, @@ -2937,65 +2937,65 @@ async function pr(e) { created_at: t, vault_relay_hints: e.vaultRelayUrls, protocol: { - password_kdf: Je, - capsule_encryption: me, - recovery_derivation: be + password_kdf: Qe, + capsule_encryption: ke, + recovery_derivation: Ee } - }, y = await Fe({ + }, h = await Ye({ locatorPrivateKey: d.locatorPrivateKey, capsuleKey: d.capsuleKey, - payload: h - }), m = new S(e.vaultRelayUrls, { authPrivateKey: d.locatorPrivateKey }), b = await we(m, y, { - dTag: fe, + payload: p + }), b = new T(e.vaultRelayUrls, { authPrivateKey: d.locatorPrivateKey }), m = await me(b, h, { + dTag: ve, minAcks: e.minAcknowledgements, timeoutMs: e.timeoutMs }); - m.closeAll(); - const _ = Vn({ + b.closeAll(); + const P = Fn({ oldLocatorPrivateKey: r.locatorPrivateKey, - createdAt: gt(s.created_at, t) - }), I = hr({ + createdAt: bt(s.created_at, t) + }), I = fr({ privateKey: r.locatorPrivateKey, eventIdToDelete: s.id, deletedEventKind: he, createdAt: t - }), E = new S(e.vaultRelayUrls, { authPrivateKey: r.locatorPrivateKey }), [P, K] = await Promise.all([ - E.publishAll(_, e.timeoutMs), + }), E = new T(e.vaultRelayUrls, { authPrivateKey: r.locatorPrivateKey }), [_, K] = await Promise.all([ + E.publishAll(P, e.timeoutMs), E.publishAll(I, e.timeoutMs) ]); - if (E.closeAll(), !b.success) + if (E.closeAll(), !m.success) throw new ae("The new credential capsule did not reach the required relay acknowledgement and readback quorum. Please retry."); - return await mt(l, c.operational_public_key, { generation: f }), { + return await kt(l, c.operational_public_key, { generation: f }), { normalizedLoginName: n, oldLocatorPublicKey: o, - newLocatorPublicKey: v, + newLocatorPublicKey: g, newGeneration: f, recoveryPublicKey: c.recovery_public_key, recoveryCapsuleEvent: c.recovery_capsule_event, - newCredentialEvent: y, - tombstoneEvent: _, + newCredentialEvent: h, + tombstoneEvent: P, deletionRequestEvent: I, - newCredentialPublish: b, - tombstoneAcknowledgedCount: te(P), + newCredentialPublish: m, + tombstoneAcknowledgedCount: te(_), deletionAcknowledgedCount: te(K) }; } /*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */ -function fr(e) { +function wr(e) { return e instanceof Uint8Array || ArrayBuffer.isView(e) && e.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in e && e.BYTES_PER_ELEMENT === 1; } -function rt(e) { +function it(e) { if (typeof e != "boolean") throw new TypeError(`boolean expected, not ${e}`); } -function Te(e) { +function Me(e) { if (typeof e != "number") throw new TypeError("number expected, got " + typeof e); if (!Number.isSafeInteger(e) || e < 0) throw new RangeError("positive integer expected, got " + e); } -function T(e, t, n = "") { - const r = fr(e), o = e?.length, a = t !== void 0; +function S(e, t, n = "") { + const r = wr(e), o = e?.length, a = t !== void 0; if (!r || a && o !== t) { const i = n && `"${n}" `, c = a ? ` of length ${t}` : "", s = r ? `length=${o}` : `type=${typeof e}`, l = i + "expected Uint8Array" + c + ", got " + s; throw r ? new RangeError(l) : new TypeError(l); @@ -3009,38 +3009,38 @@ function se(...e) { for (let t = 0; t < e.length; t++) e[t].fill(0); } -const H = new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68, vr = (e) => e << 24 & 4278190080 | e << 8 & 16711680 | e >>> 8 & 65280 | e >>> 24 & 255, gr = (e) => { +const W = new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68, br = (e) => e << 24 & 4278190080 | e << 8 & 16711680 | e >>> 8 & 65280 | e >>> 24 & 255, mr = (e) => { for (let t = 0; t < e.length; t++) - e[t] = vr(e[t]); + e[t] = br(e[t]); return e; -}, N = H ? (e) => e : gr; -function wr(e, t) { +}, N = W ? (e) => e : mr; +function kr(e, t) { return !e.byteLength || !t.byteLength ? !1 : e.buffer === t.buffer && // best we can do, may fail with an obscure Proxy e.byteOffset < t.byteOffset + t.byteLength && // a starts before b end t.byteOffset < e.byteOffset + e.byteLength; } -function $t(e, t) { - if (wr(e, t) && e.byteOffset < t.byteOffset) +function Gt(e, t) { + if (kr(e, t) && e.byteOffset < t.byteOffset) throw new Error("complex overlap of input and output is not supported"); } -function mr(e, t) { +function Er(e, t) { if (t == null || typeof t != "object") throw new Error("options must be defined"); return Object.assign(e, t); } -const br = /* @__NO_SIDE_EFFECTS__ */ (e, t) => { +const Pr = /* @__NO_SIDE_EFFECTS__ */ (e, t) => { function n(r, ...o) { - if (T(r, void 0, "key"), e.nonceLength !== void 0) { + if (S(r, void 0, "key"), e.nonceLength !== void 0) { const u = o[0]; - T(u, e.varSizeNonce ? void 0 : e.nonceLength, "nonce"); + S(u, e.varSizeNonce ? void 0 : e.nonceLength, "nonce"); } const a = e.tagLength; - a && o[1] !== void 0 && T(o[1], void 0, "AAD"); + a && o[1] !== void 0 && S(o[1], void 0, "AAD"); const i = t(r, ...o), c = (u, d) => { if (d !== void 0) { if (u !== 2) throw new Error("cipher output not supported"); - T(d, void 0, "output"); + S(d, void 0, "output"); } }; let s = !1; @@ -3048,10 +3048,10 @@ const br = /* @__NO_SIDE_EFFECTS__ */ (e, t) => { encrypt(u, d) { if (s) throw new Error("cannot encrypt() twice with same key + nonce"); - return s = !0, T(u), c(i.encrypt.length, d), i.encrypt(u, d); + return s = !0, S(u), c(i.encrypt.length, d), i.encrypt(u, d); }, decrypt(u, d) { - if (T(u), a && u.length < a) + if (S(u), a && u.length < a) throw new Error('"ciphertext" expected length bigger than tagLength=' + a); return c(i.decrypt.length, d), i.decrypt(u, d); } @@ -3059,10 +3059,10 @@ const br = /* @__NO_SIDE_EFFECTS__ */ (e, t) => { } return Object.assign(n, e), n; }; -function Ye(e, t, n = !0) { +function Xe(e, t, n = !0) { if (t === void 0) return new Uint8Array(e); - if (T(t, void 0, "output"), t.length !== e) + if (S(t, void 0, "output"), t.length !== e) throw new Error('"output" expected Uint8Array of length ' + e + ", got: " + t.length); if (n && !Q(t)) throw new Error("invalid output, must be aligned"); @@ -3072,181 +3072,181 @@ function Q(e) { return e.byteOffset % 4 === 0; } function ie(e) { - return Uint8Array.from(T(e)); + return Uint8Array.from(S(e)); } -const Bt = (e) => Uint8Array.from(e.split(""), (t) => t.charCodeAt(0)), kr = N(A(Bt("expand 16-byte k"))), Er = N(A(Bt("expand 32-byte k"))); +const Vt = (e) => Uint8Array.from(e.split(""), (t) => t.charCodeAt(0)), _r = N(A(Vt("expand 16-byte k"))), Kr = N(A(Vt("expand 32-byte k"))); function k(e, t) { return e << t | e >>> 32 - t; } -const ve = 64, _r = 16, Me = 2 ** 32 - 1, ot = /* @__PURE__ */ Uint32Array.of(); -function Pr(e, t, n, r, o, a, i, c) { - const s = o.length, l = new Uint8Array(ve), u = A(l), d = H && Q(o) && Q(a), v = d ? A(o) : ot, g = d ? A(a) : ot; - if (!H) { - for (let p = 0; p < s; i++) { - if (e(t, n, r, u, i, c), N(u), i >= Me) +const fe = 64, Rr = 16, De = 2 ** 32 - 1, st = /* @__PURE__ */ Uint32Array.of(); +function xr(e, t, n, r, o, a, i, c) { + const s = o.length, l = new Uint8Array(fe), u = A(l), d = W && Q(o) && Q(a), g = d ? A(o) : st, w = d ? A(a) : st; + if (!W) { + for (let v = 0; v < s; i++) { + if (e(t, n, r, u, i, c), N(u), i >= De) throw new Error("arx: counter overflow"); - const f = Math.min(ve, s - p); - for (let h = 0, y; h < f; h++) - y = p + h, a[y] = o[y] ^ l[h]; - p += f; + const f = Math.min(fe, s - v); + for (let p = 0, h; p < f; p++) + h = v + p, a[h] = o[h] ^ l[p]; + v += f; } return; } - for (let p = 0; p < s; i++) { - if (e(t, n, r, u, i, c), i >= Me) + for (let v = 0; v < s; i++) { + if (e(t, n, r, u, i, c), i >= De) throw new Error("arx: counter overflow"); - const f = Math.min(ve, s - p); - if (d && f === ve) { - const h = p / 4; - if (p % 4 !== 0) + const f = Math.min(fe, s - v); + if (d && f === fe) { + const p = v / 4; + if (v % 4 !== 0) throw new Error("arx: invalid block position"); - for (let y = 0, m; y < _r; y++) - m = h + y, g[m] = v[m] ^ u[y]; - p += ve; + for (let h = 0, b; h < Rr; h++) + b = p + h, w[b] = g[b] ^ u[h]; + v += fe; continue; } - for (let h = 0, y; h < f; h++) - y = p + h, a[y] = o[y] ^ l[h]; - p += f; + for (let p = 0, h; p < f; p++) + h = v + p, a[h] = o[h] ^ l[p]; + v += f; } } -function Kr(e, t) { - const { allowShortKeys: n, extendNonceFn: r, counterLength: o, counterRight: a, rounds: i } = mr({ allowShortKeys: !1, counterLength: 8, counterRight: !1, rounds: 20 }, t); +function Ar(e, t) { + const { allowShortKeys: n, extendNonceFn: r, counterLength: o, counterRight: a, rounds: i } = Er({ allowShortKeys: !1, counterLength: 8, counterRight: !1, rounds: 20 }, t); if (typeof e != "function") throw new Error("core must be a function"); - return Te(o), Te(i), rt(a), rt(n), (c, s, l, u, d = 0) => { - T(c, void 0, "key"), T(s, void 0, "nonce"), T(l, void 0, "data"); - const v = l.length; - if (u = Ye(v, u, !1), Te(d), d < 0 || d >= Me) + return Me(o), Me(i), it(a), it(n), (c, s, l, u, d = 0) => { + S(c, void 0, "key"), S(s, void 0, "nonce"), S(l, void 0, "data"); + const g = l.length; + if (u = Xe(g, u, !1), Me(d), d < 0 || d >= De) throw new Error("arx: counter overflow"); - const g = []; - let p = c.length, f, h; - if (p === 32) - g.push(f = ie(c)), h = Er; - else if (p === 16 && n) - f = new Uint8Array(32), f.set(c), f.set(c, 16), h = kr, g.push(f); + const w = []; + let v = c.length, f, p; + if (v === 32) + w.push(f = ie(c)), p = Kr; + else if (v === 16 && n) + f = new Uint8Array(32), f.set(c), f.set(c, 16), p = _r, w.push(f); else - throw T(c, 32, "arx key"), new Error("invalid key size"); - (!H || !Q(s)) && g.push(s = ie(s)); - let y = A(f); + throw S(c, 32, "arx key"), new Error("invalid key size"); + (!W || !Q(s)) && w.push(s = ie(s)); + let h = A(f); if (r) { if (s.length !== 24) throw new Error("arx: extended nonce must be 24 bytes"); - const _ = s.subarray(0, 16); - if (H) - r(h, y, A(_), y); + const P = s.subarray(0, 16); + if (W) + r(p, h, A(P), h); else { - const I = N(Uint32Array.from(h)); - r(I, y, A(_), y), se(I), N(y); + const I = N(Uint32Array.from(p)); + r(I, h, A(P), h), se(I), N(h); } s = s.subarray(16); - } else H || N(y); - const m = 16 - o; - if (m !== s.length) - throw new Error(`arx: nonce must be ${m} or 16 bytes`); - if (m !== 12) { - const _ = new Uint8Array(12); - _.set(s, a ? 0 : 12 - s.length), s = _, g.push(s); + } else W || N(h); + const b = 16 - o; + if (b !== s.length) + throw new Error(`arx: nonce must be ${b} or 16 bytes`); + if (b !== 12) { + const P = new Uint8Array(12); + P.set(s, a ? 0 : 12 - s.length), s = P, w.push(s); } - const b = N(A(s)); + const m = N(A(s)); try { - return Pr(e, h, y, b, l, u, d, i), u; + return xr(e, p, h, m, l, u, d, i), u; } finally { - se(...g); + se(...w); } }; } -function Rr(e, t, n, r, o, a = 20) { - let i = e[0], c = e[1], s = e[2], l = e[3], u = t[0], d = t[1], v = t[2], g = t[3], p = t[4], f = t[5], h = t[6], y = t[7], m = o, b = n[0], _ = n[1], I = n[2], E = i, P = c, K = s, L = l, M = u, U = d, O = v, D = g, q = p, z = f, j = h, $ = y, B = m, G = b, V = _, W = I; - for (let Ze = 0; Ze < a; Ze += 2) - E = E + M | 0, B = k(B ^ E, 16), q = q + B | 0, M = k(M ^ q, 12), E = E + M | 0, B = k(B ^ E, 8), q = q + B | 0, M = k(M ^ q, 7), P = P + U | 0, G = k(G ^ P, 16), z = z + G | 0, U = k(U ^ z, 12), P = P + U | 0, G = k(G ^ P, 8), z = z + G | 0, U = k(U ^ z, 7), K = K + O | 0, V = k(V ^ K, 16), j = j + V | 0, O = k(O ^ j, 12), K = K + O | 0, V = k(V ^ K, 8), j = j + V | 0, O = k(O ^ j, 7), L = L + D | 0, W = k(W ^ L, 16), $ = $ + W | 0, D = k(D ^ $, 12), L = L + D | 0, W = k(W ^ L, 8), $ = $ + W | 0, D = k(D ^ $, 7), E = E + U | 0, W = k(W ^ E, 16), j = j + W | 0, U = k(U ^ j, 12), E = E + U | 0, W = k(W ^ E, 8), j = j + W | 0, U = k(U ^ j, 7), P = P + O | 0, B = k(B ^ P, 16), $ = $ + B | 0, O = k(O ^ $, 12), P = P + O | 0, B = k(B ^ P, 8), $ = $ + B | 0, O = k(O ^ $, 7), K = K + D | 0, G = k(G ^ K, 16), q = q + G | 0, D = k(D ^ q, 12), K = K + D | 0, G = k(G ^ K, 8), q = q + G | 0, D = k(D ^ q, 7), L = L + M | 0, V = k(V ^ L, 16), z = z + V | 0, M = k(M ^ z, 12), L = L + M | 0, V = k(V ^ L, 8), z = z + V | 0, M = k(M ^ z, 7); +function Cr(e, t, n, r, o, a = 20) { + let i = e[0], c = e[1], s = e[2], l = e[3], u = t[0], d = t[1], g = t[2], w = t[3], v = t[4], f = t[5], p = t[6], h = t[7], b = o, m = n[0], P = n[1], I = n[2], E = i, _ = c, K = s, L = l, M = u, U = d, O = g, D = w, q = v, z = f, j = p, $ = h, B = b, G = m, V = P, H = I; + for (let nt = 0; nt < a; nt += 2) + E = E + M | 0, B = k(B ^ E, 16), q = q + B | 0, M = k(M ^ q, 12), E = E + M | 0, B = k(B ^ E, 8), q = q + B | 0, M = k(M ^ q, 7), _ = _ + U | 0, G = k(G ^ _, 16), z = z + G | 0, U = k(U ^ z, 12), _ = _ + U | 0, G = k(G ^ _, 8), z = z + G | 0, U = k(U ^ z, 7), K = K + O | 0, V = k(V ^ K, 16), j = j + V | 0, O = k(O ^ j, 12), K = K + O | 0, V = k(V ^ K, 8), j = j + V | 0, O = k(O ^ j, 7), L = L + D | 0, H = k(H ^ L, 16), $ = $ + H | 0, D = k(D ^ $, 12), L = L + D | 0, H = k(H ^ L, 8), $ = $ + H | 0, D = k(D ^ $, 7), E = E + U | 0, H = k(H ^ E, 16), j = j + H | 0, U = k(U ^ j, 12), E = E + U | 0, H = k(H ^ E, 8), j = j + H | 0, U = k(U ^ j, 7), _ = _ + O | 0, B = k(B ^ _, 16), $ = $ + B | 0, O = k(O ^ $, 12), _ = _ + O | 0, B = k(B ^ _, 8), $ = $ + B | 0, O = k(O ^ $, 7), K = K + D | 0, G = k(G ^ K, 16), q = q + G | 0, D = k(D ^ q, 12), K = K + D | 0, G = k(G ^ K, 8), q = q + G | 0, D = k(D ^ q, 7), L = L + M | 0, V = k(V ^ L, 16), z = z + V | 0, M = k(M ^ z, 12), L = L + M | 0, V = k(V ^ L, 8), z = z + V | 0, M = k(M ^ z, 7); let C = 0; - r[C++] = i + E | 0, r[C++] = c + P | 0, r[C++] = s + K | 0, r[C++] = l + L | 0, r[C++] = u + M | 0, r[C++] = d + U | 0, r[C++] = v + O | 0, r[C++] = g + D | 0, r[C++] = p + q | 0, r[C++] = f + z | 0, r[C++] = h + j | 0, r[C++] = y + $ | 0, r[C++] = m + B | 0, r[C++] = b + G | 0, r[C++] = _ + V | 0, r[C++] = I + W | 0; + r[C++] = i + E | 0, r[C++] = c + _ | 0, r[C++] = s + K | 0, r[C++] = l + L | 0, r[C++] = u + M | 0, r[C++] = d + U | 0, r[C++] = g + O | 0, r[C++] = w + D | 0, r[C++] = v + q | 0, r[C++] = f + z | 0, r[C++] = p + j | 0, r[C++] = h + $ | 0, r[C++] = b + B | 0, r[C++] = m + G | 0, r[C++] = P + V | 0, r[C++] = I + H | 0; } -const Gt = /* @__PURE__ */ Kr(Rr, { +const Ht = /* @__PURE__ */ Ar(Cr, { counterRight: !1, counterLength: 4, allowShortKeys: !1 -}), Vt = 2, Ue = 1, Wt = 65536, Oe = 4294967295; -function at(e, t) { - const n = Ce(new Uint8Array([2]), Ge(t)), o = ht.getSharedSecret(e, n, !0).slice(1, 33); - return mn(pe("nip44-v2"), o); +}), Wt = 2, qe = 1, Ft = 65536, ze = 4294967295; +function ct(e, t) { + const n = Te(new Uint8Array([2]), Se(t)), o = vt.getSharedSecret(e, n, !0).slice(1, 33); + return kn(pe("nip44-v2"), o); } -function Ht(e, t) { - const n = kn(e, t, 76); +function Jt(e, t) { + const n = Pn(e, t, 76); return { chachaKey: n.slice(0, 32), chachaNonce: n.slice(32, 44), hmacKey: n.slice(44, 76) }; } -function Ft(e) { +function Yt(e) { if (e <= 32) return 32; const t = 2 ** Math.floor(Math.log2(e - 1) + 1), n = t <= 256 ? 32 : t / 8; return n * (Math.floor((e - 1) / n) + 1); } -function xr(e) { - if (e < Wt) { +function Lr(e) { + if (e < Ft) { const n = new Uint8Array(2); return new DataView(n.buffer).setUint16(0, e, !1), n; } const t = new Uint8Array(6); return new DataView(t.buffer).setUint32(2, e, !1), t; } -function Ar(e) { +function Sr(e) { const t = e.length; - if (t < Ue || t > Oe) - throw new Error(`NIP-44 plaintext length must be between ${Ue} and ${Oe} bytes.`); - const n = xr(t), r = Ft(t), o = new Uint8Array(n.length + r); + if (t < qe || t > ze) + throw new Error(`NIP-44 plaintext length must be between ${qe} and ${ze} bytes.`); + const n = Lr(t), r = Yt(t), o = new Uint8Array(n.length + r); return o.set(n, 0), o.set(e, n.length), o; } -function Cr(e) { +function Nr(e) { const t = new DataView(e.buffer, e.byteOffset, e.byteLength), n = t.getUint16(0, !1); let r, o; if (n === 0) { - if (r = t.getUint32(2, !1), o = 6, r < Wt) + if (r = t.getUint32(2, !1), o = 6, r < Ft) throw new Error("NIP-44 payload has inconsistent padding."); } else r = n, o = 2; - if (r < Ue || r > Oe || e.length !== o + Ft(r)) + if (r < qe || r > ze || e.length !== o + Yt(r)) throw new Error("NIP-44 payload has inconsistent padding."); return e.slice(o, o + r); } -function Jt(e, t, n) { - return lt(ut, e, Ce(t, n)); +function Qt(e, t, n) { + return dt(yt, e, Te(t, n)); } -function Lr(e, t, n) { - const r = yt(32), { chachaKey: o, chachaNonce: a, hmacKey: i } = Ht(e, r), c = Ar(pe(t)), s = Gt(o, a, c), l = Jt(i, r, s); - return de.encode(Ce(new Uint8Array([Vt]), r, s, l)); +function Tr(e, t, n) { + const r = pt(32), { chachaKey: o, chachaNonce: a, hmacKey: i } = Jt(e, r), c = Sr(pe(t)), s = Ht(o, a, c), l = Qt(i, r, s); + return de.encode(Te(new Uint8Array([Wt]), r, s, l)); } -function Tr(e, t) { +function Ir(e, t) { const n = de.decode(t); - if (n[0] !== Vt) + if (n[0] !== Wt) throw new Error(`Unsupported NIP-44 version: ${n[0]}`); - const r = n.slice(1, 33), o = n.slice(n.length - 32), a = n.slice(33, n.length - 32), { chachaKey: i, chachaNonce: c, hmacKey: s } = Ht(e, r), l = Jt(s, r, a); - if (!bn(o, l)) + const r = n.slice(1, 33), o = n.slice(n.length - 32), a = n.slice(33, n.length - 32), { chachaKey: i, chachaNonce: c, hmacKey: s } = Jt(e, r), l = Qt(s, r, a); + if (!En(o, l)) throw new Error("NIP-44 MAC verification failed: payload is corrupted, tampered, or uses the wrong key."); - const u = Gt(i, c, a); - return je(Cr(u)); + const u = Ht(i, c, a); + return Ge(Nr(u)); } -const ne = 16, Nr = 283; -function Sr(e) { +const ne = 16, Mr = 283; +function Ur(e) { if (![16, 24, 32].includes(e.length)) throw new Error('"aes key" expected Uint8Array of length 16/24/32, got length=' + e.length); } -function Qe(e) { - return e << 1 ^ Nr & -(e >> 7); +function Ze(e) { + return e << 1 ^ Mr & -(e >> 7); } function le(e, t) { let n = 0; for (; t > 0; t >>= 1) - n ^= e & -(t & 1), e = Qe(e); + n ^= e & -(t & 1), e = Ze(e); return n; } -const De = /* @__PURE__ */ (() => { +const je = /* @__PURE__ */ (() => { const e = new Uint8Array(256); - for (let n = 0, r = 1; n < 256; n++, r ^= Qe(r)) + for (let n = 0, r = 1; n < 256; n++, r ^= Ze(r)) e[n] = r; const t = new Uint8Array(256); t[0] = 99; @@ -3255,11 +3255,11 @@ const De = /* @__PURE__ */ (() => { r |= r << 8, t[e[n]] = (r ^ r >> 4 ^ r >> 5 ^ r >> 6 ^ r >> 7 ^ 99) & 255; } return se(e), t; -})(), Ir = /* @__PURE__ */ De.map((e, t) => De.indexOf(t)), Mr = (e) => e << 24 | e >>> 8, Ne = (e) => e << 8 | e >>> 24; -function Yt(e, t) { +})(), Or = /* @__PURE__ */ je.map((e, t) => je.indexOf(t)), Dr = (e) => e << 24 | e >>> 8, Ue = (e) => e << 8 | e >>> 24; +function Xt(e, t) { if (e.length !== 256) throw new Error("Wrong sbox length"); - const n = new Uint32Array(256).map((l, u) => t(e[u])), r = n.map(Ne), o = r.map(Ne), a = o.map(Ne), i = new Uint32Array(256 * 256), c = new Uint32Array(256 * 256), s = new Uint16Array(256 * 256); + const n = new Uint32Array(256).map((l, u) => t(e[u])), r = n.map(Ue), o = r.map(Ue), a = o.map(Ue), i = new Uint32Array(256 * 256), c = new Uint32Array(256 * 256), s = new Uint16Array(256 * 256); for (let l = 0; l < 256; l++) for (let u = 0; u < 256; u++) { const d = l * 256 + u; @@ -3267,28 +3267,28 @@ function Yt(e, t) { } return { sbox: e, sbox2: s, T0: n, T1: r, T2: o, T3: a, T01: i, T23: c }; } -const Xe = /* @__PURE__ */ Yt(De, (e) => le(e, 3) << 24 | e << 16 | e << 8 | le(e, 2)), Qt = /* @__PURE__ */ Yt(Ir, (e) => le(e, 11) << 24 | le(e, 13) << 16 | le(e, 9) << 8 | le(e, 14)), Ur = /* @__PURE__ */ (() => { +const et = /* @__PURE__ */ Xt(je, (e) => le(e, 3) << 24 | e << 16 | e << 8 | le(e, 2)), Zt = /* @__PURE__ */ Xt(Or, (e) => le(e, 11) << 24 | le(e, 13) << 16 | le(e, 9) << 8 | le(e, 14)), qr = /* @__PURE__ */ (() => { const e = new Uint8Array(16); - for (let t = 0, n = 1; t < 16; t++, n = Qe(n)) + for (let t = 0, n = 1; t < 16; t++, n = Ze(n)) e[t] = n; return e; })(); -function Xt(e) { - T(e); +function en(e) { + S(e); const t = e.length; - Sr(e); - const { sbox2: n } = Xe, r = []; - (!H || !Q(e)) && r.push(e = ie(e)); + Ur(e); + const { sbox2: n } = et, r = []; + (!W || !Q(e)) && r.push(e = ie(e)); const o = N(A(e)), a = o.length, i = (s) => F(n, s, s, s, s), c = new Uint32Array(t + 28); c.set(o); for (let s = a; s < c.length; s++) { let l = c[s - 1]; - s % a === 0 ? l = i(Mr(l)) ^ Ur[s / a - 1] : a > 6 && s % a === 4 && (l = i(l)), c[s] = c[s - a] ^ l; + s % a === 0 ? l = i(Dr(l)) ^ qr[s / a - 1] : a > 6 && s % a === 4 && (l = i(l)), c[s] = c[s - a] ^ l; } return se(...r), c; } -function Or(e) { - const t = Xt(e), n = t.slice(), r = t.length, { sbox2: o } = Xe, { T0: a, T1: i, T2: c, T3: s } = Qt; +function zr(e) { + const t = en(e), n = t.slice(), r = t.length, { sbox2: o } = et, { T0: a, T1: i, T2: c, T3: s } = Zt; for (let l = 0; l < r; l += 4) for (let u = 0; u < 4; u++) n[l + u] = t[r - l - 4 + u]; @@ -3305,36 +3305,36 @@ function ee(e, t, n, r, o, a) { function F(e, t, n, r, o) { return e[t & 255 | n & 65280] | e[r >>> 16 & 255 | o >>> 16 & 65280] << 16; } -function it(e, t, n, r, o) { - const { sbox2: a, T01: i, T23: c } = Xe; +function lt(e, t, n, r, o) { + const { sbox2: a, T01: i, T23: c } = et; let s = 0; t ^= e[s++], n ^= e[s++], r ^= e[s++], o ^= e[s++]; const l = e.length / 4 - 2; - for (let p = 0; p < l; p++) { - const f = e[s++] ^ ee(i, c, t, n, r, o), h = e[s++] ^ ee(i, c, n, r, o, t), y = e[s++] ^ ee(i, c, r, o, t, n), m = e[s++] ^ ee(i, c, o, t, n, r); - t = f, n = h, r = y, o = m; + for (let v = 0; v < l; v++) { + const f = e[s++] ^ ee(i, c, t, n, r, o), p = e[s++] ^ ee(i, c, n, r, o, t), h = e[s++] ^ ee(i, c, r, o, t, n), b = e[s++] ^ ee(i, c, o, t, n, r); + t = f, n = p, r = h, o = b; } - const u = e[s++] ^ F(a, t, n, r, o), d = e[s++] ^ F(a, n, r, o, t), v = e[s++] ^ F(a, r, o, t, n), g = e[s++] ^ F(a, o, t, n, r); - return { s0: u, s1: d, s2: v, s3: g }; + const u = e[s++] ^ F(a, t, n, r, o), d = e[s++] ^ F(a, n, r, o, t), g = e[s++] ^ F(a, r, o, t, n), w = e[s++] ^ F(a, o, t, n, r); + return { s0: u, s1: d, s2: g, s3: w }; } -function Dr(e, t, n, r, o) { - const { sbox2: a, T01: i, T23: c } = Qt; +function jr(e, t, n, r, o) { + const { sbox2: a, T01: i, T23: c } = Zt; let s = 0; t ^= e[s++], n ^= e[s++], r ^= e[s++], o ^= e[s++]; const l = e.length / 4 - 2; - for (let p = 0; p < l; p++) { - const f = e[s++] ^ ee(i, c, t, o, r, n), h = e[s++] ^ ee(i, c, n, t, o, r), y = e[s++] ^ ee(i, c, r, n, t, o), m = e[s++] ^ ee(i, c, o, r, n, t); - t = f, n = h, r = y, o = m; + for (let v = 0; v < l; v++) { + const f = e[s++] ^ ee(i, c, t, o, r, n), p = e[s++] ^ ee(i, c, n, t, o, r), h = e[s++] ^ ee(i, c, r, n, t, o), b = e[s++] ^ ee(i, c, o, r, n, t); + t = f, n = p, r = h, o = b; } - const u = e[s++] ^ F(a, t, o, r, n), d = e[s++] ^ F(a, n, t, o, r), v = e[s++] ^ F(a, r, n, t, o), g = e[s++] ^ F(a, o, r, n, t); - return { s0: u, s1: d, s2: v, s3: g }; + const u = e[s++] ^ F(a, t, o, r, n), d = e[s++] ^ F(a, n, t, o, r), g = e[s++] ^ F(a, r, n, t, o), w = e[s++] ^ F(a, o, r, n, t); + return { s0: u, s1: d, s2: g, s3: w }; } -function qr(e) { - if (T(e), e.length % ne !== 0) +function $r(e) { + if (S(e), e.length % ne !== 0) throw new Error("aes-(cbc/ecb).decrypt ciphertext should consist of blocks with size " + ne); } -function zr(e, t, n) { - T(e); +function Br(e, t, n) { + S(e); let r = e.length; const o = r % ne; if (!t && o !== 0) @@ -3343,13 +3343,13 @@ function zr(e, t, n) { let c = ne - o; c || (c = ne), r = r + c; } - n = Ye(r, n), $t(e, n), (!H || !Q(e)) && (e = ie(e)); + n = Xe(r, n), Gt(e, n), (!W || !Q(e)) && (e = ie(e)); const a = A(e); N(a); const i = A(n); return { b: a, o: i, out: n }; } -function jr(e, t) { +function Gr(e, t) { if (!t) return e; const n = e.length; @@ -3366,7 +3366,7 @@ function jr(e, t) { throw new Error("aes/pkcs7: wrong padding"); return e.subarray(0, n - r); } -function $r(e) { +function Vr(e) { const t = new Uint8Array(16), n = A(t); t.set(e); const r = ne - e.length; @@ -3374,66 +3374,66 @@ function $r(e) { t[o] = r; return n; } -const Zt = /* @__PURE__ */ br({ blockSize: 16, nonceLength: 16 }, function(t, n, r = {}) { +const tn = /* @__PURE__ */ Pr({ blockSize: 16, nonceLength: 16 }, function(t, n, r = {}) { const o = !r.disablePadding; return { encrypt(a, i) { - const c = Xt(t), { b: s, o: l, out: u } = zr(a, o, i); + const c = en(t), { b: s, o: l, out: u } = Br(a, o, i); let d = n; - const v = [c]; - (!H || !Q(d)) && v.push(d = ie(d)); - const g = A(d); - N(g); - let p = g[0], f = g[1], h = g[2], y = g[3], m = 0; - for (; m + 4 <= s.length; ) - p ^= s[m + 0], f ^= s[m + 1], h ^= s[m + 2], y ^= s[m + 3], { s0: p, s1: f, s2: h, s3: y } = it(c, p, f, h, y), l[m++] = p, l[m++] = f, l[m++] = h, l[m++] = y; + const g = [c]; + (!W || !Q(d)) && g.push(d = ie(d)); + const w = A(d); + N(w); + let v = w[0], f = w[1], p = w[2], h = w[3], b = 0; + for (; b + 4 <= s.length; ) + v ^= s[b + 0], f ^= s[b + 1], p ^= s[b + 2], h ^= s[b + 3], { s0: v, s1: f, s2: p, s3: h } = lt(c, v, f, p, h), l[b++] = v, l[b++] = f, l[b++] = p, l[b++] = h; if (o) { - const b = $r(a.subarray(m * 4)); - N(b), p ^= b[0], f ^= b[1], h ^= b[2], y ^= b[3], { s0: p, s1: f, s2: h, s3: y } = it(c, p, f, h, y), l[m++] = p, l[m++] = f, l[m++] = h, l[m++] = y; + const m = Vr(a.subarray(b * 4)); + N(m), v ^= m[0], f ^= m[1], p ^= m[2], h ^= m[3], { s0: v, s1: f, s2: p, s3: h } = lt(c, v, f, p, h), l[b++] = v, l[b++] = f, l[b++] = p, l[b++] = h; } - return N(l), se(...v), u; + return N(l), se(...g), u; }, decrypt(a, i) { - qr(a); - const c = Or(t); + $r(a); + const c = zr(t); let s = n; const l = [c]; - (!H || !Q(s)) && l.push(s = ie(s)); + (!W || !Q(s)) && l.push(s = ie(s)); const u = A(s); - N(u), i = Ye(a.length, i), $t(a, i), (!H || !Q(a)) && l.push(a = ie(a)); - const d = A(a), v = A(i); + N(u), i = Xe(a.length, i), Gt(a, i), (!W || !Q(a)) && l.push(a = ie(a)); + const d = A(a), g = A(i); N(d); - let g = u[0], p = u[1], f = u[2], h = u[3]; - for (let y = 0; y + 4 <= d.length; ) { - const m = g, b = p, _ = f, I = h; - g = d[y + 0], p = d[y + 1], f = d[y + 2], h = d[y + 3]; - const { s0: E, s1: P, s2: K, s3: L } = Dr(c, g, p, f, h); - v[y++] = E ^ m, v[y++] = P ^ b, v[y++] = K ^ _, v[y++] = L ^ I; + let w = u[0], v = u[1], f = u[2], p = u[3]; + for (let h = 0; h + 4 <= d.length; ) { + const b = w, m = v, P = f, I = p; + w = d[h + 0], v = d[h + 1], f = d[h + 2], p = d[h + 3]; + const { s0: E, s1: _, s2: K, s3: L } = jr(c, w, v, f, p); + g[h++] = E ^ b, g[h++] = _ ^ m, g[h++] = K ^ P, g[h++] = L ^ I; } - return N(v), se(...l), jr(i, o); + return N(g), se(...l), Gr(i, o); } }; }); -function en(e, t) { - const n = Ce(new Uint8Array([2]), Ge(t)); - return ht.getSharedSecret(e, n, !0).slice(1, 33); +function nn(e, t) { + const n = Te(new Uint8Array([2]), Se(t)); + return vt.getSharedSecret(e, n, !0).slice(1, 33); } -function Br(e, t, n, r) { - const o = en(e, t), a = yt(16), i = Zt(o, a).encrypt(pe(n)); +function Hr(e, t, n, r) { + const o = nn(e, t), a = pt(16), i = tn(o, a).encrypt(pe(n)); return `${de.encode(i)}?iv=${de.encode(a)}`; } -function Gr(e, t, n) { +function Wr(e, t, n) { const r = n.indexOf("?iv="); if (r === -1) throw new Error('NIP-04 payload is missing its "?iv=" suffix.'); - const o = de.decode(n.slice(0, r)), a = de.decode(n.slice(r + 4)), i = en(e, t), c = Zt(i, a).decrypt(o); - return je(c); + const o = de.decode(n.slice(0, r)), a = de.decode(n.slice(r + 4)), i = nn(e, t), c = tn(i, a).decrypt(o); + return Ge(c); } -function Vr(...e) { +function Fr(...e) { for (const t of e) t && t.fill(0); } -class Se { +class _e { privateKey; publicKeyHex; destroyed = !1; @@ -3457,48 +3457,48 @@ class Se { }, this.privateKey); } nip44Encrypt(t, n) { - return this.assertAlive(), Lr(at(this.privateKey, t), n); + return this.assertAlive(), Tr(ct(this.privateKey, t), n); } nip44Decrypt(t, n) { - return this.assertAlive(), Tr(at(this.privateKey, t), n); + return this.assertAlive(), Ir(ct(this.privateKey, t), n); } /** Legacy relative to nip44Encrypt above, but still what a real NIP-07 extension exposes as * window.nostr.nip04.encrypt -- implemented for drop-in parity. */ nip04Encrypt(t, n) { - return this.assertAlive(), Br(this.privateKey, t, n); + return this.assertAlive(), Hr(this.privateKey, t, n); } nip04Decrypt(t, n) { - return this.assertAlive(), Gr(this.privateKey, t, n); + return this.assertAlive(), Wr(this.privateKey, t, n); } /** Best-practical secret wipe (§11.10, §21.4): overwrites the private key buffer in place. */ destroy() { - Vr(this.privateKey), this.destroyed = !0; + Fr(this.privateKey), this.destroyed = !0; } } -const Wr = [ +const Jr = [ "wss://relay.damus.io", "wss://nos.lol", "wss://relay.nostr.band", "wss://nostr.wine", "wss://relay.snort.social" -], Hr = [ +], Yr = [ "wss://purplepag.es", "wss://relay.nostr.band", "wss://nostr-pub.wellorder.net" -], Fr = "bitlogin", Z = "kv", Jr = 1; -function Yr() { +], Qr = "bitlogin", Z = "kv", Xr = 1; +function Zr() { return new Promise((e, t) => { - const n = indexedDB.open(Fr, Jr); + const n = indexedDB.open(Qr, Xr); n.onupgradeneeded = () => { const r = n.result; r.objectStoreNames.contains(Z) || r.createObjectStore(Z); }, n.onsuccess = () => e(n.result), n.onerror = () => t(n.error); }); } -class Qr { +class eo { dbPromise = null; db() { - return this.dbPromise || (this.dbPromise = Yr()), this.dbPromise; + return this.dbPromise || (this.dbPromise = Zr()), this.dbPromise; } async get(t) { const n = await this.db(); @@ -3522,7 +3522,48 @@ class Qr { }); } } -const w = { +const tt = "bitlogin:session:v1"; +async function to(e, t) { + const n = { + everydayPrivateKeyHex: _n(t.everydayPrivateKey), + accountId: t.accountId, + recoveryPublicKey: t.recoveryPublicKey, + activeCredentialEvent: t.activeCredentialEvent, + activeRecoveryEvent: t.activeRecoveryEvent + }; + try { + await e.set(tt, JSON.stringify(n)); + } catch { + } +} +async function no(e) { + let t; + try { + t = await e.get(tt); + } catch { + return null; + } + if (!t) return null; + try { + const n = JSON.parse(t); + return typeof n.everydayPrivateKeyHex != "string" || typeof n.accountId != "string" || typeof n.recoveryPublicKey != "string" || !n.activeCredentialEvent || !n.activeRecoveryEvent ? null : { + everydayPrivateKey: Se(n.everydayPrivateKeyHex), + accountId: n.accountId, + recoveryPublicKey: n.recoveryPublicKey, + activeCredentialEvent: n.activeCredentialEvent, + activeRecoveryEvent: n.activeRecoveryEvent + }; + } catch { + return null; + } +} +async function ro(e) { + try { + await e.delete(tt); + } catch { + } +} +const y = { signer: null, everydayPrivateKey: null, accountId: null, @@ -3531,25 +3572,34 @@ const w = { activeRecoveryEvent: null, pendingRecovery: null }; -let J = [...Wr], Ie = [...Hr]; -const st = new Qr(); +let J = [...Jr], Oe = [...Yr]; +const we = new eo(); function Y() { - if (!w.signer || !w.everydayPrivateKey) + if (!y.signer || !y.everydayPrivateKey) throw new Error("No identity is unlocked in this session."); - return { signer: w.signer, everydayPrivateKey: w.everydayPrivateKey }; + return { signer: y.signer, everydayPrivateKey: y.everydayPrivateKey }; } -function Ee() { - w.signer?.destroy(), w.everydayPrivateKey && w.everydayPrivateKey.fill(0), w.signer = null, w.everydayPrivateKey = null, w.accountId = null, w.recoveryPublicKey = null, w.activeCredentialEvent = null, w.activeRecoveryEvent = null, w.pendingRecovery && (w.pendingRecovery.recoveryPrivateKey.fill(0), w.pendingRecovery.everydayPrivateKey.fill(0), w.pendingRecovery = null); +function ge() { + y.signer?.destroy(), y.everydayPrivateKey && y.everydayPrivateKey.fill(0), y.signer = null, y.everydayPrivateKey = null, y.accountId = null, y.recoveryPublicKey = null, y.activeCredentialEvent = null, y.activeRecoveryEvent = null, y.pendingRecovery && (y.pendingRecovery.recoveryPrivateKey.fill(0), y.pendingRecovery.everydayPrivateKey.fill(0), y.pendingRecovery = null); } -async function Xr(e, t) { +async function Ke() { + !y.everydayPrivateKey || !y.accountId || !y.recoveryPublicKey || !y.activeCredentialEvent || !y.activeRecoveryEvent || await to(we, { + everydayPrivateKey: y.everydayPrivateKey, + accountId: y.accountId, + recoveryPublicKey: y.recoveryPublicKey, + activeCredentialEvent: y.activeCredentialEvent, + activeRecoveryEvent: y.activeRecoveryEvent + }); +} +async function oo(e, t) { switch (e) { case "configure": { const n = t; - return n.vaultRelayUrls?.length && (J = n.vaultRelayUrls), n.discoveryRelayUrls?.length && (Ie = n.discoveryRelayUrls), {}; + return n.vaultRelayUrls?.length && (J = n.vaultRelayUrls), n.discoveryRelayUrls?.length && (Oe = n.discoveryRelayUrls), {}; } case "register": { - const n = t, r = n.importKey ? await lr({ nsecOrHex: n.importKey, loginName: n.loginName, password: n.password, vaultRelayUrls: J }) : await qt({ loginName: n.loginName, password: n.password, vaultRelayUrls: J }); - return Ee(), w.signer = new Se(r.everydayPrivateKey), w.everydayPrivateKey = r.everydayPrivateKey, w.accountId = r.accountId, w.recoveryPublicKey = r.recoveryPublicKey, w.activeCredentialEvent = r.credentialEvent, w.activeRecoveryEvent = r.recoveryEvent, { + const n = t, r = n.importKey ? await yr({ nsecOrHex: n.importKey, loginName: n.loginName, password: n.password, vaultRelayUrls: J }) : await jt({ loginName: n.loginName, password: n.password, vaultRelayUrls: J }); + return ge(), y.signer = new _e(r.everydayPrivateKey), y.everydayPrivateKey = r.everydayPrivateKey, y.accountId = r.accountId, y.recoveryPublicKey = r.recoveryPublicKey, y.activeCredentialEvent = r.credentialEvent, y.activeRecoveryEvent = r.recoveryEvent, await Ke(), { recoveryPhrase: r.recoveryPhrase, everydayPublicKey: r.everydayPublicKey, recoveryPublicKey: r.recoveryPublicKey, @@ -3560,18 +3610,18 @@ async function Xr(e, t) { }; } case "previewImportKey": { - const r = zt(t.nsecOrHex), o = R(r), a = { everydayPublicKey: o, npub: tt(o) }; + const r = $t(t.nsecOrHex), o = R(r), a = { everydayPublicKey: o, npub: ot(o) }; return r.fill(0), a; } case "login": { - const n = t, r = await ur({ + const n = t, r = await hr({ loginName: n.loginName, password: n.password, vaultRelayUrls: J, - store: st, + store: we, acknowledgeRollback: n.acknowledgeRollback }); - return Ee(), w.signer = new Se(r.everydayPrivateKey), w.everydayPrivateKey = r.everydayPrivateKey, w.accountId = r.accountId, w.recoveryPublicKey = r.recoveryPublicKey, w.activeCredentialEvent = r.credentialEvent, w.activeRecoveryEvent = r.recoveryCapsuleEvent, { + return ge(), y.signer = new _e(r.everydayPrivateKey), y.everydayPrivateKey = r.everydayPrivateKey, y.accountId = r.accountId, y.recoveryPublicKey = r.recoveryPublicKey, y.activeCredentialEvent = r.credentialEvent, y.activeRecoveryEvent = r.recoveryCapsuleEvent, await Ke(), { everydayPublicKey: r.everydayPublicKey, accountId: r.accountId, generation: r.generation, @@ -3580,13 +3630,13 @@ async function Xr(e, t) { }; } case "recover": { - const n = t, r = await dr({ + const n = t, r = await pr({ phrase: n.phrase, vaultRelayUrls: J, - discoveryRelayUrls: Ie, + discoveryRelayUrls: Oe, offlineRecoveryCapsuleEvents: n.offlineExportFile?.recovery_capsule_events }); - return Ee(), w.pendingRecovery = r, w.signer = new Se(r.everydayPrivateKey), w.everydayPrivateKey = r.everydayPrivateKey, w.accountId = r.accountId, w.recoveryPublicKey = r.recoveryPublicKey, w.activeRecoveryEvent = r.currentRecoveryEvent, { + return ge(), y.pendingRecovery = r, y.signer = new _e(r.everydayPrivateKey), y.everydayPrivateKey = r.everydayPrivateKey, y.accountId = r.accountId, y.recoveryPublicKey = r.recoveryPublicKey, y.activeRecoveryEvent = r.currentRecoveryEvent, { everydayPublicKey: r.everydayPublicKey, accountId: r.accountId, generalRelays: r.generalRelays, @@ -3596,29 +3646,29 @@ async function Xr(e, t) { } case "completeRecovery": { const n = t; - if (!w.pendingRecovery) throw new Error("No recovery is in progress in this session."); - const r = await yr({ - recovered: w.pendingRecovery, + if (!y.pendingRecovery) throw new Error("No recovery is in progress in this session."); + const r = await vr({ + recovered: y.pendingRecovery, newLoginName: n.newLoginName, newPassword: n.newPassword, vaultRelayUrls: J }); - return w.activeCredentialEvent = r.credentialEvent, w.activeRecoveryEvent = r.refreshedRecoveryEvent, w.pendingRecovery.recoveryPrivateKey.fill(0), w.pendingRecovery = null, { + return y.activeCredentialEvent = r.credentialEvent, y.activeRecoveryEvent = r.refreshedRecoveryEvent, y.pendingRecovery.recoveryPrivateKey.fill(0), y.pendingRecovery = null, await Ke(), { locatorPublicKey: r.locatorPublicKey, credentialEventId: r.credentialEvent.id, refreshedRecoveryEventId: r.refreshedRecoveryEvent.id }; } case "changePassword": { - const n = t, r = await pr({ + const n = t, r = await gr({ loginName: n.loginName, oldPassword: n.oldPassword, newPassword: n.newPassword, vaultRelayUrls: J, - store: st, + store: we, acknowledgeRollback: n.acknowledgeRollback }); - return w.activeCredentialEvent = r.newCredentialEvent, w.recoveryPublicKey = r.recoveryPublicKey, w.activeRecoveryEvent = r.recoveryCapsuleEvent, { + return y.activeCredentialEvent = r.newCredentialEvent, y.recoveryPublicKey = r.recoveryPublicKey, y.activeRecoveryEvent = r.recoveryCapsuleEvent, await Ke(), { newLocatorPublicKey: r.newLocatorPublicKey, newGeneration: r.newGeneration, tombstoneAcknowledgedCount: r.tombstoneAcknowledgedCount, @@ -3627,14 +3677,14 @@ async function Xr(e, t) { } case "publishProfileAndRelayLists": { const n = t, { everydayPrivateKey: r } = Y(); - return Nn({ + return Mn({ everydayPrivateKey: r, name: n.name, about: n.about, picture: n.picture, generalRelays: n.generalRelays, dmRelays: n.dmRelays, - discoveryRelays: Ie + discoveryRelays: Oe }); } case "getPublicKey": { @@ -3663,35 +3713,44 @@ async function Xr(e, t) { } case "exportIdentity": { const { everydayPrivateKey: n, signer: r } = Y(); - return { nsec: _n(n), npub: tt(r.getPublicKey()) }; + return { nsec: Rn(n), npub: ot(r.getPublicKey()) }; } case "buildRecoveryExport": { - if (Y(), !w.recoveryPublicKey || !w.activeRecoveryEvent) + if (Y(), !y.recoveryPublicKey || !y.activeRecoveryEvent) throw new Error("No recovery capsule is known in this session yet."); - return En({ - recoveryPublicKeyHex: w.recoveryPublicKey, + return Kn({ + recoveryPublicKeyHex: y.recoveryPublicKey, vaultRelayUrls: J, - recoveryCapsuleEvents: [w.activeRecoveryEvent], + recoveryCapsuleEvents: [y.activeRecoveryEvent], relayListEvents: [] }); } case "repairReplicas": { - if (!w.activeCredentialEvent || !w.activeRecoveryEvent) + if (!y.activeCredentialEvent || !y.activeRecoveryEvent) throw new Error("No active capsule events are known in this session yet."); - const n = new S(J), r = await Yn(n, w.activeCredentialEvent, w.activeRecoveryEvent); + const n = new T(J), r = await Zn(n, y.activeCredentialEvent, y.activeRecoveryEvent); return n.closeAll(), r; } case "getSessionStatus": - return { unlocked: !!w.signer, everydayPublicKey: w.signer?.getPublicKey() }; + return { unlocked: !!y.signer, everydayPublicKey: y.signer?.getPublicKey() }; + // Called once, right after "configure", before the widget renders its welcome + // screen -- restores whatever persistSession() last cached for this origin, so + // a page reload doesn't ask for the login name + password again. A missing or + // corrupt cache is not an error: it just means the widget falls through to its + // normal welcome screen, exactly like it always has. + case "restoreSession": { + const n = await no(we); + return n ? (ge(), y.signer = new _e(n.everydayPrivateKey), y.everydayPrivateKey = n.everydayPrivateKey, y.accountId = n.accountId, y.recoveryPublicKey = n.recoveryPublicKey, y.activeCredentialEvent = n.activeCredentialEvent, y.activeRecoveryEvent = n.activeRecoveryEvent, { restored: !0, everydayPublicKey: y.signer.getPublicKey(), accountId: n.accountId }) : { restored: !1 }; + } case "logout": - return Ee(), {}; + return ge(), await ro(we), {}; default: throw new Error(`Unknown worker action: ${e}`); } } self.addEventListener("message", (e) => { const { id: t, action: n, payload: r } = e.data; - Xr(n, r).then( + oo(n, r).then( (o) => { const a = { id: t, ok: !0, result: o }; self.postMessage(a); From 18e08bf2cbc2c54de2bc898980ee7921f6f9f271 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Sat, 25 Jul 2026 22:25:50 -0400 Subject: [PATCH 6/8] feat: refine trust controls and harden BitLogin --- components/login-modal.html | 51 +- config/instance-config.js | 14 +- .../docs/community/community-guidelines.md | 4 +- css/tailwind.source.css | 49 + docs/moderation-service-overview.md | 2 +- docs/moderation/README.md | 8 +- docs/moderation/testing.md | 4 +- docs/moderation/web-of-trust-examples.md | 4 +- docs/moderation/web-of-trust.md | 6 +- docs/moderationService-overview.md | 6 +- docs/relay-trust-bootstrap.md | 2 +- js/feedEngine/stages.js | 5 +- js/index.js | 2 + js/services/moderationService.js | 6 +- js/services/trustBootstrap.js | 6 +- js/ui/bitloginModalIntegration.js | 28 +- .../ProfileModerationController.js | 2 +- js/ui/siteUpdateNotice.js | 179 +++ scripts/build-dist.mjs | 2 +- tests/bitlogin-modal-integration.test.mjs | 21 + tests/login-modal-controller.test.mjs | 8 + tests/moderation-stage.test.mjs | 14 +- tests/moderation/hide-thresholds.test.mjs | 3 +- tests/moderation/trust-seeds.test.mjs | 21 +- .../moderation/trusted-report-count.test.mjs | 10 +- tests/site-update-notice.test.mjs | 80 + tests/validate-config.test.mjs | 11 + tests/video-modal-mobile-layout.test.mjs | 26 + vendor/bitlogin.integrity.json | 12 +- ...51Pos3V.js => bitlogin-shared-QIBe5Omw.js} | 5 +- vendor/bitlogin/bitlogin.js | 2 +- vendor/bitlogin/cryptoWorker.js | 1354 +++++++++-------- 32 files changed, 1207 insertions(+), 740 deletions(-) create mode 100644 js/ui/siteUpdateNotice.js create mode 100644 tests/bitlogin-modal-integration.test.mjs create mode 100644 tests/site-update-notice.test.mjs create mode 100644 tests/video-modal-mobile-layout.test.mjs rename vendor/bitlogin/{bitlogin-shared-n51Pos3V.js => bitlogin-shared-QIBe5Omw.js} (99%) diff --git a/components/login-modal.html b/components/login-modal.html index 4bda5112..ad91b182 100644 --- a/components/login-modal.html +++ b/components/login-modal.html @@ -47,8 +47,8 @@ >

- Sign in with a login name and password that works the same on every - BitLogin site, or use one of the other options below. + Use BitLogin for a password-style sign-in, or connect the Nostr + identity you already use. Both keep you in control of your keys.

@@ -63,6 +63,12 @@ id="bitloginMount" class="rounded-3xl border border-border bg-overlay-panel-soft/90 p-4 shadow-lg backdrop-blur-sm sm:p-5" > +
+

BitLogin

+

+ A portable login name and password for BitLogin-enabled sites. +

+
-
- - Other login options - -
-
-

Available methods

-

- Pick the option that matches how you manage your Nostr identity. -

-
+
+
+

+ Nostr sign-in +

+

+ Use your existing Nostr identity +

+

+ Browser extension (NIP-07), remote signer (NIP-46), or your own key. +

+
- -
-
-
+ +
+
diff --git a/config/instance-config.js b/config/instance-config.js index 183a3b90..3734d977 100644 --- a/config/instance-config.js +++ b/config/instance-config.js @@ -287,7 +287,7 @@ export const FEATURE_BITCOIN_CONNECT = true; * Unstable-branch experiment (AGENTS.md §1) -- reset to `false` before this * reaches main until verified end-to-end against production relays. */ -export const FEATURE_BITLOGIN = true; +export const FEATURE_BITLOGIN = false; /** * Whether this deployment offers Blossom (nostr-native blob storage) as a @@ -372,9 +372,9 @@ export const DEFAULT_TRUST_SEED_NPUBS = [ * When a video receives at least this many trusted reports for the active * category (for example, `nudity`), thumbnails render with the blurred preview * by default. Align this value with your moderation policy — the upstream - * deployment targets 1 trusted report before blurring. + * deployment requires 3 trusted reports before blurring. */ -export const DEFAULT_BLUR_THRESHOLD = 1; +export const DEFAULT_BLUR_THRESHOLD = 3; /** * Baseline trusted-report count that blocks autoplay during browsing. @@ -382,9 +382,9 @@ export const DEFAULT_BLUR_THRESHOLD = 1; * Videos that meet or exceed this count will not autoplay in feeds unless the * viewer explicitly opts in. Increase the number for a more permissive stance * or decrease it if you want autoplay to stop sooner. The upstream deployment - * targets 1 trusted report before blocking autoplay. + * requires 3 trusted reports before blocking autoplay. */ -export const DEFAULT_AUTOPLAY_BLOCK_THRESHOLD = 1; +export const DEFAULT_AUTOPLAY_BLOCK_THRESHOLD = 3; /** * Trusted mute threshold that hides creators globally. @@ -401,10 +401,10 @@ export const DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD = 20; * * When a video's trusted report count reaches this number, bitvid treats it as * spam or policy violating content and removes it from default feeds. Align - * the value with your moderation policy — 1 trusted report is the upstream + * the value with your moderation policy — 5 trusted reports is the current * baseline. */ -export const DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD = 1; +export const DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD = 5; /** * Optional theme accent overrides for light and dark mode. diff --git a/content/docs/community/community-guidelines.md b/content/docs/community/community-guidelines.md index e8becbdc..2e8a32e1 100644 --- a/content/docs/community/community-guidelines.md +++ b/content/docs/community/community-guidelines.md @@ -56,7 +56,7 @@ To maintain a functional and ethical platform, the following types of content ar bitvid enforces these guidelines through client-side visibility rules that match the current `moreMenuController` and `userBlocks` behavior: -- **Blurred previews:** When trusted NIP-56 `nudity` reports reach the configured blur threshold (`DEFAULT_BLUR_THRESHOLD` in [`config/instance-config.js`](../config/instance-config.js)), thumbnails render blurred and autoplay previews pause. Every blur includes a “show anyway” override. The upstream configuration currently uses one trusted report, but adjust the export to match your policy. +- **Blurred previews:** When trusted NIP-56 `nudity` reports reach the configured blur threshold (`DEFAULT_BLUR_THRESHOLD` in [`config/instance-config.js`](../config/instance-config.js)), thumbnails render blurred and autoplay previews pause. Every blur includes a “show anyway” override. The hosted default requires three trusted reports. - **Downranked authors:** Muting a creator adds them to your NIP-51 mute list (kind `10000`). Videos from muted creators are deprioritized but still visible with clear badges so you can reverse the action. - **Personal blocks:** Blocking a creator adds them to your private block list (`userBlocks`). Videos from blocked creators are hidden from feeds, carousels, and modals unless you explicitly unblock them. - **Admin hard hides:** Moderator-maintained blacklists (NIP-51 kind `30000`) remove creators from default feeds for everyone using the hosted client. Moderators can still review the content through “show anyway” controls when auditing decisions. @@ -88,7 +88,7 @@ You can manage moderation directly from the client using the existing menus rend ### **5.5 Safety & Moderation thresholds** -- Open your profile avatar → **Profile** → **Safety & Moderation** to adjust blur and autoplay thresholds. Leave a field blank to return to the defaults exported from [`config/instance-config.js`](../config/instance-config.js) (`DEFAULT_BLUR_THRESHOLD`, `DEFAULT_AUTOPLAY_BLOCK_THRESHOLD`, `DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD`, `DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD`). The upstream repo ships example values of 1 trusted report for blur, 1 for autoplay blocking, 20 trusted mutes to hide authors, and 1 trusted spam report to hide videos—tune the config file before deployment if your community needs different baselines. +- Open your profile avatar → **Profile** → **Safety & Moderation** to adjust blur and autoplay thresholds. Leave a field blank to return to the defaults exported from [`config/instance-config.js`](../config/instance-config.js) (`DEFAULT_BLUR_THRESHOLD`, `DEFAULT_AUTOPLAY_BLOCK_THRESHOLD`, `DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD`, `DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD`). The hosted defaults require 3 trusted reports for blur and autoplay blocking, 20 trusted mutes to hide authors, and 5 trusted spam reports to hide videos—tune the config file before deployment if your community needs different baselines. - These controls update immediately and sync with the moderation overlays applied by `moreMenuController` and `VideoCard` components. Operators can edit the config file to change the baseline before deploying their own instance. ## **6. Appeals and Feedback** diff --git a/css/tailwind.source.css b/css/tailwind.source.css index d52ec7d6..162af361 100644 --- a/css/tailwind.source.css +++ b/css/tailwind.source.css @@ -751,6 +751,22 @@ transition-property: border-color, box-shadow, transform; } + .site-update-notice { + @apply fixed bottom-5 right-5 z-50 flex max-w-sm flex-col items-end gap-3; + } + + .site-update-notice__button { + @apply btn focus-ring rounded-full px-4 py-3 text-sm shadow-lg; + } + + .site-update-notice__panel { + @apply w-full rounded-2xl border border-border bg-overlay-panel-soft p-4 text-left text-text shadow-modal backdrop-blur-sm; + } + + .site-update-notice__close { + @apply btn-ghost focus-ring -mr-2 -mt-2 h-9 w-9 rounded-full p-0 text-lg leading-none; + } + .login-option::after { content: ""; position: absolute; @@ -4514,6 +4530,39 @@ -webkit-overflow-scrolling: touch; } + /* The player owns a single scroll region. On mobile, `100vh` can extend + underneath the browser controls/home indicator, which previously made the + final composer action hard to reach. Prefer the dynamic viewport and keep + scrolling on the panel (where the sticky player navigation listens). */ + @media (max-width: calc(theme("screens.lg") - 0.02px)) { + #playerModal { + height: 100vh; + height: 100dvh; + min-height: 100vh; + min-height: 100dvh; + overflow: hidden; + } + + #playerModal > .bv-modal__panel { + block-size: 100%; + min-block-size: 0; + overflow-x: hidden; + overflow-y: auto; + overscroll-behavior: contain; + -webkit-overflow-scrolling: touch; + } + + #playerModal .player-modal__container { + block-size: auto; + min-block-size: 100%; + } + + #playerModal .comment-composer { + padding-bottom: max(var(--space-lg), env(safe-area-inset-bottom)); + scroll-margin-block-end: max(var(--space-lg), env(safe-area-inset-bottom)); + } + } + #modalVideo { width: 100%; aspect-ratio: 16 / 9; diff --git a/docs/moderation-service-overview.md b/docs/moderation-service-overview.md index 1387305e..a0fab83f 100644 --- a/docs/moderation-service-overview.md +++ b/docs/moderation-service-overview.md @@ -9,7 +9,7 @@ Instead of a centralized moderation authority, the service builds a local "trust - **Viewer**: The currently logged-in user. - **Trusted Contacts**: Users followed by the Viewer (Kind 3 contact list). - **Trusted Seeds**: An optional set of admin-defined pubkeys that provide a baseline of trust (e.g., project maintainers). -- **Admin Whitelist/Blacklist**: Hardcoded lists in `accessControl` that override dynamic trust. +- **Admin Whitelist/Blacklist**: Nostr-backed lists in `accessControl`. The creator whitelist controls access only; a blacklist always filters first and suppresses its members' content and reports. ### 2. Trust Aggregation When a piece of content (Event) is reported (Kind 1984), the service aggregates these reports but *only counts reports from trusted users*. diff --git a/docs/moderation/README.md b/docs/moderation/README.md index 833cc494..28a44f5d 100644 --- a/docs/moderation/README.md +++ b/docs/moderation/README.md @@ -6,7 +6,7 @@ bitvid is follow-centric. Your Home feed comes from people you follow (F1). Disc ## Core principles - **Freedom to choose**: User picks filters. Defaults are safe but reversible. - **Explain decisions**: Every blur/hide shows a “why” badge and a “show anyway” control. -- **Blacklist > whitelist**: Admin blacklists and moderation gates always win; whitelists only influence Discovery ranking. +- **Separate access and trust**: The creator whitelist controls site and content access only; moderation uses the viewer's trust graph and admin blacklists. - **Minimal central power**: Admin lists are opt-in; users can unsubscribe. ## Threat model (short) @@ -47,10 +47,10 @@ Thread new moderation behaviors through the same service → stage → app → U - Hide videos when `DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD` trusted contacts mute the author. Cards render with `data-moderation-hidden="true"`, the badge reads `Hidden · {count} trusted mute(s)`, and a "Show anyway" button becomes available alongside a "Restore default moderation" control once the override is active. - Hide videos when `DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD` trusted contacts file spam reports. The badge copy escalates to `Hidden · {count} trusted spam report(s)` and the card stays hidden until the viewer overrides it. - Downrank author when any F1 has them in mute list (10000). -- Opt-in admin lists (30000 with `d=bitvid:admin:*`) can hard-hide content, but whitelist entries no longer bypass moderation gates—they only influence Discovery rankings when a viewer opts into that list. +- Opt-in admin lists (30000 with `d=bitvid:admin:*`) can hard-hide content through the blacklist. The creator whitelist is access-only and does not change web-of-trust blur, autoplay, or hide decisions. - Trust seeds now come from the Super Admin plus every active moderator, so their reports shape anonymous/default visitor filters automatically. The `DEFAULT_TRUST_SEED_NPUBS` export only activates as an emergency fallback when those live lists cannot be loaded (toggle via `FEATURE_TRUST_SEEDS`). -> Default thresholds now come directly from [`config/instance-config.js`](../../config/instance-config.js). Look for the exports `DEFAULT_BLUR_THRESHOLD`, `DEFAULT_AUTOPLAY_BLOCK_THRESHOLD`, `DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD`, and `DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD` to adjust the instance-wide behavior before deploying. The upstream repo currently publishes example values of 1 trusted report for blur, 1 for autoplay blocking, 20 trusted mutes to hide authors, and 1 trusted spam report to hide videos. +> Default thresholds now come directly from [`config/instance-config.js`](../../config/instance-config.js). Look for the exports `DEFAULT_BLUR_THRESHOLD`, `DEFAULT_AUTOPLAY_BLOCK_THRESHOLD`, `DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD`, and `DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD` to adjust the instance-wide behavior before deploying. The hosted defaults require 3 trusted reports for blur and autoplay blocking, 20 trusted mutes to hide authors, and 5 trusted spam reports to hide videos. > Operators can still override these thresholds on a per-viewer basis in **Settings → Safety & Moderation**; leaving the UI fields blank restores the config-driven defaults. Update [`config/instance-config.js`](../../config/instance-config.js) to change what new viewers receive out of the box. @@ -89,7 +89,7 @@ The trusted mute hide helper copy clarifies the escalation path: once the truste ### Runtime flags - `TRUSTED_MUTE_HIDE_THRESHOLD` — numeric default for the trusted mute hide control. Initialized from `DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD` in [`config/instance-config.js`](../../config/instance-config.js); the upstream config sets this to `20`, but adjust the export to match your policy. -- `TRUSTED_SPAM_HIDE_THRESHOLD` — numeric default for the trusted spam hide control. Initialized from `DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD` in [`config/instance-config.js`](../../config/instance-config.js); the upstream config example is `1`. +- `TRUSTED_SPAM_HIDE_THRESHOLD` — numeric default for the trusted spam hide control. Initialized from `DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD` in [`config/instance-config.js`](../../config/instance-config.js); the hosted default is `5`. - `FEATURE_TRUSTED_HIDE_CONTROLS` — boolean toggle to hide/show the new trusted hide controls in the UI (default `true`). ## Files in this folder diff --git a/docs/moderation/testing.md b/docs/moderation/testing.md index 27c85eb2..a8dc368a 100644 --- a/docs/moderation/testing.md +++ b/docs/moderation/testing.md @@ -40,8 +40,8 @@ Create three distinct F1 reporter keys; each sends: Expected: * After 1 report → no blur; autoplay allowed. -* Once reports reach `DEFAULT_AUTOPLAY_BLOCK_THRESHOLD` → autoplay blocked (the upstream configuration currently sets this to 2, but rely on your `config/instance-config.js` value). -* Once reports reach `DEFAULT_BLUR_THRESHOLD` → thumbnail blurred; reason chip shown (upstream example: 1). At the spam threshold (`TRUSTED_SPAM_HIDE_THRESHOLD` sourced from `DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD` in [`config/instance-config.js`](../../config/instance-config.js)) the card hides entirely and the badge copy switches to "Hidden · {count} trusted spam report(s)" until the viewer overrides it. +* Once reports reach `DEFAULT_AUTOPLAY_BLOCK_THRESHOLD` → autoplay blocked (the hosted configuration currently sets this to 3, but rely on your `config/instance-config.js` value). +* Once reports reach `DEFAULT_BLUR_THRESHOLD` → thumbnail blurred; reason chip shown (the hosted default is 3). At the spam threshold (`TRUSTED_SPAM_HIDE_THRESHOLD` sourced from `DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD` in [`config/instance-config.js`](../../config/instance-config.js), currently 5) the card hides entirely and the badge copy switches to "Hidden · {count} trusted spam report(s)" until the viewer overrides it. **Exercises:** [`ModerationService.ingestReportEvent()`](../../js/services/moderationService.js) and [`ModerationService.getTrustedReportSummary()`](../../js/services/moderationService.js) wire through [`createModerationStage()`](../../js/feedEngine/stages.js) so [`bitvidApp.decorateVideoModeration()`](../../js/app.js) can hand the summary to [`VideoCard.refreshModerationUi()`](../../js/ui/components/VideoCard.js). diff --git a/docs/moderation/web-of-trust-examples.md b/docs/moderation/web-of-trust-examples.md index 4111688d..9c677c2c 100644 --- a/docs/moderation/web-of-trust-examples.md +++ b/docs/moderation/web-of-trust-examples.md @@ -54,8 +54,8 @@ list. Blocking still follows the precedence rules above. - A video has 3 `nudity` reports from those seed accounts. Result: -- Thumbnail is blurred if `DEFAULT_BLUR_THRESHOLD` is 1 (met). -- Autoplay is blocked if `DEFAULT_AUTOPLAY_BLOCK_THRESHOLD` is 1 (met). +- Thumbnail is blurred because the 3 trusted reports meet `DEFAULT_BLUR_THRESHOLD`. +- Autoplay is blocked because the 3 trusted reports meet `DEFAULT_AUTOPLAY_BLOCK_THRESHOLD`. - No hard hide unless trusted mute/spam thresholds are met. ### Example 2: Logged-in viewer with personal blocks diff --git a/docs/moderation/web-of-trust.md b/docs/moderation/web-of-trust.md index 01b27884..91a01dae 100644 --- a/docs/moderation/web-of-trust.md +++ b/docs/moderation/web-of-trust.md @@ -20,7 +20,7 @@ - `hideIfTrustedMuteCount(author, category) >= trustedMuteHideThresholds[category] ?? DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD` - `hideIfTrustedSpamReports(event) >= DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD` -Threshold constants are exported from [`config/instance-config.js`](../../config/instance-config.js) so operators can change the defaults without touching moderation code. Inspect the `DEFAULT_BLUR_THRESHOLD`, `DEFAULT_AUTOPLAY_BLOCK_THRESHOLD`, `DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD`, and `DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD` exports to set your policy. The upstream repo includes example values (blur at 1, autoplay block at 1, trusted mute hide at 20, trusted spam hide at 1), but treat those as guidance rather than hard-coded requirements. Per-category trusted mute hide thresholds now live in moderation settings as `trustedMuteHideThresholds` (e.g., `{ spam: 5, nudity: 10 }`), and any category not present falls back to the default trusted mute hide threshold. When evaluating trusted mutes we use the content's report type if available, otherwise we fall back to any category metadata attached to trusted mute list entries. +Threshold constants are exported from [`config/instance-config.js`](../../config/instance-config.js) so operators can change the defaults without touching moderation code. The hosted baseline requires 3 trusted reports to blur or block autoplay, 20 trusted mutes to hide an author, and 5 trusted spam reports to hide a video. Per-category trusted mute hide thresholds now live in moderation settings as `trustedMuteHideThresholds` (e.g., `{ spam: 5, nudity: 10 }`), and any category not present falls back to the default trusted mute hide threshold. When evaluating trusted mutes we use the content's report type if available, otherwise we fall back to any category metadata attached to trusted mute list entries. ### Trusted mute decay window - Trusted mute counts are **time-bounded**: only mute lists updated within the rolling window (currently 60 days) contribute to `trustedMuteCount`. @@ -34,7 +34,7 @@ Threshold constants are exported from [`config/instance-config.js`](../../config ## Admin lists (opt-in) - We recognize curated lists using `30000` events: - `['d','bitvid:admin:blacklist']` → hard-hide when subscribed. -- `['d','bitvid:admin:whitelist']` → improves Discovery ranking when subscribed but no longer bypasses moderation gates. +- `['d','bitvid:admin:whitelist']` → controls creator access only; it does not add a reporter to the trust graph or alter moderation decisions. - `['d','bitvid:admin:editors']` → trusted channel editors. - Users can subscribe/unsubscribe any time. @@ -42,7 +42,7 @@ Threshold constants are exported from [`config/instance-config.js`](../../config 1. **Personal blocks win first.** If a viewer blocks an author or reporter, we ignore their content and reports regardless of admin lists. 2. **Admin blacklist applies next.** Entries on `bitvid:admin:blacklist` are hard-hidden and their reports suppressed before looking at thresholds. -3. **F1 thresholds run last.** Blur/autoplay gating only evaluates trusted-report counts after personal blocks and admin blacklists. Admin whitelists contribute to Discovery ranking when a viewer subscribes, but they never override moderation gates or a viewer block. +3. **F1 thresholds run last.** Blur/autoplay gating evaluates trusted-report counts after personal blocks and admin blacklists. The creator whitelist is not an input to these decisions. ## Pseudocode diff --git a/docs/moderationService-overview.md b/docs/moderationService-overview.md index a5c0beda..6f7eddec 100644 --- a/docs/moderationService-overview.md +++ b/docs/moderationService-overview.md @@ -20,7 +20,7 @@ Instead of a central authority deciding what is "safe," the service aggregates r - Users can report content for reasons like `nudity`, `spam`, `illegal`, etc. - Reports are public events signed by the reporter. - The service subscribes to reports for active content and aggregates them locally. -- **Crucially**: Reports from users *outside* the trusted contact list are ignored (unless whitelisted by admin). +- **Crucially**: Reports from users *outside* the trusted contact list are ignored. Creator whitelist membership does not establish moderation trust. ### NIP-51 Mutes (Kind 10000) - Users can mute other users to hide their content entirely. @@ -28,7 +28,7 @@ Instead of a central authority deciding what is "safe," the service aggregates r - It also supports "Trusted Mutes" where mutes from trusted contacts can influence visibility (though this feature is evolving). ### Admin Overrides -- The service integrates with `AccessControl` to respect an instance-wide **Blacklist** (always hidden) and **Whitelist** (always trusted). +- The service integrates with `AccessControl` to respect an instance-wide **Blacklist** (always hidden). The creator whitelist is access-only and never establishes moderation trust. ## Architecture & Data Flow @@ -89,7 +89,7 @@ await moderationService.submitReport({ ## Invariants - **Trust is Subjective**: A report only counts if the *viewer* follows the reporter. Changing the viewer changes all trust scores immediately. -- **Admin Trumps Trust**: Blacklisted users are ignored even if followed. Whitelisted users are trusted even if not followed. +- **Admin Trumps Trust**: Blacklisted users are ignored even if followed. Creator whitelist membership neither grants nor removes trust. - **Reactive**: The service emits events (`summary`, `contacts`, `user-blocks`) to drive UI updates; it does not directly manipulate the DOM. ## When to Change diff --git a/docs/relay-trust-bootstrap.md b/docs/relay-trust-bootstrap.md index 3436ac35..58fc0ef0 100644 --- a/docs/relay-trust-bootstrap.md +++ b/docs/relay-trust-bootstrap.md @@ -38,7 +38,7 @@ Bootstrapping is intentionally defensive so the UI can initialize quickly: - **Access control hydration**: `waitForAccessControl()` races `accessControl.ensureReady()` against a 3.5s timeout. If hydration times out, bootstrapping logs a dev warning and falls back to defaults. - **Immediate apply**: Seeds are applied immediately after the first hydration attempt, even if the admin list hydration failed, so moderation does not block on relay availability. - **Retry when relays are ready**: If hydration failed, `bootstrapTrustedSeeds()` checks for relay readiness via `nostrService.nostrClient.relays` (and pool existence). If relays become available within 3.5s, it retries admin list hydration once. If relays never become ready, it logs a dev warning and re-applies the current seeds to keep moderation functional. -- **Change listeners**: `accessControl.onWhitelistChange()` and `accessControl.onEditorsChange()` are registered so any subsequent admin/editor updates re-run seed application and recompute moderation summaries. +- **Change listeners**: `accessControl.onEditorsChange()` and `accessControl.onBlacklistChange()` are registered so changes affecting the moderator roster or eligibility re-run seed application and recompute moderation summaries. Creator-whitelist changes do not affect trust seeds. ## Relay list persistence and refresh diff --git a/js/feedEngine/stages.js b/js/feedEngine/stages.js index c9c7a1c1..85c62697 100644 --- a/js/feedEngine/stages.js +++ b/js/feedEngine/stages.js @@ -1135,13 +1135,14 @@ export function createModerationStage({ trustedMuteCategory, ); + const adminWhitelist = adminStatus?.whitelisted === true; + const adminWhitelistBypass = false; + const blockAutoplay = trustedCount >= normalizedAutoplayThreshold || trustedMuted || viewerMuted; const blurFromReports = trustedCount >= normalizedBlurThreshold; let blurThumbnail = blurFromReports; let blurReason = blurThumbnail ? "trusted-report" : ""; - const adminWhitelist = adminStatus?.whitelisted === true; - const adminWhitelistBypass = false; if (!item.metadata || typeof item.metadata !== "object") { item.metadata = {}; diff --git a/js/index.js b/js/index.js index 945636dd..f63970fe 100644 --- a/js/index.js +++ b/js/index.js @@ -54,6 +54,7 @@ import { userBlocks } from "./userBlocks.js"; import { relayManager } from "./relayManager.js"; import createApplication from "./bootstrap.js"; import SearchFilterModal from "./ui/components/SearchFilterModal.js"; +import { initializeSiteUpdateNotice } from "./ui/siteUpdateNotice.js"; import { installTestHarness } from "./testHarness.js"; validateInstanceConfig(); @@ -1320,6 +1321,7 @@ async function bootstrapInterface() { } async function initializeInterface() { + initializeSiteUpdateNotice(); startApplication(); try { diff --git a/js/services/moderationService.js b/js/services/moderationService.js index fbb108f9..355800c7 100644 --- a/js/services/moderationService.js +++ b/js/services/moderationService.js @@ -1910,7 +1910,7 @@ export class ModerationService { * * Algorithm: * 1. Iterate over all reports for the event. - * 2. Check if the reporter is in the `trustedContacts` set (or Admin Whitelist). + * 2. Check if the reporter is in the `trustedContacts` set. * 3. If trusted, increment the count for that report type (e.g., "nudity"). * 4. Update the summary state and emit change events if thresholds are crossed. * @@ -1966,7 +1966,7 @@ export class ModerationService { continue; } - const isTrustedReporter = status.whitelisted || this.trustedContacts.has(reporterHex); + const isTrustedReporter = this.trustedContacts.has(reporterHex); for (const [type, detail] of typeMap.entries()) { let stats = typeStats.get(type); @@ -2092,7 +2092,7 @@ export class ModerationService { continue; } - const isTrustedReporter = status.whitelisted || this.trustedContacts.has(reporterHex); + const isTrustedReporter = this.trustedContacts.has(reporterHex); if (!isTrustedReporter) { continue; } diff --git a/js/services/trustBootstrap.js b/js/services/trustBootstrap.js index 36f1df8e..8b41c3fa 100644 --- a/js/services/trustBootstrap.js +++ b/js/services/trustBootstrap.js @@ -70,7 +70,7 @@ async function waitForAccessControl({ timeoutMs = TRUST_SEED_READY_TIMEOUT_MS } // Cached admin lists (hydrated synchronously from localStorage at // construction) are enough to seed trust — don't stall boot behind the relay - // refresh. If the refresh later changes editors/whitelist/blacklist, the + // refresh. If the refresh later changes editors/blacklist, the // change listeners registered in bootstrapTrustedSeeds re-apply the seeds. if ( typeof accessControl.isHydrated === "function" && @@ -189,10 +189,6 @@ export async function bootstrapTrustedSeeds() { applySeeds(); }; - if (accessControl && typeof accessControl.onWhitelistChange === "function") { - accessControl.onWhitelistChange(applyOnChange); - } - if (accessControl && typeof accessControl.onEditorsChange === "function") { accessControl.onEditorsChange(applyOnChange); } diff --git a/js/ui/bitloginModalIntegration.js b/js/ui/bitloginModalIntegration.js index 99e3391e..eec967eb 100644 --- a/js/ui/bitloginModalIntegration.js +++ b/js/ui/bitloginModalIntegration.js @@ -25,6 +25,7 @@ import { FEATURE_BITLOGIN } from "../constants.js"; // side effect, which retroactively upgrades the already-present // tag in the modal's static HTML once it resolves. const BITLOGIN_WIDGET_BUNDLE_URL = "../../vendor/bitlogin/bitlogin.js"; +const PUBKEY_PATTERN = /^[0-9a-f]{64}$/i; let widgetLoadPromise = null; @@ -52,6 +53,29 @@ function closeLoginModal() { // and each independently call requestLogin() for one real sign-in. const wiredWidgets = new WeakSet(); +/** + * A CustomEvent's detail is not an authentication boundary: any same-origin + * script can dispatch one. Re-read the key from the widget's crypto worker and + * require it to match before BitVid adopts the identity or its signer. + */ +export async function resolveVerifiedBitloginPubkey(widget, claimedPubkey) { + const claimed = typeof claimedPubkey === "string" ? claimedPubkey.trim() : ""; + if (!PUBKEY_PATTERN.test(claimed) || typeof widget?.getPublicKey !== "function") { + return ""; + } + try { + const actual = await widget.getPublicKey(); + const normalizedActual = typeof actual === "string" ? actual.trim() : ""; + if (!PUBKEY_PATTERN.test(normalizedActual) || normalizedActual.toLowerCase() !== claimed.toLowerCase()) { + return ""; + } + return normalizedActual.toLowerCase(); + } catch (error) { + devLogger.warn("[BitLogin] Unable to verify the widget signer:", error); + return ""; + } +} + function attachWidget(app, widget) { const mount = document.getElementById("bitloginMount"); if (!FEATURE_BITLOGIN) { @@ -77,11 +101,13 @@ function attachWidget(app, widget) { }); widget.addEventListener("bitlogin-login", async (event) => { - const pubkey = + const claimedPubkey = event?.detail && typeof event.detail.publicKey === "string" ? event.detail.publicKey.trim() : ""; + const pubkey = await resolveVerifiedBitloginPubkey(widget, claimedPubkey); if (!pubkey) { + userLogger.warn("[BitLogin] Ignored an unverifiable login event."); return; } diff --git a/js/ui/profileModal/ProfileModerationController.js b/js/ui/profileModal/ProfileModerationController.js index 9491cbf2..10322638 100644 --- a/js/ui/profileModal/ProfileModerationController.js +++ b/js/ui/profileModal/ProfileModerationController.js @@ -857,7 +857,7 @@ export class ProfileModerationController { if (isBlocked(status.hex)) { return false; } - return Boolean(status.whitelisted || trustedContacts.has(status.hex)); + return trustedContacts.has(status.hex); }; if (service.trustedMuteLists instanceof Map) { diff --git a/js/ui/siteUpdateNotice.js b/js/ui/siteUpdateNotice.js new file mode 100644 index 00000000..620b12d8 --- /dev/null +++ b/js/ui/siteUpdateNotice.js @@ -0,0 +1,179 @@ +export const SITE_UPDATE_LAST_SEEN_KEY = "bitvid:site-update:last-seen"; +export const SITE_UPDATE_DISMISSED_KEY = "bitvid:site-update:dismissed"; +export const GITHUB_COMMITS_ENDPOINT = + "https://api.github.com/repos/PR0M3TH3AN/bitvid/commits?sha=unstable&per_page=5"; + +export const LATEST_UPDATE_ITEMS = Object.freeze([ + "Clearer Nostr sign-in choices alongside BitLogin.", + "Creator approval is now separate from web-of-trust moderation.", + "Trusted-report thresholds now require broader agreement before action.", +]); + +function readStorage(storage, key) { + try { + return storage?.getItem?.(key) || ""; + } catch { + return ""; + } +} + +function writeStorage(storage, key, value) { + try { + storage?.setItem?.(key, value); + } catch { + // Privacy mode or quota errors should never block the application. + } +} + +function createElement(doc, tagName, className, text = "") { + const element = doc.createElement(tagName); + if (className) { + element.className = className; + } + if (text) { + element.textContent = text; + } + return element; +} + +function renderUpdateItems(doc, list, items) { + list.replaceChildren(); + for (const item of Array.isArray(items) ? items : []) { + if (typeof item === "string" && item.trim()) { + list.appendChild(createElement(doc, "li", "", item.trim())); + } + } +} + +function normalizeCommitItems(payload) { + if (!Array.isArray(payload)) { + return []; + } + + return payload + .map((entry) => { + const sha = typeof entry?.sha === "string" ? entry.sha.slice(0, 8) : ""; + const message = + typeof entry?.commit?.message === "string" + ? entry.commit.message.split("\n")[0].trim() + : ""; + return sha && message ? `${sha} — ${message}` : ""; + }) + .filter(Boolean); +} + +async function loadGitHubCommitItems({ doc, list, fetchImpl, fallbackItems }) { + if (typeof fetchImpl !== "function") { + return; + } + + try { + const response = await fetchImpl(GITHUB_COMMITS_ENDPOINT, { + headers: { Accept: "application/vnd.github+json" }, + }); + if (!response?.ok) { + throw new Error("github-commits-unavailable"); + } + const commits = normalizeCommitItems(await response.json()); + if (!commits.length) { + throw new Error("github-commits-empty"); + } + renderUpdateItems(doc, list, commits); + } catch { + renderUpdateItems(doc, list, fallbackItems); + } +} + +export function initializeSiteUpdateNotice({ + doc = typeof document === "undefined" ? null : document, + storage = typeof window === "undefined" ? null : window.localStorage, + updateItems = LATEST_UPDATE_ITEMS, + fetchImpl = typeof fetch === "function" ? fetch : null, +} = {}) { + if (!doc?.body) { + return { shown: false, reason: "document-unavailable" }; + } + + const versionMarker = doc.querySelector("[data-site-version]"); + const version = versionMarker?.dataset?.siteVersion?.trim() || ""; + const date = versionMarker?.dataset?.siteVersionDate?.trim() || ""; + if (!version) { + return { shown: false, reason: "version-unavailable" }; + } + + const lastSeen = readStorage(storage, SITE_UPDATE_LAST_SEEN_KEY); + const dismissed = readStorage(storage, SITE_UPDATE_DISMISSED_KEY); + if (!lastSeen) { + writeStorage(storage, SITE_UPDATE_LAST_SEEN_KEY, version); + return { shown: false, reason: "first-visit" }; + } + + if (lastSeen === version || dismissed === version) { + return { shown: false, reason: "already-seen" }; + } + + const root = createElement(doc, "aside", "site-update-notice"); + root.setAttribute("aria-label", "Latest site update"); + + const panel = createElement(doc, "section", "site-update-notice__panel hidden"); + panel.id = "siteUpdateNoticePanel"; + panel.setAttribute("role", "status"); + panel.setAttribute("aria-live", "polite"); + + const panelHeader = createElement(doc, "div", "flex items-start justify-between gap-3"); + const title = createElement(doc, "h2", "text-base font-semibold", "Latest update"); + const closeButton = createElement(doc, "button", "site-update-notice__close", "×"); + closeButton.type = "button"; + closeButton.setAttribute("aria-label", "Dismiss this update"); + panelHeader.append(title, closeButton); + + const intro = createElement( + doc, + "p", + "mt-2 text-sm leading-relaxed text-text-muted", + "BitVid has been updated. Here is what changed:", + ); + const list = createElement(doc, "ul", "mt-3 list-disc space-y-1 pl-5 text-sm text-text-muted"); + renderUpdateItems(doc, list, updateItems); + const build = createElement( + doc, + "p", + "mt-3 text-xs font-mono text-text-muted", + `Build ${version}${date ? ` • ${date}` : ""}`, + ); + panel.append(panelHeader, intro, list, build); + + const toggleButton = createElement(doc, "button", "site-update-notice__button", "Latest update"); + toggleButton.type = "button"; + toggleButton.setAttribute("aria-expanded", "false"); + toggleButton.setAttribute("aria-controls", panel.id); + + const setPanelOpen = (open) => { + panel.classList.toggle("hidden", !open); + toggleButton.setAttribute("aria-expanded", String(open)); + }; + + let commitLoadStarted = false; + toggleButton.addEventListener("click", () => { + const nextOpen = panel.classList.contains("hidden"); + setPanelOpen(nextOpen); + if (nextOpen && !commitLoadStarted) { + commitLoadStarted = true; + void loadGitHubCommitItems({ + doc, + list, + fetchImpl, + fallbackItems: updateItems, + }); + } + }); + closeButton.addEventListener("click", () => { + writeStorage(storage, SITE_UPDATE_LAST_SEEN_KEY, version); + writeStorage(storage, SITE_UPDATE_DISMISSED_KEY, version); + root.remove(); + }); + + root.append(panel, toggleButton); + doc.body.appendChild(root); + return { shown: true, version, root }; +} diff --git a/scripts/build-dist.mjs b/scripts/build-dist.mjs index 71651902..6ca5e560 100644 --- a/scripts/build-dist.mjs +++ b/scripts/build-dist.mjs @@ -167,7 +167,7 @@ function injectVersionInfo() { let content = fs.readFileSync(indexHtmlPath, 'utf8'); const versionHtml = ` -
+
v: ${hash.slice(0, 8)} • ${date}
`; diff --git a/tests/bitlogin-modal-integration.test.mjs b/tests/bitlogin-modal-integration.test.mjs new file mode 100644 index 00000000..3e930937 --- /dev/null +++ b/tests/bitlogin-modal-integration.test.mjs @@ -0,0 +1,21 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { resolveVerifiedBitloginPubkey } from "../js/ui/bitloginModalIntegration.js"; + +const PUBKEY = "a".repeat(64); + +test("BitLogin accepts a login identity only when the event and widget worker agree", async () => { + const widget = { getPublicKey: async () => PUBKEY.toUpperCase() }; + assert.equal(await resolveVerifiedBitloginPubkey(widget, PUBKEY), PUBKEY); +}); + +test("BitLogin rejects a forged event pubkey even when its claimed account has access", async () => { + const widget = { getPublicKey: async () => "b".repeat(64) }; + assert.equal(await resolveVerifiedBitloginPubkey(widget, PUBKEY), ""); +}); + +test("BitLogin rejects malformed claims and widgets without a worker-backed public key", async () => { + assert.equal(await resolveVerifiedBitloginPubkey({}, PUBKEY), ""); + assert.equal(await resolveVerifiedBitloginPubkey({ getPublicKey: async () => PUBKEY }, "npub1nope"), ""); +}); diff --git a/tests/login-modal-controller.test.mjs b/tests/login-modal-controller.test.mjs index b465deed..2a98cf90 100644 --- a/tests/login-modal-controller.test.mjs +++ b/tests/login-modal-controller.test.mjs @@ -163,6 +163,14 @@ function createController(options = {}) { }); } +test('login modal keeps Nostr sign-in options visible beside BitLogin', () => { + const nostrSection = container.querySelector('[aria-labelledby="nostrLoginOptionsTitle"]'); + assert.ok(nostrSection, 'Nostr login options should be visible without expanding a disclosure'); + assert.ok(nostrSection.querySelector('[data-login-providers]')); + assert.equal(container.querySelector('details'), null); + assert.match(container.querySelector('#bitloginMount').textContent, /BitLogin/); +}); + test('LoginModalController shows custom error for empty nsec input', async (t) => { const providers = [{ id: 'nsec', label: 'Secret Key', login: async () => {} }]; const authService = { requestLogin: async () => {} }; diff --git a/tests/moderation-stage.test.mjs b/tests/moderation-stage.test.mjs index 538d42ef..cbf83039 100644 --- a/tests/moderation-stage.test.mjs +++ b/tests/moderation-stage.test.mjs @@ -6,7 +6,7 @@ import { DEFAULT_BLUR_THRESHOLD, } from "../js/constants.js"; -test("moderation stage enforces admin lists without whitelist bypass", async () => { +test("moderation stage keeps web-of-trust actions separate from creator access", async () => { const whitelistedHex = "1".repeat(64); const blockedHex = "2".repeat(64); const blacklistedHex = "3".repeat(64); @@ -496,17 +496,22 @@ test("moderation stage propagates whitelist, muters, and threshold updates", asy assert.equal(updatedMuted.video.moderation.blurReason, undefined); }); -test("moderation stage blurs viewer-muted authors", async () => { +test("moderation stage preserves a viewer mute for a whitelisted creator", async () => { const mutedHex = "f".repeat(64); const service = { async refreshViewerFromClient() {}, async setActiveEventIds() {}, getAdminListSnapshot() { - return { whitelist: new Set(), whitelistHex: new Set(), blacklist: new Set(), blacklistHex: new Set() }; + return { + whitelist: new Set([`npub${mutedHex}`]), + whitelistHex: new Set([mutedHex]), + blacklist: new Set(), + blacklistHex: new Set(), + }; }, getAccessControlStatus(identifier) { - return { hex: identifier, whitelisted: false, blacklisted: false }; + return { hex: identifier, whitelisted: identifier === mutedHex, blacklisted: false }; }, getTrustedReportSummary() { return null; @@ -551,6 +556,7 @@ test("moderation stage blurs viewer-muted authors", async () => { assert.equal(entry.video.moderation.viewerMuted, true); assert.equal(entry.metadata.moderation.viewerMuted, true); + assert.equal(entry.video.moderation.adminWhitelistBypass, false); assert.equal(entry.video.moderation.blockAutoplay, true); assert.equal(entry.video.moderation.blurThumbnail, true); assert.equal(entry.video.moderation.blurReason, "viewer-mute"); diff --git a/tests/moderation/hide-thresholds.test.mjs b/tests/moderation/hide-thresholds.test.mjs index a806dead..031b68c3 100644 --- a/tests/moderation/hide-thresholds.test.mjs +++ b/tests/moderation/hide-thresholds.test.mjs @@ -329,7 +329,7 @@ test("moderation stage bypasses hard hides on home feed", async () => { assert.equal(hideWhy.hideBypass, "feed-policy"); }); -test("moderation stage hides admin-whitelisted videos once thresholds fire", async () => { +test("moderation stage hides admin-whitelisted videos when web-of-trust thresholds fire", async () => { const whitelistedHex = hex("f"); const videoId = hex("4"); @@ -400,4 +400,3 @@ test("moderation stage hides admin-whitelisted videos once thresholds fire", asy assert.equal(hideWhy.adminWhitelist, true); assert.equal(hideWhy.trustedReportCount, 6); }); - diff --git a/tests/moderation/trust-seeds.test.mjs b/tests/moderation/trust-seeds.test.mjs index 01e970a5..b3fa0929 100644 --- a/tests/moderation/trust-seeds.test.mjs +++ b/tests/moderation/trust-seeds.test.mjs @@ -224,22 +224,7 @@ test("bootstrap seeds track editor roster and ignore whitelist-only changes", as "initial editor roster should seed trust" ); - const whitelistListener = Array.from(whitelistListeners)[0]; - if (typeof whitelistListener === "function") { - whitelistListener(["npub1whitelistentrywhitelistentrywhitelistentry"]); - } - - assert.equal( - setTrustedSeedsCalls.length, - 2, - "whitelist change should retrigger trusted seed derivation" - ); - const [, whitelistSeeds] = setTrustedSeedsCalls; - assert.deepEqual( - new Set(whitelistSeeds), - new Set(initialSeeds), - "whitelist updates should not introduce new trusted seeds" - ); + assert.equal(whitelistListeners.size, 0, "creator whitelist changes must not affect trust seeds"); currentEditors = [ "npub1newmoderatorexamplemoderatorexamplemoderator", @@ -251,10 +236,10 @@ test("bootstrap seeds track editor roster and ignore whitelist-only changes", as assert.equal( setTrustedSeedsCalls.length, - 3, + 2, "editor change should recompute trusted seed roster" ); - const latestSeeds = setTrustedSeedsCalls[2]; + const latestSeeds = setTrustedSeedsCalls[1]; assert.deepEqual( new Set(latestSeeds), new Set([ADMIN_SUPER_NPUB, ...currentEditors]), diff --git a/tests/moderation/trusted-report-count.test.mjs b/tests/moderation/trusted-report-count.test.mjs index 449f1051..68690623 100644 --- a/tests/moderation/trusted-report-count.test.mjs +++ b/tests/moderation/trusted-report-count.test.mjs @@ -167,7 +167,7 @@ test("blocking and unblocking reporters recomputes trusted summaries", async (t) assert.deepEqual(setActiveCalls, [[eventId], [eventId]]); }); -test("trustedReportCount only counts eligible F1 reporters and admin whitelist", (t) => { +test("trustedReportCount only counts eligible F1 reporters, not creator whitelist entries", (t) => { withMockedNostrTools(t); const eventId = "c".repeat(64); @@ -217,14 +217,14 @@ test("trustedReportCount only counts eligible F1 reporters and admin whitelist", } const summary = service.getTrustedReportSummary(eventId); - assert.equal(summary.totalTrusted, 2); + assert.equal(summary.totalTrusted, 1); assert.equal(summary.types.nudity.total, 3); - assert.equal(summary.types.nudity.trusted, 2); - assert.equal(service.trustedReportCount(eventId, "nudity"), 2); + assert.equal(summary.types.nudity.trusted, 1); + assert.equal(service.trustedReportCount(eventId, "nudity"), 1); const reporters = service.getTrustedReporters(eventId, "nudity"); assert.deepEqual( reporters.map((entry) => entry.pubkey).sort(), - [adminWhitelisted, f1Reporter].sort(), + [f1Reporter], ); }); diff --git a/tests/site-update-notice.test.mjs b/tests/site-update-notice.test.mjs new file mode 100644 index 00000000..62156eec --- /dev/null +++ b/tests/site-update-notice.test.mjs @@ -0,0 +1,80 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { JSDOM } from "jsdom"; + +import { + SITE_UPDATE_DISMISSED_KEY, + SITE_UPDATE_LAST_SEEN_KEY, + initializeSiteUpdateNotice, +} from "../js/ui/siteUpdateNotice.js"; + +function createDocument(version = "deadbeef") { + return new JSDOM( + `
`, + { url: "https://bitvid.example" }, + ); +} + +test("site update notice records a first visit without interrupting it", () => { + const dom = createDocument(); + const result = initializeSiteUpdateNotice({ + doc: dom.window.document, + storage: dom.window.localStorage, + }); + + assert.deepEqual(result, { shown: false, reason: "first-visit" }); + assert.equal(dom.window.localStorage.getItem(SITE_UPDATE_LAST_SEEN_KEY), "deadbeef"); + assert.equal(dom.window.document.querySelector(".site-update-notice"), null); +}); + +test("site update notice shows a dismissible build summary after an update", async () => { + const dom = createDocument("cafebabe"); + dom.window.localStorage.setItem(SITE_UPDATE_LAST_SEEN_KEY, "deadbeef"); + + const result = initializeSiteUpdateNotice({ + doc: dom.window.document, + storage: dom.window.localStorage, + fetchImpl: async () => ({ + ok: true, + json: async () => [ + { sha: "1234567890abcdef", commit: { message: "Make Nostr login obvious\n\nMore detail" } }, + ], + }), + }); + + assert.equal(result.shown, true); + const button = dom.window.document.querySelector(".site-update-notice__button"); + const panel = dom.window.document.querySelector(".site-update-notice__panel"); + assert.ok(button); + assert.ok(panel.classList.contains("hidden")); + + button.click(); + await new Promise((resolve) => setTimeout(resolve, 0)); + assert.equal(button.getAttribute("aria-expanded"), "true"); + assert.equal(panel.classList.contains("hidden"), false); + assert.match(panel.textContent, /cafebabe/); + assert.match(panel.textContent, /12345678 — Make Nostr login obvious/); + + dom.window.document.querySelector("[aria-label='Dismiss this update']").click(); + assert.equal(dom.window.document.querySelector(".site-update-notice"), null); + assert.equal(dom.window.localStorage.getItem(SITE_UPDATE_LAST_SEEN_KEY), "cafebabe"); + assert.equal(dom.window.localStorage.getItem(SITE_UPDATE_DISMISSED_KEY), "cafebabe"); +}); + +test("site update notice keeps its curated summary when GitHub is unavailable", async () => { + const dom = createDocument("cafebabe"); + dom.window.localStorage.setItem(SITE_UPDATE_LAST_SEEN_KEY, "deadbeef"); + initializeSiteUpdateNotice({ + doc: dom.window.document, + storage: dom.window.localStorage, + updateItems: ["Fallback update copy."], + fetchImpl: async () => ({ ok: false }), + }); + + dom.window.document.querySelector(".site-update-notice__button").click(); + await new Promise((resolve) => setTimeout(resolve, 0)); + assert.match( + dom.window.document.querySelector(".site-update-notice__panel").textContent, + /Fallback update copy/, + ); +}); diff --git a/tests/validate-config.test.mjs b/tests/validate-config.test.mjs index a974578b..8452ba8c 100644 --- a/tests/validate-config.test.mjs +++ b/tests/validate-config.test.mjs @@ -1,6 +1,11 @@ import assert from "node:assert/strict"; import { test } from "node:test"; +import { + DEFAULT_AUTOPLAY_BLOCK_THRESHOLD, + DEFAULT_BLUR_THRESHOLD, + DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD, +} from "../config/instance-config.js"; import { validateInstanceConfig } from "../config/validate-config.js"; // Common valid overrides to use as a base @@ -10,6 +15,12 @@ const VALID_OVERRIDES = { PLATFORM_LUD16_OVERRIDE: "test@example.com", }; +test("instance moderation defaults require multiple trusted reports", () => { + assert.equal(DEFAULT_BLUR_THRESHOLD, 3); + assert.equal(DEFAULT_AUTOPLAY_BLOCK_THRESHOLD, 3); + assert.equal(DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD, 5); +}); + test("validateInstanceConfig succeeds with valid configuration", () => { assert.doesNotThrow(() => validateInstanceConfig(VALID_OVERRIDES)); }); diff --git a/tests/video-modal-mobile-layout.test.mjs b/tests/video-modal-mobile-layout.test.mjs new file mode 100644 index 00000000..1a0f220b --- /dev/null +++ b/tests/video-modal-mobile-layout.test.mjs @@ -0,0 +1,26 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; + +const playerStyles = await readFile( + new URL("../css/tailwind.source.css", import.meta.url), + "utf8" +); + +test("mobile player modal uses the dynamic viewport and keeps the comment action above browser chrome", () => { + assert.match( + playerStyles, + /#playerModal\s*\{[^}]*height:\s*100dvh;[^}]*min-height:\s*100dvh;[^}]*overflow:\s*hidden;/s, + "the player root should use the dynamic viewport instead of scrolling beneath browser chrome" + ); + assert.match( + playerStyles, + /#playerModal\s*>\s*\.bv-modal__panel\s*\{[^}]*overflow-y:\s*auto;/s, + "the modal panel should remain the single scroll region used by sticky navigation" + ); + assert.match( + playerStyles, + /#playerModal\s+\.comment-composer\s*\{[^}]*padding-bottom:\s*max\(var\(--space-lg\),\s*env\(safe-area-inset-bottom\)\);/s, + "the comment action row should clear the mobile safe area" + ); +}); diff --git a/vendor/bitlogin.integrity.json b/vendor/bitlogin.integrity.json index 98e083d0..0bb87036 100644 --- a/vendor/bitlogin.integrity.json +++ b/vendor/bitlogin.integrity.json @@ -2,17 +2,17 @@ "package": "@bitlogin/widget", "version": "0.1.0", "files": { - "bitlogin-shared-n51Pos3V.js": { - "sha256": "bf7ee691d519f3f9fed7fe1d0955389ea33d0b6de5282432d4815c82cdffe25e", - "bytes": 288287 + "bitlogin-shared-QIBe5Omw.js": { + "sha256": "7ce4ed0a5c3c7f744e864f55745f7fa78482cbfa19cd30eb551f67a415f55191", + "bytes": 288275 }, "bitlogin.js": { - "sha256": "927646a94a1bb0fff4a0080778f2fc58be60107f1e4e1250369074f980f19ac4", + "sha256": "55312cef3462a2e41043b57bd3c87a80bad4e077dd6e529faf25fa55db4ba7a8", "bytes": 67232 }, "cryptoWorker.js": { - "sha256": "ba5ded0d533f394e02b5a1a66949f6c5c17e3af1ab83db34dc7b4c3c48506a0c", - "bytes": 76304 + "sha256": "49a421e1026ad94243ceb0653f3761820d4e98e19b8fbe469307cd692f9a8489", + "bytes": 79173 } } } diff --git a/vendor/bitlogin/bitlogin-shared-n51Pos3V.js b/vendor/bitlogin/bitlogin-shared-QIBe5Omw.js similarity index 99% rename from vendor/bitlogin/bitlogin-shared-n51Pos3V.js rename to vendor/bitlogin/bitlogin-shared-QIBe5Omw.js index fcc5bc40..8f88c18b 100644 --- a/vendor/bitlogin/bitlogin-shared-n51Pos3V.js +++ b/vendor/bitlogin/bitlogin-shared-QIBe5Omw.js @@ -10807,9 +10807,8 @@ export { $1 as a, U1 as a0, Rt as a1, - jt as a2, - t2 as a3, - W1 as a4, + t2 as a2, + W1 as a3, z1 as b, wn as c, Y1 as d, diff --git a/vendor/bitlogin/bitlogin.js b/vendor/bitlogin/bitlogin.js index 8d25b38e..c2bb0caf 100644 --- a/vendor/bitlogin/bitlogin.js +++ b/vendor/bitlogin/bitlogin.js @@ -1,4 +1,4 @@ -import { r as y, e as w, E as v, p as Q, R as k, i as h, a as d } from "./bitlogin-shared-n51Pos3V.js"; +import { r as y, e as w, E as v, p as Q, R as k, i as h, a as d } from "./bitlogin-shared-QIBe5Omw.js"; const x = 6, p = 64; function u(a = x) { const e = w(), t = []; diff --git a/vendor/bitlogin/cryptoWorker.js b/vendor/bitlogin/cryptoWorker.js index 96f5fe3e..15aa1275 100644 --- a/vendor/bitlogin/cryptoWorker.js +++ b/vendor/bitlogin/cryptoWorker.js @@ -1,58 +1,58 @@ -import { v as ce, g as R, s as X, K as rn, c as on, b as ut, d as $e, f as Be, h as he, u as pe, j as an, k as sn, l as oe, m as ue, n as Ge, S as Ve, o as He, q as Ce, D as ve, t as Le, w as cn, x as ln, y as un, z as dn, A as Ie, B as rt, C as dt, F as yn, G as hn, H as pn, I as Pe, J as yt, L as vn, M as fn, N as Se, O as Ne, P as be, Q as gn, T as ht, U as wn, V as bn, W as mn, X as pt, Y as de, Z as Te, _ as vt, $ as kn, a0 as En, a1 as Pn, a2 as _n, a3 as Kn, a as ot, a4 as Rn } from "./bitlogin-shared-n51Pos3V.js"; -class ye extends Error { +import { v as le, g as x, s as Z, K as ln, c as un, b as vt, d as Be, f as Ge, h as he, u as ve, j as dn, k as yn, l as ae, m as de, n as Ve, S as He, o as We, q as Le, D as fe, t as Ne, w as pn, x as hn, y as vn, z as fn, A as Oe, B as at, C as ft, F as gn, G as wn, H as bn, I as Ke, J as gt, L as mn, M as kn, N as Fe, O as Ie, P as me, Q as En, T as wt, U as Pn, V as _n, W as Kn, X as bt, Y as ye, Z as Te, _ as mt, $ as xn, a0 as Rn, a1 as An, a2 as Cn, a as it, a3 as Sn } from "./bitlogin-shared-QIBe5Omw.js"; +class pe extends Error { reason; constructor(t) { super("Account not found or credentials incorrect."), this.name = "AccountNotFoundError", this.reason = t; } } -class ae extends Error { +class ie extends Error { constructor(t) { super(t), this.name = "RegistrationFailedError"; } } -class ft extends Error { +class kt extends Error { constructor(t = "An account already exists with this login name and password. Sign in instead, or choose different credentials.") { super(t), this.name = "AccountAlreadyExistsError"; } } -class gt extends Error { +class Et extends Error { constructor(t) { super(t), this.name = "RecoveryFailedError"; } } -class wt extends Error { +class Pt extends Error { seenGeneration; capsuleGeneration; constructor(t, n) { super(`This credential capsule reports generation ${n}, but this device has already seen generation ${t}. Refusing to log in with older, possibly-revoked credentials.`), this.name = "RollbackDetectedError", this.seenGeneration = t, this.capsuleGeneration = n; } } -function bt(e, t) { +function _t(e, t) { const n = t ?? Math.floor(Date.now() / 1e3); return e == null ? n : Math.max(n, e + 1); } -const xn = { generation: -1, recoveryGeneration: -1 }; -function mt(e) { +const Ln = { generation: -1, recoveryGeneration: -1 }; +function Kt(e) { return `bitlogin:hwm:${e}`; } -async function We(e, t) { - const n = await e.get(mt(t)); - return n ? JSON.parse(n) : xn; +async function Je(e, t) { + const n = await e.get(Kt(t)); + return n ? JSON.parse(n) : Ln; } -async function kt(e, t, n) { - const r = await We(e, t), o = { +async function xt(e, t, n) { + const r = await Je(e, t), o = { generation: Math.max(r.generation, n.generation ?? -1), recoveryGeneration: Math.max(r.recoveryGeneration, n.recoveryGeneration ?? -1) }; - return await e.set(mt(t), JSON.stringify(o)), o; + return await e.set(Kt(t), JSON.stringify(o)), o; } -function An() { +function Nn() { const e = globalThis.WebSocket; if (!e) throw new Error("No global WebSocket implementation is available in this environment."); return e; } -class Cn { +class In { url; ws = null; connectPromise = null; @@ -66,7 +66,7 @@ class Cn { } async connect() { return this.connectPromise ? this.connectPromise : (this.connectPromise = new Promise((t, n) => { - const r = An(), o = new r(this.url); + const r = Nn(), o = new r(this.url); this.ws = o; const a = setTimeout(() => { n(new Error(`Timed out connecting to relay ${this.url}`)); @@ -101,8 +101,8 @@ class Cn { return; const [r, ...o] = n; if (r === "EVENT") { - const [a, i] = o, c = this.subs.get(a); - c && ce(i) && c.events.push(i); + const [a, i] = o, s = this.subs.get(a); + s && le(i) && s.events.push(i); return; } if (r === "EOSE") { @@ -111,8 +111,8 @@ class Cn { return; } if (r === "OK") { - const [a, i, c] = o; - this.pendingPublishes.get(a)?.({ ok: i, message: c ?? "" }), this.pendingPublishes.delete(a); + const [a, i, s] = o; + this.pendingPublishes.get(a)?.({ ok: i, message: s ?? "" }), this.pendingPublishes.delete(a); return; } if (r === "AUTH") { @@ -124,10 +124,10 @@ class Cn { async respondToAuthChallenge(t) { if (!this.authPrivateKey) return; - const n = R(this.authPrivateKey), r = X({ + const n = x(this.authPrivateKey), r = Z({ pubkey: n, created_at: Math.floor(Date.now() / 1e3), - kind: rn, + kind: ln, tags: [ ["relay", this.url], ["challenge", t] @@ -151,7 +151,7 @@ class Cn { } async queryOnce(t, n = 8e3) { await this.connect(); - const r = on({ + const r = un({ pubkey: "0".repeat(64), created_at: Date.now(), kind: 0, @@ -160,31 +160,31 @@ class Cn { }).slice(0, 16); return new Promise((o) => { const a = [], i = () => { - clearTimeout(c), this.subs.delete(r), this.send(["CLOSE", r]), o(a); - }, c = setTimeout(i, n); + clearTimeout(s), this.subs.delete(r), this.send(["CLOSE", r]), o(a); + }, s = setTimeout(i, n); this.subs.set(r, { events: a, onEose: i }), this.send(["REQ", r, t]); }); } } -class T { +class I { connections = /* @__PURE__ */ new Map(); constructor(t, n = {}) { for (const r of new Set(t)) - this.connections.set(r, new Cn(r, n)); + this.connections.set(r, new In(r, n)); } get relayUrls() { return [...this.connections.keys()]; } /** Queries every configured relay and waits for a quorum of responses (or all timeouts) before returning (§16.2). */ async queryQuorum(t, n = 8e3) { - const r = [...this.connections.entries()], o = await Promise.all(r.map(async ([c, s]) => { + const r = [...this.connections.entries()], o = await Promise.all(r.map(async ([s, c]) => { try { - const l = await s.queryOnce(t, n); - return { relayUrl: c, events: l, responded: !0 }; + const l = await c.queryOnce(t, n); + return { relayUrl: s, events: l, responded: !0 }; } catch (l) { - return { relayUrl: c, events: [], responded: !1, error: l.message }; + return { relayUrl: s, events: [], responded: !1, error: l.message }; } - })), a = o.filter((c) => c.responded).length, i = o.length; + })), a = o.filter((s) => s.responded).length, i = o.length; return { outcomes: o, quorumMet: a >= Math.ceil(i / 2), @@ -209,202 +209,202 @@ class T { t.close(); } } -function te(e) { +function ne(e) { return e.filter((t) => t.result.ok).length; } -function Ln(e, t, n) { - return X({ pubkey: R(e), created_at: n, kind: ut, tags: [], content: JSON.stringify(t) }, e); +function Tn(e, t, n) { + return Z({ pubkey: x(e), created_at: n, kind: vt, tags: [], content: JSON.stringify(t) }, e); } -function Sn(e, t, n) { +function On(e, t, n) { const r = t.map((o) => { const a = ["r", o.url]; return o.read && !o.write && a.push("read"), o.write && !o.read && a.push("write"), a; }); - return X({ pubkey: R(e), created_at: n, kind: $e, tags: r, content: "" }, e); + return Z({ pubkey: x(e), created_at: n, kind: Be, tags: r, content: "" }, e); } -function Nn(e, t, n) { - return X({ - pubkey: R(e), +function Mn(e, t, n) { + return Z({ + pubkey: x(e), created_at: n, - kind: Be, + kind: Ge, tags: t.map((r) => ["relay", r]), content: "" }, e); } -function Tn(e) { +function Un(e) { return e.tags.filter((t) => t[0] === "r" && t[1]).map((t) => t[1]); } -function In(e) { +function Dn(e) { return e.tags.filter((t) => t[0] === "relay" && t[1]).map((t) => t[1]); } -async function Mn(e) { - const t = R(e.everydayPrivateKey), n = Math.floor(Date.now() / 1e3), r = [.../* @__PURE__ */ new Set([...e.generalRelays, ...e.dmRelays, ...e.discoveryRelays])], o = new T(r), [a, i, c] = await Promise.all([ - o.queryQuorum({ authors: [t], kinds: [ut] }), - o.queryQuorum({ authors: [t], kinds: [$e] }), - o.queryQuorum({ authors: [t], kinds: [Be] }) +async function qn(e) { + const t = x(e.everydayPrivateKey), n = Math.floor(Date.now() / 1e3), r = [.../* @__PURE__ */ new Set([...e.generalRelays, ...e.dmRelays, ...e.discoveryRelays])], o = new I(r), [a, i, s] = await Promise.all([ + o.queryQuorum({ authors: [t], kinds: [vt] }), + o.queryQuorum({ authors: [t], kinds: [Be] }), + o.queryQuorum({ authors: [t], kinds: [Ge] }) ]); o.closeAll(); - const s = a.outcomes.some((p) => p.events.length > 0), l = i.outcomes.some((p) => p.events.length > 0), u = c.outcomes.some((p) => p.events.length > 0), d = new T(r); + const c = a.outcomes.some((h) => h.events.length > 0), l = i.outcomes.some((h) => h.events.length > 0), u = s.outcomes.some((h) => h.events.length > 0), d = new I(r); let g = null, w = null, v = null; const f = []; - if (!s && (e.name || e.about || e.picture)) { - const p = Ln(e.everydayPrivateKey, { name: e.name, about: e.about, picture: e.picture }, n); - f.push(d.publishAll(p).then((h) => void (g = h))); + if (!c && (e.name || e.about || e.picture)) { + const h = Tn(e.everydayPrivateKey, { name: e.name, about: e.about, picture: e.picture }, n); + f.push(d.publishAll(h).then((p) => void (g = p))); } if (!l) { - const p = Sn(e.everydayPrivateKey, e.generalRelays.map((h) => ({ url: h, read: !0, write: !0 })), n); - f.push(d.publishAll(p).then((h) => void (w = h))); + const h = On(e.everydayPrivateKey, e.generalRelays.map((p) => ({ url: p, read: !0, write: !0 })), n); + f.push(d.publishAll(h).then((p) => void (w = p))); } if (!u) { - const p = Nn(e.everydayPrivateKey, e.dmRelays, n); - f.push(d.publishAll(p).then((h) => void (v = h))); + const h = Mn(e.everydayPrivateKey, e.dmRelays, n); + f.push(d.publishAll(h).then((p) => void (v = p))); } return await Promise.all(f), d.closeAll(), { - profilePublished: g !== null && te(g) > 0, - relayListAcknowledgedCount: w !== null ? te(w) : 0, - dmRelayListAcknowledgedCount: v !== null ? te(v) : 0, - profileSkippedExisting: s, + profilePublished: g !== null && ne(g) > 0, + relayListAcknowledgedCount: w !== null ? ne(w) : 0, + dmRelayListAcknowledgedCount: v !== null ? ne(v) : 0, + profileSkippedExisting: c, relayListSkippedExisting: l, dmRelayListSkippedExisting: u }; } -async function me(e, t, n) { - const r = n.minAcks ?? 2, o = n.minReadbacks ?? 2, a = await e.publishAll(t, n.timeoutMs), i = te(a), s = (await e.queryQuorum({ kinds: [he], authors: [t.pubkey], "#d": [n.dTag], limit: 5 }, n.timeoutMs)).outcomes.filter((l) => l.events.some((u) => u.id === t.id)).length; +async function ke(e, t, n) { + const r = n.minAcks ?? 2, o = n.minReadbacks ?? 2, a = await e.publishAll(t, n.timeoutMs), i = ne(a), c = (await e.queryQuorum({ kinds: [he], authors: [t.pubkey], "#d": [n.dTag], limit: 5 }, n.timeoutMs)).outcomes.filter((l) => l.events.some((u) => u.id === t.id)).length; return { acknowledgedCount: i, - readbackVerifiedCount: s, - success: i >= r && s >= o + readbackVerifiedCount: c, + success: i >= r && c >= o }; } -function Un(e) { - const t = an(e); +function jn(e) { + const t = dn(e); if (t === void 0) throw new Error("Value is not JSON-serializable for canonicalization."); return t; } -function On(e) { - return pe(Un(e)); +function zn(e) { + return ve(jn(e)); } -const Re = [1024, 2048, 4096], re = 4; -function Dn(e) { - const t = re + e.length, n = Re.find((a) => a >= t); +const Ae = [1024, 2048, 4096], oe = 4; +function $n(e) { + const t = oe + e.length, n = Ae.find((a) => a >= t); if (n === void 0) - throw new Error(`Payload of ${e.length} bytes exceeds the largest padding bucket (${Re[Re.length - 1]} bytes minus ${re}-byte length prefix).`); + throw new Error(`Payload of ${e.length} bytes exceeds the largest padding bucket (${Ae[Ae.length - 1]} bytes minus ${oe}-byte length prefix).`); const r = new Uint8Array(n); - return new DataView(r.buffer).setUint32(0, e.length, !1), r.set(e, re), r; + return new DataView(r.buffer).setUint32(0, e.length, !1), r.set(e, oe), r; } -function qn(e) { - if (!Re.includes(e.length)) +function Bn(e) { + if (!Ae.includes(e.length)) throw new Error(`Padded plaintext length ${e.length} does not match a known bucket.`); const n = new DataView(e.buffer, e.byteOffset, e.byteLength).getUint32(0, !1); - if (n > e.length - re) + if (n > e.length - oe) throw new Error("Declared payload length exceeds the padded bucket size."); - const r = e.slice(re, re + n), o = e.slice(re + n); + const r = e.slice(oe, oe + n), o = e.slice(oe + n); for (const a of o) if (a !== 0) throw new Error("Padding bytes are not all zero; capsule plaintext is malformed."); return r; } -function Fe() { +function Ye() { const e = globalThis.crypto; if (!e || !e.subtle) throw new Error("WebCrypto SubtleCrypto is not available in this environment."); return e; } -async function Et(e) { +async function Rt(e) { if (e.length !== 32) throw new Error("AES-256-GCM key must be exactly 32 bytes."); - return Fe().subtle.importKey("raw", e, "AES-GCM", !1, ["encrypt", "decrypt"]); + return Ye().subtle.importKey("raw", e, "AES-GCM", !1, ["encrypt", "decrypt"]); } -async function zn(e, t, n) { - const r = sn(), o = await Et(e), a = await Fe().subtle.encrypt({ name: "AES-GCM", iv: r, additionalData: n, tagLength: 128 }, o, t); +async function Gn(e, t, n) { + const r = yn(), o = await Rt(e), a = await Ye().subtle.encrypt({ name: "AES-GCM", iv: r, additionalData: n, tagLength: 128 }, o, t); return { nonce: r, ciphertext: new Uint8Array(a) }; } -async function jn(e, t, n, r) { - const o = await Et(e); +async function Vn(e, t, n, r) { + const o = await Rt(e); try { - const a = await Fe().subtle.decrypt({ name: "AES-GCM", iv: t, additionalData: r, tagLength: 128 }, o, n); + const a = await Ye().subtle.decrypt({ name: "AES-GCM", iv: t, additionalData: r, tagLength: 128 }, o, n); return new Uint8Array(a); } catch { throw new Error("AES-256-GCM authentication failed: capsule is corrupted, tampered, or the wrong key was used."); } } -function Pt(e) { - return pe(`bitlogin|password-capsule|v1|${e}|30078|bitlogin:password:v1`); +function At(e) { + return ve(`bitlogin|password-capsule|v1|${e}|30078|bitlogin:password:v1`); } -function _t(e) { - return pe(`bitlogin|recovery-capsule|v1|${e}|30078|bitlogin:recovery:v1`); +function Ct(e) { + return ve(`bitlogin|recovery-capsule|v1|${e}|30078|bitlogin:recovery:v1`); } -async function Kt(e, t, n) { - const r = On(e), o = Dn(r), a = await zn(t, o, n); +async function St(e, t, n) { + const r = zn(e), o = $n(r), a = await Gn(t, o, n); return { version: 1, algorithm: "aes-256-gcm", - nonce: oe(a.nonce), - ciphertext: oe(a.ciphertext) + nonce: ae(a.nonce), + ciphertext: ae(a.ciphertext) }; } -async function Rt(e, t, n) { +async function Lt(e, t, n) { if (e.version !== 1 || e.algorithm !== "aes-256-gcm") throw new Error(`Unsupported capsule envelope version/algorithm: ${e.version}/${e.algorithm}`); - const r = ue(e.nonce), o = ue(e.ciphertext), a = await jn(t, r, o, n), i = qn(a); - return JSON.parse(Ge(i)); + const r = de(e.nonce), o = de(e.ciphertext), a = await Vn(t, r, o, n), i = Bn(a); + return JSON.parse(Ve(i)); } -class Je extends Error { +class Qe extends Error { constructor(t) { super(t), this.name = "CapsuleValidationError"; } } -const $n = /* @__PURE__ */ new Set(["wss:", "ws:"]), Bn = 1e6, Gn = /^[0-9a-f]{64}$/u; -function x(e, t) { +const Hn = /* @__PURE__ */ new Set(["wss:", "ws:"]), Wn = 1e6, Fn = /^[0-9a-f]{64}$/u; +function R(e, t) { if (!e) - throw new Je(t); + throw new Qe(t); } -function xe(e) { - return typeof e == "string" && Gn.test(e); +function Ce(e) { + return typeof e == "string" && Fn.test(e); } -function xt(e) { - x(Array.isArray(e), "vault_relay_hints must be an array (§12.4.7)."); +function Nt(e) { + R(Array.isArray(e), "vault_relay_hints must be an array (§12.4.7)."); for (const t of e) { - x(typeof t == "string", "Each relay hint must be a string (§12.4.7)."); + R(typeof t == "string", "Each relay hint must be a string (§12.4.7)."); let n; try { n = new URL(t); } catch { - throw new Je(`Invalid relay URL: ${String(t)} (§12.4.7)`); + throw new Qe(`Invalid relay URL: ${String(t)} (§12.4.7)`); } - x($n.has(n.protocol), `Relay URL uses a disallowed scheme: ${t} (§12.4.7)`); + R(Hn.has(n.protocol), `Relay URL uses a disallowed scheme: ${t} (§12.4.7)`); } } -function At(e) { - x(typeof e == "string", "account_id must be a string (§12.4.2)."); +function It(e) { + R(typeof e == "string", "account_id must be a string (§12.4.2)."); let t; try { - t = ue(e); + t = de(e); } catch { - throw new Je("account_id is not valid base64url (§12.4.2)."); + throw new Qe("account_id is not valid base64url (§12.4.2)."); } - x(t.length === 16, "account_id must decode to exactly 128 bits (§12.4.2)."); + R(t.length === 16, "account_id must decode to exactly 128 bits (§12.4.2)."); } -function Ct(e, t) { - x(typeof e == "string", "operational_private_key must be a string (§12.4.3)."); - const n = ue(e); - x(n.length === 32, "operational_private_key must be exactly 32 bytes (§12.4.3)."), x(He(n), "operational_private_key is not a valid secp256k1 scalar (§12.4.3)."), x(xe(t), "operational_public_key must be lowercase 64-char hex (§12.4.4)."); - const r = R(n); - x(r === t, "operational_public_key does not match the derived public key (§12.4.4)."); +function Tt(e, t) { + R(typeof e == "string", "operational_private_key must be a string (§12.4.3)."); + const n = de(e); + R(n.length === 32, "operational_private_key must be exactly 32 bytes (§12.4.3)."), R(We(n), "operational_private_key is not a valid secp256k1 scalar (§12.4.3)."), R(Ce(t), "operational_public_key must be lowercase 64-char hex (§12.4.4)."); + const r = x(n); + R(r === t, "operational_public_key does not match the derived public key (§12.4.4)."); } -function Lt(e, t) { - x(Number.isInteger(e) && e >= 0 && e <= Bn, `${t} is out of supported bounds (§12.4.8).`); +function Ot(e, t) { + R(Number.isInteger(e) && e >= 0 && e <= Wn, `${t} is out of supported bounds (§12.4.8).`); } -function Vn(e) { - x(e.schema === Ce, `Unsupported or unknown schema: ${String(e.schema)} (§12.4.1)`), At(e.account_id), Lt(e.generation, "generation"), Ct(e.operational_private_key, e.operational_public_key), x(xe(e.recovery_public_key), "recovery_public_key must be lowercase 64-char hex (§12.4.5)."), xt(e.vault_relay_hints); +function Jn(e) { + R(e.schema === Le, `Unsupported or unknown schema: ${String(e.schema)} (§12.4.1)`), It(e.account_id), Ot(e.generation, "generation"), Tt(e.operational_private_key, e.operational_public_key), R(Ce(e.recovery_public_key), "recovery_public_key must be lowercase 64-char hex (§12.4.5)."), Nt(e.vault_relay_hints); const t = e.recovery_capsule_event; - x(!!t && typeof t == "object", "recovery_capsule_event must be present (§12.4.6)."), x(ce(t), "Embedded recovery_capsule_event has an invalid event id or signature (§12.4.6)."), x(t.pubkey === e.recovery_public_key, "Embedded recovery_capsule_event author does not match recovery_public_key (§12.4.5)."); + R(!!t && typeof t == "object", "recovery_capsule_event must be present (§12.4.6)."), R(le(t), "Embedded recovery_capsule_event has an invalid event id or signature (§12.4.6)."), R(t.pubkey === e.recovery_public_key, "Embedded recovery_capsule_event author does not match recovery_public_key (§12.4.5)."); } -function Hn(e) { - x(e.schema === Ve, `Unsupported or unknown schema: ${String(e.schema)} (§12.4.1)`), At(e.account_id), Lt(e.recovery_generation, "recovery_generation"), x(e.previous_recovery_event_id === null || xe(e.previous_recovery_event_id), "previous_recovery_event_id must be null or lowercase 64-char hex (§12.3)."), Ct(e.operational_private_key, e.operational_public_key), x(xe(e.recovery_public_key), "recovery_public_key must be lowercase 64-char hex."), xt(e.vault_relay_hints); +function Yn(e) { + R(e.schema === He, `Unsupported or unknown schema: ${String(e.schema)} (§12.4.1)`), It(e.account_id), Ot(e.recovery_generation, "recovery_generation"), R(e.previous_recovery_event_id === null || Ce(e.previous_recovery_event_id), "previous_recovery_event_id must be null or lowercase 64-char hex (§12.3)."), Tt(e.operational_private_key, e.operational_public_key), R(Ce(e.recovery_public_key), "recovery_public_key must be lowercase 64-char hex."), Nt(e.vault_relay_hints); } -function Wn(e) { +function Qn(e) { const t = new Map(e.map((r) => [r.recoveryGeneration, r])), n = [...e].sort((r, o) => r.recoveryGeneration - o.recoveryGeneration); for (let r = 1; r < n.length; r++) { const o = n[r], a = t.get(o.recoveryGeneration - 1); @@ -418,99 +418,99 @@ function Wn(e) { } return { consistent: !0 }; } -async function Ye(e) { - const t = R(e.locatorPrivateKey), n = await Kt(e.payload, e.capsuleKey, Pt(t)); - return X({ +async function Xe(e) { + const t = x(e.locatorPrivateKey), n = await St(e.payload, e.capsuleKey, At(t)); + return Z({ pubkey: t, created_at: e.payload.created_at, kind: he, - tags: [["d", ve]], + tags: [["d", fe]], content: JSON.stringify(n) }, e.locatorPrivateKey); } -function Fn(e) { - const t = R(e.oldLocatorPrivateKey); - return X({ +function Xn(e) { + const t = x(e.oldLocatorPrivateKey); + return Z({ pubkey: t, created_at: e.createdAt, kind: he, - tags: [["d", ve]], + tags: [["d", fe]], content: "" }, e.oldLocatorPrivateKey); } -async function Jn(e, t) { - if (!ce(e)) +async function Zn(e, t) { + if (!le(e)) throw new Error("Credential capsule event has an invalid id or signature."); - const n = JSON.parse(e.content), r = await Rt(n, t, Pt(e.pubkey)); - return Vn(r), r; + const n = JSON.parse(e.content), r = await Lt(n, t, At(e.pubkey)); + return Jn(r), r; } -async function St(e) { - const t = R(e.recoveryPrivateKey), n = await Kt(e.payload, e.capsuleKey, _t(t)); - return X({ +async function Mt(e) { + const t = x(e.recoveryPrivateKey), n = await St(e.payload, e.capsuleKey, Ct(t)); + return Z({ pubkey: t, created_at: e.payload.created_at, kind: he, - tags: [["d", Le]], + tags: [["d", Ne]], content: JSON.stringify(n) }, e.recoveryPrivateKey); } -async function Nt(e, t) { - if (!ce(e)) +async function Ut(e, t) { + if (!le(e)) throw new Error("Recovery capsule event has an invalid id or signature."); - const n = JSON.parse(e.content), r = await Rt(n, t, _t(e.pubkey)); - return Hn(r), r; + const n = JSON.parse(e.content), r = await Lt(n, t, Ct(e.pubkey)); + return Yn(r), r; } -function Yn(e) { +function er(e) { const t = /* @__PURE__ */ new Map(); for (const n of e) - ce(n) && t.set(n.id, n); + le(n) && t.set(n.id, n); return [...t.values()].sort((n, r) => r.created_at - n.created_at); } -async function Tt(e, t, n, r, o) { - const a = await e.queryQuorum({ kinds: [he], authors: [t], "#d": [n], limit: 5 }, o), i = a.outcomes.flatMap((g) => g.events), c = Yn(i), s = []; - for (const g of c) +async function Dt(e, t, n, r, o) { + const a = await e.queryQuorum({ kinds: [he], authors: [t], "#d": [n], limit: 5 }, o), i = a.outcomes.flatMap((g) => g.events), s = er(i), c = []; + for (const g of s) try { const w = await r(g); - s.push({ event: g, payload: w }); + c.push({ event: g, payload: w }); } catch (w) { - s.push({ event: g, payload: null, error: w.message }); + c.push({ event: g, payload: null, error: w.message }); } - const l = s.find((g) => g.payload !== null) ?? null, u = a.outcomes.filter((g) => g.responded && g.events.length > 0).map((g) => g.events.slice().sort((w, v) => v.created_at - w.created_at)[0].id), d = new Set(u).size > 1; + const l = c.find((g) => g.payload !== null) ?? null, u = a.outcomes.filter((g) => g.responded && g.events.length > 0).map((g) => g.events.slice().sort((w, v) => v.created_at - w.created_at)[0].id), d = new Set(u).size > 1; return { quorumMet: a.quorumMet, respondedCount: a.respondedCount, totalCount: a.totalCount, - candidates: s, + candidates: c, best: l, relayDisagreement: d }; } -async function Ae(e, t, n, r = 8e3) { - return Tt(e, t, ve, (o) => Jn(o, n), r); +async function Se(e, t, n, r = 8e3) { + return Dt(e, t, fe, (o) => Zn(o, n), r); } -async function Qn(e, t, n, r = 8e3) { - return Tt(e, t, Le, (o) => Nt(o, n), r); +async function tr(e, t, n, r = 8e3) { + return Dt(e, t, Ne, (o) => Ut(o, n), r); } -function Xn(e) { +function nr(e) { const t = e.filter((n) => n.payload !== null); - return t.length < 2 ? { consistent: !0 } : Wn(t.map((n) => ({ + return t.length < 2 ? { consistent: !0 } : Qn(t.map((n) => ({ eventId: n.event.id, recoveryGeneration: n.payload.recovery_generation, previousRecoveryEventId: n.payload.previous_recovery_event_id }))); } -async function Zn(e, t, n, r) { +async function rr(e, t, n, r) { const [o, a] = await Promise.all([ e.publishAll(t, r), e.publishAll(n, r) ]); return { - credentialAcknowledgedCount: te(o), - recoveryAcknowledgedCount: te(a), + credentialAcknowledgedCount: ne(o), + recoveryAcknowledgedCount: ne(a), relaysTried: e.relayUrls.length }; } -const It = `abandon +const qt = `abandon ability able about @@ -2559,167 +2559,167 @@ zero zone zoo`.split(` `); -function er(e, t, n, r) { - un(e); - const o = dn({ dkLen: 32, asyncTick: 10 }, r), { c: a, dkLen: i, asyncTick: c } = o; - if (Ie(a), Ie(i), Ie(c), a < 1) +function or(e, t, n, r) { + vn(e); + const o = fn({ dkLen: 32, asyncTick: 10 }, r), { c: a, dkLen: i, asyncTick: s } = o; + if (Oe(a), Oe(i), Oe(s), a < 1) throw new Error("iterations (c) should be >= 1"); - const s = rt(t), l = rt(n), u = new Uint8Array(i), d = dt.create(e, s), g = d._cloneInto().update(l); - return { c: a, dkLen: i, asyncTick: c, DK: u, PRF: d, PRFSalt: g }; + const c = at(t), l = at(n), u = new Uint8Array(i), d = ft.create(e, c), g = d._cloneInto().update(l); + return { c: a, dkLen: i, asyncTick: s, DK: u, PRF: d, PRFSalt: g }; } -function tr(e, t, n, r, o) { - return e.destroy(), t.destroy(), r && r.destroy(), yn(o), n; +function ar(e, t, n, r, o) { + return e.destroy(), t.destroy(), r && r.destroy(), gn(o), n; } -async function nr(e, t, n, r) { - const { c: o, dkLen: a, asyncTick: i, DK: c, PRF: s, PRFSalt: l } = er(e, t, n, r); +async function ir(e, t, n, r) { + const { c: o, dkLen: a, asyncTick: i, DK: s, PRF: c, PRFSalt: l } = or(e, t, n, r); let u; - const d = new Uint8Array(4), g = cn(d), w = new Uint8Array(s.outputLen); - for (let v = 1, f = 0; f < a; v++, f += s.outputLen) { - const p = c.subarray(f, f + s.outputLen); - g.setInt32(0, v, !1), (u = l._cloneInto(u)).update(d).digestInto(w), p.set(w.subarray(0, p.length)), await ln(o - 1, i, () => { - s._cloneInto(u).update(w).digestInto(w); - for (let h = 0; h < p.length; h++) - p[h] ^= w[h]; + const d = new Uint8Array(4), g = pn(d), w = new Uint8Array(c.outputLen); + for (let v = 1, f = 0; f < a; v++, f += c.outputLen) { + const h = s.subarray(f, f + c.outputLen); + g.setInt32(0, v, !1), (u = l._cloneInto(u)).update(d).digestInto(w), h.set(w.subarray(0, h.length)), await hn(o - 1, i, () => { + c._cloneInto(u).update(w).digestInto(w); + for (let p = 0; p < h.length; p++) + h[p] ^= w[p]; }); } - return tr(s, l, c, u, w); + return ar(c, l, s, u, w); } /*! scure-bip39 - MIT License (c) 2022 Patricio Palladino, Paul Miller (paulmillr.com) */ -const rr = (e) => e[0] === "あいこくしん"; -function Mt(e) { +const sr = (e) => e[0] === "あいこくしん"; +function jt(e) { if (typeof e != "string") throw new TypeError("invalid mnemonic type: " + typeof e); return e.normalize("NFKD"); } -function Ut(e) { - const t = Mt(e), n = t.split(" "); +function zt(e) { + const t = jt(e), n = t.split(" "); if (![12, 15, 18, 21, 24].includes(n.length)) throw new Error("Invalid mnemonic"); return { nfkd: t, words: n }; } -function Ot(e) { - pn(e, 16, 20, 24, 28, 32); +function $t(e) { + bn(e, 16, 20, 24, 28, 32); } -const or = (e) => { +const cr = (e) => { const t = 8 - e.length / 4; - return new Uint8Array([yt(e)[0] >> t << t]); + return new Uint8Array([gt(e)[0] >> t << t]); }; -function Dt(e) { +function Bt(e) { if (!Array.isArray(e) || e.length !== 2048 || typeof e[0] != "string") throw new Error("Wordlist: expected array of 2048 strings"); return e.forEach((t) => { if (typeof t != "string") throw new Error("wordlist: non-string element: " + t); - }), Pe.chain(Pe.checksum(1, or), Pe.radix2(11, !0), Pe.alphabet(e)); + }), Ke.chain(Ke.checksum(1, cr), Ke.radix2(11, !0), Ke.alphabet(e)); } -function ar(e, t) { - const { words: n } = Ut(e), r = Dt(t).decode(n); - return Ot(r), r; +function lr(e, t) { + const { words: n } = zt(e), r = Bt(t).decode(n); + return $t(r), r; } -function ir(e, t) { - return Ot(e), Dt(t).encode(e).join(rr(t) ? " " : " "); +function ur(e, t) { + return $t(e), Bt(t).encode(e).join(sr(t) ? " " : " "); } -function sr(e, t) { +function dr(e, t) { try { - ar(e, t); + lr(e, t); } catch { return !1; } return !0; } -const cr = (e) => Mt("mnemonic" + e); -function lr(e, t = "") { - return nr(hn, Ut(e).nfkd, cr(t), { c: 2048, dkLen: 64 }); +const yr = (e) => jt("mnemonic" + e); +function pr(e, t = "") { + return ir(wn, zt(e).nfkd, yr(t), { c: 2048, dkLen: 64 }); } -function ur(e) { +function hr(e) { if (e.length !== 16) throw new Error("Recovery phrase entropy must be exactly 128 bits (16 bytes)."); - return ir(e, It); + return ur(e, qt); } -function dr(e) { +function vr(e) { try { - return sr(qt(e), It); + return dr(Gt(e), qt); } catch { return !1; } } -function qt(e) { +function Gt(e) { return e.trim().normalize("NFKD").split(/\s+/u).join(" "); } -async function zt(e) { - return lr(qt(e), ""); +async function Vt(e) { + return pr(Gt(e), ""); } -const Qe = vn.id, ke = "aes-256-gcm-v1", Ee = "bitlogin-bip39-hkdf-v1"; -async function jt(e) { - const t = Ne(e.loginName), n = e.now ?? Math.floor(Date.now() / 1e3), { locatorPrivateKey: r, capsuleKey: o } = await be(e.password, t), a = R(r), i = new T(e.vaultRelayUrls, { authPrivateKey: r }); - let c; +const Ze = mn.id, Ee = "aes-256-gcm-v1", Pe = "bitlogin-bip39-hkdf-v1"; +async function Ht(e) { + const t = Ie(e.loginName), n = e.now ?? Math.floor(Date.now() / 1e3), { locatorPrivateKey: r, capsuleKey: o } = await me(e.password, t), a = x(r), i = new I(e.vaultRelayUrls, { authPrivateKey: r }); + let s; try { - c = await Ae(i, a, o, e.timeoutMs); + s = await Se(i, a, o, e.timeoutMs); } finally { i.closeAll(); } - if (!c.quorumMet) - throw new ae("Couldn't verify this login name and password aren't already registered. Please retry, or add more vault relays."); - if (c.candidates.length > 0) - throw new ft(); - const s = ur(gn()), l = await zt(s), { recoveryPrivateKey: u, capsuleKey: d } = ht(l), g = R(u), w = e.everydayPrivateKey !== void 0; - if (w && !He(e.everydayPrivateKey)) - throw new ae("The provided key is not a valid secp256k1 private key."); - const v = w ? e.everydayPrivateKey : wn(), f = R(v), p = oe(bn()), h = { - schema: Ve, - account_id: p, + if (!s.quorumMet) + throw new ie("Couldn't verify this login name and password aren't already registered. Please retry, or add more vault relays."); + if (s.candidates.length > 0) + throw new kt(); + const c = hr(En()), l = await Vt(c), { recoveryPrivateKey: u, capsuleKey: d } = wt(l), g = x(u), w = e.everydayPrivateKey !== void 0; + if (w && !We(e.everydayPrivateKey)) + throw new ie("The provided key is not a valid secp256k1 private key."); + const v = w ? e.everydayPrivateKey : Pn(), f = x(v), h = ae(_n()), p = { + schema: He, + account_id: h, recovery_generation: 0, previous_recovery_event_id: null, - operational_private_key: oe(v), + operational_private_key: ae(v), operational_public_key: f, recovery_public_key: g, created_at: n, vault_relay_hints: e.vaultRelayUrls, - protocol: { capsule_encryption: ke, recovery_derivation: Ee } - }, b = await St({ + protocol: { capsule_encryption: Ee, recovery_derivation: Pe } + }, b = await Mt({ recoveryPrivateKey: u, capsuleKey: d, - payload: h + payload: p }), m = { - schema: Ce, - account_id: p, + schema: Le, + account_id: h, generation: 0, - operational_private_key: oe(v), + operational_private_key: ae(v), operational_public_key: f, recovery_public_key: g, recovery_capsule_event: b, created_at: n, vault_relay_hints: e.vaultRelayUrls, protocol: { - password_kdf: Qe, - capsule_encryption: ke, - recovery_derivation: Ee + password_kdf: Ze, + capsule_encryption: Ee, + recovery_derivation: Pe } - }, P = await Ye({ + }, P = await Xe({ locatorPrivateKey: r, capsuleKey: o, payload: m - }), I = new T(e.vaultRelayUrls, { authPrivateKey: u }), E = await me(I, b, { - dTag: Le, + }), T = new I(e.vaultRelayUrls, { authPrivateKey: u }), E = await ke(T, b, { + dTag: Ne, minAcks: e.minAcknowledgements, timeoutMs: e.timeoutMs }); - I.closeAll(); - const _ = new T(e.vaultRelayUrls, { authPrivateKey: r }), K = await me(_, P, { - dTag: ve, + T.closeAll(); + const _ = new I(e.vaultRelayUrls, { authPrivateKey: r }), K = await ke(_, P, { + dTag: fe, minAcks: e.minAcknowledgements, timeoutMs: e.timeoutMs }); if (_.closeAll(), !E.success || !K.success) - throw new ae("Registration did not reach the required relay acknowledgement and readback quorum. Please retry, or add more vault relays."); + throw new ie("Registration did not reach the required relay acknowledgement and readback quorum. Please retry, or add more vault relays."); return { normalizedLoginName: t, - recoveryPhrase: s, + recoveryPhrase: c, everydayPrivateKey: v, everydayPublicKey: f, recoveryPublicKey: g, locatorPublicKey: a, - accountId: p, + accountId: h, imported: w, credentialEvent: P, recoveryEvent: b, @@ -2727,24 +2727,24 @@ async function jt(e) { recoveryPublish: E }; } -async function yr(e) { - const { nsecOrHex: t, ...n } = e, r = $t(t); - return jt({ ...n, everydayPrivateKey: r }); +async function fr(e) { + const { nsecOrHex: t, ...n } = e, r = Wt(t); + return Ht({ ...n, everydayPrivateKey: r }); } -function $t(e) { +function Wt(e) { const t = e.trim(); let n; if (t.startsWith("nsec1")) - n = fn(t); + n = kn(t); else if (/^[0-9a-fA-F]{64}$/u.test(t)) - n = Se(t.toLowerCase()); + n = Fe(t.toLowerCase()); else - throw new ae("Enter a valid nsec (nsec1…) or a 64-character hex private key."); - if (!He(n)) - throw new ae("The provided key is not a valid secp256k1 private key."); + throw new ie("Enter a valid nsec (nsec1…) or a 64-character hex private key."); + if (!We(n)) + throw new ie("The provided key is not a valid secp256k1 private key."); return n; } -class Bt { +class Ft { map = /* @__PURE__ */ new Map(); async get(t) { return this.map.get(t); @@ -2756,28 +2756,28 @@ class Bt { this.map.delete(t); } } -async function hr(e) { - const t = Ne(e.loginName), { locatorPrivateKey: n, capsuleKey: r } = await be(e.password, t), o = R(n), a = new T(e.vaultRelayUrls, { authPrivateKey: n }); +async function gr(e) { + const t = Ie(e.loginName), { locatorPrivateKey: n, capsuleKey: r } = await me(e.password, t), o = x(n), a = new I(e.vaultRelayUrls, { authPrivateKey: n }); try { - const i = await Ae(a, o, r, e.timeoutMs); + const i = await Se(a, o, r, e.timeoutMs); if (!i.quorumMet) - throw new ye("quorum-not-met"); + throw new pe("quorum-not-met"); if (!i.best) - throw new ye(i.candidates.length > 0 ? "no-valid-candidate" : "no-matching-event"); - const c = i.best.payload, s = e.store ?? new Bt(), l = await We(s, c.operational_public_key), u = c.generation < l.generation; + throw new pe(i.candidates.length > 0 ? "no-valid-candidate" : "no-matching-event"); + const s = i.best.payload, c = e.store ?? new Ft(), l = await Je(c, s.operational_public_key), u = s.generation < l.generation; if (u && !e.acknowledgeRollback) - throw new wt(l.generation, c.generation); - const d = u ? `This device previously saw credential generation ${l.generation}, but the accepted capsule is generation ${c.generation}. Relays may be serving stale data, or an old capsule is being replayed.` : void 0; - await kt(s, c.operational_public_key, { generation: c.generation }); + throw new Pt(l.generation, s.generation); + const d = u ? `This device previously saw credential generation ${l.generation}, but the accepted capsule is generation ${s.generation}. Relays may be serving stale data, or an old capsule is being replayed.` : void 0; + await xt(c, s.operational_public_key, { generation: s.generation }); const g = i.relayDisagreement ? 'Configured relays returned different credential capsules as "latest" for this account. Some relays may be stale, censored, or malicious.' : void 0; return { - everydayPrivateKey: ue(c.operational_private_key), - everydayPublicKey: c.operational_public_key, - recoveryPublicKey: c.recovery_public_key, - accountId: c.account_id, - generation: c.generation, + everydayPrivateKey: de(s.operational_private_key), + everydayPublicKey: s.operational_public_key, + recoveryPublicKey: s.recovery_public_key, + accountId: s.account_id, + generation: s.generation, credentialEvent: i.best.event, - recoveryCapsuleEvent: c.recovery_capsule_event, + recoveryCapsuleEvent: s.recovery_capsule_event, rollbackWarning: d, relayDisagreementWarning: g }; @@ -2785,45 +2785,45 @@ async function hr(e) { a.closeAll(); } } -function at(e) { - return e.filter((t) => ce(t)).sort((t, n) => n.created_at - t.created_at)[0]; +function st(e) { + return e.filter((t) => le(t)).sort((t, n) => n.created_at - t.created_at)[0]; } -async function pr(e) { - if (!dr(e.phrase)) - throw new gt("This does not look like a valid 12-word BitLogin recovery phrase."); - const t = await zt(e.phrase), { recoveryPrivateKey: n, capsuleKey: r } = ht(t), o = R(n), a = new T(e.vaultRelayUrls, { authPrivateKey: n }); +async function wr(e) { + if (!vr(e.phrase)) + throw new Et("This does not look like a valid 12-word BitLogin recovery phrase."); + const t = await Vt(e.phrase), { recoveryPrivateKey: n, capsuleKey: r } = wt(t), o = x(n), a = new I(e.vaultRelayUrls, { authPrivateKey: n }); let i; try { - i = await Qn(a, o, r, e.timeoutMs); + i = await tr(a, o, r, e.timeoutMs); } finally { a.closeAll(); } if (e.offlineRecoveryCapsuleEvents?.length) { const f = []; for (const m of e.offlineRecoveryCapsuleEvents) - if (ce(m)) + if (le(m)) try { - f.push({ event: m, payload: await Nt(m, r) }); + f.push({ event: m, payload: await Ut(m, r) }); } catch (P) { f.push({ event: m, payload: null, error: P.message }); } - const p = /* @__PURE__ */ new Map(); + const h = /* @__PURE__ */ new Map(); for (const m of [...i.candidates, ...f]) - p.set(m.event.id, m); - const h = [...p.values()].sort((m, P) => P.event.created_at - m.event.created_at), b = h.find((m) => m.payload !== null) ?? null; - i = { ...i, candidates: h, best: b, quorumMet: i.quorumMet || b !== null }; + h.set(m.event.id, m); + const p = [...h.values()].sort((m, P) => P.event.created_at - m.event.created_at), b = p.find((m) => m.payload !== null) ?? null; + i = { ...i, candidates: p, best: b, quorumMet: i.quorumMet || b !== null }; } if (!i.quorumMet) - throw new ye("quorum-not-met"); + throw new pe("quorum-not-met"); if (!i.best) - throw new ye(i.candidates.length > 0 ? "no-valid-candidate" : "no-matching-event"); - const c = i.best.payload, s = Xn(i.candidates), l = ue(c.operational_private_key), u = c.operational_public_key, d = [.../* @__PURE__ */ new Set([...e.discoveryRelayUrls, ...c.vault_relay_hints])], g = new T(d); + throw new pe(i.candidates.length > 0 ? "no-valid-candidate" : "no-matching-event"); + const s = i.best.payload, c = nr(i.candidates), l = de(s.operational_private_key), u = s.operational_public_key, d = [.../* @__PURE__ */ new Set([...e.discoveryRelayUrls, ...s.vault_relay_hints])], g = new I(d); let w = [], v = []; try { - const f = await g.queryQuorum({ kinds: [$e], authors: [u], limit: 5 }, e.timeoutMs), p = at(f.outcomes.flatMap((m) => m.events)); - p && (w = Tn(p)); - const h = await g.queryQuorum({ kinds: [Be], authors: [u], limit: 5 }, e.timeoutMs), b = at(h.outcomes.flatMap((m) => m.events)); - b && (v = In(b)); + const f = await g.queryQuorum({ kinds: [Be], authors: [u], limit: 5 }, e.timeoutMs), h = st(f.outcomes.flatMap((m) => m.events)); + h && (w = Un(h)); + const p = await g.queryQuorum({ kinds: [Ge], authors: [u], limit: 5 }, e.timeoutMs), b = st(p.outcomes.flatMap((m) => m.events)); + b && (v = Dn(b)); } finally { g.closeAll(); } @@ -2833,66 +2833,66 @@ async function pr(e) { recoveryPrivateKey: n, recoveryPublicKey: o, recoveryCapsuleKey: r, - accountId: c.account_id, + accountId: s.account_id, currentRecoveryEvent: i.best.event, - currentRecoveryPayload: c, + currentRecoveryPayload: s, generalRelays: w, dmRelays: v, - chainWarning: s.consistent ? void 0 : s.warning + chainWarning: c.consistent ? void 0 : c.warning }; } -async function vr(e) { - const t = e.now ?? Math.floor(Date.now() / 1e3), n = Ne(e.newLoginName), { recovered: r } = e, o = { - schema: Ve, +async function br(e) { + const t = e.now ?? Math.floor(Date.now() / 1e3), n = Ie(e.newLoginName), { recovered: r } = e, o = { + schema: He, account_id: r.accountId, recovery_generation: r.currentRecoveryPayload.recovery_generation + 1, previous_recovery_event_id: r.currentRecoveryEvent.id, - operational_private_key: oe(r.everydayPrivateKey), + operational_private_key: ae(r.everydayPrivateKey), operational_public_key: r.everydayPublicKey, recovery_public_key: r.recoveryPublicKey, - created_at: bt(r.currentRecoveryEvent.created_at, t), + created_at: _t(r.currentRecoveryEvent.created_at, t), vault_relay_hints: e.vaultRelayUrls, - protocol: { capsule_encryption: ke, recovery_derivation: Ee } - }, a = await St({ + protocol: { capsule_encryption: Ee, recovery_derivation: Pe } + }, a = await Mt({ recoveryPrivateKey: r.recoveryPrivateKey, capsuleKey: r.recoveryCapsuleKey, payload: o - }), i = new T(e.vaultRelayUrls, { authPrivateKey: r.recoveryPrivateKey }), c = await me(i, a, { - dTag: Le, + }), i = new I(e.vaultRelayUrls, { authPrivateKey: r.recoveryPrivateKey }), s = await ke(i, a, { + dTag: Ne, minAcks: e.minAcknowledgements, timeoutMs: e.timeoutMs }); i.closeAll(); - const { locatorPrivateKey: s, capsuleKey: l } = await be(e.newPassword, n), u = R(s), d = { - schema: Ce, + const { locatorPrivateKey: c, capsuleKey: l } = await me(e.newPassword, n), u = x(c), d = { + schema: Le, account_id: r.accountId, generation: 0, - operational_private_key: oe(r.everydayPrivateKey), + operational_private_key: ae(r.everydayPrivateKey), operational_public_key: r.everydayPublicKey, recovery_public_key: r.recoveryPublicKey, recovery_capsule_event: a, created_at: t, vault_relay_hints: e.vaultRelayUrls, protocol: { - password_kdf: Qe, - capsule_encryption: ke, - recovery_derivation: Ee + password_kdf: Ze, + capsule_encryption: Ee, + recovery_derivation: Pe } - }, g = await Ye({ locatorPrivateKey: s, capsuleKey: l, payload: d }), w = new T(e.vaultRelayUrls, { authPrivateKey: s }), v = await me(w, g, { - dTag: ve, + }, g = await Xe({ locatorPrivateKey: c, capsuleKey: l, payload: d }), w = new I(e.vaultRelayUrls, { authPrivateKey: c }), v = await ke(w, g, { + dTag: fe, minAcks: e.minAcknowledgements, timeoutMs: e.timeoutMs }); - if (w.closeAll(), !c.success || !v.success) - throw new gt("Could not publish the refreshed recovery and credential capsules to enough relays. Please retry."); - return { normalizedLoginName: n, locatorPublicKey: u, credentialEvent: g, refreshedRecoveryEvent: a, credentialPublish: v, recoveryPublish: c }; + if (w.closeAll(), !s.success || !v.success) + throw new Et("Could not publish the refreshed recovery and credential capsules to enough relays. Please retry."); + return { normalizedLoginName: n, locatorPublicKey: u, credentialEvent: g, refreshedRecoveryEvent: a, credentialPublish: v, recoveryPublish: s }; } -function fr(e) { - const t = R(e.privateKey); - return X({ +function mr(e) { + const t = x(e.privateKey); + return Z({ pubkey: t, created_at: e.createdAt, - kind: mn, + kind: Kn, tags: [ ["e", e.eventIdToDelete], ["k", String(e.deletedEventKind)] @@ -2900,91 +2900,91 @@ function fr(e) { content: e.reason ?? "" }, e.privateKey); } -async function gr(e) { - const t = e.now ?? Math.floor(Date.now() / 1e3), n = Ne(e.loginName), r = await be(e.oldPassword, n), o = R(r.locatorPrivateKey), a = new T(e.vaultRelayUrls, { authPrivateKey: r.locatorPrivateKey }); +async function kr(e) { + const t = e.now ?? Math.floor(Date.now() / 1e3), n = Ie(e.loginName), r = await me(e.oldPassword, n), o = x(r.locatorPrivateKey), a = new I(e.vaultRelayUrls, { authPrivateKey: r.locatorPrivateKey }); let i; try { - i = await Ae(a, o, r.capsuleKey, e.timeoutMs); + i = await Se(a, o, r.capsuleKey, e.timeoutMs); } finally { a.closeAll(); } if (!i.quorumMet) - throw new ye("quorum-not-met"); + throw new pe("quorum-not-met"); if (!i.best) - throw new ye(i.candidates.length > 0 ? "no-valid-candidate" : "no-matching-event"); - const c = i.best.payload, s = i.best.event, l = e.store ?? new Bt(), u = await We(l, c.operational_public_key); - if (c.generation < u.generation && !e.acknowledgeRollback) - throw new wt(u.generation, c.generation); - const d = await be(e.newPassword, n), g = R(d.locatorPrivateKey), w = new T(e.vaultRelayUrls, { authPrivateKey: d.locatorPrivateKey }); + throw new pe(i.candidates.length > 0 ? "no-valid-candidate" : "no-matching-event"); + const s = i.best.payload, c = i.best.event, l = e.store ?? new Ft(), u = await Je(l, s.operational_public_key); + if (s.generation < u.generation && !e.acknowledgeRollback) + throw new Pt(u.generation, s.generation); + const d = await me(e.newPassword, n), g = x(d.locatorPrivateKey), w = new I(e.vaultRelayUrls, { authPrivateKey: d.locatorPrivateKey }); let v; try { - v = await Ae(w, g, d.capsuleKey, e.timeoutMs); + v = await Se(w, g, d.capsuleKey, e.timeoutMs); } finally { w.closeAll(); } if (!v.quorumMet) - throw new ae("Couldn't verify the new password isn't already registered under this login name. Please retry, or add more vault relays."); + throw new ie("Couldn't verify the new password isn't already registered under this login name. Please retry, or add more vault relays."); if (v.candidates.length > 0) - throw new ft("Another account is already registered with this login name and the new password you chose. Pick a different new password."); - const f = c.generation + 1, p = { - schema: Ce, - account_id: c.account_id, + throw new kt("Another account is already registered with this login name and the new password you chose. Pick a different new password."); + const f = s.generation + 1, h = { + schema: Le, + account_id: s.account_id, generation: f, - operational_private_key: c.operational_private_key, - operational_public_key: c.operational_public_key, - recovery_public_key: c.recovery_public_key, - recovery_capsule_event: c.recovery_capsule_event, + operational_private_key: s.operational_private_key, + operational_public_key: s.operational_public_key, + recovery_public_key: s.recovery_public_key, + recovery_capsule_event: s.recovery_capsule_event, created_at: t, vault_relay_hints: e.vaultRelayUrls, protocol: { - password_kdf: Qe, - capsule_encryption: ke, - recovery_derivation: Ee + password_kdf: Ze, + capsule_encryption: Ee, + recovery_derivation: Pe } - }, h = await Ye({ + }, p = await Xe({ locatorPrivateKey: d.locatorPrivateKey, capsuleKey: d.capsuleKey, - payload: p - }), b = new T(e.vaultRelayUrls, { authPrivateKey: d.locatorPrivateKey }), m = await me(b, h, { - dTag: ve, + payload: h + }), b = new I(e.vaultRelayUrls, { authPrivateKey: d.locatorPrivateKey }), m = await ke(b, p, { + dTag: fe, minAcks: e.minAcknowledgements, timeoutMs: e.timeoutMs }); b.closeAll(); - const P = Fn({ + const P = Xn({ oldLocatorPrivateKey: r.locatorPrivateKey, - createdAt: bt(s.created_at, t) - }), I = fr({ + createdAt: _t(c.created_at, t) + }), T = mr({ privateKey: r.locatorPrivateKey, - eventIdToDelete: s.id, + eventIdToDelete: c.id, deletedEventKind: he, createdAt: t - }), E = new T(e.vaultRelayUrls, { authPrivateKey: r.locatorPrivateKey }), [_, K] = await Promise.all([ + }), E = new I(e.vaultRelayUrls, { authPrivateKey: r.locatorPrivateKey }), [_, K] = await Promise.all([ E.publishAll(P, e.timeoutMs), - E.publishAll(I, e.timeoutMs) + E.publishAll(T, e.timeoutMs) ]); if (E.closeAll(), !m.success) - throw new ae("The new credential capsule did not reach the required relay acknowledgement and readback quorum. Please retry."); - return await kt(l, c.operational_public_key, { generation: f }), { + throw new ie("The new credential capsule did not reach the required relay acknowledgement and readback quorum. Please retry."); + return await xt(l, s.operational_public_key, { generation: f }), { normalizedLoginName: n, oldLocatorPublicKey: o, newLocatorPublicKey: g, newGeneration: f, - recoveryPublicKey: c.recovery_public_key, - recoveryCapsuleEvent: c.recovery_capsule_event, - newCredentialEvent: h, + recoveryPublicKey: s.recovery_public_key, + recoveryCapsuleEvent: s.recovery_capsule_event, + newCredentialEvent: p, tombstoneEvent: P, - deletionRequestEvent: I, + deletionRequestEvent: T, newCredentialPublish: m, - tombstoneAcknowledgedCount: te(_), - deletionAcknowledgedCount: te(K) + tombstoneAcknowledgedCount: ne(_), + deletionAcknowledgedCount: ne(K) }; } /*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */ -function wr(e) { +function Er(e) { return e instanceof Uint8Array || ArrayBuffer.isView(e) && e.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in e && e.BYTES_PER_ELEMENT === 1; } -function it(e) { +function ct(e) { if (typeof e != "boolean") throw new TypeError(`boolean expected, not ${e}`); } @@ -2994,10 +2994,10 @@ function Me(e) { if (!Number.isSafeInteger(e) || e < 0) throw new RangeError("positive integer expected, got " + e); } -function S(e, t, n = "") { - const r = wr(e), o = e?.length, a = t !== void 0; +function L(e, t, n = "") { + const r = Er(e), o = e?.length, a = t !== void 0; if (!r || a && o !== t) { - const i = n && `"${n}" `, c = a ? ` of length ${t}` : "", s = r ? `length=${o}` : `type=${typeof e}`, l = i + "expected Uint8Array" + c + ", got " + s; + const i = n && `"${n}" `, s = a ? ` of length ${t}` : "", c = r ? `length=${o}` : `type=${typeof e}`, l = i + "expected Uint8Array" + s + ", got " + c; throw r ? new RangeError(l) : new TypeError(l); } return e; @@ -3005,248 +3005,248 @@ function S(e, t, n = "") { function A(e) { return new Uint32Array(e.buffer, e.byteOffset, Math.floor(e.byteLength / 4)); } -function se(...e) { +function ce(...e) { for (let t = 0; t < e.length; t++) e[t].fill(0); } -const W = new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68, br = (e) => e << 24 & 4278190080 | e << 8 & 16711680 | e >>> 8 & 65280 | e >>> 24 & 255, mr = (e) => { +const W = new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68, Pr = (e) => e << 24 & 4278190080 | e << 8 & 16711680 | e >>> 8 & 65280 | e >>> 24 & 255, _r = (e) => { for (let t = 0; t < e.length; t++) - e[t] = br(e[t]); + e[t] = Pr(e[t]); return e; -}, N = W ? (e) => e : mr; -function kr(e, t) { +}, N = W ? (e) => e : _r; +function Kr(e, t) { return !e.byteLength || !t.byteLength ? !1 : e.buffer === t.buffer && // best we can do, may fail with an obscure Proxy e.byteOffset < t.byteOffset + t.byteLength && // a starts before b end t.byteOffset < e.byteOffset + e.byteLength; } -function Gt(e, t) { - if (kr(e, t) && e.byteOffset < t.byteOffset) +function Jt(e, t) { + if (Kr(e, t) && e.byteOffset < t.byteOffset) throw new Error("complex overlap of input and output is not supported"); } -function Er(e, t) { +function xr(e, t) { if (t == null || typeof t != "object") throw new Error("options must be defined"); return Object.assign(e, t); } -const Pr = /* @__NO_SIDE_EFFECTS__ */ (e, t) => { +const Rr = /* @__NO_SIDE_EFFECTS__ */ (e, t) => { function n(r, ...o) { - if (S(r, void 0, "key"), e.nonceLength !== void 0) { + if (L(r, void 0, "key"), e.nonceLength !== void 0) { const u = o[0]; - S(u, e.varSizeNonce ? void 0 : e.nonceLength, "nonce"); + L(u, e.varSizeNonce ? void 0 : e.nonceLength, "nonce"); } const a = e.tagLength; - a && o[1] !== void 0 && S(o[1], void 0, "AAD"); - const i = t(r, ...o), c = (u, d) => { + a && o[1] !== void 0 && L(o[1], void 0, "AAD"); + const i = t(r, ...o), s = (u, d) => { if (d !== void 0) { if (u !== 2) throw new Error("cipher output not supported"); - S(d, void 0, "output"); + L(d, void 0, "output"); } }; - let s = !1; + let c = !1; return { encrypt(u, d) { - if (s) + if (c) throw new Error("cannot encrypt() twice with same key + nonce"); - return s = !0, S(u), c(i.encrypt.length, d), i.encrypt(u, d); + return c = !0, L(u), s(i.encrypt.length, d), i.encrypt(u, d); }, decrypt(u, d) { - if (S(u), a && u.length < a) + if (L(u), a && u.length < a) throw new Error('"ciphertext" expected length bigger than tagLength=' + a); - return c(i.decrypt.length, d), i.decrypt(u, d); + return s(i.decrypt.length, d), i.decrypt(u, d); } }; } return Object.assign(n, e), n; }; -function Xe(e, t, n = !0) { +function et(e, t, n = !0) { if (t === void 0) return new Uint8Array(e); - if (S(t, void 0, "output"), t.length !== e) + if (L(t, void 0, "output"), t.length !== e) throw new Error('"output" expected Uint8Array of length ' + e + ", got: " + t.length); - if (n && !Q(t)) + if (n && !X(t)) throw new Error("invalid output, must be aligned"); return t; } -function Q(e) { +function X(e) { return e.byteOffset % 4 === 0; } -function ie(e) { - return Uint8Array.from(S(e)); +function se(e) { + return Uint8Array.from(L(e)); } -const Vt = (e) => Uint8Array.from(e.split(""), (t) => t.charCodeAt(0)), _r = N(A(Vt("expand 16-byte k"))), Kr = N(A(Vt("expand 32-byte k"))); +const Yt = (e) => Uint8Array.from(e.split(""), (t) => t.charCodeAt(0)), Ar = N(A(Yt("expand 16-byte k"))), Cr = N(A(Yt("expand 32-byte k"))); function k(e, t) { return e << t | e >>> 32 - t; } -const fe = 64, Rr = 16, De = 2 ** 32 - 1, st = /* @__PURE__ */ Uint32Array.of(); -function xr(e, t, n, r, o, a, i, c) { - const s = o.length, l = new Uint8Array(fe), u = A(l), d = W && Q(o) && Q(a), g = d ? A(o) : st, w = d ? A(a) : st; +const ge = 64, Sr = 16, qe = 2 ** 32 - 1, lt = /* @__PURE__ */ Uint32Array.of(); +function Lr(e, t, n, r, o, a, i, s) { + const c = o.length, l = new Uint8Array(ge), u = A(l), d = W && X(o) && X(a), g = d ? A(o) : lt, w = d ? A(a) : lt; if (!W) { - for (let v = 0; v < s; i++) { - if (e(t, n, r, u, i, c), N(u), i >= De) + for (let v = 0; v < c; i++) { + if (e(t, n, r, u, i, s), N(u), i >= qe) throw new Error("arx: counter overflow"); - const f = Math.min(fe, s - v); - for (let p = 0, h; p < f; p++) - h = v + p, a[h] = o[h] ^ l[p]; + const f = Math.min(ge, c - v); + for (let h = 0, p; h < f; h++) + p = v + h, a[p] = o[p] ^ l[h]; v += f; } return; } - for (let v = 0; v < s; i++) { - if (e(t, n, r, u, i, c), i >= De) + for (let v = 0; v < c; i++) { + if (e(t, n, r, u, i, s), i >= qe) throw new Error("arx: counter overflow"); - const f = Math.min(fe, s - v); - if (d && f === fe) { - const p = v / 4; + const f = Math.min(ge, c - v); + if (d && f === ge) { + const h = v / 4; if (v % 4 !== 0) throw new Error("arx: invalid block position"); - for (let h = 0, b; h < Rr; h++) - b = p + h, w[b] = g[b] ^ u[h]; - v += fe; + for (let p = 0, b; p < Sr; p++) + b = h + p, w[b] = g[b] ^ u[p]; + v += ge; continue; } - for (let p = 0, h; p < f; p++) - h = v + p, a[h] = o[h] ^ l[p]; + for (let h = 0, p; h < f; h++) + p = v + h, a[p] = o[p] ^ l[h]; v += f; } } -function Ar(e, t) { - const { allowShortKeys: n, extendNonceFn: r, counterLength: o, counterRight: a, rounds: i } = Er({ allowShortKeys: !1, counterLength: 8, counterRight: !1, rounds: 20 }, t); +function Nr(e, t) { + const { allowShortKeys: n, extendNonceFn: r, counterLength: o, counterRight: a, rounds: i } = xr({ allowShortKeys: !1, counterLength: 8, counterRight: !1, rounds: 20 }, t); if (typeof e != "function") throw new Error("core must be a function"); - return Me(o), Me(i), it(a), it(n), (c, s, l, u, d = 0) => { - S(c, void 0, "key"), S(s, void 0, "nonce"), S(l, void 0, "data"); + return Me(o), Me(i), ct(a), ct(n), (s, c, l, u, d = 0) => { + L(s, void 0, "key"), L(c, void 0, "nonce"), L(l, void 0, "data"); const g = l.length; - if (u = Xe(g, u, !1), Me(d), d < 0 || d >= De) + if (u = et(g, u, !1), Me(d), d < 0 || d >= qe) throw new Error("arx: counter overflow"); const w = []; - let v = c.length, f, p; + let v = s.length, f, h; if (v === 32) - w.push(f = ie(c)), p = Kr; + w.push(f = se(s)), h = Cr; else if (v === 16 && n) - f = new Uint8Array(32), f.set(c), f.set(c, 16), p = _r, w.push(f); + f = new Uint8Array(32), f.set(s), f.set(s, 16), h = Ar, w.push(f); else - throw S(c, 32, "arx key"), new Error("invalid key size"); - (!W || !Q(s)) && w.push(s = ie(s)); - let h = A(f); + throw L(s, 32, "arx key"), new Error("invalid key size"); + (!W || !X(c)) && w.push(c = se(c)); + let p = A(f); if (r) { - if (s.length !== 24) + if (c.length !== 24) throw new Error("arx: extended nonce must be 24 bytes"); - const P = s.subarray(0, 16); + const P = c.subarray(0, 16); if (W) - r(p, h, A(P), h); + r(h, p, A(P), p); else { - const I = N(Uint32Array.from(p)); - r(I, h, A(P), h), se(I), N(h); + const T = N(Uint32Array.from(h)); + r(T, p, A(P), p), ce(T), N(p); } - s = s.subarray(16); - } else W || N(h); + c = c.subarray(16); + } else W || N(p); const b = 16 - o; - if (b !== s.length) + if (b !== c.length) throw new Error(`arx: nonce must be ${b} or 16 bytes`); if (b !== 12) { const P = new Uint8Array(12); - P.set(s, a ? 0 : 12 - s.length), s = P, w.push(s); + P.set(c, a ? 0 : 12 - c.length), c = P, w.push(c); } - const m = N(A(s)); + const m = N(A(c)); try { - return xr(e, p, h, m, l, u, d, i), u; + return Lr(e, h, p, m, l, u, d, i), u; } finally { - se(...w); + ce(...w); } }; } -function Cr(e, t, n, r, o, a = 20) { - let i = e[0], c = e[1], s = e[2], l = e[3], u = t[0], d = t[1], g = t[2], w = t[3], v = t[4], f = t[5], p = t[6], h = t[7], b = o, m = n[0], P = n[1], I = n[2], E = i, _ = c, K = s, L = l, M = u, U = d, O = g, D = w, q = v, z = f, j = p, $ = h, B = b, G = m, V = P, H = I; - for (let nt = 0; nt < a; nt += 2) - E = E + M | 0, B = k(B ^ E, 16), q = q + B | 0, M = k(M ^ q, 12), E = E + M | 0, B = k(B ^ E, 8), q = q + B | 0, M = k(M ^ q, 7), _ = _ + U | 0, G = k(G ^ _, 16), z = z + G | 0, U = k(U ^ z, 12), _ = _ + U | 0, G = k(G ^ _, 8), z = z + G | 0, U = k(U ^ z, 7), K = K + O | 0, V = k(V ^ K, 16), j = j + V | 0, O = k(O ^ j, 12), K = K + O | 0, V = k(V ^ K, 8), j = j + V | 0, O = k(O ^ j, 7), L = L + D | 0, H = k(H ^ L, 16), $ = $ + H | 0, D = k(D ^ $, 12), L = L + D | 0, H = k(H ^ L, 8), $ = $ + H | 0, D = k(D ^ $, 7), E = E + U | 0, H = k(H ^ E, 16), j = j + H | 0, U = k(U ^ j, 12), E = E + U | 0, H = k(H ^ E, 8), j = j + H | 0, U = k(U ^ j, 7), _ = _ + O | 0, B = k(B ^ _, 16), $ = $ + B | 0, O = k(O ^ $, 12), _ = _ + O | 0, B = k(B ^ _, 8), $ = $ + B | 0, O = k(O ^ $, 7), K = K + D | 0, G = k(G ^ K, 16), q = q + G | 0, D = k(D ^ q, 12), K = K + D | 0, G = k(G ^ K, 8), q = q + G | 0, D = k(D ^ q, 7), L = L + M | 0, V = k(V ^ L, 16), z = z + V | 0, M = k(M ^ z, 12), L = L + M | 0, V = k(V ^ L, 8), z = z + V | 0, M = k(M ^ z, 7); +function Ir(e, t, n, r, o, a = 20) { + let i = e[0], s = e[1], c = e[2], l = e[3], u = t[0], d = t[1], g = t[2], w = t[3], v = t[4], f = t[5], h = t[6], p = t[7], b = o, m = n[0], P = n[1], T = n[2], E = i, _ = s, K = c, S = l, O = u, M = d, U = g, D = w, q = v, j = f, z = h, $ = p, B = b, G = m, V = P, H = T; + for (let ot = 0; ot < a; ot += 2) + E = E + O | 0, B = k(B ^ E, 16), q = q + B | 0, O = k(O ^ q, 12), E = E + O | 0, B = k(B ^ E, 8), q = q + B | 0, O = k(O ^ q, 7), _ = _ + M | 0, G = k(G ^ _, 16), j = j + G | 0, M = k(M ^ j, 12), _ = _ + M | 0, G = k(G ^ _, 8), j = j + G | 0, M = k(M ^ j, 7), K = K + U | 0, V = k(V ^ K, 16), z = z + V | 0, U = k(U ^ z, 12), K = K + U | 0, V = k(V ^ K, 8), z = z + V | 0, U = k(U ^ z, 7), S = S + D | 0, H = k(H ^ S, 16), $ = $ + H | 0, D = k(D ^ $, 12), S = S + D | 0, H = k(H ^ S, 8), $ = $ + H | 0, D = k(D ^ $, 7), E = E + M | 0, H = k(H ^ E, 16), z = z + H | 0, M = k(M ^ z, 12), E = E + M | 0, H = k(H ^ E, 8), z = z + H | 0, M = k(M ^ z, 7), _ = _ + U | 0, B = k(B ^ _, 16), $ = $ + B | 0, U = k(U ^ $, 12), _ = _ + U | 0, B = k(B ^ _, 8), $ = $ + B | 0, U = k(U ^ $, 7), K = K + D | 0, G = k(G ^ K, 16), q = q + G | 0, D = k(D ^ q, 12), K = K + D | 0, G = k(G ^ K, 8), q = q + G | 0, D = k(D ^ q, 7), S = S + O | 0, V = k(V ^ S, 16), j = j + V | 0, O = k(O ^ j, 12), S = S + O | 0, V = k(V ^ S, 8), j = j + V | 0, O = k(O ^ j, 7); let C = 0; - r[C++] = i + E | 0, r[C++] = c + _ | 0, r[C++] = s + K | 0, r[C++] = l + L | 0, r[C++] = u + M | 0, r[C++] = d + U | 0, r[C++] = g + O | 0, r[C++] = w + D | 0, r[C++] = v + q | 0, r[C++] = f + z | 0, r[C++] = p + j | 0, r[C++] = h + $ | 0, r[C++] = b + B | 0, r[C++] = m + G | 0, r[C++] = P + V | 0, r[C++] = I + H | 0; + r[C++] = i + E | 0, r[C++] = s + _ | 0, r[C++] = c + K | 0, r[C++] = l + S | 0, r[C++] = u + O | 0, r[C++] = d + M | 0, r[C++] = g + U | 0, r[C++] = w + D | 0, r[C++] = v + q | 0, r[C++] = f + j | 0, r[C++] = h + z | 0, r[C++] = p + $ | 0, r[C++] = b + B | 0, r[C++] = m + G | 0, r[C++] = P + V | 0, r[C++] = T + H | 0; } -const Ht = /* @__PURE__ */ Ar(Cr, { +const Qt = /* @__PURE__ */ Nr(Ir, { counterRight: !1, counterLength: 4, allowShortKeys: !1 -}), Wt = 2, qe = 1, Ft = 65536, ze = 4294967295; -function ct(e, t) { - const n = Te(new Uint8Array([2]), Se(t)), o = vt.getSharedSecret(e, n, !0).slice(1, 33); - return kn(pe("nip44-v2"), o); +}), Xt = 2, je = 1, Zt = 65536, ze = 4294967295; +function ut(e, t) { + const n = Te(new Uint8Array([2]), Fe(t)), o = mt.getSharedSecret(e, n, !0).slice(1, 33); + return xn(ve("nip44-v2"), o); } -function Jt(e, t) { - const n = Pn(e, t, 76); +function en(e, t) { + const n = An(e, t, 76); return { chachaKey: n.slice(0, 32), chachaNonce: n.slice(32, 44), hmacKey: n.slice(44, 76) }; } -function Yt(e) { +function tn(e) { if (e <= 32) return 32; const t = 2 ** Math.floor(Math.log2(e - 1) + 1), n = t <= 256 ? 32 : t / 8; return n * (Math.floor((e - 1) / n) + 1); } -function Lr(e) { - if (e < Ft) { +function Tr(e) { + if (e < Zt) { const n = new Uint8Array(2); return new DataView(n.buffer).setUint16(0, e, !1), n; } const t = new Uint8Array(6); return new DataView(t.buffer).setUint32(2, e, !1), t; } -function Sr(e) { +function Or(e) { const t = e.length; - if (t < qe || t > ze) - throw new Error(`NIP-44 plaintext length must be between ${qe} and ${ze} bytes.`); - const n = Lr(t), r = Yt(t), o = new Uint8Array(n.length + r); + if (t < je || t > ze) + throw new Error(`NIP-44 plaintext length must be between ${je} and ${ze} bytes.`); + const n = Tr(t), r = tn(t), o = new Uint8Array(n.length + r); return o.set(n, 0), o.set(e, n.length), o; } -function Nr(e) { +function Mr(e) { const t = new DataView(e.buffer, e.byteOffset, e.byteLength), n = t.getUint16(0, !1); let r, o; if (n === 0) { - if (r = t.getUint32(2, !1), o = 6, r < Ft) + if (r = t.getUint32(2, !1), o = 6, r < Zt) throw new Error("NIP-44 payload has inconsistent padding."); } else r = n, o = 2; - if (r < qe || r > ze || e.length !== o + Yt(r)) + if (r < je || r > ze || e.length !== o + tn(r)) throw new Error("NIP-44 payload has inconsistent padding."); return e.slice(o, o + r); } -function Qt(e, t, n) { - return dt(yt, e, Te(t, n)); +function nn(e, t, n) { + return ft(gt, e, Te(t, n)); } -function Tr(e, t, n) { - const r = pt(32), { chachaKey: o, chachaNonce: a, hmacKey: i } = Jt(e, r), c = Sr(pe(t)), s = Ht(o, a, c), l = Qt(i, r, s); - return de.encode(Te(new Uint8Array([Wt]), r, s, l)); +function Ur(e, t, n) { + const r = bt(32), { chachaKey: o, chachaNonce: a, hmacKey: i } = en(e, r), s = Or(ve(t)), c = Qt(o, a, s), l = nn(i, r, c); + return ye.encode(Te(new Uint8Array([Xt]), r, c, l)); } -function Ir(e, t) { - const n = de.decode(t); - if (n[0] !== Wt) +function Dr(e, t) { + const n = ye.decode(t); + if (n[0] !== Xt) throw new Error(`Unsupported NIP-44 version: ${n[0]}`); - const r = n.slice(1, 33), o = n.slice(n.length - 32), a = n.slice(33, n.length - 32), { chachaKey: i, chachaNonce: c, hmacKey: s } = Jt(e, r), l = Qt(s, r, a); - if (!En(o, l)) + const r = n.slice(1, 33), o = n.slice(n.length - 32), a = n.slice(33, n.length - 32), { chachaKey: i, chachaNonce: s, hmacKey: c } = en(e, r), l = nn(c, r, a); + if (!Rn(o, l)) throw new Error("NIP-44 MAC verification failed: payload is corrupted, tampered, or uses the wrong key."); - const u = Ht(i, c, a); - return Ge(Nr(u)); + const u = Qt(i, s, a); + return Ve(Mr(u)); } -const ne = 16, Mr = 283; -function Ur(e) { +const re = 16, qr = 283; +function jr(e) { if (![16, 24, 32].includes(e.length)) throw new Error('"aes key" expected Uint8Array of length 16/24/32, got length=' + e.length); } -function Ze(e) { - return e << 1 ^ Mr & -(e >> 7); +function tt(e) { + return e << 1 ^ qr & -(e >> 7); } -function le(e, t) { +function ue(e, t) { let n = 0; for (; t > 0; t >>= 1) - n ^= e & -(t & 1), e = Ze(e); + n ^= e & -(t & 1), e = tt(e); return n; } -const je = /* @__PURE__ */ (() => { +const $e = /* @__PURE__ */ (() => { const e = new Uint8Array(256); - for (let n = 0, r = 1; n < 256; n++, r ^= Ze(r)) + for (let n = 0, r = 1; n < 256; n++, r ^= tt(r)) e[n] = r; const t = new Uint8Array(256); t[0] = 99; @@ -3254,102 +3254,102 @@ const je = /* @__PURE__ */ (() => { let r = e[255 - n]; r |= r << 8, t[e[n]] = (r ^ r >> 4 ^ r >> 5 ^ r >> 6 ^ r >> 7 ^ 99) & 255; } - return se(e), t; -})(), Or = /* @__PURE__ */ je.map((e, t) => je.indexOf(t)), Dr = (e) => e << 24 | e >>> 8, Ue = (e) => e << 8 | e >>> 24; -function Xt(e, t) { + return ce(e), t; +})(), zr = /* @__PURE__ */ $e.map((e, t) => $e.indexOf(t)), $r = (e) => e << 24 | e >>> 8, Ue = (e) => e << 8 | e >>> 24; +function rn(e, t) { if (e.length !== 256) throw new Error("Wrong sbox length"); - const n = new Uint32Array(256).map((l, u) => t(e[u])), r = n.map(Ue), o = r.map(Ue), a = o.map(Ue), i = new Uint32Array(256 * 256), c = new Uint32Array(256 * 256), s = new Uint16Array(256 * 256); + const n = new Uint32Array(256).map((l, u) => t(e[u])), r = n.map(Ue), o = r.map(Ue), a = o.map(Ue), i = new Uint32Array(256 * 256), s = new Uint32Array(256 * 256), c = new Uint16Array(256 * 256); for (let l = 0; l < 256; l++) for (let u = 0; u < 256; u++) { const d = l * 256 + u; - i[d] = n[l] ^ r[u], c[d] = o[l] ^ a[u], s[d] = e[l] << 8 | e[u]; + i[d] = n[l] ^ r[u], s[d] = o[l] ^ a[u], c[d] = e[l] << 8 | e[u]; } - return { sbox: e, sbox2: s, T0: n, T1: r, T2: o, T3: a, T01: i, T23: c }; + return { sbox: e, sbox2: c, T0: n, T1: r, T2: o, T3: a, T01: i, T23: s }; } -const et = /* @__PURE__ */ Xt(je, (e) => le(e, 3) << 24 | e << 16 | e << 8 | le(e, 2)), Zt = /* @__PURE__ */ Xt(Or, (e) => le(e, 11) << 24 | le(e, 13) << 16 | le(e, 9) << 8 | le(e, 14)), qr = /* @__PURE__ */ (() => { +const nt = /* @__PURE__ */ rn($e, (e) => ue(e, 3) << 24 | e << 16 | e << 8 | ue(e, 2)), on = /* @__PURE__ */ rn(zr, (e) => ue(e, 11) << 24 | ue(e, 13) << 16 | ue(e, 9) << 8 | ue(e, 14)), Br = /* @__PURE__ */ (() => { const e = new Uint8Array(16); - for (let t = 0, n = 1; t < 16; t++, n = Ze(n)) + for (let t = 0, n = 1; t < 16; t++, n = tt(n)) e[t] = n; return e; })(); -function en(e) { - S(e); +function an(e) { + L(e); const t = e.length; - Ur(e); - const { sbox2: n } = et, r = []; - (!W || !Q(e)) && r.push(e = ie(e)); - const o = N(A(e)), a = o.length, i = (s) => F(n, s, s, s, s), c = new Uint32Array(t + 28); - c.set(o); - for (let s = a; s < c.length; s++) { - let l = c[s - 1]; - s % a === 0 ? l = i(Dr(l)) ^ qr[s / a - 1] : a > 6 && s % a === 4 && (l = i(l)), c[s] = c[s - a] ^ l; + jr(e); + const { sbox2: n } = nt, r = []; + (!W || !X(e)) && r.push(e = se(e)); + const o = N(A(e)), a = o.length, i = (c) => J(n, c, c, c, c), s = new Uint32Array(t + 28); + s.set(o); + for (let c = a; c < s.length; c++) { + let l = s[c - 1]; + c % a === 0 ? l = i($r(l)) ^ Br[c / a - 1] : a > 6 && c % a === 4 && (l = i(l)), s[c] = s[c - a] ^ l; } - return se(...r), c; + return ce(...r), s; } -function zr(e) { - const t = en(e), n = t.slice(), r = t.length, { sbox2: o } = et, { T0: a, T1: i, T2: c, T3: s } = Zt; +function Gr(e) { + const t = an(e), n = t.slice(), r = t.length, { sbox2: o } = nt, { T0: a, T1: i, T2: s, T3: c } = on; for (let l = 0; l < r; l += 4) for (let u = 0; u < 4; u++) n[l + u] = t[r - l - 4 + u]; - se(t); + ce(t); for (let l = 4; l < r - 4; l++) { - const u = n[l], d = F(o, u, u, u, u); - n[l] = a[d & 255] ^ i[d >>> 8 & 255] ^ c[d >>> 16 & 255] ^ s[d >>> 24]; + const u = n[l], d = J(o, u, u, u, u); + n[l] = a[d & 255] ^ i[d >>> 8 & 255] ^ s[d >>> 16 & 255] ^ c[d >>> 24]; } return n; } -function ee(e, t, n, r, o, a) { +function te(e, t, n, r, o, a) { return e[n << 8 & 65280 | r >>> 8 & 255] ^ t[o >>> 8 & 65280 | a >>> 24 & 255]; } -function F(e, t, n, r, o) { +function J(e, t, n, r, o) { return e[t & 255 | n & 65280] | e[r >>> 16 & 255 | o >>> 16 & 65280] << 16; } -function lt(e, t, n, r, o) { - const { sbox2: a, T01: i, T23: c } = et; - let s = 0; - t ^= e[s++], n ^= e[s++], r ^= e[s++], o ^= e[s++]; +function dt(e, t, n, r, o) { + const { sbox2: a, T01: i, T23: s } = nt; + let c = 0; + t ^= e[c++], n ^= e[c++], r ^= e[c++], o ^= e[c++]; const l = e.length / 4 - 2; for (let v = 0; v < l; v++) { - const f = e[s++] ^ ee(i, c, t, n, r, o), p = e[s++] ^ ee(i, c, n, r, o, t), h = e[s++] ^ ee(i, c, r, o, t, n), b = e[s++] ^ ee(i, c, o, t, n, r); - t = f, n = p, r = h, o = b; + const f = e[c++] ^ te(i, s, t, n, r, o), h = e[c++] ^ te(i, s, n, r, o, t), p = e[c++] ^ te(i, s, r, o, t, n), b = e[c++] ^ te(i, s, o, t, n, r); + t = f, n = h, r = p, o = b; } - const u = e[s++] ^ F(a, t, n, r, o), d = e[s++] ^ F(a, n, r, o, t), g = e[s++] ^ F(a, r, o, t, n), w = e[s++] ^ F(a, o, t, n, r); + const u = e[c++] ^ J(a, t, n, r, o), d = e[c++] ^ J(a, n, r, o, t), g = e[c++] ^ J(a, r, o, t, n), w = e[c++] ^ J(a, o, t, n, r); return { s0: u, s1: d, s2: g, s3: w }; } -function jr(e, t, n, r, o) { - const { sbox2: a, T01: i, T23: c } = Zt; - let s = 0; - t ^= e[s++], n ^= e[s++], r ^= e[s++], o ^= e[s++]; +function Vr(e, t, n, r, o) { + const { sbox2: a, T01: i, T23: s } = on; + let c = 0; + t ^= e[c++], n ^= e[c++], r ^= e[c++], o ^= e[c++]; const l = e.length / 4 - 2; for (let v = 0; v < l; v++) { - const f = e[s++] ^ ee(i, c, t, o, r, n), p = e[s++] ^ ee(i, c, n, t, o, r), h = e[s++] ^ ee(i, c, r, n, t, o), b = e[s++] ^ ee(i, c, o, r, n, t); - t = f, n = p, r = h, o = b; + const f = e[c++] ^ te(i, s, t, o, r, n), h = e[c++] ^ te(i, s, n, t, o, r), p = e[c++] ^ te(i, s, r, n, t, o), b = e[c++] ^ te(i, s, o, r, n, t); + t = f, n = h, r = p, o = b; } - const u = e[s++] ^ F(a, t, o, r, n), d = e[s++] ^ F(a, n, t, o, r), g = e[s++] ^ F(a, r, n, t, o), w = e[s++] ^ F(a, o, r, n, t); + const u = e[c++] ^ J(a, t, o, r, n), d = e[c++] ^ J(a, n, t, o, r), g = e[c++] ^ J(a, r, n, t, o), w = e[c++] ^ J(a, o, r, n, t); return { s0: u, s1: d, s2: g, s3: w }; } -function $r(e) { - if (S(e), e.length % ne !== 0) - throw new Error("aes-(cbc/ecb).decrypt ciphertext should consist of blocks with size " + ne); +function Hr(e) { + if (L(e), e.length % re !== 0) + throw new Error("aes-(cbc/ecb).decrypt ciphertext should consist of blocks with size " + re); } -function Br(e, t, n) { - S(e); +function Wr(e, t, n) { + L(e); let r = e.length; - const o = r % ne; + const o = r % re; if (!t && o !== 0) throw new Error("aec/(cbc-ecb): unpadded plaintext with disabled padding"); if (t) { - let c = ne - o; - c || (c = ne), r = r + c; + let s = re - o; + s || (s = re), r = r + s; } - n = Xe(r, n), Gt(e, n), (!W || !Q(e)) && (e = ie(e)); + n = et(r, n), Jt(e, n), (!W || !X(e)) && (e = se(e)); const a = A(e); N(a); const i = A(n); return { b: a, o: i, out: n }; } -function Gr(e, t) { +function Fr(e, t) { if (!t) return e; const n = e.length; @@ -3359,86 +3359,86 @@ function Gr(e, t) { let o = 1; o &= r - 1 >>> 31 ^ 1, o &= 16 - r >>> 31 ^ 1; for (let a = 0; a < 16; a++) { - const i = a - r >>> 31, c = (e[n - 1 - a] ^ r) === 0 ? 1 : 0; - o &= c | i ^ 1; + const i = a - r >>> 31, s = (e[n - 1 - a] ^ r) === 0 ? 1 : 0; + o &= s | i ^ 1; } if (!o) throw new Error("aes/pkcs7: wrong padding"); return e.subarray(0, n - r); } -function Vr(e) { +function Jr(e) { const t = new Uint8Array(16), n = A(t); t.set(e); - const r = ne - e.length; - for (let o = ne - r; o < ne; o++) + const r = re - e.length; + for (let o = re - r; o < re; o++) t[o] = r; return n; } -const tn = /* @__PURE__ */ Pr({ blockSize: 16, nonceLength: 16 }, function(t, n, r = {}) { +const sn = /* @__PURE__ */ Rr({ blockSize: 16, nonceLength: 16 }, function(t, n, r = {}) { const o = !r.disablePadding; return { encrypt(a, i) { - const c = en(t), { b: s, o: l, out: u } = Br(a, o, i); + const s = an(t), { b: c, o: l, out: u } = Wr(a, o, i); let d = n; - const g = [c]; - (!W || !Q(d)) && g.push(d = ie(d)); + const g = [s]; + (!W || !X(d)) && g.push(d = se(d)); const w = A(d); N(w); - let v = w[0], f = w[1], p = w[2], h = w[3], b = 0; - for (; b + 4 <= s.length; ) - v ^= s[b + 0], f ^= s[b + 1], p ^= s[b + 2], h ^= s[b + 3], { s0: v, s1: f, s2: p, s3: h } = lt(c, v, f, p, h), l[b++] = v, l[b++] = f, l[b++] = p, l[b++] = h; + let v = w[0], f = w[1], h = w[2], p = w[3], b = 0; + for (; b + 4 <= c.length; ) + v ^= c[b + 0], f ^= c[b + 1], h ^= c[b + 2], p ^= c[b + 3], { s0: v, s1: f, s2: h, s3: p } = dt(s, v, f, h, p), l[b++] = v, l[b++] = f, l[b++] = h, l[b++] = p; if (o) { - const m = Vr(a.subarray(b * 4)); - N(m), v ^= m[0], f ^= m[1], p ^= m[2], h ^= m[3], { s0: v, s1: f, s2: p, s3: h } = lt(c, v, f, p, h), l[b++] = v, l[b++] = f, l[b++] = p, l[b++] = h; + const m = Jr(a.subarray(b * 4)); + N(m), v ^= m[0], f ^= m[1], h ^= m[2], p ^= m[3], { s0: v, s1: f, s2: h, s3: p } = dt(s, v, f, h, p), l[b++] = v, l[b++] = f, l[b++] = h, l[b++] = p; } - return N(l), se(...g), u; + return N(l), ce(...g), u; }, decrypt(a, i) { - $r(a); - const c = zr(t); - let s = n; - const l = [c]; - (!W || !Q(s)) && l.push(s = ie(s)); - const u = A(s); - N(u), i = Xe(a.length, i), Gt(a, i), (!W || !Q(a)) && l.push(a = ie(a)); + Hr(a); + const s = Gr(t); + let c = n; + const l = [s]; + (!W || !X(c)) && l.push(c = se(c)); + const u = A(c); + N(u), i = et(a.length, i), Jt(a, i), (!W || !X(a)) && l.push(a = se(a)); const d = A(a), g = A(i); N(d); - let w = u[0], v = u[1], f = u[2], p = u[3]; - for (let h = 0; h + 4 <= d.length; ) { - const b = w, m = v, P = f, I = p; - w = d[h + 0], v = d[h + 1], f = d[h + 2], p = d[h + 3]; - const { s0: E, s1: _, s2: K, s3: L } = jr(c, w, v, f, p); - g[h++] = E ^ b, g[h++] = _ ^ m, g[h++] = K ^ P, g[h++] = L ^ I; + let w = u[0], v = u[1], f = u[2], h = u[3]; + for (let p = 0; p + 4 <= d.length; ) { + const b = w, m = v, P = f, T = h; + w = d[p + 0], v = d[p + 1], f = d[p + 2], h = d[p + 3]; + const { s0: E, s1: _, s2: K, s3: S } = Vr(s, w, v, f, h); + g[p++] = E ^ b, g[p++] = _ ^ m, g[p++] = K ^ P, g[p++] = S ^ T; } - return N(g), se(...l), Gr(i, o); + return N(g), ce(...l), Fr(i, o); } }; }); -function nn(e, t) { - const n = Te(new Uint8Array([2]), Se(t)); - return vt.getSharedSecret(e, n, !0).slice(1, 33); +function cn(e, t) { + const n = Te(new Uint8Array([2]), Fe(t)); + return mt.getSharedSecret(e, n, !0).slice(1, 33); } -function Hr(e, t, n, r) { - const o = nn(e, t), a = pt(16), i = tn(o, a).encrypt(pe(n)); - return `${de.encode(i)}?iv=${de.encode(a)}`; +function Yr(e, t, n, r) { + const o = cn(e, t), a = bt(16), i = sn(o, a).encrypt(ve(n)); + return `${ye.encode(i)}?iv=${ye.encode(a)}`; } -function Wr(e, t, n) { +function Qr(e, t, n) { const r = n.indexOf("?iv="); if (r === -1) throw new Error('NIP-04 payload is missing its "?iv=" suffix.'); - const o = de.decode(n.slice(0, r)), a = de.decode(n.slice(r + 4)), i = nn(e, t), c = tn(i, a).decrypt(o); - return Ge(c); + const o = ye.decode(n.slice(0, r)), a = ye.decode(n.slice(r + 4)), i = cn(e, t), s = sn(i, a).decrypt(o); + return Ve(s); } -function Fr(...e) { +function Xr(...e) { for (const t of e) t && t.fill(0); } -class _e { +class xe { privateKey; publicKeyHex; destroyed = !1; constructor(t) { - this.privateKey = t, this.publicKeyHex = R(t); + this.privateKey = t, this.publicKeyHex = x(t); } assertAlive() { if (this.destroyed) @@ -3448,7 +3448,7 @@ class _e { return this.assertAlive(), this.publicKeyHex; } signEvent(t) { - return this.assertAlive(), X({ + return this.assertAlive(), Z({ pubkey: this.publicKeyHex, created_at: t.created_at ?? Math.floor(Date.now() / 1e3), kind: t.kind, @@ -3457,109 +3457,181 @@ class _e { }, this.privateKey); } nip44Encrypt(t, n) { - return this.assertAlive(), Tr(ct(this.privateKey, t), n); + return this.assertAlive(), Ur(ut(this.privateKey, t), n); } nip44Decrypt(t, n) { - return this.assertAlive(), Ir(ct(this.privateKey, t), n); + return this.assertAlive(), Dr(ut(this.privateKey, t), n); } /** Legacy relative to nip44Encrypt above, but still what a real NIP-07 extension exposes as * window.nostr.nip04.encrypt -- implemented for drop-in parity. */ nip04Encrypt(t, n) { - return this.assertAlive(), Hr(this.privateKey, t, n); + return this.assertAlive(), Yr(this.privateKey, t, n); } nip04Decrypt(t, n) { - return this.assertAlive(), Wr(this.privateKey, t, n); + return this.assertAlive(), Qr(this.privateKey, t, n); } /** Best-practical secret wipe (§11.10, §21.4): overwrites the private key buffer in place. */ destroy() { - Fr(this.privateKey), this.destroyed = !0; + Xr(this.privateKey), this.destroyed = !0; } } -const Jr = [ +const Zr = [ "wss://relay.damus.io", "wss://nos.lol", "wss://relay.nostr.band", "wss://nostr.wine", "wss://relay.snort.social" -], Yr = [ +], eo = [ "wss://purplepag.es", "wss://relay.nostr.band", "wss://nostr-pub.wellorder.net" -], Qr = "bitlogin", Z = "kv", Xr = 1; -function Zr() { +], to = "bitlogin", ee = "kv", F = "device-keys", no = 2; +function ro() { return new Promise((e, t) => { - const n = indexedDB.open(Qr, Xr); + const n = indexedDB.open(to, no); n.onupgradeneeded = () => { const r = n.result; - r.objectStoreNames.contains(Z) || r.createObjectStore(Z); + r.objectStoreNames.contains(ee) || r.createObjectStore(ee), r.objectStoreNames.contains(F) || r.createObjectStore(F); }, n.onsuccess = () => e(n.result), n.onerror = () => t(n.error); }); } -class eo { +class oo { dbPromise = null; db() { - return this.dbPromise || (this.dbPromise = Zr()), this.dbPromise; + return this.dbPromise || (this.dbPromise = ro()), this.dbPromise; } async get(t) { const n = await this.db(); return new Promise((r, o) => { - const i = n.transaction(Z, "readonly").objectStore(Z).get(t); + const i = n.transaction(ee, "readonly").objectStore(ee).get(t); i.onsuccess = () => r(i.result), i.onerror = () => o(i.error); }); } async set(t, n) { const r = await this.db(); return new Promise((o, a) => { - const i = r.transaction(Z, "readwrite"); - i.objectStore(Z).put(n, t), i.oncomplete = () => o(), i.onerror = () => a(i.error); + const i = r.transaction(ee, "readwrite"); + i.objectStore(ee).put(n, t), i.oncomplete = () => o(), i.onerror = () => a(i.error); }); } async delete(t) { const n = await this.db(); return new Promise((r, o) => { - const a = n.transaction(Z, "readwrite"); - a.objectStore(Z).delete(t), a.oncomplete = () => r(), a.onerror = () => o(a.error); + const a = n.transaction(ee, "readwrite"); + a.objectStore(ee).delete(t), a.oncomplete = () => r(), a.onerror = () => o(a.error); + }); + } + /** + * Returns a non-extractable AES-GCM key kept by the browser's IndexedDB + * implementation. The key is deliberately separate from the string-only KV + * API so callers cannot accidentally serialize it as application data. + */ + async getOrCreateDeviceKey(t) { + const n = await this.db(), r = await new Promise((a, i) => { + const s = n.transaction(F, "readonly").objectStore(F).get(t); + s.onsuccess = () => a(s.result), s.onerror = () => i(s.error); + }); + if (r) return r; + const o = await crypto.subtle.generateKey( + { name: "AES-GCM", length: 256 }, + !1, + ["encrypt", "decrypt"] + ); + try { + return await new Promise((a, i) => { + const s = n.transaction(F, "readwrite"); + s.objectStore(F).add(o, t), s.oncomplete = () => a(), s.onerror = () => i(s.error); + }), o; + } catch { + const a = await new Promise((i, s) => { + const c = n.transaction(F, "readonly").objectStore(F).get(t); + c.onsuccess = () => i(c.result), c.onerror = () => s(c.error); + }); + if (a) return a; + throw new Error("Unable to create the browser-bound session key."); + } + } + async deleteDeviceKey(t) { + const n = await this.db(); + return new Promise((r, o) => { + const a = n.transaction(F, "readwrite"); + a.objectStore(F).delete(t), a.oncomplete = () => r(), a.onerror = () => o(a.error); }); } } -const tt = "bitlogin:session:v1"; -async function to(e, t) { +const _e = "bitlogin:session:v1", rt = "bitlogin:session-device-key:v1"; +function yt(e) { + let t = ""; + for (const n of e) t += String.fromCharCode(n); + return btoa(t); +} +function pt(e) { + const t = atob(e); + return Uint8Array.from(t, (n) => n.charCodeAt(0)); +} +function ht(e) { + const t = new Uint8Array(e.byteLength); + return t.set(e), t.buffer; +} +function ao(e) { + return !!(e && typeof e == "object" && e.v === 2 && typeof e.iv == "string" && typeof e.ciphertext == "string"); +} +async function io(e, t) { const n = { - everydayPrivateKeyHex: _n(t.everydayPrivateKey), + everydayPrivateKeyHex: Array.from(t.everydayPrivateKey, (r) => r.toString(16).padStart(2, "0")).join(""), accountId: t.accountId, recoveryPublicKey: t.recoveryPublicKey, activeCredentialEvent: t.activeCredentialEvent, activeRecoveryEvent: t.activeRecoveryEvent }; try { - await e.set(tt, JSON.stringify(n)); + if (typeof e.getOrCreateDeviceKey != "function") { + await e.delete(_e); + return; + } + const r = await e.getOrCreateDeviceKey(rt), o = crypto.getRandomValues(new Uint8Array(12)), a = new TextEncoder().encode(JSON.stringify(n)), i = await crypto.subtle.encrypt({ name: "AES-GCM", iv: o }, r, a), s = { + v: 2, + iv: yt(o), + ciphertext: yt(new Uint8Array(i)) + }; + await e.set(_e, JSON.stringify(s)); } catch { } } -async function no(e) { +async function so(e) { let t; try { - t = await e.get(tt); + t = await e.get(_e); } catch { return null; } if (!t) return null; try { const n = JSON.parse(t); - return typeof n.everydayPrivateKeyHex != "string" || typeof n.accountId != "string" || typeof n.recoveryPublicKey != "string" || !n.activeCredentialEvent || !n.activeRecoveryEvent ? null : { - everydayPrivateKey: Se(n.everydayPrivateKeyHex), - accountId: n.accountId, - recoveryPublicKey: n.recoveryPublicKey, - activeCredentialEvent: n.activeCredentialEvent, - activeRecoveryEvent: n.activeRecoveryEvent + if (!ao(n) || typeof e.getOrCreateDeviceKey != "function") + return await e.delete(_e), null; + const r = await e.getOrCreateDeviceKey(rt), o = await crypto.subtle.decrypt( + { name: "AES-GCM", iv: ht(pt(n.iv)) }, + r, + ht(pt(n.ciphertext)) + ), a = JSON.parse(new TextDecoder().decode(o)); + return typeof a.everydayPrivateKeyHex != "string" || typeof a.accountId != "string" || typeof a.recoveryPublicKey != "string" || !a.activeCredentialEvent || !a.activeRecoveryEvent ? null : { + everydayPrivateKey: Uint8Array.from( + a.everydayPrivateKeyHex.match(/.{1,2}/g) || [], + (i) => Number.parseInt(i, 16) + ), + accountId: a.accountId, + recoveryPublicKey: a.recoveryPublicKey, + activeCredentialEvent: a.activeCredentialEvent, + activeRecoveryEvent: a.activeRecoveryEvent }; } catch { return null; } } -async function ro(e) { +async function co(e) { try { - await e.delete(tt); + await e.delete(_e), typeof e.deleteDeviceKey == "function" && await e.deleteDeviceKey(rt); } catch { } } @@ -3572,18 +3644,18 @@ const y = { activeRecoveryEvent: null, pendingRecovery: null }; -let J = [...Jr], Oe = [...Yr]; -const we = new eo(); -function Y() { +let Y = [...Zr], De = [...eo]; +const be = new oo(); +function Q() { if (!y.signer || !y.everydayPrivateKey) throw new Error("No identity is unlocked in this session."); return { signer: y.signer, everydayPrivateKey: y.everydayPrivateKey }; } -function ge() { +function we() { y.signer?.destroy(), y.everydayPrivateKey && y.everydayPrivateKey.fill(0), y.signer = null, y.everydayPrivateKey = null, y.accountId = null, y.recoveryPublicKey = null, y.activeCredentialEvent = null, y.activeRecoveryEvent = null, y.pendingRecovery && (y.pendingRecovery.recoveryPrivateKey.fill(0), y.pendingRecovery.everydayPrivateKey.fill(0), y.pendingRecovery = null); } -async function Ke() { - !y.everydayPrivateKey || !y.accountId || !y.recoveryPublicKey || !y.activeCredentialEvent || !y.activeRecoveryEvent || await to(we, { +async function Re() { + !y.everydayPrivateKey || !y.accountId || !y.recoveryPublicKey || !y.activeCredentialEvent || !y.activeRecoveryEvent || await io(be, { everydayPrivateKey: y.everydayPrivateKey, accountId: y.accountId, recoveryPublicKey: y.recoveryPublicKey, @@ -3591,15 +3663,15 @@ async function Ke() { activeRecoveryEvent: y.activeRecoveryEvent }); } -async function oo(e, t) { +async function lo(e, t) { switch (e) { case "configure": { const n = t; - return n.vaultRelayUrls?.length && (J = n.vaultRelayUrls), n.discoveryRelayUrls?.length && (Oe = n.discoveryRelayUrls), {}; + return n.vaultRelayUrls?.length && (Y = n.vaultRelayUrls), n.discoveryRelayUrls?.length && (De = n.discoveryRelayUrls), {}; } case "register": { - const n = t, r = n.importKey ? await yr({ nsecOrHex: n.importKey, loginName: n.loginName, password: n.password, vaultRelayUrls: J }) : await jt({ loginName: n.loginName, password: n.password, vaultRelayUrls: J }); - return ge(), y.signer = new _e(r.everydayPrivateKey), y.everydayPrivateKey = r.everydayPrivateKey, y.accountId = r.accountId, y.recoveryPublicKey = r.recoveryPublicKey, y.activeCredentialEvent = r.credentialEvent, y.activeRecoveryEvent = r.recoveryEvent, await Ke(), { + const n = t, r = n.importKey ? await fr({ nsecOrHex: n.importKey, loginName: n.loginName, password: n.password, vaultRelayUrls: Y }) : await Ht({ loginName: n.loginName, password: n.password, vaultRelayUrls: Y }); + return we(), y.signer = new xe(r.everydayPrivateKey), y.everydayPrivateKey = r.everydayPrivateKey, y.accountId = r.accountId, y.recoveryPublicKey = r.recoveryPublicKey, y.activeCredentialEvent = r.credentialEvent, y.activeRecoveryEvent = r.recoveryEvent, await Re(), { recoveryPhrase: r.recoveryPhrase, everydayPublicKey: r.everydayPublicKey, recoveryPublicKey: r.recoveryPublicKey, @@ -3610,18 +3682,18 @@ async function oo(e, t) { }; } case "previewImportKey": { - const r = $t(t.nsecOrHex), o = R(r), a = { everydayPublicKey: o, npub: ot(o) }; + const r = Wt(t.nsecOrHex), o = x(r), a = { everydayPublicKey: o, npub: it(o) }; return r.fill(0), a; } case "login": { - const n = t, r = await hr({ + const n = t, r = await gr({ loginName: n.loginName, password: n.password, - vaultRelayUrls: J, - store: we, + vaultRelayUrls: Y, + store: be, acknowledgeRollback: n.acknowledgeRollback }); - return ge(), y.signer = new _e(r.everydayPrivateKey), y.everydayPrivateKey = r.everydayPrivateKey, y.accountId = r.accountId, y.recoveryPublicKey = r.recoveryPublicKey, y.activeCredentialEvent = r.credentialEvent, y.activeRecoveryEvent = r.recoveryCapsuleEvent, await Ke(), { + return we(), y.signer = new xe(r.everydayPrivateKey), y.everydayPrivateKey = r.everydayPrivateKey, y.accountId = r.accountId, y.recoveryPublicKey = r.recoveryPublicKey, y.activeCredentialEvent = r.credentialEvent, y.activeRecoveryEvent = r.recoveryCapsuleEvent, await Re(), { everydayPublicKey: r.everydayPublicKey, accountId: r.accountId, generation: r.generation, @@ -3630,13 +3702,13 @@ async function oo(e, t) { }; } case "recover": { - const n = t, r = await pr({ + const n = t, r = await wr({ phrase: n.phrase, - vaultRelayUrls: J, - discoveryRelayUrls: Oe, + vaultRelayUrls: Y, + discoveryRelayUrls: De, offlineRecoveryCapsuleEvents: n.offlineExportFile?.recovery_capsule_events }); - return ge(), y.pendingRecovery = r, y.signer = new _e(r.everydayPrivateKey), y.everydayPrivateKey = r.everydayPrivateKey, y.accountId = r.accountId, y.recoveryPublicKey = r.recoveryPublicKey, y.activeRecoveryEvent = r.currentRecoveryEvent, { + return we(), y.pendingRecovery = r, y.signer = new xe(r.everydayPrivateKey), y.everydayPrivateKey = r.everydayPrivateKey, y.accountId = r.accountId, y.recoveryPublicKey = r.recoveryPublicKey, y.activeRecoveryEvent = r.currentRecoveryEvent, { everydayPublicKey: r.everydayPublicKey, accountId: r.accountId, generalRelays: r.generalRelays, @@ -3647,28 +3719,28 @@ async function oo(e, t) { case "completeRecovery": { const n = t; if (!y.pendingRecovery) throw new Error("No recovery is in progress in this session."); - const r = await vr({ + const r = await br({ recovered: y.pendingRecovery, newLoginName: n.newLoginName, newPassword: n.newPassword, - vaultRelayUrls: J + vaultRelayUrls: Y }); - return y.activeCredentialEvent = r.credentialEvent, y.activeRecoveryEvent = r.refreshedRecoveryEvent, y.pendingRecovery.recoveryPrivateKey.fill(0), y.pendingRecovery = null, await Ke(), { + return y.activeCredentialEvent = r.credentialEvent, y.activeRecoveryEvent = r.refreshedRecoveryEvent, y.pendingRecovery.recoveryPrivateKey.fill(0), y.pendingRecovery = null, await Re(), { locatorPublicKey: r.locatorPublicKey, credentialEventId: r.credentialEvent.id, refreshedRecoveryEventId: r.refreshedRecoveryEvent.id }; } case "changePassword": { - const n = t, r = await gr({ + const n = t, r = await kr({ loginName: n.loginName, oldPassword: n.oldPassword, newPassword: n.newPassword, - vaultRelayUrls: J, - store: we, + vaultRelayUrls: Y, + store: be, acknowledgeRollback: n.acknowledgeRollback }); - return y.activeCredentialEvent = r.newCredentialEvent, y.recoveryPublicKey = r.recoveryPublicKey, y.activeRecoveryEvent = r.recoveryCapsuleEvent, await Ke(), { + return y.activeCredentialEvent = r.newCredentialEvent, y.recoveryPublicKey = r.recoveryPublicKey, y.activeRecoveryEvent = r.recoveryCapsuleEvent, await Re(), { newLocatorPublicKey: r.newLocatorPublicKey, newGeneration: r.newGeneration, tombstoneAcknowledgedCount: r.tombstoneAcknowledgedCount, @@ -3676,51 +3748,51 @@ async function oo(e, t) { }; } case "publishProfileAndRelayLists": { - const n = t, { everydayPrivateKey: r } = Y(); - return Mn({ + const n = t, { everydayPrivateKey: r } = Q(); + return qn({ everydayPrivateKey: r, name: n.name, about: n.about, picture: n.picture, generalRelays: n.generalRelays, dmRelays: n.dmRelays, - discoveryRelays: Oe + discoveryRelays: De }); } case "getPublicKey": { - const { signer: n } = Y(); + const { signer: n } = Q(); return { publicKey: n.getPublicKey() }; } case "signEvent": { - const { signer: n } = Y(), r = t; + const { signer: n } = Q(), r = t; return n.signEvent({ kind: r.kind, tags: r.tags, content: r.content, created_at: r.created_at }); } case "nip44Encrypt": { - const { signer: n } = Y(), r = t; + const { signer: n } = Q(), r = t; return { ciphertext: n.nip44Encrypt(r.peerPublicKey, r.plaintext) }; } case "nip44Decrypt": { - const { signer: n } = Y(), r = t; + const { signer: n } = Q(), r = t; return { plaintext: n.nip44Decrypt(r.peerPublicKey, r.payload) }; } case "nip04Encrypt": { - const { signer: n } = Y(), r = t; + const { signer: n } = Q(), r = t; return { ciphertext: n.nip04Encrypt(r.peerPublicKey, r.plaintext) }; } case "nip04Decrypt": { - const { signer: n } = Y(), r = t; + const { signer: n } = Q(), r = t; return { plaintext: n.nip04Decrypt(r.peerPublicKey, r.payload) }; } case "exportIdentity": { - const { everydayPrivateKey: n, signer: r } = Y(); - return { nsec: Rn(n), npub: ot(r.getPublicKey()) }; + const { everydayPrivateKey: n, signer: r } = Q(); + return { nsec: Sn(n), npub: it(r.getPublicKey()) }; } case "buildRecoveryExport": { - if (Y(), !y.recoveryPublicKey || !y.activeRecoveryEvent) + if (Q(), !y.recoveryPublicKey || !y.activeRecoveryEvent) throw new Error("No recovery capsule is known in this session yet."); - return Kn({ + return Cn({ recoveryPublicKeyHex: y.recoveryPublicKey, - vaultRelayUrls: J, + vaultRelayUrls: Y, recoveryCapsuleEvents: [y.activeRecoveryEvent], relayListEvents: [] }); @@ -3728,7 +3800,7 @@ async function oo(e, t) { case "repairReplicas": { if (!y.activeCredentialEvent || !y.activeRecoveryEvent) throw new Error("No active capsule events are known in this session yet."); - const n = new T(J), r = await Zn(n, y.activeCredentialEvent, y.activeRecoveryEvent); + const n = new I(Y), r = await rr(n, y.activeCredentialEvent, y.activeRecoveryEvent); return n.closeAll(), r; } case "getSessionStatus": @@ -3739,18 +3811,18 @@ async function oo(e, t) { // corrupt cache is not an error: it just means the widget falls through to its // normal welcome screen, exactly like it always has. case "restoreSession": { - const n = await no(we); - return n ? (ge(), y.signer = new _e(n.everydayPrivateKey), y.everydayPrivateKey = n.everydayPrivateKey, y.accountId = n.accountId, y.recoveryPublicKey = n.recoveryPublicKey, y.activeCredentialEvent = n.activeCredentialEvent, y.activeRecoveryEvent = n.activeRecoveryEvent, { restored: !0, everydayPublicKey: y.signer.getPublicKey(), accountId: n.accountId }) : { restored: !1 }; + const n = await so(be); + return n ? (we(), y.signer = new xe(n.everydayPrivateKey), y.everydayPrivateKey = n.everydayPrivateKey, y.accountId = n.accountId, y.recoveryPublicKey = n.recoveryPublicKey, y.activeCredentialEvent = n.activeCredentialEvent, y.activeRecoveryEvent = n.activeRecoveryEvent, { restored: !0, everydayPublicKey: y.signer.getPublicKey(), accountId: n.accountId }) : { restored: !1 }; } case "logout": - return ge(), await ro(we), {}; + return we(), await co(be), {}; default: throw new Error(`Unknown worker action: ${e}`); } } self.addEventListener("message", (e) => { const { id: t, action: n, payload: r } = e.data; - oo(n, r).then( + lo(n, r).then( (o) => { const a = { id: t, ok: !0, result: o }; self.postMessage(a); From 589bda8cb0a80beeaefbb546f4b855eb8f93d717 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Sat, 25 Jul 2026 22:33:32 -0400 Subject: [PATCH 7/8] feat(auth): enable BitLogin on unstable --- config/instance-config.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/config/instance-config.js b/config/instance-config.js index 3734d977..5fccd349 100644 --- a/config/instance-config.js +++ b/config/instance-config.js @@ -284,10 +284,11 @@ export const FEATURE_BITCOIN_CONNECT = true; * The vendored widget bundle (vendor/bitlogin/, scripts/build-bitlogin-widget.mjs) * only lazy-loads once the login modal's HTML is present in the DOM. * - * Unstable-branch experiment (AGENTS.md §1) -- reset to `false` before this - * reaches main until verified end-to-end against production relays. + * Enabled on unstable for the final production-relay smoke test. Keep this + * gated from beta/main until that manual login, reload, logout, and recovery + * pass is confirmed. */ -export const FEATURE_BITLOGIN = false; +export const FEATURE_BITLOGIN = true; /** * Whether this deployment offers Blossom (nostr-native blob storage) as a From 0513c7fcd839d9afb7738b9805843d80dc2cbb1b Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Sat, 25 Jul 2026 22:48:46 -0400 Subject: [PATCH 8/8] test(moderation): align channel blur fixture with threshold --- tests/app/channel-profile-moderation.test.mjs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/app/channel-profile-moderation.test.mjs b/tests/app/channel-profile-moderation.test.mjs index d82c871d..98a966be 100644 --- a/tests/app/channel-profile-moderation.test.mjs +++ b/tests/app/channel-profile-moderation.test.mjs @@ -296,9 +296,10 @@ test("moderation override clears channel blur via event wiring", async (t) => { const container = document.getElementById("channelVideoList"); const moderationState = { - trustedCount: 2, + // BitVid's current default blur threshold is three trusted reports. + trustedCount: 3, summary: { - types: { nudity: { trusted: 2 } }, + types: { nudity: { trusted: 3 } }, }, }; const video = createVideoFixture({