rustywallet_multisig/
config.rs1use crate::error::{MultisigError, Result};
4
5pub const MAX_MULTISIG_KEYS: usize = 15;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct MultisigConfig {
11 threshold: u8,
13 total: u8,
15 public_keys: Vec<[u8; 33]>,
17}
18
19impl MultisigConfig {
20 pub fn new(threshold: u8, public_keys: Vec<[u8; 33]>) -> Result<Self> {
34 let n = public_keys.len();
35
36 if threshold == 0 || threshold as usize > n {
38 return Err(MultisigError::InvalidThreshold {
39 m: threshold,
40 n: n as u8,
41 });
42 }
43
44 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 let mut sorted_keys = public_keys;
55 sorted_keys.sort();
56
57 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 pub fn threshold(&self) -> u8 {
73 self.threshold
74 }
75
76 pub fn total(&self) -> u8 {
78 self.total
79 }
80
81 pub fn public_keys(&self) -> &[[u8; 33]] {
83 &self.public_keys
84 }
85
86 pub fn description(&self) -> String {
88 format!("{}-of-{}", self.threshold, self.total)
89 }
90
91 pub fn contains_key(&self, pubkey: &[u8; 33]) -> bool {
93 self.public_keys.contains(pubkey)
94 }
95
96 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; 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 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}