1use std::{
6 hash::{Hash, Hasher},
7 str::FromStr,
8};
9
10pub use enum_dispatch::enum_dispatch;
11use fastcrypto::{
12 ed25519::Ed25519PublicKey,
13 encoding::{Base64, Encoding},
14 error::FastCryptoError,
15 hash::HashFunction,
16 secp256k1::Secp256k1PublicKey,
17 secp256r1::Secp256r1PublicKey,
18 traits::{EncodeDecodeBase64, ToFromBytes, VerifyingKey},
19};
20use iota_sdk_types::crypto::IntentMessage;
21use once_cell::sync::OnceCell;
22use serde::{Deserialize, Serialize};
23use serde_with::serde_as;
24
25use crate::{
26 base_types::IotaAddress,
27 crypto::{CompressedSignature, DefaultHash, PublicKey, SignatureScheme},
28 error::IotaError,
29 passkey_authenticator::PasskeyAuthenticator,
30 signature::{AuthenticatorTrait, GenericSignature, VerifyParams},
31};
32
33#[cfg(test)]
34#[path = "unit_tests/multisig_tests.rs"]
35mod multisig_tests;
36
37pub type WeightUnit = u8;
38pub type ThresholdUnit = u16;
39pub type BitmapUnit = u16;
40pub const MAX_SIGNER_IN_MULTISIG: usize = 10;
41pub const MAX_BITMAP_VALUE: BitmapUnit = 0b1111111111;
42#[serde_as]
45#[derive(Debug, Serialize, Deserialize, Clone)]
46pub struct MultiSig {
47 sigs: Vec<CompressedSignature>,
49 bitmap: BitmapUnit,
52 multisig_pk: MultiSigPublicKey,
55 #[serde(skip)]
58 bytes: OnceCell<Vec<u8>>,
59}
60
61impl PartialEq for MultiSig {
63 fn eq(&self, other: &Self) -> bool {
64 self.sigs == other.sigs
65 && self.bitmap == other.bitmap
66 && self.multisig_pk == other.multisig_pk
67 }
68}
69
70impl Eq for MultiSig {}
72
73impl Hash for MultiSig {
75 fn hash<H: Hasher>(&self, state: &mut H) {
76 self.as_ref().hash(state);
77 }
78}
79
80impl AuthenticatorTrait for MultiSig {
81 fn verify_claims<T>(
82 &self,
83 value: &IntentMessage<T>,
84 multisig_address: IotaAddress,
85 verify_params: &VerifyParams,
86 ) -> Result<(), IotaError>
87 where
88 T: Serialize,
89 {
90 self.multisig_pk
91 .validate()
92 .map_err(|_| IotaError::InvalidSignature {
93 error: "Invalid multisig pubkey".to_string(),
94 })?;
95
96 if IotaAddress::from(&self.multisig_pk) != multisig_address {
97 return Err(IotaError::InvalidSignature {
98 error: "Invalid address derived from pks".to_string(),
99 });
100 }
101
102 if self.has_passkey_sigs() && !verify_params.accept_passkey_in_multisig {
103 return Err(IotaError::InvalidSignature {
104 error: "Passkey sig not supported inside multisig".to_string(),
105 });
106 }
107
108 let mut weight_sum: u16 = 0;
109 let message = bcs::to_bytes(&value).expect("Message serialization should not fail");
110 let mut hasher = DefaultHash::default();
111 hasher.update(message);
112 let digest = hasher.finalize().digest;
113 for (sig, i) in self.sigs.iter().zip(as_indices(self.bitmap)?) {
117 let (subsig_pubkey, weight) =
118 self.multisig_pk
119 .pk_map
120 .get(i as usize)
121 .ok_or(IotaError::InvalidSignature {
122 error: "Invalid public keys index".to_string(),
123 })?;
124 let res = match sig {
125 CompressedSignature::Ed25519(s) => {
126 if verify_params.additional_multisig_checks
127 && !matches!(subsig_pubkey.scheme(), SignatureScheme::ED25519)
128 {
129 return Err(IotaError::InvalidSignature {
130 error: format!(
131 "Invalid sig for pk={} address={:?} error=signature/pubkey type mismatch",
132 subsig_pubkey.encode_base64(),
133 IotaAddress::from(subsig_pubkey)
134 ),
135 });
136 }
137 let pk =
138 Ed25519PublicKey::from_bytes(subsig_pubkey.as_ref()).map_err(|_| {
139 IotaError::InvalidSignature {
140 error: "Invalid ed25519 pk bytes".to_string(),
141 }
142 })?;
143 pk.verify(
144 &digest,
145 &s.try_into().map_err(|_| IotaError::InvalidSignature {
146 error: "Invalid ed25519 signature bytes".to_string(),
147 })?,
148 )
149 }
150 CompressedSignature::Secp256k1(s) => {
151 if verify_params.additional_multisig_checks
152 && !matches!(subsig_pubkey.scheme(), SignatureScheme::Secp256k1)
153 {
154 return Err(IotaError::InvalidSignature {
155 error: format!(
156 "Invalid sig for pk={} address={:?} error=signature/pubkey type mismatch",
157 subsig_pubkey.encode_base64(),
158 IotaAddress::from(subsig_pubkey)
159 ),
160 });
161 }
162 let pk =
163 Secp256k1PublicKey::from_bytes(subsig_pubkey.as_ref()).map_err(|_| {
164 IotaError::InvalidSignature {
165 error: "Invalid k1 pk bytes".to_string(),
166 }
167 })?;
168 pk.verify(
169 &digest,
170 &s.try_into().map_err(|_| IotaError::InvalidSignature {
171 error: "Invalid k1 signature bytes".to_string(),
172 })?,
173 )
174 }
175 CompressedSignature::Secp256r1(s) => {
176 if verify_params.additional_multisig_checks
177 && !matches!(subsig_pubkey.scheme(), SignatureScheme::Secp256r1)
178 {
179 return Err(IotaError::InvalidSignature {
180 error: format!(
181 "Invalid sig for pk={} address={:?} error=signature/pubkey type mismatch",
182 subsig_pubkey.encode_base64(),
183 IotaAddress::from(subsig_pubkey)
184 ),
185 });
186 }
187 let pk =
188 Secp256r1PublicKey::from_bytes(subsig_pubkey.as_ref()).map_err(|_| {
189 IotaError::InvalidSignature {
190 error: "Invalid r1 pk bytes".to_string(),
191 }
192 })?;
193 pk.verify(
194 &digest,
195 &s.try_into().map_err(|_| IotaError::InvalidSignature {
196 error: "Invalid r1 signature bytes".to_string(),
197 })?,
198 )
199 }
200 #[allow(deprecated)]
201 CompressedSignature::ZkLoginDeprecated => {
202 return Err(IotaError::InvalidSignature {
203 error: "zkLogin is not supported".to_string(),
204 });
205 }
206 CompressedSignature::Passkey(bytes) => {
207 let authenticator =
208 PasskeyAuthenticator::from_bytes(&bytes.0).map_err(|_| {
209 IotaError::InvalidSignature {
210 error: "Invalid passkey authenticator bytes".to_string(),
211 }
212 })?;
213 authenticator
214 .verify_claims(value, IotaAddress::from(subsig_pubkey), verify_params)
215 .map_err(|e| FastCryptoError::GeneralError(e.to_string()))
216 }
217 CompressedSignature::Move(_move_authenticator_as_bytes) => {
218 return Err(IotaError::InvalidSignature {
219 error: "Move authenticator cannot be used for multisig".to_string(),
220 });
221 }
222 };
223 if res.is_ok() {
224 weight_sum += *weight as u16;
225 } else {
226 return res.map_err(|e| IotaError::InvalidSignature {
227 error: format!(
228 "Invalid sig for pk={} address={:?} error={:?}",
229 subsig_pubkey.encode_base64(),
230 IotaAddress::from(subsig_pubkey),
231 e.to_string()
232 ),
233 });
234 }
235 }
236 if weight_sum >= self.multisig_pk.threshold {
237 Ok(())
238 } else {
239 Err(IotaError::InvalidSignature {
240 error: format!(
241 "Insufficient weight={:?} threshold={:?}",
242 weight_sum, self.multisig_pk.threshold
243 ),
244 })
245 }
246 }
247}
248
249pub fn as_indices(bitmap: u16) -> Result<Vec<u8>, IotaError> {
252 if bitmap > MAX_BITMAP_VALUE {
253 return Err(IotaError::InvalidSignature {
254 error: "Invalid bitmap".to_string(),
255 });
256 }
257 let mut res = Vec::new();
258 for i in 0..10 {
259 if bitmap & (1 << i) != 0 {
260 res.push(i as u8);
261 }
262 }
263 Ok(res)
264}
265
266impl MultiSig {
267 pub fn insecure_new(
269 sigs: Vec<CompressedSignature>,
270 bitmap: BitmapUnit,
271 multisig_pk: MultiSigPublicKey,
272 ) -> Self {
273 Self {
274 sigs,
275 bitmap,
276 multisig_pk,
277 bytes: OnceCell::new(),
278 }
279 }
280 pub fn combine(
286 full_sigs: Vec<GenericSignature>,
287 multisig_pk: MultiSigPublicKey,
288 ) -> Result<Self, IotaError> {
289 multisig_pk
290 .validate()
291 .map_err(|_| IotaError::InvalidSignature {
292 error: "Invalid multisig public key".to_string(),
293 })?;
294
295 if full_sigs.len() > multisig_pk.pk_map.len() || full_sigs.is_empty() {
296 return Err(IotaError::InvalidSignature {
297 error: "Invalid number of signatures".to_string(),
298 });
299 }
300 let mut bitmap = 0;
301 let mut sigs = Vec::with_capacity(full_sigs.len());
302 for s in full_sigs {
303 let pk = s.to_public_key()?;
304 let index = multisig_pk
305 .get_index(&pk)
306 .ok_or(IotaError::IncorrectSigner {
307 error: format!("pk does not exist: {pk:?}"),
308 })?;
309 if bitmap & (1 << index) != 0 {
310 return Err(IotaError::InvalidSignature {
311 error: "Duplicate public key".to_string(),
312 });
313 }
314 bitmap |= 1 << index;
315 sigs.push(s.to_compressed()?);
316 }
317
318 Ok(MultiSig {
319 sigs,
320 bitmap,
321 multisig_pk,
322 bytes: OnceCell::new(),
323 })
324 }
325
326 pub fn init_and_validate(&mut self) -> Result<Self, FastCryptoError> {
327 if self.sigs.len() > self.multisig_pk.pk_map.len()
328 || self.sigs.is_empty()
329 || self.bitmap > MAX_BITMAP_VALUE
330 {
331 return Err(FastCryptoError::InvalidInput);
332 }
333 self.multisig_pk.validate()?;
334 Ok(self.to_owned())
335 }
336
337 pub fn get_pk(&self) -> &MultiSigPublicKey {
338 &self.multisig_pk
339 }
340
341 pub fn get_sigs(&self) -> &[CompressedSignature] {
342 &self.sigs
343 }
344
345 pub fn get_indices(&self) -> Result<Vec<u8>, IotaError> {
346 as_indices(self.bitmap)
347 }
348
349 pub fn has_passkey_sigs(&self) -> bool {
350 self.sigs
351 .iter()
352 .any(|s| matches!(s, CompressedSignature::Passkey(_)))
353 }
354}
355
356impl ToFromBytes for MultiSig {
357 fn from_bytes(bytes: &[u8]) -> Result<MultiSig, FastCryptoError> {
358 if bytes.first().ok_or(FastCryptoError::InvalidInput)? != &SignatureScheme::MultiSig.flag()
360 {
361 return Err(FastCryptoError::InvalidInput);
362 }
363 let mut multisig: MultiSig =
364 bcs::from_bytes(&bytes[1..]).map_err(|_| FastCryptoError::InvalidSignature)?;
365 multisig.init_and_validate()
366 }
367}
368
369impl FromStr for MultiSig {
370 type Err = IotaError;
371
372 fn from_str(s: &str) -> Result<Self, Self::Err> {
373 let bytes = Base64::decode(s).map_err(|_| IotaError::InvalidSignature {
374 error: "Invalid base64 string".to_string(),
375 })?;
376 let sig = MultiSig::from_bytes(&bytes).map_err(|_| IotaError::InvalidSignature {
377 error: "Invalid multisig bytes".to_string(),
378 })?;
379 Ok(sig)
380 }
381}
382
383impl AsRef<[u8]> for MultiSig {
387 fn as_ref(&self) -> &[u8] {
388 self.bytes
389 .get_or_try_init::<_, eyre::Report>(|| {
390 let as_bytes = bcs::to_bytes(self).expect("BCS serialization should not fail");
391 let mut bytes = Vec::with_capacity(1 + as_bytes.len());
392 bytes.push(SignatureScheme::MultiSig.flag());
393 bytes.extend_from_slice(as_bytes.as_slice());
394 Ok(bytes)
395 })
396 .expect("OnceCell invariant violated")
397 }
398}
399
400#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
402pub struct MultiSigPublicKey {
403 pk_map: Vec<(PublicKey, WeightUnit)>,
405 threshold: ThresholdUnit,
408}
409
410impl MultiSigPublicKey {
411 pub fn insecure_new(pk_map: Vec<(PublicKey, WeightUnit)>, threshold: ThresholdUnit) -> Self {
413 Self { pk_map, threshold }
414 }
415
416 pub fn new(
417 pks: Vec<PublicKey>,
418 weights: Vec<WeightUnit>,
419 threshold: ThresholdUnit,
420 ) -> Result<Self, IotaError> {
421 if pks.is_empty()
422 || weights.is_empty()
423 || threshold == 0
424 || pks.len() != weights.len()
425 || pks.len() > MAX_SIGNER_IN_MULTISIG
426 || weights.contains(&0)
427 || weights
428 .iter()
429 .map(|w| *w as ThresholdUnit)
430 .sum::<ThresholdUnit>()
431 < threshold
432 || pks
433 .iter()
434 .enumerate()
435 .any(|(i, pk)| pks.iter().skip(i + 1).any(|other_pk| *pk == *other_pk))
436 {
437 return Err(IotaError::InvalidSignature {
438 error: "Invalid multisig public key construction".to_string(),
439 });
440 }
441
442 Ok(MultiSigPublicKey {
443 pk_map: pks.into_iter().zip(weights).collect(),
444 threshold,
445 })
446 }
447
448 pub fn get_index(&self, pk: &PublicKey) -> Option<u8> {
449 self.pk_map.iter().position(|x| &x.0 == pk).map(|x| x as u8)
450 }
451
452 pub fn threshold(&self) -> &ThresholdUnit {
453 &self.threshold
454 }
455
456 pub fn pubkeys(&self) -> &Vec<(PublicKey, WeightUnit)> {
457 &self.pk_map
458 }
459
460 pub fn validate(&self) -> Result<MultiSigPublicKey, FastCryptoError> {
461 let pk_map = self.pubkeys();
462 if self.threshold == 0
463 || pk_map.is_empty()
464 || pk_map.len() > MAX_SIGNER_IN_MULTISIG
465 || pk_map.iter().any(|(_pk, weight)| *weight == 0)
466 || pk_map
467 .iter()
468 .map(|(_pk, weight)| *weight as ThresholdUnit)
469 .sum::<ThresholdUnit>()
470 < self.threshold
471 || pk_map.iter().enumerate().any(|(i, (pk, _weight))| {
472 pk_map
473 .iter()
474 .skip(i + 1)
475 .any(|(other_pk, _weight)| *pk == *other_pk)
476 })
477 {
478 return Err(FastCryptoError::InvalidInput);
479 }
480 Ok(self.to_owned())
481 }
482}