mirror of
https://github.com/Rate-Limiting-Nullifier/rln-docs.git
synced 2026-01-09 15:28:03 -05:00
504 lines
36 KiB
HTML
504 lines
36 KiB
HTML
<!DOCTYPE HTML>
|
||
<html lang="en" class="sidebar-visible no-js light">
|
||
<head>
|
||
<!-- Book generated using mdBook -->
|
||
<meta charset="UTF-8">
|
||
<title>Rate-Limiting Nullifier</title>
|
||
<meta name="robots" content="noindex" />
|
||
|
||
|
||
<!-- Custom HTML head -->
|
||
|
||
<meta name="description" content="">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<meta name="theme-color" content="#ffffff" />
|
||
|
||
<link rel="icon" href="favicon.svg">
|
||
<link rel="shortcut icon" href="favicon.png">
|
||
<link rel="stylesheet" href="css/variables.css">
|
||
<link rel="stylesheet" href="css/general.css">
|
||
<link rel="stylesheet" href="css/chrome.css">
|
||
<link rel="stylesheet" href="css/print.css" media="print">
|
||
|
||
<!-- Fonts -->
|
||
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
|
||
<link rel="stylesheet" href="fonts/fonts.css">
|
||
|
||
<!-- Highlight.js Stylesheets -->
|
||
<link rel="stylesheet" href="highlight.css">
|
||
<link rel="stylesheet" href="tomorrow-night.css">
|
||
<link rel="stylesheet" href="ayu-highlight.css">
|
||
|
||
<!-- Custom theme stylesheets -->
|
||
|
||
<!-- MathJax -->
|
||
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
||
</head>
|
||
<body>
|
||
<div id="body-container">
|
||
<!-- Provide site root to javascript -->
|
||
<script>
|
||
var path_to_root = "";
|
||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
|
||
</script>
|
||
|
||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||
<script>
|
||
try {
|
||
var theme = localStorage.getItem('mdbook-theme');
|
||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||
|
||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||
}
|
||
|
||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||
}
|
||
} catch (e) { }
|
||
</script>
|
||
|
||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||
<script>
|
||
var theme;
|
||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||
var html = document.querySelector('html');
|
||
html.classList.remove('no-js')
|
||
html.classList.remove('light')
|
||
html.classList.add(theme);
|
||
html.classList.add('js');
|
||
</script>
|
||
|
||
<!-- Hide / unhide sidebar before it is displayed -->
|
||
<script>
|
||
var html = document.querySelector('html');
|
||
var sidebar = null;
|
||
if (document.body.clientWidth >= 1080) {
|
||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||
sidebar = sidebar || 'visible';
|
||
} else {
|
||
sidebar = 'hidden';
|
||
}
|
||
html.classList.remove('sidebar-visible');
|
||
html.classList.add("sidebar-" + sidebar);
|
||
</script>
|
||
|
||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||
<div class="sidebar-scrollbox">
|
||
<ol class="chapter"><li class="chapter-item expanded "><a href="rln.html"><strong aria-hidden="true">1.</strong> RLN</a></li><li class="chapter-item expanded "><a href="overview.html"><strong aria-hidden="true">2.</strong> Overview</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="what_is_rln.html"><strong aria-hidden="true">2.1.</strong> What is RLN</a></li><li class="chapter-item expanded "><a href="rln_in_details.html"><strong aria-hidden="true">2.2.</strong> RLN in details</a></li><li class="chapter-item expanded "><a href="formal_spec.html"><strong aria-hidden="true">2.3.</strong> Formal spec</a></li><li class="chapter-item expanded "><a href="smart_contract.html"><strong aria-hidden="true">2.4.</strong> Smart-contract</a></li><li class="chapter-item expanded "><a href="rln_uses.html"><strong aria-hidden="true">2.5.</strong> RLN Uses</a></li></ol></li><li class="chapter-item expanded "><a href="sss.html"><strong aria-hidden="true">3.</strong> Shamir's Secret Sharing</a></li><li class="chapter-item expanded "><a href="research.html"><strong aria-hidden="true">4.</strong> Research</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="kzg_rln.html"><strong aria-hidden="true">4.1.</strong> KZG-RLN</a></li><li class="chapter-item expanded "><a href="noir_rln.html"><strong aria-hidden="true">4.2.</strong> Noir-RLN</a></li></ol></li></ol>
|
||
</div>
|
||
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
||
</nav>
|
||
|
||
<!-- Track and set sidebar scroll position -->
|
||
<script>
|
||
var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
|
||
sidebarScrollbox.addEventListener('click', function(e) {
|
||
if (e.target.tagName === 'A') {
|
||
sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
|
||
}
|
||
}, { passive: true });
|
||
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
|
||
sessionStorage.removeItem('sidebar-scroll');
|
||
if (sidebarScrollTop) {
|
||
// preserve sidebar scroll position when navigating via links within sidebar
|
||
sidebarScrollbox.scrollTop = sidebarScrollTop;
|
||
} else {
|
||
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
|
||
var activeSection = document.querySelector('#sidebar .active');
|
||
if (activeSection) {
|
||
activeSection.scrollIntoView({ block: 'center' });
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<div id="page-wrapper" class="page-wrapper">
|
||
|
||
<div class="page">
|
||
<div id="menu-bar-hover-placeholder"></div>
|
||
<div id="menu-bar" class="menu-bar sticky">
|
||
<div class="left-buttons">
|
||
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||
<i class="fa fa-bars"></i>
|
||
</button>
|
||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||
<i class="fa fa-paint-brush"></i>
|
||
</button>
|
||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||
</ul>
|
||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||
<i class="fa fa-search"></i>
|
||
</button>
|
||
</div>
|
||
|
||
<h1 class="menu-title">Rate-Limiting Nullifier</h1>
|
||
|
||
<div class="right-buttons">
|
||
<a href="print.html" title="Print this book" aria-label="Print this book">
|
||
<i id="print-button" class="fa fa-print"></i>
|
||
</a>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div id="search-wrapper" class="hidden">
|
||
<form id="searchbar-outer" class="searchbar-outer">
|
||
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||
</form>
|
||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||
<div id="searchresults-header" class="searchresults-header"></div>
|
||
<ul id="searchresults">
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||
<script>
|
||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||
});
|
||
</script>
|
||
|
||
<div id="content" class="content">
|
||
<main>
|
||
<div align="center">
|
||
<p><img src="./images/logo.svg" alt="RLN Logo" /></p>
|
||
</div>
|
||
<p><strong>RLN</strong> (Rate-Limiting Nullifier) is a zk-gadget/protocol that enables spam prevention mechanism for anonymous environments.</p>
|
||
<!-- ## RLN Components, Tools, and Libraries -->
|
||
<!-- | | Version | Stable | In Development | URL |
|
||
|--------------|-----:|:-:|:--:|-|-|
|
||
| circom-rln | 1.0 | ✅ | ✅ | [main branch](https://github.com/Rate-Limiting-Nullifier/rln-circuits/)|
|
||
| | 2.0 | ❌ | ✅ | [v2 issue](https://github.com/Rate-Limiting-Nullifier/rln-circuits/issues/3)|
|
||
| RLNjs | 1.0 | ✅ | ❌ | [v1 commit](https://github.com/Rate-Limiting-Nullifier/rlnjs/tree/35b9d21c7d97289ef10c018c7e214d00fa779976)|
|
||
| | 2.0 | ❌ | ✅ | [main branch](https://github.com/Rate-Limiting-Nullifier/rlnjs/tree/main)|
|
||
| | 2.1 | ❌ | ✅ | [v2.1 Issue](https://github.com/Rate-Limiting-Nullifier/rlnjs/issues/17)| -->
|
||
<blockquote>
|
||
<p><strong>RLN</strong> is part of (<strong>PSE</strong>) <a href="https://appliedzkp.org">Privacy & Scaling Explorations</a>, a multidisciplinary team supported by the Ethereum Foundation. PSE explores new use cases for zero-knowledge proofs and other cryptographic primitives.</p>
|
||
</blockquote>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="overview"><a class="header" href="#overview">Overview</a></h1>
|
||
<p>This section is a starting point for understanding the concepts of <strong>RLN</strong>.</p>
|
||
<p>Here we'll discuss:</p>
|
||
<ul>
|
||
<li>Basic explanation of the <strong>RLN</strong> protocol;</li>
|
||
<li><strong>RLN</strong> protocol under the hood;</li>
|
||
<li><strong>RLN</strong> uses.</li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="what-is-rate-limiting-nullifier"><a class="header" href="#what-is-rate-limiting-nullifier">What is Rate-Limiting Nullifier?</a></h1>
|
||
<p><strong>RLN</strong> is a zero-knowledge gadget that enables spam prevention in anonymous environments.</p>
|
||
<p>The anonymity property opens up the possibility for spam, which could seriously degrade the user experience and the overall functioning of the application. For example, imagine a chat application where users are anonymous. Now, everyone can write an unlimited number of spam messages, but we don't have the ability to kick this member because the spammer is anonymous. </p>
|
||
<p><strong>RLN</strong> helps us identify and "kick" the spammer.</p>
|
||
<p>Moreover, <strong>RLN</strong> can be useful not only to prevent spam attacks but, in general, to limit users (in anonymous environments) in the number of actions (f.e. to vote or to make a bid).</p>
|
||
<h2 id="how-it-works"><a class="header" href="#how-it-works">How it works</a></h2>
|
||
<p>The <strong>RLN</strong> construct's functionality consists of three parts These parts should be integrated by the upstream applications, that require anonymity and spam protection. The applications can be centralized or decentralized. For decentralized applications, each user maintains separate storage and compute resources for the application. </p>
|
||
<p>The three parts are:</p>
|
||
<ul>
|
||
<li>registration;</li>
|
||
<li>interaction;</li>
|
||
<li>withdrawal (or slashing);</li>
|
||
</ul>
|
||
<h3 id="registration"><a class="header" href="#registration">Registration</a></h3>
|
||
<p>Before registering to the application, the user needs to generate a secret key and derive an identity commitment from the secret key using the Poseidon hash function: </p>
|
||
<p>\[identityCommitment = Poseidon(secretKey)\]</p>
|
||
<p>The user registers to the application by providing a form of stake and their identity commitment, which is derived from the secret key. The application maintains a Merkle tree data structure (in the latest iteration of <strong>RLN</strong>, we use an Incremental Merkle Tree algorithm for gas efficiency, but the Merkle tree does not have to be on-chain), which stores the identity commitments of the registered users. Based on the stake amount apps can derive what's the messageLimit (\(userMessageLimit\)) for a user. Then the rateCommitment:</p>
|
||
<p>\[rateCommitment = Poseidon(identitytCommitment, userMessageLimit)\]</p>
|
||
<p>will be stored in the membership Merkle tree.</p>
|
||
<h3 id="interaction"><a class="header" href="#interaction">Interaction</a></h3>
|
||
<p>For each interaction that the user wants to make with the application, the user must generate a zero-knowledge proof ensuring that their identity commitment (or specifically rate commitment) is the part of the membership Merkle tree.</p>
|
||
<p>There are a number of use-cases for <strong>RLN</strong>, such as voting applications (1 vote per election), chat (one message per second), and rate-limiting cache access (CDN denial of service protection). The verifier can be a server for centralized applications or the other users for decentralized applications.</p>
|
||
<p>The general anti-spam rule is usually in the form of: <em>users must not make more than X interactions per epoch</em>.</p>
|
||
<p>The epoch can be translated as a time interval of \(Y\) units of time unit \(Z\). For simplicity's sake, let's transform the rule into: <em>users must not send more than one message per second</em>.</p>
|
||
<p>We can implement this using <a href="./sss.html"><em>Shamir's Secret Sharing (SSS)</em> scheme</a>, which allows you to split a secret to \(n\) parts and recover it when any \(m\) of \(n\) parts \(m \le n\) are presented.</p>
|
||
<p>Thus, users have to split their secret key into \(n\) parts, and for each interaction, they have to reveal the new part of the secret key. So, in addition to proving the membership, users have to prove that the revealed part is truly the part of their secret key.</p>
|
||
<p>If they make more interactions than allowed per epoch, their secret key can be fully reconstructed.</p>
|
||
<h3 id="withdrawal-or-slashing"><a class="header" href="#withdrawal-or-slashing">Withdrawal (or slashing)</a></h3>
|
||
<p>The final property of the <strong>RLN</strong> mechanism is that it allows for the users to be removed from the
|
||
membership tree by anyone that knows their secret key. Thus, if someone spams, it'll be possible to recover the secret key and withdraw the stake (or <em>slash</em>) of a spammer - that's why it's economically inefficient for users to spam.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="technical-side-of-rln"><a class="header" href="#technical-side-of-rln">Technical side of RLN</a></h1>
|
||
<p><em>This topic is a less strict version of specifications. If you want a more formal description, you can find specs in the <a href="./references.html">references</a>. Also, if you're unfamiliar with Shamir's Secret Sharing scheme, you can <a href="./sss.html">read it here</a>.</em></p>
|
||
<hr />
|
||
<p><img src="./images/rln-circuit.png" alt="alt text" /></p>
|
||
<p align="center">
|
||
<i>Under the hood: The <b>RLN</b> Circom Circuit</i>
|
||
</p>
|
||
<p><strong>RLN</strong> consists of three parts:</p>
|
||
<ul>
|
||
<li>User registration</li>
|
||
<li>User interaction (signaling)</li>
|
||
<li>User removal (slashing) - additional part</li>
|
||
</ul>
|
||
<p>Well, let's discuss them.</p>
|
||
<h2 id="user-registration"><a class="header" href="#user-registration">User registration</a></h2>
|
||
<p>The first part of <strong>RLN</strong> is registration. There is nothing special in <strong>RLN</strong> registration; it's almost the same process as in other protocols/apps with anonymous environments: we need to create a Merkle Tree, and every participant must submit a <code>commitment</code> and place it in the Merkle Tree, and after that to interact with the app every participant will create a zkProof's, that they are a <em>member of the tree</em> (we use an <em>Incremental Merkle Tree</em>, as it more <em>GAS efficient</em>).</p>
|
||
<p>So, each member generates a secret key, denoted by \(a_0\). Identity commitment \(q\) is the hash (Poseidon) of the secret key: \(q = Poseidon(a_0)\).</p>
|
||
<p><strong>RLN</strong> wouldn't work if there were no punishment for spam; that's why to become a member, a user has to register and provide something at stake. So, whoever has our \(a_0\) can "slash" us. </p>
|
||
<p>The slight difference is that we must enable a <em>secret sharing</em> scheme (to split the <code>commitment</code> into parts). We need to come up with a polynomial. For simplicity we use linear polynomial (e.g. \(f(x) = kx + b\). Therefore, with two points, we can reconstruct the polynomial and recover the secret. </p>
|
||
<p>Our polynomial will be: \(A(x) = a_1 * x + a_0\), where \(a_1 = Poseidon(a_0, external\_nullifier)\).
|
||
The meaning of \(external\_nullifier\) is described below.</p>
|
||
<h2 id="signalling"><a class="header" href="#signalling">Signalling</a></h2>
|
||
<p>Now that the user is registered, he wants to interact with the system. Imagine that the system is an <em>anonymous chat</em> and the interaction is the sending of messages.
|
||
So, to send a message user have to come up with <em>share</em> - the point \((x, y)\) on her polynomial.
|
||
We denote: \(x = Poseidon(message), y = A(x)\). </p>
|
||
<p>Thus, if the same epoch user sends more than one message, their polynomial and, therefore, their secret (\(a_0\)) can be recovered.</p>
|
||
<p>Of course, we somehow must prove that our <em>share</em> = \((x, y)\) is valid (that this is really a point on our <code>polynomial = A(x)</code>), as well as we must prove other things are valid too, that's why we use zkSNARK. An explanation of the zk-circuits can be found in the next topic.</p>
|
||
<h2 id="slashing"><a class="header" href="#slashing">Slashing</a></h2>
|
||
<p>As it's been said, if a user sends more than one message, everyone else will be able to recover his secret, slash them and take their stake.</p>
|
||
<h2 id="nullifiers"><a class="header" href="#nullifiers">Nullifiers</a></h2>
|
||
<p>There are also \(internal\_nullifier\) and \(external\_nullifier\), which can be found in the <strong>RLN</strong> protocol/circuits.</p>
|
||
<p>\(external\_nullifier = Poseidon(epoch, rln\_identifier)\), where \(rln\_identifier\) is a random finite field value, unique per RLN app.</p>
|
||
<p>The \(external\_nullifier\) is required so that the user can securely use the same private key \(a_0\) across different <strong>RLN</strong> apps - in different applications (and in different eras) with the same secret key, the user will have different values of the coefficient \(a_1\).</p>
|
||
<p>Now, imagine there are a lot of users sending messages, and after each received message, we need to check if any member can be slashed. To do this, we can use all combinations of received <em>shares</em> and try to recover the polynomial, but this is a naive and non-optimal approach. Suppose we have a mechanism that will tell us about the connection between a person and their messages while not revealing their identity. In that case, we can solve this without brute-forcing all possibilities by using a public \(internal\_nullifier = Poseidon(a_1)\), so if a user sends more than one message, it will be immediately visible to everyone.</p>
|
||
<h2 id="some-important-notes"><a class="header" href="#some-important-notes">Some important notes</a></h2>
|
||
<p>Also, in our example (and <a href="https://github.com/njofce/zk-chat">zk-chat</a> implementation), we use linear polynomial, but <a href="sss.html">SSS</a> allows us to use various degree polynomials; therefore we can implement a protocol, where more than one signal (message) can be sent in per epoch. </p>
|
||
<p>To learn more, check out the <a href="https://hackmd.io/7GR5Vi28Rz2EpEmLK0E0Aw?view">specification</a>; there are also <a href="https://github.com/privacy-scaling-explorations/rln/tree/master/circuits">circuits</a> implemented for various degree polynomials too.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="formal-spec-of-circom-rln"><a class="header" href="#formal-spec-of-circom-rln">Formal spec of circom-rln</a></h1>
|
||
<ul>
|
||
<li><a href="formal_spec.html#utils-templates">Utils</a>
|
||
<ul>
|
||
<li><a href="formal_spec.html#merkletreeinclusionproof">MerkleTreeInclusionProof</a></li>
|
||
<li><a href="formal_spec.html#rangecheck">RangeCheck</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a href="formal_spec.html#rln">RLN</a></li>
|
||
<li><a href="formal_spec.html#withdrawal">Withdrawal</a></li>
|
||
</ul>
|
||
<hr />
|
||
<h2 id="utils"><a class="header" href="#utils">Utils</a></h2>
|
||
<p><a href="https://github.com/Rate-Limiting-Nullifier/circom-rln/blob/main/circuits/utils.circom">utils.circom</a> is a set of templates/gadgets that the RLN circuit uses.</p>
|
||
<p>These are: </p>
|
||
<ul>
|
||
<li>MerkleTreeInclusionProof - Merkle tree inclusion check, used like set membership check;</li>
|
||
<li>RangeCheck - used for range check.</li>
|
||
</ul>
|
||
<p>Their description is given below.</p>
|
||
<h3 id="merkletreeinclusionproof"><a class="header" href="#merkletreeinclusionproof">MerkleTreeInclusionProof</a></h3>
|
||
<p><strong>MerkleTreeInclusionProof(DEPTH)</strong> template used for verification of inclusion in full binary incremental merkle tree. The implementation is a fork of https://github.com/privacy-scaling-explorations/incrementalquintree, and changed to <em>binary</em> tree and refactored to <em>Circom 2.1.0</em>.</p>
|
||
<p><strong>Parameters</strong>:</p>
|
||
<ul>
|
||
<li><strong>DEPTH</strong> - depth of the Merkle Tree.</li>
|
||
</ul>
|
||
<p><strong>Inputs</strong>:</p>
|
||
<ul>
|
||
<li>\(leaf\) - \(Poseidon(elem)\), where \(elem\) is the element that's checked for inclusion;</li>
|
||
<li>\(pathIndex[DEPTH]\) - array of length = \(DEPTH\), consists of \(0 | 1\), represents Merkle proof path.
|
||
Basically, it says how to calculate Poseidon hash, e.g. for two inputs \(input1\), \(input2\), if the \(pathIndex[i] = 0\) it shoud be calculated as \(Poseidon(input1, input2)\), otherwise \(Poseidon(input2, input1)\);</li>
|
||
<li>\(pathElements[DEPTH]\) - array of length = \(DEPTH\), represents elements of the Merkle proof.</li>
|
||
</ul>
|
||
<p><strong>Outputs</strong>:</p>
|
||
<ul>
|
||
<li>\(root\) - Root of the merkle tree.</li>
|
||
</ul>
|
||
<p><strong>Templates used</strong>:</p>
|
||
<ul>
|
||
<li><a href="https://github.com/iden3/circomlib/blob/master/circuits/mux1.circom">mux1.circom</a> from circomlib;</li>
|
||
<li><a href="https://github.com/iden3/circomlib/blob/master/circuits/poseidon.circom">poseidon.circom</a> from circomlib.</li>
|
||
</ul>
|
||
<h3 id="rangecheck"><a class="header" href="#rangecheck">RangeCheck</a></h3>
|
||
<p><strong>RangeCheck(LIMIT_BIT_SIZE)</strong> template used for range check, e.g. \(x \le y \le z\).</p>
|
||
<p><strong>Parameters</strong>:</p>
|
||
<ul>
|
||
<li>\(LIMIT\_BIT\_SIZE\) - maximum bit size of numbers that are used in range check, f.e. for the \(LIMIT\_BIT\_SIZE = 16\), input numbers allowed to be in the interval \([0, 65536)\).</li>
|
||
</ul>
|
||
<p><strong>Inputs</strong>:</p>
|
||
<ul>
|
||
<li>\(messageId\) - denotes counter value, that'll be described further;</li>
|
||
<li>\(limit\) - maximum value.</li>
|
||
</ul>
|
||
<p><strong>Templates used</strong>:</p>
|
||
<ul>
|
||
<li><a href="https://github.com/iden3/circomlib/blob/master/circuits/comparators.circom#L105">LessThan(n)</a> from circomlib;</li>
|
||
<li><a href="https://github.com/iden3/circomlib/blob/master/circuits/bitify.circom#L25">Num2Bits(n)</a> from circomlib.</li>
|
||
</ul>
|
||
<p><strong>Logic/Constraints</strong>:
|
||
Checked that \(0 \le messageId < limit\). </p>
|
||
<hr />
|
||
<h2 id="rln"><a class="header" href="#rln">RLN</a></h2>
|
||
<p><a href="https://github.com/Rate-Limiting-Nullifier/circom-rln/blob/main/circuits/rln.circom">rln.circom</a> is a template that's used for RLN protocol. </p>
|
||
<p><strong>Parameters</strong>:</p>
|
||
<ul>
|
||
<li>\(DEPTH\) - depth of a Merkle Tree. Described <a href="formal_spec.html#merkletreeinclusionproof">here</a>;</li>
|
||
<li>\(LIMIT\_BIT\_SIZE\) - maximum bit size of numbers that are used in range check. Described <a href="formal_spec.html#rangecheck">here</a>.</li>
|
||
</ul>
|
||
<p><strong>Private inputs</strong>:</p>
|
||
<ul>
|
||
<li>\(identitySecret\) - randomly generated number in \(\mathbb{F_p}\), used as a private key;</li>
|
||
<li>\(userMessageLimit\) - message limit of the user;</li>
|
||
<li>\(messageId\) - id of the message;</li>
|
||
<li>\(pathElements[DEPTH]\) - pathElements[DEPTH], described <a href="formal_spec.html#merkletreeinclusionproof">here</a>;</li>
|
||
<li>\(identityPathIndex[DEPTH]\) - pathIndex[DEPTH], described <a href="formal_spec.html#merkletreeinclusionproof">here</a>.</li>
|
||
</ul>
|
||
<p><strong>Public inputs</strong>:</p>
|
||
<ul>
|
||
<li>\(x\) - \(Hash(signal)\), where \(signal\) is for example message, that was sent by user;</li>
|
||
<li>\(externalNullifier\) - \(Hash(epoch, rln_identifier)\).</li>
|
||
</ul>
|
||
<p><strong>Outputs</strong>:</p>
|
||
<ul>
|
||
<li>\(y\) - calculated first-degree linear polynomial \((y = kx + b)\);</li>
|
||
<li>\(root\) - root of the Merkle Tree;</li>
|
||
<li>\(nullifier\) - internal nullifier/pseudonym of the user in anonyomus environment.</li>
|
||
</ul>
|
||
<p><strong>Logic/Constraints</strong>:</p>
|
||
<ol>
|
||
<li>Merkle tree membership check:
|
||
<ul>
|
||
<li>\(identityCommitment = Poseidon(identitySecret)\) calculation;</li>
|
||
<li>\(rateCommitment = Poseidon(identityCommitment, userMessageLimit)\) calculation;</li>
|
||
<li><a href="formal_spec.html#merkletreeinclusionproof">Merkle tree inclusion check</a> for the \(rateCommitment\).</li>
|
||
</ul>
|
||
</li>
|
||
<li>Range check:
|
||
<ul>
|
||
<li><a href="formal_spec.html#rangecheck">Range check</a> that \(0 \le messageId < limit\).</li>
|
||
</ul>
|
||
</li>
|
||
<li>Polynomial share calculation:
|
||
<ul>
|
||
<li>\(a_1 = Poseidon(identitySecret, externalNullifier, messageId)\);</li>
|
||
<li>\(y = identitySecret + a_1 * x\).</li>
|
||
</ul>
|
||
</li>
|
||
<li>Output of calculated \(root\), \(y = share\) and \(nullifier = Poseidon(a_1)\) values.</li>
|
||
</ol>
|
||
<hr />
|
||
<h3 id="withdrawal"><a class="header" href="#withdrawal">Withdrawal</a></h3>
|
||
<p><a href="https://github.com/Rate-Limiting-Nullifier/circom-rln/blob/main/circuits/withdraw.circom">withdraw.circom</a> is a circuit that's used for the withdrawal/slashing and is needed to prevent frontrun while withdrawing the stake from the smart-contract/registry. </p>
|
||
<p><strong>Private inputs</strong>:</p>
|
||
<ul>
|
||
<li>\(identitySecret\) - randomly generated number in \(\mathbb{F_p}\), used as private key.</li>
|
||
</ul>
|
||
<p><strong>Public inputs</strong>:</p>
|
||
<ul>
|
||
<li>\(address\) - \(\mathbb{F_p}\) scalar field element; denotes ETH address that'll receive stake. </li>
|
||
</ul>
|
||
<p><strong>Outputs</strong>:</p>
|
||
<ul>
|
||
<li>\(identityCommitment = Poseidon(identitySecret)\).</li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="smart-contract"><a class="header" href="#smart-contract">Smart-contract</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="uses"><a class="header" href="#uses">Uses</a></h1>
|
||
<p>This section contains list of apps that use <strong>RLN</strong>:</p>
|
||
<ul>
|
||
<li><a href="https://github.com/Rate-Limiting-Nullifier/zk-chat-client-server">zk-chat</a> - a spam resistant instant messaging application for private and anonymous communication;</li>
|
||
<li><a href="https://rfc.vac.dev/spec/17/">waku-rln-relay</a> - extension of <a href="https://rfc.vac.dev/spec/11/">waku-relay</a> (spam protection with <strong>RLN</strong>);</li>
|
||
<li><a href="https://github.com/nabladelta/lambdadelta">lambdadelta</a> - P2P event feed library secured by RLN proofs;</li>
|
||
<li><a href="https://github.com/nabladelta/bernkastel">bernkastel</a> - decentralized event feed, based on lambdadelta library.</li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="shamirs-secret-sharing-scheme"><a class="header" href="#shamirs-secret-sharing-scheme">Shamir's Secret Sharing Scheme</a></h1>
|
||
<p><em>This topic is an explanation of <strong>Shamir's Secret Sharing</strong> scheme (<strong>SSS</strong>), also known as \((k, n)\) threshold secret sharing scheme. <strong>SSS</strong> is one of the critical parts of <strong>RLN</strong>.</em></p>
|
||
<h2 id="overview-1"><a class="header" href="#overview-1">Overview</a></h2>
|
||
<p>Imagine if you have some important secret (secret key) and you don't want to store it anywhere. For that, you can use the <em>SSS</em> scheme. It allows you to split this secret into \(n\) parts (each individual part doesn't give any information about the secret) and restore this secret upon presentation of \(k\) \((k <= n)\) parts.</p>
|
||
<p>For example, you have a secret that you want to split into \(n\) parts/shares. You can divide these shares between your friends (1 share to 1 friend). Now when \(k\) of your friends reveal their share, you can restore the secret.</p>
|
||
<p>This scheme is also called \((k, n)\) <em>threshold secret sharing scheme</em>.</p>
|
||
<p>This scheme is possible due to <em>polynomial interpolation</em> (especially Lagrange interpolation). Let's describe how <em>Lagrange interpolation</em> works and how it's used in a <em>SSS</em> scheme.</p>
|
||
<h2 id="polynomial-lagrange-interpolation"><a class="header" href="#polynomial-lagrange-interpolation">Polynomial (Lagrange) interpolation</a></h2>
|
||
<p><em>Interpolation</em> is a method of constructing (or restoring) new points/values (or function) based on the range of a set of known points/values (f.e. we can restore the line (linear function) from two points that are from this line). The previous example describes how that works. </p>
|
||
<p align="center">
|
||
<img src="./images/graph1.png" width="300">
|
||
</p>
|
||
<p align="center">
|
||
<i>An unlimited number of parabolas (second-degree polynomials) can be drawn through two points. To choose the only one, you need a third point.</i>
|
||
</p>
|
||
<p>Thus, if we have a polynomial \(f(x) = 3x + 2\), we only need two points from this polynomial to restore it. Let's peek two random \(x\) values and calculate \(f(x)\):</p>
|
||
<ul>
|
||
<li>For \(x = 1\) we have \(f(1) = 3 * 1 + 2 = 5\)</li>
|
||
<li>For \(x = 10\) we have \(f(10) = 32\)</li>
|
||
</ul>
|
||
<p>Now we have to shares: \((1, 5)\) and \((10, 32)\). If we draw a graph based on these two shares, we can easily see that this is the same line (function):</p>
|
||
<p align="center">
|
||
<img src="./images/line.png" width="500" height="400">
|
||
</p>
|
||
<p>We also can "restore" the function analytically. For that let's denote: \[f(x) = y_1 * \frac{x - x_2}{x_1 - x_2} + y_2 * \frac{x - x_1}{x_2 - x_1}\]
|
||
where \(x_1 = 1, x_2 = 10, y_1 = 5, y_2 = 32\). If we make substitution we got: \[f(x) = 3x + 2 \]
|
||
which is the same polynomial.</p>
|
||
<p>The same technique can be made with every polynomial. Main thing to remember is that we need \(n + 1\) points to interpolate \(n\)-degree polynomial.</p>
|
||
<p>Now that we know how interpolation works, we can learn how it is used in SSS.</p>
|
||
<h2 id="shamirs-secret-sharing"><a class="header" href="#shamirs-secret-sharing">Shamir's Secret Sharing</a></h2>
|
||
<p>To create the <strong>SSS</strong> construct, we must choose \((k, n)\), where \(n\) is the number of shares we want to get from the secret and \(k\) is the number of shares required to restore the secret. The degree of the "secret" polynomial is \(k - 1\) (covered in the previous section).
|
||
Let's try to construct <strong>SSS</strong> with an example.</p>
|
||
<h3 id="sharing"><a class="header" href="#sharing">Sharing</a></h3>
|
||
<ol>
|
||
<li>Our secret = \(S = 30\) </li>
|
||
<li>As the linear polynomial used in current <strong>RLN</strong> implementations, let's set \(k = 2\) (2 points are enough to recover the polynomial); \(n\) is not that important, but we can make it any number, f.e. 3</li>
|
||
<li>The secret polynomial is: \[f(x) = a_1 * x + a_0 \]
|
||
where zero coefficient \(a_0 = S\), and \(a_1\) is some random number (f.e. 5); </li>
|
||
<li>We must pick \(n = 3\) different points (shares) on that polynomial, for that we can pick three random \(x\) values (f.e. 5, 8, 16) and calculate \(f(x)\):
|
||
\[f(5) = 5 * 5 + 30 = 55\]
|
||
\[f(8) = 5 * 8 + 30 = 70 \]
|
||
\[f(16) = 5 * 16 + 30 = 110 \]
|
||
So, the shares are: \((5, 55), (8, 70), (16, 110)\)</li>
|
||
</ol>
|
||
<h3 id="recovering"><a class="header" href="#recovering">Recovering</a></h3>
|
||
<p>We can take any two shares to recover (as described in the interpolation section) the "secret" polynomial. Zero coefficient (\(a_0\)) in the recovered polynomial is the secret \(S\).</p>
|
||
<h2 id="important-notes"><a class="header" href="#important-notes">Important notes</a></h2>
|
||
<p>Arithmetic in this topic is usual for us. However, in real life, <strong>SSS</strong> arithmetic is defined over some finite field. This means that all calculations are carried out modulo some big prime field. In fact, it happens by itself in Circom because the arithmetic there is defined over the finite field, too, so we don't need to do anything extra). </p>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="research"><a class="header" href="#research">Research</a></h1>
|
||
<p>In addition to production-ready <strong>circom-rln</strong> version of RLN protocol, there is also wip on <strong>RLN</strong> R&D to reduce proving time and have bigger choice of implementations.
|
||
For now you can follow the work on:</p>
|
||
<ul>
|
||
<li><a href="./kzg_rln.html">kzg-rln</a>;</li>
|
||
<li><a href="./noir_rln.html">noir-rln</a>.</li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="kzg-rln"><a class="header" href="#kzg-rln">KZG-RLN</a></h1>
|
||
<p>Read <a href="https://zkresear.ch/t/rln-on-kzg-polynomial-commitment-scheme-cross-posted/114">this post</a> on zkresear.ch.</p>
|
||
<p>WIP in the <a href="https://github.com/Rate-Limiting-Nullifier/kzg-rln">kzg-rln</a> repo.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="noir-rln"><a class="header" href="#noir-rln">Noir-RLN</a></h1>
|
||
<p>WIP in the <a href="https://github.com/Rate-Limiting-Nullifier/noir-rln">noir-rln</a> repo.</p>
|
||
|
||
</main>
|
||
|
||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||
<!-- Mobile navigation buttons -->
|
||
|
||
|
||
<div style="clear: both"></div>
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
|
||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||
|
||
</nav>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
|
||
<script>
|
||
window.playground_copyable = true;
|
||
</script>
|
||
|
||
|
||
<script src="elasticlunr.min.js"></script>
|
||
<script src="mark.min.js"></script>
|
||
<script src="searcher.js"></script>
|
||
|
||
<script src="clipboard.min.js"></script>
|
||
<script src="highlight.js"></script>
|
||
<script src="book.js"></script>
|
||
|
||
<!-- Custom JS scripts -->
|
||
<script src="mermaid.min.js"></script>
|
||
<script src="mermaid-init.js"></script>
|
||
|
||
<script>
|
||
window.addEventListener('load', function() {
|
||
MathJax.Hub.Register.StartupHook('End', function() {
|
||
window.setTimeout(window.print, 100);
|
||
});
|
||
});
|
||
</script>
|
||
|
||
</div>
|
||
</body>
|
||
</html>
|