diff --git a/example/wasm-hello-world/Cargo.toml b/example/wasm-hello-world/Cargo.toml index 140dada81..1c926aaa7 100644 --- a/example/wasm-hello-world/Cargo.toml +++ b/example/wasm-hello-world/Cargo.toml @@ -22,3 +22,4 @@ getrandom = { version = "0.2.8", features = ["custom"] } [features] default = [] no-entrypoint = [] +client = [ "darkfi-serial/async" ] diff --git a/example/wasm-hello-world/client/.gitignore b/example/wasm-hello-world/client/.gitignore new file mode 100644 index 000000000..8abde3c7a --- /dev/null +++ b/example/wasm-hello-world/client/.gitignore @@ -0,0 +1,4 @@ +/target +Cargo.lock +rustfmt.toml +client diff --git a/example/wasm-hello-world/client/Cargo.toml b/example/wasm-hello-world/client/Cargo.toml new file mode 100644 index 000000000..9843d768c --- /dev/null +++ b/example/wasm-hello-world/client/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "client" +version = "0.0.1" +description = "Basic client for wasm hello world example." +authors = ["Dyne.org foundation "] +repository = "https://codeberg.org/darkrenaissance/darkfi" +license = "AGPL-3.0-only" +edition = "2021" + +[workspace] + +[dependencies] +# The contract +wasm_hello_world = {path = "../", features = [ "client" ]} + +# Darkfi +darkfi = {path = "../../../", features = ["tx", "rpc"]} +darkfi-sdk = {path = "../../../src/sdk"} +darkfi-serial = "0.5.0" + +# Misc +clap = {version = "4.4.11", features = ["derive"]} +rand = "0.8.5" +smol = "2.0.2" +url = "2.5.4" + +[patch.crates-io] +halo2_proofs = {git="https://github.com/parazyd/halo2", branch="v031"} +halo2_gadgets = {git="https://github.com/parazyd/halo2", branch="v031"} diff --git a/example/wasm-hello-world/client/Makefile b/example/wasm-hello-world/client/Makefile new file mode 100644 index 000000000..521fff826 --- /dev/null +++ b/example/wasm-hello-world/client/Makefile @@ -0,0 +1,37 @@ +.POSIX: + +# Install prefix +PREFIX = $(HOME)/.cargo + +# Cargo binary +CARGO = cargo + +# Compile target +RUST_TARGET = $(shell rustc -Vv | grep '^host: ' | cut -d' ' -f2) +# Uncomment when doing musl static builds +#RUSTFLAGS = -C target-feature=+crt-static -C link-self-contained=yes + +SRC = \ + Cargo.toml \ + $(shell find src -type f -name '*.rs') \ + +BIN = $(shell grep '^name = ' Cargo.toml | cut -d' ' -f3 | tr -d '"') + +all: $(BIN) + +$(BIN): $(SRC) + RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) build --target=$(RUST_TARGET) --release --package $@ + cp -f target/$(RUST_TARGET)/release/$@ $@ + +fmt: + $(CARGO) +nightly fmt --all + +clippy: + RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) clippy --target=$(RUST_TARGET) \ + --release --all-features --workspace --tests + +clean: + RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) clean --target=$(RUST_TARGET) --release --package $(BIN) + rm -f $(BIN) + +.PHONY: all fmt clippy clean diff --git a/example/wasm-hello-world/client/src/commitment.rs b/example/wasm-hello-world/client/src/commitment.rs new file mode 100644 index 000000000..a6d06897c --- /dev/null +++ b/example/wasm-hello-world/client/src/commitment.rs @@ -0,0 +1,57 @@ +/* This file is part of DarkFi (https://dark.fi) + * + * Copyright (C) 2020-2025 Dyne.org foundation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use darkfi::{ + zk::{halo2::Value, Proof, ProvingKey, Witness, ZkCircuit}, + zkas::ZkBinary, + Result, +}; +use darkfi_sdk::crypto::Keypair; +use rand::rngs::OsRng; +use wasm_hello_world::HelloParams; + +pub struct ContractCallDebris { + pub params: HelloParams, + pub proofs: Vec, +} + +/// Struct holding necessary information to build a wasm-hello-world contract call. +pub struct ContractCallBuilder { + /// Member keypair this call is for + pub member: Keypair, + /// `SecretCommitment` zkas circuit ZkBinary + pub commitment_zkbin: ZkBinary, + /// Proving key for the `SecretCommitment` zk circuit, + pub commitment_pk: ProvingKey, +} + +impl ContractCallBuilder { + pub fn build(&self) -> Result { + // Build the commitment proof + let prover_witnesses = vec![Witness::Base(Value::known(self.member.secret.inner()))]; + let (public_x, public_y) = self.member.public.xy(); + let public_inputs = vec![public_x, public_y]; + let circuit = ZkCircuit::new(prover_witnesses, &self.commitment_zkbin); + let proof = Proof::create(&self.commitment_pk, &[circuit], &public_inputs, &mut OsRng)?; + + // Generate the params and call debris + let params = HelloParams { x: public_x, y: public_y }; + let debris = ContractCallDebris { params, proofs: vec![proof] }; + Ok(debris) + } +} diff --git a/example/wasm-hello-world/client/src/main.rs b/example/wasm-hello-world/client/src/main.rs new file mode 100644 index 000000000..812b6ab32 --- /dev/null +++ b/example/wasm-hello-world/client/src/main.rs @@ -0,0 +1,283 @@ +/* This file is part of DarkFi (https://dark.fi) + * + * Copyright (C) 2020-2025 Dyne.org foundation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::{collections::BTreeMap, str::FromStr, sync::Arc}; + +use clap::{Parser, Subcommand}; +use darkfi::{ + cli_desc, + rpc::{client::RpcClient, jsonrpc::JsonRequest, util::JsonValue}, + tx::{ContractCallLeaf, TransactionBuilder}, + util::encoding::base64, + zk::{empty_witnesses, ProvingKey, ZkCircuit}, + zkas::ZkBinary, + Error, Result, +}; +use darkfi_sdk::{ + crypto::{ContractId, Keypair, PublicKey, SecretKey}, + pasta::pallas, + ContractCall, +}; +use darkfi_serial::{deserialize, serialize, Encodable}; +use smol::Executor; +use url::Url; + +use wasm_hello_world::{ + ContractFunction, HELLO_CONTRACT_MEMBER_TREE, HELLO_CONTRACT_ZKAS_SECRETCOMMIT_NS, +}; + +mod commitment; +use commitment::ContractCallBuilder; + +#[derive(Parser)] +#[command(about = cli_desc!())] +struct Args { + #[arg(short, long)] + /// Deployed Contract ID + contract_id: String, + + #[arg(short, long, default_value = "tcp://127.0.0.1:8340")] + /// darkfid JSON-RPC endpoint + endpoint: Url, + + #[command(subcommand)] + command: Subcmd, +} + +#[derive(Subcommand)] +enum Subcmd { + /// Display current members + List { + /// Specific member to check if present (optional) + member: Option, + }, + + /// Generate a transaction adding a new member + Register { + /// To be added member secret key + member_secret: String, + }, + + /// Generate a transaction removing a member + Deregister { + /// To be removed member secret key + member_secret: String, + }, +} + +fn main() -> Result<()> { + // Parse arguments + let args = Args::parse(); + let contract_id = match ContractId::from_str(&args.contract_id) { + Ok(c) => c, + Err(e) => { + eprintln!("Invalid contract id: {e}"); + return Err(Error::ParseFailed("Invalid contract id")); + } + }; + + // Initialize an executor + let executor = Arc::new(Executor::new()); + smol::block_on(executor.run(async { + // Initialize an rpc client + let rpc_client = RpcClient::new(args.endpoint, executor.clone()).await?; + + // Execute a subcommand + match args.command { + Subcmd::List { member } => { + match member { + // Check if specific member exists in our contract members tree + Some(member) => { + // Parse the member public key + let member = PublicKey::from_str(&member)?; + + // Create the request params + let params = JsonValue::Array(vec![ + JsonValue::String(contract_id.to_string()), + JsonValue::String(HELLO_CONTRACT_MEMBER_TREE.to_string()), + JsonValue::String(member.to_string()), + ]); + + // Execute the request + let req = JsonRequest::new("blockchain.get_contract_state_key", params); + let rep = rpc_client.request(req).await?; + + // Parse response + let bytes = base64::decode(rep.get::().unwrap()).unwrap(); + + // Print info message + println!("Member {member} was found!"); + println!("Value validity check: {}", bytes.is_empty()); + } + // Retrieve all contract members tree records + None => { + // Create the request params + let params = JsonValue::Array(vec![ + JsonValue::String(contract_id.to_string()), + JsonValue::String(HELLO_CONTRACT_MEMBER_TREE.to_string()), + ]); + + // Execute the request + let req = JsonRequest::new("blockchain.get_contract_state", params); + let rep = rpc_client.request(req).await?; + + // Parse response + let bytes = base64::decode(rep.get::().unwrap()).unwrap(); + let members: BTreeMap, Vec> = deserialize(&bytes)?; + + // Print records + println!("{contract_id} members:"); + if members.is_empty() { + println!("No members found"); + } else { + let mut index = 1; + for member in members.keys() { + let member: pallas::Base = deserialize(member)?; + println!("{index}. {member:?}"); + index += 1; + } + } + } + } + } + + Subcmd::Register { member_secret } => { + // Parse the member secret key + let member_secret = SecretKey::from_str(&member_secret)?; + let member = Keypair::new(member_secret); + + // Now we need to do a lookup for the zkas proof bincodes, and create + // the circuit objects and proving keys so we can build the transaction. + // We also do this through the RPC. + let params = JsonValue::Array(vec![JsonValue::String(contract_id.to_string())]); + + // Execute the request + let req = JsonRequest::new("blockchain.lookup_zkas", params); + let rep = rpc_client.request(req).await?; + let params = rep.get::>().unwrap(); + + // Parse response + let mut zkas_bins = Vec::with_capacity(params.len()); + for param in params { + let zkas_ns = param[0].get::().unwrap().clone(); + let zkas_bincode_bytes = + base64::decode(param[1].get::().unwrap()).unwrap(); + zkas_bins.push((zkas_ns, zkas_bincode_bytes)); + } + + let Some(commitment_zkbin) = + zkas_bins.iter().find(|x| x.0 == HELLO_CONTRACT_ZKAS_SECRETCOMMIT_NS) + else { + return Err(Error::Custom("Secret commitment circuit not found".to_string())) + }; + let commitment_zkbin = ZkBinary::decode(&commitment_zkbin.1)?; + let commitment_circuit = + ZkCircuit::new(empty_witnesses(&commitment_zkbin)?, &commitment_zkbin); + + // Creating secret commitment circuit proving keys + let commitment_pk = ProvingKey::build(commitment_zkbin.k, &commitment_circuit); + + // Create the contract call + let builder = ContractCallBuilder { member, commitment_zkbin, commitment_pk }; + let debris = builder.build()?; + + // Encode the call + let mut data = vec![ContractFunction::Register as u8]; + debris.params.encode(&mut data)?; + let call = ContractCall { contract_id, data }; + + // Create the TransactionBuilder containing above call + let mut tx_builder = TransactionBuilder::new( + ContractCallLeaf { call, proofs: debris.proofs }, + vec![], + )?; + + // Build the transaction and attach the corresponding signatures + let mut tx = tx_builder.build()?; + let sigs = tx.create_sigs(&[])?; + tx.signatures.push(sigs); + + println!("{}", base64::encode(&serialize(&tx))); + } + + Subcmd::Deregister { member_secret } => { + // Parse the member secret key + let member_secret = SecretKey::from_str(&member_secret)?; + let member = Keypair::new(member_secret); + + // Now we need to do a lookup for the zkas proof bincodes, and create + // the circuit objects and proving keys so we can build the transaction. + // We also do this through the RPC. + let params = JsonValue::Array(vec![JsonValue::String(contract_id.to_string())]); + + // Execute the request + let req = JsonRequest::new("blockchain.lookup_zkas", params); + let rep = rpc_client.request(req).await?; + let params = rep.get::>().unwrap(); + + // Parse response + let mut zkas_bins = Vec::with_capacity(params.len()); + for param in params { + let zkas_ns = param[0].get::().unwrap().clone(); + let zkas_bincode_bytes = + base64::decode(param[1].get::().unwrap()).unwrap(); + zkas_bins.push((zkas_ns, zkas_bincode_bytes)); + } + + let Some(commitment_zkbin) = + zkas_bins.iter().find(|x| x.0 == HELLO_CONTRACT_ZKAS_SECRETCOMMIT_NS) + else { + return Err(Error::Custom("Secret commitment circuit not found".to_string())) + }; + let commitment_zkbin = ZkBinary::decode(&commitment_zkbin.1)?; + let commitment_circuit = + ZkCircuit::new(empty_witnesses(&commitment_zkbin)?, &commitment_zkbin); + + // Creating secret commitment circuit proving keys + let commitment_pk = ProvingKey::build(commitment_zkbin.k, &commitment_circuit); + + // Create the contract call + let builder = ContractCallBuilder { member, commitment_zkbin, commitment_pk }; + let debris = builder.build()?; + + // Encode the call + let mut data = vec![ContractFunction::Deregister as u8]; + debris.params.encode(&mut data)?; + let call = ContractCall { contract_id, data }; + + // Create the TransactionBuilder containing above call + let mut tx_builder = TransactionBuilder::new( + ContractCallLeaf { call, proofs: debris.proofs }, + vec![], + )?; + + // Build the transaction and attach the corresponding signatures + let mut tx = tx_builder.build()?; + let sigs = tx.create_sigs(&[])?; + tx.signatures.push(sigs); + + println!("{}", base64::encode(&serialize(&tx))); + } + } + + // Stop the rpc client + rpc_client.stop().await; + + Ok(()) + })) +} diff --git a/example/wasm-hello-world/src/entrypoint.rs b/example/wasm-hello-world/src/entrypoint.rs index d45aba5ed..d87f8f848 100644 --- a/example/wasm-hello-world/src/entrypoint.rs +++ b/example/wasm-hello-world/src/entrypoint.rs @@ -122,9 +122,7 @@ fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult { } } - let mut update_data = vec![func as u8]; - commitment.encode(&mut update_data)?; - wasm::util::set_return_data(&serialize(&update_data)) + wasm::util::set_return_data(&serialize(&commitment)) } fn process_update(cid: ContractId, update_data: &[u8]) -> ContractResult { diff --git a/example/wasm-hello-world/src/lib.rs b/example/wasm-hello-world/src/lib.rs index 491493f28..c4dc3472d 100644 --- a/example/wasm-hello-world/src/lib.rs +++ b/example/wasm-hello-world/src/lib.rs @@ -19,6 +19,9 @@ use darkfi_sdk::{error::ContractError, pasta::pallas}; use darkfi_serial::{SerialDecodable, SerialEncodable}; +#[cfg(feature = "client")] +use darkfi_serial::async_trait; + /// Functions available in the contract #[repr(u8)] pub enum ContractFunction {