Tenzro
Custody and wallet

Passkey onboarding.

The default consumer wallet on Tenzro is a passkey-bound ERC-4337 smart account. The signing key lives in the user’s hardware secure element (Apple Secure Enclave, Windows Hello / TPM, Android StrongBox, YubiKey). No seed phrase. Rotation, recovery, and agent delegation all happen on-chain through ERC-7579 modular validators.
STATUS
Testnet
CRATE
tenzro-node
STABILITY
Stable
REFERENCE
passkey-onboarding
01

The model

Three pillars: the smart account address is the identity, not the signing key. Keys rotate without the address changing. Custody is enforced on-chain at signing time through composable ERC-7579 validator modules.

Reference implementations Tenzro is aligned with: Coinbase Smart Wallet, Daimo, Privy, Safe with modular validators, Argent on StarkNet. All share the same shape — passkey primary + guardian quorum recovery + session keys for app grants.

02

Substrate

tenzro-crypto::p256              # P-256 keypair, signer, verifier
tenzro-crypto::webauthn         # full WebAuthn assertion verifier
tenzro-vm precompile 0x100      # EIP-7951 / RIP-7212 P256VERIFY
tenzro-vm::WebAuthnValidator    # ERC-7579 primary validator
tenzro-vm::SocialRecoveryValidator   # 0x101d N-of-M guardians
tenzro-vm::SessionKeyValidator       # 0x101e scoped session keys
tenzro-vm::SpendingLimitValidator    # 0x101f per-tx + daily caps
tenzro-vm::AccountFactory       # ERC-4337 v0.8 EntryPoint compatible
03

Enrollment

The client acquires a WebAuthn registration credential from the platform authenticator and passes the P-256 public key + opaque credential ID + ML-DSA-65 verifying key (for the post-quantum leg) to tenzro_enrollPasskey. The node creates a TDIP human identity, deploys a smart account via the shared AccountFactory, installs WebAuthnValidator as the primary signer, and persists everything to CF_AGENTS + CF_VALIDATOR_MODULES.

POST https://rpc.tenzro.xyz
{
  "jsonrpc": "2.0",
  "method": "tenzro_enrollPasskey",
  "params": {
    "display_name": "Hilal's iPhone",
    "passkey_public_key_hex": "0x04...",
    "credential_id_hex": "0x...",
    "ml_dsa_public_key_hex": "0x..."
  },
  "id": 1
}
04

Signing

The client builds a UserOperation, hashes it per ERC-4337 v0.8, calls navigator.credentials.get() with the hash as the challenge, and submits the resulting WebAuthn assertion + ML-DSA-65 signature to tenzro_signWithPasskey. The node verifies the challenge matches the op hash, the P-256 leg validates against the registered public key, and the PQ leg validates against the registered ML-DSA verifying key.

For production transactions, the same hybrid signature packed as HybridWebAuthnSignature is supplied as the userOp.signature field on the standard eth_sendRawTransaction / ERC-4337 EntryPoint path. Each assertion carries the credential_id it was produced with, so the validator selects the right registered public key when an account has several devices enrolled. The on-chain WebAuthnValidator runs the same verification through the 0x100 P256VERIFY precompile.

05

Browser-launch login

A WebAuthn ceremony has to run in a browser context, next to the platform authenticator. For CLI and headless callers, Tenzro brokers the ceremony through a short-lived session the same way a cloud CLI brokers a device login. The caller opens tenzro_createPasskeySession, which returns a session id and a URL; the node serves the page at /auth/passkey?session=<id>. The page runs navigator.credentials.create() or get() and posts the outcome back against the session. The caller polls tenzro_getPasskeySession until the session reaches a terminal state, then reads the enrolled account or verified assertion off the result.

The session kind is one of enroll (new account via tenzro_enrollPasskey), add (additional device credential via tenzro_addPasskey), or sign (op-hash assertion via tenzro_signWithPasskey). Per-kind parameters are validated at createPasskeySession time so an error surfaces at the CLI rather than on a dead browser page. Sessions persist to CF_VALIDATOR_MODULES with a MAC-tagged row, so an in-flight login survives a node restart and a tampered row is refused at read time; expired sessions return a terminal expired status.

# gcloud-style browser login  creates an account
tenzro passkey login

# add another device to an existing account
tenzro passkey add --account 0x...

# approve one operation hash from the browser
tenzro passkey sign --account 0x... --op-hash 0x...
06

QR device handoff

The account owner does not have to complete the ceremony on the machine that started it. The node-served page renders an inline SVG QR code of its own session URL. Scanning it with a phone opens the same page on the phone, so the WebAuthn credential is created in the phone’s secure element (Apple Secure Enclave, Android StrongBox) and bound to the account. This is the “add my phone as a device” path: start tenzro passkey add on a laptop, scan the QR with the phone, complete Face ID / fingerprint there.

The private key never leaves the device that created it. A single Tenzro identity can carry several device credentials — a laptop platform authenticator, a phone, a hardware key — any one of which authorises operations unless a stricter second-factor policy is set.

An added device only supplies its WebAuthn P-256 credential. The node mints that credential’s ML-DSA-65 post-quantum leg in its TEE, so a phone or hardware key never has to carry a post-quantum key of its own.

07

Devices and second-factor policy

Credentials are managed per account: tenzro_addPasskey registers another device, tenzro_listPasskeys lists the enrolled credentials, and tenzro_removePasskey revokes one by credential_id. Removing a device does not change the smart account address.

The per-account authorisation requirement is a policy: tenzro_setPasskeyPolicy takes single_credential (any one enrolled credential authorises — the default) or two_credentials (two distinct enrolled credentials must both sign the same op hash). tenzro_getPasskeyPolicy reports the current policy and the enrolled-credential count.

A hardware second factor is not a policy variant — it is expressed by installing the hardware-signer validator module alongside the passkey validator (see below). The ValidatorRegistry AND-combines every installed module, so a two-credential policy and a hardware signer compose rather than conflict.

tenzro passkey add          --account 0x...   # register a device
tenzro passkey list         --account 0x...   # enrolled credentials
tenzro passkey remove       --account 0x... --credential-id 0x...
tenzro passkey set-policy   --account 0x... --policy two_credentials
tenzro passkey get-policy   --account 0x...
08

Social recovery

When the user enrolls, they nominate guardians — typically family members, a recovery service, or a backup hardware key. Each guardian holds an Ed25519 + ML-DSA-65 composite key. To rotate the account to a new passkey (after device loss), the user enrolls a new passkey on a new device, calls tenzro_initiateRecovery, and shares the returned recovery_op_hash with the guardians. Each guardian signs and submits via tenzro_submitRecoverySignature. Once quorum is reached, the user calls tenzro_finalizeRecoveryand the node installs the new passkey as the smart account’s primary validator. The smart account address never changes.

tenzro_addGuardian                  # register one guardian
tenzro_initiateRecovery             # open the rotation ceremony
tenzro_submitRecoverySignature      # one per guardian
tenzro_finalizeRecovery             # install new passkey on quorum
tenzro_listPendingRecoveries        # observe ceremony state
09

Session keys for agents

Agents never hold the human’s passkey. The human grants the agent a scoped session key: tenzro_grantSessionKey installs a SessionKeyValidator config with allowed function selectors, target contracts, per-call value cap, cumulative lifetime cap, and validity window. The agent uses its own Ed25519 key to sign user ops within the scope; anything outside the scope is refused at the on-chain validator.

Revocation is one call: tenzro_revokeSessionKey. The session validator disengages immediately and the agent loses signing authority.

10

Hardware signers

Power users layer a Ledger / Trezor / GridPlus / YubiKey as an additional ANDed validator: tenzro_addHardwareSigner installs a hardware validator module that the EntryPoint AND-combines with the primary passkey validator. Configure required_always: true for a hardware second factor on every operation, or set required_above_wei for a value threshold above which the hardware key is mandatory.

11

Surfaces

The passkey RPCs map 1:1 to client surfaces:

  • Rust SDK: TenzroClient::passkey_rpc() returns a PasskeyClient.
  • TypeScript SDK: TenzroClient.passkeyRpc.
  • CLI: tenzro passkey login | add | list | remove | sign | set-policy | get-policy | enroll | add-guardian | initiate-recovery | grant-session-key | add-hardware-signer | list-smart-accounts
  • MCP tools: enroll_passkey, sign_with_passkey, add_passkey_guardian, initiate_passkey_recovery, submit_recovery_signature, finalize_passkey_recovery, grant_session_key, revoke_session_key, set_spending_limit, add_hardware_signer, get_smart_account, list_smart_accounts, list_pending_recoveries, add_passkey, list_passkeys, remove_passkey, set_passkey_policy, get_passkey_policy, create_passkey_session, get_passkey_session.
  • A2A: passkey-wallet skill.
12

Hybrid post-quantum

Every passkey-bound smart account on Tenzro carries a hybrid PQ leg in addition to the classical P-256. The WebAuthnValidatorAND-combines the two — both must verify for the user op to validate. The PQ key (ML-DSA-65, 1952-byte vk, 3309-byte sig) lives alongside the passkey in the user’s key store and is enrolled at the same time as the passkey. This aligns Tenzro with the genesis v3 PQ-hybrid posture: classical + ML-DSA on every validator and identity.

Related
← All docs