mirror of
https://github.com/kccleoc/seedpgp-web.git
synced 2026-03-07 09:57:50 +08:00
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:
34
REFERENCE/krux-test/.gitignore
vendored
Normal file
34
REFERENCE/krux-test/.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
# dependencies (bun install)
|
||||
node_modules
|
||||
|
||||
# output
|
||||
out
|
||||
dist
|
||||
*.tgz
|
||||
|
||||
# code coverage
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# logs
|
||||
logs
|
||||
_.log
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# caches
|
||||
.eslintcache
|
||||
.cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
15
REFERENCE/krux-test/README.md
Normal file
15
REFERENCE/krux-test/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# krux-test
|
||||
|
||||
To install dependencies:
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
To run:
|
||||
|
||||
```bash
|
||||
bun run
|
||||
```
|
||||
|
||||
This project was created using `bun init` in bun v1.3.8. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
|
||||
33
REFERENCE/krux-test/bun.lock
Normal file
33
REFERENCE/krux-test/bun.lock
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "krux-test",
|
||||
"dependencies": {
|
||||
"bip39": "^3.1.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="],
|
||||
|
||||
"@types/node": ["@types/node@25.2.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w=="],
|
||||
|
||||
"bip39": ["bip39@3.1.0", "", { "dependencies": { "@noble/hashes": "^1.2.0" } }, "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||
}
|
||||
}
|
||||
123
REFERENCE/krux-test/krux-test.ts
Normal file
123
REFERENCE/krux-test/krux-test.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import * as bip39 from "bip39";
|
||||
// Bun implements the Web Crypto API globally as `crypto`
|
||||
|
||||
const BASE43_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ$*+-./:";
|
||||
|
||||
// --- Helper: Base43 Decode ---
|
||||
function base43Decode(str: string): Uint8Array {
|
||||
let value = 0n;
|
||||
const base = 43n;
|
||||
|
||||
for (const char of str) {
|
||||
const index = BASE43_ALPHABET.indexOf(char);
|
||||
if (index === -1) throw new Error(`Invalid Base43 char: ${char}`);
|
||||
value = value * base + BigInt(index);
|
||||
}
|
||||
|
||||
// Convert BigInt to Buffer/Uint8Array
|
||||
let hex = value.toString(16);
|
||||
if (hex.length % 2 !== 0) hex = '0' + hex;
|
||||
return new Uint8Array(Buffer.from(hex, 'hex'));
|
||||
}
|
||||
|
||||
// --- Main Decryption Function ---
|
||||
async function decryptKruxKEF(kefData: string, passphrase: string) {
|
||||
// 1. Decode Base43
|
||||
const rawBytes = base43Decode(kefData);
|
||||
|
||||
// 2. Parse Envelope
|
||||
let offset = 0;
|
||||
|
||||
// ID Length (1 byte)
|
||||
const idLen = rawBytes[offset++];
|
||||
|
||||
// ID / Salt
|
||||
const salt = rawBytes.slice(offset, offset + idLen);
|
||||
offset += idLen;
|
||||
|
||||
// Version (1 byte)
|
||||
const version = rawBytes[offset++];
|
||||
|
||||
// Iterations (3 bytes, Big-Endian)
|
||||
const iterBytes = rawBytes.slice(offset, offset + 3);
|
||||
const iterations = (iterBytes[0] << 16) | (iterBytes[1] << 8) | iterBytes[2];
|
||||
offset += 3;
|
||||
|
||||
// Payload: [IV (12 bytes)] [Ciphertext (N)] [Tag (4 bytes)]
|
||||
const payload = rawBytes.slice(offset);
|
||||
const iv = payload.slice(0, 12);
|
||||
const tagLength = 4;
|
||||
const ciphertextWithTag = payload.slice(12);
|
||||
|
||||
console.log("--- Parsed KEF Data ---");
|
||||
console.log(`Version: ${version}`);
|
||||
console.log(`Iterations: ${iterations}`);
|
||||
console.log(`Salt (hex): ${Buffer.from(salt).toString('hex')}`);
|
||||
|
||||
if (version !== 20) {
|
||||
throw new Error("Only KEF Version 20 (AES-GCM) is supported.");
|
||||
}
|
||||
|
||||
// 3. Derive Key (PBKDF2-HMAC-SHA256)
|
||||
const keyMaterial = await crypto.subtle.importKey(
|
||||
"raw",
|
||||
new TextEncoder().encode(passphrase),
|
||||
{ name: "PBKDF2" },
|
||||
false,
|
||||
["deriveKey"]
|
||||
);
|
||||
|
||||
const key = await crypto.subtle.deriveKey(
|
||||
{
|
||||
name: "PBKDF2",
|
||||
salt: salt,
|
||||
iterations: iterations,
|
||||
hash: "SHA-256",
|
||||
},
|
||||
keyMaterial,
|
||||
{ name: "AES-GCM", length: 256 },
|
||||
false,
|
||||
["decrypt"]
|
||||
);
|
||||
|
||||
// 4. Decrypt (AES-GCM)
|
||||
try {
|
||||
const decryptedBuffer = await crypto.subtle.decrypt(
|
||||
{
|
||||
name: "AES-GCM",
|
||||
iv: iv,
|
||||
tagLength: 32, // 4 bytes * 8
|
||||
},
|
||||
key,
|
||||
ciphertextWithTag
|
||||
);
|
||||
|
||||
return new Uint8Array(decryptedBuffer);
|
||||
|
||||
} catch (error) {
|
||||
throw new Error(`Decryption failed: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Run Test ---
|
||||
const kefString = "1334+HGXM$F8PPOIRNHX0.R*:SBMHK$X88LX$*/Y417R/6S1ZQOB2LHM-L+4T1YQVU:B*CKGXONP7:Y/R-B*:$R8FK";
|
||||
const passphrase = "aaa";
|
||||
const expectedMnemonic = "differ release beauty fresh tortoise usage curtain spoil october town embrace ridge rough reject cabin snap glimpse enter book coach green lonely hundred mercy";
|
||||
|
||||
console.log(`\nDecrypting KEF String...`);
|
||||
try {
|
||||
const entropy = await decryptKruxKEF(kefString, passphrase);
|
||||
const mnemonic = bip39.entropyToMnemonic(Buffer.from(entropy));
|
||||
|
||||
console.log("\n--- Result ---");
|
||||
console.log(`Mnemonic: ${mnemonic}`);
|
||||
|
||||
if (mnemonic === expectedMnemonic) {
|
||||
console.log("\n✅ SUCCESS: Mnemonic matches expected output.");
|
||||
} else {
|
||||
console.log("\n❌ FAIL: Mnemonic does not match.");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
13
REFERENCE/krux-test/package.json
Normal file
13
REFERENCE/krux-test/package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "krux-test",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
},
|
||||
"dependencies": {
|
||||
"bip39": "^3.1.0"
|
||||
}
|
||||
}
|
||||
29
REFERENCE/krux-test/tsconfig.json
Normal file
29
REFERENCE/krux-test/tsconfig.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Environment setup & latest features
|
||||
"lib": ["ESNext"],
|
||||
"target": "ESNext",
|
||||
"module": "Preserve",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user