Building Smart Contracts With Partisia Blockchain
January 3rd, 2023

Today, I want to show how easy it is to build smart contracts on the upcoming Layer 1(and Layer 2. It’s a hybrid) blockchain called Partisia Blockchain. This is my first post on a series of posts I plan to do on Partisia Blockchain.

Partisia Blockchain(hereafter referred to as PBC) offers Zero-Knowledge computations(zk-Computation, specifically Secure Multiparty Computation, a.k.a MPC ) on-chain, off-chain and inter-chain. For those who don’t know what Zero-Knowledge is, I have also added a primer on that in this post.

Recently, there has been a great interest in Zero-Knowledge Proofs(ZKP) in the blockchain industry, with several players bringing in new ways to build ZKP-based Decentralized Applications(dApps). This has also led to the creation of ZK-based Virtual Machines(zkVMs).

Zero-Knowledge Proofs Primer

ZKP is a way to prove the validity of a statement(secret) without revealing any information about the statement(secret) itself.

Two parties involved in ZKP - Prover and Verifier
Two parties involved in ZKP - Prover and Verifier

In ZKPs, we often have two actors - The prover and the Verifier. The Prover is the one who holds the secret, and Verifier is the one who validates the proof and tells whether it is valid or not. The implications of this are enormous.

Consider an example where you are supposed to prove that you are not from the US as part of KYC to a service provider(maybe an ICO or a new Centralized Exchange). Instead of sending a copy of your government-provided ID, you could generate mathematical proof that the service provider can easily verify.

This process of not revealing zero details about the information being exchanged is why the protocol is known as Zero-Knowledge.

ZKP In Blockchain - Verifiable Computation

Verifiable Computation in the context of blockchains is the process of off-loading certain computations to off-chain systems that later returns the result along with proof of valid computation.

Using Verifiable Computations, layer 1 protocols like Ethereum can offload computations to off-chain systems like Layer 2 or sidechains. These off-chain systems submit the computation result and the proof to the on-chain state. For layer 1, like Ethereum, to trust these computations, valid proof is required, which can be used to update the state of Ethereum without re-executing the offloaded transactions.

The off-chain systems that execute the transactions on their off-chain nodes use ZKP to generate these Validity Proofs that prove the correctness of the off-chain execution.

Examples of such systems are ZK Rollup systems like StarkNet, zkSync, and Polygon Zero(earlier known as Mir Protocol).

Zero Knowledge Computation

Traditionally, to perform computation on encrypted data, we have to decrypt it. But, with the help of specific cryptographic techniques, we can perform computations on top of encrypted data without decrypting them. This technique is called ZK Computation.

Public-Key Cryptography has helped us transmit and store data securely, but it doesn’t support computation on encrypted data. This is extremely important in this era of a cloud-first mindset.

ZK Computation techniques like Fully Homomorphic Encryption(FHE) and Functional Encryption help deal with this.

Consider a common situation when an online business owner wants to clean spam emails from his email list(leads). Usually, such a user uploads his email list to a service provider that performs the filtering process on its server. What most people never think about is the privacy of the email list that just got uploaded for filtering. The emails are now accessible to the service provider, and the user must trust the service provider to deal with the customer data safely.

A user making use of Functional Encryption to have privacy-preserving email filtering
A user making use of Functional Encryption to have privacy-preserving email filtering

Using Functional Encryption, the service provider can perform filtering on encrypted email list data without the need for decrypting the entire data.

Now, consider a situation where there is a need to do ZK computation on a distributed setup involving multiple users/nodes. The type of ZK Computation that can help with this is Secure Multi-Party Computation(sMPC/MPC).

An example of sMPC is the sharing of the total amount to invest in an ICO(Initial Coin Offering) among a group without revealing the individual investment of the parties involved.

Assume that 3 friends - Vinny, Uros and Yoshi want to calculate how much they are willing to invest in an ICO together such that they don’t want to reveal individual investments they would make among themselves or to any trusted third party.

Let’s say that Vinny decides to invest $100,000 while Uros and Yoshi decide to go with $50,000 and $10,000, respectively. sMPC makes use of a cryptographic technique known as Additive Secret Sharing, which allows a user to split their secret into multiple tiny pieces of data that can be shared with others.

In the case of our example, let’s assume that using Additive Secret Sharing, the 3 friends decided to split each of their investments as follows randomly:

Shows the random secrets distributed among participants
Shows the random secrets distributed among participants

In the table above, Vinny has split his $100,000 secret into 3 random secrets that add up to $100,000. And he has kept one share(18191) and distributed the other 2 shares to Uros(31,339) and Yoshi(50,470). Note that Vinny’s shared secrets are denoted in red colour.

Similarly, Uros has split his secret into three - 19476(shared with Vinny), 11990(kept himself) and 18534(shared with Yoshi). Yoshi’s 10,000 was split into 5191(shared with Vinny), 2642(shared with Uros) and 2167(kept himself).

When the secret sharing is done, all three participants hold secret slices from each. The critical fact is that each secret slice alone doesn’t yield any helpful information, but the different slices from all three together are useful.

Shows privacy zones of each party and their partial result
Shows privacy zones of each party and their partial result

As shown above, each participant can compute a Partial Result by adding the secret slices they have in their Privacy Zone. Once the partial results are there, the participants can combine these partial results to find the total investment. In the example above, we combine the partial results of Vinny(42858), Uros(45971) and Yoshi(71171) to get $160,000.

If we think about this, we understand that every privacy zone has a partial result, but none of them knows what the other party would invest. Yet, they could compute the total amount they were willing to invest for the ICO.

ZK Computation In Partisia Blockchain

Partisia Blockchain is a perfect match made between blockchain and ZK Computation with a focus on Multi-Party Computation(MPC).

The ZK Computations are performed by a network of nodes in Partisia known as the ZK Nodes. At the core of the Partisia Blockchain solution, there are 3 categories of nodes:

  • Baker Nodes - Core network nodes that deal with the underlying P2P mechanism, Consensus, basic transaction handling and block production

  • ZK Nodes - Nodes that deal with the ZK Computation

  • Oracle Nodes - Nodes that deal with moving the data and blockchain-agnostic cross-chain transactions via Bring Your Own Coin(BYOC) mechanism

In PBC, the MPC is performed using Delegated ZK Computation. In this mechanism, the ZK computation is delegated to a set of trusted ZK Nodes.

Shows the trust model of delegated ZK computation
Shows the trust model of delegated ZK computation

In a Delegated ZK Computation, a.k.a Delegated Trust Model for ZK Computation, as depicted in the diagram above, a strong trust system can be achieved by incorporating trusted accredited ZK Nodes and having an incentive mechanism on top of it. This is how PBC handles the delegated ZK computation.

This is a high-level view of how PBC would deal with ZK Computation. But, PBC could accommodate a variety of trust models other than what I just described. For example, a trust model wherein a threshold could be defined such that X number of ZK Nodes from a pool of Y ZK Nodes could be malicious yet yield a secure computation result, where X and Y could be any arbitrary number.

One of the questions that might come up in your mind is how PBC could deal with CPU-intensive computations that usually require a pre-processing step. Well, PBC has some complex protocols that help with such pre-processing by using the Baker Nodes while retaining the security of the computation.

Show computation that makes use of pre-processed data via oblivious transfer
Show computation that makes use of pre-processed data via oblivious transfer

As shown in the diagram above, a set of Baker Nodes perform the pre-processing and sends it over to the ZK Nodes for ZK Computation via Oblivious Transfer Protocol. Oblivious Transfer(OT) is a cryptographic technique by which the sender sends data to a receiver but never knows what data was sent or received.

PBC allows flexible customization for the trust models. For instance, it allows us to choose the ZK Nodes based on region or jurisdiction. This could find application when we are concerned about keeping computations in specific Zk Nodes in specific jurisdictions like Asia, Europe, etc.

The PBC Stack

Much like how we have tech stacks for full-stack development like MERN(MongoDB+ ExpressJS + ReactJS + NodeJS), MEAN(MongoDB+ ExpressJS + Angular + NodeJS), etc., in blockchain, we have the stack of tech to help us build our solutions.

PBC's stack consists of seven different components, together named ZEUS.

PBC Stack - ZEUS
PBC Stack - ZEUS

Smart Contracts In PBC

I discussed the underlying concepts that would help you get started with PBC. But, the main intention of this post is to help you start building Decentralized Applications(dApps) on top of PBC leveraging its smart contracts.

Smart Contracts were initially created back in the early 1990s by Nick Szabo, who defined them as follows:

A set of promises, specified in digital form, including protocols within which the parties perform on these promises

Fast-forward to today, in the age of blockchains; they are the programs that are executed on-chain. Unlike most other popular blockchains, PBC allows you to have a privacy layer on top of its blockchain optionally. This is made possible via its smart contracts.

Types of smart contracts in PBC
Types of smart contracts in PBC

PBC has three different types of Smart Contracts:

  • System Smart Contracts - The core permanent contracts that maintain the PBC ecosystem. They deal with things like the maintenance of public and private smart contracts, help in block production and are involved in pre-processing of ZK computation.

  • Public Smart Contracts - They are the ones written in PBC smart contract language that is based on Rust. This is the normal type of contract(state and associated actions/methods) we see in blockchains like Ethereum.

  • Private Smart Contracts - They allow us to do ZK Computations using the privacy layer of PBC

As I described in the PBC Stack section, PBC’s smart contract tooling is Apollo. The unified approach In Apollo means that, as developers, we have a single, smart contract language(based on Rust) that can help us build public and private smart contracts.

In this post, I’ll explain how we can build public and private smart contracts on PBC. Before we start with the actual coding part, let’s prepare the prerequisites for development.

Prerequisites For Development

The prerequisites are:

The smart contract programming language for PBC is based on Rust. If you are unfamiliar with Rust, head to Rust Lang Website to learn the basics.

Rust is a modern, high-performance and reliable programming language that has been voted the most loved programming language for the past seven years.

Rust supports multiple platforms, a.k.a targets. The official Rust team maintains some of these targets, while the community maintains some. Web Assembly is a target that is maintained by the community, and support for it can be added via:

rustup target add wasm32-unknown-unknown

PBC contracts are compiled to Web Assembly code(wasm). So, the above command has to be run after you have Rust installed on your system.

The ZK/Private Contracts require the Java SDK, and this can be installed from OpenJDK website. If you are on Mac like me, then you can install it via Homebrew:

brew install openjdk@17

We build smart contract projects using a cargo subcommand called cargo-partisia-contract. Cargo Subcommands are a way to extend Cargo(Rust’s build system and package manager) such that we can install them via cargo install cargo-<subcommand name>. To install the cargo-partisia-contract subcommand, do the following:

cargo install cargo-partisia-contract

Partisia Blockchain Wallet is required to deploy contract and interact with it. You will also need some gas to get started. You have few options to get some free test gas:

Once you have some testnet gas, you could use the PBC Faucet to get more gas.

PBC Smart Contract - Hello World

Let's create a simple hello world smart contract with PBC and deploy it on testnet to test the waters.

Create a smart contract project using the cargo-partisia-contract subcommand.

cargo partisia-contract new hello-world

If it succeeded in creating the project, it should display the following message:

Success message when contract project creation is successful
Success message when contract project creation is successful

A boilerplate code is generated that shows an example smart contract. Let’s replace this default contract with the following that allows user to call a function greet that sets the state variable greeting to a string that greets the user :

//! hello-world

#[macro_use]
extern crate pbc_contract_codegen;

use pbc_contract_common::context::{ContractContext};


/// This is the state of the contract which is persisted on chain.
#[state]
struct ContractState {
    greeting: String,
}

impl ContractState {
    fn greet(&mut self, name: &str) {
        self.greeting = "Hello ".to_string() + name;
    }
}

#[init]
fn initialize(
    _ctx: ContractContext
) -> ContractState {

    let state = ContractState {
        greeting: "Hello World".to_string(),
    };

    state
}

#[action(shortname = 0x01)]
fn greet(
    _context: ContractContext,
    state: ContractState,
    name: String,
) -> ContractState {
    assert!(!name.is_empty(), "name must not be empty");

    let mut new_state = state;
    new_state.greet(&name);

    new_state
}

Notice that the contract has three core parts - Init, State and Actions on top of this state. The use of Rust’s attribute-like procedural macros indicates these.

  • #[init] - This annotation declares that the annotated function code will be run when the contract is deployed. This is similar to the constructor function in Ethereum

  • #[state] - This macro declares that the annotated struct is the top level contract state

  • #[action] - This optional macro declares that the annotated function can be called by client code or other contracts

In the contract above, you can see that the Action macro(#[action(shortname = 0x01)]) contains a shortname. This a unique identifier that defaults to an automatically generated one. But, when specific names are required, we can set this to a u32 type via the syntax #[action(shortname = <short u32 value>)]. This unsigned 32-bit integer value is encoded via Little Endian Base 128(LEB128) into a lowercase zero-padded hex value.

Apart from the macros we see in the example above, PBC comes with additional categories of macros as shown below - ABI Attribute specific and ZK Lifetime Attribute specific:

Shows all the currently available list of contract macros
Shows all the currently available list of contract macros

Hello World Contract Breakdown

Let’s unpack the hello world contract we saw earlier.

pbc_contract_codegen is a crate that comes with different macros(listed in the image above - ABI Attribute macros and ZK Lifetime Attribute macros) that allows us to annotate different parts of our contract code.

Although the example above imports all macros in the crate via #[macro_use], we could also do the following to explicitly import only the ones required:

use pbc_contract_codegen::{init, state, action};

ContractContext from pbc_contract_common contains the blockchain state information like:

  • contract_address - Address of the contract being called

  • sender - Sender of the transaction

  • block_time - Block time

  • block_production_time - Block production time in milliseconds UTC

  • current_transaction - Hash of current transaction

  • original_transaction - Hash of parent transaction, if available

Now, let’s have a look at the ContractState.

Shows the contract state snippet
Shows the contract state snippet

The state attribute-like macro annotation is a required one and should be defined only once in a contract. This macro internally derives a ReadWriteState trait that comes with serialization and de-serialization functionalities for the state. This means that only fields that have an implementation for this trait can be used in the state. This means floating point data types like f32 or f64 can’t be used as they don’t impl the ReadWriteState.

In the example here, the greet method basically allows user to set a name to the greeting message in the contract state. Note that greeting is String which doesn’t have a direct impl of ReadWriteState trait.

Shows internal representation of String in Rust
Shows internal representation of String in Rust

But, since String is internally represented as a vector of u8, which has an impl of ReadWriteState, it works fine.

Now, let’s look at the initialize function which is annotated by the #[init] macro.

Shows the initialize function snippet
Shows the initialize function snippet

This is the code that gets executed when the contract is deployed and is similar to the constructor function in Ethereum’s Solidity. Here, the function returns ContractState. If the contract had interactions with other contracts, then the return type would have been a tuple (ContractState, Vec<EventGroup>).

EventGroup is a struct that holds the list of events that represent the way to interact with other contracts along with an optional callback payload data. EventGroup is created using EventGroupBuilder.

Shows creation of EventGroup using EventGroupBuilder
Shows creation of EventGroup using EventGroupBuilder

Notice that EventGroup contains the events. Each event are the contract interactions(represented by the Interaction struct, under the hood). The structure of an Interaction is as follows:

  • dest - Address of contract to call

  • payload - Payload to send

  • cost - The amount of gas to allocate for handling the callback. This is defaulted to None, which automatically calculates the amount from the remaining gas

Let’s look at the action code now.

Shows the action snippet
Shows the action snippet

The action macro allows us to set a shortname for the function. This is for size optimization. When shortname isn’t given, PBC automatically defaults to a shortname based on the function name. This is done by taking the first four bytes of the SHA256 hash of the function name. In the example,above, the greet function returns the updated ContractState. Technically, this could be a tuple of (ContractState, Vec<EventGroup>), similar to what I mentioned in initialize part.

Compiling Contract

The contract can be compiled using the partisia-contract cargo subcommand.

Shows compiling contract in release mode
Shows compiling contract in release mode

Notice that in the above command, we are building the contract in release mode. This is the optimized mode for deployment compared to the other default mode - debug.

Once the command is run, we should get both the .wasm contract as well as the .abi file. These should be available in the following path:

Shows the path to compiled artifacts
Shows the path to compiled artifacts

Deploying Contract

We can deploy contracts to a PBC network(testnet or mainnet; we focus on testnet in this post)using the PBC Explorer’s Deploy Wasm Contract Page.

Shows the contract deployment page
Shows the contract deployment page

In this page, you can click on those buttons to choose the .wasm and .abi files from the compilation stage.

Shows the contract deployment submit option when artifacts are chosen
Shows the contract deployment submit option when artifacts are chosen

Copy the Deploy gas cost value from the text field as we will use that gas value to perform the deployment.

Now, click on the SUBMIT button to bring up the PBC Wallet confirmation dialog.

Shows confirmation dialog in PBC wallet
Shows confirmation dialog in PBC wallet

Then click on Edit Fee button to update the gas fee to the value copied in the previous step and click on save to confirm the updated value:

Shows the editing of fee to updated value
Shows the editing of fee to updated value

Once you click on the TRANSACT button, the transaction should be submitted to the blockchain, after password confirmation:

Shows the contract deployed sucessfully
Shows the contract deployed sucessfully

Contract Interaction

Once the contract is deployed, you get the contract address(027d207c3f0a810cc39aa3bdd7a03af3ca7a3630e3 above).

Notice that the last 20 bytes of the transaction hash is same as the last 20 bytes of the contract address.

Addresses in PBC are of four types:

  • User account - Prefix of 0x00

  • System Contract - Prefix of 0x01

  • Public Contract - Prefix of 0x02

  • ZK Contract - Prefix of 0x03

Each address is a combination of the Address Type and a unique 20 bytes identifier that is derived from the hash of the public key of an account.

In the hello world contract we deployed, the contract address is 027d207c3f0a810cc39aa3bdd7a03af3ca7a3630e3. This address can be decomposed into:

Shows the Address Type and Address Identifier split
Shows the Address Type and Address Identifier split

To interact with the deployed contract, click on the contract address in the PBC explorer or search for the contract address.

Shows the contract interaction page
Shows the contract interaction page

Notice that the initial state is set to what we set in the initialize function - Hello World.

In the Contract Interaction section, you can see the actions available in the contract - GREET. Click on that button to initiate an action.

Shows the action form
Shows the action form

Provide a name as shown and click the SUBMIT button to trigger the action.

Once the action is successful, you should see an execution success message along with the RPC call data.

Shows the execution status and the RPC call data
Shows the execution status and the RPC call data

The state should also be updated as shown below:

Shows the updated state
Shows the updated state

Conclusion

We have seen how to deploy public smart contracts on PBC. In the upcoming posts, we will see how we can leverage PBC to build and deploy Private/ZK Smart contracts.

The code for this tutorial is available at - hello-world-pbc

Subscribe to ronnakamoto.eth
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.
More from ronnakamoto.eth

Skeleton

Skeleton

Skeleton