Introduction to EY Nightfall — How does it make token transactions private?
The lack of privacy on Ethereum is a well known problem. Any data published on Ethereum is visible to everyone, implying the lack of confidentiality. The address that published this data is also visible. The address by itself does not reveal identity unless this can be linked back to the owner, implying only pseudo-anonymity.
As a result, despite its promise of immutability and decentralisation, Ethereum is still not a viable platform for enterprise applications due to lack of privacy. EY, driven by the vision of its Global Blockchain Head Paul Brody, believes that enterprises can achieve previously unattainable network effects by building applications on public blockchains but lack of privacy makes that impractical. Solving this was the motivation for the creation of Nightfall.
What is Nightfall?
Nightfall enables transfer of fungible and non-fungible tokens between parties such that the value/token id of the token remains confidential and the recipient remains anonymous. It uses zk-SNARKS. I won’t go into the details of how zk-SNARKs work. For anyone interested to know more about zk-SNARKs, this and this are a good place to start.
Nightfall currently uses ERC-20 token standard for fungible tokens and ERC-721 token standard for non-fungible tokens.
At its core, Nightfall can be subdivided in to 6 sub-protocols
- Mint ERC-20 Token Commitment
- Transfer ERC-20 Token Commitment
- Burn ERC-20 Token Commitment
- Mint ERC-721 Token Commitment
- Transfer ERC-721 Token Commitment
- Burn ERC-721 Token Commitment
Mint an ERC-20 or ERC-721 Token Commitment:
Mint converts a publicly visible ERC-20 (fungible) or ERC-721 (non-fungible) token into a token commitment that holds similar value or token id as that of the token and the public key of the intended commitment owner. A commitment is a cryptographic primitive that binds the value held within while also hiding it. Confidentiality of value and recipient is attained in this manner.
How to mint?
When Alice wants to mint a token commitment of a public ERC-20 or ERC-721 token that she owns, she generates a zk-SNARK proof π on her offchain server using public inputs and private inputs (witness). She then submits it to the shield contract, a π that says,
- she created a commitment
Z_A
by hashing value/token id of tokenα
, public key of intended commitment ownerpk_A
and saltσ
(to provide commitment uniqueness),
Inputs for proof generation (done off chain) :* Witness (secret inputs) - pk_A, σ
* Public inputs - α, Z_A
The shield contract on receipt of the mint transaction,
verifies the proof by calling verify function in the verifier contract along with the public inputs and only on successful verification,
- calls transfer function of ERC-20 or ERC-721 contract to transfer token value/token id
α
from Alice to the address of the shield contract, and - adds the commitment
Z_A
to its commitments merkle tree
Inputs for proof verification (done on chain) : * Public inputs - α, Z_A
What everybody on the blockchain sees?
That Alice minted a token commitment Z_A
holding a value/token id α.
Transfer an ERC-20 or ERC-721 Token Commitment:
Transfer enables the transfer of a token commitment between two parties by nullifying the initial commitment(s) and creating new commitment(s) that holds the same token value(s)/token id(s) as well as the public key of the new intended commitment owner(s).
First, I will explain ERC-20 transfer as it is relatively more complex. ERC-721 is straight forward to understand after.
How to transfer an ERC-20 Commitment?
Alice currently owns two commitments Z’_A
and Z’’_A
that hold 10α
and 5α
respectively and she wants to transfer 12α
to Bob. She generates a zk-SNARK proof π on her off-chain server and submits it to the shield contract from a one off Ethereum address, a π that says,
- she knows the secret inputs of two token commitments
Z’_A
andZ’’_A
by concatenating and hashing the values10α and 5α
, her public keypk_A
, and saltσ_1
andσ_2
associated with the commitments, such that
2. that these token commitments exist in the commitments merkle tree. Showing where in the commitments merkle tree these commitments are present reveals which commitments are being used. Instead she proves that starting with each of the Z’_A
and Z’’_A
she can hash repeatedly (depth of tree -1 times) to produce the root of the commitments merkle tree, R
. She does this by repetitively concatenating with the sibling node at each level and hashing.
3. that she created nullifiers for Z’_A
and Z’’_A
using her secret key sk_A
and corresponding commitment salts σ_1
and σ_2
, such that
4. that the public key pk_A
used in token commitments Z’_A
and Z’’_A
is derived from the same secret key sk_A
used in their respective nullifiers, such that
5. that she created two new token commitments Z’’’_A
and Z_B
for her and Bob using their public keys pk_A
and pk_B
respectively, such that
6. that values of commitments nullified is equal to the values of new commitments created, 10α + 5α = 3α + 12α
7. that the most significant bits of each of 10α, 5α, 3α, 12α
are all zero. This prevents the output values 3α
and 12α
from exceeding the maximum bit-lengths accepted by the circuit (overflow check).
Inputs for proof generation (done off chain) :* Witness (secret inputs) - 10α, 5α, 3α, 12α, pk_A, pk_B, σ_1, σ_2, σ_3, σ_4, sk_A, Z’_A, Z’’_A, path from commitment to root of the commitments merkle tree
* Public inputs - N_10α, N_5α, Z’’’_A, Z_B, R
The shield contract on receipt of the transfer transaction,
verifies the proof by calling verify function in the verifier contract along with the public inputs and only on successful verification,
- adds
N_10α
andN_5α
to its nullifiers list - adds
Z’’’_A
andZ_B
to its commitments merkle tree
Inputs for proof verification (done on chain) :* Public inputs - N_10α, N_5α, Z’’’_A, Z_B, R
Bob is informed of Z_B
, 12α
, and σ_4
through off chain communication.
What everybody on the blockchain sees?
that a new Etheruem address (that could probably be traced to Alice) has nullified two amongst many token commitments that it either minted or received. They can also see that it created two new token commitments Z’’’_A
and Z_B
. But they can’t see who owns these new token commitments or which two token commitments were spent. Also, the values spent and transferred are not revealed.
How to transfer an ERC-721 Commitment ?
When Alice wants to transfer α
held in token commitment Z_A
to Bob, she generates a zk-SNARK proof π on her offchain server and submits it to the shield contract from a one off Ethereum address, a π that says,
- she knows the inputs of a token commitment
Z_A
by concatenating and hashing the token idα
, her public keypk_A
, and saltσ
associated with the commitment, such that
2. that this token commitment exists in the commitments merkle tree, by showing that starting with Z_A
she can hash repeatedly (depth of tree -1 times) to produce the root of the commitments merkle tree, R
. She does this by repetitively concatenating with the sibling node at each level and hashing.
3. that she created a nullifier for Z_A
using her secret key sk_A
and corresponding commitment salt σ
, such that
4. that the public key pk_A
used in token commitment Z_A
is derived from the same secret key sk_A
used in its nullifier, such that
5. that she created a new token commitment Z_B
for Bob with the same token id α
, Bob;s public key pk_B
and salt σ’
, such that
Inputs for proof generation (done off chain) :* Witness (secret inputs) - α, pk_A, pk_B, σ, σ’, sk_A, Z_A, path from commitment to root of the commitments merkle tree
* Public inputs - N_α, Z_B, R
The shield contract on receipt of the transfer transaction,
verifies the proof by calling verify function in the verifier contract along with the public inputs and only on successful verification,
- adds
N_α
to its nullifiers list - adds
Z_B
to its commitments merkle tree
Inputs for proof verification (done on chain) :* Public inputs - N_α, Z_B, R
Bob is informed of Z_B
, α
, and σ’
through off chain communication.
What everybody on the blockchain sees?
that a new Etheruem address (that could probably be traced to Alice) has nullified a token commitment that it either minted or received. They can also see that it created a new token commitment Z_B
. But they can’t see who owns this new token commitment or which token commitment was spent. Also, the token id transferred is not revealed.
A one off Ethereum address is used to send transfer transactions to the shield contract. It provides only pseudo-anonymity for the sender of the transaction because the sender will need to pay for the transaction costs and it is likely that this gas could have been received from another of sender’s known address and can be traced to it.
Burn an ERC-20 or ERC-721 Token Commitment:
Burn converts an ERC-20 or ERC-721 token commitment into a publicly visible ERC-20 or ERC-721 token that holds similar value/token id respectively. This does the opposite in function to what a mint does.
How to burn?
When Alice wants to burn a token commitment Z_A
that she owns, she generates a zk-SNARK proof π on her offchain server and submits it to the shield contract, a π that says,
- she knows the inputs of a token commitment
Z_A
by concatenating and hashing the token idα
, her public keypk_A
, and saltσ
associated with the commitment, such that
2. that this token commitment exists in the commitments merkle tree, by showing that starting with Z_A
she can hash repeatedly (depth of tree -1 times) to produce the root of the commitments merkle tree, R
. She does this by repetitively concatenating with the sibling node at each level and hashing.
3. that she created a nullifier for Z_A
using her secret key sk_A
, such that
4. that the public key pk_A
used in token commitment Z_A
is derived from the same secret key sk_A
used in its nullifier, such that
5. that the address payTo_address
Alice wants to send the public ERC-20 or ERC-721 token to is equal to the private address payTo_address_private
, such that, payTo_address == payTo_address_private
. This check ensures that no miners/intermediaries, cannot replace the payTo_address
address with their own address
Inputs for proof generation (done off chain) :* Witness (secret inputs) - pk_A, σ, sk_A, Z_A, path from Z_A to root of merkle tree to hash to R, payTo_address_private
* Public inputs - α, N_α, R, payTo_address
The shield contract on receipt of the burn transaction,
verifies the proof by calling verify function in the verifier contract along with the public inputs and only on successful verification,
- adds
N_α
to its nullifiers list - calls transfer function of ERC-20 or ERC-721 contract, to transfer a value/token id of
α
from address of the shield contract to the intended target address specified by Alice (it could be Alice’s or somebody else’s),
Inputs for proof verification (done on chain) :* Public inputs - α, N_α, R
What everybody on the blockchain sees?
that a new Etheruem address (that could probably be traced to Alice) has nullified a token commitment that it either minted or received. They can also see that a value/token id of α
has been received by Alice/somebody else. But they can’t see which token commitment was spent.
Conclusion
Nightfall has been put in the public domain here on 31 May 2019 and its whitepaper can be found here.
It is an experimental solution and still being actively developed. This is not intended to be a production-ready application and do not recommend anyone to use it as such. The project hopes that people will feel motivated to contribute their own ideas and improvements.
Also, in the future, aggregation of proofs is being considered to bring down costs of verification.
Great to be part of Team Nightfall, with thanks to Paul Brody, Michael Connor, Duncan Westland, Quentin Drouot
Further Reading (if interested in knowing more about ZKP)
- https://zkp.science — A brilliant collection of papers, articles, libraries etc related to Zero Knowledge Proofs
- https://electriccoin.co/blog/snark-explain — 7 Part Series of how zkSNARKS work
- https://media.consensys.net/introduction-to-zksnarks-with-examples-3283b554fc3b — A good and quick introduction to zkSNARKS