<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://zkbricks.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://zkbricks.com/" rel="alternate" type="text/html" /><updated>2026-10-05T23:14:41+00:00</updated><id>https://zkbricks.com/feed.xml</id><title type="html">zkBricks</title><subtitle>Translating cutting-edge cryptography into practical tools for privacy, safety, and trust in digital systems. Open-source public goods.</subtitle><author><name>zkBricks</name></author><entry><title type="html">Proving Personhood Without Handing Over the Keys</title><link href="https://zkbricks.com/blogposts/proofs-of-personhood.html" rel="alternate" type="text/html" title="Proving Personhood Without Handing Over the Keys" /><published>2026-02-22T00:00:00+00:00</published><updated>2026-02-22T00:00:00+00:00</updated><id>https://zkbricks.com/blogposts/proofs-of-personhood</id><content type="html" xml:base="https://zkbricks.com/blogposts/proofs-of-personhood.html"><![CDATA[<p><em>“On the Internet, nobody knows you’re a dog.”</em> The famous 1993 New Yorker cartoon (<a href="https://en.wikipedia.org/wiki/On_the_Internet,_nobody_knows_you%27re_a_dog">Wikipedia</a>) was a joke—but it pointed at a real problem. How do you know you’re interacting with a real human online? Or a human of the right age, with the right credentials? Today that question is sharper than ever.</p>

<figure class="post-figure">
<svg class="figure-svg" viewBox="0 0 570 330" role="img" aria-label="A website sees a verified human profile. Behind an isometric laptop, an abstract dog with a circular head and rounded snout thinks: I'm human." fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
  <!-- What the site sees (a verified profile) versus who is actually typing (a dog). -->
  <path class="diagram-body" d="M100 118L112 111L185 153V236L173 243L100 201Z" />
  <path class="diagram-top" d="M100 118L173 160V243L100 201Z" />
  <path d="M100 118L112 111L185 153L173 160M173 160V243" />
  <g transform="matrix(.866025 .5 0 1 100 118)">
    <circle cx="34" cy="22" r="10" fill="var(--paper)" />
    <path d="M18 50C18 38 25 32 34 32C43 32 50 38 50 50Z" fill="var(--paper)" />
    <path d="M18 64H62M18 73H48" stroke="var(--muted)" stroke-width="3" />
  </g>
  <g transform="translate(154 173)">
    <circle r="8" fill="var(--paper)" stroke="none" /><circle r="8" class="diagram-badge" />
    <path d="M-4 0L-1 3L4 -3" stroke="var(--accent)" />
  </g>
  <path class="diagram-guide" d="M206 178C246 166 292 166 330 178" stroke-dasharray="2 6" />
  <!-- Align the dog's leftmost body point with the keyboard edge from north (411,199) to east (476,237): midpoint (443.5,218). -->
  <g class="typing-dog" transform="translate(443.5 218) scale(1.2) translate(-360.76 -202.68)" stroke-width="1.25">
  <!-- The same circular head and hemispherical body as the isometric person. -->
  <ellipse cx="380" cy="141" rx="8" ry="19" transform="rotate(18 380 141)" class="diagram-body" />
  <g transform="translate(404 130) scale(.92)" stroke-width="1.36">
    <!-- Original landing-page person: circular head, hemispherical body, flat translucent fill.
     Opaque underlays keep guides and the body outline from showing through the head. -->
<g fill="var(--paper)">
  <path d="M-47 79C-47 50 -25 26 0 26C25 26 47 50 47 79A47 23.5 0 0 1 -47 79Z" fill="var(--paper)" stroke="none" />
  <path d="M-47 79C-47 50 -25 26 0 26C25 26 47 50 47 79A47 23.5 0 0 1 -47 79Z" />
  <circle cy="8" r="28" fill="var(--paper)" stroke="none" />
  <circle cy="8" r="28" />
</g>

  </g>
  <!-- The nearer ear overlaps the head; the far ear stays behind it. -->
  <ellipse cx="428" cy="141" rx="8" ry="19" transform="rotate(-18 428 141)" class="diagram-body" />
  <!-- A small hemisphere projects from the lower part of the head; no eyes. -->
  <g transform="translate(397 159)">
    <path d="M-15 0C-15 -9 -8 -16 0 -16C8 -16 15 -9 15 0A15 7.5 0 0 1 -15 0Z" class="diagram-body" />
    <circle cx="-5" cy="-3" r="4.5" fill="currentColor" stroke="none" />
  </g>
  </g>
  <!-- Raise and center the laptop in front of the torso, with its keyboard within reach. -->
  <g class="dog-laptop" transform="translate(24 -12)">
  <path d="M332 243L387 211L452 249V255L397 287L332 249Z" class="diagram-body" />
  <path d="M332 243L387 211L452 249L397 281Z" fill="var(--paper)" />
  <path d="M397 281V287" />
  <g transform="matrix(.866025 .5 -.866025 .5 387 218)" stroke="var(--rule-strong)">
    <path d="M8 6H65V38H8ZM8 16H65M8 27H65M19 6V38M31 6V38M43 6V38M55 6V38" />
  </g>
  <path d="M332 190L338 186L403 224V277L397 281L332 243Z" class="diagram-body" />
  <path d="M332 190L397 228V281L332 243Z" fill="var(--paper)" />
  <path d="M332 190L397 228V281L332 243Z" fill="var(--accent-surface)" />
  <path d="M332 190L397 228L403 224M397 228V281" />
  <g transform="matrix(.866025 .5 0 1 332 190)"><circle cx="37" cy="26" r="5" stroke="var(--muted)" /></g>
  </g>
  <!-- Thought bubble. -->
  <circle cx="486" cy="96" r="2.5" fill="var(--paper)" />
  <circle cx="500" cy="88" r="4" fill="var(--paper)" />
  <rect x="448" y="48" width="82" height="32" rx="16" fill="var(--paper)" />
  <text class="diagram-label diagram-small" x="489" y="68" text-anchor="middle">I'M HUMAN</text>
  <text class="diagram-label" x="142" y="315" text-anchor="middle">WHAT THE SITE SEES</text>
  <text class="diagram-label diagram-accent-text" x="450" y="315" text-anchor="middle">WHO IS TYPING</text>
</svg>

<figcaption>Who’s on the other side? Sometimes even “verified” isn’t.</figcaption>
</figure>

<div class="figure-pair">
<figure class="post-figure">
<svg class="figure-svg" viewBox="0 0 300 280" role="img" aria-label="A phone whose screen lists missed calls from Unknown, Spam risk, Unknown caller and Possible scam, with a 99+ badge." fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
  <!-- A phone full of missed calls from senders it cannot identify. -->
  <rect x="76" y="22" width="148" height="214" rx="16" class="diagram-body" />
  <rect x="84" y="44" width="132" height="170" rx="4" fill="var(--paper)" stroke="var(--rule-strong)" stroke-width="1" />
  <path d="M136 33H164" stroke="var(--muted)" />
  <path d="M138 225H162" stroke="var(--muted)" />
  <g stroke="var(--rule)" stroke-width="1"><path d="M90 86H210M90 128H210M90 170H210" /></g>
  <g stroke="var(--accent)">
    <path d="M103 54L95 62M95 57V62H100" />
    <path d="M103 96L95 104M95 99V104H100" />
    <path d="M103 138L95 146M95 141V146H100" />
    <path d="M103 180L95 188M95 183V188H100" />
  </g>
  <g class="diagram-text">
    <text x="110" y="62">Unknown</text>
    <text x="110" y="104">Spam risk</text>
    <text x="110" y="146">Unknown caller</text>
    <text x="110" y="188">Possible scam</text>
  </g>
  <g class="diagram-text chart-muted" style="font-size:10px">
    <text x="110" y="76">Missed call</text>
    <text x="110" y="118">Missed call</text>
    <text x="110" y="160">Missed call</text>
    <text x="110" y="202">Missed call</text>
  </g>
  <circle cx="222" cy="26" r="15" fill="var(--accent)" stroke="var(--paper)" stroke-width="2" />
  <text x="222" y="30" text-anchor="middle" fill="var(--on-accent)" stroke="none" style="font-family:var(--sans);font-size:10.5px;font-weight:700">99+</text>
  <g class="diagram-text chart-muted" style="font-size:32px"><text x="240" y="96">?</text><text x="256" y="128">?</text><text x="44" y="150">?</text></g>
  <text class="diagram-label" x="150" y="266" text-anchor="middle">WHO IS CALLING?</text>
</svg>

<figcaption>Who’s really on the other end? It’s getting harder to tell.</figcaption>
</figure>
<figure class="post-figure">
<svg class="figure-svg" viewBox="0 0 300 280" role="img" aria-label="A robot clicks the I'm not a robot checkbox of a CAPTCHA, and the check passes." fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
  <!-- A bot ticks the "I'm not a robot" box and passes. -->
  <path d="M100 40V30" />
  <circle cx="100" cy="26" r="4" fill="var(--accent)" stroke="var(--accent)" />
  <rect x="66" y="58" width="6" height="16" rx="2" class="diagram-body" />
  <rect x="128" y="58" width="6" height="16" rx="2" class="diagram-body" />
  <rect x="72" y="40" width="56" height="50" rx="9" fill="var(--paper)" />
  <circle cx="88" cy="60" r="6" class="diagram-badge" />
  <circle cx="112" cy="60" r="6" class="diagram-badge" />
  <rect x="86" y="74" width="28" height="8" rx="2" />
  <path d="M93 74V82M100 74V82M107 74V82" />
  <path class="diagram-guide" d="M100 98C100 112 94 124 86 134" stroke-dasharray="2 6" />
  <rect x="34" y="140" width="232" height="64" rx="4" fill="var(--paper)" />
  <rect x="52" y="157" width="30" height="30" rx="3" class="diagram-badge" />
  <path d="M58 172L64.5 178.5L76 164" class="diagram-check" />
  <text class="diagram-text" x="96" y="177" style="font-size:13.5px">I'm not a robot</text>
  <path d="M245 164A9 9 0 1 1 236 173" stroke="var(--muted)" />
  <path d="M245 158V164H239" stroke="var(--muted)" />
  <text class="diagram-label diagram-small chart-muted" x="258" y="196" text-anchor="end">CAPTCHA</text>
  <path d="M74 182L74 198L78.5 193.5L82 201L85 199.5L81.5 192.5L88 192.5Z" fill="var(--paper)" />
  <g transform="translate(222 86)">
    <circle r="17" fill="var(--paper)" stroke="none" />
    <circle r="17" class="diagram-badge" />
    <path d="M-7 0L-2 5L7 -5" class="diagram-check" />
  </g>
  <text class="diagram-label diagram-small diagram-accent-text" x="222" y="122" text-anchor="middle">PASSED</text>
  <text class="diagram-label" x="150" y="266" text-anchor="middle">PROVE YOU'RE HUMAN</text>
</svg>

<figcaption>Bots like Akirabot can pass the “prove you’re human” test. We need something stronger.</figcaption>
</figure>
</div>

<p>Have you noticed more calls and messages from unknown senders—someone “just trying to help”? Often it’s a scammer or an ad-bot that sounds just like a human. We’re at the beginning of a reality where what you see or hear online may not be real. Are we prepared?</p>

<p>Early systems like CAPTCHA<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> (Eurocrypt ’03) were built on a simple idea: give the user a task that’s easy for humans but hard for machines. For a while they worked. Today they’re essentially broken. LLMs and other ML tools can defeat CAPTCHAs at scale—for example, Akirabot<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">2</a></sup>, an OpenAI-based tool, was used to hit <strong>420,000 sites</strong> with spam by bypassing CAPTCHAs. We can no longer rely on humans being “smarter” than bots.</p>

<p>And personhood isn’t only “am I human?” Online we also need <strong>relationship-backed trust</strong>, <strong>reputation</strong>, <strong>membership in a community</strong>, or <strong>context-specific endorsements</strong>—age verification, attestations, “who vouches for you”—and all of that should preserve privacy. Yet many current approaches are ad-hoc and privacy-hostile. Mobile driver’s licenses (mDLs) can let the verifier query the DMV on every check, so the DMV can see and record every verification. Discord had <strong>over 70,000 government IDs</strong> stolen, then required government ID for “adult” content. Worldcoin’s global biometric system has been called a “privacy nightmare” and has faced shutdowns or strict limits in Spain, Bavaria, Hong Kong, and Kenya. So: <em>how do we prove personhood without handing over the keys?</em></p>

<div class="figure-pair">
<figure class="post-figure">
<svg class="figure-svg" viewBox="0 0 300 280" role="img" aria-label="A person surrounded by four badges: trust, reputation, membership and endorsement." fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
  <!-- Personhood is more than "human": trust, reputation, membership and endorsements surround the person. -->
  <path class="diagram-guide" d="M128 112L80 78M172 112L220 78M128 156L80 188M172 156L220 188" stroke-dasharray="2 6" />
  <g transform="translate(150 113) scale(.68)" stroke-width="2.2">
    <!-- Original landing-page person: circular head, hemispherical body, flat translucent fill.
     Opaque underlays keep guides and the body outline from showing through the head. -->
<g fill="var(--accent-surface)">
  <path d="M-47 79C-47 50 -25 26 0 26C25 26 47 50 47 79A47 23.5 0 0 1 -47 79Z" fill="var(--paper)" stroke="none" />
  <path d="M-47 79C-47 50 -25 26 0 26C25 26 47 50 47 79A47 23.5 0 0 1 -47 79Z" />
  <circle cy="8" r="28" fill="var(--paper)" stroke="none" />
  <circle cy="8" r="28" />
</g>

  </g>
  <g transform="translate(62 64)">
    <circle r="19" fill="var(--paper)" stroke="none" /><circle r="19" class="diagram-badge" />
    <circle cx="-4" r="6" stroke="var(--accent)" /><circle cx="4" r="6" stroke="var(--accent)" />
  </g>
  <g transform="translate(238 64)">
    <circle r="19" fill="var(--paper)" stroke="none" /><circle r="19" class="diagram-badge" />
    <path d="M0 -9L2.6 -3.4L8.6 -2.8L4.1 1.3L5.3 7.3L0 4.3L-5.3 7.3L-4.1 1.3L-8.6 -2.8L-2.6 -3.4Z" stroke="var(--accent)" />
  </g>
  <g transform="translate(62 204)">
    <circle r="19" fill="var(--paper)" stroke="none" /><circle r="19" class="diagram-badge" />
    <g stroke="var(--accent)"><circle cx="-6" cy="-3" r="2.6" /><circle cx="6" cy="-3" r="2.6" /><circle cx="0" cy="-5" r="3" /><path d="M-11 6C-11 2 -9 0.5 -6 0.5C-4.6 0.5 -3.5 0.8 -2.8 1.4M11 6C11 2 9 0.5 6 0.5C4.6 0.5 3.5 0.8 2.8 1.4M-5 8C-5 3.5 -3 1.5 0 1.5C3 1.5 5 3.5 5 8" /></g>
  </g>
  <g transform="translate(238 204)">
    <circle r="19" fill="var(--paper)" stroke="none" /><circle r="19" class="diagram-badge" />
    <path d="M-7 0L-2 5L7 -5" class="diagram-check" />
  </g>
  <g class="diagram-label diagram-small" text-anchor="middle">
    <text x="62" y="102">TRUST</text>
    <text x="238" y="102">REPUTATION</text>
    <text x="62" y="242">MEMBER</text>
    <text x="238" y="242">ENDORSED</text>
  </g>
  <text class="diagram-label diagram-accent-text" x="150" y="210" text-anchor="middle">YOU</text>
</svg>

<figcaption>It’s not just “am I human?”—it’s trust, reputation, and who stands behind you.</figcaption>
</figure>
<figure class="post-figure">
<svg class="figure-svg" viewBox="0 0 300 280" role="img" aria-label="A private key passes from a person toward three spherical eyes representing verifiers, all looking at the key." fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
  <!-- Handing over the keys: the person gives up their credential to watching verifiers. -->
  <g transform="translate(62 127) scale(.68)" stroke-width="2.2">
    <!-- Original landing-page person: circular head, hemispherical body, flat translucent fill.
     Opaque underlays keep guides and the body outline from showing through the head. -->
<g fill="var(--accent-surface)">
  <path d="M-47 79C-47 50 -25 26 0 26C25 26 47 50 47 79A47 23.5 0 0 1 -47 79Z" fill="var(--paper)" stroke="none" />
  <path d="M-47 79C-47 50 -25 26 0 26C25 26 47 50 47 79A47 23.5 0 0 1 -47 79Z" />
  <circle cy="8" r="28" fill="var(--paper)" stroke="none" />
  <circle cy="8" r="28" />
</g>

  </g>
  <!-- The key is a separate object; the dashed arrow denotes transfer, not an arm. -->
  <g transform="translate(118 142)"><g transform="matrix(.866025 -.5 .866025 .5 0 0)" stroke="var(--accent)">
    <!-- A depth vector of (-5, 5) projects to five pixels vertically below the top. -->
    <g fill="var(--paper-2)">
      <path transform="translate(-5 5)" d="M9 -4H42V3H36V8H30V3H24V8H18V3H9A10 10 0 1 1 9 -4Z" />
      <path d="M9 3A10 10 0 1 1 9 -4L4 1A10 10 0 1 0 4 8Z" />
      <path d="M42 3L36 3L31 8L37 8Z" />
      <path d="M36 8L30 8L25 13L31 13Z" />
      <path d="M30 8L30 3L25 8L25 13Z" />
      <path d="M30 3L24 3L19 8L25 8Z" />
      <path d="M24 8L18 8L13 13L19 13Z" />
      <path d="M18 8L18 3L13 8L13 13Z" />
      <path d="M18 3L9 3L4 8L13 8Z" />
    </g>
    <path d="M9 -4H42V3H36V8H30V3H24V8H18V3H9A10 10 0 1 1 9 -4Z" fill="var(--paper)" />
    <path d="M9 -4H42V3H36V8H30V3H24V8H18V3H9A10 10 0 1 1 9 -4Z" fill="var(--accent-surface)" />
    <circle r="4" fill="var(--paper)" />
  </g></g>
  <path class="diagram-guide" d="M112 180H174" stroke-dasharray="2 6" />
  <path d="M168 175L174 180L168 185" stroke="var(--muted)" />
  <text class="diagram-label diagram-small" x="142" y="208" text-anchor="middle">PRIVATE KEY</text>
  <!-- Horizontal eyes wrap around the key-facing rim; each sphere clips the far corner. -->
  <defs>
    <clipPath id="pp-key-eye-sphere-1" clipPathUnits="userSpaceOnUse"><circle cx="226" cy="103" r="24" /></clipPath>
    <clipPath id="pp-key-eye-sphere-2" clipPathUnits="userSpaceOnUse"><circle cx="253" cy="164" r="26" /></clipPath>
    <clipPath id="pp-key-eye-sphere-3" clipPathUnits="userSpaceOnUse"><circle cx="226" cy="225" r="23" /></clipPath>
  </defs>
  <g class="verifier-eye">
    <circle cx="226" cy="103" r="24" fill="var(--paper)" stroke="none" />
    <g clip-path="url(#pp-key-eye-sphere-1)">
      <g class="verifier-eye-face" transform="translate(203.90 107.38) scale(1.6)" stroke-width=".9375">
        <path d="M-11 0Q0 -14 11 0Q0 14 -11 0Z" fill="var(--paper)" />
        <g transform="rotate(157.65)">
          <ellipse rx="4.5" ry="6" fill="var(--accent-surface)" stroke="var(--accent)" />
          <circle cx="1.5" r="2.3" fill="currentColor" stroke="none" />
        </g>
      </g>
    </g>
    <circle cx="226" cy="103" r="24" fill="none" />
  </g>
  <g class="verifier-eye">
    <circle cx="253" cy="164" r="26" fill="var(--paper)" stroke="none" />
    <g clip-path="url(#pp-key-eye-sphere-2)">
      <g class="verifier-eye-face" transform="translate(228.62 161.49) scale(1.6)" stroke-width=".9375">
        <path d="M-11 0Q0 -14 11 0Q0 14 -11 0Z" fill="var(--paper)" />
        <g transform="rotate(-168.41)">
          <ellipse rx="4.5" ry="6" fill="var(--accent-surface)" stroke="var(--accent)" />
          <circle cx="1.5" r="2.3" fill="currentColor" stroke="none" />
        </g>
      </g>
    </g>
    <circle cx="253" cy="164" r="26" fill="none" />
  </g>
  <g class="verifier-eye">
    <circle cx="226" cy="225" r="23" fill="var(--paper)" stroke="none" />
    <g clip-path="url(#pp-key-eye-sphere-3)">
      <g class="verifier-eye-face" transform="translate(205.79 217.42) scale(1.6)" stroke-width=".9375">
        <path d="M-11 0Q0 -14 11 0Q0 14 -11 0Z" fill="var(--paper)" />
        <g transform="rotate(-136.64)">
          <ellipse rx="4.5" ry="6" fill="var(--accent-surface)" stroke="var(--accent)" />
          <circle cx="1.5" r="2.3" fill="currentColor" stroke="none" />
        </g>
      </g>
    </g>
    <circle cx="226" cy="225" r="23" fill="none" />
  </g>
  <text class="diagram-label" x="62" y="266" text-anchor="middle">YOU</text>
  <text class="diagram-label" x="232" y="266" text-anchor="middle">EVERY VERIFIER</text>
</svg>

<figcaption>Proving who you are shouldn’t mean handing over the keys.</figcaption>
</figure>
</div>

<p>In this blog we explain our recent work<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">3</a></sup>: we show how to construct proofs of personhood so that humans can prove their reputation and credentials online in a <strong>privacy-preserving</strong> way. The design is <strong>decentralized</strong>—no reliance on centralized parties for setup—and we use <strong>zero-knowledge (ZK) proofs</strong> so people can prove what they need without leaking the rest. This work contributes to the vision of the <a href="https://www.firstperson.network/">First Person Network</a><sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">4</a></sup>: a global infrastructure for real people and real trust, with no intermediaries. Below we walk through a typical protocol with a running example and highlight the key ideas and security requirements at each stage. No prior work considers the goal of constructing efficient zk proofs for this setting.</p>

<h2 id="stage-i-getting-a-personhood-credential">Stage I: Getting a Personhood Credential</h2>

<p>Someone has to vouch that you’re a real person before you can vouch for others. We call these vouchers <strong>issuers</strong>: organizations everyone can look up and trust. They might be your local DMV, a passport office, or even a loyalty program. In practice, many personhood systems use <strong>government-issued IDs</strong> (passports, mobile driver’s licenses) as the “one person, one credential” anchor—and our protocol supports that.</p>

<p><strong>Alex</strong> is a ride-share driver. Alex goes to a <strong>government authority</strong> (say, the DMV or a passport office) and gets a <strong>personhood credential</strong> (PHC) that attests to some facts about him—for example:</p>

<div class="jellyk-code-box">
<pre><code>att = {
  Name = Alex
  Occupation = Driver
  License type = Commercial
  Date of birth = 1990-05-15
}</code></pre>
</div>

<p>The authority has a public key that anyone can check. Alex provides his own public key and these attributes, proves he owns the provided key, and goes through the issuance flow. At the end, he holds a credential that he can verify himself using the authority’s public key. No one can fake that credential—that’s our first guarantee:</p>

<div class="jellyk-highlight-box">
    <p><strong>PHC Unforgeability.</strong> Credentials cannot be forged.</p>
</div>

<p><strong>But wait—privacy.</strong> If Alex used the <em>same</em> public key with every issuer, then different issuers could link his requests together and learn more about him than he intended. We don’t want that. So we require:</p>

<div class="jellyk-highlight-box">
    <p><strong>PHC Receiver Unlinkability.</strong> Different issuers cannot link credentials issued to the same person.</p>
</div>

<p>The fix: Alex <strong>derives a different-looking public key for each issuer</strong> from his secret key and that issuer’s public key—so each key looks unrelated to the others. To each issuer he only shows this derived key; they can’t tell it’s the same person going to another issuer. He proves in zero knowledge that the derived key was computed correctly, without revealing his master secret.</p>

<p><strong>A quick note on trust.</strong> How does the issuer know Alex’s details are real? In the real world, that’s the issuer’s job (e.g. the DMV checks your documents). For added robustness, we allow for the fact that issuers can sometimes be fooled: we attach a <strong>confidence parameter</strong> to each issuer. When someone later checks a credential, they can weigh <em>who</em> issued it. The protocol still keeps privacy and security even when we don’t assume every issuer is perfect.</p>

<hr />

<h2 id="stage-ii-vouching-for-others-verifiable-relationship-credentials">Stage II: Vouching for Others (Verifiable Relationship Credentials)</h2>

<p>Once you have a personhood credential, you can <strong>vouch for someone else</strong> in a way that others can check. We call these <strong>verifiable relationship credentials (VRCs)</strong>. Think of them as signed, checkable endorsements. We refer to these VRC issuers as <em>vouchers</em>, to distinguish them from <em>issuers</em>, who only issue PHCs.</p>

<p><strong>Back to our example.</strong> <strong>Carol</strong> is a passenger who just had a great ride with Alex. She has her own personhood credential (from the government or another issuer). She wants to endorse Alex by saying:</p>

<div class="jellyk-code-box">
<pre><code>st = "Is a good driver."</code></pre>
</div>

<p>She doesn’t want every endorsement to be linkable to the same “Carol.” So she uses a <strong>context-specific key</strong> for the ride-share setting. In fact, even issuers cannot link a PHC issuance with an endorsement. Different contexts (ride-share, work, social) get different keys, so no one can tie all her vouchers together.</p>

<div class="jellyk-highlight-box">
    <p><strong>Voucher Cross-Context Unlinkability.</strong> Your endorsements in one context (e.g. ride-share) cannot be linked to you in another context.</p>
</div>

<p>Alex has the same kind of privacy need: he doesn’t want every passenger who vouches for him to see that he’s the same driver across all of them. So he uses a <strong>fresh receiver key</strong> for this interaction, derived from Carol’s context key. Different passengers see different keys; they can’t link Alex across his VRCs.</p>

<div class="jellyk-highlight-box">
    <p><strong>VRC Receiver Unlinkability.</strong> Vouchers cannot link those VRCs to the same person (you) across different vouchers.</p>
</div>

<p>Carol only reveals what she chooses—for example, just her name—and the rest of her attributes stay hidden. She uses her credential to create a VRC bound to Alex’s receiver key and the statement “Is a good driver.” Anyone can check that the VRC is valid and that the keys were derived correctly (via zero-knowledge proofs), without learning Carol’s or Alex’s secrets. One more guarantee:</p>

<div class="jellyk-highlight-box">
  <p><strong>VRC Unforgeability.</strong> No one can create a valid endorsement without holding a real credential and going through the proper issuance flow.</p>
</div>

<hr />

<h2 id="stage-iii-proving-your-reputation-without-revealing-everything">Stage III: Proving Your Reputation (Without Revealing Everything)</h2>

<p>Alex has collected several “good driver” endorsements from passengers. Now he wants to <strong>prove</strong> to someone—the ride-share platform, or a new passenger—that he has that reputation, <em>without</em> handing over every VRC he’s ever received. That would be slow and would leak more than needed. Instead, he produces a <strong>zero-knowledge proof</strong>: one short proof that backs up his claim.</p>

<div class="jellyk-claim-box">
  <p><strong>Claim.</strong> Three different passengers vouched for me as a good driver.</p>
</div>

<p>The proof shows, in zero knowledge, that: (1) he holds three valid VRCs, (2) each says “Is a good driver,” (3) they come from three <em>different</em> vouchers (so not one person vouching three times), (4) each VRC was issued by a voucher with a valid PHC from a trusted issuer (or approved issuer set), and (5) all of the VRCs were issued to the <em>same</em> receiver, namely Alex, without revealing his secret key. He sends this proof plus the identities of the issuers (so the verifier can decide how much to trust them). The verifier checks the proof and can accept or reject the claim.</p>

<p><strong>Privacy in this step:</strong> The verifier only learns the <em>claim</em> (“three passengers said I’m a good driver”) and whatever issuer info is needed to calibrate trust. They do <em>not</em> learn Alex’s full set of VRCs, his keys, or any attributes he didn’t choose to reveal.</p>

<div class="jellyk-highlight-box">
  <p><strong>Showing Privacy.</strong> The verifier learns only the claimed statement and the issuer information needed for trust, not the full set of VRCs or hidden attributes.</p>
</div>

<h2 id="wrapping-up">Wrapping Up</h2>

<p>This post conveys the flow and the guarantees of our system without the full formal machinery. We refer the reader to our paper<sup id="fnref:2:1" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">3</a></sup> for the full definitions, constructions, and experiments.</p>

<p>In our paper we present an early prototype showing that zero-knowledge proofs for this setting can be practical. We leave further efficiency improvements for future work. Additionally, going forward, we aim to add support for legacy credentials in our system.
We also want to support proving membership in a community where trust is defined by being directly connected to a trust anchor or to a sufficient number of other members in the community.</p>

<div class="jellyk-figure" style="padding: 0.5rem;">
  <iframe src="/assets/papers/A%20Cryptographic%20Framework%20for%20Proofs%20of%20Personhood.pdf" title="A Cryptographic Framework for Proofs of Personhood (PDF)" style="width: 100%; height: 70vh; border: 0;"></iframe>
</div>

<h2 id="references">References</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Luis von Ahn, Manuel Blum, Nicholas J. Hopper, and John Langford. <strong>CAPTCHA: Using Hard AI Problems for Security.</strong> In <em>Advances in Cryptology — EUROCRYPT 2003</em>, pp. 294–311. Springer, 2003. <a href="https://link.springer.com/chapter/10.1007/3-540-39200-9_18">https://link.springer.com/chapter/10.1007/3-540-39200-9_18</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>Akirabot — an OpenAI-based tool used to target 420,000 sites with spam by bypassing CAPTCHAs; see e.g. reporting on CAPTCHA-breaking attacks. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>Arka Rai Choudhuri, Sanjam Garg, Keewoo Lee, Hart Montgomery, Guru Vamsi Policharla, and Rohit Sinha. <strong>A Cryptographic Framework for Proofs of Personhood.</strong> <a href="https://eprint.iacr.org/2026/333.pdf">PDF</a> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:2:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:4" role="doc-endnote">
      <p><strong>The First Person Network.</strong> A global digital utility for trusted connections between individuals, communities, and organizations—no intermediaries, no platform, no surveillance. <a href="https://www.firstperson.network/">firstperson.network</a> <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Arka Rai Choudhuri</name></author><summary type="html"><![CDATA[A new cryptographic framework for proofs of personhood.]]></summary></entry><entry><title type="html">A New Vision for Threshold Encryption</title><link href="https://zkbricks.com/blogposts/threshold-encryption.html" rel="alternate" type="text/html" title="A New Vision for Threshold Encryption" /><published>2026-01-28T00:00:00+00:00</published><updated>2026-01-28T00:00:00+00:00</updated><id>https://zkbricks.com/blogposts/threshold-cryptography</id><content type="html" xml:base="https://zkbricks.com/blogposts/threshold-encryption.html"><![CDATA[<p>Threshold encryption is a foundational primitive for building distributed systems that need confidentiality without relying on any single party. A sender encrypts a message to a <em>quorum</em> of $n$ users such that <strong>any</strong> set of $t$ users can decrypt the ciphertext, while <strong>no</strong> set of $t-1$ users can. Importantly, the ciphertext can be <em>succinct</em>: it does not grow with $n$ (or $t$).</p>

<figure class="post-figure">
<svg class="figure-svg" viewBox="0 0 540 340" role="img" aria-label="A quorum of nine members holds shares of one key. Three of them each send a partial decryption, and together the three open the ciphertext; the other six take no part." fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
  <!-- Any three of nine quorum members send partial decryptions; together they open the ciphertext. -->
  <path class="diagram-body" d="M100 119.5L114.72 128V145L100 153.5L85.28 145V128Z" />
  <path class="diagram-top" d="M100 119.5L114.72 128L100 136.5L85.28 128Z" />
  <path d="M85.28 128L100 136.5L114.72 128M100 136.5V153.5" />
  <path d="M100 119.5L114.72 128L100 136.5L85.28 128Z" fill="var(--accent)" stroke="var(--brick-outline)" />
  <path class="diagram-body" d="M66 139.13L80.72 147.63V164.63L66 173.13L51.28 164.63V147.63Z" />
  <path class="diagram-top" d="M66 139.13L80.72 147.63L66 156.13L51.28 147.63Z" />
  <path d="M51.28 147.63L66 156.13L80.72 147.63M66 156.13V173.13" />
  <path class="diagram-body" d="M134 139.13L148.72 147.63V164.63L134 173.13L119.28 164.63V147.63Z" />
  <path class="diagram-top" d="M134 139.13L148.72 147.63L134 156.13L119.28 147.63Z" />
  <path d="M119.28 147.63L134 156.13L148.72 147.63M134 156.13V173.13" />
  <path class="diagram-body" d="M32 158.76L46.72 167.26V184.26L32 192.76L17.28 184.26V167.26Z" />
  <path class="diagram-top" d="M32 158.76L46.72 167.26L32 175.76L17.28 167.26Z" />
  <path d="M17.28 167.26L32 175.76L46.72 167.26M32 175.76V192.76" />
  <path d="M32 158.76L46.72 167.26L32 175.76L17.28 167.26Z" fill="var(--accent)" stroke="var(--brick-outline)" />
  <path class="diagram-body" d="M100 158.76L114.72 167.26V184.26L100 192.76L85.28 184.26V167.26Z" />
  <path class="diagram-top" d="M100 158.76L114.72 167.26L100 175.76L85.28 167.26Z" />
  <path d="M85.28 167.26L100 175.76L114.72 167.26M100 175.76V192.76" />
  <path class="diagram-body" d="M168 158.76L182.72 167.26V184.26L168 192.76L153.28 184.26V167.26Z" />
  <path class="diagram-top" d="M168 158.76L182.72 167.26L168 175.76L153.28 167.26Z" />
  <path d="M153.28 167.26L168 175.76L182.72 167.26M168 175.76V192.76" />
  <path d="M168 158.76L182.72 167.26L168 175.76L153.28 167.26Z" fill="var(--accent)" stroke="var(--brick-outline)" />
  <path class="diagram-body" d="M66 178.39L80.72 186.89V203.89L66 212.39L51.28 203.89V186.89Z" />
  <path class="diagram-top" d="M66 178.39L80.72 186.89L66 195.39L51.28 186.89Z" />
  <path d="M51.28 186.89L66 195.39L80.72 186.89M66 195.39V212.39" />
  <path class="diagram-body" d="M134 178.39L148.72 186.89V203.89L134 212.39L119.28 203.89V186.89Z" />
  <path class="diagram-top" d="M134 178.39L148.72 186.89L134 195.39L119.28 186.89Z" />
  <path d="M119.28 186.89L134 195.39L148.72 186.89M134 195.39V212.39" />
  <path class="diagram-body" d="M100 198.02L114.72 206.52V223.52L100 232.02L85.28 223.52V206.52Z" />
  <path class="diagram-top" d="M100 198.02L114.72 206.52L100 215.02L85.28 206.52Z" />
  <path d="M85.28 206.52L100 215.02L114.72 206.52M100 215.02V232.02" />
  <!-- Match hero.js: quadratic arcs with midpoint control x and control y above both endpoints.
       Vertically spaced, left-aligned destinations keep the arcs nested and separate; each starts at a highlighted top-face center. -->
  <g class="threshold-share-arcs" stroke="var(--accent)">
    <path d="M32 167.26Q150 20 268 90" />
    <path d="M100 128Q184 60 268 135" />
    <path d="M168 167.26Q218 105 268 180" />
    <g fill="var(--accent)" stroke="none"><circle cx="32" cy="167.26" r="2.5" /><circle cx="100" cy="128" r="2.5" /><circle cx="168" cy="167.26" r="2.5" /></g>
  </g>
  <g class="partial-decryption" transform="translate(268 90)">
    <rect y="-9.5" width="30" height="19" rx="2" fill="var(--paper)" />
    <path d="M5 -3.5H17M5 2.5H13" stroke="var(--muted)" />
    <circle cx="24" cy="2.5" r="2" fill="var(--accent)" stroke="none" />
  </g>
  <g class="partial-decryption" transform="translate(268 135)">
    <rect y="-9.5" width="30" height="19" rx="2" fill="var(--paper)" />
    <path d="M5 -3.5H17M5 2.5H13" stroke="var(--muted)" />
    <circle cx="24" cy="2.5" r="2" fill="var(--accent)" stroke="none" />
  </g>
  <g class="partial-decryption" transform="translate(268 180)">
    <rect y="-9.5" width="30" height="19" rx="2" fill="var(--paper)" />
    <path d="M5 -3.5H17M5 2.5H13" stroke="var(--muted)" />
    <circle cx="24" cy="2.5" r="2" fill="var(--accent)" stroke="none" />
  </g>
  <path class="diagram-guide" d="M308 180C334 170 362 170 388 180" stroke-dasharray="2 6" />
  <path class="diagram-body" d="M450 131L500.23 160V210L450 239L399.77 210V160Z" />
  <path class="diagram-blue" d="M450 131L500.23 160L450 189L399.77 160Z" />
  <path d="M399.77 160L450 189L500.23 160M450 189V239" />
  <g transform="matrix(.866025 .5 0 1 399.77 160)"><path d="M10 16H40M10 27H32M10 38H44" stroke="var(--muted)" stroke-width="2.5" stroke-dasharray="3 4" /></g>
  <g transform="matrix(.866025 .5 -.866025 .5 450 131)"><path d="M14 14H44M14 24H36" stroke="var(--accent)" stroke-width="2.5" /></g>
  <g transform="translate(450 92)"><circle r="17" fill="var(--paper)" class="diagram-badge" /><rect x="-7" y="-2" width="14" height="11" rx="2" stroke="var(--accent)" fill="var(--paper)" /><path d="M-4.5 -2V-6A4.5 4.5 0 0 1 4.5 -6V-8" stroke="var(--accent)" /></g>
  <text class="diagram-label" x="100" y="307" text-anchor="middle">QUORUM · 3 OF 9</text>
  <text class="diagram-label diagram-accent-text" x="283" y="307" text-anchor="middle">PARTIAL DECRYPTIONS</text>
  <text class="diagram-label" x="450" y="307" text-anchor="middle">PLAINTEXT</text>
</svg>

<figcaption>Threshold decryption in action. The bricks are quorum members, each holding a share. The three highlighted members are the \(t\) participants: each sends a partial decryption, and together the partials open the ciphertext. The others hold shares but take no part in this decryption.</figcaption>
</figure>

<p>As threshold encryption moves from theory into production—especially in systems like blockchains—two practical bottlenecks quickly become dominant:</p>

<ol>
  <li>
    <p><strong>Setup friction (DKG “setup tax”).</strong><br />
Traditionally, without assuming a trusted dealer, threshold encryption relies on a <strong>distributed key generation (DKG)</strong> protocol. A global secret key is generated jointly by the quorum, and each user learns a share. DKG works, but it comes with significant engineering and security headaches: multiple interactive rounds, complex failure modes, attribution of misbehavior, and challenges with churn and asynchrony. Just as importantly, DKG tends to <em>lock in</em> the quorum and threshold, making membership effectively static. In settings where the quorum should evolve dynamically, re-running DKG on every membership change becomes a recurring operational cost—the “setup tax” that makes threshold encryption hard to deploy at scale.</p>
  </li>
  <li>
    <p><strong>Decryption scalability.</strong><br />
Standard threshold decryption requires <em>per-ciphertext</em> collaboration: each committee member locally computes a <strong>partial decryption</strong>, and any $t$ partial decryptions can be combined to recover the plaintext. This is perfectly fine for a small number of ciphertexts, but it becomes a scaling bottleneck when the system needs to decrypt <em>many</em> ciphertexts, such as in blockchain <strong>mempool privacy</strong> where large batches of encrypted transactions may need to be opened together.</p>
  </li>
</ol>

<p>Based on a novel approach using witness encryption<sup id="fnref:5" role="doc-noteref"><a href="#fn:5" class="footnote" rel="footnote">1</a></sup>, <sup id="fnref:6" role="doc-noteref"><a href="#fn:6" class="footnote" rel="footnote">2</a></sup>, our team has been working on a new vision for threshold encryption that removes the setup tax <em>and</em> makes decryption scalable when many ciphertexts are involved. Two lines of work capture this direction: <strong>silent setup</strong> and <strong>batched threshold encryption</strong>.</p>

<h2 id="silent-setup">Silent Setup</h2>

<p><em>Silent setup</em>, introduced in our Crypto 2024 work<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">3</a></sup>, replaces interactive setup with a <em>non-interactive</em> procedure.</p>

<p>Instead of running a DKG, each user locally generates a public/secret key pair $(\mathsf{pk},\mathsf{sk})$ and publishes $\mathsf{pk}$. That is the entire setup. While each user’s published public-key material grows with the maximum number of users supported, this cost is incurred only once.</p>

<p>This unlocks a much more flexible deployment model: a sender can choose the quorum and threshold <strong>on the fly</strong>, and derive an encryption key for that chosen quorum using only the published public keys of its members. Any $t$ members of the selected quorum can then collaborate to decrypt—preserving the threshold guarantee without the operational burden of DKG.</p>

<h2 id="batched-threshold-encryption">Batched Threshold Encryption</h2>

<p>In a second line of work<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">4</a></sup>, we introduce <strong>batched threshold encryption (BTE)</strong>, designed to make threshold decryption scalable for large batches.</p>

<p>In the common case where a batch of $B$ ciphertexts is encrypted to the same quorum, BTE ensures that the size of each user’s partial decryption <strong>does not depend on $B$</strong>. This enables applications like an <em>encrypted mempool</em> in blockchains: users encrypt transactions under a quorum, and once a set of transactions is selected for inclusion in a block, a threshold of miners can collaboratively decrypt them by broadcasting partial decryptions. Crucially, those partial decryptions remain small—even when decrypting a large batch of transactions.</p>

<p>BTE has since seen improvements<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">5</a></sup>, and a recent work<sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">6</a></sup> obtains a BTE scheme that also supports silent setup—bringing these two threads of the new vision together.</p>

<h2 id="working-with-us">Working with us</h2>

<p>We’re excited about what silent setup and batched threshold encryption make possible: threshold encryption that is substantially easier to deploy and continues to perform at the scales demanded by modern distributed systems.</p>

<p>If you’re interested in deploying silent setup, batched threshold encryption, or related threshold-encryption infrastructure in your application, we’d love to talk—whether you’re exploring early prototypes or preparing for production deployment.</p>

<p><em>For more information <a href="mailto:arka@zkbricks.com">contact us</a>.</em></p>

<h3 id="references">References</h3>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:5" role="doc-endnote">
      <p>Sanjam Garg, Craig Gentry, Amit Sahai, Brent Waters. <em>Witness encryption and its applications.</em> STOC 2013. <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6" role="doc-endnote">
      <p>Sanjam Garg, Mohammad Hajiabadi, Dimitris Kolonelos, Abhiram Kothapalli, Guru-Vamsi Policharla. <em>A Framework for Witness Encryption from Linearly Verifiable SNARKs and Applications.</em> CRYPTO 2025: 504-539. <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:1" role="doc-endnote">
      <p>Sanjam Garg, Dimitris Kolonelos, Guru-Vamsi Policharla, and Mingyuan Wang. <em>Threshold Encryption with Silent Setup.</em> Crypto 2024. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>Arka Rai Choudhuri, Sanjam Garg, Julien Piet, and Guru-Vamsi Policharla. <em>Mempool Transaction Privacy via Batched Threshold Encryption: Attacks and Defenses.</em> USENIX Security 2024. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>Arka Rai Choudhuri, Sanjam Garg, Guru-Vamsi Policharla, and Mingyuan Wang. <em>Practical Mempool Privacy via One-time Setup Batched Threshold Encryption.</em> USENIX Security 2025. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4" role="doc-endnote">
      <p>Jan Bormet, Arka Rai Choudhuri, Sebastian Faust, Sanjam Garg, Hussien Othman, Guru-Vamsi Policharla, Ziyan Qu, and Mingyuan Wang. <em>BEAST-MEV: Batched Threshold Encryption with Silent Setup for MEV prevention.</em> Preprint. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Arka Rai Choudhuri</name></author><summary type="html"><![CDATA[A new path to deployable threshold encryption: silent setup to eliminate DKG friction, and batched threshold encryption to keep decryption scalable.]]></summary></entry></feed>