feat: fix CompactSeedQR binary QR code scanning with jsQR library

- Replace BarcodeDetector with jsQR for raw binary byte access
- BarcodeDetector forced UTF-8 decoding which corrupted binary data
- jsQR's binaryData property preserves raw bytes without text conversion
- Fix regex bug: use single backslash \x00 instead of \x00 for binary detection
- Add debug logging for scan data inspection
- QR generation already worked (Krux-compatible), only scanning was broken

Resolves binary QR code scanning for 12/24-word CompactSeedQR format.
Tested with Krux device - full bidirectional compatibility confirmed.
This commit is contained in:
LC mac
2026-02-07 04:22:56 +08:00
parent 49d73a7ae4
commit aa06c9ae27
39 changed files with 4664 additions and 777 deletions

View File

@@ -39,13 +39,22 @@ function getCrypto(): Promise<SubtleCrypto> {
if (typeof window !== 'undefined' && window.crypto?.subtle) {
return window.crypto.subtle;
}
const { webcrypto } = await import('crypto');
return webcrypto.subtle;
if (import.meta.env.SSR) {
const { webcrypto } = await import('crypto');
return webcrypto.subtle as SubtleCrypto;
}
throw new Error("SubtleCrypto not found in this environment");
})();
}
return cryptoPromise;
}
function toArrayBuffer(data: Uint8Array): ArrayBuffer {
const buffer = new ArrayBuffer(data.byteLength);
new Uint8Array(buffer).set(data);
return buffer;
}
// --- BIP39 Wordlist Loading ---
/**
@@ -74,7 +83,7 @@ if (BIP39_WORDLIST.length !== 2048) {
*/
async function sha256(data: Uint8Array): Promise<Uint8Array> {
const subtle = await getCrypto();
const hashBuffer = await subtle.digest('SHA-256', data);
const hashBuffer = await subtle.digest('SHA-256', toArrayBuffer(data));
return new Uint8Array(hashBuffer);
}
@@ -88,12 +97,12 @@ async function hmacSha256(key: Uint8Array, data: Uint8Array): Promise<Uint8Array
const subtle = await getCrypto();
const cryptoKey = await subtle.importKey(
'raw',
key,
toArrayBuffer(key),
{ name: 'HMAC', hash: 'SHA-256' },
false, // not exportable
['sign']
);
const signature = await subtle.sign('HMAC', cryptoKey, data);
const signature = await subtle.sign('HMAC', cryptoKey, toArrayBuffer(data));
return new Uint8Array(signature);
}
@@ -143,7 +152,7 @@ export async function hkdfExtractExpand(
dataToHmac.set(info, t.length);
dataToHmac.set([counter], t.length + info.length);
t = await hmacSha256(prk, dataToHmac);
t = new Uint8Array(await hmacSha256(prk, dataToHmac));
const toWrite = Math.min(t.length, length - written);
okm.set(t.slice(0, toWrite), written);
@@ -322,7 +331,7 @@ export function calculateDiceStats(diceRolls: string): DiceStats {
const sum = rolls.reduce((a, b) => a + b, 0);
const mean = sum / n;
const variance = rolls.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / n;
const stdDev = n > 1 ? Math.sqrt(rolls.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / (n - 1)) : 0;
const estimatedEntropyBits = n * Math.log2(6);