vetkd

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

vetKeys (Verifiable Encrypted Threshold Keys)

vetKeys(可验证加密阈值密钥)

Note: vetKeys is a newer feature of the IC. The
ic-vetkeys
Rust crate and
@dfinity/vetkeys
npm package are published, but the APIs may still change over time. Pin your dependency versions and check the DFINITY forum for any migration guides after upgrades.
注意: vetKeys是IC的一项较新功能。
ic-vetkeys
Rust crate和
@dfinity/vetkeys
npm包已发布,但API可能仍会随版本更新而变化。 请固定依赖版本,并在升级后查看DFINITY论坛获取迁移指南。

What This Is

功能概述

vetKeys (verifiably encrypted threshold keys) bring on-chain privacy to the IC via the vetKD protocol: secure, on-demand key derivation so that a public blockchain can hold and work with secret data. Keys are verifiable (users can check correctness and lack of tampering), encrypted (derived keys are encrypted under a user-supplied transport key—no node or canister ever sees the raw key), and threshold (a quorum of subnet nodes cooperates to derive keys; no single party has the master key). A canister requests a derived key from the subnet’s threshold infrastructure, receives it encrypted under the client’s transport public key, and only the client decrypts it locally. This unlocks decentralized key management (DKMS), encrypted on-chain storage, private messaging, identity-based encryption (IBE), timelock encryption, threshold BLS, and verifiable randomness—use cases.
vetKeys(可验证加密阈值密钥)通过vetKD协议为IC带来链上隐私:安全的按需密钥派生,使公共区块链能够存储和处理机密数据。密钥具备可验证性(用户可检查正确性和是否被篡改)、加密性(派生密钥会用用户提供的传输密钥加密——没有节点或canister能看到原始密钥)和阈值特性(子网节点的法定人数协作派生密钥;没有单一主体持有主密钥)。canister向子网的阈值基础设施请求派生密钥,接收用客户端传输公钥加密后的密钥,只有客户端能在本地解密。这解锁了去中心化密钥管理(DKMS)、加密链上存储、私密消息、基于身份的加密(IBE)、时间锁加密、阈值BLS以及可验证随机性等用例。

Prerequisites

前置条件

  • Rust:
    ic-vetkeys = "0.6"
    (crates.io)
  • Motoko: Use the raw management canister approach shown below
  • Frontend:
    @dfinity/vetkeys
    v0.4.0
  • Rust:
    ic-vetkeys = "0.6"
    crates.io
  • Motoko:使用下文展示的原生管理canister方式
  • 前端:
    @dfinity/vetkeys
    v0.4.0

Canister IDs

Canister ID

CanisterIDPurpose
Management Canister
aaaaa-aa
Exposes
vetkd_public_key
and
vetkd_derive_key
system APIs
Chain-key testing canister
vrqyr-saaaa-aaaan-qzn4q-cai
Testing only: fake vetKD implementation to test key derivation without paying production API fees. Insecure, do not use in production.
The management canister is not a real canister, it is a system-level API endpoint. Calls to
aaaaa-aa
are routed by the system to the vetKD-enabled subnet that holds the master key specified in
key_id
; that subnet's nodes run the threshold key derivation. Your canister can call from any subnet.
Testing canister: The chain-key testing canister is deployed on mainnet and provides a fake vetKD implementation (hard-coded keys, no threshold) so you can exercise key derivation without production cycle costs. Use key name
insecure_test_key_1
. Insecure, for testing only: never use it in production or with sensitive data. You can also deploy your own instance from the repo.
CanisterID用途
管理Canister
aaaaa-aa
暴露
vetkd_public_key
vetkd_derive_key
系统API
链密钥测试Canister
vrqyr-saaaa-aaaan-qzn4q-cai
仅用于测试: 模拟vetKD实现,无需支付生产API费用即可测试密钥派生。不安全,请勿用于生产环境。
管理Canister并非真实的canister,它是系统级API端点。对
aaaaa-aa
的调用会由系统路由到支持vetKD且持有
key_id
指定主密钥的子网;该子网的节点会运行阈值密钥派生流程。你的canister可以从任何子网发起调用。
测试Canister: 链密钥测试Canister部署在主网上,提供模拟vetKD实现(硬编码密钥,无阈值特性),让你无需消耗生产周期即可测试密钥派生。请使用密钥名称
insecure_test_key_1
仅用于测试,不安全: 切勿在生产环境或处理敏感数据时使用。你也可以从仓库部署自己的实例。

Master Key Names and API Fees

主密钥名称与API费用

Any canister on the IC can use any available master key regardless of which subnet the canister or the key resides on; the management canister routes calls to the subnet that holds the master key.
Key nameEnvironmentPurposeCycles (approx.)Notes
test_key_1
Local + MainnetDevelopment & testing10_000_000_000 (mainnet)Works both locally and on mainnet. Use for development and testing.
key_1
MainnetProduction26_153_846_153Subnet pzp6e (backed up on uzr34)
Fees depend on the subnet where the master key resides (and its size), not on the calling canister's subnet. If the canister may be blackholed or used by other canisters, send more cycles than the current cost so that future subnet size increases do not cause calls to fail; unused cycles are refunded. See vetKD API — API fees for current USD estimates.
IC上的任何canister都可以使用任何可用的主密钥,无论canister或密钥所在的子网;管理canister会将调用路由到持有主密钥的子网。
密钥名称环境用途周期(约)说明
test_key_1
本地 + 主网开发与测试10_000_000_000(主网)支持本地和主网环境,用于开发和测试。
key_1
主网生产环境26_153_846_153子网pzp6e(备份在uzr34)
费用取决于主密钥所在的子网(及其规模),而非调用canister所在的子网。如果canister可能被黑洞化或被其他canister调用,请发送超过当前成本的周期,以便未来子网规模扩大时不会导致调用失败;未使用的周期会被退还。有关当前USD估算,请查看vetKD API — API费用

Key Concepts

核心概念

  • vetKey: Key material derived deterministically from
    (canister_id, context, input)
    . Same inputs always produce the same key. Neither the canister nor any subnet node ever sees the raw key, as it is encrypted under the client's transport key until decrypted locally.
  • Transport key: An ephemeral key pair generated by the client. The public key is sent to the canister so the IC can encrypt the derived key for delivery. Only the client holding the corresponding private key can decrypt the result.
  • Context: A domain separator blob. Isolates derived subkeys per use case (e.g. per feature or key purpose) and prevents key collisions within the same canister. Think of it as a namespace.
  • Input: Application-defined data that identifies which key to derive (e.g. user principal, file ID, chat room ID). It is sent in plaintext to the management canister. Use it only as an identifier, never for secret data.
  • IBE (Identity-Based Encryption): A scheme where you encrypt to an identity (e.g. a principal) using a derived public key. vetKeys enables IBE on the IC: anyone can encrypt to a principal using the canister's derived public key; only that principal can obtain the matching vetKey and decrypt.
  • vetKey:由
    (canister_id, context, input)
    确定性派生的密钥材料。相同的输入始终生成相同的密钥。由于密钥在本地解密前会用客户端的传输密钥加密,因此canister和任何子网节点都无法看到原始密钥。
  • 传输密钥(Transport key):由客户端生成的临时密钥对。公钥会发送给canister,以便IC可以加密派生的密钥进行传输。只有持有对应私钥的客户端才能解密结果。
  • 上下文(Context):一个域分隔符二进制数据(blob)。按用例(如按功能或密钥用途)隔离派生的子密钥,防止同一canister内的密钥冲突。可以将其视为命名空间。
  • 输入(Input):由应用定义的标识派生密钥的数据(如用户principal、文件ID、聊天室ID)。它会以明文形式发送给管理canister。仅将其用作标识符,切勿用于存储机密数据。
  • IBE(基于身份的加密,Identity-Based Encryption):一种使用派生公钥向某个身份(如principal)加密的方案。vetKeys在IC上实现了IBE:任何人都可以使用canister的派生公钥向某个principal加密;只有该principal才能获取匹配的vetKey并解密。

Mistakes That Break Your Build

导致构建失败的常见错误

  1. Not pinning dependency versions. The
    ic-vetkeys
    crate and
    @dfinity/vetkeys
    npm package are published, but the APIs may still change in new releases. Pin your versions and re-test after upgrades. If something stops working after an upgrade, consult the relevant change notes to understand what happened.
  2. Reusing transport keys across sessions. Each session must generate a fresh transport key pair. The Rust and TypeScript libraries include support for generating keys safely; use them if at all possible.
  3. Using raw
    vetkd_derive_key
    output as an encryption key.
    The output is an encrypted blob. You must decrypt it with the transport secret to get the vetKey (raw key material). What you do next depends on your use case: for example, you might derive a symmetric key (e.g. for AES) via
    toDerivedKeyMaterial()
    or the equivalent. Do not use the decrypted bytes directly as an AES key. Other uses (IBE decryption, signing, etc.) consume the vetKey in their own way; the libraries document the right pattern for each.
  4. Confusing vetKD with traditional public-key crypto. There are no static key pairs per user. Keys are derived on-demand from the subnet's threshold master key (via the vetKD protocol). The same (canister, context, input) always yields the same derived key.
  5. Putting secret data in the
    input
    field.
    The input is sent to the management canister in plaintext. It is a key identifier, not encrypted payload. Use it for IDs (principal, document ID), never for the actual secret data.
  6. Forgetting that
    vetkd_derive_key
    is an async inter-canister call.
    It costs cycles and requires
    await
    . Capture
    caller
    before the await as defensive practice.
  7. Using
    context
    inconsistently.
    If the backend uses
    b"my_app_v1"
    as context but the frontend verification uses
    b"my_app"
    , the derived keys will not match and decryption will silently fail.
  8. Not attaching enough cycles to
    vetkd_derive_key
    .
    vetkd_derive_key
    consumes cycles;
    vetkd_public_key
    does not. For derive_key,
    key_1
    costs ~26B cycles and
    test_key_1
    costs ~10B cycles.
  9. Rolling your own IBE without proper authorization checks. If you implement IBE manually (bypassing
    KeyManager
    /
    EncryptedMaps
    ), your canister must enforce that
    vetkd_derive_key
    only returns the derived key to the authorized caller — e.g. the principal whose identity was used as the
    input
    . Without this check, any caller can request any derived key and decrypt messages meant for someone else. The provided
    ic-vetkeys
    /
    @dfinity/vetkeys
    libraries handle this correctly; prefer them over a custom implementation.
  1. 未固定依赖版本
    ic-vetkeys
    crate和
    @dfinity/vetkeys
    npm包已发布,但新版本可能会更改API。请固定版本,并在升级后重新测试。如果升级后功能失效,请查看相关变更说明了解原因。
  2. 跨会话重用传输密钥。每个会话都必须生成新的传输密钥对。Rust和TypeScript库包含安全生成密钥的支持;请尽可能使用这些库。
  3. 将原始
    vetkd_derive_key
    输出用作加密密钥
    。输出是加密后的二进制数据。你必须使用传输密钥解密才能得到vetKey(原始密钥材料)。后续操作取决于你的用例:例如,你可以通过
    toDerivedKeyMaterial()
    或等效方法派生对称密钥(如AES密钥)。请勿直接将解密后的字节用作AES密钥。其他用途(如IBE解密、签名等)会以各自的方式使用vetKey;库文档中说明了每种用例的正确模式。
  4. 混淆vetKD与传统公钥加密。没有每个用户的静态密钥对。密钥是通过vetKD协议从子网的阈值主密钥按需派生的。相同的(canister, context, input)始终生成相同的派生密钥。
  5. input
    字段中存储机密数据
    。输入会以明文形式发送给管理canister。它是密钥标识符,不是加密负载。仅将其用于ID(principal、文档ID),切勿用于存储实际机密数据。
  6. 忘记
    vetkd_derive_key
    是异步跨canister调用
    。它会消耗周期,需要使用
    await
    。作为防御性实践,请在
    await
    前捕获
    caller
  7. 不一致地使用
    context
    。如果后端使用
    b"my_app_v1"
    作为上下文,但前端验证使用
    b"my_app"
    ,则派生的密钥将不匹配,解密会静默失败。
  8. 未为
    vetkd_derive_key
    附加足够的周期
    vetkd_derive_key
    会消耗周期;
    vetkd_public_key
    不会。对于derive_key,
    key_1
    约消耗26B周期,
    test_key_1
    约消耗10B周期。
  9. 未正确授权就自行实现IBE。如果你手动实现IBE(绕过
    KeyManager
    /
    EncryptedMaps
    ),你的canister必须强制
    vetkd_derive_key
    仅将派生密钥返回给授权调用者——例如,将其身份用作
    input
    的principal。没有此检查,任何调用者都可以请求任何派生密钥并解密为他人加密的消息。提供的
    ic-vetkeys
    /
    @dfinity/vetkeys
    库已正确处理此问题;优先使用库而非自定义实现。

System API (Candid)

系统API(Candid)

The vetKD API lets canisters request vetKeys derived by the threshold protocol. Derivation is deterministic: the same inputs always produce the same key, so keys can be retrieved reliably. Different inputs yield different keys—canisters can derive an unlimited number of unique keys. Summary below; full spec: vetKD API and the IC interface specification.
vetKD API允许canister请求通过阈值协议派生的vetKeys。派生是确定性的:相同的输入始终生成相同的密钥,因此可以可靠地检索密钥。不同的输入会生成不同的密钥——canister可以派生无限数量的唯一密钥。以下是摘要;完整规范请查看vetKD APIIC接口规范

vetkd_public_key

vetkd_public_key

Returns a public key used to verify keys derived with
vetkd_derive_key
. With an empty context you get the canister-level master public key; with a non-empty context you get the derived subkey for that context. In IBE, this public key lets anyone encrypt to an identity (e.g. a principal); only the holder of that identity can later obtain the matching vetKey and decrypt—no prior key exchange or recipient presence required.
candid
vetkd_public_key : (record {
  canister_id : opt canister_id;
  context : blob;
  key_id : record { curve : vetkd_curve; name : text };
}) -> (record { public_key : blob })
  • canister_id
    : Optional. If omitted (
    null
    ), the public key for the calling canister is returned; if provided, the key for that canister is returned.
  • context
    : Domain separator which has the same meaning as in
    vetkd_derive_key
    . Ensures keys are derived in a specific context and avoids collisions across apps or use cases.
  • key_id.curve
    :
    bls12_381_g2
    (only supported curve).
  • key_id.name
    : Master key name:
    test_key_1
    (local + mainnet testing) or
    key_1
    (production).
You can also derive this public key offline from the known mainnet master public key; see "Offline Public Key Derivation" below.
返回用于验证通过
vetkd_derive_key
派生的密钥的公钥。如果上下文为空,你将获得canister级别的主公钥;如果上下文非空,你将获得该上下文的派生子密钥。在IBE中,此公钥允许任何人向某个身份(如principal)加密;只有该身份的持有者才能获取匹配的vetKey并解密——无需预先交换密钥或收件人在线。
candid
vetkd_public_key : (record {
  canister_id : opt canister_id;
  context : blob;
  key_id : record { curve : vetkd_curve; name : text };
}) -> (record { public_key : blob })
  • canister_id
    :可选。如果省略(
    null
    ),则返回调用canister的公钥;如果提供,则返回该canister的公钥。
  • context
    :域分隔符,与
    vetkd_derive_key
    中的context含义相同。确保密钥在特定上下文中派生,避免跨应用或用例的冲突。
  • key_id.curve
    bls12_381_g2
    (唯一支持的曲线)。
  • key_id.name
    :主密钥名称:
    test_key_1
    (本地+主网测试)或
    key_1
    (生产环境)。
你也可以从已知的主网主公钥离线派生此公钥;请查看下文的“离线公钥派生”。

vetkd_derive_key

vetkd_derive_key

Derives key material for the given (context, input) and returns it encrypted under the recipient's transport public key. Only the holder of the transport secret can decrypt. The decrypted material is then used according to your use case (e.g. via
toDerivedKeyMaterial()
for symmetric keys, or for IBE decryption).
candid
vetkd_derive_key : (record {
  input : blob;
  context : blob;
  transport_public_key : blob;
  key_id : record { curve : vetkd_curve; name : text };
}) -> (record { encrypted_key : blob })
  • input
    : Arbitrary data used as the key identifier—different inputs yield different derived keys. Does not need to be random; sent in plaintext to the management canister.
  • context
    : Domain separator; must match the context used when obtaining the public key (e.g. for verification or IBE).
  • transport_public_key
    : The recipient's public key; the derived key is encrypted under this for secure delivery.
  • Returns:
    encrypted_key
    . Decrypt with the transport secret to get the raw vetKey, then use it as required (e.g. derive a symmetric key; do not use raw bytes directly as an AES key).
Master key names and cycle costs are in Master Key Names and API Fees under Canister IDs.
为给定的(context, input)派生密钥材料,并返回用收件人传输公钥加密后的结果。只有持有传输私钥的主体才能解密。解密后的材料将根据你的用例使用(例如,通过
toDerivedKeyMaterial()
获取对称密钥,或用于IBE解密)。
candid
vetkd_derive_key : (record {
  input : blob;
  context : blob;
  transport_public_key : blob;
  key_id : record { curve : vetkd_curve; name : text };
}) -> (record { encrypted_key : blob })
  • input
    :用作密钥标识符的任意数据——不同的输入会生成不同的派生密钥。不需要是随机值;会以明文形式发送给管理canister。
  • context
    :域分隔符;必须与获取公钥时使用的上下文匹配(如用于验证或IBE)。
  • transport_public_key
    :收件人的公钥;派生的密钥会用此密钥加密以确保传输安全。
  • 返回:
    encrypted_key
    。使用传输私钥解密以获取原始vetKey,然后根据需要使用(例如,派生对称密钥;请勿直接将原始字节用作AES密钥)。
主密钥名称和周期成本请查看Canister ID下的“主密钥名称与API费用”部分。

Implementation

实现示例

Rust

Rust

Cargo.toml:
toml
[dependencies]
candid = "0.10"
ic-cdk = "0.19"
serde = { version = "1", features = ["derive"] }
serde_bytes = "0.11"
Cargo.toml:
toml
[dependencies]
candid = "0.10"
ic-cdk = "0.19"
serde = { version = "1", features = ["derive"] }
serde_bytes = "0.11"

High-level library (recommended) — source: https://github.com/dfinity/vetkeys

高层级库(推荐)—— 源码:https://github.com/dfinity/vetkeys

ic-vetkeys = "0.6" ic-stable-structures = "0.7"

**Using ic-vetkeys library (recommended):**

```rust
use candid::Principal;
use ic_cdk::update;
use ic_stable_structures::memory_manager::{MemoryId, MemoryManager, VirtualMemory};
use ic_stable_structures::DefaultMemoryImpl;
use ic_vetkeys::key_manager::KeyManager;
use ic_vetkeys::types::{AccessRights, VetKDCurve, VetKDKeyId};

// KeyManager is generic over an AccessControl type — AccessRights is the default.
// It uses stable memory for persistent storage of access control state.
thread_local! {
    static MEMORY_MANAGER: std::cell::RefCell<MemoryManager<DefaultMemoryImpl>> =
        std::cell::RefCell::new(MemoryManager::init(DefaultMemoryImpl::default()));

    static KEY_MANAGER: std::cell::RefCell<Option<KeyManager<AccessRights>>> =
        std::cell::RefCell::new(None);
}

#[ic_cdk::init]
fn init() {
    let key_id = VetKDKeyId {
        curve: VetKDCurve::Bls12381G2,
        name: "key_1".to_string(), // "test_key_1" for local + mainnet testing
    };
    MEMORY_MANAGER.with(|mm| {
        let mm = mm.borrow();
        KEY_MANAGER.with(|km| {
            *km.borrow_mut() = Some(KeyManager::init(
                "my_app_v1",              // domain separator
                key_id,
                mm.get(MemoryId::new(0)), // config memory
                mm.get(MemoryId::new(1)), // access control memory
                mm.get(MemoryId::new(2)), // shared keys memory
            ));
        });
    });
}

#[update]
async fn get_encrypted_vetkey(subkey_id: Vec<u8>, transport_public_key: Vec<u8>) -> Vec<u8> {
    let caller = ic_cdk::caller(); // Capture BEFORE await
    let future = KEY_MANAGER.with(|km| {
        let km = km.borrow();
        let km = km.as_ref().expect("not initialized");
        km.get_encrypted_vetkey(caller, subkey_id, transport_public_key)
            .expect("access denied")
    });
    future.await
}

#[update]
async fn get_vetkey_verification_key() -> Vec<u8> {
    let future = KEY_MANAGER.with(|km| {
        let km = km.borrow();
        let km = km.as_ref().expect("not initialized");
        km.get_vetkey_verification_key()
    });
    future.await
}
Calling management canister directly (lower level):
rust
use candid::{CandidType, Deserialize, Principal};
use ic_cdk::update;

#[derive(CandidType, Deserialize)]
struct VetKdKeyId {
    curve: VetKdCurve,
    name: String,
}

#[derive(CandidType, Deserialize)]
enum VetKdCurve {
    #[serde(rename = "bls12_381_g2")]
    Bls12381G2,
}

#[derive(CandidType)]
struct VetKdPublicKeyRequest {
    canister_id: Option<Principal>,
    context: Vec<u8>,
    key_id: VetKdKeyId,
}

#[derive(CandidType, Deserialize)]
struct VetKdPublicKeyResponse {
    public_key: Vec<u8>,
}

#[derive(CandidType)]
struct VetKdDeriveKeyRequest {
    input: Vec<u8>,
    context: Vec<u8>,
    transport_public_key: Vec<u8>,
    key_id: VetKdKeyId,
}

#[derive(CandidType, Deserialize)]
struct VetKdDeriveKeyResponse {
    encrypted_key: Vec<u8>,
}

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

fn key_id() -> VetKdKeyId {
    VetKdKeyId {
        curve: VetKdCurve::Bls12381G2,
        // Key names: "test_key_1" for local + mainnet testing, "key_1" for production
        name: "key_1".to_string(),
    }
}

#[update]
async fn vetkd_public_key() -> Vec<u8> {
    let request = VetKdPublicKeyRequest {
        canister_id: None, // defaults to this canister
        context: CONTEXT.to_vec(),
        key_id: key_id(),
    };

    // vetkd_public_key does not require cycles (unlike vetkd_derive_key).
    let (response,): (VetKdPublicKeyResponse,) = ic_cdk::api::call::call(
        Principal::management_canister(), // aaaaa-aa
        "vetkd_public_key",
        (request,),
    )
    .await
    .expect("vetkd_public_key call failed");

    response.public_key
}

#[update]
async fn vetkd_derive_key(transport_public_key: Vec<u8>) -> Vec<u8> {
    let caller = ic_cdk::caller(); // MUST capture before await

    let request = VetKdDeriveKeyRequest {
        input: caller.as_slice().to_vec(), // derive key specific to this caller
        context: CONTEXT.to_vec(),
        transport_public_key,
        key_id: key_id(),
    };

    // key_1 costs ~26B cycles, test_key_1 costs ~10B cycles.
    let (response,): (VetKdDeriveKeyResponse,) = ic_cdk::api::call::call_with_payment128(
        Principal::management_canister(),
        "vetkd_derive_key",
        (request,),
        26_000_000_000, // cycles for key_1 (use 10_000_000_000 for test_key_1)
    )
    .await
    .expect("vetkd_derive_key call failed");

    response.encrypted_key
}
ic-vetkeys = "0.6" ic-stable-structures = "0.7"

**使用ic-vetkeys库(推荐):**

```rust
use candid::Principal;
use ic_cdk::update;
use ic_stable_structures::memory_manager::{MemoryId, MemoryManager, VirtualMemory};
use ic_stable_structures::DefaultMemoryImpl;
use ic_vetkeys::key_manager::KeyManager;
use ic_vetkeys::types::{AccessRights, VetKDCurve, VetKDKeyId};

// KeyManager是泛型类型,依赖AccessControl类型 —— AccessRights是默认实现。
// 它使用稳定内存持久化存储访问控制状态。
thread_local! {
    static MEMORY_MANAGER: std::cell::RefCell<MemoryManager<DefaultMemoryImpl>> =
        std::cell::RefCell::new(MemoryManager::init(DefaultMemoryImpl::default()));

    static KEY_MANAGER: std::cell::RefCell<Option<KeyManager<AccessRights>>> =
        std::cell::RefCell::new(None);
}

#[ic_cdk::init]
fn init() {
    let key_id = VetKDKeyId {
        curve: VetKDCurve::Bls12381G2,
        name: "key_1".to_string(), // 本地+主网测试请使用"test_key_1"
    };
    MEMORY_MANAGER.with(|mm| {
        let mm = mm.borrow();
        KEY_MANAGER.with(|km| {
            *km.borrow_mut() = Some(KeyManager::init(
                "my_app_v1",              // 域分隔符
                key_id,
                mm.get(MemoryId::new(0)), // 配置内存
                mm.get(MemoryId::new(1)), // 访问控制内存
                mm.get(MemoryId::new(2)), // 共享密钥内存
            ));
        });
    });
}

#[update]
async fn get_encrypted_vetkey(subkey_id: Vec<u8>, transport_public_key: Vec<u8>) -> Vec<u8> {
    let caller = ic_cdk::caller(); // 必须在await前捕获
    let future = KEY_MANAGER.with(|km| {
        let km = km.borrow();
        let km = km.as_ref().expect("未初始化");
        km.get_encrypted_vetkey(caller, subkey_id, transport_public_key)
            .expect("访问被拒绝")
    });
    future.await
}

#[update]
async fn get_vetkey_verification_key() -> Vec<u8> {
    let future = KEY_MANAGER.with(|km| {
        let km = km.borrow();
        let km = km.as_ref().expect("未初始化");
        km.get_vetkey_verification_key()
    });
    future.await
}
直接调用管理canister(底层实现):
rust
use candid::{CandidType, Deserialize, Principal};
use ic_cdk::update;

#[derive(CandidType, Deserialize)]
struct VetKdKeyId {
    curve: VetKdCurve,
    name: String,
}

#[derive(CandidType, Deserialize)]
enum VetKdCurve {
    #[serde(rename = "bls12_381_g2")]
    Bls12381G2,
}

#[derive(CandidType)]
struct VetKdPublicKeyRequest {
    canister_id: Option<Principal>,
    context: Vec<u8>,
    key_id: VetKdKeyId,
}

#[derive(CandidType, Deserialize)]
struct VetKdPublicKeyResponse {
    public_key: Vec<u8>,
}

#[derive(CandidType)]
struct VetKdDeriveKeyRequest {
    input: Vec<u8>,
    context: Vec<u8>,
    transport_public_key: Vec<u8>,
    key_id: VetKdKeyId,
}

#[derive(CandidType, Deserialize)]
struct VetKdDeriveKeyResponse {
    encrypted_key: Vec<u8>,
}

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

fn key_id() -> VetKdKeyId {
    VetKdKeyId {
        curve: VetKdCurve::Bls12381G2,
        // 密钥名称:本地+主网测试用"test_key_1",生产环境用"key_1"
        name: "key_1".to_string(),
    }
}

#[update]
async fn vetkd_public_key() -> Vec<u8> {
    let request = VetKdPublicKeyRequest {
        canister_id: None, // 默认当前canister
        context: CONTEXT.to_vec(),
        key_id: key_id(),
    };

    // vetkd_public_key不需要消耗周期(与vetkd_derive_key不同)。
    let (response,): (VetKdPublicKeyResponse,) = ic_cdk::api::call::call(
        Principal::management_canister(), // aaaaa-aa
        "vetkd_public_key",
        (request,),
    )
    .await
    .expect("vetkd_public_key调用失败");

    response.public_key
}

#[update]
async fn vetkd_derive_key(transport_public_key: Vec<u8>) -> Vec<u8> {
    let caller = ic_cdk::caller(); // 必须在await前捕获

    let request = VetKdDeriveKeyRequest {
        input: caller.as_slice().to_vec(), // 派生当前调用者的专属密钥
        context: CONTEXT.to_vec(),
        transport_public_key,
        key_id: key_id(),
    };

    // key_1约消耗26B周期,test_key_1约消耗10B周期。
    let (response,): (VetKdDeriveKeyResponse,) = ic_cdk::api::call::call_with_payment128(
        Principal::management_canister(),
        "vetkd_derive_key",
        (request,),
        26_000_000_000, // key_1的周期数(test_key_1请使用10_000_000_000)
    )
    .await
    .expect("vetkd_derive_key调用失败");

    response.encrypted_key
}

Motoko

Motoko

mops.toml:
toml
[package]
name = "my-vetkd-app"
version = "0.1.0"

[dependencies]
core = "2.0.0"
Using the management canister directly:
motoko
import Blob "mo:core/Blob";
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 context : Blob = Text.encodeUtf8("my_app_v1");

  // Key names: "test_key_1" for local + mainnet testing, "key_1" for production
  func keyId() : VetKdKeyId {
    { curve = #bls12_381_g2; name = "key_1" }
  };

  public shared func getPublicKey() : async Blob {
    // vetkd_public_key does not require cycles (unlike vetkd_derive_key).
    let response = await managementCanister.vetkd_public_key({
      canister_id = null;
      context;
      key_id = keyId();
    });
    response.public_key
  };

  public shared ({ caller }) func deriveKey(transportPublicKey : Blob) : async Blob {
    // caller is captured here, before the await. vetkd_derive_key requires cycles.
    let response = await (with cycles = 26_000_000_000) managementCanister.vetkd_derive_key({
      input = Principal.toBlob(caller);
      context;
      transport_public_key = transportPublicKey;
      key_id = keyId();
    });
    response.encrypted_key
  };
};
mops.toml:
toml
[package]
name = "my-vetkd-app"
version = "0.1.0"

[dependencies]
core = "2.0.0"
直接调用管理canister:
motoko
import Blob "mo:core/Blob";
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 context : Blob = Text.encodeUtf8("my_app_v1");

  // 密钥名称:本地+主网测试用"test_key_1",生产环境用"key_1"
  func keyId() : VetKdKeyId {
    { curve = #bls12_381_g2; name = "key_1" }
  };

  public shared func getPublicKey() : async Blob {
    // vetkd_public_key不需要消耗周期(与vetkd_derive_key不同)。
    let response = await managementCanister.vetkd_public_key({
      canister_id = null;
      context;
      key_id = keyId();
    });
    response.public_key
  };

  public shared ({ caller }) func deriveKey(transportPublicKey : Blob) : async Blob {
    // caller在此处捕获,在await之前。vetkd_derive_key需要消耗周期。
    let response = await (with cycles = 26_000_000_000) managementCanister.vetkd_derive_key({
      input = Principal.toBlob(caller);
      context;
      transport_public_key = transportPublicKey;
      key_id = keyId();
    });
    response.encrypted_key
  };
};

Frontend (TypeScript)

前端(TypeScript)

The frontend generates a transport key pair, sends the public half to the canister, receives the encrypted derived key, decrypts it with the transport secret to get the vetKey (raw key material), then derives a symmetric key from that material (e.g. via
toDerivedKeyMaterial()
) for AES or other use.
typescript
import { TransportSecretKey, DerivedPublicKey, EncryptedVetKey } from "@dfinity/vetkeys";

// 1. Generate a transport secret key (BLS12-381)
const seed = crypto.getRandomValues(new Uint8Array(32));
const transportSecretKey = TransportSecretKey.fromSeed(seed);
const transportPublicKey = transportSecretKey.publicKey();

// 2. Request encrypted vetkey and verification key from your canister
const [encryptedKeyBytes, verificationKeyBytes] = await Promise.all([
  backendActor.get_encrypted_vetkey(subkeyId, transportPublicKey),
  backendActor.get_vetkey_verification_key(),
]);

// 3. Deserialize and decrypt
const verificationKey = DerivedPublicKey.deserialize(new Uint8Array(verificationKeyBytes));
const encryptedVetKey = EncryptedVetKey.deserialize(new Uint8Array(encryptedKeyBytes));
const vetKey = encryptedVetKey.decryptAndVerify(
  transportSecretKey,
  verificationKey,
  new Uint8Array(subkeyId),
);

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

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

// 6. Decrypt
const plaintext = await crypto.subtle.decrypt(
  { name: "AES-GCM", iv },
  aesKey,
  ciphertext,
);
The
@dfinity/vetkeys
package also provides higher-level abstractions via sub-paths:
  • @dfinity/vetkeys/key_manager
    --
    KeyManager
    and
    DefaultKeyManagerClient
    for managing access-controlled keys
  • @dfinity/vetkeys/encrypted_maps
    --
    EncryptedMaps
    and
    DefaultEncryptedMapsClient
    for encrypted key-value storage
These mirror the Rust
KeyManager
and
EncryptedMaps
types and handle the transport key flow automatically.
前端生成传输密钥对,将公钥发送给canister,接收加密后的派生密钥,使用传输私钥解密得到vetKey(原始密钥材料),然后从该材料派生对称密钥(如通过
toDerivedKeyMaterial()
)用于AES或其他用途。
typescript
import { TransportSecretKey, DerivedPublicKey, EncryptedVetKey } from "@dfinity/vetkeys";

// 1. 生成传输私钥(BLS12-381)
const seed = crypto.getRandomValues(new Uint8Array(32));
const transportSecretKey = TransportSecretKey.fromSeed(seed);
const transportPublicKey = transportSecretKey.publicKey();

// 2. 从canister请求加密后的vetkey和验证密钥
const [encryptedKeyBytes, verificationKeyBytes] = await Promise.all([
  backendActor.get_encrypted_vetkey(subkeyId, transportPublicKey),
  backendActor.get_vetkey_verification_key(),
]);

// 3. 反序列化并解密
const verificationKey = DerivedPublicKey.deserialize(new Uint8Array(verificationKeyBytes));
const encryptedVetKey = EncryptedVetKey.deserialize(new Uint8Array(encryptedKeyBytes));
const vetKey = encryptedVetKey.decryptAndVerify(
  transportSecretKey,
  verificationKey,
  new Uint8Array(subkeyId),
);

// 4. 派生AES-GCM对称密钥
const aesKeyMaterial = vetKey.toDerivedKeyMaterial();
const aesKey = await crypto.subtle.importKey(
  "raw",
  aesKeyMaterial.data.slice(0, 32), // 256位AES密钥
  { name: "AES-GCM" },
  false,
  ["encrypt", "decrypt"],
);

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

// 6. 解密
const plaintext = await crypto.subtle.decrypt(
  { name: "AES-GCM", iv },
  aesKey,
  ciphertext,
);
@dfinity/vetkeys
包还通过子路径提供了更高层级的抽象:
  • @dfinity/vetkeys/key_manager
    --
    KeyManager
    DefaultKeyManagerClient
    ,用于管理受访问控制的密钥
  • @dfinity/vetkeys/encrypted_maps
    --
    EncryptedMaps
    DefaultEncryptedMapsClient
    ,用于加密键值存储
这些抽象与Rust的
KeyManager
EncryptedMaps
类型对应,并自动处理传输密钥流程。

Offline Public Key Derivation

离线公钥派生

You can derive public keys offline (without any canister calls) from the known mainnet master public key for a given key name (e.g.
key_1
). This is useful for IBE: you derive the canister's public key for your context, then encrypt to an identity (e.g. a principal) without the recipient or the canister being online.
Rust:
rust
use ic_vetkeys::{MasterPublicKey, DerivedPublicKey};

// Start from the known mainnet master public key for key_1
let master_key = MasterPublicKey::for_mainnet_key("key_1")
    .expect("unknown key name");

// Derive the canister-level key
let canister_key = master_key.derive_canister_key(canister_id.as_slice());

// Derive a sub-key for a specific context/identity
let derived_key: DerivedPublicKey = canister_key.derive_sub_key(b"my_app_v1");

// Use derived_key for IBE encryption — no canister call needed
TypeScript:
typescript
import { MasterPublicKey, DerivedPublicKey } from "@dfinity/vetkeys";

// Start from the known mainnet master public key
const masterKey = MasterPublicKey.productionKey();

// Derive the canister-level key
const canisterKey = masterKey.deriveCanisterKey(canisterId);

// Derive a sub-key for a specific context/identity
const derivedKey: DerivedPublicKey = canisterKey.deriveSubKey(
  new TextEncoder().encode("my_app_v1"),
);

// Use derivedKey for IBE encryption — no canister call needed
你可以从已知的主网主公钥为给定的密钥名称(如
key_1
离线派生公钥(无需任何canister调用)。这对IBE很有用:你可以为你的上下文派生canister的公钥,然后向某个身份(如principal)加密,而无需收件人或canister在线。
Rust:
rust
use ic_vetkeys::{MasterPublicKey, DerivedPublicKey};

// 从已知的主网key_1主公钥开始
let master_key = MasterPublicKey::for_mainnet_key("key_1")
    .expect("未知密钥名称");

// 派生canister级密钥
let canister_key = master_key.derive_canister_key(canister_id.as_slice());

// 为特定上下文/身份派生子密钥
let derived_key: DerivedPublicKey = canister_key.derive_sub_key(b"my_app_v1");

// 使用derived_key进行IBE加密 —— 无需canister调用
TypeScript:
typescript
import { MasterPublicKey, DerivedPublicKey } from "@dfinity/vetkeys";

// 从已知的主网主公钥开始
const masterKey = MasterPublicKey.productionKey();

// 派生canister级密钥
const canisterKey = masterKey.deriveCanisterKey(canisterId);

// 为特定上下文/身份派生子密钥
const derivedKey: DerivedPublicKey = canisterKey.deriveSubKey(
  new TextEncoder().encode("my_app_v1"),
);

// 使用derived_key进行IBE加密 —— 无需canister调用

Identity-Based Encryption (IBE)

基于身份的加密(IBE)

IBE lets you encrypt to an identity (e.g. a principal) using only the canister's derived public key—the recipient does not need to be online or have registered a key beforehand. The recipient later authenticates to the canister, obtains their vetKey (derived for that identity) via
vetkd_derive_key
, and decrypts locally.
TypeScript:
typescript
import {
  TransportSecretKey, DerivedPublicKey, EncryptedVetKey,
  IbeCiphertext, IbeIdentity, IbeSeed,
} from "@dfinity/vetkeys";

// --- Encrypt (sender side, no canister call needed) ---

// Derive the recipient's public key offline (see "Offline Public Key Derivation" above)
const recipientIdentity = IbeIdentity.fromBytes(recipientPrincipalBytes);
const seed = IbeSeed.random();
const plaintext = new TextEncoder().encode("secret message");

const ciphertext = IbeCiphertext.encrypt(derivedPublicKey, recipientIdentity, plaintext, seed);
const serialized = ciphertext.serialize(); // store or transmit this

// --- Decrypt (recipient side, requires canister call to get vetKey) ---

// 1. Get the vetKey (same flow as the Frontend section above)
const transportSecretKey = TransportSecretKey.fromSeed(crypto.getRandomValues(new Uint8Array(32)));
const [encryptedKeyBytes, verificationKeyBytes] = await Promise.all([
  backendActor.get_encrypted_vetkey(subkeyId, transportSecretKey.publicKey()),
  backendActor.get_vetkey_verification_key(),
]);
const verificationKey = DerivedPublicKey.deserialize(new Uint8Array(verificationKeyBytes));
const encryptedVetKey = EncryptedVetKey.deserialize(new Uint8Array(encryptedKeyBytes));
const vetKey = encryptedVetKey.decryptAndVerify(
  transportSecretKey, verificationKey, new Uint8Array(subkeyId),
);

// 2. Decrypt the IBE ciphertext
const deserialized = IbeCiphertext.deserialize(serialized);
const decrypted = deserialized.decrypt(vetKey);
// decrypted is Uint8Array containing "secret message"
Rust (off-chain client or test):
rust
use ic_vetkeys::{
    DerivedPublicKey, IbeCiphertext, IbeIdentity, IbeSeed, VetKey,
};

// --- Encrypt ---
let identity = IbeIdentity::from_bytes(recipient_principal.as_slice());
let seed = IbeSeed::new(&mut rand::rng());
let plaintext = b"secret message";

let ciphertext = IbeCiphertext::encrypt(
    &derived_public_key,
    &identity,
    plaintext,
    &seed,
);
let serialized = ciphertext.serialize();

// --- Decrypt (after obtaining the VetKey) ---
let deserialized = IbeCiphertext::deserialize(&serialized)
    .expect("invalid ciphertext");
let decrypted = deserialized.decrypt(&vet_key)
    .expect("decryption failed");
// decrypted == b"secret message"
IBE允许你仅使用canister的派生公钥向某个身份(如principal)加密——收件人无需在线或预先注册密钥。收件人稍后向canister认证,通过
vetkd_derive_key
获取其vetKey(为该身份派生),并在本地解密。
TypeScript:
typescript
import {
  TransportSecretKey, DerivedPublicKey, EncryptedVetKey,
  IbeCiphertext, IbeIdentity, IbeSeed,
} from "@dfinity/vetkeys";

// --- 加密(发送方,无需canister调用) ---

// 离线派生收件人的公钥(请查看上文的“离线公钥派生”)
const recipientIdentity = IbeIdentity.fromBytes(recipientPrincipalBytes);
const seed = IbeSeed.random();
const plaintext = new TextEncoder().encode("secret message");

const ciphertext = IbeCiphertext.encrypt(derivedPublicKey, recipientIdentity, plaintext, seed);
const serialized = ciphertext.serialize(); // 存储或传输此数据

// --- 解密(接收方,需要调用canister获取vetKey) ---

// 1. 获取vetKey(流程与上文的前端部分相同)
const transportSecretKey = TransportSecretKey.fromSeed(crypto.getRandomValues(new Uint8Array(32)));
const [encryptedKeyBytes, verificationKeyBytes] = await Promise.all([
  backendActor.get_encrypted_vetkey(subkeyId, transportSecretKey.publicKey()),
  backendActor.get_vetkey_verification_key(),
]);
const verificationKey = DerivedPublicKey.deserialize(new Uint8Array(verificationKeyBytes));
const encryptedVetKey = EncryptedVetKey.deserialize(new Uint8Array(encryptedKeyBytes));
const vetKey = encryptedVetKey.decryptAndVerify(
  transportSecretKey, verificationKey, new Uint8Array(subkeyId),
);

// 2. 解密IBE密文
const deserialized = IbeCiphertext.deserialize(serialized);
const decrypted = deserialized.decrypt(vetKey);
// decrypted是包含"secret message"的Uint8Array
Rust(链下客户端或测试):
rust
use ic_vetkeys::{
    DerivedPublicKey, IbeCiphertext, IbeIdentity, IbeSeed, VetKey,
};

// --- 加密 ---
let identity = IbeIdentity::from_bytes(recipient_principal.as_slice());
let seed = IbeSeed::new(&mut rand::rng());
let plaintext = b"secret message";

let ciphertext = IbeCiphertext::encrypt(
    &derived_public_key,
    &identity,
    plaintext,
    &seed,
);
let serialized = ciphertext.serialize();

// --- 解密(获取VetKey后) ---
let deserialized = IbeCiphertext::deserialize(&serialized)
    .expect("无效密文");
let decrypted = deserialized.decrypt(&vet_key)
    .expect("解密失败");
// decrypted == b"secret message"

Higher-Level Abstractions: KeyManager & EncryptedMaps

高层级抽象:KeyManager & EncryptedMaps

Both the Rust crate and TypeScript package provide two higher-level modules that handle the transport key flow, access control, and encrypted storage for you:
  • KeyManager<T: AccessControl>
    (Rust) /
    KeyManager
    (TS) — Manages access-controlled vetKeys with stable storage. The canister enforces who may request which keys; the library handles derivation requests, user rights (
    Read
    ,
    ReadWrite
    ,
    ReadWriteManage
    ), and key sharing between principals.
  • EncryptedMaps<T: AccessControl>
    (Rust) /
    EncryptedMaps
    (TS) — Builds on KeyManager to provide an encrypted key-value store. Each map is access-controlled and encrypted under a derived vetKey. Encryption and decryption of values are handled on the client (frontend) using vetKeys; the canister only stores ciphertext.
In Rust, these live in
ic_vetkeys::key_manager
and
ic_vetkeys::encrypted_maps
. In TypeScript, import from
@dfinity/vetkeys/key_manager
and
@dfinity/vetkeys/encrypted_maps
. See the vetkeys repository for full examples.
Rust crate和TypeScript包都提供了两个高层级模块,自动处理传输密钥流程、访问控制和加密存储:
  • KeyManager<T: AccessControl>
    (Rust)/
    KeyManager
    (TS)—— 管理受访问控制的vetKeys,使用稳定内存存储。Canister强制谁可以请求哪些密钥;库处理派生请求、用户权限(
    Read
    ReadWrite
    ReadWriteManage
    )以及principal之间的密钥共享。
  • EncryptedMaps<T: AccessControl>
    (Rust)/
    EncryptedMaps
    (TS)—— 基于KeyManager构建,提供加密键值存储。每个映射都受访问控制,并使用派生的vetKey加密。值的加密和解密在客户端(前端)使用vetKeys处理;canister仅存储密文。
在Rust中,这些模块位于
ic_vetkeys::key_manager
ic_vetkeys::encrypted_maps
。在TypeScript中,从
@dfinity/vetkeys/key_manager
@dfinity/vetkeys/encrypted_maps
导入。完整示例请查看vetkeys仓库

Deploy & Test

部署与测试

Local Development

本地开发

bash
undefined
bash
undefined

Start the local network (provisions test_key_1 and key_1 automatically)

启动本地网络(自动预配置test_key_1和key_1)

icp network start -d
icp network start -d

Deploy your canister

部署你的canister

icp deploy backend
icp deploy backend

Test public key retrieval

测试公钥检索

icp canister call backend getPublicKey '()'
icp canister call backend getPublicKey '()'

Returns: (blob "...") -- the vetKD public key

返回:(blob "...") -- vetKD公钥

For derive_key, you need a transport public key (generated by frontend)

对于derive_key,你需要传输公钥(由前端生成)

Test with a dummy 48-byte blob:

使用虚拟的48字节二进制数据测试:

icp canister call backend deriveKey '(blob "\00\01\02\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\0f\10\11\12\13\14\15\16\17\18\19\1a\1b\1c\1d\1e\1f\20\21\22\23\24\25\26\27\28\29\2a\2b\2c\2d\2e\2f")'
undefined
icp canister call backend deriveKey '(blob "\00\01\02\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\0f\10\11\12\13\14\15\16\17\18\19\1a\1b\1c\1d\1e\1f\20\21\22\23\24\25\26\27\28\29\2a\2b\2c\2d\2e\2f")'
undefined

Mainnet

主网

bash
undefined
bash
undefined

Deploy to mainnet

部署到主网

icp deploy backend -e ic
icp deploy backend -e ic

Use test_key_1 for initial testing, key_1 for production

初始测试使用test_key_1,生产环境使用key_1

Make sure your canister code references the correct key name

确保你的canister代码引用了正确的密钥名称

undefined
undefined

Verify It Works

验证功能正常

bash
undefined
bash
undefined

1. Verify public key is returned (non-empty blob)

1. 验证返回公钥(非空二进制数据)

icp canister call backend getPublicKey '()'
icp canister call backend getPublicKey '()'

Expected: (blob "\ab\cd\ef...") -- 48+ bytes of BLS public key data

预期:(blob "\ab\cd\ef...") -- 48字节以上的BLS公钥数据

2. Verify derive_key returns encrypted key (non-empty blob)

2. 验证derive_key返回加密密钥(非空二进制数据)

icp canister call backend deriveKey '(blob "\00\01...")'
icp canister call backend deriveKey '(blob "\00\01...")'

Expected: (blob "\12\34\56...") -- encrypted key material

预期:(blob "\12\34\56...") -- 加密后的密钥材料

3. Verify determinism: same (caller, context, input) and same transport key produce same encrypted_key

3. 验证确定性:相同的(caller, context, input)和相同的传输密钥生成相同的encrypted_key

Call deriveKey twice with the same identity and transport key

使用相同的身份和传输密钥调用deriveKey两次

Expected: identical encrypted_key blobs both times

预期:两次返回的encrypted_key二进制数据完全相同

4. Verify isolation: different callers get different keys

4. 验证隔离性:不同调用者获取不同的密钥

icp identity new test-user-1 --storage-mode=plaintext icp identity new test-user-2 --storage-mode=plaintext icp identity default test-user-1 icp canister call backend deriveKey '(blob "\00\01...")'
icp identity new test-user-1 --storage-mode=plaintext icp identity new test-user-2 --storage-mode=plaintext icp identity default test-user-1 icp canister call backend deriveKey '(blob "\00\01...")'

Note the output

记录输出

icp identity default test-user-2 icp canister call backend deriveKey '(blob "\00\01...")'
icp identity default test-user-2 icp canister call backend deriveKey '(blob "\00\01...")'

Expected: DIFFERENT encrypted_key (different caller = different derived key)

预期:返回不同的encrypted_key(不同调用者=不同的派生密钥)

5. Frontend integration test

5. 前端集成测试

Open the frontend, trigger encryption/decryption

打开前端,触发加密/解密操作

Verify: encrypted data can be decrypted by the same user

验证:加密的数据可以被同一用户解密

Verify: encrypted data CANNOT be decrypted by a different user

验证:加密的数据无法被其他用户解密

undefined
undefined