Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions examples/CRISP/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion examples/CRISP/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ members = [
".enclave/support/dev",
"program",
"crates/zk-inputs",
"crates/zk-inputs-wasm"
"crates/zk-inputs-wasm",
"crates/evm_helpers"
]
resolver = "3"

Expand All @@ -18,6 +19,7 @@ repository = "https://github.com/gnosisguild/enclave"
[workspace.dependencies]
e3-user-program = { path = "./program" }

alloy = { version = "=1.0.41", features = ["full", "rpc-types-eth"] }
alloy-primitives = { version = "=1.3.0", default-features = false, features = [
"rlp",
"serde",
Expand Down
12 changes: 6 additions & 6 deletions examples/CRISP/circuits/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ use utils::{check_coefficient_values_with_balance, check_coefficient_zero};

fn main(
// Ciphertext Addition Section.
prev_ct0is: [Polynomial<2048>; 1],
prev_ct1is: [Polynomial<2048>; 1],
prev_ct0is: [Polynomial<2048>; 1], // todo: make this pub
prev_ct1is: [Polynomial<2048>; 1], // todo: make this pub
sum_ct0is: [Polynomial<2048>; 1],
sum_ct1is: [Polynomial<2048>; 1],
sum_r0is: [Polynomial<2048>; 1],
sum_r1is: [Polynomial<2048>; 1],
// Greco Section.
params: Params<2048, 1>,
params: Params<2048, 1>, // todo: make this pub
pk0is: [Polynomial<2048>; 1],
pk1is: [Polynomial<2048>; 1],
ct0is: [Polynomial<2048>; 1],
Expand All @@ -45,16 +45,16 @@ fn main(
signature: [u8; 64],
hashed_message: [u8; 32],
// Merkle Tree Section.
merkle_root: Field,
merkle_root: Field, // todo: make this pub
merkle_proof_length: u32,
merkle_proof_indices: [u1; 20],
merkle_proof_siblings: [Field; 20],
// Slot Address Section.
slot_address: Field,
slot_address: pub Field,
// Balance Section.
balance: Field,
// Whether this is the first vote for this slot.
is_first_vote: bool,
is_first_vote: pub bool,
) {
// Verify the ECDSA signature.
let is_signature_valid =
Expand Down
2 changes: 1 addition & 1 deletion examples/CRISP/client/libs/wasm/pkg/crisp_worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ self.onmessage = async function (event) {
success: true,
encryptedVote: {
vote: encryptedVote,
proofData: proof,
proof: proof.proof,
Comment thread
cedoor marked this conversation as resolved.
},
})
} catch (error) {
Expand Down
1 change: 0 additions & 1 deletion examples/CRISP/client/src/hooks/voting/useVoteCasting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ export const useVoteCasting = () => {
round_id: roundState.id,
enc_vote_bytes: Array.from(voteEncrypted.vote),
proof: Array.from(voteEncrypted.proof),
public_inputs: voteEncrypted.public_inputs,
address: user.address,
}

Expand Down
4 changes: 1 addition & 3 deletions examples/CRISP/client/src/hooks/wasm/useWebAssembly.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,11 @@ export const useWebAssemblyHook = () => {
const { type, success, encryptedVote, error } = event.data
if (type === 'encrypt_vote') {
if (success) {
const { vote, proofData } = encryptedVote
const { proof, publicInputs } = proofData
const { vote, proof } = encryptedVote

resolve({
vote: vote,
proof: proof,
public_inputs: publicInputs,
})
} else {
showToast({
Expand Down
2 changes: 0 additions & 2 deletions examples/CRISP/client/src/model/vote.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ export interface BroadcastVoteRequest {
round_id: number
enc_vote_bytes: number[]
proof: number[]
public_inputs: string[]
address: string
}

Expand Down Expand Up @@ -56,5 +55,4 @@ export interface VoteStateLite {
export interface EncryptedVote {
vote: Uint8Array
proof: Uint8Array
public_inputs: string[]
}
15 changes: 15 additions & 0 deletions examples/CRISP/crates/evm_helpers/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "evm-helpers"
version.workspace = true
edition.workspace = true
license.workspace = true
description = "CRISP EVM Contract Helpers"

[dependencies]
alloy.workspace = true
eyre.workspace = true

[dev-dependencies]
tokio.workspace = true
alloy = { workspace = true, features = ["node-bindings"] }

109 changes: 109 additions & 0 deletions examples/CRISP/crates/evm_helpers/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: LGPL-3.0-only
//
// This file is provided WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.

use alloy::{
network::{Ethereum, EthereumWallet},
primitives::{Address, U256},
providers::{
fillers::{
BlobGasFiller, ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller,
WalletFiller,
},
Identity, ProviderBuilder, RootProvider,
},
rpc::types::TransactionReceipt,
signers::local::PrivateKeySigner,
sol,
};
use eyre::Result;
use std::sync::Arc;

sol! {
#[derive(Debug)]
#[sol(rpc)]
contract CRISPProgram {
function setRoundData(uint256 _root, address _token, uint256 _balanceThreshold) external;
}
}

/// Type alias for write provider (same as EnclaveWriteProvider)
pub type CRISPWriteProvider = FillProvider<
JoinFill<
JoinFill<
Identity,
JoinFill<GasFiller, JoinFill<BlobGasFiller, JoinFill<NonceFiller, ChainIdFiller>>>,
>,
WalletFiller<EthereumWallet>,
>,
RootProvider<Ethereum>,
Ethereum,
>;

/// CRISP contract instance for interacting with CRISPProgram
#[derive(Clone)]
pub struct CRISPContract {
provider: Arc<CRISPWriteProvider>,
contract_address: Address,
}

impl CRISPContract {
/// Get the contract address
pub fn address(&self) -> &Address {
&self.contract_address
}

/// Create a new CRISP contract instance
pub async fn new(
http_rpc_url: &str,
private_key: &str,
contract_address: &str,
) -> Result<CRISPContract> {
let contract_address = contract_address.parse()?;
let signer: PrivateKeySigner = private_key.parse()?;
let wallet = EthereumWallet::from(signer);
let provider = ProviderBuilder::new()
.wallet(wallet)
.connect(http_rpc_url)
.await?;

Ok(CRISPContract {
provider: Arc::new(provider),
contract_address,
})
}

/// Set round data on the CRISPProgram contract
pub async fn set_round_data(
&self,
merkle_root: U256,
token_address: Address,
balance_threshold: U256,
) -> Result<TransactionReceipt> {
let contract = CRISPProgram::new(self.contract_address, self.provider.as_ref());
let receipt = contract
.setRoundData(merkle_root, token_address, balance_threshold)
.send()
.await?
.get_receipt()
.await?;

Ok(receipt)
}
}

/// Factory for creating CRISP contract instances
pub struct CRISPContractFactory;

impl CRISPContractFactory {
/// Create a write-capable contract
pub async fn create_write(
http_rpc_url: &str,
contract_address: &str,
private_key: &str,
) -> Result<CRISPContract> {
CRISPContract::new(http_rpc_url, private_key, contract_address).await
}
}
59 changes: 59 additions & 0 deletions examples/CRISP/crates/evm_helpers/tests/integration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: LGPL-3.0-only
//
// This file is provided WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.

use alloy::node_bindings::{Anvil, AnvilInstance};
use alloy::providers::{Provider, ProviderBuilder, WsConnect};
use alloy::signers::local::PrivateKeySigner;
use evm_helpers::CRISPContractFactory;
use eyre::Result;

async fn setup_provider() -> Result<(impl Provider, String, AnvilInstance)> {
let anvil = Anvil::new().block_time_f64(0.01).try_spawn()?;
let provider = ProviderBuilder::new()
.wallet(PrivateKeySigner::from_slice(&anvil.keys()[0].to_bytes())?)
.connect_ws(WsConnect::new(anvil.ws_endpoint()))
.await?;
let endpoint = anvil.ws_endpoint().to_string();
Ok((provider, endpoint, anvil))
}
Comment thread
cedoor marked this conversation as resolved.

#[tokio::test]
async fn test_factory_creates_contract() -> Result<()> {
let (_, endpoint, _anvil) = setup_provider().await?;
let private_key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; // Anvil default
let contract_address = "0x5FbDB2315678afecb367f032d93F642f64180aa3"; // Dummy address

let contract =
CRISPContractFactory::create_write(&endpoint, contract_address, private_key).await?;

// Verify the contract was created successfully
assert_eq!(
contract.address().to_string().to_lowercase(),
contract_address.to_lowercase()
);

Ok(())
}

#[tokio::test]
async fn test_factory_invalid_address() {
let (_, endpoint, _anvil) = setup_provider().await.unwrap();
let private_key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
let invalid_address = "not-an-address";

let result = CRISPContractFactory::create_write(&endpoint, invalid_address, private_key).await;
assert!(result.is_err());
}

#[tokio::test]
async fn test_factory_invalid_private_key() {
let (_, endpoint, _anvil) = setup_provider().await.unwrap();
let invalid_key = "not-a-key";
let contract_address = "0x5FbDB2315678afecb367f032d93F642f64180aa3";

let result = CRISPContractFactory::create_write(&endpoint, contract_address, invalid_key).await;
assert!(result.is_err());
}
27 changes: 17 additions & 10 deletions examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol
Original file line number Diff line number Diff line change
Expand Up @@ -143,20 +143,27 @@ contract CRISPProgram is IE3Program, Ownable {

function validateInput(address, bytes memory data) external returns (bytes memory input) {
// it should only be called via Enclave for now
require(authorizedContracts[msg.sender] || msg.sender == owner(), CallerNotAuthorized());
// we need to ensure that the CRISP admin set the merkle root of the census
// @todo update this once we have all components working
// if (!isDataSet) revert RoundDataNotSet();
require(
authorizedContracts[msg.sender] || msg.sender == owner(),
CallerNotAuthorized()
);
// We need to ensure that the CRISP admin set the merkle root of the census.
if (!isDataSet) revert RoundDataNotSet();

if (data.length == 0) revert EmptyInputData();

(bytes memory noirProof, bytes32[] memory noirPublicInputs, bytes memory vote, address slot) =
abi.decode(data, (bytes, bytes32[], bytes, address));
(bytes memory noirProof, bytes memory vote, address slot) = abi.decode(
data,
(bytes, bytes, address)
);

bytes32[] memory noirPublicInputs = new bytes32[](2);

/// @notice we need to check whether the slot is empty.
/// if the slot is empty
/// @todo pass it to the verifier
// bool isFirstVote = voteSlots[slot].length == 0;
// Set public inputs for the proof. Order must match Noir circuit.
noirPublicInputs[0] = bytes32(uint256(uint160(slot)));
bool isFirstVote = voteSlots[slot].length == 0;
noirPublicInputs[1] = bytes32(uint256(isFirstVote ? 1 : 0));
// noirPublicInputs[x] = bytes32(roundData.censusMerkleRoot);

// Check if the ciphertext was encrypted correctly
if (!HONK_VERIFIER.verify(noirProof, noirPublicInputs)) {
Expand Down
Loading
Loading