xftp-web: remove debug logs (#1798)

Co-authored-by: Paul Bottinelli <paul.bottinelli@trailofbits.com>
This commit is contained in:
sh
2026-06-06 08:04:26 +00:00
committed by GitHub
parent 53bc0fe663
commit 7d3cfa56d3
7 changed files with 24 additions and 102 deletions
@@ -619,7 +619,6 @@ async function handleEncrypt(id, data, fileName) {
const digest = sha512Streaming([encData], (done) => {
self.postMessage({ id, type: "progress", done: source.length + done, total });
}, encDataLen);
console.log(`[WORKER-DBG] encrypt: encData.len=${encData.length} digest=${_whex(digest, 64)} chunkSizes=[${chunkSizes.join(",")}]`);
const dir = await getSessionDir();
const fileHandle = await dir.getFileHandle("upload.bin", { create: true });
const writeHandle = await fileHandle.createSyncAccessHandle();
@@ -643,9 +642,7 @@ function handleReadChunk(id, offset, size) {
}
async function handleDecryptAndStore(id, dhSecret, nonce, body, chunkDigest, chunkNo) {
const bodyArr = new Uint8Array(body);
console.log(`[WORKER-DBG] store chunk=${chunkNo} body.len=${bodyArr.length} nonce=${_whex(nonce, 24)} dhSecret=${_whex(dhSecret)} digest=${_whex(chunkDigest, 32)} body[0..8]=${_whex(bodyArr)} body[-8..]=${_whex(bodyArr.slice(-8))}`);
const decrypted = decryptReceivedChunk(dhSecret, nonce, bodyArr, chunkDigest);
console.log(`[WORKER-DBG] decrypted chunk=${chunkNo} len=${decrypted.length} [0..8]=${_whex(decrypted)} [-8..]=${_whex(decrypted.slice(-8))}`);
if (useMemory) {
memoryChunks.set(chunkNo, decrypted);
self.postMessage({ id, type: "stored" });
@@ -660,7 +657,6 @@ async function handleDecryptAndStore(id, dhSecret, nonce, body, chunkDigest, chu
currentDownloadOffset += decrypted.length;
chunkMeta.set(chunkNo, { offset, size: decrypted.length });
const written = downloadWriteHandle.write(decrypted, { at: offset });
console.log(`[WORKER-DBG] OPFS write chunk=${chunkNo} offset=${offset} size=${decrypted.length} written=${written}`);
if (written !== decrypted.length) {
console.warn(`[WORKER] OPFS write failed chunk=${chunkNo}: ${written}/${decrypted.length}, falling back to in-memory storage`);
for (const [cn, meta] of chunkMeta.entries()) {
@@ -684,23 +680,16 @@ async function handleDecryptAndStore(id, dhSecret, nonce, body, chunkDigest, chu
return;
}
downloadWriteHandle.flush();
const verifyBuf = new Uint8Array(Math.min(8, decrypted.length));
downloadWriteHandle.read(verifyBuf, { at: offset });
const verifyEnd = new Uint8Array(Math.min(8, decrypted.length));
downloadWriteHandle.read(verifyEnd, { at: offset + decrypted.length - verifyEnd.length });
console.log(`[WORKER-DBG] OPFS verify chunk=${chunkNo} readBack[0..8]=${_whex(verifyBuf)} readBack[-8..]=${_whex(verifyEnd)} expected[0..8]=${_whex(decrypted)} expected[-8..]=${_whex(decrypted.slice(-8))}`);
self.postMessage({ id, type: "stored" });
}
async function handleVerifyAndDecrypt(id, size, digest, key, nonce) {
console.log(`[WORKER-DBG] verify: expectedSize=${size} expectedDigest=${_whex(digest, 64)} useMemory=${useMemory} chunkMeta.size=${chunkMeta.size} memoryChunks.size=${memoryChunks.size}`);
const chunks = [];
let totalSize = 0;
const total = size * 3;
let done = 0;
if (useMemory) {
const sorted = [...memoryChunks.entries()].sort((a, b) => a[0] - b[0]);
for (const [chunkNo, data] of sorted) {
console.log(`[WORKER-DBG] verify memory chunk=${chunkNo} size=${data.length}`);
for (const [, data] of sorted) {
chunks.push(data);
totalSize += data.length;
done += data.length;
@@ -715,12 +704,10 @@ async function handleVerifyAndDecrypt(id, size, digest, key, nonce) {
const dir = await getSessionDir();
const fileHandle = await dir.getFileHandle("download.bin");
const readHandle = await fileHandle.createSyncAccessHandle();
console.log(`[WORKER-DBG] verify: OPFS file size=${readHandle.getSize()}`);
const sortedEntries = [...chunkMeta.entries()].sort((a, b) => a[0] - b[0]);
for (const [chunkNo, meta] of sortedEntries) {
for (const [, meta] of sortedEntries) {
const buf = new Uint8Array(meta.size);
const bytesRead = readHandle.read(buf, { at: meta.offset });
console.log(`[WORKER-DBG] verify read chunk=${chunkNo} offset=${meta.offset} size=${meta.size} bytesRead=${bytesRead} [0..8]=${_whex(buf)} [-8..]=${_whex(buf.slice(-8))}`);
readHandle.read(buf, { at: meta.offset });
chunks.push(buf);
totalSize += meta.size;
done += meta.size;
@@ -745,20 +732,9 @@ async function handleVerifyAndDecrypt(id, size, digest, key, nonce) {
}
const actualDigest = r.crypto_hash_sha512_final(state);
if (!digestEqual(actualDigest, digest)) {
console.error(`[WORKER-DBG] DIGEST MISMATCH: expected=${_whex(digest, 64)} actual=${_whex(actualDigest, 64)} chunks=${chunks.length} totalSize=${totalSize}`);
const state2 = r.crypto_hash_sha512_init();
for (let i = 0; i < chunks.length; i++) {
const chunk = chunks[i];
for (let off = 0; off < chunk.length; off += hashSEG) {
r.crypto_hash_sha512_update(state2, chunk.subarray(off, Math.min(off + hashSEG, chunk.length)));
}
const chunkDigest = sha512Streaming([chunk]);
console.error(`[WORKER-DBG] chunk[${i}] size=${chunk.length} sha512=${_whex(chunkDigest, 32)}… [0..8]=${_whex(chunk)} [-8..]=${_whex(chunk.slice(-8))}`);
}
self.postMessage({ id, type: "error", message: "File digest mismatch" });
return;
}
console.log(`[WORKER-DBG] verify: digest OK`);
const result = decryptChunks(BigInt(size), chunks, key, nonce, (d) => {
self.postMessage({ id, type: "progress", done: size * 2 + d, total });
});
@@ -334,11 +334,6 @@ class WorkerBackend {
const nonceCopy = new Uint8Array(nonce);
const digestCopy = new Uint8Array(digest);
const buf = this.toTransferable(body);
const hex = (b, n = 8) => {
const u = b instanceof ArrayBuffer ? new Uint8Array(b) : b;
return Array.from(u.slice(0, n)).map((x) => x.toString(16).padStart(2, "0")).join("");
};
console.log(`[BACKEND-DBG] chunk=${chunkNo} body.len=${body.length} body.byteOff=${body.byteOffset} buf.byteLen=${buf.byteLength} nonce=${hex(nonceCopy, 24)} dhSecret=${hex(dhSecretCopy)} digest=${hex(digestCopy, 32)} buf[0..8]=${hex(buf)} body[-8..]=${hex(body.slice(-8))}`);
await this.send(
{ type: "decryptAndStoreChunk", dhSecret: dhSecretCopy, nonce: nonceCopy, body: buf, chunkDigest: digestCopy, chunkNo },
[buf]
@@ -10913,14 +10908,12 @@ async function sendXFTPCommandOnce(client, privateKey, entityId, cmdBytes, chunk
const block = encodeAuthTransmission(client.sessionId, corrId, entityId, cmdBytes, privateKey);
const reqBody = chunkData ? concatBytes$1(block, chunkData) : block;
const fullResp = await client.transport.post(reqBody);
console.log(`[XFTP-DBG] sendOnce: fullResp.length=${fullResp.length} entityId=${_hex(entityId)} cmdTag=${cmdBytes[0]}`);
if (fullResp.length < XFTP_BLOCK_SIZE) {
console.error("[XFTP] Response too short: %d bytes (expected >= %d)", fullResp.length, XFTP_BLOCK_SIZE);
throw new Error("Server response too short");
}
const respBlock = fullResp.subarray(0, XFTP_BLOCK_SIZE);
const body = fullResp.subarray(XFTP_BLOCK_SIZE);
console.log(`[XFTP-DBG] sendOnce: body.length=${body.length} body.byteOffset=${body.byteOffset} body.buffer.byteLength=${body.buffer.byteLength}`);
const raw = blockUnpad(respBlock);
if (raw.length < 20) {
const text = new TextDecoder().decode(raw);
@@ -10939,18 +10932,13 @@ async function sendXFTPCommandOnce(client, privateKey, entityId, cmdBytes, chunk
}
return { response, body };
}
function _hex(b, n = 8) {
return Array.from(b.slice(0, n)).map((x) => x.toString(16).padStart(2, "0")).join("");
}
async function sendXFTPCommand(agent, server, privateKey, entityId, cmdBytes, chunkData, maxRetries = 3) {
let clientP = getXFTPServerClient(agent, server);
let client = await clientP;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
if (attempt > 1) console.log(`[XFTP-DBG] sendCmd: retry attempt=${attempt}/${maxRetries}`);
return await sendXFTPCommandOnce(client, privateKey, entityId, cmdBytes, chunkData);
} catch (e) {
console.log(`[XFTP-DBG] sendCmd: attempt=${attempt} failed: ${e instanceof Error ? e.message : String(e)} retriable=${isRetriable(e)}`);
if (!isRetriable(e)) {
throw categorizeError(e);
}
@@ -10979,7 +10967,6 @@ async function downloadXFTPChunkRaw(agent, server, rpKey, fId) {
const { response, body } = await sendXFTPCommand(agent, server, rpKey, fId, cmd);
if (response.type !== "FRFile") throw new Error("unexpected response: " + response.type);
const dhSecret = dh(response.rcvDhKey, privateKey);
console.log(`[XFTP-DBG] dlChunkRaw: body.length=${body.length} nonce=${_hex(response.nonce, 24)} dhSecret=${_hex(dhSecret)} body[0..8]=${_hex(body)} body[-8..]=${_hex(body.slice(-8))}`);
return { dhSecret, nonce: response.nonce, body };
}
async function downloadXFTPChunk(agent, server, rpKey, fId, digest) {
@@ -11012,7 +10999,6 @@ function encryptFileForUpload(source, fileName) {
const encSize = BigInt(chunkSizes.reduce((a, b) => a + b, 0));
const encData = encryptFile(source, fileHdr, key, nonce, fileSize, encSize);
const digest = sha512Streaming([encData]);
console.log(`[AGENT-DBG] encrypt: encData.len=${encData.length} digest=${_dbgHex(digest, 64)} chunkSizes=[${chunkSizes.join(",")}]`);
return { encData, digest, key, nonce, chunkSizes };
}
const DEFAULT_REDIRECT_THRESHOLD = 400;
@@ -11169,9 +11155,7 @@ async function downloadFileRaw(agent, fd, onRawChunk, options) {
if (err) throw new Error("downloadFileRaw: " + err);
const { onProgress} = options ?? {};
if (fd.redirect !== null) {
console.log(`[AGENT-DBG] resolving redirect: outer size=${fd.size} chunks=${fd.chunks.length}`);
fd = await resolveRedirect(agent, fd);
console.log(`[AGENT-DBG] resolved: size=${fd.size} chunks=${fd.chunks.length} digest=${Array.from(fd.digest.slice(0, 16)).map((x) => x.toString(16).padStart(2, "0")).join("")}`);
}
const resolvedFd = fd;
let downloaded = 0;
@@ -11189,7 +11173,6 @@ async function downloadFileRaw(agent, fd, onRawChunk, options) {
const seed = decodePrivKeyEd25519(replica.replicaKey);
const kp = ed25519KeyPairFromSeed(seed);
const raw = await downloadXFTPChunkRaw(agent, server, kp.privateKey, replica.replicaId);
console.log(`[AGENT-DBG] chunk=${chunk.chunkNo} body.len=${raw.body.length} expectedChunkSize=${chunk.chunkSize} digest=${_dbgHex(chunk.digest, 32)} body.byteOffset=${raw.body.byteOffset} body.buffer.byteLength=${raw.body.buffer.byteLength}`);
await onRawChunk({
chunkNo: chunk.chunkNo,
dhSecret: raw.dhSecret,
+18 -1
View File
@@ -14,7 +14,7 @@
module XFTPWebTests (xftpWebTests) where
import Control.Concurrent (forkIO, newEmptyMVar, putMVar, takeMVar)
import Control.Monad (replicateM, when)
import Control.Monad (forM_, replicateM, when)
import Crypto.Error (throwCryptoError)
import qualified Crypto.PubKey.Curve25519 as X25519
import qualified Crypto.PubKey.Ed25519 as Ed25519
@@ -170,6 +170,7 @@ jsOut expr = "process.stdout.write(Buffer.from(" <> expr <> "));"
xftpWebTests :: IO () -> Spec
xftpWebTests dbCleanup = do
xftpWebSourceHygieneTests
distExists <- runIO $ doesDirectoryExist (xftpWebDir <> "/dist")
if distExists
then do
@@ -193,6 +194,22 @@ xftpWebTests dbCleanup = do
it "skipped (run 'cd xftp-web && npm install && npm run build' first)" $
pendingWith "TS project not compiled"
xftpWebSourceHygieneTests :: Spec
xftpWebSourceHygieneTests = describe "source hygiene" $
it "does not commit XFTP web debug logs that expose secrets or plaintext" $ do
let files =
[ "xftp-web/src/client.ts",
"xftp-web/src/agent.ts",
"xftp-web/web/crypto-backend.ts",
"xftp-web/web/crypto.worker.ts",
"apps/xftp-server/static/xftp-web-bundle/index.js",
"apps/xftp-server/static/xftp-web-bundle/crypto.worker.js"
]
markers = ["XFTP-DBG", "AGENT-DBG", "BACKEND-DBG", "WORKER-DBG"]
forM_ files $ \path -> do
contents <- B.readFile path
mapM_ (\marker -> contents `shouldNotSatisfy` B.isInfixOf marker) markers
-- ── protocol/encoding ──────────────────────────────────────────────
tsEncodingTests :: Spec
-8
View File
@@ -90,7 +90,6 @@ export function encryptFileForUpload(source: Uint8Array, fileName: string): Encr
const encSize = BigInt(chunkSizes.reduce((a, b) => a + b, 0))
const encData = encryptFile(source, fileHdr, key, nonce, fileSize, encSize)
const digest = sha512Streaming([encData])
console.log(`[AGENT-DBG] encrypt: encData.len=${encData.length} digest=${_dbgHex(digest, 64)} chunkSizes=[${chunkSizes.join(',')}]`)
return {encData, digest, key, nonce, chunkSizes}
}
@@ -280,9 +279,7 @@ export async function downloadFileRaw(
const {onProgress, concurrency = 1} = options ?? {}
// Resolve redirect on main thread (redirect data is small)
if (fd.redirect !== null) {
console.log(`[AGENT-DBG] resolving redirect: outer size=${fd.size} chunks=${fd.chunks.length}`)
fd = await resolveRedirect(agent, fd)
console.log(`[AGENT-DBG] resolved: size=${fd.size} chunks=${fd.chunks.length} digest=${Array.from(fd.digest.slice(0, 16)).map(x => x.toString(16).padStart(2, '0')).join('')}`)
}
const resolvedFd = fd
// Group chunks by server, sequential within each server, parallel across servers
@@ -301,7 +298,6 @@ export async function downloadFileRaw(
const seed = decodePrivKeyEd25519(replica.replicaKey)
const kp = ed25519KeyPairFromSeed(seed)
const raw = await downloadXFTPChunkRaw(agent, server, kp.privateKey, replica.replicaId)
console.log(`[AGENT-DBG] chunk=${chunk.chunkNo} body.len=${raw.body.length} expectedChunkSize=${chunk.chunkSize} digest=${_dbgHex(chunk.digest, 32)} body.byteOffset=${raw.body.byteOffset} body.buffer.byteLength=${raw.body.buffer.byteLength}`)
await onRawChunk({
chunkNo: chunk.chunkNo,
dhSecret: raw.dhSecret,
@@ -377,10 +373,6 @@ export async function deleteFile(agent: XFTPClientAgent, sndDescription: FileDes
// -- Internal
function _dbgHex(b: Uint8Array, n = 8): string {
return Array.from(b.slice(0, n)).map(x => x.toString(16).padStart(2, '0')).join('')
}
function digestEqual(a: Uint8Array, b: Uint8Array): boolean {
if (a.length !== b.length) return false
let diff = 0
-10
View File
@@ -311,14 +311,12 @@ async function sendXFTPCommandOnce(
const block = encodeAuthTransmission(client.sessionId, corrId, entityId, cmdBytes, privateKey)
const reqBody = chunkData ? concatBytes(block, chunkData) : block
const fullResp = await client.transport.post(reqBody)
console.log(`[XFTP-DBG] sendOnce: fullResp.length=${fullResp.length} entityId=${_hex(entityId)} cmdTag=${cmdBytes[0]}`)
if (fullResp.length < XFTP_BLOCK_SIZE) {
console.error('[XFTP] Response too short: %d bytes (expected >= %d)', fullResp.length, XFTP_BLOCK_SIZE)
throw new Error("Server response too short")
}
const respBlock = fullResp.subarray(0, XFTP_BLOCK_SIZE)
const body = fullResp.subarray(XFTP_BLOCK_SIZE)
console.log(`[XFTP-DBG] sendOnce: body.length=${body.length} body.byteOffset=${body.byteOffset} body.buffer.byteLength=${body.buffer.byteLength}`)
// Detect padded error strings (HANDSHAKE, SESSION) before decodeTransmission
const raw = blockUnpad(respBlock)
if (raw.length < 20) {
@@ -339,10 +337,6 @@ async function sendXFTPCommandOnce(
return {response, body}
}
function _hex(b: Uint8Array, n = 8): string {
return Array.from(b.slice(0, n)).map(x => x.toString(16).padStart(2, '0')).join('')
}
// -- Send command (with retry + reconnect)
export async function sendXFTPCommand(
@@ -358,10 +352,8 @@ export async function sendXFTPCommand(
let client = await clientP
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
if (attempt > 1) console.log(`[XFTP-DBG] sendCmd: retry attempt=${attempt}/${maxRetries}`)
return await sendXFTPCommandOnce(client, privateKey, entityId, cmdBytes, chunkData)
} catch (e) {
console.log(`[XFTP-DBG] sendCmd: attempt=${attempt} failed: ${e instanceof Error ? e.message : String(e)} retriable=${isRetriable(e)}`)
if (!isRetriable(e)) {
throw categorizeError(e)
}
@@ -416,7 +408,6 @@ export async function downloadXFTPChunkRaw(
const {response, body} = await sendXFTPCommand(agent, server, rpKey, fId, cmd)
if (response.type !== "FRFile") throw new Error("unexpected response: " + response.type)
const dhSecret = dh(response.rcvDhKey, privateKey)
console.log(`[XFTP-DBG] dlChunkRaw: body.length=${body.length} nonce=${_hex(response.nonce, 24)} dhSecret=${_hex(dhSecret)} body[0..8]=${_hex(body)} body[-8..]=${_hex(body.slice(-8))}`)
return {dhSecret, nonce: response.nonce, body}
}
@@ -444,4 +435,3 @@ export async function pingXFTP(agent: XFTPClientAgent, server: XFTPServer): Prom
const response = decodeResponse(command)
if (response.type !== "FRPong") throw new Error("unexpected response: " + response.type)
}
-5
View File
@@ -108,11 +108,6 @@ class WorkerBackend implements CryptoBackend {
const nonceCopy = new Uint8Array(nonce)
const digestCopy = new Uint8Array(digest)
const buf = this.toTransferable(body)
const hex = (b: Uint8Array | ArrayBuffer, n = 8) => {
const u = b instanceof ArrayBuffer ? new Uint8Array(b) : b
return Array.from(u.slice(0, n)).map(x => x.toString(16).padStart(2, '0')).join('')
}
console.log(`[BACKEND-DBG] chunk=${chunkNo} body.len=${body.length} body.byteOff=${body.byteOffset} buf.byteLen=${buf.byteLength} nonce=${hex(nonceCopy, 24)} dhSecret=${hex(dhSecretCopy)} digest=${hex(digestCopy, 32)} buf[0..8]=${hex(buf)} body[-8..]=${hex(body.slice(-8))}`)
await this.send(
{type: 'decryptAndStoreChunk', dhSecret: dhSecretCopy, nonce: nonceCopy, body: buf, chunkDigest: digestCopy, chunkNo},
[buf]
+3 -34
View File
@@ -59,7 +59,6 @@ async function handleEncrypt(id: number, data: ArrayBuffer, fileName: string) {
const digest = sha512Streaming([encData], (done) => {
self.postMessage({id, type: 'progress', done: source.length + done, total})
}, encDataLen)
console.log(`[WORKER-DBG] encrypt: encData.len=${encData.length} digest=${_whex(digest, 64)} chunkSizes=[${chunkSizes.join(',')}]`)
// Write to OPFS
const dir = await getSessionDir()
@@ -93,9 +92,7 @@ async function handleDecryptAndStore(
body: ArrayBuffer, chunkDigest: Uint8Array, chunkNo: number
) {
const bodyArr = new Uint8Array(body)
console.log(`[WORKER-DBG] store chunk=${chunkNo} body.len=${bodyArr.length} nonce=${_whex(nonce, 24)} dhSecret=${_whex(dhSecret)} digest=${_whex(chunkDigest, 32)} body[0..8]=${_whex(bodyArr)} body[-8..]=${_whex(bodyArr.slice(-8))}`)
const decrypted = decryptReceivedChunk(dhSecret, nonce, bodyArr, chunkDigest)
console.log(`[WORKER-DBG] decrypted chunk=${chunkNo} len=${decrypted.length} [0..8]=${_whex(decrypted)} [-8..]=${_whex(decrypted.slice(-8))}`)
if (useMemory) {
memoryChunks.set(chunkNo, decrypted)
@@ -113,7 +110,6 @@ async function handleDecryptAndStore(
currentDownloadOffset += decrypted.length
chunkMeta.set(chunkNo, {offset, size: decrypted.length})
const written = downloadWriteHandle.write(decrypted, {at: offset})
console.log(`[WORKER-DBG] OPFS write chunk=${chunkNo} offset=${offset} size=${decrypted.length} written=${written}`)
if (written !== decrypted.length) {
console.warn(`[WORKER] OPFS write failed chunk=${chunkNo}: ${written}/${decrypted.length}, falling back to in-memory storage`)
@@ -140,21 +136,12 @@ async function handleDecryptAndStore(
downloadWriteHandle.flush()
// Verify: read back and compare first/last 8 bytes
const verifyBuf = new Uint8Array(Math.min(8, decrypted.length))
downloadWriteHandle.read(verifyBuf, {at: offset})
const verifyEnd = new Uint8Array(Math.min(8, decrypted.length))
downloadWriteHandle.read(verifyEnd, {at: offset + decrypted.length - verifyEnd.length})
console.log(`[WORKER-DBG] OPFS verify chunk=${chunkNo} readBack[0..8]=${_whex(verifyBuf)} readBack[-8..]=${_whex(verifyEnd)} expected[0..8]=${_whex(decrypted)} expected[-8..]=${_whex(decrypted.slice(-8))}`)
self.postMessage({id, type: 'stored'})
}
async function handleVerifyAndDecrypt(
id: number, size: number, digest: Uint8Array, key: Uint8Array, nonce: Uint8Array
) {
console.log(`[WORKER-DBG] verify: expectedSize=${size} expectedDigest=${_whex(digest, 64)} useMemory=${useMemory} chunkMeta.size=${chunkMeta.size} memoryChunks.size=${memoryChunks.size}`)
// Read chunks — from memory (fallback) or OPFS
const chunks: Uint8Array[] = []
let totalSize = 0
@@ -162,8 +149,7 @@ async function handleVerifyAndDecrypt(
let done = 0
if (useMemory) {
const sorted = [...memoryChunks.entries()].sort((a, b) => a[0] - b[0])
for (const [chunkNo, data] of sorted) {
console.log(`[WORKER-DBG] verify memory chunk=${chunkNo} size=${data.length}`)
for (const [, data] of sorted) {
chunks.push(data)
totalSize += data.length
done += data.length
@@ -179,12 +165,10 @@ async function handleVerifyAndDecrypt(
const dir = await getSessionDir()
const fileHandle = await dir.getFileHandle('download.bin')
const readHandle = await fileHandle.createSyncAccessHandle()
console.log(`[WORKER-DBG] verify: OPFS file size=${readHandle.getSize()}`)
const sortedEntries = [...chunkMeta.entries()].sort((a, b) => a[0] - b[0])
for (const [chunkNo, meta] of sortedEntries) {
for (const [, meta] of sortedEntries) {
const buf = new Uint8Array(meta.size)
const bytesRead = readHandle.read(buf, {at: meta.offset})
console.log(`[WORKER-DBG] verify read chunk=${chunkNo} offset=${meta.offset} size=${meta.size} bytesRead=${bytesRead} [0..8]=${_whex(buf)} [-8..]=${_whex(buf.slice(-8))}`)
readHandle.read(buf, {at: meta.offset})
chunks.push(buf)
totalSize += meta.size
done += meta.size
@@ -212,20 +196,9 @@ async function handleVerifyAndDecrypt(
}
const actualDigest = sodium.crypto_hash_sha512_final(state)
if (!digestEqual(actualDigest, digest)) {
console.error(`[WORKER-DBG] DIGEST MISMATCH: expected=${_whex(digest, 64)} actual=${_whex(actualDigest, 64)} chunks=${chunks.length} totalSize=${totalSize}`)
const state2 = sodium.crypto_hash_sha512_init() as unknown as import('libsodium-wrappers').StateAddress
for (let i = 0; i < chunks.length; i++) {
const chunk = chunks[i]
for (let off = 0; off < chunk.length; off += hashSEG) {
sodium.crypto_hash_sha512_update(state2, chunk.subarray(off, Math.min(off + hashSEG, chunk.length)))
}
const chunkDigest = sha512Streaming([chunk])
console.error(`[WORKER-DBG] chunk[${i}] size=${chunk.length} sha512=${_whex(chunkDigest, 32)}… [0..8]=${_whex(chunk)} [-8..]=${_whex(chunk.slice(-8))}`)
}
self.postMessage({id, type: 'error', message: 'File digest mismatch'})
return
}
console.log(`[WORKER-DBG] verify: digest OK`)
// File-level decrypt with byte-level progress
const result = decryptChunks(BigInt(size), chunks, key, nonce, (d) => {
@@ -312,10 +285,6 @@ self.onmessage = (e: MessageEvent) => {
// ── Helpers ─────────────────────────────────────────────────────
function _whex(b: Uint8Array, n = 8): string {
return Array.from(b.slice(0, n)).map(x => x.toString(16).padStart(2, '0')).join('')
}
function digestEqual(a: Uint8Array, b: Uint8Array): boolean {
if (a.length !== b.length) return false
let diff = 0