Repository navigation
B3Note v2: native vetKD, burn-after-reading links, on-chain AI, ic-reactor 4 - #10
Merged
Merged
Conversation
The old canister relied on DFINITY's unsafe demo vetKD canister (its master secret key is in the source), encrypted every note to the anonymous principal and handed that decryption key to any caller, and let anyone overwrite any note. This replaces it: - ic-cdk 0.20, ic-stable-structures 0.7 and the management canister's vetkd_derive_key / vetkd_public_key - per-user vetKeys (input: the caller's principal) for AES-GCM note encryption done in the browser; owner-checked CRUD with optimistic concurrency, quotas and self-destruct timers - burn-after-reading share links: IBE ciphertext for the share id plus an Ed25519 key carried in the link; the canister releases the share's vetKey once per view and deletes the ciphertext after the last one - ai_assist through the on-chain LLM canister (ic-llm types), with per-user and global hourly budgets - cleanup timers re-registered after upgrades, config via install args - 27 unit tests and 29 PocketIC tests that do the client-side crypto with ic-vetkeys against real vetKD, plus a mock LLM canister Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017mjAwRTxCWYEMCuMXgkyjS
Replaces the Next.js 13 / rematch / @dfinity/agent 0.19 app. - Vite + React 19 + Tailwind v4, TanStack Router with lazy routes - @ic-reactor/vite-plugin codegen with factories; list_notes as an infinite query, mutations with invalidation and reactorUpdateRetry - Internet Identity through ic-reactor's AuthenticationManager, plus guest keys (a local Ed25519 identity with a recovery key) - the vault: the user's vetKey, fetched with a one-time transport key, verified, turned into a non-extractable AES-GCM key and cached in IndexedDB; on mainnet the canister's public keys are checked against the IC master key - markdown notes with tasks, tags, pinning, search, self-destruct timers, autosave with conflict handling, export and import - burn-after-reading links (IBE + Ed25519 key in the URL fragment) - AI assistant (summaries, titles, tags, rewrites, translation, to-dos) and "Ask your notes" with client-side retrieval, behind a consent step - icp.yaml for icp-cli (local network with II, production environment) - tests: crypto with a fake vetKD, session store, Markdown, the data layer against ic-reactor's fake replica, and a PicJS suite that runs the frontend code against the real canister in PocketIC Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017mjAwRTxCWYEMCuMXgkyjS
…message - GitHub Actions: rustfmt, clippy -D warnings, unit + PocketIC tests, frontend format/typecheck/tests/build and the PicJS suite against the built canister - README rewritten: features, encryption design, local development with icp-cli or PocketIC, mainnet deployment, testing, what v2 fixes - MIT license text (the LICENSE file was empty), root package.json - inspect_message refused public reads and update_config for anonymous callers, which is icp-cli's default local identity (and the local controller); update_config still checks for a controller itself Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017mjAwRTxCWYEMCuMXgkyjS
- Bindings: @ic-reactor/vite-plugin 4 generates src/canisters/backend.ts (candid-core) from backend.did; CI checks the committed file matches. - One client per tab (createClient, network "env") borrowed by ReactorProvider; components use TanStack's useQuery/useMutation with client.queryOptions/mutationOptions, other code calls the backend directly. Results are unwrapped and an Err is a typed canister_err. - SessionAuth: one AuthLike over Internet Identity and the guest key, so useAuth(), query keys and signing follow whichever is active. Signing in as a guest signs a lingering Internet Identity session out. - list_notes pages through useInfiniteQuery over client-built reads under the caller-scoped key; decrypted notes never carry over to another caller. - Share links open with an anonymous reader client, which keeps the reader's principal off the share and uses the backend's anonymous budget. - Tests: the data layer and sign-in on ic-reactor's test client (fake replica, mocked canister); the PicJS suite and the PocketIC dev script use v4 clients and the generated InitArgs schema. The dev script is now TypeScript (Node 22.18+). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017mjAwRTxCWYEMCuMXgkyjS
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.
Summary
A rewrite of B3Note. v1 had security and correctness problems serious enough that patching wasn't practical (see "What v2 fixes" in the README):
Backend (
backend/, Rust)b3note/user-key/v1, input = caller);b3note/share/v1).list_notes, optimistic concurrency (expected_version→Conflict), self-destruct timers, quotas.#fragment, and the reader signs(domain, share id, transport key);ai_assist): summarize, title, tags, improve, grammar, shorten, action items, translate, continue, and ask across notes. It has per-user and global hourly budgets and a controller kill switch.inspect_messageallow-list, cleanup timers that are re-registered after upgrades, and validated install args.backend.didis checked against the code by a unit test (UPDATE_CANDID=1regenerates it).Frontend (
frontend/)@icp-sdk/*and@icp-sdk/vetkeys.backend.didintosrc/canisters/backend.ts(candid-core);ReactorProvider;client.queryOptions/mutationOptionswith TanStack's own hooks.SessionAuth): oneAuthLikeover Internet Identity and a guest key. The guest key comes with a recovery key that can be exported and imported.Tooling
icp.yaml(icp-cli 1.6): Rust and asset-canister recipes, a local network with Internet Identity, and aproductionenvironment.scripts/build-canisters.sh,scripts/test-backend.sh, andfrontend/scripts/pocketic-dev.tsfor UI work without icp-cli..github/workflows/ci.yml):backend.did, and PicJS against the real canister.LICENSE(it was empty).Testing
./scripts/test-backend.sh: 28 Rust unit tests and 30 PocketIC integration tests with real vetKD. The integration tests check keys with theic-vetkeyscrate and use a mock LLM canister.pnpm test(frontend): 39 tests. The real crypto runs against a fake vetKD with a known master key; the data layer and sign-in run on ic-reactor's test client.pnpm test:pic: 4 tests of the browser code and ic-reactor clients against the real canister in PocketIC.icp deployon a local icp-cli network: guest sign-in, a note, reload, and opening a share link from a fresh browser.id.ai.localhost:8000/authorizeand cancelling is handled, but a headless browser couldn't create the passkey, so this needs a manual check.Notes for reviewers
@ic-reactor/*and@candid-core/*versions are pinned exactly.🤖 Generated with Claude Code
https://claude.ai/code/session_017mjAwRTxCWYEMCuMXgkyjS
Generated by Claude Code