Zero Network: Privacy Preserving Transactions Blockchain based on Substrate

Abstract

We describe the blockchain which supports privacy preserving transactions for both transfers and smart constract executions depending only on cryptgraphic hardness assumption. In this document, we describe how we realize privacy with only cryptgraphy instead TEE, L2 solutions and trusted parties, optimistic assumptions.

As a part of our protocol, we combine some cryptgraphic toools. We would like to intruduce these tools and compare these with other alternative choices, and we finally describe how we implement the privacy preserving transactions with them.

Contents

Firstly we describe the problems we face to when we realize the privacy preserving blockchain, the difinition of privacy and how we address the problems in Overview. Finally, we describe the concrete constraints that the proof of transactions should satisfy in Transaction Constraints. Addtionally, we add related research in Related Tools.

Reference

Crypto Note v 2.0
Additive homomorphic encryption which supports one-time multiplication
Zether: Towards Privacy in a Smart Contract World
Zerochain Book
A specification for a ZK-EVM
ZKPs for privacy-Preserving Smart Contracts and Transactions
plonkup: A simplified polynomial protocol for lookup tables
Pinocchio: Nearly Practical Verifiable Computation

Introduction

Basically, all public blockchain state is public for everyone and it can be looked by unknown someone without any permission. To keep the privacy, the projects for example Zcash , Monero and so on realized the privacy preserving transfer. Now people can transfer crypto currency with private. However, the real world applications require more complicated functionalities and the blockchain should support various of use case. It was hard to realize the general purpose privacy preserving transactions but recent scaling and privacy technologies evolution allows us to make it practical.

To achieve general purpose privacy preserving transactions, there are mainly five problems to be addressed. Hide transfer amount, Gas limit, Zero knowledge scheme and Contract constraint, Attack protection. Firstly, we would like to define what is the privacy and describe the solution. Finally, we would like to describe the solution for the attack.

Contents

The introduction contents are following.

  1. What is Privacy
  2. Hide Transfer Amount
  3. Gas Limit
  4. Zero Knowledge Scheme
  5. Transaction Constraints

What is Privacy: We define what privacy is before we discuss the protocol.
Hide Transfer Amount: We describe how to hide the transaction values.
Gas Limit: We describe how to save the workload avoid to exceed the gas limit.
Zero Knowledge Scheme: We compare the zero knowledge scheme and describe which is suitable for privacy preserving transactions.
Transaction Constraints: We describe how user generates the transaction proof.

These sections are work in progress and we are going to add the experiment result.

What is Privacy

Before we describe our protocol, we would like to define what the privacy exactly means.

Confidential vs Anonymous

There are two types of privacy level confidential and anonymous. The difference between them is that how much information we hide. The confidential means it hides the input and output. The anonymous means it hide the users related the transaction.

Transfer Example

If the protocol supports confidential, the users would be able to hide the input and output. When users send the transaction, the input and output are going to be balance, transfer amount and after balance. The function needs to know that the transfer satisfies the conditions for example, the amount is not negative, the balance is more than transfer amount and so on. The confidential transactions can verify these conditions without revealing input and ouput values. We use the homomorphic encryption to realize this feature. You can see it on Hide Transfer Amount section.

If the protocol supports anonymous, the users would be able to hide the users information related to transactions in addition to confidential. When users transfer the assets, the users information related to transactions are going to be sender and recipient. There are some ways to hide users information and we describe some of them in related tools. The typical tool to hide the sender is the Ring Signature and the recipient is the Stealth Address.

Summarize

To summarize the story, the confidential hides the transaction contents and the anonymous hides the transaction senders and recipients. We describe the contents and privacy level in table.

ItemConfidentialAnonymous
Balance
Transfer Amount
Sender-
Recipient-

Hide Transfer Amount

In this section, we describe how we hide the transfer amount.

Homomorphic Encryption vs Commitment

We have two options to hide the transfer amount homomorphic encryption and commitment. The homomorphic encryption allows us to calculate the encrypted number. To combine the homomorphic encryption and zero knowledge proof, we can calculate the encrypted number without decrypting and check whether the calculation was done correctly by zero knowledge proof. And the other option is commitment. The commitment uses the one-way function for example hash function. It hides the original value. The user provides the part of original value and check whether original value satisfies the condition. We describe the famous commitment in Pedersen Commitment section.

If we use the commitment scheme, we need to generate the randomness for each transaction and prove the validity with it. It's hard for users to deal every randomness and also we support the contract execution so it's not practical to generate randomness for each value. If we use the homomorphic encryption, we can realize it simpler way. We are going to describe how we hide the transfer amount and prove whether that value satisfies the condition in Transaction Constraints section.

Scheme

To summarize the two scheme difference, it would be following.

Homomorphic Encryption

The homomorphic encryption can calculate the encrypted number. Let Enc() encrypt function and we can't know the input from output. We can get calculation result without revealing actual value.

$$ Enc(10) + Enc(5) = Enc(15) $$

The encrypted value doesn't expose any information so we need to attach the proof which proves the value satisfies the condition. If users try to transfer the asset, user need to prove that the user balance is more than transfer amount, transfer amout is not negative and so on.

Commitment

The commitment can hide the number and prove that the value satisfies the condition. Let Hash() hash function. To make it hard to know the input from output, we generate the randomness. The hash function takes amount and randomness as argument. If user wants to send 3 asset and it's balance is 10, user would generate the randomness and prove that following equation holds.

$$ Hash(3, 100) + Hash(7, 200) - Hash(10, 300) = 0 $$

The transfer amount user wants send and the amount after transfer is equal to current balance when it's added. The hashed value is called commitment. We describe this more detail with example in Pedersen Commitment section.

Gas Limit

In this section, we describe the potential problem that privacy preserving transactions project have.

Transaction Scheme

When we realize the privacy preserving transactions with homomorphic encryption, the transaction sender transaction scheme will first calculate the encrypted value and second generate the proof which proves the validity of these relationship. First schmeme needs to perform homomorphic arithmetic, and second one needs elliptic curve arithmetic and polynomial evaluations. And the verifier need to verify the proof by performing the pairing and homomorphic arithmetic. Both side needs to perform the heavy workload computation. The more computation we perform, the more gas cost we need to pay. If the verify function exceeds the gas limit, we would be unable to realize the protocol. To make it practical, we optimize the workload.

Account Base vs UTXO

When we generate the zero knowledge proof, the more complex data structure we need to prove the condition, the more computation we need. There are mainly two types of data blockchain structure. The account base is just key-value mapping data structure. It's easy to prove the condition. The UTXO uses the input and output transactions when it transfers the asset. It's complicated comparing with account base. However, the UTXO can prevent the double spending with data structure and it's hard to track the transaction history.

EVM vs Wasm

When we verify the zero knowledge proof, the verify costs depend on efficiency of VM environment. If we perform the verify calculation efficiency, we would save the gas cost. When the Ethereum was launched, it's not designed for perform the zero knowledge functions so we have limitation of optimization once the blockchain is deployed. The Wasm is more efficient and we can customize and create new bytecodes. We have a lot of ways to optimize.

Zero Knowledge Friendly

The data structure is the trade off between security and workload. We use the account base data structure because the gas limit is the main bottleneck. We describe the solutions for the security problems when we use the account base data structure in Attack Protection section. We are going to support Wasm because it's high performance and we can optimize the workload on blockchain. We don't have a plan to support EVM now so our blockchain doesn't have compatible with Ethereum contracts.

Zero Knowledge Scheme

In this section, we describe the zero knowledge scheme features.

SNARKs vs STARKs vs Bulletproofs

We compare the three types of zero knowledge scheme zk-SNARKs, zk-STARKs and Bulletproofs. The zk-SNARKs is the most efficient. We can verify the proof with const or almost const time and generate proof process is also efficient. The proof size is also small. However, it's necessary to setup the parameters. We can save a lot of workload because of this but it would be the critical security issue. The zk-STARKs doesn't need to setup parameters and it has quantum tolerance. However, its proof size is far bigger than zk-SNARKs and the workload of verification process also far bigger than zk-SNARKs. The Bulletproofs doesn't need to setup parameters and its feature is in the middle between zk-SNARKs and zk-STARKs but it doesn't have quantum tolerance.

Summerize

To summerize the above comparison, it would be as following table.

SchemeTrusted SetupProver CostVerifier CostProof SizeQuantum Tolerance
zk-SNARKsNecessaryLowLowSmallNo
zk-STARKsUnnecessaryModerateHighLargeYes
BulletproofsUnnecessaryLowModerateModerateNo

Privacy Preserving Transactions Friendly

The Bulletproofs is mainly used for other privacy preserving transactions project for example Aztec, Zether and so on. That's because these projects are Ethereum smart contract base projects so if they use the zk-SNARKs, it's necessary to setup the parameters for each deploy smart contracts. It's really hard to collect enough parties to setup the parameters for each deploy. We use the zk-SNARKs because of its efficiency and the plonk allows us to setup parameters without depending on circuit. In other words, once we setup the parameters, we can reuse them when we prove the transactions. 

Transaction Constraints

In this section, we describe how to generate the proof for each transactions.

Transfer

When we transfer the assets, as we described at Hide Transfer Amount section, at first we encrypt the value and the second we generate the proof which proves the validity of value. In terms of the transfer transactions, the constraints the transfer transactions need to satisfy are always same. We can modularize these constraints and generating the proof. The more details for the constraints, you can see it on Transaction Constraints section.

Smart Contract Execution

When we execute the smart contract, the constraints for each transaction is not the same so we can't use same with as in confidential transfer so we generate constraints for each opcode because the opcode operation is always same. We generate proof for each opcode which proves that the opcode was performed correctly and put these together to one proof. It's the same approach with zkevm. The Substrate works on wasm so we are going to implement zkwasm. We also describe the details constraints in Transaction Constraints section.

Summerize

In terms of transfer, the contraints are always same so we can modularize these constraints. In terms of smart contract execution, the constraints are different for each smart contract so we customize the compiler and output the constraints when it compiles the smart contracts. The developer provides the constraints for users and they can know the constraints when they generate the proof.

Transaction Constraints

In this section, we describe the concrete constraints for the transactions.

Abstract

The transaction constraints consist of smaller pieces of constraints. There are three types of constraints we have as following.

Constraint TypeChecking ConditionStatic or Dynamic
Common TransactionBasic transactions object for example gas, signature and so onStatic
Confidential TransferConfidential transfer conditionStatic
Confidential Smart ContractConfidential smart contract conditionDynamic

The Common Transaction checks the basic transactions constraints and used for every transaction. When the users transfer assets, they need to generate the proof satisfying both Common Transaction and Confidential Transfer constraints. This consists of static constraints so the condition that the proof needs to satisfy are always same so we can modularize easily. However, when the users execute the smart contract, they need to generate the proof satisfying both Common Transaction and Confidential Smart Contract constraints. This consists of static and dynamic constraints so the condition that the proof needs to satisfy are always different.

We would like to describe the constraints more detail and how we generate the proof for dynamic constraints.

Process

Every transaction needs to be attached the proof which satisfies the constraints. The proof is generated by proving key according to plonk protocol. The blockchain needs to setup the parameters and the user needs to generate key pair as following.

ItemDescription
xuser private key
yuser public key
srssetup parameters
srandomness used for setup
setup()setup function
Ffield
Gelliptic curve group
gelliptic curve generator
dpolynomial degree
pkproving key
vkverification key

Setup

Setup the paramaters used for generate and verify proof.

$$ g ∈ G $$ $$ setup(d, s) = [g, [s] g, [s^2] g, ... , [s^{d-1}] g] = (pk, vk) $$

Key Pair

Generate the key pair for sign the transaction.

$$ x ∈ F $$ $$ y = g^x $$

Common Transaction Constraints

Every transaction has common object items and this Common Transaction Constraints checks these condition. The transaction object items are following.

ItemDescription
sourcetransactor
targetdestination address
inputtransaction data
valuemessage value
gasLimitgas limit of transaction
gasPricegas price of transaction
nonceuser account nonce

The Common Transaction Constraints checks that the value, gasLimit and gasPrice are valid. We use following function to generate proof and signature.

FunctionDescription
enc()ElGamal encryption
sign()sign transaction with private key
prove()proof generation

Encrypt Variables

First of all, the number should be encrypted by ElGamal.

$$ enc(x, value, gasLimit, gasPrice, nonce) = value_{enc}, gasLimit_{enc}, gasPrice_{enc}, nonce_{enc} $$

Generate Proof and Signature

Generate the proof with proving key and prove that this common params satisfy the statement.

$$ π = Prove(pk, statement_{common_constraint}[value_{enc}, gasLimit_{enc}, gasPrice_{enc}, nonce_{enc}]) $$

Generate the signature with private key and prevent front-running attack.

$$ σ = Sign(x, value_{enc}, gasLimit_{enc}, gasPrice_{enc}, nonce_{enc}, π) $$

Confidential Smart Contract

This Confidential Transfer Constraints checks that the users smart constract execution is valid. The condition that users proof needs to satisfy is different for each contracts so it's provided by Dapp owner as the same with that the Ethererum Dapp owner provides the ABI of smart contract. When the developers finish implementing the smart contract, the smart contract is compiled and output the Wasm binary and constraints metadata. The constraints metadata is the polynomials expressing the smart contract constraints. The users need the metadata, their secret key and srs when they generate the proof.

Confidential Transfer Constraints

This Confidential Transfer Constraints checks whether users transfer transaction was done correctly.

Transaction Requirement

Specifically, constraints check following conditions.

  1. The transfer amount is encrypted by exact sender and recipient public key
  2. The transfer amount and sender remaining balance are in valid range (not negative)
  3. The transfer amount and sender remaining balance are calculated correctly

On the premise that every balances are encrypted by homomorphic encryption with different key and we need to perform addition and subtraction without revealing actual value. If Alice transfer Bob crypto currency, sender and recipient need to encrypt same transfer amount with their key in order to add and subtract their account without decrypt. First constraints are that they encrypt same transfer amount with their keys. And the next, we need to clarify that Alice has enough balance and her transfer amount is valid. Second constraints are that Alice balance is more than transfer amount and the transfer amount is not negative. The user needs to generate the proof which proves that the transaction satisfies above condition.

Transfer Scheme

We describe the confidential transfer scheme here. We assume the case that Alice wants to transfer t amount crypto currency to Bob.

We define the symbol for each parameters as following.

SymbolDescription
skAlice private key
pkAlice public key
pk'Bob public key
tTransfer amount
bAlice remaining balance
enc_bal_leftBalance encrypted by Alice
enc_bal_rightBalance encrypted by Alice
enc_leftTransfer amount encrypted by Alice
enc_rightTransfer amount encrypted by Alice
enc_t'Transfer amount encrypted by Bob
rRandomness
gGenerator of elliptic curve point

We perform this transfer scheme on jubjub curve and the constraints are following.

$$ enc_{left} = g^tpk^r\ \land enc_{right} = g^r\ \land enc_t' = (g^{b'}pk'^r)\ \land \ enc_{left}/ enc_{balleft} = g^{b'} (enc_{right}/enc_{balright})^{sk}\ \land t \in [0,Max]\ \land b \in [0,Max] $$

Users generate proof and attach it with transaction.

Transaction Speed

In my local PC, it takes about 5 seconds to generate proof and 78 milli-seconds to be verified.

Confidential Smart Contract

Primitive

The Zero Network consist of cryptographic primitives which are compatible with no_std and parity-scale-codec. Followings are short summary of primitives.

The zero-crypto crate is in charge of basic cryptographic primitive. This includes Field, Curve, ExtensionField and so on, and allows us to easily setup cryptocraphy implementation without implementing actual algorithms and test automatically.

The zero-jubjub crate is in charge of Jubjub curve arithmetic. This supports Jubjub rational point additive and scalar by finite field.

The zero-bls12-381 crate is in charge of Bls12 381 arithmetic. This supports Bls12 381 $G_1$ and $G_2$ rational point additive and multiplicative, and scalar by finite field, and also supports $F_q^2$, $F_q^6$ and $F_q^{12}$ extension field arithmetic.

The zero-elgamal crate is in charge of additive homomorphic ElGamal arithmetic. This supports ElGamal encryption and decription.

The zero-pairing crate is in charge of Tate Pairing arithmetic. This supports miller loop algorithm and final exponentiation.

You can import as adding dependencies to our crate.

[dependencies]
zero-crypto = { version = "0.2.1" }
zero-jubjub = { version = "0.2.0" }
zero-bls12-381 = { version = "0.2.0" }
zero-elgamal = { version = "0.2.0" }
zero-pairing = { version = "0.2.0" }

Crypto crates.io badge

This crate provides basic cryptographic implementation as in Field, Curve and Pairing, Fft, Kzg, and also supports fully no_std and parity-scale-codec.

Usage

Field

The following Fr support four basic operation.

use zero_crypto::common::*;
use zero_crypto::dress::field::*;
use zero_crypto::arithmetic::bits_256::*;
use serde::{Deserialize, Serialize};

#[derive(Clone, Copy, Decode, Encode, Serialize, Deserialize)]
pub struct Fr(pub [u64; 4]);

const MODULUS: [u64; 4] = [
    0xffffffff00000001,
    0x53bda402fffe5bfe,
    0x3339d80809a1d805,
    0x73eda753299d7d48,
];

const GENERATOR: [u64; 4] = [
    0x0000000efffffff1,
    0x17e363d300189c0f,
    0xff9c57876f8457b0,
    0x351332208fc5a8c4,
];

/// R = 2^256 mod r
const R: [u64; 4] = [
    0x00000001fffffffe,
    0x5884b7fa00034802,
    0x998c4fefecbc4ff5,
    0x1824b159acc5056f,
];

/// R^2 = 2^512 mod r
const R2: [u64; 4] = [
    0xc999e990f3f29c6d,
    0x2b6cedcb87925c23,
    0x05d314967254398f,
    0x0748d9d99f59ff11,
];

/// R^3 = 2^768 mod r
const R3: [u64; 4] = [
    0xc62c1807439b73af,
    0x1b3e0d188cf06990,
    0x73d13c71c7b5f418,
    0x6e2a5bb9c8db33e9,
];

pub const INV: u64 = 0xfffffffeffffffff;

const S: usize = 32;

pub const ROOT_OF_UNITY: Fr = Fr([
    0xb9b58d8c5f0e466a,
    0x5b1b4c801819d7ec,
    0x0af53ae352a31e64,
    0x5bf3adda19e9b27b,
]);

impl Fr {
    pub const fn to_mont_form(val: [u64; 4]) -> Self {
        Self(to_mont_form(val, R2, MODULUS, INV))
    }

    pub(crate) const fn montgomery_reduce(self) -> [u64; 4] {
        mont(
            [self.0[0], self.0[1], self.0[2], self.0[3], 0, 0, 0, 0],
            MODULUS,
            INV,
        )
    }
}

fft_field_operation!(Fr, MODULUS, GENERATOR, INV, ROOT_OF_UNITY, R, R2, R3, S);

#[cfg(test)]
mod tests {
    use super::*;
    use paste::paste;
    use rand_core::OsRng;

    field_test!(bls12_381_scalar, Fr, 1000);
}

Curve

The following G1Affine and G1Projective supports point arithmetic.

use crate::fq::Fq;
use crate::fr::Fr;
use zero_crypto::arithmetic::bits_384::*;
use zero_crypto::common::*;
use zero_crypto::dress::curve::*;

/// The projective form of coordinate
#[derive(Debug, Clone, Copy, Decode, Encode)]
pub struct G1Projective {
    pub(crate) x: Fq,
    pub(crate) y: Fq,
    pub(crate) z: Fq,
}

/// The projective form of coordinate
#[derive(Debug, Clone, Copy, Decode, Encode)]
pub struct G1Affine {
    pub(crate) x: Fq,
    pub(crate) y: Fq,
    is_infinity: bool,
}

curve_operation!(
    Fr,
    Fq,
    G1_PARAM_A,
    G1_PARAM_B,
    G1Affine,
    G1Projective,
    G1_GENERATOR_X,
    G1_GENERATOR_Y
);

#[cfg(test)]
mod tests {
    #[allow(unused_imports)]
    use super::*;

    curve_test!(bls12_381, Fr, G1Affine, G1Projective, 100);
}

Jubjub Curve crates.io badge

This crate provides jubjub curve arithmetic and also supports fully no_std and parity-scale-codec.

This crate uses https://github.com/zkcrypto/jubjub algorithm designed by @str4d and @ebfull. We replace field and curve implementation with zero-crypto to make this compatible with Substrate.

Specification

The Jubjub curve is one of twisted edwards curve.

  • Twisted Edwards Curve $$ ax^2 + y^2 = 1 + dx^2y^2 $$

  • Addition Law $$ (x_3 = \frac{x_1y_1 + y_1x_1}{1 + dx_1x_1y_1y_1}, y_3 = \frac{y_1y_1 + ax_1x_1}{1 - dx_1x_1y_1y_1}) $$

Bls12 381 Curve crates.io badge

Pairing friendly bls12-381 curve.

This crate partly uses https://github.com/dusk-network/bls12_381 and https://github.com/dusk-network/bls12_381 implementation designed by Dusk-Network team and, @str4d and @ebfull. We replace field and curve implementation with zero-crypto to make this compatible with Substrate.

Overview

This crate includes field and extension fields, curve implementation. There are two curve $G1$ and $G2$ described as following.

$G1: y^2 = x^3 + 4$
$G2: y^2 = x^3 + 4(u + 1)$

These two group supports bilinearity by pairing. Let $G$ and $H$ be generator of $G1$, and $G2$, and $e$ be pairing function. The relationship is described as following.

$e(aG, bH) = e(G, H)^{ab}$

ElGamal Encryption crates.io badge

This crate provides additive homomorphic ElGamal encryption over jubjub curve and also supports fully no_std and parity-scale-codec.

Scheme

Alice has balance $a$ and public key $b$.
She generates the randomness $r$ and computes encrypted balance $(g^r, g^a * b^r)$.
When Bob transfers $c$ to Alice, he generates the randomness $r'$ and computes encrypted transfer amount $(g^{r'}, g^c * b^{r'})$.
The sum of encrypted balance and transfer amount is folloing.

$$ (g^{r + r'}, g^{a + c} * b^{r + r'}) $$

Tate Pairing crates.io badge

This crate uses https://github.com/zkcrypto/bls12_381 algorithm designed by @str4d and @ebfull. We replace field and curve implementation with zero-crypto to make this compatible with Substrate.

Pallet

The Zero Network supports privacy-preserving transactions. These functionalities are powered by pallets. Followings are short summary of pallets.

The pallet-plonk pallet is a wrapper of plonk library and in charge of proving and verifying the validity of computation.

The pallet-encrypted-balance pallet provides balance encryption by default. This replaces balance storage value with encrypted value.

The confidential_transfer pallet provides transfer function with hiding transfer amount. This pallet is coupling pallet-plonk and pallet-encrypted-balance, and changes the balance with encryped and checks the validity of computation.

You can import as adding dependencies to our crate.

[dependencies]
pallet-plonk = { version = "0.2.3" }

Plonk crates.io badge

Abstract

We implemented the plonk library as a pallet in order for developers to customize circuits and use the plonk protocol on Substrate runtime. This project allowed us to use following functionalities.

  • Custom circuit
  • Trusted setup
  • Generate proof
  • Verify proof

Acknowledgements

Encrypted Balances Module

The Balances module provides functionality for handling accounts and balances.

Overview

The Balances module provides functions for:

  • Getting and setting free balances.
  • Retrieving total, reserved and unreserved balances.
  • Repatriating a reserved balance to a beneficiary account that exists.
  • Transferring a balance between accounts (when not reserved).
  • Slashing an account balance.
  • Account creation and removal.
  • Managing total issuance.
  • Setting and managing locks.

Terminology

  • Existential Deposit: The minimum balance required to create or keep an account open. This prevents "dust accounts" from filling storage. When the free plus the reserved balance (i.e. the total balance) fall below this, then the account is said to be dead; and it loses its functionality as well as any prior history and all information on it is removed from the chain's state. No account should ever have a total balance that is strictly between 0 and the existential deposit (exclusive). If this ever happens, it indicates either a bug in this module or an erroneous raw mutation of storage.

  • Total Issuance: The total number of units in existence in a system.

  • Reaping an account: The act of removing an account by resetting its nonce. Happens after its total balance has become zero (or, strictly speaking, less than the Existential Deposit).

  • Free Balance: The portion of a balance that is not reserved. The free balance is the only balance that matters for most operations.

  • Reserved Balance: Reserved balance still belongs to the account holder, but is suspended. Reserved balance can still be slashed, but only after all the free balance has been slashed.

  • Imbalance: A condition when some funds were credited or debited without equal and opposite accounting (i.e. a difference between total issuance and account balances). Functions that result in an imbalance will return an object of the Imbalance trait that can be managed within your runtime logic. (If an imbalance is simply dropped, it should automatically maintain any book-keeping such as total issuance.)

  • Lock: A freeze on a specified amount of an account's free balance until a specified block number. Multiple locks always operate over the same funds, so they "overlay" rather than "stack".

Implementations

The Balances module provides implementations for the following traits. If these traits provide the functionality that you need, then you can avoid coupling with the Balances module.

  • Currency: Functions for dealing with a fungible assets system.
  • ReservableCurrency: Functions for dealing with assets that can be reserved from an account.
  • LockableCurrency: Functions for dealing with accounts that allow liquidity restrictions.
  • Imbalance: Functions for handling imbalances between total issuance in the system and account balances. Must be used when a function creates new funds (e.g. a reward) or destroys some funds (e.g. a system fee).
  • IsDeadAccount: Determiner to say whether a given account is unused.

Interface

Dispatchable Functions

  • transfer - Transfer some liquid free balance to another account.
  • set_balance - Set the balances of a given account. The origin of this call must be root.

Usage

The following examples show how to use the Balances module in your custom module.

Examples from the FRAME

The Contract module uses the Currency trait to handle gas payment, and its types inherit from Currency:

#![allow(unused)]
fn main() {
use frame_support::traits::Currency;

pub type BalanceOf<T> = <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
pub type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::NegativeImbalance;

}

The Staking module uses the LockableCurrency trait to lock a stash account's funds:

#![allow(unused)]
fn main() {
use frame_support::traits::{WithdrawReasons, LockableCurrency};
use sp_runtime::traits::Bounded;
pub trait Config: frame_system::Config {
	type Currency: LockableCurrency<Self::AccountId, Moment=Self::BlockNumber>;
}

fn update_ledger<T: Config>(
	controller: &T::AccountId,
	ledger: &StakingLedger<T>
) {
	T::Currency::set_lock(
		STAKING_ID,
		&ledger.stash,
		ledger.total,
		WithdrawReasons::all()
	);
	// <Ledger<T>>::insert(controller, ledger); // Commented out as we don't have access to Staking's storage here.
}
}

Genesis config

The Balances module depends on the GenesisConfig.

Assumptions

  • Total issued balanced of all accounts should be less than Config::Balance::max_value().

License: Apache-2.0

Confidential Transfer Pallet

Related Tools

Abstract

In this section, we would like to explain about the cryptgraphic scheme used for other privacy project.

Stealth Address

The Stealth Address hides the recipient address by creating one time address. Shortly, creating one time address from public key and recovering that private key with using Diffie-Hellman key exchange, users can hide who exactly receives the assets. The Monero uses this technology.

Pedersen Commitment

The Pedersen Commitment hides the transfer amount by commitment scheme. Shortly, using zero testing and generating blind factors, users can prove the amount validity without revealing actual amount. The Monero uses this technology.

Non Interactive Zero Knowledge Proof

The Non Interactive Zero Knowledge Proof proves the computation validity without revealing information about the value used with computation. In this section, we describe the zk-SNARKs. The Zcash uses this technology. Shortly, the QAP converts computation into polynomials, the Polynomial Commitment proves the validity of polynomials and the Homomorphic Encryption evaluates the polynomials without revealing raw coefficients.

Stealth Address

Abstract

The Stealth Address is the technology which allows us to hide the recipient address.

Details

The blockchain for example Ethereum, we generate the private key and the public key based on private key. The hash of the public key is going to be a address which specifies the recipient. This address corresponds one private key and public key pair so we can easily identify who is the recipient of the transaction. In our blockchain, we generate recipient address for each transactions and make it hard to identify the recipient.

  1. Generates recipient public key pairs
  2. Generates recipient Stealth Address
  3. Prove the ownership of Stealth Address

Above sequence used for confidential transfer to keep the recipient address anonymous.

Generates Recipient Public Key Pairs

Every transaction has recipient and we hide the recipient with stealth address. We assume Alice send transaction to Bod.

First of all, Bod generates the two key pairs (a, A) and (b, B) such that aG = A and bG = B. a and b are the private keys and, A and B are the public keys.

VariableExplanationDerivation
aBob private keya ∈ Fp
bBob private keyb ∈ Fp
ABob public key for aa * G
BBob public key for bb * G

Generates Recipient Stealth Address

Secondly, Alice generates the Bob recipient address as referred to the Stealth Address. Alice selects the random number r and calculate the Stealth Address with Bob public keys such that following.

$$ P = H(r*A) * G + B $$

No one can link P address with A and B because it's concealed by elliptic curve arithmetic. Alice publish the P and R calculated as R = rG.

VariableExplanationDerivation
rrandomness generated by Alicer ∈ Fp
Hone-way hash function which takes curve point and maps field elemente: xG -> Fp
Rpublic value generated by Alicer * G
Pstealth address of BobH(r*A) * G + B

Prove the ownership of Stealth Address

Lastly, Bob needs to prove the ownership of Stealth Address to use assets associated with it. Bob knows his private keys a and b. He can calculate the private key of P with x = H(a * R) + b. This is the Diffie–Hellman key exchange algorithm. Only Bob can know the x. This is how concealing the recipient address. Next section, we explain how we conceal the amount of transactions.

Pedersen Commitment

Abstract

The Pedersen Commitment is the technology which allows us to check the transfer amount is valid without revealing actual amount.

Details

This technology uses additivity of elliptic curve. Hiding the transfer amount as scalar of elliptic curve point and mixing random value refer as to blinding factor. Let's take a look the squence.

  1. Setup the parameters
  2. Hide the transfer amount
  3. Verify the transfer amount

Above sequence used for confidential transfer to keep the transfer amount secret.

Setup The Parameters

First of all, we'd like to setup the parameters we are going to use with the Pedersen Commitment. Selecting generator G over prime order elliptic curve group and randomness a less than order prime. Calculating H = aG and making H and G public. We can't predict the a value from H and G because of discrete logarithm.

VariableExplanationDerivation
pprime number-
arandom numbera ∈ Fp
C(x)elliptic curve function-
Ggenerator of elliptic curve pointG ∈ C(Fp)
Hgenerator made by random aH = aG

Hide The Transfer Amount

Let's assume that Alice has 10 balance and send Bob to 3. We need to check {Alice balance} - {transfer amount} = {Alice after balance} without revealing any information about Alice balance. Alice knows H, G and her balance so she computes following value.

  • Alice balance commitment $$ C(10) = 10H + x_1G $$
  • Transfer amount commitment $$ C(3) = 3H + x_2G $$
  • Alice after balance commitment $$ C(10 - 3) = 7H + x_3G $$

In above equation, x_1 ~ x_3 are called blinding factor and each blinding factor need to be set to hold {Alice balance} - {transfer amount} = {Alice after balance} equation. Let's say x_1 = 330, x_2 = 30 and x_3 = 300.

Verify The Transfer Amount

In previous section, Alice computes the following commitment.

$$ C(10, 330) = 10H + 330G $$ $$ C(3, 30) = 3H + 30G $$ $$ C(10 - 3, 300) = 7H + 300G $$

Let's check if Alice transfer amount is valid. The elliptic curve arithmetic supports additive so we can check {Alice balance} - {transfer amount} = {Alice after balance} as following.

$$ C(10, 330) - C(3, 30) - C(10 - 3, 300) = C(10 - 3 - 7, 330 - 30 - 300) = 0H + 0G = 0 $$

If above equation holds up, we can know the transfer amount is valid. Through this process, balance and transfer amount are encrypted by elliptic curve so no one can predict actual value from public information. This is how we conceal the transfer transaction and verify the validity.

Non Interactive Zero Knowledge Proof

The Non Interactive Zero Knowledge Proof referred as to NIZK prove the statement without revealing any information about the statement. There are some types of NIZK for example SNORKs, STARKs and so on. In this section, we describe the SNARKs and especially Pinocchio Protocol. It's a little bit complicated technology so I divide into three parts.

Abstract

The SNARKs converts the computation problems into the polynomial equations. We can not only hide the computation itself but also verify the computation faster than compute it again. That's why the SNARKs is also used for scaling solution for example zk rollup.

Detail

The SNARKs has three steps.

  1. QAP

Converting computation which we want to prove without revealing additional information into polynomial equations. In Pinocchio Protocol, we need to generate polynonial equations for each computation. The polynomial equations are decided for corresponding computation.

  1. Polynomail Commitment

Hiding the secret as polynomial coefficients, opening with evaluating that polynomials at point, and we can prove the computation was done correctly.

  1. Homomorphic Encryption

The polynomial coefficients are encrypted to keep the secret so we need perform evaluation with remaining encrypted. The Homomorphic Encryption can perform the multiple time addition and one time multiplication for encrypted number using elliptic curve and pairing.

QAP (Quadratic Arithmetic Programs)

Abstract

The QAP is the technology which converts computation to polynomial groups. With this, we can check whether the computation was executed correctly just factor the polynomial without execute computation again.

Details

Let's take a look at details. I give a example. Let's prove that following computation was executed correctly.

$$a^2 \cdot b^2 = c.$$

Assume that c is public input. a and b are private input. Prove that knowledge of a and b satisfying above equation.

Flattening

First of all, let's disassemble the computation to minimum form using multicative.

$$ 1: a * a = a^2 $$ $$ 2: b * b = b^2 $$ $$ 3: a^2 * b^2 = c $$

Now the computation was disassembled to three gate.

R1Cs

As described, we have three multicative computation and want to check whether each steps are executed correctly to set constraint. Before that, we permute above characters as following.

$$ [a, a^2, b, b^2, c] -> [v, w, x, y, z] $$

And now, our computation can be expressed as following table. Left, Right and Output.

GateLRO
1vvw
2xxy
3ywz

QAP

Let's express above table as polynomail groups. As example, express v polynomial on L column. x cordinate is Gate number and y cordinate is if that variable is used, it's going to be 1 and oserwise 0. In L column, v is only used Gate 1 so express as (1, 1) (2, 0) (3, 0). Find polynomial using Lagrange interpolation formula for each variables.

L column polynomial

VariableCordinatePolynomialName
v(1, 1) (2, 0) (3, 0)$$ \frac{x^2}{2} - \frac{5x}{2} + 3 \ $$Lv
w(1, 0) (2, 0) (3, 0)0Lw
x(1, 0) (2, 1) (3, 0)$$ -x^2 + 4x - 3 $$Lx
y(1, 0) (2, 0) (3, 1)$$ \frac{x^2}{2} - \frac{3x}{2} + 1 \ $$Ly
z(1, 0) (2, 0) (3, 0)0Lz

Above polynomial expresses the gate that variable uses. When we pass gate number to polynomial v $$ \frac{x^2}{2} - \frac{5x}{2} + 3 \ $$, we can know which gate the v is used. For example, we pass 1 to polynomial v, it returns 1 so the variable v is used on gate 1 but it returns 0 when we pass 2 and 3 so it's not used on these gate. When we add all polynomial Lv + Lw + Lx + Ly + Lz = L(x), it returns 1 when we pass 1, 2 and 3. We do the same operation for each column and get polynomials as well.

  • L(x)
    Lv + Lw + Lx + Ly + Lz
  • R(x)
    Rv + Rw + Rx + Ry + Rz
  • O(x)
    Ov + Ow + Ox + Oy + Oz

We can intruduce above polynomials when we decide the computation.

Proof

From now on, we are going to prove the state ment. In here, we use a = 2, b = 3 and c = 36. We can get actual value as following.

$$ [v, w, x, y, z] -> [2, 4, 3, 9, 36] $$

And we multiply above variables by for each polynomial. For example, L(x) is following.

$$ L(x) = v * Lv + w * Lw + x * Lx + y * Ly + z * Lz $$

When we pass the 1 to L(x), we can get v because only Lv returns 1 and others return 0. R(x) as well and O(x) returns w so following equation holds.

$$ L(1) * R(1) - O(1) = v * v - w = 2 * 2 - 4 = 0 $$

It corresponds the table we saw in R1Cs and above also holds the case x = 2 and x = 3. When we want to prove the statement, we are going to make above polynomial with secret [v, w, x, y, z] -> [2, 4, 3, 9, 36] so polynomial would be integrated as one as following.

$$ L(x) = 2 * Lv + 4 * Lw + 3 * Lx + 9 * Ly + 36 * Lz $$ $$ R(x) = 2 * Rv + 4 * Rw + 3 * Rx + 9 * Ry + 36 * Rz $$ $$ O(x) = 2 * Ov + 4 * Ow + 3 * Ox + 9 * Oy + 36 * Oz $$ $$ L(x) * R(x) - O(x) = P(x) $$

We make the P(x) to prove the statement.

Verification

We can know whether computation was executed correctly to devide P(x) with (x - 1) * (x - 2) * (x - 3). If it's devided as following prover knows the secret a and b leading c.

$$ P(x) = (x - 1) * (x - 2) * (x - 3) * T(x) $$

Next

In this section, we understood how to convert computation to polynomial groups but there are some possibility that P(x) was made without using secret. In addition to this, we can know the secret to factor the polynomial. Zk SNARKs addresses the former problem with Polynomial Commitment and latter problem with homomorphic encryption.

Polynomial Commitment

In previous section, we enable to check whether computation was done correctly by the knowledge of polynomial which can be devided by minimal polynomial Z(x) as following.

$$ L(x) * R(x) - O(x) = Z(x) * T(x) $$

However, we can create equation easily because Z(x) is public information. Then we need to verify whether L(x), R(x), O(x) are created by using valid input. To do so, we are going to use Polynomial Commitment.

Abstract

The Polynomial Commitment check whether the prover know polynomials L(x), R(x), O(x) and these comes from valid input.

Details

To know polynomials L(x), R(x), O(x) means having knowledge of coefficients of them.

Combination

First of all, we combine these polynomials to one in order to make check process easier. Let's say degree of polynomials L(x), R(x), O(x) as d, we can combine them into one as following and let combined polynomial as F(x).

$$ L(x) + R(x) * X^{d+1} + O(x) * X^{2d+1} = F(x) $$

In polynomial F(x), the coefficients of 0~d degree expresses L(x) coefficients, d+1~2d is R(x) and 2d+1~3d is O(x) as well. The polynomial F(x) degree is 3d and when we denote coefficients as k, it would be following.

$$ F(x) = k_0 + k_1X + k_2X^2 + ... + k_{3d}X^{3d} $$

Verification

The verification processes are following.

  1. Bob choses random α, (a_0,...,a_{3d}) ∈ F and compute (b_0,...,b_{3d}) = α(a_0,...,a_{3d}).
  2. Bob sends Alice to (a_0,...,a_{3d}) and (b_0,...,b_{3d}).
  3. Alice computes following. $$ (\acute a_0,...,\acute a_{3d}, \acute b_0,...,\acute b_{3d}) = (a_0 * k_0,...,a_{3d} * k_{3d}, b_0 * k_0,...,b_{3d} * k_{3d}) $$
  4. Bob checks following. $$ (\acute a_0,...,\acute a_{3d}) = α(\acute b_0,...,\acute b_{3d}) $$

If Alice don't know the coefficients, she couldn't do step 3. With using this step, we can know that the prover know polynomials L(x), R(x), O(x) and these comes from valid input.

Next

In this section, we understood how to check the prover polynomials comming from valid input but these information would be known by verifier. To hide these information from verifier, we are using homomorphic encryption.

Homomorphic Encryption

In previous two sections, we enable to check whether computation was done correctly through polynomials equation and these polynomials are generated with valid process with following equations.

  • QAP $$ L(x) * R(x) - O(x) = Z(x) * T(x) $$

  • Polynomial Commitment $$ L(x) + R(x) * X^{d+1} + O(x) * X^{2d+1} = F(x) $$ $$ F(x) = k_0 + k_1X + k_2X^2 + ... + k_{3d}X^{3d} $$ $$ (\acute a_0,...,\acute a_{3d}, \acute b_0,...,\acute b_{3d}) = (a_0 * k_0,...,a_{3d} * k_{3d}, b_0 * k_0,...,b_{3d} * k_{3d}) $$ $$ (\acute a_0,...,\acute a_{3d}) = α(\acute b_0,...,\acute b_{3d}) $$

Lastly, we would like to send these information with zero knowledge. To do so, we are going to use Homomorphic Encryption.

Abstract

The Homomorphic Encryption achieves above equations evaluation without revealing any information. The difference between homomorphic encryption and normal encryption is that the Homomorphic Encryption can do add, sub and mul remaining encrypted. With this, the verifier doesn't know any information about these polynomials but able to check the relation between them.

Details

Specifically, if the encryption supports additive and multiplicative, we would call it Full Homomorphic Encryption but it takes so much cost to calculate or ciphertext is too big to transfer through the internet. In this case, we deal the encryption which supports multiple additive and one time multiplicative. We realize these with elliptic curve and pairing.

Elliptic Curve

The elliptic curve is the equation look like following.

$$ y^2 = x^3 + ax + b $$

Pairing

The pairing is the mapping which takes two elliptic curve points and map to the element of finitie field as following.

$$ f: G * G -> F_p $$

There are some types of pairing functions but it's complicated so describing the relationship with simple model. Let's denote the two generators of elliptic as G1, G2 and, each scalar as a, b and generator of finite field as g. We can express the relactionship between these two elliptic curve points and finite field element as following.

$$ f: aG1 * bG2 -> g^{ab} $$

Tutorial

In this section, we describe how to use the pallet for privacy-preserving transactions.

You can check Frequent Errors when the error happens.

Abstract

The privacy-preserving transactions consists of several pallet components. We roughly explain what kind of role for each pallet has.

pallet-plonk

plonk is a zk-Snarks scheme and allows us to prove that the computation was done correctly. We perform transaction on off-chain and generate the proof. The blockchain verifies the proof and approve the transaction. We define the constraints circuit for confidential transfers and confidential smart contracts by this pallet.

pallet-encrypted-balance

Users balances are encrypted by default. We use additive homomorphic arithmetic to hide the integer in transaction. Combining original pallet-balance and ElGamal encryption and we implemented pallet-encrypted-balance. This pallet can't be used only by this self, because this doesn't check the validity of additive homomorphic arithmetic.

confidential_transfer

Users can transfer without being known actual amount by others with this pallet. plonk checks the confidential transfer constraints and pallet-encrypted-balance performs the additive homomorphic state transition.

pallet-plonk

In this tutorial, we are going to import plonk-pallet to substrate runtime and test its functionalities.

The steps are following.

  1. Define the plonk-pallet as depencencies
  2. Couple the plonk-pallet to your own pallet
  3. Define the plonk-pallet functions on your pallet
  4. Import the coupling pallet to TestRuntime and define your Circuit
  5. Test whether the functions work correctly

1. Define the plonk-pallet as depencencies

First of all, you need to define the plonk-pallet when you start to implement your pallet. Please define as following.

  • /Cargo.toml
[dependencies]
pallet-plonk = { git = "https://github.com/zero-network/zero", branch = "master", default-features = false }
zero-jubjub = { git = "https://github.com/zero-network/zero", branch = "master", default-features = false }
zero-plonk = { git = "https://github.com/zero-network/zero", branch = "master", default-features = false }
rand_core = {version="0.6", default-features = false }

The plonk-pallet depends on rand_core so please import it.

2. Couple the plonk-pallet to your own pallet

The next, the plonk-pallet need to be coupled with your pallet. Please couple the pallet Config as following.

  • /src/main.rs
#[frame_support::pallet]
pub mod pallet {
    use frame_support::pallet_prelude::*;
    use frame_system::pallet_prelude::*;
    pub use plonk_pallet::{FullcodecRng, Proof, PublicInputValue, Transcript, VerifierData};

    /// Coupling configuration trait with plonk_pallet.
    #[pallet::config]
    pub trait Config: frame_system::Config + plonk_pallet::Config {
        /// The overarching event type.
        type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
    }

With this step, you can use the plonk-pallet in your pallet through Module.

3. Define the plonk-pallet functions on your pallet

The next, let's define the plonk-pallet function on your pallet. We are going to define the trusted_setup function which generates the public parameters refered as to srs and the verify function which verified the proof. In this tutorial, we use sum-storage pallet as example and add the verify function before set Thing1 storage value on set_thing_1. If the verify is success, the set_thing_1 can set Thing1 value.

  • /src/main.rs
#![allow(unused)]
fn main() {
    // The module's dispatchable functions.
    #[pallet::call]
    impl<T: Config> Pallet<T> {
        // Coupled trusted setup
        #[pallet::weight(10_000)]
        pub fn trusted_setup(
            origin: OriginFor<T>,
            val: u32,
            rng: FullcodecRng,
        ) -> DispatchResultWithPostInfo {
            pallet_plonk::Pallet::<T>::trusted_setup(origin, val, rng)?;
            Ok(().into())
        }

        /// Sets the first simple storage value
        #[pallet::weight(10_000)]
        pub fn set_thing_1(
            origin: OriginFor<T>,
            val: u32,
            proof: Proof,
            public_inputs: Vec<Fr>,
        ) -> DispatchResultWithPostInfo {
            // Define the proof verification
            pallet_plonk::Pallet::<T>::verify(origin, proof, public_inputs)?;

            Thing1::<T>::put(val);

            Self::deposit_event(Event::ValueSet(1, val));
            Ok(().into())
        }
}

With this step, we can check whether the proof is valid before setting the Thing1 value and only if the proof is valid, the value is set.

4. Import the coupling pallet to TestRuntime and define your Circuit

We already imported the plonk-pallet functions so we are going to import it to TestRumtime and define your customized Circuit.

In order to use plonk-pallet in TestRuntime, we need to import plonk-pallet crate and define the pallet config to construct_runtime as following.

  • runtime/src/main.rs
#![allow(unused)]
fn main() {
use crate::{self as sum_storage, Config};

use frame_support::dispatch::{DispatchError, DispatchErrorWithPostInfo, PostDispatchInfo};
use frame_support::{assert_ok, construct_runtime, parameter_types};

// Import `plonk_pallet` and dependency
pub use plonk_pallet::*;
use rand_core::SeedableRng;

--- snip ---

construct_runtime!(
    pub enum TestRuntime where
        Block = Block,
        NodeBlock = Block,
        UncheckedExtrinsic = UncheckedExtrinsic,
    {
        System: frame_system::{Module, Call, Config, Storage, Event<T>},
        // Define the `plonk_pallet` in `contruct_runtime`
        Plonk: plonk_pallet::{Module, Call, Storage, Event<T>},
        {YourPallet}: {your_pallet}::{Module, Call, Storage, Event<T>},
    }
);
}

As the final step of runtime configuration, we define the zk-SNARKs circuit and extend the TestRuntime config with it. You can replace TestCircuit with your own circuit.

  • runtime/src/main.rs
#![allow(unused)]
fn main() {
// Implement a circuit that checks:
// 1) a + b = c where C is a PI
// 2) a <= 2^6
// 3) b <= 2^5
// 4) a * b = d where D is a PI
// 5) JubJub::GENERATOR * e(JubJubScalar) = f where F is a Public Input

#[derive(Debug, Default)]
pub struct TestCircuit {
    pub a: BlsScalar,
    pub b: BlsScalar,
    pub c: BlsScalar,
    pub d: BlsScalar,
    pub e: JubJubScalar,
    pub f: JubJubAffine,
}

impl Circuit for TestCircuit {
    fn circuit<C>(&self, composer: &mut C) -> Result<(), Error>
    where
        C: Composer,
    {
        let a = composer.append_witness(self.a);
        let b = composer.append_witness(self.b);

        // Make first constraint a + b = c
        let constraint = Constraint::new().left(1).right(1).public(-self.c).a(a).b(b);

        composer.append_gate(constraint);

        // Check that a and b are in range
        composer.component_range(a, 1 << 6);
        composer.component_range(b, 1 << 5);

        // Make second constraint a * b = d
        let constraint = Constraint::new()
            .mult(1)
            .output(1)
            .public(-self.d)
            .a(a)
            .b(b);

        composer.append_gate(constraint);

        let e = composer.append_witness(self.e);
        let scalar_mul_result = composer.component_mul_generator(e, GENERATOR_EXTENDED)?;
        composer.assert_equal_public_point(scalar_mul_result, self.f);
        Ok(())
    }
}

impl plonk_pallet::Config for TestRuntime {
    type CustomCircuit = TestCircuit;
    type Event = Event;
}
}

With this step, we finish to setup the plonk runtime environment.

5. Test whether the functions work correctly

The plonk functions is available on your pallet so we are going to test them as following tests.

  • /src/main.rs
fn main() {
    let mut rng = get_rng();
    let label = b"verify";
    let test_circuit = TestCircuit {
        a: BlsScalar::from(20u64),
        b: BlsScalar::from(5u64),
        c: BlsScalar::from(25u64),
        d: BlsScalar::from(100u64),
        e: JubJubScalar::from(2u64),
        f: JubJubAffine::from(GENERATOR_EXTENDED * JubJubScalar::from(2u64)),
    };

    new_test_ext().execute_with(|| {
        assert_eq!(SumStorage::get_sum(), 0);
        assert_ok!(Plonk::trusted_setup(Origin::signed(1), 12, rng.clone()));

        let pp = Plonk::public_parameter().unwrap();
        let (prover, _) =
            Compiler::compile::<TestCircuit>(&pp, label).expect("failed to compile circuit");

        let (proof, public_inputs) = prover
            .prove(&mut rng, &test_circuit)
            .expect("failed to prove");

        assert_ok!(SumStorage::set_thing_1(
            Origin::signed(1),
            42,
            proof,
            public_inputs
        ));
        assert_eq!(SumStorage::get_sum(), 42);
    })
}

With above tests, we can confirm that your pallet is coupling with plonk-pallet and these functions work correctly. You can check the plonk-pallet example here. Happy hacking!

confidential_transfer

In this tutorial, we are going to generate test data and test its functionalities. We assume that you already unserstand what Confidential Transfer is.

The steps are following.

  1. Define the confidential_transfer as depencencies
  2. Generate test data used for confidential_transfer
  3. Test funcitonalities

1. Define the confidential_transfer as depencencies

First of all, you need to define the confidential_transfer.

  • /Cargo.toml
confidential_transfer = { git = "https://github.com/zero-network/zero", branch = "master", default-features = false }
pallet_encrypted_balance = { git = "https://github.com/zero-network/zero", branch = "master", default-features = false }
pallet_plonk = { git = "https://github.com/zero-network/zero", branch = "master", default-features = false }
zero_elgamal = { git = "https://github.com/zero-network/zero", branch = "master", default-features = false }
zero_bls12_381 = { git = "https://github.com/zero-network/zero", branch = "master", default-features = false }
rand_core = {version="0.6", default-features = false }

The confidential_transfer depends on rand_core so please import it.

2. Generate test data used for confidential_transfer

Secondly, we would like like to setup the Alice and Bob account on testing runtime. Define the new_test_ext for genesis config and reflect the testing data for runtime storage.

#![allow(unused)]
fn main() {
fn new_test_ext(
    alice_address: u64,
    alice_private_key: Fp,
    alice_balance: u32,
    alice_radomness: Fp,
    bob_private_key: Fp,
    bob_address: u64,
    bob_balance: u32,
    bob_radomness: Fp,
) -> sp_io::TestExternalities {
    let alice_balance = EncryptedNumber::encrypt(alice_private_key, alice_balance, alice_radomness);
    let bob_balance = EncryptedNumber::encrypt(bob_private_key, bob_balance, bob_radomness);

    let mut t = frame_system::GenesisConfig::default()
        .build_storage::<TestRuntime>()
        .unwrap();
    pallet_encrypted_balance::GenesisConfig::<TestRuntime> {
        balances: vec![(alice_address, alice_balance), (bob_address, bob_balance)],
    }
    .assimilate_storage(&mut t)
    .unwrap();

    let mut ext = sp_io::TestExternalities::new(t);
    ext.execute_with(|| System::set_block_number(1));
    ext
}
}

Thirdly, we define generate_default_test_data to generate parameters used for confidential_transfer.

#![allow(unused)]
fn main() {
fn generate_default_test_data() -> (u64, Fp, u16, Fp, Fp, u64, u16, Fp, u16, u16, u16) {
    let mut rng = rand::thread_rng();

    let alice_address = rng.gen::<u64>();
    let alice_private_key = Fp::random(OsRng);
    let alice_balance = rng.gen::<u16>();
    let alice_radomness = Fp::random(OsRng);
    let bob_private_key = Fp::random(OsRng);
    let bob_address = rng.gen::<u64>();
    let bob_balance = rng.gen::<u16>();
    let bob_radomness = Fp::random(OsRng);
    let transfer_amount = rng.gen_range(0..alice_balance);
    let alice_after_balance = alice_balance - transfer_amount;
    let bob_after_balance = bob_balance + transfer_amount;

    (
        alice_address,
        alice_private_key,
        alice_balance,
        alice_radomness,
        bob_private_key,
        bob_address,
        bob_balance,
        bob_radomness,
        transfer_amount,
        alice_after_balance,
        bob_after_balance,
    )
}
}

3. Test funcitonalities

Finally, we combine previous sections together and test functionalities.

fn main() {
    let k = 14;
    let label = b"verify";
    let mut rng = get_rng();
    let (
        alice_address,
        alice_private_key,
        alice_balance,
        alice_radomness,
        bob_private_key,
        bob_address,
        bob_balance,
        bob_radomness,
        transfer_amount,
        alice_after_balance,
        bob_after_balance,
        transfer_randomness,
    ) = generate_default_test_data();
    new_test_ext(
        alice_address,
        alice_private_key,
        alice_balance,
        alice_radomness,
        bob_private_key,
        bob_address,
        bob_balance,
        bob_radomness,
    )
    .execute_with(|| {
        // default balance decryption check
        let alice_encrypted_balance = ConfidentialTransfer::total_balance(&alice_address);
        let alice_raw_balance = alice_encrypted_balance.decrypt(alice_private_key);
        let bob_encrypted_balance = ConfidentialTransfer::total_balance(&bob_address);
        let bob_raw_balance = bob_encrypted_balance.decrypt(bob_private_key);

        assert_eq!(alice_raw_balance.unwrap() as u16, alice_balance);
        assert_eq!(bob_raw_balance.unwrap() as u16, bob_balance);

        // trusted setup check
        let result =
            ConfidentialTransfer::trusted_setup(Origin::signed(alice_address), k, rng.clone());
        assert_ok!(result);

        // proof generation
        let pp = Plonk::public_parameter().unwrap();
        let alice_public_key = GENERATOR_EXTENDED * alice_private_key;
        let bob_public_key = GENERATOR_EXTENDED * bob_private_key;
        let transfer_amount_scalar = Fp::from(transfer_amount as u64);
        let alice_after_balance_scalar = Fp::from(alice_after_balance as u64);

        let alice_balance =
            EncryptedNumber::encrypt(alice_private_key, alice_balance.into(), alice_radomness);
        let alice_transfer_amount = EncryptedNumber::encrypt(
            alice_private_key,
            transfer_amount.into(),
            transfer_randomness,
        );
        let bob_encrypted_transfer_amount =
            (GENERATOR_EXTENDED * transfer_amount_scalar) + (bob_public_key * transfer_randomness);
        let alice_public_key = JubJubAffine::from(alice_public_key);
        let bob_public_key = JubJubAffine::from(bob_public_key);
        let bob_encrypted_transfer_amount = JubJubAffine::from(bob_encrypted_transfer_amount);
        let bob_encrypted_transfer_amount_other = (GENERATOR_EXTENDED * transfer_randomness).into();

        let confidential_transfer_circuit = ConfidentialTransferCircuit::new(
            alice_public_key,
            bob_public_key,
            alice_balance,
            alice_transfer_amount,
            bob_encrypted_transfer_amount,
            alice_private_key,
            transfer_amount_scalar,
            alice_after_balance_scalar,
            transfer_randomness,
        );
        let prover = Compiler::compile::<ConfidentialTransferCircuit>(&pp, label)
            .expect("failed to compile circuit");
        let proof = prover
            .0
            .prove(&mut rng, &confidential_transfer_circuit)
            .expect("failed to prove");

        // confidential transfer check
        let transaction_params = ConfidentialTransferTransaction::new(
            alice_public_key,
            bob_public_key,
            alice_transfer_amount,
            bob_encrypted_transfer_amount,
            bob_encrypted_transfer_amount_other,
        );
        let result = ConfidentialTransfer::confidential_transfer(
            Origin::signed(alice_address),
            bob_address,
            proof.0,
            transaction_params,
        );
        assert_ok!(result);

        // balance transition check
        let alice_balance = ConfidentialTransfer::total_balance(&alice_address);
        let alice_raw_balance = alice_balance.decrypt(alice_private_key);
        let bob_balance = ConfidentialTransfer::total_balance(&bob_address);
        let bob_raw_balance = bob_balance.decrypt(bob_private_key);

        assert_eq!(alice_raw_balance.unwrap() as u16, alice_after_balance);
        assert_eq!(bob_raw_balance.unwrap() as u16, bob_after_balance);
    })
}

With above tests, we can confirm that confidential transfer works correctly. You can check the confidential_transfer example here. Happy hacking!

Frequent Errors

The main errors happen during development of runtime pallet are followings.

  • error: duplicate lang item in crate
  • error: the wasm32-unknown-unknown target is not supported by default, you may need to enable the "js" feature
  • error[E0603]: module "group" is private
  • error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
  • error[E0432]: unresolved import sp_core::to_substrate_wasm_fn_return_value

Explaining causes and remedies.

error: duplicate lang item in crate

This error happens when we use different version crate but same crate on one crate. The error says the dependencies duplication so we can query the crate name as following.

$ cargo tree -e features -i {crate}

If we find the duplication of crate that we use same crate different version multiple times, we should align the version.

error: the wasm32-unknown-unknown target is not supported by default, you may need to enable the "js" feature

This error happens getrandom crate dependency on std.
We need to disable std feature of getrandom.

Firstly, checking which libraries depend on getrandom depending on std to execute following command.

$ cargo tree -e features

cargo tree command displays the dependencies tree.
The libraries with (*) doesn't depend on std but if there is getrandom not marked as (*), it would cause error.

Secondly, independing from std library by followings.

  • Add default-features = false to crate in Cargo.toml which is not marked as (*)
  • Add #![cfg_attr(not(feature = "std"), no_std)] if imported crate is made by self.

And run cargo tree and check whether getrandom is marked as (*)

You can also use cargo nono check to check dependency on std.

$ cargo nono check

error[E0603]: module "group" is private

This error happens syn crate because its interface was change. We need to indicate exact version of syn as using expected behavior.

$ cargo update -p syn --precise 1.0.96

error[E0512]: cannot transmute between types of different sizes, or dependently-sized types

This error happens on runtime-interface and both macro available when #[cfg(all(not(feature = "std"), not(feature = "disable_target_static_assertions")))] so we need to specify std as following.

[features]
default = ["std"]
std = [
    "crate/std"
]

error[E0432]: unresolved import sp_core::to_substrate_wasm_fn_return_value

This error happens the crate which has sp_api dependency. And to clarify every crate which imported as default-features = false is described as crate/std in [features].

[features]
default = ["std"]
std = [
    "crate/std"
]