@virtonetwork/authenticators-webauthn
v1.2.6
Published
An Authenticator compatible with KreivoPassSigner that uses the WebAuthn standard
Readme
WebAuthn Authenticator
A TypeScript helper that wires passkeys (WebAuthn resident credentials) to the @virtonetwork/signer stack. It exposes a single class, WebAuthn, that fulfils the Authenticator<number> interface used by PassSigner.
The implementation is browser‑only and keeps all credential mapping in the caller’s hands — perfect for SPAs or wallet extensions that already manage users.
✨ Features
- One‑line setup →
await new WebAuthn(user).setup() - Kreivo‑compatible challenges for secure on‑chain attestations
- Deterministic
deviceId = Blake2‑256(credentialId) - Produces SCALE‑encoded
Attestation/PassAuthenticateobjects - Zero persistence: inject or register credentials as you see fit
📦 Installation
npm i @virtonetwork/authenticators-webauthnSetup
First, initialize the WebAuthn authenticator with the user's identifier and a challenger.
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.
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.
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);
}🛠️ API
| Method | Returns | Notes |
| --------------------------------------------- | ------------------------------- | -------------------------------------------------------------------------------------- |
| setup() | Promise< this > | Computes hashedUserId. Call once. |
| register(blockNo, blockHash, [displayName]) | Promise<TAttestation<number>> | Generates a WebAuthn credential and attestation. Throws if credentialId already set. |
| authenticate(challenge, context) | Promise<TPassAuthenticate> | Signs an arbitrary 32‑byte challenge. Requires credentialId. |
| getDeviceId(webAuthn) | Promise |Blake2‑256(credentialId)wrapped inBinary. |
| setCredentialId(id) |void` | Inject credential id after construction. |
Type parameter
<number>→contextinside attestations/assertions is the block number.
📝 Persistence Strategy
This package does not store credential ids. A typical strategy is:
- During registration, persist
attestation.publicKey.byteskeyed byuserId. - On next load, feed that id into the
WebAuthnconstructor. - For multiple devices per account, maintain an array of ids and pick one UI‑side.
⚠️ Error Handling
| Error message | Cause | Fix |
| ------------------------------ | -------------------------------------------------------- | --------------------------------------------- |
| Already have a credentialId… | Called register() when id already present | Skip registration or call with a new instance |
| credentialId unknown… | Tried to authenticate/get device id without a credential | Inject stored id or call register() |
| DOMException: … | User dismissed the WebAuthn prompt | Ask user to retry |
🧳 Dependencies
- @virtonetwork/signer ≥ 0.10 — interfaces,
KreivoBlockChallenger,PassSigner - @polkadot-api/substrate-bindings —
Binary,Blake2256 - Browser with WebAuthn (Chrome ≥ 109, Firefox ≥ 106, Safari ≥ 16)
🩹 Development
# lint & type‑check
npm run lint && npm run typecheckTests
Go to tests/test.ts to check out our tests.
📄 License
MIT © Virto Network contributors
