Crypto Overview
Packet messages are end-to-end encrypted. The crypto layer is fully separate from the on-chain program — you encrypt before sending and decrypt after fetching. The PacketClient exposes encryption through the client.crypto accessor, which returns a PacketEncryptionClient.
How it works
- A random 256-bit content key is generated for each message.
- The plaintext is encrypted with AES-256-GCM (or XSalsa20-Poly1305) using that key.
- For each intended reader, the content key is wrapped (encrypted) using the reader's public key and their key algorithm.
- The encrypted body — ciphertext plus all wrapped keys — is stored as a JSON blob, either inline on-chain or off-chain via IPFS/Irys/Arweave.
To decrypt, a reader finds their wrapped key entry, unwraps the content key with their private key, and decrypts the ciphertext.
Key algorithms
Two asymmetric algorithms are supported for wrapping the content key:
| Algorithm | Constant | Key derivation | Body encryption |
|---|---|---|---|
| X25519 | AsymmetricEncryptionAlgorithm.X25519 | HKDF-SHA256 | AES-256-GCM |
| Solana wallet-derived | AsymmetricEncryptionAlgorithm.SOLANA_ED25519_X25519 | NaCl Box Before | XSalsa20-Poly1305 |
SOLANA_ED25519_X25519 lets a Solana wallet sign messages and derive an X25519 key from its Ed25519 key, so the same wallet used for transactions can also decrypt messages without a separate key pair.
Setting up an identity
Before encrypting or decrypting, set an identity on the client. There are three helpers depending on your environment:
Node / server-side (keypair available)
client.useSolanaCryptoKeypair(keypair);
Uses the raw Keypair directly. Not available for browser wallets — they don't expose secret keys.
Browser wallet (password-based)
const derived = await client.useWalletPasswordCrypto({
password: "my-password",
signMessage: wallet.signMessage, // from your wallet adapter
});
Derives a deterministic X25519 identity from password + wallet address + a signed message. The user's wallet never exposes its secret key.
Manual
import { AsymmetricEncryptionAlgorithm } from "xpkt-sdk";
client.useCrypto({
ownerWallet: myPublicKey,
keyAlg: AsymmetricEncryptionAlgorithm.X25519,
publicKey: myX25519PublicKey,
privateKey: myX25519PrivateKey,
});
Typical flow
// 1. Set up identity (once per session)
client.useSolanaCryptoKeypair(keypair);
// 2. Encrypt — self is auto-included as a reader by default
const content = await client.crypto.encryptToContent({
plaintext: "Hello!",
readers: [
client.crypto.reader({
ownerWallet: recipientAddress,
keyAlg: AsymmetricEncryptionAlgorithm.SOLANA_ED25519_X25519,
publicKey: recipientPublicKeyBytes,
}),
],
});
// 3. Send the encrypted content as a normal text message
await thread.sendMessage({
messageType: MessageType.Text,
content,
});
// 4. Fetch the message and decrypt
const msg = await thread.loadLastMessage();
const loaded = await msg.loadContent();
const result = await client.crypto.maybeDecrypt(loaded.text!);
if (result.encrypted) {
console.log(result.plaintext);
}
The full method reference is in Encrypt & Decrypt. Type definitions are in Crypto Types.