Skip to content

PassKey Signers

Passkeys signers are one of the available signers available in the web3_signers package, and is built on top of the passkeys library from corbado. Only signers that conform to the multi-signer-interface (MSI) can be used with variance.

import 'package:web3_signers/web3_signers.dart';

Passkey signers conform to the MSI and allow you to sign payloads using your device's passkeys. They fall under the secp256r1 category and can be verified on-chain using a P256 Verifier.

Creating a Passkey Options

To create a Passkey Options object, you need to provide the following information:

  1. namespace: The relying party ID (domain name), e.g., "variance.space".
  2. name: The relying party name, e.g., "variance".
  3. origin: The relying party origin, e.g., "https://variance.space".
  4. userVerification: required or preffered.
  5. requireResidentKey: a boolean value that specifies if the authenticator should require a resident key.,
  6. sharedWebauthnSigner: address of safe shared WebAuthn signer.
final options = PassKeysOptions(
    name: "variance",
    namespace: "variance.space",
    origin: "https://variance.space",
    userVerification: "required",
    requireResidentKey: true,
    sharedWebauthnSigner: EthereumAddress.fromHex("0xfD90FAd33ee8b58f32c00aceEad1358e4AFC23f9"));

Creating a Passkey Signer

To create a Passkey Options object, you need to provide the following information:

  1. options: A PassKeysOptions object.

You can optionally provide:

  1. knownCredentials: A set of known credential IDs (defaults to an empty set). If you already know the credential IDs created for the user, you can pass the knownCredentials when creating the Passkey signer. This enables the authenticator to filter the passkeys presented to the user for signing operations.
final PassKeySigner pkpSigner = PassKeySigner(
  options, knownCredentials: {...} // knownCredential is optional
);

Registering a New Passkey

To register a new passkey, you can use the register method:

PassKeyPair pkp = await pkpSigner.register("user@variance.space", "test user"); 
// Username is required, display name is optional

This returns a PassKeyPair object that contains the following information:

  • authData.b64Credential: The credential ID in base64 format.
  • authData.rawCredential: Uint8List representation of the credential ID.
  • authData.publicKey: A tuple containing the x and y coordinates of the public key as Uint256 instances.
  • username: The username of the registered user.
  • displayName: The display name of the registered user.
  • authData.aaGUID: The Authenticator Attestation GUID.
  • registrationTime: The timestamp of when the passkey was registered.

The PassKeyPair class provides methods to convert its instance to and from JSON format:

  • PassKeyPair.fromJson(String source): Constructs a PassKeyPair instance from a JSON string.
  • toJson(): Returns a JSON string representation of the PassKeyPair instance.

Signing with Passkeys

There are three methods for signing a payload using the Passkey signer:

Using personalSign

personalSign returns a Uint8List which is an encoded representation of the PassKeySignature object needed on-chain. To extract individual values, you need to abi.decode() it. The signed challenge is excluded from this object, and it is assumed that your relying party is aware of this challenge, which should be Base64Url encoded.

final sig = await pkpSigner.personalSign(Uint8List(32));

Using signToEc

Similar to personalSign, signToEc conforms to the MSI and returns an instance of MsgSignature containing the r, s, and v values of the signature. Effectively, v is 0.

final sig = await pkpSigner.signToEc(Uint8List(32));

Using signToPasskeySignature

This method is not part of the MSI but is called internally by personalSign and signToEc, and it returns the raw PassKeySignature object.

final sig = await pkpSigner.signToPasskeySignature(Uint8List(32));

For each of the above methods, you can pass an index if you have knownCredentials. This prompts the authenticator to sign specifically with a particular credential. For example:

await pkpSigner.signToPasskeySignature(Uint8List(32), 2); // Signing with knownCredential at index 2

PassKey Signature

The PassKeySignature class represents the signature generated by signing a payload with a passkey. It has the following properties:

  1. b64Credential: The signed credential ID in base64 format.
  2. rawCredential: Uint8List representation of the credential ID.
  3. signature: A tuple containing the r and s values of the signature as Uint256 instances.
  4. authData: The authenticator data as a Uint8List.
  5. clientDataJSON: The client data JSON string.
  6. challengePos: The position of the challenge in the client data JSON string.
  7. userId: The user ID.

The PassKeySignature class also provides a method to convert its instance to a Uint8List using ABI encoding:

  1. toUint8List(): Returns the ABI-encoded representation of the PassKeySignature instance as a Uint8List.

Usage

final pkpSigner = PassKeySigner(options: passkeyOptions); 
 
final walletFactory = SmartWalletFactory(chain, pkpSigner);
final keypair = await pkpSigner.register(username, displayName);
 
final salt = Uint256.zero;
final wallet = await walletFactory.createSafeAccountWithPasskey(keypair, salt, passkeyOptions.sharedWebauthnSigner);
 
print("wallet created ${wallet.address.hex} ");

By default the RIP-7212 precompile is used for signature verifications. but you can change it by adding an optional verifier.

final wallet = await walletFactory.createSafeAccountWithPasskey(
      keypair,
      salt,
      passkeyOptions.sharedWebauthnSigner,
      p256Verifier);