ZEOS Orchard
This is the main application of the ZEOS protocol for private and untraceable transactions on the EOS Blockchain. This application will be deployed to the EOS Mainnet.
See also:
- The ZEOS Book (including a full protocol specification)
- Token Contract (Orchard)
- JS Wallet (Orchard)
Description
This repository is a fork of Zcash Orchard. The application enables Zcash-like shielded transactions of fungible and non-fungible tokens on the EOS blockchain. Check out the Whitepaper for more Information.
This application is built on EOSIO and Liquidapps' DAPP Network services.
Getting Started
To setup the full workspace clone the dependencies rustzeos, halo2, pasta_curves, reddsa, the smart contract and the JS wallet as well:
mkdir zeos
cd zeos
git clone https://github.com/mschoenebeck/rustzeos.git
git clone https://github.com/mschoenebeck/halo2.git
git clone https://github.com/mschoenebeck/pasta_curves.git
git clone https://github.com/mschoenebeck/reddsa.git
git clone https://github.com/mschoenebeck/thezeostoken.git
git clone https://github.com/mschoenebeck/zeos-verifier.git
cd thezeostoken && git checkout orchard && cd ..
git clone https://github.com/mschoenebeck/zeos-wallet.git
cd zeos-wallet && git checkout orchard && cd ..
Clone this repository:
git clone https://github.com/mschoenebeck/zeos-orchard.git
cd zeos-orchard
Build the project as Rust library:
cargo build
Dependencies
Help
If you need help join us on Telegram.
Authors
Matthias Schönebeck
License
Copyright 2020-2022 The Electric Coin Company.
You may use this package under the Bootstrap Open Source Licence, version 1.0,
or at your option, any later version. See the file COPYING
for
more details, and LICENSE-BOSL
for the terms of the Bootstrap
Open Source Licence, version 1.0.
The purpose of the BOSL is to allow commercial improvements to the package while ensuring that all improvements are open source. See here for why the BOSL exists.
Acknowledgments
Big thanks to the Electric Coin Company for developing, documenting and maintaining this awesome open source codebase for zk-SNARKs!
Introduction
The protocol presented here is a fork of the Zcash Orchard Shielded Protocol, but tailored for EOSIO/Antelope blockchains and their asset classes. One of the key differences to Zcash is that the ZEOS Orchard Shielded Protocol is implemented as a smart contract on a general purpose blockchain (rather than as a stand-alone application on its own blockchain). Deployed on a public blockchain like the EOS mainnet, it exists as one of many applications in a diverse ecosystem. This makes it desirable to enable not only private peer-to-peer transactions, as with Zcash, but also private peer-to-contract transactions.
This makes the ZEOS Orchard Shielded Protocol much more sophisticated than the original version of Zcash. After all, there is only one fungible token on the Zcash blockchain - the native Zcash cryptocurrency - and no smart contracts at all. On EOSIO/Antelope blockchains, however, there are countless tokens with different symbols and precision, which are even issued and managed by different smart contracts. Finally, there are even different token types, such as fungible and non-fungible tokens.
This increased complexity in an environment of general-purpose blockchains such as EOSIO/Antelope also inevitably increases the requirements for a protocol for private transactions on such smart contract platforms. The ZEOS Orchard Shielded Protocol provides a fully comprehensive solution for user privacy in such an environment. Implemented and deployed as an application on a public blockchain such as EOS, it allows users to remain completely anonymous in their daily interactions with decentralized applications.
The ZEOS Orchard Shielded Protocol enables private DeFi on EOSIO/Antelope blockchains.
Preliminaries
This work is based on a thorough study of the following protocols and applications:
- Nightfall: A protocol for private peer-to-peer transactions on Ethereum based on zk-SNARK.
- Zcash Sprout
- Zcash Sapling
- Zcash Orchard
Related Research:
- Monero: A protocol for private peer-to-peer transactions based on Stealth Addresses, Ring Signatures, and RingCT.
- Dero: A protocol for private peer-to-peer transactions based on Homomorphic Encryption.
Zcash Protocol Specification
Since the protocol presented here is a fork of the Zcash Orchard Shielded Protocol the same specification applies almost everywhere. Only differences/extensions of the original protocol are specified here. Thus there are a lot of references to the original Zcash Protocol Specification.
ZEOS Whitepaper
The ZEOS whitepaper contains a first version of the concepts specified here. However, it was written in regards to the Groth16 proving system while the protocol presented here is based on the Halo2 proving system. The concepts described there are still mostly valid though.
Terminology
The terminology used is based on that of Nightfall and Zcash. The abbreviation 'UTXO' means 'Unspent Transaction Output' and is used interchangably with the term 'note'. The terms 'mint' and 'burn' refer to the creation (mint) or nullification (burn) of UTXOs (aka notes). The term 'ZEOS smart contract' refers to an EOSIO/Antelope smart contract that implements the ZEOS Orchard Shielded Protocol as specified here.
Introduction Video
The following video gives a short introduction to the features of the Zcash Orchard Shielded Protocol by the leading developers Sean Bowe and Daira Hopwood.
Overview
The ZEOS Orchard Shielded Protocol specifies a non-traceable UTXO transaction model implemented in an EOSIO/Antelope smart contract. This means that private ZEOS wallets do not exist directly in blockchain RAM - like EOSIO/Antelope accounts, for example - but only in the form of UTXOs that are assigned to specific wallet addresses. Users can therefore - as with any legacy cryptocurrency - simply create a new ZEOS wallet by picking a large random number (aka private key).
All assets held in private ZEOS wallets are actually in custody of the ZEOS smart contract. In order to transfer an asset from a transparent EOS account to a private ZEOS wallet, it is actually transferred to the ZEOS smart contract, while at the same time a (private) UTXO is minted, assigned to a private ZEOS wallet address specified by the user. UTXOs can then be transferred completely anonymously between ZEOS wallets without their ownership being traceable on chain.
The only data managed by the ZEOS smart contract are cryptographic commitments of valid UTXOs and their nullifiers. The validity of commitments and nullifiers is proven to the public using zero knowledge proofs (more precisely: zk-SNARKs) without revealing any sensitive information about the private UTXOs themselves.
Analogous to minting, UTXOs can also be burned in order to transfer assets from private ZEOS wallets back to a transparent EOS accounts. By burning the corresponding UTXO, the underlying asset is freed from custody of the ZEOS smart contract and transferred to an EOS account specified by the user. Using mint and burn actions, assets can be moved between transparent EOS accounts and private ZEOS wallets.

Privacy therefore only exists for assets "inside" the ZEOS application - i.e. only for assets in custody of the ZEOS smart contract and represented by UTXOs in private ZEOS wallets. The underlying EOSIO/Antelope blockchain remains transparent, of course. However, the introduction of a so-called authenticator token enables private token deposits and withdrawals. This means that users are able to interact directly from their ZEOS wallets with third party smart contracts of the same EOSIO/Antelope blockchain - as long as those contracts implement the necessary interface of the ZEOS Orchard Shielded Protocol.
The introduction of the authenticator token and the private deposits and withdrawals it enables make the ZEOS Orchard Shielded Protocol a truly universal privacy solution for EOSIO/Antelope blockchains that offers users of private ZEOS wallets almost the same blockchain experience as users of transparent EOS accounts - but with complete privacy.
See the ZEOS Whitepaper for more information about the concepts of the protocol presented here.
Keys & Addresses
The entire underlying cryptography of the protocol is identical to Zcash Orchard and is precisely specified in the Zcash Protocol Specification. The key and address derivation is thus also exactly identical to Zcash Orchard. The only difference is that there are no transparent UTXOs in the ZEOS Orchard Shielded Protocol and thus no unshielded transactions. The newly introduced concept of 'Unified Payment Addresses' in Zcash is therefore not adopted. In ZEOS there are only 'Shielded Payment Addresses'.
For a detailed explanation of all key components, please refer to the Zcash Protocol Specification, section 3.1 and section 4.2.3 respectively. In this document only the most important parts are briefly discussed. All key components are 32 bytes long.

Spending Key
The Spending Key is a randomly chosen number from which all further key material is derived. Since the introduction of BIP 32 and ZIP 32, respectively, spending keys are usually derived deterministically from long, human readable seed phrases via pseudo-random function.
Full Viewing Key
The Full Viewing Key is the actual set of keys needed to generate private transactions. It is derived directly from the Spending Key and consists of three sub-keys:
- - Spend Validating Key
- - Nullifier Deriving Key
- - Randomness
This key represents the minimum authority needed to spend UTXOs.
Incoming Viewing Key
The Incoming Viewing Key can be used to detect incoming payments (i.e. received UTXOs), but not to spend them. This key can be used, for example, for payment terminals to be able to confirm successfully received payments to customers.
In addition, each UTXO has a unique Viewing Key that can be shared by the sender with others in order to prove that a transmitted UTXO has been received.
Outgoing Viewing Key
The Outgoing Viewing Key can be used to detect all sent payments (i.e. spent UTXOs) of a wallet.
Shielded Payment Address
An Orchard Shielded Payment Address is 43 bytes long and consists of:
- - Diversifier (10 bytes)
- - Diversified Transmission Key (33 bytes)
Since Zcash Orchard it is possible to deterministically derive several diversified payment addresses from one and the same spending authority. A group of such addresses share the same Full Viewing Key, Incoming Viewing Key and Outgoing Viewing Key.
UTXOs (aka Notes)
While there is only one fungible token in Zcash - the native cryptocurrency ZEC - there is significantly more token diversity in EOSIO/Antelope ecosystems. The ZEOS Orchard Shielded Protocol defines three different UTXO types:
-
Fungible Token (FT) Fungible tokens on EOSIO/Antelope blockchains are defined by the amount, symbol, and code of the smart contract (aka EOS account name) that issues them.
-
Non-Fungible Token (NFT) NFTs on EOSIO/Antelope Blockchains mainly follow the AtomicAssets token standard and are thus defined by a unique identifier and code of the smart contract (aka EOS account name) that issues them. The latter is the atomicassets smart contract in most cases, but can also be a custom NFT contract that follows the same standard.
-
Authenticator Token (AT) These tokens represent a permission to access specific assets in custody of a specific smart contract. They are characterized by the code of the respective smart contract and their value.
Tuple
To cover all three ZEOS token types, the Zcash Orchard Note Tuple (as defined in Section 3.2 of the Zcash Protocol Specification is extended to the following structure:
- is the diversifier of the recipient’s shielded payment address
- is the diversified transmission key of the recipient’s shielded payment address
- is an integer representing either the amount of the UTXO (fungible token) or the lower 64 bits of an identifier (non-fungible token)
- is an integer representing either the symbol of the UTXO (fungible token) or the upper 64 bits of an identifier (non-fungible token)
- is an integer representing the code of the issuing smart contract
- is a boolean determining if this UTXO is a fungible token (FT) or non-fungible token (NFT or AT)
- is randomness used to derive the nullifier of the UTXO
- is additional randomness used in deriving the nullifier
- is a random commitment trapdoor as defined in section 4.1.8 of the Zcash Protocol Specification
Note: In addition to the above listed attributes each UTXO struct contains a header field (64 bit) and a memo field (512 bytes).
Commitment
When UTXOs are created (through minting or transfer), only a cryptographic commitment called to the tupel attributes listed above is publicly disclosed and added to a global data structure called Commitment Tree. This allows the sensitive information such as amount, symbol, and recipient of the UTXO to be kept secret, while the commitment is used by the zk-SNARK proof to verify that the UTXO's secret information is valid.
Since the UTXO tuple has been extended for the ZEOS Orchard Shielded Protocol, the definition of the UTXO Commitment must also be modified. The original is defined in section 5.4.8.4 of the Zcash Protocol Specification. It is changed to:
Nullifier
Each UTXO has a unique nullifier which is deterministically derived from the UTXO tuple. Spending a UTXO invalidates it by publicly revealing the associated nullifier and adding it to the global set of all nullifiers called Nullifier Set. Analogous to the UTXO commitment, this way the sensitive information (amount, symbol, receiver) of the UTXO can be kept secret, while the nullifier is used by the zk-SNARK proof to check whether the nullifier is valid. That is, whether it actually nullifies an existing valid UTXO. Furthermore, the smart contract must check if the nullifier does not yet exist in the global set of all nullifiers to avoid double spends.
The exact function for deriving the nullifier is defined in section 4.16 of the Zcash Protocol Specification.
where is the value of the corresponding UTXO.
No modification is required, since it depends only on the UTXO randomness as well as its commitment. The latter has already been adapted to the new UTXO tuple structure (see Commitment).
In-band secret distribution of UTXOs
The sender of a UTXO must be able to transmit the sensitive information (i.e. the UTXO tuple) to the receiver. After all, these are required so that receivers of UTXOs can successfully spend them. Thus, a private communication channel is needed through which the sensitive UTXO tuples can be transmitted.
For this purpose, Zcash Orchard - like all existing privacy coins in the crypto space - uses the underlying blockchain itself: Each UTXO tuple is encrypted and its ciphertext uploaded to the blockchain where it can be fetched and decrypted by the receiver. For the ZEOS Orchard Shielded Protocol a global data structure called Transmitted UTXO Ciphertext List is maintained by the ZEOS smart contract in which UTXO ciphertexts can be added.
The use of this communication channel is optional. It is important to keep in mind that all encrypted UTXOs added to that list become part of the underlying EOSIO/Antelope blockchain history which never forgets. At some point in the future the currently used encryption scheme might become unsafe and thus privately transmitted UTXO ciphertexts using this on chain communication channel might become traceable. This is a general problem all privacy cryptocurrencies in existence today suffer from.
The exact encryption scheme based on Incoming, Outgoing and Full Viewing Key is described in section 4.19 of the Zcash Protocol Specification and is adopted exactly the same way for the ZEOS Orchard Shielded Protocol.
Global Data Sets
The ZEOS smart contract maintains the following global data sets which define the state of the UTXO transaction model.
Commitment Tree
The UTXO Commitment Tree is a merkle tree managed by the ZEOS smart contract. It is instantiated with the Sinsemilla hash function, which can be efficiently implemented in Halo 2 arithmetic circuits. The leaves of the tree contain all existing and thus valid UTXO commitments. Thus, each UTXO that is newly created either by minting or transfer has an associated commitment leaf in the merkle tree. The number of leaves in the tree defines the size of the so-called anonymity set, which is directly determined by the depth of the tree.

The tree is always filled from left to right. Depending on the depth of the tree, there is room for a certain amount of leaves per tree. When a tree is full, it 'overflows' to the right into a new, empty tree. Each time one or more leaves are added, the root of the tree is updated.
Commitment Tree Root Set
In addition to the tree itself, the ZEOS smart contract maintains a set of merkle nodes in which all valid tree roots that have ever been created are being recorded. This way, the anonymity set of the protocol can be easily configured via the merkle tree depth, without having to worry about how many trees exist at a given time or to which tree a certain leaf (aka UTXO commitment) belongs.
For a UTXO to be valid, the following must be true: There exists (or existed) a merkle path that leads (or led) to a valid merkle root. This must be proven via zero knowledge proof for a UTXO in order to be spent.
The Commitment Tree itself as well as the Commitment Tree Roots set are both part of the global UTXO transaction state.
Nullifier Set
The UTXO Nullifier Set maintains a list of all nullifiers that have been publicly exposed. Each nullifier invalidates a particular UTXO which prevents double spending. The validity of nullifiers must be proven via zero knowledge proof in order to be able to spend UTXOs.
The Nullifier Set is part of the global UTXO transaction state.
Transmitted UTXO Ciphertext List
This global list of transmitted UTXO ciphertexts is used for the In-band secret distribution of UTXOs. It is not part of the global UTXO transaction state and its usage is optional.
Arithmetic Circuit
Every privacy protocol based on zk-SNARKs is built around a so-called arithmetic circuit, which defines the constraint system based on which all zero knowledge proofs are generated. It is essentially a mathematical function that takes a set of private inputs and a set of public inputs and either resolves to (valid inputs) or (invalid inputs). The arithmetic circuit forms the core of any privacy protocol and should be designed to be as optimal as possible. In Halo 2, the complexity (i.e. the size) of the circuit is directly correlated to the proof verification time as well as the proof size. Thus, the cost (i.e. blockchain resources) of private transactions is directly determined by the complexity of the arithmetic circuit.
The circuits of all three Zcash protocols - Sprout, Sapling and Orchard - are fundamentally different from each other. This is mainly due to the use of different zk-SNARK proving systems, in which cryptographic "gadgets" - i.e. arithmetic implementations of hash functions or elliptic curve cryptography - have different complexity (i.e. number of constraints).
The Zcash Orchard Shielded Protocol, for example, is based on the Halo 2 Proving System and a PLONKish Arithmetization in which lookup tables can be used. This allows the Sinsemilla hash function, which was developed specifically for this protocol, to be implemented highly efficiently within the circuit. This is crucial for the complexity of the circuit, since for each UTXO that is issued, the valid merkle path must be proven, resulting in a concatenation of multiple Sinsemilla gadgets within the circuit. Since the merkle path accounts for the vast majority of the complexity of the entire circuit, the choice of an efficiently implementable hash function is crucial.
For example, in the protocol evolution from Zcash Sprout to Zcash Sapling, the hash function used in the Merkle tree was changed from Sha256 to Blake2s, since the latter leads to a significantly lower number of constraints in a Groth16 proving system (over the curves Bls12-381/Jubjub) and R1CS arithmetization. There are a number of interesting discussions on this topic by Zcash developers on Github.
The arithmetic circuit is of critical importance to the protocol, as it defines exactly what a valid UTXO transaction is. In the following, we will first roughly explain the arithmetic circuit of the Zcash Orchard Shielded Protocol and then describe the changes that lead to the design of the ZEOS Orchard Shielded Protocol arithmetic circuit.
Notation
The following notation is used to formally express arithmetic circuits and their context.
- : The private inputs of an arithmetic circuit
- : The public inputs of an arithmetic circuit
- : An arithmetic circuit
- : A proof for a circuit created with arguments
Zcash Action Circuit
A Zcash Orchard transaction is defined as a sequence of one or more actions. The Zcash Orchard circuit defines what a valid action is. It is important to note that in addition to shielded UTXOs, Zcash also has a transparent value pool (i.e. the Zcash protocol also allows for transparent, traceable transactions).
The general idea is as follows: Each action allows the spending of up to one UTXO () and the creation of up to one new UTXO (). To balance the value difference between them there is a balancing value ().
The equation applies:
where refers to the value (i.e. the amount of ZEC) of a UTXO as defined in section 3.2 (p.14) of the Zcash Protocol Specification.
The value thus expresses whether the UTXO transfer of a single Orchard action results in a positive or negative value surplus. Over the entire transaction - i.e. over the sum of all actions - must obviously result in zero. This means that the sum of all inputs of a transaction must be equal to the sum of all outputs.
However, the actual value surplus of an action remains secret - just like the private inputs of the circuit (i.e. the sensitive UTXO data). Instead, only a so-called of is published, which is specified in section 5.4.8.3 of the Zcash Protocol Specification.
The is a Pedersen Commitment that has special cryptographic properties such as homomorphic addition. This property allows for the values of all actions of the same transaction to be balanced without having to disclose the actual differences of individual actions publicly: The homomorphically encrypted of all individual actions can be summed up and eventually balanced with a single transparent value from the transparent value pool (in case the sum is not zero). See section 4.14 of the Zcash Protocol Specification to learn more about the final balancing value of a shielded transaction.
A shielded transaction in Zcash Orchard therefore either generates a transparent output (), or it consumes a transparent input (), or equals zero, in which case it is a fully shielded transaction with no inputs or outputs from the transparent value pool.
The flexibility of this circuit design therefore allows the four different types of actions:
- transparent → shielded (, resembles a mint).
- shielded → transparent (, resembles a burn)
- shielded → shielded (, resembles a shielded transfer)
- mixed → mixed (either mint + shielded transfer or burn + shielded transfer)
The following schematic shows a highly simplified representation of the Zcash Orchard top level arithmetic circuit, reduced to the essential components. The black inputs are the private inputs of the arithmetic circuit and the blue inputs are the public inputs. Only if all equality gates (==) resolve to true the output of the circuit is , otherwise it is .

The circuit is divided into three areas, which are highlighted in different colors:
Area A contains all the logic regarding . This is by far the most complex part of the circuit, since the validity of as well as the validity of must be proven here. Specifically, it must be proven that:
- There exists a sister path to the of , which leads to a valid of the merkle tree (i.e. is a valid UTXO).
- The address of the UTXO can be derived from (i.e. is indeed the correct private key to ).
- The nullifier is valid (i.e. is indeed the nullifier of ).
Area B proves that is indeed the correct to the newly created UTXO .
Area C proves that actually represents the correct (aka Pedersen commitment) of the value surplus .
Modifications
However, for the ZEOS Orchard Shielded Protocol, the Zcash Orchard action circuit needs to be slightly adapted. Firstly, because there are no transparent transactions and therefore no transparent value pool in ZEOS. Furthermore, there is not only one, but countless different value pools in ZEOS because of the token diversity in EOSIO/Antelope ecosystems. Finally, there are numerous different fungible tokens as well as NFTs, which have no value pool at all because of their uniqueness.
Therefore the following approach is chosen:
- is renamed to
- is renamed to
- the difference value is converted into a third UTXO
- the of then becomes a of
The previous equation of the Zcash Orchard action circuit thus becomes:
where refers to the value (FT) or ID (NFT) of a UTXO as defined in UTXO Tuple.
Interestingly, the above equation works for both fungible and non-fungible tokens: In case of fungible token transfers, represents the change that (normally) goes back into the sender's wallet. In case of NFT transfers, however, is necessarily zero, since an NFT cannot be split. But if equals zero, it follows from the above equation that which corresponds exactly to the desired NFT transfer from to .
In addition to above equations, the following conditions must hold:
The equality of all values enforces that either all UTXOs in the circuit must have the same token symbol (FT) or that the upper 64 bits of an ID must match (in case of NFT, but then must equal zero). The equality of all values enforces that all UTXOs in the circuit represent tokens issued by the same smart contract.
However, introducing and replacing the with a comes with the trade-off that value surpluses of multiple actions of the same transaction can no longer be balanced with each other, since a is no Pedersen commitment and therefore does not have homomorphic properties. This means that in the ZEOS Orchard Shielded Protocol every single action must be balanced out to zero surplus using , i.e. the inputs of an action () must equal the outputs of the same action ().
ZEOS Action Circuit

As in Zcash Orchard there is only one circuit which is used to generate proofs for all privacy actions. The ZEOS Orchard circuit, , is very similar to the Zcash Orchard circuit. It can be divided into three parts: A, B and C. Each circuit part represents a UTXO and the action circuit describes their relationship to each other. There are two main configurations for this circuit.
It is either:
or
The first configuration is used for all transfer and burn actions. UTXO represents the note which is being spent by the action. UTXO represents the receiving part of the action whereas UTXO represents the 'change' which goes usually back into the wallet of the sender (spender of UTXO ). Hence the relation between the UTXOs.
In case of NFT transfers (or burns) UTXO is always zero which enforces . This statement must be true for NFT transfers since NFTs are not divisable.
The second configuration is used for minting notes only. The configuration effectively disables the circuit parts and leaving only part enabled.
See also:
- the schematic of the action circuit (TODO: Legend)
- the layout of the action circuit (column types explained here)
Private Inputs ()
The following list contains all private inputs to the top level ZEOS action circuit.
Note (action input):
- : Authentication path of note commitment A. The sister path of note commitment A which is required in order to calculate it's merkle plath.
- : Position of note commitment A inside the merkle tree. Specifically this is the leaf index of note commitment A.
- : Address diversify hash of note A. Deterministically derived from a diversifier index (see p. 37 Zcash Protocol Specification).
- : Address public key of note A. Derived from Incoming Viewing Key and diversify hash (see p. 14 and 37 Zcash Protocol Specification).
- : Either the value of note A (fungible token) or the (lower 64 bits of the) unique identifier of note A (non-fungible token).
- : Either the symbol code of note A (fungible token) or the (upper 64 bits of the) unique identifier of note A (non-fungible token).
- : Randomness to derive nullifier of note A (equals nullifier of note that was spent in order to create note A) (see p. 14 Zcash Protocol Specification)
- : Additional randomness to derive nullifier of note A (see p. 14 Zcash Protocol Specification)
- : Random commitment trapdoor of note commitment A (see p. 14 and 28 Zcash Protocol Specification)
- : Note commit of note A (see p. 28 Zcash Protocol Specification).
- : Randomness to derive a spend authorization signature for note A (see p. 55 Zcash Protocol Specification).
- : Spend Validating Key which is part of the Full Viewing Key components (see p. 36 and 116 Zcash Protocol Specification).
- : Nullifier Deriving Key which is part of the Full Viewing Key components (see p. 36 and 116 Zcash Protocol Specification).
- : Randomness which is part of the Full Viewing Key components to derive corresponding Incoming Viewing Key of the address diversify hash of note A (see p. 36, 37 and 116 Zcash Protocol Specification).
Note (action output):
- : Address diversify hash of note B.
- : Address public key of note B.
- : Either the value of note B (fungible token) or the (lower 64 bits of the) unique identifier of note B (non-fungible token).
- : Either the symbol code of note B (fungible token) or the (upper 64 bits of the) unique identifier of note B (non-fungible token).
- : The code of the smart contract issuing notes A, B and C.
- : Randomness to derive nullifier of note B (equals nullifier of note A).
- : Additional randomness to derive nullifier of note B.
- : Random commitment trapdoor of note commitment B.
- : Code of EOS account in which this note is 'burned'.
Note (action output):
- : Address diversify hash of note C.
- : Address public key of note C.
- : Value of note C (fungible token only).
- : Additional randomness to derive nullifier of note C.
- : Random commitment trapdoor of note commitment C.
- : Code of EOS account in which this note is 'burned'.
Public Inputs ()
The following list contains all public inputs to the top level ZEOS action circuit.
- : Merkle tree root of authentication path of note A (see p. 17 to 19 Zcash Protocol Specification).
- : Nullifier of note A (see p. 56 Zcash Protocol Specification).
- : Spend Authority (x component) of (, ) (see p. 61 Zcash Protocol Specification).
- : Spend Authority (y component).
- : Indicates if notes in circuit represent NFTs or fungible tokens.
- : Exposes value (fungible token) or unique id (non-fungible token) of note B in case of MINT or BURN.
- : Exposes symbol (fungible token) or unique id (non-fungible token) of note B in case of MINT or BURN.
- : Exposes smart contract code of note B in case of MINT or BURN.
- : Exposes value of note C in case of BURNFT2 (fungible tokens only).
- : Note commitment of note B.
- : Note commitment of note C.
- : Code of EOS account into which note B is 'burned'.
- : Code of EOS account into which note C is 'burned'.
Circuit Internal Signals
The following signals are circuit-internal only.
- : Derived root of merkle path of note A.
- : Derived commitment of note A.
- : Derived address public key of note A.
- : Derived spend authority (x component) of note A.
- : Derived spend authority (y component) of note A.
- : Derived nullifier of note A.
- : Derived commitment of note B.
- : Derived commitment of note C.
Constraints
Constraining an arithmetic circuit in Halo 2 is very similar to integrated circuit design in computer engineering. All Constraints are expressed as mathematical equations evaluating to zero. The logical AND becomes a multiplication and the logical OR becomes an addition.
The following statements for private and public inputs must hold.
The global statement 'either or ' results in the following constraint for the note values :
For circuit part A the following constraints must hold:
For circuit part B the following constraints must hold:
For circuit part C the following constraints must hold:
Valid Circuit Configurations
The following table lists the zactions and the corresponding configurations of the circuit's public inputs .
MINTFT

Given:
because of internal signals (1), (4), (5), (6) and constraints (2), (5), (6) and (8)
Given:
because of internal signal (7)
Given:
because of constraint (1)
Given:
because of constraint (13)
Given:
because of constraint (17)
MINTNFT/BURNAT
Note: The actions MINTNFT and BURNAT share the exact same circuit configuration.

Given:
because of internal signals (1), (4), (5), (6) and constraints (2), (5), (6) and (8)
Given:
because of internal signal (7)
Given:
because of constraints (1) and (14)
Given:
because of constraint (13)
Given:
because of constraint (17)
TRANSFERFT

Given:
because of internal signals (1), (4), (5), (6) and constraints (2), (5), (6) and (8)
Given:
because of constraint (1)
because of constraint (7)
because of constraint (8)
Given:
because of constraint (13)
Given:
because of constraint (17)
TRANSFERNFT

Given:
because of internal signals (1), (4), (5), (6) and constraints (2), (5), (6) and (8)
Given:
because of constraint (14)
Given:
because of constraint (1)
Given:
because of constraint (7)
because of constraint (8)
Given:
because of constraint (13)
Given:
because of constraint (17)
BURNFT

Given:
because of internal signals (1), (4), (5), (6) and constraints (2), (5), (6) and (8)
Given:
because of constraint (1)
because of constraint (7)
because of constraint (8)
Given:
because of constraint (17)
BURNFT2

Given:
because of internal signals (1), (4), (5), (6) and constraints (2), (5), (6) and (8)
Given:
because of constraint (1)
because of constraint (7)
because of constraint (8)
BURNNFT

Given:
because of internal signals (1), (4), (5), (6) and constraints (2), (5), (6) and (8)
Given:
because of constraint (14)
Given:
because of constraint (1)
Given
because of constraint (7)
because of constraint (8)
Given:
because of constraint (17)
NoteCommit Chip
Besides the changes in the top level design, the chip has to be adapted to the new UTXO tuple. However, these adaptations are not trivial.
The Sinsemilla hash function operates on multiples of 10 bits of the input message. However, since this is an implementation within an arithmetic circuit (and not an integrated circuit) there are no bits and bytes, only field elements (aka large numbers) as input signals. Thus, the various elements of the UTXO tuple must first be decomposed so that they can be concatenated into chunks of 10 bits and the message parts finally digested as 10 bit chunks. This requires canonicity checks for the value ranges of all message parts.
Compare the original version of this chip from the Zcash Orchard Shielded Protocol.
Message decomposition
is used in the function. The input to is:
where:
- are representations of Pallas curve points, with bits used for the -coordinate and bit used for the -coordinate.
- are Pallas base field elements.
- is a -bit value.
- is a -bit value.
- is a -bit value.
- is a -bit value.
Sinsemilla operates on multiples of 10 bits, so we start by decomposing the message into chunks:
Then we recompose the chunks into message pieces:
where is 5 zero bits (corresponding to the padding applied by the Sinsemilla function).
Each message piece is constrained by to its stated length. Additionally:
- and are witnessed and checked to be valid elliptic curve points.
- is witnessed as a field element, but its decomposition is sufficient to constrain it to be a 64-bit value.
- is witnessed as a field element, but its decomposition is sufficient to constrain it to be a 64-bit value.
- is witnessed as a field element, but its decomposition is sufficient to constrain it to be a 64-bit value.
- is witnessed as a field element, but its decomposition is sufficient to constrain it to be a 1-bit value.
- and are witnessed as field elements, so we know they are canonical.
However, we need additional constraints to enforce that:
- The chunks are the correct bit lengths (or else they could overlap in the decompositions and allow the prover to witness an arbitrary message).
- The chunks contain the canonical decompositions of , , , and (or else the prover could witness multiple equivalent inputs to ).
Some of these constraints are implemented with a reusable circuit gadget, . We define custom gates for the remainder. Since these gates use simple boolean selectors activated on different rows, their selectors are eligible for combining, reducing the overall proof size.
Message piece decomposition
We check the decomposition of each message piece in its own region. There is no need to check the whole pieces:
- ( bits) is witnessed and constrained outside the gate;
- ( bits) is witnessed and constrained outside the gate;
- ( bits) is witnessed and constrained outside the gate;
The following helper gates are defined:
- .
- is a short lookup range check.
has been constrained to be bits by the Sinsemilla hash.
Region layout
Constraints
Outside this gate, we have constrained:
has been constrained to be bits by the .
Region layout
Constraints
Outside this gate, we have constrained:
- is equality-constrained to , where the latter is the index-1 running sum output of , constrained by the hash to be bits.
has been constrained to be bits by the .
Region layout
Constraints
Outside this gate, we have constrained:
has been constrained to be bits by the .
Region layout
Constraints
Outside this gate, we have constrained:
- is equality-constrained to , where the latter is the index-1 running sum output of , constrained by the hash to be 240 bits.
has been constrained to be bits by the .
Region layout
Constraints
Outside this gate, we have constrained:
- is equality-constrained to , where the latter is the index-1 running sum output of , constrained by the hash to be 60 bits.
has been constrained to be bits by the .
Region layout
Constraints
Outside this gate, we have constrained:
- is equality-constrained to , where the latter is the index-1 running sum output of , constrained by the hash to be 50 bits.
has been constrained to be bits by the .
Region layout
Constraints
Outside this gate, we have constrained:
Field element checks
All message pieces and subpieces have been range-constrained by the earlier decomposition gates. They are now used to:
- constrain each field element , , , and to be 255-bit values, with top bits , , , and respectively.
- constrain where is the Pallas base field modulus.
- check that these are indeed canonically-encoded field elements, i.e.
The Pallas base field modulus has the form , where is 126 bits. We therefore know that if the top bit is not set, then the remaining bits will always comprise a canonical encoding of a field element. Thus the canonicity checks below are enforced if and only if the corresponding top bit is set to 1.
In the constraints below we use a base- variant of the method used in libsnark (originally from [SVPBABW2012, Appendix C.1]) for range constraints :
- Let be the smallest power of greater than .
- Enforce .
- Let .
- Enforce .
with
Recall that . When the top bit is set, we check that :
-
Since we know that and in particular
-
To check that , we use two constraints:
a) . This is expressed in the custom gate as where is the index-13 running sum output by
b) . To check this, we decompose into thirteen 10-bit words (little-endian) using a running sum , looking up each word in a -bit lookup table. We then enforce in the custom gate that
Region layout
Constraints
with
Recall that . When the top bit is set, we check that :
-
To check that we use two constraints:
a) is already constrained individually to be a -bit value. is the index-13 running sum output by By constraining we constrain
b) . To check this, we decompose into fourteen 10-bit words (little-endian) using a running sum , looking up each word in a -bit lookup table. We then enforce in the custom gate that
Region layout
Constraints
Region layout
Constraints
with
Recall that . When the top bit is set, we check that :
-
To check that we use two constraints:
a) is already constrained individually to be a -bit value. is the index-13 running sum output by By constraining we constrain
b) . To check this, we decompose into fourteen 10-bit words (little-endian) using a running sum , looking up each word in a -bit lookup table. We then enforce in the custom gate that
Region layout
Constraints
with
Recall that . When the top bit is set, we check that :
-
Since we know that and in particular
-
To check that we use two constraints:
a) is already constrained individually to be a -bit value. is the index-13 running sum output by By constraining we constrain
b) . To check this, we decompose into thirteen 10-bit words (little-endian) using a running sum , looking up each word in a -bit lookup table. We then enforce in the custom gate that
Region layout
Constraints
Region layout
Constraints
Region layout
Constraints
Region layout
Constraints
-coordinate checks
Note that only the LSB of the -coordinates was input to the hash, while the other bits of the -coordinate were unused. However, we must still check that the witnessed bit matches the original point's -coordinate. The checks for will follow the same format. For each -coordinate, we witness:
where is for , and for . Let We decompose to be bits using a strict word ten-bit lookup. The running sum outputs allow us to susbstitute
Recall that and were pieces input to the Sinsemilla hash and have already been boolean-constrained. and are constrained outside this gate to and bits respectively. To constrain the remaining chunks, we use the following constraints:
Then, to check that the decomposition was correct:
with
In these cases, we check that :
-
Since we know that and in particular
-
To check that , we use two constraints:
a) . This is expressed in the custom gate as where is the index-13 running sum output by the -bit lookup decomposition of .
b) . To check this, we decompose into thirteen 10-bit words (little-endian) using a running sum , looking up each word in a -bit lookup table. We then enforce in the custom gate that
Region layout
Constraints
Outside this gate, we have constrained:
This can be checked in exactly the same way as , with replaced by .
ZEOS Actions (ZActions)
Analogous to EOSIO/Antelope actions ZEOS privacy actions (aka zactions) change the global state of the UTXO transaction model, specifically of the Commitment Tree, Commitment Tree Root Set and Nullifier Set. The only exceptions are MINTAT and BURNAT which are used to mint and burn authenticator tokens and only change the state of the third party smart contract they are associated with (see Private Deposits & Withdrawals for more details).
Tuple
Analogous to the EOSIO/Antelope action struct, a zaction struct is defined. The tuple contains the following elements:
- : The type of zaction to be executed (see Types below)
- : The public inputs of this zaction (see public inuts tuple under ZEOS Action Circuit)
- : A memo field used in BURN actions to set the memo for the resulting EOSIO/Antelope token transfer action
Based on this zaction tuple and in analogy to EOSIO/Antelope transactions, a ztransaction is defined as a sequence of zactions.
Types
Based on the constraint system of the ZEOS Orchard action circuit the following privacy actions (i.e. zactions) are defined:
- MINTFT: Mints a new UTXO representing a fungible asset.
- MINTNFT: Mints a new UTXO representing a non-fungible asset.
- MINTAT: Mints a new UTXO representing a permission.
- TRANSFERFT: Transfers a UTXO representing a fungible asset.
- TRANSFERNFT: Transfers a UTXO representing a non-fungible asset.
- BURNFT: Burns a UTXO representing a fungible asset.
- BURNFT2: Burns a UTXO representing a fungible asset with two different receivers.
- BURNNFT: Burns a UTXO representing a non-fungible asset.
- BURNAT: Burns a UTXO representing a permission.
MINTFT

Mints a new fungible UTXO. This effectively moves a fungible asset from a transparent EOSIO/Antelope account into a private ZEOS wallet. For this operation only part B of the ZEOS Action Circuit is used since only one new UTXO is created by this action.
Privacy Implications
This action provides only limited privacy protection:
- sender: traceable - the sender's EOSIO/Antelope account
- asset: traceable - quantity of the asset's smart contract's transfer action
- memo: untraceable - hidden in UTXO ciphertext
- receiver: untraceable - hidden in UTXO ciphertext
Flow
The following steps specify the flow of MINTFT.
Step 0
The fungible EOSIO/Antelope asset to be transferred is defined by the tuple:
- : The amount of asset as specified in the EOSIO developer documentation
- : The symbol of asset as specified in the EOSIO developer documentation
- : The EOSIO/Antelope account of the smart contract that issues asset
Step 1
Create a new UTXO tuple representing the fungible asset :
- Diversifier index of the receiving ZEOS wallet address
- Public key of the receiving ZEOS wallet address
- Choose a random value
- Choose a random value
- Choose a random value
Step 2
Calculate the diversified transmission key of the receiving ZEOS wallet address (see section 5.4.1.6 of the Zcash Protocol Specification):
Step 3
Calculate the of (see UTXO Commitment):
Step 4
Set the private inputs of the arithmetic circuit:
Step 5
Set the public inputs of the arithmetic circuit:
Step 6
Generate a proof of knowledge of satisfying arguments so that
The pair is the zk-SNARK which attests to knowledge of private inputs without revealing them.
Step 7
Generate UTXO ciphertext of for the receiver of the UTXO (see section 4.19.1 of the Zcash Protocol Specification)
Step 8
Transfer asset to the ZEOS smart contract. On reception, the ZEOS smart contract stores it in an asset buffer until MINTFT is executed.
Step 9
Execute the MINTFT action of the ZEOS smart contract. This action takes the following arguments:
- : The zero knowledge proof of satisfying arguments
- : The public inputs of the zero knowledge proof
- : The UTXO ciphertext which is transmitted to the receiver of
The ZEOS smart contract then performs the following checks:
- Is the zero knowledge proof valid?
- Does the UTXO represent the correct asset which is held in the asset buffer? I.e. are the following statements true:
- Is the NFT flag unset ()?
Step 10
If , the ZEOS smart contract performs the following operations:
- Add , the note commitment of , to the next free leaf of the Commitment Tree
- Add the new root of the Commitment Tree to the Commitment Tree Root Set
- Add ciphertext to the Transmitted UTXO Ciphertext List
If , cancel execution.
MINTNFT

Mints a new non-fungible UTXO. This effectively moves a non-fungible asset from a transparent EOSIO/Antelope account into a private ZEOS wallet. For this operation only part B of the ZEOS Action Circuit is used since only one new UTXO is created by this action.
Since EOSIO/Antelope does not specify a standard for non-fungible tokens the token standard of AtomicAssets is applied.
Privacy Implications
This action provides only limited privacy protection:
- sender: traceable - the sender's EOSIO/Antelope account
- asset: traceable - asset id of the asset's smart contract's transfer¹ action
- memo: untraceable - hidden in UTXO ciphertext
- receiver: untraceable - hidden in UTXO ciphertext
¹ refers to the transfer action of the AtomicAssets smart contract
Flow
The following steps specify the flow of MINTNFT.
Step 0
The non-fungible EOSIO/Antelope asset to be transferred is defined by the tuple:
- : The id of asset as specified in the AtomicAssets developer documentation
- : The EOSIO/Antelope account of the AtomicAssets smart contract that issues asset
Step 1
Create a new UTXO tuple representing the non-fungible asset :
- Diversifier index of the receiving ZEOS wallet address
- Public key of the receiving ZEOS wallet address
- Choose a random value
- Choose a random value
- Choose a random value
Step 2
Calculate the diversified transmission key of the receiving ZEOS wallet address (see section 5.4.1.6 of the Zcash Protocol Specification):
Step 3
Calculate the of (see UTXO Commitment):
Step 4
Set the private inputs of the arithmetic circuit:
Step 5
Set the public inputs of the arithmetic circuit:
Step 6
Generate a proof of knowledge of satisfying arguments so that
The pair is the zk-SNARK which attests to knowledge of private inputs without revealing them.
Step 7
Generate UTXO ciphertext of for the receiver of the UTXO (see section 4.19.1 of the Zcash Protocol Specification)
Step 8
Transfer asset to the ZEOS smart contract. On reception, the ZEOS smart contract stores it in an asset buffer until MINTNFT is executed.
Step 9
Execute the MINTNFT action of the ZEOS smart contract. This action takes the following arguments:
- : The zero knowledge proof of satisfying arguments
- : The public inputs of the zero knowledge proof
- : The UTXO ciphertext which is transmitted to the receiver of
The ZEOS smart contract then performs the following checks:
- Is the zero knowledge proof valid?
- Does the UTXO represent the correct asset which is held in the asset buffer? I.e. are the following statements true:
- ¹
- Is the NFT flag set ()?
¹NFTs of smart contracts following the AtomicAssets standard have a 64 Bit unique identifier only, thus no upper 64 Bits.
Step 10
If , the ZEOS smart contract performs the following operations:
- Add , the note commitment of , to the next free leaf of the Commitment Tree
- Add the new root of the Commitment Tree to the Commitment Tree Root Set
- Add ciphertext to the Transmitted UTXO Ciphertext List
If , cancel execution.
MINTAT
Mints a new authenticator token. This action does not require a zero knowledge proof. Authenticator tokens do not (directly) represent real assets and thus have no leaves in the merkle tree. Instead, the corresponding of an authenticator token is used as an identifier for third party smart contracts where assets are privately deposited. By proving knowledge of the secret randomness of an authenticator token, the deposited assets asociated with this token can be withdrawn from the third party smart contract at a later point in time.
Privacy Implications
No assets are being moved by this action.
Flow
The following steps specify the flow of MINTAT.
Step 1
Create a new UTXO tuple representing the permission:
- Diversifier index of the ZEOS wallet address which owns this permission
- Public key of the ZEOS wallet address which owns this permission
- EOSIO/Antelope account of the third party smart contract where this permission is valid
- Choose a random value
- Choose a random value
- Choose a random value
Step 2
Generate UTXO ciphertext of for the owner of the permission (see section 4.19.1 of the Zcash Protocol Specification)
Step 3
Execute the MINTAT action of the ZEOS smart contract. This action takes the following arguments:
- : The UTXO ciphertext which is transmitted to the receiver of
Step 4
The ZEOS smart contract performs the following operations:
- Add ciphertext to the Transmitted UTXO Ciphertext List
TRANSFERFT

Transfers (part of) a fungible UTXO. This effectively moves a certain balance of a fungible asset from one private ZEOS wallet to another. For this operation the entire ZEOS Action Circuit is used since one existing UTXO is being spent while two new UTXOs are being created by this action.
Privacy Implications
This action provides full privacy protection:
- sender: untraceable - hidden in zk-SNARK
- asset: untraceable - hidden in UTXO ciphertext
- memo: untraceable - hidden in UTXO ciphertext
- receiver: untraceable - hidden in UTXO ciphertext
Flow
The following steps specify the flow of TRANSFERFT.
Step 0
The UTXO represents an amount of a fungible EOSIO/Antelope asset from which a certain (partial) is to be transmitted. Therefore the following must apply: .
Step 1
Create a new UTXO tuple representing the receiving of the asset:
- Diversifier index of the receiving ZEOS wallet address
- Public key of the receiving ZEOS wallet address
- Choose a random value
- Choose a random value
- Choose a random value
Create a new UTXO tuple representing the 'change' to of :
- Diversifier index of the sending¹ ZEOS wallet address
- Public key of the sending¹ ZEOS wallet address
- Choose a random value
- Choose a random value
- Choose a random value
¹ Note that it is also possible to choose a third wallet address for instead of the sender's address.
Step 2
Calculate the diversified transmission keys of all ZEOS wallet addresses involved (see section 5.4.1.6 of the Zcash Protocol Specification):
Step 3
Calculate the Commitment of all three UTXOs , and :
Step 4
Calculate the of the Commitment Tree based on the sister path of .
Step 5
Calculate the Nullifier of :
Step 6
Choose a random value and calculate the Spend Authority for this action (see section 4.17.4 of the Zcash Protocol Specification):
- Choose a random value
where is the Spend Validating Key of which is part of the Full Viewing Key.
Step 7
Set the private inputs of the arithmetic circuit:
- sister path of in the Commitment Tree
- leaf index of in the Commitment Tree
- Spend Validating Key of which is part of the Full Viewing Key
- Nullifier Deriving Key of which is part of the Full Viewing Key
- Randomness of which is part of the Full Viewing Key
Step 8
Set the public inputs of the arithmetic circuit:
Step 9
Generate a proof of knowledge of satisfying arguments so that
The pair is the zk-SNARK which attests to knowledge of private inputs without revealing them.
Step 10
Generate UTXO ciphertexts of and of for the receivers of the UTXOs (see section 4.19.1 of the Zcash Protocol Specification)
Step 11
Execute the TRANSFERFT action of the ZEOS smart contract. This action takes the following arguments:
- : The zero knowledge proof of satisfying arguments
- : The public inputs of the zero knowledge proof
- : The UTXO ciphertext which is transmitted to the receiver of
- : The UTXO ciphertext which is transmitted to the receiver of
The ZEOS smart contract then performs the following checks:
- Is the zero knowledge proof valid?
- Is the NFT flag unset ()?
Step 12
If , the ZEOS smart contract performs the following operations:
- Add , the nullifier of to the Nullifier Set
- Add , the note commitment of , to the next free leaf of the Commitment Tree
- Add , the note commitment of , to the next free leaf of the Commitment Tree
- Add the new root of the Commitment Tree to the Commitment Tree Root Set
- Add ciphertext to the Transmitted UTXO Ciphertext List
- Add ciphertext to the Transmitted UTXO Ciphertext List
If , cancel execution.
TRANSFERNFT

Transfers a non-fungible UTXO. This effectively moves a non-fungible asset from one private ZEOS wallet to another. For this operation only part A and B of the ZEOS Action Circuit is used since one existing UTXO is being spent while one new UTXO is being created by this action.
Privacy Implications
This action provides full privacy protection:
- sender: untraceable - hidden in zk-SNARK
- asset: untraceable - hidden in UTXO ciphertext
- memo: untraceable - hidden in UTXO ciphertext
- receiver: untraceable - hidden in UTXO ciphertext
Flow
The following steps specify the flow of TRANSFERNFT.
Step 0
The UTXO represents a non-fungible EOSIO/Antelope asset to be transmitted.
Step 1
Create a new UTXO tuple representing the non-fungible asset at the receiving address with new randomness:
- Diversifier index of the receiving ZEOS wallet address
- Public key of the receiving ZEOS wallet address
- Choose a random value
- Choose a random value
- Choose a random value
Step 2
Calculate the diversified transmission keys of all ZEOS wallet addresses involved (see section 5.4.1.6 of the Zcash Protocol Specification):
Step 3
Calculate the Commitment of the two UTXOs and :
Step 4
Calculate the of the Commitment Tree based on the sister path of .
Step 5
Calculate the Nullifier of :
Step 6
Choose a random value and calculate the Spend Authority for this action (see section 4.17.4 of the Zcash Protocol Specification):
- Choose a random value
where is the Spend Validating Key of which is part of the Full Viewing Key.
Step 7
Set the private inputs of the arithmetic circuit:
- sister path of in the Commitment Tree
- leaf index of in the Commitment Tree
- Spend Validating Key of which is part of the Full Viewing Key
- Nullifier Deriving Key of which is part of the Full Viewing Key
- Randomness of which is part of the Full Viewing Key
Step 8
Set the public inputs of the arithmetic circuit:
Step 9
Generate a proof of knowledge of satisfying arguments so that
The pair is the zk-SNARK which attests to knowledge of private inputs without revealing them.
Step 10
Generate UTXO ciphertext of for the receiver of the UTXOs (see section 4.19.1 of the Zcash Protocol Specification)
Step 11
Execute the TRANSFERNFT action of the ZEOS smart contract. This action takes the following arguments:
- : The zero knowledge proof of satisfying arguments
- : The public inputs of the zero knowledge proof
- : The UTXO ciphertext which is transmitted to the receiver of
The ZEOS smart contract then performs the following checks:
- Is the zero knowledge proof valid?
- Is the NFT flag set ()?
Step 12
If , the ZEOS smart contract performs the following operations:
- Add , the nullifier of to the Nullifier Set
- Add , the note commitment of , to the next free leaf of the Commitment Tree
- Add the new root of the Commitment Tree to the Commitment Tree Root Set
- Add ciphertext to the Transmitted UTXO Ciphertext List
If , cancel execution.
BURNFT

Burns (part of) a fungible UTXO. This effectively moves a certain balance of a fungible asset from a private ZEOS wallet to a transparent EOSIO/Antelope account. For this operation the entire ZEOS Action Circuit is used since one existing UTXO is being spent, part of its balance is publicly revealed and one new UTXO is being created by this action.
Privacy Implications
This action provides only limited privacy protection:
- sender: untraceable - hidden in zk-SNARK
- asset: traceable - quantity of the asset's smart contract's transfer action
- memo: traceable - memo of the asset's smart contract's transfer action
- receiver: traceable - the receiver's EOSIO/Antelope account
Flow
The following steps specify the flow of BURNFT.
Step 0
The UTXO represents an amount of a fungible EOSIO/Antelope asset from which a certain (partial) is to be transmitted to the EOSIO/Antelope account . Therefore the following must apply: .
Step 1
Create a new UTXO tuple representing the 'change' to of :
- Diversifier index of the sending¹ ZEOS wallet address
- Public key of the sending¹ ZEOS wallet address
- Choose a random value
- Choose a random value
- Choose a random value
¹ Note that it is also possible to choose a third wallet address for instead of the sender's address.
Step 2
Calculate the diversified transmission keys of all ZEOS wallet addresses involved (see section 5.4.1.6 of the Zcash Protocol Specification):
Step 3
Calculate the Commitment of the two UTXOs and :
Step 4
Calculate the of the Commitment Tree based on the sister path of .
Step 5
Calculate the Nullifier of :
Step 6
Choose a random value and calculate the Spend Authority for this action (see section 4.17.4 of the Zcash Protocol Specification):
- Choose a random value
where is the Spend Validating Key of which is part of the Full Viewing Key.
Step 7
Set the private inputs of the arithmetic circuit:
- sister path of in the Commitment Tree
- leaf index of in the Commitment Tree
- Spend Validating Key of which is part of the Full Viewing Key
- Nullifier Deriving Key of which is part of the Full Viewing Key
- Randomness of which is part of the Full Viewing Key
Step 8
Set the public inputs of the arithmetic circuit:
Step 9
Generate a proof of knowledge of satisfying arguments so that
The pair is the zk-SNARK which attests to knowledge of private inputs without revealing them.
Step 10
Generate UTXO ciphertext of for the receiver of the UTXO (see section 4.19.1 of the Zcash Protocol Specification)
Generate an additional UTXO ciphertext of which is burned and therefore has the BURN flag set. This ciphertext is created only for the sender's wallet transaction history to detect burned UTXOs when scanning the ZEOS smart contract's state.
Step 11
Execute the BURNFT action of the ZEOS smart contract. This action takes the following arguments:
- : The zero knowledge proof of satisfying arguments
- : The public inputs of the zero knowledge proof
- : The UTXO ciphertext which is transmitted to the receiver of
- : The UTXO ciphertext which indicates the 'burned'
The ZEOS smart contract then performs the following checks:
- Is the zero knowledge proof valid?
- Is the NFT flag unset ()?
Step 12
If , the ZEOS smart contract performs the following operations:
- Add , the nullifier of to the Nullifier Set
- Add , the note commitment of , to the next free leaf of the Commitment Tree
- Add the new root of the Commitment Tree to the Commitment Tree Root Set
- Add ciphertext to the Transmitted UTXO Ciphertext List
- Add ciphertext to the Transmitted UTXO Ciphertext List
- Transfer of asset represented by into the EOSIO/Antelope account .
If , cancel execution.
BURNFT2

Burns a fungible UTXO. This effectively moves the entire amount of a fungible UTXO from a private ZEOS wallet into two different transparent EOSIO/Antelope accounts. For this operation the entire ZEOS Action Circuit is used since one existing UTXO is being spent and its amount publicly revealed by this action.
Privacy Implications
This action provides only limited privacy protection:
- sender: untraceable - hidden in zk-SNARK
- asset: traceable - quantity of the asset's smart contract's transfer actions
- memo: traceable - memo of the asset's smart contract's transfer actions
- receiver: traceable - the receiver's EOSIO/Antelope accounts
Flow
The following steps specify the flow of BURNFT2.
Step 0
The UTXO represents an amount of a fungible EOSIO/Antelope asset from which a certain (partial) is to be transmitted to the EOSIO/Antelope and the remaining is to be transmitted to the EOSIO/Antelope . Therefore the following must apply: .
Step 1
Calculate the diversified transmission keys of all ZEOS wallet addresses involved (see section 5.4.1.6 of the Zcash Protocol Specification):
Step 2
Calculate the Commitment of the UTXO :
Step 3
Calculate the of the Commitment Tree based on the sister path of .
Step 4
Calculate the Nullifier of :
Step 5
Choose a random value and calculate the Spend Authority for this action (see section 4.17.4 of the Zcash Protocol Specification):
- Choose a random value
where is the Spend Validating Key of which is part of the Full Viewing Key.
Step 6
Set the private inputs of the arithmetic circuit:
- sister path of in the Commitment Tree
- leaf index of in the Commitment Tree
- Spend Validating Key of which is part of the Full Viewing Key
- Nullifier Deriving Key of which is part of the Full Viewing Key
- Randomness of which is part of the Full Viewing Key
Step 7
Set the public inputs of the arithmetic circuit:
Step 8
Generate a proof of knowledge of satisfying arguments so that
The pair is the zk-SNARK which attests to knowledge of private inputs without revealing them.
Step 9
Generate UTXO ciphertexts of and of which are burned and therefore have the BURN flag set. These ciphertexts are created only for the sender's wallet transaction history to detect burned UTXOs when scanning the ZEOS smart contract's state (see section 4.19.1 of the Zcash Protocol Specification).
Step 10
Execute the BURNFT2 action of the ZEOS smart contract. This action takes the following arguments:
- : The zero knowledge proof of satisfying arguments
- : The public inputs of the zero knowledge proof
- : The UTXO ciphertext which indicates the 'burned'
- : The UTXO ciphertext which indicates the 'burned'
The ZEOS smart contract then performs the following checks:
- Is the zero knowledge proof valid?
- Is the NFT flag unset ()?
Step 11
If , the ZEOS smart contract performs the following operations:
- Add , the nullifier of to the Nullifier Set
- Add ciphertext to the Transmitted UTXO Ciphertext List
- Add ciphertext to the Transmitted UTXO Ciphertext List
- Transfer of asset represented by into the EOSIO/Antelope account .
- Transfer of asset represented by into the EOSIO/Antelope account .
If , cancel execution.
BURNNFT

Burns a non-fungible UTXO. This effectively moves a non-fungible asset from a private ZEOS wallet to a transparent EOSIO/Antelope account. For this operation only part A and B of the ZEOS Action Circuit is used since one existing UTXO is being spent and is publicly revealed by this action.
Privacy Implications
This action provides only limited privacy protection:
- sender: untraceable - hidden in zk-SNARK
- asset: traceable - asset id of the asset's smart contract's transfer¹ action
- memo: traceable - memo of the asset's smart contract's transfer¹ action
- receiver: traceable - the receiver's EOSIO/Antelope account
¹ refers to the transfer action of the AtomicAssets smart contract
Flow
The following steps specify the flow of BURNNFT.
Step 0
The UTXO represents a non-fungible EOSIO/Antelope asset which is to be transmitted to the EOSIO/Antelope account .
Step 1
Calculate the diversified transmission keys of all ZEOS wallet addresses involved (see section 5.4.1.6 of the Zcash Protocol Specification):
Step 2
Calculate the Commitment of the UTXO :
Step 3
Calculate the of the Commitment Tree based on the sister path of .
Step 4
Calculate the Nullifier of :
Step 5
Choose a random value and calculate the Spend Authority for this action (see section 4.17.4 of the Zcash Protocol Specification):
- Choose a random value
where is the Spend Validating Key of which is part of the Full Viewing Key.
Step 6
Set the private inputs of the arithmetic circuit:
- sister path of in the Commitment Tree
- leaf index of in the Commitment Tree
- Spend Validating Key of which is part of the Full Viewing Key
- Nullifier Deriving Key of which is part of the Full Viewing Key
- Randomness of which is part of the Full Viewing Key
Step 7
Set the public inputs of the arithmetic circuit:
Step 8
Generate a proof of knowledge of satisfying arguments so that
The pair is the zk-SNARK which attests to knowledge of private inputs without revealing them.
Step 9
Generate UTXO ciphertext of which is burned and therefore has the BURN flag set. This ciphertext is created only for the sender's wallet transaction history to detect burned UTXOs when scanning the ZEOS smart contract's state (see section 4.19.1 of the Zcash Protocol Specification).
Step 10
Execute the BURNNFT action of the ZEOS smart contract. This action takes the following arguments:
- : The zero knowledge proof of satisfying arguments
- : The public inputs of the zero knowledge proof
- : The UTXO ciphertext which indicates the 'burned'
The ZEOS smart contract then performs the following checks:
- Is the zero knowledge proof valid?
- Is the NFT flag set ()?
Step 11
If , the ZEOS smart contract performs the following operations:
- Add , the nullifier of to the Nullifier Set
- Add ciphertext to the Transmitted UTXO Ciphertext List
- Transfer the non-fungible asset represented by into the EOSIO/Antelope account .
If , cancel execution.
BURNAT
Burns an authenticator token. The zero knowledge proof required for this action is identical to the one of the MINTNFT action. The only difference is that it does not create a new leaf in the merkle tree. Instead it just proves knowledge of the spend authority of the address and of the secret randomness the authenticator token was minted with. This gives access to privately withdraw assets from a third party smart contract where they have been deposited using the authenticator token's note commitment value as an identifier.
Privacy Implications
No assets are being moved by this action.
Flow
The following steps specify the flow of BURNAT.
Step 0
The UTXO is an authenticator token representing a permission.
Step 1
Calculate the diversified transmission key of the ZEOS wallet address owning the permission (see section 5.4.1.6 of the Zcash Protocol Specification):
Step 2
Calculate the of (see UTXO Commitment):
Step 3
Set the private inputs of the arithmetic circuit:
Step 4
Set the public inputs of the arithmetic circuit:
Step 5
Generate a proof of knowledge of satisfying arguments so that
The pair is the zk-SNARK which attests to knowledge of private inputs without revealing them.
Step 6
Generate UTXO ciphertext of which is burned and therefore has the BURN flag set. This ciphertext is created only for the sender's wallet transaction history to detect burned UTXOs when scanning the ZEOS smart contract's state (see section 4.19.1 of the Zcash Protocol Specification).
Step 7
Execute the BURNAT action of the ZEOS smart contract. This action takes the following arguments:
- : The zero knowledge proof of satisfying arguments
- : The public inputs of the zero knowledge proof
- : The UTXO ciphertext which indicates the 'burned' permission
The ZEOS smart contract then performs the following checks:
- Is the zero knowledge proof valid?
- Is the NFT flag set ()?
Step 8
If , the ZEOS smart contract performs the following operations:
- Add ciphertext to the Transmitted UTXO Ciphertext List
If , cancel execution.
Private Deposits and Withdrawals
Based on the ZActions defined in the previous section it is now possible to specify a concept for private interactions with EOSIO/Antelope smart contracts using the ZEOS wallet instead of EOSIO/Antelope accounts. The following sections specify the processes of private token deposits and withdrawals involving a third party smart contract. Check out pages 22 to 26 of the ZEOS Whitepaper for a first introduction of the concept.
Private Deposit of fungible tokens
The way token deposits work on EOSIO/Antelope blockchains is by using notification handler. This is a callback function which is executed by the receiving smart contract on incoming token transfers. Within the handler function, it can then be determined from which EOS account the transaction was sent and thus the token amount can be assigned to the corresponding user.
In order to do such a deposit privately using a ZEOS wallet the BURNFT (or BURNNFT for NFTs) action is being utilized. Executing this action effectively moves an asset from a private ZEOS wallet (i.e. from custody of the ZEOS smart contract) into an EOSIO/Antelope account specified by the user. If the receiving account has a smart contract with a notification handler deployed it will be able to handle the deposit in almost the same way traditional deposits work.
Flow
The following steps describe the flow of actions for a private deposit into a third party smart contract on EOSIO/Antelope.
Step 0
The (non-)fungible asset to be deposited to the third party smart contract is being held in a private ZEOS wallet.
Step 1
The MINTAT action is executed to create a new authenticator token. The UTXO created by this action, , has the value . As specified in MINTAT the attribute is set to the EOSIO/Antelope account (code) of the third party smart contract.
Step 2
Encode the 32 bytes long value of the authenticator token as a string. Using a memory efficient encoding like base85 this will result in an ASCII string of exactly 40 bytes.
Step 3
The BURNFT (or BURNNFT) action is executed to move the (non-)fungible asset to be deposited from the private ZEOS wallet into the account of the third party smart contract. The 40 bytes long string-encoded value of the previously minted authenticator token is being used as a memo for the EOSIO/Antelope transfer action resulting from the BURNFT (BURNNFT) action.
Step 4
The notification handler of the receiving third party smart contract now needs to distinguish between incoming transfers from either the ZEOS smart contract or other EOSIO/Antelope accounts:
If the sending EOSIO/Antelope account is the ZEOS smart contract:
- Read the memo field and decode the first 40 bytes which contains to retrieve the value of the user's authenticator token:
- Use the lower 64 bits of as primary key to book the (non-)fungible asset to be deposited in a seperate multi-index table for private deposits (i.e. not the same table used for traditional token deposits via EOSIO/Antelope accounts)
Else:
- Book the deposited (non-)fungible asset using the EOSIO/Antelope account name in the conventional manner (traditional EOSIO/Antelope token deposit)
Note that for security reasons it is important to use two different multi-index tables: One for traditional token deposits (i.e. transparent EOSIO/Antelope deposits) and one for private token deposits. That is to prevent attackers from creating an EOSIO/Antelope account with the exact lower 64 Bits of an authenticator token's value and thus being able to illegitimately withdraw privately deposited assets.
Private Withdrawal of fungible tokens
In order to privately withdraw assets from a third party smart contract back into a private ZEOS wallet a private withdrawal action needs to be implemented by the third party smart contract. This action takes the following parameters:
- The public inputs of a BURNAT action (containing the value of the corresponding authenticator token )
- A zero knowledge proof of satisfying arguments to execute BURNAT
- The public inputs of a MINTFT or MINTNFT action (containing the necessary information (amount/id, symbol, code) of the corresponding asset to be withdrawn)
- A zero knowledge proof of satisfying arguments to execute MINTFT or MINTNFT action
Flow
The following steps describe the flow of actions for a private withdrawal from a third party smart contract on EOSIO/Antelope.
Step 0
The (non-)fungible asset to be withdrawn from the third party smart contract is being held in a multi-index table booked under the lower 64 bits of a value as primary key.
Step 1
The private withdrawal action of the third party smart contract is executed with the above listed parameters generated by the user.
Step 2
Within the private withdrawal action the third party contract performs the following steps:
- Check if matches the EOSIO/Antelope account name of the third party smart contract (i.e. was the authenticator token created for this contract?)
- Extract the lower 64 bits of the value of the authenticator token from the public inputs and use it as primary key to find the corresponding asset inside the multi-index table of privately deposited assets
- If key doesn't exist, cancel execution
- If key does exist, check if:
- matches the deposited asset's amount (or id in case of NFT)
- matches the deposited asset's symbol (or 0 in case of NFT)
- matches the deposited asset's smart contract code
- Execute the BURNAT action of the ZEOS smart contract as an inline action with to authorize the withdrawal (if it fails, execution cancels)
- Transfer the (non-)fungible asset to be withdrawn to the ZEOS smart contract (where it is stored in the asset buffer)
- Execute the MINTFT (or MINTNFT) action of the ZEOS smart contract as an inline action with to create the corresponding UTXO in the private ZEOS wallet of the user
Proof Bundling in EOSIO/Antelope Transactions
The Halo 2 proving system comes with a powerful feature called Recursive Proof Composition. It allows for multiple zero knowledge proofs of the same arithmetic circuit to be bundled together into a single proof bundle. This feature of Halo 2 can be exploited to scale private transactions on EOSIO/Antelope blockchains.
Groth16 versus Halo2
Some notes on performance: The currently widely adopted Groth16 proving system is heavily optimized in regards to verification time and proof size which is both small and even constant for any zk-SNARK independent of the complexity of the underlying arithmetic circuit. This means that Groth16 proof verification time scales linearly with the number of proofs to be verified.
With Halo2's recursive proof composition, however, the verification time of multiple zk-SNARKs bundled together scales logarithmically with the number of proofs. While a single proof verification in Halo2 is more expensive than in Groth16, the overall performance of Halo2 can significantly increase and even beat Groth16 in case of multiple proofs being bundled and verified together. This is even true for a single-threaded Halo2 verifier but in contrast to Groth16 the proof verification in Halo2 can even be performed multi-threaded.
For a more detailed explanation of the benefits of Halo2 checkout the Technical Explainer. For use cases of complex privacy transactions on EOSIO/Antelope containing multiple zk-SNARKs refer to the ZEOS Whitepaper pages 34 to 37.
Concept for EOSIO/Antelope Transactions
In order to exploit the recursive proof composition feature of Halo2 for EOSIO/Antelope transactions the following concept is introduced.
The Problem
The ZEOS privacy actions (aka ZActions) all depend on a tuple of zero knowledge proof and corresponding public inputs in order to be executed. Each zaction is then executed as a seperate EOSIO/Antelope action in which the corresponding proof is verified independently from all other ZActions of the same EOSIO/Antelope transaction. In order to take advantage of the recursive proof composition feature of Halo2, the proofs of all ZActions of the same EOSIO/Antelope transaction are bundled into a single proof bundle which then depends on the set of public inputs of all ZActions in that particular transaction.
where:
- is the composition function to bundle multiple zero knowledge proofs
- is the number of ZActions to be executed
- is the set of private inputs
- is the set of public inputs
But if the proofs of all ZActions of an EOSIO/Antelope transaction were to be composed into a single proof bundle, there is also only one single EOSIO/Antelope action to verify this proof bundle and thus validates all subsequent ZActions. But if that is the case, then all subsequent ZActions need to be able to reference their public inputs from this one EOSIO/Antelope action that verifies the bundle.
After all, it can't be trusted that the user executes the subsequent ZActions with the same public inputs that were used to verify the proof bundle.
Since EOSIO/Antelope actions are executed independently from each other, there is no (direct) way, to enforce the execution of two independent EOSIO/Antelope actions with the same input parameters. To prevent fraudulent actions (like verifying the proof bundle using one set of public inputs but then execute the subsequent ZActions using a different set of public inputs ) it needs to be guaranteed by the ZEOS smart contract that the proof bundle is verified with the very same public inputs that are used to execute the subsequent ZActions.
The following graphics illustrate the problem. The first picture shows a transaction execution without proof composition where each zaction verifies its own zero knowledge proof using the corresponding public inputs .
The second picture shows the same transaction using proof composition where the first action verifies the zero knowledge proof bundle using the valid set of public inputs but the subsequent ZActions are executed using a different, fraudulent set of public inputs . This way an attacker is able to defraud the system by executing invalid ZActions.
The Solution
It needs to be guaranteed that subsequent ZActions which are validated by the proof bundle are executed with the exact same public inputs as the proof bundle was verified with. In order to achieve this, the following concept is introduced.
The exec action
The ZEOS smart contract features an 'exec' action which takes a sequence of ZActions (i.e. a ztransaction) as parameter and executes them one by one. This EOSIO/Antelope action is only executable by the ZEOS smart contract itself, to ensure it is only executed if the proof bundle that validates the sequence of zactions is verified within the same transaction.
Third party smart contracts
As described in Private Deposits & Withdrawals all third party smart contracts which choose to implement private withdrawals depend on executing ZActions as EOSIO/Antelope inline actions. To be able to support those third party private withdrawal actions they need to follow certain design guidelines which make them look very similar to the exec action of the ZEOS smart contract:
- They take a sequence of ZActions as their first action parameter
- They are only executable by the ZEOS smart contract
- They need to be whitelisted by the ZEOS smart contract
The begin and step actions
Two more actions are introduced as part of the ZEOS smart contract:
- begin: This action takes a Halo 2 proof bundle and a sequence of packed EOSIO/Antelope actions. The packed actions within the sequence need to be whitelisted by the ZEOS smart contract, may contain a sequence of ZActions as their first action parameter and are ready to be executed, after the proof bundle has been verified. In addition to that the begin action takes a sequence of encrypted UTXO ciphertexts, , which is neglected here.
- step: Each 'step' action is a placeholder which executes exactly one of the packed EOSIO/Antelope actions from the sequence of actions passed to 'begin'. It is a wrapper that executes an EOSIO/Antelope action and, if applicable, the 'exec' action right afterwards with the sequence of ZActions which is serialized as the first parameter of the preceding EOSIO/Antelope action. The number of 'step' actions in the EOSIO/Antelop transaction must equal the number of EOSIO/Antelope actions in the sequence passed to 'begin'.
The following example is given to illustrate the concept:

begin
This action initiates a sequence of step actions.
Parameters
- : A proof bundle validating all zactions within this EOSIO/Antelope transaction
- : A sequence of EOSIO/Antelope actions to be executed within this EOSIO/Antelope transaction
- : A sequence of encrypted UTXO ciphertexts to be added to the Transmitted UTXO Ciphertext List as part of the In-band secret distribution of UTXOs
Flow
The following steps specify the flow of 'begin'.
Step 0
The 'begin' action is called as part of an EOSIO/Antelope transaction.
Step 1
The currently executing EOSIO/Antelope transaction is scanned by looping through all actions contained in this transaction. The following checks are performed:
- There is only one 'begin' action within the entire EOSIO/Antelope transaction
- The number of 'step' actions within the transaction equals the number of actions contained in the sequence passed as parameter
- The sequence of 'step' actions is continuous and succeed the 'begin' action
Step 2
Loop through all actions within the sequence and perform the following steps:
- Is this action whitelisted by the ZEOS smart contract? If : cancel execution
- Does this action depend on a sequence of zactions? If :
- Loop through all zactions of this action and collect public inputs by adding them to the set of public inputs
Step 3
Verify the Halo2 proof bundle with the set of public inputs collected in the previous step. If proof verification fails, cancel execution.
Step 4
Check if the number of encrypted UTXO ciphertexts in matches the number of expected UTXO ciphertexts based on the zactions in this transaction. If so, add the sequence to the global set of Transmitted UTXO Ciphertext List.
Step 5
Add all UTXO commitments and contained in the set of public inputs which are not related to authenticator tokens to the Commitment Tree. Add the new root of the tree to the Commitment Tree Root Set.
Step 6
Copy the sequence of EOSIO/Antelope actions into the ZEOS smart contract's transaction buffer. This is a singleton which buffers the sequence of actions, \mathsf{tx}, for actual execution in the upcoming 'step' actions.
step
This action is just a placeholder for one of the EOSIO/Antelope actions in the sequence of actions passed as a parameter to the preceeding begin action. It is part of a sequence of 'step' actions succeeding the execution of a begin action. It is used to execute exactly one of the actions in the transaction buffer of the ZEOS smart contract.
Parameters
None.
Flow
The following steps specify the flow of 'step'.
Step 0
The 'step' action is called as part of an EOSIO/Antelope transaction.
Step 1
Check if the transaction buffer of the ZEOS smart contract is initialized. If not, 'begin' hasn't been executed and execution must cancel.
Step 2
Pop the next EOSIO/Antelope action to be executed from the transaction buffer and execute it as an inline action.
Step 3
If the EOSIO/Antelope action which was just executed in the previous step is not the exec action of the ZEOS smart contract itself, check if it depends on a sequence of zactions. If so, execute exec with the sequence of zactions of the actual EOSIO/Antelope action (executed in step 2) as an inline action.
The third party smart contract action from step 2 can thus be assured that the sequence of zactions (which was just processed by the third party contract in step 2) is actually executed right after the action itself.
Step 4
If this was the last action in the transaction buffer, reset the buffer.
exec
This action executes a sequence of ZActions.
Parameters
- : A sequence of zaction tuples
Flow
The following steps specify the flow of 'exec'.
Step 0
The 'exec' action is always executed as an inline action of a step action which always follows the execution of a begin, where the proof bundle is verified which validates all subsequent zactions.
Step 1
Let loop through the sequence of zactions and check :
-
if MINTFT execute step 9 and step 10 of the corresponding action flow:
- The zero knowledge proof is already verified by the begin action
- Do the public inputs of this zaction represent the correct asset which is held in the asset buffer? I.e. are the following statements true:
- Check if the NFT flag is unset ()?
- The UTXO commitment is already added to the Commitment Tree by the begin action
- The new root of the Commitment Tree is already added to the Commitment Tree Root Set by the begin action
- The UTXO ciphertext is already added to the Transmitted UTXO Ciphertext List by the begin action
-
if MINTNFT execute step 9 and step 10 of the corresponding action flow:
- The zero knowledge proof is already verified by the begin action
- Do the public inputs of this zaction represent the correct asset which is held in the asset buffer? I.e. are the following statements true:
- Check if the NFT flag is set ()?
- The UTXO commitment is already added to the Commitment Tree by the begin action
- The new root of the Commitment Tree is already added to the Commitment Tree Root Set by the begin action
- The UTXO ciphertext is already added to the Transmitted UTXO Ciphertext List by the begin action
-
if MINTAT execute step 4 of the corresponding action flow:
- The UTXO ciphertext is already added to the Transmitted UTXO Ciphertext List by the begin action
-
if TRANSFERFT execute step 11 and step 12 of the corresponding action flow:
- The zero knowledge proof is already verified by the begin action
- Check if the NFT flag is unset ()?
- Add to the Nullifier Set
- The UTXO commitments are already added to the Commitment Tree by the begin action
- The new root of the Commitment Tree is already added to the Commitment Tree Root Set by the begin action
- The UTXO ciphertexts are already added to the Transmitted UTXO Ciphertext List by the begin action
-
if TRANSFERNFT execute step 11 and step 12 of the corresponding action flow:
- The zero knowledge proof is already verified by the begin action
- Check if the NFT flag is set ()?
- Add to the Nullifier Set
- The UTXO commitment is already added to the Commitment Tree by the begin action
- The new root of the Commitment Tree is already added to the Commitment Tree Root Set by the begin action
- The UTXO ciphertext is already added to the Transmitted UTXO Ciphertext List by the begin action
-
if BURNFT execute step 11 and step 12 of the corresponding action flow:
- The zero knowledge proof is already verified by the begin action
- Check if the NFT flag is unset ()?
- Add to the Nullifier Set
- The UTXO commitment is already added to the Commitment Tree by the begin action
- The new root of the Commitment Tree is already added to the Commitment Tree Root Set by the begin action
- The UTXO ciphertext is already added to the Transmitted UTXO Ciphertext List by the begin action
- Continue to loop with and perform above steps to sum up all the amounts while:
- equals BURNFT
- remains the same (i.e. same token symbol)
- remains the same (i.e. same token contract)
- Transfer the sum of all amounts as one EOSIO/Antelope 'transfer' action of smart contract using as the memo into the EOSIO/Antelope account . The loop ensures that burning multiple UTXOs of the same currency is combined into one EOSIO/Antelope transfer action.
-
if BURNFT2 execute step 10 and step 11 of the corresponding action flow:
- TODO: Not yet finalized.
-
if BURNNFT execute step 10 and step 11 of the corresponding action flow:
- The zero knowledge proof is already verified by the begin action
- Check if the NFT flag is set ()?
- Add to the Nullifier Set
- The UTXO ciphertext is already added to the Transmitted UTXO Ciphertext List by the begin action
- Transfer the non-fungible asset (, , ) into the EOSIO/Antelope account .
-
if BURNAT execute step 7 and step 8 of the corresponding action flow:
- The zero knowledge proof is already verified by the begin action
- Check if the NFT flag is set ()?
- The UTXO ciphertext is already added to the Transmitted UTXO Ciphertext List by the begin action
ZEOS Application
The ZEOS Application implements the ZEOS Orchard Shielded Protocol on the EOS public blockchain. At it's core there is the ZEOS smart contract maintaining all Global Datasets and a library for browser-based user interfaces which allows for easy creation of private transactions according to the ZEOS Orchard Shielded Protocol.
Technology
In order to be able to implement the protocol on the EOS public blockchain services of LiquidApps are being utilized as it turned out that the zk-SNARK verification is too computationally expensive to be executed directly on chain (at this point in time). To work around this current limitation the vcpu service of LiquidApps is utilized to execute zero knowledge proof verification off chain simultaneously on a network of DSPs. The same applies to the merkle path calculations of the Commitment Tree which are too expensive to be executed on chain as well because of the Sinsemilla hash function. For the Commitment Tree itself the vram service is utilized to save RAM costs. Since the merkle tree is kind of a 'write-only' data structure of which only the roots are required to verify its state on chain the using vram to store all merkle nodes is a perfect fit for this data structure. The same applies to the Transmitted UTXO Ciphertext List which is used for the In-band secret distribution of UTXOs. For the verifying key as well as all zero knowledge proofs the LiquidStorage service is utilized. It is possible to use cheap off chain storage for both, verifying key and proofs, since the proof verification is performed off chain using vcpu.
ZEOS Orchard Library
The ZEOS Orchard Library is a fork of the Zcash Orchard Library. It is a Rust implementation encapsulating the entire application logic required in order to create private transactions according to the ZEOS Orchard Shielded Protocol. At its core is the implementation of the ZEOS Action Circuit from which proving and verifying keys are derived. The proving key is required by client applications to create zero knowledge proofs for private transactions while the verifying key is required by the ZEOS smart contract in order to verify zero knowledge proofs of private transactions. In addition to all the logic related to zk-SNARKs it provides functions to scan the global UTXO state of the ZEOS smart contract and synchronize client applications local state. In contrast to the original Zcash library the ZEOS Orchard library is build to be used in Javascript browser applications compiled to WebAssembly.
- Github Repository: ZEOS Orchard
ZEOS smart contract
Tables
vk
Maps LiquidStorage IPFS URIs of verifying keys to EOSIO/Antelope names.
type
eosio::multi_index
struct
id: eosio::name
vk: std::string
notes
Represents Transmitted UTXO Ciphertext List which holds all UTXO ciphertexts that are transmitted via In-band secret distribution of UTXOs.
type
dapp::advanced_multi_index
struct
id: uint64_t
block_number: uint64_t
leaf_index: uint64_t
encrypted_note: TransmittedNoteCiphertext