Skip to content

Commit e64cf19

Browse files
committed
Merge branch 'cli-rust-rewrite' into ceyhun/config
2 parents 30f9f89 + 32e79a3 commit e64cf19

File tree

6 files changed

+74
-128
lines changed

6 files changed

+74
-128
lines changed

src/backend.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
// Backend communication logic for Clementine CLI
22

3+
use crate::config::CliConfig;
34
use crate::deposit::parse_taproot_address;
4-
use crate::{EVMAddress, config::CliConfig};
5-
use bitcoin::Address;
5+
use crate::{BitcoinAddress, CitreaAddress};
66
use colored::*;
77
use serde_json::json;
88

99
/// Make a POST request to create a deposit account
1010
pub fn create_deposit_account(
11-
evm_address: &EVMAddress,
12-
recovery_taproot_address: &Address,
11+
citrea_address: &CitreaAddress,
12+
recovery_taproot_address: &BitcoinAddress,
1313
config: CliConfig,
14-
) -> Result<Address, Box<dyn std::error::Error>> {
14+
) -> Result<BitcoinAddress, Box<dyn std::error::Error>> {
1515
let backend_endpoint = config.citrea_backend_endpoint;
1616
let url = format!("{}deposit-accounts", backend_endpoint);
1717

1818
// Prepare request body
1919
let request_body = json!({
20-
"evm_addr": evm_address.to_string(),
20+
"citrea_addr": citrea_address.to_string(),
2121
"recovery_taproot_addr": recovery_taproot_address.to_string()
2222
});
2323

src/bitcoin_utils.rs

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,32 @@
33
use bitcoin::secp256k1::{Keypair, Secp256k1, SecretKey, schnorr};
44
use bitcoin::taproot::{LeafVersion, TaprootBuilder, TaprootSpendInfo};
55
use bitcoin::{
6-
Address, Amount, FeeRate, Network, OutPoint, ScriptBuf, Sequence, TapLeafHash, TapNodeHash,
7-
TapSighash, TapTweakHash, Transaction, TxIn, TxOut, Txid, Weight, Witness, XOnlyPublicKey,
6+
Amount, FeeRate, Network, OutPoint, ScriptBuf, Sequence, TapLeafHash, TapNodeHash, TapSighash,
7+
TapTweakHash, Transaction, TxIn, TxOut, Txid, Weight, Witness, XOnlyPublicKey,
88
};
99
use colored::*;
1010
use std::io::{self, Write};
1111
use std::str::FromStr;
1212
use std::sync::LazyLock;
1313

14-
use crate::EVMAddress;
1514
use crate::config::{CliConfig, UNSPENDABLE_XONLY_PUBKEY};
1615
use crate::musig2::AggregateFromPublicKeys;
1716
use crate::script::{deposit_script, recover_script};
17+
use crate::{BitcoinAddress, CitreaAddress};
1818
use bitcoin::hashes::Hash;
1919

2020
pub static SECP: LazyLock<Secp256k1<bitcoin::secp256k1::All>> = LazyLock::new(Secp256k1::new);
2121

2222
/// Calculate taproot address from a keypair
23-
pub fn calculate_taproot_address(keypair: &Keypair, network: Network) -> Address {
23+
pub fn calculate_taproot_address(keypair: &Keypair, network: Network) -> BitcoinAddress {
2424
let (xonly_public_key, _parity) = keypair.public_key().x_only_public_key();
25-
Address::p2tr(&SECP, xonly_public_key, None, network)
25+
BitcoinAddress::p2tr(&SECP, xonly_public_key, None, network)
2626
}
2727

2828
/// Generate a new random secret key and calculate its corresponding taproot address
2929
pub fn generate_key_and_taproot_address(
3030
network: Network,
31-
) -> Result<(Keypair, Address), Box<dyn std::error::Error>> {
31+
) -> Result<(Keypair, BitcoinAddress), Box<dyn std::error::Error>> {
3232
let keypair = Keypair::new(&SECP, &mut bitcoin::secp256k1::rand::thread_rng());
3333
let address = calculate_taproot_address(&keypair, network);
3434
Ok((keypair, address))
@@ -37,7 +37,7 @@ pub fn generate_key_and_taproot_address(
3737
pub fn generate_keypair_and_taproot_address_from_private_key(
3838
private_key: &str,
3939
network: Network,
40-
) -> Result<(Keypair, Address), Box<dyn std::error::Error>> {
40+
) -> Result<(Keypair, BitcoinAddress), Box<dyn std::error::Error>> {
4141
let sk = SecretKey::from_str(private_key)?;
4242
let keypair = Keypair::from_secret_key(&SECP, &sk);
4343
let address = calculate_taproot_address(&keypair, network);
@@ -66,16 +66,16 @@ pub fn confirm_private_key_storage(auto_yes: bool) -> Result<bool, Box<dyn std::
6666
Ok(input.trim().to_lowercase() == "y" || input.trim().to_lowercase() == "yes")
6767
}
6868

69-
/// Calculate the deposit address and taproot spend info for a given EVM address and recovery taproot address
69+
/// Calculate the deposit address and taproot spend info for a given Citrea address and recovery taproot address
7070
pub fn calculate_deposit_address(
71-
evm_address: &EVMAddress,
72-
recovery_taproot_address: &Address,
71+
citrea_address: &CitreaAddress,
72+
recovery_taproot_address: &BitcoinAddress,
7373
config: CliConfig,
74-
) -> Result<(Address, TaprootSpendInfo), Box<dyn std::error::Error>> {
74+
) -> Result<(BitcoinAddress, TaprootSpendInfo), Box<dyn std::error::Error>> {
7575
let agg_pk = XOnlyPublicKey::from_musig2_pks(config.verifiers_pks.as_slice())?;
7676
debug!("verifiers_public_keys: {:?}", config.verifiers_pks);
7777
debug!("agg_pk: {:?}", agg_pk.to_string());
78-
let deposit_script = deposit_script(*evm_address, agg_pk);
78+
let deposit_script = deposit_script(*citrea_address, agg_pk);
7979
let recovery_key =
8080
XOnlyPublicKey::from_slice(&recovery_taproot_address.script_pubkey().to_bytes()[2..34])?;
8181
let recover_script = recover_script(recovery_key, config.user_takes_after);
@@ -88,7 +88,7 @@ pub fn calculate_deposit_address(
8888
.finalize(&SECP, *UNSPENDABLE_XONLY_PUBKEY)
8989
.expect("finalized script is valid");
9090

91-
let deposit_address = Address::p2tr(
91+
let deposit_address = BitcoinAddress::p2tr(
9292
&SECP,
9393
*UNSPENDABLE_XONLY_PUBKEY,
9494
taproot_spend_info.merkle_root(),
@@ -125,19 +125,19 @@ pub fn sign_with_tweak(
125125
}
126126

127127
#[allow(clippy::too_many_arguments)]
128-
/// Sign a recovery transaction with a given keypair, EVM address, recovery taproot address, deposit outpoint, deposit amount, claim address, fee rate, and network
128+
/// Sign a recovery transaction with a given keypair, Citrea address, recovery taproot address, deposit outpoint, deposit amount, claim address, fee rate, and network
129129
pub fn sign_recovery_tx(
130130
keypair: &Keypair,
131-
evm_address: &EVMAddress,
132-
recovery_taproot_address: &Address,
131+
citrea_address: &CitreaAddress,
132+
recovery_taproot_address: &BitcoinAddress,
133133
deposit_outpoint: &OutPoint,
134134
deposit_amount: Option<Amount>,
135-
claim_address: &Address,
135+
claim_address: &BitcoinAddress,
136136
fee_rate: Option<FeeRate>,
137137
config: CliConfig,
138138
) -> Result<Transaction, Box<dyn std::error::Error>> {
139139
let (deposit_address, taproot_spend_info) =
140-
calculate_deposit_address(evm_address, recovery_taproot_address, config.clone())?;
140+
calculate_deposit_address(citrea_address, recovery_taproot_address, config.clone())?;
141141

142142
let recovery_script = recover_script(
143143
XOnlyPublicKey::from_slice(&recovery_taproot_address.script_pubkey().to_bytes()[2..34])?,
@@ -248,11 +248,11 @@ pub fn sign_recovery_tx(
248248

249249
pub fn verify_recovery_tx(
250250
recovery_tx: &Transaction,
251-
evm_address: &EVMAddress,
252-
recovery_taproot_address: &Address,
251+
citrea_address: &CitreaAddress,
252+
recovery_taproot_address: &BitcoinAddress,
253253
input_amount: Option<Amount>,
254254
config: CliConfig,
255-
) -> Result<(Txid, Address, Amount), Box<dyn std::error::Error>> {
255+
) -> Result<(Txid, BitcoinAddress, Amount), Box<dyn std::error::Error>> {
256256
// sanity check input count
257257
if recovery_tx.input.len() != 1 {
258258
return Err("Recovery transaction must have exactly one input".into());
@@ -274,7 +274,7 @@ pub fn verify_recovery_tx(
274274
}
275275

276276
let (deposit_address, taproot_spend_info) =
277-
calculate_deposit_address(evm_address, recovery_taproot_address, config.clone())?;
277+
calculate_deposit_address(citrea_address, recovery_taproot_address, config.clone())?;
278278

279279
let recovery_key =
280280
XOnlyPublicKey::from_slice(&recovery_taproot_address.script_pubkey().to_bytes()[2..34])?;
@@ -358,7 +358,7 @@ pub fn verify_recovery_tx(
358358
"Signature verification failed. Possible causes include an incorrect input amount, an invalid signature, or a mismatched public key.".into()
359359
})?;
360360

361-
let output_address = Address::from_script(
361+
let output_address = BitcoinAddress::from_script(
362362
&recovery_tx.output[0].script_pubkey,
363363
config.network,
364364
)
@@ -375,9 +375,9 @@ pub fn verify_recovery_tx(
375375

376376
pub fn sign_withdrawal_signature(
377377
keypair: &Keypair,
378-
signer_address: &Address,
378+
signer_address: &BitcoinAddress,
379379
withdrawal_utxo: &OutPoint,
380-
claim_address: &Address,
380+
claim_address: &BitcoinAddress,
381381
amount: Amount,
382382
) -> Result<bitcoin::taproot::Signature, Box<dyn std::error::Error>> {
383383
let txin = TxIn {
@@ -426,9 +426,9 @@ pub fn sign_withdrawal_signature(
426426

427427
pub fn verify_withdrawal_signature(
428428
sig: &bitcoin::taproot::Signature,
429-
signer_address: &Address,
429+
signer_address: &BitcoinAddress,
430430
withdrawal_utxo: &OutPoint,
431-
claim_address: &Address,
431+
claim_address: &BitcoinAddress,
432432
amount: Amount,
433433
) -> Result<(), Box<dyn std::error::Error>> {
434434
let txin = TxIn {

src/deposit.rs

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Deposit-related commands and logic for Clementine CLI
22

3-
use crate::EVMAddress;
43
use crate::backend::create_deposit_account;
54
use crate::bitcoin_utils::{
65
calculate_deposit_address, confirm_private_key_storage, generate_key_and_taproot_address,
@@ -10,22 +9,24 @@ use crate::bitcoin_utils::{
109
sign_recovery_tx as utils_sign_recovery_tx,
1110
};
1211
use crate::config::CliConfig;
12+
1313
use crate::parameters::get_citrea_deposit_params;
1414
use crate::storage::load_key;
1515
use crate::storage::store_key;
1616
use crate::withdrawal::{get_tx_details, get_txout_details_from_rpc};
17+
use crate::{BitcoinAddress, CitreaAddress, parse_citrea_address};
1718
use bitcoin::AddressType;
1819
use bitcoin::consensus::deserialize;
19-
use bitcoin::{Address, Network, address::NetworkUnchecked};
2020
use bitcoin::{Amount, FeeRate, OutPoint, Transaction, Txid};
21+
use bitcoin::{Network, address::NetworkUnchecked};
2122
use colored::*;
2223
use std::str::FromStr;
2324

2425
pub fn parse_address(
2526
address: &str,
2627
network: Network,
27-
) -> Result<Address, Box<dyn std::error::Error>> {
28-
let unchecked_address: Address<NetworkUnchecked> = address
28+
) -> Result<BitcoinAddress, Box<dyn std::error::Error>> {
29+
let unchecked_address: BitcoinAddress<NetworkUnchecked> = address
2930
.parse()
3031
.map_err(|_| "Invalid Bitcoin address format")?;
3132
let address = unchecked_address.require_network(network)?;
@@ -36,7 +37,7 @@ pub fn parse_address(
3637
pub fn parse_taproot_address(
3738
address: &str,
3839
network: Network,
39-
) -> Result<Address, Box<dyn std::error::Error>> {
40+
) -> Result<BitcoinAddress, Box<dyn std::error::Error>> {
4041
let address = parse_address(address, network)?;
4142

4243
// Verify it's a taproot (P2TR) address
@@ -85,7 +86,12 @@ pub fn get_deposit_address(
8586
recovery_taproot_address: &str,
8687
config: CliConfig,
8788
) -> Result<(), Box<dyn std::error::Error>> {
88-
let citrea_address = EVMAddress::try_from(citrea_address)?;
89+
let citrea_address: CitreaAddress = parse_citrea_address(citrea_address)?;
90+
println!(
91+
"{} {}",
92+
"CITREA_ADDRESS (checksummed)".green().bold(),
93+
citrea_address,
94+
);
8995
let recovery_taproot_address = parse_taproot_address(recovery_taproot_address, config.network)?;
9096

9197
// Call backend to create deposit account
@@ -149,7 +155,7 @@ pub async fn get_deposit_params(
149155

150156
#[allow(clippy::too_many_arguments)]
151157
pub fn sign_recovery_tx(
152-
evm_address: &str,
158+
citrea_address: &str,
153159
recovery_taproot_address: &str,
154160
deposit_txid: &str,
155161
deposit_vout: u32,
@@ -158,9 +164,9 @@ pub fn sign_recovery_tx(
158164
amount: Option<f64>,
159165
config: CliConfig,
160166
) -> Result<(), Box<dyn std::error::Error>> {
161-
let evm_addr = EVMAddress::try_from(evm_address)?;
167+
let citrea_addr: CitreaAddress = parse_citrea_address(citrea_address)?;
162168
let recovery_addr = parse_taproot_address(recovery_taproot_address, config.network)?;
163-
let claim_addr = Address::from_str(claim_address)?.require_network(config.network)?;
169+
let claim_addr = BitcoinAddress::from_str(claim_address)?.require_network(config.network)?;
164170
let txid = Txid::from_str(deposit_txid)?;
165171
let outpoint = OutPoint {
166172
txid,
@@ -177,7 +183,7 @@ pub fn sign_recovery_tx(
177183
let fee_rate_opt = fee_rate.map(FeeRate::from_sat_per_vb_unchecked);
178184
let signed_tx = utils_sign_recovery_tx(
179185
&keypair,
180-
&evm_addr,
186+
&citrea_addr,
181187
&recovery_addr,
182188
&outpoint,
183189
deposit_amount,
@@ -195,16 +201,16 @@ pub fn sign_recovery_tx(
195201
#[allow(clippy::too_many_arguments)]
196202
pub fn verify_recovery_tx(
197203
recovery_tx: &str,
198-
evm_address: &str,
204+
citrea_address: &str,
199205
recovery_taproot_address: &str,
200206
amount: Option<f64>,
201207
config: CliConfig,
202-
) -> Result<(Txid, Address, Amount), Box<dyn std::error::Error>> {
208+
) -> Result<(Txid, BitcoinAddress, Amount), Box<dyn std::error::Error>> {
203209
let recovery_tx: Transaction = deserialize(&hex::decode(recovery_tx)?)?;
204210

205211
let (txid, address, amount) = crate::bitcoin_utils::verify_recovery_tx(
206212
&recovery_tx,
207-
&EVMAddress::try_from(evm_address)?,
213+
&parse_citrea_address(citrea_address)?,
208214
&parse_taproot_address(recovery_taproot_address, config.network)?,
209215
amount.map(|amount| Amount::from_btc(amount).unwrap()),
210216
config,

src/lib.rs

Lines changed: 12 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
use std::env;
2+
use std::str::FromStr;
3+
4+
pub type BitcoinAddress<V = bitcoin::address::NetworkChecked> = bitcoin::Address<V>;
5+
pub use bitcoin::address::{NetworkChecked, NetworkUnchecked};
6+
7+
pub type CitreaAddress = alloy::primitives::Address;
28

39
/// Check if debug mode is enabled via CLEMENTINE_DEBUG environment variable
410
pub fn is_debug_enabled() -> bool {
@@ -43,79 +49,10 @@ pub mod storage;
4349
pub mod types;
4450
pub mod withdrawal;
4551

46-
/// EVM Address type - 20 bytes
47-
#[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
48-
pub struct EVMAddress(pub [u8; 20]);
49-
50-
impl TryFrom<Vec<u8>> for EVMAddress {
51-
type Error = &'static str;
52-
53-
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
54-
if value.len() == 20 {
55-
Ok(EVMAddress(value.try_into().unwrap()))
56-
} else {
57-
Err("Expected a Vec<u8> of length 20")
58-
}
59-
}
60-
}
61-
62-
impl TryFrom<&str> for EVMAddress {
63-
type Error = &'static str;
64-
65-
fn try_from(value: &str) -> Result<Self, Self::Error> {
66-
let clean_address = value.strip_prefix("0x").unwrap_or(value);
67-
let bytes = hex::decode(clean_address).map_err(|_| "Invalid hex format for EVM address")?;
68-
Self::try_from(bytes)
69-
}
70-
}
71-
72-
impl std::fmt::Display for EVMAddress {
73-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74-
write!(f, "0x{}", hex::encode(self.0))
75-
}
76-
}
77-
78-
#[cfg(test)]
79-
mod tests {
80-
use super::*;
81-
82-
#[test]
83-
fn test_evm_address_from_vec() {
84-
let bytes = vec![0u8; 20];
85-
let addr = EVMAddress::try_from(bytes).unwrap();
86-
assert_eq!(addr.0, [0u8; 20]);
87-
88-
let too_short = vec![0u8; 19];
89-
assert!(EVMAddress::try_from(too_short).is_err());
90-
91-
let too_long = vec![0u8; 21];
92-
assert!(EVMAddress::try_from(too_long).is_err());
93-
}
94-
95-
#[test]
96-
fn test_evm_address_from_str() {
97-
let addr_str = "0x0000000000000000000000000000000000000000";
98-
let addr = EVMAddress::try_from(addr_str).unwrap();
99-
assert_eq!(addr.0, [0u8; 20]);
100-
101-
let without_prefix = "0000000000000000000000000000000000000000";
102-
let addr = EVMAddress::try_from(without_prefix).unwrap();
103-
assert_eq!(addr.0, [0u8; 20]);
104-
105-
let invalid_hex = "0xgggggggggggggggggggggggggggggggggggggggg";
106-
assert!(EVMAddress::try_from(invalid_hex).is_err());
107-
108-
let wrong_length = "0x00000000000000000000000000000000000000";
109-
assert!(EVMAddress::try_from(wrong_length).is_err());
110-
}
111-
112-
#[test]
113-
fn test_evm_address_display() {
114-
let bytes = vec![0u8; 20];
115-
let addr = EVMAddress::try_from(bytes).unwrap();
116-
assert_eq!(
117-
addr.to_string(),
118-
"0x0000000000000000000000000000000000000000"
119-
);
120-
}
52+
pub fn parse_citrea_address(
53+
citrea_address: &str,
54+
) -> Result<CitreaAddress, Box<dyn std::error::Error>> {
55+
let citrea_address: CitreaAddress =
56+
CitreaAddress::from_str(citrea_address).map_err(|_| "Invalid Citrea address format")?;
57+
Ok(citrea_address)
12158
}

0 commit comments

Comments
 (0)