Skip to content

Commit 32e79a3

Browse files
authored
Merge pull request #25 from chainwayxyz/ozan/use-alloy-addr
Use alloy::primitives::Address instead of manual implementation
2 parents 33b4bbd + 05c18e3 commit 32e79a3

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,24 +1,24 @@
11
// Backend communication logic for Clementine CLI
22

3-
use crate::EVMAddress;
43
use crate::config::get_backend_endpoint;
54
use crate::deposit::parse_taproot_address;
6-
use bitcoin::{Address, Network};
5+
use crate::{BitcoinAddress, CitreaAddress};
6+
use bitcoin::Network;
77
use colored::*;
88
use serde_json::json;
99

1010
/// Make a POST request to create a deposit account
1111
pub fn create_deposit_account(
12-
evm_address: &EVMAddress,
13-
recovery_taproot_address: &Address,
12+
citrea_address: &CitreaAddress,
13+
recovery_taproot_address: &BitcoinAddress,
1414
network: Network,
15-
) -> Result<Address, Box<dyn std::error::Error>> {
15+
) -> Result<BitcoinAddress, Box<dyn std::error::Error>> {
1616
let backend_endpoint = get_backend_endpoint(network);
1717
let url = format!("{}deposit-accounts", backend_endpoint);
1818

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

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::{BRIDGE_AMOUNT, UNSPENDABLE_XONLY_PUBKEY, USER_TAKES_AFTER, get_verifier_pks};
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,17 +66,17 @@ 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
network: Network,
74-
) -> Result<(Address, TaprootSpendInfo), Box<dyn std::error::Error>> {
74+
) -> Result<(BitcoinAddress, TaprootSpendInfo), Box<dyn std::error::Error>> {
7575
let verifiers_public_keys = get_verifier_pks(network);
7676
let agg_pk = XOnlyPublicKey::from_musig2_pks(&verifiers_public_keys)?;
7777
debug!("agg_pk: {:?}", agg_pk.to_string());
7878
debug!("verifiers_public_keys: {:?}", verifiers_public_keys);
79-
let deposit_script = deposit_script(*evm_address, agg_pk);
79+
let deposit_script = deposit_script(*citrea_address, agg_pk);
8080
let recovery_key =
8181
XOnlyPublicKey::from_slice(&recovery_taproot_address.script_pubkey().to_bytes()[2..34])?;
8282
let recover_script = recover_script(recovery_key, USER_TAKES_AFTER);
@@ -89,7 +89,7 @@ pub fn calculate_deposit_address(
8989
.finalize(&SECP, *UNSPENDABLE_XONLY_PUBKEY)
9090
.expect("finalized script is valid");
9191

92-
let deposit_address = Address::p2tr(
92+
let deposit_address = BitcoinAddress::p2tr(
9393
&SECP,
9494
*UNSPENDABLE_XONLY_PUBKEY,
9595
taproot_spend_info.merkle_root(),
@@ -126,19 +126,19 @@ pub fn sign_with_tweak(
126126
}
127127

128128
#[allow(clippy::too_many_arguments)]
129-
/// Sign a recovery transaction with a given keypair, EVM address, recovery taproot address, deposit outpoint, deposit amount, claim address, fee rate, and network
129+
/// Sign a recovery transaction with a given keypair, Citrea address, recovery taproot address, deposit outpoint, deposit amount, claim address, fee rate, and network
130130
pub fn sign_recovery_tx(
131131
keypair: &Keypair,
132-
evm_address: &EVMAddress,
133-
recovery_taproot_address: &Address,
132+
citrea_address: &CitreaAddress,
133+
recovery_taproot_address: &BitcoinAddress,
134134
deposit_outpoint: &OutPoint,
135135
deposit_amount: Option<Amount>,
136-
claim_address: &Address,
136+
claim_address: &BitcoinAddress,
137137
fee_rate: Option<FeeRate>,
138138
network: Network,
139139
) -> Result<Transaction, Box<dyn std::error::Error>> {
140140
let (deposit_address, taproot_spend_info) =
141-
calculate_deposit_address(evm_address, recovery_taproot_address, network)?;
141+
calculate_deposit_address(citrea_address, recovery_taproot_address, network)?;
142142

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

250250
pub fn verify_recovery_tx(
251251
recovery_tx: &Transaction,
252-
evm_address: &EVMAddress,
253-
recovery_taproot_address: &Address,
252+
citrea_address: &CitreaAddress,
253+
recovery_taproot_address: &BitcoinAddress,
254254
input_amount: Option<Amount>,
255255
network: Network,
256-
) -> Result<(Txid, Address, Amount), Box<dyn std::error::Error>> {
256+
) -> Result<(Txid, BitcoinAddress, Amount), Box<dyn std::error::Error>> {
257257
// sanity check input count
258258
if recovery_tx.input.len() != 1 {
259259
return Err("Recovery transaction must have exactly one input".into());
@@ -275,7 +275,7 @@ pub fn verify_recovery_tx(
275275
}
276276

277277
let (deposit_address, taproot_spend_info) =
278-
calculate_deposit_address(evm_address, recovery_taproot_address, network)?;
278+
calculate_deposit_address(citrea_address, recovery_taproot_address, network)?;
279279

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

362-
let output_address = Address::from_script(
362+
let output_address = BitcoinAddress::from_script(
363363
&recovery_tx.output[0].script_pubkey,
364364
network,
365365
)
@@ -376,9 +376,9 @@ pub fn verify_recovery_tx(
376376

377377
pub fn sign_withdrawal_signature(
378378
keypair: &Keypair,
379-
signer_address: &Address,
379+
signer_address: &BitcoinAddress,
380380
withdrawal_utxo: &OutPoint,
381-
claim_address: &Address,
381+
claim_address: &BitcoinAddress,
382382
amount: Amount,
383383
) -> Result<bitcoin::taproot::Signature, Box<dyn std::error::Error>> {
384384
let txin = TxIn {
@@ -427,9 +427,9 @@ pub fn sign_withdrawal_signature(
427427

428428
pub fn verify_withdrawal_signature(
429429
sig: &bitcoin::taproot::Signature,
430-
signer_address: &Address,
430+
signer_address: &BitcoinAddress,
431431
withdrawal_utxo: &OutPoint,
432-
claim_address: &Address,
432+
claim_address: &BitcoinAddress,
433433
amount: Amount,
434434
) -> Result<(), Box<dyn std::error::Error>> {
435435
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,
@@ -9,22 +8,24 @@ use crate::bitcoin_utils::{
98
generate_keypair_and_taproot_address_from_private_key,
109
sign_recovery_tx as utils_sign_recovery_tx,
1110
};
11+
1212
use crate::parameters::get_citrea_deposit_params;
1313
use crate::storage::load_key;
1414
use crate::storage::store_key;
1515
use crate::withdrawal::{get_tx_details, get_txout_details_from_rpc};
16+
use crate::{BitcoinAddress, CitreaAddress, parse_citrea_address};
1617
use bitcoin::AddressType;
1718
use bitcoin::consensus::deserialize;
18-
use bitcoin::{Address, Network, address::NetworkUnchecked};
1919
use bitcoin::{Amount, FeeRate, OutPoint, Transaction, Txid};
20+
use bitcoin::{Network, address::NetworkUnchecked};
2021
use colored::*;
2122
use std::str::FromStr;
2223

2324
pub fn parse_address(
2425
address: &str,
2526
network: Network,
26-
) -> Result<Address, Box<dyn std::error::Error>> {
27-
let unchecked_address: Address<NetworkUnchecked> = address
27+
) -> Result<BitcoinAddress, Box<dyn std::error::Error>> {
28+
let unchecked_address: BitcoinAddress<NetworkUnchecked> = address
2829
.parse()
2930
.map_err(|_| "Invalid Bitcoin address format")?;
3031
let address = unchecked_address.require_network(network)?;
@@ -35,7 +36,7 @@ pub fn parse_address(
3536
pub fn parse_taproot_address(
3637
address: &str,
3738
network: Network,
38-
) -> Result<Address, Box<dyn std::error::Error>> {
39+
) -> Result<BitcoinAddress, Box<dyn std::error::Error>> {
3940
let address = parse_address(address, network)?;
4041

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

9096
// Call backend to create deposit account
@@ -148,7 +154,7 @@ pub async fn get_deposit_params(
148154

149155
#[allow(clippy::too_many_arguments)]
150156
pub fn sign_recovery_tx(
151-
evm_address: &str,
157+
citrea_address: &str,
152158
recovery_taproot_address: &str,
153159
deposit_txid: &str,
154160
deposit_vout: u32,
@@ -157,9 +163,9 @@ pub fn sign_recovery_tx(
157163
amount: Option<f64>,
158164
network: Network,
159165
) -> Result<(), Box<dyn std::error::Error>> {
160-
let evm_addr = EVMAddress::try_from(evm_address)?;
166+
let citrea_addr: CitreaAddress = parse_citrea_address(citrea_address)?;
161167
let recovery_addr = parse_taproot_address(recovery_taproot_address, network)?;
162-
let claim_addr = Address::from_str(claim_address)?.require_network(network)?;
168+
let claim_addr = BitcoinAddress::from_str(claim_address)?.require_network(network)?;
163169
let txid = Txid::from_str(deposit_txid)?;
164170
let outpoint = OutPoint {
165171
txid,
@@ -176,7 +182,7 @@ pub fn sign_recovery_tx(
176182
let fee_rate_opt = fee_rate.map(FeeRate::from_sat_per_vb_unchecked);
177183
let signed_tx = utils_sign_recovery_tx(
178184
&keypair,
179-
&evm_addr,
185+
&citrea_addr,
180186
&recovery_addr,
181187
&outpoint,
182188
deposit_amount,
@@ -194,16 +200,16 @@ pub fn sign_recovery_tx(
194200
#[allow(clippy::too_many_arguments)]
195201
pub fn verify_recovery_tx(
196202
recovery_tx: &str,
197-
evm_address: &str,
203+
citrea_address: &str,
198204
recovery_taproot_address: &str,
199205
amount: Option<f64>,
200206
network: Network,
201-
) -> Result<(Txid, Address, Amount), Box<dyn std::error::Error>> {
207+
) -> Result<(Txid, BitcoinAddress, Amount), Box<dyn std::error::Error>> {
202208
let recovery_tx: Transaction = deserialize(&hex::decode(recovery_tx)?)?;
203209

204210
let (txid, address, amount) = crate::bitcoin_utils::verify_recovery_tx(
205211
&recovery_tx,
206-
&EVMAddress::try_from(evm_address)?,
212+
&parse_citrea_address(citrea_address)?,
207213
&parse_taproot_address(recovery_taproot_address, network)?,
208214
amount.map(|amount| Amount::from_btc(amount).unwrap()),
209215
network,

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)