From 7dfce95380029e0583fb01581f2b137ee9faa9a7 Mon Sep 17 00:00:00 2001 From: Forgata Date: Fri, 6 Mar 2026 08:30:24 +0200 Subject: [PATCH 01/22] install hash and cipher utilities from @noble add ronomon reed-solomom --- package-lock.json | 43 +++++++++++++++++++++++++++++++++++++++++++ package.json | 3 +++ 2 files changed, 46 insertions(+) diff --git a/package-lock.json b/package-lock.json index 6b2a7a5..4b93166 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,10 @@ "version": "0.0.0", "license": "MIT", "dependencies": { + "@noble/ciphers": "^2.1.1", + "@noble/hashes": "^2.0.1", "@picovoice/pvrecorder-node": "^1.2.8", + "@ronomon/reed-solomon": "^6.0.0", "chalk": "^5.6.2", "fft.js": "^4.0.4" }, @@ -19,6 +22,30 @@ "typescript": "^5.9.3" } }, + "node_modules/@noble/ciphers": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.1.1.tgz", + "integrity": "sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", + "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@picovoice/pvrecorder-node": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@picovoice/pvrecorder-node/-/pvrecorder-node-1.2.8.tgz", @@ -28,6 +55,22 @@ "node": ">=18.0.0" } }, + "node_modules/@ronomon/queue": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@ronomon/queue/-/queue-3.0.1.tgz", + "integrity": "sha512-STcqSvk+c7ArMrZgYxhM92p6O6F7t0SUbGr+zm8s9fJple5EdJAMwP3dXqgdXeF95xWhBpha5kjEqNAIdI0r4w==", + "license": "MIT" + }, + "node_modules/@ronomon/reed-solomon": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@ronomon/reed-solomon/-/reed-solomon-6.0.0.tgz", + "integrity": "sha512-Tb2mRSQaNQBt11tsHeIR8Pjc/Or3TwtgWb+ta1sf9tPmyCUg4D1p1AUq5X1VNir/FtTvhYVd3qtNpmQcBVktfg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@ronomon/queue": "^3.0.1" + } + }, "node_modules/@types/node": { "version": "25.3.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz", diff --git a/package.json b/package.json index 3777d04..47bc317 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,10 @@ "typescript": "^5.9.3" }, "dependencies": { + "@noble/ciphers": "^2.1.1", + "@noble/hashes": "^2.0.1", "@picovoice/pvrecorder-node": "^1.2.8", + "@ronomon/reed-solomon": "^6.0.0", "chalk": "^5.6.2", "fft.js": "^4.0.4" } From 79bc7c6ed3c2dc363e783f30c2e21fc01171b23b Mon Sep 17 00:00:00 2001 From: Forgata Date: Fri, 6 Mar 2026 10:49:40 +0200 Subject: [PATCH 02/22] update ignorefile to exlcude the data folder that will keep the payload to be encrypted --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9acfdcc..1fdad4c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules dist/ build/ .env +data/ From ed3ef5530af413577796a1b48369327ab9bf3b39 Mon Sep 17 00:00:00 2001 From: Forgata Date: Fri, 6 Mar 2026 10:50:56 +0200 Subject: [PATCH 03/22] feat: add function to prepend a high-entropy sync pattern to the bitstream and return the synchronised bitstream --- src/core/embedding/bitstream/preamble.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/core/embedding/bitstream/preamble.ts diff --git a/src/core/embedding/bitstream/preamble.ts b/src/core/embedding/bitstream/preamble.ts new file mode 100644 index 0000000..75ab1c4 --- /dev/null +++ b/src/core/embedding/bitstream/preamble.ts @@ -0,0 +1,21 @@ +/** + * Sync Preamble + * Prepends a high-entropy sync pattern to the bitstream. + */ + +export function injectPreamble(payloadBits: Uint8Array) { + const PREAMBLE = new Uint8Array([ + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, + 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, + 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, + 1, 0, 1, 1, 1, + ]); + + const totalBits = PREAMBLE.length + payloadBits.length; + const syncStream = new Uint8Array(totalBits); + + syncStream.set(PREAMBLE, 0); + syncStream.set(payloadBits, PREAMBLE.length); + + return syncStream; +} From 8f059e12602b65185ebcd634ce3c107aad5c3ec6 Mon Sep 17 00:00:00 2001 From: Forgata Date: Fri, 6 Mar 2026 10:52:29 +0200 Subject: [PATCH 04/22] feat: add function to convert bytes to bit for preamble injection by unpacking the bytes into a Uint8[] --- src/core/embedding/bitstream/serialiser.ts | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/core/embedding/bitstream/serialiser.ts diff --git a/src/core/embedding/bitstream/serialiser.ts b/src/core/embedding/bitstream/serialiser.ts new file mode 100644 index 0000000..350d9e8 --- /dev/null +++ b/src/core/embedding/bitstream/serialiser.ts @@ -0,0 +1,24 @@ +/** + * Bitstream Serialisation + * Unpacks bytes into an array of bits + */ + +export function serialiseBits(interleadShards: Uint8Array[]): Uint8Array { + const totalBytes = interleadShards.reduce( + (acc, shard) => acc + shard.length, + 0, + ); + const bitstream = new Uint8Array(totalBytes * 8); + + let bitIndex = 0; + for (const shard of interleadShards) { + for (let i = 0; i < shard.length; i++) { + const byte = shard[i]; + + for (let shift = 0; shift < 8; shift++) { + bitstream[bitIndex++] = (byte! >> shift) & 1; + } + } + } + return bitstream; +} From 06a173001512f6285f90f29833ba1f242af6fb45 Mon Sep 17 00:00:00 2001 From: Forgata Date: Fri, 6 Mar 2026 10:53:55 +0200 Subject: [PATCH 05/22] feat: add GCM encryption espectially AES-GCM encryption of the payload and authTag --- src/core/embedding/crypto/aes.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/core/embedding/crypto/aes.ts diff --git a/src/core/embedding/crypto/aes.ts b/src/core/embedding/crypto/aes.ts new file mode 100644 index 0000000..b0e9bc3 --- /dev/null +++ b/src/core/embedding/crypto/aes.ts @@ -0,0 +1,21 @@ +import { createCipheriv, randomBytes } from "node:crypto"; + +/** + * AES-256-GCM Encryption + * Encrypts the framed payload and adds the authentication tag. + * @returns packet of @type Uint8Array + */ + +export function encryptPayload(framedPayload: Uint8Array, key: Uint8Array) { + const nonce = randomBytes(12); + const cipher = createCipheriv("aes-256-gcm", key, nonce); + + const cipherText = Buffer.concat([ + cipher.update(framedPayload), + cipher.final(), + ]); + const authTag = cipher.getAuthTag(); + + const encryptedPacket = Buffer.concat([nonce, cipherText, authTag]); + return new Uint8Array(encryptedPacket); +} From 4805d40d2388c23518efb9a14c8c0c0be651aa81 Mon Sep 17 00:00:00 2001 From: Forgata Date: Fri, 6 Mar 2026 10:55:08 +0200 Subject: [PATCH 06/22] feat: add function to break encrypted blob into chunks to ease reconstruction by receiver --- src/core/embedding/payload/packer.ts | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/core/embedding/payload/packer.ts diff --git a/src/core/embedding/payload/packer.ts b/src/core/embedding/payload/packer.ts new file mode 100644 index 0000000..48f67dd --- /dev/null +++ b/src/core/embedding/payload/packer.ts @@ -0,0 +1,32 @@ +/** + * Packetization + * Slices the encrypted payload into manageable frames with IDs. + */ + +export function packetize( + encryptedData: Uint8Array, + frameSize: number = 512, +): Uint8Array[] { + const frames: Uint8Array[] = []; + const totalBytes = encryptedData.length; + + let offset = 0; + let frameId = 0; + + while (offset < totalBytes) { + const end = Math.min(offset + frameSize, totalBytes); + const chunk = encryptedData.slice(offset, end); + + const frame = new Uint8Array(4 + chunk.length); + const view = new DataView(frame.buffer); + + view.setUint32(0, frameId, false); + frame.set(chunk, 4); + frames.push(frame); + + offset += frameSize; + frameId++; + } + + return frames; +} From 8eb231603187aaefd825773b8a42a4961ff8bd97 Mon Sep 17 00:00:00 2001 From: Forgata Date: Fri, 6 Mar 2026 10:56:14 +0200 Subject: [PATCH 07/22] feat: turn packets into a redundant array using reed-solomon encoding --- src/core/embedding/fec/readSolomon.ts | 45 +++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/core/embedding/fec/readSolomon.ts diff --git a/src/core/embedding/fec/readSolomon.ts b/src/core/embedding/fec/readSolomon.ts new file mode 100644 index 0000000..31c7df5 --- /dev/null +++ b/src/core/embedding/fec/readSolomon.ts @@ -0,0 +1,45 @@ +const RS = require("@ronomon/reed-solomon"); + +/** + * Forward Error Correction (FEC) + * + * Groups packets into blocks and adds shard for recovery + * + * @param packets + * @param dataShards + * @param parityShards + */ + +export function applyFEC( + packets: Uint8Array[], + dataShards: number, + parityShards: number = 3, +) { + const totalShard = dataShards + parityShards; + const encodedStream: Uint8Array[] = []; + + for (let i = 0; i < packets.length; i += dataShards) { + const block = packets.slice(i, i + dataShards); + + while (block.length < totalShard) { + block.push(new Uint8Array(packets[0]!.length).fill(0)); + } + + const shardLength = block[0]!.length; + const buffer = Buffer.alloc(totalShard * shardLength); + + for (let j = 0; j < totalShard; j++) { + Buffer.from(block[j]!).copy(buffer, j * shardLength); + } + + RS.encode(buffer, dataShards, parityShards); + + for (let k = 0; k < totalShard; k++) { + const shard = new Uint8Array(shardLength); + shard.set(buffer.subarray(k * shardLength, (k + 1) * shardLength)); + encodedStream.push(shard); + } + } + + return encodedStream; +} From 17bac98dd616f7afcb3cc516d0fefe1fa2f32edb Mon Sep 17 00:00:00 2001 From: Forgata Date: Fri, 6 Mar 2026 10:57:06 +0200 Subject: [PATCH 08/22] feat: derive key using the crypto's pbkdf2 --- src/core/embedding/crypto/keyDerivation.ts | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/core/embedding/crypto/keyDerivation.ts diff --git a/src/core/embedding/crypto/keyDerivation.ts b/src/core/embedding/crypto/keyDerivation.ts new file mode 100644 index 0000000..49c0b06 --- /dev/null +++ b/src/core/embedding/crypto/keyDerivation.ts @@ -0,0 +1,27 @@ +import { pbkdf2 } from "node:crypto"; +import { promisify } from "node:util"; + +const pbkdf2Async = promisify(pbkdf2); + +/** + * Key Derivation + * Transforms a human password into a high-entropy 256-bit key. + */ +export async function deriveKey(password: string, salt: Uint8Array) { + const iterations = 600_000; + const keyLength = 32; + const digest = "sha256"; + + try { + const derivedKey = await pbkdf2Async( + password, + salt, + iterations, + keyLength, + digest, + ); + return new Uint8Array(derivedKey); + } catch (error) { + throw new Error("Failed to derive key"); + } +} From dd3f8772b225374116bc3ac0937f5aa5922ce95b Mon Sep 17 00:00:00 2001 From: Forgata Date: Fri, 6 Mar 2026 10:58:20 +0200 Subject: [PATCH 09/22] add function to prevent acoustic event from wiping out the entire RS block --- src/core/embedding/fec/interleave.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/core/embedding/fec/interleave.ts diff --git a/src/core/embedding/fec/interleave.ts b/src/core/embedding/fec/interleave.ts new file mode 100644 index 0000000..485e88a --- /dev/null +++ b/src/core/embedding/fec/interleave.ts @@ -0,0 +1,28 @@ +/** + * Interleaving + * Reorders shards so that consecutive physical errors are distributed + * across different logical RS blocks. + */ + +export function interleave( + shards: Uint8Array[], + dataShards: number, + parityShards: number, +) { + const totalShardsPerBlock = dataShards + parityShards; + const numBlocks = Math.ceil(shards.length / totalShardsPerBlock); + const interleaved: Uint8Array[] = new Array(shards.length); + + let index = 0; + for (let col = 0; col < totalShardsPerBlock; col++) { + for (let row = 0; row < numBlocks; row++) { + const sourceIdx = row * totalShardsPerBlock + col; + + if (sourceIdx < shards.length) { + interleaved[index++] = shards[sourceIdx]!; + } + } + } + + return interleaved; +} From 9043854d36fb979cbb66f2fdc2a92b5b3d62e343 Mon Sep 17 00:00:00 2001 From: Forgata Date: Fri, 6 Mar 2026 11:00:15 +0200 Subject: [PATCH 10/22] feat: added packet framing using the DataView for ease parsing later --- src/core/embedding/payload/framing.ts | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/core/embedding/payload/framing.ts diff --git a/src/core/embedding/payload/framing.ts b/src/core/embedding/payload/framing.ts new file mode 100644 index 0000000..47704f7 --- /dev/null +++ b/src/core/embedding/payload/framing.ts @@ -0,0 +1,28 @@ +/** + * Payload Framing + * Wraps raw bytes with a protocol header for identification and reconstruction. + * @returns packet of @type Uint8Array + */ + +export function framePayload(filebytes: Uint8Array, filename: string) { + const encoder = new TextEncoder(); + const filenameBytes = encoder.encode(filename); + + if (filenameBytes.length > 255) { + throw new Error("Filename too long"); + } + + const headerSize = 10; + const totalSize = headerSize + filenameBytes.length + filebytes.length; + const packet = new Uint8Array(totalSize); + const view = new DataView(packet.buffer); + + view.setUint32(4, 0x44484944, false); + view.setUint32(4, 0x01); + view.setUint8(5, filenameBytes.length); + view.setUint32(6, filebytes.length, false); + + packet.set(filenameBytes, headerSize); + packet.set(filebytes, headerSize + filenameBytes.length); + return packet; +} From 80342e2d0903c9062e57b21eb69ad074906a2aee Mon Sep 17 00:00:00 2001 From: Forgata Date: Fri, 6 Mar 2026 11:01:23 +0200 Subject: [PATCH 11/22] feat: add payload controller to bridge payload functionality in modular approach --- src/core/embedding/generator.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/core/embedding/generator.ts diff --git a/src/core/embedding/generator.ts b/src/core/embedding/generator.ts new file mode 100644 index 0000000..4b9b353 --- /dev/null +++ b/src/core/embedding/generator.ts @@ -0,0 +1,32 @@ +import { randomBytes } from "node:crypto"; +import { framePayload } from "./payload/framing.js"; +import { loadFileToUint8 } from "./payload/Uint8FileReader.js"; +import { deriveKey } from "./crypto/keyDerivation.js"; +import { encryptPayload } from "./crypto/aes.js"; +import { packetize } from "./payload/packer.js"; +import { applyFEC } from "./fec/readSolomon.js"; +import { interleave } from "./fec/interleave.js"; +import { serialiseBits } from "./bitstream/serialiser.js"; +import { injectPreamble } from "./bitstream/preamble.js"; + +export async function preparePayload(filename: string, password: string) { + const rawBytes = await loadFileToUint8(filename); + const framed = framePayload(rawBytes, filename); + + const salt = randomBytes(16); + const key = await deriveKey(password, salt); + + const encrypted = encryptPayload(framed, key); + + const packets = packetize(encrypted, 256); + + const FEC_SHARDS = applyFEC(packets, 6, 3); + + const interleaved = interleave(FEC_SHARDS, 6, 3); + + const payloadBits = serialiseBits(interleaved); + + const finalBitStream = injectPreamble(payloadBits); + + return { finalBitStream, salt }; +} From a2805e63beee8ca1f45d2e34f94f43a71245a72c Mon Sep 17 00:00:00 2001 From: Forgata Date: Fri, 6 Mar 2026 11:02:05 +0200 Subject: [PATCH 12/22] add function to pipe raw bytes into an uint8 --- src/core/embedding/payload/Uint8FileReader.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/core/embedding/payload/Uint8FileReader.ts diff --git a/src/core/embedding/payload/Uint8FileReader.ts b/src/core/embedding/payload/Uint8FileReader.ts new file mode 100644 index 0000000..d0cb3be --- /dev/null +++ b/src/core/embedding/payload/Uint8FileReader.ts @@ -0,0 +1,11 @@ +import { readFile } from "node:fs/promises"; +import path from "node:path"; +export async function loadFileToUint8(filename: string): Promise { + const filepath = path.join(process.cwd(), "data", filename); + try { + const buffer = await readFile(filepath); + return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength); + } catch (error) { + throw new Error(`Failed to read file: ${filepath}`); + } +} From 3a02d4852b05737a6ab1f05b6c96e8a254053e56 Mon Sep 17 00:00:00 2001 From: Forgata Date: Fri, 6 Mar 2026 11:02:42 +0200 Subject: [PATCH 13/22] refactor: add start function and invoked it --- src/main.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/main.ts b/src/main.ts index 1907e9f..8d5dd6e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,12 +1,19 @@ +import { preparePayload } from "./core/embedding/generator.js"; import { recorder } from "./core/profiler/recorder.js"; import { initialise } from "./utils/initialise.js"; -try { - initialise(); +async function start() { + try { + initialise(); + console.log("Preparing Bit Stream..."); + const { finalBitStream } = await preparePayload("file.txt", "1234"); - await recorder(); -} catch (error: unknown) { - console.error(error); + await recorder(); + } catch (error: unknown) { + console.error(error); + } } process.on("SIGINT", () => process.exit()); + +await start(); From 097b5773802e8dfb23684bcdaac4b292de7e0be9 Mon Sep 17 00:00:00 2001 From: Forgata Date: Sat, 7 Mar 2026 15:08:24 +0200 Subject: [PATCH 14/22] change: swap out @ronomon's reed solomon for sunspace's wasm implementation --- package-lock.json | 25 ++++++++++++------------- package.json | 2 +- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4b93166..244a372 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@noble/ciphers": "^2.1.1", "@noble/hashes": "^2.0.1", "@picovoice/pvrecorder-node": "^1.2.8", - "@ronomon/reed-solomon": "^6.0.0", + "@subspace/reed-solomon-erasure.wasm": "^0.2.5", "chalk": "^5.6.2", "fft.js": "^4.0.4" }, @@ -55,22 +55,21 @@ "node": ">=18.0.0" } }, - "node_modules/@ronomon/queue": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@ronomon/queue/-/queue-3.0.1.tgz", - "integrity": "sha512-STcqSvk+c7ArMrZgYxhM92p6O6F7t0SUbGr+zm8s9fJple5EdJAMwP3dXqgdXeF95xWhBpha5kjEqNAIdI0r4w==", - "license": "MIT" - }, - "node_modules/@ronomon/reed-solomon": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@ronomon/reed-solomon/-/reed-solomon-6.0.0.tgz", - "integrity": "sha512-Tb2mRSQaNQBt11tsHeIR8Pjc/Or3TwtgWb+ta1sf9tPmyCUg4D1p1AUq5X1VNir/FtTvhYVd3qtNpmQcBVktfg==", - "hasInstallScript": true, + "node_modules/@subspace/reed-solomon-erasure.wasm": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@subspace/reed-solomon-erasure.wasm/-/reed-solomon-erasure.wasm-0.2.5.tgz", + "integrity": "sha512-dMAvEY2Z1Txquat0Ur2424sqz18YRD4OwXihiLxCLTKl5zJvH1/LuuUPOYl5kqu+M41j0TTzscvhXHuK+GiOoQ==", "license": "MIT", "dependencies": { - "@ronomon/queue": "^3.0.1" + "@types/node": "^12.7.5" } }, + "node_modules/@subspace/reed-solomon-erasure.wasm/node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "25.3.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz", diff --git a/package.json b/package.json index 47bc317..77ae10c 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@noble/ciphers": "^2.1.1", "@noble/hashes": "^2.0.1", "@picovoice/pvrecorder-node": "^1.2.8", - "@ronomon/reed-solomon": "^6.0.0", + "@subspace/reed-solomon-erasure.wasm": "^0.2.5", "chalk": "^5.6.2", "fft.js": "^4.0.4" } From aa70dc1e964f4730a92b15f03100b14804569028 Mon Sep 17 00:00:00 2001 From: Forgata Date: Sat, 7 Mar 2026 15:09:23 +0200 Subject: [PATCH 15/22] add configs to support cjs and module interop --- tsconfig.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index c1dc82b..012c115 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -41,7 +41,9 @@ "noUncheckedSideEffectImports": true, "moduleDetection": "force", "skipLibCheck": true, - "forceConsistentCasingInFileNames": true + "forceConsistentCasingInFileNames": true, + "esModuleInterop": true, + "allowJs": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] From 904738bd777b5fb02fb22b593449a759041a32a0 Mon Sep 17 00:00:00 2001 From: Forgata Date: Sat, 7 Mar 2026 15:10:08 +0200 Subject: [PATCH 16/22] refactor change the recorder function to accept the bit stream from the prepared payload as an arg --- src/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.ts b/src/main.ts index 8d5dd6e..4b56db4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,7 +8,7 @@ async function start() { console.log("Preparing Bit Stream..."); const { finalBitStream } = await preparePayload("file.txt", "1234"); - await recorder(); + await recorder(finalBitStream); } catch (error: unknown) { console.error(error); } From aefa645a0dfef70b5f5e8daeea2582c72b27eab7 Mon Sep 17 00:00:00 2001 From: Forgata Date: Sat, 7 Mar 2026 15:10:38 +0200 Subject: [PATCH 17/22] fix await the FEC function --- src/core/embedding/generator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/embedding/generator.ts b/src/core/embedding/generator.ts index 4b9b353..7c98519 100644 --- a/src/core/embedding/generator.ts +++ b/src/core/embedding/generator.ts @@ -20,7 +20,7 @@ export async function preparePayload(filename: string, password: string) { const packets = packetize(encrypted, 256); - const FEC_SHARDS = applyFEC(packets, 6, 3); + const FEC_SHARDS = await applyFEC(packets, 6, 3); const interleaved = interleave(FEC_SHARDS, 6, 3); From 3529a89122faa168bc7ce016d15245bcc0cc701a Mon Sep 17 00:00:00 2001 From: Forgata Date: Sat, 7 Mar 2026 15:14:27 +0200 Subject: [PATCH 18/22] major feat: implemented the wasm RS by manual checking of erasure instance and using it in the applyFEC refactor: change functin to be aync --- src/core/embedding/fec/readSolomon.ts | 59 ++++++++++++++++++++------- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/src/core/embedding/fec/readSolomon.ts b/src/core/embedding/fec/readSolomon.ts index 31c7df5..a28ae64 100644 --- a/src/core/embedding/fec/readSolomon.ts +++ b/src/core/embedding/fec/readSolomon.ts @@ -1,3 +1,27 @@ +import { ReedSolomonErasure } from "@subspace/reed-solomon-erasure.wasm"; +import fs from "node:fs"; +import path from "node:path"; +import { createRequire } from "node:module"; + +const require = createRequire(import.meta.url); +let rsInstance: ReedSolomonErasure | null = null; + +async function getRSEngine(): Promise { + if (!rsInstance) { + const pkgPath = require.resolve("@subspace/reed-solomon-erasure.wasm"); + const pkgDir = path.dirname(pkgPath); + + const wasmPath = path.join(pkgDir, "reed_solomon_erasure_bg.wasm"); + if (!fs.existsSync(wasmPath)) { + throw new Error(`WASM file missing! Looked for it at: ${wasmPath}`); + } + + const wasmBuffer = fs.readFileSync(wasmPath); + rsInstance = ReedSolomonErasure.fromBytes(wasmBuffer); + } + return rsInstance; +} + const RS = require("@ronomon/reed-solomon"); /** @@ -10,33 +34,40 @@ const RS = require("@ronomon/reed-solomon"); * @param parityShards */ -export function applyFEC( +export async function applyFEC( packets: Uint8Array[], dataShards: number, parityShards: number = 3, -) { - const totalShard = dataShards + parityShards; +): Promise { + if (packets.length === 0) return []; + + const rs = await getRSEngine(); + const shardLength = packets[0]!.length; const encodedStream: Uint8Array[] = []; for (let i = 0; i < packets.length; i += dataShards) { const block = packets.slice(i, i + dataShards); - while (block.length < totalShard) { - block.push(new Uint8Array(packets[0]!.length).fill(0)); + const totalShards = dataShards + parityShards; + const contiguousBuffer = new Uint8Array(totalShards * shardLength); + + for (let j = 0; j < dataShards; j++) { + if (block[j]) { + contiguousBuffer.set(block[j]!, j * shardLength); + } } - const shardLength = block[0]!.length; - const buffer = Buffer.alloc(totalShard * shardLength); + const result = rs.encode(contiguousBuffer, dataShards, parityShards); - for (let j = 0; j < totalShard; j++) { - Buffer.from(block[j]!).copy(buffer, j * shardLength); + if (result !== ReedSolomonErasure.RESULT_OK) { + throw new Error(`WASM FEC Encoding failed with internal code: ${result}`); } - RS.encode(buffer, dataShards, parityShards); - - for (let k = 0; k < totalShard; k++) { - const shard = new Uint8Array(shardLength); - shard.set(buffer.subarray(k * shardLength, (k + 1) * shardLength)); + for (let j = 0; j < totalShards; j++) { + const shard = contiguousBuffer.slice( + j * shardLength, + (j + 1) * shardLength, + ); encodedStream.push(shard); } } From d812d3d816932c7f0e2cc9e803d3f3737aef9e35 Mon Sep 17 00:00:00 2001 From: Forgata Date: Sat, 7 Mar 2026 15:16:01 +0200 Subject: [PATCH 19/22] refactor: remove unnecessary comments and logs. refactor: add bitstream as argument in the recorder function clear logs on injection finish --- src/core/profiler/recorder.ts | 36 ++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/src/core/profiler/recorder.ts b/src/core/profiler/recorder.ts index 71c4b58..d72266e 100644 --- a/src/core/profiler/recorder.ts +++ b/src/core/profiler/recorder.ts @@ -2,9 +2,10 @@ import { PvRecorder } from "@picovoice/pvrecorder-node"; import { buffer } from "../../types/AudioRingBuffer.js"; import { processSTFT } from "./processFrame.js"; -export async function recorder() { +export async function recorder(bitstream: Uint8Array) { const frameSize = 512; const pvRecorder = new PvRecorder(frameSize, -1); + let bitPtr = 0; pvRecorder.start(); @@ -15,15 +16,36 @@ export async function recorder() { if (buffer.size >= 1024) { const maskingMap = processSTFT(buffer); + if (maskingMap.length > 0) { + // console.log( + // "Processed Frame Index:", + // maskingMap[maskingMap.length - 1]!.frameIndex, + // ); + + // console.log( + // "Safe Bins:", + // maskingMap[maskingMap.length - 1]!.safeBins.length, + // ); + + const latestFrame = maskingMap[maskingMap.length - 1]!; + const safebins = latestFrame.safeBins; + + if (safebins.length > 0 && bitPtr < bitstream.length) { + for (const bitIndex of safebins) { + if (bitPtr >= bitstream.length) break; + + const currentBit = bitstream[bitPtr]; + + bitPtr++; + } + } + console.log( - "Processed Frame Index:", - maskingMap[maskingMap.length - 1]!.frameIndex, - ); - console.log( - "Safe Bins:", - maskingMap[maskingMap.length - 1]!.safeBins.length, + `Frame: ${latestFrame.frameIndex} | Safe Bins: ${safebins.length} | Progress: ${bitPtr}/${bitstream.length} bits`, ); + if (bitPtr >= bitstream.length) + console.log("SUCCESS! entire bitstream injected"); } } } From f825802a25f0982e26ff8932ff5c8f56136c0dd9 Mon Sep 17 00:00:00 2001 From: Forgata Date: Sat, 7 Mar 2026 15:18:54 +0200 Subject: [PATCH 20/22] remove comments --- src/core/profiler/recorder.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/core/profiler/recorder.ts b/src/core/profiler/recorder.ts index d72266e..6a74a7d 100644 --- a/src/core/profiler/recorder.ts +++ b/src/core/profiler/recorder.ts @@ -18,16 +18,6 @@ export async function recorder(bitstream: Uint8Array) { const maskingMap = processSTFT(buffer); if (maskingMap.length > 0) { - // console.log( - // "Processed Frame Index:", - // maskingMap[maskingMap.length - 1]!.frameIndex, - // ); - - // console.log( - // "Safe Bins:", - // maskingMap[maskingMap.length - 1]!.safeBins.length, - // ); - const latestFrame = maskingMap[maskingMap.length - 1]!; const safebins = latestFrame.safeBins; From 7c643be640cabf520aab7613fc17002dcad6bd08 Mon Sep 17 00:00:00 2001 From: Forgata Date: Sat, 7 Mar 2026 15:23:52 +0200 Subject: [PATCH 21/22] fix remove old package import --- src/core/embedding/fec/readSolomon.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/core/embedding/fec/readSolomon.ts b/src/core/embedding/fec/readSolomon.ts index a28ae64..204ae60 100644 --- a/src/core/embedding/fec/readSolomon.ts +++ b/src/core/embedding/fec/readSolomon.ts @@ -22,8 +22,6 @@ async function getRSEngine(): Promise { return rsInstance; } -const RS = require("@ronomon/reed-solomon"); - /** * Forward Error Correction (FEC) * From 35e49e1a06a486e4ce5c3ab36508d5bb0b943420 Mon Sep 17 00:00:00 2001 From: Forgata Date: Sun, 8 Mar 2026 13:38:24 +0200 Subject: [PATCH 22/22] remove comments --- src/core/profiler/freqBarkMap.ts | 2 -- src/core/profiler/recorder.ts | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/profiler/freqBarkMap.ts b/src/core/profiler/freqBarkMap.ts index 667bf5e..c5790fa 100644 --- a/src/core/profiler/freqBarkMap.ts +++ b/src/core/profiler/freqBarkMap.ts @@ -27,7 +27,6 @@ export function identifySafeBins( powerSpectrum: Float32Array, thresholds: Float32Array, ) { - // let count = 0; const result: number[] = []; for (let i = 0; i < powerSpectrum.length; i++) { @@ -37,7 +36,6 @@ export function identifySafeBins( if (binPower < bandThreshold) { result.push(i); - // count++; } } return result; diff --git a/src/core/profiler/recorder.ts b/src/core/profiler/recorder.ts index 6a74a7d..5003b13 100644 --- a/src/core/profiler/recorder.ts +++ b/src/core/profiler/recorder.ts @@ -34,8 +34,10 @@ export async function recorder(bitstream: Uint8Array) { console.log( `Frame: ${latestFrame.frameIndex} | Safe Bins: ${safebins.length} | Progress: ${bitPtr}/${bitstream.length} bits`, ); - if (bitPtr >= bitstream.length) + if (bitPtr >= bitstream.length) { console.log("SUCCESS! entire bitstream injected"); + // break; + } } } }