Skip to content

feat(vetkeys): adopt EncryptedMaps mixins in the Motoko examples (ic-vetkeys 0.6.0) - #1475

Merged
marc0olo merged 6 commits into
masterfrom
feat/vetkeys-motoko-0.6.0-mixins
Aug 12, 2026
Merged

feat(vetkeys): adopt EncryptedMaps mixins in the Motoko examples (ic-vetkeys 0.6.0)#1475
marc0olo merged 6 commits into
masterfrom
feat/vetkeys-motoko-0.6.0-mixins

Conversation

@marc0olo

@marc0olo marc0olo commented Aug 12, 2026

Copy link
Copy Markdown
Member

Motoko counterpart to #1474, using the mixins shipped in mops ic-vetkeys 0.6.0 — the Motoko half of dfinity/vetkeys#423 (PR dfinity/vetkeys#425).

Mixins

  • password_manager includes EncryptedMapsCanister, which contributes the complete Encrypted Maps endpoint set: 228 → 38 lines.
  • password_manager_with_metadata includes EncryptedMapsControlPlaneCanister, which contributes the control-plane endpoints and the in-scope encryptedMaps object but none of the value read/write endpoints: 266 → 197 lines. The plain insert_encrypted_value/remove_encrypted_value mutators would write a value with no metadata row and desync the two stores, so they are never exposed; the canister keeps its own *_with_metadata endpoints.

Neither mixin declares stable state — the actor declares the EncryptedMapsState and 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_NAME canister environment variable (defaulting to test_key_1 so init stays total), replacing actor class (keyName : Text) + init_args. This follows the upstream reference canisters and removes the actor class from every main canister — encrypted_notes_app_vetkd also loses a shared ({ 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 transient instead would re-read it on every upgrade, turning an edited variable into silent data loss behind a green deploy.

Other changes

  • Domain separators: both password managers shared password_manager_example_app; they 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 examples pinning ic-vetkeys move together. The new toolchain surfaces dot-notation warnings (auto-fixed), deprecated Nat64.fromNat / Nat8.fromNat / Blob.fromArray, and never-reassigned vars — changing those to let needs no migration, since matching stable fields may differ in mutability.
  • Regenerated the committed Candid: each service loses its (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 check clean and icp project show valid for all six; every backend builds.

Local icp deploy end-to-end: both password managers (insert/update/read with metadata, vault sharing, values and metadata surviving an upgrade, insert_encrypted_value correctly 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:

  • The variable is really delivered — the ?? "test_key_1" default yields identical results if it never arrives, so basic_ibe was deployed with VETKD_KEY_NAME=no_such_key and vetKD rejected the name.
  • The key is really frozenbasic_ibe was then upgraded with VETKD_KEY_NAME=no_such_key and 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_value would make the two languages consistent, but #1474 is already tested and open, so that belongs in its own PR.

🤖 Generated with Claude Code

marc0olo and others added 3 commits August 12, 2026 11:18
…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>
@marc0olo
marc0olo requested a review from a team as a code owner August 12, 2026 10:02
marc0olo and others added 2 commits August 12, 2026 12:09
…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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 / EncryptedMapsControlPlaneCanister mixins in the password manager examples.
  • Move vetKD key configuration from init args to VETKD_KEY_NAME environment variable (documenting the “frozen on first install” behavior) across the Motoko vetKeys examples.
  • Bump moc/mo:core and ic-vetkeys versions and regenerate committed .did files 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.

Comment thread motoko/vetkeys/password_manager/backend/main.mo Outdated
Comment thread motoko/vetkeys/password_manager_with_metadata/backend/main.mo
Comment thread motoko/vetkeys/basic_bls_signing/backend/main.mo
Comment thread motoko/vetkeys/basic_vetkd/backend/main.mo
Comment thread motoko/vetkeys/basic_ibe/backend/main.mo
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>
@marc0olo
marc0olo merged commit c73823d into master Aug 12, 2026
17 checks passed
@marc0olo
marc0olo deleted the feat/vetkeys-motoko-0.6.0-mixins branch August 12, 2026 11:49
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants