Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Bugfixes #43

Merged
merged 11 commits into from
Mar 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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

Expand Down
14 changes: 7 additions & 7 deletions circuits/base64.circom
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,35 @@ 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);

// ['a', 'z']
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);

// ['0', '9']
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);

Expand Down Expand Up @@ -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();
Expand Down
30 changes: 19 additions & 11 deletions regex_to_circom/gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -10,19 +16,32 @@
# 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
# assert ord(k) < 128
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
Expand All @@ -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]:
Expand All @@ -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);")
Expand Down
2 changes: 2 additions & 0 deletions src/contracts/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALCHEMY_GOERLI_KEY=
ETHERSCAN_API_KEY=
24 changes: 21 additions & 3 deletions src/contracts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -10,15 +12,31 @@ 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

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
```
1 change: 0 additions & 1 deletion src/contracts/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
11 changes: 6 additions & 5 deletions src/contracts/src/NFTSVG.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import "./hexStrings.sol";
library NFTSVG {
using Strings for uint256;
using Strings for address;
using HexStrings for *;

struct SVGParams {
string username;
Expand Down Expand Up @@ -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;
}
Expand All @@ -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));
}
}
14 changes: 13 additions & 1 deletion src/contracts/src/test/TestTwitter.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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);
}
}
4 changes: 4 additions & 0 deletions src/contracts/src/twitterEmailHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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,
Expand Down
15 changes: 9 additions & 6 deletions src/helpers/dkim/tools.js
Original file line number Diff line number Diff line change
@@ -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;
}
}
Expand Down Expand Up @@ -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";
Expand Down
12 changes: 8 additions & 4 deletions src/pages/MainPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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:
</span>
<NumberedStep step={1}>
Send yourself a <a href="https://twitter.com/i/flow/password_reset">password reset email</a> from Twitter in incognito.
Send yourself a{" "}
<a href="https://twitter.com/i/flow/password_reset" target="_blank" rel="noreferrer">
password reset email
</a>{" "}
from Twitter.
</NumberedStep>
<NumberedStep step={2}>In your inbox, find the email from Twitter and download headers (three dots, then download message).</NumberedStep>
<NumberedStep step={2}>In your inbox, find the email from Twitter and click the three dot menu, then "Show original" then "Copy to clipboard".</NumberedStep>
<NumberedStep step={3}>
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.
</NumberedStep>
<NumberedStep step={4}>
Paste in your sending Ethereum address. This ensures that no one else can "steal" your proof for another account (frontrunning protection!).
Expand Down
2 changes: 1 addition & 1 deletion src/scripts/generate_input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down