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.
Encryption receives a string. For user-facing messages, that string is usually an encoded Packet envelope built with PacketEnvelope or buildPacketEnvelopePayload. Encrypt the envelope string, not each attachment separately, when you want one message to contain multiple MIME-aware parts.
How it works
- Build a plaintext string. This can be plain text, JSON, or an encoded Packet envelope.
- A random 256-bit content key is generated for each encrypted body.
- The plaintext string 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
import {
AsymmetricEncryptionAlgorithm,
MessageType,
buildPacketEnvelopePayload,
parsePacketEnvelopeText,
} from "xpkt-sdk";
// 1. Set up identity (once per session)
client.useSolanaCryptoKeypair(keypair);
// 2. Build the payload. For real messages this can be a Packet envelope.
const plaintext = buildPacketEnvelopePayload({
subject: "Hello",
content: "Hello!",
contentType: "text/plain",
});
// 3. Encrypt — self is auto-included as a reader by default
const content = await client.crypto.encryptToContent({
plaintext,
readers: [
client.crypto.reader({
ownerWallet: recipientAddress,
keyAlg: AsymmetricEncryptionAlgorithm.SOLANA_ED25519_X25519,
publicKey: recipientPublicKeyBytes,
}),
],
});
// 4. Send the encrypted content as a normal text message
await thread.sendMessage({
messageType: MessageType.Text,
content,
});
// 5. Fetch the message, decrypt, then parse the Packet envelope
const msg = await thread.loadLastMessage();
const loaded = await msg.loadContent();
const result = await client.crypto.maybeDecrypt(loaded.text!);
if (result.encrypted) {
console.log(parsePacketEnvelopeText(result.plaintext).message);
}
The full method reference is in Encrypt & Decrypt. Type definitions are in Crypto Types.