Skip to main content

Zero-knowledge Proof extension

The Zero-knowledge Proof (ZKP) extension for Chromia enables developers to integrate advanced privacy-preserving features directly into their decentralized applications. By supporting PLONK (Permutations over Lagrange-bases for Oecumenical Noninteractive arguments of Knowledge) zero-knowledge proofs, this extension allows applications to verify computations and validate data without disclosing sensitive information.

PLONK is a zero-knowledge proof system that allows one party to prove to another that they have knowledge of certain information without revealing the information itself. PLONK is known for its efficient verification process and universal trusted setup, making it particularly suitable for blockchain applications. It improves upon previous ZK-SNARK systems by requiring only one universal trusted setup, which can be utilized for all circuits of a given size.

Original paper: https://eprint.iacr.org/2019/953.pdf

Circuit and key prerequisites

Chromia does not provide tools for creating zero-knowledge circuits or generating verification keys. You must create these externally using tools like snarkjs before configuring your Chromia blockchain. The verification keys shown in the configuration below are the output of your external circuit development and trusted setup process.

Blockchain configuration

To be able to verify PLONK proofs, you need to add ZKPGTXModule to your blockchain configuration. In addition, you also need to add your verification key(s) to it. Since it's possible to have more than one key, you need to give them an identifier in the configuration. See the example below:

blockchains:
<my_blockchain_name>:
module: <my_module_name>
config:
gtx:
modules:
- "net.postchain.zkp.ZKPGTXModule"
zkp:
plonk:
verification_keys:
<verification_key_id>:
Qc:
x: 0L
y: 0L
Ql:
x: 14834510898339141169329695685511700666258129880115403401229217490569515343302L
y: 9464224989088393801563875716232716915352580613417007162533827452727295446934L
Qm:
x: 14319141532948637259101778790582856386713203034743319527668731828185741400626L
y: 18803411281712541135482987792568462445904566200816418568722397188523035189143L
Qo:
x: 21364495085187694205438833340386123229650091635008748320283906080029143641039L
y: 20444368598332986012476714370015271579755130392073789088854120974413040917081L
Qr:
x: 16351212189779639003908022196613349497559341482069657547941244577951374625350L
y: 4031150659123603989265639197462571629326313607153532089054168665871713116509L
S1:
x: 18126157909848214547544885505634038550836716698610211113963616409746950617323L
y: 9480441132510474920656224855370744169816505579366946377741851539331522216404L
S2:
x: 15312771180064260077439958970242428939484644647049778052207315835174955686500L
y: 3394171162668419319900753190450314872629638145244728026210508800204487774112L
S3:
x: 15657500937529974074969620066780498543702080929706007838285907462589433927278L
y: 10353909101117571868937190939592663817839933444432483255231136649076311981690L
X_2:
x1: 19518502430870438181592443054401419581386850463392492809261261189226441340111L
x2: 8184812567585147824016587988320202893865389288562095514010129273046904740147L
y1: 10435902743221815611441838668065138241026433470816816910474433321658510434042L
y2: 3114164934987634673554850993955208553008076750558125585683111415888626769753L
curve: bn128
k1: 2L
k2: 3L
nPublic: 1
power: 11

As you can see, you also specify which curve that should be used in the key. Currently, the following values are allowed:

  • bn128
  • bls12381

These correspond to the supported curves: BN128 and BLS12-381.

Rell library

There is a small Rell library that you can install to help verify proofs:

libs:
zkp:
registry: https://gitlab.com/chromaway/postchain-chromia.git
path: chromia-infrastructure/rell/src/lib/zkp
rid: x"8934250CED0D8C7FB46C458CDB303236AA70F3666DA67376E75E89D16F125FF9"

This library exposes the following functions that you can use to verify if a proof is present in the current transaction:

/**
* Checks whether or not the current transaction contains a valid PLONK proof.
*
* @param verification_key_id ID of the verification key that the proof must have been validated with
* @param public_signals The public signals that the proof must have been validated with
*/
function check_plonk_proof(
verification_key_id: text,
public_signals: list<big_integer>
)

/**
* Checks whether or not the current transaction contains a valid PLONK proof operation before current operation.
*
* @param verification_key_id ID of the verification key that the proof must have been validated with
* @return The public signals of the preceding proof operation
*/
function extract_signals_from_preceeding_proof_op(verification_key_id: text): list<big_integer>

To add a proof to a transaction, you need to call a gtx operation zkp_plonk_verify with the following arguments:

[
GtvString, // verification key ID, needs to match verification key id defined in chromia.yml
PlonkProof, // Proof itself, see details below
GtvArray<GtvBigInteger> // Public signals
]

The PlonkProof consists of a GtvArray with the following structure:

[
GtvArray<GtvBigInteger>, // A [x, y]
GtvArray<GtvBigInteger>, // B [x, y],
GtvArray<GtvBigInteger>, // C [x, y],
GtvArray<GtvBigInteger>, // Z [x, y],
GtvArray<GtvBigInteger>, // T1 [x, y],
GtvArray<GtvBigInteger>, // T2 [x, y],
GtvArray<GtvBigInteger>, // T3 [x, y],
GtvArray<GtvBigInteger>, // Wxi [x, y],
GtvArray<GtvBigInteger>, // Wxiw [x, y],
GtvBigInteger, // eval a
GtvBigInteger, // eval b
GtvBigInteger, // eval c
GtvBigInteger, // eval s1
GtvBigInteger, // eval s2
GtvBigInteger, // eval zW
]

Example dapp operations

Converts public FT4 tokens to private tokens

This operation, known as "shielding," converts publicly visible FT4 tokens into private commitments on the blockchain. The zero-knowledge proof ensures that the conversion is valid and that the correct amount is being shielded without revealing the actual token amounts or the user's private information. operation shield_tokens()

Converts private tokens back to public FT4 tokens

This operation, known as "unshielding," converts private token commitments back to publicly visible FT4 tokens, making them available for standard blockchain operations without using zero-knowledge proof operations. operation unshield_tokens()

Private transfer functionality using zero-knowledge proofs

This operation allows users to transfer private tokens while ensuring complete privacy regarding the transaction amounts. The zero-knowledge proof verifies that the sender has a sufficient amount of tokens without revealing the actual number of tokens.

operation private_transfer()

Submitting a Proof from a Client

Here is an example of how to call the zkp_plonk_verify operation from the client side. In this example, we use the shield_tokens operation.

note

Note that the string used for the second argument in the zkp_plonk_verify operation matches both the string used in the call to extract_signals_from_preceding_proof_op within the shield_token operation and the verification_key_id defined in the chromia.yml.

let txBuilder = this.session.transactionBuilder();
txBuilder.add(op("zkp_plonk_verify", "shield_operation", proofResult.proof, proofResult.publicSignals), {
authenticator: authenticator1,
});
txBuilder.add(op("shield_tokens", Buffer.from(encryptedNote)), {
authenticator: authenticator2,
});
const result = await txBuilder.buildAndSend();