This is a placeholder post.
Sending money on-chain today requires manually copying someone's wallet address and transferring ERC20 tokens. This process is cumbersome and requires both parties to coordinate directly. It's also risky—clipboard hacks can lead to sending funds to the wrong address, and there's no recourse for getting those funds back. Additionally, the receiver needs an existing wallet, creating friction for new users who aren't yet set up. Paypal addressed this kind of problem with paypal.me links, making it easy to send money just by sharing a simple link. Web3 could benefit greatly from a similar, permissionless peer-to-peer transfer system, allowing users to send funds securely without requiring prior coordination or revealing addresses upfront. We present a fully decentralized, zero-knowledge (ZK) based peer-to-peer transfer system for ERC20 tokens. In this system, the receiver's wallet address is only revealed after the transfer is submitted, and critical secret redemption keys are never exposed to the mempool, mitigating the risk of front-running and other potential attacks.
Why Existing Solutions Fall Short
Existing solutions like traditional escrow contracts or centralized payment services like Venmo or Paypal fall short because they introduce custody risks. Your funds either aren't self-custodied, meaning you rely on a third party to manage your assets, or they are temporarily held by a third-party smart contract during the redemption process, which exposes them to potential vulnerabilities or mismanagement. Additionally, these centralized entities are subject to regulatory controls and can freeze or restrict access to your funds, limiting financial autonomy.
Early Attempts
Initial Idea: Permit2 with a Secret
One approach we tried first was to have the sender create a Permit2 signature that allows our custom contract to spend a specific amount of a token before a given deadline. The sender also generates a random secret, and the hash of this secret is stored in the 'nonce' field of the Permit2 data. The sender then encodes the secret and Permit2 data into a link, which can be shared with anyone.
The recipient of the link, called the redeemer, uses it to call a redeem function in our contract, passing the secret, its hash, and the Permit2 data. The contract checks if the provided hash matches the stored hash in the nonce field, and if it does, it proceeds with the transfer from the sender to the redeemer's address.
The main problem with this approach is that the secret gets exposed to the node processing the transaction, making it vulnerable to front-running attacks by anyone monitoring the mempool. While using a trusted block builder like Flashbots could mitigate this, it's far from an ideal solution.
Commit-Reveal Approach
To improve on the previous approach, we tried using a common 'commit-reveal' cryptographic procedure. Instead of revealing the secret in the first transaction, the redeemer follows a two-step process:
- Commit Phase: The redeemer passes the hash of their address combined with the secret to the contract by calling the 'commit' function. The contract stores this in a mapping from the redeemer's address to the hash.
- Reveal Phase: The redeemer then calls the 'redeem' function, providing the secret, Permit2 data, and signature. The contract verifies that the caller's address matches the stored hash, and if so, proceeds with the transfer.
While this approach added some security, it still had a critical flaw. If the second transaction fails for any reason (e.g., due to gas issues), the secret is exposed in the mempool, making it susceptible to front-running attacks. No matter how many steps we add, as long as the secret is revealed to anyone other than the sender and redeemer, there is a risk of compromise. This led us to consider a zero-knowledge proof as the next step.
The Zero-Knowledge Solution
To solve the issues of exposing secrets on-chain and vulnerability to front-running, we turned to zero-knowledge (ZK) proofs. This approach allows the redeemer to prove they possess the secret without ever revealing it, ensuring security and privacy throughout the transaction process.
Protocol Overview
The general approach is:
- The sender generates a secret, Permit2 data, and a Permit2 signature, then encodes it into a URL to send to the redeemer.
- Instead of revealing the secret on-chain, the sender creates a ZK proof to demonstrate possession of the secret. This proof is generated client-side, directly in the browser, using Circom circuits and SnarkJS, with Poseidon as the hash function.
- The redeemer uses the link to call the 'redeem' function on the smart contract, providing the ZK proof along with the Permit2 data. The contract verifies the ZK proof without needing access to the actual secret. If validated, the contract transfers the funds from the sender to the redeemer.
This combination of ZK proofs and Permit2 provides an elegant solution with no intermediaries, escrows, or trusted third parties. The sender’s funds remain in their wallet until the redeemer redeems the link, and even then, the contract itself never holds the funds at any point.
Key Properties
- Privacy-Preserving: The ZK proof allows the redeemer to prove possession of the secret without revealing it, ensuring privacy and mitigating front-running attacks.
- No Custody: Funds stay in the sender's wallet until the transaction is completed, ensuring no third party ever has custody of the assets.
- Permissionless and Trustless: No trusted third party or escrow service is required to facilitate the transfer.
- Battle-tested foundation: Our solution relies on Uniswap’s permit2 contract for secure signature based transfers. Permit2 has been extensively audited and used in production.
How It Works
Creating a Payment Link
- The sender uses Circom to generate a unique secret and prepares the Permit2 data.
- The secret, Permit2 data, and Permit2 signature are encoded in a URL, which is then sent to the redeemer.
Redeeming Funds
- The redeemer accesses the link, which triggers the generation of a ZK proof in their browser, proving they possess the secret without exposing it.
- The 'redeem' function is called on the PaymentLink contract, which verifies the ZK proof and executes the token transfer.
Security Considerations
- Front-Running Protection: By ensuring that the secret is never revealed in the mempool, ZK proofs effectively mitigate the risk of front-running attacks.
- Secure Transfer: The funds remain in the sender's wallet until the proof is validated and the transfer is confirmed, ensuring that at no point are funds in the custody of a third party.
Conclusion
Zero-Knowledge Link-Based P2P ERC20 Transfers provide a secure, private, and elegant way to transfer funds without relying on intermediaries or exposing sensitive data on-chain. By leveraging ZK proofs and Permit2, we ensure that funds remain in the sender's control until the moment they are redeemed, effectively mitigating risks like front-running and eliminating custody issues.
All of the code for this solution is open source and available on our GitHub. We also offer an SDK to make integrating this solution into your own projects as easy as possible.
Below, you can find the contract addresses and the list of supported chains.