Skip to main content
BaarkingDog

How Does Zcash Work?

How does Zcash work? Understanding Zerocash and Zcash
Article heading

Transactions in well-known cryptocurrencies such as Bitcoin and Ethereum are publicly recorded and permanently stored in the blockchain. While blockchain’s decentralization provides transparency, it can also compromise privacy. In a centralized model, privacy is easily achieved as long as the central party is trusted. In traditional banking services, a customer’s transaction is kept confidential unless there is a legal reason to disclose it. However, achieving both decentralization and privacy can be challenging because transactions must be visible to everyone for proper verification of transaction history.

One way to hide transaction history in coins is by using a mixing service. The mixing service receives coins from multiple users and combines them to obscure the destination of each transaction. Nevertheless, there are drawbacks: maintaining privacy can be challenging when there are few users. Additionally, the mixing service has access to all information and may potentially steal coins if it behaves maliciously.

Zerocash, proposed by Ben-Sasson et al., is a decentralized anonymous payment scheme and is considered one of the privacy coins. It allows a user to send coins without revealing the destination or amount in a decentralized manner using zero-knowledge proofs.

In this post, we will discuss the structure of Zerocash and its real-world applications, specifically Zcash.

Backgrounds

In this section, we will introduce basic cryptographic protocols which are building blocks of Zerocash.

Commitment Scheme

A commitment scheme is a cryptographic protocol that enables a party to commit to a message or value without revealing the value itself, and later reveal it in a verifiable manner. This is useful in scenarios where a party needs to prove that they made a commitment to a particular message at an earlier time without revealing the message until a later time.

Commitment schemes can be constructed using a cryptographic hash function. To commit a message mm, it is enough to reveal com=h(rm)\text{com} = h(r || m), where rr is a randomness, hh is a cryptographic hash function, and || denotes a concatenation operation. Due to preimage resistance property, it is impossible to recover mm from com\text{com}. We will denote a commitment for a message mm with a given randomness rr as comr(m)\text{com}_r(m).

Pseudorandom Function

A pseudorandom function (PRF) is a function that takes input xx and outputs a string that looks random. The PRF is initialized with a seed, which is generally public. It is deterministic, so if the seed and xx are the same, the output is always the same. Additionally, if xx is unknown, the output is unpredictable.

The PRF can also be constructed from a cryptographic hash function. For instance, PRFseed(x)=h(seedx)\text{PRF}_{\text{seed}}(x) = h(\text{seed} || x) is unpredictable.

Zero-Knowledge Proof and zk-SNARK

A zero-knowledge proof is a cryptographic technique that allows a prover to convince a verifier of the truth of a statement to a verifier without revealing any additional information beyond the truth of the statement. This technique enables the prover to exhibit knowledge of a confidential value to the verifier without disclosing that value or any related details. Zero-knowledge proofs must satisfy three properties:

  1. Completeness. If the statement is true, then an honest prover should be able to convince the verifier of its truth.
  2. Soundness. If the statement is false, then no cheating prover should be able to convince the verifier of its truth.
  3. Zero-knowledgeness. The proof should not reveal any additional information beyond the truth of the statement.

Intuitively, the prover can convince their knowledge of xx such that f(x)=yf(x) = y without revealing xx itself to the verifier.

zk-SNARK is a type of zero-knowledge–proof protocol that has one additional feature called succintness (i.e., proof size and verifier time are sublinear). Zerocash uses zk-SNARK for zero-knowledge proofs. Even without knowledge of how zk-SNARK works, one can understand the structure of Zerocash based on the aforementioned properties. Therefore, we will omit further details. Check Intro to Zero-Knowledge: A Guided Tour of the ZK Landscape, Part 1 in our blog if interested.

UTXO: Transactions in Bitcoin

To gain a better understanding of the Zerocash protocol, it is important to first examine how transactions work in Bitcoin. In Bitcoin, the amount of assets each user holds is managed in unspent transaction output (UTXO) model. In a typical banking system, each user has a balance and the amount of the balance increases or decreases depending on deposits/withdrawals. This model is called the account model. However, in the UTXO model, individual units of coins named as UTXOs are transferred between users.

Assume that Alice and Bob earn 6.25 BTC from mining, respectively. Then each mining result is recorded in transaction. In other words, Alice and Bob each have 6.25 BTC UTXO. UTXO lists for each user are shown below:

  • Alice : 6.25 BTC,
  • Bob : 6.25 BTC.

They wish to send 1 BTC to Charles. Then, Alice’s transaction input is the 6.25 BTC UTXO she mined, and outputs are 1 BTC UTXO for Charles and 5.25 BTC UTXO for Alice. She retrieves a change by sending the remaining coin to herself. Now, 6.25 BTC transaction output is spent and a new 5.25 BTC UTXO is created. Sames goes for Bob. UTXO lists for each user are shown below:

  • Alice : 5.25 BTC,
  • Bob : 5.25 BTC,
  • Charles : 1 BTC, 1 BTC.

Finally, Charles wants to send 1.5 BTC to Daniel. Then Charles’s new transaction inputs are two 1 BTC UTXO, and the outputs are that 1.5 BTC UTXO belong to Daniel and 0.5 BTC UTXO belong to Charles. UTXO lists for each user are shown below:

  • Alice : 5.25 BTC,
  • Bob : 5.25 BTC,
  • Charles : 0.5 BTC,
  • Daniel : 1.5 BTC.
Graphical overview of transactions example.

A transaction is considered valid if 1) UTXO inputs have not been used before, 2) the sum of inputs and outputs are equal (transaction fee is ignored for simplicity), and 3) the transaction is generated by senders. The first two conditions can be easily verified. To verify the third condition, a digital signature for the transaction is embedded in the transaction. If the UTXO inputs belong to more than one owner, then multi-signature is embedded.

Zerocash Protocol

A transaction in the Zerocash protocol is based on a transaction in Bitcoin. To clarify, since the Zerocash protocol also uses the UTXO model, we only need to fulfill the three aforementioned conditions. However, it may be impossible to satisfy all of these conditions when the destination and even the amount are hidden. For instance, it may seem strange to verify a sum of inputs and a sum of outputs are equal without revealing the amounts. Nevertheless, this is made possible through the use of zero-knowledge proofs.

It has been mentioned that a prover can convince a verifier of their knowledge of xx such that f(x)=yf(x) = y without revealing xx itself to the verifier using zero-knowledge proof. Therefore, once we represent all conditions of a transaction in equations, the sender can prove the validity of their transaction, revealing sensitive information. We will discuss how to set the conditions into equations. The simplification below will give us a bigger picture of the protocol, and then we’ll break it down to understand the details of a Zerocash transaction.

Protocol (Simplified)

This is the simplified version of the Zerocash protocol introduced in the Zerocash paper. In this protocol, the value of the coin is fixed. If Alice wants to mint a new coin cc, Alice does the following:

  1. Alice samples a random serial number snsn and a trapdoor rr, computes a coin commitment cm=comr(sn)cm = \text{com}_r(sn). Now a new coin c:=(r,sn,cm)c := (r,sn,cm) is defined.
  2. A corresponding mint transaction txMint\text{tx}_\text{Mint} containing cmcm is sent to the ledger. Note that neither snsn nor rr will be sent.

If an Alice wants to spend cc, Alice does the following:

  1. Alice posts transaction txSpend\text{tx}_\text{Spend} containing the coin’s serial number snsn and a zk-SNARK proof π\pi of her knowledge of rr such that comr(sn)\text{com}_r(sn) appears in the list of coin commitments. The transaction is valid if snsn does not appear in past spending transactions.

Maintaining the list of coin commitments in a Merkle tree enables efficient membership checks while ensuring anonymity through the zero-knowledgeness of zk-SNARK. Users can prove their knowledge of rr without revealing which coin corresponds to comr(sn)\text{com}_r(sn) in the list of coin commitments.

Protocol (Exact)

While the simplified version helps to understand the basic concepts of Zerocash, it has several limitations. Firstly, assuming that one coin corresponds to one dollar, if a user wants to transfer 100 dollars, they must post 100 transactions, which is inefficient and leaks the amount. Secondly, transferring a coin is not naturally defined. In this procedure, Alice can prove that coin cc was minted by herself. However, if Alice wishes to transfer a cc to Bob, the only possible way is to inform Bob of rr and snsn. In this scenario, Alice may spend cc before Bob, and even if she does not, if Bob spends cc, Alice will become aware of it by examining the snsn in the transaction.

The exact protocol includes several methods to prevent these flaws. Let’s check out the protocol.

In the full protocol, each user generates addresses. As usual, address is a key pair (apk,ask)(a_{pk}, a_{sk}) where apka_{pk} is a public key and aska_{sk} is a private key. The key pair is generated by randomly sampling aska_{sk} first and setting apk:=PRFask(0)a_{pk} := \text{PRF}_{a_{sk}}(0). A user can generate any number of address key pairs.

Minting

To mint a new coin cc with value vv, Alice follows these steps:

  1. Alice samples ρ\rho and then the coin’s serial number sn:=PRFask(ρ)sn := \text{PRF}_{a_{sk}}(\rho).
  2. Alice computes k:=comr(apkρ)k := \text{com}_r(a_{pk} || \rho) for a random rr, then computes cm:=coms(vk)cm := \text{com}_s(v || k) for a random ss.
  3. A coin c:=(apk,v,ρ,r,s,cm)c := (a_{pk}, v, \rho, r, s, cm) and a mint transaction txMint:=(v,k,s,cm)\text{tx}_{\text{Mint}} := (v, k, s, cm) are generated.

Compared to the simplified protocol, the transaction includes the user’s public address. Additionally, the commitment process has become more complex. This nested structure allows anyone to verify that cmcm is a commitment for a coin value vv by checking that cm=coms(vk)cm = \text{com}_s(v || k) without zk-SNARK proofs, while the owner and serial number remain hidden.

Pouring

Coins are spent using the pour operation, which works similarly to the UTXO model. The operation takes a set of input coins to be consumed and pours their value into a set of fresh output coins.

The procedure for Alice, with address key pair (apk,ask)(a_{pk}, a_{sk}), consumes her coin

cold:=(apkold,vold,ρold,rold,sold,cmold)c^{\text{old}} := (a_{pk}^{\text{old}}, v^{\text{old}}, \rho^{\text{old}}, r^{\text{old}}, s^{\text{old}}, cm^{\text{old}})

and produces two new coins:

c1new:=(apk,1new,v1new,ρ1new,r1new,s1new,cm1new)c_1^{\text{new}} := (a_{pk,1}^{\text{new}}, v_1^{\text{new}}, \rho_1^{\text{new}}, r_1^{\text{new}}, s_1^{\text{new}}, cm_1^{\text{new}}) and c2new:=(apk,2new,v2new,ρ2new,r2new,s2new,cm2new)c_2^{\text{new}} := (a_{pk,2}^{\text{new}}, v_2^{\text{new}}, \rho_2^{\text{new}}, r_2^{\text{new}}, s_2^{\text{new}}, cm_2^{\text{new}})

The procedure is as follows.

  1. Alice samples ρinew\rho_i^{\text{new}} for i{1,2}i \in \{1,2\}.
  2. Alice computes kinew:=comrinew(apk,inewρinew)k_i^{\text{new}} := \text{com}_{r_i^{\text{new}}}(a_{pk,i}^{\text{new}} || \rho_i^{\text{new}}) for a random rinewr^{\text{new}}_i for i{1,2}i \in \{1,2\}.
  3. Alice computes cminew:=comsinew(vinewkinew)cm_i^{\text{new}} := \text{com}_{s_i^{\text{new}}}(v_i^{\text{new}} || k_i^{\text{new}}) for a random sinews_i^{\text{new}} for i{1,2}i \in \{1,2\}.
  4. Now two new coins c1new:=(apk,1new,v1new,ρ1new,r1new,s1new,cm1new)c_1^{\text{new}} := (a_{pk,1}^{\text{new}}, v_1^{\text{new}}, \rho_1^{\text{new}}, r_1^{\text{new}}, s_1^{\text{new}}, cm_1^{\text{new}}) and c2new:=(apk,2new,v2new,ρ2new,r2new,s2new,cm2new)c_2^{\text{new}} := (a_{pk,2}^{\text{new}}, v_2^{\text{new}}, \rho_2^{\text{new}}, r_2^{\text{new}}, s_2^{\text{new}}, cm_2^{\text{new}}) are produced. Alice produces a zk-SNARK proof πPour\pi_{\text{Pour}} of her knowledge of cold,c1new,c2newc^{\text{old}}, c_1^{\text{new}}, c_2^{\text{new}} and askolda_{sk}^{\text{old}} such that
    • The coins are well-formed: kold=comrold(apkold)k^{\text{old}}=\text{com}_{r^{\text{old}}}(a_{pk}^{\text{old}}), k1new=comr1new(apk,1new)k_1^{\text{new}}=\text{com}_{r_1^{\text{new}}}(a_{pk,1}^{\text{new}}), k2new=comr2new(apk,2new)k_2^{\text{new}}=\text{com}_{r_2^{\text{new}}}(a_{pk,2}^{\text{new}}), cmold=comsold(voldkold)cm^{\text{old}} = \text{com}_{s^{\text{old}}}(v^{\text{old}} || k^{\text{old}}), cm1new=coms1new(v1newk1new)cm_1^{\text{new}} = \text{com}_{s_1^{\text{new}}}(v_1^{\text{new}} || k_1^{\text{new}}), and cm2new=coms2new(v2newk2new)cm_2^{\text{new}} = \text{com}_{s_2^{\text{new}}}(v_2^{\text{new}} || k_2^{\text{new}}).
    • The address secret key matches the public key: apkold=PRFaskold(0)a_{pk}^{\text{old}} = \text{PRF}_{a_{sk}^{\text{old}}}(0).
    • The serial number is computed correctly: snold:=PRFaskold(ρold).sn^{\text{old}} := \text{PRF}_{a_{sk}^{\text{old}}}(\rho^{\text{old}}).
    • The coin commitment cmoldcm^{\text{old}} appears in the list of coin commitments.
    • The vaules add up: v1new+v2new=voldv_1^{\text{new}} + v_2^{\text{new}} = v^{\text{old}}.
  5. A resulting pour transaction txPour:=(snold,cm1new,cm2new,πPour)\text{tx}_{\text{Pour}} := (sn^{\text{old}}, \text{cm}_1^{\text{new}}, \text{cm}_2^{\text{new}}, \pi_{\text{Pour}}) is appended to the ledger. The transaction is valid if snoldsn^{\text{old}} has not appeared in past pour transactions.

The below illustration introduces a structure of the coin commitment.

Illustration of the structure of a coin commitment. pkencpk_{enc} refers receiver’s public key in public-key cryptosystem mentioned in point 1. Images from Zerocash paper.

The statements that zk-SNARK πPour\pi_{\text{Pour}} proves are quite complicated. It is recommended to check each statement carefully. To help the reader understand, here are some points:

  1. If Alice wants to send c1newc_1^\text{new} to Bob, she must first need to know Bob’s address public key and set apk,1newa_{pk,1}^{\text{new}} as Bob’s address public key. Moreover, she also needs to provide (v1new,ρ1new,r1new,s1new)(v_1^\text{new}, \rho_1^\text{new}, r_1^\text{new}, s_1^\text{new}). These can be transferred over a private channel or by preparing a public-key cryptosystem beforehand and adding ciphertext Enc(v1new,ρ1new,r1new,s1new)\text{Enc}(v_1^\text{new}, \rho_1^\text{new}, r_1^\text{new}, s_1^\text{new}) with Bob’s public key in transaction.
  2. Suppose (v1new,ρ1new,r1new,s1new)(v_1^\text{new}, \rho_1^\text{new}, r_1^\text{new}, s_1^\text{new}) are provided using public-key cryptosystem. Then in order to receive a coin, Bob should verify the entire pour transactions so that Enc(v1new,ρ1new,r1new,s1new)\text{Enc}(v_1^\text{new}, \rho_1^\text{new}, r_1^\text{new}, s_1^\text{new}) is decrypted with Bob’s own private key.
  3. If Alice does not know ask,1newa_{sk,1}^{\text{new}}, then Alice cannot generate valid πPour\pi_{\text{Pour}} to spend c1newc_1^{\text{new}}. In particular, it contradicts with the second statement, the address secret key matches the public key.
  4. Assume that c1newc_1^\text{new} belongs to Bob. When Bob spends c1newc_1^{\text{new}}, Alice cannot find it, because sn1new=PRFask,1new(ρ1new)sn_1^{\text{new}} = \text{PRF}_{a_{sk,1}^\text{new}}(\rho_1^\text{new}) is revealed in the transaction, but Alice does not know it.

Now that we understand the protocol, let’s check out a real-life implementation of Zerocash: Zcash.

Zcash: An Implementation of Zerocash

Zcash is an implementation of Zerocash. Zerocash was announced in 2014, and Zcash was launched in 2016. Since then, it has undergone continuous development. In essence, Zcash follows the approach introduced in Zerocash but with several changes made over the past decade. It has evolved from a simple proof-of-concept implementation to a vast ecosystem with a market capitalization of approximately 400 million dollars.

Zcash offers two types of address: z-addresses and t-addresses. Transactions between t-addresses are transparent, similar to Bitcoin transactions. Transactions between z-addresses are shielded, simliar to Zerocash transactions. It is also possible to send coins from a t-address to a z-address or from a z-address to t-address. In both cases, only the value is revealed on the t-address side.

Zcash has three shielded protocols named Sprout, Sapling, and Orchard. Sprout was the original protocol implemented in the launch of Zcash. Sapling, introduced in 2018, addressed the limitations of the Sprout protocol and offers faster transaction generation and verification. Orchard, the latest protocol upgrade activated in 2022, further enhances privacy and scalability. Orchard utilizes a new zk-SNARK called Halo2, which eliminates the need for a trusted setup. This is an improvement over BCTV14 in Sprout and Groth16 in Sapling. Currently, Sapling is the most widely used shielded protocol.

The amount of ZEC stored in each of Zcash’s three shielded pools since 2016. Images from ELECTRIC COIN CO.

For more information on the protocol’s structure, please refer to the Zcash Protocol Specification.

Attacks on Zcash

There are cases where cryptographic vulnerabilities in the Zcash protocol have been discovered and the protocol has been modified. We will discuss Faerie Gold attack and Internal hash collision attack, which are also mentioned in the protocol specification. Note that these vulnerabilities are already addressed by developers.

Faerie Gold Attack

In the Zerocash protocol, the coin serial number is determined by sn=PRFask(ρ)sn = \text{PRF}_{a_{sk}}(\rho) with arbitrary chosen ρ\rho. If Alice needs send two coins c1c_1 and c2c_2 to Bob but wants to do something maliciously, then she may choose the same ρ\rho for c1c_1 and c2c_2. Then serial numbers sn1sn_1 and sn2sn_2 are collided. Although both coins are valid, Bob can only use one of them because the serial number in the transaction is checked to see if it has appeared before.

To address this issue, Zcash ensures the uniqueness of ρ\rho by changing the specification. Instead of randomly choosing ρ\rho, it is derived from a PRF. The inputs of the PRF are a hash digest hSigh_{\text{Sig}}, which differ in each transaction, and the output order index ii. If c1c_1 and c2c_2 are generated in different transactions, hSigh_{\text{Sig}} values will differ, and if they are generated in the same transaction, the output order index ii will differ. Therefore ρ\rho will never collide.

Internal Hash Collision Attack

Recall the minting process with a new coin cc with value vv in Zerocash:

  1. Alice samples ρ\rho and then the coin’s serial number sn:=PRFask(ρ)sn := \text{PRF}_{a_{sk}}(\rho).
  2. Alice computes k:=comr(apkρ)k := \text{com}_r(a_{pk} || \rho) for a random rr, then computes cm:=coms(vk)cm := \text{com}_s(v || k) for a random ss.
  3. A coin c:=(apk,v,ρ,r,s,cm)c := (a_{pk}, v, \rho, r, s, cm) and a mint transaction txMint:=(v,k,s,cm)\text{tx}_{\text{Mint}} := (v, k, s, cm) are generated.

In the original Zcash design, commitment cmcm is derived as follows:

  1. InternalH=SHA256(apkρ) truncated to 128 bitsInternalH = \text{SHA256}(a_{pk} || \rho) \text{ truncated to 128 bits}
  2. k=SHA256(rInternalH)k = \text{SHA256}(r || InternalH)
  3. cm=SHA256(k0192v)cm = \text{SHA256}(k || 0^{192} || v)

Since InternalHInternalH is only 128-bits, an attacker can find (apk,ρ)(a_{pk}, \rho) and (apk,ρ)(a_{pk}', \rho') pairs that have the same InternalHInternalH with O(264)O(2^{64}) computations. Two coins derived from (apk,ρ)(a_{pk}, \rho) and (apk,ρ)(a_{pk}', \rho') looks different, as their serial numbers PRFask(ρ)\text{PRF}_{a_{sk}}(\rho) and PRFask(ρ)\text{PRF}_{a_{sk}'}(\rho') are different. Therefore an attacker can double-spend them by generating two different zk-SNARK proofs using (apk,ρ)(a_{pk}, \rho) and (apk,ρ)(a_{pk}', \rho').

To mitigate this, commitment is changed into cm=SHA256(0xB0apkvρr)cm = \text{SHA256}(\text{0xB0} || a_{pk} || v || \rho || r). With this fix, Zcash commits apk,v,ρa_{pk}, v, \rho and rr all at once, whereas Zerocash uses a nested commitment scheme where apka_{pk} and kk are committed first, then vv. The reason for the nested commitment scheme in Zerocash is to allow mint transactions to be publically verified without requiring zk-SNARK proofs. However, in Zcash, mint and pour transactions are combined and each transfer always uses a zk-SNARK proof. Therefore Zcash does not require the nesting.

Conclusion

This post discusses Zerocash and its use in Zcash, a cryptocurrency. Zerocash is a protocol that enhances privacy and anonymity in cryptocurrencies, addressing security and privacy concerns. It uses advanced cryptographic techniques, such as zk-SNARKs, to prove transaction validity while anonymizing the sender, receiver, and transaction amount. This feature enables users to preserve the transparency and integrity of the blockchain while concealing the content of their transactions. Moreover, from a researcher’s perspective, it is interesting to investigate the protocol further to identify potential vulnerabilities, such as the Faerie Gold attack.

About Us

Zellic specializes in securing emerging technologies. Our security researchers have uncovered vulnerabilities in the most valuable targets, from Fortune 500s to DeFi giants.

Developers, founders, and investors trust our security assessments to ship quickly, confidently, and without critical vulnerabilities. With our background in real-world offensive security research, we find what others miss.

Contact us for an audit that’s better than the rest. Real audits, not rubber stamps.