> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/sovranBitcoin/sovran/llms.txt
> Use this file to discover all available pages before exploring further.

# P2PK Locking

> Lock ecash tokens to specific public keys for enhanced security

## Overview

P2PK (Pay-to-Public-Key) locking allows you to lock ecash tokens so that only the holder of a specific private key can redeem them. This provides an additional layer of security for ecash transfers.

## Key Management

Sovran includes a built-in keyring for managing P2PK keys:

### Key Types

**Derived Keys:**

```typescript app/settings-pages/keyring.tsx theme={null}
// Generated from your wallet's mnemonic
const keypair = await manager.keyring.generateKeyPair();
```

**Imported Keys:**

```typescript app/settings-pages/keyring.tsx theme={null}
// Import from nsec (Nostr) or raw hex
if (input.startsWith('nsec1')) {
  const decoded = nip19.decode(input);
  if (decoded.type === 'nsec') {
    await manager.keyring.addKeyPair(decoded.data as Uint8Array);
  }
}
```

### Keyring Storage

Keys are stored securely and managed through the keyring API:

```typescript app/settings-pages/keyring.tsx theme={null}
const loadKeypairs = useCallback(async () => {
  if (!manager) return;

  try {
    setIsLoading(true);
    const allKeys = await manager.keyring.getAllKeyPairs();
    setKeypairs(allKeys);
  } catch (error) {
    console.error('Failed to load keypairs:', error);
    keysLoadFailedPopup();
  } finally {
    setIsLoading(false);
  }
}, [manager]);
```

## Quick Access

Enable quick access to show your latest P2PK key in the receive menu:

```typescript stores/settingsStore.ts theme={null}
interface SettingsState {
  quickAccessP2PK: boolean;
  regenerateP2PKOnReceive: boolean;
  // ...
}

setQuickAccessP2PK: (enabled: boolean) => set({ quickAccessP2PK: enabled }),
getQuickAccessP2PK: () => get().quickAccessP2PK,
```

### Receive Screen Integration

```typescript components/screens/ReceiveScreen.tsx theme={null}
// Load latest keypair when P2PK quick access is enabled
useEffect(() => {
  const loadLatestKeypair = async () => {
    if (!manager || !quickAccessP2PK) return;
    try {
      const latest = await manager.keyring.getLatestKeyPair();
      setLatestKeypair(latest);
    } catch (error) {
      console.error('Failed to load latest keypair:', error);
    }
  };
  loadLatestKeypair();
}, [manager, quickAccessP2PK]);

// Build tabs array based on settings
const tabs = quickAccessP2PK ? ['Lightning', 'P2PK'] : ['Lightning'];
```

### P2PK Tab

When quick access is enabled, a P2PK tab appears in the receive screen:

```typescript components/screens/ReceiveScreen.tsx theme={null}
const renderP2PKContent = () => (
  <>
    {latestKeypair ? (
      <>
        <PaymentInfo data={latestKeypair.publicKeyHex} copyTarget="p2pk" unit="p2pk" />
        <View style={{ marginHorizontal: 16 }}>
          <Section title="P2PK PUBLIC KEY">
            <ListGroup variant="secondary">
              <PressableFeedback animation={false} onPress={handleCopyP2PKKey}>
                <PressableFeedback.Scale>
                  <ListGroup.Item disabled>
                    <ListGroup.ItemPrefix>
                      <Icon name="solar:key-bold" size={20} color={opacity(foreground, 0.4)} />
                    </ListGroup.ItemPrefix>
                    <ListGroup.ItemContent>
                      <ListGroup.ItemTitle>
                        {truncateMiddle(latestKeypair.publicKeyHex, 10)}
                      </ListGroup.ItemTitle>
                    </ListGroup.ItemContent>
                    <ListGroup.ItemSuffix>
                      <Icon name="lets-icons:copy" size={20} color={opacity(foreground, 0.4)} />
                    </ListGroup.ItemSuffix>
                  </ListGroup.Item>
                </PressableFeedback.Scale>
                <PressableFeedback.Ripple />
              </PressableFeedback>
            </ListGroup>
          </Section>
        </View>
      </>
    ) : (
      <View style={{ marginHorizontal: 16, marginTop: 32 }}>
        <View
          style={{
            backgroundColor: surfaceSecondary,
            borderRadius: 12,
            padding: 24,
            alignItems: 'center',
          }}>
          <Icon name="mdi:key-variant" size={48} color={opacity(foreground, 0.25)} />
          <Text
            size={14}
            style={{
              color: opacity(foreground, 0.4),
              marginTop: 12,
              textAlign: 'center',
            }}>
            No P2PK keys yet. Generate one in Settings → P2PK Keys.
          </Text>
        </View>
      </View>
    )}
  </>
);
```

## Auto-regeneration

Automatically generate a new P2PK key after redeeming locked tokens for improved privacy:

```typescript stores/settingsStore.ts theme={null}
setRegenerateP2PKOnReceive: (enabled: boolean) => set({ regenerateP2PKOnReceive: enabled }),
getRegenerateP2PKOnReceive: () => get().regenerateP2PKOnReceive,
```

Default: **Enabled** (recommended for privacy)

## Key Display Formats

### P2PK Format

Derived keys are displayed as P2PK hex:

```typescript app/settings-pages/keyring.tsx theme={null}
const displayKey = keypair.publicKeyHex;
```

### NPUB Format

Imported keys can be displayed as Nostr npub:

```typescript app/settings-pages/keyring.tsx theme={null}
const npubValue = !isDerived
  ? nip19.npubEncode(keypair.publicKeyHex.replace(/^02/, ''))
  : undefined;
```

### Current Key Display

```typescript app/settings-pages/keyring.tsx theme={null}
const CurrentKeyItem: React.FC<{
  keypair: Keypair;
  onCopy: (publicKey: string) => void;
}> = ({ keypair, onCopy }) => {
  const [selectedTab, setSelectedTab] = useState('P2PK');

  const isDerived = keypair.derivationIndex !== undefined;

  const npubValue = !isDerived
    ? nip19.npubEncode(keypair.publicKeyHex.replace(/^02/, ''))
    : undefined;

  const isNpubTab = selectedTab === 'NPUB' && !isDerived;
  const activeData = isNpubTab ? npubValue! : keypair.publicKeyHex;
  const displayKey = isDerived ? keypair.publicKeyHex : activeData;

  return (
    <View className="p-4">
      {!isDerived && (
        <View className="mb-4">
          <Tabs tabs={['P2PK', 'NPUB']} selectedTab={selectedTab} handleTabPress={handleTabPress} />
        </View>
      )}

      <PressableFeedback
        onPress={handleShowQR}
        className="mb-4 self-center overflow-hidden rounded-xl">
        <PressableFeedback.Highlight />
        <View
          style={{
            alignItems: 'center',
            padding: 12,
            backgroundColor: foreground,
            borderRadius: 12,
          }}>
          <QRCode value={activeData} size={120} color={surface} backgroundColor={foreground} />
        </View>
      </PressableFeedback>

      <HStack align="center" spacing={8} className="mb-3.5 flex-wrap gap-y-2">
        <Badge variant="success" icon="solar:key-bold" size={11}>
          ACTIVE
        </Badge>
        {!isDerived && (
          <Badge variant="primary" icon={isNpubTab ? 'ph:user-bold' : 'solar:key-bold'} size={11}>
            {isNpubTab ? 'NPUB' : 'P2PK'}
          </Badge>
        )}
        {isDerived && (
          <Badge variant="primary" icon="mdi:key-arrow-right" size={11}>
            DERIVED {keypair.derivationIndex}
          </Badge>
        )}
      </HStack>

      <View className="bg-surface rounded-xl px-3.5 py-3">
        <Text size={12} className="text-foreground">
          {displayKey}
        </Text>
      </View>

      <HStack spacing={10} className="mt-3.5">
        <Button variant="secondary" className="flex-1" onPress={handleCopy}>
          <Icon name="lets-icons:copy" size={16} color={muted} />
          <Button.Label style={{ color: muted }}>Copy</Button.Label>
        </Button>
        <Button variant="secondary" className="flex-1" onPress={handleShowQR}>
          <Icon name="stash:qr-code" size={16} color={muted} />
          <Button.Label style={{ color: muted }}>Show QR</Button.Label>
        </Button>
      </HStack>
    </View>
  );
};
```

## Key Import

### Supported Formats

Sovran supports importing keys in multiple formats:

```typescript app/settings-pages/keyring.tsx theme={null}
const tryImportKey = async (input: string): Promise<boolean> => {
  if (!manager) return false;

  // Strategy 1: Try as nsec
  if (input.startsWith('nsec1')) {
    try {
      const decoded = nip19.decode(input);
      if (decoded.type === 'nsec') {
        await manager.keyring.addKeyPair(decoded.data as Uint8Array);
        return true;
      }
    } catch {}
  }

  // Strategy 2: Try as raw 64-char hex
  const rawBytes = hexToBytes(input);
  if (rawBytes) {
    try {
      await manager.keyring.addKeyPair(rawBytes);
      return true;
    } catch {}
  }

  return false;
};

const hexToBytes = (hex: string): Uint8Array | null => {
  if (hex.length !== 64 || !/^[0-9a-fA-F]+$/.test(hex)) {
    return null;
  }
  const bytes = new Uint8Array(32);
  for (let i = 0; i < 32; i++) {
    bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
  }
  return bytes;
};
```

### Import Flow

```typescript app/settings-pages/keyring.tsx theme={null}
const handleImportNsec = () => {
  Alert.prompt(
    'Import Private Key',
    'Enter your nsec or hex private key',
    [
      { text: 'Cancel', style: 'cancel' },
      {
        text: 'Import',
        onPress: async (value: string | undefined) => {
          if (!value || !manager) return;

          try {
            const trimmedValue = value.trim();
            const success = await tryImportKey(trimmedValue);

            if (success) {
              keyImportedPopup();
              await loadKeypairs();
            } else {
              invalidKeyFormatPopup();
            }
          } catch (error) {
            console.error('Failed to import key:', error);
            keyImportFailedPopup();
          }
        },
      },
    ],
    'plain-text'
  );
};
```

## Key Generation

```typescript app/settings-pages/keyring.tsx theme={null}
const handleGenerateKey = async () => {
  if (!manager) return;

  try {
    setIsGenerating(true);
    await manager.keyring.generateKeyPair();
    keyGeneratedPopup();
    await loadKeypairs();
  } catch (error) {
    console.error('Failed to generate keypair:', error);
    keyGenerateFailedPopup();
  } finally {
    setIsGenerating(false);
  }
};
```

## Settings

### Preferences

```typescript app/settings-pages/keyring.tsx theme={null}
<Section title="Preferences">
  <ListGroup variant="secondary">
    <ListGroup.Item>
      <ListGroup.ItemContent>
        <ListGroup.ItemTitle>Quick Access to Lock</ListGroup.ItemTitle>
        <ListGroup.ItemDescription>
          Show your latest P2PK locking key in the receive ecash menu
        </ListGroup.ItemDescription>
      </ListGroup.ItemContent>
      <ListGroup.ItemSuffix>
        <HeroSwitch
          isSelected={quickAccessP2PK ?? false}
          onSelectedChange={setQuickAccessP2PK}
        />
      </ListGroup.ItemSuffix>
    </ListGroup.Item>
    <Separator className="mx-4" />
    <ListGroup.Item>
      <ListGroup.ItemContent>
        <ListGroup.ItemTitle>Regenerate Key on Receive</ListGroup.ItemTitle>
        <ListGroup.ItemDescription>
          Automatically generate a new P2PK key after redeeming a locked token for improved
          privacy
        </ListGroup.ItemDescription>
      </ListGroup.ItemContent>
      <ListGroup.ItemSuffix>
        <HeroSwitch
          isSelected={regenerateP2PKOnReceive ?? true}
          onSelectedChange={setRegenerateP2PKOnReceive}
        />
      </ListGroup.ItemSuffix>
    </ListGroup.Item>
  </ListGroup>
</Section>
```

## Use Cases

### Locked Transfers

Send ecash tokens that can only be redeemed by a specific recipient:

1. Obtain recipient's P2PK public key
2. Create send token with P2PK lock
3. Only the recipient with matching private key can claim

### Personal Recovery

Lock tokens to your own P2PK key for additional security:

1. Generate or import a P2PK key
2. Lock tokens to your public key
3. Keep private key secure as backup recovery method

### Nostr Integration

Use Nostr keys (nsec/npub) for P2PK locking:

1. Import your Nostr nsec
2. Share your npub as receiving address
3. Tokens locked to your Nostr identity
