onemoney_protocol/utils/
multisig.rs1use alloy_primitives::{Address, keccak256};
9use om_primitives_types::transaction::{B264, MultiSigSignatureEntry, Signature};
10use thiserror::Error;
11
12pub const DST_MULTISIG_ADDR_V1: &[u8] = b"MULTISIG_V1";
14
15#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct SignerConfig {
21 pub public_key: Vec<u8>,
23 pub weight: u8,
25}
26
27impl SignerConfig {
28 pub fn new(public_key: Vec<u8>, weight: u8) -> Result<Self, MultiSigError> {
37 if public_key.len() != 33 {
38 return Err(MultiSigError::InvalidPublicKeyLength {
39 expected: 33,
40 actual: public_key.len(),
41 });
42 }
43 if weight == 0 {
44 return Err(MultiSigError::InvalidWeight);
45 }
46 Ok(Self { public_key, weight })
47 }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub struct ThresholdConfig {
53 pub threshold: u16,
55}
56
57impl ThresholdConfig {
58 pub fn new(threshold: u16) -> Result<Self, MultiSigError> {
66 if threshold == 0 {
67 return Err(MultiSigError::InvalidThreshold);
68 }
69 Ok(Self { threshold })
70 }
71}
72
73#[derive(Debug, Error)]
75pub enum MultiSigError {
76 #[error("invalid public key length: expected {expected} bytes, got {actual} bytes")]
77 InvalidPublicKeyLength { expected: usize, actual: usize },
78
79 #[error("invalid weight: must be greater than 0")]
80 InvalidWeight,
81
82 #[error("invalid threshold: must be greater than 0")]
83 InvalidThreshold,
84
85 #[error("no signers provided")]
86 NoSigners,
87
88 #[error("threshold {threshold} exceeds total weight {total_weight}")]
89 ThresholdExceedsTotalWeight { threshold: u16, total_weight: u16 },
90
91 #[error("duplicate public key found")]
92 DuplicatePublicKey,
93
94 #[error("signature count mismatch: expected at least 1, got {count}")]
95 InsufficientSignatures { count: usize },
96}
97
98pub fn derive_multisig_address(
131 signers: &[SignerConfig],
132 threshold: &ThresholdConfig,
133) -> Result<Address, MultiSigError> {
134 if signers.is_empty() {
135 return Err(MultiSigError::NoSigners);
136 }
137
138 if threshold.threshold == 0 {
139 return Err(MultiSigError::InvalidThreshold);
140 }
141
142 for signer in signers {
143 if signer.weight == 0 {
144 return Err(MultiSigError::InvalidWeight);
145 }
146 if signer.public_key.len() != 33 {
147 return Err(MultiSigError::InvalidPublicKeyLength {
148 expected: 33,
149 actual: signer.public_key.len(),
150 });
151 }
152 }
153
154 let total_weight: u16 = signers.iter().map(|s| s.weight as u16).sum();
156 if threshold.threshold > total_weight {
157 return Err(MultiSigError::ThresholdExceedsTotalWeight {
158 threshold: threshold.threshold,
159 total_weight,
160 });
161 }
162
163 let mut sorted_signers: Vec<&SignerConfig> = signers.iter().collect();
165 sorted_signers.sort_by(|a, b| a.public_key.cmp(&b.public_key));
166
167 for i in 1..sorted_signers.len() {
169 if sorted_signers[i].public_key == sorted_signers[i - 1].public_key {
170 return Err(MultiSigError::DuplicatePublicKey);
171 }
172 }
173
174 let mut data = Vec::with_capacity(DST_MULTISIG_ADDR_V1.len() + signers.len() * 34 + 2);
177 data.extend_from_slice(DST_MULTISIG_ADDR_V1);
178 for signer in &sorted_signers {
179 data.extend_from_slice(&signer.public_key);
180 data.push(signer.weight);
181 }
182 data.extend_from_slice(&threshold.threshold.to_be_bytes());
183
184 let hash = keccak256(&data);
186 let mut address_bytes = [0u8; 20];
187 address_bytes.copy_from_slice(&hash[12..32]);
188
189 Ok(Address::from(address_bytes))
190}
191
192pub struct MultiSigSignatureCollector {
216 signatures: Vec<MultiSigSignatureEntry>,
217}
218
219impl MultiSigSignatureCollector {
220 pub fn new() -> Self {
222 Self { signatures: Vec::new() }
223 }
224
225 pub fn add_signature(&mut self, signer_pubkey: B264, signature: Signature) -> &mut Self {
234 self.signatures.push(MultiSigSignatureEntry {
235 signer_pubkey,
236 signature,
237 });
238 self
239 }
240
241 pub fn add_signatures(&mut self, mut signatures: Vec<MultiSigSignatureEntry>) -> &mut Self {
249 self.signatures.append(&mut signatures);
250 self
251 }
252
253 pub fn signature_count(&self) -> usize {
255 self.signatures.len()
256 }
257
258 pub fn signatures(self) -> Vec<MultiSigSignatureEntry> {
263 self.signatures
264 }
265
266 pub fn is_empty(&self) -> bool {
268 self.signatures.is_empty()
269 }
270}
271
272impl Default for MultiSigSignatureCollector {
273 fn default() -> Self {
274 Self::new()
275 }
276}
277
278#[cfg(test)]
279mod tests {
280 use super::*;
281
282 #[test]
283 fn test_signer_config_new_valid() {
284 let pubkey = vec![2; 33];
285 let signer = SignerConfig::new(pubkey.clone(), 1).unwrap();
286 assert_eq!(signer.public_key, pubkey);
287 assert_eq!(signer.weight, 1);
288 }
289
290 #[test]
291 fn test_signer_config_invalid_pubkey_length() {
292 let pubkey = vec![2; 32]; let result = SignerConfig::new(pubkey, 1);
294 assert!(matches!(result, Err(MultiSigError::InvalidPublicKeyLength { .. })));
295 }
296
297 #[test]
298 fn test_signer_config_zero_weight() {
299 let pubkey = vec![2; 33];
300 let result = SignerConfig::new(pubkey, 0);
301 assert!(matches!(result, Err(MultiSigError::InvalidWeight)));
302 }
303
304 #[test]
305 fn test_threshold_config_new_valid() {
306 let threshold = ThresholdConfig::new(2).unwrap();
307 assert_eq!(threshold.threshold, 2);
308 }
309
310 #[test]
311 fn test_threshold_config_zero() {
312 let result = ThresholdConfig::new(0);
313 assert!(matches!(result, Err(MultiSigError::InvalidThreshold)));
314 }
315
316 #[test]
317 fn test_derive_multisig_address_2_of_3() {
318 let signer1 = SignerConfig::new(vec![2; 33], 1).unwrap();
319 let signer2 = SignerConfig::new(vec![3; 33], 1).unwrap();
320 let signer3 = SignerConfig::new(vec![4; 33], 1).unwrap();
321 let threshold = ThresholdConfig::new(2).unwrap();
322
323 let address =
324 derive_multisig_address(&[signer1.clone(), signer2.clone(), signer3.clone()], &threshold).unwrap();
325
326 let address2 = derive_multisig_address(&[signer3, signer1, signer2], &threshold).unwrap();
328 assert_eq!(
329 address, address2,
330 "Address should be deterministic regardless of input order"
331 );
332 }
333
334 #[test]
335 fn test_derive_multisig_address_no_signers() {
336 let threshold = ThresholdConfig::new(1).unwrap();
337 let result = derive_multisig_address(&[], &threshold);
338 assert!(matches!(result, Err(MultiSigError::NoSigners)));
339 }
340
341 #[test]
342 fn test_derive_multisig_address_threshold_exceeds_weight() {
343 let signer1 = SignerConfig::new(vec![2; 33], 1).unwrap();
344 let signer2 = SignerConfig::new(vec![3; 33], 1).unwrap();
345 let threshold = ThresholdConfig::new(3).unwrap(); let result = derive_multisig_address(&[signer1, signer2], &threshold);
348 assert!(matches!(result, Err(MultiSigError::ThresholdExceedsTotalWeight { .. })));
349 }
350
351 #[test]
352 fn test_derive_multisig_address_duplicate_pubkey() {
353 let signer1 = SignerConfig::new(vec![2; 33], 1).unwrap();
354 let signer2 = SignerConfig::new(vec![2; 33], 2).unwrap(); let threshold = ThresholdConfig::new(2).unwrap();
356
357 let result = derive_multisig_address(&[signer1, signer2], &threshold);
358 assert!(matches!(result, Err(MultiSigError::DuplicatePublicKey)));
359 }
360
361 #[test]
362 fn test_multisig_collector() {
363 let mut collector = MultiSigSignatureCollector::new();
364
365 assert_eq!(collector.signature_count(), 0);
366 assert!(collector.is_empty());
367
368 let sig1 = Signature::test_signature();
369 let sig2 = Signature::test_signature();
370
371 collector.add_signature(B264::repeat_byte(2), sig1);
372 collector.add_signature(B264::repeat_byte(3), sig2);
373
374 assert_eq!(collector.signature_count(), 2);
375 assert!(!collector.is_empty());
376
377 let signatures = collector.signatures();
378 assert_eq!(signatures.len(), 2);
379 }
380
381 #[test]
382 fn test_multisig_collector_default() {
383 let collector = MultiSigSignatureCollector::default();
384 assert!(collector.is_empty());
385 assert_eq!(collector.signature_count(), 0);
386 }
387}