Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

import { DeployLinkHandleEntrypointScript } from "./_DeployLinkHandleEntrypoint.s.sol";
import { HonkVerifier } from "../../test/fixtures/linkHandleCommand/discord/target/HonkVerifier.sol";

contract DeployLinkHandleEntrypointDiscordScript is DeployLinkHandleEntrypointScript {
function _deployHonkVerifier() internal override returns (address) {
return address(new HonkVerifier());
}

function _getDkimRegistryAddress() internal pure override returns (address) {
// sepolia always valid dkim registry
return 0xc4f628496b8c474096650C8f9023954643cC614F;
}

function _getPlatformName() internal pure override returns (string memory) {
return "Discord";
}

function _getRecordName() internal pure override returns (string memory) {
return "com.discord";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ Vm constant linkHandleVm = Vm(_LINK_HANDLE_VM_ADDR);
* (expected_public_inputs.json, proof, public_inputs, email.eml) to enable that platform.
*/
library LinkHandleCommandTestFixture {
function getDiscordFixture()
internal
view
returns (LinkHandleCommand memory command, bytes32[] memory publicInputs)
{
return getFixture("discord");
}

function getTwitterFixture()
internal
view
Expand Down
10 changes: 10 additions & 0 deletions test/fixtures/linkHandleCommand/discord/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "zkemail/discord_2@1"
type = "bin"
authors = []
compiler_version = ">=1.0.0"
Comment thread
benceharomi marked this conversation as resolved.

[dependencies]
zkemail = { tag = "v.1.0.1-beta.5", git = "https://github.com/zkemail/zkemail.nr", directory = "lib" }
zkregex = { tag = "2.2.0", git = "https://github.com/zkemail/zk-regex", directory = "noir" }
poseidon = { tag = "v0.1.0", git = "https://github.com/noir-lang/poseidon" }
60 changes: 60 additions & 0 deletions test/fixtures/linkHandleCommand/discord/files/blueprint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"name": "discord_2",
"values": [
{
"parts": [
{
"isPublic": false,
"regexDef": ">Hey "
},
{
"isPublic": true,
"regexDef": "[A-Za-z0-9_]+",
"maxLength": 64
},
{
"isPublic": false,
"regexDef": ","
}
],
"name": "handle",
"maxLength": 64,
"location": "body",
"maxMatchLength": 64
},
{
"parts": [
{
"isPublic": false,
"regexDef": "(?:\\r\\n|^)from:[^\\r\\n]*@"
},
{
"isPublic": true,
"regexDef": "[A-Za-z0-9][A-Za-z0-9\\.-]+",
"maxLength": 64
},
{
"isPublic": false,
"regexDef": "[>\\r\\n]"
}
Comment thread
benceharomi marked this conversation as resolved.
],
"name": "sender_domain",
"maxLength": 64,
"location": "header",
"maxMatchLength": 64
}
],
"version": 1,
"senderDomain": "discord.com",
"externalInputs": [
{
"name": "command",
"maxLength": 605
}
],
"emailBodyMaxLength": 16384,
"ignoreBodyHashCheck": false,
"emailHeaderMaxLength": 960,
"clientZkFramework": "Noir",
"serverZkFramework": "SP1"
}
118 changes: 118 additions & 0 deletions test/fixtures/linkHandleCommand/discord/src/handle_regex.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
use zkregex::utils::{
select_subarray,
captures::capture_substring,
sparse_array::SparseArray,
transitions::check_transition_with_captures
};
Comment thread
benceharomi marked this conversation as resolved.

global TRANSITION_TABLE: SparseArray<133, Field> = SparseArray {
keys: [0x00000000, 0x00024240, 0x00024241, 0x00034e4d, 0x00046d6c, 0x00058382, 0x00062c2b, 0x00073e3d, 0x00073e3e, 0x00073f3e, 0x00073f3f, 0x0007403f, 0x00074040, 0x00074140, 0x00074141, 0x00074241, 0x00074242, 0x00074342, 0x00074343, 0x00074443, 0x00074444, 0x00074544, 0x00074545, 0x00074645, 0x00074646, 0x00074746, 0x00074747, 0x00074f4e, 0x00074f4f, 0x0007504f, 0x00075050, 0x00075150, 0x00075151, 0x00075251, 0x00075252, 0x00075352, 0x00075353, 0x00075453, 0x00075454, 0x00075554, 0x00075555, 0x00075655, 0x00075656, 0x00075756, 0x00075757, 0x00075857, 0x00075858, 0x00075958, 0x00075959, 0x00075a59, 0x00075a5a, 0x00075b5a, 0x00075b5b, 0x00075c5b, 0x00075c5c, 0x00075d5c, 0x00075d5d, 0x00075e5d, 0x00075e5e, 0x00075f5e, 0x00075f5f, 0x0007605f, 0x00076060, 0x00076160, 0x00076161, 0x00076261, 0x00076262, 0x00076362, 0x00076363, 0x00076463, 0x00076464, 0x00076564, 0x00076565, 0x00076665, 0x00076666, 0x00076766, 0x00076767, 0x00076867, 0x00076868, 0x00076d6c, 0x00076d6d, 0x00076f6e, 0x00076f6f, 0x0007706f, 0x00077070, 0x00077170, 0x00077171, 0x00077271, 0x00077272, 0x00077372, 0x00077373, 0x00077473, 0x00077474, 0x00077574, 0x00077575, 0x00077675, 0x00077676, 0x00077776, 0x00077777, 0x00077877, 0x00077878, 0x00077978, 0x00077979, 0x00077a79, 0x00077a7a, 0x00077b7a, 0x00077b7b, 0x00077c7b, 0x00077c7c, 0x00077d7c, 0x00077d7d, 0x00077e7d, 0x00077e7e, 0x00077f7e, 0x00077f7f, 0x0007807f, 0x00078080, 0x00078180, 0x00078181, 0x00078281, 0x00078282, 0x00078382, 0x00078383, 0x00078483, 0x00078484, 0x00078584, 0x00078585, 0x00078685, 0x00078686, 0x00078786, 0x00078787, 0x00078887, 0x00078888, 0x00083c3b, 0x0009100f],
values: [0x00000000, 0x00000000, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000007, 0x00000005, 0x00000001, 0x00000000],
maximum: 0x0009100f
};

pub global CAPTURE_1_MAX_LENGTH: u32 = 64;
pub global NUM_CAPTURE_GROUPS: u32 = 1;

/**
* Constrains a start state to be valid
* @dev start states are hardcoded in this function - "(start_state - {state})" for each start
* example: `(start_state - 0) * (start_state - 1) * (start_state - 2)` means 0, 1, or 2
* are valid first states
*
* @param start_state - The start state of the NFA
*/
fn check_start_state(start_state: Field) {
let valid_start_state = (start_state - 0) * (start_state - 1);
assert(valid_start_state == 0, "Invalid start state");
}

/**
* Constrains the recognition of accept_state being reached. If an aceppt state is reached,
* ensures asserted traversal path is valid
* @dev accept states are hardcoded in this function - "(next_state - {state})" for each accept
* example: `(next_state - 19) * (next_state - 20) * (next_state - 21)` means 19, 20, or 21
* are valid accept states
*
* @param next_state - The asserted next state of the NFA
* @param haystack_index - The index being operated on in the haystack
* @param asserted_match_length - The asserted traversal path length
* @return - 0 if accept_state is reached, nonzero otherwise
*/
fn check_accept_state(
next_state: Field,
haystack_index: Field,
asserted_match_length: Field
) -> Field {
// check if the next state is an accept state
let accept_state_reached = (next_state - 8);
let accept_state_reached_bool = (accept_state_reached == 0) as Field;

// check if the haystack index is the asserted match length
// should equal 1 since haystack_index should be 1 less than asserted_match)length
let asserted_path_traversed = (asserted_match_length - haystack_index == 1) as Field;

(1 - (accept_state_reached_bool * asserted_path_traversed))
}

/**
* HandleRegex matching function
* Regex: >Hey ([A-Za-z0-9_]+),
* @param in_haystack - The input haystack to search from
* @param match_start - The start index in the haystack for the subarray to match from
* @param match_length - The length of the subarray to extract from haystack
* @param current_states - The current states of the NFA at each index in the match subarray
* @param next_states - The next states of the NFA at each index in the match subarray
* @param capture_group_<group>_ids - The ids of the capture groups in the match subarray
* @param capture_group_<group>_starts - The start positions of the capture groups in the match subarray
* @param capture_group_start_indices - The start indices of the capture groups in the match subarray
* @return - tuple of substring captures as dictated by the regular expression
*/
pub fn regex_match<let MAX_HAYSTACK_LEN: u32, let MAX_MATCH_LEN: u32>(
in_haystack: [u8; MAX_HAYSTACK_LEN],
match_start: u32,
match_length: u32,
current_states: [Field; MAX_MATCH_LEN],
next_states: [Field; MAX_MATCH_LEN],
capture_group_1_id: [Field; MAX_MATCH_LEN],
capture_group_1_start: [Field; MAX_MATCH_LEN],
capture_group_start_indices: [Field; NUM_CAPTURE_GROUPS],
) -> (BoundedVec<u8, CAPTURE_1_MAX_LENGTH>) {
// resize haystack
let haystack: [u8; MAX_MATCH_LEN] = select_subarray::<MAX_HAYSTACK_LEN, MAX_MATCH_LEN>(in_haystack, match_start, match_length);

check_start_state(current_states[0]);
for i in 0..MAX_MATCH_LEN-1 {
// match length - 1 since current states should be 1 less than next states
let in_range = (i < match_length - 1) as Field;
let matching_states = current_states[i + 1] - next_states[i];
assert(in_range * matching_states == 0, "Invalid Transition Input");
}
let mut reached_end_state = 1;
for i in 0..MAX_MATCH_LEN {
let active_capture_groups_at_index = [capture_group_1_id[i]];
let active_capture_groups_starts_at_index = [capture_group_1_start[i]];
check_transition_with_captures(
TRANSITION_TABLE,
haystack[i] as Field,
current_states[i],
next_states[i],
active_capture_groups_at_index,
active_capture_groups_starts_at_index,
reached_end_state
);
reached_end_state = reached_end_state * check_accept_state(next_states[i], i as Field, match_length as Field);
}
assert(reached_end_state == 0, "Did not reach a valid end state");

// Capture Group 1
let capture_1 = capture_substring::<MAX_MATCH_LEN, CAPTURE_1_MAX_LENGTH, 1>(
haystack,
capture_group_1_id,
capture_group_1_start,
capture_group_start_indices[0]
);

(capture_1)
}

83 changes: 83 additions & 0 deletions test/fixtures/linkHandleCommand/discord/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@

mod handle_regex;
mod sender_domain_regex;
use poseidon::poseidon;
use std::{collections::bounded_vec::BoundedVec, hash::pedersen_hash};
use zkemail::{
dkim::RSAPubkey, hash::poseidon_large, headers::body_hash::get_body_hash, KEY_LIMBS_2048,
partial_hash::partial_sha256_var_end, remove_soft_line_breaks::remove_soft_line_breaks,
Sequence, utils::pack_bytes,
};
fn main(
header: BoundedVec<u8, 960>,
pubkey: RSAPubkey<KEY_LIMBS_2048>,
signature: [Field; KEY_LIMBS_2048],
prover_address: [Field; 1],
dkim_header_sequence: Sequence,
body: BoundedVec<u8, 16384>,
body_hash_index: u32,
partial_body_hash: [u32; 8],
partial_body_real_length: u64,
decoded_body: BoundedVec<u8, 16384>,
command: [Field; 20],
handle_match_start: u32,
handle_match_length: u32,
handle_current_states: [Field; 64],
handle_next_states: [Field; 64],
handle_capture_group_1_id: [Field; 64],
handle_capture_group_1_start: [Field; 64],
handle_capture_group_start_indices: [Field; 1],
sender_domain_match_start: u32,
sender_domain_match_length: u32,
sender_domain_current_states: [Field; 64],
sender_domain_next_states: [Field; 64],
sender_domain_capture_group_1_id: [Field; 64],
sender_domain_capture_group_1_start: [Field; 64],
sender_domain_capture_group_start_indices: [Field; 1],
) -> pub (Field, Field, Field, Field, [Field; 1] , [Field; 20], BoundedVec<u8, 64>, BoundedVec<u8, 64>) {
Comment thread
benceharomi marked this conversation as resolved.
// check the body and header lengths are within bounds
assert(header.len() <= 960);
assert(body.len() <= 16384);
// DKIM Verification
let header_hash: [u8; 32] = pubkey.verify_dkim_signature(header, signature);
let header_hash: [Field; 2] = pack_bytes::<32, 16>(header_hash);
// Body Hash Verification
let signed_body_hash = get_body_hash(header, dkim_header_sequence, body_hash_index);
let computed_body_hash: [u8; 32] = partial_sha256_var_end(
partial_body_hash,
body.storage(),
body.len() as u64,
partial_body_real_length,
);
assert(
signed_body_hash == computed_body_hash,
"SHA256 hash computed over body does not match body hash found in DKIM-signed header",
);
assert(
remove_soft_line_breaks(body.storage(), decoded_body.storage()),
"Decoded body does not properly remove soft line breaks",
);
let pubkey_hash = pubkey.hash();
let email_nullifier = poseidon::bn254::hash_1([poseidon_large(signature)]);
let handle_capture_1 = handle_regex::regex_match::<16384, 64>(
decoded_body.storage(),
handle_match_start,
handle_match_length,
handle_current_states,
handle_next_states,
handle_capture_group_1_id,
handle_capture_group_1_start,
handle_capture_group_start_indices,
);
let sender_domain_capture_1 = sender_domain_regex::regex_match::<960, 64>(
header.storage(),
sender_domain_match_start,
sender_domain_match_length,
sender_domain_current_states,
sender_domain_next_states,
sender_domain_capture_group_1_id,
sender_domain_capture_group_1_start,
sender_domain_capture_group_start_indices,
);
(pubkey_hash, email_nullifier, header_hash[0], header_hash[1], prover_address, command, handle_capture_1, sender_domain_capture_1)
}
Loading