From eb5691679ff4362e82ff928d473482a8f54d7fb7 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 23 Aug 2024 21:39:12 +0500 Subject: [PATCH 01/62] compute provider --- packages/compute_provider/.gitignore | 4 + packages/compute_provider/Cargo.toml | 22 + packages/compute_provider/core/Cargo.toml | 11 + packages/compute_provider/core/src/lib.rs | 41 + packages/compute_provider/host/Cargo.toml | 17 + packages/compute_provider/host/src/lib.rs | 118 ++ packages/compute_provider/methods/Cargo.toml | 11 + packages/compute_provider/methods/build.rs | 12 + .../compute_provider/methods/guest/Cargo.toml | 10 + .../methods/guest/src/main.rs | 10 + packages/compute_provider/methods/src/lib.rs | 1 + packages/compute_provider/rust-toolchain.toml | 4 + packages/evm/.gitignore | 3 + packages/server/Cargo.lock | 1356 ++++++++++++++++- packages/server/Cargo.toml | 1 + packages/server/src/bin/enclave_server.rs | 1 - 16 files changed, 1544 insertions(+), 78 deletions(-) create mode 100644 packages/compute_provider/.gitignore create mode 100644 packages/compute_provider/Cargo.toml create mode 100644 packages/compute_provider/core/Cargo.toml create mode 100644 packages/compute_provider/core/src/lib.rs create mode 100644 packages/compute_provider/host/Cargo.toml create mode 100644 packages/compute_provider/host/src/lib.rs create mode 100644 packages/compute_provider/methods/Cargo.toml create mode 100644 packages/compute_provider/methods/build.rs create mode 100644 packages/compute_provider/methods/guest/Cargo.toml create mode 100644 packages/compute_provider/methods/guest/src/main.rs create mode 100644 packages/compute_provider/methods/src/lib.rs create mode 100644 packages/compute_provider/rust-toolchain.toml diff --git a/packages/compute_provider/.gitignore b/packages/compute_provider/.gitignore new file mode 100644 index 0000000..f4247e1 --- /dev/null +++ b/packages/compute_provider/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +Cargo.lock +methods/guest/Cargo.lock +target/ diff --git a/packages/compute_provider/Cargo.toml b/packages/compute_provider/Cargo.toml new file mode 100644 index 0000000..1122188 --- /dev/null +++ b/packages/compute_provider/Cargo.toml @@ -0,0 +1,22 @@ +[workspace] +resolver = "2" +members = ["host", "methods", "core"] + + +[workspace.dependencies] +risc0-build = { version = "1.0.5" } +risc0-build-ethereum = { git = "https://github.com/risc0/risc0-ethereum", tag = "v1.0.0" } +risc0-ethereum-contracts = { git = "https://github.com/risc0/risc0-ethereum", tag = "v1.0.0" } +risc0-zkvm = { version = "1.0.5", default-features = false, features = ["std", "client"] } +risc0-zkp = { version = "1.0.5" } +fhe = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } +fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } +serde = { version = "1.0", features = ["derive", "std"] } + +# Always optimize; building and running the guest takes much longer without optimization. +[profile.dev] +opt-level = 3 + +[profile.release] +debug = 1 +lto = true diff --git a/packages/compute_provider/core/Cargo.toml b/packages/compute_provider/core/Cargo.toml new file mode 100644 index 0000000..a850eb3 --- /dev/null +++ b/packages/compute_provider/core/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "compute-provider-core" +version = "0.1.0" +edition = "2021" + +[dependencies] +risc0-zkvm = { workspace = true } +risc0-zkp = { workspace = true } +serde = { workspace = true } +fhe = { workspace = true } +fhe-traits = { workspace = true } diff --git a/packages/compute_provider/core/src/lib.rs b/packages/compute_provider/core/src/lib.rs new file mode 100644 index 0000000..cd4d345 --- /dev/null +++ b/packages/compute_provider/core/src/lib.rs @@ -0,0 +1,41 @@ +use risc0_zkp::core::digest::Digest; +use risc0_zkvm::sha::{Impl, Sha256}; +use fhe::bfv::{Ciphertext, BfvParameters}; +use fhe_traits::{DeserializeParametrized, Serialize, Deserialize}; +use std::sync::Arc; + +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct TallyResult { + pub tallied_ciphertext: Vec, + pub ciphertexts_digest: Digest +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct CiphertextInput { + pub ciphertexts: Vec>, + pub params: Vec, +} + +impl CiphertextInput { + pub fn process(&self) -> TallyResult { + // Deserialize the parameters + let params = Arc::new(BfvParameters::try_deserialize(&self.params).unwrap()); + + // Tally the ciphertexts + let mut sum = Ciphertext::zero(¶ms); + for ciphertext_bytes in &self.ciphertexts { + let ciphertext = Ciphertext::from_bytes(ciphertext_bytes, ¶ms).unwrap(); + sum += &ciphertext; + } + let tally: Arc = Arc::new(sum); + + // Compute the digest of the ciphertexts + let digest = *Impl::hash_bytes(&self.ciphertexts.concat()); + + TallyResult { + tallied_ciphertext: tally.to_bytes(), + ciphertexts_digest: digest + } + } + +} \ No newline at end of file diff --git a/packages/compute_provider/host/Cargo.toml b/packages/compute_provider/host/Cargo.toml new file mode 100644 index 0000000..5090fe5 --- /dev/null +++ b/packages/compute_provider/host/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "compute-provider-host" +version = "0.1.0" +edition = "2021" + +[dependencies] +methods = { path = "../methods" } +risc0-zkvm = { workspace = true } +risc0-build-ethereum = { workspace = true } +risc0-ethereum-contracts = { workspace = true } +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +serde = "1.0" +compute-provider-core ={ path = "../core"} +fhe = { workspace = true } +fhe-traits = { workspace = true } +rand = "0.8" + diff --git a/packages/compute_provider/host/src/lib.rs b/packages/compute_provider/host/src/lib.rs new file mode 100644 index 0000000..f591426 --- /dev/null +++ b/packages/compute_provider/host/src/lib.rs @@ -0,0 +1,118 @@ +use compute_provider_core::{CiphertextInput, TallyResult}; +use methods::COMPUTE_PROVIDER_ELF; +use risc0_ethereum_contracts::groth16; +use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts, VerifierContext}; + +#[derive(Debug)] +pub struct ComputeProvider { + input: CiphertextInput, +} + +impl ComputeProvider { + pub fn new(input: CiphertextInput) -> Self { + Self { input } + } + + pub fn start(&self) -> (TallyResult, Vec) { + tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::filter::EnvFilter::from_default_env()) + .init(); + + let env = ExecutorEnv::builder() + .write(&self.input) + .unwrap() + .build() + .unwrap(); + + let receipt = default_prover() + .prove_with_ctx( + env, + &VerifierContext::default(), + COMPUTE_PROVIDER_ELF, + &ProverOpts::groth16(), + ) + .unwrap() + .receipt; + + let seal = groth16::encode(receipt.inner.groth16().unwrap().seal.clone()).unwrap(); + + (receipt.journal.decode().unwrap(), seal) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use compute_provider_core::CiphertextInput; + use fhe::bfv::{ + BfvParameters, BfvParametersBuilder, Ciphertext, Encoding, Plaintext, PublicKey, SecretKey, + }; + use fhe_traits::{ + DeserializeParametrized, FheDecoder, FheDecrypter, FheEncoder, FheEncrypter, Serialize, + }; + use rand::thread_rng; + use std::sync::Arc; + + #[test] + fn test_compute_provider() { + let params = create_params(); + let (sk, pk) = generate_keys(¶ms); + let inputs = vec![1, 1, 0]; + let ciphertexts = encrypt_inputs(&inputs, &pk, ¶ms); + + let input = create_input(&ciphertexts, ¶ms); + let provider = ComputeProvider::new(input); + let result = provider.input.process(); + + let tally = decrypt_result(&result, &sk, ¶ms); + + assert_eq!(tally, inputs.iter().sum::()); + assert_eq!(result.ciphertexts_digest.to_string().len(), 64); + } + + fn create_params() -> Arc { + BfvParametersBuilder::new() + .set_degree(1024) + .set_plaintext_modulus(65537) + .set_moduli(&[1152921504606584833]) + .build_arc() + .expect("Failed to build parameters") + } + + fn generate_keys(params: &Arc) -> (SecretKey, PublicKey) { + let mut rng = thread_rng(); + let sk = SecretKey::random(params, &mut rng); + let pk = PublicKey::new(&sk, &mut rng); + (sk, pk) + } + + fn encrypt_inputs( + inputs: &[u64], + pk: &PublicKey, + params: &Arc, + ) -> Vec { + let mut rng = thread_rng(); + inputs + .iter() + .map(|&input| { + let pt = Plaintext::try_encode(&[input], Encoding::poly(), params) + .expect("Failed to encode plaintext"); + pk.try_encrypt(&pt, &mut rng).expect("Failed to encrypt") + }) + .collect() + } + + fn create_input(ciphertexts: &[Ciphertext], params: &Arc) -> CiphertextInput { + CiphertextInput { + ciphertexts: ciphertexts.iter().map(|c| c.to_bytes()).collect(), + params: params.to_bytes(), + } + } + + fn decrypt_result(result: &TallyResult, sk: &SecretKey, params: &Arc) -> u64 { + let ct = Ciphertext::from_bytes(&result.tallied_ciphertext, params) + .expect("Failed to deserialize ciphertext"); + let decrypted = sk.try_decrypt(&ct).expect("Failed to decrypt"); + Vec::::try_decode(&decrypted, Encoding::poly()).expect("Failed to decode")[0] + } +} diff --git a/packages/compute_provider/methods/Cargo.toml b/packages/compute_provider/methods/Cargo.toml new file mode 100644 index 0000000..31add6d --- /dev/null +++ b/packages/compute_provider/methods/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "methods" +version = "0.1.0" +edition = "2021" + +[build-dependencies] +risc0-build = { workspace = true } +risc0-build-ethereum = { workspace = true } + +[package.metadata.risc0] +methods = ["guest"] diff --git a/packages/compute_provider/methods/build.rs b/packages/compute_provider/methods/build.rs new file mode 100644 index 0000000..b3b84cf --- /dev/null +++ b/packages/compute_provider/methods/build.rs @@ -0,0 +1,12 @@ + +const SOLIDITY_IMAGE_ID_PATH: &str = "../../evm/contracts/ImageID.sol"; +const SOLIDITY_ELF_PATH: &str = "../../evm/contracts/Elf.sol"; + +fn main() { + let guests = risc0_build::embed_methods(); + let solidity_opts = risc0_build_ethereum::Options::default() + .with_image_id_sol_path(SOLIDITY_IMAGE_ID_PATH) + .with_elf_sol_path(SOLIDITY_ELF_PATH); + + risc0_build_ethereum::generate_solidity_files(guests.as_slice(), &solidity_opts).unwrap(); +} \ No newline at end of file diff --git a/packages/compute_provider/methods/guest/Cargo.toml b/packages/compute_provider/methods/guest/Cargo.toml new file mode 100644 index 0000000..69ccca4 --- /dev/null +++ b/packages/compute_provider/methods/guest/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "compute_provider" +version = "0.1.0" +edition = "2021" + +[workspace] + +[dependencies] +risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] } +compute-provider-core ={ path = "../../core"} \ No newline at end of file diff --git a/packages/compute_provider/methods/guest/src/main.rs b/packages/compute_provider/methods/guest/src/main.rs new file mode 100644 index 0000000..f31b8aa --- /dev/null +++ b/packages/compute_provider/methods/guest/src/main.rs @@ -0,0 +1,10 @@ +use risc0_zkvm::guest::env; +use compute_provider_core::{CiphertextInput, TallyResult}; + +fn main() { + let input: CiphertextInput = env::read(); + + let result: TallyResult = input.process(); + + env::commit(&result); +} diff --git a/packages/compute_provider/methods/src/lib.rs b/packages/compute_provider/methods/src/lib.rs new file mode 100644 index 0000000..1bdb308 --- /dev/null +++ b/packages/compute_provider/methods/src/lib.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/methods.rs")); diff --git a/packages/compute_provider/rust-toolchain.toml b/packages/compute_provider/rust-toolchain.toml new file mode 100644 index 0000000..36614c3 --- /dev/null +++ b/packages/compute_provider/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "stable" +components = ["rustfmt", "rust-src"] +profile = "minimal" diff --git a/packages/evm/.gitignore b/packages/evm/.gitignore index 18a269e..d912983 100644 --- a/packages/evm/.gitignore +++ b/packages/evm/.gitignore @@ -19,3 +19,6 @@ deployments coverage.json package-lock.json yarn.lock + +contracts/Elf.sol +contracts/ImageID.sol diff --git a/packages/server/Cargo.lock b/packages/server/Cargo.lock index 0e08422..822ad47 100644 --- a/packages/server/Cargo.lock +++ b/packages/server/Cargo.lock @@ -38,6 +38,18 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check 0.9.4", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -47,6 +59,97 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloy-primitives" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccb3ead547f4532bc8af961649942f0b9c16ee9226e26caa3f38420651cc0bf4" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "k256", + "keccak-asm", + "proptest", + "rand 0.8.5", + "ruint", + "serde", + "tiny-keccak", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26154390b1d205a4a7ac7352aa2eb4f81f391399d4e2f546fb81a2f8bb383f62" +dependencies = [ + "arrayvec", + "bytes", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b40397ddcdcc266f59f959770f601ce1280e699a91fc1862f29cef91707cd09" +dependencies = [ + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.61", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "867a5469d61480fea08c7333ffeca52d5b621f5ca2e44f271b117ec1fc9a0525" +dependencies = [ + "alloy-sol-macro-input", + "const-hex", + "heck 0.5.0", + "indexmap", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.61", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e482dc33a32b6fadbc0f599adea520bd3aaa585c141a80b404d0a3e3fa72528" +dependencies = [ + "const-hex", + "dunce", + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.61", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-types" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a91ca40fa20793ae9c3841b83e74569d1cc9af29a2f5237314fd3452d51e38c7" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "const-hex", + "serde", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -68,6 +171,240 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" +[[package]] +name = "ark-bn254" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" +dependencies = [ + "ark-ec", + "ark-ff 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-crypto-primitives" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3a13b34da09176a8baba701233fdffbaa7c1b1192ce031a3da4e55ce1f1a56" +dependencies = [ + "ark-ec", + "ark-ff 0.4.2", + "ark-relations", + "ark-serialize 0.4.2", + "ark-snark", + "ark-std 0.4.0", + "blake2", + "derivative", + "digest 0.10.7", + "sha2", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff 0.4.2", + "ark-poly", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "hashbrown 0.13.2", + "itertools 0.10.5", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.4.0", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-groth16" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20ceafa83848c3e390f1cbf124bc3193b3e639b3f02009e0e290809a501b95fc" +dependencies = [ + "ark-crypto-primitives", + "ark-ec", + "ark-ff 0.4.2", + "ark-poly", + "ark-relations", + "ark-serialize 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-relations" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00796b6efc05a3f48225e59cb6a2cda78881e7c390872d5786aaf112f31fb4f0" +dependencies = [ + "ark-ff 0.4.2", + "ark-std 0.4.0", + "tracing", + "tracing-subscriber 0.2.25", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive", + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-snark" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d3cc6833a335bb8a600241889ead68ee89a3cf8448081fb7694c0fe503da63" +dependencies = [ + "ark-ff 0.4.2", + "ark-relations", + "ark-serialize 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + [[package]] name = "arrayvec" version = "0.7.4" @@ -257,7 +594,7 @@ checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" dependencies = [ "futures", "pharos", - "rustc_version", + "rustc_version 0.4.0", ] [[package]] @@ -335,6 +672,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.6.0" @@ -395,6 +738,15 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -418,6 +770,20 @@ dependencies = [ "piper", ] +[[package]] +name = "bonsai-sdk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1553c9f015eb3fc4ff1bf2e142fceeb2256768a3c4d94a9486784a6c656484d" +dependencies = [ + "duplicate", + "maybe-async", + "reqwest 0.12.5", + "risc0-groth16", + "serde", + "thiserror", +] + [[package]] name = "bs58" version = "0.5.1" @@ -440,6 +806,26 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" +[[package]] +name = "bytemuck" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fd4c6dcc3b0aea2f5c0b4b82c2b15fe39ddbc76041a310848f4706edf76bb31" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.61", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -502,7 +888,7 @@ checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" dependencies = [ "camino", "cargo-platform", - "semver", + "semver 1.0.23", "serde", "serde_json", "thiserror", @@ -566,7 +952,7 @@ checksum = "3b6be4a5df2098cd811f3194f64ddb96c267606bffd9689ac7b0160097b01ad3" dependencies = [ "bs58", "coins-core", - "digest", + "digest 0.10.7", "hmac", "k256", "serde", @@ -599,7 +985,7 @@ dependencies = [ "base64 0.21.7", "bech32", "bs58", - "digest", + "digest 0.10.7", "generic-array", "hex", "ripemd", @@ -610,6 +996,33 @@ dependencies = [ "thiserror", ] +[[package]] +name = "compute-provider-core" +version = "0.1.0" +dependencies = [ + "fhe", + "fhe-traits", + "risc0-zkp", + "risc0-zkvm", + "serde", +] + +[[package]] +name = "compute-provider-host" +version = "0.1.0" +dependencies = [ + "compute-provider-core", + "fhe", + "fhe-traits", + "methods", + "rand 0.8.5", + "risc0-build-ethereum", + "risc0-ethereum-contracts", + "risc0-zkvm", + "serde", + "tracing-subscriber 0.3.18", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -657,6 +1070,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation" version = "0.9.4" @@ -778,14 +1197,27 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "derive_more" version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ + "convert_case", "proc-macro2", "quote", + "rustc_version 0.4.0", "syn 1.0.109", ] @@ -803,6 +1235,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "digest" version = "0.10.7" @@ -863,12 +1304,34 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "docker-generate" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf673e0848ef09fa4aeeba78e681cf651c0c7d35f76ee38cec8e55bc32fa111" + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + [[package]] name = "dunce" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" +[[package]] +name = "duplicate" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de78e66ac9061e030587b2a2e75cc88f22304913c907b11307bca737141230cb" +dependencies = [ + "heck 0.4.1", + "proc-macro-error", +] + [[package]] name = "ecdsa" version = "0.16.9" @@ -876,7 +1339,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", - "digest", + "digest 0.10.7", "elliptic-curve", "rfc6979", "signature", @@ -889,6 +1352,12 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +[[package]] +name = "elf" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" + [[package]] name = "elliptic-curve" version = "0.13.8" @@ -897,7 +1366,7 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "digest", + "digest 0.10.7", "ff", "generic-array", "group", @@ -974,7 +1443,7 @@ checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" dependencies = [ "aes", "ctr", - "digest", + "digest 0.10.7", "hex", "hmac", "pbkdf2 0.11.0", @@ -1099,7 +1568,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "reqwest", + "reqwest 0.11.27", "serde", "serde_json", "syn 2.0.61", @@ -1161,8 +1630,8 @@ checksum = "e79e5973c26d4baf0ce55520bd732314328cabe53193286671b47144145b9649" dependencies = [ "chrono", "ethers-core", - "reqwest", - "semver", + "reqwest 0.11.27", + "semver 1.0.23", "serde", "serde_json", "thiserror", @@ -1186,7 +1655,7 @@ dependencies = [ "futures-locks", "futures-util", "instant", - "reqwest", + "reqwest 0.11.27", "serde", "serde_json", "thiserror", @@ -1209,6 +1678,7 @@ dependencies = [ "const-hex", "enr", "ethers-core", + "futures-channel", "futures-core", "futures-timer", "futures-util", @@ -1218,7 +1688,7 @@ dependencies = [ "jsonwebtoken", "once_cell", "pin-project", - "reqwest", + "reqwest 0.11.27", "serde", "serde_json", "thiserror", @@ -1271,7 +1741,7 @@ dependencies = [ "path-slash", "rayon", "regex", - "semver", + "semver 1.0.23", "serde", "serde_json", "solang-parser", @@ -1363,6 +1833,17 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + [[package]] name = "ff" version = "0.13.0" @@ -1769,6 +2250,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -1832,13 +2322,19 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + [[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -1991,9 +2487,27 @@ dependencies = [ "futures-util", "http 0.2.12", "hyper 0.14.28", - "rustls", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.3.1", + "hyper-util", + "rustls 0.23.12", + "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.0", + "tower-service", + "webpki-roots 0.26.3", ] [[package]] @@ -2127,7 +2641,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.5", ] [[package]] @@ -2191,6 +2705,15 @@ dependencies = [ "log 0.4.21", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.11.0" @@ -2255,7 +2778,7 @@ checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" dependencies = [ "base64 0.13.1", "crypto-common", - "digest", + "digest 0.10.7", "hmac", "serde", "serde_json", @@ -2285,6 +2808,16 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "keccak-asm" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "422fbc7ff2f2f5bdffeb07718e5a5324dca72b0c9293d50df4026652385e3314" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + [[package]] name = "kv-log-macro" version = "1.0.7" @@ -2307,7 +2840,7 @@ dependencies = [ "lalrpop-util", "petgraph", "regex", - "regex-syntax", + "regex-syntax 0.8.3", "string_cache", "term", "tiny-keccak", @@ -2321,7 +2854,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" dependencies = [ - "regex-automata", + "regex-automata 0.4.6", ] [[package]] @@ -2401,6 +2934,15 @@ dependencies = [ "value-bag", ] +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "matches" version = "0.1.10" @@ -2417,6 +2959,17 @@ dependencies = [ "rawpointer", ] +[[package]] +name = "maybe-async" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.61", +] + [[package]] name = "md-5" version = "0.10.6" @@ -2424,7 +2977,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", - "digest", + "digest 0.10.7", ] [[package]] @@ -2433,6 +2986,14 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +[[package]] +name = "methods" +version = "0.1.0" +dependencies = [ + "risc0-build", + "risc0-build-ethereum", +] + [[package]] name = "mime" version = "0.2.6" @@ -2529,6 +3090,16 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint" version = "0.4.5" @@ -2722,6 +3293,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parity-scale-codec" version = "3.6.9" @@ -2813,6 +3390,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "path-slash" version = "0.2.1" @@ -2825,7 +3408,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ - "digest", + "digest 0.10.7", "hmac", "password-hash", "sha2", @@ -2837,7 +3420,7 @@ version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ - "digest", + "digest 0.10.7", "hmac", ] @@ -2862,6 +3445,17 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pest" +version = "2.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + [[package]] name = "petgraph" version = "0.6.4" @@ -2879,7 +3473,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" dependencies = [ "futures", - "rustc_version", + "rustc_version 0.4.0", ] [[package]] @@ -3141,6 +3735,30 @@ dependencies = [ "toml_edit 0.21.1", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check 0.9.4", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check 0.9.4", +] + [[package]] name = "proc-macro2" version = "1.0.82" @@ -3156,13 +3774,17 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" dependencies = [ + "bit-set", + "bit-vec", "bitflags 2.5.0", "lazy_static", "num-traits", "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift 0.3.0", - "regex-syntax", + "regex-syntax 0.8.3", + "rusty-fork", + "tempfile", "unarray", ] @@ -3219,6 +3841,60 @@ dependencies = [ "prost", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quinn" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b22d8e7369034b9a7132bc2008cac12f2013c8132b45e0554e6e20e2617f2156" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.23.12", + "socket2 0.5.7", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba92fb39ec7ad06ca2582c0ca834dfeadcaf06ddfc8e635c80aa7e1c05315fdd" +dependencies = [ + "bytes", + "rand 0.8.5", + "ring 0.17.8", + "rustc-hash", + "rustls 0.23.12", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" +dependencies = [ + "libc", + "once_cell", + "socket2 0.5.7", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "quote" version = "1.0.36" @@ -3451,8 +4127,17 @@ checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.6", + "regex-syntax 0.8.3", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -3463,9 +4148,15 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.3", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.3" @@ -3487,7 +4178,7 @@ dependencies = [ "http 0.2.12", "http-body 0.4.6", "hyper 0.14.28", - "hyper-rustls", + "hyper-rustls 0.24.2", "ipnet", "js-sys", "log 0.4.21", @@ -3495,22 +4186,67 @@ dependencies = [ "once_cell", "percent-encoding 2.3.1", "pin-project-lite", - "rustls", - "rustls-pemfile", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 0.1.2", "system-configuration", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", + "tower-service", + "url 2.5.0", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.25.4", + "winreg 0.50.0", +] + +[[package]] +name = "reqwest" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.3.1", + "hyper-rustls 0.27.2", + "hyper-util", + "ipnet", + "js-sys", + "log 0.4.21", + "mime 0.3.17", + "once_cell", + "percent-encoding 2.3.1", + "pin-project-lite", + "quinn", + "rustls 0.23.12", + "rustls-pemfile 2.1.3", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "tokio", + "tokio-rustls 0.26.0", + "tokio-util", "tower-service", "url 2.5.0", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", - "webpki-roots", - "winreg", + "webpki-roots 0.26.3", + "winreg 0.52.0", ] [[package]] @@ -3531,6 +4267,7 @@ dependencies = [ "bincode", "bytes", "chrono", + "compute-provider-host", "console", "dialoguer", "ethers", @@ -3552,51 +4289,227 @@ dependencies = [ "rand_chacha 0.3.1", "router", "serde", - "serde_json", - "sha2", - "sled", - "tokio", - "walkdir", - "wasm-bindgen", + "serde_json", + "sha2", + "sled", + "tokio", + "walkdir", + "wasm-bindgen", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "risc0-binfmt" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4003dd96f2e323dfef431b21a2aaddee1c6791fc32dea8eb2bff1b438bf5caf6" +dependencies = [ + "anyhow", + "elf", + "risc0-zkp", + "risc0-zkvm-platform", + "serde", + "tracing", +] + +[[package]] +name = "risc0-build" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "452836a801f4c304c567f88855184f941d778d971cb94bee25b72d4255b56f09" +dependencies = [ + "anyhow", + "cargo_metadata", + "dirs", + "docker-generate", + "risc0-binfmt", + "risc0-zkp", + "risc0-zkvm-platform", + "serde", + "serde_json", + "tempfile", +] + +[[package]] +name = "risc0-build-ethereum" +version = "1.0.0" +source = "git+https://github.com/risc0/risc0-ethereum?tag=v1.0.0#5fbbc7cb44ab37ce438c14c087ba6c4e0a669900" +dependencies = [ + "anyhow", + "hex", + "risc0-build", + "risc0-zkp", +] + +[[package]] +name = "risc0-circuit-recursion" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c4154d2fbbde5af02a1c35c90340c2749044f5d5cd7834251b616ffa28d467" +dependencies = [ + "anyhow", + "bytemuck", + "hex", + "risc0-core", + "risc0-zkp", + "tracing", +] + +[[package]] +name = "risc0-circuit-rv32im" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce836e7c553e63cbd807d15925ba5dd641a80cdee7d123619eaa60bb555ab014" +dependencies = [ + "anyhow", + "risc0-binfmt", + "risc0-core", + "risc0-zkp", + "risc0-zkvm-platform", + "serde", + "tracing", +] + +[[package]] +name = "risc0-core" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "047cc26c68c092d664ded7488dcac0462d9e31190e1598a7820fe4246d313583" +dependencies = [ + "bytemuck", + "rand_core 0.6.4", +] + +[[package]] +name = "risc0-ethereum-contracts" +version = "1.0.0" +source = "git+https://github.com/risc0/risc0-ethereum?tag=v1.0.0#5fbbc7cb44ab37ce438c14c087ba6c4e0a669900" +dependencies = [ + "alloy-sol-types", + "anyhow", + "ethers", + "risc0-zkvm", +] + +[[package]] +name = "risc0-groth16" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3309c7acaf46ed3d21df3841185afd8ea4aab9fb63dbd1974694dfdae276970" +dependencies = [ + "anyhow", + "ark-bn254", + "ark-ec", + "ark-groth16", + "ark-serialize 0.4.2", + "bytemuck", + "hex", + "num-bigint", + "num-traits", + "risc0-binfmt", + "risc0-zkp", + "serde", ] [[package]] -name = "ring" -version = "0.16.20" +name = "risc0-zkp" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "ae55272541351a2391e5051519b33bfdf41f5648216827cc2cb94a49b6937380" dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", + "anyhow", + "blake2", + "bytemuck", + "cfg-if", + "digest 0.10.7", + "hex", + "hex-literal", + "paste", + "rand_core 0.6.4", + "risc0-core", + "risc0-zkvm-platform", + "serde", + "sha2", + "tracing", ] [[package]] -name = "ring" -version = "0.17.8" +name = "risc0-zkvm" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "f234694d9dabc1172cf418b7a3ba65447caad15b994f450e9941d2a7cc89e045" dependencies = [ - "cc", + "anyhow", + "bincode", + "bonsai-sdk", + "bytemuck", + "bytes", "cfg-if", "getrandom", - "libc", - "spin 0.9.8", - "untrusted 0.9.0", - "windows-sys 0.52.0", + "hex", + "prost", + "risc0-binfmt", + "risc0-circuit-recursion", + "risc0-circuit-rv32im", + "risc0-core", + "risc0-groth16", + "risc0-zkp", + "risc0-zkvm-platform", + "rrs-lib", + "semver 1.0.23", + "serde", + "sha2", + "tempfile", + "tracing", ] [[package]] -name = "ripemd" -version = "0.1.3" +name = "risc0-zkvm-platform" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +checksum = "16735dab52ae8bf0dc30df78fce901b674f469dfd7b5f5dfddd54caea22f14d5" dependencies = [ - "digest", + "bytemuck", + "getrandom", + "libm", ] [[package]] @@ -3638,25 +4551,80 @@ dependencies = [ "url 1.7.2", ] +[[package]] +name = "rrs-lib" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4382d3af3a4ebdae7f64ba6edd9114fff92c89808004c4943b393377a25d001" +dependencies = [ + "downcast-rs", + "paste", +] + +[[package]] +name = "ruint" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "bytes", + "fastrlp", + "num-bigint", + "num-traits", + "parity-scale-codec", + "primitive-types", + "proptest", + "rand 0.8.5", + "rlp", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + [[package]] name = "rustc-hex" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + [[package]] name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver", + "semver 1.0.23", ] [[package]] @@ -3694,10 +4662,24 @@ checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log 0.4.21", "ring 0.17.8", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.23.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +dependencies = [ + "once_cell", + "ring 0.17.8", + "rustls-pki-types", + "rustls-webpki 0.102.6", + "subtle", + "zeroize", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -3707,6 +4689,22 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pemfile" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -3717,12 +4715,35 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "rustls-webpki" +version = "0.102.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +dependencies = [ + "ring 0.17.8", + "rustls-pki-types", + "untrusted 0.9.0", +] + [[package]] name = "rustversion" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "092474d1a01ea8278f69e6a358998405fae5b8b963ddaeb2b0b04a128bf1dfb0" +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.18" @@ -3851,6 +4872,15 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + [[package]] name = "semver" version = "1.0.23" @@ -3860,6 +4890,15 @@ dependencies = [ "serde", ] +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + [[package]] name = "send_wrapper" version = "0.4.0" @@ -3932,7 +4971,7 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] @@ -3943,7 +4982,7 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] @@ -3952,10 +4991,29 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "digest", + "digest 0.10.7", "keccak", ] +[[package]] +name = "sha3-asm" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d79b758b7cb2085612b11a235055e485605a5103faccdd633f35bd7aee69dd" +dependencies = [ + "cc", + "cfg-if", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shell-words" version = "1.1.0" @@ -3977,7 +5035,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", + "digest 0.10.7", "rand_core 0.6.4", ] @@ -4149,8 +5207,8 @@ dependencies = [ "fs2", "hex", "once_cell", - "reqwest", - "semver", + "reqwest 0.11.27", + "semver 1.0.23", "serde", "serde_json", "sha2", @@ -4181,12 +5239,30 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn-solidity" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c837dc8852cb7074e46b444afb81783140dab12c58867b49fb3898fbafedf7ea" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.61", +] + [[package]] name = "sync_wrapper" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + [[package]] name = "system-configuration" version = "0.5.1" @@ -4379,7 +5455,18 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls", + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.12", + "rustls-pki-types", "tokio", ] @@ -4391,11 +5478,11 @@ checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log 0.4.21", - "rustls", + "rustls 0.21.12", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", "tungstenite", - "webpki-roots", + "webpki-roots 0.25.4", ] [[package]] @@ -4536,6 +5623,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", ] [[package]] @@ -4548,6 +5636,44 @@ dependencies = [ "tracing", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log 0.4.21", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + [[package]] name = "traitobject" version = "0.1.0" @@ -4573,7 +5699,7 @@ dependencies = [ "httparse", "log 0.4.21", "rand 0.8.5", - "rustls", + "rustls 0.21.12", "sha1", "thiserror", "url 2.5.0", @@ -4601,6 +5727,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + [[package]] name = "uint" version = "0.9.5" @@ -4720,6 +5852,12 @@ dependencies = [ "serde", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "value-bag" version = "1.9.0" @@ -4744,6 +5882,15 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "waker-fn" version = "1.1.1" @@ -4847,6 +5994,19 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +[[package]] +name = "wasm-streams" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.69" @@ -4863,6 +6023,15 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "webpki-roots" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "winapi" version = "0.3.9" @@ -5070,6 +6239,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "ws_stream_wasm" version = "0.7.4" @@ -5081,7 +6260,7 @@ dependencies = [ "js-sys", "log 0.4.21", "pharos", - "rustc_version", + "rustc_version 0.4.0", "send_wrapper 0.6.0", "thiserror", "wasm-bindgen", @@ -5104,11 +6283,34 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.61", +] + [[package]] name = "zeroize" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] [[package]] name = "zeroize_derive" diff --git a/packages/server/Cargo.toml b/packages/server/Cargo.toml index df21459..dccb664 100644 --- a/packages/server/Cargo.toml +++ b/packages/server/Cargo.toml @@ -14,6 +14,7 @@ fhe-util = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-bet rand_chacha = "0.3.1" rand = "0.8.5" ethers = "2.0" +compute-provider-host = { path = "../compute_provider/host" } getrandom = { version = "0.2.11", features = ["js"] } # Ethers' async features rely upon the Tokio async runtime. tokio = { version = "1.37.0", features = ["full"] } diff --git a/packages/server/src/bin/enclave_server.rs b/packages/server/src/bin/enclave_server.rs index c5b3451..5123f9b 100644 --- a/packages/server/src/bin/enclave_server.rs +++ b/packages/server/src/bin/enclave_server.rs @@ -18,7 +18,6 @@ use iron::Chain; use iron::headers::{Header, HeaderFormat, HeaderFormatter, Bearer}; use router::Router; use std::io::Read; - use iron_cors::CorsMiddleware; use ethers::{ From 9488522382b553dc47372ea0a2406558642085a2 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Sat, 24 Aug 2024 00:02:09 +0500 Subject: [PATCH 02/62] Update - Merkle Tree --- packages/compute_provider/core/Cargo.toml | 1 + packages/compute_provider/core/src/lib.rs | 39 +++++++++++++++++------ packages/compute_provider/host/src/lib.rs | 2 +- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/packages/compute_provider/core/Cargo.toml b/packages/compute_provider/core/Cargo.toml index a850eb3..04466f9 100644 --- a/packages/compute_provider/core/Cargo.toml +++ b/packages/compute_provider/core/Cargo.toml @@ -9,3 +9,4 @@ risc0-zkp = { workspace = true } serde = { workspace = true } fhe = { workspace = true } fhe-traits = { workspace = true } +zk-kit-imt = "0.0.5" \ No newline at end of file diff --git a/packages/compute_provider/core/src/lib.rs b/packages/compute_provider/core/src/lib.rs index cd4d345..02b75b5 100644 --- a/packages/compute_provider/core/src/lib.rs +++ b/packages/compute_provider/core/src/lib.rs @@ -1,13 +1,13 @@ -use risc0_zkp::core::digest::Digest; +use fhe::bfv::{BfvParameters, Ciphertext}; +use fhe_traits::{Deserialize, DeserializeParametrized, Serialize}; use risc0_zkvm::sha::{Impl, Sha256}; -use fhe::bfv::{Ciphertext, BfvParameters}; -use fhe_traits::{DeserializeParametrized, Serialize, Deserialize}; use std::sync::Arc; +use zk_kit_imt::imt::IMT; #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] pub struct TallyResult { pub tallied_ciphertext: Vec, - pub ciphertexts_digest: Digest + pub merkle_root: String, } #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] @@ -29,13 +29,34 @@ impl CiphertextInput { } let tally: Arc = Arc::new(sum); - // Compute the digest of the ciphertexts - let digest = *Impl::hash_bytes(&self.ciphertexts.concat()); + let merkle_root = self.compute_merkle_root(); TallyResult { tallied_ciphertext: tally.to_bytes(), - ciphertexts_digest: digest + merkle_root, } } - -} \ No newline at end of file + + fn compute_merkle_root(&self) -> String { + fn hash_function(nodes: Vec) -> String { + let concatenated = nodes.join(""); + let hash = Impl::hash_bytes(concatenated.as_bytes()); + format!("{:?}", hash) + } + + let num_leaves = self.ciphertexts.len(); + let arity = 2; + let depth = (num_leaves as f64).log(arity as f64).ceil() as usize; + let zero = format!("{:?}", Impl::hash_bytes(&[0u8])); + + let mut tree = + IMT::new(hash_function, depth, zero, arity, vec![]).expect("Failed to create IMT"); + + for ciphertext in &self.ciphertexts { + let hash = format!("{:?}", Impl::hash_bytes(ciphertext)); + tree.insert(hash).expect("Failed to insert into IMT"); + } + + tree.root().expect("Failed to get root from IMT") + } +} diff --git a/packages/compute_provider/host/src/lib.rs b/packages/compute_provider/host/src/lib.rs index f591426..eaba7d1 100644 --- a/packages/compute_provider/host/src/lib.rs +++ b/packages/compute_provider/host/src/lib.rs @@ -67,7 +67,7 @@ mod tests { let tally = decrypt_result(&result, &sk, ¶ms); assert_eq!(tally, inputs.iter().sum::()); - assert_eq!(result.ciphertexts_digest.to_string().len(), 64); + assert_eq!(result.merkle_root.len(), 72); } fn create_params() -> Arc { From ef83eee2d192684d7efc9b154e6e4632aeba2091 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Mon, 26 Aug 2024 22:38:44 +0500 Subject: [PATCH 03/62] Refactor enclave server --- packages/server/.cargo/config.toml | 3 + packages/server/Cargo.lock | 11 + packages/server/src/bin/cli.rs | 358 +---- packages/server/src/bin/enclave_server.rs | 1238 +---------------- packages/server/src/bin/start_rounds.rs | 4 +- packages/server/src/cli/auth.rs | 38 + packages/server/src/cli/mod.rs | 90 ++ packages/server/src/cli/voting.rs | 211 +++ .../server/src/enclave_server/database.rs | 66 + packages/server/src/enclave_server/mod.rs | 23 + packages/server/src/enclave_server/models.rs | 239 ++++ .../src/enclave_server/routes/ciphernode.rs | 365 +++++ .../server/src/enclave_server/routes/index.rs | 102 ++ .../server/src/enclave_server/routes/mod.rs | 15 + .../src/enclave_server/routes/rounds.rs | 243 ++++ .../server/src/enclave_server/routes/state.rs | 148 ++ .../src/enclave_server/routes/voting.rs | 144 ++ packages/server/src/lib.rs | 3 + packages/server/src/main.rs | 1 - packages/server/src/{bin => }/util.rs | 2 +- 20 files changed, 1709 insertions(+), 1595 deletions(-) create mode 100644 packages/server/.cargo/config.toml create mode 100644 packages/server/src/cli/auth.rs create mode 100644 packages/server/src/cli/mod.rs create mode 100644 packages/server/src/cli/voting.rs create mode 100644 packages/server/src/enclave_server/database.rs create mode 100644 packages/server/src/enclave_server/mod.rs create mode 100644 packages/server/src/enclave_server/models.rs create mode 100644 packages/server/src/enclave_server/routes/ciphernode.rs create mode 100644 packages/server/src/enclave_server/routes/index.rs create mode 100644 packages/server/src/enclave_server/routes/mod.rs create mode 100644 packages/server/src/enclave_server/routes/rounds.rs create mode 100644 packages/server/src/enclave_server/routes/state.rs create mode 100644 packages/server/src/enclave_server/routes/voting.rs create mode 100644 packages/server/src/lib.rs rename packages/server/src/{bin => }/util.rs (98%) diff --git a/packages/server/.cargo/config.toml b/packages/server/.cargo/config.toml new file mode 100644 index 0000000..0c51459 --- /dev/null +++ b/packages/server/.cargo/config.toml @@ -0,0 +1,3 @@ +[env] +INFURAKEY = "default val" +PRIVATEKEY = "default val" \ No newline at end of file diff --git a/packages/server/Cargo.lock b/packages/server/Cargo.lock index 822ad47..6ecccb9 100644 --- a/packages/server/Cargo.lock +++ b/packages/server/Cargo.lock @@ -1005,6 +1005,7 @@ dependencies = [ "risc0-zkp", "risc0-zkvm", "serde", + "zk-kit-imt", ] [[package]] @@ -6343,6 +6344,16 @@ dependencies = [ "zstd", ] +[[package]] +name = "zk-kit-imt" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4bec228e2acafec7bd22c9a0a5e0c5e2a6d0c17df69f5ad11c24ce6dc6356c6" +dependencies = [ + "hex", + "tiny-keccak", +] + [[package]] name = "zstd" version = "0.11.2+zstd.1.5.2" diff --git a/packages/server/src/bin/cli.rs b/packages/server/src/bin/cli.rs index 6453160..049ae4c 100644 --- a/packages/server/src/bin/cli.rs +++ b/packages/server/src/bin/cli.rs @@ -1,357 +1,5 @@ -mod util; +use rfv::cli::run_cli; -use dialoguer::{theme::ColorfulTheme, Input, FuzzySelect}; -use std::{thread, time, env}; -use serde::{Deserialize, Serialize}; -use std::fs::File; -use std::io::Read; - -use fhe::{ - bfv::{BfvParametersBuilder, Encoding, Plaintext, PublicKey}, -}; -use fhe_traits::{FheEncoder, FheEncrypter, Serialize as FheSerialize, DeserializeParametrized}; -use rand::{thread_rng}; -use util::timeit::{timeit}; - -use hyper::Request; -use hyper::Method; -use hyper_tls::HttpsConnector; -use hyper_util::{client::legacy::Client as HyperClient, rt::TokioExecutor}; -use bytes::Bytes; - -use http_body_util::Empty; -use http_body_util::BodyExt; -use tokio::io::{AsyncWriteExt as _, self}; - -use hmac::{Hmac, Mac}; -use jwt::SignWithKey; -use sha2::Sha256; -use std::collections::BTreeMap; - - -#[derive(Debug, Deserialize, Serialize)] -struct JsonResponse { - response: String -} - -#[derive(Debug, Deserialize, Serialize)] -struct JsonResponseTxHash { - response: String, - tx_hash: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct JsonRequestGetRounds { - response: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct RoundCount { - round_count: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct JsonRequest { - response: String, - pk_share: u32, - id: u32, - round_id: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct CrispConfig { - round_id: u32, - poll_length: u32, - chain_id: u32, - voting_address: String, - ciphernode_count: u32, - enclave_address: String, - authentication_id: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct PKRequest { - round_id: u32, - pk_bytes: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -struct EncryptedVote { - round_id: u32, - enc_vote_bytes: Vec, - postId: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct AuthenticationLogin { - postId: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct AuthenticationResponse { - response: String, - jwt_token: String, -} - -#[tokio::main] -async fn main() -> Result<(), Box> { - - let https = HttpsConnector::new(); - //let client = HyperClient::builder(TokioExecutor::new()).build::<_, Empty>(https); - let client_get = HyperClient::builder(TokioExecutor::new()).build::<_, Empty>(https.clone()); - let client = HyperClient::builder(TokioExecutor::new()).build::<_, String>(https); - let mut auth_res = AuthenticationResponse { - response: "".to_string(), - jwt_token: "".to_string(), - }; - - print!("{esc}[2J{esc}[1;1H", esc = 27 as char); - let selections = &[ - "CRISP: Voting Protocol (ETH)", - "More Coming Soon!" - ]; - - let selections_2 = &[ - "Initialize new CRISP round.", - "Continue Existing CRISP round." - ]; - - let selections_3 = &[ - "Abstain.", - "Vote yes.", - "Vote no." - ]; - - let selection_1 = FuzzySelect::with_theme(&ColorfulTheme::default()) - .with_prompt("Enclave (EEEE): Please choose the private execution environment you would like to run!") - .default(0) - .items(&selections[..]) - .interact() - .unwrap(); - - if selection_1 == 0 { - print!("{esc}[2J{esc}[1;1H", esc = 27 as char); - //println!("Encrypted Protocol Selected {}!", selections[selection_1]); - let selection_2 = FuzzySelect::with_theme(&ColorfulTheme::default()) - .with_prompt("Create a new CRISP round or particpate in an existing round.") - .default(0) - .items(&selections_2[..]) - .interact() - .unwrap(); - - if selection_2 == 0 { - print!("{esc}[2J{esc}[1;1H", esc = 27 as char); - println!("Starting new CRISP round!"); - // let input_token: String = Input::with_theme(&ColorfulTheme::default()) - // .with_prompt("Enter Proposal Registration Token") - // .interact_text() - // .unwrap(); - print!("{esc}[2J{esc}[1;1H", esc = 27 as char); - println!("Reading proposal details from config."); - let path = env::current_dir().unwrap(); - let mut pathst = path.display().to_string(); - pathst.push_str("/example_config.json"); - let mut file = File::open(pathst).unwrap(); - let mut data = String::new(); - file.read_to_string(&mut data).unwrap(); - let config: CrispConfig = serde_json::from_str(&data).expect("JSON was not well-formatted"); - println!("round id: {:?}", config.round_id); // get new round id from current id in server - println!("poll length {:?}", config.poll_length); - println!("chain id: {:?}", config.chain_id); - println!("voting contract: {:?}", config.voting_address); - println!("ciphernode count: {:?}", config.ciphernode_count); - - println!("Initializing Keyshare nodes..."); - - let response_id = JsonRequestGetRounds { response: "Test".to_string() }; - let _out = serde_json::to_string(&response_id).unwrap(); - let mut url_id = config.enclave_address.clone(); - url_id.push_str("/get_rounds"); - - //let token = Authorization::bearer("some-opaque-token").unwrap(); - //println!("bearer token {:?}", token.token()); - //todo: add auth field to config file to get bearer token - let req = Request::builder() - .method(Method::GET) - .uri(url_id) - .body(Empty::::new())?; - - let resp = client_get.request(req).await?; - - println!("Response status: {}", resp.status()); - - let body_bytes = resp.collect().await?.to_bytes(); - let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); - let count: RoundCount = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); - println!("Server Round Count: {:?}", count.round_count); - - // TODO: get secret from env var - // let key: Hmac = Hmac::new_from_slice(b"some-secret")?; - // let mut claims = BTreeMap::new(); - // claims.insert("postId", config.authentication); - // let mut bearer_str = "Bearer ".to_string(); - // let token_str = claims.sign_with_key(&key)?; - // bearer_str.push_str(&token_str); - // println!("{:?}", bearer_str); - - let round_id = count.round_count + 1; - let response = CrispConfig { - round_id: round_id, - poll_length: config.poll_length, - chain_id: config.chain_id, - voting_address: config.voting_address, - ciphernode_count: config.ciphernode_count, - enclave_address: config.enclave_address.clone(), - authentication_id: config.authentication_id.clone(), - }; - let out = serde_json::to_string(&response).unwrap(); - let mut url = config.enclave_address.clone(); - url.push_str("/init_crisp_round"); - let req = Request::builder() - .header("authorization", "Bearer fpKL54jvWmEGVoRdCNjG") - .method(Method::POST) - .uri(url) - .body(out)?; - - let mut resp = client.request(req).await?; - - println!("Response status: {}", resp.status()); - - while let Some(next) = resp.frame().await { - let frame = next?; - if let Some(chunk) = frame.data_ref() { - io::stdout().write_all(chunk).await?; - } - } - println!("Round Initialized."); - println!("Gathering Keyshare nodes for execution environment..."); - let three_seconds = time::Duration::from_millis(1000); - thread::sleep(three_seconds); - println!("\nYou can now vote Encrypted with Round ID: {:?}", round_id); - - } - if selection_2 == 1 { - print!("{esc}[2J{esc}[1;1H", esc = 27 as char); - let input_crisp_id: u32 = Input::with_theme(&ColorfulTheme::default()) - .with_prompt("Enter CRISP round ID.") - .interact_text() - .unwrap(); - let path = env::current_dir().unwrap(); - let mut pathst = path.display().to_string(); - pathst.push_str("/example_config.json"); - let mut file = File::open(pathst).unwrap(); - let mut data = String::new(); - file.read_to_string(&mut data).unwrap(); - let config: CrispConfig = serde_json::from_str(&data).expect("JSON was not well-formatted"); - println!("Voting state Initialized"); - - // get authentication token - let user = AuthenticationLogin { - postId: config.authentication_id.clone(), - }; - - let out = serde_json::to_string(&user).unwrap(); - let mut url = config.enclave_address.clone(); - url.push_str("/authentication_login"); - let req = Request::builder() - .method(Method::POST) - .uri(url) - .body(out)?; - - let mut resp = client.request(req).await?; - let body_bytes = resp.collect().await?.to_bytes(); - let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); - auth_res = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); - println!("Authentication response {:?}", auth_res); - - // get public encrypt key - let v: Vec = vec! [0]; - let response_pk = PKRequest { round_id: input_crisp_id, pk_bytes: v }; - let out = serde_json::to_string(&response_pk).unwrap(); - let mut url = config.enclave_address.clone(); - url.push_str("/get_pk_by_round"); - let req = Request::builder() - .method(Method::POST) - .uri(url) - .body(out)?; - - let resp = client.request(req).await?; - - println!("Response status: {}", resp.status()); - - let body_bytes = resp.collect().await?.to_bytes(); - let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); - let pk_res: PKRequest = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); - println!("Shared Public Key for CRISP round {:?} collected.", pk_res.round_id); - - let degree = 4096; - let plaintext_modulus: u64 = 4096; - let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; - // Let's generate the BFV parameters structure. - let params = timeit!( - "Parameters generation", - BfvParametersBuilder::new() - .set_degree(degree) - .set_plaintext_modulus(plaintext_modulus) - .set_moduli(&moduli) - .build_arc()? - ); - let pk_deserialized = PublicKey::from_bytes(&pk_res.pk_bytes, ¶ms).unwrap(); - // todo: validate that this user can vote - let selection_3 = FuzzySelect::with_theme(&ColorfulTheme::default()) - .with_prompt("Please select your voting option.") - .default(0) - .items(&selections_3[..]) - .interact() - .unwrap(); - - let mut vote_choice: u64 = 0; - if selection_3 == 0 { - println!("Exiting voting system. You may choose to vote later."); - vote_choice = 0; - } - if selection_3 == 1 { - vote_choice = 1; - } - if selection_3 == 2 { - vote_choice = 0; - } - println!("Encrypting vote."); - let votes: Vec = [vote_choice].to_vec(); - let pt = Plaintext::try_encode(&[votes[0]], Encoding::poly(), ¶ms)?; - let ct = pk_deserialized.try_encrypt(&pt, &mut thread_rng())?; - println!("Vote encrypted."); - println!("Calling voting contract with encrypted vote."); - - let request_contract = EncryptedVote { - round_id: input_crisp_id, - enc_vote_bytes: ct.to_bytes(), - postId: auth_res.jwt_token, - }; - let out = serde_json::to_string(&request_contract).unwrap(); - let mut url = config.enclave_address.clone(); - url.push_str("/broadcast_enc_vote"); - let req = Request::builder() - .method(Method::POST) - .uri(url) - .body(out)?; - - let resp = client.request(req).await?; - - println!("Response status: {}", resp.status()); - - let body_bytes = resp.collect().await?.to_bytes(); - let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); - let contract_res: JsonResponseTxHash = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); - println!("Contract call: {:?}", contract_res.response); - println!("TxHash is {:?}", contract_res.tx_hash); - } - - } - if selection_1 == 1 { - println!("Check back soon!"); - std::process::exit(1); - } - - Ok(()) +fn main() -> Result<(), Box> { + run_cli() } diff --git a/packages/server/src/bin/enclave_server.rs b/packages/server/src/bin/enclave_server.rs index 5123f9b..fee968a 100644 --- a/packages/server/src/bin/enclave_server.rs +++ b/packages/server/src/bin/enclave_server.rs @@ -1,1237 +1,5 @@ -mod util; +use rfv::enclave_server::start_server; -use std::{env, sync::Arc, str}; -use chrono::{Utc}; -use fhe::{ - bfv::{BfvParametersBuilder, PublicKey}, - mbfv::{AggregateIter, CommonRandomPoly, PublicKeyShare}, -}; -use fhe_traits::{Serialize as FheSerialize}; -use rand::{Rng, thread_rng}; -use util::timeit::{timeit}; -use serde::{Deserialize, Serialize}; - -use iron::prelude::*; -use iron::status; -use iron::mime::Mime; -use iron::Chain; -use iron::headers::{Header, HeaderFormat, HeaderFormatter, Bearer}; -use router::Router; -use std::io::Read; -use iron_cors::CorsMiddleware; - -use ethers::{ - prelude::{abigen}, - providers::{Http, Provider, Middleware}, - middleware::{SignerMiddleware, MiddlewareBuilder}, - signers::{LocalWallet, Signer}, - types::{Address, Bytes, TxHash, U64, BlockNumber}, -}; - -use sled::Db; -use once_cell::sync::Lazy; - -use hmac::{Hmac, Mac}; -use jwt::{ VerifyWithKey, SignWithKey }; -use sha2::Sha256; -use std::collections::BTreeMap; - -static GLOBAL_DB: Lazy = Lazy::new(|| { - let pathdb = env::current_dir().unwrap(); - let mut pathdbst = pathdb.display().to_string(); - pathdbst.push_str("/database/enclave_server"); - sled::open(pathdbst.clone()).unwrap() -}); - -//static open_db: Database = Database::new(); - -// static pathdb: String = env::current_dir().unwrap(); -// static mut pathdbst: String = pathdb.display().to_string(); -// pathdbst.push_str("/database"); -// static db = sled::open(pathdbst.clone()).unwrap(); -//static db: Db = sled::open("/home/ubuntu/guild/CRISP/packages/rust/database").unwrap(); - -// pick a string at random -fn pick_response() -> String { - "Test".to_string() -} - -#[derive(Debug, Deserialize, Serialize)] -struct JsonResponse { - response: String -} - -#[derive(Debug, Deserialize, Serialize)] -struct RegisterNodeResponse { - response: String, - node_index: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct JsonResponseTxHash { - response: String, - tx_hash: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct JsonRequest { - response: String, - pk_share: Vec, - id: u32, - round_id: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct CrispConfig { - round_id: u32, - poll_length: u32, - chain_id: u32, - voting_address: String, - ciphernode_count: u32, - enclave_address: String, - authentication_id: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct RoundCount { - round_count: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct PKShareCount { - round_id: u32, - share_id: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct PKRequest { - round_id: u32, - pk_bytes: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -struct CRPRequest { - round_id: u32, - crp_bytes: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -struct TimestampRequest { - round_id: u32, - timestamp: i64, -} - -#[derive(Debug, Deserialize, Serialize)] -struct PollLengthRequest { - round_id: u32, - poll_length: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct VoteCountRequest { - round_id: u32, - vote_count: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct SKSShareRequest { - response: String, - sks_share: Vec, - index: u32, - round_id: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct EncryptedVote { - round_id: u32, - enc_vote_bytes: Vec, - postId: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct GetRoundRequest { - round_id: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct GetEmojisRequest { - round_id: u32, - emojis: [String; 2], -} - -#[derive(Debug, Deserialize, Serialize)] -struct SKSSharePoll { - response: String, - round_id: u32, - ciphernode_count: u32, //TODO: dont need this -} - -#[derive(Debug, Deserialize, Serialize)] -struct SKSShareResponse { - response: String, - round_id: u32, - sks_shares: Vec>, -} - -#[derive(Debug, Deserialize, Serialize)] -struct ReportTallyRequest { - round_id: u32, - option_1: u32, - option_2: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct WebResultRequest { - round_id: u32, - option_1_tally: u32, - option_2_tally: u32, - total_votes: u32, - option_1_emoji: String, - option_2_emoji: String, - end_time: i64 -} - -#[derive(Debug, Deserialize, Serialize)] -struct AllWebStates { - states: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -struct StateWeb { - id: u32, - status: String, - poll_length: u32, - voting_address: String, - chain_id: u32, - ciphernode_count: u32, - pk_share_count: u32, - sks_share_count: u32, - vote_count: u32, - start_time: i64, - ciphernode_total: u32, - emojis: [String; 2], -} - -#[derive(Debug, Deserialize, Serialize)] -struct StateLite { - id: u32, - status: String, - poll_length: u32, - voting_address: String, - chain_id: u32, - ciphernode_count: u32, - pk_share_count: u32, - sks_share_count: u32, - vote_count: u32, - crp: Vec, - pk: Vec, - start_time: i64, - block_start: U64, - ciphernode_total: u32, - emojis: [String; 2], -} - -#[derive(Debug, Deserialize, Serialize)] -struct Round { - id: u32, - status: String, - poll_length: u32, - voting_address: String, - chain_id: u32, - ciphernode_count: u32, - pk_share_count: u32, - sks_share_count: u32, - vote_count: u32, - crp: Vec, - pk: Vec, - start_time: i64, - block_start: U64, - ciphernode_total: u32, - emojis: [String; 2], - votes_option_1: u32, - votes_option_2: u32, - ciphernodes: Vec, - has_voted: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -struct Ciphernode { - id: u32, - pk_share: Vec, - sks_share: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -struct GetCiphernode { - round_id: u32, - ciphernode_id: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct GetEligibilityRequest { - round_id: u32, - node_id: u32, - is_eligible: bool, - reason: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct AuthenticationDB { - jwt_tokens: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -struct AuthenticationLogin { - postId: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct AuthenticationResponse { - response: String, - jwt_token: String, -} - -fn generate_emoji() -> (String, String) { - let emojis = [ - "🍇","🍈","🍉","🍊","🍋","🍌","🍍","ðŸĨ­","🍎","🍏", - "🍐","🍑","🍒","🍓","ðŸŦ","ðŸĨ","🍅","ðŸŦ’","ðŸĨĨ","ðŸĨ‘", - "🍆","ðŸĨ”","ðŸĨ•","ðŸŒ―","ðŸŒķïļ","ðŸŦ‘","ðŸĨ’","ðŸĨŽ","ðŸĨĶ","🧄", - "🧅","🍄","ðŸĨœ","ðŸŦ˜","🌰","🍞","ðŸĨ","ðŸĨ–","ðŸŦ“","ðŸĨĻ", - "ðŸĨŊ","ðŸĨž","🧇","🧀","🍖","🍗","ðŸĨĐ","ðŸĨ“","🍔","🍟", - "🍕","🌭","ðŸĨŠ","ðŸŒŪ","ðŸŒŊ","ðŸŦ”","ðŸĨ™","🧆","ðŸĨš","ðŸģ", - "ðŸĨ˜","ðŸē","ðŸŦ•","ðŸĨĢ","ðŸĨ—","ðŸŋ","🧈","🧂","ðŸĨŦ","ðŸą", - "🍘","🍙","🍚","🍛","🍜","🍝","🍠","ðŸĒ","ðŸĢ","ðŸĪ", - "ðŸĨ","ðŸĨŪ","ðŸĄ","ðŸĨŸ","ðŸĨ ","ðŸĨĄ","ðŸĶ€","ðŸĶž","ðŸĶ","ðŸĶ‘", - "ðŸĶŠ","ðŸĶ","🍧","ðŸĻ","ðŸĐ","🍊","🎂","🍰","🧁","ðŸĨ§", - "ðŸŦ","🍎","🍭","ðŸŪ","ðŸŊ","🍞","ðŸĨ›","☕","ðŸĩ","ðŸū", - "🍷","ðŸļ","ðŸđ","🍚","ðŸŧ","ðŸĨ‚","ðŸĨƒ", - ]; - let mut index1 = rand::thread_rng().gen_range(0..emojis.len()); - let index2 = rand::thread_rng().gen_range(0..emojis.len()); - if index1 == index2 { - if index1 == emojis.len() { - index1 = index1 - 1; - } else { - index1 = index1 + 1; - }; - }; - (emojis[index1].to_string(), emojis[index2].to_string()) -} - -fn get_state(round_id: u32) -> (Round, String) { - let mut round_key = round_id.to_string(); - round_key.push_str("-storage"); - println!("Database key is {:?}", round_key); - let state_out = GLOBAL_DB.get(round_key.clone()).unwrap().unwrap(); - let state_out_str = str::from_utf8(&state_out).unwrap(); - let state_out_struct: Round = serde_json::from_str(&state_out_str).unwrap(); - (state_out_struct, round_key) -} - -fn get_round_count() -> u32 { - let round_key = "round_count"; - let round_db = GLOBAL_DB.get(round_key).unwrap(); - if round_db == None { - println!("initializing first round in db"); - GLOBAL_DB.insert(round_key, b"0".to_vec()).unwrap(); - } - let round_str = std::str::from_utf8(round_db.unwrap().as_ref()).unwrap().to_string(); - round_str.parse::().unwrap() -} - -#[tokio::main] -async fn broadcast_enc_vote(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let incoming: EncryptedVote = serde_json::from_str(&payload).unwrap(); - let mut response_str = ""; - let mut converter = "".to_string(); - let (mut state, key) = get_state(incoming.round_id); - - for i in 0..state.has_voted.len() { - if state.has_voted[i] == incoming.postId { - response_str = "User Has Already Voted"; - } else { - response_str = "Vote Successful"; - } - }; - - if response_str == "Vote Successful" { - let sol_vote = Bytes::from(incoming.enc_vote_bytes); - let tx_hash = call_contract(sol_vote, state.voting_address.clone()).await.unwrap(); - converter = "0x".to_string(); - for i in 0..32 { - if tx_hash[i] <= 16 { - converter.push_str("0"); - converter.push_str(&format!("{:x}", tx_hash[i])); - } else { - converter.push_str(&format!("{:x}", tx_hash[i])); - } - } - - state.vote_count = state.vote_count + 1; - state.has_voted.push(incoming.postId); - let state_str = serde_json::to_string(&state).unwrap(); - let state_bytes = state_str.into_bytes(); - GLOBAL_DB.insert(key, state_bytes).unwrap(); - }; - - let response = JsonResponseTxHash { response: response_str.to_string(), tx_hash: converter }; - let out = serde_json::to_string(&response).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - println!("Request for round {:?} send vote tx", incoming.round_id); - Ok(Response::with((content_type, status::Ok, out))) -} - -async fn call_contract(enc_vote: Bytes, address: String) -> Result> { - println!("calling voting contract"); - - let infura_val = env!("INFURAKEY"); - let mut rpc_url = "https://sepolia.infura.io/v3/".to_string(); - rpc_url.push_str(&infura_val); - - let provider = Provider::::try_from(rpc_url.clone())?; - // let block_number: U64 = provider.get_block_number().await?; - // println!("{block_number}"); - abigen!( - IVOTE, - r#"[ - function voteEncrypted(bytes memory _encVote) public - function getVote(address id) public returns(bytes memory) - event Transfer(address indexed from, address indexed to, uint256 value) - ]"#, - ); - - //const RPC_URL: &str = "https://eth.llamarpc.com"; - let vote_address: &str = &address; - - let eth_val = env!("PRIVATEKEY"); - let wallet: LocalWallet = eth_val - .parse::().unwrap() - .with_chain_id(11155111 as u64); - - let nonce_manager = provider.clone().nonce_manager(wallet.address()); - let curr_nonce = nonce_manager - .get_transaction_count(wallet.address(), Some(BlockNumber::Pending.into())) - .await? - .as_u64(); - - let client = SignerMiddleware::new(provider.clone(), wallet.clone()); - let address: Address = vote_address.parse()?; - let contract = IVOTE::new(address, Arc::new(client.clone())); - - let test = contract.vote_encrypted(enc_vote).nonce(curr_nonce).send().await?.clone(); - println!("{:?}", test); - Ok(test) -} - -// fn register_cyphernode(req: &mut Request) -> IronResult { - // register ip address or some way to contact nodes when a computation request comes in - -// } - -fn get_round_eligibility(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let mut incoming: GetEligibilityRequest = serde_json::from_str(&payload).unwrap(); - println!("Request node elegibility for round {:?}", incoming.round_id); - - let (state, _key) = get_state(incoming.round_id); - - for i in 1..state.ciphernodes.len() { - println!("checking ciphernode {:?}", i); - println!("server db id {:?}", state.ciphernodes[i as usize].id); - println!("incoming request id {:?}", incoming.node_id); - if state.ciphernodes[i as usize].id == incoming.node_id { - incoming.is_eligible = true; - incoming.reason = "Previously Registered".to_string(); - }; - }; - - if state.ciphernode_total == state.ciphernode_count && incoming.reason != "Previously Registered" { - incoming.is_eligible = false; - incoming.reason = "Round Full".to_string(); - }; - - if state.ciphernode_total > state.ciphernode_count && incoming.reason != "Previously Registered" { - incoming.is_eligible = true; - incoming.reason = "Open Node Spot".to_string(); - }; - - let init_time = Utc::now(); - let timestamp = init_time.timestamp(); - - if timestamp >= (state.start_time + state.poll_length as i64) { - incoming.is_eligible = false; - incoming.reason = "Waiting For New Round".to_string(); - } - - let out = serde_json::to_string(&incoming).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_node_by_round(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let incoming: GetCiphernode = serde_json::from_str(&payload).unwrap(); - println!("Request node data for round {:?}", incoming.round_id); - - let (state, _key) = get_state(incoming.round_id); - let mut cnode = Ciphernode { - id: 0, - pk_share: vec![0], - sks_share: vec![0], - }; - - for i in 0..state.ciphernodes.len() { - if state.ciphernodes[i as usize].id == incoming.ciphernode_id { - cnode.id = state.ciphernodes[i as usize].id; - cnode.pk_share = state.ciphernodes[i as usize].pk_share.clone(); - cnode.sks_share = state.ciphernodes[i as usize].sks_share.clone(); - }; - }; - - if cnode.id != 0 { - let out = serde_json::to_string(&cnode).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) - } else { - let response = JsonResponse { response: "Ciphernode Not Registered".to_string() }; - let out = serde_json::to_string(&response).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) - } - - // let response = JsonResponse { response: "Ciphernode Not Registered".to_string() }; - // let out = serde_json::to_string(&response).unwrap(); - - // let content_type = "application/json".parse::().unwrap(); - // Ok(Response::with((content_type, status::Ok, out))) -} - -fn report_tally(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let incoming: ReportTallyRequest = serde_json::from_str(&payload).unwrap(); - println!("Request report tally for round {:?}", incoming.round_id); - - let (mut state, key) = get_state(incoming.round_id); - if state.votes_option_1 == 0 && state.votes_option_2 == 0 { - state.votes_option_1 = incoming.option_1; - state.votes_option_2 = incoming.option_2; - - let state_str = serde_json::to_string(&state).unwrap(); - let state_bytes = state_str.into_bytes(); - GLOBAL_DB.insert(key, state_bytes).unwrap(); - } - let response = JsonResponse { response: "Tally Reported".to_string() }; - let out = serde_json::to_string(&response).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_web_result(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let incoming: GetRoundRequest = serde_json::from_str(&payload).unwrap(); - println!("Request web state for round {:?}", incoming.round_id); - - let (state, _key) = get_state(incoming.round_id); - - let response = WebResultRequest { - round_id: incoming.round_id, - option_1_tally: state.votes_option_1, - option_2_tally: state.votes_option_2, - total_votes: state.votes_option_1 + state.votes_option_2, - option_1_emoji: state.emojis[0].clone(), - option_2_emoji: state.emojis[1].clone(), - end_time: state.start_time + state.poll_length as i64 - }; - - let out = serde_json::to_string(&response).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_web_result_all(req: &mut Request) -> IronResult { - println!("Request all web state."); - - let round_count = get_round_count(); - let mut states: Vec = Vec::with_capacity(round_count as usize); - - for i in 1..round_count { - let (state, _key) = get_state(i); - let web_state = WebResultRequest { - round_id: i, - option_1_tally: state.votes_option_1, - option_2_tally: state.votes_option_2, - total_votes: state.votes_option_1 + state.votes_option_2, - option_1_emoji: state.emojis[0].clone(), - option_2_emoji: state.emojis[1].clone(), - end_time: state.start_time + state.poll_length as i64 - }; - states.push(web_state); - } - - let response = AllWebStates { states: states }; - let out = serde_json::to_string(&response).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_poll_length_by_round(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let mut incoming: PollLengthRequest = serde_json::from_str(&payload).unwrap(); - println!("Request poll length for round {:?}", incoming.round_id); - - let (state, _key) = get_state(incoming.round_id); - incoming.poll_length = state.poll_length; - let out = serde_json::to_string(&incoming).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_emojis_by_round(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let mut incoming: GetEmojisRequest = serde_json::from_str(&payload).unwrap(); - println!("Request emojis for round {:?}", incoming.round_id); - - let (state, _key) = get_state(incoming.round_id); - incoming.emojis = state.emojis; - let out = serde_json::to_string(&incoming).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_round_state(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let incoming: GetRoundRequest = serde_json::from_str(&payload).unwrap(); - println!("Request state for round {:?}", incoming.round_id); - - let (state, _key) = get_state(incoming.round_id); - let out = serde_json::to_string(&state).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_round_state_web(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let incoming: GetRoundRequest = serde_json::from_str(&payload).unwrap(); - println!("Request state for round {:?}", incoming.round_id); - - let (state, _key) = get_state(incoming.round_id); - let state_lite = StateWeb { - id: state.id, - status: state.status, - poll_length: state.poll_length, - voting_address: state.voting_address, - chain_id: state.chain_id, - ciphernode_count: state.ciphernode_count, - pk_share_count: state.pk_share_count, - sks_share_count: state.sks_share_count, - vote_count: state.vote_count, - start_time: state.start_time, - ciphernode_total: state.ciphernode_total, - emojis: state.emojis, - }; - - let out = serde_json::to_string(&state_lite).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_round_state_lite(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let incoming: GetRoundRequest = serde_json::from_str(&payload).unwrap(); - println!("Request state for round {:?}", incoming.round_id); - - let (state, _key) = get_state(incoming.round_id); - let state_lite = StateLite { - id: state.id, - status: state.status, - poll_length: state.poll_length, - voting_address: state.voting_address, - chain_id: state.chain_id, - ciphernode_count: state.ciphernode_count, - pk_share_count: state.pk_share_count, - sks_share_count: state.sks_share_count, - vote_count: state.vote_count, - crp: state.crp, - pk: state.pk, - start_time: state.start_time, - block_start: state.block_start, - ciphernode_total: state.ciphernode_total, - emojis: state.emojis, - }; - - let out = serde_json::to_string(&state_lite).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_vote_count_by_round(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let mut incoming: VoteCountRequest = serde_json::from_str(&payload).unwrap(); - println!("Request vote count for round {:?}", incoming.round_id); - - let (state, _key) = get_state(incoming.round_id); - incoming.vote_count = state.vote_count; - let out = serde_json::to_string(&incoming).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_start_time_by_round(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let mut incoming: TimestampRequest = serde_json::from_str(&payload).unwrap(); - println!("Request start time for round {:?}", incoming.round_id); - - let (state, _key) = get_state(incoming.round_id); - incoming.timestamp = state.start_time; - let out = serde_json::to_string(&incoming).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_crp_by_round(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let mut incoming: CRPRequest = serde_json::from_str(&payload).unwrap(); - println!("Request crp for round {:?}", incoming.round_id); - - let (state, _key) = get_state(incoming.round_id); - incoming.crp_bytes = state.crp; - let out = serde_json::to_string(&incoming).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_pk_by_round(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let mut incoming: PKRequest = serde_json::from_str(&payload).unwrap(); - - let (state, _key) = get_state(incoming.round_id); - incoming.pk_bytes = state.pk; - let out = serde_json::to_string(&incoming).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - println!("Request for round {:?} public key", incoming.round_id); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_pk_share_count(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - - let mut incoming: PKShareCount = serde_json::from_str(&payload).unwrap(); - - let (state, _key) = get_state(incoming.round_id); - incoming.share_id = state.pk_share_count; - let out = serde_json::to_string(&incoming).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_rounds(_req: &mut Request) -> IronResult { - //let test = _req.headers.get::().unwrap(); - //println!("content_type: {:?}", test); - - // let test3 = _req.headers.get::>().unwrap(); - // println!("auth: {:?}", test3.token); - // let key: Hmac = Hmac::new_from_slice(b"some-secret").unwrap(); - // let claims: BTreeMap = test3.token.verify_with_key(&key).unwrap(); - // println!("decoded hmac {:?}", claims); - - //let test2 = _req.headers.get::(); - //println!("user agent: {:?}", test2); - - let key = "round_count"; - let mut round = GLOBAL_DB.get(key).unwrap(); - if round == None { - println!("initializing first round in db"); - GLOBAL_DB.insert(key, b"0".to_vec()).unwrap(); - round = GLOBAL_DB.get(key).unwrap(); - } - let round_key = std::str::from_utf8(round.unwrap().as_ref()).unwrap().to_string(); - let round_int = round_key.parse::().unwrap(); - - let count = RoundCount {round_count: round_int}; - println!("round_count: {:?}", count.round_count); - - - let out = serde_json::to_string(&count).unwrap(); - println!("get rounds hit"); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -#[tokio::main] -async fn init_crisp_round(req: &mut Request) -> IronResult { - // let auth = _req.headers.get::>().unwrap(); - // if auth.token != env { - - // } - println!("generating round crp"); - - let infura_val = env!("INFURAKEY"); - let mut rpc_url = "https://sepolia.infura.io/v3/".to_string(); - rpc_url.push_str(&infura_val); - - let provider = Provider::::try_from(rpc_url.clone()).unwrap(); - let block_number: U64 = provider.get_block_number().await.unwrap(); - - let degree = 4096; - let plaintext_modulus: u64 = 4096; - let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; - - // Let's generate the BFV parameters structure. - let params = timeit!( - "Parameters generation", - BfvParametersBuilder::new() - .set_degree(degree) - .set_plaintext_modulus(plaintext_modulus) - .set_moduli(&moduli) - .build_arc().unwrap() - ); - let crp = CommonRandomPoly::new(¶ms, &mut thread_rng()).unwrap(); - let crp_bytes = crp.to_bytes(); - - let mut payload = String::new(); - - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - - // we're expecting the POST to match the format of our JsonRequest struct - let incoming: CrispConfig = serde_json::from_str(&payload).unwrap(); - println!("ID: {:?}", incoming.round_id); // TODO: check that client sent the expected next round_id - println!("Address: {:?}", incoming.voting_address); - - // -------------- - let key = "round_count"; - //db.remove(key)?; - let round = GLOBAL_DB.get(key).unwrap(); - if round == None { - println!("initializing first round in db"); - GLOBAL_DB.insert(key, b"0".to_vec()).unwrap(); - } - let round_key = std::str::from_utf8(round.unwrap().as_ref()).unwrap().to_string(); - let mut round_int = round_key.parse::().unwrap(); - round_int = round_int + 1; - let mut inc_round_key = round_int.to_string(); - inc_round_key.push_str("-storage"); - println!("Database key is {:?} and round int is {:?}", inc_round_key, round_int); - - let init_time = Utc::now(); - let timestamp = init_time.timestamp(); - println!("timestamp {:?}", timestamp); - - let (emoji1, emoji2) = generate_emoji(); - - let state = Round { - id: round_int, - status: "Active".to_string(), - poll_length: incoming.poll_length, - voting_address: incoming.voting_address, - chain_id: incoming.chain_id, - ciphernode_count: 0, - pk_share_count: 0, - sks_share_count: 0, - vote_count: 0, - crp: crp_bytes, - pk: vec![0], - start_time: timestamp, - block_start: block_number, - ciphernode_total: incoming.ciphernode_count, - emojis: [emoji1, emoji2], - votes_option_1: 0, - votes_option_2: 0, - ciphernodes: vec![ - Ciphernode { - id: 0, - pk_share: vec![0], - sks_share: vec![0], - } - ], - has_voted: vec!["".to_string()], - }; - - let state_str = serde_json::to_string(&state).unwrap(); - let state_bytes = state_str.into_bytes(); - let key2 = round_int.to_string(); - GLOBAL_DB.insert(inc_round_key, state_bytes).unwrap(); - - let new_round_bytes = key2.into_bytes(); - GLOBAL_DB.insert(key, new_round_bytes).unwrap(); - - // create a response with our random string, and pass in the string from the POST body - let response = JsonResponse { response: "CRISP Initiated".to_string() }; - let out = serde_json::to_string(&response).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - - -async fn aggregate_pk_shares(round_id: u32) -> Result<(), Box> { - println!("aggregating validator keyshare"); - - let degree = 4096; - let plaintext_modulus: u64 = 4096; - let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; - - // Generate a deterministic seed for the Common Poly - //let mut seed = ::Seed::default(); - - // Let's generate the BFV parameters structure. - let params = timeit!( - "Parameters generation", - BfvParametersBuilder::new() - .set_degree(degree) - .set_plaintext_modulus(plaintext_modulus) - .set_moduli(&moduli) - .build_arc()? - ); - - let mut round_key = round_id.to_string(); - round_key.push_str("-storage"); - println!("Database key is {:?}", round_key); - - let state_out = GLOBAL_DB.get(round_key.clone()).unwrap().unwrap(); - let state_out_str = str::from_utf8(&state_out).unwrap(); - let mut state: Round = serde_json::from_str(&state_out_str).unwrap(); - println!("checking db after drop {:?}", state.ciphernode_count); - println!("{:?}", state.ciphernodes[0].id); - //println!("{:?}", state.ciphernodes[0].pk_share); - - //let crp = CommonRandomPoly::new_deterministic(¶ms, seed)?; - let crp = CommonRandomPoly::deserialize(&state.crp, ¶ms)?; - - // Party setup: each party generates a secret key and shares of a collective - // public key. - struct Party { - pk_share: PublicKeyShare, - } - - let mut parties :Vec = Vec::new(); - for i in 1..state.ciphernode_total + 1 { // todo fix init code that causes offset - // read in pk_shares from storage - println!("Aggregating PKShare... id {}", i); - let data_des = PublicKeyShare::deserialize(&state.ciphernodes[i as usize].pk_share, ¶ms, crp.clone()).unwrap(); - // let pk_share = PublicKeyShare::new(&sk_share, crp.clone(), &mut thread_rng())?; - parties.push(Party { pk_share: data_des }); - } - - // Aggregation: this could be one of the parties or a separate entity. Or the - // parties can aggregate cooperatively, in a tree-like fashion. - let pk = timeit!("Public key aggregation", { - let pk: PublicKey = parties.iter().map(|p| p.pk_share.clone()).aggregate()?; - pk - }); - //println!("{:?}", pk); - println!("Multiparty Public Key Generated"); - let store_pk = pk.to_bytes(); - state.pk = store_pk; - let state_str = serde_json::to_string(&state).unwrap(); - let state_bytes = state_str.into_bytes(); - GLOBAL_DB.insert(round_key, state_bytes).unwrap(); - println!("aggregate pk stored for round {:?}", round_id); - Ok(()) -} - -fn handler(_req: &mut Request) -> IronResult { - let response = JsonResponse { response: pick_response() }; - let out = serde_json::to_string(&response).unwrap(); - println!("index handler hit"); - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn health_handler(_req: &mut Request) -> IronResult { - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok))) -} - -// polling endpoint for sks shares - -fn register_sks_share(req: &mut Request) -> IronResult { - let mut payload = String::new(); - - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - - // we're expecting the POST to match the format of our JsonRequest struct - let incoming: SKSShareRequest = serde_json::from_str(&payload).unwrap(); - println!("{:?}", incoming.response); - println!("Index: {:?}", incoming.index); // cipher node id (based on first upload of pk share) - println!("Round ID: {:?}", incoming.round_id); - - - let mut round_key = incoming.round_id.to_string(); - round_key.push_str("-storage"); - println!("Database key is {:?}", round_key); - - let state_out = GLOBAL_DB.get(round_key.clone()).unwrap().unwrap(); - let state_out_str = str::from_utf8(&state_out).unwrap(); - let mut state_out_struct: Round = serde_json::from_str(&state_out_str).unwrap(); - state_out_struct.sks_share_count = state_out_struct.sks_share_count + 1; - - let index = incoming.index; // TODO use hashmap with node id as key - state_out_struct.ciphernodes[index as usize].sks_share = incoming.sks_share; - let state_str = serde_json::to_string(&state_out_struct).unwrap(); - let state_bytes = state_str.into_bytes(); - GLOBAL_DB.insert(round_key, state_bytes).unwrap(); - println!("sks share stored for node index {:?}", incoming.index); - - // toso get share threshold from client config - if state_out_struct.sks_share_count == state_out_struct.ciphernode_total { - println!("All sks shares received"); - //aggregate_pk_shares(incoming.round_id).await; - // TODO: maybe notify cipher nodes - } - - // create a response with our random string, and pass in the string from the POST body - let response = JsonResponse { response: pick_response() }; - let out = serde_json::to_string(&response).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_sks_shares(req: &mut Request) -> IronResult { - let mut payload = String::new(); - - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - - // we're expecting the POST to match the format of our JsonRequest struct - let incoming: SKSSharePoll = serde_json::from_str(&payload).unwrap(); - //const length: usize = incoming.cyphernode_count; - - let (mut state, key) = get_state(incoming.round_id); - - let mut shares = Vec::with_capacity(incoming.ciphernode_count as usize); - - // toso get share threshold from client config - if state.sks_share_count == state.ciphernode_total { - println!("All sks shares received... sending to cipher nodes"); - for i in 1..state.ciphernode_total + 1 { - println!("reading share {:?}", i); - shares.push(state.ciphernodes[i as usize].sks_share.clone()); - } - let response = SKSShareResponse { - response: "final".to_string(), - round_id: incoming.round_id, - sks_shares: shares, - }; - state.status = "Finalized".to_string(); - let state_str = serde_json::to_string(&state).unwrap(); - let state_bytes = state_str.into_bytes(); - GLOBAL_DB.insert(key, state_bytes).unwrap(); - let out = serde_json::to_string(&response).unwrap(); - println!("get rounds hit"); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) - } else { - let response = SKSShareResponse { - response: "waiting".to_string(), - round_id: incoming.round_id, - sks_shares: shares, - }; - let out = serde_json::to_string(&response).unwrap(); - println!("get rounds hit"); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) - } -} - -#[tokio::main] -async fn register_ciphernode(req: &mut Request) -> IronResult { - let mut payload = String::new(); - - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - - // we're expecting the POST to match the format of our JsonRequest struct - let incoming: JsonRequest = serde_json::from_str(&payload).unwrap(); - println!("{:?}", incoming.response); - println!("ID: {:?}", incoming.id); - println!("Round ID: {:?}", incoming.round_id); - - let (mut state, key) = get_state(incoming.round_id); - - state.pk_share_count = state.pk_share_count + 1; - state.ciphernode_count = state.ciphernode_count + 1; - let cnode = Ciphernode { - id: incoming.id, - pk_share: incoming.pk_share, - sks_share: vec![0], - }; - state.ciphernodes.push(cnode); - let state_str = serde_json::to_string(&state).unwrap(); - let state_bytes = state_str.into_bytes(); - GLOBAL_DB.insert(key, state_bytes).unwrap(); - - println!("pk share store for node id {:?}", incoming.id); - println!("ciphernode count {:?}", state.ciphernode_count); - println!("ciphernode total {:?}", state.ciphernode_total); - println!("pk share count {:?}", state.pk_share_count); - - if state.ciphernode_count == state.ciphernode_total { - println!("All shares received"); - let _ = aggregate_pk_shares(incoming.round_id).await; - } - - let response = RegisterNodeResponse { - response: "Node Registered".to_string(), - node_index: state.ciphernode_count, - }; - let out = serde_json::to_string(&response).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn authentication_login(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let incoming: AuthenticationLogin = serde_json::from_str(&payload).unwrap(); - println!("Twitter Login Request"); - - // hmac - let hmac_key: Hmac = Hmac::new_from_slice(b"some-secret").unwrap(); - let mut claims = BTreeMap::new(); - claims.insert("postId", incoming.postId); - let token_str = claims.sign_with_key(&hmac_key).unwrap(); - - // db - let key = "authentication"; - let mut authsdb = GLOBAL_DB.get(key).unwrap(); - let mut response_str = "".to_string(); - let mut jwt_token = "".to_string(); - - if authsdb == None { - println!("initializing first auth in db"); - // hmac - let auth_struct = AuthenticationDB { - jwt_tokens: vec![token_str.clone()], - }; - let authsdb_str = serde_json::to_string(&auth_struct).unwrap(); - let authsdb_bytes = authsdb_str.into_bytes(); - GLOBAL_DB.insert(key, authsdb_bytes).unwrap(); - // set response - response_str = "Authorized".to_string(); - } else { - // look for previous auth - let mut au_db = authsdb.unwrap(); - let authsdb_out_str = str::from_utf8(&au_db).unwrap(); - let mut authsdb_out_struct: AuthenticationDB = serde_json::from_str(&authsdb_out_str).unwrap(); - - for i in 0..authsdb_out_struct.jwt_tokens.len() { - if authsdb_out_struct.jwt_tokens[i as usize] == token_str { - println!("Found previous login."); - response_str = "Already Authorized".to_string(); - } - }; - - if response_str != "Already Authorized" { - println!("Inserting new login to db."); - authsdb_out_struct.jwt_tokens.push(token_str.clone()); - let authsdb_str = serde_json::to_string(&authsdb_out_struct).unwrap(); - let authsdb_bytes = authsdb_str.into_bytes(); - GLOBAL_DB.insert(key, authsdb_bytes).unwrap(); - response_str = "Authorized".to_string(); - } - }; - - let response = AuthenticationResponse { - response: response_str, - jwt_token: token_str, - }; - let out = serde_json::to_string(&response).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} - -#[tokio::main] -async fn main() -> Result<(), Box> { - // Server Code - let mut router = Router::new(); - router.get("/", handler, "index"); - router.get("/health", health_handler, "health"); - router.get("/get_rounds", get_rounds, "get_rounds"); - router.get("/get_web_result_all", get_web_result_all, "get_web_result_all"); - router.post("/get_pk_share_count", get_pk_share_count, "get_pk_share_count"); - router.post("/register_ciphernode", register_ciphernode, "register_ciphernode"); - router.post("/init_crisp_round", init_crisp_round, "init_crisp_round"); - router.post("/get_pk_by_round", get_pk_by_round, "get_pk_by_round"); - router.post("/register_sks_share", register_sks_share, "register_sks_share"); - router.post("/get_sks_shares", get_sks_shares, "get_sks_shares"); - router.post("/get_crp_by_round", get_crp_by_round, "get_crp_by_round"); - router.post("/broadcast_enc_vote", broadcast_enc_vote, "broadcast_enc_vote"); - router.post("/get_vote_count_by_round", get_vote_count_by_round, "get_vote_count_by_round"); - router.post("/get_start_time_by_round", get_start_time_by_round, "get_start_time_by_round"); - router.post("/get_emojis_by_round", get_emojis_by_round, "get_emojis_by_round"); - router.post("/get_poll_length_by_round", get_poll_length_by_round, "get_poll_length_by_round"); - router.post("/get_round_state_lite", get_round_state_lite, "get_round_state_lite"); - router.post("/get_round_state", get_round_state, "get_round_state"); - router.post("/report_tally", report_tally, "report_tally"); - router.post("/get_web_result", get_web_result, "get_web_result"); - router.post("/get_round_state_web", get_round_state_web, "get_round_state_web"); - router.post("/get_node_by_round", get_node_by_round, "get_node_by_round"); - router.post("/get_round_eligibility", get_round_eligibility, "get_round_eligibility"); - router.post("/authentication_login", authentication_login, "authentication_login"); - - let cors_middleware = CorsMiddleware::with_allow_any(); - println!("Allowed origin hosts: *"); - - let mut chain = Chain::new(router); - chain.link_around(cors_middleware); - Iron::new(chain).http("0.0.0.0:4000").unwrap(); - - Ok(()) +fn main() -> Result<(), Box> { + start_server() } diff --git a/packages/server/src/bin/start_rounds.rs b/packages/server/src/bin/start_rounds.rs index b75b260..bd5e1ea 100644 --- a/packages/server/src/bin/start_rounds.rs +++ b/packages/server/src/bin/start_rounds.rs @@ -1,5 +1,4 @@ -mod util; - +use rfv::util::timeit::timeit; use dialoguer::{theme::ColorfulTheme, Input, FuzzySelect}; use std::{thread, time, env}; use serde::{Deserialize, Serialize}; @@ -11,7 +10,6 @@ use fhe::{ }; use fhe_traits::{FheEncoder, FheEncrypter, Serialize as FheSerialize, DeserializeParametrized}; use rand::{thread_rng}; -use util::timeit::{timeit}; use hyper::Request; use hyper::Method; diff --git a/packages/server/src/cli/auth.rs b/packages/server/src/cli/auth.rs new file mode 100644 index 0000000..c17ed8f --- /dev/null +++ b/packages/server/src/cli/auth.rs @@ -0,0 +1,38 @@ +use hyper::{Request, Method}; +use http_body_util::BodyExt; +use serde::{Deserialize, Serialize}; +use crate::cli::HyperClientPost; + +#[derive(Debug, Deserialize, Serialize)] +pub struct AuthenticationLogin { + #[allow(non_snake_case)] + pub postId: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct AuthenticationResponse { + pub response: String, + pub jwt_token: String, +} + +pub async fn authenticate_user(config: &super::CrispConfig, client: &HyperClientPost) -> Result> { + let user = AuthenticationLogin { + postId: config.authentication_id.clone(), + }; + + let out = serde_json::to_string(&user).unwrap(); + let mut url = config.enclave_address.clone(); + url.push_str("/authentication_login"); + let req = Request::builder() + .method(Method::POST) + .uri(url) + .body(out)?; + + let resp = client.request(req).await?; + let body_bytes = resp.collect().await?.to_bytes(); + let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); + let auth_res: AuthenticationResponse = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); + + println!("Authentication response {:?}", auth_res); + Ok(auth_res) +} diff --git a/packages/server/src/cli/mod.rs b/packages/server/src/cli/mod.rs new file mode 100644 index 0000000..11f249f --- /dev/null +++ b/packages/server/src/cli/mod.rs @@ -0,0 +1,90 @@ +mod auth; +mod voting; + +use dialoguer::{theme::ColorfulTheme, FuzzySelect}; +use hyper_tls::HttpsConnector; +use hyper_util::{client::legacy::{Client as HyperClient, connect::HttpConnector}, rt::TokioExecutor}; +use bytes::Bytes; +use std::env; +use std::fs::File; +use std::io::Read; +use http_body_util::Empty; + +use auth::{authenticate_user, AuthenticationResponse}; +use voting::{initialize_crisp_round, participate_in_existing_round}; +use serde::{Deserialize, Serialize}; + +type HyperClientGet = HyperClient, Empty>; +type HyperClientPost = HyperClient, String>; + +#[derive(Debug, Deserialize, Serialize)] +struct CrispConfig { + round_id: u32, + poll_length: u32, + chain_id: u32, + voting_address: String, + ciphernode_count: u32, + enclave_address: String, + authentication_id: String, +} + +#[tokio::main] +pub async fn run_cli() -> Result<(), Box> { + let https = HttpsConnector::new(); + let client_get: HyperClientGet = HyperClient::builder(TokioExecutor::new()).build(https.clone()); + let client: HyperClientPost = HyperClient::builder(TokioExecutor::new()).build(https); + + let mut auth_res = AuthenticationResponse { + response: "".to_string(), + jwt_token: "".to_string(), + }; + + print!("{esc}[2J{esc}[1;1H", esc = 27 as char); + let selections = &[ + "CRISP: Voting Protocol (ETH)", + "More Coming Soon!" + ]; + + let selection_1 = FuzzySelect::with_theme(&ColorfulTheme::default()) + .with_prompt("Enclave (EEEE): Please choose the private execution environment you would like to run!") + .default(0) + .items(&selections[..]) + .interact() + .unwrap(); + + if selection_1 == 0 { + print!("{esc}[2J{esc}[1;1H", esc = 27 as char); + let selections_2 = &[ + "Initialize new CRISP round.", + "Continue Existing CRISP round." + ]; + + let selection_2 = FuzzySelect::with_theme(&ColorfulTheme::default()) + .with_prompt("Create a new CRISP round or participate in an existing round.") + .default(0) + .items(&selections_2[..]) + .interact() + .unwrap(); + + // Read configuration + let path = env::current_dir().unwrap(); + let mut pathst = path.display().to_string(); + pathst.push_str("/example_config.json"); + let mut file = File::open(pathst).unwrap(); + let mut data = String::new(); + file.read_to_string(&mut data).unwrap(); + let config: CrispConfig = serde_json::from_str(&data).expect("JSON was not well-formatted"); + + if selection_2 == 0 { + initialize_crisp_round(&config, &client_get, &client).await?; + } else if selection_2 == 1 { + auth_res = authenticate_user(&config, &client).await?; + participate_in_existing_round(&config, &client, &auth_res).await?; + } + } else { + println!("Check back soon!"); + std::process::exit(1); + } + + Ok(()) +} diff --git a/packages/server/src/cli/voting.rs b/packages/server/src/cli/voting.rs new file mode 100644 index 0000000..2597200 --- /dev/null +++ b/packages/server/src/cli/voting.rs @@ -0,0 +1,211 @@ +use bytes::Bytes; +use dialoguer::{theme::ColorfulTheme, FuzzySelect, Input}; +use http_body_util::BodyExt; +use http_body_util::Empty; +use hyper::{Method, Request}; +use serde::{Deserialize, Serialize}; +use std::{thread, time}; +use tokio::io::{self, AsyncWriteExt as _}; + +use crate::cli::AuthenticationResponse; +use crate::cli::{HyperClientGet, HyperClientPost}; +use crate::util::timeit::timeit; +use fhe::bfv::{BfvParametersBuilder, Encoding, Plaintext, PublicKey}; +use fhe_traits::{DeserializeParametrized, FheEncoder, FheEncrypter, Serialize as FheSerialize}; +use rand::thread_rng; + +#[derive(Debug, Deserialize, Serialize)] +struct JsonRequestGetRounds { + response: String, +} + +#[derive(Debug, Deserialize, Serialize)] +struct RoundCount { + round_count: u32, +} + +#[derive(Debug, Deserialize, Serialize)] +struct PKRequest { + round_id: u32, + pk_bytes: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +struct EncryptedVote { + round_id: u32, + enc_vote_bytes: Vec, + #[allow(non_snake_case)] + postId: String, +} + +#[derive(Debug, Deserialize, Serialize)] +struct JsonResponseTxHash { + response: String, + tx_hash: String, +} + +pub async fn initialize_crisp_round( + config: &super::CrispConfig, + client_get: &HyperClientGet, + client: &HyperClientPost, +) -> Result<(), Box> { + println!("Starting new CRISP round!"); + + println!("Initializing Keyshare nodes..."); + + let response_id = JsonRequestGetRounds { + response: "Test".to_string(), + }; + let _out = serde_json::to_string(&response_id).unwrap(); + let mut url_id = config.enclave_address.clone(); + url_id.push_str("/get_rounds"); + + let req = Request::builder() + .method(Method::GET) + .uri(url_id) + .body(Empty::::new())?; + + let resp = client_get.request(req).await?; + + println!("Response status: {}", resp.status()); + + let body_bytes = resp.collect().await?.to_bytes(); + let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); + let count: RoundCount = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); + println!("Server Round Count: {:?}", count.round_count); + + let round_id = count.round_count + 1; + let response = super::CrispConfig { + round_id: round_id, + poll_length: config.poll_length, + chain_id: config.chain_id, + voting_address: config.voting_address.clone(), + ciphernode_count: config.ciphernode_count, + enclave_address: config.enclave_address.clone(), + authentication_id: config.authentication_id.clone(), + }; + let out = serde_json::to_string(&response).unwrap(); + let mut url = config.enclave_address.clone(); + url.push_str("/init_crisp_round"); + let req = Request::builder() + .header("authorization", "Bearer fpKL54jvWmEGVoRdCNjG") + .method(Method::POST) + .uri(url) + .body(out)?; + + let mut resp = client.request(req).await?; + + println!("Response status: {}", resp.status()); + + while let Some(next) = resp.frame().await { + let frame = next?; + if let Some(chunk) = frame.data_ref() { + tokio::io::stdout().write_all(chunk).await?; + } + } + println!("Round Initialized."); + println!("Gathering Keyshare nodes for execution environment..."); + let three_seconds = time::Duration::from_millis(1000); + thread::sleep(three_seconds); + println!("\nYou can now vote Encrypted with Round ID: {:?}", round_id); + + Ok(()) +} + +pub async fn participate_in_existing_round( + config: &super::CrispConfig, + client: &HyperClientPost, + auth_res: &AuthenticationResponse, +) -> Result<(), Box> { + let input_crisp_id: u32 = Input::with_theme(&ColorfulTheme::default()) + .with_prompt("Enter CRISP round ID.") + .interact_text() + .unwrap(); + println!("Voting state Initialized"); + + // get public encrypt key + let v: Vec = vec![0]; + let response_pk = PKRequest { + round_id: input_crisp_id, + pk_bytes: v, + }; + let out = serde_json::to_string(&response_pk).unwrap(); + let mut url = config.enclave_address.clone(); + url.push_str("/get_pk_by_round"); + let req = Request::builder().method(Method::POST).uri(url).body(out)?; + + let resp = client.request(req).await?; + + println!("Response status: {}", resp.status()); + + let body_bytes = resp.collect().await?.to_bytes(); + let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); + let pk_res: PKRequest = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); + println!( + "Shared Public Key for CRISP round {:?} collected.", + pk_res.round_id + ); + + let degree = 4096; + let plaintext_modulus: u64 = 4096; + let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; + // Let's generate the BFV parameters structure. + let params = timeit!( + "Parameters generation", + BfvParametersBuilder::new() + .set_degree(degree) + .set_plaintext_modulus(plaintext_modulus) + .set_moduli(&moduli) + .build_arc()? + ); + let pk_deserialized = PublicKey::from_bytes(&pk_res.pk_bytes, ¶ms).unwrap(); + + // Select voting option + let selections_3 = &["Abstain.", "Vote yes.", "Vote no."]; + + let selection_3 = FuzzySelect::with_theme(&ColorfulTheme::default()) + .with_prompt("Please select your voting option.") + .default(0) + .items(&selections_3[..]) + .interact() + .unwrap(); + + let mut vote_choice: u64 = 0; + if selection_3 == 0 { + println!("Exiting voting system. You may choose to vote later."); + return Ok(()); + } else if selection_3 == 1 { + vote_choice = 1; + } else if selection_3 == 2 { + vote_choice = 0; + } + println!("Encrypting vote."); + let votes: Vec = [vote_choice].to_vec(); + let pt = Plaintext::try_encode(&[votes[0]], Encoding::poly(), ¶ms)?; + let ct = pk_deserialized.try_encrypt(&pt, &mut thread_rng())?; + println!("Vote encrypted."); + println!("Calling voting contract with encrypted vote."); + + let request_contract = EncryptedVote { + round_id: input_crisp_id, + enc_vote_bytes: ct.to_bytes(), + postId: auth_res.jwt_token.clone(), + }; + let out = serde_json::to_string(&request_contract).unwrap(); + let mut url = config.enclave_address.clone(); + url.push_str("/broadcast_enc_vote"); + let req = Request::builder().method(Method::POST).uri(url).body(out)?; + + let resp = client.request(req).await?; + + println!("Response status: {}", resp.status()); + + let body_bytes = resp.collect().await?.to_bytes(); + let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); + let contract_res: JsonResponseTxHash = + serde_json::from_str(&body_str).expect("JSON was not well-formatted"); + println!("Contract call: {:?}", contract_res.response); + println!("TxHash is {:?}", contract_res.tx_hash); + + Ok(()) +} diff --git a/packages/server/src/enclave_server/database.rs b/packages/server/src/enclave_server/database.rs new file mode 100644 index 0000000..aa7f3ad --- /dev/null +++ b/packages/server/src/enclave_server/database.rs @@ -0,0 +1,66 @@ + +use std::{env, str}; +use once_cell::sync::Lazy; +use sled::Db; +use rand::Rng; +use super::models::Round; + +pub static GLOBAL_DB: Lazy = Lazy::new(|| { + let pathdb = env::current_dir().unwrap(); + let mut pathdbst = pathdb.display().to_string(); + pathdbst.push_str("/database/enclave_server"); + sled::open(pathdbst.clone()).unwrap() +}); + +pub fn get_state(round_id: u32) -> (Round, String) { + let mut round_key = round_id.to_string(); + round_key.push_str("-storage"); + println!("Database key is {:?}", round_key); + let state_out = GLOBAL_DB.get(round_key.clone()).unwrap().unwrap(); + let state_out_str = str::from_utf8(&state_out).unwrap(); + let state_out_struct: Round = serde_json::from_str(&state_out_str).unwrap(); + (state_out_struct, round_key) +} + +pub fn get_round_count() -> u32 { + let round_key = "round_count"; + let round_db = GLOBAL_DB.get(round_key).unwrap(); + if round_db == None { + println!("initializing first round in db"); + GLOBAL_DB.insert(round_key, b"0".to_vec()).unwrap(); + } + let round_str = std::str::from_utf8(round_db.unwrap().as_ref()).unwrap().to_string(); + round_str.parse::().unwrap() +} + + +pub fn generate_emoji() -> (String, String) { + let emojis = [ + "🍇","🍈","🍉","🍊","🍋","🍌","🍍","ðŸĨ­","🍎","🍏", + "🍐","🍑","🍒","🍓","ðŸŦ","ðŸĨ","🍅","ðŸŦ’","ðŸĨĨ","ðŸĨ‘", + "🍆","ðŸĨ”","ðŸĨ•","ðŸŒ―","ðŸŒķïļ","ðŸŦ‘","ðŸĨ’","ðŸĨŽ","ðŸĨĶ","🧄", + "🧅","🍄","ðŸĨœ","ðŸŦ˜","🌰","🍞","ðŸĨ","ðŸĨ–","ðŸŦ“","ðŸĨĻ", + "ðŸĨŊ","ðŸĨž","🧇","🧀","🍖","🍗","ðŸĨĐ","ðŸĨ“","🍔","🍟", + "🍕","🌭","ðŸĨŠ","ðŸŒŪ","ðŸŒŊ","ðŸŦ”","ðŸĨ™","🧆","ðŸĨš","ðŸģ", + "ðŸĨ˜","ðŸē","ðŸŦ•","ðŸĨĢ","ðŸĨ—","ðŸŋ","🧈","🧂","ðŸĨŦ","ðŸą", + "🍘","🍙","🍚","🍛","🍜","🍝","🍠","ðŸĒ","ðŸĢ","ðŸĪ", + "ðŸĨ","ðŸĨŪ","ðŸĄ","ðŸĨŸ","ðŸĨ ","ðŸĨĄ","ðŸĶ€","ðŸĶž","ðŸĶ","ðŸĶ‘", + "ðŸĶŠ","ðŸĶ","🍧","ðŸĻ","ðŸĐ","🍊","🎂","🍰","🧁","ðŸĨ§", + "ðŸŦ","🍎","🍭","ðŸŪ","ðŸŊ","🍞","ðŸĨ›","☕","ðŸĩ","ðŸū", + "🍷","ðŸļ","ðŸđ","🍚","ðŸŧ","ðŸĨ‚","ðŸĨƒ", + ]; + let mut index1 = rand::thread_rng().gen_range(0..emojis.len()); + let index2 = rand::thread_rng().gen_range(0..emojis.len()); + if index1 == index2 { + if index1 == emojis.len() { + index1 = index1 - 1; + } else { + index1 = index1 + 1; + }; + }; + (emojis[index1].to_string(), emojis[index2].to_string()) +} + +pub fn pick_response() -> String { + "Test".to_string() +} \ No newline at end of file diff --git a/packages/server/src/enclave_server/mod.rs b/packages/server/src/enclave_server/mod.rs new file mode 100644 index 0000000..6e6c39c --- /dev/null +++ b/packages/server/src/enclave_server/mod.rs @@ -0,0 +1,23 @@ +mod routes; +mod models; +mod database; + +use iron::prelude::*; +use iron::Chain; +use router::Router; +use iron_cors::CorsMiddleware; + +#[tokio::main] +pub async fn start_server() -> Result<(), Box> { + let mut router = Router::new(); + routes::setup_routes(&mut router); + + let cors_middleware = CorsMiddleware::with_allow_any(); + println!("Allowed origin hosts: *"); + + let mut chain = Chain::new(router); + chain.link_around(cors_middleware); + Iron::new(chain).http("0.0.0.0:4000").unwrap(); + + Ok(()) +} \ No newline at end of file diff --git a/packages/server/src/enclave_server/models.rs b/packages/server/src/enclave_server/models.rs new file mode 100644 index 0000000..8dcec95 --- /dev/null +++ b/packages/server/src/enclave_server/models.rs @@ -0,0 +1,239 @@ +use ethers::types::U64; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize)] +pub struct JsonResponse { + pub response: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct RegisterNodeResponse { + pub response: String, + pub node_index: u32, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct JsonResponseTxHash { + pub response: String, + pub tx_hash: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct JsonRequest { + pub response: String, + pub pk_share: Vec, + pub id: u32, + pub round_id: u32, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct CrispConfig { + pub round_id: u32, + pub poll_length: u32, + pub chain_id: u32, + pub voting_address: String, + pub ciphernode_count: u32, + pub enclave_address: String, + pub authentication_id: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct RoundCount { + pub round_count: u32, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct PKShareCount { + pub round_id: u32, + pub share_id: u32, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct PKRequest { + pub round_id: u32, + pub pk_bytes: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct CRPRequest { + pub round_id: u32, + pub crp_bytes: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct TimestampRequest { + pub round_id: u32, + pub timestamp: i64, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct PollLengthRequest { + pub round_id: u32, + pub poll_length: u32, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct VoteCountRequest { + pub round_id: u32, + pub vote_count: u32, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct SKSShareRequest { + pub response: String, + pub sks_share: Vec, + pub index: u32, + pub round_id: u32, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct EncryptedVote { + pub round_id: u32, + pub enc_vote_bytes: Vec, + #[allow(non_snake_case)] + pub postId: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct GetRoundRequest { + pub round_id: u32, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct GetEmojisRequest { + pub round_id: u32, + pub emojis: [String; 2], +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct SKSSharePoll { + pub response: String, + pub round_id: u32, + pub ciphernode_count: u32, //TODO: dont need this +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct SKSShareResponse { + pub response: String, + pub round_id: u32, + pub sks_shares: Vec>, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct ReportTallyRequest { + pub round_id: u32, + pub option_1: u32, + pub option_2: u32, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct WebResultRequest { + pub round_id: u32, + pub option_1_tally: u32, + pub option_2_tally: u32, + pub total_votes: u32, + pub option_1_emoji: String, + pub option_2_emoji: String, + pub end_time: i64, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct AllWebStates { + pub states: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct StateWeb { + pub id: u32, + pub status: String, + pub poll_length: u32, + pub voting_address: String, + pub chain_id: u32, + pub ciphernode_count: u32, + pub pk_share_count: u32, + pub sks_share_count: u32, + pub vote_count: u32, + pub start_time: i64, + pub ciphernode_total: u32, + pub emojis: [String; 2], +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct StateLite { + pub id: u32, + pub status: String, + pub poll_length: u32, + pub voting_address: String, + pub chain_id: u32, + pub ciphernode_count: u32, + pub pk_share_count: u32, + pub sks_share_count: u32, + pub vote_count: u32, + pub crp: Vec, + pub pk: Vec, + pub start_time: i64, + pub block_start: U64, + pub ciphernode_total: u32, + pub emojis: [String; 2], +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct Round { + pub id: u32, + pub status: String, + pub poll_length: u32, + pub voting_address: String, + pub chain_id: u32, + pub ciphernode_count: u32, + pub pk_share_count: u32, + pub sks_share_count: u32, + pub vote_count: u32, + pub crp: Vec, + pub pk: Vec, + pub start_time: i64, + pub block_start: U64, + pub ciphernode_total: u32, + pub emojis: [String; 2], + pub votes_option_1: u32, + pub votes_option_2: u32, + pub ciphernodes: Vec, + pub has_voted: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct Ciphernode { + pub id: u32, + pub pk_share: Vec, + pub sks_share: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct GetCiphernode { + pub round_id: u32, + pub ciphernode_id: u32, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct GetEligibilityRequest { + pub round_id: u32, + pub node_id: u32, + pub is_eligible: bool, + pub reason: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct AuthenticationDB { + pub jwt_tokens: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct AuthenticationLogin { + #[allow(non_snake_case)] + pub postId: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct AuthenticationResponse { + pub response: String, + pub jwt_token: String, +} diff --git a/packages/server/src/enclave_server/routes/ciphernode.rs b/packages/server/src/enclave_server/routes/ciphernode.rs new file mode 100644 index 0000000..e1b00f2 --- /dev/null +++ b/packages/server/src/enclave_server/routes/ciphernode.rs @@ -0,0 +1,365 @@ +use std::str; +use chrono::Utc; +use fhe::{ + bfv::{BfvParametersBuilder, PublicKey}, + mbfv::{AggregateIter, CommonRandomPoly, PublicKeyShare}, +}; +use fhe_traits::Serialize as FheSerialize; +use crate::util::timeit::timeit; + +use iron::prelude::*; +use iron::status; +use iron::mime::Mime; +use router::Router; +use std::io::Read; + +use crate::enclave_server::models::{Round, Ciphernode, JsonResponse, JsonRequest, RegisterNodeResponse, SKSShareRequest, SKSSharePoll, SKSShareResponse, PKShareCount, PKRequest, GetCiphernode, GetEligibilityRequest, CRPRequest}; +use crate::enclave_server::database::{GLOBAL_DB, get_state, pick_response}; + +pub fn setup_routes(router: &mut Router) { + router.post("/register_ciphernode", register_ciphernode, "register_ciphernode"); + router.post("/get_pk_share_count", get_pk_share_count, "get_pk_share_count"); + router.post("/get_pk_by_round", get_pk_by_round, "get_pk_by_round"); + router.post("/register_sks_share", register_sks_share, "register_sks_share"); + router.post("/get_sks_shares", get_sks_shares, "get_sks_shares"); + router.post("/get_crp_by_round", get_crp_by_round, "get_crp_by_round"); + router.post("/get_node_by_round", get_node_by_round, "get_node_by_round"); + router.post("/get_round_eligibility", get_round_eligibility, "get_round_eligibility"); +} + +#[tokio::main] +async fn register_ciphernode(req: &mut Request) -> IronResult { + let mut payload = String::new(); + + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + + // we're expecting the POST to match the format of our JsonRequest struct + let incoming: JsonRequest = serde_json::from_str(&payload).unwrap(); + println!("{:?}", incoming.response); + println!("ID: {:?}", incoming.id); + println!("Round ID: {:?}", incoming.round_id); + + let (mut state, key) = get_state(incoming.round_id); + + state.pk_share_count = state.pk_share_count + 1; + state.ciphernode_count = state.ciphernode_count + 1; + let cnode = Ciphernode { + id: incoming.id, + pk_share: incoming.pk_share, + sks_share: vec![0], + }; + state.ciphernodes.push(cnode); + let state_str = serde_json::to_string(&state).unwrap(); + let state_bytes = state_str.into_bytes(); + GLOBAL_DB.insert(key, state_bytes).unwrap(); + + println!("pk share store for node id {:?}", incoming.id); + println!("ciphernode count {:?}", state.ciphernode_count); + println!("ciphernode total {:?}", state.ciphernode_total); + println!("pk share count {:?}", state.pk_share_count); + + if state.ciphernode_count == state.ciphernode_total { + println!("All shares received"); + let _ = aggregate_pk_shares(incoming.round_id).await; + } + + let response = RegisterNodeResponse { + response: "Node Registered".to_string(), + node_index: state.ciphernode_count, + }; + let out = serde_json::to_string(&response).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn register_sks_share(req: &mut Request) -> IronResult { + let mut payload = String::new(); + + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + + // we're expecting the POST to match the format of our JsonRequest struct + let incoming: SKSShareRequest = serde_json::from_str(&payload).unwrap(); + println!("{:?}", incoming.response); + println!("Index: {:?}", incoming.index); // cipher node id (based on first upload of pk share) + println!("Round ID: {:?}", incoming.round_id); + + + let mut round_key = incoming.round_id.to_string(); + round_key.push_str("-storage"); + println!("Database key is {:?}", round_key); + + let state_out = GLOBAL_DB.get(round_key.clone()).unwrap().unwrap(); + let state_out_str = str::from_utf8(&state_out).unwrap(); + let mut state_out_struct: Round = serde_json::from_str(&state_out_str).unwrap(); + state_out_struct.sks_share_count = state_out_struct.sks_share_count + 1; + + let index = incoming.index; // TODO use hashmap with node id as key + state_out_struct.ciphernodes[index as usize].sks_share = incoming.sks_share; + let state_str = serde_json::to_string(&state_out_struct).unwrap(); + let state_bytes = state_str.into_bytes(); + GLOBAL_DB.insert(round_key, state_bytes).unwrap(); + println!("sks share stored for node index {:?}", incoming.index); + + // toso get share threshold from client config + if state_out_struct.sks_share_count == state_out_struct.ciphernode_total { + println!("All sks shares received"); + //aggregate_pk_shares(incoming.round_id).await; + // TODO: maybe notify cipher nodes + } + + // create a response with our random string, and pass in the string from the POST body + let response = JsonResponse { response: pick_response() }; + let out = serde_json::to_string(&response).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn get_sks_shares(req: &mut Request) -> IronResult { + let mut payload = String::new(); + + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + + // we're expecting the POST to match the format of our JsonRequest struct + let incoming: SKSSharePoll = serde_json::from_str(&payload).unwrap(); + //const length: usize = incoming.cyphernode_count; + + let (mut state, key) = get_state(incoming.round_id); + + let mut shares = Vec::with_capacity(incoming.ciphernode_count as usize); + + // toso get share threshold from client config + if state.sks_share_count == state.ciphernode_total { + println!("All sks shares received... sending to cipher nodes"); + for i in 1..state.ciphernode_total + 1 { + println!("reading share {:?}", i); + shares.push(state.ciphernodes[i as usize].sks_share.clone()); + } + let response = SKSShareResponse { + response: "final".to_string(), + round_id: incoming.round_id, + sks_shares: shares, + }; + state.status = "Finalized".to_string(); + let state_str = serde_json::to_string(&state).unwrap(); + let state_bytes = state_str.into_bytes(); + GLOBAL_DB.insert(key, state_bytes).unwrap(); + let out = serde_json::to_string(&response).unwrap(); + println!("get rounds hit"); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) + } else { + let response = SKSShareResponse { + response: "waiting".to_string(), + round_id: incoming.round_id, + sks_shares: shares, + }; + let out = serde_json::to_string(&response).unwrap(); + println!("get rounds hit"); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) + } +} + +fn get_crp_by_round(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let mut incoming: CRPRequest = serde_json::from_str(&payload).unwrap(); + println!("Request crp for round {:?}", incoming.round_id); + + let (state, _key) = get_state(incoming.round_id); + incoming.crp_bytes = state.crp; + let out = serde_json::to_string(&incoming).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn get_pk_by_round(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let mut incoming: PKRequest = serde_json::from_str(&payload).unwrap(); + + let (state, _key) = get_state(incoming.round_id); + incoming.pk_bytes = state.pk; + let out = serde_json::to_string(&incoming).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + println!("Request for round {:?} public key", incoming.round_id); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn get_pk_share_count(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + + let mut incoming: PKShareCount = serde_json::from_str(&payload).unwrap(); + + let (state, _key) = get_state(incoming.round_id); + incoming.share_id = state.pk_share_count; + let out = serde_json::to_string(&incoming).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn get_round_eligibility(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let mut incoming: GetEligibilityRequest = serde_json::from_str(&payload).unwrap(); + println!("Request node elegibility for round {:?}", incoming.round_id); + + let (state, _key) = get_state(incoming.round_id); + + for i in 1..state.ciphernodes.len() { + println!("checking ciphernode {:?}", i); + println!("server db id {:?}", state.ciphernodes[i as usize].id); + println!("incoming request id {:?}", incoming.node_id); + if state.ciphernodes[i as usize].id == incoming.node_id { + incoming.is_eligible = true; + incoming.reason = "Previously Registered".to_string(); + }; + }; + + if state.ciphernode_total == state.ciphernode_count && incoming.reason != "Previously Registered" { + incoming.is_eligible = false; + incoming.reason = "Round Full".to_string(); + }; + + if state.ciphernode_total > state.ciphernode_count && incoming.reason != "Previously Registered" { + incoming.is_eligible = true; + incoming.reason = "Open Node Spot".to_string(); + }; + + let init_time = Utc::now(); + let timestamp = init_time.timestamp(); + + if timestamp >= (state.start_time + state.poll_length as i64) { + incoming.is_eligible = false; + incoming.reason = "Waiting For New Round".to_string(); + } + + let out = serde_json::to_string(&incoming).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn get_node_by_round(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let incoming: GetCiphernode = serde_json::from_str(&payload).unwrap(); + println!("Request node data for round {:?}", incoming.round_id); + + let (state, _key) = get_state(incoming.round_id); + let mut cnode = Ciphernode { + id: 0, + pk_share: vec![0], + sks_share: vec![0], + }; + + for i in 0..state.ciphernodes.len() { + if state.ciphernodes[i as usize].id == incoming.ciphernode_id { + cnode.id = state.ciphernodes[i as usize].id; + cnode.pk_share = state.ciphernodes[i as usize].pk_share.clone(); + cnode.sks_share = state.ciphernodes[i as usize].sks_share.clone(); + }; + }; + + if cnode.id != 0 { + let out = serde_json::to_string(&cnode).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) + } else { + let response = JsonResponse { response: "Ciphernode Not Registered".to_string() }; + let out = serde_json::to_string(&response).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) + } + + // let response = JsonResponse { response: "Ciphernode Not Registered".to_string() }; + // let out = serde_json::to_string(&response).unwrap(); + + // let content_type = "application/json".parse::().unwrap(); + // Ok(Response::with((content_type, status::Ok, out))) +} + + +async fn aggregate_pk_shares(round_id: u32) -> Result<(), Box> { + println!("aggregating validator keyshare"); + + let degree = 4096; + let plaintext_modulus: u64 = 4096; + let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; + + // Generate a deterministic seed for the Common Poly + //let mut seed = ::Seed::default(); + + // Let's generate the BFV parameters structure. + let params = timeit!( + "Parameters generation", + BfvParametersBuilder::new() + .set_degree(degree) + .set_plaintext_modulus(plaintext_modulus) + .set_moduli(&moduli) + .build_arc()? + ); + + let mut round_key = round_id.to_string(); + round_key.push_str("-storage"); + println!("Database key is {:?}", round_key); + + let state_out = GLOBAL_DB.get(round_key.clone()).unwrap().unwrap(); + let state_out_str = str::from_utf8(&state_out).unwrap(); + let mut state: Round = serde_json::from_str(&state_out_str).unwrap(); + println!("checking db after drop {:?}", state.ciphernode_count); + println!("{:?}", state.ciphernodes[0].id); + //println!("{:?}", state.ciphernodes[0].pk_share); + + //let crp = CommonRandomPoly::new_deterministic(¶ms, seed)?; + let crp = CommonRandomPoly::deserialize(&state.crp, ¶ms)?; + + // Party setup: each party generates a secret key and shares of a collective + // public key. + struct Party { + pk_share: PublicKeyShare, + } + + let mut parties :Vec = Vec::new(); + for i in 1..state.ciphernode_total + 1 { // todo fix init code that causes offset + // read in pk_shares from storage + println!("Aggregating PKShare... id {}", i); + let data_des = PublicKeyShare::deserialize(&state.ciphernodes[i as usize].pk_share, ¶ms, crp.clone()).unwrap(); + // let pk_share = PublicKeyShare::new(&sk_share, crp.clone(), &mut thread_rng())?; + parties.push(Party { pk_share: data_des }); + } + + // Aggregation: this could be one of the parties or a separate entity. Or the + // parties can aggregate cooperatively, in a tree-like fashion. + let pk = timeit!("Public key aggregation", { + let pk: PublicKey = parties.iter().map(|p| p.pk_share.clone()).aggregate()?; + pk + }); + //println!("{:?}", pk); + println!("Multiparty Public Key Generated"); + let store_pk = pk.to_bytes(); + state.pk = store_pk; + let state_str = serde_json::to_string(&state).unwrap(); + let state_bytes = state_str.into_bytes(); + GLOBAL_DB.insert(round_key, state_bytes).unwrap(); + println!("aggregate pk stored for round {:?}", round_id); + Ok(()) +} diff --git a/packages/server/src/enclave_server/routes/index.rs b/packages/server/src/enclave_server/routes/index.rs new file mode 100644 index 0000000..32cf710 --- /dev/null +++ b/packages/server/src/enclave_server/routes/index.rs @@ -0,0 +1,102 @@ +use std::str; +use iron::mime::Mime; +use iron::prelude::*; +use iron::status; +use router::Router; +use std::io::Read; +use jwt::SignWithKey; +use sha2::Sha256; +use std::collections::BTreeMap; +use hmac::{Hmac, Mac}; + +use crate::enclave_server::models::{JsonResponse, AuthenticationLogin, AuthenticationDB, AuthenticationResponse}; +use crate::enclave_server::database::{GLOBAL_DB, pick_response}; + +pub fn setup_routes(router: &mut Router) { + router.get("/", handler, "index"); + router.get("/health", health_handler, "health"); + router.post( + "/authentication_login", + authentication_login, + "authentication_login", + ); +} + +fn handler(_req: &mut Request) -> IronResult { + let response = JsonResponse { + response: pick_response(), + }; + let out = serde_json::to_string(&response).unwrap(); + println!("index handler hit"); + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn health_handler(_req: &mut Request) -> IronResult { + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok))) +} + +fn authentication_login(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let incoming: AuthenticationLogin = serde_json::from_str(&payload).unwrap(); + println!("Twitter Login Request"); + + // hmac + let hmac_key: Hmac = Hmac::new_from_slice(b"some-secret").unwrap(); + let mut claims = BTreeMap::new(); + claims.insert("postId", incoming.postId); + let token_str = claims.sign_with_key(&hmac_key).unwrap(); + + // db + let key = "authentication"; + let mut authsdb = GLOBAL_DB.get(key).unwrap(); + let mut response_str = "".to_string(); + let mut jwt_token = "".to_string(); + + if authsdb == None { + println!("initializing first auth in db"); + // hmac + let auth_struct = AuthenticationDB { + jwt_tokens: vec![token_str.clone()], + }; + let authsdb_str = serde_json::to_string(&auth_struct).unwrap(); + let authsdb_bytes = authsdb_str.into_bytes(); + GLOBAL_DB.insert(key, authsdb_bytes).unwrap(); + // set response + response_str = "Authorized".to_string(); + } else { + // look for previous auth + let mut au_db = authsdb.unwrap(); + let authsdb_out_str = str::from_utf8(&au_db).unwrap(); + let mut authsdb_out_struct: AuthenticationDB = serde_json::from_str(&authsdb_out_str).unwrap(); + + for i in 0..authsdb_out_struct.jwt_tokens.len() { + if authsdb_out_struct.jwt_tokens[i as usize] == token_str { + println!("Found previous login."); + response_str = "Already Authorized".to_string(); + } + }; + + if response_str != "Already Authorized" { + println!("Inserting new login to db."); + authsdb_out_struct.jwt_tokens.push(token_str.clone()); + let authsdb_str = serde_json::to_string(&authsdb_out_struct).unwrap(); + let authsdb_bytes = authsdb_str.into_bytes(); + GLOBAL_DB.insert(key, authsdb_bytes).unwrap(); + response_str = "Authorized".to_string(); + } + }; + + let response = AuthenticationResponse { + response: response_str, + jwt_token: token_str, + }; + let out = serde_json::to_string(&response).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + diff --git a/packages/server/src/enclave_server/routes/mod.rs b/packages/server/src/enclave_server/routes/mod.rs new file mode 100644 index 0000000..f43213a --- /dev/null +++ b/packages/server/src/enclave_server/routes/mod.rs @@ -0,0 +1,15 @@ +mod index; +mod rounds; +mod ciphernode; +mod voting; +mod state; + +use router::Router; + +pub fn setup_routes(router: &mut Router) { + index::setup_routes(router); + rounds::setup_routes(router); + ciphernode::setup_routes(router); + voting::setup_routes(router); + state::setup_routes(router); +} \ No newline at end of file diff --git a/packages/server/src/enclave_server/routes/rounds.rs b/packages/server/src/enclave_server/routes/rounds.rs new file mode 100644 index 0000000..15b8971 --- /dev/null +++ b/packages/server/src/enclave_server/routes/rounds.rs @@ -0,0 +1,243 @@ +use chrono::Utc; +use fhe::{bfv::BfvParametersBuilder, mbfv::CommonRandomPoly}; +use fhe_traits::Serialize; +use iron::mime::Mime; +use iron::prelude::*; +use iron::status; +use rand::thread_rng; +use router::Router; +use std::env; +use std::io::Read; + +use ethers::{ + providers::{Http, Middleware, Provider}, + types::U64, +}; + +use crate::util::timeit::timeit; + +use crate::enclave_server::database::{generate_emoji, get_state, GLOBAL_DB}; +use crate::enclave_server::models::{ + Ciphernode, CrispConfig, JsonResponse, PollLengthRequest, ReportTallyRequest, Round, + RoundCount, TimestampRequest, +}; + +pub fn setup_routes(router: &mut Router) { + router.get("/get_rounds", get_rounds, "get_rounds"); + router.post("/init_crisp_round", init_crisp_round, "init_crisp_round"); + router.post( + "/get_start_time_by_round", + get_start_time_by_round, + "get_start_time_by_round", + ); + router.post( + "/get_poll_length_by_round", + get_poll_length_by_round, + "get_poll_length_by_round", + ); + router.post("/report_tally", report_tally, "report_tally"); +} + +fn get_rounds(_req: &mut Request) -> IronResult { + //let test = _req.headers.get::().unwrap(); + //println!("content_type: {:?}", test); + + // let test3 = _req.headers.get::>().unwrap(); + // println!("auth: {:?}", test3.token); + // let key: Hmac = Hmac::new_from_slice(b"some-secret").unwrap(); + // let claims: BTreeMap = test3.token.verify_with_key(&key).unwrap(); + // println!("decoded hmac {:?}", claims); + + //let test2 = _req.headers.get::(); + //println!("user agent: {:?}", test2); + + let key = "round_count"; + let mut round = GLOBAL_DB.get(key).unwrap(); + if round == None { + println!("initializing first round in db"); + GLOBAL_DB.insert(key, b"0".to_vec()).unwrap(); + round = GLOBAL_DB.get(key).unwrap(); + } + let round_key = std::str::from_utf8(round.unwrap().as_ref()) + .unwrap() + .to_string(); + let round_int = round_key.parse::().unwrap(); + + let count = RoundCount { + round_count: round_int, + }; + println!("round_count: {:?}", count.round_count); + + let out = serde_json::to_string(&count).unwrap(); + println!("get rounds hit"); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +#[tokio::main] +async fn init_crisp_round(req: &mut Request) -> IronResult { + // let auth = _req.headers.get::>().unwrap(); + // if auth.token != env { + + // } + println!("generating round crp"); + + let infura_val = env!("INFURAKEY"); + let mut rpc_url = "https://sepolia.infura.io/v3/".to_string(); + rpc_url.push_str(&infura_val); + + let provider = Provider::::try_from(rpc_url.clone()).unwrap(); + let block_number: U64 = provider.get_block_number().await.unwrap(); + + let degree = 4096; + let plaintext_modulus: u64 = 4096; + let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; + + // Let's generate the BFV parameters structure. + let params = timeit!( + "Parameters generation", + BfvParametersBuilder::new() + .set_degree(degree) + .set_plaintext_modulus(plaintext_modulus) + .set_moduli(&moduli) + .build_arc() + .unwrap() + ); + let crp = CommonRandomPoly::new(¶ms, &mut thread_rng()).unwrap(); + let crp_bytes = crp.to_bytes(); + + let mut payload = String::new(); + + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + + // we're expecting the POST to match the format of our JsonRequest struct + let incoming: CrispConfig = serde_json::from_str(&payload).unwrap(); + println!("ID: {:?}", incoming.round_id); // TODO: check that client sent the expected next round_id + println!("Address: {:?}", incoming.voting_address); + + // -------------- + let key = "round_count"; + //db.remove(key)?; + let round = GLOBAL_DB.get(key).unwrap(); + if round == None { + println!("initializing first round in db"); + GLOBAL_DB.insert(key, b"0".to_vec()).unwrap(); + } + let round_key = std::str::from_utf8(round.unwrap().as_ref()) + .unwrap() + .to_string(); + let mut round_int = round_key.parse::().unwrap(); + round_int = round_int + 1; + let mut inc_round_key = round_int.to_string(); + inc_round_key.push_str("-storage"); + println!( + "Database key is {:?} and round int is {:?}", + inc_round_key, round_int + ); + + let init_time = Utc::now(); + let timestamp = init_time.timestamp(); + println!("timestamp {:?}", timestamp); + + let (emoji1, emoji2) = generate_emoji(); + + let state = Round { + id: round_int, + status: "Active".to_string(), + poll_length: incoming.poll_length, + voting_address: incoming.voting_address, + chain_id: incoming.chain_id, + ciphernode_count: 0, + pk_share_count: 0, + sks_share_count: 0, + vote_count: 0, + crp: crp_bytes, + pk: vec![0], + start_time: timestamp, + block_start: block_number, + ciphernode_total: incoming.ciphernode_count, + emojis: [emoji1, emoji2], + votes_option_1: 0, + votes_option_2: 0, + ciphernodes: vec![Ciphernode { + id: 0, + pk_share: vec![0], + sks_share: vec![0], + }], + has_voted: vec!["".to_string()], + }; + + let state_str = serde_json::to_string(&state).unwrap(); + let state_bytes = state_str.into_bytes(); + let key2 = round_int.to_string(); + GLOBAL_DB.insert(inc_round_key, state_bytes).unwrap(); + + let new_round_bytes = key2.into_bytes(); + GLOBAL_DB.insert(key, new_round_bytes).unwrap(); + + // create a response with our random string, and pass in the string from the POST body + let response = JsonResponse { + response: "CRISP Initiated".to_string(), + }; + let out = serde_json::to_string(&response).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn get_start_time_by_round(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let mut incoming: TimestampRequest = serde_json::from_str(&payload).unwrap(); + println!("Request start time for round {:?}", incoming.round_id); + + let (state, _key) = get_state(incoming.round_id); + incoming.timestamp = state.start_time; + let out = serde_json::to_string(&incoming).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn get_poll_length_by_round(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let mut incoming: PollLengthRequest = serde_json::from_str(&payload).unwrap(); + println!("Request poll length for round {:?}", incoming.round_id); + + let (state, _key) = get_state(incoming.round_id); + incoming.poll_length = state.poll_length; + let out = serde_json::to_string(&incoming).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn report_tally(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let incoming: ReportTallyRequest = serde_json::from_str(&payload).unwrap(); + println!("Request report tally for round {:?}", incoming.round_id); + + let (mut state, key) = get_state(incoming.round_id); + if state.votes_option_1 == 0 && state.votes_option_2 == 0 { + state.votes_option_1 = incoming.option_1; + state.votes_option_2 = incoming.option_2; + + let state_str = serde_json::to_string(&state).unwrap(); + let state_bytes = state_str.into_bytes(); + GLOBAL_DB.insert(key, state_bytes).unwrap(); + } + let response = JsonResponse { + response: "Tally Reported".to_string(), + }; + let out = serde_json::to_string(&response).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} diff --git a/packages/server/src/enclave_server/routes/state.rs b/packages/server/src/enclave_server/routes/state.rs new file mode 100644 index 0000000..bd49a6c --- /dev/null +++ b/packages/server/src/enclave_server/routes/state.rs @@ -0,0 +1,148 @@ +use iron::prelude::*; +use iron::status; +use iron::mime::Mime; +use router::Router; +use std::io::Read; + + +use crate::enclave_server::models::{GetRoundRequest, WebResultRequest, AllWebStates, StateLite, StateWeb}; +use crate::enclave_server::database::{get_state, get_round_count}; + + +pub fn setup_routes(router: &mut Router) { + router.get("/get_web_result_all", get_web_result_all, "get_web_result_all"); + router.post("/get_round_state_lite", get_round_state_lite, "get_round_state_lite"); + router.post("/get_round_state", get_round_state, "get_round_state"); + router.post("/get_web_result", get_web_result, "get_web_result"); + router.post("/get_round_state_web", get_round_state_web, "get_round_state_web"); +} + +fn get_web_result(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let incoming: GetRoundRequest = serde_json::from_str(&payload).unwrap(); + println!("Request web state for round {:?}", incoming.round_id); + + let (state, _key) = get_state(incoming.round_id); + + let response = WebResultRequest { + round_id: incoming.round_id, + option_1_tally: state.votes_option_1, + option_2_tally: state.votes_option_2, + total_votes: state.votes_option_1 + state.votes_option_2, + option_1_emoji: state.emojis[0].clone(), + option_2_emoji: state.emojis[1].clone(), + end_time: state.start_time + state.poll_length as i64 + }; + + let out = serde_json::to_string(&response).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn get_web_result_all(_req: &mut Request) -> IronResult { + println!("Request all web state."); + + let round_count = get_round_count(); + let mut states: Vec = Vec::with_capacity(round_count as usize); + + for i in 1..round_count { + let (state, _key) = get_state(i); + let web_state = WebResultRequest { + round_id: i, + option_1_tally: state.votes_option_1, + option_2_tally: state.votes_option_2, + total_votes: state.votes_option_1 + state.votes_option_2, + option_1_emoji: state.emojis[0].clone(), + option_2_emoji: state.emojis[1].clone(), + end_time: state.start_time + state.poll_length as i64 + }; + states.push(web_state); + } + + let response = AllWebStates { states: states }; + let out = serde_json::to_string(&response).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + + + +fn get_round_state(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let incoming: GetRoundRequest = serde_json::from_str(&payload).unwrap(); + println!("Request state for round {:?}", incoming.round_id); + + let (state, _key) = get_state(incoming.round_id); + let out = serde_json::to_string(&state).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn get_round_state_web(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let incoming: GetRoundRequest = serde_json::from_str(&payload).unwrap(); + println!("Request state for round {:?}", incoming.round_id); + + let (state, _key) = get_state(incoming.round_id); + let state_lite = StateWeb { + id: state.id, + status: state.status, + poll_length: state.poll_length, + voting_address: state.voting_address, + chain_id: state.chain_id, + ciphernode_count: state.ciphernode_count, + pk_share_count: state.pk_share_count, + sks_share_count: state.sks_share_count, + vote_count: state.vote_count, + start_time: state.start_time, + ciphernode_total: state.ciphernode_total, + emojis: state.emojis, + }; + + let out = serde_json::to_string(&state_lite).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + + +fn get_round_state_lite(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let incoming: GetRoundRequest = serde_json::from_str(&payload).unwrap(); + println!("Request state for round {:?}", incoming.round_id); + + let (state, _key) = get_state(incoming.round_id); + let state_lite = StateLite { + id: state.id, + status: state.status, + poll_length: state.poll_length, + voting_address: state.voting_address, + chain_id: state.chain_id, + ciphernode_count: state.ciphernode_count, + pk_share_count: state.pk_share_count, + sks_share_count: state.sks_share_count, + vote_count: state.vote_count, + crp: state.crp, + pk: state.pk, + start_time: state.start_time, + block_start: state.block_start, + ciphernode_total: state.ciphernode_total, + emojis: state.emojis, + }; + + let out = serde_json::to_string(&state_lite).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} diff --git a/packages/server/src/enclave_server/routes/voting.rs b/packages/server/src/enclave_server/routes/voting.rs new file mode 100644 index 0000000..a92efbf --- /dev/null +++ b/packages/server/src/enclave_server/routes/voting.rs @@ -0,0 +1,144 @@ + +use std::{env, sync::Arc, str}; +use iron::prelude::*; +use iron::status; +use iron::mime::Mime; +use router::Router; +use std::io::Read; +use ethers::{ + prelude::abigen, + providers::{Http, Provider, Middleware}, + middleware::{SignerMiddleware, MiddlewareBuilder}, + signers::{LocalWallet, Signer}, + types::{Address, Bytes, TxHash, BlockNumber}, +}; + + +use crate::enclave_server::models::{EncryptedVote, JsonResponseTxHash, GetEmojisRequest, VoteCountRequest}; +use crate::enclave_server::database::{GLOBAL_DB, get_state}; + + +pub fn setup_routes(router: &mut Router) { + router.post("/broadcast_enc_vote", broadcast_enc_vote, "broadcast_enc_vote"); + router.post("/get_vote_count_by_round", get_vote_count_by_round, "get_vote_count_by_round"); + router.post("/get_emojis_by_round", get_emojis_by_round, "get_emojis_by_round"); +} + +#[tokio::main] +async fn broadcast_enc_vote(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let incoming: EncryptedVote = serde_json::from_str(&payload).unwrap(); + let mut response_str = ""; + let mut converter = "".to_string(); + let (mut state, key) = get_state(incoming.round_id); + + for i in 0..state.has_voted.len() { + if state.has_voted[i] == incoming.postId { + response_str = "User Has Already Voted"; + } else { + response_str = "Vote Successful"; + } + }; + + if response_str == "Vote Successful" { + let sol_vote = Bytes::from(incoming.enc_vote_bytes); + let tx_hash = call_contract(sol_vote, state.voting_address.clone()).await.unwrap(); + converter = "0x".to_string(); + for i in 0..32 { + if tx_hash[i] <= 16 { + converter.push_str("0"); + converter.push_str(&format!("{:x}", tx_hash[i])); + } else { + converter.push_str(&format!("{:x}", tx_hash[i])); + } + } + + state.vote_count = state.vote_count + 1; + state.has_voted.push(incoming.postId); + let state_str = serde_json::to_string(&state).unwrap(); + let state_bytes = state_str.into_bytes(); + GLOBAL_DB.insert(key, state_bytes).unwrap(); + }; + + let response = JsonResponseTxHash { response: response_str.to_string(), tx_hash: converter }; + let out = serde_json::to_string(&response).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + println!("Request for round {:?} send vote tx", incoming.round_id); + Ok(Response::with((content_type, status::Ok, out))) +} + + +fn get_emojis_by_round(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let mut incoming: GetEmojisRequest = serde_json::from_str(&payload).unwrap(); + println!("Request emojis for round {:?}", incoming.round_id); + + let (state, _key) = get_state(incoming.round_id); + incoming.emojis = state.emojis; + let out = serde_json::to_string(&incoming).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +fn get_vote_count_by_round(req: &mut Request) -> IronResult { + let mut payload = String::new(); + // read the POST body + req.body.read_to_string(&mut payload).unwrap(); + let mut incoming: VoteCountRequest = serde_json::from_str(&payload).unwrap(); + println!("Request vote count for round {:?}", incoming.round_id); + + let (state, _key) = get_state(incoming.round_id); + incoming.vote_count = state.vote_count; + let out = serde_json::to_string(&incoming).unwrap(); + + let content_type = "application/json".parse::().unwrap(); + Ok(Response::with((content_type, status::Ok, out))) +} + +async fn call_contract(enc_vote: Bytes, address: String) -> Result> { + println!("calling voting contract"); + + let infura_val = env!("INFURAKEY"); + let mut rpc_url = "https://sepolia.infura.io/v3/".to_string(); + rpc_url.push_str(&infura_val); + + let provider = Provider::::try_from(rpc_url.clone())?; + // let block_number: U64 = provider.get_block_number().await?; + // println!("{block_number}"); + abigen!( + IVOTE, + r#"[ + function voteEncrypted(bytes memory _encVote) public + function getVote(address id) public returns(bytes memory) + event Transfer(address indexed from, address indexed to, uint256 value) + ]"#, + ); + + //const RPC_URL: &str = "https://eth.llamarpc.com"; + let vote_address: &str = &address; + + let eth_val = env!("PRIVATEKEY"); + let wallet: LocalWallet = eth_val + .parse::().unwrap() + .with_chain_id(11155111 as u64); + + let nonce_manager = provider.clone().nonce_manager(wallet.address()); + let curr_nonce = nonce_manager + .get_transaction_count(wallet.address(), Some(BlockNumber::Pending.into())) + .await? + .as_u64(); + + let client = SignerMiddleware::new(provider.clone(), wallet.clone()); + let address: Address = vote_address.parse()?; + let contract = IVOTE::new(address, Arc::new(client.clone())); + + let test = contract.vote_encrypted(enc_vote).nonce(curr_nonce).send().await?.clone(); + println!("{:?}", test); + Ok(test) +} diff --git a/packages/server/src/lib.rs b/packages/server/src/lib.rs new file mode 100644 index 0000000..2fd661a --- /dev/null +++ b/packages/server/src/lib.rs @@ -0,0 +1,3 @@ +pub mod cli; +pub mod util; +pub mod enclave_server; \ No newline at end of file diff --git a/packages/server/src/main.rs b/packages/server/src/main.rs index 622a580..c1e5c88 100644 --- a/packages/server/src/main.rs +++ b/packages/server/src/main.rs @@ -1,4 +1,3 @@ fn main() -> Result<(), ()> { - Ok(()) } diff --git a/packages/server/src/bin/util.rs b/packages/server/src/util.rs similarity index 98% rename from packages/server/src/bin/util.rs rename to packages/server/src/util.rs index 8e3d167..aec57a3 100644 --- a/packages/server/src/bin/util.rs +++ b/packages/server/src/util.rs @@ -28,7 +28,7 @@ pub mod timeit { #[allow(unused_macros)] macro_rules! timeit { ($name:expr, $code:expr) => {{ - use util::DisplayDuration; + use crate::util::DisplayDuration; let start = std::time::Instant::now(); let r = $code; println!("⏱ {}: {}", $name, DisplayDuration(start.elapsed())); From d0159f3103a05bee57a60fe58e57c2ca737c8919 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 28 Aug 2024 16:24:44 +0500 Subject: [PATCH 04/62] merkle tree generation with light-poseidon --- packages/compute_provider/core/Cargo.toml | 9 +++- packages/compute_provider/core/src/lib.rs | 66 ++++++++++++++++++----- packages/compute_provider/host/src/lib.rs | 1 - 3 files changed, 60 insertions(+), 16 deletions(-) diff --git a/packages/compute_provider/core/Cargo.toml b/packages/compute_provider/core/Cargo.toml index 04466f9..611b9f1 100644 --- a/packages/compute_provider/core/Cargo.toml +++ b/packages/compute_provider/core/Cargo.toml @@ -9,4 +9,11 @@ risc0-zkp = { workspace = true } serde = { workspace = true } fhe = { workspace = true } fhe-traits = { workspace = true } -zk-kit-imt = "0.0.5" \ No newline at end of file +zk-kit-imt = "0.0.5" +sha3 = "0.10.8" +num-bigint = "0.4" +num-traits = "0.2" +hex = "0.4" +light-poseidon = "0.2.0" +ark-ff = "0.4.2" +ark-bn254 = "0.4.0" \ No newline at end of file diff --git a/packages/compute_provider/core/src/lib.rs b/packages/compute_provider/core/src/lib.rs index 02b75b5..0bcc838 100644 --- a/packages/compute_provider/core/src/lib.rs +++ b/packages/compute_provider/core/src/lib.rs @@ -1,9 +1,17 @@ +use std::{str::FromStr, sync::Arc}; + +use num_bigint::BigUint; +use num_traits::Num; +use sha3::{Digest, Keccak256}; + +use ark_bn254::Fr; +use ark_ff::{BigInt, BigInteger}; use fhe::bfv::{BfvParameters, Ciphertext}; use fhe_traits::{Deserialize, DeserializeParametrized, Serialize}; -use risc0_zkvm::sha::{Impl, Sha256}; -use std::sync::Arc; +use light_poseidon::{Poseidon, PoseidonHasher}; use zk_kit_imt::imt::IMT; + #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] pub struct TallyResult { pub tallied_ciphertext: Vec, @@ -38,23 +46,53 @@ impl CiphertextInput { } fn compute_merkle_root(&self) -> String { - fn hash_function(nodes: Vec) -> String { - let concatenated = nodes.join(""); - let hash = Impl::hash_bytes(concatenated.as_bytes()); - format!("{:?}", hash) + fn poseidon_hash(nodes: Vec) -> String { + let mut poseidon = Poseidon::::new_circom(2).unwrap(); + let mut field_elements = Vec::new(); + + for node in nodes { + let sanitized_node = node.trim_start_matches("0x"); + let numeric_str = BigUint::from_str_radix(sanitized_node, 16) + .unwrap() + .to_string(); + let field_repr = Fr::from_str(&numeric_str).unwrap(); + field_elements.push(field_repr); + } + + let result_hash: BigInt<4> = poseidon.hash(&field_elements).unwrap().into(); + hex::encode(result_hash.to_bytes_be()) } - let num_leaves = self.ciphertexts.len(); - let arity = 2; - let depth = (num_leaves as f64).log(arity as f64).ceil() as usize; - let zero = format!("{:?}", Impl::hash_bytes(&[0u8])); + const ZERO: &str = "0"; + const DEPTH: usize = 32; + const ARITY: usize = 2; + + let mut tree = IMT::new( + poseidon_hash, + DEPTH, + ZERO.to_string(), + ARITY, + vec![], + ) + .unwrap(); - let mut tree = - IMT::new(hash_function, depth, zero, arity, vec![]).expect("Failed to create IMT"); + let mut poseidon_instance = Poseidon::::new_circom(2).unwrap(); for ciphertext in &self.ciphertexts { - let hash = format!("{:?}", Impl::hash_bytes(ciphertext)); - tree.insert(hash).expect("Failed to insert into IMT"); + let mut keccak_hasher = Keccak256::new(); + keccak_hasher.update(ciphertext); + let hex_output = hex::encode(keccak_hasher.finalize()); + let sanitized_hex = hex_output.trim_start_matches("0x"); + let numeric_value = BigUint::from_str_radix(sanitized_hex, 16) + .unwrap() + .to_string(); + let fr_element = Fr::from_str(&numeric_value).unwrap(); + let zero_element = Fr::from_str("0").unwrap(); + let hash_bigint: BigInt<4> = poseidon_instance + .hash(&[fr_element, zero_element]) + .unwrap().into(); + let data_hash = hex::encode(hash_bigint.to_bytes_be()); + tree.insert(data_hash).unwrap(); } tree.root().expect("Failed to get root from IMT") diff --git a/packages/compute_provider/host/src/lib.rs b/packages/compute_provider/host/src/lib.rs index eaba7d1..8d0c539 100644 --- a/packages/compute_provider/host/src/lib.rs +++ b/packages/compute_provider/host/src/lib.rs @@ -67,7 +67,6 @@ mod tests { let tally = decrypt_result(&result, &sk, ¶ms); assert_eq!(tally, inputs.iter().sum::()); - assert_eq!(result.merkle_root.len(), 72); } fn create_params() -> Arc { From 6f27a3a9e1cd92b9a65e29d6ad268555abb0926e Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 28 Aug 2024 19:27:59 +0500 Subject: [PATCH 05/62] Update tree depth --- packages/compute_provider/core/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compute_provider/core/src/lib.rs b/packages/compute_provider/core/src/lib.rs index 0bcc838..957b772 100644 --- a/packages/compute_provider/core/src/lib.rs +++ b/packages/compute_provider/core/src/lib.rs @@ -64,7 +64,7 @@ impl CiphertextInput { } const ZERO: &str = "0"; - const DEPTH: usize = 32; + const DEPTH: usize = 10; const ARITY: usize = 2; let mut tree = IMT::new( From 32892649ebd66b50fdc5e3c7e11e7e7a5c38692c Mon Sep 17 00:00:00 2001 From: Auryn Macmillan Date: Fri, 30 Aug 2024 16:48:09 -0400 Subject: [PATCH 06/62] add: scaffold out CRISP contracts Split up the contracts into two directories `evm-base` and `risc0`. The idea here is that there are probably common elements between CRISP solidity implementations for various compute providers. I imagine the same is true for the Rust implementation, so perhaps it's worth also treating the current `compute_provider` repo as a base that the implementations for each compute provider can inherit from. The `risc0` directory was generated by `forge init` using the risc0 template, so it also includes templated host and guest code. --- packages/{evm => evm-base}/.gitignore | 0 packages/{evm => evm-base}/LICENSE.md | 0 packages/{evm => evm-base}/README.md | 0 packages/evm-base/contracts/CRISPBase.sol | 37 +++++++++++++ .../interfaces/IComputationModule.sol | 24 +++++++++ .../contracts/interfaces/IEnclave.sol | 10 ++++ .../contracts/interfaces/IInputValidator.sol | 13 +++++ packages/{evm => evm-base}/deploy/deploy.ts | 0 packages/{evm => evm-base}/hardhat.config.ts | 35 +++--------- packages/{evm => evm-base}/package.json | 2 +- .../{evm => evm-base}/test/RFVoting.spec.ts | 0 packages/{evm => evm-base}/tsconfig.json | 0 packages/evm/contracts/RFVoting.sol | 54 ------------------- packages/evm/contracts/zk/Verifier.sol | 25 --------- packages/evm/contracts/zk/ZkIdentity.sol | 40 -------------- 15 files changed, 91 insertions(+), 149 deletions(-) rename packages/{evm => evm-base}/.gitignore (100%) rename packages/{evm => evm-base}/LICENSE.md (100%) rename packages/{evm => evm-base}/README.md (100%) create mode 100644 packages/evm-base/contracts/CRISPBase.sol create mode 100644 packages/evm-base/contracts/interfaces/IComputationModule.sol create mode 100644 packages/evm-base/contracts/interfaces/IEnclave.sol create mode 100644 packages/evm-base/contracts/interfaces/IInputValidator.sol rename packages/{evm => evm-base}/deploy/deploy.ts (100%) rename packages/{evm => evm-base}/hardhat.config.ts (77%) rename packages/{evm => evm-base}/package.json (98%) rename packages/{evm => evm-base}/test/RFVoting.spec.ts (100%) rename packages/{evm => evm-base}/tsconfig.json (100%) delete mode 100644 packages/evm/contracts/RFVoting.sol delete mode 100644 packages/evm/contracts/zk/Verifier.sol delete mode 100644 packages/evm/contracts/zk/ZkIdentity.sol diff --git a/packages/evm/.gitignore b/packages/evm-base/.gitignore similarity index 100% rename from packages/evm/.gitignore rename to packages/evm-base/.gitignore diff --git a/packages/evm/LICENSE.md b/packages/evm-base/LICENSE.md similarity index 100% rename from packages/evm/LICENSE.md rename to packages/evm-base/LICENSE.md diff --git a/packages/evm/README.md b/packages/evm-base/README.md similarity index 100% rename from packages/evm/README.md rename to packages/evm-base/README.md diff --git a/packages/evm-base/contracts/CRISPBase.sol b/packages/evm-base/contracts/CRISPBase.sol new file mode 100644 index 0000000..7a927ae --- /dev/null +++ b/packages/evm-base/contracts/CRISPBase.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.26; + +import {IComputationModule, IInputValidator} from "./interfaces/IComputationModule.sol"; +import {IEnclave} from "./interfaces/IEnclave.sol"; + +struct Params { + uint64 degree; + uint64 plaintextModulus; + uint64[] ciphertextModuli; + uint256 seed; + IInputValidator inputValidator; +} + +abstract contract CRISPBase is IComputationModule { + IEnclave public enclave; + + mapping(uint256 e3Id => Params param) public params; + + error E3AlreadyInitialized(); + error E3DoesNotExist(); + error OnlyEnclave(); + + modifier onlyEnclave() { + require(msg.sender == address(enclave), OnlyEnclave()); + _; + } + + function initialize(IEnclave _enclave) public { + enclave = _enclave; + } + + function getParams(uint256 e3Id) public view returns (Params memory) { + require(params[e3Id].degree != 0, E3DoesNotExist()); + return params[e3Id]; + } +} diff --git a/packages/evm-base/contracts/interfaces/IComputationModule.sol b/packages/evm-base/contracts/interfaces/IComputationModule.sol new file mode 100644 index 0000000..1842fed --- /dev/null +++ b/packages/evm-base/contracts/interfaces/IComputationModule.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.26; + +import {IInputValidator} from "./IInputValidator.sol"; + +interface IComputationModule { + /// @notice This function should be called by the Enclave contract to validate the computation parameters. + /// @param params ABI encoded computation parameters. + /// @return inputValidator The input validator to be used for the computation. + function validate( + uint256 e3Id, + bytes calldata params + ) external returns (IInputValidator inputValidator); + + /// @notice This function should be called by the Enclave contract to verify the decrypted output of an E3. + /// @param e3Id ID of the E3. + /// @param outputData ABI encoded output data to be verified. + /// @return output The output data to be published. + /// @return success Whether the output data is valid. + function verify( + uint256 e3Id, + bytes memory outputData + ) external returns (bytes memory output, bool success); +} diff --git a/packages/evm-base/contracts/interfaces/IEnclave.sol b/packages/evm-base/contracts/interfaces/IEnclave.sol new file mode 100644 index 0000000..5cd0d88 --- /dev/null +++ b/packages/evm-base/contracts/interfaces/IEnclave.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.26; + +interface IEnclave { + /// @notice This function returns root of the input merkle tree for a given E3. + /// @dev This function MUST revert if the E3 does not exist. + /// @param e3Id ID of the E3. + /// @return root The root of the input merkle tree. + function getInputRoot(uint256 e3Id) external view returns (uint256 root); +} diff --git a/packages/evm-base/contracts/interfaces/IInputValidator.sol b/packages/evm-base/contracts/interfaces/IInputValidator.sol new file mode 100644 index 0000000..257af4d --- /dev/null +++ b/packages/evm-base/contracts/interfaces/IInputValidator.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.26; + +interface IInputValidator { + /// @notice This function should be called by the Enclave contract to validate the input parameters. + /// @param params ABI encoded input parameters. + /// @return input The input data to be published. + /// @return success Whether the input parameters are valid. + function validate( + address sender, + bytes memory params + ) external returns (bytes memory input, bool success); +} diff --git a/packages/evm/deploy/deploy.ts b/packages/evm-base/deploy/deploy.ts similarity index 100% rename from packages/evm/deploy/deploy.ts rename to packages/evm-base/deploy/deploy.ts diff --git a/packages/evm/hardhat.config.ts b/packages/evm-base/hardhat.config.ts similarity index 77% rename from packages/evm/hardhat.config.ts rename to packages/evm-base/hardhat.config.ts index 4860953..3fab0fa 100644 --- a/packages/evm/hardhat.config.ts +++ b/packages/evm-base/hardhat.config.ts @@ -2,33 +2,14 @@ import "@nomicfoundation/hardhat-toolbox"; import { config as dotenvConfig } from "dotenv"; import "hardhat-deploy"; import type { HardhatUserConfig } from "hardhat/config"; +import { vars } from "hardhat/config"; import type { NetworkUserConfig } from "hardhat/types"; import { resolve } from "path"; -const dotenvConfigPath: string = process.env.DOTENV_CONFIG_PATH || "./.env"; -dotenvConfig({ path: resolve(__dirname, dotenvConfigPath) }); +// Run 'npx hardhat vars setup' to see the list of variables that need to be set -// Ensure that we have all the environment variables we need. -const mnemonic: string | undefined = process.env.MNEMONIC; -if (!mnemonic) { - throw new Error("Please set your MNEMONIC in a .env file"); -} - -const infuraApiKey: string | undefined = process.env.INFURA_API_KEY; -if (!infuraApiKey) { - throw new Error("Please set your INFURA_API_KEY in a .env file"); -} - -const { PK } = process.env; - -const sharedNetworkConfig: HttpNetworkUserConfig = {}; -if (PK) { - sharedNetworkConfig.accounts = [PK]; -} else { - sharedNetworkConfig.accounts = { - mnemonic: MNEMONIC || DEFAULT_MNEMONIC, - }; -} +const mnemonic: string = vars.get("MNEMONIC"); +const infuraApiKey: string = vars.get("INFURA_API_KEY"); const chainIds = { "arbitrum-mainnet": 42161, @@ -113,11 +94,7 @@ const config: HardhatUserConfig = { "polygon-mainnet": getChainConfig("polygon-mainnet"), "polygon-mumbai": getChainConfig("polygon-mumbai"), sepolia: getChainConfig("sepolia"), - //goerli: getChainConfig("goerli"), - goerli: { - ...sharedNetworkConfig, - url: `https://goerli.infura.io/v3/${infuraApiKey}`, - }, + goerli: getChainConfig("goerli"), }, paths: { artifacts: "./artifacts", @@ -126,7 +103,7 @@ const config: HardhatUserConfig = { tests: "./test", }, solidity: { - version: "0.8.21", + version: "0.8.26", settings: { metadata: { // Not including the metadata hash diff --git a/packages/evm/package.json b/packages/evm-base/package.json similarity index 98% rename from packages/evm/package.json rename to packages/evm-base/package.json index de41149..7dff368 100644 --- a/packages/evm/package.json +++ b/packages/evm-base/package.json @@ -1,5 +1,5 @@ { - "name": "@gnosisguild/GGMI", + "name": "@gnosisguild/crisp", "description": "", "version": "1.0.0", "author": { diff --git a/packages/evm/test/RFVoting.spec.ts b/packages/evm-base/test/RFVoting.spec.ts similarity index 100% rename from packages/evm/test/RFVoting.spec.ts rename to packages/evm-base/test/RFVoting.spec.ts diff --git a/packages/evm/tsconfig.json b/packages/evm-base/tsconfig.json similarity index 100% rename from packages/evm/tsconfig.json rename to packages/evm-base/tsconfig.json diff --git a/packages/evm/contracts/RFVoting.sol b/packages/evm/contracts/RFVoting.sol deleted file mode 100644 index 63a6986..0000000 --- a/packages/evm/contracts/RFVoting.sol +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: LGPLv3 -pragma solidity ^0.8.20; - -contract RFVoting { - - mapping(address voter => bytes vote) public votes; - mapping(address validVoter => bool valid) public isValidVoter; - - string public tester = "test"; - uint256 public id = 0; - uint256 public pollNonce = 0; - - event Voted(address indexed voter, bytes vote); - - function voteEncrypted(bytes memory _encVote) public { - id++; - //votes[msg.sender] = _encVote; - emit Voted(msg.sender, _encVote); - } - - // function getVote(address id) public returns(bytes memory) { - // return votes[id]; - // } - - //Todo gatekeep modular, ie Bright ID extension - function register() public { - // write custom validation code here - isValidVoter[msg.sender] = true; - } - - function createPoll() public { - pollNonce++; - } - - function getPoll(uint256 _pollId) public { - - } - - function submitCoordintatiorPKEY(bytes memory _coordPKEY, uint256 _pollId) public { - - } - - function finalizeVote(uint256 _pollId) public { - - } - - function submitFHEResult(bytes memory _fheResult, uint256 _pollId) public { - - } - - function disputeFHEResult() public { - // reality.eth - } -} diff --git a/packages/evm/contracts/zk/Verifier.sol b/packages/evm/contracts/zk/Verifier.sol deleted file mode 100644 index db09b3a..0000000 --- a/packages/evm/contracts/zk/Verifier.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: LGPLv3 -pragma solidity ^0.8.20; - -contract Verifier { - function verifyProof( - uint[2] memory a, - uint[2][2] memory b, - uint[2] memory c, - uint[4] memory input - ) public view returns (bool r) { - Proof memory proof; - proof.A = Pairing.G1Point(a[0], a[1]); - proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]); - proof.C = Pairing.G1Point(c[0], c[1]); - uint[] memory inputValues = new uint[](input.length); - for(uint i = 0; i < input.length; i++){ - inputValues[i] = input[i]; - } - if (verify(inputValues, proof) == 0) { - return true; - } else { - return false; - } - } -} diff --git a/packages/evm/contracts/zk/ZkIdentity.sol b/packages/evm/contracts/zk/ZkIdentity.sol deleted file mode 100644 index 25e4e04..0000000 --- a/packages/evm/contracts/zk/ZkIdentity.sol +++ /dev/null @@ -1,40 +0,0 @@ -pragma solidity 0.5.11; - -import "./Verifier.sol"; - -contract ZkIdentity is Verifier { - address public owner; - uint256[2][2] public publicKeys; - - constructor() public { - owner = msg.sender; - publicKeys = [ - [ - 11588997684490517626294634429607198421449322964619894214090255452938985192043, - 15263799208273363060537485776371352256460743310329028590780329826273136298011 - ], - [ - 3554016859368109379302439886604355056694273932204896584100714954675075151666, - 17802713187051641282792755605644920157679664448965917618898436110214540390950 - ] - ]; - } - - function isInGroup( - uint256[2] memory a, - uint256[2][2] memory b, - uint256[2] memory c, - uint256[4] memory input // public inputs - ) public view returns (bool) { - if ( - input[0] != publicKeys[0][0] && - input[1] != publicKeys[0][1] && - input[2] != publicKeys[1][0] && - input[3] != publicKeys[1][1] - ) { - revert("Supplied public keys do not match contracts"); - } - - return verifyProof(a, b, c, input); - } -} \ No newline at end of file From 9b74eef91f531c6d66a4bc547fb494c5bbe20bf7 Mon Sep 17 00:00:00 2001 From: Auryn Macmillan Date: Fri, 30 Aug 2024 17:04:14 -0400 Subject: [PATCH 07/62] add: risc0 contracts --- packages/risc0/.github/workflows/linear.yml | 29 + packages/risc0/.github/workflows/main.yml | 130 + packages/risc0/.gitignore | 23 + packages/risc0/.gitmodules | 9 + packages/risc0/.solhint.json | 3 + packages/risc0/.vscode/settings.json | 10 + packages/risc0/Cargo.lock | 4988 +++++++++++++++++ packages/risc0/Cargo.toml | 29 + packages/risc0/README.md | 204 + packages/risc0/apps/Cargo.toml | 17 + packages/risc0/apps/README.md | 46 + packages/risc0/apps/src/bin/publisher.rs | 157 + packages/risc0/contracts/CRISPRisc0.sol | 64 + packages/risc0/contracts/README.md | 26 + packages/risc0/deployment-guide.md | 260 + packages/risc0/foundry.toml | 12 + .../risc0/images/risc0-foundry-template.png | Bin 0 -> 523833 bytes packages/risc0/methods/Cargo.toml | 18 + packages/risc0/methods/README.md | 31 + packages/risc0/methods/build.rs | 46 + packages/risc0/methods/guest/Cargo.lock | 1813 ++++++ packages/risc0/methods/guest/Cargo.toml | 18 + packages/risc0/methods/guest/README.md | 12 + .../risc0/methods/guest/src/bin/is_even.rs | 35 + packages/risc0/methods/src/lib.rs | 53 + packages/risc0/remappings.txt | 4 + packages/risc0/rust-toolchain.toml | 4 + packages/risc0/script/Deploy.s.sol | 134 + packages/risc0/script/config.toml | 16 + 29 files changed, 8191 insertions(+) create mode 100644 packages/risc0/.github/workflows/linear.yml create mode 100644 packages/risc0/.github/workflows/main.yml create mode 100644 packages/risc0/.gitignore create mode 100644 packages/risc0/.gitmodules create mode 100644 packages/risc0/.solhint.json create mode 100644 packages/risc0/.vscode/settings.json create mode 100644 packages/risc0/Cargo.lock create mode 100644 packages/risc0/Cargo.toml create mode 100644 packages/risc0/README.md create mode 100644 packages/risc0/apps/Cargo.toml create mode 100644 packages/risc0/apps/README.md create mode 100644 packages/risc0/apps/src/bin/publisher.rs create mode 100644 packages/risc0/contracts/CRISPRisc0.sol create mode 100644 packages/risc0/contracts/README.md create mode 100644 packages/risc0/deployment-guide.md create mode 100644 packages/risc0/foundry.toml create mode 100644 packages/risc0/images/risc0-foundry-template.png create mode 100644 packages/risc0/methods/Cargo.toml create mode 100644 packages/risc0/methods/README.md create mode 100644 packages/risc0/methods/build.rs create mode 100644 packages/risc0/methods/guest/Cargo.lock create mode 100644 packages/risc0/methods/guest/Cargo.toml create mode 100644 packages/risc0/methods/guest/README.md create mode 100644 packages/risc0/methods/guest/src/bin/is_even.rs create mode 100644 packages/risc0/methods/src/lib.rs create mode 100644 packages/risc0/remappings.txt create mode 100644 packages/risc0/rust-toolchain.toml create mode 100644 packages/risc0/script/Deploy.s.sol create mode 100644 packages/risc0/script/config.toml diff --git a/packages/risc0/.github/workflows/linear.yml b/packages/risc0/.github/workflows/linear.yml new file mode 100644 index 0000000..bae5463 --- /dev/null +++ b/packages/risc0/.github/workflows/linear.yml @@ -0,0 +1,29 @@ +name: Find or Create Linear Issue for PR + +on: + workflow_dispatch: + pull_request: + branches: + - main + types: ["opened", "edited", "reopened", "synchronize"] + +permissions: + pull-requests: write + repository-projects: read + + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: false + +jobs: + create-linear-issue-pr: + runs-on: ubuntu-latest + steps: + - name: Find or create a Linear Issue + uses: risc0/action-find-or-create-linear-issue@risc0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + linear-api-key: ${{ secrets.LINEAR_API_KEY }} + linear-team-key: "WEB3" + linear-created-issue-state-id: "2505ebd6-1fbe-4b25-b2a8-792dfaa50ad9" # in progress diff --git a/packages/risc0/.github/workflows/main.yml b/packages/risc0/.github/workflows/main.yml new file mode 100644 index 0000000..035a44a --- /dev/null +++ b/packages/risc0/.github/workflows/main.yml @@ -0,0 +1,130 @@ +name: main + +on: + push: + branches: [ main ] + pull_request: + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RUST_BACKTRACE: "1" + RISC0_MONOREPO_REF: "release-1.0" + +jobs: + test: + runs-on: ubuntu-latest + steps: + # This is a workaround from: https://github.com/actions/checkout/issues/590#issuecomment-970586842 + - name: checkout dummy commit (submodule bug workaround) + run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :" + + - name: clone repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install rust + uses: risc0/risc0/.github/actions/rustup@main + + - name: Install Foundry + uses: risc0/foundry-toolchain@2fe7e70b520f62368a0e3c464f997df07ede420f + + - name: risczero toolchain install + uses: risc0/risc0-ethereum/.github/actions/cargo-risczero-install@release-1.0 + with: + ref: ${{ env.RISC0_MONOREPO_REF }} + + - name: build rust guest + run: cargo build + + - name: build solidity contracts + run: forge build + + - name: run tests + run: cargo test + + - name: run foundry tests in dev mode + env: + RISC0_DEV_MODE: true + run: forge test -vvv + + integration-test: + name: integration test + runs-on: ubuntu-latest + env: + RUST_BACKTRACE: full + steps: + # This is a workaround from: https://github.com/actions/checkout/issues/590#issuecomment-970586842 + - name: checkout dummy commit (submodule bug workaround) + run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :" + + - name: clone repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install rust + uses: risc0/risc0/.github/actions/rustup@main + + - name: Install Foundry + uses: risc0/foundry-toolchain@2fe7e70b520f62368a0e3c464f997df07ede420f + + - name: risczero toolchain install + uses: risc0/risc0-ethereum/.github/actions/cargo-risczero-install@release-1.0 + with: + ref: ${{ env.RISC0_MONOREPO_REF }} + + - name: build rust guest + run: cargo build + + - name: build solidity contracts + run: forge build + + - name: run foundry tests with local prover + env: + RISC0_DEV_MODE: false + run: forge test -vvv + + lint: + runs-on: ubuntu-latest + steps: + - name: checkout code + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: install rust + uses: risc0/risc0/.github/actions/rustup@main + + - name: install cargo-sort + uses: risc0/cargo-install@v1 + with: + crate: cargo-sort + version: "=1.0.7" + + - name: Install Foundry + uses: risc0/foundry-toolchain@2fe7e70b520f62368a0e3c464f997df07ede420f + + - name: lint rust code + run: cargo fmt --all --check + + - name: lint guest rust code + working-directory: methods/guest + run: cargo fmt --all --check + + - name: lint cargo files + run: cargo sort --workspace --check + + - name: lint guest cargo files + working-directory: methods/guest + run: cargo sort --workspace --check + + - name: check solidity code formatting + run: forge fmt --check diff --git a/packages/risc0/.gitignore b/packages/risc0/.gitignore new file mode 100644 index 0000000..56cbac3 --- /dev/null +++ b/packages/risc0/.gitignore @@ -0,0 +1,23 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +broadcast/ + +# Autogenerated contracts +contracts/ImageID.sol +tests/Elf.sol + +# Libraries +lib/ + +# Dotenv file +.env + +# Cargo +target/ + +# Misc +.DS_Store +.idea \ No newline at end of file diff --git a/packages/risc0/.gitmodules b/packages/risc0/.gitmodules new file mode 100644 index 0000000..2500fc6 --- /dev/null +++ b/packages/risc0/.gitmodules @@ -0,0 +1,9 @@ +[submodule "lib/forge-std"] + path = lib/forge-std + url = https://github.com/foundry-rs/forge-std +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts +[submodule "lib/risc0-ethereum"] + path = lib/risc0-ethereum + url = https://github.com/risc0/risc0-ethereum \ No newline at end of file diff --git a/packages/risc0/.solhint.json b/packages/risc0/.solhint.json new file mode 100644 index 0000000..d7c3de9 --- /dev/null +++ b/packages/risc0/.solhint.json @@ -0,0 +1,3 @@ +{ + "extends": "solhint:default" +} diff --git a/packages/risc0/.vscode/settings.json b/packages/risc0/.vscode/settings.json new file mode 100644 index 0000000..71813c5 --- /dev/null +++ b/packages/risc0/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "rust-analyzer.linkedProjects": [ + "./apps/Cargo.toml", + "./methods/Cargo.toml", + "./methods/guest/Cargo.toml", + ], + "rust-analyzer.check.extraEnv": { + "RISC0_SKIP_BUILD": "1", + } +} \ No newline at end of file diff --git a/packages/risc0/Cargo.lock b/packages/risc0/Cargo.lock new file mode 100644 index 0000000..e780ef7 --- /dev/null +++ b/packages/risc0/Cargo.lock @@ -0,0 +1,4988 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloy-primitives" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "600d34d8de81e23b6d909c094e23b3d357e01ca36b78a8c5424c501eedbe86f0" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "k256", + "keccak-asm", + "proptest", + "rand", + "ruint", + "serde", + "tiny-keccak", +] + +[[package]] +name = "alloy-primitives" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8aa973e647ec336810a9356af8aea787249c9d00b1525359f3db29a68d231b" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "k256", + "keccak-asm", + "proptest", + "rand", + "ruint", + "serde", + "tiny-keccak", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b155716bab55763c95ba212806cf43d05bcc70e5f35b02bad20cf5ec7fe11fed" +dependencies = [ + "arrayvec", + "bytes", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86ec0a47740b20bc5613b8712d0d321d031c4efc58e9645af96085d5cccfc27" +dependencies = [ + "const-hex", + "dunce", + "heck 0.4.1", + "indexmap", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.66", + "syn-solidity 0.6.4", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dbd17d67f3e89478c8a634416358e539e577899666c927bc3d2b1328ee9b6ca" +dependencies = [ + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6da95adcf4760bb4b108fefa51d50096c5e5fdd29ee72fed3e86ee414f2e34" +dependencies = [ + "alloy-sol-macro-input", + "const-hex", + "heck 0.4.1", + "indexmap", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.66", + "syn-solidity 0.7.4", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32c8da04c1343871fb6ce5a489218f9c85323c8340a36e9106b5fc98d4dd59d5" +dependencies = [ + "const-hex", + "dunce", + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.66", + "syn-solidity 0.7.4", +] + +[[package]] +name = "alloy-sol-types" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad09ec5853fa700d12d778ad224dcdec636af424d29fad84fb9a2f16a5b0ef09" +dependencies = [ + "alloy-primitives 0.6.4", + "alloy-sol-macro 0.6.4", + "const-hex", + "serde", +] + +[[package]] +name = "alloy-sol-types" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a64d2d2395c1ac636b62419a7b17ec39031d6b2367e66e9acbf566e6055e9c" +dependencies = [ + "alloy-primitives 0.7.4", + "alloy-sol-macro 0.7.4", + "const-hex", + "serde", +] + +[[package]] +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "apps" +version = "0.1.0" +dependencies = [ + "alloy-primitives 0.6.4", + "alloy-sol-types 0.6.4", + "anyhow", + "clap", + "env_logger", + "ethers", + "log", + "methods", + "risc0-ethereum-contracts", + "risc0-zkvm", + "tokio", +] + +[[package]] +name = "ark-bn254" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" +dependencies = [ + "ark-ec", + "ark-ff 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-crypto-primitives" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3a13b34da09176a8baba701233fdffbaa7c1b1192ce031a3da4e55ce1f1a56" +dependencies = [ + "ark-ec", + "ark-ff 0.4.2", + "ark-relations", + "ark-serialize 0.4.2", + "ark-snark", + "ark-std 0.4.0", + "blake2", + "derivative", + "digest 0.10.7", + "sha2", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff 0.4.2", + "ark-poly", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "hashbrown 0.13.2", + "itertools 0.10.5", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.4.0", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-groth16" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20ceafa83848c3e390f1cbf124bc3193b3e639b3f02009e0e290809a501b95fc" +dependencies = [ + "ark-crypto-primitives", + "ark-ec", + "ark-ff 0.4.2", + "ark-poly", + "ark-relations", + "ark-serialize 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-relations" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00796b6efc05a3f48225e59cb6a2cda78881e7c390872d5786aaf112f31fb4f0" +dependencies = [ + "ark-ff 0.4.2", + "ark-std 0.4.0", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive", + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-snark" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d3cc6833a335bb8a600241889ead68ee89a3cf8448081fb7694c0fe503da63" +dependencies = [ + "ark-ff 0.4.2", + "ark-relations", + "ark-serialize 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + +[[package]] +name = "async-trait" +version = "0.1.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version 0.4.0", +] + +[[package]] +name = "auto_impl" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bonsai-sdk" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7781292e9bcc1f54de6839dbab88b4032d2a20ab1d4fb3d8f045e9cecf5486e" +dependencies = [ + "reqwest 0.12.4", + "risc0-groth16", + "serde", + "thiserror", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "sha2", + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "bytemuck" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +dependencies = [ + "serde", +] + +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "camino" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.23", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cc" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" +dependencies = [ + "jobserver", + "libc", + "once_cell", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "num-traits", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "coins-bip32" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b6be4a5df2098cd811f3194f64ddb96c267606bffd9689ac7b0160097b01ad3" +dependencies = [ + "bs58", + "coins-core", + "digest 0.10.7", + "hmac", + "k256", + "serde", + "sha2", + "thiserror", +] + +[[package]] +name = "coins-bip39" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8fba409ce3dc04f7d804074039eb68b960b0829161f8e06c95fea3f122528" +dependencies = [ + "bitvec", + "coins-bip32", + "hmac", + "once_cell", + "pbkdf2 0.12.2", + "rand", + "sha2", + "thiserror", +] + +[[package]] +name = "coins-core" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5286a0843c21f8367f7be734f89df9b822e0321d8bcce8d6e735aadff7d74979" +dependencies = [ + "base64 0.21.7", + "bech32", + "bs58", + "digest 0.10.7", + "generic-array", + "hex", + "ripemd", + "serde", + "serde_derive", + "sha2", + "sha3", + "thiserror", +] + +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + +[[package]] +name = "const-hex" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8a24a26d37e1ffd45343323dc9fe6654ceea44c12f2fcb3d7ac29e610bc6" +dependencies = [ + "cfg-if", + "cpufeatures", + "hex", + "proptest", + "serde", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version 0.4.0", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "docker-generate" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf673e0848ef09fa4aeeba78e681cf651c0c7d35f76ee38cec8e55bc32fa111" + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "either" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" + +[[package]] +name = "elf" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enr" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a3d8dc56e02f954cac8eb489772c552c473346fc34f67412bb6244fd647f7e4" +dependencies = [ + "base64 0.21.7", + "bytes", + "hex", + "k256", + "log", + "rand", + "rlp", + "serde", + "sha3", + "zeroize", +] + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "eth-keystore" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" +dependencies = [ + "aes", + "ctr", + "digest 0.10.7", + "hex", + "hmac", + "pbkdf2 0.11.0", + "rand", + "scrypt", + "serde", + "serde_json", + "sha2", + "sha3", + "thiserror", + "uuid", +] + +[[package]] +name = "ethabi" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "scale-info", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "primitive-types", + "scale-info", + "uint", +] + +[[package]] +name = "ethers" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "816841ea989f0c69e459af1cf23a6b0033b19a55424a1ea3a30099becdb8dec0" +dependencies = [ + "ethers-addressbook", + "ethers-contract", + "ethers-core", + "ethers-etherscan", + "ethers-middleware", + "ethers-providers", + "ethers-signers", + "ethers-solc", +] + +[[package]] +name = "ethers-addressbook" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5495afd16b4faa556c3bba1f21b98b4983e53c1755022377051a975c3b021759" +dependencies = [ + "ethers-core", + "once_cell", + "serde", + "serde_json", +] + +[[package]] +name = "ethers-contract" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fceafa3578c836eeb874af87abacfb041f92b4da0a78a5edd042564b8ecdaaa" +dependencies = [ + "const-hex", + "ethers-contract-abigen", + "ethers-contract-derive", + "ethers-core", + "ethers-providers", + "futures-util", + "once_cell", + "pin-project", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "ethers-contract-abigen" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04ba01fbc2331a38c429eb95d4a570166781f14290ef9fdb144278a90b5a739b" +dependencies = [ + "Inflector", + "const-hex", + "dunce", + "ethers-core", + "ethers-etherscan", + "eyre", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "reqwest 0.11.27", + "serde", + "serde_json", + "syn 2.0.66", + "toml", + "walkdir", +] + +[[package]] +name = "ethers-contract-derive" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87689dcabc0051cde10caaade298f9e9093d65f6125c14575db3fd8c669a168f" +dependencies = [ + "Inflector", + "const-hex", + "ethers-contract-abigen", + "ethers-core", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.66", +] + +[[package]] +name = "ethers-core" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d80cc6ad30b14a48ab786523af33b37f28a8623fc06afd55324816ef18fb1f" +dependencies = [ + "arrayvec", + "bytes", + "cargo_metadata", + "chrono", + "const-hex", + "elliptic-curve", + "ethabi", + "generic-array", + "k256", + "num_enum", + "once_cell", + "open-fastrlp", + "rand", + "rlp", + "serde", + "serde_json", + "strum", + "syn 2.0.66", + "tempfile", + "thiserror", + "tiny-keccak", + "unicode-xid", +] + +[[package]] +name = "ethers-etherscan" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79e5973c26d4baf0ce55520bd732314328cabe53193286671b47144145b9649" +dependencies = [ + "chrono", + "ethers-core", + "reqwest 0.11.27", + "semver 1.0.23", + "serde", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "ethers-middleware" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f9fdf09aec667c099909d91908d5eaf9be1bd0e2500ba4172c1d28bfaa43de" +dependencies = [ + "async-trait", + "auto_impl", + "ethers-contract", + "ethers-core", + "ethers-etherscan", + "ethers-providers", + "ethers-signers", + "futures-channel", + "futures-locks", + "futures-util", + "instant", + "reqwest 0.11.27", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "tracing-futures", + "url", +] + +[[package]] +name = "ethers-providers" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6434c9a33891f1effc9c75472e12666db2fa5a0fec4b29af6221680a6fe83ab2" +dependencies = [ + "async-trait", + "auto_impl", + "base64 0.21.7", + "bytes", + "const-hex", + "enr", + "ethers-core", + "futures-channel", + "futures-core", + "futures-timer", + "futures-util", + "hashers", + "http 0.2.12", + "instant", + "jsonwebtoken", + "once_cell", + "pin-project", + "reqwest 0.11.27", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-tungstenite", + "tracing", + "tracing-futures", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "ws_stream_wasm", +] + +[[package]] +name = "ethers-signers" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228875491c782ad851773b652dd8ecac62cda8571d3bc32a5853644dd26766c2" +dependencies = [ + "async-trait", + "coins-bip32", + "coins-bip39", + "const-hex", + "elliptic-curve", + "eth-keystore", + "ethers-core", + "rand", + "sha2", + "thiserror", + "tracing", +] + +[[package]] +name = "ethers-solc" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66244a771d9163282646dbeffe0e6eca4dda4146b6498644e678ac6089b11edd" +dependencies = [ + "cfg-if", + "const-hex", + "dirs", + "dunce", + "ethers-core", + "glob", + "home", + "md-5", + "num_cpus", + "once_cell", + "path-slash", + "rayon", + "regex", + "semver 1.0.23", + "serde", + "serde_json", + "solang-parser", + "svm-rs", + "thiserror", + "tiny-keccak", + "tokio", + "tracing", + "walkdir", + "yansi", +] + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-locks" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ec6fe3675af967e67c5536c0b9d44e34e6c52f86bedc4ea49c5317b8e94d06" +dependencies = [ + "futures-channel", + "futures-task", +] + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" +dependencies = [ + "gloo-timers", + "send_wrapper 0.4.0", +] + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashers" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2bca93b15ea5a746f220e56587f71e73c6165eab783df9e26590069953e3c30" +dependencies = [ + "fxhash", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +dependencies = [ + "bytes", + "futures-core", + "http 1.1.0", + "http-body 1.0.0", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.28", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.3.1", + "hyper-util", + "rustls 0.22.4", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.25.0", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "hyper 1.3.1", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken" +version = "8.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" +dependencies = [ + "base64 0.21.7", + "pem", + "ring 0.16.20", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "k256" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-asm" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47a3633291834c4fbebf8673acbc1b04ec9d151418ff9b8e26dcd79129928758" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + +[[package]] +name = "lalrpop" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools 0.11.0", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.5.0", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest 0.10.7", +] + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "methods" +version = "0.1.0" +dependencies = [ + "alloy-primitives 0.6.4", + "alloy-sol-types 0.6.4", + "hex", + "risc0-build", + "risc0-build-ethereum", + "risc0-zkp", + "risc0-zkvm", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "num-bigint" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "object" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "open-fastrlp" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786393f80485445794f6043fd3138854dd109cc6c4bd1a6383db304c9ce9b9ce" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", + "ethereum-types", + "open-fastrlp-derive", +] + +[[package]] +name = "open-fastrlp-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "003b2be5c6c53c1cfeb0a238b8a1c3915cd410feb684457a36c10038f764bb1c" +dependencies = [ + "bytes", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "parity-scale-codec" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.5", +] + +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "path-slash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.7", + "hmac", + "password-hash", + "sha2", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "hmac", +] + +[[package]] +name = "pem" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +dependencies = [ + "base64 0.13.1", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pharos" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" +dependencies = [ + "futures", + "rustc_version 0.4.0", +] + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared 0.11.2", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared 0.11.2", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared 0.11.2", + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +dependencies = [ + "proc-macro2", + "syn 2.0.66", +] + +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "scale-info", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit 0.21.1", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.5.0", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags 2.5.0", +] + +[[package]] +name = "redox_users" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.28", + "hyper-rustls 0.24.2", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-rustls 0.24.1", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.25.4", + "winreg 0.50.0", +] + +[[package]] +name = "reqwest" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.3.1", + "hyper-rustls 0.26.0", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.22.4", + "rustls-pemfile 2.1.2", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls 0.25.0", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.26.1", + "winreg 0.52.0", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "risc0-binfmt" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a33ca13e8e2fe08fc283accbb08fcbabbfdd27acf88dddc9b39654d0e487b15" +dependencies = [ + "anyhow", + "elf", + "risc0-zkp", + "risc0-zkvm-platform", + "serde", + "tracing", +] + +[[package]] +name = "risc0-build" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b68a9c8dbae8e14ec00e0cc4deca152a4f7d815aa5f745b24a9469ff08c8495d" +dependencies = [ + "anyhow", + "cargo_metadata", + "dirs", + "docker-generate", + "risc0-binfmt", + "risc0-zkp", + "risc0-zkvm-platform", + "serde", + "serde_json", + "tempfile", +] + +[[package]] +name = "risc0-build-ethereum" +version = "1.0.0" +source = "git+https://github.com/risc0/risc0-ethereum?tag=v1.0.0#5fbbc7cb44ab37ce438c14c087ba6c4e0a669900" +dependencies = [ + "anyhow", + "hex", + "risc0-build", + "risc0-zkp", +] + +[[package]] +name = "risc0-circuit-recursion" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c12ea07079420272e5705baea6a0756b21c0dadeca7ed34a7866eb9c073b9a0" +dependencies = [ + "anyhow", + "bytemuck", + "hex", + "risc0-core", + "risc0-zkp", + "tracing", +] + +[[package]] +name = "risc0-circuit-rv32im" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef57b3afe8e59bec6f535c49c99dc7cd3fda7e93254fd499e5469ec17fec1d0" +dependencies = [ + "anyhow", + "risc0-binfmt", + "risc0-core", + "risc0-zkp", + "risc0-zkvm-platform", + "serde", + "tracing", +] + +[[package]] +name = "risc0-core" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43b7bd8b9adb8bed7eaecfa5c152b6c676c4512aea1120d2cdc5fbbca4b2ffb" +dependencies = [ + "bytemuck", + "rand_core", +] + +[[package]] +name = "risc0-ethereum-contracts" +version = "1.0.0" +source = "git+https://github.com/risc0/risc0-ethereum?tag=v1.0.0#5fbbc7cb44ab37ce438c14c087ba6c4e0a669900" +dependencies = [ + "alloy-sol-types 0.7.4", + "anyhow", + "ethers", + "risc0-zkvm", +] + +[[package]] +name = "risc0-groth16" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e275963cd541e1bc9b94f36e23e85b87798a96e04fdf7b013500c08b949a8c9" +dependencies = [ + "anyhow", + "ark-bn254", + "ark-ec", + "ark-groth16", + "ark-serialize 0.4.2", + "bytemuck", + "hex", + "num-bigint", + "num-traits", + "risc0-binfmt", + "risc0-zkp", + "serde", +] + +[[package]] +name = "risc0-zkp" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53342780aef2d31ccc0526e6d4b151dab69e678d2e1495b2270ed40f5e1df6f4" +dependencies = [ + "anyhow", + "blake2", + "bytemuck", + "cfg-if", + "digest 0.10.7", + "hex", + "hex-literal", + "paste", + "rand_core", + "risc0-core", + "risc0-zkvm-platform", + "serde", + "sha2", + "tracing", +] + +[[package]] +name = "risc0-zkvm" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "774b03337fa1675204a067b3f15be740dbedde63fa46647017140fd023805afb" +dependencies = [ + "anyhow", + "bincode", + "bonsai-sdk", + "bytemuck", + "bytes", + "cfg-if", + "getrandom", + "hex", + "prost", + "risc0-binfmt", + "risc0-circuit-recursion", + "risc0-circuit-rv32im", + "risc0-core", + "risc0-groth16", + "risc0-zkp", + "risc0-zkvm-platform", + "rrs-lib", + "semver 1.0.23", + "serde", + "sha2", + "tempfile", + "tracing", +] + +[[package]] +name = "risc0-zkvm-platform" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b8df83bfa425e078ef77ed115f5eff26b5ebc4a584253b31e4e7122fa2bcced" +dependencies = [ + "bytemuck", + "getrandom", + "libm", +] + +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rlp-derive", + "rustc-hex", +] + +[[package]] +name = "rlp-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rrs-lib" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4382d3af3a4ebdae7f64ba6edd9114fff92c89808004c4943b393377a25d001" +dependencies = [ + "downcast-rs", + "paste", +] + +[[package]] +name = "ruint" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f308135fef9fc398342da5472ce7c484529df23743fb7c734e0f3d472971e62" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "bytes", + "fastrlp", + "num-bigint", + "num-traits", + "parity-scale-codec", + "primitive-types", + "proptest", + "rand", + "rlp", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86854cf50259291520509879a5c294c3c9a4c334e9ff65071c51e42ef1e2343" + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.23", +] + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring 0.17.8", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring 0.17.8", + "rustls-pki-types", + "rustls-webpki 0.102.4", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring 0.17.8", + "untrusted 0.9.0", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +dependencies = [ + "ring 0.17.8", + "rustls-pki-types", + "untrusted 0.9.0", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scale-info" +version = "2.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" +dependencies = [ + "cfg-if", + "derive_more", + "parity-scale-codec", + "scale-info-derive", +] + +[[package]] +name = "scale-info-derive" +version = "2.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scrypt" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d" +dependencies = [ + "hmac", + "pbkdf2 0.11.0", + "salsa20", + "sha2", +] + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring 0.17.8", + "untrusted 0.9.0", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + +[[package]] +name = "send_wrapper" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + +[[package]] +name = "serde" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "serde_json" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "sha3-asm" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9b57fd861253bff08bb1919e995f90ba8f4889de2726091c8876f3a4e823b40" +dependencies = [ + "cc", + "cfg-if", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core", +] + +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "solang-parser" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c425ce1c59f4b154717592f0bdf4715c3a1d55058883622d3157e1f0908a5b26" +dependencies = [ + "itertools 0.11.0", + "lalrpop", + "lalrpop-util", + "phf", + "thiserror", + "unicode-xid", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared 0.10.0", + "precomputed-hash", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.66", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "svm-rs" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11297baafe5fa0c99d5722458eac6a5e25c01eb1b8e5cd137f54079093daa7a4" +dependencies = [ + "dirs", + "fs2", + "hex", + "once_cell", + "reqwest 0.11.27", + "semver 1.0.23", + "serde", + "serde_json", + "sha2", + "thiserror", + "url", + "zip", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-solidity" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3d0961cd53c23ea94eeec56ba940f636f6394788976e9f16ca5ee0aca7464a" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "syn-solidity" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8db114c44cf843a8bacd37a146e37987a0b823a0e8bc4fdc610c9c72ab397a5" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" +dependencies = [ + "futures-util", + "log", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", + "tungstenite", + "webpki-roots 0.25.4", +] + +[[package]] +name = "tokio-util" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.13", +] + +[[package]] +name = "toml_datetime" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.6.9", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "tracing-core", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 0.2.12", + "httparse", + "log", + "rand", + "rustls 0.21.12", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.66", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "webpki-roots" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86c949fede1d13936a99f14fafd3e76fd642b556dd2ce96287fbe2e0151bfac6" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "ws_stream_wasm" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5" +dependencies = [ + "async_io_stream", + "futures", + "js-sys", + "log", + "pharos", + "rustc_version 0.4.0", + "send_wrapper 0.6.0", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "zerocopy" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "aes", + "byteorder", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac", + "pbkdf2 0.11.0", + "sha1", + "time", + "zstd", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.10+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/packages/risc0/Cargo.toml b/packages/risc0/Cargo.toml new file mode 100644 index 0000000..1fd0f20 --- /dev/null +++ b/packages/risc0/Cargo.toml @@ -0,0 +1,29 @@ +[workspace] +resolver = "2" +members = ["apps", "methods"] +exclude = ["lib"] + +[workspace.package] +version = "0.1.0" +edition = "2021" + +[workspace.dependencies] +alloy-primitives = { version = "0.6", default-features = false, features = ["rlp", "serde", "std"] } +alloy-sol-types = { version = "0.6" } +anyhow = { version = "1.0.75" } +bincode = { version = "1.3" } +bytemuck = { version = "1.14" } +ethers = { version = "2.0" } +hex = { version = "0.4" } +log = { version = "0.4" } +methods = { path = "./methods" } +risc0-build = { version = "1.0", features = ["docker"] } +risc0-build-ethereum = { git = "https://github.com/risc0/risc0-ethereum", tag = "v1.0.0" } +risc0-ethereum-contracts = { git = "https://github.com/risc0/risc0-ethereum", tag = "v1.0.0" } +risc0-zkvm = { version = "1.0", default-features = false } +risc0-zkp = { version = "1.0", default-features = false } +serde = { version = "1.0", features = ["derive", "std"] } + +[profile.release] +debug = 1 +lto = true diff --git a/packages/risc0/README.md b/packages/risc0/README.md new file mode 100644 index 0000000..2dc6cb2 --- /dev/null +++ b/packages/risc0/README.md @@ -0,0 +1,204 @@ +# RISC Zero Foundry Template + +> Prove computation with the [RISC Zero zkVM] and verify the results in your Ethereum contract. + +This repository implements an example application on Ethereum utilizing RISC Zero as a [coprocessor] to the smart contract application. +It provides a starting point for building powerful new applications on Ethereum that offload work that is computationally intensive (i.e. gas expensive), or difficult to implement in Solidity (e.g. ed25519 signature verification, or HTML parsing). + + +Integrate with [Steel][steel-repo] to execute view calls and simulate transactions on Ethereum. Check out the [ERC-20 counter][erc20-counter] demo to see an example. + +## Overview + +Here is a simplified overview of how devs can integrate RISC Zero, with [Bonsai] proving, into their Ethereum smart contracts: + +![RISC Zero Foundry Template Diagram](images/risc0-foundry-template.png) + +1. Run your application logic in the [RISC Zero zkVM]. The provided [publisher] app sends an off-chain proof request to the [Bonsai] proving service. +2. [Bonsai] generates the program result, written to the [journal], and a SNARK proof of its correctness. +3. The [publisher] app submits this proof and journal on-chain to your app contract for validation. +4. Your app contract calls the [RISC Zero Verifier] to validate the proof. If the verification is successful, the journal is deemed trustworthy and can be safely used. + +## Dependencies + +First, [install Rust] and [Foundry], and then restart your terminal. + +```sh +# Install Rust +curl https://sh.rustup.rs -sSf | sh +# Install Foundry +curl -L https://foundry.paradigm.xyz | bash +``` + +Next, you will use `rzup` to install `cargo-risczero`. + +To install `rzup`, run the following command and follow the instructions: + +```sh +curl -L https://risczero.com/install | bash +``` + +Next we can install the RISC Zero toolchain by running `rzup`: + +```sh +rzup +``` + +You can verify the installation was successful by running: + +```sh +cargo risczero --version +``` + +Now you have all the tools you need to develop and deploy an application with [RISC Zero]. + +## Quick Start + +First, install the RISC Zero toolchain using the [instructions above](#dependencies). + +Now, you can initialize a new RISC Zero project at a location of your choosing: + +```sh +forge init -t risc0/bonsai-foundry-template ./my-project +``` + +Congratulations! You've just started your first RISC Zero project. + +Your new project consists of: + +- a [zkVM program] (written in Rust), which specifies a computation that will be proven; +- a [app contract] (written in Solidity), which uses the proven results; +- a [publisher] which makes proving requests to [Bonsai] and posts the proof to Ethereum. + We provide an example implementation, but your dApp interface or application servers could act as the publisher. + +### Build the Code + +- Builds for zkVM program, the publisher app, and any other Rust code. + + ```sh + cargo build + ``` + +- Build your Solidity smart contracts + + > NOTE: `cargo build` needs to run first to generate the `ImageID.sol` contract. + + ```sh + forge build + ``` + +### Run the Tests + +- Tests your zkVM program. + + ```sh + cargo test + ``` + +- Test your Solidity contracts, integrated with your zkVM program. + + ```sh + RISC0_DEV_MODE=true forge test -vvv + ``` + +- Run the same tests, with the full zkVM prover rather than dev-mode, by setting `RISC0_DEV_MODE=false`. + + ```sh + RISC0_DEV_MODE=false forge test -vvv + ``` + + Producing the [Groth16 SNARK proofs][Groth16] for this test requires running on an x86 machine with [Docker] installed, or using [Bonsai](#configuring-bonsai). Apple silicon is currently unsupported for local proving, you can find out more info in the relevant issues [here](https://github.com/risc0/risc0/issues/1520) and [here](https://github.com/risc0/risc0/issues/1749). + +## Develop Your Application + +To build your application using the RISC Zero Foundry Template, you’ll need to make changes in three main areas: + +- ***Guest Code***: Write the code you want proven in the [methods/guest](./methods/guest/) folder. This code runs off-chain within the RISC Zero zkVM and performs the actual computations. For example, the provided template includes a computation to check if a given number is even and generate a proof of this computation. +- ***Smart Contracts***: Write the on-chain part of your project in the [contracts](./contracts/) folder. The smart contract verifies zkVM proofs and updates the blockchain state based on the results of off-chain computations. For instance, in the [EvenNumber](./contracts/EvenNumber.sol) example, the smart contract verifies a proof that a number is even and stores that number on-chain if the proof is valid. +- ***Publisher Application***: Adjust the publisher example in the [apps](./apps/) folder. The publisher application bridges off-chain computation with on-chain verification by submitting proof requests, receiving proofs, and publishing them to the smart contract on Ethereum. + +### Configuring Bonsai + +***Note:*** *To request an API key [complete the form here](https://bonsai.xyz/apply).* + +With the Bonsai proving service, you can produce a [Groth16 SNARK proof][Groth16] that is verifiable on-chain. +You can get started by setting the following environment variables with your API key and associated URL. + +```bash +export BONSAI_API_KEY="YOUR_API_KEY" # see form linked above +export BONSAI_API_URL="BONSAI_URL" # provided with your api key +``` + +Now if you run `forge test` with `RISC0_DEV_MODE=false`, the test will run as before, but will additionally use the fully verifying `RiscZeroGroth16Verifier` contract instead of `MockRiscZeroVerifier` and will request a SNARK receipt from Bonsai. + +```bash +RISC0_DEV_MODE=false forge test -vvv +``` + +### Deterministic Builds + +By setting the environment variable `RISC0_USE_DOCKER` a containerized build process via Docker will ensure that all builds of your guest code, regardless of the machine or local environment, will produce the same [image ID][image-id]. +The [image ID][image-id], and its importance to security, is explained in more detail in our [developer FAQ]. + +```bash +RISC0_USE_DOCKER=1 cargo build +``` + +> ***Note:*** *This requires having Docker installed and in your PATH. To install Docker see [Get Docker][Docker].* + +## Deploy Your Application + +When you're ready, follow the [deployment guide] to get your application running on [Sepolia] or Ethereum Mainnet. + +## Project Structure + +Below are the primary files in the project directory + +```text +. +├── Cargo.toml // Configuration for Cargo and Rust +├── foundry.toml // Configuration for Foundry +├── apps +│ ├── Cargo.toml +│ └── src +│ └── lib.rs // Utility functions +│ └── bin +│ └── publisher.rs // Example app to publish program results into your app contract +├── contracts +│ ├── EvenNumber.sol // Basic example contract for you to modify +│ └── ImageID.sol // Generated contract with the image ID for your zkVM program +├── methods +│ ├── Cargo.toml +│ ├── guest +│ │ ├── Cargo.toml +│ │ └── src +│ │ └── bin // You can add additional guest programs to this folder +│ │ └── is_even.rs // Example guest program for checking if a number is even +│ └── src +│ └── lib.rs // Compiled image IDs and tests for your guest programs +└── tests + ├── EvenNumber.t.sol // Tests for the basic example contract + └── Elf.sol // Generated contract with paths the guest program ELF files. +``` + +[Bonsai]: https://dev.bonsai.xyz/ +[Foundry]: https://getfoundry.sh/ +[Docker]: https://docs.docker.com/get-docker/ +[Groth16]: https://www.risczero.com/news/on-chain-verification +[RISC Zero Verifier]: https://github.com/risc0/risc0/blob/release-0.21/bonsai/ethereum/contracts/IRiscZeroVerifier.sol +[RISC Zero installation]: https://dev.risczero.com/api/zkvm/install +[RISC Zero zkVM]: https://dev.risczero.com/zkvm +[RISC Zero]: https://www.risczero.com/ +[Sepolia]: https://www.alchemy.com/overviews/sepolia-testnet +[app contract]: ./contracts/ +[cargo-binstall]: https://github.com/cargo-bins/cargo-binstall#cargo-binaryinstall +[coprocessor]: https://www.risczero.com/news/a-guide-to-zk-coprocessors-for-scalability +[deployment guide]: /deployment-guide.md +[developer FAQ]: https://dev.risczero.com/faq#zkvm-application-design +[image-id]: https://dev.risczero.com/terminology#image-id +[install Rust]: https://doc.rust-lang.org/cargo/getting-started/installation.html +[journal]: https://dev.risczero.com/terminology#journal +[publisher]: ./apps/README.md +[zkVM program]: ./methods/guest/ +[steel-repo]: https://github.com/risc0/risc0-ethereum/tree/main/steel +[erc20-counter]: https://github.com/risc0/risc0-ethereum/tree/main/examples/erc20-counter diff --git a/packages/risc0/apps/Cargo.toml b/packages/risc0/apps/Cargo.toml new file mode 100644 index 0000000..fa913fe --- /dev/null +++ b/packages/risc0/apps/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "apps" +version = { workspace = true } +edition = { workspace = true } + +[dependencies] +alloy-primitives = { workspace = true } +alloy-sol-types = { workspace = true } +anyhow = { workspace = true } +clap = { version = "4.0", features = ["derive", "env"] } +env_logger = { version = "0.10" } +ethers = { workspace = true } +log = { workspace = true } +methods = { workspace = true } +risc0-ethereum-contracts = { workspace = true } +risc0-zkvm = { workspace = true, features = ["client"] } +tokio = { version = "1.35", features = ["full"] } diff --git a/packages/risc0/apps/README.md b/packages/risc0/apps/README.md new file mode 100644 index 0000000..46799b0 --- /dev/null +++ b/packages/risc0/apps/README.md @@ -0,0 +1,46 @@ +# Apps + +In typical applications, an off-chain app is needed to do two main actions: + +* Produce a proof e.g. by sending a proof request to [Bonsai]. +* Send a transaction to Ethereum to execute your on-chain logic. + +This template provides the `publisher` CLI as an example application to execute these steps. +In a production application, a back-end server or your dApp client may take on this role. + +## Publisher + +The [`publisher` CLI][publisher], is an example application that sends an off-chain proof request to the [Bonsai] proving service, and publishes the received proofs to your deployed app contract. + +### Usage + +Run the `publisher` with: + +```sh +cargo run --bin publisher +``` + +```text +$ cargo run --bin publisher -- --help + +Usage: publisher --chain-id --eth-wallet-private-key --rpc-url --contract --input + +Options: + --chain-id + Ethereum chain ID + --eth-wallet-private-key + Ethereum Node endpoint [env: ETH_WALLET_PRIVATE_KEY=] + --rpc-url + Ethereum Node endpoint + --contract + Application's contract address on Ethereum + -i, --input + The input to provide to the guest binary + -h, --help + Print help + -V, --version + Print version +``` + +[publisher]: ./src/bin/publisher.rs +[Bonsai]: https://dev.bonsai.xyz/ diff --git a/packages/risc0/apps/src/bin/publisher.rs b/packages/risc0/apps/src/bin/publisher.rs new file mode 100644 index 0000000..4724071 --- /dev/null +++ b/packages/risc0/apps/src/bin/publisher.rs @@ -0,0 +1,157 @@ +// Copyright 2024 RISC Zero, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This application demonstrates how to send an off-chain proof request +// to the Bonsai proving service and publish the received proofs directly +// to your deployed app contract. + +use alloy_primitives::U256; +use alloy_sol_types::{sol, SolInterface, SolValue}; +use anyhow::{Context, Result}; +use clap::Parser; +use ethers::prelude::*; +use methods::IS_EVEN_ELF; +use risc0_ethereum_contracts::groth16; +use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts, VerifierContext}; + +// `IEvenNumber` interface automatically generated via the alloy `sol!` macro. +sol! { + interface IEvenNumber { + function set(uint256 x, bytes calldata seal); + } +} + +/// Wrapper of a `SignerMiddleware` client to send transactions to the given +/// contract's `Address`. +pub struct TxSender { + chain_id: u64, + client: SignerMiddleware, Wallet>, + contract: Address, +} + +impl TxSender { + /// Creates a new `TxSender`. + pub fn new(chain_id: u64, rpc_url: &str, private_key: &str, contract: &str) -> Result { + let provider = Provider::::try_from(rpc_url)?; + let wallet: LocalWallet = private_key.parse::()?.with_chain_id(chain_id); + let client = SignerMiddleware::new(provider.clone(), wallet.clone()); + let contract = contract.parse::
()?; + + Ok(TxSender { + chain_id, + client, + contract, + }) + } + + /// Send a transaction with the given calldata. + pub async fn send(&self, calldata: Vec) -> Result> { + let tx = TransactionRequest::new() + .chain_id(self.chain_id) + .to(self.contract) + .from(self.client.address()) + .data(calldata); + + log::info!("Transaction request: {:?}", &tx); + + let tx = self.client.send_transaction(tx, None).await?.await?; + + log::info!("Transaction receipt: {:?}", &tx); + + Ok(tx) + } +} + +/// Arguments of the publisher CLI. +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Args { + /// Ethereum chain ID + #[clap(long)] + chain_id: u64, + + /// Ethereum Node endpoint. + #[clap(long, env)] + eth_wallet_private_key: String, + + /// Ethereum Node endpoint. + #[clap(long)] + rpc_url: String, + + /// Application's contract address on Ethereum + #[clap(long)] + contract: String, + + /// The input to provide to the guest binary + #[clap(short, long)] + input: U256, +} + +fn main() -> Result<()> { + env_logger::init(); + // Parse CLI Arguments: The application starts by parsing command-line arguments provided by the user. + let args = Args::parse(); + + // Create a new transaction sender using the parsed arguments. + let tx_sender = TxSender::new( + args.chain_id, + &args.rpc_url, + &args.eth_wallet_private_key, + &args.contract, + )?; + + // ABI encode input: Before sending the proof request to the Bonsai proving service, + // the input number is ABI-encoded to match the format expected by the guest code running in the zkVM. + let input = args.input.abi_encode(); + + let env = ExecutorEnv::builder().write_slice(&input).build()?; + + let receipt = default_prover() + .prove_with_ctx( + env, + &VerifierContext::default(), + IS_EVEN_ELF, + &ProverOpts::groth16(), + )? + .receipt; + + // Encode the seal with the selector. + let seal = groth16::encode(receipt.inner.groth16()?.seal.clone())?; + + // Extract the journal from the receipt. + let journal = receipt.journal.bytes.clone(); + + // Decode Journal: Upon receiving the proof, the application decodes the journal to extract + // the verified number. This ensures that the number being submitted to the blockchain matches + // the number that was verified off-chain. + let x = U256::abi_decode(&journal, true).context("decoding journal data")?; + + // Construct function call: Using the IEvenNumber interface, the application constructs + // the ABI-encoded function call for the set function of the EvenNumber contract. + // This call includes the verified number, the post-state digest, and the seal (proof). + let calldata = IEvenNumber::IEvenNumberCalls::set(IEvenNumber::setCall { + x, + seal: seal.into(), + }) + .abi_encode(); + + // Initialize the async runtime environment to handle the transaction sending. + let runtime = tokio::runtime::Runtime::new()?; + + // Send transaction: Finally, the TxSender component sends the transaction to the Ethereum blockchain, + // effectively calling the set function of the EvenNumber contract with the verified number and proof. + runtime.block_on(tx_sender.send(calldata))?; + + Ok(()) +} diff --git a/packages/risc0/contracts/CRISPRisc0.sol b/packages/risc0/contracts/CRISPRisc0.sol new file mode 100644 index 0000000..33da0f2 --- /dev/null +++ b/packages/risc0/contracts/CRISPRisc0.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.26; + +import {CRISPBase, IComputationModule, IInputValidator, IEnclave} from "evm-base/contracts/CRISPBase.sol"; +import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; +import {ImageID} from "./ImageID.sol"; + +struct Params { + uint64 degree; + uint64 plaintextModulus; + uint64[] ciphertextModuli; + uint256 seed; + IInputValidator inputValidator; +} + +contract CRISPRisc0 is CRISPBase { + /// @notice RISC Zero verifier contract address. + IRiscZeroVerifier public verifier; + /// @notice Image ID of the only zkVM binary to accept verification from. + bytes32 public constant imageId = ImageID.IS_EVEN_ID; // TODO: update this to the CRISP image ID + + /// @notice Initialize the contract, binding it to a specified RISC Zero verifier. + constructor(IEnclave _enclave, IRiscZeroVerifier _verifier) { + initialize(_enclave, _verifier); + } + + function initialize(IEnclave _enclave, IRiscZeroVerifier _verifier) public { + CRISPBase.initialize(_enclave); + verifier = _verifier; + } + + function validate( + uint256 e3Id, + bytes memory data + ) external override returns (IInputValidator) { + require(params[e3Id].degree == 0, E3AlreadyInitialized()); + Params memory _params = abi.decode(data, (Params)); + // TODO: require that params are valid + + params[e3Id].degree = _params.degree; + params[e3Id].plaintextModulus = _params.plaintextModulus; + params[e3Id].ciphertextModuli = _params.ciphertextModuli; + params[e3Id].seed = _params.seed; + params[e3Id].inputValidator = _params.inputValidator; + + return _params.inputValidator; + } + + function verify( + uint256 e3Id, + bytes memory data + ) external view override returns (bytes memory, bool) { + require(msg.sender == address(enclave), OnlyEnclave()); + require(params[e3Id].degree != 0, E3DoesNotExist()); + uint256 inputRoot = enclave.getInputRoot(e3Id); + (bytes memory seal, bytes memory output) = abi.decode( + data, + (bytes, bytes) + ); + bytes memory journal = abi.encode(inputRoot, output); + verifier.verify(seal, imageId, sha256(journal)); + return (output, true); + } +} diff --git a/packages/risc0/contracts/README.md b/packages/risc0/contracts/README.md new file mode 100644 index 0000000..4ad00ff --- /dev/null +++ b/packages/risc0/contracts/README.md @@ -0,0 +1,26 @@ +# Solidity Contracts + +This directory contains the Solidity contracts for an application with [RISC Zero] on Ethereum. +The example contract included within the template is [`EvenNumber.sol`](./EvenNumber.sol). +It holds a number, guaranteed to be even. + +The Solidity libraries for RISC Zero can be found at [github.com/risc0/risc0-ethereum]. + +Contracts are built and tested with [forge], which is part of the [Foundry] toolkit. +Tests are defined in the `tests` directory in the root of this template. + +## Generated Contracts + +As part of the build process, this template generates the `ImageID.sol` and `Elf.sol` contracts. +Running `cargo build` will generate these contracts with up to date references to your guest code. + +- `ImageID.sol`: contains the [Image IDs][image-id] for the guests implemented in the [methods] directory. +- `Elf.sol`: contains the path of the guest binaries implemented in the [methods] directory. + This contract is saved in the `tests` directory in the root of this template. + +[Foundry]: https://getfoundry.sh/ +[RISC Zero]: https://risczero.com +[forge]: https://github.com/foundry-rs/foundry#forge +[github.com/risc0/risc0-ethereum]: https://github.com/risc0/risc0-ethereum/tree/main/contracts +[image-id]: https://dev.risczero.com/terminology#image-id +[methods]: ../methods/README.md diff --git a/packages/risc0/deployment-guide.md b/packages/risc0/deployment-guide.md new file mode 100644 index 0000000..09da976 --- /dev/null +++ b/packages/risc0/deployment-guide.md @@ -0,0 +1,260 @@ +# RISC Zero Ethereum Deployment Guide + +Welcome to the [RISC Zero] Ethereum Deployment guide! + +Once you've written your [contracts] and your [methods], and [tested] your program, you're ready to deploy your contract. + +You can either: + +- [Deploy your project to a local network] +- [Deploy to a testnet] +- [Deploy to Ethereum Mainnet] + +## Deploy your project on a local network + +You can deploy your contracts and run an end-to-end test or demo as follows: + +1. Start a local testnet with `anvil` by running: + + ```bash + anvil + ``` + + Once anvil is started, keep it running in the terminal, and switch to a new terminal. + +2. Set your environment variables: + > ***Note:*** *This requires having access to a Bonsai API Key. To request an API key [complete the form here](https://bonsai.xyz/apply).* + > Alternatively you can generate your proofs locally, assuming you have a machine with an x86 architecture and [Docker] installed. In this case do not export Bonsai related env variables. + + ```bash + # Anvil sets up a number of default wallets, and this private key is one of them. + export ETH_WALLET_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + export BONSAI_API_KEY="YOUR_API_KEY" # see form linked in the previous section + export BONSAI_API_URL="BONSAI_API_URL" # provided with your api key + ``` + +3. Build your project: + + ```bash + cargo build + ``` + +4. Deploy your contract by running: + + ```bash + forge script --rpc-url http://localhost:8545 --broadcast script/Deploy.s.sol + ``` + + This command should output something similar to: + + ```bash + ... + == Logs == + You are deploying on ChainID 31337 + Deployed RiscZeroGroth16Verifier to 0x5FbDB2315678afecb367f032d93F642f64180aa3 + Deployed EvenNumber to 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 + ... + ``` + + Save the `EvenNumber` contract address to an env variable: + + ```bash + export EVEN_NUMBER_ADDRESS=#COPY EVEN NUMBER ADDRESS FROM DEPLOY LOGS + ``` + + > You can also use the following command to set the contract address if you have [`jq`][jq] installed: + > + > ```bash + > export EVEN_NUMBER_ADDRESS=$(jq -re '.transactions[] | select(.contractName == "EvenNumber") | .contractAddress' ./broadcast/Deploy.s.sol/31337/run-latest.json) + > ``` + +### Interact with your local deployment + +1. Query the state: + + ```bash + cast call --rpc-url http://localhost:8545 ${EVEN_NUMBER_ADDRESS:?} 'get()(uint256)' + ``` + +2. Publish a new state + + ```bash + cargo run --bin publisher -- \ + --chain-id=31337 \ + --rpc-url=http://localhost:8545 \ + --contract=${EVEN_NUMBER_ADDRESS:?} \ + --input=12345678 + ``` + +3. Query the state again to see the change: + + ```bash + cast call --rpc-url http://localhost:8545 ${EVEN_NUMBER_ADDRESS:?} 'get()(uint256)' + ``` + +## Deploy your project on Sepolia testnet + +You can deploy your contracts on the `Sepolia` testnet and run an end-to-end test or demo as follows: + +1. Get access to Bonsai and an Ethereum node running on Sepolia testnet (in this example, we will be using [Alchemy](https://www.alchemy.com/) as our Ethereum node provider) and export the following environment variables: + > ***Note:*** *This requires having access to a Bonsai API Key. To request an API key [complete the form here](https://bonsai.xyz/apply).* + > Alternatively you can generate your proofs locally, assuming you have a machine with an x86 architecture and [Docker] installed. In this case do not export Bonsai related env variables. + + ```bash + export BONSAI_API_KEY="YOUR_API_KEY" # see form linked in the previous section + export BONSAI_API_URL="BONSAI_API_URL" # provided with your api key + export ALCHEMY_API_KEY="YOUR_ALCHEMY_API_KEY" # the API_KEY provided with an alchemy account + export ETH_WALLET_PRIVATE_KEY="YOUR_WALLET_PRIVATE_KEY" # the private hex-encoded key of your Sepolia testnet wallet + ``` + +2. Build your project: + + ```bash + cargo build + ``` + +3. Deploy your contract by running: + + ```bash + forge script script/Deploy.s.sol --rpc-url https://eth-sepolia.g.alchemy.com/v2/${ALCHEMY_API_KEY:?} --broadcast + ``` + + This command uses the `sepolia` profile defined in the [config][config] file, and should output something similar to: + + ```bash + ... + == Logs == + You are deploying on ChainID 11155111 + Deploying using config profile: sepolia + Using IRiscZeroVerifier contract deployed at 0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187 + Deployed EvenNumber to 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 + ... + ``` + + Save the `EvenNumber` contract address to an env variable: + + ```bash + export EVEN_NUMBER_ADDRESS=#COPY EVEN NUMBER ADDRESS FROM DEPLOY LOGS + ``` + + > You can also use the following command to set the contract address if you have [`jq`][jq] installed: + > + > ```bash + > export EVEN_NUMBER_ADDRESS=$(jq -re '.transactions[] | select(.contractName == "EvenNumber") | .contractAddress' ./broadcast/Deploy.s.sol/11155111/run-latest.json) + > ``` + +### Interact with your Sepolia testnet deployment + +1. Query the state: + + ```bash + cast call --rpc-url https://eth-sepolia.g.alchemy.com/v2/${ALCHEMY_API_KEY:?} ${EVEN_NUMBER_ADDRESS:?} 'get()(uint256)' + ``` + +2. Publish a new state + + ```bash + cargo run --bin publisher -- \ + --chain-id=11155111 \ + --rpc-url=https://eth-sepolia.g.alchemy.com/v2/${ALCHEMY_API_KEY:?} \ + --contract=${EVEN_NUMBER_ADDRESS:?} \ + --input=12345678 + ``` + +3. Query the state again to see the change: + + ```bash + cast call --rpc-url https://eth-sepolia.g.alchemy.com/v2/${ALCHEMY_API_KEY:?} ${EVEN_NUMBER_ADDRESS:?} 'get()(uint256)' + ``` + +## Deploy your project on Ethereum mainnet + +You can deploy your contract on Ethereum Mainnet as follows: + +1. Get access to Bonsai and an Ethereum node running on Mainnet (in this example, we will be using [Alchemy](https://www.alchemy.com/) as our Ethereum node provider) and export the following environment variables: + > ***Note:*** *This requires having access to a Bonsai API Key. To request an API key [complete the form here](https://bonsai.xyz/apply).* + > Alternatively you can generate your proofs locally, assuming you have a machine with an x86 architecture and [Docker] installed. In this case do not export Bonsai related env variables. + + ```bash + export BONSAI_API_KEY="YOUR_API_KEY" # see form linked in the previous section + export BONSAI_API_URL="BONSAI_API_URL" # provided with your api key + export ALCHEMY_API_KEY="YOUR_ALCHEMY_API_KEY" # the API_KEY provided with an alchemy account + export ETH_WALLET_ADDRESS="YOUR_WALLET_ADDRESS" # the account address you want to use for deployment + ``` + +2. Build your project: + + ```bash + cargo build + ``` + +3. Deploy your contract by running: + + You'll need to pass options to forge script to connect to your deployer wallet. See the [Foundry documentation][forge-script-wallet-docs]. + The example command below configures Forge to use a Ledger hardware wallet. + + ```bash + forge script script/Deploy.s.sol --rpc-url https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY:?} --broadcast --ledger + ``` + + This command uses the `mainnet` profile defined in the [config][config] file, and should output something similar to: + + ```bash + ... + == Logs == + You are deploying on ChainID 1 + Deploying using config profile: mainnet + Using IRiscZeroVerifier contract deployed at 0x8EaB2D97Dfce405A1692a21b3ff3A172d593D319 + Deployed EvenNumber to 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 + ... + ``` + + Save the `EvenNumber` contract address to an env variable: + + ```bash + export EVEN_NUMBER_ADDRESS=#COPY EVEN NUMBER ADDRESS FROM DEPLOY LOGS + ``` + + > You can also use the following command to set the contract address if you have [`jq`][jq] installed: + > + > ```bash + > export EVEN_NUMBER_ADDRESS=$(jq -re '.transactions[] | select(.contractName == "EvenNumber") | .contractAddress' ./broadcast/Deploy.s.sol/1/run-latest.json) + > ``` + +### Interact with your Ethereum Mainnet deployment + +1. Query the state: + + ```bash + cast call --rpc-url https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY:?} ${EVEN_NUMBER_ADDRESS:?} 'get()(uint256)' + ``` + +2. Publish a new state + + > NOTE: Currently only a local wallet, provided by the `ETH_WALLET_PRIVATE_KEY` env var is implemented in the example publisher app. + > Please see https://github.com/risc0/risc0-foundry-template/issues/121 for more details. + + ```bash + cargo run --bin publisher -- \ + --chain-id=1 \ + --rpc-url=https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY:?} \ + --contract=${EVEN_NUMBER_ADDRESS:?} \ + --input=12345678 + ``` + +3. Query the state again to see the change: + + ```bash + cast call --rpc-url https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY:?} ${EVEN_NUMBER_ADDRESS:?} 'get()(uint256)' + ``` + +[Deploy to Ethereum Mainnet]: #deploy-your-project-on-ethereum-mainnet +[Deploy your project to a local network]: #deploy-your-project-on-a-local-network +[RISC Zero]: https://www.risczero.com/ +[Docker]: https://docs.docker.com/engine/install/ +[contracts]: ./contracts/ +[jq]: https://jqlang.github.io/jq/ +[methods]: ./methods/ +[tested]: ./README.md#run-the-tests +[config]: ./script/config.toml +[forge-script-wallet-docs]: https://book.getfoundry.sh/reference/forge/forge-script#wallet-options---raw diff --git a/packages/risc0/foundry.toml b/packages/risc0/foundry.toml new file mode 100644 index 0000000..b8d3a5c --- /dev/null +++ b/packages/risc0/foundry.toml @@ -0,0 +1,12 @@ +[profile.default] +src = "contracts" +out = "out" +libs = ["lib", "../evm-base"] +test = "tests" +ffi = true +fs_permissions = [{ access = "read-write", path = "./"}] +via_ir = true +optimizer = true +optimizer-runs = 10_000_000 + +# See more config options https://github.com/foundry-rs/foundry/tree/master/config diff --git a/packages/risc0/images/risc0-foundry-template.png b/packages/risc0/images/risc0-foundry-template.png new file mode 100644 index 0000000000000000000000000000000000000000..607a0ec19801390eb7792115b962f68a5da19956 GIT binary patch literal 523833 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%0Db$Z_5B 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�OwVr|U?Ah2Yt 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

)0 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 z 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 wbE^@oxsRr(@JUT#= In typical use cases, the only code in this directory that you will need to edit is inside [guest/src/bin]. + + +### Writing Guest Code + +To learn to write code for the zkVM, we recommend [Guest Code 101]. + +Examples of what you can do in the guest can be found in the [RISC Zero examples]. + + +### From Guest Code to Binary File + +Code in the `methods/guest` directory will be compiled into one or more binaries. + +Build configuration for the methods is included in `methods/build.rs`. + +Each will have a corresponding image ID, which is a hash identifying the program. + + +[zkVM]: https://dev.risczero.com/zkvm +[RISC Zero]: https://www.risczero.com/ +[guest programs]: https://dev.risczero.com/terminology#guest-program +[on-chain logic]: ../contracts/ +[guest/src/bin]: ./guest/src/bin/ +[Guest Code 101]: https://dev.risczero.com/zkvm/developer-guide/guest-code-101 +[RISC Zero examples]: https://github.com/risc0/tree/v0.18.0/examples \ No newline at end of file diff --git a/packages/risc0/methods/build.rs b/packages/risc0/methods/build.rs new file mode 100644 index 0000000..11130e5 --- /dev/null +++ b/packages/risc0/methods/build.rs @@ -0,0 +1,46 @@ +// Copyright 2023 RISC Zero, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::{collections::HashMap, env}; + +use risc0_build::{embed_methods_with_options, DockerOptions, GuestOptions}; +use risc0_build_ethereum::generate_solidity_files; + +// Paths where the generated Solidity files will be written. +const SOLIDITY_IMAGE_ID_PATH: &str = "../contracts/ImageID.sol"; +const SOLIDITY_ELF_PATH: &str = "../tests/Elf.sol"; + +fn main() { + // Builds can be made deterministic, and thereby reproducible, by using Docker to build the + // guest. Check the RISC0_USE_DOCKER variable and use Docker to build the guest if set. + let use_docker = env::var("RISC0_USE_DOCKER").ok().map(|_| DockerOptions { + root_dir: Some("../".into()), + }); + + // Generate Rust source files for the methods crate. + let guests = embed_methods_with_options(HashMap::from([( + "guests", + GuestOptions { + features: Vec::new(), + use_docker, + }, + )])); + + // Generate Solidity source files for use with Forge. + let solidity_opts = risc0_build_ethereum::Options::default() + .with_image_id_sol_path(SOLIDITY_IMAGE_ID_PATH) + .with_elf_sol_path(SOLIDITY_ELF_PATH); + + generate_solidity_files(guests.as_slice(), &solidity_opts).unwrap(); +} diff --git a/packages/risc0/methods/guest/Cargo.lock b/packages/risc0/methods/guest/Cargo.lock new file mode 100644 index 0000000..50a31a2 --- /dev/null +++ b/packages/risc0/methods/guest/Cargo.lock @@ -0,0 +1,1813 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "alloy-primitives" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "600d34d8de81e23b6d909c094e23b3d357e01ca36b78a8c5424c501eedbe86f0" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "k256", + "keccak-asm", + "proptest", + "rand", + "ruint", + "serde", + "tiny-keccak", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b155716bab55763c95ba212806cf43d05bcc70e5f35b02bad20cf5ec7fe11fed" +dependencies = [ + "arrayvec", + "bytes", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86ec0a47740b20bc5613b8712d0d321d031c4efc58e9645af96085d5cccfc27" +dependencies = [ + "const-hex", + "dunce", + "heck", + "indexmap", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.66", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-types" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad09ec5853fa700d12d778ad224dcdec636af424d29fad84fb9a2f16a5b0ef09" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "const-hex", + "serde", +] + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "ark-bn254" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" +dependencies = [ + "ark-ec", + "ark-ff 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-crypto-primitives" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3a13b34da09176a8baba701233fdffbaa7c1b1192ce031a3da4e55ce1f1a56" +dependencies = [ + "ark-ec", + "ark-ff 0.4.2", + "ark-relations", + "ark-serialize 0.4.2", + "ark-snark", + "ark-std 0.4.0", + "blake2", + "derivative", + "digest 0.10.7", + "sha2", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff 0.4.2", + "ark-poly", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "hashbrown 0.13.2", + "itertools", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.4.0", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-groth16" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20ceafa83848c3e390f1cbf124bc3193b3e639b3f02009e0e290809a501b95fc" +dependencies = [ + "ark-crypto-primitives", + "ark-ec", + "ark-ff 0.4.2", + "ark-poly", + "ark-relations", + "ark-serialize 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-relations" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00796b6efc05a3f48225e59cb6a2cda78881e7c390872d5786aaf112f31fb4f0" +dependencies = [ + "ark-ff 0.4.2", + "ark-std 0.4.0", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive", + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-snark" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d3cc6833a335bb8a600241889ead68ee89a3cf8448081fb7694c0fe503da63" +dependencies = [ + "ark-ff 0.4.2", + "ark-relations", + "ark-serialize 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "auto_impl" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "bytemuck" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +dependencies = [ + "serde", +] + +[[package]] +name = "cc" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-hex" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8a24a26d37e1ffd45343323dc9fe6654ceea44c12f2fcb3d7ac29e610bc6" +dependencies = [ + "cfg-if", + "cpufeatures", + "hex", + "proptest", + "serde", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version 0.4.0", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "either" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" + +[[package]] +name = "elf" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "guests" +version = "0.1.0" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "risc0-zkvm", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "k256" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + +[[package]] +name = "keccak-asm" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47a3633291834c4fbebf8673acbc1b04ec9d151418ff9b8e26dcd79129928758" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "num-bigint" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "parity-scale-codec" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pest" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "risc0-binfmt" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a33ca13e8e2fe08fc283accbb08fcbabbfdd27acf88dddc9b39654d0e487b15" +dependencies = [ + "anyhow", + "elf", + "risc0-zkp", + "risc0-zkvm-platform", + "serde", + "tracing", +] + +[[package]] +name = "risc0-circuit-recursion" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c12ea07079420272e5705baea6a0756b21c0dadeca7ed34a7866eb9c073b9a0" +dependencies = [ + "anyhow", + "bytemuck", + "hex", + "risc0-core", + "risc0-zkp", + "tracing", +] + +[[package]] +name = "risc0-circuit-rv32im" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef57b3afe8e59bec6f535c49c99dc7cd3fda7e93254fd499e5469ec17fec1d0" +dependencies = [ + "anyhow", + "risc0-binfmt", + "risc0-core", + "risc0-zkp", + "risc0-zkvm-platform", + "serde", + "tracing", +] + +[[package]] +name = "risc0-core" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43b7bd8b9adb8bed7eaecfa5c152b6c676c4512aea1120d2cdc5fbbca4b2ffb" +dependencies = [ + "bytemuck", + "rand_core", +] + +[[package]] +name = "risc0-groth16" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e275963cd541e1bc9b94f36e23e85b87798a96e04fdf7b013500c08b949a8c9" +dependencies = [ + "anyhow", + "ark-bn254", + "ark-ec", + "ark-groth16", + "ark-serialize 0.4.2", + "bytemuck", + "hex", + "num-bigint", + "num-traits", + "risc0-binfmt", + "risc0-zkp", + "serde", +] + +[[package]] +name = "risc0-zkp" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53342780aef2d31ccc0526e6d4b151dab69e678d2e1495b2270ed40f5e1df6f4" +dependencies = [ + "anyhow", + "blake2", + "bytemuck", + "cfg-if", + "digest 0.10.7", + "hex", + "hex-literal", + "paste", + "rand_core", + "risc0-core", + "risc0-zkvm-platform", + "serde", + "sha2", + "tracing", +] + +[[package]] +name = "risc0-zkvm" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "774b03337fa1675204a067b3f15be740dbedde63fa46647017140fd023805afb" +dependencies = [ + "anyhow", + "bytemuck", + "cfg-if", + "getrandom", + "hex", + "risc0-binfmt", + "risc0-circuit-recursion", + "risc0-circuit-rv32im", + "risc0-core", + "risc0-groth16", + "risc0-zkp", + "risc0-zkvm-platform", + "rrs-lib", + "semver 1.0.23", + "serde", + "sha2", + "tracing", +] + +[[package]] +name = "risc0-zkvm-platform" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b8df83bfa425e078ef77ed115f5eff26b5ebc4a584253b31e4e7122fa2bcced" +dependencies = [ + "bytemuck", + "getrandom", + "libm", +] + +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "rrs-lib" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4382d3af3a4ebdae7f64ba6edd9114fff92c89808004c4943b393377a25d001" +dependencies = [ + "downcast-rs", + "paste", +] + +[[package]] +name = "ruint" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f308135fef9fc398342da5472ce7c484529df23743fb7c734e0f3d472971e62" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "bytes", + "fastrlp", + "num-bigint", + "num-traits", + "parity-scale-codec", + "primitive-types", + "proptest", + "rand", + "rlp", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86854cf50259291520509879a5c294c3c9a4c334e9ff65071c51e42ef1e2343" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.23", +] + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + +[[package]] +name = "serde" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3-asm" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9b57fd861253bff08bb1919e995f90ba8f4889de2726091c8876f3a4e823b40" +dependencies = [ + "cc", + "cfg-if", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-solidity" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3d0961cd53c23ea94eeec56ba940f636f6394788976e9f16ca5ee0aca7464a" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys", +] + +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "toml_datetime" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "tracing-core", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zerocopy" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] diff --git a/packages/risc0/methods/guest/Cargo.toml b/packages/risc0/methods/guest/Cargo.toml new file mode 100644 index 0000000..53cb6d9 --- /dev/null +++ b/packages/risc0/methods/guest/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "guests" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "is-even" +path = "src/bin/is_even.rs" + +[workspace] + +[dependencies] +alloy-primitives = { version = "0.6", default-features = false, features = ["rlp", "serde", "std"] } +alloy-sol-types = { version = "0.6" } +risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] } + +[profile.release] +lto = "thin" diff --git a/packages/risc0/methods/guest/README.md b/packages/risc0/methods/guest/README.md new file mode 100644 index 0000000..8daf033 --- /dev/null +++ b/packages/risc0/methods/guest/README.md @@ -0,0 +1,12 @@ +# Guest Programs + +Each file in the [`src/bin`](./src/bin) folder defines a program for the zkVM. +We refer to the program running in the zkVM as the "[guest]". + +To learn more about writing guest programs, check out the zkVM [developer docs]. +For zkVM API documentation, see the [guest module] of the [`risc0-zkvm`] crate. + +[guest]: https://dev.risczero.com/terminology#guest +[developer docs]: https://dev.risczero.com/zkvm +[guest module]: https://docs.rs/risc0-zkvm/latest/risc0_zkvm/guest/index.html +[`risc0-zkvm`]: https://docs.rs/risc0-zkvm/latest/risc0_zkvm/index.html diff --git a/packages/risc0/methods/guest/src/bin/is_even.rs b/packages/risc0/methods/guest/src/bin/is_even.rs new file mode 100644 index 0000000..9d834b6 --- /dev/null +++ b/packages/risc0/methods/guest/src/bin/is_even.rs @@ -0,0 +1,35 @@ +// Copyright 2023 RISC Zero, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::io::Read; + +use alloy_primitives::U256; +use alloy_sol_types::SolValue; +use risc0_zkvm::guest::env; + +fn main() { + // Read the input data for this application. + let mut input_bytes = Vec::::new(); + env::stdin().read_to_end(&mut input_bytes).unwrap(); + // Decode and parse the input + let number = ::abi_decode(&input_bytes, true).unwrap(); + + // Run the computation. + // In this case, asserting that the provided number is even. + assert!(!number.bit(0), "number is not even"); + + // Commit the journal that will be received by the application contract. + // Journal is encoded using Solidity ABI for easy decoding in the app contract. + env::commit_slice(number.abi_encode().as_slice()); +} diff --git a/packages/risc0/methods/src/lib.rs b/packages/risc0/methods/src/lib.rs new file mode 100644 index 0000000..c9fab1b --- /dev/null +++ b/packages/risc0/methods/src/lib.rs @@ -0,0 +1,53 @@ +// Copyright 2023 RISC Zero, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Generated crate containing the image ID and ELF binary of the build guest. +include!(concat!(env!("OUT_DIR"), "/methods.rs")); + +#[cfg(test)] +mod tests { + use alloy_primitives::U256; + use alloy_sol_types::SolValue; + use risc0_zkvm::{default_executor, ExecutorEnv}; + + #[test] + fn proves_even_number() { + let even_number = U256::from(1304); + + let env = ExecutorEnv::builder() + .write_slice(&even_number.abi_encode()) + .build() + .unwrap(); + + // NOTE: Use the executor to run tests without proving. + let session_info = default_executor().execute(env, super::IS_EVEN_ELF).unwrap(); + + let x = U256::abi_decode(&session_info.journal.bytes, true).unwrap(); + assert_eq!(x, even_number); + } + + #[test] + #[should_panic(expected = "number is not even")] + fn rejects_odd_number() { + let odd_number = U256::from(75); + + let env = ExecutorEnv::builder() + .write_slice(&odd_number.abi_encode()) + .build() + .unwrap(); + + // NOTE: Use the executor to run tests without proving. + default_executor().execute(env, super::IS_EVEN_ELF).unwrap(); + } +} diff --git a/packages/risc0/remappings.txt b/packages/risc0/remappings.txt new file mode 100644 index 0000000..87ba90f --- /dev/null +++ b/packages/risc0/remappings.txt @@ -0,0 +1,4 @@ +forge-std/=lib/forge-std/src/ +openzeppelin/=lib/openzeppelin-contracts/ +risc0/=lib/risc0-ethereum/contracts/src/ +evm-base/=../evm-base/ \ No newline at end of file diff --git a/packages/risc0/rust-toolchain.toml b/packages/risc0/rust-toolchain.toml new file mode 100644 index 0000000..6c18dfc --- /dev/null +++ b/packages/risc0/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "stable" +components = ["rustfmt", "rust-src"] +profile = "minimal" \ No newline at end of file diff --git a/packages/risc0/script/Deploy.s.sol b/packages/risc0/script/Deploy.s.sol new file mode 100644 index 0000000..57131ca --- /dev/null +++ b/packages/risc0/script/Deploy.s.sol @@ -0,0 +1,134 @@ +// Copyright 2024 RISC Zero, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.20; + +import {Script} from "forge-std/Script.sol"; +import "forge-std/Test.sol"; +import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; +import {RiscZeroGroth16Verifier} from "risc0/groth16/RiscZeroGroth16Verifier.sol"; +import {ControlID} from "risc0/groth16/ControlID.sol"; + +import {CRISPRisc0} from "../contracts/CRISPRisc0.sol"; +import {IEnclave} from "evm-base/contracts/CRISPBase.sol"; + +/// @notice Deployment script for the RISC Zero starter project. +/// @dev Use the following environment variable to control the deployment: +/// * Set one of these two environment variables to control the deployment wallet: +/// * ETH_WALLET_PRIVATE_KEY private key of the wallet account. +/// * ETH_WALLET_ADDRESS address of the wallet account. +/// +/// See the Foundry documentation for more information about Solidity scripts, +/// including information about wallet options. +/// +/// https://book.getfoundry.sh/tutorials/solidity-scripting +/// https://book.getfoundry.sh/reference/forge/forge-script +contract CRISPRisc0Deploy is Script { + // Path to deployment config file, relative to the project root. + string constant CONFIG_FILE = "script/config.toml"; + + IRiscZeroVerifier verifier; + IEnclave enclave; + + function run() external { + // Read and log the chainID + uint256 chainId = block.chainid; + console2.log("You are deploying on ChainID %d", chainId); + + // Read the config profile from the environment variable, or use the default for the chainId. + // Default is the first profile with a matching chainId field. + string memory config = vm.readFile( + string.concat(vm.projectRoot(), "/", CONFIG_FILE) + ); + string memory configProfile = vm.envOr("CONFIG_PROFILE", string("")); + if (bytes(configProfile).length == 0) { + string[] memory profileKeys = vm.parseTomlKeys(config, ".profile"); + for (uint256 i = 0; i < profileKeys.length; i++) { + if ( + stdToml.readUint( + config, + string.concat(".profile.", profileKeys[i], ".chainId") + ) == chainId + ) { + configProfile = profileKeys[i]; + break; + } + } + } + + if (bytes(configProfile).length != 0) { + console2.log("Deploying using config profile:", configProfile); + string memory configProfileKey = string.concat( + ".profile.", + configProfile + ); + address riscZeroVerifierAddress = stdToml.readAddress( + config, + string.concat(configProfileKey, ".riscZeroVerifierAddress") + ); + // If set, use the predeployed verifier address found in the config. + verifier = IRiscZeroVerifier(riscZeroVerifierAddress); + + address enclaveAddress = stdToml.readAddress( + config, + string.concat(configProfileKey, ".enclaveAddress") + ); + enclave = IEnclave(enclaveAddress); + } + + // Determine the wallet to send transactions from. + uint256 deployerKey = uint256( + vm.envOr("ETH_WALLET_PRIVATE_KEY", bytes32(0)) + ); + address deployerAddr = address(0); + if (deployerKey != 0) { + // Check for conflicts in how the two environment variables are set. + address envAddr = vm.envOr("ETH_WALLET_ADDRESS", address(0)); + require( + envAddr == address(0) || envAddr == vm.addr(deployerKey), + "conflicting settings from ETH_WALLET_PRIVATE_KEY and ETH_WALLET_ADDRESS" + ); + + vm.startBroadcast(deployerKey); + } else { + deployerAddr = vm.envAddress("ETH_WALLET_ADDRESS"); + vm.startBroadcast(deployerAddr); + } + + // Deploy the verifier, if not already deployed. + if (address(verifier) == address(0)) { + verifier = new RiscZeroGroth16Verifier( + ControlID.CONTROL_ROOT, + ControlID.BN254_CONTROL_ID + ); + console2.log( + "Deployed RiscZeroGroth16Verifier to", + address(verifier) + ); + } else { + console2.log( + "Using IRiscZeroVerifier contract deployed at", + address(verifier) + ); + } + + // Deploy the application contract. + CRISPRisc0 crisp = new CRISPRisc0(enclave, verifier); + console2.log("Deployed CRISPRisc0 to", address(crisp)); + + vm.stopBroadcast(); + } +} diff --git a/packages/risc0/script/config.toml b/packages/risc0/script/config.toml new file mode 100644 index 0000000..db71242 --- /dev/null +++ b/packages/risc0/script/config.toml @@ -0,0 +1,16 @@ +[profile.mainnet] +# RISC Zero Verifier contract deployed on mainnet (see https://dev.risczero.com/api/blockchain-integration/contracts/verifier#deployed-verifiers) +chainId = 1 +riscZeroVerifierAddress = "0x8EaB2D97Dfce405A1692a21b3ff3A172d593D319" +enclaveAddress = "0xE3000000000000000000000000000000000000E3" + +[profile.sepolia] +# RISC Zero Verifier contract deployed on sepolia (see https://dev.risczero.com/api/blockchain-integration/contracts/verifier#deployed-verifiers) +chainId = 11155111 +riscZeroVerifierAddress = "0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187" +enclaveAddress = "0xE3000000000000000000000000000000000000E3" + +# You can add additional profiles here +# [profile.custom] +# chainId = 11155111 +# riscZeroVerifierAddress = From 7fe38f93311e4dde669a85d3d7d31ea01165dcf5 Mon Sep 17 00:00:00 2001 From: Auryn Macmillan Date: Mon, 2 Sep 2024 10:26:09 -0400 Subject: [PATCH 08/62] =?UTF-8?q?fix:=20=F0=9F=90=8D=20snake=20case?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/{evm-base => evm_base}/.gitignore | 0 packages/{evm-base => evm_base}/LICENSE.md | 0 packages/{evm-base => evm_base}/README.md | 0 packages/{evm-base => evm_base}/contracts/CRISPBase.sol | 0 .../contracts/interfaces/IComputationModule.sol | 0 packages/{evm-base => evm_base}/contracts/interfaces/IEnclave.sol | 0 .../contracts/interfaces/IInputValidator.sol | 0 packages/{evm-base => evm_base}/deploy/deploy.ts | 0 packages/{evm-base => evm_base}/hardhat.config.ts | 0 packages/{evm-base => evm_base}/package.json | 0 packages/{evm-base => evm_base}/test/RFVoting.spec.ts | 0 packages/{evm-base => evm_base}/tsconfig.json | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename packages/{evm-base => evm_base}/.gitignore (100%) rename packages/{evm-base => evm_base}/LICENSE.md (100%) rename packages/{evm-base => evm_base}/README.md (100%) rename packages/{evm-base => evm_base}/contracts/CRISPBase.sol (100%) rename packages/{evm-base => evm_base}/contracts/interfaces/IComputationModule.sol (100%) rename packages/{evm-base => evm_base}/contracts/interfaces/IEnclave.sol (100%) rename packages/{evm-base => evm_base}/contracts/interfaces/IInputValidator.sol (100%) rename packages/{evm-base => evm_base}/deploy/deploy.ts (100%) rename packages/{evm-base => evm_base}/hardhat.config.ts (100%) rename packages/{evm-base => evm_base}/package.json (100%) rename packages/{evm-base => evm_base}/test/RFVoting.spec.ts (100%) rename packages/{evm-base => evm_base}/tsconfig.json (100%) diff --git a/packages/evm-base/.gitignore b/packages/evm_base/.gitignore similarity index 100% rename from packages/evm-base/.gitignore rename to packages/evm_base/.gitignore diff --git a/packages/evm-base/LICENSE.md b/packages/evm_base/LICENSE.md similarity index 100% rename from packages/evm-base/LICENSE.md rename to packages/evm_base/LICENSE.md diff --git a/packages/evm-base/README.md b/packages/evm_base/README.md similarity index 100% rename from packages/evm-base/README.md rename to packages/evm_base/README.md diff --git a/packages/evm-base/contracts/CRISPBase.sol b/packages/evm_base/contracts/CRISPBase.sol similarity index 100% rename from packages/evm-base/contracts/CRISPBase.sol rename to packages/evm_base/contracts/CRISPBase.sol diff --git a/packages/evm-base/contracts/interfaces/IComputationModule.sol b/packages/evm_base/contracts/interfaces/IComputationModule.sol similarity index 100% rename from packages/evm-base/contracts/interfaces/IComputationModule.sol rename to packages/evm_base/contracts/interfaces/IComputationModule.sol diff --git a/packages/evm-base/contracts/interfaces/IEnclave.sol b/packages/evm_base/contracts/interfaces/IEnclave.sol similarity index 100% rename from packages/evm-base/contracts/interfaces/IEnclave.sol rename to packages/evm_base/contracts/interfaces/IEnclave.sol diff --git a/packages/evm-base/contracts/interfaces/IInputValidator.sol b/packages/evm_base/contracts/interfaces/IInputValidator.sol similarity index 100% rename from packages/evm-base/contracts/interfaces/IInputValidator.sol rename to packages/evm_base/contracts/interfaces/IInputValidator.sol diff --git a/packages/evm-base/deploy/deploy.ts b/packages/evm_base/deploy/deploy.ts similarity index 100% rename from packages/evm-base/deploy/deploy.ts rename to packages/evm_base/deploy/deploy.ts diff --git a/packages/evm-base/hardhat.config.ts b/packages/evm_base/hardhat.config.ts similarity index 100% rename from packages/evm-base/hardhat.config.ts rename to packages/evm_base/hardhat.config.ts diff --git a/packages/evm-base/package.json b/packages/evm_base/package.json similarity index 100% rename from packages/evm-base/package.json rename to packages/evm_base/package.json diff --git a/packages/evm-base/test/RFVoting.spec.ts b/packages/evm_base/test/RFVoting.spec.ts similarity index 100% rename from packages/evm-base/test/RFVoting.spec.ts rename to packages/evm_base/test/RFVoting.spec.ts diff --git a/packages/evm-base/tsconfig.json b/packages/evm_base/tsconfig.json similarity index 100% rename from packages/evm-base/tsconfig.json rename to packages/evm_base/tsconfig.json From be245bc72602d3aa80bba291b9ee81e578e68712 Mon Sep 17 00:00:00 2001 From: Auryn Macmillan Date: Mon, 2 Sep 2024 13:30:51 -0400 Subject: [PATCH 09/62] feat: add forge to `evm_base` --- .gitmodules | 3 +++ packages/evm_base/foundry.toml | 6 ++++++ packages/evm_base/hardhat.config.ts | 1 + packages/evm_base/package.json | 1 + 4 files changed, 11 insertions(+) create mode 100644 .gitmodules create mode 100644 packages/evm_base/foundry.toml diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b30b90e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "packages/evm_base/lib/forge-std"] + path = packages/evm_base/lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/packages/evm_base/foundry.toml b/packages/evm_base/foundry.toml new file mode 100644 index 0000000..4f3bf9f --- /dev/null +++ b/packages/evm_base/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = 'contracts' +out = 'out' +libs = ['node_modules', 'lib'] +test = 'test' +cache_path = 'cache_forge' \ No newline at end of file diff --git a/packages/evm_base/hardhat.config.ts b/packages/evm_base/hardhat.config.ts index 3fab0fa..e5cc8d4 100644 --- a/packages/evm_base/hardhat.config.ts +++ b/packages/evm_base/hardhat.config.ts @@ -1,3 +1,4 @@ +import "@nomicfoundation/hardhat-foundry"; import "@nomicfoundation/hardhat-toolbox"; import { config as dotenvConfig } from "dotenv"; import "hardhat-deploy"; diff --git a/packages/evm_base/package.json b/packages/evm_base/package.json index 7dff368..b4c8793 100644 --- a/packages/evm_base/package.json +++ b/packages/evm_base/package.json @@ -9,6 +9,7 @@ "devDependencies": { "@nomicfoundation/hardhat-chai-matchers": "^2.0.0", "@nomicfoundation/hardhat-ethers": "^3.0.0", + "@nomicfoundation/hardhat-foundry": "^1.1.2", "@nomicfoundation/hardhat-network-helpers": "^1.0.6", "@nomicfoundation/hardhat-toolbox": "^3.0.0", "@nomicfoundation/hardhat-verify": "^1.0.0", From f9a7d2fd7b4847da130de14ebb9ba50df165f9d9 Mon Sep 17 00:00:00 2001 From: Auryn Macmillan Date: Mon, 2 Sep 2024 13:34:11 -0400 Subject: [PATCH 10/62] fix: add lib/ to gitignore --- packages/evm_base/.gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/evm_base/.gitignore b/packages/evm_base/.gitignore index d912983..badb582 100644 --- a/packages/evm_base/.gitignore +++ b/packages/evm_base/.gitignore @@ -22,3 +22,6 @@ yarn.lock contracts/Elf.sol contracts/ImageID.sol + +# libraries +lib/ From 3af32fe35801f80bb4bc5079169b1ec1ecbe77ed Mon Sep 17 00:00:00 2001 From: Auryn Macmillan Date: Mon, 2 Sep 2024 13:54:01 -0400 Subject: [PATCH 11/62] feat: add hardhat to risc0 --- packages/evm_base/package.json | 2 +- packages/risc0/.gitignore | 24 ++++- packages/risc0/contracts/Lock.sol | 34 +++++++ packages/risc0/hardhat.config.ts | 9 ++ packages/risc0/ignition/modules/Lock.ts | 17 ++++ packages/risc0/package.json | 17 ++++ packages/risc0/test/Lock.ts | 127 ++++++++++++++++++++++++ packages/risc0/tsconfig.json | 11 ++ 8 files changed, 239 insertions(+), 2 deletions(-) create mode 100644 packages/risc0/contracts/Lock.sol create mode 100644 packages/risc0/hardhat.config.ts create mode 100644 packages/risc0/ignition/modules/Lock.ts create mode 100644 packages/risc0/package.json create mode 100644 packages/risc0/test/Lock.ts create mode 100644 packages/risc0/tsconfig.json diff --git a/packages/evm_base/package.json b/packages/evm_base/package.json index b4c8793..d0ee572 100644 --- a/packages/evm_base/package.json +++ b/packages/evm_base/package.json @@ -1,6 +1,6 @@ { "name": "@gnosisguild/crisp", - "description": "", + "description": "Collusion Resistant Impartial Selection Protocol", "version": "1.0.0", "author": { "name": "gnosisguild", diff --git a/packages/risc0/.gitignore b/packages/risc0/.gitignore index 56cbac3..6eddd44 100644 --- a/packages/risc0/.gitignore +++ b/packages/risc0/.gitignore @@ -20,4 +20,26 @@ target/ # Misc .DS_Store -.idea \ No newline at end of file +.idea + +# Hardhat files +*.env +*.log +.DS_Store +.pnp.* +coverage.json +package-lock.json +yarn.lock + +# Hardhat Directories +.coverage_artifacts +.coverage_cache +.coverage_contracts +artifacts +build +cache +coverage +dist +node_modules +types +deployments \ No newline at end of file diff --git a/packages/risc0/contracts/Lock.sol b/packages/risc0/contracts/Lock.sol new file mode 100644 index 0000000..1efbef3 --- /dev/null +++ b/packages/risc0/contracts/Lock.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +// Uncomment this line to use console.log +// import "hardhat/console.sol"; + +contract Lock { + uint public unlockTime; + address payable public owner; + + event Withdrawal(uint amount, uint when); + + constructor(uint _unlockTime) payable { + require( + block.timestamp < _unlockTime, + "Unlock time should be in the future" + ); + + unlockTime = _unlockTime; + owner = payable(msg.sender); + } + + function withdraw() public { + // Uncomment this line, and the import of "hardhat/console.sol", to print a log in your terminal + // console.log("Unlock time is %o and block timestamp is %o", unlockTime, block.timestamp); + + require(block.timestamp >= unlockTime, "You can't withdraw yet"); + require(msg.sender == owner, "You aren't the owner"); + + emit Withdrawal(address(this).balance, block.timestamp); + + owner.transfer(address(this).balance); + } +} diff --git a/packages/risc0/hardhat.config.ts b/packages/risc0/hardhat.config.ts new file mode 100644 index 0000000..9bad4ec --- /dev/null +++ b/packages/risc0/hardhat.config.ts @@ -0,0 +1,9 @@ +import { HardhatUserConfig } from "hardhat/config"; +import "@nomicfoundation/hardhat-toolbox"; +import "@nomicfoundation/hardhat-foundry"; + +const config: HardhatUserConfig = { + solidity: "0.8.26", +}; + +export default config; diff --git a/packages/risc0/ignition/modules/Lock.ts b/packages/risc0/ignition/modules/Lock.ts new file mode 100644 index 0000000..eda0eba --- /dev/null +++ b/packages/risc0/ignition/modules/Lock.ts @@ -0,0 +1,17 @@ +import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; + +const JAN_1ST_2030 = 1893456000; +const ONE_GWEI: bigint = 1_000_000_000n; + +const LockModule = buildModule("LockModule", (m) => { + const unlockTime = m.getParameter("unlockTime", JAN_1ST_2030); + const lockedAmount = m.getParameter("lockedAmount", ONE_GWEI); + + const lock = m.contract("Lock", [unlockTime], { + value: lockedAmount, + }); + + return { lock }; +}); + +export default LockModule; diff --git a/packages/risc0/package.json b/packages/risc0/package.json new file mode 100644 index 0000000..16b94b8 --- /dev/null +++ b/packages/risc0/package.json @@ -0,0 +1,17 @@ +{ + "name": "crisp-risc0", + "version": "0.0.0", + "description": "Risc0 implementation of CRISP", + "repository": "https://github.com/gnosisguild/CRISP", + "author": "Gnosis Guild", + "license": "LGPL-V3", + "private": false, + "devDependencies": { + "@nomicfoundation/hardhat-foundry": "^1.1.2", + "@nomicfoundation/hardhat-toolbox": "^5.0.0", + "hardhat": "^2.22.10" + }, + "scripts": { + "hardhat": "hardhat" + } +} diff --git a/packages/risc0/test/Lock.ts b/packages/risc0/test/Lock.ts new file mode 100644 index 0000000..160dbfa --- /dev/null +++ b/packages/risc0/test/Lock.ts @@ -0,0 +1,127 @@ +import { + time, + loadFixture, +} from "@nomicfoundation/hardhat-toolbox/network-helpers"; +import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs"; +import { expect } from "chai"; +import hre from "hardhat"; + +describe("Lock", function () { + // We define a fixture to reuse the same setup in every test. + // We use loadFixture to run this setup once, snapshot that state, + // and reset Hardhat Network to that snapshot in every test. + async function deployOneYearLockFixture() { + const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60; + const ONE_GWEI = 1_000_000_000; + + const lockedAmount = ONE_GWEI; + const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS; + + // Contracts are deployed using the first signer/account by default + const [owner, otherAccount] = await hre.ethers.getSigners(); + + const Lock = await hre.ethers.getContractFactory("Lock"); + const lock = await Lock.deploy(unlockTime, { value: lockedAmount }); + + return { lock, unlockTime, lockedAmount, owner, otherAccount }; + } + + describe("Deployment", function () { + it("Should set the right unlockTime", async function () { + const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture); + + expect(await lock.unlockTime()).to.equal(unlockTime); + }); + + it("Should set the right owner", async function () { + const { lock, owner } = await loadFixture(deployOneYearLockFixture); + + expect(await lock.owner()).to.equal(owner.address); + }); + + it("Should receive and store the funds to lock", async function () { + const { lock, lockedAmount } = await loadFixture( + deployOneYearLockFixture + ); + + expect(await hre.ethers.provider.getBalance(lock.target)).to.equal( + lockedAmount + ); + }); + + it("Should fail if the unlockTime is not in the future", async function () { + // We don't use the fixture here because we want a different deployment + const latestTime = await time.latest(); + const Lock = await hre.ethers.getContractFactory("Lock"); + await expect(Lock.deploy(latestTime, { value: 1 })).to.be.revertedWith( + "Unlock time should be in the future" + ); + }); + }); + + describe("Withdrawals", function () { + describe("Validations", function () { + it("Should revert with the right error if called too soon", async function () { + const { lock } = await loadFixture(deployOneYearLockFixture); + + await expect(lock.withdraw()).to.be.revertedWith( + "You can't withdraw yet" + ); + }); + + it("Should revert with the right error if called from another account", async function () { + const { lock, unlockTime, otherAccount } = await loadFixture( + deployOneYearLockFixture + ); + + // We can increase the time in Hardhat Network + await time.increaseTo(unlockTime); + + // We use lock.connect() to send a transaction from another account + await expect(lock.connect(otherAccount).withdraw()).to.be.revertedWith( + "You aren't the owner" + ); + }); + + it("Shouldn't fail if the unlockTime has arrived and the owner calls it", async function () { + const { lock, unlockTime } = await loadFixture( + deployOneYearLockFixture + ); + + // Transactions are sent using the first signer by default + await time.increaseTo(unlockTime); + + await expect(lock.withdraw()).not.to.be.reverted; + }); + }); + + describe("Events", function () { + it("Should emit an event on withdrawals", async function () { + const { lock, unlockTime, lockedAmount } = await loadFixture( + deployOneYearLockFixture + ); + + await time.increaseTo(unlockTime); + + await expect(lock.withdraw()) + .to.emit(lock, "Withdrawal") + .withArgs(lockedAmount, anyValue); // We accept any value as `when` arg + }); + }); + + describe("Transfers", function () { + it("Should transfer the funds to the owner", async function () { + const { lock, unlockTime, lockedAmount, owner } = await loadFixture( + deployOneYearLockFixture + ); + + await time.increaseTo(unlockTime); + + await expect(lock.withdraw()).to.changeEtherBalances( + [owner, lock], + [lockedAmount, -lockedAmount] + ); + }); + }); + }); +}); diff --git a/packages/risc0/tsconfig.json b/packages/risc0/tsconfig.json new file mode 100644 index 0000000..574e785 --- /dev/null +++ b/packages/risc0/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "commonjs", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true + } +} From a749c8b50f4e7b786ab90ddbb9c1267318640cca Mon Sep 17 00:00:00 2001 From: Auryn Macmillan Date: Mon, 2 Sep 2024 14:33:07 -0400 Subject: [PATCH 12/62] fix: dependencies --- packages/evm_base/hardhat.config.ts | 2 - packages/risc0/contracts/CRISPRisc0.sol | 2 +- packages/risc0/foundry.toml | 2 +- packages/risc0/hardhat.config.ts | 123 +++++++++++++++++++++++- packages/risc0/package.json | 21 +++- packages/risc0/remappings.txt | 2 +- packages/risc0/script/Deploy.s.sol | 2 +- 7 files changed, 142 insertions(+), 12 deletions(-) diff --git a/packages/evm_base/hardhat.config.ts b/packages/evm_base/hardhat.config.ts index e5cc8d4..c6b6f52 100644 --- a/packages/evm_base/hardhat.config.ts +++ b/packages/evm_base/hardhat.config.ts @@ -1,11 +1,9 @@ import "@nomicfoundation/hardhat-foundry"; import "@nomicfoundation/hardhat-toolbox"; -import { config as dotenvConfig } from "dotenv"; import "hardhat-deploy"; import type { HardhatUserConfig } from "hardhat/config"; import { vars } from "hardhat/config"; import type { NetworkUserConfig } from "hardhat/types"; -import { resolve } from "path"; // Run 'npx hardhat vars setup' to see the list of variables that need to be set diff --git a/packages/risc0/contracts/CRISPRisc0.sol b/packages/risc0/contracts/CRISPRisc0.sol index 33da0f2..a188af4 100644 --- a/packages/risc0/contracts/CRISPRisc0.sol +++ b/packages/risc0/contracts/CRISPRisc0.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.26; -import {CRISPBase, IComputationModule, IInputValidator, IEnclave} from "evm-base/contracts/CRISPBase.sol"; +import {CRISPBase, IComputationModule, IInputValidator, IEnclave} from "evm_base/contracts/CRISPBase.sol"; import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; import {ImageID} from "./ImageID.sol"; diff --git a/packages/risc0/foundry.toml b/packages/risc0/foundry.toml index b8d3a5c..267c65a 100644 --- a/packages/risc0/foundry.toml +++ b/packages/risc0/foundry.toml @@ -1,7 +1,7 @@ [profile.default] src = "contracts" out = "out" -libs = ["lib", "../evm-base"] +libs = ["lib", "../evm_base"] test = "tests" ffi = true fs_permissions = [{ access = "read-write", path = "./"}] diff --git a/packages/risc0/hardhat.config.ts b/packages/risc0/hardhat.config.ts index 9bad4ec..c6b6f52 100644 --- a/packages/risc0/hardhat.config.ts +++ b/packages/risc0/hardhat.config.ts @@ -1,9 +1,126 @@ -import { HardhatUserConfig } from "hardhat/config"; -import "@nomicfoundation/hardhat-toolbox"; import "@nomicfoundation/hardhat-foundry"; +import "@nomicfoundation/hardhat-toolbox"; +import "hardhat-deploy"; +import type { HardhatUserConfig } from "hardhat/config"; +import { vars } from "hardhat/config"; +import type { NetworkUserConfig } from "hardhat/types"; + +// Run 'npx hardhat vars setup' to see the list of variables that need to be set + +const mnemonic: string = vars.get("MNEMONIC"); +const infuraApiKey: string = vars.get("INFURA_API_KEY"); + +const chainIds = { + "arbitrum-mainnet": 42161, + avalanche: 43114, + bsc: 56, + ganache: 1337, + hardhat: 31337, + mainnet: 1, + "optimism-mainnet": 10, + "polygon-mainnet": 137, + "polygon-mumbai": 80001, + sepolia: 11155111, + goerli: 5, +}; + +function getChainConfig(chain: keyof typeof chainIds): NetworkUserConfig { + let jsonRpcUrl: string; + switch (chain) { + case "avalanche": + jsonRpcUrl = "https://api.avax.network/ext/bc/C/rpc"; + break; + case "bsc": + jsonRpcUrl = "https://bsc-dataseed1.binance.org"; + break; + default: + jsonRpcUrl = "https://" + chain + ".infura.io/v3/" + infuraApiKey; + } + return { + accounts: { + count: 10, + mnemonic, + path: "m/44'/60'/0'/0", + }, + chainId: chainIds[chain], + url: jsonRpcUrl, + }; +} const config: HardhatUserConfig = { - solidity: "0.8.26", + defaultNetwork: "hardhat", + namedAccounts: { + deployer: 0, + }, + etherscan: { + apiKey: { + arbitrumOne: process.env.ARBISCAN_API_KEY || "", + avalanche: process.env.SNOWTRACE_API_KEY || "", + bsc: process.env.BSCSCAN_API_KEY || "", + mainnet: process.env.ETHERSCAN_API_KEY || "", + optimisticEthereum: process.env.OPTIMISM_API_KEY || "", + polygon: process.env.POLYGONSCAN_API_KEY || "", + polygonMumbai: process.env.POLYGONSCAN_API_KEY || "", + sepolia: process.env.ETHERSCAN_API_KEY || "", + goerli: process.env.ETHERSCAN_API_KEY || "", + }, + }, + gasReporter: { + currency: "USD", + enabled: process.env.REPORT_GAS ? true : false, + excludeContracts: [], + src: "./contracts", + }, + networks: { + hardhat: { + accounts: { + mnemonic, + }, + chainId: chainIds.hardhat, + }, + ganache: { + accounts: { + mnemonic, + }, + chainId: chainIds.ganache, + url: "http://localhost:8545", + }, + arbitrum: getChainConfig("arbitrum-mainnet"), + avalanche: getChainConfig("avalanche"), + bsc: getChainConfig("bsc"), + mainnet: getChainConfig("mainnet"), + optimism: getChainConfig("optimism-mainnet"), + "polygon-mainnet": getChainConfig("polygon-mainnet"), + "polygon-mumbai": getChainConfig("polygon-mumbai"), + sepolia: getChainConfig("sepolia"), + goerli: getChainConfig("goerli"), + }, + paths: { + artifacts: "./artifacts", + cache: "./cache", + sources: "./contracts", + tests: "./test", + }, + solidity: { + version: "0.8.26", + settings: { + metadata: { + // Not including the metadata hash + // https://github.com/paulrberg/hardhat-template/issues/31 + bytecodeHash: "none", + }, + // Disable the optimizer when debugging + // https://hardhat.org/hardhat-network/#solidity-optimizer-support + optimizer: { + enabled: true, + runs: 800, + }, + }, + }, + typechain: { + outDir: "types", + target: "ethers-v6", + }, }; export default config; diff --git a/packages/risc0/package.json b/packages/risc0/package.json index 16b94b8..94c0ee2 100644 --- a/packages/risc0/package.json +++ b/packages/risc0/package.json @@ -4,12 +4,27 @@ "description": "Risc0 implementation of CRISP", "repository": "https://github.com/gnosisguild/CRISP", "author": "Gnosis Guild", - "license": "LGPL-V3", - "private": false, "devDependencies": { + "@nomicfoundation/hardhat-chai-matchers": "^2.0.0", + "@nomicfoundation/hardhat-ethers": "^3.0.0", "@nomicfoundation/hardhat-foundry": "^1.1.2", + "@nomicfoundation/hardhat-ignition": "^0.15.5", + "@nomicfoundation/hardhat-ignition-ethers": "^0.15.0", + "@nomicfoundation/hardhat-network-helpers": "^1.0.0", "@nomicfoundation/hardhat-toolbox": "^5.0.0", - "hardhat": "^2.22.10" + "@nomicfoundation/hardhat-verify": "^2.0.0", + "@nomicfoundation/ignition-core": "^0.15.5", + "@typechain/ethers-v6": "^0.5.0", + "@typechain/hardhat": "^9.0.0", + "@types/chai": "^4.2.0", + "@types/mocha": ">=9.1.0", + "hardhat": "^2.22.10", + "hardhat-deploy": "^0.12.4", + "hardhat-gas-reporter": "^1.0.8", + "solidity-coverage": "^0.8.1", + "ts-node": "^10.9.2", + "typechain": "^8.3.0", + "typescript": "^5.5.4" }, "scripts": { "hardhat": "hardhat" diff --git a/packages/risc0/remappings.txt b/packages/risc0/remappings.txt index 87ba90f..8d9a01c 100644 --- a/packages/risc0/remappings.txt +++ b/packages/risc0/remappings.txt @@ -1,4 +1,4 @@ forge-std/=lib/forge-std/src/ openzeppelin/=lib/openzeppelin-contracts/ risc0/=lib/risc0-ethereum/contracts/src/ -evm-base/=../evm-base/ \ No newline at end of file +evm_base/=../evm_base/ \ No newline at end of file diff --git a/packages/risc0/script/Deploy.s.sol b/packages/risc0/script/Deploy.s.sol index 57131ca..dc50bd5 100644 --- a/packages/risc0/script/Deploy.s.sol +++ b/packages/risc0/script/Deploy.s.sol @@ -23,7 +23,7 @@ import {RiscZeroGroth16Verifier} from "risc0/groth16/RiscZeroGroth16Verifier.sol import {ControlID} from "risc0/groth16/ControlID.sol"; import {CRISPRisc0} from "../contracts/CRISPRisc0.sol"; -import {IEnclave} from "evm-base/contracts/CRISPBase.sol"; +import {IEnclave} from "evm_base/contracts/CRISPBase.sol"; /// @notice Deployment script for the RISC Zero starter project. /// @dev Use the following environment variable to control the deployment: From aba5ad0a27152e68af6b7c90143f41cf146e2131 Mon Sep 17 00:00:00 2001 From: Auryn Macmillan Date: Mon, 2 Sep 2024 14:53:24 -0400 Subject: [PATCH 13/62] add: IComputationModule.validate now takes `uint256 seed` as a parameter. --- .../interfaces/IComputationModule.sol | 1 + packages/risc0/contracts/CRISPRisc0.sol | 21 ++++++++++++------- packages/risc0/package.json | 3 ++- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/packages/evm_base/contracts/interfaces/IComputationModule.sol b/packages/evm_base/contracts/interfaces/IComputationModule.sol index 1842fed..1ce1a4f 100644 --- a/packages/evm_base/contracts/interfaces/IComputationModule.sol +++ b/packages/evm_base/contracts/interfaces/IComputationModule.sol @@ -9,6 +9,7 @@ interface IComputationModule { /// @return inputValidator The input validator to be used for the computation. function validate( uint256 e3Id, + uint256 seed, bytes calldata params ) external returns (IInputValidator inputValidator); diff --git a/packages/risc0/contracts/CRISPRisc0.sol b/packages/risc0/contracts/CRISPRisc0.sol index a188af4..d9c173f 100644 --- a/packages/risc0/contracts/CRISPRisc0.sol +++ b/packages/risc0/contracts/CRISPRisc0.sol @@ -9,7 +9,6 @@ struct Params { uint64 degree; uint64 plaintextModulus; uint64[] ciphertextModuli; - uint256 seed; IInputValidator inputValidator; } @@ -31,19 +30,25 @@ contract CRISPRisc0 is CRISPBase { function validate( uint256 e3Id, + uint256 seed, bytes memory data ) external override returns (IInputValidator) { require(params[e3Id].degree == 0, E3AlreadyInitialized()); - Params memory _params = abi.decode(data, (Params)); + ( + uint64 degree, + uint64 plaintextModulus, + uint64[] memory ciphertextModuli, + IInputValidator inputValidator + ) = abi.decode(data, (uint64, uint64, uint64[], IInputValidator)); // TODO: require that params are valid - params[e3Id].degree = _params.degree; - params[e3Id].plaintextModulus = _params.plaintextModulus; - params[e3Id].ciphertextModuli = _params.ciphertextModuli; - params[e3Id].seed = _params.seed; - params[e3Id].inputValidator = _params.inputValidator; + params[e3Id].degree = degree; + params[e3Id].plaintextModulus = plaintextModulus; + params[e3Id].ciphertextModuli = ciphertextModuli; + params[e3Id].seed = seed; + params[e3Id].inputValidator = inputValidator; - return _params.inputValidator; + return inputValidator; } function verify( diff --git a/packages/risc0/package.json b/packages/risc0/package.json index 94c0ee2..669354c 100644 --- a/packages/risc0/package.json +++ b/packages/risc0/package.json @@ -27,6 +27,7 @@ "typescript": "^5.5.4" }, "scripts": { - "hardhat": "hardhat" + "hardhat": "hardhat", + "compile": "forge compile" } } From 6c732c2f35f53a14ae928d0c9454953dd1d76da4 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 4 Sep 2024 00:32:45 +0500 Subject: [PATCH 14/62] draft: parallel merkle tree --- packages/compute_provider/core/src/lib.rs | 97 +++--------- .../compute_provider/core/src/merkle_tree.rs | 75 +++++++++ packages/compute_provider/host/Cargo.toml | 2 +- packages/compute_provider/host/src/lib.rs | 147 +++++++++++++++--- .../methods/guest/src/main.rs | 7 +- 5 files changed, 231 insertions(+), 97 deletions(-) create mode 100644 packages/compute_provider/core/src/merkle_tree.rs diff --git a/packages/compute_provider/core/src/lib.rs b/packages/compute_provider/core/src/lib.rs index 957b772..788b65e 100644 --- a/packages/compute_provider/core/src/lib.rs +++ b/packages/compute_provider/core/src/lib.rs @@ -1,31 +1,28 @@ -use std::{str::FromStr, sync::Arc}; +pub mod merkle_tree; -use num_bigint::BigUint; -use num_traits::Num; -use sha3::{Digest, Keccak256}; - -use ark_bn254::Fr; -use ark_ff::{BigInt, BigInteger}; +use merkle_tree::MerkleTree; +use std::sync::Arc; use fhe::bfv::{BfvParameters, Ciphertext}; use fhe_traits::{Deserialize, DeserializeParametrized, Serialize}; -use light_poseidon::{Poseidon, PoseidonHasher}; -use zk_kit_imt::imt::IMT; - -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct TallyResult { - pub tallied_ciphertext: Vec, +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct ComputationResult { + pub ciphertext: Vec, pub merkle_root: String, } -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct CiphertextInput { +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct ComputationInput { pub ciphertexts: Vec>, pub params: Vec, + pub leaf_hashes: Vec, + pub tree_depth: usize, + pub zero_node: String, + pub arity: usize, } -impl CiphertextInput { - pub fn process(&self) -> TallyResult { +impl ComputationInput { + pub fn process(&self) -> ComputationResult { // Deserialize the parameters let params = Arc::new(BfvParameters::try_deserialize(&self.params).unwrap()); @@ -37,64 +34,16 @@ impl CiphertextInput { } let tally: Arc = Arc::new(sum); - let merkle_root = self.compute_merkle_root(); - - TallyResult { - tallied_ciphertext: tally.to_bytes(), - merkle_root, - } - } - - fn compute_merkle_root(&self) -> String { - fn poseidon_hash(nodes: Vec) -> String { - let mut poseidon = Poseidon::::new_circom(2).unwrap(); - let mut field_elements = Vec::new(); - - for node in nodes { - let sanitized_node = node.trim_start_matches("0x"); - let numeric_str = BigUint::from_str_radix(sanitized_node, 16) - .unwrap() - .to_string(); - let field_repr = Fr::from_str(&numeric_str).unwrap(); - field_elements.push(field_repr); - } + let merkle_root = MerkleTree { + leaf_hashes: self.leaf_hashes.clone(), + tree_depth: self.tree_depth, + zero_node: self.zero_node.clone(), + arity: self.arity, + }.build_tree().root().unwrap(); - let result_hash: BigInt<4> = poseidon.hash(&field_elements).unwrap().into(); - hex::encode(result_hash.to_bytes_be()) + ComputationResult { + ciphertext: tally.to_bytes(), + merkle_root } - - const ZERO: &str = "0"; - const DEPTH: usize = 10; - const ARITY: usize = 2; - - let mut tree = IMT::new( - poseidon_hash, - DEPTH, - ZERO.to_string(), - ARITY, - vec![], - ) - .unwrap(); - - let mut poseidon_instance = Poseidon::::new_circom(2).unwrap(); - - for ciphertext in &self.ciphertexts { - let mut keccak_hasher = Keccak256::new(); - keccak_hasher.update(ciphertext); - let hex_output = hex::encode(keccak_hasher.finalize()); - let sanitized_hex = hex_output.trim_start_matches("0x"); - let numeric_value = BigUint::from_str_radix(sanitized_hex, 16) - .unwrap() - .to_string(); - let fr_element = Fr::from_str(&numeric_value).unwrap(); - let zero_element = Fr::from_str("0").unwrap(); - let hash_bigint: BigInt<4> = poseidon_instance - .hash(&[fr_element, zero_element]) - .unwrap().into(); - let data_hash = hex::encode(hash_bigint.to_bytes_be()); - tree.insert(data_hash).unwrap(); - } - - tree.root().expect("Failed to get root from IMT") } } diff --git a/packages/compute_provider/core/src/merkle_tree.rs b/packages/compute_provider/core/src/merkle_tree.rs new file mode 100644 index 0000000..74c52d6 --- /dev/null +++ b/packages/compute_provider/core/src/merkle_tree.rs @@ -0,0 +1,75 @@ +use sha3::{Digest, Keccak256}; +use num_bigint::BigUint; +use num_traits::Num; +use ark_bn254::Fr; +use ark_ff::{BigInt, BigInteger}; +use light_poseidon::{Poseidon, PoseidonHasher}; +use zk_kit_imt::imt::IMT; +use std::str::FromStr; + +pub struct MerkleTree { + pub leaf_hashes: Vec, + pub tree_depth: usize, + pub zero_node: String, + pub arity: usize, +} + +impl MerkleTree { + pub fn new(tree_depth: usize, zero_node: String, arity: usize) -> Self { + Self { + leaf_hashes: Vec::new(), + tree_depth, + zero_node, + arity, + } + } + + pub fn compute_leaf_hashes(&mut self, data: &[Vec]) { + for item in data { + let mut keccak_hasher = Keccak256::new(); + keccak_hasher.update(item); + let hex_output = hex::encode(keccak_hasher.finalize()); + let sanitized_hex = hex_output.trim_start_matches("0x"); + let numeric_value = BigUint::from_str_radix(sanitized_hex, 16) + .unwrap() + .to_string(); + let fr_element = Fr::from_str(&numeric_value).unwrap(); + let zero_element = Fr::from_str("0").unwrap(); + let mut poseidon_instance = Poseidon::::new_circom(2).unwrap(); + let hash_bigint: BigInt<4> = poseidon_instance + .hash(&[fr_element, zero_element]) + .unwrap() + .into(); + let hash = hex::encode(hash_bigint.to_bytes_be()); + self.leaf_hashes.push(hash); + } + } + + pub fn build_tree(&self) -> IMT { + fn poseidon_hash(nodes: Vec) -> String { + let mut poseidon = Poseidon::::new_circom(2).unwrap(); + let mut field_elements = Vec::new(); + + for node in nodes { + let sanitized_node = node.trim_start_matches("0x"); + let numeric_str = BigUint::from_str_radix(sanitized_node, 16) + .unwrap() + .to_string(); + let field_repr = Fr::from_str(&numeric_str).unwrap(); + field_elements.push(field_repr); + } + + let result_hash: BigInt<4> = poseidon.hash(&field_elements).unwrap().into(); + hex::encode(result_hash.to_bytes_be()) + } + + IMT::new( + poseidon_hash, + self.tree_depth, + self.zero_node.clone(), + self.arity, + self.leaf_hashes.clone(), + ) + .unwrap() + } +} diff --git a/packages/compute_provider/host/Cargo.toml b/packages/compute_provider/host/Cargo.toml index 5090fe5..eadff92 100644 --- a/packages/compute_provider/host/Cargo.toml +++ b/packages/compute_provider/host/Cargo.toml @@ -14,4 +14,4 @@ compute-provider-core ={ path = "../core"} fhe = { workspace = true } fhe-traits = { workspace = true } rand = "0.8" - +rayon = "1.10.0" diff --git a/packages/compute_provider/host/src/lib.rs b/packages/compute_provider/host/src/lib.rs index 8d0c539..061fb2d 100644 --- a/packages/compute_provider/host/src/lib.rs +++ b/packages/compute_provider/host/src/lib.rs @@ -1,23 +1,57 @@ -use compute_provider_core::{CiphertextInput, TallyResult}; +use compute_provider_core::{merkle_tree::MerkleTree, ComputationInput, ComputationResult}; use methods::COMPUTE_PROVIDER_ELF; use risc0_ethereum_contracts::groth16; use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts, VerifierContext}; +use std::sync::Arc; +use rayon::prelude::*; -#[derive(Debug)] pub struct ComputeProvider { - input: CiphertextInput, + input: ComputationInput, + use_parallel: bool, + batch_size: Option, } impl ComputeProvider { - pub fn new(input: CiphertextInput) -> Self { - Self { input } + pub fn new( + ciphertexts: Vec>, + params: Vec, + use_parallel: bool, + batch_size: Option, + ) -> Self { + Self { + input: ComputationInput { + ciphertexts, + params, + leaf_hashes: Vec::new(), + tree_depth: 10, + zero_node: String::from("0"), + arity: 0, + }, + use_parallel, + batch_size, + } } - pub fn start(&self) -> (TallyResult, Vec) { + pub fn start(&self) -> (ComputationResult, Vec) { tracing_subscriber::fmt() .with_env_filter(tracing_subscriber::filter::EnvFilter::from_default_env()) .init(); + if self.use_parallel { + self.start_parallel() + } else { + self.start_sequential() + } + } + + fn start_sequential(&self) -> (ComputationResult, Vec) { + let mut tree_handler = MerkleTree::new( + self.input.tree_depth, + self.input.zero_node.clone(), + self.input.arity, + ); + tree_handler.compute_leaf_hashes(&self.input.ciphertexts); + let env = ExecutorEnv::builder() .write(&self.input) .unwrap() @@ -38,12 +72,90 @@ impl ComputeProvider { (receipt.journal.decode().unwrap(), seal) } + + fn start_parallel(&self) -> (ComputationResult, Vec) { + let batch_size = self.batch_size.unwrap_or(1); + let parallel_tree_depth = (batch_size as f64).log2().ceil() as usize; + + let ciphertexts = Arc::new(self.input.ciphertexts.clone()); + let params = Arc::new(self.input.params.clone()); + + let chunks: Vec>> = ciphertexts + .chunks(batch_size) + .map(|chunk| chunk.to_vec()) + .collect(); + + let tally_results: Vec = chunks.into_par_iter().map(|chunk| { + let mut tree_handler = MerkleTree::new(parallel_tree_depth, "0".to_string(), 2); + tree_handler.compute_leaf_hashes(&chunk); + + let input = ComputationInput { + ciphertexts: chunk.clone(), + params: params.to_vec(), + leaf_hashes: tree_handler.leaf_hashes.clone(), + tree_depth: parallel_tree_depth, + zero_node: "0".to_string(), + arity: 2, + }; + + let env = ExecutorEnv::builder() + .write(&input) + .unwrap() + .build() + .unwrap(); + + let receipt = default_prover() + .prove_with_ctx( + env, + &VerifierContext::default(), + COMPUTE_PROVIDER_ELF, + &ProverOpts::groth16(), + ) + .unwrap() + .receipt; + + receipt.journal.decode().unwrap() + }).collect(); + + // Combine the sorted results for final computation + let final_depth = self.input.tree_depth - parallel_tree_depth; + let mut final_input = ComputationInput { + ciphertexts: tally_results.iter().map(|result| result.ciphertext.clone()).collect(), + params: params.to_vec(), + leaf_hashes: tally_results.iter().map(|result| result.merkle_root.clone()).collect(), + tree_depth: final_depth, + zero_node: String::from("0"), + arity: 2, + }; + + let mut final_tree_handler = MerkleTree::new(final_depth, final_input.zero_node.clone(), final_input.arity); + final_input.zero_node = final_tree_handler.build_tree().zeroes[parallel_tree_depth].clone(); + + let env = ExecutorEnv::builder() + .write(&final_input) + .unwrap() + .build() + .unwrap(); + + let receipt = default_prover() + .prove_with_ctx( + env, + &VerifierContext::default(), + COMPUTE_PROVIDER_ELF, + &ProverOpts::groth16(), + ) + .unwrap() + .receipt; + + let combined_seal = groth16::encode(receipt.inner.groth16().unwrap().seal.clone()).unwrap(); + (receipt.journal.decode().unwrap(), combined_seal) + } } + #[cfg(test)] mod tests { use super::*; - use compute_provider_core::CiphertextInput; use fhe::bfv::{ BfvParameters, BfvParametersBuilder, Ciphertext, Encoding, Plaintext, PublicKey, SecretKey, }; @@ -60,9 +172,13 @@ mod tests { let inputs = vec![1, 1, 0]; let ciphertexts = encrypt_inputs(&inputs, &pk, ¶ms); - let input = create_input(&ciphertexts, ¶ms); - let provider = ComputeProvider::new(input); - let result = provider.input.process(); + let provider = ComputeProvider::new( + ciphertexts.iter().map(|c| c.to_bytes()).collect(), + params.to_bytes(), + false, + None, + ); // use_parallel = false, no batch size + let (result, _seal) = provider.start(); let tally = decrypt_result(&result, &sk, ¶ms); @@ -101,15 +217,8 @@ mod tests { .collect() } - fn create_input(ciphertexts: &[Ciphertext], params: &Arc) -> CiphertextInput { - CiphertextInput { - ciphertexts: ciphertexts.iter().map(|c| c.to_bytes()).collect(), - params: params.to_bytes(), - } - } - - fn decrypt_result(result: &TallyResult, sk: &SecretKey, params: &Arc) -> u64 { - let ct = Ciphertext::from_bytes(&result.tallied_ciphertext, params) + fn decrypt_result(result: &ComputationResult, sk: &SecretKey, params: &Arc) -> u64 { + let ct = Ciphertext::from_bytes(&result.ciphertext, params) .expect("Failed to deserialize ciphertext"); let decrypted = sk.try_decrypt(&ct).expect("Failed to decrypt"); Vec::::try_decode(&decrypted, Encoding::poly()).expect("Failed to decode")[0] diff --git a/packages/compute_provider/methods/guest/src/main.rs b/packages/compute_provider/methods/guest/src/main.rs index f31b8aa..ee76599 100644 --- a/packages/compute_provider/methods/guest/src/main.rs +++ b/packages/compute_provider/methods/guest/src/main.rs @@ -1,10 +1,11 @@ use risc0_zkvm::guest::env; -use compute_provider_core::{CiphertextInput, TallyResult}; +use compute_provider_core::{ComputationInput, ComputationResult}; + fn main() { - let input: CiphertextInput = env::read(); + let input: ComputationInput = env::read(); - let result: TallyResult = input.process(); + let result: ComputationResult = input.process(); env::commit(&result); } From 8870ff7088f571f4a4bfbe4977eb2298470f09a4 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 4 Sep 2024 16:26:14 +0500 Subject: [PATCH 15/62] update tests and add zeroes --- .../compute_provider/core/src/merkle_tree.rs | 40 ++++++++++++------- packages/compute_provider/host/src/lib.rs | 19 ++++----- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/packages/compute_provider/core/src/merkle_tree.rs b/packages/compute_provider/core/src/merkle_tree.rs index 74c52d6..66e88fb 100644 --- a/packages/compute_provider/core/src/merkle_tree.rs +++ b/packages/compute_provider/core/src/merkle_tree.rs @@ -45,26 +45,36 @@ impl MerkleTree { } } - pub fn build_tree(&self) -> IMT { - fn poseidon_hash(nodes: Vec) -> String { - let mut poseidon = Poseidon::::new_circom(2).unwrap(); - let mut field_elements = Vec::new(); + fn poseidon_hash(nodes: Vec) -> String { + let mut poseidon = Poseidon::::new_circom(2).unwrap(); + let mut field_elements = Vec::new(); + + for node in nodes { + let sanitized_node = node.trim_start_matches("0x"); + let numeric_str = BigUint::from_str_radix(sanitized_node, 16) + .unwrap() + .to_string(); + let field_repr = Fr::from_str(&numeric_str).unwrap(); + field_elements.push(field_repr); + } - for node in nodes { - let sanitized_node = node.trim_start_matches("0x"); - let numeric_str = BigUint::from_str_radix(sanitized_node, 16) - .unwrap() - .to_string(); - let field_repr = Fr::from_str(&numeric_str).unwrap(); - field_elements.push(field_repr); - } + let result_hash: BigInt<4> = poseidon.hash(&field_elements).unwrap().into(); + hex::encode(result_hash.to_bytes_be()) + } - let result_hash: BigInt<4> = poseidon.hash(&field_elements).unwrap().into(); - hex::encode(result_hash.to_bytes_be()) + pub fn zeroes(&self) -> Vec { + let mut zeroes = Vec::new(); + let mut current_zero = self.zero_node.clone(); + for _ in 0..self.tree_depth { + zeroes.push(current_zero.clone()); + current_zero = Self::poseidon_hash(vec![current_zero; self.arity]); } + zeroes + } + pub fn build_tree(&self) -> IMT { IMT::new( - poseidon_hash, + Self::poseidon_hash, self.tree_depth, self.zero_node.clone(), self.arity, diff --git a/packages/compute_provider/host/src/lib.rs b/packages/compute_provider/host/src/lib.rs index 061fb2d..903e246 100644 --- a/packages/compute_provider/host/src/lib.rs +++ b/packages/compute_provider/host/src/lib.rs @@ -25,14 +25,14 @@ impl ComputeProvider { leaf_hashes: Vec::new(), tree_depth: 10, zero_node: String::from("0"), - arity: 0, + arity: 2, }, use_parallel, batch_size, } } - pub fn start(&self) -> (ComputationResult, Vec) { + pub fn start(&mut self) -> (ComputationResult, Vec) { tracing_subscriber::fmt() .with_env_filter(tracing_subscriber::filter::EnvFilter::from_default_env()) .init(); @@ -44,13 +44,14 @@ impl ComputeProvider { } } - fn start_sequential(&self) -> (ComputationResult, Vec) { + fn start_sequential(&mut self) -> (ComputationResult, Vec) { let mut tree_handler = MerkleTree::new( self.input.tree_depth, self.input.zero_node.clone(), self.input.arity, ); tree_handler.compute_leaf_hashes(&self.input.ciphertexts); + self.input.leaf_hashes = tree_handler.leaf_hashes.clone(); let env = ExecutorEnv::builder() .write(&self.input) @@ -128,8 +129,8 @@ impl ComputeProvider { arity: 2, }; - let mut final_tree_handler = MerkleTree::new(final_depth, final_input.zero_node.clone(), final_input.arity); - final_input.zero_node = final_tree_handler.build_tree().zeroes[parallel_tree_depth].clone(); + let final_tree_handler = MerkleTree::new(final_depth, final_input.zero_node.clone(), final_input.arity); + final_input.zero_node = final_tree_handler.zeroes()[parallel_tree_depth].clone(); let env = ExecutorEnv::builder() .write(&final_input) @@ -169,14 +170,14 @@ mod tests { fn test_compute_provider() { let params = create_params(); let (sk, pk) = generate_keys(¶ms); - let inputs = vec![1, 1, 0]; + let inputs = vec![1, 1, 0, 1]; let ciphertexts = encrypt_inputs(&inputs, &pk, ¶ms); - let provider = ComputeProvider::new( + let mut provider = ComputeProvider::new( ciphertexts.iter().map(|c| c.to_bytes()).collect(), params.to_bytes(), - false, - None, + true, + Some(2), ); // use_parallel = false, no batch size let (result, _seal) = provider.start(); From a57a2b2fb6df0a147f876b5a47707ae791301935 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 6 Sep 2024 14:00:00 +0500 Subject: [PATCH 16/62] Update logging --- .../ciphernode/example_ciphernode_config.json | 6 +- .../ciphernode/src/bin/start_cipher_node.rs | 10 +- packages/compute_provider/methods/build.rs | 4 +- packages/evm/.gitignore | 21 +++ packages/evm/LICENSE.md | 125 ++++++++++++++ packages/evm/README.md | 1 + packages/evm/contracts/RFVoting.sol | 54 ++++++ packages/evm/deploy/deploy.ts | 18 ++ packages/evm/hardhat.config.ts | 46 +++++ packages/evm/package.json | 85 +++++++++ packages/evm/test/RFVoting.spec.ts | 25 +++ packages/evm/tsconfig.json | 22 +++ packages/risc0/contracts/Elf.sol | 24 +++ packages/server/.cargo/config.toml | 2 +- packages/server/Cargo.lock | 162 +++++++++++++++--- packages/server/Cargo.toml | 2 + packages/server/example_config.json | 6 +- packages/server/src/bin/start_rounds.rs | 28 +-- packages/server/src/cli/auth.rs | 3 +- packages/server/src/cli/mod.rs | 3 +- packages/server/src/cli/voting.rs | 38 ++-- .../server/src/enclave_server/database.rs | 5 +- packages/server/src/enclave_server/mod.rs | 28 ++- .../src/enclave_server/routes/ciphernode.rs | 69 ++++---- .../server/src/enclave_server/routes/index.rs | 11 +- .../src/enclave_server/routes/rounds.rs | 33 ++-- .../server/src/enclave_server/routes/state.rs | 12 +- .../src/enclave_server/routes/voting.rs | 14 +- packages/server/src/util.rs | 13 +- 29 files changed, 722 insertions(+), 148 deletions(-) create mode 100644 packages/evm/.gitignore create mode 100644 packages/evm/LICENSE.md create mode 100644 packages/evm/README.md create mode 100644 packages/evm/contracts/RFVoting.sol create mode 100644 packages/evm/deploy/deploy.ts create mode 100644 packages/evm/hardhat.config.ts create mode 100644 packages/evm/package.json create mode 100644 packages/evm/test/RFVoting.spec.ts create mode 100644 packages/evm/tsconfig.json create mode 100644 packages/risc0/contracts/Elf.sol diff --git a/packages/ciphernode/example_ciphernode_config.json b/packages/ciphernode/example_ciphernode_config.json index e247e19..9fe56ef 100644 --- a/packages/ciphernode/example_ciphernode_config.json +++ b/packages/ciphernode/example_ciphernode_config.json @@ -1,5 +1 @@ -{ -"ids":[22,15838], -"enclave_address": "https://enclave.gnosisguild.org", -"enclave_port": 443 -} \ No newline at end of file +{"ids":[22,15838,53153,90423],"enclave_address":"http://0.0.0.0:4000","enclave_port":80} \ No newline at end of file diff --git a/packages/ciphernode/src/bin/start_cipher_node.rs b/packages/ciphernode/src/bin/start_cipher_node.rs index 32ee1e8..13fb948 100644 --- a/packages/ciphernode/src/bin/start_cipher_node.rs +++ b/packages/ciphernode/src/bin/start_cipher_node.rs @@ -450,10 +450,12 @@ fn get_state(node_id: u32) -> (Ciphernode, String) { async fn get_votes_contract(block_start: U64, address: String, _chain_id: u32) -> Vec> { println!("Filtering contract for votes"); // chain state - let infura_key = "INFURAKEY"; - let infura_val = env::var(infura_key).unwrap(); - let mut rpc_url = "https://sepolia.infura.io/v3/".to_string(); - rpc_url.push_str(&infura_val); + // let infura_key = "INFURAKEY"; + // let infura_val = env::var(infura_key).unwrap(); + // let mut rpc_url = "https://sepolia.infura.io/v3/".to_string(); + // rpc_url.push_str(&infura_val); + + let mut rpc_url = "http://127.0.0.1:8545".to_string(); abigen!( IVOTE, diff --git a/packages/compute_provider/methods/build.rs b/packages/compute_provider/methods/build.rs index b3b84cf..d34cb57 100644 --- a/packages/compute_provider/methods/build.rs +++ b/packages/compute_provider/methods/build.rs @@ -1,6 +1,6 @@ -const SOLIDITY_IMAGE_ID_PATH: &str = "../../evm/contracts/ImageID.sol"; -const SOLIDITY_ELF_PATH: &str = "../../evm/contracts/Elf.sol"; +const SOLIDITY_IMAGE_ID_PATH: &str = "../../risc0/contracts/ImageID.sol"; +const SOLIDITY_ELF_PATH: &str = "../../risc0/contracts/Elf.sol"; fn main() { let guests = risc0_build::embed_methods(); diff --git a/packages/evm/.gitignore b/packages/evm/.gitignore new file mode 100644 index 0000000..18a269e --- /dev/null +++ b/packages/evm/.gitignore @@ -0,0 +1,21 @@ +# directories +.coverage_artifacts +.coverage_cache +.coverage_contracts +artifacts +build +cache +coverage +dist +node_modules +types +deployments + +# files +*.env +*.log +.DS_Store +.pnp.* +coverage.json +package-lock.json +yarn.lock diff --git a/packages/evm/LICENSE.md b/packages/evm/LICENSE.md new file mode 100644 index 0000000..7f9dcfa --- /dev/null +++ b/packages/evm/LICENSE.md @@ -0,0 +1,125 @@ +GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute +verbatim copies of this license document, but changing it is not allowed. + +This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU +General Public License, supplemented by the additional permissions listed below. + +0. Additional Definitions. + +As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to +version 3 of the GNU General Public License. + +"The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined +below. + +An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on +the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by +the Library. + +A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of +the Library with which the Combined Work was made is also called the "Linked Version". + +The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding +any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not +on the Linked Version. + +The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, +including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the +System Libraries of the Combined Work. + +1. Exception to Section 3 of the GNU GPL. + +You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. + +2. Conveying Modified Versions. + +If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied +by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may +convey a copy of the modified version: + +a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not +supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, +or + +b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. + +3. Object Code Incorporating Material from Library Header Files. + +The object code form of an Application may incorporate material from a header file that is part of the Library. You may +convey such object code under terms of your choice, provided that, if the incorporated material is not limited to +numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or +fewer lines in length), you do both of the following: + +a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its +use are covered by this License. + +b) Accompany the object code with a copy of the GNU GPL and this license document. + +4. Combined Works. + +You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification +of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, +if you also do each of the following: + +a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its +use are covered by this License. + +b) Accompany the Combined Work with a copy of the GNU GPL and this license document. + +c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library +among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. + +d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + +e) Provide Installation Information, but only if you would otherwise be required to provide such information under +section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified +version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked +Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and +Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner +specified by section 6 of the GNU GPL for conveying Corresponding Source.) + +5. Combined Libraries. + +You may place library facilities that are a work based on the Library side by side in a single library together with +other library facilities that are not Applications and are not covered by this License, and convey such a combined +library under terms of your choice, if you do both of the following: + +a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library +facilities, conveyed under the terms of this License. + +b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where +to find the accompanying uncombined form of the same work. + +6. Revised Versions of the GNU Lesser General Public License. + +The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time +to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new +problems or concerns. + +Each version is given a distinguishing version number. If the Library as you received it specifies that a certain +numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of +following the terms and conditions either of that published version or of any later version published by the Free +Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General +Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software +Foundation. + +If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General +Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for +you to choose that version for the Library. diff --git a/packages/evm/README.md b/packages/evm/README.md new file mode 100644 index 0000000..b04c573 --- /dev/null +++ b/packages/evm/README.md @@ -0,0 +1 @@ +# RFV Contracts \ No newline at end of file diff --git a/packages/evm/contracts/RFVoting.sol b/packages/evm/contracts/RFVoting.sol new file mode 100644 index 0000000..63a6986 --- /dev/null +++ b/packages/evm/contracts/RFVoting.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: LGPLv3 +pragma solidity ^0.8.20; + +contract RFVoting { + + mapping(address voter => bytes vote) public votes; + mapping(address validVoter => bool valid) public isValidVoter; + + string public tester = "test"; + uint256 public id = 0; + uint256 public pollNonce = 0; + + event Voted(address indexed voter, bytes vote); + + function voteEncrypted(bytes memory _encVote) public { + id++; + //votes[msg.sender] = _encVote; + emit Voted(msg.sender, _encVote); + } + + // function getVote(address id) public returns(bytes memory) { + // return votes[id]; + // } + + //Todo gatekeep modular, ie Bright ID extension + function register() public { + // write custom validation code here + isValidVoter[msg.sender] = true; + } + + function createPoll() public { + pollNonce++; + } + + function getPoll(uint256 _pollId) public { + + } + + function submitCoordintatiorPKEY(bytes memory _coordPKEY, uint256 _pollId) public { + + } + + function finalizeVote(uint256 _pollId) public { + + } + + function submitFHEResult(bytes memory _fheResult, uint256 _pollId) public { + + } + + function disputeFHEResult() public { + // reality.eth + } +} diff --git a/packages/evm/deploy/deploy.ts b/packages/evm/deploy/deploy.ts new file mode 100644 index 0000000..19deb76 --- /dev/null +++ b/packages/evm/deploy/deploy.ts @@ -0,0 +1,18 @@ +import { DeployFunction } from "hardhat-deploy/types"; +import { HardhatRuntimeEnvironment } from "hardhat/types"; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + const { deployer } = await hre.getNamedAccounts(); + const { deploy } = hre.deployments; + console.log(deployer) + const rfvoting = await deploy("RFVoting", { + from: deployer, + args: [], + log: true, + }); + + console.log(`RFVoting contract: `, rfvoting.address); +}; +export default func; +func.id = "deploy_rfvoting"; // id required to prevent reexecution +func.tags = ["RFVoting"]; \ No newline at end of file diff --git a/packages/evm/hardhat.config.ts b/packages/evm/hardhat.config.ts new file mode 100644 index 0000000..ea504fb --- /dev/null +++ b/packages/evm/hardhat.config.ts @@ -0,0 +1,46 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { config as dotenvConfig } from "dotenv"; +import "hardhat-deploy"; +import type { HardhatUserConfig } from "hardhat/config"; +import { resolve } from "path"; + +// Load environment variables from .env file +const dotenvConfigPath: string = process.env.DOTENV_CONFIG_PATH || "./.env"; +dotenvConfig({ path: resolve(__dirname, dotenvConfigPath) }); + +// Default configuration for Hardhat network +const config: HardhatUserConfig = { + defaultNetwork: "hardhat", + namedAccounts: { + deployer: 0, + }, + networks: { + hardhat: { + chainId: 31337, // Default Hardhat Network Chain ID + accounts: { + mnemonic: process.env.MNEMONIC || "test test test test test test test test test test test junk", + }, + }, + }, + paths: { + artifacts: "./artifacts", + cache: "./cache", + sources: "./contracts", + tests: "./test", + }, + solidity: { + version: "0.8.21", + settings: { + optimizer: { + enabled: true, + runs: 800, + }, + }, + }, + typechain: { + outDir: "types", + target: "ethers-v6", + }, +}; + +export default config; diff --git a/packages/evm/package.json b/packages/evm/package.json new file mode 100644 index 0000000..de41149 --- /dev/null +++ b/packages/evm/package.json @@ -0,0 +1,85 @@ +{ + "name": "@gnosisguild/GGMI", + "description": "", + "version": "1.0.0", + "author": { + "name": "gnosisguild", + "url": "https://github.com/gnosisguild" + }, + "devDependencies": { + "@nomicfoundation/hardhat-chai-matchers": "^2.0.0", + "@nomicfoundation/hardhat-ethers": "^3.0.0", + "@nomicfoundation/hardhat-network-helpers": "^1.0.6", + "@nomicfoundation/hardhat-toolbox": "^3.0.0", + "@nomicfoundation/hardhat-verify": "^1.0.0", + "@trivago/prettier-plugin-sort-imports": "^4.0.0", + "@typechain/ethers-v6": "^0.4.0", + "@typechain/hardhat": "^8.0.0", + "@types/chai": "^4.3.4", + "@types/fs-extra": "^9.0.13", + "@types/mocha": "^10.0.0", + "@types/node": "^18.11.9", + "@typescript-eslint/eslint-plugin": "^5.44.0", + "@typescript-eslint/parser": "^5.44.0", + "chai": "^4.3.7", + "cross-env": "^7.0.3", + "dotenv": "^16.0.3", + "eslint": "^8.28.0", + "eslint-config-prettier": "^8.5.0", + "ethers": "^6.4.0", + "fs-extra": "^10.1.0", + "hardhat": "^2.12.2", + "hardhat-deploy": "^0.11.29", + "hardhat-gas-reporter": "^1.0.9", + "lodash": "^4.17.21", + "mocha": "^10.1.0", + "prettier": "^2.8.4", + "prettier-plugin-solidity": "^1.1.2", + "rimraf": "^4.1.2", + "solhint": "^3.4.0", + "solhint-plugin-prettier": "^0.0.5", + "solidity-coverage": "^0.8.2", + "ts-generator": "^0.1.1", + "ts-node": "^10.9.1", + "typechain": "^8.2.0", + "typescript": "^4.9.3" + }, + "files": [ + "contracts" + ], + "keywords": [ + "blockchain", + "ethers", + "ethereum", + "hardhat", + "smart-contracts", + "solidity", + "template", + "typescript", + "typechain" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "clean": "rimraf ./artifacts ./cache ./coverage ./types ./coverage.json && yarn typechain", + "compile": "cross-env TS_NODE_TRANSPILE_ONLY=true hardhat compile", + "coverage": "hardhat coverage --solcoverjs ./.solcover.js --temp artifacts --testfiles \"test/**/*.ts\" && yarn typechain", + "deploy:contracts": "hardhat deploy", + "deploy": "hardhat deploy --network", + "lint": "yarn lint:sol && yarn lint:ts && yarn prettier:check", + "lint:sol": "solhint --max-warnings 0 \"contracts/**/*.sol\"", + "lint:ts": "eslint --ignore-path ./.eslintignore --ext .js,.ts .", + "postinstall": "DOTENV_CONFIG_PATH=./.env.example yarn typechain", + "prettier:check": "prettier --check \"**/*.{js,json,md,sol,ts,yml}\"", + "prettier:write": "prettier --write \"**/*.{js,json,md,sol,ts,yml}\"", + "task:deployGreeter": "hardhat task:deployGreeter", + "task:setGreeting": "hardhat task:setGreeting", + "test": "hardhat test", + "typechain": "cross-env TS_NODE_TRANSPILE_ONLY=true hardhat typechain" + }, + "dependencies": { + "@openzeppelin/contracts": "^5.0.0", + "hardhat-etherscan": "^1.0.1" + } +} diff --git a/packages/evm/test/RFVoting.spec.ts b/packages/evm/test/RFVoting.spec.ts new file mode 100644 index 0000000..841045e --- /dev/null +++ b/packages/evm/test/RFVoting.spec.ts @@ -0,0 +1,25 @@ +import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; +import { expect } from "chai"; +import { ethers } from "hardhat"; + +const ADDRESS_ONE = "0x0000000000000000000000000000000000000001"; + +describe("RFVoting", () => { + async function deployContracts() { + const [deployer, sender, receiver] = await ethers.getSigners(); + const rfvotingFactory = await ethers.getContractFactory("RFVoting"); + const rfvotingContract = await rfvotingFactory + .connect(deployer) + .deploy(); + + return { deployer, sender, receiver, rfvotingContract }; + } + + describe("test", async () => { + it("testing", async () => { + const { deployer, rfvotingContract } = await loadFixture(deployContracts); + + expect(await rfvotingContract.tester()).to.equal("test"); + }); + }); +}); diff --git a/packages/evm/tsconfig.json b/packages/evm/tsconfig.json new file mode 100644 index 0000000..734e21a --- /dev/null +++ b/packages/evm/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDecoratorMetadata": true, + "esModuleInterop": true, + "experimentalDecorators": true, + "forceConsistentCasingInFileNames": true, + "lib": ["es2020"], + "module": "commonjs", + "moduleResolution": "node", + "noImplicitAny": true, + "removeComments": true, + "resolveJsonModule": true, + "sourceMap": true, + "strict": true, + "target": "es2020" + }, + "exclude": ["node_modules"], + "files": ["./hardhat.config.ts"], + "include": ["src/**/*", "tasks/**/*", "test/**/*", "deploy/**/*", "types/"] +} diff --git a/packages/risc0/contracts/Elf.sol b/packages/risc0/contracts/Elf.sol new file mode 100644 index 0000000..6ee1ab0 --- /dev/null +++ b/packages/risc0/contracts/Elf.sol @@ -0,0 +1,24 @@ +// Copyright 2024 RISC Zero, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +// This file is automatically generated + +pragma solidity ^0.8.20; + +library Elf { + string public constant COMPUTE_PROVIDER_PATH = + "/home/ace/main/gnosis/CRISP/packages/server/target/riscv-guest/riscv32im-risc0-zkvm-elf/release/compute_provider"; +} diff --git a/packages/server/.cargo/config.toml b/packages/server/.cargo/config.toml index 0c51459..3030934 100644 --- a/packages/server/.cargo/config.toml +++ b/packages/server/.cargo/config.toml @@ -1,3 +1,3 @@ [env] INFURAKEY = "default val" -PRIVATEKEY = "default val" \ No newline at end of file +PRIVATEKEY = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" \ No newline at end of file diff --git a/packages/server/Cargo.lock b/packages/server/Cargo.lock index 6ecccb9..0bac6e2 100644 --- a/packages/server/Cargo.lock +++ b/packages/server/Cargo.lock @@ -165,6 +165,55 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.83" @@ -494,7 +543,7 @@ dependencies = [ "cfg-if", "concurrent-queue", "futures-lite 1.13.0", - "log 0.4.21", + "log 0.4.22", "parking", "polling 2.8.0", "rustix 0.37.27", @@ -560,7 +609,7 @@ dependencies = [ "futures-lite 1.13.0", "gloo-timers", "kv-log-macro", - "log 0.4.21", + "log 0.4.22", "memchr", "once_cell", "pin-project-lite", @@ -996,15 +1045,28 @@ dependencies = [ "thiserror", ] +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + [[package]] name = "compute-provider-core" version = "0.1.0" dependencies = [ + "ark-bn254", + "ark-ff 0.4.2", "fhe", "fhe-traits", + "hex", + "light-poseidon", + "num-bigint", + "num-traits", "risc0-zkp", "risc0-zkvm", "serde", + "sha3", "zk-kit-imt", ] @@ -1017,6 +1079,7 @@ dependencies = [ "fhe-traits", "methods", "rand 0.8.5", + "rayon", "risc0-build-ethereum", "risc0-ethereum-contracts", "risc0-zkvm", @@ -1384,7 +1447,7 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" dependencies = [ - "log 0.4.21", + "log 0.4.22", ] [[package]] @@ -1412,7 +1475,7 @@ dependencies = [ "bytes", "hex", "k256", - "log 0.4.21", + "log 0.4.22", "rand 0.8.5", "rlp", "serde", @@ -1420,6 +1483,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "env_filter" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +dependencies = [ + "log 0.4.22", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log 0.4.22", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -2415,6 +2501,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "0.10.16" @@ -2703,9 +2795,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24b02b8856c7f14e443c483e802cf0ce693f3bec19f49d2c9a242b18f88c9b70" dependencies = [ "iron", - "log 0.4.21", + "log 0.4.22", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.10.5" @@ -2825,7 +2923,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" dependencies = [ - "log 0.4.21", + "log 0.4.22", ] [[package]] @@ -2895,6 +2993,18 @@ dependencies = [ "libc", ] +[[package]] +name = "light-poseidon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" +dependencies = [ + "ark-bn254", + "ark-ff 0.4.2", + "num-bigint", + "thiserror", +] + [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -2923,14 +3033,14 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" dependencies = [ - "log 0.4.21", + "log 0.4.22", ] [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" dependencies = [ "value-bag", ] @@ -3062,7 +3172,7 @@ checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ "lazy_static", "libc", - "log 0.4.21", + "log 0.4.22", "openssl", "openssl-probe", "openssl-sys", @@ -3646,7 +3756,7 @@ dependencies = [ "cfg-if", "concurrent-queue", "libc", - "log 0.4.21", + "log 0.4.22", "pin-project-lite", "windows-sys 0.48.0", ] @@ -3808,7 +3918,7 @@ dependencies = [ "bytes", "heck 0.5.0", "itertools 0.12.1", - "log 0.4.21", + "log 0.4.22", "multimap", "once_cell", "petgraph", @@ -4182,7 +4292,7 @@ dependencies = [ "hyper-rustls 0.24.2", "ipnet", "js-sys", - "log 0.4.21", + "log 0.4.22", "mime 0.3.17", "once_cell", "percent-encoding 2.3.1", @@ -4224,7 +4334,7 @@ dependencies = [ "hyper-util", "ipnet", "js-sys", - "log 0.4.21", + "log 0.4.22", "mime 0.3.17", "once_cell", "percent-encoding 2.3.1", @@ -4271,6 +4381,7 @@ dependencies = [ "compute-provider-host", "console", "dialoguer", + "env_logger", "ethers", "fhe", "fhe-traits", @@ -4285,6 +4396,7 @@ dependencies = [ "iron", "iron-cors", "jwt", + "log 0.4.22", "once_cell", "rand 0.8.5", "rand_chacha 0.3.1", @@ -4661,7 +4773,7 @@ version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ - "log 0.4.21", + "log 0.4.22", "ring 0.17.8", "rustls-webpki 0.101.7", "sct", @@ -5085,7 +5197,7 @@ dependencies = [ "fs2", "fxhash", "libc", - "log 0.4.21", + "log 0.4.22", "parking_lot 0.11.2", ] @@ -5478,7 +5590,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", - "log 0.4.21", + "log 0.4.22", "rustls 0.21.12", "tokio", "tokio-rustls 0.24.1", @@ -5600,7 +5712,7 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "log 0.4.21", + "log 0.4.22", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -5643,7 +5755,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ - "log 0.4.21", + "log 0.4.22", "once_cell", "tracing-core", ] @@ -5698,7 +5810,7 @@ dependencies = [ "data-encoding", "http 0.2.12", "httparse", - "log 0.4.21", + "log 0.4.22", "rand 0.8.5", "rustls 0.21.12", "sha1", @@ -5843,6 +5955,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "0.8.2" @@ -5946,7 +6064,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", - "log 0.4.21", + "log 0.4.22", "once_cell", "proc-macro2", "quote", @@ -6259,7 +6377,7 @@ dependencies = [ "async_io_stream", "futures", "js-sys", - "log 0.4.21", + "log 0.4.22", "pharos", "rustc_version 0.4.0", "send_wrapper 0.6.0", diff --git a/packages/server/Cargo.toml b/packages/server/Cargo.toml index dccb664..f6c9cdc 100644 --- a/packages/server/Cargo.toml +++ b/packages/server/Cargo.toml @@ -40,3 +40,5 @@ headers = "0.4.0" jwt = "0.16.0" hmac = "0.12.1" sha2 = "0.10.8" +log = "0.4.22" +env_logger = "0.11.5" \ No newline at end of file diff --git a/packages/server/example_config.json b/packages/server/example_config.json index 3e6d407..ba205f7 100644 --- a/packages/server/example_config.json +++ b/packages/server/example_config.json @@ -1,9 +1,9 @@ { "round_id": 0, "poll_length": 600, - "chain_id": 11155111, - "voting_address": "0x51Ec8aB3e53146134052444693Ab3Ec53663a12B", + "chain_id": 31337, + "voting_address": "0x5FbDB2315678afecb367f032d93F642f64180aa3", "ciphernode_count": 2, - "enclave_address": "https://enclave.gnosisguild.org", + "enclave_address": "http://0.0.0.0:4000", "authentication_id": "tester" } diff --git a/packages/server/src/bin/start_rounds.rs b/packages/server/src/bin/start_rounds.rs index bd5e1ea..d14464f 100644 --- a/packages/server/src/bin/start_rounds.rs +++ b/packages/server/src/bin/start_rounds.rs @@ -105,9 +105,9 @@ async fn main() -> Result<(), Box> { loop { print!("{esc}[2J{esc}[1;1H", esc = 27 as char); - println!("Starting new CRISP round!"); + info!("Starting new CRISP round!"); - println!("Reading proposal details from config."); + info!("Reading proposal details from config."); let path = env::current_dir().unwrap(); let mut pathst = path.display().to_string(); pathst.push_str("/example_config.json"); @@ -115,13 +115,13 @@ async fn main() -> Result<(), Box> { let mut data = String::new(); file.read_to_string(&mut data).unwrap(); let config: CrispConfig = serde_json::from_str(&data).expect("JSON was not well-formatted"); - println!("round id: {:?}", config.round_id); // get new round id from current id in server - println!("poll length {:?}", config.poll_length); - println!("chain id: {:?}", config.chain_id); - println!("voting contract: {:?}", config.voting_address); - println!("ciphernode count: {:?}", config.ciphernode_count); + info!("round id: {:?}", config.round_id); // get new round id from current id in server + info!("poll length {:?}", config.poll_length); + info!("chain id: {:?}", config.chain_id); + info!("voting contract: {:?}", config.voting_address); + info!("ciphernode count: {:?}", config.ciphernode_count); - println!("Initializing Keyshare nodes..."); + info!("Initializing Keyshare nodes..."); let response_id = JsonRequestGetRounds { response: "Test".to_string() }; let _out = serde_json::to_string(&response_id).unwrap(); @@ -129,7 +129,7 @@ async fn main() -> Result<(), Box> { url_id.push_str("/get_rounds"); //let token = Authorization::bearer("some-opaque-token").unwrap(); - //println!("bearer token {:?}", token.token()); + //info!("bearer token {:?}", token.token()); //todo: add auth field to config file to get bearer token let req = Request::builder() .method(Method::GET) @@ -138,12 +138,12 @@ async fn main() -> Result<(), Box> { let resp = client_get.request(req).await?; - println!("Response status: {}", resp.status()); + info!("Response status: {}", resp.status()); let body_bytes = resp.collect().await?.to_bytes(); let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); let count: RoundCount = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); - println!("Server Round Count: {:?}", count.round_count); + info!("Server Round Count: {:?}", count.round_count); // TODO: get secret from env var // let key: Hmac = Hmac::new_from_slice(b"some-secret")?; @@ -152,7 +152,7 @@ async fn main() -> Result<(), Box> { // let mut bearer_str = "Bearer ".to_string(); // let token_str = claims.sign_with_key(&key)?; // bearer_str.push_str(&token_str); - // println!("{:?}", bearer_str); + // info!("{:?}", bearer_str); let round_id = count.round_count + 1; let response = CrispConfig { @@ -175,7 +175,7 @@ async fn main() -> Result<(), Box> { let mut resp = client.request(req).await?; - println!("Response status: {}", resp.status()); + info!("Response status: {}", resp.status()); while let Some(next) = resp.frame().await { let frame = next?; @@ -183,7 +183,7 @@ async fn main() -> Result<(), Box> { io::stdout().write_all(chunk).await?; } } - println!("Round Initialized."); + info!("Round Initialized."); let next_round_start = config.poll_length + 60; let seconds = time::Duration::from_secs(next_round_start as u64); diff --git a/packages/server/src/cli/auth.rs b/packages/server/src/cli/auth.rs index c17ed8f..33c10ce 100644 --- a/packages/server/src/cli/auth.rs +++ b/packages/server/src/cli/auth.rs @@ -1,6 +1,7 @@ use hyper::{Request, Method}; use http_body_util::BodyExt; use serde::{Deserialize, Serialize}; +use log::info; use crate::cli::HyperClientPost; #[derive(Debug, Deserialize, Serialize)] @@ -33,6 +34,6 @@ pub async fn authenticate_user(config: &super::CrispConfig, client: &HyperClient let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); let auth_res: AuthenticationResponse = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); - println!("Authentication response {:?}", auth_res); + info!("Authentication response {:?}", auth_res); Ok(auth_res) } diff --git a/packages/server/src/cli/mod.rs b/packages/server/src/cli/mod.rs index 11f249f..5366222 100644 --- a/packages/server/src/cli/mod.rs +++ b/packages/server/src/cli/mod.rs @@ -9,6 +9,7 @@ use std::env; use std::fs::File; use std::io::Read; use http_body_util::Empty; +use log::info; use auth::{authenticate_user, AuthenticationResponse}; use voting::{initialize_crisp_round, participate_in_existing_round}; @@ -82,7 +83,7 @@ pub async fn run_cli() -> Result<(), Box> { participate_in_existing_round(&config, &client, &auth_res).await?; } } else { - println!("Check back soon!"); + info!("Check back soon!"); std::process::exit(1); } diff --git a/packages/server/src/cli/voting.rs b/packages/server/src/cli/voting.rs index 2597200..6b400d8 100644 --- a/packages/server/src/cli/voting.rs +++ b/packages/server/src/cli/voting.rs @@ -6,6 +6,7 @@ use hyper::{Method, Request}; use serde::{Deserialize, Serialize}; use std::{thread, time}; use tokio::io::{self, AsyncWriteExt as _}; +use log::info; use crate::cli::AuthenticationResponse; use crate::cli::{HyperClientGet, HyperClientPost}; @@ -49,9 +50,9 @@ pub async fn initialize_crisp_round( client_get: &HyperClientGet, client: &HyperClientPost, ) -> Result<(), Box> { - println!("Starting new CRISP round!"); + info!("Starting new CRISP round!"); - println!("Initializing Keyshare nodes..."); + info!("Initializing Keyshare nodes..."); let response_id = JsonRequestGetRounds { response: "Test".to_string(), @@ -67,12 +68,12 @@ pub async fn initialize_crisp_round( let resp = client_get.request(req).await?; - println!("Response status: {}", resp.status()); + info!("Response status: {}", resp.status()); let body_bytes = resp.collect().await?.to_bytes(); let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); let count: RoundCount = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); - println!("Server Round Count: {:?}", count.round_count); + info!("Server Round Count: {:?}", count.round_count); let round_id = count.round_count + 1; let response = super::CrispConfig { @@ -95,7 +96,7 @@ pub async fn initialize_crisp_round( let mut resp = client.request(req).await?; - println!("Response status: {}", resp.status()); + info!("Response status: {}", resp.status()); while let Some(next) = resp.frame().await { let frame = next?; @@ -103,11 +104,11 @@ pub async fn initialize_crisp_round( tokio::io::stdout().write_all(chunk).await?; } } - println!("Round Initialized."); - println!("Gathering Keyshare nodes for execution environment..."); + info!("Round Initialized."); + info!("Gathering Keyshare nodes for execution environment..."); let three_seconds = time::Duration::from_millis(1000); thread::sleep(three_seconds); - println!("\nYou can now vote Encrypted with Round ID: {:?}", round_id); + info!("\nYou can now vote Encrypted with Round ID: {:?}", round_id); Ok(()) } @@ -121,7 +122,7 @@ pub async fn participate_in_existing_round( .with_prompt("Enter CRISP round ID.") .interact_text() .unwrap(); - println!("Voting state Initialized"); + info!("Voting state Initialized"); // get public encrypt key let v: Vec = vec![0]; @@ -136,15 +137,16 @@ pub async fn participate_in_existing_round( let resp = client.request(req).await?; - println!("Response status: {}", resp.status()); + info!("Response status: {}", resp.status()); let body_bytes = resp.collect().await?.to_bytes(); let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); let pk_res: PKRequest = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); - println!( + info!( "Shared Public Key for CRISP round {:?} collected.", pk_res.round_id ); + info!("Public Key: {:?}", pk_res.pk_bytes); let degree = 4096; let plaintext_modulus: u64 = 4096; @@ -172,19 +174,19 @@ pub async fn participate_in_existing_round( let mut vote_choice: u64 = 0; if selection_3 == 0 { - println!("Exiting voting system. You may choose to vote later."); + info!("Exiting voting system. You may choose to vote later."); return Ok(()); } else if selection_3 == 1 { vote_choice = 1; } else if selection_3 == 2 { vote_choice = 0; } - println!("Encrypting vote."); + info!("Encrypting vote."); let votes: Vec = [vote_choice].to_vec(); let pt = Plaintext::try_encode(&[votes[0]], Encoding::poly(), ¶ms)?; let ct = pk_deserialized.try_encrypt(&pt, &mut thread_rng())?; - println!("Vote encrypted."); - println!("Calling voting contract with encrypted vote."); + info!("Vote encrypted."); + info!("Calling voting contract with encrypted vote."); let request_contract = EncryptedVote { round_id: input_crisp_id, @@ -198,14 +200,14 @@ pub async fn participate_in_existing_round( let resp = client.request(req).await?; - println!("Response status: {}", resp.status()); + info!("Response status: {}", resp.status()); let body_bytes = resp.collect().await?.to_bytes(); let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); let contract_res: JsonResponseTxHash = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); - println!("Contract call: {:?}", contract_res.response); - println!("TxHash is {:?}", contract_res.tx_hash); + info!("Contract call: {:?}", contract_res.response); + info!("TxHash is {:?}", contract_res.tx_hash); Ok(()) } diff --git a/packages/server/src/enclave_server/database.rs b/packages/server/src/enclave_server/database.rs index aa7f3ad..add1feb 100644 --- a/packages/server/src/enclave_server/database.rs +++ b/packages/server/src/enclave_server/database.rs @@ -3,6 +3,7 @@ use std::{env, str}; use once_cell::sync::Lazy; use sled::Db; use rand::Rng; +use log::info; use super::models::Round; pub static GLOBAL_DB: Lazy = Lazy::new(|| { @@ -15,7 +16,7 @@ pub static GLOBAL_DB: Lazy = Lazy::new(|| { pub fn get_state(round_id: u32) -> (Round, String) { let mut round_key = round_id.to_string(); round_key.push_str("-storage"); - println!("Database key is {:?}", round_key); + info!("Database key is {:?}", round_key); let state_out = GLOBAL_DB.get(round_key.clone()).unwrap().unwrap(); let state_out_str = str::from_utf8(&state_out).unwrap(); let state_out_struct: Round = serde_json::from_str(&state_out_str).unwrap(); @@ -26,7 +27,7 @@ pub fn get_round_count() -> u32 { let round_key = "round_count"; let round_db = GLOBAL_DB.get(round_key).unwrap(); if round_db == None { - println!("initializing first round in db"); + info!("initializing first round in db"); GLOBAL_DB.insert(round_key, b"0".to_vec()).unwrap(); } let round_str = std::str::from_utf8(round_db.unwrap().as_ref()).unwrap().to_string(); diff --git a/packages/server/src/enclave_server/mod.rs b/packages/server/src/enclave_server/mod.rs index 6e6c39c..bcb3d25 100644 --- a/packages/server/src/enclave_server/mod.rs +++ b/packages/server/src/enclave_server/mod.rs @@ -7,13 +7,39 @@ use iron::Chain; use router::Router; use iron_cors::CorsMiddleware; +use env_logger::{Builder, Target}; +use log::LevelFilter; +use log::info; +use std::io::Write; // Use `std::io::Write` for writing to the buffer + +fn init_logger() { + let mut builder = Builder::new(); + builder + .target(Target::Stdout) // Set target to stdout + .filter(None, LevelFilter::Info) // Set log level to Info + .format(|buf, record| { + writeln!( + buf, // Use `writeln!` correctly with the `buf` + "[{}:{}] - {}", + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.args() + ) + }) + .init(); +} + + #[tokio::main] pub async fn start_server() -> Result<(), Box> { + init_logger(); + + info!("Starting server..."); let mut router = Router::new(); routes::setup_routes(&mut router); let cors_middleware = CorsMiddleware::with_allow_any(); - println!("Allowed origin hosts: *"); + info!("Allowed origin hosts: *"); let mut chain = Chain::new(router); chain.link_around(cors_middleware); diff --git a/packages/server/src/enclave_server/routes/ciphernode.rs b/packages/server/src/enclave_server/routes/ciphernode.rs index e1b00f2..b3d3577 100644 --- a/packages/server/src/enclave_server/routes/ciphernode.rs +++ b/packages/server/src/enclave_server/routes/ciphernode.rs @@ -12,6 +12,7 @@ use iron::status; use iron::mime::Mime; use router::Router; use std::io::Read; +use log::info; use crate::enclave_server::models::{Round, Ciphernode, JsonResponse, JsonRequest, RegisterNodeResponse, SKSShareRequest, SKSSharePoll, SKSShareResponse, PKShareCount, PKRequest, GetCiphernode, GetEligibilityRequest, CRPRequest}; use crate::enclave_server::database::{GLOBAL_DB, get_state, pick_response}; @@ -36,9 +37,9 @@ async fn register_ciphernode(req: &mut Request) -> IronResult { // we're expecting the POST to match the format of our JsonRequest struct let incoming: JsonRequest = serde_json::from_str(&payload).unwrap(); - println!("{:?}", incoming.response); - println!("ID: {:?}", incoming.id); - println!("Round ID: {:?}", incoming.round_id); + info!("{:?}", incoming.response); + info!("ID: {:?}", incoming.id); + info!("Round ID: {:?}", incoming.round_id); let (mut state, key) = get_state(incoming.round_id); @@ -54,13 +55,13 @@ async fn register_ciphernode(req: &mut Request) -> IronResult { let state_bytes = state_str.into_bytes(); GLOBAL_DB.insert(key, state_bytes).unwrap(); - println!("pk share store for node id {:?}", incoming.id); - println!("ciphernode count {:?}", state.ciphernode_count); - println!("ciphernode total {:?}", state.ciphernode_total); - println!("pk share count {:?}", state.pk_share_count); + info!("pk share store for node id {:?}", incoming.id); + info!("ciphernode count {:?}", state.ciphernode_count); + info!("ciphernode total {:?}", state.ciphernode_total); + info!("pk share count {:?}", state.pk_share_count); if state.ciphernode_count == state.ciphernode_total { - println!("All shares received"); + info!("All shares received"); let _ = aggregate_pk_shares(incoming.round_id).await; } @@ -82,14 +83,14 @@ fn register_sks_share(req: &mut Request) -> IronResult { // we're expecting the POST to match the format of our JsonRequest struct let incoming: SKSShareRequest = serde_json::from_str(&payload).unwrap(); - println!("{:?}", incoming.response); - println!("Index: {:?}", incoming.index); // cipher node id (based on first upload of pk share) - println!("Round ID: {:?}", incoming.round_id); + info!("{:?}", incoming.response); + info!("Index: {:?}", incoming.index); // cipher node id (based on first upload of pk share) + info!("Round ID: {:?}", incoming.round_id); let mut round_key = incoming.round_id.to_string(); round_key.push_str("-storage"); - println!("Database key is {:?}", round_key); + info!("Database key is {:?}", round_key); let state_out = GLOBAL_DB.get(round_key.clone()).unwrap().unwrap(); let state_out_str = str::from_utf8(&state_out).unwrap(); @@ -101,11 +102,11 @@ fn register_sks_share(req: &mut Request) -> IronResult { let state_str = serde_json::to_string(&state_out_struct).unwrap(); let state_bytes = state_str.into_bytes(); GLOBAL_DB.insert(round_key, state_bytes).unwrap(); - println!("sks share stored for node index {:?}", incoming.index); + info!("sks share stored for node index {:?}", incoming.index); // toso get share threshold from client config if state_out_struct.sks_share_count == state_out_struct.ciphernode_total { - println!("All sks shares received"); + info!("All sks shares received"); //aggregate_pk_shares(incoming.round_id).await; // TODO: maybe notify cipher nodes } @@ -134,9 +135,9 @@ fn get_sks_shares(req: &mut Request) -> IronResult { // toso get share threshold from client config if state.sks_share_count == state.ciphernode_total { - println!("All sks shares received... sending to cipher nodes"); + info!("All sks shares received... sending to cipher nodes"); for i in 1..state.ciphernode_total + 1 { - println!("reading share {:?}", i); + info!("reading share {:?}", i); shares.push(state.ciphernodes[i as usize].sks_share.clone()); } let response = SKSShareResponse { @@ -149,7 +150,7 @@ fn get_sks_shares(req: &mut Request) -> IronResult { let state_bytes = state_str.into_bytes(); GLOBAL_DB.insert(key, state_bytes).unwrap(); let out = serde_json::to_string(&response).unwrap(); - println!("get rounds hit"); + info!("get rounds hit"); let content_type = "application/json".parse::().unwrap(); Ok(Response::with((content_type, status::Ok, out))) @@ -160,7 +161,7 @@ fn get_sks_shares(req: &mut Request) -> IronResult { sks_shares: shares, }; let out = serde_json::to_string(&response).unwrap(); - println!("get rounds hit"); + info!("get rounds hit"); let content_type = "application/json".parse::().unwrap(); Ok(Response::with((content_type, status::Ok, out))) @@ -172,7 +173,7 @@ fn get_crp_by_round(req: &mut Request) -> IronResult { // read the POST body req.body.read_to_string(&mut payload).unwrap(); let mut incoming: CRPRequest = serde_json::from_str(&payload).unwrap(); - println!("Request crp for round {:?}", incoming.round_id); + info!("Request crp for round {:?}", incoming.round_id); let (state, _key) = get_state(incoming.round_id); incoming.crp_bytes = state.crp; @@ -193,7 +194,7 @@ fn get_pk_by_round(req: &mut Request) -> IronResult { let out = serde_json::to_string(&incoming).unwrap(); let content_type = "application/json".parse::().unwrap(); - println!("Request for round {:?} public key", incoming.round_id); + info!("Request for round {:?} public key", incoming.round_id); Ok(Response::with((content_type, status::Ok, out))) } @@ -217,14 +218,14 @@ fn get_round_eligibility(req: &mut Request) -> IronResult { // read the POST body req.body.read_to_string(&mut payload).unwrap(); let mut incoming: GetEligibilityRequest = serde_json::from_str(&payload).unwrap(); - println!("Request node elegibility for round {:?}", incoming.round_id); + info!("Request node elegibility for round {:?}", incoming.round_id); let (state, _key) = get_state(incoming.round_id); for i in 1..state.ciphernodes.len() { - println!("checking ciphernode {:?}", i); - println!("server db id {:?}", state.ciphernodes[i as usize].id); - println!("incoming request id {:?}", incoming.node_id); + info!("checking ciphernode {:?}", i); + info!("server db id {:?}", state.ciphernodes[i as usize].id); + info!("incoming request id {:?}", incoming.node_id); if state.ciphernodes[i as usize].id == incoming.node_id { incoming.is_eligible = true; incoming.reason = "Previously Registered".to_string(); @@ -260,7 +261,7 @@ fn get_node_by_round(req: &mut Request) -> IronResult { // read the POST body req.body.read_to_string(&mut payload).unwrap(); let incoming: GetCiphernode = serde_json::from_str(&payload).unwrap(); - println!("Request node data for round {:?}", incoming.round_id); + info!("Request node data for round {:?}", incoming.round_id); let (state, _key) = get_state(incoming.round_id); let mut cnode = Ciphernode { @@ -299,7 +300,7 @@ fn get_node_by_round(req: &mut Request) -> IronResult { async fn aggregate_pk_shares(round_id: u32) -> Result<(), Box> { - println!("aggregating validator keyshare"); + info!("aggregating validator keyshare"); let degree = 4096; let plaintext_modulus: u64 = 4096; @@ -320,14 +321,14 @@ async fn aggregate_pk_shares(round_id: u32) -> Result<(), Box Result<(), Box = Vec::new(); for i in 1..state.ciphernode_total + 1 { // todo fix init code that causes offset // read in pk_shares from storage - println!("Aggregating PKShare... id {}", i); + info!("Aggregating PKShare... id {}", i); let data_des = PublicKeyShare::deserialize(&state.ciphernodes[i as usize].pk_share, ¶ms, crp.clone()).unwrap(); // let pk_share = PublicKeyShare::new(&sk_share, crp.clone(), &mut thread_rng())?; parties.push(Party { pk_share: data_des }); @@ -353,13 +354,13 @@ async fn aggregate_pk_shares(round_id: u32) -> Result<(), Box IronResult { response: pick_response(), }; let out = serde_json::to_string(&response).unwrap(); - println!("index handler hit"); + info!("index handler hit"); let content_type = "application/json".parse::().unwrap(); Ok(Response::with((content_type, status::Ok, out))) } @@ -42,7 +43,7 @@ fn authentication_login(req: &mut Request) -> IronResult { // read the POST body req.body.read_to_string(&mut payload).unwrap(); let incoming: AuthenticationLogin = serde_json::from_str(&payload).unwrap(); - println!("Twitter Login Request"); + info!("Twitter Login Request"); // hmac let hmac_key: Hmac = Hmac::new_from_slice(b"some-secret").unwrap(); @@ -57,7 +58,7 @@ fn authentication_login(req: &mut Request) -> IronResult { let mut jwt_token = "".to_string(); if authsdb == None { - println!("initializing first auth in db"); + info!("initializing first auth in db"); // hmac let auth_struct = AuthenticationDB { jwt_tokens: vec![token_str.clone()], @@ -75,13 +76,13 @@ fn authentication_login(req: &mut Request) -> IronResult { for i in 0..authsdb_out_struct.jwt_tokens.len() { if authsdb_out_struct.jwt_tokens[i as usize] == token_str { - println!("Found previous login."); + info!("Found previous login."); response_str = "Already Authorized".to_string(); } }; if response_str != "Already Authorized" { - println!("Inserting new login to db."); + info!("Inserting new login to db."); authsdb_out_struct.jwt_tokens.push(token_str.clone()); let authsdb_str = serde_json::to_string(&authsdb_out_struct).unwrap(); let authsdb_bytes = authsdb_str.into_bytes(); diff --git a/packages/server/src/enclave_server/routes/rounds.rs b/packages/server/src/enclave_server/routes/rounds.rs index 15b8971..62c1d54 100644 --- a/packages/server/src/enclave_server/routes/rounds.rs +++ b/packages/server/src/enclave_server/routes/rounds.rs @@ -8,6 +8,7 @@ use rand::thread_rng; use router::Router; use std::env; use std::io::Read; +use log::info; use ethers::{ providers::{Http, Middleware, Provider}, @@ -40,21 +41,21 @@ pub fn setup_routes(router: &mut Router) { fn get_rounds(_req: &mut Request) -> IronResult { //let test = _req.headers.get::().unwrap(); - //println!("content_type: {:?}", test); + //info!("content_type: {:?}", test); // let test3 = _req.headers.get::>().unwrap(); - // println!("auth: {:?}", test3.token); + // info!("auth: {:?}", test3.token); // let key: Hmac = Hmac::new_from_slice(b"some-secret").unwrap(); // let claims: BTreeMap = test3.token.verify_with_key(&key).unwrap(); - // println!("decoded hmac {:?}", claims); + // info!("decoded hmac {:?}", claims); //let test2 = _req.headers.get::(); - //println!("user agent: {:?}", test2); + //info!("user agent: {:?}", test2); let key = "round_count"; let mut round = GLOBAL_DB.get(key).unwrap(); if round == None { - println!("initializing first round in db"); + info!("initializing first round in db"); GLOBAL_DB.insert(key, b"0".to_vec()).unwrap(); round = GLOBAL_DB.get(key).unwrap(); } @@ -66,10 +67,10 @@ fn get_rounds(_req: &mut Request) -> IronResult { let count = RoundCount { round_count: round_int, }; - println!("round_count: {:?}", count.round_count); + info!("round_count: {:?}", count.round_count); let out = serde_json::to_string(&count).unwrap(); - println!("get rounds hit"); + info!("get rounds hit"); let content_type = "application/json".parse::().unwrap(); Ok(Response::with((content_type, status::Ok, out))) @@ -81,7 +82,7 @@ async fn init_crisp_round(req: &mut Request) -> IronResult { // if auth.token != env { // } - println!("generating round crp"); + info!("generating round crp"); let infura_val = env!("INFURAKEY"); let mut rpc_url = "https://sepolia.infura.io/v3/".to_string(); @@ -114,15 +115,15 @@ async fn init_crisp_round(req: &mut Request) -> IronResult { // we're expecting the POST to match the format of our JsonRequest struct let incoming: CrispConfig = serde_json::from_str(&payload).unwrap(); - println!("ID: {:?}", incoming.round_id); // TODO: check that client sent the expected next round_id - println!("Address: {:?}", incoming.voting_address); + info!("ID: {:?}", incoming.round_id); // TODO: check that client sent the expected next round_id + info!("Address: {:?}", incoming.voting_address); // -------------- let key = "round_count"; //db.remove(key)?; let round = GLOBAL_DB.get(key).unwrap(); if round == None { - println!("initializing first round in db"); + info!("initializing first round in db"); GLOBAL_DB.insert(key, b"0".to_vec()).unwrap(); } let round_key = std::str::from_utf8(round.unwrap().as_ref()) @@ -132,14 +133,14 @@ async fn init_crisp_round(req: &mut Request) -> IronResult { round_int = round_int + 1; let mut inc_round_key = round_int.to_string(); inc_round_key.push_str("-storage"); - println!( + info!( "Database key is {:?} and round int is {:?}", inc_round_key, round_int ); let init_time = Utc::now(); let timestamp = init_time.timestamp(); - println!("timestamp {:?}", timestamp); + info!("timestamp {:?}", timestamp); let (emoji1, emoji2) = generate_emoji(); @@ -192,7 +193,7 @@ fn get_start_time_by_round(req: &mut Request) -> IronResult { // read the POST body req.body.read_to_string(&mut payload).unwrap(); let mut incoming: TimestampRequest = serde_json::from_str(&payload).unwrap(); - println!("Request start time for round {:?}", incoming.round_id); + info!("Request start time for round {:?}", incoming.round_id); let (state, _key) = get_state(incoming.round_id); incoming.timestamp = state.start_time; @@ -207,7 +208,7 @@ fn get_poll_length_by_round(req: &mut Request) -> IronResult { // read the POST body req.body.read_to_string(&mut payload).unwrap(); let mut incoming: PollLengthRequest = serde_json::from_str(&payload).unwrap(); - println!("Request poll length for round {:?}", incoming.round_id); + info!("Request poll length for round {:?}", incoming.round_id); let (state, _key) = get_state(incoming.round_id); incoming.poll_length = state.poll_length; @@ -222,7 +223,7 @@ fn report_tally(req: &mut Request) -> IronResult { // read the POST body req.body.read_to_string(&mut payload).unwrap(); let incoming: ReportTallyRequest = serde_json::from_str(&payload).unwrap(); - println!("Request report tally for round {:?}", incoming.round_id); + info!("Request report tally for round {:?}", incoming.round_id); let (mut state, key) = get_state(incoming.round_id); if state.votes_option_1 == 0 && state.votes_option_2 == 0 { diff --git a/packages/server/src/enclave_server/routes/state.rs b/packages/server/src/enclave_server/routes/state.rs index bd49a6c..09203bd 100644 --- a/packages/server/src/enclave_server/routes/state.rs +++ b/packages/server/src/enclave_server/routes/state.rs @@ -3,7 +3,7 @@ use iron::status; use iron::mime::Mime; use router::Router; use std::io::Read; - +use log::info; use crate::enclave_server::models::{GetRoundRequest, WebResultRequest, AllWebStates, StateLite, StateWeb}; use crate::enclave_server::database::{get_state, get_round_count}; @@ -22,7 +22,7 @@ fn get_web_result(req: &mut Request) -> IronResult { // read the POST body req.body.read_to_string(&mut payload).unwrap(); let incoming: GetRoundRequest = serde_json::from_str(&payload).unwrap(); - println!("Request web state for round {:?}", incoming.round_id); + info!("Request web state for round {:?}", incoming.round_id); let (state, _key) = get_state(incoming.round_id); @@ -43,7 +43,7 @@ fn get_web_result(req: &mut Request) -> IronResult { } fn get_web_result_all(_req: &mut Request) -> IronResult { - println!("Request all web state."); + info!("Request all web state."); let round_count = get_round_count(); let mut states: Vec = Vec::with_capacity(round_count as usize); @@ -76,7 +76,7 @@ fn get_round_state(req: &mut Request) -> IronResult { // read the POST body req.body.read_to_string(&mut payload).unwrap(); let incoming: GetRoundRequest = serde_json::from_str(&payload).unwrap(); - println!("Request state for round {:?}", incoming.round_id); + info!("Request state for round {:?}", incoming.round_id); let (state, _key) = get_state(incoming.round_id); let out = serde_json::to_string(&state).unwrap(); @@ -90,7 +90,7 @@ fn get_round_state_web(req: &mut Request) -> IronResult { // read the POST body req.body.read_to_string(&mut payload).unwrap(); let incoming: GetRoundRequest = serde_json::from_str(&payload).unwrap(); - println!("Request state for round {:?}", incoming.round_id); + info!("Request state for round {:?}", incoming.round_id); let (state, _key) = get_state(incoming.round_id); let state_lite = StateWeb { @@ -120,7 +120,7 @@ fn get_round_state_lite(req: &mut Request) -> IronResult { // read the POST body req.body.read_to_string(&mut payload).unwrap(); let incoming: GetRoundRequest = serde_json::from_str(&payload).unwrap(); - println!("Request state for round {:?}", incoming.round_id); + info!("Request state for round {:?}", incoming.round_id); let (state, _key) = get_state(incoming.round_id); let state_lite = StateLite { diff --git a/packages/server/src/enclave_server/routes/voting.rs b/packages/server/src/enclave_server/routes/voting.rs index a92efbf..f24367e 100644 --- a/packages/server/src/enclave_server/routes/voting.rs +++ b/packages/server/src/enclave_server/routes/voting.rs @@ -12,7 +12,7 @@ use ethers::{ signers::{LocalWallet, Signer}, types::{Address, Bytes, TxHash, BlockNumber}, }; - +use log::info; use crate::enclave_server::models::{EncryptedVote, JsonResponseTxHash, GetEmojisRequest, VoteCountRequest}; use crate::enclave_server::database::{GLOBAL_DB, get_state}; @@ -66,7 +66,7 @@ async fn broadcast_enc_vote(req: &mut Request) -> IronResult { let out = serde_json::to_string(&response).unwrap(); let content_type = "application/json".parse::().unwrap(); - println!("Request for round {:?} send vote tx", incoming.round_id); + info!("Request for round {:?} send vote tx", incoming.round_id); Ok(Response::with((content_type, status::Ok, out))) } @@ -76,7 +76,7 @@ fn get_emojis_by_round(req: &mut Request) -> IronResult { // read the POST body req.body.read_to_string(&mut payload).unwrap(); let mut incoming: GetEmojisRequest = serde_json::from_str(&payload).unwrap(); - println!("Request emojis for round {:?}", incoming.round_id); + info!("Request emojis for round {:?}", incoming.round_id); let (state, _key) = get_state(incoming.round_id); incoming.emojis = state.emojis; @@ -91,7 +91,7 @@ fn get_vote_count_by_round(req: &mut Request) -> IronResult { // read the POST body req.body.read_to_string(&mut payload).unwrap(); let mut incoming: VoteCountRequest = serde_json::from_str(&payload).unwrap(); - println!("Request vote count for round {:?}", incoming.round_id); + info!("Request vote count for round {:?}", incoming.round_id); let (state, _key) = get_state(incoming.round_id); incoming.vote_count = state.vote_count; @@ -102,7 +102,7 @@ fn get_vote_count_by_round(req: &mut Request) -> IronResult { } async fn call_contract(enc_vote: Bytes, address: String) -> Result> { - println!("calling voting contract"); + info!("calling voting contract"); let infura_val = env!("INFURAKEY"); let mut rpc_url = "https://sepolia.infura.io/v3/".to_string(); @@ -110,7 +110,7 @@ async fn call_contract(enc_vote: Bytes, address: String) -> Result::try_from(rpc_url.clone())?; // let block_number: U64 = provider.get_block_number().await?; - // println!("{block_number}"); + // info!("{block_number}"); abigen!( IVOTE, r#"[ @@ -139,6 +139,6 @@ async fn call_contract(enc_vote: Bytes, address: String) -> Result Date: Fri, 6 Sep 2024 12:00:00 +0500 Subject: [PATCH 17/62] Cfeat: server switched to actix --- packages/server/Cargo.lock | 331 +++++++++++++++- packages/server/Cargo.toml | 4 +- packages/server/example_config.json | 2 +- packages/server/src/cli/auth.rs | 1 + packages/server/src/cli/mod.rs | 24 +- packages/server/src/cli/voting.rs | 5 +- .../server/src/enclave_server/database.rs | 10 +- packages/server/src/enclave_server/mod.rs | 58 +-- packages/server/src/enclave_server/models.rs | 6 + .../server/src/enclave_server/routes/auth.rs | 62 +++ .../src/enclave_server/routes/ciphernode.rs | 370 +++++++----------- .../server/src/enclave_server/routes/index.rs | 100 +---- .../server/src/enclave_server/routes/mod.rs | 21 +- .../src/enclave_server/routes/rounds.rs | 202 ++++------ .../server/src/enclave_server/routes/state.rs | 129 +++--- .../src/enclave_server/routes/voting.rs | 137 +++---- 16 files changed, 822 insertions(+), 640 deletions(-) create mode 100644 packages/server/src/enclave_server/routes/auth.rs diff --git a/packages/server/Cargo.lock b/packages/server/Cargo.lock index 0bac6e2..10355a3 100644 --- a/packages/server/Cargo.lock +++ b/packages/server/Cargo.lock @@ -12,6 +12,204 @@ dependencies = [ "regex", ] +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags 2.5.0", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-cors" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9e772b3bcafe335042b5db010ab7c09013dad6eac4915c91d8d50902769f331" +dependencies = [ + "actix-utils", + "actix-web", + "derive_more", + "futures-util", + "log 0.4.22", + "once_cell", + "smallvec", +] + +[[package]] +name = "actix-http" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d48f96fc3003717aeb9856ca3d02a8c7de502667ad76eeacd830b48d2e91fac4" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash", + "base64 0.22.1", + "bitflags 2.5.0", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "futures-core", + "h2 0.3.26", + "http 0.2.12", + "httparse", + "httpdate", + "itoa", + "language-tags 0.3.2", + "local-channel", + "mime 0.3.17", + "percent-encoding 2.3.1", + "pin-project-lite", + "rand 0.8.5", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd 0.13.2", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn 2.0.61", +] + +[[package]] +name = "actix-router" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" +dependencies = [ + "bytestring", + "cfg-if", + "http 0.2.12", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca2549781d8dd6d75c40cf6b6051260a2cc2f3c62343d761a969a0640646894" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio 1.0.2", + "socket2 0.5.7", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9180d76e5cc7ccbc4d60a506f2c727730b154010262df5b910eb17dbe4b8cb38" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "ahash", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "futures-core", + "futures-util", + "impl-more", + "itoa", + "language-tags 0.3.2", + "log 0.4.22", + "mime 0.3.17", + "once_cell", + "pin-project-lite", + "regex", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2 0.5.7", + "time 0.3.36", + "url 2.5.0", +] + +[[package]] +name = "actix-web-codegen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 2.0.61", +] + [[package]] name = "addr2line" version = "0.21.0" @@ -45,6 +243,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", + "getrandom", "once_cell", "version_check 0.9.4", "zerocopy", @@ -59,6 +258,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "alloy-primitives" version = "0.7.7" @@ -833,6 +1047,27 @@ dependencies = [ "thiserror", ] +[[package]] +name = "brotli" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bs58" version = "0.5.1" @@ -890,6 +1125,15 @@ dependencies = [ "serde", ] +[[package]] +name = "bytestring" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d80203ea6b29df88012294f62733de21cfeab47f17b41af3a38bc30a03ee72" +dependencies = [ + "bytes", +] + [[package]] name = "bzip2" version = "0.4.4" @@ -1140,6 +1384,17 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding 2.3.1", + "time 0.3.36", + "version_check 0.9.4", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -2515,7 +2770,7 @@ checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" dependencies = [ "base64 0.9.3", "httparse", - "language-tags", + "language-tags 0.2.2", "log 0.3.9", "mime 0.2.6", "num_cpus", @@ -2692,6 +2947,12 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "impl-more" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206ca75c9c03ba3d4ace2460e57b189f39f43de612c2f85836e65c929701bb2d" + [[package]] name = "impl-rlp" version = "0.3.0" @@ -2962,6 +3223,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "lazy_static" version = "1.4.0" @@ -3017,6 +3284,23 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + [[package]] name = "lock_api" version = "0.4.12" @@ -3152,6 +3436,19 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "log 0.4.22", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + [[package]] name = "modifier" version = "0.1.0" @@ -4262,6 +4559,12 @@ dependencies = [ "regex-syntax 0.8.3", ] +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + [[package]] name = "regex-syntax" version = "0.6.29" @@ -4374,6 +4677,8 @@ dependencies = [ name = "rfv" version = "0.1.0" dependencies = [ + "actix-cors", + "actix-web", "async-std", "bincode", "bytes", @@ -5531,7 +5836,7 @@ dependencies = [ "backtrace", "bytes", "libc", - "mio", + "mio 0.8.11", "num_cpus", "parking_lot 0.12.2", "pin-project-lite", @@ -6459,7 +6764,7 @@ dependencies = [ "pbkdf2 0.11.0", "sha1", "time 0.3.36", - "zstd", + "zstd 0.11.2+zstd.1.5.2", ] [[package]] @@ -6478,7 +6783,16 @@ version = "0.11.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" dependencies = [ - "zstd-safe", + "zstd-safe 5.0.2+zstd.1.5.2", +] + +[[package]] +name = "zstd" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +dependencies = [ + "zstd-safe 7.2.1", ] [[package]] @@ -6491,6 +6805,15 @@ dependencies = [ "zstd-sys", ] +[[package]] +name = "zstd-safe" +version = "7.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +dependencies = [ + "zstd-sys", +] + [[package]] name = "zstd-sys" version = "2.0.10+zstd.1.5.6" diff --git a/packages/server/Cargo.toml b/packages/server/Cargo.toml index f6c9cdc..6980237 100644 --- a/packages/server/Cargo.toml +++ b/packages/server/Cargo.toml @@ -41,4 +41,6 @@ jwt = "0.16.0" hmac = "0.12.1" sha2 = "0.10.8" log = "0.4.22" -env_logger = "0.11.5" \ No newline at end of file +env_logger = "0.11.5" +actix-web = "4.9.0" +actix-cors = "0.7.0" \ No newline at end of file diff --git a/packages/server/example_config.json b/packages/server/example_config.json index ba205f7..2814151 100644 --- a/packages/server/example_config.json +++ b/packages/server/example_config.json @@ -1,6 +1,6 @@ { "round_id": 0, - "poll_length": 600, + "poll_length": 120, "chain_id": 31337, "voting_address": "0x5FbDB2315678afecb367f032d93F642f64180aa3", "ciphernode_count": 2, diff --git a/packages/server/src/cli/auth.rs b/packages/server/src/cli/auth.rs index 33c10ce..c993196 100644 --- a/packages/server/src/cli/auth.rs +++ b/packages/server/src/cli/auth.rs @@ -25,6 +25,7 @@ pub async fn authenticate_user(config: &super::CrispConfig, client: &HyperClient let mut url = config.enclave_address.clone(); url.push_str("/authentication_login"); let req = Request::builder() + .header("Content-Type", "application/json") .method(Method::POST) .uri(url) .body(out)?; diff --git a/packages/server/src/cli/mod.rs b/packages/server/src/cli/mod.rs index 5366222..1708be8 100644 --- a/packages/server/src/cli/mod.rs +++ b/packages/server/src/cli/mod.rs @@ -9,11 +9,31 @@ use std::env; use std::fs::File; use std::io::Read; use http_body_util::Empty; -use log::info; use auth::{authenticate_user, AuthenticationResponse}; use voting::{initialize_crisp_round, participate_in_existing_round}; use serde::{Deserialize, Serialize}; +use env_logger::{Builder, Target}; +use log::LevelFilter; +use log::info; +use std::io::Write; // Use `std::io::Write` for writing to the buffer + +fn init_logger() { + let mut builder = Builder::new(); + builder + .target(Target::Stdout) // Set target to stdout + .filter(None, LevelFilter::Info) // Set log level to Info + .format(|buf, record| { + writeln!( + buf, // Use `writeln!` correctly with the `buf` + "[{}:{}] - {}", + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.args() + ) + }) + .init(); +} type HyperClientGet = HyperClient, Empty>; type HyperClientPost = HyperClient, String>; @@ -31,6 +51,8 @@ struct CrispConfig { #[tokio::main] pub async fn run_cli() -> Result<(), Box> { + init_logger(); + let https = HttpsConnector::new(); let client_get: HyperClientGet = HyperClient::builder(TokioExecutor::new()).build(https.clone()); let client: HyperClientPost = HyperClient::builder(TokioExecutor::new()).build(https); diff --git a/packages/server/src/cli/voting.rs b/packages/server/src/cli/voting.rs index 6b400d8..d14b7c4 100644 --- a/packages/server/src/cli/voting.rs +++ b/packages/server/src/cli/voting.rs @@ -90,6 +90,7 @@ pub async fn initialize_crisp_round( url.push_str("/init_crisp_round"); let req = Request::builder() .header("authorization", "Bearer fpKL54jvWmEGVoRdCNjG") + .header("Content-Type", "application/json") .method(Method::POST) .uri(url) .body(out)?; @@ -133,7 +134,7 @@ pub async fn participate_in_existing_round( let out = serde_json::to_string(&response_pk).unwrap(); let mut url = config.enclave_address.clone(); url.push_str("/get_pk_by_round"); - let req = Request::builder().method(Method::POST).uri(url).body(out)?; + let req = Request::builder().header("Content-Type", "application/json").method(Method::POST).uri(url).body(out)?; let resp = client.request(req).await?; @@ -196,7 +197,7 @@ pub async fn participate_in_existing_round( let out = serde_json::to_string(&request_contract).unwrap(); let mut url = config.enclave_address.clone(); url.push_str("/broadcast_enc_vote"); - let req = Request::builder().method(Method::POST).uri(url).body(out)?; + let req = Request::builder().header("Content-Type", "application/json").method(Method::POST).uri(url).body(out)?; let resp = client.request(req).await?; diff --git a/packages/server/src/enclave_server/database.rs b/packages/server/src/enclave_server/database.rs index add1feb..c491374 100644 --- a/packages/server/src/enclave_server/database.rs +++ b/packages/server/src/enclave_server/database.rs @@ -1,16 +1,14 @@ -use std::{env, str}; +use std::{env, str, sync::Arc}; use once_cell::sync::Lazy; use sled::Db; use rand::Rng; use log::info; use super::models::Round; -pub static GLOBAL_DB: Lazy = Lazy::new(|| { - let pathdb = env::current_dir().unwrap(); - let mut pathdbst = pathdb.display().to_string(); - pathdbst.push_str("/database/enclave_server"); - sled::open(pathdbst.clone()).unwrap() +pub static GLOBAL_DB: Lazy> = Lazy::new(|| { + let pathdb = std::env::current_dir().unwrap().join("database/enclave_server"); + Arc::new(sled::open(pathdb).unwrap()) }); pub fn get_state(round_id: u32) -> (Round, String) { diff --git a/packages/server/src/enclave_server/mod.rs b/packages/server/src/enclave_server/mod.rs index bcb3d25..598b47e 100644 --- a/packages/server/src/enclave_server/mod.rs +++ b/packages/server/src/enclave_server/mod.rs @@ -1,25 +1,30 @@ -mod routes; -mod models; mod database; +mod models; +mod routes; + +use actix_cors::Cors; +use actix_web::{web, App, HttpResponse, HttpServer, Responder}; + +use tokio::task; +use std::sync::Mutex; +use sled::Db; -use iron::prelude::*; -use iron::Chain; -use router::Router; -use iron_cors::CorsMiddleware; +use models::AppState; +use database::GLOBAL_DB; use env_logger::{Builder, Target}; -use log::LevelFilter; use log::info; -use std::io::Write; // Use `std::io::Write` for writing to the buffer +use log::LevelFilter; +use std::io::Write; fn init_logger() { let mut builder = Builder::new(); builder - .target(Target::Stdout) // Set target to stdout - .filter(None, LevelFilter::Info) // Set log level to Info + .target(Target::Stdout) + .filter(None, LevelFilter::Info) .format(|buf, record| { writeln!( - buf, // Use `writeln!` correctly with the `buf` + buf, "[{}:{}] - {}", record.file().unwrap_or("unknown"), record.line().unwrap_or(0), @@ -29,21 +34,26 @@ fn init_logger() { .init(); } - -#[tokio::main] +#[actix_web::main] pub async fn start_server() -> Result<(), Box> { init_logger(); - info!("Starting server..."); - let mut router = Router::new(); - routes::setup_routes(&mut router); - - let cors_middleware = CorsMiddleware::with_allow_any(); - info!("Allowed origin hosts: *"); - - let mut chain = Chain::new(router); - chain.link_around(cors_middleware); - Iron::new(chain).http("0.0.0.0:4000").unwrap(); + let _ = HttpServer::new(|| { + let cors = Cors::default() + .allow_any_origin() // Allow all origins + .allowed_methods(vec!["GET", "POST", "OPTIONS"]) + .allow_any_header() // Allow any custom headers + .supports_credentials() + .max_age(3600); // Cache preflight requests for an hour + App::new() + .wrap(cors) + .app_data(web::Data::new(AppState { + db: GLOBAL_DB.clone(), + })) + .configure(routes::setup_routes) // Modularized Actix routes + }) + .bind("0.0.0.0:4000")? + .run().await; Ok(()) -} \ No newline at end of file +} diff --git a/packages/server/src/enclave_server/models.rs b/packages/server/src/enclave_server/models.rs index 8dcec95..675b2e5 100644 --- a/packages/server/src/enclave_server/models.rs +++ b/packages/server/src/enclave_server/models.rs @@ -1,5 +1,11 @@ use ethers::types::U64; use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use sled::Db; + +pub struct AppState { + pub db: Arc, +} #[derive(Debug, Deserialize, Serialize)] pub struct JsonResponse { diff --git a/packages/server/src/enclave_server/routes/auth.rs b/packages/server/src/enclave_server/routes/auth.rs new file mode 100644 index 0000000..36d9335 --- /dev/null +++ b/packages/server/src/enclave_server/routes/auth.rs @@ -0,0 +1,62 @@ +use std::io::Read; +use jwt::SignWithKey; +use sha2::Sha256; +use std::collections::BTreeMap; +use hmac::{Hmac, Mac}; +use log::info; + +use actix_web::{web, HttpResponse, Responder}; + +use crate::enclave_server::models::{JsonResponse, AppState, AuthenticationLogin, AuthenticationDB, AuthenticationResponse}; +use crate::enclave_server::database::{GLOBAL_DB, pick_response}; + +pub fn setup_routes(config: &mut web::ServiceConfig) { + config + .route("/authentication_login", web::post().to(authentication_login)); +} + +async fn authentication_login(state: web::Data, data: web::Json) -> impl Responder { + let incoming = data.into_inner(); + info!("Twitter Login Request"); + + // Generate HMAC token + let hmac_key: Hmac = Hmac::new_from_slice(b"some-secret").unwrap(); + let mut claims = BTreeMap::new(); + claims.insert("postId", incoming.postId); + let token = claims.sign_with_key(&hmac_key).unwrap(); + + let key = "authentication"; + let db = &state.db; + let mut is_new = false; // Track if it's a new login + + // Perform DB update and fetch the current state + let updated = db.update_and_fetch(key, |old| { + let mut auth_db = old + .map(|existing| serde_json::from_slice::(&existing).unwrap()) + .unwrap_or_else(|| AuthenticationDB { jwt_tokens: Vec::new() }); + + // Check if the token is new + if !auth_db.jwt_tokens.contains(&token) { + auth_db.jwt_tokens.push(token.clone()); + is_new = true; // Mark as a new token + } + + // Serialize the updated auth_db back into bytes + Some(serde_json::to_vec(&auth_db).unwrap()) + }).unwrap(); + + let (response_text, log_message) = if is_new { + info!("Inserting new login to db."); + ("Authorized", "New login inserted.") + } else { + info!("Found previous login."); + ("Already Authorized", "Previous login found.") + }; + + info!("{}", log_message); + + HttpResponse::Ok().json(AuthenticationResponse { + response: response_text.to_string(), + jwt_token: token, + }) +} diff --git a/packages/server/src/enclave_server/routes/ciphernode.rs b/packages/server/src/enclave_server/routes/ciphernode.rs index b3d3577..45c99dd 100644 --- a/packages/server/src/enclave_server/routes/ciphernode.rs +++ b/packages/server/src/enclave_server/routes/ciphernode.rs @@ -6,310 +6,251 @@ use fhe::{ }; use fhe_traits::Serialize as FheSerialize; use crate::util::timeit::timeit; - -use iron::prelude::*; -use iron::status; -use iron::mime::Mime; -use router::Router; +use actix_web::{web, HttpResponse, Responder}; use std::io::Read; use log::info; -use crate::enclave_server::models::{Round, Ciphernode, JsonResponse, JsonRequest, RegisterNodeResponse, SKSShareRequest, SKSSharePoll, SKSShareResponse, PKShareCount, PKRequest, GetCiphernode, GetEligibilityRequest, CRPRequest}; -use crate::enclave_server::database::{GLOBAL_DB, get_state, pick_response}; - -pub fn setup_routes(router: &mut Router) { - router.post("/register_ciphernode", register_ciphernode, "register_ciphernode"); - router.post("/get_pk_share_count", get_pk_share_count, "get_pk_share_count"); - router.post("/get_pk_by_round", get_pk_by_round, "get_pk_by_round"); - router.post("/register_sks_share", register_sks_share, "register_sks_share"); - router.post("/get_sks_shares", get_sks_shares, "get_sks_shares"); - router.post("/get_crp_by_round", get_crp_by_round, "get_crp_by_round"); - router.post("/get_node_by_round", get_node_by_round, "get_node_by_round"); - router.post("/get_round_eligibility", get_round_eligibility, "get_round_eligibility"); +use crate::enclave_server::models::{Round, AppState, Ciphernode, JsonResponse, JsonRequest, RegisterNodeResponse, SKSShareRequest, SKSSharePoll, SKSShareResponse, PKShareCount, PKRequest, GetCiphernode, GetEligibilityRequest, CRPRequest}; +use crate::enclave_server::database::{get_state, pick_response}; + +pub fn setup_routes(config: &mut web::ServiceConfig) { + config + .route("/register_ciphernode", web::post().to(register_ciphernode)) + .route("/get_pk_share_count", web::post().to(get_pk_share_count)) + .route("/get_pk_by_round", web::post().to(get_pk_by_round)) + .route("/register_sks_share", web::post().to(register_sks_share)) + .route("/get_sks_shares", web::post().to(get_sks_shares)) + .route("/get_crp_by_round", web::post().to(get_crp_by_round)) + .route("/get_node_by_round", web::post().to(get_node_by_round)) + .route("/get_round_eligibility", web::post().to(get_round_eligibility)); } -#[tokio::main] -async fn register_ciphernode(req: &mut Request) -> IronResult { - let mut payload = String::new(); - - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - - // we're expecting the POST to match the format of our JsonRequest struct - let incoming: JsonRequest = serde_json::from_str(&payload).unwrap(); +async fn register_ciphernode( + state: web::Data, + data: web::Json, +) -> impl Responder { + let incoming = data.into_inner(); info!("{:?}", incoming.response); info!("ID: {:?}", incoming.id); info!("Round ID: {:?}", incoming.round_id); - let (mut state, key) = get_state(incoming.round_id); + let (mut state_data, key) = get_state(incoming.round_id); // Use shared DB + + state_data.pk_share_count += 1; + state_data.ciphernode_count += 1; - state.pk_share_count = state.pk_share_count + 1; - state.ciphernode_count = state.ciphernode_count + 1; let cnode = Ciphernode { id: incoming.id, pk_share: incoming.pk_share, sks_share: vec![0], }; - state.ciphernodes.push(cnode); - let state_str = serde_json::to_string(&state).unwrap(); - let state_bytes = state_str.into_bytes(); - GLOBAL_DB.insert(key, state_bytes).unwrap(); + state_data.ciphernodes.push(cnode); + let state_str = serde_json::to_string(&state_data).unwrap(); + state.db.insert(key, state_str.into_bytes()).unwrap(); info!("pk share store for node id {:?}", incoming.id); - info!("ciphernode count {:?}", state.ciphernode_count); - info!("ciphernode total {:?}", state.ciphernode_total); - info!("pk share count {:?}", state.pk_share_count); + info!("ciphernode count {:?}", state_data.ciphernode_count); + info!("ciphernode total {:?}", state_data.ciphernode_total); + info!("pk share count {:?}", state_data.pk_share_count); - if state.ciphernode_count == state.ciphernode_total { + // Trigger aggregate_pk_shares when all shares are received + if state_data.ciphernode_count == state_data.ciphernode_total { info!("All shares received"); - let _ = aggregate_pk_shares(incoming.round_id).await; + let _ = aggregate_pk_shares(incoming.round_id, state.clone()).await; // Share state in aggregation } let response = RegisterNodeResponse { response: "Node Registered".to_string(), - node_index: state.ciphernode_count, - }; - let out = serde_json::to_string(&response).unwrap(); + node_index: state_data.ciphernode_count, + }; - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) + HttpResponse::Ok().json(response) } -fn register_sks_share(req: &mut Request) -> IronResult { - let mut payload = String::new(); - - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - - // we're expecting the POST to match the format of our JsonRequest struct - let incoming: SKSShareRequest = serde_json::from_str(&payload).unwrap(); +// Register SKS Share +async fn register_sks_share( + state: web::Data, + data: web::Json, +) -> impl Responder { + let incoming = data.into_inner(); info!("{:?}", incoming.response); - info!("Index: {:?}", incoming.index); // cipher node id (based on first upload of pk share) + info!("Index: {:?}", incoming.index); info!("Round ID: {:?}", incoming.round_id); - - let mut round_key = incoming.round_id.to_string(); - round_key.push_str("-storage"); + let mut round_key = format!("{}-storage", incoming.round_id); info!("Database key is {:?}", round_key); - let state_out = GLOBAL_DB.get(round_key.clone()).unwrap().unwrap(); + let state_out = state.db.get(&round_key).unwrap().unwrap(); let state_out_str = str::from_utf8(&state_out).unwrap(); let mut state_out_struct: Round = serde_json::from_str(&state_out_str).unwrap(); - state_out_struct.sks_share_count = state_out_struct.sks_share_count + 1; - let index = incoming.index; // TODO use hashmap with node id as key + state_out_struct.sks_share_count += 1; + let index = incoming.index; state_out_struct.ciphernodes[index as usize].sks_share = incoming.sks_share; + let state_str = serde_json::to_string(&state_out_struct).unwrap(); - let state_bytes = state_str.into_bytes(); - GLOBAL_DB.insert(round_key, state_bytes).unwrap(); + state.db.insert(round_key, state_str.into_bytes()).unwrap(); info!("sks share stored for node index {:?}", incoming.index); - // toso get share threshold from client config + // Check if all SKS shares have been received if state_out_struct.sks_share_count == state_out_struct.ciphernode_total { info!("All sks shares received"); - //aggregate_pk_shares(incoming.round_id).await; - // TODO: maybe notify cipher nodes + // TODO: Trigger aggregate_pk_shares or notify cipher nodes } - // create a response with our random string, and pass in the string from the POST body - let response = JsonResponse { response: pick_response() }; - let out = serde_json::to_string(&response).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) + HttpResponse::Ok().json(JsonResponse { response: pick_response() }) } -fn get_sks_shares(req: &mut Request) -> IronResult { - let mut payload = String::new(); - - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - - // we're expecting the POST to match the format of our JsonRequest struct - let incoming: SKSSharePoll = serde_json::from_str(&payload).unwrap(); - //const length: usize = incoming.cyphernode_count; - - let (mut state, key) = get_state(incoming.round_id); - +// Get SKS Shares +async fn get_sks_shares( + state: web::Data, + data: web::Json, +) -> impl Responder { + let incoming = data.into_inner(); + let (mut state_data, key) = get_state(incoming.round_id); let mut shares = Vec::with_capacity(incoming.ciphernode_count as usize); - // toso get share threshold from client config - if state.sks_share_count == state.ciphernode_total { + // Check if all SKS shares have been received + if state_data.sks_share_count == state_data.ciphernode_total { info!("All sks shares received... sending to cipher nodes"); - for i in 1..state.ciphernode_total + 1 { - info!("reading share {:?}", i); - shares.push(state.ciphernodes[i as usize].sks_share.clone()); + + for i in 1..=state_data.ciphernode_total { + shares.push(state_data.ciphernodes[i as usize].sks_share.clone()); } - let response = SKSShareResponse { + + let response = SKSShareResponse { response: "final".to_string(), round_id: incoming.round_id, sks_shares: shares, }; - state.status = "Finalized".to_string(); - let state_str = serde_json::to_string(&state).unwrap(); - let state_bytes = state_str.into_bytes(); - GLOBAL_DB.insert(key, state_bytes).unwrap(); - let out = serde_json::to_string(&response).unwrap(); - info!("get rounds hit"); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) + state_data.status = "Finalized".to_string(); + state.db.insert(key, serde_json::to_string(&state_data).unwrap().into_bytes()).unwrap(); + HttpResponse::Ok().json(response) } else { - let response = SKSShareResponse { + let response = SKSShareResponse { response: "waiting".to_string(), round_id: incoming.round_id, sks_shares: shares, }; - let out = serde_json::to_string(&response).unwrap(); - info!("get rounds hit"); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) + HttpResponse::Ok().json(response) } } -fn get_crp_by_round(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let mut incoming: CRPRequest = serde_json::from_str(&payload).unwrap(); +// Get CRP by Round +async fn get_crp_by_round( + data: web::Json, +) -> impl Responder { + let mut incoming = data.into_inner(); info!("Request crp for round {:?}", incoming.round_id); - let (state, _key) = get_state(incoming.round_id); - incoming.crp_bytes = state.crp; - let out = serde_json::to_string(&incoming).unwrap(); + let (state_data, _) = get_state(incoming.round_id); + incoming.crp_bytes = state_data.crp; - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) + HttpResponse::Ok().json(incoming) } -fn get_pk_by_round(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let mut incoming: PKRequest = serde_json::from_str(&payload).unwrap(); - - let (state, _key) = get_state(incoming.round_id); - incoming.pk_bytes = state.pk; - let out = serde_json::to_string(&incoming).unwrap(); - - let content_type = "application/json".parse::().unwrap(); +// Get PK by Round +async fn get_pk_by_round( + data: web::Json, +) -> impl Responder { + let mut incoming = data.into_inner(); + let (state_data, _) = get_state(incoming.round_id); + incoming.pk_bytes = state_data.pk; info!("Request for round {:?} public key", incoming.round_id); - Ok(Response::with((content_type, status::Ok, out))) -} - -fn get_pk_share_count(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let mut incoming: PKShareCount = serde_json::from_str(&payload).unwrap(); + HttpResponse::Ok().json(incoming) +} - let (state, _key) = get_state(incoming.round_id); - incoming.share_id = state.pk_share_count; - let out = serde_json::to_string(&incoming).unwrap(); +// Get PK Share Count +async fn get_pk_share_count( + data: web::Json, +) -> impl Responder { + let mut incoming = data.into_inner(); + let (state_data, _) = get_state(incoming.round_id); + incoming.share_id = state_data.pk_share_count; - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) + HttpResponse::Ok().json(incoming) } -fn get_round_eligibility(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let mut incoming: GetEligibilityRequest = serde_json::from_str(&payload).unwrap(); - info!("Request node elegibility for round {:?}", incoming.round_id); +// Get Round Eligibility +async fn get_round_eligibility( + data: web::Json, +) -> impl Responder { + let mut incoming = data.into_inner(); + info!("Request node eligibility for round {:?}", incoming.round_id); - let (state, _key) = get_state(incoming.round_id); + let (state_data, _) = get_state(incoming.round_id); - for i in 1..state.ciphernodes.len() { + for i in 1..state_data.ciphernodes.len() { info!("checking ciphernode {:?}", i); - info!("server db id {:?}", state.ciphernodes[i as usize].id); - info!("incoming request id {:?}", incoming.node_id); - if state.ciphernodes[i as usize].id == incoming.node_id { + if state_data.ciphernodes[i].id == incoming.node_id { incoming.is_eligible = true; incoming.reason = "Previously Registered".to_string(); - }; - }; + } + } - if state.ciphernode_total == state.ciphernode_count && incoming.reason != "Previously Registered" { + if state_data.ciphernode_total == state_data.ciphernode_count && incoming.reason != "Previously Registered" { incoming.is_eligible = false; incoming.reason = "Round Full".to_string(); - }; + } - if state.ciphernode_total > state.ciphernode_count && incoming.reason != "Previously Registered" { + if state_data.ciphernode_total > state_data.ciphernode_count && incoming.reason != "Previously Registered" { incoming.is_eligible = true; incoming.reason = "Open Node Spot".to_string(); - }; - - let init_time = Utc::now(); - let timestamp = init_time.timestamp(); + } - if timestamp >= (state.start_time + state.poll_length as i64) { + let timestamp = Utc::now().timestamp(); + if timestamp >= (state_data.start_time + state_data.poll_length as i64) { incoming.is_eligible = false; incoming.reason = "Waiting For New Round".to_string(); } - let out = serde_json::to_string(&incoming).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) + HttpResponse::Ok().json(incoming) } -fn get_node_by_round(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let incoming: GetCiphernode = serde_json::from_str(&payload).unwrap(); +// Get Node by Round +async fn get_node_by_round( + data: web::Json, +) -> impl Responder { + let incoming = data.into_inner(); info!("Request node data for round {:?}", incoming.round_id); - let (state, _key) = get_state(incoming.round_id); + let (state_data, _) = get_state(incoming.round_id); let mut cnode = Ciphernode { id: 0, pk_share: vec![0], sks_share: vec![0], }; - for i in 0..state.ciphernodes.len() { - if state.ciphernodes[i as usize].id == incoming.ciphernode_id { - cnode.id = state.ciphernodes[i as usize].id; - cnode.pk_share = state.ciphernodes[i as usize].pk_share.clone(); - cnode.sks_share = state.ciphernodes[i as usize].sks_share.clone(); - }; - }; + for i in 0..state_data.ciphernodes.len() { + if state_data.ciphernodes[i].id == incoming.ciphernode_id { + cnode.id = state_data.ciphernodes[i].id; + cnode.pk_share = state_data.ciphernodes[i].pk_share.clone(); + cnode.sks_share = state_data.ciphernodes[i].sks_share.clone(); + } + } if cnode.id != 0 { - let out = serde_json::to_string(&cnode).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) + HttpResponse::Ok().json(cnode) } else { - let response = JsonResponse { response: "Ciphernode Not Registered".to_string() }; - let out = serde_json::to_string(&response).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) + HttpResponse::Ok().json(JsonResponse { + response: "Ciphernode Not Registered".to_string(), + }) } - - // let response = JsonResponse { response: "Ciphernode Not Registered".to_string() }; - // let out = serde_json::to_string(&response).unwrap(); - - // let content_type = "application/json".parse::().unwrap(); - // Ok(Response::with((content_type, status::Ok, out))) } - -async fn aggregate_pk_shares(round_id: u32) -> Result<(), Box> { +// Aggregate PK Shares (async) +async fn aggregate_pk_shares( + round_id: u32, + state: web::Data, // Access shared state +) -> Result<(), Box> { info!("aggregating validator keyshare"); let degree = 4096; let plaintext_modulus: u64 = 4096; let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; - // Generate a deterministic seed for the Common Poly - //let mut seed = ::Seed::default(); - - // Let's generate the BFV parameters structure. + // Generate BFV parameters let params = timeit!( "Parameters generation", BfvParametersBuilder::new() @@ -319,48 +260,31 @@ async fn aggregate_pk_shares(round_id: u32) -> Result<(), Box = Vec::new(); - for i in 1..state.ciphernode_total + 1 { // todo fix init code that causes offset - // read in pk_shares from storage + let mut parties: Vec = Vec::new(); + for i in 1..=state_data.ciphernode_total { info!("Aggregating PKShare... id {}", i); - let data_des = PublicKeyShare::deserialize(&state.ciphernodes[i as usize].pk_share, ¶ms, crp.clone()).unwrap(); - // let pk_share = PublicKeyShare::new(&sk_share, crp.clone(), &mut thread_rng())?; + let data_des = PublicKeyShare::deserialize(&state_data.ciphernodes[i as usize].pk_share, ¶ms, crp.clone()).unwrap(); parties.push(Party { pk_share: data_des }); } - // Aggregation: this could be one of the parties or a separate entity. Or the - // parties can aggregate cooperatively, in a tree-like fashion. let pk = timeit!("Public key aggregation", { let pk: PublicKey = parties.iter().map(|p| p.pk_share.clone()).aggregate()?; pk }); - //info!("{:?}", pk); + info!("Multiparty Public Key Generated"); - let store_pk = pk.to_bytes(); - state.pk = store_pk; - let state_str = serde_json::to_string(&state).unwrap(); - let state_bytes = state_str.into_bytes(); - GLOBAL_DB.insert(round_key, state_bytes).unwrap(); + state_data.pk = pk.to_bytes(); + + state.db.insert(round_key, serde_json::to_string(&state_data).unwrap().into_bytes()).unwrap(); info!("aggregate pk stored for round {:?}", round_id); + Ok(()) -} +} \ No newline at end of file diff --git a/packages/server/src/enclave_server/routes/index.rs b/packages/server/src/enclave_server/routes/index.rs index 6ed0593..ecb5277 100644 --- a/packages/server/src/enclave_server/routes/index.rs +++ b/packages/server/src/enclave_server/routes/index.rs @@ -1,8 +1,4 @@ use std::str; -use iron::mime::Mime; -use iron::prelude::*; -use iron::status; -use router::Router; use std::io::Read; use jwt::SignWithKey; use sha2::Sha256; @@ -10,94 +6,22 @@ use std::collections::BTreeMap; use hmac::{Hmac, Mac}; use log::info; -use crate::enclave_server::models::{JsonResponse, AuthenticationLogin, AuthenticationDB, AuthenticationResponse}; -use crate::enclave_server::database::{GLOBAL_DB, pick_response}; +use actix_web::{web, HttpResponse, Responder}; -pub fn setup_routes(router: &mut Router) { - router.get("/", handler, "index"); - router.get("/health", health_handler, "health"); - router.post( - "/authentication_login", - authentication_login, - "authentication_login", - ); -} +use crate::enclave_server::models::JsonResponse; -fn handler(_req: &mut Request) -> IronResult { - let response = JsonResponse { - response: pick_response(), - }; - let out = serde_json::to_string(&response).unwrap(); - info!("index handler hit"); - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) +pub fn setup_routes(config: &mut web::ServiceConfig) { + config + .route("/", web::get().to(index_handler)) + .route("/health", web::get().to(health_handler)); } -fn health_handler(_req: &mut Request) -> IronResult { - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok))) +async fn index_handler() -> impl Responder { + HttpResponse::Ok().json(JsonResponse { + response: "Welcome to the enclave server!".to_string(), + }) } -fn authentication_login(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let incoming: AuthenticationLogin = serde_json::from_str(&payload).unwrap(); - info!("Twitter Login Request"); - - // hmac - let hmac_key: Hmac = Hmac::new_from_slice(b"some-secret").unwrap(); - let mut claims = BTreeMap::new(); - claims.insert("postId", incoming.postId); - let token_str = claims.sign_with_key(&hmac_key).unwrap(); - - // db - let key = "authentication"; - let mut authsdb = GLOBAL_DB.get(key).unwrap(); - let mut response_str = "".to_string(); - let mut jwt_token = "".to_string(); - - if authsdb == None { - info!("initializing first auth in db"); - // hmac - let auth_struct = AuthenticationDB { - jwt_tokens: vec![token_str.clone()], - }; - let authsdb_str = serde_json::to_string(&auth_struct).unwrap(); - let authsdb_bytes = authsdb_str.into_bytes(); - GLOBAL_DB.insert(key, authsdb_bytes).unwrap(); - // set response - response_str = "Authorized".to_string(); - } else { - // look for previous auth - let mut au_db = authsdb.unwrap(); - let authsdb_out_str = str::from_utf8(&au_db).unwrap(); - let mut authsdb_out_struct: AuthenticationDB = serde_json::from_str(&authsdb_out_str).unwrap(); - - for i in 0..authsdb_out_struct.jwt_tokens.len() { - if authsdb_out_struct.jwt_tokens[i as usize] == token_str { - info!("Found previous login."); - response_str = "Already Authorized".to_string(); - } - }; - - if response_str != "Already Authorized" { - info!("Inserting new login to db."); - authsdb_out_struct.jwt_tokens.push(token_str.clone()); - let authsdb_str = serde_json::to_string(&authsdb_out_struct).unwrap(); - let authsdb_bytes = authsdb_str.into_bytes(); - GLOBAL_DB.insert(key, authsdb_bytes).unwrap(); - response_str = "Authorized".to_string(); - } - }; - - let response = AuthenticationResponse { - response: response_str, - jwt_token: token_str, - }; - let out = serde_json::to_string(&response).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) +async fn health_handler() -> impl Responder { + HttpResponse::Ok().finish() } - diff --git a/packages/server/src/enclave_server/routes/mod.rs b/packages/server/src/enclave_server/routes/mod.rs index f43213a..45426a0 100644 --- a/packages/server/src/enclave_server/routes/mod.rs +++ b/packages/server/src/enclave_server/routes/mod.rs @@ -1,15 +1,18 @@ mod index; +mod auth; +mod state; +mod voting; mod rounds; mod ciphernode; -mod voting; -mod state; -use router::Router; +use actix_web::{web, HttpResponse, Responder}; + +pub fn setup_routes(config: &mut web::ServiceConfig) { + index::setup_routes(config); + auth::setup_routes(config); + state::setup_routes(config); + voting::setup_routes(config); + rounds::setup_routes(config); + ciphernode::setup_routes(config); -pub fn setup_routes(router: &mut Router) { - index::setup_routes(router); - rounds::setup_routes(router); - ciphernode::setup_routes(router); - voting::setup_routes(router); - state::setup_routes(router); } \ No newline at end of file diff --git a/packages/server/src/enclave_server/routes/rounds.rs b/packages/server/src/enclave_server/routes/rounds.rs index 62c1d54..76e6ff0 100644 --- a/packages/server/src/enclave_server/routes/rounds.rs +++ b/packages/server/src/enclave_server/routes/rounds.rs @@ -1,15 +1,14 @@ use chrono::Utc; use fhe::{bfv::BfvParametersBuilder, mbfv::CommonRandomPoly}; use fhe_traits::Serialize; -use iron::mime::Mime; -use iron::prelude::*; use iron::status; use rand::thread_rng; -use router::Router; use std::env; use std::io::Read; use log::info; +use actix_web::{web, HttpResponse, Responder}; + use ethers::{ providers::{Http, Middleware, Provider}, types::U64, @@ -20,75 +19,48 @@ use crate::util::timeit::timeit; use crate::enclave_server::database::{generate_emoji, get_state, GLOBAL_DB}; use crate::enclave_server::models::{ Ciphernode, CrispConfig, JsonResponse, PollLengthRequest, ReportTallyRequest, Round, - RoundCount, TimestampRequest, + RoundCount, TimestampRequest, AppState }; -pub fn setup_routes(router: &mut Router) { - router.get("/get_rounds", get_rounds, "get_rounds"); - router.post("/init_crisp_round", init_crisp_round, "init_crisp_round"); - router.post( - "/get_start_time_by_round", - get_start_time_by_round, - "get_start_time_by_round", - ); - router.post( - "/get_poll_length_by_round", - get_poll_length_by_round, - "get_poll_length_by_round", - ); - router.post("/report_tally", report_tally, "report_tally"); +pub fn setup_routes(config: &mut web::ServiceConfig) { + config + .route("/get_rounds", web::get().to(get_rounds)) + .route("/init_crisp_round", web::post().to(init_crisp_round)) + .route("/get_start_time_by_round", web::post().to(get_start_time_by_round)) + .route("/get_poll_length_by_round", web::post().to(get_poll_length_by_round)) + .route("/report_tally", web::post().to(report_tally)); } - -fn get_rounds(_req: &mut Request) -> IronResult { - //let test = _req.headers.get::().unwrap(); - //info!("content_type: {:?}", test); - - // let test3 = _req.headers.get::>().unwrap(); - // info!("auth: {:?}", test3.token); - // let key: Hmac = Hmac::new_from_slice(b"some-secret").unwrap(); - // let claims: BTreeMap = test3.token.verify_with_key(&key).unwrap(); - // info!("decoded hmac {:?}", claims); - - //let test2 = _req.headers.get::(); - //info!("user agent: {:?}", test2); - +async fn get_rounds(state: web::Data) -> impl Responder { let key = "round_count"; - let mut round = GLOBAL_DB.get(key).unwrap(); - if round == None { + let mut round = state.db.get(key).unwrap(); + + if round.is_none() { info!("initializing first round in db"); - GLOBAL_DB.insert(key, b"0".to_vec()).unwrap(); - round = GLOBAL_DB.get(key).unwrap(); + state.db.insert(key, b"0".to_vec()).unwrap(); + round = state.db.get(key).unwrap(); } - let round_key = std::str::from_utf8(round.unwrap().as_ref()) - .unwrap() - .to_string(); + + let round_key = std::str::from_utf8(round.unwrap().as_ref()).unwrap().to_string(); let round_int = round_key.parse::().unwrap(); let count = RoundCount { round_count: round_int, }; - info!("round_count: {:?}", count.round_count); - let out = serde_json::to_string(&count).unwrap(); - info!("get rounds hit"); + info!("round_count: {:?}", count.round_count); - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) + HttpResponse::Ok().json(count) } -#[tokio::main] -async fn init_crisp_round(req: &mut Request) -> IronResult { - // let auth = _req.headers.get::>().unwrap(); - // if auth.token != env { - - // } +// Initialize CRISP Round Handler +async fn init_crisp_round( + data: web::Json, + state: web::Data, // Access shared state +) -> impl Responder { info!("generating round crp"); - let infura_val = env!("INFURAKEY"); - let mut rpc_url = "https://sepolia.infura.io/v3/".to_string(); - rpc_url.push_str(&infura_val); - - let provider = Provider::::try_from(rpc_url.clone()).unwrap(); + let rpc_url = "http://0.0.0.0:8545".to_string(); + let provider = Provider::::try_from(rpc_url).unwrap(); let block_number: U64 = provider.get_block_number().await.unwrap(); let degree = 4096; @@ -108,35 +80,24 @@ async fn init_crisp_round(req: &mut Request) -> IronResult { let crp = CommonRandomPoly::new(¶ms, &mut thread_rng()).unwrap(); let crp_bytes = crp.to_bytes(); - let mut payload = String::new(); - - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - - // we're expecting the POST to match the format of our JsonRequest struct - let incoming: CrispConfig = serde_json::from_str(&payload).unwrap(); - info!("ID: {:?}", incoming.round_id); // TODO: check that client sent the expected next round_id + let incoming = data.into_inner(); + info!("ID: {:?}", incoming.round_id); // TODO: Check that client sent the expected next round_id info!("Address: {:?}", incoming.voting_address); - // -------------- + // Initialize or increment round count let key = "round_count"; - //db.remove(key)?; - let round = GLOBAL_DB.get(key).unwrap(); - if round == None { + let round = state.db.get(key).unwrap(); + if round.is_none() { info!("initializing first round in db"); - GLOBAL_DB.insert(key, b"0".to_vec()).unwrap(); + state.db.insert(key, b"0".to_vec()).unwrap(); } - let round_key = std::str::from_utf8(round.unwrap().as_ref()) - .unwrap() - .to_string(); + + let round_key = std::str::from_utf8(round.unwrap().as_ref()).unwrap().to_string(); let mut round_int = round_key.parse::().unwrap(); - round_int = round_int + 1; - let mut inc_round_key = round_int.to_string(); - inc_round_key.push_str("-storage"); - info!( - "Database key is {:?} and round int is {:?}", - inc_round_key, round_int - ); + round_int += 1; + + let inc_round_key = format!("{}-storage", round_int); + info!("Database key is {:?} and round int is {:?}", inc_round_key, round_int); let init_time = Utc::now(); let timestamp = init_time.timestamp(); @@ -144,7 +105,7 @@ async fn init_crisp_round(req: &mut Request) -> IronResult { let (emoji1, emoji2) = generate_emoji(); - let state = Round { + let state_data = Round { id: round_int, status: "Active".to_string(), poll_length: incoming.poll_length, @@ -170,75 +131,66 @@ async fn init_crisp_round(req: &mut Request) -> IronResult { has_voted: vec!["".to_string()], }; - let state_str = serde_json::to_string(&state).unwrap(); - let state_bytes = state_str.into_bytes(); - let key2 = round_int.to_string(); - GLOBAL_DB.insert(inc_round_key, state_bytes).unwrap(); + let state_str = serde_json::to_string(&state_data).unwrap(); + state.db.insert(inc_round_key, state_str.into_bytes()).unwrap(); - let new_round_bytes = key2.into_bytes(); - GLOBAL_DB.insert(key, new_round_bytes).unwrap(); + let new_round_bytes = round_int.to_string().into_bytes(); + state.db.insert(key, new_round_bytes).unwrap(); - // create a response with our random string, and pass in the string from the POST body let response = JsonResponse { response: "CRISP Initiated".to_string(), }; - let out = serde_json::to_string(&response).unwrap(); - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) + HttpResponse::Ok().json(response) } -fn get_start_time_by_round(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let mut incoming: TimestampRequest = serde_json::from_str(&payload).unwrap(); +// Get Start Time by Round Handler +async fn get_start_time_by_round( + data: web::Json, +) -> impl Responder { + let mut incoming = data.into_inner(); info!("Request start time for round {:?}", incoming.round_id); - let (state, _key) = get_state(incoming.round_id); - incoming.timestamp = state.start_time; - let out = serde_json::to_string(&incoming).unwrap(); + let (state_data, _) = get_state(incoming.round_id); + incoming.timestamp = state_data.start_time; - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) + HttpResponse::Ok().json(incoming) } -fn get_poll_length_by_round(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let mut incoming: PollLengthRequest = serde_json::from_str(&payload).unwrap(); +// Get Poll Length by Round Handler +async fn get_poll_length_by_round( + data: web::Json, +) -> impl Responder { + let mut incoming = data.into_inner(); info!("Request poll length for round {:?}", incoming.round_id); - let (state, _key) = get_state(incoming.round_id); - incoming.poll_length = state.poll_length; - let out = serde_json::to_string(&incoming).unwrap(); + let (state_data, _) = get_state(incoming.round_id); + incoming.poll_length = state_data.poll_length; - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) + HttpResponse::Ok().json(incoming) } -fn report_tally(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let incoming: ReportTallyRequest = serde_json::from_str(&payload).unwrap(); +// Report Tally Handler +async fn report_tally( + data: web::Json, + state: web::Data, +) -> impl Responder { + let incoming = data.into_inner(); info!("Request report tally for round {:?}", incoming.round_id); - let (mut state, key) = get_state(incoming.round_id); - if state.votes_option_1 == 0 && state.votes_option_2 == 0 { - state.votes_option_1 = incoming.option_1; - state.votes_option_2 = incoming.option_2; + let (mut state_data, key) = get_state(incoming.round_id); + + if state_data.votes_option_1 == 0 && state_data.votes_option_2 == 0 { + state_data.votes_option_1 = incoming.option_1; + state_data.votes_option_2 = incoming.option_2; - let state_str = serde_json::to_string(&state).unwrap(); - let state_bytes = state_str.into_bytes(); - GLOBAL_DB.insert(key, state_bytes).unwrap(); + let state_str = serde_json::to_string(&state_data).unwrap(); + state.db.insert(key, state_str.into_bytes()).unwrap(); } + let response = JsonResponse { response: "Tally Reported".to_string(), }; - let out = serde_json::to_string(&response).unwrap(); - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} + HttpResponse::Ok().json(response) +} \ No newline at end of file diff --git a/packages/server/src/enclave_server/routes/state.rs b/packages/server/src/enclave_server/routes/state.rs index 09203bd..e41da79 100644 --- a/packages/server/src/enclave_server/routes/state.rs +++ b/packages/server/src/enclave_server/routes/state.rs @@ -1,28 +1,21 @@ -use iron::prelude::*; -use iron::status; -use iron::mime::Mime; -use router::Router; -use std::io::Read; +use actix_web::{web, HttpResponse, Responder}; use log::info; use crate::enclave_server::models::{GetRoundRequest, WebResultRequest, AllWebStates, StateLite, StateWeb}; use crate::enclave_server::database::{get_state, get_round_count}; - -pub fn setup_routes(router: &mut Router) { - router.get("/get_web_result_all", get_web_result_all, "get_web_result_all"); - router.post("/get_round_state_lite", get_round_state_lite, "get_round_state_lite"); - router.post("/get_round_state", get_round_state, "get_round_state"); - router.post("/get_web_result", get_web_result, "get_web_result"); - router.post("/get_round_state_web", get_round_state_web, "get_round_state_web"); +pub fn setup_routes(config: &mut web::ServiceConfig) { + config + .route("/get_web_result_all", web::get().to(get_web_result_all)) + .route("/get_round_state_lite", web::post().to(get_round_state_lite)) + .route("/get_round_state", web::post().to(get_round_state)) + .route("/get_web_result", web::post().to(get_web_result)) + .route("/get_round_state_web", web::post().to(get_round_state_web)); } -fn get_web_result(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let incoming: GetRoundRequest = serde_json::from_str(&payload).unwrap(); - info!("Request web state for round {:?}", incoming.round_id); +async fn get_web_result(data: web::Json) -> impl Responder { + let incoming = data.into_inner(); + info!("Request web state for round {}", incoming.round_id); let (state, _key) = get_state(incoming.round_id); @@ -33,67 +26,49 @@ fn get_web_result(req: &mut Request) -> IronResult { total_votes: state.votes_option_1 + state.votes_option_2, option_1_emoji: state.emojis[0].clone(), option_2_emoji: state.emojis[1].clone(), - end_time: state.start_time + state.poll_length as i64 + end_time: state.start_time + state.poll_length as i64, }; - let out = serde_json::to_string(&response).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) + HttpResponse::Ok().json(response) } -fn get_web_result_all(_req: &mut Request) -> IronResult { +async fn get_web_result_all() -> impl Responder { info!("Request all web state."); let round_count = get_round_count(); - let mut states: Vec = Vec::with_capacity(round_count as usize); - - for i in 1..round_count { - let (state, _key) = get_state(i); - let web_state = WebResultRequest { - round_id: i, - option_1_tally: state.votes_option_1, - option_2_tally: state.votes_option_2, - total_votes: state.votes_option_1 + state.votes_option_2, - option_1_emoji: state.emojis[0].clone(), - option_2_emoji: state.emojis[1].clone(), - end_time: state.start_time + state.poll_length as i64 - }; - states.push(web_state); - } - - let response = AllWebStates { states: states }; - let out = serde_json::to_string(&response).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) + let states: Vec = (1..round_count) + .map(|i| { + let (state, _key) = get_state(i); + WebResultRequest { + round_id: i, + option_1_tally: state.votes_option_1, + option_2_tally: state.votes_option_2, + total_votes: state.votes_option_1 + state.votes_option_2, + option_1_emoji: state.emojis[0].clone(), + option_2_emoji: state.emojis[1].clone(), + end_time: state.start_time + state.poll_length as i64, + } + }) + .collect(); + + let response = AllWebStates { states }; + HttpResponse::Ok().json(response) } - - -fn get_round_state(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let incoming: GetRoundRequest = serde_json::from_str(&payload).unwrap(); - info!("Request state for round {:?}", incoming.round_id); +async fn get_round_state(data: web::Json) -> impl Responder { + let incoming = data.into_inner(); + info!("Request state for round {}", incoming.round_id); let (state, _key) = get_state(incoming.round_id); - let out = serde_json::to_string(&state).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) + HttpResponse::Ok().json(state) } -fn get_round_state_web(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let incoming: GetRoundRequest = serde_json::from_str(&payload).unwrap(); - info!("Request state for round {:?}", incoming.round_id); +async fn get_round_state_web(data: web::Json) -> impl Responder { + let incoming = data.into_inner(); + info!("Request state for round {}", incoming.round_id); let (state, _key) = get_state(incoming.round_id); - let state_lite = StateWeb { + let state_web = StateWeb { id: state.id, status: state.status, poll_length: state.poll_length, @@ -104,23 +79,16 @@ fn get_round_state_web(req: &mut Request) -> IronResult { sks_share_count: state.sks_share_count, vote_count: state.vote_count, start_time: state.start_time, - ciphernode_total: state.ciphernode_total, + ciphernode_total: state.ciphernode_total, emojis: state.emojis, }; - let out = serde_json::to_string(&state_lite).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) + HttpResponse::Ok().json(state_web) } - -fn get_round_state_lite(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let incoming: GetRoundRequest = serde_json::from_str(&payload).unwrap(); - info!("Request state for round {:?}", incoming.round_id); +async fn get_round_state_lite(data: web::Json) -> impl Responder { + let incoming = data.into_inner(); + info!("Request state for round {}", incoming.round_id); let (state, _key) = get_state(incoming.round_id); let state_lite = StateLite { @@ -137,12 +105,9 @@ fn get_round_state_lite(req: &mut Request) -> IronResult { pk: state.pk, start_time: state.start_time, block_start: state.block_start, - ciphernode_total: state.ciphernode_total, + ciphernode_total: state.ciphernode_total, emojis: state.emojis, }; - let out = serde_json::to_string(&state_lite).unwrap(); - - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) -} + HttpResponse::Ok().json(state_lite) +} \ No newline at end of file diff --git a/packages/server/src/enclave_server/routes/voting.rs b/packages/server/src/enclave_server/routes/voting.rs index f24367e..2211a84 100644 --- a/packages/server/src/enclave_server/routes/voting.rs +++ b/packages/server/src/enclave_server/routes/voting.rs @@ -1,9 +1,5 @@ use std::{env, sync::Arc, str}; -use iron::prelude::*; -use iron::status; -use iron::mime::Mime; -use router::Router; use std::io::Read; use ethers::{ prelude::abigen, @@ -14,103 +10,97 @@ use ethers::{ }; use log::info; -use crate::enclave_server::models::{EncryptedVote, JsonResponseTxHash, GetEmojisRequest, VoteCountRequest}; -use crate::enclave_server::database::{GLOBAL_DB, get_state}; +use actix_web::{web, HttpResponse, Responder}; +use crate::enclave_server::models::{EncryptedVote, JsonResponseTxHash, AppState, GetEmojisRequest, VoteCountRequest}; +use crate::enclave_server::database::{GLOBAL_DB, get_state}; -pub fn setup_routes(router: &mut Router) { - router.post("/broadcast_enc_vote", broadcast_enc_vote, "broadcast_enc_vote"); - router.post("/get_vote_count_by_round", get_vote_count_by_round, "get_vote_count_by_round"); - router.post("/get_emojis_by_round", get_emojis_by_round, "get_emojis_by_round"); +pub fn setup_routes(config: &mut web::ServiceConfig) { + config + .route("/broadcast_enc_vote", web::post().to(broadcast_enc_vote)) + .route("/get_vote_count_by_round", web::post().to(get_vote_count_by_round)) + .route("/get_emojis_by_round", web::post().to(get_emojis_by_round)); } - -#[tokio::main] -async fn broadcast_enc_vote(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let incoming: EncryptedVote = serde_json::from_str(&payload).unwrap(); +async fn broadcast_enc_vote( + data: web::Json, + state: web::Data, // Access shared state +) -> impl Responder { + let incoming = data.into_inner(); let mut response_str = ""; let mut converter = "".to_string(); - let (mut state, key) = get_state(incoming.round_id); + let (mut state_data, key) = get_state(incoming.round_id); - for i in 0..state.has_voted.len() { - if state.has_voted[i] == incoming.postId { + for voted in &state_data.has_voted { + if *voted == incoming.postId { response_str = "User Has Already Voted"; - } else { - response_str = "Vote Successful"; + break; } - }; + } - if response_str == "Vote Successful" { + if response_str == "" { + response_str = "Vote Successful"; let sol_vote = Bytes::from(incoming.enc_vote_bytes); - let tx_hash = call_contract(sol_vote, state.voting_address.clone()).await.unwrap(); + let tx_hash = call_contract(sol_vote, state_data.voting_address.clone()).await.unwrap(); + converter = "0x".to_string(); for i in 0..32 { if tx_hash[i] <= 16 { converter.push_str("0"); - converter.push_str(&format!("{:x}", tx_hash[i])); - } else { - converter.push_str(&format!("{:x}", tx_hash[i])); } + converter.push_str(&format!("{:x}", tx_hash[i])); } - state.vote_count = state.vote_count + 1; - state.has_voted.push(incoming.postId); - let state_str = serde_json::to_string(&state).unwrap(); - let state_bytes = state_str.into_bytes(); - GLOBAL_DB.insert(key, state_bytes).unwrap(); - }; + state_data.vote_count += 1; + state_data.has_voted.push(incoming.postId.clone()); + let state_str = serde_json::to_string(&state_data).unwrap(); + state.db.insert(key, state_str.into_bytes()).unwrap(); + } - let response = JsonResponseTxHash { response: response_str.to_string(), tx_hash: converter }; - let out = serde_json::to_string(&response).unwrap(); + let response = JsonResponseTxHash { + response: response_str.to_string(), + tx_hash: converter, + }; - let content_type = "application/json".parse::().unwrap(); info!("Request for round {:?} send vote tx", incoming.round_id); - Ok(Response::with((content_type, status::Ok, out))) + HttpResponse::Ok().json(response) } - -fn get_emojis_by_round(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let mut incoming: GetEmojisRequest = serde_json::from_str(&payload).unwrap(); +// Get Emojis by Round Handler +async fn get_emojis_by_round( + data: web::Json, +) -> impl Responder { + let mut incoming = data.into_inner(); info!("Request emojis for round {:?}", incoming.round_id); - let (state, _key) = get_state(incoming.round_id); - incoming.emojis = state.emojis; - let out = serde_json::to_string(&incoming).unwrap(); + let (state_data, _) = get_state(incoming.round_id); + incoming.emojis = state_data.emojis; - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) + HttpResponse::Ok().json(incoming) } -fn get_vote_count_by_round(req: &mut Request) -> IronResult { - let mut payload = String::new(); - // read the POST body - req.body.read_to_string(&mut payload).unwrap(); - let mut incoming: VoteCountRequest = serde_json::from_str(&payload).unwrap(); +// Get Vote Count by Round Handler +async fn get_vote_count_by_round( + data: web::Json, +) -> impl Responder { + let mut incoming = data.into_inner(); info!("Request vote count for round {:?}", incoming.round_id); - let (state, _key) = get_state(incoming.round_id); - incoming.vote_count = state.vote_count; - let out = serde_json::to_string(&incoming).unwrap(); + let (state_data, _) = get_state(incoming.round_id); + incoming.vote_count = state_data.vote_count; - let content_type = "application/json".parse::().unwrap(); - Ok(Response::with((content_type, status::Ok, out))) + HttpResponse::Ok().json(incoming) } -async fn call_contract(enc_vote: Bytes, address: String) -> Result> { +// Call Contract Function +async fn call_contract( + enc_vote: Bytes, + address: String, +) -> Result> { info!("calling voting contract"); - let infura_val = env!("INFURAKEY"); - let mut rpc_url = "https://sepolia.infura.io/v3/".to_string(); - rpc_url.push_str(&infura_val); - + let rpc_url = "http://0.0.0.0:8545".to_string(); let provider = Provider::::try_from(rpc_url.clone())?; - // let block_number: U64 = provider.get_block_number().await?; - // info!("{block_number}"); + abigen!( IVOTE, r#"[ @@ -120,13 +110,11 @@ async fn call_contract(enc_vote: Bytes, address: String) -> Result().unwrap() - .with_chain_id(11155111 as u64); + .parse::()? + .with_chain_id(31337 as u64); let nonce_manager = provider.clone().nonce_manager(wallet.address()); let curr_nonce = nonce_manager @@ -138,7 +126,8 @@ async fn call_contract(enc_vote: Bytes, address: String) -> Result Date: Mon, 9 Sep 2024 17:32:34 +0500 Subject: [PATCH 18/62] feat: server swictched to actix --- packages/ciphernode/Cargo.lock | 138 +++++++++++++++--- packages/ciphernode/Cargo.toml | 2 + .../ciphernode/src/bin/start_cipher_node.rs | 113 +++++++++----- packages/ciphernode/src/bin/util.rs | 13 +- packages/client/src/contracts/rfv/index.ts | 2 +- 5 files changed, 202 insertions(+), 66 deletions(-) diff --git a/packages/ciphernode/Cargo.lock b/packages/ciphernode/Cargo.lock index 0e10bf8..5e0af00 100644 --- a/packages/ciphernode/Cargo.lock +++ b/packages/ciphernode/Cargo.lock @@ -62,6 +62,55 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.86" @@ -156,7 +205,7 @@ dependencies = [ "cfg-if", "concurrent-queue", "futures-lite 1.13.0", - "log 0.4.21", + "log 0.4.22", "parking", "polling 2.8.0", "rustix 0.37.27", @@ -222,7 +271,7 @@ dependencies = [ "futures-lite 1.13.0", "gloo-timers", "kv-log-macro", - "log 0.4.21", + "log 0.4.22", "memchr", "once_cell", "pin-project-lite", @@ -609,6 +658,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -913,7 +968,7 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" dependencies = [ - "log 0.4.21", + "log 0.4.22", ] [[package]] @@ -941,7 +996,7 @@ dependencies = [ "bytes", "hex", "k256", - "log 0.4.21", + "log 0.4.22", "rand 0.8.5", "rlp", "serde", @@ -949,6 +1004,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "env_filter" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +dependencies = [ + "log 0.4.22", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log 0.4.22", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -1917,6 +1995,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "0.10.16" @@ -2187,9 +2271,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24b02b8856c7f14e443c483e802cf0ce693f3bec19f49d2c9a242b18f88c9b70" dependencies = [ "iron", - "log 0.4.21", + "log 0.4.22", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.11.0" @@ -2290,7 +2380,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" dependencies = [ - "log 0.4.21", + "log 0.4.22", ] [[package]] @@ -2388,14 +2478,14 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" dependencies = [ - "log 0.4.21", + "log 0.4.22", ] [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" dependencies = [ "value-bag", ] @@ -2499,7 +2589,7 @@ checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ "lazy_static", "libc", - "log 0.4.21", + "log 0.4.22", "openssl", "openssl-probe", "openssl-sys", @@ -3050,7 +3140,7 @@ dependencies = [ "cfg-if", "concurrent-queue", "libc", - "log 0.4.21", + "log 0.4.22", "pin-project-lite", "windows-sys 0.48.0", ] @@ -3165,7 +3255,7 @@ dependencies = [ "bytes", "heck 0.5.0", "itertools 0.12.1", - "log 0.4.21", + "log 0.4.22", "multimap", "once_cell", "petgraph", @@ -3470,7 +3560,7 @@ dependencies = [ "hyper-rustls", "ipnet", "js-sys", - "log 0.4.21", + "log 0.4.22", "mime 0.3.17", "once_cell", "percent-encoding 2.3.1", @@ -3513,6 +3603,7 @@ dependencies = [ "chrono", "console", "dialoguer", + "env_logger", "ethers", "fhe", "fhe-traits", @@ -3527,6 +3618,7 @@ dependencies = [ "iron", "iron-cors", "jwt", + "log 0.4.22", "once_cell", "rand 0.8.5", "rand_chacha 0.3.1", @@ -3672,7 +3764,7 @@ version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ - "log 0.4.21", + "log 0.4.22", "ring 0.17.8", "rustls-webpki", "sct", @@ -4006,7 +4098,7 @@ dependencies = [ "fs2", "fxhash", "libc", - "log 0.4.21", + "log 0.4.22", "parking_lot 0.11.2", ] @@ -4370,7 +4462,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", - "log 0.4.21", + "log 0.4.22", "rustls", "tokio", "tokio-rustls", @@ -4470,7 +4562,7 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "log 0.4.21", + "log 0.4.22", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -4529,7 +4621,7 @@ dependencies = [ "data-encoding", "http 0.2.12", "httparse", - "log 0.4.21", + "log 0.4.22", "rand 0.8.5", "rustls", "sha1", @@ -4668,6 +4760,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "0.8.2" @@ -4756,7 +4854,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", - "log 0.4.21", + "log 0.4.22", "once_cell", "proc-macro2", "quote", @@ -5037,7 +5135,7 @@ dependencies = [ "async_io_stream", "futures", "js-sys", - "log 0.4.21", + "log 0.4.22", "pharos", "rustc_version", "send_wrapper 0.6.0", diff --git a/packages/ciphernode/Cargo.toml b/packages/ciphernode/Cargo.toml index df21459..eba06c3 100644 --- a/packages/ciphernode/Cargo.toml +++ b/packages/ciphernode/Cargo.toml @@ -39,3 +39,5 @@ headers = "0.4.0" jwt = "0.16.0" hmac = "0.12.1" sha2 = "0.10.8" +log = "0.4.22" +env_logger = "0.11.5" \ No newline at end of file diff --git a/packages/ciphernode/src/bin/start_cipher_node.rs b/packages/ciphernode/src/bin/start_cipher_node.rs index 13fb948..2798c3c 100644 --- a/packages/ciphernode/src/bin/start_cipher_node.rs +++ b/packages/ciphernode/src/bin/start_cipher_node.rs @@ -40,6 +40,28 @@ use jwt::SignWithKey; use sha2::Sha256; use std::collections::BTreeMap; +use env_logger::{Builder, Target}; +use log::LevelFilter; +use log::info; +use std::io::Write; // Use `std::io::Write` for writing to the buffer + +fn init_logger() { + let mut builder = Builder::new(); + builder + .target(Target::Stdout) // Set target to stdout + .filter(None, LevelFilter::Info) // Set log level to Info + .format(|buf, record| { + writeln!( + buf, // Use `writeln!` correctly with the `buf` + "[{}:{}] - {}", + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.args() + ) + }) + .init(); + } + #[derive(Debug, Deserialize, Serialize)] struct JsonRequest { response: String, @@ -189,21 +211,21 @@ static GLOBAL_DB: Lazy = Lazy::new(|| { let mut config: CiphernodeConfig = serde_json::from_str(&data).expect("JSON was not well-formatted"); let node_id: u32; if(config.ids.len() - 1) < cnode_selector { - println!("generating new ciphernode..."); + info!("generating new ciphernode..."); node_id = rand::thread_rng().gen_range(0..100000); config.ids.push(node_id); let configfile = serde_json::to_string(&config).unwrap(); fs::write(pathst.clone(), configfile).unwrap(); } else if config.ids[cnode_selector] == 0 { - println!("generating initial ciphernode id..."); + info!("generating initial ciphernode id..."); node_id = rand::thread_rng().gen_range(0..100000); config.ids[cnode_selector as usize] = node_id; let configfile = serde_json::to_string(&config).unwrap(); fs::write(pathst.clone(), configfile).unwrap(); } else { - println!("Using ciphernode id {:?}", config.ids[cnode_selector]); + info!("Using ciphernode id {:?}", config.ids[cnode_selector]); node_id = config.ids[cnode_selector]; }; @@ -211,13 +233,15 @@ static GLOBAL_DB: Lazy = Lazy::new(|| { let mut pathdbst = pathdb.display().to_string(); pathdbst.push_str("/database/ciphernode-"); pathdbst.push_str(&node_id.to_string()); - println!("Node database path {:?}", pathdbst); + info!("Node database path {:?}", pathdbst); sled::open(pathdbst.clone()).unwrap() }); #[tokio::main] async fn main() -> Result<(), Box> { - println!("Getting configuration file."); + init_logger(); + + info!("Getting configuration file."); let path = env::current_dir().unwrap(); let mut pathst = path.display().to_string(); pathst.push_str("/example_ciphernode_config.json"); @@ -232,7 +256,7 @@ async fn main() -> Result<(), Box> { }; if(config.ids.len() - 1) < cnode_selector { - println!("generating new ciphernode..."); + info!("generating new ciphernode..."); let new_id = rand::thread_rng().gen_range(0..100000); config.ids.push(new_id); @@ -241,7 +265,7 @@ async fn main() -> Result<(), Box> { } let node_id: u32 = config.ids[cnode_selector as usize]; - println!("Node ID: {:?} selected.", node_id); + info!("Node ID: {:?} selected.", node_id); let pathdb = env::current_dir().unwrap(); let mut pathdbst = pathdb.display().to_string(); pathdbst.push_str("/database/ciphernode-"); @@ -250,7 +274,7 @@ async fn main() -> Result<(), Box> { let node_state_bytes = GLOBAL_DB.get(pathdbst.clone()).unwrap(); if node_state_bytes == None { - println!("Initializing node state in database."); + info!("Initializing node state in database."); let state = Ciphernode { id: node_id, index: vec![0], @@ -281,7 +305,7 @@ async fn main() -> Result<(), Box> { let mut internal_round_count = RoundCount { round_count: 0 }; loop { - println!("Polling Enclave server."); + info!("Polling Enclave server."); let https = HttpsConnector::new(); let client_get = HyperClient::builder(TokioExecutor::new()).build::<_, Empty>(https.clone()); let client = HyperClient::builder(TokioExecutor::new()).build::<_, String>(https); @@ -309,32 +333,33 @@ async fn main() -> Result<(), Box> { }; match resp { Ok(n) => { - println!("n is {:?}", n); + info!("n is {:?}", n); let body_bytes = n.collect().await?.to_bytes(); let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); - println!("Get Round Response {:?}", body_str); + info!("Get Round Response {:?}", body_str); count = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); - println!("Server Round Count: {:?}", count.round_count); - println!("Internal Round Count: {:?}", internal_round_count.round_count); + info!("Server Round Count: {:?}", count.round_count); + info!("Internal Round Count: {:?}", internal_round_count.round_count); }, - Err(e) => println!("Error: {:?}", e), + Err(e) => info!("Error: {:?}", e), } // Check to see if the server reported a new round if count.round_count > internal_round_count.round_count { - println!("Getting New Round State."); + info!("Getting New Round State."); let response_get_state = GetRoundRequest { round_id: count.round_count }; let out = serde_json::to_string(&response_get_state).unwrap(); let mut url_get_state = config.enclave_address.clone(); url_get_state.push_str("/get_round_state_lite"); let req = Request::builder() + .header("Content-Type", "application/json") .method(Method::POST) .uri(url_get_state) .body(out)?; let resp = client.request(req).await?; - println!("Get Round State Response status: {}", resp.status()); + info!("Get Round State Response status: {}", resp.status()); let body_bytes = resp.collect().await?.to_bytes(); let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); @@ -350,17 +375,18 @@ async fn main() -> Result<(), Box> { let mut url_get_eligibility = config.enclave_address.clone(); url_get_eligibility.push_str("/get_round_eligibility"); let req = Request::builder() + .header("Content-Type", "application/json") .method(Method::POST) .uri(url_get_eligibility) .body(out)?; let resp = client.request(req).await?; - println!("Get Eligibility Response status: {}", resp.status()); + info!("Get Eligibility Response status: {}", resp.status()); let body_bytes = resp.collect().await?.to_bytes(); let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); let eligibility: GetEligibilityRequest = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); - println!("Ciphernode eligibility: {:?}", eligibility.reason); + info!("Ciphernode eligibility: {:?}", eligibility.reason); if eligibility.is_eligible == false && eligibility.reason == "Waiting For New Round" { internal_round_count.round_count = count.round_count; @@ -369,7 +395,7 @@ async fn main() -> Result<(), Box> { if eligibility.is_eligible == true && eligibility.reason == "Open Node Spot" { // do registration - println!("Generating PK share and serializing."); + info!("Generating PK share and serializing."); // deserialize crp_bytes let crp = CommonRandomPoly::deserialize(&state.crp, ¶ms).unwrap(); @@ -394,17 +420,18 @@ async fn main() -> Result<(), Box> { let mut url_register_keyshare = config.enclave_address.clone(); url_register_keyshare.push_str("/register_ciphernode"); let req = Request::builder() + .header("Content-Type", "application/json") .method(Method::POST) .uri(url_register_keyshare) .body(out)?; let resp = client.request(req).await?; - println!("Register Node Response status: {}", resp.status()); + info!("Register Node Response status: {}", resp.status()); let body_bytes = resp.collect().await?.to_bytes(); let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); let registration_res: RegisterNodeResponse = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); - println!("Ciphernode index: {:?}", registration_res.node_index); + info!("Ciphernode index: {:?}", registration_res.node_index); // index is the order in which this node registered with the server node_state.index[0] = registration_res.node_index; @@ -417,12 +444,12 @@ async fn main() -> Result<(), Box> { }; if eligibility.is_eligible == true && eligibility.reason == "Previously Registered" { - println!("Server reported to resume watching."); + info!("Server reported to resume watching."); internal_round_count.round_count = count.round_count; start_contract_watch(&state, node_id, &config).await; }; if eligibility.is_eligible == false && eligibility.reason == "Round Full" { - println!("Server reported round full, wait for next round."); + info!("Server reported round full, wait for next round."); internal_round_count.round_count = count.round_count; continue }; @@ -440,7 +467,7 @@ fn get_state(node_id: u32) -> (Ciphernode, String) { pathdbst.push_str("/database/ciphernode-"); pathdbst.push_str(&node_id.to_string()); pathdbst.push_str("-state"); - println!("Database key is {:?}", pathdbst); + info!("Database key is {:?}", pathdbst); let state_out = GLOBAL_DB.get(pathdbst.clone()).unwrap().unwrap(); let state_out_str = str::from_utf8(&state_out).unwrap(); let state_out_struct: Ciphernode = serde_json::from_str(&state_out_str).unwrap(); @@ -448,7 +475,7 @@ fn get_state(node_id: u32) -> (Ciphernode, String) { } async fn get_votes_contract(block_start: U64, address: String, _chain_id: u32) -> Vec> { - println!("Filtering contract for votes"); + info!("Filtering contract for votes"); // chain state // let infura_key = "INFURAKEY"; // let infura_val = env::var(infura_key).unwrap(); @@ -511,33 +538,37 @@ async fn start_contract_watch(state: &StateLite, node_id: u32, config: &Cipherno // TODO: move to thread so main loop can continue to look for more work loop { - println!("Waiting for round {:?} poll to end.", state.id); + info!("Waiting for round {:?} poll to end.", state.id); let now = Utc::now(); let internal_time = now.timestamp(); if (state.start_time + state.poll_length as i64) < internal_time { - print!("poll time ended... performing fhe computation"); + info!("poll time ended... performing fhe computation"); let response_get_voters = VoteCountRequest { round_id: state.id, vote_count: 0 }; let out = serde_json::to_string(&response_get_voters).unwrap(); let mut url_get_voters = config.enclave_address.clone(); url_get_voters.push_str("/get_vote_count_by_round"); let req = Request::builder() + .header("Content-Type", "application/json") .method(Method::POST) .uri(url_get_voters) .body(out).unwrap(); let resp = client.request(req).await.unwrap(); - println!("Get Vote Count Response status: {}", resp.status()); + info!("Get Vote Count Response status: {}", resp.status()); let body_bytes_get_voters = resp.collect().await.unwrap().to_bytes(); let body_str_get_voters = String::from_utf8(body_bytes_get_voters.to_vec()).unwrap(); let num_voters: VoteCountRequest = serde_json::from_str(&body_str_get_voters).expect("JSON was not well-formatted"); + info!("VoteCountRequest: {:?}", num_voters); + let votes_collected = get_votes_contract(state.block_start, state.voting_address.clone(), state.chain_id).await; - println!("All votes collected? {:?}", num_voters.vote_count == votes_collected.len() as u32); + info!("Votes Collected Len: {:?}", votes_collected.len()); + info!("All votes collected? {:?}", num_voters.vote_count == votes_collected.len() as u32); if votes_collected.len() == 0 { - println!("Vote result = {} / {}", 0, num_voters.vote_count); + info!("Vote result = {} / {}", 0, num_voters.vote_count); let response_report = ReportTallyRequest { round_id: state.id, @@ -548,12 +579,13 @@ async fn start_contract_watch(state: &StateLite, node_id: u32, config: &Cipherno let mut url_report = config.enclave_address.clone(); url_report.push_str("/report_tally"); let req = Request::builder() + .header("Content-Type", "application/json") .method(Method::POST) .uri(url_report) .body(out).unwrap(); let resp = client.request(req).await.unwrap(); - println!("Tally Reported Response status: {}", resp.status()); + info!("Tally Reported Response status: {}", resp.status()); break; } @@ -588,12 +620,13 @@ async fn start_contract_watch(state: &StateLite, node_id: u32, config: &Cipherno let mut url_register_sks = config.enclave_address.clone(); url_register_sks.push_str("/register_sks_share"); let req = Request::builder() + .header("Content-Type", "application/json") .method(Method::POST) .uri(url_register_sks) .body(out).unwrap(); let mut resp = client.request(req).await.unwrap(); - println!("Register SKS Response status: {}", resp.status()); + info!("Register SKS Response status: {}", resp.status()); // Stream the body, writing each frame to stdout as it arrives while let Some(next) = resp.frame().await { @@ -614,15 +647,16 @@ async fn start_contract_watch(state: &StateLite, node_id: u32, config: &Cipherno let mut url_register_get_sks = config.enclave_address.clone(); url_register_get_sks.push_str("/get_sks_shares"); let req = Request::builder() + .header("Content-Type", "application/json") .method(Method::POST) .uri(url_register_get_sks) .body(out).unwrap(); let resp = client.request(req).await.unwrap(); - println!("Get All SKS Response status: {}", resp.status()); + info!("Get All SKS Response status: {}", resp.status()); if resp.status().to_string() == "500 Internal Server Error" { - println!("enclave resource failed, trying to poll for sks shares again..."); + info!("enclave resource failed, trying to poll for sks shares again..."); continue; } @@ -632,7 +666,7 @@ async fn start_contract_watch(state: &StateLite, node_id: u32, config: &Cipherno if shares.response == "final" { // do decrypt - println!("collected all of the decrypt shares!"); + info!("collected all of the decrypt shares!"); for i in 0..state.ciphernode_total { decryption_shares.push(DecryptionShare::deserialize(&shares.sks_shares[i as usize], ¶ms, tally.clone())); } @@ -647,13 +681,13 @@ async fn start_contract_watch(state: &StateLite, node_id: u32, config: &Cipherno let tally_result = tally_vec[0]; // Show vote result - println!("Vote result = {} / {}", tally_result, num_voters.vote_count); + info!("Vote result = {} / {}", tally_result, num_voters.vote_count); // report result to server let option_1_total = tally_result; let option_2_total = num_voters.vote_count - tally_result as u32; - println!("option 1 total {:?}", option_1_total); - println!("option 2 total {:?}", option_2_total); + info!("option 1 total {:?}", option_1_total); + info!("option 2 total {:?}", option_2_total); let response_report = ReportTallyRequest { round_id: state.id, @@ -664,12 +698,13 @@ async fn start_contract_watch(state: &StateLite, node_id: u32, config: &Cipherno let mut url_report = config.enclave_address.clone(); url_report.push_str("/report_tally"); let req = Request::builder() + .header("Content-Type", "application/json") .method(Method::POST) .uri(url_report) .body(out).unwrap(); let resp = client.request(req).await.unwrap(); - println!("Tally Reported Response status: {}", resp.status()); + info!("Tally Reported Response status: {}", resp.status()); break; } diff --git a/packages/ciphernode/src/bin/util.rs b/packages/ciphernode/src/bin/util.rs index 8e3d167..adf668e 100644 --- a/packages/ciphernode/src/bin/util.rs +++ b/packages/ciphernode/src/bin/util.rs @@ -4,6 +4,7 @@ use fhe::bfv; use fhe_traits::FheEncoder; use fhe_util::transcode_from_bytes; use std::{cmp::min, fmt, sync::Arc, time::Duration}; +use log::info; /// Macros to time code and display a human-readable duration. pub mod timeit { @@ -16,7 +17,7 @@ pub mod timeit { for _ in 1..$loops { let _ = $code; } - println!( + info!( "⏱ {}: {}", $name, DisplayDuration(start.elapsed() / $loops) @@ -31,7 +32,7 @@ pub mod timeit { use util::DisplayDuration; let start = std::time::Instant::now(); let r = $code; - println!("⏱ {}: {}", $name, DisplayDuration(start.elapsed())); + info!("⏱ {}: {}", $name, DisplayDuration(start.elapsed())); r }}; } @@ -99,12 +100,12 @@ pub fn encode_database( number_elements_per_plaintext(par.degree(), plaintext_nbits, elements_size); let number_rows = (database.len() + number_elements_per_plaintext - 1) / number_elements_per_plaintext; - println!("number_rows = {number_rows}"); - println!("number_elements_per_plaintext = {number_elements_per_plaintext}"); + info!("number_rows = {number_rows}"); + info!("number_elements_per_plaintext = {number_elements_per_plaintext}"); let dimension_1 = (number_rows as f64).sqrt().ceil() as usize; let dimension_2 = (number_rows + dimension_1 - 1) / dimension_1; - println!("dimensions = {dimension_1} {dimension_2}"); - println!("dimension = {}", dimension_1 * dimension_2); + info!("dimensions = {dimension_1} {dimension_2}"); + info!("dimension = {}", dimension_1 * dimension_2); let mut preprocessed_database = vec![ bfv::Plaintext::zero(bfv::Encoding::poly_at_level(level), &par).unwrap(); diff --git a/packages/client/src/contracts/rfv/index.ts b/packages/client/src/contracts/rfv/index.ts index 915de34..6399843 100644 --- a/packages/client/src/contracts/rfv/index.ts +++ b/packages/client/src/contracts/rfv/index.ts @@ -1 +1 @@ -export const RFV_CONTRACT_ADDRESS = '0x51ec8ab3e53146134052444693ab3ec53663a12b' +export const RFV_CONTRACT_ADDRESS = '0x5FbDB2315678afecb367f032d93F642f64180aa3' From dd562161cdfbd919c80ae587c3d85c2145fd8f8b Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Tue, 10 Sep 2024 14:13:30 +0500 Subject: [PATCH 19/62] feat: use compute_provider as a base for risc0 --- packages/compute_provider/core/src/lib.rs | 72 +- packages/compute_provider/host/Cargo.toml | 2 +- packages/compute_provider/host/src/lib.rs | 151 +- packages/compute_provider/methods/Cargo.toml | 2 +- packages/compute_provider/methods/build.rs | 13 +- .../methods/guest/src/main.rs | 4 +- packages/evm/contracts/RFVoting.sol | 27 - packages/risc0/Cargo.lock | 621 +++++- packages/risc0/Cargo.toml | 3 + packages/risc0/apps/Cargo.toml | 2 + packages/risc0/apps/src/bin/publisher.rs | 157 -- packages/risc0/apps/src/lib.rs | 94 + packages/risc0/contracts/CRISPRisc0.sol | 2 +- packages/risc0/methods/build.rs | 2 + packages/risc0/methods/guest/Cargo.lock | 1730 ++++++++++++++++- packages/risc0/methods/guest/Cargo.toml | 5 +- .../risc0/methods/guest/src/bin/is_even.rs | 35 - .../guest/src/bin/voting.rs} | 17 +- packages/risc0/methods/src/lib.rs | 39 +- packages/server/Cargo.lock | 763 +++++++- packages/server/Cargo.toml | 8 +- packages/server/src/cli/auth.rs | 12 +- packages/server/src/cli/mod.rs | 97 +- packages/server/src/cli/voting.rs | 181 +- .../server/src/enclave_server/listener.rs | 120 ++ .../src/enclave_server/routes/voting.rs | 110 +- 26 files changed, 3589 insertions(+), 680 deletions(-) delete mode 100644 packages/risc0/apps/src/bin/publisher.rs create mode 100644 packages/risc0/apps/src/lib.rs delete mode 100644 packages/risc0/methods/guest/src/bin/is_even.rs rename packages/risc0/{contracts/Elf.sol => methods/guest/src/bin/voting.rs} (63%) create mode 100644 packages/server/src/enclave_server/listener.rs diff --git a/packages/compute_provider/core/src/lib.rs b/packages/compute_provider/core/src/lib.rs index 788b65e..01e07a7 100644 --- a/packages/compute_provider/core/src/lib.rs +++ b/packages/compute_provider/core/src/lib.rs @@ -1,9 +1,9 @@ pub mod merkle_tree; -use merkle_tree::MerkleTree; -use std::sync::Arc; use fhe::bfv::{BfvParameters, Ciphertext}; use fhe_traits::{Deserialize, DeserializeParametrized, Serialize}; +use merkle_tree::MerkleTree; +use std::sync::Arc; #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct ComputationResult { @@ -12,38 +12,76 @@ pub struct ComputationResult { } #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] -pub struct ComputationInput { +pub struct CiphertextInputs { pub ciphertexts: Vec>, pub params: Vec, +} + +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct ComputationInput { + pub ciphertexts: C, pub leaf_hashes: Vec, pub tree_depth: usize, pub zero_node: String, pub arity: usize, } -impl ComputationInput { - pub fn process(&self) -> ComputationResult { - // Deserialize the parameters - let params = Arc::new(BfvParameters::try_deserialize(&self.params).unwrap()); +pub trait CiphertextProcessor { + fn process_ciphertexts(&self) -> Vec; - // Tally the ciphertexts - let mut sum = Ciphertext::zero(¶ms); - for ciphertext_bytes in &self.ciphertexts { - let ciphertext = Ciphertext::from_bytes(ciphertext_bytes, ¶ms).unwrap(); - sum += &ciphertext; - } - let tally: Arc = Arc::new(sum); + fn get_ciphertexts(&self) -> &[Vec]; + + fn get_params(&self) -> &[u8]; +} + +impl ComputationInput { + pub fn process(&self) -> ComputationResult { + let processed_ciphertext = self.ciphertexts.process_ciphertexts(); let merkle_root = MerkleTree { leaf_hashes: self.leaf_hashes.clone(), tree_depth: self.tree_depth, zero_node: self.zero_node.clone(), arity: self.arity, - }.build_tree().root().unwrap(); + } + .build_tree() + .root() + .unwrap(); ComputationResult { - ciphertext: tally.to_bytes(), - merkle_root + ciphertext: processed_ciphertext, + merkle_root, } } + + pub fn get_ciphertexts(&self) -> &[Vec] { + self.ciphertexts.get_ciphertexts() + } + + pub fn get_params(&self) -> &[u8] { + self.ciphertexts.get_params() + } } + +impl CiphertextProcessor for CiphertextInputs { + /// Default implementation of the process_ciphertexts method + fn process_ciphertexts(&self) -> Vec { + let params = Arc::new(BfvParameters::try_deserialize(&self.params).unwrap()); + + let mut sum = Ciphertext::zero(¶ms); + for ciphertext_bytes in &self.ciphertexts { + let ciphertext = Ciphertext::from_bytes(ciphertext_bytes, ¶ms).unwrap(); + sum += &ciphertext; + } + + sum.to_bytes() + } + + fn get_ciphertexts(&self) -> &[Vec] { + &self.ciphertexts + } + + fn get_params(&self) -> &[u8] { + &self.params + } +} \ No newline at end of file diff --git a/packages/compute_provider/host/Cargo.toml b/packages/compute_provider/host/Cargo.toml index eadff92..2bcff2c 100644 --- a/packages/compute_provider/host/Cargo.toml +++ b/packages/compute_provider/host/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -methods = { path = "../methods" } +compute-provider-methods = { path = "../methods" } risc0-zkvm = { workspace = true } risc0-build-ethereum = { workspace = true } risc0-ethereum-contracts = { workspace = true } diff --git a/packages/compute_provider/host/src/lib.rs b/packages/compute_provider/host/src/lib.rs index 903e246..dc11873 100644 --- a/packages/compute_provider/host/src/lib.rs +++ b/packages/compute_provider/host/src/lib.rs @@ -1,27 +1,23 @@ -use compute_provider_core::{merkle_tree::MerkleTree, ComputationInput, ComputationResult}; -use methods::COMPUTE_PROVIDER_ELF; +use compute_provider_core::{ + merkle_tree::MerkleTree, CiphertextInputs, CiphertextProcessor, ComputationInput, + ComputationResult, +}; +use rayon::prelude::*; use risc0_ethereum_contracts::groth16; use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts, VerifierContext}; use std::sync::Arc; -use rayon::prelude::*; -pub struct ComputeProvider { - input: ComputationInput, +pub struct ComputeProvider { + input: ComputationInput, use_parallel: bool, batch_size: Option, } -impl ComputeProvider { - pub fn new( - ciphertexts: Vec>, - params: Vec, - use_parallel: bool, - batch_size: Option, - ) -> Self { +impl ComputeProvider { + pub fn new(ciphertexts: C, use_parallel: bool, batch_size: Option) -> Self { Self { input: ComputationInput { ciphertexts, - params, leaf_hashes: Vec::new(), tree_depth: 10, zero_node: String::from("0"), @@ -32,25 +28,25 @@ impl ComputeProvider { } } - pub fn start(&mut self) -> (ComputationResult, Vec) { + pub fn start(&mut self, elf: &[u8]) -> (ComputationResult, Vec) { tracing_subscriber::fmt() .with_env_filter(tracing_subscriber::filter::EnvFilter::from_default_env()) .init(); if self.use_parallel { - self.start_parallel() + self.start_parallel(elf) } else { - self.start_sequential() + self.start_sequential(elf) } } - fn start_sequential(&mut self) -> (ComputationResult, Vec) { + fn start_sequential(&mut self, elf: &[u8]) -> (ComputationResult, Vec) { let mut tree_handler = MerkleTree::new( self.input.tree_depth, self.input.zero_node.clone(), self.input.arity, ); - tree_handler.compute_leaf_hashes(&self.input.ciphertexts); + tree_handler.compute_leaf_hashes(&self.input.get_ciphertexts()); self.input.leaf_hashes = tree_handler.leaf_hashes.clone(); let env = ExecutorEnv::builder() @@ -63,7 +59,7 @@ impl ComputeProvider { .prove_with_ctx( env, &VerifierContext::default(), - COMPUTE_PROVIDER_ELF, + elf, &ProverOpts::groth16(), ) .unwrap() @@ -74,62 +70,79 @@ impl ComputeProvider { (receipt.journal.decode().unwrap(), seal) } - fn start_parallel(&self) -> (ComputationResult, Vec) { + fn start_parallel(&self, elf: &[u8]) -> (ComputationResult, Vec) { let batch_size = self.batch_size.unwrap_or(1); let parallel_tree_depth = (batch_size as f64).log2().ceil() as usize; - let ciphertexts = Arc::new(self.input.ciphertexts.clone()); - let params = Arc::new(self.input.params.clone()); + let ciphertexts = Arc::new(self.input.get_ciphertexts()); + let params = Arc::new(self.input.get_params()); let chunks: Vec>> = ciphertexts .chunks(batch_size) .map(|chunk| chunk.to_vec()) .collect(); - let tally_results: Vec = chunks.into_par_iter().map(|chunk| { - let mut tree_handler = MerkleTree::new(parallel_tree_depth, "0".to_string(), 2); - tree_handler.compute_leaf_hashes(&chunk); - - let input = ComputationInput { - ciphertexts: chunk.clone(), - params: params.to_vec(), - leaf_hashes: tree_handler.leaf_hashes.clone(), - tree_depth: parallel_tree_depth, - zero_node: "0".to_string(), - arity: 2, - }; - - let env = ExecutorEnv::builder() - .write(&input) - .unwrap() - .build() - .unwrap(); - - let receipt = default_prover() - .prove_with_ctx( - env, - &VerifierContext::default(), - COMPUTE_PROVIDER_ELF, - &ProverOpts::groth16(), - ) - .unwrap() - .receipt; - - receipt.journal.decode().unwrap() - }).collect(); + let tally_results: Vec = chunks + .into_par_iter() + .map(|chunk| { + let mut tree_handler = MerkleTree::new(parallel_tree_depth, "0".to_string(), 2); + tree_handler.compute_leaf_hashes(&chunk); + + let input = ComputationInput { + ciphertexts: CiphertextInputs { + ciphertexts: chunk.clone(), + params: params.to_vec(), // Params are shared across chunks + }, + leaf_hashes: tree_handler.leaf_hashes.clone(), + tree_depth: parallel_tree_depth, + zero_node: "0".to_string(), + arity: 2, + }; + + let env = ExecutorEnv::builder() + .write(&input) + .unwrap() + .build() + .unwrap(); + + let receipt = default_prover() + .prove_with_ctx( + env, + &VerifierContext::default(), + elf, + &ProverOpts::groth16(), + ) + .unwrap() + .receipt; + + receipt.journal.decode().unwrap() + }) + .collect(); // Combine the sorted results for final computation let final_depth = self.input.tree_depth - parallel_tree_depth; let mut final_input = ComputationInput { - ciphertexts: tally_results.iter().map(|result| result.ciphertext.clone()).collect(), - params: params.to_vec(), - leaf_hashes: tally_results.iter().map(|result| result.merkle_root.clone()).collect(), + ciphertexts: CiphertextInputs { + ciphertexts: tally_results + .iter() + .map(|result| result.ciphertext.clone()) + .collect(), + params: params.to_vec(), + }, + leaf_hashes: tally_results + .iter() + .map(|result| result.merkle_root.clone()) + .collect(), tree_depth: final_depth, zero_node: String::from("0"), arity: 2, }; - let final_tree_handler = MerkleTree::new(final_depth, final_input.zero_node.clone(), final_input.arity); + let final_tree_handler = MerkleTree::new( + final_depth, + final_input.zero_node.clone(), + final_input.arity, + ); final_input.zero_node = final_tree_handler.zeroes()[parallel_tree_depth].clone(); let env = ExecutorEnv::builder() @@ -142,7 +155,7 @@ impl ComputeProvider { .prove_with_ctx( env, &VerifierContext::default(), - COMPUTE_PROVIDER_ELF, + elf, &ProverOpts::groth16(), ) .unwrap() @@ -153,10 +166,10 @@ impl ComputeProvider { } } - #[cfg(test)] mod tests { use super::*; + use compute_provider_methods::COMPUTE_PROVIDER_ELF; use fhe::bfv::{ BfvParameters, BfvParametersBuilder, Ciphertext, Encoding, Plaintext, PublicKey, SecretKey, }; @@ -173,13 +186,13 @@ mod tests { let inputs = vec![1, 1, 0, 1]; let ciphertexts = encrypt_inputs(&inputs, &pk, ¶ms); - let mut provider = ComputeProvider::new( - ciphertexts.iter().map(|c| c.to_bytes()).collect(), - params.to_bytes(), - true, - Some(2), - ); // use_parallel = false, no batch size - let (result, _seal) = provider.start(); + let ciphertext_inputs = CiphertextInputs { + ciphertexts: ciphertexts.iter().map(|c| c.to_bytes()).collect(), + params: params.to_bytes(), + }; + + let mut provider = ComputeProvider::new(ciphertext_inputs, false, None); + let (result, _seal) = provider.start(COMPUTE_PROVIDER_ELF); let tally = decrypt_result(&result, &sk, ¶ms); @@ -218,7 +231,11 @@ mod tests { .collect() } - fn decrypt_result(result: &ComputationResult, sk: &SecretKey, params: &Arc) -> u64 { + fn decrypt_result( + result: &ComputationResult, + sk: &SecretKey, + params: &Arc, + ) -> u64 { let ct = Ciphertext::from_bytes(&result.ciphertext, params) .expect("Failed to deserialize ciphertext"); let decrypted = sk.try_decrypt(&ct).expect("Failed to decrypt"); diff --git a/packages/compute_provider/methods/Cargo.toml b/packages/compute_provider/methods/Cargo.toml index 31add6d..dfe6fc7 100644 --- a/packages/compute_provider/methods/Cargo.toml +++ b/packages/compute_provider/methods/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "methods" +name = "compute-provider-methods" version = "0.1.0" edition = "2021" diff --git a/packages/compute_provider/methods/build.rs b/packages/compute_provider/methods/build.rs index d34cb57..08a8a4e 100644 --- a/packages/compute_provider/methods/build.rs +++ b/packages/compute_provider/methods/build.rs @@ -1,12 +1,3 @@ - -const SOLIDITY_IMAGE_ID_PATH: &str = "../../risc0/contracts/ImageID.sol"; -const SOLIDITY_ELF_PATH: &str = "../../risc0/contracts/Elf.sol"; - fn main() { - let guests = risc0_build::embed_methods(); - let solidity_opts = risc0_build_ethereum::Options::default() - .with_image_id_sol_path(SOLIDITY_IMAGE_ID_PATH) - .with_elf_sol_path(SOLIDITY_ELF_PATH); - - risc0_build_ethereum::generate_solidity_files(guests.as_slice(), &solidity_opts).unwrap(); -} \ No newline at end of file + risc0_build::embed_methods(); +} diff --git a/packages/compute_provider/methods/guest/src/main.rs b/packages/compute_provider/methods/guest/src/main.rs index ee76599..8199645 100644 --- a/packages/compute_provider/methods/guest/src/main.rs +++ b/packages/compute_provider/methods/guest/src/main.rs @@ -1,9 +1,9 @@ use risc0_zkvm::guest::env; -use compute_provider_core::{ComputationInput, ComputationResult}; +use compute_provider_core::{ComputationInput, CiphertextInputs, ComputationResult}; fn main() { - let input: ComputationInput = env::read(); + let input: ComputationInput = env::read(); let result: ComputationResult = input.process(); diff --git a/packages/evm/contracts/RFVoting.sol b/packages/evm/contracts/RFVoting.sol index 63a6986..347e13d 100644 --- a/packages/evm/contracts/RFVoting.sol +++ b/packages/evm/contracts/RFVoting.sol @@ -14,41 +14,14 @@ contract RFVoting { function voteEncrypted(bytes memory _encVote) public { id++; - //votes[msg.sender] = _encVote; emit Voted(msg.sender, _encVote); } - // function getVote(address id) public returns(bytes memory) { - // return votes[id]; - // } - - //Todo gatekeep modular, ie Bright ID extension function register() public { - // write custom validation code here isValidVoter[msg.sender] = true; } function createPoll() public { pollNonce++; } - - function getPoll(uint256 _pollId) public { - - } - - function submitCoordintatiorPKEY(bytes memory _coordPKEY, uint256 _pollId) public { - - } - - function finalizeVote(uint256 _pollId) public { - - } - - function submitFHEResult(bytes memory _fheResult, uint256 _pollId) public { - - } - - function disputeFHEResult() public { - // reality.eth - } } diff --git a/packages/risc0/Cargo.lock b/packages/risc0/Cargo.lock index e780ef7..18d295a 100644 --- a/packages/risc0/Cargo.lock +++ b/packages/risc0/Cargo.lock @@ -265,6 +265,8 @@ dependencies = [ "alloy-sol-types 0.6.4", "anyhow", "clap", + "compute-provider-core", + "compute-provider-host", "env_logger", "ethers", "log", @@ -440,7 +442,7 @@ dependencies = [ "ark-ff 0.4.2", "ark-std 0.4.0", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.2.25", ] [[package]] @@ -670,6 +672,12 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + [[package]] name = "block-buffer" version = "0.10.4" @@ -681,16 +689,41 @@ dependencies = [ [[package]] name = "bonsai-sdk" -version = "0.8.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7781292e9bcc1f54de6839dbab88b4032d2a20ab1d4fb3d8f045e9cecf5486e" +checksum = "68872e247f6fcf694ecbb884832a705cb2ae09f239cbbcc8bf71ed593d609a45" dependencies = [ + "duplicate", + "maybe-async", "reqwest 0.12.4", - "risc0-groth16", "serde", "thiserror", ] +[[package]] +name = "borsh" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.66", + "syn_derive", +] + [[package]] name = "bs58" version = "0.5.1" @@ -818,6 +851,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.38" @@ -935,6 +974,50 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +[[package]] +name = "compute-provider-core" +version = "0.1.0" +dependencies = [ + "ark-bn254", + "ark-ff 0.4.2", + "fhe", + "fhe-traits", + "hex", + "light-poseidon", + "num-bigint", + "num-traits", + "risc0-zkp", + "risc0-zkvm", + "serde", + "sha3", + "zk-kit-imt", +] + +[[package]] +name = "compute-provider-host" +version = "0.1.0" +dependencies = [ + "compute-provider-core", + "compute-provider-methods", + "fhe", + "fhe-traits", + "rand", + "rayon", + "risc0-build-ethereum", + "risc0-ethereum-contracts", + "risc0-zkvm", + "serde", + "tracing-subscriber 0.3.18", +] + +[[package]] +name = "compute-provider-methods" +version = "0.1.0" +dependencies = [ + "risc0-build", + "risc0-build-ethereum", +] + [[package]] name = "const-hex" version = "1.12.0" @@ -982,6 +1065,17 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + [[package]] name = "cpufeatures" version = "0.2.12" @@ -1174,6 +1268,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "docker-generate" version = "0.1.3" @@ -1192,6 +1292,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" +[[package]] +name = "duplicate" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de78e66ac9061e030587b2a2e75cc88f22304913c907b11307bca737141230cb" +dependencies = [ + "heck 0.4.1", + "proc-macro-error", +] + [[package]] name = "ecdsa" version = "0.16.9" @@ -1621,6 +1731,12 @@ dependencies = [ "yansi", ] +[[package]] +name = "ethnum" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b90ca2580b73ab6a1f724b76ca11ab632df820fd6040c336200d2c1df7b3c82c" + [[package]] name = "eyre" version = "0.6.12" @@ -1658,6 +1774,70 @@ dependencies = [ "subtle", ] +[[package]] +name = "fhe" +version = "0.1.0-beta.7" +source = "git+https://github.com/gnosisguild/fhe.rs#93ce9d45374bdc52ad02e5320a11e13eb777e1eb" +dependencies = [ + "doc-comment", + "fhe-math", + "fhe-traits", + "fhe-util", + "itertools 0.12.1", + "ndarray", + "num-bigint", + "num-traits", + "prost 0.12.6", + "prost-build", + "rand", + "rand_chacha", + "serde", + "thiserror", + "zeroize", + "zeroize_derive", +] + +[[package]] +name = "fhe-math" +version = "0.1.0-beta.7" +source = "git+https://github.com/gnosisguild/fhe.rs#93ce9d45374bdc52ad02e5320a11e13eb777e1eb" +dependencies = [ + "ethnum", + "fhe-traits", + "fhe-util", + "itertools 0.12.1", + "ndarray", + "num-bigint", + "num-bigint-dig", + "num-traits", + "prost 0.12.6", + "prost-build", + "rand", + "rand_chacha", + "sha2", + "thiserror", + "zeroize", +] + +[[package]] +name = "fhe-traits" +version = "0.1.0-beta.7" +source = "git+https://github.com/gnosisguild/fhe.rs#93ce9d45374bdc52ad02e5320a11e13eb777e1eb" +dependencies = [ + "rand", +] + +[[package]] +name = "fhe-util" +version = "0.1.0-beta.7" +source = "git+https://github.com/gnosisguild/fhe.rs#93ce9d45374bdc52ad02e5320a11e13eb777e1eb" +dependencies = [ + "itertools 0.12.1", + "num-bigint-dig", + "num-traits", + "rand", +] + [[package]] name = "fixed-hash" version = "0.8.0" @@ -1692,6 +1872,33 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -2370,7 +2577,7 @@ dependencies = [ "lalrpop-util", "petgraph", "regex", - "regex-syntax", + "regex-syntax 0.8.3", "string_cache", "term", "tiny-keccak", @@ -2384,7 +2591,30 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" dependencies = [ - "regex-automata", + "regex-automata 0.4.6", +] + +[[package]] +name = "lazy-regex" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d8e41c97e6bc7ecb552016274b99fbb5d035e8de288c582d9b933af6677bfda" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e1d8b05d672c53cb9c7b920bbba8783845ae4f0b076e02a3db1d02c81b4163" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.66", ] [[package]] @@ -2392,6 +2622,9 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] [[package]] name = "libc" @@ -2415,6 +2648,18 @@ dependencies = [ "libc", ] +[[package]] +name = "light-poseidon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" +dependencies = [ + "ark-bn254", + "ark-ff 0.4.2", + "num-bigint", + "thiserror", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -2437,6 +2682,45 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matrixmultiply" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "maybe-async" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "md-5" version = "0.10.6" @@ -2453,6 +2737,21 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +[[package]] +name = "metal" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" +dependencies = [ + "bitflags 2.5.0", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", +] + [[package]] name = "methods" version = "0.1.0" @@ -2492,12 +2791,41 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" + +[[package]] +name = "ndarray" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "rawpointer", +] + [[package]] name = "new_debug_unreachable" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint" version = "0.4.5" @@ -2508,6 +2836,32 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "serde", + "smallvec", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -2523,6 +2877,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2564,6 +2929,15 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + [[package]] name = "object" version = "0.35.0" @@ -2610,6 +2984,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parity-scale-codec" version = "3.6.12" @@ -2947,7 +3327,7 @@ dependencies = [ "rand", "rand_chacha", "rand_xorshift", - "regex-syntax", + "regex-syntax 0.8.3", "rusty-fork", "tempfile", "unarray", @@ -2960,7 +3340,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.12.6", +] + +[[package]] +name = "prost" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2ecbe40f08db5c006b5764a2645f7f3f141ce756412ac9e1dd6087e6d32995" +dependencies = [ + "bytes", + "prost-derive 0.13.2", +] + +[[package]] +name = "prost-build" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" +dependencies = [ + "bytes", + "heck 0.5.0", + "itertools 0.12.1", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost 0.12.6", + "prost-types", + "regex", + "syn 2.0.66", + "tempfile", ] [[package]] @@ -2976,6 +3387,28 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "prost-derive" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost 0.12.6", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -3036,6 +3469,12 @@ dependencies = [ "rand_core", ] +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon" version = "1.10.0" @@ -3084,8 +3523,17 @@ checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.6", + "regex-syntax 0.8.3", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -3096,9 +3544,15 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.3", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.3" @@ -3179,10 +3633,12 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-rustls 0.25.0", + "tokio-util", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", "webpki-roots 0.26.1", "winreg 0.52.0", @@ -3239,11 +3695,12 @@ dependencies = [ [[package]] name = "risc0-binfmt" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a33ca13e8e2fe08fc283accbb08fcbabbfdd27acf88dddc9b39654d0e487b15" +checksum = "fbac77ca59e4e1d765141d93ca72f13b632e374c69ae1b18a770b425aeecafce" dependencies = [ "anyhow", + "borsh", "elf", "risc0-zkp", "risc0-zkvm-platform", @@ -3253,14 +3710,15 @@ dependencies = [ [[package]] name = "risc0-build" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b68a9c8dbae8e14ec00e0cc4deca152a4f7d815aa5f745b24a9469ff08c8495d" +checksum = "9f50757e90ff58c227a46774ecda4c927aefa3567f0851d341def452c098737b" dependencies = [ "anyhow", "cargo_metadata", "dirs", "docker-generate", + "hex", "risc0-binfmt", "risc0-zkp", "risc0-zkvm-platform", @@ -3282,13 +3740,14 @@ dependencies = [ [[package]] name = "risc0-circuit-recursion" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c12ea07079420272e5705baea6a0756b21c0dadeca7ed34a7866eb9c073b9a0" +checksum = "cabb4f5abdab268f8974d9e90ce09752be57f2cadc8ad6f7fae9a15a6ddc6773" dependencies = [ "anyhow", "bytemuck", "hex", + "metal", "risc0-core", "risc0-zkp", "tracing", @@ -3296,11 +3755,12 @@ dependencies = [ [[package]] name = "risc0-circuit-rv32im" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef57b3afe8e59bec6f535c49c99dc7cd3fda7e93254fd499e5469ec17fec1d0" +checksum = "8473dbe644e94a679d13b7e53a28c7c086b260a43d426259b5d74c862f2727de" dependencies = [ "anyhow", + "metal", "risc0-binfmt", "risc0-core", "risc0-zkp", @@ -3311,9 +3771,9 @@ dependencies = [ [[package]] name = "risc0-core" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43b7bd8b9adb8bed7eaecfa5c152b6c676c4512aea1120d2cdc5fbbca4b2ffb" +checksum = "0de09abf5e0b102e69d96213e643fd7ae320ed0ca3fad3cd4eed8ce5fbab06bc" dependencies = [ "bytemuck", "rand_core", @@ -3332,9 +3792,9 @@ dependencies = [ [[package]] name = "risc0-groth16" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e275963cd541e1bc9b94f36e23e85b87798a96e04fdf7b013500c08b949a8c9" +checksum = "17e33bc37e7797d663b1e780f09cb295ffeb80712621bf3003ce79f56b66033d" dependencies = [ "anyhow", "ark-bn254", @@ -3352,17 +3812,19 @@ dependencies = [ [[package]] name = "risc0-zkp" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53342780aef2d31ccc0526e6d4b151dab69e678d2e1495b2270ed40f5e1df6f4" +checksum = "83ae2c52905c83a62275ec75ddb60b8cdcf2388ae4add58a727f68822b4be93c" dependencies = [ "anyhow", "blake2", + "borsh", "bytemuck", "cfg-if", "digest 0.10.7", "hex", "hex-literal", + "metal", "paste", "rand_core", "risc0-core", @@ -3374,20 +3836,22 @@ dependencies = [ [[package]] name = "risc0-zkvm" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "774b03337fa1675204a067b3f15be740dbedde63fa46647017140fd023805afb" +checksum = "f99159c6f87ab49222d44a68b2de5bc3182b177d6307fb1eed6f1c43e5baa163" dependencies = [ "anyhow", "bincode", "bonsai-sdk", + "borsh", "bytemuck", "bytes", - "cfg-if", "getrandom", "hex", - "prost", + "lazy-regex", + "prost 0.13.2", "risc0-binfmt", + "risc0-build", "risc0-circuit-recursion", "risc0-circuit-rv32im", "risc0-core", @@ -3398,19 +3862,21 @@ dependencies = [ "semver 1.0.23", "serde", "sha2", + "stability", "tempfile", "tracing", ] [[package]] name = "risc0-zkvm-platform" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b8df83bfa425e078ef77ed115f5eff26b5ebc4a584253b31e4e7122fa2bcced" +checksum = "12c53def950c8c8d25f9256af2a3e02a6284774c8ee31eed5d56c3533fbcec2e" dependencies = [ "bytemuck", "getrandom", "libm", + "stability", ] [[package]] @@ -3831,6 +4297,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -3929,6 +4404,16 @@ dependencies = [ "der", ] +[[package]] +name = "stability" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" +dependencies = [ + "quote", + "syn 2.0.66", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -4048,6 +4533,18 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "sync_wrapper" version = "0.1.2" @@ -4133,6 +4630,16 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" version = "0.3.36" @@ -4382,6 +4889,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.2.25" @@ -4391,6 +4909,24 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -4631,6 +5167,19 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +[[package]] +name = "wasm-streams" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.69" @@ -4958,6 +5507,16 @@ dependencies = [ "zstd", ] +[[package]] +name = "zk-kit-imt" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4bec228e2acafec7bd22c9a0a5e0c5e2a6d0c17df69f5ad11c24ce6dc6356c6" +dependencies = [ + "hex", + "tiny-keccak", +] + [[package]] name = "zstd" version = "0.11.2+zstd.1.5.2" diff --git a/packages/risc0/Cargo.toml b/packages/risc0/Cargo.toml index 1fd0f20..4f3b15c 100644 --- a/packages/risc0/Cargo.toml +++ b/packages/risc0/Cargo.toml @@ -23,6 +23,9 @@ risc0-ethereum-contracts = { git = "https://github.com/risc0/risc0-ethereum", ta risc0-zkvm = { version = "1.0", default-features = false } risc0-zkp = { version = "1.0", default-features = false } serde = { version = "1.0", features = ["derive", "std"] } +compute-provider-host = { path = "../compute_provider/host" } +compute-provider-core = { path = "../compute_provider/core" } + [profile.release] debug = 1 diff --git a/packages/risc0/apps/Cargo.toml b/packages/risc0/apps/Cargo.toml index fa913fe..9a70327 100644 --- a/packages/risc0/apps/Cargo.toml +++ b/packages/risc0/apps/Cargo.toml @@ -15,3 +15,5 @@ methods = { workspace = true } risc0-ethereum-contracts = { workspace = true } risc0-zkvm = { workspace = true, features = ["client"] } tokio = { version = "1.35", features = ["full"] } +compute-provider-host = { workspace = true } +compute-provider-core = { workspace = true } \ No newline at end of file diff --git a/packages/risc0/apps/src/bin/publisher.rs b/packages/risc0/apps/src/bin/publisher.rs deleted file mode 100644 index 4724071..0000000 --- a/packages/risc0/apps/src/bin/publisher.rs +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2024 RISC Zero, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// This application demonstrates how to send an off-chain proof request -// to the Bonsai proving service and publish the received proofs directly -// to your deployed app contract. - -use alloy_primitives::U256; -use alloy_sol_types::{sol, SolInterface, SolValue}; -use anyhow::{Context, Result}; -use clap::Parser; -use ethers::prelude::*; -use methods::IS_EVEN_ELF; -use risc0_ethereum_contracts::groth16; -use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts, VerifierContext}; - -// `IEvenNumber` interface automatically generated via the alloy `sol!` macro. -sol! { - interface IEvenNumber { - function set(uint256 x, bytes calldata seal); - } -} - -/// Wrapper of a `SignerMiddleware` client to send transactions to the given -/// contract's `Address`. -pub struct TxSender { - chain_id: u64, - client: SignerMiddleware, Wallet>, - contract: Address, -} - -impl TxSender { - /// Creates a new `TxSender`. - pub fn new(chain_id: u64, rpc_url: &str, private_key: &str, contract: &str) -> Result { - let provider = Provider::::try_from(rpc_url)?; - let wallet: LocalWallet = private_key.parse::()?.with_chain_id(chain_id); - let client = SignerMiddleware::new(provider.clone(), wallet.clone()); - let contract = contract.parse::

()?; - - Ok(TxSender { - chain_id, - client, - contract, - }) - } - - /// Send a transaction with the given calldata. - pub async fn send(&self, calldata: Vec) -> Result> { - let tx = TransactionRequest::new() - .chain_id(self.chain_id) - .to(self.contract) - .from(self.client.address()) - .data(calldata); - - log::info!("Transaction request: {:?}", &tx); - - let tx = self.client.send_transaction(tx, None).await?.await?; - - log::info!("Transaction receipt: {:?}", &tx); - - Ok(tx) - } -} - -/// Arguments of the publisher CLI. -#[derive(Parser, Debug)] -#[clap(author, version, about, long_about = None)] -struct Args { - /// Ethereum chain ID - #[clap(long)] - chain_id: u64, - - /// Ethereum Node endpoint. - #[clap(long, env)] - eth_wallet_private_key: String, - - /// Ethereum Node endpoint. - #[clap(long)] - rpc_url: String, - - /// Application's contract address on Ethereum - #[clap(long)] - contract: String, - - /// The input to provide to the guest binary - #[clap(short, long)] - input: U256, -} - -fn main() -> Result<()> { - env_logger::init(); - // Parse CLI Arguments: The application starts by parsing command-line arguments provided by the user. - let args = Args::parse(); - - // Create a new transaction sender using the parsed arguments. - let tx_sender = TxSender::new( - args.chain_id, - &args.rpc_url, - &args.eth_wallet_private_key, - &args.contract, - )?; - - // ABI encode input: Before sending the proof request to the Bonsai proving service, - // the input number is ABI-encoded to match the format expected by the guest code running in the zkVM. - let input = args.input.abi_encode(); - - let env = ExecutorEnv::builder().write_slice(&input).build()?; - - let receipt = default_prover() - .prove_with_ctx( - env, - &VerifierContext::default(), - IS_EVEN_ELF, - &ProverOpts::groth16(), - )? - .receipt; - - // Encode the seal with the selector. - let seal = groth16::encode(receipt.inner.groth16()?.seal.clone())?; - - // Extract the journal from the receipt. - let journal = receipt.journal.bytes.clone(); - - // Decode Journal: Upon receiving the proof, the application decodes the journal to extract - // the verified number. This ensures that the number being submitted to the blockchain matches - // the number that was verified off-chain. - let x = U256::abi_decode(&journal, true).context("decoding journal data")?; - - // Construct function call: Using the IEvenNumber interface, the application constructs - // the ABI-encoded function call for the set function of the EvenNumber contract. - // This call includes the verified number, the post-state digest, and the seal (proof). - let calldata = IEvenNumber::IEvenNumberCalls::set(IEvenNumber::setCall { - x, - seal: seal.into(), - }) - .abi_encode(); - - // Initialize the async runtime environment to handle the transaction sending. - let runtime = tokio::runtime::Runtime::new()?; - - // Send transaction: Finally, the TxSender component sends the transaction to the Ethereum blockchain, - // effectively calling the set function of the EvenNumber contract with the verified number and proof. - runtime.block_on(tx_sender.send(calldata))?; - - Ok(()) -} diff --git a/packages/risc0/apps/src/lib.rs b/packages/risc0/apps/src/lib.rs new file mode 100644 index 0000000..cda2282 --- /dev/null +++ b/packages/risc0/apps/src/lib.rs @@ -0,0 +1,94 @@ +// src/lib.rs + +use alloy_sol_types::{sol, SolInterface, SolValue}; +use alloy_primitives::U256; +use anyhow::{Context, Result}; +use ethers::prelude::*; +use methods::VOTING_ELF; +use compute_provider_host::ComputeProvider; +use compute_provider_core::CiphertextInputs; + +sol! { + interface ICRISPRisc0 { + function verify(uint256 e3Id, bytes memory data); + } +} + +pub struct TxSender { + chain_id: u64, + client: SignerMiddleware, Wallet>, + contract: Address, +} + +impl TxSender { + pub fn new(chain_id: u64, rpc_url: &str, private_key: &str, contract: &str) -> Result { + let provider = Provider::::try_from(rpc_url)?; + let wallet: LocalWallet = private_key.parse::()?.with_chain_id(chain_id); + let client = SignerMiddleware::new(provider.clone(), wallet.clone()); + let contract = contract.parse::
()?; + + Ok(TxSender { + chain_id, + client, + contract, + }) + } + + pub async fn send(&self, calldata: Vec) -> Result> { + let tx = TransactionRequest::new() + .chain_id(self.chain_id) + .to(self.contract) + .from(self.client.address()) + .data(calldata); + + log::info!("Transaction request: {:?}", &tx); + + let tx = self.client.send_transaction(tx, None).await?.await?; + + log::info!("Transaction receipt: {:?}", &tx); + + Ok(tx) + } +} + +pub struct PublisherParams { + pub chain_id: u64, + pub eth_wallet_private_key: String, + pub rpc_url: String, + pub contract: String, + pub e3_id: U256, + pub ciphertexts: Vec>, + pub params: Vec, +} + +pub fn publish_proof(params: PublisherParams) -> Result<()> { + env_logger::init(); + + let tx_sender = TxSender::new( + params.chain_id, + ¶ms.rpc_url, + ¶ms.eth_wallet_private_key, + ¶ms.contract, + )?; + + let ciphertext_inputs = CiphertextInputs { + ciphertexts: params.ciphertexts, + params: params.params, + }; + + let mut provider = ComputeProvider::new(ciphertext_inputs, false, None); + let (result, seal) = provider.start(VOTING_ELF); + + // TODO: Fix this call, Encode Result and Seal into a single calldata + let calldata = ICRISPRisc0::ICRISPRisc0Calls::verify(ICRISPRisc0::verifyCall { + e3Id: params.e3_id, + data: seal.into(), + }) + .abi_encode(); + + let runtime = tokio::runtime::Runtime::new()?; + + runtime.block_on(tx_sender.send(calldata))?; + + Ok(()) +} diff --git a/packages/risc0/contracts/CRISPRisc0.sol b/packages/risc0/contracts/CRISPRisc0.sol index d9c173f..21e7629 100644 --- a/packages/risc0/contracts/CRISPRisc0.sol +++ b/packages/risc0/contracts/CRISPRisc0.sol @@ -16,7 +16,7 @@ contract CRISPRisc0 is CRISPBase { /// @notice RISC Zero verifier contract address. IRiscZeroVerifier public verifier; /// @notice Image ID of the only zkVM binary to accept verification from. - bytes32 public constant imageId = ImageID.IS_EVEN_ID; // TODO: update this to the CRISP image ID + bytes32 public constant imageId = ImageID.VOTING_ID; // TODO: update this to the CRISP image ID /// @notice Initialize the contract, binding it to a specified RISC Zero verifier. constructor(IEnclave _enclave, IRiscZeroVerifier _verifier) { diff --git a/packages/risc0/methods/build.rs b/packages/risc0/methods/build.rs index 11130e5..5004eb7 100644 --- a/packages/risc0/methods/build.rs +++ b/packages/risc0/methods/build.rs @@ -36,6 +36,8 @@ fn main() { use_docker, }, )])); + println!("Current working directory: {:?}", std::env::current_dir()); + // Generate Solidity source files for use with Forge. let solidity_opts = risc0_build_ethereum::Options::default() diff --git a/packages/risc0/methods/guest/Cargo.lock b/packages/risc0/methods/guest/Cargo.lock index 50a31a2..349e8a7 100644 --- a/packages/risc0/methods/guest/Cargo.lock +++ b/packages/risc0/methods/guest/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "ahash" version = "0.8.11" @@ -14,6 +29,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "alloy-primitives" version = "0.6.4" @@ -59,7 +83,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", "syn-solidity", "tiny-keccak", ] @@ -123,7 +147,7 @@ dependencies = [ "ark-std 0.4.0", "derivative", "hashbrown 0.13.2", - "itertools", + "itertools 0.10.5", "num-traits", "zeroize", ] @@ -158,7 +182,7 @@ dependencies = [ "ark-std 0.4.0", "derivative", "digest 0.10.7", - "itertools", + "itertools 0.10.5", "num-bigint", "num-traits", "paste", @@ -330,7 +354,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -339,18 +363,48 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + [[package]] name = "base16ct" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -366,6 +420,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.5.0" @@ -393,6 +453,12 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + [[package]] name = "block-buffer" version = "0.10.4" @@ -402,6 +468,49 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bonsai-sdk" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68872e247f6fcf694ecbb884832a705cb2ae09f239cbbcc8bf71ed593d609a45" +dependencies = [ + "duplicate", + "maybe-async", + "reqwest", + "serde", + "thiserror", +] + +[[package]] +name = "borsh" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.77", + "syn_derive", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "byte-slice-cast" version = "1.2.2" @@ -425,7 +534,7 @@ checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -443,6 +552,38 @@ dependencies = [ "serde", ] +[[package]] +name = "camino" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.23", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cc" version = "1.0.98" @@ -455,6 +596,31 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "compute-provider-core" +version = "0.1.0" +dependencies = [ + "ark-bn254", + "ark-ff 0.4.2", + "fhe", + "fhe-traits", + "hex", + "light-poseidon", + "num-bigint", + "num-traits", + "risc0-zkp", + "risc0-zkvm", + "serde", + "sha3", + "zk-kit-imt", +] + [[package]] name = "const-hex" version = "1.12.0" @@ -480,6 +646,33 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + [[package]] name = "cpufeatures" version = "0.2.12" @@ -572,6 +765,39 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "docker-generate" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf673e0848ef09fa4aeeba78e681cf651c0c7d35f76ee38cec8e55bc32fa111" + [[package]] name = "downcast-rs" version = "1.2.1" @@ -584,6 +810,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" +[[package]] +name = "duplicate" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de78e66ac9061e030587b2a2e75cc88f22304913c907b11307bca737141230cb" +dependencies = [ + "heck", + "proc-macro-error", +] + [[package]] name = "ecdsa" version = "0.16.9" @@ -642,9 +878,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] +[[package]] +name = "ethnum" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b90ca2580b73ab6a1f724b76ca11ab632df820fd6040c336200d2c1df7b3c82c" + [[package]] name = "fastrand" version = "2.1.0" @@ -672,6 +914,70 @@ dependencies = [ "subtle", ] +[[package]] +name = "fhe" +version = "0.1.0-beta.7" +source = "git+https://github.com/gnosisguild/fhe.rs#93ce9d45374bdc52ad02e5320a11e13eb777e1eb" +dependencies = [ + "doc-comment", + "fhe-math", + "fhe-traits", + "fhe-util", + "itertools 0.12.1", + "ndarray", + "num-bigint", + "num-traits", + "prost 0.12.6", + "prost-build", + "rand", + "rand_chacha", + "serde", + "thiserror", + "zeroize", + "zeroize_derive", +] + +[[package]] +name = "fhe-math" +version = "0.1.0-beta.7" +source = "git+https://github.com/gnosisguild/fhe.rs#93ce9d45374bdc52ad02e5320a11e13eb777e1eb" +dependencies = [ + "ethnum", + "fhe-traits", + "fhe-util", + "itertools 0.12.1", + "ndarray", + "num-bigint", + "num-bigint-dig", + "num-traits", + "prost 0.12.6", + "prost-build", + "rand", + "rand_chacha", + "sha2", + "thiserror", + "zeroize", +] + +[[package]] +name = "fhe-traits" +version = "0.1.0-beta.7" +source = "git+https://github.com/gnosisguild/fhe.rs#93ce9d45374bdc52ad02e5320a11e13eb777e1eb" +dependencies = [ + "rand", +] + +[[package]] +name = "fhe-util" +version = "0.1.0-beta.7" +source = "git+https://github.com/gnosisguild/fhe.rs#93ce9d45374bdc52ad02e5320a11e13eb777e1eb" +dependencies = [ + "itertools 0.12.1", + "num-bigint-dig", + "num-traits", + "rand", +] + [[package]] name = "fixed-hash" version = "0.8.0" @@ -684,18 +990,122 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "funty" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -718,6 +1128,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" + [[package]] name = "group" version = "0.13.0" @@ -735,6 +1151,7 @@ version = "0.1.0" dependencies = [ "alloy-primitives", "alloy-sol-types", + "compute-provider-core", "risc0-zkvm", ] @@ -759,6 +1176,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + [[package]] name = "hex" version = "0.4.3" @@ -784,17 +1207,124 @@ dependencies = [ ] [[package]] -name = "impl-codec" -version = "0.6.0" +name = "http" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ - "parity-scale-codec", + "bytes", + "fnv", + "itoa", ] [[package]] -name = "impl-trait-for-tuples" -version = "0.2.2" +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" + +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" dependencies = [ @@ -813,6 +1343,12 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "ipnet" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" + [[package]] name = "itertools" version = "0.10.5" @@ -822,12 +1358,30 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "js-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "k256" version = "0.13.3" @@ -842,6 +1396,15 @@ dependencies = [ "signature", ] +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + [[package]] name = "keccak-asm" version = "0.1.1" @@ -852,17 +1415,43 @@ dependencies = [ "sha3-asm", ] +[[package]] +name = "lazy-regex" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d8e41c97e6bc7ecb552016274b99fbb5d035e8de288c582d9b933af6677bfda" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e1d8b05d672c53cb9c7b920bbba8783845ae4f0b076e02a3db1d02c81b4163" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.77", +] + [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] [[package]] name = "libc" -version = "0.2.155" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libm" @@ -870,6 +1459,28 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.5.0", + "libc", +] + +[[package]] +name = "light-poseidon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" +dependencies = [ + "ark-bn254", + "ark-ff 0.4.2", + "num-bigint", + "thiserror", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -882,12 +1493,103 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "matrixmultiply" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "maybe-async" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "memchr" version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +[[package]] +name = "metal" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" +dependencies = [ + "bitflags 2.5.0", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" + +[[package]] +name = "ndarray" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "rawpointer", +] + [[package]] name = "num-bigint" version = "0.4.5" @@ -898,6 +1600,32 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "serde", + "smallvec", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -907,6 +1635,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -917,12 +1656,36 @@ dependencies = [ "libm", ] +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "object" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "parity-scale-codec" version = "3.6.12" @@ -955,6 +1718,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "pest" version = "2.7.10" @@ -966,12 +1735,48 @@ dependencies = [ "ucd-trie", ] +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "pin-project-lite" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkcs8" version = "0.10.2" @@ -988,6 +1793,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettyplease" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" +dependencies = [ + "proc-macro2", + "syn 2.0.77", +] + [[package]] name = "primitive-types" version = "0.12.2" @@ -1049,7 +1864,7 @@ checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" dependencies = [ "bit-set", "bit-vec", - "bitflags", + "bitflags 2.5.0", "lazy_static", "num-traits", "rand", @@ -1061,12 +1876,136 @@ dependencies = [ "unarray", ] +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive 0.12.6", +] + +[[package]] +name = "prost" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2ecbe40f08db5c006b5764a2645f7f3f141ce756412ac9e1dd6087e6d32995" +dependencies = [ + "bytes", + "prost-derive 0.13.2", +] + +[[package]] +name = "prost-build" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" +dependencies = [ + "bytes", + "heck", + "itertools 0.10.5", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost 0.12.6", + "prost-types", + "regex", + "syn 2.0.77", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "prost-derive" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" +dependencies = [ + "anyhow", + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost 0.12.6", +] + [[package]] name = "quick-error" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quinn" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +dependencies = [ + "bytes", + "rand", + "ring", + "rustc-hash", + "rustls", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" +dependencies = [ + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "quote" version = "1.0.36" @@ -1121,12 +2060,97 @@ dependencies = [ "rand_core", ] +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + [[package]] name = "regex-syntax" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +[[package]] +name = "reqwest" +version = "0.12.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", + "windows-registry", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -1137,13 +2161,29 @@ dependencies = [ "subtle", ] +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "risc0-binfmt" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a33ca13e8e2fe08fc283accbb08fcbabbfdd27acf88dddc9b39654d0e487b15" +checksum = "fbac77ca59e4e1d765141d93ca72f13b632e374c69ae1b18a770b425aeecafce" dependencies = [ "anyhow", + "borsh", "elf", "risc0-zkp", "risc0-zkvm-platform", @@ -1151,15 +2191,35 @@ dependencies = [ "tracing", ] +[[package]] +name = "risc0-build" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f50757e90ff58c227a46774ecda4c927aefa3567f0851d341def452c098737b" +dependencies = [ + "anyhow", + "cargo_metadata", + "dirs", + "docker-generate", + "hex", + "risc0-binfmt", + "risc0-zkp", + "risc0-zkvm-platform", + "serde", + "serde_json", + "tempfile", +] + [[package]] name = "risc0-circuit-recursion" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c12ea07079420272e5705baea6a0756b21c0dadeca7ed34a7866eb9c073b9a0" +checksum = "cabb4f5abdab268f8974d9e90ce09752be57f2cadc8ad6f7fae9a15a6ddc6773" dependencies = [ "anyhow", "bytemuck", "hex", + "metal", "risc0-core", "risc0-zkp", "tracing", @@ -1167,11 +2227,12 @@ dependencies = [ [[package]] name = "risc0-circuit-rv32im" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef57b3afe8e59bec6f535c49c99dc7cd3fda7e93254fd499e5469ec17fec1d0" +checksum = "8473dbe644e94a679d13b7e53a28c7c086b260a43d426259b5d74c862f2727de" dependencies = [ "anyhow", + "metal", "risc0-binfmt", "risc0-core", "risc0-zkp", @@ -1182,9 +2243,9 @@ dependencies = [ [[package]] name = "risc0-core" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43b7bd8b9adb8bed7eaecfa5c152b6c676c4512aea1120d2cdc5fbbca4b2ffb" +checksum = "0de09abf5e0b102e69d96213e643fd7ae320ed0ca3fad3cd4eed8ce5fbab06bc" dependencies = [ "bytemuck", "rand_core", @@ -1192,9 +2253,9 @@ dependencies = [ [[package]] name = "risc0-groth16" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e275963cd541e1bc9b94f36e23e85b87798a96e04fdf7b013500c08b949a8c9" +checksum = "17e33bc37e7797d663b1e780f09cb295ffeb80712621bf3003ce79f56b66033d" dependencies = [ "anyhow", "ark-bn254", @@ -1212,17 +2273,19 @@ dependencies = [ [[package]] name = "risc0-zkp" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53342780aef2d31ccc0526e6d4b151dab69e678d2e1495b2270ed40f5e1df6f4" +checksum = "83ae2c52905c83a62275ec75ddb60b8cdcf2388ae4add58a727f68822b4be93c" dependencies = [ "anyhow", "blake2", + "borsh", "bytemuck", "cfg-if", "digest 0.10.7", "hex", "hex-literal", + "metal", "paste", "rand_core", "risc0-core", @@ -1234,16 +2297,22 @@ dependencies = [ [[package]] name = "risc0-zkvm" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "774b03337fa1675204a067b3f15be740dbedde63fa46647017140fd023805afb" +checksum = "f99159c6f87ab49222d44a68b2de5bc3182b177d6307fb1eed6f1c43e5baa163" dependencies = [ "anyhow", + "bincode", + "bonsai-sdk", + "borsh", "bytemuck", - "cfg-if", + "bytes", "getrandom", "hex", + "lazy-regex", + "prost 0.13.2", "risc0-binfmt", + "risc0-build", "risc0-circuit-recursion", "risc0-circuit-rv32im", "risc0-core", @@ -1254,18 +2323,21 @@ dependencies = [ "semver 1.0.23", "serde", "sha2", + "stability", + "tempfile", "tracing", ] [[package]] name = "risc0-zkvm-platform" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b8df83bfa425e078ef77ed115f5eff26b5ebc4a584253b31e4e7122fa2bcced" +checksum = "12c53def950c8c8d25f9256af2a3e02a6284774c8ee31eed5d56c3533fbcec2e" dependencies = [ "bytemuck", "getrandom", "libm", + "stability", ] [[package]] @@ -1318,6 +2390,18 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f86854cf50259291520509879a5c294c3c9a4c334e9ff65071c51e42ef1e2343" +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + [[package]] name = "rustc-hex" version = "2.1.0" @@ -1348,11 +2432,52 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +dependencies = [ + "base64", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] @@ -1367,6 +2492,12 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + [[package]] name = "sec1" version = "0.7.3" @@ -1395,6 +2526,9 @@ name = "semver" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +dependencies = [ + "serde", +] [[package]] name = "semver-parser" @@ -1409,20 +2543,44 @@ dependencies = [ name = "serde" version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "serde_json" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ - "serde_derive", + "itoa", + "memchr", + "ryu", + "serde", ] [[package]] -name = "serde_derive" -version = "1.0.203" +name = "serde_urlencoded" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", + "form_urlencoded", + "itoa", + "ryu", + "serde", ] [[package]] @@ -1436,6 +2594,16 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + [[package]] name = "sha3-asm" version = "0.1.1" @@ -1456,6 +2624,43 @@ dependencies = [ "rand_core", ] +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "spki" version = "0.7.3" @@ -1466,6 +2671,16 @@ dependencies = [ "der", ] +[[package]] +name = "stability" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" +dependencies = [ + "quote", + "syn 2.0.77", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -1491,9 +2706,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.66" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -1509,7 +2724,28 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", +] + +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", ] [[package]] @@ -1527,7 +2763,7 @@ dependencies = [ "cfg-if", "fastrand", "rustix", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1547,7 +2783,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -1559,6 +2795,60 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml_datetime" version = "0.6.6" @@ -1576,6 +2866,33 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" version = "0.1.40" @@ -1596,7 +2913,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -1618,6 +2935,12 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typenum" version = "1.17.0" @@ -1648,12 +2971,44 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "valuable" version = "0.1.0" @@ -1675,84 +3030,297 @@ dependencies = [ "libc", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.77", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + +[[package]] +name = "wasm-streams" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.26.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" @@ -1789,7 +3357,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -1809,5 +3377,15 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", +] + +[[package]] +name = "zk-kit-imt" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4bec228e2acafec7bd22c9a0a5e0c5e2a6d0c17df69f5ad11c24ce6dc6356c6" +dependencies = [ + "hex", + "tiny-keccak", ] diff --git a/packages/risc0/methods/guest/Cargo.toml b/packages/risc0/methods/guest/Cargo.toml index 53cb6d9..f6e011b 100644 --- a/packages/risc0/methods/guest/Cargo.toml +++ b/packages/risc0/methods/guest/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2021" [[bin]] -name = "is-even" -path = "src/bin/is_even.rs" +name = "voting" +path = "src/bin/voting.rs" [workspace] @@ -13,6 +13,7 @@ path = "src/bin/is_even.rs" alloy-primitives = { version = "0.6", default-features = false, features = ["rlp", "serde", "std"] } alloy-sol-types = { version = "0.6" } risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] } +compute-provider-core = { path = "../../../compute_provider/core" } [profile.release] lto = "thin" diff --git a/packages/risc0/methods/guest/src/bin/is_even.rs b/packages/risc0/methods/guest/src/bin/is_even.rs deleted file mode 100644 index 9d834b6..0000000 --- a/packages/risc0/methods/guest/src/bin/is_even.rs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2023 RISC Zero, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::io::Read; - -use alloy_primitives::U256; -use alloy_sol_types::SolValue; -use risc0_zkvm::guest::env; - -fn main() { - // Read the input data for this application. - let mut input_bytes = Vec::::new(); - env::stdin().read_to_end(&mut input_bytes).unwrap(); - // Decode and parse the input - let number = ::abi_decode(&input_bytes, true).unwrap(); - - // Run the computation. - // In this case, asserting that the provided number is even. - assert!(!number.bit(0), "number is not even"); - - // Commit the journal that will be received by the application contract. - // Journal is encoded using Solidity ABI for easy decoding in the app contract. - env::commit_slice(number.abi_encode().as_slice()); -} diff --git a/packages/risc0/contracts/Elf.sol b/packages/risc0/methods/guest/src/bin/voting.rs similarity index 63% rename from packages/risc0/contracts/Elf.sol rename to packages/risc0/methods/guest/src/bin/voting.rs index 6ee1ab0..3e4e8da 100644 --- a/packages/risc0/contracts/Elf.sol +++ b/packages/risc0/methods/guest/src/bin/voting.rs @@ -1,4 +1,4 @@ -// Copyright 2024 RISC Zero, Inc. +// Copyright 2023 RISC Zero, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,14 +11,15 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// -// SPDX-License-Identifier: Apache-2.0 -// This file is automatically generated +use risc0_zkvm::guest::env; +use compute_provider_core::{ComputationInput, CiphertextInputs, ComputationResult}; + -pragma solidity ^0.8.20; +fn main() { + let input: ComputationInput = env::read(); + + let result: ComputationResult = input.process(); -library Elf { - string public constant COMPUTE_PROVIDER_PATH = - "/home/ace/main/gnosis/CRISP/packages/server/target/riscv-guest/riscv32im-risc0-zkvm-elf/release/compute_provider"; + env::commit(&result); } diff --git a/packages/risc0/methods/src/lib.rs b/packages/risc0/methods/src/lib.rs index c9fab1b..f2b36d9 100644 --- a/packages/risc0/methods/src/lib.rs +++ b/packages/risc0/methods/src/lib.rs @@ -13,41 +13,4 @@ // limitations under the License. //! Generated crate containing the image ID and ELF binary of the build guest. -include!(concat!(env!("OUT_DIR"), "/methods.rs")); - -#[cfg(test)] -mod tests { - use alloy_primitives::U256; - use alloy_sol_types::SolValue; - use risc0_zkvm::{default_executor, ExecutorEnv}; - - #[test] - fn proves_even_number() { - let even_number = U256::from(1304); - - let env = ExecutorEnv::builder() - .write_slice(&even_number.abi_encode()) - .build() - .unwrap(); - - // NOTE: Use the executor to run tests without proving. - let session_info = default_executor().execute(env, super::IS_EVEN_ELF).unwrap(); - - let x = U256::abi_decode(&session_info.journal.bytes, true).unwrap(); - assert_eq!(x, even_number); - } - - #[test] - #[should_panic(expected = "number is not even")] - fn rejects_odd_number() { - let odd_number = U256::from(75); - - let env = ExecutorEnv::builder() - .write_slice(&odd_number.abi_encode()) - .build() - .unwrap(); - - // NOTE: Use the executor to run tests without proving. - default_executor().execute(env, super::IS_EVEN_ELF).unwrap(); - } -} +include!(concat!(env!("OUT_DIR"), "/methods.rs")); \ No newline at end of file diff --git a/packages/server/Cargo.lock b/packages/server/Cargo.lock index 10355a3..2bb1785 100644 --- a/packages/server/Cargo.lock +++ b/packages/server/Cargo.lock @@ -273,6 +273,219 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "alloy" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4a4aaae80afd4be443a6aecd92a6b255dcdd000f97996928efb33d8a71e100" +dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-core", + "alloy-eips", + "alloy-genesis", + "alloy-network", + "alloy-provider", + "alloy-pubsub", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-serde", + "alloy-signer", + "alloy-signer-local", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", +] + +[[package]] +name = "alloy-chains" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b4f201b0ac8f81315fbdc55269965a8ddadbc04ab47fa65a1a468f9a40f7a5f" +dependencies = [ + "num_enum", + "strum", +] + +[[package]] +name = "alloy-consensus" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04c309895995eaa4bfcc345f5515a39c7df9447798645cc8bf462b6c5bf1dc96" +dependencies = [ + "alloy-eips", + "alloy-primitives 0.7.7", + "alloy-rlp", + "alloy-serde", + "c-kzg", + "serde", +] + +[[package]] +name = "alloy-contract" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4e0ef72b0876ae3068b2ed7dfae9ae1779ce13cfaec2ee1f08f5bd0348dc57" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-network", + "alloy-network-primitives", + "alloy-primitives 0.7.7", + "alloy-provider", + "alloy-pubsub", + "alloy-rpc-types-eth", + "alloy-sol-types 0.7.7", + "alloy-transport", + "futures", + "futures-util", + "thiserror", +] + +[[package]] +name = "alloy-core" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "529fc6310dc1126c8de51c376cbc59c79c7f662bd742be7dc67055d5421a81b4" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-primitives 0.7.7", + "alloy-sol-types 0.7.7", +] + +[[package]] +name = "alloy-dyn-abi" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413902aa18a97569e60f679c23f46a18db1656d87ab4d4e49d0e1e52042f66df" +dependencies = [ + "alloy-json-abi", + "alloy-primitives 0.7.7", + "alloy-sol-type-parser", + "alloy-sol-types 0.7.7", + "const-hex", + "itoa", + "serde", + "serde_json", + "winnow 0.6.8", +] + +[[package]] +name = "alloy-eips" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9431c99a3b3fe606ede4b3d4043bdfbcb780c45b8d8d226c3804e2b75cfbe68" +dependencies = [ + "alloy-primitives 0.7.7", + "alloy-rlp", + "alloy-serde", + "c-kzg", + "derive_more", + "k256", + "once_cell", + "serde", + "sha2", +] + +[[package]] +name = "alloy-genesis" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79614dfe86144328da11098edcc7bc1a3f25ad8d3134a9eb9e857e06f0d9840d" +dependencies = [ + "alloy-primitives 0.7.7", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-json-abi" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc05b04ac331a9f07e3a4036ef7926e49a8bf84a99a1ccfc7e2ab55a5fcbb372" +dependencies = [ + "alloy-primitives 0.7.7", + "alloy-sol-type-parser", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-json-rpc" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57e2865c4c3bb4cdad3f0d9ec1ab5c0c657ba69a375651bd35e32fb6c180ccc2" +dependencies = [ + "alloy-primitives 0.7.7", + "alloy-sol-types 0.7.7", + "serde", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "alloy-network" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e701fc87ef9a3139154b0b4ccb935b565d27ffd9de020fe541bf2dec5ae4ede" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-json-rpc", + "alloy-network-primitives", + "alloy-primitives 0.7.7", + "alloy-rpc-types-eth", + "alloy-serde", + "alloy-signer", + "alloy-sol-types 0.7.7", + "async-trait", + "auto_impl", + "futures-utils-wasm", + "thiserror", +] + +[[package]] +name = "alloy-network-primitives" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec9d5a0f9170b10988b6774498a022845e13eda94318440d17709d50687f67f9" +dependencies = [ + "alloy-primitives 0.7.7", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-primitives" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "600d34d8de81e23b6d909c094e23b3d357e01ca36b78a8c5424c501eedbe86f0" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "k256", + "keccak-asm", + "proptest", + "rand 0.8.5", + "ruint", + "serde", + "tiny-keccak", +] + [[package]] name = "alloy-primitives" version = "0.7.7" @@ -295,16 +508,216 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "alloy-provider" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9c0ab10b93de601a6396fc7ff2ea10d3b28c46f079338fa562107ebf9857c8" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips", + "alloy-json-rpc", + "alloy-network", + "alloy-network-primitives", + "alloy-primitives 0.7.7", + "alloy-pubsub", + "alloy-rpc-client", + "alloy-rpc-types-eth", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", + "async-stream", + "async-trait", + "auto_impl", + "dashmap", + "futures", + "futures-utils-wasm", + "lru", + "pin-project", + "reqwest 0.12.5", + "serde", + "serde_json", + "tokio", + "tracing", + "url 2.5.0", +] + +[[package]] +name = "alloy-pubsub" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f5da2c55cbaf229bad3c5f8b00b5ab66c74ef093e5f3a753d874cfecf7d2281" +dependencies = [ + "alloy-json-rpc", + "alloy-primitives 0.7.7", + "alloy-transport", + "bimap", + "futures", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower", + "tracing", +] + [[package]] name = "alloy-rlp" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26154390b1d205a4a7ac7352aa2eb4f81f391399d4e2f546fb81a2f8bb383f62" dependencies = [ + "alloy-rlp-derive", "arrayvec", "bytes", ] +[[package]] +name = "alloy-rlp-derive" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d0f2d905ebd295e7effec65e5f6868d153936130ae718352771de3e7d03c75c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.61", +] + +[[package]] +name = "alloy-rpc-client" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b38e3ffdb285df5d9f60cb988d336d9b8e3505acb78750c3bc60336a7af41d3" +dependencies = [ + "alloy-json-rpc", + "alloy-primitives 0.7.7", + "alloy-pubsub", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", + "futures", + "pin-project", + "reqwest 0.12.5", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower", + "tracing", + "url 2.5.0", +] + +[[package]] +name = "alloy-rpc-types" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c31a3750b8f5a350d17354e46a52b0f2f19ec5f2006d816935af599dedc521" +dependencies = [ + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-rpc-types-engine" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff63f51b2fb2f547df5218527fd0653afb1947bf7fead5b3ce58c75d170b30f7" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives 0.7.7", + "alloy-rlp", + "alloy-rpc-types-eth", + "alloy-serde", + "jsonwebtoken 9.3.0", + "rand 0.8.5", + "serde", + "thiserror", +] + +[[package]] +name = "alloy-rpc-types-eth" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81e18424d962d7700a882fe423714bd5b9dde74c7a7589d4255ea64068773aef" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-network-primitives", + "alloy-primitives 0.7.7", + "alloy-rlp", + "alloy-serde", + "alloy-sol-types 0.7.7", + "itertools 0.13.0", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "alloy-serde" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33feda6a53e6079895aed1d08dcb98a1377b000d80d16370fbbdb8155d547ef" +dependencies = [ + "alloy-primitives 0.7.7", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-signer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "740a25b92e849ed7b0fa013951fe2f64be9af1ad5abe805037b44fb7770c5c47" +dependencies = [ + "alloy-primitives 0.7.7", + "async-trait", + "auto_impl", + "elliptic-curve", + "k256", + "thiserror", +] + +[[package]] +name = "alloy-signer-local" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b0707d4f63e4356a110b30ef3add8732ab6d181dd7be4607bf79b8777105cee" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives 0.7.7", + "alloy-signer", + "async-trait", + "k256", + "rand 0.8.5", + "thiserror", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86ec0a47740b20bc5613b8712d0d321d031c4efc58e9645af96085d5cccfc27" +dependencies = [ + "const-hex", + "dunce", + "heck 0.4.1", + "indexmap", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.61", + "syn-solidity 0.6.4", + "tiny-keccak", +] + [[package]] name = "alloy-sol-macro" version = "0.7.7" @@ -325,6 +738,7 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "867a5469d61480fea08c7333ffeca52d5b621f5ca2e44f271b117ec1fc9a0525" dependencies = [ + "alloy-json-abi", "alloy-sol-macro-input", "const-hex", "heck 0.5.0", @@ -333,7 +747,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.61", - "syn-solidity", + "syn-solidity 0.7.7", "tiny-keccak", ] @@ -343,13 +757,37 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e482dc33a32b6fadbc0f599adea520bd3aaa585c141a80b404d0a3e3fa72528" dependencies = [ + "alloy-json-abi", "const-hex", "dunce", "heck 0.5.0", "proc-macro2", "quote", + "serde_json", "syn 2.0.61", - "syn-solidity", + "syn-solidity 0.7.7", +] + +[[package]] +name = "alloy-sol-type-parser" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbcba3ca07cf7975f15d871b721fb18031eec8bce51103907f6dcce00b255d98" +dependencies = [ + "serde", + "winnow 0.6.8", +] + +[[package]] +name = "alloy-sol-types" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad09ec5853fa700d12d778ad224dcdec636af424d29fad84fb9a2f16a5b0ef09" +dependencies = [ + "alloy-primitives 0.6.4", + "alloy-sol-macro 0.6.4", + "const-hex", + "serde", ] [[package]] @@ -358,12 +796,84 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a91ca40fa20793ae9c3841b83e74569d1cc9af29a2f5237314fd3452d51e38c7" dependencies = [ - "alloy-primitives", - "alloy-sol-macro", + "alloy-json-abi", + "alloy-primitives 0.7.7", + "alloy-sol-macro 0.7.7", "const-hex", "serde", ] +[[package]] +name = "alloy-transport" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0590afbdacf2f8cca49d025a2466f3b6584a016a8b28f532f29f8da1007bae" +dependencies = [ + "alloy-json-rpc", + "base64 0.22.1", + "futures-util", + "futures-utils-wasm", + "serde", + "serde_json", + "thiserror", + "tokio", + "tower", + "tracing", + "url 2.5.0", +] + +[[package]] +name = "alloy-transport-http" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2437d145d80ea1aecde8574d2058cceb8b3c9cba05f6aea8e67907c660d46698" +dependencies = [ + "alloy-json-rpc", + "alloy-transport", + "reqwest 0.12.5", + "serde_json", + "tower", + "tracing", + "url 2.5.0", +] + +[[package]] +name = "alloy-transport-ipc" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804494366e20468776db4e18f9eb5db7db0fe14f1271eb6dbf155d867233405c" +dependencies = [ + "alloy-json-rpc", + "alloy-pubsub", + "alloy-transport", + "bytes", + "futures", + "interprocess", + "pin-project", + "serde_json", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "alloy-transport-ws" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af855163e7df008799941aa6dd324a43ef2bf264b08ba4b22d44aad6ced65300" +dependencies = [ + "alloy-pubsub", + "alloy-transport", + "futures", + "http 1.1.0", + "rustls 0.23.12", + "serde_json", + "tokio", + "tokio-tungstenite 0.23.1", + "tracing", + "ws_stream_wasm", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -832,6 +1342,28 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.61", +] + [[package]] name = "async-task" version = "4.7.1" @@ -953,6 +1485,12 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +[[package]] +name = "bimap" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" + [[package]] name = "bincode" version = "1.3.3" @@ -1033,6 +1571,18 @@ dependencies = [ "piper", ] +[[package]] +name = "blst" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4378725facc195f1a538864863f6de233b500a8862747e7f165078a419d5e874" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + [[package]] name = "bonsai-sdk" version = "0.9.0" @@ -1155,6 +1705,21 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "c-kzg" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0307f72feab3300336fb803a57134159f6e20139af1357f36c54cb90d8e8928" +dependencies = [ + "blst", + "cc", + "glob", + "hex", + "libc", + "once_cell", + "serde", +] + [[package]] name = "camino" version = "1.1.6" @@ -1491,6 +2056,19 @@ dependencies = [ "cipher", ] +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core 0.9.10", +] + [[package]] name = "data-encoding" version = "2.6.0" @@ -1629,6 +2207,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf673e0848ef09fa4aeeba78e681cf651c0c7d35f76ee38cec8e55bc32fa111" +[[package]] +name = "doctest-file" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" + [[package]] name = "downcast-rs" version = "1.2.1" @@ -2027,7 +2611,7 @@ dependencies = [ "hashers", "http 0.2.12", "instant", - "jsonwebtoken", + "jsonwebtoken 8.3.0", "once_cell", "pin-project", "reqwest 0.11.27", @@ -2035,7 +2619,7 @@ dependencies = [ "serde_json", "thiserror", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.20.1", "tracing", "tracing-futures", "url 2.5.0", @@ -2477,6 +3061,12 @@ dependencies = [ "slab", ] +[[package]] +name = "futures-utils-wasm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" + [[package]] name = "fuzzy-matcher" version = "0.3.7" @@ -2606,6 +3196,10 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] [[package]] name = "hashers" @@ -2663,6 +3257,9 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] [[package]] name = "hex-literal" @@ -3016,6 +3613,21 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "interprocess" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f4e4a06d42fab3e85ab1b419ad32b09eab58b901d40c57935ff92db3287a13" +dependencies = [ + "doctest-file", + "futures-core", + "libc", + "recvmsg", + "tokio", + "widestring", + "windows-sys 0.52.0", +] + [[package]] name = "io-lifetimes" version = "1.0.11" @@ -3092,6 +3704,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -3123,13 +3744,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" dependencies = [ "base64 0.21.7", - "pem", + "pem 1.1.1", "ring 0.16.20", "serde", "serde_json", "simple_asn1", ] +[[package]] +name = "jsonwebtoken" +version = "9.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" +dependencies = [ + "base64 0.21.7", + "js-sys", + "pem 3.0.4", + "ring 0.17.8", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "jwt" version = "0.16.0" @@ -3329,6 +3965,15 @@ dependencies = [ "value-bag", ] +[[package]] +name = "lru" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" +dependencies = [ + "hashbrown 0.14.5", +] + [[package]] name = "matchers" version = "0.1.0" @@ -3841,6 +4486,16 @@ dependencies = [ "base64 0.13.1", ] +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.1", + "serde", +] + [[package]] name = "percent-encoding" version = "1.0.1" @@ -4498,6 +5153,12 @@ dependencies = [ "rand_core 0.3.1", ] +[[package]] +name = "recvmsg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" + [[package]] name = "redox_syscall" version = "0.2.16" @@ -4634,11 +5295,13 @@ dependencies = [ "http-body-util", "hyper 1.3.1", "hyper-rustls 0.27.2", + "hyper-tls", "hyper-util", "ipnet", "js-sys", "log 0.4.22", "mime 0.3.17", + "native-tls", "once_cell", "percent-encoding 2.3.1", "pin-project-lite", @@ -4651,6 +5314,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper 1.0.1", "tokio", + "tokio-native-tls", "tokio-rustls 0.26.0", "tokio-util", "tower-service", @@ -4679,6 +5343,9 @@ version = "0.1.0" dependencies = [ "actix-cors", "actix-web", + "alloy", + "alloy-primitives 0.6.4", + "alloy-sol-types 0.6.4", "async-std", "bincode", "bytes", @@ -4688,11 +5355,14 @@ dependencies = [ "dialoguer", "env_logger", "ethers", + "eyre", "fhe", "fhe-traits", "fhe-util", + "futures-util", "getrandom", "headers", + "hex", "hmac", "http-body-util", "hyper 1.3.1", @@ -4841,7 +5511,7 @@ name = "risc0-ethereum-contracts" version = "1.0.0" source = "git+https://github.com/risc0/risc0-ethereum?tag=v1.0.0#5fbbc7cb44ab37ce438c14c087ba6c4e0a669900" dependencies = [ - "alloy-sol-types", + "alloy-sol-types 0.7.7", "anyhow", "ethers", "risc0-zkvm", @@ -5657,6 +6327,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn-solidity" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3d0961cd53c23ea94eeec56ba940f636f6394788976e9f16ca5ee0aca7464a" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.61", +] + [[package]] name = "syn-solidity" version = "0.7.7" @@ -5761,6 +6443,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + [[package]] name = "time" version = "0.1.45" @@ -5888,6 +6579,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + [[package]] name = "tokio-tungstenite" version = "0.20.1" @@ -5899,10 +6602,26 @@ dependencies = [ "rustls 0.21.12", "tokio", "tokio-rustls 0.24.1", - "tungstenite", + "tungstenite 0.20.1", "webpki-roots 0.25.4", ] +[[package]] +name = "tokio-tungstenite" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" +dependencies = [ + "futures-util", + "log 0.4.22", + "rustls 0.23.12", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.0", + "tungstenite 0.23.0", + "webpki-roots 0.26.3", +] + [[package]] name = "tokio-util" version = "0.7.11" @@ -6124,6 +6843,26 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.1.0", + "httparse", + "log 0.4.22", + "rand 0.8.5", + "rustls 0.23.12", + "rustls-pki-types", + "sha1", + "thiserror", + "utf-8", +] + [[package]] name = "typeable" version = "0.1.2" @@ -6456,6 +7195,12 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "widestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" + [[package]] name = "winapi" version = "0.3.9" diff --git a/packages/server/Cargo.toml b/packages/server/Cargo.toml index 6980237..94f2715 100644 --- a/packages/server/Cargo.toml +++ b/packages/server/Cargo.toml @@ -43,4 +43,10 @@ sha2 = "0.10.8" log = "0.4.22" env_logger = "0.11.5" actix-web = "4.9.0" -actix-cors = "0.7.0" \ No newline at end of file +actix-cors = "0.7.0" +alloy-primitives = { version = "0.6", default-features = false, features = ["rlp", "serde", "std"] } +alloy-sol-types = { version = "0.6" } +alloy = { version = "0.2.1", features = ["full"] } +futures-util = "0.3" +eyre = "0.6" +hex = "0.4" \ No newline at end of file diff --git a/packages/server/src/cli/auth.rs b/packages/server/src/cli/auth.rs index c993196..0c9a8d3 100644 --- a/packages/server/src/cli/auth.rs +++ b/packages/server/src/cli/auth.rs @@ -20,15 +20,15 @@ pub async fn authenticate_user(config: &super::CrispConfig, client: &HyperClient let user = AuthenticationLogin { postId: config.authentication_id.clone(), }; - - let out = serde_json::to_string(&user).unwrap(); - let mut url = config.enclave_address.clone(); - url.push_str("/authentication_login"); + + let body = serde_json::to_string(&user)?; + let url = format!("{}/authentication_login", config.enclave_address); + let req = Request::builder() - .header("Content-Type", "application/json") + .header("Content-Type", "application/json") .method(Method::POST) .uri(url) - .body(out)?; + .body(body)?; let resp = client.request(req).await?; let body_bytes = resp.collect().await?.to_bytes(); diff --git a/packages/server/src/cli/mod.rs b/packages/server/src/cli/mod.rs index 1708be8..1d3ab80 100644 --- a/packages/server/src/cli/mod.rs +++ b/packages/server/src/cli/mod.rs @@ -48,66 +48,67 @@ struct CrispConfig { enclave_address: String, authentication_id: String, } - #[tokio::main] pub async fn run_cli() -> Result<(), Box> { init_logger(); let https = HttpsConnector::new(); - let client_get: HyperClientGet = HyperClient::builder(TokioExecutor::new()).build(https.clone()); - let client: HyperClientPost = HyperClient::builder(TokioExecutor::new()).build(https); + let client_get = HyperClient::builder(TokioExecutor::new()).build::<_, Empty>(https.clone()); + let client = HyperClient::builder(TokioExecutor::new()).build::<_, String>(https); - let mut auth_res = AuthenticationResponse { - response: "".to_string(), - jwt_token: "".to_string(), - }; + clear_screen(); - print!("{esc}[2J{esc}[1;1H", esc = 27 as char); - let selections = &[ - "CRISP: Voting Protocol (ETH)", - "More Coming Soon!" - ]; + let environment = select_environment()?; + if environment != 0 { + info!("Check back soon!"); + return Ok(()); + } - let selection_1 = FuzzySelect::with_theme(&ColorfulTheme::default()) - .with_prompt("Enclave (EEEE): Please choose the private execution environment you would like to run!") - .default(0) - .items(&selections[..]) - .interact() - .unwrap(); - - if selection_1 == 0 { - print!("{esc}[2J{esc}[1;1H", esc = 27 as char); - let selections_2 = &[ - "Initialize new CRISP round.", - "Continue Existing CRISP round." - ]; - - let selection_2 = FuzzySelect::with_theme(&ColorfulTheme::default()) - .with_prompt("Create a new CRISP round or participate in an existing round.") - .default(0) - .items(&selections_2[..]) - .interact() - .unwrap(); - - // Read configuration - let path = env::current_dir().unwrap(); - let mut pathst = path.display().to_string(); - pathst.push_str("/example_config.json"); - let mut file = File::open(pathst).unwrap(); - let mut data = String::new(); - file.read_to_string(&mut data).unwrap(); - let config: CrispConfig = serde_json::from_str(&data).expect("JSON was not well-formatted"); - - if selection_2 == 0 { + clear_screen(); + + let config = read_config()?; + let action = select_action()?; + + match action { + 0 => { initialize_crisp_round(&config, &client_get, &client).await?; - } else if selection_2 == 1 { - auth_res = authenticate_user(&config, &client).await?; + } + 1 => { + let auth_res = authenticate_user(&config, &client).await?; participate_in_existing_round(&config, &client, &auth_res).await?; } - } else { - info!("Check back soon!"); - std::process::exit(1); + _ => unreachable!(), } Ok(()) } + +fn clear_screen() { + print!("{esc}[2J{esc}[1;1H", esc = 27 as char); +} + +fn select_environment() -> Result> { + let selections = &["CRISP: Voting Protocol (ETH)", "More Coming Soon!"]; + Ok(FuzzySelect::with_theme(&ColorfulTheme::default()) + .with_prompt("Enclave (EEEE): Please choose the private execution environment you would like to run!") + .default(0) + .items(&selections[..]) + .interact()?) +} + +fn select_action() -> Result> { + let selections = &["Initialize new CRISP round.", "Continue Existing CRISP round."]; + Ok(FuzzySelect::with_theme(&ColorfulTheme::default()) + .with_prompt("Create a new CRISP round or participate in an existing round.") + .default(0) + .items(&selections[..]) + .interact()?) +} + +fn read_config() -> Result> { + let config_path = env::current_dir()?.join("example_config.json"); + let mut file = File::open(config_path)?; + let mut data = String::new(); + file.read_to_string(&mut data)?; + Ok(serde_json::from_str(&data)?) +} \ No newline at end of file diff --git a/packages/server/src/cli/voting.rs b/packages/server/src/cli/voting.rs index d14b7c4..b505f7d 100644 --- a/packages/server/src/cli/voting.rs +++ b/packages/server/src/cli/voting.rs @@ -1,15 +1,13 @@ +use std::{thread, time::Duration}; use bytes::Bytes; use dialoguer::{theme::ColorfulTheme, FuzzySelect, Input}; -use http_body_util::BodyExt; -use http_body_util::Empty; -use hyper::{Method, Request}; +use http_body_util::{BodyExt, Empty}; +use hyper::{body::Incoming, Method, Request, Response}; use serde::{Deserialize, Serialize}; -use std::{thread, time}; -use tokio::io::{self, AsyncWriteExt as _}; -use log::info; +use tokio::io::{self, AsyncWriteExt}; +use log::{info, error}; -use crate::cli::AuthenticationResponse; -use crate::cli::{HyperClientGet, HyperClientPost}; +use crate::cli::{AuthenticationResponse, HyperClientGet, HyperClientPost}; use crate::util::timeit::timeit; use fhe::bfv::{BfvParametersBuilder, Encoding, Plaintext, PublicKey}; use fhe_traits::{DeserializeParametrized, FheEncoder, FheEncrypter, Serialize as FheSerialize}; @@ -35,8 +33,8 @@ struct PKRequest { struct EncryptedVote { round_id: u32, enc_vote_bytes: Vec, - #[allow(non_snake_case)] - postId: String, + #[serde(rename = "postId")] + post_id: String, } #[derive(Debug, Deserialize, Serialize)] @@ -45,39 +43,35 @@ struct JsonResponseTxHash { tx_hash: String, } +async fn get_response_body(resp: Response) -> Result> { + let body_bytes = resp.collect().await?.to_bytes(); + Ok(String::from_utf8(body_bytes.to_vec())?) +} + pub async fn initialize_crisp_round( config: &super::CrispConfig, client_get: &HyperClientGet, client: &HyperClientPost, ) -> Result<(), Box> { info!("Starting new CRISP round!"); - info!("Initializing Keyshare nodes..."); - let response_id = JsonRequestGetRounds { - response: "Test".to_string(), - }; - let _out = serde_json::to_string(&response_id).unwrap(); - let mut url_id = config.enclave_address.clone(); - url_id.push_str("/get_rounds"); - + let url_id = format!("{}/get_rounds", config.enclave_address); let req = Request::builder() .method(Method::GET) .uri(url_id) .body(Empty::::new())?; let resp = client_get.request(req).await?; - info!("Response status: {}", resp.status()); - let body_bytes = resp.collect().await?.to_bytes(); - let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); - let count: RoundCount = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); + let body_str = get_response_body(resp).await?; + let count: RoundCount = serde_json::from_str(&body_str)?; info!("Server Round Count: {:?}", count.round_count); let round_id = count.round_count + 1; let response = super::CrispConfig { - round_id: round_id, + round_id, poll_length: config.poll_length, chain_id: config.chain_id, voting_address: config.voting_address.clone(), @@ -85,30 +79,27 @@ pub async fn initialize_crisp_round( enclave_address: config.enclave_address.clone(), authentication_id: config.authentication_id.clone(), }; - let out = serde_json::to_string(&response).unwrap(); - let mut url = config.enclave_address.clone(); - url.push_str("/init_crisp_round"); + + let url = format!("{}/init_crisp_round", config.enclave_address); let req = Request::builder() .header("authorization", "Bearer fpKL54jvWmEGVoRdCNjG") .header("Content-Type", "application/json") .method(Method::POST) .uri(url) - .body(out)?; + .body(serde_json::to_string(&response)?)?; let mut resp = client.request(req).await?; - info!("Response status: {}", resp.status()); - while let Some(next) = resp.frame().await { - let frame = next?; - if let Some(chunk) = frame.data_ref() { - tokio::io::stdout().write_all(chunk).await?; + while let Some(frame) = resp.frame().await { + if let Some(chunk) = frame?.data_ref() { + io::stdout().write_all(chunk).await?; } } + info!("Round Initialized."); info!("Gathering Keyshare nodes for execution environment..."); - let three_seconds = time::Duration::from_millis(1000); - thread::sleep(three_seconds); + thread::sleep(Duration::from_secs(1)); info!("\nYou can now vote Encrypted with Round ID: {:?}", round_id); Ok(()) @@ -121,94 +112,104 @@ pub async fn participate_in_existing_round( ) -> Result<(), Box> { let input_crisp_id: u32 = Input::with_theme(&ColorfulTheme::default()) .with_prompt("Enter CRISP round ID.") - .interact_text() - .unwrap(); + .interact_text()?; info!("Voting state Initialized"); - // get public encrypt key - let v: Vec = vec![0]; + // Get public encrypt key let response_pk = PKRequest { round_id: input_crisp_id, - pk_bytes: v, + pk_bytes: vec![0], }; - let out = serde_json::to_string(&response_pk).unwrap(); - let mut url = config.enclave_address.clone(); - url.push_str("/get_pk_by_round"); - let req = Request::builder().header("Content-Type", "application/json").method(Method::POST).uri(url).body(out)?; - let resp = client.request(req).await?; + let url = format!("{}/get_pk_by_round", config.enclave_address); + let req = Request::builder() + .header("Content-Type", "application/json") + .method(Method::POST) + .uri(url) + .body(serde_json::to_string(&response_pk)?)?; + let resp = client.request(req).await?; info!("Response status: {}", resp.status()); - let body_bytes = resp.collect().await?.to_bytes(); - let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); - let pk_res: PKRequest = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); - info!( - "Shared Public Key for CRISP round {:?} collected.", - pk_res.round_id - ); + let body_str = get_response_body(resp).await?; + let pk_res: PKRequest = serde_json::from_str(&body_str)?; + info!("Shared Public Key for CRISP round {:?} collected.", pk_res.round_id); info!("Public Key: {:?}", pk_res.pk_bytes); - let degree = 4096; - let plaintext_modulus: u64 = 4096; - let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; - // Let's generate the BFV parameters structure. let params = timeit!( "Parameters generation", - BfvParametersBuilder::new() - .set_degree(degree) - .set_plaintext_modulus(plaintext_modulus) - .set_moduli(&moduli) - .build_arc()? + generate_bfv_parameters()? ); - let pk_deserialized = PublicKey::from_bytes(&pk_res.pk_bytes, ¶ms).unwrap(); - - // Select voting option - let selections_3 = &["Abstain.", "Vote yes.", "Vote no."]; + let pk_deserialized = PublicKey::from_bytes(&pk_res.pk_bytes, ¶ms)?; - let selection_3 = FuzzySelect::with_theme(&ColorfulTheme::default()) - .with_prompt("Please select your voting option.") - .default(0) - .items(&selections_3[..]) - .interact() - .unwrap(); - - let mut vote_choice: u64 = 0; - if selection_3 == 0 { + let vote_choice = get_user_vote()?; + if vote_choice.is_none() { info!("Exiting voting system. You may choose to vote later."); return Ok(()); - } else if selection_3 == 1 { - vote_choice = 1; - } else if selection_3 == 2 { - vote_choice = 0; } + info!("Encrypting vote."); - let votes: Vec = [vote_choice].to_vec(); - let pt = Plaintext::try_encode(&[votes[0]], Encoding::poly(), ¶ms)?; - let ct = pk_deserialized.try_encrypt(&pt, &mut thread_rng())?; + let ct = encrypt_vote(vote_choice.unwrap(), &pk_deserialized, ¶ms)?; info!("Vote encrypted."); info!("Calling voting contract with encrypted vote."); let request_contract = EncryptedVote { round_id: input_crisp_id, enc_vote_bytes: ct.to_bytes(), - postId: auth_res.jwt_token.clone(), + post_id: auth_res.jwt_token.clone(), }; - let out = serde_json::to_string(&request_contract).unwrap(); - let mut url = config.enclave_address.clone(); - url.push_str("/broadcast_enc_vote"); - let req = Request::builder().header("Content-Type", "application/json").method(Method::POST).uri(url).body(out)?; - let resp = client.request(req).await?; + let url = format!("{}/broadcast_enc_vote", config.enclave_address); + let req = Request::builder() + .header("Content-Type", "application/json") + .method(Method::POST) + .uri(url) + .body(serde_json::to_string(&request_contract)?)?; + let resp = client.request(req).await?; info!("Response status: {}", resp.status()); - let body_bytes = resp.collect().await?.to_bytes(); - let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); - let contract_res: JsonResponseTxHash = - serde_json::from_str(&body_str).expect("JSON was not well-formatted"); + let body_str = get_response_body(resp).await?; + let contract_res: JsonResponseTxHash = serde_json::from_str(&body_str)?; info!("Contract call: {:?}", contract_res.response); info!("TxHash is {:?}", contract_res.tx_hash); Ok(()) } + +fn generate_bfv_parameters() -> Result, Box> { + let degree = 4096; + let plaintext_modulus: u64 = 4096; + let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; + + Ok(BfvParametersBuilder::new() + .set_degree(degree) + .set_plaintext_modulus(plaintext_modulus) + .set_moduli(&moduli) + .build_arc()?) +} + +fn get_user_vote() -> Result, Box> { + let selections = &["Abstain.", "Vote yes.", "Vote no."]; + let selection = FuzzySelect::with_theme(&ColorfulTheme::default()) + .with_prompt("Please select your voting option.") + .default(0) + .items(&selections[..]) + .interact()?; + + match selection { + 0 => Ok(None), + 1 => Ok(Some(1)), + 2 => Ok(Some(0)), + _ => Err("Invalid selection".into()), + } +} + +fn encrypt_vote( + vote: u64, + public_key: &PublicKey, + params: &std::sync::Arc, +) -> Result> { + let pt = Plaintext::try_encode(&[vote], Encoding::poly(), params)?; + Ok(public_key.try_encrypt(&pt, &mut thread_rng())?) +} \ No newline at end of file diff --git a/packages/server/src/enclave_server/listener.rs b/packages/server/src/enclave_server/listener.rs new file mode 100644 index 0000000..91758c1 --- /dev/null +++ b/packages/server/src/enclave_server/listener.rs @@ -0,0 +1,120 @@ +use alloy::{ + primitives::{address, Address, B256}, + providers::{Provider, ProviderBuilder, RootProvider}, + rpc::types::{BlockNumberOrTag, Filter, Log}, + sol, + sol_types::SolEvent, + transports::BoxTransport, +}; +use eyre::Result; +use futures_util::stream::StreamExt; +use std::collections::HashMap; +use std::fmt::Debug; +use std::sync::Arc; + +pub trait ContractEvent: Send + Sync + 'static { + fn process(&self) -> Result<()>; +} + +impl ContractEvent for T +where + T: SolEvent + Debug + Send + Sync + 'static, +{ + fn process(&self) -> Result<()> { + println!("Processing event: {:?}", self); + Ok(()) + } +} + +pub struct EventListener { + provider: Arc>, + filter: Filter, + handlers: HashMap Result> + Send + Sync>>, +} + +impl EventListener { + pub fn new(provider: Arc>, filter: Filter) -> Self { + Self { + provider, + filter, + handlers: HashMap::new(), + } + } + + pub fn add_event_handler(&mut self) + where + E: SolEvent + ContractEvent + 'static, + { + let signature = E::SIGNATURE_HASH; + let handler = Arc::new(move |log: Log| -> Result> { + let event = log.log_decode::()?.inner.data; + Ok(Box::new(event)) + }); + + self.handlers.insert(signature, handler); + } + + pub async fn listen(&self) -> Result<()> { + let mut stream = self + .provider + .subscribe_logs(&self.filter) + .await? + .into_stream(); + while let Some(log) = stream.next().await { + if let Some(topic0) = log.topic0() { + if let Some(decoder) = self.handlers.get(topic0) { + if let Ok(event) = decoder(log.clone()) { + event.process()?; + } + } + } + } + + Ok(()) + } +} + +pub struct ContractManager { + provider: Arc>, +} + +impl ContractManager { + pub async fn new(rpc_url: &str) -> Result { + let provider = ProviderBuilder::new().on_builtin(rpc_url).await?; + Ok(Self { + provider: Arc::new(provider), + }) + } + + pub fn add_listener(&self, contract_address: Address) -> EventListener { + let filter = Filter::new() + .address(contract_address) + .from_block(BlockNumberOrTag::Latest); + + EventListener::new(self.provider.clone(), filter) + } +} + +sol! { + #[derive(Debug)] + event TestingEvent(uint256 e3Id, bytes input); +} + +#[tokio::main] +async fn start_listener() -> Result<()> { + let rpc_url = "ws://127.0.0.1:8545"; + + let manager = ContractManager::new(rpc_url).await?; + + let address1 = address!("e7f1725E7734CE288F8367e1Bb143E90bb3F0512"); + let mut listener1 = manager.add_listener(address1); + listener1.add_event_handler::(); + + let address2 = address!("5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"); + let mut listener2 = manager.add_listener(address2); + listener2.add_event_handler::(); + + tokio::try_join!(listener1.listen(), listener2.listen())?; + + Ok(()) +} \ No newline at end of file diff --git a/packages/server/src/enclave_server/routes/voting.rs b/packages/server/src/enclave_server/routes/voting.rs index 2211a84..9722382 100644 --- a/packages/server/src/enclave_server/routes/voting.rs +++ b/packages/server/src/enclave_server/routes/voting.rs @@ -1,13 +1,15 @@ use std::{env, sync::Arc, str}; use std::io::Read; -use ethers::{ - prelude::abigen, - providers::{Http, Provider, Middleware}, - middleware::{SignerMiddleware, MiddlewareBuilder}, - signers::{LocalWallet, Signer}, - types::{Address, Bytes, TxHash, BlockNumber}, +use alloy::{ + network::{AnyNetwork, EthereumWallet}, + primitives::{Address, Bytes, U256, B256}, + providers::ProviderBuilder, + signers::local::PrivateKeySigner, + sol, }; + +use eyre::Result; use log::info; use actix_web::{web, HttpResponse, Responder}; @@ -21,6 +23,7 @@ pub fn setup_routes(config: &mut web::ServiceConfig) { .route("/get_vote_count_by_round", web::post().to(get_vote_count_by_round)) .route("/get_emojis_by_round", web::post().to(get_emojis_by_round)); } + async fn broadcast_enc_vote( data: web::Json, state: web::Data, // Access shared state @@ -40,16 +43,14 @@ async fn broadcast_enc_vote( if response_str == "" { response_str = "Vote Successful"; let sol_vote = Bytes::from(incoming.enc_vote_bytes); - let tx_hash = call_contract(sol_vote, state_data.voting_address.clone()).await.unwrap(); - - converter = "0x".to_string(); - for i in 0..32 { - if tx_hash[i] <= 16 { - converter.push_str("0"); + let tx_hash = match call_contract(sol_vote, state_data.voting_address.clone()).await { + Ok(hash) => hash, + Err(e) => { + info!("Error while sending vote transaction: {:?}", e); + return HttpResponse::InternalServerError().body("Failed to broadcast vote"); } - converter.push_str(&format!("{:x}", tx_hash[i])); - } - + }; + converter = tx_hash.to_string(); state_data.vote_count += 1; state_data.has_voted.push(incoming.postId.clone()); let state_str = serde_json::to_string(&state_data).unwrap(); @@ -91,43 +92,48 @@ async fn get_vote_count_by_round( HttpResponse::Ok().json(incoming) } -// Call Contract Function -async fn call_contract( +sol! { + #[allow(missing_docs)] + #[sol(rpc)] + contract IVOTE { + function voteEncrypted(bytes memory _encVote) public; + function getVote(address id) public returns (bytes memory); + event Transfer(address indexed from, address indexed to, uint256 value); + } +} + +pub async fn call_contract( enc_vote: Bytes, address: String, -) -> Result> { - info!("calling voting contract"); - - let rpc_url = "http://0.0.0.0:8545".to_string(); - let provider = Provider::::try_from(rpc_url.clone())?; - - abigen!( - IVOTE, - r#"[ - function voteEncrypted(bytes memory _encVote) public - function getVote(address id) public returns(bytes memory) - event Transfer(address indexed from, address indexed to, uint256 value) - ]"#, - ); - - let vote_address: &str = &address; - let eth_val = env!("PRIVATEKEY"); - let wallet: LocalWallet = eth_val - .parse::()? - .with_chain_id(31337 as u64); - - let nonce_manager = provider.clone().nonce_manager(wallet.address()); - let curr_nonce = nonce_manager - .get_transaction_count(wallet.address(), Some(BlockNumber::Pending.into())) - .await? - .as_u64(); - - let client = SignerMiddleware::new(provider.clone(), wallet.clone()); - let address: Address = vote_address.parse()?; - let contract = IVOTE::new(address, Arc::new(client.clone())); - - let tx = contract.vote_encrypted(enc_vote).nonce(curr_nonce).send().await?.clone(); - info!("{:?}", tx); - - Ok(tx) +) -> Result> { + info!("Calling voting contract"); + + // Set up the signer from a private key + let eth_val = env::var("PRIVATEKEY").expect("PRIVATEKEY must be set in the environment"); + let signer: PrivateKeySigner = eth_val.parse()?; + let wallet = EthereumWallet::from(signer); + + // Set up the provider using the Alloy library + let rpc_url = "http://0.0.0.0:8545".parse()?; + let provider = ProviderBuilder::new() + .with_recommended_fillers() + .network::() + .wallet(wallet) + .on_http(rpc_url); + + // Parse the address of the contract + let vote_address: Address = address.parse()?; + + // Create the contract instance + let contract = IVOTE::new(vote_address, &provider); + + // Send the voteEncrypted transaction + let builder = contract.voteEncrypted(enc_vote); + let receipt = builder.send().await?.get_receipt().await?; + + // Log the transaction hash + let tx_hash = receipt.transaction_hash; + info!("Transaction hash: {:?}", tx_hash); + + Ok(tx_hash) } \ No newline at end of file From f5f4ed972cc99c6fc29ae15fa3aa6fae60265bbe Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Tue, 10 Sep 2024 14:19:59 +0500 Subject: [PATCH 20/62] Remove folder from tracking and update .gitignore --- .gitignore | 3 + packages/evm/.gitignore | 21 ----- packages/evm/LICENSE.md | 125 ---------------------------- packages/evm/README.md | 1 - packages/evm/contracts/RFVoting.sol | 27 ------ packages/evm/deploy/deploy.ts | 18 ---- packages/evm/hardhat.config.ts | 46 ---------- packages/evm/package.json | 85 ------------------- packages/evm/test/RFVoting.spec.ts | 25 ------ packages/evm/tsconfig.json | 22 ----- 10 files changed, 3 insertions(+), 370 deletions(-) delete mode 100644 packages/evm/.gitignore delete mode 100644 packages/evm/LICENSE.md delete mode 100644 packages/evm/README.md delete mode 100644 packages/evm/contracts/RFVoting.sol delete mode 100644 packages/evm/deploy/deploy.ts delete mode 100644 packages/evm/hardhat.config.ts delete mode 100644 packages/evm/package.json delete mode 100644 packages/evm/test/RFVoting.spec.ts delete mode 100644 packages/evm/tsconfig.json diff --git a/.gitignore b/.gitignore index 5b2d531..2d56911 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ database .pnp.* package-lock.json .DS_Store + + +packages/evm \ No newline at end of file diff --git a/packages/evm/.gitignore b/packages/evm/.gitignore deleted file mode 100644 index 18a269e..0000000 --- a/packages/evm/.gitignore +++ /dev/null @@ -1,21 +0,0 @@ -# directories -.coverage_artifacts -.coverage_cache -.coverage_contracts -artifacts -build -cache -coverage -dist -node_modules -types -deployments - -# files -*.env -*.log -.DS_Store -.pnp.* -coverage.json -package-lock.json -yarn.lock diff --git a/packages/evm/LICENSE.md b/packages/evm/LICENSE.md deleted file mode 100644 index 7f9dcfa..0000000 --- a/packages/evm/LICENSE.md +++ /dev/null @@ -1,125 +0,0 @@ -GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - -Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute -verbatim copies of this license document, but changing it is not allowed. - -This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU -General Public License, supplemented by the additional permissions listed below. - -0. Additional Definitions. - -As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to -version 3 of the GNU General Public License. - -"The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined -below. - -An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on -the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by -the Library. - -A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of -the Library with which the Combined Work was made is also called the "Linked Version". - -The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding -any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not -on the Linked Version. - -The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, -including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the -System Libraries of the Combined Work. - -1. Exception to Section 3 of the GNU GPL. - -You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. - -2. Conveying Modified Versions. - -If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied -by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may -convey a copy of the modified version: - -a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not -supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, -or - -b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. - -3. Object Code Incorporating Material from Library Header Files. - -The object code form of an Application may incorporate material from a header file that is part of the Library. You may -convey such object code under terms of your choice, provided that, if the incorporated material is not limited to -numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or -fewer lines in length), you do both of the following: - -a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its -use are covered by this License. - -b) Accompany the object code with a copy of the GNU GPL and this license document. - -4. Combined Works. - -You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification -of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, -if you also do each of the following: - -a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its -use are covered by this License. - -b) Accompany the Combined Work with a copy of the GNU GPL and this license document. - -c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library -among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. - -d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - -e) Provide Installation Information, but only if you would otherwise be required to provide such information under -section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified -version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked -Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and -Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner -specified by section 6 of the GNU GPL for conveying Corresponding Source.) - -5. Combined Libraries. - -You may place library facilities that are a work based on the Library side by side in a single library together with -other library facilities that are not Applications and are not covered by this License, and convey such a combined -library under terms of your choice, if you do both of the following: - -a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library -facilities, conveyed under the terms of this License. - -b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where -to find the accompanying uncombined form of the same work. - -6. Revised Versions of the GNU Lesser General Public License. - -The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time -to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new -problems or concerns. - -Each version is given a distinguishing version number. If the Library as you received it specifies that a certain -numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of -following the terms and conditions either of that published version or of any later version published by the Free -Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General -Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software -Foundation. - -If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General -Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for -you to choose that version for the Library. diff --git a/packages/evm/README.md b/packages/evm/README.md deleted file mode 100644 index b04c573..0000000 --- a/packages/evm/README.md +++ /dev/null @@ -1 +0,0 @@ -# RFV Contracts \ No newline at end of file diff --git a/packages/evm/contracts/RFVoting.sol b/packages/evm/contracts/RFVoting.sol deleted file mode 100644 index 347e13d..0000000 --- a/packages/evm/contracts/RFVoting.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: LGPLv3 -pragma solidity ^0.8.20; - -contract RFVoting { - - mapping(address voter => bytes vote) public votes; - mapping(address validVoter => bool valid) public isValidVoter; - - string public tester = "test"; - uint256 public id = 0; - uint256 public pollNonce = 0; - - event Voted(address indexed voter, bytes vote); - - function voteEncrypted(bytes memory _encVote) public { - id++; - emit Voted(msg.sender, _encVote); - } - - function register() public { - isValidVoter[msg.sender] = true; - } - - function createPoll() public { - pollNonce++; - } -} diff --git a/packages/evm/deploy/deploy.ts b/packages/evm/deploy/deploy.ts deleted file mode 100644 index 19deb76..0000000 --- a/packages/evm/deploy/deploy.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { DeployFunction } from "hardhat-deploy/types"; -import { HardhatRuntimeEnvironment } from "hardhat/types"; - -const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { - const { deployer } = await hre.getNamedAccounts(); - const { deploy } = hre.deployments; - console.log(deployer) - const rfvoting = await deploy("RFVoting", { - from: deployer, - args: [], - log: true, - }); - - console.log(`RFVoting contract: `, rfvoting.address); -}; -export default func; -func.id = "deploy_rfvoting"; // id required to prevent reexecution -func.tags = ["RFVoting"]; \ No newline at end of file diff --git a/packages/evm/hardhat.config.ts b/packages/evm/hardhat.config.ts deleted file mode 100644 index ea504fb..0000000 --- a/packages/evm/hardhat.config.ts +++ /dev/null @@ -1,46 +0,0 @@ -import "@nomicfoundation/hardhat-toolbox"; -import { config as dotenvConfig } from "dotenv"; -import "hardhat-deploy"; -import type { HardhatUserConfig } from "hardhat/config"; -import { resolve } from "path"; - -// Load environment variables from .env file -const dotenvConfigPath: string = process.env.DOTENV_CONFIG_PATH || "./.env"; -dotenvConfig({ path: resolve(__dirname, dotenvConfigPath) }); - -// Default configuration for Hardhat network -const config: HardhatUserConfig = { - defaultNetwork: "hardhat", - namedAccounts: { - deployer: 0, - }, - networks: { - hardhat: { - chainId: 31337, // Default Hardhat Network Chain ID - accounts: { - mnemonic: process.env.MNEMONIC || "test test test test test test test test test test test junk", - }, - }, - }, - paths: { - artifacts: "./artifacts", - cache: "./cache", - sources: "./contracts", - tests: "./test", - }, - solidity: { - version: "0.8.21", - settings: { - optimizer: { - enabled: true, - runs: 800, - }, - }, - }, - typechain: { - outDir: "types", - target: "ethers-v6", - }, -}; - -export default config; diff --git a/packages/evm/package.json b/packages/evm/package.json deleted file mode 100644 index de41149..0000000 --- a/packages/evm/package.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "name": "@gnosisguild/GGMI", - "description": "", - "version": "1.0.0", - "author": { - "name": "gnosisguild", - "url": "https://github.com/gnosisguild" - }, - "devDependencies": { - "@nomicfoundation/hardhat-chai-matchers": "^2.0.0", - "@nomicfoundation/hardhat-ethers": "^3.0.0", - "@nomicfoundation/hardhat-network-helpers": "^1.0.6", - "@nomicfoundation/hardhat-toolbox": "^3.0.0", - "@nomicfoundation/hardhat-verify": "^1.0.0", - "@trivago/prettier-plugin-sort-imports": "^4.0.0", - "@typechain/ethers-v6": "^0.4.0", - "@typechain/hardhat": "^8.0.0", - "@types/chai": "^4.3.4", - "@types/fs-extra": "^9.0.13", - "@types/mocha": "^10.0.0", - "@types/node": "^18.11.9", - "@typescript-eslint/eslint-plugin": "^5.44.0", - "@typescript-eslint/parser": "^5.44.0", - "chai": "^4.3.7", - "cross-env": "^7.0.3", - "dotenv": "^16.0.3", - "eslint": "^8.28.0", - "eslint-config-prettier": "^8.5.0", - "ethers": "^6.4.0", - "fs-extra": "^10.1.0", - "hardhat": "^2.12.2", - "hardhat-deploy": "^0.11.29", - "hardhat-gas-reporter": "^1.0.9", - "lodash": "^4.17.21", - "mocha": "^10.1.0", - "prettier": "^2.8.4", - "prettier-plugin-solidity": "^1.1.2", - "rimraf": "^4.1.2", - "solhint": "^3.4.0", - "solhint-plugin-prettier": "^0.0.5", - "solidity-coverage": "^0.8.2", - "ts-generator": "^0.1.1", - "ts-node": "^10.9.1", - "typechain": "^8.2.0", - "typescript": "^4.9.3" - }, - "files": [ - "contracts" - ], - "keywords": [ - "blockchain", - "ethers", - "ethereum", - "hardhat", - "smart-contracts", - "solidity", - "template", - "typescript", - "typechain" - ], - "publishConfig": { - "access": "public" - }, - "scripts": { - "clean": "rimraf ./artifacts ./cache ./coverage ./types ./coverage.json && yarn typechain", - "compile": "cross-env TS_NODE_TRANSPILE_ONLY=true hardhat compile", - "coverage": "hardhat coverage --solcoverjs ./.solcover.js --temp artifacts --testfiles \"test/**/*.ts\" && yarn typechain", - "deploy:contracts": "hardhat deploy", - "deploy": "hardhat deploy --network", - "lint": "yarn lint:sol && yarn lint:ts && yarn prettier:check", - "lint:sol": "solhint --max-warnings 0 \"contracts/**/*.sol\"", - "lint:ts": "eslint --ignore-path ./.eslintignore --ext .js,.ts .", - "postinstall": "DOTENV_CONFIG_PATH=./.env.example yarn typechain", - "prettier:check": "prettier --check \"**/*.{js,json,md,sol,ts,yml}\"", - "prettier:write": "prettier --write \"**/*.{js,json,md,sol,ts,yml}\"", - "task:deployGreeter": "hardhat task:deployGreeter", - "task:setGreeting": "hardhat task:setGreeting", - "test": "hardhat test", - "typechain": "cross-env TS_NODE_TRANSPILE_ONLY=true hardhat typechain" - }, - "dependencies": { - "@openzeppelin/contracts": "^5.0.0", - "hardhat-etherscan": "^1.0.1" - } -} diff --git a/packages/evm/test/RFVoting.spec.ts b/packages/evm/test/RFVoting.spec.ts deleted file mode 100644 index 841045e..0000000 --- a/packages/evm/test/RFVoting.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -const ADDRESS_ONE = "0x0000000000000000000000000000000000000001"; - -describe("RFVoting", () => { - async function deployContracts() { - const [deployer, sender, receiver] = await ethers.getSigners(); - const rfvotingFactory = await ethers.getContractFactory("RFVoting"); - const rfvotingContract = await rfvotingFactory - .connect(deployer) - .deploy(); - - return { deployer, sender, receiver, rfvotingContract }; - } - - describe("test", async () => { - it("testing", async () => { - const { deployer, rfvotingContract } = await loadFixture(deployContracts); - - expect(await rfvotingContract.tester()).to.equal("test"); - }); - }); -}); diff --git a/packages/evm/tsconfig.json b/packages/evm/tsconfig.json deleted file mode 100644 index 734e21a..0000000 --- a/packages/evm/tsconfig.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "compilerOptions": { - "declaration": true, - "declarationMap": true, - "emitDecoratorMetadata": true, - "esModuleInterop": true, - "experimentalDecorators": true, - "forceConsistentCasingInFileNames": true, - "lib": ["es2020"], - "module": "commonjs", - "moduleResolution": "node", - "noImplicitAny": true, - "removeComments": true, - "resolveJsonModule": true, - "sourceMap": true, - "strict": true, - "target": "es2020" - }, - "exclude": ["node_modules"], - "files": ["./hardhat.config.ts"], - "include": ["src/**/*", "tasks/**/*", "test/**/*", "deploy/**/*", "types/"] -} From 6674be5c916ff1e1c0a4a87ba1251edfbe30b0c5 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Tue, 10 Sep 2024 19:33:55 +0500 Subject: [PATCH 21/62] feat: start listener and relayer --- .gitignore | 5 +- .../ciphernode/src/bin/start_cipher_node.rs | 926 +++++++++--------- packages/evm/.gitignore | 21 + packages/evm/LICENSE.md | 125 +++ packages/evm/README.md | 1 + packages/evm/contracts/CRISPVoting.sol | 136 +++ packages/evm/deploy/deploy.ts | 18 + packages/evm/hardhat.config.ts | 46 + packages/evm/package.json | 85 ++ packages/evm/tsconfig.json | 22 + packages/server/Cargo.lock | 18 +- packages/server/example_config.json | 2 +- packages/server/src/bin/start_rounds.rs | 194 ---- packages/server/src/cli/voting.rs | 20 +- .../src/enclave_server/blockchain/events.rs | 64 ++ .../{ => blockchain}/listener.rs | 46 +- .../src/enclave_server/blockchain/mod.rs | 3 + .../src/enclave_server/blockchain/relayer.rs | 147 +++ packages/server/src/enclave_server/mod.rs | 16 +- .../src/enclave_server/routes/ciphernode.rs | 46 +- .../src/enclave_server/routes/voting.rs | 63 +- 21 files changed, 1217 insertions(+), 787 deletions(-) create mode 100644 packages/evm/.gitignore create mode 100644 packages/evm/LICENSE.md create mode 100644 packages/evm/README.md create mode 100644 packages/evm/contracts/CRISPVoting.sol create mode 100644 packages/evm/deploy/deploy.ts create mode 100644 packages/evm/hardhat.config.ts create mode 100644 packages/evm/package.json create mode 100644 packages/evm/tsconfig.json delete mode 100644 packages/server/src/bin/start_rounds.rs create mode 100644 packages/server/src/enclave_server/blockchain/events.rs rename packages/server/src/enclave_server/{ => blockchain}/listener.rs (75%) create mode 100644 packages/server/src/enclave_server/blockchain/mod.rs create mode 100644 packages/server/src/enclave_server/blockchain/relayer.rs diff --git a/.gitignore b/.gitignore index 2d56911..fd7cb9b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,4 @@ database *.log .pnp.* package-lock.json -.DS_Store - - -packages/evm \ No newline at end of file +.DS_Store \ No newline at end of file diff --git a/packages/ciphernode/src/bin/start_cipher_node.rs b/packages/ciphernode/src/bin/start_cipher_node.rs index 2798c3c..e24268f 100644 --- a/packages/ciphernode/src/bin/start_cipher_node.rs +++ b/packages/ciphernode/src/bin/start_cipher_node.rs @@ -1,67 +1,42 @@ -mod util; - -use std::{env, sync::Arc, fs, str}; +use std::{env, sync::Arc, fs, str, thread, time}; use std::fs::File; -use std::io::Read; -use chrono::{Utc}; +use std::io::{Read, Write}; +use chrono::Utc; use fhe::{ bfv::{BfvParametersBuilder, Ciphertext, Encoding, Plaintext, SecretKey}, mbfv::{AggregateIter, CommonRandomPoly, DecryptionShare, PublicKeyShare}, }; use fhe_traits::{FheDecoder, Serialize as FheSerialize, DeserializeParametrized}; use rand::{Rng, rngs::OsRng, thread_rng}; -use util::timeit::{timeit}; use serde::{Deserialize, Serialize}; -use http_body_util::Empty; -use hyper::Request; -use hyper::Method; - +use http_body_util::{Empty, BodyExt}; +use hyper::{Request, Method, body::Bytes}; use hyper_tls::HttpsConnector; -use hyper_util::{client::legacy::Client as HyperClient, rt::TokioExecutor}; -use bytes::Bytes; -use headers::Authorization; - -use http_body_util::BodyExt; -use tokio::io::{AsyncWriteExt as _, self}; - -use std::{thread, time}; - +use hyper_util::{client::legacy::{Client as HyperClient, connect::HttpConnector}, rt::TokioExecutor}; +use tokio::io::{AsyncWriteExt, self}; use ethers::{ providers::{Http, Provider}, types::{Address, U64}, contract::abigen, }; - use sled::Db; use once_cell::sync::Lazy; - use hmac::{Hmac, Mac}; use jwt::SignWithKey; use sha2::Sha256; use std::collections::BTreeMap; - use env_logger::{Builder, Target}; -use log::LevelFilter; -use log::info; -use std::io::Write; // Use `std::io::Write` for writing to the buffer +use log::{LevelFilter, info}; -fn init_logger() { - let mut builder = Builder::new(); - builder - .target(Target::Stdout) // Set target to stdout - .filter(None, LevelFilter::Info) // Set log level to Info - .format(|buf, record| { - writeln!( - buf, // Use `writeln!` correctly with the `buf` - "[{}:{}] - {}", - record.file().unwrap_or("unknown"), - record.line().unwrap_or(0), - record.args() - ) - }) - .init(); - } +// Constants +const DEGREE: usize = 4096; +const PLAINTEXT_MODULUS: u64 = 4096; +const MODULI: [u64; 3] = [0xffffee001, 0xffffc4001, 0x1ffffe0001]; +const POLLING_INTERVAL: u64 = 6000; +type HyperClientGet = HyperClient, Empty>; +type HyperClientPost = HyperClient, String>; +// Structs #[derive(Debug, Deserialize, Serialize)] struct JsonRequest { response: String, @@ -120,6 +95,7 @@ struct SKSShareResponse { round_id: u32, sks_shares: Vec>, } + #[derive(Debug, Deserialize, Serialize)] struct SKSSharePoll { response: String, @@ -155,7 +131,7 @@ struct StateLite { pk: Vec, start_time: i64, block_start: U64, - ciphernode_total: u32, + ciphernode_total: u32, emojis: [String; 2], } @@ -183,7 +159,7 @@ struct GetEligibilityRequest { #[derive(Debug, Deserialize, Serialize)] struct Ciphernode { id: u32, - index : Vec, + index: Vec, pk_shares: Vec>, sk_shares: Vec>, } @@ -194,114 +170,66 @@ struct RegisterNodeResponse { node_index: u32, } +// Global database static GLOBAL_DB: Lazy = Lazy::new(|| { let path = env::current_dir().unwrap(); - let mut pathst = path.display().to_string(); - pathst.push_str("/example_ciphernode_config.json"); - let mut file = File::open(pathst.clone()).unwrap(); + let config_path = format!("{}/example_ciphernode_config.json", path.display()); + let mut file = File::open(&config_path).unwrap(); let mut data = String::new(); file.read_to_string(&mut data).unwrap(); let args: Vec = env::args().collect(); - let mut cnode_selector = 0; - if args.len() != 1 { - cnode_selector = args[1].parse::().unwrap(); - }; + let cnode_selector = args.get(1).map(|arg| arg.parse::().unwrap()).unwrap_or(0); let mut config: CiphernodeConfig = serde_json::from_str(&data).expect("JSON was not well-formatted"); - let node_id: u32; - if(config.ids.len() - 1) < cnode_selector { - info!("generating new ciphernode..."); - node_id = rand::thread_rng().gen_range(0..100000); - config.ids.push(node_id); - - let configfile = serde_json::to_string(&config).unwrap(); - fs::write(pathst.clone(), configfile).unwrap(); + let node_id = if config.ids.len() <= cnode_selector { + info!("Generating new ciphernode..."); + let new_id = rand::thread_rng().gen_range(0..100000); + config.ids.push(new_id); + new_id } else if config.ids[cnode_selector] == 0 { - info!("generating initial ciphernode id..."); - node_id = rand::thread_rng().gen_range(0..100000); - config.ids[cnode_selector as usize] = node_id; - - let configfile = serde_json::to_string(&config).unwrap(); - fs::write(pathst.clone(), configfile).unwrap(); + info!("Generating initial ciphernode id..."); + let new_id = rand::thread_rng().gen_range(0..100000); + config.ids[cnode_selector] = new_id; + new_id } else { info!("Using ciphernode id {:?}", config.ids[cnode_selector]); - node_id = config.ids[cnode_selector]; + config.ids[cnode_selector] }; - let pathdb = env::current_dir().unwrap(); - let mut pathdbst = pathdb.display().to_string(); - pathdbst.push_str("/database/ciphernode-"); - pathdbst.push_str(&node_id.to_string()); - info!("Node database path {:?}", pathdbst); - sled::open(pathdbst.clone()).unwrap() + let config_file = serde_json::to_string(&config).unwrap(); + fs::write(&config_path, config_file).unwrap(); + + let db_path = format!("{}/database/ciphernode-{}", path.display(), node_id); + info!("Node database path {:?}", db_path); + sled::open(db_path).unwrap() }); +fn init_logger() { + Builder::new() + .target(Target::Stdout) + .filter(None, LevelFilter::Info) + .format(|buf, record| { + writeln!( + buf, + "[{}:{}] - {}", + record.file().unwrap_or("unknown"), + record.line().unwrap_or(0), + record.args() + ) + }) + .init(); +} + #[tokio::main] async fn main() -> Result<(), Box> { init_logger(); - info!("Getting configuration file."); - let path = env::current_dir().unwrap(); - let mut pathst = path.display().to_string(); - pathst.push_str("/example_ciphernode_config.json"); - let mut file = File::open(pathst.clone()).unwrap(); - let mut data = String::new(); - file.read_to_string(&mut data).unwrap(); - let mut config: CiphernodeConfig = serde_json::from_str(&data).expect("JSON was not well-formatted"); - let args: Vec = env::args().collect(); - let mut cnode_selector = 0; - if args.len() != 1 { - cnode_selector = args[1].parse::().unwrap(); - }; - - if(config.ids.len() - 1) < cnode_selector { - info!("generating new ciphernode..."); - let new_id = rand::thread_rng().gen_range(0..100000); - config.ids.push(new_id); - - let configfile = serde_json::to_string(&config).unwrap(); - fs::write(pathst.clone(), configfile).unwrap(); - } - - let node_id: u32 = config.ids[cnode_selector as usize]; - info!("Node ID: {:?} selected.", node_id); - let pathdb = env::current_dir().unwrap(); - let mut pathdbst = pathdb.display().to_string(); - pathdbst.push_str("/database/ciphernode-"); - pathdbst.push_str(&node_id.to_string()); - pathdbst.push_str("-state"); - - let node_state_bytes = GLOBAL_DB.get(pathdbst.clone()).unwrap(); - if node_state_bytes == None { - info!("Initializing node state in database."); - let state = Ciphernode { - id: node_id, - index: vec![0], - pk_shares: vec![vec![0]], // indexed by round id - 1 - sk_shares: vec![vec![0]], - }; - - let state_str = serde_json::to_string(&state).unwrap(); - let state_bytes = state_str.into_bytes(); - GLOBAL_DB.insert(pathdbst.clone(), state_bytes).unwrap(); - } + let config = load_config()?; + let node_id = get_node_id(&config); - let degree = 4096; - let plaintext_modulus: u64 = 4096; - let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; - - // Generate the BFV parameters structure. - let params = timeit!( - "Parameters generation", - BfvParametersBuilder::new() - .set_degree(degree) - .set_plaintext_modulus(plaintext_modulus) - .set_moduli(&moduli) - .build_arc()? - ); + let params = generate_bfv_params(); - // set the expected CRISP rounds let mut internal_round_count = RoundCount { round_count: 0 }; loop { @@ -310,179 +238,273 @@ async fn main() -> Result<(), Box> { let client_get = HyperClient::builder(TokioExecutor::new()).build::<_, Empty>(https.clone()); let client = HyperClient::builder(TokioExecutor::new()).build::<_, String>(https); - let mut url_get_rounds_str = config.enclave_address.clone(); - url_get_rounds_str.push_str("/get_rounds"); + let count = get_round_count(&client_get, &config).await?; - // generate a bearer token with shared server secret - let key: Hmac = Hmac::new_from_slice(b"some-secret")?; - let mut claims = BTreeMap::new(); - claims.insert("sub", "someone"); - let mut bearer_str = "Bearer ".to_string(); - let token_str = claims.sign_with_key(&key)?; - bearer_str.push_str(&token_str); + if count.round_count > internal_round_count.round_count { + handle_new_round(&client, &config, &count, node_id, ¶ms).await?; + } - let req = Request::builder() - //.header("authorization", bearer_str) - .method(Method::GET) - .uri(url_get_rounds_str) - .body(Empty::::new())?; - - let resp = client_get.request(req).await; - let mut count = RoundCount { - round_count: 0 - }; - match resp { - Ok(n) => { - info!("n is {:?}", n); - let body_bytes = n.collect().await?.to_bytes(); - let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); - info!("Get Round Response {:?}", body_str); - count = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); - info!("Server Round Count: {:?}", count.round_count); - info!("Internal Round Count: {:?}", internal_round_count.round_count); - }, - Err(e) => info!("Error: {:?}", e), + thread::sleep(time::Duration::from_millis(POLLING_INTERVAL)); + } +} + +fn load_config() -> Result>{ + let path = env::current_dir()?; + let config_path = format!("{}/example_ciphernode_config.json", path.display()); + let mut file = File::open(config_path)?; + let mut data = String::new(); + file.read_to_string(&mut data)?; + let config: CiphernodeConfig = serde_json::from_str(&data)?; + Ok(config) +} + +fn get_node_id(config: &CiphernodeConfig) -> u32 { + let args: Vec = env::args().collect(); + let cnode_selector = args.get(1).map(|arg| arg.parse::().unwrap()).unwrap_or(0); + config.ids[cnode_selector] +} + +fn generate_bfv_params() -> Arc { + BfvParametersBuilder::new() + .set_degree(DEGREE) + .set_plaintext_modulus(PLAINTEXT_MODULUS) + .set_moduli(&MODULI) + .build_arc() + .unwrap() +} + +async fn get_round_count(client: &HyperClientGet, config: &CiphernodeConfig) -> Result> { + let url = format!("{}/get_rounds", config.enclave_address); + let req = Request::builder() + .method(Method::GET) + .uri(url) + .body(Empty::::new())?; + + let resp = client.request(req).await?; + let body_bytes = resp.collect().await?.to_bytes(); + let body_str = String::from_utf8(body_bytes.to_vec())?; + let count: RoundCount = serde_json::from_str(&body_str)?; + info!("Server Round Count: {:?}", count.round_count); + Ok(count) +} + +async fn handle_new_round( + client: &HyperClientPost, + config: &CiphernodeConfig, + count: &RoundCount, + node_id: u32, + params: &Arc +) -> Result<(), Box> { + info!("Getting New Round State."); + let state = get_round_state(client, config, count.round_count).await?; + let eligibility = get_round_eligibility(client, config, count.round_count, node_id).await?; + + match (eligibility.is_eligible, eligibility.reason.as_str()) { + (false, "Waiting For New Round") => { + // Do nothing + }, + (true, "Open Node Spot") => { + register_ciphernode(client, config, &state, node_id, params).await?; + start_contract_watch(&state, node_id, config).await; + }, + (true, "Previously Registered") => { + start_contract_watch(&state, node_id, config).await; + }, + (false, "Round Full") => { + info!("Server reported round full, wait for next round."); + }, + _ => { + info!("Unknown eligibility status: {:?}", eligibility); } + } - // Check to see if the server reported a new round - if count.round_count > internal_round_count.round_count { - info!("Getting New Round State."); + Ok(()) +} - let response_get_state = GetRoundRequest { round_id: count.round_count }; - let out = serde_json::to_string(&response_get_state).unwrap(); - let mut url_get_state = config.enclave_address.clone(); - url_get_state.push_str("/get_round_state_lite"); - let req = Request::builder() - .header("Content-Type", "application/json") - .method(Method::POST) - .uri(url_get_state) - .body(out)?; - - let resp = client.request(req).await?; - info!("Get Round State Response status: {}", resp.status()); - let body_bytes = resp.collect().await?.to_bytes(); - let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); - - let state: StateLite = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); - - let get_eligibility = GetEligibilityRequest { - round_id: count.round_count, - node_id: node_id, - is_eligible: false, - reason: "".to_string(), - }; - let out = serde_json::to_string(&get_eligibility).unwrap(); - let mut url_get_eligibility = config.enclave_address.clone(); - url_get_eligibility.push_str("/get_round_eligibility"); - let req = Request::builder() - .header("Content-Type", "application/json") - .method(Method::POST) - .uri(url_get_eligibility) - .body(out)?; - - let resp = client.request(req).await?; - info!("Get Eligibility Response status: {}", resp.status()); - let body_bytes = resp.collect().await?.to_bytes(); - let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); - - let eligibility: GetEligibilityRequest = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); - info!("Ciphernode eligibility: {:?}", eligibility.reason); - - if eligibility.is_eligible == false && eligibility.reason == "Waiting For New Round" { - internal_round_count.round_count = count.round_count; - continue - }; - - if eligibility.is_eligible == true && eligibility.reason == "Open Node Spot" { - // do registration - info!("Generating PK share and serializing."); - - // deserialize crp_bytes - let crp = CommonRandomPoly::deserialize(&state.crp, ¶ms).unwrap(); - let sk_share_1 = SecretKey::random(¶ms, &mut OsRng); - let pk_share_1 = PublicKeyShare::new(&sk_share_1, crp.clone(), &mut thread_rng())?; - // serialize pk_share - let pk_share_bytes = pk_share_1.to_bytes(); - let sk_share_bytes = sk_share_1.coeffs.into_vec(); - let (mut node_state, db_key) = get_state(node_id); - - // overwrite old shares each new round - node_state.pk_shares[0] = pk_share_bytes.clone(); - node_state.sk_shares[0] = sk_share_bytes; - - let response_key = PKShareRequest { - response: "Register Ciphernode Key".to_string(), - pk_share: pk_share_bytes, - id: node_id, - round_id: state.id - }; - let out = serde_json::to_string(&response_key).unwrap(); - let mut url_register_keyshare = config.enclave_address.clone(); - url_register_keyshare.push_str("/register_ciphernode"); - let req = Request::builder() - .header("Content-Type", "application/json") - .method(Method::POST) - .uri(url_register_keyshare) - .body(out)?; - - let resp = client.request(req).await?; - info!("Register Node Response status: {}", resp.status()); - let body_bytes = resp.collect().await?.to_bytes(); - let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); - - let registration_res: RegisterNodeResponse = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); - info!("Ciphernode index: {:?}", registration_res.node_index); - - // index is the order in which this node registered with the server - node_state.index[0] = registration_res.node_index; - - let state_str = serde_json::to_string(&node_state).unwrap(); - let state_bytes = state_str.into_bytes(); - let _ = GLOBAL_DB.insert(db_key, state_bytes); - internal_round_count.round_count = count.round_count; - start_contract_watch(&state, node_id, &config).await; - }; - - if eligibility.is_eligible == true && eligibility.reason == "Previously Registered" { - info!("Server reported to resume watching."); - internal_round_count.round_count = count.round_count; - start_contract_watch(&state, node_id, &config).await; - }; - if eligibility.is_eligible == false && eligibility.reason == "Round Full" { - info!("Server reported round full, wait for next round."); - internal_round_count.round_count = count.round_count; - continue - }; +async fn get_round_state( + client: &HyperClientPost, + config: &CiphernodeConfig, + round_id: u32 +) -> Result> { + let url = format!("{}/get_round_state_lite", config.enclave_address); + let body = serde_json::to_string(&GetRoundRequest { round_id })?; + let req = Request::builder() + .header("Content-Type", "application/json") + .method(Method::POST) + .uri(url) + .body(body)?; + + let resp = client.request(req).await?; + let body_bytes = resp.collect().await?.to_bytes(); + let body_str = String::from_utf8(body_bytes.to_vec())?; + let state: StateLite = serde_json::from_str(&body_str)?; + Ok(state) +} + +async fn get_round_eligibility( + client: &HyperClientPost, + config: &CiphernodeConfig, + round_id: u32, + node_id: u32 +) -> Result> { + let url = format!("{}/get_round_eligibility", config.enclave_address); + let body = serde_json::to_string(&GetEligibilityRequest { + round_id, + node_id, + is_eligible: false, + reason: "".to_string(), + })?; + let req = Request::builder() + .header("Content-Type", "application/json") + .method(Method::POST) + .uri(url) + .body(body)?; + + let resp = client.request(req).await?; + let body_bytes = resp.collect().await?.to_bytes(); + let body_str = String::from_utf8(body_bytes.to_vec())?; + let eligibility: GetEligibilityRequest = serde_json::from_str(&body_str)?; + info!("Ciphernode eligibility: {:?}", eligibility.reason); + Ok(eligibility) +} + +async fn register_ciphernode( + client: &HyperClientPost, + config: &CiphernodeConfig, + state: &StateLite, + node_id: u32, + params: &Arc +) -> Result<(), Box> { + info!("Generating PK share and serializing."); + let crp = CommonRandomPoly::deserialize(&state.crp,params).unwrap(); + let sk_share = SecretKey::random(params, &mut OsRng); + let pk_share = PublicKeyShare::new(&sk_share, crp.clone(), &mut thread_rng())?; + + let pk_share_bytes = pk_share.to_bytes(); + let sk_share_bytes = sk_share.coeffs.into_vec(); + + let (mut node_state, db_key) = get_state(node_id); + + node_state.pk_shares[0] = pk_share_bytes.clone(); + node_state.sk_shares[0] = sk_share_bytes; + + let response_key = PKShareRequest { + response: "Register Ciphernode Key".to_string(), + pk_share: pk_share_bytes, + id: node_id, + round_id: state.id + }; + + let url = format!("{}/register_ciphernode", config.enclave_address); + let body = serde_json::to_string(&response_key)?; + let req = Request::builder() + .header("Content-Type", "application/json") + .method(Method::POST) + .uri(url) + .body(body)?; + + let resp = client.request(req).await?; + let body_bytes = resp.collect().await?.to_bytes(); + let body_str = String::from_utf8(body_bytes.to_vec())?; + let registration_res: RegisterNodeResponse = serde_json::from_str(&body_str)?; + + info!("Ciphernode index: {:?}", registration_res.node_index); + + node_state.index[0] = registration_res.node_index; + + let state_str = serde_json::to_string(&node_state)?; + let state_bytes = state_str.into_bytes(); + GLOBAL_DB.insert(db_key, state_bytes)?; + + Ok(()) +} + +async fn start_contract_watch(state: &StateLite, node_id: u32, config: &CiphernodeConfig) { + let params = generate_bfv_params(); + let https = HttpsConnector::new(); + let client = HyperClient::builder(TokioExecutor::new()).build::<_, String>(https); + + loop { + info!("Waiting for round {:?} poll to end.", state.id); + let now = Utc::now(); + let internal_time = now.timestamp(); + + if (state.start_time + state.poll_length as i64) < internal_time { + info!("Poll time ended... performing FHE computation"); + + match process_votes(&client, state, node_id, config, ¶ms).await { + Ok(_) => break, + Err(e) => { + info!("Error processing votes: {:?}", e); + // Implement appropriate error handling or retry logic here + } + } } + + thread::sleep(time::Duration::from_millis(POLLING_INTERVAL)); + } +} - // Polling time to server... - let polling_wait = time::Duration::from_millis(6000); - thread::sleep(polling_wait); +async fn process_votes( + client: &HyperClientPost, + state: &StateLite, + node_id: u32, + config: &CiphernodeConfig, + params: &Arc +) -> Result<(), Box> { + let num_voters = get_vote_count(client, config, state.id).await?; + let votes_collected = get_votes_contract(state.block_start, state.voting_address.clone(), state.chain_id).await?; + + info!("Votes Collected Len: {:?}", votes_collected.len()); + info!("All votes collected? {:?}", num_voters.vote_count == votes_collected.len() as u32); + + if votes_collected.is_empty() { + report_tally(client, config, state.id, 0, 0).await?; + return Ok(()); } + + let tally = tally_votes(&votes_collected, params); + let decryption_shares = collect_decryption_shares(client, config, state, node_id, &tally, params).await?; + let tally_result = decrypt_tally(decryption_shares, &tally, params)?; + + let option_1_total = tally_result; + let option_2_total = num_voters.vote_count - tally_result as u32; + + info!("Vote result = {} / {}", tally_result, num_voters.vote_count); + info!("Option 1 total: {:?}", option_1_total); + info!("Option 2 total: {:?}", option_2_total); + + report_tally(client, config, state.id, option_1_total as u32, option_2_total).await?; + + Ok(()) } -fn get_state(node_id: u32) -> (Ciphernode, String) { - let pathdb = env::current_dir().unwrap(); - let mut pathdbst = pathdb.display().to_string(); - pathdbst.push_str("/database/ciphernode-"); - pathdbst.push_str(&node_id.to_string()); - pathdbst.push_str("-state"); - info!("Database key is {:?}", pathdbst); - let state_out = GLOBAL_DB.get(pathdbst.clone()).unwrap().unwrap(); - let state_out_str = str::from_utf8(&state_out).unwrap(); - let state_out_struct: Ciphernode = serde_json::from_str(&state_out_str).unwrap(); - (state_out_struct, pathdbst) +async fn get_vote_count( + client: &HyperClientPost, + config: &CiphernodeConfig, + round_id: u32 +) -> Result> { + let url = format!("{}/get_vote_count_by_round", config.enclave_address); + let body = serde_json::to_string(&VoteCountRequest { round_id, vote_count: 0 })?; + let req = Request::builder() + .header("Content-Type", "application/json") + .method(Method::POST) + .uri(url) + .body(body)?; + + let resp = client.request(req).await?; + let body_bytes = resp.collect().await?.to_bytes(); + let body_str = String::from_utf8(body_bytes.to_vec())?; + let num_voters: VoteCountRequest = serde_json::from_str(&body_str)?; + + info!("VoteCountRequest: {:?}", num_voters); + Ok(num_voters) } -async fn get_votes_contract(block_start: U64, address: String, _chain_id: u32) -> Vec> { +async fn get_votes_contract(block_start: U64, address: String, _chain_id: u32) -> Result>, Box> { info!("Filtering contract for votes"); - // chain state - // let infura_key = "INFURAKEY"; - // let infura_val = env::var(infura_key).unwrap(); - // let mut rpc_url = "https://sepolia.infura.io/v3/".to_string(); - // rpc_url.push_str(&infura_val); - - let mut rpc_url = "http://127.0.0.1:8545".to_string(); + + let rpc_url = "http://127.0.0.1:8545".to_string(); abigen!( IVOTE, @@ -493,227 +515,151 @@ async fn get_votes_contract(block_start: U64, address: String, _chain_id: u32) - event Voted(address indexed voter, bytes vote) ]"#, ); - let provider = Provider::::try_from(rpc_url.clone()).unwrap(); - let contract_address = address.parse::
().unwrap(); + + let provider = Provider::::try_from(rpc_url)?; + let contract_address = address.parse::
()?; let client = Arc::new(provider); let contract = IVOTE::new(contract_address, Arc::new(client.clone())); - let events = contract.events().from_block(block_start).query().await.unwrap(); + let events = contract.events().from_block(block_start).query().await?; + + let votes_encrypted: Vec> = events.iter().map(|event| event.vote.to_vec()).collect(); + Ok(votes_encrypted) +} - let mut votes_encrypted = Vec::with_capacity(events.len()); - for event in events.iter() { - votes_encrypted.push(event.vote.to_vec()); +fn tally_votes(votes_collected: &[Vec], params: &Arc) -> Arc { + let mut sum = Ciphertext::zero(params); + for vote in votes_collected { + let deserialized_vote = Ciphertext::from_bytes(vote, params).unwrap(); + sum += &deserialized_vote; } - votes_encrypted + Arc::new(sum) } -async fn start_contract_watch(state: &StateLite, node_id: u32, config: &CiphernodeConfig) { - let degree = 4096; - let plaintext_modulus: u64 = 4096; - let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; - - // Generate the BFV parameters structure. - let params = timeit!( - "Parameters generation", - BfvParametersBuilder::new() - .set_degree(degree) - .set_plaintext_modulus(plaintext_modulus) - .set_moduli(&moduli) - .build_arc().unwrap() - ); +async fn collect_decryption_shares( + client: &HyperClientPost, + config: &CiphernodeConfig, + state: &StateLite, + node_id: u32, + tally: &Arc, + params: &Arc +) -> Result, Box> { + let (node_state, _) = get_state(node_id); + let sk_share_coeff_bytes = node_state.sk_shares[0].clone(); + let sk_share = SecretKey::new(sk_share_coeff_bytes, params); + + let sh = DecryptionShare::new(&sk_share, tally, &mut thread_rng())?; + let sks_bytes = sh.to_bytes(); + + let response_sks = SKSShareRequest { + response: "Register_SKS_Share".to_string(), + sks_share: sks_bytes, + index: node_state.index[0], + round_id: state.id + }; - let https = HttpsConnector::new(); - let client = HyperClient::builder(TokioExecutor::new()).build::<_, String>(https); + let url = format!("{}/register_sks_share", config.enclave_address); + let body = serde_json::to_string(&response_sks)?; + let req = Request::builder() + .header("Content-Type", "application/json") + .method(Method::POST) + .uri(url) + .body(body)?; - let num_parties = state.ciphernode_total; + let mut resp = client.request(req).await?; + info!("Register SKS Response status: {}", resp.status()); - // For each voting round this node is participating in, check the contracts for vote events. - // When voting is finalized, begin group decrypt process + while let Some(frame) = resp.frame().await { + if let Some(chunk) = frame?.data_ref() { + io::stdout().write_all(chunk).await?; + } + } - // let (tx, rx) = mpsc::channel::<()>(); - // let rt = Runtime::new().unwrap(); - // //thread::spawn(move || { - // rt.spawn(async move { poll_contract(state.id, node_id, rx).await }); - // //}); + let mut decryption_shares = Vec::with_capacity(state.ciphernode_total as usize); - // TODO: move to thread so main loop can continue to look for more work loop { - info!("Waiting for round {:?} poll to end.", state.id); - let now = Utc::now(); - let internal_time = now.timestamp(); - if (state.start_time + state.poll_length as i64) < internal_time { - info!("poll time ended... performing fhe computation"); + let response_get_sks = SKSSharePoll { + response: "Get_All_SKS_Shares".to_string(), + round_id: state.id, + ciphernode_count: state.ciphernode_total + }; - let response_get_voters = VoteCountRequest { round_id: state.id, vote_count: 0 }; - let out = serde_json::to_string(&response_get_voters).unwrap(); - let mut url_get_voters = config.enclave_address.clone(); - url_get_voters.push_str("/get_vote_count_by_round"); - let req = Request::builder() + let url = format!("{}/get_sks_shares", config.enclave_address); + let body = serde_json::to_string(&response_get_sks)?; + let req = Request::builder() .header("Content-Type", "application/json") - .method(Method::POST) - .uri(url_get_voters) - .body(out).unwrap(); - - let resp = client.request(req).await.unwrap(); - info!("Get Vote Count Response status: {}", resp.status()); - - let body_bytes_get_voters = resp.collect().await.unwrap().to_bytes(); - let body_str_get_voters = String::from_utf8(body_bytes_get_voters.to_vec()).unwrap(); - let num_voters: VoteCountRequest = serde_json::from_str(&body_str_get_voters).expect("JSON was not well-formatted"); - - info!("VoteCountRequest: {:?}", num_voters); - - let votes_collected = get_votes_contract(state.block_start, state.voting_address.clone(), state.chain_id).await; - info!("Votes Collected Len: {:?}", votes_collected.len()); - info!("All votes collected? {:?}", num_voters.vote_count == votes_collected.len() as u32); - - if votes_collected.len() == 0 { - info!("Vote result = {} / {}", 0, num_voters.vote_count); - - let response_report = ReportTallyRequest { - round_id: state.id, - option_1: 0, - option_2: 0 - }; - let out = serde_json::to_string(&response_report).unwrap(); - let mut url_report = config.enclave_address.clone(); - url_report.push_str("/report_tally"); - let req = Request::builder() - .header("Content-Type", "application/json") - .method(Method::POST) - .uri(url_report) - .body(out).unwrap(); - - let resp = client.request(req).await.unwrap(); - info!("Tally Reported Response status: {}", resp.status()); - break; - } + .method(Method::POST) + .uri(url) + .body(body)?; - let tally = timeit!("Vote tallying", { - let mut sum = Ciphertext::zero(¶ms); - for i in 0..(votes_collected.len()) { - let deserialized_vote = Ciphertext::from_bytes(&votes_collected[i as usize], ¶ms).unwrap(); - sum += &deserialized_vote; - } - Arc::new(sum) - }); - - // The result of a vote is typically public, so in this scenario the parties can - // perform a collective decryption. If instead the result of the computation - // should be kept private, the parties could collectively perform a - // keyswitch to a different public key. - let mut decryption_shares = Vec::with_capacity(state.ciphernode_total as usize); - let (node_state, _db_key) = get_state(node_id); - let sk_share_coeff_bytes = node_state.sk_shares[0].clone(); - let sk_share_1 = SecretKey::new(sk_share_coeff_bytes, ¶ms); - - let sh = DecryptionShare::new(&sk_share_1, &tally, &mut thread_rng()).unwrap(); - let sks_bytes = sh.to_bytes(); - - let response_sks = SKSShareRequest { - response: "Register_SKS_Share".to_string(), - sks_share: sks_bytes, - index: node_state.index[0], // index of stored pk shares on server - round_id: state.id - }; - let out = serde_json::to_string(&response_sks).unwrap(); - let mut url_register_sks = config.enclave_address.clone(); - url_register_sks.push_str("/register_sks_share"); - let req = Request::builder() - .header("Content-Type", "application/json") - .method(Method::POST) - .uri(url_register_sks) - .body(out).unwrap(); - - let mut resp = client.request(req).await.unwrap(); - info!("Register SKS Response status: {}", resp.status()); - - // Stream the body, writing each frame to stdout as it arrives - while let Some(next) = resp.frame().await { - let frame = next.unwrap(); - if let Some(chunk) = frame.data_ref() { - io::stdout().write_all(chunk).await.unwrap(); - } - } + let resp = client.request(req).await?; + info!("Get All SKS Response status: {}", resp.status()); - // poll the enclave server to get all sks shares. - loop { - let response_get_sks = SKSSharePoll { - response: "Get_All_SKS_Shares".to_string(), - round_id: state.id, - ciphernode_count: num_parties as u32 - }; - let out = serde_json::to_string(&response_get_sks).unwrap(); - let mut url_register_get_sks = config.enclave_address.clone(); - url_register_get_sks.push_str("/get_sks_shares"); - let req = Request::builder() - .header("Content-Type", "application/json") - .method(Method::POST) - .uri(url_register_get_sks) - .body(out).unwrap(); - - let resp = client.request(req).await.unwrap(); - info!("Get All SKS Response status: {}", resp.status()); - - if resp.status().to_string() == "500 Internal Server Error" { - info!("enclave resource failed, trying to poll for sks shares again..."); - continue; - } + if resp.status().as_u16() == 500 { + info!("Enclave resource failed, trying to poll for sks shares again..."); + continue; + } - let body_bytes = resp.collect().await.unwrap().to_bytes(); - let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); - let shares: SKSShareResponse = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); - - if shares.response == "final" { - // do decrypt - info!("collected all of the decrypt shares!"); - for i in 0..state.ciphernode_total { - decryption_shares.push(DecryptionShare::deserialize(&shares.sks_shares[i as usize], ¶ms, tally.clone())); - } - - // Again, an aggregating party aggregates the decryption shares to produce the - // decrypted plaintext. - let tally_pt = timeit!("Decryption share aggregation", { - let pt: Plaintext = decryption_shares.into_iter().aggregate().unwrap(); - pt - }); - let tally_vec = Vec::::try_decode(&tally_pt, Encoding::poly()).unwrap(); - let tally_result = tally_vec[0]; - - // Show vote result - info!("Vote result = {} / {}", tally_result, num_voters.vote_count); - - // report result to server - let option_1_total = tally_result; - let option_2_total = num_voters.vote_count - tally_result as u32; - info!("option 1 total {:?}", option_1_total); - info!("option 2 total {:?}", option_2_total); - - let response_report = ReportTallyRequest { - round_id: state.id, - option_1: option_1_total as u32, - option_2: option_2_total as u32 - }; - let out = serde_json::to_string(&response_report).unwrap(); - let mut url_report = config.enclave_address.clone(); - url_report.push_str("/report_tally"); - let req = Request::builder() - .header("Content-Type", "application/json") - .method(Method::POST) - .uri(url_report) - .body(out).unwrap(); - - let resp = client.request(req).await.unwrap(); - info!("Tally Reported Response status: {}", resp.status()); - break; - } + let body_bytes = resp.collect().await?.to_bytes(); + let body_str = String::from_utf8(body_bytes.to_vec())?; + let shares: SKSShareResponse = serde_json::from_str(&body_str)?; - let polling_sks = time::Duration::from_millis(3000); - thread::sleep(polling_sks); + if shares.response == "final" { + info!("Collected all of the decrypt shares!"); + for sks_share in shares.sks_shares { + decryption_shares.push(DecryptionShare::deserialize(&sks_share, params, tally.clone())?); } break; } - let polling_end_round = time::Duration::from_millis(6000); - thread::sleep(polling_end_round); + + thread::sleep(time::Duration::from_millis(3000)); } + + Ok(decryption_shares) } + +fn decrypt_tally( + decryption_shares: Vec, + tally: &Arc, + params: &Arc +) -> Result> { + let tally_pt: Plaintext = decryption_shares.into_iter().aggregate()?; + let tally_vec = Vec::::try_decode(&tally_pt, Encoding::poly())?; + Ok(tally_vec[0]) +} + +async fn report_tally( + client: &HyperClientPost, + config: &CiphernodeConfig, + round_id: u32, + option_1: u32, + option_2: u32 +) -> Result<(), Box> { + let response_report = ReportTallyRequest { + round_id, + option_1, + option_2 + }; + + let url = format!("{}/report_tally", config.enclave_address); + let body = serde_json::to_string(&response_report)?; + let req = Request::builder() + .header("Content-Type", "application/json") + .method(Method::POST) + .uri(url) + .body(body)?; + + let resp = client.request(req).await?; + info!("Tally Reported Response status: {}", resp.status()); + Ok(()) +} + +fn get_state(node_id: u32) -> (Ciphernode, String) { + let pathdb = env::current_dir().unwrap(); + let pathdbst = format!("{}/database/ciphernode-{}-state", pathdb.display(), node_id); + info!("Database key is {:?}", pathdbst); + let state_out = GLOBAL_DB.get(pathdbst.clone()).unwrap().unwrap(); + let state_out_str = str::from_utf8(&state_out).unwrap(); + let state_out_struct: Ciphernode = serde_json::from_str(state_out_str).unwrap(); + (state_out_struct, pathdbst) +} \ No newline at end of file diff --git a/packages/evm/.gitignore b/packages/evm/.gitignore new file mode 100644 index 0000000..18a269e --- /dev/null +++ b/packages/evm/.gitignore @@ -0,0 +1,21 @@ +# directories +.coverage_artifacts +.coverage_cache +.coverage_contracts +artifacts +build +cache +coverage +dist +node_modules +types +deployments + +# files +*.env +*.log +.DS_Store +.pnp.* +coverage.json +package-lock.json +yarn.lock diff --git a/packages/evm/LICENSE.md b/packages/evm/LICENSE.md new file mode 100644 index 0000000..7f9dcfa --- /dev/null +++ b/packages/evm/LICENSE.md @@ -0,0 +1,125 @@ +GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute +verbatim copies of this license document, but changing it is not allowed. + +This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU +General Public License, supplemented by the additional permissions listed below. + +0. Additional Definitions. + +As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to +version 3 of the GNU General Public License. + +"The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined +below. + +An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on +the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by +the Library. + +A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of +the Library with which the Combined Work was made is also called the "Linked Version". + +The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding +any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not +on the Linked Version. + +The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, +including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the +System Libraries of the Combined Work. + +1. Exception to Section 3 of the GNU GPL. + +You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. + +2. Conveying Modified Versions. + +If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied +by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may +convey a copy of the modified version: + +a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not +supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, +or + +b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. + +3. Object Code Incorporating Material from Library Header Files. + +The object code form of an Application may incorporate material from a header file that is part of the Library. You may +convey such object code under terms of your choice, provided that, if the incorporated material is not limited to +numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or +fewer lines in length), you do both of the following: + +a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its +use are covered by this License. + +b) Accompany the object code with a copy of the GNU GPL and this license document. + +4. Combined Works. + +You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification +of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, +if you also do each of the following: + +a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its +use are covered by this License. + +b) Accompany the Combined Work with a copy of the GNU GPL and this license document. + +c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library +among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. + +d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + +e) Provide Installation Information, but only if you would otherwise be required to provide such information under +section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified +version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked +Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and +Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner +specified by section 6 of the GNU GPL for conveying Corresponding Source.) + +5. Combined Libraries. + +You may place library facilities that are a work based on the Library side by side in a single library together with +other library facilities that are not Applications and are not covered by this License, and convey such a combined +library under terms of your choice, if you do both of the following: + +a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library +facilities, conveyed under the terms of this License. + +b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where +to find the accompanying uncombined form of the same work. + +6. Revised Versions of the GNU Lesser General Public License. + +The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time +to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new +problems or concerns. + +Each version is given a distinguishing version number. If the Library as you received it specifies that a certain +numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of +following the terms and conditions either of that published version or of any later version published by the Free +Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General +Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software +Foundation. + +If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General +Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for +you to choose that version for the Library. diff --git a/packages/evm/README.md b/packages/evm/README.md new file mode 100644 index 0000000..b04c573 --- /dev/null +++ b/packages/evm/README.md @@ -0,0 +1 @@ +# RFV Contracts \ No newline at end of file diff --git a/packages/evm/contracts/CRISPVoting.sol b/packages/evm/contracts/CRISPVoting.sol new file mode 100644 index 0000000..66c19bc --- /dev/null +++ b/packages/evm/contracts/CRISPVoting.sol @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: LGPLv3 +pragma solidity ^0.8.20; + +contract CRISPVoting { + struct Poll { + uint256 e3Id; // Unique ID for each CRISP round (E3 computation) + uint256 startTime; // Start time of the poll + uint256 endTime; // End time of the poll + bytes e3Params; // Parameters for the E3 computation + bytes committeePublicKey; // Public key published by the committee + bytes ciphertextOutput; // Final ciphertext submitted by the relayer + bytes plaintextOutput; // Final plaintext result after decryption + } + + uint256 public e3Counter = 0; // Counter for E3 IDs + mapping(uint256 => Poll) public polls; // Stores each poll by its e3Id + + event E3Requested( + uint256 indexed e3Id, + uint256 startTime, + uint256 endTime, + bytes e3Params + ); + event VoteCast(uint256 indexed e3Id, bytes vote); + event PublicKeyPublished(uint256 indexed e3Id, bytes committeePublicKey); + event CiphertextSubmitted(uint256 indexed e3Id, bytes ciphertextOutput); + event PlaintextSubmitted(uint256 indexed e3Id, bytes plaintextOutput); + + // Function to request a new poll (E3 computation) and start a round + function requestE3( + uint256 startWindowStart, + uint256 duration, + bytes memory e3Params + ) public { + e3Counter++; + uint256 startTime = block.timestamp > startWindowStart + ? block.timestamp + : startWindowStart; + uint256 endTime = startTime + duration; + + Poll memory newPoll = Poll({ + e3Id: e3Counter, + startTime: startTime, + endTime: endTime, + e3Params: e3Params, + committeePublicKey: "", + ciphertextOutput: "", + plaintextOutput: "" + }); + + polls[e3Counter] = newPoll; + + emit E3Requested(e3Counter, startTime, endTime, e3Params); + } + + function publishPublicKey(uint256 e3Id, bytes memory committeePublicKey) + public + { + require(polls[e3Id].endTime > block.timestamp, "Poll has ended."); + require( + polls[e3Id].committeePublicKey.length == 0, + "Public key already published." + ); + + polls[e3Id].committeePublicKey = committeePublicKey; + + emit PublicKeyPublished(e3Id, committeePublicKey); + } + + function castVote(uint256 e3Id, bytes memory vote) public { + require(polls[e3Id].endTime > block.timestamp, "Poll has ended."); + + emit VoteCast(e3Id, vote); + } + + // Function to submit the final ciphertext after voting has ended + function submitCiphertext( + uint256 e3Id, + bytes memory ciphertextOutput + ) public { + require( + polls[e3Id].endTime <= block.timestamp, + "Poll is still ongoing." + ); + require( + polls[e3Id].ciphertextOutput.length == 0, + "Ciphertext already submitted." + ); + + polls[e3Id].ciphertextOutput = ciphertextOutput; + + emit CiphertextSubmitted(e3Id, ciphertextOutput); + } + + // Function to submit the final plaintext result after decryption + function submitPlaintext( + uint256 e3Id, + bytes memory plaintextOutput + ) public { + require( + polls[e3Id].endTime <= block.timestamp, + "Poll is still ongoing." + ); + require( + polls[e3Id].ciphertextOutput.length > 0, + "Ciphertext must be submitted first." + ); + require( + polls[e3Id].plaintextOutput.length == 0, + "Plaintext already submitted." + ); + + polls[e3Id].plaintextOutput = plaintextOutput; + + emit PlaintextSubmitted(e3Id, plaintextOutput); + } + + // Function to retrieve the public key for voting based on e3Id + function getPublicKey(uint256 e3Id) public view returns (bytes memory) { + return polls[e3Id].committeePublicKey; + } + + // Function to retrieve the ciphertext output for a given poll + function getCiphertextOutput( + uint256 e3Id + ) public view returns (bytes memory) { + return polls[e3Id].ciphertextOutput; + } + + // Function to retrieve the plaintext result for a given poll + function getPlaintextOutput( + uint256 e3Id + ) public view returns (bytes memory) { + return polls[e3Id].plaintextOutput; + } +} diff --git a/packages/evm/deploy/deploy.ts b/packages/evm/deploy/deploy.ts new file mode 100644 index 0000000..26e0405 --- /dev/null +++ b/packages/evm/deploy/deploy.ts @@ -0,0 +1,18 @@ +import { DeployFunction } from "hardhat-deploy/types"; +import { HardhatRuntimeEnvironment } from "hardhat/types"; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + const { deployer } = await hre.getNamedAccounts(); + const { deploy } = hre.deployments; + console.log(deployer) + const crispVoting = await deploy("CRISPVoting", { + from: deployer, + args: [], + log: true, + }); + + console.log(`CRISPVoting contract: `, crispVoting.address); +}; +export default func; +func.id = "deploy_crispVoting"; // id required to prevent reexecution +func.tags = ["CRISPVoting"]; \ No newline at end of file diff --git a/packages/evm/hardhat.config.ts b/packages/evm/hardhat.config.ts new file mode 100644 index 0000000..ea504fb --- /dev/null +++ b/packages/evm/hardhat.config.ts @@ -0,0 +1,46 @@ +import "@nomicfoundation/hardhat-toolbox"; +import { config as dotenvConfig } from "dotenv"; +import "hardhat-deploy"; +import type { HardhatUserConfig } from "hardhat/config"; +import { resolve } from "path"; + +// Load environment variables from .env file +const dotenvConfigPath: string = process.env.DOTENV_CONFIG_PATH || "./.env"; +dotenvConfig({ path: resolve(__dirname, dotenvConfigPath) }); + +// Default configuration for Hardhat network +const config: HardhatUserConfig = { + defaultNetwork: "hardhat", + namedAccounts: { + deployer: 0, + }, + networks: { + hardhat: { + chainId: 31337, // Default Hardhat Network Chain ID + accounts: { + mnemonic: process.env.MNEMONIC || "test test test test test test test test test test test junk", + }, + }, + }, + paths: { + artifacts: "./artifacts", + cache: "./cache", + sources: "./contracts", + tests: "./test", + }, + solidity: { + version: "0.8.21", + settings: { + optimizer: { + enabled: true, + runs: 800, + }, + }, + }, + typechain: { + outDir: "types", + target: "ethers-v6", + }, +}; + +export default config; diff --git a/packages/evm/package.json b/packages/evm/package.json new file mode 100644 index 0000000..de41149 --- /dev/null +++ b/packages/evm/package.json @@ -0,0 +1,85 @@ +{ + "name": "@gnosisguild/GGMI", + "description": "", + "version": "1.0.0", + "author": { + "name": "gnosisguild", + "url": "https://github.com/gnosisguild" + }, + "devDependencies": { + "@nomicfoundation/hardhat-chai-matchers": "^2.0.0", + "@nomicfoundation/hardhat-ethers": "^3.0.0", + "@nomicfoundation/hardhat-network-helpers": "^1.0.6", + "@nomicfoundation/hardhat-toolbox": "^3.0.0", + "@nomicfoundation/hardhat-verify": "^1.0.0", + "@trivago/prettier-plugin-sort-imports": "^4.0.0", + "@typechain/ethers-v6": "^0.4.0", + "@typechain/hardhat": "^8.0.0", + "@types/chai": "^4.3.4", + "@types/fs-extra": "^9.0.13", + "@types/mocha": "^10.0.0", + "@types/node": "^18.11.9", + "@typescript-eslint/eslint-plugin": "^5.44.0", + "@typescript-eslint/parser": "^5.44.0", + "chai": "^4.3.7", + "cross-env": "^7.0.3", + "dotenv": "^16.0.3", + "eslint": "^8.28.0", + "eslint-config-prettier": "^8.5.0", + "ethers": "^6.4.0", + "fs-extra": "^10.1.0", + "hardhat": "^2.12.2", + "hardhat-deploy": "^0.11.29", + "hardhat-gas-reporter": "^1.0.9", + "lodash": "^4.17.21", + "mocha": "^10.1.0", + "prettier": "^2.8.4", + "prettier-plugin-solidity": "^1.1.2", + "rimraf": "^4.1.2", + "solhint": "^3.4.0", + "solhint-plugin-prettier": "^0.0.5", + "solidity-coverage": "^0.8.2", + "ts-generator": "^0.1.1", + "ts-node": "^10.9.1", + "typechain": "^8.2.0", + "typescript": "^4.9.3" + }, + "files": [ + "contracts" + ], + "keywords": [ + "blockchain", + "ethers", + "ethereum", + "hardhat", + "smart-contracts", + "solidity", + "template", + "typescript", + "typechain" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "clean": "rimraf ./artifacts ./cache ./coverage ./types ./coverage.json && yarn typechain", + "compile": "cross-env TS_NODE_TRANSPILE_ONLY=true hardhat compile", + "coverage": "hardhat coverage --solcoverjs ./.solcover.js --temp artifacts --testfiles \"test/**/*.ts\" && yarn typechain", + "deploy:contracts": "hardhat deploy", + "deploy": "hardhat deploy --network", + "lint": "yarn lint:sol && yarn lint:ts && yarn prettier:check", + "lint:sol": "solhint --max-warnings 0 \"contracts/**/*.sol\"", + "lint:ts": "eslint --ignore-path ./.eslintignore --ext .js,.ts .", + "postinstall": "DOTENV_CONFIG_PATH=./.env.example yarn typechain", + "prettier:check": "prettier --check \"**/*.{js,json,md,sol,ts,yml}\"", + "prettier:write": "prettier --write \"**/*.{js,json,md,sol,ts,yml}\"", + "task:deployGreeter": "hardhat task:deployGreeter", + "task:setGreeting": "hardhat task:setGreeting", + "test": "hardhat test", + "typechain": "cross-env TS_NODE_TRANSPILE_ONLY=true hardhat typechain" + }, + "dependencies": { + "@openzeppelin/contracts": "^5.0.0", + "hardhat-etherscan": "^1.0.1" + } +} diff --git a/packages/evm/tsconfig.json b/packages/evm/tsconfig.json new file mode 100644 index 0000000..bf1f40a --- /dev/null +++ b/packages/evm/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDecoratorMetadata": true, + "esModuleInterop": true, + "experimentalDecorators": true, + "forceConsistentCasingInFileNames": true, + "lib": ["es2020"], + "module": "commonjs", + "moduleResolution": "node", + "noImplicitAny": true, + "removeComments": true, + "resolveJsonModule": true, + "sourceMap": true, + "strict": true, + "target": "es2020" + }, + "exclude": ["node_modules"], + "files": ["./hardhat.config.ts"], + "include": ["src/**/*", "tasks/**/*", "deploy/**/*", "types/"] +} diff --git a/packages/server/Cargo.lock b/packages/server/Cargo.lock index 2bb1785..4f95d31 100644 --- a/packages/server/Cargo.lock +++ b/packages/server/Cargo.lock @@ -1884,9 +1884,9 @@ name = "compute-provider-host" version = "0.1.0" dependencies = [ "compute-provider-core", + "compute-provider-methods", "fhe", "fhe-traits", - "methods", "rand 0.8.5", "rayon", "risc0-build-ethereum", @@ -1896,6 +1896,14 @@ dependencies = [ "tracing-subscriber 0.3.18", ] +[[package]] +name = "compute-provider-methods" +version = "0.1.0" +dependencies = [ + "risc0-build", + "risc0-build-ethereum", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -4026,14 +4034,6 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" -[[package]] -name = "methods" -version = "0.1.0" -dependencies = [ - "risc0-build", - "risc0-build-ethereum", -] - [[package]] name = "mime" version = "0.2.6" diff --git a/packages/server/example_config.json b/packages/server/example_config.json index 2814151..0738748 100644 --- a/packages/server/example_config.json +++ b/packages/server/example_config.json @@ -1,6 +1,6 @@ { "round_id": 0, - "poll_length": 120, + "poll_length": 90, "chain_id": 31337, "voting_address": "0x5FbDB2315678afecb367f032d93F642f64180aa3", "ciphernode_count": 2, diff --git a/packages/server/src/bin/start_rounds.rs b/packages/server/src/bin/start_rounds.rs deleted file mode 100644 index d14464f..0000000 --- a/packages/server/src/bin/start_rounds.rs +++ /dev/null @@ -1,194 +0,0 @@ -use rfv::util::timeit::timeit; -use dialoguer::{theme::ColorfulTheme, Input, FuzzySelect}; -use std::{thread, time, env}; -use serde::{Deserialize, Serialize}; -use std::fs::File; -use std::io::Read; - -use fhe::{ - bfv::{BfvParametersBuilder, Encoding, Plaintext, PublicKey}, -}; -use fhe_traits::{FheEncoder, FheEncrypter, Serialize as FheSerialize, DeserializeParametrized}; -use rand::{thread_rng}; - -use hyper::Request; -use hyper::Method; -use hyper_tls::HttpsConnector; -use hyper_util::{client::legacy::Client as HyperClient, rt::TokioExecutor}; -use bytes::Bytes; - -use http_body_util::Empty; -use http_body_util::BodyExt; -use tokio::io::{AsyncWriteExt as _, self}; - -use hmac::{Hmac, Mac}; -use jwt::SignWithKey; -use sha2::Sha256; -use std::collections::BTreeMap; - - -#[derive(Debug, Deserialize, Serialize)] -struct JsonResponse { - response: String -} - -#[derive(Debug, Deserialize, Serialize)] -struct JsonResponseTxHash { - response: String, - tx_hash: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct JsonRequestGetRounds { - response: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct RoundCount { - round_count: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct JsonRequest { - response: String, - pk_share: u32, - id: u32, - round_id: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct CrispConfig { - round_id: u32, - poll_length: u32, - chain_id: u32, - voting_address: String, - ciphernode_count: u32, - enclave_address: String, - authentication_id: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct PKRequest { - round_id: u32, - pk_bytes: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -struct EncryptedVote { - round_id: u32, - enc_vote_bytes: Vec, - postId: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct AuthenticationLogin { - postId: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct AuthenticationResponse { - response: String, - jwt_token: String, -} - -#[tokio::main] -async fn main() -> Result<(), Box> { - - let https = HttpsConnector::new(); - //let client = HyperClient::builder(TokioExecutor::new()).build::<_, Empty>(https); - let client_get = HyperClient::builder(TokioExecutor::new()).build::<_, Empty>(https.clone()); - let client = HyperClient::builder(TokioExecutor::new()).build::<_, String>(https); - let mut auth_res = AuthenticationResponse { - response: "".to_string(), - jwt_token: "".to_string(), - }; - - loop { - print!("{esc}[2J{esc}[1;1H", esc = 27 as char); - info!("Starting new CRISP round!"); - - info!("Reading proposal details from config."); - let path = env::current_dir().unwrap(); - let mut pathst = path.display().to_string(); - pathst.push_str("/example_config.json"); - let mut file = File::open(pathst).unwrap(); - let mut data = String::new(); - file.read_to_string(&mut data).unwrap(); - let config: CrispConfig = serde_json::from_str(&data).expect("JSON was not well-formatted"); - info!("round id: {:?}", config.round_id); // get new round id from current id in server - info!("poll length {:?}", config.poll_length); - info!("chain id: {:?}", config.chain_id); - info!("voting contract: {:?}", config.voting_address); - info!("ciphernode count: {:?}", config.ciphernode_count); - - info!("Initializing Keyshare nodes..."); - - let response_id = JsonRequestGetRounds { response: "Test".to_string() }; - let _out = serde_json::to_string(&response_id).unwrap(); - let mut url_id = config.enclave_address.clone(); - url_id.push_str("/get_rounds"); - - //let token = Authorization::bearer("some-opaque-token").unwrap(); - //info!("bearer token {:?}", token.token()); - //todo: add auth field to config file to get bearer token - let req = Request::builder() - .method(Method::GET) - .uri(url_id) - .body(Empty::::new())?; - - let resp = client_get.request(req).await?; - - info!("Response status: {}", resp.status()); - - let body_bytes = resp.collect().await?.to_bytes(); - let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); - let count: RoundCount = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); - info!("Server Round Count: {:?}", count.round_count); - - // TODO: get secret from env var - // let key: Hmac = Hmac::new_from_slice(b"some-secret")?; - // let mut claims = BTreeMap::new(); - // claims.insert("postId", config.authentication); - // let mut bearer_str = "Bearer ".to_string(); - // let token_str = claims.sign_with_key(&key)?; - // bearer_str.push_str(&token_str); - // info!("{:?}", bearer_str); - - let round_id = count.round_count + 1; - let response = CrispConfig { - round_id: round_id, - poll_length: config.poll_length, - chain_id: config.chain_id, - voting_address: config.voting_address, - ciphernode_count: config.ciphernode_count, - enclave_address: config.enclave_address.clone(), - authentication_id: config.authentication_id.clone(), - }; - let out = serde_json::to_string(&response).unwrap(); - let mut url = config.enclave_address.clone(); - url.push_str("/init_crisp_round"); - let req = Request::builder() - //.header("authorization", "Bearer fpKL54jvWmEGVoRdCNjG") - .method(Method::POST) - .uri(url) - .body(out)?; - - let mut resp = client.request(req).await?; - - info!("Response status: {}", resp.status()); - - while let Some(next) = resp.frame().await { - let frame = next?; - if let Some(chunk) = frame.data_ref() { - io::stdout().write_all(chunk).await?; - } - } - info!("Round Initialized."); - - let next_round_start = config.poll_length + 60; - let seconds = time::Duration::from_secs(next_round_start as u64); - thread::sleep(seconds); - } - - Ok(()) -} diff --git a/packages/server/src/cli/voting.rs b/packages/server/src/cli/voting.rs index b505f7d..de505ba 100644 --- a/packages/server/src/cli/voting.rs +++ b/packages/server/src/cli/voting.rs @@ -1,11 +1,16 @@ use std::{thread, time::Duration}; -use bytes::Bytes; use dialoguer::{theme::ColorfulTheme, FuzzySelect, Input}; use http_body_util::{BodyExt, Empty}; use hyper::{body::Incoming, Method, Request, Response}; use serde::{Deserialize, Serialize}; use tokio::io::{self, AsyncWriteExt}; use log::{info, error}; +use std::env; +use chrono::Utc; + +use alloy::primitives::{Bytes, U256}; + +use crate::enclave_server::blockchain::relayer::CRISPVotingContract; use crate::cli::{AuthenticationResponse, HyperClientGet, HyperClientPost}; use crate::util::timeit::timeit; @@ -43,6 +48,7 @@ struct JsonResponseTxHash { tx_hash: String, } + async fn get_response_body(resp: Response) -> Result> { let body_bytes = resp.collect().await?.to_bytes(); Ok(String::from_utf8(body_bytes.to_vec())?) @@ -55,6 +61,18 @@ pub async fn initialize_crisp_round( ) -> Result<(), Box> { info!("Starting new CRISP round!"); info!("Initializing Keyshare nodes..."); + + // let private_key = env::var("PRIVATEKEY").expect("PRIVATEKEY must be set in the environment"); + // let rpc_url = "http://0.0.0.0:8545"; + // let contract = CRISPVotingContract::new(rpc_url, &config.voting_address, &private_key).await?; + // // Current time as start time + // let start_time = U256::from(Utc::now().timestamp()); + // let e3_params = Bytes::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + // let duration = U256::from(config.poll_length); + // let res = contract.request_e3(start_time,duration, e3_params).await?; + + // println!("E3 request sent. TxHash: {:?}", res.transaction_hash); + let url_id = format!("{}/get_rounds", config.enclave_address); let req = Request::builder() diff --git a/packages/server/src/enclave_server/blockchain/events.rs b/packages/server/src/enclave_server/blockchain/events.rs new file mode 100644 index 0000000..851c776 --- /dev/null +++ b/packages/server/src/enclave_server/blockchain/events.rs @@ -0,0 +1,64 @@ +use alloy::{ + sol, + primitives::{Address, Bytes, U256}, + providers::Provider, + sol_types::{SolCall, SolEvent}, +}; +use eyre::Result; + +use super::listener::ContractEvent; + +sol! { + #[derive(Debug)] + event E3Requested(uint256 indexed e3Id, uint256 startTime, uint256 endTime, bytes e3Params); + + #[derive(Debug)] + event VoteCast(uint256 indexed e3Id, bytes vote); + + #[derive(Debug)] + event PublicKeyPublished(uint256 indexed e3Id, bytes committeePublicKey); + + #[derive(Debug)] + event CiphertextSubmitted(uint256 indexed e3Id, bytes ciphertextOutput); + + #[derive(Debug)] + event PlaintextSubmitted(uint256 indexed e3Id, bytes plaintextOutput); +} + +impl ContractEvent for E3Requested { + fn process(&self) -> Result<()> { + println!("Processing E3 request: {:?}", self); + Ok(()) + } +} + +impl ContractEvent for VoteCast { + fn process(&self) -> Result<()> { + println!("Processing vote cast: {:?}", self); + Ok(()) + } +} + +impl ContractEvent for PublicKeyPublished { + fn process(&self) -> Result<()> { + println!("Processing public key published: {:?}", self); + Ok(()) + } +} + +impl ContractEvent for CiphertextSubmitted { + fn process(&self) -> Result<()> { + println!("Processing ciphertext submitted: {:?}", self); + Ok(()) + } +} + +impl ContractEvent for PlaintextSubmitted { + fn process(&self) -> Result<()> { + println!("Processing plaintext submitted: {:?}", self); + Ok(()) + } +} + + + diff --git a/packages/server/src/enclave_server/listener.rs b/packages/server/src/enclave_server/blockchain/listener.rs similarity index 75% rename from packages/server/src/enclave_server/listener.rs rename to packages/server/src/enclave_server/blockchain/listener.rs index 91758c1..4d16a3b 100644 --- a/packages/server/src/enclave_server/listener.rs +++ b/packages/server/src/enclave_server/blockchain/listener.rs @@ -12,19 +12,21 @@ use std::collections::HashMap; use std::fmt::Debug; use std::sync::Arc; +use super::events::{E3Requested, CiphertextSubmitted, PlaintextSubmitted, PublicKeyPublished, VoteCast}; + pub trait ContractEvent: Send + Sync + 'static { fn process(&self) -> Result<()>; } -impl ContractEvent for T -where - T: SolEvent + Debug + Send + Sync + 'static, -{ - fn process(&self) -> Result<()> { - println!("Processing event: {:?}", self); - Ok(()) - } -} +// impl ContractEvent for T +// where +// T: SolEvent + Debug + Send + Sync + 'static, +// { +// fn process(&self) -> Result<()> { +// println!("Processing event: {:?}", self); +// Ok(()) +// } +// } pub struct EventListener { provider: Arc>, @@ -95,26 +97,22 @@ impl ContractManager { } } -sol! { - #[derive(Debug)] - event TestingEvent(uint256 e3Id, bytes input); -} -#[tokio::main] -async fn start_listener() -> Result<()> { +pub async fn start_listener(contract_address: &str) -> Result<()> { let rpc_url = "ws://127.0.0.1:8545"; let manager = ContractManager::new(rpc_url).await?; - let address1 = address!("e7f1725E7734CE288F8367e1Bb143E90bb3F0512"); - let mut listener1 = manager.add_listener(address1); - listener1.add_event_handler::(); - - let address2 = address!("5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"); - let mut listener2 = manager.add_listener(address2); - listener2.add_event_handler::(); + let address: Address = contract_address.parse()?; + let mut listener = manager.add_listener(address); + listener.add_event_handler::(); + listener.add_event_handler::(); + listener.add_event_handler::(); + listener.add_event_handler::(); + listener.add_event_handler::(); - tokio::try_join!(listener1.listen(), listener2.listen())?; + // Start listening + listener.listen().await?; Ok(()) -} \ No newline at end of file +} diff --git a/packages/server/src/enclave_server/blockchain/mod.rs b/packages/server/src/enclave_server/blockchain/mod.rs new file mode 100644 index 0000000..ac03dad --- /dev/null +++ b/packages/server/src/enclave_server/blockchain/mod.rs @@ -0,0 +1,3 @@ +pub mod listener; +pub mod relayer; +pub mod events; \ No newline at end of file diff --git a/packages/server/src/enclave_server/blockchain/relayer.rs b/packages/server/src/enclave_server/blockchain/relayer.rs new file mode 100644 index 0000000..9cb0af3 --- /dev/null +++ b/packages/server/src/enclave_server/blockchain/relayer.rs @@ -0,0 +1,147 @@ +use alloy::{ + network::{EthereumWallet, Ethereum}, + primitives::{address, Address, Bytes, U256}, + providers::fillers::{ + ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, WalletFiller + }, + providers::{Provider, ProviderBuilder, RootProvider, Identity}, + rpc::types::TransactionReceipt, + signers::local::PrivateKeySigner, + sol, + sol_types::SolCall, + transports::BoxTransport, +}; +use eyre::Result; +use log::info; +use std::sync::Arc; +use tokio::sync::Mutex; + +sol! { + #[derive(Debug)] + #[sol(rpc)] + contract CRISPVoting { + function requestE3( + uint256 startWindowStart, + uint256 duration, + bytes memory e3Params + ) public; + + function publishPublicKey(uint256 e3Id, bytes memory committeePublicKey) public; + + function castVote(uint256 e3Id, bytes memory vote) public; + + function submitCiphertext(uint256 e3Id, bytes memory ciphertextOutput) public; + + function submitPlaintext(uint256 e3Id, bytes memory plaintextOutput) public; + + function getPublicKey(uint256 e3Id) public view returns (bytes memory); + + function getCiphertextOutput(uint256 e3Id) public view returns (bytes memory); + + function getPlaintextOutput(uint256 e3Id) public view returns (bytes memory); + } +} + +type CRISPProvider = FillProvider< + JoinFill< + JoinFill, NonceFiller>, ChainIdFiller>, + WalletFiller, + >, + RootProvider, + BoxTransport, + Ethereum, +>; + +pub struct CRISPVotingContract { + provider: Arc, + contract_address: Address, + wallet: PrivateKeySigner, +} + +impl CRISPVotingContract { + pub async fn new(rpc_url: &str, contract_address: &str, private_key: &str) -> Result { + let signer: PrivateKeySigner = private_key.parse()?; + let wallet = EthereumWallet::from(signer.clone()); + let provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(wallet) + .on_builtin(rpc_url) + .await?; + + Ok(Self { + provider: Arc::new(provider), + contract_address: contract_address.parse()?, + wallet: signer, + }) + } + + pub async fn request_e3( + &self, + start_window_start: U256, + duration: U256, + e3_params: Bytes, + ) -> Result { + let contract = CRISPVoting::new(self.contract_address, &self.provider); + let builder = contract.requestE3(start_window_start, duration, e3_params); + let receipt = builder.send().await?.get_receipt().await?; + Ok(receipt) + } + + pub async fn publish_public_key( + &self, + e3_id: U256, + committee_public_key: Bytes, + ) -> Result { + let contract = CRISPVoting::new(self.contract_address, &self.provider); + let builder = contract.publishPublicKey(e3_id, committee_public_key); + let receipt = builder.send().await?.get_receipt().await?; + Ok(receipt) + } + + pub async fn cast_vote(&self, e3_id: U256, vote: Bytes) -> Result { + let contract = CRISPVoting::new(self.contract_address, &self.provider); + let builder = contract.castVote(e3_id, vote); + let receipt = builder.send().await?.get_receipt().await?; + Ok(receipt) + } + + pub async fn submit_ciphertext( + &self, + e3_id: U256, + ciphertext_output: Bytes, + ) -> Result { + let contract = CRISPVoting::new(self.contract_address, &self.provider); + let builder = contract.submitCiphertext(e3_id, ciphertext_output); + let receipt = builder.send().await?.get_receipt().await?; + Ok(receipt) + } + + pub async fn submit_plaintext( + &self, + e3_id: U256, + plaintext_output: Bytes, + ) -> Result { + let contract = CRISPVoting::new(self.contract_address, &self.provider); + let builder = contract.submitPlaintext(e3_id, plaintext_output); + let receipt = builder.send().await?.get_receipt().await?; + Ok(receipt) + } + + pub async fn get_public_key(&self, e3_id: U256) -> Result { + let contract = CRISPVoting::new(self.contract_address, &self.provider); + let public_key = contract.getPublicKey(e3_id).call().await?; + Ok(public_key._0) + } + + pub async fn get_ciphertext_output(&self, e3_id: U256) -> Result { + let contract = CRISPVoting::new(self.contract_address, &self.provider); + let ciphertext_output = contract.getCiphertextOutput(e3_id).call().await?; + Ok(ciphertext_output._0) + } + + pub async fn get_plaintext_output(&self, e3_id: U256) -> Result { + let contract = CRISPVoting::new(self.contract_address, &self.provider); + let plaintext_output = contract.getPlaintextOutput(e3_id).call().await?; + Ok(plaintext_output._0) + } +} diff --git a/packages/server/src/enclave_server/mod.rs b/packages/server/src/enclave_server/mod.rs index 598b47e..8edee33 100644 --- a/packages/server/src/enclave_server/mod.rs +++ b/packages/server/src/enclave_server/mod.rs @@ -1,6 +1,7 @@ mod database; mod models; mod routes; +pub mod blockchain; use actix_cors::Cors; use actix_web::{web, App, HttpResponse, HttpServer, Responder}; @@ -11,6 +12,7 @@ use sled::Db; use models::AppState; use database::GLOBAL_DB; +use blockchain::listener::start_listener; use env_logger::{Builder, Target}; use log::info; @@ -38,19 +40,25 @@ fn init_logger() { pub async fn start_server() -> Result<(), Box> { init_logger(); + tokio::spawn(async { + if let Err(e) = start_listener("0x5FbDB2315678afecb367f032d93F642f64180aa3").await { + eprintln!("Listener failed: {:?}", e); + } + }); + let _ = HttpServer::new(|| { let cors = Cors::default() - .allow_any_origin() // Allow all origins + .allow_any_origin() .allowed_methods(vec!["GET", "POST", "OPTIONS"]) - .allow_any_header() // Allow any custom headers + .allow_any_header() .supports_credentials() - .max_age(3600); // Cache preflight requests for an hour + .max_age(3600); App::new() .wrap(cors) .app_data(web::Data::new(AppState { db: GLOBAL_DB.clone(), })) - .configure(routes::setup_routes) // Modularized Actix routes + .configure(routes::setup_routes) }) .bind("0.0.0.0:4000")? .run().await; diff --git a/packages/server/src/enclave_server/routes/ciphernode.rs b/packages/server/src/enclave_server/routes/ciphernode.rs index 45c99dd..2642824 100644 --- a/packages/server/src/enclave_server/routes/ciphernode.rs +++ b/packages/server/src/enclave_server/routes/ciphernode.rs @@ -173,39 +173,31 @@ async fn get_pk_share_count( } // Get Round Eligibility -async fn get_round_eligibility( - data: web::Json, -) -> impl Responder { - let mut incoming = data.into_inner(); - info!("Request node eligibility for round {:?}", incoming.round_id); - - let (state_data, _) = get_state(incoming.round_id); - - for i in 1..state_data.ciphernodes.len() { - info!("checking ciphernode {:?}", i); - if state_data.ciphernodes[i].id == incoming.node_id { - incoming.is_eligible = true; - incoming.reason = "Previously Registered".to_string(); - } - } +async fn get_round_eligibility(data: web::Json) -> impl Responder { + let mut request = data.into_inner(); + info!("Checking node eligibility for round {}", request.round_id); - if state_data.ciphernode_total == state_data.ciphernode_count && incoming.reason != "Previously Registered" { - incoming.is_eligible = false; - incoming.reason = "Round Full".to_string(); - } + let (state_data, _) = get_state(request.round_id); + let timestamp = Utc::now().timestamp(); - if state_data.ciphernode_total > state_data.ciphernode_count && incoming.reason != "Previously Registered" { - incoming.is_eligible = true; - incoming.reason = "Open Node Spot".to_string(); + if timestamp >= (state_data.start_time + state_data.poll_length as i64) { + request.is_eligible = false; + request.reason = "Waiting For New Round".to_string(); + return HttpResponse::Ok().json(request); } - let timestamp = Utc::now().timestamp(); - if timestamp >= (state_data.start_time + state_data.poll_length as i64) { - incoming.is_eligible = false; - incoming.reason = "Waiting For New Round".to_string(); + if let Some(_) = state_data.ciphernodes.iter().find(|&node| node.id == request.node_id) { + request.is_eligible = true; + request.reason = "Previously Registered".to_string(); + } else if state_data.ciphernode_total == state_data.ciphernode_count { + request.is_eligible = false; + request.reason = "Round Full".to_string(); + } else if state_data.ciphernode_total > state_data.ciphernode_count { + request.is_eligible = true; + request.reason = "Open Node Spot".to_string(); } - HttpResponse::Ok().json(incoming) + HttpResponse::Ok().json(request) } // Get Node by Round diff --git a/packages/server/src/enclave_server/routes/voting.rs b/packages/server/src/enclave_server/routes/voting.rs index 9722382..f9b396c 100644 --- a/packages/server/src/enclave_server/routes/voting.rs +++ b/packages/server/src/enclave_server/routes/voting.rs @@ -26,46 +26,43 @@ pub fn setup_routes(config: &mut web::ServiceConfig) { async fn broadcast_enc_vote( data: web::Json, - state: web::Data, // Access shared state + state: web::Data, ) -> impl Responder { - let incoming = data.into_inner(); - let mut response_str = ""; - let mut converter = "".to_string(); - let (mut state_data, key) = get_state(incoming.round_id); - - for voted in &state_data.has_voted { - if *voted == incoming.postId { - response_str = "User Has Already Voted"; - break; - } - } - - if response_str == "" { - response_str = "Vote Successful"; - let sol_vote = Bytes::from(incoming.enc_vote_bytes); - let tx_hash = match call_contract(sol_vote, state_data.voting_address.clone()).await { - Ok(hash) => hash, - Err(e) => { - info!("Error while sending vote transaction: {:?}", e); - return HttpResponse::InternalServerError().body("Failed to broadcast vote"); - } - }; - converter = tx_hash.to_string(); - state_data.vote_count += 1; - state_data.has_voted.push(incoming.postId.clone()); - let state_str = serde_json::to_string(&state_data).unwrap(); - state.db.insert(key, state_str.into_bytes()).unwrap(); + let vote = data.into_inner(); + let (mut state_data, key) = get_state(vote.round_id); + + if state_data.has_voted.contains(&vote.postId) { + return HttpResponse::BadRequest().json(JsonResponseTxHash { + response: "User has already voted".to_string(), + tx_hash: "".to_string(), + }); } - let response = JsonResponseTxHash { - response: response_str.to_string(), - tx_hash: converter, + let sol_vote = Bytes::from(vote.enc_vote_bytes); + let tx_hash = match call_contract(sol_vote, state_data.voting_address.clone()).await { + Ok(hash) => hash.to_string(), + Err(e) => { + info!("Error while sending vote transaction: {:?}", e); + return HttpResponse::InternalServerError().body("Failed to broadcast vote"); + } }; - info!("Request for round {:?} send vote tx", incoming.round_id); - HttpResponse::Ok().json(response) + state_data.vote_count += 1; + state_data.has_voted.push(vote.postId); + + if let Err(e) = state.db.insert(key, serde_json::to_vec(&state_data).unwrap()) { + info!("Error updating state: {:?}", e); + return HttpResponse::InternalServerError().body("Failed to update state"); + } + + info!("Vote broadcast for round {}: tx_hash {}", vote.round_id, tx_hash); + HttpResponse::Ok().json(JsonResponseTxHash { + response: "Vote successful".to_string(), + tx_hash, + }) } + // Get Emojis by Round Handler async fn get_emojis_by_round( data: web::Json, From c4ef597734e47230ac3e2f3bc08ac567fc7b8576 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Fri, 13 Sep 2024 18:27:03 +0500 Subject: [PATCH 22/62] E3 Listener and Handler --- .gitignore | 5 +- .gitmodules | 9 + packages/compute_provider/core/src/lib.rs | 64 +-- packages/compute_provider/host/src/lib.rs | 24 +- .../methods/guest/src/main.rs | 6 +- packages/evm_base/contracts/CRISPBase.sol | 11 +- packages/risc0/Cargo.lock | 243 ++------- packages/risc0/Cargo.toml | 4 +- packages/risc0/apps/Cargo.toml | 7 +- packages/risc0/apps/src/lib.rs | 91 +--- packages/risc0/contracts/CRISPRisc0.sol | 3 +- packages/risc0/contracts/Lock.sol | 34 -- packages/risc0/ignition/modules/Lock.ts | 17 - packages/risc0/methods/build.rs | 3 +- packages/risc0/methods/guest/Cargo.lock | 465 ++---------------- .../risc0/methods/guest/src/bin/voting.rs | 20 +- packages/risc0/script/Deploy.s.sol | 3 +- packages/risc0/test/Lock.ts | 127 ----- packages/risc0/tests/CRISPRisc0.t.sol | 50 ++ packages/server/.cargo/config.toml | 6 +- packages/server/Cargo.lock | 148 +++++- packages/server/Cargo.toml | 5 +- packages/server/src/cli/voting.rs | 100 ++-- .../src/enclave_server/blockchain/events.rs | 68 ++- .../src/enclave_server/blockchain/handlers.rs | 148 ++++++ .../src/enclave_server/blockchain/listener.rs | 14 +- .../src/enclave_server/blockchain/mod.rs | 3 +- .../src/enclave_server/blockchain/relayer.rs | 120 ++--- .../server/src/enclave_server/database.rs | 20 +- packages/server/src/enclave_server/mod.rs | 1 + packages/server/src/enclave_server/models.rs | 32 ++ 31 files changed, 697 insertions(+), 1154 deletions(-) delete mode 100644 packages/risc0/contracts/Lock.sol delete mode 100644 packages/risc0/ignition/modules/Lock.ts delete mode 100644 packages/risc0/test/Lock.ts create mode 100644 packages/risc0/tests/CRISPRisc0.t.sol create mode 100644 packages/server/src/enclave_server/blockchain/handlers.rs diff --git a/.gitignore b/.gitignore index fd7cb9b..6caa729 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,7 @@ database *.log .pnp.* package-lock.json -.DS_Store \ No newline at end of file +.DS_Store + +packages/risc0/lib +packages/evm_base/lib \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index b30b90e..34e7a8a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,12 @@ [submodule "packages/evm_base/lib/forge-std"] path = packages/evm_base/lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "packages/risc0/lib/forge-std"] + path = lib/forge-std + url = https://github.com/foundry-rs/forge-std +[submodule "packages/risc0/lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts +[submodule "packages/risc0/lib/risc0-ethereum"] + path = lib/risc0-ethereum + url = https://github.com/risc0/risc0-ethereum diff --git a/packages/compute_provider/core/src/lib.rs b/packages/compute_provider/core/src/lib.rs index 01e07a7..9c74414 100644 --- a/packages/compute_provider/core/src/lib.rs +++ b/packages/compute_provider/core/src/lib.rs @@ -3,40 +3,36 @@ pub mod merkle_tree; use fhe::bfv::{BfvParameters, Ciphertext}; use fhe_traits::{Deserialize, DeserializeParametrized, Serialize}; use merkle_tree::MerkleTree; +use sha3::{Digest, Keccak256}; use std::sync::Arc; +pub type FHEProcessor = fn(&FHEInputs) -> Vec; + #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct ComputationResult { pub ciphertext: Vec, + pub params_hash: String, pub merkle_root: String, } #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] -pub struct CiphertextInputs { +pub struct FHEInputs { pub ciphertexts: Vec>, pub params: Vec, } #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] -pub struct ComputationInput { - pub ciphertexts: C, +pub struct ComputationInput { + pub fhe_inputs: FHEInputs, pub leaf_hashes: Vec, pub tree_depth: usize, pub zero_node: String, pub arity: usize, } -pub trait CiphertextProcessor { - fn process_ciphertexts(&self) -> Vec; - - fn get_ciphertexts(&self) -> &[Vec]; - - fn get_params(&self) -> &[u8]; -} - -impl ComputationInput { - pub fn process(&self) -> ComputationResult { - let processed_ciphertext = self.ciphertexts.process_ciphertexts(); +impl ComputationInput { + pub fn process(&self, fhe_processor: FHEProcessor) -> ComputationResult { + let processed_ciphertext = (fhe_processor)(&self.fhe_inputs); let merkle_root = MerkleTree { leaf_hashes: self.leaf_hashes.clone(), @@ -48,40 +44,30 @@ impl ComputationInput { .root() .unwrap(); + let params_hash = hex::encode( + Keccak256::new() + .chain_update(&self.fhe_inputs.params) + .finalize(), + ); + ComputationResult { ciphertext: processed_ciphertext, + params_hash, merkle_root, } } - - pub fn get_ciphertexts(&self) -> &[Vec] { - self.ciphertexts.get_ciphertexts() - } - - pub fn get_params(&self) -> &[u8] { - self.ciphertexts.get_params() - } } -impl CiphertextProcessor for CiphertextInputs { - /// Default implementation of the process_ciphertexts method - fn process_ciphertexts(&self) -> Vec { - let params = Arc::new(BfvParameters::try_deserialize(&self.params).unwrap()); - let mut sum = Ciphertext::zero(¶ms); - for ciphertext_bytes in &self.ciphertexts { - let ciphertext = Ciphertext::from_bytes(ciphertext_bytes, ¶ms).unwrap(); - sum += &ciphertext; - } +// Example Implementation of the CiphertextProcessor function +pub fn default_fhe_processor(fhe_inputs: &FHEInputs) -> Vec { + let params = Arc::new(BfvParameters::try_deserialize(&fhe_inputs.params).unwrap()); - sum.to_bytes() + let mut sum = Ciphertext::zero(¶ms); + for ciphertext_bytes in &fhe_inputs.ciphertexts { + let ciphertext = Ciphertext::from_bytes(ciphertext_bytes, ¶ms).unwrap(); + sum += &ciphertext; } - fn get_ciphertexts(&self) -> &[Vec] { - &self.ciphertexts - } - - fn get_params(&self) -> &[u8] { - &self.params - } + sum.to_bytes() } \ No newline at end of file diff --git a/packages/compute_provider/host/src/lib.rs b/packages/compute_provider/host/src/lib.rs index dc11873..0227010 100644 --- a/packages/compute_provider/host/src/lib.rs +++ b/packages/compute_provider/host/src/lib.rs @@ -1,5 +1,5 @@ use compute_provider_core::{ - merkle_tree::MerkleTree, CiphertextInputs, CiphertextProcessor, ComputationInput, + merkle_tree::MerkleTree, FHEInputs, ComputationInput, ComputationResult, }; use rayon::prelude::*; @@ -7,17 +7,17 @@ use risc0_ethereum_contracts::groth16; use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts, VerifierContext}; use std::sync::Arc; -pub struct ComputeProvider { - input: ComputationInput, +pub struct ComputeProvider { + input: ComputationInput, use_parallel: bool, batch_size: Option, } -impl ComputeProvider { - pub fn new(ciphertexts: C, use_parallel: bool, batch_size: Option) -> Self { +impl ComputeProvider { + pub fn new(fhe_inputs: FHEInputs, use_parallel: bool, batch_size: Option) -> Self { Self { input: ComputationInput { - ciphertexts, + fhe_inputs, leaf_hashes: Vec::new(), tree_depth: 10, zero_node: String::from("0"), @@ -46,7 +46,7 @@ impl ComputeProvider { self.input.zero_node.clone(), self.input.arity, ); - tree_handler.compute_leaf_hashes(&self.input.get_ciphertexts()); + tree_handler.compute_leaf_hashes(&self.input.fhe_inputs.ciphertexts); self.input.leaf_hashes = tree_handler.leaf_hashes.clone(); let env = ExecutorEnv::builder() @@ -74,8 +74,8 @@ impl ComputeProvider { let batch_size = self.batch_size.unwrap_or(1); let parallel_tree_depth = (batch_size as f64).log2().ceil() as usize; - let ciphertexts = Arc::new(self.input.get_ciphertexts()); - let params = Arc::new(self.input.get_params()); + let ciphertexts = Arc::new(self.input.fhe_inputs.ciphertexts.clone()); + let params = Arc::new(self.input.fhe_inputs.params.clone()); let chunks: Vec>> = ciphertexts .chunks(batch_size) @@ -89,7 +89,7 @@ impl ComputeProvider { tree_handler.compute_leaf_hashes(&chunk); let input = ComputationInput { - ciphertexts: CiphertextInputs { + fhe_inputs: FHEInputs { ciphertexts: chunk.clone(), params: params.to_vec(), // Params are shared across chunks }, @@ -122,7 +122,7 @@ impl ComputeProvider { // Combine the sorted results for final computation let final_depth = self.input.tree_depth - parallel_tree_depth; let mut final_input = ComputationInput { - ciphertexts: CiphertextInputs { + fhe_inputs: FHEInputs { ciphertexts: tally_results .iter() .map(|result| result.ciphertext.clone()) @@ -186,7 +186,7 @@ mod tests { let inputs = vec![1, 1, 0, 1]; let ciphertexts = encrypt_inputs(&inputs, &pk, ¶ms); - let ciphertext_inputs = CiphertextInputs { + let ciphertext_inputs = FHEInputs { ciphertexts: ciphertexts.iter().map(|c| c.to_bytes()).collect(), params: params.to_bytes(), }; diff --git a/packages/compute_provider/methods/guest/src/main.rs b/packages/compute_provider/methods/guest/src/main.rs index 8199645..2c32a90 100644 --- a/packages/compute_provider/methods/guest/src/main.rs +++ b/packages/compute_provider/methods/guest/src/main.rs @@ -1,11 +1,11 @@ use risc0_zkvm::guest::env; -use compute_provider_core::{ComputationInput, CiphertextInputs, ComputationResult}; +use compute_provider_core::{ComputationInput, ComputationResult, default_fhe_processor}; fn main() { - let input: ComputationInput = env::read(); + let input: ComputationInput = env::read(); - let result: ComputationResult = input.process(); + let result: ComputationResult = input.process(default_fhe_processor); env::commit(&result); } diff --git a/packages/evm_base/contracts/CRISPBase.sol b/packages/evm_base/contracts/CRISPBase.sol index 7a927ae..75b2797 100644 --- a/packages/evm_base/contracts/CRISPBase.sol +++ b/packages/evm_base/contracts/CRISPBase.sol @@ -19,17 +19,16 @@ abstract contract CRISPBase is IComputationModule { error E3AlreadyInitialized(); error E3DoesNotExist(); - error OnlyEnclave(); - - modifier onlyEnclave() { - require(msg.sender == address(enclave), OnlyEnclave()); - _; - } function initialize(IEnclave _enclave) public { enclave = _enclave; } + function getParamsHash(uint256 e3Id) public view returns (bytes32) { + require(params[e3Id].degree != 0, E3DoesNotExist()); + return keccak256(abi.encode(params[e3Id].degree, params[e3Id].plaintextModulus, params[e3Id].ciphertextModuli)); + } + function getParams(uint256 e3Id) public view returns (Params memory) { require(params[e3Id].degree != 0, E3DoesNotExist()); return params[e3Id]; diff --git a/packages/risc0/Cargo.lock b/packages/risc0/Cargo.lock index 18d295a..fa3ff87 100644 --- a/packages/risc0/Cargo.lock +++ b/packages/risc0/Cargo.lock @@ -269,6 +269,9 @@ dependencies = [ "compute-provider-host", "env_logger", "ethers", + "fhe", + "fhe-traits", + "fhe-util", "log", "methods", "risc0-ethereum-contracts", @@ -672,12 +675,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - [[package]] name = "block-buffer" version = "0.10.4" @@ -689,41 +686,18 @@ dependencies = [ [[package]] name = "bonsai-sdk" -version = "1.1.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68872e247f6fcf694ecbb884832a705cb2ae09f239cbbcc8bf71ed593d609a45" +checksum = "b1553c9f015eb3fc4ff1bf2e142fceeb2256768a3c4d94a9486784a6c656484d" dependencies = [ "duplicate", "maybe-async", "reqwest 0.12.4", + "risc0-groth16", "serde", "thiserror", ] -[[package]] -name = "borsh" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" -dependencies = [ - "borsh-derive", - "cfg_aliases", -] - -[[package]] -name = "borsh-derive" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" -dependencies = [ - "once_cell", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.66", - "syn_derive", -] - [[package]] name = "bs58" version = "0.5.1" @@ -851,12 +825,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - [[package]] name = "chrono" version = "0.4.38" @@ -1065,17 +1033,6 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" -[[package]] -name = "core-graphics-types" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "libc", -] - [[package]] name = "cpufeatures" version = "0.2.12" @@ -1787,7 +1744,7 @@ dependencies = [ "ndarray", "num-bigint", "num-traits", - "prost 0.12.6", + "prost", "prost-build", "rand", "rand_chacha", @@ -1810,7 +1767,7 @@ dependencies = [ "num-bigint", "num-bigint-dig", "num-traits", - "prost 0.12.6", + "prost", "prost-build", "rand", "rand_chacha", @@ -1872,33 +1829,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - [[package]] name = "form_urlencoded" version = "1.2.1" @@ -2594,29 +2524,6 @@ dependencies = [ "regex-automata 0.4.6", ] -[[package]] -name = "lazy-regex" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d8e41c97e6bc7ecb552016274b99fbb5d035e8de288c582d9b933af6677bfda" -dependencies = [ - "lazy-regex-proc_macros", - "once_cell", - "regex", -] - -[[package]] -name = "lazy-regex-proc_macros" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76e1d8b05d672c53cb9c7b920bbba8783845ae4f0b076e02a3db1d02c81b4163" -dependencies = [ - "proc-macro2", - "quote", - "regex", - "syn 2.0.66", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -2682,15 +2589,6 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - [[package]] name = "matchers" version = "0.1.0" @@ -2737,21 +2635,6 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" -[[package]] -name = "metal" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" -dependencies = [ - "bitflags 2.5.0", - "block", - "core-graphics-types", - "foreign-types", - "log", - "objc", - "paste", -] - [[package]] name = "methods" version = "0.1.0" @@ -2929,15 +2812,6 @@ dependencies = [ "syn 2.0.66", ] -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - [[package]] name = "object" version = "0.35.0" @@ -3340,17 +3214,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ "bytes", - "prost-derive 0.12.6", -] - -[[package]] -name = "prost" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2ecbe40f08db5c006b5764a2645f7f3f141ce756412ac9e1dd6087e6d32995" -dependencies = [ - "bytes", - "prost-derive 0.13.2", + "prost-derive", ] [[package]] @@ -3367,7 +3231,7 @@ dependencies = [ "once_cell", "petgraph", "prettyplease", - "prost 0.12.6", + "prost", "prost-types", "regex", "syn 2.0.66", @@ -3387,26 +3251,13 @@ dependencies = [ "syn 2.0.66", ] -[[package]] -name = "prost-derive" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" -dependencies = [ - "anyhow", - "itertools 0.12.1", - "proc-macro2", - "quote", - "syn 2.0.66", -] - [[package]] name = "prost-types" version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" dependencies = [ - "prost 0.12.6", + "prost", ] [[package]] @@ -3695,12 +3546,11 @@ dependencies = [ [[package]] name = "risc0-binfmt" -version = "1.1.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbac77ca59e4e1d765141d93ca72f13b632e374c69ae1b18a770b425aeecafce" +checksum = "4003dd96f2e323dfef431b21a2aaddee1c6791fc32dea8eb2bff1b438bf5caf6" dependencies = [ "anyhow", - "borsh", "elf", "risc0-zkp", "risc0-zkvm-platform", @@ -3710,15 +3560,14 @@ dependencies = [ [[package]] name = "risc0-build" -version = "1.1.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50757e90ff58c227a46774ecda4c927aefa3567f0851d341def452c098737b" +checksum = "452836a801f4c304c567f88855184f941d778d971cb94bee25b72d4255b56f09" dependencies = [ "anyhow", "cargo_metadata", "dirs", "docker-generate", - "hex", "risc0-binfmt", "risc0-zkp", "risc0-zkvm-platform", @@ -3740,14 +3589,13 @@ dependencies = [ [[package]] name = "risc0-circuit-recursion" -version = "1.1.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cabb4f5abdab268f8974d9e90ce09752be57f2cadc8ad6f7fae9a15a6ddc6773" +checksum = "e7c4154d2fbbde5af02a1c35c90340c2749044f5d5cd7834251b616ffa28d467" dependencies = [ "anyhow", "bytemuck", "hex", - "metal", "risc0-core", "risc0-zkp", "tracing", @@ -3755,12 +3603,11 @@ dependencies = [ [[package]] name = "risc0-circuit-rv32im" -version = "1.1.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8473dbe644e94a679d13b7e53a28c7c086b260a43d426259b5d74c862f2727de" +checksum = "ce836e7c553e63cbd807d15925ba5dd641a80cdee7d123619eaa60bb555ab014" dependencies = [ "anyhow", - "metal", "risc0-binfmt", "risc0-core", "risc0-zkp", @@ -3771,9 +3618,9 @@ dependencies = [ [[package]] name = "risc0-core" -version = "1.1.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de09abf5e0b102e69d96213e643fd7ae320ed0ca3fad3cd4eed8ce5fbab06bc" +checksum = "047cc26c68c092d664ded7488dcac0462d9e31190e1598a7820fe4246d313583" dependencies = [ "bytemuck", "rand_core", @@ -3792,9 +3639,9 @@ dependencies = [ [[package]] name = "risc0-groth16" -version = "1.1.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17e33bc37e7797d663b1e780f09cb295ffeb80712621bf3003ce79f56b66033d" +checksum = "b3309c7acaf46ed3d21df3841185afd8ea4aab9fb63dbd1974694dfdae276970" dependencies = [ "anyhow", "ark-bn254", @@ -3812,19 +3659,17 @@ dependencies = [ [[package]] name = "risc0-zkp" -version = "1.1.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83ae2c52905c83a62275ec75ddb60b8cdcf2388ae4add58a727f68822b4be93c" +checksum = "ae55272541351a2391e5051519b33bfdf41f5648216827cc2cb94a49b6937380" dependencies = [ "anyhow", "blake2", - "borsh", "bytemuck", "cfg-if", "digest 0.10.7", "hex", "hex-literal", - "metal", "paste", "rand_core", "risc0-core", @@ -3836,22 +3681,20 @@ dependencies = [ [[package]] name = "risc0-zkvm" -version = "1.1.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f99159c6f87ab49222d44a68b2de5bc3182b177d6307fb1eed6f1c43e5baa163" +checksum = "f234694d9dabc1172cf418b7a3ba65447caad15b994f450e9941d2a7cc89e045" dependencies = [ "anyhow", "bincode", "bonsai-sdk", - "borsh", "bytemuck", "bytes", + "cfg-if", "getrandom", "hex", - "lazy-regex", - "prost 0.13.2", + "prost", "risc0-binfmt", - "risc0-build", "risc0-circuit-recursion", "risc0-circuit-rv32im", "risc0-core", @@ -3862,21 +3705,19 @@ dependencies = [ "semver 1.0.23", "serde", "sha2", - "stability", "tempfile", "tracing", ] [[package]] name = "risc0-zkvm-platform" -version = "1.1.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12c53def950c8c8d25f9256af2a3e02a6284774c8ee31eed5d56c3533fbcec2e" +checksum = "16735dab52ae8bf0dc30df78fce901b674f469dfd7b5f5dfddd54caea22f14d5" dependencies = [ "bytemuck", "getrandom", "libm", - "stability", ] [[package]] @@ -4404,16 +4245,6 @@ dependencies = [ "der", ] -[[package]] -name = "stability" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" -dependencies = [ - "quote", - "syn 2.0.66", -] - [[package]] name = "static_assertions" version = "1.1.0" @@ -4533,18 +4364,6 @@ dependencies = [ "syn 2.0.66", ] -[[package]] -name = "syn_derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.66", -] - [[package]] name = "sync_wrapper" version = "0.1.2" diff --git a/packages/risc0/Cargo.toml b/packages/risc0/Cargo.toml index 4f3b15c..244ac95 100644 --- a/packages/risc0/Cargo.toml +++ b/packages/risc0/Cargo.toml @@ -23,10 +23,12 @@ risc0-ethereum-contracts = { git = "https://github.com/risc0/risc0-ethereum", ta risc0-zkvm = { version = "1.0", default-features = false } risc0-zkp = { version = "1.0", default-features = false } serde = { version = "1.0", features = ["derive", "std"] } +fhe = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } +fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } +fhe-util = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } compute-provider-host = { path = "../compute_provider/host" } compute-provider-core = { path = "../compute_provider/core" } - [profile.release] debug = 1 lto = true diff --git a/packages/risc0/apps/Cargo.toml b/packages/risc0/apps/Cargo.toml index 9a70327..10f0a3b 100644 --- a/packages/risc0/apps/Cargo.toml +++ b/packages/risc0/apps/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "apps" +name = "voting-risc0" version = { workspace = true } edition = { workspace = true } @@ -16,4 +16,7 @@ risc0-ethereum-contracts = { workspace = true } risc0-zkvm = { workspace = true, features = ["client"] } tokio = { version = "1.35", features = ["full"] } compute-provider-host = { workspace = true } -compute-provider-core = { workspace = true } \ No newline at end of file +compute-provider-core = { workspace = true } +fhe = { workspace = true } +fhe-traits = { workspace = true } +fhe-util = { workspace = true } \ No newline at end of file diff --git a/packages/risc0/apps/src/lib.rs b/packages/risc0/apps/src/lib.rs index cda2282..1c2abf4 100644 --- a/packages/risc0/apps/src/lib.rs +++ b/packages/risc0/apps/src/lib.rs @@ -1,94 +1,15 @@ // src/lib.rs - -use alloy_sol_types::{sol, SolInterface, SolValue}; -use alloy_primitives::U256; -use anyhow::{Context, Result}; -use ethers::prelude::*; +use anyhow::Result; use methods::VOTING_ELF; use compute_provider_host::ComputeProvider; -use compute_provider_core::CiphertextInputs; - -sol! { - interface ICRISPRisc0 { - function verify(uint256 e3Id, bytes memory data); - } -} - -pub struct TxSender { - chain_id: u64, - client: SignerMiddleware, Wallet>, - contract: Address, -} - -impl TxSender { - pub fn new(chain_id: u64, rpc_url: &str, private_key: &str, contract: &str) -> Result { - let provider = Provider::::try_from(rpc_url)?; - let wallet: LocalWallet = private_key.parse::()?.with_chain_id(chain_id); - let client = SignerMiddleware::new(provider.clone(), wallet.clone()); - let contract = contract.parse::
()?; - - Ok(TxSender { - chain_id, - client, - contract, - }) - } - - pub async fn send(&self, calldata: Vec) -> Result> { - let tx = TransactionRequest::new() - .chain_id(self.chain_id) - .to(self.contract) - .from(self.client.address()) - .data(calldata); - - log::info!("Transaction request: {:?}", &tx); +use compute_provider_core::{FHEInputs, ComputationResult}; - let tx = self.client.send_transaction(tx, None).await?.await?; - log::info!("Transaction receipt: {:?}", &tx); +pub fn run_compute(params: FHEInputs) -> Result<(ComputationResult, Vec)> { - Ok(tx) - } -} + let mut provider = ComputeProvider::new(params, true, None); -pub struct PublisherParams { - pub chain_id: u64, - pub eth_wallet_private_key: String, - pub rpc_url: String, - pub contract: String, - pub e3_id: U256, - pub ciphertexts: Vec>, - pub params: Vec, -} - -pub fn publish_proof(params: PublisherParams) -> Result<()> { - env_logger::init(); - - let tx_sender = TxSender::new( - params.chain_id, - ¶ms.rpc_url, - ¶ms.eth_wallet_private_key, - ¶ms.contract, - )?; - - let ciphertext_inputs = CiphertextInputs { - ciphertexts: params.ciphertexts, - params: params.params, - }; - - let mut provider = ComputeProvider::new(ciphertext_inputs, false, None); let (result, seal) = provider.start(VOTING_ELF); - // TODO: Fix this call, Encode Result and Seal into a single calldata - let calldata = ICRISPRisc0::ICRISPRisc0Calls::verify(ICRISPRisc0::verifyCall { - e3Id: params.e3_id, - data: seal.into(), - }) - .abi_encode(); - - let runtime = tokio::runtime::Runtime::new()?; - - runtime.block_on(tx_sender.send(calldata))?; - - Ok(()) -} + Ok((result, seal)) +} \ No newline at end of file diff --git a/packages/risc0/contracts/CRISPRisc0.sol b/packages/risc0/contracts/CRISPRisc0.sol index 21e7629..e5ae50d 100644 --- a/packages/risc0/contracts/CRISPRisc0.sol +++ b/packages/risc0/contracts/CRISPRisc0.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.26; -import {CRISPBase, IComputationModule, IInputValidator, IEnclave} from "evm_base/contracts/CRISPBase.sol"; +import {CRISPBase, IComputationModule, IInputValidator, IEnclave} from "evm_base/CRISPBase.sol"; import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; import {ImageID} from "./ImageID.sol"; @@ -55,7 +55,6 @@ contract CRISPRisc0 is CRISPBase { uint256 e3Id, bytes memory data ) external view override returns (bytes memory, bool) { - require(msg.sender == address(enclave), OnlyEnclave()); require(params[e3Id].degree != 0, E3DoesNotExist()); uint256 inputRoot = enclave.getInputRoot(e3Id); (bytes memory seal, bytes memory output) = abi.decode( diff --git a/packages/risc0/contracts/Lock.sol b/packages/risc0/contracts/Lock.sol deleted file mode 100644 index 1efbef3..0000000 --- a/packages/risc0/contracts/Lock.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.24; - -// Uncomment this line to use console.log -// import "hardhat/console.sol"; - -contract Lock { - uint public unlockTime; - address payable public owner; - - event Withdrawal(uint amount, uint when); - - constructor(uint _unlockTime) payable { - require( - block.timestamp < _unlockTime, - "Unlock time should be in the future" - ); - - unlockTime = _unlockTime; - owner = payable(msg.sender); - } - - function withdraw() public { - // Uncomment this line, and the import of "hardhat/console.sol", to print a log in your terminal - // console.log("Unlock time is %o and block timestamp is %o", unlockTime, block.timestamp); - - require(block.timestamp >= unlockTime, "You can't withdraw yet"); - require(msg.sender == owner, "You aren't the owner"); - - emit Withdrawal(address(this).balance, block.timestamp); - - owner.transfer(address(this).balance); - } -} diff --git a/packages/risc0/ignition/modules/Lock.ts b/packages/risc0/ignition/modules/Lock.ts deleted file mode 100644 index eda0eba..0000000 --- a/packages/risc0/ignition/modules/Lock.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; - -const JAN_1ST_2030 = 1893456000; -const ONE_GWEI: bigint = 1_000_000_000n; - -const LockModule = buildModule("LockModule", (m) => { - const unlockTime = m.getParameter("unlockTime", JAN_1ST_2030); - const lockedAmount = m.getParameter("lockedAmount", ONE_GWEI); - - const lock = m.contract("Lock", [unlockTime], { - value: lockedAmount, - }); - - return { lock }; -}); - -export default LockModule; diff --git a/packages/risc0/methods/build.rs b/packages/risc0/methods/build.rs index 5004eb7..6d2b67e 100644 --- a/packages/risc0/methods/build.rs +++ b/packages/risc0/methods/build.rs @@ -24,6 +24,7 @@ const SOLIDITY_ELF_PATH: &str = "../tests/Elf.sol"; fn main() { // Builds can be made deterministic, and thereby reproducible, by using Docker to build the // guest. Check the RISC0_USE_DOCKER variable and use Docker to build the guest if set. + println!("cargo:rerun-if-env-changed=RISC0_USE_DOCKER"); let use_docker = env::var("RISC0_USE_DOCKER").ok().map(|_| DockerOptions { root_dir: Some("../".into()), }); @@ -36,8 +37,6 @@ fn main() { use_docker, }, )])); - println!("Current working directory: {:?}", std::env::current_dir()); - // Generate Solidity source files for use with Forge. let solidity_opts = risc0_build_ethereum::Options::default() diff --git a/packages/risc0/methods/guest/Cargo.lock b/packages/risc0/methods/guest/Cargo.lock index 349e8a7..275fa81 100644 --- a/packages/risc0/methods/guest/Cargo.lock +++ b/packages/risc0/methods/guest/Cargo.lock @@ -375,7 +375,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -420,12 +420,6 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.5.0" @@ -453,12 +447,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - [[package]] name = "block-buffer" version = "0.10.4" @@ -470,41 +458,18 @@ dependencies = [ [[package]] name = "bonsai-sdk" -version = "1.1.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68872e247f6fcf694ecbb884832a705cb2ae09f239cbbcc8bf71ed593d609a45" +checksum = "b1553c9f015eb3fc4ff1bf2e142fceeb2256768a3c4d94a9486784a6c656484d" dependencies = [ "duplicate", "maybe-async", "reqwest", + "risc0-groth16", "serde", "thiserror", ] -[[package]] -name = "borsh" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" -dependencies = [ - "borsh-derive", - "cfg_aliases", -] - -[[package]] -name = "borsh-derive" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" -dependencies = [ - "once_cell", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.77", - "syn_derive", -] - [[package]] name = "bumpalo" version = "3.16.0" @@ -552,38 +517,6 @@ dependencies = [ "serde", ] -[[package]] -name = "camino" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo-platform" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" -dependencies = [ - "camino", - "cargo-platform", - "semver 1.0.23", - "serde", - "serde_json", - "thiserror", -] - [[package]] name = "cc" version = "1.0.98" @@ -596,12 +529,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - [[package]] name = "compute-provider-core" version = "0.1.0" @@ -646,33 +573,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "core-graphics-types" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "libc", -] - [[package]] name = "cpufeatures" version = "0.2.12" @@ -765,39 +665,12 @@ dependencies = [ "subtle", ] -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.48.0", -] - [[package]] name = "doc-comment" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" -[[package]] -name = "docker-generate" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf673e0848ef09fa4aeeba78e681cf651c0c7d35f76ee38cec8e55bc32fa111" - [[package]] name = "downcast-rs" version = "1.2.1" @@ -927,7 +800,7 @@ dependencies = [ "ndarray", "num-bigint", "num-traits", - "prost 0.12.6", + "prost", "prost-build", "rand", "rand_chacha", @@ -950,7 +823,7 @@ dependencies = [ "num-bigint", "num-bigint-dig", "num-traits", - "prost 0.12.6", + "prost", "prost-build", "rand", "rand_chacha", @@ -1002,33 +875,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.77", -] - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - [[package]] name = "form_urlencoded" version = "1.2.1" @@ -1415,29 +1261,6 @@ dependencies = [ "sha3-asm", ] -[[package]] -name = "lazy-regex" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d8e41c97e6bc7ecb552016274b99fbb5d035e8de288c582d9b933af6677bfda" -dependencies = [ - "lazy-regex-proc_macros", - "once_cell", - "regex", -] - -[[package]] -name = "lazy-regex-proc_macros" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76e1d8b05d672c53cb9c7b920bbba8783845ae4f0b076e02a3db1d02c81b4163" -dependencies = [ - "proc-macro2", - "quote", - "regex", - "syn 2.0.77", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -1459,16 +1282,6 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags 2.5.0", - "libc", -] - [[package]] name = "light-poseidon" version = "0.2.0" @@ -1493,15 +1306,6 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - [[package]] name = "matrixmultiply" version = "0.3.9" @@ -1529,21 +1333,6 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" -[[package]] -name = "metal" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" -dependencies = [ - "bitflags 2.5.0", - "block", - "core-graphics-types", - "foreign-types", - "log", - "objc", - "paste", -] - [[package]] name = "mime" version = "0.3.17" @@ -1656,15 +1445,6 @@ dependencies = [ "libm", ] -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - [[package]] name = "object" version = "0.36.4" @@ -1680,12 +1460,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - [[package]] name = "parity-scale-codec" version = "3.6.12" @@ -1864,7 +1638,7 @@ checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.5.0", + "bitflags", "lazy_static", "num-traits", "rand", @@ -1883,17 +1657,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ "bytes", - "prost-derive 0.12.6", -] - -[[package]] -name = "prost" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2ecbe40f08db5c006b5764a2645f7f3f141ce756412ac9e1dd6087e6d32995" -dependencies = [ - "bytes", - "prost-derive 0.13.2", + "prost-derive", ] [[package]] @@ -1910,7 +1674,7 @@ dependencies = [ "once_cell", "petgraph", "prettyplease", - "prost 0.12.6", + "prost", "prost-types", "regex", "syn 2.0.77", @@ -1930,26 +1694,13 @@ dependencies = [ "syn 2.0.77", ] -[[package]] -name = "prost-derive" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" -dependencies = [ - "anyhow", - "itertools 0.10.5", - "proc-macro2", - "quote", - "syn 2.0.77", -] - [[package]] name = "prost-types" version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" dependencies = [ - "prost 0.12.6", + "prost", ] [[package]] @@ -2066,17 +1817,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom", - "libredox", - "thiserror", -] - [[package]] name = "regex" version = "1.10.6" @@ -2178,12 +1918,11 @@ dependencies = [ [[package]] name = "risc0-binfmt" -version = "1.1.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbac77ca59e4e1d765141d93ca72f13b632e374c69ae1b18a770b425aeecafce" +checksum = "4003dd96f2e323dfef431b21a2aaddee1c6791fc32dea8eb2bff1b438bf5caf6" dependencies = [ "anyhow", - "borsh", "elf", "risc0-zkp", "risc0-zkvm-platform", @@ -2191,35 +1930,15 @@ dependencies = [ "tracing", ] -[[package]] -name = "risc0-build" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50757e90ff58c227a46774ecda4c927aefa3567f0851d341def452c098737b" -dependencies = [ - "anyhow", - "cargo_metadata", - "dirs", - "docker-generate", - "hex", - "risc0-binfmt", - "risc0-zkp", - "risc0-zkvm-platform", - "serde", - "serde_json", - "tempfile", -] - [[package]] name = "risc0-circuit-recursion" -version = "1.1.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cabb4f5abdab268f8974d9e90ce09752be57f2cadc8ad6f7fae9a15a6ddc6773" +checksum = "e7c4154d2fbbde5af02a1c35c90340c2749044f5d5cd7834251b616ffa28d467" dependencies = [ "anyhow", "bytemuck", "hex", - "metal", "risc0-core", "risc0-zkp", "tracing", @@ -2227,12 +1946,11 @@ dependencies = [ [[package]] name = "risc0-circuit-rv32im" -version = "1.1.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8473dbe644e94a679d13b7e53a28c7c086b260a43d426259b5d74c862f2727de" +checksum = "ce836e7c553e63cbd807d15925ba5dd641a80cdee7d123619eaa60bb555ab014" dependencies = [ "anyhow", - "metal", "risc0-binfmt", "risc0-core", "risc0-zkp", @@ -2243,9 +1961,9 @@ dependencies = [ [[package]] name = "risc0-core" -version = "1.1.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de09abf5e0b102e69d96213e643fd7ae320ed0ca3fad3cd4eed8ce5fbab06bc" +checksum = "047cc26c68c092d664ded7488dcac0462d9e31190e1598a7820fe4246d313583" dependencies = [ "bytemuck", "rand_core", @@ -2253,9 +1971,9 @@ dependencies = [ [[package]] name = "risc0-groth16" -version = "1.1.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17e33bc37e7797d663b1e780f09cb295ffeb80712621bf3003ce79f56b66033d" +checksum = "b3309c7acaf46ed3d21df3841185afd8ea4aab9fb63dbd1974694dfdae276970" dependencies = [ "anyhow", "ark-bn254", @@ -2273,19 +1991,17 @@ dependencies = [ [[package]] name = "risc0-zkp" -version = "1.1.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83ae2c52905c83a62275ec75ddb60b8cdcf2388ae4add58a727f68822b4be93c" +checksum = "ae55272541351a2391e5051519b33bfdf41f5648216827cc2cb94a49b6937380" dependencies = [ "anyhow", "blake2", - "borsh", "bytemuck", "cfg-if", "digest 0.10.7", "hex", "hex-literal", - "metal", "paste", "rand_core", "risc0-core", @@ -2297,22 +2013,20 @@ dependencies = [ [[package]] name = "risc0-zkvm" -version = "1.1.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f99159c6f87ab49222d44a68b2de5bc3182b177d6307fb1eed6f1c43e5baa163" +checksum = "f234694d9dabc1172cf418b7a3ba65447caad15b994f450e9941d2a7cc89e045" dependencies = [ "anyhow", "bincode", "bonsai-sdk", - "borsh", "bytemuck", "bytes", + "cfg-if", "getrandom", "hex", - "lazy-regex", - "prost 0.13.2", + "prost", "risc0-binfmt", - "risc0-build", "risc0-circuit-recursion", "risc0-circuit-rv32im", "risc0-core", @@ -2323,21 +2037,19 @@ dependencies = [ "semver 1.0.23", "serde", "sha2", - "stability", "tempfile", "tracing", ] [[package]] name = "risc0-zkvm-platform" -version = "1.1.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12c53def950c8c8d25f9256af2a3e02a6284774c8ee31eed5d56c3533fbcec2e" +checksum = "16735dab52ae8bf0dc30df78fce901b674f469dfd7b5f5dfddd54caea22f14d5" dependencies = [ "bytemuck", "getrandom", "libm", - "stability", ] [[package]] @@ -2432,7 +2144,7 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.5.0", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -2441,9 +2153,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.12" +version = "0.23.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" dependencies = [ "once_cell", "ring", @@ -2526,9 +2238,6 @@ name = "semver" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" -dependencies = [ - "serde", -] [[package]] name = "semver-parser" @@ -2671,16 +2380,6 @@ dependencies = [ "der", ] -[[package]] -name = "stability" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" -dependencies = [ - "quote", - "syn 2.0.77", -] - [[package]] name = "static_assertions" version = "1.1.0" @@ -2727,18 +2426,6 @@ dependencies = [ "syn 2.0.77", ] -[[package]] -name = "syn_derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.77", -] - [[package]] name = "sync_wrapper" version = "1.0.1" @@ -3152,7 +2839,7 @@ checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ "windows-result", "windows-strings", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -3161,7 +2848,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -3171,16 +2858,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ "windows-result", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", + "windows-targets", ] [[package]] @@ -3189,7 +2867,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -3198,22 +2876,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] @@ -3222,46 +2885,28 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3274,48 +2919,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/packages/risc0/methods/guest/src/bin/voting.rs b/packages/risc0/methods/guest/src/bin/voting.rs index 3e4e8da..2c32a90 100644 --- a/packages/risc0/methods/guest/src/bin/voting.rs +++ b/packages/risc0/methods/guest/src/bin/voting.rs @@ -1,25 +1,11 @@ -// Copyright 2023 RISC Zero, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - use risc0_zkvm::guest::env; -use compute_provider_core::{ComputationInput, CiphertextInputs, ComputationResult}; +use compute_provider_core::{ComputationInput, ComputationResult, default_fhe_processor}; fn main() { - let input: ComputationInput = env::read(); + let input: ComputationInput = env::read(); - let result: ComputationResult = input.process(); + let result: ComputationResult = input.process(default_fhe_processor); env::commit(&result); } diff --git a/packages/risc0/script/Deploy.s.sol b/packages/risc0/script/Deploy.s.sol index dc50bd5..44595e9 100644 --- a/packages/risc0/script/Deploy.s.sol +++ b/packages/risc0/script/Deploy.s.sol @@ -22,8 +22,9 @@ import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; import {RiscZeroGroth16Verifier} from "risc0/groth16/RiscZeroGroth16Verifier.sol"; import {ControlID} from "risc0/groth16/ControlID.sol"; + import {CRISPRisc0} from "../contracts/CRISPRisc0.sol"; -import {IEnclave} from "evm_base/contracts/CRISPBase.sol"; +import {IEnclave} from "evm_base/CRISPBase.sol"; /// @notice Deployment script for the RISC Zero starter project. /// @dev Use the following environment variable to control the deployment: diff --git a/packages/risc0/test/Lock.ts b/packages/risc0/test/Lock.ts deleted file mode 100644 index 160dbfa..0000000 --- a/packages/risc0/test/Lock.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { - time, - loadFixture, -} from "@nomicfoundation/hardhat-toolbox/network-helpers"; -import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs"; -import { expect } from "chai"; -import hre from "hardhat"; - -describe("Lock", function () { - // We define a fixture to reuse the same setup in every test. - // We use loadFixture to run this setup once, snapshot that state, - // and reset Hardhat Network to that snapshot in every test. - async function deployOneYearLockFixture() { - const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60; - const ONE_GWEI = 1_000_000_000; - - const lockedAmount = ONE_GWEI; - const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS; - - // Contracts are deployed using the first signer/account by default - const [owner, otherAccount] = await hre.ethers.getSigners(); - - const Lock = await hre.ethers.getContractFactory("Lock"); - const lock = await Lock.deploy(unlockTime, { value: lockedAmount }); - - return { lock, unlockTime, lockedAmount, owner, otherAccount }; - } - - describe("Deployment", function () { - it("Should set the right unlockTime", async function () { - const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture); - - expect(await lock.unlockTime()).to.equal(unlockTime); - }); - - it("Should set the right owner", async function () { - const { lock, owner } = await loadFixture(deployOneYearLockFixture); - - expect(await lock.owner()).to.equal(owner.address); - }); - - it("Should receive and store the funds to lock", async function () { - const { lock, lockedAmount } = await loadFixture( - deployOneYearLockFixture - ); - - expect(await hre.ethers.provider.getBalance(lock.target)).to.equal( - lockedAmount - ); - }); - - it("Should fail if the unlockTime is not in the future", async function () { - // We don't use the fixture here because we want a different deployment - const latestTime = await time.latest(); - const Lock = await hre.ethers.getContractFactory("Lock"); - await expect(Lock.deploy(latestTime, { value: 1 })).to.be.revertedWith( - "Unlock time should be in the future" - ); - }); - }); - - describe("Withdrawals", function () { - describe("Validations", function () { - it("Should revert with the right error if called too soon", async function () { - const { lock } = await loadFixture(deployOneYearLockFixture); - - await expect(lock.withdraw()).to.be.revertedWith( - "You can't withdraw yet" - ); - }); - - it("Should revert with the right error if called from another account", async function () { - const { lock, unlockTime, otherAccount } = await loadFixture( - deployOneYearLockFixture - ); - - // We can increase the time in Hardhat Network - await time.increaseTo(unlockTime); - - // We use lock.connect() to send a transaction from another account - await expect(lock.connect(otherAccount).withdraw()).to.be.revertedWith( - "You aren't the owner" - ); - }); - - it("Shouldn't fail if the unlockTime has arrived and the owner calls it", async function () { - const { lock, unlockTime } = await loadFixture( - deployOneYearLockFixture - ); - - // Transactions are sent using the first signer by default - await time.increaseTo(unlockTime); - - await expect(lock.withdraw()).not.to.be.reverted; - }); - }); - - describe("Events", function () { - it("Should emit an event on withdrawals", async function () { - const { lock, unlockTime, lockedAmount } = await loadFixture( - deployOneYearLockFixture - ); - - await time.increaseTo(unlockTime); - - await expect(lock.withdraw()) - .to.emit(lock, "Withdrawal") - .withArgs(lockedAmount, anyValue); // We accept any value as `when` arg - }); - }); - - describe("Transfers", function () { - it("Should transfer the funds to the owner", async function () { - const { lock, unlockTime, lockedAmount, owner } = await loadFixture( - deployOneYearLockFixture - ); - - await time.increaseTo(unlockTime); - - await expect(lock.withdraw()).to.changeEtherBalances( - [owner, lock], - [lockedAmount, -lockedAmount] - ); - }); - }); - }); -}); diff --git a/packages/risc0/tests/CRISPRisc0.t.sol b/packages/risc0/tests/CRISPRisc0.t.sol new file mode 100644 index 0000000..95cae10 --- /dev/null +++ b/packages/risc0/tests/CRISPRisc0.t.sol @@ -0,0 +1,50 @@ +// Copyright 2024 RISC Zero, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.20; + +import {RiscZeroCheats} from "risc0/test/RiscZeroCheats.sol"; +import {console2} from "forge-std/console2.sol"; +import {Test} from "forge-std/Test.sol"; +import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; +import {CRISPRisc0} from "../contracts/CRISPRisc0.sol"; +import {Elf} from "./Elf.sol"; // auto-generated contract after running `cargo build`. + +contract CRISPRisc0Test is RiscZeroCheats, Test { + CRISPRisc0 public crispRisc0; + + // function setUp() public { + // IRiscZeroVerifier verifier = deployRiscZeroVerifier(); + // crispRisc0 = new CRISPRisc0(verifier); + // assertEq(crispRisc0.owner(), address(this)); + // } + + // function test_SetEven() public { + // uint256 number = 12345678; + // (bytes memory journal, bytes memory seal) = prove(Elf.IS_EVEN_PATH, abi.encode(number)); + + // evenNumber.set(abi.decode(journal, (uint256)), seal); + // assertEq(evenNumber.get(), number); + // } + + // function test_SetZero() public { + // uint256 number = 0; + // (bytes memory journal, bytes memory seal) = prove(Elf.IS_EVEN_PATH, abi.encode(number)); + + // evenNumber.set(abi.decode(journal, (uint256)), seal); + // assertEq(evenNumber.get(), number); + // } +} diff --git a/packages/server/.cargo/config.toml b/packages/server/.cargo/config.toml index 3030934..d13436c 100644 --- a/packages/server/.cargo/config.toml +++ b/packages/server/.cargo/config.toml @@ -1,3 +1,5 @@ [env] -INFURAKEY = "default val" -PRIVATEKEY = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" \ No newline at end of file +INFURA_KEY = "INFURA_KEY" # Your Infura key +PRIVATE_KEY = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" # Anvil's private key +RPC_URL = "http://0.0.0.0:8545" # Local RPC +CONTRACT_ADDRESS = "0x5FbDB2315678afecb367f032d93F642f64180aa3" # Enclave Address \ No newline at end of file diff --git a/packages/server/Cargo.lock b/packages/server/Cargo.lock index 4f95d31..81aef1e 100644 --- a/packages/server/Cargo.lock +++ b/packages/server/Cargo.lock @@ -1642,9 +1642,9 @@ checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" [[package]] name = "bytemuck" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fd4c6dcc3b0aea2f5c0b4b82c2b15fe39ddbc76041a310848f4706edf76bb31" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" dependencies = [ "bytemuck_derive", ] @@ -1793,6 +1793,46 @@ dependencies = [ "inout", ] +[[package]] +name = "clap" +version = "4.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.61", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + [[package]] name = "cloudabi" version = "0.0.3" @@ -2340,6 +2380,19 @@ dependencies = [ "regex", ] +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log 0.4.22", + "regex", + "termcolor", +] + [[package]] name = "env_logger" version = "0.11.5" @@ -3260,6 +3313,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hex" version = "0.4.3" @@ -3447,9 +3506,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.2" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", @@ -3642,7 +3701,7 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", "windows-sys 0.48.0", ] @@ -3679,6 +3738,17 @@ dependencies = [ "log 0.4.22", ] +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi 0.4.0", + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -4034,6 +4104,16 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +[[package]] +name = "methods" +version = "0.1.0" +dependencies = [ + "hex", + "risc0-build", + "risc0-build-ethereum", + "risc0-zkp", +] + [[package]] name = "mime" version = "0.2.6" @@ -4087,7 +4167,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", "log 0.4.22", "wasi 0.11.0+wasi-snapshot-preview1", @@ -4231,7 +4311,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", ] @@ -4721,7 +4801,7 @@ checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi", + "hermit-abi 0.3.9", "pin-project-lite", "rustix 0.38.34", "tracing", @@ -4912,9 +4992,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quinn" -version = "0.11.3" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b22d8e7369034b9a7132bc2008cac12f2013c8132b45e0554e6e20e2617f2156" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" dependencies = [ "bytes", "pin-project-lite", @@ -4930,9 +5010,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba92fb39ec7ad06ca2582c0ca834dfeadcaf06ddfc8e635c80aa7e1c05315fdd" +checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" dependencies = [ "bytes", "rand 0.8.5", @@ -5294,7 +5374,7 @@ dependencies = [ "http-body 1.0.0", "http-body-util", "hyper 1.3.1", - "hyper-rustls 0.27.2", + "hyper-rustls 0.27.3", "hyper-tls", "hyper-util", "ipnet", @@ -5350,10 +5430,10 @@ dependencies = [ "bincode", "bytes", "chrono", - "compute-provider-host", + "compute-provider-core", "console", "dialoguer", - "env_logger", + "env_logger 0.11.5", "ethers", "eyre", "fhe", @@ -5381,6 +5461,7 @@ dependencies = [ "sha2", "sled", "tokio", + "voting-risc0", "walkdir", "wasm-bindgen", ] @@ -6257,6 +6338,12 @@ dependencies = [ "precomputed-hash", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" version = "0.26.2" @@ -6413,6 +6500,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.60" @@ -7045,6 +7141,28 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "voting-risc0" +version = "0.1.0" +dependencies = [ + "alloy-primitives 0.6.4", + "alloy-sol-types 0.6.4", + "anyhow", + "clap", + "compute-provider-core", + "compute-provider-host", + "env_logger 0.10.2", + "ethers", + "fhe", + "fhe-traits", + "fhe-util", + "log 0.4.22", + "methods", + "risc0-ethereum-contracts", + "risc0-zkvm", + "tokio", +] + [[package]] name = "wait-timeout" version = "0.2.0" diff --git a/packages/server/Cargo.toml b/packages/server/Cargo.toml index 94f2715..1f98ad5 100644 --- a/packages/server/Cargo.toml +++ b/packages/server/Cargo.toml @@ -11,10 +11,11 @@ console = "0.15.7" fhe = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } fhe-util = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } +compute-provider-core = { path = "../compute_provider/core" } +voting-risc0 = { path = "../risc0/apps" } rand_chacha = "0.3.1" rand = "0.8.5" ethers = "2.0" -compute-provider-host = { path = "../compute_provider/host" } getrandom = { version = "0.2.11", features = ["js"] } # Ethers' async features rely upon the Tokio async runtime. tokio = { version = "1.37.0", features = ["full"] } @@ -46,7 +47,7 @@ actix-web = "4.9.0" actix-cors = "0.7.0" alloy-primitives = { version = "0.6", default-features = false, features = ["rlp", "serde", "std"] } alloy-sol-types = { version = "0.6" } -alloy = { version = "0.2.1", features = ["full"] } +alloy = { version = "0.2.1", features = ["full", "rpc-types-eth"] } futures-util = "0.3" eyre = "0.6" hex = "0.4" \ No newline at end of file diff --git a/packages/server/src/cli/voting.rs b/packages/server/src/cli/voting.rs index de505ba..1d3cc30 100644 --- a/packages/server/src/cli/voting.rs +++ b/packages/server/src/cli/voting.rs @@ -10,7 +10,7 @@ use chrono::Utc; use alloy::primitives::{Bytes, U256}; -use crate::enclave_server::blockchain::relayer::CRISPVotingContract; +use crate::enclave_server::blockchain::relayer::EnclaveContract; use crate::cli::{AuthenticationResponse, HyperClientGet, HyperClientPost}; use crate::util::timeit::timeit; @@ -62,10 +62,10 @@ pub async fn initialize_crisp_round( info!("Starting new CRISP round!"); info!("Initializing Keyshare nodes..."); - // let private_key = env::var("PRIVATEKEY").expect("PRIVATEKEY must be set in the environment"); - // let rpc_url = "http://0.0.0.0:8545"; - // let contract = CRISPVotingContract::new(rpc_url, &config.voting_address, &private_key).await?; - // // Current time as start time + let private_key = env::var("PRIVATEKEY").expect("PRIVATEKEY must be set in the environment"); + let rpc_url = "http://0.0.0.0:8545"; + let contract = EnclaveContract::new(rpc_url, &config.voting_address, &private_key).await?; + // Current time as start time // let start_time = U256::from(Utc::now().timestamp()); // let e3_params = Bytes::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); // let duration = U256::from(config.poll_length); @@ -74,51 +74,51 @@ pub async fn initialize_crisp_round( // println!("E3 request sent. TxHash: {:?}", res.transaction_hash); - let url_id = format!("{}/get_rounds", config.enclave_address); - let req = Request::builder() - .method(Method::GET) - .uri(url_id) - .body(Empty::::new())?; - - let resp = client_get.request(req).await?; - info!("Response status: {}", resp.status()); - - let body_str = get_response_body(resp).await?; - let count: RoundCount = serde_json::from_str(&body_str)?; - info!("Server Round Count: {:?}", count.round_count); - - let round_id = count.round_count + 1; - let response = super::CrispConfig { - round_id, - poll_length: config.poll_length, - chain_id: config.chain_id, - voting_address: config.voting_address.clone(), - ciphernode_count: config.ciphernode_count, - enclave_address: config.enclave_address.clone(), - authentication_id: config.authentication_id.clone(), - }; - - let url = format!("{}/init_crisp_round", config.enclave_address); - let req = Request::builder() - .header("authorization", "Bearer fpKL54jvWmEGVoRdCNjG") - .header("Content-Type", "application/json") - .method(Method::POST) - .uri(url) - .body(serde_json::to_string(&response)?)?; - - let mut resp = client.request(req).await?; - info!("Response status: {}", resp.status()); - - while let Some(frame) = resp.frame().await { - if let Some(chunk) = frame?.data_ref() { - io::stdout().write_all(chunk).await?; - } - } - - info!("Round Initialized."); - info!("Gathering Keyshare nodes for execution environment..."); - thread::sleep(Duration::from_secs(1)); - info!("\nYou can now vote Encrypted with Round ID: {:?}", round_id); + // let url_id = format!("{}/get_rounds", config.enclave_address); + // let req = Request::builder() + // .method(Method::GET) + // .uri(url_id) + // .body(Empty::::new())?; + + // let resp = client_get.request(req).await?; + // info!("Response status: {}", resp.status()); + + // let body_str = get_response_body(resp).await?; + // let count: RoundCount = serde_json::from_str(&body_str)?; + // info!("Server Round Count: {:?}", count.round_count); + + // let round_id = count.round_count + 1; + // let response = super::CrispConfig { + // round_id, + // poll_length: config.poll_length, + // chain_id: config.chain_id, + // voting_address: config.voting_address.clone(), + // ciphernode_count: config.ciphernode_count, + // enclave_address: config.enclave_address.clone(), + // authentication_id: config.authentication_id.clone(), + // }; + + // let url = format!("{}/init_crisp_round", config.enclave_address); + // let req = Request::builder() + // .header("authorization", "Bearer fpKL54jvWmEGVoRdCNjG") + // .header("Content-Type", "application/json") + // .method(Method::POST) + // .uri(url) + // .body(serde_json::to_string(&response)?)?; + + // let mut resp = client.request(req).await?; + // info!("Response status: {}", resp.status()); + + // while let Some(frame) = resp.frame().await { + // if let Some(chunk) = frame?.data_ref() { + // io::stdout().write_all(chunk).await?; + // } + // } + + // info!("Round Initialized."); + // info!("Gathering Keyshare nodes for execution environment..."); + // thread::sleep(Duration::from_secs(1)); + // info!("\nYou can now vote Encrypted with Round ID: {:?}", round_id); Ok(()) } diff --git a/packages/server/src/enclave_server/blockchain/events.rs b/packages/server/src/enclave_server/blockchain/events.rs index 851c776..8ca583c 100644 --- a/packages/server/src/enclave_server/blockchain/events.rs +++ b/packages/server/src/enclave_server/blockchain/events.rs @@ -1,64 +1,62 @@ use alloy::{ - sol, - primitives::{Address, Bytes, U256}, - providers::Provider, - sol_types::{SolCall, SolEvent}, + primitives::{Address, Bytes, U256}, providers::Provider, sol, sol_types::{SolCall, SolEvent}, + rpc::types::Log }; + use eyre::Result; use super::listener::ContractEvent; +use super::handlers::{handle_e3, handle_input_published, handle_plaintext_output_published}; sol! { #[derive(Debug)] - event E3Requested(uint256 indexed e3Id, uint256 startTime, uint256 endTime, bytes e3Params); - - #[derive(Debug)] - event VoteCast(uint256 indexed e3Id, bytes vote); - - #[derive(Debug)] - event PublicKeyPublished(uint256 indexed e3Id, bytes committeePublicKey); + event E3Activated(uint256 e3Id, uint256 expiration, bytes committeePublicKey); #[derive(Debug)] - event CiphertextSubmitted(uint256 indexed e3Id, bytes ciphertextOutput); + event InputPublished(uint256 indexed e3Id, bytes data, uint256 inputHash, uint256 index); #[derive(Debug)] - event PlaintextSubmitted(uint256 indexed e3Id, bytes plaintextOutput); + event PlaintextOutputPublished(uint256 indexed e3Id, bytes plaintextOutput); } -impl ContractEvent for E3Requested { - fn process(&self) -> Result<()> { +impl ContractEvent for E3Activated { + fn process(&self, log: Log) -> Result<()> { println!("Processing E3 request: {:?}", self); + println!("Log: {:?}", log); + + let event_clone = self.clone(); + + tokio::spawn(async move { + if let Err(e) = handle_e3(event_clone, log).await { + eprintln!("Error handling E3 request: {:?}", e); + } + }); + + Ok(()) } } -impl ContractEvent for VoteCast { - fn process(&self) -> Result<()> { - println!("Processing vote cast: {:?}", self); +impl ContractEvent for InputPublished { + fn process(&self, log: Log) -> Result<()> { + println!("Processing input published: {:?}", self); + let event_clone = self.clone(); + if let Err(e) = handle_input_published(event_clone) { + eprintln!("Error handling input published: {:?}", e); + } Ok(()) } } -impl ContractEvent for PublicKeyPublished { - fn process(&self) -> Result<()> { +impl ContractEvent for PlaintextOutputPublished { + fn process(&self, log: Log) -> Result<()> { println!("Processing public key published: {:?}", self); - Ok(()) - } -} -impl ContractEvent for CiphertextSubmitted { - fn process(&self) -> Result<()> { - println!("Processing ciphertext submitted: {:?}", self); - Ok(()) - } -} + let event_clone = self.clone(); + if let Err(e) = handle_plaintext_output_published(event_clone) { + eprintln!("Error handling public key published: {:?}", e); + } -impl ContractEvent for PlaintextSubmitted { - fn process(&self) -> Result<()> { - println!("Processing plaintext submitted: {:?}", self); Ok(()) } } - - - diff --git a/packages/server/src/enclave_server/blockchain/handlers.rs b/packages/server/src/enclave_server/blockchain/handlers.rs new file mode 100644 index 0000000..1143548 --- /dev/null +++ b/packages/server/src/enclave_server/blockchain/handlers.rs @@ -0,0 +1,148 @@ +use super::{ + events::{E3Activated, InputPublished, PlaintextOutputPublished}, + relayer::EnclaveContract, +}; +use crate::enclave_server::database::{generate_emoji, get_e3, GLOBAL_DB}; +use crate::enclave_server::models::E3; +use alloy::{ + primitives::{Address, Bytes, U256, FixedBytes}, + providers::Provider, + rpc::types::Log, + sol, + sol_types::{SolCall, SolEvent}, +}; +use chrono::Utc; +use compute_provider_core::FHEInputs; +use std::env; +use std::error::Error; +use tokio::time::{sleep, Duration}; +use voting_risc0::run_compute; +use alloy_sol_types::SolValue; + +pub async fn handle_e3( + e3_activated: E3Activated, + log: Log, +) -> Result<(), Box> { + let e3_id = e3_activated.e3Id.to::(); + println!("Handling E3 request with id {}", e3_id); + + // Fetch E3 from the contract + let private_key = env::var("PRIVATEKEY").expect("PRIVATEKEY must be set in the environment"); + let rpc_url = env::var("RPC_URL").expect("RPC_URL must be set in the environment"); + let contract_address = + env::var("CONTRACT_ADDRESS").expect("CONTRACT_ADDRESS must be set in the environment"); + let contract = EnclaveContract::new(&rpc_url, &contract_address, &private_key).await?; + + let e3 = contract.get_e3(e3_activated.e3Id).await?; + let start_time = Utc::now().timestamp() as u64; + + let block_start = match log.block_number { + Some(bn) => bn, + None => contract.get_latest_block().await?, + }; + + let (emoji1, emoji2) = generate_emoji(); + + let e3_obj = E3 { + // Identifiers + id: e3_id, + + // Status-related + status: "Active".to_string(), + has_voted: vec!["".to_string()], + vote_count: 0, + + // Timing-related + start_time, + block_start, + duration: e3.duration.to::(), + expiration: e3.expiration.to::(), + + // Parameters + e3_params: e3.e3ProgramParams.to_vec(), + committee_public_key: e3.committeePublicKey.to_vec(), + + // Outputs + ciphertext_output: vec![], + plaintext_output: vec![], + + // Ciphertext Inputs + ciphertext_inputs: vec![], + + // Emojis + emojis: [emoji1, emoji2], + }; + + // Save E3 to the database + let key = format!("e3:{}", e3_id); + GLOBAL_DB + .insert(key, serde_json::to_vec(&e3_obj).unwrap()) + .unwrap(); + + // Sleep till the E3 expires + sleep(Duration::from_secs(e3.duration.to::())).await; + + // Get All Encrypted Votes + let (e3, _) = get_e3(e3_id).unwrap(); + + let fhe_inputs = FHEInputs { + params: e3.e3_params, + ciphertexts: e3.ciphertext_inputs, + }; + + // Call Compute Provider + let (compute_result, seal) = run_compute(fhe_inputs).unwrap(); + + let data = ( + compute_result.ciphertext, + compute_result.merkle_root, + seal, + ); + + let encoded_data = data.abi_encode(); + + // Params will be encoded on chain to create the journal + let tx = contract + .publish_ciphertext_output(e3_activated.e3Id, encoded_data.into()) + .await?; + + println!( + "CiphertextOutputPublished event published with tx: {:?}", + tx + ); + println!("E3 request handled successfully."); + Ok(()) +} + +pub fn handle_input_published(input: InputPublished) -> Result<(), Box> { + println!("Handling VoteCast event..."); + + let e3_id = input.e3Id.to::(); + let data = input.data.to_vec(); + let (mut e3, key) = get_e3(e3_id).unwrap(); + e3.ciphertext_inputs.push(data); + e3.vote_count += 1; + + GLOBAL_DB + .insert(key, serde_json::to_vec(&e3).unwrap()) + .unwrap(); + + println!("Saved Input with Hash: {:?}", input.inputHash); + Ok(()) +} + +pub fn handle_plaintext_output_published( + plaintext_output: PlaintextOutputPublished, +) -> Result<(), Box> { + println!("Handling PlaintextOutputPublished event..."); + + let e3_id = plaintext_output.e3Id.to::(); + let (mut e3, key) = get_e3(e3_id).unwrap(); + e3.plaintext_output = plaintext_output.plaintextOutput.to_vec(); + + GLOBAL_DB + .insert(key, serde_json::to_vec(&e3).unwrap()) + .unwrap(); + println!("PlaintextOutputPublished event handled."); + Ok(()) +} diff --git a/packages/server/src/enclave_server/blockchain/listener.rs b/packages/server/src/enclave_server/blockchain/listener.rs index 4d16a3b..2b90779 100644 --- a/packages/server/src/enclave_server/blockchain/listener.rs +++ b/packages/server/src/enclave_server/blockchain/listener.rs @@ -12,10 +12,10 @@ use std::collections::HashMap; use std::fmt::Debug; use std::sync::Arc; -use super::events::{E3Requested, CiphertextSubmitted, PlaintextSubmitted, PublicKeyPublished, VoteCast}; +use super::events::{E3Activated, InputPublished, PlaintextOutputPublished}; pub trait ContractEvent: Send + Sync + 'static { - fn process(&self) -> Result<()>; + fn process(&self, log: Log) -> Result<()>; } // impl ContractEvent for T @@ -66,7 +66,7 @@ impl EventListener { if let Some(topic0) = log.topic0() { if let Some(decoder) = self.handlers.get(topic0) { if let Ok(event) = decoder(log.clone()) { - event.process()?; + event.process(log)?; } } } @@ -105,11 +105,9 @@ pub async fn start_listener(contract_address: &str) -> Result<()> { let address: Address = contract_address.parse()?; let mut listener = manager.add_listener(address); - listener.add_event_handler::(); - listener.add_event_handler::(); - listener.add_event_handler::(); - listener.add_event_handler::(); - listener.add_event_handler::(); + listener.add_event_handler::(); + listener.add_event_handler::(); + listener.add_event_handler::(); // Start listening listener.listen().await?; diff --git a/packages/server/src/enclave_server/blockchain/mod.rs b/packages/server/src/enclave_server/blockchain/mod.rs index ac03dad..0f55184 100644 --- a/packages/server/src/enclave_server/blockchain/mod.rs +++ b/packages/server/src/enclave_server/blockchain/mod.rs @@ -1,3 +1,4 @@ pub mod listener; pub mod relayer; -pub mod events; \ No newline at end of file +pub mod events; +pub mod handlers; \ No newline at end of file diff --git a/packages/server/src/enclave_server/blockchain/relayer.rs b/packages/server/src/enclave_server/blockchain/relayer.rs index 9cb0af3..5edb134 100644 --- a/packages/server/src/enclave_server/blockchain/relayer.rs +++ b/packages/server/src/enclave_server/blockchain/relayer.rs @@ -1,10 +1,11 @@ use alloy::{ - network::{EthereumWallet, Ethereum}, + network::{Ethereum, EthereumWallet}, primitives::{address, Address, Bytes, U256}, providers::fillers::{ - ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, WalletFiller + ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, WalletFiller, }, - providers::{Provider, ProviderBuilder, RootProvider, Identity}, + providers::{Identity, Provider, ProviderBuilder, RootProvider}, + rpc::types::Filter, rpc::types::TransactionReceipt, signers::local::PrivateKeySigner, sol, @@ -18,27 +19,35 @@ use tokio::sync::Mutex; sol! { #[derive(Debug)] - #[sol(rpc)] - contract CRISPVoting { - function requestE3( - uint256 startWindowStart, - uint256 duration, - bytes memory e3Params - ) public; - - function publishPublicKey(uint256 e3Id, bytes memory committeePublicKey) public; + struct E3 { + uint256 seed; + uint32[2] threshold; + uint256[2] startWindow; + uint256 duration; + uint256 expiration; + address e3Program; + bytes e3ProgramParams; + address inputValidator; + address decryptionVerifier; + bytes committeePublicKey; + bytes ciphertextOutput; + bytes plaintextOutput; + } - function castVote(uint256 e3Id, bytes memory vote) public; + #[derive(Debug)] + #[sol(rpc)] + contract Enclave { + function request(address filter, uint32[2] calldata threshold, uint256[2] calldata startWindow, uint256 duration, address e3Program, bytes memory e3ProgramParams, bytes memory computeProviderParams) external payable returns (uint256 e3Id, E3 memory e3); - function submitCiphertext(uint256 e3Id, bytes memory ciphertextOutput) public; + function activate(uint256 e3Id) external returns (bool success); - function submitPlaintext(uint256 e3Id, bytes memory plaintextOutput) public; + function publishInput(uint256 e3Id, bytes memory data ) external returns (bool success); - function getPublicKey(uint256 e3Id) public view returns (bytes memory); + function publishCiphertextOutput(uint256 e3Id, bytes memory data ) external returns (bool success); - function getCiphertextOutput(uint256 e3Id) public view returns (bytes memory); + function publishPlaintextOutput(uint256 e3Id, bytes memory data) external returns (bool success); - function getPlaintextOutput(uint256 e3Id) public view returns (bytes memory); + function getE3(uint256 e3Id) external view returns (E3 memory e3); } } @@ -52,13 +61,13 @@ type CRISPProvider = FillProvider< Ethereum, >; -pub struct CRISPVotingContract { +pub struct EnclaveContract { provider: Arc, contract_address: Address, wallet: PrivateKeySigner, } -impl CRISPVotingContract { +impl EnclaveContract { pub async fn new(rpc_url: &str, contract_address: &str, private_key: &str) -> Result { let signer: PrivateKeySigner = private_key.parse()?; let wallet = EthereumWallet::from(signer.clone()); @@ -77,71 +86,72 @@ impl CRISPVotingContract { pub async fn request_e3( &self, - start_window_start: U256, + filter: Address, + threshold: [u32; 2], + start_window: [U256; 2], duration: U256, + e3_program: Address, e3_params: Bytes, + compute_provider_params: Bytes, ) -> Result { - let contract = CRISPVoting::new(self.contract_address, &self.provider); - let builder = contract.requestE3(start_window_start, duration, e3_params); + let contract = Enclave::new(self.contract_address, &self.provider); + let builder = contract.request( + filter, + threshold, + start_window, + duration, + e3_program, + e3_params, + compute_provider_params, + ); let receipt = builder.send().await?.get_receipt().await?; Ok(receipt) } - pub async fn publish_public_key( - &self, - e3_id: U256, - committee_public_key: Bytes, - ) -> Result { - let contract = CRISPVoting::new(self.contract_address, &self.provider); - let builder = contract.publishPublicKey(e3_id, committee_public_key); + pub async fn activate_e3(&self, e3_id: U256) -> Result { + let contract = Enclave::new(self.contract_address, &self.provider); + let builder = contract.activate(e3_id); let receipt = builder.send().await?.get_receipt().await?; Ok(receipt) } - pub async fn cast_vote(&self, e3_id: U256, vote: Bytes) -> Result { - let contract = CRISPVoting::new(self.contract_address, &self.provider); - let builder = contract.castVote(e3_id, vote); + pub async fn publish_input(&self, e3_id: U256, data: Bytes) -> Result { + let contract = Enclave::new(self.contract_address, &self.provider); + let builder = contract.publishInput(e3_id, data); let receipt = builder.send().await?.get_receipt().await?; Ok(receipt) } - pub async fn submit_ciphertext( + pub async fn publish_ciphertext_output( &self, e3_id: U256, - ciphertext_output: Bytes, + data: Bytes, ) -> Result { - let contract = CRISPVoting::new(self.contract_address, &self.provider); - let builder = contract.submitCiphertext(e3_id, ciphertext_output); + let contract = Enclave::new(self.contract_address, &self.provider); + let builder = contract.publishCiphertextOutput(e3_id, data); let receipt = builder.send().await?.get_receipt().await?; Ok(receipt) } - pub async fn submit_plaintext( + pub async fn publish_plaintext_output( &self, e3_id: U256, - plaintext_output: Bytes, + data: Bytes, ) -> Result { - let contract = CRISPVoting::new(self.contract_address, &self.provider); - let builder = contract.submitPlaintext(e3_id, plaintext_output); + let contract = Enclave::new(self.contract_address, &self.provider); + let builder = contract.publishPlaintextOutput(e3_id, data); let receipt = builder.send().await?.get_receipt().await?; Ok(receipt) } - pub async fn get_public_key(&self, e3_id: U256) -> Result { - let contract = CRISPVoting::new(self.contract_address, &self.provider); - let public_key = contract.getPublicKey(e3_id).call().await?; - Ok(public_key._0) - } - - pub async fn get_ciphertext_output(&self, e3_id: U256) -> Result { - let contract = CRISPVoting::new(self.contract_address, &self.provider); - let ciphertext_output = contract.getCiphertextOutput(e3_id).call().await?; - Ok(ciphertext_output._0) + pub async fn get_e3(&self, e3_id: U256) -> Result { + let contract = Enclave::new(self.contract_address, &self.provider); + let e3_return = contract.getE3(e3_id).call().await?; + Ok(e3_return.e3) } - pub async fn get_plaintext_output(&self, e3_id: U256) -> Result { - let contract = CRISPVoting::new(self.contract_address, &self.provider); - let plaintext_output = contract.getPlaintextOutput(e3_id).call().await?; - Ok(plaintext_output._0) + pub async fn get_latest_block(&self) -> Result { + let block = self.provider.get_block_number().await?; + Ok(block) } } diff --git a/packages/server/src/enclave_server/database.rs b/packages/server/src/enclave_server/database.rs index c491374..6677ec0 100644 --- a/packages/server/src/enclave_server/database.rs +++ b/packages/server/src/enclave_server/database.rs @@ -1,16 +1,31 @@ -use std::{env, str, sync::Arc}; +use std::{env, str, sync::Arc, error::Error}; use once_cell::sync::Lazy; use sled::Db; use rand::Rng; use log::info; -use super::models::Round; +use super::models::{CrispConfig, Round, E3}; pub static GLOBAL_DB: Lazy> = Lazy::new(|| { let pathdb = std::env::current_dir().unwrap().join("database/enclave_server"); Arc::new(sled::open(pathdb).unwrap()) }); +pub fn get_e3(e3_id: u64) -> Result<(E3, String), Box> { + let key = format!("e3:{}", e3_id); + + let value = match GLOBAL_DB.get(key.clone()) { + Ok(Some(v)) => v, + Ok(None) => return Err("E3 not found".into()), + Err(e) => return Err(format!("Database error: {}", e).into()), + }; + + let e3: E3 = serde_json::from_slice(&value) + .map_err(|e| format!("Failed to deserialize E3: {}", e))?; + + Ok((e3, key)) +} + pub fn get_state(round_id: u32) -> (Round, String) { let mut round_key = round_id.to_string(); round_key.push_str("-storage"); @@ -32,7 +47,6 @@ pub fn get_round_count() -> u32 { round_str.parse::().unwrap() } - pub fn generate_emoji() -> (String, String) { let emojis = [ "🍇","🍈","🍉","🍊","🍋","🍌","🍍","ðŸĨ­","🍎","🍏", diff --git a/packages/server/src/enclave_server/mod.rs b/packages/server/src/enclave_server/mod.rs index 8edee33..801297f 100644 --- a/packages/server/src/enclave_server/mod.rs +++ b/packages/server/src/enclave_server/mod.rs @@ -53,6 +53,7 @@ pub async fn start_server() -> Result<(), Box, pub has_voted: Vec, } +#[derive(Debug, Deserialize, Serialize)] +pub struct E3 { + // Identifiers + pub id: u64, + + // Status-related + pub status: String, + pub vote_count: u64, + pub has_voted: Vec, + + // Timing-related + pub start_time: u64, + pub block_start: u64, + pub duration: u64, + pub expiration: u64, + + // Parameters + pub e3_params: Vec, + pub committee_public_key: Vec, + + // Outputs + pub ciphertext_output: Vec, + pub plaintext_output: Vec, + + // Ciphertext Inputs + pub ciphertext_inputs: Vec>, + + // Emojis + pub emojis: [String; 2], +} #[derive(Debug, Deserialize, Serialize)] pub struct Ciphernode { From d5e278fc91b7bb311c8c9d70270f74f166f931c3 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Sat, 14 Sep 2024 01:07:57 +0500 Subject: [PATCH 23/62] Decoded Events Successfully --- packages/evm/contracts/CRISPVoting.sol | 201 +++++++++--------- packages/server/example_config.json | 2 +- packages/server/src/cli/voting.rs | 81 +++---- .../src/enclave_server/blockchain/events.rs | 18 +- .../src/enclave_server/blockchain/handlers.rs | 36 ++-- .../src/enclave_server/blockchain/listener.rs | 11 +- 6 files changed, 167 insertions(+), 182 deletions(-) diff --git a/packages/evm/contracts/CRISPVoting.sol b/packages/evm/contracts/CRISPVoting.sol index 66c19bc..a64aa39 100644 --- a/packages/evm/contracts/CRISPVoting.sol +++ b/packages/evm/contracts/CRISPVoting.sol @@ -2,135 +2,144 @@ pragma solidity ^0.8.20; contract CRISPVoting { - struct Poll { - uint256 e3Id; // Unique ID for each CRISP round (E3 computation) - uint256 startTime; // Start time of the poll - uint256 endTime; // End time of the poll - bytes e3Params; // Parameters for the E3 computation - bytes committeePublicKey; // Public key published by the committee - bytes ciphertextOutput; // Final ciphertext submitted by the relayer - bytes plaintextOutput; // Final plaintext result after decryption + struct E3 { + uint256 seed; + uint32[2] threshold; + uint256[2] startWindow; + uint256 duration; + uint256 expiration; + address e3Program; + bytes e3ProgramParams; + address inputValidator; + address decryptionVerifier; + bytes committeePublicKey; + bytes ciphertextOutput; + bytes plaintextOutput; } - uint256 public e3Counter = 0; // Counter for E3 IDs - mapping(uint256 => Poll) public polls; // Stores each poll by its e3Id + mapping(uint256 => E3) public e3Polls; // Stores each poll by its e3Id + mapping(uint256 e3Id => uint256 inputCount) public inputCounts; // Stores the number of inputs for each poll + + event E3Activated( + uint256 e3Id, + uint256 expiration, + bytes committeePublicKey + ); - event E3Requested( + event InputPublished( uint256 indexed e3Id, - uint256 startTime, - uint256 endTime, - bytes e3Params + bytes data, + uint256 inputHash, + uint256 index ); - event VoteCast(uint256 indexed e3Id, bytes vote); - event PublicKeyPublished(uint256 indexed e3Id, bytes committeePublicKey); - event CiphertextSubmitted(uint256 indexed e3Id, bytes ciphertextOutput); - event PlaintextSubmitted(uint256 indexed e3Id, bytes plaintextOutput); - - // Function to request a new poll (E3 computation) and start a round - function requestE3( - uint256 startWindowStart, + + event PlaintextOutputPublished(uint256 indexed e3Id, bytes plaintextOutput); + + uint256 public e3Counter = 0; // Counter for E3 IDs + + // function that emits bytes + function emitBytes() external { + emit E3Activated(1, 20, ""); + } + + // Request a new E3 computation + function request( + address filter, + uint32[2] calldata threshold, + uint256[2] calldata startWindow, uint256 duration, - bytes memory e3Params - ) public { + address e3Program, + bytes memory e3ProgramParams, + bytes memory computeProviderParams + ) external payable returns (uint256 e3Id, E3 memory e3) { e3Counter++; - uint256 startTime = block.timestamp > startWindowStart - ? block.timestamp - : startWindowStart; - uint256 endTime = startTime + duration; - - Poll memory newPoll = Poll({ - e3Id: e3Counter, - startTime: startTime, - endTime: endTime, - e3Params: e3Params, + + E3 memory newE3 = E3({ + seed: e3Counter, + threshold: threshold, + startWindow: startWindow, + duration: duration, + expiration: 0, + e3Program: e3Program, + e3ProgramParams: e3ProgramParams, + inputValidator: address(0), + decryptionVerifier: address(0), committeePublicKey: "", ciphertextOutput: "", plaintextOutput: "" }); - polls[e3Counter] = newPoll; + e3Polls[e3Counter] = newE3; - emit E3Requested(e3Counter, startTime, endTime, e3Params); + return (e3Counter, newE3); } - function publishPublicKey(uint256 e3Id, bytes memory committeePublicKey) - public - { - require(polls[e3Id].endTime > block.timestamp, "Poll has ended."); - require( - polls[e3Id].committeePublicKey.length == 0, - "Public key already published." - ); - - polls[e3Id].committeePublicKey = committeePublicKey; + // Activate the poll + function activate(uint256 e3Id) external returns (bool success) { + require(e3Polls[e3Id].seed > 0, "E3 ID does not exist."); + require(e3Polls[e3Id].expiration == 0, "Poll already activated."); - emit PublicKeyPublished(e3Id, committeePublicKey); - } - - function castVote(uint256 e3Id, bytes memory vote) public { - require(polls[e3Id].endTime > block.timestamp, "Poll has ended."); + e3Polls[e3Id].expiration = block.timestamp + e3Polls[e3Id].duration; + e3Polls[e3Id].committeePublicKey = bytes("hamza"); - emit VoteCast(e3Id, vote); + emit E3Activated(e3Id, e3Polls[e3Id].expiration, e3Polls[e3Id].committeePublicKey); + return true; } - // Function to submit the final ciphertext after voting has ended - function submitCiphertext( + // Publish input data to the poll + function publishInput( uint256 e3Id, - bytes memory ciphertextOutput - ) public { - require( - polls[e3Id].endTime <= block.timestamp, - "Poll is still ongoing." - ); + bytes memory data + ) external returns (bool success) { + require(e3Polls[e3Id].expiration > 0, "Poll not activated."); require( - polls[e3Id].ciphertextOutput.length == 0, - "Ciphertext already submitted." + e3Polls[e3Id].expiration > block.timestamp, + "Poll has expired." ); - polls[e3Id].ciphertextOutput = ciphertextOutput; - - emit CiphertextSubmitted(e3Id, ciphertextOutput); + inputCounts[e3Id]++; + uint256 inputHash = uint256(keccak256(data)); + emit InputPublished(e3Id, data, inputHash, inputCounts[e3Id] - 1); + return true; } - // Function to submit the final plaintext result after decryption - function submitPlaintext( + // Publish ciphertext output + function publishCiphertextOutput( uint256 e3Id, - bytes memory plaintextOutput - ) public { - require( - polls[e3Id].endTime <= block.timestamp, - "Poll is still ongoing." - ); + bytes memory data + ) external returns (bool success) { + E3 storage e3 = e3Polls[e3Id]; + require(e3.expiration > 0, "Poll not activated."); + require(e3.expiration > block.timestamp, "Poll has expired."); require( - polls[e3Id].ciphertextOutput.length > 0, - "Ciphertext must be submitted first." - ); - require( - polls[e3Id].plaintextOutput.length == 0, - "Plaintext already submitted." + e3.ciphertextOutput.length == 0, + "Ciphertext already published." ); - polls[e3Id].plaintextOutput = plaintextOutput; - - emit PlaintextSubmitted(e3Id, plaintextOutput); + e3.ciphertextOutput = data; + return true; } - // Function to retrieve the public key for voting based on e3Id - function getPublicKey(uint256 e3Id) public view returns (bytes memory) { - return polls[e3Id].committeePublicKey; - } + // Publish plaintext output + function publishPlaintextOutput( + uint256 e3Id, + bytes memory data + ) external returns (bool success) { + E3 storage e3 = e3Polls[e3Id]; + require(e3.expiration <= block.timestamp, "Poll is still ongoing."); + require( + e3.ciphertextOutput.length > 0, + "Ciphertext must be published first." + ); + require(e3.plaintextOutput.length == 0, "Plaintext already published."); - // Function to retrieve the ciphertext output for a given poll - function getCiphertextOutput( - uint256 e3Id - ) public view returns (bytes memory) { - return polls[e3Id].ciphertextOutput; + e3.plaintextOutput = data; + emit PlaintextOutputPublished(e3Id, data); + return true; } - // Function to retrieve the plaintext result for a given poll - function getPlaintextOutput( - uint256 e3Id - ) public view returns (bytes memory) { - return polls[e3Id].plaintextOutput; + // Retrieve the full E3 poll data by e3Id + function getE3(uint256 e3Id) external view returns (E3 memory e3) { + return e3Polls[e3Id]; } } diff --git a/packages/server/example_config.json b/packages/server/example_config.json index 0738748..2814151 100644 --- a/packages/server/example_config.json +++ b/packages/server/example_config.json @@ -1,6 +1,6 @@ { "round_id": 0, - "poll_length": 90, + "poll_length": 120, "chain_id": 31337, "voting_address": "0x5FbDB2315678afecb367f032d93F642f64180aa3", "ciphernode_count": 2, diff --git a/packages/server/src/cli/voting.rs b/packages/server/src/cli/voting.rs index 1d3cc30..1b08707 100644 --- a/packages/server/src/cli/voting.rs +++ b/packages/server/src/cli/voting.rs @@ -8,7 +8,7 @@ use log::{info, error}; use std::env; use chrono::Utc; -use alloy::primitives::{Bytes, U256}; +use alloy::primitives::{Address, Bytes, U256, U32}; use crate::enclave_server::blockchain::relayer::EnclaveContract; @@ -62,64 +62,31 @@ pub async fn initialize_crisp_round( info!("Starting new CRISP round!"); info!("Initializing Keyshare nodes..."); - let private_key = env::var("PRIVATEKEY").expect("PRIVATEKEY must be set in the environment"); + let private_key = env::var("PRIVATE_KEY").expect("PRIVATE_KEY must be set in the environment"); let rpc_url = "http://0.0.0.0:8545"; let contract = EnclaveContract::new(rpc_url, &config.voting_address, &private_key).await?; - // Current time as start time - // let start_time = U256::from(Utc::now().timestamp()); - // let e3_params = Bytes::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); - // let duration = U256::from(config.poll_length); - // let res = contract.request_e3(start_time,duration, e3_params).await?; - - // println!("E3 request sent. TxHash: {:?}", res.transaction_hash); - - - // let url_id = format!("{}/get_rounds", config.enclave_address); - // let req = Request::builder() - // .method(Method::GET) - // .uri(url_id) - // .body(Empty::::new())?; - - // let resp = client_get.request(req).await?; - // info!("Response status: {}", resp.status()); - - // let body_str = get_response_body(resp).await?; - // let count: RoundCount = serde_json::from_str(&body_str)?; - // info!("Server Round Count: {:?}", count.round_count); - - // let round_id = count.round_count + 1; - // let response = super::CrispConfig { - // round_id, - // poll_length: config.poll_length, - // chain_id: config.chain_id, - // voting_address: config.voting_address.clone(), - // ciphernode_count: config.ciphernode_count, - // enclave_address: config.enclave_address.clone(), - // authentication_id: config.authentication_id.clone(), - // }; - - // let url = format!("{}/init_crisp_round", config.enclave_address); - // let req = Request::builder() - // .header("authorization", "Bearer fpKL54jvWmEGVoRdCNjG") - // .header("Content-Type", "application/json") - // .method(Method::POST) - // .uri(url) - // .body(serde_json::to_string(&response)?)?; - - // let mut resp = client.request(req).await?; - // info!("Response status: {}", resp.status()); - - // while let Some(frame) = resp.frame().await { - // if let Some(chunk) = frame?.data_ref() { - // io::stdout().write_all(chunk).await?; - // } - // } - - // info!("Round Initialized."); - // info!("Gathering Keyshare nodes for execution environment..."); - // thread::sleep(Duration::from_secs(1)); - // info!("\nYou can now vote Encrypted with Round ID: {:?}", round_id); - + + let filter: Address = "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5".parse()?; + let threshold: [u32; 2] = [1, 2]; + let start_window: [U256; 2] = [U256::from(Utc::now().timestamp()), U256::from(Utc::now().timestamp() + 600)]; + let duration: U256 = U256::from(10); + let e3_program: Address = "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5".parse()?; + let e3_params = Bytes::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + let compute_provider_params = Bytes::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + let res = contract.request_e3(filter, threshold, start_window, duration, e3_program, e3_params, compute_provider_params).await?; + println!("E3 request sent. TxHash: {:?}", res.transaction_hash); + + + let e3_id = U256::from(3); + let res = contract.activate_e3(e3_id).await?; + println!("E3 activated. TxHash: {:?}", res.transaction_hash); + + let e3 = contract.get_e3(e3_id).await?; + println!("E3 data: {:?}", e3); + + let e3_data = Bytes::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + let res = contract.publish_input(e3_id, e3_data).await?; + println!("E3 data published. TxHash: {:?}", res.transaction_hash); Ok(()) } diff --git a/packages/server/src/enclave_server/blockchain/events.rs b/packages/server/src/enclave_server/blockchain/events.rs index 8ca583c..51ec55c 100644 --- a/packages/server/src/enclave_server/blockchain/events.rs +++ b/packages/server/src/enclave_server/blockchain/events.rs @@ -19,10 +19,10 @@ sol! { event PlaintextOutputPublished(uint256 indexed e3Id, bytes plaintextOutput); } + impl ContractEvent for E3Activated { fn process(&self, log: Log) -> Result<()> { println!("Processing E3 request: {:?}", self); - println!("Log: {:?}", log); let event_clone = self.clone(); @@ -40,10 +40,10 @@ impl ContractEvent for E3Activated { impl ContractEvent for InputPublished { fn process(&self, log: Log) -> Result<()> { println!("Processing input published: {:?}", self); - let event_clone = self.clone(); - if let Err(e) = handle_input_published(event_clone) { - eprintln!("Error handling input published: {:?}", e); - } + // let event_clone = self.clone(); + // if let Err(e) = handle_input_published(event_clone) { + // eprintln!("Error handling input published: {:?}", e); + // } Ok(()) } } @@ -52,10 +52,10 @@ impl ContractEvent for PlaintextOutputPublished { fn process(&self, log: Log) -> Result<()> { println!("Processing public key published: {:?}", self); - let event_clone = self.clone(); - if let Err(e) = handle_plaintext_output_published(event_clone) { - eprintln!("Error handling public key published: {:?}", e); - } + // let event_clone = self.clone(); + // if let Err(e) = handle_plaintext_output_published(event_clone) { + // eprintln!("Error handling public key published: {:?}", e); + // } Ok(()) } diff --git a/packages/server/src/enclave_server/blockchain/handlers.rs b/packages/server/src/enclave_server/blockchain/handlers.rs index 1143548..2ac3969 100644 --- a/packages/server/src/enclave_server/blockchain/handlers.rs +++ b/packages/server/src/enclave_server/blockchain/handlers.rs @@ -27,13 +27,16 @@ pub async fn handle_e3( println!("Handling E3 request with id {}", e3_id); // Fetch E3 from the contract - let private_key = env::var("PRIVATEKEY").expect("PRIVATEKEY must be set in the environment"); + let private_key = env::var("PRIVATE_KEY").expect("PRIVATEKEY must be set in the environment"); let rpc_url = env::var("RPC_URL").expect("RPC_URL must be set in the environment"); let contract_address = env::var("CONTRACT_ADDRESS").expect("CONTRACT_ADDRESS must be set in the environment"); let contract = EnclaveContract::new(&rpc_url, &contract_address, &private_key).await?; let e3 = contract.get_e3(e3_activated.e3Id).await?; + println!("Fetched E3 from the contract."); + println!("E3: {:?}", e3); + let start_time = Utc::now().timestamp() as u64; let block_start = match log.block_number { @@ -84,6 +87,7 @@ pub async fn handle_e3( // Get All Encrypted Votes let (e3, _) = get_e3(e3_id).unwrap(); + println!("E3 FROM DB: {:?}", e3); let fhe_inputs = FHEInputs { params: e3.e3_params, @@ -91,25 +95,25 @@ pub async fn handle_e3( }; // Call Compute Provider - let (compute_result, seal) = run_compute(fhe_inputs).unwrap(); + // let (compute_result, seal) = run_compute(fhe_inputs).unwrap(); - let data = ( - compute_result.ciphertext, - compute_result.merkle_root, - seal, - ); + // let data = ( + // compute_result.ciphertext, + // compute_result.merkle_root, + // seal, + // ); - let encoded_data = data.abi_encode(); + // let encoded_data = data.abi_encode(); - // Params will be encoded on chain to create the journal - let tx = contract - .publish_ciphertext_output(e3_activated.e3Id, encoded_data.into()) - .await?; + // // Params will be encoded on chain to create the journal + // let tx = contract + // .publish_ciphertext_output(e3_activated.e3Id, encoded_data.into()) + // .await?; - println!( - "CiphertextOutputPublished event published with tx: {:?}", - tx - ); + // println!( + // "CiphertextOutputPublished event published with tx: {:?}", + // tx + // ); println!("E3 request handled successfully."); Ok(()) } diff --git a/packages/server/src/enclave_server/blockchain/listener.rs b/packages/server/src/enclave_server/blockchain/listener.rs index 2b90779..8895ccb 100644 --- a/packages/server/src/enclave_server/blockchain/listener.rs +++ b/packages/server/src/enclave_server/blockchain/listener.rs @@ -65,8 +65,13 @@ impl EventListener { while let Some(log) = stream.next().await { if let Some(topic0) = log.topic0() { if let Some(decoder) = self.handlers.get(topic0) { - if let Ok(event) = decoder(log.clone()) { - event.process(log)?; + match decoder(log.clone()) { + Ok(event) => { + event.process(log)?; + } + Err(e) => { + println!("Error decoding event 0x{:x}: {:?}", topic0, e); + } } } } @@ -97,8 +102,8 @@ impl ContractManager { } } - pub async fn start_listener(contract_address: &str) -> Result<()> { + println!("Starting listener for contract: {}", contract_address); let rpc_url = "ws://127.0.0.1:8545"; let manager = ContractManager::new(rpc_url).await?; From cbf935cbc05f38f31a6274dd19cf317def189a8a Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Mon, 16 Sep 2024 19:20:36 +0500 Subject: [PATCH 24/62] feat: generic compute provider, updated the routes/client & models & removed ciphernode and its associated routes --- packages/ciphernode/Cargo.lock | 5227 ----------------- packages/ciphernode/Cargo.toml | 43 - .../ciphernode/example_ciphernode_config.json | 1 - .../ciphernode/src/bin/start_cipher_node.rs | 665 --- packages/ciphernode/src/bin/util.rs | 130 - packages/ciphernode/src/main.rs | 4 - .../voteManagement/VoteManagement.context.tsx | 6 +- packages/client/src/model/vote.model.ts | 17 +- .../client/src/pages/DailyPoll/DailyPoll.tsx | 4 +- .../pages/Landing/components/DailyPoll.tsx | 2 +- packages/client/src/utils/methods.ts | 2 +- packages/compute_provider/.gitignore | 4 - packages/compute_provider/Cargo.lock | 1258 ++++ packages/compute_provider/Cargo.toml | 34 +- packages/compute_provider/core/Cargo.toml | 19 - packages/compute_provider/host/Cargo.toml | 17 - packages/compute_provider/host/src/lib.rs | 244 - packages/compute_provider/methods/Cargo.toml | 11 - packages/compute_provider/methods/build.rs | 3 - .../compute_provider/methods/guest/Cargo.toml | 10 - .../methods/guest/src/main.rs | 11 - packages/compute_provider/methods/src/lib.rs | 1 - packages/compute_provider/rust-toolchain.toml | 4 - .../{core/src/lib.rs => src/compute_input.rs} | 38 +- .../compute_provider/src/compute_manager.rs | 118 + packages/compute_provider/src/lib.rs | 103 + .../{core => }/src/merkle_tree.rs | 0 packages/compute_provider/src/provider.rs | 38 + packages/evm/contracts/CRISPVoting.sol | 11 +- packages/risc0/Cargo.lock | 73 +- packages/risc0/Cargo.toml | 3 +- packages/risc0/apps/Cargo.toml | 3 +- packages/risc0/apps/src/lib.rs | 69 +- packages/risc0/methods/guest/Cargo.lock | 1021 +--- packages/risc0/methods/guest/Cargo.toml | 2 +- .../risc0/methods/guest/src/bin/voting.rs | 6 +- packages/server/Cargo.lock | 35 +- packages/server/Cargo.toml | 4 +- packages/server/src/cli/mod.rs | 10 +- packages/server/src/cli/voting.rs | 65 +- .../src/enclave_server/blockchain/events.rs | 27 +- .../src/enclave_server/blockchain/handlers.rs | 74 +- .../src/enclave_server/blockchain/listener.rs | 45 +- .../src/enclave_server/blockchain/relayer.rs | 14 +- .../server/src/enclave_server/database.rs | 59 +- packages/server/src/enclave_server/mod.rs | 7 +- packages/server/src/enclave_server/models.rs | 35 +- .../server/src/enclave_server/routes/auth.rs | 6 +- .../src/enclave_server/routes/ciphernode.rs | 282 - .../server/src/enclave_server/routes/index.rs | 8 - .../server/src/enclave_server/routes/mod.rs | 5 +- .../src/enclave_server/routes/rounds.rs | 167 +- .../server/src/enclave_server/routes/state.rs | 62 +- .../src/enclave_server/routes/voting.rs | 79 +- 54 files changed, 2127 insertions(+), 8059 deletions(-) delete mode 100644 packages/ciphernode/Cargo.lock delete mode 100644 packages/ciphernode/Cargo.toml delete mode 100644 packages/ciphernode/example_ciphernode_config.json delete mode 100644 packages/ciphernode/src/bin/start_cipher_node.rs delete mode 100644 packages/ciphernode/src/bin/util.rs delete mode 100644 packages/ciphernode/src/main.rs delete mode 100644 packages/compute_provider/.gitignore create mode 100644 packages/compute_provider/Cargo.lock delete mode 100644 packages/compute_provider/core/Cargo.toml delete mode 100644 packages/compute_provider/host/Cargo.toml delete mode 100644 packages/compute_provider/host/src/lib.rs delete mode 100644 packages/compute_provider/methods/Cargo.toml delete mode 100644 packages/compute_provider/methods/build.rs delete mode 100644 packages/compute_provider/methods/guest/Cargo.toml delete mode 100644 packages/compute_provider/methods/guest/src/main.rs delete mode 100644 packages/compute_provider/methods/src/lib.rs delete mode 100644 packages/compute_provider/rust-toolchain.toml rename packages/compute_provider/{core/src/lib.rs => src/compute_input.rs} (55%) create mode 100644 packages/compute_provider/src/compute_manager.rs create mode 100644 packages/compute_provider/src/lib.rs rename packages/compute_provider/{core => }/src/merkle_tree.rs (100%) create mode 100644 packages/compute_provider/src/provider.rs delete mode 100644 packages/server/src/enclave_server/routes/ciphernode.rs diff --git a/packages/ciphernode/Cargo.lock b/packages/ciphernode/Cargo.lock deleted file mode 100644 index 5e0af00..0000000 --- a/packages/ciphernode/Cargo.lock +++ /dev/null @@ -1,5227 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -dependencies = [ - "lazy_static", - "regex", -] - -[[package]] -name = "addr2line" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anstream" -version = "0.6.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" - -[[package]] -name = "anstyle-parse" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" -dependencies = [ - "anstyle", - "windows-sys 0.52.0", -] - -[[package]] -name = "anyhow" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" - -[[package]] -name = "arrayvec" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" - -[[package]] -name = "ascii-canvas" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" -dependencies = [ - "term", -] - -[[package]] -name = "async-attributes" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener 2.5.3", - "futures-core", -] - -[[package]] -name = "async-channel" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" -dependencies = [ - "concurrent-queue", - "event-listener-strategy 0.5.2", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b10202063978b3351199d68f8b22c4e47e4b1b822f8d43fd862d5ea8c006b29a" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand 2.1.0", - "futures-lite 2.3.0", - "slab", -] - -[[package]] -name = "async-global-executor" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" -dependencies = [ - "async-channel 2.3.1", - "async-executor", - "async-io 2.3.2", - "async-lock 3.3.0", - "blocking", - "futures-lite 2.3.0", - "once_cell", - "tokio", -] - -[[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock 2.8.0", - "autocfg 1.3.0", - "cfg-if", - "concurrent-queue", - "futures-lite 1.13.0", - "log 0.4.22", - "parking", - "polling 2.8.0", - "rustix 0.37.27", - "slab", - "socket2 0.4.10", - "waker-fn", -] - -[[package]] -name = "async-io" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" -dependencies = [ - "async-lock 3.3.0", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite 2.3.0", - "parking", - "polling 3.7.0", - "rustix 0.38.34", - "slab", - "tracing", - "windows-sys 0.52.0", -] - -[[package]] -name = "async-lock" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" -dependencies = [ - "event-listener 2.5.3", -] - -[[package]] -name = "async-lock" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" -dependencies = [ - "event-listener 4.0.3", - "event-listener-strategy 0.4.0", - "pin-project-lite", -] - -[[package]] -name = "async-std" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" -dependencies = [ - "async-attributes", - "async-channel 1.9.0", - "async-global-executor", - "async-io 1.13.0", - "async-lock 2.8.0", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite 1.13.0", - "gloo-timers", - "kv-log-macro", - "log 0.4.22", - "memchr", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - -[[package]] -name = "async-trait" -version = "0.1.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.65", -] - -[[package]] -name = "async_io_stream" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" -dependencies = [ - "futures", - "pharos", - "rustc_version", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "auto_impl" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.65", -] - -[[package]] -name = "autocfg" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" -dependencies = [ - "autocfg 1.3.0", -] - -[[package]] -name = "autocfg" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" - -[[package]] -name = "backtrace" -version = "0.3.71" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - -[[package]] -name = "base64" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" -dependencies = [ - "byteorder", - "safemem", -] - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - -[[package]] -name = "bech32" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "blocking" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "495f7104e962b7356f0aeb34247aca1fe7d2e783b346582db7f2904cb5717e88" -dependencies = [ - "async-channel 2.3.1", - "async-lock 3.3.0", - "async-task", - "futures-io", - "futures-lite 2.3.0", - "piper", -] - -[[package]] -name = "bs58" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" -dependencies = [ - "sha2", - "tinyvec", -] - -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - -[[package]] -name = "byte-slice-cast" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" -dependencies = [ - "serde", -] - -[[package]] -name = "bzip2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" -dependencies = [ - "bzip2-sys", - "libc", -] - -[[package]] -name = "bzip2-sys" -version = "0.1.11+1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - -[[package]] -name = "camino" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo-platform" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "cc" -version = "1.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" -dependencies = [ - "jobserver", - "libc", - "once_cell", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "wasm-bindgen", - "windows-targets 0.52.5", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "coins-bip32" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b6be4a5df2098cd811f3194f64ddb96c267606bffd9689ac7b0160097b01ad3" -dependencies = [ - "bs58", - "coins-core", - "digest", - "hmac", - "k256", - "serde", - "sha2", - "thiserror", -] - -[[package]] -name = "coins-bip39" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db8fba409ce3dc04f7d804074039eb68b960b0829161f8e06c95fea3f122528" -dependencies = [ - "bitvec", - "coins-bip32", - "hmac", - "once_cell", - "pbkdf2 0.12.2", - "rand 0.8.5", - "sha2", - "thiserror", -] - -[[package]] -name = "coins-core" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5286a0843c21f8367f7be734f89df9b822e0321d8bcce8d6e735aadff7d74979" -dependencies = [ - "base64 0.21.7", - "bech32", - "bs58", - "digest", - "generic-array", - "hex", - "ripemd", - "serde", - "serde_derive", - "sha2", - "sha3", - "thiserror", -] - -[[package]] -name = "colorchoice" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "console" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" -dependencies = [ - "encode_unicode", - "lazy_static", - "libc", - "unicode-width", - "windows-sys 0.52.0", -] - -[[package]] -name = "const-hex" -version = "1.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ff96486ccc291d36a958107caf2c0af8c78c0af7d31ae2f35ce055130de1a6" -dependencies = [ - "cfg-if", - "cpufeatures", - "hex", - "proptest", - "serde", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" - -[[package]] -name = "cpufeatures" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "crypto-bigint" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "subtle", - "zeroize", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher", -] - -[[package]] -name = "data-encoding" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" - -[[package]] -name = "der" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" -dependencies = [ - "const-oid", - "zeroize", -] - -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "derive_more" -version = "0.99.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "dialoguer" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" -dependencies = [ - "console", - "fuzzy-matcher", - "shell-words", - "tempfile", - "thiserror", - "zeroize", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", -] - -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.48.0", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "doc-comment" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" - -[[package]] -name = "dunce" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" - -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest", - "elliptic-curve", - "rfc6979", - "signature", - "spki", -] - -[[package]] -name = "either" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" - -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct", - "crypto-bigint", - "digest", - "ff", - "generic-array", - "group", - "pkcs8", - "rand_core 0.6.4", - "sec1", - "subtle", - "zeroize", -] - -[[package]] -name = "ena" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" -dependencies = [ - "log 0.4.22", -] - -[[package]] -name = "encode_unicode" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" - -[[package]] -name = "encoding_rs" -version = "0.8.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "enr" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a3d8dc56e02f954cac8eb489772c552c473346fc34f67412bb6244fd647f7e4" -dependencies = [ - "base64 0.21.7", - "bytes", - "hex", - "k256", - "log 0.4.22", - "rand 0.8.5", - "rlp", - "serde", - "sha3", - "zeroize", -] - -[[package]] -name = "env_filter" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" -dependencies = [ - "log 0.4.22", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "humantime", - "log 0.4.22", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "errno" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "eth-keystore" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" -dependencies = [ - "aes", - "ctr", - "digest", - "hex", - "hmac", - "pbkdf2 0.11.0", - "rand 0.8.5", - "scrypt", - "serde", - "serde_json", - "sha2", - "sha3", - "thiserror", - "uuid", -] - -[[package]] -name = "ethabi" -version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" -dependencies = [ - "ethereum-types", - "hex", - "once_cell", - "regex", - "serde", - "serde_json", - "sha3", - "thiserror", - "uint", -] - -[[package]] -name = "ethbloom" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" -dependencies = [ - "crunchy", - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "scale-info", - "tiny-keccak", -] - -[[package]] -name = "ethereum-types" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" -dependencies = [ - "ethbloom", - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "primitive-types", - "scale-info", - "uint", -] - -[[package]] -name = "ethers" -version = "2.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816841ea989f0c69e459af1cf23a6b0033b19a55424a1ea3a30099becdb8dec0" -dependencies = [ - "ethers-addressbook", - "ethers-contract", - "ethers-core", - "ethers-etherscan", - "ethers-middleware", - "ethers-providers", - "ethers-signers", - "ethers-solc", -] - -[[package]] -name = "ethers-addressbook" -version = "2.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5495afd16b4faa556c3bba1f21b98b4983e53c1755022377051a975c3b021759" -dependencies = [ - "ethers-core", - "once_cell", - "serde", - "serde_json", -] - -[[package]] -name = "ethers-contract" -version = "2.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fceafa3578c836eeb874af87abacfb041f92b4da0a78a5edd042564b8ecdaaa" -dependencies = [ - "const-hex", - "ethers-contract-abigen", - "ethers-contract-derive", - "ethers-core", - "ethers-providers", - "futures-util", - "once_cell", - "pin-project", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "ethers-contract-abigen" -version = "2.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04ba01fbc2331a38c429eb95d4a570166781f14290ef9fdb144278a90b5a739b" -dependencies = [ - "Inflector", - "const-hex", - "dunce", - "ethers-core", - "ethers-etherscan", - "eyre", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "reqwest", - "serde", - "serde_json", - "syn 2.0.65", - "toml", - "walkdir", -] - -[[package]] -name = "ethers-contract-derive" -version = "2.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87689dcabc0051cde10caaade298f9e9093d65f6125c14575db3fd8c669a168f" -dependencies = [ - "Inflector", - "const-hex", - "ethers-contract-abigen", - "ethers-core", - "proc-macro2", - "quote", - "serde_json", - "syn 2.0.65", -] - -[[package]] -name = "ethers-core" -version = "2.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d80cc6ad30b14a48ab786523af33b37f28a8623fc06afd55324816ef18fb1f" -dependencies = [ - "arrayvec", - "bytes", - "cargo_metadata", - "chrono", - "const-hex", - "elliptic-curve", - "ethabi", - "generic-array", - "k256", - "num_enum", - "once_cell", - "open-fastrlp", - "rand 0.8.5", - "rlp", - "serde", - "serde_json", - "strum", - "syn 2.0.65", - "tempfile", - "thiserror", - "tiny-keccak", - "unicode-xid", -] - -[[package]] -name = "ethers-etherscan" -version = "2.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79e5973c26d4baf0ce55520bd732314328cabe53193286671b47144145b9649" -dependencies = [ - "chrono", - "ethers-core", - "reqwest", - "semver", - "serde", - "serde_json", - "thiserror", - "tracing", -] - -[[package]] -name = "ethers-middleware" -version = "2.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f9fdf09aec667c099909d91908d5eaf9be1bd0e2500ba4172c1d28bfaa43de" -dependencies = [ - "async-trait", - "auto_impl", - "ethers-contract", - "ethers-core", - "ethers-etherscan", - "ethers-providers", - "ethers-signers", - "futures-channel", - "futures-locks", - "futures-util", - "instant", - "reqwest", - "serde", - "serde_json", - "thiserror", - "tokio", - "tracing", - "tracing-futures", - "url 2.5.0", -] - -[[package]] -name = "ethers-providers" -version = "2.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6434c9a33891f1effc9c75472e12666db2fa5a0fec4b29af6221680a6fe83ab2" -dependencies = [ - "async-trait", - "auto_impl", - "base64 0.21.7", - "bytes", - "const-hex", - "enr", - "ethers-core", - "futures-core", - "futures-timer", - "futures-util", - "hashers", - "http 0.2.12", - "instant", - "jsonwebtoken", - "once_cell", - "pin-project", - "reqwest", - "serde", - "serde_json", - "thiserror", - "tokio", - "tokio-tungstenite", - "tracing", - "tracing-futures", - "url 2.5.0", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "ws_stream_wasm", -] - -[[package]] -name = "ethers-signers" -version = "2.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "228875491c782ad851773b652dd8ecac62cda8571d3bc32a5853644dd26766c2" -dependencies = [ - "async-trait", - "coins-bip32", - "coins-bip39", - "const-hex", - "elliptic-curve", - "eth-keystore", - "ethers-core", - "rand 0.8.5", - "sha2", - "thiserror", - "tracing", -] - -[[package]] -name = "ethers-solc" -version = "2.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66244a771d9163282646dbeffe0e6eca4dda4146b6498644e678ac6089b11edd" -dependencies = [ - "cfg-if", - "const-hex", - "dirs", - "dunce", - "ethers-core", - "glob", - "home", - "md-5", - "num_cpus", - "once_cell", - "path-slash", - "rayon", - "regex", - "semver", - "serde", - "serde_json", - "solang-parser", - "svm-rs", - "thiserror", - "tiny-keccak", - "tokio", - "tracing", - "walkdir", - "yansi", -] - -[[package]] -name = "ethnum" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b90ca2580b73ab6a1f724b76ca11ab632df820fd6040c336200d2c1df7b3c82c" - -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "event-listener" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9944b8ca13534cdfb2800775f8dd4902ff3fc75a50101466decadfdf322a24" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" -dependencies = [ - "event-listener 4.0.3", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" -dependencies = [ - "event-listener 5.3.0", - "pin-project-lite", -] - -[[package]] -name = "eyre" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" -dependencies = [ - "indenter", - "once_cell", -] - -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - -[[package]] -name = "fastrand" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" - -[[package]] -name = "ff" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "fhe" -version = "0.1.0-beta.7" -source = "git+https://github.com/gnosisguild/fhe.rs#9624766dcfbb40ecfb01147f59c2f6292c447707" -dependencies = [ - "doc-comment", - "fhe-math", - "fhe-traits", - "fhe-util", - "itertools 0.12.1", - "ndarray", - "num-bigint", - "num-traits", - "prost", - "prost-build", - "rand 0.8.5", - "rand_chacha 0.3.1", - "serde", - "thiserror", - "zeroize", - "zeroize_derive", -] - -[[package]] -name = "fhe-math" -version = "0.1.0-beta.7" -source = "git+https://github.com/gnosisguild/fhe.rs#9624766dcfbb40ecfb01147f59c2f6292c447707" -dependencies = [ - "ethnum", - "fhe-traits", - "fhe-util", - "itertools 0.12.1", - "ndarray", - "num-bigint", - "num-bigint-dig", - "num-traits", - "prost", - "prost-build", - "rand 0.8.5", - "rand_chacha 0.3.1", - "sha2", - "thiserror", - "zeroize", -] - -[[package]] -name = "fhe-traits" -version = "0.1.0-beta.7" -source = "git+https://github.com/gnosisguild/fhe.rs#9624766dcfbb40ecfb01147f59c2f6292c447707" -dependencies = [ - "rand 0.8.5", -] - -[[package]] -name = "fhe-util" -version = "0.1.0-beta.7" -source = "git+https://github.com/gnosisguild/fhe.rs#9624766dcfbb40ecfb01147f59c2f6292c447707" -dependencies = [ - "itertools 0.12.1", - "num-bigint-dig", - "num-traits", - "rand 0.8.5", -] - -[[package]] -name = "fixed-hash" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" -dependencies = [ - "byteorder", - "rand 0.8.5", - "rustc-hex", - "static_assertions", -] - -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - -[[package]] -name = "flate2" -version = "1.0.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding 2.3.1", -] - -[[package]] -name = "fs2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "futures" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" - -[[package]] -name = "futures-executor" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" - -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - -[[package]] -name = "futures-lite" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" -dependencies = [ - "fastrand 2.1.0", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-locks" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45ec6fe3675af967e67c5536c0b9d44e34e6c52f86bedc4ea49c5317b8e94d06" -dependencies = [ - "futures-channel", - "futures-task", -] - -[[package]] -name = "futures-macro" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.65", -] - -[[package]] -name = "futures-sink" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" - -[[package]] -name = "futures-task" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" - -[[package]] -name = "futures-timer" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" -dependencies = [ - "gloo-timers", - "send_wrapper 0.4.0", -] - -[[package]] -name = "futures-util" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "fuzzy-matcher" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" -dependencies = [ - "thread_local", -] - -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check 0.9.4", - "zeroize", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "gimli" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" - -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] -name = "gloo-timers" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "h2" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "h2" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http 1.1.0", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "hashers" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2bca93b15ea5a746f220e56587f71e73c6165eab783df9e26590069953e3c30" -dependencies = [ - "fxhash", -] - -[[package]] -name = "headers" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" -dependencies = [ - "base64 0.21.7", - "bytes", - "headers-core", - "http 1.1.0", - "httpdate", - "mime 0.3.17", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" -dependencies = [ - "http 1.1.0", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "home" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - -[[package]] -name = "http-body" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" -dependencies = [ - "bytes", - "http 1.1.0", -] - -[[package]] -name = "http-body-util" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" -dependencies = [ - "bytes", - "futures-core", - "http 1.1.0", - "http-body 1.0.0", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] -name = "hyper" -version = "0.10.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" -dependencies = [ - "base64 0.9.3", - "httparse", - "language-tags", - "log 0.3.9", - "mime 0.2.6", - "num_cpus", - "time 0.1.45", - "traitobject", - "typeable", - "unicase", - "url 1.7.2", -] - -[[package]] -name = "hyper" -version = "0.14.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2 0.5.7", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "h2 0.4.5", - "http 1.1.0", - "http-body 1.0.0", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" -dependencies = [ - "futures-util", - "http 0.2.12", - "hyper 0.14.28", - "rustls", - "tokio", - "tokio-rustls", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper 1.3.1", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http 1.1.0", - "http-body 1.0.0", - "hyper 1.3.1", - "pin-project-lite", - "socket2 0.5.7", - "tokio", - "tower", - "tower-service", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "idna" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "impl-codec" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" -dependencies = [ - "parity-scale-codec", -] - -[[package]] -name = "impl-rlp" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" -dependencies = [ - "rlp", -] - -[[package]] -name = "impl-serde" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" -dependencies = [ - "serde", -] - -[[package]] -name = "impl-trait-for-tuples" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "indenter" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" - -[[package]] -name = "indexmap" -version = "2.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array", -] - -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "ipnet" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" - -[[package]] -name = "iron" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6d308ca2d884650a8bf9ed2ff4cb13fbb2207b71f64cda11dc9b892067295e8" -dependencies = [ - "hyper 0.10.16", - "log 0.3.9", - "mime_guess", - "modifier", - "num_cpus", - "plugin", - "typemap", - "url 1.7.2", -] - -[[package]] -name = "iron-cors" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b02b8856c7f14e443c483e802cf0ce693f3bec19f49d2c9a242b18f88c9b70" -dependencies = [ - "iron", - "log 0.4.22", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "jobserver" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" -dependencies = [ - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "jsonwebtoken" -version = "8.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" -dependencies = [ - "base64 0.21.7", - "pem", - "ring 0.16.20", - "serde", - "serde_json", - "simple_asn1", -] - -[[package]] -name = "jwt" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" -dependencies = [ - "base64 0.13.1", - "crypto-common", - "digest", - "hmac", - "serde", - "serde_json", - "sha2", -] - -[[package]] -name = "k256" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" -dependencies = [ - "cfg-if", - "ecdsa", - "elliptic-curve", - "once_cell", - "sha2", - "signature", -] - -[[package]] -name = "keccak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" -dependencies = [ - "cpufeatures", -] - -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log 0.4.22", -] - -[[package]] -name = "lalrpop" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" -dependencies = [ - "ascii-canvas", - "bit-set", - "ena", - "itertools 0.11.0", - "lalrpop-util", - "petgraph", - "regex", - "regex-syntax", - "string_cache", - "term", - "tiny-keccak", - "unicode-xid", - "walkdir", -] - -[[package]] -name = "lalrpop-util" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "language-tags" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -dependencies = [ - "spin 0.5.2", -] - -[[package]] -name = "libc" -version = "0.2.155" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" - -[[package]] -name = "libm" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags 2.5.0", - "libc", -] - -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - -[[package]] -name = "linux-raw-sys" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" - -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg 1.3.0", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" -dependencies = [ - "log 0.4.22", -] - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" -dependencies = [ - "value-bag", -] - -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - -[[package]] -name = "matrixmultiply" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2" -dependencies = [ - "autocfg 1.3.0", - "rawpointer", -] - -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest", -] - -[[package]] -name = "memchr" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" - -[[package]] -name = "mime" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" -dependencies = [ - "log 0.3.9", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mime_guess" -version = "1.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216929a5ee4dd316b1702eedf5e74548c123d370f47841ceaac38ca154690ca3" -dependencies = [ - "mime 0.2.6", - "phf 0.7.24", - "phf_codegen", - "unicase", -] - -[[package]] -name = "miniz_oxide" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" -dependencies = [ - "adler", -] - -[[package]] -name = "mio" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" -dependencies = [ - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", -] - -[[package]] -name = "modifier" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f5c9112cb662acd3b204077e0de5bc66305fa8df65c8019d5adb10e9ab6e58" - -[[package]] -name = "multimap" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" - -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log 0.4.22", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "ndarray" -version = "0.15.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" -dependencies = [ - "matrixmultiply", - "num-complex", - "num-integer", - "num-traits", - "rawpointer", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "num-bigint" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-bigint-dig" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" -dependencies = [ - "byteorder", - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.5", - "serde", - "smallvec", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg 1.3.0", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg 1.3.0", - "libm", -] - -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "num_enum" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.65", -] - -[[package]] -name = "object" -version = "0.32.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "open-fastrlp" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786393f80485445794f6043fd3138854dd109cc6c4bd1a6383db304c9ce9b9ce" -dependencies = [ - "arrayvec", - "auto_impl", - "bytes", - "ethereum-types", - "open-fastrlp-derive", -] - -[[package]] -name = "open-fastrlp-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "003b2be5c6c53c1cfeb0a238b8a1c3915cd410feb684457a36c10038f764bb1c" -dependencies = [ - "bytes", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "openssl" -version = "0.10.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" -dependencies = [ - "bitflags 2.5.0", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.65", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "parity-scale-codec" -version = "3.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" -dependencies = [ - "arrayvec", - "bitvec", - "byte-slice-cast", - "impl-trait-for-tuples", - "parity-scale-codec-derive", - "serde", -] - -[[package]] -name = "parity-scale-codec-derive" -version = "3.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "parking" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" - -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - -[[package]] -name = "parking_lot" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" -dependencies = [ - "lock_api", - "parking_lot_core 0.9.10", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall 0.2.16", - "smallvec", - "winapi", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.5.1", - "smallvec", - "windows-targets 0.52.5", -] - -[[package]] -name = "password-hash" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" -dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "path-slash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" - -[[package]] -name = "pbkdf2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" -dependencies = [ - "digest", - "hmac", - "password-hash", - "sha2", -] - -[[package]] -name = "pbkdf2" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" -dependencies = [ - "digest", - "hmac", -] - -[[package]] -name = "pem" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" -dependencies = [ - "base64 0.13.1", -] - -[[package]] -name = "percent-encoding" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "petgraph" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" -dependencies = [ - "fixedbitset", - "indexmap", -] - -[[package]] -name = "pharos" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" -dependencies = [ - "futures", - "rustc_version", -] - -[[package]] -name = "phf" -version = "0.7.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18" -dependencies = [ - "phf_shared 0.7.24", -] - -[[package]] -name = "phf" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" -dependencies = [ - "phf_macros", - "phf_shared 0.11.2", -] - -[[package]] -name = "phf_codegen" -version = "0.7.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e" -dependencies = [ - "phf_generator 0.7.24", - "phf_shared 0.7.24", -] - -[[package]] -name = "phf_generator" -version = "0.7.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" -dependencies = [ - "phf_shared 0.7.24", - "rand 0.6.5", -] - -[[package]] -name = "phf_generator" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" -dependencies = [ - "phf_shared 0.11.2", - "rand 0.8.5", -] - -[[package]] -name = "phf_macros" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" -dependencies = [ - "phf_generator 0.11.2", - "phf_shared 0.11.2", - "proc-macro2", - "quote", - "syn 2.0.65", -] - -[[package]] -name = "phf_shared" -version = "0.7.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" -dependencies = [ - "siphasher 0.2.3", - "unicase", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "pin-project" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.65", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "piper" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464db0c665917b13ebb5d453ccdec4add5658ee1adc7affc7677615356a8afaf" -dependencies = [ - "atomic-waker", - "fastrand 2.1.0", - "futures-io", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - -[[package]] -name = "pkg-config" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" - -[[package]] -name = "plugin" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a6a0dc3910bc8db877ffed8e457763b317cf880df4ae19109b9f77d277cf6e0" -dependencies = [ - "typemap", -] - -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg 1.3.0", - "bitflags 1.3.2", - "cfg-if", - "concurrent-queue", - "libc", - "log 0.4.22", - "pin-project-lite", - "windows-sys 0.48.0", -] - -[[package]] -name = "polling" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix 0.38.34", - "tracing", - "windows-sys 0.52.0", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "prettyplease" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" -dependencies = [ - "proc-macro2", - "syn 2.0.65", -] - -[[package]] -name = "primitive-types" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" -dependencies = [ - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "scale-info", - "uint", -] - -[[package]] -name = "proc-macro-crate" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" -dependencies = [ - "toml_edit 0.21.1", -] - -[[package]] -name = "proc-macro2" -version = "1.0.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proptest" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" -dependencies = [ - "bitflags 2.5.0", - "lazy_static", - "num-traits", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rand_xorshift 0.3.0", - "regex-syntax", - "unarray", -] - -[[package]] -name = "prost" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-build" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" -dependencies = [ - "bytes", - "heck 0.5.0", - "itertools 0.12.1", - "log 0.4.22", - "multimap", - "once_cell", - "petgraph", - "prettyplease", - "prost", - "prost-types", - "regex", - "syn 2.0.65", - "tempfile", -] - -[[package]] -name = "prost-derive" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" -dependencies = [ - "anyhow", - "itertools 0.12.1", - "proc-macro2", - "quote", - "syn 2.0.65", -] - -[[package]] -name = "prost-types" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" -dependencies = [ - "prost", -] - -[[package]] -name = "quote" -version = "1.0.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "rand" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -dependencies = [ - "autocfg 0.1.8", - "libc", - "rand_chacha 0.1.1", - "rand_core 0.4.2", - "rand_hc", - "rand_isaac", - "rand_jitter", - "rand_os", - "rand_pcg", - "rand_xorshift 0.1.1", - "winapi", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -dependencies = [ - "autocfg 0.1.8", - "rand_core 0.3.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_jitter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" -dependencies = [ - "libc", - "rand_core 0.4.2", - "winapi", -] - -[[package]] -name = "rand_os" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" -dependencies = [ - "cloudabi", - "fuchsia-cprng", - "libc", - "rand_core 0.4.2", - "rdrand", - "winapi", -] - -[[package]] -name = "rand_pcg" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" -dependencies = [ - "autocfg 0.1.8", - "rand_core 0.4.2", -] - -[[package]] -name = "rand_xorshift" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_xorshift" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" -dependencies = [ - "rand_core 0.6.4", -] - -[[package]] -name = "rawpointer" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" - -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" -dependencies = [ - "bitflags 2.5.0", -] - -[[package]] -name = "redox_users" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" -dependencies = [ - "getrandom", - "libredox", - "thiserror", -] - -[[package]] -name = "regex" -version = "1.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" - -[[package]] -name = "reqwest" -version = "0.11.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" -dependencies = [ - "base64 0.21.7", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.28", - "hyper-rustls", - "ipnet", - "js-sys", - "log 0.4.22", - "mime 0.3.17", - "once_cell", - "percent-encoding 2.3.1", - "pin-project-lite", - "rustls", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "system-configuration", - "tokio", - "tokio-rustls", - "tower-service", - "url 2.5.0", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots", - "winreg", -] - -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", -] - -[[package]] -name = "rfv" -version = "0.1.0" -dependencies = [ - "async-std", - "bincode", - "bytes", - "chrono", - "console", - "dialoguer", - "env_logger", - "ethers", - "fhe", - "fhe-traits", - "fhe-util", - "getrandom", - "headers", - "hmac", - "http-body-util", - "hyper 1.3.1", - "hyper-tls", - "hyper-util", - "iron", - "iron-cors", - "jwt", - "log 0.4.22", - "once_cell", - "rand 0.8.5", - "rand_chacha 0.3.1", - "router", - "serde", - "serde_json", - "sha2", - "sled", - "tokio", - "walkdir", - "wasm-bindgen", -] - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", -] - -[[package]] -name = "ring" -version = "0.17.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" -dependencies = [ - "cc", - "cfg-if", - "getrandom", - "libc", - "spin 0.9.8", - "untrusted 0.9.0", - "windows-sys 0.52.0", -] - -[[package]] -name = "ripemd" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" -dependencies = [ - "digest", -] - -[[package]] -name = "rlp" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" -dependencies = [ - "bytes", - "rlp-derive", - "rustc-hex", -] - -[[package]] -name = "rlp-derive" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "route-recognizer" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea509065eb0b3c446acdd0102f0d46567dc30902dc0be91d6552035d92b0f4f8" - -[[package]] -name = "router" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc63b6f3b8895b0d04e816b2b1aa58fdba2d5acca3cbb8f0ab8e017347d57397" -dependencies = [ - "iron", - "route-recognizer", - "url 1.7.2", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "rustc-hex" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "0.37.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - -[[package]] -name = "rustix" -version = "0.38.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" -dependencies = [ - "bitflags 2.5.0", - "errno", - "libc", - "linux-raw-sys 0.4.14", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log 0.4.22", - "ring 0.17.8", - "rustls-webpki", - "sct", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - -[[package]] -name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", -] - -[[package]] -name = "rustversion" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" - -[[package]] -name = "salsa20" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" -dependencies = [ - "cipher", -] - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "scale-info" -version = "2.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" -dependencies = [ - "cfg-if", - "derive_more", - "parity-scale-codec", - "scale-info-derive", -] - -[[package]] -name = "scale-info-derive" -version = "2.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "schannel" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "scrypt" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d" -dependencies = [ - "hmac", - "pbkdf2 0.11.0", - "salsa20", - "sha2", -] - -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", -] - -[[package]] -name = "sec1" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct", - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", -] - -[[package]] -name = "security-framework" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" -dependencies = [ - "bitflags 2.5.0", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "semver" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" -dependencies = [ - "serde", -] - -[[package]] -name = "send_wrapper" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" - -[[package]] -name = "send_wrapper" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" - -[[package]] -name = "serde" -version = "1.0.202" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.202" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.65", -] - -[[package]] -name = "serde_json" -version = "1.0.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_spanned" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha3" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" -dependencies = [ - "digest", - "keccak", -] - -[[package]] -name = "shell-words" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" - -[[package]] -name = "signal-hook-registry" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] - -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core 0.6.4", -] - -[[package]] -name = "simple_asn1" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" -dependencies = [ - "num-bigint", - "num-traits", - "thiserror", - "time 0.3.36", -] - -[[package]] -name = "siphasher" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" - -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg 1.3.0", -] - -[[package]] -name = "sled" -version = "0.34.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" -dependencies = [ - "crc32fast", - "crossbeam-epoch", - "crossbeam-utils", - "fs2", - "fxhash", - "libc", - "log 0.4.22", - "parking_lot 0.11.2", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "socket2" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "solang-parser" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c425ce1c59f4b154717592f0bdf4715c3a1d55058883622d3157e1f0908a5b26" -dependencies = [ - "itertools 0.11.0", - "lalrpop", - "lalrpop-util", - "phf 0.11.2", - "thiserror", - "unicode-xid", -] - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "string_cache" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" -dependencies = [ - "new_debug_unreachable", - "once_cell", - "parking_lot 0.12.2", - "phf_shared 0.10.0", - "precomputed-hash", -] - -[[package]] -name = "strum" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.65", -] - -[[package]] -name = "subtle" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" - -[[package]] -name = "svm-rs" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11297baafe5fa0c99d5722458eac6a5e25c01eb1b8e5cd137f54079093daa7a4" -dependencies = [ - "dirs", - "fs2", - "hex", - "once_cell", - "reqwest", - "semver", - "serde", - "serde_json", - "sha2", - "thiserror", - "url 2.5.0", - "zip", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "tempfile" -version = "3.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" -dependencies = [ - "cfg-if", - "fastrand 2.1.0", - "rustix 0.38.34", - "windows-sys 0.52.0", -] - -[[package]] -name = "term" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" -dependencies = [ - "dirs-next", - "rustversion", - "winapi", -] - -[[package]] -name = "thiserror" -version = "1.0.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.65", -] - -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - -[[package]] -name = "time" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "time" -version = "0.3.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "num_cpus", - "parking_lot 0.12.2", - "pin-project-lite", - "signal-hook-registry", - "socket2 0.5.7", - "tokio-macros", - "windows-sys 0.48.0", -] - -[[package]] -name = "tokio-macros" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.65", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" -dependencies = [ - "futures-util", - "log 0.4.22", - "rustls", - "tokio", - "tokio-rustls", - "tungstenite", - "webpki-roots", -] - -[[package]] -name = "tokio-util" -version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.8.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.22.13", -] - -[[package]] -name = "toml_datetime" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" -dependencies = [ - "indexmap", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.22.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.6.8", -] - -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" - -[[package]] -name = "tower-service" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" - -[[package]] -name = "tracing" -version = "0.1.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" -dependencies = [ - "log 0.4.22", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.65", -] - -[[package]] -name = "tracing-core" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" -dependencies = [ - "once_cell", -] - -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - -[[package]] -name = "traitobject" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "tungstenite" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http 0.2.12", - "httparse", - "log 0.4.22", - "rand 0.8.5", - "rustls", - "sha1", - "thiserror", - "url 2.5.0", - "utf-8", -] - -[[package]] -name = "typeable" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" - -[[package]] -name = "typemap" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "653be63c80a3296da5551e1bfd2cca35227e13cdd08c6668903ae2f4f77aa1f6" -dependencies = [ - "unsafe-any", -] - -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "uint" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - -[[package]] -name = "unarray" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" - -[[package]] -name = "unicase" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" -dependencies = [ - "version_check 0.1.5", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-normalization" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-width" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" - -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - -[[package]] -name = "unsafe-any" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30360d7979f5e9c6e6cea48af192ea8fab4afb3cf72597154b8f08935bc9c7f" -dependencies = [ - "traitobject", -] - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" -dependencies = [ - "idna 0.1.5", - "matches", - "percent-encoding 1.0.1", -] - -[[package]] -name = "url" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" -dependencies = [ - "form_urlencoded", - "idna 0.5.0", - "percent-encoding 2.3.1", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" -dependencies = [ - "getrandom", - "serde", -] - -[[package]] -name = "value-bag" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "waker-fn" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" -dependencies = [ - "bumpalo", - "log 0.4.22", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.65", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.65", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" - -[[package]] -name = "web-sys" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-roots" -version = "0.25.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.5", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.5", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" -dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" - -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" -dependencies = [ - "memchr", -] - -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - -[[package]] -name = "ws_stream_wasm" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5" -dependencies = [ - "async_io_stream", - "futures", - "js-sys", - "log 0.4.22", - "pharos", - "rustc_version", - "send_wrapper 0.6.0", - "thiserror", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - -[[package]] -name = "yansi" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" - -[[package]] -name = "zeroize" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" - -[[package]] -name = "zeroize_derive" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.65", -] - -[[package]] -name = "zip" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" -dependencies = [ - "aes", - "byteorder", - "bzip2", - "constant_time_eq", - "crc32fast", - "crossbeam-utils", - "flate2", - "hmac", - "pbkdf2 0.11.0", - "sha1", - "time 0.3.36", - "zstd", -] - -[[package]] -name = "zstd" -version = "0.11.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "5.0.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" -dependencies = [ - "libc", - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.10+zstd.1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/packages/ciphernode/Cargo.toml b/packages/ciphernode/Cargo.toml deleted file mode 100644 index eba06c3..0000000 --- a/packages/ciphernode/Cargo.toml +++ /dev/null @@ -1,43 +0,0 @@ -[package] -name = "rfv" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - - -[dependencies] -console = "0.15.7" -fhe = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } -fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } -fhe-util = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } -rand_chacha = "0.3.1" -rand = "0.8.5" -ethers = "2.0" -getrandom = { version = "0.2.11", features = ["js"] } -# Ethers' async features rely upon the Tokio async runtime. -tokio = { version = "1.37.0", features = ["full"] } -bincode = "1.0" -hyper = { version = "1", features = ["full"] } -http-body-util = "0.1" -hyper-util = { version = "0.1", features = ["full"] } -hyper-tls = "0.6.0" -iron = "0.6.0" -router = "0.6.0" -walkdir = "2.5.0" -dialoguer = { version = "0.11.0", features = ["fuzzy-select"] } -serde = { version = "1.0.197", features = ["derive"] } -serde_json = "1.0.114" -wasm-bindgen = "0.2" -chrono = "0.4.38" -sled = "0.34" -once_cell = "1.19.0" -async-std = { version = "1", features = ["attributes", "tokio1"] } -bytes = "1.6.0" -iron-cors = "0.8.0" -headers = "0.4.0" -jwt = "0.16.0" -hmac = "0.12.1" -sha2 = "0.10.8" -log = "0.4.22" -env_logger = "0.11.5" \ No newline at end of file diff --git a/packages/ciphernode/example_ciphernode_config.json b/packages/ciphernode/example_ciphernode_config.json deleted file mode 100644 index 9fe56ef..0000000 --- a/packages/ciphernode/example_ciphernode_config.json +++ /dev/null @@ -1 +0,0 @@ -{"ids":[22,15838,53153,90423],"enclave_address":"http://0.0.0.0:4000","enclave_port":80} \ No newline at end of file diff --git a/packages/ciphernode/src/bin/start_cipher_node.rs b/packages/ciphernode/src/bin/start_cipher_node.rs deleted file mode 100644 index e24268f..0000000 --- a/packages/ciphernode/src/bin/start_cipher_node.rs +++ /dev/null @@ -1,665 +0,0 @@ -use std::{env, sync::Arc, fs, str, thread, time}; -use std::fs::File; -use std::io::{Read, Write}; -use chrono::Utc; -use fhe::{ - bfv::{BfvParametersBuilder, Ciphertext, Encoding, Plaintext, SecretKey}, - mbfv::{AggregateIter, CommonRandomPoly, DecryptionShare, PublicKeyShare}, -}; -use fhe_traits::{FheDecoder, Serialize as FheSerialize, DeserializeParametrized}; -use rand::{Rng, rngs::OsRng, thread_rng}; -use serde::{Deserialize, Serialize}; -use http_body_util::{Empty, BodyExt}; -use hyper::{Request, Method, body::Bytes}; -use hyper_tls::HttpsConnector; -use hyper_util::{client::legacy::{Client as HyperClient, connect::HttpConnector}, rt::TokioExecutor}; -use tokio::io::{AsyncWriteExt, self}; -use ethers::{ - providers::{Http, Provider}, - types::{Address, U64}, - contract::abigen, -}; -use sled::Db; -use once_cell::sync::Lazy; -use hmac::{Hmac, Mac}; -use jwt::SignWithKey; -use sha2::Sha256; -use std::collections::BTreeMap; -use env_logger::{Builder, Target}; -use log::{LevelFilter, info}; - -// Constants -const DEGREE: usize = 4096; -const PLAINTEXT_MODULUS: u64 = 4096; -const MODULI: [u64; 3] = [0xffffee001, 0xffffc4001, 0x1ffffe0001]; -const POLLING_INTERVAL: u64 = 6000; - -type HyperClientGet = HyperClient, Empty>; -type HyperClientPost = HyperClient, String>; -// Structs -#[derive(Debug, Deserialize, Serialize)] -struct JsonRequest { - response: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct GetRoundRequest { - round_id: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct PKShareRequest { - response: String, - pk_share: Vec, - id: u32, - round_id: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct SKSShareRequest { - response: String, - sks_share: Vec, - index: u32, - round_id: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct CrispConfig { - round_id: u32, - poll_length: u32, - chain_id: u32, - voting_address: String, - ciphernode_count: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct RoundCount { - round_count: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct PKShareCount { - round_id: u32, - share_id: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct CRPRequest { - round_id: u32, - crp_bytes: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -struct SKSShareResponse { - response: String, - round_id: u32, - sks_shares: Vec>, -} - -#[derive(Debug, Deserialize, Serialize)] -struct SKSSharePoll { - response: String, - round_id: u32, - ciphernode_count: u32 -} - -#[derive(Debug, Deserialize, Serialize)] -struct VoteCountRequest { - round_id: u32, - vote_count: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct ReportTallyRequest { - round_id: u32, - option_1: u32, - option_2: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct StateLite { - id: u32, - status: String, - poll_length: u32, - voting_address: String, - chain_id: u32, - ciphernode_count: u32, - pk_share_count: u32, - sks_share_count: u32, - vote_count: u32, - crp: Vec, - pk: Vec, - start_time: i64, - block_start: U64, - ciphernode_total: u32, - emojis: [String; 2], -} - -#[derive(Debug, Deserialize, Serialize)] -struct CiphernodeConfig { - ids: Vec, - enclave_address: String, - enclave_port: u16, -} - -#[derive(Debug, Deserialize, Serialize)] -struct GetCiphernode { - round_id: u32, - ciphernode_id: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct GetEligibilityRequest { - round_id: u32, - node_id: u32, - is_eligible: bool, - reason: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct Ciphernode { - id: u32, - index: Vec, - pk_shares: Vec>, - sk_shares: Vec>, -} - -#[derive(Debug, Deserialize, Serialize)] -struct RegisterNodeResponse { - response: String, - node_index: u32, -} - -// Global database -static GLOBAL_DB: Lazy = Lazy::new(|| { - let path = env::current_dir().unwrap(); - let config_path = format!("{}/example_ciphernode_config.json", path.display()); - let mut file = File::open(&config_path).unwrap(); - let mut data = String::new(); - file.read_to_string(&mut data).unwrap(); - - let args: Vec = env::args().collect(); - let cnode_selector = args.get(1).map(|arg| arg.parse::().unwrap()).unwrap_or(0); - - let mut config: CiphernodeConfig = serde_json::from_str(&data).expect("JSON was not well-formatted"); - let node_id = if config.ids.len() <= cnode_selector { - info!("Generating new ciphernode..."); - let new_id = rand::thread_rng().gen_range(0..100000); - config.ids.push(new_id); - new_id - } else if config.ids[cnode_selector] == 0 { - info!("Generating initial ciphernode id..."); - let new_id = rand::thread_rng().gen_range(0..100000); - config.ids[cnode_selector] = new_id; - new_id - } else { - info!("Using ciphernode id {:?}", config.ids[cnode_selector]); - config.ids[cnode_selector] - }; - - let config_file = serde_json::to_string(&config).unwrap(); - fs::write(&config_path, config_file).unwrap(); - - let db_path = format!("{}/database/ciphernode-{}", path.display(), node_id); - info!("Node database path {:?}", db_path); - sled::open(db_path).unwrap() -}); - -fn init_logger() { - Builder::new() - .target(Target::Stdout) - .filter(None, LevelFilter::Info) - .format(|buf, record| { - writeln!( - buf, - "[{}:{}] - {}", - record.file().unwrap_or("unknown"), - record.line().unwrap_or(0), - record.args() - ) - }) - .init(); -} - -#[tokio::main] -async fn main() -> Result<(), Box> { - init_logger(); - - let config = load_config()?; - let node_id = get_node_id(&config); - - let params = generate_bfv_params(); - - let mut internal_round_count = RoundCount { round_count: 0 }; - - loop { - info!("Polling Enclave server."); - let https = HttpsConnector::new(); - let client_get = HyperClient::builder(TokioExecutor::new()).build::<_, Empty>(https.clone()); - let client = HyperClient::builder(TokioExecutor::new()).build::<_, String>(https); - - let count = get_round_count(&client_get, &config).await?; - - if count.round_count > internal_round_count.round_count { - handle_new_round(&client, &config, &count, node_id, ¶ms).await?; - } - - thread::sleep(time::Duration::from_millis(POLLING_INTERVAL)); - } -} - -fn load_config() -> Result>{ - let path = env::current_dir()?; - let config_path = format!("{}/example_ciphernode_config.json", path.display()); - let mut file = File::open(config_path)?; - let mut data = String::new(); - file.read_to_string(&mut data)?; - let config: CiphernodeConfig = serde_json::from_str(&data)?; - Ok(config) -} - -fn get_node_id(config: &CiphernodeConfig) -> u32 { - let args: Vec = env::args().collect(); - let cnode_selector = args.get(1).map(|arg| arg.parse::().unwrap()).unwrap_or(0); - config.ids[cnode_selector] -} - -fn generate_bfv_params() -> Arc { - BfvParametersBuilder::new() - .set_degree(DEGREE) - .set_plaintext_modulus(PLAINTEXT_MODULUS) - .set_moduli(&MODULI) - .build_arc() - .unwrap() -} - -async fn get_round_count(client: &HyperClientGet, config: &CiphernodeConfig) -> Result> { - let url = format!("{}/get_rounds", config.enclave_address); - let req = Request::builder() - .method(Method::GET) - .uri(url) - .body(Empty::::new())?; - - let resp = client.request(req).await?; - let body_bytes = resp.collect().await?.to_bytes(); - let body_str = String::from_utf8(body_bytes.to_vec())?; - let count: RoundCount = serde_json::from_str(&body_str)?; - info!("Server Round Count: {:?}", count.round_count); - Ok(count) -} - -async fn handle_new_round( - client: &HyperClientPost, - config: &CiphernodeConfig, - count: &RoundCount, - node_id: u32, - params: &Arc -) -> Result<(), Box> { - info!("Getting New Round State."); - let state = get_round_state(client, config, count.round_count).await?; - let eligibility = get_round_eligibility(client, config, count.round_count, node_id).await?; - - match (eligibility.is_eligible, eligibility.reason.as_str()) { - (false, "Waiting For New Round") => { - // Do nothing - }, - (true, "Open Node Spot") => { - register_ciphernode(client, config, &state, node_id, params).await?; - start_contract_watch(&state, node_id, config).await; - }, - (true, "Previously Registered") => { - start_contract_watch(&state, node_id, config).await; - }, - (false, "Round Full") => { - info!("Server reported round full, wait for next round."); - }, - _ => { - info!("Unknown eligibility status: {:?}", eligibility); - } - } - - Ok(()) -} - -async fn get_round_state( - client: &HyperClientPost, - config: &CiphernodeConfig, - round_id: u32 -) -> Result> { - let url = format!("{}/get_round_state_lite", config.enclave_address); - let body = serde_json::to_string(&GetRoundRequest { round_id })?; - let req = Request::builder() - .header("Content-Type", "application/json") - .method(Method::POST) - .uri(url) - .body(body)?; - - let resp = client.request(req).await?; - let body_bytes = resp.collect().await?.to_bytes(); - let body_str = String::from_utf8(body_bytes.to_vec())?; - let state: StateLite = serde_json::from_str(&body_str)?; - Ok(state) -} - -async fn get_round_eligibility( - client: &HyperClientPost, - config: &CiphernodeConfig, - round_id: u32, - node_id: u32 -) -> Result> { - let url = format!("{}/get_round_eligibility", config.enclave_address); - let body = serde_json::to_string(&GetEligibilityRequest { - round_id, - node_id, - is_eligible: false, - reason: "".to_string(), - })?; - let req = Request::builder() - .header("Content-Type", "application/json") - .method(Method::POST) - .uri(url) - .body(body)?; - - let resp = client.request(req).await?; - let body_bytes = resp.collect().await?.to_bytes(); - let body_str = String::from_utf8(body_bytes.to_vec())?; - let eligibility: GetEligibilityRequest = serde_json::from_str(&body_str)?; - info!("Ciphernode eligibility: {:?}", eligibility.reason); - Ok(eligibility) -} - -async fn register_ciphernode( - client: &HyperClientPost, - config: &CiphernodeConfig, - state: &StateLite, - node_id: u32, - params: &Arc -) -> Result<(), Box> { - info!("Generating PK share and serializing."); - let crp = CommonRandomPoly::deserialize(&state.crp,params).unwrap(); - let sk_share = SecretKey::random(params, &mut OsRng); - let pk_share = PublicKeyShare::new(&sk_share, crp.clone(), &mut thread_rng())?; - - let pk_share_bytes = pk_share.to_bytes(); - let sk_share_bytes = sk_share.coeffs.into_vec(); - - let (mut node_state, db_key) = get_state(node_id); - - node_state.pk_shares[0] = pk_share_bytes.clone(); - node_state.sk_shares[0] = sk_share_bytes; - - let response_key = PKShareRequest { - response: "Register Ciphernode Key".to_string(), - pk_share: pk_share_bytes, - id: node_id, - round_id: state.id - }; - - let url = format!("{}/register_ciphernode", config.enclave_address); - let body = serde_json::to_string(&response_key)?; - let req = Request::builder() - .header("Content-Type", "application/json") - .method(Method::POST) - .uri(url) - .body(body)?; - - let resp = client.request(req).await?; - let body_bytes = resp.collect().await?.to_bytes(); - let body_str = String::from_utf8(body_bytes.to_vec())?; - let registration_res: RegisterNodeResponse = serde_json::from_str(&body_str)?; - - info!("Ciphernode index: {:?}", registration_res.node_index); - - node_state.index[0] = registration_res.node_index; - - let state_str = serde_json::to_string(&node_state)?; - let state_bytes = state_str.into_bytes(); - GLOBAL_DB.insert(db_key, state_bytes)?; - - Ok(()) -} - -async fn start_contract_watch(state: &StateLite, node_id: u32, config: &CiphernodeConfig) { - let params = generate_bfv_params(); - let https = HttpsConnector::new(); - let client = HyperClient::builder(TokioExecutor::new()).build::<_, String>(https); - - loop { - info!("Waiting for round {:?} poll to end.", state.id); - let now = Utc::now(); - let internal_time = now.timestamp(); - - if (state.start_time + state.poll_length as i64) < internal_time { - info!("Poll time ended... performing FHE computation"); - - match process_votes(&client, state, node_id, config, ¶ms).await { - Ok(_) => break, - Err(e) => { - info!("Error processing votes: {:?}", e); - // Implement appropriate error handling or retry logic here - } - } - } - - thread::sleep(time::Duration::from_millis(POLLING_INTERVAL)); - } -} - -async fn process_votes( - client: &HyperClientPost, - state: &StateLite, - node_id: u32, - config: &CiphernodeConfig, - params: &Arc -) -> Result<(), Box> { - let num_voters = get_vote_count(client, config, state.id).await?; - let votes_collected = get_votes_contract(state.block_start, state.voting_address.clone(), state.chain_id).await?; - - info!("Votes Collected Len: {:?}", votes_collected.len()); - info!("All votes collected? {:?}", num_voters.vote_count == votes_collected.len() as u32); - - if votes_collected.is_empty() { - report_tally(client, config, state.id, 0, 0).await?; - return Ok(()); - } - - let tally = tally_votes(&votes_collected, params); - let decryption_shares = collect_decryption_shares(client, config, state, node_id, &tally, params).await?; - let tally_result = decrypt_tally(decryption_shares, &tally, params)?; - - let option_1_total = tally_result; - let option_2_total = num_voters.vote_count - tally_result as u32; - - info!("Vote result = {} / {}", tally_result, num_voters.vote_count); - info!("Option 1 total: {:?}", option_1_total); - info!("Option 2 total: {:?}", option_2_total); - - report_tally(client, config, state.id, option_1_total as u32, option_2_total).await?; - - Ok(()) -} - -async fn get_vote_count( - client: &HyperClientPost, - config: &CiphernodeConfig, - round_id: u32 -) -> Result> { - let url = format!("{}/get_vote_count_by_round", config.enclave_address); - let body = serde_json::to_string(&VoteCountRequest { round_id, vote_count: 0 })?; - let req = Request::builder() - .header("Content-Type", "application/json") - .method(Method::POST) - .uri(url) - .body(body)?; - - let resp = client.request(req).await?; - let body_bytes = resp.collect().await?.to_bytes(); - let body_str = String::from_utf8(body_bytes.to_vec())?; - let num_voters: VoteCountRequest = serde_json::from_str(&body_str)?; - - info!("VoteCountRequest: {:?}", num_voters); - Ok(num_voters) -} - -async fn get_votes_contract(block_start: U64, address: String, _chain_id: u32) -> Result>, Box> { - info!("Filtering contract for votes"); - - let rpc_url = "http://127.0.0.1:8545".to_string(); - - abigen!( - IVOTE, - r#"[ - function tester() external view returns (string) - function id() external view returns (uint256) - function voteEncrypted(bytes memory encVote) public - event Voted(address indexed voter, bytes vote) - ]"#, - ); - - let provider = Provider::::try_from(rpc_url)?; - let contract_address = address.parse::
()?; - let client = Arc::new(provider); - let contract = IVOTE::new(contract_address, Arc::new(client.clone())); - - let events = contract.events().from_block(block_start).query().await?; - - let votes_encrypted: Vec> = events.iter().map(|event| event.vote.to_vec()).collect(); - Ok(votes_encrypted) -} - -fn tally_votes(votes_collected: &[Vec], params: &Arc) -> Arc { - let mut sum = Ciphertext::zero(params); - for vote in votes_collected { - let deserialized_vote = Ciphertext::from_bytes(vote, params).unwrap(); - sum += &deserialized_vote; - } - Arc::new(sum) -} - -async fn collect_decryption_shares( - client: &HyperClientPost, - config: &CiphernodeConfig, - state: &StateLite, - node_id: u32, - tally: &Arc, - params: &Arc -) -> Result, Box> { - let (node_state, _) = get_state(node_id); - let sk_share_coeff_bytes = node_state.sk_shares[0].clone(); - let sk_share = SecretKey::new(sk_share_coeff_bytes, params); - - let sh = DecryptionShare::new(&sk_share, tally, &mut thread_rng())?; - let sks_bytes = sh.to_bytes(); - - let response_sks = SKSShareRequest { - response: "Register_SKS_Share".to_string(), - sks_share: sks_bytes, - index: node_state.index[0], - round_id: state.id - }; - - let url = format!("{}/register_sks_share", config.enclave_address); - let body = serde_json::to_string(&response_sks)?; - let req = Request::builder() - .header("Content-Type", "application/json") - .method(Method::POST) - .uri(url) - .body(body)?; - - let mut resp = client.request(req).await?; - info!("Register SKS Response status: {}", resp.status()); - - while let Some(frame) = resp.frame().await { - if let Some(chunk) = frame?.data_ref() { - io::stdout().write_all(chunk).await?; - } - } - - let mut decryption_shares = Vec::with_capacity(state.ciphernode_total as usize); - - loop { - let response_get_sks = SKSSharePoll { - response: "Get_All_SKS_Shares".to_string(), - round_id: state.id, - ciphernode_count: state.ciphernode_total - }; - - let url = format!("{}/get_sks_shares", config.enclave_address); - let body = serde_json::to_string(&response_get_sks)?; - let req = Request::builder() - .header("Content-Type", "application/json") - .method(Method::POST) - .uri(url) - .body(body)?; - - let resp = client.request(req).await?; - info!("Get All SKS Response status: {}", resp.status()); - - if resp.status().as_u16() == 500 { - info!("Enclave resource failed, trying to poll for sks shares again..."); - continue; - } - - let body_bytes = resp.collect().await?.to_bytes(); - let body_str = String::from_utf8(body_bytes.to_vec())?; - let shares: SKSShareResponse = serde_json::from_str(&body_str)?; - - if shares.response == "final" { - info!("Collected all of the decrypt shares!"); - for sks_share in shares.sks_shares { - decryption_shares.push(DecryptionShare::deserialize(&sks_share, params, tally.clone())?); - } - break; - } - - thread::sleep(time::Duration::from_millis(3000)); - } - - Ok(decryption_shares) -} - -fn decrypt_tally( - decryption_shares: Vec, - tally: &Arc, - params: &Arc -) -> Result> { - let tally_pt: Plaintext = decryption_shares.into_iter().aggregate()?; - let tally_vec = Vec::::try_decode(&tally_pt, Encoding::poly())?; - Ok(tally_vec[0]) -} - -async fn report_tally( - client: &HyperClientPost, - config: &CiphernodeConfig, - round_id: u32, - option_1: u32, - option_2: u32 -) -> Result<(), Box> { - let response_report = ReportTallyRequest { - round_id, - option_1, - option_2 - }; - - let url = format!("{}/report_tally", config.enclave_address); - let body = serde_json::to_string(&response_report)?; - let req = Request::builder() - .header("Content-Type", "application/json") - .method(Method::POST) - .uri(url) - .body(body)?; - - let resp = client.request(req).await?; - info!("Tally Reported Response status: {}", resp.status()); - Ok(()) -} - -fn get_state(node_id: u32) -> (Ciphernode, String) { - let pathdb = env::current_dir().unwrap(); - let pathdbst = format!("{}/database/ciphernode-{}-state", pathdb.display(), node_id); - info!("Database key is {:?}", pathdbst); - let state_out = GLOBAL_DB.get(pathdbst.clone()).unwrap().unwrap(); - let state_out_str = str::from_utf8(&state_out).unwrap(); - let state_out_struct: Ciphernode = serde_json::from_str(state_out_str).unwrap(); - (state_out_struct, pathdbst) -} \ No newline at end of file diff --git a/packages/ciphernode/src/bin/util.rs b/packages/ciphernode/src/bin/util.rs deleted file mode 100644 index adf668e..0000000 --- a/packages/ciphernode/src/bin/util.rs +++ /dev/null @@ -1,130 +0,0 @@ -//! Utility functions - -use fhe::bfv; -use fhe_traits::FheEncoder; -use fhe_util::transcode_from_bytes; -use std::{cmp::min, fmt, sync::Arc, time::Duration}; -use log::info; - -/// Macros to time code and display a human-readable duration. -pub mod timeit { - #[allow(unused_macros)] - macro_rules! timeit_n { - ($name:expr, $loops:expr, $code:expr) => {{ - use util::DisplayDuration; - let start = std::time::Instant::now(); - let r = $code; - for _ in 1..$loops { - let _ = $code; - } - info!( - "⏱ {}: {}", - $name, - DisplayDuration(start.elapsed() / $loops) - ); - r - }}; - } - - #[allow(unused_macros)] - macro_rules! timeit { - ($name:expr, $code:expr) => {{ - use util::DisplayDuration; - let start = std::time::Instant::now(); - let r = $code; - info!("⏱ {}: {}", $name, DisplayDuration(start.elapsed())); - r - }}; - } - - #[allow(unused_imports)] - pub(crate) use timeit; - #[allow(unused_imports)] - pub(crate) use timeit_n; -} - -/// Utility struct for displaying human-readable duration of the form "10.5 ms", -/// "350 Ξs", or "27 ns". -pub struct DisplayDuration(pub Duration); - -impl fmt::Display for DisplayDuration { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let duration_ns = self.0.as_nanos(); - if duration_ns < 1_000_u128 { - write!(f, "{duration_ns} ns") - } else if duration_ns < 1_000_000_u128 { - write!(f, "{} Ξs", (duration_ns + 500) / 1_000) - } else { - let duration_ms_times_10 = (duration_ns + 50_000) / (100_000); - write!(f, "{} ms", (duration_ms_times_10 as f64) / 10.0) - } - } -} - -// Utility functions for Private Information Retrieval. - -/// Generate a database of elements of the form [i || 0...0] where i is the 4B -/// little endian encoding of the index. When the element size is less than 4B, -/// the encoding is truncated. -#[allow(dead_code)] -pub fn generate_database(database_size: usize, elements_size: usize) -> Vec> { - assert!(database_size > 0 && elements_size > 0); - let mut database = vec![vec![0u8; elements_size]; database_size]; - for (i, element) in database.iter_mut().enumerate() { - element[..min(4, elements_size)] - .copy_from_slice(&(i as u32).to_le_bytes()[..min(4, elements_size)]); - } - database -} - -#[allow(dead_code)] -pub fn number_elements_per_plaintext( - degree: usize, - plaintext_nbits: usize, - elements_size: usize, -) -> usize { - (plaintext_nbits * degree) / (elements_size * 8) -} - -#[allow(dead_code)] -pub fn encode_database( - database: &Vec>, - par: Arc, - level: usize, -) -> (Vec, (usize, usize)) { - assert!(!database.is_empty()); - - let elements_size = database[0].len(); - let plaintext_nbits = par.plaintext().ilog2() as usize; - let number_elements_per_plaintext = - number_elements_per_plaintext(par.degree(), plaintext_nbits, elements_size); - let number_rows = - (database.len() + number_elements_per_plaintext - 1) / number_elements_per_plaintext; - info!("number_rows = {number_rows}"); - info!("number_elements_per_plaintext = {number_elements_per_plaintext}"); - let dimension_1 = (number_rows as f64).sqrt().ceil() as usize; - let dimension_2 = (number_rows + dimension_1 - 1) / dimension_1; - info!("dimensions = {dimension_1} {dimension_2}"); - info!("dimension = {}", dimension_1 * dimension_2); - let mut preprocessed_database = - vec![ - bfv::Plaintext::zero(bfv::Encoding::poly_at_level(level), &par).unwrap(); - dimension_1 * dimension_2 - ]; - (0..number_rows).for_each(|i| { - let mut serialized_plaintext = vec![0u8; number_elements_per_plaintext * elements_size]; - for j in 0..number_elements_per_plaintext { - if let Some(pt) = database.get(j + i * number_elements_per_plaintext) { - serialized_plaintext[j * elements_size..(j + 1) * elements_size].copy_from_slice(pt) - } - } - let pt_values = transcode_from_bytes(&serialized_plaintext, plaintext_nbits); - preprocessed_database[i] = - bfv::Plaintext::try_encode(&pt_values, bfv::Encoding::poly_at_level(level), &par) - .unwrap(); - }); - (preprocessed_database, (dimension_1, dimension_2)) -} - -#[allow(dead_code)] -fn main() {} \ No newline at end of file diff --git a/packages/ciphernode/src/main.rs b/packages/ciphernode/src/main.rs deleted file mode 100644 index 964d1dd..0000000 --- a/packages/ciphernode/src/main.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() -> Result<(), ()> { - - Ok(()) -} \ No newline at end of file diff --git a/packages/client/src/context/voteManagement/VoteManagement.context.tsx b/packages/client/src/context/voteManagement/VoteManagement.context.tsx index 9f0ceca..4d17939 100644 --- a/packages/client/src/context/voteManagement/VoteManagement.context.tsx +++ b/packages/client/src/context/voteManagement/VoteManagement.context.tsx @@ -65,7 +65,7 @@ const VoteManagementProvider = ({ children }: VoteManagementProviderProps) => { const getRoundStateLite = async (roundCount: number) => { const roundState = await getRoundStateLiteRequest(roundCount) - if (roundState?.pk.length === 1 && roundState.pk[0] === 0) { + if (roundState?.committee_public_key.length === 1 && roundState.committee_public_key[0] === 0) { handleGenericError('getRoundStateLite', { message: 'Enclave server failed generating the necessary pk bytes', name: 'getRoundStateLite', @@ -73,9 +73,9 @@ const VoteManagementProvider = ({ children }: VoteManagementProviderProps) => { } if (roundState) { setRoundState(roundState) - setVotingRound({ round_id: roundState.id, pk_bytes: roundState.pk }) + setVotingRound({ round_id: roundState.id, pk_bytes: roundState.committee_public_key }) setPollOptions(generatePoll({ round_id: roundState.id, emojis: roundState.emojis })) - setRoundEndDate(convertTimestampToDate(roundState.start_time, roundState.poll_length)) + setRoundEndDate(convertTimestampToDate(roundState.start_time, roundState.duration)) } } diff --git a/packages/client/src/model/vote.model.ts b/packages/client/src/model/vote.model.ts index ce328ca..30758d6 100644 --- a/packages/client/src/model/vote.model.ts +++ b/packages/client/src/model/vote.model.ts @@ -27,17 +27,16 @@ export interface BroadcastVoteResponse { export interface VoteStateLite { id: number - status: string - poll_length: number - voting_address: string chain_id: number - ciphernode_count: number - pk_share_count: number - sks_share_count: number + enclave_address: string + + status: string vote_count: number - crp: number[] - pk: number[] + start_time: number - ciphernode_total: number + duration: number + expiration: number + + committee_public_key: number[] emojis: [string, string] } diff --git a/packages/client/src/pages/DailyPoll/DailyPoll.tsx b/packages/client/src/pages/DailyPoll/DailyPoll.tsx index 959f343..53e3b3f 100644 --- a/packages/client/src/pages/DailyPoll/DailyPoll.tsx +++ b/packages/client/src/pages/DailyPoll/DailyPoll.tsx @@ -13,7 +13,7 @@ const DailyPoll: React.FC = () => { useVoteManagementContext() const [loading, setLoading] = useState(false) const [newRoundLoading, setNewRoundLoading] = useState(false) - const endTime = roundState && convertTimestampToDate(roundState?.start_time, roundState?.poll_length) + const endTime = roundState && convertTimestampToDate(roundState?.start_time, roundState?.duration) useEffect(() => { const checkRound = async () => { @@ -51,7 +51,7 @@ const DailyPoll: React.FC = () => { const handleVoted = async (vote: Poll | null) => { if (!vote || !votingRound) return setLoading(true) - + try { const voteEncrypted = await handleVoteEncryption(vote) const broadcastVoteResponse = voteEncrypted && (await handleVoteBroadcast(voteEncrypted)) diff --git a/packages/client/src/pages/Landing/components/DailyPoll.tsx b/packages/client/src/pages/Landing/components/DailyPoll.tsx index 0d8b93c..b92bc04 100644 --- a/packages/client/src/pages/Landing/components/DailyPoll.tsx +++ b/packages/client/src/pages/Landing/components/DailyPoll.tsx @@ -24,7 +24,7 @@ const DailyPollSection: React.FC = ({ onVoted, loading, e interval: 2000, }) const { user, pollOptions, setPollOptions, roundState } = useVoteManagementContext() - const isEnded = roundState ? hasPollEnded(roundState?.poll_length, roundState?.start_time) : false + const isEnded = roundState ? hasPollEnded(roundState?.duration, roundState?.start_time) : false const status = roundState?.status const [pollSelected, setPollSelected] = useState(null) const [noPollSelected, setNoPollSelected] = useState(true) diff --git a/packages/client/src/utils/methods.ts b/packages/client/src/utils/methods.ts index 525a204..a758fa7 100644 --- a/packages/client/src/utils/methods.ts +++ b/packages/client/src/utils/methods.ts @@ -93,7 +93,7 @@ export const convertPollData = (request: PollRequestResult[]): PollResult[] => { } export const convertVoteStateLite = (voteState: VoteStateLite): PollResult => { - const endTime = voteState.start_time + voteState.poll_length + const endTime = voteState.expiration const date = new Date(endTime * 1000).toISOString() const options: PollOption[] = [ diff --git a/packages/compute_provider/.gitignore b/packages/compute_provider/.gitignore deleted file mode 100644 index f4247e1..0000000 --- a/packages/compute_provider/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -.DS_Store -Cargo.lock -methods/guest/Cargo.lock -target/ diff --git a/packages/compute_provider/Cargo.lock b/packages/compute_provider/Cargo.lock new file mode 100644 index 0000000..6ec6d30 --- /dev/null +++ b/packages/compute_provider/Cargo.lock @@ -0,0 +1,1258 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" + +[[package]] +name = "ark-bn254" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", + "itertools 0.10.5", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest", + "itertools 0.10.5", + "num-bigint", + "num-traits", + "paste", + "rustc_version", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "digest", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "compute-provider" +version = "0.1.0" +dependencies = [ + "ark-bn254", + "ark-ff", + "fhe", + "fhe-traits", + "hex", + "light-poseidon", + "num-bigint", + "num-traits", + "rand", + "rayon", + "serde", + "sha3", + "tracing-subscriber", + "zk-kit-imt", +] + +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "ethnum" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b90ca2580b73ab6a1f724b76ca11ab632df820fd6040c336200d2c1df7b3c82c" + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[package]] +name = "fhe" +version = "0.1.0-beta.7" +source = "git+https://github.com/gnosisguild/fhe.rs#93ce9d45374bdc52ad02e5320a11e13eb777e1eb" +dependencies = [ + "doc-comment", + "fhe-math", + "fhe-traits", + "fhe-util", + "itertools 0.12.1", + "ndarray", + "num-bigint", + "num-traits", + "prost", + "prost-build", + "rand", + "rand_chacha", + "serde", + "thiserror", + "zeroize", + "zeroize_derive", +] + +[[package]] +name = "fhe-math" +version = "0.1.0-beta.7" +source = "git+https://github.com/gnosisguild/fhe.rs#93ce9d45374bdc52ad02e5320a11e13eb777e1eb" +dependencies = [ + "ethnum", + "fhe-traits", + "fhe-util", + "itertools 0.12.1", + "ndarray", + "num-bigint", + "num-bigint-dig", + "num-traits", + "prost", + "prost-build", + "rand", + "rand_chacha", + "sha2", + "thiserror", + "zeroize", +] + +[[package]] +name = "fhe-traits" +version = "0.1.0-beta.7" +source = "git+https://github.com/gnosisguild/fhe.rs#93ce9d45374bdc52ad02e5320a11e13eb777e1eb" +dependencies = [ + "rand", +] + +[[package]] +name = "fhe-util" +version = "0.1.0-beta.7" +source = "git+https://github.com/gnosisguild/fhe.rs#93ce9d45374bdc52ad02e5320a11e13eb777e1eb" +dependencies = [ + "itertools 0.12.1", + "num-bigint-dig", + "num-traits", + "rand", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "indexmap" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "light-poseidon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" +dependencies = [ + "ark-bn254", + "ark-ff", + "num-bigint", + "thiserror", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matrixmultiply" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" + +[[package]] +name = "ndarray" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "rawpointer", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "serde", + "smallvec", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ea5043e58958ee56f3e15a90aee535795cd7dfd319846288d93c5b57d85cbe" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" +dependencies = [ + "proc-macro2", + "syn 2.0.77", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" +dependencies = [ + "bytes", + "heck", + "itertools 0.12.1", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 2.0.77", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "zk-kit-imt" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4bec228e2acafec7bd22c9a0a5e0c5e2a6d0c17df69f5ad11c24ce6dc6356c6" +dependencies = [ + "hex", + "tiny-keccak", +] diff --git a/packages/compute_provider/Cargo.toml b/packages/compute_provider/Cargo.toml index 1122188..1ded129 100644 --- a/packages/compute_provider/Cargo.toml +++ b/packages/compute_provider/Cargo.toml @@ -1,22 +1,20 @@ -[workspace] -resolver = "2" -members = ["host", "methods", "core"] +[package] +name = "compute-provider" +version = "0.1.0" +edition = "2021" - -[workspace.dependencies] -risc0-build = { version = "1.0.5" } -risc0-build-ethereum = { git = "https://github.com/risc0/risc0-ethereum", tag = "v1.0.0" } -risc0-ethereum-contracts = { git = "https://github.com/risc0/risc0-ethereum", tag = "v1.0.0" } -risc0-zkvm = { version = "1.0.5", default-features = false, features = ["std", "client"] } -risc0-zkp = { version = "1.0.5" } +[dependencies] fhe = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } serde = { version = "1.0", features = ["derive", "std"] } - -# Always optimize; building and running the guest takes much longer without optimization. -[profile.dev] -opt-level = 3 - -[profile.release] -debug = 1 -lto = true +zk-kit-imt = "0.0.5" +sha3 = "0.10.8" +num-bigint = "0.4" +num-traits = "0.2" +hex = "0.4" +light-poseidon = "0.2.0" +ark-ff = "0.4.2" +ark-bn254 = "0.4.0" +rand = "0.8" +rayon = "1.10.0" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/packages/compute_provider/core/Cargo.toml b/packages/compute_provider/core/Cargo.toml deleted file mode 100644 index 611b9f1..0000000 --- a/packages/compute_provider/core/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "compute-provider-core" -version = "0.1.0" -edition = "2021" - -[dependencies] -risc0-zkvm = { workspace = true } -risc0-zkp = { workspace = true } -serde = { workspace = true } -fhe = { workspace = true } -fhe-traits = { workspace = true } -zk-kit-imt = "0.0.5" -sha3 = "0.10.8" -num-bigint = "0.4" -num-traits = "0.2" -hex = "0.4" -light-poseidon = "0.2.0" -ark-ff = "0.4.2" -ark-bn254 = "0.4.0" \ No newline at end of file diff --git a/packages/compute_provider/host/Cargo.toml b/packages/compute_provider/host/Cargo.toml deleted file mode 100644 index 2bcff2c..0000000 --- a/packages/compute_provider/host/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "compute-provider-host" -version = "0.1.0" -edition = "2021" - -[dependencies] -compute-provider-methods = { path = "../methods" } -risc0-zkvm = { workspace = true } -risc0-build-ethereum = { workspace = true } -risc0-ethereum-contracts = { workspace = true } -tracing-subscriber = { version = "0.3", features = ["env-filter"] } -serde = "1.0" -compute-provider-core ={ path = "../core"} -fhe = { workspace = true } -fhe-traits = { workspace = true } -rand = "0.8" -rayon = "1.10.0" diff --git a/packages/compute_provider/host/src/lib.rs b/packages/compute_provider/host/src/lib.rs deleted file mode 100644 index 0227010..0000000 --- a/packages/compute_provider/host/src/lib.rs +++ /dev/null @@ -1,244 +0,0 @@ -use compute_provider_core::{ - merkle_tree::MerkleTree, FHEInputs, ComputationInput, - ComputationResult, -}; -use rayon::prelude::*; -use risc0_ethereum_contracts::groth16; -use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts, VerifierContext}; -use std::sync::Arc; - -pub struct ComputeProvider { - input: ComputationInput, - use_parallel: bool, - batch_size: Option, -} - -impl ComputeProvider { - pub fn new(fhe_inputs: FHEInputs, use_parallel: bool, batch_size: Option) -> Self { - Self { - input: ComputationInput { - fhe_inputs, - leaf_hashes: Vec::new(), - tree_depth: 10, - zero_node: String::from("0"), - arity: 2, - }, - use_parallel, - batch_size, - } - } - - pub fn start(&mut self, elf: &[u8]) -> (ComputationResult, Vec) { - tracing_subscriber::fmt() - .with_env_filter(tracing_subscriber::filter::EnvFilter::from_default_env()) - .init(); - - if self.use_parallel { - self.start_parallel(elf) - } else { - self.start_sequential(elf) - } - } - - fn start_sequential(&mut self, elf: &[u8]) -> (ComputationResult, Vec) { - let mut tree_handler = MerkleTree::new( - self.input.tree_depth, - self.input.zero_node.clone(), - self.input.arity, - ); - tree_handler.compute_leaf_hashes(&self.input.fhe_inputs.ciphertexts); - self.input.leaf_hashes = tree_handler.leaf_hashes.clone(); - - let env = ExecutorEnv::builder() - .write(&self.input) - .unwrap() - .build() - .unwrap(); - - let receipt = default_prover() - .prove_with_ctx( - env, - &VerifierContext::default(), - elf, - &ProverOpts::groth16(), - ) - .unwrap() - .receipt; - - let seal = groth16::encode(receipt.inner.groth16().unwrap().seal.clone()).unwrap(); - - (receipt.journal.decode().unwrap(), seal) - } - - fn start_parallel(&self, elf: &[u8]) -> (ComputationResult, Vec) { - let batch_size = self.batch_size.unwrap_or(1); - let parallel_tree_depth = (batch_size as f64).log2().ceil() as usize; - - let ciphertexts = Arc::new(self.input.fhe_inputs.ciphertexts.clone()); - let params = Arc::new(self.input.fhe_inputs.params.clone()); - - let chunks: Vec>> = ciphertexts - .chunks(batch_size) - .map(|chunk| chunk.to_vec()) - .collect(); - - let tally_results: Vec = chunks - .into_par_iter() - .map(|chunk| { - let mut tree_handler = MerkleTree::new(parallel_tree_depth, "0".to_string(), 2); - tree_handler.compute_leaf_hashes(&chunk); - - let input = ComputationInput { - fhe_inputs: FHEInputs { - ciphertexts: chunk.clone(), - params: params.to_vec(), // Params are shared across chunks - }, - leaf_hashes: tree_handler.leaf_hashes.clone(), - tree_depth: parallel_tree_depth, - zero_node: "0".to_string(), - arity: 2, - }; - - let env = ExecutorEnv::builder() - .write(&input) - .unwrap() - .build() - .unwrap(); - - let receipt = default_prover() - .prove_with_ctx( - env, - &VerifierContext::default(), - elf, - &ProverOpts::groth16(), - ) - .unwrap() - .receipt; - - receipt.journal.decode().unwrap() - }) - .collect(); - - // Combine the sorted results for final computation - let final_depth = self.input.tree_depth - parallel_tree_depth; - let mut final_input = ComputationInput { - fhe_inputs: FHEInputs { - ciphertexts: tally_results - .iter() - .map(|result| result.ciphertext.clone()) - .collect(), - params: params.to_vec(), - }, - leaf_hashes: tally_results - .iter() - .map(|result| result.merkle_root.clone()) - .collect(), - tree_depth: final_depth, - zero_node: String::from("0"), - arity: 2, - }; - - let final_tree_handler = MerkleTree::new( - final_depth, - final_input.zero_node.clone(), - final_input.arity, - ); - final_input.zero_node = final_tree_handler.zeroes()[parallel_tree_depth].clone(); - - let env = ExecutorEnv::builder() - .write(&final_input) - .unwrap() - .build() - .unwrap(); - - let receipt = default_prover() - .prove_with_ctx( - env, - &VerifierContext::default(), - elf, - &ProverOpts::groth16(), - ) - .unwrap() - .receipt; - - let combined_seal = groth16::encode(receipt.inner.groth16().unwrap().seal.clone()).unwrap(); - (receipt.journal.decode().unwrap(), combined_seal) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use compute_provider_methods::COMPUTE_PROVIDER_ELF; - use fhe::bfv::{ - BfvParameters, BfvParametersBuilder, Ciphertext, Encoding, Plaintext, PublicKey, SecretKey, - }; - use fhe_traits::{ - DeserializeParametrized, FheDecoder, FheDecrypter, FheEncoder, FheEncrypter, Serialize, - }; - use rand::thread_rng; - use std::sync::Arc; - - #[test] - fn test_compute_provider() { - let params = create_params(); - let (sk, pk) = generate_keys(¶ms); - let inputs = vec![1, 1, 0, 1]; - let ciphertexts = encrypt_inputs(&inputs, &pk, ¶ms); - - let ciphertext_inputs = FHEInputs { - ciphertexts: ciphertexts.iter().map(|c| c.to_bytes()).collect(), - params: params.to_bytes(), - }; - - let mut provider = ComputeProvider::new(ciphertext_inputs, false, None); - let (result, _seal) = provider.start(COMPUTE_PROVIDER_ELF); - - let tally = decrypt_result(&result, &sk, ¶ms); - - assert_eq!(tally, inputs.iter().sum::()); - } - - fn create_params() -> Arc { - BfvParametersBuilder::new() - .set_degree(1024) - .set_plaintext_modulus(65537) - .set_moduli(&[1152921504606584833]) - .build_arc() - .expect("Failed to build parameters") - } - - fn generate_keys(params: &Arc) -> (SecretKey, PublicKey) { - let mut rng = thread_rng(); - let sk = SecretKey::random(params, &mut rng); - let pk = PublicKey::new(&sk, &mut rng); - (sk, pk) - } - - fn encrypt_inputs( - inputs: &[u64], - pk: &PublicKey, - params: &Arc, - ) -> Vec { - let mut rng = thread_rng(); - inputs - .iter() - .map(|&input| { - let pt = Plaintext::try_encode(&[input], Encoding::poly(), params) - .expect("Failed to encode plaintext"); - pk.try_encrypt(&pt, &mut rng).expect("Failed to encrypt") - }) - .collect() - } - - fn decrypt_result( - result: &ComputationResult, - sk: &SecretKey, - params: &Arc, - ) -> u64 { - let ct = Ciphertext::from_bytes(&result.ciphertext, params) - .expect("Failed to deserialize ciphertext"); - let decrypted = sk.try_decrypt(&ct).expect("Failed to decrypt"); - Vec::::try_decode(&decrypted, Encoding::poly()).expect("Failed to decode")[0] - } -} diff --git a/packages/compute_provider/methods/Cargo.toml b/packages/compute_provider/methods/Cargo.toml deleted file mode 100644 index dfe6fc7..0000000 --- a/packages/compute_provider/methods/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "compute-provider-methods" -version = "0.1.0" -edition = "2021" - -[build-dependencies] -risc0-build = { workspace = true } -risc0-build-ethereum = { workspace = true } - -[package.metadata.risc0] -methods = ["guest"] diff --git a/packages/compute_provider/methods/build.rs b/packages/compute_provider/methods/build.rs deleted file mode 100644 index 08a8a4e..0000000 --- a/packages/compute_provider/methods/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - risc0_build::embed_methods(); -} diff --git a/packages/compute_provider/methods/guest/Cargo.toml b/packages/compute_provider/methods/guest/Cargo.toml deleted file mode 100644 index 69ccca4..0000000 --- a/packages/compute_provider/methods/guest/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "compute_provider" -version = "0.1.0" -edition = "2021" - -[workspace] - -[dependencies] -risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] } -compute-provider-core ={ path = "../../core"} \ No newline at end of file diff --git a/packages/compute_provider/methods/guest/src/main.rs b/packages/compute_provider/methods/guest/src/main.rs deleted file mode 100644 index 2c32a90..0000000 --- a/packages/compute_provider/methods/guest/src/main.rs +++ /dev/null @@ -1,11 +0,0 @@ -use risc0_zkvm::guest::env; -use compute_provider_core::{ComputationInput, ComputationResult, default_fhe_processor}; - - -fn main() { - let input: ComputationInput = env::read(); - - let result: ComputationResult = input.process(default_fhe_processor); - - env::commit(&result); -} diff --git a/packages/compute_provider/methods/src/lib.rs b/packages/compute_provider/methods/src/lib.rs deleted file mode 100644 index 1bdb308..0000000 --- a/packages/compute_provider/methods/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -include!(concat!(env!("OUT_DIR"), "/methods.rs")); diff --git a/packages/compute_provider/rust-toolchain.toml b/packages/compute_provider/rust-toolchain.toml deleted file mode 100644 index 36614c3..0000000 --- a/packages/compute_provider/rust-toolchain.toml +++ /dev/null @@ -1,4 +0,0 @@ -[toolchain] -channel = "stable" -components = ["rustfmt", "rust-src"] -profile = "minimal" diff --git a/packages/compute_provider/core/src/lib.rs b/packages/compute_provider/src/compute_input.rs similarity index 55% rename from packages/compute_provider/core/src/lib.rs rename to packages/compute_provider/src/compute_input.rs index 9c74414..c713b4a 100644 --- a/packages/compute_provider/core/src/lib.rs +++ b/packages/compute_provider/src/compute_input.rs @@ -1,19 +1,9 @@ -pub mod merkle_tree; - -use fhe::bfv::{BfvParameters, Ciphertext}; -use fhe_traits::{Deserialize, DeserializeParametrized, Serialize}; -use merkle_tree::MerkleTree; +use crate::merkle_tree::MerkleTree; use sha3::{Digest, Keccak256}; -use std::sync::Arc; -pub type FHEProcessor = fn(&FHEInputs) -> Vec; +use crate::provider::ComputeResult; -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] -pub struct ComputationResult { - pub ciphertext: Vec, - pub params_hash: String, - pub merkle_root: String, -} +pub type FHEProcessor = fn(&FHEInputs) -> Vec; #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct FHEInputs { @@ -22,7 +12,7 @@ pub struct FHEInputs { } #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] -pub struct ComputationInput { +pub struct ComputeInput { pub fhe_inputs: FHEInputs, pub leaf_hashes: Vec, pub tree_depth: usize, @@ -30,8 +20,8 @@ pub struct ComputationInput { pub arity: usize, } -impl ComputationInput { - pub fn process(&self, fhe_processor: FHEProcessor) -> ComputationResult { +impl ComputeInput { + pub fn process(&self, fhe_processor: FHEProcessor) -> ComputeResult { let processed_ciphertext = (fhe_processor)(&self.fhe_inputs); let merkle_root = MerkleTree { @@ -50,24 +40,10 @@ impl ComputationInput { .finalize(), ); - ComputationResult { + ComputeResult { ciphertext: processed_ciphertext, params_hash, merkle_root, } } } - - -// Example Implementation of the CiphertextProcessor function -pub fn default_fhe_processor(fhe_inputs: &FHEInputs) -> Vec { - let params = Arc::new(BfvParameters::try_deserialize(&fhe_inputs.params).unwrap()); - - let mut sum = Ciphertext::zero(¶ms); - for ciphertext_bytes in &fhe_inputs.ciphertexts { - let ciphertext = Ciphertext::from_bytes(ciphertext_bytes, ¶ms).unwrap(); - sum += &ciphertext; - } - - sum.to_bytes() -} \ No newline at end of file diff --git a/packages/compute_provider/src/compute_manager.rs b/packages/compute_provider/src/compute_manager.rs new file mode 100644 index 0000000..39a6af4 --- /dev/null +++ b/packages/compute_provider/src/compute_manager.rs @@ -0,0 +1,118 @@ +use crate::provider::ComputeProvider; +use crate::compute_input::{ComputeInput,FHEInputs}; +use crate::merkle_tree::MerkleTree; +use crate::ComputeOutput; +use rayon::prelude::*; +use std::sync::Arc; + +pub struct ComputeManager

+where + P: ComputeProvider + Send + Sync, +{ + input: ComputeInput, + provider: P, + use_parallel: bool, + batch_size: Option, +} + +impl

ComputeManager

+where + P: ComputeProvider + Send + Sync +{ + pub fn new(provider: P, fhe_inputs: FHEInputs, use_parallel: bool, batch_size: Option) -> Self { + Self { + provider, + input: ComputeInput { + fhe_inputs, + leaf_hashes: Vec::new(), + tree_depth: 10, + zero_node: String::from("0"), + arity: 2, + }, + use_parallel, + batch_size, + } + } + + pub fn start(&mut self) -> P::Output { + if self.use_parallel { + self.start_parallel() + } else { + self.start_sequential() + } + } + + fn start_sequential(&mut self) -> P::Output { + let mut tree_handler = MerkleTree::new( + self.input.tree_depth, + self.input.zero_node.clone(), + self.input.arity, + ); + tree_handler.compute_leaf_hashes(&self.input.fhe_inputs.ciphertexts); + self.input.leaf_hashes = tree_handler.leaf_hashes.clone(); + + self.provider.prove(&self.input) + } + + fn start_parallel(&self) -> P::Output { + let batch_size = self.batch_size.unwrap_or(1); + let parallel_tree_depth = (batch_size as f64).log2().ceil() as usize; + + let ciphertexts = Arc::new(self.input.fhe_inputs.ciphertexts.clone()); + let params = Arc::new(self.input.fhe_inputs.params.clone()); + + let chunks: Vec>> = ciphertexts + .chunks(batch_size) + .map(|chunk| chunk.to_vec()) + .collect(); + + let tally_results: Vec = chunks + .into_par_iter() + .map(|chunk| { + let mut tree_handler = MerkleTree::new(parallel_tree_depth, "0".to_string(), 2); + tree_handler.compute_leaf_hashes(&chunk); + + let input = ComputeInput { + fhe_inputs: FHEInputs { + ciphertexts: chunk.clone(), + params: params.to_vec(), // Params are shared across chunks + }, + leaf_hashes: tree_handler.leaf_hashes.clone(), + tree_depth: parallel_tree_depth, + zero_node: "0".to_string(), + arity: 2, + }; + + self.provider.prove(&input) + }) + .collect(); + + // Combine the sorted results for final computation + let final_depth = self.input.tree_depth - parallel_tree_depth; + let mut final_input = ComputeInput { + fhe_inputs: FHEInputs { + ciphertexts: tally_results + .iter() + .map(|result| result.ciphertext().clone()) + .collect(), + params: params.to_vec(), + }, + leaf_hashes: tally_results + .iter() + .map(|result| result.merkle_root().clone()) + .collect(), + tree_depth: final_depth, + zero_node: String::from("0"), + arity: 2, + }; + + let final_tree_handler = MerkleTree::new( + final_depth, + final_input.zero_node.clone(), + final_input.arity, + ); + final_input.zero_node = final_tree_handler.zeroes()[parallel_tree_depth].clone(); + + self.provider.prove(&final_input) + } +} diff --git a/packages/compute_provider/src/lib.rs b/packages/compute_provider/src/lib.rs new file mode 100644 index 0000000..f4f9a0b --- /dev/null +++ b/packages/compute_provider/src/lib.rs @@ -0,0 +1,103 @@ +mod compute_input; +mod compute_manager; +mod merkle_tree; +mod provider; + +pub use compute_manager::*; +pub use compute_input::*; +pub use provider::*; + +use fhe::bfv::{BfvParameters, Ciphertext}; +use fhe_traits::{Deserialize, DeserializeParametrized, Serialize}; +use std::sync::Arc; + + +// Example Implementation of the CiphertextProcessor function +pub fn default_fhe_processor(fhe_inputs: &FHEInputs) -> Vec { + let params = Arc::new(BfvParameters::try_deserialize(&fhe_inputs.params).unwrap()); + + let mut sum = Ciphertext::zero(¶ms); + for ciphertext_bytes in &fhe_inputs.ciphertexts { + let ciphertext = Ciphertext::from_bytes(ciphertext_bytes, ¶ms).unwrap(); + sum += &ciphertext; + } + + sum.to_bytes() +} + +// #[cfg(test)] +// mod tests { +// use super::*; +// use compute_provider_methods::COMPUTE_PROVIDER_ELF; +// use fhe::bfv::{ +// BfvParameters, BfvParametersBuilder, Ciphertext, Encoding, Plaintext, PublicKey, SecretKey, +// }; +// use fhe_traits::{ +// DeserializeParametrized, FheDecoder, FheDecrypter, FheEncoder, FheEncrypter, Serialize, +// }; +// use rand::thread_rng; +// use std::sync::Arc; + +// #[test] +// fn test_compute_provider() { +// let params = create_params(); +// let (sk, pk) = generate_keys(¶ms); +// let inputs = vec![1, 1, 0, 1]; +// let ciphertexts = encrypt_inputs(&inputs, &pk, ¶ms); + +// let ciphertext_inputs = FHEInputs { +// ciphertexts: ciphertexts.iter().map(|c| c.to_bytes()).collect(), +// params: params.to_bytes(), +// }; + +// let mut provider = ComputeProvider::new(ciphertext_inputs, false, None); +// let (result, _seal) = provider.start(COMPUTE_PROVIDER_ELF); + +// let tally = decrypt_result(&result, &sk, ¶ms); + +// assert_eq!(tally, inputs.iter().sum::()); +// } + +// fn create_params() -> Arc { +// BfvParametersBuilder::new() +// .set_degree(1024) +// .set_plaintext_modulus(65537) +// .set_moduli(&[1152921504606584833]) +// .build_arc() +// .expect("Failed to build parameters") +// } + +// fn generate_keys(params: &Arc) -> (SecretKey, PublicKey) { +// let mut rng = thread_rng(); +// let sk = SecretKey::random(params, &mut rng); +// let pk = PublicKey::new(&sk, &mut rng); +// (sk, pk) +// } + +// fn encrypt_inputs( +// inputs: &[u64], +// pk: &PublicKey, +// params: &Arc, +// ) -> Vec { +// let mut rng = thread_rng(); +// inputs +// .iter() +// .map(|&input| { +// let pt = Plaintext::try_encode(&[input], Encoding::poly(), params) +// .expect("Failed to encode plaintext"); +// pk.try_encrypt(&pt, &mut rng).expect("Failed to encrypt") +// }) +// .collect() +// } + +// fn decrypt_result( +// result: &ComputationResult, +// sk: &SecretKey, +// params: &Arc, +// ) -> u64 { +// let ct = Ciphertext::from_bytes(&result.ciphertext, params) +// .expect("Failed to deserialize ciphertext"); +// let decrypted = sk.try_decrypt(&ct).expect("Failed to decrypt"); +// Vec::::try_decode(&decrypted, Encoding::poly()).expect("Failed to decode")[0] +// } +// } diff --git a/packages/compute_provider/core/src/merkle_tree.rs b/packages/compute_provider/src/merkle_tree.rs similarity index 100% rename from packages/compute_provider/core/src/merkle_tree.rs rename to packages/compute_provider/src/merkle_tree.rs diff --git a/packages/compute_provider/src/provider.rs b/packages/compute_provider/src/provider.rs new file mode 100644 index 0000000..34a082b --- /dev/null +++ b/packages/compute_provider/src/provider.rs @@ -0,0 +1,38 @@ +use crate::compute_input::ComputeInput; + +pub trait ComputeOutput { + fn ciphertext(&self) -> &Vec; + fn merkle_root(&self) -> String; + fn params_hash(&self) -> String; +} + +pub trait ComputeProvider { + type Output: ComputeOutput + Send + Sync; + + fn prove( + &self, + input: &ComputeInput + ) -> Self::Output; +} + + +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct ComputeResult { + pub ciphertext: Vec, + pub params_hash: String, + pub merkle_root: String, +} + +impl ComputeOutput for ComputeResult { + fn ciphertext(&self) -> &Vec { + &self.ciphertext + } + + fn merkle_root(&self) -> String { + self.merkle_root.clone() + } + + fn params_hash(&self) -> String { + self.params_hash.clone() + } +} diff --git a/packages/evm/contracts/CRISPVoting.sol b/packages/evm/contracts/CRISPVoting.sol index a64aa39..c5cd258 100644 --- a/packages/evm/contracts/CRISPVoting.sol +++ b/packages/evm/contracts/CRISPVoting.sol @@ -37,11 +37,6 @@ contract CRISPVoting { uint256 public e3Counter = 0; // Counter for E3 IDs - // function that emits bytes - function emitBytes() external { - emit E3Activated(1, 20, ""); - } - // Request a new E3 computation function request( address filter, @@ -75,14 +70,14 @@ contract CRISPVoting { } // Activate the poll - function activate(uint256 e3Id) external returns (bool success) { + function activate(uint256 e3Id, bytes calldata pubKey) external returns (bool success) { require(e3Polls[e3Id].seed > 0, "E3 ID does not exist."); require(e3Polls[e3Id].expiration == 0, "Poll already activated."); e3Polls[e3Id].expiration = block.timestamp + e3Polls[e3Id].duration; - e3Polls[e3Id].committeePublicKey = bytes("hamza"); + // e3Polls[e3Id].committeePublicKey = ; - emit E3Activated(e3Id, e3Polls[e3Id].expiration, e3Polls[e3Id].committeePublicKey); + emit E3Activated(e3Id, e3Polls[e3Id].expiration, pubKey); return true; } diff --git a/packages/risc0/Cargo.lock b/packages/risc0/Cargo.lock index fa3ff87..059b487 100644 --- a/packages/risc0/Cargo.lock +++ b/packages/risc0/Cargo.lock @@ -257,28 +257,6 @@ version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" -[[package]] -name = "apps" -version = "0.1.0" -dependencies = [ - "alloy-primitives 0.6.4", - "alloy-sol-types 0.6.4", - "anyhow", - "clap", - "compute-provider-core", - "compute-provider-host", - "env_logger", - "ethers", - "fhe", - "fhe-traits", - "fhe-util", - "log", - "methods", - "risc0-ethereum-contracts", - "risc0-zkvm", - "tokio", -] - [[package]] name = "ark-bn254" version = "0.4.0" @@ -943,7 +921,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] -name = "compute-provider-core" +name = "compute-provider" version = "0.1.0" dependencies = [ "ark-bn254", @@ -954,36 +932,12 @@ dependencies = [ "light-poseidon", "num-bigint", "num-traits", - "risc0-zkp", - "risc0-zkvm", - "serde", - "sha3", - "zk-kit-imt", -] - -[[package]] -name = "compute-provider-host" -version = "0.1.0" -dependencies = [ - "compute-provider-core", - "compute-provider-methods", - "fhe", - "fhe-traits", "rand", "rayon", - "risc0-build-ethereum", - "risc0-ethereum-contracts", - "risc0-zkvm", "serde", + "sha3", "tracing-subscriber 0.3.18", -] - -[[package]] -name = "compute-provider-methods" -version = "0.1.0" -dependencies = [ - "risc0-build", - "risc0-build-ethereum", + "zk-kit-imt", ] [[package]] @@ -4886,6 +4840,27 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "voting-risc0" +version = "0.1.0" +dependencies = [ + "alloy-primitives 0.6.4", + "alloy-sol-types 0.6.4", + "anyhow", + "clap", + "compute-provider", + "env_logger", + "ethers", + "fhe", + "fhe-traits", + "fhe-util", + "log", + "methods", + "risc0-ethereum-contracts", + "risc0-zkvm", + "tokio", +] + [[package]] name = "wait-timeout" version = "0.2.0" diff --git a/packages/risc0/Cargo.toml b/packages/risc0/Cargo.toml index 244ac95..b519d3a 100644 --- a/packages/risc0/Cargo.toml +++ b/packages/risc0/Cargo.toml @@ -26,8 +26,7 @@ serde = { version = "1.0", features = ["derive", "std"] } fhe = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } fhe-util = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } -compute-provider-host = { path = "../compute_provider/host" } -compute-provider-core = { path = "../compute_provider/core" } +compute-provider ={ path = "../compute_provider" } [profile.release] debug = 1 diff --git a/packages/risc0/apps/Cargo.toml b/packages/risc0/apps/Cargo.toml index 10f0a3b..9d309c7 100644 --- a/packages/risc0/apps/Cargo.toml +++ b/packages/risc0/apps/Cargo.toml @@ -15,8 +15,7 @@ methods = { workspace = true } risc0-ethereum-contracts = { workspace = true } risc0-zkvm = { workspace = true, features = ["client"] } tokio = { version = "1.35", features = ["full"] } -compute-provider-host = { workspace = true } -compute-provider-core = { workspace = true } +compute-provider = { workspace = true } fhe = { workspace = true } fhe-traits = { workspace = true } fhe-util = { workspace = true } \ No newline at end of file diff --git a/packages/risc0/apps/src/lib.rs b/packages/risc0/apps/src/lib.rs index 1c2abf4..1ff4cbe 100644 --- a/packages/risc0/apps/src/lib.rs +++ b/packages/risc0/apps/src/lib.rs @@ -1,15 +1,70 @@ // src/lib.rs use anyhow::Result; +use compute_provider::{ + ComputeInput, ComputeManager, ComputeOutput, ComputeProvider, ComputeResult, FHEInputs, +}; use methods::VOTING_ELF; -use compute_provider_host::ComputeProvider; -use compute_provider_core::{FHEInputs, ComputationResult}; +use risc0_ethereum_contracts::groth16; +use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts, VerifierContext}; +pub struct Risc0Provider; +pub struct Risc0Output { + pub result: ComputeResult, + pub seal: Vec, +} -pub fn run_compute(params: FHEInputs) -> Result<(ComputationResult, Vec)> { +impl ComputeOutput for Risc0Output { + fn ciphertext(&self) -> &Vec { + &self.result.ciphertext + } - let mut provider = ComputeProvider::new(params, true, None); + fn merkle_root(&self) -> String { + self.result.merkle_root.clone() + } - let (result, seal) = provider.start(VOTING_ELF); + fn params_hash(&self) -> String { + self.result.params_hash.clone() + } +} - Ok((result, seal)) -} \ No newline at end of file +impl ComputeProvider for Risc0Provider { + type Output = Risc0Output; + + fn prove(&self, input: &ComputeInput) -> Self::Output { + println!("Proving with RISC0 provider"); + let env = ExecutorEnv::builder() + .write(input) + .unwrap() + .build() + .unwrap(); + + let receipt = default_prover() + .prove_with_ctx( + env, + &VerifierContext::default(), + VOTING_ELF, + &ProverOpts::groth16(), + ) + .unwrap() + .receipt; + + let decoded_journal = receipt.journal.decode().unwrap(); + + let seal = groth16::encode(receipt.inner.groth16().unwrap().seal.clone()).unwrap(); + + Risc0Output { + result: decoded_journal, + seal, + } + } +} + +pub fn run_compute(params: FHEInputs) -> Result<(ComputeResult, Vec)> { + let risc0_provider = Risc0Provider; + + let mut provider = ComputeManager::new(risc0_provider, params, false, None); + + let output = provider.start(); + + Ok((output.result, output.seal)) +} diff --git a/packages/risc0/methods/guest/Cargo.lock b/packages/risc0/methods/guest/Cargo.lock index 275fa81..f634dd1 100644 --- a/packages/risc0/methods/guest/Cargo.lock +++ b/packages/risc0/methods/guest/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - [[package]] name = "ahash" version = "0.8.11" @@ -272,7 +257,7 @@ dependencies = [ "ark-ff 0.4.2", "ark-std 0.4.0", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.2.25", ] [[package]] @@ -363,48 +348,18 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" -[[package]] -name = "backtrace" -version = "0.3.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets", -] - [[package]] name = "base16ct" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - [[package]] name = "base64ct" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "bit-set" version = "0.5.3" @@ -456,26 +411,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "bonsai-sdk" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1553c9f015eb3fc4ff1bf2e142fceeb2256768a3c4d94a9486784a6c656484d" -dependencies = [ - "duplicate", - "maybe-async", - "reqwest", - "risc0-groth16", - "serde", - "thiserror", -] - -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - [[package]] name = "byte-slice-cast" version = "1.2.2" @@ -530,7 +465,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "compute-provider-core" +name = "compute-provider" version = "0.1.0" dependencies = [ "ark-bn254", @@ -541,10 +476,11 @@ dependencies = [ "light-poseidon", "num-bigint", "num-traits", - "risc0-zkp", - "risc0-zkvm", + "rand", + "rayon", "serde", "sha3", + "tracing-subscriber 0.3.18", "zk-kit-imt", ] @@ -582,6 +518,31 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "crunchy" version = "0.2.2" @@ -683,16 +644,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" -[[package]] -name = "duplicate" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de78e66ac9061e030587b2a2e75cc88f22304913c907b11307bca737141230cb" -dependencies = [ - "heck", - "proc-macro-error", -] - [[package]] name = "ecdsa" version = "0.16.9" @@ -751,7 +702,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -875,83 +826,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - [[package]] name = "funty" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" -[[package]] -name = "futures-channel" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" - -[[package]] -name = "futures-io" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" - -[[package]] -name = "futures-macro" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.77", -] - -[[package]] -name = "futures-sink" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" - -[[package]] -name = "futures-task" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" - -[[package]] -name = "futures-util" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" -dependencies = [ - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -974,12 +854,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "gimli" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" - [[package]] name = "group" version = "0.13.0" @@ -997,7 +871,7 @@ version = "0.1.0" dependencies = [ "alloy-primitives", "alloy-sol-types", - "compute-provider-core", + "compute-provider", "risc0-zkvm", ] @@ -1022,12 +896,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "hex" version = "0.4.3" @@ -1052,113 +920,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "http" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" -dependencies = [ - "bytes", - "futures-util", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" - -[[package]] -name = "hyper" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" -dependencies = [ - "futures-util", - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", - "webpki-roots", -] - -[[package]] -name = "hyper-util" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "pin-project-lite", - "socket2", - "tokio", - "tower", - "tower-service", - "tracing", -] - -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "impl-codec" version = "0.6.0" @@ -1189,12 +950,6 @@ dependencies = [ "hashbrown 0.14.5", ] -[[package]] -name = "ipnet" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" - [[package]] name = "itertools" version = "0.10.5" @@ -1219,15 +974,6 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" -[[package]] -name = "js-sys" -version = "0.3.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" -dependencies = [ - "wasm-bindgen", -] - [[package]] name = "k256" version = "0.13.3" @@ -1267,7 +1013,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" dependencies = [ - "spin 0.5.2", + "spin", ] [[package]] @@ -1307,24 +1053,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] -name = "matrixmultiply" -version = "0.3.9" +name = "matchers" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "autocfg", - "rawpointer", + "regex-automata 0.1.10", ] [[package]] -name = "maybe-async" -version = "0.2.10" +name = "matrixmultiply" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.77", + "autocfg", + "rawpointer", ] [[package]] @@ -1333,33 +1077,6 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "miniz_oxide" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" -dependencies = [ - "hermit-abi", - "libc", - "wasi", - "windows-sys 0.52.0", -] - [[package]] name = "multimap" version = "0.10.0" @@ -1379,6 +1096,16 @@ dependencies = [ "rawpointer", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint" version = "0.4.5" @@ -1445,21 +1172,18 @@ dependencies = [ "libm", ] -[[package]] -name = "object" -version = "0.36.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parity-scale-codec" version = "3.6.12" @@ -1492,12 +1216,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - [[package]] name = "pest" version = "2.7.10" @@ -1519,38 +1237,12 @@ dependencies = [ "indexmap", ] -[[package]] -name = "pin-project" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.77", -] - [[package]] name = "pin-project-lite" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "pkcs8" version = "0.10.2" @@ -1644,7 +1336,7 @@ dependencies = [ "rand", "rand_chacha", "rand_xorshift", - "regex-syntax", + "regex-syntax 0.8.3", "rusty-fork", "tempfile", "unarray", @@ -1668,7 +1360,7 @@ checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes", "heck", - "itertools 0.10.5", + "itertools 0.12.1", "log", "multimap", "once_cell", @@ -1688,7 +1380,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.77", @@ -1710,67 +1402,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] -name = "quinn" -version = "0.11.5" +name = "quote" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ - "bytes", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2", - "thiserror", - "tokio", - "tracing", + "proc-macro2", ] [[package]] -name = "quinn-proto" -version = "0.11.8" +name = "radium" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" -dependencies = [ - "bytes", - "rand", - "ring", - "rustc-hash", - "rustls", - "slab", - "thiserror", - "tinyvec", - "tracing", -] - -[[package]] -name = "quinn-udp" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" -dependencies = [ - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "quote" -version = "1.0.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" @@ -1817,6 +1461,26 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "regex" version = "1.10.6" @@ -1825,8 +1489,17 @@ checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.7", + "regex-syntax 0.8.3", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -1837,59 +1510,20 @@ checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.3", ] [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] -name = "reqwest" -version = "0.12.7" +name = "regex-syntax" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "ipnet", - "js-sys", - "log", - "mime", - "once_cell", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pemfile", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tokio-util", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "webpki-roots", - "windows-registry", -] +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "rfc6979" @@ -1901,21 +1535,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "ring" -version = "0.17.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" -dependencies = [ - "cc", - "cfg-if", - "getrandom", - "libc", - "spin 0.9.8", - "untrusted", - "windows-sys 0.52.0", -] - [[package]] name = "risc0-binfmt" version = "1.0.5" @@ -2018,14 +1637,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f234694d9dabc1172cf418b7a3ba65447caad15b994f450e9941d2a7cc89e045" dependencies = [ "anyhow", - "bincode", - "bonsai-sdk", "bytemuck", - "bytes", "cfg-if", "getrandom", "hex", - "prost", "risc0-binfmt", "risc0-circuit-recursion", "risc0-circuit-rv32im", @@ -2037,7 +1652,6 @@ dependencies = [ "semver 1.0.23", "serde", "sha2", - "tempfile", "tracing", ] @@ -2102,18 +1716,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f86854cf50259291520509879a5c294c3c9a4c334e9ff65071c51e42ef1e2343" -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "rustc-hash" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" - [[package]] name = "rustc-hex" version = "2.1.0" @@ -2148,48 +1750,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustls" -version = "0.23.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" -dependencies = [ - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pemfile" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" -dependencies = [ - "base64", - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" - -[[package]] -name = "rustls-webpki" -version = "0.102.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", + "windows-sys", ] [[package]] @@ -2204,12 +1765,6 @@ dependencies = [ "wait-timeout", ] -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - [[package]] name = "sec1" version = "0.7.3" @@ -2268,30 +1823,6 @@ dependencies = [ "syn 2.0.77", ] -[[package]] -name = "serde_json" -version = "1.0.128" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - [[package]] name = "sha2" version = "0.10.8" @@ -2324,22 +1855,22 @@ dependencies = [ ] [[package]] -name = "signature" -version = "2.2.0" +name = "sharded-slab" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ - "digest 0.10.7", - "rand_core", + "lazy_static", ] [[package]] -name = "slab" -version = "0.4.9" +name = "signature" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "autocfg", + "digest 0.10.7", + "rand_core", ] [[package]] @@ -2348,28 +1879,12 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" -[[package]] -name = "socket2" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - [[package]] name = "spki" version = "0.7.3" @@ -2426,15 +1941,6 @@ dependencies = [ "syn 2.0.77", ] -[[package]] -name = "sync_wrapper" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" -dependencies = [ - "futures-core", -] - [[package]] name = "tap" version = "1.0.1" @@ -2450,7 +1956,7 @@ dependencies = [ "cfg-if", "fastrand", "rustix", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -2474,66 +1980,22 @@ dependencies = [ ] [[package]] -name = "tiny-keccak" -version = "2.0.2" +name = "thread_local" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ - "crunchy", -] - -[[package]] -name = "tinyvec" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.40.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "pin-project-lite", - "socket2", - "windows-sys 0.52.0", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" -dependencies = [ - "rustls", - "rustls-pki-types", - "tokio", + "cfg-if", + "once_cell", ] [[package]] -name = "tokio-util" -version = "0.7.12" +name = "tiny-keccak" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", + "crunchy", ] [[package]] @@ -2553,33 +2015,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - [[package]] name = "tracing" version = "0.1.40" @@ -2613,6 +2048,17 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.2.25" @@ -2623,10 +2069,22 @@ dependencies = [ ] [[package]] -name = "try-lock" -version = "0.2.5" +name = "tracing-subscriber" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] [[package]] name = "typenum" @@ -2658,44 +2116,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" -[[package]] -name = "unicode-normalization" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - [[package]] name = "valuable" version = "0.1.0" @@ -2717,15 +2143,6 @@ dependencies = [ "libc", ] -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2733,133 +2150,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasm-bindgen" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" -dependencies = [ - "cfg-if", - "once_cell", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.77", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.93" +name = "winapi" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.77", - "wasm-bindgen-backend", - "wasm-bindgen-shared", + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", ] [[package]] -name = "wasm-bindgen-shared" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" - -[[package]] -name = "wasm-streams" +name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "web-sys" -version = "0.3.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" -dependencies = [ - "js-sys", - "wasm-bindgen", -] +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] -name = "webpki-roots" -version = "0.26.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "windows-registry" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" -dependencies = [ - "windows-result", - "windows-strings", - "windows-targets", -] - -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-strings" -version = "0.1.0" +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result", - "windows-targets", -] +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" @@ -2870,15 +2180,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-targets" version = "0.52.6" diff --git a/packages/risc0/methods/guest/Cargo.toml b/packages/risc0/methods/guest/Cargo.toml index f6e011b..cec5ae1 100644 --- a/packages/risc0/methods/guest/Cargo.toml +++ b/packages/risc0/methods/guest/Cargo.toml @@ -13,7 +13,7 @@ path = "src/bin/voting.rs" alloy-primitives = { version = "0.6", default-features = false, features = ["rlp", "serde", "std"] } alloy-sol-types = { version = "0.6" } risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] } -compute-provider-core = { path = "../../../compute_provider/core" } +compute-provider = { path = "../../../compute_provider" } [profile.release] lto = "thin" diff --git a/packages/risc0/methods/guest/src/bin/voting.rs b/packages/risc0/methods/guest/src/bin/voting.rs index 2c32a90..62df10b 100644 --- a/packages/risc0/methods/guest/src/bin/voting.rs +++ b/packages/risc0/methods/guest/src/bin/voting.rs @@ -1,11 +1,11 @@ use risc0_zkvm::guest::env; -use compute_provider_core::{ComputationInput, ComputationResult, default_fhe_processor}; +use compute_provider::{ComputeInput, ComputeResult, default_fhe_processor}; fn main() { - let input: ComputationInput = env::read(); + let input: ComputeInput = env::read(); - let result: ComputationResult = input.process(default_fhe_processor); + let result: ComputeResult = input.process(default_fhe_processor); env::commit(&result); } diff --git a/packages/server/Cargo.lock b/packages/server/Cargo.lock index 81aef1e..6d24c64 100644 --- a/packages/server/Cargo.lock +++ b/packages/server/Cargo.lock @@ -1901,7 +1901,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] -name = "compute-provider-core" +name = "compute-provider" version = "0.1.0" dependencies = [ "ark-bn254", @@ -1912,36 +1912,12 @@ dependencies = [ "light-poseidon", "num-bigint", "num-traits", - "risc0-zkp", - "risc0-zkvm", - "serde", - "sha3", - "zk-kit-imt", -] - -[[package]] -name = "compute-provider-host" -version = "0.1.0" -dependencies = [ - "compute-provider-core", - "compute-provider-methods", - "fhe", - "fhe-traits", "rand 0.8.5", "rayon", - "risc0-build-ethereum", - "risc0-ethereum-contracts", - "risc0-zkvm", "serde", + "sha3", "tracing-subscriber 0.3.18", -] - -[[package]] -name = "compute-provider-methods" -version = "0.1.0" -dependencies = [ - "risc0-build", - "risc0-build-ethereum", + "zk-kit-imt", ] [[package]] @@ -5430,7 +5406,7 @@ dependencies = [ "bincode", "bytes", "chrono", - "compute-provider-core", + "compute-provider", "console", "dialoguer", "env_logger 0.11.5", @@ -7149,8 +7125,7 @@ dependencies = [ "alloy-sol-types 0.6.4", "anyhow", "clap", - "compute-provider-core", - "compute-provider-host", + "compute-provider", "env_logger 0.10.2", "ethers", "fhe", diff --git a/packages/server/Cargo.toml b/packages/server/Cargo.toml index 1f98ad5..6922fa6 100644 --- a/packages/server/Cargo.toml +++ b/packages/server/Cargo.toml @@ -11,7 +11,7 @@ console = "0.15.7" fhe = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } fhe-util = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } -compute-provider-core = { path = "../compute_provider/core" } +compute-provider = { path = "../compute_provider" } voting-risc0 = { path = "../risc0/apps" } rand_chacha = "0.3.1" rand = "0.8.5" @@ -19,7 +19,7 @@ ethers = "2.0" getrandom = { version = "0.2.11", features = ["js"] } # Ethers' async features rely upon the Tokio async runtime. tokio = { version = "1.37.0", features = ["full"] } -bincode = "1.0" +bincode = "1.3.3" hyper = { version = "1", features = ["full"] } http-body-util = "0.1" hyper-util = { version = "0.1", features = ["full"] } diff --git a/packages/server/src/cli/mod.rs b/packages/server/src/cli/mod.rs index 1d3ab80..8c8d514 100644 --- a/packages/server/src/cli/mod.rs +++ b/packages/server/src/cli/mod.rs @@ -11,7 +11,7 @@ use std::io::Read; use http_body_util::Empty; use auth::{authenticate_user, AuthenticationResponse}; -use voting::{initialize_crisp_round, participate_in_existing_round}; +use voting::{initialize_crisp_round, participate_in_existing_round, activate_e3_round}; use serde::{Deserialize, Serialize}; use env_logger::{Builder, Target}; use log::LevelFilter; @@ -71,9 +71,13 @@ pub async fn run_cli() -> Result<(), Box> { match action { 0 => { - initialize_crisp_round(&config, &client_get, &client).await?; + initialize_crisp_round(&config).await?; } 1 => { + let auth_res = authenticate_user(&config, &client).await?; + activate_e3_round(&config).await?; + } + 2 => { let auth_res = authenticate_user(&config, &client).await?; participate_in_existing_round(&config, &client, &auth_res).await?; } @@ -97,7 +101,7 @@ fn select_environment() -> Result Result> { - let selections = &["Initialize new CRISP round.", "Continue Existing CRISP round."]; + let selections = &["Initialize new E3 round.", "Activate an E3 round.", "Continue Existing E3 round."]; Ok(FuzzySelect::with_theme(&ColorfulTheme::default()) .with_prompt("Create a new CRISP round or participate in an existing round.") .default(0) diff --git a/packages/server/src/cli/voting.rs b/packages/server/src/cli/voting.rs index 1b08707..8fc6638 100644 --- a/packages/server/src/cli/voting.rs +++ b/packages/server/src/cli/voting.rs @@ -1,21 +1,19 @@ -use std::{thread, time::Duration}; +use std::{env, sync::Arc}; use dialoguer::{theme::ColorfulTheme, FuzzySelect, Input}; -use http_body_util::{BodyExt, Empty}; +use http_body_util::BodyExt; use hyper::{body::Incoming, Method, Request, Response}; use serde::{Deserialize, Serialize}; -use tokio::io::{self, AsyncWriteExt}; -use log::{info, error}; -use std::env; +use log::info; use chrono::Utc; -use alloy::primitives::{Address, Bytes, U256, U32}; +use alloy::primitives::{Address, Bytes, U256}; use crate::enclave_server::blockchain::relayer::EnclaveContract; -use crate::cli::{AuthenticationResponse, HyperClientGet, HyperClientPost}; +use crate::cli::{AuthenticationResponse, HyperClientPost}; use crate::util::timeit::timeit; -use fhe::bfv::{BfvParametersBuilder, Encoding, Plaintext, PublicKey}; -use fhe_traits::{DeserializeParametrized, FheEncoder, FheEncrypter, Serialize as FheSerialize}; +use fhe::bfv::{BfvParametersBuilder, Encoding, Plaintext, PublicKey, BfvParameters, SecretKey}; +use fhe_traits::{DeserializeParametrized, Deserialize as FheDeserialize, FheEncoder, FheEncrypter, Serialize as FheSerialize}; use rand::thread_rng; #[derive(Debug, Deserialize, Serialize)] @@ -55,12 +53,12 @@ async fn get_response_body(resp: Response) -> Result Result<(), Box> { info!("Starting new CRISP round!"); info!("Initializing Keyshare nodes..."); + + let params = generate_bfv_parameters().unwrap().to_bytes(); let private_key = env::var("PRIVATE_KEY").expect("PRIVATE_KEY must be set in the environment"); let rpc_url = "http://0.0.0.0:8545"; @@ -69,27 +67,44 @@ pub async fn initialize_crisp_round( let filter: Address = "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5".parse()?; let threshold: [u32; 2] = [1, 2]; let start_window: [U256; 2] = [U256::from(Utc::now().timestamp()), U256::from(Utc::now().timestamp() + 600)]; - let duration: U256 = U256::from(10); + let duration: U256 = U256::from(40); let e3_program: Address = "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5".parse()?; - let e3_params = Bytes::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + let e3_params = Bytes::from(params); let compute_provider_params = Bytes::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); let res = contract.request_e3(filter, threshold, start_window, duration, e3_program, e3_params, compute_provider_params).await?; println!("E3 request sent. TxHash: {:?}", res.transaction_hash); - let e3_id = U256::from(3); - let res = contract.activate_e3(e3_id).await?; - println!("E3 activated. TxHash: {:?}", res.transaction_hash); + // let e3_data = Bytes::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + // let res = contract.publish_input(e3_id, e3_data).await?; + // println!("E3 data published. TxHash: {:?}", res.transaction_hash); + Ok(()) +} + +pub async fn activate_e3_round(config: &super::CrispConfig) -> Result<(), Box> { + let input_e3_id: u64 = Input::with_theme(&ColorfulTheme::default()) + .with_prompt("Enter CRISP round ID.") + .interact_text()?; + info!("Voting state Initialized"); + + let params = generate_bfv_parameters().unwrap(); + let (sk, pk) = generate_keys(¶ms); - let e3 = contract.get_e3(e3_id).await?; - println!("E3 data: {:?}", e3); + let private_key = env::var("PRIVATE_KEY").expect("PRIVATE_KEY must be set in the environment"); + let rpc_url = "http://0.0.0.0:8545"; + let contract = EnclaveContract::new(rpc_url, &config.voting_address, &private_key).await?; + + let pk_bytes = Bytes::from(pk.to_bytes()); + // Print how many bytes are in the public key + println!("Public key bytes: {:?}", pk_bytes.len()); + let e3_id = U256::from(input_e3_id); + let res = contract.activate_e3(e3_id, pk_bytes).await?; + println!("E3 activated. TxHash: {:?}", res.transaction_hash); - let e3_data = Bytes::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); - let res = contract.publish_input(e3_id, e3_data).await?; - println!("E3 data published. TxHash: {:?}", res.transaction_hash); Ok(()) } + pub async fn participate_in_existing_round( config: &super::CrispConfig, client: &HyperClientPost, @@ -173,6 +188,12 @@ fn generate_bfv_parameters() -> Result, .set_moduli(&moduli) .build_arc()?) } +fn generate_keys(params: &Arc) -> (SecretKey, PublicKey) { + let mut rng = thread_rng(); + let sk = SecretKey::random(params, &mut rng); + let pk = PublicKey::new(&sk, &mut rng); + (sk, pk) +} fn get_user_vote() -> Result, Box> { let selections = &["Abstain.", "Vote yes.", "Vote no."]; diff --git a/packages/server/src/enclave_server/blockchain/events.rs b/packages/server/src/enclave_server/blockchain/events.rs index 51ec55c..73e3004 100644 --- a/packages/server/src/enclave_server/blockchain/events.rs +++ b/packages/server/src/enclave_server/blockchain/events.rs @@ -1,5 +1,5 @@ use alloy::{ - primitives::{Address, Bytes, U256}, providers::Provider, sol, sol_types::{SolCall, SolEvent}, + sol, sol_types::{SolCall, SolEvent}, rpc::types::Log }; @@ -23,9 +23,7 @@ sol! { impl ContractEvent for E3Activated { fn process(&self, log: Log) -> Result<()> { println!("Processing E3 request: {:?}", self); - let event_clone = self.clone(); - tokio::spawn(async move { if let Err(e) = handle_e3(event_clone, log).await { eprintln!("Error handling E3 request: {:?}", e); @@ -38,24 +36,21 @@ impl ContractEvent for E3Activated { } impl ContractEvent for InputPublished { - fn process(&self, log: Log) -> Result<()> { - println!("Processing input published: {:?}", self); - // let event_clone = self.clone(); - // if let Err(e) = handle_input_published(event_clone) { - // eprintln!("Error handling input published: {:?}", e); - // } + fn process(&self, _log: Log) -> Result<()> { + let event_clone = self.clone(); + if let Err(e) = handle_input_published(event_clone) { + eprintln!("Error handling input published: {:?}", e); + } Ok(()) } } impl ContractEvent for PlaintextOutputPublished { - fn process(&self, log: Log) -> Result<()> { - println!("Processing public key published: {:?}", self); - - // let event_clone = self.clone(); - // if let Err(e) = handle_plaintext_output_published(event_clone) { - // eprintln!("Error handling public key published: {:?}", e); - // } + fn process(&self, _log: Log) -> Result<()> { + let event_clone = self.clone(); + if let Err(e) = handle_plaintext_output_published(event_clone) { + eprintln!("Error handling public key published: {:?}", e); + } Ok(()) } diff --git a/packages/server/src/enclave_server/blockchain/handlers.rs b/packages/server/src/enclave_server/blockchain/handlers.rs index 2ac3969..5c5fa60 100644 --- a/packages/server/src/enclave_server/blockchain/handlers.rs +++ b/packages/server/src/enclave_server/blockchain/handlers.rs @@ -2,29 +2,28 @@ use super::{ events::{E3Activated, InputPublished, PlaintextOutputPublished}, relayer::EnclaveContract, }; -use crate::enclave_server::database::{generate_emoji, get_e3, GLOBAL_DB}; +use crate::enclave_server::database::{generate_emoji, get_e3, GLOBAL_DB, increment_e3_round}; use crate::enclave_server::models::E3; use alloy::{ - primitives::{Address, Bytes, U256, FixedBytes}, - providers::Provider, rpc::types::Log, - sol, sol_types::{SolCall, SolEvent}, }; use chrono::Utc; -use compute_provider_core::FHEInputs; +use compute_provider::FHEInputs; use std::env; use std::error::Error; use tokio::time::{sleep, Duration}; use voting_risc0::run_compute; use alloy_sol_types::SolValue; +use log::info; + pub async fn handle_e3( e3_activated: E3Activated, log: Log, ) -> Result<(), Box> { let e3_id = e3_activated.e3Id.to::(); - println!("Handling E3 request with id {}", e3_id); + info!("Handling E3 request with id {}", e3_id); // Fetch E3 from the contract let private_key = env::var("PRIVATE_KEY").expect("PRIVATEKEY must be set in the environment"); @@ -34,8 +33,8 @@ pub async fn handle_e3( let contract = EnclaveContract::new(&rpc_url, &contract_address, &private_key).await?; let e3 = contract.get_e3(e3_activated.e3Id).await?; - println!("Fetched E3 from the contract."); - println!("E3: {:?}", e3); + info!("Fetched E3 from the contract."); + info!("E3: {:?}", e3); let start_time = Utc::now().timestamp() as u64; @@ -49,11 +48,15 @@ pub async fn handle_e3( let e3_obj = E3 { // Identifiers id: e3_id, + chain_id: 31337 as u64, // Hardcoded for testing + enclave_address: contract_address, // Status-related status: "Active".to_string(), has_voted: vec!["".to_string()], vote_count: 0, + votes_option_1: 0, + votes_option_2: 0, // Timing-related start_time, @@ -63,7 +66,7 @@ pub async fn handle_e3( // Parameters e3_params: e3.e3ProgramParams.to_vec(), - committee_public_key: e3.committeePublicKey.to_vec(), + committee_public_key: e3_activated.committeePublicKey.to_vec(), // Outputs ciphertext_output: vec![], @@ -82,12 +85,15 @@ pub async fn handle_e3( .insert(key, serde_json::to_vec(&e3_obj).unwrap()) .unwrap(); + increment_e3_round().unwrap(); + // Sleep till the E3 expires sleep(Duration::from_secs(e3.duration.to::())).await; // Get All Encrypted Votes let (e3, _) = get_e3(e3_id).unwrap(); - println!("E3 FROM DB: {:?}", e3); + info!("E3 FROM DB"); + info!("Vote Count: {:?}", e3.vote_count); let fhe_inputs = FHEInputs { params: e3.e3_params, @@ -95,31 +101,31 @@ pub async fn handle_e3( }; // Call Compute Provider - // let (compute_result, seal) = run_compute(fhe_inputs).unwrap(); - - // let data = ( - // compute_result.ciphertext, - // compute_result.merkle_root, - // seal, - // ); - - // let encoded_data = data.abi_encode(); - - // // Params will be encoded on chain to create the journal - // let tx = contract - // .publish_ciphertext_output(e3_activated.e3Id, encoded_data.into()) - // .await?; - - // println!( - // "CiphertextOutputPublished event published with tx: {:?}", - // tx - // ); - println!("E3 request handled successfully."); + let (compute_result, seal) = run_compute(fhe_inputs).unwrap(); + + let data = ( + compute_result.ciphertext, + compute_result.merkle_root, + seal, + ); + + let encoded_data = data.abi_encode(); + + // Params will be encoded on chain to create the journal + let tx = contract + .publish_ciphertext_output(e3_activated.e3Id, encoded_data.into()) + .await?; + + info!( + "CiphertextOutputPublished event published with tx: {:?}", + tx + ); + info!("E3 request handled successfully."); Ok(()) } pub fn handle_input_published(input: InputPublished) -> Result<(), Box> { - println!("Handling VoteCast event..."); + info!("Handling VoteCast event..."); let e3_id = input.e3Id.to::(); let data = input.data.to_vec(); @@ -131,14 +137,14 @@ pub fn handle_input_published(input: InputPublished) -> Result<(), Box Result<(), Box> { - println!("Handling PlaintextOutputPublished event..."); + info!("Handling PlaintextOutputPublished event..."); let e3_id = plaintext_output.e3Id.to::(); let (mut e3, key) = get_e3(e3_id).unwrap(); @@ -147,6 +153,6 @@ pub fn handle_plaintext_output_published( GLOBAL_DB .insert(key, serde_json::to_vec(&e3).unwrap()) .unwrap(); - println!("PlaintextOutputPublished event handled."); + info!("PlaintextOutputPublished event handled."); Ok(()) } diff --git a/packages/server/src/enclave_server/blockchain/listener.rs b/packages/server/src/enclave_server/blockchain/listener.rs index 8895ccb..d2d1d3a 100644 --- a/packages/server/src/enclave_server/blockchain/listener.rs +++ b/packages/server/src/enclave_server/blockchain/listener.rs @@ -1,16 +1,17 @@ use alloy::{ - primitives::{address, Address, B256}, + primitives::{Address, B256}, providers::{Provider, ProviderBuilder, RootProvider}, rpc::types::{BlockNumberOrTag, Filter, Log}, - sol, sol_types::SolEvent, transports::BoxTransport, }; use eyre::Result; use futures_util::stream::StreamExt; use std::collections::HashMap; -use std::fmt::Debug; use std::sync::Arc; +use std::time::Duration; +use tokio::time::sleep; +use log::{info, error}; use super::events::{E3Activated, InputPublished, PlaintextOutputPublished}; @@ -101,21 +102,43 @@ impl ContractManager { EventListener::new(self.provider.clone(), filter) } } - pub async fn start_listener(contract_address: &str) -> Result<()> { - println!("Starting listener for contract: {}", contract_address); let rpc_url = "ws://127.0.0.1:8545"; + let address: Address = contract_address.parse()?; + + loop { + match run_listener(rpc_url, address).await { + Ok(_) => { + info!("Listener finished successfully. Checking for reconnection..."); + }, + Err(e) => { + error!("Error occurred in listener: {}. Reconnecting after delay...", e); + } + } + sleep(Duration::from_secs(5)).await; + } +} +// Separate function to encapsulate listener logic +async fn run_listener(rpc_url: &str, contract_address: Address) -> Result<()> { let manager = ContractManager::new(rpc_url).await?; - - let address: Address = contract_address.parse()?; - let mut listener = manager.add_listener(address); + + let mut listener = manager.add_listener(contract_address); listener.add_event_handler::(); listener.add_event_handler::(); listener.add_event_handler::(); - // Start listening - listener.listen().await?; - + loop { + match listener.listen().await { + Ok(_) => { + info!("Listener is still active..."); + } + Err(e) => { + error!("Connection lost or error occurred: {}. Attempting to reconnect...", e); + break; + } + } + } + Ok(()) } diff --git a/packages/server/src/enclave_server/blockchain/relayer.rs b/packages/server/src/enclave_server/blockchain/relayer.rs index 5edb134..b4c9c81 100644 --- a/packages/server/src/enclave_server/blockchain/relayer.rs +++ b/packages/server/src/enclave_server/blockchain/relayer.rs @@ -1,21 +1,17 @@ use alloy::{ network::{Ethereum, EthereumWallet}, - primitives::{address, Address, Bytes, U256}, + primitives::{Address, Bytes, U256}, providers::fillers::{ ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, WalletFiller, }, providers::{Identity, Provider, ProviderBuilder, RootProvider}, - rpc::types::Filter, rpc::types::TransactionReceipt, signers::local::PrivateKeySigner, sol, - sol_types::SolCall, transports::BoxTransport, }; use eyre::Result; -use log::info; use std::sync::Arc; -use tokio::sync::Mutex; sol! { #[derive(Debug)] @@ -39,7 +35,7 @@ sol! { contract Enclave { function request(address filter, uint32[2] calldata threshold, uint256[2] calldata startWindow, uint256 duration, address e3Program, bytes memory e3ProgramParams, bytes memory computeProviderParams) external payable returns (uint256 e3Id, E3 memory e3); - function activate(uint256 e3Id) external returns (bool success); + function activate(uint256 e3Id, bytes memory pubKey) external returns (bool success); function publishInput(uint256 e3Id, bytes memory data ) external returns (bool success); @@ -64,7 +60,6 @@ type CRISPProvider = FillProvider< pub struct EnclaveContract { provider: Arc, contract_address: Address, - wallet: PrivateKeySigner, } impl EnclaveContract { @@ -80,7 +75,6 @@ impl EnclaveContract { Ok(Self { provider: Arc::new(provider), contract_address: contract_address.parse()?, - wallet: signer, }) } @@ -108,9 +102,9 @@ impl EnclaveContract { Ok(receipt) } - pub async fn activate_e3(&self, e3_id: U256) -> Result { + pub async fn activate_e3(&self, e3_id: U256, pub_key: Bytes) -> Result { let contract = Enclave::new(self.contract_address, &self.provider); - let builder = contract.activate(e3_id); + let builder = contract.activate(e3_id, pub_key); let receipt = builder.send().await?.get_receipt().await?; Ok(receipt) } diff --git a/packages/server/src/enclave_server/database.rs b/packages/server/src/enclave_server/database.rs index 6677ec0..71b7925 100644 --- a/packages/server/src/enclave_server/database.rs +++ b/packages/server/src/enclave_server/database.rs @@ -1,22 +1,21 @@ -use std::{env, str, sync::Arc, error::Error}; +use std::{str, sync::Arc, error::Error}; use once_cell::sync::Lazy; -use sled::Db; +use sled::{Db, IVec}; use rand::Rng; use log::info; -use super::models::{CrispConfig, Round, E3}; +use super::models::{Round, E3}; pub static GLOBAL_DB: Lazy> = Lazy::new(|| { let pathdb = std::env::current_dir().unwrap().join("database/enclave_server"); Arc::new(sled::open(pathdb).unwrap()) }); - pub fn get_e3(e3_id: u64) -> Result<(E3, String), Box> { let key = format!("e3:{}", e3_id); let value = match GLOBAL_DB.get(key.clone()) { Ok(Some(v)) => v, - Ok(None) => return Err("E3 not found".into()), + Ok(None) => return Err(format!("E3 not found: {}", key).into()), Err(e) => return Err(format!("Database error: {}", e).into()), }; @@ -26,6 +25,56 @@ pub fn get_e3(e3_id: u64) -> Result<(E3, String), Box> { Ok((e3, key)) } +pub fn get_e3_round() -> Result> { + let key = "e3:round"; + + let round_count: u64 = match GLOBAL_DB.get(key) { + Ok(Some(bytes)) => { + match bincode::deserialize::(&bytes) { + Ok(count) => count, + Err(e) => { + info!("Failed to deserialize round count: {}", e); + return Err(format!("Failed to retrieve round count").into()); + } + } + } + Ok(None) => { + info!("Initializing first round in db"); + let initial_count = 0u64; + let encoded = bincode::serialize(&initial_count).unwrap(); + if let Err(e) = GLOBAL_DB.insert(key, IVec::from(encoded)) { + info!("Failed to initialize first round in db: {}", e); + return Err(format!("Failed to initialize round count").into()); + } + initial_count + } + Err(e) => { + info!("Database error: {}", e); + return Err(format!("Database error").into()); + } + }; + + Ok(round_count) +} + + +pub fn increment_e3_round() -> Result<(), Box> { + let key = "e3:round"; + + match get_e3_round() { + Ok(round_count) => { + let new_round_count = round_count + 1; + let encoded = bincode::serialize(&new_round_count).unwrap(); + GLOBAL_DB.insert(key, IVec::from(encoded))?; + } + Err(e) => { + return Err(e); + } + } + + Ok(()) +} + pub fn get_state(round_id: u32) -> (Round, String) { let mut round_key = round_id.to_string(); round_key.push_str("-storage"); diff --git a/packages/server/src/enclave_server/mod.rs b/packages/server/src/enclave_server/mod.rs index 801297f..975489a 100644 --- a/packages/server/src/enclave_server/mod.rs +++ b/packages/server/src/enclave_server/mod.rs @@ -4,18 +4,13 @@ mod routes; pub mod blockchain; use actix_cors::Cors; -use actix_web::{web, App, HttpResponse, HttpServer, Responder}; - -use tokio::task; -use std::sync::Mutex; -use sled::Db; +use actix_web::{web, App, HttpServer}; use models::AppState; use database::GLOBAL_DB; use blockchain::listener::start_listener; use env_logger::{Builder, Target}; -use log::info; use log::LevelFilter; use std::io::Write; diff --git a/packages/server/src/enclave_server/models.rs b/packages/server/src/enclave_server/models.rs index cd0d229..c827b0b 100644 --- a/packages/server/src/enclave_server/models.rs +++ b/packages/server/src/enclave_server/models.rs @@ -1,6 +1,4 @@ use ethers::types::U64; -// use alloy::primitives::U64; -use alloy_primitives::U256; use serde::{Deserialize, Serialize}; use std::sync::Arc; use sled::Db; @@ -136,13 +134,13 @@ pub struct ReportTallyRequest { #[derive(Debug, Deserialize, Serialize)] pub struct WebResultRequest { - pub round_id: u32, - pub option_1_tally: u32, - pub option_2_tally: u32, - pub total_votes: u32, + pub round_id: u64, + pub option_1_tally: u64, + pub option_2_tally: u64, + pub total_votes: u64, pub option_1_emoji: String, pub option_2_emoji: String, - pub end_time: i64, + pub end_time: u64, } #[derive(Debug, Deserialize, Serialize)] @@ -211,11 +209,15 @@ pub struct Round { pub struct E3 { // Identifiers pub id: u64, + pub chain_id: u64, + pub enclave_address: String, // Status-related pub status: String, - pub vote_count: u64, pub has_voted: Vec, + pub vote_count: u64, + pub votes_option_1: u64, + pub votes_option_2: u64, // Timing-related pub start_time: u64, @@ -238,6 +240,23 @@ pub struct E3 { pub emojis: [String; 2], } +#[derive(Debug, Deserialize, Serialize)] +pub struct E3StateLite { + pub id: u64, + pub chain_id: u64, + pub enclave_address: String, + + pub status: String, + pub vote_count: u64, + + pub start_time: u64, + pub duration: u64, + pub expiration: u64, + + pub committee_public_key: Vec, + pub emojis: [String; 2], +} + #[derive(Debug, Deserialize, Serialize)] pub struct Ciphernode { pub id: u32, diff --git a/packages/server/src/enclave_server/routes/auth.rs b/packages/server/src/enclave_server/routes/auth.rs index 36d9335..854c48c 100644 --- a/packages/server/src/enclave_server/routes/auth.rs +++ b/packages/server/src/enclave_server/routes/auth.rs @@ -1,4 +1,3 @@ -use std::io::Read; use jwt::SignWithKey; use sha2::Sha256; use std::collections::BTreeMap; @@ -7,8 +6,7 @@ use log::info; use actix_web::{web, HttpResponse, Responder}; -use crate::enclave_server::models::{JsonResponse, AppState, AuthenticationLogin, AuthenticationDB, AuthenticationResponse}; -use crate::enclave_server::database::{GLOBAL_DB, pick_response}; +use crate::enclave_server::models::{AppState, AuthenticationLogin, AuthenticationDB, AuthenticationResponse}; pub fn setup_routes(config: &mut web::ServiceConfig) { config @@ -30,7 +28,7 @@ async fn authentication_login(state: web::Data, data: web::Json(&existing).unwrap()) .unwrap_or_else(|| AuthenticationDB { jwt_tokens: Vec::new() }); diff --git a/packages/server/src/enclave_server/routes/ciphernode.rs b/packages/server/src/enclave_server/routes/ciphernode.rs deleted file mode 100644 index 2642824..0000000 --- a/packages/server/src/enclave_server/routes/ciphernode.rs +++ /dev/null @@ -1,282 +0,0 @@ -use std::str; -use chrono::Utc; -use fhe::{ - bfv::{BfvParametersBuilder, PublicKey}, - mbfv::{AggregateIter, CommonRandomPoly, PublicKeyShare}, -}; -use fhe_traits::Serialize as FheSerialize; -use crate::util::timeit::timeit; -use actix_web::{web, HttpResponse, Responder}; -use std::io::Read; -use log::info; - -use crate::enclave_server::models::{Round, AppState, Ciphernode, JsonResponse, JsonRequest, RegisterNodeResponse, SKSShareRequest, SKSSharePoll, SKSShareResponse, PKShareCount, PKRequest, GetCiphernode, GetEligibilityRequest, CRPRequest}; -use crate::enclave_server::database::{get_state, pick_response}; - -pub fn setup_routes(config: &mut web::ServiceConfig) { - config - .route("/register_ciphernode", web::post().to(register_ciphernode)) - .route("/get_pk_share_count", web::post().to(get_pk_share_count)) - .route("/get_pk_by_round", web::post().to(get_pk_by_round)) - .route("/register_sks_share", web::post().to(register_sks_share)) - .route("/get_sks_shares", web::post().to(get_sks_shares)) - .route("/get_crp_by_round", web::post().to(get_crp_by_round)) - .route("/get_node_by_round", web::post().to(get_node_by_round)) - .route("/get_round_eligibility", web::post().to(get_round_eligibility)); -} - -async fn register_ciphernode( - state: web::Data, - data: web::Json, -) -> impl Responder { - let incoming = data.into_inner(); - info!("{:?}", incoming.response); - info!("ID: {:?}", incoming.id); - info!("Round ID: {:?}", incoming.round_id); - - let (mut state_data, key) = get_state(incoming.round_id); // Use shared DB - - state_data.pk_share_count += 1; - state_data.ciphernode_count += 1; - - let cnode = Ciphernode { - id: incoming.id, - pk_share: incoming.pk_share, - sks_share: vec![0], - }; - state_data.ciphernodes.push(cnode); - let state_str = serde_json::to_string(&state_data).unwrap(); - state.db.insert(key, state_str.into_bytes()).unwrap(); - - info!("pk share store for node id {:?}", incoming.id); - info!("ciphernode count {:?}", state_data.ciphernode_count); - info!("ciphernode total {:?}", state_data.ciphernode_total); - info!("pk share count {:?}", state_data.pk_share_count); - - // Trigger aggregate_pk_shares when all shares are received - if state_data.ciphernode_count == state_data.ciphernode_total { - info!("All shares received"); - let _ = aggregate_pk_shares(incoming.round_id, state.clone()).await; // Share state in aggregation - } - - let response = RegisterNodeResponse { - response: "Node Registered".to_string(), - node_index: state_data.ciphernode_count, - }; - - HttpResponse::Ok().json(response) -} - -// Register SKS Share -async fn register_sks_share( - state: web::Data, - data: web::Json, -) -> impl Responder { - let incoming = data.into_inner(); - info!("{:?}", incoming.response); - info!("Index: {:?}", incoming.index); - info!("Round ID: {:?}", incoming.round_id); - - let mut round_key = format!("{}-storage", incoming.round_id); - info!("Database key is {:?}", round_key); - - let state_out = state.db.get(&round_key).unwrap().unwrap(); - let state_out_str = str::from_utf8(&state_out).unwrap(); - let mut state_out_struct: Round = serde_json::from_str(&state_out_str).unwrap(); - - state_out_struct.sks_share_count += 1; - let index = incoming.index; - state_out_struct.ciphernodes[index as usize].sks_share = incoming.sks_share; - - let state_str = serde_json::to_string(&state_out_struct).unwrap(); - state.db.insert(round_key, state_str.into_bytes()).unwrap(); - info!("sks share stored for node index {:?}", incoming.index); - - // Check if all SKS shares have been received - if state_out_struct.sks_share_count == state_out_struct.ciphernode_total { - info!("All sks shares received"); - // TODO: Trigger aggregate_pk_shares or notify cipher nodes - } - - HttpResponse::Ok().json(JsonResponse { response: pick_response() }) -} - -// Get SKS Shares -async fn get_sks_shares( - state: web::Data, - data: web::Json, -) -> impl Responder { - let incoming = data.into_inner(); - let (mut state_data, key) = get_state(incoming.round_id); - let mut shares = Vec::with_capacity(incoming.ciphernode_count as usize); - - // Check if all SKS shares have been received - if state_data.sks_share_count == state_data.ciphernode_total { - info!("All sks shares received... sending to cipher nodes"); - - for i in 1..=state_data.ciphernode_total { - shares.push(state_data.ciphernodes[i as usize].sks_share.clone()); - } - - let response = SKSShareResponse { - response: "final".to_string(), - round_id: incoming.round_id, - sks_shares: shares, - }; - state_data.status = "Finalized".to_string(); - state.db.insert(key, serde_json::to_string(&state_data).unwrap().into_bytes()).unwrap(); - HttpResponse::Ok().json(response) - } else { - let response = SKSShareResponse { - response: "waiting".to_string(), - round_id: incoming.round_id, - sks_shares: shares, - }; - HttpResponse::Ok().json(response) - } -} - -// Get CRP by Round -async fn get_crp_by_round( - data: web::Json, -) -> impl Responder { - let mut incoming = data.into_inner(); - info!("Request crp for round {:?}", incoming.round_id); - - let (state_data, _) = get_state(incoming.round_id); - incoming.crp_bytes = state_data.crp; - - HttpResponse::Ok().json(incoming) -} - -// Get PK by Round -async fn get_pk_by_round( - data: web::Json, -) -> impl Responder { - let mut incoming = data.into_inner(); - let (state_data, _) = get_state(incoming.round_id); - incoming.pk_bytes = state_data.pk; - info!("Request for round {:?} public key", incoming.round_id); - - HttpResponse::Ok().json(incoming) -} - -// Get PK Share Count -async fn get_pk_share_count( - data: web::Json, -) -> impl Responder { - let mut incoming = data.into_inner(); - let (state_data, _) = get_state(incoming.round_id); - incoming.share_id = state_data.pk_share_count; - - HttpResponse::Ok().json(incoming) -} - -// Get Round Eligibility -async fn get_round_eligibility(data: web::Json) -> impl Responder { - let mut request = data.into_inner(); - info!("Checking node eligibility for round {}", request.round_id); - - let (state_data, _) = get_state(request.round_id); - let timestamp = Utc::now().timestamp(); - - if timestamp >= (state_data.start_time + state_data.poll_length as i64) { - request.is_eligible = false; - request.reason = "Waiting For New Round".to_string(); - return HttpResponse::Ok().json(request); - } - - if let Some(_) = state_data.ciphernodes.iter().find(|&node| node.id == request.node_id) { - request.is_eligible = true; - request.reason = "Previously Registered".to_string(); - } else if state_data.ciphernode_total == state_data.ciphernode_count { - request.is_eligible = false; - request.reason = "Round Full".to_string(); - } else if state_data.ciphernode_total > state_data.ciphernode_count { - request.is_eligible = true; - request.reason = "Open Node Spot".to_string(); - } - - HttpResponse::Ok().json(request) -} - -// Get Node by Round -async fn get_node_by_round( - data: web::Json, -) -> impl Responder { - let incoming = data.into_inner(); - info!("Request node data for round {:?}", incoming.round_id); - - let (state_data, _) = get_state(incoming.round_id); - let mut cnode = Ciphernode { - id: 0, - pk_share: vec![0], - sks_share: vec![0], - }; - - for i in 0..state_data.ciphernodes.len() { - if state_data.ciphernodes[i].id == incoming.ciphernode_id { - cnode.id = state_data.ciphernodes[i].id; - cnode.pk_share = state_data.ciphernodes[i].pk_share.clone(); - cnode.sks_share = state_data.ciphernodes[i].sks_share.clone(); - } - } - - if cnode.id != 0 { - HttpResponse::Ok().json(cnode) - } else { - HttpResponse::Ok().json(JsonResponse { - response: "Ciphernode Not Registered".to_string(), - }) - } -} - -// Aggregate PK Shares (async) -async fn aggregate_pk_shares( - round_id: u32, - state: web::Data, // Access shared state -) -> Result<(), Box> { - info!("aggregating validator keyshare"); - - let degree = 4096; - let plaintext_modulus: u64 = 4096; - let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; - - // Generate BFV parameters - let params = timeit!( - "Parameters generation", - BfvParametersBuilder::new() - .set_degree(degree) - .set_plaintext_modulus(plaintext_modulus) - .set_moduli(&moduli) - .build_arc()? - ); - - let (mut state_data, round_key) = get_state(round_id); - - let crp = CommonRandomPoly::deserialize(&state_data.crp, ¶ms)?; - - struct Party { - pk_share: PublicKeyShare, - } - - let mut parties: Vec = Vec::new(); - for i in 1..=state_data.ciphernode_total { - info!("Aggregating PKShare... id {}", i); - let data_des = PublicKeyShare::deserialize(&state_data.ciphernodes[i as usize].pk_share, ¶ms, crp.clone()).unwrap(); - parties.push(Party { pk_share: data_des }); - } - - let pk = timeit!("Public key aggregation", { - let pk: PublicKey = parties.iter().map(|p| p.pk_share.clone()).aggregate()?; - pk - }); - - info!("Multiparty Public Key Generated"); - state_data.pk = pk.to_bytes(); - - state.db.insert(round_key, serde_json::to_string(&state_data).unwrap().into_bytes()).unwrap(); - info!("aggregate pk stored for round {:?}", round_id); - - Ok(()) -} \ No newline at end of file diff --git a/packages/server/src/enclave_server/routes/index.rs b/packages/server/src/enclave_server/routes/index.rs index ecb5277..09e8c10 100644 --- a/packages/server/src/enclave_server/routes/index.rs +++ b/packages/server/src/enclave_server/routes/index.rs @@ -1,11 +1,3 @@ -use std::str; -use std::io::Read; -use jwt::SignWithKey; -use sha2::Sha256; -use std::collections::BTreeMap; -use hmac::{Hmac, Mac}; -use log::info; - use actix_web::{web, HttpResponse, Responder}; use crate::enclave_server::models::JsonResponse; diff --git a/packages/server/src/enclave_server/routes/mod.rs b/packages/server/src/enclave_server/routes/mod.rs index 45426a0..8f5b2b9 100644 --- a/packages/server/src/enclave_server/routes/mod.rs +++ b/packages/server/src/enclave_server/routes/mod.rs @@ -3,9 +3,8 @@ mod auth; mod state; mod voting; mod rounds; -mod ciphernode; -use actix_web::{web, HttpResponse, Responder}; +use actix_web::web; pub fn setup_routes(config: &mut web::ServiceConfig) { index::setup_routes(config); @@ -13,6 +12,4 @@ pub fn setup_routes(config: &mut web::ServiceConfig) { state::setup_routes(config); voting::setup_routes(config); rounds::setup_routes(config); - ciphernode::setup_routes(config); - } \ No newline at end of file diff --git a/packages/server/src/enclave_server/routes/rounds.rs b/packages/server/src/enclave_server/routes/rounds.rs index 76e6ff0..1cc0fde 100644 --- a/packages/server/src/enclave_server/routes/rounds.rs +++ b/packages/server/src/enclave_server/routes/rounds.rs @@ -1,11 +1,10 @@ use chrono::Utc; use fhe::{bfv::BfvParametersBuilder, mbfv::CommonRandomPoly}; use fhe_traits::Serialize; -use iron::status; -use rand::thread_rng; -use std::env; -use std::io::Read; use log::info; +use rand::thread_rng; +use sled::IVec; +use bincode; use actix_web::{web, HttpResponse, Responder}; @@ -16,164 +15,36 @@ use ethers::{ use crate::util::timeit::timeit; -use crate::enclave_server::database::{generate_emoji, get_state, GLOBAL_DB}; +use crate::enclave_server::database::{generate_emoji, get_state, get_e3_round}; use crate::enclave_server::models::{ - Ciphernode, CrispConfig, JsonResponse, PollLengthRequest, ReportTallyRequest, Round, - RoundCount, TimestampRequest, AppState + AppState, Ciphernode, CrispConfig, JsonResponse, PollLengthRequest, ReportTallyRequest, Round, + RoundCount, TimestampRequest, }; pub fn setup_routes(config: &mut web::ServiceConfig) { config .route("/get_rounds", web::get().to(get_rounds)) - .route("/init_crisp_round", web::post().to(init_crisp_round)) - .route("/get_start_time_by_round", web::post().to(get_start_time_by_round)) - .route("/get_poll_length_by_round", web::post().to(get_poll_length_by_round)) .route("/report_tally", web::post().to(report_tally)); } -async fn get_rounds(state: web::Data) -> impl Responder { - let key = "round_count"; - let mut round = state.db.get(key).unwrap(); - if round.is_none() { - info!("initializing first round in db"); - state.db.insert(key, b"0".to_vec()).unwrap(); - round = state.db.get(key).unwrap(); +async fn get_rounds()-> impl Responder { + match get_e3_round() { + Ok(round_count) => { + let count = RoundCount { round_count: round_count as u32 }; + info!("round_count: {}", count.round_count); + HttpResponse::Ok().json(count) + } + Err(e) => { + info!("Failed to retrieve round count: {}", e); + HttpResponse::InternalServerError().body(format!("Error: {}", e)) + } } - - let round_key = std::str::from_utf8(round.unwrap().as_ref()).unwrap().to_string(); - let round_int = round_key.parse::().unwrap(); - - let count = RoundCount { - round_count: round_int, - }; - - info!("round_count: {:?}", count.round_count); - - HttpResponse::Ok().json(count) -} - -// Initialize CRISP Round Handler -async fn init_crisp_round( - data: web::Json, - state: web::Data, // Access shared state -) -> impl Responder { - info!("generating round crp"); - - let rpc_url = "http://0.0.0.0:8545".to_string(); - let provider = Provider::::try_from(rpc_url).unwrap(); - let block_number: U64 = provider.get_block_number().await.unwrap(); - - let degree = 4096; - let plaintext_modulus: u64 = 4096; - let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; - - // Let's generate the BFV parameters structure. - let params = timeit!( - "Parameters generation", - BfvParametersBuilder::new() - .set_degree(degree) - .set_plaintext_modulus(plaintext_modulus) - .set_moduli(&moduli) - .build_arc() - .unwrap() - ); - let crp = CommonRandomPoly::new(¶ms, &mut thread_rng()).unwrap(); - let crp_bytes = crp.to_bytes(); - - let incoming = data.into_inner(); - info!("ID: {:?}", incoming.round_id); // TODO: Check that client sent the expected next round_id - info!("Address: {:?}", incoming.voting_address); - - // Initialize or increment round count - let key = "round_count"; - let round = state.db.get(key).unwrap(); - if round.is_none() { - info!("initializing first round in db"); - state.db.insert(key, b"0".to_vec()).unwrap(); - } - - let round_key = std::str::from_utf8(round.unwrap().as_ref()).unwrap().to_string(); - let mut round_int = round_key.parse::().unwrap(); - round_int += 1; - - let inc_round_key = format!("{}-storage", round_int); - info!("Database key is {:?} and round int is {:?}", inc_round_key, round_int); - - let init_time = Utc::now(); - let timestamp = init_time.timestamp(); - info!("timestamp {:?}", timestamp); - - let (emoji1, emoji2) = generate_emoji(); - - let state_data = Round { - id: round_int, - status: "Active".to_string(), - poll_length: incoming.poll_length, - voting_address: incoming.voting_address, - chain_id: incoming.chain_id, - ciphernode_count: 0, - pk_share_count: 0, - sks_share_count: 0, - vote_count: 0, - crp: crp_bytes, - pk: vec![0], - start_time: timestamp, - block_start: block_number, - ciphernode_total: incoming.ciphernode_count, - emojis: [emoji1, emoji2], - votes_option_1: 0, - votes_option_2: 0, - ciphernodes: vec![Ciphernode { - id: 0, - pk_share: vec![0], - sks_share: vec![0], - }], - has_voted: vec!["".to_string()], - }; - - let state_str = serde_json::to_string(&state_data).unwrap(); - state.db.insert(inc_round_key, state_str.into_bytes()).unwrap(); - - let new_round_bytes = round_int.to_string().into_bytes(); - state.db.insert(key, new_round_bytes).unwrap(); - - let response = JsonResponse { - response: "CRISP Initiated".to_string(), - }; - - HttpResponse::Ok().json(response) -} - -// Get Start Time by Round Handler -async fn get_start_time_by_round( - data: web::Json, -) -> impl Responder { - let mut incoming = data.into_inner(); - info!("Request start time for round {:?}", incoming.round_id); - - let (state_data, _) = get_state(incoming.round_id); - incoming.timestamp = state_data.start_time; - - HttpResponse::Ok().json(incoming) -} - -// Get Poll Length by Round Handler -async fn get_poll_length_by_round( - data: web::Json, -) -> impl Responder { - let mut incoming = data.into_inner(); - info!("Request poll length for round {:?}", incoming.round_id); - - let (state_data, _) = get_state(incoming.round_id); - incoming.poll_length = state_data.poll_length; - - HttpResponse::Ok().json(incoming) } // Report Tally Handler async fn report_tally( data: web::Json, - state: web::Data, + state: web::Data, ) -> impl Responder { let incoming = data.into_inner(); info!("Request report tally for round {:?}", incoming.round_id); @@ -193,4 +64,4 @@ async fn report_tally( }; HttpResponse::Ok().json(response) -} \ No newline at end of file +} diff --git a/packages/server/src/enclave_server/routes/state.rs b/packages/server/src/enclave_server/routes/state.rs index e41da79..ca7fb6e 100644 --- a/packages/server/src/enclave_server/routes/state.rs +++ b/packages/server/src/enclave_server/routes/state.rs @@ -1,8 +1,8 @@ use actix_web::{web, HttpResponse, Responder}; use log::info; -use crate::enclave_server::models::{GetRoundRequest, WebResultRequest, AllWebStates, StateLite, StateWeb}; -use crate::enclave_server::database::{get_state, get_round_count}; +use crate::enclave_server::models::{AllWebStates, E3StateLite, GetRoundRequest, StateWeb, WebResultRequest}; +use crate::enclave_server::database::{get_e3,get_e3_round, get_round_count, get_state}; pub fn setup_routes(config: &mut web::ServiceConfig) { config @@ -17,16 +17,16 @@ async fn get_web_result(data: web::Json) -> impl Responder { let incoming = data.into_inner(); info!("Request web state for round {}", incoming.round_id); - let (state, _key) = get_state(incoming.round_id); + let (state, _key) = get_e3(incoming.round_id as u64).unwrap(); let response = WebResultRequest { - round_id: incoming.round_id, + round_id: state.id, option_1_tally: state.votes_option_1, option_2_tally: state.votes_option_2, total_votes: state.votes_option_1 + state.votes_option_2, option_1_emoji: state.emojis[0].clone(), option_2_emoji: state.emojis[1].clone(), - end_time: state.start_time + state.poll_length as i64, + end_time: state.expiration, }; HttpResponse::Ok().json(response) @@ -35,10 +35,10 @@ async fn get_web_result(data: web::Json) -> impl Responder { async fn get_web_result_all() -> impl Responder { info!("Request all web state."); - let round_count = get_round_count(); + let round_count = get_e3_round().unwrap(); let states: Vec = (1..round_count) .map(|i| { - let (state, _key) = get_state(i); + let (state, _key) = get_e3(i).unwrap(); WebResultRequest { round_id: i, option_1_tally: state.votes_option_1, @@ -46,7 +46,7 @@ async fn get_web_result_all() -> impl Responder { total_votes: state.votes_option_1 + state.votes_option_2, option_1_emoji: state.emojis[0].clone(), option_2_emoji: state.emojis[1].clone(), - end_time: state.start_time + state.poll_length as i64, + end_time: state.expiration, } }) .collect(); @@ -90,24 +90,28 @@ async fn get_round_state_lite(data: web::Json) -> impl Responde let incoming = data.into_inner(); info!("Request state for round {}", incoming.round_id); - let (state, _key) = get_state(incoming.round_id); - let state_lite = StateLite { - id: state.id, - status: state.status, - poll_length: state.poll_length, - voting_address: state.voting_address, - chain_id: state.chain_id, - ciphernode_count: state.ciphernode_count, - pk_share_count: state.pk_share_count, - sks_share_count: state.sks_share_count, - vote_count: state.vote_count, - crp: state.crp, - pk: state.pk, - start_time: state.start_time, - block_start: state.block_start, - ciphernode_total: state.ciphernode_total, - emojis: state.emojis, - }; - - HttpResponse::Ok().json(state_lite) -} \ No newline at end of file + match get_e3(incoming.round_id as u64) { + Ok((state, key)) => { + let state_lite = E3StateLite { + id: state.id, + chain_id: state.chain_id, + enclave_address: state.enclave_address, + + status: state.status, + vote_count: state.vote_count, + + start_time: state.start_time, + duration: state.duration, + expiration: state.expiration, + + committee_public_key: state.committee_public_key, + emojis: state.emojis + }; + HttpResponse::Ok().json(state_lite) + }, + Err(e) => { + info!("Error getting E3 state: {:?}", e); + HttpResponse::InternalServerError().body("Failed to get E3 state") + } + } +} diff --git a/packages/server/src/enclave_server/routes/voting.rs b/packages/server/src/enclave_server/routes/voting.rs index f9b396c..cffe750 100644 --- a/packages/server/src/enclave_server/routes/voting.rs +++ b/packages/server/src/enclave_server/routes/voting.rs @@ -1,26 +1,30 @@ - -use std::{env, sync::Arc, str}; -use std::io::Read; use alloy::{ network::{AnyNetwork, EthereumWallet}, - primitives::{Address, Bytes, U256, B256}, + primitives::{Address, Bytes, B256, U256}, providers::ProviderBuilder, signers::local::PrivateKeySigner, sol, }; +use std::{env, str}; use eyre::Result; use log::info; use actix_web::{web, HttpResponse, Responder}; -use crate::enclave_server::models::{EncryptedVote, JsonResponseTxHash, AppState, GetEmojisRequest, VoteCountRequest}; -use crate::enclave_server::database::{GLOBAL_DB, get_state}; +use crate::enclave_server::database::{get_e3, get_state}; +use crate::enclave_server::{ + blockchain::relayer::EnclaveContract, + models::{AppState, EncryptedVote, GetEmojisRequest, JsonResponseTxHash, VoteCountRequest}, +}; pub fn setup_routes(config: &mut web::ServiceConfig) { config .route("/broadcast_enc_vote", web::post().to(broadcast_enc_vote)) - .route("/get_vote_count_by_round", web::post().to(get_vote_count_by_round)) + .route( + "/get_vote_count_by_round", + web::post().to(get_vote_count_by_round), + ) .route("/get_emojis_by_round", web::post().to(get_emojis_by_round)); } @@ -28,9 +32,9 @@ async fn broadcast_enc_vote( data: web::Json, state: web::Data, ) -> impl Responder { - let vote = data.into_inner(); - let (mut state_data, key) = get_state(vote.round_id); - + let vote: EncryptedVote = data.into_inner(); + let (mut state_data, key) = get_e3(vote.round_id as u64).unwrap(); + println!("Has Voted: {:?}", state_data.has_voted); if state_data.has_voted.contains(&vote.postId) { return HttpResponse::BadRequest().json(JsonResponseTxHash { response: "User has already voted".to_string(), @@ -39,7 +43,8 @@ async fn broadcast_enc_vote( } let sol_vote = Bytes::from(vote.enc_vote_bytes); - let tx_hash = match call_contract(sol_vote, state_data.voting_address.clone()).await { + let e3_id = U256::from(vote.round_id); + let tx_hash = match call_contract(e3_id, sol_vote, state_data.enclave_address.clone()).await { Ok(hash) => hash.to_string(), Err(e) => { info!("Error while sending vote transaction: {:?}", e); @@ -47,26 +52,27 @@ async fn broadcast_enc_vote( } }; - state_data.vote_count += 1; state_data.has_voted.push(vote.postId); - - if let Err(e) = state.db.insert(key, serde_json::to_vec(&state_data).unwrap()) { + + if let Err(e) = state + .db + .insert(key, serde_json::to_vec(&state_data).unwrap()) + { info!("Error updating state: {:?}", e); - return HttpResponse::InternalServerError().body("Failed to update state"); } - info!("Vote broadcast for round {}: tx_hash {}", vote.round_id, tx_hash); + info!( + "Vote broadcast for round {}: tx_hash {}", + vote.round_id, tx_hash + ); HttpResponse::Ok().json(JsonResponseTxHash { response: "Vote successful".to_string(), tx_hash, }) } - // Get Emojis by Round Handler -async fn get_emojis_by_round( - data: web::Json, -) -> impl Responder { +async fn get_emojis_by_round(data: web::Json) -> impl Responder { let mut incoming = data.into_inner(); info!("Request emojis for round {:?}", incoming.round_id); @@ -77,9 +83,7 @@ async fn get_emojis_by_round( } // Get Vote Count by Round Handler -async fn get_vote_count_by_round( - data: web::Json, -) -> impl Responder { +async fn get_vote_count_by_round(data: web::Json) -> impl Responder { let mut incoming = data.into_inner(); info!("Request vote count for round {:?}", incoming.round_id); @@ -100,37 +104,20 @@ sol! { } pub async fn call_contract( + e3_id: U256, enc_vote: Bytes, address: String, ) -> Result> { info!("Calling voting contract"); - // Set up the signer from a private key - let eth_val = env::var("PRIVATEKEY").expect("PRIVATEKEY must be set in the environment"); - let signer: PrivateKeySigner = eth_val.parse()?; - let wallet = EthereumWallet::from(signer); - - // Set up the provider using the Alloy library - let rpc_url = "http://0.0.0.0:8545".parse()?; - let provider = ProviderBuilder::new() - .with_recommended_fillers() - .network::() - .wallet(wallet) - .on_http(rpc_url); - - // Parse the address of the contract - let vote_address: Address = address.parse()?; - - // Create the contract instance - let contract = IVOTE::new(vote_address, &provider); - - // Send the voteEncrypted transaction - let builder = contract.voteEncrypted(enc_vote); - let receipt = builder.send().await?.get_receipt().await?; + let private_key = env::var("PRIVATE_KEY").expect("PRIVATE_KEY must be set in the environment"); + let rpc_url = "http://0.0.0.0:8545"; + let contract = EnclaveContract::new(rpc_url, &address, &private_key).await?; + let receipt = contract.publish_input(e3_id, enc_vote).await?; // Log the transaction hash let tx_hash = receipt.transaction_hash; info!("Transaction hash: {:?}", tx_hash); Ok(tx_hash) -} \ No newline at end of file +} From b395e6e58fc3691f448b061938f4514443b592c3 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Mon, 16 Sep 2024 22:06:05 +0500 Subject: [PATCH 25/62] Update: risc0 contracts --- .../compute_provider/src/compute_manager.rs | 2 +- packages/compute_provider/src/lib.rs | 3 +- packages/evm/contracts/CRISPVoting.sol | 7 +-- packages/evm_base/contracts/CRISPBase.sol | 19 +----- packages/risc0/contracts/CRISPRisc0.sol | 30 +++------- .../risc0/methods/guest/src/bin/voting.rs | 1 - packages/server/src/cli/mod.rs | 2 +- .../src/enclave_server/blockchain/handlers.rs | 60 ++++++++++--------- .../src/enclave_server/routes/rounds.rs | 22 +++++-- 9 files changed, 64 insertions(+), 82 deletions(-) diff --git a/packages/compute_provider/src/compute_manager.rs b/packages/compute_provider/src/compute_manager.rs index 39a6af4..a75aab6 100644 --- a/packages/compute_provider/src/compute_manager.rs +++ b/packages/compute_provider/src/compute_manager.rs @@ -71,7 +71,7 @@ where .map(|chunk| { let mut tree_handler = MerkleTree::new(parallel_tree_depth, "0".to_string(), 2); tree_handler.compute_leaf_hashes(&chunk); - + let input = ComputeInput { fhe_inputs: FHEInputs { ciphertexts: chunk.clone(), diff --git a/packages/compute_provider/src/lib.rs b/packages/compute_provider/src/lib.rs index f4f9a0b..53477c7 100644 --- a/packages/compute_provider/src/lib.rs +++ b/packages/compute_provider/src/lib.rs @@ -11,8 +11,7 @@ use fhe::bfv::{BfvParameters, Ciphertext}; use fhe_traits::{Deserialize, DeserializeParametrized, Serialize}; use std::sync::Arc; - -// Example Implementation of the CiphertextProcessor function +/// Example Implementation of the CiphertextProcessor function pub fn default_fhe_processor(fhe_inputs: &FHEInputs) -> Vec { let params = Arc::new(BfvParameters::try_deserialize(&fhe_inputs.params).unwrap()); diff --git a/packages/evm/contracts/CRISPVoting.sol b/packages/evm/contracts/CRISPVoting.sol index c5cd258..aa6e30c 100644 --- a/packages/evm/contracts/CRISPVoting.sol +++ b/packages/evm/contracts/CRISPVoting.sol @@ -103,15 +103,12 @@ contract CRISPVoting { uint256 e3Id, bytes memory data ) external returns (bool success) { - E3 storage e3 = e3Polls[e3Id]; - require(e3.expiration > 0, "Poll not activated."); - require(e3.expiration > block.timestamp, "Poll has expired."); require( - e3.ciphertextOutput.length == 0, + e3Polls[e3Id].ciphertextOutput.length == 0, "Ciphertext already published." ); - e3.ciphertextOutput = data; + e3Polls[e3Id].ciphertextOutput = data; return true; } diff --git a/packages/evm_base/contracts/CRISPBase.sol b/packages/evm_base/contracts/CRISPBase.sol index 75b2797..6f1eb52 100644 --- a/packages/evm_base/contracts/CRISPBase.sol +++ b/packages/evm_base/contracts/CRISPBase.sol @@ -4,18 +4,11 @@ pragma solidity >=0.8.26; import {IComputationModule, IInputValidator} from "./interfaces/IComputationModule.sol"; import {IEnclave} from "./interfaces/IEnclave.sol"; -struct Params { - uint64 degree; - uint64 plaintextModulus; - uint64[] ciphertextModuli; - uint256 seed; - IInputValidator inputValidator; -} abstract contract CRISPBase is IComputationModule { IEnclave public enclave; - mapping(uint256 e3Id => Params param) public params; + mapping(uint256 e3Id => bytes32 paramsHash) public paramsHashes; error E3AlreadyInitialized(); error E3DoesNotExist(); @@ -24,13 +17,7 @@ abstract contract CRISPBase is IComputationModule { enclave = _enclave; } - function getParamsHash(uint256 e3Id) public view returns (bytes32) { - require(params[e3Id].degree != 0, E3DoesNotExist()); - return keccak256(abi.encode(params[e3Id].degree, params[e3Id].plaintextModulus, params[e3Id].ciphertextModuli)); - } - - function getParams(uint256 e3Id) public view returns (Params memory) { - require(params[e3Id].degree != 0, E3DoesNotExist()); - return params[e3Id]; + function getParamsHash(uint256 e3Id) public view returns (bytes32 memory) { + return paramsHashes[e3Id]; } } diff --git a/packages/risc0/contracts/CRISPRisc0.sol b/packages/risc0/contracts/CRISPRisc0.sol index e5ae50d..bdbb3d5 100644 --- a/packages/risc0/contracts/CRISPRisc0.sol +++ b/packages/risc0/contracts/CRISPRisc0.sol @@ -5,13 +5,6 @@ import {CRISPBase, IComputationModule, IInputValidator, IEnclave} from "evm_base import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; import {ImageID} from "./ImageID.sol"; -struct Params { - uint64 degree; - uint64 plaintextModulus; - uint64[] ciphertextModuli; - IInputValidator inputValidator; -} - contract CRISPRisc0 is CRISPBase { /// @notice RISC Zero verifier contract address. IRiscZeroVerifier public verifier; @@ -33,20 +26,13 @@ contract CRISPRisc0 is CRISPBase { uint256 seed, bytes memory data ) external override returns (IInputValidator) { - require(params[e3Id].degree == 0, E3AlreadyInitialized()); + require(paramsHashes[e3Id] == bytes32(0), E3AlreadyInitialized()); ( - uint64 degree, - uint64 plaintextModulus, - uint64[] memory ciphertextModuli, + bytes memory params, IInputValidator inputValidator - ) = abi.decode(data, (uint64, uint64, uint64[], IInputValidator)); - // TODO: require that params are valid + ) = abi.decode(data, (bytes, IInputValidator)); - params[e3Id].degree = degree; - params[e3Id].plaintextModulus = plaintextModulus; - params[e3Id].ciphertextModuli = ciphertextModuli; - params[e3Id].seed = seed; - params[e3Id].inputValidator = inputValidator; + paramsHashes[e3Id] = keccak256(params); return inputValidator; } @@ -55,14 +41,14 @@ contract CRISPRisc0 is CRISPBase { uint256 e3Id, bytes memory data ) external view override returns (bytes memory, bool) { - require(params[e3Id].degree != 0, E3DoesNotExist()); + require(paramsHashes[e3Id] != bytes32(0), E3DoesNotExist()); uint256 inputRoot = enclave.getInputRoot(e3Id); - (bytes memory seal, bytes memory output) = abi.decode( + (bytes memory ciphertext, bytes memory seal) = abi.decode( data, (bytes, bytes) ); - bytes memory journal = abi.encode(inputRoot, output); + bytes memory journal = abi.encode(ciphertext, inputRoot, paramsHashes[e3id]); verifier.verify(seal, imageId, sha256(journal)); - return (output, true); + return (ciphertext, true); } } diff --git a/packages/risc0/methods/guest/src/bin/voting.rs b/packages/risc0/methods/guest/src/bin/voting.rs index 62df10b..4f09030 100644 --- a/packages/risc0/methods/guest/src/bin/voting.rs +++ b/packages/risc0/methods/guest/src/bin/voting.rs @@ -1,7 +1,6 @@ use risc0_zkvm::guest::env; use compute_provider::{ComputeInput, ComputeResult, default_fhe_processor}; - fn main() { let input: ComputeInput = env::read(); diff --git a/packages/server/src/cli/mod.rs b/packages/server/src/cli/mod.rs index 8c8d514..31401d7 100644 --- a/packages/server/src/cli/mod.rs +++ b/packages/server/src/cli/mod.rs @@ -101,7 +101,7 @@ fn select_environment() -> Result Result> { - let selections = &["Initialize new E3 round.", "Activate an E3 round.", "Continue Existing E3 round."]; + let selections = &["Initialize new E3 round.", "Activate an E3 round.", "Participate in an E3 round."]; Ok(FuzzySelect::with_theme(&ColorfulTheme::default()) .with_prompt("Create a new CRISP round or participate in an existing round.") .default(0) diff --git a/packages/server/src/enclave_server/blockchain/handlers.rs b/packages/server/src/enclave_server/blockchain/handlers.rs index 5c5fa60..42d4f42 100644 --- a/packages/server/src/enclave_server/blockchain/handlers.rs +++ b/packages/server/src/enclave_server/blockchain/handlers.rs @@ -2,19 +2,20 @@ use super::{ events::{E3Activated, InputPublished, PlaintextOutputPublished}, relayer::EnclaveContract, }; -use crate::enclave_server::database::{generate_emoji, get_e3, GLOBAL_DB, increment_e3_round}; +use crate::enclave_server::database::{generate_emoji, get_e3, increment_e3_round, GLOBAL_DB}; use crate::enclave_server::models::E3; use alloy::{ rpc::types::Log, sol_types::{SolCall, SolEvent}, }; +use alloy_sol_types::SolValue; use chrono::Utc; use compute_provider::FHEInputs; use std::env; use std::error::Error; use tokio::time::{sleep, Duration}; +use tokio::task; use voting_risc0::run_compute; -use alloy_sol_types::SolValue; use log::info; @@ -92,34 +93,35 @@ pub async fn handle_e3( // Get All Encrypted Votes let (e3, _) = get_e3(e3_id).unwrap(); - info!("E3 FROM DB"); - info!("Vote Count: {:?}", e3.vote_count); - - let fhe_inputs = FHEInputs { - params: e3.e3_params, - ciphertexts: e3.ciphertext_inputs, - }; - - // Call Compute Provider - let (compute_result, seal) = run_compute(fhe_inputs).unwrap(); - - let data = ( - compute_result.ciphertext, - compute_result.merkle_root, - seal, - ); - - let encoded_data = data.abi_encode(); - - // Params will be encoded on chain to create the journal - let tx = contract - .publish_ciphertext_output(e3_activated.e3Id, encoded_data.into()) - .await?; + if e3.vote_count > 0 { + info!("E3 FROM DB"); + info!("Vote Count: {:?}", e3.vote_count); + + let fhe_inputs = FHEInputs { + params: e3.e3_params, + ciphertexts: e3.ciphertext_inputs, + }; + + // Call Compute Provider in a separate thread + let (compute_result, seal) = tokio::task::spawn_blocking(move || { + run_compute(fhe_inputs).unwrap() + }).await.unwrap(); + + let data = (compute_result.ciphertext, seal); + + let encoded_data = data.abi_encode(); + + // Params will be encoded on chain to create the journal + let tx = contract + .publish_ciphertext_output(e3_activated.e3Id, encoded_data.into()) + .await?; + + info!( + "CiphertextOutputPublished event published with tx: {:?}", + tx + ); + } - info!( - "CiphertextOutputPublished event published with tx: {:?}", - tx - ); info!("E3 request handled successfully."); Ok(()) } diff --git a/packages/server/src/enclave_server/routes/rounds.rs b/packages/server/src/enclave_server/routes/rounds.rs index 1cc0fde..767c3b4 100644 --- a/packages/server/src/enclave_server/routes/rounds.rs +++ b/packages/server/src/enclave_server/routes/rounds.rs @@ -13,9 +13,9 @@ use ethers::{ types::U64, }; -use crate::util::timeit::timeit; +use crate::{enclave_server::models::PKRequest, util::timeit::timeit}; -use crate::enclave_server::database::{generate_emoji, get_state, get_e3_round}; +use crate::enclave_server::database::{generate_emoji, get_e3, get_e3_round}; use crate::enclave_server::models::{ AppState, Ciphernode, CrispConfig, JsonResponse, PollLengthRequest, ReportTallyRequest, Round, RoundCount, TimestampRequest, @@ -24,6 +24,7 @@ use crate::enclave_server::models::{ pub fn setup_routes(config: &mut web::ServiceConfig) { config .route("/get_rounds", web::get().to(get_rounds)) + .route("/get_pk_by_round", web::post().to(get_pk_by_round)) .route("/report_tally", web::post().to(report_tally)); } @@ -41,6 +42,17 @@ async fn get_rounds()-> impl Responder { } } +async fn get_pk_by_round( + data: web::Json, +) -> impl Responder { + let mut incoming = data.into_inner(); + info!("Request for round {:?} public key", incoming.round_id); + let (state_data, _) = get_e3(incoming.round_id as u64).unwrap(); + incoming.pk_bytes = state_data.committee_public_key; + + HttpResponse::Ok().json(incoming) +} + // Report Tally Handler async fn report_tally( data: web::Json, @@ -49,11 +61,11 @@ async fn report_tally( let incoming = data.into_inner(); info!("Request report tally for round {:?}", incoming.round_id); - let (mut state_data, key) = get_state(incoming.round_id); + let (mut state_data, key) = get_e3(incoming.round_id as u64).unwrap(); if state_data.votes_option_1 == 0 && state_data.votes_option_2 == 0 { - state_data.votes_option_1 = incoming.option_1; - state_data.votes_option_2 = incoming.option_2; + state_data.votes_option_1 = incoming.option_1 as u64; + state_data.votes_option_2 = incoming.option_2 as u64; let state_str = serde_json::to_string(&state_data).unwrap(); state.db.insert(key, state_str.into_bytes()).unwrap(); From 47bd1f9f3a75d6e46ebeb32210273acdb5145a62 Mon Sep 17 00:00:00 2001 From: fhedude Date: Mon, 16 Sep 2024 23:05:56 -0400 Subject: [PATCH 26/62] Added Greco input validation structure and beginner functions --- packages/web-rust/Cargo.lock | 24 +- packages/web-rust/Cargo.toml | 13 +- packages/web-rust/src/bin/greco/greco.rs | 408 +++++++++++++++++++ packages/web-rust/src/bin/greco/mod.rs | 2 + packages/web-rust/src/bin/greco/poly.rs | 343 ++++++++++++++++ packages/web-rust/src/bin/web_fhe_encrypt.rs | 34 +- 6 files changed, 814 insertions(+), 10 deletions(-) create mode 100644 packages/web-rust/src/bin/greco/greco.rs create mode 100644 packages/web-rust/src/bin/greco/mod.rs create mode 100644 packages/web-rust/src/bin/greco/poly.rs diff --git a/packages/web-rust/Cargo.lock b/packages/web-rust/Cargo.lock index 3a5e56f..e20f980 100644 --- a/packages/web-rust/Cargo.lock +++ b/packages/web-rust/Cargo.lock @@ -466,10 +466,16 @@ dependencies = [ "console", "ethers", "fhe", + "fhe-math", "fhe-traits", "fhe-util", "getrandom", + "itertools 0.13.0", + "ndarray", + "num-bigint", + "num-traits", "rand", + "rayon", "serde", "serde_json", "wasm-bindgen", @@ -1090,6 +1096,7 @@ dependencies = [ [[package]] name = "fhe" version = "0.1.0-beta.7" +source = "git+https://github.com/gnosisguild/fhe.rs.git?branch=feature/greco-integration#26e5f2ff6c860d47a1c88a777936bc68eaedb129" dependencies = [ "doc-comment", "fhe-math", @@ -1112,6 +1119,7 @@ dependencies = [ [[package]] name = "fhe-math" version = "0.1.0-beta.7" +source = "git+https://github.com/gnosisguild/fhe.rs.git?branch=feature/greco-integration#26e5f2ff6c860d47a1c88a777936bc68eaedb129" dependencies = [ "ethnum", "fhe-traits", @@ -1133,6 +1141,7 @@ dependencies = [ [[package]] name = "fhe-traits" version = "0.1.0-beta.7" +source = "git+https://github.com/gnosisguild/fhe.rs.git?branch=feature/greco-integration#26e5f2ff6c860d47a1c88a777936bc68eaedb129" dependencies = [ "rand", ] @@ -1140,6 +1149,7 @@ dependencies = [ [[package]] name = "fhe-util" version = "0.1.0-beta.7" +source = "git+https://github.com/gnosisguild/fhe.rs.git?branch=feature/greco-integration#26e5f2ff6c860d47a1c88a777936bc68eaedb129" dependencies = [ "itertools 0.12.1", "num-bigint-dig", @@ -1637,6 +1647,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -1860,11 +1879,10 @@ checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] diff --git a/packages/web-rust/Cargo.toml b/packages/web-rust/Cargo.toml index 62286bf..5b78ff1 100644 --- a/packages/web-rust/Cargo.toml +++ b/packages/web-rust/Cargo.toml @@ -9,9 +9,10 @@ edition = "2021" [dependencies] web-sys = { version = "0.3", features = ["console"] } console = "0.15.7" -fhe = { path = "./fhe.rs/crates/fhe" } -fhe-traits = { path = "./fhe.rs/crates/fhe-traits" } -fhe-util = { path = "./fhe.rs/crates/fhe-util" } +fhe = { git = "https://github.com/gnosisguild/fhe.rs.git", branch = "feature/greco-integration" } +fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs.git", branch = "feature/greco-integration" } +fhe-math = { git = "https://github.com/gnosisguild/fhe.rs.git", branch = "feature/greco-integration" } +fhe-util = { git = "https://github.com/gnosisguild/fhe.rs.git", branch = "feature/greco-integration" } # fhe = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } # fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } # fhe-util = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } @@ -20,6 +21,11 @@ ethers = "2.0" getrandom = { version = "0.2.11", features = ["js"] } bincode = "1.0" +rayon = "1.10.0" +ndarray = "0.15.6" +itertools = "0.13.0" +num-bigint = "0.4.6" +num-traits = "0.2" serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0.114" wasm-bindgen = "0.2" @@ -27,4 +33,3 @@ wasm-bindgen = "0.2" [lib] crate-type = ["cdylib", "rlib"] path = "src/bin/web_fhe_encrypt.rs" - diff --git a/packages/web-rust/src/bin/greco/greco.rs b/packages/web-rust/src/bin/greco/greco.rs new file mode 100644 index 0000000..a7248b7 --- /dev/null +++ b/packages/web-rust/src/bin/greco/greco.rs @@ -0,0 +1,408 @@ +use std::ops::Deref; +use std::sync::Arc; + +use fhe::bfv::{Ciphertext, Plaintext, PublicKey}; +use fhe_math::rq::{Poly, Representation}; +use fhe_math::{rq::Context, zq::Modulus}; + +use itertools::izip; +use num_bigint::BigInt; +use num_traits::*; +use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; + +use crate::greco::poly::*; + +/// Assign and return all of the centered input validation vectors to the ZKP modulus `p`. +/// +/// # Arguments +/// +/// * `pk0is` - Centered coefficients of first public key object for each RNS modulus +/// * `pk1is` - Centered coefficients of second public key object for each RNS modulus +/// * `r2is` - Centered coefficients of r2 for each RNS modulus +/// * `r1is` - Centered coefficients of r1 for each RNS modulus +/// * `p2is` - Centered coefficients of p2 for each RNS modulus +/// * `p1is` - Centered coefficients of p1 for each RNS modulus +/// * `ct0is` - Centered coefficients of first ciphertext object for each RNS modulus +/// * `ct1is` - Centered coefficients of second ciphertext object for each RNS modulus +/// * `u` - Centered coefficients of secret polynomial used during encryption (sampled from secret key distribution) +/// * `e0` - Centered coefficients of error polynomial used during encryption (sampled from error distribution) +/// * `e1` - Centered coefficients of error polynomial used during encryption (sampled from error distribution) +/// * `k1` - Centered coefficients of [Q*m] mod t +/// * `p` - ZKP modulus +/// +pub fn input_validation_vectors_standard_form( + pk0is: &[Vec], + pk1is: &[Vec], + r2is: &[Vec], + r1is: &[Vec], + p2is: &[Vec], + p1is: &[Vec], + ct0is: &[Vec], + ct1is: &[Vec], + u: &[BigInt], + e0: &[BigInt], + e1: &[BigInt], + k1: &[BigInt], + p: &BigInt, +) -> ( + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec, + Vec, + Vec, + Vec, +) { + ( + reduce_coefficients_2d(pk0is, p), + reduce_coefficients_2d(pk1is, p), + reduce_coefficients_2d(r2is, p), + reduce_coefficients_2d(r1is, p), + reduce_coefficients_2d(p2is, p), + reduce_coefficients_2d(p1is, p), + reduce_coefficients_2d(ct0is, p), + reduce_coefficients_2d(ct1is, p), + reduce_coefficients(u, p), + reduce_coefficients(e0, p), + reduce_coefficients(e1, p), + reduce_coefficients(k1, p), + ) +} + +/// Create the centered validation vectors necessary for creating an input validation proof according to Greco. +/// For more information, please see https://eprint.iacr.org/2024/594. +/// +/// # Arguments +/// +/// * `ctx` - Context object from fhe.rs holding information about elements in Rq. +/// * `t` - Plaintext modulus object. +/// * `pt` - Plaintext from fhe.rs. +/// * `u_rns` - Private polynomial used in ciphertext sampled from secret key distribution. +/// * `e0_rns` - Error polynomial used in ciphertext sampled from error distribution. +/// * `e1_rns` - Error polynomioal used in cihpertext sampled from error distribution. +/// * `ct` - Ciphertext from fhe.rs. +/// * `pk` - Public Key from fhe.re. +/// +pub fn compute_input_validation_vectors( + ctx: &Arc, + t: &Modulus, + pt: &Plaintext, + u_rns: &Poly, + e0_rns: &Poly, + e1_rns: &Poly, + ct: &Ciphertext, + pk: &PublicKey, +) -> ( + Vec>, + Vec>, + Vec, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec, + Vec, + Vec, + Vec, +) { + let N: u64 = ctx.degree as u64; + + // Calculate k1 (independent of qi), center and reverse + let q_mod_t = (ctx.modulus() % t.modulus()).to_u64().unwrap(); // [q]_t + let mut k1_u64 = pt.value.deref().to_vec(); // m + t.scalar_mul_vec(&mut k1_u64, q_mod_t); // k1 = [q*m]_t + let mut k1: Vec = k1_u64.iter().map(|&x| BigInt::from(x)).rev().collect(); + reduce_and_center_coefficients_mut(&mut k1, &BigInt::from(t.modulus())); + + // Extract single vectors of u, e1, and e2 as Vec, center and reverse + let mut u_rns_copy = u_rns.clone(); + let mut e0_rns_copy = e0_rns.clone(); + let mut e1_rns_copy = e1_rns.clone(); + u_rns_copy.change_representation(Representation::PowerBasis); + e0_rns_copy.change_representation(Representation::PowerBasis); + e1_rns_copy.change_representation(Representation::PowerBasis); + let u: Vec = unsafe { + ctx.moduli_operators()[0] + .center_vec_vt(u_rns_copy.coefficients().row(0).as_slice().unwrap()) + .iter() + .map(|&x| BigInt::from(x)) + .rev() + .collect() + }; + + let e0: Vec = unsafe { + ctx.moduli_operators()[0] + .center_vec_vt(e0_rns_copy.coefficients().row(0).as_slice().unwrap()) + .iter() + .map(|&x| BigInt::from(x)) + .rev() + .collect() + }; + + let e1: Vec = unsafe { + ctx.moduli_operators()[0] + .center_vec_vt(e1_rns_copy.coefficients().row(0).as_slice().unwrap()) + .iter() + .map(|&x| BigInt::from(x)) + .rev() + .collect() + }; + + // Extract and convert ciphertext and plaintext polynomials + let mut ct0 = ct.c[0].clone(); + let mut ct1 = ct.c[1].clone(); + ct0.change_representation(Representation::PowerBasis); + ct1.change_representation(Representation::PowerBasis); + + let mut pk0: Poly = pk.c.c[0].clone(); + let mut pk1: Poly = pk.c.c[1].clone(); + pk0.change_representation(Representation::PowerBasis); + pk1.change_representation(Representation::PowerBasis); + + // Create cyclotomic polynomial x^N + 1 + let mut cyclo = vec![BigInt::from(0u64); (N + 1) as usize]; + cyclo[0] = BigInt::from(1u64); // x^N term + cyclo[N as usize] = BigInt::from(1u64); // x^0 term + + // Print + /* + println!("m = {:?}\n", &m); + println!("k1 = {:?}\n", &k1); + println!("u = {:?}\n", &u); + println!("e0 = {:?}\n", &e0); + println!("e1 = {:?}\n", &e1); + */ + + // Initialize matrices to store results + let num_moduli = ctx.moduli().len(); + let mut r2is: Vec> = vec![Vec::new(); num_moduli]; + let mut r1is: Vec> = vec![Vec::new(); num_moduli]; + let mut k0is: Vec = vec![BigInt::zero(); num_moduli]; + let mut ct0is: Vec> = vec![Vec::new(); num_moduli]; + let mut ct0is_hat: Vec> = vec![Vec::new(); num_moduli]; + let mut ct1is: Vec> = vec![Vec::new(); num_moduli]; + let mut ct1is_hat: Vec> = vec![Vec::new(); num_moduli]; + let mut pk0is: Vec> = vec![Vec::new(); num_moduli]; + let mut pk1is: Vec> = vec![Vec::new(); num_moduli]; + let mut p1is: Vec> = vec![Vec::new(); num_moduli]; + let mut p2is: Vec> = vec![Vec::new(); num_moduli]; + + // Initialize iterators for results calculation + let moduli_operators = ctx.moduli_operators(); + let ct0_iter = ct0.coefficients(); + let ct1_iter = ct1.coefficients(); + let pk0_iter = pk0.coefficients(); + let pk1_iter = pk1.coefficients(); + let zipped: Vec<_> = izip!( + moduli_operators, + ct0_iter.rows(), + ct1_iter.rows(), + pk0_iter.rows(), + pk1_iter.rows() + ) + .collect(); + + // Perform the main computation logic + let results: Vec<( + usize, + Vec, + Vec, + BigInt, + Vec, + Vec, + Vec, + Vec, + Vec, + Vec, + Vec, + Vec, + )> = zipped + .into_par_iter() + .enumerate() + .map( + |(i, (qi, ct0_coeffs, ct1_coeffs, pk0_coeffs, pk1_coeffs))| { + // --------------------------------------------------- ct0i --------------------------------------------------- + + // Convert to vectors of bigint, center, and reverse order. + let mut ct0i: Vec = + ct0_coeffs.iter().map(|&x| BigInt::from(x)).rev().collect(); + let mut ct1i: Vec = + ct1_coeffs.iter().map(|&x| BigInt::from(x)).rev().collect(); + let mut pk0i: Vec = + pk0_coeffs.iter().map(|&x| BigInt::from(x)).rev().collect(); + let mut pk1i: Vec = + pk1_coeffs.iter().map(|&x| BigInt::from(x)).rev().collect(); + + let qi_bigint = BigInt::from(qi.modulus()); + + reduce_and_center_coefficients_mut(&mut ct0i, &qi_bigint); + reduce_and_center_coefficients_mut(&mut ct1i, &qi_bigint); + reduce_and_center_coefficients_mut(&mut pk0i, &qi_bigint); + reduce_and_center_coefficients_mut(&mut pk1i, &qi_bigint); + + // k0qi = -t^{-1} mod qi + let koqi_u64 = qi.inv(qi.neg(t.modulus())).unwrap(); + let k0qi = BigInt::from(koqi_u64); // Do not need to center this + + // ki = k1 * k0qi + let ki = poly_scalar_mul(&k1, &k0qi); + + // Calculate ct0i_hat = pk0 * ui + e0i + ki + let ct0i_hat = { + let pk0i_times_u = poly_mul(&pk0i, &u); + assert_eq!((pk0i_times_u.len() as u64) - 1, 2 * (N - 1)); + + let e0_plus_ki = poly_add(&e0, &ki); + assert_eq!((e0_plus_ki.len() as u64) - 1, N - 1); + + poly_add(&pk0i_times_u, &e0_plus_ki) + }; + assert_eq!((ct0i_hat.len() as u64) - 1, 2 * (N - 1)); + + // Check whether ct0i_hat mod R_qi (the ring) is equal to ct0i + let mut ct0i_hat_mod_rqi = ct0i_hat.clone(); + reduce_in_ring(&mut ct0i_hat_mod_rqi, &cyclo, &qi_bigint); + assert_eq!(&ct0i, &ct0i_hat_mod_rqi); + + // Compute r2i numerator = ct0i - ct0i_hat and reduce/center the polynomial + let ct0i_minus_ct0i_hat = poly_sub(&ct0i, &ct0i_hat); + assert_eq!((ct0i_minus_ct0i_hat.len() as u64) - 1, 2 * (N - 1)); + let mut ct0i_minus_ct0i_hat_mod_zqi = ct0i_minus_ct0i_hat.clone(); + reduce_and_center_coefficients_mut(&mut ct0i_minus_ct0i_hat_mod_zqi, &qi_bigint); + + // Compute r2i as the quotient of numerator divided by the cyclotomic polynomial + // to produce: (ct0i - ct0i_hat) / (x^N + 1) mod Z_qi. Remainder should be empty. + let (r2i, r2i_rem) = poly_div(&ct0i_minus_ct0i_hat_mod_zqi, &cyclo); + assert!(r2i_rem.is_empty()); + assert_eq!((r2i.len() as u64) - 1, N - 2); // Order(r2i) = N - 2 + + // Assert that (ct0i - ct0i_hat) = (r2i * cyclo) mod Z_qi + let r2i_times_cyclo = poly_mul(&r2i, &cyclo); + let mut r2i_times_cyclo_mod_zqi = r2i_times_cyclo.clone(); + reduce_and_center_coefficients_mut(&mut r2i_times_cyclo_mod_zqi, &qi_bigint); + assert_eq!(&ct0i_minus_ct0i_hat_mod_zqi, &r2i_times_cyclo_mod_zqi); + assert_eq!((r2i_times_cyclo.len() as u64) - 1, 2 * (N - 1)); + + // Calculate r1i = (ct0i - ct0i_hat - r2i * cyclo) / qi mod Z_p. Remainder should be empty. + let r1i_num = poly_sub(&ct0i_minus_ct0i_hat, &r2i_times_cyclo); + assert_eq!((r1i_num.len() as u64) - 1, 2 * (N - 1)); + + let (r1i, r1i_rem) = poly_div(&r1i_num, &[qi_bigint.clone()]); + assert!(r1i_rem.is_empty()); + assert_eq!((r1i.len() as u64) - 1, 2 * (N - 1)); // Order(r1i) = 2*(N-1) + assert_eq!(&r1i_num, &poly_mul(&r1i, &[qi_bigint.clone()])); + + // Assert that ct0i = ct0i_hat + r1i * qi + r2i * cyclo mod Z_p + let r1i_times_qi = poly_scalar_mul(&r1i, &qi_bigint); + let mut ct0i_calculated = + poly_add(&poly_add(&ct0i_hat, &r1i_times_qi), &r2i_times_cyclo); + + while ct0i_calculated.len() > 0 && ct0i_calculated[0].is_zero() { + ct0i_calculated.remove(0); + } + + assert_eq!(&ct0i, &ct0i_calculated); + + // --------------------------------------------------- ct1i --------------------------------------------------- + + // Calculate ct1i_hat = pk1i * ui + e1i + let ct1i_hat = { + let pk1i_times_u = poly_mul(&pk1i, &u); + assert_eq!((pk1i_times_u.len() as u64) - 1, 2 * (N - 1)); + + poly_add(&pk1i_times_u, &e1) + }; + assert_eq!((ct1i_hat.len() as u64) - 1, 2 * (N - 1)); + + // Check whether ct1i_hat mod R_qi (the ring) is equal to ct1i + let mut ct1i_hat_mod_rqi = ct1i_hat.clone(); + reduce_in_ring(&mut ct1i_hat_mod_rqi, &cyclo, &qi_bigint); + assert_eq!(&ct1i, &ct1i_hat_mod_rqi); + + // Compute p2i numerator = ct1i - ct1i_hat + let ct1i_minus_ct1i_hat = poly_sub(&ct1i, &ct1i_hat); + assert_eq!((ct1i_minus_ct1i_hat.len() as u64) - 1, 2 * (N - 1)); + let mut ct1i_minus_ct1i_hat_mod_zqi = ct1i_minus_ct1i_hat.clone(); + reduce_and_center_coefficients_mut(&mut ct1i_minus_ct1i_hat_mod_zqi, &qi_bigint); + + // Compute p2i as the quotient of numerator divided by the cyclotomic polynomial, + // and reduce/center the resulting coefficients to produce: + // (ct1i - ct1i_hat) / (x^N + 1) mod Z_qi. Remainder should be empty. + let (p2i, p2i_rem) = poly_div(&ct1i_minus_ct1i_hat_mod_zqi, &cyclo.clone()); + assert!(p2i_rem.is_empty()); + assert_eq!((p2i.len() as u64) - 1, N - 2); // Order(p2i) = N - 2 + + // Assert that (ct1i - ct1i_hat) = (p2i * cyclo) mod Z_qi + let p2i_times_cyclo: Vec = poly_mul(&p2i, &cyclo); + let mut p2i_times_cyclo_mod_zqi = p2i_times_cyclo.clone(); + reduce_and_center_coefficients_mut(&mut p2i_times_cyclo_mod_zqi, &qi_bigint); + assert_eq!(&ct1i_minus_ct1i_hat_mod_zqi, &p2i_times_cyclo_mod_zqi); + assert_eq!((p2i_times_cyclo.len() as u64) - 1, 2 * (N - 1)); + + // Calculate p1i = (ct1i - ct1i_hat - p2i * cyclo) / qi mod Z_p. Remainder should be empty. + let p1i_num = poly_sub(&ct1i_minus_ct1i_hat, &p2i_times_cyclo); + assert_eq!((p1i_num.len() as u64) - 1, 2 * (N - 1)); + + let (p1i, p1i_rem) = poly_div(&p1i_num, &[BigInt::from(qi.modulus())]); + assert!(p1i_rem.is_empty()); + assert_eq!((p1i.len() as u64) - 1, 2 * (N - 1)); // Order(p1i) = 2*(N-1) + assert_eq!(&p1i_num, &poly_mul(&p1i, &[qi_bigint.clone()])); + + // Assert that ct1i = ct1i_hat + p1i * qi + p2i * cyclo mod Z_p + let p1i_times_qi = poly_scalar_mul(&p1i, &qi_bigint); + let mut ct1i_calculated = + poly_add(&poly_add(&ct1i_hat, &p1i_times_qi), &p2i_times_cyclo); + + while ct1i_calculated.len() > 0 && ct1i_calculated[0].is_zero() { + ct1i_calculated.remove(0); + } + + assert_eq!(&ct1i, &ct1i_calculated); + + /* + println!("qi = {:?}\n", &qi_bigint); + println!("ct0i = {:?}\n", &ct0i); + println!("k0qi = {:?}\n", &k0qi); + println!("pk0 = Polynomial({:?})\n", &pk0i); + println!("pk1 = Polynomial({:?})\n", &pk1i); + println!("ki = {:?}\n", &ki); + println!("ct0i_hat_mod_rqi = {:?}\n", &ct0i_hat_mod_rqi); + */ + + ( + i, r2i, r1i, k0qi, ct0i, ct0i_hat, ct1i, ct1i_hat, pk0i, pk1i, p1i, p2i, + ) + }, + ) + .collect(); + + // println!("Completed creation of polynomials!"); + + // Aggregate results into global vectors + for (i, r2i, r1i, k0i, ct0i, ct0i_hat, ct1i, ct1i_hat, pk0i, pk1i, p1i, p2i) in + results.into_iter() + { + r2is[i] = r2i; + r1is[i] = r1i; + k0is[i] = k0i; + ct0is[i] = ct0i; + ct0is_hat[i] = ct0i_hat; + ct1is[i] = ct1i; + ct1is_hat[i] = ct1i_hat; + pk0is[i] = pk0i; + pk1is[i] = pk1i; + p1is[i] = p1i; + p2is[i] = p2i; + } + + ( + r2is, r1is, k0is, ct0is, ct1is, pk0is, pk1is, p1is, p2is, u, e0, e1, k1, + ) +} diff --git a/packages/web-rust/src/bin/greco/mod.rs b/packages/web-rust/src/bin/greco/mod.rs new file mode 100644 index 0000000..2d120a8 --- /dev/null +++ b/packages/web-rust/src/bin/greco/mod.rs @@ -0,0 +1,2 @@ +pub mod greco; +mod poly; diff --git a/packages/web-rust/src/bin/greco/poly.rs b/packages/web-rust/src/bin/greco/poly.rs new file mode 100644 index 0000000..402ac77 --- /dev/null +++ b/packages/web-rust/src/bin/greco/poly.rs @@ -0,0 +1,343 @@ +/// Provides helper methods that perform modular poynomial arithmetic over polynomials encoded in vectors +/// of coefficients from largest degree to lowest. +use num_bigint::BigInt; +use num_traits::*; + +/// Adds two polynomials represented as vectors of `BigInt` coefficients in descending order of powers. +/// +/// This function aligns two polynomials of potentially different lengths and adds their coefficients. +/// It assumes that polynomials are represented from leading degree to degree zero, even if the +/// coefficient at degree zero is zero. Leading zeros are not removed to keep the order of the +/// polynomial correct, which in Greco's case is necessary so that the order can be checked. +/// +/// # Arguments +/// +/// * `poly1` - Coefficients of the first polynomial, from highest to lowest degree. +/// * `poly2` - Coefficients of the second polynomial, from highest to lowest degree. +/// +/// # Returns +/// +/// A vector of `BigInt` coefficients representing the sum of the two polynomials. +pub fn poly_add(poly1: &[BigInt], poly2: &[BigInt]) -> Vec { + // Determine the new length and create extended polynomials + let max_length = std::cmp::max(poly1.len(), poly2.len()); + let mut extended_poly1 = vec![BigInt::zero(); max_length]; + let mut extended_poly2 = vec![BigInt::zero(); max_length]; + + // Copy original coefficients into extended vectors + extended_poly1[max_length - poly1.len()..].clone_from_slice(poly1); + extended_poly2[max_length - poly2.len()..].clone_from_slice(poly2); + + // Add the coefficients + let mut result = vec![BigInt::zero(); max_length]; + for i in 0..max_length { + result[i] = &extended_poly1[i] + &extended_poly2[i]; + } + + result +} + +/// Negates the coefficients of a polynomial represented as a slice of `BigInt` coefficients. +/// +/// This function creates a new polynomial where each coefficient is the negation of the corresponding +/// coefficient in the input polynomial. +/// +/// # Arguments +/// +/// * `poly` - A slice of `BigInt` representing the coefficients of the polynomial, with the highest +/// degree term at index 0 and the constant term at the end. +/// +/// # Returns +/// +/// A vector of `BigInt` representing the polynomial with negated coefficients, with the same degree +/// order as the input polynomial. +pub fn poly_neg(poly: &[BigInt]) -> Vec { + poly.iter().map(|x| -x).collect() +} + +/// Subtracts one polynomial from another, both represented as slices of `BigInt` coefficients in descending order. +/// +/// This function subtracts the second polynomial (`poly2`) from the first polynomial (`poly1`). It does so +/// by first negating the coefficients of `poly2` and then adding the result to `poly1`. +/// +/// # Arguments +/// +/// * `poly1` - A slice of `BigInt` representing the coefficients of the first polynomial (minuend), with +/// the highest degree term at index 0 and the constant term at the end. +/// * `poly2` - A slice of `BigInt` representing the coefficients of the second polynomial (subtrahend), with +/// the highest degree term at index 0 and the constant term at the end. +/// +/// # Returns +/// +/// A vector of `BigInt` representing the coefficients of the resulting polynomial after subtraction. +pub fn poly_sub(poly1: &[BigInt], poly2: &[BigInt]) -> Vec { + poly_add(poly1, &poly_neg(poly2)) +} + +/// Multiplies two polynomials represented as slices of `BigInt` coefficients naively. +/// +/// Given two polynomials `poly1` and `poly2`, where each polynomial is represented by a slice of +/// coefficients, this function computes their product. The order of coefficients (ascending or +/// descending powers) should be the same for both input polynomials. The resulting polynomial is +/// returned as a vector of `BigInt` coefficients in the same order as the inputs. +/// +/// # Arguments +/// +/// * `poly1` - A slice of `BigInt` representing the coefficients of the first polynomial. +/// * `poly2` - A slice of `BigInt` representing the coefficients of the second polynomial. +/// +/// # Returns +/// +/// A vector of `BigInt` representing the coefficients of the resulting polynomial after multiplication, +/// in the same order as the input polynomials. +pub fn poly_mul(poly1: &[BigInt], poly2: &[BigInt]) -> Vec { + let product_len = poly1.len() + poly2.len() - 1; + let mut product = vec![BigInt::zero(); product_len]; + + for i in 0..poly1.len() { + for j in 0..poly2.len() { + product[i + j] += &poly1[i] * &poly2[j]; + } + } + + product +} + +/// Divides one polynomial by another, returning the quotient and remainder, with both polynomials +/// represented by vectors of `BigInt` coefficients in descending order of powers. +/// +/// Given two polynomials `dividend` and `divisor`, where each polynomial is represented by a vector +/// of coefficients in descending order of powers (i.e., the coefficient at index `i` corresponds +/// to the term of degree `n - i`, where `n` is the degree of the polynomial), this function computes +/// their quotient and remainder. The quotient and remainder are also represented in descending order +/// of powers. +/// +/// # Arguments +/// +/// * `dividend` - A slice of `BigInt` representing the coefficients of the dividend polynomial. +/// * `divisor` - A slice of `BigInt` representing the coefficients of the divisor polynomial. The leading +/// coefficient (highest degree term) must be non-zero. +/// +/// # Returns +/// +/// A tuple containing two vectors of `BigInt`: +/// * The first vector represents the quotient polynomial, with coefficients in descending order of powers. +/// * The second vector represents the remainder polynomial, also in descending order of powers. +/// +/// # Panics +/// +/// This function will panic if the divisor is empty or if the leading coefficient of the divisor is zero. +pub fn poly_div(dividend: &[BigInt], divisor: &[BigInt]) -> (Vec, Vec) { + assert!( + !divisor.is_empty() && !divisor[0].is_zero(), + "Leading coefficient of divisor cannot be zero" + ); + + let mut quotient = vec![BigInt::zero(); dividend.len() - divisor.len() + 1]; + let mut remainder = dividend.to_vec(); + + for i in 0..quotient.len() { + let coeff = &remainder[i] / &divisor[0]; + quotient[i] = coeff.clone(); + + for j in 0..divisor.len() { + remainder[i + j] = &remainder[i + j] - &divisor[j] * &coeff; + } + } + + while remainder.len() > 0 && remainder[0].is_zero() { + remainder.remove(0); + } + + (quotient, remainder) +} + +/// Multiplies each coefficient of a polynomial by a scalar. +/// +/// This function takes a polynomial represented as a vector of `BigInt` coefficients and multiplies each +/// coefficient by a given scalar. +/// +/// # Arguments +/// +/// * `poly` - A slice of `BigInt` representing the coefficients of the polynomial, with the highest degree term +/// at index 0 and the constant term at the end. +/// * `scalar` - A `BigInt` representing the scalar by which each coefficient of the polynomial will be multiplied. +/// +/// # Returns +/// +/// A vector of `BigInt` representing the polynomial with each coefficient multiplied by the scalar, maintaining +/// the same order of coefficients as the input polynomial. +pub fn poly_scalar_mul(poly: &[BigInt], scalar: &BigInt) -> Vec { + poly.iter().map(|coeff| coeff * scalar).collect() +} + +/// Reduces the coefficients of a polynomial by dividing it with a cyclotomic polynomial +/// and updating the coefficients with the remainder. +/// +/// This function performs a polynomial long division of the input polynomial (represented by +/// `coefficients`) by the given cyclotomic polynomial (represented by `cyclo`). It replaces +/// the original coefficients with the coefficients of the remainder from this division. +/// +/// # Arguments +/// +/// * `coefficients` - A mutable reference to a `Vec` containing the coefficients of +/// the polynomial to be reduced. The coefficients are in descending order of degree, +/// i.e., the first element is the coefficient of the highest degree term. +/// * `cyclo` - A slice of `BigInt` representing the coefficients of the cyclotomic polynomial. +/// The coefficients are also in descending order of degree. +/// +/// # Panics +/// +/// This function will panic if the remainder length exceeds the degree of the cyclotomic polynomial, +/// which would indicate an issue with the division operation. +pub fn reduce_coefficients_by_cyclo(coefficients: &mut Vec, cyclo: &[BigInt]) { + // Perform polynomial long division, assuming poly_div returns (quotient, remainder) + let (_, remainder) = poly_div(&coefficients, cyclo); + + let N = cyclo.len() - 1; + let mut out: Vec = vec![BigInt::zero(); N]; + + // Calculate the starting index in `out` where the remainder should be copied + let start_idx = N - remainder.len(); + + // Copy the remainder into the `out` vector starting from `start_idx` + out[start_idx..].clone_from_slice(&remainder); + + // Resize the original `coefficients` vector to fit the result and copy the values + coefficients.clear(); + coefficients.extend(out); +} + +/// Reduces a number modulo a prime modulus and centers it. +/// +/// This function takes an arbitrary number and reduces it modulo the specified prime modulus. +/// After reduction, the number is adjusted to be within the symmetric range +/// [−(modulus−1)/2, (modulus−1)/2]. If the number is already within this range, it remains unchanged. +/// +/// # Parameters +/// +/// - `x`: A reference to a `BigInt` representing the number to be reduced and centered. +/// - `modulus`: A reference to the prime modulus `BigInt` used for reduction. +/// - `half_modulus`: A reference to a `BigInt` representing half of the modulus used to center the coefficient. +/// +/// # Returns +/// +/// - A `BigInt` representing the reduced and centered number. +pub fn reduce_and_center(x: &BigInt, modulus: &BigInt, half_modulus: &BigInt) -> BigInt { + // Calculate the remainder ensuring it's non-negative + let mut r = x % modulus; + if r < BigInt::zero() { + r += modulus; + } + + // Adjust the remainder if it is greater than half_modulus + if r > *half_modulus { + r -= modulus; + } + + r +} + +/// Reduces and centers polynomial coefficients modulo a prime modulus. +/// +/// This function iterates over a mutable slice of polynomial coefficients, reducing each coefficient +/// modulo a given prime modulus and adjusting the result to be within the symmetric range +/// [−(modulus−1)/2, (modulus−1)/2]. +/// +/// # Parameters +/// +/// - `coefficients`: A mutable slice of `BigInt` coefficients to be reduced and centered. +/// - `modulus`: A prime modulus `BigInt` used for reduction and centering. +/// +/// # Panics +/// +/// - Panics if `modulus` is zero due to division by zero. +pub fn reduce_and_center_coefficients_mut(coefficients: &mut [BigInt], modulus: &BigInt) { + let half_modulus = modulus / BigInt::from(2); + coefficients + .iter_mut() + .for_each(|x| *x = reduce_and_center(x, modulus, &half_modulus)); +} +pub fn reduce_and_center_coefficients( + coefficients: &mut [BigInt], + modulus: &BigInt, +) -> Vec { + let half_modulus = modulus / BigInt::from(2); + coefficients + .iter() + .map(|x| reduce_and_center(x, modulus, &half_modulus)) + .collect() +} + +/// Reduces a polynomial's coefficients within a polynomial ring defined by a cyclotomic polynomial and a modulus. +/// +/// This function performs two reductions on the polynomial represented by `coefficients`: +/// 1. **Cyclotomic Reduction**: Reduces the polynomial by the cyclotomic polynomial, replacing +/// the original coefficients with the remainder after polynomial division. +/// 2. **Modulus Reduction**: Reduces the coefficients of the polynomial modulo a given modulus, +/// centering the coefficients within the range [-modulus/2, modulus/2). +/// +/// # Arguments +/// +/// * `coefficients` - A mutable reference to a `Vec` representing the coefficients of the polynomial +/// to be reduced. The coefficients should be in descending order of degree. +/// * `cyclo` - A slice of `BigInt` representing the coefficients of the cyclotomic polynomial (typically x^N + 1). +/// * `modulus` - A reference to a `BigInt` representing the modulus for the coefficient reduction. The coefficients +/// will be reduced and centered modulo this value. +pub fn reduce_in_ring(coefficients: &mut Vec, cyclo: &[BigInt], modulus: &BigInt) { + reduce_coefficients_by_cyclo(coefficients, cyclo); + reduce_and_center_coefficients_mut(coefficients, modulus); +} + +/// Reduces each element in the given slice of `BigInt` by the modulus `p`. +/// +/// This function takes a slice of `BigInt` coefficients and applies the modulus operation +/// on each element. It ensures the result is within the range `[0, p-1]` by adding `p` +/// before applying the modulus operation. The result is collected into a new `Vec`. +/// +/// # Arguments +/// +/// * `coefficients` - A slice of `BigInt` representing the coefficients to be reduced. +/// * `p` - A reference to a `BigInt` that represents the modulus value. +/// +/// # Returns +/// +/// A `Vec` where each element is reduced modulo `p`. +pub fn reduce_coefficients(coefficients: &[BigInt], p: &BigInt) -> Vec { + coefficients.iter().map(|coeff| (coeff + p) % p).collect() +} + +pub fn reduce_coefficients_2d(coefficient_matrix: &[Vec], p: &BigInt) -> Vec> { + coefficient_matrix + .iter() + .map(|coeffs| reduce_coefficients(coeffs, p)) + .collect() +} + +/// Mutably reduces each element in the given slice of `BigInt` by the modulus `p`. +/// +/// This function modifies the given mutable slice of `BigInt` coefficients in place. It adds `p` +/// to each element before applying the modulus operation, ensuring the results are within the range `[0, p-1]`. +/// +/// # Arguments +/// +/// * `coefficients` - A mutable slice of `BigInt` representing the coefficients to be reduced. +/// * `p` - A reference to a `BigInt` that represents the modulus value. +pub fn reduce_coefficients_mut(coefficients: &mut [BigInt], p: &BigInt) { + for coeff in coefficients.iter_mut() { + *coeff += p; + *coeff %= p; + } +} + +pub fn range_check_centered(vec: &[BigInt], lower_bound: &BigInt, upper_bound: &BigInt) -> bool { + vec.iter() + .all(|coeff| coeff >= lower_bound && coeff <= upper_bound) +} + +pub fn range_check_standard(vec: &[BigInt], bound: &BigInt, modulus: &BigInt) -> bool { + vec.iter().all(|coeff| { + (coeff >= &BigInt::from(0) && coeff <= bound) + || (coeff >= &(modulus - bound) && coeff < modulus) + }) +} diff --git a/packages/web-rust/src/bin/web_fhe_encrypt.rs b/packages/web-rust/src/bin/web_fhe_encrypt.rs index 5105874..3e66dbd 100644 --- a/packages/web-rust/src/bin/web_fhe_encrypt.rs +++ b/packages/web-rust/src/bin/web_fhe_encrypt.rs @@ -1,13 +1,19 @@ +mod greco; mod util; +use fhe_math::zq::Modulus; +use greco::greco::compute_input_validation_vectors; use wasm_bindgen::prelude::*; use serde::Deserialize; use std::{env, sync::Arc, thread, time}; use fhe::{ - bfv::{BfvParametersBuilder, Ciphertext, Encoding, Plaintext, PublicKey, SecretKey}, + bfv::{ + BfvParameters, BfvParametersBuilder, Ciphertext, Encoding, Plaintext, PublicKey, SecretKey, + }, mbfv::{AggregateIter, CommonRandomPoly, DecryptionShare, PublicKeyShare}, + proto::bfv::Parameters, }; use fhe_traits::{DeserializeParametrized, FheDecoder, FheEncoder, FheEncrypter, Serialize}; use rand::{distributions::Uniform, prelude::Distribution, rngs::OsRng, thread_rng, SeedableRng}; @@ -46,10 +52,32 @@ impl Encrypt { let pt = Plaintext::try_encode(&votes, Encoding::poly(), ¶ms) .map_err(|e| JsValue::from_str(&format!("Error encoding plaintext: {}", e)))?; - let ct = pk_deserialized - .try_encrypt(&pt, &mut thread_rng()) + let (ct, u_rns, e0_rns, e1_rns) = pk_deserialized + .try_encrypt_extended(&pt, &mut thread_rng()) .map_err(|e| JsValue::from_str(&format!("Error encrypting vote: {}", e)))?; + // Create Greco input validation ZKP proof + // todo: create function that modularizes this + let ctx = params + .ctx_at_level(pt.level()) + .map_err(|e| JsValue::from_str(&format!("Error extracting context: {}", e)))?; + let t = Modulus::new(params.plaintext()).map_err(|e| { + JsValue::from_str(&format!( + "Error constructing plaintext modulus object: {}", + e + )) + })?; + let input_val_vectors = compute_input_validation_vectors( + ctx, + &t, + &pt, + &u_rns, + &e0_rns, + &e1_rns, + &ct, + &pk_deserialized, + ); + self.encrypted_vote = ct.to_bytes(); Ok(self.encrypted_vote.clone()) } From f7bef8348684086303313eb561323d0c4e85d678 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Tue, 17 Sep 2024 16:43:22 +0500 Subject: [PATCH 27/62] feat: commit ciphertext hash inside guest and update compute provider --- packages/compute_provider/Cargo.lock | 448 +----------------- packages/compute_provider/Cargo.toml | 2 - .../compute_provider/src/ciphertext_output.rs | 18 + .../compute_provider/src/compute_input.rs | 18 +- .../compute_provider/src/compute_manager.rs | 88 ++-- packages/compute_provider/src/lib.rs | 98 +--- packages/compute_provider/src/provider.rs | 38 -- packages/risc0/Cargo.lock | 12 +- packages/risc0/Cargo.toml | 2 +- packages/risc0/apps/Cargo.toml | 3 +- packages/risc0/apps/src/lib.rs | 24 +- packages/risc0/contracts/CRISPRisc0.sol | 4 +- packages/risc0/core/Cargo.toml | 9 + packages/risc0/core/src/lib.rs | 17 + packages/risc0/methods/guest/Cargo.lock | 16 +- packages/risc0/methods/guest/Cargo.toml | 1 + .../risc0/methods/guest/src/bin/voting.rs | 5 +- packages/server/Cargo.lock | 12 +- .../src/enclave_server/blockchain/handlers.rs | 5 +- 19 files changed, 168 insertions(+), 652 deletions(-) create mode 100644 packages/compute_provider/src/ciphertext_output.rs delete mode 100644 packages/compute_provider/src/provider.rs create mode 100644 packages/risc0/core/Cargo.toml create mode 100644 packages/risc0/core/src/lib.rs diff --git a/packages/compute_provider/Cargo.lock b/packages/compute_provider/Cargo.lock index 6ec6d30..4cad185 100644 --- a/packages/compute_provider/Cargo.lock +++ b/packages/compute_provider/Cargo.lock @@ -23,12 +23,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "anyhow" -version = "1.0.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" - [[package]] name = "ark-bn254" version = "0.4.0" @@ -51,8 +45,8 @@ dependencies = [ "ark-serialize", "ark-std", "derivative", - "hashbrown 0.13.2", - "itertools 0.10.5", + "hashbrown", + "itertools", "num-traits", "zeroize", ] @@ -69,7 +63,7 @@ dependencies = [ "ark-std", "derivative", "digest", - "itertools 0.10.5", + "itertools", "num-bigint", "num-traits", "paste", @@ -110,7 +104,7 @@ dependencies = [ "ark-serialize", "ark-std", "derivative", - "hashbrown 0.13.2", + "hashbrown", ] [[package]] @@ -152,12 +146,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" -[[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - [[package]] name = "block-buffer" version = "0.10.4" @@ -173,12 +161,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" -[[package]] -name = "bytes" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" - [[package]] name = "cfg-if" version = "1.0.0" @@ -191,8 +173,6 @@ version = "0.1.0" dependencies = [ "ark-bn254", "ark-ff", - "fhe", - "fhe-traits", "hex", "light-poseidon", "num-bigint", @@ -276,116 +256,12 @@ dependencies = [ "crypto-common", ] -[[package]] -name = "doc-comment" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" - [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "errno" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "ethnum" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b90ca2580b73ab6a1f724b76ca11ab632df820fd6040c336200d2c1df7b3c82c" - -[[package]] -name = "fastrand" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" - -[[package]] -name = "fhe" -version = "0.1.0-beta.7" -source = "git+https://github.com/gnosisguild/fhe.rs#93ce9d45374bdc52ad02e5320a11e13eb777e1eb" -dependencies = [ - "doc-comment", - "fhe-math", - "fhe-traits", - "fhe-util", - "itertools 0.12.1", - "ndarray", - "num-bigint", - "num-traits", - "prost", - "prost-build", - "rand", - "rand_chacha", - "serde", - "thiserror", - "zeroize", - "zeroize_derive", -] - -[[package]] -name = "fhe-math" -version = "0.1.0-beta.7" -source = "git+https://github.com/gnosisguild/fhe.rs#93ce9d45374bdc52ad02e5320a11e13eb777e1eb" -dependencies = [ - "ethnum", - "fhe-traits", - "fhe-util", - "itertools 0.12.1", - "ndarray", - "num-bigint", - "num-bigint-dig", - "num-traits", - "prost", - "prost-build", - "rand", - "rand_chacha", - "sha2", - "thiserror", - "zeroize", -] - -[[package]] -name = "fhe-traits" -version = "0.1.0-beta.7" -source = "git+https://github.com/gnosisguild/fhe.rs#93ce9d45374bdc52ad02e5320a11e13eb777e1eb" -dependencies = [ - "rand", -] - -[[package]] -name = "fhe-util" -version = "0.1.0-beta.7" -source = "git+https://github.com/gnosisguild/fhe.rs#93ce9d45374bdc52ad02e5320a11e13eb777e1eb" -dependencies = [ - "itertools 0.12.1", - "num-bigint-dig", - "num-traits", - "rand", -] - -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - [[package]] name = "generic-array" version = "0.14.7" @@ -416,34 +292,12 @@ dependencies = [ "ahash", ] -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "indexmap" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" -dependencies = [ - "equivalent", - "hashbrown 0.14.5", -] - [[package]] name = "itertools" version = "0.10.5" @@ -453,15 +307,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "keccak" version = "0.1.5" @@ -476,9 +321,6 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] [[package]] name = "libc" @@ -486,12 +328,6 @@ version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" -[[package]] -name = "libm" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - [[package]] name = "light-poseidon" version = "0.2.0" @@ -504,12 +340,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "linux-raw-sys" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" - [[package]] name = "log" version = "0.4.22" @@ -525,41 +355,12 @@ dependencies = [ "regex-automata 0.1.10", ] -[[package]] -name = "matrixmultiply" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" -dependencies = [ - "autocfg", - "rawpointer", -] - [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "multimap" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" - -[[package]] -name = "ndarray" -version = "0.15.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" -dependencies = [ - "matrixmultiply", - "num-complex", - "num-integer", - "num-traits", - "rawpointer", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -580,32 +381,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-bigint-dig" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" -dependencies = [ - "byteorder", - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand", - "serde", - "smallvec", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - [[package]] name = "num-integer" version = "0.1.46" @@ -615,17 +390,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -653,16 +417,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "petgraph" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" -dependencies = [ - "fixedbitset", - "indexmap", -] - [[package]] name = "pin-project-lite" version = "0.2.14" @@ -678,16 +432,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "prettyplease" -version = "0.2.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" -dependencies = [ - "proc-macro2", - "syn 2.0.77", -] - [[package]] name = "proc-macro2" version = "1.0.86" @@ -697,59 +441,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "prost" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-build" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" -dependencies = [ - "bytes", - "heck", - "itertools 0.12.1", - "log", - "multimap", - "once_cell", - "petgraph", - "prettyplease", - "prost", - "prost-types", - "regex", - "syn 2.0.77", - "tempfile", -] - -[[package]] -name = "prost-derive" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" -dependencies = [ - "anyhow", - "itertools 0.12.1", - "proc-macro2", - "quote", - "syn 2.0.77", -] - -[[package]] -name = "prost-types" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" -dependencies = [ - "prost", -] - [[package]] name = "quote" version = "1.0.37" @@ -789,12 +480,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rawpointer" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" - [[package]] name = "rayon" version = "1.10.0" @@ -868,19 +553,6 @@ dependencies = [ "semver", ] -[[package]] -name = "rustix" -version = "0.38.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] - [[package]] name = "semver" version = "1.0.23" @@ -907,17 +579,6 @@ dependencies = [ "syn 2.0.77", ] -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "sha3" version = "0.10.8" @@ -943,12 +604,6 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - [[package]] name = "syn" version = "1.0.109" @@ -971,19 +626,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "tempfile" -version = "3.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" -dependencies = [ - "cfg-if", - "fastrand", - "once_cell", - "rustix", - "windows-sys 0.59.0", -] - [[package]] name = "thiserror" version = "1.0.63" @@ -1124,88 +766,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - [[package]] name = "zerocopy" version = "0.7.35" diff --git a/packages/compute_provider/Cargo.toml b/packages/compute_provider/Cargo.toml index 1ded129..fc01c1a 100644 --- a/packages/compute_provider/Cargo.toml +++ b/packages/compute_provider/Cargo.toml @@ -4,8 +4,6 @@ version = "0.1.0" edition = "2021" [dependencies] -fhe = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } -fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } serde = { version = "1.0", features = ["derive", "std"] } zk-kit-imt = "0.0.5" sha3 = "0.10.8" diff --git a/packages/compute_provider/src/ciphertext_output.rs b/packages/compute_provider/src/ciphertext_output.rs new file mode 100644 index 0000000..1f32758 --- /dev/null +++ b/packages/compute_provider/src/ciphertext_output.rs @@ -0,0 +1,18 @@ +use crate::compute_input::ComputeInput; + +pub trait ComputeProvider { + type Output: Send + Sync; + + fn prove( + &self, + input: &ComputeInput + ) -> Self::Output; +} + + +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct ComputeResult { + pub ciphertext_hash: Vec, + pub params_hash: Vec, + pub merkle_root: Vec, +} \ No newline at end of file diff --git a/packages/compute_provider/src/compute_input.rs b/packages/compute_provider/src/compute_input.rs index c713b4a..cd73cb8 100644 --- a/packages/compute_provider/src/compute_input.rs +++ b/packages/compute_provider/src/compute_input.rs @@ -1,8 +1,7 @@ +use crate::ciphertext_output::ComputeResult; use crate::merkle_tree::MerkleTree; use sha3::{Digest, Keccak256}; -use crate::provider::ComputeResult; - pub type FHEProcessor = fn(&FHEInputs) -> Vec; #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] @@ -14,6 +13,7 @@ pub struct FHEInputs { #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct ComputeInput { pub fhe_inputs: FHEInputs, + pub ciphertext_hash: Vec, pub leaf_hashes: Vec, pub tree_depth: usize, pub zero_node: String, @@ -23,6 +23,10 @@ pub struct ComputeInput { impl ComputeInput { pub fn process(&self, fhe_processor: FHEProcessor) -> ComputeResult { let processed_ciphertext = (fhe_processor)(&self.fhe_inputs); + let processed_hash = Keccak256::digest(&processed_ciphertext).to_vec(); + let params_hash = Keccak256::digest(&self.fhe_inputs.params).to_vec(); + + assert_eq!(processed_hash, self.ciphertext_hash, "Ciphertext hash mismatch"); let merkle_root = MerkleTree { leaf_hashes: self.leaf_hashes.clone(), @@ -34,16 +38,10 @@ impl ComputeInput { .root() .unwrap(); - let params_hash = hex::encode( - Keccak256::new() - .chain_update(&self.fhe_inputs.params) - .finalize(), - ); - ComputeResult { - ciphertext: processed_ciphertext, + ciphertext_hash: processed_hash, params_hash, - merkle_root, + merkle_root: hex::decode(merkle_root).unwrap(), } } } diff --git a/packages/compute_provider/src/compute_manager.rs b/packages/compute_provider/src/compute_manager.rs index a75aab6..b60ba8d 100644 --- a/packages/compute_provider/src/compute_manager.rs +++ b/packages/compute_provider/src/compute_manager.rs @@ -1,8 +1,9 @@ -use crate::provider::ComputeProvider; -use crate::compute_input::{ComputeInput,FHEInputs}; +use crate::ciphertext_output::ComputeProvider; +use crate::compute_input::{ComputeInput, FHEInputs}; use crate::merkle_tree::MerkleTree; -use crate::ComputeOutput; +use crate::FHEProcessor; use rayon::prelude::*; +use sha3::{Digest, Keccak256}; use std::sync::Arc; pub struct ComputeManager

@@ -11,30 +12,39 @@ where { input: ComputeInput, provider: P, + processor: FHEProcessor, use_parallel: bool, batch_size: Option, } impl

ComputeManager

where - P: ComputeProvider + Send + Sync + P: ComputeProvider + Send + Sync, { - pub fn new(provider: P, fhe_inputs: FHEInputs, use_parallel: bool, batch_size: Option) -> Self { + pub fn new( + provider: P, + fhe_inputs: FHEInputs, + fhe_processor: FHEProcessor, + use_parallel: bool, + batch_size: Option, + ) -> Self { Self { provider, input: ComputeInput { fhe_inputs, + ciphertext_hash: Vec::new(), leaf_hashes: Vec::new(), tree_depth: 10, zero_node: String::from("0"), arity: 2, }, + processor: fhe_processor, use_parallel, batch_size, } } - pub fn start(&mut self) -> P::Output { + pub fn start(&mut self) -> (P::Output, Vec) { if self.use_parallel { self.start_parallel() } else { @@ -42,7 +52,7 @@ where } } - fn start_sequential(&mut self) -> P::Output { + fn start_sequential(&mut self) -> (P::Output, Vec) { let mut tree_handler = MerkleTree::new( self.input.tree_depth, self.input.zero_node.clone(), @@ -51,10 +61,16 @@ where tree_handler.compute_leaf_hashes(&self.input.fhe_inputs.ciphertexts); self.input.leaf_hashes = tree_handler.leaf_hashes.clone(); - self.provider.prove(&self.input) + // Compute the ciphertext + let ciphertext = (self.processor)(&self.input.fhe_inputs); + + // Compute the hash of the ciphertext + self.input.ciphertext_hash = Keccak256::digest(&ciphertext).to_vec(); + + (self.provider.prove(&self.input), ciphertext) } - fn start_parallel(&self) -> P::Output { + fn start_parallel(&self) -> (P::Output, Vec) { let batch_size = self.batch_size.unwrap_or(1); let parallel_tree_depth = (batch_size as f64).log2().ceil() as usize; @@ -66,41 +82,59 @@ where .map(|chunk| chunk.to_vec()) .collect(); - let tally_results: Vec = chunks + let tally_results: Vec<(P::Output, Vec, String)> = chunks .into_par_iter() .map(|chunk| { let mut tree_handler = MerkleTree::new(parallel_tree_depth, "0".to_string(), 2); tree_handler.compute_leaf_hashes(&chunk); - + let merkle_root = tree_handler.build_tree().root().unwrap(); + + let fhe_inputs = FHEInputs { + ciphertexts: chunk.clone(), + params: params.to_vec(), + }; + + let ciphertext = (self.processor)(&fhe_inputs); + let ciphertext_hash = Keccak256::digest(&ciphertext).to_vec(); + let input = ComputeInput { - fhe_inputs: FHEInputs { - ciphertexts: chunk.clone(), - params: params.to_vec(), // Params are shared across chunks - }, + fhe_inputs, + ciphertext_hash, leaf_hashes: tree_handler.leaf_hashes.clone(), tree_depth: parallel_tree_depth, zero_node: "0".to_string(), arity: 2, }; - self.provider.prove(&input) + (self.provider.prove(&input), ciphertext, merkle_root) }) .collect(); // Combine the sorted results for final computation + // The final depth is the input tree depth minus the parallel tree depth let final_depth = self.input.tree_depth - parallel_tree_depth; - let mut final_input = ComputeInput { - fhe_inputs: FHEInputs { - ciphertexts: tally_results - .iter() - .map(|result| result.ciphertext().clone()) - .collect(), - params: params.to_vec(), - }, - leaf_hashes: tally_results + // The leaf hashes are the hashes of the merkle roots of the parallel trees + let leaf_hashes: Vec = tally_results + .iter() + .map(|result| result.2.clone()) + .collect(); + // The params are the same for all parallel trees + let fhe_inputs = FHEInputs { + // The ciphertexts are the final ciphertexts of the parallel trees + ciphertexts: tally_results .iter() - .map(|result| result.merkle_root().clone()) + .map(|result| result.1.clone()) .collect(), + params: params.to_vec(), + }; + + let ciphertext = (self.processor)(&fhe_inputs); + let ciphertext_hash = Keccak256::digest(&ciphertext).to_vec(); + + let mut final_input = ComputeInput { + fhe_inputs, + ciphertext_hash, + leaf_hashes: leaf_hashes.clone(), tree_depth: final_depth, zero_node: String::from("0"), arity: 2, @@ -113,6 +147,6 @@ where ); final_input.zero_node = final_tree_handler.zeroes()[parallel_tree_depth].clone(); - self.provider.prove(&final_input) + (self.provider.prove(&final_input), ciphertext) } } diff --git a/packages/compute_provider/src/lib.rs b/packages/compute_provider/src/lib.rs index 53477c7..1360e93 100644 --- a/packages/compute_provider/src/lib.rs +++ b/packages/compute_provider/src/lib.rs @@ -1,102 +1,8 @@ mod compute_input; mod compute_manager; mod merkle_tree; -mod provider; +mod ciphertext_output; pub use compute_manager::*; pub use compute_input::*; -pub use provider::*; - -use fhe::bfv::{BfvParameters, Ciphertext}; -use fhe_traits::{Deserialize, DeserializeParametrized, Serialize}; -use std::sync::Arc; - -/// Example Implementation of the CiphertextProcessor function -pub fn default_fhe_processor(fhe_inputs: &FHEInputs) -> Vec { - let params = Arc::new(BfvParameters::try_deserialize(&fhe_inputs.params).unwrap()); - - let mut sum = Ciphertext::zero(¶ms); - for ciphertext_bytes in &fhe_inputs.ciphertexts { - let ciphertext = Ciphertext::from_bytes(ciphertext_bytes, ¶ms).unwrap(); - sum += &ciphertext; - } - - sum.to_bytes() -} - -// #[cfg(test)] -// mod tests { -// use super::*; -// use compute_provider_methods::COMPUTE_PROVIDER_ELF; -// use fhe::bfv::{ -// BfvParameters, BfvParametersBuilder, Ciphertext, Encoding, Plaintext, PublicKey, SecretKey, -// }; -// use fhe_traits::{ -// DeserializeParametrized, FheDecoder, FheDecrypter, FheEncoder, FheEncrypter, Serialize, -// }; -// use rand::thread_rng; -// use std::sync::Arc; - -// #[test] -// fn test_compute_provider() { -// let params = create_params(); -// let (sk, pk) = generate_keys(¶ms); -// let inputs = vec![1, 1, 0, 1]; -// let ciphertexts = encrypt_inputs(&inputs, &pk, ¶ms); - -// let ciphertext_inputs = FHEInputs { -// ciphertexts: ciphertexts.iter().map(|c| c.to_bytes()).collect(), -// params: params.to_bytes(), -// }; - -// let mut provider = ComputeProvider::new(ciphertext_inputs, false, None); -// let (result, _seal) = provider.start(COMPUTE_PROVIDER_ELF); - -// let tally = decrypt_result(&result, &sk, ¶ms); - -// assert_eq!(tally, inputs.iter().sum::()); -// } - -// fn create_params() -> Arc { -// BfvParametersBuilder::new() -// .set_degree(1024) -// .set_plaintext_modulus(65537) -// .set_moduli(&[1152921504606584833]) -// .build_arc() -// .expect("Failed to build parameters") -// } - -// fn generate_keys(params: &Arc) -> (SecretKey, PublicKey) { -// let mut rng = thread_rng(); -// let sk = SecretKey::random(params, &mut rng); -// let pk = PublicKey::new(&sk, &mut rng); -// (sk, pk) -// } - -// fn encrypt_inputs( -// inputs: &[u64], -// pk: &PublicKey, -// params: &Arc, -// ) -> Vec { -// let mut rng = thread_rng(); -// inputs -// .iter() -// .map(|&input| { -// let pt = Plaintext::try_encode(&[input], Encoding::poly(), params) -// .expect("Failed to encode plaintext"); -// pk.try_encrypt(&pt, &mut rng).expect("Failed to encrypt") -// }) -// .collect() -// } - -// fn decrypt_result( -// result: &ComputationResult, -// sk: &SecretKey, -// params: &Arc, -// ) -> u64 { -// let ct = Ciphertext::from_bytes(&result.ciphertext, params) -// .expect("Failed to deserialize ciphertext"); -// let decrypted = sk.try_decrypt(&ct).expect("Failed to decrypt"); -// Vec::::try_decode(&decrypted, Encoding::poly()).expect("Failed to decode")[0] -// } -// } +pub use ciphertext_output::*; \ No newline at end of file diff --git a/packages/compute_provider/src/provider.rs b/packages/compute_provider/src/provider.rs deleted file mode 100644 index 34a082b..0000000 --- a/packages/compute_provider/src/provider.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::compute_input::ComputeInput; - -pub trait ComputeOutput { - fn ciphertext(&self) -> &Vec; - fn merkle_root(&self) -> String; - fn params_hash(&self) -> String; -} - -pub trait ComputeProvider { - type Output: ComputeOutput + Send + Sync; - - fn prove( - &self, - input: &ComputeInput - ) -> Self::Output; -} - - -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] -pub struct ComputeResult { - pub ciphertext: Vec, - pub params_hash: String, - pub merkle_root: String, -} - -impl ComputeOutput for ComputeResult { - fn ciphertext(&self) -> &Vec { - &self.ciphertext - } - - fn merkle_root(&self) -> String { - self.merkle_root.clone() - } - - fn params_hash(&self) -> String { - self.params_hash.clone() - } -} diff --git a/packages/risc0/Cargo.lock b/packages/risc0/Cargo.lock index 059b487..3d3b138 100644 --- a/packages/risc0/Cargo.lock +++ b/packages/risc0/Cargo.lock @@ -926,8 +926,6 @@ version = "0.1.0" dependencies = [ "ark-bn254", "ark-ff 0.4.2", - "fhe", - "fhe-traits", "hex", "light-poseidon", "num-bigint", @@ -4840,6 +4838,15 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "voting-core" +version = "0.1.0" +dependencies = [ + "compute-provider", + "fhe", + "fhe-traits", +] + [[package]] name = "voting-risc0" version = "0.1.0" @@ -4859,6 +4866,7 @@ dependencies = [ "risc0-ethereum-contracts", "risc0-zkvm", "tokio", + "voting-core", ] [[package]] diff --git a/packages/risc0/Cargo.toml b/packages/risc0/Cargo.toml index b519d3a..3f36659 100644 --- a/packages/risc0/Cargo.toml +++ b/packages/risc0/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = ["apps", "methods"] +members = ["apps", "core", "methods"] exclude = ["lib"] [workspace.package] diff --git a/packages/risc0/apps/Cargo.toml b/packages/risc0/apps/Cargo.toml index 9d309c7..18fedd1 100644 --- a/packages/risc0/apps/Cargo.toml +++ b/packages/risc0/apps/Cargo.toml @@ -18,4 +18,5 @@ tokio = { version = "1.35", features = ["full"] } compute-provider = { workspace = true } fhe = { workspace = true } fhe-traits = { workspace = true } -fhe-util = { workspace = true } \ No newline at end of file +fhe-util = { workspace = true } +voting-core = { path = "../core" } \ No newline at end of file diff --git a/packages/risc0/apps/src/lib.rs b/packages/risc0/apps/src/lib.rs index 1ff4cbe..536f4ea 100644 --- a/packages/risc0/apps/src/lib.rs +++ b/packages/risc0/apps/src/lib.rs @@ -1,7 +1,7 @@ -// src/lib.rs +use voting_core::fhe_processor; use anyhow::Result; use compute_provider::{ - ComputeInput, ComputeManager, ComputeOutput, ComputeProvider, ComputeResult, FHEInputs, + ComputeInput, ComputeManager, ComputeProvider, ComputeResult, FHEInputs, }; use methods::VOTING_ELF; use risc0_ethereum_contracts::groth16; @@ -13,20 +13,6 @@ pub struct Risc0Output { pub seal: Vec, } -impl ComputeOutput for Risc0Output { - fn ciphertext(&self) -> &Vec { - &self.result.ciphertext - } - - fn merkle_root(&self) -> String { - self.result.merkle_root.clone() - } - - fn params_hash(&self) -> String { - self.result.params_hash.clone() - } -} - impl ComputeProvider for Risc0Provider { type Output = Risc0Output; @@ -59,12 +45,12 @@ impl ComputeProvider for Risc0Provider { } } -pub fn run_compute(params: FHEInputs) -> Result<(ComputeResult, Vec)> { +pub fn run_compute(params: FHEInputs) -> Result<(Risc0Output, Vec)> { let risc0_provider = Risc0Provider; - let mut provider = ComputeManager::new(risc0_provider, params, false, None); + let mut provider = ComputeManager::new(risc0_provider, params, fhe_processor, false, None); let output = provider.start(); - Ok((output.result, output.seal)) + Ok(output) } diff --git a/packages/risc0/contracts/CRISPRisc0.sol b/packages/risc0/contracts/CRISPRisc0.sol index bdbb3d5..78c7477 100644 --- a/packages/risc0/contracts/CRISPRisc0.sol +++ b/packages/risc0/contracts/CRISPRisc0.sol @@ -43,11 +43,11 @@ contract CRISPRisc0 is CRISPBase { ) external view override returns (bytes memory, bool) { require(paramsHashes[e3Id] != bytes32(0), E3DoesNotExist()); uint256 inputRoot = enclave.getInputRoot(e3Id); - (bytes memory ciphertext, bytes memory seal) = abi.decode( + (bytes memory ciphertext_hash, bytes memory seal) = abi.decode( data, (bytes, bytes) ); - bytes memory journal = abi.encode(ciphertext, inputRoot, paramsHashes[e3id]); + bytes memory journal = abi.encode(ciphertext_hash, bytes(inputRoot), bytes(paramsHashes[e3id])); verifier.verify(seal, imageId, sha256(journal)); return (ciphertext, true); } diff --git a/packages/risc0/core/Cargo.toml b/packages/risc0/core/Cargo.toml new file mode 100644 index 0000000..8b5e35e --- /dev/null +++ b/packages/risc0/core/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "voting-core" +version = "0.1.0" +edition = "2021" + +[dependencies] +fhe = { workspace = true } +fhe-traits = { workspace = true } +compute-provider = { workspace = true } diff --git a/packages/risc0/core/src/lib.rs b/packages/risc0/core/src/lib.rs new file mode 100644 index 0000000..2a62d2a --- /dev/null +++ b/packages/risc0/core/src/lib.rs @@ -0,0 +1,17 @@ +use fhe::bfv::{BfvParameters, Ciphertext}; +use fhe_traits::{Deserialize, DeserializeParametrized, Serialize}; +use compute_provider::FHEInputs; +use std::sync::Arc; + +/// CRISP Implementation of the CiphertextProcessor function +pub fn fhe_processor(fhe_inputs: &FHEInputs) -> Vec { + let params = Arc::new(BfvParameters::try_deserialize(&fhe_inputs.params).unwrap()); + + let mut sum = Ciphertext::zero(¶ms); + for ciphertext_bytes in &fhe_inputs.ciphertexts { + let ciphertext = Ciphertext::from_bytes(ciphertext_bytes, ¶ms).unwrap(); + sum += &ciphertext; + } + + sum.to_bytes() +} \ No newline at end of file diff --git a/packages/risc0/methods/guest/Cargo.lock b/packages/risc0/methods/guest/Cargo.lock index f634dd1..43495ab 100644 --- a/packages/risc0/methods/guest/Cargo.lock +++ b/packages/risc0/methods/guest/Cargo.lock @@ -470,8 +470,6 @@ version = "0.1.0" dependencies = [ "ark-bn254", "ark-ff 0.4.2", - "fhe", - "fhe-traits", "hex", "light-poseidon", "num-bigint", @@ -873,6 +871,7 @@ dependencies = [ "alloy-sol-types", "compute-provider", "risc0-zkvm", + "voting-core", ] [[package]] @@ -1360,7 +1359,7 @@ checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes", "heck", - "itertools 0.12.1", + "itertools 0.10.5", "log", "multimap", "once_cell", @@ -1380,7 +1379,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.10.5", "proc-macro2", "quote", "syn 2.0.77", @@ -2134,6 +2133,15 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "voting-core" +version = "0.1.0" +dependencies = [ + "compute-provider", + "fhe", + "fhe-traits", +] + [[package]] name = "wait-timeout" version = "0.2.0" diff --git a/packages/risc0/methods/guest/Cargo.toml b/packages/risc0/methods/guest/Cargo.toml index cec5ae1..6facbee 100644 --- a/packages/risc0/methods/guest/Cargo.toml +++ b/packages/risc0/methods/guest/Cargo.toml @@ -14,6 +14,7 @@ alloy-primitives = { version = "0.6", default-features = false, features = ["rlp alloy-sol-types = { version = "0.6" } risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] } compute-provider = { path = "../../../compute_provider" } +voting-core ={ path = "../../core" } [profile.release] lto = "thin" diff --git a/packages/risc0/methods/guest/src/bin/voting.rs b/packages/risc0/methods/guest/src/bin/voting.rs index 4f09030..e838284 100644 --- a/packages/risc0/methods/guest/src/bin/voting.rs +++ b/packages/risc0/methods/guest/src/bin/voting.rs @@ -1,10 +1,11 @@ use risc0_zkvm::guest::env; -use compute_provider::{ComputeInput, ComputeResult, default_fhe_processor}; +use compute_provider::{ComputeInput, ComputeResult}; +use voting_core::fhe_processor; fn main() { let input: ComputeInput = env::read(); - let result: ComputeResult = input.process(default_fhe_processor); + let result: ComputeResult = input.process(fhe_processor); env::commit(&result); } diff --git a/packages/server/Cargo.lock b/packages/server/Cargo.lock index 6d24c64..1abd869 100644 --- a/packages/server/Cargo.lock +++ b/packages/server/Cargo.lock @@ -1906,8 +1906,6 @@ version = "0.1.0" dependencies = [ "ark-bn254", "ark-ff 0.4.2", - "fhe", - "fhe-traits", "hex", "light-poseidon", "num-bigint", @@ -7117,6 +7115,15 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "voting-core" +version = "0.1.0" +dependencies = [ + "compute-provider", + "fhe", + "fhe-traits", +] + [[package]] name = "voting-risc0" version = "0.1.0" @@ -7136,6 +7143,7 @@ dependencies = [ "risc0-ethereum-contracts", "risc0-zkvm", "tokio", + "voting-core", ] [[package]] diff --git a/packages/server/src/enclave_server/blockchain/handlers.rs b/packages/server/src/enclave_server/blockchain/handlers.rs index 42d4f42..6a844a6 100644 --- a/packages/server/src/enclave_server/blockchain/handlers.rs +++ b/packages/server/src/enclave_server/blockchain/handlers.rs @@ -6,6 +6,7 @@ use crate::enclave_server::database::{generate_emoji, get_e3, increment_e3_round use crate::enclave_server::models::E3; use alloy::{ rpc::types::Log, + primitives::B256, sol_types::{SolCall, SolEvent}, }; use alloy_sol_types::SolValue; @@ -103,11 +104,11 @@ pub async fn handle_e3( }; // Call Compute Provider in a separate thread - let (compute_result, seal) = tokio::task::spawn_blocking(move || { + let (risc0_output, ciphertext) = tokio::task::spawn_blocking(move || { run_compute(fhe_inputs).unwrap() }).await.unwrap(); - let data = (compute_result.ciphertext, seal); + let data = ((risc0_output.result.ciphertext_hash, risc0_output.seal), ciphertext); let encoded_data = data.abi_encode(); From 769d0864bb5bdca5db78aada0ad8e5f135048d33 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Tue, 17 Sep 2024 22:08:01 +0500 Subject: [PATCH 28/62] Switch to LeanIMT and Keep track of Index for CT hash --- packages/compute_provider/Cargo.lock | 7 +++ packages/compute_provider/Cargo.toml | 3 +- .../compute_provider/src/compute_input.rs | 8 +--- .../compute_provider/src/compute_manager.rs | 39 +++-------------- packages/compute_provider/src/merkle_tree.rs | 43 +++++-------------- packages/risc0/Cargo.lock | 7 +++ packages/risc0/core/src/lib.rs | 2 +- packages/risc0/methods/guest/Cargo.lock | 11 ++++- packages/server/Cargo.lock | 7 +++ .../src/enclave_server/blockchain/handlers.rs | 3 +- packages/server/src/enclave_server/models.rs | 2 +- 11 files changed, 54 insertions(+), 78 deletions(-) diff --git a/packages/compute_provider/Cargo.lock b/packages/compute_provider/Cargo.lock index 4cad185..4b8d052 100644 --- a/packages/compute_provider/Cargo.lock +++ b/packages/compute_provider/Cargo.lock @@ -174,6 +174,7 @@ dependencies = [ "ark-bn254", "ark-ff", "hex", + "lean-imt", "light-poseidon", "num-bigint", "num-traits", @@ -322,6 +323,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lean-imt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e72b0683df18378c72877bfc0a1befc69b009f43e8f0ef3689f0cc98f067222" + [[package]] name = "libc" version = "0.2.158" diff --git a/packages/compute_provider/Cargo.toml b/packages/compute_provider/Cargo.toml index fc01c1a..3d66c09 100644 --- a/packages/compute_provider/Cargo.toml +++ b/packages/compute_provider/Cargo.toml @@ -4,8 +4,10 @@ version = "0.1.0" edition = "2021" [dependencies] +tracing-subscriber = { version = "0.3", features = ["env-filter"] } serde = { version = "1.0", features = ["derive", "std"] } zk-kit-imt = "0.0.5" +lean-imt = "0.1.0" sha3 = "0.10.8" num-bigint = "0.4" num-traits = "0.2" @@ -15,4 +17,3 @@ ark-ff = "0.4.2" ark-bn254 = "0.4.0" rand = "0.8" rayon = "1.10.0" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/packages/compute_provider/src/compute_input.rs b/packages/compute_provider/src/compute_input.rs index cd73cb8..6d43864 100644 --- a/packages/compute_provider/src/compute_input.rs +++ b/packages/compute_provider/src/compute_input.rs @@ -6,7 +6,7 @@ pub type FHEProcessor = fn(&FHEInputs) -> Vec; #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct FHEInputs { - pub ciphertexts: Vec>, + pub ciphertexts: Vec<(Vec, u64)>, pub params: Vec, } @@ -15,9 +15,6 @@ pub struct ComputeInput { pub fhe_inputs: FHEInputs, pub ciphertext_hash: Vec, pub leaf_hashes: Vec, - pub tree_depth: usize, - pub zero_node: String, - pub arity: usize, } impl ComputeInput { @@ -30,9 +27,6 @@ impl ComputeInput { let merkle_root = MerkleTree { leaf_hashes: self.leaf_hashes.clone(), - tree_depth: self.tree_depth, - zero_node: self.zero_node.clone(), - arity: self.arity, } .build_tree() .root() diff --git a/packages/compute_provider/src/compute_manager.rs b/packages/compute_provider/src/compute_manager.rs index b60ba8d..3e2a37d 100644 --- a/packages/compute_provider/src/compute_manager.rs +++ b/packages/compute_provider/src/compute_manager.rs @@ -34,9 +34,6 @@ where fhe_inputs, ciphertext_hash: Vec::new(), leaf_hashes: Vec::new(), - tree_depth: 10, - zero_node: String::from("0"), - arity: 2, }, processor: fhe_processor, use_parallel, @@ -53,11 +50,7 @@ where } fn start_sequential(&mut self) -> (P::Output, Vec) { - let mut tree_handler = MerkleTree::new( - self.input.tree_depth, - self.input.zero_node.clone(), - self.input.arity, - ); + let mut tree_handler = MerkleTree::new(); tree_handler.compute_leaf_hashes(&self.input.fhe_inputs.ciphertexts); self.input.leaf_hashes = tree_handler.leaf_hashes.clone(); @@ -71,13 +64,12 @@ where } fn start_parallel(&self) -> (P::Output, Vec) { - let batch_size = self.batch_size.unwrap_or(1); - let parallel_tree_depth = (batch_size as f64).log2().ceil() as usize; + let batch_size = self.batch_size.unwrap_or(2); let ciphertexts = Arc::new(self.input.fhe_inputs.ciphertexts.clone()); let params = Arc::new(self.input.fhe_inputs.params.clone()); - let chunks: Vec>> = ciphertexts + let chunks: Vec, u64)>> = ciphertexts .chunks(batch_size) .map(|chunk| chunk.to_vec()) .collect(); @@ -85,7 +77,7 @@ where let tally_results: Vec<(P::Output, Vec, String)> = chunks .into_par_iter() .map(|chunk| { - let mut tree_handler = MerkleTree::new(parallel_tree_depth, "0".to_string(), 2); + let mut tree_handler = MerkleTree::new(); tree_handler.compute_leaf_hashes(&chunk); let merkle_root = tree_handler.build_tree().root().unwrap(); @@ -101,29 +93,22 @@ where fhe_inputs, ciphertext_hash, leaf_hashes: tree_handler.leaf_hashes.clone(), - tree_depth: parallel_tree_depth, - zero_node: "0".to_string(), - arity: 2, }; (self.provider.prove(&input), ciphertext, merkle_root) }) .collect(); - // Combine the sorted results for final computation - // The final depth is the input tree depth minus the parallel tree depth - let final_depth = self.input.tree_depth - parallel_tree_depth; // The leaf hashes are the hashes of the merkle roots of the parallel trees let leaf_hashes: Vec = tally_results .iter() .map(|result| result.2.clone()) .collect(); - // The params are the same for all parallel trees + let fhe_inputs = FHEInputs { - // The ciphertexts are the final ciphertexts of the parallel trees ciphertexts: tally_results .iter() - .map(|result| result.1.clone()) + .map(|result| (result.1.clone(), 0 as u64)) // The index is not used for the final computation in the parallel case .collect(), params: params.to_vec(), }; @@ -131,22 +116,12 @@ where let ciphertext = (self.processor)(&fhe_inputs); let ciphertext_hash = Keccak256::digest(&ciphertext).to_vec(); - let mut final_input = ComputeInput { + let final_input = ComputeInput { fhe_inputs, ciphertext_hash, leaf_hashes: leaf_hashes.clone(), - tree_depth: final_depth, - zero_node: String::from("0"), - arity: 2, }; - let final_tree_handler = MerkleTree::new( - final_depth, - final_input.zero_node.clone(), - final_input.arity, - ); - final_input.zero_node = final_tree_handler.zeroes()[parallel_tree_depth].clone(); - (self.provider.prove(&final_input), ciphertext) } } diff --git a/packages/compute_provider/src/merkle_tree.rs b/packages/compute_provider/src/merkle_tree.rs index 66e88fb..9b759ae 100644 --- a/packages/compute_provider/src/merkle_tree.rs +++ b/packages/compute_provider/src/merkle_tree.rs @@ -4,40 +4,32 @@ use num_traits::Num; use ark_bn254::Fr; use ark_ff::{BigInt, BigInteger}; use light_poseidon::{Poseidon, PoseidonHasher}; -use zk_kit_imt::imt::IMT; +use lean_imt::LeanIMT; use std::str::FromStr; pub struct MerkleTree { pub leaf_hashes: Vec, - pub tree_depth: usize, - pub zero_node: String, - pub arity: usize, } impl MerkleTree { - pub fn new(tree_depth: usize, zero_node: String, arity: usize) -> Self { + pub fn new() -> Self { Self { leaf_hashes: Vec::new(), - tree_depth, - zero_node, - arity, } } - pub fn compute_leaf_hashes(&mut self, data: &[Vec]) { + pub fn compute_leaf_hashes(&mut self, data: &[(Vec, u64)]) { for item in data { - let mut keccak_hasher = Keccak256::new(); - keccak_hasher.update(item); - let hex_output = hex::encode(keccak_hasher.finalize()); + let hex_output = hex::encode(Keccak256::digest(&item.0)); let sanitized_hex = hex_output.trim_start_matches("0x"); let numeric_value = BigUint::from_str_radix(sanitized_hex, 16) .unwrap() .to_string(); let fr_element = Fr::from_str(&numeric_value).unwrap(); - let zero_element = Fr::from_str("0").unwrap(); + let index_element = Fr::from_str(&item.1.to_string()).unwrap(); let mut poseidon_instance = Poseidon::::new_circom(2).unwrap(); let hash_bigint: BigInt<4> = poseidon_instance - .hash(&[fr_element, zero_element]) + .hash(&[fr_element, index_element]) .unwrap() .into(); let hash = hex::encode(hash_bigint.to_bytes_be()); @@ -62,24 +54,9 @@ impl MerkleTree { hex::encode(result_hash.to_bytes_be()) } - pub fn zeroes(&self) -> Vec { - let mut zeroes = Vec::new(); - let mut current_zero = self.zero_node.clone(); - for _ in 0..self.tree_depth { - zeroes.push(current_zero.clone()); - current_zero = Self::poseidon_hash(vec![current_zero; self.arity]); - } - zeroes - } - - pub fn build_tree(&self) -> IMT { - IMT::new( - Self::poseidon_hash, - self.tree_depth, - self.zero_node.clone(), - self.arity, - self.leaf_hashes.clone(), - ) - .unwrap() + pub fn build_tree(&self) -> LeanIMT { + let mut tree = LeanIMT::new(Self::poseidon_hash); + tree.insert_many(self.leaf_hashes.clone()).unwrap(); + tree } } diff --git a/packages/risc0/Cargo.lock b/packages/risc0/Cargo.lock index 3d3b138..1fcbc61 100644 --- a/packages/risc0/Cargo.lock +++ b/packages/risc0/Cargo.lock @@ -927,6 +927,7 @@ dependencies = [ "ark-bn254", "ark-ff 0.4.2", "hex", + "lean-imt", "light-poseidon", "num-bigint", "num-traits", @@ -2485,6 +2486,12 @@ dependencies = [ "spin 0.5.2", ] +[[package]] +name = "lean-imt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e72b0683df18378c72877bfc0a1befc69b009f43e8f0ef3689f0cc98f067222" + [[package]] name = "libc" version = "0.2.155" diff --git a/packages/risc0/core/src/lib.rs b/packages/risc0/core/src/lib.rs index 2a62d2a..ea38dac 100644 --- a/packages/risc0/core/src/lib.rs +++ b/packages/risc0/core/src/lib.rs @@ -9,7 +9,7 @@ pub fn fhe_processor(fhe_inputs: &FHEInputs) -> Vec { let mut sum = Ciphertext::zero(¶ms); for ciphertext_bytes in &fhe_inputs.ciphertexts { - let ciphertext = Ciphertext::from_bytes(ciphertext_bytes, ¶ms).unwrap(); + let ciphertext = Ciphertext::from_bytes(&ciphertext_bytes.0, ¶ms).unwrap(); sum += &ciphertext; } diff --git a/packages/risc0/methods/guest/Cargo.lock b/packages/risc0/methods/guest/Cargo.lock index 43495ab..ff6d9c3 100644 --- a/packages/risc0/methods/guest/Cargo.lock +++ b/packages/risc0/methods/guest/Cargo.lock @@ -471,6 +471,7 @@ dependencies = [ "ark-bn254", "ark-ff 0.4.2", "hex", + "lean-imt", "light-poseidon", "num-bigint", "num-traits", @@ -1015,6 +1016,12 @@ dependencies = [ "spin", ] +[[package]] +name = "lean-imt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e72b0683df18378c72877bfc0a1befc69b009f43e8f0ef3689f0cc98f067222" + [[package]] name = "libc" version = "0.2.158" @@ -1359,7 +1366,7 @@ checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes", "heck", - "itertools 0.10.5", + "itertools 0.12.1", "log", "multimap", "once_cell", @@ -1379,7 +1386,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.77", diff --git a/packages/server/Cargo.lock b/packages/server/Cargo.lock index 1abd869..1b520aa 100644 --- a/packages/server/Cargo.lock +++ b/packages/server/Cargo.lock @@ -1907,6 +1907,7 @@ dependencies = [ "ark-bn254", "ark-ff 0.4.2", "hex", + "lean-imt", "light-poseidon", "num-bigint", "num-traits", @@ -3926,6 +3927,12 @@ dependencies = [ "spin 0.5.2", ] +[[package]] +name = "lean-imt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e72b0683df18378c72877bfc0a1befc69b009f43e8f0ef3689f0cc98f067222" + [[package]] name = "libc" version = "0.2.154" diff --git a/packages/server/src/enclave_server/blockchain/handlers.rs b/packages/server/src/enclave_server/blockchain/handlers.rs index 6a844a6..040ed45 100644 --- a/packages/server/src/enclave_server/blockchain/handlers.rs +++ b/packages/server/src/enclave_server/blockchain/handlers.rs @@ -132,8 +132,9 @@ pub fn handle_input_published(input: InputPublished) -> Result<(), Box(); let data = input.data.to_vec(); + let input_count = input.index.to::(); let (mut e3, key) = get_e3(e3_id).unwrap(); - e3.ciphertext_inputs.push(data); + e3.ciphertext_inputs.push((data,input_count)); e3.vote_count += 1; GLOBAL_DB diff --git a/packages/server/src/enclave_server/models.rs b/packages/server/src/enclave_server/models.rs index c827b0b..fb754bb 100644 --- a/packages/server/src/enclave_server/models.rs +++ b/packages/server/src/enclave_server/models.rs @@ -234,7 +234,7 @@ pub struct E3 { pub plaintext_output: Vec, // Ciphertext Inputs - pub ciphertext_inputs: Vec>, + pub ciphertext_inputs: Vec<(Vec, u64)>, // Emojis pub emojis: [String; 2], From e528e1a39470271fccc3f3937612df7f8d0773aa Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Tue, 17 Sep 2024 23:49:11 +0500 Subject: [PATCH 29/62] refactor: avoid race conditions between routes on the DB --- packages/server/Cargo.lock | 1 + packages/server/Cargo.toml | 3 +- packages/server/src/cli/auth.rs | 2 +- .../src/enclave_server/blockchain/events.rs | 28 +++-- .../src/enclave_server/blockchain/handlers.rs | 52 ++++---- .../server/src/enclave_server/database.rs | 118 ++++++++---------- packages/server/src/enclave_server/mod.rs | 13 +- packages/server/src/enclave_server/models.rs | 66 +--------- .../server/src/enclave_server/routes/auth.rs | 2 +- .../src/enclave_server/routes/rounds.rs | 25 ++-- .../server/src/enclave_server/routes/state.rs | 94 +++++++------- .../src/enclave_server/routes/voting.rs | 37 ++---- 12 files changed, 172 insertions(+), 269 deletions(-) diff --git a/packages/server/Cargo.lock b/packages/server/Cargo.lock index 1b520aa..ea1e5a2 100644 --- a/packages/server/Cargo.lock +++ b/packages/server/Cargo.lock @@ -5441,6 +5441,7 @@ dependencies = [ "serde_json", "sha2", "sled", + "termcolor", "tokio", "voting-risc0", "walkdir", diff --git a/packages/server/Cargo.toml b/packages/server/Cargo.toml index 6922fa6..17904fd 100644 --- a/packages/server/Cargo.toml +++ b/packages/server/Cargo.toml @@ -50,4 +50,5 @@ alloy-sol-types = { version = "0.6" } alloy = { version = "0.2.1", features = ["full", "rpc-types-eth"] } futures-util = "0.3" eyre = "0.6" -hex = "0.4" \ No newline at end of file +hex = "0.4" +termcolor = "1.4.1" \ No newline at end of file diff --git a/packages/server/src/cli/auth.rs b/packages/server/src/cli/auth.rs index 0c9a8d3..0317902 100644 --- a/packages/server/src/cli/auth.rs +++ b/packages/server/src/cli/auth.rs @@ -5,8 +5,8 @@ use log::info; use crate::cli::HyperClientPost; #[derive(Debug, Deserialize, Serialize)] +#[allow(non_snake_case)] pub struct AuthenticationLogin { - #[allow(non_snake_case)] pub postId: String, } diff --git a/packages/server/src/enclave_server/blockchain/events.rs b/packages/server/src/enclave_server/blockchain/events.rs index 73e3004..e4f17fc 100644 --- a/packages/server/src/enclave_server/blockchain/events.rs +++ b/packages/server/src/enclave_server/blockchain/events.rs @@ -1,12 +1,13 @@ use alloy::{ - sol, sol_types::{SolCall, SolEvent}, - rpc::types::Log + rpc::types::Log, + sol, + sol_types::{SolCall, SolEvent}, }; use eyre::Result; -use super::listener::ContractEvent; use super::handlers::{handle_e3, handle_input_published, handle_plaintext_output_published}; +use super::listener::ContractEvent; sol! { #[derive(Debug)] @@ -19,10 +20,8 @@ sol! { event PlaintextOutputPublished(uint256 indexed e3Id, bytes plaintextOutput); } - impl ContractEvent for E3Activated { fn process(&self, log: Log) -> Result<()> { - println!("Processing E3 request: {:?}", self); let event_clone = self.clone(); tokio::spawn(async move { if let Err(e) = handle_e3(event_clone, log).await { @@ -30,7 +29,6 @@ impl ContractEvent for E3Activated { } }); - Ok(()) } } @@ -38,9 +36,12 @@ impl ContractEvent for E3Activated { impl ContractEvent for InputPublished { fn process(&self, _log: Log) -> Result<()> { let event_clone = self.clone(); - if let Err(e) = handle_input_published(event_clone) { - eprintln!("Error handling input published: {:?}", e); - } + tokio::spawn(async move { + if let Err(e) = handle_input_published(event_clone).await { + eprintln!("Error handling input published: {:?}", e); + } + }); + Ok(()) } } @@ -48,9 +49,12 @@ impl ContractEvent for InputPublished { impl ContractEvent for PlaintextOutputPublished { fn process(&self, _log: Log) -> Result<()> { let event_clone = self.clone(); - if let Err(e) = handle_plaintext_output_published(event_clone) { - eprintln!("Error handling public key published: {:?}", e); - } + + tokio::spawn(async move { + if let Err(e) = handle_plaintext_output_published(event_clone).await { + eprintln!("Error handling public key published: {:?}", e); + } + }); Ok(()) } diff --git a/packages/server/src/enclave_server/blockchain/handlers.rs b/packages/server/src/enclave_server/blockchain/handlers.rs index 040ed45..8b96bfb 100644 --- a/packages/server/src/enclave_server/blockchain/handlers.rs +++ b/packages/server/src/enclave_server/blockchain/handlers.rs @@ -6,16 +6,14 @@ use crate::enclave_server::database::{generate_emoji, get_e3, increment_e3_round use crate::enclave_server::models::E3; use alloy::{ rpc::types::Log, - primitives::B256, sol_types::{SolCall, SolEvent}, }; use alloy_sol_types::SolValue; use chrono::Utc; use compute_provider::FHEInputs; -use std::env; use std::error::Error; +use std::env; use tokio::time::{sleep, Duration}; -use tokio::task; use voting_risc0::run_compute; use log::info; @@ -83,17 +81,19 @@ pub async fn handle_e3( // Save E3 to the database let key = format!("e3:{}", e3_id); - GLOBAL_DB - .insert(key, serde_json::to_vec(&e3_obj).unwrap()) + + let db = GLOBAL_DB.write().await; + db.insert(key, serde_json::to_vec(&e3_obj).unwrap()) .unwrap(); + drop(db); - increment_e3_round().unwrap(); + increment_e3_round().await.unwrap(); // Sleep till the E3 expires sleep(Duration::from_secs(e3.duration.to::())).await; // Get All Encrypted Votes - let (e3, _) = get_e3(e3_id).unwrap(); + let (e3, _) = get_e3(e3_id).await.unwrap(); if e3.vote_count > 0 { info!("E3 FROM DB"); info!("Vote Count: {:?}", e3.vote_count); @@ -104,11 +104,15 @@ pub async fn handle_e3( }; // Call Compute Provider in a separate thread - let (risc0_output, ciphertext) = tokio::task::spawn_blocking(move || { - run_compute(fhe_inputs).unwrap() - }).await.unwrap(); - - let data = ((risc0_output.result.ciphertext_hash, risc0_output.seal), ciphertext); + let (risc0_output, ciphertext) = + tokio::task::spawn_blocking(move || run_compute(fhe_inputs).unwrap()) + .await + .unwrap(); + + let data = ( + (risc0_output.result.ciphertext_hash, risc0_output.seal), + ciphertext, + ); let encoded_data = data.abi_encode(); @@ -127,36 +131,36 @@ pub async fn handle_e3( Ok(()) } -pub fn handle_input_published(input: InputPublished) -> Result<(), Box> { +pub async fn handle_input_published( + input: InputPublished, +) -> Result<(), Box> { info!("Handling VoteCast event..."); let e3_id = input.e3Id.to::(); let data = input.data.to_vec(); let input_count = input.index.to::(); - let (mut e3, key) = get_e3(e3_id).unwrap(); - e3.ciphertext_inputs.push((data,input_count)); + let (mut e3, key) = get_e3(e3_id).await.unwrap(); + e3.ciphertext_inputs.push((data, input_count)); e3.vote_count += 1; - - GLOBAL_DB - .insert(key, serde_json::to_vec(&e3).unwrap()) - .unwrap(); + let db = GLOBAL_DB.write().await; + db.insert(key, serde_json::to_vec(&e3).unwrap()).unwrap(); info!("Saved Input with Hash: {:?}", input.inputHash); Ok(()) } -pub fn handle_plaintext_output_published( +pub async fn handle_plaintext_output_published( plaintext_output: PlaintextOutputPublished, ) -> Result<(), Box> { info!("Handling PlaintextOutputPublished event..."); let e3_id = plaintext_output.e3Id.to::(); - let (mut e3, key) = get_e3(e3_id).unwrap(); + let (mut e3, key) = get_e3(e3_id).await.unwrap(); e3.plaintext_output = plaintext_output.plaintextOutput.to_vec(); - GLOBAL_DB - .insert(key, serde_json::to_vec(&e3).unwrap()) - .unwrap(); + let db = GLOBAL_DB.write().await; + db.insert(key, serde_json::to_vec(&e3).unwrap()).unwrap(); + info!("PlaintextOutputPublished event handled."); Ok(()) } diff --git a/packages/server/src/enclave_server/database.rs b/packages/server/src/enclave_server/database.rs index 71b7925..4d98272 100644 --- a/packages/server/src/enclave_server/database.rs +++ b/packages/server/src/enclave_server/database.rs @@ -1,48 +1,55 @@ - -use std::{str, sync::Arc, error::Error}; +use super::models::E3; +use log::info; use once_cell::sync::Lazy; -use sled::{Db, IVec}; use rand::Rng; -use log::info; -use super::models::{Round, E3}; - -pub static GLOBAL_DB: Lazy> = Lazy::new(|| { - let pathdb = std::env::current_dir().unwrap().join("database/enclave_server"); - Arc::new(sled::open(pathdb).unwrap()) +use sled::{Db, IVec}; +use std::{error::Error, io::Read, str, sync::Arc}; +use tokio::sync::RwLock; + +pub static GLOBAL_DB: Lazy>> = Lazy::new(|| { + let pathdb = std::env::current_dir() + .unwrap() + .join("database/enclave_server"); + Arc::new(RwLock::new(sled::open(pathdb).unwrap())) }); -pub fn get_e3(e3_id: u64) -> Result<(E3, String), Box> { + +pub async fn get_e3(e3_id: u64) -> Result<(E3, String), Box> { let key = format!("e3:{}", e3_id); - let value = match GLOBAL_DB.get(key.clone()) { - Ok(Some(v)) => v, - Ok(None) => return Err(format!("E3 not found: {}", key).into()), + let db = GLOBAL_DB.read().await; + + let value = match db.get(key.clone()) { + Ok(Some(v)) => v, + Ok(None) => return Err(format!("E3 not found: {}", key).into()), Err(e) => return Err(format!("Database error: {}", e).into()), }; - let e3: E3 = serde_json::from_slice(&value) - .map_err(|e| format!("Failed to deserialize E3: {}", e))?; + let e3: E3 = + serde_json::from_slice(&value).map_err(|e| format!("Failed to deserialize E3: {}", e))?; Ok((e3, key)) } - -pub fn get_e3_round() -> Result> { +pub async fn get_e3_round() -> Result> { let key = "e3:round"; - let round_count: u64 = match GLOBAL_DB.get(key) { - Ok(Some(bytes)) => { - match bincode::deserialize::(&bytes) { - Ok(count) => count, - Err(e) => { - info!("Failed to deserialize round count: {}", e); - return Err(format!("Failed to retrieve round count").into()); - } + let db = GLOBAL_DB.read().await; + + let round_count: u64 = match db.get(key) { + Ok(Some(bytes)) => match bincode::deserialize::(&bytes) { + Ok(count) => count, + Err(e) => { + info!("Failed to deserialize round count: {}", e); + return Err(format!("Failed to retrieve round count").into()); } - } + }, Ok(None) => { + drop(db); + let db = GLOBAL_DB.write().await; + info!("Initializing first round in db"); let initial_count = 0u64; let encoded = bincode::serialize(&initial_count).unwrap(); - if let Err(e) = GLOBAL_DB.insert(key, IVec::from(encoded)) { + if let Err(e) = db.insert(key, IVec::from(encoded)) { info!("Failed to initialize first round in db: {}", e); return Err(format!("Failed to initialize round count").into()); } @@ -57,59 +64,36 @@ pub fn get_e3_round() -> Result> { Ok(round_count) } - -pub fn increment_e3_round() -> Result<(), Box> { +pub async fn increment_e3_round() -> Result<(), Box> { let key = "e3:round"; + let mut encoded = Vec::new(); - match get_e3_round() { + match get_e3_round().await { Ok(round_count) => { let new_round_count = round_count + 1; - let encoded = bincode::serialize(&new_round_count).unwrap(); - GLOBAL_DB.insert(key, IVec::from(encoded))?; + encoded = bincode::serialize(&new_round_count).unwrap(); } Err(e) => { return Err(e); } } + + let db = GLOBAL_DB.write().await; + db.insert(key, IVec::from(encoded))?; Ok(()) } -pub fn get_state(round_id: u32) -> (Round, String) { - let mut round_key = round_id.to_string(); - round_key.push_str("-storage"); - info!("Database key is {:?}", round_key); - let state_out = GLOBAL_DB.get(round_key.clone()).unwrap().unwrap(); - let state_out_str = str::from_utf8(&state_out).unwrap(); - let state_out_struct: Round = serde_json::from_str(&state_out_str).unwrap(); - (state_out_struct, round_key) -} - -pub fn get_round_count() -> u32 { - let round_key = "round_count"; - let round_db = GLOBAL_DB.get(round_key).unwrap(); - if round_db == None { - info!("initializing first round in db"); - GLOBAL_DB.insert(round_key, b"0".to_vec()).unwrap(); - } - let round_str = std::str::from_utf8(round_db.unwrap().as_ref()).unwrap().to_string(); - round_str.parse::().unwrap() -} - pub fn generate_emoji() -> (String, String) { let emojis = [ - "🍇","🍈","🍉","🍊","🍋","🍌","🍍","ðŸĨ­","🍎","🍏", - "🍐","🍑","🍒","🍓","ðŸŦ","ðŸĨ","🍅","ðŸŦ’","ðŸĨĨ","ðŸĨ‘", - "🍆","ðŸĨ”","ðŸĨ•","ðŸŒ―","ðŸŒķïļ","ðŸŦ‘","ðŸĨ’","ðŸĨŽ","ðŸĨĶ","🧄", - "🧅","🍄","ðŸĨœ","ðŸŦ˜","🌰","🍞","ðŸĨ","ðŸĨ–","ðŸŦ“","ðŸĨĻ", - "ðŸĨŊ","ðŸĨž","🧇","🧀","🍖","🍗","ðŸĨĐ","ðŸĨ“","🍔","🍟", - "🍕","🌭","ðŸĨŠ","ðŸŒŪ","ðŸŒŊ","ðŸŦ”","ðŸĨ™","🧆","ðŸĨš","ðŸģ", - "ðŸĨ˜","ðŸē","ðŸŦ•","ðŸĨĢ","ðŸĨ—","ðŸŋ","🧈","🧂","ðŸĨŦ","ðŸą", - "🍘","🍙","🍚","🍛","🍜","🍝","🍠","ðŸĒ","ðŸĢ","ðŸĪ", - "ðŸĨ","ðŸĨŪ","ðŸĄ","ðŸĨŸ","ðŸĨ ","ðŸĨĄ","ðŸĶ€","ðŸĶž","ðŸĶ","ðŸĶ‘", - "ðŸĶŠ","ðŸĶ","🍧","ðŸĻ","ðŸĐ","🍊","🎂","🍰","🧁","ðŸĨ§", - "ðŸŦ","🍎","🍭","ðŸŪ","ðŸŊ","🍞","ðŸĨ›","☕","ðŸĩ","ðŸū", - "🍷","ðŸļ","ðŸđ","🍚","ðŸŧ","ðŸĨ‚","ðŸĨƒ", + "🍇", "🍈", "🍉", "🍊", "🍋", "🍌", "🍍", "ðŸĨ­", "🍎", "🍏", "🍐", "🍑", "🍒", "🍓", "ðŸŦ", + "ðŸĨ", "🍅", "ðŸŦ’", "ðŸĨĨ", "ðŸĨ‘", "🍆", "ðŸĨ”", "ðŸĨ•", "ðŸŒ―", "ðŸŒķïļ", "ðŸŦ‘", "ðŸĨ’", "ðŸĨŽ", "ðŸĨĶ", "🧄", + "🧅", "🍄", "ðŸĨœ", "ðŸŦ˜", "🌰", "🍞", "ðŸĨ", "ðŸĨ–", "ðŸŦ“", "ðŸĨĻ", "ðŸĨŊ", "ðŸĨž", "🧇", "🧀", "🍖", + "🍗", "ðŸĨĐ", "ðŸĨ“", "🍔", "🍟", "🍕", "🌭", "ðŸĨŠ", "ðŸŒŪ", "ðŸŒŊ", "ðŸŦ”", "ðŸĨ™", "🧆", "ðŸĨš", "ðŸģ", + "ðŸĨ˜", "ðŸē", "ðŸŦ•", "ðŸĨĢ", "ðŸĨ—", "ðŸŋ", "🧈", "🧂", "ðŸĨŦ", "ðŸą", "🍘", "🍙", "🍚", "🍛", "🍜", + "🍝", "🍠", "ðŸĒ", "ðŸĢ", "ðŸĪ", "ðŸĨ", "ðŸĨŪ", "ðŸĄ", "ðŸĨŸ", "ðŸĨ ", "ðŸĨĄ", "ðŸĶ€", "ðŸĶž", "ðŸĶ", "ðŸĶ‘", + "ðŸĶŠ", "ðŸĶ", "🍧", "ðŸĻ", "ðŸĐ", "🍊", "🎂", "🍰", "🧁", "ðŸĨ§", "ðŸŦ", "🍎", "🍭", "ðŸŪ", "ðŸŊ", + "🍞", "ðŸĨ›", "☕", "ðŸĩ", "ðŸū", "🍷", "ðŸļ", "ðŸđ", "🍚", "ðŸŧ", "ðŸĨ‚", "ðŸĨƒ", ]; let mut index1 = rand::thread_rng().gen_range(0..emojis.len()); let index2 = rand::thread_rng().gen_range(0..emojis.len()); @@ -122,7 +106,3 @@ pub fn generate_emoji() -> (String, String) { }; (emojis[index1].to_string(), emojis[index2].to_string()) } - -pub fn pick_response() -> String { - "Test".to_string() -} \ No newline at end of file diff --git a/packages/server/src/enclave_server/mod.rs b/packages/server/src/enclave_server/mod.rs index 975489a..a364ea5 100644 --- a/packages/server/src/enclave_server/mod.rs +++ b/packages/server/src/enclave_server/mod.rs @@ -4,14 +4,15 @@ mod routes; pub mod blockchain; use actix_cors::Cors; -use actix_web::{web, App, HttpServer}; +use actix_web::{web, App, HttpServer, middleware::Logger}; use models::AppState; use database::GLOBAL_DB; use blockchain::listener::start_listener; use env_logger::{Builder, Target}; -use log::LevelFilter; +use log::{LevelFilter, Record}; +use std::path::Path; use std::io::Write; fn init_logger() { @@ -19,11 +20,14 @@ fn init_logger() { builder .target(Target::Stdout) .filter(None, LevelFilter::Info) - .format(|buf, record| { + .format(|buf, record: &Record| { + let file = record.file().unwrap_or("unknown"); + let filename = Path::new(file).file_name().unwrap_or_else(|| file.as_ref()); + writeln!( buf, "[{}:{}] - {}", - record.file().unwrap_or("unknown"), + filename.to_string_lossy(), record.line().unwrap_or(0), record.args() ) @@ -51,6 +55,7 @@ pub async fn start_server() -> Result<(), Box, + pub db: Arc>, } #[derive(Debug, Deserialize, Serialize)] @@ -93,10 +93,10 @@ pub struct SKSShareRequest { } #[derive(Debug, Deserialize, Serialize)] +#[allow(non_snake_case)] pub struct EncryptedVote { pub round_id: u32, pub enc_vote_bytes: Vec, - #[allow(non_snake_case)] pub postId: String, } @@ -147,64 +147,6 @@ pub struct WebResultRequest { pub struct AllWebStates { pub states: Vec, } - -#[derive(Debug, Deserialize, Serialize)] -pub struct StateWeb { - pub id: u32, - pub status: String, - pub poll_length: u32, - pub voting_address: String, - pub chain_id: u32, - pub ciphernode_count: u32, - pub pk_share_count: u32, - pub sks_share_count: u32, - pub vote_count: u32, - pub start_time: i64, - pub ciphernode_total: u32, - pub emojis: [String; 2], -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct StateLite { - pub id: u32, - pub status: String, - pub poll_length: u32, - pub voting_address: String, - pub chain_id: u32, - pub ciphernode_count: u32, - pub pk_share_count: u32, - pub sks_share_count: u32, - pub vote_count: u32, - pub crp: Vec, - pub pk: Vec, - pub start_time: i64, - pub block_start: U64, - pub ciphernode_total: u32, - pub emojis: [String; 2], -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct Round { - pub id: u32, - pub status: String, - pub poll_length: u32, - pub voting_address: String, - pub chain_id: u32, - pub ciphernode_count: u32, - pub pk_share_count: u32, - pub sks_share_count: u32, - pub vote_count: u32, - pub crp: Vec, - pub pk: Vec, - pub start_time: i64, - pub block_start: U64, - pub ciphernode_total: u32, - pub emojis: [String; 2], - pub votes_option_1: u32, - pub votes_option_2: u32, - pub ciphernodes: Vec, - pub has_voted: Vec, -} #[derive(Debug, Deserialize, Serialize)] pub struct E3 { // Identifiers @@ -284,8 +226,8 @@ pub struct AuthenticationDB { } #[derive(Debug, Deserialize, Serialize)] +#[allow(non_snake_case)] pub struct AuthenticationLogin { - #[allow(non_snake_case)] pub postId: String, } diff --git a/packages/server/src/enclave_server/routes/auth.rs b/packages/server/src/enclave_server/routes/auth.rs index 854c48c..1587016 100644 --- a/packages/server/src/enclave_server/routes/auth.rs +++ b/packages/server/src/enclave_server/routes/auth.rs @@ -24,7 +24,7 @@ async fn authentication_login(state: web::Data, data: web::Json impl Responder { - match get_e3_round() { + match get_e3_round().await { Ok(round_count) => { let count = RoundCount { round_count: round_count as u32 }; info!("round_count: {}", count.round_count); @@ -47,7 +35,7 @@ async fn get_pk_by_round( ) -> impl Responder { let mut incoming = data.into_inner(); info!("Request for round {:?} public key", incoming.round_id); - let (state_data, _) = get_e3(incoming.round_id as u64).unwrap(); + let (state_data, _) = get_e3(incoming.round_id as u64).await.unwrap(); incoming.pk_bytes = state_data.committee_public_key; HttpResponse::Ok().json(incoming) @@ -61,14 +49,15 @@ async fn report_tally( let incoming = data.into_inner(); info!("Request report tally for round {:?}", incoming.round_id); - let (mut state_data, key) = get_e3(incoming.round_id as u64).unwrap(); + let (mut state_data, key) = get_e3(incoming.round_id as u64).await.unwrap(); if state_data.votes_option_1 == 0 && state_data.votes_option_2 == 0 { state_data.votes_option_1 = incoming.option_1 as u64; state_data.votes_option_2 = incoming.option_2 as u64; let state_str = serde_json::to_string(&state_data).unwrap(); - state.db.insert(key, state_str.into_bytes()).unwrap(); + let db = state.db.write().await; + db.insert(key, state_str.into_bytes()).unwrap(); } let response = JsonResponse { diff --git a/packages/server/src/enclave_server/routes/state.rs b/packages/server/src/enclave_server/routes/state.rs index ca7fb6e..2c5bb0b 100644 --- a/packages/server/src/enclave_server/routes/state.rs +++ b/packages/server/src/enclave_server/routes/state.rs @@ -1,24 +1,28 @@ use actix_web::{web, HttpResponse, Responder}; use log::info; -use crate::enclave_server::models::{AllWebStates, E3StateLite, GetRoundRequest, StateWeb, WebResultRequest}; -use crate::enclave_server::database::{get_e3,get_e3_round, get_round_count, get_state}; +use crate::enclave_server::database::{get_e3, get_e3_round}; +use crate::enclave_server::models::{ + AllWebStates, E3StateLite, GetRoundRequest, WebResultRequest, +}; pub fn setup_routes(config: &mut web::ServiceConfig) { config .route("/get_web_result_all", web::get().to(get_web_result_all)) - .route("/get_round_state_lite", web::post().to(get_round_state_lite)) + .route( + "/get_round_state_lite", + web::post().to(get_round_state_lite), + ) .route("/get_round_state", web::post().to(get_round_state)) - .route("/get_web_result", web::post().to(get_web_result)) - .route("/get_round_state_web", web::post().to(get_round_state_web)); + .route("/get_web_result", web::post().to(get_web_result)); } async fn get_web_result(data: web::Json) -> impl Responder { let incoming = data.into_inner(); info!("Request web state for round {}", incoming.round_id); - let (state, _key) = get_e3(incoming.round_id as u64).unwrap(); - + let (state, _) = get_e3(incoming.round_id as u64).await.unwrap(); + let response = WebResultRequest { round_id: state.id, option_1_tally: state.votes_option_1, @@ -35,21 +39,36 @@ async fn get_web_result(data: web::Json) -> impl Responder { async fn get_web_result_all() -> impl Responder { info!("Request all web state."); - let round_count = get_e3_round().unwrap(); - let states: Vec = (1..round_count) - .map(|i| { - let (state, _key) = get_e3(i).unwrap(); - WebResultRequest { - round_id: i, - option_1_tally: state.votes_option_1, - option_2_tally: state.votes_option_2, - total_votes: state.votes_option_1 + state.votes_option_2, - option_1_emoji: state.emojis[0].clone(), - option_2_emoji: state.emojis[1].clone(), - end_time: state.expiration, + let round_count = match get_e3_round().await { + Ok(count) => count, + Err(e) => { + info!("Error retrieving round count: {:?}", e); + return HttpResponse::InternalServerError().body("Failed to retrieve round count"); + } + }; + + let mut states = Vec::new(); + for i in 1..round_count { + match get_e3(i).await { + Ok((state, _key)) => { + let web_result = WebResultRequest { + round_id: i, + option_1_tally: state.votes_option_1, + option_2_tally: state.votes_option_2, + total_votes: state.votes_option_1 + state.votes_option_2, + option_1_emoji: state.emojis[0].clone(), + option_2_emoji: state.emojis[1].clone(), + end_time: state.expiration, + }; + states.push(web_result); + } + Err(e) => { + info!("Error retrieving state for round {}: {:?}", i, e); + return HttpResponse::InternalServerError() + .body(format!("Failed to retrieve state for round {}", i)); } - }) - .collect(); + } + } let response = AllWebStates { states }; HttpResponse::Ok().json(response) @@ -59,39 +78,16 @@ async fn get_round_state(data: web::Json) -> impl Responder { let incoming = data.into_inner(); info!("Request state for round {}", incoming.round_id); - let (state, _key) = get_state(incoming.round_id); + let (state, _key) = get_e3(incoming.round_id as u64).await.unwrap(); HttpResponse::Ok().json(state) } -async fn get_round_state_web(data: web::Json) -> impl Responder { - let incoming = data.into_inner(); - info!("Request state for round {}", incoming.round_id); - - let (state, _key) = get_state(incoming.round_id); - let state_web = StateWeb { - id: state.id, - status: state.status, - poll_length: state.poll_length, - voting_address: state.voting_address, - chain_id: state.chain_id, - ciphernode_count: state.ciphernode_count, - pk_share_count: state.pk_share_count, - sks_share_count: state.sks_share_count, - vote_count: state.vote_count, - start_time: state.start_time, - ciphernode_total: state.ciphernode_total, - emojis: state.emojis, - }; - - HttpResponse::Ok().json(state_web) -} - async fn get_round_state_lite(data: web::Json) -> impl Responder { let incoming = data.into_inner(); info!("Request state for round {}", incoming.round_id); - match get_e3(incoming.round_id as u64) { - Ok((state, key)) => { + match get_e3(incoming.round_id as u64).await { + Ok((state, _)) => { let state_lite = E3StateLite { id: state.id, chain_id: state.chain_id, @@ -105,10 +101,10 @@ async fn get_round_state_lite(data: web::Json) -> impl Responde expiration: state.expiration, committee_public_key: state.committee_public_key, - emojis: state.emojis + emojis: state.emojis, }; HttpResponse::Ok().json(state_lite) - }, + } Err(e) => { info!("Error getting E3 state: {:?}", e); HttpResponse::InternalServerError().body("Failed to get E3 state") diff --git a/packages/server/src/enclave_server/routes/voting.rs b/packages/server/src/enclave_server/routes/voting.rs index cffe750..b5f478e 100644 --- a/packages/server/src/enclave_server/routes/voting.rs +++ b/packages/server/src/enclave_server/routes/voting.rs @@ -1,10 +1,4 @@ -use alloy::{ - network::{AnyNetwork, EthereumWallet}, - primitives::{Address, Bytes, B256, U256}, - providers::ProviderBuilder, - signers::local::PrivateKeySigner, - sol, -}; +use alloy::primitives::{Bytes, B256, U256}; use std::{env, str}; use eyre::Result; @@ -12,7 +6,7 @@ use log::info; use actix_web::{web, HttpResponse, Responder}; -use crate::enclave_server::database::{get_e3, get_state}; +use crate::enclave_server::database::get_e3; use crate::enclave_server::{ blockchain::relayer::EnclaveContract, models::{AppState, EncryptedVote, GetEmojisRequest, JsonResponseTxHash, VoteCountRequest}, @@ -33,8 +27,8 @@ async fn broadcast_enc_vote( state: web::Data, ) -> impl Responder { let vote: EncryptedVote = data.into_inner(); - let (mut state_data, key) = get_e3(vote.round_id as u64).unwrap(); - println!("Has Voted: {:?}", state_data.has_voted); + let (mut state_data, key) = get_e3(vote.round_id as u64).await.unwrap(); + if state_data.has_voted.contains(&vote.postId) { return HttpResponse::BadRequest().json(JsonResponseTxHash { response: "User has already voted".to_string(), @@ -53,11 +47,8 @@ async fn broadcast_enc_vote( }; state_data.has_voted.push(vote.postId); - - if let Err(e) = state - .db - .insert(key, serde_json::to_vec(&state_data).unwrap()) - { + let db = state.db.write().await; + if let Err(e) = db.insert(key, serde_json::to_vec(&state_data).unwrap()) { info!("Error updating state: {:?}", e); } @@ -76,7 +67,7 @@ async fn get_emojis_by_round(data: web::Json) -> impl Responde let mut incoming = data.into_inner(); info!("Request emojis for round {:?}", incoming.round_id); - let (state_data, _) = get_state(incoming.round_id); + let (state_data, _) = get_e3(incoming.round_id as u64).await.unwrap(); incoming.emojis = state_data.emojis; HttpResponse::Ok().json(incoming) @@ -87,22 +78,12 @@ async fn get_vote_count_by_round(data: web::Json) -> impl Resp let mut incoming = data.into_inner(); info!("Request vote count for round {:?}", incoming.round_id); - let (state_data, _) = get_state(incoming.round_id); - incoming.vote_count = state_data.vote_count; + let (state_data, _) = get_e3(incoming.round_id as u64).await.unwrap(); + incoming.vote_count = state_data.vote_count as u32; HttpResponse::Ok().json(incoming) } -sol! { - #[allow(missing_docs)] - #[sol(rpc)] - contract IVOTE { - function voteEncrypted(bytes memory _encVote) public; - function getVote(address id) public returns (bytes memory); - event Transfer(address indexed from, address indexed to, uint256 value); - } -} - pub async fn call_contract( e3_id: U256, enc_vote: Bytes, From 804d3a567ab726f85c6794781ee2c04cf1ef4166 Mon Sep 17 00:00:00 2001 From: fhedude Date: Wed, 18 Sep 2024 11:15:03 -0400 Subject: [PATCH 30/62] Refactored code and added new classes for greco --- packages/web-rust/src/bin/greco/greco.rs | 574 ++++++++++--------- packages/web-rust/src/bin/web_fhe_encrypt.rs | 18 +- 2 files changed, 294 insertions(+), 298 deletions(-) diff --git a/packages/web-rust/src/bin/greco/greco.rs b/packages/web-rust/src/bin/greco/greco.rs index a7248b7..7230187 100644 --- a/packages/web-rust/src/bin/greco/greco.rs +++ b/packages/web-rust/src/bin/greco/greco.rs @@ -4,74 +4,117 @@ use std::sync::Arc; use fhe::bfv::{Ciphertext, Plaintext, PublicKey}; use fhe_math::rq::{Poly, Representation}; use fhe_math::{rq::Context, zq::Modulus}; +use serde_json::json; +use crate::greco::poly::*; use itertools::izip; use num_bigint::BigInt; use num_traits::*; -use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; +use rayon::iter::{ParallelBridge, ParallelIterator}; + +/// Set of vectors for input validation of a ciphertext +#[derive(Clone, Debug)] +pub struct InputValidationVectors { + pk0is: Vec>, + pk1is: Vec>, + ct0is: Vec>, + ct1is: Vec>, + r1is: Vec>, + r2is: Vec>, + p1is: Vec>, + p2is: Vec>, + k0is: Vec, + u: Vec, + e0: Vec, + e1: Vec, + k1: Vec, +} -use crate::greco::poly::*; +impl InputValidationVectors { + /// Create a new `InputValidationVectors` with the given number of moduli and degree. + /// + /// # Arguments + /// + /// * `num_moduli` - The number of moduli, which determines the number of inner vectors in 2D vectors. + /// * `degree` - The size of each inner vector in the 2D vectors. + /// + /// # Returns + /// + /// Returns a new instance of `InputValidationVectors` with all fields initialized to zero. + pub fn new(num_moduli: usize, degree: usize) -> Self { + InputValidationVectors { + pk0is: vec![vec![BigInt::zero(); degree]; num_moduli], + pk1is: vec![vec![BigInt::zero(); degree]; num_moduli], + ct0is: vec![vec![BigInt::zero(); degree]; num_moduli], + ct1is: vec![vec![BigInt::zero(); degree]; num_moduli], + r1is: vec![vec![BigInt::zero(); 2 * (degree - 1)]; num_moduli], + r2is: vec![vec![BigInt::zero(); degree - 2]; num_moduli], + p1is: vec![vec![BigInt::zero(); 2 * (degree - 1)]; num_moduli], + p2is: vec![vec![BigInt::zero(); degree - 2]; num_moduli], + k0is: vec![BigInt::zero(); num_moduli], + u: vec![BigInt::zero(); degree], + e0: vec![BigInt::zero(); degree], + e1: vec![BigInt::zero(); degree], + k1: vec![BigInt::zero(); degree], + } + } -/// Assign and return all of the centered input validation vectors to the ZKP modulus `p`. -/// -/// # Arguments -/// -/// * `pk0is` - Centered coefficients of first public key object for each RNS modulus -/// * `pk1is` - Centered coefficients of second public key object for each RNS modulus -/// * `r2is` - Centered coefficients of r2 for each RNS modulus -/// * `r1is` - Centered coefficients of r1 for each RNS modulus -/// * `p2is` - Centered coefficients of p2 for each RNS modulus -/// * `p1is` - Centered coefficients of p1 for each RNS modulus -/// * `ct0is` - Centered coefficients of first ciphertext object for each RNS modulus -/// * `ct1is` - Centered coefficients of second ciphertext object for each RNS modulus -/// * `u` - Centered coefficients of secret polynomial used during encryption (sampled from secret key distribution) -/// * `e0` - Centered coefficients of error polynomial used during encryption (sampled from error distribution) -/// * `e1` - Centered coefficients of error polynomial used during encryption (sampled from error distribution) -/// * `k1` - Centered coefficients of [Q*m] mod t -/// * `p` - ZKP modulus -/// -pub fn input_validation_vectors_standard_form( - pk0is: &[Vec], - pk1is: &[Vec], - r2is: &[Vec], - r1is: &[Vec], - p2is: &[Vec], - p1is: &[Vec], - ct0is: &[Vec], - ct1is: &[Vec], - u: &[BigInt], - e0: &[BigInt], - e1: &[BigInt], - k1: &[BigInt], - p: &BigInt, -) -> ( - Vec>, - Vec>, - Vec>, - Vec>, - Vec>, - Vec>, - Vec>, - Vec>, - Vec, - Vec, - Vec, - Vec, -) { - ( - reduce_coefficients_2d(pk0is, p), - reduce_coefficients_2d(pk1is, p), - reduce_coefficients_2d(r2is, p), - reduce_coefficients_2d(r1is, p), - reduce_coefficients_2d(p2is, p), - reduce_coefficients_2d(p1is, p), - reduce_coefficients_2d(ct0is, p), - reduce_coefficients_2d(ct1is, p), - reduce_coefficients(u, p), - reduce_coefficients(e0, p), - reduce_coefficients(e1, p), - reduce_coefficients(k1, p), - ) + /// Assign and return all of the centered input validation vectors to the ZKP modulus `p`. + /// + /// # Arguments + /// + /// * `p` - ZKP modulus + /// + /// # Returns + /// + /// Returns a new `InputValidationVectors` struct with all coefficients reduced modulo `p`. + pub fn standard_form(&self, p: &BigInt) -> Self { + InputValidationVectors { + pk0is: reduce_coefficients_2d(&self.pk0is, p), + pk1is: reduce_coefficients_2d(&self.pk1is, p), + ct0is: reduce_coefficients_2d(&self.ct0is, p), + ct1is: reduce_coefficients_2d(&self.ct1is, p), + r1is: reduce_coefficients_2d(&self.r1is, p), + r2is: reduce_coefficients_2d(&self.r2is, p), + p1is: reduce_coefficients_2d(&self.p1is, p), + p2is: reduce_coefficients_2d(&self.p2is, p), + k0is: self.k0is.clone(), + u: reduce_coefficients(&self.u, p), + e0: reduce_coefficients(&self.e0, p), + e1: reduce_coefficients(&self.e1, p), + k1: reduce_coefficients(&self.k1, p), + } + } + + /// Convert the `InputValidationVectors` to a JSON object. + /// + /// # Returns + /// + /// Returns a `serde_json::Value` representing the JSON serialization of the `InputValidationVectors`. + pub fn to_json(&self) -> serde_json::Value { + json!({ + "pk0i": to_string_2d_vec(&self.pk0is), + "pk1i": to_string_2d_vec(&self.pk1is), + "u": to_string_1d_vec(&self.u), + "e0": to_string_1d_vec(&self.e0), + "e1": to_string_1d_vec(&self.e1), + "k1": to_string_1d_vec(&self.k1), + "r2is": to_string_2d_vec(&self.r2is), + "r1is": to_string_2d_vec(&self.r1is), + "p2is": to_string_2d_vec(&self.p2is), + "p1is": to_string_2d_vec(&self.p1is), + "ct0is": to_string_2d_vec(&self.ct0is), + "ct1is": to_string_2d_vec(&self.ct1is), + }) + } +} + +fn to_string_1d_vec(poly: &Vec) -> Vec { + poly.iter().map(|coef| coef.to_string()).collect() +} + +fn to_string_2d_vec(poly: &Vec>) -> Vec> { + poly.iter().map(|row| to_string_1d_vec(row)).collect() } /// Create the centered validation vectors necessary for creating an input validation proof according to Greco. @@ -97,21 +140,7 @@ pub fn compute_input_validation_vectors( e1_rns: &Poly, ct: &Ciphertext, pk: &PublicKey, -) -> ( - Vec>, - Vec>, - Vec, - Vec>, - Vec>, - Vec>, - Vec>, - Vec>, - Vec>, - Vec, - Vec, - Vec, - Vec, -) { +) -> InputValidationVectors { let N: u64 = ctx.degree as u64; // Calculate k1 (independent of qi), center and reverse @@ -125,15 +154,17 @@ pub fn compute_input_validation_vectors( let mut u_rns_copy = u_rns.clone(); let mut e0_rns_copy = e0_rns.clone(); let mut e1_rns_copy = e1_rns.clone(); + u_rns_copy.change_representation(Representation::PowerBasis); e0_rns_copy.change_representation(Representation::PowerBasis); e1_rns_copy.change_representation(Representation::PowerBasis); + let u: Vec = unsafe { ctx.moduli_operators()[0] .center_vec_vt(u_rns_copy.coefficients().row(0).as_slice().unwrap()) .iter() - .map(|&x| BigInt::from(x)) .rev() + .map(|&x| BigInt::from(x)) .collect() }; @@ -141,8 +172,8 @@ pub fn compute_input_validation_vectors( ctx.moduli_operators()[0] .center_vec_vt(e0_rns_copy.coefficients().row(0).as_slice().unwrap()) .iter() - .map(|&x| BigInt::from(x)) .rev() + .map(|&x| BigInt::from(x)) .collect() }; @@ -150,8 +181,8 @@ pub fn compute_input_validation_vectors( ctx.moduli_operators()[0] .center_vec_vt(e1_rns_copy.coefficients().row(0).as_slice().unwrap()) .iter() - .map(|&x| BigInt::from(x)) .rev() + .map(|&x| BigInt::from(x)) .collect() }; @@ -182,32 +213,7 @@ pub fn compute_input_validation_vectors( // Initialize matrices to store results let num_moduli = ctx.moduli().len(); - let mut r2is: Vec> = vec![Vec::new(); num_moduli]; - let mut r1is: Vec> = vec![Vec::new(); num_moduli]; - let mut k0is: Vec = vec![BigInt::zero(); num_moduli]; - let mut ct0is: Vec> = vec![Vec::new(); num_moduli]; - let mut ct0is_hat: Vec> = vec![Vec::new(); num_moduli]; - let mut ct1is: Vec> = vec![Vec::new(); num_moduli]; - let mut ct1is_hat: Vec> = vec![Vec::new(); num_moduli]; - let mut pk0is: Vec> = vec![Vec::new(); num_moduli]; - let mut pk1is: Vec> = vec![Vec::new(); num_moduli]; - let mut p1is: Vec> = vec![Vec::new(); num_moduli]; - let mut p2is: Vec> = vec![Vec::new(); num_moduli]; - - // Initialize iterators for results calculation - let moduli_operators = ctx.moduli_operators(); - let ct0_iter = ct0.coefficients(); - let ct1_iter = ct1.coefficients(); - let pk0_iter = pk0.coefficients(); - let pk1_iter = pk1.coefficients(); - let zipped: Vec<_> = izip!( - moduli_operators, - ct0_iter.rows(), - ct1_iter.rows(), - pk0_iter.rows(), - pk1_iter.rows() - ) - .collect(); + let mut res = InputValidationVectors::new(num_moduli, N as usize); // Perform the main computation logic let results: Vec<( @@ -221,188 +227,186 @@ pub fn compute_input_validation_vectors( Vec, Vec, Vec, - Vec, - Vec, - )> = zipped - .into_par_iter() - .enumerate() - .map( - |(i, (qi, ct0_coeffs, ct1_coeffs, pk0_coeffs, pk1_coeffs))| { - // --------------------------------------------------- ct0i --------------------------------------------------- - - // Convert to vectors of bigint, center, and reverse order. - let mut ct0i: Vec = - ct0_coeffs.iter().map(|&x| BigInt::from(x)).rev().collect(); - let mut ct1i: Vec = - ct1_coeffs.iter().map(|&x| BigInt::from(x)).rev().collect(); - let mut pk0i: Vec = - pk0_coeffs.iter().map(|&x| BigInt::from(x)).rev().collect(); - let mut pk1i: Vec = - pk1_coeffs.iter().map(|&x| BigInt::from(x)).rev().collect(); - - let qi_bigint = BigInt::from(qi.modulus()); - - reduce_and_center_coefficients_mut(&mut ct0i, &qi_bigint); - reduce_and_center_coefficients_mut(&mut ct1i, &qi_bigint); - reduce_and_center_coefficients_mut(&mut pk0i, &qi_bigint); - reduce_and_center_coefficients_mut(&mut pk1i, &qi_bigint); - - // k0qi = -t^{-1} mod qi - let koqi_u64 = qi.inv(qi.neg(t.modulus())).unwrap(); - let k0qi = BigInt::from(koqi_u64); // Do not need to center this - - // ki = k1 * k0qi - let ki = poly_scalar_mul(&k1, &k0qi); - - // Calculate ct0i_hat = pk0 * ui + e0i + ki - let ct0i_hat = { - let pk0i_times_u = poly_mul(&pk0i, &u); - assert_eq!((pk0i_times_u.len() as u64) - 1, 2 * (N - 1)); - - let e0_plus_ki = poly_add(&e0, &ki); - assert_eq!((e0_plus_ki.len() as u64) - 1, N - 1); - - poly_add(&pk0i_times_u, &e0_plus_ki) - }; - assert_eq!((ct0i_hat.len() as u64) - 1, 2 * (N - 1)); - - // Check whether ct0i_hat mod R_qi (the ring) is equal to ct0i - let mut ct0i_hat_mod_rqi = ct0i_hat.clone(); - reduce_in_ring(&mut ct0i_hat_mod_rqi, &cyclo, &qi_bigint); - assert_eq!(&ct0i, &ct0i_hat_mod_rqi); - - // Compute r2i numerator = ct0i - ct0i_hat and reduce/center the polynomial - let ct0i_minus_ct0i_hat = poly_sub(&ct0i, &ct0i_hat); - assert_eq!((ct0i_minus_ct0i_hat.len() as u64) - 1, 2 * (N - 1)); - let mut ct0i_minus_ct0i_hat_mod_zqi = ct0i_minus_ct0i_hat.clone(); - reduce_and_center_coefficients_mut(&mut ct0i_minus_ct0i_hat_mod_zqi, &qi_bigint); - - // Compute r2i as the quotient of numerator divided by the cyclotomic polynomial - // to produce: (ct0i - ct0i_hat) / (x^N + 1) mod Z_qi. Remainder should be empty. - let (r2i, r2i_rem) = poly_div(&ct0i_minus_ct0i_hat_mod_zqi, &cyclo); - assert!(r2i_rem.is_empty()); - assert_eq!((r2i.len() as u64) - 1, N - 2); // Order(r2i) = N - 2 - - // Assert that (ct0i - ct0i_hat) = (r2i * cyclo) mod Z_qi - let r2i_times_cyclo = poly_mul(&r2i, &cyclo); - let mut r2i_times_cyclo_mod_zqi = r2i_times_cyclo.clone(); - reduce_and_center_coefficients_mut(&mut r2i_times_cyclo_mod_zqi, &qi_bigint); - assert_eq!(&ct0i_minus_ct0i_hat_mod_zqi, &r2i_times_cyclo_mod_zqi); - assert_eq!((r2i_times_cyclo.len() as u64) - 1, 2 * (N - 1)); - - // Calculate r1i = (ct0i - ct0i_hat - r2i * cyclo) / qi mod Z_p. Remainder should be empty. - let r1i_num = poly_sub(&ct0i_minus_ct0i_hat, &r2i_times_cyclo); - assert_eq!((r1i_num.len() as u64) - 1, 2 * (N - 1)); - - let (r1i, r1i_rem) = poly_div(&r1i_num, &[qi_bigint.clone()]); - assert!(r1i_rem.is_empty()); - assert_eq!((r1i.len() as u64) - 1, 2 * (N - 1)); // Order(r1i) = 2*(N-1) - assert_eq!(&r1i_num, &poly_mul(&r1i, &[qi_bigint.clone()])); - - // Assert that ct0i = ct0i_hat + r1i * qi + r2i * cyclo mod Z_p - let r1i_times_qi = poly_scalar_mul(&r1i, &qi_bigint); - let mut ct0i_calculated = - poly_add(&poly_add(&ct0i_hat, &r1i_times_qi), &r2i_times_cyclo); - - while ct0i_calculated.len() > 0 && ct0i_calculated[0].is_zero() { - ct0i_calculated.remove(0); - } - - assert_eq!(&ct0i, &ct0i_calculated); - - // --------------------------------------------------- ct1i --------------------------------------------------- - - // Calculate ct1i_hat = pk1i * ui + e1i - let ct1i_hat = { - let pk1i_times_u = poly_mul(&pk1i, &u); - assert_eq!((pk1i_times_u.len() as u64) - 1, 2 * (N - 1)); - - poly_add(&pk1i_times_u, &e1) - }; - assert_eq!((ct1i_hat.len() as u64) - 1, 2 * (N - 1)); - - // Check whether ct1i_hat mod R_qi (the ring) is equal to ct1i - let mut ct1i_hat_mod_rqi = ct1i_hat.clone(); - reduce_in_ring(&mut ct1i_hat_mod_rqi, &cyclo, &qi_bigint); - assert_eq!(&ct1i, &ct1i_hat_mod_rqi); - - // Compute p2i numerator = ct1i - ct1i_hat - let ct1i_minus_ct1i_hat = poly_sub(&ct1i, &ct1i_hat); - assert_eq!((ct1i_minus_ct1i_hat.len() as u64) - 1, 2 * (N - 1)); - let mut ct1i_minus_ct1i_hat_mod_zqi = ct1i_minus_ct1i_hat.clone(); - reduce_and_center_coefficients_mut(&mut ct1i_minus_ct1i_hat_mod_zqi, &qi_bigint); - - // Compute p2i as the quotient of numerator divided by the cyclotomic polynomial, - // and reduce/center the resulting coefficients to produce: - // (ct1i - ct1i_hat) / (x^N + 1) mod Z_qi. Remainder should be empty. - let (p2i, p2i_rem) = poly_div(&ct1i_minus_ct1i_hat_mod_zqi, &cyclo.clone()); - assert!(p2i_rem.is_empty()); - assert_eq!((p2i.len() as u64) - 1, N - 2); // Order(p2i) = N - 2 - - // Assert that (ct1i - ct1i_hat) = (p2i * cyclo) mod Z_qi - let p2i_times_cyclo: Vec = poly_mul(&p2i, &cyclo); - let mut p2i_times_cyclo_mod_zqi = p2i_times_cyclo.clone(); - reduce_and_center_coefficients_mut(&mut p2i_times_cyclo_mod_zqi, &qi_bigint); - assert_eq!(&ct1i_minus_ct1i_hat_mod_zqi, &p2i_times_cyclo_mod_zqi); - assert_eq!((p2i_times_cyclo.len() as u64) - 1, 2 * (N - 1)); - - // Calculate p1i = (ct1i - ct1i_hat - p2i * cyclo) / qi mod Z_p. Remainder should be empty. - let p1i_num = poly_sub(&ct1i_minus_ct1i_hat, &p2i_times_cyclo); - assert_eq!((p1i_num.len() as u64) - 1, 2 * (N - 1)); - - let (p1i, p1i_rem) = poly_div(&p1i_num, &[BigInt::from(qi.modulus())]); - assert!(p1i_rem.is_empty()); - assert_eq!((p1i.len() as u64) - 1, 2 * (N - 1)); // Order(p1i) = 2*(N-1) - assert_eq!(&p1i_num, &poly_mul(&p1i, &[qi_bigint.clone()])); - - // Assert that ct1i = ct1i_hat + p1i * qi + p2i * cyclo mod Z_p - let p1i_times_qi = poly_scalar_mul(&p1i, &qi_bigint); - let mut ct1i_calculated = - poly_add(&poly_add(&ct1i_hat, &p1i_times_qi), &p2i_times_cyclo); - - while ct1i_calculated.len() > 0 && ct1i_calculated[0].is_zero() { - ct1i_calculated.remove(0); - } - - assert_eq!(&ct1i, &ct1i_calculated); - - /* - println!("qi = {:?}\n", &qi_bigint); - println!("ct0i = {:?}\n", &ct0i); - println!("k0qi = {:?}\n", &k0qi); - println!("pk0 = Polynomial({:?})\n", &pk0i); - println!("pk1 = Polynomial({:?})\n", &pk1i); - println!("ki = {:?}\n", &ki); - println!("ct0i_hat_mod_rqi = {:?}\n", &ct0i_hat_mod_rqi); - */ - - ( - i, r2i, r1i, k0qi, ct0i, ct0i_hat, ct1i, ct1i_hat, pk0i, pk1i, p1i, p2i, - ) - }, - ) - .collect(); + )> = izip!( + ctx.moduli_operators(), + ct0.coefficients().rows(), + ct1.coefficients().rows(), + pk0.coefficients().rows(), + pk1.coefficients().rows() + ) + .enumerate() + .par_bridge() + .map( + |(i, (qi, ct0_coeffs, ct1_coeffs, pk0_coeffs, pk1_coeffs))| { + // --------------------------------------------------- ct0i --------------------------------------------------- + + // Convert to vectors of bigint, center, and reverse order. + let mut ct0i: Vec = ct0_coeffs.iter().rev().map(|&x| BigInt::from(x)).collect(); + let mut ct1i: Vec = ct1_coeffs.iter().rev().map(|&x| BigInt::from(x)).collect(); + let mut pk0i: Vec = pk0_coeffs.iter().rev().map(|&x| BigInt::from(x)).collect(); + let mut pk1i: Vec = pk1_coeffs.iter().rev().map(|&x| BigInt::from(x)).collect(); + + let qi_bigint = BigInt::from(qi.modulus()); + + reduce_and_center_coefficients_mut(&mut ct0i, &qi_bigint); + reduce_and_center_coefficients_mut(&mut ct1i, &qi_bigint); + reduce_and_center_coefficients_mut(&mut pk0i, &qi_bigint); + reduce_and_center_coefficients_mut(&mut pk1i, &qi_bigint); + + // k0qi = -t^{-1} mod qi + let koqi_u64 = qi.inv(qi.neg(t.modulus())).unwrap(); + let k0qi = BigInt::from(koqi_u64); // Do not need to center this + + // ki = k1 * k0qi + let ki = poly_scalar_mul(&k1, &k0qi); + + // Calculate ct0i_hat = pk0 * ui + e0i + ki + let ct0i_hat = { + let pk0i_times_u = poly_mul(&pk0i, &u); + assert_eq!((pk0i_times_u.len() as u64) - 1, 2 * (N - 1)); + + let e0_plus_ki = poly_add(&e0, &ki); + assert_eq!((e0_plus_ki.len() as u64) - 1, N - 1); + + poly_add(&pk0i_times_u, &e0_plus_ki) + }; + assert_eq!((ct0i_hat.len() as u64) - 1, 2 * (N - 1)); + + // Check whether ct0i_hat mod R_qi (the ring) is equal to ct0i + let mut ct0i_hat_mod_rqi = ct0i_hat.clone(); + reduce_in_ring(&mut ct0i_hat_mod_rqi, &cyclo, &qi_bigint); + assert_eq!(&ct0i, &ct0i_hat_mod_rqi); + + // Compute r2i numerator = ct0i - ct0i_hat and reduce/center the polynomial + let ct0i_minus_ct0i_hat = poly_sub(&ct0i, &ct0i_hat); + assert_eq!((ct0i_minus_ct0i_hat.len() as u64) - 1, 2 * (N - 1)); + let mut ct0i_minus_ct0i_hat_mod_zqi = ct0i_minus_ct0i_hat.clone(); + reduce_and_center_coefficients_mut(&mut ct0i_minus_ct0i_hat_mod_zqi, &qi_bigint); + + // Compute r2i as the quotient of numerator divided by the cyclotomic polynomial + // to produce: (ct0i - ct0i_hat) / (x^N + 1) mod Z_qi. Remainder should be empty. + let (r2i, r2i_rem) = poly_div(&ct0i_minus_ct0i_hat_mod_zqi, &cyclo); + assert!(r2i_rem.is_empty()); + assert_eq!((r2i.len() as u64) - 1, N - 2); // Order(r2i) = N - 2 + + // Assert that (ct0i - ct0i_hat) = (r2i * cyclo) mod Z_qi + let r2i_times_cyclo = poly_mul(&r2i, &cyclo); + let mut r2i_times_cyclo_mod_zqi = r2i_times_cyclo.clone(); + reduce_and_center_coefficients_mut(&mut r2i_times_cyclo_mod_zqi, &qi_bigint); + assert_eq!(&ct0i_minus_ct0i_hat_mod_zqi, &r2i_times_cyclo_mod_zqi); + assert_eq!((r2i_times_cyclo.len() as u64) - 1, 2 * (N - 1)); + + // Calculate r1i = (ct0i - ct0i_hat - r2i * cyclo) / qi mod Z_p. Remainder should be empty. + let r1i_num = poly_sub(&ct0i_minus_ct0i_hat, &r2i_times_cyclo); + assert_eq!((r1i_num.len() as u64) - 1, 2 * (N - 1)); + + let (r1i, r1i_rem) = poly_div(&r1i_num, &[qi_bigint.clone()]); + assert!(r1i_rem.is_empty()); + assert_eq!((r1i.len() as u64) - 1, 2 * (N - 1)); // Order(r1i) = 2*(N-1) + assert_eq!(&r1i_num, &poly_mul(&r1i, &[qi_bigint.clone()])); + + // Assert that ct0i = ct0i_hat + r1i * qi + r2i * cyclo mod Z_p + let r1i_times_qi = poly_scalar_mul(&r1i, &qi_bigint); + let mut ct0i_calculated = + poly_add(&poly_add(&ct0i_hat, &r1i_times_qi), &r2i_times_cyclo); + + while ct0i_calculated.len() > 0 && ct0i_calculated[0].is_zero() { + ct0i_calculated.remove(0); + } + + assert_eq!(&ct0i, &ct0i_calculated); + + // --------------------------------------------------- ct1i --------------------------------------------------- + + // Calculate ct1i_hat = pk1i * ui + e1i + let ct1i_hat = { + let pk1i_times_u = poly_mul(&pk1i, &u); + assert_eq!((pk1i_times_u.len() as u64) - 1, 2 * (N - 1)); + + poly_add(&pk1i_times_u, &e1) + }; + assert_eq!((ct1i_hat.len() as u64) - 1, 2 * (N - 1)); + + // Check whether ct1i_hat mod R_qi (the ring) is equal to ct1i + let mut ct1i_hat_mod_rqi = ct1i_hat.clone(); + reduce_in_ring(&mut ct1i_hat_mod_rqi, &cyclo, &qi_bigint); + assert_eq!(&ct1i, &ct1i_hat_mod_rqi); + + // Compute p2i numerator = ct1i - ct1i_hat + let ct1i_minus_ct1i_hat = poly_sub(&ct1i, &ct1i_hat); + assert_eq!((ct1i_minus_ct1i_hat.len() as u64) - 1, 2 * (N - 1)); + let mut ct1i_minus_ct1i_hat_mod_zqi = ct1i_minus_ct1i_hat.clone(); + reduce_and_center_coefficients_mut(&mut ct1i_minus_ct1i_hat_mod_zqi, &qi_bigint); + + // Compute p2i as the quotient of numerator divided by the cyclotomic polynomial, + // and reduce/center the resulting coefficients to produce: + // (ct1i - ct1i_hat) / (x^N + 1) mod Z_qi. Remainder should be empty. + let (p2i, p2i_rem) = poly_div(&ct1i_minus_ct1i_hat_mod_zqi, &cyclo.clone()); + assert!(p2i_rem.is_empty()); + assert_eq!((p2i.len() as u64) - 1, N - 2); // Order(p2i) = N - 2 + + // Assert that (ct1i - ct1i_hat) = (p2i * cyclo) mod Z_qi + let p2i_times_cyclo: Vec = poly_mul(&p2i, &cyclo); + let mut p2i_times_cyclo_mod_zqi = p2i_times_cyclo.clone(); + reduce_and_center_coefficients_mut(&mut p2i_times_cyclo_mod_zqi, &qi_bigint); + assert_eq!(&ct1i_minus_ct1i_hat_mod_zqi, &p2i_times_cyclo_mod_zqi); + assert_eq!((p2i_times_cyclo.len() as u64) - 1, 2 * (N - 1)); + + // Calculate p1i = (ct1i - ct1i_hat - p2i * cyclo) / qi mod Z_p. Remainder should be empty. + let p1i_num = poly_sub(&ct1i_minus_ct1i_hat, &p2i_times_cyclo); + assert_eq!((p1i_num.len() as u64) - 1, 2 * (N - 1)); + + let (p1i, p1i_rem) = poly_div(&p1i_num, &[BigInt::from(qi.modulus())]); + assert!(p1i_rem.is_empty()); + assert_eq!((p1i.len() as u64) - 1, 2 * (N - 1)); // Order(p1i) = 2*(N-1) + assert_eq!(&p1i_num, &poly_mul(&p1i, &[qi_bigint.clone()])); + + // Assert that ct1i = ct1i_hat + p1i * qi + p2i * cyclo mod Z_p + let p1i_times_qi = poly_scalar_mul(&p1i, &qi_bigint); + let mut ct1i_calculated = + poly_add(&poly_add(&ct1i_hat, &p1i_times_qi), &p2i_times_cyclo); + + while ct1i_calculated.len() > 0 && ct1i_calculated[0].is_zero() { + ct1i_calculated.remove(0); + } + + assert_eq!(&ct1i, &ct1i_calculated); + + /* + println!("qi = {:?}\n", &qi_bigint); + println!("ct0i = {:?}\n", &ct0i); + println!("k0qi = {:?}\n", &k0qi); + println!("pk0 = Polynomial({:?})\n", &pk0i); + println!("pk1 = Polynomial({:?})\n", &pk1i); + println!("ki = {:?}\n", &ki); + println!("ct0i_hat_mod_rqi = {:?}\n", &ct0i_hat_mod_rqi); + */ + + (i, r2i, r1i, k0qi, ct0i, ct1i, pk0i, pk1i, p1i, p2i) + }, + ) + .collect(); // println!("Completed creation of polynomials!"); - // Aggregate results into global vectors - for (i, r2i, r1i, k0i, ct0i, ct0i_hat, ct1i, ct1i_hat, pk0i, pk1i, p1i, p2i) in - results.into_iter() - { - r2is[i] = r2i; - r1is[i] = r1i; - k0is[i] = k0i; - ct0is[i] = ct0i; - ct0is_hat[i] = ct0i_hat; - ct1is[i] = ct1i; - ct1is_hat[i] = ct1i_hat; - pk0is[i] = pk0i; - pk1is[i] = pk1i; - p1is[i] = p1i; - p2is[i] = p2i; + // Merge results into the `res` structure after parallel execution + for (i, r2i, r1i, k0i, ct0i, ct1i, pk0i, pk1i, p1i, p2i) in results.into_iter() { + res.r2is[i] = r2i; + res.r1is[i] = r1i; + res.k0is[i] = k0i; + res.ct0is[i] = ct0i; + res.ct1is[i] = ct1i; + res.pk0is[i] = pk0i; + res.pk1is[i] = pk1i; + res.p1is[i] = p1i; + res.p2is[i] = p2i; } - ( - r2is, r1is, k0is, ct0is, ct1is, pk0is, pk1is, p1is, p2is, u, e0, e1, k1, - ) + // Set final result vectors + res.u = u; + res.e0 = e0; + res.e1 = e1; + res.k1 = k1; + + res } diff --git a/packages/web-rust/src/bin/web_fhe_encrypt.rs b/packages/web-rust/src/bin/web_fhe_encrypt.rs index 3e66dbd..e2d5071 100644 --- a/packages/web-rust/src/bin/web_fhe_encrypt.rs +++ b/packages/web-rust/src/bin/web_fhe_encrypt.rs @@ -2,7 +2,7 @@ mod greco; mod util; use fhe_math::zq::Modulus; -use greco::greco::compute_input_validation_vectors; +use greco::greco::*; use wasm_bindgen::prelude::*; use serde::Deserialize; @@ -45,14 +45,14 @@ impl Encrypt { .build_arc() .map_err(|e| JsValue::from_str(&format!("Error generating parameters: {}", e)))?; - let pk_deserialized = PublicKey::from_bytes(&public_key, ¶ms) + let pk = PublicKey::from_bytes(&public_key, ¶ms) .map_err(|e| JsValue::from_str(&format!("Error deserializing public key: {}", e)))?; let votes = vec![vote]; let pt = Plaintext::try_encode(&votes, Encoding::poly(), ¶ms) .map_err(|e| JsValue::from_str(&format!("Error encoding plaintext: {}", e)))?; - let (ct, u_rns, e0_rns, e1_rns) = pk_deserialized + let (ct, u_rns, e0_rns, e1_rns) = pk .try_encrypt_extended(&pt, &mut thread_rng()) .map_err(|e| JsValue::from_str(&format!("Error encrypting vote: {}", e)))?; @@ -67,16 +67,8 @@ impl Encrypt { e )) })?; - let input_val_vectors = compute_input_validation_vectors( - ctx, - &t, - &pt, - &u_rns, - &e0_rns, - &e1_rns, - &ct, - &pk_deserialized, - ); + let input_val_vectors = + compute_input_validation_vectors(&ctx, &t, &pt, &u_rns, &e0_rns, &e1_rns, &ct, &pk); self.encrypted_vote = ct.to_bytes(); Ok(self.encrypted_vote.clone()) From 28b889fd0778c630577722cc6b4b13e6278d5cf3 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 18 Sep 2024 20:58:24 +0500 Subject: [PATCH 31/62] feat: add config, param decryption and result publishing --- packages/evm/contracts/CRISPVoting.sol | 23 +- packages/server/.cargo/config.toml | 5 - packages/server/.env.example | 4 + packages/server/Cargo.lock | 287 ++++++++++++++++-- packages/server/Cargo.toml | 10 +- packages/server/src/cli/config.rs | 26 ++ packages/server/src/cli/mod.rs | 18 +- packages/server/src/cli/voting.rs | 126 +++++--- .../src/enclave_server/blockchain/events.rs | 18 +- .../src/enclave_server/blockchain/handlers.rs | 33 +- .../src/enclave_server/blockchain/listener.rs | 3 +- .../src/enclave_server/blockchain/relayer.rs | 9 +- packages/server/src/enclave_server/config.rs | 26 ++ packages/server/src/enclave_server/mod.rs | 4 +- packages/server/src/enclave_server/models.rs | 7 +- .../src/enclave_server/routes/rounds.rs | 37 +-- .../src/enclave_server/routes/voting.rs | 24 +- 17 files changed, 527 insertions(+), 133 deletions(-) delete mode 100644 packages/server/.cargo/config.toml create mode 100644 packages/server/.env.example create mode 100644 packages/server/src/cli/config.rs create mode 100644 packages/server/src/enclave_server/config.rs diff --git a/packages/evm/contracts/CRISPVoting.sol b/packages/evm/contracts/CRISPVoting.sol index aa6e30c..c3e0ff7 100644 --- a/packages/evm/contracts/CRISPVoting.sol +++ b/packages/evm/contracts/CRISPVoting.sol @@ -34,6 +34,10 @@ contract CRISPVoting { ); event PlaintextOutputPublished(uint256 indexed e3Id, bytes plaintextOutput); + event CiphertextOutputPublished( + uint256 indexed e3Id, + bytes ciphertextOutput + ); uint256 public e3Counter = 0; // Counter for E3 IDs @@ -70,12 +74,15 @@ contract CRISPVoting { } // Activate the poll - function activate(uint256 e3Id, bytes calldata pubKey) external returns (bool success) { + function activate( + uint256 e3Id, + bytes calldata pubKey + ) external returns (bool success) { require(e3Polls[e3Id].seed > 0, "E3 ID does not exist."); require(e3Polls[e3Id].expiration == 0, "Poll already activated."); e3Polls[e3Id].expiration = block.timestamp + e3Polls[e3Id].duration; - // e3Polls[e3Id].committeePublicKey = ; + e3Polls[e3Id].committeePublicKey = abi.encodePacked(keccak256(pubKey)); emit E3Activated(e3Id, e3Polls[e3Id].expiration, pubKey); return true; @@ -108,7 +115,15 @@ contract CRISPVoting { "Ciphertext already published." ); - e3Polls[e3Id].ciphertextOutput = data; + ( + bytes memory verification, + bytes memory ciphertext + ) = abi.decode(data, (bytes, bytes)); + + e3Polls[e3Id].ciphertextOutput = abi.encodePacked( + keccak256(ciphertext) + ); + emit CiphertextOutputPublished(e3Id, ciphertext); return true; } @@ -125,7 +140,7 @@ contract CRISPVoting { ); require(e3.plaintextOutput.length == 0, "Plaintext already published."); - e3.plaintextOutput = data; + e3.plaintextOutput = abi.encodePacked(keccak256(data)); emit PlaintextOutputPublished(e3Id, data); return true; } diff --git a/packages/server/.cargo/config.toml b/packages/server/.cargo/config.toml deleted file mode 100644 index d13436c..0000000 --- a/packages/server/.cargo/config.toml +++ /dev/null @@ -1,5 +0,0 @@ -[env] -INFURA_KEY = "INFURA_KEY" # Your Infura key -PRIVATE_KEY = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" # Anvil's private key -RPC_URL = "http://0.0.0.0:8545" # Local RPC -CONTRACT_ADDRESS = "0x5FbDB2315678afecb367f032d93F642f64180aa3" # Enclave Address \ No newline at end of file diff --git a/packages/server/.env.example b/packages/server/.env.example new file mode 100644 index 0000000..e966016 --- /dev/null +++ b/packages/server/.env.example @@ -0,0 +1,4 @@ +PRIVATE_KEY=your_private_key_value +RPC_URL=https://your_rpc_url +CONTRACT_ADDRESS=your_contract_address +CHAIN_ID=your_chain_id \ No newline at end of file diff --git a/packages/server/Cargo.lock b/packages/server/Cargo.lock index ea1e5a2..3d668ea 100644 --- a/packages/server/Cargo.lock +++ b/packages/server/Cargo.lock @@ -1526,6 +1526,9 @@ name = "bitflags" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +dependencies = [ + "serde", +] [[package]] name = "bitvec" @@ -1928,6 +1931,26 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "config" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be" +dependencies = [ + "async-trait", + "convert_case 0.6.0", + "json5", + "lazy_static", + "nom", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml", + "yaml-rust", +] + [[package]] name = "console" version = "0.15.8" @@ -1960,6 +1983,26 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom", + "once_cell", + "tiny-keccak", +] + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -1972,6 +2015,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cookie" version = "0.16.2" @@ -2134,7 +2186,7 @@ version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ - "convert_case", + "convert_case 0.4.0", "proc-macro2", "quote", "rustc_version 0.4.0", @@ -2218,6 +2270,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + [[package]] name = "doc-comment" version = "0.3.3" @@ -2236,6 +2297,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "downcast-rs" version = "1.2.1" @@ -2816,15 +2883,38 @@ dependencies = [ "subtle", ] +[[package]] +name = "fhe" +version = "0.1.0-beta.7" +source = "git+https://github.com/gnosisguild/fhe.rs?branch=feature/greco-integration#26e5f2ff6c860d47a1c88a777936bc68eaedb129" +dependencies = [ + "doc-comment", + "fhe-math 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs?branch=feature/greco-integration)", + "fhe-traits 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs?branch=feature/greco-integration)", + "fhe-util 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs?branch=feature/greco-integration)", + "itertools 0.12.1", + "ndarray", + "num-bigint", + "num-traits", + "prost", + "prost-build", + "rand 0.8.5", + "rand_chacha 0.3.1", + "serde", + "thiserror", + "zeroize", + "zeroize_derive", +] + [[package]] name = "fhe" version = "0.1.0-beta.7" source = "git+https://github.com/gnosisguild/fhe.rs#9624766dcfbb40ecfb01147f59c2f6292c447707" dependencies = [ "doc-comment", - "fhe-math", - "fhe-traits", - "fhe-util", + "fhe-math 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs)", + "fhe-traits 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs)", + "fhe-util 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs)", "itertools 0.12.1", "ndarray", "num-bigint", @@ -2839,14 +2929,36 @@ dependencies = [ "zeroize_derive", ] +[[package]] +name = "fhe-math" +version = "0.1.0-beta.7" +source = "git+https://github.com/gnosisguild/fhe.rs?branch=feature/greco-integration#26e5f2ff6c860d47a1c88a777936bc68eaedb129" +dependencies = [ + "ethnum", + "fhe-traits 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs?branch=feature/greco-integration)", + "fhe-util 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs?branch=feature/greco-integration)", + "itertools 0.12.1", + "ndarray", + "num-bigint", + "num-bigint-dig", + "num-traits", + "prost", + "prost-build", + "rand 0.8.5", + "rand_chacha 0.3.1", + "sha2", + "thiserror", + "zeroize", +] + [[package]] name = "fhe-math" version = "0.1.0-beta.7" source = "git+https://github.com/gnosisguild/fhe.rs#9624766dcfbb40ecfb01147f59c2f6292c447707" dependencies = [ "ethnum", - "fhe-traits", - "fhe-util", + "fhe-traits 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs)", + "fhe-util 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs)", "itertools 0.12.1", "ndarray", "num-bigint", @@ -2861,6 +2973,14 @@ dependencies = [ "zeroize", ] +[[package]] +name = "fhe-traits" +version = "0.1.0-beta.7" +source = "git+https://github.com/gnosisguild/fhe.rs?branch=feature/greco-integration#26e5f2ff6c860d47a1c88a777936bc68eaedb129" +dependencies = [ + "rand 0.8.5", +] + [[package]] name = "fhe-traits" version = "0.1.0-beta.7" @@ -2869,6 +2989,17 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "fhe-util" +version = "0.1.0-beta.7" +source = "git+https://github.com/gnosisguild/fhe.rs?branch=feature/greco-integration#26e5f2ff6c860d47a1c88a777936bc68eaedb129" +dependencies = [ + "itertools 0.12.1", + "num-bigint-dig", + "num-traits", + "rand 0.8.5", +] + [[package]] name = "fhe-util" version = "0.1.0-beta.7" @@ -3790,6 +3921,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + [[package]] name = "jsonwebtoken" version = "8.3.0" @@ -3967,6 +4109,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -4122,6 +4270,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.2" @@ -4204,6 +4358,16 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -4407,6 +4571,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-multimap" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ed8acf08e98e744e5384c8bc63ceb0364e68a6854187221c18df61c4797690e" +dependencies = [ + "dlv-list", + "hashbrown 0.13.2", +] + [[package]] name = "overload" version = "0.1.1" @@ -4516,6 +4690,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + [[package]] name = "pbkdf2" version = "0.11.0" @@ -4571,15 +4751,49 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.11" +version = "2.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea" dependencies = [ "memchr", "thiserror", "ucd-trie", ] +[[package]] +name = "pest_derive" +version = "2.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "664d22978e2815783adbdd2c588b455b1bd625299ce36b2a99881ac9627e6d8d" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d5487022d5d33f4c30d91c22afa240ce2a644e87fe08caad974d4eab6badbe" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.61", +] + +[[package]] +name = "pest_meta" +version = "2.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0091754bbd0ea592c4deb3a122ce8ecbb0753b738aa82bc055fcc2eccc8d8174" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "petgraph" version = "0.6.4" @@ -5412,14 +5626,17 @@ dependencies = [ "bytes", "chrono", "compute-provider", + "config", "console", "dialoguer", + "dotenvy", "env_logger 0.11.5", "ethers", "eyre", - "fhe", - "fhe-traits", - "fhe-util", + "fhe 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs?branch=feature/greco-integration)", + "fhe-math 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs?branch=feature/greco-integration)", + "fhe-traits 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs?branch=feature/greco-integration)", + "fhe-util 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs?branch=feature/greco-integration)", "futures-util", "getrandom", "headers", @@ -5441,7 +5658,6 @@ dependencies = [ "serde_json", "sha2", "sled", - "termcolor", "tokio", "voting-risc0", "walkdir", @@ -5685,6 +5901,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64 0.21.7", + "bitflags 2.5.0", + "serde", + "serde_derive", +] + [[package]] name = "route-recognizer" version = "0.1.13" @@ -5742,6 +5970,16 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" +[[package]] +name = "rust-ini" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e2a3bcec1f113553ef1c88aae6c020a369d03d55b58de9869a0908930385091" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -7016,6 +7254,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-width" version = "0.1.12" @@ -7128,8 +7372,8 @@ name = "voting-core" version = "0.1.0" dependencies = [ "compute-provider", - "fhe", - "fhe-traits", + "fhe 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs)", + "fhe-traits 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs)", ] [[package]] @@ -7143,9 +7387,9 @@ dependencies = [ "compute-provider", "env_logger 0.10.2", "ethers", - "fhe", - "fhe-traits", - "fhe-util", + "fhe 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs)", + "fhe-traits 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs)", + "fhe-util 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs)", "log 0.4.22", "methods", "risc0-ethereum-contracts", @@ -7555,6 +7799,15 @@ dependencies = [ "tap", ] +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "yansi" version = "0.5.1" diff --git a/packages/server/Cargo.toml b/packages/server/Cargo.toml index 17904fd..c466a20 100644 --- a/packages/server/Cargo.toml +++ b/packages/server/Cargo.toml @@ -8,9 +8,10 @@ edition = "2021" [dependencies] console = "0.15.7" -fhe = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } -fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } -fhe-util = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } +fhe = { git = "https://github.com/gnosisguild/fhe.rs", branch = "feature/greco-integration", version = "0.1.0-beta.7" } +fhe-math = { git = "https://github.com/gnosisguild/fhe.rs", branch = "feature/greco-integration", version = "0.1.0-beta.7" } +fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs", branch = "feature/greco-integration", version = "0.1.0-beta.7" } +fhe-util = { git = "https://github.com/gnosisguild/fhe.rs", branch = "feature/greco-integration", version = "0.1.0-beta.7" } compute-provider = { path = "../compute_provider" } voting-risc0 = { path = "../risc0/apps" } rand_chacha = "0.3.1" @@ -51,4 +52,5 @@ alloy = { version = "0.2.1", features = ["full", "rpc-types-eth"] } futures-util = "0.3" eyre = "0.6" hex = "0.4" -termcolor = "1.4.1" \ No newline at end of file +dotenvy = "0.15.7" +config = "0.14.0" \ No newline at end of file diff --git a/packages/server/src/cli/config.rs b/packages/server/src/cli/config.rs new file mode 100644 index 0000000..d4bc0ea --- /dev/null +++ b/packages/server/src/cli/config.rs @@ -0,0 +1,26 @@ +use config::{Config as ConfigManager, ConfigError}; +use dotenvy::dotenv; +use once_cell::sync::Lazy; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +pub struct Config { + pub private_key: String, + pub http_rpc_url: String, + pub ws_rpc_url: String, + pub contract_address: String, +} + +impl Config { + pub fn from_env() -> Result { + dotenv().ok(); + ConfigManager::builder() + .add_source(config::Environment::default()) + .build()? + .try_deserialize() + } +} + +pub static CONFIG: Lazy = Lazy::new(|| { + Config::from_env().expect("Failed to load configuration") +}); \ No newline at end of file diff --git a/packages/server/src/cli/mod.rs b/packages/server/src/cli/mod.rs index 31401d7..2edc28e 100644 --- a/packages/server/src/cli/mod.rs +++ b/packages/server/src/cli/mod.rs @@ -1,4 +1,5 @@ mod auth; +mod config; mod voting; use dialoguer::{theme::ColorfulTheme, FuzzySelect}; @@ -16,7 +17,20 @@ use serde::{Deserialize, Serialize}; use env_logger::{Builder, Target}; use log::LevelFilter; use log::info; -use std::io::Write; // Use `std::io::Write` for writing to the buffer +use std::io::Write; + +use once_cell::sync::Lazy; +use sled::{Db, IVec}; +use std::{error::Error, str, sync::Arc}; +use tokio::sync::RwLock; + +pub static GLOBAL_DB: Lazy>> = Lazy::new(|| { + let pathdb = std::env::current_dir() + .unwrap() + .join("database/cli"); + Arc::new(RwLock::new(sled::open(pathdb).unwrap())) +}); + fn init_logger() { let mut builder = Builder::new(); @@ -101,7 +115,7 @@ fn select_environment() -> Result Result> { - let selections = &["Initialize new E3 round.", "Activate an E3 round.", "Participate in an E3 round."]; + let selections = &["Initialize new E3 round.", "Activate an E3 round.", "Participate in an E3 round.", "Decrypt Ciphertext & Publish Results"]; Ok(FuzzySelect::with_theme(&ColorfulTheme::default()) .with_prompt("Create a new CRISP round or participate in an existing round.") .default(0) diff --git a/packages/server/src/cli/voting.rs b/packages/server/src/cli/voting.rs index 8fc6638..d36bb6e 100644 --- a/packages/server/src/cli/voting.rs +++ b/packages/server/src/cli/voting.rs @@ -10,10 +10,11 @@ use alloy::primitives::{Address, Bytes, U256}; use crate::enclave_server::blockchain::relayer::EnclaveContract; -use crate::cli::{AuthenticationResponse, HyperClientPost}; +use crate::cli::{AuthenticationResponse, HyperClientPost, GLOBAL_DB}; use crate::util::timeit::timeit; use fhe::bfv::{BfvParametersBuilder, Encoding, Plaintext, PublicKey, BfvParameters, SecretKey}; -use fhe_traits::{DeserializeParametrized, Deserialize as FheDeserialize, FheEncoder, FheEncrypter, Serialize as FheSerialize}; +use fhe_traits::{Deserialize as FheDeserialize, DeserializeParametrized, DeserializeWithContext, FheDecoder, FheDecrypter, FheEncoder, FheEncrypter, Serialize as FheSerialize}; +use fhe_math::rq::Poly; use rand::thread_rng; #[derive(Debug, Deserialize, Serialize)] @@ -26,12 +27,25 @@ struct RoundCount { round_count: u32, } +#[derive(Debug, Deserialize, Serialize)] +struct FHEParams { + params: Vec, + pk: Vec, + sk: Vec, +} + #[derive(Debug, Deserialize, Serialize)] struct PKRequest { - round_id: u32, + round_id: u64, pk_bytes: Vec, } +#[derive(Debug, Deserialize, Serialize)] +struct CTRequest { + round_id: u64, + ct_bytes: Vec, +} + #[derive(Debug, Deserialize, Serialize)] struct EncryptedVote { round_id: u32, @@ -46,7 +60,6 @@ struct JsonResponseTxHash { tx_hash: String, } - async fn get_response_body(resp: Response) -> Result> { let body_bytes = resp.collect().await?.to_bytes(); Ok(String::from_utf8(body_bytes.to_vec())?) @@ -60,9 +73,7 @@ pub async fn initialize_crisp_round( let params = generate_bfv_parameters().unwrap().to_bytes(); - let private_key = env::var("PRIVATE_KEY").expect("PRIVATE_KEY must be set in the environment"); - let rpc_url = "http://0.0.0.0:8545"; - let contract = EnclaveContract::new(rpc_url, &config.voting_address, &private_key).await?; + let contract = EnclaveContract::new().await?; let filter: Address = "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5".parse()?; let threshold: [u32; 2] = [1, 2]; @@ -74,10 +85,6 @@ pub async fn initialize_crisp_round( let res = contract.request_e3(filter, threshold, start_window, duration, e3_program, e3_params, compute_provider_params).await?; println!("E3 request sent. TxHash: {:?}", res.transaction_hash); - - // let e3_data = Bytes::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); - // let res = contract.publish_input(e3_id, e3_data).await?; - // println!("E3 data published. TxHash: {:?}", res.transaction_hash); Ok(()) } @@ -90,9 +97,7 @@ pub async fn activate_e3_round(config: &super::CrispConfig) -> Result<(), Box Result<(), Box Result<(), Box> { + let input_crisp_id: u64 = Input::with_theme(&ColorfulTheme::default()) + .with_prompt("Enter CRISP round ID.") + .interact_text()?; + info!("Decryption Initialized"); + + // Get final Ciphertext + let response_pk = CTRequest { + round_id: input_crisp_id, + ct_bytes: vec![0], + }; + + let url = format!("{}/get_ct_by_round", config.enclave_address); + let req = Request::builder() + .header("Content-Type", "application/json") + .method(Method::POST) + .uri(url) + .body(serde_json::to_string(&response_pk)?)?; + + let resp = client.request(req).await?; + info!("Response status: {}", resp.status()); + + let body_str = get_response_body(resp).await?; + let ct_res: CTRequest = serde_json::from_str(&body_str)?; + info!("Shared Public Key for CRISP round {:?} collected.", ct_res.round_id); + info!("Public Key: {:?}", ct_res.ct_bytes); + + let db = GLOBAL_DB.read().await; + let params_bytes = db.get(format!("e3:{}", input_crisp_id))?.unwrap(); + let e3_params: FHEParams = serde_json::from_slice(¶ms_bytes)?; + let params = timeit!( + "Parameters generation", + generate_bfv_parameters()? + ); + let pk_deserialized = PublicKey::from_bytes(&e3_params.pk, ¶ms)?; + let sk_deserialized = SecretKey::new(e3_params.sk, ¶ms); + + let ct = fhe::bfv::Ciphertext::from_bytes(&ct_res.ct_bytes, ¶ms)?; + + let pt = sk_deserialized.try_decrypt(&ct)?; + let votes = Vec::::try_decode(&pt, Encoding::poly())?[0]; + println!("Vote count: {:?}", votes); + + info!("Calling contract with plaintext output."); + let contract = EnclaveContract::new().await?; + let res = contract.publish_plaintext_output(U256::from(input_crisp_id), Bytes::from(votes.to_be_bytes())).await?; + println!("Vote broadcast. TxHash: {:?}", res.transaction_hash); + Ok(()) } @@ -110,7 +181,7 @@ pub async fn participate_in_existing_round( client: &HyperClientPost, auth_res: &AuthenticationResponse, ) -> Result<(), Box> { - let input_crisp_id: u32 = Input::with_theme(&ColorfulTheme::default()) + let input_crisp_id: u64 = Input::with_theme(&ColorfulTheme::default()) .with_prompt("Enter CRISP round ID.") .interact_text()?; info!("Voting state Initialized"); @@ -153,27 +224,10 @@ pub async fn participate_in_existing_round( info!("Vote encrypted."); info!("Calling voting contract with encrypted vote."); - let request_contract = EncryptedVote { - round_id: input_crisp_id, - enc_vote_bytes: ct.to_bytes(), - post_id: auth_res.jwt_token.clone(), - }; - - let url = format!("{}/broadcast_enc_vote", config.enclave_address); - let req = Request::builder() - .header("Content-Type", "application/json") - .method(Method::POST) - .uri(url) - .body(serde_json::to_string(&request_contract)?)?; - - let resp = client.request(req).await?; - info!("Response status: {}", resp.status()); - - let body_str = get_response_body(resp).await?; - let contract_res: JsonResponseTxHash = serde_json::from_str(&body_str)?; - info!("Contract call: {:?}", contract_res.response); - info!("TxHash is {:?}", contract_res.tx_hash); - + let contract = EnclaveContract::new().await?; + let res = contract.publish_input(U256::from(input_crisp_id), Bytes::from(ct.to_bytes())).await?; + println!("Vote broadcast. TxHash: {:?}", res.transaction_hash); + Ok(()) } diff --git a/packages/server/src/enclave_server/blockchain/events.rs b/packages/server/src/enclave_server/blockchain/events.rs index e4f17fc..896488b 100644 --- a/packages/server/src/enclave_server/blockchain/events.rs +++ b/packages/server/src/enclave_server/blockchain/events.rs @@ -6,7 +6,7 @@ use alloy::{ use eyre::Result; -use super::handlers::{handle_e3, handle_input_published, handle_plaintext_output_published}; +use super::handlers::{handle_e3, handle_input_published, handle_plaintext_output_published, handle_ciphertext_output_published}; use super::listener::ContractEvent; sol! { @@ -16,6 +16,9 @@ sol! { #[derive(Debug)] event InputPublished(uint256 indexed e3Id, bytes data, uint256 inputHash, uint256 index); + #[derive(Debug)] + event CiphertextOutputPublished(uint256 indexed e3Id, bytes ciphertextOutput); + #[derive(Debug)] event PlaintextOutputPublished(uint256 indexed e3Id, bytes plaintextOutput); } @@ -46,6 +49,19 @@ impl ContractEvent for InputPublished { } } +impl ContractEvent for CiphertextOutputPublished { + fn process(&self, _log: Log) -> Result<()> { + let event_clone = self.clone(); + tokio::spawn(async move { + if let Err(e) = handle_ciphertext_output_published(event_clone).await { + eprintln!("Error handling ciphertext output published: {:?}", e); + } + }); + + Ok(()) + } +} + impl ContractEvent for PlaintextOutputPublished { fn process(&self, _log: Log) -> Result<()> { let event_clone = self.clone(); diff --git a/packages/server/src/enclave_server/blockchain/handlers.rs b/packages/server/src/enclave_server/blockchain/handlers.rs index 8b96bfb..38fb20b 100644 --- a/packages/server/src/enclave_server/blockchain/handlers.rs +++ b/packages/server/src/enclave_server/blockchain/handlers.rs @@ -1,8 +1,8 @@ use super::{ - events::{E3Activated, InputPublished, PlaintextOutputPublished}, + events::{CiphertextOutputPublished, E3Activated, InputPublished, PlaintextOutputPublished}, relayer::EnclaveContract, }; -use crate::enclave_server::database::{generate_emoji, get_e3, increment_e3_round, GLOBAL_DB}; +use crate::enclave_server::{config::CONFIG, database::{generate_emoji, get_e3, increment_e3_round, GLOBAL_DB}}; use crate::enclave_server::models::E3; use alloy::{ rpc::types::Log, @@ -26,11 +26,7 @@ pub async fn handle_e3( info!("Handling E3 request with id {}", e3_id); // Fetch E3 from the contract - let private_key = env::var("PRIVATE_KEY").expect("PRIVATEKEY must be set in the environment"); - let rpc_url = env::var("RPC_URL").expect("RPC_URL must be set in the environment"); - let contract_address = - env::var("CONTRACT_ADDRESS").expect("CONTRACT_ADDRESS must be set in the environment"); - let contract = EnclaveContract::new(&rpc_url, &contract_address, &private_key).await?; + let contract = EnclaveContract::new().await?; let e3 = contract.get_e3(e3_activated.e3Id).await?; info!("Fetched E3 from the contract."); @@ -49,7 +45,7 @@ pub async fn handle_e3( // Identifiers id: e3_id, chain_id: 31337 as u64, // Hardcoded for testing - enclave_address: contract_address, + enclave_address: CONFIG.contract_address.clone(), // Status-related status: "Active".to_string(), @@ -149,6 +145,24 @@ pub async fn handle_input_published( Ok(()) } + + +pub async fn handle_ciphertext_output_published( + ciphertext_output: CiphertextOutputPublished, +) -> Result<(), Box> { + info!("Handling CiphertextOutputPublished event..."); + + let e3_id = ciphertext_output.e3Id.to::(); + let (mut e3, key) = get_e3(e3_id).await.unwrap(); + e3.ciphertext_output = ciphertext_output.ciphertextOutput.to_vec(); + + let db = GLOBAL_DB.write().await; + db.insert(key, serde_json::to_vec(&e3).unwrap()).unwrap(); + + info!("CiphertextOutputPublished event handled."); + Ok(()) +} + pub async fn handle_plaintext_output_published( plaintext_output: PlaintextOutputPublished, ) -> Result<(), Box> { @@ -158,6 +172,9 @@ pub async fn handle_plaintext_output_published( let (mut e3, key) = get_e3(e3_id).await.unwrap(); e3.plaintext_output = plaintext_output.plaintextOutput.to_vec(); + e3.votes_option_2 = u64::from_be_bytes(e3.plaintext_output.as_slice().try_into().unwrap()); + e3.votes_option_1 = e3.vote_count - e3.votes_option_2; + let db = GLOBAL_DB.write().await; db.insert(key, serde_json::to_vec(&e3).unwrap()).unwrap(); diff --git a/packages/server/src/enclave_server/blockchain/listener.rs b/packages/server/src/enclave_server/blockchain/listener.rs index d2d1d3a..948c40d 100644 --- a/packages/server/src/enclave_server/blockchain/listener.rs +++ b/packages/server/src/enclave_server/blockchain/listener.rs @@ -102,8 +102,7 @@ impl ContractManager { EventListener::new(self.provider.clone(), filter) } } -pub async fn start_listener(contract_address: &str) -> Result<()> { - let rpc_url = "ws://127.0.0.1:8545"; +pub async fn start_listener(rpc_url: &str, contract_address: &str) -> Result<()> { let address: Address = contract_address.parse()?; loop { diff --git a/packages/server/src/enclave_server/blockchain/relayer.rs b/packages/server/src/enclave_server/blockchain/relayer.rs index b4c9c81..f49086e 100644 --- a/packages/server/src/enclave_server/blockchain/relayer.rs +++ b/packages/server/src/enclave_server/blockchain/relayer.rs @@ -12,6 +12,7 @@ use alloy::{ }; use eyre::Result; use std::sync::Arc; +use crate::enclave_server::CONFIG; sol! { #[derive(Debug)] @@ -63,18 +64,18 @@ pub struct EnclaveContract { } impl EnclaveContract { - pub async fn new(rpc_url: &str, contract_address: &str, private_key: &str) -> Result { - let signer: PrivateKeySigner = private_key.parse()?; + pub async fn new() -> Result { + let signer: PrivateKeySigner = CONFIG.private_key.parse()?; let wallet = EthereumWallet::from(signer.clone()); let provider = ProviderBuilder::new() .with_recommended_fillers() .wallet(wallet) - .on_builtin(rpc_url) + .on_builtin(&CONFIG.http_rpc_url) .await?; Ok(Self { provider: Arc::new(provider), - contract_address: contract_address.parse()?, + contract_address: CONFIG.contract_address.parse()?, }) } diff --git a/packages/server/src/enclave_server/config.rs b/packages/server/src/enclave_server/config.rs new file mode 100644 index 0000000..d4bc0ea --- /dev/null +++ b/packages/server/src/enclave_server/config.rs @@ -0,0 +1,26 @@ +use config::{Config as ConfigManager, ConfigError}; +use dotenvy::dotenv; +use once_cell::sync::Lazy; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +pub struct Config { + pub private_key: String, + pub http_rpc_url: String, + pub ws_rpc_url: String, + pub contract_address: String, +} + +impl Config { + pub fn from_env() -> Result { + dotenv().ok(); + ConfigManager::builder() + .add_source(config::Environment::default()) + .build()? + .try_deserialize() + } +} + +pub static CONFIG: Lazy = Lazy::new(|| { + Config::from_env().expect("Failed to load configuration") +}); \ No newline at end of file diff --git a/packages/server/src/enclave_server/mod.rs b/packages/server/src/enclave_server/mod.rs index a364ea5..915cceb 100644 --- a/packages/server/src/enclave_server/mod.rs +++ b/packages/server/src/enclave_server/mod.rs @@ -1,6 +1,7 @@ mod database; mod models; mod routes; +mod config; pub mod blockchain; use actix_cors::Cors; @@ -14,6 +15,7 @@ use env_logger::{Builder, Target}; use log::{LevelFilter, Record}; use std::path::Path; use std::io::Write; +use config::CONFIG; fn init_logger() { let mut builder = Builder::new(); @@ -40,7 +42,7 @@ pub async fn start_server() -> Result<(), Box, } +#[derive(Debug, Deserialize, Serialize)] +pub struct CTRequest { + pub round_id: u64, + pub ct_bytes: Vec, +} #[derive(Debug, Deserialize, Serialize)] pub struct CRPRequest { diff --git a/packages/server/src/enclave_server/routes/rounds.rs b/packages/server/src/enclave_server/routes/rounds.rs index f545ca6..b57a724 100644 --- a/packages/server/src/enclave_server/routes/rounds.rs +++ b/packages/server/src/enclave_server/routes/rounds.rs @@ -3,7 +3,7 @@ use log::info; use actix_web::{web, HttpResponse, Responder}; -use crate::enclave_server::models::PKRequest; +use crate::enclave_server::models::{CTRequest, PKRequest}; use crate::enclave_server::database::{generate_emoji, get_e3, get_e3_round}; use crate::enclave_server::models::{ AppState, CrispConfig, JsonResponse, ReportTallyRequest, RoundCount @@ -12,8 +12,7 @@ use crate::enclave_server::models::{ pub fn setup_routes(config: &mut web::ServiceConfig) { config .route("/get_rounds", web::get().to(get_rounds)) - .route("/get_pk_by_round", web::post().to(get_pk_by_round)) - .route("/report_tally", web::post().to(report_tally)); + .route("/get_pk_by_round", web::post().to(get_pk_by_round)); } async fn get_rounds()-> impl Responder { @@ -35,34 +34,18 @@ async fn get_pk_by_round( ) -> impl Responder { let mut incoming = data.into_inner(); info!("Request for round {:?} public key", incoming.round_id); - let (state_data, _) = get_e3(incoming.round_id as u64).await.unwrap(); + let (state_data, _) = get_e3(incoming.round_id).await.unwrap(); incoming.pk_bytes = state_data.committee_public_key; HttpResponse::Ok().json(incoming) } - -// Report Tally Handler -async fn report_tally( - data: web::Json, - state: web::Data, +async fn get_ct_by_round( + data: web::Json, ) -> impl Responder { - let incoming = data.into_inner(); - info!("Request report tally for round {:?}", incoming.round_id); - - let (mut state_data, key) = get_e3(incoming.round_id as u64).await.unwrap(); - - if state_data.votes_option_1 == 0 && state_data.votes_option_2 == 0 { - state_data.votes_option_1 = incoming.option_1 as u64; - state_data.votes_option_2 = incoming.option_2 as u64; - - let state_str = serde_json::to_string(&state_data).unwrap(); - let db = state.db.write().await; - db.insert(key, state_str.into_bytes()).unwrap(); - } - - let response = JsonResponse { - response: "Tally Reported".to_string(), - }; + let mut incoming = data.into_inner(); + info!("Request for round {:?} public key", incoming.round_id); + let (state_data, _) = get_e3(incoming.round_id).await.unwrap(); + incoming.ct_bytes = state_data.ciphertext_output; - HttpResponse::Ok().json(response) + HttpResponse::Ok().json(incoming) } diff --git a/packages/server/src/enclave_server/routes/voting.rs b/packages/server/src/enclave_server/routes/voting.rs index b5f478e..7698560 100644 --- a/packages/server/src/enclave_server/routes/voting.rs +++ b/packages/server/src/enclave_server/routes/voting.rs @@ -38,8 +38,9 @@ async fn broadcast_enc_vote( let sol_vote = Bytes::from(vote.enc_vote_bytes); let e3_id = U256::from(vote.round_id); - let tx_hash = match call_contract(e3_id, sol_vote, state_data.enclave_address.clone()).await { - Ok(hash) => hash.to_string(), + let contract = EnclaveContract::new().await.unwrap(); + let tx_hash = match contract.publish_input(e3_id, sol_vote).await { + Ok(hash) => hash.transaction_hash.to_string(), Err(e) => { info!("Error while sending vote transaction: {:?}", e); return HttpResponse::InternalServerError().body("Failed to broadcast vote"); @@ -83,22 +84,3 @@ async fn get_vote_count_by_round(data: web::Json) -> impl Resp HttpResponse::Ok().json(incoming) } - -pub async fn call_contract( - e3_id: U256, - enc_vote: Bytes, - address: String, -) -> Result> { - info!("Calling voting contract"); - - let private_key = env::var("PRIVATE_KEY").expect("PRIVATE_KEY must be set in the environment"); - let rpc_url = "http://0.0.0.0:8545"; - let contract = EnclaveContract::new(rpc_url, &address, &private_key).await?; - let receipt = contract.publish_input(e3_id, enc_vote).await?; - - // Log the transaction hash - let tx_hash = receipt.transaction_hash; - info!("Transaction hash: {:?}", tx_hash); - - Ok(tx_hash) -} From 1d7a88f57495290222701371d773bd9127a68edf Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Thu, 19 Sep 2024 01:16:52 +0500 Subject: [PATCH 32/62] Update Cli: Decyption and Plaintext Publish --- packages/evm/contracts/CRISPVoting.sol | 13 +- packages/risc0/apps/src/lib.rs | 14 +- packages/server/src/cli/mod.rs | 8 +- packages/server/src/cli/voting.rs | 128 +++++++++--------- .../src/enclave_server/blockchain/events.rs | 1 + .../src/enclave_server/blockchain/handlers.rs | 23 ++-- .../src/enclave_server/blockchain/listener.rs | 3 +- .../src/enclave_server/blockchain/relayer.rs | 5 +- .../src/enclave_server/routes/rounds.rs | 17 +-- 9 files changed, 107 insertions(+), 105 deletions(-) diff --git a/packages/evm/contracts/CRISPVoting.sol b/packages/evm/contracts/CRISPVoting.sol index c3e0ff7..bc37dd7 100644 --- a/packages/evm/contracts/CRISPVoting.sol +++ b/packages/evm/contracts/CRISPVoting.sol @@ -108,22 +108,19 @@ contract CRISPVoting { // Publish ciphertext output function publishCiphertextOutput( uint256 e3Id, - bytes memory data + bytes calldata data, + bytes memory proof ) external returns (bool success) { require( e3Polls[e3Id].ciphertextOutput.length == 0, "Ciphertext already published." ); - - ( - bytes memory verification, - bytes memory ciphertext - ) = abi.decode(data, (bytes, bytes)); + require(proof.length > 0, "Proof is Invalid."); e3Polls[e3Id].ciphertextOutput = abi.encodePacked( - keccak256(ciphertext) + keccak256(data) ); - emit CiphertextOutputPublished(e3Id, ciphertext); + emit CiphertextOutputPublished(e3Id, data); return true; } diff --git a/packages/risc0/apps/src/lib.rs b/packages/risc0/apps/src/lib.rs index 536f4ea..abfd4cd 100644 --- a/packages/risc0/apps/src/lib.rs +++ b/packages/risc0/apps/src/lib.rs @@ -1,11 +1,10 @@ -use voting_core::fhe_processor; use anyhow::Result; -use compute_provider::{ - ComputeInput, ComputeManager, ComputeProvider, ComputeResult, FHEInputs, -}; +use compute_provider::{ComputeInput, ComputeManager, ComputeProvider, ComputeResult, FHEInputs}; use methods::VOTING_ELF; use risc0_ethereum_contracts::groth16; use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts, VerifierContext}; +use voting_core::fhe_processor; +use std::env; pub struct Risc0Provider; pub struct Risc0Output { @@ -18,6 +17,13 @@ impl ComputeProvider for Risc0Provider { fn prove(&self, input: &ComputeInput) -> Self::Output { println!("Proving with RISC0 provider"); + let var_name = "BONSAI_API_URL"; + + match env::var(var_name) { + Ok(value) => println!("{}: {}", var_name, value), + Err(e) => println!("Couldn't read {}: {}", var_name, e), + } + let env = ExecutorEnv::builder() .write(input) .unwrap() diff --git a/packages/server/src/cli/mod.rs b/packages/server/src/cli/mod.rs index 2edc28e..85ef36c 100644 --- a/packages/server/src/cli/mod.rs +++ b/packages/server/src/cli/mod.rs @@ -12,7 +12,7 @@ use std::io::Read; use http_body_util::Empty; use auth::{authenticate_user, AuthenticationResponse}; -use voting::{initialize_crisp_round, participate_in_existing_round, activate_e3_round}; +use voting::{initialize_crisp_round, participate_in_existing_round, activate_e3_round, decrypt_and_publish_result}; use serde::{Deserialize, Serialize}; use env_logger::{Builder, Target}; use log::LevelFilter; @@ -88,12 +88,14 @@ pub async fn run_cli() -> Result<(), Box> { initialize_crisp_round(&config).await?; } 1 => { - let auth_res = authenticate_user(&config, &client).await?; activate_e3_round(&config).await?; } 2 => { + participate_in_existing_round(&config).await?; + } + 3 => { let auth_res = authenticate_user(&config, &client).await?; - participate_in_existing_round(&config, &client, &auth_res).await?; + decrypt_and_publish_result(&config, &client, &auth_res).await?; } _ => unreachable!(), } diff --git a/packages/server/src/cli/voting.rs b/packages/server/src/cli/voting.rs index d36bb6e..70e50b8 100644 --- a/packages/server/src/cli/voting.rs +++ b/packages/server/src/cli/voting.rs @@ -1,20 +1,26 @@ -use std::{env, sync::Arc}; +use chrono::Utc; use dialoguer::{theme::ColorfulTheme, FuzzySelect, Input}; use http_body_util::BodyExt; use hyper::{body::Incoming, Method, Request, Response}; -use serde::{Deserialize, Serialize}; use log::info; -use chrono::Utc; +use serde::{Deserialize, Serialize}; +use std::{env, sync::Arc}; -use alloy::primitives::{Address, Bytes, U256}; +use alloy::{ + primitives::{Address, Bytes, U256}, + sol_types::{SolCall, SolEvent, SolValue}, +}; use crate::enclave_server::blockchain::relayer::EnclaveContract; use crate::cli::{AuthenticationResponse, HyperClientPost, GLOBAL_DB}; use crate::util::timeit::timeit; -use fhe::bfv::{BfvParametersBuilder, Encoding, Plaintext, PublicKey, BfvParameters, SecretKey}; -use fhe_traits::{Deserialize as FheDeserialize, DeserializeParametrized, DeserializeWithContext, FheDecoder, FheDecrypter, FheEncoder, FheEncrypter, Serialize as FheSerialize}; +use fhe::bfv::{BfvParameters, BfvParametersBuilder, Encoding, Plaintext, PublicKey, SecretKey, Ciphertext}; use fhe_math::rq::Poly; +use fhe_traits::{ + Deserialize as FheDeserialize, DeserializeParametrized, DeserializeWithContext, FheDecoder, + FheDecrypter, FheEncoder, FheEncrypter, Serialize as FheSerialize, +}; use rand::thread_rng; #[derive(Debug, Deserialize, Serialize)] @@ -60,21 +66,21 @@ struct JsonResponseTxHash { tx_hash: String, } -async fn get_response_body(resp: Response) -> Result> { +async fn get_response_body( + resp: Response, +) -> Result> { let body_bytes = resp.collect().await?.to_bytes(); Ok(String::from_utf8(body_bytes.to_vec())?) } pub async fn initialize_crisp_round( - config: &super::CrispConfig + config: &super::CrispConfig, ) -> Result<(), Box> { info!("Starting new CRISP round!"); info!("Initializing Keyshare nodes..."); + let contract = EnclaveContract::new().await?; let params = generate_bfv_parameters().unwrap().to_bytes(); - - let contract = EnclaveContract::new().await?; - let filter: Address = "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5".parse()?; let threshold: [u32; 2] = [1, 2]; let start_window: [U256; 2] = [U256::from(Utc::now().timestamp()), U256::from(Utc::now().timestamp() + 600)]; @@ -88,7 +94,9 @@ pub async fn initialize_crisp_round( Ok(()) } -pub async fn activate_e3_round(config: &super::CrispConfig) -> Result<(), Box> { +pub async fn activate_e3_round( + config: &super::CrispConfig, +) -> Result<(), Box> { let input_e3_id: u64 = Input::with_theme(&ColorfulTheme::default()) .with_prompt("Enter CRISP round ID.") .interact_text()?; @@ -96,9 +104,18 @@ pub async fn activate_e3_round(config: &super::CrispConfig) -> Result<(), Box Result<(), Box::try_decode(&pt, Encoding::poly())?[0]; println!("Vote count: {:?}", votes); info!("Calling contract with plaintext output."); let contract = EnclaveContract::new().await?; - let res = contract.publish_plaintext_output(U256::from(input_crisp_id), Bytes::from(votes.to_be_bytes())).await?; + let res = contract + .publish_plaintext_output(U256::from(input_crisp_id), Bytes::from(votes.to_be_bytes())) + .await?; println!("Vote broadcast. TxHash: {:?}", res.transaction_hash); - + Ok(()) } - pub async fn participate_in_existing_round( - config: &super::CrispConfig, - client: &HyperClientPost, - auth_res: &AuthenticationResponse, + config: &super::CrispConfig ) -> Result<(), Box> { let input_crisp_id: u64 = Input::with_theme(&ColorfulTheme::default()) .with_prompt("Enter CRISP round ID.") .interact_text()?; info!("Voting state Initialized"); - // Get public encrypt key - let response_pk = PKRequest { - round_id: input_crisp_id, - pk_bytes: vec![0], - }; - - let url = format!("{}/get_pk_by_round", config.enclave_address); - let req = Request::builder() - .header("Content-Type", "application/json") - .method(Method::POST) - .uri(url) - .body(serde_json::to_string(&response_pk)?)?; - - let resp = client.request(req).await?; - info!("Response status: {}", resp.status()); - - let body_str = get_response_body(resp).await?; - let pk_res: PKRequest = serde_json::from_str(&body_str)?; - info!("Shared Public Key for CRISP round {:?} collected.", pk_res.round_id); - info!("Public Key: {:?}", pk_res.pk_bytes); - - let params = timeit!( - "Parameters generation", - generate_bfv_parameters()? - ); - let pk_deserialized = PublicKey::from_bytes(&pk_res.pk_bytes, ¶ms)?; + let db = GLOBAL_DB.read().await; + let params_bytes = db.get(format!("e3:{}", input_crisp_id))?.ok_or("Key not found")?; + let e3_params: FHEParams = serde_json::from_slice(¶ms_bytes)?; + let params = timeit!("Parameters generation", generate_bfv_parameters()?); + let pk_deserialized = PublicKey::from_bytes(&e3_params.pk, ¶ms)?; let vote_choice = get_user_vote()?; if vote_choice.is_none() { @@ -225,17 +228,20 @@ pub async fn participate_in_existing_round( info!("Calling voting contract with encrypted vote."); let contract = EnclaveContract::new().await?; - let res = contract.publish_input(U256::from(input_crisp_id), Bytes::from(ct.to_bytes())).await?; + let res = contract + .publish_input(U256::from(input_crisp_id), Bytes::from(ct.to_bytes())) + .await?; println!("Vote broadcast. TxHash: {:?}", res.transaction_hash); - + Ok(()) } -fn generate_bfv_parameters() -> Result, Box> { +fn generate_bfv_parameters( +) -> Result, Box> { let degree = 4096; let plaintext_modulus: u64 = 4096; let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; - + Ok(BfvParametersBuilder::new() .set_degree(degree) .set_plaintext_modulus(plaintext_modulus) @@ -272,4 +278,4 @@ fn encrypt_vote( ) -> Result> { let pt = Plaintext::try_encode(&[vote], Encoding::poly(), params)?; Ok(public_key.try_encrypt(&pt, &mut thread_rng())?) -} \ No newline at end of file +} diff --git a/packages/server/src/enclave_server/blockchain/events.rs b/packages/server/src/enclave_server/blockchain/events.rs index 896488b..6c715f1 100644 --- a/packages/server/src/enclave_server/blockchain/events.rs +++ b/packages/server/src/enclave_server/blockchain/events.rs @@ -5,6 +5,7 @@ use alloy::{ }; use eyre::Result; +use log::info; use super::handlers::{handle_e3, handle_input_published, handle_plaintext_output_published, handle_ciphertext_output_published}; use super::listener::ContractEvent; diff --git a/packages/server/src/enclave_server/blockchain/handlers.rs b/packages/server/src/enclave_server/blockchain/handlers.rs index 38fb20b..33bf43a 100644 --- a/packages/server/src/enclave_server/blockchain/handlers.rs +++ b/packages/server/src/enclave_server/blockchain/handlers.rs @@ -2,8 +2,11 @@ use super::{ events::{CiphertextOutputPublished, E3Activated, InputPublished, PlaintextOutputPublished}, relayer::EnclaveContract, }; -use crate::enclave_server::{config::CONFIG, database::{generate_emoji, get_e3, increment_e3_round, GLOBAL_DB}}; use crate::enclave_server::models::E3; +use crate::enclave_server::{ + config::CONFIG, + database::{generate_emoji, get_e3, increment_e3_round, GLOBAL_DB}, +}; use alloy::{ rpc::types::Log, sol_types::{SolCall, SolEvent}, @@ -11,8 +14,8 @@ use alloy::{ use alloy_sol_types::SolValue; use chrono::Utc; use compute_provider::FHEInputs; -use std::error::Error; use std::env; +use std::error::Error; use tokio::time::{sleep, Duration}; use voting_risc0::run_compute; @@ -105,21 +108,18 @@ pub async fn handle_e3( .await .unwrap(); - let data = ( - (risc0_output.result.ciphertext_hash, risc0_output.seal), - ciphertext, - ); - - let encoded_data = data.abi_encode(); + let proof = ( + risc0_output.result.ciphertext_hash, risc0_output.seal + ).abi_encode(); // Params will be encoded on chain to create the journal let tx = contract - .publish_ciphertext_output(e3_activated.e3Id, encoded_data.into()) + .publish_ciphertext_output(e3_activated.e3Id, ciphertext.into(), proof.into()) .await?; info!( "CiphertextOutputPublished event published with tx: {:?}", - tx + tx.transaction_hash ); } @@ -145,8 +145,6 @@ pub async fn handle_input_published( Ok(()) } - - pub async fn handle_ciphertext_output_published( ciphertext_output: CiphertextOutputPublished, ) -> Result<(), Box> { @@ -169,6 +167,7 @@ pub async fn handle_plaintext_output_published( info!("Handling PlaintextOutputPublished event..."); let e3_id = plaintext_output.e3Id.to::(); + let (mut e3, key) = get_e3(e3_id).await.unwrap(); e3.plaintext_output = plaintext_output.plaintextOutput.to_vec(); diff --git a/packages/server/src/enclave_server/blockchain/listener.rs b/packages/server/src/enclave_server/blockchain/listener.rs index 948c40d..3c9dede 100644 --- a/packages/server/src/enclave_server/blockchain/listener.rs +++ b/packages/server/src/enclave_server/blockchain/listener.rs @@ -13,7 +13,7 @@ use std::time::Duration; use tokio::time::sleep; use log::{info, error}; -use super::events::{E3Activated, InputPublished, PlaintextOutputPublished}; +use super::events::{E3Activated, InputPublished, PlaintextOutputPublished, CiphertextOutputPublished}; pub trait ContractEvent: Send + Sync + 'static { fn process(&self, log: Log) -> Result<()>; @@ -126,6 +126,7 @@ async fn run_listener(rpc_url: &str, contract_address: Address) -> Result<()> { listener.add_event_handler::(); listener.add_event_handler::(); listener.add_event_handler::(); + listener.add_event_handler::(); loop { match listener.listen().await { diff --git a/packages/server/src/enclave_server/blockchain/relayer.rs b/packages/server/src/enclave_server/blockchain/relayer.rs index f49086e..bdea7fb 100644 --- a/packages/server/src/enclave_server/blockchain/relayer.rs +++ b/packages/server/src/enclave_server/blockchain/relayer.rs @@ -40,7 +40,7 @@ sol! { function publishInput(uint256 e3Id, bytes memory data ) external returns (bool success); - function publishCiphertextOutput(uint256 e3Id, bytes memory data ) external returns (bool success); + function publishCiphertextOutput(uint256 e3Id, bytes calldata data, bytes memory proof) external returns (bool success); function publishPlaintextOutput(uint256 e3Id, bytes memory data) external returns (bool success); @@ -121,9 +121,10 @@ impl EnclaveContract { &self, e3_id: U256, data: Bytes, + proof: Bytes, ) -> Result { let contract = Enclave::new(self.contract_address, &self.provider); - let builder = contract.publishCiphertextOutput(e3_id, data); + let builder = contract.publishCiphertextOutput(e3_id, data, proof); let receipt = builder.send().await?.get_receipt().await?; Ok(receipt) } diff --git a/packages/server/src/enclave_server/routes/rounds.rs b/packages/server/src/enclave_server/routes/rounds.rs index b57a724..1eb583e 100644 --- a/packages/server/src/enclave_server/routes/rounds.rs +++ b/packages/server/src/enclave_server/routes/rounds.rs @@ -4,7 +4,7 @@ use actix_web::{web, HttpResponse, Responder}; use crate::enclave_server::models::{CTRequest, PKRequest}; -use crate::enclave_server::database::{generate_emoji, get_e3, get_e3_round}; +use crate::enclave_server::database::{generate_emoji, get_e3, get_e3_round, GLOBAL_DB}; use crate::enclave_server::models::{ AppState, CrispConfig, JsonResponse, ReportTallyRequest, RoundCount }; @@ -12,7 +12,7 @@ use crate::enclave_server::models::{ pub fn setup_routes(config: &mut web::ServiceConfig) { config .route("/get_rounds", web::get().to(get_rounds)) - .route("/get_pk_by_round", web::post().to(get_pk_by_round)); + .route("/get_ct_by_round", web::post().to(get_ct_by_round)); } async fn get_rounds()-> impl Responder { @@ -29,23 +29,12 @@ async fn get_rounds()-> impl Responder { } } -async fn get_pk_by_round( - data: web::Json, -) -> impl Responder { - let mut incoming = data.into_inner(); - info!("Request for round {:?} public key", incoming.round_id); - let (state_data, _) = get_e3(incoming.round_id).await.unwrap(); - incoming.pk_bytes = state_data.committee_public_key; - - HttpResponse::Ok().json(incoming) -} async fn get_ct_by_round( data: web::Json, ) -> impl Responder { let mut incoming = data.into_inner(); - info!("Request for round {:?} public key", incoming.round_id); + info!("Request for round {:?} ciphertext", incoming.round_id); let (state_data, _) = get_e3(incoming.round_id).await.unwrap(); incoming.ct_bytes = state_data.ciphertext_output; - HttpResponse::Ok().json(incoming) } From 6f18f9a98e21f32c9cd066f20a4da705a8a4d41d Mon Sep 17 00:00:00 2001 From: fhedude Date: Wed, 18 Sep 2024 22:54:33 -0400 Subject: [PATCH 33/62] Made interface much easier to use for greco integration --- packages/web-rust/Cargo.toml | 3 --- packages/web-rust/src/bin/greco/greco.rs | 11 +++++------ packages/web-rust/src/bin/web_fhe_encrypt.rs | 12 +----------- 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/packages/web-rust/Cargo.toml b/packages/web-rust/Cargo.toml index 5b78ff1..b13e7a0 100644 --- a/packages/web-rust/Cargo.toml +++ b/packages/web-rust/Cargo.toml @@ -13,9 +13,6 @@ fhe = { git = "https://github.com/gnosisguild/fhe.rs.git", branch = "feature/gre fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs.git", branch = "feature/greco-integration" } fhe-math = { git = "https://github.com/gnosisguild/fhe.rs.git", branch = "feature/greco-integration" } fhe-util = { git = "https://github.com/gnosisguild/fhe.rs.git", branch = "feature/greco-integration" } -# fhe = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } -# fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } -# fhe-util = { git = "https://github.com/gnosisguild/fhe.rs", version = "0.1.0-beta.7" } rand = "0.8.5" ethers = "2.0" getrandom = { version = "0.2.11", features = ["js"] } diff --git a/packages/web-rust/src/bin/greco/greco.rs b/packages/web-rust/src/bin/greco/greco.rs index 7230187..f0391f1 100644 --- a/packages/web-rust/src/bin/greco/greco.rs +++ b/packages/web-rust/src/bin/greco/greco.rs @@ -1,9 +1,8 @@ use std::ops::Deref; -use std::sync::Arc; use fhe::bfv::{Ciphertext, Plaintext, PublicKey}; use fhe_math::rq::{Poly, Representation}; -use fhe_math::{rq::Context, zq::Modulus}; +use fhe_math::zq::Modulus; use serde_json::json; use crate::greco::poly::*; @@ -122,8 +121,6 @@ fn to_string_2d_vec(poly: &Vec>) -> Vec> { /// /// # Arguments /// -/// * `ctx` - Context object from fhe.rs holding information about elements in Rq. -/// * `t` - Plaintext modulus object. /// * `pt` - Plaintext from fhe.rs. /// * `u_rns` - Private polynomial used in ciphertext sampled from secret key distribution. /// * `e0_rns` - Error polynomial used in ciphertext sampled from error distribution. @@ -132,8 +129,6 @@ fn to_string_2d_vec(poly: &Vec>) -> Vec> { /// * `pk` - Public Key from fhe.re. /// pub fn compute_input_validation_vectors( - ctx: &Arc, - t: &Modulus, pt: &Plaintext, u_rns: &Poly, e0_rns: &Poly, @@ -141,6 +136,10 @@ pub fn compute_input_validation_vectors( ct: &Ciphertext, pk: &PublicKey, ) -> InputValidationVectors { + // Get context, plaintext modulus, and degree + let params = &pk.par; + let ctx = params.ctx_at_level(pt.level()).unwrap(); + let t = Modulus::new(params.plaintext()).unwrap(); let N: u64 = ctx.degree as u64; // Calculate k1 (independent of qi), center and reverse diff --git a/packages/web-rust/src/bin/web_fhe_encrypt.rs b/packages/web-rust/src/bin/web_fhe_encrypt.rs index e2d5071..9671c88 100644 --- a/packages/web-rust/src/bin/web_fhe_encrypt.rs +++ b/packages/web-rust/src/bin/web_fhe_encrypt.rs @@ -57,18 +57,8 @@ impl Encrypt { .map_err(|e| JsValue::from_str(&format!("Error encrypting vote: {}", e)))?; // Create Greco input validation ZKP proof - // todo: create function that modularizes this - let ctx = params - .ctx_at_level(pt.level()) - .map_err(|e| JsValue::from_str(&format!("Error extracting context: {}", e)))?; - let t = Modulus::new(params.plaintext()).map_err(|e| { - JsValue::from_str(&format!( - "Error constructing plaintext modulus object: {}", - e - )) - })?; let input_val_vectors = - compute_input_validation_vectors(&ctx, &t, &pt, &u_rns, &e0_rns, &e1_rns, &ct, &pk); + compute_input_validation_vectors(&pt, &u_rns, &e0_rns, &e1_rns, &ct, &pk); self.encrypted_vote = ct.to_bytes(); Ok(self.encrypted_vote.clone()) From c0ff1c7cdce10e8b481ce3d09cf932481ac67e76 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Thu, 19 Sep 2024 22:05:08 +0500 Subject: [PATCH 34/62] feat: Use Web Worker for wasm --- .../client/libs/wasm/pkg/crisp_web_bg.wasm | Bin 401823 -> 489830 bytes packages/client/libs/wasm/pkg/crisp_worker.js | 33 +++++++ packages/client/src/App.tsx | 6 +- .../voteManagement/VoteManagement.context.tsx | 7 +- .../voteManagement/VoteManagement.types.ts | 5 -- .../client/src/hooks/wasm/useWebAssembly.tsx | 63 +++++++------- packages/evm/contracts/CRISPVoting.sol | 12 +-- packages/server/src/cli/voting.rs | 2 +- .../src/enclave_server/blockchain/handlers.rs | 2 + .../src/enclave_server/blockchain/relayer.rs | 8 ++ .../src/enclave_server/routes/voting.rs | 1 + packages/web-rust/Cargo.lock | 82 +++++++++++++++--- packages/web-rust/Cargo.toml | 3 +- packages/web-rust/src/bin/web_fhe_encrypt.rs | 7 ++ 14 files changed, 165 insertions(+), 66 deletions(-) create mode 100644 packages/client/libs/wasm/pkg/crisp_worker.js diff --git a/packages/client/libs/wasm/pkg/crisp_web_bg.wasm b/packages/client/libs/wasm/pkg/crisp_web_bg.wasm index e2e91ad73b704721c5f71753b8f0e54d674032d3..5714a6ded7e694206e76fc268c2bcf370c4b5012 100644 GIT binary patch literal 489830 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%0DTZ$X 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 { useScrollToTop() - const { initialLoad, wasmInstance } = useVoteManagementContext() + const { initialLoad } = useVoteManagementContext() useEffect(() => { - if (!wasmInstance) { async function loadWasm() { await initialLoad() } loadWasm() - } - }, [wasmInstance]) + }, []) return ( diff --git a/packages/client/src/context/voteManagement/VoteManagement.context.tsx b/packages/client/src/context/voteManagement/VoteManagement.context.tsx index 4d17939..fa01e18 100644 --- a/packages/client/src/context/voteManagement/VoteManagement.context.tsx +++ b/packages/client/src/context/voteManagement/VoteManagement.context.tsx @@ -31,7 +31,7 @@ const VoteManagementProvider = ({ children }: VoteManagementProviderProps) => { /** * Voting Management Methods **/ - const { isLoading: wasmLoading, wasmInstance, encryptInstance, initWebAssembly, encryptVote } = useWebAssemblyHook() + const { isLoading: wasmLoading, encryptVote } = useWebAssemblyHook() const { isLoading: enclaveLoading, getRoundStateLite: getRoundStateLiteRequest, @@ -43,7 +43,7 @@ const VoteManagementProvider = ({ children }: VoteManagementProviderProps) => { } = useEnclaveServer() const initialLoad = async () => { - await initWebAssembly() + console.log("Loading wasm"); const round = await getRound() if (round) { await getRoundStateLite(round.round_count) @@ -104,8 +104,6 @@ const VoteManagementProvider = ({ children }: VoteManagementProviderProps) => { { broadcastVote, setVotingRound, setUser, - initWebAssembly, encryptVote, logout, }} diff --git a/packages/client/src/context/voteManagement/VoteManagement.types.ts b/packages/client/src/context/voteManagement/VoteManagement.types.ts index f195882..5f3bdea 100644 --- a/packages/client/src/context/voteManagement/VoteManagement.types.ts +++ b/packages/client/src/context/voteManagement/VoteManagement.types.ts @@ -1,6 +1,4 @@ import { ReactNode } from 'react' -import * as WasmInstance from 'libs/wasm/pkg/crisp_web' - import { BroadcastVoteRequest, BroadcastVoteResponse, VoteStateLite, VotingRound } from '@/model/vote.model' import { Poll, PollRequestResult, PollResult } from '@/model/poll.model' import { StatusAPIResponse } from '@farcaster/auth-client' @@ -8,8 +6,6 @@ import { Auth } from '@/model/auth.model' export type VoteManagementContextType = { isLoading: boolean - wasmInstance: WasmInstance.InitOutput | null - encryptInstance: WasmInstance.Encrypt | null user: StatusAPIResponse | null votingRound: VotingRound | null roundEndDate: Date | null @@ -28,7 +24,6 @@ export type VoteManagementContextType = { getPastPolls: () => Promise setVotingRound: React.Dispatch> setUser: (value: StatusAPIResponse | null) => void - initWebAssembly: () => Promise encryptVote: (voteId: bigint, publicKey: Uint8Array) => Promise broadcastVote: (vote: BroadcastVoteRequest) => Promise getRoundStateLite: (roundCount: number) => Promise diff --git a/packages/client/src/hooks/wasm/useWebAssembly.tsx b/packages/client/src/hooks/wasm/useWebAssembly.tsx index 4ab798c..f9f8e5e 100644 --- a/packages/client/src/hooks/wasm/useWebAssembly.tsx +++ b/packages/client/src/hooks/wasm/useWebAssembly.tsx @@ -1,50 +1,53 @@ -import { useState } from 'react' -import * as WasmInstance from 'libs/wasm/pkg/crisp_web' +import { useState, useEffect } from 'react' import { handleGenericError } from '@/utils/handle-generic-error' - import { useNotificationAlertContext } from '@/context/NotificationAlert' export const useWebAssemblyHook = () => { const { showToast } = useNotificationAlertContext() - const [wasmInstance, setWasmInstance] = useState(null) - const [encryptInstance, setEncryptInstance] = useState(null) const [isLoading, setIsLoading] = useState(false) + const [worker, setWorker] = useState(null) - const initWebAssembly = async () => { - try { - const wasmModule = await WasmInstance.default() - const newEncryptInstance = new WasmInstance.Encrypt() - setWasmInstance(wasmModule) - setEncryptInstance(newEncryptInstance) - } catch (err) { - handleGenericError('initWebAssembly', err as Error) - } finally { - setIsLoading(false) + useEffect(() => { + const newWorker = new Worker(new URL('libs/wasm/pkg/crisp_worker.js', import.meta.url), { + type: 'module', + }) + setWorker(newWorker) + return () => { + newWorker.terminate() } - } + }, []) const encryptVote = async (voteId: bigint, publicKey: Uint8Array): Promise => { - if (!encryptInstance) { - console.error('WebAssembly module not initialized') + if (!worker) { + console.error('WebAssembly worker not initialized') return } - try { + + return new Promise((resolve, reject) => { setIsLoading(true) - return encryptInstance.encrypt_vote(voteId, publicKey) - } catch (err) { - showToast({ - type: 'danger', - message: err as string, - }) - handleGenericError('encryptVote', { message: err } as Error) - } + worker.postMessage({ type: 'encrypt_vote', data: { voteId, publicKey } }) + worker.onmessage = (event) => { + const { type, success, encryptedVote, error } = event.data + if (type === 'encrypt_vote') { + if (success) { + resolve(encryptedVote) + } else { + showToast({ + type: 'danger', + message: error, + }) + handleGenericError('encryptVote', new Error(error)) + reject(new Error(error)) + } + setIsLoading(false) + } + } + }) } return { isLoading, - wasmInstance, - encryptInstance, - initWebAssembly, encryptVote, } } + diff --git a/packages/evm/contracts/CRISPVoting.sol b/packages/evm/contracts/CRISPVoting.sol index bc37dd7..ecb7c84 100644 --- a/packages/evm/contracts/CRISPVoting.sol +++ b/packages/evm/contracts/CRISPVoting.sol @@ -39,7 +39,7 @@ contract CRISPVoting { bytes ciphertextOutput ); - uint256 public e3Counter = 0; // Counter for E3 IDs + uint256 public nexte3Id = 0; // Counter for E3 IDs // Request a new E3 computation function request( @@ -51,10 +51,10 @@ contract CRISPVoting { bytes memory e3ProgramParams, bytes memory computeProviderParams ) external payable returns (uint256 e3Id, E3 memory e3) { - e3Counter++; + nexte3Id++; E3 memory newE3 = E3({ - seed: e3Counter, + seed: nexte3Id, threshold: threshold, startWindow: startWindow, duration: duration, @@ -68,9 +68,9 @@ contract CRISPVoting { plaintextOutput: "" }); - e3Polls[e3Counter] = newE3; + e3Polls[nexte3Id] = newE3; - return (e3Counter, newE3); + return (nexte3Id, newE3); } // Activate the poll @@ -137,7 +137,7 @@ contract CRISPVoting { ); require(e3.plaintextOutput.length == 0, "Plaintext already published."); - e3.plaintextOutput = abi.encodePacked(keccak256(data)); + e3.plaintextOutput = data; emit PlaintextOutputPublished(e3Id, data); return true; } diff --git a/packages/server/src/cli/voting.rs b/packages/server/src/cli/voting.rs index 70e50b8..83ab5d8 100644 --- a/packages/server/src/cli/voting.rs +++ b/packages/server/src/cli/voting.rs @@ -84,7 +84,7 @@ pub async fn initialize_crisp_round( let filter: Address = "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5".parse()?; let threshold: [u32; 2] = [1, 2]; let start_window: [U256; 2] = [U256::from(Utc::now().timestamp()), U256::from(Utc::now().timestamp() + 600)]; - let duration: U256 = U256::from(40); + let duration: U256 = U256::from(300); let e3_program: Address = "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5".parse()?; let e3_params = Bytes::from(params); let compute_provider_params = Bytes::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); diff --git a/packages/server/src/enclave_server/blockchain/handlers.rs b/packages/server/src/enclave_server/blockchain/handlers.rs index 33bf43a..ed45b6b 100644 --- a/packages/server/src/enclave_server/blockchain/handlers.rs +++ b/packages/server/src/enclave_server/blockchain/handlers.rs @@ -153,6 +153,7 @@ pub async fn handle_ciphertext_output_published( let e3_id = ciphertext_output.e3Id.to::(); let (mut e3, key) = get_e3(e3_id).await.unwrap(); e3.ciphertext_output = ciphertext_output.ciphertextOutput.to_vec(); + e3.status = "Published".to_string(); let db = GLOBAL_DB.write().await; db.insert(key, serde_json::to_vec(&e3).unwrap()).unwrap(); @@ -173,6 +174,7 @@ pub async fn handle_plaintext_output_published( e3.votes_option_2 = u64::from_be_bytes(e3.plaintext_output.as_slice().try_into().unwrap()); e3.votes_option_1 = e3.vote_count - e3.votes_option_2; + e3.status = "Finished".to_string(); let db = GLOBAL_DB.write().await; db.insert(key, serde_json::to_vec(&e3).unwrap()).unwrap(); diff --git a/packages/server/src/enclave_server/blockchain/relayer.rs b/packages/server/src/enclave_server/blockchain/relayer.rs index bdea7fb..7235b3a 100644 --- a/packages/server/src/enclave_server/blockchain/relayer.rs +++ b/packages/server/src/enclave_server/blockchain/relayer.rs @@ -34,6 +34,8 @@ sol! { #[derive(Debug)] #[sol(rpc)] contract Enclave { + uint256 public nexte3Id = 0; + function request(address filter, uint32[2] calldata threshold, uint256[2] calldata startWindow, uint256 duration, address e3Program, bytes memory e3ProgramParams, bytes memory computeProviderParams) external payable returns (uint256 e3Id, E3 memory e3); function activate(uint256 e3Id, bytes memory pubKey) external returns (bool success); @@ -140,6 +142,12 @@ impl EnclaveContract { Ok(receipt) } + pub async fn get_e3_id(&self) -> Result { + let contract = Enclave::new(self.contract_address, &self.provider); + let e3_id = contract.nexte3Id().call().await?; + Ok(e3_id.nexte3Id) + } + pub async fn get_e3(&self, e3_id: U256) -> Result { let contract = Enclave::new(self.contract_address, &self.provider); let e3_return = contract.getE3(e3_id).call().await?; diff --git a/packages/server/src/enclave_server/routes/voting.rs b/packages/server/src/enclave_server/routes/voting.rs index 7698560..c7a84d3 100644 --- a/packages/server/src/enclave_server/routes/voting.rs +++ b/packages/server/src/enclave_server/routes/voting.rs @@ -48,6 +48,7 @@ async fn broadcast_enc_vote( }; state_data.has_voted.push(vote.postId); + // Lock the database for writing let db = state.db.write().await; if let Err(e) = db.insert(key, serde_json::to_vec(&state_data).unwrap()) { info!("Error updating state: {:?}", e); diff --git a/packages/web-rust/Cargo.lock b/packages/web-rust/Cargo.lock index e20f980..be09467 100644 --- a/packages/web-rust/Cargo.lock +++ b/packages/web-rust/Cargo.lock @@ -399,6 +399,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "const-hex" version = "1.11.3" @@ -479,6 +489,7 @@ dependencies = [ "serde", "serde_json", "wasm-bindgen", + "wasm-bindgen-test", "web-sys", ] @@ -1673,9 +1684,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] @@ -1832,6 +1843,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minicov" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c71e683cd655513b99affab7d317deb690528255a0d5f717f1024093c12b169" +dependencies = [ + "cc", + "walkdir", +] + [[package]] name = "miniz_oxide" version = "0.7.2" @@ -2756,6 +2777,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -3536,19 +3563,20 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", @@ -3561,9 +3589,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", @@ -3573,9 +3601,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3583,9 +3611,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", @@ -3596,9 +3624,35 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68497a05fb21143a08a7d24fc81763384a3072ee43c44e86aad1744d6adef9d9" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "minicov", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "4b8220be1fa9e4c889b30fd207d4906657e7e90b12e0e6b0c8b8d8709f5de021" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.59", +] [[package]] name = "web-sys" diff --git a/packages/web-rust/Cargo.toml b/packages/web-rust/Cargo.toml index b13e7a0..c06aa34 100644 --- a/packages/web-rust/Cargo.toml +++ b/packages/web-rust/Cargo.toml @@ -25,7 +25,8 @@ num-bigint = "0.4.6" num-traits = "0.2" serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0.114" -wasm-bindgen = "0.2" +wasm-bindgen = "0.2.93" +wasm-bindgen-test = "0.3.43" [lib] crate-type = ["cdylib", "rlib"] diff --git a/packages/web-rust/src/bin/web_fhe_encrypt.rs b/packages/web-rust/src/bin/web_fhe_encrypt.rs index 9671c88..1d7e193 100644 --- a/packages/web-rust/src/bin/web_fhe_encrypt.rs +++ b/packages/web-rust/src/bin/web_fhe_encrypt.rs @@ -4,6 +4,7 @@ mod util; use fhe_math::zq::Modulus; use greco::greco::*; use wasm_bindgen::prelude::*; +use wasm_bindgen_test::*; use serde::Deserialize; use std::{env, sync::Arc, thread, time}; @@ -72,3 +73,9 @@ impl Encrypt { fn main() -> Result<(), Box> { Ok(()) } +// Tests +#[wasm_bindgen_test] +fn test_encrypt_vote() { + // assert true + assert_eq!(1, 1); +} From 6da3324da83f911d1dd7598c32c48062ffb0bba6 Mon Sep 17 00:00:00 2001 From: fhedude Date: Thu, 19 Sep 2024 14:40:04 -0400 Subject: [PATCH 35/62] Included bounds code in greco, refactored substantially --- packages/web-rust/src/bin/greco/greco.rs | 980 +++++++++++++------ packages/web-rust/src/bin/web_fhe_encrypt.rs | 7 +- 2 files changed, 695 insertions(+), 292 deletions(-) diff --git a/packages/web-rust/src/bin/greco/greco.rs b/packages/web-rust/src/bin/greco/greco.rs index f0391f1..838e097 100644 --- a/packages/web-rust/src/bin/greco/greco.rs +++ b/packages/web-rust/src/bin/greco/greco.rs @@ -1,6 +1,10 @@ +use std::fs::File; +use std::io::Write; use std::ops::Deref; +use std::path::Path; +use std::sync::Arc; -use fhe::bfv::{Ciphertext, Plaintext, PublicKey}; +use fhe::bfv::{BfvParameters, Ciphertext, Plaintext, PublicKey}; use fhe_math::rq::{Poly, Representation}; use fhe_math::zq::Modulus; use serde_json::json; @@ -106,306 +110,708 @@ impl InputValidationVectors { "ct1is": to_string_2d_vec(&self.ct1is), }) } -} -fn to_string_1d_vec(poly: &Vec) -> Vec { - poly.iter().map(|coef| coef.to_string()).collect() + /// Check whether all members of `self` have the correct length based on the provided `degree` and `num_moduli`. + /// + /// # Arguments + /// + /// * `num_moduli` - The expected number of moduli (outer vector length). + /// * `degree` - The expected degree (inner vector length). + /// + /// # Returns + /// + /// Returns `true` if all vectors have the correct lengths, `false` otherwise. + pub fn check_correct_lengths(&self, num_moduli: usize, degree: usize) -> bool { + // Helper function to check 2D vector lengths + let check_2d_lengths = + |vec: &Vec>, expected_outer_len: usize, expected_inner_len: usize| { + vec.len() == expected_outer_len && vec.iter().all(|v| v.len() == expected_inner_len) + }; + + // Helper function to check 1D vector lengths + let check_1d_lengths = |vec: &Vec, expected_len: usize| vec.len() == expected_len; + + // Use all to combine all checks into a single statement + [ + // 2D vector checks + check_2d_lengths(&self.pk0is, num_moduli, degree), + check_2d_lengths(&self.pk1is, num_moduli, degree), + check_2d_lengths(&self.ct0is, num_moduli, degree), + check_2d_lengths(&self.ct1is, num_moduli, degree), + check_2d_lengths(&self.r1is, num_moduli, 2 * (degree - 1)), + check_2d_lengths(&self.r2is, num_moduli, degree - 2), + check_2d_lengths(&self.p1is, num_moduli, 2 * (degree - 1)), + check_2d_lengths(&self.p2is, num_moduli, degree - 2), + // 1D vector checks + check_1d_lengths(&self.k0is, num_moduli), + check_1d_lengths(&self.u, degree), + check_1d_lengths(&self.e0, degree), + check_1d_lengths(&self.e1, degree), + check_1d_lengths(&self.k1, degree), + ] + .iter() + .all(|&check| check) + } + + /// Create the centered validation vectors necessary for creating an input validation proof according to Greco. + /// For more information, please see https://eprint.iacr.org/2024/594. + /// + /// # Arguments + /// + /// * `pt` - Plaintext from fhe.rs. + /// * `u_rns` - Private polynomial used in ciphertext sampled from secret key distribution. + /// * `e0_rns` - Error polynomial used in ciphertext sampled from error distribution. + /// * `e1_rns` - Error polynomioal used in cihpertext sampled from error distribution. + /// * `ct` - Ciphertext from fhe.rs. + /// * `pk` - Public Key from fhe.rs. + pub fn compute( + pt: &Plaintext, + u_rns: &Poly, + e0_rns: &Poly, + e1_rns: &Poly, + ct: &Ciphertext, + pk: &PublicKey, + ) -> InputValidationVectors { + // Get context, plaintext modulus, and degree + let params = &pk.par; + let ctx = params.ctx_at_level(pt.level()).unwrap(); + let t = Modulus::new(params.plaintext()).unwrap(); + let N: u64 = ctx.degree as u64; + + // Calculate k1 (independent of qi), center and reverse + let q_mod_t = (ctx.modulus() % t.modulus()).to_u64().unwrap(); // [q]_t + let mut k1_u64 = pt.value.deref().to_vec(); // m + t.scalar_mul_vec(&mut k1_u64, q_mod_t); // k1 = [q*m]_t + let mut k1: Vec = k1_u64.iter().map(|&x| BigInt::from(x)).rev().collect(); + reduce_and_center_coefficients_mut(&mut k1, &BigInt::from(t.modulus())); + + // Extract single vectors of u, e1, and e2 as Vec, center and reverse + let mut u_rns_copy = u_rns.clone(); + let mut e0_rns_copy = e0_rns.clone(); + let mut e1_rns_copy = e1_rns.clone(); + + u_rns_copy.change_representation(Representation::PowerBasis); + e0_rns_copy.change_representation(Representation::PowerBasis); + e1_rns_copy.change_representation(Representation::PowerBasis); + + let u: Vec = unsafe { + ctx.moduli_operators()[0] + .center_vec_vt(u_rns_copy.coefficients().row(0).as_slice().unwrap()) + .iter() + .rev() + .map(|&x| BigInt::from(x)) + .collect() + }; + + let e0: Vec = unsafe { + ctx.moduli_operators()[0] + .center_vec_vt(e0_rns_copy.coefficients().row(0).as_slice().unwrap()) + .iter() + .rev() + .map(|&x| BigInt::from(x)) + .collect() + }; + + let e1: Vec = unsafe { + ctx.moduli_operators()[0] + .center_vec_vt(e1_rns_copy.coefficients().row(0).as_slice().unwrap()) + .iter() + .rev() + .map(|&x| BigInt::from(x)) + .collect() + }; + + // Extract and convert ciphertext and plaintext polynomials + let mut ct0 = ct.c[0].clone(); + let mut ct1 = ct.c[1].clone(); + ct0.change_representation(Representation::PowerBasis); + ct1.change_representation(Representation::PowerBasis); + + let mut pk0: Poly = pk.c.c[0].clone(); + let mut pk1: Poly = pk.c.c[1].clone(); + pk0.change_representation(Representation::PowerBasis); + pk1.change_representation(Representation::PowerBasis); + + // Create cyclotomic polynomial x^N + 1 + let mut cyclo = vec![BigInt::from(0u64); (N + 1) as usize]; + cyclo[0] = BigInt::from(1u64); // x^N term + cyclo[N as usize] = BigInt::from(1u64); // x^0 term + + // Print + /* + println!("m = {:?}\n", &m); + println!("k1 = {:?}\n", &k1); + println!("u = {:?}\n", &u); + println!("e0 = {:?}\n", &e0); + println!("e1 = {:?}\n", &e1); + */ + + // Initialize matrices to store results + let num_moduli = ctx.moduli().len(); + let mut res = InputValidationVectors::new(num_moduli, N as usize); + + // Perform the main computation logic + let results: Vec<( + usize, + Vec, + Vec, + BigInt, + Vec, + Vec, + Vec, + Vec, + Vec, + Vec, + )> = izip!( + ctx.moduli_operators(), + ct0.coefficients().rows(), + ct1.coefficients().rows(), + pk0.coefficients().rows(), + pk1.coefficients().rows() + ) + .enumerate() + .par_bridge() + .map( + |(i, (qi, ct0_coeffs, ct1_coeffs, pk0_coeffs, pk1_coeffs))| { + // --------------------------------------------------- ct0i --------------------------------------------------- + + // Convert to vectors of bigint, center, and reverse order. + let mut ct0i: Vec = + ct0_coeffs.iter().rev().map(|&x| BigInt::from(x)).collect(); + let mut ct1i: Vec = + ct1_coeffs.iter().rev().map(|&x| BigInt::from(x)).collect(); + let mut pk0i: Vec = + pk0_coeffs.iter().rev().map(|&x| BigInt::from(x)).collect(); + let mut pk1i: Vec = + pk1_coeffs.iter().rev().map(|&x| BigInt::from(x)).collect(); + + let qi_bigint = BigInt::from(qi.modulus()); + + reduce_and_center_coefficients_mut(&mut ct0i, &qi_bigint); + reduce_and_center_coefficients_mut(&mut ct1i, &qi_bigint); + reduce_and_center_coefficients_mut(&mut pk0i, &qi_bigint); + reduce_and_center_coefficients_mut(&mut pk1i, &qi_bigint); + + // k0qi = -t^{-1} mod qi + let koqi_u64 = qi.inv(qi.neg(t.modulus())).unwrap(); + let k0qi = BigInt::from(koqi_u64); // Do not need to center this + + // ki = k1 * k0qi + let ki = poly_scalar_mul(&k1, &k0qi); + + // Calculate ct0i_hat = pk0 * ui + e0i + ki + let ct0i_hat = { + let pk0i_times_u = poly_mul(&pk0i, &u); + assert_eq!((pk0i_times_u.len() as u64) - 1, 2 * (N - 1)); + + let e0_plus_ki = poly_add(&e0, &ki); + assert_eq!((e0_plus_ki.len() as u64) - 1, N - 1); + + poly_add(&pk0i_times_u, &e0_plus_ki) + }; + assert_eq!((ct0i_hat.len() as u64) - 1, 2 * (N - 1)); + + // Check whether ct0i_hat mod R_qi (the ring) is equal to ct0i + let mut ct0i_hat_mod_rqi = ct0i_hat.clone(); + reduce_in_ring(&mut ct0i_hat_mod_rqi, &cyclo, &qi_bigint); + assert_eq!(&ct0i, &ct0i_hat_mod_rqi); + + // Compute r2i numerator = ct0i - ct0i_hat and reduce/center the polynomial + let ct0i_minus_ct0i_hat = poly_sub(&ct0i, &ct0i_hat); + assert_eq!((ct0i_minus_ct0i_hat.len() as u64) - 1, 2 * (N - 1)); + let mut ct0i_minus_ct0i_hat_mod_zqi = ct0i_minus_ct0i_hat.clone(); + reduce_and_center_coefficients_mut(&mut ct0i_minus_ct0i_hat_mod_zqi, &qi_bigint); + + // Compute r2i as the quotient of numerator divided by the cyclotomic polynomial + // to produce: (ct0i - ct0i_hat) / (x^N + 1) mod Z_qi. Remainder should be empty. + let (r2i, r2i_rem) = poly_div(&ct0i_minus_ct0i_hat_mod_zqi, &cyclo); + assert!(r2i_rem.is_empty()); + assert_eq!((r2i.len() as u64) - 1, N - 2); // Order(r2i) = N - 2 + + // Assert that (ct0i - ct0i_hat) = (r2i * cyclo) mod Z_qi + let r2i_times_cyclo = poly_mul(&r2i, &cyclo); + let mut r2i_times_cyclo_mod_zqi = r2i_times_cyclo.clone(); + reduce_and_center_coefficients_mut(&mut r2i_times_cyclo_mod_zqi, &qi_bigint); + assert_eq!(&ct0i_minus_ct0i_hat_mod_zqi, &r2i_times_cyclo_mod_zqi); + assert_eq!((r2i_times_cyclo.len() as u64) - 1, 2 * (N - 1)); + + // Calculate r1i = (ct0i - ct0i_hat - r2i * cyclo) / qi mod Z_p. Remainder should be empty. + let r1i_num = poly_sub(&ct0i_minus_ct0i_hat, &r2i_times_cyclo); + assert_eq!((r1i_num.len() as u64) - 1, 2 * (N - 1)); + + let (r1i, r1i_rem) = poly_div(&r1i_num, &[qi_bigint.clone()]); + assert!(r1i_rem.is_empty()); + assert_eq!((r1i.len() as u64) - 1, 2 * (N - 1)); // Order(r1i) = 2*(N-1) + assert_eq!(&r1i_num, &poly_mul(&r1i, &[qi_bigint.clone()])); + + // Assert that ct0i = ct0i_hat + r1i * qi + r2i * cyclo mod Z_p + let r1i_times_qi = poly_scalar_mul(&r1i, &qi_bigint); + let mut ct0i_calculated = + poly_add(&poly_add(&ct0i_hat, &r1i_times_qi), &r2i_times_cyclo); + + while ct0i_calculated.len() > 0 && ct0i_calculated[0].is_zero() { + ct0i_calculated.remove(0); + } + + assert_eq!(&ct0i, &ct0i_calculated); + + // --------------------------------------------------- ct1i --------------------------------------------------- + + // Calculate ct1i_hat = pk1i * ui + e1i + let ct1i_hat = { + let pk1i_times_u = poly_mul(&pk1i, &u); + assert_eq!((pk1i_times_u.len() as u64) - 1, 2 * (N - 1)); + + poly_add(&pk1i_times_u, &e1) + }; + assert_eq!((ct1i_hat.len() as u64) - 1, 2 * (N - 1)); + + // Check whether ct1i_hat mod R_qi (the ring) is equal to ct1i + let mut ct1i_hat_mod_rqi = ct1i_hat.clone(); + reduce_in_ring(&mut ct1i_hat_mod_rqi, &cyclo, &qi_bigint); + assert_eq!(&ct1i, &ct1i_hat_mod_rqi); + + // Compute p2i numerator = ct1i - ct1i_hat + let ct1i_minus_ct1i_hat = poly_sub(&ct1i, &ct1i_hat); + assert_eq!((ct1i_minus_ct1i_hat.len() as u64) - 1, 2 * (N - 1)); + let mut ct1i_minus_ct1i_hat_mod_zqi = ct1i_minus_ct1i_hat.clone(); + reduce_and_center_coefficients_mut(&mut ct1i_minus_ct1i_hat_mod_zqi, &qi_bigint); + + // Compute p2i as the quotient of numerator divided by the cyclotomic polynomial, + // and reduce/center the resulting coefficients to produce: + // (ct1i - ct1i_hat) / (x^N + 1) mod Z_qi. Remainder should be empty. + let (p2i, p2i_rem) = poly_div(&ct1i_minus_ct1i_hat_mod_zqi, &cyclo.clone()); + assert!(p2i_rem.is_empty()); + assert_eq!((p2i.len() as u64) - 1, N - 2); // Order(p2i) = N - 2 + + // Assert that (ct1i - ct1i_hat) = (p2i * cyclo) mod Z_qi + let p2i_times_cyclo: Vec = poly_mul(&p2i, &cyclo); + let mut p2i_times_cyclo_mod_zqi = p2i_times_cyclo.clone(); + reduce_and_center_coefficients_mut(&mut p2i_times_cyclo_mod_zqi, &qi_bigint); + assert_eq!(&ct1i_minus_ct1i_hat_mod_zqi, &p2i_times_cyclo_mod_zqi); + assert_eq!((p2i_times_cyclo.len() as u64) - 1, 2 * (N - 1)); + + // Calculate p1i = (ct1i - ct1i_hat - p2i * cyclo) / qi mod Z_p. Remainder should be empty. + let p1i_num = poly_sub(&ct1i_minus_ct1i_hat, &p2i_times_cyclo); + assert_eq!((p1i_num.len() as u64) - 1, 2 * (N - 1)); + + let (p1i, p1i_rem) = poly_div(&p1i_num, &[BigInt::from(qi.modulus())]); + assert!(p1i_rem.is_empty()); + assert_eq!((p1i.len() as u64) - 1, 2 * (N - 1)); // Order(p1i) = 2*(N-1) + assert_eq!(&p1i_num, &poly_mul(&p1i, &[qi_bigint.clone()])); + + // Assert that ct1i = ct1i_hat + p1i * qi + p2i * cyclo mod Z_p + let p1i_times_qi = poly_scalar_mul(&p1i, &qi_bigint); + let mut ct1i_calculated = + poly_add(&poly_add(&ct1i_hat, &p1i_times_qi), &p2i_times_cyclo); + + while ct1i_calculated.len() > 0 && ct1i_calculated[0].is_zero() { + ct1i_calculated.remove(0); + } + + assert_eq!(&ct1i, &ct1i_calculated); + + /* + println!("qi = {:?}\n", &qi_bigint); + println!("ct0i = {:?}\n", &ct0i); + println!("k0qi = {:?}\n", &k0qi); + println!("pk0 = Polynomial({:?})\n", &pk0i); + println!("pk1 = Polynomial({:?})\n", &pk1i); + println!("ki = {:?}\n", &ki); + println!("ct0i_hat_mod_rqi = {:?}\n", &ct0i_hat_mod_rqi); + */ + + (i, r2i, r1i, k0qi, ct0i, ct1i, pk0i, pk1i, p1i, p2i) + }, + ) + .collect(); + + // println!("Completed creation of polynomials!"); + + // Merge results into the `res` structure after parallel execution + for (i, r2i, r1i, k0i, ct0i, ct1i, pk0i, pk1i, p1i, p2i) in results.into_iter() { + res.r2is[i] = r2i; + res.r1is[i] = r1i; + res.k0is[i] = k0i; + res.ct0is[i] = ct0i; + res.ct1is[i] = ct1i; + res.pk0is[i] = pk0i; + res.pk1is[i] = pk1i; + res.p1is[i] = p1i; + res.p2is[i] = p2i; + } + + // Set final result vectors + res.u = u; + res.e0 = e0; + res.e1 = e1; + res.k1 = k1; + + res + } } -fn to_string_2d_vec(poly: &Vec>) -> Vec> { - poly.iter().map(|row| to_string_1d_vec(row)).collect() +/// The `InputValidationBounds` struct holds the bounds for various vectors and polynomials used in the input validation process. +/// These bounds are calculated from a set of BFV encryption parameters and represent limits on the values of different fields +/// to ensure that the inputs remain within valid ranges during operations. +#[derive(Clone, Debug)] +pub struct InputValidationBounds { + u: BigInt, + e: BigInt, + t: BigInt, + k1: BigInt, + pk: Vec, + r1: Vec, + r2: Vec, + p1: Vec, + p2: Vec, } -/// Create the centered validation vectors necessary for creating an input validation proof according to Greco. -/// For more information, please see https://eprint.iacr.org/2024/594. -/// -/// # Arguments -/// -/// * `pt` - Plaintext from fhe.rs. -/// * `u_rns` - Private polynomial used in ciphertext sampled from secret key distribution. -/// * `e0_rns` - Error polynomial used in ciphertext sampled from error distribution. -/// * `e1_rns` - Error polynomioal used in cihpertext sampled from error distribution. -/// * `ct` - Ciphertext from fhe.rs. -/// * `pk` - Public Key from fhe.re. -/// -pub fn compute_input_validation_vectors( - pt: &Plaintext, - u_rns: &Poly, - e0_rns: &Poly, - e1_rns: &Poly, - ct: &Ciphertext, - pk: &PublicKey, -) -> InputValidationVectors { - // Get context, plaintext modulus, and degree - let params = &pk.par; - let ctx = params.ctx_at_level(pt.level()).unwrap(); - let t = Modulus::new(params.plaintext()).unwrap(); - let N: u64 = ctx.degree as u64; - - // Calculate k1 (independent of qi), center and reverse - let q_mod_t = (ctx.modulus() % t.modulus()).to_u64().unwrap(); // [q]_t - let mut k1_u64 = pt.value.deref().to_vec(); // m - t.scalar_mul_vec(&mut k1_u64, q_mod_t); // k1 = [q*m]_t - let mut k1: Vec = k1_u64.iter().map(|&x| BigInt::from(x)).rev().collect(); - reduce_and_center_coefficients_mut(&mut k1, &BigInt::from(t.modulus())); - - // Extract single vectors of u, e1, and e2 as Vec, center and reverse - let mut u_rns_copy = u_rns.clone(); - let mut e0_rns_copy = e0_rns.clone(); - let mut e1_rns_copy = e1_rns.clone(); - - u_rns_copy.change_representation(Representation::PowerBasis); - e0_rns_copy.change_representation(Representation::PowerBasis); - e1_rns_copy.change_representation(Representation::PowerBasis); - - let u: Vec = unsafe { - ctx.moduli_operators()[0] - .center_vec_vt(u_rns_copy.coefficients().row(0).as_slice().unwrap()) - .iter() - .rev() - .map(|&x| BigInt::from(x)) - .collect() - }; - - let e0: Vec = unsafe { - ctx.moduli_operators()[0] - .center_vec_vt(e0_rns_copy.coefficients().row(0).as_slice().unwrap()) - .iter() - .rev() - .map(|&x| BigInt::from(x)) - .collect() - }; - - let e1: Vec = unsafe { - ctx.moduli_operators()[0] - .center_vec_vt(e1_rns_copy.coefficients().row(0).as_slice().unwrap()) - .iter() - .rev() - .map(|&x| BigInt::from(x)) - .collect() - }; - - // Extract and convert ciphertext and plaintext polynomials - let mut ct0 = ct.c[0].clone(); - let mut ct1 = ct.c[1].clone(); - ct0.change_representation(Representation::PowerBasis); - ct1.change_representation(Representation::PowerBasis); - - let mut pk0: Poly = pk.c.c[0].clone(); - let mut pk1: Poly = pk.c.c[1].clone(); - pk0.change_representation(Representation::PowerBasis); - pk1.change_representation(Representation::PowerBasis); - - // Create cyclotomic polynomial x^N + 1 - let mut cyclo = vec![BigInt::from(0u64); (N + 1) as usize]; - cyclo[0] = BigInt::from(1u64); // x^N term - cyclo[N as usize] = BigInt::from(1u64); // x^0 term - - // Print - /* - println!("m = {:?}\n", &m); - println!("k1 = {:?}\n", &k1); - println!("u = {:?}\n", &u); - println!("e0 = {:?}\n", &e0); - println!("e1 = {:?}\n", &e1); - */ - - // Initialize matrices to store results - let num_moduli = ctx.moduli().len(); - let mut res = InputValidationVectors::new(num_moduli, N as usize); - - // Perform the main computation logic - let results: Vec<( - usize, - Vec, - Vec, - BigInt, - Vec, - Vec, - Vec, - Vec, - Vec, - Vec, - )> = izip!( - ctx.moduli_operators(), - ct0.coefficients().rows(), - ct1.coefficients().rows(), - pk0.coefficients().rows(), - pk1.coefficients().rows() - ) - .enumerate() - .par_bridge() - .map( - |(i, (qi, ct0_coeffs, ct1_coeffs, pk0_coeffs, pk1_coeffs))| { - // --------------------------------------------------- ct0i --------------------------------------------------- - - // Convert to vectors of bigint, center, and reverse order. - let mut ct0i: Vec = ct0_coeffs.iter().rev().map(|&x| BigInt::from(x)).collect(); - let mut ct1i: Vec = ct1_coeffs.iter().rev().map(|&x| BigInt::from(x)).collect(); - let mut pk0i: Vec = pk0_coeffs.iter().rev().map(|&x| BigInt::from(x)).collect(); - let mut pk1i: Vec = pk1_coeffs.iter().rev().map(|&x| BigInt::from(x)).collect(); +impl InputValidationBounds { + /// Checks the constraints of the input validation vectors against the bounds stored in `InputValidationBounds`. + /// + /// # Arguments + /// + /// * `vecs` - A reference to `InputValidationVectors`, which contains the vectors to be validated. + /// * `p` - The prime modulus used in the encryption scheme. + /// + /// This function checks whether the coefficients of the vectors `u`, `e0`, `e1`, `k1`, and others are within + /// the specified ranges, using both centered and standard range checks. It asserts that the vectors stay within + /// these predefined bounds. + pub fn check_constraints(&self, vecs: &InputValidationVectors, p: &BigInt) { + let vecs_std = vecs.standard_form(p); + + // constraint. The coefficients of u, e0, e1 should be in the range [-⌈6σ⌋, ⌈6σ⌋] + // where ⌈6σ⌋ is the upper bound of the discrete Gaussian distribution + assert!(range_check_centered(&vecs.u, &-&self.u, &self.u)); + assert!(range_check_centered(&vecs.e0, &-&self.e, &self.e)); + assert!(range_check_centered(&vecs.e1, &-&self.e, &self.e)); + assert!(range_check_standard(&vecs_std.u, &self.u, &p)); + assert!(range_check_standard(&vecs_std.e0, &self.e, &p)); + assert!(range_check_standard(&vecs_std.e1, &self.e, &p)); + + // constraint. The coefficients of k1 should be in the range [-(t-1)/2, (t-1)/2] + assert!(range_check_centered(&vecs.k1, &-&self.k1, &self.k1)); + assert!(range_check_standard(&vecs_std.k1, &self.k1, &p)); + + // Perform asserts for polynomials depending on each qi + for i in 0..self.r2.len() { + // constraint. The coefficients of ct0i and ct1i should be in the range [-(qi-1)/2, (qi-1)/2] + assert!(range_check_centered( + &vecs.ct0is[i], + &-&self.pk[i], + &self.pk[i] + )); + assert!(range_check_centered( + &vecs.ct1is[i], + &-&self.pk[i], + &self.pk[i] + )); + + // constraint. The coefficients of pk0i and pk1i should be in range [-(qi-1)/2 , (qi-1)/2] + assert!(range_check_centered( + &vecs.pk0is[i], + &-&self.pk[i], + &self.pk[i] + )); + assert!(range_check_centered( + &vecs.pk1is[i], + &-&self.pk[i], + &self.pk[i] + )); + assert!(range_check_standard(&vecs_std.pk0is[i], &self.pk[i], &p)); + assert!(range_check_standard(&vecs_std.pk1is[i], &self.pk[i], &p)); + + // constraint. The coefficients of r2i should be in the range [-(qi-1)/2, (qi-1)/2] + assert!(range_check_centered( + &vecs.r2is[i], + &-&self.r2[i], + &self.r2[i] + )); + assert!(range_check_standard(&vecs_std.r2is[i], &self.r2[i], &p)); + + // constraint. The coefficients of (ct0i - ct0i_hat - r2i * cyclo) / qi = r1i should be in the range + // $[ + // \frac{- ((N+2) \cdot \frac{q_i - 1}{2} + B + \frac{t - 1}{2} \cdot |K_{0,i}|)}{q_i}, + // \frac{ (N+2) \cdot \frac{q_i - 1}{2} + B + \frac{t - 1}{2} \cdot |K_{0,i}| }{q_i} + // ]$ + assert!(range_check_centered( + &vecs.r1is[i], + &-&self.r1[i], + &self.r1[i] + )); + assert!(range_check_standard(&vecs_std.r1is[i], &self.r1[i], &p)); + + // constraint. The coefficients of p2 should be in the range [-(qi-1)/2, (qi-1)/2] + assert!(range_check_centered( + &vecs.p2is[i], + &-&self.p2[i], + &self.p2[i] + )); + assert!(range_check_standard(&vecs_std.p2is[i], &self.p2[i], &p)); + + // constraint. The coefficients of (ct0i - ct0i_hat - p2i * cyclo) / qi = p1i should be in the range + // $[ + // \frac{- ((N+2) \cdot \frac{q_i - 1}{2} + B)}{q_i}, + // \frac{ (N+2) \cdot \frac{q_i - 1}{2} + B }{q_i} + // ]$ + assert!(range_check_centered( + &vecs.p1is[i], + &-&self.p1[i], + &self.p1[i] + )); + assert!(range_check_standard(&vecs_std.p1is[i], &self.p1[i], &p)); + } + } + /// Compute the input validation bounds from a set of BFV encryption parameters. + /// + /// # Arguments + /// + /// * `params` - A reference to the BFV parameters. + /// * `level` - The encryption level, which determines the number of moduli used. + /// + /// # Returns + /// + /// A new `InputValidationBounds` instance containing the bounds for vectors and polynomials + /// based on the BFV parameters and the specified level. + pub fn compute(params: &Arc, level: usize) -> InputValidationBounds { + // Get cyclotomic degree and context at provided level + let N = BigInt::from(params.degree()); + let t = BigInt::from(params.plaintext()); + let ctx = params.ctx_at_level(level).unwrap(); + + // Note: the secret key in fhe.rs is sampled from a discrete gaussian distribution + // rather than a ternary distribution as in bfv.py. + let gauss_bound = BigInt::from( + f64::ceil(6_f64 * f64::sqrt(params.variance() as f64)) + .to_i64() + .unwrap(), + ); + let u_bound = gauss_bound.clone(); + let e_bound = gauss_bound.clone(); + let ptxt_bound = (t - BigInt::from(1)) / BigInt::from(2); + let k1_bound = ptxt_bound.clone(); + + // Calculate qi-based bounds + let num_moduli = ctx.moduli().len(); + let mut pk_bounds: Vec = vec![BigInt::zero(); num_moduli]; + let mut r2_bounds: Vec = vec![BigInt::zero(); num_moduli]; + let mut r1_bounds: Vec = vec![BigInt::zero(); num_moduli]; + let mut p2_bounds: Vec = vec![BigInt::zero(); num_moduli]; + let mut p1_bounds: Vec = vec![BigInt::zero(); num_moduli]; + for (i, qi) in ctx.moduli_operators().iter().enumerate() { let qi_bigint = BigInt::from(qi.modulus()); + let qi_bound = (&qi_bigint - BigInt::from(1)) / BigInt::from(2); - reduce_and_center_coefficients_mut(&mut ct0i, &qi_bigint); - reduce_and_center_coefficients_mut(&mut ct1i, &qi_bigint); - reduce_and_center_coefficients_mut(&mut pk0i, &qi_bigint); - reduce_and_center_coefficients_mut(&mut pk1i, &qi_bigint); + // Calculate the k0qi for the bounds (these are also constant wrt BFV params) + let k0qi = BigInt::from(qi.inv(qi.neg(params.plaintext())).unwrap()); - // k0qi = -t^{-1} mod qi - let koqi_u64 = qi.inv(qi.neg(t.modulus())).unwrap(); - let k0qi = BigInt::from(koqi_u64); // Do not need to center this + pk_bounds[i] = qi_bound.clone(); + r2_bounds[i] = qi_bound.clone(); + r1_bounds[i] = ((&N + 2) * &qi_bound + &gauss_bound + &ptxt_bound * BigInt::abs(&k0qi)) + / &qi_bigint; + p2_bounds[i] = qi_bound.clone(); + p1_bounds[i] = ((&N + 2) * &qi_bound + &gauss_bound) / &qi_bigint; + } - // ki = k1 * k0qi - let ki = poly_scalar_mul(&k1, &k0qi); + InputValidationBounds { + u: u_bound, + e: e_bound, + t: ptxt_bound, + k1: k1_bound, + pk: pk_bounds, + r1: r1_bounds, + r2: r2_bounds, + p1: p1_bounds, + p2: p2_bounds, + } + } - // Calculate ct0i_hat = pk0 * ui + e0i + ki - let ct0i_hat = { - let pk0i_times_u = poly_mul(&pk0i, &u); - assert_eq!((pk0i_times_u.len() as u64) - 1, 2 * (N - 1)); + /// Writes the input validation bounds to a file that can be imported as a Rust module. + /// + /// # Arguments + /// + /// * `params` - Reference to BFV parameters to extract context information. + /// * `output_file` - The path where the output constants should be saved. + /// + /// This function calculates certain constants like `k0i` values for each modulus `qi` and writes the bounds and other + /// relevant constants in a Rust-friendly format to the file specified by `output_file`. + fn to_file(&self, params: &Arc, output_file: &str) { + let level = params.moduli().len() - self.r2.len(); + let ctx = params.ctx_at_level(level).unwrap(); + + // Calculate k0i constants + let k0i_constants: Vec = ctx + .moduli_operators() + .iter() + .map(|qi| BigInt::from(qi.inv(qi.neg(params.plaintext())).unwrap())) + .collect(); - let e0_plus_ki = poly_add(&e0, &ki); - assert_eq!((e0_plus_ki.len() as u64) - 1, N - 1); + // Set the output file path + let output_path = Path::new("src") + .join("constants") + .join("pk_enc_constants") + .join(output_file); - poly_add(&pk0i_times_u, &e0_plus_ki) - }; - assert_eq!((ct0i_hat.len() as u64) - 1, 2 * (N - 1)); - - // Check whether ct0i_hat mod R_qi (the ring) is equal to ct0i - let mut ct0i_hat_mod_rqi = ct0i_hat.clone(); - reduce_in_ring(&mut ct0i_hat_mod_rqi, &cyclo, &qi_bigint); - assert_eq!(&ct0i, &ct0i_hat_mod_rqi); - - // Compute r2i numerator = ct0i - ct0i_hat and reduce/center the polynomial - let ct0i_minus_ct0i_hat = poly_sub(&ct0i, &ct0i_hat); - assert_eq!((ct0i_minus_ct0i_hat.len() as u64) - 1, 2 * (N - 1)); - let mut ct0i_minus_ct0i_hat_mod_zqi = ct0i_minus_ct0i_hat.clone(); - reduce_and_center_coefficients_mut(&mut ct0i_minus_ct0i_hat_mod_zqi, &qi_bigint); - - // Compute r2i as the quotient of numerator divided by the cyclotomic polynomial - // to produce: (ct0i - ct0i_hat) / (x^N + 1) mod Z_qi. Remainder should be empty. - let (r2i, r2i_rem) = poly_div(&ct0i_minus_ct0i_hat_mod_zqi, &cyclo); - assert!(r2i_rem.is_empty()); - assert_eq!((r2i.len() as u64) - 1, N - 2); // Order(r2i) = N - 2 - - // Assert that (ct0i - ct0i_hat) = (r2i * cyclo) mod Z_qi - let r2i_times_cyclo = poly_mul(&r2i, &cyclo); - let mut r2i_times_cyclo_mod_zqi = r2i_times_cyclo.clone(); - reduce_and_center_coefficients_mut(&mut r2i_times_cyclo_mod_zqi, &qi_bigint); - assert_eq!(&ct0i_minus_ct0i_hat_mod_zqi, &r2i_times_cyclo_mod_zqi); - assert_eq!((r2i_times_cyclo.len() as u64) - 1, 2 * (N - 1)); - - // Calculate r1i = (ct0i - ct0i_hat - r2i * cyclo) / qi mod Z_p. Remainder should be empty. - let r1i_num = poly_sub(&ct0i_minus_ct0i_hat, &r2i_times_cyclo); - assert_eq!((r1i_num.len() as u64) - 1, 2 * (N - 1)); - - let (r1i, r1i_rem) = poly_div(&r1i_num, &[qi_bigint.clone()]); - assert!(r1i_rem.is_empty()); - assert_eq!((r1i.len() as u64) - 1, 2 * (N - 1)); // Order(r1i) = 2*(N-1) - assert_eq!(&r1i_num, &poly_mul(&r1i, &[qi_bigint.clone()])); - - // Assert that ct0i = ct0i_hat + r1i * qi + r2i * cyclo mod Z_p - let r1i_times_qi = poly_scalar_mul(&r1i, &qi_bigint); - let mut ct0i_calculated = - poly_add(&poly_add(&ct0i_hat, &r1i_times_qi), &r2i_times_cyclo); - - while ct0i_calculated.len() > 0 && ct0i_calculated[0].is_zero() { - ct0i_calculated.remove(0); - } - - assert_eq!(&ct0i, &ct0i_calculated); - - // --------------------------------------------------- ct1i --------------------------------------------------- - - // Calculate ct1i_hat = pk1i * ui + e1i - let ct1i_hat = { - let pk1i_times_u = poly_mul(&pk1i, &u); - assert_eq!((pk1i_times_u.len() as u64) - 1, 2 * (N - 1)); - - poly_add(&pk1i_times_u, &e1) - }; - assert_eq!((ct1i_hat.len() as u64) - 1, 2 * (N - 1)); - - // Check whether ct1i_hat mod R_qi (the ring) is equal to ct1i - let mut ct1i_hat_mod_rqi = ct1i_hat.clone(); - reduce_in_ring(&mut ct1i_hat_mod_rqi, &cyclo, &qi_bigint); - assert_eq!(&ct1i, &ct1i_hat_mod_rqi); - - // Compute p2i numerator = ct1i - ct1i_hat - let ct1i_minus_ct1i_hat = poly_sub(&ct1i, &ct1i_hat); - assert_eq!((ct1i_minus_ct1i_hat.len() as u64) - 1, 2 * (N - 1)); - let mut ct1i_minus_ct1i_hat_mod_zqi = ct1i_minus_ct1i_hat.clone(); - reduce_and_center_coefficients_mut(&mut ct1i_minus_ct1i_hat_mod_zqi, &qi_bigint); - - // Compute p2i as the quotient of numerator divided by the cyclotomic polynomial, - // and reduce/center the resulting coefficients to produce: - // (ct1i - ct1i_hat) / (x^N + 1) mod Z_qi. Remainder should be empty. - let (p2i, p2i_rem) = poly_div(&ct1i_minus_ct1i_hat_mod_zqi, &cyclo.clone()); - assert!(p2i_rem.is_empty()); - assert_eq!((p2i.len() as u64) - 1, N - 2); // Order(p2i) = N - 2 - - // Assert that (ct1i - ct1i_hat) = (p2i * cyclo) mod Z_qi - let p2i_times_cyclo: Vec = poly_mul(&p2i, &cyclo); - let mut p2i_times_cyclo_mod_zqi = p2i_times_cyclo.clone(); - reduce_and_center_coefficients_mut(&mut p2i_times_cyclo_mod_zqi, &qi_bigint); - assert_eq!(&ct1i_minus_ct1i_hat_mod_zqi, &p2i_times_cyclo_mod_zqi); - assert_eq!((p2i_times_cyclo.len() as u64) - 1, 2 * (N - 1)); - - // Calculate p1i = (ct1i - ct1i_hat - p2i * cyclo) / qi mod Z_p. Remainder should be empty. - let p1i_num = poly_sub(&ct1i_minus_ct1i_hat, &p2i_times_cyclo); - assert_eq!((p1i_num.len() as u64) - 1, 2 * (N - 1)); - - let (p1i, p1i_rem) = poly_div(&p1i_num, &[BigInt::from(qi.modulus())]); - assert!(p1i_rem.is_empty()); - assert_eq!((p1i.len() as u64) - 1, 2 * (N - 1)); // Order(p1i) = 2*(N-1) - assert_eq!(&p1i_num, &poly_mul(&p1i, &[qi_bigint.clone()])); - - // Assert that ct1i = ct1i_hat + p1i * qi + p2i * cyclo mod Z_p - let p1i_times_qi = poly_scalar_mul(&p1i, &qi_bigint); - let mut ct1i_calculated = - poly_add(&poly_add(&ct1i_hat, &p1i_times_qi), &p2i_times_cyclo); - - while ct1i_calculated.len() > 0 && ct1i_calculated[0].is_zero() { - ct1i_calculated.remove(0); - } - - assert_eq!(&ct1i, &ct1i_calculated); - - /* - println!("qi = {:?}\n", &qi_bigint); - println!("ct0i = {:?}\n", &ct0i); - println!("k0qi = {:?}\n", &k0qi); - println!("pk0 = Polynomial({:?})\n", &pk0i); - println!("pk1 = Polynomial({:?})\n", &pk1i); - println!("ki = {:?}\n", &ki); - println!("ct0i_hat_mod_rqi = {:?}\n", &ct0i_hat_mod_rqi); - */ - - (i, r2i, r1i, k0qi, ct0i, ct1i, pk0i, pk1i, p1i, p2i) - }, - ) - .collect(); - - // println!("Completed creation of polynomials!"); - - // Merge results into the `res` structure after parallel execution - for (i, r2i, r1i, k0i, ct0i, ct1i, pk0i, pk1i, p1i, p2i) in results.into_iter() { - res.r2is[i] = r2i; - res.r1is[i] = r1i; - res.k0is[i] = k0i; - res.ct0is[i] = ct0i; - res.ct1is[i] = ct1i; - res.pk0is[i] = pk0i; - res.pk1is[i] = pk1i; - res.p1is[i] = p1i; - res.p2is[i] = p2i; + let mut file = File::create(output_path).expect("Unable to create file"); + + // Writing the constants to the file + writeln!(file, "/// `N` is the degree of the cyclotomic polynomial defining the ring `Rq = Zq[X]/(X^N + 1)`.") + .expect("Unable to write to file"); + writeln!(file, "pub const N: usize = {};", params.degree()) + .expect("Unable to write to file"); + + let pk_bound_str = self + .pk + .iter() + .map(|x| x.to_string()) + .collect::>() + .join(", "); + writeln!(file, "/// The coefficients of the polynomial `pk0is` and `pk1is` should exist in the interval `[-PK_BOUND, PK_BOUND]`.") + .expect("Unable to write to file"); + writeln!( + file, + "pub const PK_BOUND: [u64; {}] = [{}];", + self.pk.len(), + pk_bound_str + ) + .expect("Unable to write to file"); + + writeln!(file, "/// The coefficients of the polynomial `pk1is` should exist in the interval `[-PK0_BOUND, PK0_BOUND]`.") + .expect("Unable to write to file"); + + writeln!(file, "/// The coefficients of the polynomial `e` should exist in the interval `[-E_BOUND, E_BOUND]` where `E_BOUND` is the upper bound of the gaussian distribution with 𝜎 = 3.2.") + .expect("Unable to write to file"); + writeln!(file, "pub const E_BOUND: u64 = {};", self.e).expect("Unable to write to file"); + + writeln!(file, "/// The coefficients of the polynomial `s` should exist in the interval `[-S_BOUND, S_BOUND]`.") + .expect("Unable to write to file"); + writeln!(file, "pub const U_BOUND: u64 = {};", self.u).expect("Unable to write to file"); + + let r1_bounds_str = self + .r1 + .iter() + .map(|x| x.to_string()) + .collect::>() + .join(", "); + writeln!(file, "/// The coefficients of the polynomials `r1is` should exist in the interval `[-R1_BOUND[i], R1_BOUND[i]]` where `R1_BOUND[i]` is equal to `(qi-1)/2`.") + .expect("Unable to write to file"); + writeln!( + file, + "pub const R1_BOUNDS: [u64; {}] = [{}];", + self.r1.len(), + r1_bounds_str + ) + .expect("Unable to write to file"); + + let r2_bounds_str = self + .r2 + .iter() + .map(|x| x.to_string()) + .collect::>() + .join(", "); + writeln!(file, "/// The coefficients of the polynomials `r2is` should exist in the interval `[-R2_BOUND[i], R2_BOUND[i]]` where `R2_BOUND[i]` is equal to $\\frac{{(N+2) \\cdot \\frac{{q_i - 1}}{{2}} + B + \\frac{{t - 1}}{{2}} \\cdot |K_{{0,i}}|}}{{q_i}}`.") + .expect("Unable to write to file"); + writeln!( + file, + "pub const R2_BOUNDS: [u64; {}] = [{}];", + self.r2.len(), + r2_bounds_str + ) + .expect("Unable to write to file"); + + let p1_bounds_str = self + .p1 + .iter() + .map(|x| x.to_string()) + .collect::>() + .join(", "); + writeln!(file, "/// The coefficients of the polynomials `p1is` should exist in the interval `[-P1_BOUND[i], P1_BOUND[i]]` where `P1_BOUND[i]` is equal to (((qis[i] - 1) / 2) * (n + 2) + b ) / qis[i].") + .expect("Unable to write to file"); + writeln!( + file, + "pub const P1_BOUNDS: [u64; {}] = [{}];", + self.p1.len(), + p1_bounds_str + ) + .expect("Unable to write to file"); + + let p2_bounds_str = self + .p2 + .iter() + .map(|x| x.to_string()) + .collect::>() + .join(", "); + writeln!(file, "/// The coefficients of the polynomials `p2is` should exist in the interval `[-P2_BOUND[i], P2_BOUND[i]]` where `P2_BOUND[i]` is equal to (qis[i] - 1) / 2.") + .expect("Unable to write to file"); + writeln!( + file, + "pub const P2_BOUNDS: [u64; {}] = [{}];", + self.p2.len(), + p2_bounds_str + ) + .expect("Unable to write to file"); + + writeln!(file, "/// The coefficients of `k1` should exist in the interval `[-K1_BOUND, K1_BOUND]` where `K1_BOUND` is equal to `(t-1)/2`.") + .expect("Unable to write to file"); + writeln!(file, "pub const K1_BOUND: u64 = {};", self.k1).expect("Unable to write to file"); + + let qis_str = ctx + .moduli() + .iter() + .map(|x| format!("\"{}\"", x)) + .collect::>() + .join(", "); + writeln!(file, "/// List of scalars `qis` such that `qis[i]` is the modulus of the i-th CRT basis of `q` (ciphertext space modulus).") + .expect("Unable to write to file"); + writeln!( + file, + "pub const QIS: [&str; {}] = [{}];", + ctx.moduli().len(), + qis_str + ) + .expect("Unable to write to file"); + + let k0is_str = k0i_constants + .iter() + .map(|x| format!("\"{}\"", x)) + .collect::>() + .join(", "); + writeln!(file, "/// List of scalars `k0is` such that `k0i[i]` is equal to the negative of the multiplicative inverses of t mod qi.") + .expect("Unable to write to file"); + writeln!( + file, + "pub const K0IS: [&str; {}] = [{}];", + k0i_constants.len(), + k0is_str + ) + .expect("Unable to write to file"); } +} + +fn to_string_1d_vec(poly: &Vec) -> Vec { + poly.iter().map(|coef| coef.to_string()).collect() +} - // Set final result vectors - res.u = u; - res.e0 = e0; - res.e1 = e1; - res.k1 = k1; +fn to_string_2d_vec(poly: &Vec>) -> Vec> { + poly.iter().map(|row| to_string_1d_vec(row)).collect() +} - res +/// Writes the given JSON data to a file in the specified output path. +/// +/// # Arguments +/// +/// * `output_path` - A reference to the base directory path where the file will be created. +/// * `filename` - The name of the file to create. +/// * `json_data` - A reference to the JSON data to be written into the file. +/// +/// # Panics +/// +/// This function will panic if the file cannot be created or if writing to the file fails. +fn write_json_to_file(output_path: &Path, filename: &str, json_data: &serde_json::Value) { + let file_path = output_path.join(filename); + let mut file = File::create(file_path).expect("Unable to create file"); + file.write_all(serde_json::to_string_pretty(json_data).unwrap().as_bytes()) + .expect("Unable to write data"); } diff --git a/packages/web-rust/src/bin/web_fhe_encrypt.rs b/packages/web-rust/src/bin/web_fhe_encrypt.rs index 1d7e193..29f5e36 100644 --- a/packages/web-rust/src/bin/web_fhe_encrypt.rs +++ b/packages/web-rust/src/bin/web_fhe_encrypt.rs @@ -1,7 +1,6 @@ mod greco; mod util; -use fhe_math::zq::Modulus; use greco::greco::*; use wasm_bindgen::prelude::*; use wasm_bindgen_test::*; @@ -10,9 +9,7 @@ use serde::Deserialize; use std::{env, sync::Arc, thread, time}; use fhe::{ - bfv::{ - BfvParameters, BfvParametersBuilder, Ciphertext, Encoding, Plaintext, PublicKey, SecretKey, - }, + bfv::{BfvParametersBuilder, Encoding, Plaintext, PublicKey, SecretKey}, mbfv::{AggregateIter, CommonRandomPoly, DecryptionShare, PublicKeyShare}, proto::bfv::Parameters, }; @@ -59,7 +56,7 @@ impl Encrypt { // Create Greco input validation ZKP proof let input_val_vectors = - compute_input_validation_vectors(&pt, &u_rns, &e0_rns, &e1_rns, &ct, &pk); + InputValidationVectors::compute(&pt, &u_rns, &e0_rns, &e1_rns, &ct, &pk); self.encrypted_vote = ct.to_bytes(); Ok(self.encrypted_vote.clone()) From fe41b71bf3744be0118b7018e7e09781534abfa5 Mon Sep 17 00:00:00 2001 From: fhedude Date: Thu, 19 Sep 2024 16:56:41 -0400 Subject: [PATCH 36/62] Updated greco code to newer version, added test --- packages/web-rust/Cargo.toml | 2 + packages/web-rust/src/bin/greco/greco.rs | 144 +++++++++++-------- packages/web-rust/src/bin/web_fhe_encrypt.rs | 45 +++++- 3 files changed, 121 insertions(+), 70 deletions(-) diff --git a/packages/web-rust/Cargo.toml b/packages/web-rust/Cargo.toml index c06aa34..48aca5c 100644 --- a/packages/web-rust/Cargo.toml +++ b/packages/web-rust/Cargo.toml @@ -27,6 +27,8 @@ serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0.114" wasm-bindgen = "0.2.93" wasm-bindgen-test = "0.3.43" +console_log = "0.2" +log = "0.4" [lib] crate-type = ["cdylib", "rlib"] diff --git a/packages/web-rust/src/bin/greco/greco.rs b/packages/web-rust/src/bin/greco/greco.rs index 838e097..a73d406 100644 --- a/packages/web-rust/src/bin/greco/greco.rs +++ b/packages/web-rust/src/bin/greco/greco.rs @@ -171,15 +171,17 @@ impl InputValidationVectors { e1_rns: &Poly, ct: &Ciphertext, pk: &PublicKey, - ) -> InputValidationVectors { + ) -> Result> { // Get context, plaintext modulus, and degree let params = &pk.par; - let ctx = params.ctx_at_level(pt.level()).unwrap(); - let t = Modulus::new(params.plaintext()).unwrap(); + let ctx = params.ctx_at_level(pt.level())?; + let t = Modulus::new(params.plaintext())?; let N: u64 = ctx.degree as u64; // Calculate k1 (independent of qi), center and reverse - let q_mod_t = (ctx.modulus() % t.modulus()).to_u64().unwrap(); // [q]_t + let q_mod_t = (ctx.modulus() % t.modulus()) + .to_u64() + .ok_or_else(|| "Cannot convert BigInt to u64.".to_string())?; // [q]_t let mut k1_u64 = pt.value.deref().to_vec(); // m t.scalar_mul_vec(&mut k1_u64, q_mod_t); // k1 = [q*m]_t let mut k1: Vec = k1_u64.iter().map(|&x| BigInt::from(x)).rev().collect(); @@ -196,7 +198,13 @@ impl InputValidationVectors { let u: Vec = unsafe { ctx.moduli_operators()[0] - .center_vec_vt(u_rns_copy.coefficients().row(0).as_slice().unwrap()) + .center_vec_vt( + u_rns_copy + .coefficients() + .row(0) + .as_slice() + .ok_or_else(|| "Cannot center coefficients.".to_string())?, + ) .iter() .rev() .map(|&x| BigInt::from(x)) @@ -205,7 +213,13 @@ impl InputValidationVectors { let e0: Vec = unsafe { ctx.moduli_operators()[0] - .center_vec_vt(e0_rns_copy.coefficients().row(0).as_slice().unwrap()) + .center_vec_vt( + e0_rns_copy + .coefficients() + .row(0) + .as_slice() + .ok_or_else(|| "Cannot center coefficients.".to_string())?, + ) .iter() .rev() .map(|&x| BigInt::from(x)) @@ -214,7 +228,13 @@ impl InputValidationVectors { let e1: Vec = unsafe { ctx.moduli_operators()[0] - .center_vec_vt(e1_rns_copy.coefficients().row(0).as_slice().unwrap()) + .center_vec_vt( + e1_rns_copy + .coefficients() + .row(0) + .as_slice() + .ok_or_else(|| "Cannot center coefficients.".to_string())?, + ) .iter() .rev() .map(|&x| BigInt::from(x)) @@ -447,7 +467,7 @@ impl InputValidationVectors { res.e1 = e1; res.k1 = k1; - res + Ok(res) } } @@ -575,18 +595,21 @@ impl InputValidationBounds { /// /// A new `InputValidationBounds` instance containing the bounds for vectors and polynomials /// based on the BFV parameters and the specified level. - pub fn compute(params: &Arc, level: usize) -> InputValidationBounds { + pub fn compute( + params: &Arc, + level: usize, + ) -> Result> { // Get cyclotomic degree and context at provided level let N = BigInt::from(params.degree()); let t = BigInt::from(params.plaintext()); - let ctx = params.ctx_at_level(level).unwrap(); + let ctx = params.ctx_at_level(level)?; // Note: the secret key in fhe.rs is sampled from a discrete gaussian distribution // rather than a ternary distribution as in bfv.py. let gauss_bound = BigInt::from( f64::ceil(6_f64 * f64::sqrt(params.variance() as f64)) .to_i64() - .unwrap(), + .ok_or_else(|| "Failed to convert variance to i64".to_string())?, ); let u_bound = gauss_bound.clone(); let e_bound = gauss_bound.clone(); @@ -605,7 +628,10 @@ impl InputValidationBounds { let qi_bound = (&qi_bigint - BigInt::from(1)) / BigInt::from(2); // Calculate the k0qi for the bounds (these are also constant wrt BFV params) - let k0qi = BigInt::from(qi.inv(qi.neg(params.plaintext())).unwrap()); + let k0qi = BigInt::from( + qi.inv(qi.neg(params.plaintext())) + .ok_or_else(|| "Failed to calculate modulus inverse for k0qi".to_string())?, + ); pk_bounds[i] = qi_bound.clone(); r2_bounds[i] = qi_bound.clone(); @@ -615,7 +641,7 @@ impl InputValidationBounds { p1_bounds[i] = ((&N + 2) * &qi_bound + &gauss_bound) / &qi_bigint; } - InputValidationBounds { + Ok(InputValidationBounds { u: u_bound, e: e_bound, t: ptxt_bound, @@ -625,7 +651,7 @@ impl InputValidationBounds { r2: r2_bounds, p1: p1_bounds, p2: p2_bounds, - } + }) } /// Writes the input validation bounds to a file that can be imported as a Rust module. @@ -637,16 +663,26 @@ impl InputValidationBounds { /// /// This function calculates certain constants like `k0i` values for each modulus `qi` and writes the bounds and other /// relevant constants in a Rust-friendly format to the file specified by `output_file`. - fn to_file(&self, params: &Arc, output_file: &str) { + fn to_file( + &self, + params: &Arc, + output_file: &str, + ) -> Result<(), Box> { let level = params.moduli().len() - self.r2.len(); - let ctx = params.ctx_at_level(level).unwrap(); + let ctx = params.ctx_at_level(level)?; // Calculate k0i constants - let k0i_constants: Vec = ctx + let k0i_constants = ctx .moduli_operators() .iter() - .map(|qi| BigInt::from(qi.inv(qi.neg(params.plaintext())).unwrap())) - .collect(); + .map(|qi| { + // Use the ? operator to propagate errors + let k0qi_value = qi + .inv(qi.neg(params.plaintext())) + .ok_or_else(|| "Failed to calculate modulus inverse for k0qi".to_string())?; + Ok(BigInt::from(k0qi_value)) + }) + .collect::, String>>()?; // Set the output file path let output_path = Path::new("src") @@ -654,13 +690,11 @@ impl InputValidationBounds { .join("pk_enc_constants") .join(output_file); - let mut file = File::create(output_path).expect("Unable to create file"); + let mut file = File::create(output_path)?; // Writing the constants to the file - writeln!(file, "/// `N` is the degree of the cyclotomic polynomial defining the ring `Rq = Zq[X]/(X^N + 1)`.") - .expect("Unable to write to file"); - writeln!(file, "pub const N: usize = {};", params.degree()) - .expect("Unable to write to file"); + writeln!(file, "/// `N` is the degree of the cyclotomic polynomial defining the ring `Rq = Zq[X]/(X^N + 1)`.")?; + writeln!(file, "pub const N: usize = {};", params.degree())?; let pk_bound_str = self .pk @@ -668,26 +702,21 @@ impl InputValidationBounds { .map(|x| x.to_string()) .collect::>() .join(", "); - writeln!(file, "/// The coefficients of the polynomial `pk0is` and `pk1is` should exist in the interval `[-PK_BOUND, PK_BOUND]`.") - .expect("Unable to write to file"); + writeln!(file, "/// The coefficients of the polynomial `pk0is` and `pk1is` should exist in the interval `[-PK_BOUND, PK_BOUND]`.")?; writeln!( file, "pub const PK_BOUND: [u64; {}] = [{}];", self.pk.len(), pk_bound_str - ) - .expect("Unable to write to file"); + )?; - writeln!(file, "/// The coefficients of the polynomial `pk1is` should exist in the interval `[-PK0_BOUND, PK0_BOUND]`.") - .expect("Unable to write to file"); + writeln!(file, "/// The coefficients of the polynomial `pk1is` should exist in the interval `[-PK0_BOUND, PK0_BOUND]`.")?; - writeln!(file, "/// The coefficients of the polynomial `e` should exist in the interval `[-E_BOUND, E_BOUND]` where `E_BOUND` is the upper bound of the gaussian distribution with 𝜎 = 3.2.") - .expect("Unable to write to file"); - writeln!(file, "pub const E_BOUND: u64 = {};", self.e).expect("Unable to write to file"); + writeln!(file, "/// The coefficients of the polynomial `e` should exist in the interval `[-E_BOUND, E_BOUND]` where `E_BOUND` is the upper bound of the gaussian distribution with 𝜎 = 3.2.")?; + writeln!(file, "pub const E_BOUND: u64 = {};", self.e)?; - writeln!(file, "/// The coefficients of the polynomial `s` should exist in the interval `[-S_BOUND, S_BOUND]`.") - .expect("Unable to write to file"); - writeln!(file, "pub const U_BOUND: u64 = {};", self.u).expect("Unable to write to file"); + writeln!(file, "/// The coefficients of the polynomial `s` should exist in the interval `[-S_BOUND, S_BOUND]`.")?; + writeln!(file, "pub const U_BOUND: u64 = {};", self.u)?; let r1_bounds_str = self .r1 @@ -695,15 +724,13 @@ impl InputValidationBounds { .map(|x| x.to_string()) .collect::>() .join(", "); - writeln!(file, "/// The coefficients of the polynomials `r1is` should exist in the interval `[-R1_BOUND[i], R1_BOUND[i]]` where `R1_BOUND[i]` is equal to `(qi-1)/2`.") - .expect("Unable to write to file"); + writeln!(file, "/// The coefficients of the polynomials `r1is` should exist in the interval `[-R1_BOUND[i], R1_BOUND[i]]` where `R1_BOUND[i]` is equal to `(qi-1)/2`.")?; writeln!( file, "pub const R1_BOUNDS: [u64; {}] = [{}];", self.r1.len(), r1_bounds_str - ) - .expect("Unable to write to file"); + )?; let r2_bounds_str = self .r2 @@ -711,15 +738,13 @@ impl InputValidationBounds { .map(|x| x.to_string()) .collect::>() .join(", "); - writeln!(file, "/// The coefficients of the polynomials `r2is` should exist in the interval `[-R2_BOUND[i], R2_BOUND[i]]` where `R2_BOUND[i]` is equal to $\\frac{{(N+2) \\cdot \\frac{{q_i - 1}}{{2}} + B + \\frac{{t - 1}}{{2}} \\cdot |K_{{0,i}}|}}{{q_i}}`.") - .expect("Unable to write to file"); + writeln!(file, "/// The coefficients of the polynomials `r2is` should exist in the interval `[-R2_BOUND[i], R2_BOUND[i]]` where `R2_BOUND[i]` is equal to $\\frac{{(N+2) \\cdot \\frac{{q_i - 1}}{{2}} + B + \\frac{{t - 1}}{{2}} \\cdot |K_{{0,i}}|}}{{q_i}}`.")?; writeln!( file, "pub const R2_BOUNDS: [u64; {}] = [{}];", self.r2.len(), r2_bounds_str - ) - .expect("Unable to write to file"); + )?; let p1_bounds_str = self .p1 @@ -727,15 +752,13 @@ impl InputValidationBounds { .map(|x| x.to_string()) .collect::>() .join(", "); - writeln!(file, "/// The coefficients of the polynomials `p1is` should exist in the interval `[-P1_BOUND[i], P1_BOUND[i]]` where `P1_BOUND[i]` is equal to (((qis[i] - 1) / 2) * (n + 2) + b ) / qis[i].") - .expect("Unable to write to file"); + writeln!(file, "/// The coefficients of the polynomials `p1is` should exist in the interval `[-P1_BOUND[i], P1_BOUND[i]]` where `P1_BOUND[i]` is equal to (((qis[i] - 1) / 2) * (n + 2) + b ) / qis[i].")?; writeln!( file, "pub const P1_BOUNDS: [u64; {}] = [{}];", self.p1.len(), p1_bounds_str - ) - .expect("Unable to write to file"); + )?; let p2_bounds_str = self .p2 @@ -743,19 +766,16 @@ impl InputValidationBounds { .map(|x| x.to_string()) .collect::>() .join(", "); - writeln!(file, "/// The coefficients of the polynomials `p2is` should exist in the interval `[-P2_BOUND[i], P2_BOUND[i]]` where `P2_BOUND[i]` is equal to (qis[i] - 1) / 2.") - .expect("Unable to write to file"); + writeln!(file, "/// The coefficients of the polynomials `p2is` should exist in the interval `[-P2_BOUND[i], P2_BOUND[i]]` where `P2_BOUND[i]` is equal to (qis[i] - 1) / 2.")?; writeln!( file, "pub const P2_BOUNDS: [u64; {}] = [{}];", self.p2.len(), p2_bounds_str - ) - .expect("Unable to write to file"); + )?; - writeln!(file, "/// The coefficients of `k1` should exist in the interval `[-K1_BOUND, K1_BOUND]` where `K1_BOUND` is equal to `(t-1)/2`.") - .expect("Unable to write to file"); - writeln!(file, "pub const K1_BOUND: u64 = {};", self.k1).expect("Unable to write to file"); + writeln!(file, "/// The coefficients of `k1` should exist in the interval `[-K1_BOUND, K1_BOUND]` where `K1_BOUND` is equal to `(t-1)/2`.")?; + writeln!(file, "pub const K1_BOUND: u64 = {};", self.k1)?; let qis_str = ctx .moduli() @@ -763,30 +783,28 @@ impl InputValidationBounds { .map(|x| format!("\"{}\"", x)) .collect::>() .join(", "); - writeln!(file, "/// List of scalars `qis` such that `qis[i]` is the modulus of the i-th CRT basis of `q` (ciphertext space modulus).") - .expect("Unable to write to file"); + writeln!(file, "/// List of scalars `qis` such that `qis[i]` is the modulus of the i-th CRT basis of `q` (ciphertext space modulus).")?; writeln!( file, "pub const QIS: [&str; {}] = [{}];", ctx.moduli().len(), qis_str - ) - .expect("Unable to write to file"); + )?; let k0is_str = k0i_constants .iter() .map(|x| format!("\"{}\"", x)) .collect::>() .join(", "); - writeln!(file, "/// List of scalars `k0is` such that `k0i[i]` is equal to the negative of the multiplicative inverses of t mod qi.") - .expect("Unable to write to file"); + writeln!(file, "/// List of scalars `k0is` such that `k0i[i]` is equal to the negative of the multiplicative inverses of t mod qi.")?; writeln!( file, "pub const K0IS: [&str; {}] = [{}];", k0i_constants.len(), k0is_str - ) - .expect("Unable to write to file"); + )?; + + Ok(()) } } diff --git a/packages/web-rust/src/bin/web_fhe_encrypt.rs b/packages/web-rust/src/bin/web_fhe_encrypt.rs index 29f5e36..616124e 100644 --- a/packages/web-rust/src/bin/web_fhe_encrypt.rs +++ b/packages/web-rust/src/bin/web_fhe_encrypt.rs @@ -1,19 +1,23 @@ mod greco; mod util; +use console_log; use greco::greco::*; +use log::info; // Use log macros from the `log` crate use wasm_bindgen::prelude::*; -use wasm_bindgen_test::*; +use wasm_bindgen_test::*; // For setting up logging to the browser console use serde::Deserialize; use std::{env, sync::Arc, thread, time}; use fhe::{ - bfv::{BfvParametersBuilder, Encoding, Plaintext, PublicKey, SecretKey}, + bfv::{BfvParametersBuilder, Ciphertext, Encoding, Plaintext, PublicKey, SecretKey}, mbfv::{AggregateIter, CommonRandomPoly, DecryptionShare, PublicKeyShare}, proto::bfv::Parameters, }; -use fhe_traits::{DeserializeParametrized, FheDecoder, FheEncoder, FheEncrypter, Serialize}; +use fhe_traits::{ + DeserializeParametrized, FheDecoder, FheDecrypter, FheEncoder, FheEncrypter, Serialize, +}; use rand::{distributions::Uniform, prelude::Distribution, rngs::OsRng, thread_rng, SeedableRng}; use util::timeit::{timeit, timeit_n}; @@ -33,7 +37,7 @@ impl Encrypt { pub fn encrypt_vote(&mut self, vote: u64, public_key: Vec) -> Result, JsValue> { let degree = 4096; - let plaintext_modulus: u64 = 4096; + let plaintext_modulus: u64 = 65537; let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; let params = BfvParametersBuilder::new() @@ -56,7 +60,9 @@ impl Encrypt { // Create Greco input validation ZKP proof let input_val_vectors = - InputValidationVectors::compute(&pt, &u_rns, &e0_rns, &e1_rns, &ct, &pk); + InputValidationVectors::compute(&pt, &u_rns, &e0_rns, &e1_rns, &ct, &pk).map_err( + |e| JsValue::from_str(&format!("Error computing input validation vectors: {}", e)), + )?; self.encrypted_vote = ct.to_bytes(); Ok(self.encrypted_vote.clone()) @@ -70,9 +76,34 @@ impl Encrypt { fn main() -> Result<(), Box> { Ok(()) } + // Tests #[wasm_bindgen_test] fn test_encrypt_vote() { - // assert true - assert_eq!(1, 1); + // Initialize the logger to print to the browser's console + console_log::init_with_level(log::Level::Info).expect("Error initializing logger"); + + let degree = 4096; + let plaintext_modulus: u64 = 65537; // Must be co-prime with Q + let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; + + let params = BfvParametersBuilder::new() + .set_degree(degree) + .set_plaintext_modulus(plaintext_modulus) + .set_moduli(&moduli) + .build_arc() + .unwrap(); + + let mut rng = thread_rng(); + let sk = SecretKey::random(¶ms, &mut rng); + let pk = PublicKey::new(&sk, &mut rng); + + let mut test = Encrypt::new(); + let vote = 10; + test.encrypt_vote(10, pk.to_bytes()).unwrap(); + + let ct = Ciphertext::from_bytes(&test.encrypted_vote, ¶ms).unwrap(); + let pt = sk.try_decrypt(&ct).unwrap(); + + assert_eq!(pt.value[0], vote); } From b8561e82ad72c565cca7680762b7f9aed0646bf0 Mon Sep 17 00:00:00 2001 From: fhedude Date: Thu, 19 Sep 2024 16:59:28 -0400 Subject: [PATCH 37/62] Fixed typo in encrypt vote test --- packages/web-rust/Cargo.lock | 12 ++++++++++++ packages/web-rust/src/bin/web_fhe_encrypt.rs | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/web-rust/Cargo.lock b/packages/web-rust/Cargo.lock index be09467..1ff1287 100644 --- a/packages/web-rust/Cargo.lock +++ b/packages/web-rust/Cargo.lock @@ -409,6 +409,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "console_log" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89f72f65e8501878b8a004d5a1afb780987e2ce2b4532c562e367a72c57499f" +dependencies = [ + "log", + "web-sys", +] + [[package]] name = "const-hex" version = "1.11.3" @@ -474,6 +484,7 @@ version = "0.1.0" dependencies = [ "bincode", "console", + "console_log", "ethers", "fhe", "fhe-math", @@ -481,6 +492,7 @@ dependencies = [ "fhe-util", "getrandom", "itertools 0.13.0", + "log", "ndarray", "num-bigint", "num-traits", diff --git a/packages/web-rust/src/bin/web_fhe_encrypt.rs b/packages/web-rust/src/bin/web_fhe_encrypt.rs index 616124e..faad1d3 100644 --- a/packages/web-rust/src/bin/web_fhe_encrypt.rs +++ b/packages/web-rust/src/bin/web_fhe_encrypt.rs @@ -100,7 +100,7 @@ fn test_encrypt_vote() { let mut test = Encrypt::new(); let vote = 10; - test.encrypt_vote(10, pk.to_bytes()).unwrap(); + test.encrypt_vote(vote, pk.to_bytes()).unwrap(); let ct = Ciphertext::from_bytes(&test.encrypted_vote, ¶ms).unwrap(); let pt = sk.try_decrypt(&ct).unwrap(); From 49d54e3289b624e6129a3ecba9cd54d559dea2e7 Mon Sep 17 00:00:00 2001 From: Auryn Macmillan Date: Fri, 20 Sep 2024 13:00:25 -0400 Subject: [PATCH 38/62] feat: import Enclave solidity interfaces from @gnosis-guild/enclave --- packages/risc0/contracts/CRISPRisc0.sol | 54 ++++++++++++++++--------- packages/risc0/hardhat.config.ts | 2 +- packages/risc0/script/Deploy.s.sol | 3 +- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/packages/risc0/contracts/CRISPRisc0.sol b/packages/risc0/contracts/CRISPRisc0.sol index 78c7477..e4cefaf 100644 --- a/packages/risc0/contracts/CRISPRisc0.sol +++ b/packages/risc0/contracts/CRISPRisc0.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-only -pragma solidity >=0.8.26; +pragma solidity >=0.8.27; -import {CRISPBase, IComputationModule, IInputValidator, IEnclave} from "evm_base/CRISPBase.sol"; +import {CRISPBase, IEnclave, IE3Program, IInputValidator, IDecryptionVerifier} from "evm_base/contracts/CRISPBase.sol"; import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; import {ImageID} from "./ImageID.sol"; @@ -9,7 +9,11 @@ contract CRISPRisc0 is CRISPBase { /// @notice RISC Zero verifier contract address. IRiscZeroVerifier public verifier; /// @notice Image ID of the only zkVM binary to accept verification from. - bytes32 public constant imageId = ImageID.VOTING_ID; // TODO: update this to the CRISP image ID + // bytes32 public constant imageId = ImageID.VOTING_ID; // TODO: update this to the CRISP image ID + + bytes32 public constant encryptionSchemeId = keccak256("fhe.rs:BFV"); + + mapping(uint256 e3Ids => bytes32 imageId) public imageIds; /// @notice Initialize the contract, binding it to a specified RISC Zero verifier. constructor(IEnclave _enclave, IRiscZeroVerifier _verifier) { @@ -23,32 +27,44 @@ contract CRISPRisc0 is CRISPBase { function validate( uint256 e3Id, - uint256 seed, - bytes memory data - ) external override returns (IInputValidator) { + uint256, + bytes calldata e3ProgramParams, + bytes calldata computeProviderParams + ) + external + override + returns (bytes32, IInputValidator, IDecryptionVerifier) + { require(paramsHashes[e3Id] == bytes32(0), E3AlreadyInitialized()); - ( - bytes memory params, - IInputValidator inputValidator - ) = abi.decode(data, (bytes, IInputValidator)); + (bytes memory params, IInputValidator inputValidator) = abi.decode( + e3ProgramParams, + (bytes, IInputValidator) + ); paramsHashes[e3Id] = keccak256(params); - return inputValidator; + IDecryptionVerifier decryptionVerifier = abi.decode( + computeProviderParams, + (IDecryptionVerifier) + ); + + return (encryptionSchemeId, inputValidator, decryptionVerifier); } function verify( uint256 e3Id, - bytes memory data - ) external view override returns (bytes memory, bool) { + bytes32 ciphertextOutputHash, + bytes memory proof + ) external view override returns (bool) { require(paramsHashes[e3Id] != bytes32(0), E3DoesNotExist()); uint256 inputRoot = enclave.getInputRoot(e3Id); - (bytes memory ciphertext_hash, bytes memory seal) = abi.decode( - data, - (bytes, bytes) + bytes memory seal = abi.decode(proof, (bytes)); + bytes memory journal = abi.encode( + ciphertextOutputHash, + inputRoot, + paramsHashes[e3Id] ); - bytes memory journal = abi.encode(ciphertext_hash, bytes(inputRoot), bytes(paramsHashes[e3id])); - verifier.verify(seal, imageId, sha256(journal)); - return (ciphertext, true); + verifier.verify(seal, imageIds[e3Id], sha256(journal)); + return (true); } } diff --git a/packages/risc0/hardhat.config.ts b/packages/risc0/hardhat.config.ts index c6b6f52..bc73c74 100644 --- a/packages/risc0/hardhat.config.ts +++ b/packages/risc0/hardhat.config.ts @@ -102,7 +102,7 @@ const config: HardhatUserConfig = { tests: "./test", }, solidity: { - version: "0.8.26", + version: "0.8.27", settings: { metadata: { // Not including the metadata hash diff --git a/packages/risc0/script/Deploy.s.sol b/packages/risc0/script/Deploy.s.sol index 44595e9..2295a69 100644 --- a/packages/risc0/script/Deploy.s.sol +++ b/packages/risc0/script/Deploy.s.sol @@ -22,9 +22,8 @@ import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; import {RiscZeroGroth16Verifier} from "risc0/groth16/RiscZeroGroth16Verifier.sol"; import {ControlID} from "risc0/groth16/ControlID.sol"; - import {CRISPRisc0} from "../contracts/CRISPRisc0.sol"; -import {IEnclave} from "evm_base/CRISPBase.sol"; +import {IEnclave} from "@gnosis-guild/enclave/contracts/interfaces/IEnclave.sol"; /// @notice Deployment script for the RISC Zero starter project. /// @dev Use the following environment variable to control the deployment: From eef92ebf4ee1e031802d8e79b06158c871d0fe6c Mon Sep 17 00:00:00 2001 From: Auryn Macmillan Date: Fri, 20 Sep 2024 13:57:14 -0400 Subject: [PATCH 39/62] feat: import Enclave solidity interfaces from @gnosis-guild/enclave --- packages/evm_base/contracts/CRISPBase.sol | 11 ++++---- .../interfaces/IComputationModule.sol | 25 ------------------- .../contracts/interfaces/IEnclave.sol | 10 -------- .../contracts/interfaces/IInputValidator.sol | 13 ---------- packages/evm_base/hardhat.config.ts | 2 +- packages/evm_base/package.json | 1 + 6 files changed, 7 insertions(+), 55 deletions(-) delete mode 100644 packages/evm_base/contracts/interfaces/IComputationModule.sol delete mode 100644 packages/evm_base/contracts/interfaces/IEnclave.sol delete mode 100644 packages/evm_base/contracts/interfaces/IInputValidator.sol diff --git a/packages/evm_base/contracts/CRISPBase.sol b/packages/evm_base/contracts/CRISPBase.sol index 6f1eb52..3ed00a3 100644 --- a/packages/evm_base/contracts/CRISPBase.sol +++ b/packages/evm_base/contracts/CRISPBase.sol @@ -1,11 +1,10 @@ // SPDX-License-Identifier: LGPL-3.0-only -pragma solidity >=0.8.26; +pragma solidity >=0.8.27; -import {IComputationModule, IInputValidator} from "./interfaces/IComputationModule.sol"; -import {IEnclave} from "./interfaces/IEnclave.sol"; +import {IE3Program, IInputValidator, IDecryptionVerifier} from "@gnosis-guild/enclave/contracts/interfaces/IE3Program.sol"; +import {IEnclave} from "@gnosis-guild/enclave/contracts/interfaces/IEnclave.sol"; - -abstract contract CRISPBase is IComputationModule { +abstract contract CRISPBase is IE3Program { IEnclave public enclave; mapping(uint256 e3Id => bytes32 paramsHash) public paramsHashes; @@ -17,7 +16,7 @@ abstract contract CRISPBase is IComputationModule { enclave = _enclave; } - function getParamsHash(uint256 e3Id) public view returns (bytes32 memory) { + function getParamsHash(uint256 e3Id) public view returns (bytes32) { return paramsHashes[e3Id]; } } diff --git a/packages/evm_base/contracts/interfaces/IComputationModule.sol b/packages/evm_base/contracts/interfaces/IComputationModule.sol deleted file mode 100644 index 1ce1a4f..0000000 --- a/packages/evm_base/contracts/interfaces/IComputationModule.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity >=0.8.26; - -import {IInputValidator} from "./IInputValidator.sol"; - -interface IComputationModule { - /// @notice This function should be called by the Enclave contract to validate the computation parameters. - /// @param params ABI encoded computation parameters. - /// @return inputValidator The input validator to be used for the computation. - function validate( - uint256 e3Id, - uint256 seed, - bytes calldata params - ) external returns (IInputValidator inputValidator); - - /// @notice This function should be called by the Enclave contract to verify the decrypted output of an E3. - /// @param e3Id ID of the E3. - /// @param outputData ABI encoded output data to be verified. - /// @return output The output data to be published. - /// @return success Whether the output data is valid. - function verify( - uint256 e3Id, - bytes memory outputData - ) external returns (bytes memory output, bool success); -} diff --git a/packages/evm_base/contracts/interfaces/IEnclave.sol b/packages/evm_base/contracts/interfaces/IEnclave.sol deleted file mode 100644 index 5cd0d88..0000000 --- a/packages/evm_base/contracts/interfaces/IEnclave.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity >=0.8.26; - -interface IEnclave { - /// @notice This function returns root of the input merkle tree for a given E3. - /// @dev This function MUST revert if the E3 does not exist. - /// @param e3Id ID of the E3. - /// @return root The root of the input merkle tree. - function getInputRoot(uint256 e3Id) external view returns (uint256 root); -} diff --git a/packages/evm_base/contracts/interfaces/IInputValidator.sol b/packages/evm_base/contracts/interfaces/IInputValidator.sol deleted file mode 100644 index 257af4d..0000000 --- a/packages/evm_base/contracts/interfaces/IInputValidator.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity >=0.8.26; - -interface IInputValidator { - /// @notice This function should be called by the Enclave contract to validate the input parameters. - /// @param params ABI encoded input parameters. - /// @return input The input data to be published. - /// @return success Whether the input parameters are valid. - function validate( - address sender, - bytes memory params - ) external returns (bytes memory input, bool success); -} diff --git a/packages/evm_base/hardhat.config.ts b/packages/evm_base/hardhat.config.ts index c6b6f52..bc73c74 100644 --- a/packages/evm_base/hardhat.config.ts +++ b/packages/evm_base/hardhat.config.ts @@ -102,7 +102,7 @@ const config: HardhatUserConfig = { tests: "./test", }, solidity: { - version: "0.8.26", + version: "0.8.27", settings: { metadata: { // Not including the metadata hash diff --git a/packages/evm_base/package.json b/packages/evm_base/package.json index d0ee572..756a4d2 100644 --- a/packages/evm_base/package.json +++ b/packages/evm_base/package.json @@ -80,6 +80,7 @@ "typechain": "cross-env TS_NODE_TRANSPILE_ONLY=true hardhat typechain" }, "dependencies": { + "@gnosis-guild/enclave": "^0.0.1", "@openzeppelin/contracts": "^5.0.0", "hardhat-etherscan": "^1.0.1" } From 77ec9661d610fac7a10aed718b36a9f57e2bac9f Mon Sep 17 00:00:00 2001 From: Auryn Macmillan Date: Fri, 20 Sep 2024 15:59:12 -0400 Subject: [PATCH 40/62] feat: update to enclave v0.0.3 --- packages/evm_base/contracts/CRISPBase.sol | 2 +- packages/evm_base/package.json | 2 +- packages/risc0/contracts/CRISPRisc0.sol | 17 ++++------------- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/packages/evm_base/contracts/CRISPBase.sol b/packages/evm_base/contracts/CRISPBase.sol index 3ed00a3..d1511b2 100644 --- a/packages/evm_base/contracts/CRISPBase.sol +++ b/packages/evm_base/contracts/CRISPBase.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.27; -import {IE3Program, IInputValidator, IDecryptionVerifier} from "@gnosis-guild/enclave/contracts/interfaces/IE3Program.sol"; +import {IE3Program, IInputValidator} from "@gnosis-guild/enclave/contracts/interfaces/IE3Program.sol"; import {IEnclave} from "@gnosis-guild/enclave/contracts/interfaces/IEnclave.sol"; abstract contract CRISPBase is IE3Program { diff --git a/packages/evm_base/package.json b/packages/evm_base/package.json index 756a4d2..995b037 100644 --- a/packages/evm_base/package.json +++ b/packages/evm_base/package.json @@ -80,7 +80,7 @@ "typechain": "cross-env TS_NODE_TRANSPILE_ONLY=true hardhat typechain" }, "dependencies": { - "@gnosis-guild/enclave": "^0.0.1", + "@gnosis-guild/enclave": "^0.0.3", "@openzeppelin/contracts": "^5.0.0", "hardhat-etherscan": "^1.0.1" } diff --git a/packages/risc0/contracts/CRISPRisc0.sol b/packages/risc0/contracts/CRISPRisc0.sol index e4cefaf..a05a3f0 100644 --- a/packages/risc0/contracts/CRISPRisc0.sol +++ b/packages/risc0/contracts/CRISPRisc0.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.27; -import {CRISPBase, IEnclave, IE3Program, IInputValidator, IDecryptionVerifier} from "evm_base/contracts/CRISPBase.sol"; +import {CRISPBase, IEnclave, IE3Program, IInputValidator} from "evm_base/contracts/CRISPBase.sol"; import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; import {ImageID} from "./ImageID.sol"; @@ -29,12 +29,8 @@ contract CRISPRisc0 is CRISPBase { uint256 e3Id, uint256, bytes calldata e3ProgramParams, - bytes calldata computeProviderParams - ) - external - override - returns (bytes32, IInputValidator, IDecryptionVerifier) - { + bytes calldata + ) external override returns (bytes32, IInputValidator) { require(paramsHashes[e3Id] == bytes32(0), E3AlreadyInitialized()); (bytes memory params, IInputValidator inputValidator) = abi.decode( e3ProgramParams, @@ -43,12 +39,7 @@ contract CRISPRisc0 is CRISPBase { paramsHashes[e3Id] = keccak256(params); - IDecryptionVerifier decryptionVerifier = abi.decode( - computeProviderParams, - (IDecryptionVerifier) - ); - - return (encryptionSchemeId, inputValidator, decryptionVerifier); + return (encryptionSchemeId, inputValidator); } function verify( From a524d8edf8729ce5422a0fee95130bb0462c1725 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Mon, 23 Sep 2024 13:32:14 +0500 Subject: [PATCH 41/62] feat: Sync Db with contracts and update deployment --- packages/risc0/script/Deploy.s.sol | 112 +++++++++--------- .../src/enclave_server/blockchain/events.rs | 2 - .../src/enclave_server/blockchain/handlers.rs | 59 ++++----- .../src/enclave_server/blockchain/mod.rs | 3 +- .../src/enclave_server/blockchain/relayer.rs | 11 +- .../src/enclave_server/blockchain/sync.rs | 76 ++++++++++++ .../server/src/enclave_server/database.rs | 18 ++- packages/server/src/enclave_server/mod.rs | 2 + .../src/enclave_server/routes/rounds.rs | 10 +- .../src/enclave_server/routes/voting.rs | 8 +- 10 files changed, 187 insertions(+), 114 deletions(-) create mode 100644 packages/server/src/enclave_server/blockchain/sync.rs diff --git a/packages/risc0/script/Deploy.s.sol b/packages/risc0/script/Deploy.s.sol index 2295a69..f581a30 100644 --- a/packages/risc0/script/Deploy.s.sol +++ b/packages/risc0/script/Deploy.s.sol @@ -14,7 +14,7 @@ // // SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.20; +pragma solidity ^0.8.27; import {Script} from "forge-std/Script.sol"; import "forge-std/Test.sol"; @@ -46,89 +46,93 @@ contract CRISPRisc0Deploy is Script { function run() external { // Read and log the chainID uint256 chainId = block.chainid; - console2.log("You are deploying on ChainID %d", chainId); + console2.log("Deploying on ChainID %d", chainId); + setupVerifier(); + setupDeployer(); + + // Contracts to Deploy + deployCrispRisc0(); + + vm.stopBroadcast(); + } + + function setupVerifier() private { // Read the config profile from the environment variable, or use the default for the chainId. // Default is the first profile with a matching chainId field. string memory config = vm.readFile( string.concat(vm.projectRoot(), "/", CONFIG_FILE) ); - string memory configProfile = vm.envOr("CONFIG_PROFILE", string("")); - if (bytes(configProfile).length == 0) { - string[] memory profileKeys = vm.parseTomlKeys(config, ".profile"); - for (uint256 i = 0; i < profileKeys.length; i++) { - if ( - stdToml.readUint( - config, - string.concat(".profile.", profileKeys[i], ".chainId") - ) == chainId - ) { - configProfile = profileKeys[i]; - break; - } - } - } + string memory configProfile = getConfigProfile(config); if (bytes(configProfile).length != 0) { - console2.log("Deploying using config profile:", configProfile); - string memory configProfileKey = string.concat( - ".profile.", - configProfile - ); + console2.log("Using config profile:", configProfile); address riscZeroVerifierAddress = stdToml.readAddress( config, - string.concat(configProfileKey, ".riscZeroVerifierAddress") + string.concat( + ".profile.", + configProfile, + ".riscZeroVerifierAddress" + ) ); - // If set, use the predeployed verifier address found in the config. verifier = IRiscZeroVerifier(riscZeroVerifierAddress); + } - address enclaveAddress = stdToml.readAddress( - config, - string.concat(configProfileKey, ".enclaveAddress") + if (address(verifier) == address(0)) { + verifier = new RiscZeroGroth16Verifier( + ControlID.CONTROL_ROOT, + ControlID.BN254_CONTROL_ID ); - enclave = IEnclave(enclaveAddress); + console2.log( + "Deployed RiscZeroGroth16Verifier to", + address(verifier) + ); + } else { + console2.log("Using IRiscZeroVerifier at", address(verifier)); } + } - // Determine the wallet to send transactions from. + function setupDeployer() private { uint256 deployerKey = uint256( vm.envOr("ETH_WALLET_PRIVATE_KEY", bytes32(0)) ); - address deployerAddr = address(0); + address deployerAddr = vm.envOr("ETH_WALLET_ADDRESS", address(0)); + if (deployerKey != 0) { - // Check for conflicts in how the two environment variables are set. - address envAddr = vm.envOr("ETH_WALLET_ADDRESS", address(0)); require( - envAddr == address(0) || envAddr == vm.addr(deployerKey), - "conflicting settings from ETH_WALLET_PRIVATE_KEY and ETH_WALLET_ADDRESS" + deployerAddr == address(0) || + deployerAddr == vm.addr(deployerKey), + "Conflicting wallet settings" ); - vm.startBroadcast(deployerKey); } else { - deployerAddr = vm.envAddress("ETH_WALLET_ADDRESS"); + require(deployerAddr != address(0), "No deployer address set"); vm.startBroadcast(deployerAddr); } + } - // Deploy the verifier, if not already deployed. - if (address(verifier) == address(0)) { - verifier = new RiscZeroGroth16Verifier( - ControlID.CONTROL_ROOT, - ControlID.BN254_CONTROL_ID - ); - console2.log( - "Deployed RiscZeroGroth16Verifier to", - address(verifier) - ); - } else { - console2.log( - "Using IRiscZeroVerifier contract deployed at", - address(verifier) - ); + function getConfigProfile( + string memory config + ) private view returns (string memory) { + string memory configProfile = vm.envOr("CONFIG_PROFILE", string("")); + if (bytes(configProfile).length == 0) { + string[] memory profileKeys = vm.parseTomlKeys(config, ".profile"); + for (uint256 i = 0; i < profileKeys.length; i++) { + if ( + stdToml.readUint( + config, + string.concat(".profile.", profileKeys[i], ".chainId") + ) == block.chainid + ) { + return profileKeys[i]; + } + } } + return configProfile; + } - // Deploy the application contract. + function deployCrispRisc0() private { CRISPRisc0 crisp = new CRISPRisc0(enclave, verifier); console2.log("Deployed CRISPRisc0 to", address(crisp)); - - vm.stopBroadcast(); } } diff --git a/packages/server/src/enclave_server/blockchain/events.rs b/packages/server/src/enclave_server/blockchain/events.rs index 6c715f1..21e240c 100644 --- a/packages/server/src/enclave_server/blockchain/events.rs +++ b/packages/server/src/enclave_server/blockchain/events.rs @@ -1,11 +1,9 @@ use alloy::{ rpc::types::Log, sol, - sol_types::{SolCall, SolEvent}, }; use eyre::Result; -use log::info; use super::handlers::{handle_e3, handle_input_published, handle_plaintext_output_published, handle_ciphertext_output_published}; use super::listener::ContractEvent; diff --git a/packages/server/src/enclave_server/blockchain/handlers.rs b/packages/server/src/enclave_server/blockchain/handlers.rs index ed45b6b..5cdfdae 100644 --- a/packages/server/src/enclave_server/blockchain/handlers.rs +++ b/packages/server/src/enclave_server/blockchain/handlers.rs @@ -5,26 +5,23 @@ use super::{ use crate::enclave_server::models::E3; use crate::enclave_server::{ config::CONFIG, - database::{generate_emoji, get_e3, increment_e3_round, GLOBAL_DB}, -}; -use alloy::{ - rpc::types::Log, - sol_types::{SolCall, SolEvent}, + database::{generate_emoji, get_e3, save_e3, increment_e3_round}, }; +use alloy::rpc::types::Log; use alloy_sol_types::SolValue; use chrono::Utc; use compute_provider::FHEInputs; -use std::env; use std::error::Error; use tokio::time::{sleep, Duration}; use voting_risc0::run_compute; - use log::info; +type Result = std::result::Result>; + pub async fn handle_e3( e3_activated: E3Activated, log: Log, -) -> Result<(), Box> { +) -> Result<()> { let e3_id = e3_activated.e3Id.to::(); info!("Handling E3 request with id {}", e3_id); @@ -80,12 +77,7 @@ pub async fn handle_e3( // Save E3 to the database let key = format!("e3:{}", e3_id); - - let db = GLOBAL_DB.write().await; - db.insert(key, serde_json::to_vec(&e3_obj).unwrap()) - .unwrap(); - drop(db); - + save_e3(&e3_obj, &key).await?; increment_e3_round().await.unwrap(); // Sleep till the E3 expires @@ -127,58 +119,49 @@ pub async fn handle_e3( Ok(()) } -pub async fn handle_input_published( - input: InputPublished, -) -> Result<(), Box> { +pub async fn handle_input_published(input: InputPublished) -> Result<()> { info!("Handling VoteCast event..."); let e3_id = input.e3Id.to::(); - let data = input.data.to_vec(); - let input_count = input.index.to::(); - let (mut e3, key) = get_e3(e3_id).await.unwrap(); - e3.ciphertext_inputs.push((data, input_count)); + let (mut e3, key) = get_e3(e3_id).await?; + + e3.ciphertext_inputs.push((input.data.to_vec(), input.index.to::())); e3.vote_count += 1; - let db = GLOBAL_DB.write().await; - db.insert(key, serde_json::to_vec(&e3).unwrap()).unwrap(); + + save_e3(&e3, &key).await?; info!("Saved Input with Hash: {:?}", input.inputHash); Ok(()) } -pub async fn handle_ciphertext_output_published( - ciphertext_output: CiphertextOutputPublished, -) -> Result<(), Box> { +pub async fn handle_ciphertext_output_published(ciphertext_output: CiphertextOutputPublished) -> Result<()> { info!("Handling CiphertextOutputPublished event..."); let e3_id = ciphertext_output.e3Id.to::(); - let (mut e3, key) = get_e3(e3_id).await.unwrap(); + let (mut e3, key) = get_e3(e3_id).await?; + e3.ciphertext_output = ciphertext_output.ciphertextOutput.to_vec(); e3.status = "Published".to_string(); - let db = GLOBAL_DB.write().await; - db.insert(key, serde_json::to_vec(&e3).unwrap()).unwrap(); + save_e3(&e3, &key).await?; info!("CiphertextOutputPublished event handled."); Ok(()) } -pub async fn handle_plaintext_output_published( - plaintext_output: PlaintextOutputPublished, -) -> Result<(), Box> { +pub async fn handle_plaintext_output_published(plaintext_output: PlaintextOutputPublished) -> Result<()> { info!("Handling PlaintextOutputPublished event..."); let e3_id = plaintext_output.e3Id.to::(); - - let (mut e3, key) = get_e3(e3_id).await.unwrap(); - e3.plaintext_output = plaintext_output.plaintextOutput.to_vec(); + let (mut e3, key) = get_e3(e3_id).await?; + e3.plaintext_output = plaintext_output.plaintextOutput.to_vec(); e3.votes_option_2 = u64::from_be_bytes(e3.plaintext_output.as_slice().try_into().unwrap()); e3.votes_option_1 = e3.vote_count - e3.votes_option_2; e3.status = "Finished".to_string(); - let db = GLOBAL_DB.write().await; - db.insert(key, serde_json::to_vec(&e3).unwrap()).unwrap(); + save_e3(&e3, &key).await?; info!("PlaintextOutputPublished event handled."); Ok(()) -} +} \ No newline at end of file diff --git a/packages/server/src/enclave_server/blockchain/mod.rs b/packages/server/src/enclave_server/blockchain/mod.rs index 0f55184..cffdf6f 100644 --- a/packages/server/src/enclave_server/blockchain/mod.rs +++ b/packages/server/src/enclave_server/blockchain/mod.rs @@ -1,4 +1,5 @@ pub mod listener; pub mod relayer; pub mod events; -pub mod handlers; \ No newline at end of file +pub mod handlers; +pub mod sync; \ No newline at end of file diff --git a/packages/server/src/enclave_server/blockchain/relayer.rs b/packages/server/src/enclave_server/blockchain/relayer.rs index 7235b3a..1634cb6 100644 --- a/packages/server/src/enclave_server/blockchain/relayer.rs +++ b/packages/server/src/enclave_server/blockchain/relayer.rs @@ -35,6 +35,7 @@ sol! { #[sol(rpc)] contract Enclave { uint256 public nexte3Id = 0; + mapping(uint256 e3Id => uint256 inputCount) public inputCounts; function request(address filter, uint32[2] calldata threshold, uint256[2] calldata startWindow, uint256 duration, address e3Program, bytes memory e3ProgramParams, bytes memory computeProviderParams) external payable returns (uint256 e3Id, E3 memory e3); @@ -61,8 +62,8 @@ type CRISPProvider = FillProvider< >; pub struct EnclaveContract { - provider: Arc, - contract_address: Address, + pub provider: Arc, + pub contract_address: Address, } impl EnclaveContract { @@ -154,6 +155,12 @@ impl EnclaveContract { Ok(e3_return.e3) } + pub async fn get_input_count(&self, e3_id: U256) -> Result { + let contract = Enclave::new(self.contract_address, &self.provider); + let input_count = contract.inputCounts(e3_id).call().await?; + Ok(input_count.inputCount) + } + pub async fn get_latest_block(&self) -> Result { let block = self.provider.get_block_number().await?; Ok(block) diff --git a/packages/server/src/enclave_server/blockchain/sync.rs b/packages/server/src/enclave_server/blockchain/sync.rs new file mode 100644 index 0000000..7a7d7aa --- /dev/null +++ b/packages/server/src/enclave_server/blockchain/sync.rs @@ -0,0 +1,76 @@ +use super::relayer::EnclaveContract; +use crate::enclave_server::database::{get_e3, get_e3_round, save_e3, generate_emoji}; +use crate::enclave_server::models::E3; +use alloy::primitives::U256; +use chrono::Utc; +use eyre::Result; +use log::info; +pub async fn sync_contracts_db() -> Result<(), Box> { + info!("Syncing contracts with database"); + let contract = EnclaveContract::new().await?; + let contract_e3_id = contract.get_e3_id().await?.to::(); + let db_e3_id = get_e3_round().await?; + + if contract_e3_id == 0 { + info!("No E3s found in contract, skipping sync"); + return Ok(()); + } + + // Update existing E3 if expired + if let Ok((mut e3, key)) = get_e3(db_e3_id).await { + if e3.status != "Finished" && e3.expiration < Utc::now().timestamp() as u64 { + let c_e3 = contract.get_e3(U256::from(db_e3_id)).await?; + let inputs_count = contract.get_input_count(U256::from(db_e3_id)).await?.to::(); + + e3.plaintext_output = c_e3.plaintextOutput.to_vec(); + e3.votes_option_2 = u64::from_be_bytes(e3.plaintext_output.as_slice().try_into()?); + e3.votes_option_1 = inputs_count - e3.votes_option_2; + e3.status = "Finished".to_string(); + + save_e3(&e3, &key).await?; + } + } + + // Sync new E3s + for e3_id in db_e3_id + 1..=contract_e3_id { + let e3 = contract.get_e3(U256::from(e3_id)).await?; + let inputs_count = contract.get_input_count(U256::from(e3_id)).await?.to::(); + + let (status, votes) = if e3.plaintextOutput.is_empty() { + if e3.ciphertextOutput.is_empty() { + ("Active", 0) + } else { + ("Published", 0) + } + } else { + let votes = u64::from_be_bytes(e3.plaintextOutput.to_vec().as_slice().try_into()?); + ("Finished", votes) + }; + + let e3_obj = E3 { + id: e3_id, + chain_id: 31337, // Hardcoded for testing + enclave_address: contract.contract_address.to_string(), + status: status.to_string(), + has_voted: vec!["".to_string()], + vote_count: inputs_count, + votes_option_1: inputs_count - votes, + votes_option_2: votes, + start_time: e3.expiration.to::() - e3.duration.to::(), + block_start: 0, + duration: e3.duration.to::(), + expiration: e3.expiration.to::(), + e3_params: e3.e3ProgramParams.to_vec(), + committee_public_key: e3.committeePublicKey.to_vec(), + ciphertext_output: e3.ciphertextOutput.to_vec(), + plaintext_output: e3.plaintextOutput.to_vec(), + ciphertext_inputs: vec![], + emojis: generate_emoji().into(), + }; + + save_e3(&e3_obj, &format!("e3:{}", e3_id)).await?; + } + + info!("Contracts synced with database"); + Ok(()) +} \ No newline at end of file diff --git a/packages/server/src/enclave_server/database.rs b/packages/server/src/enclave_server/database.rs index 4d98272..a381adb 100644 --- a/packages/server/src/enclave_server/database.rs +++ b/packages/server/src/enclave_server/database.rs @@ -3,7 +3,7 @@ use log::info; use once_cell::sync::Lazy; use rand::Rng; use sled::{Db, IVec}; -use std::{error::Error, io::Read, str, sync::Arc}; +use std::{error::Error, str, sync::Arc}; use tokio::sync::RwLock; pub static GLOBAL_DB: Lazy>> = Lazy::new(|| { @@ -13,7 +13,7 @@ pub static GLOBAL_DB: Lazy>> = Lazy::new(|| { Arc::new(RwLock::new(sled::open(pathdb).unwrap())) }); -pub async fn get_e3(e3_id: u64) -> Result<(E3, String), Box> { +pub async fn get_e3(e3_id: u64) -> Result<(E3, String), Box> { let key = format!("e3:{}", e3_id); let db = GLOBAL_DB.read().await; @@ -29,7 +29,17 @@ pub async fn get_e3(e3_id: u64) -> Result<(E3, String), Box> { Ok((e3, key)) } -pub async fn get_e3_round() -> Result> { + +pub async fn save_e3(e3: &E3, key: &str) -> Result<(), Box> { + let db = GLOBAL_DB.write().await; + match db.insert(key.to_string(), serde_json::to_vec(e3)?) { + Ok(_) => (), + Err(e) => return Err(format!("Failed to save E3: {}", e).into()), + }; + Ok(()) +} + +pub async fn get_e3_round() -> Result> { let key = "e3:round"; let db = GLOBAL_DB.read().await; @@ -66,7 +76,7 @@ pub async fn get_e3_round() -> Result> { pub async fn increment_e3_round() -> Result<(), Box> { let key = "e3:round"; - let mut encoded = Vec::new(); + let mut encoded = vec![]; match get_e3_round().await { Ok(round_count) => { diff --git a/packages/server/src/enclave_server/mod.rs b/packages/server/src/enclave_server/mod.rs index 915cceb..5fcb272 100644 --- a/packages/server/src/enclave_server/mod.rs +++ b/packages/server/src/enclave_server/mod.rs @@ -10,6 +10,7 @@ use actix_web::{web, App, HttpServer, middleware::Logger}; use models::AppState; use database::GLOBAL_DB; use blockchain::listener::start_listener; +use blockchain::sync::sync_contracts_db; use env_logger::{Builder, Target}; use log::{LevelFilter, Record}; @@ -40,6 +41,7 @@ fn init_logger() { #[actix_web::main] pub async fn start_server() -> Result<(), Box> { init_logger(); + sync_contracts_db().await?; tokio::spawn(async { if let Err(e) = start_listener(&CONFIG.ws_rpc_url, &CONFIG.contract_address).await { diff --git a/packages/server/src/enclave_server/routes/rounds.rs b/packages/server/src/enclave_server/routes/rounds.rs index 1eb583e..767145c 100644 --- a/packages/server/src/enclave_server/routes/rounds.rs +++ b/packages/server/src/enclave_server/routes/rounds.rs @@ -1,13 +1,9 @@ -use chrono::Utc; use log::info; use actix_web::{web, HttpResponse, Responder}; - -use crate::enclave_server::models::{CTRequest, PKRequest}; -use crate::enclave_server::database::{generate_emoji, get_e3, get_e3_round, GLOBAL_DB}; -use crate::enclave_server::models::{ - AppState, CrispConfig, JsonResponse, ReportTallyRequest, RoundCount -}; +use crate::enclave_server::models::CTRequest; +use crate::enclave_server::database::{get_e3, get_e3_round}; +use crate::enclave_server::models::RoundCount; pub fn setup_routes(config: &mut web::ServiceConfig) { config diff --git a/packages/server/src/enclave_server/routes/voting.rs b/packages/server/src/enclave_server/routes/voting.rs index c7a84d3..c0ed525 100644 --- a/packages/server/src/enclave_server/routes/voting.rs +++ b/packages/server/src/enclave_server/routes/voting.rs @@ -1,10 +1,6 @@ -use alloy::primitives::{Bytes, B256, U256}; -use std::{env, str}; - -use eyre::Result; -use log::info; - +use alloy::primitives::{Bytes, U256}; use actix_web::{web, HttpResponse, Responder}; +use log::info; use crate::enclave_server::database::get_e3; use crate::enclave_server::{ From fc062e13fa56380ff3fbad4d5ef1fb2878814c15 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Tue, 24 Sep 2024 21:37:53 +0500 Subject: [PATCH 42/62] Fix Journal encoding --- packages/risc0/Cargo.lock | 1 + packages/risc0/apps/Cargo.toml | 1 + packages/risc0/apps/src/lib.rs | 14 +++------ packages/risc0/contracts/CRISPRisc0.sol | 22 +++++++++---- packages/risc0/script/Deploy.s.sol | 2 +- packages/server/Cargo.lock | 1 + packages/server/src/cli/voting.rs | 24 +++++++------- .../src/enclave_server/blockchain/handlers.rs | 31 ++++++++++--------- .../src/enclave_server/blockchain/relayer.rs | 23 +++++++++++--- .../src/enclave_server/blockchain/sync.rs | 2 +- packages/server/src/enclave_server/mod.rs | 27 ++++++++-------- 11 files changed, 88 insertions(+), 60 deletions(-) diff --git a/packages/risc0/Cargo.lock b/packages/risc0/Cargo.lock index 1fcbc61..859a529 100644 --- a/packages/risc0/Cargo.lock +++ b/packages/risc0/Cargo.lock @@ -4872,6 +4872,7 @@ dependencies = [ "methods", "risc0-ethereum-contracts", "risc0-zkvm", + "serde", "tokio", "voting-core", ] diff --git a/packages/risc0/apps/Cargo.toml b/packages/risc0/apps/Cargo.toml index 18fedd1..9308825 100644 --- a/packages/risc0/apps/Cargo.toml +++ b/packages/risc0/apps/Cargo.toml @@ -4,6 +4,7 @@ version = { workspace = true } edition = { workspace = true } [dependencies] +serde = { version = "1.0", features = ["derive", "std"] } alloy-primitives = { workspace = true } alloy-sol-types = { workspace = true } anyhow = { workspace = true } diff --git a/packages/risc0/apps/src/lib.rs b/packages/risc0/apps/src/lib.rs index abfd4cd..5115e1c 100644 --- a/packages/risc0/apps/src/lib.rs +++ b/packages/risc0/apps/src/lib.rs @@ -4,26 +4,21 @@ use methods::VOTING_ELF; use risc0_ethereum_contracts::groth16; use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts, VerifierContext}; use voting_core::fhe_processor; -use std::env; +use serde::{Deserialize, Serialize}; pub struct Risc0Provider; +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Risc0Output { pub result: ComputeResult, + pub bytes: Vec, pub seal: Vec, } impl ComputeProvider for Risc0Provider { type Output = Risc0Output; - fn prove(&self, input: &ComputeInput) -> Self::Output { - println!("Proving with RISC0 provider"); - let var_name = "BONSAI_API_URL"; + fn prove(&self, input: &ComputeInput) -> Self::Output { - match env::var(var_name) { - Ok(value) => println!("{}: {}", var_name, value), - Err(e) => println!("Couldn't read {}: {}", var_name, e), - } - let env = ExecutorEnv::builder() .write(input) .unwrap() @@ -46,6 +41,7 @@ impl ComputeProvider for Risc0Provider { Risc0Output { result: decoded_journal, + bytes: receipt.journal.bytes.clone(), seal, } } diff --git a/packages/risc0/contracts/CRISPRisc0.sol b/packages/risc0/contracts/CRISPRisc0.sol index a05a3f0..d6932e1 100644 --- a/packages/risc0/contracts/CRISPRisc0.sol +++ b/packages/risc0/contracts/CRISPRisc0.sol @@ -48,14 +48,24 @@ contract CRISPRisc0 is CRISPBase { bytes memory proof ) external view override returns (bool) { require(paramsHashes[e3Id] != bytes32(0), E3DoesNotExist()); - uint256 inputRoot = enclave.getInputRoot(e3Id); + bytes32 inputRoot = bytes32(enclave.getInputRoot(e3Id)); bytes memory seal = abi.decode(proof, (bytes)); - bytes memory journal = abi.encode( - ciphertextOutputHash, - inputRoot, - paramsHashes[e3Id] - ); + + bytes memory journal = new bytes(396); // (32 + 1) * 4 * 3 + + encodeLengthPrefixAndHash(journal, 0, ciphertextOutputHash); + encodeLengthPrefixAndHash(journal, 132, paramsHashes[e3Id]); + encodeLengthPrefixAndHash(journal, 264, inputRoot); + verifier.verify(seal, imageIds[e3Id], sha256(journal)); return (true); } + + function encodeLengthPrefixAndHash(bytes memory journal, uint256 startIndex, bytes32 hashVal) internal pure { + journal[startIndex] = 0x20; + startIndex += 4; + for (uint256 i = 0; i < 32; i++) { + journal[startIndex + i * 4] = hashVal[i]; + } + } } diff --git a/packages/risc0/script/Deploy.s.sol b/packages/risc0/script/Deploy.s.sol index f581a30..1108d08 100644 --- a/packages/risc0/script/Deploy.s.sol +++ b/packages/risc0/script/Deploy.s.sol @@ -48,8 +48,8 @@ contract CRISPRisc0Deploy is Script { uint256 chainId = block.chainid; console2.log("Deploying on ChainID %d", chainId); - setupVerifier(); setupDeployer(); + setupVerifier(); // Contracts to Deploy deployCrispRisc0(); diff --git a/packages/server/Cargo.lock b/packages/server/Cargo.lock index 3d668ea..dd12175 100644 --- a/packages/server/Cargo.lock +++ b/packages/server/Cargo.lock @@ -7394,6 +7394,7 @@ dependencies = [ "methods", "risc0-ethereum-contracts", "risc0-zkvm", + "serde", "tokio", "voting-core", ] diff --git a/packages/server/src/cli/voting.rs b/packages/server/src/cli/voting.rs index 83ab5d8..23596a5 100644 --- a/packages/server/src/cli/voting.rs +++ b/packages/server/src/cli/voting.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use std::{env, sync::Arc}; use alloy::{ - primitives::{Address, Bytes, U256}, + primitives::{Address, Bytes, Keccak256, U256}, sol_types::{SolCall, SolEvent, SolValue}, }; @@ -80,11 +80,21 @@ pub async fn initialize_crisp_round( info!("Initializing Keyshare nodes..."); let contract = EnclaveContract::new().await?; + // let tx = contract.get_root(U256::from(1)).await?; + // let bytes: [u8; 32] = tx.to_be_bytes::<32>(); + // println!("Root: {:?}", bytes); + + // let tx = contract.get_e3_params(U256::from(1)).await?; + // println!("E3 Params: {:?}", tx.to_vec()); + // let mut keccak = Keccak256::new(); + // keccak.update(tx.to_vec()); + // let hsh = keccak.finalize(); + // println!("Hash: {:?}", hsh.to_vec()); let params = generate_bfv_parameters().unwrap().to_bytes(); let filter: Address = "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5".parse()?; let threshold: [u32; 2] = [1, 2]; let start_window: [U256; 2] = [U256::from(Utc::now().timestamp()), U256::from(Utc::now().timestamp() + 600)]; - let duration: U256 = U256::from(300); + let duration: U256 = U256::from(60); let e3_program: Address = "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5".parse()?; let e3_params = Bytes::from(params); let compute_provider_params = Bytes::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); @@ -106,16 +116,6 @@ pub async fn activate_e3_round( let (sk, pk) = generate_keys(¶ms); let contract = EnclaveContract::new().await?; - // let value = vec![5u64]; - // let pt = Plaintext::try_encode(&value, Encoding::poly(), ¶ms)?; - // let ct = pk.try_encrypt(&pt, &mut thread_rng())?; - // let data = (vec![1u8; 32], vec![2u8; 32]).abi_encode(); - // let ct_bytes = ct.to_bytes(); - // println!("Ciphertext bytes: {:?}", ct_bytes.len()); - - // let res = contract.publish_ciphertext_output(U256::from(5u64), ct_bytes.into(), data.into()).await?; - // println!("Event emitted: {:?}", res.transaction_hash); - let pk_bytes = Bytes::from(pk.to_bytes()); // Print how many bytes are in the public key println!("Public key bytes: {:?}", pk_bytes.len()); diff --git a/packages/server/src/enclave_server/blockchain/handlers.rs b/packages/server/src/enclave_server/blockchain/handlers.rs index 5cdfdae..0bdb3ff 100644 --- a/packages/server/src/enclave_server/blockchain/handlers.rs +++ b/packages/server/src/enclave_server/blockchain/handlers.rs @@ -5,10 +5,9 @@ use super::{ use crate::enclave_server::models::E3; use crate::enclave_server::{ config::CONFIG, - database::{generate_emoji, get_e3, save_e3, increment_e3_round}, + database::{generate_emoji, get_e3, increment_e3_round, save_e3}, }; use alloy::rpc::types::Log; -use alloy_sol_types::SolValue; use chrono::Utc; use compute_provider::FHEInputs; use std::error::Error; @@ -18,10 +17,7 @@ use log::info; type Result = std::result::Result>; -pub async fn handle_e3( - e3_activated: E3Activated, - log: Log, -) -> Result<()> { +pub async fn handle_e3(e3_activated: E3Activated, log: Log) -> Result<()> { let e3_id = e3_activated.e3Id.to::(); info!("Handling E3 request with id {}", e3_id); @@ -100,13 +96,15 @@ pub async fn handle_e3( .await .unwrap(); - let proof = ( - risc0_output.result.ciphertext_hash, risc0_output.seal - ).abi_encode(); + println!("RISC0 Output: {:?}", risc0_output); // Params will be encoded on chain to create the journal let tx = contract - .publish_ciphertext_output(e3_activated.e3Id, ciphertext.into(), proof.into()) + .publish_ciphertext_output( + e3_activated.e3Id, + ciphertext.into(), + risc0_output.seal.into(), + ) .await?; info!( @@ -125,7 +123,8 @@ pub async fn handle_input_published(input: InputPublished) -> Result<()> { let e3_id = input.e3Id.to::(); let (mut e3, key) = get_e3(e3_id).await?; - e3.ciphertext_inputs.push((input.data.to_vec(), input.index.to::())); + e3.ciphertext_inputs + .push((input.data.to_vec(), input.index.to::())); e3.vote_count += 1; save_e3(&e3, &key).await?; @@ -134,7 +133,9 @@ pub async fn handle_input_published(input: InputPublished) -> Result<()> { Ok(()) } -pub async fn handle_ciphertext_output_published(ciphertext_output: CiphertextOutputPublished) -> Result<()> { +pub async fn handle_ciphertext_output_published( + ciphertext_output: CiphertextOutputPublished, +) -> Result<()> { info!("Handling CiphertextOutputPublished event..."); let e3_id = ciphertext_output.e3Id.to::(); @@ -149,7 +150,9 @@ pub async fn handle_ciphertext_output_published(ciphertext_output: CiphertextOut Ok(()) } -pub async fn handle_plaintext_output_published(plaintext_output: PlaintextOutputPublished) -> Result<()> { +pub async fn handle_plaintext_output_published( + plaintext_output: PlaintextOutputPublished, +) -> Result<()> { info!("Handling PlaintextOutputPublished event..."); let e3_id = plaintext_output.e3Id.to::(); @@ -164,4 +167,4 @@ pub async fn handle_plaintext_output_published(plaintext_output: PlaintextOutput info!("PlaintextOutputPublished event handled."); Ok(()) -} \ No newline at end of file +} diff --git a/packages/server/src/enclave_server/blockchain/relayer.rs b/packages/server/src/enclave_server/blockchain/relayer.rs index 1634cb6..9043187 100644 --- a/packages/server/src/enclave_server/blockchain/relayer.rs +++ b/packages/server/src/enclave_server/blockchain/relayer.rs @@ -1,3 +1,4 @@ +use crate::enclave_server::CONFIG; use alloy::{ network::{Ethereum, EthereumWallet}, primitives::{Address, Bytes, U256}, @@ -12,7 +13,6 @@ use alloy::{ }; use eyre::Result; use std::sync::Arc; -use crate::enclave_server::CONFIG; sol! { #[derive(Debug)] @@ -26,8 +26,8 @@ sol! { bytes e3ProgramParams; address inputValidator; address decryptionVerifier; - bytes committeePublicKey; - bytes ciphertextOutput; + bytes32 committeePublicKey; + bytes32 ciphertextOutput; bytes plaintextOutput; } @@ -36,6 +36,7 @@ sol! { contract Enclave { uint256 public nexte3Id = 0; mapping(uint256 e3Id => uint256 inputCount) public inputCounts; + mapping(uint256 e3Id => bytes params) public e3Params; function request(address filter, uint32[2] calldata threshold, uint256[2] calldata startWindow, uint256 duration, address e3Program, bytes memory e3ProgramParams, bytes memory computeProviderParams) external payable returns (uint256 e3Id, E3 memory e3); @@ -43,11 +44,13 @@ sol! { function publishInput(uint256 e3Id, bytes memory data ) external returns (bool success); - function publishCiphertextOutput(uint256 e3Id, bytes calldata data, bytes memory proof) external returns (bool success); + function publishCiphertextOutput(uint256 e3Id, bytes memory ciphertextOutput, bytes memory proof) external returns (bool success); function publishPlaintextOutput(uint256 e3Id, bytes memory data) external returns (bool success); function getE3(uint256 e3Id) external view returns (E3 memory e3); + + function getRoot(uint256 id) public view returns (uint256); } } @@ -165,4 +168,16 @@ impl EnclaveContract { let block = self.provider.get_block_number().await?; Ok(block) } + + pub async fn get_root(&self, id: U256) -> Result { + let contract = Enclave::new(self.contract_address, &self.provider); + let root = contract.getRoot(id).call().await?; + Ok(root._0) + } + + pub async fn get_e3_params(&self, e3_id: U256) -> Result { + let contract = Enclave::new(self.contract_address, &self.provider); + let params = contract.e3Params(e3_id).call().await?; + Ok(params.params) + } } diff --git a/packages/server/src/enclave_server/blockchain/sync.rs b/packages/server/src/enclave_server/blockchain/sync.rs index 7a7d7aa..f4abc13 100644 --- a/packages/server/src/enclave_server/blockchain/sync.rs +++ b/packages/server/src/enclave_server/blockchain/sync.rs @@ -18,7 +18,7 @@ pub async fn sync_contracts_db() -> Result<(), Box(); diff --git a/packages/server/src/enclave_server/mod.rs b/packages/server/src/enclave_server/mod.rs index 5fcb272..28f3f2a 100644 --- a/packages/server/src/enclave_server/mod.rs +++ b/packages/server/src/enclave_server/mod.rs @@ -1,28 +1,29 @@ +pub mod blockchain; +mod config; mod database; mod models; mod routes; -mod config; -pub mod blockchain; use actix_cors::Cors; -use actix_web::{web, App, HttpServer, middleware::Logger}; +use actix_web::{middleware::Logger, web, App, HttpServer}; -use models::AppState; -use database::GLOBAL_DB; use blockchain::listener::start_listener; use blockchain::sync::sync_contracts_db; +use database::GLOBAL_DB; +use models::AppState; +use config::CONFIG; use env_logger::{Builder, Target}; use log::{LevelFilter, Record}; -use std::path::Path; +use serde::{Deserialize, Serialize}; use std::io::Write; -use config::CONFIG; +use std::path::Path; fn init_logger() { let mut builder = Builder::new(); builder - .target(Target::Stdout) - .filter(None, LevelFilter::Info) + .target(Target::Stdout) + .filter(None, LevelFilter::Info) .format(|buf, record: &Record| { let file = record.file().unwrap_or("unknown"); let filename = Path::new(file).file_name().unwrap_or_else(|| file.as_ref()); @@ -51,12 +52,12 @@ pub async fn start_server() -> Result<(), Box Date: Tue, 24 Sep 2024 22:17:54 +0500 Subject: [PATCH 43/62] Fix Deployment & remove mock evm --- packages/evm/.gitignore | 21 --- packages/evm/LICENSE.md | 125 --------------- packages/evm/README.md | 1 - packages/evm/contracts/CRISPVoting.sol | 149 ------------------ packages/evm/deploy/deploy.ts | 18 --- packages/evm/hardhat.config.ts | 46 ------ packages/evm/package.json | 85 ---------- packages/evm/tsconfig.json | 22 --- packages/evm_base/contracts/CRISPBase.sol | 2 + packages/risc0/script/Deploy.s.sol | 10 ++ packages/risc0/script/config.toml | 7 +- .../src/enclave_server/blockchain/handlers.rs | 5 +- 12 files changed, 20 insertions(+), 471 deletions(-) delete mode 100644 packages/evm/.gitignore delete mode 100644 packages/evm/LICENSE.md delete mode 100644 packages/evm/README.md delete mode 100644 packages/evm/contracts/CRISPVoting.sol delete mode 100644 packages/evm/deploy/deploy.ts delete mode 100644 packages/evm/hardhat.config.ts delete mode 100644 packages/evm/package.json delete mode 100644 packages/evm/tsconfig.json diff --git a/packages/evm/.gitignore b/packages/evm/.gitignore deleted file mode 100644 index 18a269e..0000000 --- a/packages/evm/.gitignore +++ /dev/null @@ -1,21 +0,0 @@ -# directories -.coverage_artifacts -.coverage_cache -.coverage_contracts -artifacts -build -cache -coverage -dist -node_modules -types -deployments - -# files -*.env -*.log -.DS_Store -.pnp.* -coverage.json -package-lock.json -yarn.lock diff --git a/packages/evm/LICENSE.md b/packages/evm/LICENSE.md deleted file mode 100644 index 7f9dcfa..0000000 --- a/packages/evm/LICENSE.md +++ /dev/null @@ -1,125 +0,0 @@ -GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - -Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute -verbatim copies of this license document, but changing it is not allowed. - -This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU -General Public License, supplemented by the additional permissions listed below. - -0. Additional Definitions. - -As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to -version 3 of the GNU General Public License. - -"The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined -below. - -An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on -the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by -the Library. - -A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of -the Library with which the Combined Work was made is also called the "Linked Version". - -The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding -any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not -on the Linked Version. - -The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, -including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the -System Libraries of the Combined Work. - -1. Exception to Section 3 of the GNU GPL. - -You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. - -2. Conveying Modified Versions. - -If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied -by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may -convey a copy of the modified version: - -a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not -supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, -or - -b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. - -3. Object Code Incorporating Material from Library Header Files. - -The object code form of an Application may incorporate material from a header file that is part of the Library. You may -convey such object code under terms of your choice, provided that, if the incorporated material is not limited to -numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or -fewer lines in length), you do both of the following: - -a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its -use are covered by this License. - -b) Accompany the object code with a copy of the GNU GPL and this license document. - -4. Combined Works. - -You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification -of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, -if you also do each of the following: - -a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its -use are covered by this License. - -b) Accompany the Combined Work with a copy of the GNU GPL and this license document. - -c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library -among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. - -d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - -e) Provide Installation Information, but only if you would otherwise be required to provide such information under -section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified -version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked -Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and -Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner -specified by section 6 of the GNU GPL for conveying Corresponding Source.) - -5. Combined Libraries. - -You may place library facilities that are a work based on the Library side by side in a single library together with -other library facilities that are not Applications and are not covered by this License, and convey such a combined -library under terms of your choice, if you do both of the following: - -a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library -facilities, conveyed under the terms of this License. - -b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where -to find the accompanying uncombined form of the same work. - -6. Revised Versions of the GNU Lesser General Public License. - -The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time -to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new -problems or concerns. - -Each version is given a distinguishing version number. If the Library as you received it specifies that a certain -numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of -following the terms and conditions either of that published version or of any later version published by the Free -Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General -Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software -Foundation. - -If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General -Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for -you to choose that version for the Library. diff --git a/packages/evm/README.md b/packages/evm/README.md deleted file mode 100644 index b04c573..0000000 --- a/packages/evm/README.md +++ /dev/null @@ -1 +0,0 @@ -# RFV Contracts \ No newline at end of file diff --git a/packages/evm/contracts/CRISPVoting.sol b/packages/evm/contracts/CRISPVoting.sol deleted file mode 100644 index ecb7c84..0000000 --- a/packages/evm/contracts/CRISPVoting.sol +++ /dev/null @@ -1,149 +0,0 @@ -// SPDX-License-Identifier: LGPLv3 -pragma solidity ^0.8.20; - -contract CRISPVoting { - struct E3 { - uint256 seed; - uint32[2] threshold; - uint256[2] startWindow; - uint256 duration; - uint256 expiration; - address e3Program; - bytes e3ProgramParams; - address inputValidator; - address decryptionVerifier; - bytes committeePublicKey; - bytes ciphertextOutput; - bytes plaintextOutput; - } - - mapping(uint256 => E3) public e3Polls; // Stores each poll by its e3Id - mapping(uint256 e3Id => uint256 inputCount) public inputCounts; // Stores the number of inputs for each poll - - event E3Activated( - uint256 e3Id, - uint256 expiration, - bytes committeePublicKey - ); - - event InputPublished( - uint256 indexed e3Id, - bytes data, - uint256 inputHash, - uint256 index - ); - - event PlaintextOutputPublished(uint256 indexed e3Id, bytes plaintextOutput); - event CiphertextOutputPublished( - uint256 indexed e3Id, - bytes ciphertextOutput - ); - - uint256 public nexte3Id = 0; // Counter for E3 IDs - - // Request a new E3 computation - function request( - address filter, - uint32[2] calldata threshold, - uint256[2] calldata startWindow, - uint256 duration, - address e3Program, - bytes memory e3ProgramParams, - bytes memory computeProviderParams - ) external payable returns (uint256 e3Id, E3 memory e3) { - nexte3Id++; - - E3 memory newE3 = E3({ - seed: nexte3Id, - threshold: threshold, - startWindow: startWindow, - duration: duration, - expiration: 0, - e3Program: e3Program, - e3ProgramParams: e3ProgramParams, - inputValidator: address(0), - decryptionVerifier: address(0), - committeePublicKey: "", - ciphertextOutput: "", - plaintextOutput: "" - }); - - e3Polls[nexte3Id] = newE3; - - return (nexte3Id, newE3); - } - - // Activate the poll - function activate( - uint256 e3Id, - bytes calldata pubKey - ) external returns (bool success) { - require(e3Polls[e3Id].seed > 0, "E3 ID does not exist."); - require(e3Polls[e3Id].expiration == 0, "Poll already activated."); - - e3Polls[e3Id].expiration = block.timestamp + e3Polls[e3Id].duration; - e3Polls[e3Id].committeePublicKey = abi.encodePacked(keccak256(pubKey)); - - emit E3Activated(e3Id, e3Polls[e3Id].expiration, pubKey); - return true; - } - - // Publish input data to the poll - function publishInput( - uint256 e3Id, - bytes memory data - ) external returns (bool success) { - require(e3Polls[e3Id].expiration > 0, "Poll not activated."); - require( - e3Polls[e3Id].expiration > block.timestamp, - "Poll has expired." - ); - - inputCounts[e3Id]++; - uint256 inputHash = uint256(keccak256(data)); - emit InputPublished(e3Id, data, inputHash, inputCounts[e3Id] - 1); - return true; - } - - // Publish ciphertext output - function publishCiphertextOutput( - uint256 e3Id, - bytes calldata data, - bytes memory proof - ) external returns (bool success) { - require( - e3Polls[e3Id].ciphertextOutput.length == 0, - "Ciphertext already published." - ); - require(proof.length > 0, "Proof is Invalid."); - - e3Polls[e3Id].ciphertextOutput = abi.encodePacked( - keccak256(data) - ); - emit CiphertextOutputPublished(e3Id, data); - return true; - } - - // Publish plaintext output - function publishPlaintextOutput( - uint256 e3Id, - bytes memory data - ) external returns (bool success) { - E3 storage e3 = e3Polls[e3Id]; - require(e3.expiration <= block.timestamp, "Poll is still ongoing."); - require( - e3.ciphertextOutput.length > 0, - "Ciphertext must be published first." - ); - require(e3.plaintextOutput.length == 0, "Plaintext already published."); - - e3.plaintextOutput = data; - emit PlaintextOutputPublished(e3Id, data); - return true; - } - - // Retrieve the full E3 poll data by e3Id - function getE3(uint256 e3Id) external view returns (E3 memory e3) { - return e3Polls[e3Id]; - } -} diff --git a/packages/evm/deploy/deploy.ts b/packages/evm/deploy/deploy.ts deleted file mode 100644 index 26e0405..0000000 --- a/packages/evm/deploy/deploy.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { DeployFunction } from "hardhat-deploy/types"; -import { HardhatRuntimeEnvironment } from "hardhat/types"; - -const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { - const { deployer } = await hre.getNamedAccounts(); - const { deploy } = hre.deployments; - console.log(deployer) - const crispVoting = await deploy("CRISPVoting", { - from: deployer, - args: [], - log: true, - }); - - console.log(`CRISPVoting contract: `, crispVoting.address); -}; -export default func; -func.id = "deploy_crispVoting"; // id required to prevent reexecution -func.tags = ["CRISPVoting"]; \ No newline at end of file diff --git a/packages/evm/hardhat.config.ts b/packages/evm/hardhat.config.ts deleted file mode 100644 index ea504fb..0000000 --- a/packages/evm/hardhat.config.ts +++ /dev/null @@ -1,46 +0,0 @@ -import "@nomicfoundation/hardhat-toolbox"; -import { config as dotenvConfig } from "dotenv"; -import "hardhat-deploy"; -import type { HardhatUserConfig } from "hardhat/config"; -import { resolve } from "path"; - -// Load environment variables from .env file -const dotenvConfigPath: string = process.env.DOTENV_CONFIG_PATH || "./.env"; -dotenvConfig({ path: resolve(__dirname, dotenvConfigPath) }); - -// Default configuration for Hardhat network -const config: HardhatUserConfig = { - defaultNetwork: "hardhat", - namedAccounts: { - deployer: 0, - }, - networks: { - hardhat: { - chainId: 31337, // Default Hardhat Network Chain ID - accounts: { - mnemonic: process.env.MNEMONIC || "test test test test test test test test test test test junk", - }, - }, - }, - paths: { - artifacts: "./artifacts", - cache: "./cache", - sources: "./contracts", - tests: "./test", - }, - solidity: { - version: "0.8.21", - settings: { - optimizer: { - enabled: true, - runs: 800, - }, - }, - }, - typechain: { - outDir: "types", - target: "ethers-v6", - }, -}; - -export default config; diff --git a/packages/evm/package.json b/packages/evm/package.json deleted file mode 100644 index de41149..0000000 --- a/packages/evm/package.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "name": "@gnosisguild/GGMI", - "description": "", - "version": "1.0.0", - "author": { - "name": "gnosisguild", - "url": "https://github.com/gnosisguild" - }, - "devDependencies": { - "@nomicfoundation/hardhat-chai-matchers": "^2.0.0", - "@nomicfoundation/hardhat-ethers": "^3.0.0", - "@nomicfoundation/hardhat-network-helpers": "^1.0.6", - "@nomicfoundation/hardhat-toolbox": "^3.0.0", - "@nomicfoundation/hardhat-verify": "^1.0.0", - "@trivago/prettier-plugin-sort-imports": "^4.0.0", - "@typechain/ethers-v6": "^0.4.0", - "@typechain/hardhat": "^8.0.0", - "@types/chai": "^4.3.4", - "@types/fs-extra": "^9.0.13", - "@types/mocha": "^10.0.0", - "@types/node": "^18.11.9", - "@typescript-eslint/eslint-plugin": "^5.44.0", - "@typescript-eslint/parser": "^5.44.0", - "chai": "^4.3.7", - "cross-env": "^7.0.3", - "dotenv": "^16.0.3", - "eslint": "^8.28.0", - "eslint-config-prettier": "^8.5.0", - "ethers": "^6.4.0", - "fs-extra": "^10.1.0", - "hardhat": "^2.12.2", - "hardhat-deploy": "^0.11.29", - "hardhat-gas-reporter": "^1.0.9", - "lodash": "^4.17.21", - "mocha": "^10.1.0", - "prettier": "^2.8.4", - "prettier-plugin-solidity": "^1.1.2", - "rimraf": "^4.1.2", - "solhint": "^3.4.0", - "solhint-plugin-prettier": "^0.0.5", - "solidity-coverage": "^0.8.2", - "ts-generator": "^0.1.1", - "ts-node": "^10.9.1", - "typechain": "^8.2.0", - "typescript": "^4.9.3" - }, - "files": [ - "contracts" - ], - "keywords": [ - "blockchain", - "ethers", - "ethereum", - "hardhat", - "smart-contracts", - "solidity", - "template", - "typescript", - "typechain" - ], - "publishConfig": { - "access": "public" - }, - "scripts": { - "clean": "rimraf ./artifacts ./cache ./coverage ./types ./coverage.json && yarn typechain", - "compile": "cross-env TS_NODE_TRANSPILE_ONLY=true hardhat compile", - "coverage": "hardhat coverage --solcoverjs ./.solcover.js --temp artifacts --testfiles \"test/**/*.ts\" && yarn typechain", - "deploy:contracts": "hardhat deploy", - "deploy": "hardhat deploy --network", - "lint": "yarn lint:sol && yarn lint:ts && yarn prettier:check", - "lint:sol": "solhint --max-warnings 0 \"contracts/**/*.sol\"", - "lint:ts": "eslint --ignore-path ./.eslintignore --ext .js,.ts .", - "postinstall": "DOTENV_CONFIG_PATH=./.env.example yarn typechain", - "prettier:check": "prettier --check \"**/*.{js,json,md,sol,ts,yml}\"", - "prettier:write": "prettier --write \"**/*.{js,json,md,sol,ts,yml}\"", - "task:deployGreeter": "hardhat task:deployGreeter", - "task:setGreeting": "hardhat task:setGreeting", - "test": "hardhat test", - "typechain": "cross-env TS_NODE_TRANSPILE_ONLY=true hardhat typechain" - }, - "dependencies": { - "@openzeppelin/contracts": "^5.0.0", - "hardhat-etherscan": "^1.0.1" - } -} diff --git a/packages/evm/tsconfig.json b/packages/evm/tsconfig.json deleted file mode 100644 index bf1f40a..0000000 --- a/packages/evm/tsconfig.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "compilerOptions": { - "declaration": true, - "declarationMap": true, - "emitDecoratorMetadata": true, - "esModuleInterop": true, - "experimentalDecorators": true, - "forceConsistentCasingInFileNames": true, - "lib": ["es2020"], - "module": "commonjs", - "moduleResolution": "node", - "noImplicitAny": true, - "removeComments": true, - "resolveJsonModule": true, - "sourceMap": true, - "strict": true, - "target": "es2020" - }, - "exclude": ["node_modules"], - "files": ["./hardhat.config.ts"], - "include": ["src/**/*", "tasks/**/*", "deploy/**/*", "types/"] -} diff --git a/packages/evm_base/contracts/CRISPBase.sol b/packages/evm_base/contracts/CRISPBase.sol index d1511b2..9b6ec00 100644 --- a/packages/evm_base/contracts/CRISPBase.sol +++ b/packages/evm_base/contracts/CRISPBase.sol @@ -11,8 +11,10 @@ abstract contract CRISPBase is IE3Program { error E3AlreadyInitialized(); error E3DoesNotExist(); + error EnclaveAddressZero(); function initialize(IEnclave _enclave) public { + require(address(enclave) == address(0), EnclaveAddressZero()); enclave = _enclave; } diff --git a/packages/risc0/script/Deploy.s.sol b/packages/risc0/script/Deploy.s.sol index 1108d08..04abdff 100644 --- a/packages/risc0/script/Deploy.s.sol +++ b/packages/risc0/script/Deploy.s.sol @@ -76,6 +76,13 @@ contract CRISPRisc0Deploy is Script { ) ); verifier = IRiscZeroVerifier(riscZeroVerifierAddress); + + address enclaveAddress = stdToml.readAddress( + config, + string.concat(".profile.", configProfile, ".enclaveAddress") + ); + + enclave = IEnclave(enclaveAddress); } if (address(verifier) == address(0)) { @@ -132,6 +139,9 @@ contract CRISPRisc0Deploy is Script { } function deployCrispRisc0() private { + console2.log("Deploying CRISPRisc0"); + console2.log("Enclave Address: ", address(enclave)); + console2.log("Verifier Address: ", address(verifier)); CRISPRisc0 crisp = new CRISPRisc0(enclave, verifier); console2.log("Deployed CRISPRisc0 to", address(crisp)); } diff --git a/packages/risc0/script/config.toml b/packages/risc0/script/config.toml index db71242..dab4a98 100644 --- a/packages/risc0/script/config.toml +++ b/packages/risc0/script/config.toml @@ -11,6 +11,7 @@ riscZeroVerifierAddress = "0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187" enclaveAddress = "0xE3000000000000000000000000000000000000E3" # You can add additional profiles here -# [profile.custom] -# chainId = 11155111 -# riscZeroVerifierAddress = +[profile.custom] +chainId = 31337 +riscZeroVerifierAddress = "0x0000000000000000000000000000000000000000" +enclaveAddress = "0xE3000000000000000000000000000000000000E3" diff --git a/packages/server/src/enclave_server/blockchain/handlers.rs b/packages/server/src/enclave_server/blockchain/handlers.rs index 0bdb3ff..541e274 100644 --- a/packages/server/src/enclave_server/blockchain/handlers.rs +++ b/packages/server/src/enclave_server/blockchain/handlers.rs @@ -10,10 +10,10 @@ use crate::enclave_server::{ use alloy::rpc::types::Log; use chrono::Utc; use compute_provider::FHEInputs; +use log::info; use std::error::Error; use tokio::time::{sleep, Duration}; use voting_risc0::run_compute; -use log::info; type Result = std::result::Result>; @@ -111,6 +111,9 @@ pub async fn handle_e3(e3_activated: E3Activated, log: Log) -> Result<()> { "CiphertextOutputPublished event published with tx: {:?}", tx.transaction_hash ); + } else { + e3.status = "Finished".to_string(); + save_e3(&e3, &key).await.unwrap(); } info!("E3 request handled successfully."); From 0ee6ccbdc5b67de4a7ad28c9157711620eed8644 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Tue, 24 Sep 2024 22:49:32 +0500 Subject: [PATCH 44/62] Handle Error --- packages/server/src/enclave_server/blockchain/handlers.rs | 4 +--- packages/server/src/enclave_server/mod.rs | 4 +++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/server/src/enclave_server/blockchain/handlers.rs b/packages/server/src/enclave_server/blockchain/handlers.rs index 541e274..759a059 100644 --- a/packages/server/src/enclave_server/blockchain/handlers.rs +++ b/packages/server/src/enclave_server/blockchain/handlers.rs @@ -80,7 +80,7 @@ pub async fn handle_e3(e3_activated: E3Activated, log: Log) -> Result<()> { sleep(Duration::from_secs(e3.duration.to::())).await; // Get All Encrypted Votes - let (e3, _) = get_e3(e3_id).await.unwrap(); + let (mut e3, _) = get_e3(e3_id).await.unwrap(); if e3.vote_count > 0 { info!("E3 FROM DB"); info!("Vote Count: {:?}", e3.vote_count); @@ -96,8 +96,6 @@ pub async fn handle_e3(e3_activated: E3Activated, log: Log) -> Result<()> { .await .unwrap(); - println!("RISC0 Output: {:?}", risc0_output); - // Params will be encoded on chain to create the journal let tx = contract .publish_ciphertext_output( diff --git a/packages/server/src/enclave_server/mod.rs b/packages/server/src/enclave_server/mod.rs index 28f3f2a..c009012 100644 --- a/packages/server/src/enclave_server/mod.rs +++ b/packages/server/src/enclave_server/mod.rs @@ -42,7 +42,9 @@ fn init_logger() { #[actix_web::main] pub async fn start_server() -> Result<(), Box> { init_logger(); - sync_contracts_db().await?; + if let Err(e) = sync_contracts_db().await { + eprintln!("Failed to sync contracts: {:?}", e); + } tokio::spawn(async { if let Err(e) = start_listener(&CONFIG.ws_rpc_url, &CONFIG.contract_address).await { From 9ffc89aedf92e8f8eb8a21b03091b1f48b587fe4 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Tue, 24 Sep 2024 23:11:55 +0500 Subject: [PATCH 45/62] Use Chain ID --- packages/server/src/enclave_server/blockchain/handlers.rs | 2 +- packages/server/src/enclave_server/blockchain/sync.rs | 4 +++- packages/server/src/enclave_server/config.rs | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/server/src/enclave_server/blockchain/handlers.rs b/packages/server/src/enclave_server/blockchain/handlers.rs index 759a059..c610f5a 100644 --- a/packages/server/src/enclave_server/blockchain/handlers.rs +++ b/packages/server/src/enclave_server/blockchain/handlers.rs @@ -40,7 +40,7 @@ pub async fn handle_e3(e3_activated: E3Activated, log: Log) -> Result<()> { let e3_obj = E3 { // Identifiers id: e3_id, - chain_id: 31337 as u64, // Hardcoded for testing + chain_id: CONFIG.chain_id, // Hardcoded for testing enclave_address: CONFIG.contract_address.clone(), // Status-related diff --git a/packages/server/src/enclave_server/blockchain/sync.rs b/packages/server/src/enclave_server/blockchain/sync.rs index f4abc13..a64f540 100644 --- a/packages/server/src/enclave_server/blockchain/sync.rs +++ b/packages/server/src/enclave_server/blockchain/sync.rs @@ -1,7 +1,9 @@ use super::relayer::EnclaveContract; +use crate::enclave_server::config::CONFIG; use crate::enclave_server::database::{get_e3, get_e3_round, save_e3, generate_emoji}; use crate::enclave_server::models::E3; use alloy::primitives::U256; +use alloy::providers::Provider; use chrono::Utc; use eyre::Result; use log::info; @@ -49,7 +51,7 @@ pub async fn sync_contracts_db() -> Result<(), Box Date: Tue, 1 Oct 2024 21:28:23 +0500 Subject: [PATCH 46/62] Ciphernode integration changes --- packages/risc0/apps/src/lib.rs | 14 ++ packages/risc0/contracts/CRISPRisc0.sol | 80 ++++++--- packages/risc0/deployment-guide.md | 1 + packages/risc0/script/Deploy.s.sol | 11 +- packages/risc0/script/config.toml | 3 +- packages/server/.env.example | 14 +- packages/server/src/cli/config.rs | 8 +- packages/server/src/cli/mod.rs | 17 +- packages/server/src/cli/voting.rs | 156 ++++++++++-------- .../src/enclave_server/blockchain/handlers.rs | 17 +- .../src/enclave_server/blockchain/relayer.rs | 26 +-- .../src/enclave_server/blockchain/sync.rs | 3 +- packages/server/src/enclave_server/config.rs | 2 +- .../server/src/enclave_server/database.rs | 18 +- packages/server/src/enclave_server/mod.rs | 3 +- .../src/enclave_server/routes/rounds.rs | 14 +- .../src/enclave_server/routes/voting.rs | 3 +- 17 files changed, 245 insertions(+), 145 deletions(-) diff --git a/packages/risc0/apps/src/lib.rs b/packages/risc0/apps/src/lib.rs index 5115e1c..1ad7770 100644 --- a/packages/risc0/apps/src/lib.rs +++ b/packages/risc0/apps/src/lib.rs @@ -5,6 +5,7 @@ use risc0_ethereum_contracts::groth16; use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts, VerifierContext}; use voting_core::fhe_processor; use serde::{Deserialize, Serialize}; +use std::time::Instant; pub struct Risc0Provider; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -52,7 +53,20 @@ pub fn run_compute(params: FHEInputs) -> Result<(Risc0Output, Vec)> { let mut provider = ComputeManager::new(risc0_provider, params, fhe_processor, false, None); + // Start timer + let start_time = Instant::now(); + let output = provider.start(); + // Capture end time and calculate the duration + let elapsed_time = start_time.elapsed(); + + // Convert the elapsed time to minutes and seconds + let minutes = elapsed_time.as_secs() / 60; + let seconds = elapsed_time.as_secs() % 60; + + println!("Prove function execution time: {} minutes and {} seconds", minutes, seconds); + + Ok(output) } diff --git a/packages/risc0/contracts/CRISPRisc0.sol b/packages/risc0/contracts/CRISPRisc0.sol index d6932e1..080bbd3 100644 --- a/packages/risc0/contracts/CRISPRisc0.sol +++ b/packages/risc0/contracts/CRISPRisc0.sol @@ -4,50 +4,88 @@ pragma solidity >=0.8.27; import {CRISPBase, IEnclave, IE3Program, IInputValidator} from "evm_base/contracts/CRISPBase.sol"; import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; import {ImageID} from "./ImageID.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -contract CRISPRisc0 is CRISPBase { - /// @notice RISC Zero verifier contract address. +contract CRISPRisc0 is CRISPBase, Ownable { + // Constants + bytes32 public constant IMAGE_ID = ImageID.VOTING_ID; // TODO: update this to the CRISP image ID + bytes32 public constant ENCRYPTION_SCHEME_ID = keccak256("fhe.rs:BFV"); + + // State variables IRiscZeroVerifier public verifier; - /// @notice Image ID of the only zkVM binary to accept verification from. - // bytes32 public constant imageId = ImageID.VOTING_ID; // TODO: update this to the CRISP image ID + IInputValidator public inputValidator; + + // Mappings + mapping(uint256 e3Id => bytes32 imageId) public imageIds; + mapping(address => bool) public authorizedContracts; - bytes32 public constant encryptionSchemeId = keccak256("fhe.rs:BFV"); + // Events + event InputValidatorUpdated(address indexed newValidator); - mapping(uint256 e3Ids => bytes32 imageId) public imageIds; + // Errors + error CallerNotAuthorized(); /// @notice Initialize the contract, binding it to a specified RISC Zero verifier. - constructor(IEnclave _enclave, IRiscZeroVerifier _verifier) { - initialize(_enclave, _verifier); + /// @param _enclave The enclave address + /// @param _inputValidator The input validator address + /// @param _verifier The RISC Zero verifier address + constructor( + IEnclave _enclave, + IInputValidator _inputValidator, + IRiscZeroVerifier _verifier + ) Ownable(msg.sender) { + initialize(_enclave, _inputValidator, _verifier); } - function initialize(IEnclave _enclave, IRiscZeroVerifier _verifier) public { + /// @notice Initialize the contract components + /// @param _enclave The enclave address + /// @param _inputValidator The input validator address + /// @param _verifier The RISC Zero verifier address + function initialize( + IEnclave _enclave, + IInputValidator _inputValidator, + IRiscZeroVerifier _verifier + ) public { CRISPBase.initialize(_enclave); + inputValidator = _inputValidator; verifier = _verifier; + authorizedContracts[address(_enclave)] = true; + } + + /// @notice Set a new input validator + /// @param _inputValidator The new input validator address + function setInputValidator(IInputValidator _inputValidator) external onlyOwner { + inputValidator = _inputValidator; + emit InputValidatorUpdated(address(_inputValidator)); } + /// @notice Validate the E3 program parameters + /// @param e3Id The E3 program ID + /// @param e3ProgramParams The E3 program parameters function validate( uint256 e3Id, uint256, bytes calldata e3ProgramParams, bytes calldata ) external override returns (bytes32, IInputValidator) { + require(authorizedContracts[msg.sender] || msg.sender == owner(), CallerNotAuthorized()); require(paramsHashes[e3Id] == bytes32(0), E3AlreadyInitialized()); - (bytes memory params, IInputValidator inputValidator) = abi.decode( - e3ProgramParams, - (bytes, IInputValidator) - ); - paramsHashes[e3Id] = keccak256(params); + paramsHashes[e3Id] = keccak256(e3ProgramParams); - return (encryptionSchemeId, inputValidator); + return (ENCRYPTION_SCHEME_ID, inputValidator); } + /// @notice Verify the proof + /// @param e3Id The E3 program ID + /// @param ciphertextOutputHash The hash of the ciphertext output + /// @param proof The proof to verify function verify( uint256 e3Id, bytes32 ciphertextOutputHash, bytes memory proof ) external view override returns (bool) { - require(paramsHashes[e3Id] != bytes32(0), E3DoesNotExist()); + require(paramsHashes[e3Id] != bytes32(0), "E3 does not exist"); bytes32 inputRoot = bytes32(enclave.getInputRoot(e3Id)); bytes memory seal = abi.decode(proof, (bytes)); @@ -57,10 +95,14 @@ contract CRISPRisc0 is CRISPBase { encodeLengthPrefixAndHash(journal, 132, paramsHashes[e3Id]); encodeLengthPrefixAndHash(journal, 264, inputRoot); - verifier.verify(seal, imageIds[e3Id], sha256(journal)); - return (true); + verifier.verify(seal, IMAGE_ID, sha256(journal)); + return true; } + /// @notice Encode length prefix and hash + /// @param journal The journal to encode into + /// @param startIndex The start index in the journal + /// @param hashVal The hash value to encode function encodeLengthPrefixAndHash(bytes memory journal, uint256 startIndex, bytes32 hashVal) internal pure { journal[startIndex] = 0x20; startIndex += 4; @@ -68,4 +110,4 @@ contract CRISPRisc0 is CRISPBase { journal[startIndex + i * 4] = hashVal[i]; } } -} +} \ No newline at end of file diff --git a/packages/risc0/deployment-guide.md b/packages/risc0/deployment-guide.md index 09da976..5232908 100644 --- a/packages/risc0/deployment-guide.md +++ b/packages/risc0/deployment-guide.md @@ -29,6 +29,7 @@ You can deploy your contracts and run an end-to-end test or demo as follows: ```bash # Anvil sets up a number of default wallets, and this private key is one of them. export ETH_WALLET_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + export ETH_WALLET_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 export BONSAI_API_KEY="YOUR_API_KEY" # see form linked in the previous section export BONSAI_API_URL="BONSAI_API_URL" # provided with your api key ``` diff --git a/packages/risc0/script/Deploy.s.sol b/packages/risc0/script/Deploy.s.sol index 04abdff..c82831f 100644 --- a/packages/risc0/script/Deploy.s.sol +++ b/packages/risc0/script/Deploy.s.sol @@ -24,6 +24,7 @@ import {ControlID} from "risc0/groth16/ControlID.sol"; import {CRISPRisc0} from "../contracts/CRISPRisc0.sol"; import {IEnclave} from "@gnosis-guild/enclave/contracts/interfaces/IEnclave.sol"; +import {IInputValidator} from "@gnosis-guild/enclave/contracts/interfaces/IInputValidator.sol"; /// @notice Deployment script for the RISC Zero starter project. /// @dev Use the following environment variable to control the deployment: @@ -42,6 +43,8 @@ contract CRISPRisc0Deploy is Script { IRiscZeroVerifier verifier; IEnclave enclave; + IInputValidator inputValidator; + function run() external { // Read and log the chainID @@ -83,6 +86,12 @@ contract CRISPRisc0Deploy is Script { ); enclave = IEnclave(enclaveAddress); + + address inputValidatorAddress = stdToml.readAddress( + config, + string.concat(".profile.", configProfile, ".inputValidatorAddress") + ); + inputValidator = IInputValidator(inputValidatorAddress); } if (address(verifier) == address(0)) { @@ -142,7 +151,7 @@ contract CRISPRisc0Deploy is Script { console2.log("Deploying CRISPRisc0"); console2.log("Enclave Address: ", address(enclave)); console2.log("Verifier Address: ", address(verifier)); - CRISPRisc0 crisp = new CRISPRisc0(enclave, verifier); + CRISPRisc0 crisp = new CRISPRisc0(enclave, inputValidator, verifier); console2.log("Deployed CRISPRisc0 to", address(crisp)); } } diff --git a/packages/risc0/script/config.toml b/packages/risc0/script/config.toml index dab4a98..b2d08c1 100644 --- a/packages/risc0/script/config.toml +++ b/packages/risc0/script/config.toml @@ -14,4 +14,5 @@ enclaveAddress = "0xE3000000000000000000000000000000000000E3" [profile.custom] chainId = 31337 riscZeroVerifierAddress = "0x0000000000000000000000000000000000000000" -enclaveAddress = "0xE3000000000000000000000000000000000000E3" +enclaveAddress = "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" # Based on default deployment address using local anvil node +inputValidatorAddress = "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" # Based on default deployment address using local anvil node \ No newline at end of file diff --git a/packages/server/.env.example b/packages/server/.env.example index e966016..f2ae0cb 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -1,4 +1,10 @@ -PRIVATE_KEY=your_private_key_value -RPC_URL=https://your_rpc_url -CONTRACT_ADDRESS=your_contract_address -CHAIN_ID=your_chain_id \ No newline at end of file +PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +ENCLAVE_SERVER_URL=http://0.0.0.0:4000 +HTTP_RPC_URL=http://127.0.0.1:8545 +WS_RPC_URL=ws://127.0.0.1:8545 +CHAIN_ID=31337 + +# Based on Default Hardhat Deployments (Only for testing) +ENCLAVE_ADDRESS=0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 +CIPHERNODE_REGISTRY_OWNABLE_ADDRESS=0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 +NAIVE_REGISTRY_FILTER_ADDRESS=0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 \ No newline at end of file diff --git a/packages/server/src/cli/config.rs b/packages/server/src/cli/config.rs index d4bc0ea..0eab3dd 100644 --- a/packages/server/src/cli/config.rs +++ b/packages/server/src/cli/config.rs @@ -5,10 +5,10 @@ use serde::Deserialize; #[derive(Debug, Deserialize)] pub struct Config { - pub private_key: String, - pub http_rpc_url: String, - pub ws_rpc_url: String, - pub contract_address: String, + pub enclave_server_url: String, + pub enclave_address: String, + pub naive_registry_filter_address: String, + pub e3_program_address: String, } impl Config { diff --git a/packages/server/src/cli/mod.rs b/packages/server/src/cli/mod.rs index 85ef36c..86d93e6 100644 --- a/packages/server/src/cli/mod.rs +++ b/packages/server/src/cli/mod.rs @@ -1,6 +1,6 @@ mod auth; -mod config; mod voting; +mod config; use dialoguer::{theme::ColorfulTheme, FuzzySelect}; use hyper_tls::HttpsConnector; @@ -13,6 +13,7 @@ use http_body_util::Empty; use auth::{authenticate_user, AuthenticationResponse}; use voting::{initialize_crisp_round, participate_in_existing_round, activate_e3_round, decrypt_and_publish_result}; +use config::CONFIG; use serde::{Deserialize, Serialize}; use env_logger::{Builder, Target}; use log::LevelFilter; @@ -20,8 +21,8 @@ use log::info; use std::io::Write; use once_cell::sync::Lazy; -use sled::{Db, IVec}; -use std::{error::Error, str, sync::Arc}; +use sled::Db; +use std::{str, sync::Arc}; use tokio::sync::RwLock; pub static GLOBAL_DB: Lazy>> = Lazy::new(|| { @@ -49,7 +50,7 @@ fn init_logger() { .init(); } -type HyperClientGet = HyperClient, Empty>; +type _HyperClientGet = HyperClient, Empty>; type HyperClientPost = HyperClient, String>; #[derive(Debug, Deserialize, Serialize)] @@ -67,7 +68,7 @@ pub async fn run_cli() -> Result<(), Box> { init_logger(); let https = HttpsConnector::new(); - let client_get = HyperClient::builder(TokioExecutor::new()).build::<_, Empty>(https.clone()); + let _client_get = HyperClient::builder(TokioExecutor::new()).build::<_, Empty>(https.clone()); let client = HyperClient::builder(TokioExecutor::new()).build::<_, String>(https); clear_screen(); @@ -85,13 +86,13 @@ pub async fn run_cli() -> Result<(), Box> { match action { 0 => { - initialize_crisp_round(&config).await?; + initialize_crisp_round().await?; } 1 => { - activate_e3_round(&config).await?; + activate_e3_round().await?; } 2 => { - participate_in_existing_round(&config).await?; + participate_in_existing_round(&client).await?; } 3 => { let auth_res = authenticate_user(&config, &client).await?; diff --git a/packages/server/src/cli/voting.rs b/packages/server/src/cli/voting.rs index 23596a5..d8475ab 100644 --- a/packages/server/src/cli/voting.rs +++ b/packages/server/src/cli/voting.rs @@ -4,21 +4,17 @@ use http_body_util::BodyExt; use hyper::{body::Incoming, Method, Request, Response}; use log::info; use serde::{Deserialize, Serialize}; -use std::{env, sync::Arc}; +use std::sync::Arc; +use super::CONFIG; -use alloy::{ - primitives::{Address, Bytes, Keccak256, U256}, - sol_types::{SolCall, SolEvent, SolValue}, -}; +use alloy::primitives::{Address, Bytes, U256}; use crate::enclave_server::blockchain::relayer::EnclaveContract; use crate::cli::{AuthenticationResponse, HyperClientPost, GLOBAL_DB}; use crate::util::timeit::timeit; use fhe::bfv::{BfvParameters, BfvParametersBuilder, Encoding, Plaintext, PublicKey, SecretKey, Ciphertext}; -use fhe_math::rq::Poly; -use fhe_traits::{ - Deserialize as FheDeserialize, DeserializeParametrized, DeserializeWithContext, FheDecoder, +use fhe_traits::{DeserializeParametrized, FheDecoder, FheDecrypter, FheEncoder, FheEncrypter, Serialize as FheSerialize, }; use rand::thread_rng; @@ -73,29 +69,25 @@ async fn get_response_body( Ok(String::from_utf8(body_bytes.to_vec())?) } -pub async fn initialize_crisp_round( - config: &super::CrispConfig, -) -> Result<(), Box> { +pub async fn initialize_crisp_round() -> Result<(), Box> { info!("Starting new CRISP round!"); info!("Initializing Keyshare nodes..."); - let contract = EnclaveContract::new().await?; - - // let tx = contract.get_root(U256::from(1)).await?; - // let bytes: [u8; 32] = tx.to_be_bytes::<32>(); - // println!("Root: {:?}", bytes); - - // let tx = contract.get_e3_params(U256::from(1)).await?; - // println!("E3 Params: {:?}", tx.to_vec()); - // let mut keccak = Keccak256::new(); - // keccak.update(tx.to_vec()); - // let hsh = keccak.finalize(); - // println!("Hash: {:?}", hsh.to_vec()); + let contract = EnclaveContract::new(CONFIG.enclave_address.clone()).await?; + let e3_program: Address = CONFIG.e3_program_address.parse()?; + + info!("Enabling E3 Program..."); + match contract.enable_e3_program(e3_program).await { + Ok(res) => println!("E3 Program enabled. TxHash: {:?}", res.transaction_hash), + Err(e) => println!("Error enabling E3 Program: {:?}", e), + }; + info!("Generating parameters..."); let params = generate_bfv_parameters().unwrap().to_bytes(); - let filter: Address = "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5".parse()?; + + info!("Requesting E3..."); + let filter: Address = CONFIG.naive_registry_filter_address.parse()?; let threshold: [u32; 2] = [1, 2]; let start_window: [U256; 2] = [U256::from(Utc::now().timestamp()), U256::from(Utc::now().timestamp() + 600)]; - let duration: U256 = U256::from(60); - let e3_program: Address = "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5".parse()?; + let duration: U256 = U256::from(150); let e3_params = Bytes::from(params); let compute_provider_params = Bytes::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); let res = contract.request_e3(filter, threshold, start_window, duration, e3_program, e3_params, compute_provider_params).await?; @@ -104,9 +96,7 @@ pub async fn initialize_crisp_round( Ok(()) } -pub async fn activate_e3_round( - config: &super::CrispConfig, -) -> Result<(), Box> { +pub async fn activate_e3_round() -> Result<(), Box> { let input_e3_id: u64 = Input::with_theme(&ColorfulTheme::default()) .with_prompt("Enter CRISP round ID.") .interact_text()?; @@ -114,8 +104,8 @@ pub async fn activate_e3_round( let params = generate_bfv_parameters().unwrap(); let (sk, pk) = generate_keys(¶ms); - let contract = EnclaveContract::new().await?; - + let contract = EnclaveContract::new(CONFIG.enclave_address.clone()).await?; + println!("Public key Len: {:?}", pk.to_bytes().len()); let pk_bytes = Bytes::from(pk.to_bytes()); // Print how many bytes are in the public key println!("Public key bytes: {:?}", pk_bytes.len()); @@ -138,14 +128,73 @@ pub async fn activate_e3_round( )?; db.flush()?; println!("E3 parameters stored in database."); + println!("Public key Len: {:?}", pk.to_bytes().len()); + + + Ok(()) +} + +pub async fn participate_in_existing_round( + client: &HyperClientPost, +) -> Result<(), Box> { + let input_crisp_id: u64 = Input::with_theme(&ColorfulTheme::default()) + .with_prompt("Enter CRISP round ID.") + .interact_text()?; + info!("Voting state Initialized"); + + let response_pk = PKRequest { + round_id: input_crisp_id, + pk_bytes: vec![0], + }; + + let url = format!("{}/get_pk_by_round", CONFIG.enclave_server_url); + let req = Request::builder() + .header("Content-Type", "application/json") + .method(Method::POST) + .uri(url) + .body(serde_json::to_string(&response_pk)?)?; + + let resp = client.request(req).await?; + info!("Response status: {}", resp.status()); + + let body_str = get_response_body(resp).await?; + let pk_res: PKRequest = serde_json::from_str(&body_str)?; + info!( + "Shared Public Key for CRISP round {:?} collected.", + pk_res.round_id + ); + info!("PK Key: {:?}", pk_res.pk_bytes); + + let params = timeit!("Parameters generation", generate_bfv_parameters()?); + let pk_deserialized = PublicKey::from_bytes(&pk_res.pk_bytes, ¶ms)?; + + let vote_choice = get_user_vote()?; + if vote_choice.is_none() { + info!("Exiting voting system. You may choose to vote later."); + return Ok(()); + } + + info!("Encrypting vote."); + let ct = encrypt_vote(vote_choice.unwrap(), &pk_deserialized, ¶ms)?; + info!("Vote encrypted."); + info!("Calling voting contract with encrypted vote."); + + info!("Enclave Address: {:?}", CONFIG.enclave_address); + + let contract = EnclaveContract::new(CONFIG.enclave_address.clone()).await?; + let res = contract + .publish_input(U256::from(input_crisp_id), Bytes::from(ct.to_bytes())) + .await?; + println!("Vote broadcast. TxHash: {:?}", res.transaction_hash); Ok(()) } + pub async fn decrypt_and_publish_result( config: &super::CrispConfig, client: &HyperClientPost, - auth_res: &AuthenticationResponse, + _auth_res: &AuthenticationResponse, ) -> Result<(), Box> { let input_crisp_id: u64 = Input::with_theme(&ColorfulTheme::default()) .with_prompt("Enter CRISP round ID.") @@ -180,8 +229,6 @@ pub async fn decrypt_and_publish_result( let params_bytes = db.get(format!("e3:{}", input_crisp_id))?.ok_or("Key not found")?; let e3_params: FHEParams = serde_json::from_slice(¶ms_bytes)?; let params = timeit!("Parameters generation", generate_bfv_parameters()?); - let pk_deserialized = PublicKey::from_bytes(&e3_params.pk, ¶ms)?; - info!("Public key deserialized."); let sk_deserialized = SecretKey::new(e3_params.sk, ¶ms); info!("Secret key deserialized."); @@ -193,7 +240,7 @@ pub async fn decrypt_and_publish_result( println!("Vote count: {:?}", votes); info!("Calling contract with plaintext output."); - let contract = EnclaveContract::new().await?; + let contract = EnclaveContract::new(CONFIG.enclave_address.clone()).await?; let res = contract .publish_plaintext_output(U256::from(input_crisp_id), Bytes::from(votes.to_be_bytes())) .await?; @@ -202,45 +249,12 @@ pub async fn decrypt_and_publish_result( Ok(()) } -pub async fn participate_in_existing_round( - config: &super::CrispConfig -) -> Result<(), Box> { - let input_crisp_id: u64 = Input::with_theme(&ColorfulTheme::default()) - .with_prompt("Enter CRISP round ID.") - .interact_text()?; - info!("Voting state Initialized"); - - let db = GLOBAL_DB.read().await; - let params_bytes = db.get(format!("e3:{}", input_crisp_id))?.ok_or("Key not found")?; - let e3_params: FHEParams = serde_json::from_slice(¶ms_bytes)?; - let params = timeit!("Parameters generation", generate_bfv_parameters()?); - let pk_deserialized = PublicKey::from_bytes(&e3_params.pk, ¶ms)?; - - let vote_choice = get_user_vote()?; - if vote_choice.is_none() { - info!("Exiting voting system. You may choose to vote later."); - return Ok(()); - } - - info!("Encrypting vote."); - let ct = encrypt_vote(vote_choice.unwrap(), &pk_deserialized, ¶ms)?; - info!("Vote encrypted."); - info!("Calling voting contract with encrypted vote."); - - let contract = EnclaveContract::new().await?; - let res = contract - .publish_input(U256::from(input_crisp_id), Bytes::from(ct.to_bytes())) - .await?; - println!("Vote broadcast. TxHash: {:?}", res.transaction_hash); - - Ok(()) -} fn generate_bfv_parameters( ) -> Result, Box> { - let degree = 4096; - let plaintext_modulus: u64 = 4096; - let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; + let degree = 2048; + let plaintext_modulus: u64 = 1032193; + let moduli = vec![0x3FFFFFFF000001]; Ok(BfvParametersBuilder::new() .set_degree(degree) diff --git a/packages/server/src/enclave_server/blockchain/handlers.rs b/packages/server/src/enclave_server/blockchain/handlers.rs index c610f5a..d134893 100644 --- a/packages/server/src/enclave_server/blockchain/handlers.rs +++ b/packages/server/src/enclave_server/blockchain/handlers.rs @@ -7,7 +7,7 @@ use crate::enclave_server::{ config::CONFIG, database::{generate_emoji, get_e3, increment_e3_round, save_e3}, }; -use alloy::rpc::types::Log; +use alloy:: rpc::types::Log; use chrono::Utc; use compute_provider::FHEInputs; use log::info; @@ -22,7 +22,7 @@ pub async fn handle_e3(e3_activated: E3Activated, log: Log) -> Result<()> { info!("Handling E3 request with id {}", e3_id); // Fetch E3 from the contract - let contract = EnclaveContract::new().await?; + let contract = EnclaveContract::new(CONFIG.enclave_address.clone()).await?; let e3 = contract.get_e3(e3_activated.e3Id).await?; info!("Fetched E3 from the contract."); @@ -41,7 +41,7 @@ pub async fn handle_e3(e3_activated: E3Activated, log: Log) -> Result<()> { // Identifiers id: e3_id, chain_id: CONFIG.chain_id, // Hardcoded for testing - enclave_address: CONFIG.contract_address.clone(), + enclave_address: CONFIG.enclave_address.clone(), // Status-related status: "Active".to_string(), @@ -89,6 +89,7 @@ pub async fn handle_e3(e3_activated: E3Activated, log: Log) -> Result<()> { params: e3.e3_params, ciphertexts: e3.ciphertext_inputs, }; + println!("Starting computation for E3: {}", e3_id); // Call Compute Provider in a separate thread let (risc0_output, ciphertext) = @@ -96,6 +97,10 @@ pub async fn handle_e3(e3_activated: E3Activated, log: Log) -> Result<()> { .await .unwrap(); + println!("Computation completed for E3: {}", e3_id); + println!("Ciphertext: {:?}", ciphertext); + println!("RISC0 Output: {:?}", risc0_output); + // Params will be encoded on chain to create the journal let tx = contract .publish_ciphertext_output( @@ -155,12 +160,14 @@ pub async fn handle_plaintext_output_published( plaintext_output: PlaintextOutputPublished, ) -> Result<()> { info!("Handling PlaintextOutputPublished event..."); - + info!("Plaintext Output: {:?}", plaintext_output); let e3_id = plaintext_output.e3Id.to::(); let (mut e3, key) = get_e3(e3_id).await?; + let decoded: Vec = bincode::deserialize(&plaintext_output.plaintextOutput.to_vec())?; + info!("Decoded plaintext output: {:?}", decoded); e3.plaintext_output = plaintext_output.plaintextOutput.to_vec(); - e3.votes_option_2 = u64::from_be_bytes(e3.plaintext_output.as_slice().try_into().unwrap()); + e3.votes_option_2 = decoded[0]; e3.votes_option_1 = e3.vote_count - e3.votes_option_2; e3.status = "Finished".to_string(); diff --git a/packages/server/src/enclave_server/blockchain/relayer.rs b/packages/server/src/enclave_server/blockchain/relayer.rs index 9043187..8f3f473 100644 --- a/packages/server/src/enclave_server/blockchain/relayer.rs +++ b/packages/server/src/enclave_server/blockchain/relayer.rs @@ -22,6 +22,7 @@ sol! { uint256[2] startWindow; uint256 duration; uint256 expiration; + bytes32 encryptionSchemeId; address e3Program; bytes e3ProgramParams; address inputValidator; @@ -37,19 +38,13 @@ sol! { uint256 public nexte3Id = 0; mapping(uint256 e3Id => uint256 inputCount) public inputCounts; mapping(uint256 e3Id => bytes params) public e3Params; - - function request(address filter, uint32[2] calldata threshold, uint256[2] calldata startWindow, uint256 duration, address e3Program, bytes memory e3ProgramParams, bytes memory computeProviderParams) external payable returns (uint256 e3Id, E3 memory e3); - + function request(address filter, uint32[2] calldata threshold, uint256[2] calldata startWindow, uint256 duration, address e3Program, bytes memory e3ProgramParams, bytes memory computeProviderParams) external payable returns (uint256 e3Id, E3 memory e3); function activate(uint256 e3Id, bytes memory pubKey) external returns (bool success); - - function publishInput(uint256 e3Id, bytes memory data ) external returns (bool success); - + function enableE3Program(address e3Program) public onlyOwner returns (bool success); + function publishInput(uint256 e3Id, bytes memory data) external returns (bool success); function publishCiphertextOutput(uint256 e3Id, bytes memory ciphertextOutput, bytes memory proof) external returns (bool success); - function publishPlaintextOutput(uint256 e3Id, bytes memory data) external returns (bool success); - function getE3(uint256 e3Id) external view returns (E3 memory e3); - function getRoot(uint256 id) public view returns (uint256); } } @@ -70,7 +65,7 @@ pub struct EnclaveContract { } impl EnclaveContract { - pub async fn new() -> Result { + pub async fn new(contract_address: String) -> Result { let signer: PrivateKeySigner = CONFIG.private_key.parse()?; let wallet = EthereumWallet::from(signer.clone()); let provider = ProviderBuilder::new() @@ -81,7 +76,7 @@ impl EnclaveContract { Ok(Self { provider: Arc::new(provider), - contract_address: CONFIG.contract_address.parse()?, + contract_address: contract_address.parse()?, }) } @@ -104,7 +99,7 @@ impl EnclaveContract { e3_program, e3_params, compute_provider_params, - ); + ).value(U256::from(10000000)); let receipt = builder.send().await?.get_receipt().await?; Ok(receipt) } @@ -116,6 +111,13 @@ impl EnclaveContract { Ok(receipt) } + pub async fn enable_e3_program(&self, e3_program: Address) -> Result { + let contract = Enclave::new(self.contract_address, &self.provider); + let builder = contract.enableE3Program(e3_program); + let receipt = builder.send().await?.get_receipt().await?; + Ok(receipt) + } + pub async fn publish_input(&self, e3_id: U256, data: Bytes) -> Result { let contract = Enclave::new(self.contract_address, &self.provider); let builder = contract.publishInput(e3_id, data); diff --git a/packages/server/src/enclave_server/blockchain/sync.rs b/packages/server/src/enclave_server/blockchain/sync.rs index a64f540..0625fdd 100644 --- a/packages/server/src/enclave_server/blockchain/sync.rs +++ b/packages/server/src/enclave_server/blockchain/sync.rs @@ -3,13 +3,12 @@ use crate::enclave_server::config::CONFIG; use crate::enclave_server::database::{get_e3, get_e3_round, save_e3, generate_emoji}; use crate::enclave_server::models::E3; use alloy::primitives::U256; -use alloy::providers::Provider; use chrono::Utc; use eyre::Result; use log::info; pub async fn sync_contracts_db() -> Result<(), Box> { info!("Syncing contracts with database"); - let contract = EnclaveContract::new().await?; + let contract = EnclaveContract::new(CONFIG.enclave_address.clone()).await?; let contract_e3_id = contract.get_e3_id().await?.to::(); let db_e3_id = get_e3_round().await?; diff --git a/packages/server/src/enclave_server/config.rs b/packages/server/src/enclave_server/config.rs index 21500ee..9dd3e37 100644 --- a/packages/server/src/enclave_server/config.rs +++ b/packages/server/src/enclave_server/config.rs @@ -8,7 +8,7 @@ pub struct Config { pub private_key: String, pub http_rpc_url: String, pub ws_rpc_url: String, - pub contract_address: String, + pub enclave_address: String, pub chain_id: u64, } diff --git a/packages/server/src/enclave_server/database.rs b/packages/server/src/enclave_server/database.rs index a381adb..90641aa 100644 --- a/packages/server/src/enclave_server/database.rs +++ b/packages/server/src/enclave_server/database.rs @@ -76,20 +76,14 @@ pub async fn get_e3_round() -> Result> { pub async fn increment_e3_round() -> Result<(), Box> { let key = "e3:round"; - let mut encoded = vec![]; - match get_e3_round().await { - Ok(round_count) => { - let new_round_count = round_count + 1; - encoded = bincode::serialize(&new_round_count).unwrap(); - } - Err(e) => { - return Err(e); - } - } - + let new_round_count = match get_e3_round().await { + Ok(round_count) => round_count + 1, + Err(e) => return Err(e), + }; + let db = GLOBAL_DB.write().await; - db.insert(key, IVec::from(encoded))?; + db.insert(key, IVec::from(bincode::serialize(&new_round_count)?))?; Ok(()) } diff --git a/packages/server/src/enclave_server/mod.rs b/packages/server/src/enclave_server/mod.rs index c009012..155b8d1 100644 --- a/packages/server/src/enclave_server/mod.rs +++ b/packages/server/src/enclave_server/mod.rs @@ -15,7 +15,6 @@ use models::AppState; use config::CONFIG; use env_logger::{Builder, Target}; use log::{LevelFilter, Record}; -use serde::{Deserialize, Serialize}; use std::io::Write; use std::path::Path; @@ -47,7 +46,7 @@ pub async fn start_server() -> Result<(), Box, +) -> impl Responder { + let mut incoming = data.into_inner(); + info!("Request for round {:?} pk", incoming.round_id); + let (state_data, _) = get_e3(incoming.round_id).await.unwrap(); + incoming.pk_bytes = state_data.committee_public_key; + HttpResponse::Ok().json(incoming) +} diff --git a/packages/server/src/enclave_server/routes/voting.rs b/packages/server/src/enclave_server/routes/voting.rs index c0ed525..0467111 100644 --- a/packages/server/src/enclave_server/routes/voting.rs +++ b/packages/server/src/enclave_server/routes/voting.rs @@ -2,6 +2,7 @@ use alloy::primitives::{Bytes, U256}; use actix_web::{web, HttpResponse, Responder}; use log::info; +use crate::enclave_server::config::CONFIG; use crate::enclave_server::database::get_e3; use crate::enclave_server::{ blockchain::relayer::EnclaveContract, @@ -34,7 +35,7 @@ async fn broadcast_enc_vote( let sol_vote = Bytes::from(vote.enc_vote_bytes); let e3_id = U256::from(vote.round_id); - let contract = EnclaveContract::new().await.unwrap(); + let contract = EnclaveContract::new(CONFIG.enclave_address.clone()).await.unwrap(); let tx_hash = match contract.publish_input(e3_id, sol_vote).await { Ok(hash) => hash.transaction_hash.to_string(), Err(e) => { From 3610303f4df4b31d8e36ff72d19c811557b94b9b Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Tue, 1 Oct 2024 21:48:39 +0500 Subject: [PATCH 47/62] Update web-rust params to match the ciphernode setup --- packages/client/libs/wasm/pkg/crisp_web.d.ts | 100 ++- packages/client/libs/wasm/pkg/crisp_web.js | 702 ++++++++++++++++-- .../client/libs/wasm/pkg/crisp_web_bg.wasm | Bin 489830 -> 490998 bytes .../libs/wasm/pkg/crisp_web_bg.wasm.d.ts | 21 +- packages/client/libs/wasm/pkg/package.json | 4 +- packages/server/src/cli/voting.rs | 2 +- .../src/enclave_server/routes/rounds.rs | 2 +- packages/web-rust/src/bin/web_fhe_encrypt.rs | 20 +- 8 files changed, 756 insertions(+), 95 deletions(-) diff --git a/packages/client/libs/wasm/pkg/crisp_web.d.ts b/packages/client/libs/wasm/pkg/crisp_web.d.ts index 2dd3109..982aa2b 100644 --- a/packages/client/libs/wasm/pkg/crisp_web.d.ts +++ b/packages/client/libs/wasm/pkg/crisp_web.d.ts @@ -1,6 +1,40 @@ /* tslint:disable */ /* eslint-disable */ /** +* Handler for `console.log` invocations. +* +* If a test is currently running it takes the `args` array and stringifies +* it and appends it to the current output of the test. Otherwise it passes +* the arguments to the original `console.log` function, psased as +* `original`. +* @param {Array} args +*/ +export function __wbgtest_console_log(args: Array): void; +/** +* Handler for `console.debug` invocations. See above. +* @param {Array} args +*/ +export function __wbgtest_console_debug(args: Array): void; +/** +* Handler for `console.info` invocations. See above. +* @param {Array} args +*/ +export function __wbgtest_console_info(args: Array): void; +/** +* Handler for `console.warn` invocations. See above. +* @param {Array} args +*/ +export function __wbgtest_console_warn(args: Array): void; +/** +* Handler for `console.error` invocations. See above. +* @param {Array} args +*/ +export function __wbgtest_console_error(args: Array): void; +/** +* @returns {Uint8Array | undefined} +*/ +export function __wbgtest_cov_dump(): Uint8Array | undefined; +/** */ export class Encrypt { free(): void; @@ -17,19 +51,73 @@ export class Encrypt { */ static test(): void; } +/** +* Runtime test harness support instantiated in JS. +* +* The node.js entry script instantiates a `Context` here which is used to +* drive test execution. +*/ +export class WasmBindgenTestContext { + free(): void; +/** +* Creates a new context ready to run tests. +* +* A `Context` is the main structure through which test execution is +* coordinated, and this will collect output and results for all executed +* tests. +*/ + constructor(); +/** +* Inform this context about runtime arguments passed to the test +* harness. +* @param {any[]} args +*/ + args(args: any[]): void; +/** +* Executes a list of tests, returning a promise representing their +* eventual completion. +* +* This is the main entry point for executing tests. All the tests passed +* in are the JS `Function` object that was plucked off the +* `WebAssembly.Instance` exports list. +* +* The promise returned resolves to either `true` if all tests passed or +* `false` if at least one test failed. +* @param {any[]} tests +* @returns {Promise} +*/ + run(tests: any[]): Promise; +} export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; export interface InitOutput { readonly memory: WebAssembly.Memory; - readonly __wbg_encrypt_free: (a: number) => void; + readonly __wbg_encrypt_free: (a: number, b: number) => void; readonly encrypt_new: () => number; readonly encrypt_encrypt_vote: (a: number, b: number, c: number, d: number, e: number) => void; + readonly __wbgt_test_encrypt_vote_0: (a: number) => void; readonly encrypt_test: () => void; - readonly __wbindgen_add_to_stack_pointer: (a: number) => number; + readonly __wbg_wasmbindgentestcontext_free: (a: number, b: number) => void; + readonly wasmbindgentestcontext_new: () => number; + readonly wasmbindgentestcontext_args: (a: number, b: number, c: number) => void; + readonly wasmbindgentestcontext_run: (a: number, b: number, c: number) => number; + readonly __wbgtest_console_log: (a: number) => void; + readonly __wbgtest_console_debug: (a: number) => void; + readonly __wbgtest_console_info: (a: number) => void; + readonly __wbgtest_console_warn: (a: number) => void; + readonly __wbgtest_console_error: (a: number) => void; + readonly __wbgtest_cov_dump: (a: number) => void; readonly __wbindgen_malloc: (a: number, b: number) => number; + readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; + readonly __wbindgen_export_2: WebAssembly.Table; + readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__he113566e5555d502: (a: number, b: number, c: number) => void; + readonly __wbindgen_add_to_stack_pointer: (a: number) => number; readonly __wbindgen_free: (a: number, b: number, c: number) => void; + readonly wasm_bindgen__convert__closures__invoke0_mut__ha21cbda765bd45f6: (a: number, b: number) => void; readonly __wbindgen_exn_store: (a: number) => void; + readonly wasm_bindgen__convert__closures__invoke3_mut__h0a13f5b0b2f359e6: (a: number, b: number, c: number, d: number, e: number) => void; + readonly wasm_bindgen__convert__closures__invoke2_mut__hbbd268dd51530abf: (a: number, b: number, c: number, d: number) => void; } export type SyncInitInput = BufferSource | WebAssembly.Module; @@ -37,18 +125,18 @@ export type SyncInitInput = BufferSource | WebAssembly.Module; * Instantiates the given `module`, which can either be bytes or * a precompiled `WebAssembly.Module`. * -* @param {SyncInitInput} module +* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated. * * @returns {InitOutput} */ -export function initSync(module: SyncInitInput): InitOutput; +export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput; /** * If `module_or_path` is {RequestInfo} or {URL}, makes a request and * for everything else, calls `WebAssembly.instantiate` directly. * -* @param {InitInput | Promise} module_or_path +* @param {{ module_or_path: InitInput | Promise }} module_or_path - Passing `InitInput` directly is deprecated. * * @returns {Promise} */ -export default function __wbg_init (module_or_path?: InitInput | Promise): Promise; +export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise } | InitInput | Promise): Promise; diff --git a/packages/client/libs/wasm/pkg/crisp_web.js b/packages/client/libs/wasm/pkg/crisp_web.js index 00f9b8e..bddd647 100644 --- a/packages/client/libs/wasm/pkg/crisp_web.js +++ b/packages/client/libs/wasm/pkg/crisp_web.js @@ -1,29 +1,43 @@ let wasm; +const heap = new Array(128).fill(undefined); + +heap.push(undefined, null, true, false); + +function getObject(idx) { return heap[idx]; } + +let heap_next = heap.length; + +function dropObject(idx) { + if (idx < 132) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; -let cachedUint8Memory0 = null; +let cachedUint8ArrayMemory0 = null; -function getUint8Memory0() { - if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) { - cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); +function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); } - return cachedUint8Memory0; + return cachedUint8ArrayMemory0; } function getStringFromWasm0(ptr, len) { ptr = ptr >>> 0; - return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); } -const heap = new Array(128).fill(undefined); - -heap.push(undefined, null, true, false); - -let heap_next = heap.length; - function addHeapObject(obj) { if (heap_next === heap.length) heap.push(heap.length + 1); const idx = heap_next; @@ -33,41 +47,270 @@ function addHeapObject(obj) { return idx; } -function getObject(idx) { return heap[idx]; } +let WASM_VECTOR_LEN = 0; -function dropObject(idx) { - if (idx < 132) return; - heap[idx] = heap_next; - heap_next = idx; +const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } ); + +const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' + ? function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); } + : function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; +}); -function takeObject(idx) { - const ret = getObject(idx); - dropObject(idx); - return ret; +function passStringToWasm0(arg, malloc, realloc) { + + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8ArrayMemory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + + offset += ret.written; + ptr = realloc(ptr, len, offset, 1) >>> 0; + } + + WASM_VECTOR_LEN = offset; + return ptr; } -let WASM_VECTOR_LEN = 0; +function isLikeNone(x) { + return x === undefined || x === null; +} + +let cachedDataViewMemory0 = null; + +function getDataViewMemory0() { + if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) { + cachedDataViewMemory0 = new DataView(wasm.memory.buffer); + } + return cachedDataViewMemory0; +} + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} + +const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(state => { + wasm.__wbindgen_export_2.get(state.dtor)(state.a, state.b) +}); + +function makeMutClosure(arg0, arg1, dtor, f) { + const state = { a: arg0, b: arg1, cnt: 1, dtor }; + const real = (...args) => { + // First up with a closure we increment the internal reference + // count. This ensures that the Rust closure environment won't + // be deallocated while we're invoking it. + state.cnt++; + const a = state.a; + state.a = 0; + try { + return f(a, state.b, ...args); + } finally { + if (--state.cnt === 0) { + wasm.__wbindgen_export_2.get(state.dtor)(a, state.b); + CLOSURE_DTORS.unregister(state); + } else { + state.a = a; + } + } + }; + real.original = state; + CLOSURE_DTORS.register(real, state, state); + return real; +} +function __wbg_adapter_28(arg0, arg1, arg2) { + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__he113566e5555d502(arg0, arg1, addHeapObject(arg2)); +} function passArray8ToWasm0(arg, malloc) { const ptr = malloc(arg.length * 1, 1) >>> 0; - getUint8Memory0().set(arg, ptr / 1); + getUint8ArrayMemory0().set(arg, ptr / 1); WASM_VECTOR_LEN = arg.length; return ptr; } -let cachedInt32Memory0 = null; +function getArrayU8FromWasm0(ptr, len) { + ptr = ptr >>> 0; + return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len); +} -function getInt32Memory0() { - if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) { - cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); +function passArrayJsValueToWasm0(array, malloc) { + const ptr = malloc(array.length * 4, 4) >>> 0; + const mem = getDataViewMemory0(); + for (let i = 0; i < array.length; i++) { + mem.setUint32(ptr + 4 * i, addHeapObject(array[i]), true); } - return cachedInt32Memory0; + WASM_VECTOR_LEN = array.length; + return ptr; } -function getArrayU8FromWasm0(ptr, len) { - ptr = ptr >>> 0; - return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len); +let stack_pointer = 128; + +function addBorrowedObject(obj) { + if (stack_pointer == 1) throw new Error('out of js stack'); + heap[--stack_pointer] = obj; + return stack_pointer; +} +/** +* Handler for `console.log` invocations. +* +* If a test is currently running it takes the `args` array and stringifies +* it and appends it to the current output of the test. Otherwise it passes +* the arguments to the original `console.log` function, psased as +* `original`. +* @param {Array} args +*/ +export function __wbgtest_console_log(args) { + try { + wasm.__wbgtest_console_log(addBorrowedObject(args)); + } finally { + heap[stack_pointer++] = undefined; + } +} + +/** +* Handler for `console.debug` invocations. See above. +* @param {Array} args +*/ +export function __wbgtest_console_debug(args) { + try { + wasm.__wbgtest_console_debug(addBorrowedObject(args)); + } finally { + heap[stack_pointer++] = undefined; + } +} + +/** +* Handler for `console.info` invocations. See above. +* @param {Array} args +*/ +export function __wbgtest_console_info(args) { + try { + wasm.__wbgtest_console_info(addBorrowedObject(args)); + } finally { + heap[stack_pointer++] = undefined; + } +} + +/** +* Handler for `console.warn` invocations. See above. +* @param {Array} args +*/ +export function __wbgtest_console_warn(args) { + try { + wasm.__wbgtest_console_warn(addBorrowedObject(args)); + } finally { + heap[stack_pointer++] = undefined; + } +} + +/** +* Handler for `console.error` invocations. See above. +* @param {Array} args +*/ +export function __wbgtest_console_error(args) { + try { + wasm.__wbgtest_console_error(addBorrowedObject(args)); + } finally { + heap[stack_pointer++] = undefined; + } +} + +function __wbg_adapter_44(arg0, arg1) { + wasm.wasm_bindgen__convert__closures__invoke0_mut__ha21cbda765bd45f6(arg0, arg1); } function handleError(f, args) { @@ -77,10 +320,37 @@ function handleError(f, args) { wasm.__wbindgen_exn_store(addHeapObject(e)); } } +/** +* @returns {Uint8Array | undefined} +*/ +export function __wbgtest_cov_dump() { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.__wbgtest_cov_dump(retptr); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + let v1; + if (r0 !== 0) { + v1 = getArrayU8FromWasm0(r0, r1).slice(); + wasm.__wbindgen_free(r0, r1 * 1, 1); + } + return v1; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +function __wbg_adapter_108(arg0, arg1, arg2, arg3, arg4) { + wasm.wasm_bindgen__convert__closures__invoke3_mut__h0a13f5b0b2f359e6(arg0, arg1, addHeapObject(arg2), arg3, addHeapObject(arg4)); +} + +function __wbg_adapter_121(arg0, arg1, arg2, arg3) { + wasm.wasm_bindgen__convert__closures__invoke2_mut__hbbd268dd51530abf(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3)); +} const EncryptFinalization = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } - : new FinalizationRegistry(ptr => wasm.__wbg_encrypt_free(ptr >>> 0)); + : new FinalizationRegistry(ptr => wasm.__wbg_encrypt_free(ptr >>> 0, 1)); /** */ export class Encrypt { @@ -94,13 +364,14 @@ export class Encrypt { free() { const ptr = this.__destroy_into_raw(); - wasm.__wbg_encrypt_free(ptr); + wasm.__wbg_encrypt_free(ptr, 0); } /** */ constructor() { const ret = wasm.encrypt_new(); this.__wbg_ptr = ret >>> 0; + EncryptFinalization.register(this, this.__wbg_ptr, this); return this; } /** @@ -114,10 +385,10 @@ export class Encrypt { const ptr0 = passArray8ToWasm0(public_key, wasm.__wbindgen_malloc); const len0 = WASM_VECTOR_LEN; wasm.encrypt_encrypt_vote(retptr, this.__wbg_ptr, vote, ptr0, len0); - var r0 = getInt32Memory0()[retptr / 4 + 0]; - var r1 = getInt32Memory0()[retptr / 4 + 1]; - var r2 = getInt32Memory0()[retptr / 4 + 2]; - var r3 = getInt32Memory0()[retptr / 4 + 3]; + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); if (r3) { throw takeObject(r2); } @@ -135,6 +406,72 @@ export class Encrypt { } } +const WasmBindgenTestContextFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_wasmbindgentestcontext_free(ptr >>> 0, 1)); +/** +* Runtime test harness support instantiated in JS. +* +* The node.js entry script instantiates a `Context` here which is used to +* drive test execution. +*/ +export class WasmBindgenTestContext { + + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + WasmBindgenTestContextFinalization.unregister(this); + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_wasmbindgentestcontext_free(ptr, 0); + } + /** + * Creates a new context ready to run tests. + * + * A `Context` is the main structure through which test execution is + * coordinated, and this will collect output and results for all executed + * tests. + */ + constructor() { + const ret = wasm.wasmbindgentestcontext_new(); + this.__wbg_ptr = ret >>> 0; + WasmBindgenTestContextFinalization.register(this, this.__wbg_ptr, this); + return this; + } + /** + * Inform this context about runtime arguments passed to the test + * harness. + * @param {any[]} args + */ + args(args) { + const ptr0 = passArrayJsValueToWasm0(args, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + wasm.wasmbindgentestcontext_args(this.__wbg_ptr, ptr0, len0); + } + /** + * Executes a list of tests, returning a promise representing their + * eventual completion. + * + * This is the main entry point for executing tests. All the tests passed + * in are the JS `Function` object that was plucked off the + * `WebAssembly.Instance` exports list. + * + * The promise returned resolves to either `true` if all tests passed or + * `false` if at least one test failed. + * @param {any[]} tests + * @returns {Promise} + */ + run(tests) { + const ptr0 = passArrayJsValueToWasm0(tests, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.wasmbindgentestcontext_run(this.__wbg_ptr, ptr0, len0); + return takeObject(ret); + } +} + async function __wbg_load(module, imports) { if (typeof Response === 'function' && module instanceof Response) { if (typeof WebAssembly.instantiateStreaming === 'function') { @@ -169,16 +506,168 @@ async function __wbg_load(module, imports) { function __wbg_get_imports() { const imports = {}; imports.wbg = {}; + imports.wbg.__wbindgen_object_drop_ref = function(arg0) { + takeObject(arg0); + }; imports.wbg.__wbindgen_string_new = function(arg0, arg1) { const ret = getStringFromWasm0(arg0, arg1); return addHeapObject(ret); }; - imports.wbg.__wbindgen_object_drop_ref = function(arg0) { - takeObject(arg0); + imports.wbg.__wbg_log_4c6146472facbfaa = function(arg0, arg1) { + console.log(getStringFromWasm0(arg0, arg1)); + }; + imports.wbg.__wbg_String_e73d90ae9c871912 = function(arg0, arg1) { + const ret = String(getObject(arg1)); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbg_getElementById_8e651e19b1db8af4 = function(arg0, arg1, arg2) { + const ret = getObject(arg0).getElementById(getStringFromWasm0(arg1, arg2)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_settextcontent_f5ce03c2d5452fdb = function(arg0, arg1, arg2) { + getObject(arg0).textContent = getStringFromWasm0(arg1, arg2); + }; + imports.wbg.__wbindgen_string_get = function(arg0, arg1) { + const obj = getObject(arg1); + const ret = typeof(obj) === 'string' ? obj : undefined; + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbindgen_number_new = function(arg0) { + const ret = arg0; + return addHeapObject(ret); + }; + imports.wbg.__wbg_wbgtestinvoke_a9c01fbf474f5d4b = function() { return handleError(function (arg0, arg1) { + try { + var state0 = {a: arg0, b: arg1}; + var cb0 = () => { + const a = state0.a; + state0.a = 0; + try { + return __wbg_adapter_44(a, state0.b, ); + } finally { + state0.a = a; + } + }; + __wbg_test_invoke(cb0); + } finally { + state0.a = state0.b = 0; + } + }, arguments) }; + imports.wbg.__wbg_wbgtestoutputwriteln_af26aa5032b93b71 = function(arg0) { + __wbg_test_output_writeln(takeObject(arg0)); + }; + imports.wbg.__wbindgen_object_clone_ref = function(arg0) { + const ret = getObject(arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_stack_0cf5246a8b98a5c3 = function(arg0) { + const ret = getObject(arg0).stack; + return addHeapObject(ret); + }; + imports.wbg.__wbg_self_91e88697873c977b = function(arg0) { + const ret = getObject(arg0).self; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_constructor_2aca75e2f55853d3 = function(arg0) { + const ret = getObject(arg0).constructor; + return addHeapObject(ret); + }; + imports.wbg.__wbg_name_8e788143eb60943f = function(arg0, arg1) { + const ret = getObject(arg1).name; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbg_Deno_5db6104106b466fc = function(arg0) { + const ret = getObject(arg0).Deno; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_stack_b73d0c34f7b4416b = function(arg0, arg1) { + const ret = getObject(arg1).stack; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbg_static_accessor_DOCUMENT_c9d3ded98505f352 = function() { + const ret = document; + return addHeapObject(ret); + }; + imports.wbg.__wbg_textcontent_9f35c3e14d1b1ad8 = function(arg0, arg1) { + const ret = getObject(arg1).textContent; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbg_stack_b18cfcfd5aef8d27 = function(arg0) { + const ret = getObject(arg0).stack; + return addHeapObject(ret); + }; + imports.wbg.__wbg_new_abda76e883ba8a5f = function() { + const ret = new Error(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_stack_658279fe44541cf6 = function(arg0, arg1) { + const ret = getObject(arg1).stack; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbg_error_f851667af71bcfc6 = function(arg0, arg1) { + let deferred0_0; + let deferred0_1; + try { + deferred0_0 = arg0; + deferred0_1 = arg1; + console.error(getStringFromWasm0(arg0, arg1)); + } finally { + wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); + } + }; + imports.wbg.__wbg_queueMicrotask_48421b3cc9052b68 = function(arg0) { + const ret = getObject(arg0).queueMicrotask; + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_is_function = function(arg0) { + const ret = typeof(getObject(arg0)) === 'function'; + return ret; + }; + imports.wbg.__wbindgen_cb_drop = function(arg0) { + const obj = takeObject(arg0).original; + if (obj.cnt-- == 1) { + obj.a = 0; + return true; + } + const ret = false; + return ret; + }; + imports.wbg.__wbg_queueMicrotask_12a30234db4045d3 = function(arg0) { + queueMicrotask(getObject(arg0)); + }; + imports.wbg.__wbg_debug_5fb96680aecf5dc8 = function(arg0) { + console.debug(getObject(arg0)); + }; + imports.wbg.__wbg_error_8e3928cfb8a43e2b = function(arg0) { + console.error(getObject(arg0)); + }; + imports.wbg.__wbg_info_530a29cb2e4e3304 = function(arg0) { + console.info(getObject(arg0)); }; imports.wbg.__wbg_log_5bb5f88f245d7762 = function(arg0) { console.log(getObject(arg0)); }; + imports.wbg.__wbg_warn_63bbae1730aead09 = function(arg0) { + console.warn(getObject(arg0)); + }; imports.wbg.__wbg_crypto_566d7465cdbb6b7a = function(arg0) { const ret = getObject(arg0).crypto; return addHeapObject(ret); @@ -204,17 +693,13 @@ function __wbg_get_imports() { const ret = typeof(getObject(arg0)) === 'string'; return ret; }; - imports.wbg.__wbg_msCrypto_0b84745e9245cdf6 = function(arg0) { - const ret = getObject(arg0).msCrypto; - return addHeapObject(ret); - }; imports.wbg.__wbg_require_94a9da52636aacbf = function() { return handleError(function () { const ret = module.require; return addHeapObject(ret); }, arguments) }; - imports.wbg.__wbindgen_is_function = function(arg0) { - const ret = typeof(getObject(arg0)) === 'function'; - return ret; + imports.wbg.__wbg_msCrypto_0b84745e9245cdf6 = function(arg0) { + const ret = getObject(arg0).msCrypto; + return addHeapObject(ret); }; imports.wbg.__wbg_randomFillSync_290977693942bf03 = function() { return handleError(function (arg0, arg1) { getObject(arg0).randomFillSync(takeObject(arg1)); @@ -222,31 +707,27 @@ function __wbg_get_imports() { imports.wbg.__wbg_getRandomValues_260cc23a41afad9a = function() { return handleError(function (arg0, arg1) { getObject(arg0).getRandomValues(getObject(arg1)); }, arguments) }; - imports.wbg.__wbg_newnoargs_e258087cd0daa0ea = function(arg0, arg1) { + imports.wbg.__wbg_newnoargs_76313bd6ff35d0f2 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return addHeapObject(ret); }; - imports.wbg.__wbg_call_27c0f87801dedf93 = function() { return handleError(function (arg0, arg1) { + imports.wbg.__wbg_call_1084a111329e68ce = function() { return handleError(function (arg0, arg1) { const ret = getObject(arg0).call(getObject(arg1)); return addHeapObject(ret); }, arguments) }; - imports.wbg.__wbindgen_object_clone_ref = function(arg0) { - const ret = getObject(arg0); - return addHeapObject(ret); - }; - imports.wbg.__wbg_self_ce0dbfc45cf2f5be = function() { return handleError(function () { + imports.wbg.__wbg_self_3093d5d1f7bcb682 = function() { return handleError(function () { const ret = self.self; return addHeapObject(ret); }, arguments) }; - imports.wbg.__wbg_window_c6fb939a7f436783 = function() { return handleError(function () { + imports.wbg.__wbg_window_3bcfc4d31bc012f8 = function() { return handleError(function () { const ret = window.window; return addHeapObject(ret); }, arguments) }; - imports.wbg.__wbg_globalThis_d1e6af4856ba331b = function() { return handleError(function () { + imports.wbg.__wbg_globalThis_86b222e13bdf32ed = function() { return handleError(function () { const ret = globalThis.globalThis; return addHeapObject(ret); }, arguments) }; - imports.wbg.__wbg_global_207b558942527489 = function() { return handleError(function () { + imports.wbg.__wbg_global_e5a3fe56f8be9485 = function() { return handleError(function () { const ret = global.global; return addHeapObject(ret); }, arguments) }; @@ -254,33 +735,91 @@ function __wbg_get_imports() { const ret = getObject(arg0) === undefined; return ret; }; - imports.wbg.__wbg_call_b3ca7c6051f9bec1 = function() { return handleError(function (arg0, arg1, arg2) { + imports.wbg.__wbg_forEach_1778105a4f7c1a63 = function(arg0, arg1, arg2) { + try { + var state0 = {a: arg1, b: arg2}; + var cb0 = (arg0, arg1, arg2) => { + const a = state0.a; + state0.a = 0; + try { + return __wbg_adapter_108(a, state0.b, arg0, arg1, arg2); + } finally { + state0.a = a; + } + }; + getObject(arg0).forEach(cb0); + } finally { + state0.a = state0.b = 0; + } + }; + imports.wbg.__wbg_message_e18bae0a0e2c097a = function(arg0) { + const ret = getObject(arg0).message; + return addHeapObject(ret); + }; + imports.wbg.__wbg_name_ac78212e803c7941 = function(arg0) { + const ret = getObject(arg0).name; + return addHeapObject(ret); + }; + imports.wbg.__wbg_call_89af060b4e1523f2 = function() { return handleError(function (arg0, arg1, arg2) { const ret = getObject(arg0).call(getObject(arg1), getObject(arg2)); return addHeapObject(ret); }, arguments) }; - imports.wbg.__wbg_buffer_12d079cc21e14bdb = function(arg0) { + imports.wbg.__wbg_new_b85e72ed1bfd57f9 = function(arg0, arg1) { + try { + var state0 = {a: arg0, b: arg1}; + var cb0 = (arg0, arg1) => { + const a = state0.a; + state0.a = 0; + try { + return __wbg_adapter_121(a, state0.b, arg0, arg1); + } finally { + state0.a = a; + } + }; + const ret = new Promise(cb0); + return addHeapObject(ret); + } finally { + state0.a = state0.b = 0; + } + }; + imports.wbg.__wbg_resolve_570458cb99d56a43 = function(arg0) { + const ret = Promise.resolve(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_then_95e6edc0f89b73b1 = function(arg0, arg1) { + const ret = getObject(arg0).then(getObject(arg1)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_buffer_b7b08af79b0b0974 = function(arg0) { const ret = getObject(arg0).buffer; return addHeapObject(ret); }; - imports.wbg.__wbg_newwithbyteoffsetandlength_aa4a17c33a06e5cb = function(arg0, arg1, arg2) { + imports.wbg.__wbg_newwithbyteoffsetandlength_8a2cb9ca96b27ec9 = function(arg0, arg1, arg2) { const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0); return addHeapObject(ret); }; - imports.wbg.__wbg_new_63b92bc8671ed464 = function(arg0) { + imports.wbg.__wbg_new_ea1883e1e5e86686 = function(arg0) { const ret = new Uint8Array(getObject(arg0)); return addHeapObject(ret); }; - imports.wbg.__wbg_set_a47bac70306a19a7 = function(arg0, arg1, arg2) { + imports.wbg.__wbg_set_d1e79e2388520f18 = function(arg0, arg1, arg2) { getObject(arg0).set(getObject(arg1), arg2 >>> 0); }; - imports.wbg.__wbg_newwithlength_e9b4878cebadb3d3 = function(arg0) { + imports.wbg.__wbg_newwithlength_ec548f448387c968 = function(arg0) { const ret = new Uint8Array(arg0 >>> 0); return addHeapObject(ret); }; - imports.wbg.__wbg_subarray_a1f73cd4b5b42fe1 = function(arg0, arg1, arg2) { + imports.wbg.__wbg_subarray_7c2e3576afe181d1 = function(arg0, arg1, arg2) { const ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0); return addHeapObject(ret); }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(getObject(arg1)); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; imports.wbg.__wbindgen_throw = function(arg0, arg1) { throw new Error(getStringFromWasm0(arg0, arg1)); }; @@ -288,19 +827,24 @@ function __wbg_get_imports() { const ret = wasm.memory; return addHeapObject(ret); }; + imports.wbg.__wbindgen_closure_wrapper268 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 82, __wbg_adapter_28); + return addHeapObject(ret); + }; return imports; } -function __wbg_init_memory(imports, maybe_memory) { +function __wbg_init_memory(imports, memory) { } function __wbg_finalize_init(instance, module) { wasm = instance.exports; __wbg_init.__wbindgen_wasm_module = module; - cachedInt32Memory0 = null; - cachedUint8Memory0 = null; + cachedDataViewMemory0 = null; + cachedUint8ArrayMemory0 = null; + return wasm; @@ -309,6 +853,12 @@ function __wbg_finalize_init(instance, module) { function initSync(module) { if (wasm !== undefined) return wasm; + + if (typeof module !== 'undefined' && Object.getPrototypeOf(module) === Object.prototype) + ({module} = module) + else + console.warn('using deprecated parameters for `initSync()`; pass a single object instead') + const imports = __wbg_get_imports(); __wbg_init_memory(imports); @@ -322,24 +872,30 @@ function initSync(module) { return __wbg_finalize_init(instance, module); } -async function __wbg_init(input) { +async function __wbg_init(module_or_path) { if (wasm !== undefined) return wasm; - if (typeof input === 'undefined') { - input = new URL('crisp_web_bg.wasm', import.meta.url); + + if (typeof module_or_path !== 'undefined' && Object.getPrototypeOf(module_or_path) === Object.prototype) + ({module_or_path} = module_or_path) + else + console.warn('using deprecated parameters for the initialization function; pass a single object instead') + + if (typeof module_or_path === 'undefined') { + module_or_path = new URL('crisp_web_bg.wasm', import.meta.url); } const imports = __wbg_get_imports(); - if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) { - input = fetch(input); + if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) { + module_or_path = fetch(module_or_path); } __wbg_init_memory(imports); - const { instance, module } = await __wbg_load(await input, imports); + const { instance, module } = await __wbg_load(await module_or_path, imports); return __wbg_finalize_init(instance, module); } -export { initSync } +export { initSync }; export default __wbg_init; diff --git a/packages/client/libs/wasm/pkg/crisp_web_bg.wasm b/packages/client/libs/wasm/pkg/crisp_web_bg.wasm index 5714a6ded7e694206e76fc268c2bcf370c4b5012..7ac35b1d2f4a3eb4df723a259b63c7173c06a616 100644 GIT binary patch literal 490998 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 Result<(), Box impl Responder { match get_e3_round().await { Ok(round_count) => { - let count = RoundCount { round_count: round_count as u32 }; + let count = RoundCount { round_count: round_count as u32 - 1 }; info!("round_count: {}", count.round_count); HttpResponse::Ok().json(count) } diff --git a/packages/web-rust/src/bin/web_fhe_encrypt.rs b/packages/web-rust/src/bin/web_fhe_encrypt.rs index faad1d3..1f6f1ca 100644 --- a/packages/web-rust/src/bin/web_fhe_encrypt.rs +++ b/packages/web-rust/src/bin/web_fhe_encrypt.rs @@ -36,9 +36,9 @@ impl Encrypt { } pub fn encrypt_vote(&mut self, vote: u64, public_key: Vec) -> Result, JsValue> { - let degree = 4096; - let plaintext_modulus: u64 = 65537; - let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; + let degree = 2048; + let plaintext_modulus: u64 = 1032193; + let moduli = vec![0x3FFFFFFF000001]; let params = BfvParametersBuilder::new() .set_degree(degree) @@ -59,10 +59,10 @@ impl Encrypt { .map_err(|e| JsValue::from_str(&format!("Error encrypting vote: {}", e)))?; // Create Greco input validation ZKP proof - let input_val_vectors = - InputValidationVectors::compute(&pt, &u_rns, &e0_rns, &e1_rns, &ct, &pk).map_err( - |e| JsValue::from_str(&format!("Error computing input validation vectors: {}", e)), - )?; + // let input_val_vectors = + // InputValidationVectors::compute(&pt, &u_rns, &e0_rns, &e1_rns, &ct, &pk).map_err( + // |e| JsValue::from_str(&format!("Error computing input validation vectors: {}", e)), + // )?; self.encrypted_vote = ct.to_bytes(); Ok(self.encrypted_vote.clone()) @@ -83,9 +83,9 @@ fn test_encrypt_vote() { // Initialize the logger to print to the browser's console console_log::init_with_level(log::Level::Info).expect("Error initializing logger"); - let degree = 4096; - let plaintext_modulus: u64 = 65537; // Must be co-prime with Q - let moduli = vec![0xffffee001, 0xffffc4001, 0x1ffffe0001]; + let degree = 2048; + let plaintext_modulus: u64 = 1032193; // Must be Co-prime with Q + let moduli = vec![0x3FFFFFFF000001]; let params = BfvParametersBuilder::new() .set_degree(degree) From 42eb28762fe6a23940c0f85c60a31fa92bf0960f Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 2 Oct 2024 01:33:23 +0500 Subject: [PATCH 48/62] Activate e3 after committee key is published --- packages/risc0/contracts/CRISPRisc0.sol | 4 +- packages/server/.env.example | 5 +- packages/server/src/cli/voting.rs | 4 +- .../src/enclave_server/blockchain/events.rs | 18 ++++- .../src/enclave_server/blockchain/handlers.rs | 21 ++++-- .../src/enclave_server/blockchain/listener.rs | 67 +++++++++---------- .../src/enclave_server/blockchain/relayer.rs | 16 ++--- packages/server/src/enclave_server/config.rs | 1 + packages/server/src/enclave_server/mod.rs | 2 +- 9 files changed, 82 insertions(+), 56 deletions(-) diff --git a/packages/risc0/contracts/CRISPRisc0.sol b/packages/risc0/contracts/CRISPRisc0.sol index 080bbd3..2fcd3da 100644 --- a/packages/risc0/contracts/CRISPRisc0.sol +++ b/packages/risc0/contracts/CRISPRisc0.sol @@ -87,15 +87,13 @@ contract CRISPRisc0 is CRISPBase, Ownable { ) external view override returns (bool) { require(paramsHashes[e3Id] != bytes32(0), "E3 does not exist"); bytes32 inputRoot = bytes32(enclave.getInputRoot(e3Id)); - bytes memory seal = abi.decode(proof, (bytes)); - bytes memory journal = new bytes(396); // (32 + 1) * 4 * 3 encodeLengthPrefixAndHash(journal, 0, ciphertextOutputHash); encodeLengthPrefixAndHash(journal, 132, paramsHashes[e3Id]); encodeLengthPrefixAndHash(journal, 264, inputRoot); - verifier.verify(seal, IMAGE_ID, sha256(journal)); + verifier.verify(proof, IMAGE_ID, sha256(journal)); return true; } diff --git a/packages/server/.env.example b/packages/server/.env.example index f2ae0cb..9d02e58 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -6,5 +6,6 @@ CHAIN_ID=31337 # Based on Default Hardhat Deployments (Only for testing) ENCLAVE_ADDRESS=0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 -CIPHERNODE_REGISTRY_OWNABLE_ADDRESS=0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 -NAIVE_REGISTRY_FILTER_ADDRESS=0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 \ No newline at end of file +CIPHERNODE_REGISTRY_ADDRESS=0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 +NAIVE_REGISTRY_FILTER_ADDRESS=0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 +E3_PROGRAM_ADDRESS=0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e diff --git a/packages/server/src/cli/voting.rs b/packages/server/src/cli/voting.rs index 1a0200f..ff2fdde 100644 --- a/packages/server/src/cli/voting.rs +++ b/packages/server/src/cli/voting.rs @@ -87,7 +87,7 @@ pub async fn initialize_crisp_round() -> Result<(), Box Result<(), Box Result<()> { + let event_clone = self.clone(); + tokio::spawn(async move { + if let Err(e) = handle_committee_published(event_clone).await { + eprintln!("Error handling committee published: {:?}", e); + } + }); + + Ok(()) + } +} diff --git a/packages/server/src/enclave_server/blockchain/handlers.rs b/packages/server/src/enclave_server/blockchain/handlers.rs index d134893..bf5d99c 100644 --- a/packages/server/src/enclave_server/blockchain/handlers.rs +++ b/packages/server/src/enclave_server/blockchain/handlers.rs @@ -1,5 +1,5 @@ use super::{ - events::{CiphertextOutputPublished, E3Activated, InputPublished, PlaintextOutputPublished}, + events::{CiphertextOutputPublished, E3Activated, InputPublished, PlaintextOutputPublished, CommitteePublished}, relayer::EnclaveContract, }; use crate::enclave_server::models::E3; @@ -77,7 +77,7 @@ pub async fn handle_e3(e3_activated: E3Activated, log: Log) -> Result<()> { increment_e3_round().await.unwrap(); // Sleep till the E3 expires - sleep(Duration::from_secs(e3.duration.to::())).await; + sleep(Duration::from_secs(e3.duration.to::() + 5)).await; // Get All Encrypted Votes let (mut e3, _) = get_e3(e3_id).await.unwrap(); @@ -98,7 +98,6 @@ pub async fn handle_e3(e3_activated: E3Activated, log: Log) -> Result<()> { .unwrap(); println!("Computation completed for E3: {}", e3_id); - println!("Ciphertext: {:?}", ciphertext); println!("RISC0 Output: {:?}", risc0_output); // Params will be encoded on chain to create the journal @@ -160,19 +159,31 @@ pub async fn handle_plaintext_output_published( plaintext_output: PlaintextOutputPublished, ) -> Result<()> { info!("Handling PlaintextOutputPublished event..."); - info!("Plaintext Output: {:?}", plaintext_output); let e3_id = plaintext_output.e3Id.to::(); let (mut e3, key) = get_e3(e3_id).await?; let decoded: Vec = bincode::deserialize(&plaintext_output.plaintextOutput.to_vec())?; - info!("Decoded plaintext output: {:?}", decoded); e3.plaintext_output = plaintext_output.plaintextOutput.to_vec(); e3.votes_option_2 = decoded[0]; e3.votes_option_1 = e3.vote_count - e3.votes_option_2; e3.status = "Finished".to_string(); + info!("Vote Count: {:?}", e3.vote_count); + info!("Votes Option 1: {:?}", e3.votes_option_1); + info!("Votes Option 2: {:?}", e3.votes_option_2); + save_e3(&e3, &key).await?; info!("PlaintextOutputPublished event handled."); Ok(()) } + +pub async fn handle_committee_published(committee_published: CommitteePublished) -> Result<()> { + info!("Handling CommitteePublished event..."); + info!("Committee Published: {:?}", committee_published); + + let contract = EnclaveContract::new(CONFIG.enclave_address.clone()).await?; + let tx = contract.activate(committee_published.e3Id, committee_published.publicKey).await?; + info!("E3 activated with tx: {:?}", tx.transaction_hash); + Ok(()) +} diff --git a/packages/server/src/enclave_server/blockchain/listener.rs b/packages/server/src/enclave_server/blockchain/listener.rs index 3c9dede..52a4b4b 100644 --- a/packages/server/src/enclave_server/blockchain/listener.rs +++ b/packages/server/src/enclave_server/blockchain/listener.rs @@ -13,22 +13,12 @@ use std::time::Duration; use tokio::time::sleep; use log::{info, error}; -use super::events::{E3Activated, InputPublished, PlaintextOutputPublished, CiphertextOutputPublished}; +use super::events::{E3Activated, InputPublished, PlaintextOutputPublished, CiphertextOutputPublished, CommitteePublished}; pub trait ContractEvent: Send + Sync + 'static { fn process(&self, log: Log) -> Result<()>; } -// impl ContractEvent for T -// where -// T: SolEvent + Debug + Send + Sync + 'static, -// { -// fn process(&self) -> Result<()> { -// println!("Processing event: {:?}", self); -// Ok(()) -// } -// } - pub struct EventListener { provider: Arc>, filter: Filter, @@ -82,11 +72,11 @@ impl EventListener { } } -pub struct ContractManager { +pub struct EnclaveContract { provider: Arc>, } -impl ContractManager { +impl EnclaveContract { pub async fn new(rpc_url: &str) -> Result { let provider = ProviderBuilder::new().on_builtin(rpc_url).await?; Ok(Self { @@ -102,11 +92,13 @@ impl ContractManager { EventListener::new(self.provider.clone(), filter) } } -pub async fn start_listener(rpc_url: &str, contract_address: &str) -> Result<()> { - let address: Address = contract_address.parse()?; + +pub async fn start_listener(rpc_url: &str, enclave_address: &str, registry_address: &str) -> Result<()> { + let enclave_address: Address = enclave_address.parse()?; + let registry_address: Address = registry_address.parse()?; loop { - match run_listener(rpc_url, address).await { + match run_listener(rpc_url, enclave_address, registry_address).await { Ok(_) => { info!("Listener finished successfully. Checking for reconnection..."); }, @@ -119,26 +111,33 @@ pub async fn start_listener(rpc_url: &str, contract_address: &str) -> Result<()> } // Separate function to encapsulate listener logic -async fn run_listener(rpc_url: &str, contract_address: Address) -> Result<()> { - let manager = ContractManager::new(rpc_url).await?; +async fn run_listener(rpc_url: &str, enclave_address: Address, registry_address: Address) -> Result<()> { + let manager = EnclaveContract::new(rpc_url).await?; - let mut listener = manager.add_listener(contract_address); - listener.add_event_handler::(); - listener.add_event_handler::(); - listener.add_event_handler::(); - listener.add_event_handler::(); + let mut enclave_listener = manager.add_listener(enclave_address); + enclave_listener.add_event_handler::(); + enclave_listener.add_event_handler::(); + enclave_listener.add_event_handler::(); + enclave_listener.add_event_handler::(); - loop { - match listener.listen().await { - Ok(_) => { - info!("Listener is still active..."); - } - Err(e) => { - error!("Connection lost or error occurred: {}. Attempting to reconnect...", e); - break; - } + let mut registry_listener = manager.add_listener(registry_address); + registry_listener.add_event_handler::(); + + let enclave_handle = tokio::spawn(async move { + match enclave_listener.listen().await { + Ok(_) => info!("Enclave listener finished"), + Err(e) => error!("Error in enclave listener: {}", e), } - } - + }); + + let registry_handle = tokio::spawn(async move { + match registry_listener.listen().await { + Ok(_) => info!("Registry listener finished"), + Err(e) => error!("Error in registry listener: {}", e), + } + }); + + tokio::try_join!(enclave_handle, registry_handle)?; + Ok(()) } diff --git a/packages/server/src/enclave_server/blockchain/relayer.rs b/packages/server/src/enclave_server/blockchain/relayer.rs index 8f3f473..2055fe9 100644 --- a/packages/server/src/enclave_server/blockchain/relayer.rs +++ b/packages/server/src/enclave_server/blockchain/relayer.rs @@ -39,7 +39,7 @@ sol! { mapping(uint256 e3Id => uint256 inputCount) public inputCounts; mapping(uint256 e3Id => bytes params) public e3Params; function request(address filter, uint32[2] calldata threshold, uint256[2] calldata startWindow, uint256 duration, address e3Program, bytes memory e3ProgramParams, bytes memory computeProviderParams) external payable returns (uint256 e3Id, E3 memory e3); - function activate(uint256 e3Id, bytes memory pubKey) external returns (bool success); + function activate(uint256 e3Id,bytes memory publicKey) external returns (bool success); function enableE3Program(address e3Program) public onlyOwner returns (bool success); function publishInput(uint256 e3Id, bytes memory data) external returns (bool success); function publishCiphertextOutput(uint256 e3Id, bytes memory ciphertextOutput, bytes memory proof) external returns (bool success); @@ -99,15 +99,15 @@ impl EnclaveContract { e3_program, e3_params, compute_provider_params, - ).value(U256::from(10000000)); - let receipt = builder.send().await?.get_receipt().await?; + ).value(U256::from(100)); + let receipt = builder.send().await.unwrap().get_receipt().await.unwrap(); Ok(receipt) } - pub async fn activate_e3(&self, e3_id: U256, pub_key: Bytes) -> Result { + pub async fn activate(&self, e3_id: U256, pub_key: Bytes) -> Result { let contract = Enclave::new(self.contract_address, &self.provider); let builder = contract.activate(e3_id, pub_key); - let receipt = builder.send().await?.get_receipt().await?; + let receipt = builder.send().await.unwrap().get_receipt().await.unwrap(); Ok(receipt) } @@ -121,7 +121,7 @@ impl EnclaveContract { pub async fn publish_input(&self, e3_id: U256, data: Bytes) -> Result { let contract = Enclave::new(self.contract_address, &self.provider); let builder = contract.publishInput(e3_id, data); - let receipt = builder.send().await?.get_receipt().await?; + let receipt = builder.send().await.unwrap().get_receipt().await.unwrap(); Ok(receipt) } @@ -133,7 +133,7 @@ impl EnclaveContract { ) -> Result { let contract = Enclave::new(self.contract_address, &self.provider); let builder = contract.publishCiphertextOutput(e3_id, data, proof); - let receipt = builder.send().await?.get_receipt().await?; + let receipt = builder.send().await.unwrap().get_receipt().await.unwrap(); Ok(receipt) } @@ -144,7 +144,7 @@ impl EnclaveContract { ) -> Result { let contract = Enclave::new(self.contract_address, &self.provider); let builder = contract.publishPlaintextOutput(e3_id, data); - let receipt = builder.send().await?.get_receipt().await?; + let receipt = builder.send().await.unwrap().get_receipt().await.unwrap(); Ok(receipt) } diff --git a/packages/server/src/enclave_server/config.rs b/packages/server/src/enclave_server/config.rs index 9dd3e37..9bd1431 100644 --- a/packages/server/src/enclave_server/config.rs +++ b/packages/server/src/enclave_server/config.rs @@ -9,6 +9,7 @@ pub struct Config { pub http_rpc_url: String, pub ws_rpc_url: String, pub enclave_address: String, + pub ciphernode_registry_address: String, pub chain_id: u64, } diff --git a/packages/server/src/enclave_server/mod.rs b/packages/server/src/enclave_server/mod.rs index 155b8d1..92273ca 100644 --- a/packages/server/src/enclave_server/mod.rs +++ b/packages/server/src/enclave_server/mod.rs @@ -46,7 +46,7 @@ pub async fn start_server() -> Result<(), Box Date: Wed, 2 Oct 2024 20:05:03 +0500 Subject: [PATCH 49/62] feat: e3 request cron --- .gitignore | 5 +- .gitmodules | 6 +- packages/risc0/.gitignore | 4 +- packages/risc0/.gitmodules | 9 - packages/server/Cargo.lock | 159 ++++++++----- packages/server/Cargo.toml | 7 +- packages/server/src/bin/e3_cron.rs | 58 +++++ packages/server/src/cli/auth.rs | 40 ---- packages/server/src/cli/mod.rs | 75 +++--- packages/server/src/cli/voting.rs | 215 +++++------------- .../src/enclave_server/blockchain/handlers.rs | 1 + .../src/enclave_server/blockchain/relayer.rs | 7 + packages/server/src/enclave_server/config.rs | 3 + packages/server/src/enclave_server/models.rs | 5 + .../src/enclave_server/routes/rounds.rs | 86 ++++++- 15 files changed, 350 insertions(+), 330 deletions(-) delete mode 100644 packages/risc0/.gitmodules create mode 100644 packages/server/src/bin/e3_cron.rs delete mode 100644 packages/server/src/cli/auth.rs diff --git a/.gitignore b/.gitignore index 6caa729..fd7cb9b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,4 @@ database *.log .pnp.* package-lock.json -.DS_Store - -packages/risc0/lib -packages/evm_base/lib \ No newline at end of file +.DS_Store \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 34e7a8a..861bb53 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,11 +2,11 @@ path = packages/evm_base/lib/forge-std url = https://github.com/foundry-rs/forge-std [submodule "packages/risc0/lib/forge-std"] - path = lib/forge-std + path = packages/risc0/lib/forge-std url = https://github.com/foundry-rs/forge-std [submodule "packages/risc0/lib/openzeppelin-contracts"] - path = lib/openzeppelin-contracts + path = packages/risc0/lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts [submodule "packages/risc0/lib/risc0-ethereum"] - path = lib/risc0-ethereum + path = packages/risc0/lib/risc0-ethereum url = https://github.com/risc0/risc0-ethereum diff --git a/packages/risc0/.gitignore b/packages/risc0/.gitignore index 6eddd44..d1fb9a4 100644 --- a/packages/risc0/.gitignore +++ b/packages/risc0/.gitignore @@ -9,9 +9,7 @@ broadcast/ contracts/ImageID.sol tests/Elf.sol -# Libraries -lib/ - +haha/ # Dotenv file .env diff --git a/packages/risc0/.gitmodules b/packages/risc0/.gitmodules deleted file mode 100644 index 2500fc6..0000000 --- a/packages/risc0/.gitmodules +++ /dev/null @@ -1,9 +0,0 @@ -[submodule "lib/forge-std"] - path = lib/forge-std - url = https://github.com/foundry-rs/forge-std -[submodule "lib/openzeppelin-contracts"] - path = lib/openzeppelin-contracts - url = https://github.com/OpenZeppelin/openzeppelin-contracts -[submodule "lib/risc0-ethereum"] - path = lib/risc0-ethereum - url = https://github.com/risc0/risc0-ethereum \ No newline at end of file diff --git a/packages/server/Cargo.lock b/packages/server/Cargo.lock index dd12175..a50a3ad 100644 --- a/packages/server/Cargo.lock +++ b/packages/server/Cargo.lock @@ -536,7 +536,7 @@ dependencies = [ "futures-utils-wasm", "lru", "pin-project", - "reqwest 0.12.5", + "reqwest 0.12.8", "serde", "serde_json", "tokio", @@ -600,7 +600,7 @@ dependencies = [ "alloy-transport-ws", "futures", "pin-project", - "reqwest 0.12.5", + "reqwest 0.12.8", "serde", "serde_json", "tokio", @@ -830,7 +830,7 @@ checksum = "2437d145d80ea1aecde8574d2058cceb8b3c9cba05f6aea8e67907c660d46698" dependencies = [ "alloy-json-rpc", "alloy-transport", - "reqwest 0.12.5", + "reqwest 0.12.8", "serde_json", "tower", "tracing", @@ -1594,7 +1594,7 @@ checksum = "b1553c9f015eb3fc4ff1bf2e142fceeb2256768a3c4d94a9486784a6c656484d" dependencies = [ "duplicate", "maybe-async", - "reqwest 0.12.5", + "reqwest 0.12.8", "risc0-groth16", "serde", "thiserror", @@ -1783,7 +1783,7 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -3834,16 +3834,6 @@ dependencies = [ "url 1.7.2", ] -[[package]] -name = "iron-cors" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b02b8856c7f14e443c483e802cf0ce693f3bec19f49d2c9a242b18f88c9b70" -dependencies = [ - "iron", - "log 0.4.22", -] - [[package]] name = "is-terminal" version = "0.4.13" @@ -4664,7 +4654,7 @@ dependencies = [ "libc", "redox_syscall 0.5.1", "smallvec", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -5542,7 +5532,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "sync_wrapper 0.1.2", - "system-configuration", + "system-configuration 0.5.1", "tokio", "tokio-rustls 0.24.1", "tower-service", @@ -5551,20 +5541,22 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "webpki-roots 0.25.4", - "winreg 0.50.0", + "winreg", ] [[package]] name = "reqwest" -version = "0.12.5" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ "base64 0.22.1", "bytes", + "encoding_rs", "futures-channel", "futures-core", "futures-util", + "h2 0.4.4", "http 1.1.0", "http-body 1.0.0", "http-body-util", @@ -5588,6 +5580,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "sync_wrapper 1.0.1", + "system-configuration 0.6.1", "tokio", "tokio-native-tls", "tokio-rustls 0.26.0", @@ -5599,7 +5592,7 @@ dependencies = [ "wasm-streams", "web-sys", "webpki-roots 0.26.3", - "winreg 0.52.0", + "windows-registry", ] [[package]] @@ -5631,7 +5624,6 @@ dependencies = [ "dialoguer", "dotenvy", "env_logger 0.11.5", - "ethers", "eyre", "fhe 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs?branch=feature/greco-integration)", "fhe-math 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs?branch=feature/greco-integration)", @@ -5646,13 +5638,12 @@ dependencies = [ "hyper 1.3.1", "hyper-tls", "hyper-util", - "iron", - "iron-cors", "jwt", "log 0.4.22", "once_cell", "rand 0.8.5", "rand_chacha 0.3.1", + "reqwest 0.12.8", "router", "serde", "serde_json", @@ -6669,6 +6660,9 @@ name = "sync_wrapper" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] [[package]] name = "system-configuration" @@ -6678,7 +6672,18 @@ checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", "core-foundation", - "system-configuration-sys", + "system-configuration-sys 0.5.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.5.0", + "core-foundation", + "system-configuration-sys 0.6.0", ] [[package]] @@ -6691,6 +6696,16 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tap" version = "1.0.1" @@ -7592,7 +7607,37 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", ] [[package]] @@ -7610,7 +7655,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -7630,18 +7675,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -7652,9 +7697,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -7664,9 +7709,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -7676,15 +7721,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -7694,9 +7739,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -7706,9 +7751,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -7718,9 +7763,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -7730,9 +7775,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" @@ -7762,16 +7807,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "winreg" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "ws_stream_wasm" version = "0.7.4" diff --git a/packages/server/Cargo.toml b/packages/server/Cargo.toml index c466a20..2fdbcf2 100644 --- a/packages/server/Cargo.toml +++ b/packages/server/Cargo.toml @@ -16,16 +16,13 @@ compute-provider = { path = "../compute_provider" } voting-risc0 = { path = "../risc0/apps" } rand_chacha = "0.3.1" rand = "0.8.5" -ethers = "2.0" getrandom = { version = "0.2.11", features = ["js"] } -# Ethers' async features rely upon the Tokio async runtime. tokio = { version = "1.37.0", features = ["full"] } bincode = "1.3.3" hyper = { version = "1", features = ["full"] } http-body-util = "0.1" hyper-util = { version = "0.1", features = ["full"] } hyper-tls = "0.6.0" -iron = "0.6.0" router = "0.6.0" walkdir = "2.5.0" dialoguer = { version = "0.11.0", features = ["fuzzy-select"] } @@ -37,7 +34,6 @@ sled = "0.34" once_cell = "1.19.0" async-std = { version = "1", features = ["attributes", "tokio1"] } bytes = "1.6.0" -iron-cors = "0.8.0" headers = "0.4.0" jwt = "0.16.0" hmac = "0.12.1" @@ -53,4 +49,5 @@ futures-util = "0.3" eyre = "0.6" hex = "0.4" dotenvy = "0.15.7" -config = "0.14.0" \ No newline at end of file +config = "0.14.0" +reqwest = { version = "0.12.8", features = ["json"] } \ No newline at end of file diff --git a/packages/server/src/bin/e3_cron.rs b/packages/server/src/bin/e3_cron.rs new file mode 100644 index 0000000..f309610 --- /dev/null +++ b/packages/server/src/bin/e3_cron.rs @@ -0,0 +1,58 @@ +use reqwest::Client; +use serde_json::json; +use std::error::Error; +use std::time::Duration; +use tokio::time::sleep; + +const MAX_RETRIES: u8 = 5; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new(); + let cron_api_key = std::env::var("CRON_API_KEY").unwrap_or_else(|_| "1234567890".to_string()); + let enclave_server_url = std::env::var("ENCLAVE_SERVER_URL").unwrap_or_else(|_| "http://localhost:4000".to_string()); + + loop { + println!("Requesting new E3 round..."); + let mut retries = 0; + let mut success = false; + + while retries < MAX_RETRIES { + let response = client + .post(format!("{}/request_e3_round", enclave_server_url)) + .json(&json!({ + "cron_api_key": cron_api_key + })) + .send() + .await; + + match response { + Ok(res) => { + if res.status().is_success() { + println!("Successfully requested new E3 round"); + success = true; + break; + } else { + println!("Failed to request new E3 round: {:?}", res.text().await?); + } + } + Err(e) => { + println!("Error making request: {:?}", e); + } + } + + retries += 1; + if retries < MAX_RETRIES { + let backoff_time = Duration::from_secs(2u64.pow(retries.into())); + println!("Retrying in {} seconds...", backoff_time.as_secs()); + sleep(backoff_time).await; + } + } + + if !success { + println!("Failed to request new E3 round after {} retries. Skipping for now.", MAX_RETRIES); + } + + sleep(Duration::from_secs(24 * 60 * 60)).await; + } +} diff --git a/packages/server/src/cli/auth.rs b/packages/server/src/cli/auth.rs deleted file mode 100644 index 0317902..0000000 --- a/packages/server/src/cli/auth.rs +++ /dev/null @@ -1,40 +0,0 @@ -use hyper::{Request, Method}; -use http_body_util::BodyExt; -use serde::{Deserialize, Serialize}; -use log::info; -use crate::cli::HyperClientPost; - -#[derive(Debug, Deserialize, Serialize)] -#[allow(non_snake_case)] -pub struct AuthenticationLogin { - pub postId: String, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct AuthenticationResponse { - pub response: String, - pub jwt_token: String, -} - -pub async fn authenticate_user(config: &super::CrispConfig, client: &HyperClientPost) -> Result> { - let user = AuthenticationLogin { - postId: config.authentication_id.clone(), - }; - - let body = serde_json::to_string(&user)?; - let url = format!("{}/authentication_login", config.enclave_address); - - let req = Request::builder() - .header("Content-Type", "application/json") - .method(Method::POST) - .uri(url) - .body(body)?; - - let resp = client.request(req).await?; - let body_bytes = resp.collect().await?.to_bytes(); - let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); - let auth_res: AuthenticationResponse = serde_json::from_str(&body_str).expect("JSON was not well-formatted"); - - info!("Authentication response {:?}", auth_res); - Ok(auth_res) -} diff --git a/packages/server/src/cli/mod.rs b/packages/server/src/cli/mod.rs index 86d93e6..7585bd6 100644 --- a/packages/server/src/cli/mod.rs +++ b/packages/server/src/cli/mod.rs @@ -1,48 +1,44 @@ -mod auth; -mod voting; mod config; +mod voting; use dialoguer::{theme::ColorfulTheme, FuzzySelect}; -use hyper_tls::HttpsConnector; -use hyper_util::{client::legacy::{Client as HyperClient, connect::HttpConnector}, rt::TokioExecutor}; -use bytes::Bytes; -use std::env; -use std::fs::File; -use std::io::Read; -use http_body_util::Empty; - -use auth::{authenticate_user, AuthenticationResponse}; -use voting::{initialize_crisp_round, participate_in_existing_round, activate_e3_round, decrypt_and_publish_result}; +use reqwest::Client; + use config::CONFIG; -use serde::{Deserialize, Serialize}; use env_logger::{Builder, Target}; -use log::LevelFilter; -use log::info; -use std::io::Write; +use log::{info, LevelFilter, Record}; +use serde::{Deserialize, Serialize}; +use voting::{ + activate_e3_round, decrypt_and_publish_result, initialize_crisp_round, + participate_in_existing_round, +}; use once_cell::sync::Lazy; + use sled::Db; -use std::{str, sync::Arc}; +use std::sync::Arc; +use std::path::Path; +use std::io::Write; use tokio::sync::RwLock; pub static GLOBAL_DB: Lazy>> = Lazy::new(|| { - let pathdb = std::env::current_dir() - .unwrap() - .join("database/cli"); + let pathdb = std::env::current_dir().unwrap().join("database/cli"); Arc::new(RwLock::new(sled::open(pathdb).unwrap())) }); - fn init_logger() { let mut builder = Builder::new(); builder - .target(Target::Stdout) // Set target to stdout - .filter(None, LevelFilter::Info) // Set log level to Info - .format(|buf, record| { + .target(Target::Stdout) + .filter(None, LevelFilter::Info) + .format(|buf, record: &Record| { + let file = record.file().unwrap_or("unknown"); + let filename = Path::new(file).file_name().unwrap_or_else(|| file.as_ref()); + writeln!( - buf, // Use `writeln!` correctly with the `buf` + buf, "[{}:{}] - {}", - record.file().unwrap_or("unknown"), + filename.to_string_lossy(), record.line().unwrap_or(0), record.args() ) @@ -50,9 +46,6 @@ fn init_logger() { .init(); } -type _HyperClientGet = HyperClient, Empty>; -type HyperClientPost = HyperClient, String>; - #[derive(Debug, Deserialize, Serialize)] struct CrispConfig { round_id: u32, @@ -63,13 +56,12 @@ struct CrispConfig { enclave_address: String, authentication_id: String, } + #[tokio::main] pub async fn run_cli() -> Result<(), Box> { init_logger(); - let https = HttpsConnector::new(); - let _client_get = HyperClient::builder(TokioExecutor::new()).build::<_, Empty>(https.clone()); - let client = HyperClient::builder(TokioExecutor::new()).build::<_, String>(https); + let client = Client::new(); clear_screen(); @@ -81,7 +73,6 @@ pub async fn run_cli() -> Result<(), Box> { clear_screen(); - let config = read_config()?; let action = select_action()?; match action { @@ -95,8 +86,7 @@ pub async fn run_cli() -> Result<(), Box> { participate_in_existing_round(&client).await?; } 3 => { - let auth_res = authenticate_user(&config, &client).await?; - decrypt_and_publish_result(&config, &client, &auth_res).await?; + decrypt_and_publish_result(&client).await?; } _ => unreachable!(), } @@ -118,18 +108,15 @@ fn select_environment() -> Result Result> { - let selections = &["Initialize new E3 round.", "Activate an E3 round.", "Participate in an E3 round.", "Decrypt Ciphertext & Publish Results"]; + let selections = &[ + "Initialize new E3 round.", + "Activate an E3 round.", + "Participate in an E3 round.", + "Decrypt Ciphertext & Publish Results", + ]; Ok(FuzzySelect::with_theme(&ColorfulTheme::default()) .with_prompt("Create a new CRISP round or participate in an existing round.") .default(0) .items(&selections[..]) .interact()?) } - -fn read_config() -> Result> { - let config_path = env::current_dir()?.join("example_config.json"); - let mut file = File::open(config_path)?; - let mut data = String::new(); - file.read_to_string(&mut data)?; - Ok(serde_json::from_str(&data)?) -} \ No newline at end of file diff --git a/packages/server/src/cli/voting.rs b/packages/server/src/cli/voting.rs index ff2fdde..60f10d3 100644 --- a/packages/server/src/cli/voting.rs +++ b/packages/server/src/cli/voting.rs @@ -1,33 +1,18 @@ use chrono::Utc; use dialoguer::{theme::ColorfulTheme, FuzzySelect, Input}; -use http_body_util::BodyExt; -use hyper::{body::Incoming, Method, Request, Response}; -use log::info; use serde::{Deserialize, Serialize}; -use std::sync::Arc; -use super::CONFIG; +use reqwest::Client; +use log::info; use alloy::primitives::{Address, Bytes, U256}; - use crate::enclave_server::blockchain::relayer::EnclaveContract; - -use crate::cli::{AuthenticationResponse, HyperClientPost, GLOBAL_DB}; +use crate::cli::GLOBAL_DB; use crate::util::timeit::timeit; use fhe::bfv::{BfvParameters, BfvParametersBuilder, Encoding, Plaintext, PublicKey, SecretKey, Ciphertext}; -use fhe_traits::{DeserializeParametrized, FheDecoder, - FheDecrypter, FheEncoder, FheEncrypter, Serialize as FheSerialize, -}; +use fhe_traits::{DeserializeParametrized, FheDecoder, FheDecrypter, FheEncoder, FheEncrypter, Serialize as FheSerialize}; use rand::thread_rng; -#[derive(Debug, Deserialize, Serialize)] -struct JsonRequestGetRounds { - response: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct RoundCount { - round_count: u32, -} +use super::CONFIG; #[derive(Debug, Deserialize, Serialize)] struct FHEParams { @@ -48,50 +33,35 @@ struct CTRequest { ct_bytes: Vec, } -#[derive(Debug, Deserialize, Serialize)] -struct EncryptedVote { - round_id: u32, - enc_vote_bytes: Vec, - #[serde(rename = "postId")] - post_id: String, -} - -#[derive(Debug, Deserialize, Serialize)] -struct JsonResponseTxHash { - response: String, - tx_hash: String, -} - -async fn get_response_body( - resp: Response, -) -> Result> { - let body_bytes = resp.collect().await?.to_bytes(); - Ok(String::from_utf8(body_bytes.to_vec())?) -} - pub async fn initialize_crisp_round() -> Result<(), Box> { info!("Starting new CRISP round!"); - info!("Initializing Keyshare nodes..."); let contract = EnclaveContract::new(CONFIG.enclave_address.clone()).await?; let e3_program: Address = CONFIG.e3_program_address.parse()?; info!("Enabling E3 Program..."); - match contract.enable_e3_program(e3_program).await { - Ok(res) => println!("E3 Program enabled. TxHash: {:?}", res.transaction_hash), - Err(e) => println!("Error enabling E3 Program: {:?}", e), - }; - info!("Generating parameters..."); - let params = generate_bfv_parameters().unwrap().to_bytes(); + match contract.is_e3_program_enabled(e3_program).await { + Ok(enabled) => { + if !enabled { + match contract.enable_e3_program(e3_program).await { + Ok(res) => println!("E3 Program enabled. TxHash: {:?}", res.transaction_hash), + Err(e) => println!("Error enabling E3 Program: {:?}", e), + } + } else { + info!("E3 Program already enabled"); + } + } + Err(e) => println!("Error checking E3 Program enabled: {:?}", e), + } - info!("Requesting E3..."); let filter: Address = CONFIG.naive_registry_filter_address.parse()?; let threshold: [u32; 2] = [1, 2]; let start_window: [U256; 2] = [U256::from(Utc::now().timestamp()), U256::from(Utc::now().timestamp() + 600)]; - let duration: U256 = U256::from(50); - let e3_params = Bytes::from(params); + let duration: U256 = U256::from(600); + let e3_params = Bytes::from(generate_bfv_parameters()?.to_bytes()); let compute_provider_params = Bytes::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + let res = contract.request_e3(filter, threshold, start_window, duration, e3_program, e3_params, compute_provider_params).await?; - println!("E3 request sent. TxHash: {:?}", res.transaction_hash); + info!("E3 request sent. TxHash: {:?}", res.transaction_hash); Ok(()) } @@ -100,18 +70,14 @@ pub async fn activate_e3_round() -> Result<(), Box Result<(), Box Result<(), Box> { +pub async fn participate_in_existing_round(client: &Client) -> Result<(), Box> { let input_crisp_id: u64 = Input::with_theme(&ColorfulTheme::default()) .with_prompt("Enter CRISP round ID.") .interact_text()?; - info!("Voting state Initialized"); - - let response_pk = PKRequest { - round_id: input_crisp_id, - pk_bytes: vec![0], - }; let url = format!("{}/get_pk_by_round", CONFIG.enclave_server_url); - let req = Request::builder() - .header("Content-Type", "application/json") - .method(Method::POST) - .uri(url) - .body(serde_json::to_string(&response_pk)?)?; - - let resp = client.request(req).await?; - info!("Response status: {}", resp.status()); - - let body_str = get_response_body(resp).await?; - let pk_res: PKRequest = serde_json::from_str(&body_str)?; - info!( - "Shared Public Key for CRISP round {:?} collected.", - pk_res.round_id - ); - info!("PK Key: {:?}", pk_res.pk_bytes); + let resp = client.post(&url) + .json(&PKRequest { round_id: input_crisp_id, pk_bytes: vec![0] }) + .send() + .await?; + let pk_res: PKRequest = resp.json().await?; let params = timeit!("Parameters generation", generate_bfv_parameters()?); let pk_deserialized = PublicKey::from_bytes(&pk_res.pk_bytes, ¶ms)?; let vote_choice = get_user_vote()?; - if vote_choice.is_none() { - info!("Exiting voting system. You may choose to vote later."); - return Ok(()); + if let Some(vote) = vote_choice { + let ct = encrypt_vote(vote, &pk_deserialized, ¶ms)?; + let contract = EnclaveContract::new(CONFIG.enclave_address.clone()).await?; + let res = contract + .publish_input(U256::from(input_crisp_id), Bytes::from(ct.to_bytes())) + .await?; + info!("Vote broadcast. TxHash: {:?}", res.transaction_hash); } - info!("Encrypting vote."); - let ct = encrypt_vote(vote_choice.unwrap(), &pk_deserialized, ¶ms)?; - info!("Vote encrypted."); - info!("Calling voting contract with encrypted vote."); - - info!("Enclave Address: {:?}", CONFIG.enclave_address); - - let contract = EnclaveContract::new(CONFIG.enclave_address.clone()).await?; - let res = contract - .publish_input(U256::from(input_crisp_id), Bytes::from(ct.to_bytes())) - .await?; - println!("Vote broadcast. TxHash: {:?}", res.transaction_hash); - Ok(()) } - -pub async fn decrypt_and_publish_result( - config: &super::CrispConfig, - client: &HyperClientPost, - _auth_res: &AuthenticationResponse, -) -> Result<(), Box> { +pub async fn decrypt_and_publish_result(client: &Client) -> Result<(), Box> { let input_crisp_id: u64 = Input::with_theme(&ColorfulTheme::default()) .with_prompt("Enter CRISP round ID.") .interact_text()?; - info!("Decryption Initialized"); - // Get final Ciphertext - let response_pk = CTRequest { - round_id: input_crisp_id, - ct_bytes: vec![0], - }; - - let url = format!("{}/get_ct_by_round", config.enclave_address); - let req = Request::builder() - .header("Content-Type", "application/json") - .method(Method::POST) - .uri(url) - .body(serde_json::to_string(&response_pk)?)?; - - let resp = client.request(req).await?; - info!("Response status: {}", resp.status()); + let url = format!("{}/get_ct_by_round", CONFIG.enclave_address); + let resp = client.post(&url) + .json(&CTRequest { round_id: input_crisp_id, ct_bytes: vec![0] }) + .send() + .await?; - let body_str = get_response_body(resp).await?; - let ct_res: CTRequest = serde_json::from_str(&body_str)?; - info!( - "Shared Public Key for CRISP round {:?} collected.", - ct_res.round_id - ); - info!("CT Key: {:?}", ct_res.ct_bytes); + let ct_res: CTRequest = resp.json().await?; let db = GLOBAL_DB.read().await; let params_bytes = db.get(format!("e3:{}", input_crisp_id))?.ok_or("Key not found")?; let e3_params: FHEParams = serde_json::from_slice(¶ms_bytes)?; - let params = timeit!("Parameters generation", generate_bfv_parameters()?); + let params = generate_bfv_parameters()?; let sk_deserialized = SecretKey::new(e3_params.sk, ¶ms); - info!("Secret key deserialized."); let ct = Ciphertext::from_bytes(&ct_res.ct_bytes, ¶ms)?; - info!("Ciphertext deserialized."); - let pt = sk_deserialized.try_decrypt(&ct)?; let votes = Vec::::try_decode(&pt, Encoding::poly())?[0]; - println!("Vote count: {:?}", votes); + info!("Vote count: {:?}", votes); - info!("Calling contract with plaintext output."); let contract = EnclaveContract::new(CONFIG.enclave_address.clone()).await?; let res = contract .publish_plaintext_output(U256::from(input_crisp_id), Bytes::from(votes.to_be_bytes())) .await?; - println!("Vote broadcast. TxHash: {:?}", res.transaction_hash); + info!("Vote broadcast. TxHash: {:?}", res.transaction_hash); Ok(()) } - -fn generate_bfv_parameters( -) -> Result, Box> { - let degree = 2048; - let plaintext_modulus: u64 = 1032193; - let moduli = vec![0x3FFFFFFF000001]; - +fn generate_bfv_parameters() -> Result, Box> { Ok(BfvParametersBuilder::new() - .set_degree(degree) - .set_plaintext_modulus(plaintext_modulus) - .set_moduli(&moduli) + .set_degree(2048) + .set_plaintext_modulus(1032193) + .set_moduli(&[0x3FFFFFFF000001]) .build_arc()?) } -fn generate_keys(params: &Arc) -> (SecretKey, PublicKey) { + +fn generate_keys(params: &std::sync::Arc) -> (SecretKey, PublicKey) { let mut rng = thread_rng(); let sk = SecretKey::random(params, &mut rng); let pk = PublicKey::new(&sk, &mut rng); @@ -288,8 +189,8 @@ fn get_user_vote() -> Result, Box, -) -> Result> { + params: &std::sync::Arc, +) -> Result> { let pt = Plaintext::try_encode(&[vote], Encoding::poly(), params)?; Ok(public_key.try_encrypt(&pt, &mut thread_rng())?) -} +} \ No newline at end of file diff --git a/packages/server/src/enclave_server/blockchain/handlers.rs b/packages/server/src/enclave_server/blockchain/handlers.rs index bf5d99c..c77da0e 100644 --- a/packages/server/src/enclave_server/blockchain/handlers.rs +++ b/packages/server/src/enclave_server/blockchain/handlers.rs @@ -114,6 +114,7 @@ pub async fn handle_e3(e3_activated: E3Activated, log: Log) -> Result<()> { tx.transaction_hash ); } else { + info!("E3 has no votes to decrypt. Setting status to Finished."); e3.status = "Finished".to_string(); save_e3(&e3, &key).await.unwrap(); } diff --git a/packages/server/src/enclave_server/blockchain/relayer.rs b/packages/server/src/enclave_server/blockchain/relayer.rs index 2055fe9..e98be79 100644 --- a/packages/server/src/enclave_server/blockchain/relayer.rs +++ b/packages/server/src/enclave_server/blockchain/relayer.rs @@ -38,6 +38,7 @@ sol! { uint256 public nexte3Id = 0; mapping(uint256 e3Id => uint256 inputCount) public inputCounts; mapping(uint256 e3Id => bytes params) public e3Params; + mapping(address e3Program => bool allowed) public e3Programs; function request(address filter, uint32[2] calldata threshold, uint256[2] calldata startWindow, uint256 duration, address e3Program, bytes memory e3ProgramParams, bytes memory computeProviderParams) external payable returns (uint256 e3Id, E3 memory e3); function activate(uint256 e3Id,bytes memory publicKey) external returns (bool success); function enableE3Program(address e3Program) public onlyOwner returns (bool success); @@ -182,4 +183,10 @@ impl EnclaveContract { let params = contract.e3Params(e3_id).call().await?; Ok(params.params) } + + pub async fn is_e3_program_enabled(&self, e3_program: Address) -> Result { + let contract = Enclave::new(self.contract_address, &self.provider); + let enabled = contract.e3Programs(e3_program).call().await?; + Ok(enabled.allowed) + } } diff --git a/packages/server/src/enclave_server/config.rs b/packages/server/src/enclave_server/config.rs index 9bd1431..85013ce 100644 --- a/packages/server/src/enclave_server/config.rs +++ b/packages/server/src/enclave_server/config.rs @@ -9,8 +9,11 @@ pub struct Config { pub http_rpc_url: String, pub ws_rpc_url: String, pub enclave_address: String, + pub e3_program_address: String, pub ciphernode_registry_address: String, + pub naive_registry_filter_address: String, pub chain_id: u64, + pub cron_api_key: String, } impl Config { diff --git a/packages/server/src/enclave_server/models.rs b/packages/server/src/enclave_server/models.rs index 0c00bfe..bd62173 100644 --- a/packages/server/src/enclave_server/models.rs +++ b/packages/server/src/enclave_server/models.rs @@ -241,3 +241,8 @@ pub struct AuthenticationResponse { pub response: String, pub jwt_token: String, } + +#[derive(Debug, Deserialize)] +pub struct CronRequestE3 { + pub cron_api_key: String, +} \ No newline at end of file diff --git a/packages/server/src/enclave_server/routes/rounds.rs b/packages/server/src/enclave_server/routes/rounds.rs index 0f28e86..de17a49 100644 --- a/packages/server/src/enclave_server/routes/rounds.rs +++ b/packages/server/src/enclave_server/routes/rounds.rs @@ -1,14 +1,41 @@ use log::info; use actix_web::{web, HttpResponse, Responder}; - -use crate::enclave_server::models::{CTRequest, RoundCount, PKRequest}; +use alloy::primitives::{Address, U256, Bytes}; +use chrono::Utc; +use fhe::bfv::BfvParametersBuilder; +use fhe_traits::Serialize; +use crate::enclave_server::blockchain::relayer::EnclaveContract; +use crate::enclave_server::config::CONFIG; +use crate::enclave_server::models::{CTRequest, RoundCount, PKRequest, CronRequestE3, JsonResponse}; use crate::enclave_server::database::{get_e3, get_e3_round}; pub fn setup_routes(config: &mut web::ServiceConfig) { config .route("/get_rounds", web::get().to(get_rounds)) .route("/get_pk_by_round", web::post().to(get_pk_by_round)) - .route("/get_ct_by_round", web::post().to(get_ct_by_round)); + .route("/get_ct_by_round", web::post().to(get_ct_by_round)) + .route("/request_e3_round", web::post().to(request_e3_round)); +} + +async fn request_e3_round( + data: web::Json +) -> impl Responder { + // Check API key + if data.cron_api_key != CONFIG.cron_api_key { + return HttpResponse::Unauthorized().json(JsonResponse { + response: "Invalid API key".to_string(), + }); + } + + // Initialize a new E3 round + match initialize_crisp_round().await { + Ok(_) => HttpResponse::Ok().json(JsonResponse { + response: "New E3 round requested successfully".to_string(), + }), + Err(e) => HttpResponse::InternalServerError().json(JsonResponse { + response: format!("Failed to request new E3 round: {}", e), + }), + } } async fn get_rounds()-> impl Responder { @@ -44,3 +71,56 @@ async fn get_pk_by_round( incoming.pk_bytes = state_data.committee_public_key; HttpResponse::Ok().json(incoming) } + + + +pub async fn initialize_crisp_round() -> Result<(), Box> { + info!("Starting new CRISP round!"); + + let contract = EnclaveContract::new(CONFIG.enclave_address.clone()).await?; + let e3_program: Address = CONFIG.e3_program_address.parse()?; + + // Enable E3 Program + info!("Enabling E3 Program..."); + match contract.is_e3_program_enabled(e3_program).await { + Ok(enabled) => { + if !enabled { + match contract.enable_e3_program(e3_program).await { + Ok(res) => println!("E3 Program enabled. TxHash: {:?}", res.transaction_hash), + Err(e) => println!("Error enabling E3 Program: {:?}", e), + } + } else { + info!("E3 Program already enabled"); + } + } + Err(e) => println!("Error checking E3 Program enabled: {:?}", e), + } + + info!("Generating parameters..."); + let params = generate_bfv_parameters().unwrap().to_bytes(); + + info!("Requesting E3..."); + let filter: Address = CONFIG.naive_registry_filter_address.parse()?; + let threshold: [u32; 2] = [1, 2]; + let start_window: [U256; 2] = [U256::from(Utc::now().timestamp()), U256::from(Utc::now().timestamp() + 600)]; + let duration: U256 = U256::from(600); + let e3_params = Bytes::from(params); + let compute_provider_params = Bytes::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + let res = contract.request_e3(filter, threshold, start_window, duration, e3_program, e3_params, compute_provider_params).await?; + println!("E3 request sent. TxHash: {:?}", res.transaction_hash); + + Ok(()) +} + +fn generate_bfv_parameters( +) -> Result, Box> { + let degree = 2048; + let plaintext_modulus: u64 = 1032193; + let moduli = vec![0x3FFFFFFF000001]; + + Ok(BfvParametersBuilder::new() + .set_degree(degree) + .set_plaintext_modulus(plaintext_modulus) + .set_moduli(&moduli) + .build_arc()?) +} \ No newline at end of file From 110cb1ed6858a3003f57d910748937fbdf470f98 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 2 Oct 2024 20:12:09 +0500 Subject: [PATCH 50/62] Manually added submodules --- packages/evm_base/.gitignore | 2 -- packages/evm_base/lib/forge-std | 1 + packages/risc0/lib/forge-std | 1 + packages/risc0/lib/openzeppelin-contracts | 1 + packages/risc0/lib/risc0-ethereum | 1 + 5 files changed, 4 insertions(+), 2 deletions(-) create mode 160000 packages/evm_base/lib/forge-std create mode 160000 packages/risc0/lib/forge-std create mode 160000 packages/risc0/lib/openzeppelin-contracts create mode 160000 packages/risc0/lib/risc0-ethereum diff --git a/packages/evm_base/.gitignore b/packages/evm_base/.gitignore index badb582..4619f3e 100644 --- a/packages/evm_base/.gitignore +++ b/packages/evm_base/.gitignore @@ -23,5 +23,3 @@ yarn.lock contracts/Elf.sol contracts/ImageID.sol -# libraries -lib/ diff --git a/packages/evm_base/lib/forge-std b/packages/evm_base/lib/forge-std new file mode 160000 index 0000000..ab6de56 --- /dev/null +++ b/packages/evm_base/lib/forge-std @@ -0,0 +1 @@ +Subproject commit ab6de56ed94bed75866c78c62cad882e8a046348 diff --git a/packages/risc0/lib/forge-std b/packages/risc0/lib/forge-std new file mode 160000 index 0000000..ab6de56 --- /dev/null +++ b/packages/risc0/lib/forge-std @@ -0,0 +1 @@ +Subproject commit ab6de56ed94bed75866c78c62cad882e8a046348 diff --git a/packages/risc0/lib/openzeppelin-contracts b/packages/risc0/lib/openzeppelin-contracts new file mode 160000 index 0000000..49cd645 --- /dev/null +++ b/packages/risc0/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit 49cd64565aafa5b8f6863bf60a30ef015861614c diff --git a/packages/risc0/lib/risc0-ethereum b/packages/risc0/lib/risc0-ethereum new file mode 160000 index 0000000..30b69fe --- /dev/null +++ b/packages/risc0/lib/risc0-ethereum @@ -0,0 +1 @@ +Subproject commit 30b69fe161b74c4a75ddf62bda220634e1ac726e From faf1da55184272938c087112f134818aa304928f Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 2 Oct 2024 20:15:43 +0500 Subject: [PATCH 51/62] Switch submodules to specific commit --- packages/evm_base/lib/forge-std | 2 +- packages/risc0/lib/forge-std | 2 +- packages/risc0/lib/openzeppelin-contracts | 2 +- packages/risc0/lib/risc0-ethereum | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/evm_base/lib/forge-std b/packages/evm_base/lib/forge-std index ab6de56..978ac6f 160000 --- a/packages/evm_base/lib/forge-std +++ b/packages/evm_base/lib/forge-std @@ -1 +1 @@ -Subproject commit ab6de56ed94bed75866c78c62cad882e8a046348 +Subproject commit 978ac6fadb62f5f0b723c996f64be52eddba6801 diff --git a/packages/risc0/lib/forge-std b/packages/risc0/lib/forge-std index ab6de56..978ac6f 160000 --- a/packages/risc0/lib/forge-std +++ b/packages/risc0/lib/forge-std @@ -1 +1 @@ -Subproject commit ab6de56ed94bed75866c78c62cad882e8a046348 +Subproject commit 978ac6fadb62f5f0b723c996f64be52eddba6801 diff --git a/packages/risc0/lib/openzeppelin-contracts b/packages/risc0/lib/openzeppelin-contracts index 49cd645..01ef448 160000 --- a/packages/risc0/lib/openzeppelin-contracts +++ b/packages/risc0/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit 49cd64565aafa5b8f6863bf60a30ef015861614c +Subproject commit 01ef448981be9d20ca85f2faf6ebdf591ce409f3 diff --git a/packages/risc0/lib/risc0-ethereum b/packages/risc0/lib/risc0-ethereum index 30b69fe..5fbbc7c 160000 --- a/packages/risc0/lib/risc0-ethereum +++ b/packages/risc0/lib/risc0-ethereum @@ -1 +1 @@ -Subproject commit 30b69fe161b74c4a75ddf62bda220634e1ac726e +Subproject commit 5fbbc7cb44ab37ce438c14c087ba6c4e0a669900 From 28812e698d416b111f33da408f9445d97ebee796 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 2 Oct 2024 20:57:43 +0500 Subject: [PATCH 52/62] Update DB --- .../src/enclave_server/blockchain/handlers.rs | 5 ++--- .../server/src/enclave_server/database.rs | 1 + .../src/enclave_server/routes/rounds.rs | 2 +- .../src/enclave_server/routes/voting.rs | 19 +++++-------------- 4 files changed, 9 insertions(+), 18 deletions(-) diff --git a/packages/server/src/enclave_server/blockchain/handlers.rs b/packages/server/src/enclave_server/blockchain/handlers.rs index c77da0e..d90b115 100644 --- a/packages/server/src/enclave_server/blockchain/handlers.rs +++ b/packages/server/src/enclave_server/blockchain/handlers.rs @@ -45,7 +45,7 @@ pub async fn handle_e3(e3_activated: E3Activated, log: Log) -> Result<()> { // Status-related status: "Active".to_string(), - has_voted: vec!["".to_string()], + has_voted: vec![], vote_count: 0, votes_option_1: 0, votes_option_2: 0, @@ -74,7 +74,6 @@ pub async fn handle_e3(e3_activated: E3Activated, log: Log) -> Result<()> { // Save E3 to the database let key = format!("e3:{}", e3_id); save_e3(&e3_obj, &key).await?; - increment_e3_round().await.unwrap(); // Sleep till the E3 expires sleep(Duration::from_secs(e3.duration.to::() + 5)).await; @@ -118,7 +117,7 @@ pub async fn handle_e3(e3_activated: E3Activated, log: Log) -> Result<()> { e3.status = "Finished".to_string(); save_e3(&e3, &key).await.unwrap(); } - + increment_e3_round().await.unwrap(); info!("E3 request handled successfully."); Ok(()) } diff --git a/packages/server/src/enclave_server/database.rs b/packages/server/src/enclave_server/database.rs index 90641aa..9126e93 100644 --- a/packages/server/src/enclave_server/database.rs +++ b/packages/server/src/enclave_server/database.rs @@ -36,6 +36,7 @@ pub async fn save_e3(e3: &E3, key: &str) -> Result<(), Box (), Err(e) => return Err(format!("Failed to save E3: {}", e).into()), }; + db.flush().unwrap(); Ok(()) } diff --git a/packages/server/src/enclave_server/routes/rounds.rs b/packages/server/src/enclave_server/routes/rounds.rs index de17a49..0dd8157 100644 --- a/packages/server/src/enclave_server/routes/rounds.rs +++ b/packages/server/src/enclave_server/routes/rounds.rs @@ -41,7 +41,7 @@ async fn request_e3_round( async fn get_rounds()-> impl Responder { match get_e3_round().await { Ok(round_count) => { - let count = RoundCount { round_count: round_count as u32 - 1 }; + let count = RoundCount { round_count: round_count as u32 }; info!("round_count: {}", count.round_count); HttpResponse::Ok().json(count) } diff --git a/packages/server/src/enclave_server/routes/voting.rs b/packages/server/src/enclave_server/routes/voting.rs index 0467111..afc84ac 100644 --- a/packages/server/src/enclave_server/routes/voting.rs +++ b/packages/server/src/enclave_server/routes/voting.rs @@ -3,10 +3,10 @@ use actix_web::{web, HttpResponse, Responder}; use log::info; use crate::enclave_server::config::CONFIG; -use crate::enclave_server::database::get_e3; +use crate::enclave_server::database::{get_e3, save_e3}; use crate::enclave_server::{ blockchain::relayer::EnclaveContract, - models::{AppState, EncryptedVote, GetEmojisRequest, JsonResponseTxHash, VoteCountRequest}, + models::{EncryptedVote, GetEmojisRequest, JsonResponseTxHash, VoteCountRequest}, }; pub fn setup_routes(config: &mut web::ServiceConfig) { @@ -20,12 +20,11 @@ pub fn setup_routes(config: &mut web::ServiceConfig) { } async fn broadcast_enc_vote( - data: web::Json, - state: web::Data, + data: web::Json ) -> impl Responder { let vote: EncryptedVote = data.into_inner(); - let (mut state_data, key) = get_e3(vote.round_id as u64).await.unwrap(); + let (mut state_data, key) = get_e3(vote.round_id as u64).await.unwrap(); if state_data.has_voted.contains(&vote.postId) { return HttpResponse::BadRequest().json(JsonResponseTxHash { response: "User has already voted".to_string(), @@ -45,16 +44,8 @@ async fn broadcast_enc_vote( }; state_data.has_voted.push(vote.postId); - // Lock the database for writing - let db = state.db.write().await; - if let Err(e) = db.insert(key, serde_json::to_vec(&state_data).unwrap()) { - info!("Error updating state: {:?}", e); - } + save_e3(&state_data, &key).await.unwrap(); - info!( - "Vote broadcast for round {}: tx_hash {}", - vote.round_id, tx_hash - ); HttpResponse::Ok().json(JsonResponseTxHash { response: "Vote successful".to_string(), tx_hash, From 0fc27cd85a04a6a49ff192a6f800ef7e1e92cf4f Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 2 Oct 2024 20:59:07 +0500 Subject: [PATCH 53/62] Update .env.example --- packages/server/.env.example | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/server/.env.example b/packages/server/.env.example index 9d02e58..352c626 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -1,8 +1,11 @@ +# Private key for the enclave server PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 ENCLAVE_SERVER_URL=http://0.0.0.0:4000 HTTP_RPC_URL=http://127.0.0.1:8545 WS_RPC_URL=ws://127.0.0.1:8545 CHAIN_ID=31337 +# Cron-job API key to trigger new rounds +CRON_API_KEY=1234567890 # Based on Default Hardhat Deployments (Only for testing) ENCLAVE_ADDRESS=0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 From fc2da49280461874f0f2d17925729bab1426fb88 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 2 Oct 2024 21:11:09 +0500 Subject: [PATCH 54/62] Remove unused models --- packages/server/src/cli/mod.rs | 11 --- packages/server/src/enclave_server/models.rs | 93 -------------------- 2 files changed, 104 deletions(-) diff --git a/packages/server/src/cli/mod.rs b/packages/server/src/cli/mod.rs index 7585bd6..b322e29 100644 --- a/packages/server/src/cli/mod.rs +++ b/packages/server/src/cli/mod.rs @@ -7,7 +7,6 @@ use reqwest::Client; use config::CONFIG; use env_logger::{Builder, Target}; use log::{info, LevelFilter, Record}; -use serde::{Deserialize, Serialize}; use voting::{ activate_e3_round, decrypt_and_publish_result, initialize_crisp_round, participate_in_existing_round, @@ -46,16 +45,6 @@ fn init_logger() { .init(); } -#[derive(Debug, Deserialize, Serialize)] -struct CrispConfig { - round_id: u32, - poll_length: u32, - chain_id: u32, - voting_address: String, - ciphernode_count: u32, - enclave_address: String, - authentication_id: String, -} #[tokio::main] pub async fn run_cli() -> Result<(), Box> { diff --git a/packages/server/src/enclave_server/models.rs b/packages/server/src/enclave_server/models.rs index bd62173..ff48133 100644 --- a/packages/server/src/enclave_server/models.rs +++ b/packages/server/src/enclave_server/models.rs @@ -12,11 +12,6 @@ pub struct JsonResponse { pub response: String, } -#[derive(Debug, Deserialize, Serialize)] -pub struct RegisterNodeResponse { - pub response: String, - pub node_index: u32, -} #[derive(Debug, Deserialize, Serialize)] pub struct JsonResponseTxHash { @@ -24,35 +19,12 @@ pub struct JsonResponseTxHash { pub tx_hash: String, } -#[derive(Debug, Deserialize, Serialize)] -pub struct JsonRequest { - pub response: String, - pub pk_share: Vec, - pub id: u32, - pub round_id: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct CrispConfig { - pub round_id: u32, - pub poll_length: u32, - pub chain_id: u32, - pub voting_address: String, - pub ciphernode_count: u32, - pub enclave_address: String, - pub authentication_id: String, -} #[derive(Debug, Deserialize, Serialize)] pub struct RoundCount { pub round_count: u32, } -#[derive(Debug, Deserialize, Serialize)] -pub struct PKShareCount { - pub round_id: u32, - pub share_id: u32, -} #[derive(Debug, Deserialize, Serialize)] pub struct PKRequest { @@ -65,37 +37,12 @@ pub struct CTRequest { pub ct_bytes: Vec, } -#[derive(Debug, Deserialize, Serialize)] -pub struct CRPRequest { - pub round_id: u32, - pub crp_bytes: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct TimestampRequest { - pub round_id: u32, - pub timestamp: i64, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct PollLengthRequest { - pub round_id: u32, - pub poll_length: u32, -} - #[derive(Debug, Deserialize, Serialize)] pub struct VoteCountRequest { pub round_id: u32, pub vote_count: u32, } -#[derive(Debug, Deserialize, Serialize)] -pub struct SKSShareRequest { - pub response: String, - pub sks_share: Vec, - pub index: u32, - pub round_id: u32, -} #[derive(Debug, Deserialize, Serialize)] #[allow(non_snake_case)] @@ -116,26 +63,6 @@ pub struct GetEmojisRequest { pub emojis: [String; 2], } -#[derive(Debug, Deserialize, Serialize)] -pub struct SKSSharePoll { - pub response: String, - pub round_id: u32, - pub ciphernode_count: u32, //TODO: dont need this -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct SKSShareResponse { - pub response: String, - pub round_id: u32, - pub sks_shares: Vec>, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct ReportTallyRequest { - pub round_id: u32, - pub option_1: u32, - pub option_2: u32, -} #[derive(Debug, Deserialize, Serialize)] pub struct WebResultRequest { @@ -204,26 +131,6 @@ pub struct E3StateLite { pub emojis: [String; 2], } -#[derive(Debug, Deserialize, Serialize)] -pub struct Ciphernode { - pub id: u32, - pub pk_share: Vec, - pub sks_share: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct GetCiphernode { - pub round_id: u32, - pub ciphernode_id: u32, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct GetEligibilityRequest { - pub round_id: u32, - pub node_id: u32, - pub is_eligible: bool, - pub reason: String, -} #[derive(Debug, Deserialize, Serialize)] pub struct AuthenticationDB { From 8878492d74a53e76f674d3f7d257dbce03b6dbc3 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 2 Oct 2024 21:15:04 +0500 Subject: [PATCH 55/62] Remove example config --- packages/server/example_config.json | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 packages/server/example_config.json diff --git a/packages/server/example_config.json b/packages/server/example_config.json deleted file mode 100644 index 2814151..0000000 --- a/packages/server/example_config.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "round_id": 0, - "poll_length": 120, - "chain_id": 31337, - "voting_address": "0x5FbDB2315678afecb367f032d93F642f64180aa3", - "ciphernode_count": 2, - "enclave_address": "http://0.0.0.0:4000", - "authentication_id": "tester" -} From 75fc659bd50c4b49e29fceb8811e53e559ee8958 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Thu, 3 Oct 2024 19:01:49 +0500 Subject: [PATCH 56/62] Update Fhe Params, remove unused code and update readme --- README.md | 183 ++- .../client/libs/wasm/pkg/crisp_web_bg.wasm | Bin 490998 -> 490998 bytes packages/compute_provider/.gitignore | 1 + packages/compute_provider/Cargo.lock | 231 ---- packages/compute_provider/Cargo.toml | 2 - packages/compute_provider/Readme.md | 87 ++ packages/risc0/.cargo/config.toml | 4 + packages/risc0/contracts/CRISPRisc0.sol | 3 +- packages/risc0/methods/guest/Cargo.lock | 132 +- packages/server/Cargo.lock | 1182 ++--------------- packages/server/Cargo.toml | 15 - packages/server/Readme.md | 87 ++ packages/server/abi/rfv.json | 107 -- packages/server/src/cli/voting.rs | 2 +- .../src/enclave_server/routes/rounds.rs | 2 +- packages/server/src/util.rs | 76 +- packages/web-rust/src/bin/web_fhe_encrypt.rs | 4 +- 17 files changed, 476 insertions(+), 1642 deletions(-) create mode 100644 packages/compute_provider/.gitignore create mode 100644 packages/compute_provider/Readme.md create mode 100644 packages/risc0/.cargo/config.toml create mode 100644 packages/server/Readme.md delete mode 100644 packages/server/abi/rfv.json diff --git a/README.md b/README.md index 8a8d3a6..b3cd8db 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,9 @@ CRISP/packages │ ├── /public/ - Static files │ ├── /src/ - React components and source code │ └── [configuration files and README] -├── /evm/ - Ethereum Virtual Machine related code -├── /rust/ - Rust server-side logic +├── /compute_provider/ - Helper library for RISC Zero compute provider +├── /risc0/ - RISC Zero zkVM and Verifier contracts +├── /server/ - Rust server-side logic └── /web-rust/ - Rust to WebAssembly logic ``` @@ -29,88 +30,202 @@ CRISP/packages imageÏ

+## Prerequisites -### Setting Up Web App +Before getting started, make sure you have the following tools installed: + +- **Rust** +- **Foundry** +- **RISC Zero toolchain** +- **Node.js** (for client-side dependencies) +- **Anvil** (for local testnet) + +## Dependencies + +### Install Rust and Foundry + +You need to install Rust and Foundry first. After installation, restart your terminal. + +```sh +# Install Rust +curl https://sh.rustup.rs -sSf | sh + +# Install Foundry +curl -L https://foundry.paradigm.xyz | bash +``` + +### Install RISC Zero Toolchain + +Next, install `rzup` for the `cargo-risczero` toolchain. + +```sh +# Install rzup +curl -L https://risczero.com/install | bash + +# Install RISC Zero toolchain +rzup +``` + +Verify the installation was successful by running: + +```sh +cargo risczero --version +``` + +At this point, you should have all the tools required to develop and deploy an application with [RISC Zero](https://www.risczero.com). + +## Setting Up the Web App To set up the CRISP dApp in your local environment, follow these steps: 1. Clone the repository: + ```sh git clone https://github.com/gnosisguild/CRISP.git ``` + 2. Navigate to the `client` directory: + ```sh cd CRISP/packages/client ``` + 3. Install dependencies: + ```sh yarn install ``` + 4. Start the development server: + ```sh yarn dev ``` -## Setting Up the Enclave Server +## Setting Up the CRISP Server + +Setting up the CRISP server involves several components, but this guide will walk you through each step. + +### Step 1: Start a Local Testnet with Anvil + +```sh +anvil +``` + +Keep Anvil running in the terminal, and open a new terminal for the next steps. -### Prerequisites +### Step 2: Deploy the Enclave Contracts -Before running the Enclave server, you must have Rust installed on your machine along with the necessary environment variables set. Follow these steps to set up your environment: +1. Clone the [Enclave Repo](https://github.com/gnosisguild/enclave): -1. Install Rust: ```sh - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + git clone https://github.com/gnosisguild/enclave.git ``` - This will install `rustup`, Rust's toolchain installer. Follow the on-screen instructions to complete the installation. -2. Add the Rust toolchain to your system's PATH: +2. Navigate to the `evm` directory: + ```sh - source $HOME/.cargo/env + cd enclave/packages/evm ``` -### Running the Enclave Server +3. Install dependencies: -Navigate to the `enclave_server` directory and run the server with: + ```sh + yarn install + ``` -```sh -cargo run --bin enclave_server -``` +4. Deploy the contracts on the local testnet: + + ```sh + yarn deploy:mocks --network localhost + ``` -### Setting Up Cipher Nodes +After deployment, note down the addresses for the following contracts: +- Enclave +- Ciphernode Registry +- Naive Registry Filter +- Mock Input Validator -Open 4 separate terminal windows for the various components. +### Step 3: Deploy the RISC Zero Contracts + +1. Navigate to the `risc0` directory. + +2. Set up environment variables by creating a `.cargo` directory and `config.toml` file: -1. In two terminals, start the cipher nodes by running: ```sh - cargo run --bin start_cipher_node + mkdir .cargo && cd .cargo && touch config.toml + ``` + +3. Add the following configuration to `config.toml`: + + > ***Note:*** *This requires having access to a Bonsai API Key. To request an API key [complete the form here](https://bonsai.xyz/apply).* + ```toml + [env] + ETH_WALLET_PRIVATE_KEY="your_private_key" + BONSAI_API_KEY="your_api_key" + BONSAI_API_URL="your_api_url" + ``` + +4. In the `risc0/script` directory, update the `config.toml` with the deployed contract addresses: + + ```toml + [profile.custom] + chainId = 31337 + riscZeroVerifierAddress = "0x0000000000000000000000000000000000000000" + enclaveAddress = "your_enclave_address" + inputValidatorAddress = "your_input_validator_address" ``` - Wait for the `enclave_server` to be up and running before executing this command in each terminal. -2. In the last terminal, run the CLI client: +5. Deploy the contracts: + ```sh - cargo run --bin cli + forge script --rpc-url http://localhost:8545 --broadcast script/Deploy.s.sol ``` -### Interacting with CRISP via CLI +Note down the CRISPRisc0 Contract Address, which will be used as the E3 Program Address. + +### Step 4: Set up Environment Variables + +Create a `.env` file in the `server` directory with the following: + +```sh +PRIVATE_KEY=your_private_key +HTTP_RPC_URL=http://localhost:8545 +WS_RPC_URL=ws://localhost:8546 +ENCLAVE_ADDRESS=your_enclave_contract_address +E3_PROGRAM_ADDRESS=your_e3_program_address # CRISPRisc0 Contract Address +CIPHERNODE_REGISTRY_ADDRESS=your_ciphernode_registry_address +NAIVE_REGISTRY_FILTER_ADDRESS=your_naive_registry_filter_address +CHAIN_ID=your_chain_id +CRON_API_KEY=your_cron_api_key # Optional for e3_cron binary +``` + +## Running the Enclave Server + +To run the Enclave Server, navigate to the `enclave_server` directory and execute the following command: + +```sh +cargo run --bin enclave_server +``` -Once the CLI client is running, you can interact with the CRISP voting protocol as follows: +## Interacting with CRISP via CLI -1. Select the option `CRISP: Voting Protocol (ETH)`. +Once the CLI client is running, you can interact with the CRISP voting protocol by following these steps: -2. To start a new CRISP round, select the option `Initialize new CRISP round`. +1. Select `CRISP: Voting Protocol (ETH)` from the menu. -Ensure all components are running and communicating with each other properly before initializing a new round. +2. To initiate a new CRISP round, choose the option `Initialize new CRISP round`. -Remember to provide exact paths if the `enclave_server`, `start_cipher_node`, and `cli` binaries are located in specific directories. If any specific configuration is needed in the environment files, make sure to outline what changes need to be made and where the files are located. It's also crucial to include any ports that need to be open or additional services that are required for the application to run correctly. +Ensure all services are running correctly and that components are communicating as expected before starting a new CRISP round. ## Contributing -We welcome and encourage community contributions to this repo. Please make sure to read the [CLA](https://github.com/gnosisguild/CLA) before contributing. +We welcome and encourage community contributions to this repository. Please ensure that you read and understand the [Contributor License Agreement (CLA)](https://github.com/gnosisguild/CLA) before submitting any contributions. -### Security and Liability +## Security and Liability -This repo is provided WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +This project is provided **WITHOUT ANY WARRANTY**; without even the implied warranty of **MERCHANTABILITY** or **FITNESS FOR A PARTICULAR PURPOSE**. -### License +## License -This repo created under the [LGPL-3.0+ license](LICENSE). \ No newline at end of file +This repository is licensed under the [LGPL-3.0+ license](LICENSE). \ No newline at end of file diff --git a/packages/client/libs/wasm/pkg/crisp_web_bg.wasm b/packages/client/libs/wasm/pkg/crisp_web_bg.wasm index 7ac35b1d2f4a3eb4df723a259b63c7173c06a616..669cd4daa1f51e81a1c6caf0a79e5e71d082c01f 100644 GIT binary patch delta 53 zcmezNTlU*;*@i8Qsr%Uu{09Q|?b-VoJKjS%?LXHs0WmWWvj8#c_Mhw6{+ZxSF@ delta 53 zcmezNTlU*;*@i8Qsr%U)e*FLcUw(V`e#VaXP)_^Lbxc6a48$xz%)0&OI<~*(0AYn6 A_y7O^ diff --git a/packages/compute_provider/.gitignore b/packages/compute_provider/.gitignore new file mode 100644 index 0000000..9f97022 --- /dev/null +++ b/packages/compute_provider/.gitignore @@ -0,0 +1 @@ +target/ \ No newline at end of file diff --git a/packages/compute_provider/Cargo.lock b/packages/compute_provider/Cargo.lock index 4b8d052..f018e03 100644 --- a/packages/compute_provider/Cargo.lock +++ b/packages/compute_provider/Cargo.lock @@ -14,15 +14,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - [[package]] name = "ark-bn254" version = "0.4.0" @@ -182,8 +173,6 @@ dependencies = [ "rayon", "serde", "sha3", - "tracing-subscriber", - "zk-kit-imt", ] [[package]] @@ -220,12 +209,6 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-common" version = "0.1.6" @@ -317,12 +300,6 @@ dependencies = [ "cpufeatures", ] -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - [[package]] name = "lean-imt" version = "0.1.0" @@ -347,37 +324,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - [[package]] name = "num-bigint" version = "0.4.6" @@ -412,24 +358,12 @@ version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ea5043e58958ee56f3e15a90aee535795cd7dfd319846288d93c5b57d85cbe" -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "pin-project-lite" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" - [[package]] name = "ppv-lite86" version = "0.2.20" @@ -507,50 +441,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "regex" -version = "1.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", -] - -[[package]] -name = "regex-automata" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax 0.8.4", -] - -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" - [[package]] name = "rustc_version" version = "0.4.1" @@ -596,21 +486,6 @@ dependencies = [ "keccak", ] -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - [[package]] name = "syn" version = "1.0.109" @@ -653,74 +528,6 @@ dependencies = [ "syn 2.0.77", ] -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - -[[package]] -name = "tracing" -version = "0.1.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" -dependencies = [ - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - [[package]] name = "typenum" version = "1.17.0" @@ -733,12 +540,6 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - [[package]] name = "version_check" version = "0.9.5" @@ -751,28 +552,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "zerocopy" version = "0.7.35" @@ -813,13 +592,3 @@ dependencies = [ "quote", "syn 2.0.77", ] - -[[package]] -name = "zk-kit-imt" -version = "0.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4bec228e2acafec7bd22c9a0a5e0c5e2a6d0c17df69f5ad11c24ce6dc6356c6" -dependencies = [ - "hex", - "tiny-keccak", -] diff --git a/packages/compute_provider/Cargo.toml b/packages/compute_provider/Cargo.toml index 3d66c09..1264627 100644 --- a/packages/compute_provider/Cargo.toml +++ b/packages/compute_provider/Cargo.toml @@ -4,9 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -tracing-subscriber = { version = "0.3", features = ["env-filter"] } serde = { version = "1.0", features = ["derive", "std"] } -zk-kit-imt = "0.0.5" lean-imt = "0.1.0" sha3 = "0.10.8" num-bigint = "0.4" diff --git a/packages/compute_provider/Readme.md b/packages/compute_provider/Readme.md new file mode 100644 index 0000000..1418191 --- /dev/null +++ b/packages/compute_provider/Readme.md @@ -0,0 +1,87 @@ +# FHE Compute Manager + +This project provides a flexible and efficient framework for managing Secure Programs (SP) of the [Enclave Protocol](enclave.gg). It supports both sequential and parallel processing, with the ability to integrate various compute providers. + +## Features + +- Support for both sequential and parallel FHE computations +- Flexible integration of different compute providers +- Merkle tree generation for input verification +- Ciphertext hashing for output verification + +## Installation + +To use this library, add it to your `Cargo.toml`: + +```toml +[dependencies] +compute-provider = { git = "https://github.com/gnosisguild/compute-provider" } +``` + +## Usage + +To use the library, follow these steps: + +1. Create an instance of the `ComputeManager` with your desired configuration. +2. Call the `start` method to begin the computation process. +3. The method will return the computed ciphertext and the corresponding proof. + +```rust +use anyhow::Result; +use compute_provider::{ComputeInput, ComputeManager, ComputeProvider, ComputeResult, FHEInputs}; +use voting_core::fhe_processor; + +// Define your Risc0Provider struct and implement the ComputeProvider trait +pub fn run_compute(params: FHEInputs) -> Result<(Risc0Output, Vec)> { + let risc0_provider = Risc0Provider; + let mut provider = ComputeManager::new(risc0_provider, params, fhe_processor, false, None); + let output = provider.start(); + Ok(output) +} +``` + + +## Risc0 Example + +Here's a more detailed example of how to use the Compute Manager with Risc0: + +```rust +use compute_provider::{ComputeInput, ComputeManager, ComputeProvider, ComputeResult, FHEInputs}; +use methods::VOTING_ELF; +use risc0_ethereum_contracts::groth16; +use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts, VerifierContext}; +use serde::{Deserialize, Serialize}; + +pub struct Risc0Provider; +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Risc0Output { + pub result: ComputeResult, + pub seal: Vec, +} + +impl ComputeProvider for Risc0Provider { + type Output = Risc0Output; + fn prove(&self, input: &ComputeInput) -> Self::Output { + // Implementation details + } +} +pub fn run_compute(params: FHEInputs) -> Result<(Risc0Output, Vec)> { + let risc0_provider = Risc0Provider; + let mut provider = ComputeManager::new(risc0_provider, params, fhe_processor, false, None); + let output: (Risc0Output, Vec) = provider.start(); + Ok(output) +} +``` + + +This example demonstrates how to create a Risc0Provider, use it with the ComputeManager, and measure the execution time of the computation. + +## Configuration + +The `ComputeManager::new()` function takes several parameters: + +- `provider`: An instance of your compute provider (e.g., `Risc0Provider`) +- `fhe_inputs`: The FHE inputs for the computation +- `fhe_processor`: A function to process the FHE inputs +- `use_parallel`: A boolean indicating whether to use parallel processing +- `batch_size`: An optional batch size for parallel processing, must be a power of 2 \ No newline at end of file diff --git a/packages/risc0/.cargo/config.toml b/packages/risc0/.cargo/config.toml new file mode 100644 index 0000000..b16f907 --- /dev/null +++ b/packages/risc0/.cargo/config.toml @@ -0,0 +1,4 @@ +[env] +ETH_WALLET_PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +BONSAI_API_KEY="YOUR_API_KEY" +BONSAI_API_URL="BONSAI_API_URL" \ No newline at end of file diff --git a/packages/risc0/contracts/CRISPRisc0.sol b/packages/risc0/contracts/CRISPRisc0.sol index 2fcd3da..2969854 100644 --- a/packages/risc0/contracts/CRISPRisc0.sol +++ b/packages/risc0/contracts/CRISPRisc0.sol @@ -8,7 +8,7 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; contract CRISPRisc0 is CRISPBase, Ownable { // Constants - bytes32 public constant IMAGE_ID = ImageID.VOTING_ID; // TODO: update this to the CRISP image ID + bytes32 public constant IMAGE_ID = ImageID.VOTING_ID; bytes32 public constant ENCRYPTION_SCHEME_ID = keccak256("fhe.rs:BFV"); // State variables @@ -16,7 +16,6 @@ contract CRISPRisc0 is CRISPBase, Ownable { IInputValidator public inputValidator; // Mappings - mapping(uint256 e3Id => bytes32 imageId) public imageIds; mapping(address => bool) public authorizedContracts; // Events diff --git a/packages/risc0/methods/guest/Cargo.lock b/packages/risc0/methods/guest/Cargo.lock index ff6d9c3..e3d40f7 100644 --- a/packages/risc0/methods/guest/Cargo.lock +++ b/packages/risc0/methods/guest/Cargo.lock @@ -257,7 +257,7 @@ dependencies = [ "ark-ff 0.4.2", "ark-std 0.4.0", "tracing", - "tracing-subscriber 0.2.25", + "tracing-subscriber", ] [[package]] @@ -479,8 +479,6 @@ dependencies = [ "rayon", "serde", "sha3", - "tracing-subscriber 0.3.18", - "zk-kit-imt", ] [[package]] @@ -1058,15 +1056,6 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] - [[package]] name = "matrixmultiply" version = "0.3.9" @@ -1102,16 +1091,6 @@ dependencies = [ "rawpointer", ] -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - [[package]] name = "num-bigint" version = "0.4.5" @@ -1184,12 +1163,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "parity-scale-codec" version = "3.6.12" @@ -1342,7 +1315,7 @@ dependencies = [ "rand", "rand_chacha", "rand_xorshift", - "regex-syntax 0.8.3", + "regex-syntax", "rusty-fork", "tempfile", "unarray", @@ -1495,17 +1468,8 @@ checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.3", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", + "regex-automata", + "regex-syntax", ] [[package]] @@ -1516,15 +1480,9 @@ checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.3", + "regex-syntax", ] -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "regex-syntax" version = "0.8.3" @@ -1860,15 +1818,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - [[package]] name = "signature" version = "2.2.0" @@ -1985,16 +1934,6 @@ dependencies = [ "syn 2.0.77", ] -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - [[package]] name = "tiny-keccak" version = "2.0.2" @@ -2054,17 +1993,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - [[package]] name = "tracing-subscriber" version = "0.2.25" @@ -2074,24 +2002,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-subscriber" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - [[package]] name = "typenum" version = "1.17.0" @@ -2164,28 +2074,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-sys" version = "0.52.0" @@ -2316,13 +2204,3 @@ dependencies = [ "quote", "syn 2.0.77", ] - -[[package]] -name = "zk-kit-imt" -version = "0.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4bec228e2acafec7bd22c9a0a5e0c5e2a6d0c17df69f5ad11c24ce6dc6356c6" -dependencies = [ - "hex", - "tiny-keccak", -] diff --git a/packages/server/Cargo.lock b/packages/server/Cargo.lock index a50a3ad..420f61c 100644 --- a/packages/server/Cargo.lock +++ b/packages/server/Cargo.lock @@ -39,7 +39,7 @@ dependencies = [ "actix-web", "derive_more", "futures-util", - "log 0.4.22", + "log", "once_cell", "smallvec", ] @@ -69,12 +69,12 @@ dependencies = [ "httparse", "httpdate", "itoa", - "language-tags 0.3.2", + "language-tags", "local-channel", - "mime 0.3.17", - "percent-encoding 2.3.1", + "mime", + "percent-encoding", "pin-project-lite", - "rand 0.8.5", + "rand", "sha1", "smallvec", "tokio", @@ -130,7 +130,7 @@ dependencies = [ "futures-core", "futures-util", "mio 1.0.2", - "socket2 0.5.7", + "socket2", "tokio", "tracing", ] @@ -182,9 +182,9 @@ dependencies = [ "futures-util", "impl-more", "itoa", - "language-tags 0.3.2", - "log 0.4.22", - "mime 0.3.17", + "language-tags", + "log", + "mime", "once_cell", "pin-project-lite", "regex", @@ -193,9 +193,9 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", - "socket2 0.5.7", - "time 0.3.36", - "url 2.5.0", + "socket2", + "time", + "url", ] [[package]] @@ -245,7 +245,7 @@ dependencies = [ "cfg-if", "getrandom", "once_cell", - "version_check 0.9.4", + "version_check", "zerocopy", ] @@ -480,7 +480,7 @@ dependencies = [ "k256", "keccak-asm", "proptest", - "rand 0.8.5", + "rand", "ruint", "serde", "tiny-keccak", @@ -502,7 +502,7 @@ dependencies = [ "k256", "keccak-asm", "proptest", - "rand 0.8.5", + "rand", "ruint", "serde", "tiny-keccak", @@ -541,7 +541,7 @@ dependencies = [ "serde_json", "tokio", "tracing", - "url 2.5.0", + "url", ] [[package]] @@ -607,7 +607,7 @@ dependencies = [ "tokio-stream", "tower", "tracing", - "url 2.5.0", + "url", ] [[package]] @@ -635,7 +635,7 @@ dependencies = [ "alloy-rpc-types-eth", "alloy-serde", "jsonwebtoken 9.3.0", - "rand 0.8.5", + "rand", "serde", "thiserror", ] @@ -696,7 +696,7 @@ dependencies = [ "alloy-signer", "async-trait", "k256", - "rand 0.8.5", + "rand", "thiserror", ] @@ -819,7 +819,7 @@ dependencies = [ "tokio", "tower", "tracing", - "url 2.5.0", + "url", ] [[package]] @@ -834,7 +834,7 @@ dependencies = [ "serde_json", "tower", "tracing", - "url 2.5.0", + "url", ] [[package]] @@ -1110,7 +1110,7 @@ dependencies = [ "ark-ff 0.4.2", "ark-std 0.4.0", "tracing", - "tracing-subscriber 0.2.25", + "tracing-subscriber", ] [[package]] @@ -1165,7 +1165,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ "num-traits", - "rand 0.8.5", + "rand", ] [[package]] @@ -1175,7 +1175,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ "num-traits", - "rand 0.8.5", + "rand", ] [[package]] @@ -1193,155 +1193,6 @@ dependencies = [ "term", ] -[[package]] -name = "async-attributes" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener 2.5.3", - "futures-core", -] - -[[package]] -name = "async-channel" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d4d23bcc79e27423727b36823d86233aad06dfea531837b038394d11e9928" -dependencies = [ - "concurrent-queue", - "event-listener 5.3.0", - "event-listener-strategy 0.5.2", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b10202063978b3351199d68f8b22c4e47e4b1b822f8d43fd862d5ea8c006b29a" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand 2.1.0", - "futures-lite 2.3.0", - "slab", -] - -[[package]] -name = "async-global-executor" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" -dependencies = [ - "async-channel 2.2.1", - "async-executor", - "async-io 2.3.2", - "async-lock 3.3.0", - "blocking", - "futures-lite 2.3.0", - "once_cell", - "tokio", -] - -[[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock 2.8.0", - "autocfg 1.3.0", - "cfg-if", - "concurrent-queue", - "futures-lite 1.13.0", - "log 0.4.22", - "parking", - "polling 2.8.0", - "rustix 0.37.27", - "slab", - "socket2 0.4.10", - "waker-fn", -] - -[[package]] -name = "async-io" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" -dependencies = [ - "async-lock 3.3.0", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite 2.3.0", - "parking", - "polling 3.7.0", - "rustix 0.38.34", - "slab", - "tracing", - "windows-sys 0.52.0", -] - -[[package]] -name = "async-lock" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" -dependencies = [ - "event-listener 2.5.3", -] - -[[package]] -name = "async-lock" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" -dependencies = [ - "event-listener 4.0.3", - "event-listener-strategy 0.4.0", - "pin-project-lite", -] - -[[package]] -name = "async-std" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" -dependencies = [ - "async-attributes", - "async-channel 1.9.0", - "async-global-executor", - "async-io 1.13.0", - "async-lock 2.8.0", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite 1.13.0", - "gloo-timers", - "kv-log-macro", - "log 0.4.22", - "memchr", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", -] - [[package]] name = "async-stream" version = "0.3.5" @@ -1364,12 +1215,6 @@ dependencies = [ "syn 2.0.61", ] -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - [[package]] name = "async-trait" version = "0.1.80" @@ -1392,12 +1237,6 @@ dependencies = [ "rustc_version 0.4.0", ] -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - [[package]] name = "auto_impl" version = "1.2.0" @@ -1409,15 +1248,6 @@ dependencies = [ "syn 2.0.61", ] -[[package]] -name = "autocfg" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" -dependencies = [ - "autocfg 1.3.0", -] - [[package]] name = "autocfg" version = "1.3.0" @@ -1445,16 +1275,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" -[[package]] -name = "base64" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" -dependencies = [ - "byteorder", - "safemem", -] - [[package]] name = "base64" version = "0.13.1" @@ -1560,20 +1380,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "blocking" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "495f7104e962b7356f0aeb34247aca1fe7d2e783b346582db7f2904cb5717e88" -dependencies = [ - "async-channel 2.2.1", - "async-lock 3.3.0", - "async-task", - "futures-io", - "futures-lite 2.3.0", - "piper", -] - [[package]] name = "blst" version = "0.3.13" @@ -1836,15 +1642,6 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "coins-bip32" version = "0.8.7" @@ -1872,7 +1669,7 @@ dependencies = [ "hmac", "once_cell", "pbkdf2 0.12.2", - "rand 0.8.5", + "rand", "sha2", "thiserror", ] @@ -1914,21 +1711,10 @@ dependencies = [ "light-poseidon", "num-bigint", "num-traits", - "rand 0.8.5", + "rand", "rayon", "serde", "sha3", - "tracing-subscriber 0.3.18", - "zk-kit-imt", -] - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", ] [[package]] @@ -2030,9 +1816,9 @@ version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ - "percent-encoding 2.3.1", - "time 0.3.36", - "version_check 0.9.4", + "percent-encoding", + "time", + "version_check", ] [[package]] @@ -2107,7 +1893,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core 0.6.4", + "rand_core", "subtle", "zeroize", ] @@ -2364,7 +2150,7 @@ dependencies = [ "generic-array", "group", "pkcs8", - "rand_core 0.6.4", + "rand_core", "sec1", "subtle", "zeroize", @@ -2376,7 +2162,7 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" dependencies = [ - "log 0.4.22", + "log", ] [[package]] @@ -2404,8 +2190,8 @@ dependencies = [ "bytes", "hex", "k256", - "log 0.4.22", - "rand 0.8.5", + "log", + "rand", "rlp", "serde", "sha3", @@ -2418,7 +2204,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" dependencies = [ - "log 0.4.22", + "log", "regex", ] @@ -2430,7 +2216,7 @@ checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" dependencies = [ "humantime", "is-terminal", - "log 0.4.22", + "log", "regex", "termcolor", ] @@ -2445,7 +2231,7 @@ dependencies = [ "anstyle", "env_filter", "humantime", - "log 0.4.22", + "log", ] [[package]] @@ -2476,7 +2262,7 @@ dependencies = [ "hex", "hmac", "pbkdf2 0.11.0", - "rand 0.8.5", + "rand", "scrypt", "serde", "serde_json", @@ -2639,7 +2425,7 @@ dependencies = [ "num_enum", "once_cell", "open-fastrlp", - "rand 0.8.5", + "rand", "rlp", "serde", "serde_json", @@ -2691,7 +2477,7 @@ dependencies = [ "tokio", "tracing", "tracing-futures", - "url 2.5.0", + "url", ] [[package]] @@ -2725,7 +2511,7 @@ dependencies = [ "tokio-tungstenite 0.20.1", "tracing", "tracing-futures", - "url 2.5.0", + "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -2745,7 +2531,7 @@ dependencies = [ "elliptic-curve", "eth-keystore", "ethers-core", - "rand 0.8.5", + "rand", "sha2", "thiserror", "tracing", @@ -2789,54 +2575,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b90ca2580b73ab6a1f724b76ca11ab632df820fd6040c336200d2c1df7b3c82c" -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "event-listener" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9944b8ca13534cdfb2800775f8dd4902ff3fc75a50101466decadfdf322a24" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" -dependencies = [ - "event-listener 4.0.3", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" -dependencies = [ - "event-listener 5.3.0", - "pin-project-lite", -] - [[package]] name = "eyre" version = "0.6.12" @@ -2847,15 +2585,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - [[package]] name = "fastrand" version = "2.1.0" @@ -2879,7 +2608,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -2898,8 +2627,8 @@ dependencies = [ "num-traits", "prost", "prost-build", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand", + "rand_chacha", "serde", "thiserror", "zeroize", @@ -2921,8 +2650,8 @@ dependencies = [ "num-traits", "prost", "prost-build", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand", + "rand_chacha", "serde", "thiserror", "zeroize", @@ -2944,8 +2673,8 @@ dependencies = [ "num-traits", "prost", "prost-build", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand", + "rand_chacha", "sha2", "thiserror", "zeroize", @@ -2966,8 +2695,8 @@ dependencies = [ "num-traits", "prost", "prost-build", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand", + "rand_chacha", "sha2", "thiserror", "zeroize", @@ -2978,7 +2707,7 @@ name = "fhe-traits" version = "0.1.0-beta.7" source = "git+https://github.com/gnosisguild/fhe.rs?branch=feature/greco-integration#26e5f2ff6c860d47a1c88a777936bc68eaedb129" dependencies = [ - "rand 0.8.5", + "rand", ] [[package]] @@ -2986,7 +2715,7 @@ name = "fhe-traits" version = "0.1.0-beta.7" source = "git+https://github.com/gnosisguild/fhe.rs#9624766dcfbb40ecfb01147f59c2f6292c447707" dependencies = [ - "rand 0.8.5", + "rand", ] [[package]] @@ -2997,7 +2726,7 @@ dependencies = [ "itertools 0.12.1", "num-bigint-dig", "num-traits", - "rand 0.8.5", + "rand", ] [[package]] @@ -3008,7 +2737,7 @@ dependencies = [ "itertools 0.12.1", "num-bigint-dig", "num-traits", - "rand 0.8.5", + "rand", ] [[package]] @@ -3018,7 +2747,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ "byteorder", - "rand 0.8.5", + "rand", "rustc-hex", "static_assertions", ] @@ -3066,7 +2795,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ - "percent-encoding 2.3.1", + "percent-encoding", ] [[package]] @@ -3079,12 +2808,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - [[package]] name = "funty" version = "2.0.0" @@ -3139,34 +2862,6 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - -[[package]] -name = "futures-lite" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" -dependencies = [ - "fastrand 2.1.0", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - [[package]] name = "futures-locks" version = "0.7.1" @@ -3259,7 +2954,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", - "version_check 0.9.4", + "version_check", "zeroize", ] @@ -3272,7 +2967,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] @@ -3307,7 +3002,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -3377,30 +3072,6 @@ dependencies = [ "fxhash", ] -[[package]] -name = "headers" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" -dependencies = [ - "base64 0.21.7", - "bytes", - "headers-core", - "http 1.1.0", - "httpdate", - "mime 0.3.17", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" -dependencies = [ - "http 1.1.0", -] - [[package]] name = "heck" version = "0.4.1" @@ -3532,25 +3203,6 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" -[[package]] -name = "hyper" -version = "0.10.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" -dependencies = [ - "base64 0.9.3", - "httparse", - "language-tags 0.2.2", - "log 0.3.9", - "mime 0.2.6", - "num_cpus", - "time 0.1.45", - "traitobject", - "typeable", - "unicase", - "url 1.7.2", -] - [[package]] name = "hyper" version = "0.14.28" @@ -3568,7 +3220,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.7", + "socket2", "tokio", "tower-service", "tracing", @@ -3588,7 +3240,6 @@ dependencies = [ "http 1.1.0", "http-body 1.0.0", "httparse", - "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -3657,7 +3308,7 @@ dependencies = [ "http-body 1.0.0", "hyper 1.3.1", "pin-project-lite", - "socket2 0.5.7", + "socket2", "tokio", "tower", "tower-service", @@ -3687,17 +3338,6 @@ dependencies = [ "cc", ] -[[package]] -name = "idna" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "0.5.0" @@ -3801,39 +3441,12 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "ipnet" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" -[[package]] -name = "iron" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6d308ca2d884650a8bf9ed2ff4cb13fbb2207b71f64cda11dc9b892067295e8" -dependencies = [ - "hyper 0.10.16", - "log 0.3.9", - "mime_guess", - "modifier", - "num_cpus", - "plugin", - "typemap", - "url 1.7.2", -] - [[package]] name = "is-terminal" version = "0.4.13" @@ -3999,15 +3612,6 @@ dependencies = [ "sha3-asm", ] -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log 0.4.22", -] - [[package]] name = "lalrpop" version = "0.20.2" @@ -4021,7 +3625,7 @@ dependencies = [ "lalrpop-util", "petgraph", "regex", - "regex-syntax 0.8.3", + "regex-syntax", "string_cache", "term", "tiny-keccak", @@ -4035,15 +3639,9 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" dependencies = [ - "regex-automata 0.4.6", + "regex-automata", ] -[[package]] -name = "language-tags" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" - [[package]] name = "language-tags" version = "0.3.2" @@ -4105,12 +3703,6 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - [[package]] name = "linux-raw-sys" version = "0.4.13" @@ -4140,27 +3732,15 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ - "autocfg 1.3.0", + "autocfg", "scopeguard", ] -[[package]] -name = "log" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" -dependencies = [ - "log 0.4.22", -] - [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" -dependencies = [ - "value-bag", -] [[package]] name = "lru" @@ -4171,28 +3751,13 @@ dependencies = [ "hashbrown 0.14.5", ] -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] - -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - [[package]] name = "matrixmultiply" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2" dependencies = [ - "autocfg 1.3.0", + "autocfg", "rawpointer", ] @@ -4233,33 +3798,12 @@ dependencies = [ "risc0-zkp", ] -[[package]] -name = "mime" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" -dependencies = [ - "log 0.3.9", -] - [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "mime_guess" -version = "1.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216929a5ee4dd316b1702eedf5e74548c123d370f47841ceaac38ca154690ca3" -dependencies = [ - "mime 0.2.6", - "phf 0.7.24", - "phf_codegen", - "unicase", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -4282,7 +3826,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.48.0", ] @@ -4294,17 +3838,11 @@ checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi 0.3.9", "libc", - "log 0.4.22", - "wasi 0.11.0+wasi-snapshot-preview1", + "log", + "wasi", "windows-sys 0.52.0", ] -[[package]] -name = "modifier" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f5c9112cb662acd3b204077e0de5bc66305fa8df65c8019d5adb10e9ab6e58" - [[package]] name = "multimap" version = "0.10.0" @@ -4319,7 +3857,7 @@ checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ "lazy_static", "libc", - "log 0.4.22", + "log", "openssl", "openssl-probe", "openssl-sys", @@ -4358,16 +3896,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - [[package]] name = "num-bigint" version = "0.4.5" @@ -4390,7 +3918,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.5", + "rand", "serde", "smallvec", ] @@ -4425,7 +3953,7 @@ version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ - "autocfg 1.3.0", + "autocfg", "num-integer", "num-traits", ] @@ -4436,7 +3964,7 @@ version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "autocfg 1.3.0", + "autocfg", "libm", ] @@ -4571,12 +4099,6 @@ dependencies = [ "hashbrown 0.13.2", ] -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "parity-scale-codec" version = "3.6.9" @@ -4603,12 +4125,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "parking" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" - [[package]] name = "parking_lot" version = "0.11.2" @@ -4664,7 +4180,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" dependencies = [ "base64ct", - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -4727,12 +4243,6 @@ dependencies = [ "serde", ] -[[package]] -name = "percent-encoding" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" - [[package]] name = "percent-encoding" version = "2.3.1" @@ -4804,15 +4314,6 @@ dependencies = [ "rustc_version 0.4.0", ] -[[package]] -name = "phf" -version = "0.7.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18" -dependencies = [ - "phf_shared 0.7.24", -] - [[package]] name = "phf" version = "0.11.2" @@ -4823,26 +4324,6 @@ dependencies = [ "phf_shared 0.11.2", ] -[[package]] -name = "phf_codegen" -version = "0.7.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e" -dependencies = [ - "phf_generator 0.7.24", - "phf_shared 0.7.24", -] - -[[package]] -name = "phf_generator" -version = "0.7.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" -dependencies = [ - "phf_shared 0.7.24", - "rand 0.6.5", -] - [[package]] name = "phf_generator" version = "0.11.2" @@ -4850,7 +4331,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" dependencies = [ "phf_shared 0.11.2", - "rand 0.8.5", + "rand", ] [[package]] @@ -4859,21 +4340,11 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" dependencies = [ - "phf_generator 0.11.2", + "phf_generator", "phf_shared 0.11.2", - "proc-macro2", - "quote", - "syn 2.0.61", -] - -[[package]] -name = "phf_shared" -version = "0.7.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" -dependencies = [ - "siphasher 0.2.3", - "unicase", + "proc-macro2", + "quote", + "syn 2.0.61", ] [[package]] @@ -4882,7 +4353,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" dependencies = [ - "siphasher 0.3.11", + "siphasher", ] [[package]] @@ -4891,7 +4362,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" dependencies = [ - "siphasher 0.3.11", + "siphasher", ] [[package]] @@ -4926,17 +4397,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "piper" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" -dependencies = [ - "atomic-waker", - "fastrand 2.1.0", - "futures-io", -] - [[package]] name = "pkcs8" version = "0.10.2" @@ -4953,46 +4413,6 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" -[[package]] -name = "plugin" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a6a0dc3910bc8db877ffed8e457763b317cf880df4ae19109b9f77d277cf6e0" -dependencies = [ - "typemap", -] - -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg 1.3.0", - "bitflags 1.3.2", - "cfg-if", - "concurrent-queue", - "libc", - "log 0.4.22", - "pin-project-lite", - "windows-sys 0.48.0", -] - -[[package]] -name = "polling" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi 0.3.9", - "pin-project-lite", - "rustix 0.38.34", - "tracing", - "windows-sys 0.52.0", -] - [[package]] name = "powerfmt" version = "0.2.0" @@ -5073,7 +4493,7 @@ dependencies = [ "proc-macro2", "quote", "syn 1.0.109", - "version_check 0.9.4", + "version_check", ] [[package]] @@ -5084,7 +4504,7 @@ checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2", "quote", - "version_check 0.9.4", + "version_check", ] [[package]] @@ -5107,10 +4527,10 @@ dependencies = [ "bitflags 2.5.0", "lazy_static", "num-traits", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rand_xorshift 0.3.0", - "regex-syntax 0.8.3", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", "rusty-fork", "tempfile", "unarray", @@ -5135,7 +4555,7 @@ dependencies = [ "bytes", "heck 0.5.0", "itertools 0.12.1", - "log 0.4.22", + "log", "multimap", "once_cell", "petgraph", @@ -5187,7 +4607,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.23.12", - "socket2 0.5.7", + "socket2", "thiserror", "tokio", "tracing", @@ -5200,7 +4620,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" dependencies = [ "bytes", - "rand 0.8.5", + "rand", "ring 0.17.8", "rustc-hash", "rustls 0.23.12", @@ -5218,7 +4638,7 @@ checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" dependencies = [ "libc", "once_cell", - "socket2 0.5.7", + "socket2", "tracing", "windows-sys 0.52.0", ] @@ -5238,25 +4658,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" -[[package]] -name = "rand" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -dependencies = [ - "autocfg 0.1.8", - "libc", - "rand_chacha 0.1.1", - "rand_core 0.4.2", - "rand_hc", - "rand_isaac", - "rand_jitter", - "rand_os", - "rand_pcg", - "rand_xorshift 0.1.1", - "winapi", -] - [[package]] name = "rand" version = "0.8.5" @@ -5264,18 +4665,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -dependencies = [ - "autocfg 0.1.8", - "rand_core 0.3.1", + "rand_chacha", + "rand_core", ] [[package]] @@ -5285,24 +4676,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", + "rand_core", ] -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - [[package]] name = "rand_core" version = "0.6.4" @@ -5312,75 +4688,13 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_jitter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" -dependencies = [ - "libc", - "rand_core 0.4.2", - "winapi", -] - -[[package]] -name = "rand_os" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" -dependencies = [ - "cloudabi", - "fuchsia-cprng", - "libc", - "rand_core 0.4.2", - "rdrand", - "winapi", -] - -[[package]] -name = "rand_pcg" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" -dependencies = [ - "autocfg 0.1.8", - "rand_core 0.4.2", -] - -[[package]] -name = "rand_xorshift" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "rand_xorshift" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -5409,15 +4723,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "recvmsg" version = "1.0.0" @@ -5461,17 +4766,8 @@ checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.6", - "regex-syntax 0.8.3", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", + "regex-automata", + "regex-syntax", ] [[package]] @@ -5482,7 +4778,7 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.3", + "regex-syntax", ] [[package]] @@ -5491,12 +4787,6 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "regex-syntax" version = "0.8.3" @@ -5521,10 +4811,10 @@ dependencies = [ "hyper-rustls 0.24.2", "ipnet", "js-sys", - "log 0.4.22", - "mime 0.3.17", + "log", + "mime", "once_cell", - "percent-encoding 2.3.1", + "percent-encoding", "pin-project-lite", "rustls 0.21.12", "rustls-pemfile 1.0.4", @@ -5536,7 +4826,7 @@ dependencies = [ "tokio", "tokio-rustls 0.24.1", "tower-service", - "url 2.5.0", + "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -5566,11 +4856,11 @@ dependencies = [ "hyper-util", "ipnet", "js-sys", - "log 0.4.22", - "mime 0.3.17", + "log", + "mime", "native-tls", "once_cell", - "percent-encoding 2.3.1", + "percent-encoding", "pin-project-lite", "quinn", "rustls 0.23.12", @@ -5586,7 +4876,7 @@ dependencies = [ "tokio-rustls 0.26.0", "tokio-util", "tower-service", - "url 2.5.0", + "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", @@ -5614,44 +4904,30 @@ dependencies = [ "alloy", "alloy-primitives 0.6.4", "alloy-sol-types 0.6.4", - "async-std", "bincode", - "bytes", "chrono", "compute-provider", "config", - "console", "dialoguer", "dotenvy", "env_logger 0.11.5", "eyre", "fhe 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs?branch=feature/greco-integration)", - "fhe-math 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs?branch=feature/greco-integration)", "fhe-traits 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs?branch=feature/greco-integration)", - "fhe-util 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs?branch=feature/greco-integration)", "futures-util", - "getrandom", - "headers", "hex", "hmac", - "http-body-util", - "hyper 1.3.1", - "hyper-tls", - "hyper-util", "jwt", - "log 0.4.22", + "log", "once_cell", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand", "reqwest 0.12.8", - "router", "serde", "serde_json", "sha2", "sled", "tokio", "voting-risc0", - "walkdir", "wasm-bindgen", ] @@ -5773,7 +5049,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "047cc26c68c092d664ded7488dcac0462d9e31190e1598a7820fe4246d313583" dependencies = [ "bytemuck", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -5821,7 +5097,7 @@ dependencies = [ "hex", "hex-literal", "paste", - "rand_core 0.6.4", + "rand_core", "risc0-core", "risc0-zkvm-platform", "serde", @@ -5904,23 +5180,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "route-recognizer" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea509065eb0b3c446acdd0102f0d46567dc30902dc0be91d6552035d92b0f4f8" - -[[package]] -name = "router" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc63b6f3b8895b0d04e816b2b1aa58fdba2d5acca3cbb8f0ab8e017347d57397" -dependencies = [ - "iron", - "route-recognizer", - "url 1.7.2", -] - [[package]] name = "rrs-lib" version = "0.1.0" @@ -5947,7 +5206,7 @@ dependencies = [ "parity-scale-codec", "primitive-types", "proptest", - "rand 0.8.5", + "rand", "rlp", "ruint-macro", "serde", @@ -6007,20 +5266,6 @@ dependencies = [ "semver 1.0.23", ] -[[package]] -name = "rustix" -version = "0.37.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - [[package]] name = "rustix" version = "0.38.34" @@ -6030,7 +5275,7 @@ dependencies = [ "bitflags 2.5.0", "errno", "libc", - "linux-raw-sys 0.4.13", + "linux-raw-sys", "windows-sys 0.52.0", ] @@ -6040,7 +5285,7 @@ version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ - "log 0.4.22", + "log", "ring 0.17.8", "rustls-webpki 0.101.7", "sct", @@ -6130,12 +5375,6 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" - [[package]] name = "salsa20" version = "0.10.2" @@ -6385,15 +5624,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - [[package]] name = "shell-words" version = "1.1.0" @@ -6416,7 +5646,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest 0.10.7", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -6428,15 +5658,9 @@ dependencies = [ "num-bigint", "num-traits", "thiserror", - "time 0.3.36", + "time", ] -[[package]] -name = "siphasher" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" - [[package]] name = "siphasher" version = "0.3.11" @@ -6449,7 +5673,7 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ - "autocfg 1.3.0", + "autocfg", ] [[package]] @@ -6464,7 +5688,7 @@ dependencies = [ "fs2", "fxhash", "libc", - "log 0.4.22", + "log", "parking_lot 0.11.2", ] @@ -6474,16 +5698,6 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" -[[package]] -name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "socket2" version = "0.5.7" @@ -6503,7 +5717,7 @@ dependencies = [ "itertools 0.11.0", "lalrpop", "lalrpop-util", - "phf 0.11.2", + "phf", "thiserror", "unicode-xid", ] @@ -6599,7 +5813,7 @@ dependencies = [ "serde_json", "sha2", "thiserror", - "url 2.5.0", + "url", "zip", ] @@ -6719,8 +5933,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", - "fastrand 2.1.0", - "rustix 0.38.34", + "fastrand", + "rustix", "windows-sys 0.52.0", ] @@ -6783,17 +5997,6 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "time" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - [[package]] name = "time" version = "0.3.36" @@ -6863,7 +6066,7 @@ dependencies = [ "parking_lot 0.12.2", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.7", + "socket2", "tokio-macros", "windows-sys 0.48.0", ] @@ -6929,7 +6132,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", - "log 0.4.22", + "log", "rustls 0.21.12", "tokio", "tokio-rustls 0.24.1", @@ -6944,7 +6147,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" dependencies = [ "futures-util", - "log 0.4.22", + "log", "rustls 0.23.12", "rustls-pki-types", "tokio", @@ -7067,7 +6270,7 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "log 0.4.22", + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -7104,17 +6307,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log 0.4.22", - "once_cell", - "tracing-core", -] - [[package]] name = "tracing-subscriber" version = "0.2.25" @@ -7124,30 +6316,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-subscriber" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "traitobject" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" - [[package]] name = "try-lock" version = "0.2.5" @@ -7165,12 +6333,12 @@ dependencies = [ "data-encoding", "http 0.2.12", "httparse", - "log 0.4.22", - "rand 0.8.5", + "log", + "rand", "rustls 0.21.12", "sha1", "thiserror", - "url 2.5.0", + "url", "utf-8", ] @@ -7185,8 +6353,8 @@ dependencies = [ "data-encoding", "http 1.1.0", "httparse", - "log 0.4.22", - "rand 0.8.5", + "log", + "rand", "rustls 0.23.12", "rustls-pki-types", "sha1", @@ -7194,21 +6362,6 @@ dependencies = [ "utf-8", ] -[[package]] -name = "typeable" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" - -[[package]] -name = "typemap" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "653be63c80a3296da5551e1bfd2cca35227e13cdd08c6668903ae2f4f77aa1f6" -dependencies = [ - "unsafe-any", -] - [[package]] name = "typenum" version = "1.17.0" @@ -7239,15 +6392,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" -[[package]] -name = "unicase" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" -dependencies = [ - "version_check 0.1.5", -] - [[package]] name = "unicode-bidi" version = "0.3.15" @@ -7287,15 +6431,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" -[[package]] -name = "unsafe-any" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30360d7979f5e9c6e6cea48af192ea8fab4afb3cf72597154b8f08935bc9c7f" -dependencies = [ - "traitobject", -] - [[package]] name = "untrusted" version = "0.7.1" @@ -7308,17 +6443,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "url" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" -dependencies = [ - "idna 0.1.5", - "matches", - "percent-encoding 1.0.1", -] - [[package]] name = "url" version = "2.5.0" @@ -7326,8 +6450,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", - "idna 0.5.0", - "percent-encoding 2.3.1", + "idna", + "percent-encoding", ] [[package]] @@ -7358,24 +6482,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" -[[package]] -name = "value-bag" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" - [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "version_check" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" - [[package]] name = "version_check" version = "0.9.4" @@ -7405,7 +6517,7 @@ dependencies = [ "fhe 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs)", "fhe-traits 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs)", "fhe-util 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs)", - "log 0.4.22", + "log", "methods", "risc0-ethereum-contracts", "risc0-zkvm", @@ -7423,12 +6535,6 @@ dependencies = [ "libc", ] -[[package]] -name = "waker-fn" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" - [[package]] name = "walkdir" version = "2.5.0" @@ -7448,12 +6554,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -7477,7 +6577,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", - "log 0.4.22", + "log", "once_cell", "proc-macro2", "quote", @@ -7816,7 +6916,7 @@ dependencies = [ "async_io_stream", "futures", "js-sys", - "log 0.4.22", + "log", "pharos", "rustc_version 0.4.0", "send_wrapper 0.6.0", @@ -7906,20 +7006,10 @@ dependencies = [ "hmac", "pbkdf2 0.11.0", "sha1", - "time 0.3.36", + "time", "zstd 0.11.2+zstd.1.5.2", ] -[[package]] -name = "zk-kit-imt" -version = "0.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4bec228e2acafec7bd22c9a0a5e0c5e2a6d0c17df69f5ad11c24ce6dc6356c6" -dependencies = [ - "hex", - "tiny-keccak", -] - [[package]] name = "zstd" version = "0.11.2+zstd.1.5.2" diff --git a/packages/server/Cargo.toml b/packages/server/Cargo.toml index 2fdbcf2..1c23d1f 100644 --- a/packages/server/Cargo.toml +++ b/packages/server/Cargo.toml @@ -5,26 +5,14 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] -console = "0.15.7" fhe = { git = "https://github.com/gnosisguild/fhe.rs", branch = "feature/greco-integration", version = "0.1.0-beta.7" } -fhe-math = { git = "https://github.com/gnosisguild/fhe.rs", branch = "feature/greco-integration", version = "0.1.0-beta.7" } fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs", branch = "feature/greco-integration", version = "0.1.0-beta.7" } -fhe-util = { git = "https://github.com/gnosisguild/fhe.rs", branch = "feature/greco-integration", version = "0.1.0-beta.7" } compute-provider = { path = "../compute_provider" } voting-risc0 = { path = "../risc0/apps" } -rand_chacha = "0.3.1" rand = "0.8.5" -getrandom = { version = "0.2.11", features = ["js"] } tokio = { version = "1.37.0", features = ["full"] } bincode = "1.3.3" -hyper = { version = "1", features = ["full"] } -http-body-util = "0.1" -hyper-util = { version = "0.1", features = ["full"] } -hyper-tls = "0.6.0" -router = "0.6.0" -walkdir = "2.5.0" dialoguer = { version = "0.11.0", features = ["fuzzy-select"] } serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0.114" @@ -32,9 +20,6 @@ wasm-bindgen = "0.2" chrono = "0.4.38" sled = "0.34" once_cell = "1.19.0" -async-std = { version = "1", features = ["attributes", "tokio1"] } -bytes = "1.6.0" -headers = "0.4.0" jwt = "0.16.0" hmac = "0.12.1" sha2 = "0.10.8" diff --git a/packages/server/Readme.md b/packages/server/Readme.md new file mode 100644 index 0000000..7067037 --- /dev/null +++ b/packages/server/Readme.md @@ -0,0 +1,87 @@ +# Enclave Server + +This is a Rust-based server implementation for an Enclave system, which handles E3 (Encrypted Execution Environment) rounds and voting processes. + +## Features + +- Create and manage voting rounds (E3 rounds) +- Secure vote casting using FHE +- Real-time blockchain event handling and processing +- RISC Zero compute provider for proof generation +- CLI for manual interaction + +## Prerequisites +- Rust (latest stable version) +- Cargo (Rust's package manager) +- Foundry (for deploying contracts) +- Anvil (for local testnet) + +## Setup + +1. Install dependencies: + ``` + cargo build --release + ``` + +2. Set up environment variables: + Create a `.env` with the following content: + ``` + PRIVATE_KEY=your_private_key + HTTP_RPC_URL=your_http_rpc_url + WS_RPC_URL=your_websocket_rpc_url + ENCLAVE_ADDRESS=your_enclave_contract_address + E3_PROGRAM_ADDRESS=your_e3_program_address + CIPHERNODE_REGISTRY_ADDRESS=your_ciphernode_registry_address + NAIVE_REGISTRY_FILTER_ADDRESS=your_naive_registry_filter_address + CHAIN_ID=your_chain_id + CRON_API_KEY=your_cron_api_key + ``` + +## Running the Server + +1. Start the enclave server: + ``` + cargo run --bin enclave_server + ``` + +2. To start the E3 cron job that requests new rounds every 24 hours, run: + ``` + cargo run --bin e3_cron + ``` + +## Using the CLI + +To interact with the CRISP system using the CLI: + +``` +cargo run --bin cli +``` + +Follow the prompts to initialize new E3 rounds, activate rounds, participate in voting, or decrypt and publish results. + +## API Endpoints + +The server exposes several RESTful API endpoints: + +- `/get_rounds`: Get the current round count +- `/get_pk_by_round`: Get the public key for a specific round +- `/get_ct_by_round`: Get the ciphertext for a specific round +- `/request_e3_round`: Request a new E3 round (protected by API key) +- `/broadcast_enc_vote`: Submit an encrypted vote +- `/get_vote_count_by_round`: Get the vote count for a specific round +- `/get_emojis_by_round`: Get the emojis associated with a round +- `/get_web_result_all`: Get results for all rounds +- `/get_round_state_lite`: Get a lightweight state of a specific round +- `/get_round_state`: Get the full state of a specific round +- `/get_web_result`: Get the web-friendly result of a specific round + +## Architecture + +The project is structured into several modules: + +- `cli`: Command-line interface for interacting with the system +- `enclave_server`: Main server implementation +- `blockchain`: Handlers for blockchain events and interactions +- `models`: Data structures used throughout the application +- `routes`: API endpoint implementations +- `database`: Database operations for storing and retrieving E3 round data diff --git a/packages/server/abi/rfv.json b/packages/server/abi/rfv.json deleted file mode 100644 index 11e14d0..0000000 --- a/packages/server/abi/rfv.json +++ /dev/null @@ -1,107 +0,0 @@ -{ - "_format": "hh-sol-artifact-1", - "contractName": "RFVoting", - "sourceName": "contracts/RFVoting.sol", - "abi": [ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "voter", - "type": "address" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "vote", - "type": "bytes" - } - ], - "name": "Voted", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "id", - "type": "address" - } - ], - "name": "getVote", - "outputs": [ - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "id", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "tester", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes", - "name": "_encVote", - "type": "bytes" - } - ], - "name": "voteEncrypted", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "voter", - "type": "address" - } - ], - "name": "votes", - "outputs": [ - { - "internalType": "bytes", - "name": "vote", - "type": "bytes" - } - ], - "stateMutability": "view", - "type": "function" - } - ], - "bytecode": "0x60c060405260046080908152631d195cdd60e21b60a05260019061002390826100da565b50600060025534801561003557600080fd5b50610199565b634e487b7160e01b600052604160045260246000fd5b600181811c9082168061006557607f821691505b60208210810361008557634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156100d557600081815260208120601f850160051c810160208610156100b25750805b601f850160051c820191505b818110156100d1578281556001016100be565b5050505b505050565b81516001600160401b038111156100f3576100f361003b565b610107816101018454610051565b8461008b565b602080601f83116001811461013c57600084156101245750858301515b600019600386901b1c1916600185901b1785556100d1565b600085815260208120601f198616915b8281101561016b5788860151825594840194600190910190840161014c565b50858210156101895787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b61046d806101a86000396000f3fe608060405234801561001057600080fd5b50600436106100675760003560e01c80638d337b81116100505780638d337b811461009f578063af640d0f146100b2578063d8bff5a5146100c957600080fd5b806351d321dd1461006c5780638308abd414610081575b600080fd5b61007f61007a3660046102b8565b6100dc565b005b610089610142565b60405161009691906103af565b60405180910390f35b6100896100ad3660046103c9565b6101d0565b6100bb60025481565b604051908152602001610096565b6100896100d73660046103c9565b610289565b600280549060006100ec836103ff565b91905055503373ffffffffffffffffffffffffffffffffffffffff167fa41dfcf9a32d4a045f0fc9b70c87033b28841009e2331a87810194c0e57bdf088260405161013791906103af565b60405180910390a250565b6001805461014f90610426565b80601f016020809104026020016040519081016040528092919081815260200182805461017b90610426565b80156101c85780601f1061019d576101008083540402835291602001916101c8565b820191906000526020600020905b8154815290600101906020018083116101ab57829003601f168201915b505050505081565b73ffffffffffffffffffffffffffffffffffffffff8116600090815260208190526040902080546060919061020490610426565b80601f016020809104026020016040519081016040528092919081815260200182805461023090610426565b801561027d5780601f106102525761010080835404028352916020019161027d565b820191906000526020600020905b81548152906001019060200180831161026057829003601f168201915b50505050509050919050565b6000602081905290815260409020805461014f90610426565b634e487b7160e01b600052604160045260246000fd5b6000602082840312156102ca57600080fd5b813567ffffffffffffffff808211156102e257600080fd5b818401915084601f8301126102f657600080fd5b813581811115610308576103086102a2565b604051601f8201601f19908116603f01168101908382118183101715610330576103306102a2565b8160405282815287602084870101111561034957600080fd5b826020860160208301376000928101602001929092525095945050505050565b6000815180845260005b8181101561038f57602081850181015186830182015201610373565b506000602082860101526020601f19601f83011685010191505092915050565b6020815260006103c26020830184610369565b9392505050565b6000602082840312156103db57600080fd5b813573ffffffffffffffffffffffffffffffffffffffff811681146103c257600080fd5b60006001820161041f57634e487b7160e01b600052601160045260246000fd5b5060010190565b600181811c9082168061043a57607f821691505b60208210810361045a57634e487b7160e01b600052602260045260246000fd5b5091905056fea164736f6c6343000815000a", - "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100675760003560e01c80638d337b81116100505780638d337b811461009f578063af640d0f146100b2578063d8bff5a5146100c957600080fd5b806351d321dd1461006c5780638308abd414610081575b600080fd5b61007f61007a3660046102b8565b6100dc565b005b610089610142565b60405161009691906103af565b60405180910390f35b6100896100ad3660046103c9565b6101d0565b6100bb60025481565b604051908152602001610096565b6100896100d73660046103c9565b610289565b600280549060006100ec836103ff565b91905055503373ffffffffffffffffffffffffffffffffffffffff167fa41dfcf9a32d4a045f0fc9b70c87033b28841009e2331a87810194c0e57bdf088260405161013791906103af565b60405180910390a250565b6001805461014f90610426565b80601f016020809104026020016040519081016040528092919081815260200182805461017b90610426565b80156101c85780601f1061019d576101008083540402835291602001916101c8565b820191906000526020600020905b8154815290600101906020018083116101ab57829003601f168201915b505050505081565b73ffffffffffffffffffffffffffffffffffffffff8116600090815260208190526040902080546060919061020490610426565b80601f016020809104026020016040519081016040528092919081815260200182805461023090610426565b801561027d5780601f106102525761010080835404028352916020019161027d565b820191906000526020600020905b81548152906001019060200180831161026057829003601f168201915b50505050509050919050565b6000602081905290815260409020805461014f90610426565b634e487b7160e01b600052604160045260246000fd5b6000602082840312156102ca57600080fd5b813567ffffffffffffffff808211156102e257600080fd5b818401915084601f8301126102f657600080fd5b813581811115610308576103086102a2565b604051601f8201601f19908116603f01168101908382118183101715610330576103306102a2565b8160405282815287602084870101111561034957600080fd5b826020860160208301376000928101602001929092525095945050505050565b6000815180845260005b8181101561038f57602081850181015186830182015201610373565b506000602082860101526020601f19601f83011685010191505092915050565b6020815260006103c26020830184610369565b9392505050565b6000602082840312156103db57600080fd5b813573ffffffffffffffffffffffffffffffffffffffff811681146103c257600080fd5b60006001820161041f57634e487b7160e01b600052601160045260246000fd5b5060010190565b600181811c9082168061043a57607f821691505b60208210810361045a57634e487b7160e01b600052602260045260246000fd5b5091905056fea164736f6c6343000815000a", - "linkReferences": {}, - "deployedLinkReferences": {} -} diff --git a/packages/server/src/cli/voting.rs b/packages/server/src/cli/voting.rs index 60f10d3..27e8031 100644 --- a/packages/server/src/cli/voting.rs +++ b/packages/server/src/cli/voting.rs @@ -159,7 +159,7 @@ fn generate_bfv_parameters() -> Result, Box Result, Box> { let degree = 2048; let plaintext_modulus: u64 = 1032193; - let moduli = vec![0x3FFFFFFF000001]; + let moduli = vec![0xffffffff00001]; Ok(BfvParametersBuilder::new() .set_degree(degree) diff --git a/packages/server/src/util.rs b/packages/server/src/util.rs index 8740518..48a703f 100644 --- a/packages/server/src/util.rs +++ b/packages/server/src/util.rs @@ -1,10 +1,6 @@ //! Utility functions -use fhe::bfv; -use fhe_traits::FheEncoder; -use fhe_util::transcode_from_bytes; -use std::{cmp::min, fmt, sync::Arc, time::Duration}; -use log::info; +use std::{fmt, time::Duration}; /// Macros to time code and display a human-readable duration. pub mod timeit { @@ -59,72 +55,4 @@ impl fmt::Display for DisplayDuration { write!(f, "{} ms", (duration_ms_times_10 as f64) / 10.0) } } -} - -// Utility functions for Private Information Retrieval. - -/// Generate a database of elements of the form [i || 0...0] where i is the 4B -/// little endian encoding of the index. When the element size is less than 4B, -/// the encoding is truncated. -#[allow(dead_code)] -pub fn generate_database(database_size: usize, elements_size: usize) -> Vec> { - assert!(database_size > 0 && elements_size > 0); - let mut database = vec![vec![0u8; elements_size]; database_size]; - for (i, element) in database.iter_mut().enumerate() { - element[..min(4, elements_size)] - .copy_from_slice(&(i as u32).to_le_bytes()[..min(4, elements_size)]); - } - database -} - -#[allow(dead_code)] -pub fn number_elements_per_plaintext( - degree: usize, - plaintext_nbits: usize, - elements_size: usize, -) -> usize { - (plaintext_nbits * degree) / (elements_size * 8) -} - -#[allow(dead_code)] -pub fn encode_database( - database: &Vec>, - par: Arc, - level: usize, -) -> (Vec, (usize, usize)) { - assert!(!database.is_empty()); - - let elements_size = database[0].len(); - let plaintext_nbits = par.plaintext().ilog2() as usize; - let number_elements_per_plaintext = - number_elements_per_plaintext(par.degree(), plaintext_nbits, elements_size); - let number_rows = - (database.len() + number_elements_per_plaintext - 1) / number_elements_per_plaintext; - info!("number_rows = {number_rows}"); - info!("number_elements_per_plaintext = {number_elements_per_plaintext}"); - let dimension_1 = (number_rows as f64).sqrt().ceil() as usize; - let dimension_2 = (number_rows + dimension_1 - 1) / dimension_1; - info!("dimensions = {dimension_1} {dimension_2}"); - info!("dimension = {}", dimension_1 * dimension_2); - let mut preprocessed_database = - vec![ - bfv::Plaintext::zero(bfv::Encoding::poly_at_level(level), &par).unwrap(); - dimension_1 * dimension_2 - ]; - (0..number_rows).for_each(|i| { - let mut serialized_plaintext = vec![0u8; number_elements_per_plaintext * elements_size]; - for j in 0..number_elements_per_plaintext { - if let Some(pt) = database.get(j + i * number_elements_per_plaintext) { - serialized_plaintext[j * elements_size..(j + 1) * elements_size].copy_from_slice(pt) - } - } - let pt_values = transcode_from_bytes(&serialized_plaintext, plaintext_nbits); - preprocessed_database[i] = - bfv::Plaintext::try_encode(&pt_values, bfv::Encoding::poly_at_level(level), &par) - .unwrap(); - }); - (preprocessed_database, (dimension_1, dimension_2)) -} - -#[allow(dead_code)] -fn main() {} \ No newline at end of file +} \ No newline at end of file diff --git a/packages/web-rust/src/bin/web_fhe_encrypt.rs b/packages/web-rust/src/bin/web_fhe_encrypt.rs index 1f6f1ca..b083f4a 100644 --- a/packages/web-rust/src/bin/web_fhe_encrypt.rs +++ b/packages/web-rust/src/bin/web_fhe_encrypt.rs @@ -38,7 +38,7 @@ impl Encrypt { pub fn encrypt_vote(&mut self, vote: u64, public_key: Vec) -> Result, JsValue> { let degree = 2048; let plaintext_modulus: u64 = 1032193; - let moduli = vec![0x3FFFFFFF000001]; + let moduli = vec![0xffffffff00001]; let params = BfvParametersBuilder::new() .set_degree(degree) @@ -85,7 +85,7 @@ fn test_encrypt_vote() { let degree = 2048; let plaintext_modulus: u64 = 1032193; // Must be Co-prime with Q - let moduli = vec![0x3FFFFFFF000001]; + let moduli = vec![0xffffffff00001]; // Must be 52-bit or less let params = BfvParametersBuilder::new() .set_degree(degree) From e674e79f54a14987642189132f5d6a42f35144ee Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Thu, 3 Oct 2024 19:36:51 +0500 Subject: [PATCH 57/62] Add Local Testnet Readme --- README.md | 188 +---------------- packages/local_testnet/Readme.md | 237 ++++++++++++++++++++++ packages/local_testnet/add_ciphernodes.sh | 10 + packages/local_testnet/run_aggregator.sh | 9 + packages/local_testnet/run_ciphernodes.sh | 51 +++++ 5 files changed, 308 insertions(+), 187 deletions(-) create mode 100644 packages/local_testnet/Readme.md create mode 100644 packages/local_testnet/add_ciphernodes.sh create mode 100644 packages/local_testnet/run_aggregator.sh create mode 100644 packages/local_testnet/run_ciphernodes.sh diff --git a/README.md b/README.md index b3cd8db..4e85e50 100644 --- a/README.md +++ b/README.md @@ -30,193 +30,7 @@ CRISP/packages imageÏ

-## Prerequisites - -Before getting started, make sure you have the following tools installed: - -- **Rust** -- **Foundry** -- **RISC Zero toolchain** -- **Node.js** (for client-side dependencies) -- **Anvil** (for local testnet) - -## Dependencies - -### Install Rust and Foundry - -You need to install Rust and Foundry first. After installation, restart your terminal. - -```sh -# Install Rust -curl https://sh.rustup.rs -sSf | sh - -# Install Foundry -curl -L https://foundry.paradigm.xyz | bash -``` - -### Install RISC Zero Toolchain - -Next, install `rzup` for the `cargo-risczero` toolchain. - -```sh -# Install rzup -curl -L https://risczero.com/install | bash - -# Install RISC Zero toolchain -rzup -``` - -Verify the installation was successful by running: - -```sh -cargo risczero --version -``` - -At this point, you should have all the tools required to develop and deploy an application with [RISC Zero](https://www.risczero.com). - -## Setting Up the Web App - -To set up the CRISP dApp in your local environment, follow these steps: - -1. Clone the repository: - - ```sh - git clone https://github.com/gnosisguild/CRISP.git - ``` - -2. Navigate to the `client` directory: - - ```sh - cd CRISP/packages/client - ``` - -3. Install dependencies: - - ```sh - yarn install - ``` - -4. Start the development server: - - ```sh - yarn dev - ``` - -## Setting Up the CRISP Server - -Setting up the CRISP server involves several components, but this guide will walk you through each step. - -### Step 1: Start a Local Testnet with Anvil - -```sh -anvil -``` - -Keep Anvil running in the terminal, and open a new terminal for the next steps. - -### Step 2: Deploy the Enclave Contracts - -1. Clone the [Enclave Repo](https://github.com/gnosisguild/enclave): - - ```sh - git clone https://github.com/gnosisguild/enclave.git - ``` - -2. Navigate to the `evm` directory: - - ```sh - cd enclave/packages/evm - ``` - -3. Install dependencies: - - ```sh - yarn install - ``` - -4. Deploy the contracts on the local testnet: - - ```sh - yarn deploy:mocks --network localhost - ``` - -After deployment, note down the addresses for the following contracts: -- Enclave -- Ciphernode Registry -- Naive Registry Filter -- Mock Input Validator - -### Step 3: Deploy the RISC Zero Contracts - -1. Navigate to the `risc0` directory. - -2. Set up environment variables by creating a `.cargo` directory and `config.toml` file: - - ```sh - mkdir .cargo && cd .cargo && touch config.toml - ``` - -3. Add the following configuration to `config.toml`: - - > ***Note:*** *This requires having access to a Bonsai API Key. To request an API key [complete the form here](https://bonsai.xyz/apply).* - ```toml - [env] - ETH_WALLET_PRIVATE_KEY="your_private_key" - BONSAI_API_KEY="your_api_key" - BONSAI_API_URL="your_api_url" - ``` - -4. In the `risc0/script` directory, update the `config.toml` with the deployed contract addresses: - - ```toml - [profile.custom] - chainId = 31337 - riscZeroVerifierAddress = "0x0000000000000000000000000000000000000000" - enclaveAddress = "your_enclave_address" - inputValidatorAddress = "your_input_validator_address" - ``` - -5. Deploy the contracts: - - ```sh - forge script --rpc-url http://localhost:8545 --broadcast script/Deploy.s.sol - ``` - -Note down the CRISPRisc0 Contract Address, which will be used as the E3 Program Address. - -### Step 4: Set up Environment Variables - -Create a `.env` file in the `server` directory with the following: - -```sh -PRIVATE_KEY=your_private_key -HTTP_RPC_URL=http://localhost:8545 -WS_RPC_URL=ws://localhost:8546 -ENCLAVE_ADDRESS=your_enclave_contract_address -E3_PROGRAM_ADDRESS=your_e3_program_address # CRISPRisc0 Contract Address -CIPHERNODE_REGISTRY_ADDRESS=your_ciphernode_registry_address -NAIVE_REGISTRY_FILTER_ADDRESS=your_naive_registry_filter_address -CHAIN_ID=your_chain_id -CRON_API_KEY=your_cron_api_key # Optional for e3_cron binary -``` - -## Running the Enclave Server - -To run the Enclave Server, navigate to the `enclave_server` directory and execute the following command: - -```sh -cargo run --bin enclave_server -``` - -## Interacting with CRISP via CLI - -Once the CLI client is running, you can interact with the CRISP voting protocol by following these steps: - -1. Select `CRISP: Voting Protocol (ETH)` from the menu. - -2. To initiate a new CRISP round, choose the option `Initialize new CRISP round`. - -Ensure all services are running correctly and that components are communicating as expected before starting a new CRISP round. +Check out the [README file in the `/packages/local_testnet` directory](packages/local_testnet/Readme.md) for detailed instructions on how to run the project locally. ## Contributing diff --git a/packages/local_testnet/Readme.md b/packages/local_testnet/Readme.md new file mode 100644 index 0000000..de4872b --- /dev/null +++ b/packages/local_testnet/Readme.md @@ -0,0 +1,237 @@ +# CRISP - Collusion-Resistant Impartial Selection Protocol +Welcome to the CRISP project! This document provides a comprehensive guide to setting up and deploying the application both locally. Follow the steps carefully to ensure that all dependencies, services, and components are properly configured. + +## Project Structure + +``` +CRISP/packages +├── /client/ +│ ├── /libs/wasm/pkg/ - WebAssembly library package +│ ├── /public/ - Static files +│ ├── /src/ - React components and source code +│ └── [configuration files and README] +├── /compute_provider/ - Helper library for RISC Zero compute provider +├── /risc0/ - RISC Zero zkVM and Verifier contracts +├── /server/ - Rust server-side logic +└── /web-rust/ - Rust to WebAssembly logic +``` + +## Prerequisites + +Before getting started, make sure you have the following tools installed: + +- **Rust** +- **Foundry** +- **RISC Zero toolchain** +- **Node.js** (for client-side dependencies) +- **Anvil** (for local testnet) + +## Dependencies + +### Install Rust and Foundry + +You need to install Rust and Foundry first. After installation, restart your terminal. + +```sh +# Install Rust +curl https://sh.rustup.rs -sSf | sh + +# Install Foundry +curl -L https://foundry.paradigm.xyz | bash +``` + +### Install RISC Zero Toolchain + +Next, install `rzup` for the `cargo-risczero` toolchain. + +```sh +# Install rzup +curl -L https://risczero.com/install | bash + +# Install RISC Zero toolchain +rzup +``` + +Verify the installation was successful by running: + +```sh +cargo risczero --version +``` + +At this point, you should have all the tools required to develop and deploy an application with [RISC Zero](https://www.risczero.com). + +## Setting Up the Web App + +To set up the CRISP dApp in your local environment, follow these steps: + +1. Clone the repository: + + ```sh + git clone https://github.com/gnosisguild/CRISP.git + ``` + +2. Navigate to the `client` directory: + + ```sh + cd CRISP/packages/client + ``` + +3. Install dependencies: + + ```sh + yarn install + ``` + +4. Start the development server: + + ```sh + yarn dev + ``` + +## Setting Up the CRISP Server + +Setting up the CRISP server involves several components, but this guide will walk you through each step. + +### Step 1: Start a Local Testnet with Anvil + +```sh +anvil +``` + +Keep Anvil running in the terminal, and open a new terminal for the next steps. + +### Step 2: Setting Up the Ciphernodes + +1. Clone the [Enclave Repo](https://github.com/gnosisguild/enclave): + + ```sh + git clone https://github.com/gnosisguild/enclave.git + ``` + +2. Navigate to the `evm` directory: + + ```sh + cd enclave/packages/evm + ``` + +3. Install dependencies: + + ```sh + yarn install + ``` + +4. Deploy the contracts on the local testnet: + + ```sh + yarn deploy:mocks --network localhost + ``` + +After deployment, note down the addresses for the following contracts: +- Enclave +- Ciphernode Registry +- Naive Registry Filter +- Mock Input Validator + +### Step 3: Deploy the RISC Zero Contracts + +1. Navigate to the `CRISP/packages/risc0` directory. + +2. Set up environment variables by creating a `.cargo` directory and `config.toml` file: + + ```sh + mkdir .cargo && cd .cargo && touch config.toml + ``` + +3. Add the following configuration to `config.toml`: + + > ***Note:*** *This requires having access to a Bonsai API Key. To request an API key [complete the form here](https://bonsai.xyz/apply).* + ```toml + [env] + ETH_WALLET_PRIVATE_KEY="your_private_key" + BONSAI_API_KEY="your_api_key" + BONSAI_API_URL="your_api_url" + ``` + +4. In the `risc0/script` directory, update the `config.toml` with the deployed contract addresses: + + ```toml + [profile.custom] + chainId = 31337 + riscZeroVerifierAddress = "0x0000000000000000000000000000000000000000" + enclaveAddress = "your_enclave_address" + inputValidatorAddress = "your_input_validator_address" + ``` + +5. Deploy the contracts: + + ```sh + forge script --rpc-url http://localhost:8545 --broadcast script/Deploy.s.sol + ``` + +Note down the CRISPRisc0 Contract Address, which will be used as the E3 Program Address. + +### Step 4: Set up Environment Variables + +Create a `.env` file in the `server` directory with the following: + +```sh +PRIVATE_KEY=your_private_key +HTTP_RPC_URL=http://localhost:8545 +WS_RPC_URL=ws://localhost:8546 +ENCLAVE_ADDRESS=your_enclave_contract_address +E3_PROGRAM_ADDRESS=your_e3_program_address # CRISPRisc0 Contract Address +CIPHERNODE_REGISTRY_ADDRESS=your_ciphernode_registry_address +NAIVE_REGISTRY_FILTER_ADDRESS=your_naive_registry_filter_address +CHAIN_ID=your_chain_id +CRON_API_KEY=your_cron_api_key # Optional for e3_cron binary +``` + +## Running Ciphernodes + +In the root `enclave` directory, you have to run the Ciphernodes. To run 4 Ciphernodes, use the provided script `run_ciphernodes.sh`. Ensure you run the script from the root `enclave` directory to set the environment variables correctly: + +```sh +./run_ciphernodes.sh +``` + +After starting the Ciphernodes, run the aggregator with the script `run_aggregator.sh`: + +```sh +./run_aggregator.sh +``` + +Once the aggregator is running, you can add the Ciphernodes to the registry with the script `add_ciphernodes.sh`: + +```sh +./add_ciphernodes.sh +``` + +## Running the Enclave Server + +To run the Enclave Server, navigate to the `enclave_server` directory and execute the following command: + +```sh +cargo run --bin enclave_server +``` + +## Interacting with CRISP via CLI + +Once the CLI client is running, you can interact with the CRISP voting protocol by following these steps: + +1. Select `CRISP: Voting Protocol (ETH)` from the menu. + +2. To initiate a new CRISP round, choose the option `Initialize new CRISP round`. + +Ensure all services are running correctly and that components are communicating as expected before starting a new CRISP round. + +## Contributing + +We welcome and encourage community contributions to this repository. Please ensure that you read and understand the [Contributor License Agreement (CLA)](https://github.com/gnosisguild/CLA) before submitting any contributions. + +## Security and Liability + +This project is provided **WITHOUT ANY WARRANTY**; without even the implied warranty of **MERCHANTABILITY** or **FITNESS FOR A PARTICULAR PURPOSE**. + +## License + +This repository is licensed under the [LGPL-3.0+ license](LICENSE). \ No newline at end of file diff --git a/packages/local_testnet/add_ciphernodes.sh b/packages/local_testnet/add_ciphernodes.sh new file mode 100644 index 0000000..c144537 --- /dev/null +++ b/packages/local_testnet/add_ciphernodes.sh @@ -0,0 +1,10 @@ +export CIPHERNODE_ADDRESS_1="0x2546BcD3c84621e976D8185a91A922aE77ECEc30" +export CIPHERNODE_ADDRESS_2="0xbDA5747bFD65F08deb54cb465eB87D40e51B197E" +export CIPHERNODE_ADDRESS_3="0xdD2FD4581271e230360230F9337D5c0430Bf44C0" +export CIPHERNODE_ADDRESS_4="0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199" + +# Add ciphernodes +yarn ciphernode:add --ciphernode-address $CIPHERNODE_ADDRESS_1 --network localhost +yarn ciphernode:add --ciphernode-address $CIPHERNODE_ADDRESS_2 --network localhost +yarn ciphernode:add --ciphernode-address $CIPHERNODE_ADDRESS_3 --network localhost +yarn ciphernode:add --ciphernode-address $CIPHERNODE_ADDRESS_4 --network localhost \ No newline at end of file diff --git a/packages/local_testnet/run_aggregator.sh b/packages/local_testnet/run_aggregator.sh new file mode 100644 index 0000000..8f88f6e --- /dev/null +++ b/packages/local_testnet/run_aggregator.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# Set common environment variables +export RPC_URL="ws://localhost:8545" +export ENCLAVE_CONTRACT="0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" +export REGISTRY_CONTRACT="0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" +export REGISTRY_FILTER_CONTRACT="0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" + +yarn ciphernode:aggregator --rpc "$RPC_URL" --enclave-contract $ENCLAVE_CONTRACT --registry-contract $REGISTRY_CONTRACT --registry-filter-contract $REGISTRY_FILTER_CONTRACT --pubkey-write-path "../../tests/basic_integration/output/pubkey.bin" --plaintext-write-path "../../tests/basic_integration/output/plaintext.txt" \ No newline at end of file diff --git a/packages/local_testnet/run_ciphernodes.sh b/packages/local_testnet/run_ciphernodes.sh new file mode 100644 index 0000000..f03fc79 --- /dev/null +++ b/packages/local_testnet/run_ciphernodes.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +# Set common environment variables +export RPC_URL="ws://localhost:8545" +export ENCLAVE_CONTRACT="0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" +export REGISTRY_CONTRACT="0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" + +# Function to run ciphernode +run_ciphernode() { + local address=$1 + local log_file=$2 + + if [ -n "$log_file" ]; then + yarn ciphernode:launch --address $address --rpc "$RPC_URL" --enclave-contract $ENCLAVE_CONTRACT --registry-contract $REGISTRY_CONTRACT > "$log_file" 2>&1 & + echo "Started ciphernode for address $address (PID: $!) - Logging to $log_file" + else + yarn ciphernode:launch --address $address --rpc "$RPC_URL" --enclave-contract $ENCLAVE_CONTRACT --registry-contract $REGISTRY_CONTRACT & + echo "Started ciphernode for address $address (PID: $!)" + fi +} + +# Check if an argument is provided +if [ "$1" = "--log" ]; then + log_to_file=true +else + log_to_file=false +fi + +# Run ciphernodes +addresses=( + "0x2546BcD3c84621e976D8185a91A922aE77ECEc30" + "0xbDA5747bFD65F08deb54cb465eB87D40e51B197E" + "0xdD2FD4581271e230360230F9337D5c0430Bf44C0" + "0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199" +) + +for address in "${addresses[@]}"; do + if $log_to_file; then + run_ciphernode "$address" "ciphernode_$address.log" + else + run_ciphernode "$address" + fi +done + +# If logging to files, use tail to display logs in real-time +if $log_to_file; then + tail -f ciphernode_*.log +else + # Wait for all background processes to finish + wait +fi \ No newline at end of file From b72f09aaa4f32d904c6ebde6d01c9ce4244e5e41 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Thu, 3 Oct 2024 19:37:39 +0500 Subject: [PATCH 58/62] Update Readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4e85e50..8e2268a 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ CRISP/packages imageÏ

+## Running the project locally + Check out the [README file in the `/packages/local_testnet` directory](packages/local_testnet/Readme.md) for detailed instructions on how to run the project locally. ## Contributing From 1aa2d92afb8195ddd9d94470b20daa0f76961ed7 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Thu, 3 Oct 2024 19:39:08 +0500 Subject: [PATCH 59/62] Update Scripts --- packages/local_testnet/add_ciphernodes.sh | 10 ++++++---- packages/local_testnet/run_aggregator.sh | 8 ++++---- packages/local_testnet/run_ciphernodes.sh | 6 +++--- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/local_testnet/add_ciphernodes.sh b/packages/local_testnet/add_ciphernodes.sh index c144537..0fcfb97 100644 --- a/packages/local_testnet/add_ciphernodes.sh +++ b/packages/local_testnet/add_ciphernodes.sh @@ -1,7 +1,9 @@ -export CIPHERNODE_ADDRESS_1="0x2546BcD3c84621e976D8185a91A922aE77ECEc30" -export CIPHERNODE_ADDRESS_2="0xbDA5747bFD65F08deb54cb465eB87D40e51B197E" -export CIPHERNODE_ADDRESS_3="0xdD2FD4581271e230360230F9337D5c0430Bf44C0" -export CIPHERNODE_ADDRESS_4="0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199" +#!/bin/bash + +CIPHERNODE_ADDRESS_1="0x2546BcD3c84621e976D8185a91A922aE77ECEc30" +CIPHERNODE_ADDRESS_2="0xbDA5747bFD65F08deb54cb465eB87D40e51B197E" +CIPHERNODE_ADDRESS_3="0xdD2FD4581271e230360230F9337D5c0430Bf44C0" +CIPHERNODE_ADDRESS_4="0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199" # Add ciphernodes yarn ciphernode:add --ciphernode-address $CIPHERNODE_ADDRESS_1 --network localhost diff --git a/packages/local_testnet/run_aggregator.sh b/packages/local_testnet/run_aggregator.sh index 8f88f6e..77d7759 100644 --- a/packages/local_testnet/run_aggregator.sh +++ b/packages/local_testnet/run_aggregator.sh @@ -1,9 +1,9 @@ #!/bin/bash # Set common environment variables -export RPC_URL="ws://localhost:8545" -export ENCLAVE_CONTRACT="0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" -export REGISTRY_CONTRACT="0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" -export REGISTRY_FILTER_CONTRACT="0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" +RPC_URL="ws://localhost:8545" +ENCLAVE_CONTRACT="0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" +REGISTRY_CONTRACT="0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" +REGISTRY_FILTER_CONTRACT="0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" yarn ciphernode:aggregator --rpc "$RPC_URL" --enclave-contract $ENCLAVE_CONTRACT --registry-contract $REGISTRY_CONTRACT --registry-filter-contract $REGISTRY_FILTER_CONTRACT --pubkey-write-path "../../tests/basic_integration/output/pubkey.bin" --plaintext-write-path "../../tests/basic_integration/output/plaintext.txt" \ No newline at end of file diff --git a/packages/local_testnet/run_ciphernodes.sh b/packages/local_testnet/run_ciphernodes.sh index f03fc79..b046fc7 100644 --- a/packages/local_testnet/run_ciphernodes.sh +++ b/packages/local_testnet/run_ciphernodes.sh @@ -1,9 +1,9 @@ #!/bin/bash # Set common environment variables -export RPC_URL="ws://localhost:8545" -export ENCLAVE_CONTRACT="0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" -export REGISTRY_CONTRACT="0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" +RPC_URL="ws://localhost:8545" +ENCLAVE_CONTRACT="0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" +REGISTRY_CONTRACT="0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" # Function to run ciphernode run_ciphernode() { From 63146a074d865c30ee3d8e3a98e91a31f770fa71 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Mon, 7 Oct 2024 17:02:26 +0500 Subject: [PATCH 60/62] Refactor server, Naming, Remove unused code and Documentation --- .../voteManagement/VoteManagement.context.tsx | 14 +- .../client/src/contracts/rfv/abi/rfvAbi.ts | 15 -- packages/client/src/contracts/rfv/index.ts | 1 - .../src/hooks/enclave/useEnclaveServer.ts | 37 ++--- packages/client/src/model/vote.model.ts | 5 +- packages/client/src/utils/methods.ts | 13 -- packages/server/Cargo.lock | 93 +++++++------ packages/server/Cargo.toml | 74 +++++++--- packages/server/Readme.md | 28 ++-- packages/server/src/bin/enclave_server.rs | 5 - .../server/src/cli/{voting.rs => commands.rs} | 14 +- packages/server/src/cli/{mod.rs => main.rs} | 34 +---- .../src/{bin/e3_cron.rs => cron/main.rs} | 0 .../src/enclave_server/blockchain/sync.rs | 77 ----------- .../server/src/enclave_server/database.rs | 113 ---------------- .../server/src/enclave_server/routes/index.rs | 19 --- .../server/src/enclave_server/routes/state.rs | 113 ---------------- .../src/enclave_server/routes/voting.rs | 75 ---------- packages/server/src/lib.rs | 5 +- packages/server/src/logger.rs | 27 ++++ .../blockchain/events.rs | 0 .../blockchain/handlers.rs | 39 ++++-- .../blockchain/listener.rs | 0 .../blockchain/mod.rs | 3 +- .../blockchain/relayer.rs | 2 +- .../src/{enclave_server => server}/config.rs | 0 packages/server/src/server/database.rs | 78 +++++++++++ .../server/src/{bin/cli.rs => server/main.rs} | 6 +- .../src/{enclave_server => server}/mod.rs | 33 +---- .../src/{enclave_server => server}/models.rs | 128 ++++++++++-------- .../{enclave_server => server}/routes/auth.rs | 21 ++- .../{enclave_server => server}/routes/mod.rs | 2 - .../routes/rounds.rs | 106 ++++++++++----- packages/server/src/server/routes/state.rs | 88 ++++++++++++ packages/server/src/server/routes/voting.rs | 96 +++++++++++++ packages/server/src/util.rs | 58 -------- 36 files changed, 638 insertions(+), 784 deletions(-) delete mode 100644 packages/client/src/contracts/rfv/abi/rfvAbi.ts delete mode 100644 packages/client/src/contracts/rfv/index.ts delete mode 100644 packages/server/src/bin/enclave_server.rs rename packages/server/src/cli/{voting.rs => commands.rs} (95%) rename packages/server/src/cli/{mod.rs => main.rs} (70%) rename packages/server/src/{bin/e3_cron.rs => cron/main.rs} (100%) delete mode 100644 packages/server/src/enclave_server/blockchain/sync.rs delete mode 100644 packages/server/src/enclave_server/database.rs delete mode 100644 packages/server/src/enclave_server/routes/index.rs delete mode 100644 packages/server/src/enclave_server/routes/state.rs delete mode 100644 packages/server/src/enclave_server/routes/voting.rs create mode 100644 packages/server/src/logger.rs rename packages/server/src/{enclave_server => server}/blockchain/events.rs (100%) rename packages/server/src/{enclave_server => server}/blockchain/handlers.rs (85%) rename packages/server/src/{enclave_server => server}/blockchain/listener.rs (100%) rename packages/server/src/{enclave_server => server}/blockchain/mod.rs (62%) rename packages/server/src/{enclave_server => server}/blockchain/relayer.rs (99%) rename packages/server/src/{enclave_server => server}/config.rs (100%) create mode 100644 packages/server/src/server/database.rs rename packages/server/src/{bin/cli.rs => server/main.rs} (63%) rename packages/server/src/{enclave_server => server}/mod.rs (52%) rename packages/server/src/{enclave_server => server}/models.rs (70%) rename packages/server/src/{enclave_server => server}/routes/auth.rs (72%) rename packages/server/src/{enclave_server => server}/routes/mod.rs (85%) rename packages/server/src/{enclave_server => server}/routes/rounds.rs (58%) create mode 100644 packages/server/src/server/routes/state.rs create mode 100644 packages/server/src/server/routes/voting.rs delete mode 100644 packages/server/src/util.rs diff --git a/packages/client/src/context/voteManagement/VoteManagement.context.tsx b/packages/client/src/context/voteManagement/VoteManagement.context.tsx index fa01e18..7fe01f4 100644 --- a/packages/client/src/context/voteManagement/VoteManagement.context.tsx +++ b/packages/client/src/context/voteManagement/VoteManagement.context.tsx @@ -38,22 +38,22 @@ const VoteManagementProvider = ({ children }: VoteManagementProviderProps) => { getWebResultByRound, getToken, getWebResult, - getRound, + getCurrentRound, broadcastVote, } = useEnclaveServer() const initialLoad = async () => { console.log("Loading wasm"); - const round = await getRound() - if (round) { - await getRoundStateLite(round.round_count) + const currentRound = await getCurrentRound() + if (currentRound) { + await getRoundStateLite(currentRound.id) } } const existNewRound = async () => { - const round = await getRound() - if (round && votingRound && round.round_count > votingRound.round_id) { - await getRoundStateLite(round.round_count) + const currentRound = await getCurrentRound() + if (currentRound && votingRound && currentRound.id > votingRound.round_id) { + await getRoundStateLite(currentRound.id) } } diff --git a/packages/client/src/contracts/rfv/abi/rfvAbi.ts b/packages/client/src/contracts/rfv/abi/rfvAbi.ts deleted file mode 100644 index e5b1520..0000000 --- a/packages/client/src/contracts/rfv/abi/rfvAbi.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const RFV_ABI = [ - { - inputs: [ - { - internalType: 'bytes', - name: '_encVote', - type: 'bytes', - }, - ], - name: 'voteEncrypted', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, -] diff --git a/packages/client/src/contracts/rfv/index.ts b/packages/client/src/contracts/rfv/index.ts deleted file mode 100644 index 6399843..0000000 --- a/packages/client/src/contracts/rfv/index.ts +++ /dev/null @@ -1 +0,0 @@ -export const RFV_CONTRACT_ADDRESS = '0x5FbDB2315678afecb367f032d93F642f64180aa3' diff --git a/packages/client/src/hooks/enclave/useEnclaveServer.ts b/packages/client/src/hooks/enclave/useEnclaveServer.ts index c81f9f7..001ff0b 100644 --- a/packages/client/src/hooks/enclave/useEnclaveServer.ts +++ b/packages/client/src/hooks/enclave/useEnclaveServer.ts @@ -1,8 +1,7 @@ import { handleGenericError } from '@/utils/handle-generic-error' -import { BroadcastVoteRequest, BroadcastVoteResponse, RoundCount, VoteStateLite } from '@/model/vote.model' +import { BroadcastVoteRequest, BroadcastVoteResponse, CurrentRound, VoteStateLite } from '@/model/vote.model' import { useApi } from '../generic/useFetchApi' import { PollRequestResult } from '@/model/poll.model' -import { fixPollResult, fixResult } from '@/utils/methods' import { Auth } from '@/model/auth.model' @@ -11,42 +10,30 @@ const ENCLAVE_API = import.meta.env.VITE_ENCLAVE_API if (!ENCLAVE_API) handleGenericError('useEnclaveServer', { name: 'ENCLAVE_API', message: 'Missing env VITE_ENCLAVE_API' }) const EnclaveEndpoints = { - GetRound: `${ENCLAVE_API}/get_rounds`, - GetRoundStateLite: `${ENCLAVE_API}/get_round_state_lite`, - GetWebResult: `${ENCLAVE_API}/get_web_result`, - GetWebAllResult: `${ENCLAVE_API}/get_web_result_all`, - BroadcastVote: `${ENCLAVE_API}/broadcast_enc_vote`, - Authentication: `${ENCLAVE_API}/authentication_login`, + GetCurrentRound: `${ENCLAVE_API}/rounds/current`, + GetRoundStateLite: `${ENCLAVE_API}/state/lite`, + GetWebResult: `${ENCLAVE_API}/state/result`, + GetWebAllResult: `${ENCLAVE_API}/state/all`, + BroadcastVote: `${ENCLAVE_API}/voting/broadcast`, + Authentication: `${ENCLAVE_API}/auth/login`, } as const export const useEnclaveServer = () => { - const { GetRound, GetWebAllResult, BroadcastVote, GetRoundStateLite, Authentication, GetWebResult } = EnclaveEndpoints + const { GetCurrentRound, GetWebAllResult, BroadcastVote, GetRoundStateLite, Authentication, GetWebResult } = EnclaveEndpoints const { fetchData, isLoading } = useApi() - const getRound = () => fetchData(GetRound) + const getCurrentRound = () => fetchData(GetCurrentRound) const getToken = (postId: string) => fetchData(Authentication, 'post', { postId }) const getRoundStateLite = (round_id: number) => fetchData(GetRoundStateLite, 'post', { round_id }) const broadcastVote = (vote: BroadcastVoteRequest) => fetchData(BroadcastVote, 'post', vote) - const getWebResult = async () => { - const result = await fetchData<{ states: PollRequestResult[] }, void>(GetWebAllResult, 'get') - if (result) { - return fixPollResult(result.states) - } - return - } - const getWebResultByRound = async (round_id: number) => { - const result = await fetchData(GetWebResult, 'post', { round_id }) - if (result) { - return fixResult(result) - } - return - } + const getWebResult = () => fetchData(GetWebAllResult, 'get') + const getWebResultByRound = (round_id: number) => fetchData(GetWebResult, 'post', { round_id }) return { isLoading, getWebResultByRound, getToken, getWebResult, - getRound, + getCurrentRound, getRoundStateLite, broadcastVote, } diff --git a/packages/client/src/model/vote.model.ts b/packages/client/src/model/vote.model.ts index 30758d6..184005f 100644 --- a/packages/client/src/model/vote.model.ts +++ b/packages/client/src/model/vote.model.ts @@ -10,8 +10,9 @@ export interface VotingRound { round_id: number pk_bytes: number[] } -export interface RoundCount { - round_count: number + +export interface CurrentRound { + id: number } export interface BroadcastVoteRequest { diff --git a/packages/client/src/utils/methods.ts b/packages/client/src/utils/methods.ts index a758fa7..8883c03 100644 --- a/packages/client/src/utils/methods.ts +++ b/packages/client/src/utils/methods.ts @@ -45,19 +45,6 @@ export const formatDate = (isoDateString: string): string => { return `${dateFormatter.format(date)} - ${timeFormatter.format(date)}` } -export const fixResult = (poll: PollRequestResult): PollRequestResult => { - let fixedPollResult = { ...poll } - fixedPollResult.option_1_tally = poll.option_2_tally - fixedPollResult.option_2_tally = poll.option_1_tally - return fixedPollResult -} - -export const fixPollResult = (polls: PollRequestResult[]): PollRequestResult[] => { - return polls.map((poll) => { - return fixResult(poll) - }) -} - export const convertPollData = (request: PollRequestResult[]): PollResult[] => { const pollResults = request.map((poll) => { const totalVotes = poll.total_votes diff --git a/packages/server/Cargo.lock b/packages/server/Cargo.lock index 420f61c..ad8a5e1 100644 --- a/packages/server/Cargo.lock +++ b/packages/server/Cargo.lock @@ -1563,13 +1563,13 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.97" +version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" +checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" dependencies = [ "jobserver", "libc", - "once_cell", + "shlex", ] [[package]] @@ -1855,6 +1855,43 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crisp" +version = "0.1.0" +dependencies = [ + "actix-cors", + "actix-web", + "alloy", + "alloy-primitives 0.6.4", + "alloy-sol-types 0.6.4", + "bincode", + "chrono", + "compute-provider", + "config", + "dialoguer", + "dotenvy", + "env_logger 0.11.5", + "eyre", + "fhe 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs?branch=feature/greco-integration)", + "fhe-traits 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs?branch=feature/greco-integration)", + "futures-util", + "hex", + "hmac", + "jwt", + "log", + "once_cell", + "rand", + "reqwest 0.12.8", + "serde", + "serde_json", + "sha2", + "sled", + "thiserror", + "tokio", + "voting-risc0", + "wasm-bindgen", +] + [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -4895,42 +4932,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "rfv" -version = "0.1.0" -dependencies = [ - "actix-cors", - "actix-web", - "alloy", - "alloy-primitives 0.6.4", - "alloy-sol-types 0.6.4", - "bincode", - "chrono", - "compute-provider", - "config", - "dialoguer", - "dotenvy", - "env_logger 0.11.5", - "eyre", - "fhe 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs?branch=feature/greco-integration)", - "fhe-traits 0.1.0-beta.7 (git+https://github.com/gnosisguild/fhe.rs?branch=feature/greco-integration)", - "futures-util", - "hex", - "hmac", - "jwt", - "log", - "once_cell", - "rand", - "reqwest 0.12.8", - "serde", - "serde_json", - "sha2", - "sled", - "tokio", - "voting-risc0", - "wasm-bindgen", -] - [[package]] name = "ring" version = "0.16.20" @@ -5630,6 +5631,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -5960,18 +5967,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.60" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.60" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", diff --git a/packages/server/Cargo.toml b/packages/server/Cargo.toml index 1c23d1f..be6e69c 100644 --- a/packages/server/Cargo.toml +++ b/packages/server/Cargo.toml @@ -1,38 +1,70 @@ [package] -name = "rfv" +name = "crisp" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[[bin]] +name = "server" +path = "src/server/main.rs" + +[[bin]] +name = "cli" +path = "src/cli/main.rs" + +[[bin]] +name = "cron" +path = "src/cron/main.rs" [dependencies] +# Web framework and related +actix-cors = "0.7.0" +actix-web = "4.9.0" + +# Async and networking +futures-util = "0.3" +reqwest = { version = "0.12.8", features = ["json"] } +tokio = { version = "1.37.0", features = ["full"] } + +# Cryptography and blockchain +alloy = { version = "0.2.1", features = ["full", "rpc-types-eth"] } +alloy-primitives = { version = "0.6", default-features = false, features = ["rlp", "serde", "std"] } +alloy-sol-types = { version = "0.6" } fhe = { git = "https://github.com/gnosisguild/fhe.rs", branch = "feature/greco-integration", version = "0.1.0-beta.7" } fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs", branch = "feature/greco-integration", version = "0.1.0-beta.7" } +hmac = "0.12.1" +jwt = "0.16.0" +sha2 = "0.10.8" + +# Local dependencies compute-provider = { path = "../compute_provider" } voting-risc0 = { path = "../risc0/apps" } -rand = "0.8.5" -tokio = { version = "1.37.0", features = ["full"] } -bincode = "1.3.3" + +# CLI and user interaction dialoguer = { version = "0.11.0", features = ["fuzzy-select"] } + +# Serialization and deserialization +bincode = "1.3.3" serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0.114" -wasm-bindgen = "0.2" + +# Utility libraries chrono = "0.4.38" -sled = "0.34" -once_cell = "1.19.0" -jwt = "0.16.0" -hmac = "0.12.1" -sha2 = "0.10.8" -log = "0.4.22" -env_logger = "0.11.5" -actix-web = "4.9.0" -actix-cors = "0.7.0" -alloy-primitives = { version = "0.6", default-features = false, features = ["rlp", "serde", "std"] } -alloy-sol-types = { version = "0.6" } -alloy = { version = "0.2.1", features = ["full", "rpc-types-eth"] } -futures-util = "0.3" eyre = "0.6" hex = "0.4" -dotenvy = "0.15.7" +once_cell = "1.19.0" +rand = "0.8.5" +thiserror = "1.0.64" + +# Database +sled = "0.34.7" + +# WebAssembly +wasm-bindgen = "0.2" + +# Logging +env_logger = "0.11.5" +log = "0.4.22" + +# Configuration config = "0.14.0" -reqwest = { version = "0.12.8", features = ["json"] } \ No newline at end of file +dotenvy = "0.15.7" diff --git a/packages/server/Readme.md b/packages/server/Readme.md index 7067037..0bf841a 100644 --- a/packages/server/Readme.md +++ b/packages/server/Readme.md @@ -39,14 +39,14 @@ This is a Rust-based server implementation for an Enclave system, which handles ## Running the Server -1. Start the enclave server: +1. Start the crisp server: ``` - cargo run --bin enclave_server + cargo run --bin server ``` 2. To start the E3 cron job that requests new rounds every 24 hours, run: ``` - cargo run --bin e3_cron + cargo run --bin cron ``` ## Using the CLI @@ -63,24 +63,22 @@ Follow the prompts to initialize new E3 rounds, activate rounds, participate in The server exposes several RESTful API endpoints: -- `/get_rounds`: Get the current round count -- `/get_pk_by_round`: Get the public key for a specific round -- `/get_ct_by_round`: Get the ciphertext for a specific round -- `/request_e3_round`: Request a new E3 round (protected by API key) -- `/broadcast_enc_vote`: Submit an encrypted vote -- `/get_vote_count_by_round`: Get the vote count for a specific round -- `/get_emojis_by_round`: Get the emojis associated with a round -- `/get_web_result_all`: Get results for all rounds -- `/get_round_state_lite`: Get a lightweight state of a specific round -- `/get_round_state`: Get the full state of a specific round -- `/get_web_result`: Get the web-friendly result of a specific round +- `POST /auth/login`: Authenticate a user login +- `GET /rounds/current`: Get the current round information +- `POST /rounds/public-key`: Get the public key for a specific round +- `POST /rounds/ciphertext`: Get the ciphertext for a specific round +- `POST /rounds/request`: Request a new E3 round (protected by API key) +- `POST /state/result`: Get the result for a specific round +- `GET /state/all`: Get results for all rounds +- `POST /state/lite`: Get a lite version of the state for a specific round +- `POST /voting/broadcast`: Broadcast an encrypted vote ## Architecture The project is structured into several modules: - `cli`: Command-line interface for interacting with the system -- `enclave_server`: Main server implementation +- `server`: Main server implementation - `blockchain`: Handlers for blockchain events and interactions - `models`: Data structures used throughout the application - `routes`: API endpoint implementations diff --git a/packages/server/src/bin/enclave_server.rs b/packages/server/src/bin/enclave_server.rs deleted file mode 100644 index fee968a..0000000 --- a/packages/server/src/bin/enclave_server.rs +++ /dev/null @@ -1,5 +0,0 @@ -use rfv::enclave_server::start_server; - -fn main() -> Result<(), Box> { - start_server() -} diff --git a/packages/server/src/cli/voting.rs b/packages/server/src/cli/commands.rs similarity index 95% rename from packages/server/src/cli/voting.rs rename to packages/server/src/cli/commands.rs index 27e8031..b7173fe 100644 --- a/packages/server/src/cli/voting.rs +++ b/packages/server/src/cli/commands.rs @@ -5,14 +5,12 @@ use reqwest::Client; use log::info; use alloy::primitives::{Address, Bytes, U256}; -use crate::enclave_server::blockchain::relayer::EnclaveContract; -use crate::cli::GLOBAL_DB; -use crate::util::timeit::timeit; +use crisp::server::blockchain::relayer::EnclaveContract; use fhe::bfv::{BfvParameters, BfvParametersBuilder, Encoding, Plaintext, PublicKey, SecretKey, Ciphertext}; use fhe_traits::{DeserializeParametrized, FheDecoder, FheDecrypter, FheEncoder, FheEncrypter, Serialize as FheSerialize}; use rand::thread_rng; -use super::CONFIG; +use super::{CONFIG, CLI_DB}; #[derive(Debug, Deserialize, Serialize)] struct FHEParams { @@ -56,7 +54,7 @@ pub async fn initialize_crisp_round() -> Result<(), Box Result<(), Box Result<(), Box Result<(), Box>> = Lazy::new(|| { +pub static CLI_DB: Lazy>> = Lazy::new(|| { let pathdb = std::env::current_dir().unwrap().join("database/cli"); Arc::new(RwLock::new(sled::open(pathdb).unwrap())) }); -fn init_logger() { - let mut builder = Builder::new(); - builder - .target(Target::Stdout) - .filter(None, LevelFilter::Info) - .format(|buf, record: &Record| { - let file = record.file().unwrap_or("unknown"); - let filename = Path::new(file).file_name().unwrap_or_else(|| file.as_ref()); - - writeln!( - buf, - "[{}:{}] - {}", - filename.to_string_lossy(), - record.line().unwrap_or(0), - record.args() - ) - }) - .init(); -} - #[tokio::main] -pub async fn run_cli() -> Result<(), Box> { +pub async fn main() -> Result<(), Box> { init_logger(); let client = Client::new(); diff --git a/packages/server/src/bin/e3_cron.rs b/packages/server/src/cron/main.rs similarity index 100% rename from packages/server/src/bin/e3_cron.rs rename to packages/server/src/cron/main.rs diff --git a/packages/server/src/enclave_server/blockchain/sync.rs b/packages/server/src/enclave_server/blockchain/sync.rs deleted file mode 100644 index 0625fdd..0000000 --- a/packages/server/src/enclave_server/blockchain/sync.rs +++ /dev/null @@ -1,77 +0,0 @@ -use super::relayer::EnclaveContract; -use crate::enclave_server::config::CONFIG; -use crate::enclave_server::database::{get_e3, get_e3_round, save_e3, generate_emoji}; -use crate::enclave_server::models::E3; -use alloy::primitives::U256; -use chrono::Utc; -use eyre::Result; -use log::info; -pub async fn sync_contracts_db() -> Result<(), Box> { - info!("Syncing contracts with database"); - let contract = EnclaveContract::new(CONFIG.enclave_address.clone()).await?; - let contract_e3_id = contract.get_e3_id().await?.to::(); - let db_e3_id = get_e3_round().await?; - - if contract_e3_id == 0 { - info!("No E3s found in contract, skipping sync"); - return Ok(()); - } - - // Update existing E3 if expired - if let Ok((mut e3, key)) = get_e3(db_e3_id).await { - if e3.status != "Finished" && e3.status == "Published" && e3.expiration < Utc::now().timestamp() as u64 { - let c_e3 = contract.get_e3(U256::from(db_e3_id)).await?; - let inputs_count = contract.get_input_count(U256::from(db_e3_id)).await?.to::(); - - e3.plaintext_output = c_e3.plaintextOutput.to_vec(); - e3.votes_option_2 = u64::from_be_bytes(e3.plaintext_output.as_slice().try_into()?); - e3.votes_option_1 = inputs_count - e3.votes_option_2; - e3.status = "Finished".to_string(); - - save_e3(&e3, &key).await?; - } - } - - // Sync new E3s - for e3_id in db_e3_id + 1..=contract_e3_id { - let e3 = contract.get_e3(U256::from(e3_id)).await?; - let inputs_count = contract.get_input_count(U256::from(e3_id)).await?.to::(); - - let (status, votes) = if e3.plaintextOutput.is_empty() { - if e3.ciphertextOutput.is_empty() { - ("Active", 0) - } else { - ("Published", 0) - } - } else { - let votes = u64::from_be_bytes(e3.plaintextOutput.to_vec().as_slice().try_into()?); - ("Finished", votes) - }; - - let e3_obj = E3 { - id: e3_id, - chain_id: CONFIG.chain_id, - enclave_address: contract.contract_address.to_string(), - status: status.to_string(), - has_voted: vec!["".to_string()], - vote_count: inputs_count, - votes_option_1: inputs_count - votes, - votes_option_2: votes, - start_time: e3.expiration.to::() - e3.duration.to::(), - block_start: 0, - duration: e3.duration.to::(), - expiration: e3.expiration.to::(), - e3_params: e3.e3ProgramParams.to_vec(), - committee_public_key: e3.committeePublicKey.to_vec(), - ciphertext_output: e3.ciphertextOutput.to_vec(), - plaintext_output: e3.plaintextOutput.to_vec(), - ciphertext_inputs: vec![], - emojis: generate_emoji().into(), - }; - - save_e3(&e3_obj, &format!("e3:{}", e3_id)).await?; - } - - info!("Contracts synced with database"); - Ok(()) -} \ No newline at end of file diff --git a/packages/server/src/enclave_server/database.rs b/packages/server/src/enclave_server/database.rs deleted file mode 100644 index 9126e93..0000000 --- a/packages/server/src/enclave_server/database.rs +++ /dev/null @@ -1,113 +0,0 @@ -use super::models::E3; -use log::info; -use once_cell::sync::Lazy; -use rand::Rng; -use sled::{Db, IVec}; -use std::{error::Error, str, sync::Arc}; -use tokio::sync::RwLock; - -pub static GLOBAL_DB: Lazy>> = Lazy::new(|| { - let pathdb = std::env::current_dir() - .unwrap() - .join("database/enclave_server"); - Arc::new(RwLock::new(sled::open(pathdb).unwrap())) -}); - -pub async fn get_e3(e3_id: u64) -> Result<(E3, String), Box> { - let key = format!("e3:{}", e3_id); - - let db = GLOBAL_DB.read().await; - - let value = match db.get(key.clone()) { - Ok(Some(v)) => v, - Ok(None) => return Err(format!("E3 not found: {}", key).into()), - Err(e) => return Err(format!("Database error: {}", e).into()), - }; - - let e3: E3 = - serde_json::from_slice(&value).map_err(|e| format!("Failed to deserialize E3: {}", e))?; - - Ok((e3, key)) -} - -pub async fn save_e3(e3: &E3, key: &str) -> Result<(), Box> { - let db = GLOBAL_DB.write().await; - match db.insert(key.to_string(), serde_json::to_vec(e3)?) { - Ok(_) => (), - Err(e) => return Err(format!("Failed to save E3: {}", e).into()), - }; - db.flush().unwrap(); - Ok(()) -} - -pub async fn get_e3_round() -> Result> { - let key = "e3:round"; - - let db = GLOBAL_DB.read().await; - - let round_count: u64 = match db.get(key) { - Ok(Some(bytes)) => match bincode::deserialize::(&bytes) { - Ok(count) => count, - Err(e) => { - info!("Failed to deserialize round count: {}", e); - return Err(format!("Failed to retrieve round count").into()); - } - }, - Ok(None) => { - drop(db); - let db = GLOBAL_DB.write().await; - - info!("Initializing first round in db"); - let initial_count = 0u64; - let encoded = bincode::serialize(&initial_count).unwrap(); - if let Err(e) = db.insert(key, IVec::from(encoded)) { - info!("Failed to initialize first round in db: {}", e); - return Err(format!("Failed to initialize round count").into()); - } - initial_count - } - Err(e) => { - info!("Database error: {}", e); - return Err(format!("Database error").into()); - } - }; - - Ok(round_count) -} - -pub async fn increment_e3_round() -> Result<(), Box> { - let key = "e3:round"; - - let new_round_count = match get_e3_round().await { - Ok(round_count) => round_count + 1, - Err(e) => return Err(e), - }; - - let db = GLOBAL_DB.write().await; - db.insert(key, IVec::from(bincode::serialize(&new_round_count)?))?; - - Ok(()) -} - -pub fn generate_emoji() -> (String, String) { - let emojis = [ - "🍇", "🍈", "🍉", "🍊", "🍋", "🍌", "🍍", "ðŸĨ­", "🍎", "🍏", "🍐", "🍑", "🍒", "🍓", "ðŸŦ", - "ðŸĨ", "🍅", "ðŸŦ’", "ðŸĨĨ", "ðŸĨ‘", "🍆", "ðŸĨ”", "ðŸĨ•", "ðŸŒ―", "ðŸŒķïļ", "ðŸŦ‘", "ðŸĨ’", "ðŸĨŽ", "ðŸĨĶ", "🧄", - "🧅", "🍄", "ðŸĨœ", "ðŸŦ˜", "🌰", "🍞", "ðŸĨ", "ðŸĨ–", "ðŸŦ“", "ðŸĨĻ", "ðŸĨŊ", "ðŸĨž", "🧇", "🧀", "🍖", - "🍗", "ðŸĨĐ", "ðŸĨ“", "🍔", "🍟", "🍕", "🌭", "ðŸĨŠ", "ðŸŒŪ", "ðŸŒŊ", "ðŸŦ”", "ðŸĨ™", "🧆", "ðŸĨš", "ðŸģ", - "ðŸĨ˜", "ðŸē", "ðŸŦ•", "ðŸĨĢ", "ðŸĨ—", "ðŸŋ", "🧈", "🧂", "ðŸĨŦ", "ðŸą", "🍘", "🍙", "🍚", "🍛", "🍜", - "🍝", "🍠", "ðŸĒ", "ðŸĢ", "ðŸĪ", "ðŸĨ", "ðŸĨŪ", "ðŸĄ", "ðŸĨŸ", "ðŸĨ ", "ðŸĨĄ", "ðŸĶ€", "ðŸĶž", "ðŸĶ", "ðŸĶ‘", - "ðŸĶŠ", "ðŸĶ", "🍧", "ðŸĻ", "ðŸĐ", "🍊", "🎂", "🍰", "🧁", "ðŸĨ§", "ðŸŦ", "🍎", "🍭", "ðŸŪ", "ðŸŊ", - "🍞", "ðŸĨ›", "☕", "ðŸĩ", "ðŸū", "🍷", "ðŸļ", "ðŸđ", "🍚", "ðŸŧ", "ðŸĨ‚", "ðŸĨƒ", - ]; - let mut index1 = rand::thread_rng().gen_range(0..emojis.len()); - let index2 = rand::thread_rng().gen_range(0..emojis.len()); - if index1 == index2 { - if index1 == emojis.len() { - index1 = index1 - 1; - } else { - index1 = index1 + 1; - }; - }; - (emojis[index1].to_string(), emojis[index2].to_string()) -} diff --git a/packages/server/src/enclave_server/routes/index.rs b/packages/server/src/enclave_server/routes/index.rs deleted file mode 100644 index 09e8c10..0000000 --- a/packages/server/src/enclave_server/routes/index.rs +++ /dev/null @@ -1,19 +0,0 @@ -use actix_web::{web, HttpResponse, Responder}; - -use crate::enclave_server::models::JsonResponse; - -pub fn setup_routes(config: &mut web::ServiceConfig) { - config - .route("/", web::get().to(index_handler)) - .route("/health", web::get().to(health_handler)); -} - -async fn index_handler() -> impl Responder { - HttpResponse::Ok().json(JsonResponse { - response: "Welcome to the enclave server!".to_string(), - }) -} - -async fn health_handler() -> impl Responder { - HttpResponse::Ok().finish() -} diff --git a/packages/server/src/enclave_server/routes/state.rs b/packages/server/src/enclave_server/routes/state.rs deleted file mode 100644 index 2c5bb0b..0000000 --- a/packages/server/src/enclave_server/routes/state.rs +++ /dev/null @@ -1,113 +0,0 @@ -use actix_web::{web, HttpResponse, Responder}; -use log::info; - -use crate::enclave_server::database::{get_e3, get_e3_round}; -use crate::enclave_server::models::{ - AllWebStates, E3StateLite, GetRoundRequest, WebResultRequest, -}; - -pub fn setup_routes(config: &mut web::ServiceConfig) { - config - .route("/get_web_result_all", web::get().to(get_web_result_all)) - .route( - "/get_round_state_lite", - web::post().to(get_round_state_lite), - ) - .route("/get_round_state", web::post().to(get_round_state)) - .route("/get_web_result", web::post().to(get_web_result)); -} - -async fn get_web_result(data: web::Json) -> impl Responder { - let incoming = data.into_inner(); - info!("Request web state for round {}", incoming.round_id); - - let (state, _) = get_e3(incoming.round_id as u64).await.unwrap(); - - let response = WebResultRequest { - round_id: state.id, - option_1_tally: state.votes_option_1, - option_2_tally: state.votes_option_2, - total_votes: state.votes_option_1 + state.votes_option_2, - option_1_emoji: state.emojis[0].clone(), - option_2_emoji: state.emojis[1].clone(), - end_time: state.expiration, - }; - - HttpResponse::Ok().json(response) -} - -async fn get_web_result_all() -> impl Responder { - info!("Request all web state."); - - let round_count = match get_e3_round().await { - Ok(count) => count, - Err(e) => { - info!("Error retrieving round count: {:?}", e); - return HttpResponse::InternalServerError().body("Failed to retrieve round count"); - } - }; - - let mut states = Vec::new(); - for i in 1..round_count { - match get_e3(i).await { - Ok((state, _key)) => { - let web_result = WebResultRequest { - round_id: i, - option_1_tally: state.votes_option_1, - option_2_tally: state.votes_option_2, - total_votes: state.votes_option_1 + state.votes_option_2, - option_1_emoji: state.emojis[0].clone(), - option_2_emoji: state.emojis[1].clone(), - end_time: state.expiration, - }; - states.push(web_result); - } - Err(e) => { - info!("Error retrieving state for round {}: {:?}", i, e); - return HttpResponse::InternalServerError() - .body(format!("Failed to retrieve state for round {}", i)); - } - } - } - - let response = AllWebStates { states }; - HttpResponse::Ok().json(response) -} - -async fn get_round_state(data: web::Json) -> impl Responder { - let incoming = data.into_inner(); - info!("Request state for round {}", incoming.round_id); - - let (state, _key) = get_e3(incoming.round_id as u64).await.unwrap(); - HttpResponse::Ok().json(state) -} - -async fn get_round_state_lite(data: web::Json) -> impl Responder { - let incoming = data.into_inner(); - info!("Request state for round {}", incoming.round_id); - - match get_e3(incoming.round_id as u64).await { - Ok((state, _)) => { - let state_lite = E3StateLite { - id: state.id, - chain_id: state.chain_id, - enclave_address: state.enclave_address, - - status: state.status, - vote_count: state.vote_count, - - start_time: state.start_time, - duration: state.duration, - expiration: state.expiration, - - committee_public_key: state.committee_public_key, - emojis: state.emojis, - }; - HttpResponse::Ok().json(state_lite) - } - Err(e) => { - info!("Error getting E3 state: {:?}", e); - HttpResponse::InternalServerError().body("Failed to get E3 state") - } - } -} diff --git a/packages/server/src/enclave_server/routes/voting.rs b/packages/server/src/enclave_server/routes/voting.rs deleted file mode 100644 index afc84ac..0000000 --- a/packages/server/src/enclave_server/routes/voting.rs +++ /dev/null @@ -1,75 +0,0 @@ -use alloy::primitives::{Bytes, U256}; -use actix_web::{web, HttpResponse, Responder}; -use log::info; - -use crate::enclave_server::config::CONFIG; -use crate::enclave_server::database::{get_e3, save_e3}; -use crate::enclave_server::{ - blockchain::relayer::EnclaveContract, - models::{EncryptedVote, GetEmojisRequest, JsonResponseTxHash, VoteCountRequest}, -}; - -pub fn setup_routes(config: &mut web::ServiceConfig) { - config - .route("/broadcast_enc_vote", web::post().to(broadcast_enc_vote)) - .route( - "/get_vote_count_by_round", - web::post().to(get_vote_count_by_round), - ) - .route("/get_emojis_by_round", web::post().to(get_emojis_by_round)); -} - -async fn broadcast_enc_vote( - data: web::Json -) -> impl Responder { - let vote: EncryptedVote = data.into_inner(); - - let (mut state_data, key) = get_e3(vote.round_id as u64).await.unwrap(); - if state_data.has_voted.contains(&vote.postId) { - return HttpResponse::BadRequest().json(JsonResponseTxHash { - response: "User has already voted".to_string(), - tx_hash: "".to_string(), - }); - } - - let sol_vote = Bytes::from(vote.enc_vote_bytes); - let e3_id = U256::from(vote.round_id); - let contract = EnclaveContract::new(CONFIG.enclave_address.clone()).await.unwrap(); - let tx_hash = match contract.publish_input(e3_id, sol_vote).await { - Ok(hash) => hash.transaction_hash.to_string(), - Err(e) => { - info!("Error while sending vote transaction: {:?}", e); - return HttpResponse::InternalServerError().body("Failed to broadcast vote"); - } - }; - - state_data.has_voted.push(vote.postId); - save_e3(&state_data, &key).await.unwrap(); - - HttpResponse::Ok().json(JsonResponseTxHash { - response: "Vote successful".to_string(), - tx_hash, - }) -} - -// Get Emojis by Round Handler -async fn get_emojis_by_round(data: web::Json) -> impl Responder { - let mut incoming = data.into_inner(); - info!("Request emojis for round {:?}", incoming.round_id); - - let (state_data, _) = get_e3(incoming.round_id as u64).await.unwrap(); - incoming.emojis = state_data.emojis; - - HttpResponse::Ok().json(incoming) -} - -// Get Vote Count by Round Handler -async fn get_vote_count_by_round(data: web::Json) -> impl Responder { - let mut incoming = data.into_inner(); - info!("Request vote count for round {:?}", incoming.round_id); - - let (state_data, _) = get_e3(incoming.round_id as u64).await.unwrap(); - incoming.vote_count = state_data.vote_count as u32; - - HttpResponse::Ok().json(incoming) -} diff --git a/packages/server/src/lib.rs b/packages/server/src/lib.rs index 2fd661a..2a83feb 100644 --- a/packages/server/src/lib.rs +++ b/packages/server/src/lib.rs @@ -1,3 +1,2 @@ -pub mod cli; -pub mod util; -pub mod enclave_server; \ No newline at end of file +pub mod server; +pub mod logger; \ No newline at end of file diff --git a/packages/server/src/logger.rs b/packages/server/src/logger.rs new file mode 100644 index 0000000..150e31e --- /dev/null +++ b/packages/server/src/logger.rs @@ -0,0 +1,27 @@ +use env_logger::{Builder, Target}; +use log::{LevelFilter, Record}; +use std::io::Write; +use std::path::Path; +use chrono::Utc; + +pub fn init_logger() { + let mut builder = Builder::new(); + builder + .target(Target::Stdout) + .filter(None, LevelFilter::Info) + .format(|buf, record: &Record| { + let file = record.file().unwrap_or("unknown"); + let filename = Path::new(file).file_name().unwrap_or_else(|| file.as_ref()); + let timestamp = Utc::now().format("%Y-%m-%d %H:%M:%S"); + + writeln!( + buf, + "[{}] [{}:{}] - {}", + timestamp, + filename.to_string_lossy(), + record.line().unwrap_or(0), + record.args() + ) + }) + .init(); +} \ No newline at end of file diff --git a/packages/server/src/enclave_server/blockchain/events.rs b/packages/server/src/server/blockchain/events.rs similarity index 100% rename from packages/server/src/enclave_server/blockchain/events.rs rename to packages/server/src/server/blockchain/events.rs diff --git a/packages/server/src/enclave_server/blockchain/handlers.rs b/packages/server/src/server/blockchain/handlers.rs similarity index 85% rename from packages/server/src/enclave_server/blockchain/handlers.rs rename to packages/server/src/server/blockchain/handlers.rs index d90b115..823ff13 100644 --- a/packages/server/src/enclave_server/blockchain/handlers.rs +++ b/packages/server/src/server/blockchain/handlers.rs @@ -1,13 +1,16 @@ use super::{ - events::{CiphertextOutputPublished, E3Activated, InputPublished, PlaintextOutputPublished, CommitteePublished}, + events::{ + CiphertextOutputPublished, CommitteePublished, E3Activated, InputPublished, + PlaintextOutputPublished, + }, relayer::EnclaveContract, }; -use crate::enclave_server::models::E3; -use crate::enclave_server::{ +use crate::server::{ config::CONFIG, - database::{generate_emoji, get_e3, increment_e3_round, save_e3}, + database::{generate_emoji, get_e3, GLOBAL_DB}, + models::{E3, CurrentRound}, }; -use alloy:: rpc::types::Log; +use alloy::rpc::types::Log; use chrono::Utc; use compute_provider::FHEInputs; use log::info; @@ -73,10 +76,16 @@ pub async fn handle_e3(e3_activated: E3Activated, log: Log) -> Result<()> { // Save E3 to the database let key = format!("e3:{}", e3_id); - save_e3(&e3_obj, &key).await?; + GLOBAL_DB.insert(&key, &e3_obj).await?; + + // Set Current Round + let current_round = CurrentRound { + id: e3_id, + }; + GLOBAL_DB.insert("e3:current_round", ¤t_round).await?; // Sleep till the E3 expires - sleep(Duration::from_secs(e3.duration.to::() + 5)).await; + sleep(Duration::from_secs(e3.duration.to::())).await; // Get All Encrypted Votes let (mut e3, _) = get_e3(e3_id).await.unwrap(); @@ -115,9 +124,9 @@ pub async fn handle_e3(e3_activated: E3Activated, log: Log) -> Result<()> { } else { info!("E3 has no votes to decrypt. Setting status to Finished."); e3.status = "Finished".to_string(); - save_e3(&e3, &key).await.unwrap(); + + GLOBAL_DB.insert(&key, &e3_obj).await?; } - increment_e3_round().await.unwrap(); info!("E3 request handled successfully."); Ok(()) } @@ -132,7 +141,7 @@ pub async fn handle_input_published(input: InputPublished) -> Result<()> { .push((input.data.to_vec(), input.index.to::())); e3.vote_count += 1; - save_e3(&e3, &key).await?; + GLOBAL_DB.insert(&key, &e3).await?; info!("Saved Input with Hash: {:?}", input.inputHash); Ok(()) @@ -149,7 +158,7 @@ pub async fn handle_ciphertext_output_published( e3.ciphertext_output = ciphertext_output.ciphertextOutput.to_vec(); e3.status = "Published".to_string(); - save_e3(&e3, &key).await?; + GLOBAL_DB.insert(&key, &e3).await?; info!("CiphertextOutputPublished event handled."); Ok(()) @@ -172,7 +181,7 @@ pub async fn handle_plaintext_output_published( info!("Votes Option 1: {:?}", e3.votes_option_1); info!("Votes Option 2: {:?}", e3.votes_option_2); - save_e3(&e3, &key).await?; + GLOBAL_DB.insert(&key, &e3).await?; info!("PlaintextOutputPublished event handled."); Ok(()) @@ -180,10 +189,12 @@ pub async fn handle_plaintext_output_published( pub async fn handle_committee_published(committee_published: CommitteePublished) -> Result<()> { info!("Handling CommitteePublished event..."); - info!("Committee Published: {:?}", committee_published); + info!("Committee Published for round: {:?}", committee_published.e3Id); let contract = EnclaveContract::new(CONFIG.enclave_address.clone()).await?; - let tx = contract.activate(committee_published.e3Id, committee_published.publicKey).await?; + let tx = contract + .activate(committee_published.e3Id, committee_published.publicKey) + .await?; info!("E3 activated with tx: {:?}", tx.transaction_hash); Ok(()) } diff --git a/packages/server/src/enclave_server/blockchain/listener.rs b/packages/server/src/server/blockchain/listener.rs similarity index 100% rename from packages/server/src/enclave_server/blockchain/listener.rs rename to packages/server/src/server/blockchain/listener.rs diff --git a/packages/server/src/enclave_server/blockchain/mod.rs b/packages/server/src/server/blockchain/mod.rs similarity index 62% rename from packages/server/src/enclave_server/blockchain/mod.rs rename to packages/server/src/server/blockchain/mod.rs index cffdf6f..0f55184 100644 --- a/packages/server/src/enclave_server/blockchain/mod.rs +++ b/packages/server/src/server/blockchain/mod.rs @@ -1,5 +1,4 @@ pub mod listener; pub mod relayer; pub mod events; -pub mod handlers; -pub mod sync; \ No newline at end of file +pub mod handlers; \ No newline at end of file diff --git a/packages/server/src/enclave_server/blockchain/relayer.rs b/packages/server/src/server/blockchain/relayer.rs similarity index 99% rename from packages/server/src/enclave_server/blockchain/relayer.rs rename to packages/server/src/server/blockchain/relayer.rs index e98be79..e400468 100644 --- a/packages/server/src/enclave_server/blockchain/relayer.rs +++ b/packages/server/src/server/blockchain/relayer.rs @@ -1,4 +1,4 @@ -use crate::enclave_server::CONFIG; +use crate::server::CONFIG; use alloy::{ network::{Ethereum, EthereumWallet}, primitives::{Address, Bytes, U256}, diff --git a/packages/server/src/enclave_server/config.rs b/packages/server/src/server/config.rs similarity index 100% rename from packages/server/src/enclave_server/config.rs rename to packages/server/src/server/config.rs diff --git a/packages/server/src/server/database.rs b/packages/server/src/server/database.rs new file mode 100644 index 0000000..f80e9ba --- /dev/null +++ b/packages/server/src/server/database.rs @@ -0,0 +1,78 @@ +use super::models::E3; +use once_cell::sync::Lazy; +use rand::Rng; +use sled::Db; +use std::{error::Error, str, sync::Arc}; +use tokio::sync::RwLock; +use thiserror::Error; +use serde::{Serialize, de::DeserializeOwned}; + +#[derive(Error, Debug)] +pub enum DatabaseError { + #[error("SledDB error: {0}")] + SledDB(#[from] sled::Error), + #[error("Serialization error: {0}")] + Serialization(#[from] serde_json::Error), +} +#[derive(Clone)] +pub struct SledDB { + pub db: Arc>, +} + +impl SledDB { + pub fn new(path: &str) -> Result { + let db = sled::open(path)?; + Ok(Self { db: Arc::new(RwLock::new(db)) }) + } + + pub async fn insert(&self, key: &str, value: &T) -> Result<(), DatabaseError> { + let serialized = serde_json::to_vec(value)?; + self.db.write().await.insert(key.as_bytes(), serialized)?; + Ok(()) + } + + pub async fn get(&self, key: &str) -> Result, DatabaseError> { + if let Some(bytes) = self.db.read().await.get(key.as_bytes())? { + let value = serde_json::from_slice(&bytes)?; + Ok(Some(value)) + } else { + Ok(None) + } + } +} + +pub static GLOBAL_DB: Lazy = Lazy::new(|| { + let pathdb = std::env::current_dir() + .unwrap() + .join("database/server"); + SledDB::new(pathdb.to_str().unwrap()).unwrap() +}); + +pub async fn get_e3(e3_id: u64) -> Result<(E3, String), Box> { + let key = format!("e3:{}", e3_id); + let e3 = GLOBAL_DB.get::(&key).await?; + Ok((e3.unwrap(), key)) +} + +pub fn generate_emoji() -> (String, String) { + let emojis = [ + "🍇", "🍈", "🍉", "🍊", "🍋", "🍌", "🍍", "ðŸĨ­", "🍎", "🍏", "🍐", "🍑", "🍒", "🍓", "ðŸŦ", + "ðŸĨ", "🍅", "ðŸŦ’", "ðŸĨĨ", "ðŸĨ‘", "🍆", "ðŸĨ”", "ðŸĨ•", "ðŸŒ―", "ðŸŒķïļ", "ðŸŦ‘", "ðŸĨ’", "ðŸĨŽ", "ðŸĨĶ", "🧄", + "🧅", "🍄", "ðŸĨœ", "ðŸŦ˜", "🌰", "🍞", "ðŸĨ", "ðŸĨ–", "ðŸŦ“", "ðŸĨĻ", "ðŸĨŊ", "ðŸĨž", "🧇", "🧀", "🍖", + "🍗", "ðŸĨĐ", "ðŸĨ“", "🍔", "🍟", "🍕", "🌭", "ðŸĨŠ", "ðŸŒŪ", "ðŸŒŊ", "ðŸŦ”", "ðŸĨ™", "🧆", "ðŸĨš", "ðŸģ", + "ðŸĨ˜", "ðŸē", "ðŸŦ•", "ðŸĨĢ", "ðŸĨ—", "ðŸŋ", "🧈", "🧂", "ðŸĨŦ", "ðŸą", "🍘", "🍙", "🍚", "🍛", "🍜", + "🍝", "🍠", "ðŸĒ", "ðŸĢ", "ðŸĪ", "ðŸĨ", "ðŸĨŪ", "ðŸĄ", "ðŸĨŸ", "ðŸĨ ", "ðŸĨĄ", "ðŸĶ€", "ðŸĶž", "ðŸĶ", "ðŸĶ‘", + "ðŸĶŠ", "ðŸĶ", "🍧", "ðŸĻ", "ðŸĐ", "🍊", "🎂", "🍰", "🧁", "ðŸĨ§", "ðŸŦ", "🍎", "🍭", "ðŸŪ", "ðŸŊ", + "🍞", "ðŸĨ›", "☕", "ðŸĩ", "ðŸū", "🍷", "ðŸļ", "ðŸđ", "🍚", "ðŸŧ", "ðŸĨ‚", "ðŸĨƒ", + ]; + let mut index1 = rand::thread_rng().gen_range(0..emojis.len()); + let index2 = rand::thread_rng().gen_range(0..emojis.len()); + if index1 == index2 { + if index1 == emojis.len() { + index1 = index1 - 1; + } else { + index1 = index1 + 1; + }; + }; + (emojis[index1].to_string(), emojis[index2].to_string()) +} diff --git a/packages/server/src/bin/cli.rs b/packages/server/src/server/main.rs similarity index 63% rename from packages/server/src/bin/cli.rs rename to packages/server/src/server/main.rs index 049ae4c..b94a9d7 100644 --- a/packages/server/src/bin/cli.rs +++ b/packages/server/src/server/main.rs @@ -1,5 +1,5 @@ -use rfv::cli::run_cli; +use crisp::server; fn main() -> Result<(), Box> { - run_cli() -} + server::start() +} \ No newline at end of file diff --git a/packages/server/src/enclave_server/mod.rs b/packages/server/src/server/mod.rs similarity index 52% rename from packages/server/src/enclave_server/mod.rs rename to packages/server/src/server/mod.rs index 92273ca..3bdbe00 100644 --- a/packages/server/src/enclave_server/mod.rs +++ b/packages/server/src/server/mod.rs @@ -8,42 +8,15 @@ use actix_cors::Cors; use actix_web::{middleware::Logger, web, App, HttpServer}; use blockchain::listener::start_listener; -use blockchain::sync::sync_contracts_db; use database::GLOBAL_DB; use models::AppState; use config::CONFIG; -use env_logger::{Builder, Target}; -use log::{LevelFilter, Record}; -use std::io::Write; -use std::path::Path; - -fn init_logger() { - let mut builder = Builder::new(); - builder - .target(Target::Stdout) - .filter(None, LevelFilter::Info) - .format(|buf, record: &Record| { - let file = record.file().unwrap_or("unknown"); - let filename = Path::new(file).file_name().unwrap_or_else(|| file.as_ref()); - - writeln!( - buf, - "[{}:{}] - {}", - filename.to_string_lossy(), - record.line().unwrap_or(0), - record.args() - ) - }) - .init(); -} +use crate::logger::init_logger; #[actix_web::main] -pub async fn start_server() -> Result<(), Box> { +pub async fn start() -> Result<(), Box> { init_logger(); - if let Err(e) = sync_contracts_db().await { - eprintln!("Failed to sync contracts: {:?}", e); - } tokio::spawn(async { if let Err(e) = start_listener(&CONFIG.ws_rpc_url, &CONFIG.enclave_address, &CONFIG.ciphernode_registry_address).await { @@ -63,7 +36,7 @@ pub async fn start_server() -> Result<(), Box>, + pub sled: SledDB, } #[derive(Debug, Deserialize, Serialize)] @@ -22,9 +20,13 @@ pub struct JsonResponseTxHash { #[derive(Debug, Deserialize, Serialize)] pub struct RoundCount { - pub round_count: u32, + pub round_count: u64, } +#[derive(Debug, Deserialize, Serialize)] +pub struct CurrentRound { + pub id: u64, +} #[derive(Debug, Deserialize, Serialize)] pub struct PKRequest { @@ -37,32 +39,41 @@ pub struct CTRequest { pub ct_bytes: Vec, } -#[derive(Debug, Deserialize, Serialize)] -pub struct VoteCountRequest { - pub round_id: u32, - pub vote_count: u32, -} - - #[derive(Debug, Deserialize, Serialize)] #[allow(non_snake_case)] pub struct EncryptedVote { - pub round_id: u32, + pub round_id: u64, pub enc_vote_bytes: Vec, pub postId: String, } #[derive(Debug, Deserialize, Serialize)] pub struct GetRoundRequest { - pub round_id: u32, + pub round_id: u64, } + #[derive(Debug, Deserialize, Serialize)] -pub struct GetEmojisRequest { - pub round_id: u32, - pub emojis: [String; 2], +pub struct AuthenticationDB { + pub jwt_tokens: Vec, } +#[derive(Debug, Deserialize, Serialize)] +#[allow(non_snake_case)] +pub struct AuthenticationLogin { + pub postId: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct AuthenticationResponse { + pub response: String, + pub jwt_token: String, +} + +#[derive(Debug, Deserialize)] +pub struct CronRequestE3 { + pub cron_api_key: String, +} #[derive(Debug, Deserialize, Serialize)] pub struct WebResultRequest { @@ -76,9 +87,22 @@ pub struct WebResultRequest { } #[derive(Debug, Deserialize, Serialize)] -pub struct AllWebStates { - pub states: Vec, +pub struct E3StateLite { + pub id: u64, + pub chain_id: u64, + pub enclave_address: String, + + pub status: String, + pub vote_count: u64, + + pub start_time: u64, + pub duration: u64, + pub expiration: u64, + + pub committee_public_key: Vec, + pub emojis: [String; 2], } + #[derive(Debug, Deserialize, Serialize)] pub struct E3 { // Identifiers @@ -114,42 +138,34 @@ pub struct E3 { pub emojis: [String; 2], } -#[derive(Debug, Deserialize, Serialize)] -pub struct E3StateLite { - pub id: u64, - pub chain_id: u64, - pub enclave_address: String, - - pub status: String, - pub vote_count: u64, - - pub start_time: u64, - pub duration: u64, - pub expiration: u64, - - pub committee_public_key: Vec, - pub emojis: [String; 2], -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct AuthenticationDB { - pub jwt_tokens: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -#[allow(non_snake_case)] -pub struct AuthenticationLogin { - pub postId: String, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct AuthenticationResponse { - pub response: String, - pub jwt_token: String, -} - -#[derive(Debug, Deserialize)] -pub struct CronRequestE3 { - pub cron_api_key: String, +impl From for WebResultRequest { + fn from(e3: E3) -> Self { + WebResultRequest { + round_id: e3.id, + option_1_tally: e3.votes_option_1, + option_2_tally: e3.votes_option_2, + total_votes: e3.votes_option_1 + e3.votes_option_2, + option_1_emoji: e3.emojis[0].clone(), + option_2_emoji: e3.emojis[1].clone(), + end_time: e3.expiration, + } + } +} + +impl From for E3StateLite { + fn from(e3: E3) -> Self { + E3StateLite { + id: e3.id, + chain_id: e3.chain_id, + enclave_address: e3.enclave_address, + status: e3.status, + vote_count: e3.vote_count, + start_time: e3.start_time, + duration: e3.duration, + expiration: e3.expiration, + committee_public_key: e3.committee_public_key, + emojis: e3.emojis, + } + } } \ No newline at end of file diff --git a/packages/server/src/enclave_server/routes/auth.rs b/packages/server/src/server/routes/auth.rs similarity index 72% rename from packages/server/src/enclave_server/routes/auth.rs rename to packages/server/src/server/routes/auth.rs index 1587016..12bb180 100644 --- a/packages/server/src/enclave_server/routes/auth.rs +++ b/packages/server/src/server/routes/auth.rs @@ -6,14 +6,27 @@ use log::info; use actix_web::{web, HttpResponse, Responder}; -use crate::enclave_server::models::{AppState, AuthenticationLogin, AuthenticationDB, AuthenticationResponse}; +use crate::server::models::{AppState, AuthenticationLogin, AuthenticationDB, AuthenticationResponse}; pub fn setup_routes(config: &mut web::ServiceConfig) { config - .route("/authentication_login", web::post().to(authentication_login)); + .service( + web::scope("/auth") + .route("/login", web::post().to(authenticate_login)) + ); } -async fn authentication_login(state: web::Data, data: web::Json) -> impl Responder { +/// Authenticate a login +/// +/// # Arguments +/// +/// * `state` - The application state +/// * `AuthenticationLogin` - The post ID for the login +/// +/// # Returns +/// +/// * `AuthenticationResponse` - The response indicating the success or failure of the login +async fn authenticate_login(state: web::Data, data: web::Json) -> impl Responder { let incoming = data.into_inner(); info!("Twitter Login Request"); @@ -24,7 +37,7 @@ async fn authentication_login(state: web::Data, data: web::Json ) -> impl Responder { - // Check API key if data.cron_api_key != CONFIG.cron_api_key { return HttpResponse::Unauthorized().json(JsonResponse { response: "Invalid API key".to_string(), }); } - // Initialize a new E3 round match initialize_crisp_round().await { Ok(_) => HttpResponse::Ok().json(JsonResponse { response: "New E3 round requested successfully".to_string(), @@ -38,42 +48,76 @@ async fn request_e3_round( } } -async fn get_rounds()-> impl Responder { - match get_e3_round().await { - Ok(round_count) => { - let count = RoundCount { round_count: round_count as u32 }; - info!("round_count: {}", count.round_count); - HttpResponse::Ok().json(count) - } - Err(e) => { - info!("Failed to retrieve round count: {}", e); - HttpResponse::InternalServerError().body(format!("Error: {}", e)) - } +/// Get the current E3 round +/// +/// # Arguments +/// +/// * `AppState` - The application state +/// +/// # Returns +/// +/// * A JSON response containing the current round +async fn get_current_round(state: web::Data) -> impl Responder { + match state.sled.get::("e3:current_round").await { + Ok(Some(current_round)) => HttpResponse::Ok().json(current_round), + Ok(None) => HttpResponse::NotFound().json(JsonResponse { + response: "No current round found".to_string(), + }), + Err(e) => HttpResponse::InternalServerError().json(JsonResponse { + response: format!("Failed to retrieve current round: {}", e), + }), } } -async fn get_ct_by_round( +/// Get the ciphertext for a given round +/// +/// # Arguments +/// +/// * `CTRequest` - The request data containing the round ID +/// +/// # Returns +/// +/// * A JSON response containing the ciphertext +async fn get_ciphertext( data: web::Json, ) -> impl Responder { let mut incoming = data.into_inner(); - info!("Request for round {:?} ciphertext", incoming.round_id); + let (state_data, _) = get_e3(incoming.round_id).await.unwrap(); + incoming.ct_bytes = state_data.ciphertext_output; + HttpResponse::Ok().json(incoming) } -async fn get_pk_by_round( +/// Get the public key for a given round +/// +/// # Arguments +/// +/// * `PKRequest` - The request data containing the round ID +/// +/// # Returns +/// +/// * A JSON response containing the public key +async fn get_public_key( data: web::Json, ) -> impl Responder { let mut incoming = data.into_inner(); - info!("Request for round {:?} pk", incoming.round_id); + let (state_data, _) = get_e3(incoming.round_id).await.unwrap(); + incoming.pk_bytes = state_data.committee_public_key; + HttpResponse::Ok().json(incoming) } - - +/// Initialize a new CRISP round +/// +/// Creates a new CRISP round by enabling the E3 program, generating the necessary parameters, and requesting E3. +/// +/// # Returns +/// +/// * A result indicating the success of the operation pub async fn initialize_crisp_round() -> Result<(), Box> { info!("Starting new CRISP round!"); @@ -93,7 +137,7 @@ pub async fn initialize_crisp_round() -> Result<(), Box println!("Error checking E3 Program enabled: {:?}", e), + Err(e) => error!("Error checking E3 Program enabled: {:?}", e), } info!("Generating parameters..."); @@ -107,7 +151,7 @@ pub async fn initialize_crisp_round() -> Result<(), Box) -> impl Responder { + let incoming = data.into_inner(); + + let (state, _) = get_e3(incoming.round_id).await.unwrap(); + + let response: WebResultRequest = state.into(); + + HttpResponse::Ok().json(response) +} + +/// Get all the results for all rounds +/// +/// # Returns +/// +/// * A JSON response containing the results for all rounds +async fn get_all_round_results() -> impl Responder { + let round_count = match GLOBAL_DB.get::("e3:current_round").await { + Ok(count) => count.unwrap().id, + Err(e) => { + info!("Error retrieving round count: {:?}", e); + return HttpResponse::InternalServerError().body("Failed to retrieve round count"); + } + }; + + let mut states = Vec::new(); + for i in 0..round_count + 1 { + match get_e3(i).await { + Ok((state, _key)) => { + let web_result: WebResultRequest = state.into(); + states.push(web_result); + } + Err(e) => { + info!("Error retrieving state for round {}: {:?}", i, e); + return HttpResponse::InternalServerError() + .body(format!("Failed to retrieve state for round {}", i)); + } + } + } + + HttpResponse::Ok().json(states) +} + +/// Get the state for a given round +/// +/// # Arguments +/// +/// * `GetRoundRequest` - The request data containing the round ID +/// +/// # Returns +/// +async fn get_round_state_lite(data: web::Json) -> impl Responder { + let incoming = data.into_inner(); + + match get_e3(incoming.round_id as u64).await { + Ok((state, _)) => { + let state_lite: E3StateLite = state.into(); + HttpResponse::Ok().json(state_lite) + } + Err(e) => { + info!("Error getting E3 state: {:?}", e); + HttpResponse::InternalServerError().body("Failed to get E3 state") + } + } +} diff --git a/packages/server/src/server/routes/voting.rs b/packages/server/src/server/routes/voting.rs new file mode 100644 index 0000000..cadd8d4 --- /dev/null +++ b/packages/server/src/server/routes/voting.rs @@ -0,0 +1,96 @@ +use alloy::primitives::{Bytes, U256}; +use actix_web::{web, HttpResponse, Responder}; +use log::info; +use eyre::Error; +use crate::server::{ + config::CONFIG, + database::{get_e3, GLOBAL_DB}, + blockchain::relayer::EnclaveContract, + models::{EncryptedVote, JsonResponseTxHash, E3}, +}; + +pub fn setup_routes(config: &mut web::ServiceConfig) { + config + .service( + web::scope("/voting") + .route("/broadcast", web::post().to(broadcast_encrypted_vote)) + ); +} + +/// Broadcast an encrypted vote to the blockchain +/// +/// # Arguments +/// +/// * `EncryptedVote` - The vote data to be broadcast +/// +/// # Returns +/// +/// * A JSON response indicating the success or failure of the operation +async fn broadcast_encrypted_vote(data: web::Json) -> impl Responder { + let vote = data.into_inner(); + + // Validate and update vote status + let (mut state_data, key) = match validate_and_update_vote_status(&vote).await { + Ok(result) => result, + Err(response) => return response, + }; + + // Prepare vote data for blockchain + let e3_id = U256::from(vote.round_id); + let sol_vote = Bytes::from(vote.enc_vote_bytes); + + // Broadcast vote to blockchain + let contract = EnclaveContract::new(CONFIG.enclave_address.clone()).await.unwrap(); + match contract.publish_input(e3_id, sol_vote).await { + Ok(hash) => HttpResponse::Ok().json(JsonResponseTxHash { + response: "Vote successful".to_string(), + tx_hash: hash.transaction_hash.to_string(), + }), + Err(e) => handle_vote_error(e, &mut state_data, &key, &vote.postId).await, + } +} + +/// Validate and update the vote status +/// +/// # Arguments +/// +/// * `vote` - The vote data to be validated and updated +/// +/// # Returns +/// +/// * A tuple containing the state data and the key +async fn validate_and_update_vote_status(vote: &EncryptedVote) -> Result<(E3, String), HttpResponse> { + let (mut state_data, key) = get_e3(vote.round_id).await.unwrap(); + + if state_data.has_voted.contains(&vote.postId) { + return Err(HttpResponse::BadRequest().json(JsonResponseTxHash { + response: "User has already voted".to_string(), + tx_hash: "".to_string(), + })); + } + + state_data.has_voted.push(vote.postId.clone()); + GLOBAL_DB.insert(&key, &state_data).await.unwrap(); + + Ok((state_data, key.to_string())) +} + +/// Handle the vote error +/// +/// # Arguments +/// +/// * `e` - The error that occurred +/// * `state_data` - The state data to be rolled back +/// * `key` - The key for the state data +/// * `post_id` - The post ID for the vote +async fn handle_vote_error(e: Error, state_data: &mut E3, key: &str, post_id: &str) -> HttpResponse { + info!("Error while sending vote transaction: {:?}", e); + + // Rollback the vote + if let Some(pos) = state_data.has_voted.iter().position(|x| x == post_id) { + state_data.has_voted.remove(pos); + GLOBAL_DB.insert(key, state_data).await.unwrap(); + } + + HttpResponse::InternalServerError().body("Failed to broadcast vote") +} \ No newline at end of file diff --git a/packages/server/src/util.rs b/packages/server/src/util.rs deleted file mode 100644 index 48a703f..0000000 --- a/packages/server/src/util.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! Utility functions - -use std::{fmt, time::Duration}; - -/// Macros to time code and display a human-readable duration. -pub mod timeit { - #[allow(unused_macros)] - macro_rules! timeit_n { - ($name:expr, $loops:expr, $code:expr) => {{ - use util::DisplayDuration; - let start = std::time::Instant::now(); - let r = $code; - for _ in 1..$loops { - let _ = $code; - } - info!( - "⏱ {}: {}", - $name, - DisplayDuration(start.elapsed() / $loops) - ); - r - }}; - } - - #[allow(unused_macros)] - macro_rules! timeit { - ($name:expr, $code:expr) => {{ - use crate::util::DisplayDuration; - let start = std::time::Instant::now(); - let r = $code; - info!("⏱ {}: {}", $name, DisplayDuration(start.elapsed())); - r - }}; - } - - #[allow(unused_imports)] - pub(crate) use timeit; - #[allow(unused_imports)] - pub(crate) use timeit_n; -} - -/// Utility struct for displaying human-readable duration of the form "10.5 ms", -/// "350 Ξs", or "27 ns". -pub struct DisplayDuration(pub Duration); - -impl fmt::Display for DisplayDuration { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let duration_ns = self.0.as_nanos(); - if duration_ns < 1_000_u128 { - write!(f, "{duration_ns} ns") - } else if duration_ns < 1_000_000_u128 { - write!(f, "{} Ξs", (duration_ns + 500) / 1_000) - } else { - let duration_ms_times_10 = (duration_ns + 50_000) / (100_000); - write!(f, "{} ms", (duration_ms_times_10 as f64) / 10.0) - } - } -} \ No newline at end of file From 174557da500442c63360dfd113f76697e75ccb9e Mon Sep 17 00:00:00 2001 From: samepant Date: Mon, 7 Oct 2024 11:46:00 -0400 Subject: [PATCH 61/62] specify CRISP server more clearly --- packages/local_testnet/Readme.md | 13 ++++++++----- packages/server/Readme.md | 7 +++++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/local_testnet/Readme.md b/packages/local_testnet/Readme.md index de4872b..4e567b0 100644 --- a/packages/local_testnet/Readme.md +++ b/packages/local_testnet/Readme.md @@ -1,4 +1,5 @@ # CRISP - Collusion-Resistant Impartial Selection Protocol + Welcome to the CRISP project! This document provides a comprehensive guide to setting up and deploying the application both locally. Follow the steps carefully to ensure that all dependencies, services, and components are properly configured. ## Project Structure @@ -127,6 +128,7 @@ Keep Anvil running in the terminal, and open a new terminal for the next steps. ``` After deployment, note down the addresses for the following contracts: + - Enclave - Ciphernode Registry - Naive Registry Filter @@ -144,7 +146,8 @@ After deployment, note down the addresses for the following contracts: 3. Add the following configuration to `config.toml`: - > ***Note:*** *This requires having access to a Bonsai API Key. To request an API key [complete the form here](https://bonsai.xyz/apply).* + > **_Note:_** _This requires having access to a Bonsai API Key. To request an API key [complete the form here](https://bonsai.xyz/apply)._ + ```toml [env] ETH_WALLET_PRIVATE_KEY="your_private_key" @@ -206,12 +209,12 @@ Once the aggregator is running, you can add the Ciphernodes to the registry with ./add_ciphernodes.sh ``` -## Running the Enclave Server +## Running the CRISP Server -To run the Enclave Server, navigate to the `enclave_server` directory and execute the following command: +To run the CRISP Server, navigate to the `server` directory and execute the following command: ```sh -cargo run --bin enclave_server +cargo run --bin server ``` ## Interacting with CRISP via CLI @@ -234,4 +237,4 @@ This project is provided **WITHOUT ANY WARRANTY**; without even the implied warr ## License -This repository is licensed under the [LGPL-3.0+ license](LICENSE). \ No newline at end of file +This repository is licensed under the [LGPL-3.0+ license](LICENSE). diff --git a/packages/server/Readme.md b/packages/server/Readme.md index 0bf841a..227071f 100644 --- a/packages/server/Readme.md +++ b/packages/server/Readme.md @@ -1,6 +1,6 @@ -# Enclave Server +# CRISP Server -This is a Rust-based server implementation for an Enclave system, which handles E3 (Encrypted Execution Environment) rounds and voting processes. +This is a Rust-based server implementation for CRISP, which is built on top of the Enclave Protocol, which handles E3 (Encrypted Execution Environment) rounds and voting processes. ## Features @@ -11,6 +11,7 @@ This is a Rust-based server implementation for an Enclave system, which handles - CLI for manual interaction ## Prerequisites + - Rust (latest stable version) - Cargo (Rust's package manager) - Foundry (for deploying contracts) @@ -19,6 +20,7 @@ This is a Rust-based server implementation for an Enclave system, which handles ## Setup 1. Install dependencies: + ``` cargo build --release ``` @@ -40,6 +42,7 @@ This is a Rust-based server implementation for an Enclave system, which handles ## Running the Server 1. Start the crisp server: + ``` cargo run --bin server ``` From 06d88d66f34a069fa019f669b44ef1626410190a Mon Sep 17 00:00:00 2001 From: samepant Date: Mon, 7 Oct 2024 11:54:54 -0400 Subject: [PATCH 62/62] remove unused gh action files --- .github/workflows/cla.yaml | 37 ----------------------------------- .github/workflows/client.yaml | 36 ---------------------------------- 2 files changed, 73 deletions(-) delete mode 100644 .github/workflows/cla.yaml delete mode 100644 .github/workflows/client.yaml diff --git a/.github/workflows/cla.yaml b/.github/workflows/cla.yaml deleted file mode 100644 index 304a646..0000000 --- a/.github/workflows/cla.yaml +++ /dev/null @@ -1,37 +0,0 @@ -name: "CLA Assistant" -on: - issue_comment: - types: [created] - pull_request_target: - types: [opened, closed, synchronize] - -jobs: - CLAssistant: - runs-on: ubuntu-latest - steps: - - name: "CLA Assistant" - if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' - # Beta Release - uses: cla-assistant/github-action@v2.1.3-beta - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # the below token should have repo scope and must be manually added by you in the repository's secret - PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} - with: - path-to-signatures: "signatures/version1/cla.json" - path-to-document: "https://github.com/gnosis/CLA" - # branch should not be protected - branch: "cla-signatures" - allowlist: auryn-macmillan, nginnever,*bot # may need to update this expression if we add new bots - - - #below are the optional inputs - If the optional inputs are not given, then default values will be taken - #remote-organization-name: enter the remote organization name where the signatures should be stored (Default is storing the signatures in the same repository) - #remote-repository-name: enter the remote repository name where the signatures should be stored (Default is storing the signatures in the same repository) - #create-file-commit-message: 'For example: Creating file for storing CLA Signatures' - #signed-commit-message: 'For example: $contributorName has signed the CLA in #$pullRequestNo' - #custom-notsigned-prcomment: 'pull request comment with Introductory message to ask new contributors to sign' - #custom-pr-sign-comment: 'The signature to be committed in order to sign the CLA' - #custom-allsigned-prcomment: 'pull request comment when all contributors has signed, defaults to **CLA Assistant Lite bot** All Contributors have signed the CLA.' - #lock-pullrequest-aftermerge: false - if you don't want this bot to automatically lock the pull request after merging (default - true) - #use-dco-flag: true - If you are using DCO instead of CLA \ No newline at end of file diff --git a/.github/workflows/client.yaml b/.github/workflows/client.yaml deleted file mode 100644 index 06fcacd..0000000 --- a/.github/workflows/client.yaml +++ /dev/null @@ -1,36 +0,0 @@ -name: Deploy Client - -on: - push: - branches: - - main - -jobs: - deploy: - runs-on: ubuntu-latest - name: Deploy Client - steps: - - uses: actions/checkout@v3 - - name: Set up Node.js - uses: actions/setup-node@v3 - with: - node-version: 18 - cache: "yarn" - cache-dependency-path: "packages/client/yarn.lock" - - name: Install dependencies - run: | - cd packages/client - yarn install --frozen-lockfile - - name: Build - run: | - cd packages/client - yarn run build - env: - VITE_TWITTER_SERVERLESS_API: ${{secrets.VITE_TWITTER_SERVERLESS_API}} - VITE_ENCLAVE_API: ${{secrets.VITE_ENCLAVE_API}} - - name: Deploy to GitHub Pages - uses: JamesIves/github-pages-deploy-action@v4.3.3 - with: - branch: gh-pages # The branch the action should deploy to. - folder: packages/client/dist # The folder the action should deploy. -