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, CheckSig, Multisig, ReplacementDepositScript, SpendableScript,
9    TimelockScript,
10};
11use crate::bitvm_client::SECP;
12use crate::deposit::SecurityCouncil;
13use crate::errors::BridgeError;
14use crate::utils::ScriptBufExt;
15use crate::{bitvm_client, EVMAddress};
16use bitcoin::address::NetworkUnchecked;
17use bitcoin::{
18    secp256k1::XOnlyPublicKey,
19    taproot::{TaprootBuilder, TaprootSpendInfo},
20    Address, ScriptBuf,
21};
22
23use eyre::Context;
24
25/// A helper to construct a `TaprootBuilder` from a slice of script buffers, forming the script tree.
26/// Finds the needed depth the script tree needs to be to fit all the scripts and inserts the scripts.
27pub fn taproot_builder_with_scripts(scripts: impl Into<Vec<ScriptBuf>>) -> TaprootBuilder {
28    // doesn't clone if its already an owned Vec
29    let mut scripts: Vec<ScriptBuf> = scripts.into();
30    let builder = TaprootBuilder::new();
31    let num_scripts = scripts.len();
32
33    // Special return cases for n = 0 or n = 1
34    match num_scripts {
35        0 => return builder,
36        1 => {
37            return builder
38                .add_leaf(0, scripts.remove(0))
39                .expect("one root leaf added on empty builder")
40        }
41        _ => {}
42    }
43
44    let deepest_layer_depth: u8 = ((num_scripts - 1).ilog2() + 1) as u8;
45
46    let num_empty_nodes_in_final_depth = 2_usize.pow(deepest_layer_depth.into()) - num_scripts;
47    let num_nodes_in_final_depth = num_scripts - num_empty_nodes_in_final_depth;
48
49    scripts
50        .into_iter()
51        .enumerate()
52        .fold(builder, |acc, (i, script)| {
53            let is_node_in_last_minus_one_depth = (i >= num_nodes_in_final_depth) as u8;
54
55            acc.add_leaf(
56                deepest_layer_depth - is_node_in_last_minus_one_depth,
57                script,
58            )
59            .expect("algorithm tested to be correct")
60        })
61}
62
63/// Calculates the depth of each leaf in a balanced Taproot tree structure.
64/// The returned Vec contains the depth for each script at the corresponding index.
65pub fn calculate_taproot_leaf_depths(num_scripts: usize) -> Vec<u8> {
66    match num_scripts {
67        0 => return vec![],
68        1 => return vec![0],
69        _ => {}
70    }
71
72    let deepest_layer_depth: u8 = ((num_scripts - 1).ilog2() + 1) as u8;
73
74    let num_empty_nodes_in_final_depth = 2_usize.pow(deepest_layer_depth.into()) - num_scripts;
75    let num_nodes_in_final_depth = num_scripts - num_empty_nodes_in_final_depth;
76
77    (0..num_scripts)
78        .map(|i| {
79            let is_node_in_last_minus_one_depth = (i >= num_nodes_in_final_depth) as u8;
80            deepest_layer_depth - is_node_in_last_minus_one_depth
81        })
82        .collect()
83}
84
85/// Creates a taproot address with given scripts and internal key.
86///
87/// # Arguments
88///
89/// - `scripts`: If empty, it is most likely a key path spend address
90/// - `internal_key`: If not given, will be defaulted to an unspendable x-only public key
91/// - `network`: Bitcoin network
92/// - If both `scripts` and `internal_key` are given, it means one can spend using both script and key path.
93/// - If none given, it is an unspendable address.
94///
95/// # Returns
96///
97/// - [`Address`]: Generated taproot address
98/// - [`TaprootSpendInfo`]: Taproot spending information
99///
100/// # Panics
101///
102/// Will panic if some of the operations have invalid parameters.
103pub fn create_taproot_address(
104    scripts: &[ScriptBuf],
105    internal_key: Option<XOnlyPublicKey>,
106    network: bitcoin::Network,
107) -> (Address, TaprootSpendInfo) {
108    // Build script tree
109    let taproot_builder = taproot_builder_with_scripts(scripts);
110    // Finalize the tree
111    let tree_info = match internal_key {
112        Some(xonly_pk) => taproot_builder
113            .finalize(&SECP, xonly_pk)
114            .expect("builder return is finalizable"),
115        None => taproot_builder
116            .finalize(&SECP, *bitvm_client::UNSPENDABLE_XONLY_PUBKEY)
117            .expect("builder return is finalizable"),
118    };
119
120    // Create the address
121    let taproot_address: Address = Address::p2tr_tweaked(tree_info.output_key(), network);
122
123    (taproot_address, tree_info)
124}
125
126/// Generates a deposit address for the user. Funds can be spent by N-of-N or
127/// user can take after specified time should the deposit fail.
128///
129/// # Parameters
130///
131/// - `nofn_xonly_pk`: N-of-N x-only public key of the depositor
132/// - `recovery_taproot_address`: User's x-only public key that can be used to
133///   take funds after some time
134/// - `user_evm_address`: User's EVM address.
135/// - `amount`: Amount to deposit
136/// - `network`: Bitcoin network to work on
137/// - `user_takes_after`: User can take the funds back, after this amounts of
138///   blocks have passed
139///
140/// # Returns
141///
142/// - [`Address`]: Deposit taproot Bitcoin address
143/// - [`TaprootSpendInfo`]: Deposit address's taproot spending information
144///
145/// # Panics
146///
147/// Panics if given parameters are malformed.
148pub fn generate_deposit_address(
149    nofn_xonly_pk: XOnlyPublicKey,
150    recovery_taproot_address: &Address<NetworkUnchecked>,
151    user_evm_address: EVMAddress,
152    network: bitcoin::Network,
153    user_takes_after: u16,
154) -> Result<(Address, TaprootSpendInfo), BridgeError> {
155    let deposit_script = BaseDepositScript::new(nofn_xonly_pk, user_evm_address).to_script_buf();
156
157    let recovery_script_pubkey = recovery_taproot_address
158        .clone()
159        .assume_checked()
160        .script_pubkey();
161
162    let recovery_extracted_xonly_pk = recovery_script_pubkey
163        .try_get_taproot_pk()
164        .wrap_err("Recovery taproot address is not a valid taproot address")?;
165
166    let script_timelock =
167        TimelockScript::new(Some(recovery_extracted_xonly_pk), user_takes_after).to_script_buf();
168
169    let (addr, spend) = create_taproot_address(&[deposit_script, script_timelock], None, network);
170    Ok((addr, spend))
171}
172
173/// Builds a Taproot address specifically for replacement deposits.
174/// Replacement deposits are to replace old move_to_vault transactions in case any issue is found on the bridge.
175/// This address incorporates a script committing to an old move transaction ID
176/// and a multisig script for the security council.
177/// This replacement deposit address will be used to create a new deposit transaction, which will then be used to
178/// sign the new related bridge deposit tx's.
179///
180/// # Parameters
181///
182/// - `old_move_txid`: The `Txid` of the old move_to_vault transaction that is being replaced.
183/// - `nofn_xonly_pk`: The N-of-N XOnlyPublicKey for the deposit.
184/// - `network`: The Bitcoin network on which the address will be used.
185/// - `security_council`: The `SecurityCouncil` configuration for the multisig script.
186///
187/// # Returns
188///
189/// - `Ok((Address, TaprootSpendInfo))` containing the new replacement deposit address
190///   and its associated `TaprootSpendInfo` if successful.
191/// - `Err(BridgeError)` if any error occurs during address generation.
192pub fn generate_replacement_deposit_address(
193    old_move_txid: bitcoin::Txid,
194    nofn_xonly_pk: XOnlyPublicKey,
195    network: bitcoin::Network,
196    security_council: SecurityCouncil,
197) -> Result<(Address, TaprootSpendInfo), BridgeError> {
198    let deposit_script =
199        ReplacementDepositScript::new(nofn_xonly_pk, old_move_txid).to_script_buf();
200
201    let security_council_script = Multisig::from_security_council(security_council).to_script_buf();
202
203    let (addr, spend) =
204        create_taproot_address(&[deposit_script, security_council_script], None, network);
205    Ok((addr, spend))
206}
207
208/// Shorthand function for creating a checksig taproot address: A single checksig script with the given xonly PK and no internal key.
209///
210/// # Returns
211///
212/// See [`create_taproot_address`].
213///
214/// - [`Address`]: Checksig taproot Bitcoin address
215/// - [`TaprootSpendInfo`]: Checksig address's taproot spending information
216pub fn create_checksig_address(
217    xonly_pk: XOnlyPublicKey,
218    network: bitcoin::Network,
219) -> (Address, TaprootSpendInfo) {
220    let script = CheckSig::new(xonly_pk);
221    create_taproot_address(&[script.to_script_buf()], None, network)
222}
223
224#[cfg(test)]
225mod tests {
226    use crate::{
227        bitvm_client::{self, SECP},
228        builder::{self, address::calculate_taproot_leaf_depths},
229    };
230    use bitcoin::secp256k1::rand;
231    use bitcoin::{
232        key::{Keypair, TapTweak},
233        secp256k1::SecretKey,
234        AddressType, ScriptBuf, XOnlyPublicKey,
235    };
236
237    #[test]
238    fn create_taproot_address() {
239        let secret_key = SecretKey::new(&mut rand::thread_rng());
240        let internal_key =
241            XOnlyPublicKey::from_keypair(&Keypair::from_secret_key(&SECP, &secret_key)).0;
242
243        // No internal key or scripts (key path spend).
244        let (address, spend_info) =
245            builder::address::create_taproot_address(&[], None, bitcoin::Network::Regtest);
246        assert_eq!(address.address_type().unwrap(), AddressType::P2tr);
247        assert!(address.is_related_to_xonly_pubkey(
248            &bitvm_client::UNSPENDABLE_XONLY_PUBKEY
249                .tap_tweak(&SECP, spend_info.merkle_root())
250                .0
251                .to_x_only_public_key()
252        ));
253        assert_eq!(
254            spend_info.internal_key(),
255            *bitvm_client::UNSPENDABLE_XONLY_PUBKEY
256        );
257        assert!(spend_info.merkle_root().is_none());
258
259        // Key path spend.
260        let (address, spend_info) = builder::address::create_taproot_address(
261            &[],
262            Some(internal_key),
263            bitcoin::Network::Regtest,
264        );
265        assert_eq!(address.address_type().unwrap(), AddressType::P2tr);
266        assert!(address.is_related_to_xonly_pubkey(
267            &internal_key
268                .tap_tweak(&SECP, spend_info.merkle_root())
269                .0
270                .to_x_only_public_key()
271        ));
272        assert_eq!(spend_info.internal_key(), internal_key);
273        assert!(spend_info.merkle_root().is_none());
274
275        let scripts = [ScriptBuf::new()];
276        let (address, spend_info) = builder::address::create_taproot_address(
277            &scripts,
278            Some(internal_key),
279            bitcoin::Network::Regtest,
280        );
281        assert_eq!(address.address_type().unwrap(), AddressType::P2tr);
282        assert!(address.is_related_to_xonly_pubkey(
283            &internal_key
284                .tap_tweak(&SECP, spend_info.merkle_root())
285                .0
286                .to_x_only_public_key()
287        ));
288        assert_eq!(spend_info.internal_key(), internal_key);
289        assert!(spend_info.merkle_root().is_some());
290
291        let scripts = [ScriptBuf::new(), ScriptBuf::new()];
292        let (address, spend_info) = builder::address::create_taproot_address(
293            &scripts,
294            Some(internal_key),
295            bitcoin::Network::Regtest,
296        );
297        assert_eq!(address.address_type().unwrap(), AddressType::P2tr);
298        assert!(address.is_related_to_xonly_pubkey(
299            &internal_key
300                .tap_tweak(&SECP, spend_info.merkle_root())
301                .0
302                .to_x_only_public_key()
303        ));
304        assert_eq!(spend_info.internal_key(), internal_key);
305        assert!(spend_info.merkle_root().is_some());
306    }
307
308    #[test]
309    pub fn test_taproot_builder_with_scripts() {
310        for i in [0, 1, 10, 50, 100, 1000].into_iter() {
311            let scripts = (0..i)
312                .map(|k| ScriptBuf::builder().push_int(k).into_script())
313                .collect::<Vec<_>>();
314            let builder = super::taproot_builder_with_scripts(scripts);
315            let tree_info = builder
316                .finalize(&SECP, *bitvm_client::UNSPENDABLE_XONLY_PUBKEY)
317                .unwrap();
318
319            assert_eq!(tree_info.script_map().len(), i as usize);
320        }
321    }
322
323    #[test]
324    fn test_calculate_taproot_leaf_depths() {
325        // Test case 1: 0 scripts
326        let expected: Vec<u8> = vec![];
327        assert_eq!(calculate_taproot_leaf_depths(0), expected);
328
329        // Test case 2: 1 script
330        assert_eq!(calculate_taproot_leaf_depths(1), vec![0]);
331
332        // Test case 3: 2 scripts (balanced tree, depth 1 for both)
333        assert_eq!(calculate_taproot_leaf_depths(2), vec![1, 1]);
334
335        // Test case 4: 3 scripts (unbalanced)
336        // The first two scripts are at depth 2, the last is promoted to depth 1.
337        assert_eq!(calculate_taproot_leaf_depths(3), vec![2, 2, 1]);
338
339        // Test case 5: 4 scripts (perfectly balanced tree, all at depth 2)
340        assert_eq!(calculate_taproot_leaf_depths(4), vec![2, 2, 2, 2]);
341
342        // Test case 6: 5 scripts (unbalanced)
343        // num_nodes_in_final_depth is 2, so first two are at depth 3, rest are at depth 2.
344        // deepest_layer_depth = ilog2(4) + 1 = 3
345        // num_empty_nodes = 2^3 - 5 = 3
346        // num_nodes_in_final_depth = 5 - 3 = 2
347        // Depths: (3, 3, 2, 2, 2)
348        assert_eq!(calculate_taproot_leaf_depths(5), vec![3, 3, 2, 2, 2]);
349
350        // Test case 7: 8 scripts (perfectly balanced tree, all at depth 3)
351        assert_eq!(
352            calculate_taproot_leaf_depths(8),
353            vec![3, 3, 3, 3, 3, 3, 3, 3]
354        );
355    }
356}