Skip to content

WebAuthn Authenticator

The WebAuthn authenticator allows using Passkeys (TouchID, FaceID, YubiKey) to sign transactions.

Setup

First, initialize the WebAuthn authenticator with the user's identifier and a challenger.

ts
wa = await new WebAuthn(USERNAME, blockHashChallenger(client)).setup();

Registration

To register a new credential, call the register method. This will trigger the browser's WebAuthn prompt. The returned attestation must be submitted to the chain using the Pass.register extrinsic.

ts
const finalizedBlock = await client.getFinalizedBlock();
const attestation = await wa.register(finalizedBlock.number);

const tx = api.tx.Pass.register({
  user: Binary.fromBytes(wa.hashedUserId),
  attestation: {
    type: 'WebAuthn',
    value: {
      meta: attestation.meta,
      authenticator_data: attestation.authenticator_data,
      client_data: attestation.client_data,
      public_key: attestation.public_key,
    },
  },
});

await new Promise<void>((resolve, error) => {
  tx.signSubmitAndWatch(ALICE).subscribe({
    next: (event) => {
      if (event.type === 'finalized') {
        resolve();
      }
    },
    error,
  });
});

Authentication

Once registered, you can use the WebAuthn instance to create a KreivoPassSigner. This signer can then be used to sign transactions, which will trigger the browser's WebAuthn prompt for authentication.

ts
const kreivoPassSigner = new KreivoPassSigner(wa);
const accountId = ss58Encode(kreivoPassSigner.publicKey, 2);

// Transfer tokens
{
  const tx = api.tx.Balances.transfer_keep_alive({
    dest: { type: 'Id', value: accountId },
    value: 1_0000000000n,
  });

  await new Promise<void>((resolve, error) =>
    tx.signSubmitAndWatch(ALICE).subscribe({
      next: (event) => {
        if (event.type === 'finalized') {
          resolve();
        }
      },
      error,
    }),
  );
}

// Sign remark
{
  const remark = Binary.fromText('Hello, Kreivo!');
  const tx = api.tx.System.remark_with_event({ remark });

  const signedTx = await tx.sign(kreivoPassSigner, {
    mortality: { mortal: false },
  });
  const txBytes = Vector(u8).dec(signedTx);

  const txResult = await api.apis.BlockBuilder.apply_extrinsic(
    Binary.fromBytes(new Uint8Array(txBytes)),
  );

  assert(txResult.success);
  assert(txResult.value.success);
}