feat(vetkeys): adopt EncryptedMaps mixins in the Motoko examples (ic-vetkeys 0.6.0) - #1475
Merged
Merged
Conversation
…gers mops ic-vetkeys 0.6.0 adds the Motoko counterpart to the Rust macro, closing the Motoko half of dfinity/vetkeys#423: - `EncryptedMapsCanister` (mo:ic-vetkeys/encrypted_maps/Canister) contributes the complete Encrypted Maps endpoint set. `password_manager` drops from 228 lines of hand-written delegation to ~15. - `EncryptedMapsControlPlaneCanister` (.../ControlPlaneCanister) contributes the control-plane endpoints and the in-scope `encryptedMaps` object but none of the value read/write endpoints. `password_manager_with_metadata` uses it, so the plain insert/remove mutators — which would write a value with no metadata row and desync the two stores — are never exposed, while the canister keeps its own `*_with_metadata` endpoints. Neither mixin declares stable state: the actor declares the `EncryptedMapsState` and passes it in, keeping the persistent state a plain, visible stable variable the canister owns and can migrate. Both password managers become plain persistent actors that resolve the vetKD key name from the `VETKD_KEY_NAME` canister environment variable (defaulting to test_key_1 so init stays total), replacing the `actor class (keyName : Text)` and its `init_args`. This follows the upstream reference canisters and removes the actor class from the main canister. `icp.yaml` sets the variable under `settings.environment_variables`; the generated Candid loses its `(text)` init argument but exposes an identical 11-method interface. Domain separators now name their own example: both Motoko password managers shared `password_manager_example_app`, and are now `password_manager_app` and `password_manager_with_metadata_app`, matching the Rust examples. 0.6.0 requires moc 1.13.0 and mo:core 2.6.1 (raised from 1.11.0 / 2.5.0), so all five Motoko examples pinning ic-vetkeys move together. The new toolchain surfaces warnings in basic_ibe, basic_bls_signing and basic_vetkd: dot notation (auto-fixed), the deprecated Nat64.fromNat / Nat8.fromNat / Blob.fromArray, and never-reassigned `var`s. Changing those to `let` needs no migration — matching stable fields may differ in mutability. Verified: mops check clean for all five; all backends build; local icp deploy e2e on both password managers — insert/update/read with metadata, vault sharing via the mixin-generated control plane, values and metadata surviving an upgrade, and `insert_encrypted_value` correctly absent from the metadata canister (IC0536). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The two password managers moved to the `VETKD_KEY_NAME` canister environment
variable when they adopted the Encrypted Maps mixins, which left the other
Motoko vetKeys examples on `actor class (keyName : Text)` + `init_args` — two
conventions side by side in one directory. This converts the rest:
- basic_ibe, basic_bls_signing: actor class parameter -> env var
- basic_vetkd: the key name was hardcoded to `test_key_1`; it is now
configurable through the same variable
- encrypted_notes_app_vetkd: also drops the actor class. Its
`shared ({ caller = initializer })` binding was never used, so nothing needed
replacing.
Each canister is now a plain actor reading the key at install with a
`?? "test_key_1"` default, so init stays total. `icp.yaml` sets the variable
under `settings.environment_variables`, and the READMEs document it — including
that the key cannot be changed once a canister holds data, since it feeds vetKD
key derivation.
Regenerated the committed Candid: every service loses its `(text)` init
argument, and the method sets are unchanged. basic_vetkd's file was previously
hand-formatted, so it also picks up the generator's parameter names and ordering.
Verified: mops check clean; local icp deploy for all four — IBE public key, BLS
sign + list, symmetric key verification, and a note round trip. Confirmed the
variable is genuinely delivered to the canister rather than silently falling
back to the default, by deploying basic_ibe with VETKD_KEY_NAME=no_such_key and
watching vetKD reject that key name.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The `icp.yaml` comment claimed the key is read at install time, which is only true for the password managers: they bake it into the stable EncryptedMapsState, so a later change is ignored. The other four hold it in a `transient`, re-read on every upgrade, so a changed variable does take effect — and orphans everything encrypted under the old key. The code comments and READMEs of those four said only "do not change it" without saying what happens; they now state it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ient basic_ibe, basic_bls_signing, basic_vetkd and encrypted_notes_app_vetkd held the key name in a `transient`, which is re-read on every upgrade — so editing VETKD_KEY_NAME and redeploying silently re-keyed the canister and orphaned everything encrypted under the old key, with a green deploy and no error. Dropping `transient` makes it a stable variable captured at the first install, matching the password managers, where the key is baked into the EncryptedMapsState. A later change to the variable is now ignored instead of destructive; only a reinstall, which drops all data, switches keys. This takes away no capability. Changing the key already destroyed the ability to read the data — a migration could rewrite the field, but the canister only ever sees ciphertext and cannot re-encrypt, so it would just yield a canister that cannot read its own store. Real re-keying needs application-level key rotation, which these examples do not implement and which their READMEs already note. All six examples now behave identically, so the code comments, the icp.yaml comments and the README paragraph are one shared wording that states when the key is read, that later changes are ignored, and why reinstall is the only escape. Verified: mops check and icp project show clean for all six; deployed basic_ibe, then upgraded it with VETKD_KEY_NAME=no_such_key and confirmed the IBE public key is unchanged — the misconfiguration is ignored rather than silently orphaning data. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Mirrors the review feedback on #1474 for the Motoko side. The header block describing each canister becomes `///`, matching how cert-var, random_maze and canister_factory already document their `main.mo`. Only the two headers change. Inline notes on individual declarations stay `//`: they are implementation notes, not interface documentation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the Motoko vetKeys examples to use the ic-vetkeys 0.6.0 Encrypted Maps mixins and standardizes vetKD key selection via the VETKD_KEY_NAME canister environment variable (captured on first install), while bumping the Motoko toolchain/deps and regenerating Candid where needed.
Changes:
- Replace hand-written Encrypted Maps delegation with
EncryptedMapsCanister/EncryptedMapsControlPlaneCanistermixins in the password manager examples. - Move vetKD key configuration from init args to
VETKD_KEY_NAMEenvironment variable (documenting the “frozen on first install” behavior) across the Motoko vetKeys examples. - Bump
moc/mo:coreandic-vetkeysversions and regenerate committed.didfiles accordingly.
Reviewed changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| motoko/vetkeys/password_manager/README.md | Documents env-var-based key selection and mixin-based endpoint generation. |
| motoko/vetkeys/password_manager/mops.toml | Bumps moc, core, and ic-vetkeys versions. |
| motoko/vetkeys/password_manager/icp.yaml | Switches from init_args to settings.environment_variables for VETKD_KEY_NAME. |
| motoko/vetkeys/password_manager/backend/main.mo | Replaces hand-written Encrypted Maps endpoints with EncryptedMapsCanister mixin usage. |
| motoko/vetkeys/password_manager_with_metadata/README.md | Documents env-var key selection and control-plane-only mixin rationale. |
| motoko/vetkeys/password_manager_with_metadata/mops.toml | Bumps moc, core, and ic-vetkeys versions. |
| motoko/vetkeys/password_manager_with_metadata/icp.yaml | Switches from init_args to settings.environment_variables for VETKD_KEY_NAME. |
| motoko/vetkeys/password_manager_with_metadata/backend/main.mo | Adopts EncryptedMapsControlPlaneCanister mixin and keeps custom *_with_metadata endpoints. |
| motoko/vetkeys/password_manager_with_metadata/backend/backend.did | Regenerates service definition to remove init arg and reflect updated canister shape. |
| motoko/vetkeys/encrypted_notes_app_vetkd/README.md | Adds env-var master key selection documentation. |
| motoko/vetkeys/encrypted_notes_app_vetkd/icp.yaml | Switches from init_args to settings.environment_variables for VETKD_KEY_NAME. |
| motoko/vetkeys/encrypted_notes_app_vetkd/backend/main.mo | Removes actor class init arg and reads VETKD_KEY_NAME in-canister. |
| motoko/vetkeys/encrypted_notes_app_vetkd/backend/backend.did | Regenerates service definition to remove init arg. |
| motoko/vetkeys/basic_vetkd/README.md | Adds env-var master key selection documentation. |
| motoko/vetkeys/basic_vetkd/mops.toml | Bumps moc, core, and ic-vetkeys versions. |
| motoko/vetkeys/basic_vetkd/icp.yaml | Adds VETKD_KEY_NAME environment variable configuration. |
| motoko/vetkeys/basic_vetkd/backend/main.mo | Makes key name configurable via Runtime.envVar rather than hardcoding. |
| motoko/vetkeys/basic_vetkd/backend/backend.did | Regenerates formatting/field names in the service definition output. |
| motoko/vetkeys/basic_ibe/README.md | Documents env-var-based key selection and the reinstall requirement to switch keys. |
| motoko/vetkeys/basic_ibe/mops.toml | Bumps moc, core, and ic-vetkeys versions. |
| motoko/vetkeys/basic_ibe/icp.yaml | Switches from init_args to settings.environment_variables for VETKD_KEY_NAME. |
| motoko/vetkeys/basic_ibe/backend/main.mo | Removes actor class init arg and updates to newer mo:core APIs (dot-notation, conversions). |
| motoko/vetkeys/basic_ibe/backend/backend.did | Regenerates service definition to remove init arg. |
| motoko/vetkeys/basic_bls_signing/README.md | Documents env-var master key selection behavior. |
| motoko/vetkeys/basic_bls_signing/mops.toml | Bumps moc, core, and ic-vetkeys versions. |
| motoko/vetkeys/basic_bls_signing/icp.yaml | Switches from init_args to settings.environment_variables for VETKD_KEY_NAME. |
| motoko/vetkeys/basic_bls_signing/backend/main.mo | Removes actor class init arg and updates to newer mo:core APIs (dot-notation, conversions). |
| motoko/vetkeys/basic_bls_signing/backend/backend.did | Regenerates service definition to remove init arg. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Review catch on #1475: the last two lines of the header stayed `//`, truncating the doc comment mid-sentence. The script that converted the block ended it at the first "actor " it found, which matched the words "this actor declares" inside the prose rather than the declaration. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
alin-at-dfinity
approved these changes
Aug 12, 2026
marc0olo
added a commit
to dfinity/icskills
that referenced
this pull request
Aug 14, 2026
## Summary Replaces the outdated `vetkd` skill with **two** skills split by abstraction level, updated to the current vetKeys libraries and verified end-to-end. - **`vetkeys`** — the vetKD management API plus advanced primitives: symmetric key derivation, IBE, timelock encryption, threshold BLS signatures, offline public-key derivation, and VRF. Feature depth lives in `references/ibe.md` and `references/bls-signing.md`. - **`encrypted-maps`** — high-level, access-controlled encrypted key-value storage (the default for password managers / encrypted notes): the Rust `export_encrypted_maps_canister!` macro, the Motoko `EncryptedMapsCanister` mixin, the TS `EncryptedMaps` client, a short `KeyManager` section, and the metadata / custom-value-endpoint variant in `references/metadata.md`. Each skill's description routes to the other, so agents land on the right one (bidirectional routing verified in the trigger evals). ## Why replace `vetkd`? The shipped `vetkd` skill was inaccurate against the current libraries. It used the renamed `@dfinity/vetkeys` (frozen at 0.4), told Motoko developers to hand-roll the management canister (a full Motoko `ic-vetkeys` library now exists), pinned Rust `ic-vetkeys` 0.6 (now 0.9), used the deprecated `ic-cdk` call API, referenced a non-existent `toDerivedKeyMaterial()`, and misstated cycle costs. ## What's corrected - `@icp-sdk/vetkeys` 0.5 (renamed from `@dfinity/vetkeys`); frontend agent/identity from `@icp-sdk/core`, not `@dfinity/agent` - Rust `ic-vetkeys` 0.9 + `ic-cdk` 0.20 + `ic-cdk-management-canister`; Motoko `ic-vetkeys` 0.6 (moc 1.13 / core 2.6.1) - Library management helpers (`ic-cdk-management-canister`, `mo:ic-vetkeys/ManagementCanister`) instead of hand-rolled Candid + `actor "aaaaa-aa"` - `VetKey.asDerivedKeyMaterial()` → `encryptMessage`/`decryptMessage` (the old skill's `toDerivedKeyMaterial()` does not exist) - Correct cycle costs — `test_key_1` and `key_1` behave the same locally and on mainnet; helpers attach the amount - `verifyBlsSignature(DerivedPublicKey, …)`, `persistent actor` (compiles with or without `--default-persistent-actors`), and the Motoko-has-no-frontend-crypto asymmetry documented ## Verification Every code block is derived from the canonical `dfinity/examples` sources (Rust `master`, Motoko from dfinity/examples#1475, frontends `master`) and the library source. All Motoko snippets compiled verbatim on `moc 1.13.0` / `ic-vetkeys 0.6.0` / `core 2.6.1`, in both mops configurations (bare and `--default-persistent-actors`). Cycle costs were measured on a local replica. `npm run validate` passes for both skills. ## Honoring prior contributions This supersedes and builds on prior community work — thank you both: - **@andreacerulli** — the `vetkeys` / `encrypted-maps` split and the BLS / IBE / timelock scoping (#162, #158) - **@raymondk** — the accuracy review and eval seed (#272, #285, #286, #308) Closes #73, #272, #285, #286. Supersedes #158, #162, #308. ## Evals <details> <summary>Output evals — all cases pass with the skill; strong with-skill vs baseline deltas</summary> **vetkeys** | Case | WITH skill | WITHOUT (baseline) | |---|---|---| | ibe_encrypt_to_principal | 4/4 | 2/4 | | symmetric_key_material_api | 3/3 | 1/3 | | motoko_has_no_low_level_primitives | 4/4 | 0/4 | | cycle_cost_and_helpers | 3/3 | 0/3 | | bls_verify_uses_derived_public_key | 3/3 | 0/3 | **encrypted-maps** | Case | WITH skill | WITHOUT (baseline) | |---|---|---| | scaffold_backend_with_macro | 4/4 | 1/4 | | share_map_access_rights_variant | 2/2 | 0/2 | | scaffold_motoko_backend_mixin | 5/5 | 0/5 | | derived_key_material_caching | 3/3 | 1/3 | </details> <details> <summary>Trigger evals — 100% on both skills, including bidirectional routing</summary> - **vetkeys** — should_trigger 4/4, should_not_trigger 2/2 (declines the password-manager query → routes to `encrypted-maps`) - **encrypted-maps** — should_trigger 3/3, should_not_trigger 2/2 (declines the IBE / BLS queries → routes to `vetkeys`) </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motoko counterpart to #1474, using the mixins shipped in mops
ic-vetkeys0.6.0 — the Motoko half of dfinity/vetkeys#423 (PR dfinity/vetkeys#425).Mixins
password_managerincludesEncryptedMapsCanister, which contributes the complete Encrypted Maps endpoint set: 228 → 38 lines.password_manager_with_metadataincludesEncryptedMapsControlPlaneCanister, which contributes the control-plane endpoints and the in-scopeencryptedMapsobject but none of the value read/write endpoints: 266 → 197 lines. The plaininsert_encrypted_value/remove_encrypted_valuemutators would write a value with no metadata row and desync the two stores, so they are never exposed; the canister keeps its own*_with_metadataendpoints.Neither mixin declares stable state — the actor declares the
EncryptedMapsStateand passes it in, so the persistent state stays a plain, visible stable variable the canister owns and can migrate.Key configuration
All six Motoko vetKeys examples now resolve the vetKD key name from the
VETKD_KEY_NAMEcanister environment variable (defaulting totest_key_1so init stays total), replacingactor class (keyName : Text)+init_args. This follows the upstream reference canisters and removes the actor class from every main canister —encrypted_notes_app_vetkdalso loses ashared ({ caller = initializer })binding that was never used.basic_vetkd's key name was hardcoded and is now configurable like the rest.The key is captured in stable state at the first install, so a later change to the variable is silently ignored and only a reinstall switches keys. This is deliberate: the key feeds vetKD derivation, and since a canister only ever sees ciphertext it can never re-encrypt what the old key protected. Holding it in a
transientinstead would re-read it on every upgrade, turning an edited variable into silent data loss behind a green deploy.Other changes
password_manager_example_app; they are nowpassword_manager_appandpassword_manager_with_metadata_app, matching the Rust examples.mo:core2.6.1 (raised from 1.11.0 / 2.5.0), so all five examples pinningic-vetkeysmove together. The new toolchain surfaces dot-notation warnings (auto-fixed), deprecatedNat64.fromNat/Nat8.fromNat/Blob.fromArray, and never-reassignedvars — changing those toletneeds no migration, since matching stable fields may differ in mutability.(text)init argument with method sets unchanged, so the frontends are untouched.basic_vetkd's file was hand-formatted, so it also picks up the generator's parameter names and ordering.Verification
mops checkclean andicp project showvalid for all six; every backend builds.Local
icp deployend-to-end: both password managers (insert/update/read with metadata, vault sharing, values and metadata surviving an upgrade,insert_encrypted_valuecorrectly absent from the metadata canister), plus IBE public key, BLS sign + list, symmetric key verification and a note round trip for the other four. Browser-tested by @marc0olo: both password managers (share a vault, modify a password, redeploy — state survived) and the two IBE apps.Two things a green deploy would not have caught, checked separately:
?? "test_key_1"default yields identical results if it never arrives, sobasic_ibewas deployed withVETKD_KEY_NAME=no_such_keyand vetKD rejected the name.basic_ibewas then upgraded withVETKD_KEY_NAME=no_such_keyand returned an unchanged IBE public key.Follow-up
The Rust examples still take the key name as an init argument; moving them to
ic_cdk::api::env_var_valuewould make the two languages consistent, but #1474 is already tested and open, so that belongs in its own PR.🤖 Generated with Claude Code