Enigma brings data privacy to smart contracts. The basic building blocks for this are secret contracts: smart contracts that execute on the decentralized network without revealing input data, ensuring data privacy. In light of our most recent Discovery release (which includes encrypted state, Rust-based secret contracts, and other new features), we are continuing with our three-part “getting started” series for developers, which covers:

a guide to Rust an introduction to Secret Contracts and setting up the Enigma developer environment building a front end for our Enigma-powered dApp

We had previously planned on pushing the material regarding the Enigma developer environment setup, network deployment, and testing of secret contracts for the 3rd post, but rapid progress made by the team has made it possible (and more logical) to include that material in this guide itself!

💡Tip:

Want to see the repo implementing all the completed code from this 3-part guide? Save yourself some copy-paste and check it out.

Part 2: An Introduction to Secret Contracts

In the previous installment of this series, we discussed our motivation for using Rust as our language of choice for writing secret contracts and dove into developing a standalone Rust program that naively accomplishes Yao’s Millionaires’ Problem. If you have not gone over that yet, or are simply in need of a refresher, we highly recommend you go through that quick guide before continuing here.

What follows is a walkthrough guide where we will install/set up the Enigma developer testnet and write/unit test our first Rust secret contract underpinning our decentralized, privacy-centric implementation of Yao’s Millionaires’ Problem.

While the last post was absolutely critical towards developing a strong general Rust foundation, it intentionally fell short of solving for data privacy…but not today! Today we begin unveiling secret contracts with the following goals in mind:

understanding how to setup the Enigma Discovery docker network and initialize your coding environment developing an understanding of what makes a secret contract special conceptually and syntactically

Setting Up the Enigma Developer Testnet

Installing and Basic Setup

In order to get started writing our first secret contract, we first need to set up our developer environment. Let us open up our terminal and take the following steps:

Install the discovery-cli , a command line interface (CLI) for the Enigma protocol’s Discovery release dev environment by globally npm-installing the package: npm i -g @enigmampc/discovery-cli Create and navigate to a directory in which we will write our project code:

mkdir <path>/millionaires_problem_demo

cd <path>/millionaires_problem_demo Initialize this directory as an Enigma project by running discovery init . This command pulls the necessary pre-built docker images to run the Enigma testnet locally. When asked whether to set up the environment in the current folder, choose Y for yes. When asked whether to use hardware mode (HW) or simulation mode (SW), we will select sw . Finally, choose Y to allow the docker images to be pulled to your machine — if this is your first time creating an Enigma project and/or pulling these docker images, this may take ~15–20 minutes.

Nice! The basic tool belt for writing secret contracts and Enigma-powered dApps has now been installed. Some intuitive and logical scaffolding (especially for those of you with experience working with Truffle) has been auto-generated that we can easily work with:

build : All of our compiled build files are written here and organized by type depending on whether they are Enigma protocol contracts, Ethereum smart contracts, or Enigma secret contracts (often abbreviated ESC as a heads up)

: All of our compiled build files are written here and organized by type depending on whether they are Enigma protocol contracts, Ethereum smart contracts, or Enigma secret contracts (often abbreviated ESC as a heads up) migrations : Our migrations files, much like you would see in a Truffle environment

: Our migrations files, much like you would see in a Truffle environment smart_contracts : Any Ethereum smart contracts central to your dApp logic, with an existing example contract, Sample.sol

: Any Ethereum smart contracts central to your dApp logic, with an existing example contract, secret_contracts : Any Enigma secret contracts central to your dApp logic, with an existing example Rust library simple_addition . We have included a Cargo.toml.template file which you can use as a foundation for your custom library’s Cargo.toml specification.

: Any Enigma secret contracts central to your dApp logic, with an existing example Rust library . We have included a file which you can use as a foundation for your custom library’s specification. test : Any unit tests for your contracts

Starting/Stopping the Dockerized Enigma Discovery Network

From within our Discovery-initialized project, to:

Start the network — discovery start

Stop the network — discovery stop

Let us go ahead and start the network, which should only take 15–20 seconds (unless it is your first time, in which case it may take 3–4 minutes).

Project Structure

As mentioned above, some helpful sample contracts and folders are generously part of the scaffolding — we encourage you to reference these in the future as you write your own dApps.

We will go ahead and create a brand new Rust library containing our secret contract logic with the following command: cargo new secret_contracts/millionaires_problem --lib

Copy and paste the contents of the secret_contracts/Cargo.toml.template file into the Cargo.toml file inside the library, secret_contracts/millionaires_problem , which we have just created. We will add just one line that will be explained a bit later underneath the [dependencies] that looks like this: serde = “1.0.84” . Our full, Cargo.toml file should now look like this:

[package]

name = "contract"

version = "0.1.0"



[dependencies]

eng-wasm = "0.1.3"

eng-wasm-derive = "0.1.3"

serde = "1.0.84"



[lib]

crate-type = ["cdylib"]



[profile.release]

panic = "abort"

lto = true

opt-level = "z"

Amazing — now we are all set up and good to get started writing our very first secret contract!

Writing the Millionaires’ Problem Secret Contract

Our secret contract logic will be written entirely inside the file located at secret_contracts/millionaires_problem/src/lib.rs . Replace the auto-generated contents in this file with the following secret contract code. Do not worry if it looks overwhelming, we break each part down to explain exactly what is going on right after!

Built-In Attributes [Lines 1–2]

We begin all of our secret contracts exactly like this. While Rust’s standard library comes with a wide array of useful and well-tested abstractions, not all environments support these features, SGX being one of these environments. The `#![no_std]` attribute removes the standard library from our program’s prelude accordingly.

Imports [Lines 4–11]

In order to import certain package functionality we bring the following packages into scope:

eng_wasm — This crate allows us to use the Enigma runtime, offering important functionalities that we will soon see, including reading from state, writing to state, and printing.

— This crate allows us to use the Enigma runtime, offering important functionalities that we will soon see, including reading from state, writing to state, and printing. eng_wasm_derive — This crate provides functions exposed by the contract that can be called from the Enigma network and the ability to call functions of Ethereum contracts from a secret contract (will be demonstrated in a later post, but it is super easy and simple, we promise!).

— This crate provides functions exposed by the contract that can be called from the Enigma network and the ability to call functions of Ethereum contracts from a secret contract (will be demonstrated in a later post, but it is super easy and simple, we promise!). eng_wasm_derive::pub_interface — Gives us the ability to add the #[pub_interface] trait to any public-facing functions within our secret contract. We will cover this further shortly.

— Gives us the ability to add the trait to any public-facing functions within our secret contract. We will cover this further shortly. serde::{Serialize, Deserialize} — Allows us to serialize and deserialize custom struct types for example into and out of our secret contract state. We will cover this further shortly. And for those of you keeping track, this serde import is why we added the corresponding line item to the template Cargo.toml file above.

Enigma State Key [Lines 13–14]

As you must all be aware of by now, secret contracts can maintain encrypted state. In order for our contract to eventually compute the richest millionaire, it needs to maintain a list of millionaires in state. Thus our state needs to have an entry <key, value> pair that looks something like this: <"millionaires", <vector of Millionaire structs>> . As demonstrated, we chose "millionaires" to be the key into this encrypted state maintaining the list of millionaires.

Custom Structs [Lines 16–21]

A millionaire’s metadata consists of a couple key attributes: his/her ETH address and net_worth . What is particularly interesting here are the variable types. We chose address to be of type H160 , which corresponds to a 160-bit/20-byte hash value (recall that ETH addresses are 20 bytes long) and net_worth to be of type U256 , which corresponds to a 256-bit/32-byte value. For those of you with Solidity backgrounds, you can think of these as address and uint256 types, respectively.

Contract Struct [Lines 23–24]

We initialize a public struct called Contract which can consist of both private and public-facing functions to be defined afterwards.

Private Methods [Lines 26–31]

We implement our private methods for the secret contract in this fashion. In our particular case, we have a private method called get_millionaires() which returns Vec<Millionaire> , a vector of Millionaire structs stored in state. Take special note of how we read from secret contract state with the macro read_state!(<key>) . The unwrap_or_default() method is a handy way of returning the contained value, otherwise the default for that type.

Public Method Interface Definition [Lines 33–38]

We need to declare the public-facing secret contract signatures, and we do so by creating a public trait with the #[pub_interface] macro. In our case, we created a trait called ContractInterface and created two method definitions:

add_millionaire — Allows us to add/register a new millionaire with an Ethereum address ( H160 ) and net worth ( U256 )

— Allows us to add/register a new millionaire with an Ethereum address ( ) and net worth ( ) compute_richest — Allows us to compute the richest millionaire. This takes no parameters (will only read from state) and returns the richest millionaire’s address, which is of type H160

— Allows us to compute the richest millionaire. This takes no parameters (will only read from state) and returns the richest millionaire’s address, which is of type construct — This is actually not present in our secret contract, because we are not initializing our contract with any data. Therefore, we can get away with leaving it out entirely, meaning it defaults to the implicit construct() .

Public Method Implementation [Lines 40–62]

In this section, we implement the above-defined public-facing secret contract methods. Be sure to note the #[no_mangle] annotation above every method definition, which turns off Rust’s automatic method/function name-changing behavior.

add_millionaire — We read the secret contract state to obtain the list of Millionaire structs (using the private method get_millionaires mentioned above). We push a new Millionaire struct with the fields initialized to the parameters passed into this method ( address and net_worth ). And finally, we update/write to the secret contract state with the macro write_state!(<key_1> => <value_1>, <key2 => <value_2>, ..., <key_n> => <value_n>) .

— We read the secret contract state to obtain the list of Millionaire structs (using the private method mentioned above). We push a new struct with the fields initialized to the parameters passed into this method ( and ). . compute_richest — Once again, we obtain the list of Millionaire structs stored in contract state. Using a combination of Rust’s powerful match and max_by_key functionalities, we return the address associated with the millionaire with the highest net_worth , otherwise the empty address 0x0 .

— Once again, we obtain the list of structs stored in contract state. Using a combination of Rust’s powerful and functionalities, we return the associated with the millionaire with the highest , otherwise the empty address . construct —As mentioned above, since we do not need our own implementation of a secret contract constructor, we are utilizing the default empty construct() definition, and thus leave it out entirely.

And that is about it — by this point, you have written your very first Enigma secret contract! Next up, we will explore how to compile, migrate, and unit-test our contract.

Compiling Contracts

Much like we would compile our Ethereum contracts in a Truffle environment, we need to compile the contracts (both Ethereum smart contracts and Enigma secret contracts) in our project. We accomplish this by running: discovery compile . Any Ethereum smart contracts compile into .json files under build/smart_contracts and any Enigma secret contracts compile into .wasm files under build/secret_contracts . The .wasm files will be named according to the Rust library name within which the secret contract has been written. The secret contract above should compile without any issues, but in the off-chance there is an error, it should be quick to trace it down given Rust’s excellent compilation error explanations.

Deploying/Migrating Contracts

Just as is the case for contract compilation, Enigma’s migration framework is analogous to Truffle’s setup as well, so it should be a seamless transition for most of you. The scaffolding already generates the initial migration file ( 1_initial_migration.js ) and a second migration file that we will edit ( 2_deploy_contracts.js ). This second file includes a handy-dandy helper function deploySecretContract(config) that will make it really simple for us to deploy our millionaires’ problem secret contract and any other secret contracts. Add the following bit of code to the end of this file, but make sure it is included before the last }; !

const configMillionairesProblem = {

filename: 'millionaires_problem.wasm',

fn: 'construct()',

args: [],

gasLimit: 1000000,

gasPrice: utils.toGrains(1),

from: accounts[0]

};

const addressMillionairesProblem = await deploySecretContract(configMillionairesProblem);

console.log(`Secret Contract "${configMillionairesProblem.filename}" deployed at Enigma address: ${addressMillionairesProblem}`);

As you can see, we created a configMillionairesProblem object with the necessary metadata to deploy our contract and pass that to our helper function. The important metadata for a secret contract deployment task includes:

filename — This filename should be of the format <secret contract Rust lib name>.wasm . If you recall, earlier in this guide we created a new Rust library within the secret_contracts directory called millionaires_problem , thus we point to the compiled secret contract called millionaires_problem.wasm .

— This filename should be of the format . If you recall, earlier in this guide we created a new Rust library within the directory called , thus we point to the compiled secret contract called . fn — The function signature we are calling, which in the case of a deployment is always construct(type_1,type_2,...,type_n) . In our situation, there are no arguments, thus simply is construct() . If you recall from above, our secret contract has the corresponding hidden construct() function (hidden because when we are just using the empty constructor, we do not need to add/implement it in our secret contract. It comes for free!). However, say for example there were two 32-byte numbers passed to initialize our contract, our fn attribute would look like this instead: construct(uint256,uint256) (and there would be an explicitly-defined constructor in our secret contract as well). In other words, this fn should match the function signature of the constructor within our secret contract, but be extra careful to not include any spaces whatsoever between the argument types. Also, in the context of this hypothetical example, take special note that the types here are meant to be Solidity types (ex. uint256 ) even though the corresponding Enigma secret contract constructor defines them differently (ex. U256 ).

— The function signature we are calling, which in the case of a deployment is always . In our situation, there are no arguments, thus simply is . If you recall from above, our secret contract has the corresponding hidden function (hidden because when we are just using the empty constructor, we do not need to add/implement it in our secret contract. It comes for free!). However, say for example there were two 32-byte numbers passed to initialize our contract, our attribute would look like this instead: (and there would be an explicitly-defined constructor in our secret contract as well). In other words, this should match the function signature of the constructor within our secret contract, but be extra careful to not include any spaces whatsoever between the argument types. Also, in the context of this hypothetical example, take special note that the types here are meant to be Solidity types (ex. ) even though the corresponding Enigma secret contract constructor defines them differently (ex. ). args — Any arguments mean to be passed into the constructor. In our case, the constructor itself is empty, thus args are simply [] . If there were to be args, however, it would be written in this format [[arg_1, type_1], [arg_2, type_2], ..., [arg_n, type_n]] . For example, if the Enigma secret contract’s constructor were to accept two U256 types, we might pass in an args value that looks like this: [[5, ‘uint256’], [10, ‘uint256’]] . Once again, take special note here that the types are the Solidity types, even though the secret contract has defined them differently.

— Any arguments mean to be passed into the constructor. In our case, the constructor itself is empty, thus args are simply . If there were to be args, however, it would be written in this format . For example, if the Enigma secret contract’s constructor were to accept two types, we might pass in an value that looks like this: . Once again, take special note here that the types are the Solidity types, even though the secret contract has defined them differently. gasLimit — ENG gas units to be used for the deployment task

— ENG gas units to be used for the deployment task gasPrice — ENG gas price to be used for the deployment task in grains format (10⁸)

— ENG gas price to be used for the deployment task in grains format (10⁸) from — Ethereum address deploying the contract

Now, all we need to do to migrate and deploy our contracts is: discovery migrate . Upon success, our terminal will log the addresses our contracts have been deployed at.

Writing Unit Tests

In order to test our contract, we will get our very first taste of the enigma-js library, a client-library designed to allow dApp developers/users interface with smart contracts (Enigma contract and other Ethereum contracts) and the Enigma network (including any secret contracts deployed). It is with this library that we can deploy secret contracts and execute compute tasks. But wait…we already deployed a contract — yes! Turns out our helper function in the deployment script above utilized the enigma-js library without us even realizing, but now we will actually take a closer look at the library and a few of its functions, and use it a little more directly. Create a new file at test/test_millionaires_problem.js and paste the following code, which we will explain right after:

Secret Contract Address [Line 40]

The deployment/migration of the contract writes the address of the deployed secret contract to a file within our test directory. We can read the contents of this file to obtain the secret contract’s address, which is necessary for any computation task submissions within the remainder of the test.

Compute Task [Lines 45–59]

In order to create a new computation task, we call the enigma-js client-library’s computeTask wrapper function, which requires a couple key arguments:

fn — The signature of the function we are calling. In our case here, we are calling the add_millionaire(address: H160, net_worth: U256) function in our secret contract, thus we specify our task’s fn to be add_millionaire(address,uint256) . Once again, note that there are no spaces and the types are the Solidity-equivalents for the Rust-specified types in our secret contract.

— The signature of the function we are calling. In our case here, we are calling the function in our secret contract, thus we specify our task’s to be . Once again, note that there are no spaces and the types are the Solidity-equivalents for the Rust-specified types in our secret contract. args — The arguments to be passed into our secret contract’s method. Given our Rust secret contract’s method definition of add_millionaire , we desire to pass in H160 and U256 types and thus, for the purposes of our unit test, pass in [[accounts[1], ‘address’],[1000000, ‘uint256’],] . This adheres to the args format of [[arg_1, type_1], [arg_2, type_2], …, [arg_n, type_n]] where the types are the Solidity-equivalents for the Rust types defined in our secret contract.

— The arguments to be passed into our secret contract’s method. Given our Rust secret contract’s method definition of , we desire to pass in and types and thus, for the purposes of our unit test, pass in . This adheres to the format of where the types are the Solidity-equivalents for the Rust types defined in our secret contract. gasLimit — ENG gas units to be used for the computation task

— ENG gas units to be used for the computation task gasPx — ENG gas price to be used for the computation task in grains format (10⁸)

— ENG gas price to be used for the computation task in grains format (10⁸) sender — Ethereum address deploying the contract

— Ethereum address deploying the contract scAddr — The secret contract address for which this computation task belongs to. In our case, this is the millionaires’ problem contract we deployed earlier and saved on disk so that we can retrieve it in our test.

Reading through this section, many of you might be getting a strong sense of déjà vu — the compute task arguments look very similar to the deployment config object’s attributes in the section above. Apologies for the seeming redundancy, but we really wanted to demonstrate that a deployment and computation task are in many ways one and the same; deployment and compute tasks are handled very similarly in all steps of their lifecycles and components of the Enigma protocol, whether you consider the client library, the Enigma contract, or the Enigma network.

Getting Task Result [Lines 61–74]

We can obtain the status of our task at any point using the enigma-js library’s enigma.getTaskRecordStatus(task) function. This updates our task object with an updated ethStatus attribute, which is an enum representing Ethereum’s awareness of the Enigma network’s status of the computation task. A value of 2 indicates that Ethereum has processed/finalized the Enigma network’s successful task computation.

Decrypting Task Result [Lines 135–147]

The previous tasks simply update the secret contract state, with no encrypted output return value we are interested in processing. However, the final computation task, compute_richest() [Lines 107–118], returns the richest millionaire’s address. This returned value is encrypted however, and must be decrypted using the enigma-js library’s decryptTaskResult(task) function. Decryption is only possible if you are in fact the appropriate user for this (i.e. you have the correct private key for which the worker computing the task has encrypted the output). If we abi-decode this decrypted output with the appropriate Solidity types we expect, we can inspect and validate a computation task’s output.

Running the Tests

In order to run the unit test we have just written together, all we need to run on the command line is discovery test , which will deploy/migrate the contracts and run the unit tests:

The unit tests all pass with flying colors!

Conclusion

Congratulations! In this walk-through guide, we installed and set up the Enigma developer testnet, learned how to start and stop the network, modified the default scaffolding, wrote our first Enigma secret contract, compiled and deployed our secret contract, and unit-tested our contract utilizing the enigma-js client library. That is…a lot!