Encryption
Packet uses asymmetric encryption to ensure only intended recipients can read message content. This page explains the cryptographic model and key derivation strategies.
Asymmetric Model
Every encrypted message is wrapped using an X25519 Diffie-Hellman key exchange. The sender and each intended reader contribute their public keys; the cipher is sealed so that only a party holding the corresponding private key can open it.
A single message can be readable by multiple readers simultaneously — for example, both the sender and receiver. This allows the sender's own app to re-read sent messages without storing a separate copy.
Key Sources
There are two ways a wallet establishes an encryption identity:
1. Wallet-Derived Key
The wallet's Ed25519 signing key is converted to an X25519 key via a deterministic curve conversion. This requires no on-chain account and no user interaction beyond owning the wallet. It works for any wallet that can sign transactions.
// SDK: set wallet-derived identity
client.useSolanaCryptoKeypair(keypair);
// Load a reader for a wallet using this method
const reader = client.loadWalletDerivedReader(recipientPublicKey);
Tradeoff: The encryption key is tied to the signing key. If the signing key is compromised, so is the encryption key.
2. Registered Key Account
A wallet can register a separate X25519 public key on-chain via a Key account. This decouples signing from encryption — the user can rotate or revoke the encryption key independently.
// Register a key on-chain
await client.createKeyFromCrypto();
// Load a reader using the registered key
const reader = await client.loadReaderForOwner({ ownerWallet: recipientPublicKey });
loadReaderForOwner can automatically fall back to the wallet-derived method if no Key account exists:
const reader = await client.loadReaderForOwner({
ownerWallet: recipientPublicKey,
fallbackToWalletDerived: true,
});
Encryption Identity in the CLI
The CLI always derives the identity from the configured keypair:
# The CLI uses useSolanaCryptoKeypair(keypair) automatically
# when --encrypt is passed to send commands
packet message new-thread --to <pubkey> --content "hello" --encrypt
Content Storage
Encrypted content is a JSON envelope produced by client.crypto.toJson(body). It contains:
- The ciphertext (base64)
- Per-reader encrypted symmetric keys
- Nonce and algorithm metadata
For large payloads, this JSON is uploaded to Irys and the message stores the CID. The receiver fetches the CID, downloads the JSON, then decrypts.
SDK Reference
client.useSolanaCryptoKeypair(keypair)— set identity from a Solana Keypair (server/Node)client.useWalletPasswordCrypto({ password, signMessage })— derive a deterministic X25519 identity from password + wallet signature (browser)client.crypto.encrypt({ plaintext, readers })— encrypt content for one or more readersclient.crypto.decrypt({ body })— decrypt content using the active identityclient.clearCrypto()— wipe the in-memory identity on logout
See Encryption Guide in the TypeScript SDK section for the full API reference.