diff --git a/example-apps/dashproof-lab/public/dashproof-lite.html b/example-apps/dashproof-lab/public/dashproof-lite.html
index ef53352..a9e10f5 100644
--- a/example-apps/dashproof-lab/public/dashproof-lite.html
+++ b/example-apps/dashproof-lab/public/dashproof-lite.html
@@ -217,6 +217,11 @@
History by chainId
// SHA-256 via WebCrypto. Runs entirely in the browser; the file never
// leaves the page.
async function hashFile(file) {
+ if (typeof crypto === 'undefined' || !crypto.subtle) {
+ throw new Error(
+ 'SHA-256 hashing requires a secure context. Open this page over HTTPS or via http://localhost — the browser disables crypto.subtle on plain http:// origins.',
+ );
+ }
const buf = await file.arrayBuffer();
return new Uint8Array(await crypto.subtle.digest('SHA-256', buf));
}
diff --git a/example-apps/dashproof-lab/src/lib/hash.ts b/example-apps/dashproof-lab/src/lib/hash.ts
index d8e4353..384f4fb 100644
--- a/example-apps/dashproof-lab/src/lib/hash.ts
+++ b/example-apps/dashproof-lab/src/lib/hash.ts
@@ -1,4 +1,9 @@
export async function hashFile(file: File): Promise {
+ if (typeof crypto === "undefined" || !crypto.subtle) {
+ throw new Error(
+ "SHA-256 hashing requires a secure context. Open this app over HTTPS or via http://localhost — the browser disables crypto.subtle on plain http:// origins.",
+ );
+ }
const buffer =
typeof file.arrayBuffer === "function"
? await file.arrayBuffer()
diff --git a/example-apps/dashproof-lab/test/hashFile.test.ts b/example-apps/dashproof-lab/test/hashFile.test.ts
index 5c93395..2a20095 100644
--- a/example-apps/dashproof-lab/test/hashFile.test.ts
+++ b/example-apps/dashproof-lab/test/hashFile.test.ts
@@ -3,11 +3,21 @@
// which would make this regression test useless. Node 20+ provides a working
// global File backed by Undici's Blob implementation.
-import { describe, expect, it } from "vitest";
+import { afterEach, describe, expect, it } from "vitest";
import { bytesToHex, hashFile } from "../src/lib/hash";
describe("hashFile", () => {
+ const originalSubtle = globalThis.crypto?.subtle;
+ afterEach(() => {
+ if (globalThis.crypto) {
+ Object.defineProperty(globalThis.crypto, "subtle", {
+ configurable: true,
+ value: originalSubtle,
+ });
+ }
+ });
+
it("hashes binary (non-UTF-8) bytes correctly", async () => {
// 0xFF 0xFE is invalid UTF-8 — a TextEncoder-based fallback would
// corrupt these bytes into the U+FFFD replacement character before
@@ -30,4 +40,15 @@ describe("hashFile", () => {
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
);
});
+
+ it("throws a clear Secure Context error when crypto.subtle is missing", async () => {
+ Object.defineProperty(globalThis.crypto, "subtle", {
+ configurable: true,
+ value: undefined,
+ });
+ const file = new File([Uint8Array.from([0x00])], "x.bin");
+ await expect(hashFile(file)).rejects.toThrow(
+ "SHA-256 hashing requires a secure context. Open this app over HTTPS or via http://localhost — the browser disables crypto.subtle on plain http:// origins.",
+ );
+ });
});