<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Akanji Rahman</title>
    <description>The latest articles on DEV Community by Akanji Rahman (@akanji_rahman_45ea46f9a7a).</description>
    <link>https://dev.to/akanji_rahman_45ea46f9a7a</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3880035%2F9dd14048-0e84-458f-a056-506b24a6ad80.png</url>
      <title>DEV Community: Akanji Rahman</title>
      <link>https://dev.to/akanji_rahman_45ea46f9a7a</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/akanji_rahman_45ea46f9a7a"/>
    <language>en</language>
    <item>
      <title>Selective Disclosure Patterns in Compact</title>
      <dc:creator>Akanji Rahman</dc:creator>
      <pubDate>Sun, 03 May 2026 05:17:35 +0000</pubDate>
      <link>https://dev.to/akanji_rahman_45ea46f9a7a/selective-disclosure-patterns-in-compact-1745</link>
      <guid>https://dev.to/akanji_rahman_45ea46f9a7a/selective-disclosure-patterns-in-compact-1745</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;How does Midnight solve this problem?&lt;/li&gt;
&lt;li&gt;Understanding the privacy model&lt;/li&gt;
&lt;li&gt;
Using disclose() correctly

&lt;ul&gt;
&lt;li&gt;Placing disclose() at the right scope&lt;/li&gt;
&lt;li&gt;Disclosure through conditional expressions&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

What's safe to disclose vs what leaks privacy

&lt;ul&gt;
&lt;li&gt;Safe to disclose&lt;/li&gt;
&lt;li&gt;What actually leaks privacy?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Applying domain-separated hashing for cross-property unlinkability

&lt;ul&gt;
&lt;li&gt;How does this relate to Compact?&lt;/li&gt;
&lt;li&gt;The round counter and linkability&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Running a privacy audit on your contract&lt;/li&gt;

&lt;li&gt;Conclusion&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;This is an intermediate tutorial. Before reading, you should be comfortable with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Basic Compact syntax&lt;/strong&gt; — you understand what &lt;code&gt;witness&lt;/code&gt;, &lt;code&gt;circuit&lt;/code&gt;, &lt;code&gt;ledger&lt;/code&gt;, and &lt;code&gt;export&lt;/code&gt; declarations do. If not, start with the &lt;a href="https://docs.midnight.network/compact" rel="noopener noreferrer"&gt;Compact language reference&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Compact compiler installed&lt;/strong&gt;  — follow the &lt;a href="https://docs.midnight.network/getting-started/installation" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt; to set it up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-knowledge proofs at a conceptual level&lt;/strong&gt; — you understand that a ZK proof lets you prove a statement is true without revealing the underlying data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Familiarity with a typed language like TypeScript&lt;/strong&gt; — Compact's syntax is modelled on TypeScript, so comfort with typed functions, structs, and return types will help.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How does Midnight solve this problem?
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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:&lt;/p&gt;

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

&lt;p&gt;Before we get into the coding technicalities, how does Compact even work?&lt;/p&gt;

&lt;p&gt;Every Compact Contract operates across two kinds of state. The Public State and The Private State.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4o3yaj1vt26w09zonsy0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4o3yaj1vt26w09zonsy0.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Public State: This is the data that is written to the onchain ledger which is open and visible to all network participants.&lt;br&gt;
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.&lt;br&gt;
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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;
  
  
  Using &lt;code&gt;disclose()&lt;/code&gt; correctly
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnt39v0mfe5df69sm1xkj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnt39v0mfe5df69sm1xkj.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;disclose()&lt;/code&gt; 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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pragma language_version &amp;gt;= 0.16 &amp;amp;&amp;amp; &amp;lt;= 0.22;

import CompactStandardLibrary;

witness getBalance(): Bytes&amp;lt;32&amp;gt;;
export ledger balance: Bytes&amp;lt;32&amp;gt;;

export circuit recordBalance(): [] {
balance = disclose(getBalance());
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you remove the &lt;code&gt;disclose()&lt;/code&gt; wrapper, the compiler immediately rejects the program:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ohhhqyabu4nskqxtwla.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ohhhqyabu4nskqxtwla.png" alt=" " width="800" height="103"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;disclose()&lt;/code&gt; wrapper.&lt;/p&gt;

&lt;p&gt;It is important to note that placing a &lt;code&gt;disclose()&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;disclose()&lt;/code&gt; is the declaration, not the action.&lt;/p&gt;

&lt;h3&gt;
  
  
  Placing &lt;code&gt;disclose()&lt;/code&gt; at the right scope
&lt;/h3&gt;

&lt;p&gt;For simple values, place &lt;code&gt;disclose()&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;For structured values such as tuples, vectors or structs, wrap only the field you intend to disclose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pragma language_version &amp;gt;= 0.16 &amp;amp;&amp;amp; &amp;lt;= 0.22;

import CompactStandardLibrary;

struct UserRecord {
publicName: Bytes&amp;lt;32&amp;gt;;
privateScore: Uint&amp;lt;64&amp;gt;;
}

witness getUserRecord(): UserRecord;
export ledger displayName: Bytes&amp;lt;32&amp;gt;;

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

&lt;/div&gt;



&lt;p&gt;This pattern is important because wrapping the entire struct in a single &lt;code&gt;disclose()&lt;/code&gt; would declare the &lt;code&gt;privateScore&lt;/code&gt; 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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Disclosure through conditional expressions
&lt;/h3&gt;

&lt;p&gt;The compiler also catches indirect disclosure through boolean comparisons. This is one of the most common unintentional privacy leaks in zero-knowledge contracts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import CompactStandardLibrary;
witness getBalance(): Uint&amp;lt;64&amp;gt;;

// This will NOT compile — the comparison result leaks the witness value
export circuit balanceExceeds(n: Uint&amp;lt;64&amp;gt;): Boolean {
return getBalance() &amp;gt; n;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4288y1aawftljxgpxtw7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4288y1aawftljxgpxtw7.png" alt=" " width="800" height="133"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import CompactStandardLibrary;
witness getBalance(): Uint&amp;lt;64&amp;gt;;

export circuit balanceExceeds(n: Uint&amp;lt;64&amp;gt;): Boolean {
return disclose(getBalance()) &amp;gt; n;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's safe to disclose vs what leaks privacy
&lt;/h2&gt;

&lt;p&gt;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?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8h2nc9heaxpe53dl2gxp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8h2nc9heaxpe53dl2gxp.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Safe to disclose
&lt;/h3&gt;

&lt;p&gt;Derived commitments and hashes (with correct library functions).&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;The transient variants (&lt;code&gt;transientCommit&lt;/code&gt; and &lt;code&gt;transientHash&lt;/code&gt;) 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 (&lt;code&gt;persistentCommit&lt;/code&gt; and &lt;code&gt;persistentHash&lt;/code&gt;) use SHA-256 and are guaranteed to remain consistent across upgrades, so use these for any values stored in ledger state.&lt;/p&gt;

&lt;h4&gt;
  
  
  Aggregate results that do not reveal individual values
&lt;/h4&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h4&gt;
  
  
  Public keys derived from private keys through domain-separated hashing
&lt;/h4&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  What actually leaks privacy? (even with &lt;code&gt;disclose()&lt;/code&gt;)?
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Raw witness values
&lt;/h4&gt;

&lt;p&gt;Storing &lt;code&gt;disclose(getBalance())&lt;/code&gt; 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.&lt;/p&gt;

&lt;h4&gt;
  
  
  Arithmetic on witness values
&lt;/h4&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Dangerous — disclosing an offset balance still reveals the balance
balance = disclose(getBalance() + 73);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If an attacker knows the offset which in this case is 73, they recover the original value immediately.&lt;/p&gt;

&lt;h4&gt;
  
  
  Boolean outputs from comparisons on private data
&lt;/h4&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h4&gt;
  
  
  Enum variants derived from private logic
&lt;/h4&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Applying domain-separated hashing for cross-property unlinkability
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foo9iusuosh6nh5g3013h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foo9iusuosh6nh5g3013h.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How does this relate to Compact?
&lt;/h3&gt;

&lt;p&gt;The previous examples use this pattern directly. The &lt;code&gt;publicKey&lt;/code&gt; circuit in the lock example incorporates a domain tag as part of the hash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pragma language_version &amp;gt;= 0.16 &amp;amp;&amp;amp; &amp;lt;= 0.22;

import CompactStandardLibrary;

circuit publicKey(round: Field, sk: Bytes&amp;lt;32&amp;gt;): Bytes&amp;lt;32&amp;gt; {
return persistentHash&amp;lt;Vector&amp;lt;3, Bytes&amp;lt;32&amp;gt;&amp;gt;&amp;gt;(
[pad(32, "midnight:examples:lock:pk"), round as Bytes&amp;lt;32&amp;gt;, sk]
);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The string &lt;code&gt;midnight:examples:lock:pk&lt;/code&gt; 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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pragma language_version &amp;gt;= 0.16 &amp;amp;&amp;amp; &amp;lt;= 0.22;

import CompactStandardLibrary;

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

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

witness secretKey(): Bytes&amp;lt;32&amp;gt;;
export ledger kycAuthority: Bytes&amp;lt;32&amp;gt;;
export ledger ageAuthority: Bytes&amp;lt;32&amp;gt;;
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));
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By doing this, an observer who sees both &lt;code&gt;kycAuthority&lt;/code&gt; and &lt;code&gt;ageAuthority&lt;/code&gt; 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.&lt;/p&gt;

&lt;h3&gt;
  
  
  The round counter and linkability
&lt;/h3&gt;

&lt;p&gt;The round counter in the examples above serves a second purpose. Without it, a user who calls &lt;code&gt;registerKycTier()&lt;/code&gt; twice would produce the same &lt;code&gt;kycAuthority&lt;/code&gt; value both times, allowing an observer to link both calls to the same identity. By introducing &lt;code&gt;round&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;Something to note before moving on: never reuse a nonce in a commitment scheme. If you use &lt;code&gt;persistentCommit(value, nonce)&lt;/code&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running a privacy audit on your contract
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Privacy audit checklist
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Disclosure scope
&lt;/h4&gt;

&lt;p&gt;(i). Every &lt;code&gt;disclose()&lt;/code&gt; call wraps the minimum expression necessary, not an entire struct when only one field needs to be public.&lt;br&gt;
(ii). No &lt;code&gt;disclose()&lt;/code&gt; 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.&lt;br&gt;
(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.&lt;/p&gt;

&lt;h4&gt;
  
  
  Boolean and comparison leaks
&lt;/h4&gt;

&lt;p&gt;(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.&lt;br&gt;
(ii). For threshold checks (e.g. &lt;code&gt;balance &amp;gt; n&lt;/code&gt;), consider whether repeated calls allow binary search over the private value. If so, document the tradeoff or implement rate limiting at the DApp layer.&lt;/p&gt;

&lt;h4&gt;
  
  
  Hashing and commitments
&lt;/h4&gt;

&lt;p&gt;(i). &lt;code&gt;transientHash&lt;/code&gt;/&lt;code&gt;transientCommit&lt;/code&gt; outputs should not be used to derive ledger state that needs to persist across network upgrades. Use &lt;code&gt;persistentHash&lt;/code&gt;/&lt;code&gt;persistentCommit&lt;/code&gt; for those values.&lt;br&gt;
(ii). Nonces used in &lt;code&gt;persistentCommit&lt;/code&gt; are never reused. Reusing a nonce with the same value allows on-chain commitment linking.&lt;br&gt;
(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).&lt;/p&gt;

&lt;h4&gt;
  
  
  Cross-property linkability
&lt;/h4&gt;

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

&lt;h4&gt;
  
  
  Compiler verification
&lt;/h4&gt;

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

&lt;h4&gt;
  
  
  Documentation
&lt;/h4&gt;

&lt;p&gt;(i). Each &lt;code&gt;disclose()&lt;/code&gt; site is accompanied by an inline comment explaining what is being disclosed and why it is intentional.&lt;br&gt;
(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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;disclose()&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;disclose()&lt;/code&gt; correctly, how to scope it precisely to avoid over-disclosure on structured values, and how the compiler catches indirect leaks through arithmetic and conditionals.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>privacy</category>
      <category>tutorial</category>
      <category>web3</category>
    </item>
  </channel>
</rss>
