diff --git a/.github/workflows/vetkeys-basic-bls-signing.yml b/.github/workflows/vetkeys-basic-bls-signing.yml index e38e0b617d..15f812e6ce 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,28 @@ 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 + working-directory: motoko/vetkeys/basic_bls_signing + run: | + icp network start -d + icp deploy + + 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 + working-directory: rust/vetkeys/basic_bls_signing + run: | + icp network start -d + icp deploy diff --git a/motoko/vetkeys/basic_bls_signing/README.md b/motoko/vetkeys/basic_bls_signing/README.md new file mode 100644 index 0000000000..847656ed57 --- /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 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. + +![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 88% rename from rust/vetkeys/basic_bls_signing/motoko/backend/src/Main.mo rename to motoko/vetkeys/basic_bls_signing/backend/app.mo index 1d126eb386..f5feddae83 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) = { +actor class (keyName : Text) = { // Types type Signature = { message : Text; @@ -36,7 +36,7 @@ shared persistent 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 @@ -46,8 +46,8 @@ shared persistent actor class (keyName : Text) = { // Helper function to create context for vetKD private func context(signer : Principal) : Blob { - // Domain separator for this dapp - let domainSeparator : [Nat8] = Blob.toArray(Text.encodeUtf8("basic_bls_signing_dapp")); + // Domain separator for this app + 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 @@ -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/ui_screenshot.png b/motoko/vetkeys/basic_bls_signing/ui_screenshot.png new file mode 100644 index 0000000000..a59025e841 Binary files /dev/null and b/motoko/vetkeys/basic_bls_signing/ui_screenshot.png differ diff --git a/rust/vetkeys/basic_bls_signing/rust/Cargo.toml b/rust/vetkeys/basic_bls_signing/Cargo.toml similarity index 100% rename from rust/vetkeys/basic_bls_signing/rust/Cargo.toml rename to rust/vetkeys/basic_bls_signing/Cargo.toml diff --git a/rust/vetkeys/basic_bls_signing/README.md b/rust/vetkeys/basic_bls_signing/README.md index 7ddb0b5423..95b9a30870 100644 --- a/rust/vetkeys/basic_bls_signing/README.md +++ b/rust/vetkeys/basic_bls_signing/README.md @@ -1,85 +1,82 @@ -# Threshold BLS Signatures +# Threshold BLS Signatures (Rust) - - +[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 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. ![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 +icp build backend && candid-extractor target/wasm32-unknown-unknown/release/backend.wasm > backend/backend.did ``` -## 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 90% rename from rust/vetkeys/basic_bls_signing/rust/backend/Cargo.toml rename to rust/vetkeys/basic_bls_signing/backend/Cargo.toml index 36ff581387..35b7410dfa 100644 --- a/rust/vetkeys/basic_bls_signing/rust/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/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 96% rename from rust/vetkeys/basic_bls_signing/rust/backend/src/lib.rs rename to rust/vetkeys/basic_bls_signing/backend/src/lib.rs index 60963a41f4..4738a392df 100644 --- a/rust/vetkeys/basic_bls_signing/rust/backend/src/lib.rs +++ b/rust/vetkeys/basic_bls_signing/backend/src/lib.rs @@ -104,8 +104,8 @@ 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. - const DOMAIN_SEPARATOR: [u8; 22] = *b"basic_bls_signing_dapp"; + // A domain separator is not strictly necessary in this app, but having one is considered a good practice. + 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() 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 52% rename from rust/vetkeys/basic_bls_signing/rust/icp.yaml rename to rust/vetkeys/basic_bls_signing/icp.yaml index 11ca107565..3b7759d854 100644 --- a/rust/vetkeys/basic_bls_signing/rust/icp.yaml +++ b/rust/vetkeys/basic_bls_signing/icp.yaml @@ -1,21 +1,21 @@ canisters: - - name: basic_bls_signing + - 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 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/backend/Makefile b/rust/vetkeys/basic_bls_signing/rust/backend/Makefile deleted file mode 100644 index 8d9d222159..0000000000 --- a/rust/vetkeys/basic_bls_signing/rust/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/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