clementine_core/builder/
address.rs

1//! # Bitcoin Address Construction
2//!
3//! Contains helper functions to create taproot addresses with given scripts and internal key.
4//! Contains helper functions to create correct deposit addresses. Addresses need to be of a specific format to be
5//! valid deposit addresses.
6
7use super::script::{
8    BaseDepositScript, Multisig, ReplacementDepositScript, SpendableScript, TimelockScript,
9};
10use crate::deposit::SecurityCouncil;
11use crate::utils::ScriptBufExt;
12use bitcoin::address::NetworkUnchecked;
13use bitcoin::{secp256k1::XOnlyPublicKey, taproot::TaprootSpendInfo, Address};
14use clementine_errors::BridgeError;
15use clementine_primitives::EVMAddress;
16
17use eyre::Context;
18
19pub use clementine_utils::address::{
20    calculate_taproot_leaf_depths, create_taproot_address, taproot_builder_with_scripts,
21};
22
23/// Generates a deposit address for the user. Funds can be spent by N-of-N or
24/// user can take after specified time should the deposit fail.
25///
26/// # Parameters
27///
28/// - `nofn_xonly_pk`: N-of-N x-only public key of the depositor
29/// - `recovery_taproot_address`: User's x-only public key that can be used to
30///   take funds after some time
31/// - `user_evm_address`: User's EVM address.
32/// - `amount`: Amount to deposit
33/// - `network`: Bitcoin network to work on
34/// - `user_takes_after`: User can take the funds back, after this amounts of
35///   blocks have passed
36///
37/// # Returns
38///
39/// - [`Address`]: Deposit taproot Bitcoin address
40/// - [`TaprootSpendInfo`]: Deposit address's taproot spending information
41///
42/// # Panics
43///
44/// Panics if given parameters are malformed.
45pub fn generate_deposit_address(
46    nofn_xonly_pk: XOnlyPublicKey,
47    recovery_taproot_address: &Address<NetworkUnchecked>,
48    user_evm_address: EVMAddress,
49    network: bitcoin::Network,
50    user_takes_after: u16,
51) -> Result<(Address, TaprootSpendInfo), BridgeError> {
52    let deposit_script = BaseDepositScript::new(nofn_xonly_pk, user_evm_address).to_script_buf();
53
54    let recovery_script_pubkey = recovery_taproot_address
55        .clone()
56        .assume_checked()
57        .script_pubkey();
58
59    let recovery_extracted_xonly_pk = recovery_script_pubkey
60        .try_get_taproot_pk()
61        .wrap_err("Recovery taproot address is not a valid taproot address")?;
62
63    let script_timelock =
64        TimelockScript::new(Some(recovery_extracted_xonly_pk), user_takes_after).to_script_buf();
65
66    let (addr, spend) = create_taproot_address(&[deposit_script, script_timelock], None, network);
67    Ok((addr, spend))
68}
69
70/// Builds a Taproot address specifically for replacement deposits.
71/// Replacement deposits are to replace old move_to_vault transactions in case any issue is found on the bridge.
72/// This address incorporates a script committing to an old move transaction ID
73/// and a multisig script for the security council.
74/// This replacement deposit address will be used to create a new deposit transaction, which will then be used to
75/// sign the new related bridge deposit tx's.
76///
77/// # Parameters
78///
79/// - `old_move_txid`: The `Txid` of the old move_to_vault transaction that is being replaced.
80/// - `nofn_xonly_pk`: The N-of-N XOnlyPublicKey for the deposit.
81/// - `network`: The Bitcoin network on which the address will be used.
82/// - `security_council`: The `SecurityCouncil` configuration for the multisig script.
83///
84/// # Returns
85///
86/// - `Ok((Address, TaprootSpendInfo))` containing the new replacement deposit address
87///   and its associated `TaprootSpendInfo` if successful.
88/// - `Err(BridgeError)` if any error occurs during address generation.
89pub fn generate_replacement_deposit_address(
90    old_move_txid: bitcoin::Txid,
91    nofn_xonly_pk: XOnlyPublicKey,
92    network: bitcoin::Network,
93    security_council: SecurityCouncil,
94) -> Result<(Address, TaprootSpendInfo), BridgeError> {
95    let deposit_script =
96        ReplacementDepositScript::new(nofn_xonly_pk, old_move_txid).to_script_buf();
97
98    let security_council_script = Multisig::from_security_council(security_council).to_script_buf();
99
100    let (addr, spend) =
101        create_taproot_address(&[deposit_script, security_council_script], None, network);
102    Ok((addr, spend))
103}
104
105#[cfg(test)]
106mod tests {
107    use crate::{
108        bitvm_client::{self, SECP},
109        builder::{self, address::calculate_taproot_leaf_depths},
110    };
111    use bitcoin::secp256k1::rand;
112    use bitcoin::{
113        key::{Keypair, TapTweak},
114        secp256k1::SecretKey,
115        AddressType, ScriptBuf, XOnlyPublicKey,
116    };
117
118    #[test]
119    fn create_taproot_address() {
120        let secret_key = SecretKey::new(&mut rand::thread_rng());
121        let internal_key =
122            XOnlyPublicKey::from_keypair(&Keypair::from_secret_key(&SECP, &secret_key)).0;
123
124        // No internal key or scripts (key path spend).
125        let (address, spend_info) =
126            builder::address::create_taproot_address(&[], None, bitcoin::Network::Regtest);
127        assert_eq!(address.address_type().unwrap(), AddressType::P2tr);
128        assert!(address.is_related_to_xonly_pubkey(
129            &bitvm_client::UNSPENDABLE_XONLY_PUBKEY
130                .tap_tweak(&SECP, spend_info.merkle_root())
131                .0
132                .to_x_only_public_key()
133        ));
134        assert_eq!(
135            spend_info.internal_key(),
136            *bitvm_client::UNSPENDABLE_XONLY_PUBKEY
137        );
138        assert!(spend_info.merkle_root().is_none());
139
140        // Key path spend.
141        let (address, spend_info) = builder::address::create_taproot_address(
142            &[],
143            Some(internal_key),
144            bitcoin::Network::Regtest,
145        );
146        assert_eq!(address.address_type().unwrap(), AddressType::P2tr);
147        assert!(address.is_related_to_xonly_pubkey(
148            &internal_key
149                .tap_tweak(&SECP, spend_info.merkle_root())
150                .0
151                .to_x_only_public_key()
152        ));
153        assert_eq!(spend_info.internal_key(), internal_key);
154        assert!(spend_info.merkle_root().is_none());
155
156        let scripts = [ScriptBuf::new()];
157        let (address, spend_info) = builder::address::create_taproot_address(
158            &scripts,
159            Some(internal_key),
160            bitcoin::Network::Regtest,
161        );
162        assert_eq!(address.address_type().unwrap(), AddressType::P2tr);
163        assert!(address.is_related_to_xonly_pubkey(
164            &internal_key
165                .tap_tweak(&SECP, spend_info.merkle_root())
166                .0
167                .to_x_only_public_key()
168        ));
169        assert_eq!(spend_info.internal_key(), internal_key);
170        assert!(spend_info.merkle_root().is_some());
171
172        let scripts = [ScriptBuf::new(), ScriptBuf::new()];
173        let (address, spend_info) = builder::address::create_taproot_address(
174            &scripts,
175            Some(internal_key),
176            bitcoin::Network::Regtest,
177        );
178        assert_eq!(address.address_type().unwrap(), AddressType::P2tr);
179        assert!(address.is_related_to_xonly_pubkey(
180            &internal_key
181                .tap_tweak(&SECP, spend_info.merkle_root())
182                .0
183                .to_x_only_public_key()
184        ));
185        assert_eq!(spend_info.internal_key(), internal_key);
186        assert!(spend_info.merkle_root().is_some());
187    }
188
189    #[test]
190    pub fn test_taproot_builder_with_scripts() {
191        for i in [0, 1, 10, 50, 100, 1000].into_iter() {
192            let scripts = (0..i)
193                .map(|k| ScriptBuf::builder().push_int(k).into_script())
194                .collect::<Vec<_>>();
195            let builder = super::taproot_builder_with_scripts(scripts);
196            let tree_info = builder
197                .finalize(&SECP, *bitvm_client::UNSPENDABLE_XONLY_PUBKEY)
198                .unwrap();
199
200            assert_eq!(tree_info.script_map().len(), i as usize);
201        }
202    }
203
204    #[test]
205    fn test_calculate_taproot_leaf_depths() {
206        // Test case 1: 0 scripts
207        let expected: Vec<u8> = vec![];
208        assert_eq!(calculate_taproot_leaf_depths(0), expected);
209
210        // Test case 2: 1 script
211        assert_eq!(calculate_taproot_leaf_depths(1), vec![0]);
212
213        // Test case 3: 2 scripts (balanced tree, depth 1 for both)
214        assert_eq!(calculate_taproot_leaf_depths(2), vec![1, 1]);
215
216        // Test case 4: 3 scripts (unbalanced)
217        // The first two scripts are at depth 2, the last is promoted to depth 1.
218        assert_eq!(calculate_taproot_leaf_depths(3), vec![2, 2, 1]);
219
220        // Test case 5: 4 scripts (perfectly balanced tree, all at depth 2)
221        assert_eq!(calculate_taproot_leaf_depths(4), vec![2, 2, 2, 2]);
222
223        // Test case 6: 5 scripts (unbalanced)
224        // num_nodes_in_final_depth is 2, so first two are at depth 3, rest are at depth 2.
225        // deepest_layer_depth = ilog2(4) + 1 = 3
226        // num_empty_nodes = 2^3 - 5 = 3
227        // num_nodes_in_final_depth = 5 - 3 = 2
228        // Depths: (3, 3, 2, 2, 2)
229        assert_eq!(calculate_taproot_leaf_depths(5), vec![3, 3, 2, 2, 2]);
230
231        // Test case 7: 8 scripts (perfectly balanced tree, all at depth 3)
232        assert_eq!(
233            calculate_taproot_leaf_depths(8),
234            vec![3, 3, 3, 3, 3, 3, 3, 3]
235        );
236    }
237}