From 977975ce766edad4130e3b2bf3d40559c875a9c6 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 16 Jul 2026 17:32:43 +0200 Subject: [PATCH 1/8] chore(vetkeys): split basic_bls_signing into self-contained motoko + rust examples Same split as basic_ibe (#1443): move the Motoko variant to motoko/vetkeys/basic_bls_signing and flatten the Rust variant to rust/vetkeys/basic_bls_signing, each self-contained with its own frontend copy (previously a shared frontend symlinked into motoko/ and rust/ subdirs). - Frontend: per-language copy; bindings via the @icp-sdk/bindgen Vite plugin from the committed backend/backend.did (replaces scripts/gen_bindings.sh); dropped eslint; root workspace package.json so `npm run dev` runs from the example root. - Motoko: idiomatic camelCase interface (signMessage, getMySignatures, getMyVerificationKey); moc 1.11.0 + --default-persistent-actors (dropped `persistent`); dropped unused sha2 dep; committed backend/backend.did. - Rust: unchanged (snake_case), relocated to the flattened layout. - READMEs split per language (linked prerequisites); test.sh added (query method called with --query); CI split into motoko + rust jobs on the new paths. Baked in the two Copilot fixes from #1443 (error message names `backend`; --query for the query method). CODEOWNERS unchanged: /motoko/vetkeys/ and /rust/vetkeys/ both map to @dfinity/core-protocol. Verified: Motoko deploy + test.sh + signature survives an upgrade; Rust cargo build + deploy + test.sh; both frontends build/typecheck/serve. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../workflows/vetkeys-basic-bls-signing.yml | 28 +- motoko/vetkeys/basic_bls_signing/README.md | 82 ++++ .../vetkeys/basic_bls_signing/backend/app.mo | 8 +- .../basic_bls_signing/backend/backend.did | 13 + .../basic_bls_signing/frontend/.gitignore | 3 + .../basic_bls_signing/frontend/index.html | 13 + .../basic_bls_signing/frontend/package.json | 20 + .../frontend/public/.ic-assets.json5 | 10 + .../basic_bls_signing/frontend/src/main.ts | 276 +++++++++++++ .../basic_bls_signing/frontend/src/style.css | 374 ++++++++++++++++++ .../frontend/src/vite-end.d.ts | 1 + .../basic_bls_signing/frontend/tsconfig.json | 25 ++ .../basic_bls_signing/frontend/vite.config.ts | 49 +++ .../vetkeys/basic_bls_signing}/icp.yaml | 9 +- motoko/vetkeys/basic_bls_signing/mops.toml | 16 + motoko/vetkeys/basic_bls_signing/package.json | 13 + motoko/vetkeys/basic_bls_signing/test.sh | 14 + .../basic_bls_signing/ui_screenshot.png | Bin 0 -> 109893 bytes .../basic_bls_signing/{rust => }/Cargo.toml | 0 rust/vetkeys/basic_bls_signing/README.md | 91 +++-- .../{rust => }/backend/Cargo.toml | 0 .../{rust => }/backend/Makefile | 0 .../{rust => }/backend/backend.did | 0 .../{rust => }/backend/src/lib.rs | 0 .../{rust => }/backend/src/types.rs | 0 .../basic_bls_signing/frontend/.gitignore | 3 + .../frontend/eslint.config.mjs | 30 -- .../basic_bls_signing/frontend/package.json | 48 +-- .../frontend/scripts/gen_bindings.sh | 21 - .../basic_bls_signing/frontend/src/main.ts | 8 +- .../basic_bls_signing/frontend/vite.config.ts | 72 ++-- .../basic_bls_signing/{rust => }/icp.yaml | 9 +- .../vetkeys/basic_bls_signing/motoko/frontend | 1 - .../basic_bls_signing/motoko/mops.toml | 10 - rust/vetkeys/basic_bls_signing/package.json | 13 + .../{rust => }/rust-toolchain.toml | 0 rust/vetkeys/basic_bls_signing/rust/frontend | 1 - rust/vetkeys/basic_bls_signing/test.sh | 14 + 38 files changed, 1072 insertions(+), 203 deletions(-) create mode 100644 motoko/vetkeys/basic_bls_signing/README.md rename rust/vetkeys/basic_bls_signing/motoko/backend/src/Main.mo => motoko/vetkeys/basic_bls_signing/backend/app.mo (92%) create mode 100644 motoko/vetkeys/basic_bls_signing/backend/backend.did create mode 100644 motoko/vetkeys/basic_bls_signing/frontend/.gitignore create mode 100644 motoko/vetkeys/basic_bls_signing/frontend/index.html create mode 100644 motoko/vetkeys/basic_bls_signing/frontend/package.json create mode 100644 motoko/vetkeys/basic_bls_signing/frontend/public/.ic-assets.json5 create mode 100644 motoko/vetkeys/basic_bls_signing/frontend/src/main.ts create mode 100644 motoko/vetkeys/basic_bls_signing/frontend/src/style.css create mode 100644 motoko/vetkeys/basic_bls_signing/frontend/src/vite-end.d.ts create mode 100644 motoko/vetkeys/basic_bls_signing/frontend/tsconfig.json create mode 100644 motoko/vetkeys/basic_bls_signing/frontend/vite.config.ts rename {rust/vetkeys/basic_bls_signing/motoko => motoko/vetkeys/basic_bls_signing}/icp.yaml (62%) create mode 100644 motoko/vetkeys/basic_bls_signing/mops.toml create mode 100644 motoko/vetkeys/basic_bls_signing/package.json create mode 100755 motoko/vetkeys/basic_bls_signing/test.sh create mode 100644 motoko/vetkeys/basic_bls_signing/ui_screenshot.png rename rust/vetkeys/basic_bls_signing/{rust => }/Cargo.toml (100%) rename rust/vetkeys/basic_bls_signing/{rust => }/backend/Cargo.toml (100%) rename rust/vetkeys/basic_bls_signing/{rust => }/backend/Makefile (100%) rename rust/vetkeys/basic_bls_signing/{rust => }/backend/backend.did (100%) rename rust/vetkeys/basic_bls_signing/{rust => }/backend/src/lib.rs (100%) rename rust/vetkeys/basic_bls_signing/{rust => }/backend/src/types.rs (100%) create mode 100644 rust/vetkeys/basic_bls_signing/frontend/.gitignore delete mode 100644 rust/vetkeys/basic_bls_signing/frontend/eslint.config.mjs delete mode 100755 rust/vetkeys/basic_bls_signing/frontend/scripts/gen_bindings.sh rename rust/vetkeys/basic_bls_signing/{rust => }/icp.yaml (70%) delete mode 120000 rust/vetkeys/basic_bls_signing/motoko/frontend delete mode 100644 rust/vetkeys/basic_bls_signing/motoko/mops.toml create mode 100644 rust/vetkeys/basic_bls_signing/package.json rename rust/vetkeys/basic_bls_signing/{rust => }/rust-toolchain.toml (100%) delete mode 120000 rust/vetkeys/basic_bls_signing/rust/frontend create mode 100755 rust/vetkeys/basic_bls_signing/test.sh diff --git a/.github/workflows/vetkeys-basic-bls-signing.yml b/.github/workflows/vetkeys-basic-bls-signing.yml index e38e0b617d..1448957545 100644 --- a/.github/workflows/vetkeys-basic-bls-signing.yml +++ b/.github/workflows/vetkeys-basic-bls-signing.yml @@ -6,6 +6,7 @@ on: - master pull_request: paths: + - motoko/vetkeys/basic_bls_signing/** - rust/vetkeys/basic_bls_signing/** - .github/workflows/vetkeys-basic-bls-signing.yml @@ -14,23 +15,30 @@ concurrency: cancel-in-progress: true jobs: - rust: + motoko: runs-on: ubuntu-24.04 - container: ghcr.io/dfinity/icp-dev-env-rust:1.0.1 + container: ghcr.io/dfinity/icp-dev-env-motoko:1.0.1 env: ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - name: Deploy Basic Bls Signing Rust - working-directory: rust/vetkeys/basic_bls_signing/rust - run: icp network start -d && icp deploy - motoko: + - name: Deploy and test + working-directory: motoko/vetkeys/basic_bls_signing + run: | + icp network start -d + icp deploy + bash test.sh + + rust: runs-on: ubuntu-24.04 - container: ghcr.io/dfinity/icp-dev-env-motoko:1.0.1 + container: ghcr.io/dfinity/icp-dev-env-rust:1.0.1 env: ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - name: Deploy Basic Bls Signing Motoko - working-directory: rust/vetkeys/basic_bls_signing/motoko - run: icp network start -d && icp deploy + - name: Deploy and test + working-directory: rust/vetkeys/basic_bls_signing + run: | + icp network start -d + icp deploy + bash test.sh diff --git a/motoko/vetkeys/basic_bls_signing/README.md b/motoko/vetkeys/basic_bls_signing/README.md new file mode 100644 index 0000000000..94fbfe1eba --- /dev/null +++ b/motoko/vetkeys/basic_bls_signing/README.md @@ -0,0 +1,82 @@ +# Threshold BLS Signatures (Motoko) + +[View this sample's code on GitHub](https://github.com/dfinity/examples/tree/master/motoko/vetkeys/basic_bls_signing) + +Also available in: [Rust](../../../rust/vetkeys/basic_bls_signing) + +The **Basic BLS signing** example demonstrates how to use **[VetKeys](https://docs.internetcomputer.org/concepts/vetkeys)** to implement a threshold BLS signing service on the **Internet Computer (IC)**, where every authenticated user can ask the canister (IC smart contract) to produce signatures, with the **Internet Identity Principal** identifying the signer. The canister ensures a user can only produce signatures for their own principal, not for someone else's. Furthermore, the vetKeys in this dapp can only be produced upon a user request, as specified in the canister code — the canister cannot produce signatures for arbitrary users or messages. + +To confirm the canister can only produce signatures in the intended way, users need to inspect the code installed in the canister. For this, it is crucial that canisters using VetKeys have their code public. + +![UI Screenshot](ui_screenshot.png) + +## Features + +- **Signer Authorization**: Only authorized users can produce signatures, and only for their own identity. +- **Frontend Signature Verification**: Any user can publish a signature from their principal in the canister storage, and the frontend automatically checks its validity. + +## Build and deploy from the command line + +### Prerequisites + +- Install [Node.js](https://nodejs.org/en/download/) +- Install [icp-cli](https://cli.internetcomputer.org): `npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm` +- Install [ic-mops](https://mops.one): `npm install -g ic-mops` + +### (Optionally) choose a different master key + +This example uses `test_key_1` by default. To use a different [available master key](https://docs.internetcomputer.org/concepts/vetkeys/#api-overview), change the `init_args` value in `icp.yaml` before deploying. + +### Install + +```bash +git clone https://github.com/dfinity/examples +cd examples/motoko/vetkeys/basic_bls_signing +``` + +### Deploy + +```bash +icp network start -d +icp deploy +``` + +Open the frontend URL printed by `icp deploy`. + +To run the frontend in development mode with hot reloading (after `icp deploy`): + +```bash +npm run dev +``` + +When done, stop the local network to free up the port for other projects: + +```bash +icp network stop +``` + +## Example components + +### Backend (`backend/`) + +A single Motoko canister that: +- Produces BLS signatures upon a user request. +- Lets users retrieve the public key used to verify their signatures. +- Lets users store signatures (real or fake) in a log data structure. + +### Frontend (`frontend/`) + +A vanilla TypeScript application providing a simple interface for signing, showing the signatures stored in the canister, and verifying a signature. Canister bindings are generated from `backend/backend.did` at build time by the `@icp-sdk/bindgen` Vite plugin. + +## Updating the Candid interface + +`backend/backend.did` defines the backend's public interface; the frontend bindings are generated from it during the build. If you change the backend's public API, regenerate it: + +```bash +mops generate candid backend +``` + +## Additional resources + +- **[What are VetKeys](https://docs.internetcomputer.org/concepts/vetkeys)** — more information about VetKeys and VetKD. +- [Security best practices](https://docs.internetcomputer.org/guides/security/overview) diff --git a/rust/vetkeys/basic_bls_signing/motoko/backend/src/Main.mo b/motoko/vetkeys/basic_bls_signing/backend/app.mo similarity index 92% rename from rust/vetkeys/basic_bls_signing/motoko/backend/src/Main.mo rename to motoko/vetkeys/basic_bls_signing/backend/app.mo index 1d126eb386..560bcad9de 100644 --- a/rust/vetkeys/basic_bls_signing/motoko/backend/src/Main.mo +++ b/motoko/vetkeys/basic_bls_signing/backend/app.mo @@ -10,7 +10,7 @@ import Nat8 "mo:core/Nat8"; import VetKeys "mo:ic-vetkeys"; import Order "mo:core/Order"; -shared persistent actor class (keyName : Text) = { +shared actor class (keyName : Text) = { // Types type Signature = { message : Text; @@ -71,7 +71,7 @@ shared persistent actor class (keyName : Text) = { }; // Sign a message using BLS - public shared ({ caller }) func sign_message(message : Text) : async Blob { + public shared ({ caller }) func signMessage(message : Text) : async Blob { let signatureBytes = await VetKeys.ManagementCanister.signWithBls( Text.encodeUtf8(message), context(caller), @@ -97,7 +97,7 @@ shared persistent actor class (keyName : Text) = { }; // Get all signatures for the current caller - public shared query ({ caller }) func get_my_signatures() : async [Signature] { + public shared query ({ caller }) func getMySignatures() : async [Signature] { var callerSignatures = List.empty(); for ((key, value) in Map.entries(signatures)) { @@ -110,7 +110,7 @@ shared persistent actor class (keyName : Text) = { }; // Get verification key for the current caller - public shared ({ caller }) func get_my_verification_key() : async Blob { + public shared ({ caller }) func getMyVerificationKey() : async Blob { await VetKeys.ManagementCanister.blsPublicKey( null, context(caller), diff --git a/motoko/vetkeys/basic_bls_signing/backend/backend.did b/motoko/vetkeys/basic_bls_signing/backend/backend.did new file mode 100644 index 0000000000..15c0da0814 --- /dev/null +++ b/motoko/vetkeys/basic_bls_signing/backend/backend.did @@ -0,0 +1,13 @@ +type _anon_class_13_1 = + service { + getMySignatures: () -> (vec Signature) query; + getMyVerificationKey: () -> (blob); + signMessage: (message: text) -> (blob); + }; +type Signature = + record { + message: text; + signature: blob; + timestamp: nat64; + }; +service : (keyName: text) -> _anon_class_13_1 diff --git a/motoko/vetkeys/basic_bls_signing/frontend/.gitignore b/motoko/vetkeys/basic_bls_signing/frontend/.gitignore new file mode 100644 index 0000000000..061e2e66e1 --- /dev/null +++ b/motoko/vetkeys/basic_bls_signing/frontend/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +src/bindings/ diff --git a/motoko/vetkeys/basic_bls_signing/frontend/index.html b/motoko/vetkeys/basic_bls_signing/frontend/index.html new file mode 100644 index 0000000000..7240c1828a --- /dev/null +++ b/motoko/vetkeys/basic_bls_signing/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + VetKeys: Basic BLS Signing + + +
+ + + diff --git a/motoko/vetkeys/basic_bls_signing/frontend/package.json b/motoko/vetkeys/basic_bls_signing/frontend/package.json new file mode 100644 index 0000000000..0a68d864e4 --- /dev/null +++ b/motoko/vetkeys/basic_bls_signing/frontend/package.json @@ -0,0 +1,20 @@ +{ + "name": "frontend", + "private": true, + "type": "module", + "scripts": { + "prebuild": "npm i --include=dev", + "build": "vite build", + "dev": "vite" + }, + "dependencies": { + "@icp-sdk/auth": "^7.1.0", + "@icp-sdk/core": "^5.4.0", + "@icp-sdk/vetkeys": "^0.5.0-beta.0" + }, + "devDependencies": { + "@icp-sdk/bindgen": "~0.2.2", + "typescript": "~5.7.2", + "vite": "^6.4.1" + } +} diff --git a/motoko/vetkeys/basic_bls_signing/frontend/public/.ic-assets.json5 b/motoko/vetkeys/basic_bls_signing/frontend/public/.ic-assets.json5 new file mode 100644 index 0000000000..58968d2884 --- /dev/null +++ b/motoko/vetkeys/basic_bls_signing/frontend/public/.ic-assets.json5 @@ -0,0 +1,10 @@ +[ + { + match: "**/*", + security_policy: "hardened", + headers: { + "Content-Security-Policy": "default-src 'self';script-src 'self';connect-src 'self' http://localhost:* https://icp0.io https://*.icp0.io https://icp-api.io;img-src 'self';object-src 'none';base-uri 'self';frame-ancestors 'none';form-action 'self';upgrade-insecure-requests;", + }, + allow_raw_access: false + }, +] diff --git a/motoko/vetkeys/basic_bls_signing/frontend/src/main.ts b/motoko/vetkeys/basic_bls_signing/frontend/src/main.ts new file mode 100644 index 0000000000..18c7c4de4d --- /dev/null +++ b/motoko/vetkeys/basic_bls_signing/frontend/src/main.ts @@ -0,0 +1,276 @@ +import "./style.css"; +import { Principal } from "@icp-sdk/core/principal"; +import { AuthClient, LocalStorage } from "@icp-sdk/auth/client"; +import { HttpAgent } from "@icp-sdk/core/agent"; +import { createActor, type Backend, type Signature } from "./bindings/backend"; +import { DerivedPublicKey, verifyBlsSignature } from "@icp-sdk/vetkeys"; +import { safeGetCanisterEnv } from "@icp-sdk/core/agent/canister-env"; + +const canisterEnv = safeGetCanisterEnv<{ + "PUBLIC_CANISTER_ID:backend": string; +}>(); + +let myPrincipal: Principal | undefined = undefined; +let authClient: AuthClient | undefined; +let basicBlsSigningActor: Backend | undefined; +// let canisterPublicKey: DerivedPublicKey | undefined; +let myVerificationKey: DerivedPublicKey | undefined; + +async function getBasicBlsSigningActor(): Promise { + if (basicBlsSigningActor) return basicBlsSigningActor; + const canisterId = canisterEnv?.["PUBLIC_CANISTER_ID:backend"]; + if (!canisterId) { + throw Error("Canister ID for backend is not set"); + } + if (!authClient) { + throw Error("Auth client is not initialized"); + } + const agent = await HttpAgent.create({ + identity: await authClient.getIdentity(), + host: window.location.origin, + rootKey: canisterEnv?.IC_ROOT_KEY, + }); + basicBlsSigningActor = createActor(canisterId, { agent }); + return basicBlsSigningActor; +} + +export async function login(client: AuthClient): Promise { + try { + const identity = await client.signIn({ + maxTimeToLive: BigInt(1800) * BigInt(1_000_000_000), + }); + myPrincipal = identity.getPrincipal(); + updateUI(true); + } catch (error: unknown) { + alert("Authentication failed: " + error); + } +} + +export function logout() { + void authClient?.signOut(); + myPrincipal = undefined; + myVerificationKey = undefined; + basicBlsSigningActor = undefined; + updateUI(false); + document.getElementById("signaturesList")!.classList.toggle("hidden", true); +} + +async function initAuth() { + const isLocalEnv = + window.location.hostname === "localhost" || + window.location.hostname.endsWith(".localhost"); + // Workaround for https://github.com/dfinity/icp-js-auth/issues/120 + // IdbStorage has a race condition on localhost dev servers. LocalStorage + // avoids IDB on local but uses plain string storage (less secure), so + // production deployments keep the default secure IdbStorage + ECDSA key. + authClient = new AuthClient({ + identityProvider: isLocalEnv + ? "http://id.ai.localhost:8000/authorize" + : "https://id.ai/authorize", + ...(isLocalEnv ? { storage: new LocalStorage(), keyType: "Ed25519" as const } : {}), + }); + const isAuthenticated = authClient.isAuthenticated(); + + if (isAuthenticated) { + myPrincipal = (await authClient.getIdentity()).getPrincipal(); + updateUI(true); + } else { + updateUI(false); + } +} + +function updateUI(isAuthenticated: boolean) { + const loginButton = document.getElementById("loginButton")!; + const principalDisplay = document.getElementById("principalDisplay")!; + const logoutButton = document.getElementById("logoutButton")!; + const signingActions = document.getElementById("signingActions")!; + const customSignatureForm = document.getElementById("customSignatureForm")!; + const signaturesList = document.getElementById("signaturesList")!; + + loginButton.classList.toggle("hidden", isAuthenticated); + principalDisplay.classList.toggle("hidden", !isAuthenticated); + logoutButton.classList.toggle("hidden", !isAuthenticated); + signingActions.classList.toggle("hidden", !isAuthenticated); + customSignatureForm.classList.toggle("hidden", true); + signaturesList.classList.toggle("hidden", true); + + if (isAuthenticated && myPrincipal) { + principalDisplay.textContent = `Principal: ${myPrincipal.toString()}`; + } +} + +function handleLogin() { + if (!authClient) { + alert("Auth client not initialized"); + return; + } + void login(authClient); +} + +document.querySelector("#app")!.innerHTML = ` +
+

Basic BLS Signing using VetKeys

+
+
+ +
+ +
+ + + +
+
+

Verify Custom Signature

+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+

My Signatures

+
+
+
+`; + +// Add event listeners +document.getElementById("loginButton")!.addEventListener("click", handleLogin); +document.getElementById("logoutButton")!.addEventListener("click", logout); +document.getElementById("signMessageButton")!.addEventListener("click", () => { + void (async () => { + const message = prompt("Enter message to sign:"); + if (message) { + try { + await (await getBasicBlsSigningActor()).signMessage(message); + alert("Created and stored signature successfully."); + } catch (error) { + alert(`Error: ${error as Error}`); + } + } + })(); +}); + +document + .getElementById("customSignatureButton")! + .addEventListener("click", () => { + document + .getElementById("customSignatureForm")! + .classList.toggle("hidden", false); + document.getElementById("signaturesList")!.classList.toggle("hidden", true); + }); + +document + .getElementById("listSignaturesButton")! + .addEventListener("click", () => { + void listSignatures(); + }); + +document + .getElementById("submitSignatureForm")! + .addEventListener("submit", (e) => { + e.preventDefault(); + const message = (document.getElementById("message") as HTMLInputElement) + .value; + const signatureHex = ( + document.getElementById("signature") as HTMLInputElement + ).value; + const pubkeyHex = (document.getElementById("pubkey") as HTMLInputElement) + .value; + const messageBytes = new TextEncoder().encode(message); + + try { + const signatureBytes = new Uint8Array( + signatureHex.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16)), + ); + const pubkeyBytes = new Uint8Array( + pubkeyHex.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16)), + ); + + const verificationKey = DerivedPublicKey.deserialize(pubkeyBytes); + + const result = verifyBlsSignature( + verificationKey, + messageBytes, + signatureBytes, + ); + alert(`Verification result: ${result ? "Valid" : "INVALID"}`); + } catch { + alert("Verification failed."); + } + }); + +async function listSignatures() { + const actor = await getBasicBlsSigningActor(); + const signatures: Array = await actor.getMySignatures(); + const signaturesDiv = document.getElementById("signatures")!; + signaturesDiv.innerHTML = ""; + + if (signatures.length === 0) { + signaturesDiv.innerHTML = ` +
+

No signatures have been published yet.

+
+ `; + } else { + if (!myVerificationKey) { + const myVerificationKeyRaw = await actor.getMyVerificationKey(); + myVerificationKey = DerivedPublicKey.deserialize( + Uint8Array.from(myVerificationKeyRaw), + ); + } + const myVerificationKeyHex = Array.from(myVerificationKey.publicKeyBytes()) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); + + for (const signatureData of signatures.slice().reverse()) { + const signatureHex = Array.from(signatureData.signature) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); + + // Convert nanoseconds to milliseconds and create a Date object + const timestamp = new Date(Number(signatureData.timestamp) / 1_000_000); + const formattedDate = timestamp.toLocaleString(); + + const signatureElement = document.createElement("div"); + signatureElement.className = "signature"; + + const isValid = verifyBlsSignature( + myVerificationKey, + new TextEncoder().encode(signatureData.message), + Uint8Array.from(signatureData.signature), + ); + + signatureElement.innerHTML = ` +
Signed message: ${signatureData.message}
+

Signature: ${signatureHex}

+

Public key: ${myVerificationKeyHex}

+

Verification: ${isValid ? "Valid" : "Invalid"}

+

Added: ${formattedDate}

+ `; + + signaturesDiv.appendChild(signatureElement); + } + } + + document.getElementById("signaturesList")!.classList.toggle("hidden", false); + document + .getElementById("customSignatureForm")! + .classList.toggle("hidden", true); +} + +// Initialize auth +void initAuth(); diff --git a/motoko/vetkeys/basic_bls_signing/frontend/src/style.css b/motoko/vetkeys/basic_bls_signing/frontend/src/style.css new file mode 100644 index 0000000000..6322015180 --- /dev/null +++ b/motoko/vetkeys/basic_bls_signing/frontend/src/style.css @@ -0,0 +1,374 @@ +/* Base styles */ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +h1 { + font-size: 2.2em; + line-height: 1.1; + margin-bottom: 2rem; +} + +h3 { + color: var(--text-color); + font-size: 1.5rem; + margin-bottom: 1.5rem; + font-weight: 600; +} + +h4 { + color: var(--text-color); + font-size: 1.25rem; + margin-bottom: 1rem; + font-weight: 600; +} + +h5 { + color: var(--text-color); + font-size: 1.1rem; + margin-bottom: 0.5rem; + font-weight: 600; +} + +/* Principal container */ +.principal-container { + display: flex; + align-items: center; + gap: 1rem; + margin-bottom: 1rem; + justify-content: center; +} + +.principal-display { + font-family: monospace; + background-color: rgba(0, 0, 0, 0.2); + padding: 0.5rem 1rem; + border-radius: 8px; + color: #a8a6a6; + white-space: pre-wrap; + word-break: break-all; + max-width: 600px; +} + +/* Buttons */ +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} + +button:hover { + border-color: #646cff; +} + +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +#loginButton { + background-color: #28a745; + color: white; + min-width: 120px; +} + +#loginButton:hover { + background-color: #218838; +} + +#logoutButton { + background-color: #dc3545; + color: white; +} + +#logoutButton:hover { + background-color: #c82333; +} + +/* Signing actions */ +#signingActions { + display: flex; + gap: 1rem; + justify-content: center; + margin: 2rem 0; +} + +#signingActions button { + background-color: #1a1a1a; + color: white; +} + +#signingActions button:hover { + background-color: #2a2a2a; +} + +/* Custom signature form */ +#customSignatureForm { + max-width: 600px; + margin: 2rem auto; + padding: 2rem 3rem; + background-color: #1a1a1a; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); +} + +#customSignatureForm div { + margin-bottom: 1.5rem; +} + +#customSignatureForm label { + display: block; + margin-bottom: 0.5rem; + font-weight: 500; + color: rgba(255, 255, 255, 0.87); +} + +#customSignatureForm input { + width: 100%; + padding: 0.75rem; + background-color: #242424; + border: 1px solid #333; + border-radius: 8px; + font-size: 1rem; +} + +#customSignatureForm input:focus { + outline: none; + border-color: #646cff; +} + +#customSignatureForm button { + width: 100%; + background-color: #28a745; + color: white; +} + +#customSignatureForm button:hover { + background-color: #218838; +} + +/* Signatures list */ +#signaturesList { + margin: 2rem 0; +} + +.signature { + margin: 1.5rem 0; + padding: 1.5rem; + background-color: #1a1a1a; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + transition: transform 0.2s ease; + position: relative; +} + +.signature:hover { + transform: translateY(-2px); +} + +.signature h5 { + margin: 0 0 1rem 0; + color: rgba(255, 255, 255, 0.87); +} + +.signature p { + margin: 0.5rem 0; + color: #a8a6a6; +} + +.signature-hex { + font-family: monospace; + word-break: break-all; + background-color: rgba(0, 0, 0, 0.2); + padding: 0.5rem; + border-radius: 4px; +} + +.verification-key-hex { + font-family: monospace; + word-break: break-all; + background-color: rgba(0, 0, 0, 0.2); + padding: 0.5rem; + border-radius: 4px; +} + +.verification-status { + font-weight: 500; +} + +.verification-status.valid { + color: #2ecc71; + font-weight: bold; +} + +.verification-status.invalid { + color: #e03926; + font-weight: bold; +} + +.principal { + font-family: monospace; + color: #a8a6a6; +} + +.login-container { + display: flex; + justify-content: center; + margin: 2rem 0; + width: 100%; +} + +/* Responsive design */ +@media (max-width: 768px) { + #app { + padding: 1rem; + } + + h1 { + font-size: 1.8em; + } + + .principal-container { + flex-direction: column; + } + + #signingActions { + flex-direction: column; + } +} + +/* Light theme */ +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + button { + background-color: #f9f9f9; + } + + .principal-display { + background-color: rgba(0, 0, 0, 0.05); + } + + #customSignatureForm { + background-color: #f9f9f9; + } + + #customSignatureForm label { + color: #213547; + } + + #customSignatureForm input { + background-color: #ffffff; + border-color: #ddd; + } + + .signature { + background-color: #f9f9f9; + } + + .signature h5 { + color: #213547; + } + + .signature p { + color: #666; + } + + .signature-hex { + background-color: rgba(0, 0, 0, 0.05); + } + + .verification-key-hex { + background-color: rgba(0, 0, 0, 0.05); + } + + .principal { + color: #666; + } +} + +.no-signatures { + text-align: center; + padding: 2rem; + background-color: #1a1a1a; + border-radius: 8px; + margin: 1rem 0; +} + +.no-signatures p { + color: #a8a6a6; + font-size: 1.1rem; + margin: 0; +} + +.timestamp { + color: #a8a6a6; + font-size: 0.9rem; + margin-top: 0.5rem; + font-style: italic; +} + +/* Auth state classes */ +.hidden { + display: none !important; +} + +/* Initial state classes for auth elements */ +#loginButton { + display: block; +} + +#signingActions { + display: flex; +} + +#principalDisplay { + display: block; +} + +#logoutButton { + display: block; +} + +#customSignatureForm { + display: block; +} + +#signaturesList { + display: block; +} \ No newline at end of file diff --git a/motoko/vetkeys/basic_bls_signing/frontend/src/vite-end.d.ts b/motoko/vetkeys/basic_bls_signing/frontend/src/vite-end.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/motoko/vetkeys/basic_bls_signing/frontend/src/vite-end.d.ts @@ -0,0 +1 @@ +/// diff --git a/motoko/vetkeys/basic_bls_signing/frontend/tsconfig.json b/motoko/vetkeys/basic_bls_signing/frontend/tsconfig.json new file mode 100644 index 0000000000..55cf3a6efa --- /dev/null +++ b/motoko/vetkeys/basic_bls_signing/frontend/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] + } + \ No newline at end of file diff --git a/motoko/vetkeys/basic_bls_signing/frontend/vite.config.ts b/motoko/vetkeys/basic_bls_signing/frontend/vite.config.ts new file mode 100644 index 0000000000..cd9cb43bc1 --- /dev/null +++ b/motoko/vetkeys/basic_bls_signing/frontend/vite.config.ts @@ -0,0 +1,49 @@ +import { defineConfig } from "vite"; +import { execSync } from "child_process"; +import { icpBindgen } from "@icp-sdk/bindgen/plugins/vite"; + +function getDevServerConfig() { + try { + const canisterId = execSync("icp canister status backend -e local -i", { + encoding: "utf-8", + stdio: "pipe", + }).trim(); + const networkStatus = JSON.parse( + execSync("icp network status --json", { + encoding: "utf-8", + stdio: "pipe", + }) + ); + return { + headers: { + "Set-Cookie": `ic_env=${encodeURIComponent( + `ic_root_key=${networkStatus.root_key}&PUBLIC_CANISTER_ID:backend=${canisterId}` + )}; SameSite=Lax;`, + }, + proxy: { + "/api": { target: "http://127.0.0.1:8000", changeOrigin: true }, + }, + }; + } catch {} + + throw new Error( + "No local network running. Start with:\n icp network start -d && icp deploy" + ); +} + +export default defineConfig(({ command }) => ({ + base: "./", + plugins: [ + icpBindgen({ + didFile: "../backend/backend.did", + outDir: "./src/bindings", + }), + ], + optimizeDeps: { + esbuildOptions: { define: { global: "globalThis" } }, + }, + build: { + sourcemap: true, + }, + server: command === "serve" ? getDevServerConfig() : undefined, +})); diff --git a/rust/vetkeys/basic_bls_signing/motoko/icp.yaml b/motoko/vetkeys/basic_bls_signing/icp.yaml similarity index 62% rename from rust/vetkeys/basic_bls_signing/motoko/icp.yaml rename to motoko/vetkeys/basic_bls_signing/icp.yaml index a68c4b5ea8..ba5979ac3f 100644 --- a/rust/vetkeys/basic_bls_signing/motoko/icp.yaml +++ b/motoko/vetkeys/basic_bls_signing/icp.yaml @@ -1,18 +1,19 @@ canisters: - - name: basic_bls_signing + - name: backend recipe: type: "@dfinity/motoko@v5.0.0" init_args: type: text value: "(\"test_key_1\")" - - name: www + - name: frontend recipe: type: "@dfinity/asset-canister@v2.2.1" configuration: - dir: dist + dir: frontend/dist build: - - cd frontend && npm i --include=dev && npm run build && cd - && rm -rf dist; mv frontend/dist ./ + - npm install --prefix frontend + - npm run build --prefix frontend networks: - name: local diff --git a/motoko/vetkeys/basic_bls_signing/mops.toml b/motoko/vetkeys/basic_bls_signing/mops.toml new file mode 100644 index 0000000000..5dae136934 --- /dev/null +++ b/motoko/vetkeys/basic_bls_signing/mops.toml @@ -0,0 +1,16 @@ +[toolchain] +moc = "1.11.0" + +[dependencies] +core = "2.5.0" +ic-vetkeys = "0.5.0" + +[moc] +# M0236: use context dot notation +# M0237: redundant explicit implicit arguments +# M0223: redundant type instantiation +args = ["--default-persistent-actors", "-W=M0236,M0237,M0223"] + +[canisters.backend] +main = "backend/app.mo" +candid = "backend/backend.did" diff --git a/motoko/vetkeys/basic_bls_signing/package.json b/motoko/vetkeys/basic_bls_signing/package.json new file mode 100644 index 0000000000..13c11af71a --- /dev/null +++ b/motoko/vetkeys/basic_bls_signing/package.json @@ -0,0 +1,13 @@ +{ + "name": "basic_bls_signing", + "private": true, + "type": "module", + "scripts": { + "build": "npm run build --workspaces --if-present", + "prebuild": "npm run prebuild --workspaces --if-present", + "dev": "npm run dev --workspaces --if-present" + }, + "workspaces": [ + "frontend" + ] +} diff --git a/motoko/vetkeys/basic_bls_signing/test.sh b/motoko/vetkeys/basic_bls_signing/test.sh new file mode 100755 index 0000000000..b446d6697d --- /dev/null +++ b/motoko/vetkeys/basic_bls_signing/test.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -e + +echo "--- Testing basic_bls_signing: verification key ---" +result=$(icp canister call backend getMyVerificationKey '()') && \ + echo "$result" | head -1 && \ + echo "$result" | grep -q 'blob' && \ + echo "PASS" || (echo "FAIL" && exit 1) + +echo "--- Testing basic_bls_signing: my signatures (query) ---" +result=$(icp canister call backend getMySignatures '()' --query) && \ + echo "$result" && \ + echo "$result" | grep -q 'vec' && \ + echo "PASS" || (echo "FAIL" && exit 1) diff --git a/motoko/vetkeys/basic_bls_signing/ui_screenshot.png b/motoko/vetkeys/basic_bls_signing/ui_screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..a59025e841438525856312045612fb7cead31fa9 GIT binary patch literal 109893 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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%0A}<+BEZVef1~Ccs?|q2w7$d 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 v-zANo*BX4e - +[View this sample's code on GitHub](https://github.com/dfinity/examples/tree/master/rust/vetkeys/basic_bls_signing) -The **Basic BLS signing** example demonstrates how to use **[vetKeys](https://docs.internetcomputer.org/concepts/vetkeys)** to implement a threshold BLS signing service on the **Internet Computer (IC)**, where every authenticated user can ask the canister (IC smart contract) to produce signatures, where the **Internet Identity Principal** identifies the signer. This canister ensures that users can only produce signature for their own principal and not for someone else's principal. Furthermore, the vetKeys in this dapp can only be produced upon a user request, as specified in the canister code, meaning that the canister cannot produce signatures for arbitrary users or messages. +Also available in: [Motoko](../../../motoko/vetkeys/basic_bls_signing) -For confirming that the canister can only produce signatures in the intended way, users need to inspect the code installed in the canister. For this, it is crucial that canisters using VetKeys have their code public. +The **Basic BLS signing** example demonstrates how to use **[VetKeys](https://docs.internetcomputer.org/concepts/vetkeys)** to implement a threshold BLS signing service on the **Internet Computer (IC)**, where every authenticated user can ask the canister (IC smart contract) to produce signatures, with the **Internet Identity Principal** identifying the signer. The canister ensures a user can only produce signatures for their own principal, not for someone else's. Furthermore, the vetKeys in this dapp can only be produced upon a user request, as specified in the canister code — the canister cannot produce signatures for arbitrary users or messages. + +To confirm the canister can only produce signatures in the intended way, users need to inspect the code installed in the canister. For this, it is crucial that canisters using VetKeys have their code public. ![UI Screenshot](ui_screenshot.png) ## Features -- **Signer Authorization**: Only authorized users can produce signatures and only for their own identity. -- **Frontend Signature Verification**: Any user can publish any signature from their principal in the canister storage and the frontend automatically checks the signature validity. +- **Signer Authorization**: Only authorized users can produce signatures, and only for their own identity. +- **Frontend Signature Verification**: Any user can publish a signature from their principal in the canister storage, and the frontend automatically checks its validity. -## Setup +## Build and deploy from the command line ### Prerequisites -- [ICP CLI](https://cli.internetcomputer.org) -- [npm](https://www.npmjs.com/package/npm) - -### (Optionally) Choose a Different Master Key +- Install [Node.js](https://nodejs.org/en/download/) +- Install [icp-cli](https://cli.internetcomputer.org): `npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm` +- Install the [Rust toolchain](https://www.rust-lang.org/tools/install), then add the WASM target: `rustup target add wasm32-unknown-unknown` -This example uses `test_key_1` by default. To use a different [available master key](https://docs.internetcomputer.org/concepts/vetkeys/#api-overview), change the `value: "(\"test_key_1\")"` line in `icp.yaml` to the desired key before running `icp deploy` in the next step. +### (Optionally) choose a different master key -### Folder Structure +This example uses `test_key_1` by default. To use a different [available master key](https://docs.internetcomputer.org/concepts/vetkeys/#api-overview), change the `init_args` value in `icp.yaml` before deploying. -This example provides both a **Rust** and a **Motoko** backend, sharing a common `frontend/`: +### Install -``` -basic_bls_signing/ -├── frontend/ ← shared frontend (symlinked into rust/ and motoko/) -├── motoko/ ← Motoko backend + icp.yaml -└── rust/ ← Rust backend + icp.yaml +```bash +git clone https://github.com/dfinity/examples +cd examples/rust/vetkeys/basic_bls_signing ``` -### Deploy the Canisters Locally +### Deploy -Deploy with the **Motoko** backend: ```bash -cd motoko -icp network start -d && icp deploy +icp network start -d +icp deploy ``` -Or deploy with the **Rust** backend: +Open the frontend URL printed by `icp deploy`. + +To run the frontend in development mode with hot reloading (after `icp deploy`): + ```bash -cd rust -icp network start -d && icp deploy +npm run dev ``` -When finished, stop the local network: +When done, stop the local network to free up the port for other projects: + ```bash icp network stop ``` -## Example Components +## Example components + +### Backend (`backend/`) + +A single Rust canister that: +- Produces BLS signatures upon a user request. +- Lets users retrieve the public key used to verify their signatures. +- Lets users store signatures (real or fake) in a log data structure. -### Backend +### Frontend (`frontend/`) -The backend consists of a canister that: -* Produces signatures upon a user request. -* Allows users to retrieve the root public key that can be used to check any user's signature for this canister. -* Allows users to store signatures (real or fake) in a log datastructure. +A vanilla TypeScript application providing a simple interface for signing, showing the signatures stored in the canister, and verifying a signature. Canister bindings are generated from `backend/backend.did` at build time by the `@icp-sdk/bindgen` Vite plugin. -### Frontend +## Updating the Candid interface -The frontend is a vanilla typescript application providing a simple interface for signing, showing the signatures stored in the canister, and publishing a signature. +`backend/backend.did` defines the backend's public interface; the frontend bindings are generated from it during the build. If you change the backend's public API, regenerate it: -To run the frontend in development mode with hot reloading (after running `icp deploy`): ```bash -cd frontend -npm run dev:rust # if you deployed the Rust backend -# or -npm run dev:motoko # if you deployed the Motoko backend +cd backend && make extract-candid ``` -## Additional Resources +## Additional resources -- **[What are VetKeys](https://docs.internetcomputer.org/concepts/vetkeys)** - For more information about VetKeys and VetKD. +- **[What are VetKeys](https://docs.internetcomputer.org/concepts/vetkeys)** — more information about VetKeys and VetKD. +- [Security best practices](https://docs.internetcomputer.org/guides/security/overview) diff --git a/rust/vetkeys/basic_bls_signing/rust/backend/Cargo.toml b/rust/vetkeys/basic_bls_signing/backend/Cargo.toml similarity index 100% rename from rust/vetkeys/basic_bls_signing/rust/backend/Cargo.toml rename to rust/vetkeys/basic_bls_signing/backend/Cargo.toml diff --git a/rust/vetkeys/basic_bls_signing/rust/backend/Makefile b/rust/vetkeys/basic_bls_signing/backend/Makefile similarity index 100% rename from rust/vetkeys/basic_bls_signing/rust/backend/Makefile rename to rust/vetkeys/basic_bls_signing/backend/Makefile diff --git a/rust/vetkeys/basic_bls_signing/rust/backend/backend.did b/rust/vetkeys/basic_bls_signing/backend/backend.did similarity index 100% rename from rust/vetkeys/basic_bls_signing/rust/backend/backend.did rename to rust/vetkeys/basic_bls_signing/backend/backend.did diff --git a/rust/vetkeys/basic_bls_signing/rust/backend/src/lib.rs b/rust/vetkeys/basic_bls_signing/backend/src/lib.rs similarity index 100% rename from rust/vetkeys/basic_bls_signing/rust/backend/src/lib.rs rename to rust/vetkeys/basic_bls_signing/backend/src/lib.rs diff --git a/rust/vetkeys/basic_bls_signing/rust/backend/src/types.rs b/rust/vetkeys/basic_bls_signing/backend/src/types.rs similarity index 100% rename from rust/vetkeys/basic_bls_signing/rust/backend/src/types.rs rename to rust/vetkeys/basic_bls_signing/backend/src/types.rs diff --git a/rust/vetkeys/basic_bls_signing/frontend/.gitignore b/rust/vetkeys/basic_bls_signing/frontend/.gitignore new file mode 100644 index 0000000000..061e2e66e1 --- /dev/null +++ b/rust/vetkeys/basic_bls_signing/frontend/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +src/bindings/ diff --git a/rust/vetkeys/basic_bls_signing/frontend/eslint.config.mjs b/rust/vetkeys/basic_bls_signing/frontend/eslint.config.mjs deleted file mode 100644 index 39313b167a..0000000000 --- a/rust/vetkeys/basic_bls_signing/frontend/eslint.config.mjs +++ /dev/null @@ -1,30 +0,0 @@ -// @ts-check - -import eslint from "@eslint/js"; -import tseslint from "typescript-eslint"; -import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"; - -export default tseslint.config( - eslint.configs.recommended, - tseslint.configs.recommendedTypeChecked, - eslintPluginPrettierRecommended, - { - languageOptions: { - parserOptions: { - project: true, - tsconfigRootDir: import.meta.dirname, - }, - }, - }, - { - ignores: [ - "dist/", - "src/declarations", - "coverage/", - "*.config.js", - "*.config.cjs", - "*.config.mjs", - "*.config.ts", - ], - } -); diff --git a/rust/vetkeys/basic_bls_signing/frontend/package.json b/rust/vetkeys/basic_bls_signing/frontend/package.json index 38f9215767..0a68d864e4 100644 --- a/rust/vetkeys/basic_bls_signing/frontend/package.json +++ b/rust/vetkeys/basic_bls_signing/frontend/package.json @@ -1,32 +1,20 @@ { - "name": "basic_bls_signing_frontend", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "printf '\\nNo backend specified. Use one of:\\n\\n npm run dev:motoko\\n npm run dev:rust\\n\\n' && exit 1", - "dev:motoko": "npm run build:bindings && BACKEND=motoko vite", - "dev:rust": "npm run build:bindings && BACKEND=rust vite", - "build": "npm run build:bindings && tsc && vite build", - "build:bindings": "cd scripts && ./gen_bindings.sh", - "preview": "vite preview", - "lint": "eslint" - }, - "devDependencies": { - "@eslint/js": "^9.24.0", - "@rollup/plugin-typescript": "^12.1.2", - "@types/node": "^24.0.10", - "eslint": "^9.24.0", - "eslint-config-prettier": "^10.1.5", - "eslint-plugin-prettier": "^5.4.0", - "tslib": "^2.8.1", - "typescript": "~5.7.2", - "typescript-eslint": "^8.35.1", - "vite": "^6.4.1" - }, - "dependencies": { - "@icp-sdk/auth": "^7.1.0", - "@icp-sdk/core": "^5.4.0", - "@icp-sdk/vetkeys": "^0.5.0-beta.0" - } + "name": "frontend", + "private": true, + "type": "module", + "scripts": { + "prebuild": "npm i --include=dev", + "build": "vite build", + "dev": "vite" + }, + "dependencies": { + "@icp-sdk/auth": "^7.1.0", + "@icp-sdk/core": "^5.4.0", + "@icp-sdk/vetkeys": "^0.5.0-beta.0" + }, + "devDependencies": { + "@icp-sdk/bindgen": "~0.2.2", + "typescript": "~5.7.2", + "vite": "^6.4.1" + } } diff --git a/rust/vetkeys/basic_bls_signing/frontend/scripts/gen_bindings.sh b/rust/vetkeys/basic_bls_signing/frontend/scripts/gen_bindings.sh deleted file mode 100755 index 6cfbd7e742..0000000000 --- a/rust/vetkeys/basic_bls_signing/frontend/scripts/gen_bindings.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -set -eu - -# Resolve the script's physical location so we work correctly even when the -# icp CLI has symlinked `frontend/` into a backend subdirectory for the build. -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" -FRONTEND_DIR="$(dirname "$SCRIPT_DIR")" -EXAMPLE_ROOT="$(dirname "$FRONTEND_DIR")" - -# Bindings are always generated from the Rust backend since both backends -# expose the same Candid interface. -if command -v candid-extractor >/dev/null 2>&1; then - (cd "$EXAMPLE_ROOT/rust/backend" && make extract-candid) -fi - -rm -rf "$FRONTEND_DIR/src/declarations/basic_bls_signing" -mkdir -p "$FRONTEND_DIR/src/declarations/basic_bls_signing" -npx --yes @icp-sdk/bindgen \ - --did-file "$EXAMPLE_ROOT/rust/backend/backend.did" \ - --out-dir "$FRONTEND_DIR/src/declarations/basic_bls_signing" \ - --declarations-flat --force diff --git a/rust/vetkeys/basic_bls_signing/frontend/src/main.ts b/rust/vetkeys/basic_bls_signing/frontend/src/main.ts index 6fd1369203..fea6e11069 100644 --- a/rust/vetkeys/basic_bls_signing/frontend/src/main.ts +++ b/rust/vetkeys/basic_bls_signing/frontend/src/main.ts @@ -2,12 +2,12 @@ import "./style.css"; import { Principal } from "@icp-sdk/core/principal"; import { AuthClient, LocalStorage } from "@icp-sdk/auth/client"; import { HttpAgent } from "@icp-sdk/core/agent"; -import { createActor, type Backend, type Signature } from "./declarations/basic_bls_signing/backend"; +import { createActor, type Backend, type Signature } from "./bindings/backend"; import { DerivedPublicKey, verifyBlsSignature } from "@icp-sdk/vetkeys"; import { safeGetCanisterEnv } from "@icp-sdk/core/agent/canister-env"; const canisterEnv = safeGetCanisterEnv<{ - "PUBLIC_CANISTER_ID:basic_bls_signing": string; + "PUBLIC_CANISTER_ID:backend": string; }>(); let myPrincipal: Principal | undefined = undefined; @@ -18,9 +18,9 @@ let myVerificationKey: DerivedPublicKey | undefined; async function getBasicBlsSigningActor(): Promise { if (basicBlsSigningActor) return basicBlsSigningActor; - const canisterId = canisterEnv?.["PUBLIC_CANISTER_ID:basic_bls_signing"]; + const canisterId = canisterEnv?.["PUBLIC_CANISTER_ID:backend"]; if (!canisterId) { - throw Error("Canister ID for basic_bls_signing is not set"); + throw Error("Canister ID for backend is not set"); } if (!authClient) { throw Error("Auth client is not initialized"); diff --git a/rust/vetkeys/basic_bls_signing/frontend/vite.config.ts b/rust/vetkeys/basic_bls_signing/frontend/vite.config.ts index 0615cf5742..cd9cb43bc1 100644 --- a/rust/vetkeys/basic_bls_signing/frontend/vite.config.ts +++ b/rust/vetkeys/basic_bls_signing/frontend/vite.config.ts @@ -1,51 +1,49 @@ import { defineConfig } from "vite"; import { execSync } from "child_process"; - -const environment = process.env.ICP_ENVIRONMENT || "local"; -const CANISTER_NAMES = ["basic_bls_signing"]; +import { icpBindgen } from "@icp-sdk/bindgen/plugins/vite"; function getDevServerConfig() { - const backend = process.env.BACKEND; - if (!backend) { - throw new Error( - "BACKEND env var is required. Use `npm run dev:motoko` or `npm run dev:rust`.", + try { + const canisterId = execSync("icp canister status backend -e local -i", { + encoding: "utf-8", + stdio: "pipe", + }).trim(); + const networkStatus = JSON.parse( + execSync("icp network status --json", { + encoding: "utf-8", + stdio: "pipe", + }) ); - } + return { + headers: { + "Set-Cookie": `ic_env=${encodeURIComponent( + `ic_root_key=${networkStatus.root_key}&PUBLIC_CANISTER_ID:backend=${canisterId}` + )}; SameSite=Lax;`, + }, + proxy: { + "/api": { target: "http://127.0.0.1:8000", changeOrigin: true }, + }, + }; + } catch {} - const networkStatus = JSON.parse( - execSync(`icp network status -e ${environment} --json --project-root-override ../${backend}`, { - encoding: "utf-8", - }), + throw new Error( + "No local network running. Start with:\n icp network start -d && icp deploy" ); - const canisterParams = CANISTER_NAMES.map((name) => { - const id = execSync( - `icp canister status ${name} -e ${environment} --id-only --project-root-override ../${backend}`, - { encoding: "utf-8", stdio: "pipe" }, - ).trim(); - return `PUBLIC_CANISTER_ID:${name}=${id}`; - }).join("&"); - return { - headers: { - "Set-Cookie": `ic_env=${encodeURIComponent( - `${canisterParams}&ic_root_key=${networkStatus.root_key}`, - )}; SameSite=Lax;`, - }, - proxy: { - "/api": { target: networkStatus.api_url, changeOrigin: true }, - }, - hmr: false, - }; } export default defineConfig(({ command }) => ({ + base: "./", + plugins: [ + icpBindgen({ + didFile: "../backend/backend.did", + outDir: "./src/bindings", + }), + ], + optimizeDeps: { + esbuildOptions: { define: { global: "globalThis" } }, + }, build: { sourcemap: true, - rollupOptions: { - output: { - inlineDynamicImports: true, - }, - }, }, - root: "./", - ...(command === "serve" ? { server: getDevServerConfig() } : {}), + server: command === "serve" ? getDevServerConfig() : undefined, })); diff --git a/rust/vetkeys/basic_bls_signing/rust/icp.yaml b/rust/vetkeys/basic_bls_signing/icp.yaml similarity index 70% rename from rust/vetkeys/basic_bls_signing/rust/icp.yaml rename to rust/vetkeys/basic_bls_signing/icp.yaml index 11ca107565..5a4c691889 100644 --- a/rust/vetkeys/basic_bls_signing/rust/icp.yaml +++ b/rust/vetkeys/basic_bls_signing/icp.yaml @@ -1,5 +1,5 @@ canisters: - - name: basic_bls_signing + - name: backend recipe: type: "@dfinity/rust@v3.2.0" configuration: @@ -9,13 +9,14 @@ canisters: type: text value: "(\"test_key_1\")" - - name: www + - name: frontend recipe: type: "@dfinity/asset-canister@v2.2.1" configuration: - dir: dist + dir: frontend/dist build: - - cd frontend && npm i --include=dev && npm run build && cd - && rm -rf dist; mv frontend/dist ./ + - npm install --prefix frontend + - npm run build --prefix frontend networks: - name: local diff --git a/rust/vetkeys/basic_bls_signing/motoko/frontend b/rust/vetkeys/basic_bls_signing/motoko/frontend deleted file mode 120000 index af288785f3..0000000000 --- a/rust/vetkeys/basic_bls_signing/motoko/frontend +++ /dev/null @@ -1 +0,0 @@ -../frontend \ No newline at end of file diff --git a/rust/vetkeys/basic_bls_signing/motoko/mops.toml b/rust/vetkeys/basic_bls_signing/motoko/mops.toml deleted file mode 100644 index a29d5213fd..0000000000 --- a/rust/vetkeys/basic_bls_signing/motoko/mops.toml +++ /dev/null @@ -1,10 +0,0 @@ -[toolchain] -moc = "1.9.0" - -[dependencies] -core = "2.5.0" -ic-vetkeys = "0.5.0" -sha2 = "0.1.14" - -[canisters.basic_bls_signing] -main = "backend/src/Main.mo" diff --git a/rust/vetkeys/basic_bls_signing/package.json b/rust/vetkeys/basic_bls_signing/package.json new file mode 100644 index 0000000000..13c11af71a --- /dev/null +++ b/rust/vetkeys/basic_bls_signing/package.json @@ -0,0 +1,13 @@ +{ + "name": "basic_bls_signing", + "private": true, + "type": "module", + "scripts": { + "build": "npm run build --workspaces --if-present", + "prebuild": "npm run prebuild --workspaces --if-present", + "dev": "npm run dev --workspaces --if-present" + }, + "workspaces": [ + "frontend" + ] +} diff --git a/rust/vetkeys/basic_bls_signing/rust/rust-toolchain.toml b/rust/vetkeys/basic_bls_signing/rust-toolchain.toml similarity index 100% rename from rust/vetkeys/basic_bls_signing/rust/rust-toolchain.toml rename to rust/vetkeys/basic_bls_signing/rust-toolchain.toml diff --git a/rust/vetkeys/basic_bls_signing/rust/frontend b/rust/vetkeys/basic_bls_signing/rust/frontend deleted file mode 120000 index af288785f3..0000000000 --- a/rust/vetkeys/basic_bls_signing/rust/frontend +++ /dev/null @@ -1 +0,0 @@ -../frontend \ No newline at end of file diff --git a/rust/vetkeys/basic_bls_signing/test.sh b/rust/vetkeys/basic_bls_signing/test.sh new file mode 100755 index 0000000000..7d1f274390 --- /dev/null +++ b/rust/vetkeys/basic_bls_signing/test.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -e + +echo "--- Testing basic_bls_signing: verification key ---" +result=$(icp canister call backend get_my_verification_key '()') && \ + echo "$result" | head -1 && \ + echo "$result" | grep -q 'blob' && \ + echo "PASS" || (echo "FAIL" && exit 1) + +echo "--- Testing basic_bls_signing: my signatures (query) ---" +result=$(icp canister call backend get_my_signatures '()' --query) && \ + echo "$result" && \ + echo "$result" | grep -q 'vec' && \ + echo "PASS" || (echo "FAIL" && exit 1) From 318b4788066eb11fece3fcb36990a3aefa3f54f9 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Fri, 17 Jul 2026 10:10:56 +0200 Subject: [PATCH 2/8] chore(vetkeys/basic_bls_signing): rust v3.3.0, package `backend`, drop Makefile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bump the Rust recipe to @dfinity/rust@v3.3.0 and rename the Cargo package to `backend`, so the recipe no longer needs a custom `package:` in its configuration (matches daily_planner and the other migrated rust examples). - Drop backend/Makefile; advertise candid regeneration directly in the README via `icp build backend && candid-extractor … > backend/backend.did` (the Makefile's `rm -rf .icp` clean target was also wrong — only .icp/cache should ever be wiped, and that isn't advertised elsewhere anyway). Verified: rust deploy + test.sh pass; `icp build backend` produces backend.wasm. Co-Authored-By: Claude Opus 4.8 (1M context) --- rust/vetkeys/basic_bls_signing/README.md | 2 +- rust/vetkeys/basic_bls_signing/backend/Cargo.toml | 2 +- rust/vetkeys/basic_bls_signing/backend/Makefile | 15 --------------- rust/vetkeys/basic_bls_signing/icp.yaml | 3 +-- 4 files changed, 3 insertions(+), 19 deletions(-) delete mode 100644 rust/vetkeys/basic_bls_signing/backend/Makefile diff --git a/rust/vetkeys/basic_bls_signing/README.md b/rust/vetkeys/basic_bls_signing/README.md index 3d295c7c66..4bc2bc9062 100644 --- a/rust/vetkeys/basic_bls_signing/README.md +++ b/rust/vetkeys/basic_bls_signing/README.md @@ -73,7 +73,7 @@ A vanilla TypeScript application providing a simple interface for signing, showi `backend/backend.did` defines the backend's public interface; the frontend bindings are generated from it during the build. If you change the backend's public API, regenerate it: ```bash -cd backend && make extract-candid +icp build backend && candid-extractor target/wasm32-unknown-unknown/release/backend.wasm > backend/backend.did ``` ## Additional resources diff --git a/rust/vetkeys/basic_bls_signing/backend/Cargo.toml b/rust/vetkeys/basic_bls_signing/backend/Cargo.toml index 36ff581387..35b7410dfa 100644 --- a/rust/vetkeys/basic_bls_signing/backend/Cargo.toml +++ b/rust/vetkeys/basic_bls_signing/backend/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "ic-vetkeys-example-basic-bls-signing-backend" +name = "backend" authors = ["DFINITY Stiftung"] version = "0.1.0" edition = "2021" diff --git a/rust/vetkeys/basic_bls_signing/backend/Makefile b/rust/vetkeys/basic_bls_signing/backend/Makefile deleted file mode 100644 index 8d9d222159..0000000000 --- a/rust/vetkeys/basic_bls_signing/backend/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -.PHONY: compile-wasm -.SILENT: compile-wasm -compile-wasm: - cargo build --release --target wasm32-unknown-unknown - -.PHONY: extract-candid -.SILENT: extract-candid -extract-candid: compile-wasm - candid-extractor ../target/wasm32-unknown-unknown/release/ic_vetkeys_example_basic_bls_signing_backend.wasm > backend.did - -.PHONY: clean -.SILENT: clean -clean: - cargo clean - rm -rf .icp \ No newline at end of file diff --git a/rust/vetkeys/basic_bls_signing/icp.yaml b/rust/vetkeys/basic_bls_signing/icp.yaml index 5a4c691889..3b7759d854 100644 --- a/rust/vetkeys/basic_bls_signing/icp.yaml +++ b/rust/vetkeys/basic_bls_signing/icp.yaml @@ -1,9 +1,8 @@ canisters: - name: backend recipe: - type: "@dfinity/rust@v3.2.0" + type: "@dfinity/rust@v3.3.0" configuration: - package: ic-vetkeys-example-basic-bls-signing-backend candid: backend/backend.did init_args: type: text From 3ccafefcca5db867f20da07eb2e0b47566a8333d Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Fri, 17 Jul 2026 10:42:17 +0200 Subject: [PATCH 3/8] fix(vetkeys/basic_bls_signing): explicit persistent actor class; drop test.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address Copilot review: - Use `persistent actor class` explicitly (drop the redundant class-level `shared` and the `--default-persistent-actors` mops arg). Empirically the flag DID persist this main-canister actor class across upgrades — AGENTS.md's caveat is about actor classes spawned as sub-WASMs, not a main canister that is an actor class for its init arg — but explicit `persistent` is unambiguous and matches AGENTS.md's literal guidance. Verified: state survives an upgrade. - Drop test.sh (motoko + rust) and run deploy-only CI. The example never had a test.sh on master; the deploy itself verifies build + install. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/vetkeys-basic-bls-signing.yml | 6 ++---- motoko/vetkeys/basic_bls_signing/backend/app.mo | 2 +- motoko/vetkeys/basic_bls_signing/mops.toml | 2 +- motoko/vetkeys/basic_bls_signing/test.sh | 14 -------------- rust/vetkeys/basic_bls_signing/test.sh | 14 -------------- 5 files changed, 4 insertions(+), 34 deletions(-) delete mode 100755 motoko/vetkeys/basic_bls_signing/test.sh delete mode 100755 rust/vetkeys/basic_bls_signing/test.sh diff --git a/.github/workflows/vetkeys-basic-bls-signing.yml b/.github/workflows/vetkeys-basic-bls-signing.yml index 1448957545..15f812e6ce 100644 --- a/.github/workflows/vetkeys-basic-bls-signing.yml +++ b/.github/workflows/vetkeys-basic-bls-signing.yml @@ -22,12 +22,11 @@ jobs: ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - name: Deploy and test + - name: Deploy working-directory: motoko/vetkeys/basic_bls_signing run: | icp network start -d icp deploy - bash test.sh rust: runs-on: ubuntu-24.04 @@ -36,9 +35,8 @@ jobs: ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - name: Deploy and test + - name: Deploy working-directory: rust/vetkeys/basic_bls_signing run: | icp network start -d icp deploy - bash test.sh diff --git a/motoko/vetkeys/basic_bls_signing/backend/app.mo b/motoko/vetkeys/basic_bls_signing/backend/app.mo index 560bcad9de..96e33a0111 100644 --- a/motoko/vetkeys/basic_bls_signing/backend/app.mo +++ b/motoko/vetkeys/basic_bls_signing/backend/app.mo @@ -10,7 +10,7 @@ import Nat8 "mo:core/Nat8"; import VetKeys "mo:ic-vetkeys"; import Order "mo:core/Order"; -shared actor class (keyName : Text) = { +persistent actor class (keyName : Text) = { // Types type Signature = { message : Text; diff --git a/motoko/vetkeys/basic_bls_signing/mops.toml b/motoko/vetkeys/basic_bls_signing/mops.toml index 5dae136934..70dc7dca37 100644 --- a/motoko/vetkeys/basic_bls_signing/mops.toml +++ b/motoko/vetkeys/basic_bls_signing/mops.toml @@ -9,7 +9,7 @@ ic-vetkeys = "0.5.0" # M0236: use context dot notation # M0237: redundant explicit implicit arguments # M0223: redundant type instantiation -args = ["--default-persistent-actors", "-W=M0236,M0237,M0223"] +args = ["-W=M0236,M0237,M0223"] [canisters.backend] main = "backend/app.mo" diff --git a/motoko/vetkeys/basic_bls_signing/test.sh b/motoko/vetkeys/basic_bls_signing/test.sh deleted file mode 100755 index b446d6697d..0000000000 --- a/motoko/vetkeys/basic_bls_signing/test.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -set -e - -echo "--- Testing basic_bls_signing: verification key ---" -result=$(icp canister call backend getMyVerificationKey '()') && \ - echo "$result" | head -1 && \ - echo "$result" | grep -q 'blob' && \ - echo "PASS" || (echo "FAIL" && exit 1) - -echo "--- Testing basic_bls_signing: my signatures (query) ---" -result=$(icp canister call backend getMySignatures '()' --query) && \ - echo "$result" && \ - echo "$result" | grep -q 'vec' && \ - echo "PASS" || (echo "FAIL" && exit 1) diff --git a/rust/vetkeys/basic_bls_signing/test.sh b/rust/vetkeys/basic_bls_signing/test.sh deleted file mode 100755 index 7d1f274390..0000000000 --- a/rust/vetkeys/basic_bls_signing/test.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -set -e - -echo "--- Testing basic_bls_signing: verification key ---" -result=$(icp canister call backend get_my_verification_key '()') && \ - echo "$result" | head -1 && \ - echo "$result" | grep -q 'blob' && \ - echo "PASS" || (echo "FAIL" && exit 1) - -echo "--- Testing basic_bls_signing: my signatures (query) ---" -result=$(icp canister call backend get_my_signatures '()' --query) && \ - echo "$result" && \ - echo "$result" | grep -q 'vec' && \ - echo "PASS" || (echo "FAIL" && exit 1) From 01fa53ff020736fe70770f7d4cf9de37c5a6245c Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Fri, 17 Jul 2026 10:46:59 +0200 Subject: [PATCH 4/8] chore(vetkeys/basic_bls_signing): keep flag-based persistence; drop redundant `shared` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert the explicit `persistent` detour: keep `--default-persistent-actors` (no `persistent` keyword) — the flag is required today (moc 1.11.0 errors M0219 otherwise) and is the forward-compatible choice, and it provably persists this main-canister actor class across upgrades. Keep the actor class as a plain `actor class` (the previous class-level `shared` was unnecessary — the `shared` that matters is on the `public shared ({ caller })` methods). Co-Authored-By: Claude Opus 4.8 (1M context) --- motoko/vetkeys/basic_bls_signing/backend/app.mo | 2 +- motoko/vetkeys/basic_bls_signing/mops.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/motoko/vetkeys/basic_bls_signing/backend/app.mo b/motoko/vetkeys/basic_bls_signing/backend/app.mo index 96e33a0111..736b081171 100644 --- a/motoko/vetkeys/basic_bls_signing/backend/app.mo +++ b/motoko/vetkeys/basic_bls_signing/backend/app.mo @@ -10,7 +10,7 @@ import Nat8 "mo:core/Nat8"; import VetKeys "mo:ic-vetkeys"; import Order "mo:core/Order"; -persistent actor class (keyName : Text) = { +actor class (keyName : Text) = { // Types type Signature = { message : Text; diff --git a/motoko/vetkeys/basic_bls_signing/mops.toml b/motoko/vetkeys/basic_bls_signing/mops.toml index 70dc7dca37..5dae136934 100644 --- a/motoko/vetkeys/basic_bls_signing/mops.toml +++ b/motoko/vetkeys/basic_bls_signing/mops.toml @@ -9,7 +9,7 @@ ic-vetkeys = "0.5.0" # M0236: use context dot notation # M0237: redundant explicit implicit arguments # M0223: redundant type instantiation -args = ["-W=M0236,M0237,M0223"] +args = ["--default-persistent-actors", "-W=M0236,M0237,M0223"] [canisters.backend] main = "backend/app.mo" From ec3b9da26dd90577c03bcf189f475e39457583ab Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Fri, 17 Jul 2026 12:05:08 +0200 Subject: [PATCH 5/8] docs(vetkeys/basic_bls_signing): drop "smart contract" web3 jargon "ask the canister (IC smart contract) to produce" -> "ask the canister to produce". Co-Authored-By: Claude Opus 4.8 (1M context) --- motoko/vetkeys/basic_bls_signing/README.md | 2 +- rust/vetkeys/basic_bls_signing/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/motoko/vetkeys/basic_bls_signing/README.md b/motoko/vetkeys/basic_bls_signing/README.md index 94fbfe1eba..eeb3ddcb6d 100644 --- a/motoko/vetkeys/basic_bls_signing/README.md +++ b/motoko/vetkeys/basic_bls_signing/README.md @@ -4,7 +4,7 @@ Also available in: [Rust](../../../rust/vetkeys/basic_bls_signing) -The **Basic BLS signing** example demonstrates how to use **[VetKeys](https://docs.internetcomputer.org/concepts/vetkeys)** to implement a threshold BLS signing service on the **Internet Computer (IC)**, where every authenticated user can ask the canister (IC smart contract) to produce signatures, with the **Internet Identity Principal** identifying the signer. The canister ensures a user can only produce signatures for their own principal, not for someone else's. Furthermore, the vetKeys in this dapp can only be produced upon a user request, as specified in the canister code — the canister cannot produce signatures for arbitrary users or messages. +The **Basic BLS signing** example demonstrates how to use **[VetKeys](https://docs.internetcomputer.org/concepts/vetkeys)** to implement a threshold BLS signing service on the **Internet Computer (IC)**, where every authenticated user can ask the canister to produce signatures, with the **Internet Identity Principal** identifying the signer. The canister ensures a user can only produce signatures for their own principal, not for someone else's. Furthermore, the vetKeys in this dapp can only be produced upon a user request, as specified in the canister code — the canister cannot produce signatures for arbitrary users or messages. To confirm the canister can only produce signatures in the intended way, users need to inspect the code installed in the canister. For this, it is crucial that canisters using VetKeys have their code public. diff --git a/rust/vetkeys/basic_bls_signing/README.md b/rust/vetkeys/basic_bls_signing/README.md index 4bc2bc9062..7492b9cd51 100644 --- a/rust/vetkeys/basic_bls_signing/README.md +++ b/rust/vetkeys/basic_bls_signing/README.md @@ -4,7 +4,7 @@ Also available in: [Motoko](../../../motoko/vetkeys/basic_bls_signing) -The **Basic BLS signing** example demonstrates how to use **[VetKeys](https://docs.internetcomputer.org/concepts/vetkeys)** to implement a threshold BLS signing service on the **Internet Computer (IC)**, where every authenticated user can ask the canister (IC smart contract) to produce signatures, with the **Internet Identity Principal** identifying the signer. The canister ensures a user can only produce signatures for their own principal, not for someone else's. Furthermore, the vetKeys in this dapp can only be produced upon a user request, as specified in the canister code — the canister cannot produce signatures for arbitrary users or messages. +The **Basic BLS signing** example demonstrates how to use **[VetKeys](https://docs.internetcomputer.org/concepts/vetkeys)** to implement a threshold BLS signing service on the **Internet Computer (IC)**, where every authenticated user can ask the canister to produce signatures, with the **Internet Identity Principal** identifying the signer. The canister ensures a user can only produce signatures for their own principal, not for someone else's. Furthermore, the vetKeys in this dapp can only be produced upon a user request, as specified in the canister code — the canister cannot produce signatures for arbitrary users or messages. To confirm the canister can only produce signatures in the intended way, users need to inspect the code installed in the canister. For this, it is crucial that canisters using VetKeys have their code public. From 27be51bdab845b27b6c9084c58f7f46de729fb42 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Fri, 17 Jul 2026 12:35:13 +0200 Subject: [PATCH 6/8] docs(vetkeys/basic_bls_signing): drop "dapp", fix security link Replace "dapp" with "app" in READMEs and code comments and normalize the security best-practices link to https://docs.internetcomputer.org/guides/security/overview/. The functional vetKD domain-separator string ("basic_bls_signing_dapp") is left unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- motoko/vetkeys/basic_bls_signing/README.md | 4 ++-- motoko/vetkeys/basic_bls_signing/backend/app.mo | 2 +- rust/vetkeys/basic_bls_signing/README.md | 4 ++-- rust/vetkeys/basic_bls_signing/backend/src/lib.rs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/motoko/vetkeys/basic_bls_signing/README.md b/motoko/vetkeys/basic_bls_signing/README.md index eeb3ddcb6d..847656ed57 100644 --- a/motoko/vetkeys/basic_bls_signing/README.md +++ b/motoko/vetkeys/basic_bls_signing/README.md @@ -4,7 +4,7 @@ Also available in: [Rust](../../../rust/vetkeys/basic_bls_signing) -The **Basic BLS signing** example demonstrates how to use **[VetKeys](https://docs.internetcomputer.org/concepts/vetkeys)** to implement a threshold BLS signing service on the **Internet Computer (IC)**, where every authenticated user can ask the canister to produce signatures, with the **Internet Identity Principal** identifying the signer. The canister ensures a user can only produce signatures for their own principal, not for someone else's. Furthermore, the vetKeys in this dapp can only be produced upon a user request, as specified in the canister code — the canister cannot produce signatures for arbitrary users or messages. +The **Basic BLS signing** example demonstrates how to use **[VetKeys](https://docs.internetcomputer.org/concepts/vetkeys)** to implement a threshold BLS signing service on the **Internet Computer (IC)**, where every authenticated user can ask the canister to produce signatures, with the **Internet Identity Principal** identifying the signer. The canister ensures a user can only produce signatures for their own principal, not for someone else's. Furthermore, the vetKeys in this app can only be produced upon a user request, as specified in the canister code — the canister cannot produce signatures for arbitrary users or messages. To confirm the canister can only produce signatures in the intended way, users need to inspect the code installed in the canister. For this, it is crucial that canisters using VetKeys have their code public. @@ -79,4 +79,4 @@ mops generate candid backend ## Additional resources - **[What are VetKeys](https://docs.internetcomputer.org/concepts/vetkeys)** — more information about VetKeys and VetKD. -- [Security best practices](https://docs.internetcomputer.org/guides/security/overview) +- [Security best practices](https://docs.internetcomputer.org/guides/security/overview/) diff --git a/motoko/vetkeys/basic_bls_signing/backend/app.mo b/motoko/vetkeys/basic_bls_signing/backend/app.mo index 736b081171..fd42cc85ee 100644 --- a/motoko/vetkeys/basic_bls_signing/backend/app.mo +++ b/motoko/vetkeys/basic_bls_signing/backend/app.mo @@ -46,7 +46,7 @@ actor class (keyName : Text) = { // Helper function to create context for vetKD private func context(signer : Principal) : Blob { - // Domain separator for this dapp + // Domain separator for this app let domainSeparator : [Nat8] = Blob.toArray(Text.encodeUtf8("basic_bls_signing_dapp")); let domainSeparatorLength : [Nat8] = [Nat8.fromNat(domainSeparator.size())]; // Length of domain separator diff --git a/rust/vetkeys/basic_bls_signing/README.md b/rust/vetkeys/basic_bls_signing/README.md index 7492b9cd51..95b9a30870 100644 --- a/rust/vetkeys/basic_bls_signing/README.md +++ b/rust/vetkeys/basic_bls_signing/README.md @@ -4,7 +4,7 @@ Also available in: [Motoko](../../../motoko/vetkeys/basic_bls_signing) -The **Basic BLS signing** example demonstrates how to use **[VetKeys](https://docs.internetcomputer.org/concepts/vetkeys)** to implement a threshold BLS signing service on the **Internet Computer (IC)**, where every authenticated user can ask the canister to produce signatures, with the **Internet Identity Principal** identifying the signer. The canister ensures a user can only produce signatures for their own principal, not for someone else's. Furthermore, the vetKeys in this dapp can only be produced upon a user request, as specified in the canister code — the canister cannot produce signatures for arbitrary users or messages. +The **Basic BLS signing** example demonstrates how to use **[VetKeys](https://docs.internetcomputer.org/concepts/vetkeys)** to implement a threshold BLS signing service on the **Internet Computer (IC)**, where every authenticated user can ask the canister to produce signatures, with the **Internet Identity Principal** identifying the signer. The canister ensures a user can only produce signatures for their own principal, not for someone else's. Furthermore, the vetKeys in this app can only be produced upon a user request, as specified in the canister code — the canister cannot produce signatures for arbitrary users or messages. To confirm the canister can only produce signatures in the intended way, users need to inspect the code installed in the canister. For this, it is crucial that canisters using VetKeys have their code public. @@ -79,4 +79,4 @@ icp build backend && candid-extractor target/wasm32-unknown-unknown/release/back ## Additional resources - **[What are VetKeys](https://docs.internetcomputer.org/concepts/vetkeys)** — more information about VetKeys and VetKD. -- [Security best practices](https://docs.internetcomputer.org/guides/security/overview) +- [Security best practices](https://docs.internetcomputer.org/guides/security/overview/) diff --git a/rust/vetkeys/basic_bls_signing/backend/src/lib.rs b/rust/vetkeys/basic_bls_signing/backend/src/lib.rs index 60963a41f4..d98702e2ec 100644 --- a/rust/vetkeys/basic_bls_signing/backend/src/lib.rs +++ b/rust/vetkeys/basic_bls_signing/backend/src/lib.rs @@ -104,7 +104,7 @@ async fn get_my_verification_key() -> VetKeyPublicKey { } fn context(signer: &Principal) -> Vec { - // A domain separator is not strictly necessary in this dapp, but having one is considered a good practice. + // A domain separator is not strictly necessary in this app, but having one is considered a good practice. const DOMAIN_SEPARATOR: [u8; 22] = *b"basic_bls_signing_dapp"; const DOMAIN_SEPARATOR_LENGTH: u8 = DOMAIN_SEPARATOR.len() as u8; [DOMAIN_SEPARATOR_LENGTH] From 36a5dc4db94fdf6ad04f1d16b81670b8cbcb2506 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Fri, 17 Jul 2026 12:36:51 +0200 Subject: [PATCH 7/8] docs(vetkeys/basic_bls_signing): fix outdated "stable" comment terminology MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `signatures` field is retained across upgrades because it is not declared `transient` — there is no `stable` keyword in use. Update the comment to describe this accurately. Co-Authored-By: Claude Opus 4.8 (1M context) --- motoko/vetkeys/basic_bls_signing/backend/app.mo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/motoko/vetkeys/basic_bls_signing/backend/app.mo b/motoko/vetkeys/basic_bls_signing/backend/app.mo index fd42cc85ee..1f167ddb1f 100644 --- a/motoko/vetkeys/basic_bls_signing/backend/app.mo +++ b/motoko/vetkeys/basic_bls_signing/backend/app.mo @@ -36,7 +36,7 @@ actor class (keyName : Text) = { } }; - // Stable storage for signatures + // Signatures are retained across upgrades: this actor field is not declared `transient`. private var signatures = Map.empty(); // Helper function to get current timestamp From 99946f96354f968785704feb6151239ebf4abd23 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Fri, 17 Jul 2026 15:24:32 +0200 Subject: [PATCH 8/8] refactor(vetkeys/basic_bls_signing): rename domain separator to drop "dapp" Change the vetKD domain-separator string from "basic_bls_signing_dapp" to "basic_bls_signing_app" in both backends (Rust array length 22 -> 21). These are fresh examples with no previously derived keys to stay compatible with, so the change is safe. Both backends compile. Co-Authored-By: Claude Opus 4.8 (1M context) --- motoko/vetkeys/basic_bls_signing/backend/app.mo | 2 +- rust/vetkeys/basic_bls_signing/backend/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/motoko/vetkeys/basic_bls_signing/backend/app.mo b/motoko/vetkeys/basic_bls_signing/backend/app.mo index 1f167ddb1f..f5feddae83 100644 --- a/motoko/vetkeys/basic_bls_signing/backend/app.mo +++ b/motoko/vetkeys/basic_bls_signing/backend/app.mo @@ -47,7 +47,7 @@ actor class (keyName : Text) = { // Helper function to create context for vetKD private func context(signer : Principal) : Blob { // Domain separator for this app - let domainSeparator : [Nat8] = Blob.toArray(Text.encodeUtf8("basic_bls_signing_dapp")); + let domainSeparator : [Nat8] = Blob.toArray(Text.encodeUtf8("basic_bls_signing_app")); let domainSeparatorLength : [Nat8] = [Nat8.fromNat(domainSeparator.size())]; // Length of domain separator // Combine domain separator length, domain separator, and signer principal diff --git a/rust/vetkeys/basic_bls_signing/backend/src/lib.rs b/rust/vetkeys/basic_bls_signing/backend/src/lib.rs index d98702e2ec..4738a392df 100644 --- a/rust/vetkeys/basic_bls_signing/backend/src/lib.rs +++ b/rust/vetkeys/basic_bls_signing/backend/src/lib.rs @@ -105,7 +105,7 @@ async fn get_my_verification_key() -> VetKeyPublicKey { fn context(signer: &Principal) -> Vec { // A domain separator is not strictly necessary in this app, but having one is considered a good practice. - const DOMAIN_SEPARATOR: [u8; 22] = *b"basic_bls_signing_dapp"; + const DOMAIN_SEPARATOR: [u8; 21] = *b"basic_bls_signing_app"; const DOMAIN_SEPARATOR_LENGTH: u8 = DOMAIN_SEPARATOR.len() as u8; [DOMAIN_SEPARATOR_LENGTH] .into_iter()