Skip to content

Commit 1fa327f

Browse files
committed
feat: passing tests
now need to clean up logging
1 parent 9701078 commit 1fa327f

File tree

4 files changed

+249
-67
lines changed

4 files changed

+249
-67
lines changed

packages/auth-server/components/account-recovery/passkey-generation-flow/Step3ConfirmLater.vue

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,22 +45,50 @@ const props = defineProps<{
4545
const recoveryUrl = computedAsync(async () => {
4646
const queryParams = new URLSearchParams();
4747
48-
const credentialId = props.newPasskey.credentialId;
48+
// Use base64url format for credentialId (required by contract)
49+
const credentialId = props.newPasskey.credentialIdBase64url;
4950
// Serialize the public key as JSON since it's {x, y} format
5051
const credentialPublicKey = JSON.stringify(props.newPasskey.credentialPublicKey);
5152
53+
/* eslint-disable no-console */
54+
console.log("🔧 URL GENERATION DEBUG");
55+
console.log("================================");
56+
console.log("Original values BEFORE URLSearchParams:");
57+
console.log(" accountAddress:", props.accountAddress);
58+
console.log(" credentialId:", credentialId);
59+
console.log(" credentialPublicKey:", credentialPublicKey);
60+
console.log(" credentialPublicKey (length):", credentialPublicKey.length);
61+
console.log(" credentialPublicKey (first 20 chars):", credentialPublicKey.substring(0, 20));
62+
5263
queryParams.set("credentialId", credentialId);
5364
queryParams.set("credentialPublicKey", credentialPublicKey);
5465
queryParams.set("accountAddress", props.accountAddress);
5566
5667
// Create checksum from concatenated credential data
57-
const dataToHash = `${props.accountAddress}:${credentialId}:${credentialPublicKey}`;
68+
// Normalize accountAddress to lowercase for consistent hashing
69+
const normalizedAddress = props.accountAddress.toLowerCase();
70+
const dataToHash = `${normalizedAddress}:${credentialId}:${credentialPublicKey}`;
71+
console.log("\n🔐 Checksum generation:");
72+
console.log(" Data to hash:", dataToHash);
73+
console.log(" Data to hash (length):", dataToHash.length);
74+
5875
const fullHash = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(dataToHash)));
5976
const shortHash = fullHash.slice(0, 8); // Take first 8 bytes of the hash
6077
const checksum = uint8ArrayToHex(shortHash);
6178
79+
console.log(" Generated checksum:", checksum);
80+
6281
queryParams.set("checksum", checksum);
6382
64-
return new URL(`/recovery/guardian/confirm-recovery?${queryParams.toString()}`, window.location.origin).toString();
83+
const finalUrl = new URL(`/recovery/guardian/confirm-recovery?${queryParams.toString()}`, window.location.origin).toString();
84+
85+
console.log("\n📋 URLSearchParams encoding:");
86+
console.log(" credentialPublicKey (encoded):", queryParams.get("credentialPublicKey"));
87+
console.log(" Full query string:", queryParams.toString());
88+
console.log("\n🔗 Final URL:", finalUrl);
89+
console.log("================================\n");
90+
/* eslint-enable no-console */
91+
92+
return finalUrl;
6593
});
6694
</script>

packages/auth-server/components/account-recovery/passkey-generation-flow/Step3ConfirmNow.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,12 @@
5656
>
5757
Confirm Recovery
5858
</ZkButton>
59-
<!-- <CommonConnectButton
59+
<CommonConnectButton
6060
v-if="!selectedGuardianInfo?.isSsoAccount"
6161
type="primary"
6262
class="w-full mt-4"
6363
:disabled="initRecoveryInProgress || getConfigurableAccountInProgress"
64-
/> -->
64+
/>
6565
</template>
6666

6767
<ZkButton

packages/auth-server/pages/recovery/guardian/(actions)/confirm-recovery.vue

Lines changed: 157 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@
105105

106106
<script setup lang="ts">
107107
import { 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";
110110
import { z } from "zod";
111111
112112
import { 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
146203
const generalError = ref<string | null>(null);
204+
const recoveryCheckTrigger = ref(0);
147205
148206
const isLoadingGuardians = ref(false);
149207
const loadingGuardiansError = ref<string | null>(null);
@@ -154,6 +212,30 @@ const isConnectedWalletGuardian = computed(() => (
154212
155213
const 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("\ncredentialPublicKey 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+
157239
const 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
168250
const 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
174301
const 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

Comments
 (0)