Passkey vs KDF Signers
Detailed Explanation: Difference between Passkeys and KDF for Safe Creation
Overview
The main issue is that passkeys and KDF work differently at the signing level:
- Passkeys: The server can sign directly via WebAuthn (the browser/device signs)
- KDF: The server only has the public address, the private key remains on the client side
1. PASSKEY Mode - How it currently works
Step 1: Signer creation (line 843 of passkey.ts)
What Safe.createPasskeySigner() does:
- Creates a signer object that can sign directly via WebAuthn
- The signer contains the WebAuthn credential (public key + ID)
- When
safeSigner.sign()is called, it uses WebAuthn to sign (via the browser/device)
Important: This signer is "real" - it can sign immediately.
Step 2: Safe creation (line 972 of passkey.ts)
What getSafe() does:
- Uses
Safe4337Pack.init()with thesafeSigner - The Safe Global can use this signer to:
- Calculate the Safe address (deterministic from the signer)
- Check if the Safe is deployed
- Sign transactions (via WebAuthn)
Step 3: Storage in the DB (line 1141 of passkey.ts)
Why store the signer:
- During Safe operations (POST
/v1.2/safes/operations), we retrievesigner.data.safe.signer(line 1650 ofsafesOperations.ts) - This signer is used to sign Safe transactions
Step 4: Usage during operations (line 1650 of safesOperations.ts)
Summary for Passkeys:
- ✅ The signer can sign directly (via WebAuthn)
- ✅ The Safe is created with this signer
- ✅ The signer is stored in the DB
- ✅ Operations use this signer to sign
2. KDF Mode - How it currently works (PROBLEM)
Step 1: Signature Verification (line 829 of v1.2/auth.ts)
What happens:
- The client derives the private key from the KDF secret (client-side)
- The client signs the challenge with this private key
- The client sends the signature to the server
- The server verifies the signature but does not have the private key
Important: The server only has:
publicKey(EOA address) -0x3cd6ca93356d4d010442C50011D805B66ffcc8f6- The challenge signature (proof that the client has the private key)
- BUT NOT the private key itself
Step 2: Signer creation (line 854 of v1.2/auth.ts)
Problem: The signer is created but:
- ❌ There is no
data.safe.signer - ❌ The Safe is not created
- ❌ We cannot use this signer to create a Safe because we don't have the private key
Step 3: Returning the response (line 893 of v1.2/auth.ts)
Result: The Safe is not created, thus safeAddress: \{\}
3. Proposed solution for KDF - "Virtual" Signer
Concept: Virtual signer vs real signer
Real signer (Passkeys):
- Can sign directly
- Contains the necessary information to sign (WebAuthn credential)
- Used to create the Safe AND to sign transactions
Virtual signer (KDF/Email):
- Can not sign directly (no private key)
- Only contains the EOA address
- Used only to create the Safe (calculate the address)
- The real signer (with private key) will be used during operations
Step 1: Create a virtual signer from the EOA address
Important: This virtual signer:
- ✅ Can be used by
getSafe()to calculate the Safe address - ✅ Can be used to check if the Safe is deployed
- ❌ Cannot sign transactions (no private key)
Step 2: Create the Safe with the virtual signer
Note: getSafe() does not need to sign to calculate the Safe address. It just needs to know what the signer will be (EOA address).
Step 3: Store the virtual signer in the DB
Important: We store the virtual signer in data.safe.signer, just like for passkeys.
Step 4: Create the Safe in the DB
Result: The Safe is now created and registered in the DB.
Step 5: Usage during operations - The Problem
During a Safe operation (POST /v1.2/safes/operations), the current code does:
Solution: The code must be modified for KDF/Email:
4. Visual Comparison
Passkeys (Real Signer)
KDF (Virtual Signer)
Safe Operations - Difference
Passkeys:
KDF (current - does not work):
KDF (proposed - to be implemented):
Important: The client's signature (KDF) must sign the userOpHash (just like the passkey signs via WebAuthn), which allows us to validate the Safe transaction in the same way. The only difference is:
- Passkeys: WebAuthn signature (via credential)
- KDF: EOA signature (via derived private key)
5. Questions to Resolve
Question 1: How to create an EOA signer from the address?
Option A: Create a minimal object
Option B: Use Safe Global with the address directly
Option C: Create a "mock" signer that implements the Safe interface
Question 2: How to manage Safe operations for KDF/Email?
Answer: The client must sign the userOpHash (like the passkey), but with an EOA signature instead of a WebAuthn signature.
Proposed Flow:
-
POST /v1.2/safes/operations (preparation - identical to passkeys):
- Creates the Safe transaction (userOp)
- Calculates
userOpHash = operation.getHash() - For passkeys: Returns
credentialRequestOptions(WebAuthn) - For KDF/Email: Returns
userOpHashdirectly (or in a similar format)
-
PUT /v1.2/safes/operations (execution - different for KDF/Email):
- For passkeys: The client sends
credential(WebAuthn signature) - For KDF/Email: The client sends
signature(EOA signature of the userOpHash) - The server adds the signature to the userOp (like
addSignature()for passkeys) - The server executes the transaction
- For passkeys: The client sends
Important: The EOA signature must sign the same userOpHash as the WebAuthn signature, ensuring both methods validate the Safe transaction equivalently.
Question 3: Should the virtual signer be stored in data.safe.signer?
Answer: Yes, to maintain consistency with passkeys. But the operations code must be modified to detect the signer type and handle it differently.
6. Recommendation
- Create the virtual signer from the EOA address for KDF/Email
- Create the Safe with this virtual signer (to calculate the address)
- Store the virtual signer in
data.safe.signer(like passkeys) - Modify the operations code to detect the signer type:
- If
SignerType.PASSKEY: use the signer normally - If
SignerType.EOAorSignerType.EMAIL_TOKEN: prompt the client to sign
- If
This will allow us to:
- ✅ Create the Safe during signup (like passkeys)
- ✅ Store the signer in the DB (like passkeys)
- ✅ Use the real signer during operations (via client signature)