iota_types/
multisig.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use 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/// The struct that contains signatures and public keys necessary for
48/// authenticating a MultiSig.
49#[serde_as]
50#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)]
51pub struct MultiSig {
52    /// The plain signature encoded with signature scheme.
53    sigs: Vec<CompressedSignature>,
54    /// A bitmap that indicates the position of which public key the signature
55    /// should be authenticated with.
56    bitmap: BitmapUnit,
57    /// The public key encoded with each public key with its signature scheme
58    /// used along with the corresponding weight.
59    multisig_pk: MultiSigPublicKey,
60    /// A bytes representation of [struct MultiSig]. This helps with
61    /// implementing [trait AsRef<[u8]>].
62    #[serde(skip)]
63    bytes: OnceCell<Vec<u8>>,
64}
65
66/// Necessary trait for [struct SenderSignedData].
67impl 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
75/// Necessary trait for [struct SenderSignedData].
76impl Eq for MultiSig {}
77
78/// Necessary trait for [struct SenderSignedData].
79impl 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        // If there is any zkLogin signatures, filter and check epoch for each.
92        // TODO: call this on all sigs to avoid future lapses
93        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        // Verify each signature against its corresponding signature scheme and public
138        // key. TODO: further optimization can be done because multiple Ed25519
139        // signatures can be batch verified.
140        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
301/// Interpret a bitmap of 01s as a list of indices that is set to 1s.
302/// e.g. 22 = 0b10110, then the result is [1, 2, 4].
303pub 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    /// Create MultiSig from its fields without validation
320    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    /// This combines a list of [enum Signature] `flag || signature || pk` to a
333    /// MultiSig. The order of full_sigs must be the same as the order of
334    /// public keys in [enum MultiSigPublicKey]. e.g. for [pk1, pk2, pk3,
335    /// pk4, pk5], [sig1, sig2, sig5] is valid, but [sig2, sig1, sig5] is
336    /// invalid.
337    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        // The first byte matches the flag of MultiSig.
430        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
454/// This initialize the underlying bytes representation of MultiSig. It encodes
455/// [struct MultiSig] as the MultiSig flag (0x03) concat with the bcs bytes
456/// of [struct MultiSig] i.e. `flag || bcs_bytes(MultiSig)`.
457impl 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/// The struct that contains the public key used for authenticating a MultiSig.
472#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
473pub struct MultiSigPublicKey {
474    /// A list of public key and its corresponding weight.
475    pk_map: Vec<(PublicKey, WeightUnit)>,
476    /// If the total weight of the public keys corresponding to verified
477    /// signatures is larger than threshold, the MultiSig is verified.
478    threshold: ThresholdUnit,
479}
480
481impl MultiSigPublicKey {
482    /// Construct MultiSigPublicKey without validation.
483    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}