├── contracts ├── .gitsave └── Blind.sol ├── jest.config.js ├── declarations.d.ts ├── scripts ├── helpers │ ├── shaHash.ts │ ├── rsa.ts │ ├── poseidonHash.ts │ ├── merkle.ts │ ├── constants.ts │ ├── sshFormat.ts │ ├── binaryFormat.ts │ └── zkp.ts ├── deploy.js ├── generate_input.ts └── fast-sha256.ts ├── regex_to_circom ├── halo2_regex_lookup.txt ├── README.md ├── gen.py └── regex_to_dfa.js ├── circuits ├── ascii.circom ├── regex_helpers.circom ├── test │ └── jwt.test.ts ├── base64.circom ├── timestamp.circom ├── sha.circom ├── sha256partial.circom ├── jwt_type_regex.circom ├── fp.circom ├── jwt_email_regex.circom ├── rsa.circom ├── utils.circom ├── jwt.circom ├── sha256general.circom ├── scripts │ └── compile.js ├── bigint_func.circom └── bigint.circom ├── shell_scripts ├── 2_gen_wtns.sh ├── 1_compile.sh ├── benchmark.sh └── upload_to_s3.py ├── .gitignore ├── hardhat.config.js ├── tsconfig.json ├── package.json ├── README.md ├── test └── blind.test.js ├── inputs ├── openai.json └── jwt_client.json └── test.ipynb /contracts/.gitsave: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | }; -------------------------------------------------------------------------------- /declarations.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'circomlibjs'; 2 | declare module 'circom_tester'; 3 | declare module 'ffjavascript'; -------------------------------------------------------------------------------- /scripts/helpers/shaHash.ts: -------------------------------------------------------------------------------- 1 | import { createHash } from "crypto"; 2 | // const { webcrypto, KeyObject } = await import('crypto'); 3 | // const { subtle } = webcrypto; 4 | 5 | export async function shaHash(str: Uint8Array) { 6 | return createHash("sha256").update(str).digest(); 7 | } 8 | -------------------------------------------------------------------------------- /regex_to_circom/halo2_regex_lookup.txt: -------------------------------------------------------------------------------- 1 | 1 2 | 0 2 34 3 | 1 1 48 4 | 1 1 49 5 | 1 1 50 6 | 1 1 51 7 | 1 1 52 8 | 1 1 53 9 | 1 1 54 10 | 1 1 55 11 | 1 1 56 12 | 1 1 57 13 | 2 3 101 14 | 3 4 120 15 | 4 5 112 16 | 5 6 34 17 | 6 7 58 18 | 7 1 48 19 | 7 1 49 20 | 7 1 50 21 | 7 1 51 22 | 7 1 52 23 | 7 1 53 24 | 7 1 54 25 | 7 1 55 26 | 7 1 56 27 | 7 1 57 28 | -------------------------------------------------------------------------------- /scripts/helpers/rsa.ts: -------------------------------------------------------------------------------- 1 | 2 | function modExp(a: bigint, b: number, c: bigint): bigint { 3 | let res = 1n; 4 | for (let i = 0; i < 30; ++i) { 5 | if ((b >> i) & 1) res = (res * a) % c; 6 | a = (a * a) % c; 7 | } 8 | return res; 9 | } 10 | 11 | 12 | export function verifyRSA(sig: bigint, modulus: bigint): bigint { 13 | return modExp(sig, 65537, modulus); 14 | } -------------------------------------------------------------------------------- /circuits/ascii.circom: -------------------------------------------------------------------------------- 1 | pragma circom 2.1.4; 2 | include "../node_modules/circomlib/circuits/comparators.circom"; 3 | 4 | // only converts ascii to numbers from 0-9 5 | template AsciiToNum (max_input) { 6 | signal input in[max_input]; 7 | signal output out; 8 | 9 | var temp = in[0] - 48; 10 | 11 | for (var i = 1; i < max_input; i++) { 12 | temp *= 10; 13 | temp += in[i] - 48; 14 | } 15 | 16 | out <== temp; 17 | } -------------------------------------------------------------------------------- /shell_scripts/2_gen_wtns.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CIRCUIT_NAME=jwt 4 | BUILD_DIR="./build/$CIRCUIT_NAME" 5 | 6 | echo "****GENERATING WITNESS FOR SAMPLE INPUT****" 7 | start=`date +%s` 8 | set -x 9 | node "$BUILD_DIR"/"$CIRCUIT_NAME"_js/generate_witness.js "$BUILD_DIR"/"$CIRCUIT_NAME"_js/"$CIRCUIT_NAME".wasm ./inputs/jwt_client.json "$BUILD_DIR"/witness.wtns 10 | { set +x; } 2>/dev/null 11 | end=`date +%s` 12 | echo "DONE ($((end-start))s)" 13 | echo 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # node 2 | node_modules/ 3 | 4 | # hardhat 5 | cache 6 | artifacts 7 | 8 | # Ignore the output from circom because we want people to build these 9 | # Users can remove these lines and commit the artifacts 10 | circuits/*.r1cs 11 | circuits/*.vkey.json 12 | circuits/*.wasm 13 | circuits/*.zkey 14 | circuits/*.ptau 15 | 16 | build/ 17 | keys/ 18 | 19 | secret.json 20 | yarn.lock 21 | 22 | .env 23 | 24 | .DS_Store 25 | .DS_Store? 26 | 27 | pyjwt-env/ -------------------------------------------------------------------------------- /shell_scripts/1_compile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CIRCUIT_NAME=jwt 4 | BUILD_DIR="./build/$CIRCUIT_NAME" 5 | 6 | if [ ! -d "$BUILD_DIR" ]; then 7 | echo "No build directory found. Creating build directory..." 8 | mkdir -p "$BUILD_DIR" 9 | fi 10 | 11 | echo '****COMPILING CIRCUIT****' 12 | start=`date +%s` 13 | set -x 14 | circom -l node_modules "./circuits/$CIRCUIT_NAME".circom --r1cs --wasm --sym --c --wat --output "$BUILD_DIR" 15 | { set +x; } 2>/dev/null 16 | end=`date +%s` 17 | echo "DONE ($((end-start))s)" 18 | echo 19 | -------------------------------------------------------------------------------- /scripts/deploy.js: -------------------------------------------------------------------------------- 1 | const hre = require("hardhat"); 2 | const secret = require("../secret.json"); 3 | 4 | async function main() { 5 | const verifier = await hre.ethers.getContractFactory("contracts/Verifier.sol:Verifier"); 6 | const v = await verifier.deploy(); 7 | await v.deployed(); 8 | 9 | console.log("Verifier.sol deployed to:", v.address) 10 | 11 | const blind = await hre.ethers.getContractFactory("Blind"); 12 | 13 | const b = await blind.deploy( 14 | v.address 15 | ); 16 | await b.deployed(); 17 | 18 | console.log("Blind.sol deployed to:", b.address) 19 | } 20 | 21 | main(); 22 | -------------------------------------------------------------------------------- /scripts/helpers/poseidonHash.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { buildPoseidon } from "circomlibjs"; 3 | 4 | let poseidonHasher: any; 5 | export async function initializePoseidon() { 6 | if (!poseidonHasher) { 7 | poseidonHasher = await buildPoseidon(); 8 | } 9 | } 10 | export const poseidon = (arr: (number | bigint | string)[]): string => 11 | poseidonHasher.F.toString(poseidonHasher(arr)); 12 | 13 | export const poseidonK = (ar: (number | bigint | string)[]): string => { 14 | let cur: (number | bigint | string)[] = []; 15 | for (const elt of ar) { 16 | cur.push(elt); 17 | if (cur.length === 16) { 18 | cur = [poseidon(cur)]; 19 | } 20 | } 21 | if (cur.length === 1) return `${cur[0]}`; 22 | while (cur.length < 16) cur.push(0); 23 | return poseidon(cur); 24 | }; 25 | -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("hardhat-circom"); 2 | let secret = require("./secret"); 3 | // require("@nomicfoundation/hardhat-toolbox"); 4 | require("@nomiclabs/hardhat-waffle"); 5 | 6 | /** 7 | * @type import('hardhat/config').HardhatUserConfig 8 | */ 9 | module.exports = { 10 | solidity: { 11 | compilers: [ 12 | { 13 | version: "0.6.11", 14 | }, 15 | { 16 | version: "0.8.9", 17 | }, 18 | ], 19 | }, 20 | networks: { 21 | goerli: { 22 | url: secret.goerli, 23 | accounts: [secret.key], 24 | gas: 350000000000, 25 | gasPrice: 80000000000, 26 | }, 27 | }, 28 | allowUnlimitedContractSize: true, 29 | circom: { 30 | inputBasePath: "./circuits", 31 | ptau: "powersOfTau28_hez_final_22.ptau", 32 | circuits: [ 33 | { 34 | name: "jwt", 35 | protocol: "groth16", 36 | }, 37 | ], 38 | }, 39 | }; 40 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | 6 | "esModuleInterop": true, 7 | "lib": [ 8 | "dom", 9 | "dom.iterable", 10 | "es2020","esnext", 11 | ], 12 | "allowJs": true, 13 | "skipLibCheck": true, 14 | "allowSyntheticDefaultImports": true, 15 | "strict": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "moduleResolution": "node", 19 | "resolveJsonModule": true, 20 | "isolatedModules": true, 21 | "noEmit": true, 22 | "jsx": "react-jsx", 23 | "types": [ 24 | "node", "jest" 25 | ], 26 | "incremental": true 27 | }, 28 | "include": [ 29 | "src", 30 | "circuits/scripts/", 31 | "declarations.d.ts", 32 | ], 33 | "exclude": [ 34 | "node_modules" 35 | ] 36 | } -------------------------------------------------------------------------------- /scripts/helpers/merkle.ts: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import { poseidon } from "./poseidonHash"; 3 | import { CIRCOM_LEVELS } from "./constants"; 4 | 5 | export function buildMerkleTree(leaves: string[]): string[] { 6 | const SIZE = leaves.length; 7 | const res = _.times(2 * SIZE, () => "0"); 8 | for (let i = 0; i < SIZE; ++i) { 9 | res[SIZE + i] = leaves[i]; 10 | } 11 | for (let i = SIZE - 1; i > 0; --i) { 12 | res[i] = poseidon([res[2 * i], res[2 * i + 1]]); 13 | } 14 | return res; 15 | } 16 | 17 | export async function getMerkleProof(merkleTree: string[], leaf: string) { 18 | const pathElements = []; 19 | const pathIndices = []; 20 | for (let idx = merkleTree.indexOf(leaf); idx > 1; idx = idx >> 1) { 21 | pathElements.push(merkleTree[idx ^ 1]); 22 | pathIndices.push(idx & 1); 23 | } 24 | while (pathElements.length < CIRCOM_LEVELS) { 25 | pathElements.push(0); 26 | pathIndices.push(0); 27 | } 28 | const root = merkleTree[1]; 29 | return { 30 | leaf, 31 | pathElements, 32 | pathIndices, 33 | root, 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /circuits/regex_helpers.circom: -------------------------------------------------------------------------------- 1 | pragma circom 2.0.3; 2 | 3 | include "../node_modules/circomlib/circuits/comparators.circom"; 4 | include "../node_modules/circomlib/circuits/gates.circom"; 5 | 6 | template MultiOROld(n) { 7 | signal input in[n]; 8 | signal output out; 9 | component or1; 10 | component or2; 11 | component ors[2]; 12 | if (n==1) { 13 | out <== in[0]; 14 | } else if (n==2) { 15 | or1 = OR(); 16 | or1.a <== in[0]; 17 | or1.b <== in[1]; 18 | out <== or1.out; 19 | } else { 20 | or2 = OR(); 21 | var n1 = n\2; 22 | var n2 = n-n\2; 23 | ors[0] = MultiOR(n1); 24 | ors[1] = MultiOR(n2); 25 | var i; 26 | for (i=0; i=16" 21 | }, 22 | "scripts": { 23 | "circom:dev": "hardhat circom --deterministic --debug --verbose", 24 | "circom:prod": "hardhat circom --verbose", 25 | "test": "hardhat test" 26 | }, 27 | "dependencies": { 28 | "@nomiclabs/hardhat-ethers": "^2.2.1", 29 | "@nomiclabs/hardhat-waffle": "^2.0.3", 30 | "@zk-email/circuits": "^3.2.0", 31 | "@zk-email/zk-regex-circom": "^1.2.1", 32 | "circom": "https://github.com/iden3/circom.git", 33 | "cryo": "^0.0.6", 34 | "ethereum-waffle": "^3.4.4", 35 | "ethers": "^5.7.2", 36 | "fs": "^0.0.1-security", 37 | "global": "^4.4.0", 38 | "lodash": "^4.17.21", 39 | "node-forge": "^1.3.1", 40 | "snarkjs": "^0.6.12", 41 | "ts-node": "^10.9.1", 42 | "tsx": "^3.12.7", 43 | "typescript": "^4.9.4" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /shell_scripts/benchmark.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CIRCUIT_DIR=./circuits/jwt_benchmarks 4 | TIME=(gtime -f "mem %M\ntime %e\ncpu %P") 5 | CIRCUIT_NAME=jwt 6 | BUILD_DIR="./build/$CIRCUIT_NAME" 7 | 8 | avg_time() { 9 | # 10 | # usage: avg_time n command ... 11 | # 12 | n=$1; shift 13 | (($# > 0)) || return # bail if no command given 14 | echo "$@" 15 | for ((i = 0; i < n; i++)); do 16 | "${TIME[@]}" "$@" 2>&1 17 | done | awk ' 18 | /mem/ { mem = mem + $2; nm++ } 19 | /time/ { time = time + $2; nt++ } 20 | /cpu/ { cpu = cpu + substr($2,1,length($2)-1); nc++} 21 | END { 22 | if (nm>0) printf("mem %f\n", mem/nm); 23 | if (nt>0) printf("time %f\n", time/nt); 24 | if (nc>0) printf("cpu %f\n", cpu/nc) 25 | }' 26 | } 27 | 28 | function normalProve() { 29 | # pushd "$CIRCUIT_DIR" 30 | avg_time 1 snarkjs groth16 prove "$BUILD_DIR"/jwt_single1.zkey "$BUILD_DIR"/witness.wtns "$BUILD_DIR"/proof.json "$BUILD_DIR"/public.json 31 | proof_size=$(ls -lh "$BUILD_DIR"/proof.json | awk '{print $5}') 32 | echo "Proof size: $proof_size" 33 | # popd 34 | } 35 | 36 | function verify() { 37 | # pushd "$CIRCUIT_DIR" 38 | avg_time 1 snarkjs groth16 verify "$BUILD_DIR"/verification_key.json "$BUILD_DIR"/public.json "$BUILD_DIR"/proof.json 39 | # popd 40 | } 41 | 42 | echo "========== Step1: prove ==========" 43 | normalProve 44 | 45 | echo "========== Step2: verify ==========" 46 | verify 47 | -------------------------------------------------------------------------------- /shell_scripts/upload_to_s3.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import os 3 | import tarfile 4 | import time 5 | 6 | # Set up the client for the AWS S3 service 7 | s3 = boto3.client('s3') 8 | 9 | # Set the name of the remote directory and the AWS bucket 10 | zkey_dir = '../build/jwt/' 11 | wasm_dir = '../build/jwt/jwt_js/' 12 | bucket_name = 'zkjwt-zkey-chunks' # us-west-2 13 | 14 | def upload_to_s3(filename, dir=""): 15 | with open(dir + filename, 'rb') as file: 16 | print("Starting upload...") 17 | s3.upload_fileobj(file, bucket_name, filename, ExtraArgs={'ACL': 'public-read', 'ContentType': 'binary/octet-stream'}) 18 | print("Done uploading!") 19 | 20 | # Loop through the files in the remote directory 21 | for dir in [zkey_dir, wasm_dir]: 22 | for file in os.listdir(dir): 23 | # Check if the file matches the pattern 24 | if file.startswith('jwt.zkey'): 25 | upload_to_s3(file, dir) # Uncompressed file 26 | 27 | # Create a zip file for the file 28 | tar_file_name = file + '.tar.gz' 29 | with tarfile.open(tar_file_name, 'w:gz') as tar_file: 30 | print("Compressing: ", dir + file) 31 | tar_file.add(dir + file) 32 | 33 | # Upload the zip file to the AWS bucket, overwriting any existing file with the same name 34 | upload_to_s3(tar_file_name) 35 | 36 | os.remove(tar_file_name) 37 | 38 | if file.startswith('vkey.json') or file.startswith('jwt.wasm'): 39 | # Upload the zip file to the AWS bucket, overwriting any existing file with the same name 40 | upload_to_s3(file, dir) 41 | -------------------------------------------------------------------------------- /scripts/helpers/constants.ts: -------------------------------------------------------------------------------- 1 | export const CIRCOM_FIELD_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617n; 2 | export const MAX_MSG_PADDED_BYTES = 1024; // NOTE: this must be the same as the first arg in the email in main args circom 3 | 4 | // circom constants from main.circom / https://zkrepl.dev/?gist=30d21c7a7285b1b14f608325f172417b 5 | // template RSAGroupSigVerify(n, k, levels) { 6 | // component main { public [ modulus ] } = RSAVerify(121, 17); 7 | // component main { public [ root, payload1 ] } = RSAGroupSigVerify(121, 17, 30); 8 | export const CIRCOM_BIGINT_N = 121; 9 | export const CIRCOM_BIGINT_K = 17; 10 | export const CIRCOM_LEVELS = 30; 11 | 12 | export const OPENAI_PUBKEY = `-----BEGIN PUBLIC KEY----- 13 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA27rOErDOPvPc3mOADYtQ 14 | BeenQm5NS5VHVaoO/Zmgsf1M0Wa/2WgLm9jX65Ru/K8Az2f4MOdpBxxLL686ZS+K 15 | 7eJC/oOnrxCRzFYBqQbYo+JMeqNkrCn34yed4XkX4ttoHi7MwCEpVfb05Qf/ZAmN 16 | I1XjecFYTyZQFrd9LjkX6lr05zY6aM/+MCBNeBWp35pLLKhiq9AieB1wbDPcGnqx 17 | lXuU/bLgIyqUltqLkr9JHsf/2T4VrXXNyNeQyBq5wjYlRkpBQDDDNOcdGpx1buRr 18 | Z2hFyYuXDRrMcR6BQGC0ur9hI5obRYlchDFhlb0ElsJ2bshDDGRk5k3doHqbhj2I 19 | gQIDAQAB 20 | -----END PUBLIC KEY-----` 21 | 22 | export const JWT_CLIENT_PUBKEY = `-----BEGIN PUBLIC KEY----- 23 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmhnp/JyDgCZXpMj5h+G4 24 | DEAuoCLlJSugN++pEnDJGuCfoiT8/0TFG04grKi2UIEy7QtLc+6D4qCK9qddrPqq 25 | kWc+dB5f6Is5FNfcU8N6fyOI5lFk46M6ECynwcWltBj9EVG553HXe7J4aI+Ixj5v 26 | be759MaXBOZFyHO2S6JF1CBoYjiOp/9c0JK822JWstoBuuqOWEUd1EH60K8+2mbX 27 | kvzKNvlOln6As6RrqtfcE2H3AWxfpofBOaGZ1lr5MAO3LIC9XQn//xoG5gKCj8qX 28 | 3iVhu67Vz/45dL7UmTWPa2dg0UZq38uwKSaS+CdLUxQXbNqQlzBnkAjbPn/cI/q8 29 | SwIDAQAB 30 | -----END PUBLIC KEY-----` 31 | 32 | -------------------------------------------------------------------------------- /circuits/test/jwt.test.ts: -------------------------------------------------------------------------------- 1 | import { wasm as wasm_tester } from "circom_tester"; 2 | import { Scalar } from "ffjavascript"; 3 | import path from "path"; 4 | 5 | import { ICircuitInputs, generate_inputs } from "../../scripts/generate_input"; 6 | 7 | exports.p = Scalar.fromString( 8 | "21888242871839275222246405745257275088548364400416034343698204186575808495617" 9 | ); 10 | 11 | describe("RSA", () => { 12 | jest.setTimeout(10 * 60 * 1000); // 10 minutes 13 | 14 | let circuit: any; 15 | // let dkimResult: DKIMVerificationResult; 16 | 17 | beforeAll(async () => { 18 | circuit = await wasm_tester( 19 | path.join(__dirname, "../jwt.circom"), 20 | { 21 | // @dev During development recompile can be set to false if you are only making changes in the tests. 22 | // This will save time by not recompiling the circuit every time. 23 | // Compile: circom "./tests/email-verifier-test.circom" --r1cs --wasm --sym --c --wat --output "./tests/compiled-test-circuit" 24 | recompile: true, 25 | output: path.join(__dirname, "./compiled-test-circuit"), 26 | include: path.join(__dirname, "../../../node_modules"), 27 | } 28 | ); 29 | }); 30 | 31 | it("should verify rsa signature correctly", async function () { 32 | const emailVerifierInputs: ICircuitInputs = await generate_inputs(); 33 | 34 | const witness = await circuit.calculateWitness({ 35 | ...emailVerifierInputs, 36 | // message[max_msg_bytes], 37 | // modulus[k], 38 | // signature[k], 39 | // message_padded_bytes, 40 | // period_idx, 41 | // domain_idx, 42 | // time_idx, 43 | // time, 44 | }); 45 | await circuit.checkConstraints(witness); 46 | await circuit.assertOut(witness, {}) 47 | }); 48 | 49 | // it("should fail verifing incorrect rsa signature", async function () { 50 | // const emailVerifierInputs: ICircuitInputs = await generate_inputs(); 51 | 52 | // const witness = await circuit.calculateWitness({ 53 | // ...emailVerifierInputs, 54 | // time_idx: "1", 55 | // // message[max_msg_bytes], 56 | // // modulus[k], 57 | // // signature[k], 58 | // // message_padded_bytes, 59 | // // period_idx, 60 | // // domain_idx, 61 | // // time_idx, 62 | // // time, 63 | // }); 64 | // await circuit.checkConstraints(witness); 65 | // await circuit.assertOut(witness, {}) 66 | // }); 67 | }); 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zk-blind 2 | 3 | post anonymous confessions about your work place / organization in zero-knowledge! 4 | 5 | `yarn` to install all dependencies. 6 | 7 | ## generate inputs 8 | 9 | to generate inputs into `____.json`, replace `signature`, `msg`, and `ethAddress` in `node-ts scripts/generate_input.ts`. currently, this file will only generate inputs for OpenAI JWTs and JWTs generated by our dummy JWT generator application(https://get-jwt.vercel.app/), but feel free to add more public keys to support JWTs from different sites. 10 | 11 | on the last line of `scripts/gen_inputs.ts`, edit the json file name. 12 | ``` 13 | ts-node scripts/generate_input.ts 14 | ``` 15 | 16 | ## circuits 17 | 18 | These circuits check for (1) valid rsa signature, (2) that the message is a JWT, (3) ownership of a specific email domain, and (4) JWT expiration. 19 | 20 | compile circuits in root project directory. 21 | ``` 22 | ./shell_scripts/1_compile.sh 23 | ``` 24 | 25 | generate witness 26 | ``` 27 | ./shell_scripts/2_gen_wtns.sh 28 | ``` 29 | make sure to edit the input json file name to the correct input file you generated in the generate inputs step. 30 | 31 | phase 2 and getting full zkey + vkey 32 | ``` 33 | snarkjs groth16 setup ./build/jwt/jwt.r1cs ./circuits/powersOfTau28_hez_final_22.ptau ./build/jwt/jwt_single.zkey 34 | 35 | snarkjs zkey contribute ./build/jwt/jwt_single.zkey ./build/jwt/jwt_single1.zkey --name="1st Contributor Name" -v 36 | 37 | snarkjs zkey export verificationkey ./build/jwt/jwt_single1.zkey ./build/jwt/verification_key.json 38 | 39 | ``` 40 | 41 | generate proof 42 | ``` 43 | snarkjs groth16 prove ./build/jwt/jwt_single1.zkey ./build/jwt/witness.wtns ./build/jwt/proof.json ./build/jwt/public.json 44 | ``` 45 | 46 | verify proof offchain 47 | ``` 48 | snarkjs groth16 verify ./build/jwt/verification_key.json ./build/jwt/public.json ./build/jwt/proof.json 49 | ``` 50 | 51 | generate verifier.sol 52 | ``` 53 | snarkjs zkey export solidityverifier ./build/jwt/jwt_single1.zkey contracts/Verifier.sol 54 | ``` 55 | 56 | run local hardhat test 57 | ``` 58 | npx hardhat test ./test/blind.test.js 59 | ``` 60 | 61 | deploy blind and verifier contracts 62 | ``` 63 | npx hardhat run ./scripts/deploy.js --network goerli 64 | ``` 65 | 66 | ## on-chain verification 67 | 68 | in our code, we have examples of verifying an OpenAI JWT on-chain. however, `./contracts/Blind.sol` is not updated with the current state of the circuit, since our proof of concept app, Nozee, does not use on-chain verification. 69 | 70 | however, if you are interested in deploying on-chain, `./scripts/deploy.js` allows you to do a hardhat deploy, and `./test/blind.test.js` are examples of how we tested and deployed our previously working Blind.sol contract. 71 | 72 | run hardhat contract tests, first create a `secret.json` file that has a private key and goerli node provider endpoint. 73 | ``` 74 | yarn test 75 | ``` 76 | -------------------------------------------------------------------------------- /contracts/Blind.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.6.11; 3 | 4 | import {Verifier} from "./Verifier.sol"; 5 | 6 | contract Blind { 7 | mapping(address => string) public companies; 8 | Verifier public verifier; 9 | 10 | constructor(address _verifier) public { 11 | verifier = Verifier(_verifier); 12 | } 13 | 14 | function add( 15 | uint[2] memory a, 16 | uint[2][2] memory b, 17 | uint[2] memory c, 18 | uint[48] memory input // Just the Hex representation of our public circuit inputs 19 | ) public { 20 | 21 | // 0 to 16 modulus 22 | // 17 pubkey 23 | // 18 to 47 domain 24 | 25 | // Verify Proof 26 | require(verifier.verifyProof(a, b, c, input) == true, "Proof failed to verify"); 27 | 28 | // Verify right public key 29 | uint256[] memory openaiPubkey = new uint256[](17); 30 | openaiPubkey[0] = 0x0000000000000000000000000000000000c8430c6464e64ddda07a9b863d8881; 31 | openaiPubkey[1] = 0x0000000000000000000000000000000001cd0da2c4ae4218b0cade824b613b37; 32 | openaiPubkey[2] = 0x000000000000000000000000000000000062e5c346b31c47a050182d2eafd848; 33 | openaiPubkey[3] = 0x00000000000000000000000000000000000618669ce3a3538eaddc8d6ced08b9; 34 | openaiPubkey[4] = 0x0000000000000000000000000000000000d75cdc8d790c81ab9c23625464a414; 35 | openaiPubkey[5] = 0x00000000000000000000000000000000011954a4b6d45c95fa48f63ffec9f0ad; 36 | openaiPubkey[6] = 0x0000000000000000000000000000000001e075c1b0cf7069eac655ee53f6cb80; 37 | openaiPubkey[7] = 0x000000000000000000000000000000000060409af02b53bf34965950c557a044; 38 | openaiPubkey[8] = 0x000000000000000000000000000000000016b77d2e3917ea5af4e7363a68cffe; 39 | openaiPubkey[9] = 0x00000000000000000000000000000000007283ffb204c691aaf1bce0ac279328; 40 | openaiPubkey[10] = 0x0000000000000000000000000000000001785e45f8b6da078bb330084a557dbd; 41 | openaiPubkey[11] = 0x00000000000000000000000000000000003520db147c498f546c95853efc64f3; 42 | openaiPubkey[12] = 0x0000000000000000000000000000000001a652f8aede242fe83a7af1091cc560; 43 | openaiPubkey[13] = 0x000000000000000000000000000000000177e578067b3fc1873b4838e2597d79; 44 | openaiPubkey[14] = 0x000000000000000000000000000000000082c7f533459aff65a02e6f635fae51; 45 | openaiPubkey[15] = 0x0000000000000000000000000000000000a00bcf4e84dc9a972a8eab541dfb33; 46 | openaiPubkey[16] = 0x000000000000000000000000000000000000dbbace12b0ce3ef3dcde63800d8b; 47 | 48 | 49 | for (uint256 i = 0; i < 17; i++) { 50 | require(uint256(input[i]) == openaiPubkey[i], "Ivalid input key to snark"); 51 | } 52 | 53 | // User's eth pubkey 54 | address addr = address(uint160(uint256(bytes32(input[17])))); 55 | 56 | bytes memory domain = new bytes(0); 57 | 58 | // Domain 59 | for (uint256 i = 18; i < 47; i++) { 60 | if (input[i] != uint256(0)) { 61 | domain = abi.encodePacked(domain, byte(uint8(input[i]))); 62 | } 63 | } 64 | 65 | companies[addr] = string(domain); 66 | } 67 | 68 | function get(address addr) public view returns (string memory){ 69 | return companies[addr]; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /test/blind.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const { ethers } = require("hardhat"); 3 | 4 | describe("Token contract", function () { 5 | it("User's public key and company should be added to contract mapping", async function () { 6 | const [owner] = await ethers.getSigners(); 7 | 8 | const verifier = await ethers.getContractFactory( 9 | "contracts/Verifier.sol:Verifier" 10 | ); 11 | const v = await verifier.deploy(); 12 | await v.deployed(); 13 | 14 | console.log("Verifier.sol deployed to:", v.address); 15 | 16 | const blind = await ethers.getContractFactory("Blind"); 17 | 18 | const bl = await blind.deploy(v.address); 19 | await bl.deployed(); 20 | console.log("Blind.sol deployed to:", bl.address); 21 | 22 | const a = [ 23 | "0x2c1e3db7ca8d2d6a79baffd041517bcf998238e0bf8ea3effc497928fd311eb1", 24 | "0x1a392f897b8fd49f444bc564bbdbf1e6de0f543f21c0da843cbbffa49c35f413", 25 | ]; 26 | const b = [ 27 | [ 28 | "0x06d60c0960e5131a815d8a6a47c9ae09d55adaab4bc1ec2b2ce05c8313776b22", 29 | "0x26a4709c8ddf5a0daae9e88d08f7651f38f361c3320c0f28f983d4db37817f7d", 30 | ], 31 | [ 32 | "0x28cbe868d242cc688dd02c27988aa2bca07183be2382a7655a164c501f5a995a", 33 | "0x0ec161649cb422bd543460a6c2c3ed6d5d614d2c0374080ee409740f905ca5f3", 34 | ], 35 | ]; 36 | const c = [ 37 | "0x1b884deb93e61579d476150218f33c3e9d753e62c348533df84ae43af8523614", 38 | "0x222d4e918221b586d3d0a721cbfd74d72ee44c854a8016e5792cd0ce30d72e4b", 39 | ]; 40 | const input = [ 41 | "0xc8430c6464e64ddda07a9b863d8881", 42 | "0x01cd0da2c4ae4218b0cade824b613b37", 43 | "0x62e5c346b31c47a050182d2eafd848", 44 | "0x0618669ce3a3538eaddc8d6ced08b9", 45 | "0xd75cdc8d790c81ab9c23625464a414", 46 | "0x011954a4b6d45c95fa48f63ffec9f0ad", 47 | "0x01e075c1b0cf7069eac655ee53f6cb80", 48 | "0x60409af02b53bf34965950c557a044", 49 | "0x16b77d2e3917ea5af4e7363a68cffe", 50 | "0x7283ffb204c691aaf1bce0ac279328", 51 | "0x01785e45f8b6da078bb330084a557dbd", 52 | "0x3520db147c498f546c95853efc64f3", 53 | "0x01a652f8aede242fe83a7af1091cc560", 54 | "0x0177e578067b3fc1873b4838e2597d79", 55 | "0x82c7f533459aff65a02e6f635fae51", 56 | "0xa00bcf4e84dc9a972a8eab541dfb33", 57 | "0xdbbace12b0ce3ef3dcde63800d8b", 58 | "0x0acba2baa02f59d8a3d738d97008f909fb92e9fb", 59 | "0x62", 60 | "0x65", 61 | "0x72", 62 | "0x6b", 63 | "0x65", 64 | "0x6c", 65 | "0x65", 66 | "0x79", 67 | "0x00", 68 | "0x00", 69 | "0x00", 70 | "0x00", 71 | "0x00", 72 | "0x00", 73 | "0x00", 74 | "0x00", 75 | "0x00", 76 | "0x00", 77 | "0x00", 78 | "0x00", 79 | "0x00", 80 | "0x00", 81 | "0x00", 82 | "0x00", 83 | "0x00", 84 | "0x00", 85 | "0x00", 86 | "0x00", 87 | "0x00", 88 | "0x00", 89 | ]; 90 | 91 | const add = await bl.add(a, b, c, input); 92 | console.log(add); 93 | 94 | const res = await bl.get("0x0acba2baa02f59d8a3d738d97008f909fb92e9fb"); 95 | console.log(res); 96 | 97 | expect(res).to.equal("berkeley"); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /scripts/helpers/sshFormat.ts: -------------------------------------------------------------------------------- 1 | import { stringToBytes } from "./binaryFormat"; 2 | import atob from "atob"; 3 | 4 | function bytesToInt(bytes: Uint8Array) { 5 | return bytes[3] + 256 * (bytes[2] + 256 * (bytes[1] + 256 * bytes[0])); 6 | } 7 | 8 | function unpackSshBytes(bytes: Uint8Array, numStrings: number) { 9 | const result = []; 10 | let offset = 0; 11 | for (let i = 0; i < numStrings; ++i) { 12 | const lenBytes = bytes.slice(offset, offset + 4); 13 | // first 4 bytes is length in big endian 14 | const len = bytesToInt(lenBytes); 15 | const str = bytes.slice(offset + 4, offset + 4 + len); 16 | result.push(str); 17 | offset += 4 + len; 18 | } 19 | if (offset !== bytes.length) { 20 | throw new Error("Error unpacking; offset is not at end of bytes"); 21 | } 22 | return result; 23 | } 24 | 25 | export function getRawSignature(signature: string) { 26 | // 0. strip out "armor" headers (lines that start with -----) 27 | // 1. base64 -d 28 | // 2. skipping first 10 bytes (for MAGIC_PREAMBLE and SIG_VERSION), unpack into 5 strings: publickey, namespace, reserved, hash_algorithm, signature 29 | // 3. convert public key and signature to bignum 30 | 31 | // #define MAGIC_PREAMBLE "SSHSIG" 32 | // byte[6] MAGIC_PREAMBLE 33 | // string namespace 34 | // string reserved 35 | // string hash_algorithm 36 | // string H(payload1) 37 | 38 | const encodedPart = signature 39 | .split("\n") 40 | .filter((line) => !line.includes("SSH SIGNATURE")) 41 | .join(""); 42 | const bytes = stringToBytes(atob(encodedPart)); 43 | const strings = unpackSshBytes(bytes.slice(10), 5); 44 | const [pubKeyEncoded, namespace, , hash_algorithm, rawSignatureEncoded] = strings; 45 | 46 | // decrypt pub key https://github.dev/openssh/openssh-portable/blob/4bbe815ba974b4fd89cc3fc3e3ef1be847a0befe/sshsig.c#L203-L204 47 | // https://github.dev/openssh/openssh-portable/blob/4bbe815ba974b4fd89cc3fc3e3ef1be847a0befe/sshkey.c#L828-L829 48 | const pubKeyParts = unpackSshBytes(pubKeyEncoded, 3); 49 | const pubSSHKeyStr = Array.prototype.map 50 | .call(pubKeyEncoded, function (ch) { 51 | return String.fromCharCode(ch); 52 | }) 53 | .join(""); 54 | // decrypt signature https://github.dev/openssh/openssh-portable/blob/4bbe815ba974b4fd89cc3fc3e3ef1be847a0befe/ssh-rsa.c#L223-L224 55 | const rawSigParts = unpackSshBytes(rawSignatureEncoded, 2); 56 | const rawSignature = rawSigParts[1]; 57 | return { 58 | rawSignature, 59 | namespace, 60 | hash_algorithm, 61 | pubKeyEncoded, 62 | pubKeyParts, 63 | pubSSHKeyStr, 64 | } as const; 65 | } 66 | 67 | export function sshSignatureToPubKey(signature: string) { 68 | try { 69 | const encodedPart = signature 70 | .split("\n") 71 | .filter((line) => !line.includes("SSH SIGNATURE")) 72 | .join(""); 73 | const bytes = stringToBytes(atob(encodedPart)); 74 | const strings = unpackSshBytes(bytes.slice(10), 5); 75 | const [ 76 | pubKeyEncoded, 77 | // namespace, 78 | // reserved, 79 | // hash_algorithm, 80 | // rawSignatureEncoded, 81 | ] = strings; 82 | 83 | const pubKeyParts = unpackSshBytes(pubKeyEncoded, 3); 84 | 85 | const pubSSHKeyStr: string = Array.prototype.map 86 | .call(pubKeyEncoded, function (ch) { 87 | return String.fromCharCode(ch); 88 | }) 89 | .join(""); 90 | const keytype = new TextDecoder().decode(pubKeyParts[0]); 91 | if (keytype !== "ssh-rsa") { 92 | return "ERROR GRR"; 93 | } 94 | return keytype + " " + btoa(pubSSHKeyStr); 95 | } catch (e) { 96 | return ""; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /circuits/base64.circom: -------------------------------------------------------------------------------- 1 | pragma circom 2.0.3; 2 | 3 | include "../node_modules/circomlib/circuits/comparators.circom"; 4 | 5 | // http://0x80.pl/notesen/2016-01-17-sse-base64-decoding.html#vector-lookup-base 6 | template Base64Lookup() { 7 | signal input in; 8 | signal output out; 9 | 10 | // ['A', 'Z'] 11 | component le_Z = LessThan(8); 12 | le_Z.in[0] <== in; 13 | le_Z.in[1] <== 90+1; 14 | 15 | component ge_A = GreaterThan(8); 16 | ge_A.in[0] <== in; 17 | ge_A.in[1] <== 65-1; 18 | 19 | signal range_AZ <== ge_A.out * le_Z.out; 20 | signal sum_AZ <== range_AZ * (in - 65); 21 | 22 | // ['a', 'z'] 23 | component le_z = LessThan(8); 24 | le_z.in[0] <== in; 25 | le_z.in[1] <== 122+1; 26 | 27 | component ge_a = GreaterThan(8); 28 | ge_a.in[0] <== in; 29 | ge_a.in[1] <== 97-1; 30 | 31 | signal range_az <== ge_a.out * le_z.out; 32 | signal sum_az <== sum_AZ + range_az * (in - 71); 33 | 34 | // ['0', '9'] 35 | component le_9 = LessThan(8); 36 | le_9.in[0] <== in; 37 | le_9.in[1] <== 57+1; 38 | 39 | component ge_0 = GreaterThan(8); 40 | ge_0.in[0] <== in; 41 | ge_0.in[1] <== 48-1; 42 | 43 | signal range_09 <== ge_0.out * le_9.out; 44 | signal sum_09 <== sum_az + range_09 * (in + 4); 45 | 46 | // '+' 47 | component equal_plus = IsZero(); 48 | equal_plus.in <== in - 43; 49 | signal sum_plus <== sum_09 + equal_plus.out * (in + 19); 50 | 51 | // // '.' 52 | // component equal_period = IsZero(); 53 | // equal_period.in <== in - 46; 54 | // signal sum_period <== sum_plus + equal_period.out * (in - 30); 55 | 56 | // '/' 57 | component equal_slash = IsZero(); 58 | equal_slash.in <== in - 47; 59 | signal sum_slash <== sum_plus + equal_slash.out * (in + 16); 60 | 61 | out <== sum_slash; 62 | } 63 | 64 | template Base64Decode(N) { 65 | var M = 4*((N+2)\3); 66 | signal input in[M]; 67 | signal output out[N]; 68 | 69 | component bits_in[M\4][4]; 70 | component bits_out[M\4][3]; 71 | component translate[M\4][4]; 72 | 73 | var idx = 0; 74 | for (var i = 0; i < M; i += 4) { 75 | for (var j = 0; j < 3; j++) { 76 | bits_out[i\4][j] = Bits2Num(8); 77 | } 78 | 79 | for (var j = 0; j < 4; j++) { 80 | bits_in[i\4][j] = Num2Bits(6); 81 | translate[i\4][j] = Base64Lookup(); 82 | translate[i\4][j].in <== in[i+j]; 83 | translate[i\4][j].out ==> bits_in[i\4][j].in; 84 | } 85 | 86 | // Do the re-packing from four 6-bit words to three 8-bit words. 87 | for (var j = 0; j < 6; j++) { 88 | bits_out[i\4][0].in[j+2] <== bits_in[i\4][0].out[j]; 89 | } 90 | bits_out[i\4][0].in[0] <== bits_in[i\4][1].out[4]; 91 | bits_out[i\4][0].in[1] <== bits_in[i\4][1].out[5]; 92 | 93 | for (var j = 0; j < 4; j++) { 94 | bits_out[i\4][1].in[j+4] <== bits_in[i\4][1].out[j]; 95 | } 96 | for (var j = 0; j < 4; j++) { 97 | bits_out[i\4][1].in[j] <== bits_in[i\4][2].out[j+2]; 98 | } 99 | 100 | bits_out[i\4][2].in[6] <== bits_in[i\4][2].out[0]; 101 | bits_out[i\4][2].in[7] <== bits_in[i\4][2].out[1]; 102 | for (var j = 0; j < 6; j++) { 103 | bits_out[i\4][2].in[j] <== bits_in[i\4][3].out[j]; 104 | } 105 | 106 | for (var j = 0; j < 3; j++) { 107 | if (idx+j < N) { 108 | out[idx+j] <== bits_out[i\4][j].out; 109 | } 110 | } 111 | idx += 3; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /circuits/timestamp.circom: -------------------------------------------------------------------------------- 1 | pragma circom 2.1.4; 2 | 3 | include "@zk-email/zk-regex-circom/circuits/regex_helpers.circom"; 4 | 5 | template Timestamp(max_json_bytes) { 6 | signal input msg[max_json_bytes]; 7 | signal output out; 8 | 9 | var num_bytes = max_json_bytes + 1; 10 | signal in[num_bytes]; 11 | in[0] <== 128; // \x80 (sentinel for first character in string) 12 | for (var i = 0; i < max_json_bytes; i++) { 13 | in[i+1] <== msg[i]; 14 | } 15 | 16 | component eq[6][num_bytes]; 17 | component lt[4][num_bytes]; 18 | component and[10][num_bytes]; 19 | component multi_or[1][num_bytes]; 20 | signal states[num_bytes+1][8]; 21 | 22 | for (var i = 0; i < num_bytes; i++) { 23 | states[i][0] <== 1; 24 | } 25 | for (var i = 1; i < 8; i++) { 26 | states[0][i] <== 0; 27 | } 28 | 29 | for (var i = 0; i < num_bytes; i++) { 30 | lt[0][i] = LessThan(8); 31 | lt[0][i].in[0] <== 47; 32 | lt[0][i].in[1] <== in[i]; 33 | lt[1][i] = LessThan(8); 34 | lt[1][i].in[0] <== in[i]; 35 | lt[1][i].in[1] <== 58; 36 | and[0][i] = AND(); 37 | and[0][i].a <== lt[0][i].out; 38 | and[0][i].b <== lt[1][i].out; 39 | and[1][i] = AND(); 40 | and[1][i].a <== states[i][1]; 41 | and[1][i].b <== and[0][i].out; 42 | lt[2][i] = LessThan(8); 43 | lt[2][i].in[0] <== 47; 44 | lt[2][i].in[1] <== in[i]; 45 | lt[3][i] = LessThan(8); 46 | lt[3][i].in[0] <== in[i]; 47 | lt[3][i].in[1] <== 58; 48 | and[2][i] = AND(); 49 | and[2][i].a <== lt[2][i].out; 50 | and[2][i].b <== lt[3][i].out; 51 | and[3][i] = AND(); 52 | and[3][i].a <== states[i][7]; 53 | and[3][i].b <== and[2][i].out; 54 | multi_or[0][i] = MultiOR(2); 55 | multi_or[0][i].in[0] <== and[1][i].out; 56 | multi_or[0][i].in[1] <== and[3][i].out; 57 | states[i+1][1] <== multi_or[0][i].out; 58 | eq[0][i] = IsEqual(); 59 | eq[0][i].in[0] <== in[i]; 60 | eq[0][i].in[1] <== 34; 61 | and[4][i] = AND(); 62 | and[4][i].a <== states[i][0]; 63 | and[4][i].b <== eq[0][i].out; 64 | states[i+1][2] <== and[4][i].out; 65 | eq[1][i] = IsEqual(); 66 | eq[1][i].in[0] <== in[i]; 67 | eq[1][i].in[1] <== 101; 68 | and[5][i] = AND(); 69 | and[5][i].a <== states[i][2]; 70 | and[5][i].b <== eq[1][i].out; 71 | states[i+1][3] <== and[5][i].out; 72 | eq[2][i] = IsEqual(); 73 | eq[2][i].in[0] <== in[i]; 74 | eq[2][i].in[1] <== 120; 75 | and[6][i] = AND(); 76 | and[6][i].a <== states[i][3]; 77 | and[6][i].b <== eq[2][i].out; 78 | states[i+1][4] <== and[6][i].out; 79 | eq[3][i] = IsEqual(); 80 | eq[3][i].in[0] <== in[i]; 81 | eq[3][i].in[1] <== 112; 82 | and[7][i] = AND(); 83 | and[7][i].a <== states[i][4]; 84 | and[7][i].b <== eq[3][i].out; 85 | states[i+1][5] <== and[7][i].out; 86 | eq[4][i] = IsEqual(); 87 | eq[4][i].in[0] <== in[i]; 88 | eq[4][i].in[1] <== 34; 89 | and[8][i] = AND(); 90 | and[8][i].a <== states[i][5]; 91 | and[8][i].b <== eq[4][i].out; 92 | states[i+1][6] <== and[8][i].out; 93 | eq[5][i] = IsEqual(); 94 | eq[5][i].in[0] <== in[i]; 95 | eq[5][i].in[1] <== 58; 96 | and[9][i] = AND(); 97 | and[9][i].a <== states[i][6]; 98 | and[9][i].b <== eq[5][i].out; 99 | states[i+1][7] <== and[9][i].out; 100 | } 101 | 102 | signal final_state_sum[num_bytes+1]; 103 | final_state_sum[0] <== states[0][7]; 104 | for (var i = 1; i <= num_bytes; i++) { 105 | final_state_sum[i] <== final_state_sum[i-1] + states[i][7]; 106 | } 107 | out <== final_state_sum[num_bytes]; 108 | 109 | // reveals (cut the last character) 110 | signal output reveal[max_json_bytes]; 111 | for (var i = 0; i < max_json_bytes; i++) { 112 | reveal[i] <== in[i] * states[i+1][1]; 113 | } 114 | } -------------------------------------------------------------------------------- /circuits/sha.circom: -------------------------------------------------------------------------------- 1 | pragma circom 2.0.3; 2 | 3 | include "../node_modules/circomlib/circuits/bitify.circom"; 4 | include "./sha256general.circom"; 5 | include "./sha256partial.circom"; 6 | 7 | template Sha256Bytes(max_num_bytes) { 8 | signal input in_padded[max_num_bytes]; 9 | signal input in_len_padded_bytes; 10 | signal output out[256]; 11 | 12 | var num_bits = max_num_bytes * 8; 13 | component sha = Sha256General(num_bits); 14 | 15 | component bytes[max_num_bytes]; 16 | for (var i = 0; i < max_num_bytes; i++) { 17 | bytes[i] = Num2Bits(8); 18 | bytes[i].in <== in_padded[i]; 19 | for (var j = 0; j < 8; j++) { 20 | sha.paddedIn[i*8+j] <== bytes[i].out[7-j]; 21 | } 22 | } 23 | sha.in_len_padded_bits <== in_len_padded_bytes * 8; 24 | 25 | for (var i = 0; i < 256; i++) { 26 | out[i] <== sha.out[i]; 27 | } 28 | } 29 | 30 | template Sha256BytesPartial(max_num_bytes) { 31 | signal input in_padded[max_num_bytes]; 32 | signal input in_len_padded_bytes; 33 | signal input pre_hash[32]; 34 | signal output out[256]; 35 | 36 | var num_bits = max_num_bytes * 8; 37 | component sha = Sha256Partial(num_bits); 38 | 39 | component bytes[max_num_bytes]; 40 | for (var i = 0; i < max_num_bytes; i++) { 41 | bytes[i] = Num2Bits(8); 42 | bytes[i].in <== in_padded[i]; 43 | for (var j = 0; j < 8; j++) { 44 | sha.paddedIn[i*8+j] <== bytes[i].out[7-j]; 45 | } 46 | } 47 | sha.in_len_padded_bits <== in_len_padded_bytes * 8; 48 | 49 | component states[32]; 50 | for (var i = 0; i < 32; i++) { 51 | states[i] = Num2Bits(8); 52 | states[i].in <== pre_hash[i]; 53 | for (var j = 0; j < 8; j++) { 54 | sha.pre_state[8*i+j] <== states[i].out[7-j]; 55 | } 56 | } 57 | 58 | for (var i = 0; i < 256; i++) { 59 | out[i] <== sha.out[i]; 60 | } 61 | } 62 | 63 | // Takes in 2^(8 * 31)-sized integers, not bytes, to save calldata. n is usually 31. 64 | // max_num_n_bytes is the number of n-byte size inputs we have. expected to be max_num_bytes / (n + 1) 65 | // template Sha256NBytes(max_num_n_bytes, n) { 66 | // assert(1 << log_ceil(max_num_n_bytes) == max_num_n_bytes); // max_num_n_bytes is a power of 2 67 | // assert(1 << log_ceil(n+1) == n+1); // n+1 is a power of 2 68 | 69 | // // assert(1 << log_ceil(in_len_padded_bytes) == in_len_padded_bytes); // in_len_padded_bytes is a power of 2 70 | // // assert(in_len_padded_bytes <= max_num_n_bytes * n) 71 | // signal input in_padded[max_num_n_bytes]; 72 | // signal input in_len_padded_bytes; // Keep this in bytes for now. Can make n_bytes later. 73 | // signal output out[256]; 74 | 75 | // var num_bits = max_num_n_bytes * 8 * (n + 1); // makes it a power of 2, though we waste 3% of constraints 76 | // assert(1 << log_ceil(num_bits) == num_bits); // num_bits is a power of 2 77 | 78 | // component sha = Sha256General(num_bits); 79 | 80 | // component n_bytes[max_num_n_bytes]; 81 | // for (var i = 0; i < max_num_n_bytes; i++) { 82 | // n_bytes[i] = Num2Bits(8 * n); 83 | // n_bytes[i].in <== in_padded[i]; 84 | // for (var k = 0; k < n; k++){ 85 | // for (var j = 0; j < 8; j++) { 86 | // sha.paddedIn[i * 8 * n + k * 8 + j] <== n_bytes[i].out[k * 8 + 7 - j]; // Big/little endian handled here 87 | // } 88 | // } 89 | // } 90 | // for (var j = 0; j < 8; j++) { 91 | // sha.paddedIn[max_num_n_bytes * 8 * n + j] <== 0; 92 | // } 93 | 94 | // sha.in_len_padded_bits <== in_len_padded_bytes * 8; 95 | 96 | // for (var i = 0; i < 256; i++) { 97 | // out[i] <== sha.out[i]; 98 | // } 99 | // } 100 | 101 | // component main { public [ in_padded, in_len_padded_bytes ] } = Sha256Bytes(448); 102 | // Note that Sha256NBytes is an unnecesary optimization. 103 | // component main { public [ in_padded, in_len_padded_bytes ] } = Sha256NBytes(64, 31); 104 | -------------------------------------------------------------------------------- /circuits/sha256partial.circom: -------------------------------------------------------------------------------- 1 | pragma circom 2.0.3; 2 | 3 | include "../node_modules/circomlib/circuits/sha256/constants.circom"; 4 | include "../node_modules/circomlib/circuits/sha256/sha256compression.circom"; 5 | include "../node_modules/circomlib/circuits/comparators.circom"; 6 | include "./utils.circom"; 7 | 8 | // Completing the sha256 hash given a pre-computed state and additional data 9 | template Sha256Partial(maxBitsPadded) { 10 | // maxBitsPadded must be a multiple of 512, and the bit circuits in this file are limited to 15 so must be raised if the message is longer. 11 | assert(maxBitsPadded % 512 == 0); 12 | var maxBitsPaddedBits = log2_ceil(maxBitsPadded); 13 | assert(2 ** maxBitsPaddedBits > maxBitsPadded); 14 | 15 | // Note that maxBitsPadded = maxBits + 64 16 | signal input paddedIn[maxBitsPadded]; 17 | signal input pre_state[256]; 18 | signal output out[256]; 19 | signal input in_len_padded_bits; // This is the padded length of the message pre-hash. 20 | 21 | signal inBlockIndex; 22 | 23 | var i; 24 | var k; 25 | var j; 26 | var maxBlocks; 27 | var bitsLastBlock; 28 | maxBlocks = (maxBitsPadded\512); 29 | var maxBlocksBits = log2_ceil(maxBlocks); 30 | assert(2 ** maxBlocksBits > maxBlocks); 31 | 32 | inBlockIndex <-- (in_len_padded_bits >> 9); 33 | in_len_padded_bits === inBlockIndex * 512; 34 | 35 | // These verify we pass in a valid number of bits to the SHA256 compression circuit. 36 | component bitLengthVerifier = LessEqThan(maxBitsPaddedBits); // todo verify the length passed in is less than nbits. note that maxBitsPaddedBits can likely be lowered or made it a fn of maxbits 37 | bitLengthVerifier.in[0] <== in_len_padded_bits; 38 | bitLengthVerifier.in[1] <== maxBitsPadded; 39 | bitLengthVerifier.out === 1; 40 | 41 | component ha0 = H(0); 42 | component hb0 = H(1); 43 | component hc0 = H(2); 44 | component hd0 = H(3); 45 | component he0 = H(4); 46 | component hf0 = H(5); 47 | component hg0 = H(6); 48 | component hh0 = H(7); 49 | 50 | component sha256compression[maxBlocks]; 51 | 52 | for (i=0; i)` (that does not support generic brackets or character ranges, only the limited syntax in https://zkregex.com/min_dfa) in `regex_to_circom/regex_to_dfa.js`. The top line of min_dfa tool corresponds to the "raw_regex", and the second line corresponds to the expanded "regex". 14 | 15 | Then run `npx tsx regex_to_dfa.js` to make sure that it compiles and `tsx` is installed, and then remove all `console.log` statements except for the last line, and finally run `python3 gen.py`. 16 | 17 | This will output a circom body. Wrap it the same way for instance circuits/regexes/from_regex.circom is written. Note that states in the zkregex [min_dfa visualizer](zkregex.com/min_dfa) are now 0 indexed (previous to Apr 2023 you had to subtract 1 from the indexes that showed up to match the circom, now it is the same). 18 | 19 | Note that if your regex uses `^` at the start to mean sentinel starting character, you have to edit the resulting regex.circom file to manually change `94` (ascii code of ^) to `128` (manually inserted sentinel character meaning start, you'll see it defined as the 0th character of the string). 20 | 21 | We will soon have a website [WIP](https://frontend-zk-regex.vercel.app/) that automatically does this. If you'd like to make this process simpler, cleaner, and less hacky, we'd recommend making a PR here or to the zk-regex library (which is a bit out of date regex-string wise and match group-wise). 22 | 23 | ## Notes 24 | 25 | states[i+1][j] means that there was a character at msg[i] which led to the transition to state j. 26 | 27 | This means that reveal for index i should be looking at state of index i+1. 28 | 29 | Note that ^ has to be manually replaced with \x80 in the circom regex. 30 | 31 | ## Halo2 32 | 33 | You can use the compiled halo2_regex_lookup.txt file as input to the https://github.com/zk-email-verify/halo2-regex/ library, which will generate a regex circuit in halo2 instead. That circuit is more efficient than this one for large inputs for use for fast clientside proofs that require privacy. 34 | 35 | ## Some regexes 36 | 37 | There are more in the regex section of the top level zk-email-verify README. Here are some examples however, for instance for from/subject/to order-free extraction: 38 | 39 | 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))+ 40 | 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))+) 41 | -------------------------------------------------------------------------------- /circuits/jwt_type_regex.circom: -------------------------------------------------------------------------------- 1 | pragma circom 2.1.5; 2 | 3 | include "@zk-email/zk-regex-circom/circuits/regex_helpers.circom"; 4 | 5 | /* tests whether jwt type detected */ 6 | template MessageType(max_json_bytes) { 7 | signal input msg[max_json_bytes]; 8 | signal output out; 9 | 10 | var num_bytes = max_json_bytes; 11 | signal in[num_bytes]; 12 | for (var i = 0; i < num_bytes; i++) { 13 | in[i] <== msg[i]; 14 | } 15 | 16 | component eq[11][num_bytes]; 17 | component and[11][num_bytes]; 18 | signal states[num_bytes+1][12]; 19 | 20 | for (var i = 0; i < num_bytes; i++) { 21 | states[i][0] <== 1; 22 | } 23 | for (var i = 1; i < 12; i++) { 24 | states[0][i] <== 0; 25 | } 26 | 27 | for (var i = 0; i < num_bytes; i++) { 28 | eq[0][i] = IsEqual(); 29 | eq[0][i].in[0] <== in[i]; 30 | eq[0][i].in[1] <== 34; 31 | and[0][i] = AND(); 32 | and[0][i].a <== states[i][0]; 33 | and[0][i].b <== eq[0][i].out; 34 | states[i+1][1] <== and[0][i].out; 35 | eq[1][i] = IsEqual(); 36 | eq[1][i].in[0] <== in[i]; 37 | eq[1][i].in[1] <== 116; 38 | and[1][i] = AND(); 39 | and[1][i].a <== states[i][1]; 40 | and[1][i].b <== eq[1][i].out; 41 | states[i+1][2] <== and[1][i].out; 42 | eq[2][i] = IsEqual(); 43 | eq[2][i].in[0] <== in[i]; 44 | eq[2][i].in[1] <== 121; 45 | and[2][i] = AND(); 46 | and[2][i].a <== states[i][2]; 47 | and[2][i].b <== eq[2][i].out; 48 | states[i+1][3] <== and[2][i].out; 49 | eq[3][i] = IsEqual(); 50 | eq[3][i].in[0] <== in[i]; 51 | eq[3][i].in[1] <== 112; 52 | and[3][i] = AND(); 53 | and[3][i].a <== states[i][3]; 54 | and[3][i].b <== eq[3][i].out; 55 | states[i+1][4] <== and[3][i].out; 56 | eq[4][i] = IsEqual(); 57 | eq[4][i].in[0] <== in[i]; 58 | eq[4][i].in[1] <== 34; 59 | and[4][i] = AND(); 60 | and[4][i].a <== states[i][4]; 61 | and[4][i].b <== eq[4][i].out; 62 | states[i+1][5] <== and[4][i].out; 63 | eq[5][i] = IsEqual(); 64 | eq[5][i].in[0] <== in[i]; 65 | eq[5][i].in[1] <== 58; 66 | and[5][i] = AND(); 67 | and[5][i].a <== states[i][5]; 68 | and[5][i].b <== eq[5][i].out; 69 | states[i+1][6] <== and[5][i].out; 70 | eq[6][i] = IsEqual(); 71 | eq[6][i].in[0] <== in[i]; 72 | eq[6][i].in[1] <== 34; 73 | and[6][i] = AND(); 74 | and[6][i].a <== states[i][6]; 75 | and[6][i].b <== eq[6][i].out; 76 | states[i+1][7] <== and[6][i].out; 77 | eq[7][i] = IsEqual(); 78 | eq[7][i].in[0] <== in[i]; 79 | eq[7][i].in[1] <== 74; 80 | and[7][i] = AND(); 81 | and[7][i].a <== states[i][7]; 82 | and[7][i].b <== eq[7][i].out; 83 | states[i+1][8] <== and[7][i].out; 84 | eq[8][i] = IsEqual(); 85 | eq[8][i].in[0] <== in[i]; 86 | eq[8][i].in[1] <== 87; 87 | and[8][i] = AND(); 88 | and[8][i].a <== states[i][8]; 89 | and[8][i].b <== eq[8][i].out; 90 | states[i+1][9] <== and[8][i].out; 91 | eq[9][i] = IsEqual(); 92 | eq[9][i].in[0] <== in[i]; 93 | eq[9][i].in[1] <== 84; 94 | and[9][i] = AND(); 95 | and[9][i].a <== states[i][9]; 96 | and[9][i].b <== eq[9][i].out; 97 | states[i+1][10] <== and[9][i].out; 98 | eq[10][i] = IsEqual(); 99 | eq[10][i].in[0] <== in[i]; 100 | eq[10][i].in[1] <== 34; 101 | and[10][i] = AND(); 102 | and[10][i].a <== states[i][10]; 103 | and[10][i].b <== eq[10][i].out; 104 | states[i+1][11] <== and[10][i].out; 105 | } 106 | 107 | signal final_state_sum[num_bytes+1]; 108 | final_state_sum[0] <== states[0][11]; 109 | for (var i = 1; i <= num_bytes; i++) { 110 | final_state_sum[i] <== final_state_sum[i-1] + states[i][11]; 111 | } 112 | out <== final_state_sum[num_bytes]; 113 | } 114 | -------------------------------------------------------------------------------- /scripts/helpers/binaryFormat.ts: -------------------------------------------------------------------------------- 1 | import { CIRCOM_BIGINT_N, CIRCOM_BIGINT_K } from "./constants"; 2 | 3 | export function bytesToString(bytes: Uint8Array): string { 4 | return new TextDecoder().decode(bytes); 5 | } 6 | 7 | export function stringToBytes(str: string) { 8 | const encodedText = new TextEncoder().encode(str); 9 | const toReturn = Uint8Array.from(str, (x) => x.charCodeAt(0)); 10 | const buf = Buffer.from(str, "utf8"); 11 | return toReturn; 12 | // TODO: Check encoding mismatch if the proof doesnt work 13 | // Note that our custom encoding function maps (239, 191, 189) -> (253) 14 | // Note that our custom encoding function maps (207, 181) -> (245) 15 | // throw Error( 16 | // "TextEncoder does not match string2bytes function" + 17 | // "\n" + 18 | // str + 19 | // "\n" + 20 | // buf + 21 | // "\n" + 22 | // Uint8Array.from(buf) + 23 | // "\n" + 24 | // JSON.stringify(encodedText) + 25 | // "\n" + 26 | // JSON.stringify(toReturn) 27 | // ); 28 | } 29 | 30 | export function bufferToUint8Array(buf: Buffer): Uint8Array { 31 | const ab = new ArrayBuffer(buf.length); 32 | const view = new Uint8Array(ab); 33 | for (let i = 0; i < buf.length; ++i) { 34 | view[i] = buf[i]; 35 | } 36 | return Uint8Array.from(view); 37 | } 38 | 39 | export function bufferToString(buf: Buffer): String { 40 | let intermediate = bufferToUint8Array(buf); 41 | return bytesToString(intermediate); 42 | } 43 | 44 | export function bytesToBigInt(bytes: Uint8Array) { 45 | let res = 0n; 46 | for (let i = 0; i < bytes.length; ++i) { 47 | res = (res << 8n) + BigInt(bytes[i]); 48 | } 49 | return res; 50 | } 51 | 52 | export function toCircomBigIntBytes(num: BigInt | bigint) { 53 | const res = []; 54 | const bigintNum: bigint = typeof num == "bigint" ? num : num.valueOf(); 55 | const msk = (1n << BigInt(CIRCOM_BIGINT_N)) - 1n; 56 | for (let i = 0; i < CIRCOM_BIGINT_K; ++i) { 57 | res.push(((bigintNum >> BigInt(i * CIRCOM_BIGINT_N)) & msk).toString()); 58 | } 59 | return res; 60 | } 61 | 62 | // https://stackoverflow.com/a/69585881 63 | const HEX_STRINGS = "0123456789abcdef"; 64 | const MAP_HEX = { 65 | 0: 0, 66 | 1: 1, 67 | 2: 2, 68 | 3: 3, 69 | 4: 4, 70 | 5: 5, 71 | 6: 6, 72 | 7: 7, 73 | 8: 8, 74 | 9: 9, 75 | a: 10, 76 | b: 11, 77 | c: 12, 78 | d: 13, 79 | e: 14, 80 | f: 15, 81 | A: 10, 82 | B: 11, 83 | C: 12, 84 | D: 13, 85 | E: 14, 86 | F: 15, 87 | } as const; 88 | 89 | // Fast Uint8Array to hex 90 | export function toHex(bytes: Uint8Array): string { 91 | return Array.from(bytes || []) 92 | .map((b) => HEX_STRINGS[b >> 4] + HEX_STRINGS[b & 15]) 93 | .join(""); 94 | } 95 | 96 | // Mimics Buffer.from(x, 'hex') logic 97 | // Stops on first non-hex string and returns 98 | // https://github.com/nodejs/node/blob/v14.18.1/src/string_bytes.cc#L246-L261 99 | export function fromHex(hexString: string): Uint8Array { 100 | let hexStringTrimmed: string = hexString; 101 | if(hexString[0] === "0" && hexString[1] === "x") { 102 | hexStringTrimmed = hexString.slice(2); 103 | } 104 | const bytes = new Uint8Array(Math.floor((hexStringTrimmed || "").length / 2)); 105 | let i; 106 | for (i = 0; i < bytes.length; i++) { 107 | const a = MAP_HEX[hexStringTrimmed[i * 2] as keyof typeof MAP_HEX]; 108 | const b = MAP_HEX[hexStringTrimmed[i * 2 + 1] as keyof typeof MAP_HEX]; 109 | if (a === undefined || b === undefined) { 110 | break; 111 | } 112 | bytes[i] = (a << 4) | b; 113 | } 114 | return i === bytes.length ? bytes : bytes.slice(0, i); 115 | } 116 | 117 | export function packedNBytesToString(packedBytes: bigint[], n: number = 7): string { 118 | let chars: number[] = []; 119 | for (let i = 0; i < packedBytes.length; i++) { 120 | for (var k = 0n; k < n; k++) { 121 | chars.push(Number((packedBytes[i] >> (k * 8n)) % 256n)); 122 | } 123 | } 124 | return bytesToString(Uint8Array.from(chars)); 125 | } 126 | 127 | 128 | export function packBytesIntoNBytes(messagePaddedRaw: Uint8Array | string, n = 7): Array { 129 | const messagePadded: Uint8Array = typeof messagePaddedRaw === "string" ? stringToBytes(messagePaddedRaw) : messagePaddedRaw; 130 | let output: Array = []; 131 | for (let i = 0; i < messagePadded.length; i++) { 132 | if (i % n === 0) { 133 | output.push(0n); 134 | } 135 | const j = (i / n) | 0; 136 | console.assert(j === output.length - 1, "Not editing the index of the last element -- packing loop invariants bug!"); 137 | output[j] += BigInt(messagePadded[i]) << BigInt((i % n) * 8); 138 | } 139 | return output; 140 | } 141 | // Usage: let in_padded_n_bytes = packBytesIntoNBytes(messagePadded, 7).map((x) => x.toString()); // Packed into 7 byte signals 142 | -------------------------------------------------------------------------------- /circuits/fp.circom: -------------------------------------------------------------------------------- 1 | pragma circom 2.0.3; 2 | 3 | include "../node_modules/circomlib/circuits/bitify.circom"; 4 | include "../node_modules/circomlib/circuits/comparators.circom"; 5 | include "../node_modules/circomlib/circuits/sign.circom"; 6 | include "./bigint.circom"; 7 | include "./bigint_func.circom"; 8 | 9 | // These functions operate over values in Z/Zp for some integer p (typically, 10 | // but not necessarily prime). Values are stored as standard bignums with k 11 | // chunks of n bits, but intermediate values often have "overflow" bits inside 12 | // various chunks. 13 | // 14 | // These Fp functions will always correctly generate witnesses mod p, but they 15 | // do not *check* that values are normalized to < p; they only check that 16 | // values are correct mod p. This is to save the comparison circuit. 17 | // They *will* always check for intended results mod p (soundness), but it may 18 | // not have a unique intermediate signal. 19 | // 20 | // Conversely, some templates may not be satisfiable if the input witnesses are 21 | // not < p. This does not break completeness, as honest provers will always 22 | // generate witnesses which are canonical (between 0 and p). 23 | 24 | // a * b = r mod p 25 | // a * b - p * q - r for some q 26 | template FpMul(n, k) { 27 | assert(n + n + log_ceil(k) + 2 <= 252); 28 | signal input a[k]; 29 | signal input b[k]; 30 | signal input p[k]; 31 | 32 | signal output out[k]; 33 | 34 | signal v_ab[2*k-1]; 35 | for (var x = 0; x < 2*k-1; x++) { 36 | var v_a = poly_eval(k, a, x); 37 | var v_b = poly_eval(k, b, x); 38 | v_ab[x] <== v_a * v_b; 39 | } 40 | 41 | var ab[200] = poly_interp(2*k-1, v_ab); 42 | // ab_proper has length 2*k 43 | var ab_proper[200] = getProperRepresentation(n + n + log_ceil(k), n, 2*k-1, ab); 44 | 45 | var long_div_out[2][100] = long_div(n, k, k, ab_proper, p); 46 | 47 | // Since we're only computing a*b, we know that q < p will suffice, so we 48 | // know it fits into k chunks and can do size n range checks. 49 | signal q[k]; 50 | component q_range_check[k]; 51 | signal r[k]; 52 | component r_range_check[k]; 53 | for (var i = 0; i < k; i++) { 54 | q[i] <-- long_div_out[0][i]; 55 | q_range_check[i] = Num2Bits(n); 56 | q_range_check[i].in <== q[i]; 57 | 58 | r[i] <-- long_div_out[1][i]; 59 | r_range_check[i] = Num2Bits(n); 60 | r_range_check[i].in <== r[i]; 61 | } 62 | 63 | signal v_pq_r[2*k-1]; 64 | for (var x = 0; x < 2*k-1; x++) { 65 | var v_p = poly_eval(k, p, x); 66 | var v_q = poly_eval(k, q, x); 67 | var v_r = poly_eval(k, r, x); 68 | v_pq_r[x] <== v_p * v_q + v_r; 69 | } 70 | 71 | signal v_t[2*k-1]; 72 | for (var x = 0; x < 2*k-1; x++) { 73 | v_t[x] <== v_ab[x] - v_pq_r[x]; 74 | } 75 | 76 | var t[200] = poly_interp(2*k-1, v_t); 77 | component tCheck = CheckCarryToZero(n, n + n + log_ceil(k) + 2, 2*k-1); 78 | for (var i = 0; i < 2*k-1; i++) { 79 | tCheck.in[i] <== t[i]; 80 | } 81 | 82 | for (var i = 0; i < k; i++) { 83 | out[i] <== r[i]; 84 | } 85 | } 86 | 87 | // Lifted from https://sourcegraph.com/github.com/darkforest-eth/circuits/-/blob/range_proof/circuit.circom 88 | // NB: RangeProof is inclusive. 89 | // input: field element, whose abs is claimed to be less than max_abs_value 90 | // output: none 91 | // we also want something like 4 * (abs(in) + max_abs_value) < 2 ** bits 92 | // and bits << 256 93 | // NB: RangeProof is inclusive. 94 | // input: field element, whose abs is claimed to be <= than max_abs_value 95 | // output: none 96 | // also checks that both max and abs(in) are expressible in `bits` bits 97 | template RangeProof(bits) { 98 | signal input in; 99 | signal input max_abs_value; 100 | 101 | /* check that both max and abs(in) are expressible in `bits` bits */ 102 | component n2b1 = Num2Bits(bits+1); 103 | n2b1.in <== in + (1 << bits); 104 | component n2b2 = Num2Bits(bits); 105 | n2b2.in <== max_abs_value; 106 | 107 | /* check that in + max is between 0 and 2*max */ 108 | component lowerBound = LessThan(bits+1); 109 | component upperBound = LessThan(bits+1); 110 | 111 | lowerBound.in[0] <== max_abs_value + in; 112 | lowerBound.in[1] <== 0; 113 | lowerBound.out === 0; 114 | 115 | upperBound.in[0] <== 2 * max_abs_value; 116 | upperBound.in[1] <== max_abs_value + in; 117 | upperBound.out === 0; 118 | } 119 | 120 | // input: n field elements, whose abs are claimed to be less than max_abs_value 121 | // output: none 122 | template MultiRangeProof(n, bits) { 123 | signal input in[n]; 124 | signal input max_abs_value; 125 | component rangeProofs[n]; 126 | 127 | for (var i = 0; i < n; i++) { 128 | rangeProofs[i] = RangeProof(bits); 129 | rangeProofs[i].in <== in[i]; 130 | rangeProofs[i].max_abs_value <== max_abs_value; 131 | } 132 | } 133 | 134 | template IsNegative(){ 135 | signal input in; 136 | signal output out; 137 | component n2b = Num2Bits(254); 138 | component sign = Sign(); 139 | in ==> n2b.in; 140 | for (var i = 0; i<254; i++) { 141 | n2b.out[i] ==> sign.in[i]; 142 | } 143 | sign.sign ==> out; 144 | } 145 | -------------------------------------------------------------------------------- /circuits/jwt_email_regex.circom: -------------------------------------------------------------------------------- 1 | 2 | pragma circom 2.1.5; 3 | 4 | include "@zk-email/zk-regex-circom/circuits/regex_helpers.circom"; 5 | 6 | // regex: @(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|-|.)+" 7 | template EmailDomain(msg_bytes) { 8 | signal input msg[msg_bytes]; 9 | signal output out; 10 | 11 | var num_bytes = msg_bytes+1; 12 | signal in[num_bytes]; 13 | in[0]<==255; 14 | for (var i = 0; i < msg_bytes; i++) { 15 | in[i+1] <== msg[i]; 16 | } 17 | 18 | component eq[14][num_bytes]; 19 | component lt[4][num_bytes]; 20 | component and[6][num_bytes]; 21 | component multi_or[2][num_bytes]; 22 | signal states[num_bytes+1][4]; 23 | component state_changed[num_bytes]; 24 | 25 | states[0][0] <== 1; 26 | for (var i = 1; i < 4; i++) { 27 | states[0][i] <== 0; 28 | } 29 | 30 | for (var i = 0; i < num_bytes; i++) { 31 | state_changed[i] = MultiOR(3); 32 | lt[0][i] = LessEqThan(8); 33 | lt[0][i].in[0] <== 65; 34 | lt[0][i].in[1] <== in[i]; 35 | lt[1][i] = LessEqThan(8); 36 | lt[1][i].in[0] <== in[i]; 37 | lt[1][i].in[1] <== 90; 38 | and[0][i] = AND(); 39 | and[0][i].a <== lt[0][i].out; 40 | and[0][i].b <== lt[1][i].out; 41 | lt[2][i] = LessEqThan(8); 42 | lt[2][i].in[0] <== 97; 43 | lt[2][i].in[1] <== in[i]; 44 | lt[3][i] = LessEqThan(8); 45 | lt[3][i].in[0] <== in[i]; 46 | lt[3][i].in[1] <== 122; 47 | and[1][i] = AND(); 48 | and[1][i].a <== lt[2][i].out; 49 | and[1][i].b <== lt[3][i].out; 50 | eq[0][i] = IsEqual(); 51 | eq[0][i].in[0] <== in[i]; 52 | eq[0][i].in[1] <== 45; 53 | eq[1][i] = IsEqual(); 54 | eq[1][i].in[0] <== in[i]; 55 | eq[1][i].in[1] <== 46; 56 | eq[2][i] = IsEqual(); 57 | eq[2][i].in[0] <== in[i]; 58 | eq[2][i].in[1] <== 48; 59 | eq[3][i] = IsEqual(); 60 | eq[3][i].in[0] <== in[i]; 61 | eq[3][i].in[1] <== 49; 62 | eq[4][i] = IsEqual(); 63 | eq[4][i].in[0] <== in[i]; 64 | eq[4][i].in[1] <== 50; 65 | eq[5][i] = IsEqual(); 66 | eq[5][i].in[0] <== in[i]; 67 | eq[5][i].in[1] <== 51; 68 | eq[6][i] = IsEqual(); 69 | eq[6][i].in[0] <== in[i]; 70 | eq[6][i].in[1] <== 52; 71 | eq[7][i] = IsEqual(); 72 | eq[7][i].in[0] <== in[i]; 73 | eq[7][i].in[1] <== 53; 74 | eq[8][i] = IsEqual(); 75 | eq[8][i].in[0] <== in[i]; 76 | eq[8][i].in[1] <== 54; 77 | eq[9][i] = IsEqual(); 78 | eq[9][i].in[0] <== in[i]; 79 | eq[9][i].in[1] <== 55; 80 | eq[10][i] = IsEqual(); 81 | eq[10][i].in[0] <== in[i]; 82 | eq[10][i].in[1] <== 56; 83 | eq[11][i] = IsEqual(); 84 | eq[11][i].in[0] <== in[i]; 85 | eq[11][i].in[1] <== 57; 86 | and[2][i] = AND(); 87 | and[2][i].a <== states[i][1]; 88 | multi_or[0][i] = MultiOR(14); 89 | multi_or[0][i].in[0] <== and[0][i].out; 90 | multi_or[0][i].in[1] <== and[1][i].out; 91 | multi_or[0][i].in[2] <== eq[0][i].out; 92 | multi_or[0][i].in[3] <== eq[1][i].out; 93 | multi_or[0][i].in[4] <== eq[2][i].out; 94 | multi_or[0][i].in[5] <== eq[3][i].out; 95 | multi_or[0][i].in[6] <== eq[4][i].out; 96 | multi_or[0][i].in[7] <== eq[5][i].out; 97 | multi_or[0][i].in[8] <== eq[6][i].out; 98 | multi_or[0][i].in[9] <== eq[7][i].out; 99 | multi_or[0][i].in[10] <== eq[8][i].out; 100 | multi_or[0][i].in[11] <== eq[9][i].out; 101 | multi_or[0][i].in[12] <== eq[10][i].out; 102 | multi_or[0][i].in[13] <== eq[11][i].out; 103 | and[2][i].b <== multi_or[0][i].out; 104 | and[3][i] = AND(); 105 | and[3][i].a <== states[i][2]; 106 | and[3][i].b <== multi_or[0][i].out; 107 | multi_or[1][i] = MultiOR(2); 108 | multi_or[1][i].in[0] <== and[2][i].out; 109 | multi_or[1][i].in[1] <== and[3][i].out; 110 | states[i+1][1] <== multi_or[1][i].out; 111 | state_changed[i].in[0] <== states[i+1][1]; 112 | eq[12][i] = IsEqual(); 113 | eq[12][i].in[0] <== in[i]; 114 | eq[12][i].in[1] <== 64; 115 | and[4][i] = AND(); 116 | and[4][i].a <== states[i][0]; 117 | and[4][i].b <== eq[12][i].out; 118 | states[i+1][2] <== and[4][i].out; 119 | state_changed[i].in[1] <== states[i+1][2]; 120 | eq[13][i] = IsEqual(); 121 | eq[13][i].in[0] <== in[i]; 122 | eq[13][i].in[1] <== 34; 123 | and[5][i] = AND(); 124 | and[5][i].a <== states[i][1]; 125 | and[5][i].b <== eq[13][i].out; 126 | states[i+1][3] <== and[5][i].out; 127 | state_changed[i].in[2] <== states[i+1][3]; 128 | states[i+1][0] <== 1 - state_changed[i].out; 129 | } 130 | 131 | component final_state_result = MultiOR(num_bytes+1); 132 | for (var i = 0; i <= num_bytes; i++) { 133 | final_state_result.in[i] <== states[i][3]; 134 | } 135 | out <== final_state_result.out; 136 | 137 | signal is_consecutive[msg_bytes+1][2]; 138 | is_consecutive[msg_bytes][1] <== 1; 139 | for (var i = 0; i < msg_bytes; i++) { 140 | is_consecutive[msg_bytes-1-i][0] <== states[num_bytes-i][3] * (1 - is_consecutive[msg_bytes-i][1]) + is_consecutive[msg_bytes-i][1]; 141 | is_consecutive[msg_bytes-1-i][1] <== state_changed[msg_bytes-i].out * is_consecutive[msg_bytes-1-i][0]; 142 | } 143 | 144 | signal is_substr0[msg_bytes][3]; 145 | signal is_reveal0[msg_bytes]; 146 | signal output reveal0[msg_bytes]; 147 | for (var i = 0; i < msg_bytes; i++) { 148 | is_substr0[i][0] <== 0; 149 | is_substr0[i][1] <== is_substr0[i][0] + states[i+1][2] * states[i+2][1]; 150 | is_substr0[i][2] <== is_substr0[i][1] + states[i+1][1] * states[i+2][1]; 151 | is_reveal0[i] <== is_substr0[i][2] * is_consecutive[i][1]; 152 | reveal0[i] <== in[i+1] * is_reveal0[i]; 153 | } 154 | } -------------------------------------------------------------------------------- /circuits/rsa.circom: -------------------------------------------------------------------------------- 1 | pragma circom 2.0.3; 2 | 3 | include "./fp.circom"; 4 | 5 | // Computes base^65537 mod modulus 6 | // Does not necessarily reduce fully mod modulus (the answer could be 7 | // too big by a multiple of modulus) 8 | template FpPow65537Mod(n, k) { 9 | signal input base[k]; 10 | // Exponent is hardcoded at 65537 11 | signal input modulus[k]; 12 | signal output out[k]; 13 | 14 | component doublers[16]; 15 | component adder = FpMul(n, k); 16 | for (var i = 0; i < 16; i++) { 17 | doublers[i] = FpMul(n, k); 18 | } 19 | 20 | for (var j = 0; j < k; j++) { 21 | adder.p[j] <== modulus[j]; 22 | for (var i = 0; i < 16; i++) { 23 | doublers[i].p[j] <== modulus[j]; 24 | } 25 | } 26 | for (var j = 0; j < k; j++) { 27 | doublers[0].a[j] <== base[j]; 28 | doublers[0].b[j] <== base[j]; 29 | } 30 | for (var i = 0; i + 1 < 16; i++) { 31 | for (var j = 0; j < k; j++) { 32 | doublers[i + 1].a[j] <== doublers[i].out[j]; 33 | doublers[i + 1].b[j] <== doublers[i].out[j]; 34 | } 35 | } 36 | for (var j = 0; j < k; j++) { 37 | adder.a[j] <== base[j]; 38 | adder.b[j] <== doublers[15].out[j]; 39 | } 40 | for (var j = 0; j < k; j++) { 41 | out[j] <== adder.out[j]; 42 | } 43 | } 44 | 45 | template RSAPad(n, k) { 46 | signal input modulus[k]; 47 | signal input base_message[k]; 48 | signal output padded_message[k]; 49 | 50 | var base_len = 408; 51 | var msg_len = 256; 52 | 53 | signal padded_message_bits[n*k]; 54 | 55 | component modulus_n2b[k]; 56 | component base_message_n2b[k]; 57 | signal modulus_bits[n*k]; 58 | signal base_message_bits[n*k]; 59 | for (var i = 0; i < k; i++) { 60 | base_message_n2b[i] = Num2Bits(n); 61 | base_message_n2b[i].in <== base_message[i]; 62 | for (var j = 0; j < n; j++) { 63 | base_message_bits[i*n+j] <== base_message_n2b[i].out[j]; 64 | } 65 | modulus_n2b[i] = Num2Bits(n); 66 | modulus_n2b[i].in <== modulus[i]; 67 | for (var j = 0; j < n; j++) { 68 | modulus_bits[i*n+j] <== modulus_n2b[i].out[j]; 69 | } 70 | } 71 | 72 | for (var i = msg_len; i < n*k; i++) { 73 | base_message_bits[i] === 0; 74 | } 75 | 76 | for (var i = 0; i < msg_len; i++) { 77 | padded_message_bits[i] <== base_message_bits[i]; 78 | } 79 | 80 | for (var i = base_len; i < base_len + 8; i++) { 81 | padded_message_bits[i] <== 0; 82 | } 83 | 84 | for (var i = msg_len; i < base_len; i++) { 85 | padded_message_bits[i] <== (0x3031300d060960864801650304020105000420 >> (i - msg_len)) & 1; 86 | } 87 | 88 | component modulus_zero[(n*k + 7 - (base_len + 8))\8]; 89 | { 90 | var modulus_prefix = 0; 91 | for (var i = n*k - 1; i >= base_len + 8; i--) { 92 | if (i+8 < n*k) { 93 | modulus_prefix += modulus_bits[i+8]; 94 | if (i % 8 == 0) { 95 | var idx = (i - (base_len + 8)) / 8; 96 | modulus_zero[idx] = IsZero(); 97 | modulus_zero[idx].in <== modulus_prefix; 98 | padded_message_bits[i] <== 1-modulus_zero[idx].out; 99 | } else { 100 | padded_message_bits[i] <== padded_message_bits[i+1]; 101 | } 102 | } else { 103 | padded_message_bits[i] <== 0; 104 | } 105 | } 106 | } 107 | 108 | // The RFC guarantees at least 8 octets of 0xff padding. 109 | assert(base_len + 8 + 65 <= n*k); 110 | for (var i = base_len + 8; i < base_len + 8 + 65; i++) { 111 | padded_message_bits[i] === 1; 112 | } 113 | 114 | component padded_message_b2n[k]; 115 | for (var i = 0; i < k; i++) { 116 | padded_message_b2n[i] = Bits2Num(n); 117 | for (var j = 0; j < n; j++) { 118 | padded_message_b2n[i].in[j] <== padded_message_bits[i*n+j]; 119 | } 120 | padded_message[i] <== padded_message_b2n[i].out; 121 | } 122 | } 123 | 124 | template RSAVerify65537(n, k) { 125 | signal input signature[k]; 126 | signal input modulus[k]; 127 | signal input base_message[k]; 128 | 129 | component padder = RSAPad(n, k); 130 | for (var i = 0; i < k; i++) { 131 | padder.modulus[i] <== modulus[i]; 132 | padder.base_message[i] <== base_message[i]; 133 | } 134 | 135 | // Check that the signature is in proper form and reduced mod modulus. 136 | component signatureRangeCheck[k]; 137 | component bigLessThan = BigLessThan(n, k); 138 | for (var i = 0; i < k; i++) { 139 | signatureRangeCheck[i] = Num2Bits(n); 140 | signatureRangeCheck[i].in <== signature[i]; 141 | bigLessThan.a[i] <== signature[i]; 142 | bigLessThan.b[i] <== modulus[i]; 143 | } 144 | bigLessThan.out === 1; 145 | 146 | component bigPow = FpPow65537Mod(n, k); 147 | for (var i = 0; i < k; i++) { 148 | bigPow.base[i] <== signature[i]; 149 | bigPow.modulus[i] <== modulus[i]; 150 | } 151 | // By construction of the padding, the padded message is necessarily 152 | // smaller than the modulus. Thus, we don't have to check that bigPow is fully reduced. 153 | for (var i = 0; i < k; i++) { 154 | log(bigPow.out[i]); 155 | } 156 | log("space"); 157 | for (var i = 0; i < k; i++) { 158 | log(padder.padded_message[i]); 159 | } 160 | for (var i = 0; i < k; i++) { 161 | bigPow.out[i] === padder.padded_message[i]; 162 | } 163 | } 164 | 165 | // component main { public [ modulus, signature ] } = RSAVerify65537(); -------------------------------------------------------------------------------- /circuits/utils.circom: -------------------------------------------------------------------------------- 1 | pragma circom 2.0.3; 2 | 3 | include "../node_modules/circomlib/circuits/bitify.circom"; 4 | include "../node_modules/circomlib/circuits/comparators.circom"; 5 | include "./fp.circom"; 6 | 7 | // returns ceil(log2(a+1)) 8 | function log2_ceil(a) { 9 | var n = a+1; 10 | var r = 0; 11 | while (n>0) { 12 | r++; 13 | n \= 2; 14 | } 15 | return r; 16 | } 17 | 18 | // Lifted from MACI https://github.com/privacy-scaling-explorations/maci/blob/v1/circuits/circom/trees/incrementalQuinTree.circom#L29 19 | // Bits is ceil(log2 choices) 20 | template QuinSelector(choices, bits) { 21 | signal input in[choices]; 22 | signal input index; 23 | signal output out; 24 | 25 | // Ensure that index < choices 26 | component lessThan = LessThan(bits); 27 | lessThan.in[0] <== index; 28 | lessThan.in[1] <== choices; 29 | lessThan.out === 1; 30 | 31 | component calcTotal = CalculateTotal(choices); 32 | component eqs[choices]; 33 | 34 | // For each item, check whether its index equals the input index. 35 | for (var i = 0; i < choices; i ++) { 36 | eqs[i] = IsEqual(); 37 | eqs[i].in[0] <== i; 38 | eqs[i].in[1] <== index; 39 | 40 | // eqs[i].out is 1 if the index matches. As such, at most one input to 41 | // calcTotal is not 0. 42 | calcTotal.nums[i] <== eqs[i].out * in[i]; 43 | } 44 | 45 | // Returns 0 + 0 + ... + item 46 | out <== calcTotal.sum; 47 | } 48 | 49 | template CalculateTotal(n) { 50 | signal input nums[n]; 51 | signal output sum; 52 | 53 | signal sums[n]; 54 | sums[0] <== nums[0]; 55 | 56 | for (var i=1; i < n; i++) { 57 | sums[i] <== sums[i - 1] + nums[i]; 58 | } 59 | 60 | sum <== sums[n - 1]; 61 | } 62 | 63 | // Modulo lifted from https://sourcegraph.com/github.com/darkforest-eth/circuits/-/blob/perlin/perlin.circom and https://sourcegraph.com/github.com/zk-ml/demo/-/blob/circuits/math/circuit.circom 64 | // input: dividend and divisor field elements in [0, sqrt(p)) 65 | // output: remainder and quotient field elements in [0, p-1] and [0, sqrt(p) 66 | // Haven't thought about negative divisor yet. Not needed. 67 | // -8 % 5 = 2. [-8 -> 8. 8 % 5 -> 3. 5 - 3 -> 2.] 68 | // (-8 - 2) // 5 = -2 69 | // -8 + 2 * 5 = 2 70 | // check: 2 - 2 * 5 = -8 71 | template Modulo(divisor_bits) { 72 | signal input dividend; // -8 73 | signal input divisor; // 5 74 | signal output remainder; // 2 75 | signal output quotient; // -2 76 | 77 | component is_neg = IsNegative(); 78 | is_neg.in <== dividend; 79 | 80 | signal output is_dividend_negative; 81 | is_dividend_negative <== is_neg.out; 82 | 83 | signal output dividend_adjustment; 84 | dividend_adjustment <== 1 + is_dividend_negative * -2; // 1 or -1 85 | 86 | signal output abs_dividend; 87 | abs_dividend <== dividend * dividend_adjustment; // 8 88 | 89 | signal output raw_remainder; 90 | raw_remainder <-- abs_dividend % divisor; 91 | 92 | signal output neg_remainder; 93 | neg_remainder <-- divisor - raw_remainder; 94 | 95 | if (is_dividend_negative == 1 && raw_remainder != 0) { 96 | remainder <-- neg_remainder; 97 | } else { 98 | remainder <-- raw_remainder; 99 | } 100 | 101 | quotient <-- (dividend - remainder) / divisor; // (-8 - 2) / 5 = -2. 102 | 103 | dividend === divisor * quotient + remainder; // -8 = 5 * -2 + 2. 104 | 105 | component rp = MultiRangeProof(3, 128); 106 | rp.in[0] <== divisor; 107 | rp.in[1] <== quotient; 108 | rp.in[2] <== dividend; 109 | //rp.max_abs_value <== SQRT_P; 110 | 111 | // check that 0 <= remainder < divisor 112 | component remainderUpper = LessThan(divisor_bits); 113 | remainderUpper.in[0] <== remainder; 114 | remainderUpper.in[1] <== divisor; 115 | remainderUpper.out === 1; 116 | } 117 | 118 | // Written by us 119 | // n bytes per signal, n = 31 usually 120 | template Packed2Bytes(n){ 121 | signal input in; // < 2 ^ (8 * 31) 122 | signal output out[n]; // each out is < 64 123 | // Rangecheck in and out? 124 | 125 | // Constrain bits 126 | component nbytes = Num2Bits(8 * n); 127 | nbytes.in <== in; 128 | component bytes[n]; 129 | 130 | for (var k = 0; k < n; k++){ 131 | // Witness gen out 132 | out[k] <-- (in >> (k * 8)) % 256; 133 | 134 | // Constrain bits to match 135 | bytes[k] = Num2Bits(8); 136 | bytes[k].in <== out[k]; 137 | for (var j = 0; j < 8; j++) { 138 | nbytes.out[k * 8 + j] === bytes[k].out[j]; 139 | } 140 | } 141 | } 142 | 143 | // Written by us 144 | // n bytes per signal, n = 31 usually 145 | template Bytes2Packed(n){ 146 | signal input in[n]; // each in is < 64 147 | signal pow2[n+1]; // [k] is 2^k 148 | signal in_prefix_sum[n+1]; // each [k] is in[0] + 2^8 in[1]... 2^{8k-8} in[k-1]. cont. 149 | // [0] is 0. [1] is in[0]. [n+1] is out. 150 | signal output out; // < 2 ^ (8 * 31) 151 | // Rangecheck in and out? 152 | 153 | // Witness gen out 154 | in_prefix_sum[0] <-- 0; 155 | for (var k = 0; k < n; k++){ 156 | in_prefix_sum[k+1] <-- in_prefix_sum[k] + in[k] * (2 ** (k * 8)); 157 | } 158 | out <-- in_prefix_sum[n]; 159 | 160 | // Constrain out bits 161 | component nbytes = Num2Bits(8 * n); 162 | nbytes.in <== out; // I think this auto-rangechecks out to be < 8*n bits. 163 | component bytes[n]; 164 | 165 | for (var k = 0; k < n; k++){ 166 | bytes[k] = Num2Bits(8); 167 | bytes[k].in <== in[k]; 168 | for (var j = 0; j < 8; j++) { 169 | nbytes.out[k * 8 + j] === bytes[k].out[j]; 170 | } 171 | } 172 | } 173 | 174 | // component main { public [in, index ] } = QuinSelector(3, 2); -------------------------------------------------------------------------------- /circuits/jwt.circom: -------------------------------------------------------------------------------- 1 | pragma circom 2.1.5; 2 | 3 | // Circuits originally 1159565 4 | include "./base64.circom"; // Adds 2046 constraints 5 | include "@zk-email/circuits/helpers/sha.circom"; 6 | include "@zk-email/circuits/helpers/rsa.circom"; 7 | include "@zk-email/circuits/helpers/extract.circom"; // For VarShiftLeft and VarShiftMaskedStr 8 | include "./jwt_email_regex.circom"; 9 | include "./jwt_type_regex.circom"; 10 | include "./ascii.circom"; 11 | include "./timestamp.circom"; 12 | 13 | template JWTVerify(max_msg_bytes, max_json_bytes, n, k) { 14 | signal input message[max_msg_bytes]; 15 | signal input modulus[k]; // rsa pubkey 16 | signal input signature[k]; 17 | 18 | signal input message_padded_bytes; // length of the message including the padding 19 | 20 | signal input period_idx; // index of the period in the base64 encoded msg 21 | var max_domain_len = 30; 22 | var max_timestamp_len = 10; 23 | 24 | signal reveal_timestamp[max_timestamp_len][max_json_bytes]; 25 | 26 | signal input domain_idx; // index of email domain in message 27 | // signal input domain[max_domain_len]; // input domain with padding 28 | signal reveal_email[max_domain_len][max_json_bytes]; // reveals found email domain 29 | 30 | signal input time_idx; // index of expiration timestamp 31 | signal input time; 32 | 33 | // *********** hash the padded message *********** 34 | component sha = Sha256Bytes(max_msg_bytes); 35 | for (var i = 0; i < max_msg_bytes; i++) { 36 | sha.in_padded[i] <== message[i]; 37 | } 38 | sha.in_len_padded_bytes <== message_padded_bytes; 39 | 40 | var msg_len = (256+n)\n; 41 | component base_msg[msg_len]; 42 | for (var i = 0; i < msg_len; i++) { 43 | base_msg[i] = Bits2Num(n); 44 | } 45 | for (var i = 0; i < 256; i++) { 46 | base_msg[i\n].in[i%n] <== sha.out[255 - i]; 47 | } 48 | for (var i = 256; i < n*msg_len; i++) { 49 | base_msg[i\n].in[i%n] <== 0; 50 | } 51 | 52 | // *********** verify signature for the message *********** 53 | component rsa = RSAVerify65537(n, k); 54 | for (var i = 0; i < msg_len; i++) { 55 | rsa.base_message[i] <== base_msg[i].out; 56 | } 57 | for (var i = msg_len; i < k; i++) { 58 | rsa.base_message[i] <== 0; 59 | } 60 | for (var i = 0; i < k; i++) { 61 | rsa.modulus[i] <== modulus[i]; 62 | } 63 | for (var i = 0; i < k; i++) { 64 | rsa.signature[i] <== signature[i]; 65 | } 66 | 67 | // decode to JSON format 68 | component message_b64 = Base64Decode(max_json_bytes); 69 | component eqs[max_msg_bytes]; 70 | signal int[max_msg_bytes]; 71 | 72 | for (var i = 0; i < max_msg_bytes - 1; i++) { 73 | eqs[i] = GreaterEqThan(15); 74 | eqs[i].in[0] <== i; 75 | eqs[i].in[1] <== period_idx; 76 | 77 | var i_plus_one = eqs[i].out; 78 | var i_normal = 1 - eqs[i].out; 79 | 80 | int[i] <== (message[i] * i_normal); 81 | message_b64.in[i] <== (message[i + 1] * (i_plus_one)) + int[i]; 82 | } 83 | message_b64.in[max_msg_bytes - 1] <== 0; 84 | 85 | /************************** JWT REGEXES *****************************/ 86 | 87 | /* ensures signature is type jwt */ 88 | // component type_jwt_regex = MessageType(max_json_bytes); 89 | // for (var i = 0; i < max_json_bytes; i++) { 90 | // type_jwt_regex.msg[i] <== message_b64.out[i]; 91 | // } 92 | // type_jwt_regex.out === 1; 93 | 94 | // /* ensures an email in json found */ 95 | component email_regex = EmailDomain(max_json_bytes); 96 | for (var i = 0; i < max_json_bytes; i++) { 97 | email_regex.msg[i] <== message_b64.out[i]; 98 | } 99 | email_regex.out === 1; 100 | 101 | // From 20K constraints to 13K constraints 102 | signal output domain[max_domain_len] <== VarShiftMaskedStr(max_json_bytes, max_domain_len)(email_regex.reveal0, domain_idx); 103 | 104 | // check expiration date is found 105 | // Updating this regex reduced it 8K, from 1,112,569 to 1,104,851 constraints 106 | component time_regex = Timestamp(max_json_bytes); 107 | for (var i = 0; i < max_json_bytes; i++) { 108 | time_regex.msg[i] <== message_b64.out[i]; 109 | } 110 | time_regex.out === 1; 111 | 112 | // isolate where expiration index is 113 | component exp_eq[max_json_bytes]; 114 | for (var i = 0; i < max_json_bytes; i++) { 115 | exp_eq[i] = IsEqual(); 116 | exp_eq[i].in[0] <== i; 117 | exp_eq[i].in[1] <== time_idx; 118 | } 119 | // shifts timestamp to start of string 120 | for (var j = 0; j < max_timestamp_len; j++) { 121 | reveal_timestamp[j][j] <== exp_eq[j].out * time_regex.reveal[j]; 122 | for (var i = j + 1; i < max_json_bytes; i++) { 123 | reveal_timestamp[j][i] <== reveal_timestamp[j][i - 1] + exp_eq[i-j].out * time_regex.reveal[i]; 124 | } 125 | } 126 | 127 | // convert to number 128 | component time_num = AsciiToNum(max_timestamp_len); 129 | // signal time_reveal[max_timestamp_len] <== VarShiftMaskedStr(max_json_bytes, max_timestamp_len)(time_regex.reveal, time_idx); 130 | // time_num.in <== time_reveal; 131 | // <== VarShiftMaskedStr(max_json_bytes, max_timestamp_len)(reveal_timestamp, time_idx); 132 | for (var j = 0; j < max_timestamp_len; j++) { 133 | time_num.in[j] <== reveal_timestamp[j][max_json_bytes -1]; 134 | } 135 | 136 | signal exp_time <== time_num.out + 86400; 137 | // check that the current time is less than a day after the expiration date 138 | component less_exp_time = LessThan(34); 139 | less_exp_time.in[0] <== time; 140 | less_exp_time.in[1] <== exp_time; 141 | 142 | less_exp_time.out === 1; 143 | } 144 | 145 | component main { public [ modulus, time ] } = JWTVerify(1024, 766, 121, 17); 146 | -------------------------------------------------------------------------------- /circuits/sha256general.circom: -------------------------------------------------------------------------------- 1 | pragma circom 2.0.3; 2 | 3 | include "../node_modules/circomlib/circuits/sha256/constants.circom"; 4 | include "../node_modules/circomlib/circuits/sha256/sha256compression.circom"; 5 | include "../node_modules/circomlib/circuits/comparators.circom"; 6 | include "./utils.circom"; 7 | 8 | // A modified version of the SHA256 circuit that allows specified length messages up to a max to all work via array indexing on the SHA256 compression circuit. 9 | template Sha256General(maxBitsPadded) { 10 | // maxBitsPadded must be a multiple of 512, and the bit circuits in this file are limited to 15 so must be raised if the message is longer. 11 | assert(maxBitsPadded % 512 == 0); 12 | var maxBitsPaddedBits = log2_ceil(maxBitsPadded); 13 | assert(2 ** maxBitsPaddedBits > maxBitsPadded); 14 | 15 | // Note that maxBitsPadded = maxBits + 64 16 | signal input paddedIn[maxBitsPadded]; 17 | signal output out[256]; 18 | signal input in_len_padded_bits; // This is the padded length of the message pre-hash. 19 | signal inBlockIndex; 20 | 21 | var i; 22 | var k; 23 | var j; 24 | var maxBlocks; 25 | var bitsLastBlock; 26 | maxBlocks = (maxBitsPadded\512); 27 | var maxBlocksBits = log2_ceil(maxBlocks); 28 | assert(2 ** maxBlocksBits > maxBlocks); 29 | 30 | inBlockIndex <-- (in_len_padded_bits >> 9); 31 | in_len_padded_bits === inBlockIndex * 512; 32 | 33 | // These verify the unconstrained floor calculation is the uniquely correct integer that represents the floor 34 | // component floorVerifierUnder = LessEqThan(maxBitsPaddedBits); // todo verify the length passed in is less than nbits. note that maxBitsPaddedBits can likely be lowered or made it a fn of maxbits 35 | // floorVerifierUnder.in[0] <== (inBlockIndex)*512; 36 | // floorVerifierUnder.in[1] <== in_len_padded_bits; 37 | // floorVerifierUnder.out === 1; 38 | 39 | // component floorVerifierOver = GreaterThan(maxBitsPaddedBits); 40 | // floorVerifierOver.in[0] <== (inBlockIndex+1)*512; 41 | // floorVerifierOver.in[1] <== in_len_padded_bits; 42 | // floorVerifierOver.out === 1; 43 | 44 | // These verify we pass in a valid number of bits to the SHA256 compression circuit. 45 | component bitLengthVerifier = LessEqThan(maxBitsPaddedBits); // todo verify the length passed in is less than nbits. note that maxBitsPaddedBits can likely be lowered or made it a fn of maxbits 46 | bitLengthVerifier.in[0] <== in_len_padded_bits; 47 | bitLengthVerifier.in[1] <== maxBitsPadded; 48 | bitLengthVerifier.out === 1; 49 | 50 | // Note that we can no longer do padded verification efficiently inside the SHA because it requires non deterministic array indexing. 51 | // We can do it if we add a constraint, but since guessing a valid SHA2 preimage is hard anyways, we'll just do it outside the circuit. 52 | 53 | // signal paddedIn[maxBlocks*512]; 54 | // for (k=0; k> k)&1; 63 | // } 64 | 65 | component ha0 = H(0); 66 | component hb0 = H(1); 67 | component hc0 = H(2); 68 | component hd0 = H(3); 69 | component he0 = H(4); 70 | component hf0 = H(5); 71 | component hg0 = H(6); 72 | component hh0 = H(7); 73 | 74 | component sha256compression[maxBlocks]; 75 | 76 | for (i=0; i el.toString()); 141 | const YArr = bigIntToArray(64, 4, BigInt("0x" + pk.slice(68, 68 + 64))).map((el) => el.toString()); 142 | 143 | return [XArr, YArr]; 144 | } 145 | 146 | // taken from generation code in dizkus-circuits tests 147 | function sigToRSArrays(sig: string) { 148 | const rArr = bigIntToArray(64, 4, BigInt("0x" + sig.slice(2, 2 + 64))).map((el) => el.toString()); 149 | const sArr = bigIntToArray(64, 4, BigInt("0x" + sig.slice(66, 66 + 64))).map((el) => el.toString()); 150 | 151 | return [rArr, sArr]; 152 | } 153 | 154 | export function buildInput(pubkey: string, msghash: string, sig: string) { 155 | const [r, s] = sigToRSArrays(sig); 156 | 157 | return { 158 | r: r, 159 | s: s, 160 | msghash: bigIntToArray(64, 4, BigInt(msghash)), 161 | pubkey: pubkeyToXYArrays(pubkey), 162 | }; 163 | } 164 | -------------------------------------------------------------------------------- /inputs/openai.json: -------------------------------------------------------------------------------- 1 | {"message":["101","121","74","104","98","71","99","105","79","105","74","83","85","122","73","49","78","105","73","115","73","110","82","53","99","67","73","54","73","107","112","88","86","67","73","115","73","109","116","112","90","67","73","54","73","107","49","85","97","69","86","79","86","85","112","72","84","107","86","78","77","86","70","85","82","84","82","78","77","69","90","67","84","87","112","107","81","48","53","85","90","122","86","78","82","70","85","120","85","108","82","86","100","49","70","86","83","107","82","78","82","85","49","51","85","109","116","71","82","86","70","114","82","88","112","83","90","121","74","57","46","101","121","74","111","100","72","82","119","99","122","111","118","76","50","70","119","97","83","53","118","99","71","86","117","89","87","107","117","89","50","57","116","76","51","66","121","98","50","90","112","98","71","85","105","79","110","115","105","90","87","49","104","97","87","119","105","79","105","74","108","98","87","49","104","90","51","86","118","81","71","74","108","99","109","116","108","98","71","86","53","76","109","86","107","100","83","73","115","73","109","86","116","89","87","108","115","88","51","90","108","99","109","108","109","97","87","86","107","73","106","112","48","99","110","86","108","102","83","119","105","97","72","82","48","99","72","77","54","76","121","57","104","99","71","107","117","98","51","66","108","98","109","70","112","76","109","78","118","98","83","57","104","100","88","82","111","73","106","112","55","73","110","86","122","90","88","74","102","97","87","81","105","79","105","74","49","99","50","86","121","76","85","112","52","84","87","52","48","89","110","108","106","85","51","90","87","77","71","104","119","89","51","100","79","90","109","116","106","82","122","82","104","87","67","74","57","76","67","74","112","99","51","77","105","79","105","74","111","100","72","82","119","99","122","111","118","76","50","70","49","100","71","103","119","76","109","57","119","90","87","53","104","97","83","53","106","98","50","48","118","73","105","119","105","99","51","86","105","73","106","111","105","90","50","57","118","90","50","120","108","76","87","57","104","100","88","82","111","77","110","119","120","77","84","81","120","78","122","69","121","77","68","103","51","79","84","107","120","77","122","65","51","78","106","65","120","78","106","89","105","76","67","74","104","100","87","81","105","79","108","115","105","97","72","82","48","99","72","77","54","76","121","57","104","99","71","107","117","98","51","66","108","98","109","70","112","76","109","78","118","98","83","57","50","77","83","73","115","73","109","104","48","100","72","66","122","79","105","56","118","98","51","66","108","98","109","70","112","76","109","57","119","90","87","53","104","97","83","53","104","100","88","82","111","77","71","70","119","99","67","53","106","98","50","48","118","100","88","78","108","99","109","108","117","90","109","56","105","88","83","119","105","97","87","70","48","73","106","111","120","78","106","103","49","78","68","69","49","77","106","107","53","76","67","74","108","101","72","65","105","79","106","69","50","79","68","89","50","77","106","81","52","79","84","107","115","73","109","70","54","99","67","73","54","73","108","82","107","83","107","108","106","89","109","85","120","78","108","100","118","86","69","104","48","84","106","107","49","98","110","108","53","100","50","103","49","82","84","82","53","84","50","56","50","83","88","82","72","73","105","119","105","99","50","78","118","99","71","85","105","79","105","74","118","99","71","86","117","97","87","81","103","99","72","74","118","90","109","108","115","90","83","66","108","98","87","70","112","98","67","66","116","98","50","82","108","98","67","53","121","90","87","70","107","73","71","49","118","90","71","86","115","76","110","74","108","99","88","86","108","99","51","81","103","98","51","74","110","89","87","53","112","101","109","70","48","97","87","57","117","76","110","74","108","89","87","81","103","98","51","74","110","89","87","53","112","101","109","70","48","97","87","57","117","76","110","100","121","97","88","82","108","73","110","48","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","24","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"],"modulus":["1039819274958841503552777425237411969","2393925418941457468536305535389088567","513505235307821578406185944870803528","31648688809132041103725691608565945","1118227280248002501343932784260195348","1460752189656646928843376724380610733","2494690879775849992239868627264129920","499770848099786006855805824914661444","117952129670880907578696311220260862","594599095806595023021313781486031656","1954215709028388479536967672374066621","275858127917207716435784616531223795","2192832134592444363563023272016397664","1951765503135207318741689711604628857","679054217888353607009053133437382225","831007028401303788228965296099949363","4456647917934998006260668783660427"],"signature":["489911935971102878614252432439165534","1774237619997688263972396224417828292","1896388336023153089861639642638555425","2232344157616540787518521817927099509","695438689758228174156568294920938863","1804764894396360647210422352210107584","18482226666256726227098366491542044","207361857071398845217376123013532831","925769911473059843169417090682313379","446584479767496656376400514594495662","107003049147303286358992586655755841","1135368332134467483506907618486953416","539500328807718862193202991855358485","163068218585611064911576908082720750","478845682556184499256027263546025920","1148850760263490026221220131626222105","4101587133765593135384542100989103"],"message_padded_bytes":"832","period_idx":"120","domain_idx":"142","domain":["98","101","114","107","101","108","101","121","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0"],"time":"1686624899","time_idx":"437"} -------------------------------------------------------------------------------- /test.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "Requirement already satisfied: regex in ./pyjwt-env/lib/python3.9/site-packages (2023.5.5)\n", 13 | "\u001b[33mWARNING: You are using pip version 20.2.3; however, version 23.1.2 is available.\n", 14 | "You should consider upgrading via the '/Users/emmaguo/zk-blind/pyjwt-env/bin/python3 -m pip install --upgrade pip' command.\u001b[0m\n", 15 | "Requirement already satisfied: PyJWT in ./pyjwt-env/lib/python3.9/site-packages (2.6.0)\n", 16 | "\u001b[33mWARNING: You are using pip version 20.2.3; however, version 23.1.2 is available.\n", 17 | "You should consider upgrading via the '/Users/emmaguo/zk-blind/pyjwt-env/bin/python3 -m pip install --upgrade pip' command.\u001b[0m\n" 18 | ] 19 | } 20 | ], 21 | "source": [ 22 | "!pip3 install regex\n", 23 | "!pip3 install PyJWT" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": 2, 29 | "metadata": {}, 30 | "outputs": [ 31 | { 32 | "name": "stdout", 33 | "output_type": "stream", 34 | "text": [ 35 | "Name: PyJWT\n", 36 | "Version: 2.6.0\n", 37 | "Summary: JSON Web Token implementation in Python\n", 38 | "Home-page: https://github.com/jpadilla/pyjwt\n", 39 | "Author: Jose Padilla\n", 40 | "Author-email: hello@jpadilla.com\n", 41 | "License: MIT\n", 42 | "Location: /Users/emmaguo/zk-blind/pyjwt-env/lib/python3.9/site-packages\n", 43 | "Requires: \n", 44 | "Required-by: \n" 45 | ] 46 | } 47 | ], 48 | "source": [ 49 | "!pip3 show PyJWT" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": 2, 55 | "metadata": {}, 56 | "outputs": [], 57 | "source": [ 58 | "import re\n", 59 | "import jwt\n", 60 | "import time\n", 61 | "import json\n" 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": 4, 67 | "metadata": {}, 68 | "outputs": [ 69 | { 70 | "name": "stdout", 71 | "output_type": "stream", 72 | "text": [ 73 | "1684235095\n" 74 | ] 75 | } 76 | ], 77 | "source": [ 78 | "current_time_seconds = int(time.time())\n", 79 | "print(current_time_seconds)" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": 32, 85 | "metadata": {}, 86 | "outputs": [ 87 | { 88 | "name": "stdout", 89 | "output_type": "stream", 90 | "text": [ 91 | "{\"iat\": 1682580425, \"exp\": 1684235195, \"azp\": \"xkQY1I0RgfxPhCNtJZ70cd8oTzymDT1r\", \"scope\": \"openid profile email\"}\n", 92 | "\n" 93 | ] 94 | } 95 | ], 96 | "source": [ 97 | "#encoded_jwt = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c\"\n", 98 | "# encoded_jwt = jwt.encode({\n", 99 | "# \"iat\": 1682580425,\n", 100 | "# \"exp\": current_time_seconds + 100,\n", 101 | "# \"azp\": \"xkQY1I0RgfxPhCNtJZ70cd8oTzymDT1r\",\n", 102 | "# \"scope\": \"openid profile email\"\n", 103 | "# }, \"secret\", algorithm=\"HS256\")\n", 104 | "# decoded_jwt = jwt.decode(encoded_jwt, \"secret\", algorithms=[\"HS256\"])\n", 105 | "\n", 106 | "catch_all_nums = '(0|1|2|3|4|5|6|7|8|9)';\n", 107 | "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)';\n", 108 | "\n", 109 | "decoded_jwt = json.dumps({\"iat\":1682580425, \"exp\": 1684235195, \"azp\": \"xkQY1I0RgfxPhCNtJZ70cd8oTzymDT1r\", \"scope\": \"openid profile email\"})\n", 110 | "# exp_regex = re.compile(r'\"exp\"\\s*:\\s*[0-9]+')\n", 111 | "# exp_regex = re.compile(r'\"exp\"\\s*:\\s*(\\d+)')\n", 112 | "exp_regex = re.compile(r'\"exp\"\\s*:\\s*(0|1|2|3|4|5|6|7|8|9)+')\n", 113 | "print(decoded_jwt)\n", 114 | "# exp_regex = re.compile('\\\"exp\\\"\\\\s*:')\n", 115 | "match = exp_regex.search(decoded_jwt)\n", 116 | "\n", 117 | "print(match)\n", 118 | "\n", 119 | "# if match:\n", 120 | "# exp_timestamp = int(match.group(1))\n", 121 | "# print(exp_timestamp)\n", 122 | "# else:\n", 123 | "# print(\"Expiration timestamp not found.\")" 124 | ] 125 | }, 126 | { 127 | "cell_type": "code", 128 | "execution_count": 1, 129 | "metadata": {}, 130 | "outputs": [ 131 | { 132 | "ename": "SyntaxError", 133 | "evalue": "invalid syntax (537008972.py, line 1)", 134 | "output_type": "error", 135 | "traceback": [ 136 | "\u001b[0;36m Cell \u001b[0;32mIn[1], line 1\u001b[0;36m\u001b[0m\n\u001b[0;31m json = json.dumps({\"alg\":\"RS256\",\"typ\":\"JWT\",\"kid\":\"MThENUJGNEM1QTE4M0FBMjdCNTg5MDU1RTUwQUJDMEMwRkFEQkEzRg\"}{\"https://api.openai.com/profile\":{\"email\":\"sehyun@berkeley.edu\",\"email_verified\":true,\"geoip_country\":\"US\"},\"https://api.openai.com/auth\":{\"user_id\":\"user-kWLipsOwLdWx1wLsB7rTwRqe\"},\"iss\":\"https://auth0.openai.com/\",\"sub\":\"google-oauth2|116609862103911306807\",\"aud\":[\"https://api.openai.com/v1\",\"https://openai.auth0.com/userinfo\"],\"iat\":1673155446,\"exp\":1673760246,\"azp\":\"TdJIcbe16WoTHtN95nyywh5E4yOo6ItG\",\"scope\":\"openid profile email model.read model.request organization.read offline_access\"})\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n" 137 | ] 138 | } 139 | ], 140 | "source": [ 141 | "json_w_spaces = json.dumps({\"alg\":\"RS256\",\"typ\":\"JWT\",\"kid\":\"MThENUJGNEM1QTE4M0FBMjdCNTg5MDU1RTUwQUJDMEMwRkFEQkEzRg\"}{\"https://api.openai.com/profile\":{\"email\":\"sehyun@berkeley.edu\",\"email_verified\":true,\"geoip_country\":\"US\"},\"https://api.openai.com/auth\":{\"user_id\":\"user-kWLipsOwLdWx1wLsB7rTwRqe\"},\"iss\":\"https://auth0.openai.com/\",\"sub\":\"google-oauth2|116609862103911306807\",\"aud\":[\"https://api.openai.com/v1\",\"https://openai.auth0.com/userinfo\"],\"iat\":1673155446,\"exp\":1673760246,\"azp\":\"TdJIcbe16WoTHtN95nyywh5E4yOo6ItG\",\"scope\":\"openid profile email model.read model.request organization.read offline_access\"})" 142 | ] 143 | } 144 | ], 145 | "metadata": { 146 | "kernelspec": { 147 | "display_name": "Python 3", 148 | "language": "python", 149 | "name": "python3" 150 | }, 151 | "language_info": { 152 | "codemirror_mode": { 153 | "name": "ipython", 154 | "version": 3 155 | }, 156 | "file_extension": ".py", 157 | "mimetype": "text/x-python", 158 | "name": "python", 159 | "nbconvert_exporter": "python", 160 | "pygments_lexer": "ipython3", 161 | "version": "3.9.2" 162 | }, 163 | "orig_nbformat": 4 164 | }, 165 | "nbformat": 4, 166 | "nbformat_minor": 2 167 | } 168 | -------------------------------------------------------------------------------- /regex_to_circom/gen.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import json 3 | import string 4 | 5 | # Clear file 6 | OUTPUT_HALO2 = True 7 | 8 | graph_json = json.loads(subprocess.check_output(['npx', 'tsx', 'regex_to_dfa.js'])) 9 | N = len(graph_json) 10 | 11 | # Outgoing nodes 12 | graph = [{} for i in range(N)] 13 | # Incoming Nodes 14 | rev_graph = [[] for i in range(N)] 15 | accept_nodes = set() 16 | 17 | for i in range(N): 18 | for k in graph_json[i]['edges']: 19 | # assert len(k) == 1 20 | # assert ord(k) < 128 21 | v = graph_json[i]['edges'][k] 22 | graph[i][k] = v 23 | rev_graph[v].append((k, i)) 24 | # Iterates over value in set for halo2 lookup, append to file 25 | 26 | if graph_json[i]['type'] == 'accept': 27 | accept_nodes.add(i) 28 | 29 | accept_nodes = list(accept_nodes) 30 | assert len(accept_nodes) == 1 31 | 32 | if (OUTPUT_HALO2): 33 | with open('halo2_regex_lookup.txt', 'w') as f: 34 | for a in accept_nodes: 35 | print(str(a) + " ", file=f, end='') 36 | print("", file=f) 37 | for i in range(N): 38 | for k in graph_json[i]['edges']: 39 | v = graph_json[i]['edges'][k] 40 | for val in json.loads(k): 41 | with open('halo2_regex_lookup.txt', 'a') as f: 42 | print(i, v, ord(val), file=f) 43 | 44 | eq_i = 0 45 | lt_i = 0 46 | and_i = 0 47 | multi_or_i = 0 48 | 49 | lines = [] 50 | lines.append("for (var i = 0; i < num_bytes; i++) {") 51 | 52 | assert 0 not in accept_nodes 53 | 54 | for i in range(1, N): 55 | outputs = [] 56 | for k, prev_i in rev_graph[i]: 57 | vals = json.loads(k) 58 | eq_outputs = [] 59 | 60 | uppercase = set(string.ascii_uppercase) 61 | lowercase = set(string.ascii_lowercase) 62 | digits = set(string.digits) 63 | vals = set(vals) 64 | 65 | if uppercase <= vals: 66 | vals -= uppercase 67 | lines.append(f"\tlt[{lt_i}][i] = LessThan(8);") 68 | lines.append(f"\tlt[{lt_i}][i].in[0] <== 64;") 69 | lines.append(f"\tlt[{lt_i}][i].in[1] <== in[i];") 70 | 71 | lines.append(f"\tlt[{lt_i+1}][i] = LessThan(8);") 72 | lines.append(f"\tlt[{lt_i+1}][i].in[0] <== in[i];") 73 | lines.append(f"\tlt[{lt_i+1}][i].in[1] <== 91;") 74 | 75 | lines.append(f"\tand[{and_i}][i] = AND();") 76 | lines.append(f"\tand[{and_i}][i].a <== lt[{lt_i}][i].out;") 77 | lines.append(f"\tand[{and_i}][i].b <== lt[{lt_i+1}][i].out;") 78 | 79 | eq_outputs.append(('and', and_i)) 80 | lt_i += 2 81 | and_i += 1 82 | 83 | if lowercase <= vals: 84 | vals -= lowercase 85 | lines.append(f"\tlt[{lt_i}][i] = LessThan(8);") 86 | lines.append(f"\tlt[{lt_i}][i].in[0] <== 96;") 87 | lines.append(f"\tlt[{lt_i}][i].in[1] <== in[i];") 88 | 89 | lines.append(f"\tlt[{lt_i+1}][i] = LessThan(8);") 90 | lines.append(f"\tlt[{lt_i+1}][i].in[0] <== in[i];") 91 | lines.append(f"\tlt[{lt_i+1}][i].in[1] <== 123;") 92 | 93 | lines.append(f"\tand[{and_i}][i] = AND();") 94 | lines.append(f"\tand[{and_i}][i].a <== lt[{lt_i}][i].out;") 95 | lines.append(f"\tand[{and_i}][i].b <== lt[{lt_i+1}][i].out;") 96 | 97 | eq_outputs.append(('and', and_i)) 98 | lt_i += 2 99 | and_i += 1 100 | 101 | if digits <= vals: 102 | vals -= digits 103 | lines.append(f"\tlt[{lt_i}][i] = LessThan(8);") 104 | lines.append(f"\tlt[{lt_i}][i].in[0] <== 47;") 105 | lines.append(f"\tlt[{lt_i}][i].in[1] <== in[i];") 106 | 107 | lines.append(f"\tlt[{lt_i+1}][i] = LessThan(8);") 108 | lines.append(f"\tlt[{lt_i+1}][i].in[0] <== in[i];") 109 | lines.append(f"\tlt[{lt_i+1}][i].in[1] <== 58;") 110 | 111 | lines.append(f"\tand[{and_i}][i] = AND();") 112 | lines.append(f"\tand[{and_i}][i].a <== lt[{lt_i}][i].out;") 113 | lines.append(f"\tand[{and_i}][i].b <== lt[{lt_i+1}][i].out;") 114 | 115 | eq_outputs.append(('and', and_i)) 116 | lt_i += 2 117 | and_i += 1 118 | 119 | for c in vals: 120 | assert len(c) == 1 121 | lines.append(f"\teq[{eq_i}][i] = IsEqual();") 122 | lines.append(f"\teq[{eq_i}][i].in[0] <== in[i];") 123 | lines.append(f"\teq[{eq_i}][i].in[1] <== {ord(c)};") 124 | eq_outputs.append(('eq', eq_i)) 125 | eq_i += 1 126 | 127 | lines.append(f"\tand[{and_i}][i] = AND();") 128 | lines.append(f"\tand[{and_i}][i].a <== states[i][{prev_i}];") 129 | 130 | if len(eq_outputs) == 1: 131 | lines.append(f"\tand[{and_i}][i].b <== {eq_outputs[0][0]}[{eq_outputs[0][1]}][i].out;") 132 | elif len(eq_outputs) > 1: 133 | lines.append(f"\tmulti_or[{multi_or_i}][i] = MultiOR({len(eq_outputs)});") 134 | for output_i in range(len(eq_outputs)): 135 | lines.append(f"\tmulti_or[{multi_or_i}][i].in[{output_i}] <== {eq_outputs[output_i][0]}[{eq_outputs[output_i][1]}][i].out;") 136 | lines.append(f"\tand[{and_i}][i].b <== multi_or[{multi_or_i}][i].out;") 137 | multi_or_i += 1 138 | 139 | outputs.append(and_i) 140 | and_i += 1 141 | # print(f"states[i+1][{i}] = states[i][{prev_i}] AND (in[i] == {repr(k)})") 142 | if len(outputs) == 1: 143 | lines.append(f"\tstates[i+1][{i}] <== and[{outputs[0]}][i].out;") 144 | elif len(outputs) > 1: 145 | lines.append(f"\tmulti_or[{multi_or_i}][i] = MultiOR({len(outputs)});") 146 | for output_i in range(len(outputs)): 147 | lines.append(f"\tmulti_or[{multi_or_i}][i].in[{output_i}] <== and[{outputs[output_i]}][i].out;") 148 | lines.append(f"\tstates[i+1][{i}] <== multi_or[{multi_or_i}][i].out;") 149 | multi_or_i += 1 150 | 151 | lines.append("}") 152 | 153 | declarations = [] 154 | 155 | if eq_i > 0: 156 | declarations.append(f"component eq[{eq_i}][num_bytes];") 157 | if lt_i > 0: 158 | declarations.append(f"component lt[{lt_i}][num_bytes];") 159 | if and_i > 0: 160 | declarations.append(f"component and[{and_i}][num_bytes];") 161 | if multi_or_i > 0: 162 | declarations.append(f"component multi_or[{multi_or_i}][num_bytes];") 163 | declarations.append(f"signal states[num_bytes+1][{N}];") 164 | declarations.append("") 165 | 166 | init_code = [] 167 | 168 | init_code.append("for (var i = 0; i < num_bytes; i++) {") 169 | init_code.append("\tstates[i][0] <== 1;") 170 | init_code.append("}") 171 | 172 | init_code.append(f"for (var i = 1; i < {N}; i++) {{") 173 | init_code.append("\tstates[0][i] <== 0;") 174 | init_code.append("}") 175 | 176 | init_code.append("") 177 | 178 | lines = declarations + init_code + lines 179 | 180 | accept_node = accept_nodes[0] 181 | accept_lines = [""] 182 | accept_lines.append("signal final_state_sum[num_bytes+1];") 183 | accept_lines.append(f"final_state_sum[0] <== states[0][{accept_node}];") 184 | accept_lines.append("for (var i = 1; i <= num_bytes; i++) {") 185 | accept_lines.append(f"\tfinal_state_sum[i] <== final_state_sum[i-1] + states[i][{accept_node}];") 186 | accept_lines.append("}") 187 | accept_lines.append("out <== final_state_sum[num_bytes];") 188 | 189 | lines += accept_lines 190 | 191 | print("\n".join(lines)) 192 | -------------------------------------------------------------------------------- /circuits/scripts/compile.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | 3 | const { execSync } = require("child_process"); 4 | const fs = require("fs"); 5 | 6 | let circuitsList = process.argv[2]; 7 | const deterministic = false; 8 | const contributingExtraRandomness = false; 9 | const ptau_size = 21; 10 | // process.argv[3] === "true" || process.argv[3] === undefined; 11 | 12 | // TODO: add an option to generate with entropy for production keys 13 | 14 | if (process.argv.length < 3 || process.argv.length > 6) { 15 | console.log("usage"); 16 | console.log( 17 | "compile comma,seperated,list,of,circuits,or,--all [`true` if deterministic / `false` if not] [skip-r1cswasm to skip recompiling the circuit, anything else to recompile] [skip-zkey to skip recompiling the zkey, anything else to do it]" 18 | ); 19 | process.exit(1); 20 | } 21 | 22 | const cwd = process.cwd(); 23 | console.log(cwd); 24 | 25 | if (circuitsList === "-A" || circuitsList === "--all") { 26 | try { 27 | circuitsList = fs 28 | .readdirSync(cwd + "/circuits", { withFileTypes: true }) 29 | .filter((dirent) => dirent.isDirectory()) 30 | .map((dirent) => dirent.name) 31 | .join(); 32 | 33 | console.log("Compiling all circuits..."); 34 | console.log(circuitsList); 35 | } catch (error) { 36 | console.log(error); 37 | process.exit(1); 38 | } 39 | } 40 | 41 | for (let circuitName of circuitsList.split(",")) { 42 | let beacon; 43 | if (!process.env["beacon"]) { 44 | console.log("INSECURE ZKEY: You dont have a beacon in your .env file, test beacon assigned"); 45 | beacon = "test"; 46 | } else { 47 | beacon = process.env["beacon"]; 48 | } 49 | 50 | console.log("\nCompiling and sorting files for circuit: " + circuitName + "..."); 51 | 52 | process.chdir(cwd + "/circuits/"); 53 | 54 | if (!fs.existsSync("compiled")) { 55 | fs.mkdirSync("compiled"); 56 | } 57 | if (!fs.existsSync("contracts")) { 58 | fs.mkdirSync("contracts"); 59 | } 60 | if (!fs.existsSync("inputs")) { 61 | fs.mkdirSync("inputs"); 62 | } 63 | if (!fs.existsSync("keys")) { 64 | fs.mkdirSync("keys"); 65 | } 66 | 67 | // doesnt catch yet 68 | // https://github.com/iden3/snarkjs/pull/75 69 | // node --max-old-space-size=614400 ${snarkJSPath} -> 70 | // node --max-old-space-size=614400 ${snarkJSPath}` 71 | 72 | try { 73 | let circuitNamePrimary = circuitName.split("/").pop(); 74 | let snarkJSPath = "./../node_modules/.bin/snarkjs"; 75 | if (process.argv.length >= 4 && process.argv[4] === "skip-r1cswasm") { 76 | console.log("Skipping initial re generation of r1cs and wasm"); 77 | } else { 78 | execSync(`circom ${circuitNamePrimary}.circom --r1cs --wasm --sym`, { stdio: "inherit" }); 79 | execSync(`node --max-old-space-size=614400 ./../node_modules/snarkjs r1cs info ${circuitNamePrimary}.r1cs`, { stdio: "inherit" }); 80 | execSync(`cp ${circuitNamePrimary}_js/${circuitNamePrimary}.wasm ${circuitNamePrimary}.wasm`, { stdio: "inherit" }); 81 | execSync(`node ${circuitNamePrimary}_js/generate_witness.js ${circuitNamePrimary}.wasm inputs/input_${circuitNamePrimary}.json ${circuitNamePrimary}.wtns`, { 82 | stdio: "inherit", 83 | }); 84 | } 85 | 86 | if (process.argv.length >= 5 && process.argv[5] === "skip-zkey" && process.argv[4] === "skip-r1cswasm") { 87 | console.log("Skipping initial re generation of zkey"); 88 | } else { 89 | console.log("Generating zkey [very slow]..."); 90 | let zkeyOutputName = "circuit"; 91 | if (contributingExtraRandomness) { 92 | zkeyOutputName = "circuit_0"; 93 | } 94 | let command = `node --max-old-space-size=614400 ${snarkJSPath} groth16 setup ${circuitNamePrimary}.r1cs ../../powersoftau/powersOfTau28_hez_final_${ptau_size}.ptau ${zkeyOutputName}.zkey`; 95 | console.log(command); 96 | execSync(command, { stdio: "inherit" }); 97 | console.log("Done first zkey step!"); 98 | if (contributingExtraRandomness) { 99 | if (deterministic) { 100 | let command = `node --max-old-space-size=614400 ${snarkJSPath} zkey beacon ${zkeyOutputName}.zkey circuit.zkey ${beacon} 10`; 101 | console.log(command); 102 | execSync(command, { stdio: "inherit" }); 103 | } else { 104 | let command = `node --max-old-space-size=614400 ${snarkJSPath} zkey contribute ${zkeyOutputName}.zkey circuit.zkey -e="${Date.now()}"`; 105 | console.log(command); 106 | execSync(command, { stdio: "inherit" }); 107 | } 108 | } 109 | execSync(`node --max-old-space-size=614400 ${snarkJSPath} zkey verify ${circuitNamePrimary}.r1cs ../../powersoftau/powersOfTau28_hez_final_${ptau_size}.ptau circuit.zkey`, { 110 | stdio: "inherit", 111 | }); 112 | execSync(`mkdir -p ${cwd}/circuits/${circuitNamePrimary}/keys/`, { stdio: "inherit" }); 113 | execSync(`node --max-old-space-size=614400 ${snarkJSPath} zkey export verificationkey circuit.zkey ${circuitNamePrimary}/keys/verification_key.json`, { 114 | stdio: "inherit", 115 | }); 116 | execSync( 117 | `node --max-old-space-size=614400 ${snarkJSPath} groth16 prove circuit.zkey ${circuitNamePrimary}.wtns ${circuitNamePrimary}_proof.json ${circuitNamePrimary}_public.json`, 118 | { stdio: "inherit" } 119 | ); 120 | execSync( 121 | `node --max-old-space-size=614400 ${snarkJSPath} groth16 verify ${circuitNamePrimary}/keys/verification_key.json ${circuitNamePrimary}_public.json ${circuitNamePrimary}_proof.json`, 122 | { stdio: "inherit" } 123 | ); 124 | 125 | execSync(`mkdir -p ${cwd}/circuits/${circuitNamePrimary}/compiled/`, { stdio: "inherit" }); 126 | execSync(`mkdir -p ${cwd}/circuits/${circuitNamePrimary}/keys/`, { stdio: "inherit" }); 127 | 128 | execSync(`cp ${circuitNamePrimary}.wasm ${cwd}/circuits/${circuitNamePrimary}/compiled/circuit.wasm`, { stdio: "inherit" }); 129 | execSync(`cp circuit.zkey ${cwd}/circuits/${circuitNamePrimary}/keys/circuit_final.zkey`, { stdio: "inherit" }); 130 | // fs.copyFileSync("circuit.wasm", cwd + "/circuits/" + circuitNamePrimary + "/compiled/circuit.wasm"); 131 | // fs.unlinkSync("circuit.wasm"); 132 | // fs.copyFileSync("circuit.zkey", cwd + "/circuits/" + circuitNamePrimary + "/keys/circuit_final.zkey"); 133 | // fs.unlinkSync("circuit.zkey"); 134 | } 135 | execSync( 136 | `node --max-old-space-size=614400 ${snarkJSPath} zkey export solidityverifier ${cwd}/circuits/${circuitNamePrimary}/keys/circuit_final.zkey ${cwd}/circuits/contracts/verifier.sol`, 137 | { 138 | stdio: "inherit", 139 | } 140 | ); 141 | // copy files to appropriate places when integrated with scaffold-eth (zkaffold-eth) 142 | 143 | execSync(`mkdir -p ${cwd}/src/circuits/`, { stdio: "inherit" }); 144 | execSync(`mkdir -p ${cwd}/src/contracts/`, { stdio: "inherit" }); 145 | fs.copyFileSync(`${cwd}/circuits/contracts/verifier.sol`, `${cwd}/src/contracts/src/${circuitNamePrimary}Verifier.sol`); 146 | fs.copyFileSync(`${cwd}/circuits/${circuitNamePrimary}/compiled/circuit.wasm`, `${cwd}/src/circuits/${circuitNamePrimary}_circuit.wasm`); 147 | fs.copyFileSync(`${cwd}/circuits/${circuitNamePrimary}/keys/circuit_final.zkey`, `${cwd}/src/circuits/${circuitNamePrimary}_circuit_final.zkey`); 148 | fs.copyFileSync(`${cwd}/circuits/${circuitNamePrimary}/keys/verification_key.json`, `${cwd}/src/circuits/${circuitNamePrimary}_verification_key.json`); 149 | } catch (error) { 150 | console.log(error); 151 | process.exit(1); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /scripts/generate_input.ts: -------------------------------------------------------------------------------- 1 | import { shaHash } from "./helpers/shaHash"; 2 | import { 3 | toCircomBigIntBytes, 4 | } from "./helpers/binaryFormat"; 5 | import { 6 | MAX_MSG_PADDED_BYTES, 7 | OPENAI_PUBKEY, 8 | JWT_CLIENT_PUBKEY 9 | } from "./helpers/constants"; 10 | import { Hash } from "./fast-sha256"; 11 | const pki = require("node-forge").pki; 12 | import * as fs from "fs"; 13 | 14 | export async function generate_inputs( 15 | // signature: string = "yjlOzb9yd8JGpvPGJK9uoVubC2Hy69dFizQTgQyXDjqN7cyhGkenxTXZefAD7PxI-TJ07E804H0zBf3Gfna3vnuo4ggqGvwYzSFW1U_YWgJisHc-gTFbNS5AUm6ha-rVDSpQ-yyC1bkErAShLtWBk35Cw3el27lcskv7C9dyperELb0bK9qjE_fdTFg5_jPv3qJp2cZPgOPPn83I1077WnYV2TCHK3K478Wfa8HfwsTh4KOYCF78ZPK0lBcABS1YxR5W_W94hDdzYdSu3J0L_g0jrbTsp6RYMdGcdGsrOQZkhqtpVj1YoKA6GVLgqC4biF5ahfj9ndZe7U7MxazCXg", 16 | // msg: string = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik1UaEVOVUpHTkVNMVFURTRNMEZCTWpkQ05UZzVNRFUxUlRVd1FVSkRNRU13UmtGRVFrRXpSZyJ9.eyJodHRwczovL2FwaS5vcGVuYWkuY29tL3Byb2ZpbGUiOnsiZW1haWwiOiJlbW1hZ3VvQGJlcmtlbGV5LmVkdSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlfSwiaHR0cHM6Ly9hcGkub3BlbmFpLmNvbS9hdXRoIjp7InVzZXJfaWQiOiJ1c2VyLUp4TW40YnljU3ZWMGhwY3dOZmtjRzRhWCJ9LCJpc3MiOiJodHRwczovL2F1dGgwLm9wZW5haS5jb20vIiwic3ViIjoiZ29vZ2xlLW9hdXRoMnwxMTQxNzEyMDg3OTkxMzA3NjAxNjYiLCJhdWQiOlsiaHR0cHM6Ly9hcGkub3BlbmFpLmNvbS92MSIsImh0dHBzOi8vb3BlbmFpLm9wZW5haS5hdXRoMGFwcC5jb20vdXNlcmluZm8iXSwiaWF0IjoxNjg1NDE1Mjk5LCJleHAiOjE2ODY2MjQ4OTksImF6cCI6IlRkSkljYmUxNldvVEh0Tjk1bnl5d2g1RTR5T282SXRHIiwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCBtb2RlbC5yZWFkIG1vZGVsLnJlcXVlc3Qgb3JnYW5pemF0aW9uLnJlYWQgb3JnYW5pemF0aW9uLndyaXRlIn0", 17 | // signer: string = "OPENAI" 18 | 19 | signature: string = "hi0TaowIDbmo-MlHUxsgwxxLaJthvWo6QC990ix8dLMGExozkZLRwZhUFRpvx6gVDg55pSAjjiRVAfwZ9E4UKduhO5QE9bt6dI9_VZgyKoadolHJDjivPnQElhIhd2gqTtH_fovf1-hAzkExlPCZYX1icbPcpU9dQrkV3lzzLG4QwfDaux3mcmMwCifQEYdqpqpf9z2oQpvp5z9tbIOTakupTBMPxS2eLwQbIo3IjM3BHdetqNfPZBmb5MPIw6wWN4-dDGyo1Z-ODw5LmPOYY1Cuqpr3w3bBzytt8K-PTvtfpEGiPLFk7DXQNDRY5n8s29V-derC4SBY0lqfBg3miQ", 20 | msg: string = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImVtbWFndW9AYmVya2VsZXkuZWR1IiwiaWF0IjoxNjg4MjQyNDMyLCJpc3MiOiJ1cm46ZXhhbXBsZTppc3N1ZXIiLCJhdWQiOiJ1cm46ZXhhbXBsZTphdWRpZW5jZSIsImV4cCI6MTY4ODI0OTYzMn0", 21 | signer: string = "JWT_CLIENT" 22 | ): Promise { 23 | console.log("🚀 ~ signature", signature); 24 | const sig = BigInt("0x" + Buffer.from(signature, "base64").toString("hex")); 25 | const message = Buffer.from(msg); 26 | const period_idx_num = BigInt(msg.indexOf(".")); 27 | 28 | const { domain: domainStr, domain_idx: domain_index } = findDomain(msg); 29 | const domain = Buffer.from(domainStr ?? ""); 30 | const domain_idx_num = BigInt(domain_index ?? 0); 31 | 32 | const { timestamp: timeStr, time_index: timestamp_idx } = 33 | findTimestampInJSON(msg); 34 | const timestamp = BigInt(timeStr); 35 | const timestamp_idx_num = BigInt(timestamp_idx ?? 0); 36 | 37 | let currentKey; 38 | 39 | if (signer == "JWT_CLIENT") { 40 | currentKey = JWT_CLIENT_PUBKEY; 41 | } else if (signer == "OPENAI") { 42 | currentKey = OPENAI_PUBKEY 43 | } 44 | 45 | const pubKeyData = pki.publicKeyFromPem(currentKey); 46 | 47 | const modulus = BigInt(pubKeyData.n.toString()); 48 | const fin_result = await getCircuitInputs( 49 | sig, 50 | modulus, 51 | message, 52 | period_idx_num, 53 | domain_idx_num, 54 | domain, 55 | timestamp, 56 | timestamp_idx_num 57 | ); 58 | 59 | return fin_result.circuitInputs; 60 | } 61 | 62 | export interface ICircuitInputs { 63 | message?: string[]; 64 | modulus?: string[]; 65 | signature?: string[]; 66 | message_padded_bytes?: string; 67 | period_idx?: string; 68 | domain_idx?: string; 69 | domain?: string[]; 70 | time?: string; 71 | time_idx?: string; 72 | } 73 | 74 | function assert(cond: boolean, errorMessage: string) { 75 | if (!cond) { 76 | throw new Error(errorMessage); 77 | } 78 | } 79 | 80 | // Works only on 32 bit sha text lengths 81 | function int32toBytes(num: number): Uint8Array { 82 | const arr = new ArrayBuffer(4); // an Int32 takes 4 bytes 83 | const view = new DataView(arr); 84 | view.setUint32(0, num, false); // byteOffset = 0; litteEndian = false 85 | return new Uint8Array(arr); 86 | } 87 | 88 | // Works only on 32 bit sha text lengths 89 | function int8toBytes(num: number): Uint8Array { 90 | const arr = new ArrayBuffer(1); // an Int8 takes 4 bytes 91 | const view = new DataView(arr); 92 | view.setUint8(0, num); // byteOffset = 0; litteEndian = false 93 | return new Uint8Array(arr); 94 | } 95 | 96 | // converts ascii to string 97 | function AsciiArrayToString(arr: Buffer) { 98 | let str = ""; 99 | for (let i = 0; i < arr.length; i++) { 100 | str += String.fromCharCode(arr[i]); 101 | } 102 | return str; 103 | } 104 | 105 | // find email domain in msg 106 | function findDomain(msg: string) { 107 | let domain_idx; 108 | let domain; 109 | var s = Buffer.from(msg, "base64"); 110 | var json = AsciiArrayToString(s); 111 | const email_regex = /([-a-zA-Z._+]+)@([-a-zA-Z]+).([a-zA-Z]+)/; 112 | const match = json.match(email_regex); 113 | 114 | if (match) { 115 | domain = match[2]; // [0] = whole group, then capture groups 116 | let email_index = match.index; 117 | if (email_index) domain_idx = match[0].indexOf(domain) + email_index; 118 | } 119 | return { domain, domain_idx }; 120 | } 121 | 122 | function findTimestampInJSON(msg: string) { 123 | var s = Buffer.from(msg, "base64"); 124 | var json = AsciiArrayToString(s); 125 | let time_index = json.indexOf(`"exp":`) + 6; 126 | let timestamp = json.substring(time_index, time_index + 10); 127 | 128 | time_index += 1; 129 | 130 | return { timestamp, time_index }; 131 | } 132 | 133 | function mergeUInt8Arrays(a1: Uint8Array, a2: Uint8Array): Uint8Array { 134 | // sum of individual array lengths 135 | var mergedArray = new Uint8Array(a1.length + a2.length); 136 | mergedArray.set(a1); 137 | mergedArray.set(a2, a1.length); 138 | return mergedArray; 139 | } 140 | 141 | // Puts an end selector, a bunch of 0s, then the length, then fill the rest with 0s. 142 | async function sha256Pad( 143 | prehash_prepad_m: Uint8Array, 144 | maxShaBytes: number 145 | ): Promise<[Uint8Array, number]> { 146 | const length_bits = prehash_prepad_m.length * 8; // bytes to bits 147 | const length_in_bytes = int32toBytes(length_bits); 148 | prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, int8toBytes(2 ** 7)); 149 | while ( 150 | (prehash_prepad_m.length * 8 + length_in_bytes.length * 8) % 512 !== 151 | 0 152 | ) { 153 | prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, int8toBytes(0)); 154 | } 155 | prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, length_in_bytes); 156 | assert( 157 | (prehash_prepad_m.length * 8) % 512 === 0, 158 | "Padding did not compconste properly!" 159 | ); 160 | const messageLen = prehash_prepad_m.length; 161 | while (prehash_prepad_m.length < maxShaBytes) { 162 | prehash_prepad_m = mergeUInt8Arrays(prehash_prepad_m, int32toBytes(0)); 163 | } 164 | assert( 165 | prehash_prepad_m.length === maxShaBytes, 166 | "Padding to max length did not compconste properly!" 167 | ); 168 | 169 | return [prehash_prepad_m, messageLen]; 170 | } 171 | 172 | async function Uint8ArrayToCharArray(a: Uint8Array): Promise { 173 | return Array.from(a).map((x) => x.toString()); 174 | } 175 | 176 | async function Uint8ArrayToString(a: Uint8Array): Promise { 177 | return Array.from(a) 178 | .map((x) => x.toString()) 179 | .join(";"); 180 | } 181 | 182 | async function partialSha( 183 | msg: Uint8Array, 184 | msgLen: number 185 | ): Promise { 186 | const shaGadget = new Hash(); 187 | return await shaGadget.update(msg, msgLen).cacheState(); 188 | } 189 | 190 | export interface CircuitInputsResponse { 191 | valid: { 192 | validSignatureFormat?: boolean; 193 | validMessage?: boolean; 194 | }; 195 | circuitInputs: ICircuitInputs; 196 | } 197 | 198 | export async function getCircuitInputs( 199 | rsa_signature: BigInt, 200 | rsa_modulus: BigInt, 201 | msg: Buffer, 202 | period_idx_num: BigInt, 203 | domain_idx_num: BigInt, 204 | domain_raw: Buffer, 205 | timestamp: BigInt, 206 | timestamp_idx_num: BigInt 207 | ): Promise { 208 | const modulusBigInt = rsa_modulus; 209 | // Message is the header + payload 210 | const prehash_message_string = msg; 211 | const signatureBigInt = rsa_signature; 212 | 213 | // Perform conversions 214 | const prehashBytesUnpadded = 215 | typeof prehash_message_string == "string" 216 | ? new TextEncoder().encode(prehash_message_string) 217 | : Uint8Array.from(prehash_message_string); 218 | 219 | // Sha add padding 220 | const [messagePadded, messagePaddedLen] = await sha256Pad( 221 | prehashBytesUnpadded, 222 | MAX_MSG_PADDED_BYTES 223 | ); 224 | 225 | // domain padding 226 | const domainUnpadded = 227 | typeof domain_raw == "string" 228 | ? new TextEncoder().encode(domain_raw) 229 | : Uint8Array.from(domain_raw); 230 | 231 | const zerosPadArray = new Uint8Array(30 - domainUnpadded.length); 232 | const domainPadded = new Uint8Array([...domainUnpadded, ...zerosPadArray]); 233 | 234 | // Ensure SHA manual unpadded is running the correct function 235 | const shaOut = await partialSha(messagePadded, messagePaddedLen); 236 | assert( 237 | (await Uint8ArrayToString(shaOut)) === 238 | (await Uint8ArrayToString( 239 | Uint8Array.from(await shaHash(prehashBytesUnpadded)) 240 | )), 241 | "SHA256 calculation did not match!" 242 | ); 243 | 244 | // Compute identity revealer 245 | const modulus = toCircomBigIntBytes(modulusBigInt); 246 | const signature = toCircomBigIntBytes(signatureBigInt); 247 | 248 | const message_padded_bytes = messagePaddedLen.toString(); 249 | const message = await Uint8ArrayToCharArray(messagePadded); // Packed into 1 byte signals 250 | const domain = await Uint8ArrayToCharArray(domainPadded); 251 | const period_idx = period_idx_num.toString(); 252 | const domain_idx = domain_idx_num.toString(); 253 | 254 | const time = timestamp.toString(); 255 | const time_idx = timestamp_idx_num.toString(); 256 | 257 | const circuitInputs = { 258 | message, 259 | modulus, 260 | signature, 261 | message_padded_bytes, 262 | period_idx, 263 | domain_idx, 264 | domain, 265 | time, 266 | time_idx, 267 | }; 268 | return { 269 | circuitInputs, 270 | valid: {}, 271 | }; 272 | } 273 | 274 | if (typeof require !== "undefined" && require.main === module) { 275 | const circuitInputs = generate_inputs().then((res) => { 276 | console.log("Writing to file..."); 277 | fs.writeFileSync(`./inputs/jwt_client.json`, JSON.stringify(res), { flag: "w" }); 278 | }); 279 | } 280 | -------------------------------------------------------------------------------- /circuits/bigint_func.circom: -------------------------------------------------------------------------------- 1 | pragma circom 2.0.2; 2 | 3 | function isNegative(x) { 4 | // half babyjubjub field size 5 | return x > 10944121435919637611123202872628637544274182200208017171849102093287904247808 ? 1 : 0; 6 | } 7 | 8 | function div_ceil(m, n) { 9 | var ret = 0; 10 | if (m % n == 0) { 11 | ret = m \ n; 12 | } else { 13 | ret = m \ n + 1; 14 | } 15 | return ret; 16 | } 17 | 18 | function log_ceil(n) { 19 | var n_temp = n; 20 | for (var i = 0; i < 254; i++) { 21 | if (n_temp == 0) { 22 | return i; 23 | } 24 | n_temp = n_temp \ 2; 25 | } 26 | return 254; 27 | } 28 | 29 | function SplitFn(in, n, m) { 30 | return [in % (1 << n), (in \ (1 << n)) % (1 << m)]; 31 | } 32 | 33 | function SplitThreeFn(in, n, m, k) { 34 | return [in % (1 << n), (in \ (1 << n)) % (1 << m), (in \ (1 << n + m)) % (1 << k)]; 35 | } 36 | 37 | // m bits per overflowed register (values are potentially negative) 38 | // n bits per properly-sized register 39 | // in has k registers 40 | // out has k + ceil(m/n) - 1 + 1 registers. highest-order potentially negative, 41 | // all others are positive 42 | // - 1 since the last register is included in the last ceil(m/n) array 43 | // + 1 since the carries from previous registers could push you over 44 | function getProperRepresentation(m, n, k, in) { 45 | var ceilMN = div_ceil(m, n); 46 | 47 | var out[100]; // should be out[k + ceilMN] 48 | assert(k + ceilMN < 100); 49 | for (var i = 0; i < k; i++) { 50 | out[i] = in[i]; 51 | } 52 | for (var i = k; i < 100; i++) { 53 | out[i] = 0; 54 | } 55 | assert(n <= m); 56 | for (var i = 0; i+1 < k + ceilMN; i++) { 57 | assert((1 << m) >= out[i] && out[i] >= -(1 << m)); 58 | var shifted_val = out[i] + (1 << m); 59 | assert(0 <= shifted_val && shifted_val <= (1 << (m+1))); 60 | out[i] = shifted_val & ((1 << n) - 1); 61 | out[i+1] += (shifted_val >> n) - (1 << (m - n)); 62 | } 63 | 64 | return out; 65 | } 66 | 67 | // Evaluate polynomial a at point x 68 | function poly_eval(len, a, x) { 69 | var v = 0; 70 | for (var i = 0; i < len; i++) { 71 | v += a[i] * (x ** i); 72 | } 73 | return v; 74 | } 75 | 76 | // Interpolate a degree len-1 polynomial given its evaluations at 0..len-1 77 | function poly_interp(len, v) { 78 | assert(len <= 200); 79 | var out[200]; 80 | for (var i = 0; i < len; i++) { 81 | out[i] = 0; 82 | } 83 | 84 | // Product_{i=0..len-1} (x-i) 85 | var full_poly[201]; 86 | full_poly[0] = 1; 87 | for (var i = 0; i < len; i++) { 88 | full_poly[i+1] = 0; 89 | for (var j = i; j >= 0; j--) { 90 | full_poly[j+1] += full_poly[j]; 91 | full_poly[j] *= -i; 92 | } 93 | } 94 | 95 | for (var i = 0; i < len; i++) { 96 | var cur_v = 1; 97 | for (var j = 0; j < len; j++) { 98 | if (i == j) { 99 | // do nothing 100 | } else { 101 | cur_v *= i-j; 102 | } 103 | } 104 | cur_v = v[i] / cur_v; 105 | 106 | var cur_rem = full_poly[len]; 107 | for (var j = len-1; j >= 0; j--) { 108 | out[j] += cur_v * cur_rem; 109 | cur_rem = full_poly[j] + i * cur_rem; 110 | } 111 | assert(cur_rem == 0); 112 | } 113 | 114 | return out; 115 | } 116 | 117 | // 1 if true, 0 if false 118 | function long_gt(n, k, a, b) { 119 | for (var i = k - 1; i >= 0; i--) { 120 | if (a[i] > b[i]) { 121 | return 1; 122 | } 123 | if (a[i] < b[i]) { 124 | return 0; 125 | } 126 | } 127 | return 0; 128 | } 129 | 130 | // n bits per register 131 | // a has k registers 132 | // b has k registers 133 | // a >= b 134 | function long_sub(n, k, a, b) { 135 | var diff[100]; 136 | var borrow[100]; 137 | for (var i = 0; i < k; i++) { 138 | if (i == 0) { 139 | if (a[i] >= b[i]) { 140 | diff[i] = a[i] - b[i]; 141 | borrow[i] = 0; 142 | } else { 143 | diff[i] = a[i] - b[i] + (1 << n); 144 | borrow[i] = 1; 145 | } 146 | } else { 147 | if (a[i] >= b[i] + borrow[i - 1]) { 148 | diff[i] = a[i] - b[i] - borrow[i - 1]; 149 | borrow[i] = 0; 150 | } else { 151 | diff[i] = (1 << n) + a[i] - b[i] - borrow[i - 1]; 152 | borrow[i] = 1; 153 | } 154 | } 155 | } 156 | return diff; 157 | } 158 | 159 | // a is a n-bit scalar 160 | // b has k registers 161 | function long_scalar_mult(n, k, a, b) { 162 | var out[100]; 163 | for (var i = 0; i < 100; i++) { 164 | out[i] = 0; 165 | } 166 | for (var i = 0; i < k; i++) { 167 | var temp = out[i] + (a * b[i]); 168 | out[i] = temp % (1 << n); 169 | out[i + 1] = out[i + 1] + temp \ (1 << n); 170 | } 171 | return out; 172 | } 173 | 174 | 175 | // n bits per register 176 | // a has k + m registers 177 | // b has k registers 178 | // out[0] has length m + 1 -- quotient 179 | // out[1] has length k -- remainder 180 | // implements algorithm of https://people.eecs.berkeley.edu/~fateman/282/F%20Wright%20notes/week4.pdf 181 | function long_div(n, k, m, a, b){ 182 | var out[2][100]; 183 | m += k; 184 | while (b[k-1] == 0) { 185 | out[1][k] = 0; 186 | k--; 187 | assert(k > 0); 188 | } 189 | m -= k; 190 | 191 | var remainder[200]; 192 | for (var i = 0; i < m + k; i++) { 193 | remainder[i] = a[i]; 194 | } 195 | 196 | var mult[200]; 197 | var dividend[200]; 198 | for (var i = m; i >= 0; i--) { 199 | if (i == m) { 200 | dividend[k] = 0; 201 | for (var j = k - 1; j >= 0; j--) { 202 | dividend[j] = remainder[j + m]; 203 | } 204 | } else { 205 | for (var j = k; j >= 0; j--) { 206 | dividend[j] = remainder[j + i]; 207 | } 208 | } 209 | 210 | out[0][i] = short_div(n, k, dividend, b); 211 | 212 | var mult_shift[100] = long_scalar_mult(n, k, out[0][i], b); 213 | var subtrahend[200]; 214 | for (var j = 0; j < m + k; j++) { 215 | subtrahend[j] = 0; 216 | } 217 | for (var j = 0; j <= k; j++) { 218 | if (i + j < m + k) { 219 | subtrahend[i + j] = mult_shift[j]; 220 | } 221 | } 222 | remainder = long_sub(n, m + k, remainder, subtrahend); 223 | } 224 | for (var i = 0; i < k; i++) { 225 | out[1][i] = remainder[i]; 226 | } 227 | out[1][k] = 0; 228 | 229 | return out; 230 | } 231 | 232 | // n bits per register 233 | // a has k + 1 registers 234 | // b has k registers 235 | // assumes leading digit of b is at least 2 ** (n - 1) 236 | // 0 <= a < (2**n) * b 237 | function short_div_norm(n, k, a, b) { 238 | var qhat = (a[k] * (1 << n) + a[k - 1]) \ b[k - 1]; 239 | if (qhat > (1 << n) - 1) { 240 | qhat = (1 << n) - 1; 241 | } 242 | 243 | var mult[100] = long_scalar_mult(n, k, qhat, b); 244 | if (long_gt(n, k + 1, mult, a) == 1) { 245 | mult = long_sub(n, k + 1, mult, b); 246 | if (long_gt(n, k + 1, mult, a) == 1) { 247 | return qhat - 2; 248 | } else { 249 | return qhat - 1; 250 | } 251 | } else { 252 | return qhat; 253 | } 254 | } 255 | 256 | // n bits per register 257 | // a has k + 1 registers 258 | // b has k registers 259 | // assumes leading digit of b is non-zero 260 | // 0 <= a < (2**n) * b 261 | function short_div(n, k, a, b) { 262 | var scale = (1 << n) \ (1 + b[k - 1]); 263 | 264 | // k + 2 registers now 265 | var norm_a[200] = long_scalar_mult(n, k + 1, scale, a); 266 | // k + 1 registers now 267 | var norm_b[200] = long_scalar_mult(n, k, scale, b); 268 | 269 | var ret; 270 | if (norm_b[k] != 0) { 271 | ret = short_div_norm(n, k + 1, norm_a, norm_b); 272 | } else { 273 | ret = short_div_norm(n, k, norm_a, norm_b); 274 | } 275 | return ret; 276 | } 277 | 278 | // n bits per register 279 | // a and b both have k registers 280 | // out[0] has length 2 * k 281 | // adapted from BigMulShortLong and LongToShortNoEndCarry2 witness computation 282 | function prod(n, k, a, b) { 283 | // first compute the intermediate values. taken from BigMulShortLong 284 | var prod_val[100]; // length is 2 * k - 1 285 | for (var i = 0; i < 2 * k - 1; i++) { 286 | prod_val[i] = 0; 287 | if (i < k) { 288 | for (var a_idx = 0; a_idx <= i; a_idx++) { 289 | prod_val[i] = prod_val[i] + a[a_idx] * b[i - a_idx]; 290 | } 291 | } else { 292 | for (var a_idx = i - k + 1; a_idx < k; a_idx++) { 293 | prod_val[i] = prod_val[i] + a[a_idx] * b[i - a_idx]; 294 | } 295 | } 296 | } 297 | 298 | // now do a bunch of carrying to make sure registers not overflowed. taken from LongToShortNoEndCarry2 299 | var out[100]; // length is 2 * k 300 | 301 | var split[100][3]; // first dimension has length 2 * k - 1 302 | for (var i = 0; i < 2 * k - 1; i++) { 303 | split[i] = SplitThreeFn(prod_val[i], n, n, n); 304 | } 305 | 306 | var carry[100]; // length is 2 * k - 1 307 | carry[0] = 0; 308 | out[0] = split[0][0]; 309 | if (2 * k - 1 > 1) { 310 | var sumAndCarry[2] = SplitFn(split[0][1] + split[1][0], n, n); 311 | out[1] = sumAndCarry[0]; 312 | carry[1] = sumAndCarry[1]; 313 | } 314 | if (2 * k - 1 > 2) { 315 | for (var i = 2; i < 2 * k - 1; i++) { 316 | var sumAndCarry[2] = SplitFn(split[i][0] + split[i-1][1] + split[i-2][2] + carry[i-1], n, n); 317 | out[i] = sumAndCarry[0]; 318 | carry[i] = sumAndCarry[1]; 319 | } 320 | out[2 * k - 1] = split[2*k-2][1] + split[2*k-3][2] + carry[2*k-2]; 321 | } 322 | return out; 323 | } 324 | 325 | // n bits per register 326 | // a has k registers 327 | // p has k registers 328 | // e has k registers 329 | // k * n <= 500 330 | // p is a prime 331 | // computes a^e mod p 332 | function mod_exp(n, k, a, p, e) { 333 | var eBits[500]; // length is k * n 334 | for (var i = 0; i < k; i++) { 335 | for (var j = 0; j < n; j++) { 336 | eBits[j + n * i] = (e[i] >> j) & 1; 337 | } 338 | } 339 | 340 | var out[100]; // length is k 341 | for (var i = 0; i < 100; i++) { 342 | out[i] = 0; 343 | } 344 | out[0] = 1; 345 | 346 | // repeated squaring 347 | for (var i = k * n - 1; i >= 0; i--) { 348 | // multiply by a if bit is 0 349 | if (eBits[i] == 1) { 350 | var temp[200]; // length 2 * k 351 | temp = prod(n, k, out, a); 352 | var temp2[2][100]; 353 | temp2 = long_div(n, k, k, temp, p); 354 | out = temp2[1]; 355 | } 356 | 357 | // square, unless we're at the end 358 | if (i > 0) { 359 | var temp[200]; // length 2 * k 360 | temp = prod(n, k, out, out); 361 | var temp2[2][100]; 362 | temp2 = long_div(n, k, k, temp, p); 363 | out = temp2[1]; 364 | } 365 | 366 | } 367 | return out; 368 | } 369 | 370 | // n bits per register 371 | // a has k registers 372 | // p has k registers 373 | // k * n <= 500 374 | // p is a prime 375 | // if a == 0 mod p, returns 0 376 | // else computes inv = a^(p-2) mod p 377 | function mod_inv(n, k, a, p) { 378 | var isZero = 1; 379 | for (var i = 0; i < k; i++) { 380 | if (a[i] != 0) { 381 | isZero = 0; 382 | } 383 | } 384 | if (isZero == 1) { 385 | var ret[100]; 386 | for (var i = 0; i < k; i++) { 387 | ret[i] = 0; 388 | } 389 | return ret; 390 | } 391 | 392 | var pCopy[100]; 393 | for (var i = 0; i < 100; i++) { 394 | if (i < k) { 395 | pCopy[i] = p[i]; 396 | } else { 397 | pCopy[i] = 0; 398 | } 399 | } 400 | 401 | var two[100]; 402 | for (var i = 0; i < 100; i++) { 403 | two[i] = 0; 404 | } 405 | two[0] = 2; 406 | 407 | var pMinusTwo[100]; 408 | pMinusTwo = long_sub(n, k, pCopy, two); // length k 409 | var out[100]; 410 | out = mod_exp(n, k, a, pCopy, pMinusTwo); 411 | return out; 412 | } 413 | 414 | // a, b and out are all n bits k registers 415 | function long_sub_mod_p(n, k, a, b, p){ 416 | var gt = long_gt(n, k, a, b); 417 | var tmp[100]; 418 | if(gt){ 419 | tmp = long_sub(n, k, a, b); 420 | } 421 | else{ 422 | tmp = long_sub(n, k, b, a); 423 | } 424 | var out[2][100]; 425 | for(var i = k;i < 2 * k; i++){ 426 | tmp[i] = 0; 427 | } 428 | out = long_div(n, k, k, tmp, p); 429 | if(gt==0){ 430 | tmp = long_sub(n, k, p, out[1]); 431 | } 432 | return tmp; 433 | } 434 | 435 | // a, b, p and out are all n bits k registers 436 | function prod_mod_p(n, k, a, b, p){ 437 | var tmp[100]; 438 | var result[2][100]; 439 | tmp = prod(n, k, a, b); 440 | result = long_div(n, k, k, tmp, p); 441 | return result[1]; 442 | } 443 | -------------------------------------------------------------------------------- /regex_to_circom/regex_to_dfa.js: -------------------------------------------------------------------------------- 1 | /*jslint browser: true*/ 2 | /*global require, exports*/ 3 | // import { STRING_PRESELECTOR } from "../src/helpers/constants.ts"; 4 | import { minDfa, nfaToDfa, regexToNfa } from "./lexical"; 5 | 6 | /** This section defines helper regex components -- to edit the regex used, edit the return 7 | * of the test_regex function. 8 | * All of the relevant regexes are in the main repo README. 9 | */ 10 | 11 | // Helper components 12 | 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"; 13 | 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"; 14 | const r0to9 = "0|1|2|3|4|5|6|7|8|9"; 15 | const alphanum = `${a2z}|${A2Z}|${r0to9}`; 16 | 17 | const key_chars = `(${a2z})`; 18 | const catch_all = 19 | "(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)"; 20 | const catch_all_without_semicolon = 21 | "(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)"; 22 | 23 | const email_chars = `${alphanum}|_|.|-`; 24 | const base_64 = `(${alphanum}|\\+|/|=)`; 25 | const word_char = `(${alphanum}|_)`; 26 | 27 | const a2z_nosep = "abcdefghijklmnopqrstuvwxyz"; 28 | const A2Z_nosep = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 29 | const a2f_nosep = "abcdef"; 30 | const A2F_nosep = "ABCDEF"; 31 | const r0to9_nosep = "0123456789"; 32 | 33 | // TODO: Note that this is replicated code in lexical.js as well 34 | // Note that ^ has to be manually replaced with \x80 in the regex 35 | const escapeMap = { n: "\n", r: "\r", t: "\t", v: "\v", f: "\f" }; 36 | let whitespace = Object.values(escapeMap); 37 | const slash_s = whitespace.join("|"); 38 | 39 | // The test_regex function whose return needs to be edited 40 | // Note that in order to specify some strings in regex, we must use \\ to escape \'s. 41 | // For instance, matching the literal + is represented as \\+. 42 | // However, matching the literal \r (ascii 60) character is still \r 43 | // Matching \ then an r as two characters would be \\r in the js string literal 44 | function test_regex() { 45 | // let to_from_regex_old = '(\r\n|\x80)(to|from):([A-Za-z0-9 _."@-]+<)?[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.]+>?\r\n'; 46 | // let regex = `\r\ndkim-signature:(${key_chars}=${catch_all_without_semicolon}+; )+bh=${base_64}+; `; 47 | // 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 48 | let email_address_regex = `(@[a-zA-Z0-9.-]+)"`; 49 | 50 | // ------- HEADER/SIGNATURE REGEX -------- 51 | 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`; 52 | let sig_regex = `\r\ndkim-signature:(${key_chars}=${catch_all_without_semicolon}+; )+bh=${base_64}+; `; 53 | 54 | // let full_header_regex = order_invariant_header_regex_raw + sig_regex; 55 | // let raw_regex = order_invariant_header_regex_raw; 56 | // let regex = regexToMinDFASpec(raw_regex) + sig_regex; 57 | // console.log(format_regex_printable(sig_regex)); 58 | 59 | // -------- SUBJECT REGEXES -------- 60 | // This raw subject line (with \\ replaced with \) can be put into regexr.com to test new match strings and sanity check that it works 61 | // TODO: Other valid chars in email addresses: #$%!^/&*, outlined at https://ladedu.com/valid-characters-for-email-addresses-the-complete-list/ and in the RFC 62 | 63 | // -- SEND SPECIFIC REGEXES -- 64 | // let send_specific_raw_subject_regex = `((\r\n)|^)subject:[Ss]end (\$)?[0-9]+(.[0-9]+)? [a-zA-Z]+ to (${email_address_regex}|0x[0-9a-fA_F]+)\r\n`; 65 | // let raw_subject_regex = `((\r\n)|^)subject:[a-zA-Z]+ (\\$)?[0-9]+(.[0-9]+)? [a-zA-Z]+ to (([a-zA-Z0-9._%\\+-=]+@[a-zA-Z0-9.-]+)|0x[0-9]+)\r\n`; 66 | // Input: ((\\r\\n)|^)subject:[Ss]end (\$)?[0-9]+(.[0-9]+)? [a-zA-Z]+ to (([a-zA-Z0-9._%\+-=]+@[a-zA-Z0-9.-]+)|0x[0-9]+)\\r\\n 67 | // This can be pasted into the first line of https://zkregex.com/min_dfa (after replacing \\ -> \) 68 | // ((\\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 69 | // console.log(raw_subject_regex); 70 | 71 | // -- GENERIC WALLET COMMANDS -- 72 | let raw_subject_regex = `((\r\n)|^)subject:[a-zA-Z]+ (\\$)?[0-9]+(.[0-9]+)? [a-zA-Z]+ to (${email_address_regex}|0x[0-9a-fA_F]+)\r\n`; 73 | 74 | // -------- OTHER FIELD REGEXES -------- 75 | let raw_from_regex = `(\r\n|^)from:([A-Za-z0-9 _.,"@-]+)<[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.-]+>\r\n`; 76 | // let message_id_regex = `(\r\n|^)message-id:<[=@.\\+_-a-zA-Z0-9]+>\r\n`; 77 | 78 | // -------- TWITTER BODY REGEX --------- 79 | // let regex = STRING_PRESELECTOR + `${word_char}+`; 80 | 81 | // ---------- DEPRECATAED REGEXES ---------- 82 | // 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))+)`; 83 | // 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 84 | // let old_regex = '(\r\n|\x80)(to|from):([A-Za-z0-9 _."@-]+<)?[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.]+>?\r\n'; 85 | // let regex = `(\n|^)(to|from):((${email_chars}|"|@| )+<)?(${email_chars})+@(${email_chars})+>?\r`; 86 | // let regex = `(\r\n|^)(to|from):((${email_chars}|"|@| )+<)?(${email_chars})+@(${email_chars})+>?\r\n`; 87 | // '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|\\+|/|=)+; ' 88 | // let regex = 'hello(0|1|2|3|4|5|6|7|8|9)+world'; 89 | 90 | // TODO: this did not work 91 | // let raw_timestamp_regex = `"exp"\s*:\s*[0-9]+` 92 | 93 | // ignoring the spaces 94 | let raw_timestamp_regex = `"exp":(0|1|2|3|4|5|6|7|8|9)+` 95 | 96 | // --------- FINAL CONVERSION --------- 97 | // console.log(format_regex_printable(raw_subject_regex)); 98 | let regex = regexToMinDFASpec(raw_timestamp_regex); 99 | // console.log(format_regex_printable(regex)); 100 | 101 | return regex; 102 | } 103 | 104 | // Escapes and prints regexes (might be buggy) 105 | function format_regex_printable(s) { 106 | const escaped_string_json = JSON.stringify(s); 107 | const escaped_string = escaped_string_json.slice(1, escaped_string_json.length - 1); 108 | return escaped_string 109 | .replaceAll("\\\\\\\\", "\\") 110 | .replaceAll("\\\\", "\\") 111 | .replaceAll("\\|", "\\\\|") 112 | .replaceAll("/", "\\/") 113 | .replaceAll("\u000b", "\\♥") 114 | .replaceAll("|[|", "|\\[|") 115 | .replaceAll("|]|", "|\\]|") 116 | .replaceAll("|.|", "|\\.|") 117 | .replaceAll("|$|", "|\\$|") 118 | .replaceAll("|^|", "|\\^|"); 119 | // let escaped = escape_whitespace(escape_whitespace(s.replaceAll("\\\\", "ZZZZZZZ"))); 120 | // let fixed = escaped.replaceAll("\\(", "(").replaceAll("\\)", ")").replaceAll("\\+", "+").replaceAll("\\*", "*").replaceAll("\\?", "?"); 121 | } 122 | 123 | // Note that this is not complete and very case specific i.e. can only handle a-z and a-f, and not a-c. 124 | // This function expands [] sections to convert values for https://zkregex.com/min_dfa 125 | // The input is a regex with [] and special characters (i.e. the first line of min_dfa tool) 126 | // The output is expanded regexes without any special characters 127 | function regexToMinDFASpec(str) { 128 | // Replace all A-Z with A2Z etc 129 | // TODO: Upstream this to min_dfa 130 | let combined_nosep = str 131 | .replaceAll("A-Z", A2Z_nosep) 132 | .replaceAll("a-z", a2z_nosep) 133 | .replaceAll("A-F", A2F_nosep) 134 | .replaceAll("a-f", a2f_nosep) 135 | .replaceAll("0-9", r0to9_nosep) 136 | .replaceAll("\\w", A2Z_nosep + r0to9_nosep + a2z_nosep + "_") 137 | .replaceAll("\\d", r0to9_nosep) 138 | .replaceAll("\\s", slash_s); 139 | // .replaceAll("\\w", A2Z_nosep + r0to9_nosep + a2z_nosep); // I think that there's also an underscore here 140 | 141 | function addPipeInsideBrackets(str) { 142 | let result = ""; 143 | let insideBrackets = false; 144 | for (let i = 0; i < str.length; i++) { 145 | if (str[i] === "[") { 146 | result += str[i]; 147 | insideBrackets = true; 148 | continue; 149 | } else if (str[i] === "]") { 150 | insideBrackets = false; 151 | } 152 | let str_to_add = str[i]; 153 | if (str[i] === "\\") { 154 | i++; 155 | str_to_add += str[i]; 156 | } 157 | result += insideBrackets ? "|" + str_to_add : str_to_add; 158 | } 159 | return result.replaceAll("[|", "[").replaceAll("[", "(").replaceAll("]", ")"); 160 | } 161 | 162 | // function makeCurlyBracesFallback(str) { 163 | // let result = ""; 164 | // let insideBrackets = false; 165 | // for (let i = 0; i < str.length; i++) { 166 | // if (str[i] === "{") { 167 | // result += str[i]; 168 | // insideBrackets = true; 169 | // continue; 170 | // } else if (str[i] === "}") { 171 | // insideBrackets = false; 172 | // } 173 | // result += insideBrackets ? "|" + str[i] : str[i]; 174 | // } 175 | // return result.replaceAll("[|", "[").replaceAll("[", "(").replaceAll("]", ")"); 176 | // } 177 | 178 | function checkIfBracketsHavePipes(str) { 179 | let result = true; 180 | let insideBrackets = false; 181 | let insideParens = 0; 182 | let indexAt = 0; 183 | for (let i = 0; i < str.length; i++) { 184 | if (indexAt >= str.length) break; 185 | if (str[indexAt] === "[") { 186 | insideBrackets = true; 187 | indexAt++; 188 | continue; 189 | } else if (str[indexAt] === "]") { 190 | insideBrackets = false; 191 | } 192 | if (str[indexAt] === "(") { 193 | insideParens++; 194 | } else if (str[indexAt] === ")") { 195 | insideParens--; 196 | } 197 | if (insideBrackets) { 198 | if (str[indexAt] === "|") { 199 | indexAt++; 200 | } else { 201 | result = false; 202 | return result; 203 | } 204 | } 205 | if (!insideParens && str[indexAt] === "|") { 206 | console.log("Error: | outside of parens!"); 207 | } 208 | if (str[indexAt] === "\\") { 209 | indexAt++; 210 | } 211 | indexAt++; 212 | } 213 | return result; 214 | } 215 | 216 | let combined; 217 | if (!checkIfBracketsHavePipes(combined_nosep)) { 218 | // console.log("Adding pipes within brackets between everything!"); 219 | combined = addPipeInsideBrackets(combined_nosep); 220 | if (!checkIfBracketsHavePipes(combined)) { 221 | console.log("Did not add brackets correctly!"); 222 | } 223 | } else { 224 | combined = combined_nosep; 225 | } 226 | 227 | return combined; 228 | } 229 | 230 | function toNature(col) { 231 | var i, 232 | j, 233 | base = "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 234 | result = 0; 235 | if ("1" <= col[0] && col[0] <= "9") { 236 | result = parseInt(col, 10); 237 | } else { 238 | for (i = 0, j = col.length - 1; i < col.length; i += 1, j -= 1) { 239 | result += Math.pow(base.length, j) * (base.indexOf(col[i]) + 1); 240 | } 241 | } 242 | return result; 243 | } 244 | 245 | function printGraphForRegex(regex) { 246 | let nfa = regexToNfa(regex); 247 | let dfa = minDfa(nfaToDfa(nfa)); 248 | 249 | var i, 250 | j, 251 | states = {}, 252 | nodes = [], 253 | stack = [dfa], 254 | symbols = [], 255 | top; 256 | 257 | while (stack.length > 0) { 258 | top = stack.pop(); 259 | if (!states.hasOwnProperty(top.id)) { 260 | states[top.id] = top; 261 | top.nature = toNature(top.id); 262 | nodes.push(top); 263 | for (i = 0; i < top.edges.length; i += 1) { 264 | if (top.edges[i][0] !== "ϵ" && symbols.indexOf(top.edges[i][0]) < 0) { 265 | symbols.push(top.edges[i][0]); 266 | } 267 | stack.push(top.edges[i][1]); 268 | } 269 | } 270 | } 271 | nodes.sort(function (a, b) { 272 | return a.nature - b.nature; 273 | }); 274 | symbols.sort(); 275 | 276 | let graph = []; 277 | for (let i = 0; i < nodes.length; i += 1) { 278 | let curr = {}; 279 | curr.type = nodes[i].type; 280 | curr.edges = {}; 281 | for (let j = 0; j < symbols.length; j += 1) { 282 | if (nodes[i].trans.hasOwnProperty(symbols[j])) { 283 | curr.edges[symbols[j]] = nodes[i].trans[symbols[j]].nature - 1; 284 | } 285 | } 286 | graph[nodes[i].nature - 1] = curr; 287 | } 288 | 289 | console.log(JSON.stringify(graph)); 290 | return JSON.stringify(graph); 291 | } 292 | 293 | let regex = test_regex(); 294 | printGraphForRegex(regex); 295 | 296 | if (typeof require === "function") { 297 | exports.regexToMinDFASpec = regexToMinDFASpec; 298 | exports.toNature = toNature; 299 | } 300 | -------------------------------------------------------------------------------- /scripts/fast-sha256.ts: -------------------------------------------------------------------------------- 1 | // SHA-256 (+ HMAC and PBKDF2) for JavaScript. 2 | // 3 | // Written in 2014-2016 by Dmitry Chestnykh. 4 | // Public domain, no warranty. 5 | // 6 | // Functions (accept and return Uint8Arrays): 7 | // 8 | // sha256(message) -> hash 9 | // sha256.hmac(key, message) -> mac 10 | // sha256.pbkdf2(password, salt, rounds, dkLen) -> dk 11 | // 12 | // Classes: 13 | // 14 | // new sha256.Hash() 15 | // new sha256.HMAC(key) 16 | // 17 | export const digestLength: number = 32; 18 | export const blockSize: number = 64; 19 | 20 | // SHA-256 constants 21 | const K = new Uint32Array([ 22 | 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 23 | 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 24 | 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 25 | 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 26 | 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 27 | 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 28 | 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 29 | 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 30 | 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 31 | 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 32 | 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 33 | 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 34 | 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 35 | ]); 36 | 37 | function hashBlocks(w: Int32Array, v: Int32Array, p: Uint8Array, pos: number, len: number): number { 38 | let a: number, b: number, c: number, d: number, e: number, 39 | f: number, g: number, h: number, u: number, i: number, 40 | j: number, t1: number, t2: number; 41 | while (len >= 64) { 42 | a = v[0]; 43 | b = v[1]; 44 | c = v[2]; 45 | d = v[3]; 46 | e = v[4]; 47 | f = v[5]; 48 | g = v[6]; 49 | h = v[7]; 50 | 51 | for (i = 0; i < 16; i++) { 52 | j = pos + i * 4; 53 | w[i] = (((p[j] & 0xff) << 24) | ((p[j + 1] & 0xff) << 16) | 54 | ((p[j + 2] & 0xff) << 8) | (p[j + 3] & 0xff)); 55 | } 56 | 57 | for (i = 16; i < 64; i++) { 58 | u = w[i - 2]; 59 | t1 = (u >>> 17 | u << (32 - 17)) ^ (u >>> 19 | u << (32 - 19)) ^ (u >>> 10); 60 | 61 | u = w[i - 15]; 62 | t2 = (u >>> 7 | u << (32 - 7)) ^ (u >>> 18 | u << (32 - 18)) ^ (u >>> 3); 63 | 64 | w[i] = (t1 + w[i - 7] | 0) + (t2 + w[i - 16] | 0); 65 | } 66 | 67 | for (i = 0; i < 64; i++) { 68 | t1 = (((((e >>> 6 | e << (32 - 6)) ^ (e >>> 11 | e << (32 - 11)) ^ 69 | (e >>> 25 | e << (32 - 25))) + ((e & f) ^ (~e & g))) | 0) + 70 | ((h + ((K[i] + w[i]) | 0)) | 0)) | 0; 71 | 72 | t2 = (((a >>> 2 | a << (32 - 2)) ^ (a >>> 13 | a << (32 - 13)) ^ 73 | (a >>> 22 | a << (32 - 22))) + ((a & b) ^ (a & c) ^ (b & c))) | 0; 74 | 75 | h = g; 76 | g = f; 77 | f = e; 78 | e = (d + t1) | 0; 79 | d = c; 80 | c = b; 81 | b = a; 82 | a = (t1 + t2) | 0; 83 | } 84 | 85 | v[0] += a; 86 | v[1] += b; 87 | v[2] += c; 88 | v[3] += d; 89 | v[4] += e; 90 | v[5] += f; 91 | v[6] += g; 92 | v[7] += h; 93 | 94 | pos += 64; 95 | len -= 64; 96 | } 97 | return pos; 98 | } 99 | 100 | // Hash implements SHA256 hash algorithm. 101 | export class Hash { 102 | digestLength: number = digestLength; 103 | blockSize: number = blockSize; 104 | 105 | // Note: Int32Array is used instead of Uint32Array for performance reasons. 106 | private state: Int32Array = new Int32Array(8); // hash state 107 | private temp: Int32Array = new Int32Array(64); // temporary state 108 | private buffer: Uint8Array = new Uint8Array(128); // buffer for data to hash 109 | private bufferLength: number = 0; // number of bytes in buffer 110 | private bytesHashed: number = 0; // number of total bytes hashed 111 | 112 | finished: boolean = false; // indicates whether the hash was finalized 113 | 114 | constructor() { 115 | this.reset(); 116 | } 117 | 118 | // Resets hash state making it possible 119 | // to re-use this instance to hash other data. 120 | reset(): this { 121 | this.state[0] = 0x6a09e667; 122 | this.state[1] = 0xbb67ae85; 123 | this.state[2] = 0x3c6ef372; 124 | this.state[3] = 0xa54ff53a; 125 | this.state[4] = 0x510e527f; 126 | this.state[5] = 0x9b05688c; 127 | this.state[6] = 0x1f83d9ab; 128 | this.state[7] = 0x5be0cd19; 129 | this.bufferLength = 0; 130 | this.bytesHashed = 0; 131 | this.finished = false; 132 | return this; 133 | } 134 | 135 | // Cleans internal buffers and re-initializes hash state. 136 | clean() { 137 | for (let i = 0; i < this.buffer.length; i++) { 138 | this.buffer[i] = 0; 139 | } 140 | for (let i = 0; i < this.temp.length; i++) { 141 | this.temp[i] = 0; 142 | } 143 | this.reset(); 144 | } 145 | 146 | // Updates hash state with the given data. 147 | // 148 | // Optionally, length of the data can be specified to hash 149 | // fewer bytes than data.length. 150 | // 151 | // Throws error when trying to update already finalized hash: 152 | // instance must be reset to use it again. 153 | update(data: Uint8Array, dataLength: number = data.length): this { 154 | if (this.finished) { 155 | throw new Error("SHA256: can't update because hash was finished."); 156 | } 157 | let dataPos = 0; 158 | this.bytesHashed += dataLength; 159 | if (this.bufferLength > 0) { 160 | while (this.bufferLength < 64 && dataLength > 0) { 161 | this.buffer[this.bufferLength++] = data[dataPos++]; 162 | dataLength--; 163 | } 164 | if (this.bufferLength === 64) { 165 | hashBlocks(this.temp, this.state, this.buffer, 0, 64); 166 | this.bufferLength = 0; 167 | } 168 | } 169 | if (dataLength >= 64) { 170 | dataPos = hashBlocks(this.temp, this.state, data, dataPos, dataLength); 171 | dataLength %= 64; 172 | } 173 | while (dataLength > 0) { 174 | this.buffer[this.bufferLength++] = data[dataPos++]; 175 | dataLength--; 176 | } 177 | return this; 178 | } 179 | 180 | // Finalizes hash state and puts hash into out. 181 | // 182 | // If hash was already finalized, puts the same value. 183 | finish(out: Uint8Array): this { 184 | if (!this.finished) { 185 | const bytesHashed = this.bytesHashed; 186 | const left = this.bufferLength; 187 | const bitLenHi = (bytesHashed / 0x20000000) | 0; 188 | const bitLenLo = bytesHashed << 3; 189 | const padLength = (bytesHashed % 64 < 56) ? 64 : 128; 190 | 191 | this.buffer[left] = 0x80; 192 | for (let i = left + 1; i < padLength - 8; i++) { 193 | this.buffer[i] = 0; 194 | } 195 | this.buffer[padLength - 8] = (bitLenHi >>> 24) & 0xff; 196 | this.buffer[padLength - 7] = (bitLenHi >>> 16) & 0xff; 197 | this.buffer[padLength - 6] = (bitLenHi >>> 8) & 0xff; 198 | this.buffer[padLength - 5] = (bitLenHi >>> 0) & 0xff; 199 | this.buffer[padLength - 4] = (bitLenLo >>> 24) & 0xff; 200 | this.buffer[padLength - 3] = (bitLenLo >>> 16) & 0xff; 201 | this.buffer[padLength - 2] = (bitLenLo >>> 8) & 0xff; 202 | this.buffer[padLength - 1] = (bitLenLo >>> 0) & 0xff; 203 | 204 | hashBlocks(this.temp, this.state, this.buffer, 0, padLength); 205 | 206 | this.finished = true; 207 | } 208 | 209 | for (let i = 0; i < 8; i++) { 210 | out[i * 4 + 0] = (this.state[i] >>> 24) & 0xff; 211 | out[i * 4 + 1] = (this.state[i] >>> 16) & 0xff; 212 | out[i * 4 + 2] = (this.state[i] >>> 8) & 0xff; 213 | out[i * 4 + 3] = (this.state[i] >>> 0) & 0xff; 214 | } 215 | 216 | return this; 217 | } 218 | 219 | // Returns the final hash digest. 220 | digest(): Uint8Array { 221 | const out = new Uint8Array(this.digestLength); 222 | this.finish(out); 223 | return out; 224 | } 225 | 226 | // Returns the current hash state. 227 | cacheState(): Uint8Array { 228 | const out32 = new Uint32Array(this.state.length); 229 | this._saveState(out32); 230 | const out = new Uint8Array(this.state.length * 4); 231 | for (let i = 0; i < 8; i++) { 232 | out[i * 4 + 0] = (this.state[i] >>> 24) & 0xff; 233 | out[i * 4 + 1] = (this.state[i] >>> 16) & 0xff; 234 | out[i * 4 + 2] = (this.state[i] >>> 8) & 0xff; 235 | out[i * 4 + 3] = (this.state[i] >>> 0) & 0xff; 236 | } 237 | return out; 238 | } 239 | 240 | // Internal function for use in HMAC for optimization. 241 | _saveState(out: Uint32Array) { 242 | for (let i = 0; i < this.state.length; i++) { 243 | out[i] = this.state[i]; 244 | } 245 | } 246 | 247 | // Internal function for use in HMAC for optimization. 248 | _restoreState(from: Uint32Array, bytesHashed: number) { 249 | for (let i = 0; i < this.state.length; i++) { 250 | this.state[i] = from[i]; 251 | } 252 | this.bytesHashed = bytesHashed; 253 | this.finished = false; 254 | this.bufferLength = 0; 255 | } 256 | } 257 | 258 | // HMAC implements HMAC-SHA256 message authentication algorithm. 259 | export class HMAC { 260 | private inner: Hash = new Hash(); 261 | private outer: Hash = new Hash(); 262 | 263 | blockSize: number = this.inner.blockSize; 264 | digestLength: number = this.inner.digestLength; 265 | 266 | // Copies of hash states after keying. 267 | // Need for quick reset without hashing they key again. 268 | private istate: Uint32Array; 269 | private ostate: Uint32Array; 270 | 271 | constructor(key: Uint8Array) { 272 | const pad = new Uint8Array(this.blockSize); 273 | if (key.length > this.blockSize) { 274 | (new Hash()).update(key).finish(pad).clean(); 275 | } else { 276 | for (let i = 0; i < key.length; i++) { 277 | pad[i] = key[i]; 278 | } 279 | } 280 | for (let i = 0; i < pad.length; i++) { 281 | pad[i] ^= 0x36; 282 | } 283 | this.inner.update(pad); 284 | 285 | for (let i = 0; i < pad.length; i++) { 286 | pad[i] ^= 0x36 ^ 0x5c; 287 | } 288 | this.outer.update(pad); 289 | 290 | this.istate = new Uint32Array(8); 291 | this.ostate = new Uint32Array(8); 292 | 293 | this.inner._saveState(this.istate); 294 | this.outer._saveState(this.ostate); 295 | 296 | for (let i = 0; i < pad.length; i++) { 297 | pad[i] = 0; 298 | } 299 | } 300 | 301 | // Returns HMAC state to the state initialized with key 302 | // to make it possible to run HMAC over the other data with the same 303 | // key without creating a new instance. 304 | reset(): this { 305 | this.inner._restoreState(this.istate, this.inner.blockSize); 306 | this.outer._restoreState(this.ostate, this.outer.blockSize); 307 | return this; 308 | } 309 | 310 | // Cleans HMAC state. 311 | clean() { 312 | for (let i = 0; i < this.istate.length; i++) { 313 | this.ostate[i] = this.istate[i] = 0; 314 | } 315 | this.inner.clean(); 316 | this.outer.clean(); 317 | } 318 | 319 | // Updates state with provided data. 320 | update(data: Uint8Array): this { 321 | this.inner.update(data); 322 | return this; 323 | } 324 | 325 | // Finalizes HMAC and puts the result in out. 326 | finish(out: Uint8Array): this { 327 | if (this.outer.finished) { 328 | this.outer.finish(out); 329 | } else { 330 | this.inner.finish(out); 331 | this.outer.update(out, this.digestLength).finish(out); 332 | } 333 | return this; 334 | } 335 | 336 | // Returns message authentication code. 337 | digest(): Uint8Array { 338 | const out = new Uint8Array(this.digestLength); 339 | this.finish(out); 340 | return out; 341 | } 342 | } 343 | 344 | // Returns SHA256 hash of data. 345 | export function hash(data: Uint8Array): Uint8Array { 346 | const h = (new Hash()).update(data); 347 | const digest = h.digest(); 348 | h.clean(); 349 | return digest; 350 | } 351 | 352 | // Function hash is both available as module.hash and as default export. 353 | export default hash; 354 | 355 | // Returns HMAC-SHA256 of data under the key. 356 | export function hmac(key: Uint8Array, data: Uint8Array) { 357 | const h = (new HMAC(key)).update(data); 358 | const digest = h.digest(); 359 | h.clean(); 360 | return digest; 361 | } 362 | 363 | // Fills hkdf buffer like this: 364 | // T(1) = HMAC-Hash(PRK, T(0) | info | 0x01) 365 | function fillBuffer(buffer: Uint8Array, hmac: HMAC, info: Uint8Array | undefined, counter: Uint8Array) { 366 | // Counter is a byte value: check if it overflowed. 367 | const num = counter[0]; 368 | 369 | if (num === 0) { 370 | throw new Error("hkdf: cannot expand more"); 371 | } 372 | 373 | // Prepare HMAC instance for new data with old key. 374 | hmac.reset(); 375 | 376 | // Hash in previous output if it was generated 377 | // (i.e. counter is greater than 1). 378 | if (num > 1) { 379 | hmac.update(buffer); 380 | } 381 | 382 | // Hash in info if it exists. 383 | if (info) { 384 | hmac.update(info); 385 | } 386 | 387 | // Hash in the counter. 388 | hmac.update(counter); 389 | 390 | // Output result to buffer and clean HMAC instance. 391 | hmac.finish(buffer); 392 | 393 | // Increment counter inside typed array, this works properly. 394 | counter[0]++; 395 | } 396 | 397 | const hkdfSalt = new Uint8Array(digestLength); // Filled with zeroes. 398 | export function hkdf(key: Uint8Array, salt: Uint8Array = hkdfSalt, info?: Uint8Array, length: number = 32) { 399 | const counter = new Uint8Array([1]); 400 | 401 | // HKDF-Extract uses salt as HMAC key, and key as data. 402 | const okm = hmac(salt, key); 403 | 404 | // Initialize HMAC for expanding with extracted key. 405 | // Ensure no collisions with `hmac` function. 406 | const hmac_ = new HMAC(okm); 407 | 408 | // Allocate buffer. 409 | const buffer = new Uint8Array(hmac_.digestLength); 410 | let bufpos = buffer.length; 411 | 412 | const out = new Uint8Array(length); 413 | for (let i = 0; i < length; i++) { 414 | if (bufpos === buffer.length) { 415 | fillBuffer(buffer, hmac_, info, counter); 416 | bufpos = 0; 417 | } 418 | out[i] = buffer[bufpos++]; 419 | } 420 | 421 | hmac_.clean(); 422 | buffer.fill(0); 423 | counter.fill(0); 424 | return out; 425 | } 426 | 427 | // Derives a key from password and salt using PBKDF2-HMAC-SHA256 428 | // with the given number of iterations. 429 | // 430 | // The number of bytes returned is equal to dkLen. 431 | // 432 | // (For better security, avoid dkLen greater than hash length - 32 bytes). 433 | export function pbkdf2(password: Uint8Array, salt: Uint8Array, iterations: number, dkLen: number) { 434 | const prf = new HMAC(password); 435 | const len = prf.digestLength; 436 | const ctr = new Uint8Array(4); 437 | const t = new Uint8Array(len); 438 | const u = new Uint8Array(len); 439 | const dk = new Uint8Array(dkLen); 440 | 441 | for (let i = 0; i * len < dkLen; i++) { 442 | let c = i + 1; 443 | ctr[0] = (c >>> 24) & 0xff; 444 | ctr[1] = (c >>> 16) & 0xff; 445 | ctr[2] = (c >>> 8) & 0xff; 446 | ctr[3] = (c >>> 0) & 0xff; 447 | prf.reset(); 448 | prf.update(salt); 449 | prf.update(ctr); 450 | prf.finish(u); 451 | for (let j = 0; j < len; j++) { 452 | t[j] = u[j]; 453 | } 454 | for (let j = 2; j <= iterations; j++) { 455 | prf.reset(); 456 | prf.update(u).finish(u); 457 | for (let k = 0; k < len; k++) { 458 | t[k] ^= u[k]; 459 | } 460 | } 461 | for (let j = 0; j < len && i * len + j < dkLen; j++) { 462 | dk[i * len + j] = t[j]; 463 | } 464 | } 465 | for (let i = 0; i < len; i++) { 466 | t[i] = u[i] = 0; 467 | } 468 | for (let i = 0; i < 4; i++) { 469 | ctr[i] = 0; 470 | } 471 | prf.clean(); 472 | return dk; 473 | } 474 | -------------------------------------------------------------------------------- /circuits/bigint.circom: -------------------------------------------------------------------------------- 1 | pragma circom 2.0.2; 2 | 3 | include "../node_modules/circomlib/circuits/comparators.circom"; 4 | include "../node_modules/circomlib/circuits/bitify.circom"; 5 | include "../node_modules/circomlib/circuits/gates.circom"; 6 | 7 | include "bigint_func.circom"; 8 | 9 | // addition mod 2**n with carry bit 10 | template ModSum(n) { 11 | assert(n <= 252); 12 | signal input a; 13 | signal input b; 14 | signal output sum; 15 | signal output carry; 16 | 17 | component n2b = Num2Bits(n + 1); 18 | n2b.in <== a + b; 19 | carry <== n2b.out[n]; 20 | sum <== a + b - carry * (1 << n); 21 | } 22 | 23 | // a - b 24 | template ModSub(n) { 25 | assert(n <= 252); 26 | signal input a; 27 | signal input b; 28 | signal output out; 29 | signal output borrow; 30 | component lt = LessThan(n); 31 | lt.in[0] <== a; 32 | lt.in[1] <== b; 33 | borrow <== lt.out; 34 | out <== borrow * (1 << n) + a - b; 35 | } 36 | 37 | // a - b - c 38 | // assume a - b - c + 2**n >= 0 39 | template ModSubThree(n) { 40 | assert(n + 2 <= 253); 41 | signal input a; 42 | signal input b; 43 | signal input c; 44 | assert(a - b - c + (1 << n) >= 0); 45 | signal output out; 46 | signal output borrow; 47 | signal b_plus_c; 48 | b_plus_c <== b + c; 49 | component lt = LessThan(n + 1); 50 | lt.in[0] <== a; 51 | lt.in[1] <== b_plus_c; 52 | borrow <== lt.out; 53 | out <== borrow * (1 << n) + a - b_plus_c; 54 | } 55 | 56 | template ModSumThree(n) { 57 | assert(n + 2 <= 253); 58 | signal input a; 59 | signal input b; 60 | signal input c; 61 | signal output sum; 62 | signal output carry; 63 | 64 | component n2b = Num2Bits(n + 2); 65 | n2b.in <== a + b + c; 66 | carry <== n2b.out[n] + 2 * n2b.out[n + 1]; 67 | sum <== a + b + c - carry * (1 << n); 68 | } 69 | 70 | template ModSumFour(n) { 71 | assert(n + 2 <= 253); 72 | signal input a; 73 | signal input b; 74 | signal input c; 75 | signal input d; 76 | signal output sum; 77 | signal output carry; 78 | 79 | component n2b = Num2Bits(n + 2); 80 | n2b.in <== a + b + c + d; 81 | carry <== n2b.out[n] + 2 * n2b.out[n + 1]; 82 | sum <== a + b + c + d - carry * (1 << n); 83 | } 84 | 85 | // product mod 2**n with carry 86 | template ModProd(n) { 87 | assert(n <= 126); 88 | signal input a; 89 | signal input b; 90 | signal output prod; 91 | signal output carry; 92 | 93 | component n2b = Num2Bits(2 * n); 94 | n2b.in <== a * b; 95 | 96 | component b2n1 = Bits2Num(n); 97 | component b2n2 = Bits2Num(n); 98 | var i; 99 | for (i = 0; i < n; i++) { 100 | b2n1.in[i] <== n2b.out[i]; 101 | b2n2.in[i] <== n2b.out[i + n]; 102 | } 103 | prod <== b2n1.out; 104 | carry <== b2n2.out; 105 | } 106 | 107 | // split a n + m bit input into two outputs 108 | template Split(n, m) { 109 | assert(n <= 126); 110 | signal input in; 111 | signal output small; 112 | signal output big; 113 | 114 | small <-- in % (1 << n); 115 | big <-- in \ (1 << n); 116 | 117 | component n2b_small = Num2Bits(n); 118 | n2b_small.in <== small; 119 | component n2b_big = Num2Bits(m); 120 | n2b_big.in <== big; 121 | 122 | in === small + big * (1 << n); 123 | } 124 | 125 | // split a n + m + k bit input into three outputs 126 | template SplitThree(n, m, k) { 127 | assert(n <= 126); 128 | signal input in; 129 | signal output small; 130 | signal output medium; 131 | signal output big; 132 | 133 | small <-- in % (1 << n); 134 | medium <-- (in \ (1 << n)) % (1 << m); 135 | big <-- in \ (1 << n + m); 136 | 137 | component n2b_small = Num2Bits(n); 138 | n2b_small.in <== small; 139 | component n2b_medium = Num2Bits(m); 140 | n2b_medium.in <== medium; 141 | component n2b_big = Num2Bits(k); 142 | n2b_big.in <== big; 143 | 144 | in === small + medium * (1 << n) + big * (1 << n + m); 145 | } 146 | 147 | // a[i], b[i] in 0... 2**n-1 148 | // represent a = a[0] + a[1] * 2**n + .. + a[k - 1] * 2**(n * k) 149 | template BigAdd(n, k) { 150 | assert(n <= 252); 151 | signal input a[k]; 152 | signal input b[k]; 153 | signal output out[k + 1]; 154 | 155 | component unit0 = ModSum(n); 156 | unit0.a <== a[0]; 157 | unit0.b <== b[0]; 158 | out[0] <== unit0.sum; 159 | 160 | component unit[k - 1]; 161 | for (var i = 1; i < k; i++) { 162 | unit[i - 1] = ModSumThree(n); 163 | unit[i - 1].a <== a[i]; 164 | unit[i - 1].b <== b[i]; 165 | if (i == 1) { 166 | unit[i - 1].c <== unit0.carry; 167 | } else { 168 | unit[i - 1].c <== unit[i - 2].carry; 169 | } 170 | out[i] <== unit[i - 1].sum; 171 | } 172 | out[k] <== unit[k - 2].carry; 173 | } 174 | 175 | // a and b have n-bit registers 176 | // a has ka registers, each with NONNEGATIVE ma-bit values (ma can be > n) 177 | // b has kb registers, each with NONNEGATIVE mb-bit values (mb can be > n) 178 | // out has ka + kb - 1 registers, each with (ma + mb + ceil(log(max(ka, kb))))-bit values 179 | template BigMultNoCarry(n, ma, mb, ka, kb) { 180 | assert(ma + mb <= 253); 181 | signal input a[ka]; 182 | signal input b[kb]; 183 | signal output out[ka + kb - 1]; 184 | 185 | var prod_val[ka + kb - 1]; 186 | for (var i = 0; i < ka + kb - 1; i++) { 187 | prod_val[i] = 0; 188 | } 189 | for (var i = 0; i < ka; i++) { 190 | for (var j = 0; j < kb; j++) { 191 | prod_val[i + j] += a[i] * b[j]; 192 | } 193 | } 194 | for (var i = 0; i < ka + kb - 1; i++) { 195 | out[i] <-- prod_val[i]; 196 | } 197 | 198 | var a_poly[ka + kb - 1]; 199 | var b_poly[ka + kb - 1]; 200 | var out_poly[ka + kb - 1]; 201 | for (var i = 0; i < ka + kb - 1; i++) { 202 | out_poly[i] = 0; 203 | a_poly[i] = 0; 204 | b_poly[i] = 0; 205 | for (var j = 0; j < ka + kb - 1; j++) { 206 | out_poly[i] = out_poly[i] + out[j] * (i ** j); 207 | } 208 | for (var j = 0; j < ka; j++) { 209 | a_poly[i] = a_poly[i] + a[j] * (i ** j); 210 | } 211 | for (var j = 0; j < kb; j++) { 212 | b_poly[i] = b_poly[i] + b[j] * (i ** j); 213 | } 214 | } 215 | for (var i = 0; i < ka + kb - 1; i++) { 216 | out_poly[i] === a_poly[i] * b_poly[i]; 217 | } 218 | } 219 | 220 | 221 | // in[i] contains longs 222 | // out[i] contains shorts 223 | template LongToShortNoEndCarry(n, k) { 224 | assert(n <= 126); 225 | signal input in[k]; 226 | signal output out[k+1]; 227 | 228 | var split[k][3]; 229 | for (var i = 0; i < k; i++) { 230 | split[i] = SplitThreeFn(in[i], n, n, n); 231 | } 232 | 233 | var carry[k]; 234 | carry[0] = 0; 235 | out[0] <-- split[0][0]; 236 | if (k == 1) { 237 | out[1] <-- split[0][1]; 238 | } 239 | if (k > 1) { 240 | var sumAndCarry[2] = SplitFn(split[0][1] + split[1][0], n, n); 241 | out[1] <-- sumAndCarry[0]; 242 | carry[1] = sumAndCarry[1]; 243 | } 244 | if (k == 2) { 245 | out[2] <-- split[1][1] + split[0][2] + carry[1]; 246 | } 247 | if (k > 2) { 248 | for (var i = 2; i < k; i++) { 249 | var sumAndCarry[2] = SplitFn(split[i][0] + split[i-1][1] + split[i-2][2] + carry[i-1], n, n); 250 | out[i] <-- sumAndCarry[0]; 251 | carry[i] = sumAndCarry[1]; 252 | } 253 | out[k] <-- split[k-1][1] + split[k-2][2] + carry[k-1]; 254 | } 255 | 256 | component outRangeChecks[k+1]; 257 | for (var i = 0; i < k+1; i++) { 258 | outRangeChecks[i] = Num2Bits(n); 259 | outRangeChecks[i].in <== out[i]; 260 | } 261 | 262 | signal runningCarry[k]; 263 | component runningCarryRangeChecks[k]; 264 | runningCarry[0] <-- (in[0] - out[0]) / (1 << n); 265 | runningCarryRangeChecks[0] = Num2Bits(n + log_ceil(k)); 266 | runningCarryRangeChecks[0].in <== runningCarry[0]; 267 | runningCarry[0] * (1 << n) === in[0] - out[0]; 268 | for (var i = 1; i < k; i++) { 269 | runningCarry[i] <-- (in[i] - out[i] + runningCarry[i-1]) / (1 << n); 270 | runningCarryRangeChecks[i] = Num2Bits(n + log_ceil(k)); 271 | runningCarryRangeChecks[i].in <== runningCarry[i]; 272 | runningCarry[i] * (1 << n) === in[i] - out[i] + runningCarry[i-1]; 273 | } 274 | runningCarry[k-1] === out[k]; 275 | } 276 | 277 | template BigMult(n, k) { 278 | signal input a[k]; 279 | signal input b[k]; 280 | signal output out[2 * k]; 281 | 282 | component mult = BigMultNoCarry(n, n, n, k, k); 283 | for (var i = 0; i < k; i++) { 284 | mult.a[i] <== a[i]; 285 | mult.b[i] <== b[i]; 286 | } 287 | 288 | // no carry is possible in the highest order register 289 | component longshort = LongToShortNoEndCarry(n, 2 * k - 1); 290 | for (var i = 0; i < 2 * k - 1; i++) { 291 | longshort.in[i] <== mult.out[i]; 292 | } 293 | for (var i = 0; i < 2 * k; i++) { 294 | out[i] <== longshort.out[i]; 295 | } 296 | } 297 | 298 | template BigLessThan(n, k){ 299 | signal input a[k]; 300 | signal input b[k]; 301 | signal output out; 302 | 303 | component lt[k]; 304 | component eq[k]; 305 | for (var i = 0; i < k; i++) { 306 | lt[i] = LessThan(n); 307 | lt[i].in[0] <== a[i]; 308 | lt[i].in[1] <== b[i]; 309 | eq[i] = IsEqual(); 310 | eq[i].in[0] <== a[i]; 311 | eq[i].in[1] <== b[i]; 312 | } 313 | 314 | // ors[i] holds (lt[k - 1] || (eq[k - 1] && lt[k - 2]) .. || (eq[k - 1] && .. && lt[i])) 315 | // ands[i] holds (eq[k - 1] && .. && lt[i]) 316 | // eq_ands[i] holds (eq[k - 1] && .. && eq[i]) 317 | component ors[k - 1]; 318 | component ands[k - 1]; 319 | component eq_ands[k - 1]; 320 | for (var i = k - 2; i >= 0; i--) { 321 | ands[i] = AND(); 322 | eq_ands[i] = AND(); 323 | ors[i] = OR(); 324 | 325 | if (i == k - 2) { 326 | ands[i].a <== eq[k - 1].out; 327 | ands[i].b <== lt[k - 2].out; 328 | eq_ands[i].a <== eq[k - 1].out; 329 | eq_ands[i].b <== eq[k - 2].out; 330 | ors[i].a <== lt[k - 1].out; 331 | ors[i].b <== ands[i].out; 332 | } else { 333 | ands[i].a <== eq_ands[i + 1].out; 334 | ands[i].b <== lt[i].out; 335 | eq_ands[i].a <== eq_ands[i + 1].out; 336 | eq_ands[i].b <== eq[i].out; 337 | ors[i].a <== ors[i + 1].out; 338 | ors[i].b <== ands[i].out; 339 | } 340 | } 341 | out <== ors[0].out; 342 | } 343 | 344 | template BigIsEqual(k){ 345 | signal input in[2][k]; 346 | signal output out; 347 | component isEqual[k+1]; 348 | var sum = 0; 349 | for(var i = 0; i < k; i++){ 350 | isEqual[i] = IsEqual(); 351 | isEqual[i].in[0] <== in[0][i]; 352 | isEqual[i].in[1] <== in[1][i]; 353 | sum = sum + isEqual[i].out; 354 | } 355 | 356 | isEqual[k] = IsEqual(); 357 | isEqual[k].in[0] <== sum; 358 | isEqual[k].in[1] <== k; 359 | out <== isEqual[k].out; 360 | } 361 | 362 | // leading register of b should be non-zero 363 | template BigMod(n, k) { 364 | assert(n <= 126); 365 | signal input a[2 * k]; 366 | signal input b[k]; 367 | 368 | signal output div[k + 1]; 369 | signal output mod[k]; 370 | 371 | var longdiv[2][100] = long_div(n, k, k, a, b); 372 | for (var i = 0; i < k; i++) { 373 | div[i] <-- longdiv[0][i]; 374 | mod[i] <-- longdiv[1][i]; 375 | } 376 | div[k] <-- longdiv[0][k]; 377 | component range_checks[k + 1]; 378 | for (var i = 0; i <= k; i++) { 379 | range_checks[i] = Num2Bits(n); 380 | range_checks[i].in <== div[i]; 381 | } 382 | 383 | component mul = BigMult(n, k + 1); 384 | for (var i = 0; i < k; i++) { 385 | mul.a[i] <== div[i]; 386 | mul.b[i] <== b[i]; 387 | } 388 | mul.a[k] <== div[k]; 389 | mul.b[k] <== 0; 390 | 391 | component add = BigAdd(n, 2 * k + 2); 392 | for (var i = 0; i < 2 * k; i++) { 393 | add.a[i] <== mul.out[i]; 394 | if (i < k) { 395 | add.b[i] <== mod[i]; 396 | } else { 397 | add.b[i] <== 0; 398 | } 399 | } 400 | add.a[2 * k] <== mul.out[2 * k]; 401 | add.a[2 * k + 1] <== mul.out[2 * k + 1]; 402 | add.b[2 * k] <== 0; 403 | add.b[2 * k + 1] <== 0; 404 | 405 | for (var i = 0; i < 2 * k; i++) { 406 | add.out[i] === a[i]; 407 | } 408 | add.out[2 * k] === 0; 409 | add.out[2 * k + 1] === 0; 410 | 411 | component lt = BigLessThan(n, k); 412 | for (var i = 0; i < k; i++) { 413 | lt.a[i] <== mod[i]; 414 | lt.b[i] <== b[i]; 415 | } 416 | lt.out === 1; 417 | } 418 | 419 | // a[i], b[i] in 0... 2**n-1 420 | // represent a = a[0] + a[1] * 2**n + .. + a[k - 1] * 2**(n * k) 421 | // assume a >= b 422 | template BigSub(n, k) { 423 | assert(n <= 252); 424 | signal input a[k]; 425 | signal input b[k]; 426 | signal output out[k]; 427 | signal output underflow; 428 | 429 | component unit0 = ModSub(n); 430 | unit0.a <== a[0]; 431 | unit0.b <== b[0]; 432 | out[0] <== unit0.out; 433 | 434 | component unit[k - 1]; 435 | for (var i = 1; i < k; i++) { 436 | unit[i - 1] = ModSubThree(n); 437 | unit[i - 1].a <== a[i]; 438 | unit[i - 1].b <== b[i]; 439 | if (i == 1) { 440 | unit[i - 1].c <== unit0.borrow; 441 | } else { 442 | unit[i - 1].c <== unit[i - 2].borrow; 443 | } 444 | out[i] <== unit[i - 1].out; 445 | } 446 | underflow <== unit[k - 2].borrow; 447 | } 448 | 449 | // calculates (a - b) % p, where a, b < p 450 | // note: does not assume a >= b 451 | template BigSubModP(n, k){ 452 | assert(n <= 252); 453 | signal input a[k]; 454 | signal input b[k]; 455 | signal input p[k]; 456 | signal output out[k]; 457 | component sub = BigSub(n, k); 458 | for (var i = 0; i < k; i++){ 459 | sub.a[i] <== a[i]; 460 | sub.b[i] <== b[i]; 461 | } 462 | signal flag; 463 | flag <== sub.underflow; 464 | component add = BigAdd(n, k); 465 | for (var i = 0; i < k; i++){ 466 | add.a[i] <== sub.out[i]; 467 | add.b[i] <== flag * p[i]; 468 | } 469 | for (var i = 0; i < k; i++){ 470 | out[i] <== add.out[i]; 471 | } 472 | } 473 | 474 | template BigMultModP(n, k) { 475 | assert(n <= 252); 476 | signal input a[k]; 477 | signal input b[k]; 478 | signal input p[k]; 479 | signal output out[k]; 480 | 481 | component big_mult = BigMult(n, k); 482 | for (var i = 0; i < k; i++) { 483 | big_mult.a[i] <== a[i]; 484 | big_mult.b[i] <== b[i]; 485 | } 486 | component big_mod = BigMod(n, k); 487 | for (var i = 0; i < 2 * k; i++) { 488 | big_mod.a[i] <== big_mult.out[i]; 489 | } 490 | for (var i = 0; i < k; i++) { 491 | big_mod.b[i] <== p[i]; 492 | } 493 | for (var i = 0; i < k; i++) { 494 | out[i] <== big_mod.mod[i]; 495 | } 496 | } 497 | 498 | template BigModInv(n, k) { 499 | assert(n <= 252); 500 | signal input in[k]; 501 | signal input p[k]; 502 | signal output out[k]; 503 | 504 | // length k 505 | var inv[100] = mod_inv(n, k, in, p); 506 | for (var i = 0; i < k; i++) { 507 | out[i] <-- inv[i]; 508 | } 509 | component range_checks[k]; 510 | for (var i = 0; i < k; i++) { 511 | range_checks[i] = Num2Bits(n); 512 | range_checks[i].in <== out[i]; 513 | } 514 | 515 | component mult = BigMult(n, k); 516 | for (var i = 0; i < k; i++) { 517 | mult.a[i] <== in[i]; 518 | mult.b[i] <== out[i]; 519 | } 520 | component mod = BigMod(n, k); 521 | for (var i = 0; i < 2 * k; i++) { 522 | mod.a[i] <== mult.out[i]; 523 | } 524 | for (var i = 0; i < k; i++) { 525 | mod.b[i] <== p[i]; 526 | } 527 | mod.mod[0] === 1; 528 | for (var i = 1; i < k; i++) { 529 | mod.mod[i] === 0; 530 | } 531 | } 532 | 533 | // in[i] contains values in the range -2^(m-1) to 2^(m-1) 534 | // constrain that in[] as a big integer is zero 535 | // each limbs is n bits 536 | template CheckCarryToZero(n, m, k) { 537 | assert(k >= 2); 538 | 539 | var EPSILON = 3; 540 | 541 | assert(m + EPSILON <= 253); 542 | 543 | signal input in[k]; 544 | 545 | signal carry[k]; 546 | component carryRangeChecks[k]; 547 | for (var i = 0; i < k-1; i++){ 548 | carryRangeChecks[i] = Num2Bits(m + EPSILON - n); 549 | if( i == 0 ){ 550 | carry[i] <-- in[i] / (1<