# Custom Smart Contracts Users can deploy their own zero-knowledge contracts, written for the DarkFi [zkVM][1], becoming anonymous engineers themselves! More information about the smart contracts architecture can be found [here][2]. ## Hello World For the purposes of this guide we are going to use the smart contract template found [here][3]. It's a very simple smart contract simulating a registration ledger, where users can create their membership keys and register or remove themselves from it. Each user is represented as the [poseidon hash][4] of their membership public key. First, open another terminal, clone the template repository and enter its directory: ```shell $ git clone https://codeberg.org/darkrenaissance/smart-contract $ cd smart-contract ``` We will use the provided Makefile to generate the contract WASM bincode and its client. The Makefile uses [zkas][5], DarkFi's zero-knowledge proof circuit compiler, to do this. Running `make` will compile the ZK proof circuits that protect the contract's privacy, build the WASM module that runs on the DarkFi network, and build a command-line client for interacting with the contract. We first need to ensure the Makefile is configured so that it can find the `zkas` binary. That might mean you need to modify the path shown below. ```makefile # Path to the zkas compiler (or just `zkas` if it's in your $PATH) ZKAS = ../darkfi/zkas ``` ```shell $ make ../darkfi/zkas proof/membership_proof.zk -o proof/membership_proof.zk.bin Wrote output to proof/membership_proof.zk.bin cargo build --target=wasm32-unknown-unknown --release --lib ... Compiling membership v0.0.1 (/home/anon/smart-contract) Finished `release` profile [optimized] target(s) in 14.45s cp -f target/wasm32-unknown-unknown/release/membership.wasm membership.wasm cargo build --release --features=client --bin membership ... Compiling membership v0.0.1 (/home/anon/smart-contract) Finished `release` profile [optimized] target(s) in 30.78s cp -f target/release/membership membership ``` Now both the contract and its client are ready to use. Leave this terminal open, as we will come back to it later in the guide, and return back to your `drk` interactive shell. ## Creating contracts Each contract is controlled by a secret key, from which its Contract ID derives. To deploy a smart contract, we first need to generate an authority keypair. The Contract ID shown in the outputs is a placeholder for the one that will be generated from you. In the rest of the guide, use the one you generated by replacing the corresponding placeholder. We can create our own contract authority by executing the following command: ```shell drk> contract generate-deploy Generating a new keypair Created new contract deploy authority Contract ID: {CONTRACT_ID} ``` You can list your mint authorities with: ```shell drk> contract list Contract ID | Secret Key | Locked | Lock Height ---------------+-----------------------+--------+------------- {CONTRACT_ID} | {CONTRACT_SECRET_KEY} | false | - ``` ## Deploy transaction Now that we have a contract authority, we can deploy the example contract we compiled earlier using it: ```shell drk> contract deploy {CONTRACT_ID} ../smart-contract/membership.wasm | broadcast [mark_tx_spend] Processing transaction: d0824bb0ecb9b12af69579c01c570c0275e399b80ef10f0a9c645af65bdd0415 [mark_tx_spend] Found Money contract in call 1 Broadcasting transaction... Transaction ID: d0824bb0ecb9b12af69579c01c570c0275e399b80ef10f0a9c645af65bdd0415 ``` Now the transaction should be published to the network. When the transaction is confirmed, the contract history will show its record: ```shell drk> contract list {CONTRACT_ID} Transaction Hash | Type | Block Height ------------------+------------+-------------- {TX_HASH} | DEPLOYMENT | 34 ``` We can redeploy the contract as many times as we want, as long as it's not locked. Each redeployment will show a new record in the contract history. We can also export the deployed data by executing: ```shell drk> contract export-data {TX_HASH} > membership.dat ``` The exported file contains the WASM bincode and instruction data deployed by that transaction, encoded in `base64` as a tuple. ## Lock transaction After we finished deploying our contract and don't require further code changes, we can lock it. This will not allow further deployment transactions, effectively locking the smart contract on-chain code. To lock down the contract, execute: ```shell drk> contract lock {CONTRACT_ID} | broadcast [mark_tx_spend] Processing transaction: 9eee9799d77d0ef1dd115738982296c9c481b4412c75a0a0955fd67d87bfe6a0 [mark_tx_spend] Found Money contract in call 1 Broadcasting transaction... Transaction ID: 9eee9799d77d0ef1dd115738982296c9c481b4412c75a0a0955fd67d87bfe6a0 ``` After the transaction has been confirmed, we will see our contract `Locked` status set to `true`, along with the block height it was locked on: ```shell drk> contract list Contract ID | Secret Key | Locked | Lock Height ---------------+-----------------------+--------+------------- {CONTRACT_ID} | {CONTRACT_SECRET_KEY} | true | 36 ``` We will also see the lock transaction in its history: ```shell drk> contract list {CONTRACT_ID} Transaction Hash | Type | Block Height ------------------+------------+-------------- {TX_HASH} | DEPLOYMENT | 34 {LOCK_TX_HASH} | LOCK | 36 ``` ## Interacting with the smart contract Now that the contract code is set on-chain and cannot be modified further, let's interact with it using its client! ### Registration Let's go to the contract client terminal, and create our membership keys: ```shell $ ./membership generate Secret key: {IDENTITY_SECRET_KEY} Public key: {IDENTITY_PUBLIC_KEY} ``` > Note: This is a very basic example client so secret keys are used > as plaintext for simplicity. Do not run this in a machine with > commands history or in a hostile environment where your secret key > can be exposed. Now we can create our `register` call using our membership secret key: ```shell $ ./membership register {CONTRACT_ID} {IDENTITY_SECRET_KEY} > register.call ``` Now we need to go back to our `drk` interactive shell, to generate the actual registration transaction, attach a fee to it and broadcast it to the network: ```shell drk> tx-from-calls < ../smart-contract/register.call | broadcast [mark_tx_spend] Processing transaction: 23ea7d01ae16389e71d73fa27748ce1633d39c6b55a4aa31d8f5ba1017a4f840 [mark_tx_spend] Found Money contract in call 1 Broadcasting transaction... Transaction ID: 23ea7d01ae16389e71d73fa27748ce1633d39c6b55a4aa31d8f5ba1017a4f840 ``` After the transaction has been confirmed, our membership commitment will exist in our contract registry. ### Deregistration To remove ourselves from the registry, we create a `deregister` call with the contract client: ```shell $ ./membership deregister {CONTRACT_ID} {IDENTITY_SECRET_KEY} > deregister.call ``` Then, we build the actual deregistration transaction again, attach its fee and broadcast it to the network: ```shell drk> tx-from-calls < ../smart-contract/deregister.call | broadcast [mark_tx_spend] Processing transaction: f3304e6f5673d9ece211af6dd85c70ec8c8e85e91439b8cffbcf5387b11de1d0 [mark_tx_spend] Found Money contract in call 1 Broadcasting transaction... Transaction ID: f3304e6f5673d9ece211af6dd85c70ec8c8e85e91439b8cffbcf5387b11de1d0 ``` When the transaction gets confirmed, our membership commitment will not exist in our contract registry. ### Extending the smart contract client The template client is barebones and doesn't provide us with a way to view the on chain records of our registry. For that purpose we can create a new small program, or extend the client to support this functionality. Following you will find example code for retrieving a smart contract's on-chain records from the [JSON-RPC][6] server in `darkfid` which we can use to list our registry records: {{#tabs }} {{#tab name="Rust" }} ```rust // Parse Contract ID let contract_id = match ContractId::from_str(&args.contract_id)?; // Initialize an rpc client let rpc_client = RpcClient::new(args.endpoint, executor.clone()).await?; // Create the request params let params = JsonValue::Array(vec![ JsonValue::String(contract_id.to_string()), JsonValue::String(String::from("smart-contract-members")), ]); // 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; } } ``` {{#endtab }} {{#tab name="Python" }} ```python import json import socket payload = json.dumps({ "jsonrpc": "2.0", "method": "blockchain.get_contract_state", "params": [contract_id, "smart-contract-members"], "id": 1, }) with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.connect((host, port)) sock.sendall((payload + "\n").encode("utf-8")) response = b"" while True: chunk = sock.recv(1) if not chunk or chunk == b"\n": break response += chunk print(json.loads(response.decode("utf-8"))) ``` {{#endtab }} {{#endtabs }} [1]: ../zkas/index.md [2]: ../arch/sc/sc.md [3]: https://codeberg.org/darkrenaissance/smart-contract [4]: ../spec/crypto-schemes.md#poseidonhash-function [5]: ../zkas/zkas.md [6]: ../clients/darkfid_jsonrpc.md