PassKey
Passkeys provide a significant advantage by freeing users from the need to manually manage and secure private keys. Instead of relying on traditional 12-word seed phrases, users can simply use a passkey-enabled device to manage their wallet. The hardware securely stores the passkey, while the hardware vendor (e.g., Apple/Google) ensures secure backup, providing both convenience and safety.
Additionally, Elytro wallet's default validator already supports passkey signature verification, further simplifying the user experience. This integration enables users to authenticate transactions and manage their wallets using passkeys, ensuring a seamless and secure interaction with Elytro wallet.
How passkey works in Elytro wallet
Passkey create process
A passkey is generated from a client-server handshake, with the frontend using the @passwordless-id/webauthn library to prompt the user to create a passkey.
For WebAuthn and passkeys, the most common format is a 64-byte uncompressed public key, consisting of the X and Y coordinates. However, in Elytro wallet, this public key is converted into a
bytes32format.Once a passkey is generated, its public key is used to calculate a unique
bytes32string. Thisbytes32string is then set as the passkey public key owner in Elytro wallet. The conversion from the public key to thebytes32key hash is performed as follows:
public static publicKeyToKeyhash(publicKey: ECCPoint | RSAPublicKey): string {
if (typeof publicKey === 'object' && Object.prototype.hasOwnProperty.call(publicKey, 'x') && Object.prototype.hasOwnProperty.call(publicKey, 'y')) {
// ES256
const es256Key = publicKey as ECCPoint;
return WebAuthN.p256PublicKeyToKeyhash(es256Key);
} else if (typeof publicKey === 'object' && Object.prototype.hasOwnProperty.call(publicKey, 'e') && Object.prototype.hasOwnProperty.call(publicKey, 'n')) {
// RS256
const rs256Key = publicKey as RSAPublicKey;
return WebAuthN.rs256PublicKeyToKeyhash(rs256Key);
} else {
throw new Error('invalid publicKey');
}
}In the Elytro wallet smart contract, this passkey will be a parameter in the
addOwnersfunction and will be stored as the passkey public key in the Elytro wallet smart contract.
function addOwners(bytes32[] calldata owners) external override {
ownerManagementAccess();
_addOwners(owners);
}Passkey verification process
For each ERC-4337 transaction, a signed UserOperation is sent to the bundler. Each UserOperation includes a unique UserOperation hash. The passkey is used to sign this UserOperation hash, ensuring that the transaction is authenticated by the rightful owner before being processed by the bundler.
The frontend will prompt the user to sign this
UserOperationhash using their passkey.
const signByPasskey = async (credential: any, userOpHash: string) => {
const userOpHashForBytes = userOpHash.startsWith('0x') ? userOpHash.substr(2) : userOpHash;
var byteArray = new Uint8Array(32);
for (var i = 0; i < 64; i += 2) {
byteArray[i / 2] = parseInt(userOpHashForBytes.substr(i, 2), 16);
}
let challenge = base64Tobase64url(btoa(String.fromCharCode(...byteArray)));
console.log('Authenticating with credential id', credential.id);
let authentication = await client.authenticate([credential.id], challenge, {
userVerification: 'required',
authenticatorType: 'both',
});
const authenticatorData = `0x${base64ToBigInt(base64urlTobase64(authentication.authenticatorData)).toString(16)}`;
const clientData = atob(base64urlTobase64(authentication.clientData));
const sliceIndex = clientData.indexOf(`","origin"`);
const clientDataSuffix = clientData.slice(sliceIndex);
console.log('decoded clientData', clientData, clientDataSuffix);
const signature = base64urlTobase64(authentication.signature);
console.log(`signature: ${signature}`);
if (credential.algorithm === 'ES256') {
const { r, s } = decodeDER(signature);
return {
messageHash: userOpHash,
publicKey: credential.publicKey,
r,
s,
authenticatorData,
clientDataSuffix,
};
} else if (credential.algorithm === 'RS256') {
return {
messageHash: userOpHash,
publicKey: credential.publicKey,
signature,
authenticatorData,
clientDataSuffix,
};
}
};Once the passkey signature is generated, the frontend will compose the
UserOperationsignature in the following format.
+-----------------+--------------------------------------------------------------------------------------------+
| | |
| Validator | Validator Signature |
| Signature | |
+-----------------+--------------------------------------------------------------------------------------------+
| | |
| Signature Type | Signature Data |
| | |
+-----------------+--------------------------------------------------------------------------------------------+
| | |
| 0x02 | Passkey Dynamic Signature |
| | |
+-----------------+--------------------------------------------------------------------------------------------+
When this
UserOperationis sent to the bundler, the Elytro wallet smart contract will verify the signature using the validator logic. Once the verification is successfully passed by the validator, theUserOperationtransaction will be considered valid and will proceed. The passkey verification logic is implemented in the code as shown below:
function recover(uint8 signatureType, bytes32 rawHash, bytes calldata rawSignature)
internal
view
returns (bytes32 recovered, bool success)
{
if (signatureType == 0x0 || signatureType == 0x1) {
//ecdas recover
(address recoveredAddr, ECDSA.RecoverError error,) = ECDSA.tryRecover(rawHash, rawSignature);
if (error != ECDSA.RecoverError.NoError) {
success = false;
} else {
success = true;
}
recovered = recoveredAddr.toBytes32();
} else if (signatureType == 0x2 || signatureType == 0x3) {
bytes32 publicKey = WebAuthn.recover(rawHash, rawSignature);
if (publicKey == 0) {
recovered = publicKey;
success = false;
} else {
recovered = publicKey;
success = true;
}
} else {
revert Errors.INVALID_SIGNTYPE();
}
}How are Passkeys Managed Across Devices and Restored?
Both Apple and Google offer built-in solutions for passkey synchronization and recovery:
For Apple users, passkeys generated on one device are automatically synced across other devices via iCloud Keychain, as long as the user is signed in with their Apple ID. For more details, please refer to Apple's Passkeys documentation.
For Google users, passkeys are seamlessly synced across devices using Google Password Manager. For more information, please refer to Google's Passkeys supported environments.
Last updated