/* 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(()) })) }