Skip to content

Commit

Permalink
Merge pull request #32 from brolag/feature/add-circom-challenges
Browse files Browse the repository at this point in the history
feat: Added new circom challenges
  • Loading branch information
brolag committed Sep 13, 2024
2 parents d3a8d47 + a3d1857 commit 09814a9
Show file tree
Hide file tree
Showing 20 changed files with 845 additions and 15 deletions.
9 changes: 7 additions & 2 deletions packages/nextjs/app/engineering/circom/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,13 @@ export default async function Page({ params }: PageProps) {
<>
<Statement challengeId={challengeId} lang={"circom"} />
<hr className="border-0 h-px bg-blue-500 shadow-[0_0_10px_2px_rgba(59,130,246,0.7)]" />
<Verifier challengeId={challengeId} />
<hr className="border-0 h-px bg-blue-500 shadow-[0_0_10px_2px_rgba(59,130,246,0.7)] mb-10" />

{["2", "3", "4"].includes(params.slug) && (
<>
<Verifier challengeId={challengeId} />
<hr className="border-0 h-px bg-blue-500 shadow-[0_0_10px_2px_rgba(59,130,246,0.7)] mb-10" />
</>
)}
</>
)}

Expand Down
9 changes: 6 additions & 3 deletions packages/nextjs/app/engineering/circom/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ import { chunkArray } from "~~/utils/chunkArray";
const challenges = [
{ id: 1, title: "Challenge #1", isLocked: false },
{ id: 2, title: "Challenge #2", isLocked: false },
{ id: 3, title: "Challenge #3", isLocked: true },
{ id: 3, title: "Challenge #3", isLocked: false },
{ id: 4, title: "Challenge #4", isLocked: false },
{ id: 5, title: "Challenge #5", isLocked: true },
{ id: 6, title: "Challenge #6", isLocked: true },
{ id: 5, title: "Challenge #5", isLocked: false },
{ id: 6, title: "Challenge #6", isLocked: false },
{ id: 7, title: "Challenge #7", isLocked: false },
{ id: 8, title: "Challenge #8", isLocked: true },
{ id: 9, title: "Challenge #9", isLocked: true },
];

const Home: NextPage = () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/nextjs/app/engineering/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const languages = [
title: "Python",
image: "/images/languages/python.png",
link: "/engineering/python",
locked: false,
locked: true,
},
];

Expand Down
24 changes: 24 additions & 0 deletions packages/nextjs/components/Statement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,36 @@ const Statement = ({ challengeId, lang }: StatementProps) => {
);
setChallengeContent(() => Challenge2Content);
break;
case "3":
const { default: Challenge3Content } = await import(
"../public/challenges/circom/challenge_3/statement"
);
setChallengeContent(() => Challenge3Content);
break;
case "4":
const { default: Challenge4Content } = await import(
"../public/challenges/circom/challenge_4/statement"
);
setChallengeContent(() => Challenge4Content);
break;
case "5":
const { default: Challenge5Content } = await import(
"../public/challenges/circom/challenge_5/statement"
);
setChallengeContent(() => Challenge5Content);
break;
case "6":
const { default: Challenge6Content } = await import(
"../public/challenges/circom/challenge_6/statement"
);
setChallengeContent(() => Challenge6Content);
break;
case "7":
const { default: Challenge7Content } = await import(
"../public/challenges/circom/challenge_7/statement"
);
setChallengeContent(() => Challenge7Content);
break;
default:
setChallengeContent(() => NotFoundContent);
break;
Expand Down
9 changes: 8 additions & 1 deletion packages/nextjs/components/Verifier.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

import React, { useState } from "react";
import Image from "next/image";
import { generateAndVerifyProof2, generateAndVerifyProof4 } from "~~/utils/generateAndVerifyProof";
import {
generateAndVerifyProof2,
generateAndVerifyProof3,
generateAndVerifyProof4,
} from "~~/utils/generateAndVerifyProof";

type VerifierProps = {
challengeId: string;
Expand All @@ -17,6 +21,9 @@ function Verifier({ challengeId }: VerifierProps) {
if (challengeId == "2") {
const result = (await generateAndVerifyProof2(characterA, characterB)) ? "Valid proof" : "Invalid proof";
setResult(result);
} else if (challengeId == "3") {
const result = (await generateAndVerifyProof3(characterA, characterB)) ? "Valid proof" : "Invalid proof";
setResult(result);
} else if (challengeId == "4") {
const result = (await generateAndVerifyProof4(characterA, characterB)) ? "Valid proof" : "Invalid proof";
setResult(result);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
pragma circom 2.1.6;

template KnightsKnaves() {
signal input A_is_knight; // 1 if A is a knight, 0 if A is a knave
signal input B_is_knight; // 1 if B is a knight, 0 if B is a knave

// Intermediate signals for A's statement
signal A_statement_part1;
signal A_statement_part2;

// A says "B is a knave"
A_statement_part1 <== A_is_knight * (1 - B_is_knight);
A_statement_part2 <== (1 - A_is_knight) * B_is_knight;
signal A_statement_valid;
A_statement_valid <== A_statement_part1 + A_statement_part2;

// Intermediate signals for B's statement
signal B_statement_part1;
signal B_statement_part2;
signal B_statement_part1_1;
signal B_statement_part1_2;
signal B_statement_part1_3;
signal B_statement_part2_1;
signal B_statement_part2_2;
signal B_statement_part2_3;

// If B is a knight (1), then A and B must be of opposite types
//B_statement_part1 <== B_is_knight * (A_is_knight * (1 - B_is_knight) + (1 - A_is_knight) * B_is_knight);
B_statement_part1_1 <== A_is_knight * (1 - B_is_knight);
B_statement_part1_2 <== (1 - A_is_knight) * B_is_knight;
B_statement_part1_3 <== (B_statement_part1_1 + B_statement_part1_2);
B_statement_part1 <== B_is_knight * B_statement_part1_3;

// If B is a knave (0), then A and B must be of the same type
//B_statement_part2 <== (1 - B_is_knight) * (A_is_knight * B_is_knight + (1 - A_is_knight) * (1 - B_is_knight));
B_statement_part2_1 <== A_is_knight * B_is_knight;
B_statement_part2_2 <== (1 - A_is_knight) * (1 - B_is_knight);
B_statement_part2_3 <== B_statement_part2_1 + B_statement_part2_2;
B_statement_part2 <== (1 - B_is_knight) * B_statement_part2_3;

signal B_statement_valid;
B_statement_valid <== B_statement_part1 + B_statement_part2;

// Enforce that both statements must be valid
signal output is_consistent;
is_consistent <== A_statement_valid * B_statement_valid;

// Enforce that is_consistent must be 1
// This makes sure the circuit will only accept valid proofs
is_consistent * (is_consistent - 1) === 0; // This ensures is_consistent is either 0 or 1
is_consistent === 1; // This ensures is_consistent is 1

}

component main = KnightsKnaves();
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
[
{
"inputs": [
{
"internalType": "uint256[2]",
"name": "_pA",
"type": "uint256[2]"
},
{
"internalType": "uint256[2][2]",
"name": "_pB",
"type": "uint256[2][2]"
},
{
"internalType": "uint256[2]",
"name": "_pC",
"type": "uint256[2]"
},
{
"internalType": "uint256[1]",
"name": "_pubSignals",
"type": "uint256[1]"
}
],
"name": "sendProof",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "circomVeriferAddress",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "publicInput",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
]
151 changes: 151 additions & 0 deletions packages/nextjs/public/challenges/circom/challenge_3/files/custom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
const NETWORK_ID = 11155111

const MY_CONTRACT_ADDRESS = "0xCCa8c40aeA7Ef91249725C53e86764FBA527d845"
const MY_CONTRACT_ABI_PATH = "./abi.json"
var my_contract

var accounts
var web3

function metamaskReloadCallback() {
window.ethereum.on('accountsChanged', (accounts) => {
document.getElementById("web3_message").textContent="Se cambió el account, refrescando...";
window.location.reload()
})
window.ethereum.on('networkChanged', (accounts) => {
document.getElementById("web3_message").textContent="Se el network, refrescando...";
window.location.reload()
})
}

const getWeb3 = async () => {
return new Promise((resolve, reject) => {
if(document.readyState=="complete")
{
if (window.ethereum) {
const web3 = new Web3(window.ethereum)
window.location.reload()
resolve(web3)
} else {
reject("must install MetaMask")
document.getElementById("web3_message").textContent="Error: Porfavor conéctate a Metamask";
}
}else
{
window.addEventListener("load", async () => {
if (window.ethereum) {
const web3 = new Web3(window.ethereum)
resolve(web3)
} else {
reject("must install MetaMask")
document.getElementById("web3_message").textContent="Error: Please install Metamask";
}
});
}
});
};

const getContract = async (web3, address, abi_path) => {
const response = await fetch(abi_path);
const data = await response.json();

const netId = await web3.eth.net.getId();
contract = new web3.eth.Contract(
data,
address
);
return contract
}

async function loadDapp() {
metamaskReloadCallback()
document.getElementById("web3_message").textContent="Please connect to Metamask"
var awaitWeb3 = async function () {
web3 = await getWeb3()
web3.eth.net.getId((err, netId) => {
if (netId == NETWORK_ID) {
var awaitContract = async function () {
my_contract = await getContract(web3, MY_CONTRACT_ADDRESS, MY_CONTRACT_ABI_PATH)
document.getElementById("web3_message").textContent="You are connected to Metamask"
onContractInitCallback()
web3.eth.getAccounts(function(err, _accounts){
accounts = _accounts
if (err != null)
{
console.error("An error occurred: "+err)
} else if (accounts.length > 0)
{
onWalletConnectedCallback()
document.getElementById("account_address").style.display = "block"
} else
{
document.getElementById("connect_button").style.display = "block"
}
});
};
awaitContract();
} else {
document.getElementById("web3_message").textContent="Please connect to Scroll Testnet";
}
});
};
awaitWeb3();
}

async function connectWallet() {
await window.ethereum.request({ method: "eth_requestAccounts" })
accounts = await web3.eth.getAccounts()
onWalletConnectedCallback()
}

loadDapp()

const onContractInitCallback = async () => {
var publicInput = await my_contract.methods.publicInput().call()
var contract_state = "Public input: " + publicInput
console.log(contract_state);
document.getElementById("contract_state").textContent = contract_state;
}

const onWalletConnectedCallback = async () => {
}


//// Functions ////

const sendProof = async (a, b) => {
document.getElementById("web3_message").textContent="Generating proof...";

const { proof, publicSignals } = await snarkjs.groth16.fullProve( { A_is_knight: a, B_is_knight: b}, "../zk_artifacts/myCircuit.wasm", "../zk_artifacts/myCircuit_final.zkey");

const vkey = await fetch("../zk_artifacts/verification_key.json").then( function(res) {
return res.json();
});

const res = await snarkjs.groth16.verify(vkey, publicSignals, proof);

pA = proof.pi_a
pA.pop()
pB = proof.pi_b
pB.pop()
pC = proof.pi_c
pC.pop()

console.log(pA);
console.log(pB);
console.log(pC);
console.log(publicSignals);

document.getElementById("web3_message").textContent="Proof generated please confirm transaction.";

const result = await my_contract.methods.sendProof(pA, pB, pC, publicSignals)
.send({ from: accounts[0], gas: 0, value: 0 })
.on('transactionHash', function(hash){
document.getElementById("web3_message").textContent="Executing...";
})
.on('receipt', function(receipt){
document.getElementById("web3_message").textContent="Success."; })
.catch((revertReason) => {
console.log("ERROR! Transaction reverted: " + revertReason.receipt.transactionHash)
});
}
Loading

0 comments on commit 09814a9

Please sign in to comment.