rustywallet_multisig/
address.rs1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum Network {
12 Mainnet,
14 Testnet,
16}
17
18#[derive(Debug, Clone)]
20pub struct MultisigWallet {
21 pub config: MultisigConfig,
23 pub redeem_script: Vec<u8>,
25 pub address_p2sh: String,
27 pub address_p2wsh: String,
29 pub address_p2sh_p2wsh: String,
31 pub network: Network,
33}
34
35impl MultisigWallet {
36 pub fn new(config: MultisigConfig, network: Network) -> Result<Self> {
38 let redeem_script = build_multisig_script(&config);
39
40 let script_hash = hash160(&redeem_script);
42 let address_p2sh = encode_p2sh_address(&script_hash, network);
43
44 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 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 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 pub fn witness_script(&self) -> &[u8] {
76 &self.redeem_script
77 }
78
79 pub fn witness_script_hash(&self) -> [u8; 32] {
81 sha256(&self.redeem_script)
82 }
83
84 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
91pub 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
100pub 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
108fn 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 let checksum = double_sha256(&data);
121 data.extend_from_slice(&checksum[..4]);
122
123 bs58::encode(data).into_string()
124}
125
126fn 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 let mut data = Vec::with_capacity(33);
135 data.push(0); 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
142fn 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}