bin: Remove darkotc and merge it with drk

This commit is contained in:
parazyd
2022-12-06 12:38:30 +01:00
parent 17f999449c
commit f722db3131
11 changed files with 392 additions and 1013 deletions

17
Cargo.lock generated
View File

@@ -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"

View File

@@ -22,7 +22,6 @@ members = [
"bin/zkas",
#"bin/cashierd",
"bin/darkfid",
"bin/darkotc",
"bin/drk",
"bin/faucetd",
"bin/fud/fu",

View File

@@ -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"

View File

@@ -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!

View File

@@ -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(&note, &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(&note, &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(&note, &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(())
}
}
}

View File

@@ -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))
}
}

View File

@@ -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);
}

View File

@@ -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)?;

View File

@@ -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
View 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(&note.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(())
}
}

View File

@@ -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,