feat(storage): Blossom Phase 0 — flag + vendored SDK + service skeleton (#30)

Phase 0 of docs/blossom-plan.md. No UX; off-by-default ("off = no trace").

- Vendored: pinned [email protected] bundled by scripts/build-blossom-sdk.mjs
  → vendor/blossom-client-sdk.bundle.min.js (esbuild, browser ESM, ~27KB). The
  optional @cashu/cashu-ts peer dep (BUD-07 payments, only reached on a lazy 402
  branch bitvid never hits) is aliased to a throwing stub. `npm run build:blossom-sdk`.
- Flag: FEATURE_BLOSSOM_STORAGE wired like FEATURE_AUDIO_INGEST / _BITCOIN_CONNECT
  (instance-config → config → constants), default FALSE.
- Service: js/services/blossomService.js implements the same
  uploadFile({…}) → {url,key} contract as s3UploadService (so a later
  isBlossomProvider branch drops into mediaUploader). Lazy-loads the SDK; core
  path = createUploadAuth(signer, sha256) + multiServerUpload (BUD-11 auth via
  bitvid's existing signer, BUD-04 mirror). Torrent parity + result-shape mapping +
  mediaUploader wiring + Storage-pane config land in Phase 1.

Tests: tests/blossom-service.test.mjs (5 — availability gate + input guards).
Lint + build clean. Nothing wired into the app yet.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.8 committed 2026-07-09 15:42:19 -04:00
1 parent b7c6be830f
commit 02e60f1842
12 files changed
+363

No files matched your search

+13
View File
@@ -276,6 +276,19 @@ export const FEATURE_AUDIO_INGEST = false;
*/
export const FEATURE_BITCOIN_CONNECT = true;
/**
* Whether this deployment offers Blossom (nostr-native blob storage) as a
* first-class upload provider alongside Cloudflare R2 / generic S3 / Backblaze B2
* (docs/blossom-plan.md, TODO #30). Blossom uploads authorize with the user's
* existing nostr signer (kind-24242 auth events) instead of S3 access keys, and
* mirror to multiple servers for resilience.
*
* Defaults to `false` ("off = no trace"): with it off the Storage pane shows only
* R2 / S3 / B2 as today and the vendored Blossom SDK is never imported. Flip to
* `true` to expose the Blossom provider.
*/
export const FEATURE_BLOSSOM_STORAGE = false;
/**
* Card liveness visibility policy — what a non-owner video card does while its
* CDN/WebTorrent liveness probes are still running:
+2
View File
@@ -27,6 +27,7 @@ import {
FEATURE_NIP71_INGEST,
FEATURE_AUDIO_INGEST,
FEATURE_BITCOIN_CONNECT,
FEATURE_BLOSSOM_STORAGE,
CARD_LIVENESS_POLICY,
LIVENESS_PROBE_PREFETCH_MARGIN,
DEFAULT_TRUST_SEED_NPUBS,
@@ -125,6 +126,7 @@ export { ALLOW_NSFW_CONTENT };
export { FEATURE_NIP71_INGEST };
export { FEATURE_AUDIO_INGEST };
export { FEATURE_BITCOIN_CONNECT };
export { FEATURE_BLOSSOM_STORAGE };
export { CARD_LIVENESS_POLICY };
export { LIVENESS_PROBE_PREFETCH_MARGIN };
export { DEFAULT_TRUST_SEED_NPUBS };
+10
View File
@@ -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_BLOSSOM_STORAGE as CONFIG_FEATURE_BLOSSOM_STORAGE,
CARD_LIVENESS_POLICY as CONFIG_CARD_LIVENESS_POLICY,
LIVENESS_PROBE_PREFETCH_MARGIN as CONFIG_LIVENESS_PROBE_PREFETCH_MARGIN,
IS_DEV_MODE,
@@ -203,6 +204,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,
// 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,
FEATURE_HASHTAG_PREFERENCES: false,
FEATURE_TRUST_SEEDS: true, // Rollback: disable to drop baseline trust seeds without code changes.
FEATURE_TRUSTED_HIDE_CONTROLS: true,
@@ -231,6 +235,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_BLOSSOM_STORAGE: DEFAULT_FLAGS.FEATURE_BLOSSOM_STORAGE,
FEATURE_HASHTAG_PREFERENCES: DEFAULT_FLAGS.FEATURE_HASHTAG_PREFERENCES,
FEATURE_TRUST_SEEDS: DEFAULT_FLAGS.FEATURE_TRUST_SEEDS,
FEATURE_TRUSTED_HIDE_CONTROLS: DEFAULT_FLAGS.FEATURE_TRUSTED_HIDE_CONTROLS,
@@ -286,6 +291,11 @@ export let FEATURE_BITCOIN_CONNECT = coerceBoolean(
DEFAULT_FLAGS.FEATURE_BITCOIN_CONNECT
);
export let FEATURE_BLOSSOM_STORAGE = coerceBoolean(
runtimeFlags.FEATURE_BLOSSOM_STORAGE,
DEFAULT_FLAGS.FEATURE_BLOSSOM_STORAGE
);
export let FEATURE_HASHTAG_PREFERENCES = coerceBoolean(
runtimeFlags.FEATURE_HASHTAG_PREFERENCES,
DEFAULT_FLAGS.FEATURE_HASHTAG_PREFERENCES
+123
View File
@@ -0,0 +1,123 @@
// Blossom (nostr-native blob storage) upload service — Phase 0 skeleton.
//
// Implements the same `uploadFile({…}) → { url, key, … }` contract as
// s3UploadService / r2Service (see js/ui/components/mediaUploader.js), so a later
// `isBlossomProvider` branch can route to it with no change downstream. Auth is a
// signed kind-24242 nostr event (BUD-11) built from bitvid's existing signer — no
// access keys. Uploads mirror to multiple servers for resilience (BUD-04).
//
// Phase 0: the vendored SDK loader, availability gate, and the core upload path
// exist but nothing is wired into mediaUploader yet, and the flag is off by
// default. Phase 1 completes torrent parity (magnet/infoHash), the result-shape
// mapping, and the mediaUploader routing + Storage-pane config. See
// docs/blossom-plan.md.
import { FEATURE_BLOSSOM_STORAGE } from "../constants.js";
import { userLogger } from "../utils/logger.js";
// Vendored, pinned blossom-client-sdk bundle (scripts/build-blossom-sdk.mjs).
// Lazy-imported so its (small) weight never touches the main bundle.
const BLOSSOM_SDK_BUNDLE_URL = "../../vendor/blossom-client-sdk.bundle.min.js";
function normalizeServerList(servers) {
const out = [];
const seen = new Set();
for (const entry of Array.isArray(servers) ? servers : []) {
const url = typeof entry === "string" ? entry.trim() : "";
if (!url || seen.has(url)) {
continue;
}
seen.add(url);
out.push(url);
}
return out;
}
export class BlossomService {
constructor(deps = {}) {
this.deps = deps;
this._sdkPromise = null;
}
// On/off gate — off ⇒ the SDK is never imported and callers should not route
// uploads here.
isAvailable() {
return FEATURE_BLOSSOM_STORAGE === true;
}
// Lazy-load the vendored SDK once.
async loadSdk() {
if (this._sdkPromise) {
return this._sdkPromise;
}
this._sdkPromise = import(BLOSSOM_SDK_BUNDLE_URL).catch((error) => {
this._sdkPromise = null;
throw error;
});
return this._sdkPromise;
}
/**
* Upload a file to one or more Blossom servers, mirroring for resilience.
*
* @param {object} params
* @param {File|Blob} params.file - the blob to upload.
* @param {string[]} params.servers - Blossom server base URLs (first = primary).
* @param {(draft: object) => Promise<object>} params.signer - bitvid's active
* signer (BUD-11 kind-24242 auth). Same shape the app already uses.
* @param {string} [params.type] - the auth `t` verb (default "upload").
* @param {(pct:number)=>void} [params.onProgress]
* @returns {Promise<{ url: string, key: string, servers: string[], descriptors: object[] }>}
*
* NOTE (Phase 1): torrent parity (magnet/infoHash), storagePointer, and the full
* mediaUploader result shape are added when this is wired into the upload flow.
*/
async uploadFile({ file, servers, signer, type = "upload", onProgress } = {}) {
if (!file) {
throw new Error("Blossom upload requires a file.");
}
const serverList = normalizeServerList(servers);
if (serverList.length === 0) {
throw new Error("Blossom upload requires at least one server URL.");
}
if (typeof signer !== "function") {
throw new Error("Blossom upload requires a signer (a signed-event function).");
}
const sdk = await this.loadSdk();
const { multiServerUpload, createUploadAuth } = sdk;
if (
typeof multiServerUpload !== "function" ||
typeof createUploadAuth !== "function"
) {
throw new Error("Blossom SDK is missing expected exports.");
}
// One auth per action, scoped to the blob's sha256; the SDK reuses it across
// the mirror set so a multi-server upload isn't N signer prompts.
const results = await multiServerUpload(serverList, file, {
onAuth: (server, sha256) =>
createUploadAuth(signer, sha256, { type }),
onProgress:
typeof onProgress === "function" ? onProgress : undefined,
});
// results: Map<serverUrl, BlobDescriptor{ url, sha256, size, type, … }>
const descriptors = Array.from(results?.values?.() || []);
const primary =
descriptors.find((d) => typeof d?.url === "string" && d.url) || null;
if (!primary) {
throw new Error("Blossom upload did not return a usable blob URL.");
}
return {
url: primary.url,
key: primary.sha256 || "",
servers: serverList,
descriptors,
};
}
}
const blossomService = new BlossomService();
export default blossomService;
+39
View File
@@ -22,6 +22,7 @@
"devDependencies": {
"@getalby/bitcoin-connect": "3.12.3",
"@playwright/test": "^1.58.2",
"blossom-client-sdk": "5.0.0",
"cssnano": "^7.1.2",
"fake-indexeddb": "^6.2.5",
"jsdom": "^28.1.0",
@@ -1862,6 +1863,44 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/blossom-client-sdk": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/blossom-client-sdk/-/blossom-client-sdk-5.0.0.tgz",
"integrity": "sha512-xz7w6eBQv6uiNK9ogh5AymnzCAohiU029XySpKxJyG7RsDHkoru+xoZhY55tiIOyWFX/5s8pip4hVh5hO1PN/Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@noble/hashes": "^1.8.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@cashu/cashu-ts": "^2.4.3",
"hls.js": "^1.6.16"
},
"peerDependenciesMeta": {
"@cashu/cashu-ts": {
"optional": true
},
"hls.js": {
"optional": true
}
}
},
"node_modules/blossom-client-sdk/node_modules/@noble/hashes": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+2
View File
@@ -15,6 +15,7 @@
"build:beacon:bundle": "node scripts/build-beacon.mjs",
"build:beacon": "npm run build:beacon:bundle && npm run lint:inline-styles",
"build:bitcoin-connect": "node scripts/build-bitcoin-connect.mjs",
"build:blossom-sdk": "node scripts/build-blossom-sdk.mjs",
"ci:beacon": "npm run build:beacon",
"format": "prettier --cache --write css/tailwind.source.css css/tokens.css css/docs.css \"docs/**/*.html\" tailwind.config.cjs README.md CONTRIBUTING.md",
"audit": "node scripts/daily-design-system-audit.mjs",
@@ -60,6 +61,7 @@
"devDependencies": {
"@getalby/bitcoin-connect": "3.12.3",
"@playwright/test": "^1.58.2",
"blossom-client-sdk": "5.0.0",
"cssnano": "^7.1.2",
"fake-indexeddb": "^6.2.5",
"jsdom": "^28.1.0",
+51
View File
@@ -0,0 +1,51 @@
// Bundles blossom-client-sdk into a single self-contained ESM file that bitvid
// lazy-imports behind FEATURE_BLOSSOM_STORAGE (docs/blossom-plan.md, TODO #30).
// Mirrors the bitcoin-connect / floating-ui vendor steps. Pinned via the exact
// devDependency version in package.json.
import { build } from "esbuild";
import { mkdir, copyFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const entryPoint = path.join(repoRoot, "scripts/vendor/blossom-sdk-entry.mjs");
const vendorDir = path.join(repoRoot, "vendor");
const outFile = path.join(vendorDir, "blossom-client-sdk.bundle.min.js");
const licenseSrc = path.join(
repoRoot,
"node_modules/blossom-client-sdk/LICENSE.txt",
);
const licenseOut = path.join(vendorDir, "blossom-client-sdk.LICENSE");
await mkdir(vendorDir, { recursive: true });
await build({
bundle: true,
entryPoints: [entryPoint],
outfile: outFile,
format: "esm",
splitting: false,
platform: "browser",
target: ["es2020"],
minify: true,
sourcemap: false,
define: {
"process.env.NODE_ENV": '"production"',
global: "globalThis",
},
// @cashu/cashu-ts is an OPTIONAL peer dep the SDK only lazy-imports on the
// BUD-07 payment branch (HTTP 402). bitvid uses free Blossom servers, so alias
// it to a throwing stub instead of pulling the dep.
alias: {
"@cashu/cashu-ts": path.join(repoRoot, "scripts/vendor/cashu-stub.mjs"),
},
logLevel: "info",
});
try {
await copyFile(licenseSrc, licenseOut);
} catch (error) {
console.warn("[build-blossom-sdk] could not copy LICENSE:", error.message);
}
console.log("[build-blossom-sdk] wrote", path.relative(repoRoot, outFile));
+25
View File
@@ -0,0 +1,25 @@
// Vendor entry for blossom-client-sdk (hzrd149), bundled by
// scripts/build-blossom-sdk.mjs into vendor/blossom-client-sdk.bundle.min.js so
// bitvid can lazy-import it behind FEATURE_BLOSSOM_STORAGE without a runtime CDN.
// Re-exports only the surface bitvid needs. See docs/blossom-plan.md.
//
// The action helpers live under the `./actions/*` subpaths (the root entry
// namespaces them as `Actions`); auth + discovery are top-level modules.
export {
createUploadAuth,
createMirrorAuth,
createListAuth,
encodeAuthorizationHeader,
} from "blossom-client-sdk/auth";
export {
getServersFromServerListEvent,
USER_BLOSSOM_SERVER_LIST_KIND,
} from "blossom-client-sdk/nostr";
export {
multiServerUpload,
multiServerMediaUpload,
} from "blossom-client-sdk/actions/multi-server";
export { uploadBlob } from "blossom-client-sdk/actions/upload";
export { mirrorBlob } from "blossom-client-sdk/actions/mirror";
export { listBlobs, iterateBlobs } from "blossom-client-sdk/actions/list";
export { deleteBlob } from "blossom-client-sdk/actions/delete";
+16
View File
@@ -0,0 +1,16 @@
// Stub for the optional @cashu/cashu-ts peer dep, aliased in when bundling
// blossom-client-sdk (scripts/build-blossom-sdk.mjs). The SDK only reaches for it
// on the BUD-07 cashu-payment branch (a lazy `await import()` triggered by an HTTP
// 402). bitvid targets free Blossom servers, so this never runs; the stubs throw
// loudly if a payment-gated server is ever hit rather than silently misbehaving.
function unsupported() {
throw new Error(
"Cashu payments (BUD-07) are not supported in the bitvid Blossom bundle",
);
}
export const getEncodedToken = unsupported;
export const decodePaymentRequest = unsupported;
export const getDecodedToken = unsupported;
export default {};
+55
View File
@@ -0,0 +1,55 @@
// Phase 0 skeleton tests for blossomService: the availability gate and the
// input guards run without loading the vendored SDK (they throw first). Upload
// round-trips against a real Blossom server are exercised in Phase 1. See
// docs/blossom-plan.md / TODO #30.
import test from "node:test";
import assert from "node:assert/strict";
import blossomService, { BlossomService } from "../js/services/blossomService.js";
test("isAvailable reflects the FEATURE_BLOSSOM_STORAGE flag (off by default)", () => {
assert.equal(blossomService.isAvailable(), false);
});
test("uploadFile rejects a missing file before touching the SDK", async () => {
const svc = new BlossomService();
await assert.rejects(
() => svc.uploadFile({ servers: ["https://blossom.example"], signer: async () => ({}) }),
/requires a file/,
);
});
test("uploadFile rejects an empty server list", async () => {
const svc = new BlossomService();
await assert.rejects(
() => svc.uploadFile({ file: new Blob(["x"]), servers: [], signer: async () => ({}) }),
/at least one server/,
);
});
test("uploadFile rejects a non-function signer", async () => {
const svc = new BlossomService();
await assert.rejects(
() =>
svc.uploadFile({
file: new Blob(["x"]),
servers: ["https://blossom.example"],
signer: null,
}),
/requires a signer/,
);
});
test("uploadFile de-dupes and trims the server list before requiring one", async () => {
const svc = new BlossomService();
// All-blank/dup servers collapse to empty → the "at least one server" guard.
await assert.rejects(
() =>
svc.uploadFile({
file: new Blob(["x"]),
servers: [" ", "", " "],
signer: async () => ({}),
}),
/at least one server/,
);
});
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 hzrd149
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.
+6
View File
@@ -0,0 +1,6 @@
var _t=Object.defineProperty;var ue=(n,e,t)=>e in n?_t(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t;var $=(n,e)=>()=>(n&&(e=n(n=0)),e);var Et=(n,e)=>{for(var t in e)_t(n,t,{get:e[t],enumerable:!0})};var Q=(n,e,t)=>ue(n,typeof e!="symbol"?e+"":e,t);function he(n){return n instanceof Uint8Array||ArrayBuffer.isView(n)&&n.constructor.name==="Uint8Array"}function Y(n,...e){if(!he(n))throw new Error("Uint8Array expected");if(e.length>0&&!e.includes(n.length))throw new Error("Uint8Array expected of length "+e+", got length="+n.length)}function St(n,e=!0){if(n.destroyed)throw new Error("Hash instance has been destroyed");if(e&&n.finished)throw new Error("Hash#digest() has already been called")}function Mt(n,e){Y(n);let t=e.outputLen;if(n.length<t)throw new Error("digestInto() expects output buffer of length at least "+t)}function V(...n){for(let e=0;e<n.length;e++)n[e].fill(0)}function st(n){return new DataView(n.buffer,n.byteOffset,n.byteLength)}function R(n,e){return n<<32-e|n>>>e}function Ft(n){if(Y(n),fe)return n.toHex();let e="";for(let t=0;t<n.length;t++)e+=le[n[t]];return e}function de(n){if(typeof n!="string")throw new Error("string expected");return new Uint8Array(new TextEncoder().encode(n))}function Bt(n){return typeof n=="string"&&(n=de(n)),Y(n),n}function N(n){let e=r=>n().update(Bt(r)).digest(),t=n();return e.outputLen=t.outputLen,e.blockLen=t.blockLen,e.create=()=>n(),e}var fe,le,it,ct=$(()=>{fe=typeof Uint8Array.from([]).toHex=="function"&&typeof Uint8Array.fromHex=="function",le=Array.from({length:256},(n,e)=>e.toString(16).padStart(2,"0"));it=class{}});function xe(n,e,t,r){if(typeof n.setBigUint64=="function")return n.setBigUint64(e,t,r);let o=BigInt(32),i=BigInt(4294967295),a=Number(t>>o&i),s=Number(t&i),u=r?4:0,h=r?0:4;n.setUint32(e+u,a,r),n.setUint32(e+h,s,r)}function kt(n,e,t){return n&e^~n&t}function Dt(n,e,t){return n&e^n&t^e&t}var Z,_,M,E,S,jt=$(()=>{ct();Z=class extends it{constructor(e,t,r,o){super(),this.finished=!1,this.length=0,this.pos=0,this.destroyed=!1,this.blockLen=e,this.outputLen=t,this.padOffset=r,this.isLE=o,this.buffer=new Uint8Array(e),this.view=st(this.buffer)}update(e){St(this),e=Bt(e),Y(e);let{view:t,buffer:r,blockLen:o}=this,i=e.length;for(let a=0;a<i;){let s=Math.min(o-this.pos,i-a);if(s===o){let u=st(e);for(;o<=i-a;a+=o)this.process(u,a);continue}r.set(e.subarray(a,a+s),this.pos),this.pos+=s,a+=s,this.pos===o&&(this.process(t,0),this.pos=0)}return this.length+=e.length,this.roundClean(),this}digestInto(e){St(this),Mt(e,this),this.finished=!0;let{buffer:t,view:r,blockLen:o,isLE:i}=this,{pos:a}=this;t[a++]=128,V(this.buffer.subarray(a)),this.padOffset>o-a&&(this.process(r,0),a=0);for(let c=a;c<o;c++)t[c]=0;xe(r,o-8,BigInt(this.length*8),i),this.process(r,0);let s=st(e),u=this.outputLen;if(u%4)throw new Error("_sha2: outputLen should be aligned to 32bit");let h=u/4,d=this.get();if(h>d.length)throw new Error("_sha2: outputLen bigger than state");for(let c=0;c<h;c++)s.setUint32(4*c,d[c],i)}digest(){let{buffer:e,outputLen:t}=this;this.digestInto(e);let r=e.slice(0,t);return this.destroy(),r}_cloneInto(e){e||(e=new this.constructor),e.set(...this.get());let{blockLen:t,buffer:r,length:o,finished:i,destroyed:a,pos:s}=this;return e.destroyed=a,e.finished=i,e.length=o,e.pos=s,o%t&&e.buffer.set(r),e}clone(){return this._cloneInto()}},_=Uint32Array.from([1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225]),M=Uint32Array.from([3238371032,914150663,812702999,4144912697,4290775857,1750603025,1694076839,3204075428]),E=Uint32Array.from([3418070365,3238371032,1654270250,914150663,2438529370,812702999,355462360,4144912697,1731405415,4290775857,2394180231,1750603025,3675008525,1694076839,1203062813,3204075428]),S=Uint32Array.from([1779033703,4089235720,3144134277,2227873595,1013904242,4271175723,2773480762,1595750129,1359893119,2917565137,2600822924,725511199,528734635,4215389547,1541459225,327033209])});function me(n,e=!1){return e?{h:Number(n&ut),l:Number(n>>Vt&ut)}:{h:Number(n>>Vt&ut)|0,l:Number(n&ut)|0}}function Nt(n,e=!1){let t=n.length,r=new Uint32Array(t),o=new Uint32Array(t);for(let i=0;i<t;i++){let{h:a,l:s}=me(n[i],e);[r[i],o[i]]=[a,s]}return[r,o]}function I(n,e,t,r){let o=(e>>>0)+(r>>>0);return{h:n+t+(o/2**32|0)|0,l:o|0}}var ut,Vt,Ut,Ht,G,W,tt,et,Gt,Wt,Ot,qt,Xt,Kt,$t=$(()=>{ut=BigInt(4294967295),Vt=BigInt(32);Ut=(n,e,t)=>n>>>t,Ht=(n,e,t)=>n<<32-t|e>>>t,G=(n,e,t)=>n>>>t|e<<32-t,W=(n,e,t)=>n<<32-t|e>>>t,tt=(n,e,t)=>n<<64-t|e>>>t-32,et=(n,e,t)=>n>>>t-32|e<<64-t;Gt=(n,e,t)=>(n>>>0)+(e>>>0)+(t>>>0),Wt=(n,e,t,r)=>e+t+r+(n/2**32|0)|0,Ot=(n,e,t,r)=>(n>>>0)+(e>>>0)+(t>>>0)+(r>>>0),qt=(n,e,t,r,o)=>e+t+r+o+(n/2**32|0)|0,Xt=(n,e,t,r,o)=>(n>>>0)+(e>>>0)+(t>>>0)+(r>>>0)+(o>>>0),Kt=(n,e,t,r,o,i)=>e+t+r+o+i+(n/2**32|0)|0});var Qt={};Et(Qt,{SHA224:()=>ht,SHA256:()=>nt,SHA384:()=>ft,SHA512:()=>O,SHA512_224:()=>lt,SHA512_256:()=>dt,sha224:()=>Ae,sha256:()=>ye,sha384:()=>Se,sha512:()=>Ee,sha512_224:()=>Ue,sha512_256:()=>Be});var ge,k,nt,ht,Jt,wLine truncated
/*! Bundled license information:
@noble/hashes/esm/utils.js:
(*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
*/