v2: richtungsgetrennte Schluessel, Offset im Schluessel, PWA

- Neues Schluesselmodell: pro Beziehung zwei Stroeme (send.key/recv.key),
  Sende-Offset lebt im Schluessel-Header und wird beim Senden fortgeschrieben.
  Loest two-time-pad bei bidirektionaler Nutzung deterministisch.
- Stream-Identifier im Bild -> falscher Schluessel wird erkannt statt
  Kauderwelsch zu liefern. Rollen-Guards (send/recv) gegen Fehlbenutzung.
- CLI genkey erzeugt gerichtetes Keypaar (meine_seite/partner_seite).
- Web-App auf v2 umgestellt; Senden liefert aktualisierten send.key zum
  Download (Browser-Sandbox kann nicht auf den Stick schreiben).
- PWA: manifest + service worker (offline) + icons.
- Interop CLI<->Web in beide Richtungen per Node-Test abgesichert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 21:46:06 +00:00
parent ad0b309acf
commit 30127db247
9 changed files with 549 additions and 469 deletions

View File

@@ -1,46 +1,41 @@
// Interop-Test: lädt EXAKT den Core-Block aus der ausgelieferten index.html
// und prüft Byte-Kompatibilität mit dem Python-CLI in beide Richtungen.
import { readFileSync, writeFileSync } from "node:fs";
// Interop-Test v2: lädt EXAKT den Core aus der ausgelieferten index.html und
// prüft Byte-Kompatibilität mit dem Python-CLI (richtungsgetrennte Schlüssel).
import { readFileSync, writeFileSync } from 'node:fs';
const html = readFileSync(new URL("./index.html", import.meta.url), "utf8");
const U = (p) => new Uint8Array(readFileSync(new URL(p, import.meta.url)));
const html = readFileSync(new URL('./index.html', import.meta.url), 'utf8');
const core = html.match(/==CORE START==.*?\*\/([\s\S]*?)\/\*\s*==CORE END==/)[1];
// Core in diesem Modul-Scope ausführen; er hängt SP an globalThis.
new Function(core)();
const SP = globalThis.SP;
let fails = 0;
const ok = (c, m) => { console.log((c ? "" : "") + m); if (!c) fails++; };
const ok = (c, m) => { console.log((c ? '' : '') + m); if (!c) fails++; };
// ---------- A: CLI -> Web (mit dem CLI erzeugtes Bild im Browser-Core aufdecken)
// A: CLI (send.key) -> Web-Core (recv.key)
{
const dims = readFileSync(new URL("./_a_dims.txt", import.meta.url), "utf8").trim();
const rgb = new Uint8Array(readFileSync(new URL("./_a_rgb.bin", import.meta.url)));
const pad = new Uint8Array(readFileSync(new URL("../pad.key", import.meta.url)));
const expected = "Treffpunkt morgen 18 Uhr am alten Hafen. Bring das Buch mit. 🕵️";
const { offset, ciphertext } = SP.extract(rgb);
const plain = SP.otpXor(ciphertext, pad, offset);
const rgb = U('./_a_rgb.bin');
const recv = SP.parseKey(U('../t2/rel_partner_seite/recv.key'));
const info = SP.extract(rgb);
const plain = SP.otpXor(info.ciphertext, recv.material, info.offset);
const text = new TextDecoder().decode(plain);
console.log("A) CLI → Web (dims " + dims + ")");
ok(offset === 0, "Offset aus Bild gelesen = 0");
ok(text === expected, "Klartext stimmt: " + JSON.stringify(text));
console.log('A) CLI → Web');
ok(SP.sameStream(info.streamId, recv.streamId), 'stream_id passt zum recv.key');
ok(text === 'MsgA vom CLI 🕵️', 'Klartext: ' + JSON.stringify(text));
}
// ---------- B: Web -> CLI (im Browser-Core einbetten, vom CLI aufdecken lassen)
// B: Web-Core (send.key) -> CLI (recv.key)
{
const [w, h] = readFileSync(new URL("./_b_dims.txt", import.meta.url), "utf8").trim().split(" ").map(Number);
const rgb = new Uint8Array(readFileSync(new URL("./_b_rgb.bin", import.meta.url)));
const pad = new Uint8Array(readFileSync(new URL("../pad.key", import.meta.url)));
const msg = "Nachricht direkt aus dem Browser-Core 🌐 — Interop!";
const offset = 500; // frischer Pad-Bereich
const rgb = U('./_b_rgb.bin');
const send = SP.parseKey(U('../t2/rel_partner_seite/send.key'));
const msg = 'MsgB aus dem Browser-Core 🌐';
ok(send.role === SP.ROLE_SEND, 'partner send.key hat Rolle SEND');
const plain = new TextEncoder().encode(msg);
const ct = SP.otpXor(plain, pad, offset);
const payload = SP.buildPayload(offset, ct);
const ct = SP.otpXor(plain, send.material, send.sendOffset);
const payload = SP.buildStego(send.streamId, send.sendOffset, ct);
SP.embed(rgb, payload);
writeFileSync(new URL("./_b_out_rgb.bin", import.meta.url), rgb);
console.log("B) Web → CLI");
ok(true, `eingebettet (${plain.length} B ab Offset ${offset}) → _b_out_rgb.bin (${w}x${h})`);
writeFileSync(new URL('./_b_out_rgb.bin', import.meta.url), rgb);
console.log('B) Web → CLI');
ok(true, `eingebettet (${plain.length} B ab Offset ${send.sendOffset}) → _b_out_rgb.bin`);
}
process.exit(fails ? 1 : 0);