mirror of
https://github.com/kccleoc/seedpgp-web.git
synced 2026-03-06 17:37:51 +08:00
- 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.
692 lines
27 KiB
Plaintext
692 lines
27 KiB
Plaintext
{\rtf1\ansi\ansicpg1252\cocoartf2867
|
|
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\froman\fcharset0 Times-Roman;\f1\froman\fcharset0 Times-Bold;\f2\froman\fcharset0 TimesNewRomanPSMT;
|
|
\f3\fmodern\fcharset0 Courier-Bold;\f4\fnil\fcharset0 HelveticaNeue;\f5\fmodern\fcharset0 Courier;
|
|
\f6\fmodern\fcharset0 Courier-Oblique;\f7\fnil\fcharset0 Menlo-Italic;\f8\fnil\fcharset0 AppleColorEmoji;
|
|
\f9\fnil\fcharset0 Menlo-Regular;}
|
|
{\colortbl;\red255\green255\blue255;\red255\green255\blue255;\red0\green0\blue0;\red185\green188\blue186;
|
|
\red162\green127\blue173;\red166\green178\blue85;\red111\green144\blue176;\red212\green128\blue77;\red132\green134\blue132;
|
|
\red0\green0\blue0;\red0\green0\blue233;}
|
|
{\*\expandedcolortbl;;\cssrgb\c100000\c100000\c100000;\cssrgb\c0\c0\c0\c84706;\cssrgb\c77255\c78431\c77647;
|
|
\cssrgb\c69804\c58039\c73333;\cssrgb\c70980\c74118\c40784;\cssrgb\c50588\c63529\c74510;\cssrgb\c87059\c57647\c37255;\cssrgb\c58824\c59608\c58824;
|
|
\cssrgb\c0\c0\c0;\cssrgb\c0\c0\c93333;}
|
|
{\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{decimal\}}{\leveltext\leveltemplateid1\'01\'00;}{\levelnumbers\'01;}\fi-360\li720\lin720 }{\listname ;}\listid1}}
|
|
{\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}}
|
|
\paperw11900\paperh16840\margl1440\margr1440\vieww11520\viewh8400\viewkind0
|
|
\deftab720
|
|
\pard\pardeftab720\sa240\partightenfactor0
|
|
|
|
\f0\fs24 \cf2 \expnd0\expndtw0\kerning0
|
|
\outl0\strokewidth0 \strokec2 You're right to clarify! The test code I provided
|
|
\f1\b only covers reading/decoding CompactSeedQR
|
|
\f0\b0 (direction 2). Let me provide a
|
|
\f1\b complete bidirectional test
|
|
\f0\b0 that covers both:\
|
|
\pard\tx220\tx720\pardeftab720\li720\fi-720\sa240\partightenfactor0
|
|
\ls1\ilvl0
|
|
\f1\b \cf2 \kerning1\expnd0\expndtw0 \outl0\strokewidth0 {\listtext 1 }\expnd0\expndtw0\kerning0
|
|
\outl0\strokewidth0 \strokec2 Encoding
|
|
\f0\b0 : Mnemonic
|
|
\f2 \uc0\u8594
|
|
\f0 CompactSeedQR (binary data that goes into QR code)\
|
|
\ls1\ilvl0
|
|
\f1\b \kerning1\expnd0\expndtw0 \outl0\strokewidth0 {\listtext 2 }\expnd0\expndtw0\kerning0
|
|
\outl0\strokewidth0 \strokec2 Decoding
|
|
\f0\b0 : CompactSeedQR
|
|
\f2 \uc0\u8594
|
|
\f0 Mnemonic (restore from QR)\
|
|
\pard\pardeftab720\sa298\partightenfactor0
|
|
|
|
\f1\b\fs36 \cf2 \strokec2 Complete Bidirectional Test (
|
|
\f3\fs39 compact-seedqr-complete.ts
|
|
\f1\fs36 )\
|
|
\pard\pardeftab720\qc\partightenfactor0
|
|
|
|
\f4\b0\fs22 \cf3 \strokec3 \
|
|
\pard\pardeftab720\partightenfactor0
|
|
|
|
\f5\fs26 \cf2 \strokec2 bash\
|
|
\pard\pardeftab720\partightenfactor0
|
|
\cf4 \strokec4 bun add bip39 qrcode\
|
|
\pard\pardeftab720\qc\partightenfactor0
|
|
|
|
\f4\fs22 \cf3 \strokec3 \
|
|
\pard\pardeftab720\partightenfactor0
|
|
|
|
\f5\fs26 \cf2 \strokec2 typescript\
|
|
\pard\pardeftab720\partightenfactor0
|
|
|
|
\f3\b \cf5 \strokec5 import
|
|
\f5\b0 \cf4 \strokec4 *
|
|
\f3\b \cf5 \strokec5 as
|
|
\f5\b0 \cf4 \strokec4 bip39
|
|
\f3\b \cf5 \strokec5 from
|
|
\f5\b0 \cf4 \strokec4 \cf6 \strokec6 "bip39"\cf4 \strokec4 ;\
|
|
|
|
\f3\b \cf5 \strokec5 import
|
|
\f5\b0 \cf4 \strokec4 QRCode
|
|
\f3\b \cf5 \strokec5 from
|
|
\f5\b0 \cf4 \strokec4 \cf6 \strokec6 "qrcode"\cf4 \strokec4 ;\
|
|
\
|
|
|
|
\f3\b \cf5 \strokec5 interface
|
|
\f5\b0 \cf4 \strokec4 \cf7 \strokec7 CompactSeedQRTestVector\cf4 \strokec4 \{\
|
|
name: \cf8 \strokec8 string\cf4 \strokec4 ;\
|
|
mnemonic: \cf8 \strokec8 string\cf4 \strokec4 ;\
|
|
hexEntropy: \cf8 \strokec8 string\cf4 \strokec4 ;\
|
|
wordCount: \cf8 \strokec8 12\cf4 \strokec4 | \cf8 \strokec8 24\cf4 \strokec4 ;\
|
|
\}\
|
|
\
|
|
|
|
\f3\b \cf5 \strokec5 const
|
|
\f5\b0 \cf4 \strokec4 TEST_VECTORS: CompactSeedQRTestVector[] = [\
|
|
\{\
|
|
name: \cf6 \strokec6 "Test Vector 1: 24-word"\cf4 \strokec4 ,\
|
|
mnemonic: \cf6 \strokec6 "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 ,\
|
|
hexEntropy: \cf6 \strokec6 "0e74b64107f94cc0ccfae6a13dcbec3662154fec67e0e00999c07892597d190a"\cf4 \strokec4 ,\
|
|
wordCount: \cf8 \strokec8 24\cf4 \strokec4 \
|
|
\},\
|
|
\{\
|
|
name: \cf6 \strokec6 "Test Vector 4: 12-word"\cf4 \strokec4 ,\
|
|
mnemonic: \cf6 \strokec6 "forum undo fragile fade shy sign arrest garment culture tube off merit"\cf4 \strokec4 ,\
|
|
hexEntropy: \cf6 \strokec6 "5bbd9d71a8ec799083laff359d456545"\cf4 \strokec4 ,\
|
|
wordCount: \cf8 \strokec8 12\cf4 \strokec4 \
|
|
\},\
|
|
\{\
|
|
name: \cf6 \strokec6 "Test Vector 6: 12-word (with null byte)"\cf4 \strokec4 ,\
|
|
mnemonic: \cf6 \strokec6 "approve fruit lens brass ring actual stool coin doll boss strong rate"\cf4 \strokec4 ,\
|
|
hexEntropy: \cf6 \strokec6 "0acbba008d9ba005f5996b40a3475cd9"\cf4 \strokec4 ,\
|
|
wordCount: \cf8 \strokec8 12\cf4 \strokec4 \
|
|
\}\
|
|
];\
|
|
\
|
|
\pard\pardeftab720\partightenfactor0
|
|
|
|
\f6\i \cf9 \strokec9 // ============================================================================
|
|
\f5\i0 \cf4 \strokec4 \
|
|
|
|
\f6\i \cf9 \strokec9 // DIRECTION 1: ENCODE - Mnemonic
|
|
\f7 \uc0\u8594
|
|
\f6 CompactSeedQR (Binary for QR Code)
|
|
\f5\i0 \cf4 \strokec4 \
|
|
|
|
\f6\i \cf9 \strokec9 // ============================================================================
|
|
\f5\i0 \cf4 \strokec4 \
|
|
\
|
|
|
|
\f6\i \cf9 \strokec9 /**\
|
|
* Convert mnemonic to CompactSeedQR format (raw entropy bytes)\
|
|
* This is what you encode into the QR code as BINARY data\
|
|
*/
|
|
\f5\i0 \cf4 \strokec4 \
|
|
\pard\pardeftab720\partightenfactor0
|
|
|
|
\f3\b \cf5 \strokec5 function
|
|
\f5\b0 \cf4 \strokec4 encodeToCompactSeedQR(mnemonic: \cf8 \strokec8 string\cf4 \strokec4 ): Buffer \{\
|
|
|
|
\f6\i \cf9 \strokec9 // Validate mnemonic
|
|
\f5\i0 \cf4 \strokec4 \
|
|
|
|
\f3\b \cf5 \strokec5 if
|
|
\f5\b0 \cf4 \strokec4 (!bip39.validateMnemonic(mnemonic)) \{\
|
|
|
|
\f3\b \cf5 \strokec5 throw
|
|
\f5\b0 \cf4 \strokec4
|
|
\f3\b \cf5 \strokec5 new
|
|
\f5\b0 \cf4 \strokec4 \cf7 \strokec7 Error\cf4 \strokec4 (\cf6 \strokec6 "Invalid mnemonic"\cf4 \strokec4 );\
|
|
\}\
|
|
\
|
|
|
|
\f6\i \cf9 \strokec9 // Extract entropy (WITHOUT checksum)
|
|
\f5\i0 \cf4 \strokec4 \
|
|
|
|
\f6\i \cf9 \strokec9 // bip39.mnemonicToEntropy returns hex string
|
|
\f5\i0 \cf4 \strokec4 \
|
|
|
|
\f3\b \cf5 \strokec5 const
|
|
\f5\b0 \cf4 \strokec4 entropyHex = bip39.mnemonicToEntropy(mnemonic);\
|
|
|
|
\f3\b \cf5 \strokec5 const
|
|
\f5\b0 \cf4 \strokec4 entropy = Buffer.from(entropyHex, \cf6 \strokec6 "hex"\cf4 \strokec4 );\
|
|
\
|
|
|
|
\f6\i \cf9 \strokec9 // Validate length
|
|
\f5\i0 \cf4 \strokec4 \
|
|
|
|
\f3\b \cf5 \strokec5 if
|
|
\f5\b0 \cf4 \strokec4 (entropy.length !== \cf8 \strokec8 16\cf4 \strokec4 && entropy.length !== \cf8 \strokec8 32\cf4 \strokec4 ) \{\
|
|
|
|
\f3\b \cf5 \strokec5 throw
|
|
\f5\b0 \cf4 \strokec4
|
|
\f3\b \cf5 \strokec5 new
|
|
\f5\b0 \cf4 \strokec4 \cf7 \strokec7 Error\cf4 \strokec4 (\cf6 \strokec6 `Invalid entropy length: \cf4 \strokec4 $\{entropy.length\}\cf6 \strokec6 `\cf4 \strokec4 );\
|
|
\}\
|
|
\
|
|
|
|
\f3\b \cf5 \strokec5 return
|
|
\f5\b0 \cf4 \strokec4 entropy;\
|
|
\}\
|
|
\
|
|
\pard\pardeftab720\partightenfactor0
|
|
|
|
\f6\i \cf9 \strokec9 /**\
|
|
* Generate a QR code from mnemonic (as PNG data URL)\
|
|
*/
|
|
\f5\i0 \cf4 \strokec4 \
|
|
\pard\pardeftab720\partightenfactor0
|
|
|
|
\f3\b \cf5 \strokec5 async
|
|
\f5\b0 \cf4 \strokec4
|
|
\f3\b \cf5 \strokec5 function
|
|
\f5\b0 \cf4 \strokec4 generateCompactSeedQR(mnemonic: \cf8 \strokec8 string\cf4 \strokec4 ): \cf8 \strokec8 Promise\cf4 \strokec4 <\cf8 \strokec8 string\cf4 \strokec4 > \{\
|
|
|
|
\f3\b \cf5 \strokec5 const
|
|
\f5\b0 \cf4 \strokec4 entropy = encodeToCompactSeedQR(mnemonic);\
|
|
\
|
|
|
|
\f6\i \cf9 \strokec9 // Generate QR code with BINARY data
|
|
\f5\i0 \cf4 \strokec4 \
|
|
|
|
\f6\i \cf9 \strokec9 // Important: Use 'byte' mode for raw binary data
|
|
\f5\i0 \cf4 \strokec4 \
|
|
|
|
\f3\b \cf5 \strokec5 const
|
|
\f5\b0 \cf4 \strokec4 qrDataURL =
|
|
\f3\b \cf5 \strokec5 await
|
|
\f5\b0 \cf4 \strokec4 QRCode.toDataURL([\{ data: entropy, mode: \cf6 \strokec6 'byte'\cf4 \strokec4 \}], \{\
|
|
errorCorrectionLevel: \cf6 \strokec6 'L'\cf4 \strokec4 ,
|
|
\f6\i \cf9 \strokec9 // SeedSigner uses Low error correction
|
|
\f5\i0 \cf4 \strokec4 \
|
|
type: \cf6 \strokec6 'image/png'\cf4 \strokec4 ,\
|
|
width: \cf8 \strokec8 300\cf4 \strokec4 \
|
|
\});\
|
|
\
|
|
|
|
\f3\b \cf5 \strokec5 return
|
|
\f5\b0 \cf4 \strokec4 qrDataURL;\
|
|
\}\
|
|
\
|
|
\pard\pardeftab720\partightenfactor0
|
|
|
|
\f6\i \cf9 \strokec9 // ============================================================================
|
|
\f5\i0 \cf4 \strokec4 \
|
|
|
|
\f6\i \cf9 \strokec9 // DIRECTION 2: DECODE - CompactSeedQR
|
|
\f7 \uc0\u8594
|
|
\f6 Mnemonic (Restore from QR)
|
|
\f5\i0 \cf4 \strokec4 \
|
|
|
|
\f6\i \cf9 \strokec9 // ============================================================================
|
|
\f5\i0 \cf4 \strokec4 \
|
|
\
|
|
|
|
\f6\i \cf9 \strokec9 /**\
|
|
* Parse CompactSeedQR from raw bytes (what QR scanner gives you)\
|
|
*/
|
|
\f5\i0 \cf4 \strokec4 \
|
|
\pard\pardeftab720\partightenfactor0
|
|
|
|
\f3\b \cf5 \strokec5 function
|
|
\f5\b0 \cf4 \strokec4 decodeCompactSeedQR(data: Buffer | Uint8Array): \cf8 \strokec8 string\cf4 \strokec4 \{\
|
|
|
|
\f3\b \cf5 \strokec5 const
|
|
\f5\b0 \cf4 \strokec4 entropy = Buffer.from(data);\
|
|
\
|
|
|
|
\f6\i \cf9 \strokec9 // Validate length
|
|
\f5\i0 \cf4 \strokec4 \
|
|
|
|
\f3\b \cf5 \strokec5 if
|
|
\f5\b0 \cf4 \strokec4 (entropy.length !== \cf8 \strokec8 16\cf4 \strokec4 && entropy.length !== \cf8 \strokec8 32\cf4 \strokec4 ) \{\
|
|
|
|
\f3\b \cf5 \strokec5 throw
|
|
\f5\b0 \cf4 \strokec4
|
|
\f3\b \cf5 \strokec5 new
|
|
\f5\b0 \cf4 \strokec4 \cf7 \strokec7 Error\cf4 \strokec4 (\
|
|
\cf6 \strokec6 `Invalid CompactSeedQR length: \cf4 \strokec4 $\{entropy.length\}\cf6 \strokec6 bytes. Must be 16 (12-word) or 32 (24-word).`\cf4 \strokec4 \
|
|
);\
|
|
\}\
|
|
\
|
|
|
|
\f6\i \cf9 \strokec9 // Convert entropy to mnemonic (automatically adds BIP39 checksum)
|
|
\f5\i0 \cf4 \strokec4 \
|
|
|
|
\f3\b \cf5 \strokec5 const
|
|
\f5\b0 \cf4 \strokec4 mnemonic = bip39.entropyToMnemonic(entropy);\
|
|
\
|
|
|
|
\f3\b \cf5 \strokec5 return
|
|
\f5\b0 \cf4 \strokec4 mnemonic;\
|
|
\}\
|
|
\
|
|
\pard\pardeftab720\partightenfactor0
|
|
|
|
\f6\i \cf9 \strokec9 /**\
|
|
* Parse from hex string (for testing)\
|
|
*/
|
|
\f5\i0 \cf4 \strokec4 \
|
|
\pard\pardeftab720\partightenfactor0
|
|
|
|
\f3\b \cf5 \strokec5 function
|
|
\f5\b0 \cf4 \strokec4 decodeCompactSeedQRFromHex(hexEntropy: \cf8 \strokec8 string\cf4 \strokec4 ): \cf8 \strokec8 string\cf4 \strokec4 \{\
|
|
|
|
\f3\b \cf5 \strokec5 const
|
|
\f5\b0 \cf4 \strokec4 entropy = Buffer.from(hexEntropy, \cf6 \strokec6 "hex"\cf4 \strokec4 );\
|
|
|
|
\f3\b \cf5 \strokec5 return
|
|
\f5\b0 \cf4 \strokec4 decodeCompactSeedQR(entropy);\
|
|
\}\
|
|
\
|
|
\pard\pardeftab720\partightenfactor0
|
|
|
|
\f6\i \cf9 \strokec9 // ============================================================================
|
|
\f5\i0 \cf4 \strokec4 \
|
|
|
|
\f6\i \cf9 \strokec9 // TESTS
|
|
\f5\i0 \cf4 \strokec4 \
|
|
|
|
\f6\i \cf9 \strokec9 // ============================================================================
|
|
\f5\i0 \cf4 \strokec4 \
|
|
\
|
|
\pard\pardeftab720\partightenfactor0
|
|
|
|
\f3\b \cf5 \strokec5 async
|
|
\f5\b0 \cf4 \strokec4
|
|
\f3\b \cf5 \strokec5 function
|
|
\f5\b0 \cf4 \strokec4 runBidirectionalTests() \{\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 "
|
|
\f8 \uc0\u55358 \u56810
|
|
\f5 CompactSeedQR Bidirectional Tests\\n"\cf4 \strokec4 );\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 "="\cf4 \strokec4 .repeat(\cf8 \strokec8 80\cf4 \strokec4 ));\
|
|
\
|
|
|
|
\f3\b \cf5 \strokec5 let
|
|
\f5\b0 \cf4 \strokec4 passed = \cf8 \strokec8 0\cf4 \strokec4 ;\
|
|
|
|
\f3\b \cf5 \strokec5 let
|
|
\f5\b0 \cf4 \strokec4 failed = \cf8 \strokec8 0\cf4 \strokec4 ;\
|
|
\
|
|
|
|
\f3\b \cf5 \strokec5 for
|
|
\f5\b0 \cf4 \strokec4 (
|
|
\f3\b \cf5 \strokec5 const
|
|
\f5\b0 \cf4 \strokec4 vector
|
|
\f3\b \cf5 \strokec5 of
|
|
\f5\b0 \cf4 \strokec4 TEST_VECTORS) \{\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 `\\n
|
|
\f8 \uc0\u55357 \u56523
|
|
\f5 \cf4 \strokec4 $\{vector.name\}\cf6 \strokec6 `\cf4 \strokec4 );\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 `Mnemonic: \cf4 \strokec4 $\{vector.mnemonic.slice(\cf8 \strokec8 0\cf4 \strokec4 , \cf8 \strokec8 50\cf4 \strokec4 )\}\cf6 \strokec6 ...`\cf4 \strokec4 );\
|
|
\
|
|
|
|
\f3\b \cf5 \strokec5 try
|
|
\f5\b0 \cf4 \strokec4 \{\
|
|
|
|
\f6\i \cf9 \strokec9 // ===== DIRECTION 1: ENCODE =====
|
|
\f5\i0 \cf4 \strokec4 \
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 "\\n
|
|
\f8 \uc0\u55357 \u56594
|
|
\f5 ENCODE: Mnemonic
|
|
\f9 \uc0\u8594
|
|
\f5 CompactSeedQR"\cf4 \strokec4 );\
|
|
\
|
|
|
|
\f3\b \cf5 \strokec5 const
|
|
\f5\b0 \cf4 \strokec4 encodedEntropy = encodeToCompactSeedQR(vector.mnemonic);\
|
|
|
|
\f3\b \cf5 \strokec5 const
|
|
\f5\b0 \cf4 \strokec4 encodedHex = encodedEntropy.toString(\cf6 \strokec6 "hex"\cf4 \strokec4 );\
|
|
\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 ` Generated entropy: \cf4 \strokec4 $\{encodedHex\}\cf6 \strokec6 `\cf4 \strokec4 );\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 ` Expected entropy: \cf4 \strokec4 $\{vector.hexEntropy\}\cf6 \strokec6 `\cf4 \strokec4 );\
|
|
\
|
|
|
|
\f3\b \cf5 \strokec5 const
|
|
\f5\b0 \cf4 \strokec4 encodeMatches = encodedHex === vector.hexEntropy;\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 ` \cf4 \strokec4 $\{encodeMatches ? \cf6 \strokec6 "
|
|
\f8 \uc0\u9989
|
|
\f5 "\cf4 \strokec4 : \cf6 \strokec6 "
|
|
\f8 \uc0\u10060
|
|
\f5 "\cf4 \strokec4 \}\cf6 \strokec6 Encode \cf4 \strokec4 $\{encodeMatches ? \cf6 \strokec6 "PASSED"\cf4 \strokec4 : \cf6 \strokec6 "FAILED"\cf4 \strokec4 \}\cf6 \strokec6 `\cf4 \strokec4 );\
|
|
\
|
|
|
|
\f6\i \cf9 \strokec9 // Generate actual QR code
|
|
\f5\i0 \cf4 \strokec4 \
|
|
|
|
\f3\b \cf5 \strokec5 const
|
|
\f5\b0 \cf4 \strokec4 qrDataURL =
|
|
\f3\b \cf5 \strokec5 await
|
|
\f5\b0 \cf4 \strokec4 generateCompactSeedQR(vector.mnemonic);\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 `
|
|
\f8 \uc0\u55357 \u56561
|
|
\f5 QR Code generated (\cf4 \strokec4 $\{qrDataURL.length\}\cf6 \strokec6 bytes PNG data)`\cf4 \strokec4 );\
|
|
\
|
|
|
|
\f6\i \cf9 \strokec9 // ===== DIRECTION 2: DECODE =====
|
|
\f5\i0 \cf4 \strokec4 \
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 "\\n
|
|
\f8 \uc0\u55357 \u56595
|
|
\f5 DECODE: CompactSeedQR
|
|
\f9 \uc0\u8594
|
|
\f5 Mnemonic"\cf4 \strokec4 );\
|
|
\
|
|
|
|
\f3\b \cf5 \strokec5 const
|
|
\f5\b0 \cf4 \strokec4 decodedMnemonic = decodeCompactSeedQRFromHex(vector.hexEntropy);\
|
|
\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 ` Decoded: \cf4 \strokec4 $\{decodedMnemonic.slice(\cf8 \strokec8 0\cf4 \strokec4 , \cf8 \strokec8 50\cf4 \strokec4 )\}\cf6 \strokec6 ...`\cf4 \strokec4 );\
|
|
\
|
|
|
|
\f3\b \cf5 \strokec5 const
|
|
\f5\b0 \cf4 \strokec4 decodeMatches = decodedMnemonic === vector.mnemonic;\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 ` \cf4 \strokec4 $\{decodeMatches ? \cf6 \strokec6 "
|
|
\f8 \uc0\u9989
|
|
\f5 "\cf4 \strokec4 : \cf6 \strokec6 "
|
|
\f8 \uc0\u10060
|
|
\f5 "\cf4 \strokec4 \}\cf6 \strokec6 Decode \cf4 \strokec4 $\{decodeMatches ? \cf6 \strokec6 "PASSED"\cf4 \strokec4 : \cf6 \strokec6 "FAILED"\cf4 \strokec4 \}\cf6 \strokec6 `\cf4 \strokec4 );\
|
|
\
|
|
|
|
\f6\i \cf9 \strokec9 // ===== ROUND-TRIP TEST =====
|
|
\f5\i0 \cf4 \strokec4 \
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 "\\n
|
|
\f8 \uc0\u55357 \u56580
|
|
\f5 ROUND-TRIP: Mnemonic
|
|
\f9 \uc0\u8594
|
|
\f5 QR
|
|
\f9 \uc0\u8594
|
|
\f5 Mnemonic"\cf4 \strokec4 );\
|
|
\
|
|
|
|
\f3\b \cf5 \strokec5 const
|
|
\f5\b0 \cf4 \strokec4 roundTripMnemonic = decodeCompactSeedQR(encodedEntropy);\
|
|
|
|
\f3\b \cf5 \strokec5 const
|
|
\f5\b0 \cf4 \strokec4 roundTripMatches = roundTripMnemonic === vector.mnemonic;\
|
|
\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 ` \cf4 \strokec4 $\{roundTripMatches ? \cf6 \strokec6 "
|
|
\f8 \uc0\u9989
|
|
\f5 "\cf4 \strokec4 : \cf6 \strokec6 "
|
|
\f8 \uc0\u10060
|
|
\f5 "\cf4 \strokec4 \}\cf6 \strokec6 Round-trip \cf4 \strokec4 $\{roundTripMatches ? \cf6 \strokec6 "PASSED"\cf4 \strokec4 : \cf6 \strokec6 "FAILED"\cf4 \strokec4 \}\cf6 \strokec6 `\cf4 \strokec4 );\
|
|
\
|
|
|
|
\f3\b \cf5 \strokec5 if
|
|
\f5\b0 \cf4 \strokec4 (encodeMatches && decodeMatches && roundTripMatches) \{\
|
|
passed++;\
|
|
\}
|
|
\f3\b \cf5 \strokec5 else
|
|
\f5\b0 \cf4 \strokec4 \{\
|
|
failed++;\
|
|
\}\
|
|
\
|
|
\}
|
|
\f3\b \cf5 \strokec5 catch
|
|
\f5\b0 \cf4 \strokec4 (error) \{\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 `
|
|
\f8 \uc0\u10060
|
|
\f5 ERROR: \cf4 \strokec4 $\{error\}\cf6 \strokec6 `\cf4 \strokec4 );\
|
|
failed++;\
|
|
\}\
|
|
\}\
|
|
\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 "\\n"\cf4 \strokec4 + \cf6 \strokec6 "="\cf4 \strokec4 .repeat(\cf8 \strokec8 80\cf4 \strokec4 ));\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 `\\n
|
|
\f8 \uc0\u55357 \u56522
|
|
\f5 Results: \cf4 \strokec4 $\{passed\}\cf6 \strokec6 /\cf4 \strokec4 $\{TEST_VECTORS.length\}\cf6 \strokec6 passed`\cf4 \strokec4 );\
|
|
\
|
|
|
|
\f3\b \cf5 \strokec5 if
|
|
\f5\b0 \cf4 \strokec4 (failed === \cf8 \strokec8 0\cf4 \strokec4 ) \{\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 "
|
|
\f8 \uc0\u55356 \u57225
|
|
\f5 ALL TESTS PASSED!"\cf4 \strokec4 );\
|
|
\}\
|
|
\}\
|
|
\
|
|
\pard\pardeftab720\partightenfactor0
|
|
|
|
\f6\i \cf9 \strokec9 // ============================================================================
|
|
\f5\i0 \cf4 \strokec4 \
|
|
|
|
\f6\i \cf9 \strokec9 // EXAMPLE USAGE
|
|
\f5\i0 \cf4 \strokec4 \
|
|
|
|
\f6\i \cf9 \strokec9 // ============================================================================
|
|
\f5\i0 \cf4 \strokec4 \
|
|
\
|
|
\pard\pardeftab720\partightenfactor0
|
|
|
|
\f3\b \cf5 \strokec5 async
|
|
\f5\b0 \cf4 \strokec4
|
|
\f3\b \cf5 \strokec5 function
|
|
\f5\b0 \cf4 \strokec4 demonstrateUsage() \{\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 "\\n\\n"\cf4 \strokec4 + \cf6 \strokec6 "="\cf4 \strokec4 .repeat(\cf8 \strokec8 80\cf4 \strokec4 ));\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 "
|
|
\f8 \uc0\u55357 \u56481
|
|
\f5 USAGE EXAMPLE\\n"\cf4 \strokec4 );\
|
|
\
|
|
|
|
\f3\b \cf5 \strokec5 const
|
|
\f5\b0 \cf4 \strokec4 exampleMnemonic = \cf6 \strokec6 "forum undo fragile fade shy sign arrest garment culture tube off merit"\cf4 \strokec4 ;\
|
|
\
|
|
|
|
\f6\i \cf9 \strokec9 // ===== CREATE QR CODE =====
|
|
\f5\i0 \cf4 \strokec4 \
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 "
|
|
\f8 1\uc0\u65039 \u8419
|
|
\f5 CREATE CompactSeedQR from mnemonic:"\cf4 \strokec4 );\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 ` Input: \cf4 \strokec4 $\{exampleMnemonic\}\cf6 \strokec6 `\cf4 \strokec4 );\
|
|
\
|
|
|
|
\f3\b \cf5 \strokec5 const
|
|
\f5\b0 \cf4 \strokec4 entropy = encodeToCompactSeedQR(exampleMnemonic);\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 ` Binary data (hex): \cf4 \strokec4 $\{entropy.toString(\cf6 \strokec6 "hex"\cf4 \strokec4 )\}\cf6 \strokec6 `\cf4 \strokec4 );\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 ` Binary data (bytes): [\cf4 \strokec4 $\{\cf8 \strokec8 Array\cf4 \strokec4 .from(entropy).join(\cf6 \strokec6 ", "\cf4 \strokec4 )\}\cf6 \strokec6 ]`\cf4 \strokec4 );\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 ` Length: \cf4 \strokec4 $\{entropy.length\}\cf6 \strokec6 bytes (\cf4 \strokec4 $\{entropy.length === \cf8 \strokec8 16\cf4 \strokec4 ? \cf6 \strokec6 "12-word"\cf4 \strokec4 : \cf6 \strokec6 "24-word"\cf4 \strokec4 \}\cf6 \strokec6 )`\cf4 \strokec4 );\
|
|
\
|
|
|
|
\f3\b \cf5 \strokec5 const
|
|
\f5\b0 \cf4 \strokec4 qrDataURL =
|
|
\f3\b \cf5 \strokec5 await
|
|
\f5\b0 \cf4 \strokec4 generateCompactSeedQR(exampleMnemonic);\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 `
|
|
\f8 \uc0\u9989
|
|
\f5 QR code generated!`\cf4 \strokec4 );\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 `
|
|
\f8 \uc0\u55357 \u56561
|
|
\f5 Display this QR: <img src="\cf4 \strokec4 $\{qrDataURL.slice(\cf8 \strokec8 0\cf4 \strokec4 , \cf8 \strokec8 50\cf4 \strokec4 )\}\cf6 \strokec6 ..." />`\cf4 \strokec4 );\
|
|
\
|
|
|
|
\f6\i \cf9 \strokec9 // ===== SCAN QR CODE =====
|
|
\f5\i0 \cf4 \strokec4 \
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 "\\n
|
|
\f8 2\uc0\u65039 \u8419
|
|
\f5 SCAN CompactSeedQR and restore mnemonic:"\cf4 \strokec4 );\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 ` Simulating QR scanner output...`\cf4 \strokec4 );\
|
|
\
|
|
|
|
\f6\i \cf9 \strokec9 // In real app, your QR scanner returns raw bytes:
|
|
\f5\i0 \cf4 \strokec4 \
|
|
|
|
\f6\i \cf9 \strokec9 // const scannedBytes = qrScanner.decode();
|
|
\f5\i0 \cf4 \strokec4 \
|
|
|
|
\f3\b \cf5 \strokec5 const
|
|
\f5\b0 \cf4 \strokec4 scannedBytes = entropy;
|
|
\f6\i \cf9 \strokec9 // Simulate scanning the QR we just created
|
|
\f5\i0 \cf4 \strokec4 \
|
|
\
|
|
|
|
\f3\b \cf5 \strokec5 const
|
|
\f5\b0 \cf4 \strokec4 restoredMnemonic = decodeCompactSeedQR(scannedBytes);\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 ` Restored: \cf4 \strokec4 $\{restoredMnemonic\}\cf6 \strokec6 `\cf4 \strokec4 );\
|
|
\cf8 \strokec8 console\cf4 \strokec4 .log(\cf6 \strokec6 `
|
|
\f8 \uc0\u9989
|
|
\f5 Match: \cf4 \strokec4 $\{restoredMnemonic === exampleMnemonic\}\cf6 \strokec6 `\cf4 \strokec4 );\
|
|
\}\
|
|
\
|
|
\pard\pardeftab720\partightenfactor0
|
|
|
|
\f6\i \cf9 \strokec9 // Run everything
|
|
\f5\i0 \cf4 \strokec4 \
|
|
runBidirectionalTests().then(() => demonstrateUsage());\
|
|
\pard\pardeftab720\sa298\partightenfactor0
|
|
|
|
\f1\b\fs36 \cf2 \strokec2 Run Command\
|
|
\pard\pardeftab720\qc\partightenfactor0
|
|
|
|
\f4\b0\fs22 \cf3 \strokec3 \
|
|
\pard\pardeftab720\partightenfactor0
|
|
|
|
\f5\fs26 \cf2 \strokec2 bash\
|
|
\pard\pardeftab720\partightenfactor0
|
|
\cf4 \strokec4 bun run compact-seedqr-complete.ts\
|
|
\pard\pardeftab720\sa298\partightenfactor0
|
|
|
|
\f1\b\fs36 \cf2 \strokec2 Expected Output\
|
|
\pard\pardeftab720\qc\partightenfactor0
|
|
|
|
\f4\b0\fs22 \cf3 \strokec3 \
|
|
\pard\pardeftab720\partightenfactor0
|
|
|
|
\f5\fs26 \cf2 \strokec2 text\
|
|
\pard\pardeftab720\partightenfactor0
|
|
|
|
\f8 \cf4 \strokec4 \uc0\u55358 \u56810
|
|
\f5 CompactSeedQR Bidirectional Tests\
|
|
\
|
|
================================================================================\
|
|
\
|
|
|
|
\f8 \uc0\u55357 \u56523
|
|
\f5 Test Vector 1: 24-word\
|
|
Mnemonic: attack pizza motion avocado network gather crop fre...\
|
|
\
|
|
|
|
\f8 \uc0\u55357 \u56594
|
|
\f5 ENCODE: Mnemonic
|
|
\f9 \uc0\u8594
|
|
\f5 CompactSeedQR\
|
|
Generated entropy: 0e74b64107f94cc0ccfae6a13dcbec3662154fec67e0e00999c07892597d190a\
|
|
Expected entropy: 0e74b64107f94cc0ccfae6a13dcbec3662154fec67e0e00999c07892597d190a\
|
|
|
|
\f8 \uc0\u9989
|
|
\f5 Encode PASSED\
|
|
|
|
\f8 \uc0\u55357 \u56561
|
|
\f5 QR Code generated (2847 bytes PNG data)\
|
|
\
|
|
|
|
\f8 \uc0\u55357 \u56595
|
|
\f5 DECODE: CompactSeedQR
|
|
\f9 \uc0\u8594
|
|
\f5 Mnemonic\
|
|
Decoded: attack pizza motion avocado network gather crop fre...\
|
|
|
|
\f8 \uc0\u9989
|
|
\f5 Decode PASSED\
|
|
\
|
|
|
|
\f8 \uc0\u55357 \u56580
|
|
\f5 ROUND-TRIP: Mnemonic
|
|
\f9 \uc0\u8594
|
|
\f5 QR
|
|
\f9 \uc0\u8594
|
|
\f5 Mnemonic\
|
|
|
|
\f8 \uc0\u9989
|
|
\f5 Round-trip PASSED\
|
|
\
|
|
...\
|
|
\
|
|
================================================================================\
|
|
\
|
|
|
|
\f8 \uc0\u55357 \u56522
|
|
\f5 Results: 3/3 passed\
|
|
|
|
\f8 \uc0\u55356 \u57225
|
|
\f5 ALL TESTS PASSED!\
|
|
\
|
|
================================================================================\
|
|
|
|
\f8 \uc0\u55357 \u56481
|
|
\f5 USAGE EXAMPLE\
|
|
\
|
|
|
|
\f8 1\uc0\u65039 \u8419
|
|
\f5 CREATE CompactSeedQR from mnemonic:\
|
|
Input: forum undo fragile fade shy sign arrest garment culture tube off merit\
|
|
Binary data (hex): 5bbd9d71a8ec799083laff359d456545\
|
|
Binary data (bytes): [91, 189, 157, 113, 168, 236, 121, ...]\
|
|
Length: 16 bytes (12-word)\
|
|
|
|
\f8 \uc0\u9989
|
|
\f5 QR code generated!\
|
|
\
|
|
|
|
\f8 2\uc0\u65039 \u8419
|
|
\f5 SCAN CompactSeedQR and restore mnemonic:\
|
|
Simulating QR scanner output...\
|
|
Restored: forum undo fragile fade shy sign arrest garment culture tube off merit\
|
|
|
|
\f8 \uc0\u9989
|
|
\f5 Match: true\
|
|
\pard\pardeftab720\sa298\partightenfactor0
|
|
|
|
\f1\b\fs36 \cf2 \strokec2 Summary: What the Code Does\
|
|
|
|
\itap1\trowd \taflags0 \trgaph108\trleft-108 \trbrdrt\brdrnil \trbrdrl\brdrnil \trbrdrr\brdrnil
|
|
\clvertalc \clshdrawnil \clwWidth1000\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx2160
|
|
\clvertalc \clshdrawnil \clwWidth2033\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx4320
|
|
\clvertalc \clshdrawnil \clwWidth2779\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx6480
|
|
\clvertalc \clshdrawnil \clwWidth4979\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 Direction\cf0 \cell
|
|
\pard\intbl\itap1\pardeftab720\qc\partightenfactor0
|
|
\cf2 Input\cf0 \cell
|
|
\pard\intbl\itap1\pardeftab720\qc\partightenfactor0
|
|
\cf2 Output\cf0 \cell
|
|
\pard\intbl\itap1\pardeftab720\qc\partightenfactor0
|
|
\cf2 What It Does\cf0 \cell \row
|
|
|
|
\itap1\trowd \taflags0 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrr\brdrnil
|
|
\clvertalc \clshdrawnil \clwWidth1000\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx2160
|
|
\clvertalc \clshdrawnil \clwWidth2033\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx4320
|
|
\clvertalc \clshdrawnil \clwWidth2779\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx6480
|
|
\clvertalc \clshdrawnil \clwWidth4979\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 1. Encode
|
|
\f0\b0 \cf0 \cell
|
|
\pard\intbl\itap1\pardeftab720\partightenfactor0
|
|
\cf2 Mnemonic (words)\cf0 \cell
|
|
\pard\intbl\itap1\pardeftab720\partightenfactor0
|
|
\cf2 Binary entropy (16/32 bytes)\cf0 \cell
|
|
\pard\intbl\itap1\pardeftab720\partightenfactor0
|
|
\cf2 Strips checksum, creates raw bytes for QR\cf0 \cell \row
|
|
|
|
\itap1\trowd \taflags0 \trgaph108\trleft-108 \trbrdrl\brdrnil \trbrdrt\brdrnil \trbrdrr\brdrnil
|
|
\clvertalc \clshdrawnil \clwWidth1000\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx2160
|
|
\clvertalc \clshdrawnil \clwWidth2033\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx4320
|
|
\clvertalc \clshdrawnil \clwWidth2779\clftsWidth3 \clmart10 \clmarl10 \clmarb10 \clmarr10 \clbrdrt\brdrnil \clbrdrl\brdrnil \clbrdrb\brdrnil \clbrdrr\brdrnil \clpadt20 \clpadl20 \clpadb20 \clpadr20 \gaph\cellx6480
|
|
\clvertalc \clshdrawnil \clwWidth4979\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 2. Decode
|
|
\f0\b0 \cf0 \cell
|
|
\pard\intbl\itap1\pardeftab720\partightenfactor0
|
|
\cf2 Binary data from QR\cf0 \cell
|
|
\pard\intbl\itap1\pardeftab720\partightenfactor0
|
|
\cf2 Mnemonic (words)\cf0 \cell
|
|
\pard\intbl\itap1\pardeftab720\partightenfactor0
|
|
\cf2 Reads raw bytes, adds checksum, converts to words\cf0 \cell \lastrow\row
|
|
\pard\pardeftab720\qc\partightenfactor0
|
|
|
|
\f4\fs22 \cf3 \strokec3 \
|
|
\
|
|
\pard\pardeftab720\sa240\partightenfactor0
|
|
|
|
\f0\fs24 \cf2 \strokec2 The QR code itself contains
|
|
\f1\b raw binary entropy
|
|
\f0\b0 (NOT encoded as hex or base64). This is why CompactSeedQR is smaller than Standard SeedQR!{\field{\*\fldinst{HYPERLINK "https://github.com/SeedSigner/seedsigner/blob/dev/docs/seed_qr/README.md"}}{\fldrslt \cf11 \ul \ulc11 \strokec11 github+1}}\
|
|
} |