-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #121 from hyperledger-labs/lock-nullifiers
Locking by spending and creating UTXOs
- Loading branch information
Showing
143 changed files
with
4,722 additions
and
3,509 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# ERC20 Tokens integration | ||
|
||
All fungible Zeto implementations support integration with an ERC20 contract via `deposit` and `withdraw` functions. | ||
|
||
 | ||
|
||
## Deposit | ||
|
||
When depositing, users take their balances in ERC20 and exchanges for the same amount in Zeto tokens. The deposited amount will be transfered to the Zeto contract to own, until the time when a withdraw is called. | ||
|
||
The ZKP circuit for the deposit function contains the following statements: | ||
|
||
- the commitments for the output UTXOs are based on positive numbers | ||
- the commitments for the output UTXOs are well formed, obeying the `hash(value, owner public key, salt)` formula | ||
- the sum of the output UTXO values are returned as the output signal, which can be compared with the `amount` value in the transaction call. aka `depositAmount == sum(outputs)` | ||
|
||
One obvious observation with the deposit function is that it leaks the value of the output UTXO. For instance, consider the following transaction: | ||
|
||
```javascript | ||
deposit(amount, outputUTXO, proof); | ||
``` | ||
|
||
The output UTXO's value will be equal to the `amount`. To mitigate this, the output is an array of UTXOs, of size `2`. This way the exact value of each of the UTXOs in the output is unknown except by the owner(s). | ||
|
||
## Withdraw | ||
|
||
When withdrawing, users spend their UTXOs in the Zeto contract and request for the corresponding amount to be transferred to their Ethereum account in the ERC20 contract. | ||
|
||
The ZKP circuit for the withdraw function contains the following statements: | ||
|
||
- the commitments for the output UTXOs are based on positive numbers | ||
- the commitments for the input and output UTXOs are well formed, obeying the `hash(value, owner public key, salt)` formula | ||
- the sum of the input UTXO values is subtracted by the sum of the output UTXO values, with the result returned as the output signal, which can be compared with the `amount` value in the transaction call. aka `sum(inputs) == sum(outputs) + withdarwAmount` | ||
|
||
## How to use ERC20 integration | ||
|
||
It's very easy to enable the ERC20 integration on any fungible Zeto implementation. Call `setERC20(erc20_contract_address)` to configure the ERC20 contract that the Zeto token should work with. That's it! | ||
|
||
## deposit/withdraw vs. mint | ||
|
||
A solution developer who considers using the ERC20 integration feature must take into account how this works alongside the `mint` function. While the `mint` function preserves the privacy of new token issuance inside the Zeto contract, it could lead to an insufficient balance in the ERC20 contract when the `withdraw` function is invoked. | ||
|
||
Consider the following sequence of events: | ||
|
||
- The Zeto contract is deployed and configured to work with an ERC20 contract | ||
- Alice deposits 100 from her ERC20 balance | ||
> Zeto contract's balance becomes 100 | ||
- The regulator mints 50 to Alice | ||
- Bob deposits 100 from his ERC20 balance | ||
> Zeto contract's balance becomes 200 | ||
- Alice withdraws all her 150 Zeto tokens | ||
> Zeto contract's balance becomes 50 | ||
- Bob attempts to withdraw 100 | ||
> This will fail because the Zeto contract's balance is below the requested amount |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# Locking UTXOs | ||
|
||
In a typical atomic swap flow, based on the popular ERC20 token standard, the tokens from the trading parties are transferred to an escrow contract, which then coordinates the settlements with all the trading parties to ensure safety for all the involved parties. | ||
|
||
This type of swap design is not possible with Zeto tokens, unfortunately. An escrow contract can not own tokens because Solidity contract doesn't have the ability to generate ZK proofs required to spend Zeto tokens. | ||
|
||
This is where the `locking` mechanism comes in. | ||
|
||
 | ||
|
||
As illustrated above, regular (unlocked) UTXOs can be spent by any Ethereum account submitting a valid proof. This is an important privacy feature because it doesn't require the Ethereum transaction signing account to be tied to the ownership of the Zeto tokens. As a result, the Zeto tokens owner can use a different Ethereum signing key for each transaction, to avoid their transaction history to be analyzed based on the base ledger transactions. | ||
|
||
On the other hand, a UTXO can be locked with a designated `spender`, which is an Ethereum account address. The owner of the token is still required to produce a valid proof, which then must be submitted by the designated `spender` key, signing the transaction to spend the locked UTXO(s). | ||
|
||
 | ||
|
||
In the locking transaction above, a locked UTXO, \#3 was created. The owner is still Alice, but the spender has been set to the address of an escrow contract. This means Alice as the owner can no longer spend UTXO \#3, even though she can produce a valid spending proof. In order to spend a locked UTXO, a valid proof must be submitted by the designated spender. | ||
|
||
## Lock, then delegate | ||
|
||
The following diagram illustrates a typical flow to use the locking mechanism. | ||
|
||
 | ||
|
||
- Alice and Bob are in a bilateral trade where Alice sends Bob 100 Zeto tokens for payment, at the same time Bob sends Alice some asset tokens which are omitted from the diagram | ||
- In transaction 1, `Tx1`, Alice calls `lock()` to lock 100 into a new UTXO \#3, by spending two existing UTOXs \#1 and \#2. The transaction also creates \#4 for the remainder value, which is unlocked. This transaction designates the escrow contract as the spender for the locked UTXO \#3 | ||
- Alice then sends another transaction, `Tx2`, to call `prepareUnlock()` on the escrow contract and sends a valid proof to the contract. This proof can be used to spend the locked \#3 UTXO and create \#5, which will be owned by Bob | ||
> the contract will verify that the proof is valid for the intended UTXO spending | ||
- Alice and Bob continues with the trade using the escrow contract logic. The details of the remainder of the trade flow are omitted | ||
- When the trade setup is complete, and ready to settle atomically, a party can call the escrow contract to carry out the execution phase. The escrow contract calls Zeto to `transfer()` the locked UTXO \#3 and creates \#5, as was originally intended in the trade setup phase |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# Supporting different UTXO array sizes | ||
|
||
Using ZK proofs presents a special challenge for supporting UTXO inputs and outputs that are of different sizes. For instance, a transaction proposal that consumes 1 UTXO, owned by Alice, but generates 3 UTXOs to be owned by Bob, Charlie and Alice, will require a different circuit for the proof than a transaction that consumes 2 UTXOs and generates 2 UTXOs. | ||
|
||
 | ||
_Using different array sizes for the input signals require different verification circuits_ | ||
|
||
This is because a ZKP circuit must always perform the exact same computation. Therefore, if there are arrays in the input signals, the size of the arrays must be known at compile time. | ||
|
||
 | ||
_Using the same array sizes for the input signals require the same verification circuit_ | ||
|
||
For all Zeto token implementations, two sizes are chosen for the circuits: `2` and `10`. | ||
|
||
## Size = 2 | ||
|
||
For example, the following top-level circuit is for the token implementation `Zeto_Anon`, | ||
|
||
``` | ||
[file: zkp/circuits/anon.circom] | ||
include "./basetokens/anon_base.circom"; | ||
component main { public [ inputCommitments, outputCommitments ] } = Zeto(2, 2); | ||
``` | ||
|
||
The `Zeto(2, 2)` part provides fixed values for the parameterized circuit template from `basetokens/anon_base.circom`, which looks like this, | ||
|
||
``` | ||
template Zeto(nInputs, nOutputs) { | ||
signal input inputCommitments[nInputs]; | ||
signal input inputValues[nInputs]; | ||
signal input inputSalts[nInputs]; | ||
signal input outputCommitments[nOutputs]; | ||
signal input outputValues[nOutputs]; | ||
signal input outputSalts[nOutputs]; | ||
signal input outputOwnerPublicKeys[nOutputs][2]; | ||
... | ||
} | ||
``` | ||
|
||
The parameterized template support different array sizes for both the inputs and outputs, but for the final circuit to be compiled, we set the size to `2` for both the inputs and outputs. This corresponds to the Solidity function in the token implementation: | ||
|
||
```javascript | ||
function transfer( | ||
uint256[] memory inputs, | ||
uint256[] memory outputs, | ||
Commonlib.Proof calldata proof, | ||
bytes calldata data | ||
) public returns (bool) { ... } | ||
``` | ||
|
||
When a transaction calls this function with inputs and outputs sizes of 1 or 2, the Solidity code will pad the arrays to size 2, and use the verifier library generated from the above circuit (`Zeto(2, 2)`) to verify the proof. | ||
|
||
## Size = 10 | ||
|
||
To support array size of 10 in the input signals, we simply set the size parameters to `10` in the top-level circuit: | ||
|
||
``` | ||
[file: zkp/circuits/anon_batch.circom] | ||
include "./basetokens/anon_base.circom"; | ||
component main { public [ inputCommitments, outputCommitments ] } = Zeto(10, 10); | ||
``` |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.