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.
- Zero Network
- Overview
- Transaction Constraints
- Primitive
- Pallet
- Related Tools
- Tutorial
- Frequent Errors
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.
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.
Item | Confidential | Anonymous |
---|---|---|
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.
Scheme | Trusted Setup | Prover Cost | Verifier Cost | Proof Size | Quantum Tolerance |
---|---|---|---|---|---|
zk-SNARKs | Necessary | Low | Low | Small | No |
zk-STARKs | Unnecessary | Moderate | High | Large | Yes |
Bulletproofs | Unnecessary | Low | Moderate | Moderate | No |
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 Type | Checking Condition | Static or Dynamic |
---|---|---|
Common Transaction | Basic transactions object for example gas, signature and so on | Static |
Confidential Transfer | Confidential transfer condition | Static |
Confidential Smart Contract | Confidential smart contract condition | Dynamic |
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.
Item | Description |
---|---|
x | user private key |
y | user public key |
srs | setup parameters |
s | randomness used for setup |
setup() | setup function |
F | field |
G | elliptic curve group |
g | elliptic curve generator |
d | polynomial degree |
pk | proving key |
vk | verification 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.
Item | Description |
---|---|
source | transactor |
target | destination address |
input | transaction data |
value | message value |
gasLimit | gas limit of transaction |
gasPrice | gas price of transaction |
nonce | user account nonce |
The Common Transaction Constraints
checks that the value, gasLimit and gasPrice are valid.
We use following function to generate proof and signature.
Function | Description |
---|---|
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.
- The transfer amount is encrypted by exact sender and recipient public key
- The transfer amount and sender remaining balance are in valid range (not negative)
- 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.
Symbol | Description |
---|---|
sk | Alice private key |
pk | Alice public key |
pk' | Bob public key |
t | Transfer amount |
b | Alice remaining balance |
enc_bal_left | Balance encrypted by Alice |
enc_bal_right | Balance encrypted by Alice |
enc_left | Transfer amount encrypted by Alice |
enc_right | Transfer amount encrypted by Alice |
enc_t' | Transfer amount encrypted by Bob |
r | Randomness |
g | Generator 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
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
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
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
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
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
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
- Initial rust implementation of PLONK by ZK-Garage
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.
- Generates recipient public key pairs
- Generates recipient
Stealth Address
- 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.
Variable | Explanation | Derivation |
---|---|---|
a | Bob private key | a ∈ Fp |
b | Bob private key | b ∈ Fp |
A | Bob public key for a | a * G |
B | Bob public key for b | b * 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
.
Variable | Explanation | Derivation |
---|---|---|
r | randomness generated by Alice | r ∈ Fp |
H | one-way hash function which takes curve point and maps field element | e: xG -> Fp |
R | public value generated by Alice | r * G |
P | stealth address of Bob | H(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.
- Setup the parameters
- Hide the transfer amount
- 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.
Variable | Explanation | Derivation |
---|---|---|
p | prime number | - |
a | random number | a ∈ Fp |
C(x) | elliptic curve function | - |
G | generator of elliptic curve point | G ∈ C(Fp) |
H | generator made by random a | H = 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.
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.
Hiding the secret as polynomial coefficients, opening with evaluating that polynomials at point, and we can prove the computation was done correctly.
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.
Gate | L | R | O |
---|---|---|---|
1 | v | v | w |
2 | x | x | y |
3 | y | w | z |
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
Variable | Cordinate | Polynomial | Name |
---|---|---|---|
v | (1, 1) (2, 0) (3, 0) | $$ \frac{x^2}{2} - \frac{5x}{2} + 3 \ $$ | Lv |
w | (1, 0) (2, 0) (3, 0) | 0 | Lw |
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) | 0 | Lz |
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.
- Bob choses random
α, (a_0,...,a_{3d}) ∈ F
and compute(b_0,...,b_{3d}) = α(a_0,...,a_{3d})
. - Bob sends Alice to
(a_0,...,a_{3d})
and(b_0,...,b_{3d})
. - 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}) $$
- 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.
- Define the plonk-pallet as depencencies
- Couple the plonk-pallet to your own pallet
- Define the plonk-pallet functions on your pallet
- Import the coupling pallet to TestRuntime and define your Circuit
- 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.
- Define the
confidential_transfer
as depencencies - Generate test data used for
confidential_transfer
- 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 inCargo.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"
]