mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-10 07:08:05 -05:00
bin: Remove darkotc and merge it with drk
This commit is contained in:
17
Cargo.lock
generated
17
Cargo.lock
generated
@@ -1342,23 +1342,6 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darkotc"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"bs58",
|
||||
"clap 4.0.29",
|
||||
"darkfi",
|
||||
"darkfi-sdk",
|
||||
"darkfi-serial",
|
||||
"halo2_gadgets",
|
||||
"halo2_proofs",
|
||||
"rand",
|
||||
"serde_json",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darkwiki"
|
||||
version = "0.4.0"
|
||||
|
||||
@@ -22,7 +22,6 @@ members = [
|
||||
"bin/zkas",
|
||||
#"bin/cashierd",
|
||||
"bin/darkfid",
|
||||
"bin/darkotc",
|
||||
"bin/drk",
|
||||
"bin/faucetd",
|
||||
"bin/fud/fu",
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
[package]
|
||||
name = "darkotc"
|
||||
version = "0.3.0"
|
||||
homepage = "https://dark.fi"
|
||||
description = "Atomic swap commandline tool"
|
||||
authors = ["Dyne.org foundation <foundation@dyne.org>"]
|
||||
repository = "https://github.com/darkrenaissance/darkfi"
|
||||
license = "AGPL-3.0-only"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
async-std = {version = "1.12.0", features = ["attributes"]}
|
||||
bs58 = "0.4.0"
|
||||
clap = {version = "4.0.29", features = ["derive"]}
|
||||
darkfi = {path = "../../", features = ["crypto", "rpc", "util", "tx"]}
|
||||
darkfi-sdk = {path = "../../src/sdk"}
|
||||
darkfi-serial = {path = "../../src/serial"}
|
||||
halo2_proofs = "0.2.0"
|
||||
halo2_gadgets = "0.2.0"
|
||||
rand = "0.8.5"
|
||||
serde_json = "1.0.89"
|
||||
url = "2.3.1"
|
||||
@@ -1,142 +0,0 @@
|
||||
darkotc
|
||||
=======
|
||||
|
||||
Commandline tool for atomic swaps.
|
||||
|
||||
|
||||
## Usage (localnet)
|
||||
|
||||
Prerequisite: Compile all the tools:
|
||||
|
||||
```
|
||||
% make BINS="darkfid drk faucetd darkotc"
|
||||
```
|
||||
|
||||
First start the localnet simulation from `contrib/localnet/darkfid`
|
||||
called `tmux_sessions.sh` and allow them all to run and sync up.
|
||||
|
||||
We will communicate with two darkfid nodes which represent two peers
|
||||
in the atomic swap, and with the faucet, so we can get some coins in
|
||||
our wallets.
|
||||
|
||||
* The first endpoint (Alice) JSON-RPC URI is: `tcp://127.0.0.1:8440`.
|
||||
* The second endpoint (Bob) JSON-RPC URI is: `tcp://127.0.0.1:8540`.
|
||||
* The faucet JSON-RPC URI is: `tcp://127.0.0.1:8640`.
|
||||
|
||||
|
||||
### Airdropping shitcoins
|
||||
|
||||
Once the daemons are running, we can interact with them. First we
|
||||
will airdrop some coins to both Alice and Bob, so they can swap them
|
||||
between each other. We'll also export some variables for easier usage.
|
||||
|
||||
```
|
||||
% export ALICE_RPC="tcp://127.0.0.1:8440"
|
||||
% export BOB_RPC="tcp://127.0.0.1:8540"
|
||||
% export FAUCET_RPC="tcp://127.0.0.1:8640"
|
||||
% export ALICE_TOKEN="A7f1RKsCUUHrSXA7a9ogmwg8p3bs6F47ggsW826HD4yd"
|
||||
% export BOB_TOKEN="BNBZ9YprWvEGMYHW4dFvbLuLfHnN9Bs64zuTFQAbw9Dy"
|
||||
% ./drk -e "${ALICE_RPC}" airdrop --faucet-endpoint "${FAUCET_RPC}" \
|
||||
--token-id "${ALICE_TOKEN}" 113
|
||||
% ./drk -e "${BOB_RPC}" airdrop --faucet-endpoint "${FAUCET_RPC}" \
|
||||
--token-id "${BOB_TOKEN}" 14
|
||||
```
|
||||
|
||||
This will airdrop 113 `ALICE_TOKEN` into Alice's wallet, and 14
|
||||
`BOB_TOKEN` into Bob's wallet.
|
||||
|
||||
Wait a bit until the transactions settle on the blockchain and they
|
||||
appear in your wallet. You can check this with the `drk` utility:
|
||||
|
||||
```
|
||||
% ./drk -e "${ALICE_RPC}" wallet --balance
|
||||
% ./drk -e "${BOB_RPC}" wallet --balance
|
||||
```
|
||||
|
||||
Once the coins are settled, you should see them in a table after
|
||||
running one of the above commands.
|
||||
|
||||
### Doing everyone's part
|
||||
|
||||
After we have the coins in the wallets, Alice and Bob decide they
|
||||
want to swap 113 `ALICE_TOKEN` for 14 `BOB_TOKEN`. Now both of them
|
||||
will do a partial transaction which they are going to join if they
|
||||
both agree on the parameters, and finally publish it on the network.
|
||||
|
||||
So first, let's have Alice do her part:
|
||||
|
||||
```
|
||||
% ./darkotc -e "${ALICE_RPC}" init -t "${ALICE_TOKEN}:${BOB_TOKEN}" \
|
||||
-v 113:14 > alice_partial_swap
|
||||
```
|
||||
|
||||
This command will make the `darkotc` tool communicate with
|
||||
her `darkfid` instance and gather necessary information to
|
||||
create her half of the atomic swap. This includes the ZK mint
|
||||
and burn proofs, and all the necessary transaction data (see
|
||||
`darkotc/src/main.rs::PartialSwapData`).
|
||||
|
||||
The way this works is that Alice _mints_ the tokens she wants to
|
||||
receive (in this case, 14 `BOB_TOKEN`, and _burns_ the token she
|
||||
wants to send to Bob (in this case 113 `ALICE_TOKEN`). In turn,
|
||||
Bob does the same with his tokens.
|
||||
|
||||
Once Alice builds her half of the atomic swap, she can send the newly
|
||||
created file that we redirected from running the `darkotc` tool to
|
||||
Bob over an encrypted channel. When Bob has it, he can inspect if
|
||||
everything is in order, beacuse Alice will send all the blinding
|
||||
values necessary to convince Bob that the transaction they will make
|
||||
is true. Note however that this cannot be exploited in a way of
|
||||
stealing Alice's coins, because Alice does not create a signature
|
||||
for the data she is sending, so if Bob published something else,
|
||||
it would not be considered valid on the network.
|
||||
|
||||
To inspect the `alice_partial_swap` file, Bob runs:
|
||||
|
||||
```
|
||||
% ./darkotc -e "${BOB_RPC}" inspect-partial < alice_partial_swap
|
||||
```
|
||||
|
||||
The tool will then verify all pieces of the partial transaction
|
||||
and report what is or isn't valid. If Bob is satisfied, then he can
|
||||
proceed by creating his half of the atomic swap:
|
||||
|
||||
```
|
||||
% ./darkotc -e "${BOB_RPC}" init -t "${BOB_TOKEN}:${ALICE_TOKEN}" \
|
||||
-v 14:113 > bob_partial_swap
|
||||
```
|
||||
|
||||
(Note how the values separated by `:` are swapped in contrast to what
|
||||
Alice has done).
|
||||
|
||||
Now Bob can send this back to Alice for verification. We'll leave that
|
||||
as an excercise to the reader.
|
||||
|
||||
Once both parties are satisfied with the contents, they can join the
|
||||
two halves into a full transaction, and sign it. First, Bob will join
|
||||
both parts and create a signature:
|
||||
|
||||
```
|
||||
% ./darkotc -e "${BOB_RPC}" join alice_partial_swap bob_partial_swap \
|
||||
| ./darkotc -e "${BOB_RPC}" sign-tx > bob_signed_swap
|
||||
```
|
||||
|
||||
Now the `bob_signed_swap` file contains a full atomic swap signed
|
||||
by Bob. However we're still missing Alice's signature. So Bob sends
|
||||
back the signed data to Alice, and she can now sign and broadcast it
|
||||
to the network.
|
||||
|
||||
```
|
||||
% ./darkotc -e "${ALICE_RPC}" sign-tx < bob_signed_swap > signed_swap \
|
||||
| ./drk -e "${ALICE_RPC}" broadcast
|
||||
```
|
||||
|
||||
On success, Alice should see a transaction ID. Now wait until the
|
||||
swap settles on the network, and check the balances:
|
||||
|
||||
```
|
||||
% ./drk -e "${ALICE_RPC}" wallet --balance
|
||||
% ./drk -e "${BOB_RPC}" wallet --balance
|
||||
```
|
||||
|
||||
Alice and Bob successfully executed an atomic swap!
|
||||
@@ -1,612 +0,0 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2022 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::{
|
||||
io::{stdin, Read},
|
||||
process::exit,
|
||||
};
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use darkfi_sdk::crypto::{
|
||||
pedersen::{pedersen_commitment_base, pedersen_commitment_u64},
|
||||
schnorr,
|
||||
schnorr::SchnorrSecret,
|
||||
PublicKey, SecretKey, TokenId,
|
||||
};
|
||||
use darkfi_serial::{deserialize, serialize, SerialDecodable, SerialEncodable};
|
||||
use halo2_proofs::arithmetic::Field;
|
||||
use rand::rngs::OsRng;
|
||||
use url::Url;
|
||||
|
||||
use darkfi::{
|
||||
cli_desc,
|
||||
crypto::{
|
||||
burn_proof::{create_burn_proof, verify_burn_proof},
|
||||
mint_proof::{create_mint_proof, verify_mint_proof},
|
||||
note::{EncryptedNote, Note},
|
||||
proof::{ProvingKey, VerifyingKey},
|
||||
types::{
|
||||
DrkCoinBlind, DrkSerial, DrkSpendHook, DrkUserData, DrkUserDataBlind, DrkValueBlind,
|
||||
},
|
||||
BurnRevealedValues, MintRevealedValues, Proof,
|
||||
},
|
||||
rpc::client::RpcClient,
|
||||
tx::{
|
||||
partial::{PartialTransaction, PartialTransactionInput},
|
||||
Transaction, TransactionInput, TransactionOutput,
|
||||
},
|
||||
util::{
|
||||
cli::{fg_green, fg_red, progress_bar},
|
||||
parse::encode_base10,
|
||||
},
|
||||
zk::circuit::{BurnContract, MintContract},
|
||||
Result,
|
||||
};
|
||||
|
||||
mod cli_util;
|
||||
use cli_util::{parse_token_pair, parse_value_pair};
|
||||
mod rpc;
|
||||
use rpc::Rpc;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(name = "darkotc", about = cli_desc!(), version)]
|
||||
#[clap(arg_required_else_help(true))]
|
||||
struct Args {
|
||||
#[clap(short, parse(from_occurrences))]
|
||||
/// Increase verbosity (-vvv supported)
|
||||
verbose: u8,
|
||||
|
||||
#[clap(short, long, default_value = "tcp://127.0.0.1:8340")]
|
||||
/// darkfid JSON-RPC endpoint
|
||||
endpoint: Url,
|
||||
|
||||
#[clap(subcommand)]
|
||||
command: Subcmd,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Subcmd {
|
||||
/// Initialize an atomic swap
|
||||
Init {
|
||||
#[clap(short, long)]
|
||||
/// Pair of token IDs to swap: token_to_send:token_to_recv
|
||||
token_pair: String,
|
||||
|
||||
#[clap(short, long)]
|
||||
/// Pair of values to swap: value_to_send:value_to_recv
|
||||
value_pair: String,
|
||||
},
|
||||
|
||||
/// Inspect partial swap data from stdin.
|
||||
InspectPartial,
|
||||
|
||||
/// Join two partial swap data files and build a tx
|
||||
Join { data0: String, data1: String },
|
||||
|
||||
/// Sign a transaction given from stdin.
|
||||
SignTx,
|
||||
}
|
||||
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
/// Half of the swap data, includes the coin that is supposed to be received,
|
||||
/// and the coin that is supposed to be sent.
|
||||
struct PartialSwapData {
|
||||
/// Mint proof of coin to be received
|
||||
mint_proof: Proof,
|
||||
/// Public values for the mint proof
|
||||
mint_revealed: MintRevealedValues,
|
||||
/// Value of the coin to be received
|
||||
mint_value: u64,
|
||||
/// Token ID of the coin to be received
|
||||
mint_token: TokenId,
|
||||
/// Blinding factor for the minted value pedersen commitment
|
||||
mint_value_blind: DrkValueBlind,
|
||||
/// Blinding factor for the minted token ID pedersen commitment
|
||||
mint_token_blind: DrkValueBlind,
|
||||
/// Burn proof of the coin to be sent
|
||||
burn_proof: Proof,
|
||||
/// Public values for the burn proof
|
||||
burn_revealed: BurnRevealedValues,
|
||||
/// Value of the coin to be sent
|
||||
burn_value: u64,
|
||||
/// Token ID of the coin to be sent
|
||||
burn_token: TokenId,
|
||||
/// Blinding factor for the burned value pedersen commitment
|
||||
burn_value_blind: DrkValueBlind,
|
||||
/// Blinding factor for the burned token ID pedersen commitment
|
||||
burn_token_blind: DrkValueBlind,
|
||||
/// Encrypted note
|
||||
encrypted_note: EncryptedNote,
|
||||
}
|
||||
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
/// Full swap data, containing two instances of `PartialSwapData`, which
|
||||
/// represent an atomic swap.
|
||||
struct SwapData {
|
||||
swap0: PartialSwapData,
|
||||
swap1: PartialSwapData,
|
||||
}
|
||||
|
||||
async fn init_swap(
|
||||
endpoint: Url,
|
||||
token_pair: (TokenId, TokenId),
|
||||
value_pair: (u64, u64),
|
||||
) -> Result<PartialSwapData> {
|
||||
let rpc_client = match RpcClient::new(endpoint).await {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("Error: Failed connecting to darkfid JSON-RPC endpoint.");
|
||||
return Err(e)
|
||||
}
|
||||
};
|
||||
let rpc = Rpc { rpc_client };
|
||||
|
||||
// TODO: Implement metadata for decimals, don't hardcode.
|
||||
let vp = value_pair;
|
||||
|
||||
// Connect to darkfid and see if there's available funds.
|
||||
let balance = rpc.balance_of(token_pair.0).await?;
|
||||
if balance < vp.0 {
|
||||
eprintln!(
|
||||
"Error: There's not enough balance for token \"{}\" in your wallet.",
|
||||
token_pair.0
|
||||
);
|
||||
eprintln!("Available balance is {} ({})", encode_base10(balance, 8), balance);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// If there's not enough funds in a single coin, mint a single new coin
|
||||
// with the funds. We do this to minimize the size of the swap transaction.
|
||||
// i.e. 2 inputs and 2 outputs.
|
||||
// TODO: Implement ^
|
||||
// TODO: Maybe this should be done by the user beforehand?
|
||||
|
||||
// Find a coin to spend. We can find multiple, but we'll pick the first one.
|
||||
let coins = rpc.get_coins_valtok(vp.0, token_pair.0).await?;
|
||||
if coins.is_empty() {
|
||||
eprintln!("Error: Did not manage to find a coin with enough value to spend.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Fetch our default address
|
||||
let our_addr = rpc.wallet_address().await?;
|
||||
let our_pubk = match PublicKey::try_from(our_addr) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("Error converting our address into PublicKey: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Build ZK proving keys
|
||||
let pb = progress_bar("Building proving key for the Mint contract");
|
||||
let mint_pk = ProvingKey::build(11, &MintContract::default());
|
||||
pb.finish();
|
||||
|
||||
let pb = progress_bar("Building proving key for the Burn contract");
|
||||
let burn_pk = ProvingKey::build(11, &BurnContract::default());
|
||||
pb.finish();
|
||||
|
||||
// The coin we want to receive
|
||||
let recv_value_blind = DrkValueBlind::random(&mut OsRng);
|
||||
let recv_token_blind = DrkValueBlind::random(&mut OsRng);
|
||||
let recv_coin_blind = DrkCoinBlind::random(&mut OsRng);
|
||||
let recv_serial = DrkSerial::random(&mut OsRng);
|
||||
|
||||
// Spend hook and user data disabled
|
||||
let spend_hook = DrkSpendHook::from(0);
|
||||
let user_data = DrkUserData::from(0);
|
||||
|
||||
let pb = progress_bar("Building Mint proof for the receiving coin");
|
||||
let (mint_proof, mint_revealed) = create_mint_proof(
|
||||
&mint_pk,
|
||||
vp.1,
|
||||
token_pair.1,
|
||||
recv_value_blind,
|
||||
recv_token_blind,
|
||||
recv_serial,
|
||||
spend_hook,
|
||||
user_data,
|
||||
recv_coin_blind,
|
||||
our_pubk,
|
||||
)?;
|
||||
pb.finish();
|
||||
|
||||
// The coin we are spending.
|
||||
let coin = coins[0].clone();
|
||||
|
||||
let pb = progress_bar("Building Burn proof for the spending coin");
|
||||
let signature_secret = SecretKey::random(&mut OsRng);
|
||||
let merkle_path = match rpc.get_merkle_path(usize::from(coin.leaf_position)).await {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to get Merkle path for our coin from darkfid RPC: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Spend hook and user data disabled
|
||||
let spend_hook = DrkSpendHook::from(0);
|
||||
let user_data = DrkUserData::from(0);
|
||||
let user_data_blind = DrkUserDataBlind::random(&mut OsRng);
|
||||
|
||||
let (burn_proof, burn_revealed) = create_burn_proof(
|
||||
&burn_pk,
|
||||
vp.0,
|
||||
token_pair.0,
|
||||
coin.note.value_blind,
|
||||
coin.note.token_blind,
|
||||
coin.note.serial,
|
||||
spend_hook,
|
||||
user_data,
|
||||
user_data_blind,
|
||||
coin.note.coin_blind,
|
||||
coin.secret,
|
||||
coin.leaf_position,
|
||||
merkle_path,
|
||||
signature_secret,
|
||||
)?;
|
||||
pb.finish();
|
||||
|
||||
// Create encrypted note
|
||||
let note = Note {
|
||||
serial: recv_serial,
|
||||
value: vp.1,
|
||||
token_id: token_pair.1,
|
||||
coin_blind: recv_coin_blind,
|
||||
value_blind: recv_value_blind,
|
||||
token_blind: recv_token_blind,
|
||||
// Here we store our secret key we used for signing
|
||||
memo: serialize(&signature_secret),
|
||||
};
|
||||
let encrypted_note = note.encrypt(&our_pubk)?;
|
||||
|
||||
// Pack proofs together with pedersen commitment openings so
|
||||
// counterparty can verify correctness.
|
||||
let partial_swap_data = PartialSwapData {
|
||||
mint_proof,
|
||||
mint_revealed,
|
||||
mint_value: vp.1,
|
||||
mint_token: token_pair.1,
|
||||
mint_value_blind: recv_value_blind,
|
||||
mint_token_blind: recv_token_blind,
|
||||
burn_proof,
|
||||
burn_value: vp.0,
|
||||
burn_token: token_pair.0,
|
||||
burn_revealed,
|
||||
burn_value_blind: coin.note.value_blind,
|
||||
burn_token_blind: coin.note.token_blind,
|
||||
encrypted_note,
|
||||
};
|
||||
|
||||
Ok(partial_swap_data)
|
||||
}
|
||||
|
||||
fn inspect_partial(data: &str) -> Result<()> {
|
||||
let bytes = match bs58::decode(data).into_vec() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("Error decoding base58 data from input: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let sd: PartialSwapData = match deserialize(&bytes) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("Error deserializing partial swap data into struct: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
eprintln!("Successfully decoded partial swap data");
|
||||
|
||||
// Build ZK verifying keys
|
||||
let pb = progress_bar("Building verifying key for the Mint contract");
|
||||
let mint_vk = VerifyingKey::build(11, &MintContract::default());
|
||||
pb.finish();
|
||||
|
||||
let pb = progress_bar("Building verifying key for the Burn contract");
|
||||
let burn_vk = VerifyingKey::build(11, &BurnContract::default());
|
||||
pb.finish();
|
||||
|
||||
let pb = progress_bar("Verifying Burn proof");
|
||||
let burn_valid = verify_burn_proof(&burn_vk, &sd.burn_proof, &sd.burn_revealed).is_ok();
|
||||
pb.finish();
|
||||
|
||||
let pb = progress_bar("Verifying Mint proof");
|
||||
let mint_valid = verify_mint_proof(&mint_vk, &sd.mint_proof, &sd.mint_revealed).is_ok();
|
||||
pb.finish();
|
||||
|
||||
eprintln!(" Verifying Pedersen commitments");
|
||||
|
||||
let burn_value_valid = pedersen_commitment_u64(sd.burn_value, sd.burn_value_blind) ==
|
||||
sd.burn_revealed.value_commit;
|
||||
|
||||
let burn_token_valid = pedersen_commitment_base(sd.burn_token.inner(), sd.burn_token_blind) ==
|
||||
sd.burn_revealed.token_commit;
|
||||
|
||||
let mint_value_valid = pedersen_commitment_u64(sd.mint_value, sd.mint_value_blind) ==
|
||||
sd.mint_revealed.value_commit;
|
||||
|
||||
let mint_token_valid = pedersen_commitment_base(sd.mint_token.inner(), sd.mint_token_blind) ==
|
||||
sd.mint_revealed.token_commit;
|
||||
|
||||
let mut valid = true;
|
||||
eprintln!("Summary:");
|
||||
|
||||
eprint!(" Burn proof: ");
|
||||
if burn_valid {
|
||||
eprintln!("{}", fg_green("VALID"));
|
||||
} else {
|
||||
eprintln!("{}", fg_red("INVALID"));
|
||||
valid = false;
|
||||
}
|
||||
|
||||
eprint!(" Burn proof value commitment: ");
|
||||
if burn_value_valid {
|
||||
eprintln!("{}", fg_green("VALID"));
|
||||
} else {
|
||||
eprintln!("{}", fg_red("INVALID"));
|
||||
valid = false;
|
||||
}
|
||||
|
||||
eprint!(" Burn proof token commitment: ");
|
||||
if burn_token_valid {
|
||||
eprintln!("{}", fg_green("VALID"));
|
||||
} else {
|
||||
eprintln!("{}", fg_red("INVALID"));
|
||||
valid = false;
|
||||
}
|
||||
|
||||
eprint!(" Mint proof: ");
|
||||
if mint_valid {
|
||||
eprintln!("{}", fg_green("VALID"));
|
||||
} else {
|
||||
eprintln!("{}", fg_red("INVALID"));
|
||||
valid = false;
|
||||
}
|
||||
|
||||
eprint!(" Mint proof value commitment: ");
|
||||
if mint_value_valid {
|
||||
eprintln!("{}", fg_green("VALID"));
|
||||
} else {
|
||||
eprintln!("{}", fg_red("INVALID"));
|
||||
valid = false;
|
||||
}
|
||||
|
||||
eprint!(" Mint proof token commitment: ");
|
||||
if mint_token_valid {
|
||||
eprintln!("{}", fg_green("VALID"));
|
||||
} else {
|
||||
eprintln!("{}", fg_red("INVALID"));
|
||||
valid = false;
|
||||
}
|
||||
|
||||
eprintln!("========================================");
|
||||
|
||||
eprintln!("Mint: {} {}", encode_base10(sd.mint_value, 8), sd.mint_token);
|
||||
eprintln!("Burn: {} {}", encode_base10(sd.burn_value, 8), sd.burn_token);
|
||||
|
||||
eprint!("\nThe ZK proofs and commitments inspected are ");
|
||||
if !valid {
|
||||
println!("{}", fg_red("NOT VALID"));
|
||||
exit(1);
|
||||
} else {
|
||||
eprintln!("{}", fg_green("VALID"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn join(endpoint: Url, d0: PartialSwapData, d1: PartialSwapData) -> Result<Transaction> {
|
||||
eprintln!("Joining data into a transaction");
|
||||
|
||||
let input0 = PartialTransactionInput { burn_proof: d0.burn_proof, revealed: d0.burn_revealed };
|
||||
let input1 = PartialTransactionInput { burn_proof: d1.burn_proof, revealed: d1.burn_revealed };
|
||||
let inputs = vec![input0, input1];
|
||||
|
||||
let output0 = TransactionOutput {
|
||||
mint_proof: d0.mint_proof,
|
||||
revealed: d0.mint_revealed,
|
||||
enc_note: d0.encrypted_note.clone(),
|
||||
};
|
||||
let output1 = TransactionOutput {
|
||||
mint_proof: d1.mint_proof,
|
||||
revealed: d1.mint_revealed,
|
||||
enc_note: d1.encrypted_note.clone(),
|
||||
};
|
||||
let outputs = vec![output0, output1];
|
||||
|
||||
let partial_tx = PartialTransaction { clear_inputs: vec![], inputs, outputs };
|
||||
let unsigned_tx_data = serialize(&partial_tx);
|
||||
|
||||
let mut inputs = vec![];
|
||||
let mut signed: bool;
|
||||
|
||||
eprint!("Trying to decrypt the note of the first half... ");
|
||||
let rpc_client = RpcClient::new(endpoint.clone()).await?;
|
||||
let rpc = Rpc { rpc_client };
|
||||
let note = match rpc.decrypt_note(&d0.encrypted_note).await {
|
||||
Ok(v) => v,
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
if let Some(note) = note {
|
||||
eprintln!("{}", fg_green("Success"));
|
||||
let signature = try_sign_tx(¬e, &unsigned_tx_data[..])?;
|
||||
let input = TransactionInput::from_partial(partial_tx.inputs[0].clone(), signature);
|
||||
inputs.push(input);
|
||||
signed = true;
|
||||
} else {
|
||||
eprintln!("{}", fg_red("Failure"));
|
||||
let signature = schnorr::Signature::dummy();
|
||||
let input = TransactionInput::from_partial(partial_tx.inputs[0].clone(), signature);
|
||||
inputs.push(input);
|
||||
signed = false;
|
||||
}
|
||||
|
||||
// If we have signed, we shouldn't have to look in the other one, but we might
|
||||
// be sending to ourself for some reason.
|
||||
eprint!("Trying to decrypt the note of the second half... ");
|
||||
let rpc_client = RpcClient::new(endpoint).await?;
|
||||
let rpc = Rpc { rpc_client };
|
||||
let note = match rpc.decrypt_note(&d1.encrypted_note).await {
|
||||
Ok(v) => v,
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
if let Some(note) = note {
|
||||
eprintln!("{}", fg_green("Success"));
|
||||
let signature = try_sign_tx(¬e, &unsigned_tx_data[..])?;
|
||||
let input = TransactionInput::from_partial(partial_tx.inputs[1].clone(), signature);
|
||||
inputs.push(input);
|
||||
signed = true;
|
||||
} else {
|
||||
eprintln!("{}", fg_red("Failure"));
|
||||
let signature = schnorr::Signature::dummy();
|
||||
let input = TransactionInput::from_partial(partial_tx.inputs[1].clone(), signature);
|
||||
inputs.push(input);
|
||||
if !signed {
|
||||
eprintln!("Error: Failed to sign transaction!");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if !signed {
|
||||
eprintln!("Error: Failed to sign transaction!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let tx = Transaction { clear_inputs: vec![], inputs, outputs: partial_tx.outputs };
|
||||
Ok(tx)
|
||||
}
|
||||
|
||||
async fn sign_tx(endpoint: Url, data: &str) -> Result<Transaction> {
|
||||
eprintln!("Trying to sign transaction");
|
||||
let mut tx: Transaction = deserialize(&bs58::decode(data).into_vec()?)?;
|
||||
|
||||
// We assume our input and our output are in the same index, since this
|
||||
// transaction contains 2 inputs and 2 outputs, and one of each is ours,
|
||||
// and one of each is the other party's. So we go on and sign the input
|
||||
// index of the output index we can decrypt the note for.
|
||||
let mut idx_to_sign = 0;
|
||||
let mut signature = schnorr::Signature::dummy();
|
||||
|
||||
eprintln!("Looking for an encrypted note we can decrypt...");
|
||||
let mut found_secret = false;
|
||||
for (i, output) in tx.outputs.iter().enumerate() {
|
||||
// TODO: FIXME: Consider not closing the RPC on failure.
|
||||
let rpc = Rpc { rpc_client: RpcClient::new(endpoint.clone()).await? };
|
||||
|
||||
let note = match rpc.decrypt_note(&output.enc_note).await {
|
||||
Ok(v) => v,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
if let Some(note) = note {
|
||||
eprintln!("Successfully decrypted note in output {}", i);
|
||||
eprintln!("Creating signature...");
|
||||
let mut unsigned_tx_data = vec![];
|
||||
let _ = tx.encode_without_signature(&mut unsigned_tx_data)?;
|
||||
|
||||
signature = try_sign_tx(¬e, &unsigned_tx_data[..])?;
|
||||
found_secret = true;
|
||||
idx_to_sign = i;
|
||||
break
|
||||
}
|
||||
|
||||
eprintln!("Failed to find a note to decrypt. Signing failed.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if !found_secret {
|
||||
eprintln!("Error: Did not manage to sign transaction. Couldn't find any secret keys.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
tx.inputs[idx_to_sign].signature = signature;
|
||||
Ok(tx)
|
||||
}
|
||||
|
||||
fn try_sign_tx(note: &Note, tx_data: &[u8]) -> Result<schnorr::Signature> {
|
||||
if note.memo.len() != 32 {
|
||||
eprintln!("Error: The note memo is not 32 bytes");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let secret = match SecretKey::from_bytes(note.memo.clone().try_into().unwrap()) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("Did not manage to cast bytes into SecretKey: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
eprintln!("Signing transaction...");
|
||||
let signature = secret.sign(&mut OsRng, tx_data);
|
||||
Ok(signature)
|
||||
}
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
match args.command {
|
||||
Subcmd::Init { token_pair, value_pair } => {
|
||||
let token_pair = parse_token_pair(&token_pair)?;
|
||||
let value_pair = parse_value_pair(&value_pair)?;
|
||||
eprintln!("Creating half of an atomic swap");
|
||||
eprintln!("Send: {} {} tokens.", encode_base10(value_pair.0, 8), token_pair.0);
|
||||
eprintln!("Recv: {} {} tokens.", encode_base10(value_pair.1, 8), token_pair.1);
|
||||
let swap_data = init_swap(args.endpoint, token_pair, value_pair).await?;
|
||||
|
||||
println!("{}", bs58::encode(serialize(&swap_data)).into_string());
|
||||
Ok(())
|
||||
}
|
||||
Subcmd::InspectPartial => {
|
||||
let mut buf = String::new();
|
||||
stdin().read_to_string(&mut buf)?;
|
||||
|
||||
inspect_partial(buf.trim())
|
||||
}
|
||||
Subcmd::Join { data0, data1 } => {
|
||||
let d0 = std::fs::read_to_string(data0)?;
|
||||
let d1 = std::fs::read_to_string(data1)?;
|
||||
|
||||
let d0 = deserialize(&bs58::decode(&d0.trim()).into_vec()?)?;
|
||||
let d1 = deserialize(&bs58::decode(&d1.trim()).into_vec()?)?;
|
||||
|
||||
let tx = join(args.endpoint, d0, d1).await?;
|
||||
|
||||
println!("{}", bs58::encode(&serialize(&tx)).into_string());
|
||||
eprintln!("Successfully signed transaction");
|
||||
Ok(())
|
||||
}
|
||||
Subcmd::SignTx => {
|
||||
let mut buf = String::new();
|
||||
stdin().read_to_string(&mut buf)?;
|
||||
|
||||
let tx = sign_tx(args.endpoint, buf.trim()).await?;
|
||||
|
||||
println!("{}", bs58::encode(&serialize(&tx)).into_string());
|
||||
eprintln!("Successfully signed transaction");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2022 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::{process::exit, str::FromStr};
|
||||
|
||||
use darkfi_sdk::crypto::{Address, MerkleNode, TokenId};
|
||||
use darkfi_serial::{deserialize, serialize};
|
||||
use serde_json::json;
|
||||
|
||||
use darkfi::{
|
||||
crypto::{
|
||||
coin::OwnCoin,
|
||||
note::{EncryptedNote, Note},
|
||||
},
|
||||
rpc::{client::RpcClient, jsonrpc::JsonRequest},
|
||||
Result,
|
||||
};
|
||||
|
||||
/// The RPC object with functionality for connecting to darkfid.
|
||||
pub struct Rpc {
|
||||
pub rpc_client: RpcClient,
|
||||
}
|
||||
|
||||
impl Rpc {
|
||||
/// Fetch wallet balance of given token ID and return its u64 representation.
|
||||
pub async fn balance_of(&self, token_id: TokenId) -> Result<u64> {
|
||||
let req = JsonRequest::new("wallet.get_balances", json!([]));
|
||||
let rep = self.rpc_client.request(req).await?;
|
||||
|
||||
if !rep.is_object() {
|
||||
eprintln!("Error: Invalid balance data received from darkfid RPC endpoint.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
for i in rep.as_object().unwrap().keys() {
|
||||
if TokenId::try_from(i.as_str()).unwrap() == token_id {
|
||||
if let Some(balance) = rep[i].as_u64() {
|
||||
return Ok(balance)
|
||||
}
|
||||
|
||||
eprintln!("Error: Invalid balance data received from darkfid RPC endpoint.");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
/// Fetch default wallet address from the darkfid RPC endpoint.
|
||||
pub async fn wallet_address(&self) -> Result<Address> {
|
||||
let req = JsonRequest::new("wallet.get_addrs", json!([0_i64]));
|
||||
let rep = self.rpc_client.request(req).await?;
|
||||
|
||||
if !rep.is_array() || !rep.as_array().unwrap()[0].is_string() {
|
||||
eprintln!("Error: Invalid wallet address received from darkfid RPC endpoint.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
match Address::from_str(rep[0].as_str().unwrap()) {
|
||||
Ok(v) => Ok(v),
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"Error: Invalid wallet address received from darkfid RPC endpoint: {}",
|
||||
e
|
||||
);
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Query wallet for unspent coins in wallet matching value and token_id.
|
||||
pub async fn get_coins_valtok(&self, value: u64, token_id: TokenId) -> Result<Vec<OwnCoin>> {
|
||||
let req = JsonRequest::new(
|
||||
"wallet.get_coins_valtok",
|
||||
json!([value, format!("{}", token_id), true]),
|
||||
);
|
||||
let rep = self.rpc_client.request(req).await?;
|
||||
|
||||
if !rep.is_array() {
|
||||
eprintln!("Error: Invalid coin data received from darkfid RPC endpoint.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let rep = rep.as_array().unwrap();
|
||||
let mut ret = vec![];
|
||||
|
||||
for i in rep {
|
||||
if !i.is_string() {
|
||||
eprintln!(
|
||||
"Error: Invalid base58 data for OwnCoin received from darkfid RPC endpoint."
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let data = match bs58::decode(i.as_str().unwrap()).into_vec() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("Error: Failed decoding base58 data for OwnCoin: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let oc = match deserialize(&data) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("Error: Failed deserializing OwnCoin: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
ret.push(oc);
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Fetch the merkle path for a given leaf position in the coin tree
|
||||
pub async fn get_merkle_path(&self, leaf_pos: usize) -> Result<Vec<MerkleNode>> {
|
||||
let req = JsonRequest::new("wallet.get_merkle_path", json!([leaf_pos as u64]));
|
||||
let rep = self.rpc_client.request(req).await?;
|
||||
|
||||
if !rep.is_array() {
|
||||
eprintln!("Error: Invalid merkle path data received from darkfid RPC endpoint.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let rep = rep.as_array().unwrap();
|
||||
let mut ret = vec![];
|
||||
|
||||
for i in rep {
|
||||
if !i.is_string() {
|
||||
eprintln!("Error: Invalid base58 data received for MerkleNode");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let n = match bs58::decode(i.as_str().unwrap()).into_vec() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("Error: Failed decoding base58 for MerkleNode: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
if n.len() != 32 {
|
||||
eprintln!("error: MerkleNode byte length is not 32");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let n = MerkleNode::from_bytes(n.try_into().unwrap());
|
||||
if n.is_none() {
|
||||
eprintln!("Error: Noncanonical bytes of MerkleNode");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
ret.push(n.unwrap());
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Try to decrypt a given `EncryptedNote`
|
||||
pub async fn decrypt_note(&self, enc_note: &EncryptedNote) -> Result<Option<Note>> {
|
||||
let encoded = bs58::encode(&serialize(enc_note)).into_string();
|
||||
let req = JsonRequest::new("wallet.decrypt_note", json!([encoded]));
|
||||
let rep = self.rpc_client.oneshot_request(req).await?;
|
||||
|
||||
if !rep.is_string() {
|
||||
eprintln!("Error: decrypt_note() RPC call returned invalid data");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let decoded = match bs58::decode(rep.as_str().unwrap()).into_vec() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("Error decoding base58 data received from RPC call: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let note = match deserialize(&decoded) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("Failed deserializing bytes into Note: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Some(note))
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::process::exit;
|
||||
|
||||
use darkfi::{util::parse::decode_base10, Result};
|
||||
@@ -24,7 +23,7 @@ use darkfi_sdk::crypto::TokenId;
|
||||
pub fn parse_value_pair(s: &str) -> Result<(u64, u64)> {
|
||||
let v: Vec<&str> = s.split(':').collect();
|
||||
if v.len() != 2 {
|
||||
eprintln!("Invalid value pair. Use a pair such as '13.37:11.0'.");
|
||||
eprintln!("Invalid value pair. Use a pair such as 13.37:11.0");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -33,7 +32,7 @@ pub fn parse_value_pair(s: &str) -> Result<(u64, u64)> {
|
||||
let val1 = decode_base10(v[1], 8, true);
|
||||
|
||||
if val0.is_err() || val1.is_err() {
|
||||
eprintln!("Invalid value pair. Use a pair such as '13.37:11.0'.");
|
||||
eprintln!("Invalid value pair. Use a pair such as 13.37:11.0");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -48,12 +48,20 @@ mod rpc_airdrop;
|
||||
/// Payment methods
|
||||
mod rpc_transfer;
|
||||
|
||||
/// Swap methods
|
||||
mod rpc_swap;
|
||||
use rpc_swap::PartialSwapData;
|
||||
|
||||
/// Blockchain methods
|
||||
mod rpc_blockchain;
|
||||
|
||||
/// Wallet operation methods for darkfid's JSON-RPC
|
||||
mod rpc_wallet;
|
||||
|
||||
/// CLI utility functions
|
||||
mod cli_util;
|
||||
use cli_util::{parse_token_pair, parse_value_pair};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = cli_desc!())]
|
||||
struct Args {
|
||||
@@ -139,6 +147,10 @@ enum Subcmd {
|
||||
recipient: String,
|
||||
},
|
||||
|
||||
/// OTC atomic swap
|
||||
#[command(subcommand)]
|
||||
Otc(OtcSubcmd),
|
||||
|
||||
/// Inspect a transaction from stdin
|
||||
Inspect,
|
||||
|
||||
@@ -160,6 +172,26 @@ enum Subcmd {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum OtcSubcmd {
|
||||
/// Initialize the first half of the atomic swap
|
||||
Init {
|
||||
/// Value pair to send:recv (11.55:99.42)
|
||||
#[clap(short, long)]
|
||||
value_pair: String,
|
||||
|
||||
/// Token pair to send:recv (f00:b4r)
|
||||
#[clap(short, long)]
|
||||
token_pair: String,
|
||||
},
|
||||
|
||||
/// Build entire swap tx given the first half from stdin
|
||||
Join,
|
||||
|
||||
/// Sign a transaction given from stdin as the first-half
|
||||
Sign,
|
||||
}
|
||||
|
||||
pub struct Drk {
|
||||
pub rpc_client: RpcClient,
|
||||
}
|
||||
@@ -267,7 +299,7 @@ async fn main() -> Result<()> {
|
||||
drk.rpc_client.close().await?;
|
||||
|
||||
for i in coins {
|
||||
print!("{} ", bs58::encode(i.0.coin.inner().to_repr()).into_string());
|
||||
print!("{:?} ", i.0.coin.inner());
|
||||
if i.1 {
|
||||
println!("(spent)");
|
||||
} else {
|
||||
@@ -348,15 +380,64 @@ async fn main() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Subcmd::Otc(cmd) => {
|
||||
let rpc_client = RpcClient::new(args.endpoint)
|
||||
.await
|
||||
.with_context(|| "Could not connect to darkfid RPC endpoint")?;
|
||||
|
||||
let drk = Drk { rpc_client };
|
||||
|
||||
match cmd {
|
||||
OtcSubcmd::Init { value_pair, token_pair } => {
|
||||
let (vp_send, vp_recv) = parse_value_pair(&value_pair)?;
|
||||
let (tp_send, tp_recv) = parse_token_pair(&token_pair)?;
|
||||
|
||||
let half = drk
|
||||
.init_swap(vp_send, tp_send, vp_recv, tp_recv)
|
||||
.await
|
||||
.with_context(|| "Failed to create swap transaction half")?;
|
||||
|
||||
println!("{}", bs58::encode(&serialize(&half)).into_string());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
OtcSubcmd::Join => {
|
||||
let mut buf = String::new();
|
||||
stdin().read_to_string(&mut buf)?;
|
||||
let bytes = bs58::decode(&buf.trim()).into_vec()?;
|
||||
let partial: PartialSwapData = deserialize(&bytes)?;
|
||||
|
||||
let tx = drk
|
||||
.join_swap(partial)
|
||||
.await
|
||||
.with_context(|| "Failed to create a join swap transaction")?;
|
||||
|
||||
println!("{}", bs58::encode(&serialize(&tx)).into_string());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
OtcSubcmd::Sign => {
|
||||
let mut buf = String::new();
|
||||
stdin().read_to_string(&mut buf)?;
|
||||
let bytes = bs58::decode(&buf.trim()).into_vec()?;
|
||||
let mut tx: Transaction = deserialize(&bytes)?;
|
||||
|
||||
drk.sign_swap(&mut tx)
|
||||
.await
|
||||
.with_context(|| "Failed to sign joined swap transaction")?;
|
||||
|
||||
println!("{}", bs58::encode(&serialize(&tx)).into_string());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Subcmd::Inspect => {
|
||||
let mut buf = String::new();
|
||||
stdin().read_to_string(&mut buf)?;
|
||||
|
||||
let bytes = bs58::decode(&buf.trim()).into_vec()?;
|
||||
let tx: Transaction = deserialize(&bytes)?;
|
||||
|
||||
println!("{:#?}", tx);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -364,7 +445,6 @@ async fn main() -> Result<()> {
|
||||
eprintln!("Reading transaction from stdin...");
|
||||
let mut buf = String::new();
|
||||
stdin().read_to_string(&mut buf)?;
|
||||
|
||||
let bytes = bs58::decode(&buf.trim()).into_vec()?;
|
||||
let tx = deserialize(&bytes)?;
|
||||
|
||||
|
||||
@@ -123,19 +123,28 @@ impl Drk {
|
||||
|
||||
let mut outputs: Vec<Output> = vec![];
|
||||
|
||||
let mf = MoneyFunction::Transfer as u8;
|
||||
|
||||
// TODO: FIXME: This shouldn't be hardcoded here obviously.
|
||||
let contract_id = ContractId::from(pallas::Base::from(u64::MAX - 420));
|
||||
|
||||
for (i, tx) in block.txs.iter().enumerate() {
|
||||
for (j, call) in tx.calls.iter().enumerate() {
|
||||
if call.contract_id == contract_id && call.data[0] == mf {
|
||||
eprintln!("Found money transfer in call {} in tx {}", j, i);
|
||||
if call.contract_id == contract_id && call.data[0] == MoneyFunction::Transfer as u8
|
||||
{
|
||||
eprintln!("Found Money::Transfer in call {} in tx {}", j, i);
|
||||
let params: MoneyTransferParams = deserialize(&call.data[1..])?;
|
||||
for output in params.outputs {
|
||||
outputs.push(output);
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if call.contract_id == contract_id && call.data[0] == MoneyFunction::OtcSwap as u8 {
|
||||
eprintln!("Found Money::OtcSwap in call {} in tx {}", j, i);
|
||||
let params: MoneyTransferParams = deserialize(&call.data[1..])?;
|
||||
for output in params.outputs {
|
||||
outputs.push(output);
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,9 +161,11 @@ impl Drk {
|
||||
|
||||
let mut owncoins = vec![];
|
||||
|
||||
// FIXME: We end up adding duplicate coins that could already be in the tree
|
||||
for output in outputs {
|
||||
// Append the new coin to the Merkle tree. Every coin has to be added.
|
||||
let coin = output.coin;
|
||||
|
||||
// Append the new coin to the Merkle tree. Every coin has to be added.
|
||||
tree.append(&MerkleNode::from(coin));
|
||||
|
||||
// Attempt to decrypt the note
|
||||
@@ -204,6 +215,7 @@ impl Drk {
|
||||
|
||||
eprintln!("Found {} OwnCoin(s) in block", owncoins.len());
|
||||
for owncoin in owncoins {
|
||||
eprintln!("Owncoin: {:?}", owncoin.coin);
|
||||
let params = json!([
|
||||
query,
|
||||
QueryType::Blob as u8,
|
||||
|
||||
280
bin/drk/src/rpc_swap.rs
Normal file
280
bin/drk/src/rpc_swap.rs
Normal file
@@ -0,0 +1,280 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2022 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use darkfi::{
|
||||
tx::Transaction,
|
||||
zk::{proof::ProvingKey, vm::ZkCircuit, vm_stack::empty_witnesses, Proof},
|
||||
zkas::ZkBinary,
|
||||
};
|
||||
use darkfi_money_contract::{
|
||||
client::{build_half_swap_tx, EncryptedNote},
|
||||
state::MoneyTransferParams,
|
||||
MoneyFunction, ZKAS_BURN_NS, ZKAS_MINT_NS,
|
||||
};
|
||||
use darkfi_sdk::{
|
||||
crypto::{pedersen::ValueBlind, ContractId, SecretKey, TokenId},
|
||||
pasta::pallas,
|
||||
tx::ContractCall,
|
||||
};
|
||||
use darkfi_serial::{deserialize, Encodable, SerialDecodable, SerialEncodable};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use super::Drk;
|
||||
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
/// Half of the swap data, includes the coin that is supposed to be sent,
|
||||
/// and the coin that is supposed to be received.
|
||||
pub struct PartialSwapData {
|
||||
params: MoneyTransferParams,
|
||||
proofs: Vec<Proof>,
|
||||
value_pair: (u64, u64),
|
||||
token_pair: (TokenId, TokenId),
|
||||
value_blinds: Vec<ValueBlind>,
|
||||
token_blinds: Vec<ValueBlind>,
|
||||
}
|
||||
|
||||
impl Drk {
|
||||
/// Initialize the first half of an atomic swap
|
||||
pub async fn init_swap(
|
||||
&self,
|
||||
value_send: u64,
|
||||
token_send: TokenId,
|
||||
value_recv: u64,
|
||||
token_recv: TokenId,
|
||||
) -> Result<PartialSwapData> {
|
||||
// First we'll fetch all of our unspent coins from the wallet.
|
||||
let mut owncoins = self.wallet_coins(false).await?;
|
||||
// Then we see if we have one that we can send.
|
||||
owncoins.retain(|x| (x.0.note.value == value_send && x.0.note.token_id == token_send));
|
||||
if owncoins.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Did not find any unspent coins of value {} and token_id {}",
|
||||
value_send,
|
||||
token_send
|
||||
))
|
||||
}
|
||||
|
||||
// If there are any, we'll just spend the first one we see.
|
||||
let burn_coin = owncoins[0].0.clone();
|
||||
|
||||
// Fetch our default address
|
||||
let address = self.wallet_address(0).await?;
|
||||
|
||||
// We'll also need our Merkle tree
|
||||
let tree = self.wallet_tree().await?;
|
||||
|
||||
// TODO: FIXME: Do not hardcode the contract ID
|
||||
let contract_id = ContractId::from(pallas::Base::from(u64::MAX - 420));
|
||||
|
||||
// Now we need to do a lookup for the zkas proof bincodes, and create
|
||||
// the circuit objects and proving keys so we can build the transaction.
|
||||
// We also do this through the RPC.
|
||||
let zkas_bins = self.lookup_zkas(&contract_id).await?;
|
||||
|
||||
let Some(mint_zkbin) = zkas_bins.iter().find(|x| x.0 == ZKAS_MINT_NS) else {
|
||||
return Err(anyhow!("Mint circuit not found"))
|
||||
};
|
||||
|
||||
let Some(burn_zkbin) = zkas_bins.iter().find(|x| x.0 == ZKAS_BURN_NS) else {
|
||||
return Err(anyhow!("Burn circuit not found"))
|
||||
};
|
||||
|
||||
let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?;
|
||||
let burn_zkbin = ZkBinary::decode(&burn_zkbin.1)?;
|
||||
|
||||
let k = 13;
|
||||
let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin), mint_zkbin.clone());
|
||||
let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin), burn_zkbin.clone());
|
||||
|
||||
eprintln!("Creating Mint circuit proving key");
|
||||
let mint_pk = ProvingKey::build(k, &mint_circuit);
|
||||
eprintln!("Creating Burn circuit proving key");
|
||||
let burn_pk = ProvingKey::build(k, &burn_circuit);
|
||||
|
||||
// Now we should have everything we need to build the swap half
|
||||
eprintln!("Building first half of the swap transaction");
|
||||
let (half_params, half_proofs, _half_keys, _spent_coins, value_blinds, token_blinds) =
|
||||
build_half_swap_tx(
|
||||
&address,
|
||||
value_send,
|
||||
token_send,
|
||||
value_recv,
|
||||
token_recv,
|
||||
&[],
|
||||
&[],
|
||||
&[burn_coin],
|
||||
&tree,
|
||||
&mint_zkbin,
|
||||
&mint_pk,
|
||||
&burn_zkbin,
|
||||
&burn_pk,
|
||||
)?;
|
||||
|
||||
// Now we have the half, so we can build `PartialSwapData` and return it.
|
||||
let ret = PartialSwapData {
|
||||
params: half_params,
|
||||
proofs: half_proofs,
|
||||
value_pair: (value_send, value_recv),
|
||||
token_pair: (token_send, token_recv),
|
||||
value_blinds,
|
||||
token_blinds,
|
||||
};
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Create a full transaction by inspecting and verifying given partial swap data,
|
||||
/// making the other half, and joining all this into a `Transaction` object.
|
||||
pub async fn join_swap(&self, partial: PartialSwapData) -> Result<Transaction> {
|
||||
// Our side of the tx in the pairs is the second half, so we try to find
|
||||
// an unspent coin like that in our wallet.
|
||||
let mut owncoins = self.wallet_coins(false).await?;
|
||||
owncoins.retain(|x| {
|
||||
x.0.note.value == partial.value_pair.1 && x.0.note.token_id == partial.token_pair.1
|
||||
});
|
||||
|
||||
if owncoins.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Did not find any unspent coins of value {} and token_id {}",
|
||||
partial.value_pair.1,
|
||||
partial.token_pair.1
|
||||
))
|
||||
}
|
||||
|
||||
// If there are any, we'll just spend the first one we see.
|
||||
let burn_coin = owncoins[0].0.clone();
|
||||
|
||||
// Fetch our default address
|
||||
let address = self.wallet_address(0).await?;
|
||||
|
||||
// We'll also need our Merkle tree
|
||||
let tree = self.wallet_tree().await?;
|
||||
|
||||
// TODO: FIXME: Do not hardcode the contract ID
|
||||
let contract_id = ContractId::from(pallas::Base::from(u64::MAX - 420));
|
||||
|
||||
// Now we need to do a lookup for the zkas proof bincodes, and create
|
||||
// the circuit objects and proving keys so we can build the transaction.
|
||||
// We also do this through the RPC.
|
||||
let zkas_bins = self.lookup_zkas(&contract_id).await?;
|
||||
|
||||
let Some(mint_zkbin) = zkas_bins.iter().find(|x| x.0 == ZKAS_MINT_NS) else {
|
||||
return Err(anyhow!("Mint circuit not found"))
|
||||
};
|
||||
|
||||
let Some(burn_zkbin) = zkas_bins.iter().find(|x| x.0 == ZKAS_BURN_NS) else {
|
||||
return Err(anyhow!("Burn circuit not found"))
|
||||
};
|
||||
|
||||
let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?;
|
||||
let burn_zkbin = ZkBinary::decode(&burn_zkbin.1)?;
|
||||
|
||||
let k = 13;
|
||||
let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin), mint_zkbin.clone());
|
||||
let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin), burn_zkbin.clone());
|
||||
|
||||
eprintln!("Creating Mint circuit proving key");
|
||||
let mint_pk = ProvingKey::build(k, &mint_circuit);
|
||||
eprintln!("Creating Burn circuit proving key");
|
||||
let burn_pk = ProvingKey::build(k, &burn_circuit);
|
||||
|
||||
// TODO: Maybe some kind of verification at this point
|
||||
|
||||
// Now we should have everything we need to build the swap half
|
||||
eprintln!("Building second half of the swap transaction");
|
||||
let (half_params, half_proofs, half_keys, _spent_coins, _value_blinds, _token_blinds) =
|
||||
build_half_swap_tx(
|
||||
&address,
|
||||
partial.value_pair.1,
|
||||
partial.token_pair.1,
|
||||
partial.value_pair.0,
|
||||
partial.token_pair.0,
|
||||
&partial.value_blinds,
|
||||
&partial.token_blinds,
|
||||
&[burn_coin],
|
||||
&tree,
|
||||
&mint_zkbin,
|
||||
&mint_pk,
|
||||
&burn_zkbin,
|
||||
&burn_pk,
|
||||
)?;
|
||||
|
||||
let full_params = MoneyTransferParams {
|
||||
clear_inputs: vec![],
|
||||
inputs: vec![partial.params.inputs[0].clone(), half_params.inputs[0].clone()],
|
||||
outputs: vec![partial.params.outputs[0].clone(), half_params.outputs[0].clone()],
|
||||
};
|
||||
|
||||
let full_proofs = vec![
|
||||
partial.proofs[0].clone(),
|
||||
half_proofs[0].clone(),
|
||||
partial.proofs[1].clone(),
|
||||
half_proofs[1].clone(),
|
||||
];
|
||||
|
||||
let mut data = vec![MoneyFunction::OtcSwap as u8];
|
||||
full_params.encode(&mut data)?;
|
||||
let mut tx = Transaction {
|
||||
calls: vec![ContractCall { contract_id, data }],
|
||||
proofs: vec![full_proofs],
|
||||
signatures: vec![],
|
||||
};
|
||||
eprintln!("Signing swap transaction");
|
||||
let sigs = tx.create_sigs(&mut OsRng, &half_keys)?;
|
||||
tx.signatures = vec![sigs];
|
||||
|
||||
Ok(tx)
|
||||
}
|
||||
|
||||
/// Sign a given transaction by retrieving the secret key from the encrypted
|
||||
/// note and prepending it to the transaction's signatures.
|
||||
pub async fn sign_swap(&self, tx: &mut Transaction) -> Result<()> {
|
||||
// We need our secret keys to try and decrypt the note
|
||||
let secret_keys = self.wallet_secrets().await?;
|
||||
let params: MoneyTransferParams = deserialize(&tx.calls[0].data[1..])?;
|
||||
|
||||
// Our output should be outputs[0] so we try to decrypt that.
|
||||
let ciphertext = params.outputs[0].ciphertext.clone();
|
||||
let ephem_public = params.outputs[0].ephem_public;
|
||||
let encrypted_note = EncryptedNote { ciphertext, ephem_public };
|
||||
|
||||
eprintln!("Trying to decrypt note in outputs[0]");
|
||||
let mut skey = None;
|
||||
|
||||
for secret in &secret_keys {
|
||||
if let Ok(note) = encrypted_note.decrypt(secret) {
|
||||
let s: SecretKey = deserialize(¬e.memo)?;
|
||||
eprintln!("Successfully decrypted and found an ephemeral secret");
|
||||
skey = Some(s);
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let Some(skey) = skey else {
|
||||
eprintln!("Error: Failed to decrypt note with any of our secret keys");
|
||||
return Err(anyhow!("Failed to decrypt note with any of our secret keys"))
|
||||
};
|
||||
|
||||
eprintln!("Signing swap transaction");
|
||||
let sigs = tx.create_sigs(&mut OsRng, &[skey])?;
|
||||
tx.signatures[0].insert(0, sigs[0]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ impl ProvingKey {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, PartialEq, Eq, SerialEncodable, SerialDecodable)]
|
||||
#[derive(Clone, Default, PartialEq, Eq, SerialEncodable, SerialDecodable)]
|
||||
pub struct Proof(Vec<u8>);
|
||||
|
||||
impl AsRef<[u8]> for Proof {
|
||||
@@ -63,6 +63,13 @@ impl AsRef<[u8]> for Proof {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for Proof {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "Proof({:?})", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Proof {
|
||||
pub fn create(
|
||||
pk: &ProvingKey,
|
||||
|
||||
Reference in New Issue
Block a user