﻿# Encryption with VetKeys

> For the complete documentation index, see [llms.txt](/llms.txt)

VetKeys enable canisters to derive cryptographic key material on demand so that clients can encrypt and decrypt data without the canister ever seeing the raw key. This guide covers the complete flow: exposing vetKD endpoints in a canister, generating a transport key pair on the frontend, and using the derived key for symmetric encryption. It also covers higher-level patterns: the `EncryptedMaps` abstraction for encrypted key-value storage, and identity-based encryption (IBE) for sending encrypted messages to a principal.

For background on how the vetKD protocol works, see [VetKeys](../../concepts/vetkeys.md).

## Prerequisites

### Rust

Add the following to `Cargo.toml`:

```toml
[dependencies]
candid = "0.10"
ic-cdk = "0.19"
ic-vetkeys = "0.6"
ic-stable-structures = "0.7"
serde = { version = "1", features = ["derive"] }
serde_bytes = "0.11"
```

### Motoko

Add `ic-vetkeys` to `mops.toml`:

```toml
[package]
name = "my-vetkd-app"
version = "0.1.0"

[dependencies]
core = "2.0.0"
```

Frontend (TypeScript):

```bash
npm install @dfinity/vetkeys@0.4.0
```

## Step 1: Expose vetKD endpoints in the backend canister

The backend canister wraps the management canister's `vetkd_derive_key` and `vetkd_public_key` methods and enforces per-caller key isolation. The context passed to both API methods encodes the domain separator and the caller's principal, so each caller's keys are cryptographically separate and only that caller can retrieve them.

### Motoko

```motoko
import Array "mo:core/Array";
import Blob "mo:core/Blob";
import Nat8 "mo:core/Nat8";
import Principal "mo:core/Principal";
import Text "mo:core/Text";

persistent actor {

  type VetKdCurve = { #bls12_381_g2 };

  type VetKdKeyId = {
    curve : VetKdCurve;
    name  : Text;
  };

  type VetKdPublicKeyRequest = {
    canister_id : ?Principal;
    context     : Blob;
    key_id      : VetKdKeyId;
  };

  type VetKdPublicKeyResponse = {
    public_key : Blob;
  };

  type VetKdDeriveKeyRequest = {
    input                : Blob;
    context              : Blob;
    transport_public_key : Blob;
    key_id               : VetKdKeyId;
  };

  type VetKdDeriveKeyResponse = {
    encrypted_key : Blob;
  };

  let managementCanister : actor {
    vetkd_public_key : VetKdPublicKeyRequest -> async VetKdPublicKeyResponse;
    vetkd_derive_key : VetKdDeriveKeyRequest -> async VetKdDeriveKeyResponse;
  } = actor "aaaaa-aa";

  let domainSeparator : [Nat8] = Blob.toArray(Text.encodeUtf8("my_app_v1"));

  // Encodes domain separator + caller principal so each caller's keys are isolated.
  func callerContext(caller : Principal) : Blob {
    Blob.fromArray(
      Array.flatten([
        [Nat8.fromNat(domainSeparator.size())],
        domainSeparator,
        Blob.toArray(Principal.toBlob(caller)),
      ])
    )
  };

  func keyId() : VetKdKeyId {
    { curve = #bls12_381_g2; name = "test_key_1" }
    // Use "key_1" for production
  };

  public shared ({ caller }) func getPublicKey() : async Blob {
    let response = await managementCanister.vetkd_public_key({
      canister_id = null;
      context = callerContext(caller);
      key_id = keyId();
    });
    response.public_key
  };

  public shared ({ caller }) func getEncryptedVetKey(
    input : Blob,
    transportPublicKey : Blob,
  ) : async Blob {
    // test_key_1 costs ~10B cycles; key_1 costs ~26B cycles
    let response = await (with cycles = 10_000_000_000) managementCanister.vetkd_derive_key({
      input;
      context = callerContext(caller);
      transport_public_key = transportPublicKey;
      key_id = keyId();
    });
    response.encrypted_key
  };
};
```

### Rust

```rust
use ic_cdk::update;

const DOMAIN_SEPARATOR: &[u8] = b"my_app_v1";

/// Encodes domain separator + caller principal so each caller's keys are isolated.
fn caller_context(caller: candid::Principal) -> Vec<u8> {
    [DOMAIN_SEPARATOR.len() as u8]
        .into_iter()
        .chain(DOMAIN_SEPARATOR.iter().copied())
        .chain(caller.as_slice().iter().copied())
        .collect()
}

fn key_id() -> ic_cdk::management_canister::VetKDKeyId {
    ic_cdk::management_canister::VetKDKeyId {
        curve: ic_cdk::management_canister::VetKDCurve::Bls12_381_G2,
        name: "test_key_1".to_string(), // Use "key_1" for production
    }
}

#[update]
async fn get_public_key() -> Vec<u8> {
    let caller = ic_cdk::caller();
    let request = ic_cdk::management_canister::VetKDPublicKeyArgs {
        canister_id: None,
        context: caller_context(caller),
        key_id: key_id(),
    };
    let reply = ic_cdk::management_canister::vetkd_public_key(&request)
        .await
        .expect("vetkd_public_key call failed");
    reply.public_key
}

#[update]
async fn get_encrypted_vetkey(input: Vec<u8>, transport_public_key: Vec<u8>) -> Vec<u8> {
    let caller = ic_cdk::caller(); // capture before await
    // test_key_1 costs ~10B cycles; key_1 costs ~26B cycles
    let request = ic_cdk::management_canister::VetKDDeriveKeyArgs {
        input,
        context: caller_context(caller),
        transport_public_key,
        key_id: key_id(),
    };
    let reply = ic_cdk::management_canister::vetkd_derive_key(&request)
        .await
        .expect("vetkd_derive_key call failed");
    reply.encrypted_key
}

ic_cdk::export_candid!();
```

**Key decisions:**

- **Context**: encodes the domain separator (`my_app_v1`) plus the caller's principal. This makes every caller's keys cryptographically separate; a key derived for one principal cannot be decrypted by another. Both `getPublicKey` and `getEncryptedVetKey` must use the same context so that `decryptAndVerify` succeeds on the frontend.
- **Input**: an additional identifier within the caller's key space (a document ID, a room ID, or an empty vector for a single per-user key). Different inputs yield different keys; the same input always yields the same key.
- **Caller capture before `await`**: always read `caller` before any `await` in an update call.

## Step 2: Generate a transport key pair on the frontend

The transport key pair is ephemeral. Generate it fresh for each session or each key request.

```typescript
import { TransportSecretKey } from "@dfinity/vetkeys";

const transportSecretKey = TransportSecretKey.random();
const transportPublicKey = transportSecretKey.publicKeyBytes();
```

Pass `transportPublicKey` to the canister when requesting a derived key.

## Step 3: Retrieve and decrypt the vetKey

```typescript
import {
  TransportSecretKey,
  DerivedPublicKey,
  EncryptedVetKey,
} from "@dfinity/vetkeys";

// An additional identifier within the caller's key space.
// Use an empty vector for a single per-user key, or a document/room ID for multiple.
const input = new Uint8Array(0);

const [encryptedKeyBytes, verificationKeyBytes] = await Promise.all([
  backendActor.get_encrypted_vetkey(input, transportPublicKey),
  backendActor.get_public_key(),
]);

const verificationKey = DerivedPublicKey.deserialize(
  new Uint8Array(verificationKeyBytes),
);
const encryptedVetKey = EncryptedVetKey.deserialize(
  new Uint8Array(encryptedKeyBytes),
);

// Verify and decrypt: throws if the key is malformed or was tampered with
const vetKey = encryptedVetKey.decryptAndVerify(
  transportSecretKey,
  verificationKey,
  input,
);
```

## Step 4: Derive a symmetric key and encrypt data

The raw vetKey is not used directly as an AES key. Use `toDerivedKeyMaterial()` to derive a symmetric key from it.

```typescript
// Derive a 256-bit AES-GCM key
const aesKeyMaterial = vetKey.toDerivedKeyMaterial();
const aesKey = await crypto.subtle.importKey(
  "raw",
  aesKeyMaterial.data.slice(0, 32),
  { name: "AES-GCM" },
  false,
  ["encrypt", "decrypt"],
);

// Encrypt
const iv = crypto.getRandomValues(new Uint8Array(12));
const ciphertext = await crypto.subtle.encrypt(
  { name: "AES-GCM", iv },
  aesKey,
  new TextEncoder().encode("secret message"),
);

// Store ciphertext (and iv) in the canister; never store the key

// Decrypt
const plaintext = await crypto.subtle.decrypt(
  { name: "AES-GCM", iv },
  aesKey,
  ciphertext,
);
```

Store only the ciphertext and IV in the canister; the raw key exists only in the client's memory for the duration of the session.

## Using EncryptedMaps for encrypted key-value storage

`EncryptedMaps` is a higher-level abstraction that combines `KeyManager` (access-controlled vetKey derivation) with encrypted storage. It manages key derivation, access control, and client-side encryption transparently. Each named map is secured with a single vetKey; all key-value pairs in the map share the same access permissions.

### TypeScript

```typescript
import { EncryptedMaps } from "@dfinity/vetkeys/encrypted_maps";

// encryptedMapsClientInstance connects to your backend canister
const encryptedMaps = new EncryptedMaps(encryptedMapsClientInstance);

const mapOwner = Principal.fromText("aaaaa-aa");
const mapName = "passwords";
const mapKey = "email_account";

// Store an encrypted value (encryption is automatic)
const value = new TextEncoder().encode("my_secure_password");
await encryptedMaps.setValue(mapOwner, mapName, mapKey, value);

// Retrieve and decrypt a stored value
const stored = await encryptedMaps.getValue(mapOwner, mapName, mapKey);

// Grant another user read-write access to the map
const user = Principal.fromText("bbbbbb-bb");
await encryptedMaps.setUserRights(mapOwner, mapName, user, { ReadWrite: null });
```

The backend `EncryptedMaps` component stores only ciphertext; all plaintext stays on the frontend. See the [password manager example](https://github.com/dfinity/examples/tree/master/rust/vetkeys/password_manager) (Motoko + Rust) for a full implementation, or the [password manager with metadata](https://github.com/dfinity/examples/tree/master/rust/vetkeys/password_manager_with_metadata) variant that adds unencrypted metadata alongside encrypted values.

For the Rust backend, `EncryptedMaps` lives in `ic_vetkeys::encrypted_maps`; for TypeScript, import from `@dfinity/vetkeys/encrypted_maps`.

## Identity-based encryption (IBE)

IBE lets anyone encrypt a message to a principal using only the canister's public key. The recipient authenticates to the canister, obtains their corresponding vetKey, and decrypts. No prior key exchange is needed and the sender does not need the recipient to be online.

**Encrypt (sender, no canister call needed):**

```typescript
import {
  IbeCiphertext,
  IbeIdentity,
  IbeSeed,
  DerivedPublicKey,
} from "@dfinity/vetkeys";

// Derive the canister's IBE public key (fetch once, cache)
const publicKeyBytes = await backendActor.get_public_key();
const ibePublicKey = DerivedPublicKey.deserialize(new Uint8Array(publicKeyBytes));

// Encrypt to the recipient's principal
const recipientIdentity = IbeIdentity.fromBytes(recipientPrincipalBytes);
const seed = IbeSeed.random();
const plaintext = new TextEncoder().encode("secret message");

const ciphertext = IbeCiphertext.encrypt(
  ibePublicKey,
  recipientIdentity,
  plaintext,
  seed,
);
const serialized = ciphertext.serialize(); // store in the canister or transmit
```

**Decrypt (recipient, after obtaining vetKey):**

```typescript
import { IbeCiphertext } from "@dfinity/vetkeys";

// Obtain the vetKey for the recipient's principal (steps 2-3 above)
const vetKey = /* ... decryptAndVerify as shown in Step 3 ... */;

const deserialized = IbeCiphertext.deserialize(serialized);
const decrypted = deserialized.decrypt(vetKey);
// decrypted is Uint8Array of the plaintext
```

See the [basic IBE example](https://github.com/dfinity/examples/tree/master/rust/vetkeys/basic_ibe) (Motoko + Rust) for a complete backend and frontend implementation. For IBE with a time-based release condition (timelock encryption), see the [secret-bid auction example](https://github.com/dfinity/examples/tree/master/rust/vetkeys/basic_timelock_ibe).

## Testing locally

Start the local network and deploy:

```bash
icp network start -d
icp deploy backend
```

The local network automatically provisions both `test_key_1` and `key_1`. Verify that your canister returns a public key:

```bash
icp canister call backend get_public_key '()'
# Returns: (blob "...") -- 48+ bytes of BLS public key data
```

For `vetkd_derive_key` testing, use the [chain-key testing canister](https://github.com/dfinity/chainkey-testing-canister) (`vrqyr-saaaa-aaaan-qzn4q-cai`) on mainnet as a lower-cost alternative during development. It provides a fake vetKD implementation with no threshold. Use key name `insecure_test_key_1`. Never use it with real data or in production.

## Common mistakes

- **Reusing transport keys across sessions.** Each session must generate a fresh transport key pair.
- **Using the raw vetKey as an AES key.** Always call `toDerivedKeyMaterial()` first; do not pass the raw bytes to `importKey`.
- **Putting secret data in the `input` field.** The `input` is sent to the management canister in plaintext. Use it as an identifier (principal, document ID), not for the secret data itself.
- **Mismatched `context` between `getPublicKey` and `getEncryptedVetKey`.** Both endpoints must derive context from the same inputs (domain separator + caller principal). If they differ, `decryptAndVerify` will fail silently.
- **Not attaching enough cycles to `vetkd_derive_key`.** `test_key_1` costs approximately 10 billion cycles; `key_1` costs approximately 26 billion cycles.

## Next steps

- [VetKeys concept](../../concepts/vetkeys.md): how the vetKD protocol works and what use cases it enables
- [Data integrity](data-integrity.md): certified variables and response verification
- [Internet Identity](../authentication/internet-identity.md): authenticate users before granting access to vetKeys
- [vetkeys examples](https://github.com/dfinity/examples/tree/master/rust/vetkeys): password manager, encrypted notes, IBE messaging, BLS signing, and secret-bid auction
- [ic-vetkeys library](https://github.com/dfinity/vetkeys): Rust crate and TypeScript package source
