diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml new file mode 100644 index 00000000..3b722e76 --- /dev/null +++ b/.github/workflows/docs.yaml @@ -0,0 +1,61 @@ +name: Docs Site + +on: + push: + branches: + - main + paths: + - 'doc-site/docs/**' + pull_request: + paths: + - 'doc-site/docs/**' + release: + types: [released] +jobs: + build: + runs-on: ubuntu-latest + env: + LATEST_TAG: '' + permissions: + contents: write + steps: + - name: Checkout source + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Docs Deploy + run: | + git config --global user.name "GitHub Actions" + git config --global user.email "noreply@github.com" + + - name: Get the latest tag + run: | + git fetch --tags + latest_tag=$(git tag -l | sort -V | grep -v "rc" | tail -n 1) + echo "latest tag: $latest_tag" + echo "LATEST_TAG=$latest_tag" >> $GITHUB_ENV + + - name: Install docs dependencies + working-directory: doc-site + run: pip install -r requirements.txt + + - name: Update doc site for release + if: github.event.action == 'released' && github.ref_name != env.LATEST_TAG + working-directory: doc-site + run: mike deploy ${{ github.event.release.tag_name }} --push + + - name: Update doc site for latest release + if: github.event.action == 'released' && github.ref_name == env.LATEST_TAG + working-directory: doc-site + run: mike deploy ${{ github.event.release.tag_name }} latest -u --push + + - name: Update doc site for `main` branch + if: ${{ github.event_name == 'push' }} + working-directory: doc-site + run: mike deploy head --push + + - name: Test building the doc site but do not deploy it + if: ${{ github.event_name == 'pull_request' }} + working-directory: doc-site + run: mkdocs build diff --git a/.gitignore b/.gitignore index e43b0f98..71712d13 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .DS_Store +.vscode/launch.json \ No newline at end of file diff --git a/README.md b/README.md index bda8fe62..c78029c4 100644 --- a/README.md +++ b/README.md @@ -1,174 +1,8 @@ # Zeto - UTXO based privacy-preserving token toolkit using Zero Knowledge Proofs -This project hosts the multiple patterns to implement privacy preserving tokens on EVM. The patterns all share the same basic architectural concepts: +This project hosts the multiple patterns to implement privacy preserving tokens on EVM. -- **Transaction model**: the UTXO model is adopted instead of the account model, for better support of parallel processing. Due to the necessity of maintaining private states offchain in order to achieve privacy, the client must continuously keep their private states in sync with the smart contract. Using an account model makes it more difficult to achieve this because incoming transfers from other parties would invalidate an account's state, making the account owner unable to spend from its account unless the private state has been sync'ed again. Solutions to this issue, often referred to as front-running, typically involve a spending window with a pending queue, which result in limited parallel processing of transactions from the same spending account. With a UTXO model, each state is independent of the others, so parallel processing is better achieved. -- **Commitments**: each UTXO is tracked by the smart contract as a hash, or commitment, of the following components: value, salt and owner public key -- **Finality**: each transaction's validity is verified by the smart contract before allowing the proposed input UTXOs to be nullified and the output UTXOs to come into existence. In other words, this is not an optimistic design and as such does not rely on a multi-day challenge period before a transaction is finalized. Every transaction is immediately finalized once it's mined into a block. - -# Overview of how Zeto tokens work - -The following diagram illustrates the basics of Zeto tokens. - -![Zeto token basics](/resources/c-utxo-zkp-1.jpg) - -- Party A owns 3 Zeto tokens at the beginning: `#1, #2, #3`. The 3 tokens have been minted in the Zeto smart contract and represented by their commitments, or `hash(value, owner public key, salt)` - - As the owner of the tokens, party A also has access to the secrets that the commitments can be opened to, namely the value and salt. The secrets are represented as private states: `s1, s2, s3` - - How party A obtained the secrets for the owned tokens, is dependent on the specific Zeto implementation. It can be from offchain channels or from onchain (encrypted) data -- Party A sends transaction `Tx1` to transfer some value to party B. The transaction consumes 2 tokens (`#1, #2`) and produces 2 new tokens (`#4, #5`). `#5` is the value to be transferred to party B. `#4` is the remainder value that goes back to party A - - Even though party A knows the secrets of `#5`, they won't be able to spend the token because party A is not the owner of the token. Ownership verification is enforced by the Zeto smart contract when it verifies the zero knowledge proofs. Each ZKP circuit ensures that the sender's private key is used as a private input signal to derive the public key, which is then hashed to calculate the commitments -- Party B sends transaction `Tx2` to transfer some value to party C. This works the same as `Tx1` -- All parties get the commitments, `#1, #2, ... #7`, from the onchain events - -The above diagram illustrates that the secrets are transmitted from the sender to the receiver in an off-chain secure channel. Other means of sharing the secrets are avaiable in Zeto token implementations. For instance, the [Zeto_AnonEnc](./solidity/contracts/zeto_anon_enc.sol) implementation includes encrypted secrets in the transaction input, and emits an event with the encrypted values. The encrypted values can only be decrypted by the receiver. - -# Zeto fungible and non-fungible token implementations - -The various patterns in this project use Zero Knowledge Proofs (ZKP) to demonstrate the validity of the proposed transaction. There is no centralized party to trust as in the [Notary pattern](#enforce-token-transfer-policies-with-a-notary), which is not implemented in this project but discussed briefly below. - -Using ZKPs as validity proofs, each participant can independently submit transactions to the smart contract directly. As long as the participant is able to produce a valid proof, the transaction will be successfully verified and allowed to go through. - -This project includes multiple ZKP circuits to support various privacy levels with Zeto, as listed below. - -Performing key pair operations, such as deriving the public key from the private key, in the ZKP circuit requires using ZKP-friendly curves, for which we picked Babyjubjub instead of the regular Ethereum curve (secp256k1). - -Another implication to the usage of ZKPs as transaction validity proof and the usage of the Babyjubjub curve, is that the signer of the transaction, eg. `msg.sender`, no longer bears the same significance as in other token implementations such as ERC20, ERC721, where the signer's EVM account address holds the actual assets. In Zeto tokens, it's the Babyjubjub public keys that hold the entitlement to spend the tokens. In fact, the applications are encouraged to use a different signing key for each transaction, to avoid leaking transaction behaviors and breaking anonymity. - -## Zeto_Anon - -This is the simplest version of the ZKP circuit. Because the secrets required to open the commitment hashes, namely the output UTXO value and salt, are NOT encrypted and published as part of the transaction payload, using this version requires the secrets to be transmitted from the sender to the receiver in off-chain channels. - -The statements in the proof include: - -- each value in the output commitments must be a positive number in the range 0 ~ (2\*\*40 - 1) -- the sum of the input values match the sum of output values -- the hashes in the input and output match the `hash(value, salt, owner public key)` formula -- the sender possesses the private BabyJubjub key, whose public key is part of the pre-image of the input commitment hashes - -There is no history masking, meaning the associations between the consumed input UTXOs and the output UTXOs are in the clear. - -## Zeto_AnonEnc - -This verison of the ZKP circuit adds encryption that makes it possible to provide data availability onchain. The circuit uses the sender's private key and the receiver's public key to generate a shared secret with ECDH, which guarantees that the receiver will be able to decrypt the values. The encrypted values include the value and salt of the output UTXO for the receiver. With these values the receiver is guaranteed to be able to spend the UTXO sent to them. - -The statements in the proof include: - -- each value in the output commitments must be a positive number in the range 0 ~ (2\*\*40 - 1) -- the sum of the input values match the sum of output values -- the hashes in the input and output match the hash(value, salt, owner public key) formula -- the sender possesses the private BabyJubjub key, whose public key is part of the pre-image of the input commitment hashes -- the encrypted values in the transaction are derived from the receiver's UTXO value and encrypted with a shared secret using the ECDH protocol between a random private key and the receiver (this guarantees data availability for the receiver, because the public key for the random private key used by the sender is published in the transaction) - -There is no history masking, meaning the association between the consumed input UTXOs and the output UTXOs are in the clear. - -## Zeto_AnonNullifier - -To mask the association between the consumed UTXOs and the output UTXOs, we hide which UTXOs are being consumed by each transaction. - -To achieve this, we employ the usage of `nullifiers`. It's a unique hash derived from the unique commitment it consumes. For a UTXO commitment `hash(value, salt, owner public key)`, the nullifier is calculated as `hash(value, salt, owner private key)`. Only the owner of the commitment can generate the nullifier hash. Each transaction will record the nullifiers in the smart contract, to ensure that they don't get re-used (double spending). - -In order to prove that the UTXOs to be spent actually exist, we use a merkle tree proof inside the zero knowledge proof circuit. The merkle proof is validated against a merkle tree root that is maintained by the smart contract. The smart contract keeps track of all the new UTXOs in each transaction's output commitments array, and uses a merkle tree to calculate the root hash. Then the ZKP circuit can use a root hash as public input, to prove that the input commitments (UTXOs to be spent), which are private inputs to the circuit, are included in the merkle tree represented by the root. - -The end result is that, from the onchain data, no one can figure out which UTXOs have been spent, while double spending is prevented. - -The statements in the proof include: - -- each value in the output commitments must be a positive number in the range 0 ~ (2\*\*40 - 1) -- the sum of the nullified values match the sum of output values -- the hashes in the output match the hash(value, salt, owner public key) formula -- the sender possesses the private BabyJubjub key, whose public key is part of the pre-image of the input commitment hashes, which match the corresponding nullifiers -- the nullifiers represent input commitments that are included in a Sparse Merkle Tree represented by the root hash - -![History masking with nullifiers](/resources/c-utxo-zkp-2.jpg) - -## Zeto_AnonNullifierKyc - -The concept of "KYC with privacy" is introduced in this implementation pattern. - -How to enforce a policy of "all senders and receivers of a transaction must be in a KYC registry", while maintaining anomymity of the sender and the receiver? The solution is similar to how nullifiers are supported, via merkle tree proofs. - -The implementation of this pattern maintains a `KYC registry` in the smart contract as a Sparse Merkle Tree. The registry is maintained by a designated authority, and includes the public keys of entities that have cleared the KYC process. Each transaction must demonstrate that the public keys of the sender and the receivers are included in the KYC merkle tree, by generating a merkle proof and using it as a private input to the ZKP circuit. - -The statements in the proof include: - -- each value in the output commitments must be a positive number in the range 0 ~ (2\*\*40 - 1) -- the sum of the nullified values match the sum of output values -- the hashes in the output match the hash(value, salt, owner public key) formula -- the sender possesses the private BabyJubjub key, whose public key is part of the pre-image of the input commitment hashes, which match the corresponding nullifiers -- the nullifiers represent input commitments that are included in a Sparse Merkle Tree represented by the root hash -- the sender and receiver public keys are included in the Sparse Merkle Tree for the KYC registry, represented by the latest root hash known to the smart contract - -## Zeto_AnonEncNullifier - -This implementation adds encryption, as described in the section above for Zeto_AnonEnc, to the pattern Zeto_AnonNullifier above. - -The statements in the proof include: - -- each value in the output commitments must be a positive number in the range 0 ~ (2\*\*40 - 1) -- the sum of the nullified values match the sum of output values -- the hashes in the output match the hash(value, salt, owner public key) formula -- the sender possesses the private BabyJubjub key, whose public key is part of the pre-image of the input commitment hashes, which match the corresponding nullifiers -- the nullifiers represent input commitments that are included in a Sparse Merkle Tree represented by the root hash -- the encrypted values in the transaction are derived from the receiver's UTXO value and encrypted with a shared secret using the ECDH protocol between a random private key and the receiver (this guarantees data availability for the receiver, because the public key for the random private key used by the sender is published in the transaction) - -## Zeto_AnonEncNullifierKyc - -This implementation adds encryption, as described in the section above for Zeto_AnonEnc, to the pattern Zeto_AnonNullifierKyc above. - -The statements in the proof include: - -- each value in the output commitments must be a positive number in the range 0 ~ (2\*\*40 - 1) -- the sum of the nullified values match the sum of output values -- the hashes in the output match the hash(value, salt, owner public key) formula -- the sender possesses the private BabyJubjub key, whose public key is part of the pre-image of the input commitment hashes, which match the corresponding nullifiers -- the nullifiers represent input commitments that are included in a Sparse Merkle Tree represented by the root hash -- the sender and receiver public keys are included in the Sparse Merkle Tree for the KYC registry, represented by the latest root hash known to the smart contract -- the encrypted values in the transaction are derived from the receiver's UTXO value and encrypted with a shared secret using the ECDH protocol between a random private key and the receiver (this guarantees data availability for the receiver, because the public key for the random private key used by the sender is published in the transaction) - -## Zeto_AnonEncNullifierNonRepudiation - -The concept of "non-repudiation" is introduced in this implementation pattern. - -Since all onchain states are hashes, with ownership information for the assets hidden, it's possible that a participant can send a transaction but subsequently deny it. Because the transaction signer account no longer reflects the identity of the asset owner, as discussed above, it will be impossible to know who was the sender of a transaction from purely looking at the onchain data, which is exactly the point for Zeto's anonymity support. This gives a malicious party the ability to gain repudiation, or deny that they were responsible for a past transaction. - -This implementation pattern addresses that concern by encrypting the ownership information of each UTXO involved in a transaction with an authority's registered key. Only the designated authority will be able to decrypt the ownership information. The encryption is performed inside the ZKP circuit, thus guaranteeing that they are the actual owners of the UTXOs. - -The statements in the proof include: - -- each value in the output commitments must be a positive number in the range 0 ~ (2\*\*40 - 1) -- the sum of the nullified values match the sum of output values -- the hashes in the output match the hash(value, salt, owner public key) formula -- the sender possesses the private BabyJubjub key, whose public key is part of the pre-image of the input commitment hashes, which match the corresponding nullifiers -- the nullifiers represent input commitments that are included in a Sparse Merkle Tree represented by the root hash -- the encrypted values in the transaction contains cipher texts derived from the receiver's UTXO values and encrypted with a shared secret using the ECDH protocol between a random private key and the receiver (this guarantees data availability for the receiver, because the public key for the random private key used by the sender is published in the transaction) -- the encrypted values in the transaction contains cipher texts derived from the receiver's UTXO values and encrypted with a shared secret using the ECDH protocol between a random private key and the authority's public key - -## Zeto_NfAnon - -This implements a basic non-fungible token. - -For non-fungible tokens, the main concern with the transaction validity check is that the output UTXO contains the same secrets (id, uri) as the input UTXO, with only the ownership updated. - -The statements in the proof include: - -- the output UTXO hashes are based on the same `id, uri` as the input UTXO hashes -- the sender possesses the private BabyJubjub key, whose public key is part of the pre-image of the input commitment hashes - -## Zeto_NfAnonNullifier - -This implements a non-fungible token using nullifiers, thus hiding the spending graph. - -The statements in the proof include: - -- the output UTXO hashes are based on the same `id, uri` as the input UTXO hashes -- the sender possesses the private BabyJubjub key, whose public key is part of the pre-image of the input commitment hashes, which match the corresponding nullifiers -- the nullifiers represent input commitments that are included in a Sparse Merkle Tree represented by the root hash - -# Enforce token transfer policies with a Notary - -This pattern relies on a central party, called "Notary", that has access to the private states of all the parties in the system. This knowledge allows the Notary to check the validity of a proposed transaction, and enforce dynamic policies that would otherwise be difficult with some of the other approaches. Every transaction must be accompanied by a "notary certificate" that approve the proposed transaction. The certificate will be verified by the smart contract before allowing the transaction to go through. - -The project does not include an implementation of a notary based token transfer policy enforcement. +Refer to the [Zeto documentation](https://hyperledger-labs.github.io/zeto) for details. # Sub-projects diff --git a/doc-site/.devcontainer/Dockerfile b/doc-site/.devcontainer/Dockerfile new file mode 100644 index 00000000..156896f7 --- /dev/null +++ b/doc-site/.devcontainer/Dockerfile @@ -0,0 +1,3 @@ +# Specify version of MkDocs Material +FROM squidfunk/mkdocs-material:latest +RUN pip install mike \ No newline at end of file diff --git a/doc-site/.devcontainer/devcontainer.json b/doc-site/.devcontainer/devcontainer.json new file mode 100644 index 00000000..05068657 --- /dev/null +++ b/doc-site/.devcontainer/devcontainer.json @@ -0,0 +1,20 @@ +{ + "name": "MkDocs Material editor", + "dockerFile": "Dockerfile", + "workspaceMount": "source=${localWorkspaceFolder}/doc-site,target=/docs,type=bind", + "workspaceFolder": "/docs", + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.shell.linux": "/bin/sh" + }, + "extensions": [ + "ms-vscode-remote.remote-containers" + ] + } + }, + "forwardPorts": [ + 8001 + ], + "postStartCommand": "mkdocs serve -a localhost:8001", +} diff --git a/doc-site/.gitignore b/doc-site/.gitignore new file mode 100644 index 00000000..b25f8f90 --- /dev/null +++ b/doc-site/.gitignore @@ -0,0 +1,2 @@ +venv +site diff --git a/doc-site/README.md b/doc-site/README.md new file mode 100644 index 00000000..82523b9b --- /dev/null +++ b/doc-site/README.md @@ -0,0 +1,74 @@ +# Zeto Documentation Site + +This directory is based on the [Hyperledger documentation template](https://github.com/hyperledger-labs/documentation-template). The template utilizes MkDocs (documentation at [mkdocs.org](https://www.mkdocs.org)) and the theme Material for MkDocs (documentation at [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/)). Material adds a number of extra features to MkDocs, and Hyperledger repositories can take advantage of the theme's [Insiders](https://squidfunk.github.io/mkdocs-material/insiders/) capabilities. + +[Material for MkDocs]: https://squidfunk.github.io/mkdocs-material/ +[Mike]: https://github.com/jimporter/mike + +## Prerequisites + +To test the documents and update the published site, the following tools are needed: + +- A Bash shell +- git +- Python 3 +- The [Material for Mkdocs] theme. +- The [Mike] MkDocs plugin for publishing versions to gh-pages. + - Not used locally, but referenced in the `mkdocs.yml` file and needed for + deploying the site to gh-pages. + +### git + +`git` can be installed locally, as described in the [Install Git Guide from GitHub](https://github.com/git-guides/install-git). + +### Python 3 + +`Python 3` can be installed locally, as described in the [Python Getting Started guide](https://www.python.org/about/gettingstarted/). + +### Virtual environment + +It is recommended to install your Python dependencies in a virtual environment in case you have other conflicting Python installations on your machine. This also removes the need to install these packages globally on your computer. + +```bash +cd doc-site +python3 -m venv venv +source venv/bin/activate +``` + +### Mkdocs + +The Mkdocs-related items can be installed locally, as described in the [Material +for Mkdocs] installation instructions. The short, case-specific version of those +instructions follow: + +```bash +pip install -r requirements.txt +``` + +### Verify Setup + +To verify your setup, check that you can run `mkdocs` by running the command `mkdocs --help` to see the help text. + +## Useful MkDocs Commands + +The commands you will usually use with `mkdocs` are: + +- `mkdocs serve` - Start the live-reloading docs server. +- `mkdocs build` - Build the documentation site. +- `mkdocs -h` - Print help message and exit. + +## Adding Content + +The basic process for adding content to the site is: + +- Create a new markdown file under the `docs` folder +- Add the new file to the table of contents (`nav` section in the `mkdocs.yml` file) + +If you are using this as a template for creating your own documentation, please see [the instructions for customization](./docs/index.md). + +## Repository layout + + mkdocs.yml # The configuration file. + docs/ + index.md # The documentation homepage. + ... # Other markdown pages, images and other files. diff --git a/doc-site/docs/assets/paladin-icon-light.png b/doc-site/docs/assets/paladin-icon-light.png new file mode 100644 index 00000000..5222675c Binary files /dev/null and b/doc-site/docs/assets/paladin-icon-light.png differ diff --git a/doc-site/docs/assets/paladin-logo-dark.svg b/doc-site/docs/assets/paladin-logo-dark.svg new file mode 100644 index 00000000..d326b322 --- /dev/null +++ b/doc-site/docs/assets/paladin-logo-dark.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/doc-site/docs/assets/paladin-logo-light.svg b/doc-site/docs/assets/paladin-logo-light.svg new file mode 100644 index 00000000..d075c322 --- /dev/null +++ b/doc-site/docs/assets/paladin-logo-light.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/doc-site/docs/concepts/basics.md b/doc-site/docs/concepts/basics.md new file mode 100644 index 00000000..2760f2fa --- /dev/null +++ b/doc-site/docs/concepts/basics.md @@ -0,0 +1,34 @@ +# Basic Concepts + +Zeto is built on the following fundamental concepts. + +## Onchain state model: UTXO + +The smart contract for a token economy must maintain states onchain. There are two ways to model states: account vs. UTXO. + +- The account model is very popular in the existing token standards such as ERC20/721/1155 etc. Each account is an entry in a map that the smart contract maintains, with the value being the account's state such as current balance, or list of assets. A transaction updates the states of one or more accounts. +- The UTXO model works very differently, by creating states that are independent to each other. Each UTXO state is in one of two modes: unspent or spent. A transaction consumes unspent states, at which point they become spent, and produces new (unspent) states. Each UTXO state specifies the spending rules that must be satisfied when a transaction attempts to spend it. + +### UTXO for better parallel processing + +The UTXO model is adopted for Zeto instead of the account model, for better support of parallel processing. Due to the necessity of maintaining private states offchain in order to achieve privacy, the client must continuously keep their private states in sync with the smart contract. Using an account model makes it more difficult to achieve this because incoming transfers from other parties would invalidate an account's state, making the account owner unable to spend from its account unless the private state has been sync'ed again. Solutions to this issue, often referred to as front-running, typically involve a spending window with a pending queue. One example of this is [Zether](https://eprint.iacr.org/2019/191), which describes an `epoch` construct with pending transaction queues to address the front-running problem. Using epochs results in limited parallel processing of transactions from the same spending account. + +With a UTXO model, on the other hand, since each state is independent of the others, spending multiple UTXOs at the same time, even if they call come from the same owner, can be easily achieved. + +### UTXO for better anonymity + +With an account model, given that the states are mapped to the owning accounts in the onchain storage, there is an inherent challenge to anonymity, where checking which account was updated by a transaction would reveal the counterparties of a transaction. This could be mitigated by using anonymity sets, where more accounts besides the real sender and receiver of a transaction are updated with an encrypted zero value. However this increases the gas cost of transactions. + +With the UTXO model, since each UTXO state describes its own ownership, in a masked form (`hash(value, owner public key, salt)`), there is no revelation of the owning account when a UTXO is spent or produced. Furthermore, the entitlement to spend the private UTXO token is demonstrated by the ZK proof, rather than the transaction signer (aka `msg.sender`). This allows Zeto transactions to be signed by any accounts. Zeto clients are encouraged to use one-time signing keys to submit transactions, such as those from an [HD wallet](https://en.bitcoin.it/wiki/BIP_0032). + +## Commitments + +Each UTXO is tracked by the smart contract as a hash of the following components: value (for fungible Zeto) or token Id (for non-fungible Zeto), owner public key and salt. These are called commitments. They serve several important purposes: + +- Representing the existence of a Zeto token. A Zeto token's value is recognized only if it's commitment is known to the smart contract that maintains the token economy. +- Hiding the secret information about a Zeto token. The value or token Id and the ownership information are hidden behind the secure hash string, which can not be reverse-engineered back to the secerts. +- Acting as the public inputs for verifying ZK proofs. All Zeto transactions consume some existing UTXOs and produce some new UTXOs. For the smart contract to be convinced that a transaction is valid, a ZK proof must be provided that demonstrates a transaction proposal conforming to the policies of the specific token implementation. The commitment hashes are critical public inputs in almost all such ZK proof verifications. + +## Finality + +Each transaction's validity is verified by the smart contract before allowing the proposed input UTXOs to be nullified and the output UTXOs to come into existence. In other words, this is not an optimistic design and as such does not rely on a multi-day challenge period before a transaction is finalized. Every transaction is immediately finalized once it's mined into a block. diff --git a/doc-site/docs/concepts/how-zeto-works.md b/doc-site/docs/concepts/how-zeto-works.md new file mode 100644 index 00000000..5ae4d3c4 --- /dev/null +++ b/doc-site/docs/concepts/how-zeto-works.md @@ -0,0 +1,22 @@ +# How Zeto tokens work + +![Zeto overview](../images/overview.jpg) + +- Party A owns 3 Zeto tokens at the beginning: `#1, #2, #3`. The 3 tokens have been minted in the Zeto smart contract and represented by their commitments, aka `hash(value, owner public key, salt)` + - As the owner of the tokens, party A also has access to the secrets that the commitments can be opened to, namely the value and salt. The secrets are represented as private states: `s1, s2, s3` + - How party A obtained the secrets for the owned tokens, is dependent on the specific Zeto implementation. It can be from offchain channels or from onchain (encrypted) data +- Party A sends transaction `Tx1` to transfer some value to party B. The transaction consumes 2 tokens `#1, #2` and produces 2 new tokens `#4, #5`. `#5` is the value to be transferred to party B. `#4` is the remainder value that goes back to party A + - Even though party A knows the secrets of `#5`, they won't be able to spend the token because party A is not the owner of the token. + - Ownership verification is enforced by the Zeto smart contract when it verifies the zero knowledge proofs. Each ZKP circuit ensures that the sender's private key is used as a private input signal to derive the public key, which is then hashed to calculate the commitments +- Party B sends transaction `Tx2` to transfer some value to party C. This works the same as `Tx1` +- All parties get the commitments, `#1, #2, ... #7`, from the onchain events + +The above diagram illustrates that the secrets are transmitted from the sender to the receiver in an off-chain secure channel. Other means of sharing the secrets are avaiable in Zeto token implementations. For instance, the [Zeto_AnonEnc](https://github.com/hyperledger-labs/zeto/blob/main/solidity/contracts/zeto_anon_enc.sol) implementation includes encrypted secrets in the transaction input, and emits an event with the encrypted values. The encrypted values can only be decrypted by the receiver. + +## EC Cryptography + +Performing key pair operations, such as deriving the public key from the private key, in the ZKP circuit requires ZKP-friendly curves, for which we picked [Babyjubjub](https://docs.iden3.io/publications/pdfs/Baby-Jubjub.pdf) instead of the regular Ethereum curve (secp256k1). + +## Transaction Signing + +Another implication to the usage of ZKPs as transaction validity proof and the usage of the Babyjubjub curve, is that the signer of the transaction, eg. `msg.sender`, no longer bears the same significance as in other token implementations such as ERC20, ERC721, where the signer's EVM account address holds the actual assets. In Zeto tokens, it's the Babyjubjub public keys that hold the entitlement to spend the tokens. In fact, the applications are encouraged to use a different signing key for each transaction, to avoid leaking transaction behaviors and breaking anonymity. diff --git a/doc-site/docs/contributing/asking-a-question.md b/doc-site/docs/contributing/asking-a-question.md new file mode 100644 index 00000000..64826007 --- /dev/null +++ b/doc-site/docs/contributing/asking-a-question.md @@ -0,0 +1,18 @@ +# Asking a Question + +!!! tip + + * check the [FAQs](../faqs.md) to see if your question has already been asked. + * make sure you provide all relevant details. + * include information about what you have already tried. + * review [How to Ask Technical Questions to Get Quality Answers](https://opensource.com/life/16/10/how-ask-technical-questions) prior to asking your question. + +## Chat + +[Hyperledger’s Discord server](https://discord.gg/hyperledger) is the place to go for real-time chat about everything from quick help to involved discussions. + +For general Zeto discussions, join the Discord server and visit _Labs/Zeto_. + +## Mailing Lists + +The Paladin mailing list is hosted by the Hyperledger Foundation: https://lists.lfdecentralizedtrust.org. diff --git a/doc-site/docs/contributing/how-to-contribute.md b/doc-site/docs/contributing/how-to-contribute.md new file mode 100644 index 00000000..63cf427d --- /dev/null +++ b/doc-site/docs/contributing/how-to-contribute.md @@ -0,0 +1,68 @@ +# How to Contribute + +## Ways to Contribute + +Contributions from the development community help improve the capabilities of +Zeto. These contributions are the most effective way to +make a positive impact on the project. + +Ways you can contribute: + +- Bugs or issues: Report problems or defects found when working with the project (see [Reporting a Bug](./reporting-a-bug.md)) +- Core features and enhancements: Provide expanded capabilities or optimizations +- Documentation: Improve existing documentation or create new information +- Tests: Add functional, performance, or scalability tests + +Issues can be found in GitHub. Any unassigned items are probably still open. When in doubt, ask on Discord about a specific issue (see [Asking a Question](./asking-a-question.md)). We also use the #good-first-issue tag to represent issues that might be good for first timers. + +## The Commit Process + +Zeto is Apache 2.0 licensed and accepts contributions via GitHub pull requests. When contributing code, please follow these guidelines: + +- Fork the repository and make your changes in a feature branch +- Include unit and integration tests for any new features and updates to existing tests +- Ensure that the unit and integration tests run successfully prior to submitting the pull request. + +### Pull Request Guidelines + +A pull request can contain a single commit or multiple commits. The most +important guideline is that a single commit should map to a single fix or +enhancement. Here are some example scenarios: + +- If a pull request adds a feature but also fixes two bugs, the pull request should have three commits: one commit for the feature change and two commits for the bug fixes. +- If a PR is opened with five commits that contain changes to fix a single issue, the PR should be rebased to a single commit. +- If a PR is opened with several commits, where the first commit fixes one issue and the rest fix a separate issue, the PR should be rebased to two commits (one for each issue). + +!!! important +Your pull request should be rebased against the current master branch. Do not merge the current master branch in with your topic branch. Do not use the Update Branch button provided by GitHub on the pull request page. + +### Commit Messages + +Commit messages should follow common Git conventions, such as using the imperative mood, separate subject lines, and a line length of 72 characters. These rules are well documented in [Chris Beam's blog post](https://chris.beams.io/posts/git-commit/#seven-rules). + +### Signed-off-by + +Each commit must include a "Signed-off-by" line in the commit message (`git commit -s`). This sign-off indicates that you agree the commit satisfies the [Developer Certificate of Origin (DCO)](http://developercertificate.org/). + +### Commit Email Address + +Your commit email address must match your GitHub email address. For more information, see https://help.github.com/articles/setting-your-commit-email-address-in-git/ + +### Important GitHub Requirements + +A pull request cannot merged until it has passed these status checks: + +- The build must pass all checks +- The PR must be approved by at least two reviewers without any + outstanding requests for changes + +## Inclusive Language + +- Consider that users who will read the source code and documentation are from different background and cultures and that they have different preferences. +- Avoid potential offensive terms and, for instance, prefer "allow list and deny list" to "white list and black list". +- We believe that we all have a role to play to improve our world, and even if writing inclusive code and documentation might not look like a huge improvement, it's a first step in the right direction. +- We suggest to refer to [Microsoft bias free writing guidelines](https://learn.microsoft.com/en-us/style-guide/bias-free-communication) and [Google inclusive doc writing guide](https://developers.google.com/style/inclusive-documentation) as starting points. + +## Credits + +This document is based on [Hyperledger Sawtooth's Contributing documentation](https://github.com/hyperledger/sawtooth-docs/blob/main/community/contributing.md). diff --git a/doc-site/docs/contributing/reporting-a-bug.md b/doc-site/docs/contributing/reporting-a-bug.md new file mode 100644 index 00000000..e6793a66 --- /dev/null +++ b/doc-site/docs/contributing/reporting-a-bug.md @@ -0,0 +1,14 @@ +# Reporting a Bug + +To report a bug, submit an issue in our public [issue tracker]. + +When reporting an issue, please provide as much detail as possible about how to reproduce it. If possible, explain how to reproduce the issue. Details are very helpful. Please include the following information: + +- Operating system and version (if Mac, include the processor) +- Project version +- Environment details (virtual, physical, etc.) +- Steps to reproduce the issue +- Actual results +- Expected results + + [issue tracker]: https://github.com/hyperledger-labs/zeto/issues diff --git a/doc-site/docs/contributing/requesting-a-change.md b/doc-site/docs/contributing/requesting-a-change.md new file mode 100644 index 00000000..e052da28 --- /dev/null +++ b/doc-site/docs/contributing/requesting-a-change.md @@ -0,0 +1,158 @@ +# Requesting a Change + +Zeto is a powerful tool which serves a wide range of use cases. +Put yourself in our shoes – with a project of this size, it can be challenging +to maintain existing functionality while constantly adding new features at the +same time. We highly value every idea or contribution from our community, and +we kindly ask you to take the time to read the following guidelines before +submitting your change request in our public [issue tracker]. This will help us +better understand the proposed change, and how it will benefit the community. + +This guide is our best effort to explain the criteria and reasoning behind our +decisions when evaluating change requests and considering them for +implementation. + +[issue tracker]: https://github.com/hyperledger-labs/zeto/issues + +## Before creating an issue + +Before you invest your time to fill out and submit a change request, we kindly +ask you to do some preliminary work by answering some questions to determine if +your idea is a good fit and matches the project's philosophy and tone. + +**Please find answers to the following questions before creating an issue.** + +### It's not a bug, it's a feature + +Change requests are intended for suggesting minor adjustments, ideas for new +features, or to influence the project's direction and vision. It is important +to note that change requests are not intended for reporting bugs, as they're +missing essential information for debugging. + +If you want to report a bug, please refer to our [bug reporting guide] instead. + +[bug reporting guide]: reporting-a-bug.md + +### Source of inspiration + +If you have seen your idea implemented in similar project, make sure to collect enough information on its implementation before submitting, as this allows us to evaluate potential fit more quickly. Explain what you like and dislike about the implementation. + +### Benefit for the community + +Our [Discord server] is the best place to connect with our community. When +evaluating new ideas, it's essential to seek input from other users and consider +alternative viewpoints. This approach helps to implement new features in a way +that benefits a large number of users. + +[Discord server]: https://discord.gg/hyperledger + +## Issue template + +Now that you have taken the time to do the necessary preliminary work and ensure +that your idea meets our requirements, you are invited to create a change +request. The following guide will walk you through all necessary steps to help +you submit a comprehensive and useful issue: + +- [Title] +- [Context] optional +- [Description] +- [Related links] +- [Use cases] +- [Visuals] optional +- [Checklist] + + [Title]: #title + [Context]: #context + [Description]: #description + [Related links]: #related-links + [Use cases]: #use-cases + [Visuals]: #visuals + [Checklist]: #checklist + +### Title + +A good title is short and descriptive. It should be a one-sentence executive +summary of the idea, so the potential impact and benefit for the community can +be inferred from the title. + +### Context optional { #context } + +Before describing your idea, you can provide additional context for us to +understand what you are trying to achieve. Explain the circumstances +in which you're using Zeto, and what you _think_ might be +relevant. Don't write about the change request here. + +!!! success "Why we need this" +Some ideas might only benefit specific settings, environments or edge cases. With a little context, change requests can be prioritized more accurately. + +### Description + +Next, provide a detailed and clear description of your idea. Explain why your +idea is relevant to Zeto and must be implemented here, and not +in one of its dependencies. + +- **Explain the what, not the why** – don't explain + [the benefits of your idea][Use cases] here, we're getting there. + Focus on describing the proposed change request as precisely as possible. + +- **Keep it short and concise** – be brief and to the point when describing + your idea, there is no need to over-describe it. Maintainers and future + users will be grateful for having to read less. + +- **One idea at a time** – if you have multiple ideas that don't belong + together, please open separate change requests for each of those ideas. + +- :material-run-fast: **Stretch goal** – if you have a customization or another way to add the proposed change, you can help other users by sharing it here before we maintainers can add it to our code base. + +!!! success "Why we need this" +To understand and evaluate your proposed change, we need to have a clear understanding of your idea. By providing a detailed and precise description, you can help save you and us time spent discussing further clarification of your idea in the comments. + +### Related links + +Please provide any relevant links to issues, discussions, or documentation +sections related to your change request. If you (or someone else) already +discussed this idea with the community on our discussion board, please include +the link to the discussion as well. + +!!! success "Why we need this" +Related links help us gain a comprehensive understanding of your change request by providing additional context. Additionally, linking to previous issues and discussions allows us to quickly evaluate the feedback and input already provided by the community. + +### Use cases + +Explain how your change request would work from a user's +perspective – what's the expected impact and why does it not only benefit you, +but other users? How many of them? Furthermore, would it potentially break +existing functionality? + +!!! success "Why we need this" +Understanding the use cases and benefits of an idea is crucial in evaluating its potential impact and usefulness for the project and its users. This information helps us to understand the expected value of the idea and how it aligns with the goals of the project. + +### Visuals optional { #visuals } + +We now have a clear and detailed description of your idea, including information +on its potential use cases and relevant links for context. If you have any +visuals, such as sketches, screenshots, mockups, or external assets, you may +present them in this section. + +!!! tip +You can drag and drop the files here or include links to external assets. + +Additionally, if you have seen this change, feature, or improvement used +elsewhere, please provide an example by showcasing +it and describing how it was implemented and incorporated. + +!!! success "Why we need this" +Illustrations and visuals can help us maintainers better understand and envision your idea. Screenshots, sketches, or mockups can create an additional level of detail and clarity that text alone may not be able to convey. Also, seeing how your idea has been implemented in other projects can help us understand its potential impact and feasibility in Zeto, which helps us maintainers evaluate and triage change requests. + +### Checklist + +Thanks for following the change request guide and creating a high-quality +change request. This section ensures that you have read this guide and have +worked to your best knowledge to provide us with every piece of information to +review your idea for Zeto. + +**We'll take it from here.** + +## Credits + +This document is based on [Material for MkDocs Requesting a Change](https://github.com/squidfunk/mkdocs-material/blob/master/docs/contributing/requesting-a-change.md). diff --git a/doc-site/docs/faqs.md b/doc-site/docs/faqs.md new file mode 100644 index 00000000..dbba8748 --- /dev/null +++ b/doc-site/docs/faqs.md @@ -0,0 +1,48 @@ +# Frequently Asked Questions + +## Can I develop a self-custody wallet for Zeto tokens? + +Due to the usage of UTXOs for the onchain token commitments, a "wallet" for Zeto will be architecturally similar to a Bitcoin wallet. It must index the entire chain of blocks since the deployment of the Zeto contract, in order to discover all the Zeto tokens that belong to the user. Figuring out the user's balance is achieved by adding together the values of all the UTXOs owned by the account, meaning the `owner public key` part of the commitment hash matches the user's Babyjubjub public key. + +Spending Zeto tokens requires surveying the account's UTXOs and select the collection of tokens that makes the most sense for the requested amount. + +Finally the wallet must be able to generate the appropriate ZK proofs for the intended transactions to send to the Zeto smart contract. + +In summary, a Zeto wallet is a sophisticated software that will be more complex than typical self-custody wallets such as Metamask, which only needs to manage signing keys and make JSON-RPC calls against the target blockchain. Refer to an existing implementation of a Zeto client such as [Paladin](https://github.com/LF-Decentralized-Trust-labs/paladin) for details. + +## What "SDK" should I use in my application to work with Zeto tokens? + +As explained above, working with Zeto tokens requires a sophisticated client such as [Paladin](https://github.com/LF-Decentralized-Trust-labs/paladin). Even though the Zeto project provides a [go-sdk](https://github.com/hyperledger-labs/zeto/tree/main/go-sdk), to build a robust client for Zeto is a major engineering effort. Starting with Paladin is highly recommended instead of re-implementing one from scratch. + +## Why do I need Zeto tokens if I already get privacy with ZK rollups or Validium Layer 2's? + +It's a common misconception that ZK rollups (Linea, zkSync, Polygon zkEVM, etc.) provides privacy. As of this writing, all the ZK rollups except Aztec use Zero Knowledge Proofs for scalability rather than privacy. All the transaction data are public information in two contexts: + +- the L2 network is a transparent shared ledger, where all the transactions are broadcast to all the L2 nodes +- all the L2 transaction data are sent to the verifier contract in L1 + +Due to the above two aspects, ZK rollups provides no privacy over L2 transactions. + +What about [Validium L2's](https://ethereum.org/en/developers/docs/scaling/validium/)? They send hashes of the L2 transactions rather than the transaction data to L1, and uses DAC's (Data Availability Committees) to manage the transaction data. Even though there is no information exposure to L1, there is still no privacy due to the follow two aspects: + +- the L2 network is a transparent shared ledger, where all the transactions are broadcast to all the L2 nodes +- the DAC must make all the transaction data available to anyone who wants to ask for it, in order for a L2 user to generate a merkle proof to withdraw their assets from the Validium network's L1 contract + +In summary, even if a L2 network uses ZKP, there is no privacy over the transactions that are sent to the L2 network. + +## How is Zeto different than Aztec? + +As mentioned above, [Aztec](https://docs.aztec.network/) is a rollup L2 that offers privacy at the protocol level. Zeto shares some common designs with Aztec: + +- UTXOs: both Zeto and Aztec use UTXOs (Unspent Transaction Outputs) as the onchain state model +- private storage: client maintains private storage that persists secrets belonging to a user +- ZKP: zero knowledge proofs are used to ensure transactions are honestly constructed and state transitions are correct + +On the other hand, there are significant differences: + +- target EVM: Zeto is implemented on top of a vanilla EVM, making it compatible with any EVM blockchain including both L1's and L2's. On the other hand, Aztec is an L2 protocol and relies on a specialized EVM. So far Zeto has been tested on multiple EVM based L1 and L2 blockchains: + - Ethereum + - Polygon + - Linea + - Arbitrum +- ZK circuit design language: Zeto currently uses [circom](https://github.com/iden3/circomlib) which is the most widely used DSL for designing ZK circuits, and most widely supported by the different proving systems (groth16, plonk, fflonk, nova). To develop ZK circuits for Aztec, you use [Noir](https://noir-lang.org/docs). diff --git a/doc-site/docs/glossary.md b/doc-site/docs/glossary.md new file mode 100644 index 00000000..47d22af7 --- /dev/null +++ b/doc-site/docs/glossary.md @@ -0,0 +1,11 @@ +**UTXO** + +: Unspent Transaction Output. First pioneered by the Bitcoin network, the UTXO state model uses individual tokens similar to coins to represent states. Instead of maintaining a per-account state (such as ERC20 balances) as is in the account model, which is the other significant state model, UTXO states are independent to each other. Each UTXO defines its own spending rules that must be satisfied in order to spend. + +**Commitment** + +: Every UTXO state maintained by the Zeto smart contract is a representation of the secrets (value, ownership) behind the token. The form of representation is a secure cryptographic hash of the secrets. This is formally called a commitment because it "locks down" the secrets. Any attempts to change the secrets such as the value or the ownership of a token will result in a different hash. These commitments are critical in verifying ZKPs to process transactions. + +**Nullifier** + +: It's a special type of hash that is securely, and secretly, bound to an UTXO. It's sole purpose is to demonstrate that a UTXO has been spent, although which particular UTXO is known only to the owner. Only the owner of an UTXO can produce the proper nullifier for the UTXO, which can be proven with a ZKP. Nullifiers are used when history masking is required, so that no one other than the owner of a UTXO can find out which UTXO have been spent by a transaction. diff --git a/resources/c-utxo-zkp-2.jpg b/doc-site/docs/images/nullifiers.jpg similarity index 100% rename from resources/c-utxo-zkp-2.jpg rename to doc-site/docs/images/nullifiers.jpg diff --git a/doc-site/docs/images/overview.jpg b/doc-site/docs/images/overview.jpg new file mode 100644 index 00000000..cc5bc286 Binary files /dev/null and b/doc-site/docs/images/overview.jpg differ diff --git a/doc-site/docs/images/zeto-arch.svg b/doc-site/docs/images/zeto-arch.svg new file mode 100644 index 00000000..1fd9a624 --- /dev/null +++ b/doc-site/docs/images/zeto-arch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc-site/docs/implementations/anon.md b/doc-site/docs/implementations/anon.md new file mode 100644 index 00000000..ee40c4ea --- /dev/null +++ b/doc-site/docs/implementations/anon.md @@ -0,0 +1,16 @@ +# Zeto_Anon + +| Anonymity | History Masking | Encryption | KYC | Non-repudiation | Gas Cost (estimate) | +| ------------------ | --------------- | ---------- | --- | --------------- | ------------------- | +| :heavy_check_mark: | - | - | - | - | 326,583 | + +This is the simplest version of the ZKP circuit. Because the secrets required to open the commitment hashes, namely the output UTXO value and salt, are NOT encrypted and published as part of the transaction payload, using this version requires the secrets to be transmitted from the sender to the receiver in off-chain channels. + +The statements in the proof include: + +- each value in the output commitments must be a positive number in the range 0 ~ (2\*\*40 - 1) +- the sum of the input values match the sum of output values +- the hashes in the input and output match the hash(value, salt, owner public key) formula +- the sender possesses the private BabyJubjub key, whose public key is part of the pre-image of the input commitment hashes + +There is no history masking, meaning the associations between the consumed input UTXOs and the output UTXOs are in the clear. diff --git a/doc-site/docs/implementations/anon_enc.md b/doc-site/docs/implementations/anon_enc.md new file mode 100644 index 00000000..25f0aabb --- /dev/null +++ b/doc-site/docs/implementations/anon_enc.md @@ -0,0 +1,17 @@ +# Zeto_AnonEnc + +| Anonymity | History Masking | Encryption | KYC | Non-repudiation | Gas Cost (estimate) | +| ------------------ | --------------- | ------------------ | --- | --------------- | ------------------- | +| :heavy_check_mark: | - | :heavy_check_mark: | - | - | 425,338 | + +This verison of the ZKP circuit adds encryption that makes it possible to provide data availability onchain. The circuit uses the sender's private key and the receiver's public key to generate a shared secret with ECDH, which guarantees that the receiver will be able to decrypt the values. The encrypted values include the value and salt of the output UTXO for the receiver. With these values the receiver is guaranteed to be able to spend the UTXO sent to them. + +The statements in the proof include: + +- each value in the output commitments must be a positive number in the range 0 ~ (2\*\*40 - 1) +- the sum of the input values match the sum of output values +- the hashes in the input and output match the hash(value, salt, owner public key) formula +- the sender possesses the private BabyJubjub key, whose public key is part of the pre-image of the input commitment hashes +- the encrypted values in the transaction are derived from the receiver's UTXO value and encrypted with a shared secret using the ECDH protocol between a random private key and the receiver (this guarantees data availability for the receiver, because the public key for the random private key used by the sender is published in the transaction) + +There is no history masking, meaning the association between the consumed input UTXOs and the output UTXOs are in the clear. diff --git a/doc-site/docs/implementations/anon_enc_nullifier.md b/doc-site/docs/implementations/anon_enc_nullifier.md new file mode 100644 index 00000000..c9cc2137 --- /dev/null +++ b/doc-site/docs/implementations/anon_enc_nullifier.md @@ -0,0 +1,16 @@ +# Zeto_AnonEncNullifier + +| Anonymity | History Masking | Encryption | KYC | Non-repudiation | Gas Cost (estimate) | +| ------------------ | ------------------ | ------------------ | --- | --------------- | ------------------- | +| :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | - | 2,472,994 | + +This implementation adds encryption, as described in the section above for Zeto_AnonEnc, to the pattern Zeto_AnonNullifier above. + +The statements in the proof include: + +- each value in the output commitments must be a positive number in the range 0 ~ (2\*\*40 - 1) +- the sum of the nullified values match the sum of output values +- the hashes in the output match the hash(value, salt, owner public key) formula +- the sender possesses the private BabyJubjub key, whose public key is part of the pre-image of the input commitment hashes, which match the corresponding nullifiers +- the nullifiers represent input commitments that are included in a Sparse Merkle Tree represented by the root hash +- the encrypted values in the transaction are derived from the receiver's UTXO value and encrypted with a shared secret using the ECDH protocol between a random private key and the receiver (this guarantees data availability for the receiver, because the public key for the random private key used by the sender is published in the transaction) diff --git a/doc-site/docs/implementations/anon_enc_nullifier_kyc.md b/doc-site/docs/implementations/anon_enc_nullifier_kyc.md new file mode 100644 index 00000000..850dc4d9 --- /dev/null +++ b/doc-site/docs/implementations/anon_enc_nullifier_kyc.md @@ -0,0 +1,17 @@ +# Zeto_AnonEncNullifier + +| Anonymity | History Masking | Encryption | KYC | Non-repudiation | Gas Cost (estimate) | +| ------------------ | ------------------ | ------------------ | ------------------ | --------------- | ------------------- | +| :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | 2,414,345 | + +This implementation adds encryption, as described in the section above for Zeto_AnonEnc, to the pattern Zeto_AnonNullifierKyc above. + +The statements in the proof include: + +- each value in the output commitments must be a positive number in the range 0 ~ (2\*\*40 - 1) +- the sum of the nullified values match the sum of output values +- the hashes in the output match the hash(value, salt, owner public key) formula +- the sender possesses the private BabyJubjub key, whose public key is part of the pre-image of the input commitment hashes, which match the corresponding nullifiers +- the nullifiers represent input commitments that are included in a Sparse Merkle Tree represented by the root hash +- the sender and receiver public keys are included in the Sparse Merkle Tree for the KYC registry, represented by the latest root hash known to the smart contract +- the encrypted values in the transaction are derived from the receiver's UTXO value and encrypted with a shared secret using the ECDH protocol between a random private key and the receiver (this guarantees data availability for the receiver, because the public key for the random private key used by the sender is published in the transaction) diff --git a/doc-site/docs/implementations/anon_enc_nullifier_non_repudiation.md b/doc-site/docs/implementations/anon_enc_nullifier_non_repudiation.md new file mode 100644 index 00000000..b5fcec77 --- /dev/null +++ b/doc-site/docs/implementations/anon_enc_nullifier_non_repudiation.md @@ -0,0 +1,21 @@ +# Zeto_AnonEncNullifierNonRepudiation + +| Anonymity | History Masking | Encryption | KYC | Non-repudiation | Gas Cost (estimate) | +| ------------------ | ------------------ | ------------------ | --- | ------------------ | ------------------- | +| :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | :heavy_check_mark: | 2,763,071 | + +The concept of "non-repudiation" is introduced in this implementation pattern. + +Since all onchain states are hashes, with ownership information for the assets hidden, it's possible that a participant can send a transaction but subsequently deny it. Because the transaction signer account no longer reflects the identity of the asset owner, as discussed above, it will be impossible to know who was the sender of a transaction from purely looking at the onchain data, which is exactly the point for Zeto's anonymity support. This gives a malicious party the ability to gain repudiation, or deny that they were responsible for a past transaction. + +This implementation pattern addresses that concern by encrypting the ownership information of each UTXO involved in a transaction with an authority's registered key. Only the designated authority will be able to decrypt the ownership information. The encryption is performed inside the ZKP circuit, thus guaranteeing that they are the actual owners of the UTXOs. + +The statements in the proof include: + +- each value in the output commitments must be a positive number in the range 0 ~ (2\*\*40 - 1) +- the sum of the nullified values match the sum of output values +- the hashes in the output match the hash(value, salt, owner public key) formula +- the sender possesses the private BabyJubjub key, whose public key is part of the pre-image of the input commitment hashes, which match the corresponding nullifiers +- the nullifiers represent input commitments that are included in a Sparse Merkle Tree represented by the root hash +- the encrypted values in the transaction contains cipher texts derived from the receiver's UTXO values and encrypted with a shared secret using the ECDH protocol between a random private key and the receiver (this guarantees data availability for the receiver, because the public key for the random private key used by the sender is published in the transaction) +- the encrypted values in the transaction contains cipher texts derived from the receiver's UTXO values and encrypted with a shared secret using the ECDH protocol between a random private key and the authority's public key diff --git a/doc-site/docs/implementations/anon_nullifier.md b/doc-site/docs/implementations/anon_nullifier.md new file mode 100644 index 00000000..efb40f0c --- /dev/null +++ b/doc-site/docs/implementations/anon_nullifier.md @@ -0,0 +1,23 @@ +# Zeto_AnonNullifier + +| Anonymity | History Masking | Encryption | KYC | Non-repudiation | Gas Cost (estimate) | +| ------------------ | ------------------ | ---------- | --- | --------------- | ------------------- | +| :heavy_check_mark: | :heavy_check_mark: | - | - | - | 2,005,587 | + +To mask the association between the consumed UTXOs and the output UTXOs, we hide which UTXOs are being consumed by each transaction. + +To achieve this, we employ the usage of nullifiers. It's a unique hash derived from the unique commitment it consumes. For a UTXO commitment hash(value, salt, owner public key), the nullifier is calculated as hash(value, salt, owner private key). Only the owner of the commitment can generate the nullifier hash. Each transaction will record the nullifiers in the smart contract, to ensure that they don't get re-used (double spending). + +In order to prove that the UTXOs to be spent actually exist, we use a merkle tree proof inside the zero knowledge proof circuit. The merkle proof is validated against a merkle tree root that is maintained by the smart contract. The smart contract keeps track of all the new UTXOs in each transaction's output commitments array, and uses a merkle tree to calculate the root hash. Then the ZKP circuit can use a root hash as public input, to prove that the input commitments (UTXOs to be spent), which are private inputs to the circuit, are included in the merkle tree represented by the root. + +The end result is that, from the onchain data, no one can figure out which UTXOs have been spent, while double spending is prevented. + +The statements in the proof include: + +- each value in the output commitments must be a positive number in the range 0 ~ (2\*\*40 - 1) +- the sum of the nullified values match the sum of output values +- the hashes in the output match the hash(value, salt, owner public key) formula +- the sender possesses the private BabyJubjub key, whose public key is part of the pre-image of the input commitment hashes, which match the corresponding nullifiers +- the nullifiers represent input commitments that are included in a Sparse Merkle Tree represented by the root hash + +![nullifiers](../images/nullifiers.jpg) diff --git a/doc-site/docs/implementations/anon_nullifier_kyc.md b/doc-site/docs/implementations/anon_nullifier_kyc.md new file mode 100644 index 00000000..73e575c2 --- /dev/null +++ b/doc-site/docs/implementations/anon_nullifier_kyc.md @@ -0,0 +1,20 @@ +# Zeto_AnonNullifierKyc + +| Anonymity | History Masking | Encryption | KYC | Non-repudiation | Gas Cost (estimate) | +| ------------------ | ------------------ | ---------- | ------------------ | --------------- | ------------------- | +| :heavy_check_mark: | :heavy_check_mark: | - | :heavy_check_mark: | - | 2,310,424 | + +The concept of "KYC with privacy" is introduced in this implementation pattern. + +How to enforce a policy of "all senders and receivers of a transaction must be in a KYC registry", while maintaining anomymity of the sender and the receiver? The solution is similar to how nullifiers are supported, via merkle tree proofs. + +The implementation of this pattern maintains a KYC registry in the smart contract as a Sparse Merkle Tree. The registry is maintained by a designated authority, and includes the public keys of entities that have cleared the KYC process. Each transaction must demonstrate that the public keys of the sender and the receivers are included in the KYC merkle tree, by generating a merkle proof and using it as a private input to the ZKP circuit. + +The statements in the proof include: + +- each value in the output commitments must be a positive number in the range 0 ~ (2\*\*40 - 1) +- the sum of the nullified values match the sum of output values +- the hashes in the output match the hash(value, salt, owner public key) formula +- the sender possesses the private BabyJubjub key, whose public key is part of the pre-image of the input commitment hashes, which match the corresponding nullifiers +- the nullifiers represent input commitments that are included in a Sparse Merkle Tree represented by the root hash +- the sender and receiver public keys are included in the Sparse Merkle Tree for the KYC registry, represented by the latest root hash known to the smart contract diff --git a/doc-site/docs/implementations/index.md b/doc-site/docs/implementations/index.md new file mode 100644 index 00000000..2892d132 --- /dev/null +++ b/doc-site/docs/implementations/index.md @@ -0,0 +1,26 @@ +# Overview of Zeto Token Implementations + +Zeto is not a single privacy-preserving token implementation. It's a collection of implementations that meet a wide range of requirements in different use cases. The collection will continue to grow as new patterns are implemented. + +Below is a summary and comparison table among the current list of implementations. + +| Fungible Token Implementation | Anonymity | History Masking | Encryption | KYC | Non-repudiation | Gas Cost (estimate) | +| ----------------------------------- | ------------------ | ------------------ | ------------------ | ------------------ | ------------------ | ------------------- | +| Zeto_Anon | :heavy_check_mark: | - | - | - | - | 326,583 | +| Zeto_AnonNullifier | :heavy_check_mark: | :heavy_check_mark: | - | - | - | 2,005,587 | +| Zeto_AnonEnc | :heavy_check_mark: | - | :heavy_check_mark: | - | - | 425,338 | +| Zeto_AnonEncNullifier | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | - | 2,472,994 | +| Zeto_AnonNullifierKyc | :heavy_check_mark: | :heavy_check_mark: | - | :heavy_check_mark: | - | 2,310,424 | +| Zeto_AnonEncNullifierKyc | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | 2,414,345 | +| Zeto_AnonEncNullifierNonRepudiation | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | :heavy_check_mark: | 2,763,071 | + +| Non-Fungible Token Implementation | Anonymity | History Masking | Encryption | KYC | Non-repudiation | Gas Cost (estimate) | +| --------------------------------- | ------------------ | ------------------ | ---------- | --- | --------------- | ------------------- | +| Zeto_NfAnon | :heavy_check_mark: | - | - | - | - | 271,890 | +| Zeto_NfAnonNullifier | :heavy_check_mark: | :heavy_check_mark: | - | - | - | 1,450,258 | + +The various patterns in this project use Zero Knowledge Proofs (ZKP) to demonstrate the validity of the proposed transaction. There is no centralized party to trust as in the Notary pattern, which is not implemented in this project but [in the Paladin project](https://lf-decentralized-trust-labs.github.io/paladin/head/concepts/tokens/). + +Using ZKPs as validity proofs, each participant can independently submit transactions to the smart contract directly. As long as the participant is able to produce a valid proof, the transaction will be successfully verified and allowed to go through. + +This project includes multiple ZKP circuits to support various privacy levels with Zeto. diff --git a/doc-site/docs/implementations/nf_anon.md b/doc-site/docs/implementations/nf_anon.md new file mode 100644 index 00000000..453480a5 --- /dev/null +++ b/doc-site/docs/implementations/nf_anon.md @@ -0,0 +1,14 @@ +# Zeto_NfAnon + +| Anonymity | History Masking | Encryption | KYC | Non-repudiation | Gas Cost (estimate) | +| ------------------ | --------------- | ---------- | --- | --------------- | ------------------- | +| :heavy_check_mark: | - | - | - | - | 271,890 | + +This implements a basic non-fungible token. + +For non-fungible tokens, the main concern with the transaction validity check is that the output UTXO contains the same secrets (id, uri) as the input UTXO, with only the ownership updated. + +The statements in the proof include: + +- the output UTXO hashes are based on the same id, uri as the input UTXO hashes +- the sender possesses the private BabyJubjub key, whose public key is part of the pre-image of the input commitment hashes diff --git a/doc-site/docs/implementations/nf_anon_nullifier.md b/doc-site/docs/implementations/nf_anon_nullifier.md new file mode 100644 index 00000000..0d9b56a4 --- /dev/null +++ b/doc-site/docs/implementations/nf_anon_nullifier.md @@ -0,0 +1,13 @@ +# Zeto_NfAnonNullifier + +| Anonymity | History Masking | Encryption | KYC | Non-repudiation | Gas Cost (estimate) | +| ------------------ | ------------------ | ---------- | --- | --------------- | ------------------- | +| :heavy_check_mark: | :heavy_check_mark: | - | - | - | 1,450,258 | + +This implements a non-fungible token using nullifiers, thus hiding the spending graph. + +The statements in the proof include: + +- the output UTXO hashes are based on the same id, uri as the input UTXO hashes +- the sender possesses the private BabyJubjub key, whose public key is part of the pre-image of the input commitment hashes, which match the corresponding nullifiers +- the nullifiers represent input commitments that are included in a Sparse Merkle Tree represented by the root hash diff --git a/doc-site/docs/index.md b/doc-site/docs/index.md new file mode 100644 index 00000000..0eff4e2f --- /dev/null +++ b/doc-site/docs/index.md @@ -0,0 +1,27 @@ +# Zeto + +Zeto is a collection of privacy-preserving token implementations on EVM, using Zero Knowledge Proof to enforce a varieties of tokenonmic policies, suited for use in enterprise use cases such as CBDCs, tokenized deposits, security trading that meet regulatory requirements such as KYC. + +![Zeto overview](./images/zeto-arch.svg) + +Zeto tokens give enterprises control of sensitive information without compromising transparency or scalability. + +The Zeto project is Apache 2.0 open source, with open governance through [Linux Foundation Decentralized Trust](https://www.lfdecentralizedtrust.org). + +## Programmable privacy for EVM + +The Ethereum Virtual Machine (EVM) powers over 80% of global blockchain projects, making it the 'de facto' +runtime environment for both enterprise and permissionless networks. + +However, there are requirements for enterprise use cases that are not met by the core standard +of EVM. The Zeto project is part of a larger effort, [the Paladin project](https://lf-decentralized-trust-labs.github.io/paladin/head/), which brings latest generation of innovation in solving these requirements in the +EVM ecosystem, and provides a comprehensive enterprise grade Apache 2.0 open source stack to deliver them. + +- Anonymity for all parties involved in transactions +- Confidential transaction details +- Transaction history masking to prevent tracking +- Selective data sharing +- Confidential business logic +- Privacy preserving smart contracts +- Private token and asset management +- Atomic transactions across privacy domains diff --git a/doc-site/docs/stylesheets/zeto.css b/doc-site/docs/stylesheets/zeto.css new file mode 100644 index 00000000..e4969dd0 --- /dev/null +++ b/doc-site/docs/stylesheets/zeto.css @@ -0,0 +1,44 @@ +[data-md-color-scheme="zeto"] { + --md-primary-fg-color: #F4FCFC; + --md-primary-fg-color--light: #2cd3d3; + --md-primary-fg-color--dark: #2cd3d3; + --md-primary-bg-color: #107070; + --md-primary-bg-color--light: #9E9E9E; + --md-primary-bg-color--dark: #9E9E9E; + --md-typeset-border-color: #1EC4C4; +} + +[data-md-color-scheme="zeto"] footer { + border-top: solid 1px #1EC4C4; + color: #107070; + background-color: #F4FCFC; +} + +[data-md-color-scheme="zeto"] img[src$="#only-dark"] { + display: none; +} + +[data-md-color-scheme="zeto-dark"] { + --md-primary-fg-color: #212121; + --md-primary-fg-color--light: #212121; + --md-primary-fg-color--dark: #20E2E2; + --md-primary-bg-color: #1EC4C4; + --md-primary-bg-color--light: #9E9E9E; + --md-primary-bg-color--dark: #9E9E9E; + --md-typeset-a-color: #1EC4C4; + --md-default-bg-color: #2C2C2C; + --md-default-fg-color--light: #EEEEEE; + --md-typeset-color: #EEEEEE; + --md-code-bg-color: #212121; + --md-code-text-color: #9E9E9E; + --md-code-fg-color: #9E9E9E; + --md-code-hl-punctuation-color: #9E9E9E; + --md-code-hl-name-color: #20E2E2; + --md-code-hl-string-color: #29c361; +} +[data-md-color-scheme="zeto-dark"] .md-tabs__link { + font-weight: bolder; +} +[data-md-color-scheme="zeto-dark"] img[src$="#only-light"] { + display: none; +} diff --git a/doc-site/mkdocs.yml b/doc-site/mkdocs.yml new file mode 100644 index 00000000..61e74180 --- /dev/null +++ b/doc-site/mkdocs.yml @@ -0,0 +1,117 @@ +site_name: Zeto +repo_name: hyperledger-labs/zeto +repo_url: https://github.com/hyperledger-labs/zeto +theme: + name: material + custom_dir: overrides + logo: assets/paladin-logo-light.svg + logo_dark: assets/paladin-logo-dark.svg + favicon: assets/paladin-icon-light.png + icon: + repo: fontawesome/brands/github + palette: + # Palette toggle for light mode + - media: '(prefers-color-scheme: light)' + scheme: zeto + toggle: + icon: material/brightness-7 + name: Switch to dark mode + # Palette toggle for dark mode + - media: '(prefers-color-scheme: dark)' + scheme: zeto-dark + toggle: + icon: material/brightness-4 + name: Switch to light mode + primary: custom + features: + - content.code.copy + - navigation.expand + - navigation.footer + - navigation.instant + - navigation.tabs + - navigation.tabs.sticky + - navigation.sidebar + - navigation.tracking + - navigation.path + - navigation.indexes +extra_css: + - stylesheets/zeto.css +markdown_extensions: + - abbr + - admonition + - attr_list + - def_list + - footnotes + - md_in_html + - toc: + permalink: true + toc_depth: 3 + - pymdownx.arithmatex: + generic: true + - pymdownx.betterem: + smart_enable: all + - pymdownx.caret + - pymdownx.details + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.keys + - pymdownx.magiclink: + repo_url_shorthand: true + user: squidfunk + repo: mkdocs-material + - pymdownx.mark + - pymdownx.smartsymbols + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - pymdownx.tabbed: + alternate_style: true + - pymdownx.tasklist: + custom_checkbox: true + - pymdownx.tilde +plugins: + - include-markdown: + rewrite_relative_urls: false + - literate-nav + - search + - mike +extra: + analytics: + provider: google + property: !ENV GOOGLE_ANALYTICS_KEY + version: + provider: mike + generator: false +exclude_docs: | + _includes/ +nav: + - Introduction: + - Introduction: index.md + - Concepts: concepts/basics.md + - How Zeto Works: concepts/how-zeto-works.md + - Implementations: + - Overview: implementations/index.md + - Fungible: + - Zeto_Anon: implementations/anon.md + - Zeto_AnonNullifier: implementations/anon_nullifier.md + - Zeto_AnonEnc: implementations/anon_enc.md + - Zeto_AnonNullifierKyc: implementations/anon_nullifier_kyc.md + - Zeto_AnonEncNullifier: implementations/anon_enc_nullifier.md + - Zeto_AnonEncNullifierKyc: implementations/anon_enc_nullifier_kyc.md + - Zeto_AnonEncNullifierNonRepudiation: implementations/anon_enc_nullifier_non_repudiation.md + - Non-Fungible: + - Zeto_NfAnon: implementations/nf_anon.md + - Zeto_NfAnonNullifier: implementations/nf_anon_nullifier.md + - FAQs: faqs.md + - Glossary: glossary.md + - Contributing: + - Ask a Question: contributing/asking-a-question.md + - How to contribute: contributing/how-to-contribute.md + - Report a bug: contributing/reporting-a-bug.md + - Request a change: contributing/requesting-a-change.md diff --git a/doc-site/overrides/main.html b/doc-site/overrides/main.html new file mode 100644 index 00000000..8d9f5050 --- /dev/null +++ b/doc-site/overrides/main.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} + +{% block outdated %} + You're not viewing the latest version. + + Click here to go to latest. + +{% endblock %} diff --git a/doc-site/overrides/partials/header.html b/doc-site/overrides/partials/header.html new file mode 100644 index 00000000..f5368d56 --- /dev/null +++ b/doc-site/overrides/partials/header.html @@ -0,0 +1,161 @@ + + + +{% set class = "md-header" %} +{% if "navigation.tabs.sticky" in features %} + {% set class = class ~ " md-header--lifted" %} +{% endif %} + + +
+ + + + {% if "navigation.tabs.sticky" in features %} + {% if "navigation.tabs" in features %} + {% include "partials/tabs.html" %} + {% endif %} + {% endif %} +
\ No newline at end of file diff --git a/doc-site/overrides/partials/logo.html b/doc-site/overrides/partials/logo.html new file mode 100644 index 00000000..15a370c1 --- /dev/null +++ b/doc-site/overrides/partials/logo.html @@ -0,0 +1,25 @@ + + + +Paladin +Paladin diff --git a/doc-site/overrides/partials/nav.html b/doc-site/overrides/partials/nav.html new file mode 100644 index 00000000..e81caffc --- /dev/null +++ b/doc-site/overrides/partials/nav.html @@ -0,0 +1,34 @@ +{#- + This file was automatically generated - do not edit +-#} +{% import "partials/nav-item.html" as item with context %} +{% set class = "md-nav md-nav--primary" %} +{% if "navigation.tabs" in features %} + {% set class = class ~ " md-nav--lifted" %} +{% endif %} +{% if "toc.integrate" in features %} + {% set class = class ~ " md-nav--integrated" %} +{% endif %} + diff --git a/doc-site/requirements.txt b/doc-site/requirements.txt new file mode 100644 index 00000000..1bd4b365 --- /dev/null +++ b/doc-site/requirements.txt @@ -0,0 +1,39 @@ +babel==2.16.0 +bracex==2.5.post1 +certifi==2024.8.30 +charset-normalizer==3.4.0 +click==8.1.7 +colorama==0.4.6 +ghp-import==2.1.0 +idna==3.10 +importlib_metadata==8.5.0 +importlib_resources==6.4.5 +Jinja2==3.1.4 +Markdown==3.7 +MarkupSafe==3.0.2 +mergedeep==1.3.4 +mike==2.1.3 +mkdocs==1.6.1 +mkdocs-get-deps==0.2.0 +mkdocs-include-markdown-plugin==7.0.0 +mkdocs-literate-nav==0.6.1 +mkdocs-material==9.5.44 +mkdocs-material-extensions==1.3.1 +packaging==24.2 +paginate==0.5.7 +pathspec==0.12.1 +platformdirs==4.3.6 +Pygments==2.18.0 +pymdown-extensions==10.12 +pyparsing==3.2.0 +python-dateutil==2.9.0.post0 +PyYAML==6.0.2 +pyyaml_env_tag==0.1 +regex==2024.11.6 +requests==2.32.3 +six==1.16.0 +urllib3==2.2.3 +verspec==0.1.0 +watchdog==6.0.0 +wcmatch==10.0 +zipp==3.20.2 \ No newline at end of file diff --git a/resources/c-utxo-zkp-1.jpg b/resources/c-utxo-zkp-1.jpg deleted file mode 100644 index 7422661a..00000000 Binary files a/resources/c-utxo-zkp-1.jpg and /dev/null differ