Skip to content
SayloolPublic

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

6 Commits

Folders and files

Repository files navigation

ZGuard — AI Payment Firewall for Zcash (Testnet MVP)

ZGuard is an AI Payment Firewall for Zcash: AI can propose payments, a deterministic policy engine validates them, and the user controls approval/signing.

AI understands the payment. ZGuard controls the payment. The user authorizes the payment.

Flow: natural language → server-side LLM route (/api/intent) → strictly validated PaymentIntent → deterministic ZGuard policy engine → 🟢 ALLOWED / 🔴 BLOCKED → explicit user APPROVE & SIGN → authorizePayment() re-evaluates the exact reviewed intent at sign time → WebZjs signs locally in the browser → broadcast to Zcash Testnet.

Pages

  • / — the MVP dashboard and demo: network/wallet/balance/policy cards, AI payment assistant, ZGuard security check, payment review with APPROVE & SIGN / CANCEL, signing/broadcast/insufficient-funds states, security & privacy explainers.
  • /wallet — developer page for the raw wallet foundation (address, sync, balances, manual send).
  • /policy — minimal policy-engine tester.
  • /assistant — earlier minimal assistant flow (kept for reference).

The demo requires OPENAI_API_KEY (server-side only) for the AI step, and testnet funds (see faucet below) for a real broadcast. Without funds the flow ends honestly at INSUFFICIENT TESTNET FUNDS — no txid is ever faked.

Policy engine (lib/policy/)

The security boundary between untrusted AI payment intents and the wallet:

  • types.ts — PaymentIntent (untrusted AI output), ZGuardPolicy (user-owned, never part of AI output), SpendingState, PolicyDecision.
  • engine.ts — pure, deterministic evaluatePaymentIntent(intent, policy, spendingState) → { allowed, reasons[], amountZatoshis, requiresUserApproval: true }. No LLM involved. Checks: amount validity (rejects zero/negative/NaN/Infinity/sub-zatoshi precision; integer-zatoshi bigint math throughout), per-transaction limit, monthly limit, shielded-only, testnet recipient (mainnet rejected; full address decoding happens again in librustzcash at propose_transfer, so this gate fails closed twice). Malformed policy values also fail closed.
  • spending.ts — browser-localStorage monthly spend tracking (documented MVP limitation; swappable behind the SpendingState interface for a server-side or transaction-derived ledger later).
  • engine.test.ts — 20 security tests (npm test, Vitest): limits, boundaries (exact limit ALLOW, +1 zatoshi BLOCK), invalid amounts/addresses, mainnet rejection, shielded-only, AI-manipulation scenarios, determinism.

ALLOW means "permitted by policy" only — explicit user approval always precedes signing, and the engine cannot send transactions. /policy is a minimal test UI.

Approval boundary (lib/payment/authorize.ts)

authorizePayment(intent, policy, freshSpendingState, userApproved) is the only path to the wallet: it requires the explicit approval flag AND re-evaluates the exact reviewed intent through the policy engine at sign time, then returns integer zatoshis

  • recipient for sendShielded (propose → prove+sign in a browser web worker → broadcast). Failures are never auto-retried (duplicate-spend safety); insufficient balance surfaces as a distinct state. 44 security tests cover the boundary (lib/payment/authorize.test.ts, lib/ai/intentSchema.test.ts, lib/policy/engine.test.ts).

Architecture

  • Next.js 15 / TypeScript / App Router, Vercel-compatible. No custom server.
  • WebZjs (@chainsafe/webzjs-wallet, built from source) vendored under public/webzjs/. Loaded at runtime in the browser via dynamic import.
  • All wallet state and keys live in the browser. The seed phrase is generated client-side and (for this spike only) persisted in localStorage. Nothing secret is ever sent to a server, API route, or LLM. The only API route (/api/intent) receives the user's natural-language message and nothing else.
  • Zcash Testnet via ChainSafe's public gRPC-web proxy for lightwalletd: https://zcash-testnet.chainsafe.dev (proxies testnet.zec.rocks:443).
  • COOP/COEP headers (see next.config.ts) enable SharedArrayBuffer for multithreaded Wasm (wasm-bindgen-rayon thread pool + sync web worker).

Building WebZjs from source

@chainsafe/webzjs-wallet is not published to npm; it must be built from https://github.com/ChainSafe/WebZjs (commands from its justfile):

Prerequisites: Rust nightly-2025-01-07 (+ rust-src, wasm32-unknown-unknown), wasm-pack ≥ 0.13, clang ≥ 17.

git clone https://github.com/ChainSafe/WebZjs webzjs && cd webzjs
rustup toolchain install nightly-2025-01-07 --component rust-src --target wasm32-unknown-unknown

cd crates/webzjs-wallet
CC=clang-18 AR=llvm-ar-18 CC_wasm32_unknown_unknown=clang-18 AR_wasm32_unknown_unknown=llvm-ar-18 \
wasm-pack build -t web --release --scope chainsafe --out-dir ../../packages/webzjs-wallet \
  --no-default-features --features="wasm wasm-parallel" -Z build-std="panic_abort,std"
cd ../.. && ./add-worker-module.sh

Copy packages/webzjs-wallet/ into this repo's public/webzjs/webzjs-wallet/, then apply two one-line patches (required because we load the wasm-pack -t web output directly from /public without a bundler — both files import the package directory, which only bundlers can resolve):

  • snippets/wasm-bindgen-rayon-*/src/workerHelpers.js: import('../../..') → import('../../../webzjs_wallet.js')
  • snippets/wasm_thread-*/src/wasm32/js/web_worker_module.bundler.js: from "../../../../../" → from "../../../../../webzjs_wallet.js"

Without the second patch, wallet.sync() hangs forever (its web worker fails silently).

Running locally

npm install
npm run dev   # http://localhost:3000

npm run build produces the Vercel-compatible production build.

Getting testnet funds (TAZ)

Use the Zcash Foundation faucet at https://fauzec.com — paste the unified address (utest1...) shown by the app. 1 TAZ per address per 24h, delivered shielded. TAZ has no monetary value. Never enter a mainnet address or real funds.

What the wallet layer does

  1. Generates (or restores) a testnet wallet fully client-side; shows the unified address.
  2. Fetches the current chain tip from the lightwalletd proxy (used as wallet birthday).
  3. Syncs with testnet and shows Orchard/Sapling/transparent balances.
  4. Sends a small shielded transaction (propose → prove+sign in a browser web worker → broadcast), only to utest1.../ztestsapling1... recipients, and links the txid to https://testnet.zcashexplorer.app.

Security invariants

  • Seed/spending keys never leave the browser; no server or LLM ever sees them.
  • No wallet code runs in a serverless/API route; the repo has none.
  • Mainnet addresses are rejected; the network is hardcoded to "test".

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages