diff --git a/images/rln-circuit.png b/images/rln-circuit.png new file mode 100644 index 0000000..5f64f0d Binary files /dev/null and b/images/rln-circuit.png differ diff --git a/print.html b/print.html index 6e55cc9..1b213e1 100644 --- a/print.html +++ b/print.html @@ -215,48 +215,7 @@ We denote: x = Poseidon(message), and y = A(x).

Also, in our example (and zk-chat implementation), we use linear polynomial, but SSS 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.

To learn more, check out the specification; there are also circuits implemented for various degree polynomials too.

Diagram

-
flowchart TB
-
-    subgraph Generate Secret Key
-      random0(Random 32 bytes) --> a_0(Secret Key)
-      random1(Random 32 bytes) --> a_0
-    end
-
-    subgraph RLN
-
-      subgraph Identity Commitment
-        a_0 --> h0(Poseidon Hash)
-        h0 --> q(Identity Commitment)
-      end
-
-      subgraph Calculate Internal Nullifier
-        a_0 --> h1(Poseidon Hash)
-        epoch(Epoch) --> h1
-        h1 --> a_1
-        rln_identifier(RLN Identifier) --> h2(Poseidon Hash)
-        a_1 --> h2
-        h2 --> nullifier(RLN Internal Nullifier)
-      end
-
-      subgraph Merkle Tree
-        q --> merkle_tree_inclusion_proof(Merkle Tree Inclusion Proof)
-        merkle_tree_inclusion_proof --> root(ZKP of Merkle Tree Root)
-      end
-
-      subgraph Shamirs Secret Scheme
-        a_0 --> plus(+)
-        a_1 --> multiply(*)
-        x(Hashed Messaage) --> multiply
-        multiply --> plus
-        plus --> share_y
-      end
-
-        nullifier --> proof(ZKP)
-        root --> proof
-        share_y --> proof
-    end
-
-
+

alt text

Circuits

zkSNARK is used in the RLN core. Therefore, we must represent the protocol in R1CS (as we use Groth16). Circom was chosen for this. This section explains RLN circuits for the linear polynomial case (one message per epoch). You can find implementation for the general case here


diff --git a/protocol_spec.html b/protocol_spec.html index 276302d..73e6c27 100644 --- a/protocol_spec.html +++ b/protocol_spec.html @@ -167,48 +167,7 @@ We denote: x = Poseidon(message), and y = A(x).

Also, in our example (and zk-chat implementation), we use linear polynomial, but SSS 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.

To learn more, check out the specification; there are also circuits implemented for various degree polynomials too.

Diagram

-
flowchart TB
-
-    subgraph Generate Secret Key
-      random0(Random 32 bytes) --> a_0(Secret Key)
-      random1(Random 32 bytes) --> a_0
-    end
-
-    subgraph RLN
-
-      subgraph Identity Commitment
-        a_0 --> h0(Poseidon Hash)
-        h0 --> q(Identity Commitment)
-      end
-
-      subgraph Calculate Internal Nullifier
-        a_0 --> h1(Poseidon Hash)
-        epoch(Epoch) --> h1
-        h1 --> a_1
-        rln_identifier(RLN Identifier) --> h2(Poseidon Hash)
-        a_1 --> h2
-        h2 --> nullifier(RLN Internal Nullifier)
-      end
-
-      subgraph Merkle Tree
-        q --> merkle_tree_inclusion_proof(Merkle Tree Inclusion Proof)
-        merkle_tree_inclusion_proof --> root(ZKP of Merkle Tree Root)
-      end
-
-      subgraph Shamirs Secret Scheme
-        a_0 --> plus(+)
-        a_1 --> multiply(*)
-        x(Hashed Messaage) --> multiply
-        multiply --> plus
-        plus --> share_y
-      end
-
-        nullifier --> proof(ZKP)
-        root --> proof
-        share_y --> proof
-    end
-
-
+

alt text

diff --git a/searchindex.js b/searchindex.js index de7089b..5023aff 100644 --- a/searchindex.js +++ b/searchindex.js @@ -1 +1 @@ -Object.assign(window.search, {"doc_urls":["rln.html#rln","overview.html#overview","what_is_rln.html#what-is-rate-limiting-nullifier","what_is_rln.html#how-it-works","what_is_rln.html#user-registration","what_is_rln.html#user-interaction","what_is_rln.html#user-removal-slashing","under_the_hood.html#under-the-hood","protocol_spec.html#technical-side-of-rln","protocol_spec.html#user-registration","protocol_spec.html#signalling","protocol_spec.html#slashing","protocol_spec.html#some-important-notes","protocol_spec.html#diagram","circuits.html#circuits","circuits.html#merkle-tree-circuit","circuits.html#rln-core","circuits.html#membership-in-merkle-tree","circuits.html#correctness-of-secret-share","circuits.html#main-runner-of-the-circuits","uses.html#uses","how_to_use.html#how-to-use","theory.html#theory","sss.html#shamirs-secret-sharing-scheme","appendix.html#appendix","terminology.html#terminology","references.html#references"],"index":{"documentStore":{"docInfo":{"0":{"body":35,"breadcrumbs":2,"title":1},"1":{"body":19,"breadcrumbs":2,"title":1},"10":{"body":64,"breadcrumbs":6,"title":1},"11":{"body":12,"breadcrumbs":6,"title":1},"12":{"body":159,"breadcrumbs":7,"title":2},"13":{"body":107,"breadcrumbs":6,"title":1},"14":{"body":35,"breadcrumbs":5,"title":1},"15":{"body":209,"breadcrumbs":7,"title":3},"16":{"body":171,"breadcrumbs":6,"title":2},"17":{"body":56,"breadcrumbs":7,"title":3},"18":{"body":37,"breadcrumbs":7,"title":3},"19":{"body":55,"breadcrumbs":7,"title":3},"2":{"body":74,"breadcrumbs":5,"title":3},"20":{"body":34,"breadcrumbs":3,"title":1},"21":{"body":14,"breadcrumbs":2,"title":1},"22":{"body":12,"breadcrumbs":2,"title":1},"23":{"body":239,"breadcrumbs":8,"title":4},"24":{"body":9,"breadcrumbs":2,"title":1},"25":{"body":203,"breadcrumbs":3,"title":1},"26":{"body":26,"breadcrumbs":4,"title":1},"3":{"body":43,"breadcrumbs":3,"title":1},"4":{"body":69,"breadcrumbs":4,"title":2},"5":{"body":136,"breadcrumbs":4,"title":2},"6":{"body":64,"breadcrumbs":5,"title":3},"7":{"body":15,"breadcrumbs":5,"title":2},"8":{"body":37,"breadcrumbs":8,"title":3},"9":{"body":124,"breadcrumbs":7,"title":2}},"docs":{"0":{"body":"RLN (Rate-Limiting Nullifier) is a zk-gadget/protocol that enables spam prevention mechanism for anonymous environments. RLN is part of ( PSE ) Privacy & Scaling Explorations , a multidisciplinary team supported by the Ethereum Foundation. PSE explores new use cases for zero-knowledge proofs and other cryptographic primitives. alt text","breadcrumbs":"RLN » RLN","id":"0","title":"RLN"},"1":{"body":"This section is a starting point for understanding the concepts of RLN . Here we'll discuss: Basic explanation of the RLN protocol RLN protocol under the hood RLN uses","breadcrumbs":"Overview » Overview","id":"1","title":"Overview"},"10":{"body":"Now that the user is registered, he wants to interact with the system. Imagine that the system is an anonymous chat and the interaction is the sending of messages. So, to send a message user have to come up with share - the point (x, y) on her polynomial. We denote: x = Poseidon(message), and y = A(x). Thus, if the same epoch user sends more than one message, their polynomial and, therefore, their secret (a_0) can be recovered. Of course, we somehow must prove that our share = (x, y) is valid (that this is really a point on our polynomial = A(x)), 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.","breadcrumbs":"Overview » Under the hood » Protocol spec » Signalling","id":"10","title":"Signalling"},"11":{"body":"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.","breadcrumbs":"Overview » Under the hood » Protocol spec » Slashing","id":"11","title":"Slashing"},"12":{"body":"There are also nullifier and rln_identifier, which can be found in the RLN protocol/circuits. So, rln_identifier is just a random value that's unique per RLN app. It's used for additional cross-application security - to protect the user secrets from being compromised if they use the same credentials across different RLN apps. If rln_identifier is not present, the user uses the same credentials and sends a message in two different RLN apps using the same epoch, then their secret key can be revealed. Adding the rln_identifier field, we obscure the nullifier, so this kind of attack cannot happen. The only kind of attack that is possible is if we have an entity with a global view of all messages, and they try to brute-force different combinations of x and y shares for different nullifiers. 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 shares 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 nullifier (nullifier = Poseidon(a_1, rln_identifier)), so if a user sends more than one message, it will be immediately visible to everyone. Also, in our example (and zk-chat implementation), we use linear polynomial, but SSS 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. To learn more, check out the specification ; there are also circuits implemented for various degree polynomials too.","breadcrumbs":"Overview » Under the hood » Protocol spec » Some important notes","id":"12","title":"Some important notes"},"13":{"body":"flowchart TB subgraph Generate Secret Key random0(Random 32 bytes) --> a_0(Secret Key) random1(Random 32 bytes) --> a_0 end subgraph RLN subgraph Identity Commitment a_0 --> h0(Poseidon Hash) h0 --> q(Identity Commitment) end subgraph Calculate Internal Nullifier a_0 --> h1(Poseidon Hash) epoch(Epoch) --> h1 h1 --> a_1 rln_identifier(RLN Identifier) --> h2(Poseidon Hash) a_1 --> h2 h2 --> nullifier(RLN Internal Nullifier) end subgraph Merkle Tree q --> merkle_tree_inclusion_proof(Merkle Tree Inclusion Proof) merkle_tree_inclusion_proof --> root(ZKP of Merkle Tree Root) end subgraph Shamirs Secret Scheme a_0 --> plus(+) a_1 --> multiply(*) x(Hashed Messaage) --> multiply multiply --> plus plus --> share_y end nullifier --> proof(ZKP) root --> proof share_y --> proof end","breadcrumbs":"Overview » Under the hood » Protocol spec » Diagram","id":"13","title":"Diagram"},"14":{"body":"zkSNARK is used in the RLN core. Therefore, we must represent the protocol in R1CS (as we use Groth16 ). Circom was chosen for this. This section explains RLN circuits for the linear polynomial case (one message per epoch). You can find implementation for the general case here RLN circuits implement the logic described in previous topic .","breadcrumbs":"Overview » Under the hood » Circuits » Circuits","id":"14","title":"Circuits"},"15":{"body":"One of the critical components of RLN is the Incremental Merkle Tree for the membership tree. Any Merkle tree can be used, but we have chosen the Incremental Merkle Tree for gas efficiency. Let's look at the implementation . At the beginning of the file, we denote that we use Circom 2.0 and include two helper zk-gadgets : pragma circom 2.0.0; include \"../node_modules/circomlib/circuits/poseidon.circom\";\ninclude \"../node_modules/circomlib/circuits/mux1.circom\"; Poseidon gadget is just the implementation of the Poseidon hash function; the mux1 gadget will be described later. Next, we can see two implemented gadgets: template PoseidonHashT3() { var nInputs = 2; signal input inputs[nInputs]; signal output out; component hasher = Poseidon(nInputs); for (var i = 0; i < nInputs; i ++) { hasher.inputs[i] <== inputs[i]; } out <== hasher.out;\n} template HashLeftRight() { signal input left; signal input right; signal output hash; component hasher = PoseidonHashT3(); left ==> hasher.inputs[0]; right ==> hasher.inputs[1]; hash <== hasher.out;\n} These are helper gadgets to make the code more clean. Poseidon gadget is implemented with the ability to take a different number of arguments. We use PoseidonHashT3() to initialize it like a function with two arguments. And HashLeftRight use PoseidonHashT3 in a more \"readable\" way: it takes two inputs, left and right, and outputs the result of the calculation. Next comes the core of the Merkle Tree gadget: template MerkleTreeInclusionProof(n_levels) { signal input leaf; signal input path_index[n_levels]; signal input path_elements[n_levels][1]; signal output root; component hashers[n_levels]; component mux[n_levels]; signal levelHashes[n_levels + 1]; levelHashes[0] <== leaf; ... root <== levelHashes[n_levels];\n} Here we have three inputs: leaf, path_index, and path_elements. path_index is the position of the leaf represented in binary. We need the binary representation of the position in the Merkle tree to understand the hashing path from the leaf to the root (more on that \"3. Recursive Incremental Merkle Tree Algorithm, page 4\" ). path_elements are sibling leaves that are part of Merkle Proof. leaf = Poseidon(identity_secret), so it's just identity commitment . There is a Merkle Tree hashing algorithm in the omitted part, no more than that.","breadcrumbs":"Overview » Under the hood » Circuits » Merkle Tree circuit","id":"15","title":"Merkle Tree circuit"},"16":{"body":"RLN circuit is the implementation of RLN logic itself (which in turn uses the Merkle Tree gadget). You can find the implementation here . So, let's start with helper gadgets: template CalculateIdentityCommitment() { signal input identity_secret; signal output out; component hasher = Poseidon(1); hasher.inputs[0] <== identity_secret; out <== hasher.out;\n} template CalculateA1() { signal input a_0; signal input epoch; signal output out; component hasher = Poseidon(2); hasher.inputs[0] <== a_0; hasher.inputs[1] <== epoch; out <== hasher.out;\n} template CalculateNullifier() { signal input a_1; signal input rln_identifier; signal output out; component hasher = Poseidon(2); hasher.inputs[0] <== a_1; hasher.inputs[1] <== rln_identifier; out <== hasher.out;\n} It's easy to understand these samples: CalculateIdentityCommitment() is used to calculate the identity commitment. It takes secret and outputs the commitment. CalculateA1() and CalculateNullifier() are used to calculate a_1 and nullifier (internal nullifier); they are implemented as it's described in previous topic . Now, let's look at the core logic of the RLN circuit. ... signal input identity_secret; signal input path_elements[n_levels][LEAVES_PER_PATH_LEVEL]; signal input identity_path_index[n_levels]; signal input x; signal input epoch; signal input rln_identifier; signal output y; signal output root; signal output nullifier; ... So, here we have many inputs. Private inputs are: identity_secret (basically a_0 from the polynomial), path_elements[][], identity_path_index[]. Public inputs are: x (actually just the hash of a signal), epoch, rln_identifier. Outputs are: y' (share of the secret), rootof a Merkle Tree, andnullifier.` RLN circuit consists of two checks: Membership in Merkle Tree Correctness of secret share","breadcrumbs":"Overview » Under the hood » Circuits » RLN core","id":"16","title":"RLN core"},"17":{"body":"To check membership in a Merkle Tree, we can simply use the previously described Merkle Tree gadget: ... component identity_commitment = CalculateIdentityCommitment(); identity_commitment.identity_secret <== identity_secret; var i; var j; component inclusionProof = MerkleTreeInclusionProof(n_levels); inclusionProof.leaf <== identity_commitment.out; for (i = 0; i < n_levels; i++) { for (j = 0; j < LEAVES_PER_PATH_LEVEL; j++) { inclusionProof.path_elements[i][j] <== path_elements[i][j]; } inclusionProof.path_index[i] <== identity_path_index[i]; } ... Here we are calculating the identity_commitment and passing it along with sibling leaves and binary representation of the position to a Merkle Tree gadget. It gives us the calculated root as an output, and we can put the constraint on that: root <== inclusionProof.root;","breadcrumbs":"Overview » Under the hood » Circuits » Membership in Merkle Tree","id":"17","title":"Membership in Merkle Tree"},"18":{"body":"As we use linear polynomial we need to check that y = a_1 * x + a_0 (a_0 is identity secret). For that, we need these constraints: ... component a_1 = CalculateA1(); a_1.a_0 <== identity_secret; a_1.epoch <== epoch; y <== identity_secret + a_1.out * x; ... To calculate and reveal the nullifier: ... component calculateNullifier = CalculateNullifier(); calculateNullifier.a_1 <== a_1.out; calculateNullifier.rln_identifier <== rln_identifier; nullifier <== calculateNullifier.out; ...","breadcrumbs":"Overview » Under the hood » Circuits » Correctness of secret share","id":"18","title":"Correctness of secret share"},"19":{"body":"Now the Circuits can be used as gadgets. If we want to use it in our app, we need to initialize it and have a main - starting point function. It can be found here . The implementation is super basic: pragma circom 2.0.0; include \"./rln-base.circom\"; component main {public [x, epoch, rln_identifier ]} = RLN(15); That's the whole RLN Circom Circuit :) Here we just need to list all public inputs (x, epoch, rln_identifier; the rest of the inputs are private). Also, we set the depth of the Merkle Tree = 15 (max of 32768 members).","breadcrumbs":"Overview » Under the hood » Circuits » Main runner of the circuits","id":"19","title":"Main runner of the circuits"},"2":{"body":"This topic is a part of complete overview by Blagoj . RLN is a zero-knowledge gadget that enables spam prevention for decentralized, anonymous environments. The anonymity property opens up the possibility for spam and Sybil attack vectors for certain applications, 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. RLN helps us identify and \"kick\" the spammer. Moreover, RLN 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).","breadcrumbs":"Overview » What is RLN » What is Rate-Limiting Nullifier?","id":"2","title":"What is Rate-Limiting Nullifier?"},"20":{"body":"This section contains list of apps that use RLN : zk-chat - A spam resistant instant messaging application for private and anonymous communication rln-chat-app - PoC app, created using rln-js waku-rln-relay - Extension of waku-relay (spam protection with RLN )","breadcrumbs":"Overview » Uses » Uses","id":"20","title":"Uses"},"21":{"body":"This section provides information on how to use RLN in your project: JavaScript RLN (for rln-js ) Rust RLN (for zerokit-rln )","breadcrumbs":"How to use » How to use","id":"21","title":"How to use"},"22":{"body":"This section provides theoretical information that underpins RLN . Here we'll discuss: Shamir's Secret Sharing","breadcrumbs":"Theory » Theory","id":"22","title":"Theory"},"23":{"body":"Shamirs Secret Sharing allows to split the secret to n parts and restore it upon presentation any m parts (m <= n) Sharmir's Secret Sharing wikipedia is a good reference to understand the concept. Reconstruction 1: https://github.com/akinovak/semaphore-lib/blob/5b9bb3210192c8e508eced7ef6579fd56e635ed0/src/rln.ts#L31 retrievePrivateKey(x1: bigint, x2:bigint, y1:bigint, y2:bigint): Buffer | ArrayBuffer { const slope = Fq.div(Fq.sub(y2, y1), Fq.sub(x2, x1)) const privateKey = Fq.sub(y1, Fq.mul(slope, x1)); return bigintConversion.bigintToBuf(Fq.normalize(privateKey)); } Reconstruction 2: https://github.com/akinovak/semaphore-lib/blob/rln_signature_changes/test/index.ts#L250 async function testRlnSlashingSimulation() { RLN.setHasher('poseidon'); const identity = RLN.genIdentity(); const privateKey = identity.keypair.privKey; const leafIndex = 3; const idCommitments: Array = []; for (let i=0; i hasher.inputs[0]; right ==> hasher.inputs[1]; hash <== hasher.out;\n} These are helper gadgets to make the code more clean. Poseidon gadget is implemented with the ability to take a different number of arguments. We use PoseidonHashT3() to initialize it like a function with two arguments. And HashLeftRight use PoseidonHashT3 in a more \"readable\" way: it takes two inputs, left and right, and outputs the result of the calculation. Next comes the core of the Merkle Tree gadget: template MerkleTreeInclusionProof(n_levels) { signal input leaf; signal input path_index[n_levels]; signal input path_elements[n_levels][1]; signal output root; component hashers[n_levels]; component mux[n_levels]; signal levelHashes[n_levels + 1]; levelHashes[0] <== leaf; ... root <== levelHashes[n_levels];\n} Here we have three inputs: leaf, path_index, and path_elements. path_index is the position of the leaf represented in binary. We need the binary representation of the position in the Merkle tree to understand the hashing path from the leaf to the root (more on that \"3. Recursive Incremental Merkle Tree Algorithm, page 4\" ). path_elements are sibling leaves that are part of Merkle Proof. leaf = Poseidon(identity_secret), so it's just identity commitment . There is a Merkle Tree hashing algorithm in the omitted part, no more than that.","breadcrumbs":"Overview » Under the hood » Circuits » Merkle Tree circuit","id":"15","title":"Merkle Tree circuit"},"16":{"body":"RLN circuit is the implementation of RLN logic itself (which in turn uses the Merkle Tree gadget). You can find the implementation here . So, let's start with helper gadgets: template CalculateIdentityCommitment() { signal input identity_secret; signal output out; component hasher = Poseidon(1); hasher.inputs[0] <== identity_secret; out <== hasher.out;\n} template CalculateA1() { signal input a_0; signal input epoch; signal output out; component hasher = Poseidon(2); hasher.inputs[0] <== a_0; hasher.inputs[1] <== epoch; out <== hasher.out;\n} template CalculateNullifier() { signal input a_1; signal input rln_identifier; signal output out; component hasher = Poseidon(2); hasher.inputs[0] <== a_1; hasher.inputs[1] <== rln_identifier; out <== hasher.out;\n} It's easy to understand these samples: CalculateIdentityCommitment() is used to calculate the identity commitment. It takes secret and outputs the commitment. CalculateA1() and CalculateNullifier() are used to calculate a_1 and nullifier (internal nullifier); they are implemented as it's described in previous topic . Now, let's look at the core logic of the RLN circuit. ... signal input identity_secret; signal input path_elements[n_levels][LEAVES_PER_PATH_LEVEL]; signal input identity_path_index[n_levels]; signal input x; signal input epoch; signal input rln_identifier; signal output y; signal output root; signal output nullifier; ... So, here we have many inputs. Private inputs are: identity_secret (basically a_0 from the polynomial), path_elements[][], identity_path_index[]. Public inputs are: x (actually just the hash of a signal), epoch, rln_identifier. Outputs are: y' (share of the secret), rootof a Merkle Tree, andnullifier.` RLN circuit consists of two checks: Membership in Merkle Tree Correctness of secret share","breadcrumbs":"Overview » Under the hood » Circuits » RLN core","id":"16","title":"RLN core"},"17":{"body":"To check membership in a Merkle Tree, we can simply use the previously described Merkle Tree gadget: ... component identity_commitment = CalculateIdentityCommitment(); identity_commitment.identity_secret <== identity_secret; var i; var j; component inclusionProof = MerkleTreeInclusionProof(n_levels); inclusionProof.leaf <== identity_commitment.out; for (i = 0; i < n_levels; i++) { for (j = 0; j < LEAVES_PER_PATH_LEVEL; j++) { inclusionProof.path_elements[i][j] <== path_elements[i][j]; } inclusionProof.path_index[i] <== identity_path_index[i]; } ... Here we are calculating the identity_commitment and passing it along with sibling leaves and binary representation of the position to a Merkle Tree gadget. It gives us the calculated root as an output, and we can put the constraint on that: root <== inclusionProof.root;","breadcrumbs":"Overview » Under the hood » Circuits » Membership in Merkle Tree","id":"17","title":"Membership in Merkle Tree"},"18":{"body":"As we use linear polynomial we need to check that y = a_1 * x + a_0 (a_0 is identity secret). For that, we need these constraints: ... component a_1 = CalculateA1(); a_1.a_0 <== identity_secret; a_1.epoch <== epoch; y <== identity_secret + a_1.out * x; ... To calculate and reveal the nullifier: ... component calculateNullifier = CalculateNullifier(); calculateNullifier.a_1 <== a_1.out; calculateNullifier.rln_identifier <== rln_identifier; nullifier <== calculateNullifier.out; ...","breadcrumbs":"Overview » Under the hood » Circuits » Correctness of secret share","id":"18","title":"Correctness of secret share"},"19":{"body":"Now the Circuits can be used as gadgets. If we want to use it in our app, we need to initialize it and have a main - starting point function. It can be found here . The implementation is super basic: pragma circom 2.0.0; include \"./rln-base.circom\"; component main {public [x, epoch, rln_identifier ]} = RLN(15); That's the whole RLN Circom Circuit :) Here we just need to list all public inputs (x, epoch, rln_identifier; the rest of the inputs are private). Also, we set the depth of the Merkle Tree = 15 (max of 32768 members).","breadcrumbs":"Overview » Under the hood » Circuits » Main runner of the circuits","id":"19","title":"Main runner of the circuits"},"2":{"body":"This topic is a part of complete overview by Blagoj . RLN is a zero-knowledge gadget that enables spam prevention for decentralized, anonymous environments. The anonymity property opens up the possibility for spam and Sybil attack vectors for certain applications, 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. RLN helps us identify and \"kick\" the spammer. Moreover, RLN 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).","breadcrumbs":"Overview » What is RLN » What is Rate-Limiting Nullifier?","id":"2","title":"What is Rate-Limiting Nullifier?"},"20":{"body":"This section contains list of apps that use RLN : zk-chat - A spam resistant instant messaging application for private and anonymous communication rln-chat-app - PoC app, created using rln-js waku-rln-relay - Extension of waku-relay (spam protection with RLN )","breadcrumbs":"Overview » Uses » Uses","id":"20","title":"Uses"},"21":{"body":"This section provides information on how to use RLN in your project: JavaScript RLN (for rln-js ) Rust RLN (for zerokit-rln )","breadcrumbs":"How to use » How to use","id":"21","title":"How to use"},"22":{"body":"This section provides theoretical information that underpins RLN . Here we'll discuss: Shamir's Secret Sharing","breadcrumbs":"Theory » Theory","id":"22","title":"Theory"},"23":{"body":"Shamirs Secret Sharing allows to split the secret to n parts and restore it upon presentation any m parts (m <= n) Sharmir's Secret Sharing wikipedia is a good reference to understand the concept. Reconstruction 1: https://github.com/akinovak/semaphore-lib/blob/5b9bb3210192c8e508eced7ef6579fd56e635ed0/src/rln.ts#L31 retrievePrivateKey(x1: bigint, x2:bigint, y1:bigint, y2:bigint): Buffer | ArrayBuffer { const slope = Fq.div(Fq.sub(y2, y1), Fq.sub(x2, x1)) const privateKey = Fq.sub(y1, Fq.mul(slope, x1)); return bigintConversion.bigintToBuf(Fq.normalize(privateKey)); } Reconstruction 2: https://github.com/akinovak/semaphore-lib/blob/rln_signature_changes/test/index.ts#L250 async function testRlnSlashingSimulation() { RLN.setHasher('poseidon'); const identity = RLN.genIdentity(); const privateKey = identity.keypair.privKey; const leafIndex = 3; const idCommitments: Array = []; for (let i=0; i hasher.inputs[0]; right ==> hasher.inputs[1]; hash <== hasher.out;\n} These are helper gadgets to make the code more clean. Poseidon gadget is implemented with the ability to take a different number of arguments. We use PoseidonHashT3() to initialize it like a function with two arguments. And HashLeftRight use PoseidonHashT3 in a more \"readable\" way: it takes two inputs, left and right, and outputs the result of the calculation. Next comes the core of the Merkle Tree gadget: template MerkleTreeInclusionProof(n_levels) { signal input leaf; signal input path_index[n_levels]; signal input path_elements[n_levels][1]; signal output root; component hashers[n_levels]; component mux[n_levels]; signal levelHashes[n_levels + 1]; levelHashes[0] <== leaf; ... root <== levelHashes[n_levels];\n} Here we have three inputs: leaf, path_index, and path_elements. path_index is the position of the leaf represented in binary. We need the binary representation of the position in the Merkle tree to understand the hashing path from the leaf to the root (more on that \"3. Recursive Incremental Merkle Tree Algorithm, page 4\" ). path_elements are sibling leaves that are part of Merkle Proof. leaf = Poseidon(identity_secret), so it's just identity commitment . There is a Merkle Tree hashing algorithm in the omitted part, no more than that.","breadcrumbs":"Overview » Under the hood » Circuits » Merkle Tree circuit","id":"15","title":"Merkle Tree circuit"},"16":{"body":"RLN circuit is the implementation of RLN logic itself (which in turn uses the Merkle Tree gadget). You can find the implementation here . So, let's start with helper gadgets: template CalculateIdentityCommitment() { signal input identity_secret; signal output out; component hasher = Poseidon(1); hasher.inputs[0] <== identity_secret; out <== hasher.out;\n} template CalculateA1() { signal input a_0; signal input epoch; signal output out; component hasher = Poseidon(2); hasher.inputs[0] <== a_0; hasher.inputs[1] <== epoch; out <== hasher.out;\n} template CalculateNullifier() { signal input a_1; signal input rln_identifier; signal output out; component hasher = Poseidon(2); hasher.inputs[0] <== a_1; hasher.inputs[1] <== rln_identifier; out <== hasher.out;\n} It's easy to understand these samples: CalculateIdentityCommitment() is used to calculate the identity commitment. It takes secret and outputs the commitment. CalculateA1() and CalculateNullifier() are used to calculate a_1 and nullifier (internal nullifier); they are implemented as it's described in previous topic . Now, let's look at the core logic of the RLN circuit. ... signal input identity_secret; signal input path_elements[n_levels][LEAVES_PER_PATH_LEVEL]; signal input identity_path_index[n_levels]; signal input x; signal input epoch; signal input rln_identifier; signal output y; signal output root; signal output nullifier; ... So, here we have many inputs. Private inputs are: identity_secret (basically a_0 from the polynomial), path_elements[][], identity_path_index[]. Public inputs are: x (actually just the hash of a signal), epoch, rln_identifier. Outputs are: y' (share of the secret), rootof a Merkle Tree, andnullifier.` RLN circuit consists of two checks: Membership in Merkle Tree Correctness of secret share","breadcrumbs":"Overview » Under the hood » Circuits » RLN core","id":"16","title":"RLN core"},"17":{"body":"To check membership in a Merkle Tree, we can simply use the previously described Merkle Tree gadget: ... component identity_commitment = CalculateIdentityCommitment(); identity_commitment.identity_secret <== identity_secret; var i; var j; component inclusionProof = MerkleTreeInclusionProof(n_levels); inclusionProof.leaf <== identity_commitment.out; for (i = 0; i < n_levels; i++) { for (j = 0; j < LEAVES_PER_PATH_LEVEL; j++) { inclusionProof.path_elements[i][j] <== path_elements[i][j]; } inclusionProof.path_index[i] <== identity_path_index[i]; } ... Here we are calculating the identity_commitment and passing it along with sibling leaves and binary representation of the position to a Merkle Tree gadget. It gives us the calculated root as an output, and we can put the constraint on that: root <== inclusionProof.root;","breadcrumbs":"Overview » Under the hood » Circuits » Membership in Merkle Tree","id":"17","title":"Membership in Merkle Tree"},"18":{"body":"As we use linear polynomial we need to check that y = a_1 * x + a_0 (a_0 is identity secret). For that, we need these constraints: ... component a_1 = CalculateA1(); a_1.a_0 <== identity_secret; a_1.epoch <== epoch; y <== identity_secret + a_1.out * x; ... To calculate and reveal the nullifier: ... component calculateNullifier = CalculateNullifier(); calculateNullifier.a_1 <== a_1.out; calculateNullifier.rln_identifier <== rln_identifier; nullifier <== calculateNullifier.out; ...","breadcrumbs":"Overview » Under the hood » Circuits » Correctness of secret share","id":"18","title":"Correctness of secret share"},"19":{"body":"Now the Circuits can be used as gadgets. If we want to use it in our app, we need to initialize it and have a main - starting point function. It can be found here . The implementation is super basic: pragma circom 2.0.0; include \"./rln-base.circom\"; component main {public [x, epoch, rln_identifier ]} = RLN(15); That's the whole RLN Circom Circuit :) Here we just need to list all public inputs (x, epoch, rln_identifier; the rest of the inputs are private). Also, we set the depth of the Merkle Tree = 15 (max of 32768 members).","breadcrumbs":"Overview » Under the hood » Circuits » Main runner of the circuits","id":"19","title":"Main runner of the circuits"},"2":{"body":"This topic is a part of complete overview by Blagoj . RLN is a zero-knowledge gadget that enables spam prevention for decentralized, anonymous environments. The anonymity property opens up the possibility for spam and Sybil attack vectors for certain applications, 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. RLN helps us identify and \"kick\" the spammer. Moreover, RLN 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).","breadcrumbs":"Overview » What is RLN » What is Rate-Limiting Nullifier?","id":"2","title":"What is Rate-Limiting Nullifier?"},"20":{"body":"This section contains list of apps that use RLN : zk-chat - A spam resistant instant messaging application for private and anonymous communication rln-chat-app - PoC app, created using rln-js waku-rln-relay - Extension of waku-relay (spam protection with RLN )","breadcrumbs":"Overview » Uses » Uses","id":"20","title":"Uses"},"21":{"body":"This section provides information on how to use RLN in your project: JavaScript RLN (for rln-js ) Rust RLN (for zerokit-rln )","breadcrumbs":"How to use » How to use","id":"21","title":"How to use"},"22":{"body":"This section provides theoretical information that underpins RLN . Here we'll discuss: Shamir's Secret Sharing","breadcrumbs":"Theory » Theory","id":"22","title":"Theory"},"23":{"body":"Shamirs Secret Sharing allows to split the secret to n parts and restore it upon presentation any m parts (m <= n) Sharmir's Secret Sharing wikipedia is a good reference to understand the concept. Reconstruction 1: https://github.com/akinovak/semaphore-lib/blob/5b9bb3210192c8e508eced7ef6579fd56e635ed0/src/rln.ts#L31 retrievePrivateKey(x1: bigint, x2:bigint, y1:bigint, y2:bigint): Buffer | ArrayBuffer { const slope = Fq.div(Fq.sub(y2, y1), Fq.sub(x2, x1)) const privateKey = Fq.sub(y1, Fq.mul(slope, x1)); return bigintConversion.bigintToBuf(Fq.normalize(privateKey)); } Reconstruction 2: https://github.com/akinovak/semaphore-lib/blob/rln_signature_changes/test/index.ts#L250 async function testRlnSlashingSimulation() { RLN.setHasher('poseidon'); const identity = RLN.genIdentity(); const privateKey = identity.keypair.privKey; const leafIndex = 3; const idCommitments: Array = []; for (let i=0; i hasher.inputs[0]; right ==> hasher.inputs[1]; hash <== hasher.out;\n} These are helper gadgets to make the code more clean. Poseidon gadget is implemented with the ability to take a different number of arguments. We use PoseidonHashT3() to initialize it like a function with two arguments. And HashLeftRight use PoseidonHashT3 in a more \"readable\" way: it takes two inputs, left and right, and outputs the result of the calculation. Next comes the core of the Merkle Tree gadget: template MerkleTreeInclusionProof(n_levels) { signal input leaf; signal input path_index[n_levels]; signal input path_elements[n_levels][1]; signal output root; component hashers[n_levels]; component mux[n_levels]; signal levelHashes[n_levels + 1]; levelHashes[0] <== leaf; ... root <== levelHashes[n_levels];\n} Here we have three inputs: leaf, path_index, and path_elements. path_index is the position of the leaf represented in binary. We need the binary representation of the position in the Merkle tree to understand the hashing path from the leaf to the root (more on that \"3. Recursive Incremental Merkle Tree Algorithm, page 4\" ). path_elements are sibling leaves that are part of Merkle Proof. leaf = Poseidon(identity_secret), so it's just identity commitment . There is a Merkle Tree hashing algorithm in the omitted part, no more than that.","breadcrumbs":"Overview » Under the hood » Circuits » Merkle Tree circuit","id":"15","title":"Merkle Tree circuit"},"16":{"body":"RLN circuit is the implementation of RLN logic itself (which in turn uses the Merkle Tree gadget). You can find the implementation here . So, let's start with helper gadgets: template CalculateIdentityCommitment() { signal input identity_secret; signal output out; component hasher = Poseidon(1); hasher.inputs[0] <== identity_secret; out <== hasher.out;\n} template CalculateA1() { signal input a_0; signal input epoch; signal output out; component hasher = Poseidon(2); hasher.inputs[0] <== a_0; hasher.inputs[1] <== epoch; out <== hasher.out;\n} template CalculateNullifier() { signal input a_1; signal input rln_identifier; signal output out; component hasher = Poseidon(2); hasher.inputs[0] <== a_1; hasher.inputs[1] <== rln_identifier; out <== hasher.out;\n} It's easy to understand these samples: CalculateIdentityCommitment() is used to calculate the identity commitment. It takes secret and outputs the commitment. CalculateA1() and CalculateNullifier() are used to calculate a_1 and nullifier (internal nullifier); they are implemented as it's described in previous topic . Now, let's look at the core logic of the RLN circuit. ... signal input identity_secret; signal input path_elements[n_levels][LEAVES_PER_PATH_LEVEL]; signal input identity_path_index[n_levels]; signal input x; signal input epoch; signal input rln_identifier; signal output y; signal output root; signal output nullifier; ... So, here we have many inputs. Private inputs are: identity_secret (basically a_0 from the polynomial), path_elements[][], identity_path_index[]. Public inputs are: x (actually just the hash of a signal), epoch, rln_identifier. Outputs are: y' (share of the secret), rootof a Merkle Tree, andnullifier.` RLN circuit consists of two checks: Membership in Merkle Tree Correctness of secret share","breadcrumbs":"Overview » Under the hood » Circuits » RLN core","id":"16","title":"RLN core"},"17":{"body":"To check membership in a Merkle Tree, we can simply use the previously described Merkle Tree gadget: ... component identity_commitment = CalculateIdentityCommitment(); identity_commitment.identity_secret <== identity_secret; var i; var j; component inclusionProof = MerkleTreeInclusionProof(n_levels); inclusionProof.leaf <== identity_commitment.out; for (i = 0; i < n_levels; i++) { for (j = 0; j < LEAVES_PER_PATH_LEVEL; j++) { inclusionProof.path_elements[i][j] <== path_elements[i][j]; } inclusionProof.path_index[i] <== identity_path_index[i]; } ... Here we are calculating the identity_commitment and passing it along with sibling leaves and binary representation of the position to a Merkle Tree gadget. It gives us the calculated root as an output, and we can put the constraint on that: root <== inclusionProof.root;","breadcrumbs":"Overview » Under the hood » Circuits » Membership in Merkle Tree","id":"17","title":"Membership in Merkle Tree"},"18":{"body":"As we use linear polynomial we need to check that y = a_1 * x + a_0 (a_0 is identity secret). For that, we need these constraints: ... component a_1 = CalculateA1(); a_1.a_0 <== identity_secret; a_1.epoch <== epoch; y <== identity_secret + a_1.out * x; ... To calculate and reveal the nullifier: ... component calculateNullifier = CalculateNullifier(); calculateNullifier.a_1 <== a_1.out; calculateNullifier.rln_identifier <== rln_identifier; nullifier <== calculateNullifier.out; ...","breadcrumbs":"Overview » Under the hood » Circuits » Correctness of secret share","id":"18","title":"Correctness of secret share"},"19":{"body":"Now the Circuits can be used as gadgets. If we want to use it in our app, we need to initialize it and have a main - starting point function. It can be found here . The implementation is super basic: pragma circom 2.0.0; include \"./rln-base.circom\"; component main {public [x, epoch, rln_identifier ]} = RLN(15); That's the whole RLN Circom Circuit :) Here we just need to list all public inputs (x, epoch, rln_identifier; the rest of the inputs are private). Also, we set the depth of the Merkle Tree = 15 (max of 32768 members).","breadcrumbs":"Overview » Under the hood » Circuits » Main runner of the circuits","id":"19","title":"Main runner of the circuits"},"2":{"body":"This topic is a part of complete overview by Blagoj . RLN is a zero-knowledge gadget that enables spam prevention for decentralized, anonymous environments. The anonymity property opens up the possibility for spam and Sybil attack vectors for certain applications, 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. RLN helps us identify and \"kick\" the spammer. Moreover, RLN 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).","breadcrumbs":"Overview » What is RLN » What is Rate-Limiting Nullifier?","id":"2","title":"What is Rate-Limiting Nullifier?"},"20":{"body":"This section contains list of apps that use RLN : zk-chat - A spam resistant instant messaging application for private and anonymous communication rln-chat-app - PoC app, created using rln-js waku-rln-relay - Extension of waku-relay (spam protection with RLN )","breadcrumbs":"Overview » Uses » Uses","id":"20","title":"Uses"},"21":{"body":"This section provides information on how to use RLN in your project: JavaScript RLN (for rln-js ) Rust RLN (for zerokit-rln )","breadcrumbs":"How to use » How to use","id":"21","title":"How to use"},"22":{"body":"This section provides theoretical information that underpins RLN . Here we'll discuss: Shamir's Secret Sharing","breadcrumbs":"Theory » Theory","id":"22","title":"Theory"},"23":{"body":"Shamirs Secret Sharing allows to split the secret to n parts and restore it upon presentation any m parts (m <= n) Sharmir's Secret Sharing wikipedia is a good reference to understand the concept. Reconstruction 1: https://github.com/akinovak/semaphore-lib/blob/5b9bb3210192c8e508eced7ef6579fd56e635ed0/src/rln.ts#L31 retrievePrivateKey(x1: bigint, x2:bigint, y1:bigint, y2:bigint): Buffer | ArrayBuffer { const slope = Fq.div(Fq.sub(y2, y1), Fq.sub(x2, x1)) const privateKey = Fq.sub(y1, Fq.mul(slope, x1)); return bigintConversion.bigintToBuf(Fq.normalize(privateKey)); } Reconstruction 2: https://github.com/akinovak/semaphore-lib/blob/rln_signature_changes/test/index.ts#L250 async function testRlnSlashingSimulation() { RLN.setHasher('poseidon'); const identity = RLN.genIdentity(); const privateKey = identity.keypair.privKey; const leafIndex = 3; const idCommitments: Array = []; for (let i=0; i