diff --git a/Cargo.lock b/Cargo.lock index c895147..c81e371 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1185,6 +1185,7 @@ dependencies = [ "fuel-crypto 0.6.0", "fuel-types", "fuels", + "fuels-signers", "home", "rand 0.8.5", "rpassword", diff --git a/Cargo.toml b/Cargo.toml index 704e51c..9733a14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ eth-keystore = { version = "0.4" } fuel-crypto = "0.6" fuel-types = "0.5" fuels = { version = "0.20", default-features = false } +fuels-signers = { version = "0.20.0" } home = "0.5.3" rand = { version = "0.8.4", default-features = false } rpassword = "6.0.1" diff --git a/src/account.rs b/src/account.rs index 3da1ef5..c5a2ca3 100644 --- a/src/account.rs +++ b/src/account.rs @@ -2,8 +2,7 @@ use crate::utils::{ create_accounts_file, number_of_derived_accounts, Accounts, DEFAULT_WALLETS_VAULT_PATH, }; use anyhow::{bail, Result}; -use fuels::prelude::*; -use fuels::signers::wallet::Wallet; +use fuels::{prelude::*, signers::wallet::Wallet}; use std::path::PathBuf; pub(crate) fn print_account_address(path: Option, account_index: usize) -> Result<()> { @@ -13,7 +12,7 @@ pub(crate) fn print_account_address(path: Option, account_index: usize) }; let existing_accounts = Accounts::from_dir(&wallet_path)?; if let Some(account) = existing_accounts.addresses().iter().nth(account_index) { - println!("Account {} address: 0x{}", account_index, account); + println!("Account {} address: {}", account_index, account); } else { eprintln!("Account {} is not derived yet!", account_index); } @@ -43,6 +42,6 @@ pub(crate) fn new_account(path: Option) -> Result<()> { account_addresses.push(wallet.address().to_string()); create_accounts_file(&wallet_path, account_addresses)?; - println!("Wallet public address: {}", wallet.address()); + println!("Wallet address: {}", wallet.address()); Ok(()) } diff --git a/src/main.rs b/src/main.rs index 9c4f5a8..4f1b79a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,14 @@ mod account; mod init; mod list; +mod sign; mod utils; use crate::{ account::{new_account, print_account_address}, init::init_wallet, list::print_wallet_list, + sign::sign_transaction_manually, }; use anyhow::Result; use clap::{ArgEnum, Parser, Subcommand}; @@ -39,6 +41,12 @@ enum Command { account_index: usize, path: Option, }, + /// Sign a transaction by providing its ID and the signing account's index + Sign { + id: String, + account_index: usize, + path: Option, + }, } #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, ArgEnum)] @@ -60,6 +68,11 @@ async fn main() -> Result<()> { account_index, path, } => print_account_address(path, account_index)?, + Command::Sign { + id, + account_index, + path, + } => sign_transaction_manually(&id, account_index, path).await?, }; Ok(()) } diff --git a/src/sign.rs b/src/sign.rs new file mode 100644 index 0000000..798dad1 --- /dev/null +++ b/src/sign.rs @@ -0,0 +1,23 @@ +use crate::utils::{derive_account_with_index, DEFAULT_WALLETS_VAULT_PATH}; +use anyhow::{anyhow, Result}; +use fuel_crypto::{Message, Signature}; +use fuel_types::Bytes32; +use fuels::prelude::*; +use std::{path::PathBuf, str::FromStr}; + +pub(crate) async fn sign_transaction_manually( + id: &str, + account_index: usize, + path: Option, +) -> Result<(), Error> { + let wallet_path = match &path { + Some(path) => PathBuf::from(path), + None => home::home_dir().unwrap().join(DEFAULT_WALLETS_VAULT_PATH), + }; + let tx_id = Bytes32::from_str(id).map_err(|e| anyhow!("{}", e))?; + let secret_key = derive_account_with_index(&wallet_path, account_index)?; + let message_hash = unsafe { Message::from_bytes_unchecked(*tx_id) }; + let sig = Signature::sign(&secret_key, &message_hash); + println!("Signature: {sig}"); + Ok(()) +} diff --git a/src/utils.rs b/src/utils.rs index 6cdadff..b3ea8b6 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,5 @@ use anyhow::{anyhow, Result}; +use fuel_crypto::SecretKey; use serde::{Deserialize, Serialize}; use std::{fs, path::Path}; @@ -62,3 +63,14 @@ pub(crate) fn number_of_derived_accounts(path: &Path) -> usize { 0 } } + +pub(crate) fn derive_account_with_index(path: &Path, account_index: usize) -> Result { + let password = rpassword::prompt_password( + "Please enter your password to decrypt initialized wallet's phrases: ", + )?; + let phrase_recovered = eth_keystore::decrypt_key(path.join(".wallet"), password)?; + let phrase = String::from_utf8(phrase_recovered)?; + let derive_path = format!("m/44'/1179993420'/{}'/0/0", account_index); + let secret_key = SecretKey::new_from_mnemonic_phrase_with_path(&phrase, &derive_path)?; + Ok(secret_key) +}