Skip to content

Commit

Permalink
Add test for JwtVerifiter
Browse files Browse the repository at this point in the history
  • Loading branch information
wshino committed Oct 9, 2024
1 parent e71a93f commit dcceed2
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 2 deletions.
2 changes: 1 addition & 1 deletion packages/circuits/jwt-verifier-test.circom
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ pragma circom 2.1.6;

include "./jwt-verifier.circom";

component main = JWTVerifier(121, 17, 1024, 128, 896, 14, 605);
component main = JWTVerifier(121, 17, 1024, 128, 896, 72, 605);
4 changes: 3 additions & 1 deletion packages/circuits/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"build": "mkdir -p build && circom jwt-verifier-test.circom --r1cs --wasm --sym -l ../../node_modules -o ./build",
"publish": "yarn npm publish --access=public",
"test": "NODE_OPTIONS=--max_old_space_size=8192 jest --runInBand --detectOpenHandles --forceExit --verbose tests",
"dev-setup": "NODE_OPTIONS=--max_old_space_size=16384 npx ts-node scripts/dev-setup.ts --output ./build"
"dev-setup": "NODE_OPTIONS=--max_old_space_size=16384 npx ts-node scripts/dev-setup.ts --output ./build",
"gen-input": "NODE_OPTIONS=--max_old_space_size=8192 npx ts-node scripts/gen_input.ts"
},
"dependencies": {
"@zk-email/circuits": "6.1.5-nightly.2024-09-16",
Expand All @@ -33,6 +34,7 @@
"/helpers",
"/lib",
"/utils",
"/scripts",
"./jwt-verifier.circom"
],
"babel": {
Expand Down
181 changes: 181 additions & 0 deletions packages/circuits/scripts/gen-input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// @ts-ignore
import { program } from 'commander';
import { generateJWT } from '../../helpers/src/jwt'; // JWTを生成する関数のパスを指定
import { generateJWTVerifierInputs } from '../../helpers/src/input-generators'; // 入力を生成する関数のパスを指定
import { splitJWT } from "../../helpers/src/utils";
import fs from "fs";
const snarkjs = require("snarkjs");
import { promisify } from "util";
import path from "path";
const relayerUtils = require("@zk-email/relayer-utils");
import https from 'https';

program
.requiredOption(
"--input-file <string>",
"Path of a json file to write the generated input"
)
.requiredOption('-a, --accountCode <string>', 'Account code as bigint string')
.option('-h, --header <string>', 'JWT header as JSON string')
.option('-p, --payload <string>', 'JWT payload as JSON string')
.option('-m, --maxMessageLength <number>', 'Maximum message length', '1024')
.option("--silent", "No console logs")
.option("--prove", "Also generate proof");

program.parse(process.argv);

const options = program.opts();

function log(...message: any) {
if (!options.silent) {
console.log(...message);
}
}

async function main() {
const kid = BigInt("0x5aaff47c21d06e266cce395b2145c7c6d4730ea5");
const issuer = "random.website.com";
const timestamp = 1694989812;
const azp = "demo-client-id";
const email = "[email protected]";

const defaultHeader = {
alg: "RS256",
typ: "JWT",
kid: kid.toString(16),
};
const header = defaultHeader;
const defaultPayload = {
email,
iat: timestamp,
azp,
iss: issuer,
};
const payload = defaultPayload;
const accountCode = BigInt(options.accountCode);
const maxMessageLength = parseInt(options.maxMessageLength, 1024);


const { rawJWT, publicKey } = generateJWT(header, {
...payload,
nonce: "Send 0.1 ETH to [email protected]",
});

const jwtVerifierInputs = await generateJWTVerifierInputs(
rawJWT,
publicKey,
accountCode,
{ maxMessageLength }
);

console.log('JWT Verifier Inputs:', jwtVerifierInputs);

const publicKeyHash = relayerUtils.publicKeyHash(
"0x" + Buffer.from(publicKey.n, "base64").toString("hex")
);
console.log("publicKeyHash");
console.log(publicKeyHash);
const [, , signature] = splitJWT(rawJWT);
const expectedJwtNullifier = relayerUtils.emailNullifier(
"0x" + Buffer.from(signature, "base64").toString("hex")
);
console.log("expectedJwtNullifier");
console.log(expectedJwtNullifier);

if (!options.inputFile.endsWith(".json")) {
throw new Error("--input file path arg must end with .json");
}

// log("Generating Inputs for:", options);

// const circuitInputs = await genEmailCircuitInput(args.emailFile, args.accountCode, {
// maxHeaderLength: 1024,
// ignoreBodyHashCheck: true
// });
// log("\n\nGenerated Inputs:", circuitInputs, "\n\n");
const processedInputs = convertBigIntFieldsToString(jwtVerifierInputs);

await promisify(fs.writeFile)(options.inputFile, JSON.stringify(processedInputs, null, 2));

log("Inputs written to", options.inputFile);

if (options.prove) {
console.log("generate pub signal");
const fileContent = fs.readFileSync(options.inputFile as string, 'utf-8');
const jsonData = JSON.parse(fileContent);
const payload = JSON.stringify({ input: jsonData });
const urlObject = new URL("https://zkemail--jwt-prover-v0-1-0-flask-app.modal.run/prove/jwt");
const reqOptions = {
hostname: urlObject.hostname,
path: urlObject.pathname,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(payload)
}
};
await new Promise<void>((resolve, reject) => {
const req = https.request(reqOptions, (res) => {
let data = '';

res.on('data', (chunk) => {
data += chunk;
});

res.on('end', async () => {
try {
const dir = path.dirname(options.inputFile);
const responseJson = JSON.parse(data);
const proof = responseJson.proof;
// console.log(proof);
const publicSignals = responseJson.pub_signals;

await fs.promises.writeFile(
path.join(dir, "proof.json"),
JSON.stringify(convertBigIntFieldsToString(proof), null, 2)
);

await fs.promises.writeFile(
path.join(dir, "public.json"),
JSON.stringify(convertBigIntFieldsToString(publicSignals), null, 2)
);
console.log('Files written successfully');
resolve();
} catch (error) {
console.error('Error processing response:', error);
reject(error);
}
});
});

req.on('error', (error) => {
console.error('Error posting JSON data:', error);
reject(error);
});

req.write(payload);
req.end();
});

};
// Create the request

process.exit(0);
}

function convertBigIntFieldsToString(obj: any): any {
if (typeof obj === 'object' && obj !== null) {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [
key,
typeof value === 'bigint' ? value.toString() : value
])
);
}
return obj;
}

main().catch((err) => {
console.error("Error generating inputs", err);
process.exit(1);
});
3 changes: 3 additions & 0 deletions packages/contracts/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ out = "artifacts"
libs = ["../../node_modules", "lib"]
optimizer = true
optimizer-runs = 20_000
fs_permissions = [
{ access = "read", path = "./test/jwt_proofs" },
]

solc = "0.8.26"

Expand Down
74 changes: 74 additions & 0 deletions packages/contracts/test/JwtVerifier/JwtVerifier.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;

import "forge-std/Test.sol";
import "forge-std/console.sol";
import {EmailProof, JwtVerifier} from "../../src/utils/JwtVerifier.sol";
import {JwtGroth16Verifier} from "../../src/utils/JwtGroth16Verifier.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

contract JwtVerifierTest_verifyEmailProof is Test {
JwtVerifier verifier;

constructor() {}

function setUp() public {
JwtVerifier verifierImpl = new JwtVerifier();
JwtGroth16Verifier groth16Verifier = new JwtGroth16Verifier();
ERC1967Proxy verifierProxy = new ERC1967Proxy(
address(verifierImpl),
abi.encodeCall(
verifierImpl.initialize,
(msg.sender, address(groth16Verifier))
)
);
verifier = JwtVerifier(address(verifierProxy));
}

function test_verifyEmailProof() public {
// account code = 16351800486276213158813915254152097017375347006603152442842997572625254103242
console.log("test_verifyEmailProof");
EmailProof memory emailProof;
emailProof.domainName = "0x5aaff47c21d06e266cce395b2145c7c6d4730ea5|random.website.com|demo-client-id";
emailProof.publicKeyHash = bytes32(0x17b17b71ba34d6771b91f2689fddf7266d561d4dcc5d43174d0e100468d89685);
emailProof.timestamp = 1694989812;
emailProof.maskedCommand = "Send 0.1 ETH to [email protected]";
emailProof.emailNullifier = bytes32(0x24dc5e63ebcbbe243ef41484ec2d97a6ea130387702a9cad6aea2193457d5aec);
emailProof.accountSalt = bytes32(0x2426ca85629574124b746006d8d50f7e7c0e3a0d91a9cdf6c477e314ed14b8ca);
emailProof.isCodeExist = true;
emailProof.proof = proofToBytes(
string.concat(
vm.projectRoot(),
"/test/jwt_proofs/proof.json"
)
);
verifier.verifyEmailProof(emailProof);
}

function proofToBytes(
string memory proofPath
) internal view returns (bytes memory) {
string memory proofFile = vm.readFile(proofPath);
string[] memory pi_a = abi.decode(
vm.parseJson(proofFile, ".pi_a"),
(string[])
);
uint256[2] memory pA = [vm.parseUint(pi_a[0]), vm.parseUint(pi_a[1])];
string[][] memory pi_b = abi.decode(
vm.parseJson(proofFile, ".pi_b"),
(string[][])
);
uint256[2][2] memory pB = [
[vm.parseUint(pi_b[0][1]), vm.parseUint(pi_b[0][0])],
[vm.parseUint(pi_b[1][1]), vm.parseUint(pi_b[1][0])]
];
string[] memory pi_c = abi.decode(
vm.parseJson(proofFile, ".pi_c"),
(string[])
);
uint256[2] memory pC = [vm.parseUint(pi_c[0]), vm.parseUint(pi_c[1])];
bytes memory proof = abi.encode(pA, pB, pC);
return proof;
}

}

0 comments on commit dcceed2

Please sign in to comment.