Custom Smart Contracts

Users can deploy their own zero-knowledge contracts, written for the DarkFi zkVM, becoming anonymous engineers themselves!

More information about the smart contracts architecture can be found here.

Hello World

For the porpuses of this guide, an example smart contract is provided, which users can deploy to the testnet and interact with. This is a very simple smart contract simulating a registration ledger, where users can register or remove themselves from it, and each user is represented as the poseidon hash of their public key.

First, open another terminal and navigate (from repo root) to the example:

$ cd example/wasm-hello-world

Here, generate the contract WASM bincode by executing:

$ make

"../../zkas" proof/secret_commitment.zk -o proof/secret_commitment.zk.bin
Wrote output to proof/secret_commitment.zk.bin
RUSTFLAGS="" cargo build --target=wasm32-unknown-unknown \
        --release --package wasm_hello_world
...
    Compiling wasm_hello_world v0.0.1 (/home/anon/darkfi/example/wasm-hello-world)
    Finished `release` profile [optimized] target(s) in 15.90s
cp -f target/wasm32-unknown-unknown/release/wasm_hello_world.wasm wasm_hello_world.wasm
wasm-strip wasm_hello_world.wasm

Apart from the contract, an example client to interact with it is provided, which we also need to compile:

$ cd client
$ make

RUSTFLAGS="" cargo build --target=x86_64-unknown-linux-gnu --release --package client
...
   Compiling client v0.0.1 (/home/anon/darkfi/example/wasm-hello-world/client)
    Finished `release` profile [optimized] target(s) in 2m 29s
cp -f target/x86_64-unknown-linux-gnu/release/client client

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 need to generate an authority keypair first. The Contract ID shown in the outputs is a placeholder for the one that will be generated from you. In 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:

drk> contract generate-deploy

Generating a new keypair
Created new contract deploy authority
Contract ID: {CONTRACT_ID}

You can list your mint authorities with:

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:

drk> contract deploy {CONTRACT_ID} example/wasm-hello-world/wasm_hello_world.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:

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:

drk> contract export-data {TX_HASH} > wasm_hello_world.dat

The exported files 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:

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:

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:

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!

NOTE: This is a very basic example client so secrets keys are used as plainext for simplicity. Do not run this in a machine with commands history or in a hostile environment where your secret key can be exposed.

First lets generate a new throwaway address to register:

drk> wallet keygen

Generating a new keypair
New address:
{DUMMY_ADDRESS}

Set it as the default one in the wallet and grab its secret key:

drk> wallet addresses

 Key ID                | Public Key       | Secret Key                  | Is Default
-----------------------+------------------+-----------------------------+------------
 1                     | {NORMAL_ADDRESS} | {NORMAL_ADDRESS_SECRET_KEY} | *
 ...
 {DUMMY_ADDRESS_INDEX} | {DUMMY_ADDRESS}  | {DUMMY_ADDRESS_SECRET_KEY}  |

drk> wallet default-address {DUMMY_ADDRESS_INDEX}

List members

Let's go to the contract client terminal, and check current members:

$ ./client -c {CONTRACT_ID} list

{CONTRACT_ID} members:
No members found

No one has registered yet, so let's change that!

Registration

Let's go to the contract client terminal, and register ourselves:

$ ./client -c {CONTRACT_ID} register {DUMMY_ADDRESS_SECRET_KEY} > register.tx

The produced transaction doesn't contain a fee, so we need to attach it in our drk interactive shell:

drk> attach-fee < example/wasm-hello-world/client/register.tx | broadcast

[mark_tx_spend] Processing transaction: 23ea7d01ae16389e71d73fa27748ce1633d39c6b55a4aa31d8f5ba1017a4f840
[mark_tx_spend] Found Money contract in call 1
Broadcasting transaction...
Transaction ID: 23ea7d01ae16389e71d73fa27748ce1633d39c6b55a4aa31d8f5ba1017a4f840

After a while, we will see a new member in our contract registry:

$ ./client -c {CONTRACT_ID} list

{CONTRACT_ID} members:
1. 0x242d4a0dc358e0b61053c28352f666bace79889559b65e91efd1c83c7c817fdd

Deregistration

To remove ourselves from the registry, we create a deregister transaction with the contract client:

$ ./client -c {CONTRACT_ID} deregister {DUMMY_ADDRESS_SECRET_KEY} > deregister.tx

Then, we attach the fee again and broadcast it to the network:

drk> attach-fee < example/wasm-hello-world/client/deregister.tx | 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 registry will not have members again:

$ ./client -c {CONTRACT_ID} list

{CONTRACT_ID} members:
No members found