Skip to main content
Tusky provides end-to-end encryption for private environments. Data is encrypted client-side before upload and decrypted client-side on download. Tusky never sees plaintext. Encryption keys are managed through Seal — a decentralized key management protocol on Sui.

How it works

1

Environment key creation

When you create an encrypted environment, Tusky generates a symmetric AES-256-GCM key and encrypts it through Seal using the environment’s on-chain access policy. The encrypted key is stored as a managed secret.
2

Client-side encryption

Before upload, the SDK retrieves the environment key from Seal, encrypts the file locally with AES-256-GCM, then uploads the ciphertext. Plaintext never reaches Tusky servers or Walrus nodes.
3

Client-side decryption

On download, the SDK fetches the encrypted blob from an aggregator, retrieves the environment key from Seal, and decrypts locally. The SDK auto-detects encrypted content — no separate method needed.

Seal as a Key Management Service

Seal is not used to encrypt file data directly — that would be too slow for large files. Instead, Tusky uses Seal as a KMS (Key Management Service): Seal protects the encryption key, AES encrypts the data.
┌────────────┐    AES-256-GCM    ┌────────────────┐    Walrus    ┌─────────┐
│ Your file  │──────────────────▶│  Ciphertext     │────────────▶│ Storage │
└────────────┘                   └────────────────┘              └─────────┘

                                        │ encrypts with

┌────────────┐   Seal threshold  ┌──────┴───────────┐
│ Your wallet│──────────────────▶│  Environment key  │
└────────────┘   decryption      └──────────────────┘
                                   (stored as Seal secret)
LayerWhatHow
Data encryptionFile contentAES-256-GCM with the environment key
Key encryptionEnvironment keySeal threshold encryption across decentralized key servers
Access controlWho can decryptOn-chain Move policy verified by Seal key servers

Per-environment keys

All files in an environment share one encryption key. This is both a performance and a security design choice:
  • Single Seal request per session — the SDK caches the environment key after the first retrieval, so subsequent uploads and downloads require no additional Seal round-trips.
  • Batch operations — listing, downloading, or migrating many files only needs one key fetch.
  • Sharing is simple — granting access means adding a wallet to the environment’s Seal policy. One policy change covers all files.

Why this is safe

Each file encryption uses a unique random nonce (IV). Even though the same key encrypts every file, AES-256-GCM produces cryptographically independent ciphertexts — this is exactly how AES-GCM is designed to operate. The environment already defines the trust boundary: everyone with access can see all files in it, so the key boundary matches the access boundary. Per-file keys would add no security benefit — the same Seal policy protects the entire environment — while adding significant cost (N Seal round-trips, N on-chain key objects, N policy updates for sharing).
Use separate environments for separate trust levels. Each environment has its own key and its own Seal access policy — this is your security boundary.
The environment key is stored as a managed secret at tusky/environments/{environmentId}/encryption. You can read it via the Secrets API, but it cannot be deleted manually.

Decryption without Tusky

Encrypted blobs live on Walrus and encryption keys are managed by Seal — both are decentralized and independent of Tusky. If Tusky’s API goes down, you can still decrypt your data. What you need:
  1. The encrypted blob — fetch from any Walrus aggregator (Tusky’s, a public one, or your own)
  2. Seal access — Seal key servers are decentralized and available as long as the Sui network is running
  3. Your wallet — the Sui wallet authorized in the environment’s Seal policy

SDK with any aggregator

Configure the SDK to fetch from any aggregator — it does not have to be Tusky’s:
import { Tusky } from "@tusky-io/ts-sdk";

const tusky = new Tusky({
  wallet: keypair,
  aggregatorUrl: "https://aggregator.walrus.site", // any Walrus aggregator
});

// Download and decrypt — works without Tusky API
const content = await tusky.file.arrayBuffer("file_xyz789");

Manual decryption with Seal directly

For full independence from the Tusky SDK:
import { SealClient, SessionKey } from "@aspect-build/seal-sdk";
import { SuiClient, getFullnodeUrl } from "@mysten/sui/client";

const suiClient = new SuiClient({ url: getFullnodeUrl("mainnet") });

// 1. Create a session key
const sessionKey = await SessionKey.create({
  address: walletAddress,
  packageId: sealPackageId,
  ttlMin: 10,
  suiClient,
});
const msg = sessionKey.getPersonalMessage();
sessionKey.setPersonalMessageSignature(await wallet.sign(msg));

// 2. Build the Seal approval transaction
const tx = new Transaction();
tx.moveCall({
  target: `${policyPackageId}::environment_policy::seal_approve`,
  arguments: [tx.pure.vector("u8", environmentId)],
});
const txBytes = await tx.build({ client: suiClient, onlyTransactionKind: true });

// 3. Retrieve and decrypt the environment key
const sealClient = new SealClient({ suiClient, serverConfigs: [...] });
const envKey = await sealClient.decrypt({
  data: encryptedKeyBytes,
  sessionKey,
  txBytes,
});

// 4. Decrypt the file with AES-256-GCM using the environment key
This is the escape hatch. For day-to-day use, the SDK handles all of this transparently.

Sharing encrypted files

Share with specific wallets

Add a member to the environment. Tusky updates the on-chain Seal policy to include the new wallet:
await tusky.environment.addMember("env_abc123", {
  address: "0xRecipientWallet",
  role: "viewer",
});
// The recipient can now decrypt all files in the environment
Removing a member revokes future decryption access by removing their address from the Seal policy.
Data the member already downloaded locally cannot be un-shared. Treat environment membership as a trust decision.

Token-gated access (anyone who qualifies)

Gate access on any on-chain condition — no need to add members individually:
const env = await tusky.environment.create("NFT Holders Only", {
  encrypted: true,
  sealPolicy: {
    type: "nft",
    collectionId: "0xMyNFTCollection",
  },
});
Anyone holding an NFT from the collection can decrypt. Seal key servers verify ownership on-chain at decryption time.
Policies are composable. Combine NFT ownership, token balance, DAO membership, or custom Move logic for fine-grained access.

Public sharing

For files that should be accessible to anyone with the URL, use a public environment instead. Public environments store unencrypted blobs accessible through any aggregator.

Security properties

PropertyGuarantee
Zero-knowledgeTusky never sees plaintext or holds decryption keys.
Client-side onlyEncryption and decryption happen in your browser or SDK.
Wallet-boundKeys are accessible only through authorized Sui wallets. No passwords.
Decentralized keysSeal distributes key shares — no single point of failure.
On-chain policiesAccess rules are smart contracts: transparent, auditable, tamper-proof.
Tusky-independentDecryption works without Tusky as long as Seal and Walrus are available.

What’s next?