Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP DO NOT MERGE] ZK Github #45

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ circuits/*_js/*
*.sym
circuits/*.wasm
circuits/*.wtns
src/circuits/*.wasm
# src/circuits/*.wasm
*.ptau
*.zkey
# *.zkey
circuits/inputs/*.json

.pnp.*
Expand All @@ -45,7 +45,7 @@ circuits/inputs/*.json

# Added by yush
out/
circuits/circuit*zkey*
# circuits/circuit*zkey*
circuits/email/
cache/
test.log
Expand Down
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,56 @@
# ZK Github using zkEmail
Enable github proof using zk-email

Presentation: https://www.loom.com/share/4a280711e0944cecbe680149cf4de02b?sid=d1247bf1-d78c-4295-81be-832f9ceaa8b8

[UPDATE] Pause work on this repo until finishing https://github.com/JernKunpittaya/full_zk_regex to generalize any zk_regex matching for every application

All backend main features are done, feel free to test by
-delete yarn.lock and then call yarn
-create github.eml into src folder, and paste original pull request merge confirmation email into it.
-run generate_input.ts
-install poweroftau
-run scripts in dizkus-scripts folder

Frontend is almost the same as scripts, but just use fullProve. Not put zkey stuffs on aws yet, will push production frontend soon :)

Note that there are still tons of things to improve on backend to be more optimized and generalized.

This doc (and the code comment) are also needed to be written and refined.



## What is zkRepo?
zkRepo is a website where people can create a proof that they contributed to a certain github repo without revealing who they are. In this demo, we show the use case of a DAO that wants to reward github contributors who contribute to their repo. Hence, a github contributor can use zkRepo to create a proof and get verified to claim reward without revealing who they are, hence not being able to be tracked and associated with his github background/history.
This anonymous property doesn’t only serve as a “good to have” privacy but can also serve as a critical factor in how a person lives their developer life as seen in more use cases below.


## Why contribute to a Github project anonymously?

- Government - you may live in a country/region where the government tracks your contributions online
- Career - you may be contributing to a project that your employer may not approve of
- Security - you may want to contribute to a project that implies financial interest
- Prove a point - you may want to write something controversial to start a discussion.
- Privacy - You just value your privacy.

In the future, we can have a more fine-grained anonymity. For example, we can Categorize severity of bug by pull tag number and award the anonymous claimer in that category accordingly. Interestingly, we can also make it possible to claim their merit without revealing contributions e.g. I contributed to 100 githubs with each 100k+ stars.


## Innovation
- We’re hugely inspired by zkEmail (https://github.com/zk-email-verify/zk-email-verify) in being able to use email to prove that you have a certain twitter handler. Here are our innovations.
- Optimized string matching. Instead of using Regex state machine to parse and match all strings which is very expensive because we also parse plaintext into the state machine, we make sure to match the plaintext using string comparison first, and precisely identify the point of string that is necessary to use Regex state machine, and just start from that, saving a lot of constraint.
- Generalized matching group. Instead of being able to reveal a certain arbitrary group of Regex, we make it possible to create a generalized circom circuit that allows it to extract any combination of groups all at once. For example, a string of github repo (username/reponame/pull/1/ …) can be easily revealed only as username/repo
- Multiple matches. Instead of matching only the part where there is an arbitrary regex, we wrote a framework that makes it easy to match plaintext in addition as well. For example, we make sure to match “merge” and reveal username/repo which are in different chunks.


## Upcoming Innovation
- Automated DFA locator. Right now, we need to manually see the state machine that we generate and see where the state we want to extract from. We will automate this process.
- etc.




# Legacy
# ZK Email Verify

**WIP: This tech is extremely tricky to use and very much a work in progress, and we do not recommend use in any production application right now. This is both due to unaudited code, and several theoretical issues such as nullifiers, bcc’s, non-nested signatures, and hash sizings. These are all resolved for our Twitter MVP usecase, but may not be generally gauranteed. If you have a possible usecase, please run it by us so we can ensure that your guarantees are in fact correct!**
Expand Down
1 change: 1 addition & 0 deletions circom
Submodule circom added at ca3345
54 changes: 54 additions & 0 deletions circuits/check_merge.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
include "./regex_helpers.circom";

pragma circom 2.0.3;

include "./gates.circom";

template StrCmp(size) {
signal input a[size];
signal input b[size];
signal output res;

component m = MultiAND(size);
for (var i = 0; i < size; i++) {
// a[i] \ 256 === 0;
// b[i] \ 256 === 0;
// if a[i] == b[i] then input 1
m.in[i] <== 1 - (a[i] - b[i]);
}

// if all a == b then input 1
res <== m.out;
}

template SubStrFixed(str_size, substr_size) {
signal input str[str_size];
signal input substr[substr_size];
signal output res;

var N = str_size - substr_size;

component m = MultiAND(N);
component cmps[N];
for (var i = 0; i < N; i++) {
cmps[i] = StrCmp(substr_size);
for (var j = 0; j < substr_size; j++) {
cmps[i].a[j] <== str[i + j];
cmps[i].b[j] <== substr[j];
}
// if substr != str[i:i+j] then output 1
m.in[i] <== 1 - cmps[i].res;
}

// if substr not in str at all, then output 0
res <== 1 - m.out;
}

component main { public [ str, substr ] } = SubStrFixed(32, 5);
template MergeRegex (msg_bytes) {
signal input msg[msg_bytes];
signal input match_idx;
signal output start_idx;
signal output start_idx0;
signal output group_match_count;
signal output entire_count;
121 changes: 72 additions & 49 deletions circuits/email.circom
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ include "./sha.circom";
include "./rsa.circom";
include "./dkim_header_regex.circom";
include "./body_hash_regex.circom";
include "./twitter_reset_regex.circom";
include "./github_regex.circom";
include "./base64.circom";

template EmailVerify(max_header_bytes, max_body_bytes, n, k) {
Expand All @@ -21,16 +21,17 @@ template EmailVerify(max_header_bytes, max_body_bytes, n, k) {
// This body is only the part we care about, a significant prefix of the body has been pre-hashed into precomputed_sha.
signal input in_body_padded[max_body_bytes];
signal input in_body_len_padded_bytes;

signal reveal[max_header_bytes]; // bytes to reveal
signal reveal_packed[max_packed_bytes]; // packed into 7-bytes. TODO: make this rotate to take up even less space

var max_twitter_len = 21;
var max_twitter_packed_bytes = (max_twitter_len - 1) \ 7 + 1; // ceil(max_num_bytes / 7)

signal input twitter_username_idx;
signal reveal_twitter[max_twitter_len][max_body_bytes];
signal output reveal_twitter_packed[max_twitter_packed_bytes];
// small repo
var max_github_len =11;
var max_github_packed_bytes = (max_github_len - 1) \ 7 + 1; // ceil(max_num_bytes / 7)
//small repo 11+7
signal input github_body[18];
//optimized
signal reveal_github[max_github_len][max_github_len+2];
signal output reveal_github_packed[max_github_packed_bytes];

signal input address;
signal input address_plus_one;
Expand Down Expand Up @@ -77,19 +78,20 @@ template EmailVerify(max_header_bytes, max_body_bytes, n, k) {
for (var i = 0; i < max_header_bytes; i++) {
dkim_header_regex.msg[i] <== in_padded[i];
}
dkim_header_regex.out === 2;
log("dkim_header_match: ",dkim_header_regex.out);
dkim_header_regex.out === 1;
for (var i = 0; i < max_header_bytes; i++) {
reveal[i] <== dkim_header_regex.reveal[i+1];
}
log(dkim_header_regex.out);
log("dkim_header_out: ",dkim_header_regex.out);

// BODY HASH REGEX: 617,597 constraints
component body_hash_regex = BodyHashRegex(max_header_bytes);
for (var i = 0; i < max_header_bytes; i++) {
body_hash_regex.msg[i] <== in_padded[i];
}
log("body_hash_regex_out: ",body_hash_regex.out);
body_hash_regex.out === 1;
log(body_hash_regex.out);
component body_hash_eq[max_header_bytes];
for (var i = 0; i < max_header_bytes; i++) {
body_hash_eq[i] = IsEqual();
Expand Down Expand Up @@ -125,57 +127,78 @@ template EmailVerify(max_header_bytes, max_body_bytes, n, k) {
sha_body_bytes[i].out === sha_b64.out[i];
}

// TWITTER REGEX: 328,044 constraints
// Github REGEX: 328,044 constraints
// This computes the regex states on each character
component twitter_regex = TwitterResetRegex(max_body_bytes);
for (var i = 0; i < max_body_bytes; i++) {
twitter_regex.msg[i] <== in_body_padded[i];
}
// msg_bytes --> from >&lt;Jern/repo1 -->
component github_regex = RegexWithPlain(max_github_len+6, max_github_len, 4);
//optimized
for (var i = 0; i < max_github_len+6; i++) {
github_regex.msg[i] <== github_body[i];
}

github_regex.substr[0] <== 62;
github_regex.substr[1] <== 38;
github_regex.substr[2] <== 108;
github_regex.substr[3] <== 116;
// github_regex.match_idx<==0;
// This ensures we found a match at least once
component found_twitter = IsZero();
found_twitter.in <== twitter_regex.out;
found_twitter.out === 0;
log(twitter_regex.out);
// We isolate where the username begins: twitter_eq there is 1, everywhere else is 0
component twitter_eq[max_body_bytes];
for (var i = 0; i < max_body_bytes; i++) {
twitter_eq[i] = IsEqual();
twitter_eq[i].in[0] <== i;
twitter_eq[i].in[1] <== twitter_username_idx;
}
for (var j = 0; j < max_twitter_len; j++) {
// This vector is 0 everywhere except at one value
// [x][x] is the starting character of the twitter username
reveal_twitter[j][j] <== twitter_eq[j].out * twitter_regex.reveal[j];
for (var i = j + 1; i < max_body_bytes; i++) {
// This shifts the username back to the start of the string. For example,
// [0][k0] = y, where k0 >= twitter_username_idx + 0
// [1][k1] = u, where k1 >= twitter_username_idx + 1
// [2][k2] = s, where k2 >= twitter_username_idx + 2
// [3][k3] = h, where k3 >= twitter_username_idx + 3
// [4][k4] = _, where k4 >= twitter_username_idx + 4
// [5][k5] = g, where k5 >= twitter_username_idx + 5
reveal_twitter[j][i] <== reveal_twitter[j][i - 1] + twitter_eq[i-j].out * twitter_regex.reveal[i];
component found_github = IsZero();
found_github.in <== github_regex.entire_count;
log("github entire count: ",github_regex.entire_count);
found_github.out === 0;

for (var j =0; j<max_github_len;j++){
for (var i =j; i< max_github_len+2;i++){
reveal_github[j][i] <== github_regex.reveal_shifted_intermediate[j][i];
}
}

// check optimized
component checkInclusive = SubStrFixed(max_body_bytes, 18);
for (var i =0; i<max_body_bytes;i++){
checkInclusive.str[i] <== in_body_padded[i];
}
for (var i =0; i<18;i++){
checkInclusive.substr[i] <== github_body[i];
}
checkInclusive.res === 1;


component checkMerge = SubStrFixed(max_body_bytes, 9);
for (var i =0; i<max_body_bytes;i++){
checkMerge.str[i] <== in_body_padded[i];
}
//>Merged <",
checkMerge.substr[0] <== 62;
checkMerge.substr[1] <== 77;
checkMerge.substr[2] <== 101;
checkMerge.substr[3] <== 114;
checkMerge.substr[4] <== 103;
checkMerge.substr[5] <== 101;
checkMerge.substr[6] <== 100;
checkMerge.substr[7] <== 32;
checkMerge.substr[8] <== 60;

checkMerge.res === 1;
// PACKING: 16,800 constraints (Total: 3,115,057)
// Pack output for solidity verifier to be < 24kb size limit
// chunks = 7 is the number of bytes that can fit into a 255ish bit signal
var chunks = 7;
component packed_twitter_output[max_twitter_packed_bytes];
for (var i = 0; i < max_twitter_packed_bytes; i++) {
packed_twitter_output[i] = Bytes2Packed(chunks);
component packed_github_output[max_github_packed_bytes];
for (var i = 0; i < max_github_packed_bytes; i++) {
packed_github_output[i] = Bytes2Packed(chunks);
for (var j = 0; j < chunks; j++) {
var reveal_idx = i * chunks + j;
if (reveal_idx < max_body_bytes) {
packed_twitter_output[i].in[j] <== reveal_twitter[i * chunks + j][max_body_bytes - 1];
//optimized
if (reveal_idx < max_github_len) {
//optimized
packed_github_output[i].in[j] <== reveal_github[i * chunks + j][max_github_len + 1];
} else {
packed_twitter_output[i].in[j] <== 0;
packed_github_output[i].in[j] <== 0;
}
}
reveal_twitter_packed[i] <== packed_twitter_output[i].out;
log(reveal_twitter_packed[i]);
reveal_github_packed[i] <== packed_github_output[i].out;
log("reveal_github_packed: ",reveal_github_packed[i]);
}

component packed_output[max_packed_bytes];
Expand All @@ -196,4 +219,4 @@ template EmailVerify(max_header_bytes, max_body_bytes, n, k) {
// In circom, all output signals of the main component are public (and cannot be made private), the input signals of the main component are private if not stated otherwise using the keyword public as above. The rest of signals are all private and cannot be made public.
// This makes modulus and reveal_twitter_packed public. hash(signature) can optionally be made public, but is not recommended since it allows the mailserver to trace who the offender is.

component main { public [ modulus, address ] } = EmailVerify(1024, 1536, 121, 17);
component main { public [ modulus, address ] } = EmailVerify(1536, 2048, 121, 17);
Loading