rustywallet_multisig/
address.rs

1//! Multisig address generation.
2
3use crate::config::MultisigConfig;
4use crate::error::{MultisigError, Result};
5use crate::script::{build_multisig_script, build_p2sh_p2wsh_redeem_script};
6use sha2::{Sha256, Digest};
7use ripemd::Ripemd160;
8
9/// Network for address generation.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum Network {
12    /// Bitcoin mainnet
13    Mainnet,
14    /// Bitcoin testnet
15    Testnet,
16}
17
18/// A complete multisig wallet with all address types.
19#[derive(Debug, Clone)]
20pub struct MultisigWallet {
21    /// The multisig configuration
22    pub config: MultisigConfig,
23    /// The redeem script (for P2SH)
24    pub redeem_script: Vec<u8>,
25    /// P2SH address (legacy, starts with 3 or 2)
26    pub address_p2sh: String,
27    /// P2WSH address (native SegWit, starts with bc1q or tb1q)
28    pub address_p2wsh: String,
29    /// P2SH-P2WSH address (nested SegWit, starts with 3 or 2)
30    pub address_p2sh_p2wsh: String,
31    /// Network
32    pub network: Network,
33}
34
35impl MultisigWallet {
36    /// Create a new multisig wallet from configuration.
37    pub fn new(config: MultisigConfig, network: Network) -> Result<Self> {
38        let redeem_script = build_multisig_script(&config);
39        
40        // P2SH address
41        let script_hash = hash160(&redeem_script);
42        let address_p2sh = encode_p2sh_address(&script_hash, network);
43
44        // P2WSH address (witness script = redeem script for multisig)
45        let witness_script_hash = sha256(&redeem_script);
46        let address_p2wsh = encode_p2wsh_address(&witness_script_hash, network)
47            .map_err(MultisigError::AddressFailed)?;
48
49        // P2SH-P2WSH address
50        let nested_redeem = build_p2sh_p2wsh_redeem_script(&witness_script_hash);
51        let nested_hash = hash160(&nested_redeem);
52        let address_p2sh_p2wsh = encode_p2sh_address(&nested_hash, network);
53
54        Ok(Self {
55            config,
56            redeem_script,
57            address_p2sh,
58            address_p2wsh,
59            address_p2sh_p2wsh,
60            network,
61        })
62    }
63
64    /// Create from public keys.
65    pub fn from_pubkeys(
66        threshold: u8,
67        public_keys: Vec<[u8; 33]>,
68        network: Network,
69    ) -> Result<Self> {
70        let config = MultisigConfig::new(threshold, public_keys)?;
71        Self::new(config, network)
72    }
73
74    /// Get the witness script (same as redeem script for P2WSH).
75    pub fn witness_script(&self) -> &[u8] {
76        &self.redeem_script
77    }
78
79    /// Get the witness script hash (for P2WSH).
80    pub fn witness_script_hash(&self) -> [u8; 32] {
81        sha256(&self.redeem_script)
82    }
83
84    /// Get the nested redeem script (for P2SH-P2WSH).
85    pub fn nested_redeem_script(&self) -> Vec<u8> {
86        let wsh = self.witness_script_hash();
87        build_p2sh_p2wsh_redeem_script(&wsh)
88    }
89}
90
91/// Compute HASH160 (SHA256 + RIPEMD160).
92pub fn hash160(data: &[u8]) -> [u8; 20] {
93    let sha = Sha256::digest(data);
94    let ripemd = Ripemd160::digest(sha);
95    let mut result = [0u8; 20];
96    result.copy_from_slice(&ripemd);
97    result
98}
99
100/// Compute SHA256.
101pub fn sha256(data: &[u8]) -> [u8; 32] {
102    let hash = Sha256::digest(data);
103    let mut result = [0u8; 32];
104    result.copy_from_slice(&hash);
105    result
106}
107
108/// Encode P2SH address (base58check).
109fn encode_p2sh_address(script_hash: &[u8; 20], network: Network) -> String {
110    let version = match network {
111        Network::Mainnet => 0x05,
112        Network::Testnet => 0xc4,
113    };
114
115    let mut data = Vec::with_capacity(25);
116    data.push(version);
117    data.extend_from_slice(script_hash);
118
119    // Checksum
120    let checksum = double_sha256(&data);
121    data.extend_from_slice(&checksum[..4]);
122
123    bs58::encode(data).into_string()
124}
125
126/// Encode P2WSH address (bech32).
127fn encode_p2wsh_address(script_hash: &[u8; 32], network: Network) -> std::result::Result<String, String> {
128    let hrp = match network {
129        Network::Mainnet => bech32::Hrp::parse("bc").unwrap(),
130        Network::Testnet => bech32::Hrp::parse("tb").unwrap(),
131    };
132
133    // Witness version 0 + 32-byte hash
134    let mut data = Vec::with_capacity(33);
135    data.push(0); // witness version
136    data.extend_from_slice(script_hash);
137
138    bech32::segwit::encode(hrp, bech32::segwit::VERSION_0, script_hash)
139        .map_err(|e| e.to_string())
140}
141
142/// Double SHA256.
143fn double_sha256(data: &[u8]) -> [u8; 32] {
144    let first = Sha256::digest(data);
145    let second = Sha256::digest(first);
146    let mut result = [0u8; 32];
147    result.copy_from_slice(&second);
148    result
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    fn make_pubkey(seed: u8) -> [u8; 33] {
156        let mut key = [seed; 33];
157        key[0] = 0x02;
158        key
159    }
160
161    #[test]
162    fn test_create_2_of_3_wallet() {
163        let keys = vec![make_pubkey(1), make_pubkey(2), make_pubkey(3)];
164        let wallet = MultisigWallet::from_pubkeys(2, keys, Network::Mainnet).unwrap();
165
166        assert!(wallet.address_p2sh.starts_with('3'));
167        assert!(wallet.address_p2wsh.starts_with("bc1q"));
168        assert!(wallet.address_p2sh_p2wsh.starts_with('3'));
169    }
170
171    #[test]
172    fn test_testnet_addresses() {
173        let keys = vec![make_pubkey(1), make_pubkey(2), make_pubkey(3)];
174        let wallet = MultisigWallet::from_pubkeys(2, keys, Network::Testnet).unwrap();
175
176        assert!(wallet.address_p2sh.starts_with('2'));
177        assert!(wallet.address_p2wsh.starts_with("tb1q"));
178        assert!(wallet.address_p2sh_p2wsh.starts_with('2'));
179    }
180
181    #[test]
182    fn test_redeem_script_not_empty() {
183        let keys = vec![make_pubkey(1), make_pubkey(2)];
184        let wallet = MultisigWallet::from_pubkeys(2, keys, Network::Mainnet).unwrap();
185
186        assert!(!wallet.redeem_script.is_empty());
187        assert!(!wallet.witness_script().is_empty());
188    }
189
190    #[test]
191    fn test_hash160() {
192        let data = b"hello";
193        let hash = hash160(data);
194        assert_eq!(hash.len(), 20);
195    }
196
197    #[test]
198    fn test_sha256() {
199        let data = b"hello";
200        let hash = sha256(data);
201        assert_eq!(hash.len(), 32);
202    }
203}