Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions example-apps/dashproof-lab/public/dashproof-lite.html
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,11 @@ <h2>History by chainId</h2>
// 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));
}
Expand Down
5 changes: 5 additions & 0 deletions example-apps/dashproof-lab/src/lib/hash.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
export async function hashFile(file: File): Promise<Uint8Array> {
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()
Expand Down
23 changes: 22 additions & 1 deletion example-apps/dashproof-lab/test/hashFile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.",
);
});
});