From 2256f415070017129c0e429e6db5654324c47149 Mon Sep 17 00:00:00 2001 From: "S. Santos" Date: Thu, 25 Apr 2024 04:23:54 -0300 Subject: [PATCH 1/2] Change HRP to 'ml' and 'tml' --- clients/rust/src/transaction.rs | 2 + lib/src/lib.rs | 71 +++++++++++++---------- lib/src/transaction.rs | 3 +- lib/src/wallet/key_derivation.rs | 2 +- server/src/endpoints/transfer_receiver.rs | 2 +- wasm/src/lib.rs | 65 ++++++++++++++++----- 6 files changed, 96 insertions(+), 49 deletions(-) diff --git a/clients/rust/src/transaction.rs b/clients/rust/src/transaction.rs index 640aca38..ebada1b5 100644 --- a/clients/rust/src/transaction.rs +++ b/clients/rust/src/transaction.rs @@ -6,6 +6,8 @@ use crate::{client_config::ClientConfig, utils::info_config}; pub async fn new_transaction(client_config: &ClientConfig, coin: &mut Coin, to_address: &str, qt_backup_tx: u32, is_withdrawal: bool, block_height: Option, network: &str) -> Result { + // TODO: validate address first + let coin_nonce = mercury_lib::transaction::create_and_commit_nonces(&coin)?; coin.secret_nonce = Some(coin_nonce.secret_nonce); coin.public_nonce = Some(coin_nonce.public_nonce); diff --git a/lib/src/lib.rs b/lib/src/lib.rs index caaf688b..1fccb6de 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -9,13 +9,21 @@ use std::str::FromStr; use bech32::{Variant, ToBase32, FromBase32}; use bip39::Mnemonic; -use bitcoin::{secp256k1::{ffi::types::AlignedType, Secp256k1, SecretKey, AllPreallocated, PublicKey}, bip32::{ExtendedPrivKey, DerivationPath, ChildNumber}}; +use bitcoin::{bip32::{ChildNumber, DerivationPath, ExtendedPrivKey}, secp256k1::{ffi::types::AlignedType, AllPreallocated, PublicKey, Secp256k1, SecretKey}}; use anyhow::{Result, anyhow}; -pub fn encode_sc_address(user_pubkey: &PublicKey, auth_pubkey: &PublicKey) -> String { +const MAINNET_HRP : &str = "ml"; +const TESTNET_HRP : &str = "tml"; - let hrp = "sc"; +pub fn encode_sc_address(user_pubkey: &PublicKey, auth_pubkey: &PublicKey, network: bitcoin::Network) -> Result { + + let mut hrp = TESTNET_HRP; + + if network == bitcoin::Network::Bitcoin { + hrp = MAINNET_HRP; + } + let variant = Variant::Bech32m; let mut data = Vec::::new(); @@ -23,15 +31,15 @@ pub fn encode_sc_address(user_pubkey: &PublicKey, auth_pubkey: &PublicKey) -> St data.append(&mut user_pubkey.clone().serialize().to_vec()); data.append(&mut auth_pubkey.clone().serialize().to_vec()); - let encoded = bech32::encode(hrp, data.to_base32(), variant).unwrap(); + let encoded = bech32::encode(hrp, data.to_base32(), variant)?; - encoded + Ok(encoded) } pub fn decode_transfer_address(sc_address: &str) -> Result<(u8, PublicKey, PublicKey)> { - let (hrp, data, variant) = bech32::decode(sc_address).unwrap(); + let (hrp, data, variant) = bech32::decode(sc_address)?; - if hrp != "sc" { + if hrp != MAINNET_HRP && hrp != TESTNET_HRP { return Err(anyhow!("Invalid SC address".to_string())); } @@ -39,35 +47,35 @@ pub fn decode_transfer_address(sc_address: &str) -> Result<(u8, PublicKey, Publi return Err(anyhow!("Invalid address".to_string())); } - let decoded_data = Vec::::from_base32(&data).unwrap(); + let decoded_data = Vec::::from_base32(&data)?; let version = decoded_data[0]; - let user_pubkey = PublicKey::from_slice(&decoded_data[1..34]).unwrap(); - let auth_pubkey = PublicKey::from_slice(&decoded_data[34..67]).unwrap(); + let user_pubkey = PublicKey::from_slice(&decoded_data[1..34])?; + let auth_pubkey = PublicKey::from_slice(&decoded_data[34..67])?; Ok((version, user_pubkey, auth_pubkey)) } -fn get_key(secp: &Secp256k1>, root: ExtendedPrivKey, derivation_path: &str, change_index: u32, address_index:u32) -> SecretKey { +fn get_key(secp: &Secp256k1>, root: ExtendedPrivKey, derivation_path: &str, change_index: u32, address_index:u32) -> Result { // derive child xpub - let path = DerivationPath::from_str(derivation_path).unwrap(); - let child = root.derive_priv(&secp, &path).unwrap(); + let path = DerivationPath::from_str(derivation_path)?; + let child = root.derive_priv(&secp, &path)?; // generate key at m/change_index_number/address_index_number - let change_index_number = ChildNumber::from_normal_idx(change_index).unwrap(); - let address_index_number = ChildNumber::from_normal_idx(address_index).unwrap(); + let change_index_number = ChildNumber::from_normal_idx(change_index)?; + let address_index_number = ChildNumber::from_normal_idx(address_index)?; - let secret_key = child.derive_priv(&secp, &[change_index_number, address_index_number]).unwrap().private_key; + let secret_key = child.derive_priv(&secp, &[change_index_number, address_index_number])?.private_key; - secret_key + Ok(secret_key) } -pub fn get_sc_address(mnemonic: &str, index: u32) -> String { +pub fn get_sc_address(mnemonic: &str, index: u32, network: &str) -> Result { - let network = bitcoin::Network::Testnet; + let network = utils::get_network(network)?; // 1. Get the mnemonic from the wallet - let mnemonic = Mnemonic::parse_normalized(mnemonic).expect("Failed to parse mnemonic"); + let mnemonic = Mnemonic::parse_normalized(mnemonic)?; // 2. Get the seed from the mnemonic let seed = mnemonic.to_seed_normalized(""); @@ -75,22 +83,20 @@ pub fn get_sc_address(mnemonic: &str, index: u32) -> String { // we need secp256k1 context for key derivation let mut buf: Vec = Vec::new(); buf.resize(Secp256k1::preallocate_size(), AlignedType::zeroed()); - let secp = Secp256k1::preallocated_new(buf.as_mut_slice()).unwrap(); + let secp = Secp256k1::preallocated_new(buf.as_mut_slice())?; // calculate root key from seed - let root = ExtendedPrivKey::new_master(network, &seed).unwrap(); + let root = ExtendedPrivKey::new_master(network, &seed)?; let user_derivation_path = "m/86h/0h/0h"; - let user_seckey = get_key(&secp, root, &user_derivation_path, 0, index); + let user_seckey = get_key(&secp, root, &user_derivation_path, 0, index)?; let user_pubkey = user_seckey.public_key(&secp); let auth_derivation_path = "m/89h/0h/0h"; - let auth_seckey = get_key(&secp, root, &auth_derivation_path, 0, index); + let auth_seckey = get_key(&secp, root, &auth_derivation_path, 0, index)?; let auth_pubkey = auth_seckey.public_key(&secp); - let sc_address = encode_sc_address(&user_pubkey, &auth_pubkey); - - sc_address + encode_sc_address(&user_pubkey, &auth_pubkey, network) } @@ -102,9 +108,14 @@ mod tests { fn it_works() { let mnemonic = String::from("ticket sock try two evidence employ fresh beauty settle general ridge lonely"); - let sc_address = get_sc_address(&mnemonic, 0); - let expected_sc_address = "sc1qqpgha2armzyvwwglqty24ztegut27neyvlkpu3894adsgascq96tjqr78gy6adlzsre3fqyrxdx8n68henrd6fzcgfwcltu3sesuh05nvxs56pauf"; - + let network = "testnet"; + let sc_address = get_sc_address(&mnemonic, 0, network).unwrap(); + let expected_sc_address = "tml1qqpgha2armzyvwwglqty24ztegut27neyvlkpu3894adsgascq96tjqr78gy6adlzsre3fqyrxdx8n68henrd6fzcgfwcltu3sesuh05nvxslxjnxw"; + assert_eq!(sc_address, expected_sc_address); + + let network = "mainnet"; + let sc_address = get_sc_address(&mnemonic, 0, network).unwrap(); + let expected_sc_address = "ml1qqpgha2armzyvwwglqty24ztegut27neyvlkpu3894adsgascq96tjqr78gy6adlzsre3fqyrxdx8n68henrd6fzcgfwcltu3sesuh05nvxs2dd888"; assert_eq!(sc_address, expected_sc_address); } } diff --git a/lib/src/transaction.rs b/lib/src/transaction.rs index a3f38884..e31ef644 100644 --- a/lib/src/transaction.rs +++ b/lib/src/transaction.rs @@ -117,8 +117,7 @@ pub fn create_tx_out( let mut recipient_address: Option
= None; - let hrp = "sc"; - if to_address.starts_with(hrp) { + if to_address.starts_with(crate::MAINNET_HRP) || to_address.starts_with(crate::TESTNET_HRP) { let (_, recipient_user_pubkey, _) = decode_transfer_address(to_address)?; let new_address = Address::p2tr(&Secp256k1::new(), recipient_user_pubkey.x_only_public_key().0, None, network); recipient_address = Some(new_address); diff --git a/lib/src/wallet/key_derivation.rs b/lib/src/wallet/key_derivation.rs index 4027a233..22f6e16d 100644 --- a/lib/src/wallet/key_derivation.rs +++ b/lib/src/wallet/key_derivation.rs @@ -103,7 +103,7 @@ impl Wallet { let user_pubkey = client_secret_key.public_key(&secp).to_string(); let auth_pubkey = auth_secret.public_key(&secp).to_string(); - let coin_address = encode_sc_address(&client_pubkey_share, &auth_key_data.public_key); + let coin_address = encode_sc_address(&client_pubkey_share, &auth_key_data.public_key, network)?; let coin = Coin { index: address_index, diff --git a/server/src/endpoints/transfer_receiver.rs b/server/src/endpoints/transfer_receiver.rs index 5bbe6c8c..7f147a4c 100644 --- a/server/src/endpoints/transfer_receiver.rs +++ b/server/src/endpoints/transfer_receiver.rs @@ -71,7 +71,7 @@ async fn get_enclave_pubkey_and_x1pub(pool: &sqlx::PgPool, statechain_id: &str) } #[get("/info/statechain/")] -pub async fn statechain_info(statechain_entity: &State, statechain_id: String) -> status::Custom> { +pub async fn statechain_info(statechain_entity: &State, statechain_id: &str) -> status::Custom> { let lockbox_endpoint = statechain_entity.config.lockbox.clone().unwrap(); let path = "signature_count"; diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs index e7d5f607..333fc9f6 100644 --- a/wasm/src/lib.rs +++ b/wasm/src/lib.rs @@ -2,9 +2,7 @@ mod utils; -use std::str::FromStr; - -use mercury_lib::{wallet::{Wallet, Token, Coin, Activity, BackupTx, CoinStatus}, utils::ServerConfig, deposit::DepositMsg1Response, transaction::get_partial_sig_request, transfer::{sender::create_transfer_signature, receiver::{decrypt_transfer_msg, TxOutpoint, StatechainInfo, StatechainInfoResponsePayload, create_transfer_receiver_request_payload, get_new_key_info}, TransferMsg}, decode_transfer_address}; +use mercury_lib::{decode_transfer_address, deposit::DepositMsg1Response, transfer::{receiver::{create_transfer_receiver_request_payload, decrypt_transfer_msg, get_new_key_info, StatechainInfo, StatechainInfoResponsePayload, TxOutpoint}, sender::create_transfer_signature, TransferMsg}, utils::ServerConfig, wallet::{Activity, BackupTx, Coin, Settings, Token, Wallet}}; use wasm_bindgen::prelude::*; use serde::{Serialize, Deserialize}; use bip39::Mnemonic; @@ -98,9 +96,9 @@ pub fn getBalance(wallet_json: JsValue) -> u32 { } #[wasm_bindgen] -pub fn getSCAddress(wallet_json: JsValue, index: u32) -> String { +pub fn getSCAddress(wallet_json: JsValue, index: u32, network: String) -> String { let wallet: Wallet = serde_wasm_bindgen::from_value(wallet_json).unwrap(); - let address = mercury_lib::get_sc_address(&wallet.mnemonic, index); + let address = mercury_lib::get_sc_address(&wallet.mnemonic, index, &network).unwrap(); address.to_string() } @@ -114,19 +112,38 @@ pub fn generateMnemonic() -> String { #[wasm_bindgen] pub fn fromMnemonic(name: String, mnemonic: String) -> JsValue { + + let settings = Settings { + network: String::from("signet"), + block_explorerURL: None, + torProxyHost: None, + torProxyPort: None, + torProxyControlPassword: None, + torProxyControlPort: None, + statechainEntityApi: String::from("http://127.0.0.1:8000"), + torStatechainEntityApi: None, + electrumProtocol: String::from("tcp"), + electrumHost: String::from("signet-electrumx.wakiyamap.dev"), + electrumPort: String::from("50001"), + electrumType: String::from("electrs"), + notifications: false, + tutorials: false + }; + let mut wallet = Wallet { - name: name, - mnemonic: mnemonic, + name, + mnemonic, version: String::from("0.1.0"), - state_entity_endpoint: String::from(""), - electrum_endpoint: String::from(""), - network: String::from("testnet"), + state_entity_endpoint: String::from("http://127.0.0.1:8000"), + electrum_endpoint: String::from("tcp://signet-electrumx.wakiyamap.dev:50001"), + network: String::from("signet"), blockheight: 0, initlock: 100000, interval: 10, tokens: Vec::new(), activities: Vec::new(), - coins: Vec::new() + coins: Vec::new(), + settings }; serde_wasm_bindgen::to_value(&wallet).unwrap() } @@ -504,19 +521,37 @@ pub fn getMockWallet() -> JsValue { } ]; */ + let settings = Settings { + network: String::from("signet"), + block_explorerURL: None, + torProxyHost: None, + torProxyPort: None, + torProxyControlPassword: None, + torProxyControlPort: None, + statechainEntityApi: String::from("http://127.0.0.1:8000"), + torStatechainEntityApi: None, + electrumProtocol: String::from("tcp"), + electrumHost: String::from("signet-electrumx.wakiyamap.dev"), + electrumPort: String::from("50001"), + electrumType: String::from("electrs"), + notifications: false, + tutorials: false + }; + let wallet = Wallet { name: String::from("Mock Wallet"), mnemonic: String::from("coil knock parade empower divorce scorpion float force carbon side wonder choice"), version: String::from("0.1.0"), - state_entity_endpoint: String::from(""), - electrum_endpoint: String::from(""), - network: String::from("testnet"), + state_entity_endpoint: String::from("http://127.0.0.1:8000"), + electrum_endpoint: String::from("tcp://signet-electrumx.wakiyamap.dev:50001"), + network: String::from("signet"), blockheight: 0, initlock: 100000, interval: 10, tokens, activities: activity, - coins: Vec::new() // coins + coins: Vec::new(), // coins + settings }; serde_wasm_bindgen::to_value(&wallet).unwrap() } \ No newline at end of file From 1f947b8f9b71491568f035bb1d6e1273dc5d0724 Mon Sep 17 00:00:00 2001 From: "S. Santos" Date: Fri, 26 Apr 2024 06:29:58 -0300 Subject: [PATCH 2/2] Add address validation and remove standalone rust client --- clients/rust/Cargo.toml | 20 +- clients/rust/src/broadcast_backup_tx.rs | 6 + clients/rust/src/deposit.rs | 2 +- clients/rust/src/main.rs | 34 +- clients/rust/src/transaction.rs | 16 +- clients/rust/src/transfer_receiver.rs | 2 +- clients/rust/src/transfer_sender.rs | 8 +- clients/rust/src/utils.rs | 2 - clients/rust/src/withdraw.rs | 8 +- clients/standalone-rust/.gitignore | 3 - clients/standalone-rust/Cargo.toml | 28 - clients/standalone-rust/README.md | 30 -- clients/standalone-rust/Settings.toml | 6 - .../migrations/0001_signer_data_table.sql | 65 --- .../standalone-rust/src/client_config/mod.rs | 80 --- .../client_config/sqilte_manager/common.rs | 71 --- .../client_config/sqilte_manager/deposit.rs | 90 ---- .../sqilte_manager/key_derivation.rs | 101 ---- .../src/client_config/sqilte_manager/mod.rs | 7 - .../sqilte_manager/transaction.rs | 19 - .../sqilte_manager/transfer_receiver.rs | 117 ---- .../sqilte_manager/transfer_sender.rs | 120 ----- .../client_config/sqilte_manager/withdraw.rs | 88 --- clients/standalone-rust/src/deposit.rs | 202 ------- clients/standalone-rust/src/electrum.rs | 35 -- clients/standalone-rust/src/error.rs | 7 - clients/standalone-rust/src/key_derivation.rs | 129 ----- clients/standalone-rust/src/main.rs | 225 -------- clients/standalone-rust/src/send_backup.rs | 259 --------- clients/standalone-rust/src/transaction.rs | 373 ------------- .../standalone-rust/src/transfer_receiver.rs | 500 ------------------ .../standalone-rust/src/transfer_sender.rs | 249 --------- clients/standalone-rust/src/utils.rs | 44 -- clients/standalone-rust/src/wallet.rs | 183 ------- clients/standalone-rust/src/withdraw.rs | 76 --- lib/src/lib.rs | 36 +- lib/src/transaction.rs | 14 +- lib/src/transfer/sender.rs | 4 +- lib/src/wallet/mod.rs | 1 + 39 files changed, 106 insertions(+), 3154 deletions(-) delete mode 100644 clients/standalone-rust/.gitignore delete mode 100644 clients/standalone-rust/Cargo.toml delete mode 100644 clients/standalone-rust/README.md delete mode 100644 clients/standalone-rust/Settings.toml delete mode 100644 clients/standalone-rust/migrations/0001_signer_data_table.sql delete mode 100644 clients/standalone-rust/src/client_config/mod.rs delete mode 100644 clients/standalone-rust/src/client_config/sqilte_manager/common.rs delete mode 100644 clients/standalone-rust/src/client_config/sqilte_manager/deposit.rs delete mode 100644 clients/standalone-rust/src/client_config/sqilte_manager/key_derivation.rs delete mode 100644 clients/standalone-rust/src/client_config/sqilte_manager/mod.rs delete mode 100644 clients/standalone-rust/src/client_config/sqilte_manager/transaction.rs delete mode 100644 clients/standalone-rust/src/client_config/sqilte_manager/transfer_receiver.rs delete mode 100644 clients/standalone-rust/src/client_config/sqilte_manager/transfer_sender.rs delete mode 100644 clients/standalone-rust/src/client_config/sqilte_manager/withdraw.rs delete mode 100644 clients/standalone-rust/src/deposit.rs delete mode 100644 clients/standalone-rust/src/electrum.rs delete mode 100644 clients/standalone-rust/src/error.rs delete mode 100644 clients/standalone-rust/src/key_derivation.rs delete mode 100644 clients/standalone-rust/src/main.rs delete mode 100644 clients/standalone-rust/src/send_backup.rs delete mode 100644 clients/standalone-rust/src/transaction.rs delete mode 100644 clients/standalone-rust/src/transfer_receiver.rs delete mode 100644 clients/standalone-rust/src/transfer_sender.rs delete mode 100644 clients/standalone-rust/src/utils.rs delete mode 100644 clients/standalone-rust/src/wallet.rs delete mode 100644 clients/standalone-rust/src/withdraw.rs diff --git a/clients/rust/Cargo.toml b/clients/rust/Cargo.toml index 8f3dac81..a234e5ce 100644 --- a/clients/rust/Cargo.toml +++ b/clients/rust/Cargo.toml @@ -6,24 +6,24 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0" +bech32 = { version = "0.9.1", default-features = false } bitcoin = { version = "0.30.1", features = ["serde", "base64", "rand-std", "std", "bitcoinconsensus"], default-features = false } bip39 = "1.2.0" -bech32 = { version = "0.9.1", default-features = false } +clap = { version = "4.2.5", features = ["derive"]} +chrono = "0.4.31" config = "0.13.1" electrum-client = "0.18.0" +hex = "0.4.3" +rand = "0.8.5" reqwest = { version = "0.11.16", features = ["blocking", "json", "socks"] } -tokio = { version = "1.27.0", features = ["full"] } -sqlx = { version = "0.7", features = [ "runtime-tokio", "sqlite", "time", "uuid" ] } +schemars = { version = "0.8.12", features = ["chrono", "uuid"] } +secp256k1-zkp = { git = "https://github.com/ssantos21/rust-secp256k1-zkp.git", branch = "blinded-musig-scheme", features = [ "rand-std", "bitcoin_hashes", "std" ] } serde = { version = "1.0.163", features = ["derive"] } serde_json = "1.0.96" -schemars = { version = "0.8.12", features = ["chrono", "uuid"] } +sqlx = { version = "0.7", features = [ "runtime-tokio", "sqlite", "time", "uuid" ] } +tokio = { version = "1.27.0", features = ["full"] } uuid = { version = "1.3.1", features = ["v4", "serde"] } -clap = { version = "4.2.5", features = ["derive"]} -rand = "0.8.5" -hex = "0.4.3" -secp256k1-zkp = { git = "https://github.com/ssantos21/rust-secp256k1-zkp.git", branch = "blinded-musig-scheme", features = [ "rand-std", "bitcoin_hashes", "std" ] } -anyhow = "1.0" -chrono = "0.4.31" [dependencies.mercury-lib] path = "../../lib" \ No newline at end of file diff --git a/clients/rust/src/broadcast_backup_tx.rs b/clients/rust/src/broadcast_backup_tx.rs index a80d6684..f7042cc4 100644 --- a/clients/rust/src/broadcast_backup_tx.rs +++ b/clients/rust/src/broadcast_backup_tx.rs @@ -7,6 +7,12 @@ pub async fn execute(client_config: &ClientConfig, wallet_name: &str, statechain let mut wallet: mercury_lib::wallet::Wallet = get_wallet(&client_config.pool, &wallet_name).await?; + let is_address_valid = mercury_lib::validate_address(to_address, &wallet.network)?; + + if !is_address_valid { + return Err(anyhow!("Invalid address")); + } + let backup_txs = get_backup_txs(&client_config.pool, &statechain_id).await?; let backup_tx = backup_txs.iter().max_by_key(|tx| tx.tx_n); diff --git a/clients/rust/src/deposit.rs b/clients/rust/src/deposit.rs index b2bec99d..7b6b969d 100644 --- a/clients/rust/src/deposit.rs +++ b/clients/rust/src/deposit.rs @@ -39,7 +39,7 @@ pub async fn create_tx1(client_config: &ClientConfig, coin: &mut Coin, wallet_ne let to_address = get_user_backup_address(&coin, wallet_netwotk.to_string())?; - let signed_tx = new_transaction(&client_config, coin, &to_address, 0, false, None, wallet_netwotk).await?; + let signed_tx = new_transaction(&client_config, coin, &to_address, 0, false, None, wallet_netwotk, None).await?; if coin.public_nonce.is_none() { return Err(anyhow::anyhow!("coin.public_nonce is None")); diff --git a/clients/rust/src/main.rs b/clients/rust/src/main.rs index b3f7314d..126d61d9 100644 --- a/clients/rust/src/main.rs +++ b/clients/rust/src/main.rs @@ -27,33 +27,38 @@ struct Cli { #[derive(Subcommand)] enum Commands { /// Create a wallet - CreateWallet { name: String }, - /* - /// Create Aggregated Public Key - Deposit { wallet_name: String, token_id: String, amount: u32 }, - */ + CreateWallet { + /// The name of the wallet to create + name: String + }, /// Get new token. NewToken { }, /// Get new deposit address. Used to fund a new statecoin. NewDepositAddress { wallet_name: String, token_id: String, amount: u32 }, - /* /// Create a new statecoin from a deposit address - CreateStatecoin { wallet_name: String, deposit_address: String }, */ /// Broadcast the backup transaction to the network - BroadcastBackupTransaction { wallet_name: String, statechain_id: String, to_address: String, fee_rate: Option }, + BroadcastBackupTransaction { + wallet_name: String, + statechain_id: String, + to_address: String, + /// Transaction fee rate in sats per byte + fee_rate: Option + }, /// Broadcast the backup transaction to the network ListStatecoins { wallet_name: String }, /// Withdraw funds from a statechain coin to a bitcoin address - Withdraw { wallet_name: String, statechain_id: String, to_address: String, fee_rate: Option }, + Withdraw { + wallet_name: String, + statechain_id: String, + to_address: String, + /// Transaction fee rate in sats per byte + fee_rate: Option + }, /// Generate a transfer address to receive funds NewTransferAddress { wallet_name: String }, /// Send a statechain coin to a transfer address TransferSend { wallet_name: String, statechain_id: String, to_address: String, }, /// Send a statechain coin to a transfer address TransferReceive { wallet_name: String }, - /* - /// Update Coins] - UpdateCoins { wallet_name: String }, - */ } #[tokio::main(flavor = "current_thread")] @@ -120,9 +125,6 @@ async fn main() -> Result<()> { coin_status::update_coins(&client_config, &wallet_name).await?; transfer_receiver::execute(&client_config, &wallet_name).await?; }, - /*Commands::UpdateCoins { wallet_name } => { - utils::update_coins(&client_config, &wallet_name).await?; - }*/ } client_config.pool.close().await; diff --git a/clients/rust/src/transaction.rs b/clients/rust/src/transaction.rs index ebada1b5..bd1563e7 100644 --- a/clients/rust/src/transaction.rs +++ b/clients/rust/src/transaction.rs @@ -4,7 +4,15 @@ use anyhow::Result; use secp256k1_zkp::musig::MusigPartialSignature; use crate::{client_config::ClientConfig, utils::info_config}; -pub async fn new_transaction(client_config: &ClientConfig, coin: &mut Coin, to_address: &str, qt_backup_tx: u32, is_withdrawal: bool, block_height: Option, network: &str) -> Result { +pub async fn new_transaction( + client_config: &ClientConfig, + coin: &mut Coin, + to_address: &str, + qt_backup_tx: u32, + is_withdrawal: bool, + block_height: Option, + network: &str, + fee_rate: Option) -> Result { // TODO: validate address first @@ -29,7 +37,11 @@ pub async fn new_transaction(client_config: &ClientConfig, coin: &mut Coin, to_a let initlock = server_info.initlock; let interval = server_info.interval; - let fee_rate_sats_per_byte = server_info.fee_rate_sats_per_byte as u32; + + let fee_rate_sats_per_byte = match fee_rate { + Some(fee_rate) => fee_rate as u32, + None => server_info.fee_rate_sats_per_byte as u32, + }; let partial_sig_request = get_partial_sig_request( &coin, diff --git a/clients/rust/src/transfer_receiver.rs b/clients/rust/src/transfer_receiver.rs index d22bbd7a..715bf7ba 100644 --- a/clients/rust/src/transfer_receiver.rs +++ b/clients/rust/src/transfer_receiver.rs @@ -5,7 +5,7 @@ use anyhow::{anyhow, Result}; use bitcoin::{Txid, Address}; use chrono::Utc; use electrum_client::ElectrumApi; -use mercury_lib::{transfer::receiver::{GetMsgAddrResponsePayload, verify_transfer_signature, StatechainInfoResponsePayload, validate_tx0_output_pubkey, verify_latest_backup_tx_pays_to_user_pubkey, TxOutpoint, verify_transaction_signature, verify_blinded_musig_scheme, create_transfer_receiver_request_payload, TransferReceiverRequestPayload, get_new_key_info}, wallet::{Coin, Wallet, Activity, CoinStatus}, utils::{get_network, InfoConfig, get_blockheight}}; +use mercury_lib::{transfer::receiver::{GetMsgAddrResponsePayload, verify_transfer_signature, StatechainInfoResponsePayload, validate_tx0_output_pubkey, verify_latest_backup_tx_pays_to_user_pubkey, TxOutpoint, verify_transaction_signature, verify_blinded_musig_scheme, create_transfer_receiver_request_payload, TransferReceiverRequestPayload, get_new_key_info}, wallet::{Coin, Activity, CoinStatus}, utils::{get_network, InfoConfig, get_blockheight}}; use serde_json::Value; pub async fn new_transfer_address(client_config: &ClientConfig, wallet_name: &str) -> Result{ diff --git a/clients/rust/src/transfer_sender.rs b/clients/rust/src/transfer_sender.rs index 0cdb2691..14d34ea1 100644 --- a/clients/rust/src/transfer_sender.rs +++ b/clients/rust/src/transfer_sender.rs @@ -7,6 +7,12 @@ pub async fn execute(client_config: &ClientConfig, recipient_address: &str, wall let mut wallet: mercury_lib::wallet::Wallet = get_wallet(&client_config.pool, &wallet_name).await?; + let is_address_valid = mercury_lib::validate_address(recipient_address, &wallet.network)?; + + if !is_address_valid { + return Err(anyhow!("Invalid address")); + } + let mut backup_transactions = get_backup_txs(&client_config.pool, &statechain_id).await?; if backup_transactions.len() == 0 { @@ -109,7 +115,7 @@ async fn create_backup_tx_to_receiver(client_config: &ClientConfig, coin: &mut C let block_height = Some(get_blockheight(bkp_tx1)?); let is_withdrawal = false; - let signed_tx = new_transaction(client_config, coin, recipient_address, qt_backup_tx, is_withdrawal, block_height, network).await?; + let signed_tx = new_transaction(client_config, coin, recipient_address, qt_backup_tx, is_withdrawal, block_height, network, None).await?; Ok(signed_tx) } diff --git a/clients/rust/src/utils.rs b/clients/rust/src/utils.rs index 89ae84e6..a959a656 100644 --- a/clients/rust/src/utils.rs +++ b/clients/rust/src/utils.rs @@ -30,8 +30,6 @@ pub async fn info_config(client_config: &ClientConfig) -> Result{ let fee_rate_sats_per_byte = (fee_rate_btc_per_kb * 100000.0) as u64; - println!("fee_rate_sats_per_byte: {}", fee_rate_sats_per_byte); - Ok(InfoConfig { initlock, interval, diff --git a/clients/rust/src/withdraw.rs b/clients/rust/src/withdraw.rs index 22e3abcd..89a2716d 100644 --- a/clients/rust/src/withdraw.rs +++ b/clients/rust/src/withdraw.rs @@ -8,6 +8,12 @@ pub async fn execute(client_config: &ClientConfig, wallet_name: &str, statechain let mut wallet: mercury_lib::wallet::Wallet = get_wallet(&client_config.pool, &wallet_name).await?; + let is_address_valid = mercury_lib::validate_address(to_address, &wallet.network)?; + + if !is_address_valid { + return Err(anyhow!("Invalid address")); + } + let mut backup_txs = get_backup_txs(&client_config.pool, &statechain_id).await?; if backup_txs.len() == 0 { @@ -39,7 +45,7 @@ pub async fn execute(client_config: &ClientConfig, wallet_name: &str, statechain return Err(anyhow::anyhow!("Coin status must be CONFIRMED to withdraw it. The current status is {}", coin.status)); } - let signed_tx = new_transaction(client_config, coin, &to_address, qt_backup_tx, true, None, &wallet.network).await?; + let signed_tx = new_transaction(client_config, coin, &to_address, qt_backup_tx, true, None, &wallet.network, fee_rate).await?; if coin.public_nonce.is_none() { return Err(anyhow::anyhow!("coin.public_nonce is None")); diff --git a/clients/standalone-rust/.gitignore b/clients/standalone-rust/.gitignore deleted file mode 100644 index a0c4a4b8..00000000 --- a/clients/standalone-rust/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/target -wallet.db* -wallet.json diff --git a/clients/standalone-rust/Cargo.toml b/clients/standalone-rust/Cargo.toml deleted file mode 100644 index dcfcd2d5..00000000 --- a/clients/standalone-rust/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "mercury-client" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -bitcoin = { version = "0.30.1", features = ["serde", "base64", "rand-std", "std", "bitcoinconsensus"], default-features = false } -bip39 = "1.2.0" -bech32 = { version = "0.9.1", default-features = false } -config = "0.13.1" -ecies = {version = "0.2", default-features = false, features = ["pure"]} -electrum-client = "0.18.0" -reqwest = { version = "0.11.16", features = ["blocking", "json", "socks"] } -tokio = { version = "1.27.0", features = ["full"] } -sqlx = { version = "0.7", features = [ "runtime-tokio", "sqlite", "time", "uuid" ] } -serde = { version = "1.0.163", features = ["derive"] } -serde_json = "1.0.96" -schemars = { version = "0.8.12", features = ["chrono", "uuid"] } -uuid = { version = "1.3.1", features = ["v4", "serde"] } -clap = { version = "4.2.5", features = ["derive"]} -rand = "0.8.5" -hex = "0.4.3" -secp256k1-zkp = { git = "https://github.com/ssantos21/rust-secp256k1-zkp.git", branch = "blinded-musig-scheme", features = [ "rand-std", "bitcoin_hashes", "std" ] } - -[dependencies.mercury-lib] -path = "../../lib" \ No newline at end of file diff --git a/clients/standalone-rust/README.md b/clients/standalone-rust/README.md deleted file mode 100644 index e43a14c3..00000000 --- a/clients/standalone-rust/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# Mercury Layer Client - -Mercury layer client provides a user interface to the Mercury Layer protocol, via the Mercury Layer server. - -# Running - -1. Run the `server` project on localhost -2. Run one of the commands below - -# Some commands - -`cargo run show-mnemonic` shows wallet mnemonic - -`cargo run deposit ` creates a new statechain coin - -`cargo run get-balance` shows wallet balance - -`cargo run broadcast-backup-transaction ` broadcasts the backup transaction to the network - -`cargo run send-backup ` send all backup funds to the address provided - -`cargo run new-transfer-address` generates a statechain address to receive statechain coins - -`cargo run transfer-send ` transfers the specified statechain coin to the specified address - -`cargo run transfer-receive` scans for new statechain transfers - -`cargo run withdraw ` withdraws the statechain coin to the specified bitcoin address - -This is a work in progress. Several changes to the project are expected. \ No newline at end of file diff --git a/clients/standalone-rust/Settings.toml b/clients/standalone-rust/Settings.toml deleted file mode 100644 index a39a8594..00000000 --- a/clients/standalone-rust/Settings.toml +++ /dev/null @@ -1,6 +0,0 @@ -statechain_entity = "http://j23wevaeducxuy3zahd6bpn4x76cymwz2j3bdixv7ow4awjrg5p6jaid.onion" -tor_proxy = "socks5h://localhost:9050" -electrum_server = "tcp://127.0.0.1:50001" -network = "signet" -fee_rate_tolerance = 5 -database_file="wallet.db" \ No newline at end of file diff --git a/clients/standalone-rust/migrations/0001_signer_data_table.sql b/clients/standalone-rust/migrations/0001_signer_data_table.sql deleted file mode 100644 index c555615f..00000000 --- a/clients/standalone-rust/migrations/0001_signer_data_table.sql +++ /dev/null @@ -1,65 +0,0 @@ -CREATE TABLE IF NOT EXISTS signer_seed ( - seed BLOB NOT NULL, - blockheight INT, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -CREATE TABLE IF NOT EXISTS signer_data ( - - client_seckey_share BLOB UNIQUE, - client_pubkey_share BLOB UNIQUE, - backup_address TEXT, - - client_derivation_path TEXT, - auth_derivation_path TEXT, - change_index INT, - address_index INT, - - auth_seckey BLOB UNIQUE, - auth_pubkey BLOB UNIQUE, - - transfer_address TEXT, - - fingerprint TEXT, - - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -CREATE TABLE IF NOT EXISTS statechain_data ( - - token_id TEXT, - - statechain_id TEXT, - signed_statechain_id TEXT, - - amount INT, - server_pubkey_share BLOB, - aggregated_pubkey BLOB, - p2tr_agg_address TEXT, - - funding_txid TEXT, - funding_vout INT, - - status TEXT, - locktime INT, - - client_pubkey_share BLOB, - - tx_withdraw TEXT, - - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -CREATE TABLE IF NOT EXISTS backup_transaction ( - - tx_n INT, - statechain_id TEXT, - client_public_nonce BLOB, - server_public_nonce BLOB, - client_pubkey BLOB, - server_pubkey BLOB, - blinding_factor BLOB, - backup_tx BLOB, - recipient_address TEXT, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP -); \ No newline at end of file diff --git a/clients/standalone-rust/src/client_config/mod.rs b/clients/standalone-rust/src/client_config/mod.rs deleted file mode 100644 index b6224067..00000000 --- a/clients/standalone-rust/src/client_config/mod.rs +++ /dev/null @@ -1,80 +0,0 @@ -pub mod sqilte_manager; - -use bitcoin::Network; -use config::Config; -use sqlx::{Sqlite, migrate::MigrateDatabase, SqlitePool}; - -/// Config struct storing all StataChain Entity config -pub struct ClientConfig { - /// Active lockbox server addresses - pub statechain_entity: String, - /// Tor SOCKS5 proxy address - pub tor_proxy: String, - /// Electrum client - pub electrum_client: electrum_client::Client, - /// Electrum server url - pub electrum_server_url: String, - /// Bitcoin network name (testnet, regtest, mainnet) - pub network: Network, - /// Initial deposit backup nlocktime - pub fee_rate_tolerance: u32, - /// Database connection pool - pub pool: sqlx::Pool, -} - -impl ClientConfig { - pub async fn load() -> Self { - let settings = Config::builder() - .add_source(config::File::with_name("Settings")) - .build() - .unwrap(); - - let statechain_entity = settings.get_string("statechain_entity").unwrap(); - let tor_proxy = settings.get_string("tor_proxy").unwrap(); - let electrum_server = settings.get_string("electrum_server").unwrap(); - let network = settings.get_string("network").unwrap(); - let fee_rate_tolerance = settings.get_int("fee_rate_tolerance").unwrap() as u32; - let database_file = settings.get_string("database_file").unwrap(); - - // Open database connection pool - - if !Sqlite::database_exists(&database_file).await.unwrap_or(false) { - match Sqlite::create_database(&database_file).await { - Ok(_) => println!("Create db success"), - Err(error) => panic!("error: {}", error), - } - } - - let pool: sqlx::Pool = SqlitePool::connect(&database_file).await.unwrap(); - - sqlx::migrate!("./migrations") - .run(&pool) - .await - .unwrap(); - - // Convert network string to Network enum - - let network = match network.as_str() { - "signet" => Network::Signet, - "testnet" => Network::Testnet, - "regtest" => Network::Regtest, - "mainnet" => Network::Bitcoin, - _ => panic!("Invalid network name"), - }; - - // Create Electrum client - - let electrum_client = electrum_client::Client::new(electrum_server.as_str()).unwrap(); - - - ClientConfig { - statechain_entity, - tor_proxy, - electrum_client, - electrum_server_url: electrum_server, - network, - fee_rate_tolerance, - pool, - } - } -} diff --git a/clients/standalone-rust/src/client_config/sqilte_manager/common.rs b/clients/standalone-rust/src/client_config/sqilte_manager/common.rs deleted file mode 100644 index 490f81b1..00000000 --- a/clients/standalone-rust/src/client_config/sqilte_manager/common.rs +++ /dev/null @@ -1,71 +0,0 @@ -use secp256k1_zkp::PublicKey; -use sqlx::Row; - -use crate::{client_config::ClientConfig, error::CError}; - -impl ClientConfig { - - pub async fn insert_transaction( - &self, - tx_n: u32, - tx_bytes: &Vec, - client_pub_nonce: &[u8; 66], - server_pub_nonce: &[u8; 66], - client_pubkey: &PublicKey, - server_pubkey: &PublicKey, - blinding_factor: &[u8; 32], - statechain_id: &str, - recipient_address: &str) -> Result<(), CError>{ - - let row = sqlx::query("SELECT MAX(tx_n) FROM backup_transaction WHERE statechain_id = $1") - .bind(statechain_id) - .fetch_one(&self.pool) - .await - .unwrap(); - - let mut new_tx_n = row.get::(0); - - new_tx_n = new_tx_n + 1; - - if tx_n != new_tx_n { - let msg = format!("tx_n {} is not equal to the next tx_n {} in the database", tx_n, new_tx_n).to_string(); - return Err(CError::Generic(msg)); - } - - let query = "INSERT INTO backup_transaction \ - (tx_n, statechain_id, client_public_nonce, server_public_nonce, client_pubkey, server_pubkey, blinding_factor, backup_tx, recipient_address) \ - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)"; - - let _ = sqlx::query(query) - .bind(tx_n) - .bind(statechain_id) - .bind(client_pub_nonce.to_vec()) - .bind(server_pub_nonce.to_vec()) - .bind(client_pubkey.serialize().to_vec()) - .bind(server_pubkey.serialize().to_vec()) - .bind(blinding_factor.to_vec()) - .bind(tx_bytes) - .bind(recipient_address) - .execute(&self.pool) - .await - .unwrap(); - - Ok(()) - - } - - pub async fn update_coin_status_and_tx_withdraw(&self, statechain_id: &str, new_status: &str, txid: Option) { - - let query = "UPDATE statechain_data \ - SET status = $1, tx_withdraw = $2 \ - WHERE statechain_id = $3"; - - let _ = sqlx::query(query) - .bind(new_status) - .bind(txid) - .bind(statechain_id) - .execute(&self.pool) - .await - .unwrap(); - } -} \ No newline at end of file diff --git a/clients/standalone-rust/src/client_config/sqilte_manager/deposit.rs b/clients/standalone-rust/src/client_config/sqilte_manager/deposit.rs deleted file mode 100644 index d2c7c54b..00000000 --- a/clients/standalone-rust/src/client_config/sqilte_manager/deposit.rs +++ /dev/null @@ -1,90 +0,0 @@ -use bitcoin::{Address, Txid}; -use secp256k1_zkp::{PublicKey, schnorr::Signature}; -use sqlx::Row; - -use crate::{error::CError, client_config::ClientConfig}; - -impl ClientConfig { - - pub async fn insert_agg_pub_key( - &self, - token_id: &uuid::Uuid, - statechain_id: &str, - amount: u32, - server_pubkey_share: &PublicKey, - aggregated_pubkey: &PublicKey, - p2tr_agg_address: &Address, - client_pubkey_share: &PublicKey, - signed_statechain_id: &Signature) -> Result<(), CError> { - - let query = "\ - INSERT INTO statechain_data (token_id, statechain_id, amount, server_pubkey_share, aggregated_pubkey, p2tr_agg_address, client_pubkey_share, signed_statechain_id, status) \ - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 'AVAILABLE')"; - - let _ = sqlx::query(query) - .bind(token_id.to_string()) - .bind(statechain_id) - .bind(amount) - .bind(server_pubkey_share.serialize().to_vec()) - .bind(aggregated_pubkey.serialize().to_vec()) - .bind(p2tr_agg_address.to_string()) - .bind(client_pubkey_share.serialize().to_vec()) - .bind(signed_statechain_id.to_string()) - .execute(&self.pool) - .await - .unwrap(); - - Ok(()) - - } - - pub async fn update_funding_tx_outpoint(&self, txid: &Txid, vout: u32, statechain_id: &str) { - - let query = "\ - UPDATE statechain_data \ - SET funding_txid = $1, funding_vout = $2 \ - WHERE statechain_id = $3"; - - let _ = sqlx::query(query) - .bind(&txid.to_string()) - .bind(vout) - .bind(statechain_id) - .execute(&self.pool) - .await - .unwrap(); - } - - pub async fn update_locktime(&self, statechain_id: &str, locktime: u32) { - - let query = "\ - UPDATE statechain_data \ - SET locktime = $1 \ - WHERE statechain_id = $2"; - - let _ = sqlx::query(query) - .bind(locktime) - .bind(statechain_id) - .execute(&self.pool) - .await - .unwrap(); - } - - pub async fn get_backup_tx(&self, statechain_id: &str) -> Vec { - - let query = "\ - SELECT backup_tx \ - FROM backup_transaction \ - WHERE tx_n = (SELECT MAX(tx_n) FROM backup_transaction WHERE statechain_id = $1) - AND statechain_id = $1"; - - let row = sqlx::query(query) - .bind(statechain_id) - .fetch_one(&self.pool) - .await - .unwrap(); - - let tx_bytes = row.get::, _>("backup_tx"); - - tx_bytes - } -} diff --git a/clients/standalone-rust/src/client_config/sqilte_manager/key_derivation.rs b/clients/standalone-rust/src/client_config/sqilte_manager/key_derivation.rs deleted file mode 100644 index 75e13d88..00000000 --- a/clients/standalone-rust/src/client_config/sqilte_manager/key_derivation.rs +++ /dev/null @@ -1,101 +0,0 @@ -use bitcoin::Address; -use secp256k1_zkp::PublicKey; -use sqlx::Row; - -use crate::{client_config::ClientConfig, electrum, key_derivation::KeyData}; - -impl ClientConfig { - - pub async fn generate_or_get_seed(&self) -> ([u8; 32], u32) { - - let rows = sqlx::query("SELECT * FROM signer_seed") - .fetch_all(&self.pool) - .await - .unwrap(); - - if rows.len() > 1 { - panic!("More than one seed in database"); - } - - if rows.len() == 1 { - let row = rows.get(0).unwrap(); - let seed = row.get::, _>("seed"); - let block_height = row.get::("blockheight"); - let mut seed_array = [0u8; 32]; - seed_array.copy_from_slice(&seed); - return (seed_array, block_height); - } else { - let mut seed = [0u8; 32]; // 256 bits - rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut seed); - - // TODO: Now that this function gets the block height, it is better to split this function into two - // One for generating the seed and one for getting the seed - let block_header = electrum::block_headers_subscribe_raw(&self.electrum_client); - let block_height = block_header.height; - - let query = "INSERT INTO signer_seed (seed, blockheight) VALUES ($1, $2)"; - let _ = sqlx::query(query) - .bind(seed.to_vec()) - .bind(block_height as u32) - .execute(&self.pool) - .await - .unwrap(); - - (seed, block_height as u32) - } - } - - pub async fn get_next_address_index(&self, change_index: u32) -> u32 { - - let row = sqlx::query("SELECT MAX(address_index) FROM signer_data WHERE change_index = $1") - .bind(change_index) - .fetch_one(&self.pool) - .await - .unwrap(); - - let index = row.get::, _>(0); - - if index.is_some() { - return index.unwrap() + 1; - } else { - return 0; - } - } - - pub async fn insert_agg_key_data(&self, key_data: &KeyData, backup_address: &Address) { - - let query = - "INSERT INTO signer_data (client_seckey_share, client_pubkey_share, backup_address, fingerprint, client_derivation_path, change_index, address_index) \ - VALUES ($1, $2, $3, $4, $5, $6, $7)"; - - let _ = sqlx::query(query) - .bind(&key_data.secret_key.secret_bytes().to_vec()) - .bind(&key_data.public_key.serialize().to_vec()) - .bind(&backup_address.to_string()) - .bind(&key_data.fingerprint) - .bind(&key_data.derivation_path) - .bind(key_data.change_index) - .bind(key_data.address_index) - .execute(&self.pool) - .await - .unwrap(); - } - - pub async fn update_auth_key_data(&self, key_data: &KeyData, client_pubkey_share: &PublicKey, transfer_address: &str) { - - let query = "\ - UPDATE signer_data \ - SET auth_derivation_path = $1, auth_seckey = $2, auth_pubkey = $3, transfer_address = $4 \ - WHERE client_pubkey_share = $5"; - - let _ = sqlx::query(query) - .bind(&key_data.derivation_path) - .bind(&key_data.secret_key.secret_bytes().to_vec()) - .bind(&key_data.public_key.serialize().to_vec()) - .bind(transfer_address) - .bind(&client_pubkey_share.serialize().to_vec()) - .execute(&self.pool) - .await - .unwrap(); - } -} \ No newline at end of file diff --git a/clients/standalone-rust/src/client_config/sqilte_manager/mod.rs b/clients/standalone-rust/src/client_config/sqilte_manager/mod.rs deleted file mode 100644 index 17326ac3..00000000 --- a/clients/standalone-rust/src/client_config/sqilte_manager/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod deposit; -pub mod common; -pub mod key_derivation; -pub mod transaction; -pub mod transfer_sender; -pub mod withdraw; -pub mod transfer_receiver; diff --git a/clients/standalone-rust/src/client_config/sqilte_manager/transaction.rs b/clients/standalone-rust/src/client_config/sqilte_manager/transaction.rs deleted file mode 100644 index 1eedd3ad..00000000 --- a/clients/standalone-rust/src/client_config/sqilte_manager/transaction.rs +++ /dev/null @@ -1,19 +0,0 @@ -use sqlx::Row; - -use crate::client_config::ClientConfig; - -impl ClientConfig { - - pub async fn count_backup_tx(&self, statechain_id: &str) -> u32 { - - let row = sqlx::query("SELECT count(*) FROM backup_transaction WHERE statechain_id = $1") - .bind(statechain_id) - .fetch_one(&self.pool) - .await - .unwrap(); - - let count = row.get::(0); - - count - } -} \ No newline at end of file diff --git a/clients/standalone-rust/src/client_config/sqilte_manager/transfer_receiver.rs b/clients/standalone-rust/src/client_config/sqilte_manager/transfer_receiver.rs deleted file mode 100644 index 7031ef9a..00000000 --- a/clients/standalone-rust/src/client_config/sqilte_manager/transfer_receiver.rs +++ /dev/null @@ -1,117 +0,0 @@ -use bitcoin::{Address, Txid}; -use secp256k1_zkp::{PublicKey, SecretKey, schnorr::Signature}; -use sqlx::Row; - -use crate::client_config::ClientConfig; - -impl ClientConfig { - pub async fn get_all_auth_pubkey(&self) -> Vec::<(SecretKey, PublicKey, SecretKey, PublicKey)>{ - let rows = sqlx::query("SELECT auth_seckey, auth_pubkey, client_seckey_share, client_pubkey_share FROM signer_data") - .fetch_all(&self.pool) - .await - .unwrap(); - - let mut auth_pubkeys = Vec::<(SecretKey, PublicKey, SecretKey, PublicKey)>::new(); - - for row in rows { - - let auth_secret_key_bytes = row.get::, _>("auth_seckey"); - let auth_secret_key = SecretKey::from_slice(&auth_secret_key_bytes).unwrap(); - - let auth_pubkey_bytes = row.get::, _>("auth_pubkey"); - let auth_pubkey = PublicKey::from_slice(&auth_pubkey_bytes).unwrap(); - - let client_seckey_share_bytes = row.get::, _>("client_seckey_share"); - let client_seckey_share = SecretKey::from_slice(&client_seckey_share_bytes).unwrap(); - - let client_pubkey_bytes = row.get::, _>("client_pubkey_share"); - let client_pubkey = PublicKey::from_slice(&client_pubkey_bytes).unwrap(); - - auth_pubkeys.push((auth_secret_key, auth_pubkey, client_seckey_share, client_pubkey)); - } - - auth_pubkeys - } - - pub async fn insert_or_update_new_statechain( - &self, - statechain_id: &str, - amount: u32, - server_pubkey_share: &PublicKey, - aggregated_pubkey: &PublicKey, - p2tr_agg_address: &Address, - client_pubkey_share: &PublicKey, - signed_statechain_id: &Signature, - txid: &Txid, - vout: u32, - locktime: u32, - vec_backup_transactions: &Vec) { - - let mut transaction = self.pool.begin().await.unwrap(); - - let query = "\ - DELETE FROM backup_transaction \ - WHERE statechain_id = $1"; - - let _ = sqlx::query(query) - .bind(statechain_id) - .execute(&mut *transaction) - .await - .unwrap(); - - let query = "\ - DELETE FROM statechain_data \ - WHERE statechain_id = $1"; - - let _ = sqlx::query(query) - .bind(statechain_id) - .execute(&mut *transaction) - .await - .unwrap(); - - let query = "\ - INSERT INTO statechain_data (statechain_id, amount, server_pubkey_share, aggregated_pubkey, p2tr_agg_address, funding_txid, funding_vout, client_pubkey_share, signed_statechain_id, locktime, status) \ - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 'AVAILABLE')"; - - let _ = sqlx::query(query) - .bind(statechain_id) - .bind(amount) - .bind(server_pubkey_share.serialize().to_vec()) - .bind(aggregated_pubkey.serialize().to_vec()) - .bind(p2tr_agg_address.to_string()) - .bind(txid.to_string()) - .bind(vout) - .bind(client_pubkey_share.serialize().to_vec()) - .bind(signed_statechain_id.to_string()) - .bind(locktime) - .execute(&mut *transaction) - .await - .unwrap(); - - for backup_tx in vec_backup_transactions { - - let query = "INSERT INTO backup_transaction \ - (tx_n, statechain_id, client_public_nonce, server_public_nonce, client_pubkey, server_pubkey, blinding_factor, backup_tx, recipient_address) \ - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)"; - - let tx_bytes = bitcoin::consensus::encode::serialize(&backup_tx.tx); - - let _ = sqlx::query(query) - .bind(backup_tx.tx_n) - .bind(statechain_id) - .bind(backup_tx.client_public_nonce.serialize().to_vec()) - .bind(backup_tx.server_public_nonce.serialize().to_vec()) - .bind(backup_tx.client_public_key.serialize().to_vec()) - .bind(backup_tx.server_public_key.serialize().to_vec()) - .bind(backup_tx.blinding_factor.as_bytes().to_vec()) - .bind(tx_bytes) - .bind(backup_tx.recipient_address.clone()) - .execute(&mut *transaction) - .await - .unwrap(); - } - - transaction.commit().await.unwrap(); - - } -} \ No newline at end of file diff --git a/clients/standalone-rust/src/client_config/sqilte_manager/transfer_sender.rs b/clients/standalone-rust/src/client_config/sqilte_manager/transfer_sender.rs deleted file mode 100644 index 2f586e03..00000000 --- a/clients/standalone-rust/src/client_config/sqilte_manager/transfer_sender.rs +++ /dev/null @@ -1,120 +0,0 @@ -use std::str::FromStr; - -use bitcoin::{Transaction, Address}; -use secp256k1_zkp::{PublicKey, SecretKey, Secp256k1}; -use sqlx::Row; - -use crate::client_config::ClientConfig; - -pub struct StatechainCoinDetails { - pub client_seckey: SecretKey, - pub client_pubkey: PublicKey, - pub amount: u64, - pub server_pubkey: PublicKey, - pub aggregated_pubkey: PublicKey, - pub p2tr_agg_address: Address, - pub auth_seckey: SecretKey, -} - -impl ClientConfig { - - pub async fn get_backup_transactions(&self, statechain_id: &str) -> Vec:: { - - let rows = sqlx::query("SELECT * FROM backup_transaction WHERE statechain_id = $1") - .bind(statechain_id) - .fetch_all(&self.pool) - .await - .unwrap(); - - let mut backup_transactions = Vec::::new(); - - for row in rows { - let row_statechain_id = row.get::("statechain_id"); - assert!(row_statechain_id == statechain_id); - - let tx_n = row.get::("tx_n"); - - let client_public_nonce = row.get::, _>("client_public_nonce"); - - let server_public_nonce = row.get::, _>("server_public_nonce"); - - let client_public_key_bytes = row.get::, _>("client_pubkey"); - let client_public_key = PublicKey::from_slice(&client_public_key_bytes).unwrap(); - - let server_public_key_bytes = row.get::, _>("server_pubkey"); - let server_public_key = PublicKey::from_slice(&server_public_key_bytes).unwrap(); - - let blinding_factor = row.get::, _>("blinding_factor"); - - let tx_bytes = row.get::, _>("backup_tx"); - let tx = bitcoin::consensus::deserialize::(&tx_bytes).unwrap(); - - let recipient_address = row.get::("recipient_address"); - - backup_transactions.push(mercury_lib::transfer::SenderBackupTransaction { - statechain_id: row_statechain_id, - tx_n, - tx, - client_public_nonce, - server_public_nonce, - client_public_key, - server_public_key, - blinding_factor, - recipient_address, - }); - } - - backup_transactions - } - - pub async fn get_statechain_coin_details(&self,statechain_id: &str) -> StatechainCoinDetails { - - let query = "\ - SELECT sid.client_seckey_share, sid.client_pubkey_share, std.amount, std.server_pubkey_share, std.aggregated_pubkey, std.p2tr_agg_address, sid.auth_seckey \ - FROM signer_data sid INNER JOIN statechain_data std \ - ON sid.client_pubkey_share = std.client_pubkey_share \ - WHERE std.statechain_id = $1"; - - let row = sqlx::query(query) - .bind(statechain_id) - .fetch_one(&self.pool) - .await - .unwrap(); - - let secret_key_bytes = row.get::, _>("client_seckey_share"); - let client_seckey = SecretKey::from_slice(&secret_key_bytes).unwrap(); - - let client_public_key_bytes = row.get::, _>("client_pubkey_share"); - let client_pubkey = PublicKey::from_slice(&client_public_key_bytes).unwrap(); - - let amount = row.get::("amount") as u64; - - let server_public_key_bytes = row.get::, _>("server_pubkey_share"); - let server_pubkey = PublicKey::from_slice(&server_public_key_bytes).unwrap(); - - let agg_public_key_bytes = row.get::, _>("aggregated_pubkey"); - let aggregated_pubkey = PublicKey::from_slice(&agg_public_key_bytes).unwrap(); - - let agg_address_str = row.get::("p2tr_agg_address"); - let p2tr_agg_address = Address::from_str(&agg_address_str).unwrap().require_network(self.network).unwrap(); - - let auth_seckey_bytes = row.get::, _>("auth_seckey"); - let auth_seckey = SecretKey::from_slice(&auth_seckey_bytes).unwrap(); - - let aggregated_xonly_pubkey = aggregated_pubkey.x_only_public_key().0; - let address = Address::p2tr(&Secp256k1::new(), aggregated_xonly_pubkey, None, self.network); - - assert!(address.to_string() == p2tr_agg_address.to_string()); - - StatechainCoinDetails { - client_seckey, - client_pubkey, - amount, - server_pubkey, - aggregated_pubkey, - p2tr_agg_address, - auth_seckey, - } - - } -} \ No newline at end of file diff --git a/clients/standalone-rust/src/client_config/sqilte_manager/withdraw.rs b/clients/standalone-rust/src/client_config/sqilte_manager/withdraw.rs deleted file mode 100644 index e1d7d9e2..00000000 --- a/clients/standalone-rust/src/client_config/sqilte_manager/withdraw.rs +++ /dev/null @@ -1,88 +0,0 @@ -use std::str::FromStr; - -use bitcoin::{Address, Txid}; -use mercury_lib::withdraw::CoinKeyDetails; -use secp256k1_zkp::{SecretKey, PublicKey, schnorr::Signature}; -use sqlx::Row; - -use crate::client_config::ClientConfig; - -impl ClientConfig { - pub async fn get_coin_and_key_info(&self, statechain_id: &str) -> CoinKeyDetails { - - let query = "SELECT \ - sid.client_seckey_share, \ - sid.client_pubkey_share, \ - std.amount, \ - std.server_pubkey_share, \ - std.aggregated_pubkey, \ - std.p2tr_agg_address, \ - sid.auth_seckey, \ - std.signed_statechain_id, \ - std.funding_txid, \ - std.funding_vout \ - FROM signer_data sid INNER JOIN statechain_data std \ - ON sid.client_pubkey_share = std.client_pubkey_share \ - WHERE std.statechain_id = $1"; - - let row = sqlx::query(query) - .bind(statechain_id) - .fetch_one(&self.pool) - .await - .unwrap(); - - let amount = row.get::("amount"); - - let client_seckey_share_bytes = row.get::, _>("client_seckey_share"); - let client_seckey_share = SecretKey::from_slice(&client_seckey_share_bytes).unwrap(); - - let server_pubkey_share_bytes = row.get::, _>("server_pubkey_share"); - let server_pubkey_share = PublicKey::from_slice(&server_pubkey_share_bytes).unwrap(); - - let aggregated_pubkey_bytes = row.get::, _>("aggregated_pubkey"); - let aggregated_pubkey = PublicKey::from_slice(&aggregated_pubkey_bytes).unwrap(); - - let agg_address_str = row.get::("p2tr_agg_address"); - let p2tr_agg_address = Address::from_str(&agg_address_str).unwrap().require_network(self.network).unwrap(); - - let client_pubkey_share_bytes = row.get::, _>("client_pubkey_share"); - let client_pubkey_share = PublicKey::from_slice(&client_pubkey_share_bytes).unwrap(); - - let auth_seckey_bytes = row.get::, _>("auth_seckey"); - let auth_seckey = SecretKey::from_slice(&auth_seckey_bytes).unwrap(); - - let signed_statechain_id = row.get::("signed_statechain_id"); - let signed_statechain_id = Signature::from_str(&signed_statechain_id).unwrap(); - - let utxo_tx_hash = row.get::("funding_txid"); - let utxo_tx_hash = Txid::from_str(&utxo_tx_hash).unwrap(); - - let utxo_vout = row.get::("funding_vout"); - - let row = sqlx::query("SELECT MAX(tx_n) FROM backup_transaction WHERE statechain_id = $1") - .bind(statechain_id) - .fetch_one(&self.pool) - .await - .unwrap(); - - let mut new_tx_n = row.get::(0); - - new_tx_n = new_tx_n + 1; - - let coin_key_details = CoinKeyDetails { - new_tx_n, - client_seckey: client_seckey_share, - client_pubkey: client_pubkey_share, - amount: amount as u64, - server_pubkey: server_pubkey_share, - aggregated_pubkey, - p2tr_agg_address, - auth_seckey, - signed_statechain_id, - utxo_tx_hash, - utxo_vout, - }; - - coin_key_details - } -} \ No newline at end of file diff --git a/clients/standalone-rust/src/deposit.rs b/clients/standalone-rust/src/deposit.rs deleted file mode 100644 index fab79afe..00000000 --- a/clients/standalone-rust/src/deposit.rs +++ /dev/null @@ -1,202 +0,0 @@ -use std::{str::FromStr, thread, time::Duration}; - -use bitcoin::{secp256k1, hashes::sha256, Address, Txid}; -use electrum_client::ListUnspentRes; -use secp256k1_zkp::{Secp256k1, Message, PublicKey, schnorr::Signature}; -use serde::{Serialize, Deserialize}; - -use crate::{key_derivation, error::CError, electrum, client_config::ClientConfig}; - -#[derive(Debug, Serialize, Deserialize)] -pub struct DepositRequestPayload { - amount: u64, - auth_key: String, - token_id: String, - signed_token_id: String, -} - -pub async fn execute(client_config: &ClientConfig, token_id: uuid::Uuid, amount: u64) -> Result { - - let address_data = key_derivation::get_new_address(client_config).await; - - let (statechain_id, - server_pubkey_share, - signed_statechain_id) = init(client_config, &address_data, token_id, amount).await; - - let secp = Secp256k1::new(); - - let aggregate_pubkey = address_data.client_pubkey_share.combine(&server_pubkey_share).unwrap(); - - let aggregated_xonly_pubkey = aggregate_pubkey.x_only_public_key().0; - - let aggregate_address = Address::p2tr(&secp, aggregated_xonly_pubkey, None, client_config.network); - - client_config.insert_agg_pub_key( - &token_id, - &statechain_id, - amount as u32, - &server_pubkey_share, - &aggregate_pubkey, - &aggregate_address, - &address_data.client_pubkey_share, - &signed_statechain_id).await.unwrap(); - - println!("address: {}", aggregate_address.to_string()); - - println!("waiting for deposit ...."); - - let delay = Duration::from_secs(5); - - let mut utxo: Option = None; - - loop { - let utxo_list = electrum::get_script_list_unspent(&client_config.electrum_client, &aggregate_address); - - for unspent in utxo_list { - if unspent.value == amount { - utxo = Some(unspent); - break; - } - } - - if utxo.is_some() { - break; - } - - thread::sleep(delay); - } - - let utxo = utxo.unwrap(); - - client_config.update_funding_tx_outpoint(&utxo.tx_hash, utxo.tx_pos as u32, &statechain_id).await; - - let block_header = electrum::block_headers_subscribe_raw(&client_config.electrum_client); - let block_height = block_header.height; - - let to_address = address_data.backup_address; - - let (tx, client_pub_nonce, server_pub_nonce, blinding_factor) = crate::transaction::new_backup_transaction( - client_config, - block_height as u32, - &statechain_id, - &signed_statechain_id, - &address_data.client_secret_key, - &address_data.client_pubkey_share, - &server_pubkey_share, - utxo.tx_hash, - utxo.tx_pos as u32, - &aggregate_pubkey, - &aggregate_address.script_pubkey(), - amount, - &to_address, - false,).await.unwrap(); - - let lock_time = tx.lock_time.to_consensus_u32(); - - client_config.update_locktime(&statechain_id, lock_time).await; - - let tx_bytes = bitcoin::consensus::encode::serialize(&tx); - - client_config.insert_transaction( - 1, - &tx_bytes, - &client_pub_nonce.serialize(), - &server_pub_nonce.serialize(), - &address_data.client_pubkey_share, &server_pubkey_share, - blinding_factor.as_bytes(), - &statechain_id, - &address_data.transfer_address - ).await.unwrap(); - - Ok(statechain_id) - -} - -pub async fn init(client_config: &ClientConfig, address_data: &key_derivation::AddressData ,token_id: uuid::Uuid, amount: u64) -> (String, PublicKey, Signature) { - - let msg = Message::from_hashed_data::(token_id.to_string().as_bytes()); - - let secp = Secp256k1::new(); - let auth_secret_key = address_data.auth_secret_key; - let keypair = secp256k1::KeyPair::from_seckey_slice(&secp, auth_secret_key.as_ref()).unwrap(); - let signed_token_id = secp.sign_schnorr(&msg, &keypair); - - let deposit_request_payload = DepositRequestPayload { - amount, - auth_key: address_data.auth_xonly_pubkey.to_string(), - token_id: token_id.to_string(), - signed_token_id: signed_token_id.to_string(), - }; - - let endpoint = client_config.statechain_entity.clone(); - let path = "deposit/init/pod"; - - let tor_proxy = client_config.tor_proxy.clone(); - - let mut client: reqwest::Client = reqwest::Client::new(); - if tor_proxy != "".to_string() { - let tor_proxy = reqwest::Proxy::all(tor_proxy).unwrap(); - client = reqwest::Client::builder().proxy(tor_proxy).build().unwrap(); - } - - let request = client.post(&format!("{}/{}", endpoint, path)); - - let value = match request.json(&deposit_request_payload).send().await { - Ok(response) => { - let text = response.text().await.unwrap(); - text - }, - Err(err) => { - // return Err(CError::Generic(err.to_string())); - panic!("error: {}", err); - }, - }; - - let response: mercury_lib::deposit::DepositMsg1Response = serde_json::from_str(value.as_str()).expect(&format!("failed to parse: {}", value.as_str())); - - let server_pubkey_share = PublicKey::from_str(&response.server_pubkey).unwrap(); - - let statechain_id = response.statechain_id.to_string(); - - let msg = Message::from_hashed_data::(statechain_id.to_string().as_bytes()); - let signed_statechain_id = secp.sign_schnorr(&msg, &keypair); - - (statechain_id, server_pubkey_share, signed_statechain_id) -} - -pub async fn broadcast_backup_tx(client_config: &ClientConfig, statechain_id: &str) -> Txid { - - let tx_bytes = client_config.get_backup_tx(statechain_id).await; - - let txid = electrum::transaction_broadcast_raw(&client_config.electrum_client, &tx_bytes); - - txid -} - -pub async fn get_token(client_config: &ClientConfig) -> String { - - let endpoint = client_config.statechain_entity.clone(); - let path = "deposit/get_token"; - - let tor_proxy = client_config.tor_proxy.clone(); - - let mut client: reqwest::Client = reqwest::Client::new(); - if tor_proxy != "".to_string() { - let tor_proxy = reqwest::Proxy::all(tor_proxy).unwrap(); - client = reqwest::Client::builder().proxy(tor_proxy).build().unwrap(); - } - let request = client.get(&format!("{}/{}", endpoint, path)); - - let response = request.send().await.unwrap(); - - if response.status() != 200 { - let response_body = response.text().await.unwrap(); - panic!("error: {}", response_body); - } - - let value = response.text().await.unwrap(); - - let token: mercury_lib::deposit::TokenID = serde_json::from_str(value.as_str()).unwrap(); - - return token.token_id; -} diff --git a/clients/standalone-rust/src/electrum.rs b/clients/standalone-rust/src/electrum.rs deleted file mode 100644 index 362a66c0..00000000 --- a/clients/standalone-rust/src/electrum.rs +++ /dev/null @@ -1,35 +0,0 @@ -use bitcoin::{Txid, Script}; -use electrum_client::{GetBalanceRes, ElectrumApi, GetHistoryRes, ListUnspentRes, RawHeaderNotification}; - -/// return balance of address -pub fn get_address_balance(electrum_client: &electrum_client::Client, address: &bitcoin::Address) -> GetBalanceRes { - electrum_client.script_get_balance(&address.script_pubkey()).unwrap() -} - -pub fn get_address_history(electrum_client: &electrum_client::Client, address: &bitcoin::Address) -> Vec { - electrum_client.script_get_history(&address.script_pubkey()).unwrap() -} - -pub fn get_script_list_unspent(electrum_client: &electrum_client::Client, address: &bitcoin::Address) -> Vec { - electrum_client.script_list_unspent(&address.script_pubkey()).unwrap() -} - -pub fn get_script_pubkey_list_unspent(electrum_client: &electrum_client::Client, script_pubkey: &Script) -> Vec { - electrum_client.script_list_unspent(&script_pubkey).unwrap() -} - -pub fn transaction_broadcast_raw(electrum_client: &electrum_client::Client, raw_tx: &[u8]) -> Txid { - electrum_client.transaction_broadcast_raw(raw_tx).unwrap() -} - -pub fn block_headers_subscribe_raw(electrum_client: &electrum_client::Client) -> RawHeaderNotification { - electrum_client.block_headers_subscribe_raw().unwrap() -} - -pub fn estimate_fee(electrum_client: &electrum_client::Client, number: usize) -> f64 { - electrum_client.estimate_fee(number).unwrap() -} - -pub fn batch_transaction_get_raw(electrum_client: &electrum_client::Client, txids: &[Txid]) -> Vec> { - electrum_client.batch_transaction_get_raw(txids).unwrap() -} \ No newline at end of file diff --git a/clients/standalone-rust/src/error.rs b/clients/standalone-rust/src/error.rs deleted file mode 100644 index da70245e..00000000 --- a/clients/standalone-rust/src/error.rs +++ /dev/null @@ -1,7 +0,0 @@ -use serde::{Serialize, Deserialize}; - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -pub enum CError { - /// Generic error from string error message - Generic(String) -} \ No newline at end of file diff --git a/clients/standalone-rust/src/key_derivation.rs b/clients/standalone-rust/src/key_derivation.rs deleted file mode 100644 index f6ce8dca..00000000 --- a/clients/standalone-rust/src/key_derivation.rs +++ /dev/null @@ -1,129 +0,0 @@ -use std::str::FromStr; - -use bip39::{Mnemonic, Language}; -use bitcoin::{bip32::{ExtendedPrivKey, DerivationPath, ExtendedPubKey, ChildNumber}, Address}; -use secp256k1_zkp::{PublicKey, ffi::types::AlignedType, Secp256k1, SecretKey, XOnlyPublicKey}; -use bech32::{self, FromBase32, Variant}; - -use crate::{error::CError, client_config::ClientConfig}; - -pub async fn generate_new_key(client_config: &ClientConfig, derivation_path: &str, change_index: u32, address_index:u32) -> KeyData { - - let (seed, _) = client_config.generate_or_get_seed().await; - - // we need secp256k1 context for key derivation - let mut buf: Vec = Vec::new(); - buf.resize(Secp256k1::preallocate_size(), AlignedType::zeroed()); - let secp = Secp256k1::preallocated_new(buf.as_mut_slice()).unwrap(); - - // calculate root key from seed - let root = ExtendedPrivKey::new_master(client_config.network, &seed).unwrap(); - - let fingerprint = root.fingerprint(&secp).to_string(); - - // derive child xpub - let path = DerivationPath::from_str(derivation_path).unwrap(); - let child = root.derive_priv(&secp, &path).unwrap(); - let xpub = ExtendedPubKey::from_priv(&secp, &child); - - // generate first receiving address at m/0/0 - // manually creating indexes this time - let change_index_number = ChildNumber::from_normal_idx(change_index).unwrap(); - let address_index_number = ChildNumber::from_normal_idx(address_index).unwrap(); - - let derivation_path = format!("{}/{}/{}", derivation_path, change_index, address_index ); - - let secret_key = child.derive_priv(&secp, &[change_index_number, address_index_number]).unwrap().private_key; - let public_key: secp256k1_zkp::PublicKey = xpub.derive_pub(&secp, &[change_index_number, address_index_number]).unwrap().public_key; - - KeyData { - secret_key, - public_key, - fingerprint, - derivation_path, - change_index, - address_index, - } -} - -pub struct KeyData { - pub secret_key: SecretKey, - pub public_key: PublicKey, - pub fingerprint: String, - pub derivation_path: String, - pub change_index: u32, - pub address_index: u32, -} - -pub async fn get_mnemonic_and_block_height(client_config: &ClientConfig,) -> (String, u32) { - let (seed, block_height) = client_config.generate_or_get_seed().await; - - let mnemonic = Mnemonic::from_entropy_in(Language::English,&seed).unwrap(); - - (mnemonic.to_string(), block_height) -} - -pub struct AddressData { - pub client_secret_key: SecretKey, - pub client_pubkey_share: PublicKey, - pub auth_secret_key: SecretKey, - pub auth_xonly_pubkey: XOnlyPublicKey, - pub backup_address: Address, - pub transfer_address: String, -} - -pub fn decode_transfer_address(sc_address: &str) -> Result<(u8, PublicKey, PublicKey), CError> { - let (hrp, data, variant) = bech32::decode(sc_address).unwrap(); - - if hrp != "sc" { - return Err(CError::Generic("Invalid address".to_string())); - } - - if variant != Variant::Bech32m { - return Err(CError::Generic("Invalid address".to_string())); - } - - let decoded_data = Vec::::from_base32(&data).unwrap(); - - let version = decoded_data[0]; - let user_pubkey = PublicKey::from_slice(&decoded_data[1..34]).unwrap(); - let auth_pubkey = PublicKey::from_slice(&decoded_data[34..67]).unwrap(); - - Ok((version, user_pubkey, auth_pubkey)) -} - - -pub async fn get_new_address(client_config: &ClientConfig) -> AddressData { - - let derivation_path = "m/86h/0h/0h"; - let change_index = 0; - let address_index = client_config.get_next_address_index(change_index).await; - let agg_key_data = generate_new_key(client_config, derivation_path, change_index, address_index).await; - - let client_secret_key = agg_key_data.secret_key; - let client_pubkey_share = agg_key_data.public_key; - let backup_address = Address::p2tr(&Secp256k1::new(), client_pubkey_share.x_only_public_key().0, None, client_config.network); - - client_config.insert_agg_key_data(&agg_key_data, &backup_address).await; - - let derivation_path = "m/89h/0h/0h"; - let auth_key_data = generate_new_key(client_config, derivation_path, change_index, address_index).await; - - assert!(auth_key_data.fingerprint == agg_key_data.fingerprint); - assert!(auth_key_data.address_index == agg_key_data.address_index); - assert!(auth_key_data.change_index == agg_key_data.change_index); - assert!(auth_key_data.derivation_path != agg_key_data.derivation_path); - - let transfer_address = mercury_lib::encode_sc_address(&client_pubkey_share, &auth_key_data.public_key); - - client_config.update_auth_key_data(&auth_key_data, &client_pubkey_share, &transfer_address).await; - - AddressData { - client_secret_key, - client_pubkey_share, - auth_secret_key: auth_key_data.secret_key, - auth_xonly_pubkey: auth_key_data.public_key.x_only_public_key().0, - backup_address, - transfer_address, - } -} diff --git a/clients/standalone-rust/src/main.rs b/clients/standalone-rust/src/main.rs deleted file mode 100644 index 32b4240f..00000000 --- a/clients/standalone-rust/src/main.rs +++ /dev/null @@ -1,225 +0,0 @@ -mod deposit; -mod key_derivation; -mod error; -mod electrum; -mod wallet; -mod transaction; -mod send_backup; -mod transfer_sender; -mod transfer_receiver; -mod utils; -mod client_config; -mod withdraw; - -use std::{str::FromStr, fs::File, io::Write}; - -use bitcoin::Address; -use clap::{Parser, Subcommand}; -use client_config::ClientConfig; -use electrum_client::ListUnspentRes; -use serde::{Serialize, Deserialize}; -use serde_json::json; - -#[derive(Parser)] -#[command(author, version, about, long_about = None)] -#[command(propagate_version = true)] -struct Cli { - #[command(subcommand)] - command: Commands, -} - -#[derive(Subcommand)] -enum Commands { - /// Show mnemonic - ShowMnemonic { }, - /// Get new token - NewToken { }, - /// Create Aggregated Public Key - Deposit { token_id: String, amount: u64 }, - /// Get a wallet balance - GetBalance { }, - /// Broadcast the backup transaction to the network - BroadcastBackupTransaction { statechain_id: String }, - /// Send all backup funds to the address provided - SendBackup { address: String, fee_rate: Option }, - /// Generate a transfer address to receive funds - NewTransferAddress { }, - /// Send a statechain coin to a transfer address - TransferSend { recipient_address: String, statechain_id: String }, - /// Retrieve coins from server - TransferReceive { }, - /// Withdraw funds from a statechain coin to a bitcoin address - Withdraw { recipient_address: String, statechain_id: String, fee_rate: Option }, - /// Export wallet to JSON - ExportWallet { }, -} - -#[tokio::main(flavor = "current_thread")] -async fn main() { - - let cli = Cli::parse(); - - let client_config = ClientConfig::load().await; - - match cli.command { - Commands::ShowMnemonic { } => { - let (mnemonic, _) = key_derivation::get_mnemonic_and_block_height(&client_config).await; - println!("{}", serde_json::to_string_pretty(&json!({ - "mnemonic": mnemonic, - })).unwrap()); - }, - Commands::NewToken { } => { - let token_id = deposit::get_token(&client_config).await; - println!("{}", token_id); - }, - Commands::Deposit { token_id, amount } => { - let token_id = uuid::Uuid::parse_str(&token_id).unwrap(); - let statechain_id = deposit::execute(&client_config, token_id, amount).await.unwrap(); - println!("{}", serde_json::to_string_pretty(&json!({ - "statechain_id": statechain_id, - })).unwrap()); - }, - Commands::GetBalance { } => { - - #[derive(Serialize, Deserialize, Debug)] - struct Balance { - address: String, - balance: u64, - unconfirmed_balance: i64, - } - - let (agg_addresses, backup_addresses) = wallet::get_all_addresses(&client_config).await; - - let agg_result: Vec = agg_addresses.iter().map(|address| { - let balance_res = electrum::get_address_balance(&client_config.electrum_client, &address); - Balance { - address: address.to_string(), - balance: balance_res.confirmed, - unconfirmed_balance: balance_res.unconfirmed, - } - }).collect(); - - let backup_result: Vec = backup_addresses.iter().map(|address| { - let balance_res = electrum::get_address_balance(&client_config.electrum_client, &address); - Balance { - address: address.to_string(), - balance: balance_res.confirmed, - unconfirmed_balance: balance_res.unconfirmed, - } - }).collect(); - - println!("{}", serde_json::to_string_pretty(&json!({ - "statecoins": agg_result, - "backup addresses": backup_result, - })).unwrap()); - }, - Commands::BroadcastBackupTransaction { statechain_id } => { - - let txid = deposit::broadcast_backup_tx(&client_config, &statechain_id).await; - - println!("{}", serde_json::to_string_pretty(&json!({ - "txid": txid, - })).unwrap()); - }, - Commands::SendBackup { address, fee_rate } => { - - let to_address = bitcoin::Address::from_str(&address).unwrap().require_network(client_config.network).unwrap(); - - let fee_rate = match fee_rate { - Some(fee_rate) => fee_rate, - None => { - let fee_rate_btc_per_kb = electrum::estimate_fee(&client_config.electrum_client, 1); - let fee_rate_sats_per_byte = (fee_rate_btc_per_kb * 100000.0) as u64; - fee_rate_sats_per_byte - }, - }; - - let (_, backup_addresses) = wallet::get_all_addresses(&client_config).await; - - let mut list_unspent = Vec::<(ListUnspentRes, Address)>::new(); - - for address in backup_addresses { - let address_utxos = electrum::get_script_list_unspent(&client_config.electrum_client, &address); - for utxo in address_utxos { - list_unspent.push((utxo, address.clone())); - } - } - - if list_unspent.len() == 0 { - println!("No backup funds to send"); - return; - } - - let list_utxo = send_backup::get_address_info(&client_config.pool, list_unspent).await; - - - send_backup::send_all_funds(&list_utxo, &to_address, fee_rate, &client_config.electrum_client); - }, - Commands::NewTransferAddress { } => { - let address_data = key_derivation::get_new_address(&client_config).await; - println!("{}", serde_json::to_string_pretty(&json!({ - "transfer_address": address_data.transfer_address, - })).unwrap()); - }, - Commands::TransferSend { recipient_address, statechain_id } => { - - transfer_sender::init(&client_config, &recipient_address, &statechain_id).await.unwrap(); - - println!("{}", serde_json::to_string_pretty(&json!({ - "sent": true, - })).unwrap()); - - - }, - Commands::TransferReceive { } => { - transfer_receiver::receive( &client_config).await; - }, - Commands::Withdraw { recipient_address, statechain_id, fee_rate } => { - - let to_address = bitcoin::Address::from_str(&recipient_address).unwrap().require_network(client_config.network).unwrap(); - - let fee_rate = match fee_rate { - Some(fee_rate) => fee_rate, - None => { - let fee_rate_btc_per_kb = electrum::estimate_fee(&client_config.electrum_client, 1); - let fee_rate_sats_per_byte = (fee_rate_btc_per_kb * 100000.0) as u64; - fee_rate_sats_per_byte - }, - }; - - let txid = withdraw::execute(&client_config, &statechain_id, &to_address, fee_rate).await.unwrap(); - - println!("{}", serde_json::to_string_pretty(&json!({ - "txid": txid, - })).unwrap()); - }, - Commands::ExportWallet { } => { - let (mnemonic, block_height) = key_derivation::get_mnemonic_and_block_height(&client_config).await; - - let coins = wallet::get_coins(&client_config).await; - - let state_entity_endpoint = client_config.statechain_entity.clone(); - let electrum_endpoint = client_config.electrum_server_url.clone(); - - let wallet_json = serde_json::to_string_pretty(&json!({ - "wallet": { - "name": "default", - "network": client_config.network.to_string(), - "mnemonic": mnemonic, - "version": 0.1, - "state_entity_endpoint": state_entity_endpoint, - "electrum_endpoint": electrum_endpoint, - "blockheight": block_height, - "coins": coins, - } - })).unwrap(); - println!("{}", wallet_json); - - let mut file = File::create("wallet.json").unwrap(); - file.write_all(wallet_json.as_bytes()).unwrap(); - println!("JSON written to output.json"); - }, - }; - - client_config.pool.close().await; -} diff --git a/clients/standalone-rust/src/send_backup.rs b/clients/standalone-rust/src/send_backup.rs deleted file mode 100644 index 8499fceb..00000000 --- a/clients/standalone-rust/src/send_backup.rs +++ /dev/null @@ -1,259 +0,0 @@ -use std::{collections::{HashMap, BTreeMap}, str::FromStr}; - -use bitcoin::{Address, TxOut, Transaction, OutPoint, TxIn, ScriptBuf, Witness, absolute, psbt::{Psbt, Input, PsbtSighashType, self}, bip32::{Fingerprint, DerivationPath}, Amount, sighash::{TapSighashType, SighashCache, self, TapSighash}, taproot::{TapLeafHash, self}, secp256k1, key::TapTweak}; -use electrum_client::ListUnspentRes; -use secp256k1_zkp::{SecretKey, XOnlyPublicKey, PublicKey, Secp256k1}; -use sqlx::{Sqlite, Row}; - -use crate::electrum; - -#[derive(Debug)] -pub struct AddressInfo { - pub address: Address, - pub secret_key: SecretKey, - pub xonly_public_key: XOnlyPublicKey, - pub fingerprint: String, - pub derivation_path: String, - /// Confirmation height of the transaction that created this output. - pub height: usize, - /// Txid of the transaction - pub tx_hash: bitcoin::Txid, - /// Index of the output in the transaction. - pub tx_pos: usize, - /// Value of the output. - pub value: u64, -} - -pub async fn get_address_info(pool: &sqlx::Pool, list_utxo: Vec::<(ListUnspentRes, Address)>) -> Vec:: { - - let mut list_unspent = Vec::::new(); - - for (utxo, backup_address) in list_utxo { - - let query = "SELECT client_seckey_share, client_pubkey_share, fingerprint, client_derivation_path \ - FROM signer_data \ - WHERE backup_address = $1"; - - let row = sqlx::query(query) - .bind(&backup_address.to_string()) - .fetch_one(pool) - .await - .unwrap(); - - let secret_key_bytes = row.get::, _>("client_seckey_share"); - let secret_key = SecretKey::from_slice(&secret_key_bytes).unwrap(); - - let public_key_bytes = row.get::, _>("client_pubkey_share"); - let xonly_public_key = PublicKey::from_slice(&public_key_bytes).unwrap().x_only_public_key().0; - - let fingerprint = row.get::("fingerprint"); - let derivation_path = row.get::("client_derivation_path"); - - list_unspent.push(AddressInfo { - address: backup_address, - secret_key, - xonly_public_key, - fingerprint, - derivation_path, - height: utxo.height, - tx_hash: utxo.tx_hash, - tx_pos: utxo.tx_pos, - value: utxo.value, - }); - } - - list_unspent - -} - -pub fn send_all_funds(list_utxo: &Vec::, to_address: &Address, fee_rate_sats_per_byte: u64, electrum_client: &electrum_client::Client) { - - let input_amount: u64 = list_utxo.iter().map(|s| s.value).sum(); - - let outputs = vec![ - TxOut { value: input_amount, script_pubkey: to_address.script_pubkey() }, - ]; - - let tx = create_transaction(list_utxo, &outputs).unwrap(); - - let absolute_fee: u64 = tx.vsize() as u64 * fee_rate_sats_per_byte; - - let amount_out = input_amount - absolute_fee; - - let outputs = vec![ - TxOut { value: amount_out, script_pubkey: to_address.script_pubkey() }, - ]; - - let tx = create_transaction(list_utxo, &outputs).unwrap(); - - let tx_bytes = bitcoin::consensus::encode::serialize(&tx); - let txid = electrum::transaction_broadcast_raw(&electrum_client, &tx_bytes); - - println!("--> txid sent: {}", txid); - - -} - -fn create_transaction(inputs_info: &Vec::, outputs: &Vec) -> Result> { - - let secp = Secp256k1::new(); - - let mut tx_inputs = Vec::::new(); - - let mut secret_keys = HashMap::new(); - - for input in inputs_info { - secret_keys.insert(input.xonly_public_key, input.secret_key); - } - - for input in inputs_info { - let input_utxo = OutPoint { txid: input.tx_hash, vout: input.tx_pos as u32 }; - let input = TxIn { - previous_output: input_utxo, - script_sig: ScriptBuf::new(), - sequence: bitcoin::Sequence(0xFFFFFFFF), // Ignore nSequence. - witness: Witness::default(), - }; - tx_inputs.push(input); - } - - let tx1 = Transaction { - version: 2, - lock_time: absolute::LockTime::ZERO, - input: tx_inputs, - output: outputs.clone(), - }; - let mut psbt = Psbt::from_unsigned_tx(tx1).unwrap(); - - let mut origins = BTreeMap::new(); - for input in inputs_info { - origins.insert( - input.xonly_public_key, - ( - vec![], - ( - Fingerprint::from_str(&input.fingerprint).unwrap(), - DerivationPath::from_str(&input.derivation_path).unwrap(), - ), - ), - ); - } - - let mut psbt_inputs = Vec::::new(); - - for input_info in inputs_info { - let mut input = Input { - witness_utxo: { - let script_pubkey = input_info.address.script_pubkey(); - let amount = Amount::from_sat(input_info.value); - - Some(TxOut { value: amount.to_sat(), script_pubkey }) - }, - tap_key_origins: origins.clone(), - ..Default::default() - }; - let ty = PsbtSighashType::from_str("SIGHASH_ALL").unwrap(); - input.sighash_type = Some(ty); - input.tap_internal_key = Some(input_info.xonly_public_key); - psbt_inputs.push(input); - } - - psbt.inputs = psbt_inputs; - - // SIGNER - let unsigned_tx = psbt.unsigned_tx.clone(); - - let mut input_txouts = Vec::::new(); - for input_info in inputs_info { - input_txouts.push(TxOut { value: input_info.value, script_pubkey: input_info.address.script_pubkey() }); - } - - psbt.inputs.iter_mut().enumerate().try_for_each::<_, Result<(), Box>>( - |(vout, input)| { - - let hash_ty = input - .sighash_type - .and_then(|psbt_sighash_type| psbt_sighash_type.taproot_hash_ty().ok()) - .unwrap_or(TapSighashType::All); - - let hash = SighashCache::new(&unsigned_tx).taproot_key_spend_signature_hash( - vout, - &sighash::Prevouts::All(&input_txouts.as_slice()), - hash_ty, - ).unwrap(); - - let secret_key = secret_keys.get(&input.tap_internal_key.ok_or("Internal key missing in PSBT")?).unwrap(); - - sign_psbt_taproot( - &secret_key, - input.tap_internal_key.unwrap(), - None, - input, - hash, - hash_ty, - &secp, - ); - - Ok(()) - }, - ).unwrap(); - - // FINALIZER - psbt.inputs.iter_mut().for_each(|input| { - let mut script_witness: Witness = Witness::new(); - script_witness.push(input.tap_key_sig.unwrap().to_vec()); - input.final_script_witness = Some(script_witness); - - // Clear all the data fields as per the spec. - input.partial_sigs = BTreeMap::new(); - input.sighash_type = None; - input.redeem_script = None; - input.witness_script = None; - input.bip32_derivation = BTreeMap::new(); - }); - - let tx = psbt.extract_tx(); - - //let mut prev_out_verify = Vec::::new(); - for input in inputs_info { - let script_pubkey_hex = input.address.script_pubkey().to_hex_string(); - let amount = Amount::from_sat(input.value); - - //prev_out_verify.push(TxOut { value: amount.to_sat(), script_pubkey }); - tx.verify(|_| { - Some(TxOut { - value: amount.to_sat(), - script_pubkey: ScriptBuf::from_hex(&script_pubkey_hex).unwrap() - }) - }) - .expect("failed to verify transaction"); - } - - Ok(tx) -} - -fn sign_psbt_taproot( - secret_key: &SecretKey, - pubkey: XOnlyPublicKey, - leaf_hash: Option, - psbt_input: &mut psbt::Input, - hash: TapSighash, - hash_ty: TapSighashType, - secp: &Secp256k1, -) { - let keypair = secp256k1::KeyPair::from_seckey_slice(secp, secret_key.as_ref()).unwrap(); - let keypair = match leaf_hash { - None => keypair.tap_tweak(secp, psbt_input.tap_merkle_root).to_inner(), - Some(_) => keypair, // no tweak for script spend - }; - - let sig = secp.sign_schnorr(&hash.into(), &keypair); - - let final_signature = taproot::Signature { sig, hash_ty }; - - if let Some(lh) = leaf_hash { - psbt_input.tap_script_sigs.insert((pubkey, lh), final_signature); - } else { - psbt_input.tap_key_sig = Some(final_signature); - } -} \ No newline at end of file diff --git a/clients/standalone-rust/src/transaction.rs b/clients/standalone-rust/src/transaction.rs deleted file mode 100644 index 28ca431d..00000000 --- a/clients/standalone-rust/src/transaction.rs +++ /dev/null @@ -1,373 +0,0 @@ -use std::{str::FromStr, collections::BTreeMap}; - -use bitcoin::{Txid, ScriptBuf, Transaction, absolute, TxIn, OutPoint, Witness, TxOut, psbt::{Psbt, Input, PsbtSighashType}, sighash::{TapSighashType, SighashCache, self, TapSighash}, secp256k1, taproot::{TapTweakHash, self}, hashes::{Hash, sha256}, Address}; -use mercury_lib::transaction::{ServerPublicNonceResponsePayload, PartialSignatureResponsePayload}; -use rand::Rng; -use secp256k1_zkp::{SecretKey, PublicKey, Secp256k1, schnorr::Signature, Message, musig::{MusigSessionId, MusigPubNonce, BlindingFactor, MusigSession, MusigPartialSignature, blinded_musig_pubkey_xonly_tweak_add, blinded_musig_negate_seckey, MusigAggNonce}, new_musig_nonce_pair, KeyPair}; -use serde::{Serialize, Deserialize}; - -use crate::{error::CError, client_config::ClientConfig}; - -/// The purpose of this function is to get a random locktime for the withdrawal transaction. -/// This is done to improve privacy and discourage fee sniping. -/// This function assumes that the block_height is the current block height. -fn get_locktime_for_withdrawal_transaction (block_height: u32) -> u32 { - - let mut locktime = block_height as i32; - - let mut rng = rand::thread_rng(); - let number = rng.gen_range(0..=10); - - // sometimes locktime is set a bit further back, for privacy reasons - if number == 0 { - locktime = locktime - rng.gen_range(0..=99); - } - - std::cmp::max(0, locktime) as u32 -} - -pub async fn new_backup_transaction( - client_config: &ClientConfig, - block_height: u32, - statechain_id: &str, - signed_statechain_id: &Signature, - client_seckey: &SecretKey, - client_pubkey: &PublicKey, - server_pubkey: &PublicKey, - input_txid: Txid, - input_vout: u32, - input_pubkey: &PublicKey, - input_scriptpubkey: &ScriptBuf, - input_amount: u64, - to_address: &Address, - is_withdrawal: bool) - -> Result<(Transaction, MusigPubNonce, MusigPubNonce, BlindingFactor), CError> { - - const BACKUP_TX_SIZE: u64 = 112; // virtual size one input P2TR and one output P2TR - // 163 is the real size one input P2TR and one output P2TR - - let info_config_data = crate::utils::info_config(&client_config.statechain_entity, &client_config.electrum_client, &client_config.tor_proxy).await.unwrap(); - - let initlock = info_config_data.initlock; - let interval = info_config_data.interval; - let fee_rate_sats_per_byte = info_config_data.fee_rate_sats_per_byte; - - let qt_backup_tx = client_config.count_backup_tx(statechain_id).await as u32; - - let absolute_fee: u64 = BACKUP_TX_SIZE * fee_rate_sats_per_byte; - let amount_out = input_amount - absolute_fee; - - let tx_out = TxOut { value: amount_out, script_pubkey: to_address.script_pubkey() }; - - // if qt_backup_tx == 0, it means this is the first backup transaction (Tx0) - // In this case, the block_height is equal to the current block height - // Otherwise, block_height is equal to the Tx0.lock_time + initlock - let initlock = if qt_backup_tx == 0 { initlock } else { 0 }; - - let block_height = if is_withdrawal { get_locktime_for_withdrawal_transaction(block_height) } else { (block_height + initlock) - (interval * qt_backup_tx) }; - - let (tx, client_pub_nonce, server_pub_nonce, blinding_factor) = create( - client_config, - block_height, - statechain_id, - signed_statechain_id, - client_seckey, - client_pubkey, - server_pubkey, - input_txid, - input_vout, - input_pubkey, - input_scriptpubkey, - input_amount, - tx_out).await.unwrap(); - - Ok((tx, client_pub_nonce, server_pub_nonce, blinding_factor)) - -} - -async fn create( - client_config: &ClientConfig, - block_height: u32, - statechain_id: &str, - signed_statechain_id: &Signature, - client_seckey: &SecretKey, - client_pubkey: &PublicKey, - server_pubkey: &PublicKey, - input_txid: Txid, - input_vout: u32, - input_pubkey: &PublicKey, - input_scriptpubkey: &ScriptBuf, - input_amount: u64, - output: TxOut) -> Result<(Transaction, MusigPubNonce, MusigPubNonce, BlindingFactor), Box> { - - let input_xonly_pubkey = input_pubkey.x_only_public_key().0; - - let outputs = [output].to_vec(); - - let lock_time = absolute::LockTime::from_height(block_height).expect("valid height"); - - let tx1 = Transaction { - version: 2, - lock_time, - input: vec![TxIn { - previous_output: OutPoint { txid: input_txid, vout: input_vout }, - script_sig: ScriptBuf::new(), - sequence: bitcoin::Sequence(0xFFFFFFFF), // Ignore nSequence. - witness: Witness::default(), - }], - output: outputs, - }; - let mut psbt = Psbt::from_unsigned_tx(tx1)?; - - let mut input = Input { - witness_utxo: Some(TxOut { value: input_amount, script_pubkey: input_scriptpubkey.to_owned() }), - ..Default::default() - }; - let ty = PsbtSighashType::from_str("SIGHASH_ALL")?; - input.sighash_type = Some(ty); - input.tap_internal_key = Some(input_xonly_pubkey.to_owned()); - psbt.inputs = vec![input]; - - let secp = Secp256k1::new(); - - let unsigned_tx = psbt.unsigned_tx.clone(); - - // There must not be more than one input. - // The input is the funding transaction and the output the backup address. - // If there is more than one input, the UPDATE command below will rewrite the client_sec_nonce and blinding_factor. - assert!(psbt.inputs.len() == 1); - - // for (vout, input) in psbt.inputs.iter_mut().enumerate() { - - let vout = 0; - let input = psbt.inputs.iter_mut().nth(vout).unwrap(); - - let hash_ty = input - .sighash_type - .and_then(|psbt_sighash_type| psbt_sighash_type.taproot_hash_ty().ok()) - .unwrap_or(TapSighashType::All); - - let hash = SighashCache::new(&unsigned_tx).taproot_key_spend_signature_hash( - vout, - &sighash::Prevouts::All(&[TxOut { - value: input.witness_utxo.as_ref().unwrap().value, - script_pubkey: input.witness_utxo.as_ref().unwrap().script_pubkey.clone(), - }]), - hash_ty, - ).unwrap(); - - let (sig, client_pub_nonce, server_pub_nonce, blinding_factor) = musig_sign_psbt_taproot( - client_config, - statechain_id, - signed_statechain_id, - client_seckey, - client_pubkey, - server_pubkey, - input_pubkey, - hash, - &secp, - ).await.unwrap(); - - let final_signature = taproot::Signature { sig, hash_ty }; - - input.tap_key_sig = Some(final_signature); - // } - - // FINALIZER - psbt.inputs.iter_mut().for_each(|input| { - let mut script_witness: Witness = Witness::new(); - script_witness.push(input.tap_key_sig.unwrap().to_vec()); - input.final_script_witness = Some(script_witness); - - // Clear all the data fields as per the spec. - input.partial_sigs = BTreeMap::new(); - input.sighash_type = None; - input.redeem_script = None; - input.witness_script = None; - input.bip32_derivation = BTreeMap::new(); - }); - - let tx = psbt.extract_tx(); - - tx.verify(|_| { - Some(TxOut { - value: input_amount, - script_pubkey: input_scriptpubkey.to_owned(), - }) - }) - .expect("failed to verify transaction"); - - - Ok((tx, client_pub_nonce, server_pub_nonce, blinding_factor)) -} - - -#[derive(Serialize, Deserialize)] -pub struct SignFirstRequestPayload<'r> { - statechain_id: &'r str, - r2_commitment: &'r str, - blind_commitment: &'r str, - signed_statechain_id: &'r str, -} - -async fn musig_sign_psbt_taproot( - client_config: &ClientConfig, - statechain_id: &str, - signed_statechain_id: &Signature, - client_seckey: &SecretKey, - client_pubkey: &PublicKey, - server_pubkey: &PublicKey, - input_pubkey: &PublicKey, - hash: TapSighash, - secp: &Secp256k1, -) -> Result<(Signature, MusigPubNonce, MusigPubNonce, BlindingFactor), CError> { - let msg: Message = hash.into(); - - let client_session_id = MusigSessionId::new(&mut rand::thread_rng()); - - let (client_sec_nonce, client_pub_nonce) = new_musig_nonce_pair(&secp, client_session_id, None, Some(client_seckey.to_owned()), client_pubkey.to_owned(), None, None).unwrap(); - - let r2_commitment = sha256::Hash::hash(&client_pub_nonce.serialize()); - - let blinding_factor = BlindingFactor::new(&mut rand::thread_rng()); - let blind_commitment = sha256::Hash::hash(blinding_factor.as_bytes()); - - let endpoint = client_config.statechain_entity.clone(); - let path = "sign/first"; - - let tor_proxy = client_config.tor_proxy.clone(); - - let mut client: reqwest::Client = reqwest::Client::new(); - if tor_proxy != "".to_string() { - let tor_proxy = reqwest::Proxy::all(tor_proxy).unwrap(); - client = reqwest::Client::builder().proxy(tor_proxy).build().unwrap(); - } - let request = client.post(&format!("{}/{}", endpoint, path)); - - let sign_first_request_payload = SignFirstRequestPayload { - statechain_id, - r2_commitment: &r2_commitment.to_string(), - blind_commitment: &blind_commitment.to_string(), - signed_statechain_id: &signed_statechain_id.to_string(), - }; - - let value = match request.json(&sign_first_request_payload).send().await { - Ok(response) => { - let text = response.text().await.unwrap(); - text - }, - Err(err) => { - return Err(CError::Generic(err.to_string())); - }, - }; - - let response: ServerPublicNonceResponsePayload = serde_json::from_str(value.as_str()).expect(&format!("failed to parse: {}", value.as_str())); - - let mut server_pubnonce_hex = response.server_pubnonce.to_string(); - - if server_pubnonce_hex.starts_with("0x") { - server_pubnonce_hex = server_pubnonce_hex[2..].to_string(); - } - - let server_pub_nonce_bytes = hex::decode(server_pubnonce_hex.clone()).unwrap(); - - let server_pub_nonce = MusigPubNonce::from_slice(server_pub_nonce_bytes.as_slice()).unwrap(); - - let aggregate_pubkey = client_pubkey.combine(&server_pubkey).unwrap(); - - if aggregate_pubkey != input_pubkey.to_owned() { - return Err(CError::Generic("Input public key is different than the combination of client and server public keys.".to_string())); - } - - let tap_tweak = TapTweakHash::from_key_and_tweak(aggregate_pubkey.x_only_public_key().0, None); - let tap_tweak_bytes = tap_tweak.as_byte_array(); - - // tranform tweak: Scalar to SecretKey - let tweak = SecretKey::from_slice(tap_tweak_bytes).unwrap(); - - let (parity_acc, output_pubkey, out_tweak32) = blinded_musig_pubkey_xonly_tweak_add(&secp, &aggregate_pubkey, tweak); - - let aggnonce = MusigAggNonce::new(&secp, &[client_pub_nonce, server_pub_nonce]); - - let session = MusigSession::new_blinded_without_key_agg_cache( - &secp, - &output_pubkey, - aggnonce, - msg, - None, - &blinding_factor, - out_tweak32 - ); - - let negate_seckey = blinded_musig_negate_seckey( - &secp, - &output_pubkey, - parity_acc, - ); - - let client_keypair = KeyPair::from_secret_key(&secp, &client_seckey); - - let client_partial_sig = session.blinded_partial_sign_without_keyaggcoeff(&secp, client_sec_nonce, &client_keypair, negate_seckey).unwrap(); - - assert!(session.blinded_musig_partial_sig_verify(&secp, &client_partial_sig, &client_pub_nonce, &client_pubkey, &output_pubkey, parity_acc)); - - session.remove_fin_nonce_from_session(); - - let negate_seckey = match negate_seckey { - true => 1, - false => 0, - }; - - let blinded_session = session.remove_fin_nonce_from_session(); - - let payload = mercury_lib::transaction::PartialSignatureRequestPayload { - statechain_id: statechain_id.to_string(), - negate_seckey, - session: hex::encode(blinded_session.serialize()).to_string(), - signed_statechain_id: signed_statechain_id.to_string(), - server_pub_nonce: server_pubnonce_hex, - }; - - let path = "sign/second"; - - let tor_proxy = client_config.tor_proxy.clone(); - - let mut client: reqwest::Client = reqwest::Client::new(); - if tor_proxy != "".to_string() { - let tor_proxy = reqwest::Proxy::all(tor_proxy).unwrap(); - client = reqwest::Client::builder().proxy(tor_proxy).build().unwrap(); - } - let request = client.post(&format!("{}/{}", endpoint, path)); - - let value = match request.json(&payload).send().await { - Ok(response) => { - let text = response.text().await.unwrap(); - text - }, - Err(err) => { - return Err(CError::Generic(err.to_string())); - }, - }; - - let response: PartialSignatureResponsePayload = serde_json::from_str(value.as_str()).expect(&format!("failed to parse: {}", value.as_str())); - - let mut server_partial_sig_hex = response.partial_sig.to_string(); - - if server_partial_sig_hex.starts_with("0x") { - server_partial_sig_hex = server_partial_sig_hex[2..].to_string(); - } - - let server_partial_sig_bytes = hex::decode(server_partial_sig_hex).unwrap(); - - let server_partial_sig = MusigPartialSignature::from_slice(server_partial_sig_bytes.as_slice()).unwrap(); - - assert!(session.blinded_musig_partial_sig_verify(&secp, &server_partial_sig, &server_pub_nonce, &server_pubkey, &output_pubkey, parity_acc)); - - let sig = session.partial_sig_agg(&[client_partial_sig, server_partial_sig]); - - let x_only_key_tweaked = output_pubkey.x_only_public_key().0; - - assert!(secp.verify_schnorr(&sig, &msg, &x_only_key_tweaked).is_ok()); - - Ok((sig, client_pub_nonce, server_pub_nonce, blinding_factor)) -} diff --git a/clients/standalone-rust/src/transfer_receiver.rs b/clients/standalone-rust/src/transfer_receiver.rs deleted file mode 100644 index f2e6cf75..00000000 --- a/clients/standalone-rust/src/transfer_receiver.rs +++ /dev/null @@ -1,500 +0,0 @@ -use std::str::FromStr; - -use bitcoin::{Transaction, Network, Address, Txid, sighash::{SighashCache, TapSighashType, self}, TxOut, taproot::TapTweakHash, hashes::{Hash, sha256}, secp256k1}; -use mercury_lib::transfer::receiver::{GetMsgAddrResponsePayload, StatechainInfoResponsePayload, StatechainInfo}; -use secp256k1_zkp::{SecretKey, PublicKey, Secp256k1, schnorr::Signature, XOnlyPublicKey, Message, musig::{MusigAggNonce, MusigSession, blinded_musig_pubkey_xonly_tweak_add}, Scalar}; -use serde::{Serialize, Deserialize}; -use serde_json::Value; - -use crate::{error::CError, electrum, utils::InfoConfig, client_config::ClientConfig}; - -async fn get_msg_addr(auth_pubkey: &secp256k1_zkp::PublicKey, statechain_entity_url: &str, client_config: &ClientConfig) -> Result, CError> { - let endpoint = statechain_entity_url; - let path = format!("transfer/get_msg_addr/{}", auth_pubkey.to_string()); - - let tor_proxy = client_config.tor_proxy.clone(); - - let mut client: reqwest::Client = reqwest::Client::new(); - if tor_proxy != "".to_string() { - let tor_proxy = reqwest::Proxy::all(tor_proxy).unwrap(); - client = reqwest::Client::builder().proxy(tor_proxy).build().unwrap(); - } - let request = client.get(&format!("{}/{}", endpoint, path)); - - let value = match request.send().await { - Ok(response) => { - let text = response.text().await.unwrap(); - text - }, - Err(err) => { - return Err(CError::Generic(err.to_string())); - }, - }; - - let response: GetMsgAddrResponsePayload = serde_json::from_str(value.as_str()).expect(&format!("failed to parse: {}", value.as_str())); - - Ok(response.list_enc_transfer_msg) -} - -fn validate_t1pub(t1: &[u8; 32], x1_pub: &PublicKey, sender_public_key: &PublicKey) -> bool { - - let secret_t1 = SecretKey::from_slice(t1).unwrap(); - let public_t1 = secret_t1.public_key(&Secp256k1::new()); - - let result_pubkey = sender_public_key.combine(&x1_pub).unwrap(); - - result_pubkey == public_t1 -} - -fn calculate_t2(transfer_msg: &mercury_lib::transfer::TransferMsg1, client_seckey_share: &SecretKey,) -> SecretKey { - - let t1 = Scalar::from_be_bytes(transfer_msg.t1).unwrap(); - - let negated_seckey = client_seckey_share.negate(); - - let t2 = negated_seckey.add_tweak(&t1).unwrap(); - - t2 -} - -/// step 3. Owner 2 verifies that the latest backup transaction pays to their key O2 and that the input (Tx0) is unspent. -async fn verify_latest_backup_tx_pays_to_user_pubkey(transfer_msg: &mercury_lib::transfer::TransferMsg1, client_pubkey_share: &PublicKey, network: Network,) -> bool { - - let last_tx = transfer_msg.backup_transactions.last().unwrap(); - - let backup_tx = last_tx.deserialize(); - - let output = &backup_tx.tx.output[0]; - - let aggregate_address = Address::p2tr(&Secp256k1::new(), client_pubkey_share.x_only_public_key().0, None, network); - - output.script_pubkey == aggregate_address.script_pubkey() -} - -fn get_tx_hash(transaction: &Transaction, electrum_client: &electrum_client::Client) -> Message { - - let witness = transaction.input[0].witness.clone(); - - let witness_data = witness.nth(0).unwrap(); - - let vout = transaction.input[0].previous_output.vout as usize; - - let txid = transaction.input[0].previous_output.txid.to_string(); - - let res = electrum::batch_transaction_get_raw(electrum_client, &[Txid::from_str(&txid).unwrap()]); - - let funding_tx_bytes = res[0].clone(); - - let funding_tx: Transaction = bitcoin::consensus::encode::deserialize(&funding_tx_bytes).unwrap(); - - let funding_tx_output = funding_tx.output[vout].clone(); - - let sighash_type = TapSighashType::from_consensus_u8(witness_data.last().unwrap().to_owned()).unwrap(); - - let hash = SighashCache::new(transaction).taproot_key_spend_signature_hash( - 0, - &sighash::Prevouts::All(&[TxOut { - value: funding_tx_output.value, - script_pubkey: funding_tx_output.script_pubkey.clone(), - }]), - sighash_type, - ).unwrap(); - - let msg: Message = hash.into(); - - msg -} - -/// step 4a. Verifiy if the signature is valid. -async fn verify_transaction_signature(transaction: &Transaction, fee_rate_sats_per_byte: u64, client_config: &ClientConfig) -> Result<(), CError> { - - let witness = transaction.input[0].witness.clone(); - - let witness_data = witness.nth(0).unwrap(); - - // the last element is the hash type - let signature_data = witness_data.split_last().unwrap().1; - - let signature = Signature::from_slice(signature_data).unwrap(); - - let txid = transaction.input[0].previous_output.txid.to_string(); - - let res = electrum::batch_transaction_get_raw(&client_config.electrum_client, &[Txid::from_str(&txid).unwrap()]); - - let funding_tx_bytes = res[0].clone(); - - let funding_tx: Transaction = bitcoin::consensus::encode::deserialize(&funding_tx_bytes).unwrap(); - - let vout = transaction.input[0].previous_output.vout as usize; - - let funding_tx_output = funding_tx.output[vout].clone(); - - let res = electrum::get_script_pubkey_list_unspent(&client_config.electrum_client, &funding_tx_output.script_pubkey.as_script()); - - if res.len() == 0 { - return Err(CError::Generic("The funding UTXO is spent".to_string())); - } - - let xonly_pubkey = XOnlyPublicKey::from_slice(funding_tx_output.script_pubkey[2..].as_bytes()).unwrap(); - - let sighash_type = TapSighashType::from_consensus_u8(witness_data.last().unwrap().to_owned()).unwrap(); - - let hash = SighashCache::new(transaction).taproot_key_spend_signature_hash( - 0, - &sighash::Prevouts::All(&[TxOut { - value: funding_tx_output.value, - script_pubkey: funding_tx_output.script_pubkey.clone(), - }]), - sighash_type, - ).unwrap(); - - let msg: Message = hash.into(); - - let fee = funding_tx_output.value - transaction.output[0].value; - let fee_rate = fee / transaction.vsize() as u64; - - if (fee_rate as i64 + client_config.fee_rate_tolerance as i64) < fee_rate_sats_per_byte as i64 { - return Err(CError::Generic("Fee rate too low".to_string())); - } - - if (fee_rate as i64 - client_config.fee_rate_tolerance as i64) > fee_rate_sats_per_byte as i64 { - return Err(CError::Generic("Fee rate too high".to_string())); - } - - if !Secp256k1::new().verify_schnorr(&signature, &msg, &xonly_pubkey).is_ok() { - return Err(CError::Generic("Invalid signature".to_string())); - } - - Ok(()) - -} - -async fn get_funding_transaction_info(transaction: &Transaction, electrum_client: &electrum_client::Client) -> (XOnlyPublicKey, Txid, usize, u64) { - - let txid = transaction.input[0].previous_output.txid; - - let res = electrum::batch_transaction_get_raw(&electrum_client, &[txid]); - - let funding_tx_bytes = res[0].clone(); - - let funding_tx: Transaction = bitcoin::consensus::encode::deserialize(&funding_tx_bytes).unwrap(); - - let vout = transaction.input[0].previous_output.vout as usize; - - let funding_tx_output = funding_tx.output[vout].clone(); - - let xonly_pubkey = XOnlyPublicKey::from_slice(funding_tx_output.script_pubkey[2..].as_bytes()).unwrap(); - - (xonly_pubkey, txid, vout, funding_tx_output.value) -} - -async fn verify_blinded_musig_scheme(backup_tx: &mercury_lib::transfer::ReceiverBackupTransaction, statechain_info: &StatechainInfo, electrum_client: &electrum_client::Client) -> Result<(), CError> { - - let client_public_nonce = backup_tx.client_public_nonce.clone(); - let server_public_nonce = backup_tx.server_public_nonce.clone(); - let client_public_key = backup_tx.client_public_key.clone(); - let server_public_key = backup_tx.server_public_key.clone(); - let blinding_factor = &backup_tx.blinding_factor; - - let blind_commitment = sha256::Hash::hash(blinding_factor.as_bytes()); - let r2_commitment = sha256::Hash::hash(&client_public_nonce.serialize()); - - if statechain_info.blind_commitment != blind_commitment.to_string() { - return Err(CError::Generic("blind_commitment is not correct".to_string())); - } - - if statechain_info.r2_commitment != r2_commitment.to_string() { - return Err(CError::Generic("r2_commitment is not correct".to_string())); - } - - let secp = Secp256k1::new(); - - // TODO: this code is repeated in client/src/transaction/mod.rs. Move it to a common place. - let aggregate_pubkey = client_public_key.combine(&server_public_key).unwrap(); - - let tap_tweak = TapTweakHash::from_key_and_tweak(aggregate_pubkey.x_only_public_key().0, None); - let tap_tweak_bytes = tap_tweak.as_byte_array(); - - let tweak = SecretKey::from_slice(tap_tweak_bytes).unwrap(); - - let (_, output_pubkey, out_tweak32) = blinded_musig_pubkey_xonly_tweak_add(&secp, &aggregate_pubkey, tweak); - - let aggnonce = MusigAggNonce::new(&secp, &[client_public_nonce, server_public_nonce]); - - let msg = get_tx_hash(&backup_tx.tx, electrum_client); - - let session = MusigSession::new_blinded_without_key_agg_cache( - &secp, - &output_pubkey, - aggnonce, - msg, - None, - &blinding_factor, - out_tweak32 - ); - // END repeated code - - let challenge = session.get_challenge_from_session(); - let challenge = hex::encode(challenge); - - if statechain_info.challenge != challenge { - return Err(CError::Generic("challenge is not correct".to_string())); - } - - Ok(()) - -} - -async fn get_statechain_info(statechain_id: &str, statechain_entity_url: &str, client_config: &ClientConfig) -> Result { - - let endpoint = statechain_entity_url; - let path = format!("info/statechain/{}", statechain_id.to_string()); - - println!("statechain_id: {}", statechain_id.to_string()); - println!("path: {}", path); - - let tor_proxy = client_config.tor_proxy.clone(); - - let mut client: reqwest::Client = reqwest::Client::new(); - if tor_proxy != "".to_string() { - let tor_proxy = reqwest::Proxy::all(tor_proxy).unwrap(); - client = reqwest::Client::builder().proxy(tor_proxy).build().unwrap(); - } - let request = client.get(&format!("{}/{}", endpoint, path)); - - let value = match request.send().await { - Ok(response) => { - let text = response.text().await.unwrap(); - text - }, - Err(err) => { - return Err(CError::Generic(err.to_string())); - }, - }; - - let response: StatechainInfoResponsePayload = serde_json::from_str(value.as_str()).expect(&format!("failed to parse: {}", value.as_str())); - - Ok(response) -} - -fn verify_transfer_signature(new_user_pubkey: &PublicKey, input_txid: &Txid, input_vout: u32, signature: &Signature, sender_public_key: &XOnlyPublicKey) -> bool { - - let secp = Secp256k1::new(); - - let mut data_to_verify = Vec::::new(); - data_to_verify.extend_from_slice(&input_txid[..]); - data_to_verify.extend_from_slice(&input_vout.to_le_bytes()); - data_to_verify.extend_from_slice(&new_user_pubkey.serialize()[..]); - - let msg = Message::from_hashed_data::(&data_to_verify); - - secp.verify_schnorr(signature, &msg, &sender_public_key).is_ok() -} - -async fn process_encrypted_message( - client_config: &ClientConfig, - client_auth_key: &SecretKey, - client_seckey_share: &SecretKey, - client_pubkey_share: &PublicKey, - enc_messages: &Vec, - info_config: &InfoConfig) -> Result<(), CError> { - - for enc_message in enc_messages { - - let decoded_enc_message = hex::decode(enc_message).unwrap(); - - let decrypted_msg = ecies::decrypt(client_auth_key.secret_bytes().as_slice(), decoded_enc_message.as_slice()).unwrap(); - - let decrypted_msg_str = String::from_utf8(decrypted_msg).unwrap(); - - let transfer_msg: mercury_lib::transfer::TransferMsg1 = serde_json::from_str(decrypted_msg_str.as_str()).unwrap(); - - let statechain_info = get_statechain_info(&transfer_msg.statechain_id, &client_config.statechain_entity, client_config).await.unwrap(); - - let backup_transaction = transfer_msg.backup_transactions.first().unwrap(); - - let backup_transaction = backup_transaction.deserialize(); - - let (funding_xonly_pubkey, txid, vout, amount) = get_funding_transaction_info(&backup_transaction.tx, &client_config.electrum_client).await; - - let sender_public_key = PublicKey::from_str(&transfer_msg.user_public_key).unwrap(); - - let transfer_signature = Signature::from_str(&transfer_msg.transfer_signature).unwrap(); - - let is_transfer_signature_valid = verify_transfer_signature( - &client_pubkey_share, - &txid, - vout as u32, - &transfer_signature, - &sender_public_key.x_only_public_key().0 - ); - - if !is_transfer_signature_valid { - println!("Invalid transfer signature"); - continue; - } - - // validate tranfer.pub_key + client_pub_key = funding_xonly_pubkey - let enclave_public_key = PublicKey::from_str(&statechain_info.enclave_public_key).unwrap(); - - let transfer_aggregate_pubkey = sender_public_key.combine(&enclave_public_key).unwrap(); - let transfer_aggregate_xonly_pubkey = transfer_aggregate_pubkey.x_only_public_key().0; - - let secp = Secp256k1::new(); - - let transfer_aggregate_address = Address::p2tr(&secp, transfer_aggregate_xonly_pubkey, None, client_config.network); - - let transfer_aggregate_xonly_pubkey = XOnlyPublicKey::from_slice(transfer_aggregate_address.script_pubkey()[2..].as_bytes()).unwrap(); - - if transfer_aggregate_xonly_pubkey != funding_xonly_pubkey { - println!("Aggregated public keys do not match. This statecoin may have been updated."); - continue; - } - - if !verify_latest_backup_tx_pays_to_user_pubkey(&transfer_msg, client_pubkey_share, client_config.network).await { - return Err(CError::Generic("Latest backup tx does not pay to user pubkey".to_string())); - } - - if statechain_info.num_sigs != transfer_msg.backup_transactions.len() as u32 { - return Err(CError::Generic("num_sigs is not correct".to_string())); - } - - let mut previous_lock_time: Option = None; - - for (index, backup_tx) in transfer_msg.backup_transactions.iter().enumerate() { - - let statechain_info = statechain_info.statechain_info.get(index).unwrap(); - - let backup_tx = backup_tx.deserialize(); - let is_signature_valid = verify_transaction_signature(&backup_tx.tx, info_config.fee_rate_sats_per_byte, client_config).await; - if is_signature_valid.is_err() { - return Err(is_signature_valid.err().unwrap()); - } - - let is_blinded_musig_scheme_valid = verify_blinded_musig_scheme(&backup_tx, statechain_info, &client_config.electrum_client).await; - if is_blinded_musig_scheme_valid.is_err() { - return Err(is_blinded_musig_scheme_valid.err().unwrap()); - } - - if previous_lock_time.is_some() { - let prev_lock_time = previous_lock_time.unwrap(); - let current_lock_time = backup_tx.tx.lock_time.to_consensus_u32(); - if (prev_lock_time - current_lock_time) as i32 != info_config.interval as i32 { - return Err(CError::Generic("interval is not correct".to_string())); - } - } - - previous_lock_time = Some(backup_tx.tx.lock_time.to_consensus_u32()); - } - - let x1_pub = PublicKey::from_str(&statechain_info.x1_pub).unwrap(); - - if !validate_t1pub(&transfer_msg.t1, &x1_pub, &sender_public_key) { - return Err(CError::Generic("Invalid t1".to_string())); - } - - let t2 = calculate_t2(&transfer_msg, &client_seckey_share); - - let t2_hex = hex::encode(t2.secret_bytes()); - - - let client_auth_keypair = secp256k1::KeyPair::from_seckey_slice(&secp, client_auth_key.as_ref()).unwrap(); - let msg = Message::from_hashed_data::(t2_hex.as_bytes()); - let auth_sig = secp.sign_schnorr(&msg, &client_auth_keypair); - - let transfer_receiver_request_payload = mercury_lib::transfer::receiver::TransferReceiverRequestPayload { - statechain_id: transfer_msg.statechain_id.clone(), - batch_data: None, - t2: t2_hex, - auth_sig: auth_sig.to_string(), - }; - - let endpoint = client_config.statechain_entity.clone(); - let path = "transfer/receiver"; - - let tor_proxy = client_config.tor_proxy.clone(); - - let mut client: reqwest::Client = reqwest::Client::new(); - if tor_proxy != "".to_string() { - let tor_proxy = reqwest::Proxy::all(tor_proxy).unwrap(); - client = reqwest::Client::builder().proxy(tor_proxy).build().unwrap(); - } - let request = client.post(&format!("{}/{}", endpoint, path)); - - let value = match request.json(&transfer_receiver_request_payload).send().await { - Ok(response) => { - let text = response.text().await.unwrap(); - text - }, - Err(err) => { - return Err(CError::Generic(err.to_string())); - }, - }; - - let response: Value = serde_json::from_str(value.as_str()).expect(&format!("failed to parse: {}", value.as_str())); - - let server_public_key_hex = response.get("server_pubkey").unwrap().as_str().unwrap(); - - let server_pubkey_share = PublicKey::from_str(server_public_key_hex).unwrap(); - - let aggregate_pubkey = client_pubkey_share.combine(&server_pubkey_share).unwrap(); - - let aggregated_xonly_pubkey = aggregate_pubkey.x_only_public_key().0; - - let aggregate_address = Address::p2tr(&secp, aggregated_xonly_pubkey, None, client_config.network); - - let xonly_pubkey = XOnlyPublicKey::from_slice(aggregate_address.script_pubkey()[2..].as_bytes()).unwrap(); - - if funding_xonly_pubkey != xonly_pubkey { - return Err(CError::Generic("Aggregated public key is not correct".to_string())); - } - - let statechain_id = transfer_msg.statechain_id.clone(); - - println!("statechain_id: {}", statechain_id); - - let p2tr_agg_address = Address::p2tr(&secp, aggregated_xonly_pubkey, None, client_config.network); - - let msg = Message::from_hashed_data::(statechain_id.to_string().as_bytes()); - let signed_statechain_id = secp.sign_schnorr(&msg, &client_auth_keypair); - - let vec_backup_transactions: Vec = transfer_msg.backup_transactions.iter().map(|x| x.deserialize()).collect(); - - client_config.insert_or_update_new_statechain( - &statechain_id, - amount as u32, - &server_pubkey_share, - &aggregate_pubkey, - &p2tr_agg_address, - client_pubkey_share, - &signed_statechain_id, - &txid, - vout as u32, - previous_lock_time.unwrap(), - &vec_backup_transactions).await; - } - - Ok(()) -} - -pub async fn receive(client_config: &ClientConfig) { - - let info_config = crate::utils::info_config(&client_config.statechain_entity, &client_config.electrum_client, &client_config.tor_proxy).await.unwrap(); - - let client_keys = client_config.get_all_auth_pubkey().await; - - for client_key in client_keys { - let enc_messages = get_msg_addr(&client_key.1, &client_config.statechain_entity, client_config).await.unwrap(); - if enc_messages.len() == 0 { - continue; - } - process_encrypted_message( - client_config, - &client_key.0, - &client_key.2, - &client_key.3, - &enc_messages, - &info_config, - ).await.unwrap(); - } -} \ No newline at end of file diff --git a/clients/standalone-rust/src/transfer_sender.rs b/clients/standalone-rust/src/transfer_sender.rs deleted file mode 100644 index 2fc775d6..00000000 --- a/clients/standalone-rust/src/transfer_sender.rs +++ /dev/null @@ -1,249 +0,0 @@ -use bitcoin::{Transaction, Address, secp256k1, hashes::sha256, Txid}; -use mercury_lib::transfer::sender::{TransferSenderRequestPayload, TransferSenderResponsePayload, TransferUpdateMsgRequestPayload}; -use secp256k1_zkp::{PublicKey, SecretKey, Secp256k1, Message, musig::{MusigPubNonce, BlindingFactor}, schnorr::Signature, Scalar}; -use serde::{Deserialize, Serialize}; -use serde_json::json; - -use crate::{error::CError, key_derivation, client_config::ClientConfig}; - -// Step 7. Owner 1 then concatinates the Tx0 outpoint with the Owner 2 public key (O2) and signs it with their key o1 to generate SC_sig_1. -fn create_transfer_signature(new_user_pubkey: PublicKey, input_txid: &Txid, input_vout: u32, client_seckey: &SecretKey) -> Signature { - - let secp = Secp256k1::new(); - let keypair = secp256k1::KeyPair::from_seckey_slice(&secp, client_seckey.as_ref()).unwrap(); - - let mut data_to_sign = Vec::::new(); - data_to_sign.extend_from_slice(&input_txid[..]); - data_to_sign.extend_from_slice(&input_vout.to_le_bytes()); - data_to_sign.extend_from_slice(&new_user_pubkey.serialize()[..]); - - let msg = Message::from_hashed_data::(&data_to_sign); - let signature = secp.sign_schnorr(&msg, &keypair); - - signature -} - -async fn get_new_x1(client_config: &ClientConfig, statechain_id: &str, signed_statechain_id: &Signature, new_auth_pubkey: &PublicKey) -> Result, CError> { - - let endpoint = client_config.statechain_entity.clone(); - let path = "transfer/sender"; - - let tor_proxy = client_config.tor_proxy.clone(); - - let mut client: reqwest::Client = reqwest::Client::new(); - if tor_proxy != "".to_string() { - let tor_proxy = reqwest::Proxy::all(tor_proxy).unwrap(); - client = reqwest::Client::builder().proxy(tor_proxy).build().unwrap(); - } - let request = client.post(&format!("{}/{}", endpoint, path)); - - let transfer_sender_request_payload = TransferSenderRequestPayload { - statechain_id: statechain_id.to_string(), - auth_sig: signed_statechain_id.to_string(), - new_user_auth_key: new_auth_pubkey.to_string(), - batch_id: None, - }; - - let value = match request.json(&transfer_sender_request_payload).send().await { - Ok(response) => { - - let status = response.status(); - let text = response.text().await.unwrap_or("Unexpected error".to_string()); - - if status.is_success() { - text - } else { - return Err(CError::Generic(format!("status: {}, error: {}", status, text))); - } - }, - Err(err) => { - return Err(CError::Generic(format!("status: {}, error: {}", err.status().unwrap(),err.to_string()))); - }, - }; - - let response: TransferSenderResponsePayload = serde_json::from_str(value.as_str()).expect(&format!("failed to parse: {}", value.as_str())); - - let x1 = hex::decode(response.x1).unwrap(); - - Ok(x1) -} - -pub async fn save_new_backup_transaction(client_config: &ClientConfig, backup_transaction: &mercury_lib::transfer::SenderBackupTransaction) { - - let tx_n = backup_transaction.tx_n; - let tx_bytes = bitcoin::consensus::encode::serialize(&backup_transaction.tx); - let client_pub_nonce: [u8; 66] = backup_transaction.client_public_nonce.clone().try_into().unwrap(); - let server_pub_nonce: [u8; 66] = backup_transaction.server_public_nonce.clone().try_into().unwrap(); - let client_pubkey = &backup_transaction.client_public_key; - let server_pubkey = &backup_transaction.server_public_key; - let blinding_factor: [u8; 32] = backup_transaction.blinding_factor.clone().try_into().unwrap(); - let statechain_id = &backup_transaction.statechain_id; - let recipient_address = &backup_transaction.recipient_address; - - client_config.insert_transaction(tx_n, &tx_bytes, &client_pub_nonce, &server_pub_nonce, client_pubkey, server_pubkey, &blinding_factor, &statechain_id, recipient_address).await.unwrap(); -} - -pub async fn init(client_config: &ClientConfig, recipient_address: &str, statechain_id: &str) -> Result<(), CError>{ - - let (_, recipient_user_pubkey, recipient_auth_pubkey) = key_derivation::decode_transfer_address(recipient_address).unwrap(); - - let mut backup_transactions = client_config.get_backup_transactions(&statechain_id).await; - - if backup_transactions.len() == 0 { - return Err(CError::Generic("No backup transactions found".to_string())); - } - - backup_transactions.sort_by(|a, b| a.tx_n.cmp(&b.tx_n)); - - let tx1 = &backup_transactions[0]; - - let (client_seckey, - client_public_key, - server_public_key, - input_txid, - input_vout, - transaction, - client_pub_nonce, - server_pub_nonce, - blinding_factor, - signed_statechain_id) = - create_backup_tx_to_receiver(&client_config, &tx1.tx, recipient_user_pubkey, &statechain_id).await; - - let x1 = get_new_x1(client_config, &statechain_id, &signed_statechain_id, &recipient_auth_pubkey).await; - - let new_tx_n = backup_transactions.last().unwrap().tx_n + 1; - - let new_bakup_tx = mercury_lib::transfer::SenderBackupTransaction { - statechain_id: statechain_id.to_string(), - tx_n: new_tx_n, - tx: transaction, - client_public_nonce: client_pub_nonce.serialize().to_vec(), - server_public_nonce: server_pub_nonce.serialize().to_vec(), - client_public_key, - server_public_key, - blinding_factor: blinding_factor.as_bytes().to_vec(), - recipient_address: recipient_address.to_string(), - }; - - backup_transactions.push(new_bakup_tx.clone()); - - let mut serialized_backup_transactions = Vec::::new(); - - for backup_tx in backup_transactions { - serialized_backup_transactions.push(backup_tx.serialize()); - } - - let transfer_signature = create_transfer_signature(recipient_user_pubkey, &input_txid, input_vout, &client_seckey); - - let x1: [u8; 32] = x1?.try_into().unwrap(); - let x1 = Scalar::from_be_bytes(x1).unwrap(); - - let t1 = client_seckey.add_tweak(&x1).unwrap(); - - let transfer_msg = mercury_lib::transfer::TransferMsg1 { - statechain_id: statechain_id.to_string(), - transfer_signature: transfer_signature.to_string(), - backup_transactions: serialized_backup_transactions, - t1: t1.secret_bytes(), - user_public_key: client_public_key.to_string(), - }; - - let transfer_msg_json = json!(&transfer_msg); - - let transfer_msg_json_str = serde_json::to_string_pretty(&transfer_msg_json).unwrap(); - - let msg = transfer_msg_json_str.as_bytes(); - - let serialized_new_auth_pubkey = &recipient_auth_pubkey.serialize(); - let encrypted_msg = ecies::encrypt(serialized_new_auth_pubkey, msg).unwrap(); - - let encrypted_msg_string = hex::encode(&encrypted_msg); - - let transfer_update_msg_request_payload = TransferUpdateMsgRequestPayload { - statechain_id: statechain_id.to_string(), - auth_sig: signed_statechain_id.to_string(), - new_user_auth_key: recipient_auth_pubkey.to_string(), - enc_transfer_msg: encrypted_msg_string.clone(), - }; - - let endpoint = client_config.statechain_entity.clone(); - let path = "transfer/update_msg"; - - let tor_proxy = client_config.tor_proxy.clone(); - - let mut client: reqwest::Client = reqwest::Client::new(); - if tor_proxy != "".to_string() { - let tor_proxy = reqwest::Proxy::all(tor_proxy).unwrap(); - client = reqwest::Client::builder().proxy(tor_proxy).build().unwrap(); - } - let request = client.post(&format!("{}/{}", endpoint, path)); - - match request.json(&transfer_update_msg_request_payload).send().await { - Ok(response) => { - let status = response.status(); - if !status.is_success() { - return Err(CError::Generic("Failed to update transfer message".to_string())); - } - }, - Err(err) => - return Err(CError::Generic(err.to_string())) - , - }; - - // Now it is sucessfully sent to the server, we can save it to the database - save_new_backup_transaction(client_config, &new_bakup_tx).await; - - client_config.update_coin_status_and_tx_withdraw(statechain_id, "SPENT", None).await; - - Ok(()) -} - -pub async fn create_backup_tx_to_receiver(client_config: &ClientConfig, tx1: &Transaction, new_user_pubkey: PublicKey, statechain_id: &str) - -> (SecretKey, PublicKey, PublicKey, Txid, u32, Transaction, MusigPubNonce, MusigPubNonce, BlindingFactor, Signature) { - - let lock_time = tx1.lock_time; - assert!(lock_time.is_block_height()); - let block_height = lock_time.to_consensus_u32(); - - assert!(tx1.input.len() == 1); - let input = &tx1.input[0]; - - let statechain_coin_details = client_config.get_statechain_coin_details(&statechain_id).await; - - let auth_secret_key = statechain_coin_details.auth_seckey; - - let secp = Secp256k1::new(); - let keypair = secp256k1::KeyPair::from_seckey_slice(&secp, auth_secret_key.as_ref()).unwrap(); - let msg = Message::from_hashed_data::(statechain_id.to_string().as_bytes()); - let signed_statechain_id = secp.sign_schnorr(&msg, &keypair); - - let client_seckey = statechain_coin_details.client_seckey; - let client_pubkey = statechain_coin_details.client_pubkey; - let server_pubkey = statechain_coin_details.server_pubkey; - let input_txid = input.previous_output.txid; - let input_vout = input.previous_output.vout; - let input_pubkey = statechain_coin_details.aggregated_pubkey; - let input_scriptpubkey = statechain_coin_details.p2tr_agg_address.script_pubkey(); - let input_amount = statechain_coin_details.amount; - - let to_address = Address::p2tr(&Secp256k1::new(), new_user_pubkey.x_only_public_key().0, None, client_config.network); - - let (new_tx, client_pub_nonce, server_pub_nonce, blinding_factor) = crate::transaction::new_backup_transaction( - client_config, - block_height, - statechain_id, - &signed_statechain_id, - &client_seckey, - &client_pubkey, - &server_pubkey, - input_txid, - input_vout, - &input_pubkey, - &input_scriptpubkey, - input_amount, - &to_address, - false,).await.unwrap(); - - (client_seckey, client_pubkey, server_pubkey, input_txid, input_vout, new_tx, client_pub_nonce, server_pub_nonce, blinding_factor, signed_statechain_id) - -} \ No newline at end of file diff --git a/clients/standalone-rust/src/utils.rs b/clients/standalone-rust/src/utils.rs deleted file mode 100644 index 03262c46..00000000 --- a/clients/standalone-rust/src/utils.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::{error::CError, electrum}; -use crate::client_config::ClientConfig; - -pub struct InfoConfig { - pub initlock: u32, - pub interval: u32, - pub fee_rate_sats_per_byte: u64, -} - -pub async fn info_config(statechain_entity_url: &str, electrum_client: &electrum_client::Client, tor_proxy: &str) -> Result{ - - let path = "info/config"; - - let mut client: reqwest::Client = reqwest::Client::new(); - if tor_proxy != "".to_string() { - let tor_proxy = reqwest::Proxy::all(tor_proxy).unwrap(); - client = reqwest::Client::builder().proxy(tor_proxy).build().unwrap(); - } - let request = client.get(&format!("{}/{}", statechain_entity_url, path)); - - let value = match request.send().await { - Ok(response) => { - let text = response.text().await.unwrap(); - text - }, - Err(err) => { - return Err(CError::Generic(err.to_string())); - }, - }; - - let server_config: mercury_lib::utils::ServerConfig = serde_json::from_str(value.as_str()).expect(&format!("failed to parse: {}", value.as_str())); - - let initlock = server_config.initlock; - let interval = server_config.interval; - - let fee_rate_btc_per_kb = electrum::estimate_fee(&electrum_client, 3); - let fee_rate_sats_per_byte = (fee_rate_btc_per_kb * 100000.0) as u64; - - Ok(InfoConfig { - initlock, - interval, - fee_rate_sats_per_byte, - }) -} \ No newline at end of file diff --git a/clients/standalone-rust/src/wallet.rs b/clients/standalone-rust/src/wallet.rs deleted file mode 100644 index 9822f9f4..00000000 --- a/clients/standalone-rust/src/wallet.rs +++ /dev/null @@ -1,183 +0,0 @@ -use std::str::FromStr; - -use bitcoin::{Network, Address, Txid, PrivateKey}; -use secp256k1_zkp::SecretKey; -use serde::{Serialize, Deserialize}; -use sqlx::{Sqlite, Row}; - -use crate::client_config::ClientConfig; - -pub async fn get_all_addresses(client_config: &ClientConfig) -> (Vec::
, Vec::
){ - let mut agg_addresses = Vec::
::new(); - let mut backup_addresses = Vec::
::new(); - - let query = "SELECT backup_address FROM signer_data"; - - let rows = sqlx::query(query) - .fetch_all(&client_config.pool) - .await - .unwrap(); - - for row in rows { - - let backup_address_str = row.get::("backup_address"); - let backup_address = Address::from_str(&backup_address_str).unwrap().require_network(client_config.network).unwrap(); - backup_addresses.push(backup_address); - } - - let query = "SELECT p2tr_agg_address FROM statechain_data"; - - let rows = sqlx::query(query) - .fetch_all(&client_config.pool) - .await - .unwrap(); - - for row in rows { - - let p2tr_agg_address = row.get::("p2tr_agg_address"); - - if p2tr_agg_address.is_empty() { - continue; - } - - let agg_address = Address::from_str(&p2tr_agg_address).unwrap().require_network(client_config.network).unwrap(); - agg_addresses.push(agg_address); - } - - (agg_addresses, backup_addresses) -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct BackupTransactionJSON { - tx_n: u32, - tx: String, - client_public_nonce: String, - blinding_factor: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct CoinJSON { - pub utxo: String, - pub address: String, - pub amount: u32, - pub statechain_id: String, - pub privkey: String, - pub auth_key: String, - pub locktime: u32, - pub status: String, - pub tx_withdraw: Option, - pub backup_txs: Vec, -} - -pub async fn get_backup_tx(pool: &sqlx::Pool, statechain_id: &str) -> Vec:: { - - let rows = sqlx::query("SELECT * FROM backup_transaction WHERE statechain_id = $1") - .bind(statechain_id) - .fetch_all(pool) - .await - .unwrap(); - - let mut backup_transactions = Vec::::new(); - - for row in rows { - let row_statechain_id = row.get::("statechain_id"); - assert!(row_statechain_id == statechain_id); - - let tx_n = row.get::("tx_n"); - - let client_public_nonce = row.get::, _>("client_public_nonce"); - let client_public_nonce = hex::encode(client_public_nonce); - - let blinding_factor = row.get::, _>("blinding_factor"); - let blinding_factor = hex::encode(blinding_factor); - - let tx_bytes = row.get::, _>("backup_tx"); - let tx = hex::encode(tx_bytes); - - backup_transactions.push(BackupTransactionJSON { - tx_n, - tx, - client_public_nonce, - blinding_factor - }); - } - - backup_transactions - -} - -pub async fn get_coins(client_config: &ClientConfig) -> Vec { - - let query = "SELECT \ - std.funding_txid, \ - std.funding_vout, \ - std.p2tr_agg_address, \ - std.amount, \ - std.statechain_id, \ - sid.client_seckey_share, \ - sid.auth_seckey, \ - std.locktime, \ - std.status, \ - std.tx_withdraw \ - FROM signer_data sid INNER JOIN statechain_data std \ - ON sid.client_pubkey_share = std.client_pubkey_share"; - - let rows = sqlx::query(query) - .fetch_all(&client_config.pool) - .await - .unwrap(); - - let mut coins = Vec::::new(); - - for row in rows { - - let utxo_tx_hash = row.get::("funding_txid"); - let utxo_tx_hash = Txid::from_str(&utxo_tx_hash).unwrap(); - - let utxo_vout = row.get::("funding_vout"); - - let utxo = format!("{}:{}", utxo_tx_hash, utxo_vout); - - let agg_address_str = row.get::("p2tr_agg_address"); - let p2tr_agg_address = Address::from_str(&agg_address_str).unwrap().require_network(client_config.network).unwrap(); - - let amount = row.get::("amount"); - - let statechain_id = row.get::("statechain_id"); - - let client_seckey_share_bytes = row.get::, _>("client_seckey_share"); - let client_seckey_share = SecretKey::from_slice(&client_seckey_share_bytes).unwrap(); - - let privkey = PrivateKey::new(client_seckey_share, client_config.network); - - let auth_seckey_bytes = row.get::, _>("auth_seckey"); - let auth_seckey = SecretKey::from_slice(&auth_seckey_bytes).unwrap(); - - let auth_key = PrivateKey::new(auth_seckey, client_config.network); - - let locktime = row.get::("locktime"); - - let status = row.get::("status"); - - let tx_withdraw: Option = row.get::, _>("tx_withdraw"); - - let backup_txs = get_backup_tx(&client_config.pool, &statechain_id).await; - - coins.push(CoinJSON { - utxo, - address: p2tr_agg_address.to_string(), - amount, - statechain_id, - privkey: privkey.to_wif(), - auth_key: auth_key.to_wif(), - locktime, - status, - tx_withdraw, - backup_txs - }); - } - - coins - - -} \ No newline at end of file diff --git a/clients/standalone-rust/src/withdraw.rs b/clients/standalone-rust/src/withdraw.rs deleted file mode 100644 index 127fa2a3..00000000 --- a/clients/standalone-rust/src/withdraw.rs +++ /dev/null @@ -1,76 +0,0 @@ -use bitcoin::Address; - -use crate::{electrum, error::CError, client_config::ClientConfig}; - -pub async fn execute(client_config: &ClientConfig, statechain_id: &str, to_address: &Address, fee_rate: u64) -> Result { - - let block_header = electrum::block_headers_subscribe_raw(&client_config.electrum_client); - let block_height = block_header.height; - - let coin_key_details = client_config.get_coin_and_key_info(statechain_id).await; - - let (tx, client_pub_nonce, server_pub_nonce, blinding_factor) = crate::transaction::new_backup_transaction( - client_config, - block_height as u32, - &statechain_id, - &coin_key_details.signed_statechain_id, - &coin_key_details.client_seckey, - &coin_key_details.client_pubkey, - &coin_key_details.server_pubkey, - coin_key_details.utxo_tx_hash, - coin_key_details.utxo_vout, - &coin_key_details.aggregated_pubkey, - &coin_key_details.p2tr_agg_address.script_pubkey(), - coin_key_details.amount, - &to_address, - true).await.unwrap(); - - let tx_bytes = bitcoin::consensus::encode::serialize(&tx); - - client_config.insert_transaction( - coin_key_details.new_tx_n, - &tx_bytes, - &client_pub_nonce.serialize(), - &server_pub_nonce.serialize(), - &coin_key_details.client_pubkey, - &coin_key_details.server_pubkey, - blinding_factor.as_bytes(), - &statechain_id, - &to_address.to_string() - ).await.unwrap(); - - let txid = electrum::transaction_broadcast_raw(&client_config.electrum_client, &tx_bytes); - - client_config.update_coin_status_and_tx_withdraw(statechain_id, "WITHDRAWN", Some(txid.to_string())).await; - - // delete statechain on the server - let delete_statechain_payload = mercury_lib::withdraw::DeleteStatechainPayload { - statechain_id: statechain_id.to_string(), - signed_statechain_id: coin_key_details.signed_statechain_id.to_string(), - }; - - let endpoint = client_config.statechain_entity.clone(); - let path = "delete_statechain"; - - let tor_proxy = client_config.tor_proxy.clone(); - - let mut client: reqwest::Client = reqwest::Client::new(); - if tor_proxy != "".to_string() { - let tor_proxy = reqwest::Proxy::all(tor_proxy).unwrap(); - client = reqwest::Client::builder().proxy(tor_proxy).build().unwrap(); - } - let request = client.delete(&format!("{}/{}", endpoint, path)); - - let _ = match request.json(&delete_statechain_payload).send().await { - Ok(response) => { - let text = response.text().await.unwrap(); - text - }, - Err(err) => { - return Err(CError::Generic(err.to_string())); - }, - }; - - Ok(txid.to_string()) - -} \ No newline at end of file diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 1fccb6de..e2f0b07a 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -9,9 +9,9 @@ use std::str::FromStr; use bech32::{Variant, ToBase32, FromBase32}; use bip39::Mnemonic; -use bitcoin::{bip32::{ChildNumber, DerivationPath, ExtendedPrivKey}, secp256k1::{ffi::types::AlignedType, AllPreallocated, PublicKey, Secp256k1, SecretKey}}; +use bitcoin::{bip32::{ChildNumber, DerivationPath, ExtendedPrivKey}, secp256k1::{ffi::types::AlignedType, AllPreallocated, PublicKey, Secp256k1, SecretKey}, Address}; -use anyhow::{Result, anyhow}; +use anyhow::{anyhow, Result}; const MAINNET_HRP : &str = "ml"; const TESTNET_HRP : &str = "tml"; @@ -99,6 +99,38 @@ pub fn get_sc_address(mnemonic: &str, index: u32, network: &str) -> Result Result { + + let network = utils::get_network(network)?; + + if address.starts_with(MAINNET_HRP) || address.starts_with(TESTNET_HRP) { + if address.starts_with(MAINNET_HRP) && network != bitcoin::Network::Bitcoin { + return Err(anyhow!("Statechain address does not match the network")); + } + + if address.starts_with(TESTNET_HRP) && network == bitcoin::Network::Bitcoin { + return Err(anyhow!("Statechain address does not match the network")); + } + + match decode_transfer_address(address) { + Ok(_) => Ok(true), + Err(_) => Err(anyhow::anyhow!("Invalid statechain address")), + } + } + else { + match Address::from_str(address) { + Ok(addr) => { + + match addr.require_network(network) { + Ok(_) => Ok(true), + Err(_) => Err(anyhow::anyhow!("Bitcoin address does not match the network")), + } + }, + Err(_) => Err(anyhow::anyhow!("Invalid bitcoin address")), + } + + } +} #[cfg(test)] mod tests { diff --git a/lib/src/transaction.rs b/lib/src/transaction.rs index e31ef644..c10c0652 100644 --- a/lib/src/transaction.rs +++ b/lib/src/transaction.rs @@ -1,6 +1,6 @@ use std::{str::FromStr, collections::BTreeMap}; -use bitcoin::{Txid, ScriptBuf, Transaction, absolute, TxIn, OutPoint, Witness, TxOut, psbt::{Psbt, Input, PsbtSighashType}, sighash::{TapSighashType, SighashCache, self, TapSighash}, taproot::{TapTweakHash, self}, hashes::{Hash, sha256}, Address, PrivateKey, Network}; +use bitcoin::{Txid, ScriptBuf, Transaction, absolute, TxIn, OutPoint, Witness, TxOut, psbt::{Psbt, Input, PsbtSighashType}, sighash::{TapSighashType, SighashCache, self, TapSighash}, taproot::{TapTweakHash, self}, hashes::Hash, Address, PrivateKey, Network}; use secp256k1_zkp::{SecretKey, PublicKey, Secp256k1, schnorr::Signature, Message, musig::{MusigSessionId, MusigPubNonce, BlindingFactor, MusigSession, MusigPartialSignature, blinded_musig_pubkey_xonly_tweak_add, blinded_musig_negate_seckey, MusigAggNonce, MusigSecNonce}, new_musig_nonce_pair, KeyPair, rand::{self, Rng}}; use serde::{Serialize, Deserialize}; @@ -115,18 +115,14 @@ pub fn create_tx_out( let absolute_fee: u64 = BACKUP_TX_SIZE * fee_rate_sats_per_byte; let amount_out = input_amount - absolute_fee; - let mut recipient_address: Option
= None; - - if to_address.starts_with(crate::MAINNET_HRP) || to_address.starts_with(crate::TESTNET_HRP) { + let recipient_address = if to_address.starts_with(crate::MAINNET_HRP) || to_address.starts_with(crate::TESTNET_HRP) { let (_, recipient_user_pubkey, _) = decode_transfer_address(to_address)?; let new_address = Address::p2tr(&Secp256k1::new(), recipient_user_pubkey.x_only_public_key().0, None, network); - recipient_address = Some(new_address); + new_address } else { let new_address = Address::from_str(&to_address).unwrap().require_network(network)?; - recipient_address = Some(new_address); - } - - let recipient_address = recipient_address.unwrap(); + new_address + }; let tx_out = TxOut { value: amount_out, script_pubkey: recipient_address.script_pubkey() }; diff --git a/lib/src/transfer/sender.rs b/lib/src/transfer/sender.rs index a19d674c..f7d8afc3 100644 --- a/lib/src/transfer/sender.rs +++ b/lib/src/transfer/sender.rs @@ -1,9 +1,9 @@ use std::str::FromStr; use bitcoin::{secp256k1, hashes::sha256, Txid, PrivateKey}; -use secp256k1_zkp::{Secp256k1, Message, PublicKey, Scalar}; +use secp256k1_zkp::{Secp256k1, Message, Scalar}; use serde::{Serialize, Deserialize}; -use anyhow::{Result, anyhow}; +use anyhow::Result; use serde_json::json; use crate::{decode_transfer_address, wallet::{Coin, BackupTx}}; diff --git a/lib/src/wallet/mod.rs b/lib/src/wallet/mod.rs index af0ec440..9140bbb3 100644 --- a/lib/src/wallet/mod.rs +++ b/lib/src/wallet/mod.rs @@ -103,6 +103,7 @@ pub struct Coin { } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[allow(non_camel_case_types)] pub enum CoinStatus { INITIALISED, // address generated but no Tx0 yet IN_MEMPOOL, // Tx0 in mempool