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

推荐订阅源

Cloudbric
Cloudbric
有赞技术团队
有赞技术团队
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
T
Threat Research - Cisco Blogs
L
LangChain Blog
Simon Willison's Weblog
Simon Willison's Weblog
Project Zero
Project Zero
Latest news
Latest news
S
Schneier on Security
Cisco Talos Blog
Cisco Talos Blog
MyScale Blog
MyScale Blog
C
Check Point Blog
IT之家
IT之家
P
Palo Alto Networks Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
C
CERT Recently Published Vulnerability Notes
Scott Helme
Scott Helme
The Hacker News
The Hacker News
C
CXSECURITY Database RSS Feed - CXSecurity.com
G
Google Developers Blog
T
Tor Project blog
T
Threatpost
D
DataBreaches.Net
博客园 - 【当耐特】
酷 壳 – CoolShell
酷 壳 – CoolShell
T
Troy Hunt's Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Vercel News
Vercel News
云风的 BLOG
云风的 BLOG
NISL@THU
NISL@THU
P
Privacy & Cybersecurity Law Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
C
Cisco Blogs
博客园_首页
S
Securelist
T
The Exploit Database - CXSecurity.com
Last Week in AI
Last Week in AI
量子位
U
Unit 42
Know Your Adversary
Know Your Adversary
Hugging Face - Blog
Hugging Face - Blog
S
Security Affairs
Google Online Security Blog
Google Online Security Blog
Hacker News: Ask HN
Hacker News: Ask HN
Webroot Blog
Webroot Blog
S
SegmentFault 最新的问题
Engineering at Meta
Engineering at Meta
N
News and Events Feed by Topic
P
Proofpoint News Feed
阮一峰的网络日志
阮一峰的网络日志

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
Selective Disclosure Patterns in Compact
Akanji Rahma · 2026-05-03 · via DEV Community

Nowadays blockchains treat privacy like it's just an extra. Just an additional feature. Everything is visible and public. This architecture works for systems where public transparency is the end goal. But what happens when you begin to handle big data, medical records or sensitive information? It collapses. Full transparency and accountability is not a feature in these contexts, it's a problem.

Table of contents

Prerequisites

This is an intermediate tutorial. Before reading, you should be comfortable with:

  • Basic Compact syntax — you understand what witness, circuit, ledger, and export declarations do. If not, start with the Compact language reference.
  • The Compact compiler installed — follow the installation guide to set it up.
  • Zero-knowledge proofs at a conceptual level — you understand that a ZK proof lets you prove a statement is true without revealing the underlying data.
  • Familiarity with a typed language like TypeScript — Compact's syntax is modelled on TypeScript, so comfort with typed functions, structs, and return types will help.

How does Midnight solve this problem?

Midnight implements a solution known as "Selective Disclosure". This is the ability of users to choose what information to reveal and who this information can be revealed to without revealing the full data.

For example, instead of displaying "Rahman's balance is 200,000 USDT", a Midnight DApp can publish a zero-knowledge proof that says "Rahman's balance exceeds the required threshold" and nothing more. This has solved two major issues efficiently; The sensitive data stays private and the important and verifiable fact becomes public. This is the foundation of Programmable privacy on Midnight.

This tutorial walks you through how Selective disclosure works in Compact, which is Midnight's Typescript inspired smart contract language. The knowledge you'll get from reading this article include:

a). Learning the mechanics of the disclose() operator.
b). Understanding what is safe to disclose as opposed to what silently leaks privacy.
c). Applying domain-separated hashing to block cross-property linkability.
d). Working through a privacy audit checklist you can run on your own contracts before deployment.

Understanding the privacy model

Before we get into the coding technicalities, how does Compact even work?

Every Compact Contract operates across two kinds of state. The Public State and The Private State.

The Public State: This is the data that is written to the onchain ledger which is open and visible to all network participants.
The Private State: This is the data that lives only on the user's computer and is never exposed to the network. It comes from 'witness' functions that your DApp provides locally, and from any value derived from those witness values.
The zero-knowledge proof generated by a circuit proves that the computation was performed correctly, without leaking the witness values involved. The major guarantee is that the network learns that the rule was followed, not what values were used.

Compact uses a compiler's witness protection program which is an interpreter that tracks which values contain witness data and rejects any program that would disclose sensitive data without an explicit declaration. Privacy becomes the default and disclosure is an exception you must deliberately request.

Using disclose() correctly

The disclose() wrapper is how you tell the Compact compiler: "I know this value contains private data, and I am intentionally making it public." Without it, the compiler stops, displaying a detailed error. Below is a simple example of this; recording a private balance on-chain:

pragma language_version >= 0.16 && <= 0.22;

import CompactStandardLibrary;

witness getBalance(): Bytes<32>;
export ledger balance: Bytes<32>;

export circuit recordBalance(): [] {
balance = disclose(getBalance());
}

Enter fullscreen mode Exit fullscreen mode

If you remove the disclose() wrapper, the compiler immediately rejects the program:

Exception: selective.compact line 9 char 9:
  potential witness-value disclosure must be declared but is not:
    witness value potentially disclosed:
      the return value of witness getBalance at line 5 char 1
    nature of the disclosure:
      ledger operation might disclose the witness value
    via this path through the program:
      the right-hand side of = at line 9 char 9

Enter fullscreen mode Exit fullscreen mode

The error message is precise. It names the witness function that is the source of the data, explains the nature of the disclosure, and traces the full path through the program. This is because the compiler wants you to understand exactly what you are declaring when you add the disclose() wrapper.

It is important to note that placing a disclose() wrapper does not cause disclosure in itself. It simply tells the compiler to treat the wrapped expression as if it does not contain witness data. The actual disclosure happens at the storage or return point.

disclose() is the declaration, not the action.

Placing disclose() at the right scope

For simple values, place disclose() as close to the storage or return point as possible. This is important because it minimizes the scope of the disclosure declaration and makes it more difficult to accidentally reuse a disclosed value in a privacy sensitive context elsewhere in the circuit.

For structured values such as tuples, vectors or structs, wrap only the field you intend to disclose:

pragma language_version >= 0.16 && <= 0.22;

import CompactStandardLibrary;

struct UserRecord {
publicName: Bytes<32>;
privateScore: Uint<64>;
}

witness getUserRecord(): UserRecord;
export ledger displayName: Bytes<32>;

export circuit publishName(): [] {
const record = getUserRecord();
// Only disclose the name field, not the entire struct
displayName = disclose(record.publicName);
}

Enter fullscreen mode Exit fullscreen mode

This pattern is important because wrapping the entire struct in a single disclose() would declare the privateScore field as publicly disclosed too even though you never store it onchain. The compiler would accept it, but you would be declaring an intention that does not match your actual privacy requirements.

Disclosure through conditional expressions

The compiler also catches indirect disclosure through boolean comparisons. This is one of the most common unintentional privacy leaks in zero-knowledge contracts:

import CompactStandardLibrary;
witness getBalance(): Uint<64>;

// This will NOT compile — the comparison result leaks the witness value
export circuit balanceExceeds(n: Uint<64>): Boolean {
return getBalance() > n;
}

Enter fullscreen mode Exit fullscreen mode

Exception: selective.compact line 6 char 1:
  potential witness-value disclosure must be declared but is not:
    witness value potentially disclosed:
      the return value of witness getBalance at line 2 char 1
    nature of the disclosure:
      the value returned from exported circuit balanceExceeds might disclose the result of a
      comparison involving the witness value
    via this path through the program:
      the comparison at line 6 char 8

Enter fullscreen mode Exit fullscreen mode

Returning a boolean derived from a private value is still a disclosure as the result narrows the range of possible the attacker needs to test. If you intend to return this boolean, you must declare it:

import CompactStandardLibrary;
witness getBalance(): Uint<64>;

export circuit balanceExceeds(n: Uint<64>): Boolean {
return disclose(getBalance()) > n;
}

Enter fullscreen mode Exit fullscreen mode

Whether this is appropriate depends on your use case. If you are building a compliance gate (e.g "balance exceeds the minimum required to participate"), returning the boolean is intentional and acceptable. If you are building a privacy-preserving wallet, returning it leaks information about the user's balance range with every call.

What's safe to disclose vs what leaks privacy

Not all disclosures are equal. Some values are safe to publish because they carry no meaningful information about the underlying private data. Others appear harmless but silently narrow the privacy guarantees your DApp advertises. So how do we differentiate them?

Safe to disclose

Derived commitments and hashes (with correct library functions).

The Compact standard library includes functions the compiler recognizes as cryptographically sound transformations. When you wrap witness data in transientCommit(), the compiler treats the output as not containing witness data, which means no explicit disclose() is required for a commitment:

import CompactStandardLibrary;

witness getSecretValue(): Field;
witness getNonce(): Field;
export ledger commitment: Field;

export circuit commitValue(): [] {
const secret = getSecretValue();
const nonce = getNonce();
// transientCommit output is treated as non-witness by the compiler
commitment = transientCommit(secret, nonce);
}

Enter fullscreen mode Exit fullscreen mode

An important distinction between these four functions: the compiler treats transientCommit() output as not containing witness data, so no disclose() is required on the result. transientHash(), however, is NOT considered sufficient to protect witness data — if the input came from a witness, the output still requires disclose() before it can be stored in the ledger or returned from an exported circuit.

The transient variants (transientCommit and transientHash) are circuit-efficient but their algorithm is not guaranteed to stay consistent across network upgrades, so they should not be used to derive state data that needs to be verified later. The persistent variants (persistentCommit and persistentHash) use SHA-256 and are guaranteed to remain consistent across upgrades, so use these for any values stored in ledger state.

Aggregate results that do not reveal individual values

Storing a count of transactions or a running total where the per transaction values remain private is generally safe, provided the aggregate does not itself narrow the input range to be exploitable.

Public keys derived from private keys through domain-separated hashing

This pattern is used throughout the official examples. You derive a public identifier from a private key, publish the public identifier, and keep the private key local. Because the hash function is one-way and hard to invert, the public key reveals nothing about the private key. The section on domain-separated hashing covers this pattern in depth.

What actually leaks privacy? (even with disclose())?

Raw witness values

Storing disclose(getBalance()) directly onchain publishes the balance in plaintext. This is appropriate when the value is intended to be public (e.g a display name, a transaction amount for a public ledger), but it is a complete disclosure of the underlying data.

Arithmetic on witness values

Adding, subtracting, or scaling a private value before disclosure does not hide it cryptographically. The Compact compiler correctly rejects this without a disclose() wrapper, but the real risk is developers who add disclose() and assume the arithmetic hides the value. It does not. Here's what it does:

// Dangerous — disclosing an offset balance still reveals the balance
balance = disclose(getBalance() + 73);

Enter fullscreen mode Exit fullscreen mode

If an attacker knows the offset which in this case is 73, they recover the original value immediately.

Boolean outputs from comparisons on private data

Exposing true/false results of comparisons on confidential values enables progressive disclosure of sensitive information. An attacker who can call the circuit many times with different values can binary-search their way to the exact private value through repeated queries. If you must expose a threshold check, consider rate-limiting at the DApp layer and documenting the tradeoff clearly.

Enum variants derived from private logic

Returning a value that is state constant or a State enum value such as 'Pending', 'Rejected', etc that was computed from sensitive data can leak information about the data depending on the number of states that exist and how they map to the input space.

Applying domain-separated hashing for cross-property unlinkability

One of the more delicate privacy risks in zero knowledge contracts is cross-property linkability which is what occurs when two different disclosures from the same private identity can be correlated by an observer even if neither disclosure reveals the identity itself. For example, Rahman reveals two things in separate transactions: a proof that his KYC tier is "premium" and a proof that his account is over 10 months old. Each proof reveals nothing individually, but if both proofs are signed with the same public key or derived from the same hash of the Rahman's identity, an observer can link the two transactions to Rahman, reconstructing a profile without ever learning the underlying identity.

How do we resolve this? Instead of deriving a single public key from a user's secret key, you derive a different public key for each purpose (or domain) by including a domain tag in the hash input. Two hashes with different domain tags, even for the same secret, produce completely unrelated outputs.

How does this relate to Compact?

The previous examples use this pattern directly. The publicKey circuit in the lock example incorporates a domain tag as part of the hash:

pragma language_version >= 0.16 && <= 0.22;

import CompactStandardLibrary;

circuit publicKey(round: Field, sk: Bytes<32>): Bytes<32> {
return persistentHash<Vector<3, Bytes<32>>>(
[pad(32, "midnight:examples:lock:pk"), round as Bytes<32>, sk]
);
}

Enter fullscreen mode Exit fullscreen mode

The string midnight:examples:lock:pk is the domain tag. It is namespaced to prevent collision with other contracts that might use the same hash function on the same secret key. This pattern can be extended across different properties of the same identity:

pragma language_version >= 0.16 && <= 0.22;

import CompactStandardLibrary;

// Produce an unlinkable key for KYC tier disclosure
circuit kycTierKey(round: Field, sk: Bytes<32>): Bytes<32> {
return persistentHash<Vector<3, Bytes<32>>>(
[pad(32, "myapp:identity:kyc-tier"), round as Bytes<32>, sk]
);
}

// Produce a separate, unlinkable key for account-age disclosure
circuit accountAgeKey(round: Field, sk: Bytes<32>): Bytes<32> {
return persistentHash<Vector<3, Bytes<32>>>(
[pad(32, "myapp:identity:account-age"), round as Bytes<32>, sk]
);
}

witness secretKey(): Bytes<32>;
export ledger kycAuthority: Bytes<32>;
export ledger ageAuthority: Bytes<32>;
export ledger round: Counter;

export circuit registerKycTier(): [] {
const sk = secretKey();
kycAuthority = disclose(kycTierKey(round.read() as Field, sk));
}

export circuit registerAccountAge(): [] {
const sk = secretKey();
ageAuthority = disclose(accountAgeKey(round.read() as Field, sk));
}

Enter fullscreen mode Exit fullscreen mode

By doing this, an observer who sees both kycAuthority and ageAuthority on the ledger cannot determine whether they belong to the same user. The domain tags ensure the outputs are cryptographically unrelated, even though both were derived from the same secret key.

The round counter and linkability

The round counter in the examples above serves a second purpose. Without it, a user who calls registerKycTier() twice would produce the same kycAuthority value both times, allowing an observer to link both calls to the same identity. By introducing round into the hash, each call produces a different output even with the same secret key. The linkability between rounds is broken when round is incremented between calls.

Something to note before moving on: never reuse a nonce in a commitment scheme. If you use persistentCommit(value, nonce) and the same nonce appears onchain in double transactions, an observer can link both commitments and if they can observe one opening, potentially infer the other.

Running a privacy audit on your contract

Before you deploy a contract to a testnet or mainnet, you should go through this checklist first. It helps to identify, the most common privacy mistakes.

Privacy audit checklist

Disclosure scope

(i). Every disclose() call wraps the minimum expression necessary, not an entire struct when only one field needs to be public.
(ii). No disclose() is placed on a witness call whose output will be used in subsequent private computations. If a witness value travels both a public path and a private path, these should be two separate values.
(iii). All exported circuit return values are reviewed. If a return value is derived from witness data, confirm the disclosure is on purpose and document what information it reveals.

Boolean and comparison leaks

(i). No exported circuit returns a boolean, integer, or enum that is directly derived from an undisclosed private value without documenting the intended information disclosure.
(ii). For threshold checks (e.g. balance > n), consider whether repeated calls allow binary search over the private value. If so, document the tradeoff or implement rate limiting at the DApp layer.

Hashing and commitments

(i). transientHash/transientCommit outputs should not be used to derive ledger state that needs to persist across network upgrades. Use persistentHash/persistentCommit for those values.
(ii). Nonces used in persistentCommit are never reused. Reusing a nonce with the same value allows on-chain commitment linking.
(iii). Hash inputs do not contain values that an attacker could enumerate to reverse the hash (e.g a small integer hashed alone without a nonce).

Cross-property linkability

(i). Each distinct property of a private identity must use a separate, name-spaced domain tag in its key derivation hash.
(ii). Domain tag strings follow a consistent, collision-resistant naming convention (e.g., "firstproject:context:property").
(iii). A round counter or similar freshness mechanism is included in any public key derivation to prevent same-key linkability across multiple interactions.

Compiler verification

(i). The contract compiles cleanly without suppressed warnings. Review any disclose() wrapper you added in response to a compiler error rather than from intentional design.
(ii). After any refactor, recompile the contract from scratch and re-review all disclose() sites, structural changes can introduce new disclosure paths.

Documentation

(i). Each disclose() site is accompanied by an inline comment explaining what is being disclosed and why it is intentional.
(ii). The contract's README note or specification states clearly which fields in the ledger are public, which are derived from private data, and what information each derived field reveals.

Conclusion

Using Selective disclosure in Midnight is not just a feature, it is an architectural guarantee fused into the language. The compiler's witness protection program means you cannot accidentally publish private data without being forced to make a deliberate decision which is great for security of sensitive data.

The disclose() wrapper is that decision point, and treating it as such, rather than as a formality to satisfy the compiler, is what separates a contract with genuine privacy properties from one that only appears private.

In this tutorial we have covered the full practical arc of selective disclosure in Compact. We learned how the public/private state split works and why privacy is the default. We also learned how to use disclose() correctly, how to scope it precisely to avoid over-disclosure on structured values, and how the compiler catches indirect leaks through arithmetic and conditionals.

Domain-separated hashing was applied to break cross-property linkability, using the same pattern Midnight's own standard examples rely on. And now, there is a concrete privacy audit checklist to run against contracts before they reach the network.

If you have any questions on this article or you're confused somewhere, please engage and let me know. Share this with anyone you think it will help. Privacy is the default, disclosure is the exception.