Skip to content

Wallet Signature Cache Not Scoped by PKP, Causing Multi-PKP Authentication Failures #997

@p0mvn

Description

@p0mvn

Is there an existing issue for this?

  • I have searched the existing issues

SDK version

  • SDK Version: v7.3.0

Lit Network

  • Network: datil-dev

Description of the bug/issue

When working with multiple PKPs sequentially, session signature generation fails because the SDK uses a globally cached wallet signature that is not scoped to individual PKPs. This causes the second (and subsequent) PKP to incorrectly use the first PKP's cached authentication data.

Impact

  • Multi-PKP applications: Cannot reliably use multiple PKPs in the same session
  • Testing: Tests pass individually but fail when run sequentially
  • Silent failure: No error during authentication; fails later with cryptic error message
  • Error message: "Access control conditions check failed" when attempting to use the PKP

Root Cause

File: packages/lit-node-client-nodejs/src/lib/lit-node-client-nodejs.ts:353

getWalletSig = async ({ ... }: GetWalletSigProps): Promise<AuthSig> => {
  let walletSig: AuthSig;

  const storageKey = LOCAL_STORAGE_KEYS.WALLET_SIGNATURE; // ❌ Not PKP-specific
  const storedWalletSigOrError = getStorageItem(storageKey);
  
  // Retrieves cached signature without validating PKP context
  if (storedWalletSigOrError.type !== EITHER_TYPE.ERROR) {
    walletSig = JSON.parse(storedWalletSigOrError.result as string);
  }
  
  return walletSig!;
};

The cache key "lit-wallet-sig" is not scoped by PKP public key, so all PKPs share the same cached signature.

Additional Context

The same issue likely affects SESSION_KEY caching (line 238) which also uses a non-scoped storage key.

Related Code

  • Cache key definitions: packages/constants/src/lib/constants/constants.ts:1551-1559
  • Storage utilities: packages/misc-browser/src/lib/misc-browser.ts:21-70

Severity of the bug

Medium

Steps To Reproduce

import { LitNodeClient } from '@lit-protocol/lit-node-client';
import { EthWalletProvider } from '@lit-protocol/lit-auth-client';

// Test 1: Create first PKP
const authMethod1 = await EthWalletProvider.authenticate({
  signer: wallet,
  litNodeClient,
  expiration,
});

const pkp1 = await api.pkp.mint.post({
  authMethod: authMethod1,
  scopes: [AUTH_METHOD_SCOPE.SignAnything],
});

const sessionSigs1 = await litNodeClient.getSessionSigs({
  pkpPublicKey: pkp1.publicKey,
  authNeededCallback: async () => { /* ... */ },
});

console.log('PKP1 Session Sig Address:', Object.values(sessionSigs1)[0].address);
// Output: "0x3D2870939c94FEC0aa94A78ff6a53FBF6b938aAc" ✅

// Test 2: Create second PKP (WITHOUT clearing localStorage)
const authMethod2 = await EthWalletProvider.authenticate({
  signer: wallet,
  litNodeClient,
  expiration,
});

const pkp2 = await api.pkp.mint.post({
  authMethod: authMethod2,
  scopes: [AUTH_METHOD_SCOPE.SignAnything],
});

const sessionSigs2 = await litNodeClient.getSessionSigs({
  pkpPublicKey: pkp2.publicKey,
  authNeededCallback: async () => { /* ... */ },
});

console.log('PKP2 Session Sig Address:', Object.values(sessionSigs2)[0].address);
// Output: "0x3D2870939c94FEC0aa94A78ff6a53FBF6b938aAc" ❌ WRONG! Should be PKP2's address
// Bug: sessionSigs2 contains PKP1's address because it used cached signature

// Later operations fail:
await litNodeClient.executeJs({
  sessionSigs: sessionSigs2, // ❌ Fails with "Access control conditions check failed"
  code: `...`,
});

Expected Behavior

Each PKP should have its own cached wallet signature, or the cache should be invalidated when the PKP context changes.

Current Workaround

Manually clear localStorage between PKP operations:

localStorage.removeItem('lit-wallet-sig');
localStorage.removeItem('lit-session-key');

Link to code

  • Affected Files:
    • packages/lit-node-client-nodejs/src/lib/lit-node-client-nodejs.ts
    • packages/lit-auth-client/src/lib/providers/EthWalletProvider.ts

Anything else?

  • Runtime: Node.js (Bun)

Suggested Fixes

Option 1: Scope cache key by PKP (Recommended)

const storageKey = pkpPublicKey 
  ? `${LOCAL_STORAGE_KEYS.WALLET_SIGNATURE}-${pkpPublicKey}`
  : LOCAL_STORAGE_KEYS.WALLET_SIGNATURE;

Option 2: Add validation
Check if cached signature matches current PKP context before reusing:

if (storedWalletSigOrError.type !== EITHER_TYPE.ERROR) {
  const cached = JSON.parse(storedWalletSigOrError.result as string);
  
  // Validate signature is for correct PKP
  if (isValidForCurrentPKP(cached, pkpPublicKey)) {
    walletSig = cached;
  }
}

Option 3: Clear cache on new authentication
EthWalletProvider.authenticate() should clear stale cached signatures:

public static async authenticate({ ... }) {
  // Clear previous PKP's cached signature
  removeStorageItem(LOCAL_STORAGE_KEYS.WALLET_SIGNATURE);
  removeStorageItem(LOCAL_STORAGE_KEYS.SESSION_KEY);
  
  // ... rest of authentication
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions