Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4,794 changes: 2,762 additions & 2,032 deletions Cargo.lock

Large diffs are not rendered by default.

74 changes: 44 additions & 30 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
arti-client = { version = "0.18.0", default-features = false, features = ["async-std", "rustls", "onion-service-client", "onion-service-service"] }
arti-hyper = "0.18.0"
arti-client = { version = "0.44.0", features = ["static", "pt-client", "onion-service-service", "onion-service-client"] }
async-std = { version = "1", features = ["tokio1"] }
async-trait = "0.1.74"
blake2 = { package = "blake2-rfc", version = "0.2" }
Expand All @@ -22,10 +21,13 @@ dirs = "2.0"
ed25519-dalek = "2.1.1"
function_name = "0.3.0"
futures = "0.3"
fs-mistrust = "0.7.9"
fs-mistrust = "0.15.0"
hmac = { version = "0.12.0", features = ["std"] }
hyper = "0.14.28"
hyper-tls = "0.6.0"
http-body-util = "0.1.3"
hyper = { version = "1.10.1", features = ["http1", "client"] }
hyper-util = { version = "0.1.20", features = ["tokio"] }
hyper_legacy = { package = "hyper", version = "0.14.32", features = ["client", "http1", "tcp"] }
hyper-tls = "0.5.0"
itertools = { version = "0.12.0" }
jsonrpc-core = "18.0.0"
jsonrpc-derive = "18.0.0"
Expand All @@ -36,38 +38,50 @@ rand = "0.7.3"
remove_dir_all = "0.8.2"
ring = "0.16"
rpassword = "4.0"
rusqlite = { version = "0.31.0", features = ["bundled"] }
rusqlite = { version = "0.36.0", features = ["bundled"] }
serde = { version = "1", features = ["derive"] }
serde_derive = "1"
serde_json = "1"
sha2 = "0.10.0"
thiserror = "1.0.30"
tls-api = "0.9.0"
tls-api-native-tls = "0.9.0"
tokio = { version = "1.37.0", features = ["full"] }
toml = "0.8.8"
tor-hscrypto = "0.18.0"
tor-hsrproxy = "0.18.0"
tor-hsservice = "0.18.0"
tor-llcrypto = "0.18.0"
tor-keymgr = "0.18.0"
tor-rtcompat = "0.18.0"
x25519-dalek = "0.6.0"
tor-hscrypto = "0.44.0"
tor-hsrproxy = "0.44.0"
tor-hsservice = "0.44.0"
tor-llcrypto = "0.44.0"
tor-keymgr = "0.44.0"
tor-log-ratelim = "0.44.0"
tor-rtcompat = { version = "0.44.0", features = ["static"] }
log = "0.4.20"

# Bleeding Edge Grin Deps
grin_secp256k1zkp = { version = "0.7.14", features = ["bullet-proof-sizing"] }
grin_api = { git = "https://github.com/mimblewimble/grin", branch = "master" }
grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" }
grin_chain = { git = "https://github.com/mimblewimble/grin", branch = "master" }
grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "master" }
grin_p2p = { git = "https://github.com/mimblewimble/grin", branch = "master" }
grin_servers = { git = "https://github.com/mimblewimble/grin", branch = "master" }
grin_store = { git = "https://github.com/mimblewimble/grin", branch = "master" }
grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" }
grin_wallet_api = { git = "https://github.com/mimblewimble/grin-wallet", branch = "contracts" }
grin_wallet_config = { git = "https://github.com/mimblewimble/grin-wallet", branch = "contracts" }
grin_wallet_controller = { git = "https://github.com/mimblewimble/grin-wallet", branch = "contracts" }
grin_wallet_impls = { git = "https://github.com/mimblewimble/grin-wallet", branch = "contracts" }
grin_wallet_libwallet = { git = "https://github.com/mimblewimble/grin-wallet", branch = "contracts" }
grin_wallet_util = { git = "https://github.com/mimblewimble/grin-wallet", branch = "contracts" }
grin_secp256k1zkp = { version = "0.7.15", features = ["bullet-proof-sizing"] }
grin_api = { git = "https://github.com/mimblewimble/grin", branch = "staging" }
grin_core = { git = "https://github.com/mimblewimble/grin", branch = "staging" }
grin_chain = { git = "https://github.com/mimblewimble/grin", branch = "staging" }
grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "staging" }
grin_p2p = { git = "https://github.com/mimblewimble/grin", branch = "staging" }
grin_servers = { git = "https://github.com/mimblewimble/grin", branch = "staging" }
grin_store = { git = "https://github.com/mimblewimble/grin", branch = "staging" }
grin_util = { git = "https://github.com/mimblewimble/grin", branch = "staging" }
grin_wallet_api = { git = "https://github.com/wiesche89/grin-wallet", rev = "fed6733a2209aec2ed963a5691d91c6f00b4261d" }
grin_wallet_config = { git = "https://github.com/wiesche89/grin-wallet", rev = "fed6733a2209aec2ed963a5691d91c6f00b4261d" }
grin_wallet_controller = { git = "https://github.com/wiesche89/grin-wallet", rev = "fed6733a2209aec2ed963a5691d91c6f00b4261d" }
grin_wallet_impls = { git = "https://github.com/wiesche89/grin-wallet", rev = "fed6733a2209aec2ed963a5691d91c6f00b4261d" }
grin_wallet_libwallet = { git = "https://github.com/wiesche89/grin-wallet", rev = "fed6733a2209aec2ed963a5691d91c6f00b4261d" }
grin_wallet_util = { git = "https://github.com/wiesche89/grin-wallet", rev = "fed6733a2209aec2ed963a5691d91c6f00b4261d" }

[dev-dependencies]
grin_wallet_libwallet = { git = "https://github.com/wiesche89/grin-wallet", rev = "fed6733a2209aec2ed963a5691d91c6f00b4261d", features = ["mwixnet-test"] }
x25519-dalek = { version = "2", features = ["static_secrets"] }

[patch."https://github.com/wiesche89/grin-wallet"]
grin_api = { git = "https://github.com/mimblewimble/grin", branch = "staging" }
grin_chain = { git = "https://github.com/mimblewimble/grin", branch = "staging" }
grin_core = { git = "https://github.com/mimblewimble/grin", branch = "staging" }
grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "staging" }
grin_p2p = { git = "https://github.com/mimblewimble/grin", branch = "staging" }
grin_pool = { git = "https://github.com/mimblewimble/grin", branch = "staging" }
grin_store = { git = "https://github.com/mimblewimble/grin", branch = "staging" }
grin_util = { git = "https://github.com/mimblewimble/grin", branch = "staging" }
23 changes: 18 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,29 @@ We refer to the remaining servers (N<sub>2</sub>...N<sub>n</sub>) as "Mixers."
#### init-config
To setup a new server, run `mwixnet init-config`. Then enter a password for the server key when prompted.

This will generate a key for the server and then create a new config file named `mwixnet-config.toml` in the current working directory.
This generates a server key and writes `mwixnet-config.toml` to the selected network directory (for example `~/.grin/test` for testnet). Use `--config_file` to choose another path.
The configuration file will contain the private key of the server encrypted with the server password you provided.

**Back this config file up! It's the only copy of the server's private key!**

#### Wallet
A grin-wallet account must be created for receiving extra mwixnet fees. The wallet's owner API should be available (run `grin-wallet owner_api`).

To run without a server wallet, set `collect_fees = false` in the config or pass `--no_fee_collection`. Hop fees are then paid entirely to miners.

### Usage
With your wallet and fully synced node both online and listening at the addresses configured, the mwixnet server can be started by running `mwixnet` and providing the server key password and wallet password when prompted.
With a fully synced node and, when collecting fees, a wallet listening at the configured addresses, start the server by running `mwixnet` and entering the requested passwords.

`mwixnet pubkey` outputs the server's Ed25519 identity key used to configure adjacent servers.
`mwixnet onion-pubkey` outputs the server's X25519 onion encryption key that clients provide to their wallets when creating mwixnet requests.

#### Wallet workflow

1. Collect each server's X25519 onion key in route order.
2. Run the wallet's experimental `mwixnet` command with an eligible output commitment, the first server's onion address, `fee_per_hop`, and the ordered server keys.
3. The wallet creates and locks the request, then submits it to the first server's `/v1` endpoint.

Owner API clients can instead call `create_mwixnet_req` and submit the returned `request` field themselves. The result also contains the associated `tx_id` when locking was requested. Routing between servers is fixed by each server's `prev_server` and `next_server` configuration.

### SWAP API
The Swap Server (N<sub>1</sub>) provides the `swap` API, which is publicly available for use by GRIN wallets.
Expand Down Expand Up @@ -51,8 +64,8 @@ The Swap Server (N<sub>1</sub>) provides the `swap` API, which is publicly avail
<ol>
<li>Choose random x<sub>i</sub> for each node n<sub>i</sub> and create a Payload (P<sub>i</sub>) for each containing x<sub>i</sub></li>
<li>Build a rangeproof for C<sub>n</sub>=C<sub>in</sub>+(Σx<sub>1...n</sub>)*G and include it in payload P<sub>n</sub></li>
<li>Choose random initial ephemeral keypair (r<sub>1</sub>, R<sub>1</sub>)</li>
<li>Derive remaining ephemeral keypairs such that r<sub>i+1</sub>=r<sub>i</sub>*Sha256(R<sub>i</sub>||s<sub>i</sub>) where s<sub>i</sub>=ECDH(R<sub>i</sub>, K<sub>i</sub>)</li>
<li>Choose a random ephemeral keypair (r<sub>i</sub>, R<sub>i</sub>) for each hop</li>
<li>For each hop, derive s<sub>i</sub>=ECDH(r<sub>i</sub>, K<sub>i</sub>) and include R<sub>i+1</sub> in P<sub>i</sub></li>
<li>For each node n<sub>i</sub>, use ChaCha20 stream cipher with key=HmacSha256("MWIXNET"||s<sub>i</sub>) and nonce "NONCE1234567" to encrypt payloads P<sub>i...n</sub></li>
</ol>

Expand All @@ -63,4 +76,4 @@ The Swap Server (N<sub>1</sub>) provides the `swap` API, which is publicly avail

----

`Output derivation`, `Output validation`, `Kernel derivation`, and `Aggregation` steps remain unchanged from the [original design](https://forum.grin.mw/t/mimblewimble-coinswap-proposal/8322)
`Output derivation`, `Output validation`, `Kernel derivation`, and `Aggregation` steps remain unchanged from the [original design](https://forum.grin.mw/t/mimblewimble-coinswap-proposal/8322)
10 changes: 5 additions & 5 deletions doc/store.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

## Overview

The `SwapStore` is an lmdb database, responsible for storing unprocessed and in-process `SwapData` entries.
The `SwapStore` is an LMDB database for swap entries and generated swap transactions.

The `SwapStore` is used to hold onto new `SwapData` entries until the next swap round, when the mixing process actually occurs. At that time, they will be marked as `InProcess` until the swap is in a confirmed transaction, at which time they will be marked `Completed` and eventually erased.
New entries remain `Unprocessed` until a round uses them. A successful round marks them `InProcess`; entries rejected by a downstream mixer become `Failed`. Reorg checks can rebroadcast or rebuild the transaction. Automatic transition to `Completed` and deletion are not currently implemented.

## Data Model

Expand All @@ -22,6 +22,6 @@ The `SwapData` structure contains information needed to swap a single output. It
- `onion`: The remaining onion after peeling off our layer.
- `status`: The status of the swap, represented by the `SwapStatus` enum, which can be one of the following:
- `Unprocessed`: The swap has been received but not yet processed.
- `InProcess { kernel_hash: Hash }`: The swap is currently being processed, and is expected to be a transaction with the kernel matching the given `kernel_hash`.
- `Completed { kernel_hash: Hash, block_hash: Hash }`: The swap has been successfully processed and included in the block matching the given `block_hash`.
- `Failed`: The swap has failed, potentially due to expiration or because the output is no longer in the UTXO set.
- `InProcess { kernel_commit: Commitment }`: The swap was included in a generated transaction identified by its kernel commitment.
- `Completed { kernel_commit: Commitment, block_hash: Hash }`: Reserved for a swap confirmed in the given block; the current server does not set this status.
- `Failed`: The swap has failed, potentially due to expiration or because the output is no longer in the UTXO set.
12 changes: 7 additions & 5 deletions doc/swap_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ The Swap Server provides a single JSON-RPC API with the method `swap`. This API
## SWAP

### Request
The `swap` method accepts a single JSON object containing the following fields:
The `swap` method accepts one positional parameter: a `SwapReq` object containing:

- `onion`: an `Onion` data structure, which is the encrypted onion packet containing the key information necessary to transform the user's output.
- `comsig`: a Commitment Signature that proves the client knows the secret key and value of the output's commitment.
Expand All @@ -20,20 +20,20 @@ The `Onion` data structure consists of the following fields:
- `commit`: the Pedersen commitment before adjusting the excess and subtracting the fee, represented as a 33-byte `secp256k1` Pedersen commitment.
- `data`: a vector of encrypted payloads, each representing a layer of the onion. When completely decrypted, these are serialized `Payload` objects.

Each entry in the `enc_payloads` vector corresponds to a server in the system, in order, with the first entry containing the payload for the swap server, and the last entry containing the payload for the final mix server.
Each entry in `data` corresponds to one server in route order, from the swap server to the final mixer.

#### `Payload` data structure

A `Payload` represents a single, decrypted/peeled layer of an Onion. It consists of the following fields:

- `next_ephemeral_pk`: an `xPublicKey` representing the public key for the next layer.
- `next_ephemeral_pk`: the X25519 ephemeral public key for the next layer.
- `excess`: a `SecretKey` representing the excess value.
- `fee`: a `FeeFields` value representing the transaction fee.
- `rangeproof`: an optional `RangeProof` value.

### Response

A successful call to the 'swap' API will result in an empty JSON-RPC response with no error.
A successful call returns the JSON-RPC result `"success"`.

In case of errors, the API will return a `SwapError` type with one of the following variants:

Expand All @@ -46,6 +46,8 @@ In case of errors, the API will return a `SwapError` type with one of the follow
- `PeelOnionFailure`: Failed to peel onion layer due to an `OnionError`.
- `FeeTooLow`: The provided fee is too low.
- `StoreError`: An error occurred when saving swap to the data store.
- `TxError`: An error occurred while building the swap transaction.
- `NodeError`: An error occurred while communicating with the Grin node.
- `ClientError`: An error occurred during client communication.
- `SwapTxNotFound`: The previous swap transaction was not found in data store.
- `UnknownError`: An unknown error occurred.
Expand All @@ -70,4 +72,4 @@ Here is an example of how to call the 'swap' API:
},
"id": "1"
}
```
```
Loading