redgold_keys/btc/
multisig_script.rs1use 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#[derive(Clone, Debug)]
13pub struct MultisigParticipant {
14 pub public_key: PublicKey,
15 pub weight: u32, }
17
18#[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 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#[derive(Debug)]
50pub struct MultisigSpendInfo {
51 pub script: Script,
52 pub spend_paths: HashMap<Script, (u32, Vec<MultisigParticipant>)>, }
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 pub fn get_address(&self) -> String {
66 let redeem_script = self.spend_info.script.clone();
68
69 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 let script = Self::create_multisig_script(&config);
83
84 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 fn create_multisig_script(config: &MultisigConfig) -> Script {
105 let mut script_content: Vec<u8> = Vec::new();
106
107 script_content.push(config.threshold as u8 + 0x50); 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 script_content.push(config.participants.len() as u8 + 0x50); script_content.push(OP_CHECKMULTISIG.into_u8());
122
123 Script::new().into()
124 }
125
126 pub fn add_signature(&self, pubkey: PublicKey, signature: Vec<u8>) -> Result<(), String> {
128 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 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 pub fn create_descriptor(&self) -> Result<Descriptor<DescriptorPublicKey>, String> {
152 let threshold = self.config.threshold as usize;
153
154 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 pub fn sign_input(
180 &self,
181 tx: &Transaction,
182 input_index: usize,
183 pubkey: PublicKey,
184 signature: Vec<u8>
185 ) -> Result<(), String> {
186 self.add_signature(pubkey, signature)?;
188 Ok(())
189 }
190
191 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 pub fn finalize_psbt(
283 &self,
284 psbt: PartiallySignedTransaction
285 ) -> Result<Transaction, String> {
286 if !self.has_enough_signatures() {
288 return Err("Not enough signatures to finalize".to_string());
289 }
290
291 let tx = psbt.extract_tx();
293 Ok(tx)
294 }
295}
296
297#[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 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); 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 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); 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}