Skip to content

feat: Add encrypted-maps skill for vetKeys EncryptedMaps#158

Open
andreacerulli wants to merge 2 commits intomainfrom
add-encrypted-maps-skill
Open

feat: Add encrypted-maps skill for vetKeys EncryptedMaps#158
andreacerulli wants to merge 2 commits intomainfrom
add-encrypted-maps-skill

Conversation

@andreacerulli
Copy link
Copy Markdown
Collaborator

New skill covering end-to-end encrypted on-chain storage using the ic-vetkeys EncryptedMaps library. Includes complete Rust backend, Motoko backend reference, frontend TypeScript examples, icp-cli configuration, and pitfalls from real-world testing.

New skill covering end-to-end encrypted on-chain storage using the
ic-vetkeys EncryptedMaps library. Includes complete Rust backend,
Motoko backend reference, frontend TypeScript examples, icp-cli
configuration, and pitfalls from real-world testing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@andreacerulli andreacerulli requested review from a team and JoshDFN as code owners April 9, 2026 12:45
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

Skill Validation Report

Validating skill: /home/runner/work/icskills/icskills/skills/encrypted-maps

Structure

  • Pass: SKILL.md found
  • Pass: all files in references/ are referenced

Frontmatter

  • Pass: name: "encrypted-maps" (valid)
  • Pass: description: (521 chars)
  • Pass: license: "Apache-2.0"
  • Pass: compatibility: (16 chars)
  • Pass: metadata: (2 entries)

Markdown

  • Pass: no unclosed code fences found

Tokens

File Tokens
SKILL.md body 4,744
references/motoko-backend.md 2,159
Total 6,903

Content Analysis

Metric Value
Word count 1,976
Code block ratio 0.52
Imperative ratio 0.09
Information density 0.31
Instruction specificity 1.00
Sections 13
List items 19
Code blocks 11

References Content Analysis

Metric Value
Word count 899
Code block ratio 0.93
Imperative ratio 0.00
Information density 0.47
Instruction specificity 1.00
Sections 3
List items 2
Code blocks 3

Contamination Analysis

Metric Value
Contamination level medium
Contamination score 0.23
Primary language category config
Scope breadth 4
  • Warning: Language mismatch: javascript, shell, systems (3 categories differ from primary)

References Contamination Analysis

Metric Value
Contamination level low
Contamination score 0.00
Primary language category config
Scope breadth 1

Result: passed

Project Checks


WARNINGS (1):
  ⚠ encrypted-maps/SKILL.md: missing evaluations/encrypted-maps.json — see CONTRIBUTING.md for evaluation guidance

✓ Project checks passed for 1 skills (1 warnings)

@andreacerulli andreacerulli changed the title Add encrypted-maps skill for vetKeys EncryptedMaps feat: Add encrypted-maps skill for vetKeys EncryptedMaps Apr 9, 2026
Comment on lines +74 to +83
"@dfinity/agent": "^3.4.0",
"@dfinity/principal": "^3.4.0",
"@dfinity/vetkeys": "^0.4.0",
"@icp-sdk/auth": "^5.0.0",
"@icp-sdk/core": "^5.0.0"
}
}
```

`@dfinity/agent` and `@dfinity/principal` are required because `@dfinity/vetkeys` depends on them. `@icp-sdk/auth` provides Internet Identity login — import from `@icp-sdk/auth/client`, not the root module (the root has no exports). `@icp-sdk/core` provides canister ID discovery via the `ic_env` cookie set by `icp deploy`.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

same question here. can we get rid of using @dfinity/agent and @dfinity/principal ?

@marc0olo
Copy link
Copy Markdown
Member

marc0olo commented Apr 10, 2026

Review: encrypted-maps skill

Strongest of the two PRs — the structure is clear, the pitfalls section is excellent, and the frontend is well-organized into logical sub-sections. A few things to fix before merge:

Bug: remove_map_values return type mismatch

The function signature declares Result<Vec<EncryptedMapValue>, String> but the closure maps each removed entry to ByteBuf::from(...), so the inferred return type is Result<Vec<ByteBuf>, String>. These don't match — the code won't compile as-is. Fix the return type to match the closure.

Pitfall 4 — cycle cost is vague

"The library attaches cycles automatically, but the canister must have sufficient balance. Top up before testing." — an agent has no idea how much to top up. Add the exact cost: each get_encrypted_vetkey call costs 26_153_846_153 cycles (both locally on PocketIC and on mainnet, for both test_key_1 and key_1). The same PocketIC deviation note from the vetkeys skill applies here: locally, test_key_1 costs the same as key_1, not less.

Deploy section — explain the cycle top-up amount

icp cycles top-up backend 10T -e ic is reasonable but unexplained. Add a brief note: each get_encrypted_vetkey call costs ~26B cycles, so 10T covers roughly 380 key derivations as an initial buffer.

Missing: #[post_upgrade] note

Since EncryptedMaps uses stable memory exclusively, upgrades work without an explicit #[post_upgrade] handler — state survives automatically. One sentence clarifying this prevents agents from adding an unnecessary handler or worrying about data loss on upgrade.

Description — make it the explicit default

Consider adding that encrypted-maps is the default starting point for any encrypted storage need, and vetkeys is only for when lower-level control is required. Something like: "Start here for any encrypted storage use case; escalate to vetkeys only if you need BLS signatures, custom IBE, or timelock." This helps agents make the right choice on ambiguous queries like "I want to add encryption to my app."

icp.yaml section

The icp-cli skill covers recipes, frontend setup, and network config. Collapse this section to the encrypted-maps-specific part only:

canisters:
  - name: backend
    init_args: '("test_key_1")'  # Production: '("key_1")'

With a reference: "For full icp.yaml setup, see the icp-cli skill."

Deploy & Verify

Drop the generic icp network start -d and icp deploy backend — those are icp-cli territory. The domain-specific verification calls are good — keep those. Add a note that get_vetkey_verification_key triggers an actual management canister call and costs cycles, so the canister needs to be topped up first.

Missing: evaluation file

Please add evaluations/encrypted-maps.json. Suggested content:

{
  "skill": "encrypted-maps",
  "description": "Tests whether agents use the EncryptedMaps high-level library correctly, avoid raw vetkeys wiring, handle AccessRights as Candid variants, and understand the 32-byte key/name limit.",

  "output_evals": [
    {
      "name": "Password manager scaffold",
      "prompt": "I want to build a password manager on ICP where each user's passwords are encrypted and only they can read them. Show me just the frontend store and retrieve flow, no backend or deploy steps.",
      "expected_behaviors": [
        "Uses EncryptedMaps class from @dfinity/vetkeys/encrypted_maps",
        "Calls setValue() to store and getValue() to retrieve — does NOT call vetkd_derive_key directly",
        "Map name and map key are encoded with TextEncoder and are within 32 bytes",
        "Does NOT manually generate transport keys, wire AES-GCM, or call management canister methods"
      ]
    },
    {
      "name": "Multi-user vault sharing",
      "prompt": "How do I let a vault owner share read access to their encrypted map with another user? Show just the TypeScript sharing and revocation calls.",
      "expected_behaviors": [
        "Uses encryptedMaps.setUserRights() with { ReadWrite: null } or { Read: null } — NOT a string",
        "Uses encryptedMaps.removeUser() to revoke",
        "Passes the owner's principal as the first argument to setUserRights",
        "Does NOT re-encrypt the data for the new user — shared encryption handles this automatically"
      ]
    },
    {
      "name": "Adversarial: AccessRights comparison",
      "prompt": "I'm checking user access with `if (rights === 'ReadWrite')` but it's always false even after granting ReadWrite. What's wrong?",
      "expected_behaviors": [
        "Explains that Candid variants are objects, not strings: { ReadWrite: null }",
        "Shows correct comparison: 'ReadWrite' in rights, or rights?.ReadWrite !== undefined",
        "Does NOT suggest using === 'ReadWrite'"
      ]
    },
    {
      "name": "Adversarial: raw vetkeys for storage",
      "prompt": "I want to encrypt user data on ICP. Should I call vetkd_derive_key directly and manage my own AES keys?",
      "expected_behaviors": [
        "Redirects to EncryptedMaps — explains the library handles transport keys, AES-GCM, and key caching",
        "Notes that raw vetkd_derive_key is only needed for BLS, IBE, or timelock (use vetkeys skill)",
        "Does NOT show manual AES-GCM wiring"
      ]
    }
  ],

  "trigger_evals": {
    "description": "Queries to test whether the encrypted-maps skill activates correctly.",
    "should_trigger": [
      "Build a password manager on ICP",
      "I need encrypted notes that only the user can read",
      "How do I store encrypted data on-chain with access control?",
      "I want users to share encrypted files on the Internet Computer",
      "How do I use EncryptedMaps?",
      "Build a secret vault with multi-user sharing on ICP"
    ],
    "should_not_trigger": [
      "I want threshold BLS signatures in my canister",
      "How do I implement identity-based encryption?",
      "Build a sealed-bid auction on ICP",
      "Add Internet Identity login to my dapp",
      "How does stable memory work?"
    ]
  }
}

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.

2 participants