Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/backend-rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
run: |
set -eExuo pipefail
export CARGO_TERM_COLOR=always # ensure output has colors
cargo build --release --target wasm32-unknown-unknown -p ic-vetkeys-manager-canister -p ic-vetkeys-encrypted-maps-canister -p ic-vetkeys-canisters-tests
cargo build --release --target wasm32-unknown-unknown -p ic-vetkeys-manager-canister -p ic-vetkeys-encrypted-maps-canister -p ic-vetkeys-encrypted-maps-custom-canister -p ic-vetkeys-canisters-tests
cargo test
cargo test --doc
cargo-test-backend-darwin:
Expand All @@ -46,6 +46,6 @@ jobs:
run: |
set -eExuo pipefail
export CARGO_TERM_COLOR=always # ensure output has colors
cargo build --release --target wasm32-unknown-unknown -p ic-vetkeys-manager-canister -p ic-vetkeys-encrypted-maps-canister -p ic-vetkeys-canisters-tests
cargo build --release --target wasm32-unknown-unknown -p ic-vetkeys-manager-canister -p ic-vetkeys-encrypted-maps-canister -p ic-vetkeys-encrypted-maps-custom-canister -p ic-vetkeys-canisters-tests
cargo test
cargo test --doc
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ members = [
"backend/rs/ic_vetkeys",
"backend/rs/ic_vetkeys_test_utils",
"backend/rs/canisters/ic_vetkeys_encrypted_maps_canister",
"backend/rs/canisters/ic_vetkeys_encrypted_maps_custom_canister",
"backend/rs/canisters/ic_vetkeys_manager_canister",
"backend/rs/canisters/tests",
"backend/rs/benchmarks",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ The canister implemented in this folder directly exposes the methods of the encr
This is useful for:

1. running canister tests
2. implementing dapps that only require encrypted maps
2. implementing dapps that only require encrypted maps

It uses the full form of the [`export_encrypted_maps_canister!`](https://docs.rs/ic-vetkeys/latest/ic_vetkeys/macro.export_encrypted_maps_canister.html) macro. If your dapp keeps state linked to each value and needs to provide its own value endpoints, see [`ic-vetkeys-encrypted-maps-custom-canister`](../ic_vetkeys_encrypted_maps_custom_canister) and the macro's `custom_value_endpoints` form.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "ic-vetkeys-encrypted-maps-custom-canister"
authors = ["DFINITY Stiftung"]
version = "0.1.0"
edition = "2021"
license = "Apache-2.0"
description = "Reference canister for the export_encrypted_maps_canister! custom_value_endpoints form"
repository = "https://github.com/dfinity/vetkeys"
rust-version = "1.85.0"

[lib]
path = "src/lib.rs"
crate-type = ["cdylib"]

[dependencies]
candid = "0.10.2"
ic-cdk = { workspace = true }
ic-cdk-management-canister = { workspace = true }
ic-dummy-getrandom-for-wasm = "0.1.0"
ic-stable-structures = { workspace = true }
ic-vetkeys = { workspace = true }
serde = "1.0.217"
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# ic-vetkeys-encrypted-maps-custom-canister

A reference canister for the `custom_value_endpoints` form of the
[`export_encrypted_maps_canister!`](https://docs.rs/ic-vetkeys/latest/ic_vetkeys/macro.export_encrypted_maps_canister.html)
macro.

Unlike [`ic-vetkeys-encrypted-maps-canister`](../ic_vetkeys_encrypted_maps_canister),
which exposes the full EncryptedMaps interface, this canister generates only the
control-plane endpoints (vetKD keys, access control, map-name enumeration) plus
the state, lifecycle hooks, and the `with_encrypted_maps`/`with_encrypted_maps_mut`
accessors, and provides its own value read/write endpoints.

This is the pattern for dapps that keep state linked to each encrypted value
(e.g. a metadata row per entry) and therefore must own the value endpoints to
keep the two stores consistent — see `password_manager_with_metadata` in
[dfinity/examples](https://github.com/dfinity/examples). The endpoints here are
kept minimal (a plain insert/get against the accessor) to exercise the macro
form; they are not a full metadata implementation.
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Reference canister for the `custom_value_endpoints` form of
// `export_encrypted_maps_canister!`. It generates the control-plane endpoints
// plus the state, lifecycle hooks, and the `with_encrypted_maps`/`_mut`
// accessors, but NOT the value read/write endpoints — the canister exposes its
// own. This is the pattern an app uses when it keeps state linked to each value
// (e.g. a metadata row per entry) and must own the value endpoints to keep that
// state consistent.
//
// This minimal example just re-implements a plain insert/get against the
// accessor to exercise the surface; a full linked-metadata example lives in
// `password_manager_with_metadata` in dfinity/examples.
use candid::Principal;
use ic_stable_structures::memory_manager::{MemoryId, MemoryManager, VirtualMemory};
use ic_stable_structures::storable::Blob;
use ic_stable_structures::DefaultMemoryImpl;
use ic_vetkeys::types::{ByteBuf, EncryptedMapValue};
use std::cell::RefCell;

type Memory = VirtualMemory<DefaultMemoryImpl>;

thread_local! {
static MEMORY_MANAGER: RefCell<MemoryManager<DefaultMemoryImpl>> =
RefCell::new(MemoryManager::init(DefaultMemoryImpl::default()));
}

fn memory(id: u8) -> Memory {
MEMORY_MANAGER.with(|m| m.borrow().get(MemoryId::new(id)))
}

// Control plane + state + lifecycle + accessors; no value endpoints.
ic_vetkeys::export_encrypted_maps_canister!(
"encrypted_maps_custom_dapp",
[memory(0), memory(1), memory(2), memory(3)],
custom_value_endpoints,
);

fn to_blob(bytes: ByteBuf) -> Result<Blob<32>, String> {
Blob::try_from(bytes.as_ref()).map_err(|_| "too large input".to_string())
}

// A canister-owned value write, wrapping the library's own via the accessor.
// This is where linked side-state (metadata, counters, …) would be maintained
// in the same call — kept minimal here.
//
// Parameters (matching the EncryptedMaps model):
// - `map_owner`: the principal that owns the map; together with `map_name` it
// identifies the map. Maps are namespaced per owner.
// - `map_name`: the map's name within the owner's namespace (max 32 bytes).
// - `map_key`: the key of the entry within that map (max 32 bytes).
// - `value`: the already client-side-encrypted value to store.
// Returns the previous value at that key, if any. Access is checked against the
// caller (`msg_caller`) by the library.
#[ic_cdk::update]
fn insert_encrypted_value_custom(
map_owner: Principal,
map_name: ByteBuf,
map_key: ByteBuf,
value: EncryptedMapValue,
) -> Result<Option<EncryptedMapValue>, String> {
with_encrypted_maps_mut(|encrypted_maps| {
encrypted_maps.insert_encrypted_value(
ic_cdk::api::msg_caller(),
(map_owner, to_blob(map_name)?),
Comment thread
marc0olo marked this conversation as resolved.
to_blob(map_key)?,
value,
)
})
}

// A canister-owned value read via the shared-reference accessor.
//
// `map_owner` + `map_name` identify the map, `map_key` the entry within it (see
// `insert_encrypted_value_custom`). Returns the encrypted value at that key, if
// any; access is checked against the caller by the library.
#[ic_cdk::query]
fn get_encrypted_value_custom(
map_owner: Principal,
map_name: ByteBuf,
map_key: ByteBuf,
) -> Result<Option<EncryptedMapValue>, String> {
with_encrypted_maps(|encrypted_maps| {
encrypted_maps.get_encrypted_value(
ic_cdk::api::msg_caller(),
(map_owner, to_blob(map_name)?),
to_blob(map_key)?,
)
})
}

ic_cdk::export_candid!();
13 changes: 13 additions & 0 deletions backend/rs/ic_vetkeys/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Change Log

## [Unreleased]

### Added

- `export_encrypted_maps_canister!` gained a `custom_value_endpoints` form. It
generates the control-plane endpoints, the stable state, the
`#[init]`/`#[post_upgrade]` hooks, and `with_encrypted_maps`/`_mut` accessors,
but omits every endpoint that reads or writes encrypted map values — for
canisters that keep state linked to each value (e.g. a metadata row per entry)
and must own the value endpoints to keep that state consistent. Reuse the
library's vetKD/crypto/access-control logic from your own endpoints via the
accessors. The full form is unchanged and its generated Candid is identical.

## [0.8.1] - 2026-07-28

### Fixed
Expand Down
14 changes: 14 additions & 0 deletions backend/rs/ic_vetkeys/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ A canister library for derivation of encrypted vetkeys from arbitrary strings. I
## [Encrypted Maps](https://docs.rs/ic-vetkeys/latest/ic_vetkeys/encrypted_maps/struct.EncryptedMaps.html)
An efficient canister library facilitating access control and encrypted storage for a collection of maps contatining key-value pairs. It can be used in combination with the [frontend encrypted maps library](https://dfinity.github.io/vetkeys/classes/_icp-sdk_vetkeys_encrypted_maps.EncryptedMaps.html).

## Ready-made EncryptedMaps canister

The [`export_encrypted_maps_canister!`](https://docs.rs/ic-vetkeys/latest/ic_vetkeys/macro.export_encrypted_maps_canister.html) macro generates a complete EncryptedMaps canister — the `#[init]`/`#[post_upgrade]`, the stable state, and every endpoint the `@icp-sdk/vetkeys` frontend expects — in a few lines instead of ~200 of hand-written boilerplate:

```ignore
ic_vetkeys::export_encrypted_maps_canister!(
"my_app_domain_separator",
[memory(0), memory(1), memory(2), memory(3)],
);
ic_cdk::export_candid!();
```

If your canister keeps state linked to each value (e.g. a metadata row per entry) and must own the value read/write endpoints, pass `custom_value_endpoints`: the macro then generates the control-plane endpoints, state, lifecycle hooks, and `with_encrypted_maps`/`with_encrypted_maps_mut` accessors, but omits the value endpoints so you can provide your own. See the [macro documentation](https://docs.rs/ic-vetkeys/latest/ic_vetkeys/macro.export_encrypted_maps_canister.html) for the full setup and rationale.

## [Utils](https://docs.rs/ic-vetkeys/latest/)
For obtaining and decrypting verifiably-encrypted threshold keys via the Internet Computer vetKD system API. The API is located in the crate root.

Expand Down
Loading
Loading