redgold_keys/btc/
multisig_script.rs

1use bdk::bitcoin::blockdata::opcodes::all::*;
2use bdk::bitcoin::psbt::PartiallySignedTransaction;
3use bdk::bitcoin::secp256k1::{PublicKey, Secp256k1};
4use bdk::bitcoin::{Address, Network, Script, Transaction};
5use bdk::descriptor::Descriptor;
6use bdk::descriptor::DescriptorPublicKey;
7use std::collections::HashMap;
8use std::str::FromStr;
9use std::sync::{Arc, RwLock};
10
11// Structure to hold information about a participant in the multisig
12#[derive(Clone, Debug)]
13pub struct MultisigParticipant {
14    pub public_key: PublicKey,
15    pub weight: u32,  // For weighted multisig scenarios
16}
17
18// Multisig configuration
19#[derive(Clone, Debug)]
20pub struct MultisigConfig {
21    pub threshold: u32,
22    pub participants: Vec<MultisigParticipant>,
23    pub total_weight: u32,
24}
25
26impl MultisigConfig {
27    pub fn new(threshold: u32, participants: Vec<MultisigParticipant>) -> Self {
28        let total_weight = participants.iter().map(|p| p.weight).sum();
29        Self {
30            threshold,
31            participants,
32            total_weight,
33        }
34    }
35
36    // Validate that the configuration is valid
37    pub fn validate(&self) -> Result<(), String> {
38        if self.threshold > self.total_weight {
39            return Err("Threshold cannot be greater than total weight".to_string());
40        }
41        if self.participants.is_empty() {
42            return Err("Must have at least one participant".to_string());
43        }
44        Ok(())
45    }
46}
47
48// Structure for managing multisig spending paths
49#[derive(Debug)]
50pub struct MultisigSpendInfo {
51    pub script: Script,
52    pub spend_paths: HashMap<Script, (u32, Vec<MultisigParticipant>)>, // Script -> (required_weight, participants)
53}
54
55pub struct MultisigWallet {
56    pub config: MultisigConfig,
57    pub network: Network,
58    pub spend_info: MultisigSpendInfo,
59    signatures: Arc<RwLock<HashMap<PublicKey, Vec<u8>>>>,
60}
61
62impl MultisigWallet {
63
64    // Get the multisig address
65    pub fn get_address(&self) -> String {
66        // Create redeem script for multisig
67        let redeem_script = self.spend_info.script.clone();
68
69        // For P2WSH
70        let address = Address::p2wsh(&redeem_script, self.network).to_string();
71
72        address
73    }
74
75    pub fn new(config: MultisigConfig, network: Network) -> Result<Self, String> {
76        config.validate()?;
77
78        let secp = Secp256k1::new();
79        let mut spend_paths = HashMap::new();
80
81        // Create the base multisig script
82        let script = Self::create_multisig_script(&config);
83
84        // Store the script and its requirements
85        spend_paths.insert(
86            script.clone(),
87            (config.threshold, config.participants.clone())
88        );
89
90        let spend_info = MultisigSpendInfo {
91            script,
92            spend_paths,
93        };
94
95        Ok(Self {
96            config,
97            network,
98            spend_info,
99            signatures: Arc::new(RwLock::new(HashMap::new())),
100        })
101    }
102
103    // Create the base multisig script
104    fn create_multisig_script(config: &MultisigConfig) -> Script {
105        let mut script_content: Vec<u8> = Vec::new();
106
107        // Push OP_M (threshold)
108        script_content.push(config.threshold as u8 + 0x50); // Convert to OP_N
109
110        // Add each participant's public key
111        for participant in &config.participants {
112            let key_bytes = participant.public_key.serialize();
113            script_content.push(key_bytes.len() as u8);
114            script_content.extend_from_slice(&key_bytes);
115        }
116
117        // Push OP_N (total number of participants)
118        script_content.push(config.participants.len() as u8 + 0x50); // Convert to OP_N
119
120        // Add OP_CHECKMULTISIG
121        script_content.push(OP_CHECKMULTISIG.into_u8());
122
123        Script::new().into()
124    }
125
126    // Add a signature from a participant
127    pub fn add_signature(&self, pubkey: PublicKey, signature: Vec<u8>) -> Result<(), String> {
128        // Verify the signature belongs to a participant
129        if !self.config.participants.iter().any(|p| p.public_key == pubkey) {
130            return Err("Signature from unknown participant".to_string());
131        }
132
133        let mut sigs = self.signatures.write().map_err(|_| "Lock error")?;
134        sigs.insert(pubkey, signature);
135        Ok(())
136    }
137
138    // Check if we have enough signatures to complete the transaction
139    pub fn has_enough_signatures(&self) -> bool {
140        let sigs = self.signatures.read().unwrap();
141        let current_weight: u32 = self.config.participants
142            .iter()
143            .filter(|p| sigs.contains_key(&p.public_key))
144            .map(|p| p.weight)
145            .sum();
146
147        current_weight >= self.config.threshold
148    }
149
150    // Create a descriptor for the wallet
151    pub fn create_descriptor(&self) -> Result<Descriptor<DescriptorPublicKey>, String> {
152        let threshold = self.config.threshold as usize;
153
154        // Convert regular public keys to descriptor public keys
155        let keys: Result<Vec<DescriptorPublicKey>, _> = self.config.participants
156            .iter()
157            .map(|p| {
158                let key_str = format!("[{}]{}", "00000000/0'/0'/0']", p.public_key.to_string());
159                DescriptorPublicKey::from_str(&key_str)
160                    .map_err(|e| format!("Error converting key: {}", e))
161            })
162            .collect();
163
164        let keys = keys?;
165
166        let desc = format!("wsh(multi({},{}))",
167                           threshold,
168                           keys.iter()
169                               .map(|k| k.to_string())
170                               .collect::<Vec<_>>()
171                               .join(",")
172        );
173
174        Descriptor::from_str(&desc)
175            .map_err(|e| format!("Error creating descriptor: {}", e))
176    }
177
178    // Sign a transaction input
179    pub fn sign_input(
180        &self,
181        tx: &Transaction,
182        input_index: usize,
183        pubkey: PublicKey,
184        signature: Vec<u8>
185    ) -> Result<(), String> {
186        // Verify the signature belongs to a valid participant
187        self.add_signature(pubkey, signature)?;
188        Ok(())
189    }
190
191    // Finalize the transaction if we have enough signatures
192    pub fn finalize_transaction(&self, tx: Transaction) -> Result<Transaction, String> {
193        if !self.has_enough_signatures() {
194            return Err("Not enough signatures to finalize".to_string());
195        }
196        Ok(tx)
197    }
198    //
199    // // Create a new transaction with multiple outputs
200    // pub fn create_transaction(
201    //     &self,
202    //     utxos: Vec<(Transaction, u32)>, // Previous transactions and output indices to spend
203    //     destinations: Vec<(String, u64)>, // Destination addresses and amounts
204    //     fee_rate: f32, // Sats per vbyte
205    // ) -> Result<PartiallySignedTransaction, String> {
206    //
207    //     // Create transaction inputs from UTXOs
208    //     let inputs: Vec<TxIn> = utxos.iter()
209    //         .map(|(tx, vout)| TxIn {
210    //             previous_output: OutPoint {
211    //                 txid: tx.txid(),
212    //                 vout: *vout,
213    //             },
214    //             sequence: 0xFFFFFFFF,
215    //             witness: Vec::new(),
216    //             script_sig: Script::new(),
217    //         })
218    //         .collect();
219    //
220    //     // Create transaction outputs
221    //     let outputs: Result<Vec<TxOut>, String> = destinations.iter()
222    //         .map(|(addr_str, amount)| {
223    //             let addr = Address::from_str(addr_str)
224    //                 .map_err(|e| format!("Invalid address {}: {}", addr_str, e))?;
225    //             Ok(TxOut {
226    //                 value: *amount,
227    //                 script_pubkey: addr.script_pubkey(),
228    //             })
229    //         })
230    //         .collect();
231    //     let outputs = outputs?;
232    //
233    //     // Create unsigned transaction
234    //     let unsigned_tx = Transaction {
235    //         version: 2,
236    //         lock_time: 0,
237    //         input: inputs,
238    //         output: outputs,
239    //     };
240    //
241    //     // Create PSBT
242    //     let mut psbt = PartiallySignedTransaction::from_unsigned_tx(unsigned_tx)
243    //         .map_err(|e| format!("Error creating PSBT: {}", e))?;
244    //
245    //     // Add information about our inputs to the PSBT
246    //     for ((prev_tx, vout), psbt_input) in utxos.iter().zip(psbt.inputs.iter_mut()) {
247    //         // Add the full previous transaction
248    //         psbt_input.non_witness_utxo = Some(prev_tx.clone());
249    //
250    //         // Add the redeem script
251    //         psbt_input.witness_script = Some(self.spend_info.script.clone());
252    //     }
253    //
254    //     Ok(psbt)
255    // }
256    //
257    // // Add a signature to the PSBT
258    // pub fn add_signature_to_psbt(
259    //     &self,
260    //     mut psbt: PartiallySignedTransaction,
261    //     input_index: usize,
262    //     pubkey: PublicKey,
263    //     signature: Vec<u8>
264    // ) -> Result<PartiallySignedTransaction, String> {
265    //     if input_index >= psbt.inputs.len() {
266    //         return Err("Invalid input index".to_string());
267    //     }
268    //
269    //     let psbt_input = &mut psbt.inputs[input_index];
270    //
271    //     // Convert the raw signature to bitcoin ECDSA signature format
272    //     let sig = Signature::from_compact(&signature)
273    //         .map_err(|e| format!("Invalid signature: {}", e))?;
274    //
275    //     // Add to partial sigs
276    //     psbt_input.partial_sigs.insert(pubkey, sig);
277    //
278    //     Ok(psbt)
279    // }
280
281    // Finalize the PSBT into a transaction
282    pub fn finalize_psbt(
283        &self,
284        psbt: PartiallySignedTransaction
285    ) -> Result<Transaction, String> {
286        // Verify we have enough signatures
287        if !self.has_enough_signatures() {
288            return Err("Not enough signatures to finalize".to_string());
289        }
290
291        // Extract the final transaction
292        let tx = psbt.extract_tx();
293        Ok(tx)
294    }
295}
296
297// Example usage and tests
298#[cfg(test)]
299mod tests {
300    use super::*;
301    use bdk::bitcoin::secp256k1::rand::rngs::OsRng;
302
303    #[test]
304    fn test_multisig_creation() {
305        let secp = Secp256k1::new();
306        let mut rng = OsRng::default();
307
308        // Create some test participants
309        let participants = (0..3).map(|_| {
310            let (secret_key, public_key) = secp.generate_keypair(&mut rng);
311            MultisigParticipant {
312                public_key,
313                weight: 1,
314            }
315        }).collect();
316
317        let config = MultisigConfig::new(2, participants); // 2-of-3 multisig
318
319        let wallet = MultisigWallet::new(
320            config,
321            Network::Testnet
322        ).expect("Failed to create wallet");
323
324        assert!(!wallet.has_enough_signatures());
325    }
326
327    #[test]
328    fn test_descriptor_creation() {
329        let secp = Secp256k1::new();
330        let mut rng = OsRng::default();
331
332        // Create some test participants
333        let participants = (0..3).map(|_| {
334            let (secret_key, public_key) = secp.generate_keypair(&mut rng);
335            MultisigParticipant {
336                public_key,
337                weight: 1,
338            }
339        }).collect();
340
341        let config = MultisigConfig::new(2, participants); // 2-of-3 multisig
342        let wallet = MultisigWallet::new(config, Network::Testnet).expect("Failed to create wallet");
343
344        let descriptor = wallet.create_descriptor().expect("Failed to create descriptor");
345        println!("Created descriptor: {}", descriptor);
346    }
347}