or
or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up
Syntax | Example | Reference | |
---|---|---|---|
# Header | Header | 基本排版 | |
- Unordered List |
|
||
1. Ordered List |
|
||
- [ ] Todo List |
|
||
> Blockquote | Blockquote |
||
**Bold font** | Bold font | ||
*Italics font* | Italics font | ||
~~Strikethrough~~ | |||
19^th^ | 19th | ||
H~2~O | H2O | ||
++Inserted text++ | Inserted text | ||
==Marked text== | Marked text | ||
[link text](https:// "title") | Link | ||
 | Image | ||
`Code` | Code |
在筆記中貼入程式碼 | |
```javascript var i = 0; ``` |
|
||
:smile: | ![]() |
Emoji list | |
{%youtube youtube_id %} | Externals | ||
$L^aT_eX$ | LaTeX | ||
:::info This is a alert area. ::: |
This is a alert area. |
On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?
Please give us some advice and help us improve HackMD.
Do you want to remove this version name and description?
Syncing
xxxxxxxxxx
Original authors: Barry WhiteHat, Kobi Gurkan; full spec fleshed out by Koh Wei Jie; developed with assistance from the members of the Semaphore Society Telegram group.
MicroMix: A noncustodial Ethereum mixer based on zero-knowledge signalling
Since Ethereum transfers are fully visible on-chain, anyone who knows a user's address can trace the source of their funds, determine even how much they own, and analyse their on-chain activity. As such, users do not enjoy much financial privacy beyond pseudonymity. Some workarounds to obscure value flows, like using a centralised exchange wallet or a custodial mixing service, however, introduce a high degree of counterparty and surveillance risk. What the Ethereum ecosystem needs is a noncustodial mixer which works through strong cryptography, rather than on trust.
This is a specification for a minimal Ethereum mixer based on zero-knowledge proofs. It improves transaction privacy by breaking the on-chain link between recipient and destination addresses. It allows a set of users to deposit a fixed amount of ETH or ERC20 tokens each (e.g. 0.1 ETH or 100 tokens), and pay a third-party relayer to trustlessly withdraw the ETH or tokens respectively to a fresh address. As the mixer is noncustodial, they can permissionlessly withdraw their own ETH at any time.
Under the hood, the mixer uses the Semaphore zero-knowledge signalling smart contracts and circuits. At this stage, it relies on a single relayer node, and will only support fixed amounts. Future versions of this mixer will employ a burn relay registry to safely reduce centralisation.
Simplified user flow
Assume that there are three users who want to mix their funds: Alice, Bob, and Charlie, and a relayer (also known as an operator), Oscar.
Alice, Bob, and Charlie each deposit 1 ETH into the mixer's smart contract. This deposit transaction carries a piece of data which commits to their identity.
To increase her anonymity, Alice's user interface waits till after midnight UTC time to expose a button, which when clicked, generates a zk-SNARK proof and sends it to Oscar. Public inputs to this proof include the recipient's address and a fee for Oscar. The proof demonstrates that Alice had previously deposited ETH and that she had not already withdrawn it. This step, notably, does not reveal Alice's original identity.
Oscar invokes the contract's mix function, which verifies the proof and transfers ETH to the recipient's address. It also sends the a small fee to Oscar.
If Alice wishes to withdraw her ETH by herself, she can do so herself, but this will not give her privacy as this transaction will link her depositing and receiving addresses.
Technical overview
Figure: Overview of system components.
The mixer comprises of a user-facing web3 dApp and the relayer's backend server. The dApp allows users to deposit ETH or tokens and submit withdrawal proofs at a predetermined time (specifically midnight UTC). Even though the user relies on the relayer to perform withdrawals transactions, they can opt to use the dApp to withdraw their funds at any time at the cost of some anonymity.
This system is more complex than other dApps which share this pattern web3-client-server pattern. As will be described below, the UI includes an EdDSA keystore and proof generation capabilities, and the relayer's server should have internal daemons to handle a hot wallet and transaction nonces.
Component specifications
The Semaphore zk-SNARK circuit
TODO: In-depth spec of Semaphore. Alternatively, add it as an appendix. Note that the work to be done on the mixer will entail major changes to Semaphore, so its readme on Github will be obsolete in regards to the mixer.
Public inputs
signal
recipientAddress
,broadcasterAddress
, andfee
externalNullifier
root
nullifiersHash
Semaphore uses
externalNullifier
andnullifiersHash
to prevent double-withdrawals from the same user. Semaphore also usesroot
(along with other private inputs below) to ensure that the user is indeed a depositer.Private inputs
All the private inputs to the circuit are the same as those described in the Semaphore documentation. At the time of writing, these are:
identityPk
identityNullifier
identityPathElements
identityPathIndex
authSigR
authSigS
Trusted setup ceremony
TODO: Figure out how to perform a Powers of Tau ceremony for a
circom
trusted setup. Also look into the Gnosis team's trusted setup, if they did one.Batched deposits and Pedersen hashes
TODO: There is a case to be made about replacing the MiMC hash function in Semaphore with Pedersen hashes for reasons of cryptographic security. As the gas costs for Pedersen hashes before EIP-1829 and EIP-1962 are very high, there should also be a way for anyone (such as the relayer) to batch deposits and add them to the Semaphore tree. These two changes are major overhauls to Semaphore. Note that while batched deposits can be done without using Pedersen hashes, if we use expensive Pedersen hashes, we must use batched deposits. This can be done after the minimal mixer.
The mixer contract
mixAmt
address
semaphore
Semaphore
Semaphore
type in Soliditytoken
address
0x0000...
.deposit
mix
depositERC20
mixAmt
tokens from the caller into the contractmixERC20
deposit(uint256 _identityCommitment)
This function will invoke the Semaphore contract's
insertIdentity()
function, passing on theidentityCommitment
value as the leaf. In turn,insertIdentity()
updates the identity Merkle tree in the contract.identityCommitment
should be the Pedersen hash ofidentityPk
(the user's EdDSA public key) andidentityNullifier
identityNullifier
should be a random 31-byte (253 bits, to be precise) value for each deposit.depositERC20(uint256 _identityCommitment)
Does the same as
deposit()
as described above, but instead of accepting ETH, this function invokes thetransferFrom()
ERC20 function of thetoken
contract to transfer tokens from the sender to itself.mix(DepositProof _proof, address payable _relayerAddress)
mix
lets anyone with a valid and unspent zk-SNARK proof of a prior deposit release funds to a recipient. The operator pays the gas for this transaction, and receives_proof.fee
in return. We intend for third party relayers to make these transactions via a burn relay registry. An economically rational operator will only act on mix requests where the fee is high enough to compensate for the service they provide and the gas they pay.The function does the following:
_proof.fee
is less thanmixAmt
.signal
(from the proof input) into the Semaphore contract's signal hash Merkle tree._proof.fee
to the relayer at_relayerAddress
.funds - _proof.fee
to the recipient.mixERC20(DepositProof _proof, address payable _relayerAddress)
Does the same as
mix()
but transfers tokens instead of ETH. Note that the fee is paid to the relayer in tokens.The
DepositProof
data structureThe mixer contract will use code from
circom
's Solidity zk-SNARK verifier. Ascircom
's contract internally defines aProof
struct, the mixer'smix
function uses a struct namedDepositProof
instead:The values in
input
are:root
nullifiersHash
signalHash
externalNullifier
The
signal
input to the zk-SNARK, also referred to ascomputedSignal
, is the Keccak256 hash of therecipientAddress
,broadcasterAddress
, andfee
.Broadcast server
Browser requirements to generate proofs
The UI requires a browser which natively supports the Javascript
BigInt
, such as Firefox 68 (yet to be released at the time of writing) or Chrome 67 and above.web3
The browser should be web3-enabled — that is, it should have a way to sign and send Ethereum transactions, such as the MetaMask extension. The UI should also intelligently keep track and display the user's selected wallet via an on-screen widget. Good examples include those from Uniswap and Radar Relay.
Figure: The wallet widget from Radar Relay
Withdrawals at midnight, UTC
To increase the anonymity set, the UI will force the user to wait till after midnight, UTC time, before they can perform a withdrawal. The UI will only show a button to let the user withdraw funds after midnight. This restriction is only at the UI level.
This approach is a response to an initial technique of waiting for a certain number of deposits or a random time interval, whichever is longer. Said technique, however, would make the mixer vulerable to spam attacks where a user can be deanonymised by an attacker which fills up the deposit limit such that the effective anonymity set of the victim is only 1. Downsides to this technique, however, include the fact that this technique does not strongly guarantee a large anonymity set, especially when usage of the mixer is low.
Additionally, wait times can be adjusted such as once every 6 hours or once an hour.
Olivier from Argent contributed this idea and the above analysis.
EdDSA keystore
The UI will create an EdDSA key each time a user makes a deposit. A user's EdDSA key is essential for them to generate a withdrawal proof. Without this key, they cannot mix or withdraw their funds. As such, if they lose their key, they will lose any funds deposited using it.
The UI ensures availability of keys by storing them in the brower's localStorage. To keep the MVP minimal, which only handles small amounts of ETH, we do not encrypt the key.
Stage 2 ideas (for reference only)
EdDSA key encryption
UIs which handle larger amounts, should encrypt it with a password that only the user knows. TODO: The internal mechanism for key encryption and storage. Check out Hedgehog, which stores an entropy key in the user's localStorage and uses AES-256-CBC along with a username and password to encrypt a BIP-32 seed. Their approach should be examined and adopted if it makes sense. Another good approach is to use NaCl, which offers safe defaults. Also figure out how to isolate EdDSA key management code from the rest of the UI as we need to prevent NPM supply chain attacks. This may entail implementing enhancements to wallets to create a standard EdDSA signing API.
Also see the App Keys standard, currently in progress.
Security model
TODO: Create a security model around key reuse, backup, and loss. Map out risks and potential recovery mechanisms if a user loses their EdDSA key, or if it is stolen.
EdDSA key reuse
TODO: check whether it's okay to reuse keys
EdDSA key compromise
There is a risk that if a user's EdDSA key is stolen by an attacker, such as if malicious code finds its way into the frontend via an NPM security hole and extracts it from
localStorage
.EdDSA key loss
Process flow of withdrawal batching
Once a user makes a deposit, the UI will force them to wait a random amount of time (e.g. between 10 and 30 mins), and then generate the proof required to perform the withdrawal. The proof is then sent to the broadcaster, but it must have enough proofs to batch together to provide a strong enough anonymity set.
HaRold's notes on batching multiple proofs into one
Note that this differs from the MVP proposal of sequentially processing an array of multiple proofs.
Let the safe minimum anonymity set be
n
. In practice, this could be around 6 to 8. The user may also opt for extra privacy, which entails a larger anonymity setk
. Since the Semaphore contract stores a history of up to 100 Merkle tree roots (currently an arbitary number), that is the maximum value ofk
.There needs to be further research to decide on the exact values
n
andk
, where there are acceptable tradeoffs between privacy and waiting time for the minimal mixer.Given the above context, consider the process flow from the broadcaster's point of view. We assume that there are no other anonymity set sizes besides
n
andk
.The broadcaster keeps track of two lists of proofs
b[]
andc[]
. Each proof list has a UUID. It also has a threshold valuet
between 1 and 100 whose use will be described below.Whenever the user submits a proof via a
submitProof(proof, minAnonSet)
API endpiont, the broadcaster follows these steps:If
minAnonSet == n
:1.1. Generate a random value
r
between 0 and 100.1.2. If
r > t
, appendproof
toc
. Next, iflen(c) == k
, asynchronously dispatchc
to themix()
contract function.1.3. If
r <= t
, appendproof
tob
. Next, iflen(b) == n
, asynchronously dispatchb
to themix()
contract function.Otherwise:
2.1. Append
proof
toc
.2.2. If
len(c) == k
, asynchronously dispatchc
to themix()
contract function.Respond with a success code, and if the
mix()
contract function was invoked, its transaction hash.In effect, this algorithm randomly funnels 1 out of 5 users who do not opt for extra privacy to the higher anonymity set of size
k
. This means that the users who do opt for extra privacy do not have to wait for up tok-1
other users who want extra privacy, and100 - t
% of users who go with the default option can enjoy extra privacy. The downside is thatt
% of users who go with the default have to wait slightly longer to get their funds mixed.Note that
t
is an arbitary value. To determine an optimumt
, one should consider the estimated percentage of users who want extra privacy and the ration : k
.Original spec (for reference only)
Roles
Deposit
deposit(uint256 identity_commitment)
Broadcaster registration
register(address pubkey, string tor_address, uint256 fee)
There is a smart contract where anyone can register to be an operator by posting a mapping for their public key to tor address as well as the fee they charge in wei, which they can update.
User can select any of these operators to broadcast their transactions. The operator is selected via
burnedFee
weighted by their fee requirement and some randomness.Broadcaster node
Withdraw
input = hash(recipient, broadcaster, fee)
For each proof in proofs we check
burnedFee[address] -> uint256
mapping.Griefing
There is a possibility that a user sends a transaction to multiple broadcasters. The first one to withdraw gets the fee. The remainder get nothing and lose some of their gas payment.
We mitigate this 5 ways
Contract interaction
Think about how to let mixer interact nicely with smart contracts on eth.
On the way to production
circom
's Pedersen circuits.Transaction abstraction by abusing front-running bots
We should do a test to see nodes will propogate 0 value transactions