mirror of
https://github.com/simplex-chat/simplexmq.git
synced 2026-06-07 08:51:59 +00:00
async non-blocking encryptFileForUpload with 64KB yielding
This commit is contained in:
+1
-1
@@ -28,7 +28,7 @@ const servers = [
|
||||
parseXFTPServer("xftp://server2..."),
|
||||
parseXFTPServer("xftp://server3..."),
|
||||
]
|
||||
const encrypted = encryptFileForUpload(fileBytes, "photo.jpg")
|
||||
const encrypted = await encryptFileForUpload(fileBytes, "photo.jpg")
|
||||
const {rcvDescriptions, sndDescription, uri} = await uploadFile(agent, servers, encrypted, {
|
||||
onProgress: (uploaded, total) => console.log(`${uploaded}/${total}`),
|
||||
})
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// file descriptions, and DEFLATE-compressed URI encoding.
|
||||
|
||||
import pako from "pako"
|
||||
import {encryptFile, encodeFileHeader} from "./crypto/file.js"
|
||||
import {encryptFileAsync, encodeFileHeader} from "./crypto/file.js"
|
||||
import {generateEd25519KeyPair, encodePubKeyEd25519, encodePrivKeyEd25519, decodePrivKeyEd25519, ed25519KeyPairFromSeed} from "./crypto/keys.js"
|
||||
import {sha512Streaming} from "./crypto/digest.js"
|
||||
import {prepareChunkSizes, prepareChunkSpecs, getChunkDigest, fileSizeLen, authTagSize} from "./protocol/chunks.js"
|
||||
@@ -79,7 +79,11 @@ export function decodeDescriptionURI(fragment: string): FileDescription {
|
||||
|
||||
// -- Upload
|
||||
|
||||
export function encryptFileForUpload(source: Uint8Array, fileName: string): EncryptedFileInfo {
|
||||
export async function encryptFileForUpload(
|
||||
source: Uint8Array,
|
||||
fileName: string,
|
||||
onProgress?: (done: number, total: number) => void
|
||||
): Promise<EncryptedFileInfo> {
|
||||
const key = new Uint8Array(32)
|
||||
const nonce = new Uint8Array(24)
|
||||
crypto.getRandomValues(key)
|
||||
@@ -89,7 +93,7 @@ export function encryptFileForUpload(source: Uint8Array, fileName: string): Encr
|
||||
const payloadSize = Number(fileSize) + fileSizeLen + authTagSize
|
||||
const chunkSizes = prepareChunkSizes(payloadSize)
|
||||
const encSize = BigInt(chunkSizes.reduce((a, b) => a + b, 0))
|
||||
const encData = encryptFile(source, fileHdr, key, nonce, fileSize, encSize)
|
||||
const encData = await encryptFileAsync(source, fileHdr, key, nonce, fileSize, encSize, onProgress)
|
||||
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}
|
||||
@@ -229,7 +233,7 @@ async function uploadRedirectDescription(
|
||||
): Promise<FileDescription> {
|
||||
const yaml = encodeFileDescription(innerFd)
|
||||
const yamlBytes = new TextEncoder().encode(yaml)
|
||||
const enc = encryptFileForUpload(yamlBytes, "")
|
||||
const enc = await encryptFileForUpload(yamlBytes, "")
|
||||
const specs = prepareChunkSpecs(enc.chunkSizes)
|
||||
|
||||
const chunkJobs = specs.map((spec, i) => ({
|
||||
|
||||
@@ -69,6 +69,48 @@ export function encryptFile(
|
||||
return concatBytes(hdr, encSource, encPad, tag)
|
||||
}
|
||||
|
||||
// Async variant: encrypts source in 64KB slices, yielding between each to avoid blocking the main thread.
|
||||
// Produces identical output to encryptFile.
|
||||
const ENCRYPT_SLICE = 65536
|
||||
|
||||
export async function encryptFileAsync(
|
||||
source: Uint8Array,
|
||||
fileHdr: Uint8Array,
|
||||
key: Uint8Array,
|
||||
nonce: Uint8Array,
|
||||
fileSize: bigint,
|
||||
encSize: bigint,
|
||||
onProgress?: (done: number, total: number) => void
|
||||
): Promise<Uint8Array> {
|
||||
const state = sbInit(key, nonce)
|
||||
const lenStr = encodeInt64(fileSize)
|
||||
const padLen = Number(encSize - AUTH_TAG_SIZE - fileSize - 8n)
|
||||
if (padLen < 0) throw new Error("encryptFile: encSize too small")
|
||||
const totalOut = Number(encSize)
|
||||
const out = new Uint8Array(totalOut)
|
||||
let outOff = 0
|
||||
// Header (small, no yield needed)
|
||||
const hdr = sbEncryptChunk(state, concatBytes(lenStr, fileHdr))
|
||||
out.set(hdr, outOff); outOff += hdr.length
|
||||
// Source in 64KB slices, yielding between each
|
||||
for (let off = 0; off < source.length; off += ENCRYPT_SLICE) {
|
||||
const end = Math.min(off + ENCRYPT_SLICE, source.length)
|
||||
const enc = sbEncryptChunk(state, source.subarray(off, end))
|
||||
out.set(enc, outOff); outOff += enc.length
|
||||
onProgress?.(end, source.length)
|
||||
await new Promise<void>(r => setTimeout(r, 0))
|
||||
}
|
||||
// Padding (small, no yield needed)
|
||||
const padding = new Uint8Array(padLen)
|
||||
padding.fill(0x23)
|
||||
const encPad = sbEncryptChunk(state, padding)
|
||||
out.set(encPad, outOff); outOff += encPad.length
|
||||
// Auth tag
|
||||
const tag = sbAuth(state)
|
||||
out.set(tag, outOff)
|
||||
return out
|
||||
}
|
||||
|
||||
// -- Decryption (FileTransfer.Crypto:decryptChunks)
|
||||
|
||||
// Decrypt one or more XFTP chunks into a FileHeader and file content.
|
||||
|
||||
Reference in New Issue
Block a user