Link a device and set up recovery
Add a second passkey on another device, require two devices for high-value accounts, name guardians, and recover an account after a lost device with a timelock and veto.
A device-bound passkey cannot be copied off its hardware. That is what makes it a sound root for a wallet, and it is also why a wallet on one device cannot survive losing that device. Recovery on Network 1 therefore comes from more than one independent root, never from weakening any single one:
- More devices. A passkey on your phone, another on your laptop, a third on a security key. Each is device-bound; together they survive the loss of any one.
- Guardians. Other Tenzro identities that can co-sign the enrolment of a new device when you have none left.
- A timelock with a veto. A recovery does not take effect immediately. While it waits, any passkey still enrolled on the account can cancel it.
In this tutorial you link a second device to the wallet from Build a passkey wallet, check that the wallet is ready to hold value, add guardians and walk through a recovery.
Prerequisites
- A passkey wallet with its smart account address and human DID (from the previous tutorial or the console wallet).
- A second device with a passkey-capable authenticator, such as a phone.
- The
tenzroCLI (see CLI reference). The examples pass--rpc https://rpc.tenzro.xyz; point them at your own node if you run one.
export ACCOUNT=0x... # your smart account
export DID=did:tenzro:human:... # your human DID
export RPC=https://rpc.tenzro.xyz1. Check whether the wallet is ready
A wallet should not sit behind a single device. Ask the node:
tenzro device wallet-readiness $DID --rpc $RPCWith one device enrolled you see something like:
{
"identity_did": "did:tenzro:human:...",
"ready": false,
"blocker": "a wallet needs a second device before it can be created ...",
"remedy": "bind_second_device",
"bound_devices": 1,
"hardware_bound_devices": 1
}Use ready to decide what your app offers. If it is false, prompt the user to link a device before offering to receive funds.
2. Link a second device
Adding a device is a custody change, so it takes two ceremonies: proof of control from a passkey already on the account, then the new passkey itself. Knowing the account address proves nothing; it is a public identifier.
From the CLI, the flow runs in a browser. It opens the node's passkey page, you approve with the passkey you already have, and the page shows a QR code. Scan it with your phone and create the new passkey there (the FIDO cross-device flow):
tenzro passkey add --account-address $ACCOUNT --label "Alice's phone" --rpc $RPCFrom your own app, do the same in three calls. hybridAssert, createPasskey, hex and fromHex are the helpers from Build a passkey wallet:
// 1. The existing device proves control.
const ch = await client.passkeyRpc.createCustodyChallenge({
account_address: ACCOUNT,
operation: "add_passkey",
});
const proof = await hybridAssert(existingCredentialId, fromHex(ch.challenge_hex));
// 2. The new device creates its passkey (run this on the new device).
const next = await createPasskey("alice-phone");
// 3. Add it, carrying the proof.
const added = await client.passkeyRpc.addPasskey({
account_address: ACCOUNT,
new_passkey_public_key_hex: hex(next.publicKey),
new_credential_id_hex: hex(next.credentialId),
label: "Alice's phone",
authorization: {
challenge_id: ch.challenge_id,
credential_id_hex: hex(existingCredentialId),
assertion: proof.assertion,
ml_dsa_signature_hex: proof.mlDsaSignatureHex,
},
});
console.log(added);Expected output:
{
"account_address": "0x...",
"credential_id_hex": "...",
"credentials_total": 2,
"label": "Alice's phone"
}Custody challenges are single-use, bound to one account, one operation and (where there is one) one target, and they expire after a few minutes. A challenge left in a terminal's scrollback is not a standing authorisation.
3. Review what each device proved
List the credentials on the account and the devices bound to your identity:
tenzro passkey list --account-address $ACCOUNT --rpc $RPC
tenzro device list $DID --rpc $RPCThe device list shows what each registration proved about its hardware:
{
"identity_did": "did:tenzro:human:...",
"count": 2,
"hardware_bound_count": 2,
"wallet_ready": true,
"devices": [
{
"credential_id": "...",
"label": "Alice's phone",
"hardware_bound": true,
"attestation_format": "...",
"key_protection": "...",
"backup_eligible": false,
"backed_up": false,
"bound_at_ms": 1790000000000
}
]
}hardware_bound comes from the attestation the node verified, not from anything the device claimed. A synced passkey (backup_eligible: true) is accepted at a lower tier and never counts as the only root. Run tenzro device wallet-readiness again: with two independent devices it reports ready: true.
4. Require two devices for every operation (optional)
For a treasury or an operator account you can require two distinct passkeys to co-sign every operation:
tenzro passkey set-policy --account-address $ACCOUNT \
--second-factor two_credentials --rpc $RPC
tenzro passkey get-policy --account-address $ACCOUNT --rpc $RPC{
"account_address": "0x...",
"second_factor": "two_credentials",
"required_signatures": 2,
"credentials_enrolled": 2
}With this policy each signature carries two hybrid legs from two different devices. Switch back with --second-factor single_credential.
5. Add guardians
Guardians are other identities, such as a partner, a colleague or a second organisation, that can co-sign the enrolment of a new device if you lose every device you have. Each guardian gives you their hybrid public keys (an Ed25519 key and a 1952-byte ML-DSA-65 verifying key):
tenzro passkey add-guardian --account-address $ACCOUNT \
--guardian-ed25519-hex <guardian-1-ed25519> \
--guardian-ml-dsa-hex <guardian-1-ml-dsa-65> \
--label "Bob" --rpc $RPC
tenzro passkey add-guardian --account-address $ACCOUNT \
--guardian-ed25519-hex <guardian-2-ed25519> \
--guardian-ml-dsa-hex <guardian-2-ml-dsa-65> \
--label "Carol" --threshold 2 --rpc $RPC{ "account_address": "0x...", "guardian_count": 2, "threshold": 2 }Adding a guardian is a custody change like any other, so it is authorised by a passkey already on the account.
6. Recover after losing a device
If you still have another enrolled device, you do not need recovery: sign in on that device, add a replacement, and remove the lost one (step 7).
If you have lost every device, start a recovery from a new device. Create a passkey there and derive its ML-DSA-65 key as in Build a passkey wallet, then:
tenzro passkey initiate-recovery --account-address $ACCOUNT \
--new-passkey-pubkey-hex <new-p256-sec1> \
--new-credential-id-hex <new-credential-id> \
--new-ml-dsa-pubkey-hex <new-ml-dsa-65> \
--rpc $RPC{
"recovery_id": "...",
"account_address": "0x...",
"recovery_op_hash_hex": "...",
"expires_at_ms": 1790086400000,
"guardians_required": 2,
"guardians_total": 2
}Send recovery_id and recovery_op_hash_hex to your guardians. Each signs the hash with their hybrid key and submits a composite signature (the Ed25519 signature followed by the ML-DSA-65 signature):
tenzro passkey submit-recovery-signature --recovery-id <id> \
--guardian-index 0 --composite-signature-hex <ed25519||ml-dsa-65> --rpc $RPC{
"recovery_id": "...",
"guardian_signatures_collected": 2,
"guardians_required": 2,
"quorum_reached": true
}The recovery then waits out its timelock. During that window any passkey still enrolled on the account can cancel it, so a recovery started by someone else fails as soon as you notice it. Watch in-flight recoveries with:
tenzro passkey list-pending-recoveries --account-address $ACCOUNT --rpc $RPCWhen the timelock has passed unopposed, finalise:
tenzro passkey finalize-recovery --recovery-id <id> --rpc $RPCThe new passkey is enrolled on the same account. Your DID, address, balance and reputation are unchanged.
7. Remove a lost device
Unbind the lost device and end every session it authorised in one action, then remove its credential from the account:
tenzro device revoke $DID <lost-credential-id> --rpc $RPC
tenzro passkey remove --account-address $ACCOUNT \
--credential-id-hex <lost-credential-id> --rpc $RPCRemoving a credential is authorised by a passkey that remains on the account, which is exactly the protection you want: nobody who merely knows your address can lock you out.
Next steps
- Read the model behind this flow: Device linking and recovery.
- Set per-transaction and daily limits: Smart-account policies.
- Give an agent a narrow, expiring key: Install an ERC-7579 session key.