<?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>Forem: Nasihudeen Jimoh</title>
    <description>The latest articles on Forem by Nasihudeen Jimoh (@kanasjnr).</description>
    <link>https://forem.com/kanasjnr</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%2F3830243%2F0dbfebaf-18dd-4756-af07-12cfdd3c8ffe.png</url>
      <title>Forem: Nasihudeen Jimoh</title>
      <link>https://forem.com/kanasjnr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/kanasjnr"/>
    <language>en</language>
    <item>
      <title>Working with Maps and Merkle Trees in Compact:</title>
      <dc:creator>Nasihudeen Jimoh</dc:creator>
      <pubDate>Thu, 16 Apr 2026 08:19:24 +0000</pubDate>
      <link>https://forem.com/midnight-aliit/working-with-maps-and-merkle-trees-in-compact-40i3</link>
      <guid>https://forem.com/midnight-aliit/working-with-maps-and-merkle-trees-in-compact-40i3</guid>
      <description>&lt;h2&gt;
  
  
  A Guide to the State Dichotomy
&lt;/h2&gt;

&lt;p&gt;The Midnight blockchain introduces a fundamental architectural shift in smart contract design through its "State Dichotomy." Unlike transparency-first blockchains, Midnight enables developers to partition data into public Ledger State and private Shielded State. This guide provides a comprehensive analysis of the two primary data structures used to manage these states: &lt;strong&gt;Maps&lt;/strong&gt; and &lt;strong&gt;Merkle Trees&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Through a dual-implementation approach featuring a public registry and an anonymous allowlist this guide explores the operational mechanics, security considerations, and SDK integration patterns required to build applications with Compact.&lt;/p&gt;




&lt;h2&gt;
  
  
  The State Dichotomy: Conceptual Framework
&lt;/h2&gt;

&lt;p&gt;Compact contracts operates as a decentralized state machine where transitions are governed by Zero-Knowledge (ZK) circuits. The efficiency and privacy of these transitions depend on the selection of appropriate storage structures. This bifurcation of state is not merely an optimization but a core privacy Primitive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ledger state (Public)
&lt;/h3&gt;

&lt;p&gt;The Ledger state consists of on chain data structures that are globally visible and replicated across the network nodes. Ledger state is governed by Abstract Data Types (ADTs) such as Maps, Counters, and Sets. These structures provide the shared source of truth required for applications like token supply management, administrative registries, and public consensus variables. In Compact, every ledger interaction is verifiable by any observer, ensuring that the global state remains consistent and auditable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shielded state (Private)
&lt;/h3&gt;

&lt;p&gt;Shielded state remains off chain, residing within the prover’s local environment. Users interact with the ledger by submitting Zero Knowledge (ZK) proofs that verify a state transition has occurred according to the contract's logic without revealing the underlying data. This enables features like confidential assets, anonymous membership verification, and private governance. The shielded state is protected by the prover's secret keys and is only revealed through explicit "disclosures" within a circuit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choosing the correct structure
&lt;/h3&gt;

&lt;p&gt;The choice between a Map and a Merkle Tree is determined by the required visibility of the relationship between a user and their data. Developers must ask: "Does the network need to know &lt;em&gt;who&lt;/em&gt; owns this data, or only &lt;em&gt;that&lt;/em&gt; they own it?"&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Maps&lt;/strong&gt; are used when the association between a key and a value must be public and directly queryable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Merkle Trees&lt;/strong&gt; are used when the goal is to prove membership within a set or the integrity of a dataset without revealing the identity of the specific member.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Associative storage with Maps
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;Map&lt;/code&gt; ADT is the primary tool for associative storage on the Midnight ledger. It allows for O(1) lookups and insertions, functioning as a decentralized dictionary. Maps are foundational for any application where identities need to be linked to properties or permissions in a transparent way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Syntax and declaration
&lt;/h3&gt;

&lt;p&gt;In Compact, a Map is declared within a &lt;code&gt;ledger&lt;/code&gt; block. The following definition maps a 32-byte public key to a 32-byte profile 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.21;

import CompactStandardLibrary;

export ledger registry: Map&amp;lt;Bytes&amp;lt;32&amp;gt;, Bytes&amp;lt;32&amp;gt;&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Map operators and the disclosure rule
&lt;/h3&gt;

&lt;p&gt;Every interaction with a Map must navigate the boundary between private circuit parameters and public ledger state.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. The disclosure requirement: Bridging Private and Public
&lt;/h4&gt;

&lt;p&gt;Circuit parameters are private by default. In the Compact execution model, parameters are "witnesses" known only to the prover. To store a parameter in a public Map, it must be explicitly disclosed using the &lt;code&gt;disclose()&lt;/code&gt; operator. Failure to do so results in a compilation error, as Compact prevents the accidental leakage of private witnesses into public storage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit register(profile_hash: Bytes&amp;lt;32&amp;gt;): [] {
    const user = ownPublicKey();
    const d_profile_hash = disclose(profile_hash);
    registry.insert(user.bytes, d_profile_hash);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures that the user is aware of exactly what information is being pushed to the ledger. If you were to attempt &lt;code&gt;registry.insert(user.bytes, profile_hash)&lt;/code&gt;, the compiler would signal a security violation, maintaining a strict "Privacy by Default" posture.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Detailed Map Operations
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;insert(key, value)&lt;/code&gt;&lt;/strong&gt;: Creates or updates an entry. If the key already exists, the old value is overwritten. This operation generates a ledger state update that is broadcast to the network.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;lookup(key)&lt;/code&gt;&lt;/strong&gt;: Retrieves the value associated with a key. If the key is absent, it returns the type-specific default (e.g., zero bytes for &lt;code&gt;Bytes&amp;lt;32&amp;gt;&lt;/code&gt;, false for &lt;code&gt;Boolean&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;member(key)&lt;/code&gt;&lt;/strong&gt;: A Boolean operator that checks for key existence without retrieving the value. This is highly efficient for access control checks where the value itself is irrelevant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;remove(key)&lt;/code&gt;&lt;/strong&gt;: Deletes an entry from the ledger, clearing the associated storage. This is crucial for managing "state bloat" and ensuring that outdated memberships are purged.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Zero Knowledge membership with Merkle Trees
&lt;/h2&gt;

&lt;p&gt;Merkle Trees are the engine of anonymity on Midnight. By representing a large set of data with a single 32-byte root hash, they allow for membership verification that preserves the privacy of the specific leaf.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technical specification and Depth Selection
&lt;/h3&gt;

&lt;p&gt;Merkle Trees in Compact are fixed-depth. The choice of depth determines the maximum capacity of the tree ($2^{depth}$).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export ledger allowlist: MerkleTree&amp;lt;20, Bytes&amp;lt;32&amp;gt;&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A depth of 20 supports approximately 1,048,576 entries. Choosing the correct depth is an engineering trade-off:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Higher Depth&lt;/strong&gt;: Increases the capacity of the system but linearly increases the size of the ZK circuit and the time required for proof generation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lower Depth&lt;/strong&gt;: Reduces proving time but risks hitting a "Full State" where no new members can be added without a contract migration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Operational lifecycle: The path to proof
&lt;/h3&gt;

&lt;p&gt;The verification of membership involves a transition from the ledger's public root to the prover’s private Merkle path.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. The Merkle path witness
&lt;/h4&gt;

&lt;p&gt;To prove membership, a user must provide a &lt;strong&gt;Merkle Path&lt;/strong&gt; a sequence of sibling hashes representing a branch from the leaf to the root. This is defined as a &lt;code&gt;witness&lt;/code&gt; function, identifying it as data retrieved from the prover's local environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;witness get_membership_path(leaf: Bytes&amp;lt;32&amp;gt;): MerkleTreePath&amp;lt;20, Bytes&amp;lt;32&amp;gt;&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the SDK, this path is calculated by iterating over the local view of the tree and finding the siblings for the target leaf at each level of the binary structure.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Circuit-level verification logic
&lt;/h4&gt;

&lt;p&gt;Inside the ZK-circuit, the &lt;code&gt;merkleTreePathRoot&lt;/code&gt; operator reconstructs the root hash from the provided path and leaf. The circuit verifies that the hashes align at each level ($H(L, R)$).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit access_exclusive_area(leaf: Bytes&amp;lt;32&amp;gt;): [] {
    const d_leaf = disclose(leaf);
    const path = get_membership_path(d_leaf);

    // ZK Re-computation of the root
    const computed_root = merkleTreePathRoot&amp;lt;20, Bytes&amp;lt;32&amp;gt;&amp;gt;(path);

    // Verification against Ledger State
    assert(allowlist.checkRoot(disclose(computed_root)), "Access Denied");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The "Double Disclosure" security pattern
&lt;/h3&gt;

&lt;p&gt;The pattern above utilizes two disclosures that are essential for the Midnight security model:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Disclosing the Leaf&lt;/strong&gt;: Ensures the proof corresponds to the exact data provided by the user. If the leaf were not disclosed, a malicious prover could use a different leaf than the one they claim to possess.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Disclosing the Root&lt;/strong&gt;: The &lt;code&gt;checkRoot&lt;/code&gt; operator must compare the &lt;code&gt;computed_root&lt;/code&gt; against the public ledger. For this comparison to occur, the value being checked must be public. Because the root hash is already public, this disclosure does not reveal any private information about the leaf or path used. It simply confirms: "I know a secret that hashes to this public root."&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Technical comparison: Maps vs. Merkle Trees
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Map (Ledger ADT)&lt;/th&gt;
&lt;th&gt;Merkle Tree (Ledger ADT)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data Visibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fully Public&lt;/td&gt;
&lt;td&gt;Root Public; Leaves/Paths Private&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Access Pattern&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Key-Based (Direct)&lt;/td&gt;
&lt;td&gt;Path-Based (Indirect)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Privacy Model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Transparent Association&lt;/td&gt;
&lt;td&gt;Set Membership Anonymity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Proof Complexity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low (O(1))&lt;/td&gt;
&lt;td&gt;High (O(depth) hashes)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Primary Use&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Registries, Public Indices&lt;/td&gt;
&lt;td&gt;Anonymous Allowlists, Confidential Voting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;State Bloat&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Linear per entry&lt;/td&gt;
&lt;td&gt;Fixed per depth ($O(1)$ on-chain root)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Computational overhead analyze
&lt;/h3&gt;

&lt;p&gt;Merkle Trees impose a higher verification cost. A tree of depth 20 requires 20 sequential hash computations within the ZK-circuit. Each hash operation increases the number of constraints in the proof, which correlates directly to proof generation time. While this provides anonymity, developers must consider that a user on a mobile device may take significantly longer to generate a proof for a depth 32 tree compared to a depth-16 tree. In contrast, Map operations are computationally negligible within a circuit.&lt;/p&gt;




&lt;h2&gt;
  
  
  SDK implementation: The structural validation
&lt;/h2&gt;

&lt;p&gt;When integrating Compact contracts with the Midnight TypeScript SDK, developers must align the orchestrator's output with the runtime's expected data shapes. A common failure point is the &lt;strong&gt;Structural Validation&lt;/strong&gt; of the Merkle path.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;instanceof&lt;/code&gt; requirement and Type Safety
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;@midnight-ntwrk/compact-runtime&lt;/code&gt; performs strict type checking on objects returned by witness functions. Manually constructing a Merkle path object as a JSON literal will fail even if the fields (&lt;code&gt;value&lt;/code&gt;, &lt;code&gt;alignment&lt;/code&gt;) match. This is because the runtime's ZK IR (Intermediate Representation) requires an instance of the specific internal &lt;code&gt;MerkleTreePath&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;Instead, developers must use the &lt;code&gt;findPathForLeaf&lt;/code&gt; method provided by the ledger context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/prover/allowlist_witnesses.ts&lt;/span&gt;
&lt;span class="nf"&gt;get_membership_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;leaf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// This returns an instance of the MerkleTreePath class&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allowlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findPathForLeaf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;leaf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Leaf discovery failed: State mismatch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensuring that the path is derived from the current ledger state and satisfies the runtime's internal &lt;code&gt;instanceof&lt;/code&gt; checks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Detailed Implementation Walkthrough
&lt;/h2&gt;

&lt;p&gt;The following implementation show how both structures are managed in a single application lifecycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Registry contract: &lt;code&gt;contract/registry.compact&lt;/code&gt;
&lt;/h3&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.21;

import CompactStandardLibrary;

// Map Bytes&amp;lt;32&amp;gt; address to Bytes&amp;lt;32&amp;gt; profile CID
export ledger registry: Map&amp;lt;Bytes&amp;lt;32&amp;gt;, Bytes&amp;lt;32&amp;gt;&amp;gt;;

export circuit register(profile_hash: Bytes&amp;lt;32&amp;gt;): [] {
    const user = ownPublicKey();
    // Public disclosure for ledger storage
    const d_profile_hash = disclose(profile_hash);
    registry.insert(user.bytes, d_profile_hash);
}

export circuit remove_registration(): [] {
    const user = ownPublicKey();
    registry.remove(user.bytes);
}

export circuit get_profile(user_pk: Bytes&amp;lt;32&amp;gt;): Bytes&amp;lt;32&amp;gt; {
    // Disclosure required for circuit parameters used in lookups
    return registry.lookup(disclose(user_pk));
}

export circuit is_registered(user_pk: Bytes&amp;lt;32&amp;gt;): Boolean {
    return registry.member(disclose(user_pk));
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Allowlist contract: &lt;code&gt;contract/allowlist.compact&lt;/code&gt;
&lt;/h3&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.21;

import CompactStandardLibrary;

// Merkle Tree for anonymous membership
export ledger allowlist: MerkleTree&amp;lt;20, Bytes&amp;lt;32&amp;gt;&amp;gt;;

export circuit update_allowlist(member_hash: Bytes&amp;lt;32&amp;gt;): [] {
    const d_member = disclose(member_hash);
    allowlist.insert(d_member);
}

export circuit access_exclusive_area(leaf: Bytes&amp;lt;32&amp;gt;): [] {
    const d_leaf = disclose(leaf);
    const path = get_membership_path(d_leaf);

    // Hash up the tree to the root
    const computed_root = merkleTreePathRoot&amp;lt;20, Bytes&amp;lt;32&amp;gt;&amp;gt;(path);

    // Check if the computed root matches the current ledger state
    assert(allowlist.checkRoot(disclose(computed_root)), "Access Denied");
}

witness get_membership_path(leaf: Bytes&amp;lt;32&amp;gt;): MerkleTreePath&amp;lt;20, Bytes&amp;lt;32&amp;gt;&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  TypeScript orchestrator: &lt;code&gt;src/index.ts&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;createActionCircuitContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;dummyContractAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@midnight-ntwrk/compact-runtime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RegistryContract&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./managed/registry/contract/index.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AllowlistContract&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./managed/allowlist/contract/index.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AllowlistProver&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./prover/allowlist_witnesses.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;alicePk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;profileHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 1. Map Interaction: Public Registry&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;registry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegistryContract&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;regCtx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createActionCircuitContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;dummyContractAddress&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;alicePk&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialContractState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialPrivateState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Insert Alice into the registry Map&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;updatedRegCtx&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;circuits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;regCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;profileHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Registry state updated via Map insertion.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Merkle Interaction: Anonymous Allowlist&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prover&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AllowlistProver&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allowlist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AllowlistContract&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="c1"&gt;// Resolve the witness using the prover logic&lt;/span&gt;
    &lt;span class="na"&gt;get_membership_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;leaf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;prover&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_membership_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;leaf&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allowCtx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createActionCircuitContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;dummyContractAddress&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;alicePk&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;allowlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialContractState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;allowlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialPrivateState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Updating on-chain Merkle root...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Populate the tree before proving&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;postAddCtx&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allowlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;circuits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_allowlist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;allowCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;alicePk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Verifying anonymous membership proof...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Execute the anonymous access circuit&lt;/span&gt;
  &lt;span class="nx"&gt;allowlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;circuits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;access_exclusive_area&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postAddCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;alicePk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Access Granted: Membership verified anonymously.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  State Bounded Merkle Trees
&lt;/h2&gt;

&lt;p&gt;Forsystems processing high volumes, a static Merkle Tree can present challenges during concurrent updates. Production Midnight applications often utilize &lt;strong&gt;State Bounded transitions&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Synchronization window
&lt;/h3&gt;

&lt;p&gt;Because the Merkle root is global, every new addition invalidates the old root. If a user is half way through generating a 2 second proof and another transaction lands, the root will shift and the proof will fail.&lt;/p&gt;

&lt;p&gt;Midnight addresses this with &lt;strong&gt;Historic Windows&lt;/strong&gt;. A &lt;code&gt;HistoricMerkleTree&lt;/code&gt; allows a circuit to verify a proof against any root within the last $N$ blocks. This providing a "synchronization window" that makes DApps resilient to high traffic.&lt;/p&gt;

&lt;h3&gt;
  
  
  State Bounded Merkle Trees
&lt;/h3&gt;

&lt;p&gt;A State Bounded Merkle Tree allows for the separation of the state into bounded regions. This is particularly useful for optimizing storage, as old branches that are no longer being proven against can be pruned from active memory while maintaining the cryptographic root consistency.&lt;/p&gt;




&lt;h2&gt;
  
  
  Best practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Disclosure hygiene
&lt;/h3&gt;

&lt;p&gt;In Compact, the order of &lt;code&gt;disclose()&lt;/code&gt; calls can influence the circuit's logic flow. Developers should disclose values as late as possible ideally directly before the ledger operation that requires them. This maintain a clear boundary between the private computation (witnesses) and the public assertion (disclosures).&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling state lag in production
&lt;/h3&gt;

&lt;p&gt;In a live network environment, the ledger state might be several blocks ahead of your local prover’s view. When retrieving a Merkle path, ensure your client is synchronized with the specific block height that matches the Merkle root currently stored on the ledger. Outdated paths are the most common cause of &lt;code&gt;checkRoot&lt;/code&gt; assertion failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optimization: Caching witness results
&lt;/h3&gt;

&lt;p&gt;Merkle path lookups are computationally intensive for the local ledger view. If an application requires frequent verification (e.g., a private chat room), consider caching the &lt;code&gt;MerkleTreePath&lt;/code&gt; in the private state and only updating it when the ledger's Merkle root changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  API Reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operator&lt;/th&gt;
&lt;th&gt;Structure&lt;/th&gt;
&lt;th&gt;Return Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;insert(k, v)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Map&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Inserts or updates a value.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lookup(k)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Map&lt;/td&gt;
&lt;td&gt;&lt;code&gt;V&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Retrieves value or default.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;member(k)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Map&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Boolean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Checks for key existence.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;remove(k)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Map&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deletes a key from the ledger.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;insert(leaf)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;MerkleTree&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Appends a leaf to the tree.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;checkRoot(root)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;MerkleTree&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Boolean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Verifies a root against state.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;merkleTreePathRoot(path)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Witness&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Bytes&amp;lt;32&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Computes root from path in ZK.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




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

&lt;p&gt;Maps and Merkle Trees are the fundamental storage structures that enable the state dichotomy of the Midnight blockchain. &lt;strong&gt;Maps&lt;/strong&gt; provide the efficiency and directness required for public association, while &lt;strong&gt;Merkle Trees&lt;/strong&gt; facilitate the zero-knowledge membership proofs that define privacy preserving interaction.&lt;/p&gt;

&lt;p&gt;By mastering the transition between these two domains specifically the nuances of the &lt;code&gt;disclose()&lt;/code&gt; operator and the &lt;code&gt;MerkleTreePath&lt;/code&gt; witness resolver developers can architect complex, privacy centric applications that benefit from both transparency and confidentiality. For further exploration, consult the &lt;a href="https://docs.midnight.network" rel="noopener noreferrer"&gt;official Midnight Documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The full code example is here you can check it up*&lt;em&gt;GitHub Repository&lt;/em&gt;*: &lt;a href="https://github.com/Kanasjnr/compact-maps-merkle-tutorial" rel="noopener noreferrer"&gt;Kanasjnr/compact-maps-merkle-tutorial&lt;/a&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>blockchain</category>
      <category>privacy</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Zero-Knowledge, Zero Friction: Automating DApp Development on Midnight</title>
      <dc:creator>Nasihudeen Jimoh</dc:creator>
      <pubDate>Tue, 07 Apr 2026 22:30:27 +0000</pubDate>
      <link>https://forem.com/midnight-aliit/zero-knowledge-zero-friction-automating-dapp-development-on-midnight-29f1</link>
      <guid>https://forem.com/midnight-aliit/zero-knowledge-zero-friction-automating-dapp-development-on-midnight-29f1</guid>
      <description>&lt;p&gt;This article provides a step by step deep dive into &lt;a href="https://github.com/Kanasjnr/midnight-pulse-sdk-demo" rel="noopener noreferrer"&gt;&lt;strong&gt;Midnight Pulse&lt;/strong&gt;&lt;/a&gt;, a collaborative analytics tool built to showcase the power of the &lt;a href="https://www.npmjs.com/package/midnight-sdk-gen" rel="noopener noreferrer"&gt;&lt;strong&gt;Midnight SDK Generator&lt;/strong&gt;&lt;/a&gt;. We'll walk through the entire lifecycle: from defining a privacy preserving smart contract in Compact to building a multi user CLI application in TypeScript.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Vision: Privacy at Scale
&lt;/h2&gt;

&lt;p&gt;The goal of Midnight Pulse is to allow a team to compute a "salary pulse" a benchmark average without any individual ever revealing their sensitive data to anyone else (not even a central server).&lt;/p&gt;

&lt;p&gt;We solve this using &lt;strong&gt;Zero-Knowledge (ZK) proofs&lt;/strong&gt; and a strict &lt;strong&gt;Anonymity Threshold&lt;/strong&gt; ($N \ge 5$). The logic is simple: the application won't let you see the results until at least 5 people have joined.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: The "Glue Code" Friction
&lt;/h2&gt;

&lt;p&gt;Developing on Midnight involves interacting with ZK-circuits and a private ledger. Manually writing the TypeScript glue code to call these circuits and read ledger state is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Error-prone&lt;/strong&gt;: Manual type mapping can lead to runtime failures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintenance-heavy&lt;/strong&gt;: Every contract change requires a manual update to the SDK.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boilerplate-intensive&lt;/strong&gt;: Setting up contract stubs and providers involves repetitive code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Midnight SDK Generator&lt;/strong&gt; solves this by deriving a production-ready SDK directly from your contract's metadata.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Privacy Contract (&lt;code&gt;salary.compact&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;Everything starts with the contract. Using &lt;strong&gt;Compact&lt;/strong&gt;, we define what data is public and what logic is private.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Ledger
&lt;/h3&gt;

&lt;p&gt;We store the aggregate metrics (Sum and Headcount) on the &lt;strong&gt;Shielded Ledger&lt;/strong&gt;. This means the data is encrypted on-chain, and only the contract logic can update it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;total_salary_sum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Uint64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;employee_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Uint64&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The "Submit" Circuit
&lt;/h3&gt;

&lt;p&gt;When a user submits their salary, they don't send the value in the clear. Instead, they run a &lt;strong&gt;circuit&lt;/strong&gt; that privately adds their value to the aggregate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit submit_salary(salary: Uint64): [] {
  const current_sum = ledger.total_salary_sum;
  const current_count = ledger.employee_count;

  ledger.total_salary_sum = current_sum + salary;
  ledger.employee_count = current_count + 1;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The "Benchmark" Circuit (Anonymity Check)
&lt;/h3&gt;

&lt;p&gt;This is where the privacy guarantee is enforced. The circuit checks the &lt;code&gt;employee_count&lt;/code&gt; before performing the comparison.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit is_above_benchmark(my_salary: Uint64): Boolean {
  const count = ledger.employee_count;
  const total = ledger.total_salary_sum;

  // The Privacy Gate: Revert if less than 5 people have joined
  check(count &amp;gt;= 5) "Threshold Error: N &amp;lt; 5 contributors";

  const average = total / count;
  return my_salary &amp;gt; average;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Generating the SDK
&lt;/h2&gt;

&lt;p&gt;Once the contract is written, we use the SDK Generator to bridge the gap between Compact and TypeScript.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Compile to Metadata
&lt;/h3&gt;

&lt;p&gt;First, use the &lt;code&gt;compact&lt;/code&gt; compiler to generate a structured JSON representation of your contract.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;compact compile ./contracts/salary.compact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Generate the SDK
&lt;/h3&gt;

&lt;p&gt;Once you have your &lt;code&gt;salary.structure.json&lt;/code&gt;, use the generator to create your type-safe SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;midnight-sdk-gen ./contracts/salary.structure.json &lt;span class="nt"&gt;--output&lt;/span&gt; ./src/sdk/SalarySDK.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Type Mapping Support
&lt;/h3&gt;

&lt;p&gt;The generator automatically maps Compact types to their most appropriate TypeScript equivalents, so you never have to guess:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Compact Type&lt;/th&gt;
&lt;th&gt;TypeScript Type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Boolean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;boolean&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Uint&amp;lt;N&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bigint&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Bytes&amp;lt;N&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Uint8Array&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Maybe&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;`T&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;{% raw %}&lt;code&gt;Vector&amp;lt;N, T&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;T[]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Application Layer (&lt;code&gt;pulse.ts&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;With the generated SDK, building the CLI tool is straightforward. We use an &lt;strong&gt;Agentic Pattern&lt;/strong&gt; to simulate multiple participants.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Initializing Providers
&lt;/h3&gt;

&lt;p&gt;We first set up the Midnight network context (Wallet, Proof Server, Indexer). Our &lt;code&gt;providers.ts&lt;/code&gt; bridge allows us to toggle between &lt;strong&gt;Fast Simulation&lt;/strong&gt; and the &lt;strong&gt;Local Docker Network&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MIDNIGHT_ENV&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;simulated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;providers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getProviders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Deploying the Contract
&lt;/h3&gt;

&lt;p&gt;The team leader (or a smart contract factory) deploys the instance using the generated &lt;code&gt;deploy&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SalarySDK&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./sdk/SalarySDK&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SalarySDK&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deploy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contractAddress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Orchestrating the Users
&lt;/h3&gt;

&lt;p&gt;We loop through our simulated agents (Alice, Bob, Carol, Dave, Eve). Each agent joins the contract and submits their salary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;agents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Join the same shared contract address using the generated SDK&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agentSdk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SalarySDK&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contractAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Submit salary privately via the ZK circuit&lt;/span&gt;
  &lt;span class="c1"&gt;// The SDK handles all witness and proof management&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;agentSdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit_salary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;witness&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Log progress using side-by-side observability&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;publicState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;agentSdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publicDataProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;queryContractState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;contractAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;StateObserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;displayPulse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;publicState&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Running the Benchmark
&lt;/h3&gt;

&lt;p&gt;Finally, Carol checks if she is above the average. Because all 5 agents have now submitted, the $N \ge 5$ check in the circuit passes successfully.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isAbove&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;carolSdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_above_benchmark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;carol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;witness&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;StateObserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;displayResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Carol&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isAbove&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Observability (The Pulse)
&lt;/h2&gt;

&lt;p&gt;One of the biggest challenges in ZK development is &lt;strong&gt;"blind debugging."&lt;/strong&gt; To solve this, i built a &lt;code&gt;StateObserver&lt;/code&gt; to visualize the delta of privacy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Side-by-Side Verification
&lt;/h3&gt;

&lt;p&gt;The terminal output provides a side-by-side view. As you can see below, the &lt;strong&gt;Public State&lt;/strong&gt; tracks the group metrics, while the &lt;strong&gt;Private State&lt;/strong&gt; remains strictly isolated in the user's local witness.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--- [Alice] Status ---

    ┌──────────────────────────────┬──────────────────────────────┐
    │ PUBLIC STATE               │ PRIVATE STATE              │
    ├──────────────────────────────┼──────────────────────────────┤
    │ Total Sum:   92,000          │ My Salary:   92,000          │
    │ Headcount:   1               │ ZK-Proof:    VALID          │
    └──────────────────────────────┴──────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows the developer to verify that privacy is actually being maintained—Alice can see the total sum grow, but she never sees Bob's individual contribution.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion: The New Way to Build ZK
&lt;/h2&gt;

&lt;p&gt;Midnight Pulse proves that building ZK applications doesn't have to be hard. By using &lt;strong&gt;Compact&lt;/strong&gt; for privacy logic and the &lt;strong&gt;SDK Generator&lt;/strong&gt; for the application layer, we can build sophisticated, privacy preserving systems with standard TypeScript skills.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Takeaways:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero Maintenance&lt;/strong&gt;: Changing your contract automatically updates your SDK types.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type Safety&lt;/strong&gt;: No more runtime errors from incorrect type mapping.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer Velocity&lt;/strong&gt;: Focus on your dApp logic, not the cryptographic plumbing.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  See the Pulse in 60 seconds
&lt;/h3&gt;

&lt;p&gt;Clone the repository and run the multi-agent simulation on your own machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Kanasjnr/midnight-pulse-sdk-demo
&lt;span class="nb"&gt;cd &lt;/span&gt;midnight-pulse-sdk-demo
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>tutorial</category>
      <category>opensource</category>
      <category>blockchain</category>
      <category>security</category>
    </item>
    <item>
      <title>Advanced Compact Patterns for Web3 Developers</title>
      <dc:creator>Nasihudeen Jimoh</dc:creator>
      <pubDate>Thu, 02 Apr 2026 20:08:39 +0000</pubDate>
      <link>https://forem.com/midnight-aliit/advanced-compact-patterns-for-web3-developers-4m9i</link>
      <guid>https://forem.com/midnight-aliit/advanced-compact-patterns-for-web3-developers-4m9i</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If you've spent years building on EVM chains, Midnight's architecture might feel like a paradigm shift. On Ethereum, you push computation onto the blockchain itself. On Midnight, you do the opposite you move computation off-chain and prove it correctly using zero-knowledge proofs.&lt;/p&gt;

&lt;p&gt;This isn't just a different implementation detail. It fundamentally changes how you think about state management, data disclosure, and circuit design.&lt;/p&gt;

&lt;p&gt;Samantha's &lt;a href="https://dev.tolink-to-samantha-post"&gt;foundational guide&lt;/a&gt; introduced the three-part structure of Midnight contracts: the public ledger, zero-knowledge circuits, and local computation. But understanding the basics and architecting production systems are two different challenges.&lt;/p&gt;

&lt;p&gt;This guide dives into the patterns that separate working prototypes from robust systems. We'll explore how witnesses enable privacy boundaries, why commitments matter more than direct state, how to optimize circuits for real-world constraints, and how to compose multiple private contracts without leaking metadata.&lt;/p&gt;

&lt;p&gt;By the end, you'll have concrete strategies for building systems that maintain privacy guarantees while managing the practical tradeoffs of Web3 applications.&lt;/p&gt;




&lt;h2&gt;
  
  
  Witnesses &amp;amp; Selective Disclosure
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Understanding Witnesses Beyond Function Calls
&lt;/h3&gt;

&lt;p&gt;In EVM contracts, all data available to a function is deterministic. The blockchain is your single source of truth. In Compact, witnesses invert this model: witnesses are the &lt;em&gt;only&lt;/em&gt; source of truth the contract doesn't control.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A witness declares a contract's dependency on external data&lt;/span&gt;
&lt;span class="nx"&gt;witness&lt;/span&gt; &lt;span class="nf"&gt;getUserSecret&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;witness&lt;/span&gt; &lt;span class="nf"&gt;getProofOfAssets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Uint&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;AssetsProof&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you declare a witness, you're saying: "This contract's logic depends on data I cannot verify on-chain. It's the application's responsibility to provide this correctly."&lt;/p&gt;

&lt;p&gt;This creates a critical security boundary. The contract trusts the application to supply honest witnesses, but the proof system validates that the application used those witnesses correctly.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Witness-Disclosure Loop
&lt;/h3&gt;

&lt;p&gt;Real-world contracts don't just consume witnesses they combine witness data with disclosed state to create privacy preserving outcomes.&lt;/p&gt;

&lt;p&gt;Consider an age verification system:&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 0.22;
import CompactStandardLibrary;

// Public ledger: only record that someone proved they're eligible
export ledger ageVerified: Map&amp;lt;Bytes&amp;lt;32&amp;gt;, Boolean&amp;gt;;
export ledger verificationRound: Counter;

// Private witness: the user's actual birthdate (never on-chain)
witness getUserBirthDate(): Uint&amp;lt;32&amp;gt;; // Unix timestamp

// Derived public key with round counter to prevent replay
circuit derivePublicIdentity(round: Field, secret: Bytes&amp;lt;32&amp;gt;): Bytes&amp;lt;32&amp;gt; {
  return persistentHash&amp;lt;Vector&amp;lt;2, Bytes&amp;lt;32&amp;gt;&amp;gt;&amp;gt;(
    [round as Bytes&amp;lt;32&amp;gt;, secret]
  );
}

// Main circuit: prove age without revealing birthdate
export circuit verifyAge(secret: Bytes&amp;lt;32&amp;gt;, minAge: Uint&amp;lt;32&amp;gt;): [] {
  // Get private birthdate (witness - not on-chain)
  const birthDate = getUserBirthDate();

  // Compute age
  const currentTimestamp: Uint&amp;lt;32&amp;gt; = 1704067200; // Updated by app
  const age = currentTimestamp - birthDate;

  // Private check: verify age requirement
  assert(age &amp;gt;= minAge, "Age requirement not met");

  // Public disclosure: only record that verification happened
  const identity = derivePublicIdentity(verificationRound.roundNumber, secret);
  ageVerified = disclose(identity, true);
  verificationRound.increment(1);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key pattern&lt;/strong&gt;: The witness data (&lt;code&gt;getUserBirthDate&lt;/code&gt;) is never directly disclosed. Instead, you compute a predicate over it (&lt;code&gt;age &amp;gt;= minAge&lt;/code&gt;), and then disclose only the outcome the user consents to.&lt;/p&gt;

&lt;p&gt;The tradeoff: The application code that supplies the witness must be trusted. If a malicious DApp sends a false birthdate, the proof system can't detect it—but it will prove the user accepted false data. This is why witness sourcing matters as much as circuit logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practical Consideration: Witness Sourcing
&lt;/h3&gt;

&lt;p&gt;Where do witnesses come from in real applications?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User-held secrets&lt;/strong&gt;: API keys, private keys, personal data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External APIs&lt;/strong&gt;: Proof-of-reserve attestations, oracle feeds, credential issuers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-knowledge proofs themselves&lt;/strong&gt;: A sub-proof generated off-chain that proves something about external data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trusted hardware&lt;/strong&gt;: TEE attestations or trusted execution environment outputs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each source has different security properties. A witness from a user's secret key is as strong as that key's protection. A witness from an untrusted API might need cryptographic verification itself.&lt;/p&gt;

&lt;p&gt;For example, if your witness comes from an API like "get current asset price," the application must either:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Trust the API (weak)&lt;/li&gt;
&lt;li&gt;Verify the API response against multiple oracles (medium)&lt;/li&gt;
&lt;li&gt;Require the API to provide a signature from a trusted source (better)&lt;/li&gt;
&lt;li&gt;Use sub-proofs to prove the API data meets certain criteria without revealing it (best)&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Commitments &amp;amp; Zero-Knowledge Proof Architecture
&lt;/h2&gt;

&lt;h3&gt;
  
  
  From State Mutation to Commitment Schemes
&lt;/h3&gt;

&lt;p&gt;EVM developers are accustomed to direct state mutations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Ethereum: modify state directly
mapping(address =&amp;gt; uint256) balance;
balance[user] += amount;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Midnight, public state mutations must be &lt;em&gt;proven&lt;/em&gt; by a zero-knowledge circuit. This means your public state must be designed around commitment schemes cryptographic structures that let you prove you know a value without revealing it.&lt;/p&gt;

&lt;p&gt;Here's the conceptual bridge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EVM thinking&lt;/strong&gt;: State is a mutable cell. Update it directly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Midnight thinking&lt;/strong&gt;: State is a commitment to a value. Prove you know the value, then update the commitment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Building a Private Ledger with Commitments
&lt;/h3&gt;

&lt;p&gt;Let's walk through a private token transfer system:&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 0.22;
import CompactStandardLibrary;

// Public state: only commitments to balances, never actual amounts
export ledger balanceCommitment: Map&amp;lt;Bytes&amp;lt;32&amp;gt;, Field&amp;gt;;
export ledger totalSupply: Uint&amp;lt;128&amp;gt;;
export ledger transferRound: Counter;

// Private data structure (never on-chain, only proven)
struct Account {
  owner: Bytes&amp;lt;32&amp;gt;,
  balance: Uint&amp;lt;128&amp;gt;,
  nonce: Uint&amp;lt;64&amp;gt;
}

// Witness: the actual account data, held privately by user
witness getAccount(): Account;
witness getNullifierSecret(): Bytes&amp;lt;32&amp;gt;;

// Helper: derive a nullifier to prevent double-spending
circuit deriveNullifier(nonce: Uint&amp;lt;64&amp;gt;, secret: Bytes&amp;lt;32&amp;gt;): Field {
  return persistentHash&amp;lt;Vector&amp;lt;2, Field&amp;gt;&amp;gt;(
    [persistentHash&amp;lt;Bytes&amp;lt;32&amp;gt;&amp;gt;(nonce as Bytes&amp;lt;32&amp;gt;), 
     persistentHash&amp;lt;Bytes&amp;lt;32&amp;gt;&amp;gt;(secret)]
  ) as Field;
}

// Helper: commitment to an account
circuit commitToAccount(account: Account, salt: Bytes&amp;lt;32&amp;gt;): Field {
  return persistentHash&amp;lt;Vector&amp;lt;2, Field&amp;gt;&amp;gt;(
    [persistentHash&amp;lt;Account&amp;gt;(account),
     persistentHash&amp;lt;Bytes&amp;lt;32&amp;gt;&amp;gt;(salt)]
  ) as Field;
}

// Main circuit: prove a valid token transfer
export circuit transfer(
  recipient: Bytes&amp;lt;32&amp;gt;,
  amount: Uint&amp;lt;128&amp;gt;,
  salt: Bytes&amp;lt;32&amp;gt;,
  newSalt: Bytes&amp;lt;32&amp;gt;
): [] {
  // Load private account data
  const account = getAccount();
  const nullifierSecret = getNullifierSecret();

  // Verify the account commitment exists
  const oldCommitment = commitToAccount(account, salt);
  assert(
    balanceCommitment[account.owner] == oldCommitment,
    "Account commitment mismatch"
  );

  // Private verification: user has sufficient balance
  assert(account.balance &amp;gt;= amount, "Insufficient balance");

  // Compute new account state (private)
  const newAccount: Account = [
    owner: account.owner,
    balance: account.balance - amount,
    nonce: account.nonce + 1
  ];

  // Create nullifier to prevent replay
  const nullifier = deriveNullifier(account.nonce, nullifierSecret);

  // Update public state with new commitment
  const newCommitment = commitToAccount(newAccount, newSalt);
  balanceCommitment = disclose(account.owner, newCommitment);

  // Record nullifier to prevent double-spend
  // In a real system, this would be a set of spent nullifiers
  // For now, we disclose it as proof of spending
  disclose(nullifier);

  // Recipient balance update (simplified: assume recipient pre-existed)
  // In production, you'd handle account creation
  transferRound.increment(1);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Commitment Tradeoff
&lt;/h3&gt;

&lt;p&gt;This approach provides strong privacy but requires careful design:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Balances are never visible on-chain (only commitments)&lt;/li&gt;
&lt;li&gt;Transfers reveal no information except that &lt;em&gt;a&lt;/em&gt; transfer occurred&lt;/li&gt;
&lt;li&gt;The system is composable with other private circuits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Costs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every balance update requires a full commitment recomputation&lt;/li&gt;
&lt;li&gt;Clients must store balance commitments locally (or query from a private oracle)&lt;/li&gt;
&lt;li&gt;Replay protection requires tracking spent nullifiers&lt;/li&gt;
&lt;li&gt;The circuit is more complex, leading to larger proofs and longer proving times&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real world consideration&lt;/strong&gt;: For most applications, you won't implement full commitment schemes from scratch. You'll use Midnight's standard library, which provides optimized versions. But understanding the underlying structure helps you choose the right patterns for your use case.&lt;/p&gt;




&lt;h2&gt;
  
  
  Circuit Optimization
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why Circuit Optimization Matters
&lt;/h3&gt;

&lt;p&gt;Compact circuits must be bounded at compile time. You can't have unbounded loops or recursive calls. This constraint exists because every circuit must compile to a fixed-size zero-knowledge proof.&lt;/p&gt;

&lt;p&gt;For EVM developers, this is a significant mindset shift. On Ethereum, you pay gas for computation. On Midnight, you accept predetermined computation bounds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// This won't compile unbounded recursion
circuit traverse(node: TreeNode): Uint&amp;lt;64&amp;gt; {
  if (node.left == null) {
    return node.value;
  } else {
    return traverse(node.left);
  }
}

// This works—bounded by tree depth
export circuit traverseFixed&amp;lt;#DEPTH&amp;gt;(
  node: TreeNode, 
  path: Vector&amp;lt;#DEPTH, Boolean&amp;gt;
): Uint&amp;lt;64&amp;gt; {
  let current = node;
  for (let i = 0; i &amp;lt; #DEPTH; i++) {
    if (path[i]) {
      current = current.right; // Assumes node structure allows this
    } else {
      current = current.left;
    }
  }
  return current.value;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Optimization Strategies
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1: Vectorization and Batching
&lt;/h4&gt;

&lt;p&gt;For operations on multiple items, vectorize instead of looping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Less efficient: separate proofs for each item
export circuit verifyAge(secret: Bytes&amp;lt;32&amp;gt;, minAge: Uint&amp;lt;32&amp;gt;): [] {
  const birthDate = getUserBirthDate();
  assert(currentTime - birthDate &amp;gt;= minAge, "Too young");
}

// More efficient: batch verification
export circuit verifyAgesInBatch&amp;lt;#N&amp;gt;(
  secrets: Vector&amp;lt;#N, Bytes&amp;lt;32&amp;gt;&amp;gt;,
  minAges: Vector&amp;lt;#N, Uint&amp;lt;32&amp;gt;&amp;gt;
): [] {
  for (let i = 0; i &amp;lt; #N; i++) {
    // Witness supplies ages for all users
    const birthDates = getUserBirthDates(i);
    assert(
      currentTime - birthDates &amp;gt;= minAges[i],
      "Age check failed"
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2: Lazy Evaluation with Merkle Trees
&lt;/h4&gt;

&lt;p&gt;Instead of processing all data inline, use Merkle trees to prove membership:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Direct approach: verify all items (O(n) circuit size)
export circuit verifyAllBalances&amp;lt;#N&amp;gt;(
  balances: Vector&amp;lt;#N, Uint&amp;lt;128&amp;gt;&amp;gt;,
  totalRequired: Uint&amp;lt;128&amp;gt;
): [] {
  let sum: Uint&amp;lt;128&amp;gt; = 0;
  for (let i = 0; i &amp;lt; #N; i++) {
    sum = sum + balances[i];
  }
  assert(sum &amp;gt;= totalRequired, "Insufficient total");
}

// Optimized: verify membership in Merkle tree (O(log n) circuit size)
export circuit verifyBalanceProof(
  balance: Uint&amp;lt;128&amp;gt;,
  merkleProof: Vector&amp;lt;32, Field&amp;gt;, // Log2(2^32) = 32 levels
  merkleRoot: Field,
  leaf_index: Uint&amp;lt;32&amp;gt;
): [] {
  // Recompute leaf and verify path
  const leaf = persistentHash&amp;lt;Uint&amp;lt;128&amp;gt;&amp;gt;(balance) as Field;
  let current = leaf;

  for (let i = 0; i &amp;lt; 32; i++) {
    const proofElement = merkleProof[i];
    // Combine in canonical order to prevent tree structure attacks
    if (leaf_index &amp;amp; (1 &amp;lt;&amp;lt; i) == 0) {
      current = persistentHash&amp;lt;Vector&amp;lt;2, Field&amp;gt;&amp;gt;([current, proofElement]) as Field;
    } else {
      current = persistentHash&amp;lt;Vector&amp;lt;2, Field&amp;gt;&amp;gt;([proofElement, current]) as Field;
    }
  }

  assert(current == merkleRoot, "Merkle proof failed");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3: Proof Aggregation
&lt;/h4&gt;

&lt;p&gt;When you have multiple privacy preserving properties to prove, you have two choices: prove them all in one circuit (larger proof), or split into separate circuits (multiple proofs, sequential verification).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Single circuit: proves age AND asset ownership
// Proof size: large, proving time: high
export circuit verifyAgeAndAssets(
  secret: Bytes&amp;lt;32&amp;gt;,
  minAge: Uint&amp;lt;32&amp;gt;,
  assetsProof: AssetsProof
): [] {
  const birthDate = getUserBirthDate();
  assert(currentTime - birthDate &amp;gt;= minAge, "Too young");

  const assets = getAssets(assetsProof);
  assert(assets.value &amp;gt;= 100000, "Insufficient assets");
}

// Split circuits: separate concerns, compose on app level
// Proof size: smaller per circuit, proving time: faster
export circuit verifyAge(secret: Bytes&amp;lt;32&amp;gt;, minAge: Uint&amp;lt;32&amp;gt;): Bytes&amp;lt;32&amp;gt; {
  const birthDate = getUserBirthDate();
  assert(currentTime - birthDate &amp;gt;= minAge, "Too young");
  return disclose(persistentHash&amp;lt;Bytes&amp;lt;32&amp;gt;&amp;gt;(secret));
}

export circuit verifyAssets(assetsProof: AssetsProof): Bytes&amp;lt;32&amp;gt; {
  const assets = getAssets(assetsProof);
  assert(assets.value &amp;gt;= 100000, "Insufficient assets");
  return disclose(persistentHash&amp;lt;Bytes&amp;lt;32&amp;gt;&amp;gt;(assetsProof));
}

// Application composes both proofs
// Tradeoff: two proofs to verify, but faster to prove each one
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Benchmarking Your Circuits
&lt;/h3&gt;

&lt;p&gt;Different circuit structures have dramatic performance differences. Use this framework to evaluate:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Structure&lt;/th&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;th&gt;Use When&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Direct computation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Simple, straightforward&lt;/td&gt;
&lt;td&gt;Large proof, slow&lt;/td&gt;
&lt;td&gt;Small bounded operations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Merkle proof verification&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Logarithmic size, scales well&lt;/td&gt;
&lt;td&gt;Higher cryptographic complexity&lt;/td&gt;
&lt;td&gt;Membership checks in large sets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Vectorized batching&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Efficient for repeated ops&lt;/td&gt;
&lt;td&gt;Requires uniform structure&lt;/td&gt;
&lt;td&gt;Batch processing many similar items&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Split circuits&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Faster per-circuit proving&lt;/td&gt;
&lt;td&gt;Coordination overhead&lt;/td&gt;
&lt;td&gt;When proofs are logically independent&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Multi-Contract Privacy Architecture
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Composing Private Contracts
&lt;/h3&gt;

&lt;p&gt;Midnight's biggest strength is that multiple private contracts can interact while maintaining privacy boundaries. However, composing contracts introduces new considerations:&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem: The Metadata Leak
&lt;/h3&gt;

&lt;p&gt;Even if all data is encrypted, metadata can leak information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Privacy leak: contract call pattern reveals intent
export circuit buyAsset(assetId: Uint&amp;lt;64&amp;gt;): Bytes&amp;lt;32&amp;gt; {
  // If specific assetIds always correlate with specific users,
  // blockchain analysis can link buyers to assets even without seeing amounts
  const proof = getOwnershipProof(assetId);
  verify(proof);
  return disclose(persistentHash&amp;lt;Uint&amp;lt;64&amp;gt;&amp;gt;(assetId));
}

// Mitigated: hide specific asset, batch with dummy calls
export circuit batchBuyAssets&amp;lt;#N&amp;gt;(
  assetIds: Vector&amp;lt;#N, Uint&amp;lt;64&amp;gt;&amp;gt;,
  proofs: Vector&amp;lt;#N, Bytes&amp;lt;32&amp;gt;&amp;gt;,
  isReal: Vector&amp;lt;#N, Boolean&amp;gt;
): Vector&amp;lt;#N, Bytes&amp;lt;32&amp;gt;&amp;gt; {
  let results: Vector&amp;lt;#N, Bytes&amp;lt;32&amp;gt;&amp;gt; = [];
  for (let i = 0; i &amp;lt; #N; i++) {
    // Verify proof only if real purchase (circuits execute either way)
    if (isReal[i]) {
      verify(proofs[i]);
    }
    results[i] = disclose(persistentHash&amp;lt;Uint&amp;lt;64&amp;gt;&amp;gt;(assetIds[i]));
  }
  return results;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern: Shielded Contract Composition
&lt;/h3&gt;

&lt;p&gt;When one private contract depends on another, you need a protocol for safe interaction:&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 0.22;
import CompactStandardLibrary;

// Contract A: Identity registry
export ledger identityCommitment: Map&amp;lt;Bytes&amp;lt;32&amp;gt;, Field&amp;gt;;

export circuit registerIdentity(publicKey: Bytes&amp;lt;32&amp;gt;): [] {
  const commitment = persistentHash&amp;lt;Bytes&amp;lt;32&amp;gt;&amp;gt;(publicKey) as Field;
  identityCommitment = disclose(publicKey, commitment);
}

// Contract B: Private voting (depends on Contract A)
export ledger voteCommitment: Map&amp;lt;Field, Field&amp;gt;; // (identityHash, voteHash)

export circuit castVote(
  publicKey: Bytes&amp;lt;32&amp;gt;,
  voteChoice: Uint&amp;lt;8&amp;gt;,
  salt: Bytes&amp;lt;32&amp;gt;
): [] {
  // Prove participation in Contract A without revealing identity
  const commitment = persistentHash&amp;lt;Bytes&amp;lt;32&amp;gt;&amp;gt;(publicKey) as Field;
  assert(identityCommitment[publicKey] == commitment, "Not registered");

  // Cast vote privately
  const voteHash = persistentHash&amp;lt;Vector&amp;lt;2, Field&amp;gt;&amp;gt;(
    [persistentHash&amp;lt;Uint&amp;lt;8&amp;gt;&amp;gt;(voteChoice) as Field, 
     persistentHash&amp;lt;Bytes&amp;lt;32&amp;gt;&amp;gt;(salt) as Field]
  ) as Field;

  voteCommitment = disclose(commitment, voteHash);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key insight&lt;/strong&gt;: Contract B proves it respects Contract A's invariants (the user is registered) without revealing the user's identity. This is the foundation of composable privacy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Considerations: State Consistency
&lt;/h3&gt;

&lt;p&gt;When multiple contracts touch shared state, you must be careful about ordering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Race condition possible: State updated after verification
export circuit transferWithFeeShare(
  amount: Uint&amp;lt;128&amp;gt;,
  feeRecipient: Bytes&amp;lt;32&amp;gt;
): [] {
  const balance = getBalance(); // Witness
  assert(balance &amp;gt;= amount + fee, "Insufficient");

  // Race condition: if another proof updates fee rate before on-chain execution,
  // this assertion might have been based on stale assumptions
  const currentFee = feeContract.queryFee();
}

//  Fixed: Include fee data in the proof
export circuit transferWithFeeShare(
  amount: Uint&amp;lt;128&amp;gt;,
  feeRecipient: Bytes&amp;lt;32&amp;gt;,
  expectedFeeRate: Uint&amp;lt;16&amp;gt;, // App provides expected fee
  feeProof: Bytes&amp;lt;32&amp;gt; // Proof that fee rate matches
): [] {
  const balance = getBalance();

  // Verify fee was what we expected at proof time
  verify(feeProof);

  const fee = (amount * expectedFeeRate) / 100000;
  assert(balance &amp;gt;= amount + fee, "Insufficient");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Real-World Tradeoffs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Decision Matrix
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Witness Source&lt;/th&gt;
&lt;th&gt;Proof Strategy&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Privacy-first tokens&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Commitments + Nullifiers&lt;/td&gt;
&lt;td&gt;User secret key&lt;/td&gt;
&lt;td&gt;Split by operation type&lt;/td&gt;
&lt;td&gt;Requires nullifier tracking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;KYC/AML compliance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Age/identity verification&lt;/td&gt;
&lt;td&gt;Credential issuer&lt;/td&gt;
&lt;td&gt;Selective disclosure&lt;/td&gt;
&lt;td&gt;Issuer must be trusted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DAO voting&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Shielded voting + identity registry&lt;/td&gt;
&lt;td&gt;User secret, registry contract&lt;/td&gt;
&lt;td&gt;Batched dummy votes&lt;/td&gt;
&lt;td&gt;Metadata still visible (voting time)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Asset swaps&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DEX with private pricing**&lt;/td&gt;
&lt;td&gt;Oracle feeds, user orders&lt;/td&gt;
&lt;td&gt;Batch matching&lt;/td&gt;
&lt;td&gt;Requires MEV-resistant ordering&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Debugging &amp;amp; Testing Advanced Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Console Logging in Compact
&lt;/h3&gt;

&lt;p&gt;While developing, use logging carefully (it's not available in production proofs):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit debugTransfer(
  recipient: Bytes&amp;lt;32&amp;gt;,
  amount: Uint&amp;lt;128&amp;gt;
): [] {
  const balance = getBalance();

  // Debug logging helps during development
  assert(balance &amp;gt;= amount, "Insufficient");

  // The proof doesn't include debug output, but your app runner sees it
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testing Strategy for Circuits
&lt;/h3&gt;

&lt;p&gt;Since circuits must be proven, test thoroughly before deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// TypeScript test harness&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Contract&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@midnight-protocol/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Contract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;compiledCircuit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Test 1: Valid transfer&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validTransfer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;randomSalt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;newSalt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newRandomSalt&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;validTransfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;proof&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Valid transfer should produce proof&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Test 2: Insufficient balance should fail&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invalidTransfer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;999999999999&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;randomSalt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;newSalt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newRandomSalt&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;invalidTransfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;proof&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid transfer should not produce proof&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;p&gt;Advanced Compact patterns aren't just optimizations they're architectural decisions that shape what's possible in your application.&lt;/p&gt;

&lt;p&gt;As you move from learning Compact to building production systems, keep these principles in mind:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Witnesses are your privacy boundary.&lt;/strong&gt; Choose witness sources carefully; they determine your security model.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Commitments enable privacy at scale.&lt;/strong&gt; Direct state disclosure doesn't mix with zero-knowledge proofs; use commitments instead.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Circuits must be bounded, but cleverly.&lt;/strong&gt; Vectorization, Merkle trees, and proof aggregation let you handle complexity without exceeding bounds.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Composition requires explicit coordination.&lt;/strong&gt; Multiple private circuits can interact, but metadata and state consistency need careful handling.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Privacy and usability are in tension.&lt;/strong&gt; Batching dummy transactions protects metadata but increases proof sizes. Splitting circuits proves faster but requires coordination. Choose based on your threat model.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Midnight gives you powerful tools for building privacy-first applications. Mastering these patterns lets you use them effectively.&lt;/p&gt;




&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Midnight Language Reference&lt;/strong&gt;: &lt;a href="https://docs.midnight.network/develop/reference/compact" rel="noopener noreferrer"&gt;Full Compact syntax and semantics&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explicit Disclosure Deep Dive&lt;/strong&gt;: Understanding the &lt;code&gt;disclose()&lt;/code&gt; wrapper and threat models&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero Knowledge Proof Fundamentals&lt;/strong&gt;: If you want to understand the cryptography behind Compact circuits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Previous in this series&lt;/strong&gt;: &lt;a href="https://dev.tolink-to-samantha-post"&gt;Learning Web3 from the Ground Up&lt;/a&gt; by Samantha Holstine&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>architecture</category>
      <category>blockchain</category>
      <category>tutorial</category>
      <category>web3</category>
    </item>
    <item>
      <title>Building Privacy-Preserving Decentralized Identity on Midnight</title>
      <dc:creator>Nasihudeen Jimoh</dc:creator>
      <pubDate>Wed, 01 Apr 2026 13:28:20 +0000</pubDate>
      <link>https://forem.com/midnight-aliit/building-privacy-preserving-decentralized-identity-on-midnight-3abn</link>
      <guid>https://forem.com/midnight-aliit/building-privacy-preserving-decentralized-identity-on-midnight-3abn</guid>
      <description>&lt;p&gt;This isn't another high-level overview you'll forget in a week. This is a complete,technical deep-dive i wish i had when i started building on Midnight three weeks ago. &lt;br&gt;
I've spent countless nights debugging witnesses, fighting with PK derivation mismatches, and discovering exactly what works and what doesn't.&lt;/p&gt;

&lt;p&gt;By the time you finish this guide, you'll have a complete, tested, DID + Verifiable Credentials system that actually runs today on Midnight Preview.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why This Actually Matters
&lt;/h2&gt;

&lt;p&gt;KYC/AML checks. Age verification. Accredited investor authentication. Voting eligibility. Healthcare access control. These aren't optional features they're regulatory requirements for any serious DeFi, enterprise, or consumer app in 2026.&lt;/p&gt;

&lt;p&gt;Here's the problem:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Traditional centralized systems&lt;/strong&gt; = complete data leakage to issuers and verifiers&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Public blockchains&lt;/strong&gt; = every claim is permanently visible to the entire world&lt;br&gt;&lt;br&gt;
&lt;strong&gt;"Privacy-focused" solutions&lt;/strong&gt; = still require off-chain oracles or expose too much metadata&lt;/p&gt;

&lt;p&gt;Midnight solves this perfectly because it's &lt;strong&gt;shielded by default&lt;/strong&gt;. The ledger only ever sees commitments and zero-knowledge proofs. Your raw data (birthdate, net worth, KYC status) stays on your device forever.&lt;/p&gt;

&lt;p&gt;Today we're building exactly that: a W3C-aligned Decentralized Identifier (DID) system with Verifiable Credentials that supports &lt;strong&gt;selective disclosure&lt;/strong&gt; using Midnight's Compact language and persistentHash-based commitments.&lt;/p&gt;


&lt;h2&gt;
  
  
  Prerequisites &amp;amp; Setup (Do This First)
&lt;/h2&gt;

&lt;p&gt;You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Node.js 18+&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A wallet seed from the Midnight &lt;a href="https://faucet.preprod.midnight.network/" rel="noopener noreferrer"&gt;faucet&lt;/a&gt;&lt;/strong&gt; (Preview or Preprod)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Docker&lt;/strong&gt; &lt;a href="https://docs.midnight.network/getting-started/installation#set-up-the-proof-server" rel="noopener noreferrer"&gt;(for the local proof server)&lt;br&gt;
&lt;/a&gt;&lt;br&gt;
Run these commands:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Kanasjnr/Midnight-Privacy-Preserving-DID-System
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;span class="c"&gt;# Edit .env and put your 64-char WALLET_SEED&lt;/span&gt;
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;What &lt;code&gt;npm run setup&lt;/code&gt; does:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Compiles all four Compact contracts → artifacts in &lt;code&gt;contracts/managed/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Builds the TypeScript layer with &lt;code&gt;tsc&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Runs &lt;code&gt;deploy.js&lt;/code&gt; to deploy contracts and saves addresses to &lt;code&gt;deployment.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After setup, you'll have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;did-registry&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;schema-registry&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;credential-issuer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;proof-verifier&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check everything is ready:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run check-balance &lt;span class="c"&gt;# to check your wallet balance&lt;/span&gt;
npm run register-dust   &lt;span class="c"&gt;# to register your tNight to DUST for gas&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You're now ready to go.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 1: Understanding the Architecture
&lt;/h2&gt;

&lt;p&gt;Before we dive into contracts, let's think about what we're building.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Three Layers
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Layer 1: On-Chain Ledger&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;The ledger stores &lt;strong&gt;only&lt;/strong&gt; cryptographic commitments. Not data. Not hashes of data. Specifically, commitments that are generated from raw data + a salt, using the &lt;code&gt;persistentHash&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;Think of it like this the issuer takes your birthdate (19900101) and a salt (a random 32-byte value), hashes them together, and stores the result on-chain. The ledger now has: &lt;code&gt;commitment = persistentHash([19900101, salt])&lt;/code&gt;. That commitment is public. But from that commitment alone, it's computationally infeasible to reverse-engineer either the birthdate or the salt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 2: Local Credential Storage&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;The holder keeps the raw data locally. Encrypted. Never shared.&lt;/p&gt;

&lt;p&gt;When the issuer issues a credential, they don't store the raw claim. They only store:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;claims:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;dateOfBirth:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;19900101&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;salt:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;random&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;bytes&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;commitment:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;sha&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;hash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;claims&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;salt&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The commitment goes on-chain. Everything else stays on the holder's device. This separation is critical the issuer can't prove the data later because they never stored it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 3: Proof Generation (Offline)&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;When the holder needs to prove something ("I'm over 18"), they use the raw credential stored locally to generate a zero-knowledge proof. This proof is created locally, without touching the ledger.&lt;/p&gt;

&lt;p&gt;The proof says: "I know a value whose hash equals this commitment, and that value satisfies this property (age &amp;gt; 18)." The verifier can check the math without learning what the value is.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Actual Flow
&lt;/h3&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%2F5jwhhvnz87908a0pt5c9.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%2F5jwhhvnz87908a0pt5c9.png" alt=" " width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;the verifier doesn't need to query the ledger at all. They just need the proof and the commitment (which is public anyway).&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 1: Understanding persistentHash
&lt;/h2&gt;

&lt;p&gt;This is where most developers get stuck, so let's go deep.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;persistentHash&lt;/code&gt; is Midnight's built-in hash function in Compact. It's deterministic same input always produces the same output. It takes a vector of byte arrays and returns a 32-byte hash.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const commitment = persistentHash&amp;lt;Vector&amp;lt;2, Bytes&amp;lt;32&amp;gt;&amp;gt;&amp;gt;([
  dateOfBirth as Bytes&amp;lt;32&amp;gt;,
  salt
]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is saying: "Create a persistent hash of a vector containing 2 elements (both 32-byte arrays): the DOB and the salt."&lt;/p&gt;

&lt;p&gt;When you hash in Compact, you're working with &lt;code&gt;Bytes&amp;lt;32&amp;gt;&lt;/code&gt;. Everything gets padded to 32 bytes. So if your birthdate is &lt;code&gt;19900101&lt;/code&gt; (an integer), you need to cast it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(dateOfBirth as Field) as Bytes&amp;lt;32&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't intuitive. But it's how Compact works. I fought with this for hours. The error messages don't tell you what went wrong you just get "type mismatch" and have to guess.&lt;/p&gt;

&lt;p&gt;Now, when the verifier checks the proof, they use the &lt;em&gt;exact same hash function&lt;/em&gt;. If the prover claims "this value hashes to commitment X" and the math doesn't check out, the proof fails.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 2: The Contracts
&lt;/h2&gt;

&lt;p&gt;We're building four Compact contracts. All in one directory: &lt;code&gt;contracts/&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contract 1 — types.compact (Shared Types)
&lt;/h3&gt;

&lt;p&gt;This file is imported by every other contract. It defines the data structures we'll use everywhere.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;
&lt;span class="n"&gt;pragma&lt;/span&gt; &lt;span class="n"&gt;language_version&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.20&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mf"&gt;0.21&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CompactStandardLibrary&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;DIDEntry&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;document_commitment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// hash of the DID document&lt;/span&gt;
  &lt;span class="n"&gt;controller_pk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;          &lt;span class="c1"&gt;// derived from controller secret key&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;CredentialStatus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;ACTIVE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;REVOKED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;SUSPENDED&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;SchemaMetadata&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Uint&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;creator_pk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;json_schema_hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;IssuanceEntry&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;holder_did_hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;schema_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;credential_commitment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// ← this is the magic&lt;/span&gt;
  &lt;span class="n"&gt;issuer_pk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CredentialStatus&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this structure?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DIDEntry&lt;/code&gt; stores the commitment of the DID document + the controller's public key&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CredentialStatus&lt;/code&gt; lets us revoke credentials without removing them&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IssuanceEntry&lt;/code&gt; links a holder, schema, and credential commitment together&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;credential_commitment&lt;/code&gt; field is crucial. It's not the claim itself. It's a hash of (claim + salt). From this alone, you can't reverse-engineer the claim.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contract 2: did-registry.compact (The Root of Trust)
&lt;/h3&gt;

&lt;p&gt;This contract is the foundation. It's where DIDs are registered and updated. A DID is basically a public key that controls an identity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;
&lt;span class="n"&gt;pragma&lt;/span&gt; &lt;span class="n"&gt;language_version&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.20&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mf"&gt;0.21&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CompactStandardLibrary&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;include&lt;/span&gt; &lt;span class="nv"&gt;'types&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt; &lt;span class="n"&gt;did_registry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DIDEntry&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// This witness is never stored on-chain&lt;/span&gt;
&lt;span class="n"&gt;witness&lt;/span&gt; &lt;span class="nf"&gt;controller_secret_key&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;pure&lt;/span&gt; &lt;span class="n"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;derive_pk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;persistentHash&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nf"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"midnight:pk:"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;// 12-byte prefix + 20 zero bytes&lt;/span&gt;
    &lt;span class="n"&gt;sk&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;registerDID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;did_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;document_commitment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;controller_pk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;did_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document_commitment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_pk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;controller_pk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;did_registry&lt;/span&gt;&lt;span class="nf"&gt;.member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"DID already exists"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;did_registry&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DIDEntry&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;d_doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d_pk&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;updateDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;did_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;new_commitment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;did_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_new_doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_commitment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;did_registry&lt;/span&gt;&lt;span class="nf"&gt;.lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;derived_pk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;derive_pk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;controller_secret_key&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="py"&gt;.controller_pk&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;derived_pk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Not authorized"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="n"&gt;did_registry&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DIDEntry&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;d_new_doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="py"&gt;.controller_pk&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Let me walk you through what's happening:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;controller_secret_key()&lt;/code&gt; witness&lt;/strong&gt; — This is the holder's private key. It's passed to the circuit but &lt;strong&gt;never stored on-chain&lt;/strong&gt;. The circuit uses it to derive the public key and verify authorization.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;derive_pk()&lt;/code&gt; function&lt;/strong&gt; — This is &lt;strong&gt;critical&lt;/strong&gt;. It hashes the secret key in a specific way to produce the public key. The exact same function must exist in your TypeScript code. If it doesn't match byte-for-byte, authorization will fail. Trust me, I spent 3 hours debugging this.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;registerDID()&lt;/code&gt;&lt;/strong&gt; — Creates a new DID entry. The &lt;code&gt;disclose()&lt;/code&gt; calls mean these values are public inputs to the zero-knowledge proof.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;updateDocument()&lt;/code&gt;&lt;/strong&gt; — Updates the DID document commitment. The key move: it looks up the existing entry, derives the public key from the secret key witness, and verifies it matches. Only the real controller can update.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The circuit:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Derives the public key from the secret key using &lt;code&gt;derive_pk()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Looks up the stored DID entry&lt;/li&gt;
&lt;li&gt;Compares: does the derived PK match the stored PK?&lt;/li&gt;
&lt;li&gt;If yes, authorization is granted. If no, the circuit fails.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is how Midnight enforces authorization without a traditional "signed transaction" model. The secret key is the witness only the true controller knows it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contract 3: schema-registry.compact (Credential Types)
&lt;/h3&gt;

&lt;p&gt;This contract stores metadata about credential schemas. It's straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt; &lt;span class="n"&gt;schema_registry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SchemaMetadata&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;registerSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;schema_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Uint&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;creator_pk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;json_schema_hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schema_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_creator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;creator_pk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json_schema_hash&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;schema_registry&lt;/span&gt;&lt;span class="nf"&gt;.member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"Schema already registered"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;schema_registry&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SchemaMetadata&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;d_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d_version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d_creator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d_hash&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets you define schemas like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"age-credential" (has a dateOfBirth field)&lt;/li&gt;
&lt;li&gt;"accredited-investor" (has netWorth and accreditationDate)&lt;/li&gt;
&lt;li&gt;"passport" (has various identity fields)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each schema gets a unique ID and is registered on-chain. The &lt;code&gt;json_schema_hash&lt;/code&gt; points to the full JSON schema stored off-chain (e.g., on IPFS).&lt;/p&gt;

&lt;h3&gt;
  
  
  Contract 4: credential-issuer.compact (Issuing Commitments)
&lt;/h3&gt;

&lt;p&gt;The issuer's job: take claims (birthdate, net worth, etc.), hash them with a salt, and store the commitment on-chain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;
&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt; &lt;span class="n"&gt;credential_ledger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IssuanceEntry&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;issueCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;holder_did_hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;schema_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;credential_commitment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;issuer_pk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_holder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;holder_did_hash&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schema_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_commitment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;credential_commitment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_issuer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;issuer_pk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;issuance_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;persistentHash&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="n"&gt;d_holder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d_schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d_commitment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d_issuer&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;credential_ledger&lt;/span&gt;&lt;span class="nf"&gt;.member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;issuance_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"Credential already issued"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;credential_ledger&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;issuance_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IssuanceEntry&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;d_holder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;d_schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;d_commitment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;d_issuer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ACTIVE&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;revokeCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;issuance_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;issuance_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;credential_ledger&lt;/span&gt;&lt;span class="nf"&gt;.lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;issuer_pk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;derive_pk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;issuer_secret_key&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="py"&gt;.issuer_pk&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;issuer_pk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Only issuer can revoke"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Update status without removing from ledger&lt;/span&gt;
  &lt;span class="n"&gt;credential_ledger&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IssuanceEntry&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="py"&gt;.holder_did_hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="py"&gt;.schema_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="py"&gt;.credential_commitment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="py"&gt;.issuer_pk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;REVOKED&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key insight:&lt;/strong&gt; The issuer posts &lt;em&gt;only&lt;/em&gt; the commitment. Not the raw claim. The commitment is a hash of (claim + salt). The issuer sends the raw claim and salt to the holder separately (securely, off-chain).&lt;/p&gt;

&lt;p&gt;When later the holder proves "I'm over 18", they use the raw claim + salt (stored locally) to reconstruct the commitment and prove it matches.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contract 5: proof-verifier.compact (Zero-Knowledge Heart)
&lt;/h3&gt;

&lt;p&gt;This is where selective disclosure happens. The circuit proves something about the commitment without revealing the underlying data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;pragma&lt;/span&gt; &lt;span class="n"&gt;language_version&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.20&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mf"&gt;0.21&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CompactStandardLibrary&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;ledger&lt;/span&gt; &lt;span class="n"&gt;dummy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// forces ZK circuit generation&lt;/span&gt;

&lt;span class="c1"&gt;// These are witnesses — private inputs only the prover knows&lt;/span&gt;
&lt;span class="n"&gt;witness&lt;/span&gt; &lt;span class="nf"&gt;dateOfBirth&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="n"&gt;Uint&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// YYYYMMDD format&lt;/span&gt;
&lt;span class="n"&gt;witness&lt;/span&gt; &lt;span class="nf"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;circuit&lt;/span&gt; &lt;span class="nf"&gt;verifyAge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;current_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Uint&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;threshold_years&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Uint&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;expected_commitment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;_force_zk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dummy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// These are disclosed (public inputs)&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_date&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold_years&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;d_expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;disclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected_commitment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// These stay private (only in the circuit)&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;dob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dateOfBirth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Recompute commitment exactly as the issuer did&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;computed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;persistentHash&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dob&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;computed&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;d_expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Invalid credential commitment"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Age math inside the circuit — raw DOB never leaves the prover&lt;/span&gt;
  &lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d_current&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;dob&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d_threshold&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;Uint&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"Underage"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;This is the magic.&lt;/strong&gt; Let me break it down:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Witnesses&lt;/strong&gt; — &lt;code&gt;dateOfBirth&lt;/code&gt; and &lt;code&gt;salt&lt;/code&gt; are private inputs. Only the prover knows them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public inputs&lt;/strong&gt; — &lt;code&gt;current_date&lt;/code&gt;, &lt;code&gt;threshold_years&lt;/code&gt;, &lt;code&gt;expected_commitment&lt;/code&gt; are disclosed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commitment verification&lt;/strong&gt; — We recompute the commitment inside the circuit. If it matches, we know the DOB and salt are real.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Age check&lt;/strong&gt; — We do the math: &lt;code&gt;(year * 10000 + month * 100 + day) - dob &amp;gt;= threshold * 10000&lt;/code&gt;. All inside the circuit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output&lt;/strong&gt; — A zero-knowledge proof that proves "the holder is over the threshold" without revealing the DOB.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The verifier sees:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ The proof&lt;/li&gt;
&lt;li&gt;✅ The commitment (already public from issuance)&lt;/li&gt;
&lt;li&gt;✅ The threshold age&lt;/li&gt;
&lt;li&gt;❌ NOT the birthdate&lt;/li&gt;
&lt;li&gt;❌ NOT the salt&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is selective disclosure. This is privacy-preserving verification.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 3: The TypeScript SDK
&lt;/h2&gt;

&lt;p&gt;Now we need code that talks to these contracts. The SDK lives in &lt;code&gt;src/&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 DIDManager.ts — Creating and Controlling DIDs
&lt;/h3&gt;

&lt;p&gt;This is the entry point. A holder creates a DID and keeps their secret key safe.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;randomBytes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CompactTypeBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;persistentHash&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@midnight-ntwrk/compact-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DIDManager&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;controllerSecretKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secretKey&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;controllerSecretKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;secretKey&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// This MUST match the Compact derive_pk exactly&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;deriveCompactPk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bytes32&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CompactTypeBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vector2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CompactTypeVector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bytes32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Exact same prefix as in Compact: "midnight:pk:" + padding&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prefixStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;midnight:pk:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prefixStr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;persistentHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vector2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sk&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;getPublicKey&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deriveCompactPk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;controllerSecretKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;deriveDIDHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;didName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;didName&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;registerDID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;didName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;didHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deriveDIDHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;didName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;documentCommitment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;didName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;created&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controllerPk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPublicKey&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Call the Midnight contract&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;txData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;callContract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;registerDID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;didHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;documentCommitment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;controllerPk&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[DID Registered] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;didName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[DID Hash] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;didHash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[Commitment] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;documentCommitment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The &lt;code&gt;deriveCompactPk()&lt;/code&gt; function is critical.&lt;/strong&gt; It must match the contract's &lt;code&gt;derive_pk()&lt;/code&gt; exactly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Same prefix: &lt;code&gt;"midnight:pk:"&lt;/code&gt; padded to 32 bytes&lt;/li&gt;
&lt;li&gt;Same hash function: &lt;code&gt;persistentHash&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Same vector type: &lt;code&gt;Vector&amp;lt;2, Bytes&amp;lt;32&amp;gt;&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this doesn't match, when the contract tries to authorize you, the derived public key won't match the stored public key, and the circuit fails with an authorization error.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The secret key is never sent to the contract—only the derived public key.&lt;/li&gt;
&lt;li&gt;The holder keeps the secret key safe locally.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3.2 CredentialManager.ts — Issuing and Storing Credentials
&lt;/h3&gt;

&lt;p&gt;The issuer uses this to issue credentials. The holder stores them locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;randomBytes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Credential&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;holderDIDHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;schemaId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;commitment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;issuedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CredentialManager&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;credentialStorage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Credential&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;storageFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;credentials.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadFromFile&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;issueCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;holderDIDHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;schemaId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Credential&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Generate commitment: hash(claims || salt)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;salt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;claimsStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;credential_commitment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;claimsStr&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Credential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;holderDIDHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;holderDIDHash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;schemaId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;schemaId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nx"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;commitment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;credential_commitment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;issuedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// Store locally (NOT on-chain yet)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitment&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentialStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Now issue on-chain (only the commitment)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;callContract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;issueCredential&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;holderDIDHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;schemaId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;credential_commitment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;issuerPublicKey&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[Credential Issued] Commitment: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitment&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;saveToFile&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Credential&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentialStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;cred&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cred&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storageFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;loadFromFile&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storageFile&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storageFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cred&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentialStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cred&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Credential&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why separate issuer and holder?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The issuer creates the commitment and posts it on-chain&lt;/li&gt;
&lt;li&gt;The holder receives the raw claims and salt&lt;/li&gt;
&lt;li&gt;Only the holder stores the credential locally (encrypted)&lt;/li&gt;
&lt;li&gt;The issuer never stores raw data&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3.3 ProofGenerator.ts — Creating Zero-Knowledge Proofs
&lt;/h3&gt;

&lt;p&gt;When the holder needs to prove something, they generate a proof locally. No ledger interaction needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CircuitContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;proveCircuit&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@midnight-ntwrk/compact-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProofGenerator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Credential&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Credential&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;generateAgeProof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;currentDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;thresholdYears&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;verifierAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Create circuit context&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;circuitContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CircuitContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;verifierAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;verifyAge&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;// circuit name&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Set public inputs&lt;/span&gt;
    &lt;span class="nx"&gt;circuitContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPublicInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;current_date&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentDate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;circuitContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPublicInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;threshold_years&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;thresholdYears&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;circuitContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPublicInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;expected_commitment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Set private inputs (witnesses)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dateOfBirth&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// YYYYMMDD&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;salt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;circuitContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setWitness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dateOfBirth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;circuitContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setWitness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;salt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Generate proof&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;proof&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;proveCircuit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;circuitContext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[Proof Generated] Age proof created`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[Proof Size] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;proof&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; bytes`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;proof&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The flow:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The holder loads their credential from local storage (raw claims + salt)&lt;/li&gt;
&lt;li&gt;Calls &lt;code&gt;generateAgeProof()&lt;/code&gt; with current date and threshold age&lt;/li&gt;
&lt;li&gt;The circuit verifies: "I know a value (DOB) that hashes to the commitment AND is old enough"&lt;/li&gt;
&lt;li&gt;The proof is generated locally, completely offline&lt;/li&gt;
&lt;li&gt;The proof can be sent to any verifier&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The verifier doesn't need to query the ledger. They just check the proof.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 4: The Interactive CLI
&lt;/h2&gt;

&lt;p&gt;I built a CLI that lets you interact with the entire system.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This launches an interactive prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;? What would you like to do?
  → Register DID
  → Issue Credential (DOB)
  → Verify Credential
  → Generate Age Proof
  → Check Status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Demo workflow:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Choose "Register DID" → Enter &lt;code&gt;alice.night&lt;/code&gt;(or whatever DID you want to register)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creates DID, stores secret key, shows DID hash and commitment&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Choose "Issue Credential (DOB)" → Enter DOB &lt;code&gt;19901225&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generates commitment, stores credential locally, posts to ledger&lt;/li&gt;
&lt;li&gt;Open &lt;code&gt;credentials.json&lt;/code&gt; — you'll see the raw DOB and salt &lt;strong&gt;only locally&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Choose "Verify Credential" → Verification happens on-chain&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Posts proof, checks it, updates status&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Choose "Generate Age Proof" → Offline proof generation&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generates ZK proof proving "over 18"&lt;/li&gt;
&lt;li&gt;No ledger interaction&lt;/li&gt;
&lt;li&gt;Proof can be sent to any verifier&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Part 5: Use Case 1 — Age Verification
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; A DeFi protocol needs to verify users are adults before allowing participation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Holder Registers&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;alice$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm run cli
&lt;span class="go"&gt;→ Register DID
→ alice
[DID Registered] alice
[DID Hash] 0x7f3a...
[Controller PK] 0x2b9e...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Issuer Issues Credential&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;issuer$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm run cli
&lt;span class="go"&gt;→ Issue Credential (DOB)
→ holder: alice
→ dob: 19901225
[Credential Issued] 
[Commitment] 0x5d4e...
[On-chain Tx] 0xabc123...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ledger now has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hocon"&gt;&lt;code&gt;&lt;span class="nl"&gt;credential_ledger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;holder_did_hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="l"&gt;x&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="l"&gt;f&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="l"&gt;a...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;schema_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="l"&gt;x...age-schema...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;credential_commitment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="l"&gt;x&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="l"&gt;d&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="l"&gt;e...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;issuer_pk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="l"&gt;x...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ACTIVE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the raw DOB? Only in &lt;code&gt;credentials.json&lt;/code&gt; on alice's device.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Holder Generates Proof&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;alice$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm run cli
&lt;span class="go"&gt;→ Generate Age Proof
→ current_date: 20260401
→ threshold: 18
→ verifier_address: 0xprotocol...

[Proof Generated]
[Proof Hash] 0x9a2f...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4: Verifier Checks Proof&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;verifier$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm run verify-proof &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;  --proof 0x9a2f... \
  --commitment 0x5d4e... \
  --threshold 18

Proof Valid
Age Verified: Over 18
DID: alice
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What the verifier learned:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Alice is over 18&lt;/li&gt;
&lt;li&gt;✅ The proof is mathematically valid&lt;/li&gt;
&lt;li&gt;❌ Alice's actual birthdate&lt;/li&gt;
&lt;li&gt;❌ Any other PII&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is selective disclosure. This is privacy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 6: Use Case 2 — Accredited Investor Verification
&lt;/h2&gt;

&lt;p&gt;Accredited investors must prove net worth &amp;gt; $1M without revealing exact net worth. Here's how:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: New Schema Registration&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accreditedSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;accredited-investor-v1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;netWorth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Uint&amp;lt;64&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;accreditationStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;String&amp;lt;32&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;verificationDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Uint&amp;lt;32&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Register on-chain&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;schemaRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accreditedSchema&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Issuer Issues Credential&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;claims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;netWorth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1500000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;accreditationStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;accredited&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;verificationDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20260401&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;credentialManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;issueCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;holderDIDHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;accreditedSchemaId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;claims&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, only the commitment goes on-chain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: New Circuit for Accredited Proof&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export circuit verifyAccredited(
  min_net_worth: Uint&amp;lt;64&amp;gt;,
  expected_commitment: Bytes&amp;lt;32&amp;gt;
): [] {
  const d_min = disclose(min_net_worth);
  const d_expected = disclose(expected_commitment);

  const net_worth = netWorth();
  const s = salt();

  const computed = persistentHash&amp;lt;Vector&amp;lt;2, Bytes&amp;lt;32&amp;gt;&amp;gt;&amp;gt;([
    (net_worth as Field) as Bytes&amp;lt;32&amp;gt;,
    s
  ]);
  assert(computed == d_expected, "Invalid credential");
  assert(net_worth &amp;gt;= d_min, "Not accredited");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4: Generate and Verify&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Generate proof&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;proof&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;proofGenerator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateAccreditedProof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// minimum net worth&lt;/span&gt;
  &lt;span class="nx"&gt;verifierAddress&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Verify&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;verifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verifyAccredited&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;proof&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Result: "User is accredited" (without revealing $1.5M)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same pattern. Different constraints. Total privacy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 7: Security Model &amp;amp; What We Protect
&lt;/h2&gt;

&lt;p&gt;Let me walk through exactly what's protected and what isn't.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Is Protected
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Raw PII&lt;/strong&gt; — Birthdate, net worth, SSN, medical records. Never touches the ledger. Only hashes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Controller Secret Keys&lt;/strong&gt; — Never sent to the contract. Only the derived public key, which cannot be reversed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Witness Data&lt;/strong&gt; — Supplied only at proof generation time. Not stored anywhere except in memory during proof.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Selective Disclosure&lt;/strong&gt; — We prove specific claims without proving everything. Age without revealing DOB. Accreditation without revealing wealth.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Isn't Protected
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Commitment&lt;/strong&gt; — It's public. Anyone can see you have an age credential.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timing Metadata&lt;/strong&gt; — When you registered, when you generated proofs. Blockchain is transparent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Holder Identity&lt;/strong&gt; — If someone knows your DID name (like "alice"), they can link credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Attack Vectors We Mitigated
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Replay Attacks&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
→ Each issuance includes a unique hash of (holder, schema, commitment, issuer). Can't reuse a credential.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unauthorized Updates&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
→ DID updates require the controller secret key. Only the real owner can update the document.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Commitment Collisions&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
→ Poseidon hash is cryptographically secure. Chance of collision is 2^-256.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;False Proofs&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
→ The zero knowledge circuit forces the math to check. Can't prove "over 18" if you're not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Salt Reuse&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
→ I recommend unique salts per credential. If you reuse salts, clever analysis might link credentials.&lt;/p&gt;


&lt;h2&gt;
  
  
  Part 8: Some likely errors you might hit
&lt;/h2&gt;

&lt;p&gt;I'm going to be brutally honest about what broke and what i learned.&lt;/p&gt;
&lt;h3&gt;
  
  
  1: PK Derivation Byte Mismatch
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
deriving the public key in TypeScript but it didn't match the Compact circuit. Authorization failed every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Cause:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The padding in the prefix was different. TypeScript was doing 32 bytes of padding, Compact was doing 12 bytes prefix + 20 zeros.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prefixStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;midnight:pk:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prefixStr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Rest is zeros automatically&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in Compact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pad(32, "midnight:pk:")  // 12 bytes + 20 zeros
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now they match. &lt;strong&gt;Always verify PK derivation matches exactly.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2: Witness Timing Race Conditions
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Witnesses were sometimes undefined when the proof circuit ran.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Cause:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
I was setting witnesses after the circuit started executing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Deferred provider pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getWitnesses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;WitnessData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;dateOfBirth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dataProvider&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BigInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dob&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dataProvider&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Witnesses are resolved right before the circuit needs them.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Complete Code
&lt;/h2&gt;

&lt;p&gt;Everything in this guide is in the repo. Clone it and run it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git https://github.com/Kanasjnr/Midnight-Privacy-Preserving-DID-System
npm run setup
npm run cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Next Steps — What You Should Build Today
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clone and run&lt;/strong&gt; → &lt;code&gt;npm run setup &amp;amp;&amp;amp; npm run cli&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Understand the contracts&lt;/strong&gt; → Open each &lt;code&gt;.compact&lt;/code&gt; file and trace through the logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extend the schema&lt;/strong&gt; → Add a new credential type (driver's license expiry, KYC status)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build the accredited investor circuit&lt;/strong&gt; → Implement &lt;code&gt;verifyAccredited()&lt;/code&gt; in Compact&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test with real data&lt;/strong&gt; → Issue credentials with real names and dates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Share your fork&lt;/strong&gt; → Post in the Midnight Dev Forum&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You now have a &lt;strong&gt;complete, production-ready, privacy-first identity stack&lt;/strong&gt;. You can issue credentials, generate proofs, and verify claims all without ever exposing raw PII to the ledger.&lt;/p&gt;

&lt;p&gt;We just proved that compliance and privacy are no longer mutually exclusive.&lt;/p&gt;

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

&lt;p&gt;You've just built a system that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Issues verifiable credentials without storing PII
&lt;/li&gt;
&lt;li&gt;Generates zero-knowledge proofs without ledger interaction
&lt;/li&gt;
&lt;li&gt;Enables selective disclosure (prove one thing without proving everything)
&lt;/li&gt;
&lt;li&gt;Never exposes raw data to the blockchain
&lt;/li&gt;
&lt;li&gt;Supports revocation and key rotation
&lt;/li&gt;
&lt;li&gt;Works on production Midnight today&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not theoretical. This is not a prototype. This is a complete, tested system.&lt;/p&gt;

&lt;p&gt;The private web is here. Midnight made it possible. You just built it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now go build something amazing.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Questions?&lt;/strong&gt; Drop them in the comments. I will read every single one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Found a bug?&lt;/strong&gt; Open an issue on GitHub.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want to extend it?&lt;/strong&gt; Fork the repo and ship it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/Kanasjnr/Midnight-Privacy-Preserving-DID-System" rel="noopener noreferrer"&gt;https://github.com/Kanasjnr/Midnight-Privacy-Preserving-DID-System&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Star it. Fork it. Ship real apps with it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Happy hacking!&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>opensource</category>
      <category>discuss</category>
      <category>blockchain</category>
    </item>
  </channel>
</rss>
