diff --git a/README.md b/README.md index 8f4b7768c..a1344e42d 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 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. 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. ### Scrubbing Sensitive Files @@ -339,7 +339,21 @@ 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. +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. ### No available storage method found. @@ -379,9 +393,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. -### Why did you choose GPL over MIT licensing? +### What is the licensing on this technology? -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. +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. ## To-Do diff --git a/circuits/base64.circom b/circuits/base64.circom index 61e9603b7..b7b504c04 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/regex_to_circom/gen.py b/regex_to_circom/gen.py index 1e37af666..93f1b74b3 100644 --- a/regex_to_circom/gen.py +++ b/regex_to_circom/gen.py @@ -2,6 +2,12 @@ import json import string +# Clear file +OUTPUT_HALO2 = True +if(OUTPUT_HALO2): + with open('halo2_regex_lookup.txt', 'w') as f: + print("", file=f) + graph_json = json.loads(subprocess.check_output(['npx', 'tsx', 'lexical.js'])) N = len(graph_json) @@ -10,6 +16,7 @@ # 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 @@ -17,12 +24,24 @@ 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 (OUTPUT_HALO2): + for val in json.loads(k): + with open('halo2_regex_lookup.txt', 'a') as f: + print(i, v, ord(val), file=f) + if graph_json[i]['type'] == 'accept': accept_nodes.add(i) accept_nodes = list(accept_nodes) assert len(accept_nodes) == 1 +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 @@ -33,10 +52,6 @@ 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]: @@ -48,13 +63,6 @@ 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/src/contracts/.env.example b/src/contracts/.env.example new file mode 100644 index 000000000..5fc4bcec7 --- /dev/null +++ b/src/contracts/.env.example @@ -0,0 +1,2 @@ +ALCHEMY_GOERLI_KEY= +ETHERSCAN_API_KEY= diff --git a/src/contracts/README.md b/src/contracts/README.md index 2622778d9..54fb5defc 100644 --- a/src/contracts/README.md +++ b/src/contracts/README.md @@ -2,6 +2,8 @@ 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, @@ -10,7 +12,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 +forge test --via-ir ``` ## Deployment @@ -18,7 +20,23 @@ forge test To deploy contract to forked mainnet, do: ``` -anvil --fork-url https://eth-mainnet.alchemyapi.io/v2/***REMOVED*** --port 8547 # Run in tmux +# 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 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 $GORLII_ETHERSCAN_API_KEY ``` diff --git a/src/contracts/foundry.toml b/src/contracts/foundry.toml index 6e757cc38..1a94e3fa4 100644 --- a/src/contracts/foundry.toml +++ b/src/contracts/foundry.toml @@ -3,4 +3,3 @@ 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 620841c24..0783f610d 100644 --- a/src/contracts/src/NFTSVG.sol +++ b/src/contracts/src/NFTSVG.sol @@ -11,6 +11,7 @@ import "./hexStrings.sol"; library NFTSVG { using Strings for uint256; using Strings for address; + using HexStrings for *; struct SVGParams { string username; @@ -158,10 +159,6 @@ 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; } @@ -171,6 +168,10 @@ library NFTSVG { } function scale(uint256 n, uint256 inMn, uint256 inMx, uint256 outMn, uint256 outMx) public pure returns (string memory) { - return (((n - inMn) * (outMx - outMn)) / (inMx - inMn) + (outMn)).toString(); + 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)); } } diff --git a/src/contracts/src/test/TestTwitter.t.sol b/src/contracts/src/test/TestTwitter.t.sol index 235d1d814..7629ca03c 100644 --- a/src/contracts/src/test/TestTwitter.t.sol +++ b/src/contracts/src/test/TestTwitter.t.sol @@ -47,6 +47,14 @@ 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) @@ -148,8 +156,12 @@ 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 2c6a2aa4a..6cab658a8 100644 --- a/src/contracts/src/twitterEmailHandler.sol +++ b/src/contracts/src/twitterEmailHandler.sol @@ -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 "./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; @@ -59,6 +62,7 @@ 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/dkim/tools.js b/src/helpers/dkim/tools.js index 2f102161e..a24457051 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,13 +272,16 @@ const getPublicKey = async (type, name, minBitLength, resolver) => { [] .concat(list[0] || []) .join("") - .replace(/\s+/g, ""); + .replaceAll(/\s+/g, "") + .replaceAll('"', ""); 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 06ec59745..4d3ec2da3 100644 --- a/src/pages/MainPage.tsx +++ b/src/pages/MainPage.tsx @@ -191,12 +191,16 @@ 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 incognito. + Send yourself a{" "} + + password reset email + {" "} + from Twitter. - In your inbox, find the email from Twitter and download headers (three dots, then download message). + In your inbox, find the email from Twitter and click the three dot menu, then "Show original" then "Copy to clipboard". - 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. + 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. 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 c363a9e25..228089975 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 = "viv_twitter.eml"; export interface ICircuitInputs { modulus?: string[]; signature?: string[];