惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Troy Hunt's Blog
Blog — PlanetScale
Blog — PlanetScale
Engineering at Meta
Engineering at Meta
F
Full Disclosure
Recorded Future
Recorded Future
The GitHub Blog
The GitHub Blog
Microsoft Security Blog
Microsoft Security Blog
GbyAI
GbyAI
博客园_首页
博客园 - 叶小钗
MongoDB | Blog
MongoDB | Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Recent Commits to openclaw:main
Recent Commits to openclaw:main
H
Hacker News: Front Page
人人都是产品经理
人人都是产品经理
The Cloudflare Blog
博客园 - 司徒正美
Webroot Blog
Webroot Blog
Google DeepMind News
Google DeepMind News
Help Net Security
Help Net Security
Cloudbric
Cloudbric
PCI Perspectives
PCI Perspectives
有赞技术团队
有赞技术团队
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
TaoSecurity Blog
TaoSecurity Blog
L
Lohrmann on Cybersecurity
量子位
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tailwind CSS Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
B
Blog RSS Feed
Apple Machine Learning Research
Apple Machine Learning Research
大猫的无限游戏
大猫的无限游戏
P
Proofpoint News Feed
N
News and Events Feed by Topic
罗磊的独立博客
T
Threat Research - Cisco Blogs
Schneier on Security
Schneier on Security
T
Tor Project blog
IT之家
IT之家
M
MIT News - Artificial intelligence
S
Security @ Cisco Blogs
O
OpenAI News
AI
AI
S
Securelist
Simon Willison's Weblog
Simon Willison's Weblog
The Last Watchdog
The Last Watchdog
月光博客
月光博客
Security Archives - TechRepublic
Security Archives - TechRepublic
L
LINUX DO - 热门话题

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Private AI Inference on Midnight: Keep Your Data Secret, Prove Your Output is Real
Peter Maina · 2026-04-23 · via DEV Community

Introduction

There is a quiet problem at the heart of modern AI: the data powering personalised outputs often belongs to someone who never consented to share it. Artists, writers, musicians their work gets swept into training pipelines, their style replicated, and they receive nothing in return.

But what if it didn't have to work that way?

This tutorial shows you how to use Midnight a privacy-first blockchain built by IOG to run AI inference on private data and produce a cryptographic proof that the output is authentic. The user's private context never leaves their machine. The blockchain only ever sees a commitment and a proof.

By the end you will know how to:

  • Encrypt a private data vault locally
  • Generate a cryptographic Merkle commitment to that data
  • Run AI model inference using the private data as context
  • Register an artist profile on a Midnight Compact smart contract
  • Have consumers pay in DUST and receive a verified, authentic AI output

Why Midnight?

Midnight is purpose-built for exactly this kind of problem. A few key properties that matter here:

Compact is Midnight's TypeScript-inspired smart contract language. It compiles directly to ZK circuits you write business logic, the compiler handles the cryptography.

Witnesses are Compact's mechanism for private inputs. A witness is a function that runs off-chain with access to private data. It constructs a ZK proof using data that never reaches the public network.

DUST is Midnight's transaction token. DUST transactions are shielded metadata like sender, receiver, and amount are all hidden. This means even the payment for an AI query can be made privately.

NIGHT is Midnight's staking and governance token, used by node operators and for ecosystem participation.

The result: the contract sets the rules, your app runs the inference locally, and Midnight verifies the outcome — without ever seeing the sensitive inputs.

1. Architecture Overview

Key differences from a generic EVM approach:

2. Prerequisites and Setup

Install the Midnight Toolchain

# Install the Compact compiler (check docs.midnight.network for latest version)
# Download from: https://github.com/midnightntwrk/compact/releases
# Add the compact binary to your PATH

compact --version
# compact 0.22.x

Enter fullscreen mode Exit fullscreen mode

Install Node.js Dependencies

node --version   # requires v22+

mkdir private-ai-midnight && cd private-ai-midnight
yarn init -y

yarn add @midnight-ntwrk/midnight-js-contracts \
         @midnight-ntwrk/midnight-js-network-id \
         @midnight-ntwrk/midnight-js-types \
         @midnight-ntwrk/wallet \
         typescript ts-node

Enter fullscreen mode Exit fullscreen mode

Project Structure

private-ai-midnight/
├── contracts/
│   └── art-style-marketplace.compact    # Compact smart contract
├── src/
│   ├── vault.ts          # Encrypted private data vault
│   ├── commitment.ts     # Merkle commitment
│   ├── inference.ts      # Local AI inference
│   ├── deploy.ts         # Deploy the Compact contract
│   └── consumer.ts       # Consumer: pay and verify
├── package.json
└── tsconfig.json

Enter fullscreen mode Exit fullscreen mode

Start Local Devnet

# Clone the Midnight starter environment
git clone https://github.com/midnightntwrk/example-hello-world.git devnet
cd devnet && yarn install

# Start the local proof server and devnet (Docker required)
yarn env:up

Enter fullscreen mode Exit fullscreen mode


3. Step 1 Build the Private Data Vault

The vault stores all private data locally using AES-256-GCM encryption. Nothing leaves this vault unencrypted.

// src/vault.ts
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';

const ALGORITHM = 'aes-256-gcm';

export interface VaultRecord {
  artistId: string;
  styleEmbeddings: number[];   // private — artist's style vectors
  description: string;
}

export class PrivateVault {
  private key: Buffer;

  constructor(key?: Buffer) {
    this.key = key ?? randomBytes(32); // 256-bit key
  }

  static fromKey(keyHex: string): PrivateVault {
    return new PrivateVault(Buffer.from(keyHex, 'hex'));
  }

  getKeyHex(): string {
    return this.key.toString('hex');
  }

  encrypt(record: VaultRecord): Buffer {
    const nonce = randomBytes(12);          // 96-bit nonce for GCM
    const cipher = createCipheriv(ALGORITHM, this.key, nonce);
    const plaintext = JSON.stringify(record);
    const ciphertext = Buffer.concat([
      cipher.update(plaintext, 'utf8'),
      cipher.final(),
    ]);
    const authTag = cipher.getAuthTag();    // GCM authentication tag
    // Layout: [nonce (12)] [authTag (16)] [ciphertext]
    return Buffer.concat([nonce, authTag, ciphertext]);
  }

  decrypt(blob: Buffer): VaultRecord {
    const nonce = blob.subarray(0, 12);
    const authTag = blob.subarray(12, 28);
    const ciphertext = blob.subarray(28);
    const decipher = createDecipheriv(ALGORITHM, this.key, nonce);
    decipher.setAuthTag(authTag);
    const plaintext = Buffer.concat([
      decipher.update(ciphertext),
      decipher.final(),
    ]).toString('utf8');
    return JSON.parse(plaintext) as VaultRecord;
  }
}

// Usage (artist one-time setup)
const vault = new PrivateVault();
const record: VaultRecord = {
  artistId: 'alice_impressionist_v1',
  styleEmbeddings: [0.12, 0.87, 0.43, /* ... 512-dim vector */],
  description: 'Watercolour impressionist portfolio — 300 pieces',
};

const blob = vault.encrypt(record);
// Save: blob → vault.bin  |  vault.getKeyHex() → vault.key
// NEVER share vault.key

Enter fullscreen mode Exit fullscreen mode

4. Step 2 Generate a Merkle Commitment

The Merkle root is the only thing that goes on-chain. It anchors the artist's private data to a public, verifiable identity without revealing any content.

// src/commitment.ts
import { createHash } from 'crypto';

function sha256(data: Buffer): Buffer {
  return createHash('sha256').update(data).digest();
}

export function buildMerkleRoot(leaves: Buffer[]): string {
  if (leaves.length === 0) throw new Error('Need at least one leaf');

  let layer = leaves.map(l => sha256(l));

  while (layer.length > 1) {
    if (layer.length % 2 === 1) layer.push(layer[layer.length - 1]);
    const next: Buffer[] = [];
    for (let i = 0; i < layer.length; i += 2) {
      next.push(sha256(Buffer.concat([layer[i], layer[i + 1]])));
    }
    layer = next;
  }

  return layer[0].toString('hex');
}

export function commitToVault(records: object[]): string {
  /**
   * Commits to record METADATA only — not raw embeddings.
   * The root is published on-chain via the Compact contract.
   *
   * Example records: [{ embeddingId: 'style_001', shape: [512] }]
   */
  const leaves = records.map(r =>
    Buffer.from(JSON.stringify(r, Object.keys(r).sort()), 'utf8')
  );
  return buildMerkleRoot(leaves);
}

// Usage
const merkleRoot = commitToVault([
  { embeddingId: 'style_001', artistId: 'alice', shape: [512] },
  { embeddingId: 'style_002', artistId: 'alice', shape: [512] },
]);
console.log('Public commitment (goes on-chain):', merkleRoot);

Enter fullscreen mode Exit fullscreen mode

5. Step 3 Run Local AI Inference

The model runs entirely on the artist's machine. The private style embedding is loaded from the vault and used for inference. Only the output is shared never the style embedding.

// src/inference.ts
import { PrivateVault } from './vault';
import { createHash } from 'crypto';
import { readFileSync } from 'fs';

/**
 * StyleAdapter — fuses a public prompt embedding with
 * a private style embedding to produce a conditioned output.
 *
 * In production: replace with a real ONNX model or LoRA adapter.
 * The key invariant is: style_emb comes from vault, never from network.
 */
function styleAdapter(
  promptEmb: number[],
  styleEmb: number[],
): number[] {
  // Simplified: weighted combination (replace with real model)
  return promptEmb.map((p, i) => 0.6 * p + 0.4 * (styleEmb[i] ?? 0));
}

export interface InferenceResult {
  outputEmbedding: number[];
  outputHash: string;      // hash of output — used as public input to ZK proof
  commitmentRoot: string;  // artist's Merkle root — anchors to on-chain identity
}

export function runPrivateInference(
  promptEmbedding: number[],
  vaultBlob: Buffer,
  vaultKeyHex: string,
  commitmentRoot: string,
): InferenceResult {
  // 1. Load private style from vault (stays in memory only)
  const vault = PrivateVault.fromKey(vaultKeyHex);
  const record = vault.decrypt(vaultBlob);
  const styleEmb = record.styleEmbeddings;

  // 2. Run inference locally (private data NEVER leaves this function scope)
  const outputEmbedding = styleAdapter(promptEmbedding, styleEmb);

  // 3. Hash the output (public input for ZK verification)
  const outputHash = createHash('sha256')
    .update(JSON.stringify(outputEmbedding))
    .digest('hex');

  // style_emb is garbage-collected after this return — never persisted
  return { outputEmbedding, outputHash, commitmentRoot };
}

Enter fullscreen mode Exit fullscreen mode


6. Step 4 Write the Compact Smart Contract

This is the heart of the Midnight integration. The Compact contract stores the artist's commitment on-chain and verifies that inference outputs are authentic using a witness Midnight's native mechanism for private input proofs.

// contracts/art-style-marketplace.compact
pragma language_version >= 0.22;

import CompactStandardLibrary;

// ── On-chain state (public, persistent on Midnight ledger) 

// Registered artist profiles
export ledger artistCommitments: Map<Bytes<32>, ArtistProfile>;

// Used proof hashes (prevents replay attacks)
export ledger usedProofs: Set<Bytes<32>>;

// Total queries processed
export ledger totalQueries: Counter;

//  Data types 

struct ArtistProfile {
  commitment: Bytes<32>,       // Merkle root of private data vault
  pricePerQuery: Uint<64>,     // price in tDUST (smallest unit)
  active: Boolean,
  totalEarned: Uint<64>,
  totalQueries: Counter,
}

//  Witness (runs off-chain with private data) 

/**
 * The witness is the key privacy primitive.
 * It runs locally on the artist's machine, accesses the private vault,
 * and produces a proof that inference ran correctly — without any
 * private data reaching the blockchain.
 *
 * Inputs (private, never on-chain):
 *   - styleEmbedding: the artist's private style vector
 *   - promptEmbedding: the consumer's prompt vector
 *
 * Outputs (public, verified on-chain):
 *   - outputHash: SHA-256 of the inference output
 *   - commitmentRoot: artist's Merkle root (must match ledger)
 */
witness inferenceWitness(
  styleEmbedding: Bytes<64>,    // private — from encrypted vault
  promptEmbedding: Bytes<64>,   // public prompt, encoded as bytes
): { outputHash: Bytes<32>, commitmentRoot: Bytes<32> };

// Entry points (circuits)

/**
 * Artist registers their commitment on-chain.
 * Only the Merkle root is stored — no raw data.
 */
export circuit registerArtist(
  commitment: Bytes<32>,
  pricePerQuery: Uint<64>,
): [] {
  const artistKey = disclose(commitment);

  assert(!artistCommitments.member(artistKey), "Commitment already registered");
  assert(pricePerQuery > 0, "Price must be positive");

  artistCommitments.insert(artistKey, ArtistProfile {
    commitment: artistKey,
    pricePerQuery: disclose(pricePerQuery),
    active: true,
    totalEarned: 0,
    totalQueries: Counter.new(0),
  });
}

/**
 * Consumer submits a prompt and triggers proof verification.
 * The witness runs off-chain; the contract verifies the proof on-chain.
 * Payment in DUST is handled by the Midnight runtime.
 */
export circuit purchaseAndVerify(
  artistCommitment: Bytes<32>,
  promptEmbedding: Bytes<64>,
): Bytes<32> {
  // 1. Look up the registered artist
  assert(
    artistCommitments.member(artistCommitment),
    "Artist not registered"
  );
  const profile = artistCommitments.lookup(artistCommitment);
  assert(profile.active, "Artist profile is inactive");

  // 2. Run the witness off-chain (private inference + proof generation)
  //    The witness accesses the vault locally; only outputHash and
  //    commitmentRoot are returned to the on-chain circuit.
  const { outputHash, commitmentRoot } = inferenceWitness(
    /* styleEmbedding — provided by artist's local DApp, never on-chain */,
    promptEmbedding,
  );

  // 3. Verify the commitment matches the registered artist
  assert(
    commitmentRoot == artistCommitment,
    "Proof commitment does not match registered artist"
  );

  // 4. Prevent proof replay
  assert(!usedProofs.member(outputHash), "Output hash already used");
  usedProofs.insert(outputHash);

  // 5. Update counters
  totalQueries.increment(1);
  const updatedProfile = ArtistProfile {
    commitment: profile.commitment,
    pricePerQuery: profile.pricePerQuery,
    active: profile.active,
    totalEarned: profile.totalEarned + profile.pricePerQuery,
    totalQueries: profile.totalQueries,
  };
  artistCommitments.insert(artistCommitment, updatedProfile);

  // 6. Return the verified output hash to the consumer
  //    Consumer uses this to confirm the output they received is authentic
  return outputHash;
}

/**
 * Artist can deactivate their profile.
 */
export circuit deactivateArtist(commitment: Bytes<32>): [] {
  const key = disclose(commitment);
  assert(artistCommitments.member(key), "Not registered");
  const profile = artistCommitments.lookup(key);
  artistCommitments.insert(key, ArtistProfile {
    commitment: profile.commitment,
    pricePerQuery: profile.pricePerQuery,
    active: false,
    totalEarned: profile.totalEarned,
    totalQueries: profile.totalQueries,
  });
}

/**
 * Anyone can look up an artist profile by their commitment.
 */
export circuit getArtistProfile(commitment: Bytes<32>): ArtistProfile {
  assert(artistCommitments.member(commitment), "Artist not found");
  return artistCommitments.lookup(commitment);
}

Enter fullscreen mode Exit fullscreen mode

Compile the Contract

compact compile \
  contracts/art-style-marketplace.compact \
  contracts/managed/art-style-marketplace.compact

Enter fullscreen mode Exit fullscreen mode

This generates:

  • ZK circuits for every circuit entry point
  • Proving and verification keys in contracts/managed/keys/
  • TypeScript API in contracts/managed/contract/

7. Step 5 Connect with Midnight.js

// src/deploy.ts
import {
  ContractAddress,
  deployContract,
  findContractDeployment,
} from '@midnight-ntwrk/midnight-js-contracts';
import { NetworkId } from '@midnight-ntwrk/midnight-js-network-id';
import { WalletBuilder } from '@midnight-ntwrk/wallet';
import { commitToVault } from './commitment';

// The compiled contract artifacts (generated by `compact compile`)
import { ArtStyleMarketplaceContract } from '../contracts/managed/art-style-marketplace.compact/contract';

async function deployMarketplace() {
  // 1. Connect wallet (pre-funded devnet wallet for local testing)
  const wallet = await WalletBuilder.buildFromSeed(
    'http://localhost:9000',   // proof server
    'http://localhost:8080',   // indexer
    NetworkId.TestNet,
    process.env.WALLET_SEED!,
  );

  await wallet.sync();
  console.log('Wallet synced. DUST balance:', await wallet.balanceDust());

  // 2. Deploy the Compact contract
  const contract = new ArtStyleMarketplaceContract();
  const deployment = await deployContract(wallet, contract);

  console.log('Contract deployed at:', deployment.contractAddress);
  return deployment.contractAddress;
}

async function registerArtist(
  contractAddress: ContractAddress,
  vaultRecords: object[],
  pricePerQuery: bigint,
) {
  const wallet = await WalletBuilder.buildFromSeed(/* ... */);
  await wallet.sync();

  // Compute Merkle commitment from vault metadata (not raw data)
  const merkleRoot = commitToVault(vaultRecords);
  const commitmentBytes = Buffer.from(merkleRoot, 'hex');

  // Call the Compact contract entry point
  const contract = new ArtStyleMarketplaceContract();
  const deployment = await findContractDeployment(wallet, contract, contractAddress);

  await deployment.callTx.registerArtist(
    commitmentBytes,
    pricePerQuery,  // in tDUST
  );

  console.log('Artist registered with commitment:', merkleRoot);
  console.log('Price per query:', pricePerQuery, 'tDUST');
}

export { deployMarketplace, registerArtist };

Enter fullscreen mode Exit fullscreen mode

8. Step 6 Consumer Flow: Pay and Verify

// src/consumer.ts
import { findContractDeployment } from '@midnight-ntwrk/midnight-js-contracts';
import { WalletBuilder } from '@midnight-ntwrk/wallet';
import { ArtStyleMarketplaceContract } from '../contracts/managed/art-style-marketplace.compact/contract';
import { runPrivateInference } from './inference';
import { createHash } from 'crypto';

/**
 * Consumer flow:
 * 1. Look up artist profile on-chain
 * 2. Send prompt to artist's service (off-chain)
 * 3. Receive output embedding + outputHash from artist
 * 4. Call purchaseAndVerify — Midnight verifies the ZK proof
 * 5. Confirm the on-chain outputHash matches what artist sent
 */
async function purchaseInference(
  contractAddress: string,
  artistCommitment: string,
  promptEmbedding: number[],
) {
  // 1. Connect consumer wallet
  const wallet = await WalletBuilder.buildFromSeed(
    'http://localhost:9000',
    'http://localhost:8080',
    /* NetworkId.TestNet, */
    process.env.CONSUMER_WALLET_SEED!,
  );
  await wallet.sync();

  const contract = new ArtStyleMarketplaceContract();
  const deployment = await findContractDeployment(
    wallet, contract, contractAddress
  );

  // 2. Encode prompt as bytes for the contract
  const promptBytes = Buffer.from(JSON.stringify(promptEmbedding));

  // 3. Call purchaseAndVerify
  //    - Midnight routes the DUST payment privately
  //    - The witness runs on the artist's machine (off-chain)
  //    - The on-chain circuit verifies commitment + proof
  //    - Returns the verified outputHash
  const verifiedOutputHash = await deployment.callTx.purchaseAndVerify(
    Buffer.from(artistCommitment, 'hex'),
    promptBytes,
  );

  console.log(' Proof verified on-chain!');
  console.log('Verified output hash:', Buffer.from(verifiedOutputHash).toString('hex'));

  return verifiedOutputHash;
}

/**
 * After receiving the output embedding from the artist off-chain,
 * the consumer can confirm it matches the on-chain verified hash.
 */
function confirmOutputAuthenticity(
  outputEmbedding: number[],
  verifiedOutputHash: Uint8Array,
): boolean {
  const localHash = createHash('sha256')
    .update(JSON.stringify(outputEmbedding))
    .digest();

  const matches = Buffer.from(verifiedOutputHash).equals(localHash);

  if (matches) {
    console.log(' Output is authentic — hash matches on-chain verification');
  } else {
    console.log('Output hash mismatch — do not trust this output');
  }

  return matches;
}

export { purchaseInference, confirmOutputAuthenticity };

Enter fullscreen mode Exit fullscreen mode

9. The Artist Use Case End-to-End

Here is the complete journey for an artist monetising their style on Midnight.

Artist: One-Time Setup

// scripts/artist-setup.ts
import { PrivateVault } from '../src/vault';
import { deployMarketplace, registerArtist } from '../src/deploy';
import { writeFileSync } from 'fs';

async function setup() {
  // 1. Create and encrypt the private vault
  const vault = new PrivateVault();
  const record = {
    artistId: 'alice_impressionist',
    styleEmbeddings: [/* 512-dim vector from CLIP or fine-tuned model */],
    description: 'Watercolour impressionist portfolio',
  };
  const blob = vault.encrypt(record);

  writeFileSync('vault.bin', blob);
  writeFileSync('vault.key', vault.getKeyHex(), 'utf8');
  console.log('Vault encrypted and saved.');

  // 2. Deploy the marketplace contract (once per platform)
  const contractAddress = await deployMarketplace();
  writeFileSync('contract-address.txt', contractAddress, 'utf8');

  // 3. Register artist on-chain with Merkle commitment
  await registerArtist(
    contractAddress,
    [{ embeddingId: 'style_001', shape: [512] }],
    BigInt(1_000_000),  // 1 tDUST per query
  );

  console.log('Artist registered on Midnight.');
  console.log('Contract:', contractAddress);
}

setup();

Enter fullscreen mode Exit fullscreen mode

Consumer: Per-Query Flow

// scripts/consumer-query.ts
import { purchaseInference, confirmOutputAuthenticity } from '../src/consumer';
import { readFileSync } from 'fs';

async function query() {
  const contractAddress = readFileSync('contract-address.txt', 'utf8');
  const artistCommitment = process.env.ARTIST_COMMITMENT!;

  // Consumer's prompt (e.g., from a text embedding model)
  const promptEmbedding = Array.from({ length: 512 }, () => Math.random() - 0.5);

  // 1. Call purchaseAndVerify on Midnight
  //    DUST payment routed privately
  //    Witness runs on artist's machine (private inference)
  //    Contract verifies proof on-chain
  const verifiedHash = await purchaseInference(
    contractAddress,
    artistCommitment,
    promptEmbedding,
  );

  // 2. Artist sends output embedding off-chain (e.g., via API)
  const outputEmbedding = [/* received from artist's service */];

  // 3. Confirm authenticity
  const authentic = confirmOutputAuthenticity(outputEmbedding, verifiedHash);
  console.log('Authentic:', authentic);
}

query();

Enter fullscreen mode Exit fullscreen mode

What Each Party Knows

Party Knows
Artist Everything private vault, style embeddings, model
Consumer Prompt, output embedding, verified output hash
Midnight ledger Merkle commitment, verified output hash, query count
Anyone That artist registered with a given commitment. Nothing else.
Model provider Nothing inference is fully local

10. Key Takeaways

After working through this tutorial you now understand how to:

Create private context on Midnight Encrypt a vault with AES-256 locally, commit to it with a Merkle root, and register that root on a Midnight Compact contract. The raw data never leaves the artist's device.

Use Compact witnesses for private inference The witness construct in Compact is Midnight's native mechanism for off-chain private computation. The witness runs locally, accesses private inputs, and returns only the cryptographically safe outputs to the on-chain circuit.

Accept shielded payments with DUST Midnight routes DUST transactions with full privacy sender, receiver, and amount are all hidden. Consumers pay for inference without revealing their identity.

Verify outputs without seeing private data The on-chain outputHash gives consumers a cryptographic guarantee that the output was authentically generated from the registered artist's private data.

What to do next:

Midnight makes privacy a standard engineering resource not a cryptography research problem. Private AI inference is buildable today, with tools developers already know.