105105
106106<script setup lang="ts">
107107import { useAppKitAccount } from " @reown/appkit/vue" ;
108- import { type Address , hexToBytes , isAddressEqual , keccak256 , toHex } from " viem" ;
109- import { base64urlToUint8Array } from " zksync-sso-4337/utils" ;
108+ import { type Address , encodeAbiParameters , isAddressEqual , keccak256 , pad , parseAbiParameters , toHex } from " viem" ;
109+ import { base64urlToUint8Array , getPasskeySignatureFromPublicKeyBytes , getPublicKeyBytesFromPasskeySignature } from " zksync-sso-4337/utils" ;
110110import { z } from " zod" ;
111111
112112import { uint8ArrayToHex } from " @/utils/formatters" ;
@@ -132,10 +132,67 @@ const RecoveryParamsSchema = z
132132 })
133133 .refine (
134134 async (data ) => {
135- const dataToHash = ` ${data .accountAddress }:${data .credentialId }:${data .credentialPublicKey } ` ;
135+ // Debug: Log raw parameter values
136+ /* eslint-disable no-console */
137+ console .log (" 🔍 CHECKSUM VALIDATION DEBUG" );
138+ console .log (" ================================" );
139+ console .log (" Raw parameters received:" );
140+ console .log (" accountAddress:" , data .accountAddress );
141+ console .log (" credentialId:" , data .credentialId );
142+ console .log (" credentialPublicKey:" , data .credentialPublicKey );
143+ console .log (" checksum (provided):" , data .checksum );
144+
145+ // Debug: Character-level inspection of credentialPublicKey
146+ console .log (" \n 📝 CredentialPublicKey character analysis:" );
147+ console .log (" Length:" , data .credentialPublicKey .length );
148+ console .log (" First 20 chars:" , data .credentialPublicKey .substring (0 , 20 ));
149+ console .log (" Character codes (first 20):" ,
150+ Array .from (data .credentialPublicKey .substring (0 , 20 ))
151+ .map ((c , _i ) => ` ${c }=${c .charCodeAt (0 )} ` )
152+ .join (" , " ),
153+ );
154+
155+ // Debug: Try to parse as JSON
156+ console .log (" \n 🧪 JSON parsing attempt:" );
157+ try {
158+ const parsed = JSON .parse (data .credentialPublicKey );
159+ console .log (" ✅ Successfully parsed as JSON:" , parsed );
160+ } catch (e ) {
161+ console .log (" ❌ Failed to parse as JSON:" , (e as Error ).message );
162+ }
163+
164+ // Calculate checksum
165+ // Normalize accountAddress to lowercase for consistent hashing
166+ const normalizedAddress = data .accountAddress .toLowerCase ();
167+ const dataToHash = ` ${normalizedAddress }:${data .credentialId }:${data .credentialPublicKey } ` ;
168+ console .log (" \n 🔐 Checksum calculation:" );
169+ console .log (" Data to hash:" , dataToHash );
170+ console .log (" Data to hash (length):" , dataToHash .length );
171+
136172 const calculatedChecksum = uint8ArrayToHex (
137173 new Uint8Array (await crypto .subtle .digest (" SHA-256" , new TextEncoder ().encode (dataToHash ))).slice (0 , 8 ),
138174 );
175+
176+ console .log (" Calculated checksum:" , calculatedChecksum );
177+ console .log (" Provided checksum:" , data .checksum );
178+ console .log (" Match:" , calculatedChecksum === data .checksum ? " ✅ YES" : " ❌ NO" );
179+
180+ if (calculatedChecksum !== data .checksum ) {
181+ // Show character-by-character comparison
182+ console .log (" \n ❌ MISMATCH DETECTED" );
183+ console .log (" Comparing checksums character by character:" );
184+ const maxLen = Math .max (calculatedChecksum .length , data .checksum .length );
185+ for (let _i = 0 ; _i < maxLen ; _i ++ ) {
186+ const calc = calculatedChecksum [_i ] || " ∅" ;
187+ const prov = data .checksum [_i ] || " ∅" ;
188+ const match = calc === prov ? " ✓" : " ✗" ;
189+ console .log (` [${_i }] ${match } calc:'${calc }' prov:'${prov }' ` );
190+ }
191+ }
192+
193+ console .log (" ================================\n " );
194+ /* eslint-enable no-console */
195+
139196 return calculatedChecksum === data .checksum ;
140197 },
141198 {
@@ -144,6 +201,7 @@ const RecoveryParamsSchema = z
144201 );
145202
146203const generalError = ref <string | null >(null );
204+ const recoveryCheckTrigger = ref (0 );
147205
148206const isLoadingGuardians = ref (false );
149207const loadingGuardiansError = ref <string | null >(null );
@@ -154,6 +212,30 @@ const isConnectedWalletGuardian = computed(() => (
154212
155213const confirmGuardianErrorMessage = ref <string | null >(null );
156214
215+ // Debug: Log what Nuxt's router receives
216+ /* eslint-disable no-console */
217+ console .log (" 🌐 NUXT ROUTE QUERY DEBUG" );
218+ console .log (" ================================" );
219+ console .log (" route.query object:" , route .query );
220+ console .log (" Query parameters as received by Nuxt:" );
221+ console .log (" accountAddress:" , route .query .accountAddress );
222+ console .log (" credentialId:" , route .query .credentialId );
223+ console .log (" credentialPublicKey:" , route .query .credentialPublicKey );
224+ console .log (" checksum:" , route .query .checksum );
225+ if (typeof route .query .credentialPublicKey === " string" ) {
226+ console .log (" \n credentialPublicKey detailed analysis:" );
227+ console .log (" Type:" , typeof route .query .credentialPublicKey );
228+ console .log (" Length:" , route .query .credentialPublicKey .length );
229+ console .log (" First 50 chars:" , route .query .credentialPublicKey .substring (0 , 50 ));
230+ console .log (" Character codes (first 20):" ,
231+ Array .from (route .query .credentialPublicKey .substring (0 , 20 ))
232+ .map ((c ) => ` ${c }=${c .charCodeAt (0 )} ` )
233+ .join (" , " ),
234+ );
235+ }
236+ console .log (" ================================\n " );
237+ /* eslint-enable no-console */
238+
157239const recoveryParams = computedAsync (async () => RecoveryParamsSchema .parseAsync ({
158240 accountAddress: route .query .accountAddress ,
159241 credentialId: route .query .credentialId ,
@@ -166,9 +248,54 @@ const recoveryParams = computedAsync(async () => RecoveryParamsSchema.parseAsync
166248}));
167249
168250const recoveryCompleted = computedAsync (async () => {
169- if (! recoveryParams .value ?.accountAddress ) return false ;
251+ // Force re-evaluation when trigger changes
252+ const triggerValue = recoveryCheckTrigger .value ;
253+ // eslint-disable-next-line no-console
254+ console .log (" 🔍 recoveryCompleted computed called, trigger:" , triggerValue );
255+
256+ if (! recoveryParams .value ?.accountAddress || ! recoveryParams .value ?.credentialId || ! recoveryParams .value ?.credentialPublicKey ) {
257+ // eslint-disable-next-line no-console
258+ console .log (" ❌ Missing required params, returning false" );
259+ return false ;
260+ }
261+
262+ // eslint-disable-next-line no-console
263+ console .log (" 🔍 Checking recovery for account:" , recoveryParams .value .accountAddress );
264+ // eslint-disable-next-line no-console
265+ console .log (" 🔍 Checking recovery for account:" , recoveryParams .value .accountAddress );
170266 const result = await getRecovery (recoveryParams .value .accountAddress );
171- return result ?.hashedCredentialId === keccak256 (toHex (base64urlToUint8Array (recoveryParams .value .credentialId )));
267+ // eslint-disable-next-line no-console
268+ console .log (" 🔍 getRecovery result:" , result );
269+
270+ // The smart contract stores keccak256(data) where data is the encoded recovery payload
271+ // We need to reconstruct the same data structure that was passed to initializeRecovery
272+ const parsedPublicKey = JSON .parse (recoveryParams .value .credentialPublicKey );
273+ const credentialPublicKeyBytes = getPasskeySignatureFromPublicKeyBytes ([parsedPublicKey .x , parsedPublicKey .y ]);
274+ const publicKeyBytes = getPublicKeyBytesFromPasskeySignature (credentialPublicKeyBytes );
275+ const publicKeyHex = [
276+ pad (` 0x${publicKeyBytes [0 ].toString (" hex" )} ` ),
277+ pad (` 0x${publicKeyBytes [1 ].toString (" hex" )} ` ),
278+ ] as const ;
279+
280+ const recoveryData = encodeAbiParameters (
281+ parseAbiParameters (" bytes32 credentialIdHash, bytes32[2] publicKey" ),
282+ [
283+ keccak256 (toHex (base64urlToUint8Array (recoveryParams .value .credentialId ))),
284+ publicKeyHex ,
285+ ],
286+ );
287+
288+ const expectedHashedData = keccak256 (recoveryData );
289+ // eslint-disable-next-line no-console
290+ console .log (" 🔍 Expected hashedData:" , expectedHashedData );
291+ // eslint-disable-next-line no-console
292+ console .log (" 🔍 Actual hashedData:" , result ?.hashedData );
293+
294+ const isComplete = result ?.hashedData === expectedHashedData ;
295+ // eslint-disable-next-line no-console
296+ console .log (" ✅ Recovery completed:" , isComplete );
297+
298+ return isComplete ;
172299});
173300
174301const guardians = computedAsync (async () => {
@@ -215,13 +342,37 @@ const handleConfirmRecovery = async () => {
215342
216343 if (! recoveryParams .value ) return ;
217344
345+ // Parse the credentialPublicKey JSON string to get x,y coordinates
346+ const parsedPublicKey = JSON .parse (recoveryParams .value .credentialPublicKey );
347+ // Convert coordinates to proper COSE format expected by initRecovery
348+ const credentialPublicKeyBytes = getPasskeySignatureFromPublicKeyBytes ([parsedPublicKey .x , parsedPublicKey .y ]);
349+
350+ // eslint-disable-next-line no-console
351+ console .log (" 🔍 About to call initRecovery with:" );
352+ // eslint-disable-next-line no-console
353+ console .log (" credentialId:" , recoveryParams .value .credentialId );
354+ // eslint-disable-next-line no-console
355+ console .log (" Hash of credentialId:" , keccak256 (toHex (base64urlToUint8Array (recoveryParams .value .credentialId ))));
356+ // eslint-disable-next-line no-console
357+ console .log (" accountToRecover:" , recoveryParams .value .accountAddress );
358+
218359 await initRecovery ({
219360 client ,
220361 accountToRecover: recoveryParams .value .accountAddress ,
221- credentialPublicKey: hexToBytes ( ` 0x${ recoveryParams . value . credentialPublicKey } ` ) ,
362+ credentialPublicKey: credentialPublicKeyBytes ,
222363 credentialId: recoveryParams .value .credentialId ,
223364 });
224365 confirmGuardianErrorMessage .value = null ;
366+
367+ // Wait a moment for the transaction to be processed
368+ // eslint-disable-next-line no-console
369+ console .log (" ⏳ Waiting for recovery transaction to process..." );
370+ await new Promise ((resolve ) => setTimeout (resolve , 2000 ));
371+
372+ // Trigger re-check of recoveryCompleted
373+ recoveryCheckTrigger .value ++ ;
374+ // eslint-disable-next-line no-console
375+ console .log (" ✅ Recovery initiated successfully, trigger incremented to:" , recoveryCheckTrigger .value );
225376 } catch (err ) {
226377 confirmGuardianErrorMessage .value = " An error occurred while confirming the guardian. Please try again." ;
227378 // eslint-disable-next-line no-console
0 commit comments