Files
seedpgp-web/REFERENCE/bun-seedQR-implement.md.rtf
LC mac aa06c9ae27 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.
2026-02-07 04:22:56 +08:00

470 lines
23 KiB
Plaintext

{\rtf1\ansi\ansicpg1252\cocoartf2867
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\froman\fcharset0 Times-Roman;\f1\froman\fcharset0 Times-Bold;\f2\fnil\fcharset0 HelveticaNeue;
\f3\fmodern\fcharset0 Courier;\f4\fmodern\fcharset0 Courier-Bold;\f5\fmodern\fcharset0 Courier-Oblique;
\f6\fnil\fcharset0 AppleColorEmoji;}
{\colortbl;\red255\green255\blue255;\red255\green255\blue255;\red0\green0\blue0;\red185\green188\blue186;
\red111\green144\blue176;\red162\green127\blue173;\red166\green178\blue85;\red132\green134\blue132;\red212\green128\blue77;
\red0\green0\blue0;\red191\green80\blue83;}
{\*\expandedcolortbl;;\cssrgb\c100000\c100000\c100000;\cssrgb\c0\c0\c0\c84706;\cssrgb\c77255\c78431\c77647;
\cssrgb\c50588\c63529\c74510;\cssrgb\c69804\c58039\c73333;\cssrgb\c70980\c74118\c40784;\cssrgb\c58824\c59608\c58824;\cssrgb\c87059\c57647\c37255;
\cssrgb\c0\c0\c0;\cssrgb\c80000\c40000\c40000;}
\paperw11900\paperh16840\margl1440\margr1440\vieww11520\viewh8400\viewkind0
\deftab720
\pard\pardeftab720\sa240\partightenfactor0
\f0\fs24 \cf2 \expnd0\expndtw0\kerning0
\outl0\strokewidth0 \strokec2 Here's a
\f1\b Bun TypeScript test
\f0\b0 for
\f1\b SeedSigner SeedQR
\f0\b0 (both Standard and Compact formats) using
\f1\b Test Vector 1
\f0\b0 from the official specification:\
\pard\pardeftab720\sa298\partightenfactor0
\f1\b\fs36 \cf2 Setup\
\pard\pardeftab720\qc\partightenfactor0
\f2\b0\fs22 \cf3 \strokec3 \
\pard\pardeftab720\partightenfactor0
\f3\fs26 \cf2 \strokec2 bash\
\pard\pardeftab720\partightenfactor0
\cf4 \strokec4 mkdir seedsigner-test\
\pard\pardeftab720\partightenfactor0
\cf5 \strokec5 cd\cf4 \strokec4 seedsigner-test\
bun init -y\
bun add bip39\
\pard\pardeftab720\sa298\partightenfactor0
\f4\b\fs39 \cf2 \strokec2 seedsigner-test.ts
\f1\fs36 \
\pard\pardeftab720\qc\partightenfactor0
\f2\b0\fs22 \cf3 \strokec3 \
\pard\pardeftab720\partightenfactor0
\f3\fs26 \cf2 \strokec2 typescript\
\pard\pardeftab720\partightenfactor0
\f4\b \cf6 \strokec6 import
\f3\b0 \cf4 \strokec4 *
\f4\b \cf6 \strokec6 as
\f3\b0 \cf4 \strokec4 bip39
\f4\b \cf6 \strokec6 from
\f3\b0 \cf4 \strokec4 \cf7 \strokec7 "bip39"\cf4 \strokec4 ;\
\
\f4\b \cf6 \strokec6 const
\f3\b0 \cf4 \strokec4 BIP39_ENGLISH_WORDLIST = [\
\cf7 \strokec7 "abandon"\cf4 \strokec4 , \cf7 \strokec7 "ability"\cf4 \strokec4 , \cf7 \strokec7 "able"\cf4 \strokec4 , \cf7 \strokec7 "about"\cf4 \strokec4 , \cf7 \strokec7 "above"\cf4 \strokec4 , \cf7 \strokec7 "absent"\cf4 \strokec4 , \cf7 \strokec7 "absorb"\cf4 \strokec4 , \cf7 \strokec7 "abstract"\cf4 \strokec4 , \cf7 \strokec7 "absurd"\cf4 \strokec4 , \cf7 \strokec7 "abuse"\cf4 \strokec4 ,\
\cf7 \strokec7 "access"\cf4 \strokec4 , \cf7 \strokec7 "accident"\cf4 \strokec4 , \cf7 \strokec7 "account"\cf4 \strokec4 , \cf7 \strokec7 "accuse"\cf4 \strokec4 , \cf7 \strokec7 "achieve"\cf4 \strokec4 , \cf7 \strokec7 "acid"\cf4 \strokec4 , \cf7 \strokec7 "acoustic"\cf4 \strokec4 , \cf7 \strokec7 "acquire"\cf4 \strokec4 , \cf7 \strokec7 "across"\cf4 \strokec4 , \cf7 \strokec7 "act"\cf4 \strokec4 ,\
\cf7 \strokec7 "actor"\cf4 \strokec4 , \cf7 \strokec7 "actress"\cf4 \strokec4 , \cf7 \strokec7 "actual"\cf4 \strokec4 , \cf7 \strokec7 "adapt"\cf4 \strokec4 , \cf7 \strokec7 "add"\cf4 \strokec4 , \cf7 \strokec7 "addict"\cf4 \strokec4 , \cf7 \strokec7 "address"\cf4 \strokec4 , \cf7 \strokec7 "adjust"\cf4 \strokec4 , \cf7 \strokec7 "admit"\cf4 \strokec4 , \cf7 \strokec7 "adult"\cf4 \strokec4 ,\
\cf7 \strokec7 "advance"\cf4 \strokec4 , \cf7 \strokec7 "advice"\cf4 \strokec4 , \cf7 \strokec7 "aerobic"\cf4 \strokec4 , \cf7 \strokec7 "affair"\cf4 \strokec4 , \cf7 \strokec7 "afford"\cf4 \strokec4 , \cf7 \strokec7 "afraid"\cf4 \strokec4 , \cf7 \strokec7 "again"\cf4 \strokec4 , \cf7 \strokec7 "age"\cf4 \strokec4 , \cf7 \strokec7 "agent"\cf4 \strokec4 , \cf7 \strokec7 "agree"\cf4 \strokec4 ,\
\cf7 \strokec7 "ahead"\cf4 \strokec4 , \cf7 \strokec7 "aim"\cf4 \strokec4 , \cf7 \strokec7 "air"\cf4 \strokec4 , \cf7 \strokec7 "airport"\cf4 \strokec4 , \cf7 \strokec7 "aisle"\cf4 \strokec4 , \cf7 \strokec7 "alarm"\cf4 \strokec4 , \cf7 \strokec7 "album"\cf4 \strokec4 , \cf7 \strokec7 "alcohol"\cf4 \strokec4 , \cf7 \strokec7 "alert"\cf4 \strokec4 , \cf7 \strokec7 "alien"\cf4 \strokec4 ,\
\cf7 \strokec7 "all"\cf4 \strokec4 , \cf7 \strokec7 "alley"\cf4 \strokec4 , \cf7 \strokec7 "allow"\cf4 \strokec4 , \cf7 \strokec7 "almost"\cf4 \strokec4 , \cf7 \strokec7 "alone"\cf4 \strokec4 , \cf7 \strokec7 "alpha"\cf4 \strokec4 , \cf7 \strokec7 "already"\cf4 \strokec4 , \cf7 \strokec7 "also"\cf4 \strokec4 , \cf7 \strokec7 "alter"\cf4 \strokec4 , \cf7 \strokec7 "always"\cf4 \strokec4 ,\
\cf7 \strokec7 "amateur"\cf4 \strokec4 , \cf7 \strokec7 "amazing"\cf4 \strokec4 , \cf7 \strokec7 "among"\cf4 \strokec4 , \cf7 \strokec7 "amount"\cf4 \strokec4 , \cf7 \strokec7 "amused"\cf4 \strokec4 , \cf7 \strokec7 "analyst"\cf4 \strokec4 , \cf7 \strokec7 "anchor"\cf4 \strokec4 , \cf7 \strokec7 "ancient"\cf4 \strokec4 , \cf7 \strokec7 "anger"\cf4 \strokec4 , \cf7 \strokec7 "angle"\cf4 \strokec4 ,\
\cf7 \strokec7 "angry"\cf4 \strokec4 , \cf7 \strokec7 "animal"\cf4 \strokec4 , \cf7 \strokec7 "ankle"\cf4 \strokec4 , \cf7 \strokec7 "announce"\cf4 \strokec4 , \cf7 \strokec7 "annual"\cf4 \strokec4 , \cf7 \strokec7 "another"\cf4 \strokec4 , \cf7 \strokec7 "answer"\cf4 \strokec4 , \cf7 \strokec7 "antenna"\cf4 \strokec4 , \cf7 \strokec7 "antique"\cf4 \strokec4 , \cf7 \strokec7 "anxiety"\cf4 \strokec4 ,\
\cf7 \strokec7 "any"\cf4 \strokec4 , \cf7 \strokec7 "apart"\cf4 \strokec4 , \cf7 \strokec7 "apology"\cf4 \strokec4 , \cf7 \strokec7 "appear"\cf4 \strokec4 , \cf7 \strokec7 "apple"\cf4 \strokec4 , \cf7 \strokec7 "approve"\cf4 \strokec4 , \cf7 \strokec7 "april"\cf4 \strokec4 , \cf7 \strokec7 "arch"\cf4 \strokec4 , \cf7 \strokec7 "arctic"\cf4 \strokec4 , \cf7 \strokec7 "area"\cf4 \strokec4 ,\
\cf7 \strokec7 "arena"\cf4 \strokec4 , \cf7 \strokec7 "argue"\cf4 \strokec4 , \cf7 \strokec7 "arm"\cf4 \strokec4 , \cf7 \strokec7 "armed"\cf4 \strokec4 , \cf7 \strokec7 "armor"\cf4 \strokec4 , \cf7 \strokec7 "army"\cf4 \strokec4 , \cf7 \strokec7 "around"\cf4 \strokec4 , \cf7 \strokec7 "arrange"\cf4 \strokec4 , \cf7 \strokec7 "arrest"\cf4 \strokec4 , \cf7 \strokec7 "arrive"\cf4 \strokec4 ,\
\f5\i \cf8 \strokec8 // ... (truncated for brevity - full 2048 word list needed in production)
\f3\i0 \cf4 \strokec4 \
\cf7 \strokec7 "attack"\cf4 \strokec4 , \cf7 \strokec7 "pizza"\cf4 \strokec4 , \cf7 \strokec7 "motion"\cf4 \strokec4 , \cf7 \strokec7 "avocado"\cf4 \strokec4 , \cf7 \strokec7 "network"\cf4 \strokec4 , \cf7 \strokec7 "gather"\cf4 \strokec4 , \cf7 \strokec7 "crop"\cf4 \strokec4 , \cf7 \strokec7 "fresh"\cf4 \strokec4 , \cf7 \strokec7 "patrol"\cf4 \strokec4 , \cf7 \strokec7 "unusual"\cf4 \strokec4 ,\
\f5\i \cf8 \strokec8 // Full wordlist should be loaded from bip39 library
\f3\i0 \cf4 \strokec4 \
];\
\
\pard\pardeftab720\partightenfactor0
\f5\i \cf8 \strokec8 // Full wordlist from bip39
\f3\i0 \cf4 \strokec4 \
\pard\pardeftab720\partightenfactor0
\f4\b \cf6 \strokec6 const
\f3\b0 \cf4 \strokec4 FULL_WORDLIST = bip39.wordlists.english;\
\
\f4\b \cf6 \strokec6 interface
\f3\b0 \cf4 \strokec4 \cf5 \strokec5 SeedQRResult\cf4 \strokec4 \{\
mnemonic: \cf9 \strokec9 string\cf4 \strokec4 ;\
wordCount: \cf9 \strokec9 12\cf4 \strokec4 | \cf9 \strokec9 24\cf4 \strokec4 ;\
format: \cf7 \strokec7 "standard"\cf4 \strokec4 | \cf7 \strokec7 "compact"\cf4 \strokec4 ;\
\}\
\
\pard\pardeftab720\partightenfactor0
\f5\i \cf8 \strokec8 // --- Standard SeedQR: Parse numeric digit stream ---
\f3\i0 \cf4 \strokec4 \
\pard\pardeftab720\partightenfactor0
\f4\b \cf6 \strokec6 function
\f3\b0 \cf4 \strokec4 parseStandardSeedQR(digitStream: \cf9 \strokec9 string\cf4 \strokec4 ): SeedQRResult \{\
\f4\b \cf6 \strokec6 if
\f3\b0 \cf4 \strokec4 (digitStream.length % \cf9 \strokec9 4\cf4 \strokec4 !== \cf9 \strokec9 0\cf4 \strokec4 ) \{\
\f4\b \cf6 \strokec6 throw
\f3\b0 \cf4 \strokec4
\f4\b \cf6 \strokec6 new
\f3\b0 \cf4 \strokec4 \cf5 \strokec5 Error\cf4 \strokec4 (\cf7 \strokec7 "Invalid digit stream length. Must be multiple of 4."\cf4 \strokec4 );\
\}\
\
\f4\b \cf6 \strokec6 const
\f3\b0 \cf4 \strokec4 wordIndices: \cf9 \strokec9 number\cf4 \strokec4 [] = [];\
\
\f5\i \cf8 \strokec8 // Split into 4-digit indices
\f3\i0 \cf4 \strokec4 \
\f4\b \cf6 \strokec6 for
\f3\b0 \cf4 \strokec4 (
\f4\b \cf6 \strokec6 let
\f3\b0 \cf4 \strokec4 i = \cf9 \strokec9 0\cf4 \strokec4 ; i < digitStream.length; i += \cf9 \strokec9 4\cf4 \strokec4 ) \{\
\f4\b \cf6 \strokec6 const
\f3\b0 \cf4 \strokec4 indexStr = digitStream.slice(i, i + \cf9 \strokec9 4\cf4 \strokec4 );\
\f4\b \cf6 \strokec6 const
\f3\b0 \cf4 \strokec4 index = parseInt(indexStr, \cf9 \strokec9 10\cf4 \strokec4 );\
\f4\b \cf6 \strokec6 if
\f3\b0 \cf4 \strokec4 (isNaN(index) || index >= \cf9 \strokec9 2048\cf4 \strokec4 ) \{\
\f4\b \cf6 \strokec6 throw
\f3\b0 \cf4 \strokec4
\f4\b \cf6 \strokec6 new
\f3\b0 \cf4 \strokec4 \cf5 \strokec5 Error\cf4 \strokec4 (\cf7 \strokec7 `Invalid word index: \cf4 \strokec4 $\{indexStr\}\cf7 \strokec7 `\cf4 \strokec4 );\
\}\
wordIndices.push(index);\
\}\
\
\f4\b \cf6 \strokec6 const
\f3\b0 \cf4 \strokec4 wordCount = wordIndices.length;\
\f4\b \cf6 \strokec6 if
\f3\b0 \cf4 \strokec4 (wordCount !== \cf9 \strokec9 12\cf4 \strokec4 && wordCount !== \cf9 \strokec9 24\cf4 \strokec4 ) \{\
\f4\b \cf6 \strokec6 throw
\f3\b0 \cf4 \strokec4
\f4\b \cf6 \strokec6 new
\f3\b0 \cf4 \strokec4 \cf5 \strokec5 Error\cf4 \strokec4 (\cf7 \strokec7 `Invalid word count: \cf4 \strokec4 $\{wordCount\}\cf7 \strokec7 . Must be 12 or 24.`\cf4 \strokec4 );\
\}\
\
\f4\b \cf6 \strokec6 const
\f3\b0 \cf4 \strokec4 mnemonicWords = wordIndices.map(index => FULL_WORDLIST[index]);\
\f4\b \cf6 \strokec6 const
\f3\b0 \cf4 \strokec4 mnemonic = mnemonicWords.join(\cf7 \strokec7 " "\cf4 \strokec4 );\
\
\f4\b \cf6 \strokec6 return
\f3\b0 \cf4 \strokec4 \{\
mnemonic,\
wordCount,\
format: \cf7 \strokec7 "standard"\cf4 \strokec4 \
\};\
\}\
\
\pard\pardeftab720\partightenfactor0
\f5\i \cf8 \strokec8 // --- Compact SeedQR: Parse binary entropy (128 bits for 12-word, 256 bits for 24-word) ---
\f3\i0 \cf4 \strokec4 \
\pard\pardeftab720\partightenfactor0
\f4\b \cf6 \strokec6 function
\f3\b0 \cf4 \strokec4 parseCompactSeedQR(hexEntropy: \cf9 \strokec9 string\cf4 \strokec4 ): SeedQRResult \{\
\f4\b \cf6 \strokec6 const
\f3\b0 \cf4 \strokec4 entropy = Buffer.from(hexEntropy, \cf7 \strokec7 'hex'\cf4 \strokec4 );\
\
\f4\b \cf6 \strokec6 if
\f3\b0 \cf4 \strokec4 (entropy.length !== \cf9 \strokec9 16\cf4 \strokec4 && entropy.length !== \cf9 \strokec9 32\cf4 \strokec4 ) \{\
\f4\b \cf6 \strokec6 throw
\f3\b0 \cf4 \strokec4
\f4\b \cf6 \strokec6 new
\f3\b0 \cf4 \strokec4 \cf5 \strokec5 Error\cf4 \strokec4 (\cf7 \strokec7 `Invalid entropy length: \cf4 \strokec4 $\{entropy.length\}\cf7 \strokec7 . Must be 16 (12-word) or 32 (24-word) bytes.`\cf4 \strokec4 );\
\}\
\
\f4\b \cf6 \strokec6 const
\f3\b0 \cf4 \strokec4 wordCount = entropy.length === \cf9 \strokec9 16\cf4 \strokec4 ? \cf9 \strokec9 12\cf4 \strokec4 : \cf9 \strokec9 24\cf4 \strokec4 ;\
\f4\b \cf6 \strokec6 const
\f3\b0 \cf4 \strokec4 mnemonic = bip39.entropyToMnemonic(entropy);\
\
\f4\b \cf6 \strokec6 return
\f3\b0 \cf4 \strokec4 \{\
mnemonic,\
wordCount,\
format: \cf7 \strokec7 "compact"\cf4 \strokec4 \
\};\
\}\
\
\pard\pardeftab720\partightenfactor0
\f5\i \cf8 \strokec8 // --- Test Vector 1 ---
\f3\i0 \cf4 \strokec4 \
\pard\pardeftab720\partightenfactor0
\f4\b \cf6 \strokec6 async
\f3\b0 \cf4 \strokec4
\f4\b \cf6 \strokec6 function
\f3\b0 \cf4 \strokec4 runSeedSignerTests() \{\
\cf9 \strokec9 console\cf4 \strokec4 .log(\cf7 \strokec7 "
\f6 \uc0\u55358 \u56785 \u8205 \u55357 \u56620
\f3 Testing SeedSigner SeedQR Format\\n"\cf4 \strokec4 );\
\
\f5\i \cf8 \strokec8 // Test Vector 1: 24-word seed
\f3\i0 \cf4 \strokec4 \
\f4\b \cf6 \strokec6 const
\f3\b0 \cf4 \strokec4 testVector1 = \{\
mnemonic: \cf7 \strokec7 "attack pizza motion avocado network gather crop fresh patrol unusual wild holiday candy pony ranch winter theme error hybrid van cereal salon goddess expire"\cf4 \strokec4 ,\
standardDigitStream: \cf7 \strokec7 "011513251154012711900771041507421289190620080870026613431420201617920614089619290300152408010643"\cf4 \strokec4 ,\
compactHexEntropy: \cf7 \strokec7 "0e54b64107f94cc0ccfae6a13dcbec3662154fec67e0e00999c07892597d190a"\cf4 \strokec4 \
\};\
\
\cf9 \strokec9 console\cf4 \strokec4 .log(\cf7 \strokec7 "
\f6 \uc0\u55357 \u56522
\f3 Test Vector 1 (24-word)"\cf4 \strokec4 );\
\cf9 \strokec9 console\cf4 \strokec4 .log(\cf7 \strokec7 "Expected:"\cf4 \strokec4 , testVector1.mnemonic);\
\
\f4\b \cf6 \strokec6 try
\f3\b0 \cf4 \strokec4 \{\
\f5\i \cf8 \strokec8 // Test Standard SeedQR
\f3\i0 \cf4 \strokec4 \
\f4\b \cf6 \strokec6 const
\f3\b0 \cf4 \strokec4 standardResult = parseStandardSeedQR(testVector1.standardDigitStream);\
\cf9 \strokec9 console\cf4 \strokec4 .log(\cf7 \strokec7 "\\n
\f6 \uc0\u9989
\f3 Standard SeedQR PASSED"\cf4 \strokec4 );\
\cf9 \strokec9 console\cf4 \strokec4 .log(\cf7 \strokec7 ` Words: \cf4 \strokec4 $\{standardResult.wordCount\}\cf7 \strokec7 , Mnemonic: \cf4 \strokec4 $\{standardResult.mnemonic.slice(\cf9 \strokec9 0\cf4 \strokec4 , \cf9 \strokec9 50\cf4 \strokec4 )\}\cf7 \strokec7 ...`\cf4 \strokec4 );\
\
\f5\i \cf8 \strokec8 // Test Compact SeedQR
\f3\i0 \cf4 \strokec4 \
\f4\b \cf6 \strokec6 const
\f3\b0 \cf4 \strokec4 compactResult = parseCompactSeedQR(testVector1.compactHexEntropy);\
\cf9 \strokec9 console\cf4 \strokec4 .log(\cf7 \strokec7 "\\n
\f6 \uc0\u9989
\f3 Compact SeedQR PASSED"\cf4 \strokec4 );\
\cf9 \strokec9 console\cf4 \strokec4 .log(\cf7 \strokec7 ` Words: \cf4 \strokec4 $\{compactResult.wordCount\}\cf7 \strokec7 , Mnemonic: \cf4 \strokec4 $\{compactResult.mnemonic.slice(\cf9 \strokec9 0\cf4 \strokec4 , \cf9 \strokec9 50\cf4 \strokec4 )\}\cf7 \strokec7 ...`\cf4 \strokec4 );\
\
\f4\b \cf6 \strokec6 if
\f3\b0 \cf4 \strokec4 (standardResult.mnemonic === testVector1.mnemonic && compactResult.mnemonic === testVector1.mnemonic) \{\
\cf9 \strokec9 console\cf4 \strokec4 .log(\cf7 \strokec7 "\\n
\f6 \uc0\u55356 \u57225
\f3 ALL TESTS PASSED!"\cf4 \strokec4 );\
\}\
\
\}
\f4\b \cf6 \strokec6 catch
\f3\b0 \cf4 \strokec4 (error) \{\
\cf9 \strokec9 console\cf4 \strokec4 .error(\cf7 \strokec7 "
\f6 \uc0\u10060
\f3 Test failed:"\cf4 \strokec4 , error);\
\}\
\}\
\
\pard\pardeftab720\partightenfactor0
\f5\i \cf8 \strokec8 // Run tests
\f3\i0 \cf4 \strokec4 \
runSeedSignerTests();\
\pard\pardeftab720\sa298\partightenfactor0
\f1\b\fs36 \cf2 \strokec2 Run Command\
\pard\pardeftab720\qc\partightenfactor0
\f2\b0\fs22 \cf3 \strokec3 \
\pard\pardeftab720\partightenfactor0
\f3\fs26 \cf2 \strokec2 bash\
\pard\pardeftab720\partightenfactor0
\cf4 \strokec4 bun run seedsigner-test.ts\
\pard\pardeftab720\sa298\partightenfactor0
\f1\b\fs36 \cf2 \strokec2 Expected Output\
\pard\pardeftab720\qc\partightenfactor0
\f2\b0\fs22 \cf3 \strokec3 \
\pard\pardeftab720\partightenfactor0
\f3\fs26 \cf2 \strokec2 text\
\pard\pardeftab720\partightenfactor0
\f6 \cf4 \strokec4 \uc0\u55358 \u56785 \u8205 \u55357 \u56620
\f3 Testing SeedSigner SeedQR Format\
\
\f6 \uc0\u55357 \u56522
\f3 Test Vector 1 (24-word)\
Expected: attack pizza motion avocado network gather crop fresh patrol unusual wild holiday candy pony ranch winter theme error hybrid van cereal salon goddess expire\
\
\f6 \uc0\u9989
\f3 Standard SeedQR PASSED\
Words: 24, Mnemonic: attack pizza motion avocado network gather crop f...\
\
\f6 \uc0\u9989
\f3 Compact SeedQR PASSED\
Words: 24, Mnemonic: attack pizza motion avocado network gather crop f...\
\
\f6 \uc0\u55356 \u57225
\f3 ALL TESTS PASSED!\
\pard\pardeftab720\sa298\partightenfactor0
\f1\b\fs36 \cf2 \strokec2 Key Differences from Krux KEF\
\itap1\trowd \taflags0 \trgaph108\trleft-108 \trbrdrt\brdrnil \trbrdrl\brdrnil \trbrdrr\brdrnil
\clvertalc \clshdrawnil \clwWidth1160\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx2880
\clvertalc \clshdrawnil \clwWidth3425\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx5760
\clvertalc \clshdrawnil \clwWidth3052\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx8640
\pard\intbl\itap1\pardeftab720\qc\partightenfactor0
\fs24 \cf2 \strokec10 Feature\cf0 \cell
\pard\intbl\itap1\pardeftab720\qc\partightenfactor0
\cf2 SeedSigner SeedQR\cf0 \cell
\pard\intbl\itap1\pardeftab720\qc\partightenfactor0
\cf2 Krux KEF\cf0 \cell \row
\itap1\trowd \taflags0 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil
\clvertalc \clshdrawnil \clwWidth1160\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx2880
\clvertalc \clshdrawnil \clwWidth3425\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx5760
\clvertalc \clshdrawnil \clwWidth3052\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx8640
\pard\intbl\itap1\pardeftab720\partightenfactor0
\cf2 Purpose
\f0\b0 \cf0 \cell
\pard\intbl\itap1\pardeftab720\partightenfactor0
\cf2 Encode BIP39 mnemonic indices\cf0 \cell
\pard\intbl\itap1\pardeftab720\partightenfactor0
\cf2 Encrypt arbitrary data\cf0 \cell \row
\itap1\trowd \taflags0 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil
\clvertalc \clshdrawnil \clwWidth1160\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx2880
\clvertalc \clshdrawnil \clwWidth3425\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx5760
\clvertalc \clshdrawnil \clwWidth3052\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx8640
\pard\intbl\itap1\pardeftab720\partightenfactor0
\f1\b \cf2 Encryption
\f0\b0 \cf0 \cell
\pard\intbl\itap1\pardeftab720\partightenfactor0
\f1\b \cf2 None
\f0\b0 (plain text)\cf0 \cell
\pard\intbl\itap1\pardeftab720\partightenfactor0
\cf2 AES-GCM + PBKDF2\cf0 \cell \row
\itap1\trowd \taflags0 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil
\clvertalc \clshdrawnil \clwWidth1160\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx2880
\clvertalc \clshdrawnil \clwWidth3425\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx5760
\clvertalc \clshdrawnil \clwWidth3052\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx8640
\pard\intbl\itap1\pardeftab720\partightenfactor0
\f1\b \cf2 Formats
\f0\b0 \cf0 \cell
\pard\intbl\itap1\pardeftab720\partightenfactor0
\cf2 Standard (digits), Compact (binary)\cf0 \cell
\pard\intbl\itap1\pardeftab720\partightenfactor0
\cf2 Versioned envelopes\cf0 \cell \row
\itap1\trowd \taflags0 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil
\clvertalc \clshdrawnil \clwWidth1160\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx2880
\clvertalc \clshdrawnil \clwWidth3425\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx5760
\clvertalc \clshdrawnil \clwWidth3052\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx8640
\pard\intbl\itap1\pardeftab720\partightenfactor0
\f1\b \cf2 Input
\f0\b0 \cf0 \cell
\pard\intbl\itap1\pardeftab720\partightenfactor0
\cf2 QR contains raw indices/entropy\cf0 \cell
\pard\intbl\itap1\pardeftab720\partightenfactor0
\cf2 Base43-encoded encrypted data\cf0 \cell \row
\itap1\trowd \taflags0 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrt\brdrnil \trbrdrr\brdrnil
\clvertalc \clshdrawnil \clwWidth1160\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx2880
\clvertalc \clshdrawnil \clwWidth3425\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx5760
\clvertalc \clshdrawnil \clwWidth3052\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx8640
\pard\intbl\itap1\pardeftab720\partightenfactor0
\f1\b \cf2 Output
\f0\b0 \cf0 \cell
\pard\intbl\itap1\pardeftab720\partightenfactor0
\cf2 BIP39 mnemonic\cf0 \cell
\pard\intbl\itap1\pardeftab720\partightenfactor0
\cf2 Raw decrypted bytes\cf0 \cell \lastrow\row
\pard\pardeftab720\qc\partightenfactor0
\f2\fs22 \cf3 \strokec3 \
\
\pard\pardeftab720\sa298\partightenfactor0
\f1\b\fs36 \cf2 \strokec2 Usage in Your App\
\pard\pardeftab720\qc\partightenfactor0
\f2\b0\fs22 \cf3 \strokec3 \
\pard\pardeftab720\partightenfactor0
\f3\fs26 \cf2 \strokec2 typescript\
\pard\pardeftab720\partightenfactor0
\f5\i \cf8 \strokec8 // Detect format automatically
\f3\i0 \cf4 \strokec4 \
\pard\pardeftab720\partightenfactor0
\f4\b \cf6 \strokec6 export
\f3\b0 \cf4 \strokec4
\f4\b \cf6 \strokec6 async
\f3\b0 \cf4 \strokec4
\f4\b \cf6 \strokec6 function
\f3\b0 \cf4 \strokec4 parseSeedQR(qrData: \cf9 \strokec9 string\cf4 \strokec4 ): \cf9 \strokec9 Promise\cf4 \strokec4 <\cf9 \strokec9 string\cf4 \strokec4 > \{\
\f5\i \cf8 \strokec8 // Check if it's numeric digits (Standard SeedQR)
\f3\i0 \cf4 \strokec4 \
\f4\b \cf6 \strokec6 if
\f3\b0 \cf4 \strokec4 (\cf11 \strokec11 /^\\d+$/\cf4 \strokec4 .test(qrData)) \{\
\f4\b \cf6 \strokec6 return
\f3\b0 \cf4 \strokec4 parseStandardSeedQR(qrData).mnemonic;\
\}\
\
\f5\i \cf8 \strokec8 // Check if it's hex (Compact SeedQR)
\f3\i0 \cf4 \strokec4 \
\f4\b \cf6 \strokec6 if
\f3\b0 \cf4 \strokec4 (\cf11 \strokec11 /^[0-9a-fA-F]+$/\cf4 \strokec4 .test(qrData)) \{\
\f4\b \cf6 \strokec6 return
\f3\b0 \cf4 \strokec4 parseCompactSeedQR(qrData).mnemonic;\
\}\
\
\f4\b \cf6 \strokec6 throw
\f3\b0 \cf4 \strokec4
\f4\b \cf6 \strokec6 new
\f3\b0 \cf4 \strokec4 \cf5 \strokec5 Error\cf4 \strokec4 (\cf7 \strokec7 "Unsupported SeedQR format"\cf4 \strokec4 );\
\}\
\pard\pardeftab720\sa240\partightenfactor0
\f0\fs24 \cf2 \strokec2 The
\f3\fs26 bip39
\f0\fs24 library handles the full English wordlist and checksum validation automatically!\
}