From c0e6ae6047d4ef1d1ad1de8282c4d153524a2821 Mon Sep 17 00:00:00 2001 From: Nathan Xiong Date: Thu, 30 Mar 2023 18:07:17 +0700 Subject: [PATCH 01/25] adding files --- README.md | 36 +- circuits/airbnb_kyc_regex.circom | 287 +++++++++ circuits/base64.circom | 14 +- circuits/coinbase_kyc_regex.circom | 195 ++++++ circuits/email.circom | 7 +- circuits/email_airbnb.circom | 158 +++++ circuits/email_both.circom | 110 ++++ circuits/email_coinbase.circom | 158 +++++ circuits/extract_to_email_regex.circom | 701 ++++++++++++++++++++++ circuits/variable_subarray.circom | 79 +++ dizkus-scripts/1_compile_airbnb.sh | 18 + dizkus-scripts/1_compile_coinbase.sh | 18 + dizkus-scripts/1_compile_kyc.sh | 18 + dizkus-scripts/2_gen_wtns_airbnb.sh | 13 + dizkus-scripts/2_gen_wtns_coinbase.sh | 13 + dizkus-scripts/2_gen_wtns_kyc.sh | 13 + package.json | 6 +- regex_to_circom/README.md | 7 - regex_to_circom/gen.py | 34 +- regex_to_circom/halo2_regex_lookup.txt | 24 + regex_to_circom/lexical.js | 67 +-- src/contracts/.env.example | 2 - src/contracts/README.md | 24 +- src/contracts/foundry.toml | 1 + src/contracts/src/NFTSVG.sol | 11 +- src/contracts/src/test/TestTwitter.t.sol | 14 +- src/contracts/src/twitterEmailHandler.sol | 4 - src/helpers/constants.ts | 2 + src/helpers/dkim/tools.js | 15 +- src/pages/MainPage.tsx | 15 +- src/scripts/generate_input.ts | 15 +- src/scripts/generate_two_inputs.ts | 298 +++++++++ temp.py | 4 + yarn.lock | 114 ++-- 34 files changed, 2235 insertions(+), 260 deletions(-) create mode 100644 circuits/airbnb_kyc_regex.circom create mode 100644 circuits/coinbase_kyc_regex.circom create mode 100644 circuits/email_airbnb.circom create mode 100644 circuits/email_both.circom create mode 100644 circuits/email_coinbase.circom create mode 100644 circuits/extract_to_email_regex.circom create mode 100644 circuits/variable_subarray.circom create mode 100755 dizkus-scripts/1_compile_airbnb.sh create mode 100755 dizkus-scripts/1_compile_coinbase.sh create mode 100755 dizkus-scripts/1_compile_kyc.sh create mode 100755 dizkus-scripts/2_gen_wtns_airbnb.sh create mode 100755 dizkus-scripts/2_gen_wtns_coinbase.sh create mode 100755 dizkus-scripts/2_gen_wtns_kyc.sh create mode 100644 regex_to_circom/halo2_regex_lookup.txt delete mode 100644 src/contracts/.env.example create mode 100644 src/scripts/generate_two_inputs.ts create mode 100644 temp.py diff --git a/README.md b/README.md index dac300b7d..8f4b7768c 100644 --- a/README.md +++ b/README.md @@ -274,7 +274,7 @@ The full email header and body check circuit, with 7-byte packing and final publ In the browser, on a 2019 Intel Mac on Chrome, proving uses 7.3/8 cores. zk-gen takes 384 s, groth16 prove takes 375 s, and witness calculation takes 9 s. -For baremetal, proof generation time on 16 CPUs took 97 seconds. Generating zkey 0 took 17 minutes. Unclear about zkey 1. Zkey 2 took 5 minutes. r1cs + wasm generation took 5 minutes. Witness generation took 16 seconds. cpp generation of witness gen file (from script 6) took 210 minutes -- we do not run this pathway anymore. +For baremetal, proof generation time on 16 CPUs took 97 seconds. Generating zkey 0 took 17 minutes. Unclear about zkey 1. Zkey 2 took 5 minutes. r1cs + wasm generation took 5 minutes. Witness generation took 16 seconds. cpp witness gen file generation (from script 6) took 210 minutes. ### Scrubbing Sensitive Files @@ -314,26 +314,12 @@ const word_char = '(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D| let regex = `email was meant for @${word_char}+`; ``` -To understand these better, use https://cyberzhg.github.io/toolbox/ and use the 3 regex tools for visualization of the min-DFA state. - ## FAQ/Possible Errors -### Can you provide an example header for me to understand what exactly is signed? - -We are hijacking DKIM signatures in order to verify parts of emails, which can be verified on chain via succinct zero knowledge proofs. Here is an example of the final, canoncalized actual header string that is signed by google.com's public key: - -`to:"zkemailverify@gmail.com" \r\nsubject:test email\r\nmessage-id:\r\ndate:Fri, 24 Mar 2023 13:02:10 +0700\r\nfrom:ZK Email \r\nmime-version:1.0\r\ndkim-signature:v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; t=1679637743; h=to:subject:message-id:date:from:mime-version:from:to:cc:subject :date:message-id:reply-to; bh=gCRK/FdzAYnMHic55yb00uF8AHZ/3HvyLVQJbWQ2T8o=; b=` - -Thus, we can extract whatever information we want out of here via regex, including to/from/body hash! We can do the same for an email body. - ### I'm having issues with the intricacies of the SHA hashing. How do I understand the function better? Use https://sha256algorithm.com/ as an explainer! It's a great visualization of what is going on, and our code should match what is going on there. -### I'm having trouble with regex or base64 understanding. How do I understand that better? - -Use https://cyberzhg.github.io/toolbox/ to experiement with conversions to/from base64 and to/from DFAs and NFAs. - ### What are the differences between generating proofs (snarkjs.groth16.fullprove) on the client vs. on a server? If the server is generating the proof, it has to have the private input. We want people to own their own data, so client side proving is the most secure both privacy and anonymity wise. There are fancier solutions (MPC, FHE, recursive proofs etc), but those are still in the research stage. @@ -353,21 +339,7 @@ const = result.results[0].publicKey.toString(); TypeError: Cannot read properties of undefined (reading 'toString') ``` -You need to have internet connection while running dkim verification locally, in order to fetch the public key. If you have internet connection, make sure you downloaded the email with the headers: you should see a DKIM section in the file. DKIM verifiction may also fail after the public keys rotate, though this has not been confirmed. - -### How do I lookup the RSA pubkey for a domain? - -Use [easydmarc.com/tools/dkim-lookup?domain=twitter.com](https://easydmarc.com/tools/dkim-lookup?domain=twitter.com). - -### DKIM parsing/public key errors with generate_input.ts - -``` -Writing to file... -/Users/aayushgupta/Documents/.projects.nosync/zk-email-verify/src/scripts/generate_input.ts:190 - throw new Error(`No public key found on generate_inputs result ${JSON.stringify(result)}`); -``` - -Depending on the "info" error at the end of the email, you probably need to go through src/helpers/dkim/\*.js and replace some ".replace" functions with ".replaceAll" instead (likely tools.js), and also potentially strip some quotes. +You need to have internet connection while running dkim verification locally, in order to fetch the public key. If you have internet connection, make sure you downloaded the email with the headers: you should see a DKIM section in the file. ### No available storage method found. @@ -407,9 +379,9 @@ Apologies, this part is some messy legacy code from previous projects. You use v zkp.ts is the key file that calls the important proving functions. You should be able to just call the exported functions from there, along with setting up your own s3 bucket and setting the constants at the top. -### What is the licensing on this technology? +### Why did you choose GPL over MIT licensing? -Everything we write is MIT licensed. Note that circom and circomlib is GPL. Broadly we are pro permissive open source usage with attribution! We hope that those who derive profit from this, contribute that money altruistically back to this technology and open source public good. +Since circom is GPL, we are forced to use the GPL license, which is still a highly permissive license. You can dm us if you'd like to treat non-circom parts of the repo as MIT licensed, but broadly we are pro permissive open source usage with attribution! We hope that those who derive profit from this primitive contribute that money altruistically back to this technology. ## To-Do diff --git a/circuits/airbnb_kyc_regex.circom b/circuits/airbnb_kyc_regex.circom new file mode 100644 index 000000000..84e8af056 --- /dev/null +++ b/circuits/airbnb_kyc_regex.circom @@ -0,0 +1,287 @@ +pragma circom 2.0.3; + +include "./regex_helpers.circom"; + +template AirbnbKYCRegex (msg_bytes) { + signal input msg[msg_bytes]; + signal output out; + + var num_bytes = msg_bytes; + signal in[num_bytes]; + for (var i = 0; i < msg_bytes; i++) { + in[i] <== msg[i]; + } + + component eq[36][num_bytes]; + component and[36][num_bytes]; + signal states[num_bytes+1][37]; + + for (var i = 0; i < num_bytes; i++) { + states[i][0] <== 1; + } + for (var i = 1; i < 37; i++) { + states[0][i] <== 0; + } + + for (var i = 0; i < num_bytes; i++) { + eq[0][i] = IsEqual(); + eq[0][i].in[0] <== in[i]; + eq[0][i].in[1] <== 118; + and[0][i] = AND(); + and[0][i].a <== states[i][36]; + and[0][i].b <== eq[0][i].out; + states[i+1][1] <== and[0][i].out; + eq[1][i] = IsEqual(); + eq[1][i].in[0] <== in[i]; + eq[1][i].in[1] <== 101; + and[1][i] = AND(); + and[1][i].a <== states[i][1]; + and[1][i].b <== eq[1][i].out; + states[i+1][2] <== and[1][i].out; + eq[2][i] = IsEqual(); + eq[2][i].in[0] <== in[i]; + eq[2][i].in[1] <== 114; + and[2][i] = AND(); + and[2][i].a <== states[i][2]; + and[2][i].b <== eq[2][i].out; + states[i+1][3] <== and[2][i].out; + eq[3][i] = IsEqual(); + eq[3][i].in[0] <== in[i]; + eq[3][i].in[1] <== 110; + and[3][i] = AND(); + and[3][i].a <== states[i][3]; + and[3][i].b <== eq[3][i].out; + states[i+1][4] <== and[3][i].out; + eq[4][i] = IsEqual(); + eq[4][i].in[0] <== in[i]; + eq[4][i].in[1] <== 109; + and[4][i] = AND(); + and[4][i].a <== states[i][4]; + and[4][i].b <== eq[4][i].out; + states[i+1][5] <== and[4][i].out; + eq[5][i] = IsEqual(); + eq[5][i].in[0] <== in[i]; + eq[5][i].in[1] <== 101; + and[5][i] = AND(); + and[5][i].a <== states[i][5]; + and[5][i].b <== eq[5][i].out; + states[i+1][6] <== and[5][i].out; + eq[6][i] = IsEqual(); + eq[6][i].in[0] <== in[i]; + eq[6][i].in[1] <== 110; + and[6][i] = AND(); + and[6][i].a <== states[i][6]; + and[6][i].b <== eq[6][i].out; + states[i+1][7] <== and[6][i].out; + eq[7][i] = IsEqual(); + eq[7][i].in[0] <== in[i]; + eq[7][i].in[1] <== 116; + and[7][i] = AND(); + and[7][i].a <== states[i][7]; + and[7][i].b <== eq[7][i].out; + states[i+1][8] <== and[7][i].out; + eq[8][i] = IsEqual(); + eq[8][i].in[0] <== in[i]; + eq[8][i].in[1] <== 32; + and[8][i] = AND(); + and[8][i].a <== states[i][8]; + and[8][i].b <== eq[8][i].out; + states[i+1][9] <== and[8][i].out; + eq[9][i] = IsEqual(); + eq[9][i].in[0] <== in[i]; + eq[9][i].in[1] <== 73; + and[9][i] = AND(); + and[9][i].a <== states[i][9]; + and[9][i].b <== eq[9][i].out; + states[i+1][10] <== and[9][i].out; + eq[10][i] = IsEqual(); + eq[10][i].in[0] <== in[i]; + eq[10][i].in[1] <== 68; + and[10][i] = AND(); + and[10][i].a <== states[i][10]; + and[10][i].b <== eq[10][i].out; + states[i+1][11] <== and[10][i].out; + eq[11][i] = IsEqual(); + eq[11][i].in[0] <== in[i]; + eq[11][i].in[1] <== 84; + and[11][i] = AND(); + and[11][i].a <== states[i][0]; + and[11][i].b <== eq[11][i].out; + states[i+1][12] <== and[11][i].out; + eq[12][i] = IsEqual(); + eq[12][i].in[0] <== in[i]; + eq[12][i].in[1] <== 104; + and[12][i] = AND(); + and[12][i].a <== states[i][12]; + and[12][i].b <== eq[12][i].out; + states[i+1][13] <== and[12][i].out; + eq[13][i] = IsEqual(); + eq[13][i].in[0] <== in[i]; + eq[13][i].in[1] <== 97; + and[13][i] = AND(); + and[13][i].a <== states[i][13]; + and[13][i].b <== eq[13][i].out; + states[i+1][14] <== and[13][i].out; + eq[14][i] = IsEqual(); + eq[14][i].in[0] <== in[i]; + eq[14][i].in[1] <== 110; + and[14][i] = AND(); + and[14][i].a <== states[i][14]; + and[14][i].b <== eq[14][i].out; + states[i+1][15] <== and[14][i].out; + eq[15][i] = IsEqual(); + eq[15][i].in[0] <== in[i]; + eq[15][i].in[1] <== 107; + and[15][i] = AND(); + and[15][i].a <== states[i][15]; + and[15][i].b <== eq[15][i].out; + states[i+1][16] <== and[15][i].out; + eq[16][i] = IsEqual(); + eq[16][i].in[0] <== in[i]; + eq[16][i].in[1] <== 115; + and[16][i] = AND(); + and[16][i].a <== states[i][16]; + and[16][i].b <== eq[16][i].out; + states[i+1][17] <== and[16][i].out; + eq[17][i] = IsEqual(); + eq[17][i].in[0] <== in[i]; + eq[17][i].in[1] <== 32; + and[17][i] = AND(); + and[17][i].a <== states[i][17]; + and[17][i].b <== eq[17][i].out; + states[i+1][18] <== and[17][i].out; + eq[18][i] = IsEqual(); + eq[18][i].in[0] <== in[i]; + eq[18][i].in[1] <== 102; + and[18][i] = AND(); + and[18][i].a <== states[i][18]; + and[18][i].b <== eq[18][i].out; + states[i+1][19] <== and[18][i].out; + eq[19][i] = IsEqual(); + eq[19][i].in[0] <== in[i]; + eq[19][i].in[1] <== 111; + and[19][i] = AND(); + and[19][i].a <== states[i][19]; + and[19][i].b <== eq[19][i].out; + states[i+1][20] <== and[19][i].out; + eq[20][i] = IsEqual(); + eq[20][i].in[0] <== in[i]; + eq[20][i].in[1] <== 114; + and[20][i] = AND(); + and[20][i].a <== states[i][20]; + and[20][i].b <== eq[20][i].out; + states[i+1][21] <== and[20][i].out; + eq[21][i] = IsEqual(); + eq[21][i].in[0] <== in[i]; + eq[21][i].in[1] <== 32; + and[21][i] = AND(); + and[21][i].a <== states[i][21]; + and[21][i].b <== eq[21][i].out; + states[i+1][22] <== and[21][i].out; + eq[22][i] = IsEqual(); + eq[22][i].in[0] <== in[i]; + eq[22][i].in[1] <== 112; + and[22][i] = AND(); + and[22][i].a <== states[i][22]; + and[22][i].b <== eq[22][i].out; + states[i+1][23] <== and[22][i].out; + eq[23][i] = IsEqual(); + eq[23][i].in[0] <== in[i]; + eq[23][i].in[1] <== 114; + and[23][i] = AND(); + and[23][i].a <== states[i][23]; + and[23][i].b <== eq[23][i].out; + states[i+1][24] <== and[23][i].out; + eq[24][i] = IsEqual(); + eq[24][i].in[0] <== in[i]; + eq[24][i].in[1] <== 111; + and[24][i] = AND(); + and[24][i].a <== states[i][24]; + and[24][i].b <== eq[24][i].out; + states[i+1][25] <== and[24][i].out; + eq[25][i] = IsEqual(); + eq[25][i].in[0] <== in[i]; + eq[25][i].in[1] <== 118; + and[25][i] = AND(); + and[25][i].a <== states[i][25]; + and[25][i].b <== eq[25][i].out; + states[i+1][26] <== and[25][i].out; + eq[26][i] = IsEqual(); + eq[26][i].in[0] <== in[i]; + eq[26][i].in[1] <== 105; + and[26][i] = AND(); + and[26][i].a <== states[i][26]; + and[26][i].b <== eq[26][i].out; + states[i+1][27] <== and[26][i].out; + eq[27][i] = IsEqual(); + eq[27][i].in[0] <== in[i]; + eq[27][i].in[1] <== 100; + and[27][i] = AND(); + and[27][i].a <== states[i][27]; + and[27][i].b <== eq[27][i].out; + states[i+1][28] <== and[27][i].out; + eq[28][i] = IsEqual(); + eq[28][i].in[0] <== in[i]; + eq[28][i].in[1] <== 105; + and[28][i] = AND(); + and[28][i].a <== states[i][28]; + and[28][i].b <== eq[28][i].out; + states[i+1][29] <== and[28][i].out; + eq[29][i] = IsEqual(); + eq[29][i].in[0] <== in[i]; + eq[29][i].in[1] <== 110; + and[29][i] = AND(); + and[29][i].a <== states[i][29]; + and[29][i].b <== eq[29][i].out; + states[i+1][30] <== and[29][i].out; + eq[30][i] = IsEqual(); + eq[30][i].in[0] <== in[i]; + eq[30][i].in[1] <== 103; + and[30][i] = AND(); + and[30][i].a <== states[i][30]; + and[30][i].b <== eq[30][i].out; + states[i+1][31] <== and[30][i].out; + eq[31][i] = IsEqual(); + eq[31][i].in[0] <== in[i]; + eq[31][i].in[1] <== 32; + and[31][i] = AND(); + and[31][i].a <== states[i][31]; + and[31][i].b <== eq[31][i].out; + states[i+1][32] <== and[31][i].out; + eq[32][i] = IsEqual(); + eq[32][i].in[0] <== in[i]; + eq[32][i].in[1] <== 97; + and[32][i] = AND(); + and[32][i].a <== states[i][32]; + and[32][i].b <== eq[32][i].out; + states[i+1][33] <== and[32][i].out; + eq[33][i] = IsEqual(); + eq[33][i].in[0] <== in[i]; + eq[33][i].in[1] <== 32; + and[33][i] = AND(); + and[33][i].a <== states[i][33]; + and[33][i].b <== eq[33][i].out; + states[i+1][34] <== and[33][i].out; + eq[34][i] = IsEqual(); + eq[34][i].in[0] <== in[i]; + eq[34][i].in[1] <== 103; + and[34][i] = AND(); + and[34][i].a <== states[i][34]; + and[34][i].b <== eq[34][i].out; + states[i+1][35] <== and[34][i].out; + eq[35][i] = IsEqual(); + eq[35][i].in[0] <== in[i]; + eq[35][i].in[1] <== 111; + and[35][i] = AND(); + and[35][i].a <== states[i][35]; + and[35][i].b <== eq[35][i].out; + states[i+1][36] <== and[35][i].out; + } + + signal final_state_sum[num_bytes+1]; + final_state_sum[0] <== states[0][11]; + for (var i = 1; i <= num_bytes; i++) { + final_state_sum[i] <== final_state_sum[i-1] + states[i][11]; + } + out <== final_state_sum[num_bytes]; +} \ No newline at end of file diff --git a/circuits/base64.circom b/circuits/base64.circom index b7b504c04..61e9603b7 100644 --- a/circuits/base64.circom +++ b/circuits/base64.circom @@ -11,11 +11,11 @@ template Base64Lookup() { component le_Z = LessThan(8); le_Z.in[0] <== in; le_Z.in[1] <== 90+1; - + component ge_A = GreaterThan(8); ge_A.in[0] <== in; ge_A.in[1] <== 65-1; - + signal range_AZ <== ge_A.out * le_Z.out; signal sum_AZ <== range_AZ * (in - 65); @@ -23,11 +23,11 @@ template Base64Lookup() { component le_z = LessThan(8); le_z.in[0] <== in; le_z.in[1] <== 122+1; - + component ge_a = GreaterThan(8); ge_a.in[0] <== in; ge_a.in[1] <== 97-1; - + signal range_az <== ge_a.out * le_z.out; signal sum_az <== sum_AZ + range_az * (in - 71); @@ -35,11 +35,11 @@ template Base64Lookup() { component le_9 = LessThan(8); le_9.in[0] <== in; le_9.in[1] <== 57+1; - + component ge_0 = GreaterThan(8); ge_0.in[0] <== in; ge_0.in[1] <== 48-1; - + signal range_09 <== ge_0.out * le_9.out; signal sum_09 <== sum_az + range_09 * (in + 4); @@ -70,7 +70,7 @@ template Base64Decode(N) { for (var j = 0; j < 3; j++) { bits_out[i\4][j] = Bits2Num(8); } - + for (var j = 0; j < 4; j++) { bits_in[i\4][j] = Num2Bits(6); translate[i\4][j] = Base64Lookup(); diff --git a/circuits/coinbase_kyc_regex.circom b/circuits/coinbase_kyc_regex.circom new file mode 100644 index 000000000..1d2ef566c --- /dev/null +++ b/circuits/coinbase_kyc_regex.circom @@ -0,0 +1,195 @@ +pragma circom 2.0.3; + +include "./regex_helpers.circom"; + +template CoinbaseKYCRegex (msg_bytes) { + signal input msg[msg_bytes]; + signal output out; + + var num_bytes = msg_bytes; + signal in[num_bytes]; + for (var i = 0; i < msg_bytes; i++) { + in[i] <== msg[i]; + } + component eq[23][num_bytes]; + component and[23][num_bytes]; + signal states[num_bytes+1][24]; + + for (var i = 0; i < num_bytes; i++) { + states[i][0] <== 1; + } + for (var i = 1; i < 24; i++) { + states[0][i] <== 0; + } + + for (var i = 0; i < num_bytes; i++) { + eq[0][i] = IsEqual(); + eq[0][i].in[0] <== in[i]; + eq[0][i].in[1] <== 89; + and[0][i] = AND(); + and[0][i].a <== states[i][0]; + and[0][i].b <== eq[0][i].out; + states[i+1][1] <== and[0][i].out; + eq[1][i] = IsEqual(); + eq[1][i].in[0] <== in[i]; + eq[1][i].in[1] <== 111; + and[1][i] = AND(); + and[1][i].a <== states[i][1]; + and[1][i].b <== eq[1][i].out; + states[i+1][2] <== and[1][i].out; + eq[2][i] = IsEqual(); + eq[2][i].in[0] <== in[i]; + eq[2][i].in[1] <== 117; + and[2][i] = AND(); + and[2][i].a <== states[i][2]; + and[2][i].b <== eq[2][i].out; + states[i+1][3] <== and[2][i].out; + eq[3][i] = IsEqual(); + eq[3][i].in[0] <== in[i]; + eq[3][i].in[1] <== 39; + and[3][i] = AND(); + and[3][i].a <== states[i][3]; + and[3][i].b <== eq[3][i].out; + states[i+1][4] <== and[3][i].out; + eq[4][i] = IsEqual(); + eq[4][i].in[0] <== in[i]; + eq[4][i].in[1] <== 114; + and[4][i] = AND(); + and[4][i].a <== states[i][4]; + and[4][i].b <== eq[4][i].out; + states[i+1][5] <== and[4][i].out; + eq[5][i] = IsEqual(); + eq[5][i].in[0] <== in[i]; + eq[5][i].in[1] <== 101; + and[5][i] = AND(); + and[5][i].a <== states[i][5]; + and[5][i].b <== eq[5][i].out; + states[i+1][6] <== and[5][i].out; + eq[6][i] = IsEqual(); + eq[6][i].in[0] <== in[i]; + eq[6][i].in[1] <== 32; + and[6][i] = AND(); + and[6][i].a <== states[i][6]; + and[6][i].b <== eq[6][i].out; + states[i+1][7] <== and[6][i].out; + eq[7][i] = IsEqual(); + eq[7][i].in[0] <== in[i]; + eq[7][i].in[1] <== 114; + and[7][i] = AND(); + and[7][i].a <== states[i][7]; + and[7][i].b <== eq[7][i].out; + states[i+1][8] <== and[7][i].out; + eq[8][i] = IsEqual(); + eq[8][i].in[0] <== in[i]; + eq[8][i].in[1] <== 101; + and[8][i] = AND(); + and[8][i].a <== states[i][8]; + and[8][i].b <== eq[8][i].out; + states[i+1][9] <== and[8][i].out; + eq[9][i] = IsEqual(); + eq[9][i].in[0] <== in[i]; + eq[9][i].in[1] <== 97; + and[9][i] = AND(); + and[9][i].a <== states[i][9]; + and[9][i].b <== eq[9][i].out; + states[i+1][10] <== and[9][i].out; + eq[10][i] = IsEqual(); + eq[10][i].in[0] <== in[i]; + eq[10][i].in[1] <== 100; + and[10][i] = AND(); + and[10][i].a <== states[i][10]; + and[10][i].b <== eq[10][i].out; + states[i+1][11] <== and[10][i].out; + eq[11][i] = IsEqual(); + eq[11][i].in[0] <== in[i]; + eq[11][i].in[1] <== 121; + and[11][i] = AND(); + and[11][i].a <== states[i][11]; + and[11][i].b <== eq[11][i].out; + states[i+1][12] <== and[11][i].out; + eq[12][i] = IsEqual(); + eq[12][i].in[0] <== in[i]; + eq[12][i].in[1] <== 32; + and[12][i] = AND(); + and[12][i].a <== states[i][12]; + and[12][i].b <== eq[12][i].out; + states[i+1][13] <== and[12][i].out; + eq[13][i] = IsEqual(); + eq[13][i].in[0] <== in[i]; + eq[13][i].in[1] <== 116; + and[13][i] = AND(); + and[13][i].a <== states[i][13]; + and[13][i].b <== eq[13][i].out; + states[i+1][14] <== and[13][i].out; + eq[14][i] = IsEqual(); + eq[14][i].in[0] <== in[i]; + eq[14][i].in[1] <== 111; + and[14][i] = AND(); + and[14][i].a <== states[i][14]; + and[14][i].b <== eq[14][i].out; + states[i+1][15] <== and[14][i].out; + eq[15][i] = IsEqual(); + eq[15][i].in[0] <== in[i]; + eq[15][i].in[1] <== 32; + and[15][i] = AND(); + and[15][i].a <== states[i][15]; + and[15][i].b <== eq[15][i].out; + states[i+1][16] <== and[15][i].out; + eq[16][i] = IsEqual(); + eq[16][i].in[0] <== in[i]; + eq[16][i].in[1] <== 105; + and[16][i] = AND(); + and[16][i].a <== states[i][16]; + and[16][i].b <== eq[16][i].out; + states[i+1][17] <== and[16][i].out; + eq[17][i] = IsEqual(); + eq[17][i].in[0] <== in[i]; + eq[17][i].in[1] <== 110; + and[17][i] = AND(); + and[17][i].a <== states[i][17]; + and[17][i].b <== eq[17][i].out; + states[i+1][18] <== and[17][i].out; + eq[18][i] = IsEqual(); + eq[18][i].in[0] <== in[i]; + eq[18][i].in[1] <== 118; + and[18][i] = AND(); + and[18][i].a <== states[i][18]; + and[18][i].b <== eq[18][i].out; + states[i+1][19] <== and[18][i].out; + eq[19][i] = IsEqual(); + eq[19][i].in[0] <== in[i]; + eq[19][i].in[1] <== 101; + and[19][i] = AND(); + and[19][i].a <== states[i][19]; + and[19][i].b <== eq[19][i].out; + states[i+1][20] <== and[19][i].out; + eq[20][i] = IsEqual(); + eq[20][i].in[0] <== in[i]; + eq[20][i].in[1] <== 115; + and[20][i] = AND(); + and[20][i].a <== states[i][20]; + and[20][i].b <== eq[20][i].out; + states[i+1][21] <== and[20][i].out; + eq[21][i] = IsEqual(); + eq[21][i].in[0] <== in[i]; + eq[21][i].in[1] <== 116; + and[21][i] = AND(); + and[21][i].a <== states[i][21]; + and[21][i].b <== eq[21][i].out; + states[i+1][22] <== and[21][i].out; + eq[22][i] = IsEqual(); + eq[22][i].in[0] <== in[i]; + eq[22][i].in[1] <== 33; + and[22][i] = AND(); + and[22][i].a <== states[i][22]; + and[22][i].b <== eq[22][i].out; + states[i+1][23] <== and[22][i].out; + } + + signal final_state_sum[num_bytes+1]; + final_state_sum[0] <== states[0][23]; + for (var i = 1; i <= num_bytes; i++) { + final_state_sum[i] <== final_state_sum[i-1] + states[i][23]; + } + out <== final_state_sum[num_bytes]; +} \ No newline at end of file diff --git a/circuits/email.circom b/circuits/email.circom index 66eb447db..8f716ba47 100644 --- a/circuits/email.circom +++ b/circuits/email.circom @@ -16,7 +16,7 @@ template EmailVerify(max_header_bytes, max_body_bytes, n, k) { assert(max_header_bytes % 64 == 0); assert(max_body_bytes % 64 == 0); assert(n * k > 2048); // constraints for 2048 bit RSA - assert(k < 255 / 2); // we want a multiplication to fit into a circom signal + assert(k < 255 \ 2); // we want a multiplication to fit into a circom signal var max_packed_bytes = (max_header_bytes - 1) \ 7 + 1; // ceil(max_num_bytes / 7) signal input in_padded[max_header_bytes]; // prehashed email data, includes up to 512 + 64? bytes of padding pre SHA256, and padded with lots of 0s at end after the length @@ -97,8 +97,9 @@ template EmailVerify(max_header_bytes, max_body_bytes, n, k) { dkim_header_regex.out === 2; for (var i = 0; i < max_header_bytes; i++) { reveal[i] <== dkim_header_regex.reveal[i+1]; + log(reveal[i]); } - log(dkim_header_regex.out); + log(dkim_header_regex.out, "finished reveal above"); // BODY HASH REGEX: 617,597 constraints // This extracts the body hash from the header (i.e. the part after bh= within the DKIM-signature section) @@ -219,4 +220,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(1024, 1536, 121, 17); \ No newline at end of file diff --git a/circuits/email_airbnb.circom b/circuits/email_airbnb.circom new file mode 100644 index 000000000..cbe0fc84f --- /dev/null +++ b/circuits/email_airbnb.circom @@ -0,0 +1,158 @@ +pragma circom 2.0.3; + +include "../node_modules/circomlib/circuits/bitify.circom"; +include "./sha.circom"; +include "./rsa.circom"; +include "./dkim_header_regex.circom"; +include "./extract_to_email_regex.circom"; +include "./body_hash_regex.circom"; +include "./airbnb_kyc_regex.circom"; +include "./base64.circom"; + +// Here, n and k are the biginteger parameters for RSA +// This is because the number is chunked into n chunks of k bits each +// Max header bytes shouldn't need to be changed much per email, +// but the max mody bytes may need to be changed to be larger if the email has a lot of i.e. HTML formatting +template AirbnbEmailVerify(max_header_bytes, n, k) { + assert(max_header_bytes % 64 == 0); + // assert(max_body_bytes % 64 == 0); + assert(n * k > 2048); // constraints for 2048 bit RSA + assert(k < 255 \ 2); // we want a multiplication to fit into a circom signal + + var max_packed_bytes = (max_header_bytes - 1) \ 7 + 1; // ceil(max_num_bytes / 7) + signal input in_padded[max_header_bytes]; // prehashed email data, includes up to 512 + 64? bytes of padding pre SHA256, and padded with lots of 0s at end after the length + signal input modulus[k]; // rsa pubkey, verified with smart contract + optional oracle + signal input signature[k]; + signal input in_len_padded_bytes; // length of in email data including the padding, which will inform the sha256 block length + + // Next 2 signals are for decreasing SHA constraints for parsing out information from the in-body text + signal reveal[max_header_bytes]; // bytes to reveal + signal output reveal_packed[max_packed_bytes]; // packed into 7-bytes. TODO: make this rotate to take up even less space + + signal output to_email[max_header_bytes]; // to email address of email + + signal input address; + signal input address_plus_one; + + signal input email_to_idx; + + var LEN_SHA_B64 = 44; // ceil(32/3) * 4, should be automatically calculated. + signal input body_hash_idx; + signal body_hash[LEN_SHA_B64][max_header_bytes]; + + // SHA HEADER: 506,670 constraints + // This calculates the SHA256 hash of the header, which is the "base_msg" that is RSA signed. + // The header signs the fields in the "h=Date:From:To:Subject:MIME-Version:Content-Type:Message-ID;" + // section of the "DKIM-Signature:"" line, along with the body hash. + // Note that nothing above the "DKIM-Signature:" line is signed. + component sha = Sha256Bytes(max_header_bytes); + for (var i = 0; i < max_header_bytes; i++) { + sha.in_padded[i] <== in_padded[i]; + } + sha.in_len_padded_bytes <== in_len_padded_bytes; + var msg_len = (256+n)\n; + component base_msg[msg_len]; + for (var i = 0; i < msg_len; i++) { + base_msg[i] = Bits2Num(n); + } + for (var i = 0; i < 256; i++) { + base_msg[i\n].in[i%n] <== sha.out[255 - i]; + } + for (var i = 256; i < n*msg_len; i++) { + base_msg[i\n].in[i%n] <== 0; + } + + // VERIFY RSA SIGNATURE: 149,251 constraints + // The fields that this signature actually signs are defined as the body and the values in the header + component rsa = RSAVerify65537(n, k); + for (var i = 0; i < msg_len; i++) { + rsa.base_message[i] <== base_msg[i].out; + } + for (var i = msg_len; i < k; i++) { + rsa.base_message[i] <== 0; + } + for (var i = 0; i < k; i++) { + rsa.modulus[i] <== modulus[i]; + } + for (var i = 0; i < k; i++) { + rsa.signature[i] <== signature[i]; + } + + // DKIM HEADER REGEX: 736,553 constraints + // This extracts the from and the to emails, and the precise regex format can be viewed in the README + component dkim_header_regex = DKIMHeaderRegex(max_header_bytes); + for (var i = 0; i < max_header_bytes; i++) { + dkim_header_regex.msg[i] <== in_padded[i]; + } + dkim_header_regex.out === 2; + for (var i = 0; i < max_header_bytes; i++) { + reveal[i] <== dkim_header_regex.reveal[i+1]; + } + log(dkim_header_regex.out); + + // BODY HASH REGEX: 617,597 constraints + // This extracts the body hash from the header (i.e. the part after bh= within the DKIM-signature section) + // which is used to verify the body text matches this signed hash + the signature verifies this hash is legit + 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]; + } + 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(); + body_hash_eq[i].in[0] <== i; + body_hash_eq[i].in[1] <== body_hash_idx; + } + for (var j = 0; j < 44; j++) { + body_hash[j][j] <== body_hash_eq[j].out * body_hash_regex.reveal[j]; + for (var i = j + 1; i < max_header_bytes; i++) { + body_hash[j][i] <== body_hash[j][i - 1] + body_hash_eq[i-j].out * body_hash_regex.reveal[i]; + } + } + + // AIRBNB REGEX + // Checks Airbnb regex matches KYC confirmation email + component airbnb_regex = AirbnbKYCRegex(max_header_bytes); + for (var i = 0; i < max_header_bytes; i++) { + airbnb_regex.msg[i] <== in_padded[i]; + } + // This ensures we found a match at least once + component found_airbnb = IsZero(); + found_airbnb.in <== airbnb_regex.out; + found_airbnb.out === 0; + log(airbnb_regex.out); + + // 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_output[max_packed_bytes]; + for (var i = 0; i < max_packed_bytes; i++) { + packed_output[i] = Bytes2Packed(chunks); + for (var j = 0; j < chunks; j++) { + var reveal_idx = i * chunks + j; + if (reveal_idx < max_header_bytes) { + packed_output[i].in[j] <== reveal[i * chunks + j]; + } else { + packed_output[i].in[j] <== 0; + } + } + reveal_packed[i] <== packed_output[i].out; + } + + // EXTRACT TO EMAIL REGEX + // This extracts the to email + component extract_to_email_regex = ExtractToEmailRegex(max_header_bytes); + extract_to_email_regex.email_to_idx <== email_to_idx; + for (var i = 0; i < max_header_bytes; i++) { + extract_to_email_regex.msg[i] <== in_padded[i]; + } + extract_to_email_regex.out === 1; + for (var i = 0; i < max_header_bytes; i++) { + to_email[i] <== extract_to_email_regex.to_email[i+1]; + } +} + +// component main { public [ modulus, address ] } = AirbnbEmailVerify(1024, 121, 17); \ No newline at end of file diff --git a/circuits/email_both.circom b/circuits/email_both.circom new file mode 100644 index 000000000..3a503e778 --- /dev/null +++ b/circuits/email_both.circom @@ -0,0 +1,110 @@ +pragma circom 2.0.3; + +include "./email_airbnb.circom"; +include "./email_coinbase.circom"; + +// Current "to email" extractor only works on certain formats of emails. In particular, if there's a name after the "To:" and before the email, the extractor might extract the wrong thing. +// The current setup works for Airbnb and Coinbase confirmation emails, although that may change in the future. + +// Here, n and k are the biginteger parameters for RSA +// This is because the number is chunked into n chunks of k bits each +// Max header bytes shouldn't need to be changed much per email, +// but the max mody bytes may need to be changed to be larger if the email has a lot of i.e. HTML formatting +template KYCVerify(max_header_bytes, n, k) { + assert(max_header_bytes % 64 == 0); + // assert(max_body_bytes % 64 == 0); + assert(n * k > 2048); // constraints for 2048 bit RSA + assert(k < 255 \ 2); // we want a multiplication to fit into a circom signal + + // max_num_bytes must be a multiple of 64 + var max_packed_bytes = (max_header_bytes - 1) \ 7 + 1; // ceil(max_num_bytes / 7) + + // AIRBNB INPUT SIGNALS + signal input in_padded_airbnb[max_header_bytes]; // prehashed email data, includes up to 512 + 64? bytes of padding pre SHA256, and padded with lots of 0s at end after the length + signal input modulus_airbnb[k]; // rsa pubkey, verified with smart contract + optional oracle + signal input signature_airbnb[k]; + signal input in_len_padded_bytes_airbnb; // length of in email data including the padding, which will inform the sha256 block length + + // Next 2 signals are only needed if we are doing in-body verification + signal input body_hash_idx_airbnb; + signal input email_to_idx_airbnb; + + signal input address_airbnb; + signal input address_plus_one_airbnb; + + // COINBASE INPUT SIGNALS + signal input in_padded_coinbase[max_header_bytes]; // prehashed email data, includes up to 512 + 64? bytes of padding pre SHA256, and padded with lots of 0s at end after the length + signal input modulus_coinbase[k]; // rsa pubkey, verified with smart contract + optional oracle + signal input signature_coinbase[k]; + signal input in_len_padded_bytes_coinbase; // length of in email data including the padding, which will inform the sha256 block length + + // Next 2 signals are only needed if we are doing in-body verification + signal input body_hash_idx_coinbase; + signal input email_to_idx_coinbase; + + signal input address_coinbase; + signal input address_plus_one_coinbase; + + // OUTPUT SIGNALS + // Outputs the packed version of the from/to emails from both emails + signal output reveal_packed[2 * max_packed_bytes]; + + component airbnb_verify = AirbnbEmailVerify(max_header_bytes, n, k); + component coinbase_verify = CoinbaseEmailVerify(max_header_bytes, n, k); + + // Airbnb email inputs + for (var i = 0; i < max_header_bytes; i++) { + airbnb_verify.in_padded[i] <== in_padded_airbnb[i]; + } + for (var i = 0; i < k; i++) { + airbnb_verify.modulus[i] <== modulus_airbnb[i]; + } + for (var i = 0; i < k; i++) { + airbnb_verify.signature[i] <== signature_airbnb[i]; + } + airbnb_verify.in_len_padded_bytes <== in_len_padded_bytes_airbnb; + airbnb_verify.body_hash_idx <== body_hash_idx_airbnb; + airbnb_verify.email_to_idx <== email_to_idx_airbnb; + airbnb_verify.address <== address_airbnb; + airbnb_verify.address_plus_one <== address_plus_one_airbnb; + + // Coinbase email inputs + for (var i = 0; i < max_header_bytes; i++) { + coinbase_verify.in_padded[i] <== in_padded_coinbase[i]; + } + for (var i = 0; i < k; i++) { + coinbase_verify.modulus[i] <== modulus_coinbase[i]; + } + for (var i = 0; i < k; i++) { + coinbase_verify.signature[i] <== signature_coinbase[i]; + } + coinbase_verify.in_len_padded_bytes <== in_len_padded_bytes_coinbase; + coinbase_verify.body_hash_idx <== body_hash_idx_coinbase; + coinbase_verify.email_to_idx <== email_to_idx_coinbase; + coinbase_verify.address <== address_coinbase; + coinbase_verify.address_plus_one <== address_plus_one_coinbase; + + // TO EMAILS MATCH + // Check that the to emails match + signal to_email_airbnb[max_header_bytes]; + signal to_email_coinbase[max_header_bytes]; + for (var i = 0; i < max_header_bytes; i++) { + to_email_airbnb[i] <== airbnb_verify.to_email[i]; + to_email_coinbase[i] <== coinbase_verify.to_email[i]; + to_email_airbnb[i] === to_email_coinbase[i]; + } + + // PACKED OUTPUT + // Output for solidity verifier + for (var i = 0; i < max_packed_bytes; i++) { + reveal_packed[i] <== airbnb_verify.reveal_packed[i]; + } + for (var i = 0; i < max_packed_bytes; i++) { + reveal_packed[i + max_packed_bytes] <== coinbase_verify.reveal_packed[i]; + } +} + +// 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_airbnb, modulus_coinbase, address_airbnb, address_coinbase ] } = KYCVerify(1024, 121, 17); diff --git a/circuits/email_coinbase.circom b/circuits/email_coinbase.circom new file mode 100644 index 000000000..455938286 --- /dev/null +++ b/circuits/email_coinbase.circom @@ -0,0 +1,158 @@ +pragma circom 2.0.3; + +include "../node_modules/circomlib/circuits/bitify.circom"; +include "./sha.circom"; +include "./rsa.circom"; +include "./dkim_header_regex.circom"; +include "./extract_to_email_regex.circom"; +include "./body_hash_regex.circom"; +include "./coinbase_kyc_regex.circom"; +include "./base64.circom"; + +// Here, n and k are the biginteger parameters for RSA +// This is because the number is chunked into n chunks of k bits each +// Max header bytes shouldn't need to be changed much per email, +// but the max mody bytes may need to be changed to be larger if the email has a lot of i.e. HTML formatting +template CoinbaseEmailVerify(max_header_bytes, n, k) { + assert(max_header_bytes % 64 == 0); + // assert(max_body_bytes % 64 == 0); + assert(n * k > 2048); // constraints for 2048 bit RSA + assert(k < 255 \ 2); // we want a multiplication to fit into a circom signal + + var max_packed_bytes = (max_header_bytes - 1) \ 7 + 1; // ceil(max_num_bytes / 7) + signal input in_padded[max_header_bytes]; // prehashed email data, includes up to 512 + 64? bytes of padding pre SHA256, and padded with lots of 0s at end after the length + signal input modulus[k]; // rsa pubkey, verified with smart contract + optional oracle + signal input signature[k]; + signal input in_len_padded_bytes; // length of in email data including the padding, which will inform the sha256 block length + + // Next 2 signals are for decreasing SHA constraints for parsing out information from the in-body text + signal reveal[max_header_bytes]; // bytes to reveal + signal output reveal_packed[max_packed_bytes]; // packed into 7-bytes. TODO: make this rotate to take up even less space + + signal output to_email[max_header_bytes]; // to email address of email + + signal input address; + signal input address_plus_one; + + signal input email_to_idx; + + var LEN_SHA_B64 = 44; // ceil(32/3) * 4, should be automatically calculated. + signal input body_hash_idx; + signal body_hash[LEN_SHA_B64][max_header_bytes]; + + // SHA HEADER: 506,670 constraints + // This calculates the SHA256 hash of the header, which is the "base_msg" that is RSA signed. + // The header signs the fields in the "h=Date:From:To:Subject:MIME-Version:Content-Type:Message-ID;" + // section of the "DKIM-Signature:"" line, along with the body hash. + // Note that nothing above the "DKIM-Signature:" line is signed. + component sha = Sha256Bytes(max_header_bytes); + for (var i = 0; i < max_header_bytes; i++) { + sha.in_padded[i] <== in_padded[i]; + } + sha.in_len_padded_bytes <== in_len_padded_bytes; + var msg_len = (256+n)\n; + component base_msg[msg_len]; + for (var i = 0; i < msg_len; i++) { + base_msg[i] = Bits2Num(n); + } + for (var i = 0; i < 256; i++) { + base_msg[i\n].in[i%n] <== sha.out[255 - i]; + } + for (var i = 256; i < n*msg_len; i++) { + base_msg[i\n].in[i%n] <== 0; + } + + // VERIFY RSA SIGNATURE: 149,251 constraints + // The fields that this signature actually signs are defined as the body and the values in the header + component rsa = RSAVerify65537(n, k); + for (var i = 0; i < msg_len; i++) { + rsa.base_message[i] <== base_msg[i].out; + } + for (var i = msg_len; i < k; i++) { + rsa.base_message[i] <== 0; + } + for (var i = 0; i < k; i++) { + rsa.modulus[i] <== modulus[i]; + } + for (var i = 0; i < k; i++) { + rsa.signature[i] <== signature[i]; + } + + // DKIM HEADER REGEX: 736,553 constraints + // This extracts the from and the to emails, and the precise regex format can be viewed in the README + component dkim_header_regex = DKIMHeaderRegex(max_header_bytes); + for (var i = 0; i < max_header_bytes; i++) { + dkim_header_regex.msg[i] <== in_padded[i]; + } + dkim_header_regex.out === 2; + for (var i = 0; i < max_header_bytes; i++) { + reveal[i] <== dkim_header_regex.reveal[i+1]; + } + log(dkim_header_regex.out); + + // BODY HASH REGEX: 617,597 constraints + // This extracts the body hash from the header (i.e. the part after bh= within the DKIM-signature section) + // which is used to verify the body text matches this signed hash + the signature verifies this hash is legit + 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]; + } + 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(); + body_hash_eq[i].in[0] <== i; + body_hash_eq[i].in[1] <== body_hash_idx; + } + for (var j = 0; j < 44; j++) { + body_hash[j][j] <== body_hash_eq[j].out * body_hash_regex.reveal[j]; + for (var i = j + 1; i < max_header_bytes; i++) { + body_hash[j][i] <== body_hash[j][i - 1] + body_hash_eq[i-j].out * body_hash_regex.reveal[i]; + } + } + + // COINBASE REGEX + // Checks Coinbase regex matches KYC confirmation email + component coinbase_regex = CoinbaseKYCRegex(max_header_bytes); + for (var i = 0; i < max_header_bytes; i++) { + coinbase_regex.msg[i] <== in_padded[i]; + } + // This ensures we found a match at least once + component found_coinbase = IsZero(); + found_coinbase.in <== coinbase_regex.out; + found_coinbase.out === 0; + log(coinbase_regex.out); + + // 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_output[max_packed_bytes]; + for (var i = 0; i < max_packed_bytes; i++) { + packed_output[i] = Bytes2Packed(chunks); + for (var j = 0; j < chunks; j++) { + var reveal_idx = i * chunks + j; + if (reveal_idx < max_header_bytes) { + packed_output[i].in[j] <== reveal[i * chunks + j]; + } else { + packed_output[i].in[j] <== 0; + } + } + reveal_packed[i] <== packed_output[i].out; + } + + // EXTRACT TO EMAIL REGEX + // This extracts the to email + component extract_to_email_regex = ExtractToEmailRegex(max_header_bytes); + extract_to_email_regex.email_to_idx <== email_to_idx; + for (var i = 0; i < max_header_bytes; i++) { + extract_to_email_regex.msg[i] <== in_padded[i]; + } + extract_to_email_regex.out === 1; + for (var i = 0; i < max_header_bytes; i++) { + to_email[i] <== extract_to_email_regex.to_email[i+1]; + } +} + +// component main { public [ modulus, address ] } = CoinbaseEmailVerify(1024, 121, 17); \ No newline at end of file diff --git a/circuits/extract_to_email_regex.circom b/circuits/extract_to_email_regex.circom new file mode 100644 index 000000000..d075305bb --- /dev/null +++ b/circuits/extract_to_email_regex.circom @@ -0,0 +1,701 @@ +pragma circom 2.0.3; + +include "./regex_helpers.circom"; +include "./bigint_func.circom"; +include "./variable_subarray.circom"; + +template ExtractToEmailRegex (msg_bytes) { + signal input msg[msg_bytes]; + signal input email_to_idx; + signal output out; + + var num_bytes = msg_bytes+1; + signal in[num_bytes]; + in[0] <== 128; // \x80 (sentinel for first character in string) + for (var i = 0; i < msg_bytes; i++) { + in[i+1] <== msg[i]; + } + + component eq[59][num_bytes]; + component lt[54][num_bytes]; + component and[58][num_bytes]; + component multi_or[22][num_bytes]; + signal states[num_bytes+1][17]; + + for (var i = 0; i < num_bytes; i++) { + states[i][0] <== 1; + } + for (var i = 1; i < 17; i++) { + states[0][i] <== 0; + } + + for (var i = 0; i < num_bytes; i++) { + lt[0][i] = LessThan(8); + lt[0][i].in[0] <== 64; + lt[0][i].in[1] <== in[i]; + lt[1][i] = LessThan(8); + lt[1][i].in[0] <== in[i]; + lt[1][i].in[1] <== 91; + and[0][i] = AND(); + and[0][i].a <== lt[0][i].out; + and[0][i].b <== lt[1][i].out; + lt[2][i] = LessThan(8); + lt[2][i].in[0] <== 96; + lt[2][i].in[1] <== in[i]; + lt[3][i] = LessThan(8); + lt[3][i].in[0] <== in[i]; + lt[3][i].in[1] <== 123; + and[1][i] = AND(); + and[1][i].a <== lt[2][i].out; + and[1][i].b <== lt[3][i].out; + lt[4][i] = LessThan(8); + lt[4][i].in[0] <== 47; + lt[4][i].in[1] <== in[i]; + lt[5][i] = LessThan(8); + lt[5][i].in[0] <== in[i]; + lt[5][i].in[1] <== 58; + and[2][i] = AND(); + and[2][i].a <== lt[4][i].out; + and[2][i].b <== lt[5][i].out; + eq[0][i] = IsEqual(); + eq[0][i].in[0] <== in[i]; + eq[0][i].in[1] <== 45; + eq[1][i] = IsEqual(); + eq[1][i].in[0] <== in[i]; + eq[1][i].in[1] <== 46; + eq[2][i] = IsEqual(); + eq[2][i].in[0] <== in[i]; + eq[2][i].in[1] <== 95; + and[3][i] = AND(); + and[3][i].a <== states[i][1]; + multi_or[0][i] = MultiOR(6); + multi_or[0][i].in[0] <== and[0][i].out; + multi_or[0][i].in[1] <== and[1][i].out; + multi_or[0][i].in[2] <== and[2][i].out; + multi_or[0][i].in[3] <== eq[0][i].out; + multi_or[0][i].in[4] <== eq[1][i].out; + multi_or[0][i].in[5] <== eq[2][i].out; + and[3][i].b <== multi_or[0][i].out; + lt[6][i] = LessThan(8); + lt[6][i].in[0] <== 64; + lt[6][i].in[1] <== in[i]; + lt[7][i] = LessThan(8); + lt[7][i].in[0] <== in[i]; + lt[7][i].in[1] <== 91; + and[4][i] = AND(); + and[4][i].a <== lt[6][i].out; + and[4][i].b <== lt[7][i].out; + lt[8][i] = LessThan(8); + lt[8][i].in[0] <== 96; + lt[8][i].in[1] <== in[i]; + lt[9][i] = LessThan(8); + lt[9][i].in[0] <== in[i]; + lt[9][i].in[1] <== 123; + and[5][i] = AND(); + and[5][i].a <== lt[8][i].out; + and[5][i].b <== lt[9][i].out; + lt[10][i] = LessThan(8); + lt[10][i].in[0] <== 47; + lt[10][i].in[1] <== in[i]; + lt[11][i] = LessThan(8); + lt[11][i].in[0] <== in[i]; + lt[11][i].in[1] <== 58; + and[6][i] = AND(); + and[6][i].a <== lt[10][i].out; + and[6][i].b <== lt[11][i].out; + eq[3][i] = IsEqual(); + eq[3][i].in[0] <== in[i]; + eq[3][i].in[1] <== 45; + eq[4][i] = IsEqual(); + eq[4][i].in[0] <== in[i]; + eq[4][i].in[1] <== 46; + eq[5][i] = IsEqual(); + eq[5][i].in[0] <== in[i]; + eq[5][i].in[1] <== 95; + and[7][i] = AND(); + and[7][i].a <== states[i][9]; + multi_or[1][i] = MultiOR(6); + multi_or[1][i].in[0] <== and[4][i].out; + multi_or[1][i].in[1] <== and[5][i].out; + multi_or[1][i].in[2] <== and[6][i].out; + multi_or[1][i].in[3] <== eq[3][i].out; + multi_or[1][i].in[4] <== eq[4][i].out; + multi_or[1][i].in[5] <== eq[5][i].out; + and[7][i].b <== multi_or[1][i].out; + multi_or[2][i] = MultiOR(2); + multi_or[2][i].in[0] <== and[3][i].out; + multi_or[2][i].in[1] <== and[7][i].out; + states[i+1][1] <== multi_or[2][i].out; + eq[6][i] = IsEqual(); + eq[6][i].in[0] <== in[i]; + eq[6][i].in[1] <== 13; + and[8][i] = AND(); + and[8][i].a <== states[i][0]; + and[8][i].b <== eq[6][i].out; + states[i+1][2] <== and[8][i].out; + eq[7][i] = IsEqual(); + eq[7][i].in[0] <== in[i]; + eq[7][i].in[1] <== 34; + eq[8][i] = IsEqual(); + eq[8][i].in[0] <== in[i]; + eq[8][i].in[1] <== 32; + and[9][i] = AND(); + and[9][i].a <== states[i][1]; + multi_or[3][i] = MultiOR(2); + multi_or[3][i].in[0] <== eq[7][i].out; + multi_or[3][i].in[1] <== eq[8][i].out; + and[9][i].b <== multi_or[3][i].out; + lt[12][i] = LessThan(8); + lt[12][i].in[0] <== 64; + lt[12][i].in[1] <== in[i]; + lt[13][i] = LessThan(8); + lt[13][i].in[0] <== in[i]; + lt[13][i].in[1] <== 91; + and[10][i] = AND(); + and[10][i].a <== lt[12][i].out; + and[10][i].b <== lt[13][i].out; + lt[14][i] = LessThan(8); + lt[14][i].in[0] <== 96; + lt[14][i].in[1] <== in[i]; + lt[15][i] = LessThan(8); + lt[15][i].in[0] <== in[i]; + lt[15][i].in[1] <== 123; + and[11][i] = AND(); + and[11][i].a <== lt[14][i].out; + and[11][i].b <== lt[15][i].out; + lt[16][i] = LessThan(8); + lt[16][i].in[0] <== 47; + lt[16][i].in[1] <== in[i]; + lt[17][i] = LessThan(8); + lt[17][i].in[0] <== in[i]; + lt[17][i].in[1] <== 58; + and[12][i] = AND(); + and[12][i].a <== lt[16][i].out; + and[12][i].b <== lt[17][i].out; + eq[9][i] = IsEqual(); + eq[9][i].in[0] <== in[i]; + eq[9][i].in[1] <== 45; + eq[10][i] = IsEqual(); + eq[10][i].in[0] <== in[i]; + eq[10][i].in[1] <== 46; + eq[11][i] = IsEqual(); + eq[11][i].in[0] <== in[i]; + eq[11][i].in[1] <== 32; + eq[12][i] = IsEqual(); + eq[12][i].in[0] <== in[i]; + eq[12][i].in[1] <== 64; + eq[13][i] = IsEqual(); + eq[13][i].in[0] <== in[i]; + eq[13][i].in[1] <== 34; + eq[14][i] = IsEqual(); + eq[14][i].in[0] <== in[i]; + eq[14][i].in[1] <== 95; + and[13][i] = AND(); + and[13][i].a <== states[i][3]; + multi_or[4][i] = MultiOR(9); + multi_or[4][i].in[0] <== and[10][i].out; + multi_or[4][i].in[1] <== and[11][i].out; + multi_or[4][i].in[2] <== and[12][i].out; + multi_or[4][i].in[3] <== eq[9][i].out; + multi_or[4][i].in[4] <== eq[10][i].out; + multi_or[4][i].in[5] <== eq[11][i].out; + multi_or[4][i].in[6] <== eq[12][i].out; + multi_or[4][i].in[7] <== eq[13][i].out; + multi_or[4][i].in[8] <== eq[14][i].out; + and[13][i].b <== multi_or[4][i].out; + eq[15][i] = IsEqual(); + eq[15][i].in[0] <== in[i]; + eq[15][i].in[1] <== 64; + eq[16][i] = IsEqual(); + eq[16][i].in[0] <== in[i]; + eq[16][i].in[1] <== 34; + eq[17][i] = IsEqual(); + eq[17][i].in[0] <== in[i]; + eq[17][i].in[1] <== 32; + and[14][i] = AND(); + and[14][i].a <== states[i][8]; + multi_or[5][i] = MultiOR(3); + multi_or[5][i].in[0] <== eq[15][i].out; + multi_or[5][i].in[1] <== eq[16][i].out; + multi_or[5][i].in[2] <== eq[17][i].out; + and[14][i].b <== multi_or[5][i].out; + eq[18][i] = IsEqual(); + eq[18][i].in[0] <== in[i]; + eq[18][i].in[1] <== 64; + eq[19][i] = IsEqual(); + eq[19][i].in[0] <== in[i]; + eq[19][i].in[1] <== 34; + eq[20][i] = IsEqual(); + eq[20][i].in[0] <== in[i]; + eq[20][i].in[1] <== 32; + and[15][i] = AND(); + and[15][i].a <== states[i][9]; + multi_or[6][i] = MultiOR(3); + multi_or[6][i].in[0] <== eq[18][i].out; + multi_or[6][i].in[1] <== eq[19][i].out; + multi_or[6][i].in[2] <== eq[20][i].out; + and[15][i].b <== multi_or[6][i].out; + eq[21][i] = IsEqual(); + eq[21][i].in[0] <== in[i]; + eq[21][i].in[1] <== 64; + eq[22][i] = IsEqual(); + eq[22][i].in[0] <== in[i]; + eq[22][i].in[1] <== 34; + eq[23][i] = IsEqual(); + eq[23][i].in[0] <== in[i]; + eq[23][i].in[1] <== 32; + and[16][i] = AND(); + and[16][i].a <== states[i][11]; + multi_or[7][i] = MultiOR(3); + multi_or[7][i].in[0] <== eq[21][i].out; + multi_or[7][i].in[1] <== eq[22][i].out; + multi_or[7][i].in[2] <== eq[23][i].out; + and[16][i].b <== multi_or[7][i].out; + multi_or[8][i] = MultiOR(5); + multi_or[8][i].in[0] <== and[9][i].out; + multi_or[8][i].in[1] <== and[13][i].out; + multi_or[8][i].in[2] <== and[14][i].out; + multi_or[8][i].in[3] <== and[15][i].out; + multi_or[8][i].in[4] <== and[16][i].out; + states[i+1][3] <== multi_or[8][i].out; + eq[24][i] = IsEqual(); + eq[24][i].in[0] <== in[i]; + eq[24][i].in[1] <== 128; + and[17][i] = AND(); + and[17][i].a <== states[i][0]; + and[17][i].b <== eq[24][i].out; + eq[25][i] = IsEqual(); + eq[25][i].in[0] <== in[i]; + eq[25][i].in[1] <== 10; + and[18][i] = AND(); + and[18][i].a <== states[i][2]; + and[18][i].b <== eq[25][i].out; + multi_or[9][i] = MultiOR(2); + multi_or[9][i].in[0] <== and[17][i].out; + multi_or[9][i].in[1] <== and[18][i].out; + states[i+1][4] <== multi_or[9][i].out; + eq[26][i] = IsEqual(); + eq[26][i].in[0] <== in[i]; + eq[26][i].in[1] <== 60; + and[19][i] = AND(); + and[19][i].a <== states[i][1]; + and[19][i].b <== eq[26][i].out; + eq[27][i] = IsEqual(); + eq[27][i].in[0] <== in[i]; + eq[27][i].in[1] <== 60; + and[20][i] = AND(); + and[20][i].a <== states[i][3]; + and[20][i].b <== eq[27][i].out; + eq[28][i] = IsEqual(); + eq[28][i].in[0] <== in[i]; + eq[28][i].in[1] <== 60; + and[21][i] = AND(); + and[21][i].a <== states[i][8]; + and[21][i].b <== eq[28][i].out; + eq[29][i] = IsEqual(); + eq[29][i].in[0] <== in[i]; + eq[29][i].in[1] <== 60; + and[22][i] = AND(); + and[22][i].a <== states[i][11]; + and[22][i].b <== eq[29][i].out; + multi_or[10][i] = MultiOR(4); + multi_or[10][i].in[0] <== and[19][i].out; + multi_or[10][i].in[1] <== and[20][i].out; + multi_or[10][i].in[2] <== and[21][i].out; + multi_or[10][i].in[3] <== and[22][i].out; + states[i+1][5] <== multi_or[10][i].out; + eq[30][i] = IsEqual(); + eq[30][i].in[0] <== in[i]; + eq[30][i].in[1] <== 116; + and[23][i] = AND(); + and[23][i].a <== states[i][4]; + and[23][i].b <== eq[30][i].out; + states[i+1][6] <== and[23][i].out; + eq[31][i] = IsEqual(); + eq[31][i].in[0] <== in[i]; + eq[31][i].in[1] <== 111; + and[24][i] = AND(); + and[24][i].a <== states[i][6]; + and[24][i].b <== eq[31][i].out; + states[i+1][7] <== and[24][i].out; + eq[32][i] = IsEqual(); + eq[32][i].in[0] <== in[i]; + eq[32][i].in[1] <== 64; + and[25][i] = AND(); + and[25][i].a <== states[i][1]; + and[25][i].b <== eq[32][i].out; + states[i+1][8] <== and[25][i].out; + eq[33][i] = IsEqual(); + eq[33][i].in[0] <== in[i]; + eq[33][i].in[1] <== 58; + and[26][i] = AND(); + and[26][i].a <== states[i][7]; + and[26][i].b <== eq[33][i].out; + states[i+1][9] <== and[26][i].out; + lt[18][i] = LessThan(8); + lt[18][i].in[0] <== 64; + lt[18][i].in[1] <== in[i]; + lt[19][i] = LessThan(8); + lt[19][i].in[0] <== in[i]; + lt[19][i].in[1] <== 91; + and[27][i] = AND(); + and[27][i].a <== lt[18][i].out; + and[27][i].b <== lt[19][i].out; + lt[20][i] = LessThan(8); + lt[20][i].in[0] <== 96; + lt[20][i].in[1] <== in[i]; + lt[21][i] = LessThan(8); + lt[21][i].in[0] <== in[i]; + lt[21][i].in[1] <== 123; + and[28][i] = AND(); + and[28][i].a <== lt[20][i].out; + and[28][i].b <== lt[21][i].out; + lt[22][i] = LessThan(8); + lt[22][i].in[0] <== 47; + lt[22][i].in[1] <== in[i]; + lt[23][i] = LessThan(8); + lt[23][i].in[0] <== in[i]; + lt[23][i].in[1] <== 58; + and[29][i] = AND(); + and[29][i].a <== lt[22][i].out; + and[29][i].b <== lt[23][i].out; + eq[34][i] = IsEqual(); + eq[34][i].in[0] <== in[i]; + eq[34][i].in[1] <== 45; + eq[35][i] = IsEqual(); + eq[35][i].in[0] <== in[i]; + eq[35][i].in[1] <== 46; + eq[36][i] = IsEqual(); + eq[36][i].in[0] <== in[i]; + eq[36][i].in[1] <== 95; + and[30][i] = AND(); + and[30][i].a <== states[i][5]; + multi_or[11][i] = MultiOR(6); + multi_or[11][i].in[0] <== and[27][i].out; + multi_or[11][i].in[1] <== and[28][i].out; + multi_or[11][i].in[2] <== and[29][i].out; + multi_or[11][i].in[3] <== eq[34][i].out; + multi_or[11][i].in[4] <== eq[35][i].out; + multi_or[11][i].in[5] <== eq[36][i].out; + and[30][i].b <== multi_or[11][i].out; + lt[24][i] = LessThan(8); + lt[24][i].in[0] <== 64; + lt[24][i].in[1] <== in[i]; + lt[25][i] = LessThan(8); + lt[25][i].in[0] <== in[i]; + lt[25][i].in[1] <== 91; + and[31][i] = AND(); + and[31][i].a <== lt[24][i].out; + and[31][i].b <== lt[25][i].out; + lt[26][i] = LessThan(8); + lt[26][i].in[0] <== 96; + lt[26][i].in[1] <== in[i]; + lt[27][i] = LessThan(8); + lt[27][i].in[0] <== in[i]; + lt[27][i].in[1] <== 123; + and[32][i] = AND(); + and[32][i].a <== lt[26][i].out; + and[32][i].b <== lt[27][i].out; + lt[28][i] = LessThan(8); + lt[28][i].in[0] <== 47; + lt[28][i].in[1] <== in[i]; + lt[29][i] = LessThan(8); + lt[29][i].in[0] <== in[i]; + lt[29][i].in[1] <== 58; + and[33][i] = AND(); + and[33][i].a <== lt[28][i].out; + and[33][i].b <== lt[29][i].out; + eq[37][i] = IsEqual(); + eq[37][i].in[0] <== in[i]; + eq[37][i].in[1] <== 45; + eq[38][i] = IsEqual(); + eq[38][i].in[0] <== in[i]; + eq[38][i].in[1] <== 46; + eq[39][i] = IsEqual(); + eq[39][i].in[0] <== in[i]; + eq[39][i].in[1] <== 95; + and[34][i] = AND(); + and[34][i].a <== states[i][10]; + multi_or[12][i] = MultiOR(6); + multi_or[12][i].in[0] <== and[31][i].out; + multi_or[12][i].in[1] <== and[32][i].out; + multi_or[12][i].in[2] <== and[33][i].out; + multi_or[12][i].in[3] <== eq[37][i].out; + multi_or[12][i].in[4] <== eq[38][i].out; + multi_or[12][i].in[5] <== eq[39][i].out; + and[34][i].b <== multi_or[12][i].out; + multi_or[13][i] = MultiOR(2); + multi_or[13][i].in[0] <== and[30][i].out; + multi_or[13][i].in[1] <== and[34][i].out; + states[i+1][10] <== multi_or[13][i].out; + lt[30][i] = LessThan(8); + lt[30][i].in[0] <== 64; + lt[30][i].in[1] <== in[i]; + lt[31][i] = LessThan(8); + lt[31][i].in[0] <== in[i]; + lt[31][i].in[1] <== 91; + and[35][i] = AND(); + and[35][i].a <== lt[30][i].out; + and[35][i].b <== lt[31][i].out; + lt[32][i] = LessThan(8); + lt[32][i].in[0] <== 96; + lt[32][i].in[1] <== in[i]; + lt[33][i] = LessThan(8); + lt[33][i].in[0] <== in[i]; + lt[33][i].in[1] <== 123; + and[36][i] = AND(); + and[36][i].a <== lt[32][i].out; + and[36][i].b <== lt[33][i].out; + lt[34][i] = LessThan(8); + lt[34][i].in[0] <== 47; + lt[34][i].in[1] <== in[i]; + lt[35][i] = LessThan(8); + lt[35][i].in[0] <== in[i]; + lt[35][i].in[1] <== 58; + and[37][i] = AND(); + and[37][i].a <== lt[34][i].out; + and[37][i].b <== lt[35][i].out; + eq[40][i] = IsEqual(); + eq[40][i].in[0] <== in[i]; + eq[40][i].in[1] <== 45; + eq[41][i] = IsEqual(); + eq[41][i].in[0] <== in[i]; + eq[41][i].in[1] <== 46; + eq[42][i] = IsEqual(); + eq[42][i].in[0] <== in[i]; + eq[42][i].in[1] <== 95; + and[38][i] = AND(); + and[38][i].a <== states[i][8]; + multi_or[14][i] = MultiOR(6); + multi_or[14][i].in[0] <== and[35][i].out; + multi_or[14][i].in[1] <== and[36][i].out; + multi_or[14][i].in[2] <== and[37][i].out; + multi_or[14][i].in[3] <== eq[40][i].out; + multi_or[14][i].in[4] <== eq[41][i].out; + multi_or[14][i].in[5] <== eq[42][i].out; + and[38][i].b <== multi_or[14][i].out; + lt[36][i] = LessThan(8); + lt[36][i].in[0] <== 64; + lt[36][i].in[1] <== in[i]; + lt[37][i] = LessThan(8); + lt[37][i].in[0] <== in[i]; + lt[37][i].in[1] <== 91; + and[39][i] = AND(); + and[39][i].a <== lt[36][i].out; + and[39][i].b <== lt[37][i].out; + lt[38][i] = LessThan(8); + lt[38][i].in[0] <== 96; + lt[38][i].in[1] <== in[i]; + lt[39][i] = LessThan(8); + lt[39][i].in[0] <== in[i]; + lt[39][i].in[1] <== 123; + and[40][i] = AND(); + and[40][i].a <== lt[38][i].out; + and[40][i].b <== lt[39][i].out; + lt[40][i] = LessThan(8); + lt[40][i].in[0] <== 47; + lt[40][i].in[1] <== in[i]; + lt[41][i] = LessThan(8); + lt[41][i].in[0] <== in[i]; + lt[41][i].in[1] <== 58; + and[41][i] = AND(); + and[41][i].a <== lt[40][i].out; + and[41][i].b <== lt[41][i].out; + eq[43][i] = IsEqual(); + eq[43][i].in[0] <== in[i]; + eq[43][i].in[1] <== 45; + eq[44][i] = IsEqual(); + eq[44][i].in[0] <== in[i]; + eq[44][i].in[1] <== 46; + eq[45][i] = IsEqual(); + eq[45][i].in[0] <== in[i]; + eq[45][i].in[1] <== 95; + and[42][i] = AND(); + and[42][i].a <== states[i][11]; + multi_or[15][i] = MultiOR(6); + multi_or[15][i].in[0] <== and[39][i].out; + multi_or[15][i].in[1] <== and[40][i].out; + multi_or[15][i].in[2] <== and[41][i].out; + multi_or[15][i].in[3] <== eq[43][i].out; + multi_or[15][i].in[4] <== eq[44][i].out; + multi_or[15][i].in[5] <== eq[45][i].out; + and[42][i].b <== multi_or[15][i].out; + multi_or[16][i] = MultiOR(2); + multi_or[16][i].in[0] <== and[38][i].out; + multi_or[16][i].in[1] <== and[42][i].out; + states[i+1][11] <== multi_or[16][i].out; + eq[46][i] = IsEqual(); + eq[46][i].in[0] <== in[i]; + eq[46][i].in[1] <== 64; + and[43][i] = AND(); + and[43][i].a <== states[i][10]; + and[43][i].b <== eq[46][i].out; + states[i+1][12] <== and[43][i].out; + eq[47][i] = IsEqual(); + eq[47][i].in[0] <== in[i]; + eq[47][i].in[1] <== 13; + and[44][i] = AND(); + and[44][i].a <== states[i][11]; + and[44][i].b <== eq[47][i].out; + eq[48][i] = IsEqual(); + eq[48][i].in[0] <== in[i]; + eq[48][i].in[1] <== 13; + and[45][i] = AND(); + and[45][i].a <== states[i][14]; + and[45][i].b <== eq[48][i].out; + eq[49][i] = IsEqual(); + eq[49][i].in[0] <== in[i]; + eq[49][i].in[1] <== 13; + and[46][i] = AND(); + and[46][i].a <== states[i][15]; + and[46][i].b <== eq[49][i].out; + multi_or[17][i] = MultiOR(3); + multi_or[17][i].in[0] <== and[44][i].out; + multi_or[17][i].in[1] <== and[45][i].out; + multi_or[17][i].in[2] <== and[46][i].out; + states[i+1][13] <== multi_or[17][i].out; + eq[50][i] = IsEqual(); + eq[50][i].in[0] <== in[i]; + eq[50][i].in[1] <== 62; + and[47][i] = AND(); + and[47][i].a <== states[i][11]; + and[47][i].b <== eq[50][i].out; + eq[51][i] = IsEqual(); + eq[51][i].in[0] <== in[i]; + eq[51][i].in[1] <== 62; + and[48][i] = AND(); + and[48][i].a <== states[i][15]; + and[48][i].b <== eq[51][i].out; + multi_or[18][i] = MultiOR(2); + multi_or[18][i].in[0] <== and[47][i].out; + multi_or[18][i].in[1] <== and[48][i].out; + states[i+1][14] <== multi_or[18][i].out; + lt[42][i] = LessThan(8); + lt[42][i].in[0] <== 64; + lt[42][i].in[1] <== in[i]; + lt[43][i] = LessThan(8); + lt[43][i].in[0] <== in[i]; + lt[43][i].in[1] <== 91; + and[49][i] = AND(); + and[49][i].a <== lt[42][i].out; + and[49][i].b <== lt[43][i].out; + lt[44][i] = LessThan(8); + lt[44][i].in[0] <== 96; + lt[44][i].in[1] <== in[i]; + lt[45][i] = LessThan(8); + lt[45][i].in[0] <== in[i]; + lt[45][i].in[1] <== 123; + and[50][i] = AND(); + and[50][i].a <== lt[44][i].out; + and[50][i].b <== lt[45][i].out; + lt[46][i] = LessThan(8); + lt[46][i].in[0] <== 47; + lt[46][i].in[1] <== in[i]; + lt[47][i] = LessThan(8); + lt[47][i].in[0] <== in[i]; + lt[47][i].in[1] <== 58; + and[51][i] = AND(); + and[51][i].a <== lt[46][i].out; + and[51][i].b <== lt[47][i].out; + eq[52][i] = IsEqual(); + eq[52][i].in[0] <== in[i]; + eq[52][i].in[1] <== 45; + eq[53][i] = IsEqual(); + eq[53][i].in[0] <== in[i]; + eq[53][i].in[1] <== 46; + eq[54][i] = IsEqual(); + eq[54][i].in[0] <== in[i]; + eq[54][i].in[1] <== 95; + and[52][i] = AND(); + and[52][i].a <== states[i][12]; + multi_or[19][i] = MultiOR(6); + multi_or[19][i].in[0] <== and[49][i].out; + multi_or[19][i].in[1] <== and[50][i].out; + multi_or[19][i].in[2] <== and[51][i].out; + multi_or[19][i].in[3] <== eq[52][i].out; + multi_or[19][i].in[4] <== eq[53][i].out; + multi_or[19][i].in[5] <== eq[54][i].out; + and[52][i].b <== multi_or[19][i].out; + lt[48][i] = LessThan(8); + lt[48][i].in[0] <== 64; + lt[48][i].in[1] <== in[i]; + lt[49][i] = LessThan(8); + lt[49][i].in[0] <== in[i]; + lt[49][i].in[1] <== 91; + and[53][i] = AND(); + and[53][i].a <== lt[48][i].out; + and[53][i].b <== lt[49][i].out; + lt[50][i] = LessThan(8); + lt[50][i].in[0] <== 96; + lt[50][i].in[1] <== in[i]; + lt[51][i] = LessThan(8); + lt[51][i].in[0] <== in[i]; + lt[51][i].in[1] <== 123; + and[54][i] = AND(); + and[54][i].a <== lt[50][i].out; + and[54][i].b <== lt[51][i].out; + lt[52][i] = LessThan(8); + lt[52][i].in[0] <== 47; + lt[52][i].in[1] <== in[i]; + lt[53][i] = LessThan(8); + lt[53][i].in[0] <== in[i]; + lt[53][i].in[1] <== 58; + and[55][i] = AND(); + and[55][i].a <== lt[52][i].out; + and[55][i].b <== lt[53][i].out; + eq[55][i] = IsEqual(); + eq[55][i].in[0] <== in[i]; + eq[55][i].in[1] <== 45; + eq[56][i] = IsEqual(); + eq[56][i].in[0] <== in[i]; + eq[56][i].in[1] <== 46; + eq[57][i] = IsEqual(); + eq[57][i].in[0] <== in[i]; + eq[57][i].in[1] <== 95; + and[56][i] = AND(); + and[56][i].a <== states[i][15]; + multi_or[20][i] = MultiOR(6); + multi_or[20][i].in[0] <== and[53][i].out; + multi_or[20][i].in[1] <== and[54][i].out; + multi_or[20][i].in[2] <== and[55][i].out; + multi_or[20][i].in[3] <== eq[55][i].out; + multi_or[20][i].in[4] <== eq[56][i].out; + multi_or[20][i].in[5] <== eq[57][i].out; + and[56][i].b <== multi_or[20][i].out; + multi_or[21][i] = MultiOR(2); + multi_or[21][i].in[0] <== and[52][i].out; + multi_or[21][i].in[1] <== and[56][i].out; + states[i+1][15] <== multi_or[21][i].out; + eq[58][i] = IsEqual(); + eq[58][i].in[0] <== in[i]; + eq[58][i].in[1] <== 10; + and[57][i] = AND(); + and[57][i].a <== states[i][13]; + and[57][i].b <== eq[58][i].out; + states[i+1][16] <== and[57][i].out; + } + + signal final_state_sum[num_bytes+1]; + final_state_sum[0] <== states[0][16]; + for (var i = 1; i <= num_bytes; i++) { + final_state_sum[i] <== final_state_sum[i-1] + states[i][16]; + } + out <== final_state_sum[num_bytes]; + + signal to_email_unshifted[num_bytes]; + for (var i = 0; i < num_bytes; i++) { + to_email_unshifted[i] <== in[i] * (states[i+1][15] + states[i+1][11] + states[i+1][12] + states[i+1][8] + states[i+1][10] + states[i+1][1]); + } + + signal output to_email[num_bytes]; + var num_bytes_bits = log_ceil(num_bytes) + 1; + component variable_subarray = VarSubarray(num_bytes, num_bytes_bits); + for (var i = 0; i < num_bytes; i++) { + variable_subarray.in[i] <== to_email_unshifted[i]; + } + variable_subarray.start <== email_to_idx + 3; + variable_subarray.end <== num_bytes; + for (var i = 0; i < num_bytes; i++) { + to_email[i] <== variable_subarray.out[i]; + } +} \ No newline at end of file diff --git a/circuits/variable_subarray.circom b/circuits/variable_subarray.circom new file mode 100644 index 000000000..ead7910ef --- /dev/null +++ b/circuits/variable_subarray.circom @@ -0,0 +1,79 @@ +pragma circom 2.0.3; + +include "../node_modules/circomlib/circuits/bitify.circom"; +include "../node_modules/circomlib/circuits/comparators.circom"; + +template VarShiftLeft(n, nBits) { + signal input in[n]; // x + signal input shift; // k + + signal output out[n]; // y + + component n2b = Num2Bits(nBits); + n2b.in <== shift; + + signal tmp[nBits][n]; + for (var j = 0; j < nBits; j++) { + for (var i = 0; i < n; i++) { + var offset = (i + (1 << j)) % n; + // Shift left by 2^j indices if bit is 1 + if (j == 0) { + tmp[j][i] <== n2b.out[j] * (in[offset] - in[i]) + in[i]; + } else { + tmp[j][i] <== n2b.out[j] * (tmp[j-1][offset] - tmp[j-1][i]) + tmp[j-1][i]; + } + } + } + + // Return last row + for (var i = 0; i < n; i++) { + out[i] <== tmp[nBits - 1][i]; + } +} + +template VarSubarrayFromZeroIndex(n, nBits) { + signal input in[n]; // x + signal input end; // k + + signal output out[n]; // y + + component lt[n]; + for (var i = 0; i < n; i++) { + lt[i] = LessThan(nBits); + lt[i].in[0] <== i; + lt[i].in[1] <== end; + + // y[i] = (i < k) * x[i] + out[i] <== lt[i].out * in[i]; + } +} + +// l, h lie in [0, n) +// the first values of out are the values at indices [l, h) of in +// the remainder of out is 0-padded +// nBits = floor(log n) + 1 +template VarSubarray(n, nBits) { + signal input in[n]; // x + signal input start; // l + signal input end; // h + + signal output out[n]; + + // Check that l < h + component lt = LessThan(nBits); + lt.in[0] <== start; + lt.in[1] <== end; + lt.out === 1; + + // Shift left by l indices + component shiftLeft = VarShiftLeft(n, nBits); + shiftLeft.in <== in; + shiftLeft.shift <== start; + + // Take first (h - l) indices + component subarrayFromZeroIndex = VarSubarrayFromZeroIndex(n, nBits); + subarrayFromZeroIndex.in <== shiftLeft.out; + subarrayFromZeroIndex.end <== end - start; + + out <== subarrayFromZeroIndex.out; +} \ No newline at end of file diff --git a/dizkus-scripts/1_compile_airbnb.sh b/dizkus-scripts/1_compile_airbnb.sh new file mode 100755 index 000000000..1aec5cc4b --- /dev/null +++ b/dizkus-scripts/1_compile_airbnb.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +CIRCUIT_NAME=email_airbnb +BUILD_DIR="../build/$CIRCUIT_NAME" + +if [ ! -d "$BUILD_DIR" ]; then + echo "No build directory found. Creating build directory..." + mkdir -p "$BUILD_DIR" +fi + +echo '****COMPILING CIRCUIT****' +start=`date +%s` +set -x +circom "../circuits/$CIRCUIT_NAME".circom --r1cs --wasm --sym --c --wat --output "$BUILD_DIR" +{ set +x; } 2>/dev/null +end=`date +%s` +echo "DONE ($((end-start))s)" +echo diff --git a/dizkus-scripts/1_compile_coinbase.sh b/dizkus-scripts/1_compile_coinbase.sh new file mode 100755 index 000000000..cbbec6a65 --- /dev/null +++ b/dizkus-scripts/1_compile_coinbase.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +CIRCUIT_NAME=email_coinbase +BUILD_DIR="../build/$CIRCUIT_NAME" + +if [ ! -d "$BUILD_DIR" ]; then + echo "No build directory found. Creating build directory..." + mkdir -p "$BUILD_DIR" +fi + +echo '****COMPILING CIRCUIT****' +start=`date +%s` +set -x +circom "../circuits/$CIRCUIT_NAME".circom --r1cs --wasm --sym --c --wat --output "$BUILD_DIR" +{ set +x; } 2>/dev/null +end=`date +%s` +echo "DONE ($((end-start))s)" +echo diff --git a/dizkus-scripts/1_compile_kyc.sh b/dizkus-scripts/1_compile_kyc.sh new file mode 100755 index 000000000..4ba8ce8d3 --- /dev/null +++ b/dizkus-scripts/1_compile_kyc.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +CIRCUIT_NAME=email_both +BUILD_DIR="../build/$CIRCUIT_NAME" + +if [ ! -d "$BUILD_DIR" ]; then + echo "No build directory found. Creating build directory..." + mkdir -p "$BUILD_DIR" +fi + +echo '****COMPILING CIRCUIT****' +start=`date +%s` +set -x +circom "../circuits/$CIRCUIT_NAME".circom --r1cs --wasm --sym --c --wat --output "$BUILD_DIR" +{ set +x; } 2>/dev/null +end=`date +%s` +echo "DONE ($((end-start))s)" +echo diff --git a/dizkus-scripts/2_gen_wtns_airbnb.sh b/dizkus-scripts/2_gen_wtns_airbnb.sh new file mode 100755 index 000000000..f3f38e1e0 --- /dev/null +++ b/dizkus-scripts/2_gen_wtns_airbnb.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +CIRCUIT_NAME=email_airbnb +BUILD_DIR="../build/$CIRCUIT_NAME" + +echo "****GENERATING WITNESS FOR SAMPLE INPUT****" +start=`date +%s` +set -x +node "$BUILD_DIR"/"$CIRCUIT_NAME"_js/generate_witness.js "$BUILD_DIR"/"$CIRCUIT_NAME"_js/"$CIRCUIT_NAME".wasm ../circuits/inputs/input_airbnb.json "$BUILD_DIR"/witness.wtns +{ set +x; } 2>/dev/null +end=`date +%s` +echo "DONE ($((end-start))s)" +echo diff --git a/dizkus-scripts/2_gen_wtns_coinbase.sh b/dizkus-scripts/2_gen_wtns_coinbase.sh new file mode 100755 index 000000000..f52bdcf25 --- /dev/null +++ b/dizkus-scripts/2_gen_wtns_coinbase.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +CIRCUIT_NAME=email_coinbase +BUILD_DIR="../build/$CIRCUIT_NAME" + +echo "****GENERATING WITNESS FOR SAMPLE INPUT****" +start=`date +%s` +set -x +node "$BUILD_DIR"/"$CIRCUIT_NAME"_js/generate_witness.js "$BUILD_DIR"/"$CIRCUIT_NAME"_js/"$CIRCUIT_NAME".wasm ../circuits/inputs/input_coinbase.json "$BUILD_DIR"/witness.wtns +{ set +x; } 2>/dev/null +end=`date +%s` +echo "DONE ($((end-start))s)" +echo diff --git a/dizkus-scripts/2_gen_wtns_kyc.sh b/dizkus-scripts/2_gen_wtns_kyc.sh new file mode 100755 index 000000000..d0862a961 --- /dev/null +++ b/dizkus-scripts/2_gen_wtns_kyc.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +CIRCUIT_NAME=email_both +BUILD_DIR="../build/$CIRCUIT_NAME" + +echo "****GENERATING WITNESS FOR SAMPLE INPUT****" +start=`date +%s` +set -x +node "$BUILD_DIR"/"$CIRCUIT_NAME"_js/generate_witness.js "$BUILD_DIR"/"$CIRCUIT_NAME"_js/"$CIRCUIT_NAME".wasm ../circuits/inputs/input_kyc.json "$BUILD_DIR"/witness.wtns +{ set +x; } 2>/dev/null +end=`date +%s` +echo "DONE ($((end-start))s)" +echo diff --git a/package.json b/package.json index baf93480e..e4439d629 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "react-use": "^17.3.2", "readline": "^1.3.0", "serve": "^14.0.1", - "snarkjs": "https://github.com/sampritipanda/snarkjs.git#fef81fc51d17a734637555c6edbd585ecda02d9e", + "snarkjs": "git+https://github.com/vb7401/snarkjs.git#24981febe8826b6ab76ae4d76cf7f9142919d2b8", "sshpk": "^1.17.0", "styled-components": "^5.3.5", "ts-node": "^10.9.1", @@ -63,8 +63,7 @@ "eject": "react-scripts eject", "compile": "node circuits/scripts/compile.js", "gen-input": "npx tsx src/scripts/generate_input.ts", - "compile-all": "yarn gen-input && yarn compile email true", - "prepare": "husky install" + "compile-all": "yarn gen-input && yarn compile email true" }, "eslintConfig": { "extends": [ @@ -93,7 +92,6 @@ "@types/tar-stream": "^2.2.2", "browserstack-local": "^1.5.1", "browserstack-node-sdk": "^1.6.1", - "husky": "^8.0.3", "jest-junit": "^15.0.0", "msw": "^1.0.1", "nodemon": "^2.0.19", diff --git a/regex_to_circom/README.md b/regex_to_circom/README.md index d37f2a848..e5506d10e 100644 --- a/regex_to_circom/README.md +++ b/regex_to_circom/README.md @@ -15,10 +15,3 @@ You can use the compiled halo2_regex_lookup.txt file as input to the https://git states[i+1][j] means that there was a character at msg[i] which led to the transition to state j. This means that reveal for index i should be looking at state of index i+1. - -## Some regexes - -There are more in the regex section of the top level readme. Here are som examples however, for instance for from/subject/to order-free extraction: - -raw regex: ((\\n|\x80|^)(((from):([A-Za-z0-9 _."@-]+<)?[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.]+>)?|(subject:[a-zA-Z 0-9]+)?|((to):([A-Za-z0-9 _."@-]+<)?[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.]+>)?)(\\r))+ -min-dfa version: (((\n|^)(((from):([A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|0|1|2|3|4|5|6|7|8|9| |_|.|"|@|-]+<)?[a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|0|1|2|3|4|5|6|7|8|9|_|.|-]+@[a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|0|1|2|3|4|5|6|7|8|9|_|.]+>)?|(subject:[a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z| |0|1|2|3|4|5|6|7|8|9]+)?|((to):([A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|0|1|2|3|4|5|6|7|8|9| |_|.|"|@|-]+<)?[a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|0|1|2|3|4|5|6|7|8|9|_|.|-]+@[a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|0|1|2|3|4|5|6|7|8|9|_|.]+>)?)(\r))+) diff --git a/regex_to_circom/gen.py b/regex_to_circom/gen.py index 6b74217b9..1e37af666 100644 --- a/regex_to_circom/gen.py +++ b/regex_to_circom/gen.py @@ -2,9 +2,6 @@ import json import string -# Clear file -OUTPUT_HALO2 = True - graph_json = json.loads(subprocess.check_output(['npx', 'tsx', 'lexical.js'])) N = len(graph_json) @@ -13,7 +10,6 @@ # Incoming Nodes rev_graph = [[] for i in range(N)] accept_nodes = set() - for i in range(N): for k in graph_json[i]['edges']: # assert len(k) == 1 @@ -21,31 +17,12 @@ v = graph_json[i]['edges'][k] graph[i][k] = v rev_graph[v].append((k, i)) - # Iterates over value in set for halo2 lookup, append to file - if graph_json[i]['type'] == 'accept': accept_nodes.add(i) accept_nodes = list(accept_nodes) assert len(accept_nodes) == 1 -if (OUTPUT_HALO2): - with open('halo2_regex_lookup.txt', 'w') as f: - for a in accept_nodes: - print(str(a) + " ", file=f, end='') - print("", file=f) - for i in range(N): - for k in graph_json[i]['edges']: - v = graph_json[i]['edges'][k] - for val in json.loads(k): - with open('halo2_regex_lookup.txt', 'a') as f: - print(i, v, ord(val), file=f) - -print("Accept node:", accept_nodes) -print("Rev graph:", rev_graph) -print("Graph:", graph) -print("Graph json:", graph_json) - eq_i = 0 lt_i = 0 and_i = 0 @@ -56,6 +33,10 @@ assert 0 not in accept_nodes +# Clear file +with open('halo2_regex_lookup.txt', 'w') as f: + print("", file=f) + for i in range(1, N): outputs = [] for k, prev_i in rev_graph[i]: @@ -67,6 +48,13 @@ digits = set(string.digits) vals = set(vals) + # Iterates over value in set for halo2 lookup, append to file + OUTPUT_HALO2 = True + if (OUTPUT_HALO2): + for val in vals: + with open('halo2_regex_lookup.txt', 'a') as f: + print(prev_i, prev_i + 1, ord(val), file=f) + if uppercase <= vals: vals -= uppercase lines.append(f"\tlt[{lt_i}][i] = LessThan(8);") diff --git a/regex_to_circom/halo2_regex_lookup.txt b/regex_to_circom/halo2_regex_lookup.txt new file mode 100644 index 000000000..4bfa47417 --- /dev/null +++ b/regex_to_circom/halo2_regex_lookup.txt @@ -0,0 +1,24 @@ + +0 1 89 +1 2 111 +2 3 117 +3 4 39 +4 5 114 +5 6 101 +6 7 32 +7 8 114 +8 9 101 +9 10 97 +10 11 100 +11 12 121 +12 13 32 +13 14 116 +14 15 111 +15 16 32 +16 17 105 +17 18 110 +18 19 118 +19 20 101 +20 21 115 +21 22 116 +22 23 33 diff --git a/regex_to_circom/lexical.js b/regex_to_circom/lexical.js index 87e628fda..89070c947 100644 --- a/regex_to_circom/lexical.js +++ b/regex_to_circom/lexical.js @@ -1,80 +1,25 @@ /*jslint browser: true*/ /*global require, exports*/ -import { STRING_PRESELECTOR } from "../src/helpers/constants.ts"; +import { STRING_PRESELECTOR, STRING_PRESELECTOR_AIRBNB , STRING_PRESELECTOR_COINBASE } from "../src/helpers/constants.ts"; /** This section sets the 'regex' variable to the regex you want to use. * All of the relevant regexes are in the main repo README. */ -const a2z = "a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z"; -const A2Z = "A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z"; -const r0to9 = "0|1|2|3|4|5|6|7|8|9"; -const alphanum = `${a2z}|${A2Z}|${r0to9}`; - -const key_chars = `(${a2z})`; +const key_chars = "(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z)"; const catch_all = "(0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|!|\"|#|$|%|&|'|\\(|\\)|\\*|\\+|,|-|.|/|:|;|<|=|>|\\?|@|[|\\\\|]|^|_|`|{|\\||}|~| |\t|\n|\r|\x0b|\x0c)"; const catch_all_without_semicolon = "(0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|!|\"|#|$|%|&|'|\\(|\\)|\\*|\\+|,|-|.|/|:|<|=|>|\\?|@|[|\\\\|]|^|_|`|{|\\||}|~| |\t|\n|\r|\x0b|\x0c)"; - -const email_chars = `${alphanum}|_|.|-`; -const base_64 = `(${alphanum}|\\+|/|=)`; -const word_char = `(${alphanum}|_)`; - -// let to_from_regex_old = '(\r\n|\x80)(to|from):([A-Za-z0-9 _."@-]+<)?[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.]+>?\r\n'; -// let regex = `\r\ndkim-signature:(${key_chars}=${catch_all_without_semicolon}+; )+bh=${base_64}+; `; -// let sig_regex = `${catch_all_without_semicolon}\r\ndkim-signature:(${key_chars}=${catch_all_without_semicolon}+; )+bh=${base_64}+; `; -// let order_invariant_regex_raw = `((\\n|\x80|^)(((from):([A-Za-z0-9 _."@-]+<)?[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.]+>)?|(subject:[a-zA-Z 0-9]+)?|((to):([A-Za-z0-9 _."@-]+<)?[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.]+>)?|(dkim-signature:((a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z)=(0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|!|"|#|$|%|&|\'|\\(|\\)|\\*|\\+|,|-|.|/|:|<|=|>|\\?|@|[|\\\\|]|^|_|`|{|\\||}|~| |\t|\n|\r|\x0B|\f)+; ))?)(\\r))+` // Uses a-z syntax instead of | for each char - -const a2z_nosep = "abcdefghijklmnopqrstuvwxyz"; -const A2Z_nosep = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; -const r0to9_nosep = "0123456789"; - -// Note that in order to specify this string in regex, we must use \\ to escape \'s i.e. in the \r\n -let order_invariant_header_regex_raw = `(((\\n|^)(((from):([A-Za-z0-9 _."@-]+<)?[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.]+>)?|(subject:[a-zA-Z 0-9]+)?|((to):([A-Za-z0-9 _."@-]+<)?[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.]+>)?)(\\r))+)`; - -// Note that this is not complete and very case specific i.e. can only handle a-z and not a-c. -function regexToMinDFASpec(str) { - // Replace all A-Z with A2Z etc - let combined_nosep = str - .replaceAll("A-Z", A2Z_nosep) - .replaceAll("a-z", a2z_nosep) - .replaceAll("0-9", r0to9_nosep) - .replaceAll("\\w", A2Z_nosep + r0to9_nosep + a2z_nosep); - - function addPipeInsideBrackets(str) { - let result = ""; - let insideBrackets = false; - for (let i = 0; i < str.length; i++) { - if (str[i] === "[") { - result += str[i]; - insideBrackets = true; - continue; - } else if (str[i] === "]") { - insideBrackets = false; - } - result += insideBrackets ? "|" + str[i] : str[i]; - } - return result.replaceAll("[|", "[").replaceAll("[", "(").replaceAll("]", ")"); - } - - let combined = addPipeInsideBrackets(combined_nosep); - return combined; -} - -let header_regex = regexToMinDFASpec(order_invariant_header_regex_raw); -console.log(order_invariant_header_regex_raw, "\n", header_regex); -let regex = header_regex; - -// let order_invariant_regex_raw = `((\\n|\x80|^)(((from):([A-Za-z0-9 _."@-]+<)?[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.]+>)?|(subject:[a-zA-Z 0-9]+)?|((to):([A-Za-z0-9 _."@-]+<)?[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.]+>)?|(dkim-signature:((a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z)=(0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|!|"|#|$|%|&|\'|\\(|\\)|\\*|\\+|,|-|.|/|:|<|=|>|\\?|@|[|\\\\|]|^|_|\`|{|\\||}|~| |\t|\n|\r|\x0B|\f)+; ))?)(\\r))+` // Uses a-z syntax instead of | for each char +const base_64 = "(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|0|1|2|3|4|5|6|7|8|9|\\+|/|=)"; +const word_char = "(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|0|1|2|3|4|5|6|7|8|9|_)"; // let old_regex = '(\r\n|\x80)(to|from):([A-Za-z0-9 _."@-]+<)?[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.]+>?\r\n'; -// let regex = `(\n|^)(to|from):((${email_chars}|"|@| )+<)?(${email_chars})+@(${email_chars})+>?\r`; -// let regex = `(\r\n|^)(to|from):((${email_chars}|"|@| )+<)?(${email_chars})+@(${email_chars})+>?\r\n`; +// let regex = '(\r\n|\x80)(to|from):((a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|0|1|2|3|4|5|6|7|8|9| |_|.|"|@|-)+<)?(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|0|1|2|3|4|5|6|7|8|9|_|.|-)+@(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|0|1|2|3|4|5|6|7|8|9|_|.|-)+>?\r\n'; // let regex = `\r\ndkim-signature:(${key_chars}=${catch_all_without_semicolon}+; )+bh=${base_64}+; `; -// console.log(regex); // 'dkim-signature:((a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z)=(0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|!|"|#|$|%|&|\'|\\(|\\)|\\*|\\+|,|-|.|/|:|<|=|>|\\?|@|[|\\\\|]|^|_|`|{|\\||}|~| |\t|\n|\r|\x0B|\f)+; )+bh=(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|0|1|2|3|4|5|6|7|8|9|\\+|/|=)+; ' // let regex = STRING_PRESELECTOR + `${word_char}+`; +let regex = STRING_PRESELECTOR_COINBASE; // let regex = 'hello(0|1|2|3|4|5|6|7|8|9)+world'; // console.log(regex); // console.log(Buffer.from(regex).toString('base64')); diff --git a/src/contracts/.env.example b/src/contracts/.env.example deleted file mode 100644 index 5fc4bcec7..000000000 --- a/src/contracts/.env.example +++ /dev/null @@ -1,2 +0,0 @@ -ALCHEMY_GOERLI_KEY= -ETHERSCAN_API_KEY= diff --git a/src/contracts/README.md b/src/contracts/README.md index 54fb5defc..2622778d9 100644 --- a/src/contracts/README.md +++ b/src/contracts/README.md @@ -2,8 +2,6 @@ These contracts need to be modified for each usecase. This includes manually splitting the public key into bigints and passing them in as 17 signals, creating a form of DAO governance to upgrade said key if needed, and a form of DAO governance to upgrade `from emails` if needed. There are multiple contracts: `twitterEmailHandler.sol` does the body verification and from verification for the Twitter password reset email usecase. All code should be built by generalizing this file, then forking from it. We also have one file that verifies just the email to/from domains, `domainEmailHandler.sol`, that is now deprecated, and should be rewritten from the Twitter file if that is the intention. -To get syntax highlighting in VSCode to work, you have to open this directory as the root directory for the Solidity extension to read the remappings properly. - ## Testing To test solidity, @@ -12,7 +10,7 @@ To test solidity, forge install foundry-rs/forge-std cp node_modules/forge-std src/contracts/lib/forge-std cd src/contracts -forge test --via-ir +forge test ``` ## Deployment @@ -20,23 +18,7 @@ forge test --via-ir To deploy contract to forked mainnet, do: ``` -# anvil --fork-url https://eth-mainnet.alchemyapi.io/v2/$ALCHEMY_KEY - -# In tmux window 1 -export ALCHEMY_GOERLI_KEY=... -anvil --fork-url https://eth-goerli.alchemyapi.io/v2/$ALCHEMY_GOERLI_KEY --port 8547 # Run in tmux - -# In normal terminal +anvil --fork-url https://eth-mainnet.alchemyapi.io/v2/***REMOVED*** --port 8547 # Run in tmux export ETH_RPC_URL=http://localhost:8547 -export SK=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 # Public anvil sk - -forge create --rpc-url $ETH_RPC_URL NFTSVG --private-key $SK --via-ir --force | tee /dev/tty | export NFTSVG_ADDR=$(sed -n 's/.*Deployed to: //p') -forge create --rpc-url $ETH_RPC_URL HexStrings --private-key $SK --via-ir --force | tee /dev/tty | export HEXSTRINGS_ADDR=$(sed -n 's/.*Deployed to: //p') -# forge bind --libraries src/hexStrings.sol:hexStrings:$HEXSTRINGS_ADDR --libraries src/NFTSVG.sol:NFTSVG:$NFTSVG_ADDR --via-ir -echo "libraries = [\"src/NFTSVG.sol:NFTSVG:${NFTSVG_ADDR}\", \"src/hexStrings.sol:HexStrings:${HEXSTRINGS_ADDR}\"]" >> foundry.toml -forge create --rpc-url $ETH_RPC_URL VerifiedTwitterEmail --private-key $SK --via-ir --force | tee /dev/tty | export EMAIL_ADDR=$(sed -n 's/.*Deployed to: //p') -sed -i '' -e '$ d' foundry.toml -forge create --rpc-url $ETH_RPC_URL VerifiedTwitterEmail --private-key $SK --via-ir --force --libraries "HexStrings:${HEXSTRINGS_ADDR}","NFTSVG:${NFTSVG_ADDR}" | tee /dev/tty | export EMAIL_ADDR=$(sed -n 's/.*Deployed to: //p') - -forge verify-contract $EMAIL_ADDR VerifiedTwitterEmail --watch --etherscan-api-key $GORLII_ETHERSCAN_API_KEY +forge create --rpc-url $ETH_RPC_URL src/contracts/src/emailVerifier.sol:Verifier --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 # Public anvil sk ``` diff --git a/src/contracts/foundry.toml b/src/contracts/foundry.toml index 1a94e3fa4..6e757cc38 100644 --- a/src/contracts/foundry.toml +++ b/src/contracts/foundry.toml @@ -3,3 +3,4 @@ src = 'src' out = 'out' libs = ['lib'] # See more config options https://github.com/foundry-rs/foundry/tree/master/config remappings = ['ds-test=lib/ds-test/', 'forge-std/=lib/forge-std/'] +libraries = ["src/NFTSVG.sol:NFTSVG:0xC2610931017f044EC88F6867842D5cD6a4A31B20", "src/hexStrings.sol:HexStrings:0xaA8989aFfba4D72B14959E92a132587C3FD4977F"] diff --git a/src/contracts/src/NFTSVG.sol b/src/contracts/src/NFTSVG.sol index 0783f610d..620841c24 100644 --- a/src/contracts/src/NFTSVG.sol +++ b/src/contracts/src/NFTSVG.sol @@ -11,7 +11,6 @@ import "./hexStrings.sol"; library NFTSVG { using Strings for uint256; using Strings for address; - using HexStrings for *; struct SVGParams { string username; @@ -159,6 +158,10 @@ library NFTSVG { } // Helper Fns + function tokenToColorHex(uint256 token, uint256 offset) public pure returns (string memory str) { + return string(HexStrings.toHexStringNoPrefix(token >> offset, 3)); + } + function getCircleCoord(uint256 tokenAddress, uint256 offset, uint256 tokenId) public pure returns (uint256) { return (sliceTokenHex(tokenAddress, offset) * tokenId) % 255; } @@ -168,10 +171,6 @@ library NFTSVG { } function scale(uint256 n, uint256 inMn, uint256 inMx, uint256 outMn, uint256 outMx) public pure returns (string memory) { - return Strings.toString(((n - inMn) * (outMx - outMn)) / (inMx - inMn) + (outMn)); - } - - function tokenToColorHex(uint256 token, uint256 offset) public pure returns (string memory str) { - return string(HexStrings.toHexStringNoPrefix(token >> offset, 3)); + return (((n - inMn) * (outMx - outMn)) / (inMx - inMn) + (outMn)).toString(); } } diff --git a/src/contracts/src/test/TestTwitter.t.sol b/src/contracts/src/test/TestTwitter.t.sol index 7629ca03c..235d1d814 100644 --- a/src/contracts/src/test/TestTwitter.t.sol +++ b/src/contracts/src/test/TestTwitter.t.sol @@ -47,14 +47,6 @@ contract TwitterUtilsTest is Test { intended_value = "zktestemail"; assertEq(bytes32(bytes(byteList)), bytes32(bytes(intended_value))); console.logString(byteList); - - packedBytes[0] = 28557011619965818; - packedBytes[1] = 1818845549; - packedBytes[2] = 0; - byteList = testVerifier.convertPackedBytesToBytes(packedBytes, 15); - intended_value = "zktestemail"; - assertEq(bytes32(bytes(byteList)), bytes32(bytes(intended_value))); - console.logString(byteList); } // Should pass (note that there are extra 0 bytes, which are filtered out but should be noted in audits) @@ -156,12 +148,8 @@ contract TwitterUtilsTest is Test { vm.startPrank(0x6171aeBcC9e9B9E1D90EC9C2E124982932297345); testVerifier.mint(proof_a, proof_b, proof_c, publicSignals); vm.stopPrank(); - } - function testSVG() public { - testVerifyYushEmail(); - testVerifyTestEmail(); string memory svgValue = testVerifier.tokenURI(1); - // console.log(svgValue); + console.log(svgValue); } } diff --git a/src/contracts/src/twitterEmailHandler.sol b/src/contracts/src/twitterEmailHandler.sol index 6cab658a8..2c6a2aa4a 100644 --- a/src/contracts/src/twitterEmailHandler.sol +++ b/src/contracts/src/twitterEmailHandler.sol @@ -6,14 +6,11 @@ import "@openzeppelin/contracts/utils/Strings.sol"; import "@openzeppelin/contracts/utils/Counters.sol"; import "forge-std/console.sol"; // import "./base64.sol"; -import "./hexStrings.sol"; import "./NFTSVG.sol"; import "./emailVerifier.sol"; contract VerifiedTwitterEmail is ERC721Enumerable, Verifier { using Counters for Counters.Counter; - using HexStrings for *; - using NFTSVG for *; Counters.Counter private tokenCounter; @@ -62,7 +59,6 @@ contract VerifiedTwitterEmail is ERC721Enumerable, Verifier { function tokenURI(uint256 tokenId) public view override returns (string memory) { string memory username = tokenIDToName[tokenId]; address owner = ownerOf(tokenId); - NFTSVG.SVGParams memory svgParams = NFTSVG.SVGParams({ username: username, tokenId: tokenId, diff --git a/src/helpers/constants.ts b/src/helpers/constants.ts index 5d3edc90f..1d0f4a0d5 100644 --- a/src/helpers/constants.ts +++ b/src/helpers/constants.ts @@ -20,3 +20,5 @@ export const CIRCOM_LEVELS = 30; // This is the string that comes right before the target string in the email. Ideally as close to the end of the email as possible. export const STRING_PRESELECTOR = "email was meant for @"; +export const STRING_PRESELECTOR_AIRBNB = "Thanks for providing a government ID"; +export const STRING_PRESELECTOR_COINBASE = "You're ready to invest!"; \ No newline at end of file diff --git a/src/helpers/dkim/tools.js b/src/helpers/dkim/tools.js index a24457051..2f102161e 100644 --- a/src/helpers/dkim/tools.js +++ b/src/helpers/dkim/tools.js @@ -1,9 +1,9 @@ /* eslint no-control-regex: 0 */ -var isNode = false; -if (typeof process === "object") { - if (typeof process.versions === "object") { - if (typeof process.versions.node !== "undefined") { +var isNode = false; +if (typeof process === 'object') { + if (typeof process.versions === 'object') { + if (typeof process.versions.node !== 'undefined') { isNode = true; } } @@ -272,16 +272,13 @@ const getPublicKey = async (type, name, minBitLength, resolver) => { [] .concat(list[0] || []) .join("") - .replaceAll(/\s+/g, "") - .replaceAll('"', ""); + .replace(/\s+/g, ""); if (rr) { // prefix value for parsing as there is no default value - let entry = parseDkimHeaders("DNS: TXT;" + rr); + let entry = parseDkimHeaders(`DNS: TXT;${rr}`); const publicKeyValue = entry?.parsed?.p?.value; - //'v=DKIM1;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwe34ubzrMzM9sT0XVkcc3UXd7W+EHCyHoqn70l2AxXox52lAZzH/UnKwAoO+5qsuP7T9QOifIJ9ddNH9lEQ95Y/GdHBsPLGdgSJIs95mXNxscD6MSyejpenMGL9TPQAcxfqY5xPViZ+1wA1qcryjdZKRqf1f4fpMY+x3b8k7H5Qyf/Smz0sv4xFsx1r+THNIz0rzk2LO3GvE0f1ybp6P+5eAelYU4mGeZQqsKw/eB20I3jHWEyGrXuvzB67nt6ddI+N2eD5K38wg/aSytOsb5O+bUSEe7P0zx9ebRRVknCD6uuqG3gSmQmttlD5OrMWSXzrPIXe8eTBaaPd+e/jfxwIDAQAB' - // v=DKIM1;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwe34ubzrMzM9sT0XVkcc3UXd7W+EHCyHoqn70l2AxXox52lAZzH/UnKwAoO+5qsuP7T9QOifIJ9ddNH9lEQ95Y/GdHBsPLGdgSJIs95mXNxscD6MSyejpenMGL9TPQAcxfqY5xPViZ+1wA1qcr""yjdZKRqf1f4fpMY+x3b8k7H5Qyf/Smz0sv4xFsx1r+THNIz0rzk2LO3GvE0f1ybp6P+5eAelYU4mGeZQqsKw/eB20I3jHWEyGrXuvzB67nt6ddI+N2eD5K38wg/aSytOsb5O+bUSEe7P0zx9ebRRVknCD6uuqG3gSmQmttlD5OrMWSXzrPIXe8eTBaaPd+e/jfxwIDAQAB if (!publicKeyValue) { let err = new Error("Missing key value"); err.code = "EINVALIDVAL"; diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx index 683dec32c..06ec59745 100644 --- a/src/pages/MainPage.tsx +++ b/src/pages/MainPage.tsx @@ -191,19 +191,12 @@ export const MainPage: React.FC<{}> = (props) => { smaller. If you wish to generate a ZK proof of Twitter badge, you must do these: - Send yourself a{" "} - - password reset email - {" "} - from Twitter. - - - In your inbox, find the email from Twitter and click the three dot menu, then "Show original" then "Copy to clipboard". If on Outlook, download the original email as .eml - and copy it instead. + Send yourself a password reset email from Twitter in incognito. + In your inbox, find the email from Twitter and download headers (three dots, then download message). - Copy paste that into the box below. Note that we cannot use this to phish you: we do not know your password, and we never get this email info because we have no server at - all. We are actively searching for a less sketchy email. + Copy paste the entire contents of the .eml file into the box below. Note that we cannot use this to phish you: we do not know your password, and we never get this email + info because we have no server at all. We are actively searching for a less sketchy email. Paste in your sending Ethereum address. This ensures that no one else can "steal" your proof for another account (frontrunning protection!). diff --git a/src/scripts/generate_input.ts b/src/scripts/generate_input.ts index b0263478e..8fd8e8477 100644 --- a/src/scripts/generate_input.ts +++ b/src/scripts/generate_input.ts @@ -21,7 +21,7 @@ var Cryo = require("cryo"); const pki = require("node-forge").pki; // const email_file = "monia_email.eml"; // "./test_email.txt", "./twitter_msg.eml", kaylee_phone_number_email_twitter -const email_file = "zktestemail.eml"; +const email_file = "./nathan_twitter_email.eml"; export interface ICircuitInputs { modulus?: string[]; signature?: string[]; @@ -39,6 +39,7 @@ export interface ICircuitInputs { address_plus_one?: string; twitter_username_idx?: string; email_from_idx?: string; + email_to_idx?: string; } enum CircuitType { @@ -100,12 +101,8 @@ export async function getCircuitInputs( const [messagePadded, messagePaddedLen] = await sha256Pad(prehashBytesUnpadded, MAX_HEADER_PADDED_BYTES); const [bodyPadded, bodyPaddedLen] = await sha256Pad(body, Math.max(MAX_BODY_PADDED_BYTES, calc_length)); - // Convet messagePadded to string to print the specific header data that is signed - // console.log(message.toString()); - // Ensure SHA manual unpadded is running the correct function const shaOut = await partialSha(messagePadded, messagePaddedLen); - assert((await Uint8ArrayToString(shaOut)) === (await Uint8ArrayToString(Uint8Array.from(await shaHash(prehashBytesUnpadded)))), "SHA256 calculation did not match!"); // Precompute SHA prefix @@ -141,7 +138,8 @@ export async function getCircuitInputs( const address_plus_one = (bytesToBigInt(fromHex(eth_address)) + 1n).toString(); const USERNAME_SELECTOR = Buffer.from(STRING_PRESELECTOR); - const email_from_idx = Buffer.from(prehash_message_string).indexOf("From: ").toString(); + const email_from_idx = Buffer.from(prehash_message_string).indexOf("from:").toString(); + const email_to_idx = Buffer.from(prehash_message_string).indexOf("to:").toString(); const twitter_username_idx = (Buffer.from(bodyRemaining).indexOf(USERNAME_SELECTOR) + USERNAME_SELECTOR.length).toString(); console.log("Twitter Username idx: ", twitter_username_idx); @@ -165,6 +163,7 @@ export async function getCircuitInputs( address_plus_one, body_hash_idx, // email_from_idx, + // email_to_idx, }; } else { assert(circuit === CircuitType.SHA, "Invalid circuit type"); @@ -201,7 +200,7 @@ export async function generate_inputs(email: Buffer, eth_address: string): Promi // // TODO: Condiiton code on if there is an internet connection, run this code // var frozen = Cryo.stringify(result); // fs.writeFileSync(`./email_cache_2.json`, frozen, { flag: "w" }); - // } catch (e) { + // } catch (e) {i // console.log("Reading cached email instead!"); // let frozen = fs.readFileSync(`./email_cache.json`, { encoding: "utf-8" }); // result = Cryo.parse(frozen); @@ -223,7 +222,7 @@ async function do_generate() { const email = fs.readFileSync(email_file); console.log(email); const gen_inputs = await generate_inputs(email, "0x0000000000000000000000000000000000000000"); - console.log(JSON.stringify(gen_inputs)); + // console.log(JSON.stringify(gen_inputs)); return gen_inputs; } diff --git a/src/scripts/generate_two_inputs.ts b/src/scripts/generate_two_inputs.ts new file mode 100644 index 000000000..a21fa015c --- /dev/null +++ b/src/scripts/generate_two_inputs.ts @@ -0,0 +1,298 @@ +import { + bytesToBigInt, + stringToBytes, + fromHex, + toCircomBigIntBytes, + packBytesIntoNBytes, + bufferToUint8Array, + bufferToString, + bufferToHex, + Uint8ArrayToString, + Uint8ArrayToCharArray, + assert, + mergeUInt8Arrays, + int64toBytes, + } from "../helpers/binaryFormat"; + import { CIRCOM_FIELD_MODULUS, MAX_HEADER_PADDED_BYTES, MAX_BODY_PADDED_BYTES, STRING_PRESELECTOR, STRING_PRESELECTOR_AIRBNB, STRING_PRESELECTOR_COINBASE, CIRCUIT_INPUTS } from "../../src/helpers/constants"; + import { shaHash, partialSha, sha256Pad } from "../../src/helpers/shaHash"; + import { dkimVerify } from "../../src/helpers/dkim"; + import * as fs from "fs"; + var Cryo = require("cryo"); + const pki = require("node-forge").pki; + + // const email_file = "monia_email.eml"; // "./test_email.txt", "./twitter_msg.eml", kaylee_phone_number_email_twitter + const email_file_airbnb = "./nathan_airbnb_email.eml"; + const email_file_coinbase = "./nathan_coinbase_email.eml"; + const email_file_default = "./nathan_twitter_email.eml"; + export interface ICircuitInputs { + modulus?: string[]; + signature?: string[]; + base_message?: string[]; + in_padded?: string[]; + in_body_padded?: string[]; + in_body_len_padded_bytes?: string; + in_padded_n_bytes?: string[]; + in_len_padded_bytes?: string; + in_body_hash?: string[]; + precomputed_sha?: string[]; + body_hash_idx?: string; + addressParts?: string[]; + address?: string; + address_plus_one?: string; + email_from_idx?: string; + email_to_idx?: string; + } + + enum CircuitType { + RSA = "rsa", + SHA = "sha", + TEST = "test", + EMAIL = "email", + } + + enum KYCType { + AIRBNB = "airbnb", + COINBASE = "coinbase", + } + + async function findSelector(a: Uint8Array, selector: number[]): Promise { + let i = 0; + let j = 0; + while (i < a.length) { + if (a[i] === selector[j]) { + j++; + if (j === selector.length) { + return i - j + 1; + } + } else { + j = 0; + } + i++; + } + return -1; + } + + export async function getCircuitInputs( + rsa_signature: BigInt, + rsa_modulus: BigInt, + message: Buffer, + body: Buffer, + body_hash: string, + eth_address: string, + circuitType: CircuitType, + kycType: KYCType + ): Promise<{ + valid: { + validSignatureFormat?: boolean; + validMessage?: boolean; + }; + circuitInputs: ICircuitInputs; + }> { + console.log("Starting processing of inputs"); + // Derive modulus from signature + // const modulusBigInt = bytesToBigInt(pubKeyParts[2]); + const modulusBigInt = rsa_modulus; + // Message is the email header with the body hash + const prehash_message_string = message; + // const baseMessageBigInt = AAYUSH_PREHASH_MESSAGE_INT; // bytesToBigInt(stringToBytes(message)) || + // const postShaBigint = AAYUSH_POSTHASH_MESSAGE_PADDED_INT; + const signatureBigInt = rsa_signature; + + // Perform conversions + const prehashBytesUnpadded = typeof prehash_message_string == "string" ? new TextEncoder().encode(prehash_message_string) : Uint8Array.from(prehash_message_string); + const postShaBigintUnpadded = bytesToBigInt(stringToBytes((await shaHash(prehashBytesUnpadded)).toString())) % CIRCOM_FIELD_MODULUS; + + // Sha add padding + // 65 comes from the 64 at the end and the 1 bit in the start, then 63 comes from the formula to round it up to the nearest 64. see sha256algorithm.com for a more full explanation of paddnig length + const calc_length = Math.floor((body.length + 63 + 65) / 64) * 64; + const [messagePadded, messagePaddedLen] = await sha256Pad(prehashBytesUnpadded, MAX_HEADER_PADDED_BYTES); + const [bodyPadded, bodyPaddedLen] = await sha256Pad(body, Math.max(MAX_BODY_PADDED_BYTES, calc_length)); + + // Ensure SHA manual unpadded is running the correct function + const shaOut = await partialSha(messagePadded, messagePaddedLen); + assert((await Uint8ArrayToString(shaOut)) === (await Uint8ArrayToString(Uint8Array.from(await shaHash(prehashBytesUnpadded)))), "SHA256 calculation did not match!"); + + // Precompute SHA prefix + let selector; + if (kycType === KYCType.AIRBNB) { + selector = STRING_PRESELECTOR_AIRBNB.split("").map((char) => char.charCodeAt(0)); + } else if (kycType === KYCType.COINBASE) { + selector = STRING_PRESELECTOR_COINBASE.split("").map((char) => char.charCodeAt(0)); + } else { + // this line is dumb as should never happen :/ + selector = STRING_PRESELECTOR.split("").map((char) => char.charCodeAt(0)); + } + const selector_loc = await findSelector(bodyPadded, selector); + console.log("Body selector found at: ", selector_loc); + let shaCutoffIndex = Math.floor((await findSelector(bodyPadded, selector)) / 64) * 64; + const precomputeText = bodyPadded.slice(0, shaCutoffIndex); + let bodyRemaining = bodyPadded.slice(shaCutoffIndex); + const bodyRemainingLen = bodyPaddedLen - precomputeText.length; + assert(bodyRemainingLen < MAX_BODY_PADDED_BYTES, "Invalid slice"); + assert(bodyRemaining.length % 64 === 0, "Not going to be padded correctly with int64s"); + while (bodyRemaining.length < MAX_BODY_PADDED_BYTES) { + bodyRemaining = mergeUInt8Arrays(bodyRemaining, int64toBytes(0)); + } + assert(bodyRemaining.length === MAX_BODY_PADDED_BYTES, "Invalid slice"); + const bodyShaPrecompute = await partialSha(precomputeText, shaCutoffIndex); + + // Compute identity revealer + let circuitInputs; + const modulus = toCircomBigIntBytes(modulusBigInt); + const signature = toCircomBigIntBytes(signatureBigInt); + + const in_len_padded_bytes = messagePaddedLen.toString(); + const in_padded = await Uint8ArrayToCharArray(messagePadded); // Packed into 1 byte signals + const in_body_len_padded_bytes = bodyRemainingLen.toString(); + const in_body_padded = await Uint8ArrayToCharArray(bodyRemaining); + const base_message = toCircomBigIntBytes(postShaBigintUnpadded); + const precomputed_sha = await Uint8ArrayToCharArray(bodyShaPrecompute); + const body_hash_idx = bufferToString(message).indexOf(body_hash).toString(); + + const address = bytesToBigInt(fromHex(eth_address)).toString(); + const address_plus_one = (bytesToBigInt(fromHex(eth_address)) + 1n).toString(); + + const email_from_idx = Buffer.from(prehash_message_string).indexOf("from:").toString(); + const email_to_idx = Buffer.from(prehash_message_string).indexOf("to:").toString(); + + if (circuitType === CircuitType.RSA) { + circuitInputs = { + modulus, + signature, + base_message, + }; + } else if (circuitType === CircuitType.EMAIL) { + circuitInputs = { + in_padded, + modulus, + signature, + in_len_padded_bytes, + // precomputed_sha, + // in_body_padded, + // in_body_len_padded_bytes, + address, + address_plus_one, + body_hash_idx, + // email_from_idx, + email_to_idx, + }; + } else { + assert(circuitType === CircuitType.SHA, "Invalid circuit type"); + circuitInputs = { + in_padded, + in_len_padded_bytes, + precomputed_sha, + }; + } + return { + circuitInputs, + valid: {}, + }; + } + + export async function generate_inputs(email: Buffer, eth_address: string, kycType: KYCType): Promise { + var result; + console.log("DKIM verification starting"); + result = await dkimVerify(email); + if (!result.results[0]) { + throw new Error(`No result found on dkim output ${result}`); + } else { + if (!result.results[0].publicKey) { + if (result.results[0].status.message) { + throw new Error(result.results[0].status.message); + } else { + throw new Error(`No public key found on generate_inputs result ${JSON.stringify(result)}`); + } + } + } + const _ = result.results[0].publicKey.toString(); + console.log("DKIM verification successful"); + // try { + // // TODO: Condiiton code on if there is an internet connection, run this code + // var frozen = Cryo.stringify(result); + // fs.writeFileSync(`./email_cache_2.json`, frozen, { flag: "w" }); + // } catch (e) {i + // console.log("Reading cached email instead!"); + // let frozen = fs.readFileSync(`./email_cache.json`, { encoding: "utf-8" }); + // result = Cryo.parse(frozen); + // } + let sig = BigInt("0x" + Buffer.from(result.results[0].signature, "base64").toString("hex")); + let message = result.results[0].status.signature_header; + let body = result.results[0].body; + let body_hash = result.results[0].bodyHash; + let circuitType = CircuitType.EMAIL; + + let pubkey = result.results[0].publicKey; + const pubKeyData = pki.publicKeyFromPem(pubkey.toString()); + let modulus = BigInt(pubKeyData.n.toString()); + let fin_result = await getCircuitInputs(sig, modulus, message, body, body_hash, eth_address, circuitType, kycType); + return fin_result.circuitInputs; + } + + async function do_generate(kycType: KYCType) { + let email; + if (kycType === KYCType.AIRBNB) { + email = fs.readFileSync(email_file_airbnb); + } else if (kycType === KYCType.COINBASE) { + email = fs.readFileSync(email_file_coinbase); + } else { + email = fs.readFileSync(email_file_default); + } + console.log(email); + const gen_inputs = await generate_inputs(email, "0x0000000000000000000000000000000000000000", kycType); + // console.log(JSON.stringify(gen_inputs_airbnb)); + return gen_inputs; + } + + async function gen_test() { + console.log(packBytesIntoNBytes(Uint8Array.from([0, 121, 117, 115, 104, 95, 103, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]))); + } + + export async function insert13Before10(a: Uint8Array): Promise { + let ret = new Uint8Array(a.length + 1000); + let j = 0; + for (let i = 0; i < a.length; i++) { + if (a[i] === 10) { + ret[j] = 13; + j++; + } + ret[j] = a[i]; + j++; + } + return ret.slice(0, j); + } + + async function debug_file() { + const email_airbnb = fs.readFileSync(email_file_airbnb); + console.log(Uint8Array.from(email_airbnb)); + const email_coinbase = fs.readFileSync(email_file_coinbase); + console.log(Uint8Array.from(email_coinbase)); + // Key difference: file load has 13 10, web version has just 10 + } + + async function make_input_file() { + const [circuitInputs_airbnb, circuitInputs_coinbase] = await Promise.all([do_generate(KYCType.AIRBNB), do_generate(KYCType.COINBASE)]); + fs.writeFileSync(`./circuits/inputs/input_airbnb.json`, JSON.stringify(circuitInputs_airbnb), { flag: "w"}); + fs.writeFileSync(`./circuits/inputs/input_coinbase.json`, JSON.stringify(circuitInputs_coinbase), { flag: "w"}); + + let input_kyc = {}; + for (const key of Object.keys(circuitInputs_airbnb)) { + input_kyc[key.concat("_airbnb")] = circuitInputs_airbnb[key]; + input_kyc[key.concat("_coinbase")] = circuitInputs_coinbase[key]; + } + fs.writeFileSync(`./circuits/inputs/input_kyc.json`, JSON.stringify(input_kyc), { flag: "w"}); + } + + // If main + if (typeof require !== "undefined" && require.main === module) { + // debug_file(); + // const circuitInputs_airbnb = do_generate(KYCType.AIRBNB); + // const circuitInputs_coinbase = do_generate(KYCType.COINBASE); + console.log("Writing to file..."); + // circuitInputs_airbnb.then((inputs) => fs.writeFileSync(`./circuits/inputs/input_airbnb.json`, JSON.stringify(inputs), { flag: "w" })); + // circuitInputs_coinbase.then((inputs) => fs.writeFileSync(`./circuits/inputs/input_coinbase.json`, JSON.stringify(inputs), { flag: "w" })); + // gen_test(); + + make_input_file(); + } + \ No newline at end of file diff --git a/temp.py b/temp.py new file mode 100644 index 000000000..11ba8a875 --- /dev/null +++ b/temp.py @@ -0,0 +1,4 @@ +# l = ["101","99","111","114","97","116","105","111","110","58","110","111","110","101","59","45","119","101","98","107","105","116","61","13","10","45","102","111","110","116","45","115","109","111","111","116","104","105","110","103","58","97","110","116","105","97","108","105","97","115","101","100","59","34","62","32","84","104","105","115","32","101","109","97","105","108","32","119","97","115","32","109","101","97","110","116","32","102","111","114","32","64","74","117","115","116","105","110","84","53","56","52","55","55","49","50","51","32","60","47","61","13","10","115","112","97","110","62","32","60","47","116","100","62","13","10","60","47","116","114","62","13","10","60","116","114","62","13","10","60","116","100","32","104","101","105","103","104","116","61","51","68","34","54","34","32","115","116","121","108","101","61","51","68","34","104","101","105","103","104","116","58","54","112","120","59","108","105","110","101","45","104","101","105","103","104","116","58","49","112","120","59","102","111","110","116","45","115","105","122","101","58","49","112","120","59","112","97","100","100","105","110","103","58","61","13","10","48","59","109","97","114","103","105","110","58","48","59","108","105","110","101","45","104","101","105","103","104","116","58","49","112","120","59","102","111","110","116","45","115","105","122","101","58","49","112","120","59","34","62","60","47","116","100","62","13","10","60","47","116","114","62","13","10","60","116","114","62","13","10","60","116","100","32","97","108","105","103","110","61","51","68","34","99","101","110","116","101","114","34","32","115","116","121","108","101","61","51","68","34","112","97","100","100","105","110","103","58","48","59","109","97","114","103","105","110","58","48","59","108","105","110","101","45","104","101","105","103","104","116","58","49","112","120","59","102","111","110","116","45","115","105","122","101","58","61","13","10","49","112","120","59","34","62","32","60","115","112","97","110","32","99","108","97","115","115","61","51","68","34","97","100","100","114","101","115","115","34","62","32","60","97","32","104","114","101","102","61","51","68","34","35","34","32","115","116","121","108","101","61","51","68","34","116","101","120","116","45","100","101","99","111","114","97","116","105","111","110","58","110","111","110","101","61","13","10","59","98","111","114","100","101","114","45","115","116","121","108","101","58","110","111","110","101","59","98","111","114","100","101","114","58","48","59","112","97","100","100","105","110","103","58","48","59","109","97","114","103","105","110","58","48","59","102","111","110","116","45","102","97","109","105","108","121","58","39","72","101","108","118","101","116","105","99","97","78","101","117","101","39","44","61","13","10","32","39","72","101","108","118","101","116","105","99","97","32","78","101","117","101","39","44","32","72","101","108","118","101","116","105","99","97","44","32","65","114","105","97","108","44","32","115","97","110","115","45","115","101","114","105","102","59","45","119","101","98","107","105","116","45","102","111","110","116","45","115","109","111","111","116","104","105","110","103","58","97","110","116","105","61","13","10","97","108","105","97","115","101","100","59","99","111","108","111","114","58","35","56","56","57","57","65","54","59","102","111","110","116","45","115","105","122","101","58","49","50","112","120","59","112","97","100","100","105","110","103","58","48","112","120","59","109","97","114","103","105","110","58","48","112","120","59","102","111","110","116","45","119","101","105","103","104","116","58","110","111","114","61","13","10","109","97","108","59","108","105","110","101","45","104","101","105","103","104","116","58","49","50","112","120","59","99","117","114","115","111","114","58","100","101","102","97","117","108","116","59","34","62","84","119","105","116","116","101","114","44","32","73","110","99","46","32","49","51","53","53","32","77","97","114","107","101","116","32","83","116","114","101","101","116","44","32","83","117","105","61","13","10","116","101","32","57","48","48","32","83","97","110","32","70","114","97","110","99","105","115","99","111","44","32","67","65","32","57","52","49","48","51","60","47","97","62","32","60","47","115","112","97","110","62","32","60","47","116","100","62","13","10","60","47","116","114","62","13","10","60","116","114","62","13","10","60","116","100","32","104","101","105","103","104","116","61","51","68","34","55","50","34","32","115","116","121","108","101","61","51","68","34","104","101","105","103","104","116","58","55","50","112","120","59","112","97","100","100","105","110","103","58","48","59","109","97","114","103","105","110","58","48","59","108","105","110","101","45","104","101","105","103","104","116","58","49","112","120","59","102","61","13","10","111","110","116","45","115","105","122","101","58","49","112","120","59","34","62","60","47","116","100","62","13","10","60","47","116","114","62","13","10","60","47","116","98","111","100","121","62","13","10","60","47","116","97","98","108","101","62","13","10","60","33","45","45","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","32","101","110","100","32","102","111","111","116","101","114","32","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","45","45","62","32","60","47","116","100","62","13","10","60","47","116","114","62","13","10","60","47","116","98","111","100","121","62","13","10","60","47","116","97","98","108","101","62","13","10","60","47","98","111","100","121","62","13","10","60","47","104","116","109","108","62","13","10","45","45","45","45","45","45","61","95","80","97","114","116","95","50","53","50","53","53","48","56","51","50","95","49","49","49","50","56","52","57","48","48","50","46","49","54","56","48","48","48","52","54","53","51","56","56","56","45","45","13","10","128","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","1","188","40","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0"] +# l = ["99","111","110","116","101","110","116","45","116","121","112","101","58","109","117","108","116","105","112","97","114","116","47","97","108","116","101","114","110","97","116","105","118","101","59","32","98","111","117","110","100","97","114","121","61","52","54","101","99","98","48","49","101","55","49","98","101","97","101","55","51","51","50","51","102","56","100","55","54","52","57","102","49","98","102","102","101","99","48","55","49","98","56","57","50","53","55","57","54","56","55","102","57","50","51","100","102","54","102","99","57","98","98","57","55","13","10","102","114","111","109","58","65","105","114","98","110","98","32","60","97","117","116","111","109","97","116","101","100","64","97","105","114","98","110","98","46","99","111","109","62","13","10","109","105","109","101","45","118","101","114","115","105","111","110","58","49","46","48","13","10","115","117","98","106","101","99","116","58","84","104","97","110","107","115","32","102","111","114","32","112","114","111","118","105","100","105","110","103","32","97","32","103","111","118","101","114","110","109","101","110","116","32","73","68","13","10","120","45","102","101","101","100","98","97","99","107","45","105","100","58","49","54","57","51","48","51","58","83","71","13","10","116","111","58","97","97","121","117","115","104","103","117","112","116","97","48","53","64","103","109","97","105","108","46","99","111","109","13","10","100","107","105","109","45","115","105","103","110","97","116","117","114","101","58","118","61","49","59","32","97","61","114","115","97","45","115","104","97","50","53","54","59","32","99","61","114","101","108","97","120","101","100","47","114","101","108","97","120","101","100","59","32","100","61","101","109","97","105","108","46","97","105","114","98","110","98","46","99","111","109","59","32","104","61","99","111","110","116","101","110","116","45","116","121","112","101","58","102","114","111","109","58","109","105","109","101","45","118","101","114","115","105","111","110","58","115","117","98","106","101","99","116","58","120","45","102","101","101","100","98","97","99","107","45","105","100","58","116","111","59","32","115","61","115","50","48","49","53","48","52","50","56","59","32","98","104","61","55","71","103","108","107","90","57","77","76","87","76","75","103","80","66","71","55","81","117","101","50","104","90","48","74","121","57","75","72","97","117","90","83","109","84","102","71","119","89","119","82","49","56","61","59","32","98","61","128","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","14","64","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0"] +l = [60,33,68,79,67,84,89,80,69,32,104,116,109,108,32,80,85,66,76,73,67,32,34,45,47,47,87,51,67,47,47,68,84,68,32,88,72,84,77,76,32,49,46,48,32,84,114,97,110,115,105,116,105,111,110,97,108,47,47,69,78,34,32,34,104,116,116,112,58,47,47,119,119,119,46,119,51,46,111,114,103,47,84,82,47,120,104,116,109,108,49,47,68,84,68,47,120,104,116,109,108,49,45,116,114,97,110,115,105,116,105,111,110,97,108,46,100,116,100,34,62,13,10,60,104,116,109,108,62,13,10,60,104,101,97,100,62,13,10,32,32,60,109,101,116,97,32,104,116,116,112,45,101,113,117,105,118,61,34,67,111,110,116,101,110,116,45,84,121,112,101,34,32,99,111,110,116,101,110,116,61,34,116,101,120,116,47,104,116,109,108,59,32,99,104,97,114,115,101,116,61,85,84,70,45,56,34,62,13,10,32,32,60,116,105,116,108,101,62,67,111,105,110,98,97,115,101,60,47,116,105,116,108,101,62,13,10,60,47,104,101,97,100,62,13,10,13,10,13,10,60,98,111,100,121,32,115,116,121,108,101,61,34,33,105,109,112,111,114,116,97,110,116,32,98,97,99,107,103,114,111,117,110,100,45,99,111,108,111,114,58,32,35,70,50,70,53,70,55,59,32,104,101,105,103,104,116,58,32,49,48,48,37,32,33,105,109,112,111,114,116,97,110,116,59,32,109,97,114,103,105,110,58,32,48,59,32,112,97,100,100,105,110,103,58,32,48,59,32,119,105,100,116,104,58,32,49,48,48,37,32,33,105,109,112,111,114,116,97,110,116,59,34,32,98,103,99,111,108,111,114,61,34,35,70,50,70,53,70,55,34,62,13,10,32,32,60,33,45,45,32,72,73,68,68,69,78,32,80,82,69,72,69,65,68,69,82,32,84,69,88,84,32,45,45,62,13,10,32,32,60,100,105,118,32,115,116,121,108,101,61,34,99,111,108,111,114,58,32,35,102,101,102,101,102,101,59,32,100,105,115,112,108,97,121,58,32,110,111,110,101,59,32,102,111,110,116,45,102,97,109,105,108,121,58,32,79,112,101,110,32,83,97,110,115,44,32,72,101,108,118,101,116,105,99,97,44,32,65,114,105,97,108,44,32,115,97,110,115,45,115,101,114,105,102,59,32,102,111,110,116,45,115,105,122,101,58,32,49,112,120,59,32,108,105,110,101,45,104,101,105,103,104,116,58,32,49,112,120,59,32,109,97,120,45,104,101,105,103,104,116,58,32,48,112,120,59,32,109,97,120,45,119,105,100,116,104,58,32,48,112,120,59,32,111,112,97,99,105,116,121,58,32,48,59,32,111,118,101,114,102,108,111,119,58,32,104,105,100,100,101,110,59,34,62,13,10,32,32,32,32,32,32,67,111,105,110,98,97,115,101,32,109,97,107,101,115,32,100,105,103,105,116,97,108,32,99,117,114,114,101,110,99,121,32,101,97,115,121,33,13,10,32,32,60,47,100,105,118,62,13,10,13,10,32,32,60,116,97,98,108,101,32,105,100,61,34,109,97,105,110,34,32,119,105,100,116,104,61,34,49,48,48,37,34,32,104,101,105,103,104,116,61,34,49,48,48,37,34,32,99,101,108,108,112,97,100,100,105,110,103,61,34,48,34,32,99,101,108,108,115,112,97,99,105,110,103,61,34,48,34,32,98,111,114,100,101,114,61,34,48,34,62,13,10,32,32,32,32,60,116,98,111,100,121,62,13,10,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,60,116,100,32,118,97,108,105,103,110,61,34,116,111,112,34,32,97,108,105,103,110,61,34,99,101,110,116,101,114,34,32,98,103,99,111,108,111,114,61,34,35,51,98,52,97,54,57,34,32,115,116,121,108,101,61,34,98,97,99,107,103,114,111,117,110,100,58,32,108,105,110,101,97,114,45,103,114,97,100,105,101,110,116,40,45,49,56,48,100,101,103,44,32,35,48,53,55,48,68,52,32,48,37,44,32,35,48,48,54,48,66,65,32,49,48,48,37,41,32,48,37,32,48,37,32,47,32,99,111,118,101,114,59,32,112,97,100,100,105,110,103,58,32,48,32,49,53,112,120,59,34,62,13,10,32,32,32,32,32,32,32,32,32,32,60,116,97,98,108,101,32,99,108,97,115,115,61,34,105,110,110,101,114,109,97,105,110,34,32,99,101,108,108,112,97,100,100,105,110,103,61,34,48,34,32,119,105,100,116,104,61,34,49,48,48,37,34,32,99,101,108,108,115,112,97,99,105,110,103,61,34,48,34,32,98,111,114,100,101,114,61,34,48,34,32,97,108,105,103,110,61,34,99,101,110,116,101,114,34,32,115,116,121,108,101,61,34,109,97,114,103,105,110,58,48,32,97,117,116,111,59,32,116,97,98,108,101,45,108,97,121,111,117,116,58,32,102,105,120,101,100,59,32,98,111,114,100,101,114,45,99,111,108,108,97,112,115,101,58,32,99,111,108,108,97,112,115,101,32,33,105,109,112,111,114,116,97,110,116,59,32,109,97,120,45,119,105,100,116,104,58,32,54,48,48,112,120,59,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,60,116,98,111,100,121,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,60,33,45,45,32,83,84,65,82,84,32,111,102,32,77,65,73,76,32,67,111,110,116,101,110,116,32,45,45,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,97,108,105,103,110,61,34,99,101,110,116,101,114,34,32,118,97,108,105,103,110,61,34,116,111,112,34,32,119,105,100,116,104,61,34,49,48,48,37,34,62,13,10,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,33,45,45,32,76,111,103,111,32,115,116,97,114,116,32,104,101,114,101,32,45,45,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,97,98,108,101,32,99,108,97,115,115,61,34,108,111,103,111,34,32,119,105,100,116,104,61,34,49,48,48,37,34,32,99,101,108,108,112,97,100,100,105,110,103,61,34,48,34,32,99,101,108,108,115,112,97,99,105,110,103,61,34,48,34,32,98,111,114,100,101,114,61,34,48,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,98,111,100,121,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,62,60,116,100,32,97,108,105,103,110,61,34,99,101,110,116,101,114,34,32,118,97,108,105,103,110,61,34,116,111,112,34,32,115,116,121,108,101,61,34,45,109,115,45,116,101,120,116,45,115,105,122,101,45,97,100,106,117,115,116,58,32,49,48,48,37,59,32,45,119,101,98,107,105,116,45,116,101,120,116,45,115,105,122,101,45,97,100,106,117,115,116,58,32,49,48,48,37,59,32,109,115,111,45,116,97,98,108,101,45,108,115,112,97,99,101,58,32,48,112,116,59,32,109,115,111,45,116,97,98,108,101,45,114,115,112,97,99,101,58,32,48,112,116,59,32,112,97,100,100,105,110,103,58,32,51,48,112,120,32,48,59,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,105,109,103,32,115,114,99,61,34,104,116,116,112,115,58,47,47,99,98,45,98,114,97,110,100,46,115,51,46,97,109,97,122,111,110,97,119,115,46,99,111,109,47,101,53,97,100,54,97,100,56,47,99,111,105,110,98,97,115,101,45,108,111,103,111,64,50,120,46,112,110,103,34,32,98,111,114,100,101,114,61,34,48,34,32,115,116,121,108,101,61,34,45,109,115,45,105,110,116,101,114,112,111,108,97,116,105,111,110,45,109,111,100,101,58,32,98,105,99,117,98,105,99,59,32,98,111,114,100,101,114,58,32,48,59,32,100,105,115,112,108,97,121,58,32,98,108,111,99,107,59,32,104,101,105,103,104,116,58,32,51,48,37,59,32,108,105,110,101,45,104,101,105,103,104,116,58,32,49,48,48,37,59,32,111,117,116,108,105,110,101,58,32,110,111,110,101,59,32,116,101,120,116,45,100,101,99,111,114,97,116,105,111,110,58,32,110,111,110,101,59,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,100,62,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,98,111,100,121,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,97,98,108,101,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,33,45,45,32,76,111,103,111,32,101,110,100,32,104,101,114,101,32,45,45,62,13,10,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,33,45,45,32,77,97,105,110,32,67,79,78,84,69,78,84,32,45,45,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,97,98,108,101,32,119,105,100,116,104,61,34,49,48,48,37,34,32,99,101,108,108,112,97,100,100,105,110,103,61,34,48,34,32,99,101,108,108,115,112,97,99,105,110,103,61,34,48,34,32,98,111,114,100,101,114,61,34,48,34,32,98,103,99,111,108,111,114,61,34,35,102,102,102,102,102,102,34,32,115,116,121,108,101,61,34,98,111,114,100,101,114,45,114,97,100,105,117,115,58,32,52,112,120,59,32,98,111,120,45,115,104,97,100,111,119,58,32,48,32,50,112,120,32,56,112,120,32,114,103,98,97,40,48,44,48,44,48,44,48,46,48,53,41,59,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,98,111,100,121,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,104,101,105,103,104,116,61,34,52,48,34,62,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,32,115,116,121,108,101,61,34,102,111,110,116,45,102,97,109,105,108,121,58,32,45,97,112,112,108,101,45,115,121,115,116,101,109,44,66,108,105,110,107,77,97,99,83,121,115,116,101,109,70,111,110,116,44,38,35,51,57,59,83,101,103,111,101,32,85,73,38,35,51,57,59,44,38,35,51,57,59,82,111,98,111,116,111,38,35,51,57,59,44,38,35,51,57,59,79,120,121,103,101,110,38,35,51,57,59,44,38,35,51,57,59,85,98,117,110,116,117,38,35,51,57,59,44,38,35,51,57,59,67,97,110,116,97,114,101,108,108,38,35,51,57,59,44,38,35,51,57,59,70,105,114,97,32,83,97,110,115,38,35,51,57,59,44,38,35,51,57,59,68,114,111,105,100,32,83,97,110,115,38,35,51,57,59,44,38,35,51,57,59,72,101,108,118,101,116,105,99,97,32,78,101,117,101,38,35,51,57,59,44,115,97,110,115,45,115,101,114,105,102,59,32,99,111,108,111,114,58,35,52,69,53,67,54,69,59,32,102,111,110,116,45,115,105,122,101,58,49,52,112,120,59,32,108,105,110,101,45,104,101,105,103,104,116,58,50,48,112,120,59,32,109,97,114,103,105,110,45,116,111,112,58,50,48,112,120,59,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,99,108,97,115,115,61,34,99,111,110,116,101,110,116,34,32,99,111,108,115,112,97,110,61,34,50,34,32,118,97,108,105,103,110,61,34,116,111,112,34,32,97,108,105,103,110,61,34,99,101,110,116,101,114,34,32,115,116,121,108,101,61,34,112,97,100,100,105,110,103,45,108,101,102,116,58,52,48,112,120,59,32,112,97,100,100,105,110,103,45,114,105,103,104,116,58,52,48,112,120,59,34,62,13,10,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,97,98,108,101,32,119,105,100,116,104,61,34,49,48,48,37,34,32,99,101,108,108,112,97,100,100,105,110,103,61,34,48,34,32,99,101,108,108,115,112,97,99,105,110,103,61,34,48,34,32,98,111,114,100,101,114,61,34,48,34,32,98,103,99,111,108,111,114,61,34,35,102,102,102,102,102,102,34,62,13,10,32,32,60,116,98,111,100,121,62,13,10,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,60,116,100,32,97,108,105,103,110,61,34,99,101,110,116,101,114,34,62,13,10,32,32,32,32,32,32,32,32,32,32,60,100,105,118,32,115,116,121,108,101,61,34,99,111,108,111,114,58,32,35,52,56,53,52,53,100,59,32,102,111,110,116,45,115,105,122,101,58,50,54,112,120,59,32,102,111,110,116,45,119,101,105,103,104,116,58,32,51,48,48,59,32,108,105,110,101,45,104,101,105,103,104,116,58,32,50,52,112,120,59,32,109,97,114,103,105,110,58,32,50,48,112,120,32,97,117,116,111,32,52,54,112,120,59,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,67,111,110,103,114,97,116,115,33,32,89,111,117,114,32,97,99,99,111,117,110,116,32,105,115,32,114,101,97,100,121,46,13,10,32,32,32,32,32,32,32,32,32,32,60,47,100,105,118,62,13,10,32,32,32,32,32,32,32,32,60,47,116,100,62,13,10,32,32,32,32,32,32,60,47,116,114,62,13,10,13,10,32,32,32,32,32,32,60,116,100,32,115,116,121,108,101,61,34,116,101,120,116,45,97,108,105,103,110,58,32,99,101,110,116,101,114,34,62,13,10,32,32,32,32,32,32,32,32,60,115,118,103,32,119,105,100,116,104,61,34,49,52,54,34,32,104,101,105,103,104,116,61,34,49,52,54,34,32,118,101,114,115,105,111,110,61,34,49,46,49,34,32,105,100,61,34,76,97,121,101,114,95,49,34,32,120,109,108,110,115,61,34,104,116,116,112,58,47,47,119,119,119,46,119,51,46,111,114,103,47,50,48,48,48,47,115,118,103,34,32,120,109,108,110,115,58,120,108,105,110,107,61,34,104,116,116,112,58,47,47,119,119,119,46,119,51,46,111,114,103,47,49,57,57,57,47,120,108,105,110,107,34,32,120,61,34,48,112,120,34,32,121,61,34,48,112,120,34,13,10,32,32,32,32,32,32,32,32,32,32,32,118,105,101,119,66,111,120,61,34,48,32,48,32,49,53,48,32,49,53,48,34,32,115,116,121,108,101,61,34,101,110,97,98,108,101,45,98,97,99,107,103,114,111,117,110,100,58,110,101,119,32,48,32,48,32,49,53,48,32,49,53,48,59,34,32,120,109,108,58,115,112,97,99,101,61,34,112,114,101,115,101,114,118,101,34,62,13,10,32,32,32,32,32,32,32,32,60,115,116,121,108,101,32,116,121,112,101,61,34,116,101,120,116,47,99,115,115,34,62,13,10,32,32,32,32,32,32,32,32,32,32,46,115,116,48,123,102,105,108,108,58,35,68,52,69,69,70,70,59,125,13,10,32,32,32,32,32,32,32,32,32,32,46,115,116,49,123,111,112,97,99,105,116,121,58,48,46,51,59,125,13,10,32,32,32,32,32,32,32,32,32,32,46,115,116,50,123,102,105,108,108,58,35,70,70,70,70,70,70,59,125,13,10,32,32,32,32,32,32,32,32,32,32,46,115,116,51,123,102,105,108,108,58,35,48,48,67,53,55,70,59,125,13,10,32,32,32,32,32,32,32,32,32,32,46,115,116,52,123,111,112,97,99,105,116,121,58,48,46,51,59,102,105,108,108,58,35,50,57,54,56,67,57,59,125,13,10,32,32,32,32,32,32,32,32,32,32,46,115,116,53,123,102,105,108,108,58,35,50,57,54,56,67,57,59,125,13,10,32,32,32,32,32,32,32,32,32,32,46,115,116,54,123,102,105,108,108,58,35,49,54,52,69,66,49,59,125,13,10,32,32,32,32,32,32,32,32,60,47,115,116,121,108,101,62,13,10,32,32,32,32,32,32,32,32,60,99,105,114,99,108,101,32,99,108,97,115,115,61,34,115,116,48,34,32,99,120,61,34,55,53,34,32,99,121,61,34,55,53,34,32,114,61,34,55,53,34,47,62,13,10,32,32,32,32,32,32,32,32,60,103,32,99,108,97,115,115,61,34,115,116,49,34,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,50,49,46,57,44,53,57,46,56,57,99,55,46,48,55,44,48,44,49,48,46,54,53,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,115,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,115,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,49,46,53,55,45,48,46,55,49,44,51,46,49,49,45,49,46,52,44,52,46,57,57,45,49,46,57,50,99,45,48,46,51,54,45,49,46,50,57,45,48,46,55,53,45,50,46,53,55,45,49,46,49,56,45,51,46,56,51,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,50,46,49,44,48,46,53,57,45,51,46,55,57,44,49,46,51,53,45,53,46,52,54,44,50,46,49,49,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,53,44,50,46,56,50,115,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,53,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,45,49,48,46,54,52,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,45,49,48,46,54,53,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,49,46,54,55,45,48,46,55,53,45,51,46,51,55,45,49,46,53,50,45,53,46,52,56,45,50,46,49,49,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,48,46,52,51,44,49,46,50,54,45,48,46,56,50,44,50,46,53,52,45,49,46,49,56,44,51,46,56,51,99,49,46,56,57,44,48,46,53,50,44,51,46,52,51,44,49,46,50,50,44,53,46,48,49,44,49,46,57,51,67,49,49,46,50,53,44,53,56,46,50,56,44,49,52,46,56,51,44,53,57,46,56,57,44,50,49,46,57,44,53,57,46,56,57,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,49,49,53,46,54,55,44,51,55,46,48,55,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,115,45,49,48,46,54,52,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,115,45,49,48,46,54,53,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,53,46,55,55,44,48,45,56,46,55,57,45,49,46,49,56,45,49,49,46,55,55,45,50,46,53,49,99,45,48,46,54,55,44,49,46,49,53,45,49,46,51,49,44,50,46,51,51,45,49,46,57,50,44,51,46,53,50,99,51,46,51,51,44,49,46,53,44,54,46,57,44,50,46,57,57,44,49,51,46,54,57,44,50,46,57,57,99,55,46,48,55,44,48,44,49,48,46,54,53,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,115,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,54,46,55,56,44,48,44,49,48,46,51,52,45,49,46,52,56,44,49,51,46,54,55,45,50,46,57,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,48,46,54,49,45,49,46,49,57,45,49,46,50,53,45,50,46,51,55,45,49,46,57,50,45,51,46,53,50,99,45,50,46,57,55,44,49,46,51,51,45,54,44,50,46,53,49,45,49,49,46,55,53,44,50,46,53,49,67,49,50,49,46,57,50,44,51,57,46,56,57,44,49,49,56,46,56,57,44,51,56,46,53,50,44,49,49,53,46,54,55,44,51,55,46,48,55,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,50,49,46,57,44,54,55,46,56,57,99,55,46,48,55,44,48,44,49,48,46,54,53,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,115,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,115,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,50,46,48,50,45,48,46,57,49,44,51,46,57,56,45,49,46,56,44,54,46,54,57,45,50,46,51,50,99,45,48,46,50,50,45,49,46,51,50,45,48,46,52,56,45,50,46,54,51,45,48,46,55,56,45,51,46,57,51,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,51,46,49,52,44,48,46,54,49,45,53,46,51,56,44,49,46,54,50,45,55,46,53,55,44,50,46,54,49,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,53,44,50,46,56,50,115,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,53,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,45,49,48,46,54,52,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,45,49,48,46,54,53,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,50,46,49,57,45,48,46,57,57,45,52,46,52,52,45,50,45,55,46,53,57,45,50,46,54,49,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,48,46,50,57,44,49,46,51,45,48,46,53,53,44,50,46,54,49,45,48,46,55,56,44,51,46,57,51,99,50,46,55,51,44,48,46,53,51,44,52,46,54,57,44,49,46,52,49,44,54,46,55,50,44,50,46,51,51,67,49,49,46,50,53,44,54,54,46,50,56,44,49,52,46,56,51,44,54,55,46,56,57,44,50,49,46,57,44,54,55,46,56,57,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,49,49,53,46,54,55,44,50,57,46,48,55,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,115,45,49,48,46,54,52,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,115,45,49,48,46,54,53,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,51,46,50,51,44,48,45,53,46,54,45,48,46,51,55,45,55,46,53,57,45,48,46,57,52,99,45,48,46,56,50,44,49,46,49,51,45,49,46,54,49,44,50,46,50,56,45,50,46,51,55,44,51,46,52,54,99,50,46,53,49,44,48,46,56,53,44,53,46,53,53,44,49,46,52,56,44,57,46,57,53,44,49,46,52,56,99,55,46,48,55,44,48,44,49,48,46,54,53,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,115,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,52,46,51,57,44,48,44,55,46,52,51,45,48,46,54,50,44,57,46,57,51,45,49,46,52,55,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,48,46,55,54,45,49,46,49,56,45,49,46,53,52,45,50,46,51,51,45,50,46,51,55,45,51,46,52,54,99,45,49,46,57,56,44,48,46,53,54,45,52,46,51,52,44,48,46,57,51,45,55,46,53,55,44,48,46,57,51,67,49,50,49,46,57,50,44,51,49,46,56,57,44,49,49,56,46,56,57,44,51,48,46,53,50,44,49,49,53,46,54,55,44,50,57,46,48,55,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,49,52,48,46,53,56,44,49,48,49,46,48,55,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,53,44,50,46,56,50,115,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,53,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,45,49,48,46,54,52,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,45,49,48,46,54,53,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,49,46,54,56,45,48,46,55,54,45,51,46,51,57,45,49,46,53,51,45,53,46,53,49,45,50,46,49,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,48,46,53,54,44,49,46,54,53,44,49,46,49,53,44,51,46,50,56,44,49,46,56,50,44,52,46,56,55,99,48,46,55,44,48,46,50,57,44,49,46,51,55,44,48,46,53,57,44,50,46,48,53,44,48,46,57,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,115,49,48,46,54,53,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,115,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,115,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,48,46,54,55,45,48,46,51,44,49,46,51,51,45,48,46,54,44,50,46,48,50,45,48,46,56,57,99,48,46,54,54,45,49,46,54,44,49,46,50,54,45,51,46,50,50,44,49,46,56,50,45,52,46,56,55,67,49,52,51,46,57,54,44,57,57,46,53,53,44,49,52,50,46,50,54,44,49,48,48,46,51,50,44,49,52,48,46,53,56,44,49,48,49,46,48,55,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,51,54,44,49,54,46,55,50,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,115,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,48,53,44,49,46,51,56,44,54,46,50,44,50,46,56,44,49,49,46,55,54,44,51,46,49,49,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,50,45,49,46,56,52,45,52,46,48,57,45,51,46,53,55,45,54,46,50,56,45,53,46,49,57,99,45,49,46,51,56,45,48,46,52,54,45,50,46,53,57,45,49,46,48,49,45,51,46,56,51,45,49,46,53,55,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,115,45,49,48,46,54,52,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,115,45,49,48,46,54,53,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,49,46,50,53,44,48,46,53,55,45,50,46,52,57,44,49,46,49,50,45,51,46,56,57,44,49,46,53,57,99,45,50,46,49,56,44,49,46,54,49,45,52,46,50,55,44,51,46,51,52,45,54,46,50,54,44,53,46,49,55,67,50,57,46,55,57,44,49,57,46,53,50,44,51,50,46,57,52,44,49,56,46,49,44,51,54,44,49,54,46,55,50,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,49,50,56,46,49,51,44,52,55,46,56,57,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,53,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,115,45,49,48,46,54,52,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,115,45,49,48,46,54,53,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,48,46,56,55,45,48,46,51,57,45,49,46,55,53,45,48,46,55,57,45,50,46,54,57,45,49,46,49,54,99,45,48,46,53,53,44,49,46,50,49,45,49,46,48,55,44,50,46,52,52,45,49,46,53,54,44,51,46,54,57,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,48,46,57,44,48,46,51,53,44,49,46,55,53,44,48,46,55,51,44,50,46,54,44,49,46,49,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,115,49,48,46,54,53,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,115,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,48,46,56,53,45,48,46,51,57,44,49,46,54,57,45,48,46,55,54,44,50,46,53,56,45,49,46,49,49,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,48,46,52,57,45,49,46,50,53,45,49,46,48,49,45,50,46,52,55,45,49,46,53,54,45,51,46,54,57,99,45,48,46,57,51,44,48,46,51,55,45,49,46,56,44,48,46,55,55,45,50,46,54,55,44,49,46,49,54,67,49,51,55,46,51,55,44,52,54,46,53,50,44,49,51,52,46,51,52,44,52,55,46,56,57,44,49,50,56,46,49,51,44,52,55,46,56,57,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,55,53,46,48,50,44,55,46,56,57,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,50,46,48,55,45,48,46,57,51,45,52,46,49,56,45,49,46,56,56,45,55,46,48,53,45,50,46,53,99,45,51,46,52,50,44,48,46,57,50,45,54,46,55,53,44,50,46,48,54,45,57,46,57,54,44,51,46,52,52,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,48,46,56,57,45,48,46,48,55,44,49,46,56,53,45,48,46,49,50,44,50,46,57,49,45,48,46,49,50,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,99,49,46,48,52,44,48,44,49,46,57,57,44,48,46,48,52,44,50,46,56,55,44,48,46,49,49,99,45,51,46,50,45,49,46,51,55,45,54,46,53,51,45,50,46,53,49,45,57,46,57,52,45,51,46,52,51,99,45,50,46,56,54,44,48,46,54,50,45,52,46,57,55,44,49,46,53,54,45,55,46,48,51,44,50,46,52,57,13,10,32,32,32,32,32,32,32,32,32,32,32,32,67,56,52,46,50,54,44,54,46,53,50,44,56,49,46,50,50,44,55,46,56,57,44,55,53,46,48,50,44,55,46,56,57,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,50,49,46,57,44,55,53,46,56,57,99,55,46,48,55,44,48,44,49,48,46,54,53,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,115,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,115,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,50,46,50,52,45,49,46,48,49,44,52,46,52,45,49,46,57,56,44,55,46,54,45,50,46,52,56,99,45,48,46,48,56,45,49,46,51,52,45,48,46,50,49,45,50,46,54,55,45,48,46,51,54,45,51,46,57,57,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,51,46,56,54,44,48,46,53,54,45,54,46,52,49,44,49,46,55,45,56,46,56,57,44,50,46,56,51,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,53,44,50,46,56,50,115,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,53,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,45,49,48,46,54,52,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,45,49,48,46,54,53,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,50,46,52,57,45,49,46,49,50,45,53,46,48,52,45,50,46,50,55,45,56,46,57,49,45,50,46,56,51,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,48,46,49,53,44,49,46,51,50,45,48,46,50,55,44,50,46,54,53,45,48,46,51,54,44,51,46,57,57,99,51,46,50,50,44,48,46,53,44,53,46,51,56,44,49,46,52,55,44,55,46,54,51,44,50,46,52,56,67,49,49,46,50,53,44,55,52,46,50,56,44,49,52,46,56,51,44,55,53,46,56,57,44,50,49,46,57,44,55,53,46,56,57,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,54,49,46,57,49,44,49,46,49,54,99,51,46,49,56,44,49,46,52,49,44,54,46,55,49,44,50,46,55,51,44,49,51,46,49,49,44,50,46,55,51,99,54,46,51,57,44,48,44,57,46,57,50,45,49,46,51,50,44,49,51,46,49,45,50,46,55,51,67,56,51,46,56,53,44,48,46,52,49,44,55,57,46,52,56,44,48,44,55,53,44,48,13,10,32,32,32,32,32,32,32,32,32,32,32,32,67,55,48,46,53,51,44,48,44,54,54,46,49,54,44,48,46,52,49,44,54,49,46,57,49,44,49,46,49,54,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,50,49,46,57,44,57,57,46,56,57,99,55,46,48,55,44,48,44,49,48,46,54,53,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,115,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,115,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,49,46,54,49,45,48,46,55,51,44,51,46,49,56,45,49,46,52,52,44,53,46,49,50,45,49,46,57,54,99,48,46,51,57,45,49,46,52,51,44,48,46,55,53,45,50,46,56,55,44,49,46,48,53,45,52,46,51,52,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,51,46,50,55,44,48,46,54,45,53,46,53,56,44,49,46,54,52,45,55,46,56,50,44,50,46,54,53,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,53,44,50,46,56,50,115,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,53,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,45,49,48,46,54,52,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,45,49,48,46,54,53,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,50,46,50,53,45,49,46,48,50,45,52,46,53,54,45,50,46,48,54,45,55,46,56,53,45,50,46,54,54,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,48,46,51,49,44,49,46,52,54,44,48,46,54,54,44,50,46,57,49,44,49,46,48,53,44,52,46,51,52,99,49,46,57,54,44,48,46,53,51,44,51,46,53,51,44,49,46,50,52,44,53,46,49,53,44,49,46,57,55,67,49,49,46,50,53,44,57,56,46,50,56,44,49,52,46,56,51,44,57,57,46,56,57,44,50,49,46,57,44,57,57,46,56,57,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,49,48,49,46,53,55,44,49,55,46,56,57,99,45,55,46,48,55,44,48,45,49,48,46,54,52,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,115,45,49,48,46,54,53,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,48,46,54,49,44,48,45,49,46,49,56,45,48,46,48,50,45,49,46,55,51,45,48,46,48,52,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,49,46,49,51,44,49,46,50,49,45,50,46,50,50,44,50,46,52,52,45,51,46,50,55,44,51,46,55,50,99,49,46,52,54,44,48,46,50,44,51,46,49,44,48,46,51,50,44,52,46,57,57,44,48,46,51,50,99,55,46,48,55,44,48,44,49,48,46,54,53,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,49,46,56,57,44,48,44,51,46,53,50,45,48,46,49,50,44,52,46,57,55,45,48,46,51,50,99,45,49,46,48,53,45,49,46,50,56,45,50,46,49,52,45,50,46,53,50,45,51,46,50,55,45,51,46,55,51,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,48,46,53,52,44,48,46,48,50,45,49,46,49,44,48,46,48,52,45,49,46,55,44,48,46,48,52,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,53,45,50,46,56,50,67,49,49,50,46,50,50,44,49,57,46,53,49,44,49,48,56,46,54,52,44,49,55,46,56,57,44,49,48,49,46,53,55,44,49,55,46,56,57,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,50,49,46,57,44,57,49,46,56,57,99,55,46,48,55,44,48,44,49,48,46,54,53,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,115,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,115,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,50,46,48,56,45,48,46,57,52,44,52,46,48,56,45,49,46,56,52,44,54,46,57,49,45,50,46,51,54,99,48,46,50,49,45,49,46,51,55,44,48,46,51,55,45,50,46,55,53,44,48,46,53,45,52,46,49,51,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,51,46,57,53,44,48,46,53,53,45,54,46,53,52,44,49,46,55,49,45,57,46,48,54,44,50,46,56,53,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,53,44,50,46,56,50,115,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,53,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,45,49,48,46,54,52,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,45,49,48,46,54,53,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,50,46,53,51,45,49,46,49,52,45,53,46,49,50,45,50,46,51,49,45,57,46,48,57,45,50,46,56,54,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,48,46,49,51,44,49,46,51,57,44,48,46,50,57,44,50,46,55,55,44,48,46,53,44,52,46,49,51,99,50,46,56,52,44,48,46,53,50,44,52,46,56,54,44,49,46,52,51,44,54,46,57,52,44,50,46,51,55,67,49,49,46,50,53,44,57,48,46,50,56,44,49,52,46,56,51,44,57,49,46,56,57,44,50,49,46,57,44,57,49,46,56,57,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,50,49,46,57,44,56,51,46,56,57,99,55,46,48,55,44,48,44,49,48,46,54,53,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,115,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,115,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,50,46,50,54,45,49,46,48,50,44,52,46,52,52,45,50,44,55,46,54,57,45,50,46,52,57,99,48,46,48,53,45,49,46,48,55,44,48,46,48,56,45,50,46,49,52,44,48,46,48,56,45,51,46,50,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,48,45,48,46,50,56,45,48,46,48,50,45,48,46,53,53,45,48,46,48,50,45,48,46,56,51,99,45,52,46,49,53,44,48,46,53,51,45,54,46,56,44,49,46,55,51,45,57,46,52,44,50,46,57,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,53,44,50,46,56,50,115,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,53,45,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,115,45,49,48,46,54,52,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,115,45,49,48,46,54,53,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,50,46,54,45,49,46,49,55,45,53,46,50,54,45,50,46,51,56,45,57,46,52,50,45,50,46,57,67,48,46,48,50,44,55,52,46,52,53,44,48,44,55,52,46,55,50,44,48,44,55,53,99,48,44,49,46,48,56,44,48,46,48,52,44,50,46,49,53,44,48,46,48,56,44,51,46,50,50,99,51,46,50,54,44,48,46,52,57,44,53,46,52,53,44,49,46,52,55,44,55,46,55,49,44,50,46,53,13,10,32,32,32,32,32,32,32,32,32,32,32,32,67,49,49,46,50,53,44,56,50,46,50,56,44,49,52,46,56,51,44,56,51,46,56,57,44,50,49,46,57,44,56,51,46,56,57,122,34,47,62,13,10,32,32,32,32,32,32,32,32,60,47,103,62,13,10,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,51,34,32,100,61,34,77,54,46,50,54,44,49,48,53,99,49,49,46,53,56,44,50,54,46,52,56,44,51,55,46,57,57,44,52,53,44,54,56,46,55,52,44,52,53,115,53,55,46,49,55,45,49,56,46,53,50,44,54,56,46,55,52,45,52,53,72,54,46,50,54,122,34,47,62,13,10,32,32,32,32,32,32,32,32,60,112,111,108,121,103,111,110,32,99,108,97,115,115,61,34,115,116,50,34,32,112,111,105,110,116,115,61,34,55,52,46,50,53,44,49,51,53,46,51,50,32,54,52,46,51,53,44,49,50,53,46,52,50,32,54,55,46,49,56,44,49,50,50,46,54,32,55,52,46,50,53,44,49,50,57,46,54,55,32,56,54,46,56,51,44,49,49,55,46,48,57,32,56,57,46,54,53,44,49,49,57,46,57,50,32,34,47,62,13,10,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,55,53,44,53,50,99,50,56,46,53,55,44,48,44,51,48,44,53,51,44,51,48,44,53,51,72,52,53,67,52,53,44,49,48,53,44,52,54,46,52,51,44,53,50,44,55,53,44,53,50,122,34,47,62,13,10,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,48,34,32,100,61,34,77,55,53,44,53,50,118,53,51,104,51,48,67,49,48,53,44,49,48,53,44,49,48,51,46,53,55,44,53,50,44,55,53,44,53,50,122,34,47,62,13,10,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,52,34,32,100,61,34,77,55,53,44,53,50,118,53,51,104,51,48,67,49,48,53,44,49,48,53,44,49,48,51,46,53,55,44,53,50,44,55,53,44,53,50,122,34,47,62,13,10,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,53,34,32,100,61,34,77,55,53,44,52,53,99,49,56,46,49,44,48,44,49,57,44,54,48,44,49,57,44,54,48,72,53,54,67,53,54,44,49,48,53,44,53,54,46,57,44,52,53,44,55,53,44,52,53,122,34,47,62,13,10,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,54,34,32,100,61,34,77,55,53,44,52,53,99,56,46,53,55,44,48,44,57,44,54,48,44,57,44,54,48,104,49,48,67,57,52,44,49,48,53,44,57,51,46,49,44,52,53,44,55,53,44,52,53,122,34,47,62,13,10,32,32,32,32,32,32,32,32,60,101,108,108,105,112,115,101,32,99,108,97,115,115,61,34,115,116,53,34,32,99,120,61,34,55,53,34,32,99,121,61,34,50,56,34,32,114,120,61,34,56,34,32,114,121,61,34,49,50,34,47,62,13,10,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,54,34,32,100,61,34,77,55,53,44,49,54,99,50,46,55,54,44,48,44,53,44,53,46,51,55,44,53,44,49,50,99,48,44,54,46,54,51,45,50,46,50,52,44,49,50,45,53,44,49,50,99,52,46,52,50,44,48,44,56,45,53,46,51,55,44,56,45,49,50,67,56,51,44,50,49,46,51,55,44,55,57,46,52,50,44,49,54,44,55,53,44,49,54,122,34,47,62,13,10,32,32,32,32,32,32,32,32,60,47,115,118,103,62,13,10,13,10,32,32,32,32,32,32,60,47,116,100,62,13,10,32,32,32,32,60,47,116,114,62,13,10,13,10,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,60,116,100,32,97,108,105,103,110,61,34,99,101,110,116,101,114,34,62,13,10,32,32,32,32,32,32,32,32,60,100,105,118,32,115,116,121,108,101,61,34,99,111,108,111,114,58,32,35,52,56,53,52,53,100,59,32,102,111,110,116,45,115,105,122,101,58,32,49,56,112,120,59,32,102,111,110,116,45,119,101,105,103,104,116,58,32,51,48,48,59,32,108,105,110,101,45,104,101,105,103,104,116,58,32,50,52,112,120,59,32,109,97,114,103,105,110,58,32,51,54,112,120,32,97,117,116,111,32,49,56,112,120,59,34,62,13,10,32,32,32,32,32,32,32,32,32,32,89,111,117,114,32,105,100,101,110,116,105,116,121,32,104,97,115,32,98,101,101,110,32,118,101,114,105,102,105,101,100,46,13,10,32,32,32,32,32,32,32,32,60,47,100,105,118,62,13,10,32,32,32,32,32,32,60,47,116,100,62,13,10,32,32,32,32,60,47,116,114,62,13,10,13,10,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,60,116,100,32,97,108,105,103,110,61,34,99,101,110,116,101,114,34,62,13,10,32,32,32,32,32,32,32,32,60,112,32,115,116,121,108,101,61,34,109,97,114,103,105,110,45,116,111,112,58,50,48,112,120,59,32,109,97,114,103,105,110,45,98,111,116,116,111,109,58,48,59,34,62,13,10,32,32,32,32,32,32,32,32,32,32,60,97,32,104,114,101,102,61,34,104,116,116,112,115,58,47,47,119,119,119,46,99,111,105,110,98,97,115,101,46,99,111,109,47,98,117,121,34,32,115,116,121,108,101,61,34,100,105,115,112,108,97,121,58,32,105,110,108,105,110,101,45,98,108,111,99,107,59,32,102,111,110,116,45,115,105,122,101,58,32,49,56,112,120,59,32,102,111,110,116,45,119,101,105,103,104,116,58,32,51,48,48,59,32,112,97,100,100,105,110,103,58,32,51,48,112,120,59,32,98,97,99,107,103,114,111,117,110,100,45,99,111,108,111,114,58,32,35,52,65,57,48,69,50,59,32,99,111,108,111,114,58,32,35,102,102,102,102,102,102,59,32,98,111,114,100,101,114,45,114,97,100,105,117,115,58,32,52,112,120,59,32,116,101,120,116,45,100,101,99,111,114,97,116,105,111,110,58,32,110,111,110,101,59,32,119,105,100,116,104,58,32,52,48,37,59,34,62,66,117,121,32,68,105,103,105,116,97,108,32,67,117,114,114,101,110,99,121,60,47,97,62,13,10,32,32,32,32,32,32,32,32,60,47,112,62,13,10,32,32,32,32,32,32,60,47,116,100,62,13,10,32,32,32,32,60,47,116,114,62,13,10,32,32,60,47,116,98,111,100,121,62,13,10,60,47,116,97,98,108,101,62,13,10,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,104,101,105,103,104,116,61,34,52,48,34,62,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,98,111,100,121,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,97,98,108,101,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,33,45,45,32,77,97,105,110,32,67,79,78,84,69,78,84,32,101,110,100,32,104,101,114,101,32,45,45,62,13,10,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,33,45,45,32,80,82,79,77,79,32,99,111,108,117,109,110,32,115,116,97,114,116,32,104,101,114,101,32,45,45,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,33,45,45,32,83,104,111,119,32,109,111,98,105,108,101,32,112,114,111,109,111,32,55,53,37,32,111,102,32,116,104,101,32,116,105,109,101,32,45,45,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,97,98,108,101,32,105,100,61,34,112,114,111,109,111,34,32,119,105,100,116,104,61,34,49,48,48,37,34,32,99,101,108,108,112,97,100,100,105,110,103,61,34,48,34,32,99,101,108,108,115,112,97,99,105,110,103,61,34,48,34,32,98,111,114,100,101,114,61,34,48,34,32,115,116,121,108,101,61,34,109,97,114,103,105,110,45,116,111,112,58,50,48,112,120,59,34,32,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,98,111,100,121,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,99,111,108,115,112,97,110,61,34,50,34,32,104,101,105,103,104,116,61,34,50,48,34,62,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,99,111,108,115,112,97,110,61,34,50,34,32,97,108,105,103,110,61,34,99,101,110,116,101,114,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,115,112,97,110,32,115,116,121,108,101,61,34,102,111,110,116,45,115,105,122,101,58,49,52,112,120,59,32,102,111,110,116,45,119,101,105,103,104,116,58,52,48,48,59,32,109,97,114,103,105,110,45,98,111,116,116,111,109,58,49,48,112,120,59,32,99,111,108,111,114,58,32,35,102,101,102,101,102,101,59,32,102,111,110,116,45,102,97,109,105,108,121,58,32,45,97,112,112,108,101,45,115,121,115,116,101,109,44,66,108,105,110,107,77,97,99,83,121,115,116,101,109,70,111,110,116,44,38,35,51,57,59,83,101,103,111,101,32,85,73,38,35,51,57,59,44,38,35,51,57,59,82,111,98,111,116,111,38,35,51,57,59,44,38,35,51,57,59,79,120,121,103,101,110,38,35,51,57,59,44,38,35,51,57,59,85,98,117,110,116,117,38,35,51,57,59,44,38,35,51,57,59,67,97,110,116,97,114,101,108,108,38,35,51,57,59,44,38,35,51,57,59,70,105,114,97,32,83,97,110,115,38,35,51,57,59,44,38,35,51,57,59,68,114,111,105,100,32,83,97,110,115,38,35,51,57,59,44,38,35,51,57,59,72,101,108,118,101,116,105,99,97,32,78,101,117,101,38,35,51,57,59,44,115,97,110,115,45,115,101,114,105,102,59,34,62,71,101,116,32,116,104,101,32,108,97,116,101,115,116,32,67,111,105,110,98,97,115,101,32,65,112,112,32,102,111,114,32,121,111,117,114,32,112,104,111,110,101,60,47,115,112,97,110,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,99,111,108,115,112,97,110,61,34,50,34,32,104,101,105,103,104,116,61,34,50,48,34,62,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,118,97,108,105,103,110,61,34,116,111,112,34,32,119,105,100,116,104,61,34,53,48,37,34,32,97,108,105,103,110,61,34,114,105,103,104,116,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,97,32,104,114,101,102,61,34,104,116,116,112,115,58,47,47,99,111,110,116,114,111,108,46,107,111,99,104,97,118,97,46,99,111,109,47,118,49,47,99,112,105,47,99,108,105,99,107,63,99,97,109,112,97,105,103,110,95,105,100,61,107,111,99,111,105,110,98,97,115,101,45,105,111,115,53,53,50,53,51,51,98,54,56,51,55,55,57,100,52,52,53,52,101,53,54,99,57,48,97,48,38,110,101,116,119,111,114,107,95,105,100,61,50,52,50,54,38,100,101,118,105,99,101,95,105,100,61,100,101,118,105,99,101,95,105,100,38,115,105,116,101,95,105,100,61,49,34,32,115,116,121,108,101,61,34,100,105,115,112,108,97,121,58,105,110,108,105,110,101,45,98,108,111,99,107,59,109,97,114,103,105,110,45,114,105,103,104,116,58,49,48,112,120,59,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,105,109,103,32,115,114,99,61,34,104,116,116,112,115,58,47,47,115,51,46,97,109,97,122,111,110,97,119,115,46,99,111,109,47,97,112,112,45,112,117,98,108,105,99,47,67,111,105,110,98,97,115,101,45,101,109,97,105,108,47,105,79,83,95,97,112,112,46,112,110,103,34,32,104,101,105,103,104,116,61,34,52,48,34,32,98,111,114,100,101,114,61,34,48,34,32,97,108,116,61,34,67,111,105,110,98,97,115,101,32,105,79,83,32,109,111,98,105,108,101,32,98,105,116,99,111,105,110,32,119,97,108,108,101,116,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,97,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,118,97,108,105,103,110,61,34,116,111,112,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,97,32,104,114,101,102,61,34,104,116,116,112,115,58,47,47,99,111,110,116,114,111,108,46,107,111,99,104,97,118,97,46,99,111,109,47,118,49,47,99,112,105,47,99,108,105,99,107,63,99,97,109,112,97,105,103,110,95,105,100,61,107,111,99,111,105,110,98,97,115,101,45,45,45,45,112,114,111,100,117,99,116,105,111,110,53,53,51,101,99,51,98,101,50,53,99,49,51,48,56,100,97,102,50,97,53,100,50,55,57,49,38,110,101,116,119,111,114,107,95,105,100,61,50,52,50,54,38,100,101,118,105,99,101,95,105,100,61,100,101,118,105,99,101,95,105,100,38,115,105,116,101,95,105,100,61,49,38,97,112,112,101,110,100,95,97,112,112,95,99,111,110,118,95,116,114,107,95,112,97,114,97,109,115,61,49,34,32,115,116,121,108,101,61,34,100,105,115,112,108,97,121,58,105,110,108,105,110,101,45,98,108,111,99,107,59,109,97,114,103,105,110,45,108,101,102,116,58,53,112,120,59,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,105,109,103,32,115,114,99,61,34,104,116,116,112,115,58,47,47,115,51,46,97,109,97,122,111,110,97,119,115,46,99,111,109,47,97,112,112,45,112,117,98,108,105,99,47,67,111,105,110,98,97,115,101,45,101,109,97,105,108,47,65,110,100,114,111,105,100,95,97,112,112,46,112,110,103,34,32,104,101,105,103,104,116,61,34,52,48,34,32,98,111,114,100,101,114,61,34,48,34,32,97,108,116,61,34,67,111,105,110,98,97,115,101,32,65,110,100,114,111,105,100,32,109,111,98,105,108,101,32,98,105,116,99,111,105,110,32,119,97,108,108,101,116,34,32,115,116,121,108,101,61,34,104,101,105,103,104,116,58,52,48,32,33,105,109,112,111,114,116,97,110,116,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,97,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,99,111,108,115,112,97,110,61,34,50,34,32,104,101,105,103,104,116,61,34,50,48,34,62,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,98,111,100,121,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,97,98,108,101,62,13,10,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,33,45,45,32,80,82,79,77,79,32,99,111,108,117,109,110,32,101,110,100,32,104,101,114,101,32,45,45,62,13,10,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,33,45,45,32,70,79,79,84,69,82,32,115,116,97,114,116,32,104,101,114,101,32,45,45,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,97,98,108,101,32,119,105,100,116,104,61,34,49,48,48,37,34,32,99,101,108,108,112,97,100,100,105,110,103,61,34,48,34,32,99,101,108,108,115,112,97,99,105,110,103,61,34,48,34,32,98,111,114,100,101,114,61,34,48,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,98,111,100,121,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,104,101,105,103,104,116,61,34,49,48,34,62,38,110,98,115,112,59,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,118,97,108,105,103,110,61,34,116,111,112,34,32,97,108,105,103,110,61,34,99,101,110,116,101,114,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,115,112,97,110,32,115,116,121,108,101,61,34,102,111,110,116,45,102,97,109,105,108,121,58,32,45,97,112,112,108,101,45,115,121,115,116,101,109,44,66,108,105,110,107,77,97,99,83,121,115,116,101,109,70,111,110,116,44,38,35,51,57,59,83,101,103,111,101,32,85,73,38,35,51,57,59,44,38,35,51,57,59,82,111,98,111,116,111,38,35,51,57,59,44,38,35,51,57,59,79,120,121,103,101,110,38,35,51,57,59,44,38,35,51,57,59,85,98,117,110,116,117,38,35,51,57,59,44,38,35,51,57,59,67,97,110,116,97,114,101,108,108,38,35,51,57,59,44,38,35,51,57,59,70,105,114,97,32,83,97,110,115,38,35,51,57,59,44,38,35,51,57,59,68,114,111,105,100,32,83,97,110,115,38,35,51,57,59,44,38,35,51,57,59,72,101,108,118,101,116,105,99,97,32,78,101,117,101,38,35,51,57,59,44,115,97,110,115,45,115,101,114,105,102,59,32,99,111,108,111,114,58,32,35,102,101,102,101,102,101,59,32,102,111,110,116,45,115,105,122,101,58,49,48,112,120,59,34,62,38,99,111,112,121,59,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,97,32,104,114,101,102,61,34,104,116,116,112,115,58,47,47,119,119,119,46,99,111,105,110,98,97,115,101,46,99,111,109,47,34,32,116,97,114,103,101,116,61,34,95,98,108,97,110,107,34,32,115,116,121,108,101,61,34,99,111,108,111,114,58,32,35,102,101,102,101,102,101,32,33,105,109,112,111,114,116,97,110,116,59,32,116,101,120,116,45,100,101,99,111,114,97,116,105,111,110,58,110,111,110,101,59,34,62,67,111,105,110,98,97,115,101,60,47,97,62,32,50,48,50,48,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,115,112,97,110,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,104,101,105,103,104,116,61,34,53,48,34,62,38,110,98,115,112,59,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,98,111,100,121,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,97,98,108,101,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,33,45,45,32,70,79,79,84,69,82,32,101,110,100,32,104,101,114,101,32,45,45,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,98,111,100,121,62,13,10,32,32,32,32,32,32,32,32,32,32,60,47,116,97,98,108,101,62,13,10,32,32,32,32,32,32,32,32,60,47,116,100,62,13,10,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,60,47,116,98,111,100,121,62,13,10,32,32,60,47,116,97,98,108,101,62,13,10,60,47,98,111,100,121,62,13,10,60,47,104,116,109,108,62,13,10,128,0,0,0,0,0,0,0] +print("".join([chr(int(c)) for c in l])) \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index cbcec568f..2e7ca6b6c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2280,13 +2280,13 @@ __metadata: languageName: node linkType: hard -"@iden3/binfileutils@npm:0.0.11": - version: 0.0.11 - resolution: "@iden3/binfileutils@npm:0.0.11" +"@iden3/binfileutils@npm:0.0.10": + version: 0.0.10 + resolution: "@iden3/binfileutils@npm:0.0.10" dependencies: - fastfile: 0.0.20 + fastfile: 0.0.19 ffjavascript: ^0.2.48 - checksum: ca61db1325c7e038c6bd723c856eff5f2c82c76394db09d3350ef4f5b7525e3c9ab1f7429900ff5d3e9d26c5970bf5900e6126ccb5c5caa597c16a47336a6be8 + checksum: cdeb8ac01e12f485d9fb236654c00d5d5016fc89eae24f7822885dd42f09935cbef601dbdd8a0c96dfb00ded9f4f623e0eec0b568aa86d16522cf77ce6f9498b languageName: node linkType: hard @@ -5697,6 +5697,13 @@ __metadata: languageName: node linkType: hard +"big-integer@npm:^1.6.42, big-integer@npm:^1.6.48": + version: 1.6.51 + resolution: "big-integer@npm:1.6.51" + checksum: 3d444173d1b2e20747e2c175568bedeebd8315b0637ea95d75fd27830d3b8e8ba36c6af40374f36bdaea7b5de376dcada1b07587cb2a79a928fccdb6e6e3c518 + languageName: node + linkType: hard + "big.js@npm:^5.2.2": version: 5.2.2 resolution: "big.js@npm:5.2.2" @@ -6683,14 +6690,14 @@ __metadata: languageName: node linkType: hard -"circom_runtime@npm:0.1.21": - version: 0.1.21 - resolution: "circom_runtime@npm:0.1.21" +"circom_runtime@npm:0.1.17": + version: 0.1.17 + resolution: "circom_runtime@npm:0.1.17" dependencies: - ffjavascript: 0.2.56 + ffjavascript: 0.2.48 bin: calcwit: calcwit.js - checksum: 3071f1e0fba9a5fb41c940454edb911ce09edfd5d0bd12156ec79045a0bf3ff2cc5b35f46e84e42902ef8bb0a4166f428b75d0ceb363c0d485f1a111b27daba1 + checksum: 595fc0cc3a62ba5daf8d849feae41c48805c0df43965f85dde4dc434efb607e455fa7801d41c1feacfe0c3c71952a45cd3985abf26fde40c54138392891afd8c languageName: node linkType: hard @@ -8332,7 +8339,6 @@ __metadata: ethereumjs-abi: ^0.6.8 ethers: ^5.7.1 forge-std: ^1.1.2 - husky: ^8.0.3 jest-junit: ^15.0.0 libmime: ^5.1.0 localforage: ^1.10.0 @@ -8356,7 +8362,7 @@ __metadata: readline: ^1.3.0 selenium-webdriver: ^4.8.1 serve: ^14.0.1 - snarkjs: "https://github.com/sampritipanda/snarkjs.git#fef81fc51d17a734637555c6edbd585ecda02d9e" + snarkjs: "git+https://github.com/vb7401/snarkjs.git#24981febe8826b6ab76ae4d76cf7f9142919d2b8" sshpk: ^1.17.0 styled-components: ^5.3.5 ts-node: ^10.9.1 @@ -9769,10 +9775,10 @@ __metadata: languageName: node linkType: hard -"fastfile@npm:0.0.20": - version: 0.0.20 - resolution: "fastfile@npm:0.0.20" - checksum: e5d6e5f57a9b58c9534202e477cbffbca2182c407171950695ddb5c3e6b89554bc8561fbb6e370c99e371a8f23486a23fbaca527827886cec4897d481cbd03b6 +"fastfile@npm:0.0.19, fastfile@npm:^0.0.19": + version: 0.0.19 + resolution: "fastfile@npm:0.0.19" + checksum: 6179bdd7c21be9882294dae66103795c099594098b51958bcf08a4545c91387321b43511730d0542a5a9ed8c5ec9069c065e065fd67255453ac900a23895dac1 languageName: node linkType: hard @@ -9850,14 +9856,15 @@ __metadata: languageName: node linkType: hard -"ffjavascript@npm:0.2.56": - version: 0.2.56 - resolution: "ffjavascript@npm:0.2.56" +"ffjavascript@npm:0.2.48": + version: 0.2.48 + resolution: "ffjavascript@npm:0.2.48" dependencies: - wasmbuilder: 0.0.16 - wasmcurves: 0.2.0 + big-integer: ^1.6.48 + wasmbuilder: ^0.0.12 + wasmcurves: 0.1.0 web-worker: ^1.2.0 - checksum: d4e02263db4a94d111cdc7c1211ae96769370f5c8c3c338331e0ef99faed7b55e640bedf23fa8a83fc9a77f0e81140ea8f32e392812a00e15ca504221b879a4f + checksum: 68beae9a4f642c06656685353b84fd7655020ca0e628ea046e94452ab779587953cc45cde106d74b68be7177b49c8f19b105d6552c4a1d715e784ae9e7c9ed34 languageName: node linkType: hard @@ -11090,15 +11097,6 @@ __metadata: languageName: node linkType: hard -"husky@npm:^8.0.3": - version: 8.0.3 - resolution: "husky@npm:8.0.3" - bin: - husky: lib/bin.js - checksum: 837bc7e4413e58c1f2946d38fb050f5d7324c6f16b0fd66411ffce5703b294bd21429e8ba58711cd331951ee86ed529c5be4f76805959ff668a337dbfa82a1b0 - languageName: node - linkType: hard - "hyphenate-style-name@npm:^1.0.3": version: 1.0.4 resolution: "hyphenate-style-name@npm:1.0.4" @@ -16627,15 +16625,15 @@ __metadata: languageName: node linkType: hard -"r1csfile@npm:0.0.41": - version: 0.0.41 - resolution: "r1csfile@npm:0.0.41" +"r1csfile@npm:0.0.35": + version: 0.0.35 + resolution: "r1csfile@npm:0.0.35" dependencies: "@iden3/bigarray": 0.0.2 - "@iden3/binfileutils": 0.0.11 - fastfile: 0.0.20 - ffjavascript: 0.2.56 - checksum: eec689416f66f09db2d6ca66fac1ef6841b088ab29abcde487145ebd2110916c92583e11ac86f0cdcc4e8a3a7c7df9ff5352ad959e8ae385d37c3b51cec5cf4d + "@iden3/binfileutils": 0.0.10 + fastfile: 0.0.19 + ffjavascript: 0.2.48 + checksum: 84f7b4eab5bcdd6a3f6d699998c9479a5eff8d670383d4f0c5afc08431f45353abab9a8b07eeabaef89807e24b0ba50611d4d6280eb6c3a7483e1487a91f0ac6 languageName: node linkType: hard @@ -18375,24 +18373,23 @@ __metadata: languageName: node linkType: hard -"snarkjs@https://github.com/sampritipanda/snarkjs.git#fef81fc51d17a734637555c6edbd585ecda02d9e": - version: 0.5.0 - resolution: "snarkjs@https://github.com/sampritipanda/snarkjs.git#commit=fef81fc51d17a734637555c6edbd585ecda02d9e" +"snarkjs@git+https://github.com/vb7401/snarkjs.git#24981febe8826b6ab76ae4d76cf7f9142919d2b8": + version: 0.4.12 + resolution: "snarkjs@https://github.com/vb7401/snarkjs.git#commit=24981febe8826b6ab76ae4d76cf7f9142919d2b8" dependencies: - "@iden3/binfileutils": 0.0.11 - bfj: ^7.0.2 + "@iden3/binfileutils": 0.0.10 blake2b-wasm: ^2.4.0 - circom_runtime: 0.1.21 + circom_runtime: 0.1.17 ejs: ^3.1.6 - fastfile: 0.0.20 - ffjavascript: 0.2.56 + fastfile: ^0.0.19 + ffjavascript: 0.2.48 js-sha3: ^0.8.0 - localforage: ^1.10.0 logplease: ^1.2.15 - r1csfile: 0.0.41 + r1csfile: 0.0.35 + readline: ^1.3.0 bin: snarkjs: build/cli.cjs - checksum: f2050f0135d50d459ea0edddf3e394e833a2d28c6648e5889b2f896814865e5c60606e978a8a106bd5bfe7e27501c315f249db5b71895d5e7e6e9a87bfcd55ab + checksum: 9011df4b58475a0b4ae988f8b459a9a4d2bb5d2b60221d0ec370a10f2492c88909768215f3b22e514b2cf24dca79818790447005a33ed6aee177b9fda6948a75 languageName: node linkType: hard @@ -20545,6 +20542,25 @@ __metadata: languageName: node linkType: hard +"wasmbuilder@npm:^0.0.12": + version: 0.0.12 + resolution: "wasmbuilder@npm:0.0.12" + dependencies: + big-integer: ^1.6.48 + checksum: 327b3c50b0e1e5e3aac9e218e0f96fdc638b7952ab86acc2ad53960371996826dbb0a8095edce482cf1d9c245d96884449701909bc962920aa7ec8241db01214 + languageName: node + linkType: hard + +"wasmcurves@npm:0.1.0": + version: 0.1.0 + resolution: "wasmcurves@npm:0.1.0" + dependencies: + big-integer: ^1.6.42 + blakejs: ^1.1.0 + checksum: 6bf6719e659a88904af0b98d152316e3b22435ca6a2cfc8bbf4530576806f17b2776b2c7d91d1a678fe0d51485a0d1748efcd080808c181c7977bee50b26efa9 + languageName: node + linkType: hard + "wasmcurves@npm:0.2.0": version: 0.2.0 resolution: "wasmcurves@npm:0.2.0" From 02ed05509c0cbe8bd1a3066bcd05d2e6cff01b6b Mon Sep 17 00:00:00 2001 From: Anka Hu Date: Fri, 31 Mar 2023 01:24:09 +0700 Subject: [PATCH 02/25] dropdown menu for instructions --- src/components/NumberedStep.tsx | 82 +++++++++++++++++++++++++++------ src/pages/MainPage.tsx | 46 ++++++++++-------- 2 files changed, 95 insertions(+), 33 deletions(-) diff --git a/src/components/NumberedStep.tsx b/src/components/NumberedStep.tsx index ac0f6bb1c..443d43a40 100644 --- a/src/components/NumberedStep.tsx +++ b/src/components/NumberedStep.tsx @@ -1,22 +1,65 @@ +import React, { useState } from "react"; import styled from "styled-components"; -import { CenterAllDiv, Row } from "./Layout"; +import { CenterAllDiv, Col, Row } from "./Layout"; +import "./NNS.css"; + +type Step = { + title: string; + description: string; +}; + +const Step = ({ index, title, description, active, onClick }: any) => ( +
onClick(index + 1)}> + {index + 1} + {title} + {active && {description}} +
+ +); + +export const NNS: React.FC<{ + steps: Step[]; +}> = ({ steps }) => { + const [activeStep, setActiveStep] = useState(0); + + const handleClick = (index: number) => { + if (index == activeStep) { + setActiveStep(0) + } else { + setActiveStep(index); + }; + }; + + const progress = ((activeStep + 1) / steps.length) * 100; -export const NumberedStep: React.FC<{ - step: number; - children: React.ReactNode; -}> = ({ step, children }) => { return ( - - - {step} - - {children} - + +
+ + {steps.map((step, index) => ( + + ))} + +
); }; -const NumberedStepContainer = styled(Row)` - background: rgba(255, 255, 255, 0.05); +const NNSSteps = styled(CenterAllDiv)` + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 16px; + margin-top: 2rm; +`; + +const NNSContainer = styled(Col)` width: 100%; gap: 1rem; border-radius: 4px; @@ -24,7 +67,7 @@ const NumberedStepContainer = styled(Row)` color: #fff; `; -const NumberedStepLabel = styled(CenterAllDiv)` +const StepCount = styled(CenterAllDiv)` background: rgba(255, 255, 255, 0.2); border-radius: 4px; width: 24px; @@ -33,4 +76,13 @@ const NumberedStepLabel = styled(CenterAllDiv)` border: 1px solid rgba(255, 255, 255, 0.3); `; -const NumberedStepText = styled.span``; +const StepTitle = styled(CenterAllDiv)` + font-size: 18px; + font-weight: bold; +`; + +const StepDescription = styled(CenterAllDiv)` + font-size: 14px; +`; + +// export default ProgressBar; diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx index 06ec59745..764aea64e 100644 --- a/src/pages/MainPage.tsx +++ b/src/pages/MainPage.tsx @@ -168,6 +168,33 @@ export const MainPage: React.FC<{}> = (props) => { } }, [value]); + const steps = [ + { + title: "Send a Twitter Password Reset Email", + description: "Send yourself a Twitter password reset email from Twitter." + }, + { + title: "Download Email Contents", + description: "In your inbox, find the email from Twitter and click the three dot menu, then 'Show original' then 'Copy to clipboard'. If on Outlook, download the original email as .eml and copy it instead.' " + }, + { + title: "Paste Input", + description: "Copy paste that into the box below. Note that we cannot use this to phish you: we do not know your password, and we never get this email info because we have no server at all. We are actively searching for a less sketchy email." + }, + { + title: "Add Ethereum Address", + description: "Paste in your sending Ethereum address. This ensures that no one else can 'steal' your proof for another account (frontrunning protection!)." + }, + { + title: "Generate Proof", + description: "Click 'Generate Proof'. Since it is completely client side and open source, and you are not trusting us with any private information." + }, + { + title: "Verify & Mint NFT Soul Token", + description: "Click 'Verify' and then 'Mint Twitter Badge On-Chain', and approve to mint the NFT badge that proves Twitter ownership! Note that it is 700K gas right now so only feasible on Goerli, though we intend to reduce this soon." + } + ]; + if (error) console.error(error); return ( @@ -190,24 +217,7 @@ export const MainPage: React.FC<{}> = (props) => { limits of incognito mode and non-chrome browsers, you must use Chrome to generate proofs right now. Our goal for March 2023 is to make this process 10x faster and smaller. If you wish to generate a ZK proof of Twitter badge, you must do these: - - Send yourself a password reset email from Twitter in incognito. - - In your inbox, find the email from Twitter and download headers (three dots, then download message). - - Copy paste the entire contents of the .eml file into the box below. Note that we cannot use this to phish you: we do not know your password, and we never get this email - info because we have no server at all. We are actively searching for a less sketchy email. - - - Paste in your sending Ethereum address. This ensures that no one else can "steal" your proof for another account (frontrunning protection!). - - - Click "Generate Proof". Since it is completely client side and open source, and you are not trusting us with any private information. - - - Click "Verify" and then "Mint Twitter Badge On-Chain", and approve to mint the NFT badge that proves Twitter ownership! Note that it is 700K gas right now - so only feasible on Goerli, though we intend to reduce this soon. - + {}
From 72d23674347b79cdad08aea0f7284ddabc6a4165 Mon Sep 17 00:00:00 2001 From: Nathan Xiong Date: Fri, 31 Mar 2023 14:13:12 +0700 Subject: [PATCH 03/25] added solidity contract --- circuits/email_airbnb.circom | 3 +- circuits/email_both.circom | 8 +- circuits/email_coinbase.circom | 3 +- package.json | 2 +- src/contracts/src/KYCEmailHandler.sol | 203 ++++++++++++++++++++++++++ src/helpers/constants.ts | 3 +- src/scripts/generate_two_inputs.ts | 10 +- yarn.lock | 104 +++++-------- 8 files changed, 258 insertions(+), 78 deletions(-) create mode 100644 src/contracts/src/KYCEmailHandler.sol diff --git a/circuits/email_airbnb.circom b/circuits/email_airbnb.circom index cbe0fc84f..917177478 100644 --- a/circuits/email_airbnb.circom +++ b/circuits/email_airbnb.circom @@ -16,7 +16,8 @@ include "./base64.circom"; template AirbnbEmailVerify(max_header_bytes, n, k) { assert(max_header_bytes % 64 == 0); // assert(max_body_bytes % 64 == 0); - assert(n * k > 2048); // constraints for 2048 bit RSA + // assert(n * k > 2048); // constraints for 2048 bit RSA + assert(n * k > 1024); // costraints for 1024 bit RSA assert(k < 255 \ 2); // we want a multiplication to fit into a circom signal var max_packed_bytes = (max_header_bytes - 1) \ 7 + 1; // ceil(max_num_bytes / 7) diff --git a/circuits/email_both.circom b/circuits/email_both.circom index 3a503e778..e4d720813 100644 --- a/circuits/email_both.circom +++ b/circuits/email_both.circom @@ -13,7 +13,8 @@ include "./email_coinbase.circom"; template KYCVerify(max_header_bytes, n, k) { assert(max_header_bytes % 64 == 0); // assert(max_body_bytes % 64 == 0); - assert(n * k > 2048); // constraints for 2048 bit RSA + // assert(n * k > 2048); // constraints for 2048 bit RSA, e.g., Twitter + assert(n * k > 1024); // constraints for 1024 bit RSA, e.g., Airbnb and Coinbase assert(k < 255 \ 2); // we want a multiplication to fit into a circom signal // max_num_bytes must be a multiple of 64 @@ -45,9 +46,8 @@ template KYCVerify(max_header_bytes, n, k) { signal input address_coinbase; signal input address_plus_one_coinbase; - // OUTPUT SIGNALS // Outputs the packed version of the from/to emails from both emails - signal output reveal_packed[2 * max_packed_bytes]; + signal reveal_packed[2 * max_packed_bytes]; component airbnb_verify = AirbnbEmailVerify(max_header_bytes, n, k); component coinbase_verify = CoinbaseEmailVerify(max_header_bytes, n, k); @@ -107,4 +107,4 @@ template KYCVerify(max_header_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_airbnb, modulus_coinbase, address_airbnb, address_coinbase ] } = KYCVerify(1024, 121, 17); +component main { public [ modulus_airbnb, modulus_coinbase, address_airbnb, address_coinbase ] } = KYCVerify(1024, 121, 9); diff --git a/circuits/email_coinbase.circom b/circuits/email_coinbase.circom index 455938286..fed1478ca 100644 --- a/circuits/email_coinbase.circom +++ b/circuits/email_coinbase.circom @@ -16,7 +16,8 @@ include "./base64.circom"; template CoinbaseEmailVerify(max_header_bytes, n, k) { assert(max_header_bytes % 64 == 0); // assert(max_body_bytes % 64 == 0); - assert(n * k > 2048); // constraints for 2048 bit RSA + // assert(n * k > 2048); // constraints for 2048 bit RSA + assert(n * k > 1024); // constraints for 1024 bit RSA assert(k < 255 \ 2); // we want a multiplication to fit into a circom signal var max_packed_bytes = (max_header_bytes - 1) \ 7 + 1; // ceil(max_num_bytes / 7) diff --git a/package.json b/package.json index e4439d629..026b74c98 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "react-use": "^17.3.2", "readline": "^1.3.0", "serve": "^14.0.1", - "snarkjs": "git+https://github.com/vb7401/snarkjs.git#24981febe8826b6ab76ae4d76cf7f9142919d2b8", + "snarkjs": "https://github.com/sampritipanda/snarkjs.git#fef81fc51d17a734637555c6edbd585ecda02d9e", "sshpk": "^1.17.0", "styled-components": "^5.3.5", "ts-node": "^10.9.1", diff --git a/src/contracts/src/KYCEmailHandler.sol b/src/contracts/src/KYCEmailHandler.sol new file mode 100644 index 000000000..482897e55 --- /dev/null +++ b/src/contracts/src/KYCEmailHandler.sol @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; +import "@openzeppelin/contracts/utils/Counters.sol"; +import "forge-std/console.sol"; +// import "./base64.sol"; +import "./NFTSVG.sol"; +import "./emailVerifier.sol"; + +contract VerifiedKYCEmail is ERC721Enumerable, Verifier { + using Counters for Counters.Counter; + + Counters.Counter private tokenCounter; + + // uint16 public constant msg_len = 21; // header + body + uint16 public constant msg_len = 20; // two rsa moduli of length 9 as well as two (identical) addresses of length 1 + uint16 public constant bytesInPackedBytes = 7; // 7 bytes in a packed item returned from circom + // uint256 public constant body_len = 3; + // uint256 public constant body_len = 0; // no body + // uint256 public constant rsa_modulus_chunks_len = 17; + uint256 public constant rsa_modulus_chunks_len = 9; // 1024 bit RSA + // uint256 public constant header_len = msg_len - body_len; + uint256 public constant addressIndexInSignals = msg_len - 1; // TODO: FIX CONSTANT + + // mapping(string => uint256[rsa_modulus_chunks_len]) public verifiedMailserverKeys; + mapping(string => uint256[rsa_modulus_chunks_len]) public verifiedMailserverKeys; + mapping(uint256 => string) public tokenIDToName; + // string constant domain = "twitter.com"; + string constant domain_airbnb = "airbnb.com"; + string constant domain_coinbase = "coinbase.com"; + + constructor() ERC721("VerifiedKYC", "VerifiedKYC") { + // Do dig TXT outgoing._domainkey.twitter.com to verify these. + // This is the base 2^121 representation of that key. + // Circom bigint: represent a = a[0] + a[1] * 2**n + .. + a[k - 1] * 2**(n * k) + require(2 * rsa_modulus_chunks_len + 2 == msg_len, "Variable counts are wrong!"); + verifiedMailserverKeys["airbnb.com"][0] = 1782267151472132502396673758441738163; + verifiedMailserverKeys["airbnb.com"][1] = 211482981992850046267405122085516466; + verifiedMailserverKeys["airbnb.com"][2] = 454331740279802979553218083106524093; + verifiedMailserverKeys["airbnb.com"][3] = 2403631535172814929511297080499227501; + verifiedMailserverKeys["airbnb.com"][4] = 2245858962887391502631714271235221261; + verifiedMailserverKeys["airbnb.com"][5] = 2622546081161044621195511843069142201; + verifiedMailserverKeys["airbnb.com"][6] = 1247628895302131918172499597775434966; + verifiedMailserverKeys["airbnb.com"][7] = 1584816411261150842617500336767389232; + verifiedMailserverKeys["airbnb.com"][8] = 52914273202064513; + + verifiedMailserverKeys["coinbase.com"][0] = 1345060269316532707410324038691477859; + verifiedMailserverKeys["coinbase.com"][1] = 384766469338727068594017962971556116; + verifiedMailserverKeys["coinbase.com"][2] = 168911276988157118943281324996362385; + verifiedMailserverKeys["coinbase.com"][3] = 1165220578700378509253846448878043993; + verifiedMailserverKeys["coinbase.com"][4] = 1468253564629208485538769233538980768; + verifiedMailserverKeys["coinbase.com"][5] = 2375057771089481827666297753868306658; + verifiedMailserverKeys["coinbase.com"][6] = 1859460967236870128489365675225233949; + verifiedMailserverKeys["coinbase.com"][7] = 2514159567794221963503259554592798082; + verifiedMailserverKeys["coinbase.com"][8] = 37369779987712517; + } + + // change to some KYC description + function tokenDesc(uint256 tokenId) public view returns (string memory) { + // string memory twitter_username = tokenIDToName[tokenId]; + address address_owner = ownerOf(tokenId); + string memory result = string(abi.encodePacked(HexStrings.toString(address_owner), "has completed KYC")); + return result; + } + + // modify later lol + function tokenURI(uint256 tokenId) public view override returns (string memory) { + string memory username = tokenIDToName[tokenId]; + address owner = ownerOf(tokenId); + NFTSVG.SVGParams memory svgParams = NFTSVG.SVGParams({ + username: username, + tokenId: tokenId, + color0: NFTSVG.tokenToColorHex(uint256(uint160(owner)), 136), + color1: NFTSVG.tokenToColorHex(uint256(uint160(owner)), 136), + color2: NFTSVG.tokenToColorHex(uint256(uint160(owner)), 0), + color3: NFTSVG.tokenToColorHex(uint256(uint160(owner)), 0), + x1: NFTSVG.scale(NFTSVG.getCircleCoord(uint256(uint160(owner)), 16, tokenId), 0, 255, 16, 274), + y1: NFTSVG.scale(NFTSVG.getCircleCoord(uint256(uint160(owner)), 16, tokenId), 0, 255, 100, 484), + x2: NFTSVG.scale(NFTSVG.getCircleCoord(uint256(uint160(owner)), 32, tokenId), 0, 255, 16, 274), + y2: NFTSVG.scale(NFTSVG.getCircleCoord(uint256(uint160(owner)), 32, tokenId), 0, 255, 100, 484), + x3: NFTSVG.scale(NFTSVG.getCircleCoord(uint256(uint160(owner)), 48, tokenId), 0, 255, 16, 274), + y3: NFTSVG.scale(NFTSVG.getCircleCoord(uint256(uint160(owner)), 48, tokenId), 0, 255, 100, 484) + }); + string memory svgOutput = NFTSVG.generateSVG(svgParams); + + string memory json = Base64.encode( + bytes( + string( + abi.encodePacked( + '{"attributes":[ ', + '{"trait_type": "Name",', + '"value": "', + tokenIDToName[tokenId], + '"}, {"trait_type": "Owner",', + '"value": "', + HexStrings.toHexString(uint256(uint160(ownerOf(tokenId))), 42), + '"}], "description": "ZK VerifiedEmails are ZK verified proofs of email recieving on Ethereum. They only reveal parts of the email headers and body body, and are verified via mailserver signature verification: there are no special party attesters. We are working to ship more verifiable proofs of signed data including zk blind, and avoid terrible tragedy of the commons scenarios where instituition reputation is slowly spent by its members. VerifiedEmail uses ZK SNARKs to insinuate this social dynamic, with a first demo at zkemail.xyz.", "image": "data:image/svg+xml;base64,', + Base64.encode(bytes(svgOutput)), + '"}' + ) + ) + ) + ); + string memory output = string(abi.encodePacked("data:application/json;base64,", json)); + return output; + } + + // Unpacks uint256s into bytes and then extracts the non-zero characters + // Only extracts contiguous non-zero characters and ensures theres only 1 such state + // Note that unpackedLen may be more than packedBytes.length * 8 since there may be 0s + // TODO: Remove console.logs and define this as a pure function instead of a view + function convertPackedBytesToBytes(uint256[] memory packedBytes, uint256 maxBytes) public pure returns (string memory extractedString) { + uint8 state = 0; + // bytes: 0 0 0 0 y u s h _ g 0 0 0 + // state: 0 0 0 0 1 1 1 1 1 1 2 2 2 + bytes memory nonzeroBytesArray = new bytes(packedBytes.length * 7); + uint256 nonzeroBytesArrayIndex = 0; + for (uint16 i = 0; i < packedBytes.length; i++) { + uint256 packedByte = packedBytes[i]; + uint8[] memory unpackedBytes = new uint8[](bytesInPackedBytes); + for (uint j = 0; j < bytesInPackedBytes; j++) { + unpackedBytes[j] = uint8(packedByte >> (j * 8)); + } + for (uint256 j = 0; j < bytesInPackedBytes; j++) { + uint256 unpackedByte = unpackedBytes[j]; //unpackedBytes[j]; + // console.log(i, j, state, unpackedByte); + if (unpackedByte != 0) { + nonzeroBytesArray[nonzeroBytesArrayIndex] = bytes1(uint8(unpackedByte)); + nonzeroBytesArrayIndex++; + if (state % 2 == 0) { + state += 1; + } + } else { + if (state % 2 == 1) { + state += 1; + } + } + packedByte = packedByte >> 8; + } + } + string memory returnValue = string(nonzeroBytesArray); + require(state == 2, "Invalid final state of packed bytes in email"); + // console.log("Characters in username: ", nonzeroBytesArrayIndex); + require(nonzeroBytesArrayIndex <= maxBytes, "Twitter username more than 15 chars!"); + return returnValue; + // Have to end at the end of the email -- state cannot be 1 since there should be an email footer + } + + function _stringEq(string memory a, string memory b) public pure returns (bool) { + return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b)); + } + + // Need to change but doesn't seem to be called? + // TODO: Remove console.logs and define this as a pure function instead of a view + function _domainCheck(uint256[] memory headerSignals) public pure returns (bool) { + string memory senderBytes = convertPackedBytesToBytes(headerSignals, 18); + string[2] memory domainStrings = ["verify@twitter.com", "info@twitter.com"]; + return _stringEq(senderBytes, domainStrings[0]) || _stringEq(senderBytes, domainStrings[1]); + // Usage: require(_domainCheck(senderBytes, domainStrings), "Invalid domain"); + } + + function mint(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[msg_len] memory signals) public { + // Checks: Verify proof and check signals + // require(signals[0] == 1337, "invalid signals"); // TODO no invalid signal check yet, which is fine since the zk proof does it + + // 3 public signals are the masked packed message bytes, 17 are the modulus. + // uint256[] memory bodySignals = new uint256[](body_len); + // uint256[] memory rsaModulusSignals = new uint256[](msg_len); + // for (uint256 i = 0; i < body_len; i++) bodySignals[i] = signals[i]; + // for (uint256 i = 0; i < 2 * rsa_modulus_chunks_len; i++) rsaModulusSignals[i] = signals[i]; + + // Check eth address committed to in proof matches msg.sender, to avoid replayability + require(address(uint160(signals[addressIndexInSignals])) == msg.sender, "Invalid address"); + + // Check from/to email domains are correct [in this case, only from domain is checked] + // Right now, we just check that any email was received from anyone at Twitter, which is good enough for now + // We will upload the version with these domain checks soon! + // require(_domainCheck(headerSignals), "Invalid domain"); + + // Verify that the two addresses match (although it should be impossible that they are not equal) + require(signals[addressIndexInSignals - 1] == signals[addressIndexInSignals], "Invalid: addresses don't match"); + + // Verify that the public key for RSA matches the hardcoded one + for (uint i = 0; i < rsa_modulus_chunks_len; i++) { + require(signals[i] == verifiedMailserverKeys[domain_airbnb][i], string(abi.encodePacked("Invalid: RSA modulus not matched for", domain_airbnb))); + require(signals[i + rsa_modulus_chunks_len] == verifiedMailserverKeys[domain_coinbase][i], string(abi.encodePacked("Invalid: RSA modulus not matched for", domain_coinbase))); + } + require(verifyProof(a, b, c, signals), "Invalid Proof"); // checks effects iteractions, this should come first + + // Effects: Mint token + uint256 tokenId = tokenCounter.current() + 1; + // string memory messageBytes = convertPackedBytesToBytes(bodySignals, bytesInPackedBytes * body_len); + // tokenIDToName[tokenId] = messageBytes; + _mint(msg.sender, tokenId); + tokenCounter.increment(); + } + + function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal { + require(from == address(0), "Cannot transfer - VerifiedKYCEmail is soulbound"); + } +} \ No newline at end of file diff --git a/src/helpers/constants.ts b/src/helpers/constants.ts index 1d0f4a0d5..7103a3d0e 100644 --- a/src/helpers/constants.ts +++ b/src/helpers/constants.ts @@ -15,7 +15,8 @@ export const MAX_BODY_PADDED_BYTES = 1536; // NOTE: this must be the same as the // component main { public [ modulus ] } = RSAVerify(121, 17); // component main { public [ root, payload1 ] } = RSAGroupSigVerify(121, 17, 30); export const CIRCOM_BIGINT_N = 121; -export const CIRCOM_BIGINT_K = 17; +// export const CIRCOM_BIGINT_K = 17; +export const CIRCOM_BIGINT_K = 9; export const CIRCOM_LEVELS = 30; // This is the string that comes right before the target string in the email. Ideally as close to the end of the email as possible. diff --git a/src/scripts/generate_two_inputs.ts b/src/scripts/generate_two_inputs.ts index a21fa015c..c8eca7612 100644 --- a/src/scripts/generate_two_inputs.ts +++ b/src/scripts/generate_two_inputs.ts @@ -13,7 +13,7 @@ import { mergeUInt8Arrays, int64toBytes, } from "../helpers/binaryFormat"; - import { CIRCOM_FIELD_MODULUS, MAX_HEADER_PADDED_BYTES, MAX_BODY_PADDED_BYTES, STRING_PRESELECTOR, STRING_PRESELECTOR_AIRBNB, STRING_PRESELECTOR_COINBASE, CIRCUIT_INPUTS } from "../../src/helpers/constants"; + import { CIRCOM_FIELD_MODULUS, MAX_HEADER_PADDED_BYTES, MAX_BODY_PADDED_BYTES, STRING_PRESELECTOR, STRING_PRESELECTOR_AIRBNB, STRING_PRESELECTOR_COINBASE } from "../../src/helpers/constants"; import { shaHash, partialSha, sha256Pad } from "../../src/helpers/shaHash"; import { dkimVerify } from "../../src/helpers/dkim"; import * as fs from "fs"; @@ -275,10 +275,10 @@ import { fs.writeFileSync(`./circuits/inputs/input_airbnb.json`, JSON.stringify(circuitInputs_airbnb), { flag: "w"}); fs.writeFileSync(`./circuits/inputs/input_coinbase.json`, JSON.stringify(circuitInputs_coinbase), { flag: "w"}); - let input_kyc = {}; - for (const key of Object.keys(circuitInputs_airbnb)) { - input_kyc[key.concat("_airbnb")] = circuitInputs_airbnb[key]; - input_kyc[key.concat("_coinbase")] = circuitInputs_coinbase[key]; + let input_kyc: {[key:string]: any} = {}; + for (const key in circuitInputs_airbnb) { + input_kyc[key.concat("_airbnb")] = circuitInputs_airbnb[key as keyof ICircuitInputs]; + input_kyc[key.concat("_coinbase")] = circuitInputs_coinbase[key as keyof ICircuitInputs]; } fs.writeFileSync(`./circuits/inputs/input_kyc.json`, JSON.stringify(input_kyc), { flag: "w"}); } diff --git a/yarn.lock b/yarn.lock index 2e7ca6b6c..a99eb6884 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2280,13 +2280,13 @@ __metadata: languageName: node linkType: hard -"@iden3/binfileutils@npm:0.0.10": - version: 0.0.10 - resolution: "@iden3/binfileutils@npm:0.0.10" +"@iden3/binfileutils@npm:0.0.11": + version: 0.0.11 + resolution: "@iden3/binfileutils@npm:0.0.11" dependencies: - fastfile: 0.0.19 + fastfile: 0.0.20 ffjavascript: ^0.2.48 - checksum: cdeb8ac01e12f485d9fb236654c00d5d5016fc89eae24f7822885dd42f09935cbef601dbdd8a0c96dfb00ded9f4f623e0eec0b568aa86d16522cf77ce6f9498b + checksum: ca61db1325c7e038c6bd723c856eff5f2c82c76394db09d3350ef4f5b7525e3c9ab1f7429900ff5d3e9d26c5970bf5900e6126ccb5c5caa597c16a47336a6be8 languageName: node linkType: hard @@ -5697,13 +5697,6 @@ __metadata: languageName: node linkType: hard -"big-integer@npm:^1.6.42, big-integer@npm:^1.6.48": - version: 1.6.51 - resolution: "big-integer@npm:1.6.51" - checksum: 3d444173d1b2e20747e2c175568bedeebd8315b0637ea95d75fd27830d3b8e8ba36c6af40374f36bdaea7b5de376dcada1b07587cb2a79a928fccdb6e6e3c518 - languageName: node - linkType: hard - "big.js@npm:^5.2.2": version: 5.2.2 resolution: "big.js@npm:5.2.2" @@ -6690,14 +6683,14 @@ __metadata: languageName: node linkType: hard -"circom_runtime@npm:0.1.17": - version: 0.1.17 - resolution: "circom_runtime@npm:0.1.17" +"circom_runtime@npm:0.1.21": + version: 0.1.21 + resolution: "circom_runtime@npm:0.1.21" dependencies: - ffjavascript: 0.2.48 + ffjavascript: 0.2.56 bin: calcwit: calcwit.js - checksum: 595fc0cc3a62ba5daf8d849feae41c48805c0df43965f85dde4dc434efb607e455fa7801d41c1feacfe0c3c71952a45cd3985abf26fde40c54138392891afd8c + checksum: 3071f1e0fba9a5fb41c940454edb911ce09edfd5d0bd12156ec79045a0bf3ff2cc5b35f46e84e42902ef8bb0a4166f428b75d0ceb363c0d485f1a111b27daba1 languageName: node linkType: hard @@ -8362,7 +8355,7 @@ __metadata: readline: ^1.3.0 selenium-webdriver: ^4.8.1 serve: ^14.0.1 - snarkjs: "git+https://github.com/vb7401/snarkjs.git#24981febe8826b6ab76ae4d76cf7f9142919d2b8" + snarkjs: "https://github.com/sampritipanda/snarkjs.git#fef81fc51d17a734637555c6edbd585ecda02d9e" sshpk: ^1.17.0 styled-components: ^5.3.5 ts-node: ^10.9.1 @@ -9775,10 +9768,10 @@ __metadata: languageName: node linkType: hard -"fastfile@npm:0.0.19, fastfile@npm:^0.0.19": - version: 0.0.19 - resolution: "fastfile@npm:0.0.19" - checksum: 6179bdd7c21be9882294dae66103795c099594098b51958bcf08a4545c91387321b43511730d0542a5a9ed8c5ec9069c065e065fd67255453ac900a23895dac1 +"fastfile@npm:0.0.20": + version: 0.0.20 + resolution: "fastfile@npm:0.0.20" + checksum: e5d6e5f57a9b58c9534202e477cbffbca2182c407171950695ddb5c3e6b89554bc8561fbb6e370c99e371a8f23486a23fbaca527827886cec4897d481cbd03b6 languageName: node linkType: hard @@ -9856,15 +9849,14 @@ __metadata: languageName: node linkType: hard -"ffjavascript@npm:0.2.48": - version: 0.2.48 - resolution: "ffjavascript@npm:0.2.48" +"ffjavascript@npm:0.2.56": + version: 0.2.56 + resolution: "ffjavascript@npm:0.2.56" dependencies: - big-integer: ^1.6.48 - wasmbuilder: ^0.0.12 - wasmcurves: 0.1.0 + wasmbuilder: 0.0.16 + wasmcurves: 0.2.0 web-worker: ^1.2.0 - checksum: 68beae9a4f642c06656685353b84fd7655020ca0e628ea046e94452ab779587953cc45cde106d74b68be7177b49c8f19b105d6552c4a1d715e784ae9e7c9ed34 + checksum: d4e02263db4a94d111cdc7c1211ae96769370f5c8c3c338331e0ef99faed7b55e640bedf23fa8a83fc9a77f0e81140ea8f32e392812a00e15ca504221b879a4f languageName: node linkType: hard @@ -16625,15 +16617,15 @@ __metadata: languageName: node linkType: hard -"r1csfile@npm:0.0.35": - version: 0.0.35 - resolution: "r1csfile@npm:0.0.35" +"r1csfile@npm:0.0.41": + version: 0.0.41 + resolution: "r1csfile@npm:0.0.41" dependencies: "@iden3/bigarray": 0.0.2 - "@iden3/binfileutils": 0.0.10 - fastfile: 0.0.19 - ffjavascript: 0.2.48 - checksum: 84f7b4eab5bcdd6a3f6d699998c9479a5eff8d670383d4f0c5afc08431f45353abab9a8b07eeabaef89807e24b0ba50611d4d6280eb6c3a7483e1487a91f0ac6 + "@iden3/binfileutils": 0.0.11 + fastfile: 0.0.20 + ffjavascript: 0.2.56 + checksum: eec689416f66f09db2d6ca66fac1ef6841b088ab29abcde487145ebd2110916c92583e11ac86f0cdcc4e8a3a7c7df9ff5352ad959e8ae385d37c3b51cec5cf4d languageName: node linkType: hard @@ -18373,23 +18365,24 @@ __metadata: languageName: node linkType: hard -"snarkjs@git+https://github.com/vb7401/snarkjs.git#24981febe8826b6ab76ae4d76cf7f9142919d2b8": - version: 0.4.12 - resolution: "snarkjs@https://github.com/vb7401/snarkjs.git#commit=24981febe8826b6ab76ae4d76cf7f9142919d2b8" +"snarkjs@https://github.com/sampritipanda/snarkjs.git#fef81fc51d17a734637555c6edbd585ecda02d9e": + version: 0.5.0 + resolution: "snarkjs@https://github.com/sampritipanda/snarkjs.git#commit=fef81fc51d17a734637555c6edbd585ecda02d9e" dependencies: - "@iden3/binfileutils": 0.0.10 + "@iden3/binfileutils": 0.0.11 + bfj: ^7.0.2 blake2b-wasm: ^2.4.0 - circom_runtime: 0.1.17 + circom_runtime: 0.1.21 ejs: ^3.1.6 - fastfile: ^0.0.19 - ffjavascript: 0.2.48 + fastfile: 0.0.20 + ffjavascript: 0.2.56 js-sha3: ^0.8.0 + localforage: ^1.10.0 logplease: ^1.2.15 - r1csfile: 0.0.35 - readline: ^1.3.0 + r1csfile: 0.0.41 bin: snarkjs: build/cli.cjs - checksum: 9011df4b58475a0b4ae988f8b459a9a4d2bb5d2b60221d0ec370a10f2492c88909768215f3b22e514b2cf24dca79818790447005a33ed6aee177b9fda6948a75 + checksum: f2050f0135d50d459ea0edddf3e394e833a2d28c6648e5889b2f896814865e5c60606e978a8a106bd5bfe7e27501c315f249db5b71895d5e7e6e9a87bfcd55ab languageName: node linkType: hard @@ -20542,25 +20535,6 @@ __metadata: languageName: node linkType: hard -"wasmbuilder@npm:^0.0.12": - version: 0.0.12 - resolution: "wasmbuilder@npm:0.0.12" - dependencies: - big-integer: ^1.6.48 - checksum: 327b3c50b0e1e5e3aac9e218e0f96fdc638b7952ab86acc2ad53960371996826dbb0a8095edce482cf1d9c245d96884449701909bc962920aa7ec8241db01214 - languageName: node - linkType: hard - -"wasmcurves@npm:0.1.0": - version: 0.1.0 - resolution: "wasmcurves@npm:0.1.0" - dependencies: - big-integer: ^1.6.42 - blakejs: ^1.1.0 - checksum: 6bf6719e659a88904af0b98d152316e3b22435ca6a2cfc8bbf4530576806f17b2776b2c7d91d1a678fe0d51485a0d1748efcd080808c181c7977bee50b26efa9 - languageName: node - linkType: hard - "wasmcurves@npm:0.2.0": version: 0.2.0 resolution: "wasmcurves@npm:0.2.0" From 87e236bbca6636c689c42ed89f3257024399d194 Mon Sep 17 00:00:00 2001 From: Anka Hu Date: Fri, 31 Mar 2023 15:05:45 +0700 Subject: [PATCH 04/25] typo fix --- src/components/NumberedStep.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/NumberedStep.tsx b/src/components/NumberedStep.tsx index 443d43a40..3d3b1a76b 100644 --- a/src/components/NumberedStep.tsx +++ b/src/components/NumberedStep.tsx @@ -17,7 +17,7 @@ const Step = ({ index, title, description, active, onClick }: any) => ( ); -export const NNS: React.FC<{ +export const NumberedStep: React.FC<{ steps: Step[]; }> = ({ steps }) => { const [activeStep, setActiveStep] = useState(0); From e20e3bde602764dd97c27f580b1792539ec48c7c Mon Sep 17 00:00:00 2001 From: Nathan Xiong Date: Tue, 11 Apr 2023 17:48:27 -0400 Subject: [PATCH 05/25] pulling upstream --- README.md | 107 +- circuits/email_airbnb.circom | 44 +- circuits/email_both.circom | 31 +- circuits/email_coinbase.circom | 46 +- circuits/{ => helpers}/base64.circom | 4 +- circuits/{ => helpers}/bigint.circom | 18 +- circuits/{ => helpers}/bigint_func.circom | 2 +- circuits/helpers/extract.circom | 121 ++ circuits/{ => helpers}/fp.circom | 8 +- circuits/helpers/reveal_deprecated.circom | 6 + circuits/{ => helpers}/rsa.circom | 4 +- circuits/{ => helpers}/sha.circom | 4 +- circuits/{ => helpers}/sha256general.circom | 8 +- circuits/{ => helpers}/sha256partial.circom | 8 +- circuits/{ => helpers}/utils.circom | 11 +- circuits/{ => regexes}/body_hash_regex.circom | 2 +- circuits/regexes/from_regex.circom | 456 +++++++ circuits/{ => regexes}/regex_helpers.circom | 6 +- circuits/regexes/subject_regex.circom | 1088 +++++++++++++++++ circuits/regexes/to_regex.circom | 442 +++++++ .../tofrom_domain_regex.circom} | 10 +- .../{ => regexes}/twitter_reset_regex.circom | 2 +- circuits/twitter.circom | 179 +++ circuits/wallet.circom | 207 ++++ dizkus-scripts/1_compile.sh | 19 +- dizkus-scripts/2_gen_wtns.sh | 12 +- dizkus-scripts/3_gen_both_zkeys.sh | 22 +- dizkus-scripts/3_gen_chunk_zkey.sh | 48 +- dizkus-scripts/4_gen_vkey.sh | 11 +- dizkus-scripts/5_gen_proof.sh | 17 +- dizkus-scripts/6_gen_proof_rapidsnark.sh | 19 +- dizkus-scripts/upload_to_s3.py | 37 +- docs/README.md | 2 +- node_modules_old/.yarn-state.yml | 10 + node_modules_old2/.yarn-state.yml | 10 + node_modules_old3/.yarn-state.yml | 10 + package.json | 3 +- regex_to_circom/gen.py | 17 +- regex_to_circom/halo2_regex_lookup.txt | 424 ++++++- regex_to_circom/lexical.js | 96 +- regex_to_circom/regex_to_dfa.js | 272 +++++ src/contracts/README.md | 15 +- src/contracts/src/Create2.sol | 58 + ...erifier.sol => Groth16VerifierTwitter.sol} | 0 src/contracts/src/Groth16VerifierWallet.sol | 423 +++++++ src/contracts/src/MailServer.sol | 98 ++ src/contracts/src/NFTSVG.sol | 2 +- src/contracts/src/WalletEmailHandler.sol | 180 +++ src/contracts/src/domainEmailHandler.sol | 199 --- src/contracts/src/test/TestTwitter.t.sol | 24 +- src/contracts/src/test/TestWallet.t.sol | 203 +++ src/contracts/src/twitterEmailHandler.sol | 8 +- src/contracts/src/wallet-create2/Wallet.sol | 10 + .../src/wallet-create2/WalletDeployer.sol | 49 + src/helpers/constants.ts | 1 + src/helpers/shaHash.ts | 5 +- src/scripts/generate_input.ts | 91 +- temp.py | 7 +- yarn.lock | 16 + yarn.lock_old | 11 + yarn.lock_old2 | 11 + yarn.lock_old3 | 11 + 62 files changed, 4725 insertions(+), 540 deletions(-) rename circuits/{ => helpers}/base64.circom (96%) rename circuits/{ => helpers}/bigint.circom (97%) rename circuits/{ => helpers}/bigint_func.circom (99%) create mode 100644 circuits/helpers/extract.circom rename circuits/{ => helpers}/fp.circom (95%) create mode 100644 circuits/helpers/reveal_deprecated.circom rename circuits/{ => helpers}/rsa.circom (99%) rename circuits/{ => helpers}/sha.circom (97%) rename circuits/{ => helpers}/sha256general.circom (95%) rename circuits/{ => helpers}/sha256partial.circom (94%) rename circuits/{ => helpers}/utils.circom (95%) rename circuits/{ => regexes}/body_hash_regex.circom (99%) create mode 100644 circuits/regexes/from_regex.circom rename circuits/{ => regexes}/regex_helpers.circom (86%) create mode 100644 circuits/regexes/subject_regex.circom create mode 100644 circuits/regexes/to_regex.circom rename circuits/{dkim_header_regex.circom => regexes/tofrom_domain_regex.circom} (99%) rename circuits/{ => regexes}/twitter_reset_regex.circom (99%) create mode 100644 circuits/twitter.circom create mode 100644 circuits/wallet.circom create mode 100644 node_modules_old/.yarn-state.yml create mode 100644 node_modules_old2/.yarn-state.yml create mode 100644 node_modules_old3/.yarn-state.yml create mode 100644 regex_to_circom/regex_to_dfa.js create mode 100644 src/contracts/src/Create2.sol rename src/contracts/src/{emailVerifier.sol => Groth16VerifierTwitter.sol} (100%) create mode 100644 src/contracts/src/Groth16VerifierWallet.sol create mode 100644 src/contracts/src/MailServer.sol create mode 100644 src/contracts/src/WalletEmailHandler.sol delete mode 100644 src/contracts/src/domainEmailHandler.sol create mode 100644 src/contracts/src/test/TestWallet.t.sol create mode 100644 src/contracts/src/wallet-create2/Wallet.sol create mode 100644 src/contracts/src/wallet-create2/WalletDeployer.sol create mode 100644 yarn.lock_old create mode 100644 yarn.lock_old2 create mode 100644 yarn.lock_old3 diff --git a/README.md b/README.md index 8f4b7768c..cbc113072 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 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!** +**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 guaranteed. If you have a possible usecase, please run it by us so we can ensure that your trust assumptions are in fact correct!** Join the conversation via [dm'ing us](https://twitter.com/yush_g/)! We will have a broader Discord soon. @@ -72,7 +72,9 @@ public/ # Should contain vkey/wasm, but we end up fetching those from AWS server ### Regex to Circom -Modify the `let regex = ` in lexical.js and then run `python3 gen.py` +First, generate a regex. Go to our [min_dfa fork](https://mindfa.onrender.com/min_dfa) of cyberzhg's toolbox and insert your regex on the top line. We've forked [min-dfa into a UI here](https://mindfa.onrender.com/min_dfa) to create a UI that converts existing regexes with [] support, as well as escapes \_, and the character classes a-z, A-Z, and 0-9. It also shows the DFA states very clearly so you can choose accept states easily. This should make converting regexes into DFA form way cleaner. + +Modify either `let raw_regex = ` (that supports actual regex strings like `[A-Za-z0-9]` [but no other character ranges]) or modify `let regex = ` (that does not support brackets or character ranges and supports only the limited syntax in https://cyberzhg.github.io/toolbox/min_dfa) in regex_to_circom/regex_to_dfa.js and then run `python3 gen.py`. ### Email Circuit Build Steps @@ -83,17 +85,23 @@ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh # Install rust if source "$HOME/.cargo/env" # Also rust installation step git clone https://github.com/iden3/circom.git +sudo apt update +sudo apt-get install nlohmann-json3-dev libgmp-dev nasm # Ubuntu packages needed for C-based witness generator +sudo apt install build-essential # Ubuntu +brew install nlohmann-json gmp nasm # OSX cd circom cargo build --release cargo install --path circom -sudo apt-get install nlohmann-json3-dev libgmp-dev nasm # Ubuntu packages needed for C-based witness generator -brew install nlohmann-json gmp nasm # OSX ``` Inside `zk-email-verify` folder, do ``` -sudo npm i -g yarn # If don't have yarn +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash # If don't have npm +. ~/.nvm/nvm.sh # If don't have npm +nvm install 16 # If don't have node 16 +nvm use 16 # If not using node 16 +sudo npm i -g yarn # If don't have yarn (may need to remove sudo) yarn install # If this fails, delete yarn.lock and try again ``` @@ -101,11 +109,11 @@ To get the ptau, do (note that you only need the 22 file right now) ```bash wget https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_22.ptau -mv powersOfTau28_hez_final_22.ptau powersoftau/powersOfTau28_hez_final_22.ptau +mv powersOfTau28_hez_final_22.ptau circuits/powersOfTau28_hez_final_22.ptau wget https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_21.ptau # shasum pot21_final.ptau: e0ef07ede5c01b1f7ddabb14b60c0b740b357f70 -mv powersOfTau28_hez_final_21.ptau powersoftau/powersOfTau28_hez_final_21.ptau +mv powersOfTau28_hez_final_21.ptau circuits/powersOfTau28_hez_final_21.ptau ``` @@ -116,13 +124,62 @@ Put the email into ...\*.eml. Edit the constant filename at the top of generate_ To create a chunked zkey for in-browser proving, run the following (likely on a high CPU computer): ```bash -yarn add snarkjs@git+https://github.com/vb7401/snarkjs.git#24981febe8826b6ab76ae4d76cf7f9142919d2b8 # Swap to chunked generation version +yarn add snarkjs@git+https://github.com/vb7401/snarkjs.git#24981febe8826b6ab76ae4d76cf7f9142919d2b8 # Swap to chunked generation version for browser, leave this line out for serverside proofs onluy cd dizkus-scripts/ +cp entropy.env.example entropy.env +``` + +Not put random characters into the values for entropy1 and entropy2, and hexadecimal characters into the beacon. These scripts will compile and test your zkey for you. + +``` ./1_compile.sh && ./2_gen_wtns.sh && ./3_gen_chunk_zkey.sh && ./4_gen_vkey.sh && ./5_gen_proof.sh -# optional: ./6_gen_proof_rapidsnark.sh +``` + +If you want to run a fast server side prover, install rapidsnark and test proofgen: + +``` +cd ../../ +git clone https://github.com/iden3/rapidsnark +cd rapidsnark +npm install +git submodule init +git submodule update +npx task createFieldSources +``` + +You're supposed to run `npx task buildPistache` next, but that errored, so I had to manually build the pistache lib first: + +``` +cd depends/pistache +sudo apt-get install meson ninja-build +meson setup build --buildtype=release +ninja -C build +sudo ninja -C build install +sudo ldconfig +cd ../.. +``` + +Then, from rapidsnark/ I could run + +``` +npx task buildProverServer +``` + +And from zk-email-verify, convert your proof params to a rapidsnark friendly version: + +``` + +cd ../zk-email-verify/dizkus-scripts +./6_gen_proof_rapidsnark.sh + +``` + +To upload zkeys to an s3 box on AWS, change bucket_name in upload_to_s3.py and run: + +``` +sudo apt install awscli # Ubuntu +brew install awscli # Mac -# This part is to upload the zkeys, not critical -# Remember to change bucket_name in upload_to_s3.py aws configure # Only needs to be run once pip3 install boto3 python3 upload_to_s3.py @@ -134,7 +191,9 @@ Note that there's no .zkeya file, only .zkeyb ... .zkeyk. The script will automa We use a fork of [zkp.ts](https://github.com/personaelabs/heyanon/blob/main/lib/zkp.ts) to load these keys into localforage. In the browser, to read off of localforage, you have to use this fork when running the frontend locally/in prod: ``` + yarn install snarkjs@git+https://github.com/vb7401/snarkjs.git#53e86631b5e409e5bd30300611b495ca469503bc + ``` Manually copy paste the modulus in the resulting generated file into solidity verified mailserver keys. @@ -144,7 +203,9 @@ Change s3 address in the frontend to your bucket. To do a non-chunked zkey for non-browser running, ``` + yarn compile-all + ``` ### Compiling Subcircuits @@ -159,7 +220,7 @@ npm install typescript ts-node -g # uncomment do_generate function call at end of file # go to tsconfig.json and change esnext to CommonJS # if weird things dont work with this and yarn start, go go node_modules/react-scripts/config/webpack.config.ts and add/cut `target: 'node',` after like 793 after `node:`. -npx tsc --moduleResolution node --target esnext circuits/scripts/generate_input.ts +npx tsc --moduleResolution node --target esnext src/scripts/generate_input.ts ``` which will autowrite input\_.json to the inputs folder. @@ -274,7 +335,7 @@ The full email header and body check circuit, with 7-byte packing and final publ In the browser, on a 2019 Intel Mac on Chrome, proving uses 7.3/8 cores. zk-gen takes 384 s, groth16 prove takes 375 s, and witness calculation takes 9 s. -For baremetal, proof generation time on 16 CPUs took 97 seconds. Generating zkey 0 took 17 minutes. Unclear about zkey 1. Zkey 2 took 5 minutes. r1cs + wasm generation took 5 minutes. Witness generation took 16 seconds. cpp witness gen file generation (from script 6) took 210 minutes. +For baremetal, proof generation time on 16 CPUs took 97 seconds. Generating zkey 0 took 17 minutes. zkey 1 and zkey 2 each took 5 minutes. r1cs + wasm generation took 5 minutes. Witness generation took 16 seconds. cpp generation of witness gen file (from script 6) took 210 minutes -- we do not run this pathway anymore. ### Scrubbing Sensitive Files @@ -289,7 +350,7 @@ git push --set-upstream origin main --force ## Regexes we compiled -The regex to get out the from/to emails is: +Test these on cyberzhg's toolbox modified at [zkregex.com/min_dfa](https://zkregex.com/min_dfa). The regex to get out the from/to emails is: ``` // '(\r\n|\x80)(to|from):([A-Za-z0-9 _."@-]+<)?[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.]+>?\r\n'; @@ -385,10 +446,24 @@ Since circom is GPL, we are forced to use the GPL license, which is still a high ## To-Do -- Make the frontend Solidity calls work - Make a general method to get formatted signatures and bodies from all email clients - Make versions for different size RSA keys - Add ENS DNSSEC code (possibly SNARKed), so anyone can add a website's RSA key via DNS record -- Design the NFT/POAP to have the user's domain/verified identity on it +- Design the NFT/POAP to have the user's domain/verified identity on it and display SVG properly on opensea - Make a testnet faucet as a PoC for Sybil resistance and to get developers interested - Dynamically tradeoff between gzip (2x faster decompression) and xz (30% smaller file size): https://www.rootusers.com/gzip-vs-bzip2-vs-xz-performance-comparison/ based on internet speed (i.e. minimize download time + unzip time) +- Fix these circom bugs from `circom email.circom --inspect`: + - warning[CA02]: In template "Base64Decode(32)": Subcomponent input/output signal bits_out[10][2].out does not appear in any constraint of the father component + - warning[CA01]: In template "TwitterResetRegex(1536)": Local signal states[1536][0] does not appear in any constraint + - warning[CA02]: In template "EmailVerify(1024,1536,121,17,7)": Subcomponent input/output signal dkim_header_regex.reveal[0] does not appear in any constraint of the father component + - warning[CA02]: In template "RSAVerify65537(121,17)": Array of subcomponent input/output signals signatureRangeCheck[13].out contains a total of 121 signals that do not appear in any constraint of the father component + = For example: signatureRangeCheck[13].out[0], signatureRangeCheck[13].out[100]. + - warning[CA02]: In template "LessThan(8)": Array of subcomponent input/output signals n2b.out contains a total of 8 signals that do not appear in any constraint of the father component + = For example: n2b.out[0], n2b.out[1]. + - warning[CA01]: In template "DKIMHeaderRegex(1024)": Local signal states[1025][0] does not appear in any constraint + - warning[CA01]: In template "Bytes2Packed(7)": Array of local signals in_prefix_sum contains a total of 8 signals that do not appear in any constraint + = For example: in_prefix_sum[0], in_prefix_sum[1]. + - warning[CA01]: In template "Bytes2Packed(7)": Array of local signals pow2 contains a total of 8 signals that do not appear in any constraint + = For example: pow2[0], pow2[1]. +- Enable parsing of emails via tagged-dfa/lookahead/lookbehinds in all cases where 1) from:email [rare, only gcal] and 2) from: and 3) from:text +- Fix it so only a recent email after deploy cutoff can be used to send money diff --git a/circuits/email_airbnb.circom b/circuits/email_airbnb.circom index 917177478..7997143a2 100644 --- a/circuits/email_airbnb.circom +++ b/circuits/email_airbnb.circom @@ -26,9 +26,7 @@ template AirbnbEmailVerify(max_header_bytes, n, k) { signal input signature[k]; signal input in_len_padded_bytes; // length of in email data including the padding, which will inform the sha256 block length - // Next 2 signals are for decreasing SHA constraints for parsing out information from the in-body text - signal reveal[max_header_bytes]; // bytes to reveal - signal output reveal_packed[max_packed_bytes]; // packed into 7-bytes. TODO: make this rotate to take up even less space + // signal reveal[max_header_bytes]; // bytes to reveal signal output to_email[max_header_bytes]; // to email address of email @@ -38,9 +36,14 @@ template AirbnbEmailVerify(max_header_bytes, n, k) { signal input email_to_idx; var LEN_SHA_B64 = 44; // ceil(32/3) * 4, should be automatically calculated. + var max_bh_packed_bytes = 7; // ceil(44/7) signal input body_hash_idx; signal body_hash[LEN_SHA_B64][max_header_bytes]; + signal output body_hash_reveal[LEN_SHA_B64]; + + // signal output reveal_packed[max_bh_packed_bytes]; // packed into 7-bytes. TODO: make this rotate to take up even less space + // SHA HEADER: 506,670 constraints // This calculates the SHA256 hash of the header, which is the "base_msg" that is RSA signed. // The header signs the fields in the "h=Date:From:To:Subject:MIME-Version:Content-Type:Message-ID;" @@ -86,10 +89,10 @@ template AirbnbEmailVerify(max_header_bytes, n, k) { dkim_header_regex.msg[i] <== in_padded[i]; } dkim_header_regex.out === 2; - for (var i = 0; i < max_header_bytes; i++) { - reveal[i] <== dkim_header_regex.reveal[i+1]; - } - log(dkim_header_regex.out); + // for (var i = 0; i < max_header_bytes; i++) { + // reveal[i] <== dkim_header_regex.reveal[i+1]; + // } + // log(dkim_header_regex.out); // BODY HASH REGEX: 617,597 constraints // This extracts the body hash from the header (i.e. the part after bh= within the DKIM-signature section) @@ -99,7 +102,7 @@ template AirbnbEmailVerify(max_header_bytes, n, k) { body_hash_regex.msg[i] <== in_padded[i]; } body_hash_regex.out === 1; - log(body_hash_regex.out); + // 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(); @@ -112,6 +115,9 @@ template AirbnbEmailVerify(max_header_bytes, n, k) { body_hash[j][i] <== body_hash[j][i - 1] + body_hash_eq[i-j].out * body_hash_regex.reveal[i]; } } + for (var i = 0; i < 44; i++) { + body_hash_reveal[i] <== body_hash[i][max_header_bytes - 1]; + } // AIRBNB REGEX // Checks Airbnb regex matches KYC confirmation email @@ -123,25 +129,7 @@ template AirbnbEmailVerify(max_header_bytes, n, k) { component found_airbnb = IsZero(); found_airbnb.in <== airbnb_regex.out; found_airbnb.out === 0; - log(airbnb_regex.out); - - // 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_output[max_packed_bytes]; - for (var i = 0; i < max_packed_bytes; i++) { - packed_output[i] = Bytes2Packed(chunks); - for (var j = 0; j < chunks; j++) { - var reveal_idx = i * chunks + j; - if (reveal_idx < max_header_bytes) { - packed_output[i].in[j] <== reveal[i * chunks + j]; - } else { - packed_output[i].in[j] <== 0; - } - } - reveal_packed[i] <== packed_output[i].out; - } + // log(airbnb_regex.out); // EXTRACT TO EMAIL REGEX // This extracts the to email @@ -156,4 +144,4 @@ template AirbnbEmailVerify(max_header_bytes, n, k) { } } -// component main { public [ modulus, address ] } = AirbnbEmailVerify(1024, 121, 17); \ No newline at end of file +// component main { public [ modulus, address ] } = AirbnbEmailVerify(1024, 121, 9); \ No newline at end of file diff --git a/circuits/email_both.circom b/circuits/email_both.circom index e4d720813..234f571ad 100644 --- a/circuits/email_both.circom +++ b/circuits/email_both.circom @@ -20,6 +20,8 @@ template KYCVerify(max_header_bytes, n, k) { // max_num_bytes must be a multiple of 64 var max_packed_bytes = (max_header_bytes - 1) \ 7 + 1; // ceil(max_num_bytes / 7) + signal body_hash_concat[88]; // body hash output from each email has length 44 + // AIRBNB INPUT SIGNALS signal input in_padded_airbnb[max_header_bytes]; // prehashed email data, includes up to 512 + 64? bytes of padding pre SHA256, and padded with lots of 0s at end after the length signal input modulus_airbnb[k]; // rsa pubkey, verified with smart contract + optional oracle @@ -46,8 +48,11 @@ template KYCVerify(max_header_bytes, n, k) { signal input address_coinbase; signal input address_plus_one_coinbase; - // Outputs the packed version of the from/to emails from both emails + // OUTPUT SIGNALS + // Outputs the hash of the two body hashes + // Currently doesn't output from/to emails for domain check but should probably add that later signal reveal_packed[2 * max_packed_bytes]; + signal output nullifier_hash[256]; component airbnb_verify = AirbnbEmailVerify(max_header_bytes, n, k); component coinbase_verify = CoinbaseEmailVerify(max_header_bytes, n, k); @@ -95,13 +100,27 @@ template KYCVerify(max_header_bytes, n, k) { } // PACKED OUTPUT - // Output for solidity verifier - for (var i = 0; i < max_packed_bytes; i++) { - reveal_packed[i] <== airbnb_verify.reveal_packed[i]; + // Nullifier output for solidity verifier + for (var i = 0; i < 44; i++) { + body_hash_concat[i] <== airbnb_verify.body_hash_reveal[i]; + body_hash_concat[i + 44] <== coinbase_verify.body_hash_reveal[i]; + } + component sha = Sha256Bytes(128); + for (var i = 0; i < 88; i++) { + sha.in_padded[i] <== body_hash_concat[i]; } - for (var i = 0; i < max_packed_bytes; i++) { - reveal_packed[i + max_packed_bytes] <== coinbase_verify.reveal_packed[i]; + for (var i = 88; i < 128; i++) { + sha.in_padded[i] <== 0; + } + sha.in_len_padded_bytes <== 128; + + var chunks = 7; + component packed_output[] + for (var i = 0; i < 256; i++) { + nullifier_hash[i] <== sha.out[i]; } + // TODO: pack output into chunks + // TODO: change public signals in smart contract to match new public signals } // 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. diff --git a/circuits/email_coinbase.circom b/circuits/email_coinbase.circom index fed1478ca..1143fc8c6 100644 --- a/circuits/email_coinbase.circom +++ b/circuits/email_coinbase.circom @@ -17,7 +17,7 @@ template CoinbaseEmailVerify(max_header_bytes, n, k) { assert(max_header_bytes % 64 == 0); // assert(max_body_bytes % 64 == 0); // assert(n * k > 2048); // constraints for 2048 bit RSA - assert(n * k > 1024); // constraints for 1024 bit RSA + assert(n * k > 1024); // costraints for 1024 bit RSA assert(k < 255 \ 2); // we want a multiplication to fit into a circom signal var max_packed_bytes = (max_header_bytes - 1) \ 7 + 1; // ceil(max_num_bytes / 7) @@ -26,9 +26,7 @@ template CoinbaseEmailVerify(max_header_bytes, n, k) { signal input signature[k]; signal input in_len_padded_bytes; // length of in email data including the padding, which will inform the sha256 block length - // Next 2 signals are for decreasing SHA constraints for parsing out information from the in-body text - signal reveal[max_header_bytes]; // bytes to reveal - signal output reveal_packed[max_packed_bytes]; // packed into 7-bytes. TODO: make this rotate to take up even less space + // signal reveal[max_header_bytes]; // bytes to reveal signal output to_email[max_header_bytes]; // to email address of email @@ -38,9 +36,14 @@ template CoinbaseEmailVerify(max_header_bytes, n, k) { signal input email_to_idx; var LEN_SHA_B64 = 44; // ceil(32/3) * 4, should be automatically calculated. + var max_bh_packed_bytes = 7; // ceil(44/7) signal input body_hash_idx; signal body_hash[LEN_SHA_B64][max_header_bytes]; + signal output body_hash_reveal[LEN_SHA_B64]; + + // signal output reveal_packed[max_bh_packed_bytes]; // packed into 7-bytes. TODO: make this rotate to take up even less space + // SHA HEADER: 506,670 constraints // This calculates the SHA256 hash of the header, which is the "base_msg" that is RSA signed. // The header signs the fields in the "h=Date:From:To:Subject:MIME-Version:Content-Type:Message-ID;" @@ -86,10 +89,10 @@ template CoinbaseEmailVerify(max_header_bytes, n, k) { dkim_header_regex.msg[i] <== in_padded[i]; } dkim_header_regex.out === 2; - for (var i = 0; i < max_header_bytes; i++) { - reveal[i] <== dkim_header_regex.reveal[i+1]; - } - log(dkim_header_regex.out); + // for (var i = 0; i < max_header_bytes; i++) { + // reveal[i] <== dkim_header_regex.reveal[i+1]; + // } + // log(dkim_header_regex.out); // BODY HASH REGEX: 617,597 constraints // This extracts the body hash from the header (i.e. the part after bh= within the DKIM-signature section) @@ -99,7 +102,7 @@ template CoinbaseEmailVerify(max_header_bytes, n, k) { body_hash_regex.msg[i] <== in_padded[i]; } body_hash_regex.out === 1; - log(body_hash_regex.out); + // 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(); @@ -112,6 +115,9 @@ template CoinbaseEmailVerify(max_header_bytes, n, k) { body_hash[j][i] <== body_hash[j][i - 1] + body_hash_eq[i-j].out * body_hash_regex.reveal[i]; } } + for (var i = 0; i < 44; i++) { + body_hash_reveal[i] <== body_hash[i][max_header_bytes - 1]; + } // COINBASE REGEX // Checks Coinbase regex matches KYC confirmation email @@ -123,25 +129,7 @@ template CoinbaseEmailVerify(max_header_bytes, n, k) { component found_coinbase = IsZero(); found_coinbase.in <== coinbase_regex.out; found_coinbase.out === 0; - log(coinbase_regex.out); - - // 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_output[max_packed_bytes]; - for (var i = 0; i < max_packed_bytes; i++) { - packed_output[i] = Bytes2Packed(chunks); - for (var j = 0; j < chunks; j++) { - var reveal_idx = i * chunks + j; - if (reveal_idx < max_header_bytes) { - packed_output[i].in[j] <== reveal[i * chunks + j]; - } else { - packed_output[i].in[j] <== 0; - } - } - reveal_packed[i] <== packed_output[i].out; - } + // log(coinbase_regex.out); // EXTRACT TO EMAIL REGEX // This extracts the to email @@ -156,4 +144,4 @@ template CoinbaseEmailVerify(max_header_bytes, n, k) { } } -// component main { public [ modulus, address ] } = CoinbaseEmailVerify(1024, 121, 17); \ No newline at end of file +// component main { public [ modulus, address ] } = CoinbaseEmailVerify(1024, 121, 9); \ No newline at end of file diff --git a/circuits/base64.circom b/circuits/helpers/base64.circom similarity index 96% rename from circuits/base64.circom rename to circuits/helpers/base64.circom index 61e9603b7..fc41ff399 100644 --- a/circuits/base64.circom +++ b/circuits/helpers/base64.circom @@ -1,6 +1,6 @@ -pragma circom 2.0.3; +pragma circom 2.1.5; -include "../node_modules/circomlib/circuits/comparators.circom"; +include "../../node_modules/circomlib/circuits/comparators.circom"; // http://0x80.pl/notesen/2016-01-17-sse-base64-decoding.html#vector-lookup-base template Base64Lookup() { diff --git a/circuits/bigint.circom b/circuits/helpers/bigint.circom similarity index 97% rename from circuits/bigint.circom rename to circuits/helpers/bigint.circom index 998146838..c37ab92ef 100644 --- a/circuits/bigint.circom +++ b/circuits/helpers/bigint.circom @@ -1,8 +1,8 @@ -pragma circom 2.0.2; +pragma circom 2.1.5; -include "../node_modules/circomlib/circuits/comparators.circom"; -include "../node_modules/circomlib/circuits/bitify.circom"; -include "../node_modules/circomlib/circuits/gates.circom"; +include "../../node_modules/circomlib/circuits/comparators.circom"; +include "../../node_modules/circomlib/circuits/bitify.circom"; +include "../../node_modules/circomlib/circuits/gates.circom"; include "bigint_func.circom"; @@ -535,17 +535,17 @@ template BigModInv(n, k) { // each limbs is n bits template CheckCarryToZero(n, m, k) { assert(k >= 2); - + var EPSILON = 3; - + assert(m + EPSILON <= 253); signal input in[k]; - + signal carry[k]; component carryRangeChecks[k]; for (var i = 0; i < k-1; i++){ - carryRangeChecks[i] = Num2Bits(m + EPSILON - n); + carryRangeChecks[i] = Num2Bits(m + EPSILON - n); if( i == 0 ){ carry[i] <-- in[i] / (1< 2048); // constraints for 2048 bit RSA + assert(n < (255 \ 2)); // we want a multiplication to fit into a circom signal + + signal input in_padded[max_header_bytes]; // prehashed email data, includes up to 512 + 64? bytes of padding pre SHA256, and padded with lots of 0s at end after the length + signal input modulus[k]; // rsa pubkey, verified with smart contract + DNSSEC proof. split up into k parts of n bits each. + signal input signature[k]; // rsa signature. split up into k parts of n bits each. + signal input in_len_padded_bytes; // length of in email data including the padding, which will inform the sha256 block length + + // Identity commitment variables + // (note we don't need to constrain the +1 due to https://geometry.xyz/notebook/groth16-malleability) + signal input address; + + // Base 64 body hash variables + var LEN_SHA_B64 = 44; // ceil(32/3) * 4, due to base64 encoding. + signal input body_hash_idx; + + // SHA HEADER: 506,670 constraints + // This calculates the SHA256 hash of the header, which is the "base_msg" that is RSA signed. + // The header signs the fields in the "h=Date:From:To:Subject:MIME-Version:Content-Type:Message-ID;" + // section of the "DKIM-Signature:"" line, along with the body hash. + // Note that nothing above the "DKIM-Signature:" line is signed. + component sha = Sha256Bytes(max_header_bytes); + sha.in_padded <== in_padded; + sha.in_len_padded_bytes <== in_len_padded_bytes; + var msg_len = (256+n)\n; + + component base_msg[msg_len]; + for (var i = 0; i < msg_len; i++) { + base_msg[i] = Bits2Num(n); + } + for (var i = 0; i < 256; i++) { + base_msg[i\n].in[i%n] <== sha.out[255 - i]; + } + for (var i = 256; i < n*msg_len; i++) { + base_msg[i\n].in[i%n] <== 0; + } + + // VERIFY RSA SIGNATURE: 149,251 constraints + // The fields that this signature actually signs are defined as the body and the values in the header + component rsa = RSAVerify65537(n, k); + for (var i = 0; i < msg_len; i++) { + rsa.base_message[i] <== base_msg[i].out; + } + for (var i = msg_len; i < k; i++) { + rsa.base_message[i] <== 0; + } + rsa.modulus <== modulus; + rsa.signature <== signature; + + // FROM HEADER REGEX: 736,553 constraints + // This extracts the from email, and the precise regex format can be viewed in the README + if(expose_from){ + var max_email_from_len = 30; + var max_email_from_packed_bytes = count_packed(max_email_from_len, pack_size); + assert(max_email_from_packed_bytes < max_header_bytes); + + signal input email_from_idx; + signal output reveal_email_from_packed[max_email_from_packed_bytes]; // packed into 7-bytes. TODO: make this rotate to take up even less space + + signal from_regex_out, from_regex_reveal[max_header_bytes]; + (from_regex_out, from_regex_reveal) <== FromRegex(max_header_bytes)(in_padded); + log(from_regex_out); + from_regex_out === 1; + reveal_email_from_packed <== ShiftAndPack(max_header_bytes, max_email_from_len, pack_size)(from_regex_reveal, email_from_idx); + } + +/* + // TO HEADER REGEX: 736,553 constraints + // This extracts the to email, and the precise regex format can be viewed in the README +*/ // We cannot use to: field at all due to Hotmail + // if(expose_to){ + // var max_email_to_len = 30; + // var max_email_to_packed_bytes = count_packed(max_email_to_len, pack_size); + // assert(max_email_to_packed_bytes < max_header_bytes); + + // signal input email_to_idx; + // signal output reveal_email_to_packed[max_email_to_packed_bytes]; // packed into 7-bytes. TODO: make this rotate to take up even less space + + // signal to_regex_out, to_regex_reveal[max_header_bytes]; + // (to_regex_out, to_regex_reveal) <== ToRegex(max_header_bytes)(in_padded); + // to_regex_out === 1; + // reveal_email_to_packed <== ShiftAndPack(max_header_bytes, max_email_to_len, pack_size)(to_regex_reveal, email_to_idx); + // } + + // BODY HASH REGEX: 617,597 constraints + // This extracts the body hash from the header (i.e. the part after bh= within the DKIM-signature section) + // which is used to verify the body text matches this signed hash + the signature verifies this hash is legit + signal bh_regex_out, bh_reveal[max_header_bytes]; + (bh_regex_out, bh_reveal) <== BodyHashRegex(max_header_bytes)(in_padded); + bh_regex_out === 1; + signal shifted_bh_out[LEN_SHA_B64] <== VarShiftLeft(max_header_bytes, LEN_SHA_B64)(bh_reveal, body_hash_idx); + // log(body_hash_regex.out); + + + // SHA BODY: 760,142 constraints + + // Precomputed sha vars for big body hashing + // Next 3 signals are for decreasing SHA constraints for parsing out information from the in-body text + // The precomputed_sha value is the Merkle-Damgard state of our SHA hash uptil our first regex match + // This allows us to save a ton of SHA constraints by only hashing the relevant part of the body + // It doesn't have an impact on security since a user must have known the pre-image of a signed message to be able to fake it + // The lower two body signals describe the suffix of the body that we care about + // The part before these signals, a significant prefix of the body, has been pre-hashed into precomputed_sha. + signal input precomputed_sha[32]; + signal input in_body_padded[max_body_bytes]; + signal input in_body_len_padded_bytes; + + // This verifies that the hash of the body, when calculated from the precomputed part forwards, + // actually matches the hash in the header + signal sha_body_out[256] <== Sha256BytesPartial(max_body_bytes)(in_body_padded, in_body_len_padded_bytes, precomputed_sha); + signal sha_b64_out[32] <== Base64Decode(32)(shifted_bh_out); + + // When we convert the manually hashed email sha_body into bytes, it matches the + // base64 decoding of the final hash state that the signature signs (sha_b64) + component sha_body_bytes[32]; + for (var i = 0; i < 32; i++) { + sha_body_bytes[i] = Bits2Num(8); + for (var j = 0; j < 8; j++) { + sha_body_bytes[i].in[7-j] <== sha_body_out[i*8+j]; + } + sha_body_bytes[i].out === sha_b64_out[i]; + } + + // Body reveal vars + var max_twitter_len = 21; + var max_twitter_packed_bytes = count_packed(max_twitter_len, pack_size); // ceil(max_num_bytes / 7) + signal input twitter_username_idx; + signal output reveal_twitter_packed[max_twitter_packed_bytes]; + + // TWITTER REGEX: 328,044 constraints + // This computes the regex states on each character in the email body. For new emails, this is the + // section that you want to swap out via using the zk-regex library. + signal twitter_regex_out, twitter_regex_reveal[max_body_bytes]; + (twitter_regex_out, twitter_regex_reveal) <== TwitterResetRegex(max_body_bytes)(in_body_padded); + // This ensures we found a match at least once (i.e. match count is not zero) + signal is_found_twitter <== IsZero()(twitter_regex_out); + is_found_twitter === 0; + + // PACKING: 16,800 constraints (Total: 3,115,057) + reveal_twitter_packed <== ShiftAndPack(max_body_bytes, max_twitter_len, pack_size)(twitter_regex_reveal, twitter_username_idx); +} + +// 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. + +// Args: +// * max_header_bytes = 1024 is the max number of bytes in the header +// * max_body_bytes = 1536 is the max number of bytes in the body after precomputed slice +// * n = 121 is the number of bits in each chunk of the modulus (RSA parameter) +// * k = 17 is the number of chunks in the modulus (RSA parameter) +// * pack_size = 7 is the number of bytes that can fit into a 255ish bit signal (can increase later) +// * expose_from = 0 is whether to expose the from email address +// * expose_to = 0 is whether to expose the to email (not recommended) +component main { public [ modulus, address ] } = EmailVerify(1024, 1536, 121, 17, 7, 0, 0); diff --git a/circuits/wallet.circom b/circuits/wallet.circom new file mode 100644 index 000000000..e4756d611 --- /dev/null +++ b/circuits/wallet.circom @@ -0,0 +1,207 @@ +pragma circom 2.1.5; + +include "../node_modules/circomlib/circuits/bitify.circom"; +include "./helpers/sha.circom"; +include "./helpers/rsa.circom"; +include "./helpers/base64.circom"; +include "./helpers/extract.circom"; + +include "./regexes/from_regex.circom"; +include "./regexes/tofrom_domain_regex.circom"; +include "./regexes/body_hash_regex.circom"; +include "./regexes/twitter_reset_regex.circom"; +include "./regexes/subject_regex.circom"; + + +// Here, n and k are the biginteger parameters for RSA +// This is because the number is chunked into k pack_size of n bits each +// Max header bytes shouldn't need to be changed much per email, +// but the max mody bytes may need to be changed to be larger if the email has a lot of i.e. HTML formatting +// TODO: split into header and body +template EmailVerify(max_header_bytes, max_body_bytes, n, k, pack_size, expose_from, expose_to) { + assert(max_header_bytes % 64 == 0); + assert(max_body_bytes % 64 == 0); + assert(expose_from < 2); // 1 if we should expose the from, 0 if we should not + assert(expose_to == 0); // 1 if we should expose the to, 0 if we should not: due to hotmail restrictions, we force-disable this + assert(n * k > 2048); // constraints for 2048 bit RSA + assert(n < (255 \ 2)); // we want a multiplication to fit into a circom signal + + signal input in_padded[max_header_bytes]; // prehashed email data, includes up to 512 + 64? bytes of padding pre SHA256, and padded with lots of 0s at end after the length + signal input modulus[k]; // rsa pubkey, verified with smart contract + DNSSEC proof. split up into k parts of n bits each. + signal input signature[k]; // rsa signature. split up into k parts of n bits each. + signal input in_len_padded_bytes; // length of in email data including the padding, which will inform the sha256 block length + + // Precomputed sha vars for big body hashing + // Next 3 signals are for decreasing SHA constraints for parsing out information from the in-body text + // The precomputed_sha value is the Merkle-Damgard state of our SHA hash uptil our first regex match + // This allows us to save a ton of SHA constraints by only hashing the relevant part of the body + // It doesn't have an impact on security since a user must have known the pre-image of a signed message to be able to fake it + // The lower two body signals describe the suffix of the body that we care about + // The part before these signals, a significant prefix of the body, has been pre-hashed into precomputed_sha. + // signal input precomputed_sha[32]; + // signal input in_body_padded[max_body_bytes]; + // signal input in_body_len_padded_bytes; + + // Header reveal vars + // TODO: In reality, this max value is 320, and would allow people to break our gaurantees and spoof arbitrary email addresses by registering disgustingly subdomains and going past the end of the 30 + var max_subject_amount_len = 30; + var max_subject_amount_packed_bytes = count_packed(max_subject_amount_len, pack_size); + var max_subject_currency_len = 5; + var max_subject_currency_packed_bytes = count_packed(max_subject_currency_len, pack_size); + var max_subject_recipient_len = 30; + var max_subject_recipient_packed_bytes = count_packed(max_subject_recipient_len, pack_size); + + signal input amount_idx; + signal input currency_idx; + signal input recipient_idx; + signal output reveal_amount_packed[max_subject_amount_packed_bytes]; // packed into 7-bytes. TODO: make this rotate to take up even less space + signal output reveal_currency_packed[max_subject_currency_packed_bytes]; // packed into 7-bytes. TODO: make this rotate to take up even less space + signal output reveal_recipient_packed[max_subject_recipient_packed_bytes]; // packed into 7-bytes. TODO: make this rotate to take up even less space + + // Body reveal vars + // var max_twitter_len = 21; + // var max_twitter_packed_bytes = count_packed(max_twitter_len, pack_size); // ceil(max_num_bytes / 7) + // signal input twitter_username_idx; + // signal output reveal_twitter_packed[max_twitter_packed_bytes]; + + // Identity commitment variables + // (note we don't need to constrain the +1 due to https://geometry.xyz/notebook/groth16-malleability) + signal input address; + signal input address_plus_one; + + // Base 64 body hash variables + var LEN_SHA_B64 = 44; // ceil(32/3) * 4, due to base64 encoding. + signal input body_hash_idx; + + // SHA HEADER: 506,670 constraints + // This calculates the SHA256 hash of the header, which is the "base_msg" that is RSA signed. + // The header signs the fields in the "h=Date:From:To:Subject:MIME-Version:Content-Type:Message-ID;" + // section of the "DKIM-Signature:"" line, along with the body hash. + // Note that nothing above the "DKIM-Signature:" line is signed. + component sha = Sha256Bytes(max_header_bytes); + sha.in_padded <== in_padded; + sha.in_len_padded_bytes <== in_len_padded_bytes; + var msg_len = (256+n)\n; + + component base_msg[msg_len]; + for (var i = 0; i < msg_len; i++) { + base_msg[i] = Bits2Num(n); + } + for (var i = 0; i < 256; i++) { + base_msg[i\n].in[i%n] <== sha.out[255 - i]; + } + for (var i = 256; i < n*msg_len; i++) { + base_msg[i\n].in[i%n] <== 0; + } + + // VERIFY RSA SIGNATURE: 149,251 constraints + // The fields that this signature actually signs are defined as the body and the values in the header + component rsa = RSAVerify65537(n, k); + for (var i = 0; i < msg_len; i++) { + rsa.base_message[i] <== base_msg[i].out; + } + for (var i = msg_len; i < k; i++) { + rsa.base_message[i] <== 0; + } + rsa.modulus <== modulus; + rsa.signature <== signature; + + // FROM HEADER REGEX: 736,553 constraints + // This extracts the from email, and the precise regex format can be viewed in the README + if(expose_from){ + var max_email_from_len = 30; + var max_email_from_packed_bytes = count_packed(max_email_from_len, pack_size); + assert(max_email_from_packed_bytes < max_header_bytes); + + signal input email_from_idx; + signal output reveal_email_from_packed[max_email_from_packed_bytes]; // packed into 7-bytes. TODO: make this rotate to take up even less space + + signal from_regex_out, from_regex_reveal[max_header_bytes]; + (from_regex_out, from_regex_reveal) <== FromRegex(max_header_bytes)(in_padded); + log(from_regex_out); + from_regex_out === 1; + reveal_email_from_packed <== ShiftAndPack(max_header_bytes, max_email_from_len, pack_size)(from_regex_reveal, email_from_idx); + } + +/* + // TO HEADER REGEX: 736,553 constraints + // This extracts the to email, and the precise regex format can be viewed in the README +*/ // We cannot use to: field at all due to Hotmail + if(expose_to){ + // var max_email_to_len = 30; + // var max_email_to_packed_bytes = count_packed(max_email_to_len, pack_size); + // assert(max_email_to_packed_bytes < max_header_bytes); + + // signal input email_to_idx; + // signal output reveal_email_to_packed[max_email_to_packed_bytes]; // packed into 7-bytes. TODO: make this rotate to take up even less space + + // signal to_regex_out, to_regex_reveal[max_header_bytes]; + // (to_regex_out, to_regex_reveal) <== ToRegex(max_header_bytes)(in_padded); + // to_regex_out === 1; + // reveal_email_to_packed <== ShiftAndPack(max_header_bytes, max_email_to_len, pack_size)(to_regex_reveal, email_to_idx); + } + + // SUBJECT HEADER REGEX: 736,553 constraints + // This extracts the subject, and the precise regex format can be viewed in the README + signal subject_regex_out, subject_regex_reveal_amount[max_header_bytes], subject_regex_reveal_currency[max_header_bytes], subject_regex_reveal_recipient[max_header_bytes]; + (subject_regex_out, subject_regex_reveal_amount, subject_regex_reveal_currency, subject_regex_reveal_recipient) <== WalletSubjectRegex(max_header_bytes)(in_padded); + log(subject_regex_out); + subject_regex_out === 1; + + reveal_amount_packed <== ShiftAndPack(max_header_bytes, max_subject_amount_len, pack_size)(subject_regex_reveal_amount, amount_idx); + reveal_currency_packed <== ShiftAndPack(max_header_bytes, max_subject_currency_len, pack_size)(subject_regex_reveal_currency, currency_idx); + reveal_recipient_packed <== ShiftAndPack(max_header_bytes, max_subject_recipient_len, pack_size)(subject_regex_reveal_recipient, recipient_idx); + + // BODY HASH REGEX: 617,597 constraints + // This extracts the body hash from the header (i.e. the part after bh= within the DKIM-signature section) + // which is used to verify the body text matches this signed hash + the signature verifies this hash is legit + signal bh_regex_out, bh_reveal[max_header_bytes]; + (bh_regex_out, bh_reveal) <== BodyHashRegex(max_header_bytes)(in_padded); + bh_regex_out === 1; + signal shifted_bh_out[LEN_SHA_B64] <== VarShiftLeft(max_header_bytes, LEN_SHA_B64)(bh_reveal, body_hash_idx); + // log(body_hash_regex.out); + +/* // We don't need any body parsing for email wallet + // SHA BODY: 760,142 constraints + // This verifies that the hash of the body, when calculated from the precomputed part forwards, + // actually matches the hash in the header + signal sha_body_out[256] <== Sha256BytesPartial(max_body_bytes)(in_body_padded, in_body_len_padded_bytes, precomputed_sha); + signal sha_b64_out[32] <== Base64Decode(32)(shifted_bh_out); + + // When we convert the manually hashed email sha_body into bytes, it matches the + // base64 decoding of the final hash state that the signature signs (sha_b64) + component sha_body_bytes[32]; + for (var i = 0; i < 32; i++) { + sha_body_bytes[i] = Bits2Num(8); + for (var j = 0; j < 8; j++) { + sha_body_bytes[i].in[7-j] <== sha_body_out[i*8+j]; + } + sha_body_bytes[i].out === sha_b64_out[i]; + } + + // TWITTER REGEX: 328,044 constraints + // This computes the regex states on each character in the email body. For new emails, this is the + // section that you want to swap out via using the zk-regex library. + signal twitter_regex_out, twitter_regex_reveal[max_body_bytes]; + (twitter_regex_out, twitter_regex_reveal) <== TwitterResetRegex(max_body_bytes)(in_body_padded); + // This ensures we found a match at least once (i.e. match count is not zero) + signal is_found_twitter <== IsZero()(twitter_regex_out); + is_found_twitter === 0; + + // PACKING: 16,800 constraints (Total: 3,115,057) + reveal_twitter_packed <== ShiftAndPack(max_body_bytes, max_twitter_len, pack_size)(twitter_regex_reveal, twitter_username_idx); +*/ +} + +// 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. + +// Args: +// * max_header_bytes = 1024 is the max number of bytes in the header +// * max_body_bytes = 1536 is the max number of bytes in the body after precomputed slice +// * n = 121 is the number of bits in each chunk of the modulus (RSA parameter) +// * k = 17 is the number of chunks in the modulus (RSA parameter) +// * pack_size = 7 is the number of bytes that can fit into a 255ish bit signal (can increase later) +// * expose_from = 1 is whether to expose the from email address +// * expose_to = 0 is whether to expose the to email (not recommended) +component main { public [ modulus, address ] } = EmailVerify(1024, 1536, 121, 17, 7, 1, 0); diff --git a/dizkus-scripts/1_compile.sh b/dizkus-scripts/1_compile.sh index 6fe2f1867..7a1ff3eb8 100755 --- a/dizkus-scripts/1_compile.sh +++ b/dizkus-scripts/1_compile.sh @@ -1,7 +1,5 @@ #!/bin/bash - -CIRCUIT_NAME=email -BUILD_DIR="../build/$CIRCUIT_NAME" +source circuit.env if [ ! -d "$BUILD_DIR" ]; then echo "No build directory found. Creating build directory..." @@ -9,10 +7,19 @@ if [ ! -d "$BUILD_DIR" ]; then fi echo '****COMPILING CIRCUIT****' -start=`date +%s` +start=$(date +%s) set -x circom "../circuits/$CIRCUIT_NAME".circom --r1cs --wasm --sym --c --wat --output "$BUILD_DIR" { set +x; } 2>/dev/null -end=`date +%s` -echo "DONE ($((end-start))s)" +end=$(date +%s) +echo "DONE ($((end - start))s)" +echo + +echo '****INSPECTING CIRCUIT FOR UNDERCONSTRAINTS (OPTIONAL, CAN FORCE EXIT)****' +start=$(date +%s) +set -x +circom "../circuits/$CIRCUIT_NAME".circom --inspect +{ set +x; } 2>/dev/null +end=$(date +%s) +echo "DONE ($((end - start))s)" echo diff --git a/dizkus-scripts/2_gen_wtns.sh b/dizkus-scripts/2_gen_wtns.sh index 06839dbcb..975d19766 100755 --- a/dizkus-scripts/2_gen_wtns.sh +++ b/dizkus-scripts/2_gen_wtns.sh @@ -1,13 +1,11 @@ #!/bin/bash - -CIRCUIT_NAME=email -BUILD_DIR="../build/$CIRCUIT_NAME" +source circuit.env echo "****GENERATING WITNESS FOR SAMPLE INPUT****" -start=`date +%s` +start=$(date +%s) set -x -node "$BUILD_DIR"/"$CIRCUIT_NAME"_js/generate_witness.js "$BUILD_DIR"/"$CIRCUIT_NAME"_js/"$CIRCUIT_NAME".wasm ../circuits/inputs/input_twitter.json "$BUILD_DIR"/witness.wtns +node "$BUILD_DIR"/"$CIRCUIT_NAME"_js/generate_witness.js "$BUILD_DIR"/"$CIRCUIT_NAME"_js/"$CIRCUIT_NAME".wasm ../circuits/inputs/input_wallet.json "$BUILD_DIR"/witness.wtns { set +x; } 2>/dev/null -end=`date +%s` -echo "DONE ($((end-start))s)" +end=$(date +%s) +echo "DONE ($((end - start))s)" echo diff --git a/dizkus-scripts/3_gen_both_zkeys.sh b/dizkus-scripts/3_gen_both_zkeys.sh index fe84b4d69..f6e573799 100755 --- a/dizkus-scripts/3_gen_both_zkeys.sh +++ b/dizkus-scripts/3_gen_both_zkeys.sh @@ -2,8 +2,8 @@ # Tries to generate a chunked and non-chunked zkey # You need to set entropy.env for this to work -CIRCUIT_NAME=email -BUILD_DIR="../build/$CIRCUIT_NAME" +source circuit.env + R1CS_FILE="$BUILD_DIR/$CIRCUIT_NAME.r1cs" PARTIAL_ZKEYS="$BUILD_DIR"/partial_zkeys PHASE1=../circuits/powersOfTau28_hez_final_22.ptau @@ -16,9 +16,9 @@ fi # First, chunked snarkjs yarn remove snarkjs -mv ../yarn.lock ../yarn.lock_old -rm -rf ../node_modules_old -mv ../node_modules ../node_modules_old +# mv ../yarn.lock ../yarn.lock_old +# rm -r ../node_modules_old +# mv ../node_modules ../node_modules_old yarn add snarkjs@git+https://github.com/vb7401/snarkjs.git#24981febe8826b6ab76ae4d76cf7f9142919d2b8 yarn @@ -52,9 +52,9 @@ echo # Then, nonchunked snarkjs yarn remove snarkjs -mv ../yarn.lock ../yarn.lock_old2 -rm -rf ../node_modules_old2 -mv ../node_modules ../node_modules_old2 +# mv ../yarn.lock ../yarn.lock_old2 +# rm -rf ../node_modules_old2 +# mv ../node_modules ../node_modules_old2 yarn add snarkjs@latest echo "****GENERATING ZKEY NONCHUNKED 0****" @@ -85,8 +85,8 @@ echo "DONE ($((end - start))s)" echo yarn remove snarkjs -mv ../yarn.lock ../yarn.lock_old3 -rm -rf ../node_modules_old3 -mv ../node_modules ../node_modules_old3 +# mv ../yarn.lock ../yarn.lock_old3 +# rm -rf ../node_modules_old3 +# mv ../node_modules ../node_modules_old3 yarn add snarkjs@git+https://github.com/vb7401/snarkjs.git#24981febe8826b6ab76ae4d76cf7f9142919d2b8 yarn diff --git a/dizkus-scripts/3_gen_chunk_zkey.sh b/dizkus-scripts/3_gen_chunk_zkey.sh index 46c2c7c33..3e192bd2b 100755 --- a/dizkus-scripts/3_gen_chunk_zkey.sh +++ b/dizkus-scripts/3_gen_chunk_zkey.sh @@ -1,50 +1,40 @@ #!/bin/bash -CIRCUIT_NAME=email -BUILD_DIR="../build/$CIRCUIT_NAME" +source circuit.env + R1CS_FILE="$BUILD_DIR/$CIRCUIT_NAME.r1cs" PARTIAL_ZKEYS="$BUILD_DIR"/partial_zkeys PHASE1=../circuits/powersOfTau28_hez_final_22.ptau +source entropy.env if [ ! -d "$BUILD_DIR"/partial_zkeys ]; then echo "No partial_zkeys directory found. Creating partial_zkeys directory..." mkdir -p "$BUILD_DIR"/partial_zkeys fi -echo "****GENERATING ZKEY 0****" -start=`date +%s` +echo "****GENERATING ZKEY NONCHUNKED 0****" +start=$(date +%s) set -x -NODE_OPTIONS='--max-old-space-size=56000' node ../node_modules/.bin/snarkjs groth16 setup "$R1CS_FILE" "$PHASE1" "$PARTIAL_ZKEYS"/"$CIRCUIT_NAME"_0.zkey +NODE_OPTIONS='--max-old-space-size=56000' node ../node_modules/.bin/snarkjs groth16 setup "$R1CS_FILE" "$PHASE1" "$PARTIAL_ZKEYS"/"$CIRCUIT_NAME"_0.zkey -e=$ENTROPY1 { set +x; } 2>/dev/null -end=`date +%s` -echo "DONE ($((end-start))s)" +end=$(date +%s) +echo "DONE ($((end - start))s)" echo -echo "****GENERATING ZKEY 1****" -start=`date +%s` +echo "****GENERATING ZKEY NONCHUNKED 1****" +start=$(date +%s) set -x -NODE_OPTIONS='--max-old-space-size=56000' node ../node_modules/.bin/snarkjs zkey contribute "$PARTIAL_ZKEYS"/"$CIRCUIT_NAME"_0.zkey "$PARTIAL_ZKEYS"/"$CIRCUIT_NAME"_1.zkey --name="1st Contributor Name" -v +NODE_OPTIONS='--max-old-space-size=56000' node ../node_modules/.bin/snarkjs zkey contribute "$PARTIAL_ZKEYS"/"$CIRCUIT_NAME"_0.zkey "$PARTIAL_ZKEYS"/"$CIRCUIT_NAME"_1.zkey --name="1st Contributor Name" -v -e=$ENTROPY2 { set +x; } 2>/dev/null -end=`date +%s` -echo "DONE ($((end-start))s)" +end=$(date +%s) +echo "DONE ($((end - start))s)" echo -# echo "****GENERATING ZKEY 2****" -# start=`date +%s` -# set -x -# NODE_OPTIONS='--max-old-space-size=56000' node ../node_modules/.bin/snarkjs zkey contribute "$PARTIAL_ZKEYS"/"$CIRCUIT_NAME"_1.zkey "$PARTIAL_ZKEYS"/"$CIRCUIT_NAME"_2.zkey --name="2nd Contributor Name" -v -# { set +x; } 2>/dev/null -# end=`date +%s` -# echo "DONE ($((end-start))s)" -# echo - -# Use merkle root of Vivek + Lakshman MIMC merkle tree -echo "****GENERATING FINAL ZKEY****" -start=`date +%s` +echo "****GENERATING ZKEY NONCHUNKED FINAL****" +start=$(date +%s) set -x -# hashlib.sha256(b"sampritiaayush").hexdigest().upper() -NODE_OPTIONS='--max-old-space-size=56000' node ../node_modules/.bin/snarkjs zkey beacon "$PARTIAL_ZKEYS"/"$CIRCUIT_NAME"_1.zkey "$BUILD_DIR"/"$CIRCUIT_NAME".zkey AFAE5390CE7790A5EFA03DD74189A4C56DDA1AB8D3D322142C4AE3A14858D6E0 10 -n="Final Beacon phase2" +NODE_OPTIONS='--max-old-space-size=56000' node ../node_modules/.bin/snarkjs zkey beacon "$PARTIAL_ZKEYS"/"$CIRCUIT_NAME"_1.zkey "$BUILD_DIR"/"$CIRCUIT_NAME".zkey $BEACON 10 -n="Final Beacon phase2" { set +x; } 2>/dev/null -end=`date +%s` -echo "DONE ($((end-start))s)" -echo \ No newline at end of file +end=$(date +%s) +echo "DONE ($((end - start))s)" +echo diff --git a/dizkus-scripts/4_gen_vkey.sh b/dizkus-scripts/4_gen_vkey.sh index 60c88e114..212308567 100755 --- a/dizkus-scripts/4_gen_vkey.sh +++ b/dizkus-scripts/4_gen_vkey.sh @@ -1,15 +1,14 @@ #!/bin/bash -CIRCUIT_NAME=email -BUILD_DIR="../build/$CIRCUIT_NAME" +source circuit.env R1CS_FILE="$BUILD_DIR/$CIRCUIT_NAME.r1cs" PHASE1=../circuits/powersOfTau28_hez_final_22.ptau echo "****EXPORTING VKEY****" -start=`date +%s` +start=$(date +%s) set -x NODE_OPTIONS='--max-old-space-size=644000' ../node_modules/.bin/snarkjs zkey export verificationkey "$BUILD_DIR"/"$CIRCUIT_NAME".zkey "$BUILD_DIR"/vkey.json -end=`date +%s` +end=$(date +%s) { set +x; } 2>/dev/null -echo "DONE ($((end-start))s)" -echo \ No newline at end of file +echo "DONE ($((end - start))s)" +echo diff --git a/dizkus-scripts/5_gen_proof.sh b/dizkus-scripts/5_gen_proof.sh index d059e3cc4..e8ce29704 100755 --- a/dizkus-scripts/5_gen_proof.sh +++ b/dizkus-scripts/5_gen_proof.sh @@ -1,22 +1,21 @@ #!/bin/bash -CIRCUIT_NAME=email -BUILD_DIR="../build/$CIRCUIT_NAME" +source circuit.env echo "****GENERATING PROOF FOR SAMPLE INPUT****" -start=`date +%s` +start=$(date +%s) set -x NODE_OPTIONS='--max-old-space-size=644000' ../node_modules/.bin/snarkjs groth16 prove "$BUILD_DIR"/"$CIRCUIT_NAME".zkey "$BUILD_DIR"/witness.wtns "$BUILD_DIR"/proof.json "$BUILD_DIR"/public.json { set +x; } 2>/dev/null -end=`date +%s` -echo "DONE ($((end-start))s)" +end=$(date +%s) +echo "DONE ($((end - start))s)" echo echo "****VERIFYING PROOF FOR SAMPLE INPUT****" -start=`date +%s` +start=$(date +%s) set -x NODE_OPTIONS='--max-old-space-size=644000' ../node_modules/.bin/snarkjs groth16 verify "$BUILD_DIR"/vkey.json "$BUILD_DIR"/public.json "$BUILD_DIR"/proof.json -end=`date +%s` +end=$(date +%s) { set +x; } 2>/dev/null -echo "DONE ($((end-start))s)" -echo \ No newline at end of file +echo "DONE ($((end - start))s)" +echo diff --git a/dizkus-scripts/6_gen_proof_rapidsnark.sh b/dizkus-scripts/6_gen_proof_rapidsnark.sh index 084173c43..6904e1800 100755 --- a/dizkus-scripts/6_gen_proof_rapidsnark.sh +++ b/dizkus-scripts/6_gen_proof_rapidsnark.sh @@ -1,15 +1,14 @@ #!/bin/bash -CIRCUIT_NAME=email -BUILD_DIR="../build/$CIRCUIT_NAME" +source circuit.env echo "****MAKE CPP FILE FOR WITNESS GENERATION****" -start=`date +%s` +start=$(date +%s) set -x make -C "$BUILD_DIR"/"$CIRCUIT_NAME"_cpp/ { set +x; } 2>/dev/null -end=`date +%s` -echo "DONE ($((end-start))s)" +end=$(date +%s) +echo "DONE ($((end - start))s)" echo # echo "****GENERATING WITNESS FOR SAMPLE INPUT****" @@ -22,10 +21,10 @@ echo # echo echo "****GENERATING PROOF FOR SAMPLE INPUT****" -start=`date +%s` +start=$(date +%s) set -x -./../rapidsnark/build/prover "$BUILD_DIR"/"$CIRCUIT_NAME".zkey "$BUILD_DIR"/witness.wtns "$BUILD_DIR"/rapidsnark_proof.json "$BUILD_DIR"/rapidsnark_public.json +./../../rapidsnark/build/prover "$BUILD_DIR"/"$CIRCUIT_NAME".zkey "$BUILD_DIR"/witness.wtns "$BUILD_DIR"/rapidsnark_proof.json "$BUILD_DIR"/rapidsnark_public.json { set +x; } 2>/dev/null -end=`date +%s` -echo "DONE ($((end-start))s)" -echo \ No newline at end of file +end=$(date +%s) +echo "DONE ($((end - start))s)" +echo diff --git a/dizkus-scripts/upload_to_s3.py b/dizkus-scripts/upload_to_s3.py index 4a4bb5915..8e6f538dd 100644 --- a/dizkus-scripts/upload_to_s3.py +++ b/dizkus-scripts/upload_to_s3.py @@ -3,17 +3,33 @@ import tarfile import time import gzip +import argparse + +# python3 dizkus-scripts/upload_to_s3.py --dirs ~/rapidsnark/build/ --bucket_name zk-wallet --prefix prover # Set up the client for the AWS S3 service s3 = boto3.client('s3') # Ask Aayush for the access key and secret access key -# Set the name of the remote directory and the AWS bucket -source = '~/Documents/projects/zk-email-verify' -source = '.' -zkey_dir = source + '/chunked_build/email/' -wasm_dir = source + '/chunked_build/email/email_js/' -bucket_name = 'zkemail-zkey-chunks' # us-east-1 +parser = argparse.ArgumentParser(description='Upload files to S3 bucket') +parser.add_argument('--bucket_name', type=str, default='zkemail-zkey-chunks', help='Name of the S3 bucket') +parser.add_argument('--dirs', type=str, default='~/Documents/projects/zk-email-verify/chunked_build/email,~/Documents/projects/zk-email-verify/chunked_build/email/email_js', help="Comma seperated directories to upload files from") +# parser.add_argument('--build_dir', type=str, default='chunked_build', help='Name of the build directory directory with the circuitname/ folder') +# parser.add_argument('--circuit_name', type=str, default='email', help='Name of the circuit (i.e. the foldername in build_dir/)') +parser.add_argument('--prefix_to_tar', type=str, default='email.zkey', help='Prefix to match for files in order to compress to a .tar.gz and upload') +parser.add_argument('--prefix', type=str, default='vkey.json,email.wasm', help='Comma-seperated prefixes to upload without compression') +args = parser.parse_args() +bucket_name = args.bucket_name +# build_dir = args.build_dir +# circuit_name = args.circuit_name +prefix_to_tar = args.prefix_to_tar +prefixes = args.prefix.split(',') +dirs = args.dirs.split(',') +# Set the name of the remote directory and the AWS bucket +# source = '~/Documents/projects/zk-email-verify' +# source = '.' +# zkey_dir = source + '/{build_dir}/{circuit_name}/' +# wasm_dir = source + '/{build_dir}/{circuit_name}/{circuit_name}_js/' def upload_to_s3(filename, dir=""): with open(dir + filename, 'rb') as file: @@ -24,10 +40,10 @@ def upload_to_s3(filename, dir=""): # Loop through the files in the remote directory -for dir in [zkey_dir, wasm_dir]: +for dir in dirs: for file in os.listdir(dir): # Check if the file matches the pattern - if file.startswith('email.zkey'): + if file.startswith(prefix_to_tar): source_file_path = dir + file upload_to_s3(file, dir) # Uncompressed file @@ -53,6 +69,9 @@ def upload_to_s3(filename, dir=""): os.remove(tar_file_name) os.remove(gz_file_name) - if file.startswith('vkey.json') or file.startswith('email.wasm'): + # If file starts with any one of the prefixes + if any(file.startswith(prefix) for prefix in prefixes): # Upload the zip file to the AWS bucket, overwriting any existing file with the same name upload_to_s3(file, dir) + # if file.startswith('vkey.json') or file.startswith('email.wasm'): + # upload_to_s3(file, dir) diff --git a/docs/README.md b/docs/README.md index 498bc69f5..d114eb893 100644 --- a/docs/README.md +++ b/docs/README.md @@ -33,7 +33,7 @@ proof like this again. Because all web2 data is centralized to some extent, note that the Twitter mailserver or database may know other identifying metadata about you just from your username. -Becaause we do not currently have a nullifier, email addresses can generate an infinite +Because we do not currently have a nullifier, email addresses can generate an infinite number of password reset emails and thus Twitter badges corresponding to their username, meaning their credentials are safe if their Ethereum account is hijacked. This also means 'uniqueness' is hard to define, so anonymous voting protocols in some anonymity set based on zk-email verification would not be possible. diff --git a/node_modules_old/.yarn-state.yml b/node_modules_old/.yarn-state.yml new file mode 100644 index 000000000..a1fa42966 --- /dev/null +++ b/node_modules_old/.yarn-state.yml @@ -0,0 +1,10 @@ +# Warning: This file is automatically generated. Removing it is fine, but will +# cause your node_modules installation to become invalidated. + +__metadata: + version: 1 + nmMode: classic + +"zk-email-verify@workspace:.": + locations: + - "" diff --git a/node_modules_old2/.yarn-state.yml b/node_modules_old2/.yarn-state.yml new file mode 100644 index 000000000..a1fa42966 --- /dev/null +++ b/node_modules_old2/.yarn-state.yml @@ -0,0 +1,10 @@ +# Warning: This file is automatically generated. Removing it is fine, but will +# cause your node_modules installation to become invalidated. + +__metadata: + version: 1 + nmMode: classic + +"zk-email-verify@workspace:.": + locations: + - "" diff --git a/node_modules_old3/.yarn-state.yml b/node_modules_old3/.yarn-state.yml new file mode 100644 index 000000000..a1fa42966 --- /dev/null +++ b/node_modules_old3/.yarn-state.yml @@ -0,0 +1,10 @@ +# Warning: This file is automatically generated. Removing it is fine, but will +# cause your node_modules installation to become invalidated. + +__metadata: + version: 1 + nmMode: classic + +"zk-email-verify@workspace:.": + locations: + - "" diff --git a/package.json b/package.json index 026b74c98..612aa37db 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,8 @@ "ts-node": "^10.9.1", "typescript": "^4.8.3", "wagmi": "^0.6.8", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "yargs": "^17.7.1" }, "resolutions": { "react-error-overlay": "6.0.9" diff --git a/regex_to_circom/gen.py b/regex_to_circom/gen.py index 1e37af666..c2835d6dc 100644 --- a/regex_to_circom/gen.py +++ b/regex_to_circom/gen.py @@ -2,7 +2,10 @@ import json import string -graph_json = json.loads(subprocess.check_output(['npx', 'tsx', 'lexical.js'])) +# Clear file +OUTPUT_HALO2 = True + +graph_json = json.loads(subprocess.check_output(['npx', 'tsx', 'regex_to_dfa.js'])) N = len(graph_json) # Outgoing nodes @@ -23,6 +26,18 @@ accept_nodes = list(accept_nodes) assert len(accept_nodes) == 1 +if (OUTPUT_HALO2): + with open('halo2_regex_lookup.txt', 'w') as f: + for a in accept_nodes: + print(str(a) + " ", file=f, end='') + print("", file=f) + for i in range(N): + for k in graph_json[i]['edges']: + v = graph_json[i]['edges'][k] + for val in json.loads(k): + with open('halo2_regex_lookup.txt', 'a') as f: + print(i, v, ord(val), file=f) + eq_i = 0 lt_i = 0 and_i = 0 diff --git a/regex_to_circom/halo2_regex_lookup.txt b/regex_to_circom/halo2_regex_lookup.txt index 4bfa47417..2bae65b9b 100644 --- a/regex_to_circom/halo2_regex_lookup.txt +++ b/regex_to_circom/halo2_regex_lookup.txt @@ -1,24 +1,410 @@ -0 1 89 +1 2 68 +1 2 119 +1 2 90 +1 2 113 +1 2 53 +1 2 85 +1 2 71 +1 2 77 +1 2 78 +1 2 104 +1 2 86 +1 2 52 +1 2 32 +1 2 76 +1 2 50 +1 2 88 +1 2 108 +1 2 122 +1 2 98 +1 2 116 +1 2 34 +1 2 46 +1 2 79 +1 2 100 +1 2 51 +1 2 118 +1 2 81 +1 2 56 +1 2 109 1 2 111 -2 3 117 -3 4 39 -4 5 114 -5 6 101 -6 7 32 -7 8 114 +1 2 102 +1 2 49 +1 2 95 +1 2 44 +1 2 74 +1 2 120 +1 2 117 +1 2 121 +1 2 48 +1 2 55 +1 2 65 +1 2 70 +1 2 83 +1 2 84 +1 2 87 +1 2 97 +1 2 66 +1 2 73 +1 2 106 +1 2 99 +1 2 112 +1 2 69 +1 2 80 +1 2 82 +1 2 103 +1 2 64 +1 2 110 +1 2 115 +1 2 72 +1 2 54 +1 2 89 +1 2 75 +1 2 107 +1 2 67 +1 2 114 +1 2 101 +1 2 105 +1 2 57 +1 2 45 +8 9 68 +8 9 119 +8 9 90 +8 9 113 +8 9 53 +8 9 85 +8 9 71 +8 9 77 +8 9 78 +8 9 104 +8 9 86 +8 9 52 +8 9 32 +8 9 76 +8 9 50 +8 9 88 +8 9 108 +8 9 122 +8 9 98 +8 9 116 +8 9 34 +8 9 46 +8 9 79 +8 9 100 +8 9 51 +8 9 118 +8 9 81 +8 9 56 +8 9 109 +8 9 111 +8 9 102 +8 9 49 +8 9 95 +8 9 44 +8 9 74 +8 9 120 +8 9 117 +8 9 121 +8 9 48 +8 9 55 +8 9 65 +8 9 70 +8 9 83 +8 9 84 +8 9 87 +8 9 97 +8 9 66 +8 9 73 +8 9 106 +8 9 99 +8 9 112 +8 9 69 +8 9 80 +8 9 82 +8 9 103 +8 9 64 +8 9 110 +8 9 115 +8 9 72 +8 9 54 +8 9 89 +8 9 75 +8 9 107 +8 9 67 +8 9 114 8 9 101 +8 9 105 +8 9 57 +8 9 45 +0 1 13 +0 1 94 +2 3 10 +1 2 60 +3 4 116 +4 5 68 +4 5 119 +4 5 90 +4 5 113 +4 5 53 +4 5 85 +4 5 71 +4 5 77 +4 5 78 +4 5 104 +4 5 86 +4 5 52 +4 5 76 +4 5 122 +4 5 50 +4 5 88 +4 5 108 +4 5 98 +4 5 116 +4 5 46 +4 5 79 +4 5 100 +4 5 51 +4 5 118 +4 5 81 +4 5 56 +4 5 109 +4 5 111 +4 5 102 +4 5 49 +4 5 95 +4 5 74 +4 5 120 +4 5 117 +4 5 121 +4 5 48 +4 5 55 +4 5 65 +4 5 70 +4 5 83 +4 5 84 +4 5 87 +4 5 97 +4 5 66 +4 5 73 +4 5 106 +4 5 99 +4 5 112 +4 5 69 +4 5 80 +4 5 82 +4 5 103 +4 5 110 +4 5 115 +4 5 72 +4 5 54 +4 5 89 +4 5 75 +4 5 107 +4 5 67 +4 5 114 +4 5 101 +4 5 105 +4 5 57 +4 5 45 +6 7 68 +6 7 119 +6 7 90 +6 7 113 +6 7 53 +6 7 85 +6 7 71 +6 7 77 +6 7 78 +6 7 104 +6 7 86 +6 7 52 +6 7 76 +6 7 122 +6 7 50 +6 7 88 +6 7 108 +6 7 98 +6 7 116 +6 7 46 +6 7 79 +6 7 100 +6 7 51 +6 7 118 +6 7 81 +6 7 56 +6 7 109 +6 7 111 +6 7 102 +6 7 49 +6 7 95 +6 7 74 +6 7 120 +6 7 117 +6 7 121 +6 7 48 +6 7 55 +6 7 65 +6 7 70 +6 7 83 +6 7 84 +6 7 87 +6 7 97 +6 7 66 +6 7 73 +6 7 106 +6 7 99 +6 7 112 +6 7 69 +6 7 80 +6 7 82 +6 7 103 +6 7 110 +6 7 115 +6 7 72 +6 7 54 +6 7 89 +6 7 75 +6 7 107 +6 7 67 +6 7 114 +6 7 101 +6 7 105 +6 7 57 +6 7 45 +5 6 111 +7 8 58 +6 7 64 +9 10 68 +9 10 119 +9 10 90 +9 10 113 +9 10 53 +9 10 85 +9 10 71 +9 10 77 +9 10 78 +9 10 104 +9 10 86 +9 10 52 +9 10 76 +9 10 122 +9 10 50 +9 10 88 +9 10 108 +9 10 98 +9 10 116 +9 10 46 +9 10 79 +9 10 100 +9 10 51 +9 10 118 +9 10 81 +9 10 56 +9 10 109 +9 10 111 +9 10 102 +9 10 49 +9 10 95 +9 10 74 +9 10 120 +9 10 117 +9 10 121 +9 10 48 +9 10 55 +9 10 65 +9 10 70 +9 10 83 +9 10 84 +9 10 87 9 10 97 +9 10 66 +9 10 73 +9 10 106 +9 10 99 +9 10 112 +9 10 69 +9 10 80 +9 10 82 +9 10 103 +9 10 110 +9 10 115 +9 10 72 +9 10 54 +9 10 89 +9 10 75 +9 10 107 +9 10 67 +9 10 114 +9 10 101 +9 10 105 +9 10 57 +9 10 45 +10 11 68 +10 11 119 +10 11 90 +10 11 113 +10 11 53 +10 11 85 +10 11 71 +10 11 77 +10 11 78 +10 11 104 +10 11 86 +10 11 52 +10 11 76 +10 11 122 +10 11 50 +10 11 88 +10 11 108 +10 11 98 +10 11 116 +10 11 46 +10 11 79 10 11 100 -11 12 121 -12 13 32 -13 14 116 -14 15 111 -15 16 32 -16 17 105 -17 18 110 -18 19 118 -19 20 101 -20 21 115 -21 22 116 -22 23 33 +10 11 51 +10 11 118 +10 11 81 +10 11 56 +10 11 109 +10 11 111 +10 11 102 +10 11 49 +10 11 95 +10 11 74 +10 11 120 +10 11 117 +10 11 121 +10 11 48 +10 11 55 +10 11 65 +10 11 70 +10 11 83 +10 11 84 +10 11 87 +10 11 97 +10 11 66 +10 11 73 +10 11 106 +10 11 99 +10 11 112 +10 11 69 +10 11 80 +10 11 82 +10 11 103 +10 11 110 +10 11 115 +10 11 72 +10 11 54 +10 11 89 +10 11 75 +10 11 107 +10 11 67 +10 11 114 +10 11 101 +10 11 105 +10 11 57 +10 11 45 +10 11 62 +11 12 13 +12 13 10 diff --git a/regex_to_circom/lexical.js b/regex_to_circom/lexical.js index 89070c947..2905a5ea0 100644 --- a/regex_to_circom/lexical.js +++ b/regex_to_circom/lexical.js @@ -1,29 +1,3 @@ -/*jslint browser: true*/ -/*global require, exports*/ -import { STRING_PRESELECTOR, STRING_PRESELECTOR_AIRBNB , STRING_PRESELECTOR_COINBASE } from "../src/helpers/constants.ts"; - -/** This section sets the 'regex' variable to the regex you want to use. - * All of the relevant regexes are in the main repo README. - */ - -const key_chars = "(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z)"; -const catch_all = - "(0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|!|\"|#|$|%|&|'|\\(|\\)|\\*|\\+|,|-|.|/|:|;|<|=|>|\\?|@|[|\\\\|]|^|_|`|{|\\||}|~| |\t|\n|\r|\x0b|\x0c)"; -const catch_all_without_semicolon = - "(0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|!|\"|#|$|%|&|'|\\(|\\)|\\*|\\+|,|-|.|/|:|<|=|>|\\?|@|[|\\\\|]|^|_|`|{|\\||}|~| |\t|\n|\r|\x0b|\x0c)"; -const base_64 = "(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|0|1|2|3|4|5|6|7|8|9|\\+|/|=)"; -const word_char = "(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|0|1|2|3|4|5|6|7|8|9|_)"; - -// let old_regex = '(\r\n|\x80)(to|from):([A-Za-z0-9 _."@-]+<)?[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.]+>?\r\n'; -// let regex = '(\r\n|\x80)(to|from):((a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|0|1|2|3|4|5|6|7|8|9| |_|.|"|@|-)+<)?(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|0|1|2|3|4|5|6|7|8|9|_|.|-)+@(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|0|1|2|3|4|5|6|7|8|9|_|.|-)+>?\r\n'; -// let regex = `\r\ndkim-signature:(${key_chars}=${catch_all_without_semicolon}+; )+bh=${base_64}+; `; -// 'dkim-signature:((a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z)=(0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|!|"|#|$|%|&|\'|\\(|\\)|\\*|\\+|,|-|.|/|:|<|=|>|\\?|@|[|\\\\|]|^|_|`|{|\\||}|~| |\t|\n|\r|\x0B|\f)+; )+bh=(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|0|1|2|3|4|5|6|7|8|9|\\+|/|=)+; ' -// let regex = STRING_PRESELECTOR + `${word_char}+`; -let regex = STRING_PRESELECTOR_COINBASE; -// let regex = 'hello(0|1|2|3|4|5|6|7|8|9)+world'; -// console.log(regex); -// console.log(Buffer.from(regex).toString('base64')); - /** * Try parsing simple regular expression to syntax tree. * @@ -42,6 +16,8 @@ let regex = STRING_PRESELECTOR_COINBASE; * @param {string} text The input regular expression * @return {string|object} Returns a string that is an error message if failed to parse the expression, * otherwise returns an object which is the syntax tree. + * + * Edited from https://github.com/CyberZHG/toolbox/blob/gh-pages/js/lexical.js */ function parseRegex(text) { "use strict"; @@ -266,6 +242,11 @@ function nfaToDfa(nfa) { } while (stack.length > 0) { top = stack.pop(); + // If top is of type string and starts with "Error" then return error + if (typeof top === "string" && top[0] === "E") { + console.log(top); + continue; + } for (i = 0; i < top.edges.length; i += 1) { if (top.edges[i][0] === "ϵ") { if (closure.indexOf(top.edges[i][1]) < 0) { @@ -564,62 +545,9 @@ function minDfa(dfa) { return buildMinNfa(dfa, partitions, idMap, revEdges); } -function toNature(col) { - var i, - j, - base = "ABCDEFGHIJKLMNOPQRSTUVWXYZ", - result = 0; - if ("1" <= col[0] && col[0] <= "9") { - result = parseInt(col, 10); - } else { - for (i = 0, j = col.length - 1; i < col.length; i += 1, j -= 1) { - result += Math.pow(base.length, j) * (base.indexOf(col[i]) + 1); - } - } - return result; -} - -let nfa = regexToNfa(regex); -let dfa = minDfa(nfaToDfa(nfa)); - -var i, - j, - states = {}, - nodes = [], - stack = [dfa], - symbols = [], - top; - -while (stack.length > 0) { - top = stack.pop(); - if (!states.hasOwnProperty(top.id)) { - states[top.id] = top; - top.nature = toNature(top.id); - nodes.push(top); - for (i = 0; i < top.edges.length; i += 1) { - if (top.edges[i][0] !== "ϵ" && symbols.indexOf(top.edges[i][0]) < 0) { - symbols.push(top.edges[i][0]); - } - stack.push(top.edges[i][1]); - } - } +if (typeof require === "function") { + exports.parseRegex = parseRegex; + exports.regexToNfa = regexToNfa; + exports.nfaToDfa = nfaToDfa; + exports.minDfa = minDfa; } -nodes.sort(function (a, b) { - return a.nature - b.nature; -}); -symbols.sort(); - -let graph = []; -for (let i = 0; i < nodes.length; i += 1) { - let curr = {}; - curr.type = nodes[i].type; - curr.edges = {}; - for (let j = 0; j < symbols.length; j += 1) { - if (nodes[i].trans.hasOwnProperty(symbols[j])) { - curr.edges[symbols[j]] = nodes[i].trans[symbols[j]].nature - 1; - } - } - graph[nodes[i].nature - 1] = curr; -} - -console.log(JSON.stringify(graph)); diff --git a/regex_to_circom/regex_to_dfa.js b/regex_to_circom/regex_to_dfa.js new file mode 100644 index 000000000..956deb8ff --- /dev/null +++ b/regex_to_circom/regex_to_dfa.js @@ -0,0 +1,272 @@ +/*jslint browser: true*/ +/*global require, exports*/ +import { assert } from "console"; +import { STRING_PRESELECTOR, STRING_PRESELECTOR_AIRBNB, STRING_PRESELECTOR_COINBASE } from "../src/helpers/constants.ts"; +import { minDfa, nfaToDfa, regexToNfa } from "./lexical"; + +/** This section sets the 'regex' variable to the regex you want to use. + * All of the relevant regexes are in the main repo README. + */ + +const a2z = "a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z"; +const A2Z = "A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z"; +const r0to9 = "0|1|2|3|4|5|6|7|8|9"; +const alphanum = `${a2z}|${A2Z}|${r0to9}`; + +const key_chars = `(${a2z})`; +const catch_all = + "(0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|!|\"|#|$|%|&|'|\\(|\\)|\\*|\\+|,|-|.|/|:|;|<|=|>|\\?|@|[|\\\\|]|^|_|`|{|\\||}|~| |\t|\n|\r|\x0b|\x0c)"; +const catch_all_without_semicolon = + "(0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|!|\"|#|$|%|&|'|\\(|\\)|\\*|\\+|,|-|.|/|:|<|=|>|\\?|@|[|\\\\|]|^|_|`|{|\\||}|~| |\t|\n|\r|\x0b|\x0c)"; + +const email_chars = `${alphanum}|_|.|-`; +const base_64 = `(${alphanum}|\\+|/|=)`; +const word_char = `(${alphanum}|_)`; + +// let to_from_regex_old = '(\r\n|\x80)(to|from):([A-Za-z0-9 _."@-]+<)?[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.]+>?\r\n'; +// let regex = `\r\ndkim-signature:(${key_chars}=${catch_all_without_semicolon}+; )+bh=${base_64}+; `; +// let order_invariant_regex_raw = `((\\n|\x80|^)(((from):([A-Za-z0-9 _."@-]+<)?[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.]+>)?|(subject:[a-zA-Z 0-9]+)?|((to):([A-Za-z0-9 _."@-]+<)?[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.]+>)?|(dkim-signature:((a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z)=(0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|!|"|#|$|%|&|\'|\\(|\\)|\\*|\\+|,|-|.|/|:|<|=|>|\\?|@|[|\\\\|]|^|_|`|{|\\||}|~| |\t|\n|\r|\x0B|\f)+; ))?)(\\r))+` // Uses a-z syntax instead of | for each char + +const a2z_nosep = "abcdefghijklmnopqrstuvwxyz"; +const A2Z_nosep = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; +const r0to9_nosep = "0123456789"; + +// Note that in order to specify this string in regex, we must use \\ to escape \'s i.e. in the \r\n +let order_invariant_header_regex_raw = `(((\\n|^)(((from):([A-Za-z0-9 _."@-]+<)?[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.]+>)?|(subject:[a-zA-Z 0-9]+)?|((to):([A-Za-z0-9 _."@-]+<)?[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.]+>)?)(\\r))+)\\n`; +let sig_regex = `\r\ndkim-signature:(${key_chars}=${catch_all_without_semicolon}+; )+bh=${base_64}+; `; + +function format_regex_printable(s) { + const escaped_string_json = JSON.stringify(s); + const escaped_string = escaped_string_json.slice(1, escaped_string_json.length - 1); + return escaped_string + .replaceAll("\\\\\\\\", "\\") + .replaceAll("\\\\", "\\") + .replaceAll("\\|", "\\\\|") + .replaceAll("/", "\\/") + .replaceAll("\u000b", "\\♥") + .replaceAll("|[|", "|\\[|") + .replaceAll("|]|", "|\\]|") + .replaceAll("|.|", "|\\.|") + .replaceAll("|$|", "|\\$|") + .replaceAll("|^|", "|\\^|"); + // let escaped = escape_whitespace(escape_whitespace(s.replaceAll("\\\\", "ZZZZZZZ"))); + // let fixed = escaped.replaceAll("\\(", "(").replaceAll("\\)", ")").replaceAll("\\+", "+").replaceAll("\\*", "*").replaceAll("\\?", "?"); +} + +// Note that this is not complete and very case specific i.e. can only handle a-z and not a-c. +function regexToMinDFASpec(str) { + // Replace all A-Z with A2Z etc + let combined_nosep = str.replaceAll("A-Z", A2Z_nosep).replaceAll("a-z", a2z_nosep).replaceAll("0-9", r0to9_nosep); + // .replaceAll("\\w", A2Z_nosep + r0to9_nosep + a2z_nosep); // I think that there's also an underscore here + + function addPipeInsideBrackets(str) { + let result = ""; + let insideBrackets = false; + for (let i = 0; i < str.length; i++) { + if (str[i] === "[") { + result += str[i]; + insideBrackets = true; + continue; + } else if (str[i] === "]") { + insideBrackets = false; + } + let str_to_add = str[i]; + if (str[i] === "\\") { + i++; + str_to_add += str[i]; + } + result += insideBrackets ? "|" + str_to_add : str_to_add; + } + return result.replaceAll("[|", "[").replaceAll("[", "(").replaceAll("]", ")"); + } + + // function makeCurlyBracesFallback(str) { + // let result = ""; + // let insideBrackets = false; + // for (let i = 0; i < str.length; i++) { + // if (str[i] === "{") { + // result += str[i]; + // insideBrackets = true; + // continue; + // } else if (str[i] === "}") { + // insideBrackets = false; + // } + // result += insideBrackets ? "|" + str[i] : str[i]; + // } + // return result.replaceAll("[|", "[").replaceAll("[", "(").replaceAll("]", ")"); + // } + + function checkIfBracketsHavePipes(str) { + let result = true; + let insideBrackets = false; + let indexAt = 0; + for (let i = 0; i < str.length; i++) { + if (indexAt >= str.length) break; + if (str[indexAt] === "[") { + insideBrackets = true; + indexAt++; + continue; + } else if (str[indexAt] === "]") { + insideBrackets = false; + } + if (insideBrackets) { + if (str[indexAt] === "|") { + indexAt++; + } else { + result = false; + return result; + } + } + if (str[indexAt] === "\\") { + indexAt++; + } + indexAt++; + } + return result; + } + + let combined; + if (!checkIfBracketsHavePipes(combined_nosep)) { + // console.log("Adding pipes within brackets between everything!"); + combined = addPipeInsideBrackets(combined_nosep); + assert(checkIfBracketsHavePipes(combined), "Did not add brackets correctly!"); + } else { + combined = combined_nosep; + } + + return combined; +} + +// let full_header_regex = order_invariant_header_regex_raw + sig_regex; +// let raw_regex = order_invariant_header_regex_raw; +// let regex = regexToMinDFASpec(raw_regex) + sig_regex; +// console.log(format_regex_printable(sig_regex)); + +// This raw subject line (with \\ replaced with \) can be put into regexr.com to test new match strings and sanity check that it works +let email_address_regex = `([a-zA-Z0-9\\._%\\+-]+@[a-zA-Z0-9\\.-]+.[a-zA-Z0-9]+)`; +let raw_subject_regex = `((\r\n)|\^)subject:[Ss]end (\\$)?[0-9]+(\\.[0-9])? (ETH|DAI|USDC|eth|usdc|dai) to (${email_address_regex}|0x[0-9]+)\r\n`; +let raw_from_regex = `(\r\n|^)from:([A-Za-z0-9 _.,"@-]+)<[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.-]+>\r\n`; +let raw_to_regex = `(\r\n|^)to:([A-Za-z0-9 _.,"@-]+)<[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.-]+>\r\n`; +// This can be pasted into the first line of zkregex.com/min_dfa +// ((\\r\\n)|\^)subject:[Ss]end (\$)?[0-9]+(\.[0-9])? (ETH|DAI|USDC|eth|usdc|dai) to (([a-zA-Z0-9\._%\+-]+@[a-zA-Z0-9\.-]+.[a-zA-Z0-9]+)|0x[0-9]+)\\r\\n +// console.log(raw_subject_regex); +let regex = regexToMinDFASpec(raw_to_regex); +// This can be pasted into the second line of zkregex.com/min_dfa +// console.log(format_regex_printable(regex)); +// TODO" change \^ into \0x80 + +/* +One indexed! Need to subtract 1 from the DFA state! +// Transition states-- +// Amount: +53 2 +54 2 +2 2 +2 4 +4 11 +// Currency: +3 5 +5 13 +13 19 +3 6 +6 14 +14 19 +3 7 +7 15 +15 20 +20 19 +3 8 +8 16 +16 19 +3 9 +9 17 +17 19 +3 10 +10 18 +18 21 +21 19 +// Dest states only +// Recipient: +28 +34 +38 +39 +26 +32 +35 +40 +44 +*/ +// console.log(raw_regex, "\n", regex); +// let order_invariant_header_regex_raw = `(((\\n|^)(((from):([A-Za-z0-9 _."@-]+<)?[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.]+>)?|(subject:[a-zA-Z 0-9]+)?|((to):([A-Za-z0-9 _."@-]+<)?[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.]+>)?)(\\r))+)`; +// let order_invariant_full_regex_raw = `(dkim-signature:((a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z)=(0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|!|"|#|$|%|&|\'|\\(|\\)|\\*|\\+|,|-|.|/|:|<|=|>|\\?|@|[|\\\\|]|^|_|\`|{|\\||}|~| |\t|\n|\r|\x0B|\f)+; ))?)(\\r))+` // Uses a-z syntax instead of | for each char +// let old_regex = '(\r\n|\x80)(to|from):([A-Za-z0-9 _."@-]+<)?[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.]+>?\r\n'; +// let regex = `(\n|^)(to|from):((${email_chars}|"|@| )+<)?(${email_chars})+@(${email_chars})+>?\r`; +// let regex = `(\r\n|^)(to|from):((${email_chars}|"|@| )+<)?(${email_chars})+@(${email_chars})+>?\r\n`; +// let regex = `\r\ndkim-signature:(${key_chars}=${catch_all_without_semicolon}+; )+bh=${base_64}+; `; +// console.log(regex); +// 'dkim-signature:((a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z)=(0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|!|"|#|$|%|&|\'|\\(|\\)|\\*|\\+|,|-|.|/|:|<|=|>|\\?|@|[|\\\\|]|^|_|`|{|\\||}|~| |\t|\n|\r|\x0B|\f)+; )+bh=(a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|0|1|2|3|4|5|6|7|8|9|\\+|/|=)+; ' +// let regex = STRING_PRESELECTOR + `${word_char}+`; +// let regex = 'hello(0|1|2|3|4|5|6|7|8|9)+world'; +// console.log(regex); +// console.log(Buffer.from(regex).toString('base64')); + +function toNature(col) { + var i, + j, + base = "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + result = 0; + if ("1" <= col[0] && col[0] <= "9") { + result = parseInt(col, 10); + } else { + for (i = 0, j = col.length - 1; i < col.length; i += 1, j -= 1) { + result += Math.pow(base.length, j) * (base.indexOf(col[i]) + 1); + } + } + return result; +} + +let nfa = regexToNfa(regex); +let dfa = minDfa(nfaToDfa(nfa)); + +var i, + j, + states = {}, + nodes = [], + stack = [dfa], + symbols = [], + top; + +while (stack.length > 0) { + top = stack.pop(); + if (!states.hasOwnProperty(top.id)) { + states[top.id] = top; + top.nature = toNature(top.id); + nodes.push(top); + for (i = 0; i < top.edges.length; i += 1) { + if (top.edges[i][0] !== "ϵ" && symbols.indexOf(top.edges[i][0]) < 0) { + symbols.push(top.edges[i][0]); + } + stack.push(top.edges[i][1]); + } + } +} +nodes.sort(function (a, b) { + return a.nature - b.nature; +}); +symbols.sort(); + +let graph = []; +for (let i = 0; i < nodes.length; i += 1) { + let curr = {}; + curr.type = nodes[i].type; + curr.edges = {}; + for (let j = 0; j < symbols.length; j += 1) { + if (nodes[i].trans.hasOwnProperty(symbols[j])) { + curr.edges[symbols[j]] = nodes[i].trans[symbols[j]].nature - 1; + } + } + graph[nodes[i].nature - 1] = curr; +} + +console.log(JSON.stringify(graph)); diff --git a/src/contracts/README.md b/src/contracts/README.md index 2622778d9..1304d0657 100644 --- a/src/contracts/README.md +++ b/src/contracts/README.md @@ -10,7 +10,8 @@ To test solidity, forge install foundry-rs/forge-std cp node_modules/forge-std src/contracts/lib/forge-std cd src/contracts -forge test +forge test --via-ir +forge build --sizes --via-ir # Make sure these are all below 24kB ``` ## Deployment @@ -20,5 +21,15 @@ To deploy contract to forked mainnet, do: ``` anvil --fork-url https://eth-mainnet.alchemyapi.io/v2/***REMOVED*** --port 8547 # Run in tmux export ETH_RPC_URL=http://localhost:8547 -forge create --rpc-url $ETH_RPC_URL src/contracts/src/emailVerifier.sol:Verifier --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 # Public anvil sk +export SK=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 # Public anvil sk + +forge create --rpc-url $ETH_RPC_URL NFTSVG --private-key $SK --via-ir --force | tee /dev/tty | export NFTSVG_ADDR=$(sed -n 's/.*Deployed to: //p') +forge create --rpc-url $ETH_RPC_URL HexStrings --private-key $SK --via-ir --force | tee /dev/tty | export HEXSTRINGS_ADDR=$(sed -n 's/.*Deployed to: //p') +# forge bind --libraries src/hexStrings.sol:hexStrings:$HEXSTRINGS_ADDR --libraries src/NFTSVG.sol:NFTSVG:$NFTSVG_ADDR --via-ir +echo "libraries = [\"src/NFTSVG.sol:NFTSVG:${NFTSVG_ADDR}\", \"src/hexStrings.sol:HexStrings:${HEXSTRINGS_ADDR}\"]" >> foundry.toml +forge create --rpc-url $ETH_RPC_URL VerifiedTwitterEmail --private-key $SK --via-ir --force | tee /dev/tty | export EMAIL_ADDR=$(sed -n 's/.*Deployed to: //p') +sed -i '' -e '$ d' foundry.toml +forge create --rpc-url $ETH_RPC_URL VerifiedTwitterEmail --private-key $SK --via-ir --force --libraries "HexStrings:${HEXSTRINGS_ADDR}","NFTSVG:${NFTSVG_ADDR}" | tee /dev/tty | export EMAIL_ADDR=$(sed -n 's/.*Deployed to: //p') + +forge verify-contract $EMAIL_ADDR VerifiedTwitterEmail --watch --etherscan-api-key $GOERLI_ETHERSCAN_API_KEY ``` diff --git a/src/contracts/src/Create2.sol b/src/contracts/src/Create2.sol new file mode 100644 index 000000000..bba4e5db0 --- /dev/null +++ b/src/contracts/src/Create2.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.7.0) (utils/Create2.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer. + * `CREATE2` can be used to compute in advance the address where a smart + * contract will be deployed, which allows for interesting new mechanisms known + * as 'counterfactual interactions'. + * + * See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more + * information. + */ +library Create2 { + /** + * @dev Deploys a contract using `CREATE2`. The address where the contract + * will be deployed can be known in advance via {computeAddress}. + * + * The bytecode for a contract can be obtained from Solidity with + * `type(contractName).creationCode`. + * + * Requirements: + * + * - `bytecode` must not be empty. + * - `salt` must have not been used for `bytecode` already. + * - the factory must have a balance of at least `amount`. + * - if `amount` is non-zero, `bytecode` must have a `payable` constructor. + */ + function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address) { + address addr; + require(address(this).balance >= amount, "Create2: insufficient balance"); + require(bytecode.length != 0, "Create2: bytecode length is zero"); + /// @solidity memory-safe-assembly + assembly { + addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt) + } + require(addr != address(0), "Create2: Failed on deploy"); + return addr; + } + + /** + * @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the + * `bytecodeHash` or `salt` will result in a new destination address. + */ + function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) { + return computeAddress(salt, bytecodeHash, address(this)); + } + + /** + * @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at + * `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}. + */ + function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address) { + bytes32 _data = keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, bytecodeHash)); + return address(uint160(uint256(_data))); + } +} diff --git a/src/contracts/src/emailVerifier.sol b/src/contracts/src/Groth16VerifierTwitter.sol similarity index 100% rename from src/contracts/src/emailVerifier.sol rename to src/contracts/src/Groth16VerifierTwitter.sol diff --git a/src/contracts/src/Groth16VerifierWallet.sol b/src/contracts/src/Groth16VerifierWallet.sol new file mode 100644 index 000000000..5d71c540d --- /dev/null +++ b/src/contracts/src/Groth16VerifierWallet.sol @@ -0,0 +1,423 @@ +// +// Copyright 2017 Christian Reitwiessner +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// 2019 OKIMS +// ported to solidity 0.6 +// fixed linter warnings +// added requiere error messages +// +// +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.6.11; + +library Pairing { + struct G1Point { + uint X; + uint Y; + } + // Encoding of field elements is: X[0] * z + X[1] + struct G2Point { + uint[2] X; + uint[2] Y; + } + + /// @return the generator of G1 + function P1() internal pure returns (G1Point memory) { + return G1Point(1, 2); + } + + /// @return the generator of G2 + function P2() internal pure returns (G2Point memory) { + // Original code point + return + G2Point( + [11559732032986387107991004021392285783925812861821192530917403151452391805634, 10857046999023057135944570762232829481370756359578518086990519993285655852781], + [4082367875863433681332203403145435568316851327593401208105741076214120093531, 8495653923123431417604973247489272438418190587263600148770280649306958101930] + ); + + /* + // Changed by Jordi point + return G2Point( + [10857046999023057135944570762232829481370756359578518086990519993285655852781, + 11559732032986387107991004021392285783925812861821192530917403151452391805634], + [8495653923123431417604973247489272438418190587263600148770280649306958101930, + 4082367875863433681332203403145435568316851327593401208105741076214120093531] + ); +*/ + } + + /// @return r the negation of p, i.e. p.addition(p.negate()) should be zero. + function negate(G1Point memory p) internal pure returns (G1Point memory r) { + // The prime q in the base field F_q for G1 + uint q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + if (p.X == 0 && p.Y == 0) return G1Point(0, 0); + return G1Point(p.X, q - (p.Y % q)); + } + + /// @return r the sum of two points of G1 + function addition(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) { + uint[4] memory input; + input[0] = p1.X; + input[1] = p1.Y; + input[2] = p2.X; + input[3] = p2.Y; + bool success; + // solium-disable-next-line security/no-inline-assembly + assembly { + success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60) + // Use "invalid" to make gas estimation work + switch success + case 0 { + invalid() + } + } + require(success, "pairing-add-failed"); + } + + /// @return r the product of a point on G1 and a scalar, i.e. + /// p == p.scalar_mul(1) and p.addition(p) == p.scalar_mul(2) for all points p. + function scalar_mul(G1Point memory p, uint s) internal view returns (G1Point memory r) { + uint[3] memory input; + input[0] = p.X; + input[1] = p.Y; + input[2] = s; + bool success; + // solium-disable-next-line security/no-inline-assembly + assembly { + success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60) + // Use "invalid" to make gas estimation work + switch success + case 0 { + invalid() + } + } + require(success, "pairing-mul-failed"); + } + + /// @return the result of computing the pairing check + /// e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1 + /// For example pairing([P1(), P1().negate()], [P2(), P2()]) should + /// return true. + function pairing(G1Point[] memory p1, G2Point[] memory p2) internal view returns (bool) { + require(p1.length == p2.length, "pairing-lengths-failed"); + uint elements = p1.length; + uint inputSize = elements * 6; + uint[] memory input = new uint[](inputSize); + for (uint i = 0; i < elements; i++) { + input[i * 6 + 0] = p1[i].X; + input[i * 6 + 1] = p1[i].Y; + input[i * 6 + 2] = p2[i].X[0]; + input[i * 6 + 3] = p2[i].X[1]; + input[i * 6 + 4] = p2[i].Y[0]; + input[i * 6 + 5] = p2[i].Y[1]; + } + uint[1] memory out; + bool success; + // solium-disable-next-line security/no-inline-assembly + assembly { + success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20) + // Use "invalid" to make gas estimation work + switch success + case 0 { + invalid() + } + } + require(success, "pairing-opcode-failed"); + return out[0] != 0; + } + + /// Convenience method for a pairing check for two pairs. + function pairingProd2(G1Point memory a1, G2Point memory a2, G1Point memory b1, G2Point memory b2) internal view returns (bool) { + G1Point[] memory p1 = new G1Point[](2); + G2Point[] memory p2 = new G2Point[](2); + p1[0] = a1; + p1[1] = b1; + p2[0] = a2; + p2[1] = b2; + return pairing(p1, p2); + } + + /// Convenience method for a pairing check for three pairs. + function pairingProd3(G1Point memory a1, G2Point memory a2, G1Point memory b1, G2Point memory b2, G1Point memory c1, G2Point memory c2) internal view returns (bool) { + G1Point[] memory p1 = new G1Point[](3); + G2Point[] memory p2 = new G2Point[](3); + p1[0] = a1; + p1[1] = b1; + p1[2] = c1; + p2[0] = a2; + p2[1] = b2; + p2[2] = c2; + return pairing(p1, p2); + } + + /// Convenience method for a pairing check for four pairs. + function pairingProd4( + G1Point memory a1, + G2Point memory a2, + G1Point memory b1, + G2Point memory b2, + G1Point memory c1, + G2Point memory c2, + G1Point memory d1, + G2Point memory d2 + ) internal view returns (bool) { + G1Point[] memory p1 = new G1Point[](4); + G2Point[] memory p2 = new G2Point[](4); + p1[0] = a1; + p1[1] = b1; + p1[2] = c1; + p1[3] = d1; + p2[0] = a2; + p2[1] = b2; + p2[2] = c2; + p2[3] = d2; + return pairing(p1, p2); + } +} + +contract Verifier { + using Pairing for *; + struct VerifyingKey { + Pairing.G1Point alfa1; + Pairing.G2Point beta2; + Pairing.G2Point gamma2; + Pairing.G2Point delta2; + Pairing.G1Point[] IC; + } + struct Proof { + Pairing.G1Point A; + Pairing.G2Point B; + Pairing.G1Point C; + } + + function verifyingKey() internal pure returns (VerifyingKey memory vk) { + vk.alfa1 = Pairing.G1Point( + 20491192805390485299153009773594534940189261866228447918068658471970481763042, + 9383485363053290200918347156157836566562967994039712273449902621266178545958 + ); + + vk.beta2 = Pairing.G2Point( + [4252822878758300859123897981450591353533073413197771768651442665752259397132, 6375614351688725206403948262868962793625744043794305715222011528459656738731], + [21847035105528745403288232691147584728191162732299865338377159692350059136679, 10505242626370262277552901082094356697409835680220590971873171140371331206856] + ); + vk.gamma2 = Pairing.G2Point( + [11559732032986387107991004021392285783925812861821192530917403151452391805634, 10857046999023057135944570762232829481370756359578518086990519993285655852781], + [4082367875863433681332203403145435568316851327593401208105741076214120093531, 8495653923123431417604973247489272438418190587263600148770280649306958101930] + ); + vk.delta2 = Pairing.G2Point( + [1513129022268504209358763521777110646559191312944535983790343685540452445612, 12195000584114682213316512369139242650405673197296308277565313794080256232701], + [8360772797757285669693588223224368324652217960280535125476432579464480898413, 520894096459672819696448581165649200639580943346471218758886059131097679200] + ); + vk.IC = new Pairing.G1Point[](35); + + vk.IC[0] = Pairing.G1Point( + 11537328283226768393732428330956073110918624081922605220849994462257164716154, + 21836950190585457727786541794875874204342457073442279657287670575335295859939 + ); + + vk.IC[1] = Pairing.G1Point( + 2646307150509379456483324388204599416197823343744573056776580441757968778128, + 14368370831419521837815784599306984151624772530159432470308283448431330483438 + ); + + vk.IC[2] = Pairing.G1Point( + 16971624750468472956383457363672950966338870541072731251437414691217134411120, + 8171770470043873321771537156021753591829282779422875691859226771666334115210 + ); + + vk.IC[3] = Pairing.G1Point( + 17003248154885997024042723500051841853373913724967509464380365353802575974728, + 7268687626523176370339202918269959082095447462862347953587350206552205642559 + ); + + vk.IC[4] = Pairing.G1Point( + 9147424234223179035895482077044817222415554273556867838268712944193520459125, + 21578705994822322813698189503736815299185497639660343771670537250469027755948 + ); + + vk.IC[5] = Pairing.G1Point( + 16948417333412259029407181850226843375508933350680650127572170476630717282234, + 9665563179883720332817127456522837835577284997912132910805194791161889398512 + ); + + vk.IC[6] = Pairing.G1Point( + 9319994583370423638602672994382300950312503535351144768052894387193551537459, + 19171031111811822064434269353657536224339902296340766041252096715670766153578 + ); + + vk.IC[7] = Pairing.G1Point( + 9371603635986601434942264619807195148477116766259006354709633771674992552724, + 11103293940652841271958831421967970005702672122254116205870466468493278826958 + ); + + vk.IC[8] = Pairing.G1Point( + 3561757143230234005632456376532802121489770329687277876867422136368222736134, + 11734814901687269585580202167146602218102049962630061844516173384912435005151 + ); + + vk.IC[9] = Pairing.G1Point( + 16676238706569507337573176601181670688707264606170712075388734731572549373015, + 11158592034910239776632780480510406146251140896522712858892689171974748083747 + ); + + vk.IC[10] = Pairing.G1Point( + 9822993420542467873912218347366938030193407618831916003032667186678059114349, + 6863223533781083402167797754430699108902384970089129346113470095574463296737 + ); + + vk.IC[11] = Pairing.G1Point( + 4866721301322510775531094866459672146070982818674092273969310571220291630556, + 9846640210996171367705574856419053631553093780701851221089070591862473684456 + ); + + vk.IC[12] = Pairing.G1Point( + 11121919417977708038028243712145798541888876824913262431306415256568966652024, + 12609770408698645498350514024296607224475353273713173177251900216074217014850 + ); + + vk.IC[13] = Pairing.G1Point( + 14840050373651993593383756529988239317958775236310376879012996717055845150487, + 16683016138329247649135414505803188190565946292769946693147365719646757192352 + ); + + vk.IC[14] = Pairing.G1Point( + 12331471170495648864015185687161748641193043583582691780623236535573811426959, + 11714531956225516771424892308789701085922077646343882797070434158311220922011 + ); + + vk.IC[15] = Pairing.G1Point( + 20857366342421630154576707922479406842052250461154457549932210545889187427626, + 19731871495034015170236307573440414758884960293847791130561428843792753429086 + ); + + vk.IC[16] = Pairing.G1Point( + 16209406523379999199236029863462575441846948663847567298395841886469797214082, + 20718988335004414356172240953954108902387108184538803896124546491420460740169 + ); + + vk.IC[17] = Pairing.G1Point( + 8397179853577453858892608876746062307226175687475102639712031393841003387919, + 7154528864876839718549215559715861424372452521581853695056564308745992761672 + ); + + vk.IC[18] = Pairing.G1Point( + 15000425614899689626185464253582174491572354417903507356426258449407132127046, + 13602373364472815067534003393627538602411204908660744540946955553406039633451 + ); + + vk.IC[19] = Pairing.G1Point( + 5545252939208963791309142258539499940294193526436021787091019273144246885261, + 2675833647327088808677716501235324721140769279456271927816550777395489852251 + ); + + vk.IC[20] = Pairing.G1Point( + 2537047834074478023060297384888724410218136939149736151242515713484635642789, + 3193147508270124393755481767836877808561856886429215905854576456538260642120 + ); + + vk.IC[21] = Pairing.G1Point( + 13819984179784763421503658502014430958296359352165019537755065452645355466085, + 18994178127386658997162783196808799873728043389545801736093808610346271161658 + ); + + vk.IC[22] = Pairing.G1Point( + 6589370869708283514329318539826110923064805897289825414235274269171407140900, + 9625792054986455984618262255946246252268480571067521104852530192902386719593 + ); + + vk.IC[23] = Pairing.G1Point( + 14453270681522455527144160729543145025587040770258214008741757192612444737673, + 20143300120523312381709649823189168032874279267240134119356286009260298185475 + ); + + vk.IC[24] = Pairing.G1Point( + 8846979524751473322994268234754538401482894217522349506066299416316984717260, + 14238278950875447301003974303300008365515832894025960431754069250458716988985 + ); + + vk.IC[25] = Pairing.G1Point( + 1407828395464361821905309927863167268883721160860555328581866885124611897528, + 15987854649208373291691713233856831065610123809032682196292981616658494776987 + ); + + vk.IC[26] = Pairing.G1Point( + 20657483817998070093869648777824400547660398552294774144027193285451239619382, + 21826613460615350291777797645121185151869814459512699120411019581958075991542 + ); + + vk.IC[27] = Pairing.G1Point( + 20624928617240774729161710928323453036077574938263627000040473137006018415415, + 5131454817008842892320970265930200727731593782574755474310860262365965990664 + ); + + vk.IC[28] = Pairing.G1Point( + 13895249990164764352618348094492621630575085672410127797817030490897283284460, + 11796920177436300262655702063092510613755939821586920802170520825907606184557 + ); + + vk.IC[29] = Pairing.G1Point( + 3135811994020780743324086008729747183900335277491189104250977002197575809365, + 19637480211624980857501152516366188197895066151113910763057713919944678029866 + ); + + vk.IC[30] = Pairing.G1Point( + 606103022421566187118689786319065266252504972683102249226453076393842587324, + 10274587862652629723961486759561149321143114208996217550785138347396636546119 + ); + + vk.IC[31] = Pairing.G1Point( + 17876636758905622230688544604495142065478803287376541138330575508029036910921, + 845514606482911379811389987862147832754587150586772012493153117259870748818 + ); + + vk.IC[32] = Pairing.G1Point( + 3055934057788309375612017678081361462642124203699024466299800387864964176162, + 6215463225683464296816602777995066291454589608119271052091343612576965188139 + ); + + vk.IC[33] = Pairing.G1Point( + 15655816926053512024400351678850471800118254812943223488624167720306406940886, + 1318451441938966067851976459607266837893918068783070216555767331693196884126 + ); + + vk.IC[34] = Pairing.G1Point( + 13942491345765259320815758221010163510633256522676138655574537850419728932090, + 2449428677603436766608016448595332101485961035234011384134049218692804513154 + ); + } + + function verify(uint[] memory input, Proof memory proof) internal view returns (uint) { + uint256 snark_scalar_field = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + VerifyingKey memory vk = verifyingKey(); + require(input.length + 1 == vk.IC.length, "verifier-bad-input"); + // Compute the linear combination vk_x + Pairing.G1Point memory vk_x = Pairing.G1Point(0, 0); + for (uint i = 0; i < input.length; i++) { + require(input[i] < snark_scalar_field, "verifier-gte-snark-scalar-field"); + vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[i + 1], input[i])); + } + vk_x = Pairing.addition(vk_x, vk.IC[0]); + if (!Pairing.pairingProd4(Pairing.negate(proof.A), proof.B, vk.alfa1, vk.beta2, vk_x, vk.gamma2, proof.C, vk.delta2)) return 1; + return 0; + } + + /// @return r bool true if proof is valid + function verifyProof(uint[2] memory a, uint[2][2] memory b, uint[2] memory c, uint[34] memory input) public view returns (bool r) { + Proof memory proof; + proof.A = Pairing.G1Point(a[0], a[1]); + proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]); + proof.C = Pairing.G1Point(c[0], c[1]); + uint[] memory inputValues = new uint[](input.length); + for (uint i = 0; i < input.length; i++) { + inputValues[i] = input[i]; + } + if (verify(inputValues, proof) == 0) { + return true; + } else { + return false; + } + } +} diff --git a/src/contracts/src/MailServer.sol b/src/contracts/src/MailServer.sol new file mode 100644 index 000000000..0f8f00ed1 --- /dev/null +++ b/src/contracts/src/MailServer.sol @@ -0,0 +1,98 @@ +pragma solidity ^0.8.0; +import "forge-std/console.sol"; + +library MailServer { + uint16 constant rsa_modulus_chunks_len = 17; + struct Server { + mapping(string => uint256[rsa_modulus_chunks_len]) verifiedMailserverKeys; + } + + function initMailserverKeys(Server storage self) internal { + // TODO: Create a type that takes in a raw RSA key, the bit count, + // and whether or not its base64 encoded, and converts it to either 8 or 16 signals + self.verifiedMailserverKeys["gmail.com"][0] = 2645260732387577900369388087711111123; + self.verifiedMailserverKeys["gmail.com"][1] = 2332356685544126002119529566553287568; + self.verifiedMailserverKeys["gmail.com"][2] = 587306946802222480578301599869128605; + self.verifiedMailserverKeys["gmail.com"][3] = 1506808391343308562602228807782956759; + self.verifiedMailserverKeys["gmail.com"][4] = 346696857027646434280628892032962406; + self.verifiedMailserverKeys["gmail.com"][5] = 1655371642328152796841392591809876356; + self.verifiedMailserverKeys["gmail.com"][6] = 773654757689631205903545947464515700; + self.verifiedMailserverKeys["gmail.com"][7] = 137546842031326636154929265514533208; + self.verifiedMailserverKeys["gmail.com"][8] = 979104436480501594376401576155183314; + self.verifiedMailserverKeys["gmail.com"][9] = 1231402749194646866996172591430155068; + self.verifiedMailserverKeys["gmail.com"][10] = 1573385231473380013164181608611759098; + self.verifiedMailserverKeys["gmail.com"][11] = 1199794061179553911325952711127005960; + self.verifiedMailserverKeys["gmail.com"][12] = 1393369642957971131987926230229916984; + self.verifiedMailserverKeys["gmail.com"][13] = 2610100650498432208787557818514105421; + self.verifiedMailserverKeys["gmail.com"][14] = 1405475120223887084339881602469286332; + self.verifiedMailserverKeys["gmail.com"][15] = 2000538708964654339221687925776343058; + self.verifiedMailserverKeys["gmail.com"][16] = 3483697379198011592407370076533025; + + self.verifiedMailserverKeys["hotmail.com"][0] = 128339925410438117770406273090474249; + self.verifiedMailserverKeys["hotmail.com"][1] = 2158906895782814996316644028571725310; + self.verifiedMailserverKeys["hotmail.com"][2] = 2278019331164769360372919938620729773; + self.verifiedMailserverKeys["hotmail.com"][3] = 1305319804455735154587383372570664109; + self.verifiedMailserverKeys["hotmail.com"][4] = 2358345194772578919713586294428642696; + self.verifiedMailserverKeys["hotmail.com"][5] = 1333692900109074470874155333266985021; + self.verifiedMailserverKeys["hotmail.com"][6] = 2252956899717870524129098594286063236; + self.verifiedMailserverKeys["hotmail.com"][7] = 1963190090223950324858653797870319519; + self.verifiedMailserverKeys["hotmail.com"][8] = 2099240641399560863760865662500577339; + self.verifiedMailserverKeys["hotmail.com"][9] = 1591320380606901546957315803395187883; + self.verifiedMailserverKeys["hotmail.com"][10] = 1943831890994545117064894677442719428; + self.verifiedMailserverKeys["hotmail.com"][11] = 2243327453964709681573059557263184139; + self.verifiedMailserverKeys["hotmail.com"][12] = 1078181067739519006314708889181549671; + self.verifiedMailserverKeys["hotmail.com"][13] = 2209638307239559037039565345615684964; + self.verifiedMailserverKeys["hotmail.com"][14] = 1936371786309180968911326337008120155; + self.verifiedMailserverKeys["hotmail.com"][15] = 2611115500285740051274748743252547506; + self.verifiedMailserverKeys["hotmail.com"][16] = 3841983033048617585564391738126779; + + self.verifiedMailserverKeys["ethereum.org"][0] = 119886678941863893035426121053426453; + self.verifiedMailserverKeys["ethereum.org"][1] = 1819786846289142128062035525540154587; + self.verifiedMailserverKeys["ethereum.org"][2] = 18664768675154515296388092785538021; + self.verifiedMailserverKeys["ethereum.org"][3] = 2452916380017370778812419704280324749; + self.verifiedMailserverKeys["ethereum.org"][4] = 147541693845229442834461965414634823; + self.verifiedMailserverKeys["ethereum.org"][5] = 714676313158744653841521918164405002; + self.verifiedMailserverKeys["ethereum.org"][6] = 1495951612535183023869749054624579068; + self.verifiedMailserverKeys["ethereum.org"][7] = 974892773071523448175479681445882254; + self.verifiedMailserverKeys["ethereum.org"][8] = 53117264910028079; + self.verifiedMailserverKeys["ethereum.org"][9] = 0; + self.verifiedMailserverKeys["ethereum.org"][10] = 0; + self.verifiedMailserverKeys["ethereum.org"][11] = 0; + self.verifiedMailserverKeys["ethereum.org"][12] = 0; + self.verifiedMailserverKeys["ethereum.org"][13] = 0; + self.verifiedMailserverKeys["ethereum.org"][14] = 0; + self.verifiedMailserverKeys["ethereum.org"][15] = 0; + self.verifiedMailserverKeys["ethereum.org"][16] = 0; + + self.verifiedMailserverKeys["skiff.com"][0] = 2637270478154147701703365710201556843; + self.verifiedMailserverKeys["skiff.com"][1] = 2082690054369201099288110516791254232; + self.verifiedMailserverKeys["skiff.com"][2] = 1108253255381437937379143813840625818; + self.verifiedMailserverKeys["skiff.com"][3] = 1535554154331979875086566323552212673; + self.verifiedMailserverKeys["skiff.com"][4] = 273019276149049264013012583938735085; + self.verifiedMailserverKeys["skiff.com"][5] = 741436192387359949728618527229215889; + self.verifiedMailserverKeys["skiff.com"][6] = 1851608307869135205473270393049341043; + self.verifiedMailserverKeys["skiff.com"][7] = 1428718881138594152975742734455140338; + self.verifiedMailserverKeys["skiff.com"][8] = 778850382237088374273157869416671135; + self.verifiedMailserverKeys["skiff.com"][9] = 549599381370898291203601849666570597; + self.verifiedMailserverKeys["skiff.com"][10] = 221161044322752364431317167498442512; + self.verifiedMailserverKeys["skiff.com"][11] = 2041801755941244198449288035460748224; + self.verifiedMailserverKeys["skiff.com"][12] = 1083114189020989870026920716001138899; + self.verifiedMailserverKeys["skiff.com"][13] = 1380362773644527202561949550864154963; + self.verifiedMailserverKeys["skiff.com"][14] = 1366599807917971505788646146248798329; + self.verifiedMailserverKeys["skiff.com"][15] = 391565989352979266796804441125988853; + self.verifiedMailserverKeys["skiff.com"][16] = 3704766395208948862861103932863036; + } + + function _stringEq(string memory a, string memory b) public pure returns (bool) { + return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b)); + } + + function isVerified(Server storage self, string memory domain, uint256 index, uint256 val) public view returns (bool) { + // allow external queries on mapping + uint256 val1 = self.verifiedMailserverKeys[domain][index]; + uint256 val2 = val; + console.log(val1, val2); + + return self.verifiedMailserverKeys[domain][index] == val; + } +} diff --git a/src/contracts/src/NFTSVG.sol b/src/contracts/src/NFTSVG.sol index 620841c24..c2a8a19d4 100644 --- a/src/contracts/src/NFTSVG.sol +++ b/src/contracts/src/NFTSVG.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import "@openzeppelin/contracts/utils/Strings.sol"; import "@openzeppelin/contracts/utils/Base64.sol"; -import "./hexStrings.sol"; +import "./HexStrings.sol"; /// https://github.com/nalinbhardwaj/ethdosnumber/blob/main/ethdos-contracts/src/NFTSVG.sol /// @title NFTSVG diff --git a/src/contracts/src/WalletEmailHandler.sol b/src/contracts/src/WalletEmailHandler.sol new file mode 100644 index 000000000..4f89ec3e3 --- /dev/null +++ b/src/contracts/src/WalletEmailHandler.sol @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; +import "@openzeppelin/contracts/utils/Counters.sol"; +import "forge-std/console.sol"; +// import "./base64.sol"; +import "./HexStrings.sol"; +import "./NFTSVG.sol"; +import "./Groth16VerifierWallet.sol"; +import "./MailServer.sol"; + +contract VerifiedWalletEmail is Verifier { + using HexStrings for *; + using MailServer for MailServer.Server; + + uint16 public constant packSize = 7; // 7 bytes in a packed item returned from circom + + uint16 public constant body_len = 4 * 4; + uint16 public constant rsa_modulus_chunks_len = 17; + uint16 public constant commitment_len = 1; + uint16 public constant msg_len = body_len + rsa_modulus_chunks_len + commitment_len; + + uint16 public constant header_len = msg_len - body_len; + uint16 public constant addressIndexInSignals = msg_len - 1; // The last index is the commitment + + mapping(string => uint256[rsa_modulus_chunks_len]) public verifiedMailserverKeys; + mapping(string => uint256) public balance; + mapping(uint256 => bool) public nullifier; + MailServer.Server server; + + // string constant domain = "twitter.com"; + + constructor() { + // Do dig TXT outgoing._domainkey.twitter.com to verify these. + // This is the base 2^121 representation of that key. + // Circom bigint: represent a = a[0] + a[1] * 2**n + .. + a[k - 1] * 2**(n * k) + require(rsa_modulus_chunks_len + body_len + 1 == msg_len, "Variable counts are wrong!"); + server.initMailserverKeys(); + } + + // Unpacks uint256s into bytes and then extracts the non-zero characters + // Only extracts contiguous non-zero characters and ensures theres only 1 such state + // Note that unpackedLen may be more than packedBytes.length * 8 since there may be 0s + // TODO: Remove console.logs and define this as a pure function instead of a view + function convertPackedBytesToBytes(uint256[] memory packedBytes, uint256 maxBytes) public pure returns (string memory extractedString) { + uint8 state = 0; + // bytes: 0 0 0 0 y u s h _ g 0 0 0 + // state: 0 0 0 0 1 1 1 1 1 1 2 2 2 + bytes memory nonzeroBytesArray = new bytes(packedBytes.length * 7); + uint256 nonzeroBytesArrayIndex = 0; + for (uint16 i = 0; i < packedBytes.length; i++) { + uint256 packedByte = packedBytes[i]; + uint8[] memory unpackedBytes = new uint8[](packSize); + for (uint j = 0; j < packSize; j++) { + unpackedBytes[j] = uint8(packedByte >> (j * 8)); + } + for (uint256 j = 0; j < packSize; j++) { + uint256 unpackedByte = unpackedBytes[j]; //unpackedBytes[j]; + // console.log(i, j, state, unpackedByte); + if (unpackedByte != 0) { + nonzeroBytesArray[nonzeroBytesArrayIndex] = bytes1(uint8(unpackedByte)); + nonzeroBytesArrayIndex++; + if (state % 2 == 0) { + state += 1; + } + } else { + if (state % 2 == 1) { + state += 1; + } + } + packedByte = packedByte >> 8; + } + } + string memory returnValue = string(nonzeroBytesArray); + require(state >= 1, "Invalid final state of packed bytes in email"); + // console.log("Characters in username: ", nonzeroBytesArrayIndex); + require(nonzeroBytesArrayIndex <= maxBytes, "Packed bytes more than allowed max length!"); + return returnValue; + // Have to end at the end of the email -- state cannot be 1 since there should be an email footer + } + + function _stringEq(string memory a, string memory b) public pure returns (bool) { + return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b)); + } + + function transfer(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[msg_len] memory signals) public { + // Checks: Verify proof and check signals + // require(signals[0] == 1337, "invalid signals"); // TODO no invalid signal check yet, which is fine since the zk proof does it + + // 3 public signals are the masked packed message bytes, 17 are the modulus. + uint256[] memory bodySignals = new uint256[](body_len); + uint256[] memory rsaModulusSignals = new uint256[](header_len); + for (uint256 i = 0; i < body_len; i++) bodySignals[i] = signals[i]; + for (uint256 i = body_len; i < msg_len - 1; i++) rsaModulusSignals[i - body_len] = signals[i]; + + // Check eth address committed to in proof matches msg.sender, to avoid doublespend and relayer-frontrunning-relayer-for-profit + // require(address(uint160(signals[addressIndexInSignals])) == msg.sender, "Invalid address"); + + // TODO: Note that this is buggy since it is malleable + require(!nullifier[a[0]], "Value is already true"); + nullifier[a[0]] = true; + + // Check from/to email domains are correct [in this case, only from domain is checked] + // Right now, we just check that any email was received from anyone at Twitter, which is good enough for now + // We will upload the version with these domain checks soon! + // require(_domainCheck(headerSignals), "Invalid domain"); + string memory fromEmail = convertPackedBytesToBytes(sliceArray(bodySignals, 0, 4), packSize * 4); + string memory recipientEmail = convertPackedBytesToBytes(sliceArray(bodySignals, 4, 8), packSize * 4); + string memory amount = convertPackedBytesToBytes(sliceArray(bodySignals, 8, 12), packSize * 4); + string memory currency = convertPackedBytesToBytes(sliceArray(bodySignals, 12, 16), packSize * 4); + + string memory domain = getDomainFromEmail(fromEmail); + console.log(domain); + // Verify that the public key for RSA matches the hardcoded one + for (uint i = body_len; i < msg_len - 1; i++) { + require(server.isVerified(domain, i - body_len, signals[i]), "Invalid: RSA modulus not matched"); + } + require(verifyProof(a, b, c, signals), "Invalid Proof"); // checks effects iteractions, this should come first + + // Effects: Send money + if (balance[fromEmail] == 0) { + balance[fromEmail] = 10; + } + balance[fromEmail] -= stringToUint(amount); + balance[recipientEmail] += stringToUint(amount); + } + + function bytes32ToString(bytes32 input) internal pure returns (string memory) { + uint256 i; + for (i = 0; i < 32 && input[i] != 0; i++) {} + bytes memory resultBytes = new bytes(i); + for (i = 0; i < 32 && input[i] != 0; i++) { + resultBytes[i] = input[i]; + } + return string(resultBytes); + } + + function sliceArray(uint256[] memory input, uint256 start, uint256 end) internal pure returns (uint256[] memory) { + require(start <= end && end <= input.length, "Invalid slice indices"); + uint256[] memory result = new uint256[](end - start); + for (uint256 i = start; i < end; i++) { + result[i - start] = input[i]; + } + return result; + } + + function stringToUint(string memory s) internal pure returns (uint256) { + bytes memory b = bytes(s); + uint256 result = 0; + for (uint i = 0; i < b.length; i++) { + if (b[i] >= 0x30 && b[i] <= 0x39) { + result = result * 10 + (uint256(uint8(b[i])) - 48); + } + } + return result; + } + + function getDomainFromEmail(string memory fromEmail) public pure returns (string memory) { + bytes memory emailBytes = bytes(fromEmail); + uint atIndex; + for (uint i = 0; i < emailBytes.length; i++) { + if (emailBytes[i] == "@") { + atIndex = i; + break; + } + } + + bytes memory domainBytes = new bytes(emailBytes.length - atIndex - 1); + for (uint j = 0; j < domainBytes.length; j++) { + domainBytes[j] = emailBytes[atIndex + 1 + j]; + } + return bytes32ToString(bytes32(bytes(domainBytes))); + } + + function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal { + require(from == address(0), "Cannot transfer - VerifiedEmail is soulbound"); + } +} diff --git a/src/contracts/src/domainEmailHandler.sol b/src/contracts/src/domainEmailHandler.sol deleted file mode 100644 index 86daf79ff..000000000 --- a/src/contracts/src/domainEmailHandler.sol +++ /dev/null @@ -1,199 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; -import "@openzeppelin/contracts/utils/Counters.sol"; -import "./base64.sol"; -import "./emailVerifier.sol"; - -contract VerifiedEmail is ERC721Enumerable, Verifier { - using Counters for Counters.Counter; - - Counters.Counter private tokenCounter; - - mapping(string => uint256[17]) public verifiedMailserverKeys; - - constructor() ERC721("VerifiedEmail", "VerifiedEmail") { - // Do dig TXT outgoing._domainkey.mit.edu to verify these. - // This is the base 2^121 representation of that key. - // Circom bigint: represent a = a[0] + a[1] * 2**n + .. + a[k - 1] * 2**(n * k) - verifiedMailserverKeys["mit.edu"][0] = 1362844382337595676288966927845048755; - verifiedMailserverKeys["mit.edu"][1] = 2051232190029042874602123094057641579; - verifiedMailserverKeys["mit.edu"][2] = 82180903948917831722803326838373315; - verifiedMailserverKeys["mit.edu"][3] = 2138065713701593539261187725956930213; - verifiedMailserverKeys["mit.edu"][4] = 2610113944250628639012720369418287474; - verifiedMailserverKeys["mit.edu"][5] = 947386626577810308124082119170513710; - verifiedMailserverKeys["mit.edu"][6] = 536038387946359789768371937196825655; - verifiedMailserverKeys["mit.edu"][7] = 2153576889316081585234167235144487709; - verifiedMailserverKeys["mit.edu"][8] = 1287226415982257719800023032828811922; - verifiedMailserverKeys["mit.edu"][9] = 1018106194828336360857712078662978863; - verifiedMailserverKeys["mit.edu"][10] = 2182121972991273871088583422676257732; - verifiedMailserverKeys["mit.edu"][11] = 824080356450773094427801032134768781; - verifiedMailserverKeys["mit.edu"][12] = 2160330005857484633191775197216017274; - verifiedMailserverKeys["mit.edu"][13] = 2447512561136956201144186872280764330; - verifiedMailserverKeys["mit.edu"][14] = 3006152463941257314249890518041106; - verifiedMailserverKeys["mit.edu"][15] = 820607402446306410974305086636012205; - verifiedMailserverKeys["mit.edu"][16] = 343542034344264361438243465247009; - } - - function getDesc(address origin, address sink, uint256 degree) private view returns (string memory) { - // convert address to string - string memory originStr = toString(origin); - string memory sinkStr = toString(sink); - // concatenate strings - string memory result = string(abi.encodePacked(sinkStr, "is ", toString(degree), "th degree friends with ", originStr)); - - return result; - } - - function tokenDesc(uint256 tokenId) public view returns (string memory) { - return string(abi.encodePacked(toString(tokenId))); - } - - function tokenURI(uint256 tokenId) public view override returns (string memory) { - string[3] memory parts; - parts[ - 0 - ] = ''; - - // parts[1] = tokenDesc(tokenId); - - parts[2] = ""; - - string memory output = string(abi.encodePacked(parts[0], parts[1], parts[2])); - - string memory json = Base64.encode( - bytes( - string( - abi.encodePacked( - '{"domain": "', - "mit.edu", - '", "tokenId": ', - toString(tokenId), - "}", - '", "description": "VerifiedEmailIDs are ZK verified proofs of email ownership on Ethereum. They only reveal your email domain, nothing about your identity. We can usee this to create trustless oracles, decentralized anonymous KYC, permission-free integration with every company, and secret three letter spying org leaks. VerifiedEmailIDs use ZK SNARKs to insinuate this debauchery. @personae_labs on Twitter for more alpha.", "image": "data:image/svg+xml;base64,', - Base64.encode(bytes(output)), - '"}' - ) - ) - ) - ); - output = string(abi.encodePacked("data:application/json;base64,", json)); - - return output; - } - - function toString(address account) public pure returns (string memory) { - return toString(abi.encodePacked(account)); - } - - function toString(uint256 value) public pure returns (string memory) { - return toString(abi.encodePacked(value)); - } - - function toString(bytes32 value) public pure returns (string memory) { - return toString(abi.encodePacked(value)); - } - - function toString(bytes memory data) public pure returns (string memory) { - bytes memory alphabet = "0123456789abcdef"; - - bytes memory str = new bytes(2 + data.length * 2); - str[0] = "0"; - str[1] = "x"; - for (uint256 i = 0; i < data.length; i++) { - str[2 + i * 2] = alphabet[uint256(uint8(data[i] >> 4))]; - str[3 + i * 2] = alphabet[uint256(uint8(data[i] & 0x0f))]; - } - return string(str); - } - - uint16 public constant msg_len = 21; - string domain = "mit.edu"; - - // Unpacks uint256s into bytes and then extracts the non-zero characters - // Only extracts contiguous non-zero characters and ensures theres only 2 such states, and they are identical - // function convert7PackedBytesToDupedBytes(uint256[msg_len] memory packedBytes) public pure returns (bytes memory unpackedBytes) { - // string memory domain = "mit.edu"; - // uint32 fromPointer = 0; - // uint32 toPointer = 0; - // uint32 domainLength = 0; - // uint8 state = 0; - // // bytes: 0 0 0 0 m i t . e d u 0 0 0 m i t . e d u 0 0 0 - // // state: 0 0 0 0 1 1 1 1 1 1 1 2 2 2 3 3 3 3 3 3 3 4 4 4 - // // Set domain pointers, not including the mailserver key - // for (uint32 i = 0; i < msg_len - 17; i++) { - // uint256 packedByte = packedBytes[i]; - // for (uint256 j = 0; j < 7; j++) { - // uint8 memory unpackedByte = packedByte & 0xff; - // if(unpackedByte != 0) { - // // nonzeroBytes.push(unpackedByte); - // if(state % 2 == 0) { - // state += 1; - // } - // } else { - // if(state % 2 == 1) { - // state += 1; - // } - // } - // if (signals[i] == 0) { - // if (signals[i - 1] != 0) { - // state += 1; - // if (state == 2) { - // domainLength = i - fromPointer; - // } else if (state == 4) { - // require( - // domainLength == i - toPointer, - // "Invalid domain length" - // ); - // } - // } - // continue; - // } else if (signals[i - 1] == 0) { - // // transition state - // state += 1; - // require(state <= 4, "Invalid state transition"); // 0 is the start, 1 is from, 2 is between from and to, 3 is to, 4 is after the to - // if (state == 1) { - // fromPointer = i; - // } else if (state == 3) { - // toPointer = i; - // } - // } - // packedByte = packedByte >> 8; - // } - // } - // // Check domains match - // bytes memory b = bytes(domain); - // for (uint32 i = 0; i < domainLength; i++) { - // require( - // signals[fromPointer + i] == signals[toPointer + i], - // "Invalid: domains do not match" - // ); - // require(signals[fromPointer + i] == domain[i], "Invalid: domain bytes don't match the string"); - // } - // return b; - // } - - function mint(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[msg_len] memory signals) public { - // require(signals[0] == 1337, "invalid signals"); // TODO no invalid signal check yet, which is fine since the zk proof does it - require(signals[0] == 0, "Invalid starting message character"); - // msg_len-17 public signals are the masked message bytes, 17 are the modulus. - // uint8[] memory message = convert7PackedBytesToDupedBytes(signals); - for (uint32 i = msg_len - 17; i < msg_len; i++) { - require(signals[i] == verifiedMailserverKeys[domain][i], "Invalid modulus not matched"); - } - - // ENSURE THE FOLLOWING IS UNCOMMENTED - require(verifyProof(a, b, c, signals), "Invalid Proof"); // checks effects iteractions, this should come first - - uint256 tokenId = tokenCounter.current() + 1; - _mint(msg.sender, tokenId); - tokenCounter.increment(); - } - - // TODO: This should override an ERC721 but doesn't - function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal pure { - revert("Cannot transfer - VerifiedEmail is soulbound"); - } -} diff --git a/src/contracts/src/test/TestTwitter.t.sol b/src/contracts/src/test/TestTwitter.t.sol index 235d1d814..8cb9d932f 100644 --- a/src/contracts/src/test/TestTwitter.t.sol +++ b/src/contracts/src/test/TestTwitter.t.sol @@ -1,8 +1,8 @@ pragma solidity ^0.8.0; import "forge-std/Test.sol"; import "forge-std/console.sol"; -import "../twitterEmailHandler.sol"; -import "../emailVerifier.sol"; +import "../TwitterEmailHandler.sol"; +import "../Groth16VerifierTwitter.sol"; contract TwitterUtilsTest is Test { address internal constant zero = 0x0000000000000000000000000000000000000000; @@ -20,7 +20,7 @@ contract TwitterUtilsTest is Test { // } // Should pass (note that there are extra 0 bytes, which are filtered out but should be noted in audits) - function testUnpack() public { + function testUnpack1() public { uint256[] memory packedBytes = new uint256[](3); packedBytes[0] = 29096824819513600; packedBytes[1] = 0; @@ -39,12 +39,26 @@ contract TwitterUtilsTest is Test { // ASCII should fit in 2 bytes but emails may not be ASCII assertEq(bytes32(bytes(byteList)), bytes32(bytes(intended_value))); console.logString(byteList); + } + function testUnpack2() public { + uint256[] memory packedBytes = new uint256[](3); packedBytes[0] = 28557011619965818; packedBytes[1] = 1818845549; packedBytes[2] = 0; - byteList = testVerifier.convertPackedBytesToBytes(packedBytes, 15); - intended_value = "zktestemail"; + string memory byteList = testVerifier.convertPackedBytesToBytes(packedBytes, 15); + string memory intended_value = "zktestemail"; + assertEq(bytes32(bytes(byteList)), bytes32(bytes(intended_value))); + console.logString(byteList); + } + + function testUnpack3() public { + uint256[] memory packedBytes = new uint256[](3); + packedBytes[0] = 28991922601197568; + packedBytes[1] = 24941; + packedBytes[2] = 0; + string memory byteList = testVerifier.convertPackedBytesToBytes(packedBytes, 30); + string memory intended_value = "zktestemail"; assertEq(bytes32(bytes(byteList)), bytes32(bytes(intended_value))); console.logString(byteList); } diff --git a/src/contracts/src/test/TestWallet.t.sol b/src/contracts/src/test/TestWallet.t.sol new file mode 100644 index 000000000..a91b1f599 --- /dev/null +++ b/src/contracts/src/test/TestWallet.t.sol @@ -0,0 +1,203 @@ +pragma solidity ^0.8.0; +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import "../WalletEmailHandler.sol"; +import "../Groth16VerifierWallet.sol"; + +contract WalletUtilsTest is Test { + address internal constant zero = 0x0000000000000000000000000000000000000000; + address constant VM_ADDR = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D; + VerifiedWalletEmail testVerifier; + Verifier proofVerifier; + + function setUp() public { + testVerifier = new VerifiedWalletEmail(); + proofVerifier = new Verifier(); + } + + function testUnpackIntoFloat1() public { + uint256[] memory packedBytes = new uint256[](5); + packedBytes[0] = 0; + packedBytes[1] = 0; + packedBytes[2] = 0; + packedBytes[3] = 13661285; + string memory byteList = testVerifier.convertPackedBytesToBytes(packedBytes, 30); + string memory intended_value = "eth"; + assertEq(bytes32(bytes(byteList)), bytes32(bytes(intended_value))); + console.logString(byteList); + } + + function testUnpackIntoFloat2() public { + uint256[] memory packedBytes = new uint256[](4); + packedBytes[0] = 30515164652858234; + packedBytes[1] = 18147879272211830; + packedBytes[2] = 27917065853693287; + packedBytes[3] = 28015; + string memory byteList = testVerifier.convertPackedBytesToBytes(packedBytes, 30); + string memory intended_value = "zkemailverify@gmail.com"; + assertEq(bytes32(bytes(byteList)), bytes32(bytes(intended_value))); + console.logString(byteList); + } + + function testUnpackIntoFloat3() public { + uint256[] memory packedBytes = new uint256[](4); + packedBytes[0] = 0; + packedBytes[1] = 3485236; + packedBytes[2] = 0; + packedBytes[3] = 0; + string memory byteList = testVerifier.convertPackedBytesToBytes(packedBytes, 30); + string memory intended_value = "4.5"; + assertEq(stringToUint(byteList), 4); + assertEq(bytes32(bytes(byteList)), bytes32(bytes(intended_value))); + console.logString(byteList); + } + + function stringToUint(string memory s) internal pure returns (uint256) { + bytes memory b = bytes(s); + uint256 result = 0; + for (uint i = 0; i < b.length; i++) { + if (b[i] >= 0x30 && b[i] <= 0x39) { + result = result * 10 + (uint256(uint8(b[i])) - 48); + } + } + return result; + } + + function testUnpackIntoFloat4() public { + uint256[] memory packedBytes = new uint256[](4); + packedBytes[0] = 30515164652858234; + packedBytes[1] = 14207229598262646; + packedBytes[2] = 13067048790615872; + packedBytes[3] = 7171939; + string memory byteList = testVerifier.convertPackedBytesToBytes(packedBytes, 30); + string memory intended_value = "zkemailverify2@gmail.com"; + assertEq(bytes32(bytes(byteList)), bytes32(bytes(intended_value))); + console.logString(byteList); + } + + // Should pass (note that there are extra 0 bytes, which are filtered out but should be noted in audits) + function testVerifyWalletEmail() public { + uint256[34] memory publicSignals; + publicSignals[0] = 30515164652858234; + publicSignals[1] = 14207229598262646; + publicSignals[2] = 13067048790615872; + publicSignals[3] = 7171939; + publicSignals[4] = 0; + publicSignals[5] = 3485236; + publicSignals[6] = 0; + publicSignals[7] = 0; + publicSignals[8] = 0; + publicSignals[9] = 0; + publicSignals[10] = 13661285; + publicSignals[11] = 30515164652858234; + publicSignals[12] = 18147879272211830; + publicSignals[13] = 27917065853693287; + publicSignals[14] = 28015; + publicSignals[15] = 0; + publicSignals[16] = 2645260732387577900369388087711111123; + publicSignals[17] = 2332356685544126002119529566553287568; + publicSignals[18] = 587306946802222480578301599869128605; + publicSignals[19] = 1506808391343308562602228807782956759; + publicSignals[20] = 346696857027646434280628892032962406; + publicSignals[21] = 1655371642328152796841392591809876356; + publicSignals[22] = 773654757689631205903545947464515700; + publicSignals[23] = 137546842031326636154929265514533208; + publicSignals[24] = 979104436480501594376401576155183314; + publicSignals[25] = 1231402749194646866996172591430155068; + publicSignals[26] = 1573385231473380013164181608611759098; + publicSignals[27] = 1199794061179553911325952711127005960; + publicSignals[28] = 1393369642957971131987926230229916984; + publicSignals[29] = 2610100650498432208787557818514105421; + publicSignals[30] = 1405475120223887084339881602469286332; + publicSignals[31] = 2000538708964654339221687925776343058; + publicSignals[32] = 3483697379198011592407370076533025; + publicSignals[33] = 0; + // TODO switch order + uint256[2] memory proof_a = [ + 18214528451748025070455293058606558684367776249349482399993204103864741723468, + 15003530197647463595718037429164132062637106744660222086396269550328064261424 + ]; + // Note: you need to swap the order of the two elements in each subarray + uint256[2][2] memory proof_b = [ + [6461911610358766053365043908758394834732672681413987884242698462904724197255, 342103975494932482608081876029483576044074727035168137477391964391537410934], + [18351039964982209778799207158064219024562949371673722720718374575366986849311, 4669785024601609291633792167221088192727471283005169123961871153351390329210] + ]; + uint256[2] memory proof_c = [ + 17308091971421169481892128502517801279695749002269857786558424203436590932091, + 14587778590638321976005513090859474748106449498450192078465868665769372103254 + ]; + + // Test proof verification + bool verified = proofVerifier.verifyProof(proof_a, proof_b, proof_c, publicSignals); + assertEq(verified, true); + + // Test mint after spoofing msg.sender + // Vm vm = Vm(VM_ADDR); + // vm.startPrank(0x0000000000000000000000000000000000000001); + // testVerifier.transfer(proof_a, proof_b, proof_c, publicSignals); + // vm.stopPrank(); + } + + // Should pass (note that there are extra 0 bytes, which are filtered out but should be noted in audits) + function testTransferWalletEmail2() public { + uint256[34] memory publicSignals; + publicSignals[0] = 30515164652858234; + publicSignals[1] = 14207229598262646; + publicSignals[2] = 13067048790615872; + publicSignals[3] = 7171939; + publicSignals[4] = 0; + publicSignals[5] = 3485236; + publicSignals[6] = 0; + publicSignals[7] = 0; + publicSignals[8] = 0; + publicSignals[9] = 0; + publicSignals[10] = 13661285; + publicSignals[11] = 30515164652858234; + publicSignals[12] = 18147879272211830; + publicSignals[13] = 27917065853693287; + publicSignals[14] = 28015; + publicSignals[15] = 0; + publicSignals[16] = 2645260732387577900369388087711111123; + publicSignals[17] = 2332356685544126002119529566553287568; + publicSignals[18] = 587306946802222480578301599869128605; + publicSignals[19] = 1506808391343308562602228807782956759; + publicSignals[20] = 346696857027646434280628892032962406; + publicSignals[21] = 1655371642328152796841392591809876356; + publicSignals[22] = 773654757689631205903545947464515700; + publicSignals[23] = 137546842031326636154929265514533208; + publicSignals[24] = 979104436480501594376401576155183314; + publicSignals[25] = 1231402749194646866996172591430155068; + publicSignals[26] = 1573385231473380013164181608611759098; + publicSignals[27] = 1199794061179553911325952711127005960; + publicSignals[28] = 1393369642957971131987926230229916984; + publicSignals[29] = 2610100650498432208787557818514105421; + publicSignals[30] = 1405475120223887084339881602469286332; + publicSignals[31] = 2000538708964654339221687925776343058; + publicSignals[32] = 3483697379198011592407370076533025; + publicSignals[33] = 0; + // TODO switch order + uint256[2] memory proof_a = [ + 18214528451748025070455293058606558684367776249349482399993204103864741723468, + 15003530197647463595718037429164132062637106744660222086396269550328064261424 + ]; + // Note: you need to swap the order of the two elements in each subarray + uint256[2][2] memory proof_b = [ + [6461911610358766053365043908758394834732672681413987884242698462904724197255, 342103975494932482608081876029483576044074727035168137477391964391537410934], + [18351039964982209778799207158064219024562949371673722720718374575366986849311, 4669785024601609291633792167221088192727471283005169123961871153351390329210] + ]; + uint256[2] memory proof_c = [ + 17308091971421169481892128502517801279695749002269857786558424203436590932091, + 14587778590638321976005513090859474748106449498450192078465868665769372103254 + ]; + + // Test proof verification + bool verified = proofVerifier.verifyProof(proof_a, proof_b, proof_c, publicSignals); + assertEq(verified, true); + + // Test mint after spoofing msg.sender + Vm vm = Vm(VM_ADDR); + vm.startPrank(0x0000000000000000000000000000000000000001); + testVerifier.transfer(proof_a, proof_b, proof_c, publicSignals); + vm.stopPrank(); + } +} diff --git a/src/contracts/src/twitterEmailHandler.sol b/src/contracts/src/twitterEmailHandler.sol index 2c6a2aa4a..a1556f4b8 100644 --- a/src/contracts/src/twitterEmailHandler.sol +++ b/src/contracts/src/twitterEmailHandler.sol @@ -6,8 +6,9 @@ import "@openzeppelin/contracts/utils/Strings.sol"; import "@openzeppelin/contracts/utils/Counters.sol"; import "forge-std/console.sol"; // import "./base64.sol"; +import "./HexStrings.sol"; import "./NFTSVG.sol"; -import "./emailVerifier.sol"; +import "./Groth16VerifierTwitter.sol"; contract VerifiedTwitterEmail is ERC721Enumerable, Verifier { using Counters for Counters.Counter; @@ -30,6 +31,9 @@ contract VerifiedTwitterEmail is ERC721Enumerable, Verifier { // This is the base 2^121 representation of that key. // Circom bigint: represent a = a[0] + a[1] * 2**n + .. + a[k - 1] * 2**(n * k) require(rsa_modulus_chunks_len + body_len + 1 == msg_len, "Variable counts are wrong!"); + + // TODO: Create a type that takes in a raw RSA key, the bit count, + // and whether or not its base64 encoded, and converts it to either 8 or 16 signals verifiedMailserverKeys["twitter.com"][0] = 1634582323953821262989958727173988295; verifiedMailserverKeys["twitter.com"][1] = 1938094444722442142315201757874145583; verifiedMailserverKeys["twitter.com"][2] = 375300260153333632727697921604599470; @@ -133,7 +137,7 @@ contract VerifiedTwitterEmail is ERC721Enumerable, Verifier { string memory returnValue = string(nonzeroBytesArray); require(state == 2, "Invalid final state of packed bytes in email"); // console.log("Characters in username: ", nonzeroBytesArrayIndex); - require(nonzeroBytesArrayIndex <= maxBytes, "Twitter username more than 15 chars!"); + require(nonzeroBytesArrayIndex <= maxBytes, "Packed bytes more than allowed max length!"); return returnValue; // Have to end at the end of the email -- state cannot be 1 since there should be an email footer } diff --git a/src/contracts/src/wallet-create2/Wallet.sol b/src/contracts/src/wallet-create2/Wallet.sol new file mode 100644 index 000000000..bea4c02e0 --- /dev/null +++ b/src/contracts/src/wallet-create2/Wallet.sol @@ -0,0 +1,10 @@ +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract Wallet { + function transferERC20(address tokenAddress, address to, uint256 amount) public { + require(msg.sender == address(this), "Caller is not the wallet"); + IERC20(tokenAddress).transfer(to, amount); + } +} diff --git a/src/contracts/src/wallet-create2/WalletDeployer.sol b/src/contracts/src/wallet-create2/WalletDeployer.sol new file mode 100644 index 000000000..671f3e296 --- /dev/null +++ b/src/contracts/src/wallet-create2/WalletDeployer.sol @@ -0,0 +1,49 @@ +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/utils/Create2.sol"; +import "@openzeppelin/contracts/utils/Counters.sol"; +import "./Wallet.sol"; + +contract WalletDeployer { + using Counters for Counters.Counter; + Counters.Counter private _walletIndex; + + address private immutable _owner; + bytes32 private constant _saltPrefix = "WalletDeployer"; + bytes32 private _bytecodeHash; + + mapping(uint256 => address) public wallets; + + constructor() { + _owner = msg.sender; + _bytecodeHash = keccak256(type(Wallet).creationCode); + } + + modifier onlyOwner() { + require(msg.sender == _owner, "Caller is not the owner"); + _; + } + + function deployWallet() public onlyOwner returns (address wallet) { + bytes32 salt = keccak256(abi.encodePacked(_saltPrefix, _walletIndex.current())); + wallet = Create2.deploy(0, salt, type(Wallet).creationCode); + wallets[_walletIndex.current()] = wallet; + _walletIndex.increment(); + } + + function getWalletAddress(uint256 index) public view returns (address wallet) { + bytes32 salt = keccak256(abi.encodePacked(_saltPrefix, index)); + wallet = Create2.computeAddress(salt, _bytecodeHash); + } + + function moveERC20(uint256 sourceIndex, uint256 destinationIndex, address tokenAddress, uint256 amount) public onlyOwner { + require(wallets[sourceIndex] != address(0), "Source wallet not deployed"); + require(wallets[destinationIndex] != address(0), "Destination wallet not deployed"); + + Wallet sourceWallet = Wallet(wallets[sourceIndex]); + Wallet destinationWallet = Wallet(wallets[destinationIndex]); + + sourceWallet.transferERC20(tokenAddress, address(destinationWallet), amount); + } +} diff --git a/src/helpers/constants.ts b/src/helpers/constants.ts index 7103a3d0e..0e8614023 100644 --- a/src/helpers/constants.ts +++ b/src/helpers/constants.ts @@ -21,5 +21,6 @@ export const CIRCOM_LEVELS = 30; // This is the string that comes right before the target string in the email. Ideally as close to the end of the email as possible. export const STRING_PRESELECTOR = "email was meant for @"; + export const STRING_PRESELECTOR_AIRBNB = "Thanks for providing a government ID"; export const STRING_PRESELECTOR_COINBASE = "You're ready to invest!"; \ No newline at end of file diff --git a/src/helpers/shaHash.ts b/src/helpers/shaHash.ts index a8065e1c3..91f695171 100644 --- a/src/helpers/shaHash.ts +++ b/src/helpers/shaHash.ts @@ -28,6 +28,9 @@ export async function sha256Pad(prehash_prepad_m: Uint8Array, maxShaBytes: numbe while (prehash_prepad_m.length < maxShaBytes) { prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, int64toBytes(0)); } - assert(prehash_prepad_m.length === maxShaBytes, "Padding to max length did not complete properly!"); + assert( + prehash_prepad_m.length === maxShaBytes, + `Padding to max length did not complete properly! Your padded message is ${prehash_prepad_m.length} long but max is ${maxShaBytes}!` + ); return [prehash_prepad_m, messageLen]; } diff --git a/src/scripts/generate_input.ts b/src/scripts/generate_input.ts index 8fd8e8477..c0613c989 100644 --- a/src/scripts/generate_input.ts +++ b/src/scripts/generate_input.ts @@ -17,11 +17,31 @@ import { CIRCOM_FIELD_MODULUS, MAX_HEADER_PADDED_BYTES, MAX_BODY_PADDED_BYTES, S import { shaHash, partialSha, sha256Pad } from "../../src/helpers/shaHash"; import { dkimVerify } from "../../src/helpers/dkim"; import * as fs from "fs"; +import { stubObject } from "lodash"; +import * as yargs from "yargs"; var Cryo = require("cryo"); const pki = require("node-forge").pki; // const email_file = "monia_email.eml"; // "./test_email.txt", "./twitter_msg.eml", kaylee_phone_number_email_twitter -const email_file = "./nathan_twitter_email.eml"; +const email = yargs + .option("email_file", { + alias: "e", + description: "Path to email file", + type: "string", + default: "test_sendgrid.eml", + }) + .option("nonce", { + alias: "n", + description: "Nonce to disambiguate input/output files (optional, only useful for monolithic server side provers)", + type: "string", + default: null, + }) + .help() + .alias("help", "h").argv; + +const email_file = email.email_file; +const nonce = email.nonce; + export interface ICircuitInputs { modulus?: string[]; signature?: string[]; @@ -39,7 +59,9 @@ export interface ICircuitInputs { address_plus_one?: string; twitter_username_idx?: string; email_from_idx?: string; - email_to_idx?: string; + amount_idx?: string; + currency_idx?: string; + recipient_idx?: string; } enum CircuitType { @@ -47,6 +69,7 @@ enum CircuitType { SHA = "sha", TEST = "test", EMAIL = "email", + EMAILWALLET = "emailwallet", } async function findSelector(a: Uint8Array, selector: number[]): Promise { @@ -101,6 +124,9 @@ export async function getCircuitInputs( const [messagePadded, messagePaddedLen] = await sha256Pad(prehashBytesUnpadded, MAX_HEADER_PADDED_BYTES); const [bodyPadded, bodyPaddedLen] = await sha256Pad(body, Math.max(MAX_BODY_PADDED_BYTES, calc_length)); + // Convet messagePadded to string to print the specific header data that is signed + console.log(JSON.stringify(message).toString()); + // Ensure SHA manual unpadded is running the correct function const shaOut = await partialSha(messagePadded, messagePaddedLen); assert((await Uint8ArrayToString(shaOut)) === (await Uint8ArrayToString(Uint8Array.from(await shaHash(prehashBytesUnpadded)))), "SHA256 calculation did not match!"); @@ -138,10 +164,23 @@ export async function getCircuitInputs( const address_plus_one = (bytesToBigInt(fromHex(eth_address)) + 1n).toString(); const USERNAME_SELECTOR = Buffer.from(STRING_PRESELECTOR); - const email_from_idx = Buffer.from(prehash_message_string).indexOf("from:").toString(); - const email_to_idx = Buffer.from(prehash_message_string).indexOf("to:").toString(); + + function trimStrByStr(str: string, substr: string) { + const index = str.indexOf(substr); + if (index === -1) { + return str; + } + return str.slice(index + substr.length, str.length); + } + + let raw_header = Buffer.from(prehash_message_string).toString(); + const email_from_idx = raw_header.length - trimStrByStr(trimStrByStr(raw_header, "from:"), "<").length; + let email_subject = trimStrByStr(raw_header, "subject:"); + const amount_idx = raw_header.length - trimStrByStr(email_subject, "end ").length; + const currency_idx = raw_header.length - trimStrByStr(trimStrByStr(email_subject, "end "), " ").length; + const recipient_idx = raw_header.length - trimStrByStr(email_subject, "to ").length; const twitter_username_idx = (Buffer.from(bodyRemaining).indexOf(USERNAME_SELECTOR) + USERNAME_SELECTOR.length).toString(); - console.log("Twitter Username idx: ", twitter_username_idx); + console.log("Indexes into header string are: ", email_from_idx, amount_idx, currency_idx, recipient_idx, twitter_username_idx); if (circuit === CircuitType.RSA) { circuitInputs = { @@ -165,6 +204,20 @@ export async function getCircuitInputs( // email_from_idx, // email_to_idx, }; + } else if (circuit === CircuitType.EMAILWALLET) { + circuitInputs = { + in_padded, + modulus, + signature, + in_len_padded_bytes, + address, + address_plus_one, + body_hash_idx, + email_from_idx: email_from_idx.toString(), + amount_idx: amount_idx.toString(), + currency_idx: currency_idx.toString(), + recipient_idx: recipient_idx.toString(), + }; } else { assert(circuit === CircuitType.SHA, "Invalid circuit type"); circuitInputs = { @@ -179,8 +232,15 @@ export async function getCircuitInputs( }; } -export async function generate_inputs(email: Buffer, eth_address: string): Promise { - var result; +// Nonce is useful to disambiguate files for input/output when calling from the command line, it is usually null or hash(email) +export async function generate_inputs(raw_email: Buffer | string, eth_address: string, nonce_raw: number | null | string = null): Promise { + const nonce = typeof nonce_raw == "string" ? nonce_raw.trim() : nonce_raw; + + var result, email: Buffer; + if (typeof raw_email === "string") { + email = Buffer.from(raw_email); + } else email = raw_email; + console.log("DKIM verification starting"); result = await dkimVerify(email); if (!result.results[0]) { @@ -209,20 +269,24 @@ export async function generate_inputs(email: Buffer, eth_address: string): Promi let message = result.results[0].status.signature_header; let body = result.results[0].body; let body_hash = result.results[0].bodyHash; - let circuitType = CircuitType.EMAIL; + let circuitType = CircuitType.EMAILWALLET; let pubkey = result.results[0].publicKey; const pubKeyData = pki.publicKeyFromPem(pubkey.toString()); let modulus = BigInt(pubKeyData.n.toString()); let fin_result = await getCircuitInputs(sig, modulus, message, body, body_hash, eth_address, circuitType); + if (nonce !== null) { + console.log(`Writing to ../input_wallet_${nonce}.json`); + fs.writeFileSync(`../input_wallet_${nonce}.json`, JSON.stringify(fin_result.circuitInputs), { flag: "w" }); + } return fin_result.circuitInputs; } async function do_generate() { - const email = fs.readFileSync(email_file); + const email = fs.readFileSync(email_file.trim()); console.log(email); - const gen_inputs = await generate_inputs(email, "0x0000000000000000000000000000000000000000"); - // console.log(JSON.stringify(gen_inputs)); + const gen_inputs = await generate_inputs(email, "0x0000000000000000000000000000000000000000", nonce); + console.log(JSON.stringify(gen_inputs)); return gen_inputs; } @@ -255,6 +319,7 @@ if (typeof require !== "undefined" && require.main === module) { // debug_file(); const circuitInputs = do_generate(); console.log("Writing to file..."); - circuitInputs.then((inputs) => fs.writeFileSync(`./circuits/inputs/input_twitter.json`, JSON.stringify(inputs), { flag: "w" })); - // gen_test(); + if (nonce == null) { + circuitInputs.then((inputs) => fs.writeFileSync(`./circuits/inputs/input_wallet.json`, JSON.stringify(inputs), { flag: "w" })); + } // gen_test(); } diff --git a/temp.py b/temp.py index 11ba8a875..67e545a50 100644 --- a/temp.py +++ b/temp.py @@ -1,4 +1,3 @@ -# l = ["101","99","111","114","97","116","105","111","110","58","110","111","110","101","59","45","119","101","98","107","105","116","61","13","10","45","102","111","110","116","45","115","109","111","111","116","104","105","110","103","58","97","110","116","105","97","108","105","97","115","101","100","59","34","62","32","84","104","105","115","32","101","109","97","105","108","32","119","97","115","32","109","101","97","110","116","32","102","111","114","32","64","74","117","115","116","105","110","84","53","56","52","55","55","49","50","51","32","60","47","61","13","10","115","112","97","110","62","32","60","47","116","100","62","13","10","60","47","116","114","62","13","10","60","116","114","62","13","10","60","116","100","32","104","101","105","103","104","116","61","51","68","34","54","34","32","115","116","121","108","101","61","51","68","34","104","101","105","103","104","116","58","54","112","120","59","108","105","110","101","45","104","101","105","103","104","116","58","49","112","120","59","102","111","110","116","45","115","105","122","101","58","49","112","120","59","112","97","100","100","105","110","103","58","61","13","10","48","59","109","97","114","103","105","110","58","48","59","108","105","110","101","45","104","101","105","103","104","116","58","49","112","120","59","102","111","110","116","45","115","105","122","101","58","49","112","120","59","34","62","60","47","116","100","62","13","10","60","47","116","114","62","13","10","60","116","114","62","13","10","60","116","100","32","97","108","105","103","110","61","51","68","34","99","101","110","116","101","114","34","32","115","116","121","108","101","61","51","68","34","112","97","100","100","105","110","103","58","48","59","109","97","114","103","105","110","58","48","59","108","105","110","101","45","104","101","105","103","104","116","58","49","112","120","59","102","111","110","116","45","115","105","122","101","58","61","13","10","49","112","120","59","34","62","32","60","115","112","97","110","32","99","108","97","115","115","61","51","68","34","97","100","100","114","101","115","115","34","62","32","60","97","32","104","114","101","102","61","51","68","34","35","34","32","115","116","121","108","101","61","51","68","34","116","101","120","116","45","100","101","99","111","114","97","116","105","111","110","58","110","111","110","101","61","13","10","59","98","111","114","100","101","114","45","115","116","121","108","101","58","110","111","110","101","59","98","111","114","100","101","114","58","48","59","112","97","100","100","105","110","103","58","48","59","109","97","114","103","105","110","58","48","59","102","111","110","116","45","102","97","109","105","108","121","58","39","72","101","108","118","101","116","105","99","97","78","101","117","101","39","44","61","13","10","32","39","72","101","108","118","101","116","105","99","97","32","78","101","117","101","39","44","32","72","101","108","118","101","116","105","99","97","44","32","65","114","105","97","108","44","32","115","97","110","115","45","115","101","114","105","102","59","45","119","101","98","107","105","116","45","102","111","110","116","45","115","109","111","111","116","104","105","110","103","58","97","110","116","105","61","13","10","97","108","105","97","115","101","100","59","99","111","108","111","114","58","35","56","56","57","57","65","54","59","102","111","110","116","45","115","105","122","101","58","49","50","112","120","59","112","97","100","100","105","110","103","58","48","112","120","59","109","97","114","103","105","110","58","48","112","120","59","102","111","110","116","45","119","101","105","103","104","116","58","110","111","114","61","13","10","109","97","108","59","108","105","110","101","45","104","101","105","103","104","116","58","49","50","112","120","59","99","117","114","115","111","114","58","100","101","102","97","117","108","116","59","34","62","84","119","105","116","116","101","114","44","32","73","110","99","46","32","49","51","53","53","32","77","97","114","107","101","116","32","83","116","114","101","101","116","44","32","83","117","105","61","13","10","116","101","32","57","48","48","32","83","97","110","32","70","114","97","110","99","105","115","99","111","44","32","67","65","32","57","52","49","48","51","60","47","97","62","32","60","47","115","112","97","110","62","32","60","47","116","100","62","13","10","60","47","116","114","62","13","10","60","116","114","62","13","10","60","116","100","32","104","101","105","103","104","116","61","51","68","34","55","50","34","32","115","116","121","108","101","61","51","68","34","104","101","105","103","104","116","58","55","50","112","120","59","112","97","100","100","105","110","103","58","48","59","109","97","114","103","105","110","58","48","59","108","105","110","101","45","104","101","105","103","104","116","58","49","112","120","59","102","61","13","10","111","110","116","45","115","105","122","101","58","49","112","120","59","34","62","60","47","116","100","62","13","10","60","47","116","114","62","13","10","60","47","116","98","111","100","121","62","13","10","60","47","116","97","98","108","101","62","13","10","60","33","45","45","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","32","101","110","100","32","102","111","111","116","101","114","32","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","47","45","45","62","32","60","47","116","100","62","13","10","60","47","116","114","62","13","10","60","47","116","98","111","100","121","62","13","10","60","47","116","97","98","108","101","62","13","10","60","47","98","111","100","121","62","13","10","60","47","104","116","109","108","62","13","10","45","45","45","45","45","45","61","95","80","97","114","116","95","50","53","50","53","53","48","56","51","50","95","49","49","49","50","56","52","57","48","48","50","46","49","54","56","48","48","48","52","54","53","51","56","56","56","45","45","13","10","128","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","1","188","40","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0"] -# l = ["99","111","110","116","101","110","116","45","116","121","112","101","58","109","117","108","116","105","112","97","114","116","47","97","108","116","101","114","110","97","116","105","118","101","59","32","98","111","117","110","100","97","114","121","61","52","54","101","99","98","48","49","101","55","49","98","101","97","101","55","51","51","50","51","102","56","100","55","54","52","57","102","49","98","102","102","101","99","48","55","49","98","56","57","50","53","55","57","54","56","55","102","57","50","51","100","102","54","102","99","57","98","98","57","55","13","10","102","114","111","109","58","65","105","114","98","110","98","32","60","97","117","116","111","109","97","116","101","100","64","97","105","114","98","110","98","46","99","111","109","62","13","10","109","105","109","101","45","118","101","114","115","105","111","110","58","49","46","48","13","10","115","117","98","106","101","99","116","58","84","104","97","110","107","115","32","102","111","114","32","112","114","111","118","105","100","105","110","103","32","97","32","103","111","118","101","114","110","109","101","110","116","32","73","68","13","10","120","45","102","101","101","100","98","97","99","107","45","105","100","58","49","54","57","51","48","51","58","83","71","13","10","116","111","58","97","97","121","117","115","104","103","117","112","116","97","48","53","64","103","109","97","105","108","46","99","111","109","13","10","100","107","105","109","45","115","105","103","110","97","116","117","114","101","58","118","61","49","59","32","97","61","114","115","97","45","115","104","97","50","53","54","59","32","99","61","114","101","108","97","120","101","100","47","114","101","108","97","120","101","100","59","32","100","61","101","109","97","105","108","46","97","105","114","98","110","98","46","99","111","109","59","32","104","61","99","111","110","116","101","110","116","45","116","121","112","101","58","102","114","111","109","58","109","105","109","101","45","118","101","114","115","105","111","110","58","115","117","98","106","101","99","116","58","120","45","102","101","101","100","98","97","99","107","45","105","100","58","116","111","59","32","115","61","115","50","48","49","53","48","52","50","56","59","32","98","104","61","55","71","103","108","107","90","57","77","76","87","76","75","103","80","66","71","55","81","117","101","50","104","90","48","74","121","57","75","72","97","117","90","83","109","84","102","71","119","89","119","82","49","56","61","59","32","98","61","128","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","14","64","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0"] -l = [60,33,68,79,67,84,89,80,69,32,104,116,109,108,32,80,85,66,76,73,67,32,34,45,47,47,87,51,67,47,47,68,84,68,32,88,72,84,77,76,32,49,46,48,32,84,114,97,110,115,105,116,105,111,110,97,108,47,47,69,78,34,32,34,104,116,116,112,58,47,47,119,119,119,46,119,51,46,111,114,103,47,84,82,47,120,104,116,109,108,49,47,68,84,68,47,120,104,116,109,108,49,45,116,114,97,110,115,105,116,105,111,110,97,108,46,100,116,100,34,62,13,10,60,104,116,109,108,62,13,10,60,104,101,97,100,62,13,10,32,32,60,109,101,116,97,32,104,116,116,112,45,101,113,117,105,118,61,34,67,111,110,116,101,110,116,45,84,121,112,101,34,32,99,111,110,116,101,110,116,61,34,116,101,120,116,47,104,116,109,108,59,32,99,104,97,114,115,101,116,61,85,84,70,45,56,34,62,13,10,32,32,60,116,105,116,108,101,62,67,111,105,110,98,97,115,101,60,47,116,105,116,108,101,62,13,10,60,47,104,101,97,100,62,13,10,13,10,13,10,60,98,111,100,121,32,115,116,121,108,101,61,34,33,105,109,112,111,114,116,97,110,116,32,98,97,99,107,103,114,111,117,110,100,45,99,111,108,111,114,58,32,35,70,50,70,53,70,55,59,32,104,101,105,103,104,116,58,32,49,48,48,37,32,33,105,109,112,111,114,116,97,110,116,59,32,109,97,114,103,105,110,58,32,48,59,32,112,97,100,100,105,110,103,58,32,48,59,32,119,105,100,116,104,58,32,49,48,48,37,32,33,105,109,112,111,114,116,97,110,116,59,34,32,98,103,99,111,108,111,114,61,34,35,70,50,70,53,70,55,34,62,13,10,32,32,60,33,45,45,32,72,73,68,68,69,78,32,80,82,69,72,69,65,68,69,82,32,84,69,88,84,32,45,45,62,13,10,32,32,60,100,105,118,32,115,116,121,108,101,61,34,99,111,108,111,114,58,32,35,102,101,102,101,102,101,59,32,100,105,115,112,108,97,121,58,32,110,111,110,101,59,32,102,111,110,116,45,102,97,109,105,108,121,58,32,79,112,101,110,32,83,97,110,115,44,32,72,101,108,118,101,116,105,99,97,44,32,65,114,105,97,108,44,32,115,97,110,115,45,115,101,114,105,102,59,32,102,111,110,116,45,115,105,122,101,58,32,49,112,120,59,32,108,105,110,101,45,104,101,105,103,104,116,58,32,49,112,120,59,32,109,97,120,45,104,101,105,103,104,116,58,32,48,112,120,59,32,109,97,120,45,119,105,100,116,104,58,32,48,112,120,59,32,111,112,97,99,105,116,121,58,32,48,59,32,111,118,101,114,102,108,111,119,58,32,104,105,100,100,101,110,59,34,62,13,10,32,32,32,32,32,32,67,111,105,110,98,97,115,101,32,109,97,107,101,115,32,100,105,103,105,116,97,108,32,99,117,114,114,101,110,99,121,32,101,97,115,121,33,13,10,32,32,60,47,100,105,118,62,13,10,13,10,32,32,60,116,97,98,108,101,32,105,100,61,34,109,97,105,110,34,32,119,105,100,116,104,61,34,49,48,48,37,34,32,104,101,105,103,104,116,61,34,49,48,48,37,34,32,99,101,108,108,112,97,100,100,105,110,103,61,34,48,34,32,99,101,108,108,115,112,97,99,105,110,103,61,34,48,34,32,98,111,114,100,101,114,61,34,48,34,62,13,10,32,32,32,32,60,116,98,111,100,121,62,13,10,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,60,116,100,32,118,97,108,105,103,110,61,34,116,111,112,34,32,97,108,105,103,110,61,34,99,101,110,116,101,114,34,32,98,103,99,111,108,111,114,61,34,35,51,98,52,97,54,57,34,32,115,116,121,108,101,61,34,98,97,99,107,103,114,111,117,110,100,58,32,108,105,110,101,97,114,45,103,114,97,100,105,101,110,116,40,45,49,56,48,100,101,103,44,32,35,48,53,55,48,68,52,32,48,37,44,32,35,48,48,54,48,66,65,32,49,48,48,37,41,32,48,37,32,48,37,32,47,32,99,111,118,101,114,59,32,112,97,100,100,105,110,103,58,32,48,32,49,53,112,120,59,34,62,13,10,32,32,32,32,32,32,32,32,32,32,60,116,97,98,108,101,32,99,108,97,115,115,61,34,105,110,110,101,114,109,97,105,110,34,32,99,101,108,108,112,97,100,100,105,110,103,61,34,48,34,32,119,105,100,116,104,61,34,49,48,48,37,34,32,99,101,108,108,115,112,97,99,105,110,103,61,34,48,34,32,98,111,114,100,101,114,61,34,48,34,32,97,108,105,103,110,61,34,99,101,110,116,101,114,34,32,115,116,121,108,101,61,34,109,97,114,103,105,110,58,48,32,97,117,116,111,59,32,116,97,98,108,101,45,108,97,121,111,117,116,58,32,102,105,120,101,100,59,32,98,111,114,100,101,114,45,99,111,108,108,97,112,115,101,58,32,99,111,108,108,97,112,115,101,32,33,105,109,112,111,114,116,97,110,116,59,32,109,97,120,45,119,105,100,116,104,58,32,54,48,48,112,120,59,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,60,116,98,111,100,121,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,60,33,45,45,32,83,84,65,82,84,32,111,102,32,77,65,73,76,32,67,111,110,116,101,110,116,32,45,45,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,97,108,105,103,110,61,34,99,101,110,116,101,114,34,32,118,97,108,105,103,110,61,34,116,111,112,34,32,119,105,100,116,104,61,34,49,48,48,37,34,62,13,10,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,33,45,45,32,76,111,103,111,32,115,116,97,114,116,32,104,101,114,101,32,45,45,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,97,98,108,101,32,99,108,97,115,115,61,34,108,111,103,111,34,32,119,105,100,116,104,61,34,49,48,48,37,34,32,99,101,108,108,112,97,100,100,105,110,103,61,34,48,34,32,99,101,108,108,115,112,97,99,105,110,103,61,34,48,34,32,98,111,114,100,101,114,61,34,48,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,98,111,100,121,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,62,60,116,100,32,97,108,105,103,110,61,34,99,101,110,116,101,114,34,32,118,97,108,105,103,110,61,34,116,111,112,34,32,115,116,121,108,101,61,34,45,109,115,45,116,101,120,116,45,115,105,122,101,45,97,100,106,117,115,116,58,32,49,48,48,37,59,32,45,119,101,98,107,105,116,45,116,101,120,116,45,115,105,122,101,45,97,100,106,117,115,116,58,32,49,48,48,37,59,32,109,115,111,45,116,97,98,108,101,45,108,115,112,97,99,101,58,32,48,112,116,59,32,109,115,111,45,116,97,98,108,101,45,114,115,112,97,99,101,58,32,48,112,116,59,32,112,97,100,100,105,110,103,58,32,51,48,112,120,32,48,59,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,105,109,103,32,115,114,99,61,34,104,116,116,112,115,58,47,47,99,98,45,98,114,97,110,100,46,115,51,46,97,109,97,122,111,110,97,119,115,46,99,111,109,47,101,53,97,100,54,97,100,56,47,99,111,105,110,98,97,115,101,45,108,111,103,111,64,50,120,46,112,110,103,34,32,98,111,114,100,101,114,61,34,48,34,32,115,116,121,108,101,61,34,45,109,115,45,105,110,116,101,114,112,111,108,97,116,105,111,110,45,109,111,100,101,58,32,98,105,99,117,98,105,99,59,32,98,111,114,100,101,114,58,32,48,59,32,100,105,115,112,108,97,121,58,32,98,108,111,99,107,59,32,104,101,105,103,104,116,58,32,51,48,37,59,32,108,105,110,101,45,104,101,105,103,104,116,58,32,49,48,48,37,59,32,111,117,116,108,105,110,101,58,32,110,111,110,101,59,32,116,101,120,116,45,100,101,99,111,114,97,116,105,111,110,58,32,110,111,110,101,59,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,100,62,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,98,111,100,121,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,97,98,108,101,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,33,45,45,32,76,111,103,111,32,101,110,100,32,104,101,114,101,32,45,45,62,13,10,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,33,45,45,32,77,97,105,110,32,67,79,78,84,69,78,84,32,45,45,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,97,98,108,101,32,119,105,100,116,104,61,34,49,48,48,37,34,32,99,101,108,108,112,97,100,100,105,110,103,61,34,48,34,32,99,101,108,108,115,112,97,99,105,110,103,61,34,48,34,32,98,111,114,100,101,114,61,34,48,34,32,98,103,99,111,108,111,114,61,34,35,102,102,102,102,102,102,34,32,115,116,121,108,101,61,34,98,111,114,100,101,114,45,114,97,100,105,117,115,58,32,52,112,120,59,32,98,111,120,45,115,104,97,100,111,119,58,32,48,32,50,112,120,32,56,112,120,32,114,103,98,97,40,48,44,48,44,48,44,48,46,48,53,41,59,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,98,111,100,121,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,104,101,105,103,104,116,61,34,52,48,34,62,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,32,115,116,121,108,101,61,34,102,111,110,116,45,102,97,109,105,108,121,58,32,45,97,112,112,108,101,45,115,121,115,116,101,109,44,66,108,105,110,107,77,97,99,83,121,115,116,101,109,70,111,110,116,44,38,35,51,57,59,83,101,103,111,101,32,85,73,38,35,51,57,59,44,38,35,51,57,59,82,111,98,111,116,111,38,35,51,57,59,44,38,35,51,57,59,79,120,121,103,101,110,38,35,51,57,59,44,38,35,51,57,59,85,98,117,110,116,117,38,35,51,57,59,44,38,35,51,57,59,67,97,110,116,97,114,101,108,108,38,35,51,57,59,44,38,35,51,57,59,70,105,114,97,32,83,97,110,115,38,35,51,57,59,44,38,35,51,57,59,68,114,111,105,100,32,83,97,110,115,38,35,51,57,59,44,38,35,51,57,59,72,101,108,118,101,116,105,99,97,32,78,101,117,101,38,35,51,57,59,44,115,97,110,115,45,115,101,114,105,102,59,32,99,111,108,111,114,58,35,52,69,53,67,54,69,59,32,102,111,110,116,45,115,105,122,101,58,49,52,112,120,59,32,108,105,110,101,45,104,101,105,103,104,116,58,50,48,112,120,59,32,109,97,114,103,105,110,45,116,111,112,58,50,48,112,120,59,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,99,108,97,115,115,61,34,99,111,110,116,101,110,116,34,32,99,111,108,115,112,97,110,61,34,50,34,32,118,97,108,105,103,110,61,34,116,111,112,34,32,97,108,105,103,110,61,34,99,101,110,116,101,114,34,32,115,116,121,108,101,61,34,112,97,100,100,105,110,103,45,108,101,102,116,58,52,48,112,120,59,32,112,97,100,100,105,110,103,45,114,105,103,104,116,58,52,48,112,120,59,34,62,13,10,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,97,98,108,101,32,119,105,100,116,104,61,34,49,48,48,37,34,32,99,101,108,108,112,97,100,100,105,110,103,61,34,48,34,32,99,101,108,108,115,112,97,99,105,110,103,61,34,48,34,32,98,111,114,100,101,114,61,34,48,34,32,98,103,99,111,108,111,114,61,34,35,102,102,102,102,102,102,34,62,13,10,32,32,60,116,98,111,100,121,62,13,10,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,60,116,100,32,97,108,105,103,110,61,34,99,101,110,116,101,114,34,62,13,10,32,32,32,32,32,32,32,32,32,32,60,100,105,118,32,115,116,121,108,101,61,34,99,111,108,111,114,58,32,35,52,56,53,52,53,100,59,32,102,111,110,116,45,115,105,122,101,58,50,54,112,120,59,32,102,111,110,116,45,119,101,105,103,104,116,58,32,51,48,48,59,32,108,105,110,101,45,104,101,105,103,104,116,58,32,50,52,112,120,59,32,109,97,114,103,105,110,58,32,50,48,112,120,32,97,117,116,111,32,52,54,112,120,59,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,67,111,110,103,114,97,116,115,33,32,89,111,117,114,32,97,99,99,111,117,110,116,32,105,115,32,114,101,97,100,121,46,13,10,32,32,32,32,32,32,32,32,32,32,60,47,100,105,118,62,13,10,32,32,32,32,32,32,32,32,60,47,116,100,62,13,10,32,32,32,32,32,32,60,47,116,114,62,13,10,13,10,32,32,32,32,32,32,60,116,100,32,115,116,121,108,101,61,34,116,101,120,116,45,97,108,105,103,110,58,32,99,101,110,116,101,114,34,62,13,10,32,32,32,32,32,32,32,32,60,115,118,103,32,119,105,100,116,104,61,34,49,52,54,34,32,104,101,105,103,104,116,61,34,49,52,54,34,32,118,101,114,115,105,111,110,61,34,49,46,49,34,32,105,100,61,34,76,97,121,101,114,95,49,34,32,120,109,108,110,115,61,34,104,116,116,112,58,47,47,119,119,119,46,119,51,46,111,114,103,47,50,48,48,48,47,115,118,103,34,32,120,109,108,110,115,58,120,108,105,110,107,61,34,104,116,116,112,58,47,47,119,119,119,46,119,51,46,111,114,103,47,49,57,57,57,47,120,108,105,110,107,34,32,120,61,34,48,112,120,34,32,121,61,34,48,112,120,34,13,10,32,32,32,32,32,32,32,32,32,32,32,118,105,101,119,66,111,120,61,34,48,32,48,32,49,53,48,32,49,53,48,34,32,115,116,121,108,101,61,34,101,110,97,98,108,101,45,98,97,99,107,103,114,111,117,110,100,58,110,101,119,32,48,32,48,32,49,53,48,32,49,53,48,59,34,32,120,109,108,58,115,112,97,99,101,61,34,112,114,101,115,101,114,118,101,34,62,13,10,32,32,32,32,32,32,32,32,60,115,116,121,108,101,32,116,121,112,101,61,34,116,101,120,116,47,99,115,115,34,62,13,10,32,32,32,32,32,32,32,32,32,32,46,115,116,48,123,102,105,108,108,58,35,68,52,69,69,70,70,59,125,13,10,32,32,32,32,32,32,32,32,32,32,46,115,116,49,123,111,112,97,99,105,116,121,58,48,46,51,59,125,13,10,32,32,32,32,32,32,32,32,32,32,46,115,116,50,123,102,105,108,108,58,35,70,70,70,70,70,70,59,125,13,10,32,32,32,32,32,32,32,32,32,32,46,115,116,51,123,102,105,108,108,58,35,48,48,67,53,55,70,59,125,13,10,32,32,32,32,32,32,32,32,32,32,46,115,116,52,123,111,112,97,99,105,116,121,58,48,46,51,59,102,105,108,108,58,35,50,57,54,56,67,57,59,125,13,10,32,32,32,32,32,32,32,32,32,32,46,115,116,53,123,102,105,108,108,58,35,50,57,54,56,67,57,59,125,13,10,32,32,32,32,32,32,32,32,32,32,46,115,116,54,123,102,105,108,108,58,35,49,54,52,69,66,49,59,125,13,10,32,32,32,32,32,32,32,32,60,47,115,116,121,108,101,62,13,10,32,32,32,32,32,32,32,32,60,99,105,114,99,108,101,32,99,108,97,115,115,61,34,115,116,48,34,32,99,120,61,34,55,53,34,32,99,121,61,34,55,53,34,32,114,61,34,55,53,34,47,62,13,10,32,32,32,32,32,32,32,32,60,103,32,99,108,97,115,115,61,34,115,116,49,34,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,50,49,46,57,44,53,57,46,56,57,99,55,46,48,55,44,48,44,49,48,46,54,53,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,115,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,115,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,49,46,53,55,45,48,46,55,49,44,51,46,49,49,45,49,46,52,44,52,46,57,57,45,49,46,57,50,99,45,48,46,51,54,45,49,46,50,57,45,48,46,55,53,45,50,46,53,55,45,49,46,49,56,45,51,46,56,51,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,50,46,49,44,48,46,53,57,45,51,46,55,57,44,49,46,51,53,45,53,46,52,54,44,50,46,49,49,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,53,44,50,46,56,50,115,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,53,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,45,49,48,46,54,52,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,45,49,48,46,54,53,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,49,46,54,55,45,48,46,55,53,45,51,46,51,55,45,49,46,53,50,45,53,46,52,56,45,50,46,49,49,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,48,46,52,51,44,49,46,50,54,45,48,46,56,50,44,50,46,53,52,45,49,46,49,56,44,51,46,56,51,99,49,46,56,57,44,48,46,53,50,44,51,46,52,51,44,49,46,50,50,44,53,46,48,49,44,49,46,57,51,67,49,49,46,50,53,44,53,56,46,50,56,44,49,52,46,56,51,44,53,57,46,56,57,44,50,49,46,57,44,53,57,46,56,57,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,49,49,53,46,54,55,44,51,55,46,48,55,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,115,45,49,48,46,54,52,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,115,45,49,48,46,54,53,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,53,46,55,55,44,48,45,56,46,55,57,45,49,46,49,56,45,49,49,46,55,55,45,50,46,53,49,99,45,48,46,54,55,44,49,46,49,53,45,49,46,51,49,44,50,46,51,51,45,49,46,57,50,44,51,46,53,50,99,51,46,51,51,44,49,46,53,44,54,46,57,44,50,46,57,57,44,49,51,46,54,57,44,50,46,57,57,99,55,46,48,55,44,48,44,49,48,46,54,53,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,115,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,54,46,55,56,44,48,44,49,48,46,51,52,45,49,46,52,56,44,49,51,46,54,55,45,50,46,57,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,48,46,54,49,45,49,46,49,57,45,49,46,50,53,45,50,46,51,55,45,49,46,57,50,45,51,46,53,50,99,45,50,46,57,55,44,49,46,51,51,45,54,44,50,46,53,49,45,49,49,46,55,53,44,50,46,53,49,67,49,50,49,46,57,50,44,51,57,46,56,57,44,49,49,56,46,56,57,44,51,56,46,53,50,44,49,49,53,46,54,55,44,51,55,46,48,55,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,50,49,46,57,44,54,55,46,56,57,99,55,46,48,55,44,48,44,49,48,46,54,53,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,115,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,115,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,50,46,48,50,45,48,46,57,49,44,51,46,57,56,45,49,46,56,44,54,46,54,57,45,50,46,51,50,99,45,48,46,50,50,45,49,46,51,50,45,48,46,52,56,45,50,46,54,51,45,48,46,55,56,45,51,46,57,51,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,51,46,49,52,44,48,46,54,49,45,53,46,51,56,44,49,46,54,50,45,55,46,53,55,44,50,46,54,49,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,53,44,50,46,56,50,115,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,53,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,45,49,48,46,54,52,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,45,49,48,46,54,53,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,50,46,49,57,45,48,46,57,57,45,52,46,52,52,45,50,45,55,46,53,57,45,50,46,54,49,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,48,46,50,57,44,49,46,51,45,48,46,53,53,44,50,46,54,49,45,48,46,55,56,44,51,46,57,51,99,50,46,55,51,44,48,46,53,51,44,52,46,54,57,44,49,46,52,49,44,54,46,55,50,44,50,46,51,51,67,49,49,46,50,53,44,54,54,46,50,56,44,49,52,46,56,51,44,54,55,46,56,57,44,50,49,46,57,44,54,55,46,56,57,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,49,49,53,46,54,55,44,50,57,46,48,55,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,115,45,49,48,46,54,52,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,115,45,49,48,46,54,53,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,51,46,50,51,44,48,45,53,46,54,45,48,46,51,55,45,55,46,53,57,45,48,46,57,52,99,45,48,46,56,50,44,49,46,49,51,45,49,46,54,49,44,50,46,50,56,45,50,46,51,55,44,51,46,52,54,99,50,46,53,49,44,48,46,56,53,44,53,46,53,53,44,49,46,52,56,44,57,46,57,53,44,49,46,52,56,99,55,46,48,55,44,48,44,49,48,46,54,53,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,115,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,52,46,51,57,44,48,44,55,46,52,51,45,48,46,54,50,44,57,46,57,51,45,49,46,52,55,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,48,46,55,54,45,49,46,49,56,45,49,46,53,52,45,50,46,51,51,45,50,46,51,55,45,51,46,52,54,99,45,49,46,57,56,44,48,46,53,54,45,52,46,51,52,44,48,46,57,51,45,55,46,53,55,44,48,46,57,51,67,49,50,49,46,57,50,44,51,49,46,56,57,44,49,49,56,46,56,57,44,51,48,46,53,50,44,49,49,53,46,54,55,44,50,57,46,48,55,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,49,52,48,46,53,56,44,49,48,49,46,48,55,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,53,44,50,46,56,50,115,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,53,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,45,49,48,46,54,52,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,45,49,48,46,54,53,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,49,46,54,56,45,48,46,55,54,45,51,46,51,57,45,49,46,53,51,45,53,46,53,49,45,50,46,49,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,48,46,53,54,44,49,46,54,53,44,49,46,49,53,44,51,46,50,56,44,49,46,56,50,44,52,46,56,55,99,48,46,55,44,48,46,50,57,44,49,46,51,55,44,48,46,53,57,44,50,46,48,53,44,48,46,57,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,115,49,48,46,54,53,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,115,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,115,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,48,46,54,55,45,48,46,51,44,49,46,51,51,45,48,46,54,44,50,46,48,50,45,48,46,56,57,99,48,46,54,54,45,49,46,54,44,49,46,50,54,45,51,46,50,50,44,49,46,56,50,45,52,46,56,55,67,49,52,51,46,57,54,44,57,57,46,53,53,44,49,52,50,46,50,54,44,49,48,48,46,51,50,44,49,52,48,46,53,56,44,49,48,49,46,48,55,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,51,54,44,49,54,46,55,50,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,115,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,48,53,44,49,46,51,56,44,54,46,50,44,50,46,56,44,49,49,46,55,54,44,51,46,49,49,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,50,45,49,46,56,52,45,52,46,48,57,45,51,46,53,55,45,54,46,50,56,45,53,46,49,57,99,45,49,46,51,56,45,48,46,52,54,45,50,46,53,57,45,49,46,48,49,45,51,46,56,51,45,49,46,53,55,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,115,45,49,48,46,54,52,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,115,45,49,48,46,54,53,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,49,46,50,53,44,48,46,53,55,45,50,46,52,57,44,49,46,49,50,45,51,46,56,57,44,49,46,53,57,99,45,50,46,49,56,44,49,46,54,49,45,52,46,50,55,44,51,46,51,52,45,54,46,50,54,44,53,46,49,55,67,50,57,46,55,57,44,49,57,46,53,50,44,51,50,46,57,52,44,49,56,46,49,44,51,54,44,49,54,46,55,50,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,49,50,56,46,49,51,44,52,55,46,56,57,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,53,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,115,45,49,48,46,54,52,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,115,45,49,48,46,54,53,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,48,46,56,55,45,48,46,51,57,45,49,46,55,53,45,48,46,55,57,45,50,46,54,57,45,49,46,49,54,99,45,48,46,53,53,44,49,46,50,49,45,49,46,48,55,44,50,46,52,52,45,49,46,53,54,44,51,46,54,57,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,48,46,57,44,48,46,51,53,44,49,46,55,53,44,48,46,55,51,44,50,46,54,44,49,46,49,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,115,49,48,46,54,53,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,115,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,48,46,56,53,45,48,46,51,57,44,49,46,54,57,45,48,46,55,54,44,50,46,53,56,45,49,46,49,49,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,48,46,52,57,45,49,46,50,53,45,49,46,48,49,45,50,46,52,55,45,49,46,53,54,45,51,46,54,57,99,45,48,46,57,51,44,48,46,51,55,45,49,46,56,44,48,46,55,55,45,50,46,54,55,44,49,46,49,54,67,49,51,55,46,51,55,44,52,54,46,53,50,44,49,51,52,46,51,52,44,52,55,46,56,57,44,49,50,56,46,49,51,44,52,55,46,56,57,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,55,53,46,48,50,44,55,46,56,57,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,50,46,48,55,45,48,46,57,51,45,52,46,49,56,45,49,46,56,56,45,55,46,48,53,45,50,46,53,99,45,51,46,52,50,44,48,46,57,50,45,54,46,55,53,44,50,46,48,54,45,57,46,57,54,44,51,46,52,52,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,48,46,56,57,45,48,46,48,55,44,49,46,56,53,45,48,46,49,50,44,50,46,57,49,45,48,46,49,50,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,99,49,46,48,52,44,48,44,49,46,57,57,44,48,46,48,52,44,50,46,56,55,44,48,46,49,49,99,45,51,46,50,45,49,46,51,55,45,54,46,53,51,45,50,46,53,49,45,57,46,57,52,45,51,46,52,51,99,45,50,46,56,54,44,48,46,54,50,45,52,46,57,55,44,49,46,53,54,45,55,46,48,51,44,50,46,52,57,13,10,32,32,32,32,32,32,32,32,32,32,32,32,67,56,52,46,50,54,44,54,46,53,50,44,56,49,46,50,50,44,55,46,56,57,44,55,53,46,48,50,44,55,46,56,57,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,50,49,46,57,44,55,53,46,56,57,99,55,46,48,55,44,48,44,49,48,46,54,53,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,115,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,115,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,50,46,50,52,45,49,46,48,49,44,52,46,52,45,49,46,57,56,44,55,46,54,45,50,46,52,56,99,45,48,46,48,56,45,49,46,51,52,45,48,46,50,49,45,50,46,54,55,45,48,46,51,54,45,51,46,57,57,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,51,46,56,54,44,48,46,53,54,45,54,46,52,49,44,49,46,55,45,56,46,56,57,44,50,46,56,51,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,53,44,50,46,56,50,115,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,53,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,45,49,48,46,54,52,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,45,49,48,46,54,53,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,50,46,52,57,45,49,46,49,50,45,53,46,48,52,45,50,46,50,55,45,56,46,57,49,45,50,46,56,51,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,48,46,49,53,44,49,46,51,50,45,48,46,50,55,44,50,46,54,53,45,48,46,51,54,44,51,46,57,57,99,51,46,50,50,44,48,46,53,44,53,46,51,56,44,49,46,52,55,44,55,46,54,51,44,50,46,52,56,67,49,49,46,50,53,44,55,52,46,50,56,44,49,52,46,56,51,44,55,53,46,56,57,44,50,49,46,57,44,55,53,46,56,57,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,54,49,46,57,49,44,49,46,49,54,99,51,46,49,56,44,49,46,52,49,44,54,46,55,49,44,50,46,55,51,44,49,51,46,49,49,44,50,46,55,51,99,54,46,51,57,44,48,44,57,46,57,50,45,49,46,51,50,44,49,51,46,49,45,50,46,55,51,67,56,51,46,56,53,44,48,46,52,49,44,55,57,46,52,56,44,48,44,55,53,44,48,13,10,32,32,32,32,32,32,32,32,32,32,32,32,67,55,48,46,53,51,44,48,44,54,54,46,49,54,44,48,46,52,49,44,54,49,46,57,49,44,49,46,49,54,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,50,49,46,57,44,57,57,46,56,57,99,55,46,48,55,44,48,44,49,48,46,54,53,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,115,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,115,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,49,46,54,49,45,48,46,55,51,44,51,46,49,56,45,49,46,52,52,44,53,46,49,50,45,49,46,57,54,99,48,46,51,57,45,49,46,52,51,44,48,46,55,53,45,50,46,56,55,44,49,46,48,53,45,52,46,51,52,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,51,46,50,55,44,48,46,54,45,53,46,53,56,44,49,46,54,52,45,55,46,56,50,44,50,46,54,53,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,53,44,50,46,56,50,115,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,53,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,45,49,48,46,54,52,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,45,49,48,46,54,53,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,50,46,50,53,45,49,46,48,50,45,52,46,53,54,45,50,46,48,54,45,55,46,56,53,45,50,46,54,54,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,48,46,51,49,44,49,46,52,54,44,48,46,54,54,44,50,46,57,49,44,49,46,48,53,44,52,46,51,52,99,49,46,57,54,44,48,46,53,51,44,51,46,53,51,44,49,46,50,52,44,53,46,49,53,44,49,46,57,55,67,49,49,46,50,53,44,57,56,46,50,56,44,49,52,46,56,51,44,57,57,46,56,57,44,50,49,46,57,44,57,57,46,56,57,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,49,48,49,46,53,55,44,49,55,46,56,57,99,45,55,46,48,55,44,48,45,49,48,46,54,52,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,115,45,49,48,46,54,53,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,48,46,54,49,44,48,45,49,46,49,56,45,48,46,48,50,45,49,46,55,51,45,48,46,48,52,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,49,46,49,51,44,49,46,50,49,45,50,46,50,50,44,50,46,52,52,45,51,46,50,55,44,51,46,55,50,99,49,46,52,54,44,48,46,50,44,51,46,49,44,48,46,51,50,44,52,46,57,57,44,48,46,51,50,99,55,46,48,55,44,48,44,49,48,46,54,53,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,49,46,56,57,44,48,44,51,46,53,50,45,48,46,49,50,44,52,46,57,55,45,48,46,51,50,99,45,49,46,48,53,45,49,46,50,56,45,50,46,49,52,45,50,46,53,50,45,51,46,50,55,45,51,46,55,51,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,48,46,53,52,44,48,46,48,50,45,49,46,49,44,48,46,48,52,45,49,46,55,44,48,46,48,52,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,53,45,50,46,56,50,67,49,49,50,46,50,50,44,49,57,46,53,49,44,49,48,56,46,54,52,44,49,55,46,56,57,44,49,48,49,46,53,55,44,49,55,46,56,57,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,50,49,46,57,44,57,49,46,56,57,99,55,46,48,55,44,48,44,49,48,46,54,53,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,115,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,115,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,50,46,48,56,45,48,46,57,52,44,52,46,48,56,45,49,46,56,52,44,54,46,57,49,45,50,46,51,54,99,48,46,50,49,45,49,46,51,55,44,48,46,51,55,45,50,46,55,53,44,48,46,53,45,52,46,49,51,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,51,46,57,53,44,48,46,53,53,45,54,46,53,52,44,49,46,55,49,45,57,46,48,54,44,50,46,56,53,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,53,44,50,46,56,50,115,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,53,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,45,49,48,46,54,52,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,13,10,32,32,32,32,32,32,32,32,32,32,32,32,115,45,49,48,46,54,53,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,99,45,50,46,53,51,45,49,46,49,52,45,53,46,49,50,45,50,46,51,49,45,57,46,48,57,45,50,46,56,54,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,48,46,49,51,44,49,46,51,57,44,48,46,50,57,44,50,46,55,55,44,48,46,53,44,52,46,49,51,99,50,46,56,52,44,48,46,53,50,44,52,46,56,54,44,49,46,52,51,44,54,46,57,52,44,50,46,51,55,67,49,49,46,50,53,44,57,48,46,50,56,44,49,52,46,56,51,44,57,49,46,56,57,44,50,49,46,57,44,57,49,46,56,57,122,34,47,62,13,10,32,32,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,50,49,46,57,44,56,51,46,56,57,99,55,46,48,55,44,48,44,49,48,46,54,53,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,99,54,46,50,49,44,48,44,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,99,55,46,48,55,44,48,44,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,51,46,50,49,45,49,46,52,53,44,54,46,50,53,45,50,46,56,50,44,49,50,46,52,54,45,50,46,56,50,115,57,46,50,52,44,49,46,51,55,44,49,50,46,52,54,44,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,51,46,52,54,44,49,46,53,54,44,55,46,48,51,44,51,46,49,56,44,49,52,46,49,44,51,46,49,56,115,49,48,46,54,52,45,49,46,54,50,44,49,52,46,49,45,51,46,49,56,99,50,46,50,54,45,49,46,48,50,44,52,46,52,52,45,50,44,55,46,54,57,45,50,46,52,57,99,48,46,48,53,45,49,46,48,55,44,48,46,48,56,45,50,46,49,52,44,48,46,48,56,45,51,46,50,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,48,45,48,46,50,56,45,48,46,48,50,45,48,46,53,53,45,48,46,48,50,45,48,46,56,51,99,45,52,46,49,53,44,48,46,53,51,45,54,46,56,44,49,46,55,51,45,57,46,52,44,50,46,57,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,53,44,50,46,56,50,115,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,53,45,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,115,45,49,48,46,54,52,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,51,46,52,54,45,49,46,53,54,45,55,46,48,51,45,51,46,49,56,45,49,52,46,49,45,51,46,49,56,115,45,49,48,46,54,53,44,49,46,54,50,45,49,52,46,49,44,51,46,49,56,99,45,51,46,50,49,44,49,46,52,53,45,54,46,50,53,44,50,46,56,50,45,49,50,46,52,54,44,50,46,56,50,99,45,54,46,50,49,44,48,45,57,46,50,52,45,49,46,51,55,45,49,50,46,52,54,45,50,46,56,50,13,10,32,32,32,32,32,32,32,32,32,32,32,32,99,45,50,46,54,45,49,46,49,55,45,53,46,50,54,45,50,46,51,56,45,57,46,52,50,45,50,46,57,67,48,46,48,50,44,55,52,46,52,53,44,48,44,55,52,46,55,50,44,48,44,55,53,99,48,44,49,46,48,56,44,48,46,48,52,44,50,46,49,53,44,48,46,48,56,44,51,46,50,50,99,51,46,50,54,44,48,46,52,57,44,53,46,52,53,44,49,46,52,55,44,55,46,55,49,44,50,46,53,13,10,32,32,32,32,32,32,32,32,32,32,32,32,67,49,49,46,50,53,44,56,50,46,50,56,44,49,52,46,56,51,44,56,51,46,56,57,44,50,49,46,57,44,56,51,46,56,57,122,34,47,62,13,10,32,32,32,32,32,32,32,32,60,47,103,62,13,10,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,51,34,32,100,61,34,77,54,46,50,54,44,49,48,53,99,49,49,46,53,56,44,50,54,46,52,56,44,51,55,46,57,57,44,52,53,44,54,56,46,55,52,44,52,53,115,53,55,46,49,55,45,49,56,46,53,50,44,54,56,46,55,52,45,52,53,72,54,46,50,54,122,34,47,62,13,10,32,32,32,32,32,32,32,32,60,112,111,108,121,103,111,110,32,99,108,97,115,115,61,34,115,116,50,34,32,112,111,105,110,116,115,61,34,55,52,46,50,53,44,49,51,53,46,51,50,32,54,52,46,51,53,44,49,50,53,46,52,50,32,54,55,46,49,56,44,49,50,50,46,54,32,55,52,46,50,53,44,49,50,57,46,54,55,32,56,54,46,56,51,44,49,49,55,46,48,57,32,56,57,46,54,53,44,49,49,57,46,57,50,32,34,47,62,13,10,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,50,34,32,100,61,34,77,55,53,44,53,50,99,50,56,46,53,55,44,48,44,51,48,44,53,51,44,51,48,44,53,51,72,52,53,67,52,53,44,49,48,53,44,52,54,46,52,51,44,53,50,44,55,53,44,53,50,122,34,47,62,13,10,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,48,34,32,100,61,34,77,55,53,44,53,50,118,53,51,104,51,48,67,49,48,53,44,49,48,53,44,49,48,51,46,53,55,44,53,50,44,55,53,44,53,50,122,34,47,62,13,10,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,52,34,32,100,61,34,77,55,53,44,53,50,118,53,51,104,51,48,67,49,48,53,44,49,48,53,44,49,48,51,46,53,55,44,53,50,44,55,53,44,53,50,122,34,47,62,13,10,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,53,34,32,100,61,34,77,55,53,44,52,53,99,49,56,46,49,44,48,44,49,57,44,54,48,44,49,57,44,54,48,72,53,54,67,53,54,44,49,48,53,44,53,54,46,57,44,52,53,44,55,53,44,52,53,122,34,47,62,13,10,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,54,34,32,100,61,34,77,55,53,44,52,53,99,56,46,53,55,44,48,44,57,44,54,48,44,57,44,54,48,104,49,48,67,57,52,44,49,48,53,44,57,51,46,49,44,52,53,44,55,53,44,52,53,122,34,47,62,13,10,32,32,32,32,32,32,32,32,60,101,108,108,105,112,115,101,32,99,108,97,115,115,61,34,115,116,53,34,32,99,120,61,34,55,53,34,32,99,121,61,34,50,56,34,32,114,120,61,34,56,34,32,114,121,61,34,49,50,34,47,62,13,10,32,32,32,32,32,32,32,32,60,112,97,116,104,32,99,108,97,115,115,61,34,115,116,54,34,32,100,61,34,77,55,53,44,49,54,99,50,46,55,54,44,48,44,53,44,53,46,51,55,44,53,44,49,50,99,48,44,54,46,54,51,45,50,46,50,52,44,49,50,45,53,44,49,50,99,52,46,52,50,44,48,44,56,45,53,46,51,55,44,56,45,49,50,67,56,51,44,50,49,46,51,55,44,55,57,46,52,50,44,49,54,44,55,53,44,49,54,122,34,47,62,13,10,32,32,32,32,32,32,32,32,60,47,115,118,103,62,13,10,13,10,32,32,32,32,32,32,60,47,116,100,62,13,10,32,32,32,32,60,47,116,114,62,13,10,13,10,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,60,116,100,32,97,108,105,103,110,61,34,99,101,110,116,101,114,34,62,13,10,32,32,32,32,32,32,32,32,60,100,105,118,32,115,116,121,108,101,61,34,99,111,108,111,114,58,32,35,52,56,53,52,53,100,59,32,102,111,110,116,45,115,105,122,101,58,32,49,56,112,120,59,32,102,111,110,116,45,119,101,105,103,104,116,58,32,51,48,48,59,32,108,105,110,101,45,104,101,105,103,104,116,58,32,50,52,112,120,59,32,109,97,114,103,105,110,58,32,51,54,112,120,32,97,117,116,111,32,49,56,112,120,59,34,62,13,10,32,32,32,32,32,32,32,32,32,32,89,111,117,114,32,105,100,101,110,116,105,116,121,32,104,97,115,32,98,101,101,110,32,118,101,114,105,102,105,101,100,46,13,10,32,32,32,32,32,32,32,32,60,47,100,105,118,62,13,10,32,32,32,32,32,32,60,47,116,100,62,13,10,32,32,32,32,60,47,116,114,62,13,10,13,10,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,60,116,100,32,97,108,105,103,110,61,34,99,101,110,116,101,114,34,62,13,10,32,32,32,32,32,32,32,32,60,112,32,115,116,121,108,101,61,34,109,97,114,103,105,110,45,116,111,112,58,50,48,112,120,59,32,109,97,114,103,105,110,45,98,111,116,116,111,109,58,48,59,34,62,13,10,32,32,32,32,32,32,32,32,32,32,60,97,32,104,114,101,102,61,34,104,116,116,112,115,58,47,47,119,119,119,46,99,111,105,110,98,97,115,101,46,99,111,109,47,98,117,121,34,32,115,116,121,108,101,61,34,100,105,115,112,108,97,121,58,32,105,110,108,105,110,101,45,98,108,111,99,107,59,32,102,111,110,116,45,115,105,122,101,58,32,49,56,112,120,59,32,102,111,110,116,45,119,101,105,103,104,116,58,32,51,48,48,59,32,112,97,100,100,105,110,103,58,32,51,48,112,120,59,32,98,97,99,107,103,114,111,117,110,100,45,99,111,108,111,114,58,32,35,52,65,57,48,69,50,59,32,99,111,108,111,114,58,32,35,102,102,102,102,102,102,59,32,98,111,114,100,101,114,45,114,97,100,105,117,115,58,32,52,112,120,59,32,116,101,120,116,45,100,101,99,111,114,97,116,105,111,110,58,32,110,111,110,101,59,32,119,105,100,116,104,58,32,52,48,37,59,34,62,66,117,121,32,68,105,103,105,116,97,108,32,67,117,114,114,101,110,99,121,60,47,97,62,13,10,32,32,32,32,32,32,32,32,60,47,112,62,13,10,32,32,32,32,32,32,60,47,116,100,62,13,10,32,32,32,32,60,47,116,114,62,13,10,32,32,60,47,116,98,111,100,121,62,13,10,60,47,116,97,98,108,101,62,13,10,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,104,101,105,103,104,116,61,34,52,48,34,62,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,98,111,100,121,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,97,98,108,101,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,33,45,45,32,77,97,105,110,32,67,79,78,84,69,78,84,32,101,110,100,32,104,101,114,101,32,45,45,62,13,10,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,33,45,45,32,80,82,79,77,79,32,99,111,108,117,109,110,32,115,116,97,114,116,32,104,101,114,101,32,45,45,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,33,45,45,32,83,104,111,119,32,109,111,98,105,108,101,32,112,114,111,109,111,32,55,53,37,32,111,102,32,116,104,101,32,116,105,109,101,32,45,45,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,97,98,108,101,32,105,100,61,34,112,114,111,109,111,34,32,119,105,100,116,104,61,34,49,48,48,37,34,32,99,101,108,108,112,97,100,100,105,110,103,61,34,48,34,32,99,101,108,108,115,112,97,99,105,110,103,61,34,48,34,32,98,111,114,100,101,114,61,34,48,34,32,115,116,121,108,101,61,34,109,97,114,103,105,110,45,116,111,112,58,50,48,112,120,59,34,32,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,98,111,100,121,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,99,111,108,115,112,97,110,61,34,50,34,32,104,101,105,103,104,116,61,34,50,48,34,62,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,99,111,108,115,112,97,110,61,34,50,34,32,97,108,105,103,110,61,34,99,101,110,116,101,114,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,115,112,97,110,32,115,116,121,108,101,61,34,102,111,110,116,45,115,105,122,101,58,49,52,112,120,59,32,102,111,110,116,45,119,101,105,103,104,116,58,52,48,48,59,32,109,97,114,103,105,110,45,98,111,116,116,111,109,58,49,48,112,120,59,32,99,111,108,111,114,58,32,35,102,101,102,101,102,101,59,32,102,111,110,116,45,102,97,109,105,108,121,58,32,45,97,112,112,108,101,45,115,121,115,116,101,109,44,66,108,105,110,107,77,97,99,83,121,115,116,101,109,70,111,110,116,44,38,35,51,57,59,83,101,103,111,101,32,85,73,38,35,51,57,59,44,38,35,51,57,59,82,111,98,111,116,111,38,35,51,57,59,44,38,35,51,57,59,79,120,121,103,101,110,38,35,51,57,59,44,38,35,51,57,59,85,98,117,110,116,117,38,35,51,57,59,44,38,35,51,57,59,67,97,110,116,97,114,101,108,108,38,35,51,57,59,44,38,35,51,57,59,70,105,114,97,32,83,97,110,115,38,35,51,57,59,44,38,35,51,57,59,68,114,111,105,100,32,83,97,110,115,38,35,51,57,59,44,38,35,51,57,59,72,101,108,118,101,116,105,99,97,32,78,101,117,101,38,35,51,57,59,44,115,97,110,115,45,115,101,114,105,102,59,34,62,71,101,116,32,116,104,101,32,108,97,116,101,115,116,32,67,111,105,110,98,97,115,101,32,65,112,112,32,102,111,114,32,121,111,117,114,32,112,104,111,110,101,60,47,115,112,97,110,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,99,111,108,115,112,97,110,61,34,50,34,32,104,101,105,103,104,116,61,34,50,48,34,62,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,118,97,108,105,103,110,61,34,116,111,112,34,32,119,105,100,116,104,61,34,53,48,37,34,32,97,108,105,103,110,61,34,114,105,103,104,116,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,97,32,104,114,101,102,61,34,104,116,116,112,115,58,47,47,99,111,110,116,114,111,108,46,107,111,99,104,97,118,97,46,99,111,109,47,118,49,47,99,112,105,47,99,108,105,99,107,63,99,97,109,112,97,105,103,110,95,105,100,61,107,111,99,111,105,110,98,97,115,101,45,105,111,115,53,53,50,53,51,51,98,54,56,51,55,55,57,100,52,52,53,52,101,53,54,99,57,48,97,48,38,110,101,116,119,111,114,107,95,105,100,61,50,52,50,54,38,100,101,118,105,99,101,95,105,100,61,100,101,118,105,99,101,95,105,100,38,115,105,116,101,95,105,100,61,49,34,32,115,116,121,108,101,61,34,100,105,115,112,108,97,121,58,105,110,108,105,110,101,45,98,108,111,99,107,59,109,97,114,103,105,110,45,114,105,103,104,116,58,49,48,112,120,59,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,105,109,103,32,115,114,99,61,34,104,116,116,112,115,58,47,47,115,51,46,97,109,97,122,111,110,97,119,115,46,99,111,109,47,97,112,112,45,112,117,98,108,105,99,47,67,111,105,110,98,97,115,101,45,101,109,97,105,108,47,105,79,83,95,97,112,112,46,112,110,103,34,32,104,101,105,103,104,116,61,34,52,48,34,32,98,111,114,100,101,114,61,34,48,34,32,97,108,116,61,34,67,111,105,110,98,97,115,101,32,105,79,83,32,109,111,98,105,108,101,32,98,105,116,99,111,105,110,32,119,97,108,108,101,116,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,97,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,118,97,108,105,103,110,61,34,116,111,112,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,97,32,104,114,101,102,61,34,104,116,116,112,115,58,47,47,99,111,110,116,114,111,108,46,107,111,99,104,97,118,97,46,99,111,109,47,118,49,47,99,112,105,47,99,108,105,99,107,63,99,97,109,112,97,105,103,110,95,105,100,61,107,111,99,111,105,110,98,97,115,101,45,45,45,45,112,114,111,100,117,99,116,105,111,110,53,53,51,101,99,51,98,101,50,53,99,49,51,48,56,100,97,102,50,97,53,100,50,55,57,49,38,110,101,116,119,111,114,107,95,105,100,61,50,52,50,54,38,100,101,118,105,99,101,95,105,100,61,100,101,118,105,99,101,95,105,100,38,115,105,116,101,95,105,100,61,49,38,97,112,112,101,110,100,95,97,112,112,95,99,111,110,118,95,116,114,107,95,112,97,114,97,109,115,61,49,34,32,115,116,121,108,101,61,34,100,105,115,112,108,97,121,58,105,110,108,105,110,101,45,98,108,111,99,107,59,109,97,114,103,105,110,45,108,101,102,116,58,53,112,120,59,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,105,109,103,32,115,114,99,61,34,104,116,116,112,115,58,47,47,115,51,46,97,109,97,122,111,110,97,119,115,46,99,111,109,47,97,112,112,45,112,117,98,108,105,99,47,67,111,105,110,98,97,115,101,45,101,109,97,105,108,47,65,110,100,114,111,105,100,95,97,112,112,46,112,110,103,34,32,104,101,105,103,104,116,61,34,52,48,34,32,98,111,114,100,101,114,61,34,48,34,32,97,108,116,61,34,67,111,105,110,98,97,115,101,32,65,110,100,114,111,105,100,32,109,111,98,105,108,101,32,98,105,116,99,111,105,110,32,119,97,108,108,101,116,34,32,115,116,121,108,101,61,34,104,101,105,103,104,116,58,52,48,32,33,105,109,112,111,114,116,97,110,116,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,97,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,99,111,108,115,112,97,110,61,34,50,34,32,104,101,105,103,104,116,61,34,50,48,34,62,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,98,111,100,121,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,97,98,108,101,62,13,10,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,33,45,45,32,80,82,79,77,79,32,99,111,108,117,109,110,32,101,110,100,32,104,101,114,101,32,45,45,62,13,10,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,33,45,45,32,70,79,79,84,69,82,32,115,116,97,114,116,32,104,101,114,101,32,45,45,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,97,98,108,101,32,119,105,100,116,104,61,34,49,48,48,37,34,32,99,101,108,108,112,97,100,100,105,110,103,61,34,48,34,32,99,101,108,108,115,112,97,99,105,110,103,61,34,48,34,32,98,111,114,100,101,114,61,34,48,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,98,111,100,121,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,104,101,105,103,104,116,61,34,49,48,34,62,38,110,98,115,112,59,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,118,97,108,105,103,110,61,34,116,111,112,34,32,97,108,105,103,110,61,34,99,101,110,116,101,114,34,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,115,112,97,110,32,115,116,121,108,101,61,34,102,111,110,116,45,102,97,109,105,108,121,58,32,45,97,112,112,108,101,45,115,121,115,116,101,109,44,66,108,105,110,107,77,97,99,83,121,115,116,101,109,70,111,110,116,44,38,35,51,57,59,83,101,103,111,101,32,85,73,38,35,51,57,59,44,38,35,51,57,59,82,111,98,111,116,111,38,35,51,57,59,44,38,35,51,57,59,79,120,121,103,101,110,38,35,51,57,59,44,38,35,51,57,59,85,98,117,110,116,117,38,35,51,57,59,44,38,35,51,57,59,67,97,110,116,97,114,101,108,108,38,35,51,57,59,44,38,35,51,57,59,70,105,114,97,32,83,97,110,115,38,35,51,57,59,44,38,35,51,57,59,68,114,111,105,100,32,83,97,110,115,38,35,51,57,59,44,38,35,51,57,59,72,101,108,118,101,116,105,99,97,32,78,101,117,101,38,35,51,57,59,44,115,97,110,115,45,115,101,114,105,102,59,32,99,111,108,111,114,58,32,35,102,101,102,101,102,101,59,32,102,111,110,116,45,115,105,122,101,58,49,48,112,120,59,34,62,38,99,111,112,121,59,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,97,32,104,114,101,102,61,34,104,116,116,112,115,58,47,47,119,119,119,46,99,111,105,110,98,97,115,101,46,99,111,109,47,34,32,116,97,114,103,101,116,61,34,95,98,108,97,110,107,34,32,115,116,121,108,101,61,34,99,111,108,111,114,58,32,35,102,101,102,101,102,101,32,33,105,109,112,111,114,116,97,110,116,59,32,116,101,120,116,45,100,101,99,111,114,97,116,105,111,110,58,110,111,110,101,59,34,62,67,111,105,110,98,97,115,101,60,47,97,62,32,50,48,50,48,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,115,112,97,110,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,116,100,32,104,101,105,103,104,116,61,34,53,48,34,62,38,110,98,115,112,59,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,98,111,100,121,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,97,98,108,101,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,33,45,45,32,70,79,79,84,69,82,32,101,110,100,32,104,101,114,101,32,45,45,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,100,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,32,32,32,32,32,32,32,32,60,47,116,98,111,100,121,62,13,10,32,32,32,32,32,32,32,32,32,32,60,47,116,97,98,108,101,62,13,10,32,32,32,32,32,32,32,32,60,47,116,100,62,13,10,32,32,32,32,32,32,60,47,116,114,62,13,10,32,32,32,32,60,47,116,98,111,100,121,62,13,10,32,32,60,47,116,97,98,108,101,62,13,10,60,47,98,111,100,121,62,13,10,60,47,104,116,109,108,62,13,10,128,0,0,0,0,0,0,0] -print("".join([chr(int(c)) for c in l])) \ No newline at end of file +l1 = ["100","97","116","101","58","84","117","101","44","32","50","56","32","77","97","114","32","50","48","50","51","32","49","49","58","53","55","58","51","51","32","43","48","48","48","48","13","10","102","114","111","109","58","84","119","105","116","116","101","114","32","60","105","110","102","111","64","116","119","105","116","116","101","114","46","99","111","109","62","13","10","116","111","58","78","97","116","104","97","110","32","88","105","111","110","103","32","60","110","97","116","104","97","110","120","105","111","110","103","49","49","64","103","109","97","105","108","46","99","111","109","62","13","10","115","117","98","106","101","99","116","58","80","97","115","115","119","111","114","100","32","114","101","115","101","116","32","114","101","113","117","101","115","116","13","10","109","105","109","101","45","118","101","114","115","105","111","110","58","49","46","48","13","10","99","111","110","116","101","110","116","45","116","121","112","101","58","109","117","108","116","105","112","97","114","116","47","97","108","116","101","114","110","97","116","105","118","101","59","32","98","111","117","110","100","97","114","121","61","34","45","45","45","45","61","95","80","97","114","116","95","50","53","50","53","53","48","56","51","50","95","49","49","49","50","56","52","57","48","48","50","46","49","54","56","48","48","48","52","54","53","51","56","56","56","34","13","10","109","101","115","115","97","103","101","45","105","100","58","60","66","56","46","67","50","46","48","52","56","55","51","46","68","50","54","68","50","50","52","54","64","116","119","105","116","116","101","114","46","99","111","109","62","13","10","100","107","105","109","45","115","105","103","110","97","116","117","114","101","58","118","61","49","59","32","97","61","114","115","97","45","115","104","97","50","53","54","59","32","99","61","114","101","108","97","120","101","100","47","114","101","108","97","120","101","100","59","32","100","61","116","119","105","116","116","101","114","46","99","111","109","59","32","115","61","100","107","105","109","45","50","48","49","52","48","54","59","32","116","61","49","54","56","48","48","48","52","54","53","51","59","32","98","104","61","80","102","78","81","78","76","78","79","72","52","72","67","50","122","57","79","78","71","84","84","88","99","56","108","121","99","88","117","89","71","78","51","47","76","72","98","54","68","71","86","104","77","48","61","59","32","104","61","68","97","116","101","58","70","114","111","109","58","84","111","58","83","117","98","106","101","99","116","58","77","73","77","69","45","86","101","114","115","105","111","110","58","67","111","110","116","101","110","116","45","84","121","112","101","58","77","101","115","115","97","103","101","45","73","68","59","32","98","61","128"] +l = ["0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","1","188","40","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0"] +print(len(l)) \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index a99eb6884..9f90a8b2d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8363,6 +8363,7 @@ __metadata: typescript: ^4.8.3 wagmi: ^0.6.8 web-vitals: ^2.1.4 + yargs: ^17.7.1 languageName: unknown linkType: soft @@ -21491,6 +21492,21 @@ __metadata: languageName: node linkType: hard +"yargs@npm:^17.7.1": + version: 17.7.1 + resolution: "yargs@npm:17.7.1" + dependencies: + cliui: ^8.0.1 + escalade: ^3.1.1 + get-caller-file: ^2.0.5 + require-directory: ^2.1.1 + string-width: ^4.2.3 + y18n: ^5.0.5 + yargs-parser: ^21.1.1 + checksum: 3d8a43c336a4942bc68080768664aca85c7bd406f018bad362fd255c41c8f4e650277f42fd65d543fce99e084124ddafee7bbfc1a5c6a8fda4cec78609dcf8d4 + languageName: node + linkType: hard + "yauzl@npm:^2.10.0": version: 2.10.0 resolution: "yauzl@npm:2.10.0" diff --git a/yarn.lock_old b/yarn.lock_old new file mode 100644 index 000000000..1dfd47165 --- /dev/null +++ b/yarn.lock_old @@ -0,0 +1,11 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 6 + +"zk-email-verify@workspace:.": + version: 0.0.0-use.local + resolution: "zk-email-verify@workspace:." + languageName: unknown + linkType: soft diff --git a/yarn.lock_old2 b/yarn.lock_old2 new file mode 100644 index 000000000..1dfd47165 --- /dev/null +++ b/yarn.lock_old2 @@ -0,0 +1,11 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 6 + +"zk-email-verify@workspace:.": + version: 0.0.0-use.local + resolution: "zk-email-verify@workspace:." + languageName: unknown + linkType: soft diff --git a/yarn.lock_old3 b/yarn.lock_old3 new file mode 100644 index 000000000..1dfd47165 --- /dev/null +++ b/yarn.lock_old3 @@ -0,0 +1,11 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 6 + +"zk-email-verify@workspace:.": + version: 0.0.0-use.local + resolution: "zk-email-verify@workspace:." + languageName: unknown + linkType: soft From 0c0b297cf8c6f1cd9541313df9620002e47f493d Mon Sep 17 00:00:00 2001 From: Nathan Xiong Date: Tue, 25 Apr 2023 18:06:06 -0400 Subject: [PATCH 06/25] deleted old files --- circuits/variable_subarray.circom | 79 ------------- node_modules_old/.yarn-state.yml | 10 -- node_modules_old2/.yarn-state.yml | 10 -- node_modules_old3/.yarn-state.yml | 10 -- src/contracts/lib/openzeppelin-contracts | 2 +- ...Handler.sol => KYCEmailHandler.sol.ignore} | 110 ++++-------------- src/scripts/generate_input.ts | 2 +- src/scripts/generate_two_inputs.ts | 17 +-- yarn.lock_old | 11 -- yarn.lock_old2 | 11 -- yarn.lock_old3 | 11 -- 11 files changed, 23 insertions(+), 250 deletions(-) delete mode 100644 circuits/variable_subarray.circom delete mode 100644 node_modules_old/.yarn-state.yml delete mode 100644 node_modules_old2/.yarn-state.yml delete mode 100644 node_modules_old3/.yarn-state.yml rename src/contracts/src/{KYCEmailHandler.sol => KYCEmailHandler.sol.ignore} (52%) delete mode 100644 yarn.lock_old delete mode 100644 yarn.lock_old2 delete mode 100644 yarn.lock_old3 diff --git a/circuits/variable_subarray.circom b/circuits/variable_subarray.circom deleted file mode 100644 index ead7910ef..000000000 --- a/circuits/variable_subarray.circom +++ /dev/null @@ -1,79 +0,0 @@ -pragma circom 2.0.3; - -include "../node_modules/circomlib/circuits/bitify.circom"; -include "../node_modules/circomlib/circuits/comparators.circom"; - -template VarShiftLeft(n, nBits) { - signal input in[n]; // x - signal input shift; // k - - signal output out[n]; // y - - component n2b = Num2Bits(nBits); - n2b.in <== shift; - - signal tmp[nBits][n]; - for (var j = 0; j < nBits; j++) { - for (var i = 0; i < n; i++) { - var offset = (i + (1 << j)) % n; - // Shift left by 2^j indices if bit is 1 - if (j == 0) { - tmp[j][i] <== n2b.out[j] * (in[offset] - in[i]) + in[i]; - } else { - tmp[j][i] <== n2b.out[j] * (tmp[j-1][offset] - tmp[j-1][i]) + tmp[j-1][i]; - } - } - } - - // Return last row - for (var i = 0; i < n; i++) { - out[i] <== tmp[nBits - 1][i]; - } -} - -template VarSubarrayFromZeroIndex(n, nBits) { - signal input in[n]; // x - signal input end; // k - - signal output out[n]; // y - - component lt[n]; - for (var i = 0; i < n; i++) { - lt[i] = LessThan(nBits); - lt[i].in[0] <== i; - lt[i].in[1] <== end; - - // y[i] = (i < k) * x[i] - out[i] <== lt[i].out * in[i]; - } -} - -// l, h lie in [0, n) -// the first values of out are the values at indices [l, h) of in -// the remainder of out is 0-padded -// nBits = floor(log n) + 1 -template VarSubarray(n, nBits) { - signal input in[n]; // x - signal input start; // l - signal input end; // h - - signal output out[n]; - - // Check that l < h - component lt = LessThan(nBits); - lt.in[0] <== start; - lt.in[1] <== end; - lt.out === 1; - - // Shift left by l indices - component shiftLeft = VarShiftLeft(n, nBits); - shiftLeft.in <== in; - shiftLeft.shift <== start; - - // Take first (h - l) indices - component subarrayFromZeroIndex = VarSubarrayFromZeroIndex(n, nBits); - subarrayFromZeroIndex.in <== shiftLeft.out; - subarrayFromZeroIndex.end <== end - start; - - out <== subarrayFromZeroIndex.out; -} \ No newline at end of file diff --git a/node_modules_old/.yarn-state.yml b/node_modules_old/.yarn-state.yml deleted file mode 100644 index a1fa42966..000000000 --- a/node_modules_old/.yarn-state.yml +++ /dev/null @@ -1,10 +0,0 @@ -# Warning: This file is automatically generated. Removing it is fine, but will -# cause your node_modules installation to become invalidated. - -__metadata: - version: 1 - nmMode: classic - -"zk-email-verify@workspace:.": - locations: - - "" diff --git a/node_modules_old2/.yarn-state.yml b/node_modules_old2/.yarn-state.yml deleted file mode 100644 index a1fa42966..000000000 --- a/node_modules_old2/.yarn-state.yml +++ /dev/null @@ -1,10 +0,0 @@ -# Warning: This file is automatically generated. Removing it is fine, but will -# cause your node_modules installation to become invalidated. - -__metadata: - version: 1 - nmMode: classic - -"zk-email-verify@workspace:.": - locations: - - "" diff --git a/node_modules_old3/.yarn-state.yml b/node_modules_old3/.yarn-state.yml deleted file mode 100644 index a1fa42966..000000000 --- a/node_modules_old3/.yarn-state.yml +++ /dev/null @@ -1,10 +0,0 @@ -# Warning: This file is automatically generated. Removing it is fine, but will -# cause your node_modules installation to become invalidated. - -__metadata: - version: 1 - nmMode: classic - -"zk-email-verify@workspace:.": - locations: - - "" diff --git a/src/contracts/lib/openzeppelin-contracts b/src/contracts/lib/openzeppelin-contracts index c404862cb..f959d7e4e 160000 --- a/src/contracts/lib/openzeppelin-contracts +++ b/src/contracts/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit c404862cba658deae081a9e437942a241eee78c0 +Subproject commit f959d7e4e6ee0b022b41e5b644c79369869d8411 diff --git a/src/contracts/src/KYCEmailHandler.sol b/src/contracts/src/KYCEmailHandler.sol.ignore similarity index 52% rename from src/contracts/src/KYCEmailHandler.sol rename to src/contracts/src/KYCEmailHandler.sol.ignore index 482897e55..5bd475f62 100644 --- a/src/contracts/src/KYCEmailHandler.sol +++ b/src/contracts/src/KYCEmailHandler.sol.ignore @@ -6,11 +6,14 @@ import "@openzeppelin/contracts/utils/Strings.sol"; import "@openzeppelin/contracts/utils/Counters.sol"; import "forge-std/console.sol"; // import "./base64.sol"; +import "./StringUtils.sol"; import "./NFTSVG.sol"; -import "./emailVerifier.sol"; +import "./Groth16VerifierTwitter.sol"; contract VerifiedKYCEmail is ERC721Enumerable, Verifier { using Counters for Counters.Counter; + using StringUtils for *; + using NFTSVG for *; Counters.Counter private tokenCounter; @@ -65,87 +68,17 @@ contract VerifiedKYCEmail is ERC721Enumerable, Verifier { return result; } - // modify later lol function tokenURI(uint256 tokenId) public view override returns (string memory) { string memory username = tokenIDToName[tokenId]; address owner = ownerOf(tokenId); - NFTSVG.SVGParams memory svgParams = NFTSVG.SVGParams({ - username: username, - tokenId: tokenId, - color0: NFTSVG.tokenToColorHex(uint256(uint160(owner)), 136), - color1: NFTSVG.tokenToColorHex(uint256(uint160(owner)), 136), - color2: NFTSVG.tokenToColorHex(uint256(uint160(owner)), 0), - color3: NFTSVG.tokenToColorHex(uint256(uint160(owner)), 0), - x1: NFTSVG.scale(NFTSVG.getCircleCoord(uint256(uint160(owner)), 16, tokenId), 0, 255, 16, 274), - y1: NFTSVG.scale(NFTSVG.getCircleCoord(uint256(uint160(owner)), 16, tokenId), 0, 255, 100, 484), - x2: NFTSVG.scale(NFTSVG.getCircleCoord(uint256(uint160(owner)), 32, tokenId), 0, 255, 16, 274), - y2: NFTSVG.scale(NFTSVG.getCircleCoord(uint256(uint160(owner)), 32, tokenId), 0, 255, 100, 484), - x3: NFTSVG.scale(NFTSVG.getCircleCoord(uint256(uint160(owner)), 48, tokenId), 0, 255, 16, 274), - y3: NFTSVG.scale(NFTSVG.getCircleCoord(uint256(uint160(owner)), 48, tokenId), 0, 255, 100, 484) - }); - string memory svgOutput = NFTSVG.generateSVG(svgParams); - - string memory json = Base64.encode( - bytes( - string( - abi.encodePacked( - '{"attributes":[ ', - '{"trait_type": "Name",', - '"value": "', - tokenIDToName[tokenId], - '"}, {"trait_type": "Owner",', - '"value": "', - HexStrings.toHexString(uint256(uint160(ownerOf(tokenId))), 42), - '"}], "description": "ZK VerifiedEmails are ZK verified proofs of email recieving on Ethereum. They only reveal parts of the email headers and body body, and are verified via mailserver signature verification: there are no special party attesters. We are working to ship more verifiable proofs of signed data including zk blind, and avoid terrible tragedy of the commons scenarios where instituition reputation is slowly spent by its members. VerifiedEmail uses ZK SNARKs to insinuate this social dynamic, with a first demo at zkemail.xyz.", "image": "data:image/svg+xml;base64,', - Base64.encode(bytes(svgOutput)), - '"}' - ) - ) - ) - ); - string memory output = string(abi.encodePacked("data:application/json;base64,", json)); - return output; + return NFTSVG.constructAndReturnSVG(username, tokenId, owner); } - // Unpacks uint256s into bytes and then extracts the non-zero characters - // Only extracts contiguous non-zero characters and ensures theres only 1 such state - // Note that unpackedLen may be more than packedBytes.length * 8 since there may be 0s - // TODO: Remove console.logs and define this as a pure function instead of a view - function convertPackedBytesToBytes(uint256[] memory packedBytes, uint256 maxBytes) public pure returns (string memory extractedString) { - uint8 state = 0; - // bytes: 0 0 0 0 y u s h _ g 0 0 0 - // state: 0 0 0 0 1 1 1 1 1 1 2 2 2 - bytes memory nonzeroBytesArray = new bytes(packedBytes.length * 7); - uint256 nonzeroBytesArrayIndex = 0; - for (uint16 i = 0; i < packedBytes.length; i++) { - uint256 packedByte = packedBytes[i]; - uint8[] memory unpackedBytes = new uint8[](bytesInPackedBytes); - for (uint j = 0; j < bytesInPackedBytes; j++) { - unpackedBytes[j] = uint8(packedByte >> (j * 8)); - } - for (uint256 j = 0; j < bytesInPackedBytes; j++) { - uint256 unpackedByte = unpackedBytes[j]; //unpackedBytes[j]; - // console.log(i, j, state, unpackedByte); - if (unpackedByte != 0) { - nonzeroBytesArray[nonzeroBytesArrayIndex] = bytes1(uint8(unpackedByte)); - nonzeroBytesArrayIndex++; - if (state % 2 == 0) { - state += 1; - } - } else { - if (state % 2 == 1) { - state += 1; - } - } - packedByte = packedByte >> 8; - } - } - string memory returnValue = string(nonzeroBytesArray); - require(state == 2, "Invalid final state of packed bytes in email"); - // console.log("Characters in username: ", nonzeroBytesArrayIndex); - require(nonzeroBytesArrayIndex <= maxBytes, "Twitter username more than 15 chars!"); - return returnValue; - // Have to end at the end of the email -- state cannot be 1 since there should be an email footer + function _domainCheck(uint256[] memory headerSignals) public pure returns (bool) { + string memory senderBytes = StringUtils.convertPackedBytesToBytes(headerSignals, 18, bytesInPackedBytes); + string[2] memory domainStrings = ["verify@twitter.com", "info@twitter.com"]; + return StringUtils.stringEq(senderBytes, domainStrings[0]) || StringUtils.stringEq(senderBytes, domainStrings[1]); + // Usage: require(_domainCheck(senderBytes, domainStrings), "Invalid domain"); } function _stringEq(string memory a, string memory b) public pure returns (bool) { @@ -162,14 +95,15 @@ contract VerifiedKYCEmail is ERC721Enumerable, Verifier { } function mint(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[msg_len] memory signals) public { + // TODO no invalid signal check yet, which is fine since the zk proof does it // Checks: Verify proof and check signals - // require(signals[0] == 1337, "invalid signals"); // TODO no invalid signal check yet, which is fine since the zk proof does it + // require(signals[0] == 1337, "invalid signals"); // 3 public signals are the masked packed message bytes, 17 are the modulus. - // uint256[] memory bodySignals = new uint256[](body_len); - // uint256[] memory rsaModulusSignals = new uint256[](msg_len); - // for (uint256 i = 0; i < body_len; i++) bodySignals[i] = signals[i]; - // for (uint256 i = 0; i < 2 * rsa_modulus_chunks_len; i++) rsaModulusSignals[i] = signals[i]; + uint256[] memory bodySignals = new uint256[](body_len); + uint256[] memory rsaModulusSignals = new uint256[](header_len); + for (uint256 i = 0; i < body_len; i++) bodySignals[i] = signals[i]; + for (uint256 i = body_len; i < msg_len - 1; i++) rsaModulusSignals[i - body_len] = signals[i]; // Check eth address committed to in proof matches msg.sender, to avoid replayability require(address(uint160(signals[addressIndexInSignals])) == msg.sender, "Invalid address"); @@ -179,20 +113,16 @@ contract VerifiedKYCEmail is ERC721Enumerable, Verifier { // We will upload the version with these domain checks soon! // require(_domainCheck(headerSignals), "Invalid domain"); - // Verify that the two addresses match (although it should be impossible that they are not equal) - require(signals[addressIndexInSignals - 1] == signals[addressIndexInSignals], "Invalid: addresses don't match"); - // Verify that the public key for RSA matches the hardcoded one - for (uint i = 0; i < rsa_modulus_chunks_len; i++) { - require(signals[i] == verifiedMailserverKeys[domain_airbnb][i], string(abi.encodePacked("Invalid: RSA modulus not matched for", domain_airbnb))); - require(signals[i + rsa_modulus_chunks_len] == verifiedMailserverKeys[domain_coinbase][i], string(abi.encodePacked("Invalid: RSA modulus not matched for", domain_coinbase))); + for (uint i = body_len; i < msg_len - 1; i++) { + require(signals[i] == verifiedMailserverKeys[domain][i - body_len], "Invalid: RSA modulus not matched"); } require(verifyProof(a, b, c, signals), "Invalid Proof"); // checks effects iteractions, this should come first // Effects: Mint token uint256 tokenId = tokenCounter.current() + 1; - // string memory messageBytes = convertPackedBytesToBytes(bodySignals, bytesInPackedBytes * body_len); - // tokenIDToName[tokenId] = messageBytes; + string memory messageBytes = StringUtils.convertPackedBytesToBytes(bodySignals, bytesInPackedBytes * body_len, bytesInPackedBytes); + tokenIDToName[tokenId] = messageBytes; _mint(msg.sender, tokenId); tokenCounter.increment(); } diff --git a/src/scripts/generate_input.ts b/src/scripts/generate_input.ts index c4f7946e0..4218e7354 100644 --- a/src/scripts/generate_input.ts +++ b/src/scripts/generate_input.ts @@ -32,7 +32,7 @@ async function getArgs() { const emailFileArg = args.find((arg) => arg.startsWith("--email_file=")); const nonceArg = args.find((arg) => arg.startsWith("--nonce=")); - const email_file = emailFileArg ? emailFileArg.split("=")[1] : "nathan_airbnb_email.eml"; + const email_file = emailFileArg ? emailFileArg.split("=")[1] : "test_sendgrid.eml"; const nonce = nonceArg ? nonceArg.split("=")[1] : null; return { email_file, nonce }; diff --git a/src/scripts/generate_two_inputs.ts b/src/scripts/generate_two_inputs.ts index 3b39985ac..be002b330 100644 --- a/src/scripts/generate_two_inputs.ts +++ b/src/scripts/generate_two_inputs.ts @@ -36,7 +36,7 @@ import { const emailFileArg = args.find((arg) => arg.startsWith("--email_file=")); const nonceArg = args.find((arg) => arg.startsWith("--nonce=")); - const email_file = emailFileArg ? emailFileArg.split("=")[1] : "nathan_airbnb_email.eml"; + const email_file = emailFileArg ? emailFileArg.split("=")[1] : "test_sendgrid.eml"; const nonce = nonceArg ? nonceArg.split("=")[1] : null; return { email_file, nonce }; @@ -343,21 +343,6 @@ import { console.log(Uint8Array.from(email_coinbase)); // Key difference: file load has 13 10, web version has just 10 } - - /* - async function make_input_file() { - const [circuitInputs_airbnb, circuitInputs_coinbase] = await Promise.all([do_generate(KYCType.AIRBNB), do_generate(KYCType.COINBASE)]); - fs.writeFileSync(`./circuits/inputs/input_airbnb.json`, JSON.stringify(circuitInputs_airbnb), { flag: "w"}); - fs.writeFileSync(`./circuits/inputs/input_coinbase.json`, JSON.stringify(circuitInputs_coinbase), { flag: "w"}); - - let input_kyc: {[key:string]: any} = {}; - for (const key in circuitInputs_airbnb) { - input_kyc[key.concat("_airbnb")] = circuitInputs_airbnb[key as keyof ICircuitInputs]; - input_kyc[key.concat("_coinbase")] = circuitInputs_coinbase[key as keyof ICircuitInputs]; - } - // fs.writeFileSync(`./circuits/inputs/input_kyc.json`, JSON.stringify(input_kyc), { flag: "w"}); - } - */ // If file called directly with `npx tsx src/scripts/generate_two_inputs.ts` if (typeof require !== "undefined" && require.main === module) { diff --git a/yarn.lock_old b/yarn.lock_old deleted file mode 100644 index 1dfd47165..000000000 --- a/yarn.lock_old +++ /dev/null @@ -1,11 +0,0 @@ -# This file is generated by running "yarn install" inside your project. -# Manual changes might be lost - proceed with caution! - -__metadata: - version: 6 - -"zk-email-verify@workspace:.": - version: 0.0.0-use.local - resolution: "zk-email-verify@workspace:." - languageName: unknown - linkType: soft diff --git a/yarn.lock_old2 b/yarn.lock_old2 deleted file mode 100644 index 1dfd47165..000000000 --- a/yarn.lock_old2 +++ /dev/null @@ -1,11 +0,0 @@ -# This file is generated by running "yarn install" inside your project. -# Manual changes might be lost - proceed with caution! - -__metadata: - version: 6 - -"zk-email-verify@workspace:.": - version: 0.0.0-use.local - resolution: "zk-email-verify@workspace:." - languageName: unknown - linkType: soft diff --git a/yarn.lock_old3 b/yarn.lock_old3 deleted file mode 100644 index 1dfd47165..000000000 --- a/yarn.lock_old3 +++ /dev/null @@ -1,11 +0,0 @@ -# This file is generated by running "yarn install" inside your project. -# Manual changes might be lost - proceed with caution! - -__metadata: - version: 6 - -"zk-email-verify@workspace:.": - version: 0.0.0-use.local - resolution: "zk-email-verify@workspace:." - languageName: unknown - linkType: soft From 89f79bb6bc7f8ebc813121cddaca4252b6d56b63 Mon Sep 17 00:00:00 2001 From: Nathan Xiong Date: Thu, 27 Apr 2023 12:51:28 -0400 Subject: [PATCH 07/25] added kyc-email-handler --- .gitignore | 3 + src/contracts/src/KYCEmailHandler.sol | 138 ++++++++++++++++++++++++++ src/helpers/dkim/tools.js | 2 +- 3 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 src/contracts/src/KYCEmailHandler.sol diff --git a/.gitignore b/.gitignore index bf3a23173..142a86e86 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,6 @@ generate_input_log.txt *.debug *.env .vscode + +# from zk-kyc +temp.py \ No newline at end of file diff --git a/src/contracts/src/KYCEmailHandler.sol b/src/contracts/src/KYCEmailHandler.sol new file mode 100644 index 000000000..d06393865 --- /dev/null +++ b/src/contracts/src/KYCEmailHandler.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; +import "@openzeppelin/contracts/utils/Counters.sol"; +import "forge-std/console.sol"; +// import "./base64.sol"; +import "./StringUtils.sol"; +import "./NFTSVG.sol"; +import "./Groth16VerifierTwitter.sol"; + +contract VerifiedKYCEmail is ERC721Enumerable, Verifier { + using Counters for Counters.Counter; + using StringUtils for *; + using NFTSVG for *; + + Counters.Counter private tokenCounter; + + uint16 public constant msg_len = 41; // change later when clipping rsa modulus + // uint public constant msg_len = 25 // change to for 1024-bit RSA + uint16 public constant bytesInPackedBytes = 7; // 7 bytes in a packed item returned from circom + uint256 public constant body_len = 5; + uint256 public constant rsa_modulus_chunks_len = 17; + // uint256 public constant rsa_modulus_chunks_len = 9; // change to for 1024-bit RSA + uint256 public constant header_len = msg_len - body_len; + uint256 public constant addressIndexInSignals = msg_len - 1; // TODO: fix constant + + mapping(string => uint256[rsa_modulus_chunks_len]) public verifiedMailserverKeys; + mapping(uint256 => string) public tokenIDToName; + string constant domain_airbnb = "airbnb.com"; + string constant domain_coinbase = "coinbase.com"; + + constructor() ERC721("VerifiedKYC", "VerifiedKYC") { + // Do dig TXT outgoing._domainkey.twitter.com to verify these. + // This is the base 2^121 representation of that key. + // Circom bigint: represent a = a[0] + a[1] * 2**n + .. + a[k - 1] * 2**(n * k) + require(2 * rsa_modulus_chunks_len + body_len + 2 == msg_len, "Variable counts are wrong!"); + + // TODO: Create a type that takes in a raw RSA key, the bit count, + // and whether or not its base64 encoded, and converts it to either 8 or 16 signals + verifiedMailserverKeys["airbnb.com"][0] = 1782267151472132502396673758441738163; + verifiedMailserverKeys["airbnb.com"][1] = 211482981992850046267405122085516466; + verifiedMailserverKeys["airbnb.com"][2] = 454331740279802979553218083106524093; + verifiedMailserverKeys["airbnb.com"][3] = 2403631535172814929511297080499227501; + verifiedMailserverKeys["airbnb.com"][4] = 2245858962887391502631714271235221261; + verifiedMailserverKeys["airbnb.com"][5] = 2622546081161044621195511843069142201; + verifiedMailserverKeys["airbnb.com"][6] = 1247628895302131918172499597775434966; + verifiedMailserverKeys["airbnb.com"][7] = 1584816411261150842617500336767389232; + verifiedMailserverKeys["airbnb.com"][8] = 52914273202064513; + verifiedMailserverKeys["airbnb.com"][9] = 0; + verifiedMailserverKeys["airbnb.com"][10] = 0; + verifiedMailserverKeys["airbnb.com"][11] = 0; + verifiedMailserverKeys["airbnb.com"][12] = 0; + verifiedMailserverKeys["airbnb.com"][13] = 0; + verifiedMailserverKeys["airbnb.com"][14] = 0; + verifiedMailserverKeys["airbnb.com"][15] = 0; + verifiedMailserverKeys["airbnb.com"][16] = 0; + + // TODO: Update coinbase public key + verifiedMailserverKeys["coinbase.com"][0] = 1345060269316532707410324038691477859; + verifiedMailserverKeys["coinbase.com"][1] = 384766469338727068594017962971556116; + verifiedMailserverKeys["coinbase.com"][2] = 168911276988157118943281324996362385; + verifiedMailserverKeys["coinbase.com"][3] = 1165220578700378509253846448878043993; + verifiedMailserverKeys["coinbase.com"][4] = 1468253564629208485538769233538980768; + verifiedMailserverKeys["coinbase.com"][5] = 2375057771089481827666297753868306658; + verifiedMailserverKeys["coinbase.com"][6] = 1859460967236870128489365675225233949; + verifiedMailserverKeys["coinbase.com"][7] = 2514159567794221963503259554592798082; + verifiedMailserverKeys["coinbase.com"][8] = 37369779987712517; + verifiedMailserverKeys["coinbase.com"][9] = 0; + verifiedMailserverKeys["coinbase.com"][10] = 0; + verifiedMailserverKeys["coinbase.com"][11] = 0; + verifiedMailserverKeys["coinbase.com"][12] = 0; + verifiedMailserverKeys["coinbase.com"][13] = 0; + verifiedMailserverKeys["coinbase.com"][14] = 0; + verifiedMailserverKeys["coinbase.com"][15] = 0; + verifiedMailserverKeys["coinbase.com"][16] = 0; + } + + // change to some KYC description + function tokenDesc(uint256 tokenId) public view returns (string memory) { + address address_owner = ownerOf(tokenId); + string memory result = string(abi.encodePacked(StringUtils.toString(address_owner), "has a valid zk-KYC")); + return result; + } + + // TODO: change this function + function tokenURI(uint256 tokenId) public view override returns (string memory) { + string memory username = tokenIDToName[tokenId]; + address owner = ownerOf(tokenId); + return NFTSVG.constructAndReturnSVG(username, tokenId, owner); + } + + function _domainCheck(uint256[] memory headerSignals) public pure returns (bool) { + string memory senderBytes = StringUtils.convertPackedBytesToBytes(headerSignals, 18, bytesInPackedBytes); + string[2] memory domainStrings = ["verify@twitter.com", "info@twitter.com"]; + return StringUtils.stringEq(senderBytes, domainStrings[0]) || StringUtils.stringEq(senderBytes, domainStrings[1]); + // Usage: require(_domainCheck(senderBytes, domainStrings), "Invalid domain"); + } + + function mint(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[msg_len] memory signals) public { + // TODO no invalid signal check yet, which is fine since the zk proof does it + // Checks: Verify proof and check signals + // require(signals[0] == 1337, "invalid signals"); + + // 3 public signals are the masked packed message bytes, 17 are the modulus. + uint256[] memory bodySignals = new uint256[](body_len); + uint256[] memory rsaModulusSignals = new uint256[](header_len); // why is this defined? + for (uint256 i = 0; i < body_len; i++) bodySignals[i] = signals[i]; + for (uint256 i = body_len; i < msg_len - 1; i++) rsaModulusSignals[i - body_len] = signals[i]; + + // Check eth address committed to in proof matches msg.sender, to avoid replayability + require(address(uint160(signals[addressIndexInSignals])) == msg.sender, "Invalid address"); + + // Check from/to email domains are correct [in this case, only from domain is checked] + // Right now, we just check that any email was received from anyone at Twitter, which is good enough for now + // We will upload the version with these domain checks soon! + // require(_domainCheck(headerSignals), "Invalid domain"); + + // Verify that the public key for RSA matches the hardcoded one + for (uint i = 0; i < rsa_modulus_chunks_len; i++) { + require(signals[body_len + i] == verifiedMailserverKeys[domain_airbnb][i], "Invalid: Airbnb RSA modulus not matched"); + require(signals[body_len + rsa_modulus_chunks_len + i] == verifiedMailserverKeys[domain_coinbase][i], "Invalid, Coinbase RSA modulus not matched"); + } + require(verifyProof(a, b, c, signals), "Invalid Proof"); // checks effects iteractions, this should come first + + // Effects: Mint token + uint256 tokenId = tokenCounter.current() + 1; + string memory messageBytes = StringUtils.convertPackedBytesToBytes(bodySignals, bytesInPackedBytes * body_len, bytesInPackedBytes); + tokenIDToName[tokenId] = messageBytes; + _mint(msg.sender, tokenId); + tokenCounter.increment(); + } + + function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal { + require(from == address(0), "Cannot transfer - VerifiedKYCEmail is soulbound"); + } +} \ No newline at end of file diff --git a/src/helpers/dkim/tools.js b/src/helpers/dkim/tools.js index 2f102161e..dbef15fd5 100644 --- a/src/helpers/dkim/tools.js +++ b/src/helpers/dkim/tools.js @@ -223,7 +223,7 @@ async function resolveDNSHTTP(name, type) { }) ); const out = await resp.json(); - return [out.Answer[0].data]; + return [out.Answer[out.Answer.length - 1].data]; } // from https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String From a1c89d4b5ba6af8a88185dbd5e26dfca244c6895 Mon Sep 17 00:00:00 2001 From: Nathan Xiong Date: Tue, 2 May 2023 16:42:10 -0400 Subject: [PATCH 08/25] refactored input generation --- circuits/email_both.circom | 17 +- package.json | 2 +- src/contracts/src/KYCEmailHandler.sol | 4 +- src/contracts/src/KYCEmailHandler.sol.ignore | 133 -------------- src/scripts/generate_two_inputs.ts | 174 +++++++++++++++---- yarn.lock | 104 +++++------ 6 files changed, 189 insertions(+), 245 deletions(-) delete mode 100644 src/contracts/src/KYCEmailHandler.sol.ignore diff --git a/circuits/email_both.circom b/circuits/email_both.circom index 0a56fbe29..2ce26242c 100644 --- a/circuits/email_both.circom +++ b/circuits/email_both.circom @@ -19,6 +19,9 @@ template KYCVerify(max_header_bytes, n, k) { var max_packed_bytes = (max_header_bytes - 1) \ 7 + 1; // ceil(max_header_bytes / 7) signal body_hash_concat[128]; // body hash output from each email has length 44, round up to lowest multiple of 64 + // ADDRESS INPUT SIGNALS + signal input address; + // AIRBNB INPUT SIGNALS signal input in_padded_airbnb[max_header_bytes]; // prehashed email data, includes up to 512 + 64? bytes of padding pre SHA256, and padded with lots of 0s at end after the length signal input modulus_airbnb[k]; // rsa pubkey, verified with smart contract + optional oracle @@ -26,7 +29,6 @@ template KYCVerify(max_header_bytes, n, k) { signal input in_len_padded_bytes_airbnb; // length of in email data including the padding, which will inform the sha256 block length signal input body_hash_idx_airbnb; signal input email_to_idx_airbnb; - signal input address_airbnb; // COINBASE INPUT SIGNALS signal input in_padded_coinbase[max_header_bytes]; // prehashed email data, includes up to 512 + 64? bytes of padding pre SHA256, and padded with lots of 0s at end after the length @@ -35,7 +37,6 @@ template KYCVerify(max_header_bytes, n, k) { signal input in_len_padded_bytes_coinbase; // length of in email data including the padding, which will inform the sha256 block length signal input body_hash_idx_coinbase; signal input email_to_idx_coinbase; - signal input address_coinbase; // OUTPUT SIGNALS // Outputs the hash of the two body hashes, which serves as the nullifier @@ -47,25 +48,25 @@ template KYCVerify(max_header_bytes, n, k) { component airbnb_verify = AirbnbEmailVerify(max_header_bytes, n, k); component coinbase_verify = CoinbaseEmailVerify(max_header_bytes, n, k); - // Airbnb email inputs + // AIRBNB EMAIL IMPUTS airbnb_verify.in_padded <== in_padded_airbnb; airbnb_verify.modulus <== modulus_airbnb; airbnb_verify.signature <== signature_airbnb; airbnb_verify.in_len_padded_bytes <== in_len_padded_bytes_airbnb; airbnb_verify.body_hash_idx <== body_hash_idx_airbnb; airbnb_verify.email_to_idx <== email_to_idx_airbnb; - airbnb_verify.address <== address_airbnb; + airbnb_verify.address <== address; - // Coinbase email inputs + // COINBASE EMAIL INPUTS coinbase_verify.in_padded <== in_padded_coinbase; coinbase_verify.modulus <== modulus_coinbase; coinbase_verify.signature <== signature_coinbase; coinbase_verify.in_len_padded_bytes <== in_len_padded_bytes_coinbase; coinbase_verify.body_hash_idx <== body_hash_idx_coinbase; coinbase_verify.email_to_idx <== email_to_idx_coinbase; - coinbase_verify.address <== address_coinbase; + coinbase_verify.address <== address; - // TO EMAILS MATCH + // CHECK TO-EMAILS MATCH // Check that the to emails match signal to_email_airbnb[max_header_bytes] <== airbnb_verify.to_email; signal to_email_coinbase[max_header_bytes] <== coinbase_verify.to_email; @@ -105,4 +106,4 @@ template KYCVerify(max_header_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. // TODO: change public signals in smart contract to match new public signals -component main { public [ modulus_airbnb, modulus_coinbase, address_airbnb, address_coinbase ] } = KYCVerify(1024, 121, 17); +component main { public [ modulus_airbnb, modulus_coinbase, address ] } = KYCVerify(1024, 121, 17); diff --git a/package.json b/package.json index ec4821833..d465990c2 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "react-use": "^17.3.2", "readline": "^1.3.0", "serve": "^14.0.1", - "snarkjs": "git+https://github.com/vb7401/snarkjs.git#24981febe8826b6ab76ae4d76cf7f9142919d2b8", + "snarkjs": "https://github.com/sampritipanda/snarkjs.git#fef81fc51d17a734637555c6edbd585ecda02d9e", "sshpk": "^1.17.0", "styled-components": "^5.3.5", "ts-node": "^10.9.1", diff --git a/src/contracts/src/KYCEmailHandler.sol b/src/contracts/src/KYCEmailHandler.sol index d06393865..00be579d9 100644 --- a/src/contracts/src/KYCEmailHandler.sol +++ b/src/contracts/src/KYCEmailHandler.sol @@ -17,7 +17,7 @@ contract VerifiedKYCEmail is ERC721Enumerable, Verifier { Counters.Counter private tokenCounter; - uint16 public constant msg_len = 41; // change later when clipping rsa modulus + uint16 public constant msg_len = 40; // change later when clipping rsa modulus // uint public constant msg_len = 25 // change to for 1024-bit RSA uint16 public constant bytesInPackedBytes = 7; // 7 bytes in a packed item returned from circom uint256 public constant body_len = 5; @@ -35,7 +35,7 @@ contract VerifiedKYCEmail is ERC721Enumerable, Verifier { // Do dig TXT outgoing._domainkey.twitter.com to verify these. // This is the base 2^121 representation of that key. // Circom bigint: represent a = a[0] + a[1] * 2**n + .. + a[k - 1] * 2**(n * k) - require(2 * rsa_modulus_chunks_len + body_len + 2 == msg_len, "Variable counts are wrong!"); + require(2 * rsa_modulus_chunks_len + body_len + 1 == msg_len, "Variable counts are wrong!"); // TODO: Create a type that takes in a raw RSA key, the bit count, // and whether or not its base64 encoded, and converts it to either 8 or 16 signals diff --git a/src/contracts/src/KYCEmailHandler.sol.ignore b/src/contracts/src/KYCEmailHandler.sol.ignore deleted file mode 100644 index 5bd475f62..000000000 --- a/src/contracts/src/KYCEmailHandler.sol.ignore +++ /dev/null @@ -1,133 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; -import "@openzeppelin/contracts/utils/Counters.sol"; -import "forge-std/console.sol"; -// import "./base64.sol"; -import "./StringUtils.sol"; -import "./NFTSVG.sol"; -import "./Groth16VerifierTwitter.sol"; - -contract VerifiedKYCEmail is ERC721Enumerable, Verifier { - using Counters for Counters.Counter; - using StringUtils for *; - using NFTSVG for *; - - Counters.Counter private tokenCounter; - - // uint16 public constant msg_len = 21; // header + body - uint16 public constant msg_len = 20; // two rsa moduli of length 9 as well as two (identical) addresses of length 1 - uint16 public constant bytesInPackedBytes = 7; // 7 bytes in a packed item returned from circom - // uint256 public constant body_len = 3; - // uint256 public constant body_len = 0; // no body - // uint256 public constant rsa_modulus_chunks_len = 17; - uint256 public constant rsa_modulus_chunks_len = 9; // 1024 bit RSA - // uint256 public constant header_len = msg_len - body_len; - uint256 public constant addressIndexInSignals = msg_len - 1; // TODO: FIX CONSTANT - - // mapping(string => uint256[rsa_modulus_chunks_len]) public verifiedMailserverKeys; - mapping(string => uint256[rsa_modulus_chunks_len]) public verifiedMailserverKeys; - mapping(uint256 => string) public tokenIDToName; - // string constant domain = "twitter.com"; - string constant domain_airbnb = "airbnb.com"; - string constant domain_coinbase = "coinbase.com"; - - constructor() ERC721("VerifiedKYC", "VerifiedKYC") { - // Do dig TXT outgoing._domainkey.twitter.com to verify these. - // This is the base 2^121 representation of that key. - // Circom bigint: represent a = a[0] + a[1] * 2**n + .. + a[k - 1] * 2**(n * k) - require(2 * rsa_modulus_chunks_len + 2 == msg_len, "Variable counts are wrong!"); - verifiedMailserverKeys["airbnb.com"][0] = 1782267151472132502396673758441738163; - verifiedMailserverKeys["airbnb.com"][1] = 211482981992850046267405122085516466; - verifiedMailserverKeys["airbnb.com"][2] = 454331740279802979553218083106524093; - verifiedMailserverKeys["airbnb.com"][3] = 2403631535172814929511297080499227501; - verifiedMailserverKeys["airbnb.com"][4] = 2245858962887391502631714271235221261; - verifiedMailserverKeys["airbnb.com"][5] = 2622546081161044621195511843069142201; - verifiedMailserverKeys["airbnb.com"][6] = 1247628895302131918172499597775434966; - verifiedMailserverKeys["airbnb.com"][7] = 1584816411261150842617500336767389232; - verifiedMailserverKeys["airbnb.com"][8] = 52914273202064513; - - verifiedMailserverKeys["coinbase.com"][0] = 1345060269316532707410324038691477859; - verifiedMailserverKeys["coinbase.com"][1] = 384766469338727068594017962971556116; - verifiedMailserverKeys["coinbase.com"][2] = 168911276988157118943281324996362385; - verifiedMailserverKeys["coinbase.com"][3] = 1165220578700378509253846448878043993; - verifiedMailserverKeys["coinbase.com"][4] = 1468253564629208485538769233538980768; - verifiedMailserverKeys["coinbase.com"][5] = 2375057771089481827666297753868306658; - verifiedMailserverKeys["coinbase.com"][6] = 1859460967236870128489365675225233949; - verifiedMailserverKeys["coinbase.com"][7] = 2514159567794221963503259554592798082; - verifiedMailserverKeys["coinbase.com"][8] = 37369779987712517; - } - - // change to some KYC description - function tokenDesc(uint256 tokenId) public view returns (string memory) { - // string memory twitter_username = tokenIDToName[tokenId]; - address address_owner = ownerOf(tokenId); - string memory result = string(abi.encodePacked(HexStrings.toString(address_owner), "has completed KYC")); - return result; - } - - function tokenURI(uint256 tokenId) public view override returns (string memory) { - string memory username = tokenIDToName[tokenId]; - address owner = ownerOf(tokenId); - return NFTSVG.constructAndReturnSVG(username, tokenId, owner); - } - - function _domainCheck(uint256[] memory headerSignals) public pure returns (bool) { - string memory senderBytes = StringUtils.convertPackedBytesToBytes(headerSignals, 18, bytesInPackedBytes); - string[2] memory domainStrings = ["verify@twitter.com", "info@twitter.com"]; - return StringUtils.stringEq(senderBytes, domainStrings[0]) || StringUtils.stringEq(senderBytes, domainStrings[1]); - // Usage: require(_domainCheck(senderBytes, domainStrings), "Invalid domain"); - } - - function _stringEq(string memory a, string memory b) public pure returns (bool) { - return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b)); - } - - // Need to change but doesn't seem to be called? - // TODO: Remove console.logs and define this as a pure function instead of a view - function _domainCheck(uint256[] memory headerSignals) public pure returns (bool) { - string memory senderBytes = convertPackedBytesToBytes(headerSignals, 18); - string[2] memory domainStrings = ["verify@twitter.com", "info@twitter.com"]; - return _stringEq(senderBytes, domainStrings[0]) || _stringEq(senderBytes, domainStrings[1]); - // Usage: require(_domainCheck(senderBytes, domainStrings), "Invalid domain"); - } - - function mint(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[msg_len] memory signals) public { - // TODO no invalid signal check yet, which is fine since the zk proof does it - // Checks: Verify proof and check signals - // require(signals[0] == 1337, "invalid signals"); - - // 3 public signals are the masked packed message bytes, 17 are the modulus. - uint256[] memory bodySignals = new uint256[](body_len); - uint256[] memory rsaModulusSignals = new uint256[](header_len); - for (uint256 i = 0; i < body_len; i++) bodySignals[i] = signals[i]; - for (uint256 i = body_len; i < msg_len - 1; i++) rsaModulusSignals[i - body_len] = signals[i]; - - // Check eth address committed to in proof matches msg.sender, to avoid replayability - require(address(uint160(signals[addressIndexInSignals])) == msg.sender, "Invalid address"); - - // Check from/to email domains are correct [in this case, only from domain is checked] - // Right now, we just check that any email was received from anyone at Twitter, which is good enough for now - // We will upload the version with these domain checks soon! - // require(_domainCheck(headerSignals), "Invalid domain"); - - // Verify that the public key for RSA matches the hardcoded one - for (uint i = body_len; i < msg_len - 1; i++) { - require(signals[i] == verifiedMailserverKeys[domain][i - body_len], "Invalid: RSA modulus not matched"); - } - require(verifyProof(a, b, c, signals), "Invalid Proof"); // checks effects iteractions, this should come first - - // Effects: Mint token - uint256 tokenId = tokenCounter.current() + 1; - string memory messageBytes = StringUtils.convertPackedBytesToBytes(bodySignals, bytesInPackedBytes * body_len, bytesInPackedBytes); - tokenIDToName[tokenId] = messageBytes; - _mint(msg.sender, tokenId); - tokenCounter.increment(); - } - - function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal { - require(from == address(0), "Cannot transfer - VerifiedKYCEmail is soulbound"); - } -} \ No newline at end of file diff --git a/src/scripts/generate_two_inputs.ts b/src/scripts/generate_two_inputs.ts index be002b330..f34b1fc03 100644 --- a/src/scripts/generate_two_inputs.ts +++ b/src/scripts/generate_two_inputs.ts @@ -62,6 +62,18 @@ import { currency_idx?: string; recipient_idx?: string; email_to_idx?: string; + in_padded_airbnb?: string[]; + modulus_airbnb?: string[]; + signature_airbnb?: string[]; + in_len_padded_bytes_airbnb?: string; + body_hash_idx_airbnb?: string; + email_to_idx_airbnb?: string; + in_padded_coinbase?: string[]; + modulus_coinbase?: string[]; + signature_coinbase?: string[]; + in_len_padded_bytes_coinbase?: string; + body_hash_idx_coinbase?: string; + email_to_idx_coinbase?: string; } enum CircuitType { @@ -70,11 +82,7 @@ import { TEST = "test", EMAIL = "email", SUBJECTPARSER = "subjectparser", - } - - enum KYCType { - AIRBNB = "airbnb", - COINBASE = "coinbase", + KYC = "kyc", } async function findSelector(a: Uint8Array, selector: number[]): Promise { @@ -102,7 +110,7 @@ import { body_hash: string, eth_address: string, circuit: CircuitType, - kyc: KYCType + preselector: string = STRING_PRESELECTOR, ): Promise<{ valid: { validSignatureFormat?: boolean; @@ -138,15 +146,8 @@ import { assert((await Uint8ArrayToString(shaOut)) === (await Uint8ArrayToString(Uint8Array.from(await shaHash(prehashBytesUnpadded)))), "SHA256 calculation did not match!"); // Precompute SHA prefix - let selector; - if (kyc === KYCType.AIRBNB) { - selector = STRING_PRESELECTOR_AIRBNB.split("").map((char) => char.charCodeAt(0)); - } else if (kyc === KYCType.COINBASE) { - selector = STRING_PRESELECTOR_COINBASE.split("").map((char) => char.charCodeAt(0)); - } else { - // this line is dumb as should never happen :/ - selector = STRING_PRESELECTOR.split("").map((char) => char.charCodeAt(0)); - } + + const selector = preselector.split("").map((char) => char.charCodeAt(0)); const selector_loc = await findSelector(bodyPadded, selector); console.log("Body selector found at: ", selector_loc); let shaCutoffIndex = Math.floor((await findSelector(bodyPadded, selector)) / 64) * 64; @@ -244,11 +245,11 @@ import { valid: {}, }; } - + // Nonce is useful to disambiguate files for input/output when calling from the command line, it is usually null or hash(email) - export async function generate_inputs(raw_email: Buffer | string, eth_address: string, kycType: KYCType, nonce_raw: number | null | string = null): Promise { + export async function generate_inputs(raw_email: Buffer | string, eth_address: string, nonce_raw: number | null | string = null): Promise { const nonce = typeof nonce_raw == "string" ? nonce_raw.trim() : nonce_raw; - + var result, email: Buffer; if (typeof raw_email === "string") { email = Buffer.from(raw_email); @@ -283,38 +284,139 @@ import { let body = result.results[0].body; let body_hash = result.results[0].bodyHash; let circuitType = CircuitType.EMAIL; - + let pubkey = result.results[0].publicKey; const pubKeyData = pki.publicKeyFromPem(pubkey.toString()); let modulus = BigInt(pubKeyData.n.toString()); - let fin_result = await getCircuitInputs(sig, modulus, message, body, body_hash, eth_address, circuitType, kycType); + let fin_result = await getCircuitInputs(sig, modulus, message, body, body_hash, eth_address, circuitType); + if (nonce !== null) { + console.log(`Writing to ../input_wallet_${nonce}.json`); + fs.writeFileSync(`../input_wallet_${nonce}.json`, JSON.stringify(fin_result.circuitInputs), { flag: "w" }); + } return fin_result.circuitInputs; } + + // Nonce is useful to disambiguate files for input/output when calling from the command line, it is usually null or hash(email) + export async function generate_inputs_kyc(raw_email_airbnb: Buffer | string, raw_email_coinbase: Buffer | string, eth_address: string, nonce_raw: number | null | string = null): Promise { + const nonce = typeof nonce_raw == "string" ? nonce_raw.trim() : nonce_raw; + + var result_airbnb, email_airbnb: Buffer; + if (typeof raw_email_airbnb === "string") { + email_airbnb = Buffer.from(raw_email_airbnb); + } else email_airbnb = raw_email_airbnb; + + var result_coinbase, email_coinbase: Buffer; + if (typeof raw_email_coinbase === "string") { + email_coinbase = Buffer.from(raw_email_coinbase); + } else email_coinbase = raw_email_coinbase + + console.log("DKIM verification for Airbnb starting"); + result_airbnb = await dkimVerify(email_airbnb); + if (!result_airbnb.results[0]) { + throw new Error(`No result found on dkim output ${result_airbnb}`); + } else { + if (!result_airbnb.results[0].publicKey) { + if (result_airbnb.results[0].status.message) { + throw new Error(result_airbnb.results[0].status.message); + } else { + throw new Error(`No public key found on generate_inputs result ${JSON.stringify(result_airbnb)}`); + } + } + } + const _airbnb = result_airbnb.results[0].publicKey.toString(); + console.log("DKIM verification for Airbnb successful"); + + console.log("DKIM verification for Coinbase starting"); + result_coinbase = await dkimVerify(email_coinbase); + if (!result_coinbase.results[0]) { + throw new Error(`No result found on dkim output ${result_coinbase}`); + } else { + if (!result_coinbase.results[0].publicKey) { + if (result_coinbase.results[0].status.message) { + throw new Error(result_coinbase.results[0].status.message); + } else { + throw new Error(`No public key found on generate_inputs result ${JSON.stringify(result_coinbase)}`); + } + } + } + const _coinbase = result_coinbase.results[0].publicKey.toString(); + console.log("DKIM verification for Coinbase successful"); + + // try { + // // TODO: Condiiton code on if there is an internet connection, run this code + // var frozen = Cryo.stringify(result); + // fs.writeFileSync(`./email_cache_2.json`, frozen, { flag: "w" }); + // } catch (e) {i + // console.log("Reading cached email instead!"); + // let frozen = fs.readFileSync(`./email_cache.json`, { encoding: "utf-8" }); + // result = Cryo.parse(frozen); + // } + let sig_airbnb = BigInt("0x" + Buffer.from(result_airbnb.results[0].signature, "base64").toString("hex")); + let message_airbnb = result_airbnb.results[0].status.signature_header; + let body_airbnb = result_airbnb.results[0].body; + let body_hash_airbnb = result_airbnb.results[0].bodyHash; + let pubkey_airbnb = result_airbnb.results[0].publicKey; + const pubKeyData_airbnb = pki.publicKeyFromPem(pubkey_airbnb.toString()); + let modulus_airbnb = BigInt(pubKeyData_airbnb.n.toString()); + + let sig_coinbase = BigInt("0x" + Buffer.from(result_coinbase.results[0].signature, "base64").toString("hex")); + let message_coinbase = result_coinbase.results[0].status.signature_header; + let body_coinbase = result_coinbase.results[0].body; + let body_hash_coinbase = result_coinbase.results[0].bodyHash; + let pubkey_coinbase = result_coinbase.results[0].publicKey; + const pubKeyData_coinbase = pki.publicKeyFromPem(pubkey_coinbase.toString()); + let modulus_coinbase = BigInt(pubKeyData_coinbase.n.toString()); + + let fin_result_airbnb = await getCircuitInputs(sig_airbnb, modulus_airbnb, message_airbnb, body_airbnb, body_hash_airbnb, eth_address, CircuitType.EMAIL, STRING_PRESELECTOR_AIRBNB); + let fin_result_coinbase = await getCircuitInputs(sig_coinbase, modulus_coinbase, message_coinbase, body_coinbase, body_hash_coinbase, eth_address, CircuitType.EMAIL, STRING_PRESELECTOR_COINBASE); + const inputs_airbnb = fin_result_airbnb.circuitInputs; + const inputs_coinbase = fin_result_coinbase.circuitInputs; + + return { + address: inputs_airbnb.address, + in_padded_airbnb: inputs_airbnb.in_padded, + modulus_airbnb: inputs_airbnb.modulus, + signature_airbnb: inputs_airbnb.signature, + in_len_padded_bytes_airbnb: inputs_airbnb.in_len_padded_bytes, + body_hash_idx_airbnb: inputs_airbnb.body_hash_idx, + email_to_idx_airbnb: inputs_airbnb.email_to_idx, + in_padded_coinbase: inputs_coinbase.in_padded, + modulus_coinbase: inputs_coinbase.modulus, + signature_coinbase: inputs_coinbase.signature, + in_len_padded_bytes_coinbase: inputs_coinbase.in_len_padded_bytes, + body_hash_idx_coinbase: inputs_coinbase.body_hash_idx, + email_to_idx_coinbase: inputs_coinbase.email_to_idx, + } + } // Only called when the whole function is called from the command line, to read inputs async function do_generate(writeToFile: boolean = true) { - // const { email_file, nonce } = await getArgs(); + const { email_file, nonce } = await getArgs(); + const email = fs.readFileSync(email_file.trim()); + console.log(email); + const gen_inputs = await generate_inputs(email, "0x0000000000000000000000000000000000000000", nonce); + console.log(JSON.stringify(gen_inputs)); + if (writeToFile) { + const filename = nonce ? `../input_${nonce}.json` : "./circuits/inputs/input.json"; + console.log(`Writing to default file ${filename}`); + fs.writeFileSync(filename, JSON.stringify(gen_inputs), { flag: "w" }); + } + return gen_inputs; + } + async function do_generate_kyc(writeToFile: boolean = true) { + // const { email_file, nonce } = await getArgs(); const email_airbnb = fs.readFileSync(email_file_airbnb.trim()); const email_coinbase = fs.readFileSync(email_file_coinbase.trim()); - const gen_inputs_airbnb = await generate_inputs(email_airbnb, "0x0000000000000000000000000000000000000000", KYCType.AIRBNB); - const gen_inputs_coinbase = await generate_inputs(email_coinbase, "0x0000000000000000000000000000000000000000", KYCType.COINBASE); - + // console.log(email); + const gen_inputs = await generate_inputs_kyc(email_airbnb, email_coinbase, "0x0000000000000000000000000000000000000000"); + console.log(JSON.stringify(gen_inputs)); if (writeToFile) { // const filename = nonce ? `../input_${nonce}.json` : "./circuits/inputs/input.json"; // console.log(`Writing to default file ${filename}`); - console.log("Writing to file ./circuits/inputs/input_airbnb.json"); - fs.writeFileSync(`./circuits/inputs/input_airbnb.json`, JSON.stringify(gen_inputs_airbnb), { flag: "w"}); - console.log("Writing to file ./circuits/inputs/input_coinbase.json"); - fs.writeFileSync(`./circuits/inputs/input_coinbase.json`, JSON.stringify(gen_inputs_coinbase), { flag: "w"}); - - let input_kyc: { [key: string]: any } = {}; - for (const key in gen_inputs_airbnb) { - input_kyc[key.concat("_airbnb")] = gen_inputs_airbnb[key as keyof ICircuitInputs]; - input_kyc[key.concat("_coinbase")] = gen_inputs_coinbase[key as keyof ICircuitInputs]; - } - fs.writeFileSync(`./circuits/inputs/input_kyc.json`, JSON.stringify(input_kyc), { flag: "w"}); + fs.writeFileSync(`./circuits/inputs/input_kyc.json`, JSON.stringify(gen_inputs), { flag: "w" }); } + return gen_inputs; } async function gen_test() { @@ -346,6 +448,6 @@ import { // If file called directly with `npx tsx src/scripts/generate_two_inputs.ts` if (typeof require !== "undefined" && require.main === module) { - do_generate(true); + do_generate_kyc(true); } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 35b35c744..ccda9df06 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2280,13 +2280,13 @@ __metadata: languageName: node linkType: hard -"@iden3/binfileutils@npm:0.0.10": - version: 0.0.10 - resolution: "@iden3/binfileutils@npm:0.0.10" +"@iden3/binfileutils@npm:0.0.11": + version: 0.0.11 + resolution: "@iden3/binfileutils@npm:0.0.11" dependencies: - fastfile: 0.0.19 + fastfile: 0.0.20 ffjavascript: ^0.2.48 - checksum: cdeb8ac01e12f485d9fb236654c00d5d5016fc89eae24f7822885dd42f09935cbef601dbdd8a0c96dfb00ded9f4f623e0eec0b568aa86d16522cf77ce6f9498b + checksum: ca61db1325c7e038c6bd723c856eff5f2c82c76394db09d3350ef4f5b7525e3c9ab1f7429900ff5d3e9d26c5970bf5900e6126ccb5c5caa597c16a47336a6be8 languageName: node linkType: hard @@ -5706,13 +5706,6 @@ __metadata: languageName: node linkType: hard -"big-integer@npm:^1.6.42, big-integer@npm:^1.6.48": - version: 1.6.51 - resolution: "big-integer@npm:1.6.51" - checksum: 3d444173d1b2e20747e2c175568bedeebd8315b0637ea95d75fd27830d3b8e8ba36c6af40374f36bdaea7b5de376dcada1b07587cb2a79a928fccdb6e6e3c518 - languageName: node - linkType: hard - "big.js@npm:^5.2.2": version: 5.2.2 resolution: "big.js@npm:5.2.2" @@ -6699,14 +6692,14 @@ __metadata: languageName: node linkType: hard -"circom_runtime@npm:0.1.17": - version: 0.1.17 - resolution: "circom_runtime@npm:0.1.17" +"circom_runtime@npm:0.1.21": + version: 0.1.21 + resolution: "circom_runtime@npm:0.1.21" dependencies: - ffjavascript: 0.2.48 + ffjavascript: 0.2.56 bin: calcwit: calcwit.js - checksum: 595fc0cc3a62ba5daf8d849feae41c48805c0df43965f85dde4dc434efb607e455fa7801d41c1feacfe0c3c71952a45cd3985abf26fde40c54138392891afd8c + checksum: 3071f1e0fba9a5fb41c940454edb911ce09edfd5d0bd12156ec79045a0bf3ff2cc5b35f46e84e42902ef8bb0a4166f428b75d0ceb363c0d485f1a111b27daba1 languageName: node linkType: hard @@ -8372,7 +8365,7 @@ __metadata: readline: ^1.3.0 selenium-webdriver: ^4.8.1 serve: ^14.0.1 - snarkjs: "git+https://github.com/vb7401/snarkjs.git#24981febe8826b6ab76ae4d76cf7f9142919d2b8" + snarkjs: "https://github.com/sampritipanda/snarkjs.git#fef81fc51d17a734637555c6edbd585ecda02d9e" sshpk: ^1.17.0 styled-components: ^5.3.5 ts-node: ^10.9.1 @@ -9786,10 +9779,10 @@ __metadata: languageName: node linkType: hard -"fastfile@npm:0.0.19, fastfile@npm:^0.0.19": - version: 0.0.19 - resolution: "fastfile@npm:0.0.19" - checksum: 6179bdd7c21be9882294dae66103795c099594098b51958bcf08a4545c91387321b43511730d0542a5a9ed8c5ec9069c065e065fd67255453ac900a23895dac1 +"fastfile@npm:0.0.20": + version: 0.0.20 + resolution: "fastfile@npm:0.0.20" + checksum: e5d6e5f57a9b58c9534202e477cbffbca2182c407171950695ddb5c3e6b89554bc8561fbb6e370c99e371a8f23486a23fbaca527827886cec4897d481cbd03b6 languageName: node linkType: hard @@ -9867,15 +9860,14 @@ __metadata: languageName: node linkType: hard -"ffjavascript@npm:0.2.48": - version: 0.2.48 - resolution: "ffjavascript@npm:0.2.48" +"ffjavascript@npm:0.2.56": + version: 0.2.56 + resolution: "ffjavascript@npm:0.2.56" dependencies: - big-integer: ^1.6.48 - wasmbuilder: ^0.0.12 - wasmcurves: 0.1.0 + wasmbuilder: 0.0.16 + wasmcurves: 0.2.0 web-worker: ^1.2.0 - checksum: 68beae9a4f642c06656685353b84fd7655020ca0e628ea046e94452ab779587953cc45cde106d74b68be7177b49c8f19b105d6552c4a1d715e784ae9e7c9ed34 + checksum: d4e02263db4a94d111cdc7c1211ae96769370f5c8c3c338331e0ef99faed7b55e640bedf23fa8a83fc9a77f0e81140ea8f32e392812a00e15ca504221b879a4f languageName: node linkType: hard @@ -16636,15 +16628,15 @@ __metadata: languageName: node linkType: hard -"r1csfile@npm:0.0.35": - version: 0.0.35 - resolution: "r1csfile@npm:0.0.35" +"r1csfile@npm:0.0.41": + version: 0.0.41 + resolution: "r1csfile@npm:0.0.41" dependencies: "@iden3/bigarray": 0.0.2 - "@iden3/binfileutils": 0.0.10 - fastfile: 0.0.19 - ffjavascript: 0.2.48 - checksum: 84f7b4eab5bcdd6a3f6d699998c9479a5eff8d670383d4f0c5afc08431f45353abab9a8b07eeabaef89807e24b0ba50611d4d6280eb6c3a7483e1487a91f0ac6 + "@iden3/binfileutils": 0.0.11 + fastfile: 0.0.20 + ffjavascript: 0.2.56 + checksum: eec689416f66f09db2d6ca66fac1ef6841b088ab29abcde487145ebd2110916c92583e11ac86f0cdcc4e8a3a7c7df9ff5352ad959e8ae385d37c3b51cec5cf4d languageName: node linkType: hard @@ -18384,23 +18376,24 @@ __metadata: languageName: node linkType: hard -"snarkjs@git+https://github.com/vb7401/snarkjs.git#24981febe8826b6ab76ae4d76cf7f9142919d2b8": - version: 0.4.12 - resolution: "snarkjs@https://github.com/vb7401/snarkjs.git#commit=24981febe8826b6ab76ae4d76cf7f9142919d2b8" +"snarkjs@https://github.com/sampritipanda/snarkjs.git#fef81fc51d17a734637555c6edbd585ecda02d9e": + version: 0.5.0 + resolution: "snarkjs@https://github.com/sampritipanda/snarkjs.git#commit=fef81fc51d17a734637555c6edbd585ecda02d9e" dependencies: - "@iden3/binfileutils": 0.0.10 + "@iden3/binfileutils": 0.0.11 + bfj: ^7.0.2 blake2b-wasm: ^2.4.0 - circom_runtime: 0.1.17 + circom_runtime: 0.1.21 ejs: ^3.1.6 - fastfile: ^0.0.19 - ffjavascript: 0.2.48 + fastfile: 0.0.20 + ffjavascript: 0.2.56 js-sha3: ^0.8.0 + localforage: ^1.10.0 logplease: ^1.2.15 - r1csfile: 0.0.35 - readline: ^1.3.0 + r1csfile: 0.0.41 bin: snarkjs: build/cli.cjs - checksum: 9011df4b58475a0b4ae988f8b459a9a4d2bb5d2b60221d0ec370a10f2492c88909768215f3b22e514b2cf24dca79818790447005a33ed6aee177b9fda6948a75 + checksum: f2050f0135d50d459ea0edddf3e394e833a2d28c6648e5889b2f896814865e5c60606e978a8a106bd5bfe7e27501c315f249db5b71895d5e7e6e9a87bfcd55ab languageName: node linkType: hard @@ -20553,25 +20546,6 @@ __metadata: languageName: node linkType: hard -"wasmbuilder@npm:^0.0.12": - version: 0.0.12 - resolution: "wasmbuilder@npm:0.0.12" - dependencies: - big-integer: ^1.6.48 - checksum: 327b3c50b0e1e5e3aac9e218e0f96fdc638b7952ab86acc2ad53960371996826dbb0a8095edce482cf1d9c245d96884449701909bc962920aa7ec8241db01214 - languageName: node - linkType: hard - -"wasmcurves@npm:0.1.0": - version: 0.1.0 - resolution: "wasmcurves@npm:0.1.0" - dependencies: - big-integer: ^1.6.42 - blakejs: ^1.1.0 - checksum: 6bf6719e659a88904af0b98d152316e3b22435ca6a2cfc8bbf4530576806f17b2776b2c7d91d1a678fe0d51485a0d1748efcd080808c181c7977bee50b26efa9 - languageName: node - linkType: hard - "wasmcurves@npm:0.2.0": version: 0.2.0 resolution: "wasmcurves@npm:0.2.0" From 50588742283463817b846dfe343ec7674a4f1c87 Mon Sep 17 00:00:00 2001 From: Nathan Xiong Date: Wed, 3 May 2023 01:39:42 -0400 Subject: [PATCH 09/25] deleted files and added verifier.sol file --- circuits/wallet.circom | 207 ---------- dizkus-scripts/1_compile_airbnb.sh | 18 - dizkus-scripts/1_compile_coinbase.sh | 18 - dizkus-scripts/1_compile_kyc.sh | 18 - dizkus-scripts/2_gen_wtns_airbnb.sh | 13 - dizkus-scripts/2_gen_wtns_coinbase.sh | 13 - dizkus-scripts/2_gen_wtns_kyc.sh | 13 - package.json | 2 +- src/components/NumberedStep.tsx | 80 +--- src/components/NumberedStep.tsx.ignore | 88 ++++ src/contracts/src/Groth16VerifierKYC.sol | 414 +++++++++++++++++++ src/contracts/src/Groth16VerifierWallet.sol | 423 -------------------- src/contracts/src/KYCEmailHandler.sol | 4 +- src/contracts/src/WalletEmailHandler.sol | 180 --------- src/pages/MainPage.tsx | 72 ++-- src/scripts/generate_two_inputs.ts | 3 +- yarn.lock | 104 +++-- 17 files changed, 635 insertions(+), 1035 deletions(-) delete mode 100644 circuits/wallet.circom delete mode 100755 dizkus-scripts/1_compile_airbnb.sh delete mode 100755 dizkus-scripts/1_compile_coinbase.sh delete mode 100755 dizkus-scripts/1_compile_kyc.sh delete mode 100755 dizkus-scripts/2_gen_wtns_airbnb.sh delete mode 100755 dizkus-scripts/2_gen_wtns_coinbase.sh delete mode 100755 dizkus-scripts/2_gen_wtns_kyc.sh create mode 100644 src/components/NumberedStep.tsx.ignore create mode 100644 src/contracts/src/Groth16VerifierKYC.sol delete mode 100644 src/contracts/src/Groth16VerifierWallet.sol delete mode 100644 src/contracts/src/WalletEmailHandler.sol diff --git a/circuits/wallet.circom b/circuits/wallet.circom deleted file mode 100644 index e4756d611..000000000 --- a/circuits/wallet.circom +++ /dev/null @@ -1,207 +0,0 @@ -pragma circom 2.1.5; - -include "../node_modules/circomlib/circuits/bitify.circom"; -include "./helpers/sha.circom"; -include "./helpers/rsa.circom"; -include "./helpers/base64.circom"; -include "./helpers/extract.circom"; - -include "./regexes/from_regex.circom"; -include "./regexes/tofrom_domain_regex.circom"; -include "./regexes/body_hash_regex.circom"; -include "./regexes/twitter_reset_regex.circom"; -include "./regexes/subject_regex.circom"; - - -// Here, n and k are the biginteger parameters for RSA -// This is because the number is chunked into k pack_size of n bits each -// Max header bytes shouldn't need to be changed much per email, -// but the max mody bytes may need to be changed to be larger if the email has a lot of i.e. HTML formatting -// TODO: split into header and body -template EmailVerify(max_header_bytes, max_body_bytes, n, k, pack_size, expose_from, expose_to) { - assert(max_header_bytes % 64 == 0); - assert(max_body_bytes % 64 == 0); - assert(expose_from < 2); // 1 if we should expose the from, 0 if we should not - assert(expose_to == 0); // 1 if we should expose the to, 0 if we should not: due to hotmail restrictions, we force-disable this - assert(n * k > 2048); // constraints for 2048 bit RSA - assert(n < (255 \ 2)); // we want a multiplication to fit into a circom signal - - signal input in_padded[max_header_bytes]; // prehashed email data, includes up to 512 + 64? bytes of padding pre SHA256, and padded with lots of 0s at end after the length - signal input modulus[k]; // rsa pubkey, verified with smart contract + DNSSEC proof. split up into k parts of n bits each. - signal input signature[k]; // rsa signature. split up into k parts of n bits each. - signal input in_len_padded_bytes; // length of in email data including the padding, which will inform the sha256 block length - - // Precomputed sha vars for big body hashing - // Next 3 signals are for decreasing SHA constraints for parsing out information from the in-body text - // The precomputed_sha value is the Merkle-Damgard state of our SHA hash uptil our first regex match - // This allows us to save a ton of SHA constraints by only hashing the relevant part of the body - // It doesn't have an impact on security since a user must have known the pre-image of a signed message to be able to fake it - // The lower two body signals describe the suffix of the body that we care about - // The part before these signals, a significant prefix of the body, has been pre-hashed into precomputed_sha. - // signal input precomputed_sha[32]; - // signal input in_body_padded[max_body_bytes]; - // signal input in_body_len_padded_bytes; - - // Header reveal vars - // TODO: In reality, this max value is 320, and would allow people to break our gaurantees and spoof arbitrary email addresses by registering disgustingly subdomains and going past the end of the 30 - var max_subject_amount_len = 30; - var max_subject_amount_packed_bytes = count_packed(max_subject_amount_len, pack_size); - var max_subject_currency_len = 5; - var max_subject_currency_packed_bytes = count_packed(max_subject_currency_len, pack_size); - var max_subject_recipient_len = 30; - var max_subject_recipient_packed_bytes = count_packed(max_subject_recipient_len, pack_size); - - signal input amount_idx; - signal input currency_idx; - signal input recipient_idx; - signal output reveal_amount_packed[max_subject_amount_packed_bytes]; // packed into 7-bytes. TODO: make this rotate to take up even less space - signal output reveal_currency_packed[max_subject_currency_packed_bytes]; // packed into 7-bytes. TODO: make this rotate to take up even less space - signal output reveal_recipient_packed[max_subject_recipient_packed_bytes]; // packed into 7-bytes. TODO: make this rotate to take up even less space - - // Body reveal vars - // var max_twitter_len = 21; - // var max_twitter_packed_bytes = count_packed(max_twitter_len, pack_size); // ceil(max_num_bytes / 7) - // signal input twitter_username_idx; - // signal output reveal_twitter_packed[max_twitter_packed_bytes]; - - // Identity commitment variables - // (note we don't need to constrain the +1 due to https://geometry.xyz/notebook/groth16-malleability) - signal input address; - signal input address_plus_one; - - // Base 64 body hash variables - var LEN_SHA_B64 = 44; // ceil(32/3) * 4, due to base64 encoding. - signal input body_hash_idx; - - // SHA HEADER: 506,670 constraints - // This calculates the SHA256 hash of the header, which is the "base_msg" that is RSA signed. - // The header signs the fields in the "h=Date:From:To:Subject:MIME-Version:Content-Type:Message-ID;" - // section of the "DKIM-Signature:"" line, along with the body hash. - // Note that nothing above the "DKIM-Signature:" line is signed. - component sha = Sha256Bytes(max_header_bytes); - sha.in_padded <== in_padded; - sha.in_len_padded_bytes <== in_len_padded_bytes; - var msg_len = (256+n)\n; - - component base_msg[msg_len]; - for (var i = 0; i < msg_len; i++) { - base_msg[i] = Bits2Num(n); - } - for (var i = 0; i < 256; i++) { - base_msg[i\n].in[i%n] <== sha.out[255 - i]; - } - for (var i = 256; i < n*msg_len; i++) { - base_msg[i\n].in[i%n] <== 0; - } - - // VERIFY RSA SIGNATURE: 149,251 constraints - // The fields that this signature actually signs are defined as the body and the values in the header - component rsa = RSAVerify65537(n, k); - for (var i = 0; i < msg_len; i++) { - rsa.base_message[i] <== base_msg[i].out; - } - for (var i = msg_len; i < k; i++) { - rsa.base_message[i] <== 0; - } - rsa.modulus <== modulus; - rsa.signature <== signature; - - // FROM HEADER REGEX: 736,553 constraints - // This extracts the from email, and the precise regex format can be viewed in the README - if(expose_from){ - var max_email_from_len = 30; - var max_email_from_packed_bytes = count_packed(max_email_from_len, pack_size); - assert(max_email_from_packed_bytes < max_header_bytes); - - signal input email_from_idx; - signal output reveal_email_from_packed[max_email_from_packed_bytes]; // packed into 7-bytes. TODO: make this rotate to take up even less space - - signal from_regex_out, from_regex_reveal[max_header_bytes]; - (from_regex_out, from_regex_reveal) <== FromRegex(max_header_bytes)(in_padded); - log(from_regex_out); - from_regex_out === 1; - reveal_email_from_packed <== ShiftAndPack(max_header_bytes, max_email_from_len, pack_size)(from_regex_reveal, email_from_idx); - } - -/* - // TO HEADER REGEX: 736,553 constraints - // This extracts the to email, and the precise regex format can be viewed in the README -*/ // We cannot use to: field at all due to Hotmail - if(expose_to){ - // var max_email_to_len = 30; - // var max_email_to_packed_bytes = count_packed(max_email_to_len, pack_size); - // assert(max_email_to_packed_bytes < max_header_bytes); - - // signal input email_to_idx; - // signal output reveal_email_to_packed[max_email_to_packed_bytes]; // packed into 7-bytes. TODO: make this rotate to take up even less space - - // signal to_regex_out, to_regex_reveal[max_header_bytes]; - // (to_regex_out, to_regex_reveal) <== ToRegex(max_header_bytes)(in_padded); - // to_regex_out === 1; - // reveal_email_to_packed <== ShiftAndPack(max_header_bytes, max_email_to_len, pack_size)(to_regex_reveal, email_to_idx); - } - - // SUBJECT HEADER REGEX: 736,553 constraints - // This extracts the subject, and the precise regex format can be viewed in the README - signal subject_regex_out, subject_regex_reveal_amount[max_header_bytes], subject_regex_reveal_currency[max_header_bytes], subject_regex_reveal_recipient[max_header_bytes]; - (subject_regex_out, subject_regex_reveal_amount, subject_regex_reveal_currency, subject_regex_reveal_recipient) <== WalletSubjectRegex(max_header_bytes)(in_padded); - log(subject_regex_out); - subject_regex_out === 1; - - reveal_amount_packed <== ShiftAndPack(max_header_bytes, max_subject_amount_len, pack_size)(subject_regex_reveal_amount, amount_idx); - reveal_currency_packed <== ShiftAndPack(max_header_bytes, max_subject_currency_len, pack_size)(subject_regex_reveal_currency, currency_idx); - reveal_recipient_packed <== ShiftAndPack(max_header_bytes, max_subject_recipient_len, pack_size)(subject_regex_reveal_recipient, recipient_idx); - - // BODY HASH REGEX: 617,597 constraints - // This extracts the body hash from the header (i.e. the part after bh= within the DKIM-signature section) - // which is used to verify the body text matches this signed hash + the signature verifies this hash is legit - signal bh_regex_out, bh_reveal[max_header_bytes]; - (bh_regex_out, bh_reveal) <== BodyHashRegex(max_header_bytes)(in_padded); - bh_regex_out === 1; - signal shifted_bh_out[LEN_SHA_B64] <== VarShiftLeft(max_header_bytes, LEN_SHA_B64)(bh_reveal, body_hash_idx); - // log(body_hash_regex.out); - -/* // We don't need any body parsing for email wallet - // SHA BODY: 760,142 constraints - // This verifies that the hash of the body, when calculated from the precomputed part forwards, - // actually matches the hash in the header - signal sha_body_out[256] <== Sha256BytesPartial(max_body_bytes)(in_body_padded, in_body_len_padded_bytes, precomputed_sha); - signal sha_b64_out[32] <== Base64Decode(32)(shifted_bh_out); - - // When we convert the manually hashed email sha_body into bytes, it matches the - // base64 decoding of the final hash state that the signature signs (sha_b64) - component sha_body_bytes[32]; - for (var i = 0; i < 32; i++) { - sha_body_bytes[i] = Bits2Num(8); - for (var j = 0; j < 8; j++) { - sha_body_bytes[i].in[7-j] <== sha_body_out[i*8+j]; - } - sha_body_bytes[i].out === sha_b64_out[i]; - } - - // TWITTER REGEX: 328,044 constraints - // This computes the regex states on each character in the email body. For new emails, this is the - // section that you want to swap out via using the zk-regex library. - signal twitter_regex_out, twitter_regex_reveal[max_body_bytes]; - (twitter_regex_out, twitter_regex_reveal) <== TwitterResetRegex(max_body_bytes)(in_body_padded); - // This ensures we found a match at least once (i.e. match count is not zero) - signal is_found_twitter <== IsZero()(twitter_regex_out); - is_found_twitter === 0; - - // PACKING: 16,800 constraints (Total: 3,115,057) - reveal_twitter_packed <== ShiftAndPack(max_body_bytes, max_twitter_len, pack_size)(twitter_regex_reveal, twitter_username_idx); -*/ -} - -// 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. - -// Args: -// * max_header_bytes = 1024 is the max number of bytes in the header -// * max_body_bytes = 1536 is the max number of bytes in the body after precomputed slice -// * n = 121 is the number of bits in each chunk of the modulus (RSA parameter) -// * k = 17 is the number of chunks in the modulus (RSA parameter) -// * pack_size = 7 is the number of bytes that can fit into a 255ish bit signal (can increase later) -// * expose_from = 1 is whether to expose the from email address -// * expose_to = 0 is whether to expose the to email (not recommended) -component main { public [ modulus, address ] } = EmailVerify(1024, 1536, 121, 17, 7, 1, 0); diff --git a/dizkus-scripts/1_compile_airbnb.sh b/dizkus-scripts/1_compile_airbnb.sh deleted file mode 100755 index 1aec5cc4b..000000000 --- a/dizkus-scripts/1_compile_airbnb.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -CIRCUIT_NAME=email_airbnb -BUILD_DIR="../build/$CIRCUIT_NAME" - -if [ ! -d "$BUILD_DIR" ]; then - echo "No build directory found. Creating build directory..." - mkdir -p "$BUILD_DIR" -fi - -echo '****COMPILING CIRCUIT****' -start=`date +%s` -set -x -circom "../circuits/$CIRCUIT_NAME".circom --r1cs --wasm --sym --c --wat --output "$BUILD_DIR" -{ set +x; } 2>/dev/null -end=`date +%s` -echo "DONE ($((end-start))s)" -echo diff --git a/dizkus-scripts/1_compile_coinbase.sh b/dizkus-scripts/1_compile_coinbase.sh deleted file mode 100755 index cbbec6a65..000000000 --- a/dizkus-scripts/1_compile_coinbase.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -CIRCUIT_NAME=email_coinbase -BUILD_DIR="../build/$CIRCUIT_NAME" - -if [ ! -d "$BUILD_DIR" ]; then - echo "No build directory found. Creating build directory..." - mkdir -p "$BUILD_DIR" -fi - -echo '****COMPILING CIRCUIT****' -start=`date +%s` -set -x -circom "../circuits/$CIRCUIT_NAME".circom --r1cs --wasm --sym --c --wat --output "$BUILD_DIR" -{ set +x; } 2>/dev/null -end=`date +%s` -echo "DONE ($((end-start))s)" -echo diff --git a/dizkus-scripts/1_compile_kyc.sh b/dizkus-scripts/1_compile_kyc.sh deleted file mode 100755 index 4ba8ce8d3..000000000 --- a/dizkus-scripts/1_compile_kyc.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -CIRCUIT_NAME=email_both -BUILD_DIR="../build/$CIRCUIT_NAME" - -if [ ! -d "$BUILD_DIR" ]; then - echo "No build directory found. Creating build directory..." - mkdir -p "$BUILD_DIR" -fi - -echo '****COMPILING CIRCUIT****' -start=`date +%s` -set -x -circom "../circuits/$CIRCUIT_NAME".circom --r1cs --wasm --sym --c --wat --output "$BUILD_DIR" -{ set +x; } 2>/dev/null -end=`date +%s` -echo "DONE ($((end-start))s)" -echo diff --git a/dizkus-scripts/2_gen_wtns_airbnb.sh b/dizkus-scripts/2_gen_wtns_airbnb.sh deleted file mode 100755 index f3f38e1e0..000000000 --- a/dizkus-scripts/2_gen_wtns_airbnb.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -CIRCUIT_NAME=email_airbnb -BUILD_DIR="../build/$CIRCUIT_NAME" - -echo "****GENERATING WITNESS FOR SAMPLE INPUT****" -start=`date +%s` -set -x -node "$BUILD_DIR"/"$CIRCUIT_NAME"_js/generate_witness.js "$BUILD_DIR"/"$CIRCUIT_NAME"_js/"$CIRCUIT_NAME".wasm ../circuits/inputs/input_airbnb.json "$BUILD_DIR"/witness.wtns -{ set +x; } 2>/dev/null -end=`date +%s` -echo "DONE ($((end-start))s)" -echo diff --git a/dizkus-scripts/2_gen_wtns_coinbase.sh b/dizkus-scripts/2_gen_wtns_coinbase.sh deleted file mode 100755 index f52bdcf25..000000000 --- a/dizkus-scripts/2_gen_wtns_coinbase.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -CIRCUIT_NAME=email_coinbase -BUILD_DIR="../build/$CIRCUIT_NAME" - -echo "****GENERATING WITNESS FOR SAMPLE INPUT****" -start=`date +%s` -set -x -node "$BUILD_DIR"/"$CIRCUIT_NAME"_js/generate_witness.js "$BUILD_DIR"/"$CIRCUIT_NAME"_js/"$CIRCUIT_NAME".wasm ../circuits/inputs/input_coinbase.json "$BUILD_DIR"/witness.wtns -{ set +x; } 2>/dev/null -end=`date +%s` -echo "DONE ($((end-start))s)" -echo diff --git a/dizkus-scripts/2_gen_wtns_kyc.sh b/dizkus-scripts/2_gen_wtns_kyc.sh deleted file mode 100755 index d0862a961..000000000 --- a/dizkus-scripts/2_gen_wtns_kyc.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -CIRCUIT_NAME=email_both -BUILD_DIR="../build/$CIRCUIT_NAME" - -echo "****GENERATING WITNESS FOR SAMPLE INPUT****" -start=`date +%s` -set -x -node "$BUILD_DIR"/"$CIRCUIT_NAME"_js/generate_witness.js "$BUILD_DIR"/"$CIRCUIT_NAME"_js/"$CIRCUIT_NAME".wasm ../circuits/inputs/input_kyc.json "$BUILD_DIR"/witness.wtns -{ set +x; } 2>/dev/null -end=`date +%s` -echo "DONE ($((end-start))s)" -echo diff --git a/package.json b/package.json index d465990c2..ec4821833 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "react-use": "^17.3.2", "readline": "^1.3.0", "serve": "^14.0.1", - "snarkjs": "https://github.com/sampritipanda/snarkjs.git#fef81fc51d17a734637555c6edbd585ecda02d9e", + "snarkjs": "git+https://github.com/vb7401/snarkjs.git#24981febe8826b6ab76ae4d76cf7f9142919d2b8", "sshpk": "^1.17.0", "styled-components": "^5.3.5", "ts-node": "^10.9.1", diff --git a/src/components/NumberedStep.tsx b/src/components/NumberedStep.tsx index 3d3b1a76b..61b6aaa69 100644 --- a/src/components/NumberedStep.tsx +++ b/src/components/NumberedStep.tsx @@ -1,65 +1,22 @@ -import React, { useState } from "react"; import styled from "styled-components"; -import { CenterAllDiv, Col, Row } from "./Layout"; -import "./NNS.css"; - -type Step = { - title: string; - description: string; -}; - -const Step = ({ index, title, description, active, onClick }: any) => ( -
onClick(index + 1)}> - {index + 1} - {title} - {active && {description}} -
- -); +import { CenterAllDiv, Row } from "./Layout"; export const NumberedStep: React.FC<{ - steps: Step[]; -}> = ({ steps }) => { - const [activeStep, setActiveStep] = useState(0); - - const handleClick = (index: number) => { - if (index == activeStep) { - setActiveStep(0) - } else { - setActiveStep(index); - }; - }; - - const progress = ((activeStep + 1) / steps.length) * 100; - + step: number; + children: React.ReactNode; +}> = ({ step, children }) => { return ( - -
- - {steps.map((step, index) => ( - - ))} - -
+ + + {step} + + {children} + ); }; -const NNSSteps = styled(CenterAllDiv)` - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 16px; - margin-top: 2rm; -`; - -const NNSContainer = styled(Col)` +const NumberedStepContainer = styled(Row)` + background: rgba(255, 255, 255, 0.05); width: 100%; gap: 1rem; border-radius: 4px; @@ -67,7 +24,7 @@ const NNSContainer = styled(Col)` color: #fff; `; -const StepCount = styled(CenterAllDiv)` +const NumberedStepLabel = styled(CenterAllDiv)` background: rgba(255, 255, 255, 0.2); border-radius: 4px; width: 24px; @@ -76,13 +33,4 @@ const StepCount = styled(CenterAllDiv)` border: 1px solid rgba(255, 255, 255, 0.3); `; -const StepTitle = styled(CenterAllDiv)` - font-size: 18px; - font-weight: bold; -`; - -const StepDescription = styled(CenterAllDiv)` - font-size: 14px; -`; - -// export default ProgressBar; +const NumberedStepText = styled.span``; \ No newline at end of file diff --git a/src/components/NumberedStep.tsx.ignore b/src/components/NumberedStep.tsx.ignore new file mode 100644 index 000000000..3d3b1a76b --- /dev/null +++ b/src/components/NumberedStep.tsx.ignore @@ -0,0 +1,88 @@ +import React, { useState } from "react"; +import styled from "styled-components"; +import { CenterAllDiv, Col, Row } from "./Layout"; +import "./NNS.css"; + +type Step = { + title: string; + description: string; +}; + +const Step = ({ index, title, description, active, onClick }: any) => ( +
onClick(index + 1)}> + {index + 1} + {title} + {active && {description}} +
+ +); + +export const NumberedStep: React.FC<{ + steps: Step[]; +}> = ({ steps }) => { + const [activeStep, setActiveStep] = useState(0); + + const handleClick = (index: number) => { + if (index == activeStep) { + setActiveStep(0) + } else { + setActiveStep(index); + }; + }; + + const progress = ((activeStep + 1) / steps.length) * 100; + + return ( + +
+ + {steps.map((step, index) => ( + + ))} + +
+ ); +}; + +const NNSSteps = styled(CenterAllDiv)` + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 16px; + margin-top: 2rm; +`; + +const NNSContainer = styled(Col)` + width: 100%; + gap: 1rem; + border-radius: 4px; + padding: 8px; + color: #fff; +`; + +const StepCount = styled(CenterAllDiv)` + background: rgba(255, 255, 255, 0.2); + border-radius: 4px; + width: 24px; + height: 24px; + min-width: 24px; + border: 1px solid rgba(255, 255, 255, 0.3); +`; + +const StepTitle = styled(CenterAllDiv)` + font-size: 18px; + font-weight: bold; +`; + +const StepDescription = styled(CenterAllDiv)` + font-size: 14px; +`; + +// export default ProgressBar; diff --git a/src/contracts/src/Groth16VerifierKYC.sol b/src/contracts/src/Groth16VerifierKYC.sol new file mode 100644 index 000000000..e544eb23e --- /dev/null +++ b/src/contracts/src/Groth16VerifierKYC.sol @@ -0,0 +1,414 @@ +// +// Copyright 2017 Christian Reitwiessner +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// 2019 OKIMS +// ported to solidity 0.6 +// fixed linter warnings +// added requiere error messages +// +// +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.6.11; + +library Pairing { + struct G1Point { + uint256 X; + uint256 Y; + } + // Encoding of field elements is: X[0] * z + X[1] + + struct G2Point { + uint256[2] X; + uint256[2] Y; + } + + /// @return the generator of G1 + function P1() internal pure returns (G1Point memory) { + return G1Point(1, 2); + } + + /// @return the generator of G2 + function P2() internal pure returns (G2Point memory) { + // Original code point + return + G2Point( + [11559732032986387107991004021392285783925812861821192530917403151452391805634, 10857046999023057135944570762232829481370756359578518086990519993285655852781], + [4082367875863433681332203403145435568316851327593401208105741076214120093531, 8495653923123431417604973247489272438418190587263600148770280649306958101930] + ); + + /* + // Changed by Jordi point + return G2Point( + [10857046999023057135944570762232829481370756359578518086990519993285655852781, + 11559732032986387107991004021392285783925812861821192530917403151452391805634], + [8495653923123431417604973247489272438418190587263600148770280649306958101930, + 4082367875863433681332203403145435568316851327593401208105741076214120093531] + );*/ + } + + /// @return r the negation of p, i.e. p.addition(p.negate()) should be zero. + function negate(G1Point memory p) internal pure returns (G1Point memory r) { + // The prime q in the base field F_q for G1 + uint256 q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + if (p.X == 0 && p.Y == 0) return G1Point(0, 0); + return G1Point(p.X, q - (p.Y % q)); + } + + /// @return r the sum of two points of G1 + function addition(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) { + uint256[4] memory input; + input[0] = p1.X; + input[1] = p1.Y; + input[2] = p2.X; + input[3] = p2.Y; + bool success; + // solium-disable-next-line security/no-inline-assembly + assembly { + success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60) + // Use "invalid" to make gas estimation work + switch success + case 0 { + invalid() + } + } + require(success, "pairing-add-failed"); + } + + /// @return r the product of a point on G1 and a scalar, i.e. + /// p == p.scalar_mul(1) and p.addition(p) == p.scalar_mul(2) for all points p. + function scalar_mul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) { + uint256[3] memory input; + input[0] = p.X; + input[1] = p.Y; + input[2] = s; + bool success; + // solium-disable-next-line security/no-inline-assembly + assembly { + success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60) + // Use "invalid" to make gas estimation work + switch success + case 0 { + invalid() + } + } + require(success, "pairing-mul-failed"); + } + + /// @return the result of computing the pairing check + /// e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1 + /// For example pairing([P1(), P1().negate()], [P2(), P2()]) should + /// return true. + function pairing(G1Point[] memory p1, G2Point[] memory p2) internal view returns (bool) { + require(p1.length == p2.length, "pairing-lengths-failed"); + uint256 elements = p1.length; + uint256 inputSize = elements * 6; + uint256[] memory input = new uint256[](inputSize); + for (uint256 i = 0; i < elements; i++) { + input[i * 6 + 0] = p1[i].X; + input[i * 6 + 1] = p1[i].Y; + input[i * 6 + 2] = p2[i].X[0]; + input[i * 6 + 3] = p2[i].X[1]; + input[i * 6 + 4] = p2[i].Y[0]; + input[i * 6 + 5] = p2[i].Y[1]; + } + uint256[1] memory out; + bool success; + // solium-disable-next-line security/no-inline-assembly + assembly { + success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20) + // Use "invalid" to make gas estimation work + switch success + case 0 { + invalid() + } + } + require(success, "pairing-opcode-failed"); + return out[0] != 0; + } + + /// Convenience method for a pairing check for two pairs. + function pairingProd2(G1Point memory a1, G2Point memory a2, G1Point memory b1, G2Point memory b2) internal view returns (bool) { + G1Point[] memory p1 = new G1Point[](2); + G2Point[] memory p2 = new G2Point[](2); + p1[0] = a1; + p1[1] = b1; + p2[0] = a2; + p2[1] = b2; + return pairing(p1, p2); + } + + /// Convenience method for a pairing check for three pairs. + function pairingProd3(G1Point memory a1, G2Point memory a2, G1Point memory b1, G2Point memory b2, G1Point memory c1, G2Point memory c2) internal view returns (bool) { + G1Point[] memory p1 = new G1Point[](3); + G2Point[] memory p2 = new G2Point[](3); + p1[0] = a1; + p1[1] = b1; + p1[2] = c1; + p2[0] = a2; + p2[1] = b2; + p2[2] = c2; + return pairing(p1, p2); + } + + /// Convenience method for a pairing check for four pairs. + function pairingProd4( + G1Point memory a1, + G2Point memory a2, + G1Point memory b1, + G2Point memory b2, + G1Point memory c1, + G2Point memory c2, + G1Point memory d1, + G2Point memory d2 + ) internal view returns (bool) { + G1Point[] memory p1 = new G1Point[](4); + G2Point[] memory p2 = new G2Point[](4); + p1[0] = a1; + p1[1] = b1; + p1[2] = c1; + p1[3] = d1; + p2[0] = a2; + p2[1] = b2; + p2[2] = c2; + p2[3] = d2; + return pairing(p1, p2); + } +} + +contract Verifier { + using Pairing for *; + + struct VerifyingKey { + Pairing.G1Point alfa1; + Pairing.G2Point beta2; + Pairing.G2Point gamma2; + Pairing.G2Point delta2; + Pairing.G1Point[] IC; + } + + struct Proof { + Pairing.G1Point A; + Pairing.G2Point B; + Pairing.G1Point C; + } + + function verifyingKey() internal pure returns (VerifyingKey memory vk) { + vk.alfa1 = Pairing.G1Point( + 20491192805390485299153009773594534940189261866228447918068658471970481763042, + 9383485363053290200918347156157836566562967994039712273449902621266178545958 + ); + + vk.beta2 = Pairing.G2Point( + [4252822878758300859123897981450591353533073413197771768651442665752259397132, 6375614351688725206403948262868962793625744043794305715222011528459656738731], + [21847035105528745403288232691147584728191162732299865338377159692350059136679, 10505242626370262277552901082094356697409835680220590971873171140371331206856] + ); + vk.gamma2 = Pairing.G2Point( + [11559732032986387107991004021392285783925812861821192530917403151452391805634, 10857046999023057135944570762232829481370756359578518086990519993285655852781], + [4082367875863433681332203403145435568316851327593401208105741076214120093531, 8495653923123431417604973247489272438418190587263600148770280649306958101930] + ); + vk.delta2 = Pairing.G2Point( + [10343081133609258106524901662136446830203953069418302653607138025109354852056, 9807164380251633320270781057851447975413763120899682835651746536114812553994], + [9944420113684168402007836039557494668185622235286586265128723919315954929511, 19577108980448557774164310518706121321497510178047308624728190351127007029613] + ); + vk.IC = new Pairing.G1Point[](41); + vk.IC[0] = Pairing.G1Point( + 13911334274401924343675544560041308512588936901424910915603782906803297287632, + 2706123920292725976458900370590743282893519207291145650034358362573351809778 + ); + vk.IC[1] = Pairing.G1Point( + 6985983538948125859102879759305585957033692335816034727504459126154150675393, + 19972750462935050417295831096846277473290254620709512808517185507733918100846 + ); + vk.IC[2] = Pairing.G1Point( + 7744013186186492394192214327299469757175689346110767167154898216471196095085, + 6702729055720766626065307937755216430432678137682221614353038778589657960048 + ); + vk.IC[3] = Pairing.G1Point( + 255915235030371991993484001712616086436758891388484439648352245043294529900, + 4179505299213548642445718315004146505466351834266056645355508781336008368451 + ); + vk.IC[4] = Pairing.G1Point( + 14665449237617429987947939493398796988508835581367002665682766941024976091705, + 762004656482205873626930109934934985880770906990530286373664203254704399119 + ); + vk.IC[5] = Pairing.G1Point( + 9891081518337938052858589684877032246112975663226300403315954293261278376940, + 19489996687572280863382620397533026772053282893687720955820232087604849495176 + ); + vk.IC[6] = Pairing.G1Point( + 5800952410174350989374575600427733477116335318735807160590138906298167392978, + 10087684366978175789571270859672147139504313886600685836467201147453228792716 + ); + vk.IC[7] = Pairing.G1Point( + 21351332895842959440410596862055681606274721309941311913188097986540391756486, + 2307072863391971707807462264997264310991255504705487943650727153057932486703 + ); + vk.IC[8] = Pairing.G1Point( + 13010659854056812016439519531663311954437532545284071926934993582761474224894, + 15816218888092633942089854415791255109502438402393571397446345369212440492159 + ); + vk.IC[9] = Pairing.G1Point( + 5053705414025665707100333667090818209330898042069933520812706581768614416747, + 15102167500006968672268298512544770652224200188765016527963864802106967926258 + ); + vk.IC[10] = Pairing.G1Point( + 18842434238151770363997463153238823211722305234308000152572121185461721165534, + 2593387840584284469427225866602223950231412681510330394144072762948160157256 + ); + vk.IC[11] = Pairing.G1Point( + 15599182068312680660960536864833796975142938275261895467487695736451312294383, + 4666518800946599840711720480266222815125027594955011646010035289619688501056 + ); + vk.IC[12] = Pairing.G1Point( + 9113330458144966221935075056421416883691137461488614310024809145998323557603, + 14525998074188030501201355527081933288769508591085401978245999429699317995155 + ); + vk.IC[13] = Pairing.G1Point( + 1698361326922985986278517202575612983085060321585257306576729960115662047082, + 10811440926949560531025358441147529667704238851901100869553842063328440863767 + ); + vk.IC[14] = Pairing.G1Point( + 21813322714619416501160428454453290155766777717262000799102654107816607855965, + 1900098864322431724982182925647723282402120214677411997484653888105945993420 + ); + vk.IC[15] = Pairing.G1Point( + 4850391572444221222891473530479010507625931117274574478058276734887281510448, + 21209333872097499618431030650092175877302366840044306035116464128115890800796 + ); + vk.IC[16] = Pairing.G1Point( + 11024325828225418385172578260153741127557638280432460894781093623688133027317, + 12829771981770099593305823120912530429245566056736702770500768601904968866660 + ); + vk.IC[17] = Pairing.G1Point( + 6499199902234026878468170995556481877772698039370309856572671196533366057357, + 4457036475648163147639419384098488748765647820786558552307664597920606634328 + ); + vk.IC[18] = Pairing.G1Point( + 18022767910853449640857081466776039368894005536449264312356527521962753150045, + 19255367004765661489282403633399213738840373861881573851712640969865112253205 + ); + vk.IC[19] = Pairing.G1Point( + 3407915644635962426547873514159178484708180897582674981651195673063889056527, + 20900842108041417167531653748937888806375160842841645236594766769740801086240 + ); + vk.IC[20] = Pairing.G1Point( + 20017654297737842052107095428206289877193449936297035323430379821054305801225, + 18398515026520352890282481306377861550205376583366632991373903923950653555070 + ); + vk.IC[21] = Pairing.G1Point( + 21047973865590898317558361621568151146087679718605108018529528496898961038772, + 21278634433260452963308259175825820317340782823939379578046404446402428234210 + ); + vk.IC[22] = Pairing.G1Point( + 8241145138357410146565515812882570766759410582275691347723108605200802022956, + 1989081011088741796912864754739172167152133683074048511937493943117875172858 + ); + vk.IC[23] = Pairing.G1Point( + 17569097387405075516786396328891553530486899788694929338454645324083714108418, + 2671272267873010509558066732587132159825241218632027523162826309557010040801 + ); + vk.IC[24] = Pairing.G1Point( + 18432934328198141990735900067700545183644918237318871758528883499458953191751, + 12907904986298391408698324332133566949685445388692800711291464791587658944848 + ); + vk.IC[25] = Pairing.G1Point( + 11647174916828858228600836692025264965425510498861972640414508608610165498786, + 2789266155005779176091573091508902987969786671406288086616050740944435032366 + ); + vk.IC[26] = Pairing.G1Point( + 7716599352357689662437791281378834953674147767709763441696690660416798000193, + 2011956446519231175256986068510459528070989280957991078572908702061967826029 + ); + vk.IC[27] = Pairing.G1Point( + 14881679753221971431657980926845949992839985126401023404107224715610776731068, + 9378592721036535509644483460953612255671078585476916759911615244007744068220 + ); + vk.IC[28] = Pairing.G1Point( + 5152714233547642924425453943362933487670747811842493222288515712293139545616, + 17103096071991383067025581815771854616548148128756030455605263703527561205910 + ); + vk.IC[29] = Pairing.G1Point( + 15447374417074702151058563859314109816008078225151836487467438630855834394189, + 17622407048249609790965427996195733485238662836944547380822418048735288467805 + ); + vk.IC[30] = Pairing.G1Point( + 8651195532923628086866919418072252415467334585437736376449571499823619232889, + 14998264407811038481851626580083663176688649010417880010392153937322836336730 + ); + vk.IC[31] = Pairing.G1Point( + 7458457418733257481313115870700418691758253843381439582888002847293920934780, + 542167882620985531753545552575713620470656945139339404822606611648495438637 + ); + vk.IC[32] = Pairing.G1Point( + 11654478610393315165602893189754745536674121231400580736622038568733670735982, + 12257355783551921174011032474512008533083087974719806073358692398516604463769 + ); + vk.IC[33] = Pairing.G1Point( + 20322635549433112154444217943766297421929292916507426703075749847590584882286, + 17064859567711980715716858605905961120685111304069430254040524583096277093911 + ); + vk.IC[34] = Pairing.G1Point( + 2351611421496751815297337091125626628153834298569012697092929966223240549367, + 8981623119830487140187538030830803038148631170446603756509370347108713360372 + ); + vk.IC[35] = Pairing.G1Point( + 14536962684274075654744716239480828438557306956570655739004040615159003943312, + 15989427664222476135240023865637576890575158594894752990204712714267063734873 + ); + vk.IC[36] = Pairing.G1Point( + 2619628326543741336308640920600617374518257676707551075589324630412339157610, + 7966211775560790007913238855770952022525432549688555817284872825049075239614 + ); + vk.IC[37] = Pairing.G1Point( + 687223592036625422088204689474192126235881791115711355027369272731146690572, + 5456875514001475540695452284699708537730275839107823178023311848531691970413 + ); + vk.IC[38] = Pairing.G1Point( + 6946456658180332721992546139783275797641755855230168345362036906695016641551, + 11316286553478747631365490256288642487416717626257031294300167632075226007209 + ); + vk.IC[39] = Pairing.G1Point( + 18376919354566106859685708361984142415330243406938923421496478653958087728870, + 8827642705442411540364786437220347440481285673685576911202226958271761055385 + ); + vk.IC[40] = Pairing.G1Point( + 6057728902703241688367200208848751084102678742749995632673910557958470618058, + 7921304270410952949195938865544544978067811906564190603914442420062807566570 + ); + } + + function verify(uint256[] memory input, Proof memory proof) public view returns (uint256) { + uint256 snark_scalar_field = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + VerifyingKey memory vk = verifyingKey(); + require(input.length + 1 == vk.IC.length, "verifier-bad-input"); + // Compute the linear combination vk_x + Pairing.G1Point memory vk_x = Pairing.G1Point(0, 0); + for (uint256 i = 0; i < input.length; i++) { + require(input[i] < snark_scalar_field, "verifier-gte-snark-scalar-field"); + vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[i + 1], input[i])); + } + vk_x = Pairing.addition(vk_x, vk.IC[0]); + if (!Pairing.pairingProd4(Pairing.negate(proof.A), proof.B, vk.alfa1, vk.beta2, vk_x, vk.gamma2, proof.C, vk.delta2)) return 1; + return 0; + } + + /// @return r bool true if proof is valid + function verifyProof(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[40] memory input) public view returns (bool r) { + Proof memory proof; + proof.A = Pairing.G1Point(a[0], a[1]); + proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]); + proof.C = Pairing.G1Point(c[0], c[1]); + uint256[] memory inputValues = new uint256[](input.length); + for (uint256 i = 0; i < input.length; i++) { + inputValues[i] = input[i]; + } + if (verify(inputValues, proof) == 0) { + return true; + } else { + return false; + } + } +} diff --git a/src/contracts/src/Groth16VerifierWallet.sol b/src/contracts/src/Groth16VerifierWallet.sol deleted file mode 100644 index 5d71c540d..000000000 --- a/src/contracts/src/Groth16VerifierWallet.sol +++ /dev/null @@ -1,423 +0,0 @@ -// -// Copyright 2017 Christian Reitwiessner -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// -// 2019 OKIMS -// ported to solidity 0.6 -// fixed linter warnings -// added requiere error messages -// -// -// SPDX-License-Identifier: GPL-3.0 -pragma solidity >=0.6.11; - -library Pairing { - struct G1Point { - uint X; - uint Y; - } - // Encoding of field elements is: X[0] * z + X[1] - struct G2Point { - uint[2] X; - uint[2] Y; - } - - /// @return the generator of G1 - function P1() internal pure returns (G1Point memory) { - return G1Point(1, 2); - } - - /// @return the generator of G2 - function P2() internal pure returns (G2Point memory) { - // Original code point - return - G2Point( - [11559732032986387107991004021392285783925812861821192530917403151452391805634, 10857046999023057135944570762232829481370756359578518086990519993285655852781], - [4082367875863433681332203403145435568316851327593401208105741076214120093531, 8495653923123431417604973247489272438418190587263600148770280649306958101930] - ); - - /* - // Changed by Jordi point - return G2Point( - [10857046999023057135944570762232829481370756359578518086990519993285655852781, - 11559732032986387107991004021392285783925812861821192530917403151452391805634], - [8495653923123431417604973247489272438418190587263600148770280649306958101930, - 4082367875863433681332203403145435568316851327593401208105741076214120093531] - ); -*/ - } - - /// @return r the negation of p, i.e. p.addition(p.negate()) should be zero. - function negate(G1Point memory p) internal pure returns (G1Point memory r) { - // The prime q in the base field F_q for G1 - uint q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; - if (p.X == 0 && p.Y == 0) return G1Point(0, 0); - return G1Point(p.X, q - (p.Y % q)); - } - - /// @return r the sum of two points of G1 - function addition(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) { - uint[4] memory input; - input[0] = p1.X; - input[1] = p1.Y; - input[2] = p2.X; - input[3] = p2.Y; - bool success; - // solium-disable-next-line security/no-inline-assembly - assembly { - success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60) - // Use "invalid" to make gas estimation work - switch success - case 0 { - invalid() - } - } - require(success, "pairing-add-failed"); - } - - /// @return r the product of a point on G1 and a scalar, i.e. - /// p == p.scalar_mul(1) and p.addition(p) == p.scalar_mul(2) for all points p. - function scalar_mul(G1Point memory p, uint s) internal view returns (G1Point memory r) { - uint[3] memory input; - input[0] = p.X; - input[1] = p.Y; - input[2] = s; - bool success; - // solium-disable-next-line security/no-inline-assembly - assembly { - success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60) - // Use "invalid" to make gas estimation work - switch success - case 0 { - invalid() - } - } - require(success, "pairing-mul-failed"); - } - - /// @return the result of computing the pairing check - /// e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1 - /// For example pairing([P1(), P1().negate()], [P2(), P2()]) should - /// return true. - function pairing(G1Point[] memory p1, G2Point[] memory p2) internal view returns (bool) { - require(p1.length == p2.length, "pairing-lengths-failed"); - uint elements = p1.length; - uint inputSize = elements * 6; - uint[] memory input = new uint[](inputSize); - for (uint i = 0; i < elements; i++) { - input[i * 6 + 0] = p1[i].X; - input[i * 6 + 1] = p1[i].Y; - input[i * 6 + 2] = p2[i].X[0]; - input[i * 6 + 3] = p2[i].X[1]; - input[i * 6 + 4] = p2[i].Y[0]; - input[i * 6 + 5] = p2[i].Y[1]; - } - uint[1] memory out; - bool success; - // solium-disable-next-line security/no-inline-assembly - assembly { - success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20) - // Use "invalid" to make gas estimation work - switch success - case 0 { - invalid() - } - } - require(success, "pairing-opcode-failed"); - return out[0] != 0; - } - - /// Convenience method for a pairing check for two pairs. - function pairingProd2(G1Point memory a1, G2Point memory a2, G1Point memory b1, G2Point memory b2) internal view returns (bool) { - G1Point[] memory p1 = new G1Point[](2); - G2Point[] memory p2 = new G2Point[](2); - p1[0] = a1; - p1[1] = b1; - p2[0] = a2; - p2[1] = b2; - return pairing(p1, p2); - } - - /// Convenience method for a pairing check for three pairs. - function pairingProd3(G1Point memory a1, G2Point memory a2, G1Point memory b1, G2Point memory b2, G1Point memory c1, G2Point memory c2) internal view returns (bool) { - G1Point[] memory p1 = new G1Point[](3); - G2Point[] memory p2 = new G2Point[](3); - p1[0] = a1; - p1[1] = b1; - p1[2] = c1; - p2[0] = a2; - p2[1] = b2; - p2[2] = c2; - return pairing(p1, p2); - } - - /// Convenience method for a pairing check for four pairs. - function pairingProd4( - G1Point memory a1, - G2Point memory a2, - G1Point memory b1, - G2Point memory b2, - G1Point memory c1, - G2Point memory c2, - G1Point memory d1, - G2Point memory d2 - ) internal view returns (bool) { - G1Point[] memory p1 = new G1Point[](4); - G2Point[] memory p2 = new G2Point[](4); - p1[0] = a1; - p1[1] = b1; - p1[2] = c1; - p1[3] = d1; - p2[0] = a2; - p2[1] = b2; - p2[2] = c2; - p2[3] = d2; - return pairing(p1, p2); - } -} - -contract Verifier { - using Pairing for *; - struct VerifyingKey { - Pairing.G1Point alfa1; - Pairing.G2Point beta2; - Pairing.G2Point gamma2; - Pairing.G2Point delta2; - Pairing.G1Point[] IC; - } - struct Proof { - Pairing.G1Point A; - Pairing.G2Point B; - Pairing.G1Point C; - } - - function verifyingKey() internal pure returns (VerifyingKey memory vk) { - vk.alfa1 = Pairing.G1Point( - 20491192805390485299153009773594534940189261866228447918068658471970481763042, - 9383485363053290200918347156157836566562967994039712273449902621266178545958 - ); - - vk.beta2 = Pairing.G2Point( - [4252822878758300859123897981450591353533073413197771768651442665752259397132, 6375614351688725206403948262868962793625744043794305715222011528459656738731], - [21847035105528745403288232691147584728191162732299865338377159692350059136679, 10505242626370262277552901082094356697409835680220590971873171140371331206856] - ); - vk.gamma2 = Pairing.G2Point( - [11559732032986387107991004021392285783925812861821192530917403151452391805634, 10857046999023057135944570762232829481370756359578518086990519993285655852781], - [4082367875863433681332203403145435568316851327593401208105741076214120093531, 8495653923123431417604973247489272438418190587263600148770280649306958101930] - ); - vk.delta2 = Pairing.G2Point( - [1513129022268504209358763521777110646559191312944535983790343685540452445612, 12195000584114682213316512369139242650405673197296308277565313794080256232701], - [8360772797757285669693588223224368324652217960280535125476432579464480898413, 520894096459672819696448581165649200639580943346471218758886059131097679200] - ); - vk.IC = new Pairing.G1Point[](35); - - vk.IC[0] = Pairing.G1Point( - 11537328283226768393732428330956073110918624081922605220849994462257164716154, - 21836950190585457727786541794875874204342457073442279657287670575335295859939 - ); - - vk.IC[1] = Pairing.G1Point( - 2646307150509379456483324388204599416197823343744573056776580441757968778128, - 14368370831419521837815784599306984151624772530159432470308283448431330483438 - ); - - vk.IC[2] = Pairing.G1Point( - 16971624750468472956383457363672950966338870541072731251437414691217134411120, - 8171770470043873321771537156021753591829282779422875691859226771666334115210 - ); - - vk.IC[3] = Pairing.G1Point( - 17003248154885997024042723500051841853373913724967509464380365353802575974728, - 7268687626523176370339202918269959082095447462862347953587350206552205642559 - ); - - vk.IC[4] = Pairing.G1Point( - 9147424234223179035895482077044817222415554273556867838268712944193520459125, - 21578705994822322813698189503736815299185497639660343771670537250469027755948 - ); - - vk.IC[5] = Pairing.G1Point( - 16948417333412259029407181850226843375508933350680650127572170476630717282234, - 9665563179883720332817127456522837835577284997912132910805194791161889398512 - ); - - vk.IC[6] = Pairing.G1Point( - 9319994583370423638602672994382300950312503535351144768052894387193551537459, - 19171031111811822064434269353657536224339902296340766041252096715670766153578 - ); - - vk.IC[7] = Pairing.G1Point( - 9371603635986601434942264619807195148477116766259006354709633771674992552724, - 11103293940652841271958831421967970005702672122254116205870466468493278826958 - ); - - vk.IC[8] = Pairing.G1Point( - 3561757143230234005632456376532802121489770329687277876867422136368222736134, - 11734814901687269585580202167146602218102049962630061844516173384912435005151 - ); - - vk.IC[9] = Pairing.G1Point( - 16676238706569507337573176601181670688707264606170712075388734731572549373015, - 11158592034910239776632780480510406146251140896522712858892689171974748083747 - ); - - vk.IC[10] = Pairing.G1Point( - 9822993420542467873912218347366938030193407618831916003032667186678059114349, - 6863223533781083402167797754430699108902384970089129346113470095574463296737 - ); - - vk.IC[11] = Pairing.G1Point( - 4866721301322510775531094866459672146070982818674092273969310571220291630556, - 9846640210996171367705574856419053631553093780701851221089070591862473684456 - ); - - vk.IC[12] = Pairing.G1Point( - 11121919417977708038028243712145798541888876824913262431306415256568966652024, - 12609770408698645498350514024296607224475353273713173177251900216074217014850 - ); - - vk.IC[13] = Pairing.G1Point( - 14840050373651993593383756529988239317958775236310376879012996717055845150487, - 16683016138329247649135414505803188190565946292769946693147365719646757192352 - ); - - vk.IC[14] = Pairing.G1Point( - 12331471170495648864015185687161748641193043583582691780623236535573811426959, - 11714531956225516771424892308789701085922077646343882797070434158311220922011 - ); - - vk.IC[15] = Pairing.G1Point( - 20857366342421630154576707922479406842052250461154457549932210545889187427626, - 19731871495034015170236307573440414758884960293847791130561428843792753429086 - ); - - vk.IC[16] = Pairing.G1Point( - 16209406523379999199236029863462575441846948663847567298395841886469797214082, - 20718988335004414356172240953954108902387108184538803896124546491420460740169 - ); - - vk.IC[17] = Pairing.G1Point( - 8397179853577453858892608876746062307226175687475102639712031393841003387919, - 7154528864876839718549215559715861424372452521581853695056564308745992761672 - ); - - vk.IC[18] = Pairing.G1Point( - 15000425614899689626185464253582174491572354417903507356426258449407132127046, - 13602373364472815067534003393627538602411204908660744540946955553406039633451 - ); - - vk.IC[19] = Pairing.G1Point( - 5545252939208963791309142258539499940294193526436021787091019273144246885261, - 2675833647327088808677716501235324721140769279456271927816550777395489852251 - ); - - vk.IC[20] = Pairing.G1Point( - 2537047834074478023060297384888724410218136939149736151242515713484635642789, - 3193147508270124393755481767836877808561856886429215905854576456538260642120 - ); - - vk.IC[21] = Pairing.G1Point( - 13819984179784763421503658502014430958296359352165019537755065452645355466085, - 18994178127386658997162783196808799873728043389545801736093808610346271161658 - ); - - vk.IC[22] = Pairing.G1Point( - 6589370869708283514329318539826110923064805897289825414235274269171407140900, - 9625792054986455984618262255946246252268480571067521104852530192902386719593 - ); - - vk.IC[23] = Pairing.G1Point( - 14453270681522455527144160729543145025587040770258214008741757192612444737673, - 20143300120523312381709649823189168032874279267240134119356286009260298185475 - ); - - vk.IC[24] = Pairing.G1Point( - 8846979524751473322994268234754538401482894217522349506066299416316984717260, - 14238278950875447301003974303300008365515832894025960431754069250458716988985 - ); - - vk.IC[25] = Pairing.G1Point( - 1407828395464361821905309927863167268883721160860555328581866885124611897528, - 15987854649208373291691713233856831065610123809032682196292981616658494776987 - ); - - vk.IC[26] = Pairing.G1Point( - 20657483817998070093869648777824400547660398552294774144027193285451239619382, - 21826613460615350291777797645121185151869814459512699120411019581958075991542 - ); - - vk.IC[27] = Pairing.G1Point( - 20624928617240774729161710928323453036077574938263627000040473137006018415415, - 5131454817008842892320970265930200727731593782574755474310860262365965990664 - ); - - vk.IC[28] = Pairing.G1Point( - 13895249990164764352618348094492621630575085672410127797817030490897283284460, - 11796920177436300262655702063092510613755939821586920802170520825907606184557 - ); - - vk.IC[29] = Pairing.G1Point( - 3135811994020780743324086008729747183900335277491189104250977002197575809365, - 19637480211624980857501152516366188197895066151113910763057713919944678029866 - ); - - vk.IC[30] = Pairing.G1Point( - 606103022421566187118689786319065266252504972683102249226453076393842587324, - 10274587862652629723961486759561149321143114208996217550785138347396636546119 - ); - - vk.IC[31] = Pairing.G1Point( - 17876636758905622230688544604495142065478803287376541138330575508029036910921, - 845514606482911379811389987862147832754587150586772012493153117259870748818 - ); - - vk.IC[32] = Pairing.G1Point( - 3055934057788309375612017678081361462642124203699024466299800387864964176162, - 6215463225683464296816602777995066291454589608119271052091343612576965188139 - ); - - vk.IC[33] = Pairing.G1Point( - 15655816926053512024400351678850471800118254812943223488624167720306406940886, - 1318451441938966067851976459607266837893918068783070216555767331693196884126 - ); - - vk.IC[34] = Pairing.G1Point( - 13942491345765259320815758221010163510633256522676138655574537850419728932090, - 2449428677603436766608016448595332101485961035234011384134049218692804513154 - ); - } - - function verify(uint[] memory input, Proof memory proof) internal view returns (uint) { - uint256 snark_scalar_field = 21888242871839275222246405745257275088548364400416034343698204186575808495617; - VerifyingKey memory vk = verifyingKey(); - require(input.length + 1 == vk.IC.length, "verifier-bad-input"); - // Compute the linear combination vk_x - Pairing.G1Point memory vk_x = Pairing.G1Point(0, 0); - for (uint i = 0; i < input.length; i++) { - require(input[i] < snark_scalar_field, "verifier-gte-snark-scalar-field"); - vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[i + 1], input[i])); - } - vk_x = Pairing.addition(vk_x, vk.IC[0]); - if (!Pairing.pairingProd4(Pairing.negate(proof.A), proof.B, vk.alfa1, vk.beta2, vk_x, vk.gamma2, proof.C, vk.delta2)) return 1; - return 0; - } - - /// @return r bool true if proof is valid - function verifyProof(uint[2] memory a, uint[2][2] memory b, uint[2] memory c, uint[34] memory input) public view returns (bool r) { - Proof memory proof; - proof.A = Pairing.G1Point(a[0], a[1]); - proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]); - proof.C = Pairing.G1Point(c[0], c[1]); - uint[] memory inputValues = new uint[](input.length); - for (uint i = 0; i < input.length; i++) { - inputValues[i] = input[i]; - } - if (verify(inputValues, proof) == 0) { - return true; - } else { - return false; - } - } -} diff --git a/src/contracts/src/KYCEmailHandler.sol b/src/contracts/src/KYCEmailHandler.sol index 00be579d9..780624ccd 100644 --- a/src/contracts/src/KYCEmailHandler.sol +++ b/src/contracts/src/KYCEmailHandler.sol @@ -5,10 +5,10 @@ import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; import "@openzeppelin/contracts/utils/Strings.sol"; import "@openzeppelin/contracts/utils/Counters.sol"; import "forge-std/console.sol"; -// import "./base64.sol"; import "./StringUtils.sol"; import "./NFTSVG.sol"; -import "./Groth16VerifierTwitter.sol"; +import { Verifier } from "./Groth16VerifierKYC.sol"; +import "./MailServer.sol"; contract VerifiedKYCEmail is ERC721Enumerable, Verifier { using Counters for Counters.Counter; diff --git a/src/contracts/src/WalletEmailHandler.sol b/src/contracts/src/WalletEmailHandler.sol deleted file mode 100644 index 4f89ec3e3..000000000 --- a/src/contracts/src/WalletEmailHandler.sol +++ /dev/null @@ -1,180 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; -import "@openzeppelin/contracts/utils/Counters.sol"; -import "forge-std/console.sol"; -// import "./base64.sol"; -import "./HexStrings.sol"; -import "./NFTSVG.sol"; -import "./Groth16VerifierWallet.sol"; -import "./MailServer.sol"; - -contract VerifiedWalletEmail is Verifier { - using HexStrings for *; - using MailServer for MailServer.Server; - - uint16 public constant packSize = 7; // 7 bytes in a packed item returned from circom - - uint16 public constant body_len = 4 * 4; - uint16 public constant rsa_modulus_chunks_len = 17; - uint16 public constant commitment_len = 1; - uint16 public constant msg_len = body_len + rsa_modulus_chunks_len + commitment_len; - - uint16 public constant header_len = msg_len - body_len; - uint16 public constant addressIndexInSignals = msg_len - 1; // The last index is the commitment - - mapping(string => uint256[rsa_modulus_chunks_len]) public verifiedMailserverKeys; - mapping(string => uint256) public balance; - mapping(uint256 => bool) public nullifier; - MailServer.Server server; - - // string constant domain = "twitter.com"; - - constructor() { - // Do dig TXT outgoing._domainkey.twitter.com to verify these. - // This is the base 2^121 representation of that key. - // Circom bigint: represent a = a[0] + a[1] * 2**n + .. + a[k - 1] * 2**(n * k) - require(rsa_modulus_chunks_len + body_len + 1 == msg_len, "Variable counts are wrong!"); - server.initMailserverKeys(); - } - - // Unpacks uint256s into bytes and then extracts the non-zero characters - // Only extracts contiguous non-zero characters and ensures theres only 1 such state - // Note that unpackedLen may be more than packedBytes.length * 8 since there may be 0s - // TODO: Remove console.logs and define this as a pure function instead of a view - function convertPackedBytesToBytes(uint256[] memory packedBytes, uint256 maxBytes) public pure returns (string memory extractedString) { - uint8 state = 0; - // bytes: 0 0 0 0 y u s h _ g 0 0 0 - // state: 0 0 0 0 1 1 1 1 1 1 2 2 2 - bytes memory nonzeroBytesArray = new bytes(packedBytes.length * 7); - uint256 nonzeroBytesArrayIndex = 0; - for (uint16 i = 0; i < packedBytes.length; i++) { - uint256 packedByte = packedBytes[i]; - uint8[] memory unpackedBytes = new uint8[](packSize); - for (uint j = 0; j < packSize; j++) { - unpackedBytes[j] = uint8(packedByte >> (j * 8)); - } - for (uint256 j = 0; j < packSize; j++) { - uint256 unpackedByte = unpackedBytes[j]; //unpackedBytes[j]; - // console.log(i, j, state, unpackedByte); - if (unpackedByte != 0) { - nonzeroBytesArray[nonzeroBytesArrayIndex] = bytes1(uint8(unpackedByte)); - nonzeroBytesArrayIndex++; - if (state % 2 == 0) { - state += 1; - } - } else { - if (state % 2 == 1) { - state += 1; - } - } - packedByte = packedByte >> 8; - } - } - string memory returnValue = string(nonzeroBytesArray); - require(state >= 1, "Invalid final state of packed bytes in email"); - // console.log("Characters in username: ", nonzeroBytesArrayIndex); - require(nonzeroBytesArrayIndex <= maxBytes, "Packed bytes more than allowed max length!"); - return returnValue; - // Have to end at the end of the email -- state cannot be 1 since there should be an email footer - } - - function _stringEq(string memory a, string memory b) public pure returns (bool) { - return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b)); - } - - function transfer(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[msg_len] memory signals) public { - // Checks: Verify proof and check signals - // require(signals[0] == 1337, "invalid signals"); // TODO no invalid signal check yet, which is fine since the zk proof does it - - // 3 public signals are the masked packed message bytes, 17 are the modulus. - uint256[] memory bodySignals = new uint256[](body_len); - uint256[] memory rsaModulusSignals = new uint256[](header_len); - for (uint256 i = 0; i < body_len; i++) bodySignals[i] = signals[i]; - for (uint256 i = body_len; i < msg_len - 1; i++) rsaModulusSignals[i - body_len] = signals[i]; - - // Check eth address committed to in proof matches msg.sender, to avoid doublespend and relayer-frontrunning-relayer-for-profit - // require(address(uint160(signals[addressIndexInSignals])) == msg.sender, "Invalid address"); - - // TODO: Note that this is buggy since it is malleable - require(!nullifier[a[0]], "Value is already true"); - nullifier[a[0]] = true; - - // Check from/to email domains are correct [in this case, only from domain is checked] - // Right now, we just check that any email was received from anyone at Twitter, which is good enough for now - // We will upload the version with these domain checks soon! - // require(_domainCheck(headerSignals), "Invalid domain"); - string memory fromEmail = convertPackedBytesToBytes(sliceArray(bodySignals, 0, 4), packSize * 4); - string memory recipientEmail = convertPackedBytesToBytes(sliceArray(bodySignals, 4, 8), packSize * 4); - string memory amount = convertPackedBytesToBytes(sliceArray(bodySignals, 8, 12), packSize * 4); - string memory currency = convertPackedBytesToBytes(sliceArray(bodySignals, 12, 16), packSize * 4); - - string memory domain = getDomainFromEmail(fromEmail); - console.log(domain); - // Verify that the public key for RSA matches the hardcoded one - for (uint i = body_len; i < msg_len - 1; i++) { - require(server.isVerified(domain, i - body_len, signals[i]), "Invalid: RSA modulus not matched"); - } - require(verifyProof(a, b, c, signals), "Invalid Proof"); // checks effects iteractions, this should come first - - // Effects: Send money - if (balance[fromEmail] == 0) { - balance[fromEmail] = 10; - } - balance[fromEmail] -= stringToUint(amount); - balance[recipientEmail] += stringToUint(amount); - } - - function bytes32ToString(bytes32 input) internal pure returns (string memory) { - uint256 i; - for (i = 0; i < 32 && input[i] != 0; i++) {} - bytes memory resultBytes = new bytes(i); - for (i = 0; i < 32 && input[i] != 0; i++) { - resultBytes[i] = input[i]; - } - return string(resultBytes); - } - - function sliceArray(uint256[] memory input, uint256 start, uint256 end) internal pure returns (uint256[] memory) { - require(start <= end && end <= input.length, "Invalid slice indices"); - uint256[] memory result = new uint256[](end - start); - for (uint256 i = start; i < end; i++) { - result[i - start] = input[i]; - } - return result; - } - - function stringToUint(string memory s) internal pure returns (uint256) { - bytes memory b = bytes(s); - uint256 result = 0; - for (uint i = 0; i < b.length; i++) { - if (b[i] >= 0x30 && b[i] <= 0x39) { - result = result * 10 + (uint256(uint8(b[i])) - 48); - } - } - return result; - } - - function getDomainFromEmail(string memory fromEmail) public pure returns (string memory) { - bytes memory emailBytes = bytes(fromEmail); - uint atIndex; - for (uint i = 0; i < emailBytes.length; i++) { - if (emailBytes[i] == "@") { - atIndex = i; - break; - } - } - - bytes memory domainBytes = new bytes(emailBytes.length - atIndex - 1); - for (uint j = 0; j < domainBytes.length; j++) { - domainBytes[j] = emailBytes[atIndex + 1 + j]; - } - return bytes32ToString(bytes32(bytes(domainBytes))); - } - - function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal { - require(from == address(0), "Cannot transfer - VerifiedEmail is soulbound"); - } -} diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx index 171a0876d..c71e23c9b 100644 --- a/src/pages/MainPage.tsx +++ b/src/pages/MainPage.tsx @@ -6,6 +6,7 @@ import { useAsync, useMount, useUpdateEffect } from "react-use"; import _, { add } from "lodash"; // @ts-ignore import { generate_inputs, insert13Before10 } from "../scripts/generate_input"; +import { generate_inputs_kyc } from "../scripts/generate_two_inputs"; import styled, { CSSProperties } from "styled-components"; import { sshSignatureToPubKey } from "../helpers/sshFormat"; import { Link, useSearchParams } from "react-router-dom"; @@ -29,13 +30,15 @@ var Buffer = require("buffer/").Buffer; // note: the trailing slash is important const generate_input = require("../scripts/generate_input"); +const generate_two_inputs = require("../scripts/generate_two_inputs"); export const MainPage: React.FC<{}> = (props) => { // raw user inputs const filename = "email"; const [emailSignals, setEmailSignals] = useState(""); - const [emailFull, setEmailFull] = useState(localStorage.emailFull || ""); + const [emailFullAirbnb, setEmailFullAirbnb] = useState(localStorage.emailFullAirbnb || ""); + const [emailFullCoinbase, setEmailFullCoinbase] = useState(localStorage.emailFullCoinbase || ""); const [proof, setProof] = useState(localStorage.proof || ""); const [publicSignals, setPublicSignals] = useState(localStorage.publicSignals || ""); const [displayMessage, setDisplayMessage] = useState("Prove"); @@ -45,12 +48,12 @@ export const MainPage: React.FC<{}> = (props) => { // computed state const { value, error } = useAsync(async () => { try { - const circuitInputs = await generate_inputs(Buffer.from(atob(emailFull)), ethereumAddress); + const circuitInputs = await generate_inputs_kyc(Buffer.from(atob(emailFullAirbnb)), Buffer.from(atob(emailFullCoinbase)), ethereumAddress); return circuitInputs; } catch (e) { return {}; } - }, [emailFull, ethereumAddress]); + }, [emailFullAirbnb, emailFullCoinbase, ethereumAddress]); const circuitInputs = value || {}; console.log("Circuit inputs:", circuitInputs); @@ -152,9 +155,13 @@ export const MainPage: React.FC<{}> = (props) => { // local storage stuff useUpdateEffect(() => { if (value) { - if (localStorage.emailFull !== emailFull) { - console.info("Wrote email to localStorage"); - localStorage.emailFull = emailFull; + if (localStorage.emailFullAirbnb !== emailFullAirbnb) { + console.info("Wrote airbnb email to localStorage"); + localStorage.emailFullAirbnb = emailFullAirbnb; + } + if (localStorage.emailFullCoinbase !== emailFullCoinbase) { + console.info("Wrote coinbase email to localStorage"); + localStorage.emailFullCoinbase = emailFullCoinbase; } } if (proof) { @@ -201,6 +208,7 @@ export const MainPage: React.FC<{}> = (props) => { if (error) console.error(error); // On file drop function to extract the text from the file + /* const onFileDrop = async (file: File) => { if (file.name.endsWith(".eml")) { const content = await file.text(); @@ -209,6 +217,7 @@ export const MainPage: React.FC<{}> = (props) => { alert("Only .eml files are allowed."); } }; + */ return ( @@ -220,7 +229,7 @@ export const MainPage: React.FC<{}> = (props) => { /> )}
-
ZK Email Ownership Proof Generator From Header
+
ZK KYC with ZK Email
= (props) => {
Input - -

OR

{ - setEmailFull(e.currentTarget.value); + setEmailFullAirbnb(e.currentTarget.value); }} /> + { + setEmailFullCoinbase(e.currentTarget.value); + }} + /> = (props) => { data-testid="prove-button" disabled={ displayMessage !== 'Prove' || - emailFull.length === 0 || + emailFullAirbnb.length === 0 || + emailFullCoinbase.length === 0 || ethereumAddress.length === 0 } onClick={async () => { @@ -319,24 +332,37 @@ export const MainPage: React.FC<{}> = (props) => { const mail = 'RGVsaXZlcmVkLVRvOiBiaXN3YWppdHNhbXByaXRpQGdtYWlsLmNvbQ0KUmVjZWl2ZWQ6IGJ5IDIwMDI6YTA1OjY1MTI6M2UxNTowOjA6MDowIHdpdGggU01UUCBpZCBpMjFjc3AzMTExMzI2bGZ2Ow0KICAgICAgICBXZWQsIDIwIEp1bCAyMDIyIDIwOjU1OjIwIC0wNzAwIChQRFQpDQpYLUdvb2dsZS1TbXRwLVNvdXJjZTogQUdSeU0xdVV1cmtpL0RJRHR0ckFEM1I3Z2Y4SXNWSGFXamhqaHRtKzNsSkp6RmI4SVZMdmEyUEY1YmZXTllHaDBObHRSdFJvUDJjSg0KWC1SZWNlaXZlZDogYnkgMjAwMjphMDU6NjIyYTo1MTQ6YjA6MzFmOmI1OjdkNmUgd2l0aCBTTVRQIGlkIGwyMC0yMDAyMGEwNTYyMmEwNTE0MDBiMDAzMWYwMGI1N2Q2ZW1yODI1NTQ3NHF0eC42NzcuMTY1ODM3NTcxOTgxNDsNCiAgICAgICAgV2VkLCAyMCBKdWwgMjAyMiAyMDo1NToxOSAtMDcwMCAoUERUKQ0KQVJDLVNlYWw6IGk9MTsgYT1yc2Etc2hhMjU2OyB0PTE2NTgzNzU3MTk7IGN2PW5vbmU7DQogICAgICAgIGQ9Z29vZ2xlLmNvbTsgcz1hcmMtMjAxNjA4MTY7DQogICAgICAgIGI9T2hMY1c4TFV0RDFmNGNEQTZ3Qk13MnhwMzEvMlJtQURtWU8ycEM0T09FbExFY1FQRnQxZTFhNzAwejVUWmNqMS9hDQogICAgICAgICBNajl5dGxKa2ladGg5SzlzSjRMc2x1QmRudk1YQndDbmc0R2w1b0tTWEpoNlZiUmtWUm5nZWhrZlB2L2ZyMkhNNGthQg0KICAgICAgICAgMHRaWEVMK3JGUjJLNjN1MmVTODVKbnlmYkh6a2t5eDJiMlBKWW1CUDJnN2tUbkR6SDJnOUhOK2cvekk5czlEbERMTUMNCiAgICAgICAgIEwzNnZrbGprcTI5a1V3aUVjUU5hbVRiREFNUUk2ZFlhMmtCbVMveFdKUDVrY0dOb3lMNzFSc2x3R3R3SE15dyt1NWlvDQogICAgICAgICBsdDlkWVRHbDBMWWlyelJvdlBPUXV4eVJFaTdlYlBuN1A4VytDVUpjem1ENjhnTFA1WWFUYjBwR0FIWWF0dGJyNURRSw0KICAgICAgICAgSmJBZz09DQpBUkMtTWVzc2FnZS1TaWduYXR1cmU6IGk9MTsgYT1yc2Etc2hhMjU2OyBjPXJlbGF4ZWQvcmVsYXhlZDsgZD1nb29nbGUuY29tOyBzPWFyYy0yMDE2MDgxNjsNCiAgICAgICAgaD10bzpzdWJqZWN0Om1lc3NhZ2UtaWQ6ZGF0ZTpmcm9tOm1pbWUtdmVyc2lvbjpka2ltLXNpZ25hdHVyZTsNCiAgICAgICAgYmg9VysvWkdkQjFkM0lVOHhGNkNBUGJwNENpRDlLY2VVU3hnV2lmeFhBZkYrdz07DQogICAgICAgIGI9T25jWXZINlFnUjFHeG82Y0VGNmV4ZEdzekl5YVFaeEFRWEFXbWNuOW1hWXdRQmNWZW9HTjNGNGpURUJXMjVDV3d0DQogICAgICAgICBVUHNacnlXdExhbDNmaHF1VzVDSHF5VWNNOGlTYXRnUUt3dkFJUGVaT0pZMFpuenRtbmdmZHdwTDliSUFyRlhuR2prRA0KICAgICAgICAgTkhRLzEwTUZxRG9uNzgwR2diTXNReVJHOS83U2NwMXBySUowTTFvNGlCWUtIRFBiNkJHRkg3Q3dPcWUycWE5TnNTVy8NCiAgICAgICAgIFZnTmovU3B1QlJuTDNsZlpsZnN1MC93WlVENWNjb1pJeS9IdlllRjFYczF0bG9aWENqcWtoQ1c4RzZOZmpjRzluYkZUDQogICAgICAgICBmakZJSS9XallkazZSSkRXaUt3N0p3UUF4a3hKMHhOcEpyMUZSUDFhdGRsSGFoQ1B1QWhvUUp0MFBsdGdFUi9jU1VJcQ0KICAgICAgICAgNWlkQT09DQpBUkMtQXV0aGVudGljYXRpb24tUmVzdWx0czogaT0xOyBteC5nb29nbGUuY29tOw0KICAgICAgIGRraW09cGFzcyBoZWFkZXIuaT1AbWl0LmVkdSBoZWFkZXIucz1vdXRnb2luZyBoZWFkZXIuYj1lSGNxYmRoRzsNCiAgICAgICBzcGY9cGFzcyAoZ29vZ2xlLmNvbTogZG9tYWluIG9mIGFheXVzaGdAbWl0LmVkdSBkZXNpZ25hdGVzIDE4LjkuMjguMTEgYXMgcGVybWl0dGVkIHNlbmRlcikgc210cC5tYWlsZnJvbT1hYXl1c2hnQG1pdC5lZHU7DQogICAgICAgZG1hcmM9cGFzcyAocD1OT05FIHNwPU5PTkUgZGlzPU5PTkUpIGhlYWRlci5mcm9tPW1pdC5lZHUNClJldHVybi1QYXRoOiA8YWF5dXNoZ0BtaXQuZWR1Pg0KUmVjZWl2ZWQ6IGZyb20gb3V0Z29pbmcubWl0LmVkdSAob3V0Z29pbmctYXV0aC0xLm1pdC5lZHUuIFsxOC45LjI4LjExXSkNCiAgICAgICAgYnkgbXguZ29vZ2xlLmNvbSB3aXRoIEVTTVRQUyBpZCBhMTgtMjAwMjBhYzg0NGIyMDAwMDAwYjAwMzFlZGY0NjZiNzNzaTQ2NjAxN3F0by42NC4yMDIyLjA3LjIwLjIwLjU1LjE5DQogICAgICAgIGZvciA8Ymlzd2FqaXRzYW1wcml0aUBnbWFpbC5jb20+DQogICAgICAgICh2ZXJzaW9uPVRMUzFfMiBjaXBoZXI9RUNESEUtRUNEU0EtQUVTMTI4LUdDTS1TSEEyNTYgYml0cz0xMjgvMTI4KTsNCiAgICAgICAgV2VkLCAyMCBKdWwgMjAyMiAyMDo1NToxOSAtMDcwMCAoUERUKQ0KUmVjZWl2ZWQtU1BGOiBwYXNzIChnb29nbGUuY29tOiBkb21haW4gb2YgYWF5dXNoZ0BtaXQuZWR1IGRlc2lnbmF0ZXMgMTguOS4yOC4xMSBhcyBwZXJtaXR0ZWQgc2VuZGVyKSBjbGllbnQtaXA9MTguOS4yOC4xMTsNCkF1dGhlbnRpY2F0aW9uLVJlc3VsdHM6IG14Lmdvb2dsZS5jb207DQogICAgICAgZGtpbT1wYXNzIGhlYWRlci5pPUBtaXQuZWR1IGhlYWRlci5zPW91dGdvaW5nIGhlYWRlci5iPWVIY3FiZGhHOw0KICAgICAgIHNwZj1wYXNzIChnb29nbGUuY29tOiBkb21haW4gb2YgYWF5dXNoZ0BtaXQuZWR1IGRlc2lnbmF0ZXMgMTguOS4yOC4xMSBhcyBwZXJtaXR0ZWQgc2VuZGVyKSBzbXRwLm1haWxmcm9tPWFheXVzaGdAbWl0LmVkdTsNCiAgICAgICBkbWFyYz1wYXNzIChwPU5PTkUgc3A9Tk9ORSBkaXM9Tk9ORSkgaGVhZGVyLmZyb209bWl0LmVkdQ0KUmVjZWl2ZWQ6IGZyb20gbWFpbC15dzEtZjE4Mi5nb29nbGUuY29tIChtYWlsLXl3MS1mMTgyLmdvb2dsZS5jb20gWzIwOS44NS4xMjguMTgyXSkNCgkoYXV0aGVudGljYXRlZCBiaXRzPTApDQogICAgICAgIChVc2VyIGF1dGhlbnRpY2F0ZWQgYXMgYWF5dXNoZ0BBVEhFTkEuTUlULkVEVSkNCglieSBvdXRnb2luZy5taXQuZWR1ICg4LjE0LjcvOC4xMi40KSB3aXRoIEVTTVRQIGlkIDI2TDN0STdPMDA4NTM0DQoJKHZlcnNpb249VExTdjEvU1NMdjMgY2lwaGVyPUFFUzEyOC1HQ00tU0hBMjU2IGJpdHM9MTI4IHZlcmlmeT1OT1QpDQoJZm9yIDxiaXN3YWppdHNhbXByaXRpQGdtYWlsLmNvbT47IFdlZCwgMjAgSnVsIDIwMjIgMjM6NTU6MTkgLTA0MDANCkRLSU0tU2lnbmF0dXJlOiB2PTE7IGE9cnNhLXNoYTI1NjsgYz1yZWxheGVkL3JlbGF4ZWQ7IGQ9bWl0LmVkdTsgcz1vdXRnb2luZzsNCgl0PTE2NTgzNzU3MTk7IGJoPVcrL1pHZEIxZDNJVTh4RjZDQVBicDRDaUQ5S2NlVVN4Z1dpZnhYQWZGK3c9Ow0KCWg9RnJvbTpEYXRlOlN1YmplY3Q6VG87DQoJYj1lSGNxYmRoR29GNVM4N2YrOXIvWFB0dDVEYmdCandnb1lUcytKTVBIcUFIZ2hzazhLVVRoQTFyZkhab2hvTENVUQ0KCSBxamVEbW1rQXg0aDdKeS9ldG1nemdJSGEwZmhVRHpmbDh6Y1NZUVNDU29zM0NRTERieVlkYzNVMjJyWW0xcVhmVE4NCgkgYzRsYlhJMVQvbit0b25tcnkyMG8wZ2I1YlhMVGZVWjZTblc5RitXSGhhUFBYY0pvK3cyNzREeExoL2tJcjRTaEJNDQoJIC80Qk16MHNOaXVHeGQrZzFyR3lsclAvcjVnTTRxeHl6SlRVZjA4UVljeCtEUURVc3o3dlpVUXZLUjVJV3dSSit6TA0KCSBCZjY5cElwckZuakIzeXk1MWVxeGpIZXFnWDFWeE5GVlV0S2FoZm5VTys0dTRWVGRBQzk1MU5rRDFLRzRzb0NHWVgNCgkgYmFIMnR6Ny96QXZnQT09DQpSZWNlaXZlZDogYnkgbWFpbC15dzEtZjE4Mi5nb29nbGUuY29tIHdpdGggU01UUCBpZCAwMDcyMTE1N2FlNjgyLTMxZTQ1NTI3ZGE1c280OTg5NTU3YjMuNQ0KICAgICAgICBmb3IgPGJpc3dhaml0c2FtcHJpdGlAZ21haWwuY29tPjsgV2VkLCAyMCBKdWwgMjAyMiAyMDo1NToxOCAtMDcwMCAoUERUKQ0KWC1HbS1NZXNzYWdlLVN0YXRlOiBBSklvcmE4aXlsUmVNZmU2RWxSL0hHL3AvTXFDcGhJVGNEL2hvYkpXS0ZZU3hWQVVOMHYycmIzbg0KCUMwc2s0dkdlcmlVUklNbkdxWCsrdUhMcFZzNHlPY3pscGFLZ3FIdz0NClgtUmVjZWl2ZWQ6IGJ5IDIwMDI6YTBkOmY2YzU6MDpiMDozMWQ6YWY3ZDo1ZDRmIHdpdGggU01UUCBpZA0KIGcxODgtMjAwMjBhMGRmNmM1MDAwMDAwYjAwMzFkYWY3ZDVkNGZtcjQ0MjU4MTI2eXdmLjE4Ny4xNjU4Mzc1NzE4MDA3OyBXZWQsIDIwDQogSnVsIDIwMjIgMjA6NTU6MTggLTA3MDAgKFBEVCkNCk1JTUUtVmVyc2lvbjogMS4wDQpGcm9tOiBBYXl1c2ggR3VwdGEgPGFheXVzaGdAbWl0LmVkdT4NCkRhdGU6IFdlZCwgMjAgSnVsIDIwMjIgMjM6NTU6MDYgLTA0MDANClgtR21haWwtT3JpZ2luYWwtTWVzc2FnZS1JRDogPENBK09KNVFmRU9NN0VFYlV6MCsya1cwdXQ2b0RaVDZ0c0J5N3BUazZEZ3pBTlQtTGROd0BtYWlsLmdtYWlsLmNvbT4NCk1lc3NhZ2UtSUQ6IDxDQStPSjVRZkVPTTdFRWJVejArMmtXMHV0Nm9EWlQ2dHNCeTdwVGs2RGd6QU5ULUxkTndAbWFpbC5nbWFpbC5jb20+DQpTdWJqZWN0OiBkZXNwZXJhdGVseSB0cnlpbmcgdG8gbWFrZSBpdCB0byBjaGFpbg0KVG86ICJiaXN3YWppdHNhbXByaXRpQGdtYWlsLmNvbSIgPGJpc3dhaml0c2FtcHJpdGlAZ21haWwuY29tPg0KQ29udGVudC1UeXBlOiBtdWx0aXBhcnQvYWx0ZXJuYXRpdmU7IGJvdW5kYXJ5PSIwMDAwMDAwMDAwMDA5Mzc3YWYwNWU0NDhhZjUxIg0KDQotLTAwMDAwMDAwMDAwMDkzNzdhZjA1ZTQ0OGFmNTENCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFpbjsgY2hhcnNldD0iVVRGLTgiDQoNCndpbGwgd2UgbWFrZSBpdCB0aGlzIHRpbWUgaW50byB0aGUgemsgcHJvb2YNCg0KLS0wMDAwMDAwMDAwMDA5Mzc3YWYwNWU0NDhhZjUxDQpDb250ZW50LVR5cGU6IHRleHQvaHRtbDsgY2hhcnNldD0iVVRGLTgiDQoNCjxkaXYgZGlyPSJhdXRvIj53aWxsIHdlIG1ha2UgaXQgdGhpcyB0aW1lIGludG8gdGhlIHprIHByb29mPC9kaXY+DQoNCi0tMDAwMDAwMDAwMDAwOTM3N2FmMDVlNDQ4YWY1MS0tDQo='; - const formattedArray = await insert13Before10( - Uint8Array.from(Buffer.from(emailFull)) + const formattedArrayAirbnb = await insert13Before10( + Uint8Array.from(Buffer.from(emailFullAirbnb)) + ); + const formattedArrayCoinbase = await insert13Before10( + Uint8Array.from(Buffer.from(emailFullCoinbase)) ); // Due to a quirk in carriage return parsing in JS, we need to manually edit carriage returns to match DKIM parsing - console.log('formattedArray', formattedArray); + console.log('formattedArrayAirbnb', formattedArrayAirbnb); + console.log( + 'buffFormArray', + Buffer.from(formattedArrayAirbnb.buffer) + ); + console.log( + 'buffFormArray', + formattedArrayAirbnb.toString() + ); + console.log('formattedArrayCoinbase', formattedArrayCoinbase); console.log( 'buffFormArray', - Buffer.from(formattedArray.buffer) + Buffer.from(formattedArrayCoinbase.buffer) ); console.log( 'buffFormArray', - formattedArray.toString() + formattedArrayCoinbase.toString() ); console.log('ethereumAddress', ethereumAddress); let input = ''; try { - input = await generate_input.generate_inputs( - Buffer.from(formattedArray.buffer), + input = await generate_two_inputs.generate_inputs_kyc( + Buffer.from(formattedArrayAirbnb.buffer), + Buffer.from(formattedArrayCoinbase.buffer), ethereumAddress ); } catch (e) { @@ -461,7 +487,7 @@ export const MainPage: React.FC<{}> = (props) => { /> - {displayMessage === - 'Downloading compressed proving files... (this may take a few minutes)' && ( - - )} - - {status !== 'not-started' ? ( -
- Status: - - {status} - -
- ) : ( -
- )} - -
-
- - Output - { - setProof(e.currentTarget.value); - }} - warning={verificationMessage} - warningColor={verificationPassed ? 'green' : 'red'} - /> - { - setPublicSignals(e.currentTarget.value); - }} - // warning={ - // } - /> - - - {isSuccess && ( -
- Transaction:{' '} - - {data?.hash} - -
- )} -
-
-
+ { + setEthereumAddress(e.currentTarget.value); + }} + /> + + {displayMessage === "Downloading compressed proving files... (this may take a few minutes)" && ( + + )} + + {status !== "not-started" ? ( +
+ Status: + {status} +
+ ) : ( +
+ )} + +
+
+ + Output + { + setProof(e.currentTarget.value); + }} + warning={verificationMessage} + warningColor={verificationPassed ? "green" : "red"} + /> + { + setPublicSignals(e.currentTarget.value); + }} + // warning={ + // } + /> + + + {isSuccess && ( +
+ Transaction: {data?.hash} +
+ )} +
+
+ ); }; @@ -643,4 +515,4 @@ const Container = styled.div` width: 500px; } } -`; +`; \ No newline at end of file From db11c70e72fd7e038b342a52b5a83f7fd4c9f1d1 Mon Sep 17 00:00:00 2001 From: Nathan Xiong Date: Thu, 4 May 2023 01:13:28 -0400 Subject: [PATCH 11/25] update gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 142a86e86..23700c584 100644 --- a/.gitignore +++ b/.gitignore @@ -71,4 +71,4 @@ generate_input_log.txt .vscode # from zk-kyc -temp.py \ No newline at end of file +/temp.py \ No newline at end of file From 9e0a689277b1762161ab4e84afd697c8caa7b266 Mon Sep 17 00:00:00 2001 From: Nathan Xiong Date: Thu, 4 May 2023 01:16:16 -0400 Subject: [PATCH 12/25] remove ignored files --- temp.py | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 temp.py diff --git a/temp.py b/temp.py deleted file mode 100644 index f356b01b3..000000000 --- a/temp.py +++ /dev/null @@ -1,8 +0,0 @@ -l1 = ["99","111","110","116","101","110","116","45","116","121","112","101","58","109","117","108","116","105","112","97","114","116","47","97","108","116","101","114","110","97","116","105","118","101","59","32","98","111","117","110","100","97","114","121","61","98","50","53","50","98","49","99","101","99","101","49","101","49","48","55","98","97","52","54","57","98","54","48","56","97","55","51","99","100","55","97","55","98","57","48","98","98","51","102","99","99","102","49","50","97","101","97","99","49","53","49","99","48","49","101","99","57","55","52","97","13","10","102","114","111","109","58","65","105","114","98","110","98","32","60","97","117","116","111","109","97","116","101","100","64","97","105","114","98","110","98","46","99","111","109","62","13","10","109","105","109","101","45","118","101","114","115","105","111","110","58","49","46","48","13","10","115","117","98","106","101","99","116","58","84","104","97","110","107","115","32","102","111","114","32","112","114","111","118","105","100","105","110","103","32","97","32","103","111","118","101","114","110","109","101","110","116","32","73","68","13","10","120","45","102","101","101","100","98","97","99","107","45","105","100","58","49","54","57","51","48","51","58","83","71","13","10","116","111","58","110","97","116","104","97","110","120","105","111","110","103","49","49","64","103","109","97","105","108","46","99","111","109","13","10","100","107","105","109","45","115","105","103","110","97","116","117","114","101","58","118","61","49","59","32","97","61","114","115","97","45","115","104","97","50","53","54","59","32","99","61","114","101","108","97","120","101","100","47","114","101","108","97","120","101","100","59","32","100","61","101","109","97","105","108","46","97","105","114","98","110","98","46","99","111","109","59","32","104","61","99","111","110","116","101","110","116","45","116","121","112","101","58","102","114","111","109","58","109","105","109","101","45","118","101","114","115","105","111","110","58","115","117","98","106","101","99","116","58","120","45","102","101","101","100","98","97","99","107","45","105","100","58","116","111","58","99","99","59","32","115","61","115","50","48","49","53","48","52","50","56","59","32","98","104","61","85","112","72","88","49","75","48","75","78","108","107","111","82","43","108","74","103","110","115","70","80","69","100","76","107","98","50","76","121","115","105","50","71","56","104","104","122","54","122","122","80","117","111","61","59","32","98","61","128"] -l = [85,112,72,88,49,75,48,75,78,108,107,111,82,43,108,74,103,110,115,70,80,69,100,76,107,98,50,76,121,115,105,50,71,56,104,104,122,54,122,122,80,117,111,61] -l2 = ["99","111","110","116","101","110","116","45","116","121","112","101","58","109","117","108","116","105","112","97","114","116","47","97","108","116","101","114","110","97","116","105","118","101","59","32","98","111","117","110","100","97","114","121","61","98","50","53","50","98","49","99","101","99","101","49","101","49","48","55","98","97","52","54","57","98","54","48","56","97","55","51","99","100","55","97","55","98","57","48","98","98","51","102","99","99","102","49","50","97","101","97","99","49","53","49","99","48","49","101","99","57","55","52","97","13","10","102","114","111","109","58","65","105","114","98","110","98","32","60","97","117","116","111","109","97","116","101","100","64","97","105","114","98","110","98","46","99","111","109","62","13","10","109","105","109","101","45","118","101","114","115","105","111","110","58","49","46","48","13","10","115","117","98","106","101","99","116","58","84","104","97","110","107","115","32","102","111","114","32","112","114","111","118","105","100","105","110","103","32","97","32","103","111","118","101","114","110","109","101","110","116","32","73","68","13","10","120","45","102","101","101","100","98","97","99","107","45","105","100","58","49","54","57","51","48","51","58","83","71","13","10","116","111","58","110","97","116","104","97","110","120","105","111","110","103","49","49","64","103","109","97","105","108","46","99","111","109","13","10","100","107","105","109","45","115","105","103","110","97","116","117","114","101","58","118","61","49","59","32","97","61","114","115","97","45","115","104","97","50","53","54","59","32","99","61","114","101","108","97","120","101","100","47","114","101","108","97","120","101","100","59","32","100","61","101","109","97","105","108","46","97","105","114","98","110","98","46","99","111","109","59","32","104","61","99","111","110","116","101","110","116","45","116","121","112","101","58","102","114","111","109","58","109","105","109","101","45","118","101","114","115","105","111","110","58","115","117","98","106","101","99","116","58","120","45","102","101","101","100","98","97","99","107","45","105","100","58","116","111","58","99","99","59","32","115","61","115","50","48","49","53","48","52","50","56","59","32","98","104","61","85","112","72","88","49","75","48","75","78","108","107","111","82","43","108","74","103","110","115","70","80","69","100","76","107","98","50","76","121","115","105","50","71","56","104","104","122","54","122","122","80","117","111","61","59","32","98","61","128","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","14","88","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0"] -# print("".join([chr(int(c)) for c in l])) -print(len(l2)) - -email_from_idx = 120 -email_to_idx = 235 \ No newline at end of file From e46df0ed6c46f60032113722cfb5994d2b4ba9f3 Mon Sep 17 00:00:00 2001 From: Nathan Xiong Date: Thu, 11 May 2023 13:19:22 -0400 Subject: [PATCH 13/25] added compatibility with old public keys --- .gitignore | 4 ++- circuits/email_coinbase.circom | 3 --- dizkus-scripts/2_gen_wtns.sh | 2 +- src/contracts/src/KYCEmailHandler.sol | 1 + src/helpers/dkim/dkim-verifier.js | 35 ++++++++++++++++++++++++--- src/helpers/dkim/modulus_to_pem.py | 9 +++++++ src/helpers/dkim/tools.js | 8 +++++- src/pages/MainPage.tsx | 20 +++++++-------- src/scripts/generate_input.ts | 6 +++-- src/scripts/generate_two_inputs.ts | 12 ++++----- 10 files changed, 71 insertions(+), 29 deletions(-) create mode 100644 src/helpers/dkim/modulus_to_pem.py diff --git a/.gitignore b/.gitignore index 23700c584..5c8aa959a 100644 --- a/.gitignore +++ b/.gitignore @@ -71,4 +71,6 @@ generate_input_log.txt .vscode # from zk-kyc -/temp.py \ No newline at end of file +/temp.py +*.pem +*.der \ No newline at end of file diff --git a/circuits/email_coinbase.circom b/circuits/email_coinbase.circom index df87d4499..568de8f2f 100644 --- a/circuits/email_coinbase.circom +++ b/circuits/email_coinbase.circom @@ -81,9 +81,6 @@ template CoinbaseEmailVerify(max_header_bytes, n, k) { (to_regex_out, to_regex_reveal) <== ToRegex(max_header_bytes)(in_padded); to_regex_out === 1; to_email <== VarShiftLeft(max_header_bytes, max_header_bytes)(to_regex_reveal, email_to_idx); // can probably change output length - // for (var i = 0; i < max_header_bytes; i++) { - // log(to_email[i]); - // } // BODY HASH REGEX: 617,597 constraints // This extracts the body hash from the header (i.e. the part after bh= within the DKIM-signature section) diff --git a/dizkus-scripts/2_gen_wtns.sh b/dizkus-scripts/2_gen_wtns.sh index 533920d44..509b8a7bb 100755 --- a/dizkus-scripts/2_gen_wtns.sh +++ b/dizkus-scripts/2_gen_wtns.sh @@ -4,7 +4,7 @@ source circuit.env echo "****GENERATING WITNESS FOR SAMPLE INPUT****" start=$(date +%s) set -x -node "$BUILD_DIR"/"$CIRCUIT_NAME"_js/generate_witness.js "$BUILD_DIR"/"$CIRCUIT_NAME"_js/"$CIRCUIT_NAME".wasm ../circuits/inputs/input.json "$BUILD_DIR"/witness.wtns +node "$BUILD_DIR"/"$CIRCUIT_NAME"_js/generate_witness.js "$BUILD_DIR"/"$CIRCUIT_NAME"_js/"$CIRCUIT_NAME".wasm ../circuits/inputs/input_kyc.json "$BUILD_DIR"/witness.wtns { set +x; } 2>/dev/null end=$(date +%s) echo "DONE ($((end - start))s)" diff --git a/src/contracts/src/KYCEmailHandler.sol b/src/contracts/src/KYCEmailHandler.sol index 780624ccd..ee4b5c596 100644 --- a/src/contracts/src/KYCEmailHandler.sol +++ b/src/contracts/src/KYCEmailHandler.sol @@ -125,6 +125,7 @@ contract VerifiedKYCEmail is ERC721Enumerable, Verifier { require(verifyProof(a, b, c, signals), "Invalid Proof"); // checks effects iteractions, this should come first // Effects: Mint token + // TODO: Add nullifier functionality uint256 tokenId = tokenCounter.current() + 1; string memory messageBytes = StringUtils.convertPackedBytesToBytes(bodySignals, bytesInPackedBytes * body_len, bytesInPackedBytes); tokenIDToName[tokenId] = messageBytes; diff --git a/src/helpers/dkim/dkim-verifier.js b/src/helpers/dkim/dkim-verifier.js index 337329a4c..7f5ad2da0 100644 --- a/src/helpers/dkim/dkim-verifier.js +++ b/src/helpers/dkim/dkim-verifier.js @@ -8,7 +8,7 @@ if (typeof process === 'object') { } const LOCAL = isNode; -const { getSigningHeaderLines, getPublicKey, parseDkimHeaders, formatAuthHeaderRow, getAlignment } = require("./tools"); +const { getSigningHeaderLines, getPublicKey, parseDkimHeaders, formatAuthHeaderRow, getAlignment, oldKeys } = require("./tools"); const { MessageParser } = require("./message-parser"); const { dkimBody } = require("./body"); const { generateCanonicalizedHeader } = require("./header"); @@ -245,10 +245,37 @@ class DkimVerifier extends MessageParser { status.comment = `unknown key type`; break; - case "EINVALIDVAL": - status.result = "neutral"; + case "EINVALIDVAL": // using old public key + for (let i = 0; i < oldKeys.length; i++) { + let paddingNeeded = oldKeys[i].length % 4 ? 4 - (oldKeys[i].length % 4) : 0; + const old_key_pem = Buffer.from(`-----BEGIN PUBLIC KEY-----\n${(oldKeys[i] + "=".repeat(paddingNeeded)).replace(/.{64}/g, "$&\n")}\n-----END PUBLIC KEY-----`); + + let ver_result_old = false; + if (LOCAL) { + ver_result_old = crypto.verify( + signatureHeader.signAlgo === "rsa" ? signatureHeader.algorithm : null, + canonicalizedHeader, + old_key_pem, + Buffer.from(signatureHeader.parsed?.b?.value, "base64") + ); + } else { + let ver_old = crypto.createVerify("RSA-SHA256"); + ver_old.update(canonicalizedHeader); + ver_result_old = ver_old.verify({ key: old_key_pem.toString(), format: "pem" }, Buffer.from(signatureHeader.parsed?.b?.value, "base64")); + } + + status.signature_header = canonicalizedHeader; + status.signature_value = signatureHeader.parsed?.b?.value; + status.result = ver_result_old ? "pass" : "fail"; + + if (status.result === "pass") { + publicKey = old_key_pem; + break; + } + } + status.result = "neutral"; status.comment = `invalid public key`; - break; + break; case "ESHORTKEY": status.result = "policy"; diff --git a/src/helpers/dkim/modulus_to_pem.py b/src/helpers/dkim/modulus_to_pem.py new file mode 100644 index 000000000..a443f11bd --- /dev/null +++ b/src/helpers/dkim/modulus_to_pem.py @@ -0,0 +1,9 @@ +from Crypto.PublicKey import RSA +from Crypto.Util.number import long_to_bytes + +n = 93230141572400662170783753550307761264955081869460053639895434883348985555848023394186640556096911451485975596801521695255694734630678338219590345941037464687540511026989400486063960033279502094209058208866021776512104192812763971526144421706090065402405684981981627715733224518387601434649205998732618652003 +e = 65537 + +key = RSA.construct((n, e)) +with open("rsa_pub_key.der", "wb") as f: + f.write(key.exportKey("DER")) \ No newline at end of file diff --git a/src/helpers/dkim/tools.js b/src/helpers/dkim/tools.js index 6f2b8f3e4..3d08c5efa 100644 --- a/src/helpers/dkim/tools.js +++ b/src/helpers/dkim/tools.js @@ -479,6 +479,10 @@ const validateAlgorithm = (algorithm, strict) => { } }; +const oldKeys = [ + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCEw53tgIoF8hrRikgKN/8E0xFqT5rBWYegP1JcIbP92dtLtG6HeS1sck5UCYR6HUy8NEEcUaxn1gnSezht6iyWR0G6BwNOevGGLg3N6rO9V5YayIIf4C74tjQdCiHP34wCRJQ05zWq8q/XMrAm5B3SKQMMl2LV/WoVCjyneq5ZYwIDAQAB" +] + module.exports = { writeToStream, parseHeaders, @@ -489,7 +493,7 @@ module.exports = { formatSignatureHeaderLine, parseDkimHeaders, getPublicKey, - formatAuthHeaderRow, + formatAuthHeaderRow, escapeCommentValue, validateAlgorithm, @@ -498,4 +502,6 @@ module.exports = { formatRelaxedLine, formatDomain, + + oldKeys, }; diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx index f25553d59..fb8667112 100644 --- a/src/pages/MainPage.tsx +++ b/src/pages/MainPage.tsx @@ -31,7 +31,7 @@ const generate_input_kyc = require("../scripts/generate_two_inputs"); export const MainPage: React.FC<{}> = (props) => { // raw user inputs - const filename = "email"; + const filename = "email_both"; const [emailSignals, setEmailSignals] = useState(""); const [emailFullAirbnb, setEmailFullAirbnb] = useState(localStorage.emailFullAirbnb || ""); @@ -177,16 +177,16 @@ export const MainPage: React.FC<{}> = (props) => { const steps = [ { - title: "Send a Twitter Password Reset Email", - description: "Send yourself a Twitter password reset email from Twitter." + title: "Complete KYC Verification", + description: "Complete KYC Verification on both Airbnb and Coinbase (current setup only works with these two, but more can be added in the future)." }, { title: "Download Email Contents", - description: "In your inbox, find the email from Twitter and click the three dot menu, then 'Show original' then 'Copy to clipboard'. If on Outlook, download the original email as .eml and copy it instead.' " + description: "In your inbox, find the KYC confirmation email from Airbnb and click the three dot menu, then 'Show original' then 'Copy to clipboard'. If on Outlook, download the original email as .eml and copy it instead.' Do the same for the Coinbase email." }, { title: "Paste Input", - description: "Copy paste that into the box below. Note that we cannot use this to phish you: we do not know your password, and we never get this email info because we have no server at all. We are actively searching for a less sketchy email." + description: "Copy paste the contents of the two emails into their respective boxes. Note that we cannot use this to phish you: we never get this email info because we have no server at all." }, { title: "Add Ethereum Address", @@ -197,8 +197,8 @@ export const MainPage: React.FC<{}> = (props) => { description: "Click 'Generate Proof'. Since it is completely client side and open source, and you are not trusting us with any private information." }, { - title: "Verify & Mint NFT Soul Token", - description: "Click 'Verify' and then 'Mint Twitter Badge On-Chain', and approve to mint the NFT badge that proves Twitter ownership! Note that it is 700K gas right now so only feasible on Goerli, though we intend to reduce this soon." + title: "Verify & Mint NFT to Address", + description: "Click 'Verify' and then 'Mint KYC Badge On-Chain', and approve to mint the NFT badge for proof of personhood!" } ]; @@ -221,8 +221,8 @@ export const MainPage: React.FC<{}> = (props) => { > Note that we are actively developing and debugging this page, it is likely unstable. Due to download - limits of incognito mode and non-chrome browsers, you must use Chrome to generate proofs right now. Our goal for March 2023 is to make this process 10x faster and - smaller. If you wish to generate a ZK proof of Twitter badge, you must do these: + limits of incognito mode and non-chrome browsers, you must use Chrome to generate proofs right now. Our goal for June 2023 is to make this process 10x faster and + smaller. If you wish to generate a ZK proof of personhood badge, you must do these: {} @@ -403,7 +403,7 @@ export const MainPage: React.FC<{}> = (props) => { : isLoading ? "Confirm in wallet" : verificationPassed - ? "Mint Twitter badge on-chain" + ? "Mint KYC badge on-chain" : "Verify first, before minting on-chain!"} {isSuccess && ( diff --git a/src/scripts/generate_input.ts b/src/scripts/generate_input.ts index ad734f150..3cc34162d 100644 --- a/src/scripts/generate_input.ts +++ b/src/scripts/generate_input.ts @@ -121,7 +121,7 @@ export async function getCircuitInputs( const [bodyPadded, bodyPaddedLen] = await sha256Pad(body, Math.max(MAX_BODY_PADDED_BYTES, calc_length)); // Convet messagePadded to string to print the specific header data that is signed - console.log(JSON.stringify(message).toString()); + // console.log(JSON.stringify(message).toString()); // Ensure SHA manual unpadded is running the correct function const shaOut = await partialSha(messagePadded, messagePaddedLen); @@ -280,7 +280,9 @@ export async function generate_inputs(raw_email: Buffer | string, eth_address: s // Only called when the whole function is called from the command line, to read inputs async function do_generate(writeToFile: boolean = true) { - const { email_file, nonce } = await getArgs(); + // const { email_file, nonce } = await getArgs(); + const email_file = "./nathan_coinbase_email.eml"; + const nonce = null const email = fs.readFileSync(email_file.trim()); console.log(email); const gen_inputs = await generate_inputs(email, "0x0000000000000000000000000000000000000000", nonce); diff --git a/src/scripts/generate_two_inputs.ts b/src/scripts/generate_two_inputs.ts index b7cd559f4..201da2d33 100644 --- a/src/scripts/generate_two_inputs.ts +++ b/src/scripts/generate_two_inputs.ts @@ -28,7 +28,6 @@ import { // nonce: Nonce to diambiguate input/output files (optional, only useful for monolithic server side provers) const email_file_airbnb = "./nathan_airbnb_email.eml"; const email_file_coinbase = "./nathan_coinbase_email.eml"; - const email_file_default = "./nathan_twitter_email.eml"; // TODO: Edit function when hooking up to frontend async function getArgs() { @@ -82,7 +81,6 @@ import { TEST = "test", EMAIL = "email", SUBJECTPARSER = "subjectparser", - KYC = "kyc", } async function findSelector(a: Uint8Array, selector: number[]): Promise { @@ -188,9 +186,9 @@ import { let raw_header = Buffer.from(prehash_message_string).toString(); const email_from_idx = raw_header.length - trimStrByStr(trimStrByStr(raw_header, "from:"), "<").length; - const email_to_idx = raw_header.length - trimStrByStr(trimStrByStr(raw_header, "to:"), "<").length; - // const email_from_idx = Buffer.from(prehash_message_string).indexOf("from:").toString(); - // const email_to_idx = Buffer.from(prehash_message_string).indexOf("to:").toString(); + // If email contains "<"" in front of it, use the commented version + const email_to_idx = raw_header.length - trimStrByStr(raw_header, "to:").length; + // const email_to_idx = raw_header.length - trimStrByStr(trimStrByStr(raw_header, "to:"), "<").length; let email_subject = trimStrByStr(raw_header, "subject:"); const amount_idx = raw_header.length - trimStrByStr(email_subject, "end ").length; const currency_idx = raw_header.length - trimStrByStr(trimStrByStr(email_subject, "end "), " ").length; @@ -395,7 +393,7 @@ import { const email = fs.readFileSync(email_file.trim()); console.log(email); const gen_inputs = await generate_inputs(email, "0x0000000000000000000000000000000000000000", nonce); - console.log(JSON.stringify(gen_inputs)); + (JSON.stringify(gen_inputs)); if (writeToFile) { const filename = nonce ? `../input_${nonce}.json` : "./circuits/inputs/input.json"; console.log(`Writing to default file ${filename}`); @@ -410,7 +408,7 @@ import { const email_coinbase = fs.readFileSync(email_file_coinbase.trim()); // console.log(email); const gen_inputs = await generate_inputs_kyc(email_airbnb, email_coinbase, "0x0000000000000000000000000000000000000000"); - console.log(JSON.stringify(gen_inputs)); + // console.log(JSON.stringify(gen_inputs)); if (writeToFile) { // const filename = nonce ? `../input_${nonce}.json` : "./circuits/inputs/input.json"; // console.log(`Writing to default file ${filename}`); From 057c413fb80f62ef7c8a43df9cb9a7b84a4efe23 Mon Sep 17 00:00:00 2001 From: Nathan Xiong Date: Fri, 12 May 2023 16:03:43 -0400 Subject: [PATCH 14/25] refactored contract --- package.json | 2 +- src/contracts/src/KYCEmailHandler.sol | 72 +++++------------- src/contracts/src/MailServer.sol | 37 +++++++++ src/helpers/dkim/modulus_to_pem.py | 6 +- src/helpers/zkp.ts | 3 +- yarn.lock | 104 ++++++++++---------------- 6 files changed, 103 insertions(+), 121 deletions(-) diff --git a/package.json b/package.json index ec4821833..d465990c2 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "react-use": "^17.3.2", "readline": "^1.3.0", "serve": "^14.0.1", - "snarkjs": "git+https://github.com/vb7401/snarkjs.git#24981febe8826b6ab76ae4d76cf7f9142919d2b8", + "snarkjs": "https://github.com/sampritipanda/snarkjs.git#fef81fc51d17a734637555c6edbd585ecda02d9e", "sshpk": "^1.17.0", "styled-components": "^5.3.5", "ts-node": "^10.9.1", diff --git a/src/contracts/src/KYCEmailHandler.sol b/src/contracts/src/KYCEmailHandler.sol index ee4b5c596..0d6fc21d0 100644 --- a/src/contracts/src/KYCEmailHandler.sol +++ b/src/contracts/src/KYCEmailHandler.sol @@ -24,73 +24,36 @@ contract VerifiedKYCEmail is ERC721Enumerable, Verifier { uint256 public constant rsa_modulus_chunks_len = 17; // uint256 public constant rsa_modulus_chunks_len = 9; // change to for 1024-bit RSA uint256 public constant header_len = msg_len - body_len; - uint256 public constant addressIndexInSignals = msg_len - 1; // TODO: fix constant + uint256 public constant addressIndexInSignals = msg_len - 1; mapping(string => uint256[rsa_modulus_chunks_len]) public verifiedMailserverKeys; mapping(uint256 => string) public tokenIDToName; string constant domain_airbnb = "airbnb.com"; string constant domain_coinbase = "coinbase.com"; + MailServer mailServer; + Verifier public immutable verifier; - constructor() ERC721("VerifiedKYC", "VerifiedKYC") { - // Do dig TXT outgoing._domainkey.twitter.com to verify these. - // This is the base 2^121 representation of that key. - // Circom bigint: represent a = a[0] + a[1] * 2**n + .. + a[k - 1] * 2**(n * k) + constructor(Verifier v, MailServer m) ERC721("AnonymousKYC", "AnonKYC") { + verifier = v; + mailServer = m; require(2 * rsa_modulus_chunks_len + body_len + 1 == msg_len, "Variable counts are wrong!"); - - // TODO: Create a type that takes in a raw RSA key, the bit count, - // and whether or not its base64 encoded, and converts it to either 8 or 16 signals - verifiedMailserverKeys["airbnb.com"][0] = 1782267151472132502396673758441738163; - verifiedMailserverKeys["airbnb.com"][1] = 211482981992850046267405122085516466; - verifiedMailserverKeys["airbnb.com"][2] = 454331740279802979553218083106524093; - verifiedMailserverKeys["airbnb.com"][3] = 2403631535172814929511297080499227501; - verifiedMailserverKeys["airbnb.com"][4] = 2245858962887391502631714271235221261; - verifiedMailserverKeys["airbnb.com"][5] = 2622546081161044621195511843069142201; - verifiedMailserverKeys["airbnb.com"][6] = 1247628895302131918172499597775434966; - verifiedMailserverKeys["airbnb.com"][7] = 1584816411261150842617500336767389232; - verifiedMailserverKeys["airbnb.com"][8] = 52914273202064513; - verifiedMailserverKeys["airbnb.com"][9] = 0; - verifiedMailserverKeys["airbnb.com"][10] = 0; - verifiedMailserverKeys["airbnb.com"][11] = 0; - verifiedMailserverKeys["airbnb.com"][12] = 0; - verifiedMailserverKeys["airbnb.com"][13] = 0; - verifiedMailserverKeys["airbnb.com"][14] = 0; - verifiedMailserverKeys["airbnb.com"][15] = 0; - verifiedMailserverKeys["airbnb.com"][16] = 0; - - // TODO: Update coinbase public key - verifiedMailserverKeys["coinbase.com"][0] = 1345060269316532707410324038691477859; - verifiedMailserverKeys["coinbase.com"][1] = 384766469338727068594017962971556116; - verifiedMailserverKeys["coinbase.com"][2] = 168911276988157118943281324996362385; - verifiedMailserverKeys["coinbase.com"][3] = 1165220578700378509253846448878043993; - verifiedMailserverKeys["coinbase.com"][4] = 1468253564629208485538769233538980768; - verifiedMailserverKeys["coinbase.com"][5] = 2375057771089481827666297753868306658; - verifiedMailserverKeys["coinbase.com"][6] = 1859460967236870128489365675225233949; - verifiedMailserverKeys["coinbase.com"][7] = 2514159567794221963503259554592798082; - verifiedMailserverKeys["coinbase.com"][8] = 37369779987712517; - verifiedMailserverKeys["coinbase.com"][9] = 0; - verifiedMailserverKeys["coinbase.com"][10] = 0; - verifiedMailserverKeys["coinbase.com"][11] = 0; - verifiedMailserverKeys["coinbase.com"][12] = 0; - verifiedMailserverKeys["coinbase.com"][13] = 0; - verifiedMailserverKeys["coinbase.com"][14] = 0; - verifiedMailserverKeys["coinbase.com"][15] = 0; - verifiedMailserverKeys["coinbase.com"][16] = 0; } // change to some KYC description function tokenDesc(uint256 tokenId) public view returns (string memory) { address address_owner = ownerOf(tokenId); - string memory result = string(abi.encodePacked(StringUtils.toString(address_owner), "has a valid zk-KYC")); + string memory result = string(abi.encodePacked(StringUtils.toString(address_owner), "has a valid anonymous KYC")); return result; } - // TODO: change this function + // TODO: change this function for KYC function tokenURI(uint256 tokenId) public view override returns (string memory) { string memory username = tokenIDToName[tokenId]; address owner = ownerOf(tokenId); return NFTSVG.constructAndReturnSVG(username, tokenId, owner); } + // TODO: change this function for KYC function _domainCheck(uint256[] memory headerSignals) public pure returns (bool) { string memory senderBytes = StringUtils.convertPackedBytesToBytes(headerSignals, 18, bytesInPackedBytes); string[2] memory domainStrings = ["verify@twitter.com", "info@twitter.com"]; @@ -105,10 +68,13 @@ contract VerifiedKYCEmail is ERC721Enumerable, Verifier { // 3 public signals are the masked packed message bytes, 17 are the modulus. uint256[] memory bodySignals = new uint256[](body_len); - uint256[] memory rsaModulusSignals = new uint256[](header_len); // why is this defined? - for (uint256 i = 0; i < body_len; i++) bodySignals[i] = signals[i]; - for (uint256 i = body_len; i < msg_len - 1; i++) rsaModulusSignals[i - body_len] = signals[i]; - + uint256[] memory rsaModulusSignals = new uint256[](header_len); + for (uint256 i = 0; i < body_len; i++){ + bodySignals[i] = signals[i]; + } + for (uint256 i = body_len; i < msg_len - 1; i++) { + rsaModulusSignals[i - body_len] = signals[i]; + } // Check eth address committed to in proof matches msg.sender, to avoid replayability require(address(uint160(signals[addressIndexInSignals])) == msg.sender, "Invalid address"); @@ -119,8 +85,8 @@ contract VerifiedKYCEmail is ERC721Enumerable, Verifier { // Verify that the public key for RSA matches the hardcoded one for (uint i = 0; i < rsa_modulus_chunks_len; i++) { - require(signals[body_len + i] == verifiedMailserverKeys[domain_airbnb][i], "Invalid: Airbnb RSA modulus not matched"); - require(signals[body_len + rsa_modulus_chunks_len + i] == verifiedMailserverKeys[domain_coinbase][i], "Invalid, Coinbase RSA modulus not matched"); + require(mailServer.isVerified(domain_airbnb, i, signals[body_len + i]), "Invalid: Airbnb RSA modulus not matched"); + require(mailServer.isVerified(domain_coinbase, i, signals[body_len + rsa_modulus_chunks_len + i]), "Invalid: Coinbase RSA modulus not matched"); } require(verifyProof(a, b, c, signals), "Invalid Proof"); // checks effects iteractions, this should come first @@ -134,6 +100,6 @@ contract VerifiedKYCEmail is ERC721Enumerable, Verifier { } function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal { - require(from == address(0), "Cannot transfer - VerifiedKYCEmail is soulbound"); + require(from == address(0), "Cannot transfer - AnonKYC is soulbound"); } } \ No newline at end of file diff --git a/src/contracts/src/MailServer.sol b/src/contracts/src/MailServer.sol index 52c83cf48..6bed6b815 100644 --- a/src/contracts/src/MailServer.sol +++ b/src/contracts/src/MailServer.sol @@ -105,6 +105,43 @@ contract MailServer { verifiedMailserverKeys["skiff.com"][14] = 1366599807917971505788646146248798329; verifiedMailserverKeys["skiff.com"][15] = 391565989352979266796804441125988853; verifiedMailserverKeys["skiff.com"][16] = 3704766395208948862861103932863036; + + verifiedMailserverKeys["airbnb.com"][0] = 1782267151472132502396673758441738163; + verifiedMailserverKeys["airbnb.com"][1] = 211482981992850046267405122085516466; + verifiedMailserverKeys["airbnb.com"][2] = 454331740279802979553218083106524093; + verifiedMailserverKeys["airbnb.com"][3] = 2403631535172814929511297080499227501; + verifiedMailserverKeys["airbnb.com"][4] = 2245858962887391502631714271235221261; + verifiedMailserverKeys["airbnb.com"][5] = 2622546081161044621195511843069142201; + verifiedMailserverKeys["airbnb.com"][6] = 1247628895302131918172499597775434966; + verifiedMailserverKeys["airbnb.com"][7] = 1584816411261150842617500336767389232; + verifiedMailserverKeys["airbnb.com"][8] = 52914273202064513; + verifiedMailserverKeys["airbnb.com"][9] = 0; + verifiedMailserverKeys["airbnb.com"][10] = 0; + verifiedMailserverKeys["airbnb.com"][11] = 0; + verifiedMailserverKeys["airbnb.com"][12] = 0; + verifiedMailserverKeys["airbnb.com"][13] = 0; + verifiedMailserverKeys["airbnb.com"][14] = 0; + verifiedMailserverKeys["airbnb.com"][15] = 0; + verifiedMailserverKeys["airbnb.com"][16] = 0; + + // TODO: Update coinbase public key + verifiedMailserverKeys["coinbase.com"][0] = 1345060269316532707410324038691477859; + verifiedMailserverKeys["coinbase.com"][1] = 384766469338727068594017962971556116; + verifiedMailserverKeys["coinbase.com"][2] = 168911276988157118943281324996362385; + verifiedMailserverKeys["coinbase.com"][3] = 1165220578700378509253846448878043993; + verifiedMailserverKeys["coinbase.com"][4] = 1468253564629208485538769233538980768; + verifiedMailserverKeys["coinbase.com"][5] = 2375057771089481827666297753868306658; + verifiedMailserverKeys["coinbase.com"][6] = 1859460967236870128489365675225233949; + verifiedMailserverKeys["coinbase.com"][7] = 2514159567794221963503259554592798082; + verifiedMailserverKeys["coinbase.com"][8] = 37369779987712517; + verifiedMailserverKeys["coinbase.com"][9] = 0; + verifiedMailserverKeys["coinbase.com"][10] = 0; + verifiedMailserverKeys["coinbase.com"][11] = 0; + verifiedMailserverKeys["coinbase.com"][12] = 0; + verifiedMailserverKeys["coinbase.com"][13] = 0; + verifiedMailserverKeys["coinbase.com"][14] = 0; + verifiedMailserverKeys["coinbase.com"][15] = 0; + verifiedMailserverKeys["coinbase.com"][16] = 0; } function _stringEq(string memory a, string memory b) internal pure returns (bool) { diff --git a/src/helpers/dkim/modulus_to_pem.py b/src/helpers/dkim/modulus_to_pem.py index a443f11bd..c9edde7ed 100644 --- a/src/helpers/dkim/modulus_to_pem.py +++ b/src/helpers/dkim/modulus_to_pem.py @@ -1,9 +1,13 @@ from Crypto.PublicKey import RSA from Crypto.Util.number import long_to_bytes +# replace n with the modulus you want to use n = 93230141572400662170783753550307761264955081869460053639895434883348985555848023394186640556096911451485975596801521695255694734630678338219590345941037464687540511026989400486063960033279502094209058208866021776512104192812763971526144421706090065402405684981981627715733224518387601434649205998732618652003 e = 65537 key = RSA.construct((n, e)) with open("rsa_pub_key.der", "wb") as f: - f.write(key.exportKey("DER")) \ No newline at end of file + f.write(key.exportKey("DER")) + +# after generating the .der file, run in the terminal +# openssl rsa -inform DER -in rsa_pub_key.der -pubin -out rsa_pub_key.pem \ No newline at end of file diff --git a/src/helpers/zkp.ts b/src/helpers/zkp.ts index 33e0abee5..244226bb7 100644 --- a/src/helpers/zkp.ts +++ b/src/helpers/zkp.ts @@ -4,7 +4,8 @@ import { uncompressGz as uncompress } from "./uncompress"; const snarkjs = require("snarkjs"); -export const loadURL = "https://zkemail-zkey-chunks.s3.amazonaws.com/"; +// export const loadURL = "https://zkemail-zkey-chunks.s3.amazonaws.com/"; +export const loadURL = "https://zk-kyc-zkey-chunks.s3.amazonaws.com/"; const compressed = true; // const loadURL = "/zkemail-zkey-chunks/"; diff --git a/yarn.lock b/yarn.lock index 35b35c744..ccda9df06 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2280,13 +2280,13 @@ __metadata: languageName: node linkType: hard -"@iden3/binfileutils@npm:0.0.10": - version: 0.0.10 - resolution: "@iden3/binfileutils@npm:0.0.10" +"@iden3/binfileutils@npm:0.0.11": + version: 0.0.11 + resolution: "@iden3/binfileutils@npm:0.0.11" dependencies: - fastfile: 0.0.19 + fastfile: 0.0.20 ffjavascript: ^0.2.48 - checksum: cdeb8ac01e12f485d9fb236654c00d5d5016fc89eae24f7822885dd42f09935cbef601dbdd8a0c96dfb00ded9f4f623e0eec0b568aa86d16522cf77ce6f9498b + checksum: ca61db1325c7e038c6bd723c856eff5f2c82c76394db09d3350ef4f5b7525e3c9ab1f7429900ff5d3e9d26c5970bf5900e6126ccb5c5caa597c16a47336a6be8 languageName: node linkType: hard @@ -5706,13 +5706,6 @@ __metadata: languageName: node linkType: hard -"big-integer@npm:^1.6.42, big-integer@npm:^1.6.48": - version: 1.6.51 - resolution: "big-integer@npm:1.6.51" - checksum: 3d444173d1b2e20747e2c175568bedeebd8315b0637ea95d75fd27830d3b8e8ba36c6af40374f36bdaea7b5de376dcada1b07587cb2a79a928fccdb6e6e3c518 - languageName: node - linkType: hard - "big.js@npm:^5.2.2": version: 5.2.2 resolution: "big.js@npm:5.2.2" @@ -6699,14 +6692,14 @@ __metadata: languageName: node linkType: hard -"circom_runtime@npm:0.1.17": - version: 0.1.17 - resolution: "circom_runtime@npm:0.1.17" +"circom_runtime@npm:0.1.21": + version: 0.1.21 + resolution: "circom_runtime@npm:0.1.21" dependencies: - ffjavascript: 0.2.48 + ffjavascript: 0.2.56 bin: calcwit: calcwit.js - checksum: 595fc0cc3a62ba5daf8d849feae41c48805c0df43965f85dde4dc434efb607e455fa7801d41c1feacfe0c3c71952a45cd3985abf26fde40c54138392891afd8c + checksum: 3071f1e0fba9a5fb41c940454edb911ce09edfd5d0bd12156ec79045a0bf3ff2cc5b35f46e84e42902ef8bb0a4166f428b75d0ceb363c0d485f1a111b27daba1 languageName: node linkType: hard @@ -8372,7 +8365,7 @@ __metadata: readline: ^1.3.0 selenium-webdriver: ^4.8.1 serve: ^14.0.1 - snarkjs: "git+https://github.com/vb7401/snarkjs.git#24981febe8826b6ab76ae4d76cf7f9142919d2b8" + snarkjs: "https://github.com/sampritipanda/snarkjs.git#fef81fc51d17a734637555c6edbd585ecda02d9e" sshpk: ^1.17.0 styled-components: ^5.3.5 ts-node: ^10.9.1 @@ -9786,10 +9779,10 @@ __metadata: languageName: node linkType: hard -"fastfile@npm:0.0.19, fastfile@npm:^0.0.19": - version: 0.0.19 - resolution: "fastfile@npm:0.0.19" - checksum: 6179bdd7c21be9882294dae66103795c099594098b51958bcf08a4545c91387321b43511730d0542a5a9ed8c5ec9069c065e065fd67255453ac900a23895dac1 +"fastfile@npm:0.0.20": + version: 0.0.20 + resolution: "fastfile@npm:0.0.20" + checksum: e5d6e5f57a9b58c9534202e477cbffbca2182c407171950695ddb5c3e6b89554bc8561fbb6e370c99e371a8f23486a23fbaca527827886cec4897d481cbd03b6 languageName: node linkType: hard @@ -9867,15 +9860,14 @@ __metadata: languageName: node linkType: hard -"ffjavascript@npm:0.2.48": - version: 0.2.48 - resolution: "ffjavascript@npm:0.2.48" +"ffjavascript@npm:0.2.56": + version: 0.2.56 + resolution: "ffjavascript@npm:0.2.56" dependencies: - big-integer: ^1.6.48 - wasmbuilder: ^0.0.12 - wasmcurves: 0.1.0 + wasmbuilder: 0.0.16 + wasmcurves: 0.2.0 web-worker: ^1.2.0 - checksum: 68beae9a4f642c06656685353b84fd7655020ca0e628ea046e94452ab779587953cc45cde106d74b68be7177b49c8f19b105d6552c4a1d715e784ae9e7c9ed34 + checksum: d4e02263db4a94d111cdc7c1211ae96769370f5c8c3c338331e0ef99faed7b55e640bedf23fa8a83fc9a77f0e81140ea8f32e392812a00e15ca504221b879a4f languageName: node linkType: hard @@ -16636,15 +16628,15 @@ __metadata: languageName: node linkType: hard -"r1csfile@npm:0.0.35": - version: 0.0.35 - resolution: "r1csfile@npm:0.0.35" +"r1csfile@npm:0.0.41": + version: 0.0.41 + resolution: "r1csfile@npm:0.0.41" dependencies: "@iden3/bigarray": 0.0.2 - "@iden3/binfileutils": 0.0.10 - fastfile: 0.0.19 - ffjavascript: 0.2.48 - checksum: 84f7b4eab5bcdd6a3f6d699998c9479a5eff8d670383d4f0c5afc08431f45353abab9a8b07eeabaef89807e24b0ba50611d4d6280eb6c3a7483e1487a91f0ac6 + "@iden3/binfileutils": 0.0.11 + fastfile: 0.0.20 + ffjavascript: 0.2.56 + checksum: eec689416f66f09db2d6ca66fac1ef6841b088ab29abcde487145ebd2110916c92583e11ac86f0cdcc4e8a3a7c7df9ff5352ad959e8ae385d37c3b51cec5cf4d languageName: node linkType: hard @@ -18384,23 +18376,24 @@ __metadata: languageName: node linkType: hard -"snarkjs@git+https://github.com/vb7401/snarkjs.git#24981febe8826b6ab76ae4d76cf7f9142919d2b8": - version: 0.4.12 - resolution: "snarkjs@https://github.com/vb7401/snarkjs.git#commit=24981febe8826b6ab76ae4d76cf7f9142919d2b8" +"snarkjs@https://github.com/sampritipanda/snarkjs.git#fef81fc51d17a734637555c6edbd585ecda02d9e": + version: 0.5.0 + resolution: "snarkjs@https://github.com/sampritipanda/snarkjs.git#commit=fef81fc51d17a734637555c6edbd585ecda02d9e" dependencies: - "@iden3/binfileutils": 0.0.10 + "@iden3/binfileutils": 0.0.11 + bfj: ^7.0.2 blake2b-wasm: ^2.4.0 - circom_runtime: 0.1.17 + circom_runtime: 0.1.21 ejs: ^3.1.6 - fastfile: ^0.0.19 - ffjavascript: 0.2.48 + fastfile: 0.0.20 + ffjavascript: 0.2.56 js-sha3: ^0.8.0 + localforage: ^1.10.0 logplease: ^1.2.15 - r1csfile: 0.0.35 - readline: ^1.3.0 + r1csfile: 0.0.41 bin: snarkjs: build/cli.cjs - checksum: 9011df4b58475a0b4ae988f8b459a9a4d2bb5d2b60221d0ec370a10f2492c88909768215f3b22e514b2cf24dca79818790447005a33ed6aee177b9fda6948a75 + checksum: f2050f0135d50d459ea0edddf3e394e833a2d28c6648e5889b2f896814865e5c60606e978a8a106bd5bfe7e27501c315f249db5b71895d5e7e6e9a87bfcd55ab languageName: node linkType: hard @@ -20553,25 +20546,6 @@ __metadata: languageName: node linkType: hard -"wasmbuilder@npm:^0.0.12": - version: 0.0.12 - resolution: "wasmbuilder@npm:0.0.12" - dependencies: - big-integer: ^1.6.48 - checksum: 327b3c50b0e1e5e3aac9e218e0f96fdc638b7952ab86acc2ad53960371996826dbb0a8095edce482cf1d9c245d96884449701909bc962920aa7ec8241db01214 - languageName: node - linkType: hard - -"wasmcurves@npm:0.1.0": - version: 0.1.0 - resolution: "wasmcurves@npm:0.1.0" - dependencies: - big-integer: ^1.6.42 - blakejs: ^1.1.0 - checksum: 6bf6719e659a88904af0b98d152316e3b22435ca6a2cfc8bbf4530576806f17b2776b2c7d91d1a678fe0d51485a0d1748efcd080808c181c7977bee50b26efa9 - languageName: node - linkType: hard - "wasmcurves@npm:0.2.0": version: 0.2.0 resolution: "wasmcurves@npm:0.2.0" From 16ecfc18540503809c2c9afb3be4667bb1fc7fac Mon Sep 17 00:00:00 2001 From: novus677 <51405949+novus677@users.noreply.github.com> Date: Sat, 13 May 2023 01:22:12 -0400 Subject: [PATCH 15/25] Create CNAME --- CNAME | 1 + 1 file changed, 1 insertion(+) create mode 100644 CNAME diff --git a/CNAME b/CNAME new file mode 100644 index 000000000..48928922a --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +anonkyc.com \ No newline at end of file From d219ffaccc3c3b1e2e25dc322f89aaefefd4ee07 Mon Sep 17 00:00:00 2001 From: novus677 <51405949+novus677@users.noreply.github.com> Date: Sat, 13 May 2023 01:28:03 -0400 Subject: [PATCH 16/25] Delete CNAME --- CNAME | 1 - 1 file changed, 1 deletion(-) delete mode 100644 CNAME diff --git a/CNAME b/CNAME deleted file mode 100644 index 48928922a..000000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -anonkyc.com \ No newline at end of file From 63a0bf67fea0e1b32a44ffb0d3c9ac0babc2f432 Mon Sep 17 00:00:00 2001 From: Nathan Xiong Date: Sat, 13 May 2023 17:53:18 -0400 Subject: [PATCH 17/25] added vkey --- src/helpers/vkey.ts | 292 +++++++++++++++++++++++++++++ src/scripts/generate_two_inputs.ts | 4 +- 2 files changed, 294 insertions(+), 2 deletions(-) diff --git a/src/helpers/vkey.ts b/src/helpers/vkey.ts index b5c4861a9..229bcc08a 100644 --- a/src/helpers/vkey.ts +++ b/src/helpers/vkey.ts @@ -1,3 +1,4 @@ +/* export const vkey = { protocol: "groth16", curve: "bn128", @@ -59,3 +60,294 @@ export const vkey = { ["12028012375636649231431542422094056328652150783328682821491835725444465403330", "330627474571770559729829431278328850519463731324979004581250682428211832702", "1"], ], }; +*/ + +export const vkey = { + "protocol": "groth16", + "curve": "bn128", + "nPublic": 40, + "vk_alpha_1": [ + "20491192805390485299153009773594534940189261866228447918068658471970481763042", + "9383485363053290200918347156157836566562967994039712273449902621266178545958", + "1" + ], + "vk_beta_2": [ + [ + "6375614351688725206403948262868962793625744043794305715222011528459656738731", + "4252822878758300859123897981450591353533073413197771768651442665752259397132" + ], + [ + "10505242626370262277552901082094356697409835680220590971873171140371331206856", + "21847035105528745403288232691147584728191162732299865338377159692350059136679" + ], + [ + "1", + "0" + ] + ], + "vk_gamma_2": [ + [ + "10857046999023057135944570762232829481370756359578518086990519993285655852781", + "11559732032986387107991004021392285783925812861821192530917403151452391805634" + ], + [ + "8495653923123431417604973247489272438418190587263600148770280649306958101930", + "4082367875863433681332203403145435568316851327593401208105741076214120093531" + ], + [ + "1", + "0" + ] + ], + "vk_delta_2": [ + [ + "9807164380251633320270781057851447975413763120899682835651746536114812553994", + "10343081133609258106524901662136446830203953069418302653607138025109354852056" + ], + [ + "19577108980448557774164310518706121321497510178047308624728190351127007029613", + "9944420113684168402007836039557494668185622235286586265128723919315954929511" + ], + [ + "1", + "0" + ] + ], + "vk_alphabeta_12": [ + [ + [ + "2029413683389138792403550203267699914886160938906632433982220835551125967885", + "21072700047562757817161031222997517981543347628379360635925549008442030252106" + ], + [ + "5940354580057074848093997050200682056184807770593307860589430076672439820312", + "12156638873931618554171829126792193045421052652279363021382169897324752428276" + ], + [ + "7898200236362823042373859371574133993780991612861777490112507062703164551277", + "7074218545237549455313236346927434013100842096812539264420499035217050630853" + ] + ], + [ + [ + "7077479683546002997211712695946002074877511277312570035766170199895071832130", + "10093483419865920389913245021038182291233451549023025229112148274109565435465" + ], + [ + "4595479056700221319381530156280926371456704509942304414423590385166031118820", + "19831328484489333784475432780421641293929726139240675179672856274388269393268" + ], + [ + "11934129596455521040620786944827826205713621633706285934057045369193958244500", + "8037395052364110730298837004334506829870972346962140206007064471173334027475" + ] + ] + ], + "IC": [ + [ + "13911334274401924343675544560041308512588936901424910915603782906803297287632", + "2706123920292725976458900370590743282893519207291145650034358362573351809778", + "1" + ], + [ + "6985983538948125859102879759305585957033692335816034727504459126154150675393", + "19972750462935050417295831096846277473290254620709512808517185507733918100846", + "1" + ], + [ + "7744013186186492394192214327299469757175689346110767167154898216471196095085", + "6702729055720766626065307937755216430432678137682221614353038778589657960048", + "1" + ], + [ + "255915235030371991993484001712616086436758891388484439648352245043294529900", + "4179505299213548642445718315004146505466351834266056645355508781336008368451", + "1" + ], + [ + "14665449237617429987947939493398796988508835581367002665682766941024976091705", + "762004656482205873626930109934934985880770906990530286373664203254704399119", + "1" + ], + [ + "9891081518337938052858589684877032246112975663226300403315954293261278376940", + "19489996687572280863382620397533026772053282893687720955820232087604849495176", + "1" + ], + [ + "5800952410174350989374575600427733477116335318735807160590138906298167392978", + "10087684366978175789571270859672147139504313886600685836467201147453228792716", + "1" + ], + [ + "21351332895842959440410596862055681606274721309941311913188097986540391756486", + "2307072863391971707807462264997264310991255504705487943650727153057932486703", + "1" + ], + [ + "13010659854056812016439519531663311954437532545284071926934993582761474224894", + "15816218888092633942089854415791255109502438402393571397446345369212440492159", + "1" + ], + [ + "5053705414025665707100333667090818209330898042069933520812706581768614416747", + "15102167500006968672268298512544770652224200188765016527963864802106967926258", + "1" + ], + [ + "18842434238151770363997463153238823211722305234308000152572121185461721165534", + "2593387840584284469427225866602223950231412681510330394144072762948160157256", + "1" + ], + [ + "15599182068312680660960536864833796975142938275261895467487695736451312294383", + "4666518800946599840711720480266222815125027594955011646010035289619688501056", + "1" + ], + [ + "9113330458144966221935075056421416883691137461488614310024809145998323557603", + "14525998074188030501201355527081933288769508591085401978245999429699317995155", + "1" + ], + [ + "1698361326922985986278517202575612983085060321585257306576729960115662047082", + "10811440926949560531025358441147529667704238851901100869553842063328440863767", + "1" + ], + [ + "21813322714619416501160428454453290155766777717262000799102654107816607855965", + "1900098864322431724982182925647723282402120214677411997484653888105945993420", + "1" + ], + [ + "4850391572444221222891473530479010507625931117274574478058276734887281510448", + "21209333872097499618431030650092175877302366840044306035116464128115890800796", + "1" + ], + [ + "11024325828225418385172578260153741127557638280432460894781093623688133027317", + "12829771981770099593305823120912530429245566056736702770500768601904968866660", + "1" + ], + [ + "6499199902234026878468170995556481877772698039370309856572671196533366057357", + "4457036475648163147639419384098488748765647820786558552307664597920606634328", + "1" + ], + [ + "18022767910853449640857081466776039368894005536449264312356527521962753150045", + "19255367004765661489282403633399213738840373861881573851712640969865112253205", + "1" + ], + [ + "3407915644635962426547873514159178484708180897582674981651195673063889056527", + "20900842108041417167531653748937888806375160842841645236594766769740801086240", + "1" + ], + [ + "20017654297737842052107095428206289877193449936297035323430379821054305801225", + "18398515026520352890282481306377861550205376583366632991373903923950653555070", + "1" + ], + [ + "21047973865590898317558361621568151146087679718605108018529528496898961038772", + "21278634433260452963308259175825820317340782823939379578046404446402428234210", + "1" + ], + [ + "8241145138357410146565515812882570766759410582275691347723108605200802022956", + "1989081011088741796912864754739172167152133683074048511937493943117875172858", + "1" + ], + [ + "17569097387405075516786396328891553530486899788694929338454645324083714108418", + "2671272267873010509558066732587132159825241218632027523162826309557010040801", + "1" + ], + [ + "18432934328198141990735900067700545183644918237318871758528883499458953191751", + "12907904986298391408698324332133566949685445388692800711291464791587658944848", + "1" + ], + [ + "11647174916828858228600836692025264965425510498861972640414508608610165498786", + "2789266155005779176091573091508902987969786671406288086616050740944435032366", + "1" + ], + [ + "7716599352357689662437791281378834953674147767709763441696690660416798000193", + "2011956446519231175256986068510459528070989280957991078572908702061967826029", + "1" + ], + [ + "14881679753221971431657980926845949992839985126401023404107224715610776731068", + "9378592721036535509644483460953612255671078585476916759911615244007744068220", + "1" + ], + [ + "5152714233547642924425453943362933487670747811842493222288515712293139545616", + "17103096071991383067025581815771854616548148128756030455605263703527561205910", + "1" + ], + [ + "15447374417074702151058563859314109816008078225151836487467438630855834394189", + "17622407048249609790965427996195733485238662836944547380822418048735288467805", + "1" + ], + [ + "8651195532923628086866919418072252415467334585437736376449571499823619232889", + "14998264407811038481851626580083663176688649010417880010392153937322836336730", + "1" + ], + [ + "7458457418733257481313115870700418691758253843381439582888002847293920934780", + "542167882620985531753545552575713620470656945139339404822606611648495438637", + "1" + ], + [ + "11654478610393315165602893189754745536674121231400580736622038568733670735982", + "12257355783551921174011032474512008533083087974719806073358692398516604463769", + "1" + ], + [ + "20322635549433112154444217943766297421929292916507426703075749847590584882286", + "17064859567711980715716858605905961120685111304069430254040524583096277093911", + "1" + ], + [ + "2351611421496751815297337091125626628153834298569012697092929966223240549367", + "8981623119830487140187538030830803038148631170446603756509370347108713360372", + "1" + ], + [ + "14536962684274075654744716239480828438557306956570655739004040615159003943312", + "15989427664222476135240023865637576890575158594894752990204712714267063734873", + "1" + ], + [ + "2619628326543741336308640920600617374518257676707551075589324630412339157610", + "7966211775560790007913238855770952022525432549688555817284872825049075239614", + "1" + ], + [ + "687223592036625422088204689474192126235881791115711355027369272731146690572", + "5456875514001475540695452284699708537730275839107823178023311848531691970413", + "1" + ], + [ + "6946456658180332721992546139783275797641755855230168345362036906695016641551", + "11316286553478747631365490256288642487416717626257031294300167632075226007209", + "1" + ], + [ + "18376919354566106859685708361984142415330243406938923421496478653958087728870", + "8827642705442411540364786437220347440481285673685576911202226958271761055385", + "1" + ], + [ + "6057728902703241688367200208848751084102678742749995632673910557958470618058", + "7921304270410952949195938865544544978067811906564190603914442420062807566570", + "1" + ] + ] + } \ No newline at end of file diff --git a/src/scripts/generate_two_inputs.ts b/src/scripts/generate_two_inputs.ts index 201da2d33..5f6218fe3 100644 --- a/src/scripts/generate_two_inputs.ts +++ b/src/scripts/generate_two_inputs.ts @@ -26,7 +26,7 @@ import { // email_file: Path to email file // nonce: Nonce to diambiguate input/output files (optional, only useful for monolithic server side provers) - const email_file_airbnb = "./nathan_airbnb_email.eml"; + const email_file_airbnb = "./nathan_twitter_email.eml"; const email_file_coinbase = "./nathan_coinbase_email.eml"; // TODO: Edit function when hooking up to frontend @@ -412,7 +412,7 @@ import { if (writeToFile) { // const filename = nonce ? `../input_${nonce}.json` : "./circuits/inputs/input.json"; // console.log(`Writing to default file ${filename}`); - fs.writeFileSync(`./circuits/inputs/input_kyc.json`, JSON.stringify(gen_inputs), { flag: "w" }); + fs.writeFileSync(`./circuits/inputs/input.json`, JSON.stringify(gen_inputs), { flag: "w" }); } return gen_inputs; } From 308dc320d78a5df230e7ebe206297a5eb1ac1608 Mon Sep 17 00:00:00 2001 From: Nathan Xiong Date: Mon, 15 May 2023 00:44:24 -0400 Subject: [PATCH 18/25] strip quotes if bad fetch --- src/helpers/dkim/tools.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/helpers/dkim/tools.js b/src/helpers/dkim/tools.js index 3d08c5efa..9cff35091 100644 --- a/src/helpers/dkim/tools.js +++ b/src/helpers/dkim/tools.js @@ -278,8 +278,21 @@ const getPublicKey = async (type, name, minBitLength, resolver) => { if (rr) { // prefix value for parsing as there is no default value let entry = parseDkimHeaders(`DNS: TXT;${rr}`); + let publicKeyValue = entry?.parsed?.p?.value; + + if (!publicKeyValue) { + rr = rr.replace(/['"]+/g, ''); + entry = parseDkimHeaders(`DNS: TXT;${rr}`); + publicKeyValue = entry?.parsed?.p?.value; + + if (!publicKeyValue) { + let err = new Error("Missing key value"); + err.code = "EINVALIDVAL"; + err.rr = rr; + throw err; + } + } - const publicKeyValue = entry?.parsed?.p?.value; if (!publicKeyValue) { let err = new Error("Missing key value"); err.code = "EINVALIDVAL"; From 84330f247ff223574279963a50616990ce2348ac Mon Sep 17 00:00:00 2001 From: Nathan Xiong Date: Mon, 15 May 2023 00:44:48 -0400 Subject: [PATCH 19/25] added nullifier check --- src/contracts/src/KYCEmailHandler.sol | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/contracts/src/KYCEmailHandler.sol b/src/contracts/src/KYCEmailHandler.sol index 0d6fc21d0..dccee8b68 100644 --- a/src/contracts/src/KYCEmailHandler.sol +++ b/src/contracts/src/KYCEmailHandler.sol @@ -27,7 +27,8 @@ contract VerifiedKYCEmail is ERC721Enumerable, Verifier { uint256 public constant addressIndexInSignals = msg_len - 1; mapping(string => uint256[rsa_modulus_chunks_len]) public verifiedMailserverKeys; - mapping(uint256 => string) public tokenIDToName; + // mapping(uint256 => string) public tokenIDToName; + mapping(bytes32 => bool) public nullifiers; string constant domain_airbnb = "airbnb.com"; string constant domain_coinbase = "coinbase.com"; MailServer mailServer; @@ -47,11 +48,11 @@ contract VerifiedKYCEmail is ERC721Enumerable, Verifier { } // TODO: change this function for KYC - function tokenURI(uint256 tokenId) public view override returns (string memory) { - string memory username = tokenIDToName[tokenId]; - address owner = ownerOf(tokenId); - return NFTSVG.constructAndReturnSVG(username, tokenId, owner); - } + // function tokenURI(uint256 tokenId) public view override returns (string memory) { + // string memory username = tokenIDToName[tokenId]; + // address owner = ownerOf(tokenId); + // return NFTSVG.constructAndReturnSVG(username, tokenId, owner); + // } // TODO: change this function for KYC function _domainCheck(uint256[] memory headerSignals) public pure returns (bool) { @@ -78,6 +79,10 @@ contract VerifiedKYCEmail is ERC721Enumerable, Verifier { // Check eth address committed to in proof matches msg.sender, to avoid replayability require(address(uint160(signals[addressIndexInSignals])) == msg.sender, "Invalid address"); + // Check nullifier hasn't been used + bytes32 nullifier = keccak256(abi.encodePacked(bodySignals)); + require(!nullifiers[nullifier], "Nullifier has already been used!"); + // Check from/to email domains are correct [in this case, only from domain is checked] // Right now, we just check that any email was received from anyone at Twitter, which is good enough for now // We will upload the version with these domain checks soon! @@ -91,10 +96,10 @@ contract VerifiedKYCEmail is ERC721Enumerable, Verifier { require(verifyProof(a, b, c, signals), "Invalid Proof"); // checks effects iteractions, this should come first // Effects: Mint token - // TODO: Add nullifier functionality + nullifiers[nullifier] = true; uint256 tokenId = tokenCounter.current() + 1; - string memory messageBytes = StringUtils.convertPackedBytesToBytes(bodySignals, bytesInPackedBytes * body_len, bytesInPackedBytes); - tokenIDToName[tokenId] = messageBytes; + // string memory messageBytes = StringUtils.convertPackedBytesToBytes(bodySignals, bytesInPackedBytes * body_len, bytesInPackedBytes); + // tokenIDToName[tokenId] = messageBytes; _mint(msg.sender, tokenId); tokenCounter.increment(); } From 5bba79594327f68769d48c85fd01ea2c99a28ab2 Mon Sep 17 00:00:00 2001 From: Nathan Xiong Date: Tue, 16 May 2023 03:32:29 -0400 Subject: [PATCH 20/25] fixed contract tests --- .gitignore | 4 +- .gitmodules | 3 + circuits/email_both.circom | 2 +- lib/forge-std | 1 + src/contracts/lib/forge-std | 2 +- src/contracts/script/Deploy.s.sol | 9 +- src/contracts/src/KYCEmailHandler.sol | 8 +- src/contracts/src/test/TestKYC.t.sol | 136 +++++ src/contracts/src/test/TestWallet.t.sol | 203 ------- src/contracts/testEmailHandler.abi.ts | 736 +++++++++++++++++++++++ src/helpers/KYCEmailHandler.abi.ts | 747 ++++++++++++++++++++++++ src/pages/MainPage.tsx | 6 +- 12 files changed, 1642 insertions(+), 215 deletions(-) create mode 160000 lib/forge-std create mode 100644 src/contracts/src/test/TestKYC.t.sol delete mode 100644 src/contracts/src/test/TestWallet.t.sol create mode 100644 src/contracts/testEmailHandler.abi.ts create mode 100644 src/helpers/KYCEmailHandler.abi.ts diff --git a/.gitignore b/.gitignore index 5c8aa959a..2095f6ba3 100644 --- a/.gitignore +++ b/.gitignore @@ -73,4 +73,6 @@ generate_input_log.txt # from zk-kyc /temp.py *.pem -*.der \ No newline at end of file +*.der +src/contracts/broadcast/ +lib/forge-std/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 772baddb0..7d6f60276 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "src/contracts/lib/forge-std"] path = src/contracts/lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/forge-std"] + path = lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/circuits/email_both.circom b/circuits/email_both.circom index 2ce26242c..1340025dc 100644 --- a/circuits/email_both.circom +++ b/circuits/email_both.circom @@ -42,7 +42,7 @@ template KYCVerify(max_header_bytes, n, k) { // Outputs the hash of the two body hashes, which serves as the nullifier // Currently doesn't output from/to emails for domain check but should probably add that later var pack_size = 7; - var output_len = (32 - 1) \ pack_size + 1; + var output_len = (32 - 1) \ pack_size + 1; // output_len = 5 signal output nullifier[output_len]; component airbnb_verify = AirbnbEmailVerify(max_header_bytes, n, k); diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 000000000..9b49a72cf --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 9b49a72cfdb36bcf195eb863f868f01a6d6d3186 diff --git a/src/contracts/lib/forge-std b/src/contracts/lib/forge-std index 066ff16c5..9b49a72cf 160000 --- a/src/contracts/lib/forge-std +++ b/src/contracts/lib/forge-std @@ -1 +1 @@ -Subproject commit 066ff16c5c03e6f931cd041fd366bc4be1fae82a +Subproject commit 9b49a72cfdb36bcf195eb863f868f01a6d6d3186 diff --git a/src/contracts/script/Deploy.s.sol b/src/contracts/script/Deploy.s.sol index 191c3eec8..5140209fc 100644 --- a/src/contracts/script/Deploy.s.sol +++ b/src/contracts/script/Deploy.s.sol @@ -3,9 +3,11 @@ pragma solidity ^0.8.0; import "forge-std/Test.sol"; import "forge-std/console.sol"; import "forge-std/Script.sol"; -import "../src/TwitterEmailHandler.sol"; +// import "../src/TwitterEmailHandler.sol"; +import "../src/KYCEmailHandler.sol"; import "../src/StringUtils.sol"; -import "../src/Groth16VerifierTwitter.sol"; +// import "../src/Groth16VerifierTwitter.sol"; +import "../src/Groth16VerifierKYC.sol"; contract Deploy is Script, Test { function getPrivateKey() internal returns (uint256) { @@ -22,7 +24,8 @@ contract Deploy is Script, Test { vm.startBroadcast(sk); Verifier proofVerifier = new Verifier(); MailServer mailServer = new MailServer(); - VerifiedTwitterEmail testVerifier = new VerifiedTwitterEmail(proofVerifier, mailServer); + // VerifiedTwitterEmail testVerifier = new VerifiedTwitterEmail(proofVerifier, mailServer); + VerifiedKYCEmail testVerifier = new VerifiedKYCEmail(proofVerifier, mailServer); vm.stopBroadcast(); } } diff --git a/src/contracts/src/KYCEmailHandler.sol b/src/contracts/src/KYCEmailHandler.sol index dccee8b68..ee8ca8d72 100644 --- a/src/contracts/src/KYCEmailHandler.sol +++ b/src/contracts/src/KYCEmailHandler.sol @@ -24,7 +24,7 @@ contract VerifiedKYCEmail is ERC721Enumerable, Verifier { uint256 public constant rsa_modulus_chunks_len = 17; // uint256 public constant rsa_modulus_chunks_len = 9; // change to for 1024-bit RSA uint256 public constant header_len = msg_len - body_len; - uint256 public constant addressIndexInSignals = msg_len - 1; + uint256 public constant addressIndexInSignals = body_len; mapping(string => uint256[rsa_modulus_chunks_len]) public verifiedMailserverKeys; // mapping(uint256 => string) public tokenIDToName; @@ -74,7 +74,7 @@ contract VerifiedKYCEmail is ERC721Enumerable, Verifier { bodySignals[i] = signals[i]; } for (uint256 i = body_len; i < msg_len - 1; i++) { - rsaModulusSignals[i - body_len] = signals[i]; + rsaModulusSignals[i - body_len] = signals[i+1]; } // Check eth address committed to in proof matches msg.sender, to avoid replayability require(address(uint160(signals[addressIndexInSignals])) == msg.sender, "Invalid address"); @@ -90,8 +90,8 @@ contract VerifiedKYCEmail is ERC721Enumerable, Verifier { // Verify that the public key for RSA matches the hardcoded one for (uint i = 0; i < rsa_modulus_chunks_len; i++) { - require(mailServer.isVerified(domain_airbnb, i, signals[body_len + i]), "Invalid: Airbnb RSA modulus not matched"); - require(mailServer.isVerified(domain_coinbase, i, signals[body_len + rsa_modulus_chunks_len + i]), "Invalid: Coinbase RSA modulus not matched"); + require(mailServer.isVerified(domain_airbnb, i, signals[body_len + 1 + i]), "Invalid: Airbnb RSA modulus not matched"); + require(mailServer.isVerified(domain_coinbase, i, signals[body_len + rsa_modulus_chunks_len + 1 + i]), "Invalid: Coinbase RSA modulus not matched"); } require(verifyProof(a, b, c, signals), "Invalid Proof"); // checks effects iteractions, this should come first diff --git a/src/contracts/src/test/TestKYC.t.sol b/src/contracts/src/test/TestKYC.t.sol new file mode 100644 index 000000000..193296352 --- /dev/null +++ b/src/contracts/src/test/TestKYC.t.sol @@ -0,0 +1,136 @@ +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +// import "../TwitterEmailHandler.sol"; +// import "../Groth16VerifierTwitter.sol"; +import "../KYCEmailHandler.sol"; +import "../Groth16VerifierKYC.sol"; + +contract TwitterUtilsTest is Test { + using StringUtils for *; + + address constant VM_ADDR = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D; // Hardcoded address of the VM from foundry + + Verifier proofVerifier; + MailServer mailServer; + VerifiedKYCEmail testVerifier; + + uint16 public constant packSize = 7; + + function setUp() public { + proofVerifier = new Verifier(); + mailServer = new MailServer(); + testVerifier = new VerifiedKYCEmail(proofVerifier, mailServer); + } + + // function testMint() public { + // testVerifier.mint + // } + + // Should pass (note that there are extra 0 bytes, which are filtered out but should be noted in audits) + function testUnpack1() public { + uint256[] memory packedBytes = new uint256[](3); + packedBytes[0] = 29096824819513600; + packedBytes[1] = 0; + packedBytes[2] = 0; + + // This is 0x797573685f670000000000000000000000000000000000000000000000000000 + // packSize = 7 + string memory byteList = StringUtils.convertPackedBytesToBytes(packedBytes, 15, packSize); + // This is 0x797573685f67, since strings are internally arbitrary length arrays + string memory intended_value = "yush_g"; + + // We need to cast both to bytes32, which works since usernames can be at most 15, alphanumeric + '_' characters + // Note that this may not generalize to non-ascii characters. + // Weird characters are allowed in email addresses, see https://en.wikipedia.org/wiki/Email_address#Local-part + // See https://stackoverflow.com/a/2049510/3977093 -- you can even have international characters with RFC 6532 + // Our regex should just disallow most of these emails, but they may end up taking more than two bytes + // ASCII should fit in 2 bytes but emails may not be ASCII + assertEq(bytes32(bytes(byteList)), bytes32(bytes(intended_value))); + console.logString(byteList); + } + + function testUnpack2() public { + uint256[] memory packedBytes = new uint256[](3); + packedBytes[0] = 28557011619965818; + packedBytes[1] = 1818845549; + packedBytes[2] = 0; + string memory byteList = StringUtils.convertPackedBytesToBytes(packedBytes, 15, packSize); + string memory intended_value = "zktestemail"; + assertEq(bytes32(bytes(byteList)), bytes32(bytes(intended_value))); + console.logString(byteList); + } + + // Should pass (note that there are extra 0 bytes, which are filtered out but should be noted in audits) + function testVerifyTestEmail() public { + uint256[40] memory publicSignals; + publicSignals[0] = 70690609871614406; + publicSignals[1] = 58055581588623508; + publicSignals[2] = 22397246651374549; + publicSignals[3] = 37318600150826822; + publicSignals[4] = 2261577407; + publicSignals[5] = 746442972966401920475725247700175361147866454362; + publicSignals[6] = 1782267151472132502396673758441738163; + publicSignals[7] = 211482981992850046267405122085516466; + publicSignals[8] = 454331740279802979553218083106524093; + publicSignals[9] = 2403631535172814929511297080499227501; + publicSignals[10] = 2245858962887391502631714271235221261; + publicSignals[11] = 2622546081161044621195511843069142201; + publicSignals[12] = 1247628895302131918172499597775434966; + publicSignals[13] = 1584816411261150842617500336767389232; + publicSignals[14] = 52914273202064513; + publicSignals[15] = 0; + publicSignals[16] = 0; + publicSignals[17] = 0; + publicSignals[18] = 0; + publicSignals[19] = 0; + publicSignals[20] = 0; + publicSignals[21] = 0; + publicSignals[22] = 0; + publicSignals[23] = 1345060269316532707410324038691477859; + publicSignals[24] = 384766469338727068594017962971556116; + publicSignals[25] = 168911276988157118943281324996362385; + publicSignals[26] = 1165220578700378509253846448878043993; + publicSignals[27] = 1468253564629208485538769233538980768; + publicSignals[28] = 2375057771089481827666297753868306658; + publicSignals[29] = 1859460967236870128489365675225233949; + publicSignals[30] = 2514159567794221963503259554592798082; + publicSignals[31] = 37369779987712517; + publicSignals[32] = 0; + publicSignals[33] = 0; + publicSignals[34] = 0; + publicSignals[35] = 0; + publicSignals[36] = 0; + publicSignals[37] = 0; + publicSignals[38] = 0; + publicSignals[39] = 0; + + + uint256[2] memory proof_a = [ + 10862079222762785405941280086399026869200326165206154620732603812878833885377, + 4208139996377154785195812689695308432097093394702441574332516425978972244739 + ]; + + uint256[2][2] memory proof_b = [ + [9479317161521317673805716203059784868565164065844597368177915626332459107498, 9843182691571915655087350056319266721175223408718168238551325859304800994205], + [17629199875460838247663679794879603175329419799461256868402688444214598051409, 2002658621721373627570318787729557231703934573405726404696838197310474396618] + ]; + + uint256[2] memory proof_c = [ + 18044975281122236912928498020946409345131533244841032618058344425298894334856, + 2535152307925787078717326492571479491716008880650244024542736428184268425484 + ]; + + + // Test proof verification + bool verified = proofVerifier.verifyProof(proof_a, proof_b, proof_c, publicSignals); + assertEq(verified, true); + + // Test mint after spoofing msg.sender + Vm vm = Vm(VM_ADDR); + vm.startPrank(0x82Bfa918D51e8b6Ac23cC5425b50B60BdccC195a); + testVerifier.mint(proof_a, proof_b, proof_c, publicSignals); + vm.stopPrank(); + } +} \ No newline at end of file diff --git a/src/contracts/src/test/TestWallet.t.sol b/src/contracts/src/test/TestWallet.t.sol deleted file mode 100644 index a91b1f599..000000000 --- a/src/contracts/src/test/TestWallet.t.sol +++ /dev/null @@ -1,203 +0,0 @@ -pragma solidity ^0.8.0; -import "forge-std/Test.sol"; -import "forge-std/console.sol"; -import "../WalletEmailHandler.sol"; -import "../Groth16VerifierWallet.sol"; - -contract WalletUtilsTest is Test { - address internal constant zero = 0x0000000000000000000000000000000000000000; - address constant VM_ADDR = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D; - VerifiedWalletEmail testVerifier; - Verifier proofVerifier; - - function setUp() public { - testVerifier = new VerifiedWalletEmail(); - proofVerifier = new Verifier(); - } - - function testUnpackIntoFloat1() public { - uint256[] memory packedBytes = new uint256[](5); - packedBytes[0] = 0; - packedBytes[1] = 0; - packedBytes[2] = 0; - packedBytes[3] = 13661285; - string memory byteList = testVerifier.convertPackedBytesToBytes(packedBytes, 30); - string memory intended_value = "eth"; - assertEq(bytes32(bytes(byteList)), bytes32(bytes(intended_value))); - console.logString(byteList); - } - - function testUnpackIntoFloat2() public { - uint256[] memory packedBytes = new uint256[](4); - packedBytes[0] = 30515164652858234; - packedBytes[1] = 18147879272211830; - packedBytes[2] = 27917065853693287; - packedBytes[3] = 28015; - string memory byteList = testVerifier.convertPackedBytesToBytes(packedBytes, 30); - string memory intended_value = "zkemailverify@gmail.com"; - assertEq(bytes32(bytes(byteList)), bytes32(bytes(intended_value))); - console.logString(byteList); - } - - function testUnpackIntoFloat3() public { - uint256[] memory packedBytes = new uint256[](4); - packedBytes[0] = 0; - packedBytes[1] = 3485236; - packedBytes[2] = 0; - packedBytes[3] = 0; - string memory byteList = testVerifier.convertPackedBytesToBytes(packedBytes, 30); - string memory intended_value = "4.5"; - assertEq(stringToUint(byteList), 4); - assertEq(bytes32(bytes(byteList)), bytes32(bytes(intended_value))); - console.logString(byteList); - } - - function stringToUint(string memory s) internal pure returns (uint256) { - bytes memory b = bytes(s); - uint256 result = 0; - for (uint i = 0; i < b.length; i++) { - if (b[i] >= 0x30 && b[i] <= 0x39) { - result = result * 10 + (uint256(uint8(b[i])) - 48); - } - } - return result; - } - - function testUnpackIntoFloat4() public { - uint256[] memory packedBytes = new uint256[](4); - packedBytes[0] = 30515164652858234; - packedBytes[1] = 14207229598262646; - packedBytes[2] = 13067048790615872; - packedBytes[3] = 7171939; - string memory byteList = testVerifier.convertPackedBytesToBytes(packedBytes, 30); - string memory intended_value = "zkemailverify2@gmail.com"; - assertEq(bytes32(bytes(byteList)), bytes32(bytes(intended_value))); - console.logString(byteList); - } - - // Should pass (note that there are extra 0 bytes, which are filtered out but should be noted in audits) - function testVerifyWalletEmail() public { - uint256[34] memory publicSignals; - publicSignals[0] = 30515164652858234; - publicSignals[1] = 14207229598262646; - publicSignals[2] = 13067048790615872; - publicSignals[3] = 7171939; - publicSignals[4] = 0; - publicSignals[5] = 3485236; - publicSignals[6] = 0; - publicSignals[7] = 0; - publicSignals[8] = 0; - publicSignals[9] = 0; - publicSignals[10] = 13661285; - publicSignals[11] = 30515164652858234; - publicSignals[12] = 18147879272211830; - publicSignals[13] = 27917065853693287; - publicSignals[14] = 28015; - publicSignals[15] = 0; - publicSignals[16] = 2645260732387577900369388087711111123; - publicSignals[17] = 2332356685544126002119529566553287568; - publicSignals[18] = 587306946802222480578301599869128605; - publicSignals[19] = 1506808391343308562602228807782956759; - publicSignals[20] = 346696857027646434280628892032962406; - publicSignals[21] = 1655371642328152796841392591809876356; - publicSignals[22] = 773654757689631205903545947464515700; - publicSignals[23] = 137546842031326636154929265514533208; - publicSignals[24] = 979104436480501594376401576155183314; - publicSignals[25] = 1231402749194646866996172591430155068; - publicSignals[26] = 1573385231473380013164181608611759098; - publicSignals[27] = 1199794061179553911325952711127005960; - publicSignals[28] = 1393369642957971131987926230229916984; - publicSignals[29] = 2610100650498432208787557818514105421; - publicSignals[30] = 1405475120223887084339881602469286332; - publicSignals[31] = 2000538708964654339221687925776343058; - publicSignals[32] = 3483697379198011592407370076533025; - publicSignals[33] = 0; - // TODO switch order - uint256[2] memory proof_a = [ - 18214528451748025070455293058606558684367776249349482399993204103864741723468, - 15003530197647463595718037429164132062637106744660222086396269550328064261424 - ]; - // Note: you need to swap the order of the two elements in each subarray - uint256[2][2] memory proof_b = [ - [6461911610358766053365043908758394834732672681413987884242698462904724197255, 342103975494932482608081876029483576044074727035168137477391964391537410934], - [18351039964982209778799207158064219024562949371673722720718374575366986849311, 4669785024601609291633792167221088192727471283005169123961871153351390329210] - ]; - uint256[2] memory proof_c = [ - 17308091971421169481892128502517801279695749002269857786558424203436590932091, - 14587778590638321976005513090859474748106449498450192078465868665769372103254 - ]; - - // Test proof verification - bool verified = proofVerifier.verifyProof(proof_a, proof_b, proof_c, publicSignals); - assertEq(verified, true); - - // Test mint after spoofing msg.sender - // Vm vm = Vm(VM_ADDR); - // vm.startPrank(0x0000000000000000000000000000000000000001); - // testVerifier.transfer(proof_a, proof_b, proof_c, publicSignals); - // vm.stopPrank(); - } - - // Should pass (note that there are extra 0 bytes, which are filtered out but should be noted in audits) - function testTransferWalletEmail2() public { - uint256[34] memory publicSignals; - publicSignals[0] = 30515164652858234; - publicSignals[1] = 14207229598262646; - publicSignals[2] = 13067048790615872; - publicSignals[3] = 7171939; - publicSignals[4] = 0; - publicSignals[5] = 3485236; - publicSignals[6] = 0; - publicSignals[7] = 0; - publicSignals[8] = 0; - publicSignals[9] = 0; - publicSignals[10] = 13661285; - publicSignals[11] = 30515164652858234; - publicSignals[12] = 18147879272211830; - publicSignals[13] = 27917065853693287; - publicSignals[14] = 28015; - publicSignals[15] = 0; - publicSignals[16] = 2645260732387577900369388087711111123; - publicSignals[17] = 2332356685544126002119529566553287568; - publicSignals[18] = 587306946802222480578301599869128605; - publicSignals[19] = 1506808391343308562602228807782956759; - publicSignals[20] = 346696857027646434280628892032962406; - publicSignals[21] = 1655371642328152796841392591809876356; - publicSignals[22] = 773654757689631205903545947464515700; - publicSignals[23] = 137546842031326636154929265514533208; - publicSignals[24] = 979104436480501594376401576155183314; - publicSignals[25] = 1231402749194646866996172591430155068; - publicSignals[26] = 1573385231473380013164181608611759098; - publicSignals[27] = 1199794061179553911325952711127005960; - publicSignals[28] = 1393369642957971131987926230229916984; - publicSignals[29] = 2610100650498432208787557818514105421; - publicSignals[30] = 1405475120223887084339881602469286332; - publicSignals[31] = 2000538708964654339221687925776343058; - publicSignals[32] = 3483697379198011592407370076533025; - publicSignals[33] = 0; - // TODO switch order - uint256[2] memory proof_a = [ - 18214528451748025070455293058606558684367776249349482399993204103864741723468, - 15003530197647463595718037429164132062637106744660222086396269550328064261424 - ]; - // Note: you need to swap the order of the two elements in each subarray - uint256[2][2] memory proof_b = [ - [6461911610358766053365043908758394834732672681413987884242698462904724197255, 342103975494932482608081876029483576044074727035168137477391964391537410934], - [18351039964982209778799207158064219024562949371673722720718374575366986849311, 4669785024601609291633792167221088192727471283005169123961871153351390329210] - ]; - uint256[2] memory proof_c = [ - 17308091971421169481892128502517801279695749002269857786558424203436590932091, - 14587778590638321976005513090859474748106449498450192078465868665769372103254 - ]; - - // Test proof verification - bool verified = proofVerifier.verifyProof(proof_a, proof_b, proof_c, publicSignals); - assertEq(verified, true); - - // Test mint after spoofing msg.sender - Vm vm = Vm(VM_ADDR); - vm.startPrank(0x0000000000000000000000000000000000000001); - testVerifier.transfer(proof_a, proof_b, proof_c, publicSignals); - vm.stopPrank(); - } -} diff --git a/src/contracts/testEmailHandler.abi.ts b/src/contracts/testEmailHandler.abi.ts new file mode 100644 index 000000000..de5ff7f85 --- /dev/null +++ b/src/contracts/testEmailHandler.abi.ts @@ -0,0 +1,736 @@ +export const abi = [ + { + inputs: [], + stateMutability: "nonpayable", + type: "constructor" + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address" + }, + { + indexed: true, + internalType: "address", + name: "approved", + type: "address" + }, + { + indexed: true, + internalType: "uint256", + name: "tokenId", + type: "uint256" + } + ], + name: "Approval", + type: "event" + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address" + }, + { + indexed: true, + internalType: "address", + name: "operator", + type: "address" + }, + { + indexed: false, + internalType: "bool", + name: "approved", + type: "bool" + } + ], + name: "ApprovalForAll", + type: "event" + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "from", + type: "address" + }, + { + indexed: true, + internalType: "address", + name: "to", + type: "address" + }, + { + indexed: true, + internalType: "uint256", + name: "tokenId", + type: "uint256" + } + ], + name: "Transfer", + type: "event" + }, + { + inputs: [ + { + internalType: "uint256[]", + name: "headerSignals", + type: "uint256[]" + } + ], + name: "_domainCheck", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool" + } + ], + stateMutability: "pure", + type: "function" + }, + { + inputs: [ + + ], + name: "addressIndexInSignals", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "to", + type: "address" + }, + { + internalType: "uint256", + name: "tokenId", + type: "uint256" + } + ], + name: "approve", + outputs: [ + + ], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "owner", + type: "address" + } + ], + name: "balanceOf", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + + ], + name: "body_len", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + + ], + name: "bytesInPackedBytes", + outputs: [ + { + internalType: "uint16", + name: "", + type: "uint16" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "uint256", + name: "tokenId", + type: "uint256" + } + ], + name: "getApproved", + outputs: [ + { + internalType: "address", + name: "", + type: "address" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + + ], + name: "header_len", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "owner", + type: "address" + }, + { + internalType: "address", + name: "operator", + type: "address" + } + ], + name: "isApprovedForAll", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "uint256[2]", + name: "a", + type: "uint256[2]" + }, + { + internalType: "uint256[2][2]", + name: "b", + type: "uint256[2][2]" + }, + { + internalType: "uint256[2]", + name: "c", + type: "uint256[2]" + }, + { + internalType: "uint256[21]", + name: "signals", + type: "uint256[21]" + } + ], + name: "mint", + outputs: [ + + ], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [ + + ], + name: "msg_len", + outputs: [ + { + internalType: "uint16", + name: "", + type: "uint16" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + + ], + name: "name", + outputs: [ + { + internalType: "string", + name: "", + type: "string" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "uint256", + name: "tokenId", + type: "uint256" + } + ], + name: "ownerOf", + outputs: [ + { + internalType: "address", + name: "", + type: "address" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + + ], + name: "rsa_modulus_chunks_len", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "from", + type: "address" + }, + { + internalType: "address", + name: "to", + type: "address" + }, + { + internalType: "uint256", + name: "tokenId", + type: "uint256" + } + ], + name: "safeTransferFrom", + outputs: [ + + ], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "from", + type: "address" + }, + { + internalType: "address", + name: "to", + type: "address" + }, + { + internalType: "uint256", + name: "tokenId", + type: "uint256" + }, + { + internalType: "bytes", + name: "data", + type: "bytes" + } + ], + name: "safeTransferFrom", + outputs: [ + + ], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "operator", + type: "address" + }, + { + internalType: "bool", + name: "approved", + type: "bool" + } + ], + name: "setApprovalForAll", + outputs: [ + + ], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [ + { + internalType: "bytes4", + name: "interfaceId", + type: "bytes4" + } + ], + name: "supportsInterface", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + + ], + name: "symbol", + outputs: [ + { + internalType: "string", + name: "", + type: "string" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "uint256", + name: "index", + type: "uint256" + } + ], + name: "tokenByIndex", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "uint256", + name: "tokenId", + type: "uint256" + } + ], + name: "tokenDesc", + outputs: [ + { + internalType: "string", + name: "", + type: "string" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + name: "tokenIDToName", + outputs: [ + { + internalType: "string", + name: "", + type: "string" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "owner", + type: "address" + }, + { + internalType: "uint256", + name: "index", + type: "uint256" + } + ], + name: "tokenOfOwnerByIndex", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "uint256", + name: "tokenId", + type: "uint256" + } + ], + name: "tokenURI", + outputs: [ + { + internalType: "string", + name: "", + type: "string" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + + ], + name: "totalSupply", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "from", + type: "address" + }, + { + internalType: "address", + name: "to", + type: "address" + }, + { + internalType: "uint256", + name: "tokenId", + type: "uint256" + } + ], + name: "transferFrom", + outputs: [ + + ], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [ + { + internalType: "string", + name: "", + type: "string" + }, + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + name: "verifiedMailserverKeys", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + + ], + name: "verifier", + outputs: [ + { + internalType: "contract Verifier", + name: "", + type: "address" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "uint256[]", + name: "input", + type: "uint256[]" + }, + { + components: [ + { + components: [ + { + internalType: "uint256", + name: "X", + type: "uint256" + }, + { + internalType: "uint256", + name: "Y", + type: "uint256" + } + ], + internalType: "struct Pairing.G1Point", + name: "A", + type: "tuple" + }, + { + components: [ + { + internalType: "uint256[2]", + name: "X", + type: "uint256[2]" + }, + { + internalType: "uint256[2]", + name: "Y", + type: "uint256[2]" + } + ], + internalType: "struct Pairing.G2Point", + name: "B", + type: "tuple" + }, + { + components: [ + { + internalType: "uint256", + name: "X", + type: "uint256" + }, + { + internalType: "uint256", + name: "Y", + type: "uint256" + } + ], + internalType: "struct Pairing.G1Point", + name: "C", + type: "tuple" + } + ], + internalType: "struct Verifier.Proof", + name: "proof", + type: "tuple" + } + ], + name: "verify", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "uint256[2]", + name: "a", + type: "uint256[2]" + }, + { + internalType: "uint256[2][2]", + name: "b", + type: "uint256[2][2]" + }, + { + internalType: "uint256[2]", + name: "c", + type: "uint256[2]" + }, + { + internalType: "uint256[21]", + name: "input", + type: "uint256[21]" + } + ], + name: "verifyProof", + outputs: [ + { + internalType: "bool", + name: "r", + type: "bool" + } + ], + stateMutability: "view", + type: "function" + } +]; diff --git a/src/helpers/KYCEmailHandler.abi.ts b/src/helpers/KYCEmailHandler.abi.ts new file mode 100644 index 000000000..5c6a435fe --- /dev/null +++ b/src/helpers/KYCEmailHandler.abi.ts @@ -0,0 +1,747 @@ +export const abi = [ + { + inputs: [ + { + internalType: "contract Verifier", + name: "v", + type: "address" + }, + { + internalType: "contract MailServer", + name: "m", + type: "address" + } + ], + stateMutability: "nonpayable", + type: "constructor" + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address" + }, + { + indexed: true, + internalType: "address", + name: "approved", + type: "address" + }, + { + indexed: true, + internalType: "uint256", + name: "tokenId", + type: "uint256" + } + ], + name: "Approval", + type: "event" + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address" + }, + { + indexed: true, + internalType: "address", + name: "operator", + type: "address" + }, + { + indexed: false, + internalType: "bool", + name: "approved", + type: "bool" + } + ], + name: "ApprovalForAll", + type: "event" + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "from", + type: "address" + }, + { + indexed: true, + internalType: "address", + name: "to", + type: "address" + }, + { + indexed: true, + internalType: "uint256", + name: "tokenId", + type: "uint256" + } + ], + name: "Transfer", + type: "event" + }, + { + inputs: [ + { + internalType: "uint256[]", + name: "headerSignals", + type: "uint256[]" + } + ], + name: "_domainCheck", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool" + } + ], + stateMutability: "pure", + type: "function" + }, + { + inputs: [ + + ], + name: "addressIndexInSignals", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "to", + type: "address" + }, + { + internalType: "uint256", + name: "tokenId", + type: "uint256" + } + ], + name: "approve", + outputs: [ + + ], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "owner", + type: "address" + } + ], + name: "balanceOf", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + + ], + name: "body_len", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + + ], + name: "bytesInPackedBytes", + outputs: [ + { + internalType: "uint16", + name: "", + type: "uint16" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "uint256", + name: "tokenId", + type: "uint256" + } + ], + name: "getApproved", + outputs: [ + { + internalType: "address", + name: "", + type: "address" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + + ], + name: "header_len", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "owner", + type: "address" + }, + { + internalType: "address", + name: "operator", + type: "address" + } + ], + name: "isApprovedForAll", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "uint256[2]", + name: "a", + type: "uint256[2]" + }, + { + internalType: "uint256[2][2]", + name: "b", + type: "uint256[2][2]" + }, + { + internalType: "uint256[2]", + name: "c", + type: "uint256[2]" + }, + { + internalType: "uint256[40]", + name: "signals", + type: "uint256[40]" + } + ], + name: "mint", + outputs: [ + + ], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [ + + ], + name: "msg_len", + outputs: [ + { + internalType: "uint16", + name: "", + type: "uint16" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + + ], + name: "name", + outputs: [ + { + internalType: "string", + name: "", + type: "string" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32" + } + ], + name: "nullifiers", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "uint256", + name: "tokenId", + type: "uint256" + } + ], + name: "ownerOf", + outputs: [ + { + internalType: "address", + name: "", + type: "address" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + + ], + name: "rsa_modulus_chunks_len", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "from", + type: "address" + }, + { + internalType: "address", + name: "to", + type: "address" + }, + { + internalType: "uint256", + name: "tokenId", + type: "uint256" + } + ], + name: "safeTransferFrom", + outputs: [ + + ], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "from", + type: "address" + }, + { + internalType: "address", + name: "to", + type: "address" + }, + { + internalType: "uint256", + name: "tokenId", + type: "uint256" + }, + { + internalType: "bytes", + name: "data", + type: "bytes" + } + ], + name: "safeTransferFrom", + outputs: [ + + ], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "operator", + type: "address" + }, + { + internalType: "bool", + name: "approved", + type: "bool" + } + ], + name: "setApprovalForAll", + outputs: [ + + ], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [ + { + internalType: "bytes4", + name: "interfaceId", + type: "bytes4" + } + ], + name: "supportsInterface", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + + ], + name: "symbol", + outputs: [ + { + internalType: "string", + name: "", + type: "string" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "uint256", + name: "index", + type: "uint256" + } + ], + name: "tokenByIndex", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "uint256", + name: "tokenId", + type: "uint256" + } + ], + name: "tokenDesc", + outputs: [ + { + internalType: "string", + name: "", + type: "string" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "owner", + type: "address" + }, + { + internalType: "uint256", + name: "index", + type: "uint256" + } + ], + name: "tokenOfOwnerByIndex", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "uint256", + name: "tokenId", + type: "uint256" + } + ], + name: "tokenURI", + outputs: [ + { + internalType: "string", + name: "", + type: "string" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + + ], + name: "totalSupply", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "address", + name: "from", + type: "address" + }, + { + internalType: "address", + name: "to", + type: "address" + }, + { + internalType: "uint256", + name: "tokenId", + type: "uint256" + } + ], + name: "transferFrom", + outputs: [ + + ], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [ + { + internalType: "string", + name: "", + type: "string" + }, + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + name: "verifiedMailserverKeys", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + + ], + name: "verifier", + outputs: [ + { + internalType: "contract Verifier", + name: "", + type: "address" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "uint256[]", + name: "input", + type: "uint256[]" + }, + { + components: [ + { + components: [ + { + internalType: "uint256", + name: "X", + type: "uint256" + }, + { + internalType: "uint256", + name: "Y", + type: "uint256" + } + ], + internalType: "struct Pairing.G1Point", + name: "A", + type: "tuple" + }, + { + components: [ + { + internalType: "uint256[2]", + name: "X", + type: "uint256[2]" + }, + { + internalType: "uint256[2]", + name: "Y", + type: "uint256[2]" + } + ], + internalType: "struct Pairing.G2Point", + name: "B", + type: "tuple" + }, + { + components: [ + { + internalType: "uint256", + name: "X", + type: "uint256" + }, + { + internalType: "uint256", + name: "Y", + type: "uint256" + } + ], + internalType: "struct Pairing.G1Point", + name: "C", + type: "tuple" + } + ], + internalType: "struct Verifier.Proof", + name: "proof", + type: "tuple" + } + ], + name: "verify", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256" + } + ], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { + internalType: "uint256[2]", + name: "a", + type: "uint256[2]" + }, + { + internalType: "uint256[2][2]", + name: "b", + type: "uint256[2][2]" + }, + { + internalType: "uint256[2]", + name: "c", + type: "uint256[2]" + }, + { + internalType: "uint256[40]", + name: "input", + type: "uint256[40]" + } + ], + name: "verifyProof", + outputs: [ + { + internalType: "bool", + name: "r", + type: "bool" + } + ], + stateMutability: "view", + type: "function" + } +]; diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx index fb8667112..f19230164 100644 --- a/src/pages/MainPage.tsx +++ b/src/pages/MainPage.tsx @@ -22,7 +22,8 @@ import { NumberedStep } from "../components/NumberedStep"; import { TopBanner } from "../components/TopBanner"; import { useAccount, useContractWrite, usePrepareContractWrite } from "wagmi"; import { ProgressBar } from "../components/ProgressBar"; -import { abi } from "../helpers/twitterEmailHandler.abi"; +// import { abi } from "../helpers/twitterEmailHandler.abi"; +import { abi } from "../helpers/KYCEmailHandler.abi"; import { isSetIterator } from "util/types"; var Buffer = require("buffer/").Buffer; // note: the trailing slash is important! @@ -127,7 +128,8 @@ export const MainPage: React.FC<{}> = (props) => { }; const { config } = usePrepareContractWrite({ - addressOrName: "0x72D9d080853f1AfA52662D71A24D92498Ef84799", // TODO: get address + // addressOrName: "0x72D9d080853f1AfA52662D71A24D92498Ef84799", // TODO: get address + addressOrName: "0x8d7aab5De281c542A4c545ECB8363dff7De139aB", contractInterface: abi, // TODO: get abi functionName: "mint", args: [...reformatProofForChain(proof), publicSignals ? JSON.parse(publicSignals) : null], From ca48af0f740571acbc705a8ceff5736c60bf5080 Mon Sep 17 00:00:00 2001 From: Nathan Xiong Date: Tue, 16 May 2023 17:56:01 -0400 Subject: [PATCH 21/25] edit frontend and README --- README.md | 4 + public/index.html | 2 +- src/contracts/testEmailHandler.abi.ts | 736 -------------------------- src/pages/MainPage.tsx | 2 +- 4 files changed, 6 insertions(+), 738 deletions(-) delete mode 100644 src/contracts/testEmailHandler.abi.ts diff --git a/README.md b/README.md index a23942ff4..cb3921044 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +# Anonymous KYC with ZK Email + +Generate an anonymous proof of personhood badge at [anonkyc.com](anonkyc.com). + # 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 gotchas such as lack of nullifiers, no signed bcc’s, non-nested reply signatures, upgradability of DNS, and hash sizings. None of these affect our current Twitter MVP usecase, but are not generally guaranteed. If you have a possible usecase, we are happy to help brainstorm if your trust assumptions are in fact correct!** diff --git a/public/index.html b/public/index.html index c9be12159..86d5337cd 100644 --- a/public/index.html +++ b/public/index.html @@ -24,7 +24,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - ZK Email + ZK KYC diff --git a/src/contracts/testEmailHandler.abi.ts b/src/contracts/testEmailHandler.abi.ts deleted file mode 100644 index de5ff7f85..000000000 --- a/src/contracts/testEmailHandler.abi.ts +++ /dev/null @@ -1,736 +0,0 @@ -export const abi = [ - { - inputs: [], - stateMutability: "nonpayable", - type: "constructor" - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "address", - name: "owner", - type: "address" - }, - { - indexed: true, - internalType: "address", - name: "approved", - type: "address" - }, - { - indexed: true, - internalType: "uint256", - name: "tokenId", - type: "uint256" - } - ], - name: "Approval", - type: "event" - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "address", - name: "owner", - type: "address" - }, - { - indexed: true, - internalType: "address", - name: "operator", - type: "address" - }, - { - indexed: false, - internalType: "bool", - name: "approved", - type: "bool" - } - ], - name: "ApprovalForAll", - type: "event" - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "address", - name: "from", - type: "address" - }, - { - indexed: true, - internalType: "address", - name: "to", - type: "address" - }, - { - indexed: true, - internalType: "uint256", - name: "tokenId", - type: "uint256" - } - ], - name: "Transfer", - type: "event" - }, - { - inputs: [ - { - internalType: "uint256[]", - name: "headerSignals", - type: "uint256[]" - } - ], - name: "_domainCheck", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool" - } - ], - stateMutability: "pure", - type: "function" - }, - { - inputs: [ - - ], - name: "addressIndexInSignals", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256" - } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - { - internalType: "address", - name: "to", - type: "address" - }, - { - internalType: "uint256", - name: "tokenId", - type: "uint256" - } - ], - name: "approve", - outputs: [ - - ], - stateMutability: "nonpayable", - type: "function" - }, - { - inputs: [ - { - internalType: "address", - name: "owner", - type: "address" - } - ], - name: "balanceOf", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256" - } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - - ], - name: "body_len", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256" - } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - - ], - name: "bytesInPackedBytes", - outputs: [ - { - internalType: "uint16", - name: "", - type: "uint16" - } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - { - internalType: "uint256", - name: "tokenId", - type: "uint256" - } - ], - name: "getApproved", - outputs: [ - { - internalType: "address", - name: "", - type: "address" - } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - - ], - name: "header_len", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256" - } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - { - internalType: "address", - name: "owner", - type: "address" - }, - { - internalType: "address", - name: "operator", - type: "address" - } - ], - name: "isApprovedForAll", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool" - } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - { - internalType: "uint256[2]", - name: "a", - type: "uint256[2]" - }, - { - internalType: "uint256[2][2]", - name: "b", - type: "uint256[2][2]" - }, - { - internalType: "uint256[2]", - name: "c", - type: "uint256[2]" - }, - { - internalType: "uint256[21]", - name: "signals", - type: "uint256[21]" - } - ], - name: "mint", - outputs: [ - - ], - stateMutability: "nonpayable", - type: "function" - }, - { - inputs: [ - - ], - name: "msg_len", - outputs: [ - { - internalType: "uint16", - name: "", - type: "uint16" - } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - - ], - name: "name", - outputs: [ - { - internalType: "string", - name: "", - type: "string" - } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - { - internalType: "uint256", - name: "tokenId", - type: "uint256" - } - ], - name: "ownerOf", - outputs: [ - { - internalType: "address", - name: "", - type: "address" - } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - - ], - name: "rsa_modulus_chunks_len", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256" - } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - { - internalType: "address", - name: "from", - type: "address" - }, - { - internalType: "address", - name: "to", - type: "address" - }, - { - internalType: "uint256", - name: "tokenId", - type: "uint256" - } - ], - name: "safeTransferFrom", - outputs: [ - - ], - stateMutability: "nonpayable", - type: "function" - }, - { - inputs: [ - { - internalType: "address", - name: "from", - type: "address" - }, - { - internalType: "address", - name: "to", - type: "address" - }, - { - internalType: "uint256", - name: "tokenId", - type: "uint256" - }, - { - internalType: "bytes", - name: "data", - type: "bytes" - } - ], - name: "safeTransferFrom", - outputs: [ - - ], - stateMutability: "nonpayable", - type: "function" - }, - { - inputs: [ - { - internalType: "address", - name: "operator", - type: "address" - }, - { - internalType: "bool", - name: "approved", - type: "bool" - } - ], - name: "setApprovalForAll", - outputs: [ - - ], - stateMutability: "nonpayable", - type: "function" - }, - { - inputs: [ - { - internalType: "bytes4", - name: "interfaceId", - type: "bytes4" - } - ], - name: "supportsInterface", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool" - } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - - ], - name: "symbol", - outputs: [ - { - internalType: "string", - name: "", - type: "string" - } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - { - internalType: "uint256", - name: "index", - type: "uint256" - } - ], - name: "tokenByIndex", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256" - } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - { - internalType: "uint256", - name: "tokenId", - type: "uint256" - } - ], - name: "tokenDesc", - outputs: [ - { - internalType: "string", - name: "", - type: "string" - } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - { - internalType: "uint256", - name: "", - type: "uint256" - } - ], - name: "tokenIDToName", - outputs: [ - { - internalType: "string", - name: "", - type: "string" - } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - { - internalType: "address", - name: "owner", - type: "address" - }, - { - internalType: "uint256", - name: "index", - type: "uint256" - } - ], - name: "tokenOfOwnerByIndex", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256" - } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - { - internalType: "uint256", - name: "tokenId", - type: "uint256" - } - ], - name: "tokenURI", - outputs: [ - { - internalType: "string", - name: "", - type: "string" - } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - - ], - name: "totalSupply", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256" - } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - { - internalType: "address", - name: "from", - type: "address" - }, - { - internalType: "address", - name: "to", - type: "address" - }, - { - internalType: "uint256", - name: "tokenId", - type: "uint256" - } - ], - name: "transferFrom", - outputs: [ - - ], - stateMutability: "nonpayable", - type: "function" - }, - { - inputs: [ - { - internalType: "string", - name: "", - type: "string" - }, - { - internalType: "uint256", - name: "", - type: "uint256" - } - ], - name: "verifiedMailserverKeys", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256" - } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - - ], - name: "verifier", - outputs: [ - { - internalType: "contract Verifier", - name: "", - type: "address" - } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - { - internalType: "uint256[]", - name: "input", - type: "uint256[]" - }, - { - components: [ - { - components: [ - { - internalType: "uint256", - name: "X", - type: "uint256" - }, - { - internalType: "uint256", - name: "Y", - type: "uint256" - } - ], - internalType: "struct Pairing.G1Point", - name: "A", - type: "tuple" - }, - { - components: [ - { - internalType: "uint256[2]", - name: "X", - type: "uint256[2]" - }, - { - internalType: "uint256[2]", - name: "Y", - type: "uint256[2]" - } - ], - internalType: "struct Pairing.G2Point", - name: "B", - type: "tuple" - }, - { - components: [ - { - internalType: "uint256", - name: "X", - type: "uint256" - }, - { - internalType: "uint256", - name: "Y", - type: "uint256" - } - ], - internalType: "struct Pairing.G1Point", - name: "C", - type: "tuple" - } - ], - internalType: "struct Verifier.Proof", - name: "proof", - type: "tuple" - } - ], - name: "verify", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256" - } - ], - stateMutability: "view", - type: "function" - }, - { - inputs: [ - { - internalType: "uint256[2]", - name: "a", - type: "uint256[2]" - }, - { - internalType: "uint256[2][2]", - name: "b", - type: "uint256[2][2]" - }, - { - internalType: "uint256[2]", - name: "c", - type: "uint256[2]" - }, - { - internalType: "uint256[21]", - name: "input", - type: "uint256[21]" - } - ], - name: "verifyProof", - outputs: [ - { - internalType: "bool", - name: "r", - type: "bool" - } - ], - stateMutability: "view", - type: "function" - } -]; diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx index f19230164..d7a8a8eff 100644 --- a/src/pages/MainPage.tsx +++ b/src/pages/MainPage.tsx @@ -210,7 +210,7 @@ export const MainPage: React.FC<{}> = (props) => { {showBrowserWarning && }
-
ZK KYC with ZK Email
+
Anonymous KYC with ZK Email
Date: Wed, 17 May 2023 02:49:10 -0400 Subject: [PATCH 22/25] new contract --- src/contracts/src/MailServer.sol | 5 +++++ src/pages/MainPage.tsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/contracts/src/MailServer.sol b/src/contracts/src/MailServer.sol index 6bed6b815..36544281e 100644 --- a/src/contracts/src/MailServer.sol +++ b/src/contracts/src/MailServer.sol @@ -158,4 +158,9 @@ contract MailServer { return verifiedMailserverKeys[domain][index] == val; } + + function editMailserverKey(string memory domain, uint256 index, uint256 val) public { + require(msg.sender == 0x6171aeBcC9e9B9E1D90EC9C2E124982932297345, "Only 0x6171aeBcC9e9B9E1D90EC9C2E124982932297345 can add/change mailserver keys for now. We will change this to be a DNSSEC oracle + multisig soon!"); + verifiedMailserverKeys[domain][index] = val; + } } diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx index ef3aa7d29..b50d60441 100644 --- a/src/pages/MainPage.tsx +++ b/src/pages/MainPage.tsx @@ -129,7 +129,7 @@ export const MainPage: React.FC<{}> = (props) => { const { config } = usePrepareContractWrite({ // addressOrName: "0x72D9d080853f1AfA52662D71A24D92498Ef84799", // TODO: get address - addressOrName: "0x8d7aab5De281c542A4c545ECB8363dff7De139aB", + addressOrName: "0xEB4DCC17a76886C5D46dF8070fc61D3e1c31A768", contractInterface: abi, // TODO: get abi functionName: "mint", args: [...reformatProofForChain(proof), publicSignals ? JSON.parse(publicSignals) : null], From 984003138b9cc4d7ac683ab3b4ed569d20c5b578 Mon Sep 17 00:00:00 2001 From: novus677 <51405949+novus677@users.noreply.github.com> Date: Thu, 8 Jun 2023 12:03:19 -0400 Subject: [PATCH 23/25] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cb3921044..48153d7e5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Anonymous KYC with ZK Email -Generate an anonymous proof of personhood badge at [anonkyc.com](anonkyc.com). +Generate an anonymous proof of personhood badge at [anonkyc.com](https://anonkyc.com). # ZK Email Verify From 120ddac776e4b3d4d45b2cedbf3d1115a56946a6 Mon Sep 17 00:00:00 2001 From: novus677 <51405949+novus677@users.noreply.github.com> Date: Tue, 18 Jul 2023 13:47:31 -0400 Subject: [PATCH 24/25] Update README.md --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index 48153d7e5..750487413 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,30 @@ Generate an anonymous proof of personhood badge at [anonkyc.com](https://anonkyc.com). +## What is ZK KYC? + +A ZK KYC is KYC (Know Your Customer) that hides particular details of the user's identity such as the user's name, date of birth, citizenship, etc. Our ZK KYC proof generator implements the most basic level of ZK KYC: a proof of personhood that reveals no other information about the user. In particular, a user can have multiple addresses but can only ever have one proof of personhood badge. Other levels of ZK KYC could prove that the user is above the age of 21, or that the user is a U.S. citizen, etc. + +## Motivation + +The use of KYCs to prevent fraud and to comply with regulations compromises the goals of decentralized technologies by placing private information in the hands of centralized organizations. A ZK KYC provides a possible solution to both sides of the debate: the KYC component can give organizations trust in their customers and also provide Sybil resistance, while the ZK component keeps customers' private information completely hidden. + +## How ZK KYC works + +See the bottom of this README, or [this blog post](https://blog.aayushg.com/posts/zkemail/) for an explainer on how ZK Email works. The main idea is that we use the ZK Email circuit to verify that a KYC confirmation email from e.g., Coinbase, is real. We also use ZK-regex circuits to match the subject of the email with that of a KYC confirmation email. + +To prevent someone from just minting infinite proof of personhood badges, we also attach a nullifier to every set of inputs. In our case, we concatenate the body hashes from the Airbnb and Coinbase confirmation emails and then hash that. + +This is actually why we need two KYCs: one from Airbnb and one from Coinbase. At first glance, such a ZK KYC implementation could work with just Airbnb and an Airbnb KYC confirmation email, where your public nullifier is just the hash of the signature or the body hash. However, under such a setup, Airbnb would still be able to de-anonymize you from your public nullifier. Under our setup, Airbnb and Coinbase would have to collude in order to de-anonymize you. If we wanted to, we could add even more KYC requirements to make the system even more secure. + +## Known issues + +Our current setup has several limitations on who can generate a ZK KYC: +- Old public keys. Email domains typically rotate their public keys every six-or-so months. As a result, older KYC confirmation emails can't get verified. We are storing public keys so that we can check against them in the future, but unfortunately we don't have access to most of the older Airbnb/Coinbase public keys +- New email format. If Airbnb suddenly decides to change the subject of their KYC confirmation emails, we will need to build a new zk regex circuit to match that new subject. Such formatting changes have happened in the past with Coinbase emails. + +# 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 gotchas such as lack of nullifiers, no signed bcc’s, non-nested reply signatures, upgradability of DNS, and hash sizings. None of these affect our current Twitter MVP usecase, but are not generally guaranteed. If you have a possible usecase, we are happy to help brainstorm if your trust assumptions are in fact correct!** From 99501ac9680e6d3b661ceb185f9a10e55812e7cf Mon Sep 17 00:00:00 2001 From: novus677 <51405949+novus677@users.noreply.github.com> Date: Thu, 28 Dec 2023 14:17:26 +0900 Subject: [PATCH 25/25] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 750487413..31104f49c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Anonymous KYC with ZK Email -Generate an anonymous proof of personhood badge at [anonkyc.com](https://anonkyc.com). +Generate an anonymous proof of personhood badge at [anonkyc.com](https://anonkyc.com). Note: website likely doesn't work right now due to DKIM public keys changing. ## What is ZK KYC?