Skip to main content

Rotate your revocable key

What this lets you do

Rotate your account's revocable key to a new revocableKeyIndex. Because the revocable key participates in authorization material, rotating it limits the blast radius if it's compromised, without forcing you to re-deposit or re-derive your other keys.

Constraints & limits

  • One on-chain transaction, Keystore.updateAuthPolicy, taking the old and new authDigest plus a Merkle proof of your existing keystore leaf. It preserves the bound nullifying-key hash and swaps only the revocable-key authDigest.
  • Doesn't migrate or re-create your existing notes. They stay spendable, but only with your current key bundle: a spend proves your current keystore leaf, so after rotating you use the new revocable key, not the old one. The nullifying key and the notes themselves don't change.
  • The index is monotonically increasing. You can't roll back to a previous index without re-rotating forward.
  • For a fresh account, the canonical starting value is 0x0 (no rotation performed yet).

What it unlocks next

  • Post-rotation, your ProtocolKeys bundle must include the new revocableKeyIndex when building a session.
  • Compromise-response workflow: detect the leak, rotate, then revoke / blacklist the old auth material.

How to use it

import { CryptoService } from "@privacy-pools-v2/sdk";
import { keccak256, toBytes } from "viem";

// The SDK does not ship an EIP-712 payload builder; construct the
// Signature-Based Derivation typed data yourself. It must match the
// payload signed at registration byte-for-byte.
const signerAddress = walletClient.account.address;
const addressHash = keccak256(toBytes(signerAddress));
const purpose =
"This signature is used to deterministically derive application-specific secrets from your master seed. It is not a transaction and will not cost any gas.";

const signature = await walletClient.signTypedData({
domain: {
name: "Standardized Secret Derivation",
version: "1",
verifyingContract: "0x0000000000000000000000000000000000000000",
salt: keccak256(toBytes(APP_IDENTIFIER)), // exported by the SDK
},
types: {
SecretDerivation: [
{ name: "purpose", type: "string" },
{ name: "addressHash", type: "bytes32" },
],
},
primaryType: "SecretDerivation",
message: { purpose, addressHash },
});

const { newRevocableKeyIndex } = await session.rotateRevocableKey({
signature,
signerAddress,
addressHash,
});

// The live session already applied the rotation internally. Future
// session builds must re-derive the key bundle at the new index: the
// revocable key is derived per-index, so the pre-rotation bundle no
// longer matches the on-chain auth digest.
const rotatedKeys = new CryptoService().deriveKeysFromSignature({
signature,
signerAddress,
addressHash,
revocableKeyIndex: newRevocableKeyIndex,
});
const newSession = await PoolSessionBuilder.fromConfig({
...baseConfig,
protocolKeys: { ...rotatedKeys, revocableKeyIndex: newRevocableKeyIndex },
}).create();

The SDK derives the next revocable key from the signature and returns the new index. Persist newRevocableKeyIndex, and on later session builds re-derive the key bundle at that index; reusing a pre-rotation privateRevocableKey with the new index fails every proof.

Behind the scenes

SDK call session.rotateRevocableKey(...)
Contract method Keystore.updateAuthPolicy(oldAuthDigest, newAuthDigest, siblingNodes): rotation, not setAuthPolicy (which is first-time registration)
State change Your account's auth policy on Keystore is updated and revocableKeyIndex advances. The on-chain nullifying-key hash is unchanged.