Skip to main content

vetkeys

Build vetKeys cryptography on the Internet Computer via the vetKD system API and the ic-vetkeys (Rust, Motoko) and @icp-sdk/vetkeys (frontend) libraries: identity-based encryption (IBE), threshold BLS signatures, timelock encryption, symmetric key derivation, and offline public-key derivation. Use when implementing IBE, encrypting to a principal, BLS signing, sealed-bid or timelock schemes, deriving encryption keys on-chain, transport keys, or calling vetkd_public_key / vetkd_derive_key. For access-controlled encrypted key-value storage (password managers, encrypted notes), use the encrypted-maps skill instead. Not for authentication — use internet-identity.

Ir para a instalação

Informações da origem

Repositório
dfinity/icskills
Última atividade na origem
14 de agosto de 2026 às 16:16
Idioma detectado do SKILL.md
inglês
Estrelas
34
Forks
14

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Explorador de arquivos
3 arquivos

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
vetkeys
description
Build vetKeys cryptography on the Internet Computer via the vetKD system API and the ic-vetkeys (Rust, Motoko) and @icp-sdk/vetkeys (frontend) libraries: identity-based encryption (IBE), threshold BLS signatures, timelock encryption, symmetric key derivation, and offline public-key derivation. Use when implementing IBE, encrypting to a principal, BLS signing, sealed-bid or timelock schemes, deriving encryption keys on-chain, transport keys, or calling vetkd_public_key / vetkd_derive_key. For access-controlled encrypted key-value storage (password managers, encrypted notes), use the encrypted-maps skill instead. Not for authentication — use internet-identity.
license
Apache-2.0
compatibility
icp-cli >= 0.2.2
metadata
{"title":"vetKeys","category":"Security"}
# vetKeys (Verifiable Encrypted Threshold Keys) vetKeys bring on-chain privacy to the IC via the **vetKD** protocol: a canister requests a key derived by the subnet's threshold key-derivation infrastructure, receives it **encrypted** under a client-supplied transport key, and only the client decrypts it locally. No subnet node ever sees the raw key, and in this standard client-delivery pattern neither does the canister — it relays the still-encrypted key to the client. (Some flows deliberately have the canister obtain key material itself: threshold BLS signing and in-canister timelock decryption — see those sections.) Derivation is **deterministic**: the same `(canister, context, input)` always yields the same key. Build on the maintained libraries — do not hand-roll the cryptography or the Candid interface: | Layer | Rust | Motoko | Frontend | |-------|------|--------|----------| | Package | `ic-vetkeys` **0.9** ([crates.io](https://crates.io/crates/ic-vetkeys)) | `ic-vetkeys` **0.6** ([mops](https://mops.one/ic-vetkeys)) | `@icp-sdk/vetkeys` **0.5** ([npm](https://www.npmjs.com/package/@icp-sdk/vetkeys)) | | Management API | `ic-cdk-management-canister`, `ic_vetkeys::management_canister` | `mo:ic-vetkeys/ManagementCanister` | — | | Low-level primitives | crate root (`ic_vetkeys::…`) | — (**not available**, see below) | package root (`@icp-sdk/vetkeys`) | > **`@dfinity/vetkeys` is legacy** (frozen at 0.4.0). The package was renamed to `@icp-sdk/vetkeys` at 0.5.0. Frontend agent/identity types come from `@icp-sdk/core` (`@icp-sdk/core/agent`, `@icp-sdk/core/principal`), **not** `@dfinity/agent`/`@dfinity/principal`. Also required: Rust `ic-cdk = "0.20"` + `ic-cdk-management-canister = "0.1"` (and `ic-dummy-getrandom-for-wasm` for IBE); Motoko `ic-vetkeys` 0.6 needs `moc ≥ 1.13.0` / `core ≥ 2.6.1`; frontend also `@icp-sdk/core ^5.4`. ## Which skill / which feature | You want to… | Use | |--------------|-----| | Store & share encrypted key-value data (password manager, notes, vault) | **`encrypted-maps` skill** (higher-level, start there) | | Encrypt to a principal so only they can decrypt (messaging) | **IBE** → `references/ibe.md` | | Reveal data only after a deadline (sealed-bid auction, timelock) | **Timelock IBE** → `references/ibe.md` | | Have the canister produce a signature verifiable by anyone | **Threshold BLS** → `references/bls-signing.md` | | Derive a per-user/per-resource symmetric (AES) key | **Symmetric derivation** → this file | | Encrypt to a principal without any canister call | **Offline public-key derivation** → this file | | Produce on-chain verifiable randomness | **Verifiable randomness (VRF)** → this file | | Authenticate users / logins | not vetKeys — use the **`internet-identity` skill** | ## Core concepts - **context** — a domain-separator blob that namespaces derived keys within a canister (e.g. `"my_app"`, or a per-purpose value like `"symmetric_key"`). It **must be identical** between the public-key call, the derive call, and any client-side verify/decrypt, or the keys will not match — `decryptAndVerify` then throws (the Rust APIs return an error), so handle that failure rather than assuming success. - **input** — application data identifying *which* key to derive (e.g. a caller principal, a document ID). It is sent to the management canister **in plaintext** — use it as an identifier, never for secret data. - **transport key** — an ephemeral key pair the client generates per request. The public half is sent so the subnet can encrypt the derived key for delivery; only the holder of the secret half can decrypt. Generate a **fresh** one each request (`TransportSecretKey.random()`). - **encrypted vs unencrypted vetKeys** — IBE and symmetric derivation use the **encrypted** delivery flow (transport key → `decryptAndVerify` → `VetKey`). Threshold BLS uses the **unencrypted** vetKey directly; the library's `sign_with_bls` / `signWithBls` handles that — never feed an encrypted vetKey into BLS. - **Motoko asymmetry** — the Motoko `ic-vetkeys` library exposes only the management API + `KeyManager`/`EncryptedMaps`. It has **no** IBE, transport keys, `MasterPublicKey`/`DerivedPublicKey`, or vetKey decryption. In a Motoko app the canister returns the *encrypted* vetKey and the **frontend** (`@icp-sdk/vetkeys`) does transport-key generation, `decryptAndVerify`, IBE, and symmetric derivation. Those primitives exist in Rust and TypeScript only. ### Key names & cycles | Key name | Where | `vetkd_derive_key` cost | |----------|-------|-------------------------| | `test_key_1` | local + mainnet (testing) | 10_000_000_000 | | `key_1` | local + mainnet (production) | 26_153_846_153 | `vetkd_public_key` is **free**; `vetkd_derive_key` costs cycles. `test_key_1` and `key_1` behave the same locally and on mainnet. Let the helpers handle the amount: the Rust binding computes the exact cost, and the Motoko `ManagementCanister` attaches `26_153_846_153` with any excess refunded — you only need to keep the canister funded. The management canister is `aaaaa-aa`; calls are routed to the subnet holding the master key. - **Rust** reads the key name from an `#[init]` argument (passed via `init_args` in `icp.yaml`). - **Motoko** reads it from the `VETKD_KEY_NAME` canister environment variable, defaulting to `test_key_1`. The name is captured into stable state at first install and is **immutable** for the life of the canister's data — changing it later is silently ignored (only a `reinstall`, which drops state, switches keys). Because `test_key_1` is also a valid mainnet key, a production deploy that forgets to set `VETKD_KEY_NAME` silently runs on it — assert the expected key at deploy time if that matters. ## The vetKD management API (foundation + symmetric encryption) The management API has two endpoints: `vetkd_public_key` (verification / offline-encryption public key) and `vetkd_derive_key` (the caller's encrypted key). This is the foundation for symmetric encryption, IBE, and BLS. Call it through the library helpers so the Candid types and cycles are correct. ### Backend — Rust ```rust use ic_cdk::update; use ic_cdk_management_canister::{VetKDCurve, VetKDDeriveKeyArgs, VetKDKeyId, VetKDPublicKeyArgs}; const CONTEXT: &[u8] = b"symmetric_key"; // domain separator; must match on the client fn key_id() -> VetKDKeyId { // name comes from an #[init] arg in real code; "test_key_1" for local + mainnet testing VetKDKeyId { curve: VetKDCurve::Bls12_381_G2, name: "test_key_1".to_string() } } #[update] async fn symmetric_verification_key() -> Vec<u8> { let res = ic_cdk_management_canister::vetkd_public_key(&VetKDPublicKeyArgs { canister_id: None, // defaults to this canister context: CONTEXT.to_vec(), key_id: key_id(), }) .await .expect("vetkd_public_key failed"); res.public_key // no cycles required } #[update] async fn encrypted_symmetric_key_for_caller(transport_public_key: Vec<u8>) -> Vec<u8> { let caller = ic_cdk::api::msg_caller(); // capture BEFORE the await let res = ic_cdk_management_canister::vetkd_derive_key(&VetKDDeriveKeyArgs { input: caller.as_slice().to_vec(), // key identifier (plaintext) — never secret data context: CONTEXT.to_vec(), transport_public_key, key_id: key_id(), }) .await // the binding attaches the required cycles automatically .expect("vetkd_derive_key failed"); res.encrypted_key } ``` ### Backend — Motoko ```motoko import ManagementCanister "mo:ic-vetkeys/ManagementCanister"; import Principal "mo:core/Principal"; import Text "mo:core/Text"; import Runtime "mo:core/Runtime"; persistent actor { // Captured into keyId at first install and fixed for the life of the canister's derived keys; // changing VETKD_KEY_NAME on a later upgrade has no effect (see the key-name warning above). let keyName = Runtime.envVar<system>("VETKD_KEY_NAME") ?? "test_key_1"; let keyId : ManagementCanister.VetKdKeyid = { curve = #bls12_381_g2; name = keyName }; public shared func symmetricVerificationKey() : async Blob { // context / domain separator; no cycles required await ManagementCanister.vetKdPublicKey(null, Text.encodeUtf8("symmetric_key"), keyId); }; public shared ({ caller }) func encryptedSymmetricKeyForCaller(transportPublicKey : Blob) : async Blob { // signature is (input, context, keyId, transportPublicKey); helper attaches cycles automatically await ManagementCanister.vetKdDeriveKey( Principal.toBlob(caller), Text.encodeUtf8("symmetric_key"), keyId, transportPublicKey); }; }; ``` ### Frontend — derive an AES-GCM key (TypeScript) The canister returns the *encrypted* vetKey; the frontend generates the transport key, decrypts & verifies it into a `VetKey`, then derives AES-GCM key material. ```typescript import { TransportSecretKey, DerivedPublicKey, EncryptedVetKey } from "@icp-sdk/vetkeys"; // `backend` is your actor; `myPrincipal` is the authenticated caller's Principal (@icp-sdk/core/principal) // 1. Fresh transport key per request const tsk = TransportSecretKey.random(); // 2. Fetch the encrypted derived key + the public verification key const [encryptedKeyBytes, publicKeyBytes] = await Promise.all([ backend.encrypted_symmetric_key_for_caller(tsk.publicKeyBytes()), backend.symmetric_verification_key(), ]); // 3. Decrypt & verify -> VetKey. The identity bytes MUST equal the backend `input` // (here the caller principal), or verification throws. const vetKey = EncryptedVetKey.deserialize(new Uint8Array(encryptedKeyBytes)).decryptAndVerify( tsk, DerivedPublicKey.deserialize(new Uint8Array(publicKeyBytes)), myPrincipal.toUint8Array(), ); // 4. Derive AES-GCM key material and encrypt/decrypt. There is NO `toDerivedKeyMaterial()`. const keyMaterial = await vetKey.asDerivedKeyMaterial(); const domainSep = "my_app:notes"; // unique per app + usage const ciphertext = await keyMaterial.encryptMessage("secret message", domainSep, ""); // (msg, domainSep, associatedData) const plaintext = await keyMaterial.decryptMessage(ciphertext, domainSep, ""); // new TextDecoder().decode(plaintext) === "secret message" ``` ## Offline public-key derivation Derive a canister's public key for a context **without any canister call**, starting from the known mainnet master public key. Used to encrypt (IBE) to a principal when neither the recipient nor the canister is online. **TypeScript:** ```typescript import { MasterPublicKey } from "@icp-sdk/vetkeys"; import { Principal } from "@icp-sdk/core/principal"; const derivedPublicKey = MasterPublicKey.productionKey() // key_1 (default); MasterPublicKey.pocketicKey() for local .deriveCanisterKey(Principal.fromText(canisterId).toUint8Array()) .deriveSubKey(new TextEncoder().encode("my_app")); // the context / domain separator // derivedPublicKey (a DerivedPublicKey) can now be used for IBE encryption offline ``` **Rust:** ```rust use ic_vetkeys::{MasterPublicKey, DerivedPublicKey}; use ic_cdk_management_canister::{VetKDCurve, VetKDKeyId}; let master = MasterPublicKey::for_mainnet_key(&VetKDKeyId { curve: VetKDCurve::Bls12_381_G2, name: "key_1".to_string(), }).expect("unknown key name"); // for_pocketic_key(..) for local let derived: DerivedPublicKey = master .derive_canister_key(canister_id.as_slice()) .derive_sub_key(b"my_app"); ``` ## Verifiable randomness (VRF) A vetKey can be turned into **verifiable randomness**: a Rust canister calls `ic_vetkeys::management_canister::compute_vrf(input, context, key_id) -> VrfOutput` (scope `input`/`context` to the draw, e.g. a lottery round or leader election), and the frontend verifies the proof with `VrfOutput.deserialize(...)` from `@icp-sdk/vetkeys`. No canonical end-to-end example ships yet. (Not available in the Motoko library — derive on a Rust canister.) ## Pitfalls 1. **Wrong package / imports.** Use `@icp-sdk/vetkeys` (≥0.5), not `@dfinity/vetkeys` (frozen at 0.4). Import agent/identity from `@icp-sdk/core` (`@icp-sdk/core/agent`, `@icp-sdk/core/principal`), and build the agent with `await HttpAgent.create({ identity, host, rootKey })` — the client classes take a ready `HttpAgent`, not options. Get `rootKey` from `safeGetCanisterEnv()` (`@icp-sdk/core/agent/canister-env`); never call `fetchRootKey()` in shipped code (see the `icp-cli` skill). 2. **`toDerivedKeyMaterial()` does not exist.** For symmetric encryption: `const dkm = await vetKey.asDerivedKeyMaterial()`, then `await dkm.encryptMessage(msg, domainSep, associatedData)` / `await dkm.decryptMessage(ct, domainSep, associatedData)` (all async). Never use the raw decrypted vetKey bytes directly as an AES key. 3. **Don't hand-roll the management interface.** Rust: `ic-cdk-management-canister` (`vetkd_public_key`/`vetkd_derive_key`) or `ic_vetkeys::management_canister` (also `sign_with_bls`). Motoko: `mo:ic-vetkeys/ManagementCanister` (`vetKdPublicKey`, `vetKdDeriveKey`, `signWithBls`, `blsPublicKey`). These carry the correct Candid types and attach the right cycles automatically. Hand-declaring `actor "aaaaa-aa"` and the `vetkd_*` records is unnecessary and error-prone. 4. **Motoko has no low-level crypto.** No IBE, transport keys, `MasterPublicKey`/`DerivedPublicKey`, or vetKey decryption in the Motoko library. The Motoko canister returns the *encrypted* vetKey; the frontend `@icp-sdk/vetkeys` (or a Rust off-chain client) does the rest.
Ver no GitHub
Este SKILL.md e muito grande, entao o SkillsMP mostra aqui apenas a primeira secao. Ver no GitHub