diff --git a/cosmwasm-verifier/.gitignore b/cosmwasm-verifier/.gitignore new file mode 100644 index 0000000..cbff9d9 --- /dev/null +++ b/cosmwasm-verifier/.gitignore @@ -0,0 +1,4 @@ +target +factors_id.json +stark-receipt.json +snark-receipt.json \ No newline at end of file diff --git a/cosmwasm-verifier/.vscode/settings.json b/cosmwasm-verifier/.vscode/settings.json new file mode 100644 index 0000000..2288722 --- /dev/null +++ b/cosmwasm-verifier/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "rust-analyzer.linkedProjects": [ + "./risc0-example/methods/guest/Cargo.toml", + "./risc0-example/host/Cargo.toml", + "./cosmwasm/Cargo.toml", + ] +} \ No newline at end of file diff --git a/cosmwasm-verifier/README.md b/cosmwasm-verifier/README.md new file mode 100644 index 0000000..dc13f63 --- /dev/null +++ b/cosmwasm-verifier/README.md @@ -0,0 +1,99 @@ +# CosmWasm verifier + +### Running + +Build image ID and receipt to verify: + +```console +cd risc0-example +cargo run +``` + +Optionally set variables from the output files: + +```console +read -x IMAGE_ID < ../factors_id.json +# read -x STARK_RECEIPT < ../stark-receipt.json +read -x SNARK_RECEIPT < ../snark-receipt.json +``` + + +Install Wasmd and other dependencies (v0.52.0): + +https://docs.cosmwasm.com/docs/getting-started/installation + +(Optional) Initialize a wallet: + +```console +wasm keys add wallet +``` + +Init network: + +``` +wasmd init localnet --chain-id localnet +wasmd genesis add-genesis-account $(wasmd keys show -a wallet) 10000000000uwasm,10000000000stake +wasmd genesis gentx wallet 10000000000stake --chain-id localnet +wasmd genesis collect-gentxs +wasmd genesis validate-genesis +wasmd start +``` + +Build the contract: + +in `./cosmwasm/` + +x86_64: + +```console +docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/optimizer:0.16.0 +``` + +ARM: + +```console +docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/optimizer-arm64:0.16.0 +``` + +Deploy contract: + +```console +set -x RES $(wasmd tx wasm store artifacts/cosmwasm_verifier.wasm --from wallet --gas-prices 0.1uwasm --gas auto --gas-adjustment 2 -y --output json -b sync --node http://127.0.0.1:26657 --chain-id localnet) + +# For bash: +export CODE_ID=$(wasmd query wasm list-code --output json | jq -r '.code_infos[0].code_id') + +# Fish +set -x CODE_ID $(wasmd query wasm list-code --output json | jq -r '.code_infos[0].code_id') +``` + +Initialize contract: + +```console +wasmd tx wasm instantiate $CODE_ID "{\"image_id\":$IMAGE_ID}" --from wallet --label "Initialize with image ID" --gas-prices 0.025uwasm --gas auto --gas-adjustment 2 -b sync -y --no-admin --chain-id localnet --node http://127.0.0.1:26657 +``` + + +Get contract address: + +```console +set -x CONTRACT_ADDR $(wasmd query wasm list-contract-by-code $CODE_ID --output json --node http://127.0.0.1:26657 | jq -r '.contracts[0]') +``` + +Query the contract with a receipt: + +```console +wasmd query wasm contract-state smart $CONTRACT_ADDR "{\"Verify\":{\"receipt\":$SNARK_RECEIPT}}" --output json --node http://127.0.0.1:26657 +``` + +Sending a transaction verifying the receipt: + +```console +wasmd tx wasm execute $CONTRACT_ADDR "{\"Verify\":{\"receipt\":$SNARK_RECEIPT}}" --from wallet --gas-prices 0.025uwasm --gas auto --gas-adjustment 1.3 -y --chain-id localnet --output json --node http://127.0.0.1:26657 +``` diff --git a/cosmwasm-verifier/cosmwasm/.cargo/config.toml b/cosmwasm-verifier/cosmwasm/.cargo/config.toml new file mode 100644 index 0000000..ad60705 --- /dev/null +++ b/cosmwasm-verifier/cosmwasm/.cargo/config.toml @@ -0,0 +1,3 @@ +[alias] +wasm = "build --target wasm32-unknown-unknown --release" +wasm-debug = "build --target wasm32-unknown-unknown" diff --git a/cosmwasm-verifier/cosmwasm/.gitignore b/cosmwasm-verifier/cosmwasm/.gitignore new file mode 100644 index 0000000..c0ee47a --- /dev/null +++ b/cosmwasm-verifier/cosmwasm/.gitignore @@ -0,0 +1,2 @@ +target +artifacts \ No newline at end of file diff --git a/cosmwasm-verifier/cosmwasm/Cargo.toml b/cosmwasm-verifier/cosmwasm/Cargo.toml new file mode 100644 index 0000000..6af038f --- /dev/null +++ b/cosmwasm-verifier/cosmwasm/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "cosmwasm-verifier" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +cosmwasm-std = { version = "1.0.0-beta8", features = ["staking"] } +serde = { version = "1.0.103", default-features = false, features = [ + "derive", + "rc", +] } +risc0-zkvm = { version = "=1.0.5", default-features = false } +cw-storage-plus = "0.13.4" +serde_json = "1.0.111" + +[dev-dependencies] +cw-multi-test = "0.13.4" + +[profile.release] +codegen-units = 1 +opt-level = "z" +lto = "thin" +debug = false +panic = "abort" diff --git a/cosmwasm-verifier/cosmwasm/src/lib.rs b/cosmwasm-verifier/cosmwasm/src/lib.rs new file mode 100644 index 0000000..ca209fd --- /dev/null +++ b/cosmwasm-verifier/cosmwasm/src/lib.rs @@ -0,0 +1,117 @@ +use std::rc::Rc; + +use cosmwasm_std::{ + entry_point, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, + StdResult, +}; +use cw_storage_plus::Item; +use risc0_zkvm::{sha::Digestible, Groth16Receipt, Journal, MaybePruned, ReceiptClaim}; +use serde::{Deserialize, Serialize}; + +pub const IMAGE_ID: Item<[u32; 8]> = Item::new("image_id"); + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +pub struct InstantiateMsg { + pub image_id: [u32; 8], +} + +#[entry_point] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + IMAGE_ID.save(deps.storage, &msg.image_id)?; + + Ok(Response::new()) +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub enum QueryMsg { + Verify { + receipt: Rc<(Groth16Receipt, Journal)>, + }, +} + +#[entry_point] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Verify { receipt } => verify(deps, &receipt), + } +} + +#[entry_point] +pub fn execute(deps: DepsMut, _env: Env, _info: MessageInfo, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Verify { receipt } => { + verify(deps.as_ref(), &receipt).map(|res| Response::new().set_data(res)) + } + } +} + +fn verify( + deps: Deps, + (receipt, journal): &(Groth16Receipt, Journal), +) -> StdResult { + let image_id = IMAGE_ID.load(deps.storage)?; + + // Verify receipt. + receipt.verify_integrity().unwrap(); + + // Check that the claim on the verified receipt matches what was expected. Since we have + // constrained all field in the ReceiptClaim, we can directly construct the expected digest + // and do not need to open the claim digest on the inner receipt. + let expected_claim = ReceiptClaim::ok(image_id, MaybePruned::Pruned(journal.digest())); + if expected_claim.digest() != receipt.claim.digest() { + return Err(StdError::generic_err("Claim digest mismatch")); + } + + to_json_binary(journal.bytes.as_slice()) +} + +#[cfg(test)] +mod tests { + use super::*; + use cosmwasm_std::Addr; + use cw_multi_test::{App, ContractWrapper, Executor}; + + #[test] + fn verify_query() { + // Read image_id from JSON file + let image_id_json = include_bytes!("../../factors_id.json"); + let image_id: [u32; 8] = serde_json::from_slice(image_id_json).unwrap(); + + // Read receipt from JSON file + let receipt_json = include_bytes!("../../snark-receipt.json"); + let receipt = serde_json::from_slice(receipt_json).unwrap(); + + let mut app = App::default(); + + let code = ContractWrapper::new(execute, instantiate, query); + let code_id = app.store_code(Box::new(code)); + + let addr = app + .instantiate_contract( + code_id, + Addr::unchecked("owner"), + &InstantiateMsg { image_id }, + &[], + "Contract", + None, + ) + .unwrap(); + + let resp: Vec = app + .wrap() + .query_wasm_smart( + addr, + &QueryMsg::Verify { + receipt: Rc::new(receipt), + }, + ) + .unwrap(); + + assert_eq!([63, 0, 0, 0, 0, 0, 0, 0].as_slice(), &resp); + } +} diff --git a/cosmwasm-verifier/risc0-example/.gitignore b/cosmwasm-verifier/risc0-example/.gitignore new file mode 100644 index 0000000..f4247e1 --- /dev/null +++ b/cosmwasm-verifier/risc0-example/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +Cargo.lock +methods/guest/Cargo.lock +target/ diff --git a/cosmwasm-verifier/risc0-example/Cargo.toml b/cosmwasm-verifier/risc0-example/Cargo.toml new file mode 100644 index 0000000..5363877 --- /dev/null +++ b/cosmwasm-verifier/risc0-example/Cargo.toml @@ -0,0 +1,17 @@ +[workspace] +resolver = "2" +members = ["host", "methods"] + +# Always optimize; building and running the guest takes much longer without optimization. +[profile.dev] +opt-level = 3 + +[profile.dev.build-override] +opt-level = 3 + +[profile.release] +debug = 1 +lto = true + +[profile.release.build-override] +opt-level = 3 diff --git a/cosmwasm-verifier/risc0-example/README.md b/cosmwasm-verifier/risc0-example/README.md new file mode 100644 index 0000000..07039dd --- /dev/null +++ b/cosmwasm-verifier/risc0-example/README.md @@ -0,0 +1,112 @@ +# RISC Zero Rust Starter Template + +Welcome to the RISC Zero Rust Starter Template! This template is intended to +give you a starting point for building a project using the RISC Zero zkVM. +Throughout the template (including in this README), you'll find comments +labelled `TODO` in places where you'll need to make changes. To better +understand the concepts behind this template, check out the [zkVM +Overview][zkvm-overview]. + +## Quick Start + +First, make sure [rustup] is installed. The +[`rust-toolchain.toml`][rust-toolchain] file will be used by `cargo` to +automatically install the correct version. + +To build all methods and execute the method within the zkVM, run the following +command: + +```bash +cargo run +``` + +This is an empty template, and so there is no expected output (until you modify +the code). + +### Executing the project locally in development mode + +During development, faster iteration upon code changes can be achieved by leveraging [dev-mode], we strongly suggest activating it during your early development phase. Furthermore, you might want to get insights into the execution statistics of your project, and this can be achieved by specifying the environment variable `RUST_LOG="[executor]=info"` before running your project. + +Put together, the command to run your project in development mode while getting execution statistics is: + +```bash +RUST_LOG="[executor]=info" RISC0_DEV_MODE=1 cargo run +``` + +### Running proofs remotely on Bonsai + +_Note: The Bonsai proving service is still in early Alpha; an API key is +required for access. [Click here to request access][bonsai access]._ + +If you have access to the URL and API key to Bonsai you can run your proofs +remotely. To prove in Bonsai mode, invoke `cargo run` with two additional +environment variables: + +```bash +BONSAI_API_KEY="YOUR_API_KEY" BONSAI_API_URL="BONSAI_URL" cargo run +``` + +## How to create a project based on this template + +Search this template for the string `TODO`, and make the necessary changes to +implement the required feature described by the `TODO` comment. Some of these +changes will be complex, and so we have a number of instructional resources to +assist you in learning how to write your own code for the RISC Zero zkVM: + +- The [RISC Zero Developer Docs][dev-docs] is a great place to get started. +- Example projects are available in the [examples folder][examples] of + [`risc0`][risc0-repo] repository. +- Reference documentation is available at [https://docs.rs][docs.rs], including + [`risc0-zkvm`][risc0-zkvm], [`cargo-risczero`][cargo-risczero], + [`risc0-build`][risc0-build], and [others][crates]. + +## Directory Structure + +It is possible to organize the files for these components in various ways. +However, in this starter template we use a standard directory structure for zkVM +applications, which we think is a good starting point for your applications. + +```text +project_name +├── Cargo.toml +├── host +│ ├── Cargo.toml +│ └── src +│ └── main.rs <-- [Host code goes here] +└── methods + ├── Cargo.toml + ├── build.rs + ├── guest + │ ├── Cargo.toml + │ └── src + │ └── bin + │ └── method_name.rs <-- [Guest code goes here] + └── src + └── lib.rs +``` + +## Video Tutorial + +For a walk-through of how to build with this template, check out this [excerpt +from our workshop at ZK HACK III][zkhack-iii]. + +## Questions, Feedback, and Collaborations + +We'd love to hear from you on [Discord][discord] or [Twitter][twitter]. + +[bonsai access]: https://bonsai.xyz/apply +[cargo-risczero]: https://docs.rs/cargo-risczero +[crates]: https://github.com/risc0/risc0/blob/main/README.md#rust-binaries +[dev-docs]: https://dev.risczero.com +[dev-mode]: https://dev.risczero.com/api/zkvm/dev-mode +[discord]: https://discord.gg/risczero +[docs.rs]: https://docs.rs/releases/search?query=risc0 +[examples]: https://github.com/risc0/risc0/tree/main/examples +[risc0-build]: https://docs.rs/risc0-build +[risc0-repo]: https://www.github.com/risc0/risc0 +[risc0-zkvm]: https://docs.rs/risc0-zkvm +[rustup]: https://rustup.rs +[rust-toolchain]: rust-toolchain.toml +[twitter]: https://twitter.com/risczero +[zkvm-overview]: https://dev.risczero.com/zkvm +[zkhack-iii]: https://www.youtube.com/watch?v=Yg_BGqj_6lg&list=PLcPzhUaCxlCgig7ofeARMPwQ8vbuD6hC5&index=5 diff --git a/cosmwasm-verifier/risc0-example/host/Cargo.toml b/cosmwasm-verifier/risc0-example/host/Cargo.toml new file mode 100644 index 0000000..93859c3 --- /dev/null +++ b/cosmwasm-verifier/risc0-example/host/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "host" +version = "0.1.0" +edition = "2021" + +[dependencies] +methods = { path = "../methods" } +risc0-zkvm = { version = "=1.0.5" } +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +serde_json = "1.0.111" +anyhow = "1.0.80" diff --git a/cosmwasm-verifier/risc0-example/host/src/main.rs b/cosmwasm-verifier/risc0-example/host/src/main.rs new file mode 100644 index 0000000..b46992b --- /dev/null +++ b/cosmwasm-verifier/risc0-example/host/src/main.rs @@ -0,0 +1,32 @@ +use methods::{FACTORS_ELF, FACTORS_ID}; +use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts}; + +fn main() -> anyhow::Result<()> { + // Initialize tracing. In order to view logs, run `RUST_LOG=info cargo run` + tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::filter::EnvFilter::from_default_env()) + .init(); + + let env = ExecutorEnv::builder().write(&7u64)?.write(&9u64)?.build()?; + + // Obtain the default prover. + let prover = default_prover(); + + // Produce a receipt by proving the specified ELF binary. + let receipt = prover + .prove_with_opts(env, FACTORS_ELF, &ProverOpts::groth16())? + .receipt; + + let journal = receipt.journal; + let groth16_receipt = receipt.inner.groth16().unwrap(); + + // Write receipt to a file as JSON + let receipt_json = serde_json::to_string(&(groth16_receipt, journal))?; + std::fs::write("../snark-receipt.json", receipt_json)?; + + // Write FACTORS_ID to a file as JSON + let factors_id_json = serde_json::to_string(&FACTORS_ID)?; + std::fs::write("../factors_id.json", factors_id_json)?; + + Ok(()) +} diff --git a/cosmwasm-verifier/risc0-example/methods/Cargo.toml b/cosmwasm-verifier/risc0-example/methods/Cargo.toml new file mode 100644 index 0000000..41f313f --- /dev/null +++ b/cosmwasm-verifier/risc0-example/methods/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "methods" +version = "0.1.0" +edition = "2021" + +[build-dependencies] +risc0-build = { version = "=1.0.5" } + +[package.metadata.risc0] +methods = ["guest"] diff --git a/cosmwasm-verifier/risc0-example/methods/build.rs b/cosmwasm-verifier/risc0-example/methods/build.rs new file mode 100644 index 0000000..08a8a4e --- /dev/null +++ b/cosmwasm-verifier/risc0-example/methods/build.rs @@ -0,0 +1,3 @@ +fn main() { + risc0_build::embed_methods(); +} diff --git a/cosmwasm-verifier/risc0-example/methods/guest/Cargo.toml b/cosmwasm-verifier/risc0-example/methods/guest/Cargo.toml new file mode 100644 index 0000000..ed5137a --- /dev/null +++ b/cosmwasm-verifier/risc0-example/methods/guest/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "factors" +version = "0.1.0" +edition = "2021" + +[workspace] + +[dependencies] +# If you want to try (experimental) std support, add `features = [ "std" ]` to risc0-zkvm +risc0-zkvm = { version = "=1.0.5", default-features = false } diff --git a/cosmwasm-verifier/risc0-example/methods/guest/src/main.rs b/cosmwasm-verifier/risc0-example/methods/guest/src/main.rs new file mode 100644 index 0000000..182b105 --- /dev/null +++ b/cosmwasm-verifier/risc0-example/methods/guest/src/main.rs @@ -0,0 +1,21 @@ +#![no_main] +// If you want to try std support, also update the guest Cargo.toml file +#![no_std] // std support is experimental + +use risc0_zkvm::guest::env; + +risc0_zkvm::guest::entry!(main); + +pub fn main() { + // Load the first number from the host + let a: u64 = env::read(); + // Load the second number from the host + let b: u64 = env::read(); + // Verify that neither of them are 1 (i.e. nontrivial factors) + if a <= 1 || b <= 1 { + panic!("Trivial factors") + } + // Compute the product while being careful with integer overflow + let product = a.checked_mul(b).expect("Integer overflow"); + env::commit(&product); +} diff --git a/cosmwasm-verifier/risc0-example/methods/src/lib.rs b/cosmwasm-verifier/risc0-example/methods/src/lib.rs new file mode 100644 index 0000000..1bdb308 --- /dev/null +++ b/cosmwasm-verifier/risc0-example/methods/src/lib.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/methods.rs"));