rustywallet_multisig/
config.rs

1//! Multisig configuration.
2
3use crate::error::{MultisigError, Result};
4
5/// Maximum number of keys in standard multisig.
6pub const MAX_MULTISIG_KEYS: usize = 15;
7
8/// Configuration for a multisig wallet.
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct MultisigConfig {
11    /// Required signatures (M)
12    threshold: u8,
13    /// Total keys (N)
14    total: u8,
15    /// Sorted compressed public keys (33 bytes each)
16    public_keys: Vec<[u8; 33]>,
17}
18
19impl MultisigConfig {
20    /// Create a new multisig configuration.
21    ///
22    /// Public keys will be sorted lexicographically (BIP67).
23    ///
24    /// # Arguments
25    /// * `threshold` - Number of required signatures (M)
26    /// * `public_keys` - List of compressed public keys
27    ///
28    /// # Errors
29    /// Returns error if:
30    /// - threshold is 0 or greater than number of keys
31    /// - more than 15 keys provided
32    /// - duplicate keys found
33    pub fn new(threshold: u8, public_keys: Vec<[u8; 33]>) -> Result<Self> {
34        let n = public_keys.len();
35
36        // Validate threshold
37        if threshold == 0 || threshold as usize > n {
38            return Err(MultisigError::InvalidThreshold {
39                m: threshold,
40                n: n as u8,
41            });
42        }
43
44        // Validate key count
45        if n > MAX_MULTISIG_KEYS {
46            return Err(MultisigError::TooManyKeys { count: n });
47        }
48
49        if n == 0 {
50            return Err(MultisigError::NotEnoughKeys { need: 1, got: 0 });
51        }
52
53        // Sort keys (BIP67)
54        let mut sorted_keys = public_keys;
55        sorted_keys.sort();
56
57        // Check for duplicates
58        for i in 1..sorted_keys.len() {
59            if sorted_keys[i] == sorted_keys[i - 1] {
60                return Err(MultisigError::DuplicateKey { index: i });
61            }
62        }
63
64        Ok(Self {
65            threshold,
66            total: n as u8,
67            public_keys: sorted_keys,
68        })
69    }
70
71    /// Get the threshold (M).
72    pub fn threshold(&self) -> u8 {
73        self.threshold
74    }
75
76    /// Get the total number of keys (N).
77    pub fn total(&self) -> u8 {
78        self.total
79    }
80
81    /// Get the sorted public keys.
82    pub fn public_keys(&self) -> &[[u8; 33]] {
83        &self.public_keys
84    }
85
86    /// Get the M-of-N description.
87    pub fn description(&self) -> String {
88        format!("{}-of-{}", self.threshold, self.total)
89    }
90
91    /// Check if a public key is part of this multisig.
92    pub fn contains_key(&self, pubkey: &[u8; 33]) -> bool {
93        self.public_keys.contains(pubkey)
94    }
95
96    /// Get the index of a public key (if present).
97    pub fn key_index(&self, pubkey: &[u8; 33]) -> Option<usize> {
98        self.public_keys.iter().position(|k| k == pubkey)
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    fn make_pubkey(seed: u8) -> [u8; 33] {
107        let mut key = [seed; 33];
108        key[0] = 0x02; // Compressed prefix
109        key
110    }
111
112    #[test]
113    fn test_valid_2_of_3() {
114        let keys = vec![make_pubkey(1), make_pubkey(2), make_pubkey(3)];
115        let config = MultisigConfig::new(2, keys).unwrap();
116        assert_eq!(config.threshold(), 2);
117        assert_eq!(config.total(), 3);
118        assert_eq!(config.description(), "2-of-3");
119    }
120
121    #[test]
122    fn test_keys_sorted() {
123        let keys = vec![make_pubkey(3), make_pubkey(1), make_pubkey(2)];
124        let config = MultisigConfig::new(2, keys).unwrap();
125        
126        // Keys should be sorted
127        assert!(config.public_keys()[0] < config.public_keys()[1]);
128        assert!(config.public_keys()[1] < config.public_keys()[2]);
129    }
130
131    #[test]
132    fn test_invalid_threshold_zero() {
133        let keys = vec![make_pubkey(1), make_pubkey(2)];
134        let result = MultisigConfig::new(0, keys);
135        assert!(matches!(result, Err(MultisigError::InvalidThreshold { .. })));
136    }
137
138    #[test]
139    fn test_invalid_threshold_too_high() {
140        let keys = vec![make_pubkey(1), make_pubkey(2)];
141        let result = MultisigConfig::new(3, keys);
142        assert!(matches!(result, Err(MultisigError::InvalidThreshold { .. })));
143    }
144
145    #[test]
146    fn test_too_many_keys() {
147        let keys: Vec<_> = (0..16).map(|i| make_pubkey(i)).collect();
148        let result = MultisigConfig::new(2, keys);
149        assert!(matches!(result, Err(MultisigError::TooManyKeys { .. })));
150    }
151
152    #[test]
153    fn test_duplicate_keys() {
154        let keys = vec![make_pubkey(1), make_pubkey(1), make_pubkey(2)];
155        let result = MultisigConfig::new(2, keys);
156        assert!(matches!(result, Err(MultisigError::DuplicateKey { .. })));
157    }
158
159    #[test]
160    fn test_contains_key() {
161        let keys = vec![make_pubkey(1), make_pubkey(2), make_pubkey(3)];
162        let config = MultisigConfig::new(2, keys).unwrap();
163        
164        assert!(config.contains_key(&make_pubkey(1)));
165        assert!(config.contains_key(&make_pubkey(2)));
166        assert!(!config.contains_key(&make_pubkey(99)));
167    }
168}