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

Refactor circuit #185

Merged
merged 47 commits into from
Apr 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
447bb73
circuit: rename inputs on EmailVerifier
saleel Mar 28, 2024
6501d72
Merge remote-tracking branch 'origin/Docs-update' into feat/circuit-r…
saleel Mar 28, 2024
7747fc6
circuit: revert test email
saleel Mar 28, 2024
38ee1b6
circuit: update emailverifier doc
saleel Mar 28, 2024
a2316f0
helpers: update input generation to match new names
saleel Mar 28, 2024
561fa32
circuit: fix in emailverifier tests
saleel Mar 28, 2024
0e46fe1
circuits: move test circuits
saleel Mar 28, 2024
82ef70d
circuit: rename compiled-test-circuits
saleel Mar 28, 2024
018b454
circuit: merge email verifier tests to one file
saleel Mar 28, 2024
8102952
circuit: move test emails
saleel Mar 28, 2024
24d8158
circuit: remove circuit MakeAnonEmailSalt
saleel Mar 28, 2024
a38542a
circuit: move base64 to lib
saleel Mar 28, 2024
8e2a119
circuit: refactor rsa circuit
saleel Mar 28, 2024
61ded0b
circuit: refactor SHA circuit
saleel Mar 28, 2024
cf5cf72
circuit: fix test errors
saleel Mar 29, 2024
c1c18b8
circuit: add array utils
saleel Mar 30, 2024
85f95a6
circuit: rename bodyHash variable
saleel Mar 30, 2024
070bc4e
circuit: add bytes utils
saleel Mar 30, 2024
1a338ff
circuit: add ByteSubArrayToInts
saleel Mar 30, 2024
b14e346
circuit: update VarShiftLeft
saleel Mar 30, 2024
98ae7e5
circuit: refactor extract utils
saleel Mar 30, 2024
f60f6bf
circuit: add ByteSubArrayToInts (shift and pack)
saleel Mar 30, 2024
9f2d121
circuit: update EmailVerifier to use ExtractRegexReveal
saleel Mar 30, 2024
9fa6537
circuit: move AssertZeros to array utils
saleel Mar 30, 2024
b40c748
circuit: remove unused templates
saleel Mar 30, 2024
401694e
circuit: move DigitBytesToInt to bytes
saleel Mar 30, 2024
6df9f84
circuit: add docs for extract
saleel Mar 30, 2024
2346ed5
circuit: fix imports in sha
saleel Mar 30, 2024
36c4776
circuit: remove maxBlocksBits from SHA
saleel Mar 30, 2024
6d6a541
circuit: fix build errors
saleel Mar 30, 2024
848b6ff
circuit: fix test witness for pubkeyHash
saleel Mar 30, 2024
b9ce992
circuit: add util for PoseidonLarge
saleel Mar 30, 2024
0d29c5b
circuit: add email nullifier helper
saleel Mar 30, 2024
7ebfb17
Merge branch 'main' into feat/circuit-refactor
saleel Mar 30, 2024
a6898e8
circuit: delete redundant circuit
saleel Mar 30, 2024
e5c331c
circuit: update circom version to 2.1.6
saleel Mar 30, 2024
ea3da5d
circuit: add test eml to repo
saleel Mar 30, 2024
fd25b60
circuit: remove Dockerfile
saleel Apr 1, 2024
2879928
circuit: add SubArray selector
saleel Apr 1, 2024
a5b5340
circuit: use SubArraySelector in ByteSubArrayToInts
saleel Apr 1, 2024
e077475
circuit: rename to regex.cirocm
saleel Apr 1, 2024
2a72497
circuit: update comments
saleel Apr 1, 2024
84cad37
circuit: update deps, minor fix/cleanup
saleel Apr 1, 2024
7ad0460
Merge branch 'v4' into feat/circuit-refactor
saleel Apr 1, 2024
57a1564
circuit: remove unnecessary export from test
saleel Apr 1, 2024
92dde51
circuit: rename BytesToInts
saleel Apr 2, 2024
49109ea
circuit: rename array util templates
saleel Apr 2, 2024
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
4 changes: 2 additions & 2 deletions .github/workflows/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ jobs:
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable

- name: Download Circom Binary v2.1.5
- name: Download Circom Binary v2.1.8
run: |
wget -qO /home/runner/work/circom https://github.com/iden3/circom/releases/download/v2.1.5/circom-linux-amd64
wget -qO /home/runner/work/circom https://github.com/iden3/circom/releases/download/v2.1.8/circom-linux-amd64
chmod +x /home/runner/work/circom
sudo mv /home/runner/work/circom /bin/circom

Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ generate_input_log.txt
*.env
.vscode

packages/circuits/tests/compiled-test-circuit/*
packages/circuits/tests/compiled-test-circuits/*


.vite
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ Everything we write is MIT licensed. Note that circom and circomlib is GPL. Broa
- 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
- warning[CA02]: In template "RSAVerifier65537(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].
Expand Down
42 changes: 0 additions & 42 deletions packages/circuits/Dockerfile

This file was deleted.

2 changes: 1 addition & 1 deletion packages/circuits/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ This template implements the SHA (Secure Hash Algorithm) family of cryptographic

This template provides functionality for performing arithmetic operations on big integers, such as addition and subtraction modulo 2^n.

### bigint_func.circom
### bigint-func.circom

This template offers utility functions for handling big integers within arithmetic circuits, performing various mathematical operations on large numbers represented across multiple registers.

Expand Down
205 changes: 97 additions & 108 deletions packages/circuits/email-verifier.circom
Original file line number Diff line number Diff line change
@@ -1,133 +1,122 @@
pragma circom 2.1.5;
pragma circom 2.1.6;

include "circomlib/circuits/bitify.circom";
include "circomlib/circuits/poseidon.circom";
include "./helpers/sha.circom";
include "./helpers/rsa.circom";
include "./helpers/base64.circom";
include "./helpers/extract.circom";
include "./helpers/utils.circom";
// include "./regexes/body_hash_regex.circom";
include "@zk-email/zk-regex-circom/circuits/common/body_hash_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
// ignore_body_hash_check is a flag that allows us to skip the body hash check, for projects that dont care about the body contents
// TODO: split into header and body
template EmailVerifier(max_header_bytes, max_body_bytes, n, k, ignore_body_hash_check) {
assert(max_header_bytes % 64 == 0);
assert(max_body_bytes % 64 == 0);
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 pubkey[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


// Base 64 body hash variables
var LEN_SHA_B64 = 44; // ceil(32 / 3) * 4, due to base64 encoding.

// Assert padding is all zeroes
AssertZeroes(max_header_bytes)(in_padded, in_len_padded_bytes + 1);
include "./lib/base64.circom";
include "./lib/rsa.circom";
include "./lib/sha.circom";
include "./utils/array.circom";
include "./utils/regex.circom";
include "./utils/hash.circom";


/// @title EmailVerifier
/// @notice Circuit to verify email signature as per DKIM standard.
/// @notice Verifies the signature is valid for the given header and pubkey, and the hash of the body matches the hash in the header.
/// @notice This cicuit only verifies signature as per `rsa-sha256` algorithm.
/// @param maxHeaderLength Maximum length for the email header.
/// @param maxBodyLength Maximum length for the email body.
/// @param n Number of bits per chunk the RSA key is split into. Recommended to be 121.
/// @param k Number of chunks the RSA key is split into. Recommended to be 17.
/// @param ignoreBodyHashCheck Set 1 to skip body hash check in case data to prove/extract is only in the headers.
/// @input emailHeader Email headers that are signed (ones in `DKIM-Signature` header) as ASCII int[], padded as per SHA-256 block size.
/// @input emailHeaderLength Length of the email header including the SHA-256 padding.
/// @input pubkey RSA public key split into k chunks of n bits each.
/// @input signature RSA signature split into k chunks of n bits each.
/// @input emailBody Email body after the precomputed SHA as ASCII int[], padded as per SHA-256 block size.
/// @input emailBodyLength Length of the email body including the SHA-256 padding.
/// @input bodyHashIndex Index of the body hash `bh` in the emailHeader.
/// @input precomputedSHA Precomputed SHA-256 hash of the email body till the bodyHashIndex.
/// @output pubkeyHash Poseidon hash of the pubkey - Poseidon(n/2)(n/2 chunks of pubkey with k*2 bits per chunk).
template EmailVerifier(maxHeaderLength, maxBodyLength, n, k, ignoreBodyHashCheck) {
assert(maxHeaderLength % 64 == 0);
assert(maxBodyLength % 64 == 0);
assert(n * k > 2048); // to support 2048 bit RSA
assert(n < (255 \ 2)); // for multiplication to fit in the field (255 bits)


signal input emailHeader[maxHeaderLength];
signal input emailHeaderLength;
signal input pubkey[k];
signal input signature[k];

signal output pubkeyHash;


// Assert emailHeader data after emailHeaderLength are zeros
AssertZeroPadding(maxHeaderLength)(emailHeader, emailHeaderLength + 1);

// 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.
signal output sha[256] <== Sha256Bytes(max_header_bytes)(in_padded, in_len_padded_bytes);
signal output pubkey_hash;

var msg_len = (256 + n) \ n;

component base_msg[msg_len];
for (var i = 0; i < msg_len; i++) {
base_msg[i] = Bits2Num(n);

// Calculate SHA256 hash of the `emailHeader` - 506,670 constraints
signal output sha[256] <== Sha256Bytes(maxHeaderLength)(emailHeader, emailHeaderLength);


// Pack SHA output bytes to int[] for RSA input message
var rsaMessageSize = (256 + n) \ n;
component rsaMessage[rsaMessageSize];
for (var i = 0; i < rsaMessageSize; i++) {
rsaMessage[i] = Bits2Num(n);
}
for (var i = 0; i < 256; i++) {
base_msg[i \ n].in[i % n] <== sha[255 - i];
rsaMessage[i \ n].in[i % n] <== sha[255 - i];
}
for (var i = 256; i < n * msg_len; i++) {
base_msg[i \ n].in[i % n] <== 0;
for (var i = 256; i < n * rsaMessageSize; i++) {
rsaMessage[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;
// Verify RSA signature - 149,251 constraints
component rsaVerifier = RSAVerifier65537(n, k);
for (var i = 0; i < rsaMessageSize; i++) {
rsaVerifier.message[i] <== rsaMessage[i].out;
}
for (var i = msg_len; i < k; i++) {
rsa.base_message[i] <== 0;
for (var i = rsaMessageSize; i < k; i++) {
rsaVerifier.message[i] <== 0;
}
rsa.modulus <== pubkey;
rsa.signature <== signature;
rsaVerifier.modulus <== pubkey;
rsaVerifier.signature <== signature;


// Calculate the SHA256 hash of the body and verify it matches the hash in the header
if (ignoreBodyHashCheck != 1) {
signal input bodyHashIndex;
signal input precomputedSHA[32];
signal input emailBody[maxBodyLength];
signal input emailBodyLength;

if (ignore_body_hash_check != 1) {
signal input body_hash_idx;
// Assert data after the body (maxBodyLength - emailBody.length) is all zeroes
AssertZeroPadding(maxBodyLength)(emailBody, emailBodyLength + 1);

// 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]) <== BodyHashRegex(max_header_bytes)(in_padded);
bh_regex_out === 1;
signal shifted_bh_out[LEN_SHA_B64] <== VarShiftMaskedStr(max_header_bytes, LEN_SHA_B64)(bh_reveal, body_hash_idx);
// log(body_hash_regex.out);
// Body hash regex - 617,597 constraints
// Extract the body hash from the header (i.e. the part after bh= within the DKIM-signature section)
signal (bhRegexMatch, bhReveal[maxHeaderLength]) <== BodyHashRegex(maxHeaderLength)(emailHeader);
bhRegexMatch === 1;

var shaB64Length = 44; // Length of SHA-256 hash when base64 encoded - ceil(32 / 3) * 4
signal bhBase64[shaB64Length] <== SelectRegexReveal(maxHeaderLength, shaB64Length)(bhReveal, bodyHashIndex);
signal headerBodyHash[32] <== Base64Decode(32)(bhBase64);

// 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
// Compute SHA256 of email body : 760,142 constraints
// We are using a technique to save constraints by precomputing the SHA hash of the body till the area we want to extract
// 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;

// Assert padding is all zeroes
AssertZeroes(max_body_bytes)(in_body_padded, in_body_len_padded_bytes + 1);

// 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];
signal computedBodyHash[256] <== Sha256BytesPartial(maxBodyLength)(emailBody, emailBodyLength, precomputedSHA);

// Ensure the bodyHash from the header matches the calculated body hash
component computedBodyHashInts[32];
for (var i = 0; i < 32; i++) {
sha_body_bytes[i] = Bits2Num(8);
computedBodyHashInts[i] = Bits2Num(8);
for (var j = 0; j < 8; j++) {
sha_body_bytes[i].in[7 - j] <== sha_body_out[i * 8 + j];
computedBodyHashInts[i].in[7 - j] <== computedBodyHash[i * 8 + j];
}
sha_body_bytes[i].out === sha_b64_out[i];
computedBodyHashInts[i].out === headerBodyHash[i];
}
}

// Calculate the Poseidon hash of DKIM public key and produce as an output
// This can be used to verify the public key is correct in contract without requiring the actual key
// We are converting pub_key (modulus) in to 9 chunks of 242 bits, assuming original n, k are 121 and 17.
// This is because Posiedon circuit only support array of 16 elements.
var k2_chunked_size = k >> 1;
if(k % 2 == 1) {
k2_chunked_size += 1;
}
signal pubkey_hash_input[k2_chunked_size];
for(var i = 0; i < k2_chunked_size; i++) {
if(i==k2_chunked_size-1 && k2_chunked_size % 2 == 1) {
pubkey_hash_input[i] <== pubkey[2*i];
} else {
pubkey_hash_input[i] <== pubkey[2*i] + (1<<n) * pubkey[2*i+1];
}
}
pubkey_hash <== Poseidon(k2_chunked_size)(pubkey_hash_input);
}

// Calculate the Poseidon hash of DKIM public key as output
// This can be used to verify (by verifier/contract) the pubkey used in the proof without needing the full key
// Since PoseidonLarge concatenates nearby values its important to use same n/k (recommended 121*17) to produce uniform hashes
// https://zkrepl.dev/?gist=43ce7dce2466c63812f6efec5b13aa73 - This can be used to calculate the pubkey hash separately
pubkeyHash <== PoseidonLarge(n, k)(pubkey);
}
19 changes: 0 additions & 19 deletions packages/circuits/helpers/ascii.circom

This file was deleted.

23 changes: 23 additions & 0 deletions packages/circuits/helpers/email-nullifier.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

pragma circom 2.1.6;

include "circomlib/circuits/poseidon.circom";


/// @title EmailNullifier
/// @notice An opinionated way of calculating the email nullifier
/// @notice nullifier = poseidon(poseidon(signature))
/// @notice Not used in core circuits now, but can be used by projects building on ZKEmail
/// @param bitPerChunk The number of bits per chunk the signature is split into
/// @param chunkSize The number of chunks the signature is split into
/// @input signature The signature of the email
/// @output out The email nullifier
template EmailNullifier(bitPerChunk, chunkSize) {
signal input signature[chunkSize];

signal output out;

signal signatureHash <== PoseidonLarge(bitPerChunk, chunkSize)(signature);

out <== Poseidon(1)([signatureHash]);
}
Loading
Loading