Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

aztec-passkey-wallet

Passkey-backed Aztec accounts for the browser. The passkey is the signing key: a custom Noir account contract verifies the full WebAuthn envelope in-circuit, so every transaction is authorized by Face ID / fingerprint / PIN with no stored key material. Ephemeral session keys remove the biometric prompt for batch flows after a single on-chain authorization.

Extracted from the production wallet at Apertrue. Apache 2.0.

What's inside

Piece Description
contract/ Aztec-nr account contract (Noir). Two auth paths selected by a witness discriminator: WebAuthn P-256 (full envelope verified in-circuit: clientDataJSON challenge match, SHA-256, authenticatorData concat, ECDSA P-256) and Schnorr-on-Grumpkin session keys authorized via on-chain notes.
src/wallet.ts Passkey registration, P-256 pubkey extraction, account deployment, backup/restore (PBKDF2 310k + AES-256-GCM), diversified addresses.
src/session-keys.ts SessionKeyManager: ephemeral Schnorr keys held in JS memory only, on-chain authorize/revoke, stale-key recovery.
src/secret-storage.ts WebAuthn PRF-derived secrets (preferred) with password and plaintext fallbacks; HKDF domain separation.
src/pxe.ts Browser PXE bootstrap against a remote node: IndexedDB persistence, fail-soft connection, network-restart detection.
src/sponsored-fpc.ts SponsoredFPC fee payment; multiple FPC addresses supported for correlation mitigation.
src/browser-wallet.ts Minimal Wallet bridge between PXE, the account entrypoint, and contract calls.
src/encryption.ts Client-side AES-256-GCM file encryption keyed alongside the wallet (optional).

Quick start

import { configure, registerPasskey, getAztecWallet, startSession } from 'aztec-passkey-wallet';

// Once, at app startup
configure({
  nodeUrl: 'https://your-node.example.com',
  sponsoredFpcAddresses: ['0x...'],
  rpName: 'My App',
  // rpId: 'example.com', // defaults to the current hostname; set a registrable
  //                      // suffix when the app runs on a subdomain
});

// Create a wallet: passkey + account contract deploy
await registerPasskey();
const { wallet, meta } = await getAztecWallet(); // biometric per TX

// Optional: authorize a session key (one biometric),
// then transact without prompts until expiry
await startSession();

Serve the compiled contract artifacts (webauthn_account-WebAuthnAccount.json, SponsoredFPC.json) under contractsBasePath (default /contracts). Build the account contract with contract/build.sh (requires the aztec CLI, which runs nargo, transpiles, and generates verification keys in one step).

The account is cryptographically bound to rpId at deploy, so it must stay stable for the account's lifetime. Changing rpId (or the hostname when it's unset) produces a different account.

Security model

What the account contract enforces in-circuit for the WebAuthn path:

  • The P-256 signature verifies against the public key fixed at deploy — the private key never leaves the authenticator (secure enclave / platform keystore); the contract stores only the public key.
  • The transaction hash is bound as the WebAuthn challenge (checked inside clientDataJSON).
  • The assertion's authenticatorData User Verified and User Present flags must be set, so "user verification per transaction" is a circuit-enforced property, not just a client request.
  • The assertion's rpIdHash must equal the relying-party hash fixed at deploy, binding the account to one relying party rather than trusting the browser/authenticator to refuse other origins.

Off-circuit properties:

  • Wallet secrets (Aztec secret key + salt) are derived from the WebAuthn PRF extension where available and held only in page memory — never written to localStorage/sessionStorage/IndexedDB. A page reload re-derives them from the passkey. Fallbacks when PRF is unavailable: PRF-wrapped IndexedDB storage, password wrapping, or plaintext (with a console warning — avoid).
  • The PRF-to-field derivation follows RFC 9380 hash-to-field (derive 384 bits, then reduce mod the BN254 prime) so the secret key is statistically uniform.
  • Session keys exist only in JS memory and are cleared on logout, page unload, or expiry.
  • Backups are AES-256-GCM encrypted with a PBKDF2 (310k iterations) password key.

What this does NOT protect against

  • Session-key expiry is enforced client-side only — the circuit stores expiry in the note but does not check it. Until revoke_session_key() nullifies the note on-chain, a stolen session key works past its expiry. Treat a live session key as a bearer credential.
  • Session-key scope is stored but not enforced — every session key has full account access regardless of the scope value.
  • A compromised page (XSS) can transact while a session is active, or trigger a WebAuthn prompt the user may approve. The design limits secret exfiltration (no secret at rest to steal), not in-session misuse.
  • The plaintext secret fallback stores the Aztec secret key unencrypted in IndexedDB on authenticators without PRF or password wrapping. It exists for functionality on limited devices; don't rely on it for high-value accounts.
  • Max 10 concurrent session keys per account (circuit loop bound); JS memory zeroing is best-effort (the GC may retain copies).

This library has not had an external security audit. Review the circuit and the derivation before using it for anything of value.

Storage & namespacing

All IndexedDB databases and localStorage keys are prefixed with namespace (default aztec-passkey). Key-derivation salts are fixed protocol constants and are deliberately not namespaced — changing them would lock users out of existing wallets.

Versions

Built and tested against @aztec/* 4.1.3 (peer dependencies) and aztec-nr v4.1.2 for the contract. Browser proving requires cross-origin isolation (COOP/COEP headers) for multithreaded WASM.

Demo

demo/ contains a minimal Vite app: register a passkey, deploy the account, authorize a session key, and send transactions with and without biometrics. See demo/README.md.

License

Apache 2.0. Contributions welcome.

About

Passkey-backed Aztec account: WebAuthn P-256 verified in-circuit, Schnorr session keys, browser PXE, sponsored fees

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages