Use 11-character signatures and detect headers across WBR

This commit is contained in:
2026-09-17 22:10:12 -07:00
parent a312b1f58a
commit f8935c656c
24 changed files with 363 additions and 101 deletions

74
sig.js
View File

@@ -9,6 +9,7 @@ export const SIG = {
};
export const CIPHERS = {
0: 'PLAIN',
1: 'SPECK48_96CTR',
2: 'SPECK32_64ECB (insecure)'
};
@@ -17,36 +18,59 @@ export const CIPHER_TO_ID = Object.fromEntries(
Object.entries(CIPHERS).map(([k, v]) => [v, +k])
);
const SIGNATURES = Object.entries(SIG).flatMap(([base, prefix]) =>
Object.entries(CIPHERS).map(([id, cipher]) => {
const digits = (+id).toString(base).padStart(4, '0');
if (digits.length !== 4) throw new RangeError(`Cipher ID ${id} exceeds ZWUS-${base}'s signature capacity`);
const sig = prefix + zwus[base].unifier + zwus[base][0] +
Array.from(digits, d => zwus[base][d]).join('');
if (sig.length !== 11) throw new RangeError('Signatures must contain exactly 11 characters');
return { base, cipher, sig, legacy: false };
})
);
export function makeSig(base, cipher) {
let s = SIG[base];
const id = CIPHER_TO_ID[cipher];
if (id) {
const zwDigits = Array.from(id.toString(base).padStart(3, '0'), d => zwus[base][d]).join('');
s += zwus[base].unifier + zwus[base][0] + zwDigits;
}
return s;
const entry = SIGNATURES.find(s => s.base === String(base) && s.cipher === cipher);
if (!entry) throw new RangeError(`Unsupported signature: ZWUS-${base}, ${cipher}`);
return entry.sig;
}
export function parseSig(text) {
let sigIdx = text.indexOf(SIG_PREFIX), base;
function findSig(text, signatures) {
let sigIdx = text.indexOf(SIG_PREFIX);
while (sigIdx !== -1) {
base = Object.keys(SIG).find(b => text.startsWith(SIG[b], sigIdx));
if (base) break;
sigIdx = text.indexOf(SIG_PREFIX, sigIdx + SIG_PREFIX.length);
const entry = signatures.find(s => text.startsWith(s.sig, sigIdx) &&
(!s.legacyPlain || text[sigIdx + s.sig.length] !== zwus[s.base].unifier));
if (entry) {
const { base, cipher, sig, legacy } = entry;
return { base, cipher, payload: text.slice(sigIdx + sig.length), sigIdx, sigLen: sig.length, legacy };
}
sigIdx = text.indexOf(SIG_PREFIX, sigIdx + 1);
}
if (!base) return null;
const after = text.slice(sigIdx + SIG[base].length);
const barrier = zwus[base].unifier + zwus[base][0];
if (after.startsWith(barrier)) {
const zwDigits = Array.from(after.slice(barrier.length, barrier.length + 3));
const digits = zwDigits.map(z => Object.keys(zwus[base]).find(k => zwus[base][k] === z)).join('');
const cipher = CIPHERS[parseInt(digits, base)];
if (!cipher) return { base, cipher: 'PLAIN', payload: after, sigIdx, sigLen: SIG[base].length };
const payload = after.slice(barrier.length + 3);
const sigLen = SIG[base].length + barrier.length + 3;
return { base, cipher, payload, sigIdx, sigLen };
}
return { base, cipher: 'PLAIN', payload: after, sigIdx, sigLen: SIG[base].length };
return null;
}
// Current format: match only complete, registered 11-character signatures.
export const parseModernSig = text => findSig(text, SIGNATURES);
// Legacy compatibility: remove this registry, reader, and parseSig fallback together.
// Keep the supported bases and cipher IDs frozen; future standards use only the current format.
const LEGACY_SIGNATURES = [3, 6, 7].flatMap(base => [
...[1, 2].map(id => ({
base: String(base), cipher: CIPHERS[id], legacy: true,
sig: SIG[base] + zwus[base].unifier + zwus[base][0].repeat(3) + zwus[base][id]
})),
{ base: String(base), cipher: 'PLAIN', sig: SIG[base], legacy: true, legacyPlain: true }
]);
// Legacy plaintext never starts with a unifier; do not downgrade incomplete/unknown extensions.
export const parseLegacySig = text => findSig(text, LEGACY_SIGNATURES);
export function parseSig(text) {
const modern = parseModernSig(text), legacy = parseLegacySig(text);
if (!modern) return legacy;
// Legacy messages separated by visible text still need their own overlay.
if (legacy && legacy.sigIdx < modern.sigIdx &&
getPayloadEnd(text, legacy.base, legacy.sigIdx + legacy.sigLen) < modern.sigIdx) return legacy;
return modern;
}
export function getPayloadEnd(text, base, startOffset) {