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
30 changes: 24 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# B3Note

![B3Note](./frontend/public/logo-long.svg)
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./frontend/public/logo-long-dark.svg" />
<img alt="B3Note" src="./frontend/public/logo-long.svg" />
</picture>

**Private notes on the Internet Computer.** Notes are encrypted in your browser, so the canister only ever
stores ciphertext. With Internet Identity the key is derived from your identity by the IC's
Expand All @@ -19,10 +22,15 @@ that **burns after reading**.
can request the share's key, and it does so once per view. Readers do not need to sign in.
- **Sign in your way.** Use Internet Identity, or a *guest key*: a private key kept in your browser, with a
recovery key you can save and import on another device.
- **Optional AI assistant** on the IC's [LLM canister](https://github.com/dfinity/llm), **off by
default**: summarize, suggest a title or tags, improve the writing, translate, and ask questions
across your notes. Text sent to it leaves the end-to-end encryption, so a controller has to switch it on
(`ai_enabled`); until then the app shows no AI features. When on, nothing is sent until you choose an
- **On-device AI assistant**: summarize, suggest a title or tags, improve the writing, fix grammar,
shorten, pull out action items, translate, continue writing, and ask questions across your notes, with
a model that runs in your browser, so nothing leaves the end-to-end encryption. It uses Chrome's
built-in Gemini Nano when the browser has it, and otherwise downloads an open model
(Qwen2.5-1.5B-Instruct, about 1 GB, once, after you ask) and runs it on the GPU with
[WebLLM](https://github.com/mlc-ai/web-llm). Browsers with neither show no on-device AI.
- **Optional AI on the Internet Computer** through the IC's [LLM canister](https://github.com/dfinity/llm),
**off by default**. Text sent to it leaves the end-to-end encryption, so a controller has to switch it
on (`ai_enabled`); when on, it is a second choice in the assistant, nothing is sent until you choose an
action, and the app asks for consent first.
- **Your data stays yours.** Export to JSON or Markdown, import Markdown files, and delete your account.

Expand Down Expand Up @@ -60,7 +68,17 @@ that **burns after reading**.
signs `(domain, share id, transport key)`. The canister checks the signature, spends a view, and has the
share key derived and encrypted to the reader. A transport key can be used once per share, so a
replayed request cannot burn another view.
- **AI (when switched on).** The assistant needs plaintext. Only the text you pick for an action (or,
- **On-device AI.** The model reads the note in the browser that decrypted it. The only network traffic
is the model's files on first use (Hugging Face and the MLC model library on GitHub), which is why the
page's CSP allows those hosts and `'wasm-unsafe-eval'` for the model's WebAssembly. Both are pinned to a
commit, and WebLLM checks the model's config, its tokenizer and its WebAssembly library against hashes
before it runs them (`frontend/src/lib/localAi.ts`), so a push to either repository cannot change the
code that reads your notes.
- **No remote images.** Fetching an image sends its URL to its host, so a note's Markdown or an AI answer
(which a prompt hidden in a note could steer) could carry text out that way, and an image in a shared
note would tell its sender when it was opened. Markdown shows images as links instead, and the CSP's
`img-src` allows only the app itself.
- **AI on the canister (when switched on).** The assistant needs plaintext. Only the text you pick for an action (or,
for *Ask your notes*, the few notes your browser ranks as relevant) goes to the LLM canister.
Everything else stays encrypted.

Expand Down
7 changes: 4 additions & 3 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@
<title>B3Note: private notes on the Internet Computer</title>
<meta
name="description"
content="End-to-end encrypted notes on the Internet Computer, with vetKeys and burn-after-reading links."
content="End-to-end encrypted notes on the Internet Computer, with vetKeys, burn-after-reading links and on-device AI."
/>
<meta name="referrer" content="no-referrer" />
<meta name="theme-color" content="#6d4aff" />
<meta name="theme-color" content="#f6f4f0" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#121110" media="(prefers-color-scheme: dark)" />
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#6d4aff" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#121110" />
<link rel="manifest" href="/site.webmanifest" />
</head>
<body>
Expand Down
3 changes: 3 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
},
"dependencies": {
"@candid-core/schema": "0.3.0",
"@fontsource-variable/schibsted-grotesk": "5.3.0",
"@fontsource/martian-mono": "5.3.0",
"@ic-reactor/core": "4.0.0-beta.3",
"@ic-reactor/react": "4.0.0-beta.3",
"@icp-sdk/auth": "^10.0.1",
"@icp-sdk/core": "^6.1.0",
"@icp-sdk/vetkeys": "^0.7.0",
"@mlc-ai/web-llm": "0.2.85",
"@tanstack/react-query": "^5.104.1",
"@tanstack/react-router": "^1.170.41",
"idb-keyval": "^6.2.2",
Expand Down
32 changes: 32 additions & 0 deletions frontend/pnpm-lock.yaml

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

10 changes: 9 additions & 1 deletion frontend/public/.ic-assets.json5
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
// Headers for the asset canister (icp-cli asset-canister recipe).
//
// On-device AI: WebLLM compiles the model's WebAssembly ('wasm-unsafe-eval', not 'unsafe-eval') and
// downloads the model's files once from Hugging Face and the MLC model library on GitHub, both pinned
// and hash-checked (src/lib/localAi.ts). Only those files come in; no note text goes out. Chrome's
// built-in model needs none of this.
//
// img-src allows no other host: the app has no remote images, and Markdown shows a note's or an AI
// answer's images as links (src/components/Markdown.tsx), since loading one would send its URL out.
[
{
match: "**/*",
headers: {
"Content-Security-Policy": "default-src 'self'; script-src 'self'; connect-src 'self' http://localhost:* http://127.0.0.1:* https://icp0.io https://*.icp0.io https://icp-api.io https://ic0.app https://*.ic0.app; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; font-src 'self' data:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'",
"Content-Security-Policy": "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; connect-src 'self' http://localhost:* http://127.0.0.1:* https://icp0.io https://*.icp0.io https://icp-api.io https://ic0.app https://*.ic0.app https://huggingface.co https://*.huggingface.co https://*.hf.co https://raw.githubusercontent.com; img-src 'self' data:; style-src 'self' 'unsafe-inline'; font-src 'self' data:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'",
"Referrer-Policy": "no-referrer",
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
Expand Down
Binary file modified frontend/public/android-chrome-192x192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/public/android-chrome-512x512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/public/apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/public/favicon-16x16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/public/favicon-32x32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/public/favicon.ico
Binary file not shown.
1 change: 1 addition & 0 deletions frontend/public/logo-long-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading