falcon_multisig/
threshold.rs1#[cfg(not(feature = "std"))]
11use alloc::{string::String, vec::Vec};
12
13use crate::{
14 address::MultisigAddress,
15 error::Error,
16 keypair::PublicKey,
17 PUBLIC_KEY_BYTES,
18};
19
20pub const MIN_COMMITTEE_SIZE: usize = 2;
22
23#[derive(Clone, Debug, PartialEq, Eq)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37pub struct ThresholdConfig {
38 required: usize,
40 public_keys: Vec<PublicKey>,
42 address: MultisigAddress,
44}
45
46impl ThresholdConfig {
47 pub fn new(required: usize, public_keys: Vec<PublicKey>) -> Result<Self, Error> {
61 let total = public_keys.len();
62
63 if total < MIN_COMMITTEE_SIZE {
64 return Err(Error::CommitteeTooSmall { count: total });
65 }
66 if required == 0 || required > total {
67 return Err(Error::InvalidThreshold {
68 required,
69 total_keys: total,
70 });
71 }
72
73 for (i, pk) in public_keys.iter().enumerate() {
75 if pk.as_bytes().len() != PUBLIC_KEY_BYTES {
76 return Err(Error::InvalidPublicKeyLength {
77 expected: PUBLIC_KEY_BYTES,
78 actual: pk.as_bytes().len(),
79 });
80 }
81 let _ = i; }
83
84 let address = MultisigAddress::derive(&public_keys, required, total);
85
86 Ok(Self {
87 required,
88 public_keys,
89 address,
90 })
91 }
92
93 pub fn required(&self) -> usize {
95 self.required
96 }
97
98 pub fn total(&self) -> usize {
100 self.public_keys.len()
101 }
102
103 pub fn policy(&self) -> String {
105 format!("{}-of-{}", self.required, self.public_keys.len())
106 }
107
108 pub fn public_keys(&self) -> &[PublicKey] {
110 &self.public_keys
111 }
112
113 pub fn address(&self) -> &MultisigAddress {
115 &self.address
116 }
117
118 pub fn get_public_key(&self, index: usize) -> Option<&PublicKey> {
122 self.public_keys.get(index)
123 }
124}
125
126#[cfg(test)]
131mod tests {
132 use super::*;
133 use crate::keypair::KeyPair;
134
135 fn make_keys(n: usize) -> Vec<PublicKey> {
136 (0..n)
137 .map(|_| KeyPair::generate().public_key().clone())
138 .collect()
139 }
140
141 #[test]
142 fn valid_2of3_config() {
143 let keys = make_keys(3);
144 let cfg = ThresholdConfig::new(2, keys).unwrap();
145 assert_eq!(cfg.required(), 2);
146 assert_eq!(cfg.total(), 3);
147 assert_eq!(cfg.policy(), "2-of-3");
148 }
149
150 #[test]
151 fn valid_3of5_config() {
152 let keys = make_keys(5);
153 let cfg = ThresholdConfig::new(3, keys).unwrap();
154 assert_eq!(cfg.policy(), "3-of-5");
155 }
156
157 #[test]
158 fn nof_n_is_valid() {
159 let keys = make_keys(4);
160 let cfg = ThresholdConfig::new(4, keys).unwrap();
161 assert_eq!(cfg.required(), 4);
162 }
163
164 #[test]
165 fn single_member_committee_rejected() {
166 let keys = make_keys(1);
167 let err = ThresholdConfig::new(1, keys).unwrap_err();
168 assert!(matches!(err, Error::CommitteeTooSmall { count: 1 }));
169 }
170
171 #[test]
172 fn zero_required_rejected() {
173 let keys = make_keys(3);
174 let err = ThresholdConfig::new(0, keys).unwrap_err();
175 assert!(matches!(err, Error::InvalidThreshold { required: 0, .. }));
176 }
177
178 #[test]
179 fn required_exceeds_total_rejected() {
180 let keys = make_keys(3);
181 let err = ThresholdConfig::new(4, keys).unwrap_err();
182 assert!(matches!(err, Error::InvalidThreshold { required: 4, total_keys: 3 }));
183 }
184
185 #[test]
186 fn address_is_deterministic_regardless_of_insertion_order() {
187 let kp0 = KeyPair::generate();
188 let kp1 = KeyPair::generate();
189 let kp2 = KeyPair::generate();
190
191 let order_a = vec![
192 kp0.public_key().clone(),
193 kp1.public_key().clone(),
194 kp2.public_key().clone(),
195 ];
196 let order_b = vec![
197 kp2.public_key().clone(),
198 kp0.public_key().clone(),
199 kp1.public_key().clone(),
200 ];
201
202 let cfg_a = ThresholdConfig::new(2, order_a).unwrap();
203 let cfg_b = ThresholdConfig::new(2, order_b).unwrap();
204
205 assert_eq!(
206 cfg_a.address(),
207 cfg_b.address(),
208 "multisig address must be insertion-order-independent"
209 );
210 }
211
212 #[test]
213 fn get_public_key_returns_correct_key() {
214 let keys = make_keys(3);
215 let expected = keys[1].clone();
216 let cfg = ThresholdConfig::new(2, keys).unwrap();
217 assert_eq!(cfg.get_public_key(1), Some(&expected));
218 }
219
220 #[test]
221 fn get_public_key_out_of_bounds_returns_none() {
222 let cfg = ThresholdConfig::new(2, make_keys(3)).unwrap();
223 assert!(cfg.get_public_key(99).is_none());
224 }
225}