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