mirror of
https://github.com/Sunscreen-tech/Sunscreen.git
synced 2026-01-14 16:17:56 -05:00
555 lines
36 KiB
HTML
555 lines
36 KiB
HTML
<!DOCTYPE HTML>
|
|
<html lang="en" class="sidebar-visible no-js light">
|
|
<head>
|
|
<!-- Book generated using mdBook -->
|
|
<meta charset="UTF-8">
|
|
<title>AMMs & private token swaps - Sunscreen Documentation</title>
|
|
|
|
|
|
<!-- Custom HTML head -->
|
|
|
|
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
|
<meta name="description" content="">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="theme-color" content="#ffffff" />
|
|
|
|
<link rel="icon" href="../favicon.svg">
|
|
<link rel="shortcut icon" href="../favicon.png">
|
|
<link rel="stylesheet" href="../css/variables.css">
|
|
<link rel="stylesheet" href="../css/general.css">
|
|
<link rel="stylesheet" href="../css/chrome.css">
|
|
<link rel="stylesheet" href="../css/print.css" media="print">
|
|
|
|
<!-- Fonts -->
|
|
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
|
|
<link rel="stylesheet" href="../fonts/fonts.css">
|
|
|
|
<!-- Highlight.js Stylesheets -->
|
|
<link rel="stylesheet" href="../highlight.css">
|
|
<link rel="stylesheet" href="../tomorrow-night.css">
|
|
<link rel="stylesheet" href="../ayu-highlight.css">
|
|
|
|
<!-- Custom theme stylesheets -->
|
|
|
|
<!-- MathJax -->
|
|
<script async type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
|
</head>
|
|
<body>
|
|
<!-- Provide site root to javascript -->
|
|
<script type="text/javascript">
|
|
var path_to_root = "../";
|
|
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
|
|
</script>
|
|
|
|
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
|
<script type="text/javascript">
|
|
try {
|
|
var theme = localStorage.getItem('mdbook-theme');
|
|
var sidebar = localStorage.getItem('mdbook-sidebar');
|
|
|
|
if (theme.startsWith('"') && theme.endsWith('"')) {
|
|
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
|
}
|
|
|
|
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
|
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
|
}
|
|
} catch (e) { }
|
|
</script>
|
|
|
|
<!-- Set the theme before any content is loaded, prevents flash -->
|
|
<script type="text/javascript">
|
|
var theme;
|
|
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
|
if (theme === null || theme === undefined) { theme = default_theme; }
|
|
var html = document.querySelector('html');
|
|
html.classList.remove('no-js')
|
|
html.classList.remove('light')
|
|
html.classList.add(theme);
|
|
html.classList.add('js');
|
|
</script>
|
|
|
|
<!-- Hide / unhide sidebar before it is displayed -->
|
|
<script type="text/javascript">
|
|
var html = document.querySelector('html');
|
|
var sidebar = 'hidden';
|
|
if (document.body.clientWidth >= 1080) {
|
|
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
|
sidebar = sidebar || 'visible';
|
|
}
|
|
html.classList.remove('sidebar-visible');
|
|
html.classList.add("sidebar-" + sidebar);
|
|
</script>
|
|
|
|
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
|
<div class="sidebar-scrollbox">
|
|
<ol class="chapter"><li class="chapter-item expanded "><a href="../intro/intro.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../intro/prelim.html"><strong aria-hidden="true">1.1.</strong> Prerequisites</a></li><li class="chapter-item expanded "><a href="../intro/why.html"><strong aria-hidden="true">1.2.</strong> Why is a compiler needed for FHE?</a></li><li class="chapter-item expanded "><a href="../intro/compiler.html"><strong aria-hidden="true">1.3.</strong> Sunscreen's compiler</a></li></ol></li><li class="chapter-item expanded "><a href="../getting_started/getting_started.html"><strong aria-hidden="true">2.</strong> Getting started</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../getting_started/installation.html"><strong aria-hidden="true">2.1.</strong> Installation</a></li><li class="chapter-item expanded "><a href="../getting_started/example.html"><strong aria-hidden="true">2.2.</strong> My first FHE program</a></li></ol></li><li class="chapter-item expanded "><a href="../fhe_programs/fhe_programs.html"><strong aria-hidden="true">3.</strong> What's in an FHE program?</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../fhe_programs/types/types.html"><strong aria-hidden="true">3.1.</strong> Types</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../fhe_programs/types/signed.html"><strong aria-hidden="true">3.1.1.</strong> Signed</a></li><li class="chapter-item expanded "><a href="../fhe_programs/types/fractional.html"><strong aria-hidden="true">3.1.2.</strong> Fractional</a></li><li class="chapter-item expanded "><a href="../fhe_programs/types/rational.html"><strong aria-hidden="true">3.1.3.</strong> Rational</a></li></ol></li><li class="chapter-item expanded "><a href="../fhe_programs/writing_an_fhe_program/writing_an_fhe_program.html"><strong aria-hidden="true">3.2.</strong> How to write an FHE program</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../fhe_programs/factoring_fhe_programs.html"><strong aria-hidden="true">3.2.1.</strong> Factoring FHE programs</a></li><li class="chapter-item expanded "><a href="../fhe_programs/writing_an_fhe_program/limitations.html"><strong aria-hidden="true">3.2.2.</strong> Limitations</a></li></ol></li><li class="chapter-item expanded "><a href="../troubleshooting.html"><strong aria-hidden="true">3.3.</strong> Troubleshooting</a></li></ol></li><li class="chapter-item expanded "><a href="../fhe_programs/runtime/runtime.html"><strong aria-hidden="true">4.</strong> Runtime</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../fhe_programs/runtime/key_generation.html"><strong aria-hidden="true">4.1.</strong> Key generation</a></li><li class="chapter-item expanded "><a href="../fhe_programs/runtime/encryption.html"><strong aria-hidden="true">4.2.</strong> Encryption</a></li><li class="chapter-item expanded "><a href="../fhe_programs/runtime/running_fhe_programs.html"><strong aria-hidden="true">4.3.</strong> Running FHE programs</a></li><li class="chapter-item expanded "><a href="../fhe_programs/runtime/decryption.html"><strong aria-hidden="true">4.4.</strong> Decryption</a></li><li class="chapter-item expanded "><a href="../fhe_programs/runtime/serialization.html"><strong aria-hidden="true">4.5.</strong> Serialization</a></li></ol></li><li class="chapter-item expanded "><a href="../fhe_programs/apps.html"><strong aria-hidden="true">5.</strong> Applications</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../fhe_programs/example.html" class="active"><strong aria-hidden="true">5.1.</strong> AMMs & private token swaps</a></li><li class="chapter-item expanded "><a href="../fhe_programs/pir_intro.html"><strong aria-hidden="true">5.2.</strong> Private information retrieval</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../fhe_programs/pir_simple.html"><strong aria-hidden="true">5.2.1.</strong> Take 1</a></li><li class="chapter-item expanded "><a href="../fhe_programs/pir_matrix.html"><strong aria-hidden="true">5.2.2.</strong> Take 2</a></li></ol></li><li class="chapter-item expanded "><div><strong aria-hidden="true">5.3.</strong> Genomics & private tests</div></li></ol></li><li class="chapter-item expanded "><a href="../advanced/faq.html"><strong aria-hidden="true">6.</strong> FAQ</a></li><li class="chapter-item expanded "><a href="../advanced/advanced.html"><strong aria-hidden="true">7.</strong> Advanced topics</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../advanced/bfv.html"><strong aria-hidden="true">7.1.</strong> Why the BFV scheme?</a></li><li class="chapter-item expanded "><a href="../advanced/good_fhe_programs.html"><strong aria-hidden="true">7.2.</strong> Writing even better FHE programs</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../advanced/plain_modulus/plain_modulus.html"><strong aria-hidden="true">7.2.1.</strong> Plaintext modulus</a></li><li class="chapter-item expanded "><a href="../advanced/noise_margin.html"><strong aria-hidden="true">7.2.2.</strong> Noise</a></li><li class="chapter-item expanded "><a href="../advanced/pruning_keys.html"><strong aria-hidden="true">7.2.3.</strong> Pruning public keys</a></li></ol></li><li class="chapter-item expanded "><a href="../advanced/wasm.html"><strong aria-hidden="true">7.3.</strong> WASM support</a></li><li class="chapter-item expanded "><a href="../advanced/carryless_arithmetic.html"><strong aria-hidden="true">7.4.</strong> Funky math: carryless arithmetic</a></li><li class="chapter-item expanded "><div><strong aria-hidden="true">7.5.</strong> Batching</div></li></ol></li></ol>
|
|
</div>
|
|
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
|
</nav>
|
|
|
|
<div id="page-wrapper" class="page-wrapper">
|
|
|
|
<div class="page">
|
|
<div id="menu-bar-hover-placeholder"></div>
|
|
<div id="menu-bar" class="menu-bar sticky bordered">
|
|
<div class="left-buttons">
|
|
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
|
<i class="fa fa-bars"></i>
|
|
</button>
|
|
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
|
<i class="fa fa-paint-brush"></i>
|
|
</button>
|
|
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
|
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
|
</ul>
|
|
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
|
<i class="fa fa-search"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<h1 class="menu-title">Sunscreen Documentation</h1>
|
|
|
|
<div class="right-buttons">
|
|
<a href="../print.html" title="Print this book" aria-label="Print this book">
|
|
<i id="print-button" class="fa fa-print"></i>
|
|
</a>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div id="search-wrapper" class="hidden">
|
|
<form id="searchbar-outer" class="searchbar-outer">
|
|
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
|
</form>
|
|
<div id="searchresults-outer" class="searchresults-outer hidden">
|
|
<div id="searchresults-header" class="searchresults-header"></div>
|
|
<ul id="searchresults">
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
|
<script type="text/javascript">
|
|
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
|
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
|
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
|
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
|
});
|
|
</script>
|
|
|
|
<div id="content" class="content">
|
|
<main>
|
|
<h1 id="private-token-swap-via-automated-market-makers"><a class="header" href="#private-token-swap-via-automated-market-makers">Private token swap (via automated market makers)</a></h1>
|
|
<p>We'll now walk through a less trivial computation that can be done with FHE. Our program is inspired by computations used in <a href="https://docs.uniswap.org/protocol/V2/concepts/protocol-overview/how-uniswap-works">automated market makers</a> (AMMs).
|
|
While some of the code and ideas presented here could be useful for constructing an automated market maker with swap privacy, many details have been omitted.</p>
|
|
<p>Alice would like to swap some NU tokens for some ETH tokens. She'd like to perform this token swap without revealing to anyone her order amount. This might be done to prevent malicious actors from <a href="https://arxiv.org/pdf/1902.05164.pdf">front-running</a> her order.</p>
|
|
<p>To swap her tokens, she interacts with a "pool" that has reserves of both NU and ETH (implemented as a smart contract). For this example, we'll say the pool contains 100 ETH tokens and 1000 NU tokens. The reserve values here are public information. The exchange rate for NU ⇔ ETH <a href="https://docs.uniswap.org/protocol/V2/concepts/core-concepts/swaps">changes</a> based on the pool's reserves of the two tokens. </p>
|
|
<p>Alice will encrypt her order (i.e. the amount of NU tokens she wants to swap) and then submit it to the blockchain miner. The miner can then calculate how much <em>encrypted</em> ETH Alice should receive in exchange for her encrypted amount of NU tokens via FHE.</p>
|
|
<h2 id="an-intro-to-amms-for-the-uninitiated"><a class="header" href="#an-intro-to-amms-for-the-uninitiated">An intro to AMMs for the uninitiated</a></h2>
|
|
<p>If you're not familiar with AMMs, we suggest starting <a href="https://www.coindesk.com/learn/2021/08/20/what-is-an-automated-market-maker/">here</a>.</p>
|
|
<p>AMMs can be a great alternative to centralized exchanges since they allow you to exchange one type of a token for another with (generally) lower fees. Each token pair (in our example, we have NU and ETH) has its own "pool" which users interact with when performing a trade between those two particular tokens. You can also earn passive income from your tokens by providing liquidity (i.e. depositing two tokens) to a specific pool.</p>
|
|
<p>The exchange rate between the two tokens evolves automatically based on a known mathematical formula.</p>
|
|
<p>Unfortunately, the open and public nature of AMMs combined with the predictable behavior of the exchange rate allows for front-running attacks. Bad actors observe pending trades and then submit their own trades to "manipulate" the exchange rate in a way favorable to themselves. What does this mean for you as a potential AMM user? You may end up with a worse price than expected when your trade executes as these front-running attacks are fairly common and widespread. </p>
|
|
<p>Privacy (specifically hiding trade values) is one solution to this front-running problem. </p>
|
|
<h2 id="program-walkthrough"><a class="header" href="#program-walkthrough">Program walkthrough</a></h2>
|
|
<p>Let's look at how to implement this now.</p>
|
|
<h3 id="setup"><a class="header" href="#setup">Setup</a></h3>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span>use sunscreen::{
|
|
fhe_program,
|
|
types::{bfv::Rational, Cipher},
|
|
Ciphertext, CompiledFheProgram, Compiler, Params, PrivateKey, PublicKey,
|
|
Runtime,
|
|
};
|
|
|
|
#[fhe_program(scheme = "bfv")]
|
|
/// This program swaps NU tokens for ETH.
|
|
fn swap_nu(
|
|
nu_tokens_to_trade: Cipher<Rational>,
|
|
) -> Cipher<Rational> {
|
|
let total_eth = 100.0;
|
|
let total_nu = 1_000.0;
|
|
|
|
-(total_eth * total_nu / (total_nu + nu_tokens_to_trade) - total_eth)
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<p>We begin by importing the stuff we're going to use.</p>
|
|
<p>We declare our <code>swap_nu</code> function as an FHE program with the appropriate attribute (<code>#[fhe_program(scheme = "bfv")]</code>).</p>
|
|
<p><code>swap_nu</code> computes how much encrypted ETH a user will receive in exchange for <code>nu_tokens_to_trade</code> some amount of encrypted NU . Since we'll need to divide by a ciphertext, we'll have to use the <code>Rational</code> type here. Thus, notice that <code>swap_nu</code> takes in a <code>Cipher<Rational></code> and returns a <code>Cipher<Rational></code>. If you're wondering where the formula for <code>swap_nu</code> came from, it's from the constant product formula used by some automated market makers.</p>
|
|
<p>Notice that the other values in <code>swap_nu</code> (i.e. the pool reserves for ETH <code>total_eth</code> and NU <code>total_nu</code>) are in the clear.</p>
|
|
<h3 id="alice"><a class="header" href="#alice">Alice</a></h3>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">use sunscreen::{
|
|
</span><span class="boring"> fhe_program,
|
|
</span><span class="boring"> types::{bfv::Rational, Cipher},
|
|
</span><span class="boring"> Ciphertext, CompiledFheProgram, Compiler, Params, PrivateKey,
|
|
</span><span class="boring"> Error,
|
|
</span><span class="boring"> PublicKey,
|
|
</span><span class="boring"> Runtime,
|
|
</span><span class="boring">};
|
|
</span><span class="boring">
|
|
</span>/// Alice is a party that would like to trade some NU for ETH.
|
|
struct Alice {
|
|
/// Alice's public key
|
|
pub public_key: PublicKey,
|
|
|
|
/// Alice's private key
|
|
private_key: PrivateKey,
|
|
|
|
/// Alice's runtime
|
|
runtime: Runtime,
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<p>Alice wants to swap some encrypted (i.e. hidden) amount of NU for an encrypted (i.e. hidden) amount of ETH. She'll need a public/private key pair to do this (since she needs to encrypt her order with respect to her public key).</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">use sunscreen::{
|
|
</span><span class="boring"> fhe_program,
|
|
</span><span class="boring"> types::{bfv::Rational, Cipher},
|
|
</span><span class="boring"> Ciphertext, CompiledFheProgram, Compiler, Params, PrivateKey,
|
|
</span><span class="boring"> Error,
|
|
</span><span class="boring"> PublicKey,
|
|
</span><span class="boring"> Runtime,
|
|
</span><span class="boring">};
|
|
</span><span class="boring">
|
|
</span><span class="boring">/// Alice is a party that would like to trade some NU for ETH.
|
|
</span><span class="boring">struct Alice {
|
|
</span><span class="boring"> /// Alice's public key
|
|
</span><span class="boring"> pub public_key: PublicKey,
|
|
</span><span class="boring">
|
|
</span><span class="boring"> /// Alice's private key
|
|
</span><span class="boring"> private_key: PrivateKey,
|
|
</span><span class="boring">
|
|
</span><span class="boring"> /// Alice's runtime
|
|
</span><span class="boring"> runtime: Runtime,
|
|
</span><span class="boring">}
|
|
</span><span class="boring">
|
|
</span>impl Alice {
|
|
pub fn setup(params: &Params) -> Result<Alice, Error> {
|
|
let runtime = Runtime::new(params)?;
|
|
|
|
let (public_key, private_key) = runtime.generate_keys()?;
|
|
|
|
Ok(Alice {
|
|
public_key,
|
|
private_key,
|
|
runtime,
|
|
})
|
|
}
|
|
|
|
pub fn create_transaction(&self, amount: f64) -> Result<Ciphertext, Error> {
|
|
Ok(self.runtime
|
|
.encrypt(Rational::try_from(amount)?, &self.public_key)?
|
|
)
|
|
}
|
|
|
|
pub fn check_received_eth(&self, received_eth: Ciphertext) -> Result<(), Error> {
|
|
let received_eth: Rational = self
|
|
.runtime
|
|
.decrypt(&received_eth, &self.private_key)?;
|
|
|
|
let received_eth: f64 = received_eth.into();
|
|
|
|
println!("Alice received {}ETH", received_eth);
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<p>Alice first constructs a runtime and then can generate her public/private key pair.</p>
|
|
<p>To encrypt her order amount, she'll call <code>create_transaction</code> passing in the <code>amount</code> of NU she wants to trade and her<code>public_key</code>. We need <code>try_from</code> here to help us perform the appropriate type conversion.</p>
|
|
<p>We won't use this until the very end but <code>check_received_eth</code> will allow Alice to see how many ETH tokens she's received after performing the swap. Recall that Alice will receive an encrypted amount of ETH tokens, so in <code>check_received_eth</code> Alice will decrypt this value by passing in her <code>private_key</code> and <code>received_eth</code> the encrypted amount of ETH she received.</p>
|
|
<h3 id="miner"><a class="header" href="#miner">Miner</a></h3>
|
|
<p>Let's look at the miner next.</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">use sunscreen::{
|
|
</span><span class="boring"> fhe_program,
|
|
</span><span class="boring"> types::{bfv::Rational, Cipher},
|
|
</span><span class="boring"> Ciphertext, CompiledFheProgram, Compiler, Params, PrivateKey,
|
|
</span><span class="boring"> Error,
|
|
</span><span class="boring"> PublicKey,
|
|
</span><span class="boring"> Runtime,
|
|
</span><span class="boring">};
|
|
</span>/// Imagine this is a miner in a blockchain application. They're responsible
|
|
/// for processing transactions
|
|
struct Miner {
|
|
/// The compiled swap_nu program
|
|
pub compiled_swap_nu: CompiledFheProgram,
|
|
|
|
/// The Miner's runtime
|
|
runtime: Runtime,
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<p>Recall that the miner is responsible for processing Alice's order; thus, he'll have to run the compiled <code>swap_nu</code> program (<code>compiled_swap_nu</code>).</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">use sunscreen::{
|
|
</span><span class="boring"> fhe_program,
|
|
</span><span class="boring"> types::{bfv::Rational, Cipher},
|
|
</span><span class="boring"> Ciphertext, CompiledFheProgram, Compiler, Params, PrivateKey,
|
|
</span><span class="boring"> Error,
|
|
</span><span class="boring"> PublicKey,
|
|
</span><span class="boring"> Runtime,
|
|
</span><span class="boring">};
|
|
</span><span class="boring">
|
|
</span><span class="boring">#[fhe_program(scheme = "bfv")]
|
|
</span><span class="boring">/// This program swaps NU tokens for ETH.
|
|
</span><span class="boring">fn swap_nu(
|
|
</span><span class="boring"> nu_tokens_to_trade: Cipher<Rational>,
|
|
</span><span class="boring">) -> Cipher<Rational> {
|
|
</span><span class="boring"> let total_eth = 100.0;
|
|
</span><span class="boring"> let total_nu = 1_000.0;
|
|
</span><span class="boring">
|
|
</span><span class="boring"> -(total_eth * total_nu / (total_nu + nu_tokens_to_trade) - total_eth)
|
|
</span><span class="boring">}
|
|
</span><span class="boring">
|
|
</span><span class="boring">/// Imagine this is a miner in a blockchain application. They're responsible
|
|
</span><span class="boring">/// for processing transactions
|
|
</span><span class="boring">struct Miner {
|
|
</span><span class="boring"> /// The compiled FHE swap program
|
|
</span><span class="boring"> pub compiled_swap_nu: CompiledFheProgram,
|
|
</span><span class="boring">
|
|
</span><span class="boring"> /// The Miner's runtime
|
|
</span><span class="boring"> runtime: Runtime,
|
|
</span><span class="boring">}
|
|
</span><span class="boring">
|
|
</span>impl Miner {
|
|
pub fn setup() -> Result<Miner, Error> {
|
|
let compiled_swap_nu = Compiler::with_fhe_program(swap_nu).compile()?;
|
|
|
|
let runtime = Runtime::new(&compiled_swap_nu.metadata.params)?;
|
|
|
|
Ok(Miner {
|
|
compiled_swap_nu,
|
|
runtime,
|
|
})
|
|
}
|
|
|
|
pub fn run_contract(
|
|
&self,
|
|
nu_tokens_to_trade: Ciphertext,
|
|
public_key: &PublicKey,
|
|
) -> Result<Ciphertext, Error> {
|
|
let results = self.runtime.run(&self.compiled_swap_nu, vec![nu_tokens_to_trade], public_key)?;
|
|
|
|
Ok(results[0].clone())
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<p>In <code>setup</code>, we compile <code>swap_nu</code> and save the runnable program as <code>compiled_swap_nu</code>.
|
|
We also construct and save a <code>Runtime</code> for our miner to allow him to run it.</p>
|
|
<p>The miner can run the token swap contract (see <code>run_contract</code>) by calling <code>runtime.run</code> with the <code>compiled_swap_nu</code> program, Alice's encrypted order amount (<code>nu_tokens_to_trade</code>), and Alice's <code>public_key</code>. Recall that we must pass in arguments to an FHE program (such as <code>compiled_swap_nu</code>) via a <code>Vec</code>.</p>
|
|
<h3 id="swapping-the-tokens-privately"><a class="header" href="#swapping-the-tokens-privately">Swapping the tokens privately</a></h3>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021"><span class="boring">use sunscreen::{
|
|
</span><span class="boring"> fhe_program,
|
|
</span><span class="boring"> types::{bfv::Rational, Cipher},
|
|
</span><span class="boring"> Ciphertext, CompiledFheProgram, Compiler, Params, PrivateKey,
|
|
</span><span class="boring"> Error,
|
|
</span><span class="boring"> PublicKey,
|
|
</span><span class="boring"> Runtime,
|
|
</span><span class="boring">};
|
|
</span><span class="boring">
|
|
</span><span class="boring"> #[fhe_program(scheme = "bfv")]
|
|
</span><span class="boring">/// This program swaps NU tokens to receive ETH.
|
|
</span><span class="boring">fn swap_nu(
|
|
</span><span class="boring"> nu_tokens_to_trade: Cipher<Rational>,
|
|
</span><span class="boring">) -> Cipher<Rational> {
|
|
</span><span class="boring"> let total_eth = 100.0;
|
|
</span><span class="boring"> let total_nu = 1_000.0;
|
|
</span><span class="boring">
|
|
</span><span class="boring"> -(total_eth * total_nu / (total_nu + nu_tokens_to_trade) - total_eth)
|
|
</span><span class="boring">}
|
|
</span><span class="boring">
|
|
</span><span class="boring">/// Imagine this is a miner in a blockchain application. They're responsible
|
|
</span><span class="boring">/// for processing transactions
|
|
</span><span class="boring">struct Miner {
|
|
</span><span class="boring"> /// The compiled swap_nu program
|
|
</span><span class="boring"> pub compiled_swap_nu: CompiledFheProgram,
|
|
</span><span class="boring">
|
|
</span><span class="boring"> /// The Miner's runtime
|
|
</span><span class="boring"> runtime: Runtime,
|
|
</span><span class="boring">}
|
|
</span><span class="boring">
|
|
</span><span class="boring">impl Miner {
|
|
</span><span class="boring"> pub fn setup() -> Result<Miner, Error> {
|
|
</span><span class="boring"> let compiled_swap_nu = Compiler::with_fhe_program(swap_nu).compile()?;
|
|
</span><span class="boring">
|
|
</span><span class="boring"> let runtime = Runtime::new(&compiled_swap_nu.metadata.params)?;
|
|
</span><span class="boring">
|
|
</span><span class="boring"> Ok(Miner {
|
|
</span><span class="boring"> compiled_swap_nu,
|
|
</span><span class="boring"> runtime,
|
|
</span><span class="boring"> })
|
|
</span><span class="boring"> }
|
|
</span><span class="boring">
|
|
</span><span class="boring"> pub fn run_contract(
|
|
</span><span class="boring"> &self,
|
|
</span><span class="boring"> nu_tokens_to_trade: Ciphertext,
|
|
</span><span class="boring"> public_key: &PublicKey,
|
|
</span><span class="boring"> ) -> Result<Ciphertext, Error> {
|
|
</span><span class="boring"> let results = self.runtime.run(&self.compiled_swap_nu, vec![nu_tokens_to_trade], public_key)?;
|
|
</span><span class="boring">
|
|
</span><span class="boring"> Ok(results[0].clone())
|
|
</span><span class="boring"> }
|
|
</span><span class="boring">}
|
|
</span><span class="boring">
|
|
</span><span class="boring">/// Alice is a party that would like to trade some NU for ETH.
|
|
</span><span class="boring">struct Alice {
|
|
</span><span class="boring"> /// Alice's public key
|
|
</span><span class="boring"> pub public_key: PublicKey,
|
|
</span><span class="boring">
|
|
</span><span class="boring"> /// Alice's private key
|
|
</span><span class="boring"> private_key: PrivateKey,
|
|
</span><span class="boring">
|
|
</span><span class="boring"> /// Alice's runtime
|
|
</span><span class="boring"> runtime: Runtime,
|
|
</span><span class="boring">}
|
|
</span><span class="boring">
|
|
</span><span class="boring">impl Alice {
|
|
</span><span class="boring"> pub fn setup(params: &Params) -> Result<Alice, Error> {
|
|
</span><span class="boring"> let runtime = Runtime::new(params)?;
|
|
</span><span class="boring">
|
|
</span><span class="boring"> let (public_key, private_key) = runtime.generate_keys()?;
|
|
</span><span class="boring">
|
|
</span><span class="boring"> Ok(Alice {
|
|
</span><span class="boring"> public_key,
|
|
</span><span class="boring"> private_key,
|
|
</span><span class="boring"> runtime,
|
|
</span><span class="boring"> })
|
|
</span><span class="boring"> }
|
|
</span><span class="boring">
|
|
</span><span class="boring"> pub fn create_transaction(&self, amount: f64) -> Result<Ciphertext, Error> {
|
|
</span><span class="boring"> Ok(self.runtime
|
|
</span><span class="boring"> .encrypt(Rational::try_from(amount)?, &self.public_key)?
|
|
</span><span class="boring"> )
|
|
</span><span class="boring"> }
|
|
</span><span class="boring">
|
|
</span><span class="boring"> pub fn check_received_eth(&self, received_eth: Ciphertext) -> Result<(), Error> {
|
|
</span><span class="boring"> let received_eth: Rational = self
|
|
</span><span class="boring"> .runtime
|
|
</span><span class="boring"> .decrypt(&received_eth, &self.private_key)?;
|
|
</span><span class="boring">
|
|
</span><span class="boring"> let received_eth: f64 = received_eth.into();
|
|
</span><span class="boring">
|
|
</span><span class="boring"> println!("Alice received {}ETH", received_eth);
|
|
</span><span class="boring">
|
|
</span><span class="boring"> Ok(())
|
|
</span><span class="boring"> }
|
|
</span><span class="boring">}
|
|
</span><span class="boring">
|
|
</span>fn main() -> Result<(), Error> {
|
|
// Set up the miner with some NU and ETH tokens.
|
|
let miner = Miner::setup()?;
|
|
|
|
// Alice sets herself up. The FHE scheme parameters are public to the
|
|
// protocol, so Alice has them.
|
|
let alice = Alice::setup(&miner.compiled_swap_nu.metadata.params)?;
|
|
|
|
let transaction = alice.create_transaction(20.0)?;
|
|
|
|
let encrypted_received_eth =
|
|
miner.run_contract(transaction, &alice.public_key)?;
|
|
|
|
alice.check_received_eth(encrypted_received_eth)?;
|
|
|
|
Ok(())
|
|
}
|
|
</code></pre></pre>
|
|
<p>We set up the miner and then Alice (notice that Alice relies on parameters generated from the Miner's setup). Both of them must use the same set of FHE scheme parameters for compatibility. In deployment, these values would likely be fixed at the protocol level.</p>
|
|
<p>Alice calls <code>create_transaction</code> to encrypt her trade amount of <code>20.0</code> NU tokens.</p>
|
|
<p>The miner calls <code>run_contract</code> to calculate how much encrypted ETH Alice will receive for her encrypted NU (based on the formula from <code>swap_nu</code>). The miner passes in Alice's encrypted trade amount (the result of <code>alice.create_transaction(20.0)</code> which is a ciphertext) along with Alice's public key (<code>alice.public_key</code>).</p>
|
|
<p>Finally, Alice can determine how much ETH she actually received from the swap via <code>check_received_eth</code>.</p>
|
|
<h3 id="performance"><a class="header" href="#performance">Performance</a></h3>
|
|
<p>The entire program (not including compilation time) takes ~25 ms on an Intel Xeon @ 3.0 GHz (with 8 cores and 16 GB RAM) and ~100 ms on a Macbook Air M1.</p>
|
|
<h2 id="whats-missing"><a class="header" href="#whats-missing">What's missing?</a></h2>
|
|
<p>For simplicity, we've omitted many details that are needed to actually execute a private token swap in real life. You may have noticed we mentioned nothing about Alice's account balance (deducting the amount of NU she wants to swap or adding the amount of ETH she receives), ensuring that Alice is behaving honestly (e.g. she actually has enough NU in her account to make the swap, she isn't creating tokens out of thin air), or how to determine the new reserve values of the pool (i.e. how much NU and ETH are in the pool after Alice has made her swap).</p>
|
|
<p>If you're curious about the answers:</p>
|
|
<ul>
|
|
<li>we've omitted account balances for simplicity (but such account balances would be encrypted as well)</li>
|
|
<li>to ensure Alice is behaving honestly, we would need additional cryptographic tools such as zero-knowledge proofs</li>
|
|
<li>the primary goal of private token swaps would be to prevent <a href="https://ethereum.org/en/developers/docs/mev/#mev-examples-sandwich-trading">front-running</a>, thus there would be some additional step to "reveal" the new reserve values </li>
|
|
</ul>
|
|
|
|
</main>
|
|
|
|
<nav class="nav-wrapper" aria-label="Page navigation">
|
|
<!-- Mobile navigation buttons -->
|
|
<a rel="prev" href="../fhe_programs/apps.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|
<i class="fa fa-angle-left"></i>
|
|
</a>
|
|
|
|
<a rel="next" href="../fhe_programs/pir_intro.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
|
<i class="fa fa-angle-right"></i>
|
|
</a>
|
|
|
|
<div style="clear: both"></div>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
|
<a rel="prev" href="../fhe_programs/apps.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|
<i class="fa fa-angle-left"></i>
|
|
</a>
|
|
|
|
<a rel="next" href="../fhe_programs/pir_intro.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
|
<i class="fa fa-angle-right"></i>
|
|
</a>
|
|
</nav>
|
|
|
|
</div>
|
|
|
|
<!-- Livereload script (if served using the cli tool) -->
|
|
<script type="text/javascript">
|
|
var socket = new WebSocket("ws://localhost:3000/__livereload");
|
|
socket.onmessage = function (event) {
|
|
if (event.data === "reload") {
|
|
socket.close();
|
|
location.reload();
|
|
}
|
|
};
|
|
|
|
window.onbeforeunload = function() {
|
|
socket.close();
|
|
}
|
|
</script>
|
|
|
|
|
|
|
|
<script type="text/javascript">
|
|
window.playground_copyable = true;
|
|
</script>
|
|
|
|
|
|
<script src="../elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="../mark.min.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="../searcher.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
<script src="../clipboard.min.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="../highlight.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="../book.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
<!-- Custom JS scripts -->
|
|
|
|
|
|
</body>
|
|
</html>
|