Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
172ed5a
add a skeleton for new zeropool docs
gnull Jun 30, 2023
f08c6b9
update zeropool overview
gnull Jul 3, 2023
7a75379
add merkle tree illustration
gnull Jul 11, 2023
f8e2cbb
use png instead of svg for tikz diagrams
gnull Jul 11, 2023
9d83a41
Background with an illustration
gnull Jul 11, 2023
d528ee2
edits of the background
gnull Jul 11, 2023
5d97231
edit the background diagram
gnull Jul 12, 2023
345d1f1
early draft of the transaction description
gnull Jul 12, 2023
2036cc6
add gitignore for temporary files generated by latex
gnull Jul 12, 2023
b2895d6
update
gnull Jul 19, 2023
9686b04
Changes to section 3
gnull Jul 21, 2023
011855f
bug in gitignore
gnull Jul 21, 2023
c458f1b
Update diagrams
gnull Jul 21, 2023
5ebb70f
update transaction description
gnull Jul 21, 2023
8800999
Update
gnull Jul 21, 2023
b7f8c90
Specify randomized mappings on keys diagram
gnull Jul 28, 2023
4e7986b
typos
gnull Jul 28, 2023
3aca9e3
update
gnull Jul 28, 2023
392fc0c
Make sure that spent offset is included in availabe area
gnull Jul 28, 2023
739d763
update
gnull Jul 28, 2023
7a482c7
small changes
gnull Jul 31, 2023
d81a945
pass over the overview
gnull Aug 2, 2023
6072160
update
gnull Aug 2, 2023
d93eaed
update
gnull Aug 2, 2023
60adf6f
more details on zkSNARK CS
gnull Feb 5, 2024
acd1109
pass over nullifiers and add some more info on transaction
gnull Feb 5, 2024
49b5696
update on tx description
gnull Feb 5, 2024
637a493
put detailed technical description into the high-level overview
gnull Feb 6, 2024
5c52325
order sections, add global intro with links to the subsections
gnull Feb 6, 2024
bce7d60
Update transaction description
gnull Feb 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/00-intro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Welcome

ZeroPool is multi-blockchain privacy engine, providing anonymous account based transactions for blockchains.

Contents:

- [ZeroPool](./zeropool/) section describes the smart-contract itself.
It starts from high-level, beginner-friendly overview and then proceeds into the technical details.

- [Fawkes-crypto](./fawkes-crypto/) section describes Rust EDSL we implemented for describing zkSNARK circuits.
77 changes: 77 additions & 0 deletions docs/01-zeropool/01-background.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Background

Here, we briefly overview the main cryptographic primitives ZeroPool is built
with.

## zkSNARK

We use zkSNARKs to enable a smart contract verify conditions on secret
user-supplied data without seeing it.

See [docs for fawkes-crypto library](/docs/fawkes-crypto/background) for a
high-level overview of what zkSNARKs can do.

## Merkle Tree

Merkle tree is a mechanism for cryptographically commiting to some values. It
allows efficiently revealing part of the commited values, and recalculating the
commitment when the values change.

Leaves in a Merkle tree contain hashes of the commited values $v_0$ …
$v_{n-1}$. And each inner (that is, non-leaf) node contains the hash of its two
children.

![Merkle tree illustration](diagrams/merkle-tree-illustration.png)

Let's look at the illustration of Merkle tree above (real hash values will be
much longer, this is only an illustration). Suppose you know that the hash of
the root `3cf03f`, but you don't know the whole tree.

- If I give you a **path** to the green leaf `6631e5` (path being the sequence
"left, right, right" that leads to that leaf from the root through the
green nodes), as well as the hashes of all the nodes (highlighted in green
and light yellow), you can verify that the leaf is indeed holding the
value `6631e5`. You do that by recalculating the hashes of all the nodes
along the path and, finally, verifying that the path starts with the
expected root hash. Note that you needed to see only $O(\log n)$ hash values
to verify this, and you didn't have to know all $O(n)$ tree nodes for this.

The path (sequence of left-right turns) to a node in a Merkle tree together
with the hash values of the nodes encountered on the way is called **Merkle
proof** for that node. Merkle proof is well-defined for all nodes in the
tree, not only leaves.

- If you're holding the root of a Merkle tree (call this "old tree"), and I
want to change exactly one leaf in it (producing "new tree"), I can show you

1. the Merkle proof of the leaf in the old tree,
2. the hash of the new tree's root,
3. the Merkle proof of the leaf in the new tree;

using this information you can verify that the proofs are correct, and check
that the unmodified nodes (yellow on the picture) are the same in both
proofs (ensuring that exactly one leaf was modified).

This also works with non-leaf nodes. In this case, the modification replaces
the subtree rooted in the corresponding inner node.

- If you encode each "left" turn on the path from root to a leaf as 0, and
each "right" turn as 1, and compile them into a binary number, that will
yield the **number** (or **index**) of a leaf (counting left to right).

For example, the left-most leaf on the illustration above will have path $0,
0, 0$ and number $000_2 = 0$; its closest neighbor on the right is $001_2 =
1$ and so on.

The naive way of proving the value of a leaf in Merkle tree discloses location
of that leaf, as well as the hash of the result. The same goes for modifying
a leaf — verifier who is checking the proofs gets to see which leaf was
modified.

With zkSNARKs we can overcome this and perform both operations privately. A
Verifier (for example, a smart-contract) can hold the root of a Merkle tree and
allow users to replace it with a new value, only if the user supplies a zkSNARK
proof that modifications satisfy some criteria (for example, that only one leaf
whose index is within a certain range was modified). We can prove Merkle proofs
within a zkSNARK proof keeping them private and having Verifier store only the
root hash of a (potentially huge) Merkle tree.
66 changes: 66 additions & 0 deletions docs/01-zeropool/02-overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Functionality

Every user in ZeroPool is associated with her private spending key
$\sigma$.

ZeroPool maintains a set of accounts and notes. Each account and each note
stores some amount of tokens specified by its balance. Each note belongs to
some account. A user Alice who owns an account can join a note which belongs
to this account (destroying the note and and transferring its balance to
the account), or create a new note (transferring some of account balance to
it). She may choose to make the created note owned by a different account, say,
Bob's — which Bob can later join with his account. This way, accounts store the
balance of users and notes enable that balance to be fragmented and transferred
between accounts.

In other words, ZeroPool provides the following actions to a user Alice:

- Creating a private ZeroPool account for spending key $\sigma$ chosen
randomly by her.

- Depositing tokens to a ZeroPool account $\sigma$ from a public account (on
the underlying blockchain).

- Creating notes that belong to another user Bob's account, and topping them
up with tokens from Alice's account $\sigma$.

- Joining the notes that belong to her account with it.

- Withdrawing the tokens from her ZeroPool account $\sigma$ back to a public
account (on the underlying blockchain).

In order to keep the action that Alice performs secret, we implement all five
using one single transaction type. Let $\sigma$ be Alice's spending key. Then
one ZeroPool transaction does the following:

1. consumes the existing account associated with spending key $\sigma$ and
`INPUT` number of notes belonging to it,

2. creates a new account associated with $\sigma$ and `OUTPUT` number of notes
which may belong to any (potentially different) accounts.

The consumed account and notes are called “input”, while the produced ones are
called “output” of the transaction. So the transaction always “overwrites” one
(input) account of the user with a new (output) one, optionally joining or
creating some notes in the process. In case input account equals to the special
zero value, the transaction will assume that output account is to be created
and initialized with zero balance. From this moment on, it can be used as input
to future transactions.

The transaction keeps private the account that was created or modified by the
transaction, as well as balances of all involved notes. We fix the numbers
of `INPUT` and `OUTPUT` notes to prevent leaking the actual number of notes
used. If the user wants to use less than `INPUT` or `OUTPUT` notes, she can pad
her desired note lists with special dummy zero values — the transaction will
recognize them and understand that they shouldn't be used.

The transaction reveals the difference between the total balance of input
account and its notes on one hand, and output account and notes on the other
hand. If the difference is negative (output is greater than input), this means
that the total number of tokens in the ZeroPool is going up and therefore the
transaction will expect the user to deposit the correct number of tokens to the
smart contract's public account (on the underlying blockchain). Symmetrically,
if the difference is positive, ZeroPool will allow the user to withdraw the
said difference of tokens from the smart-contract's account to a user specified
account. When the difference is zero, it means that the net balance of ZeroPool
hasn't changed and the transaction was only moving tokens within ZeroPool.
Loading