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 once_cell::sync::OnceCell;
22use schemars::JsonSchema;
23use serde::{Deserialize, Serialize};
24use serde_with::serde_as;
25use shared_crypto::intent::IntentMessage;
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            };
270            if res.is_ok() {
271                weight_sum += *weight as u16;
272            } else {
273                return res.map_err(|e| IotaError::InvalidSignature {
274                    error: format!(
275                        "Invalid sig for pk={} address={:?} error={:?}",
276                        subsig_pubkey.encode_base64(),
277                        IotaAddress::from(subsig_pubkey),
278                        e.to_string()
279                    ),
280                });
281            }
282        }
283        if weight_sum >= self.multisig_pk.threshold {
284            Ok(())
285        } else {
286            Err(IotaError::InvalidSignature {
287                error: format!(
288                    "Insufficient weight={:?} threshold={:?}",
289                    weight_sum, self.multisig_pk.threshold
290                ),
291            })
292        }
293    }
294}
295
296/// Interpret a bitmap of 01s as a list of indices that is set to 1s.
297/// e.g. 22 = 0b10110, then the result is [1, 2, 4].
298pub fn as_indices(bitmap: u16) -> Result<Vec<u8>, IotaError> {
299    if bitmap > MAX_BITMAP_VALUE {
300        return Err(IotaError::InvalidSignature {
301            error: "Invalid bitmap".to_string(),
302        });
303    }
304    let mut res = Vec::new();
305    for i in 0..10 {
306        if bitmap & (1 << i) != 0 {
307            res.push(i as u8);
308        }
309    }
310    Ok(res)
311}
312
313impl MultiSig {
314    /// Create MultiSig from its fields without validation
315    pub fn insecure_new(
316        sigs: Vec<CompressedSignature>,
317        bitmap: BitmapUnit,
318        multisig_pk: MultiSigPublicKey,
319    ) -> Self {
320        Self {
321            sigs,
322            bitmap,
323            multisig_pk,
324            bytes: OnceCell::new(),
325        }
326    }
327    /// This combines a list of [enum Signature] `flag || signature || pk` to a
328    /// MultiSig. The order of full_sigs must be the same as the order of
329    /// public keys in [enum MultiSigPublicKey]. e.g. for [pk1, pk2, pk3,
330    /// pk4, pk5], [sig1, sig2, sig5] is valid, but [sig2, sig1, sig5] is
331    /// invalid.
332    pub fn combine(
333        full_sigs: Vec<GenericSignature>,
334        multisig_pk: MultiSigPublicKey,
335    ) -> Result<Self, IotaError> {
336        multisig_pk
337            .validate()
338            .map_err(|_| IotaError::InvalidSignature {
339                error: "Invalid multisig public key".to_string(),
340            })?;
341
342        if full_sigs.len() > multisig_pk.pk_map.len() || full_sigs.is_empty() {
343            return Err(IotaError::InvalidSignature {
344                error: "Invalid number of signatures".to_string(),
345            });
346        }
347        let mut bitmap = 0;
348        let mut sigs = Vec::with_capacity(full_sigs.len());
349        for s in full_sigs {
350            let pk = s.to_public_key()?;
351            let index = multisig_pk
352                .get_index(&pk)
353                .ok_or(IotaError::IncorrectSigner {
354                    error: format!("pk does not exist: {pk:?}"),
355                })?;
356            if bitmap & (1 << index) != 0 {
357                return Err(IotaError::InvalidSignature {
358                    error: "Duplicate public key".to_string(),
359                });
360            }
361            bitmap |= 1 << index;
362            sigs.push(s.to_compressed()?);
363        }
364
365        Ok(MultiSig {
366            sigs,
367            bitmap,
368            multisig_pk,
369            bytes: OnceCell::new(),
370        })
371    }
372
373    pub fn init_and_validate(&mut self) -> Result<Self, FastCryptoError> {
374        if self.sigs.len() > self.multisig_pk.pk_map.len()
375            || self.sigs.is_empty()
376            || self.bitmap > MAX_BITMAP_VALUE
377        {
378            return Err(FastCryptoError::InvalidInput);
379        }
380        self.multisig_pk.validate()?;
381        Ok(self.to_owned())
382    }
383
384    pub fn get_pk(&self) -> &MultiSigPublicKey {
385        &self.multisig_pk
386    }
387
388    pub fn get_sigs(&self) -> &[CompressedSignature] {
389        &self.sigs
390    }
391
392    pub fn get_zklogin_sigs(&self) -> Result<Vec<ZkLoginAuthenticator>, IotaError> {
393        let authenticator_as_bytes: Vec<_> = self
394            .sigs
395            .iter()
396            .filter_map(|s| match s {
397                CompressedSignature::ZkLogin(z) => Some(z),
398                _ => None,
399            })
400            .collect();
401        authenticator_as_bytes
402            .iter()
403            .map(|z| {
404                ZkLoginAuthenticator::from_bytes(&z.0).map_err(|_| IotaError::InvalidSignature {
405                    error: "Invalid zklogin authenticator bytes".to_string(),
406                })
407            })
408            .collect()
409    }
410
411    pub fn get_indices(&self) -> Result<Vec<u8>, IotaError> {
412        as_indices(self.bitmap)
413    }
414
415    pub fn has_passkey_sigs(&self) -> bool {
416        self.sigs
417            .iter()
418            .any(|s| matches!(s, CompressedSignature::Passkey(_)))
419    }
420}
421
422impl ToFromBytes for MultiSig {
423    fn from_bytes(bytes: &[u8]) -> Result<MultiSig, FastCryptoError> {
424        // The first byte matches the flag of MultiSig.
425        if bytes.first().ok_or(FastCryptoError::InvalidInput)? != &SignatureScheme::MultiSig.flag()
426        {
427            return Err(FastCryptoError::InvalidInput);
428        }
429        let mut multisig: MultiSig =
430            bcs::from_bytes(&bytes[1..]).map_err(|_| FastCryptoError::InvalidSignature)?;
431        multisig.init_and_validate()
432    }
433}
434
435impl FromStr for MultiSig {
436    type Err = IotaError;
437
438    fn from_str(s: &str) -> Result<Self, Self::Err> {
439        let bytes = Base64::decode(s).map_err(|_| IotaError::InvalidSignature {
440            error: "Invalid base64 string".to_string(),
441        })?;
442        let sig = MultiSig::from_bytes(&bytes).map_err(|_| IotaError::InvalidSignature {
443            error: "Invalid multisig bytes".to_string(),
444        })?;
445        Ok(sig)
446    }
447}
448
449/// This initialize the underlying bytes representation of MultiSig. It encodes
450/// [struct MultiSig] as the MultiSig flag (0x03) concat with the bcs bytes
451/// of [struct MultiSig] i.e. `flag || bcs_bytes(MultiSig)`.
452impl AsRef<[u8]> for MultiSig {
453    fn as_ref(&self) -> &[u8] {
454        self.bytes
455            .get_or_try_init::<_, eyre::Report>(|| {
456                let as_bytes = bcs::to_bytes(self).expect("BCS serialization should not fail");
457                let mut bytes = Vec::with_capacity(1 + as_bytes.len());
458                bytes.push(SignatureScheme::MultiSig.flag());
459                bytes.extend_from_slice(as_bytes.as_slice());
460                Ok(bytes)
461            })
462            .expect("OnceCell invariant violated")
463    }
464}
465
466/// The struct that contains the public key used for authenticating a MultiSig.
467#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
468pub struct MultiSigPublicKey {
469    /// A list of public key and its corresponding weight.
470    pk_map: Vec<(PublicKey, WeightUnit)>,
471    /// If the total weight of the public keys corresponding to verified
472    /// signatures is larger than threshold, the MultiSig is verified.
473    threshold: ThresholdUnit,
474}
475
476impl MultiSigPublicKey {
477    /// Construct MultiSigPublicKey without validation.
478    pub fn insecure_new(pk_map: Vec<(PublicKey, WeightUnit)>, threshold: ThresholdUnit) -> Self {
479        Self { pk_map, threshold }
480    }
481
482    pub fn new(
483        pks: Vec<PublicKey>,
484        weights: Vec<WeightUnit>,
485        threshold: ThresholdUnit,
486    ) -> Result<Self, IotaError> {
487        if pks.is_empty()
488            || weights.is_empty()
489            || threshold == 0
490            || pks.len() != weights.len()
491            || pks.len() > MAX_SIGNER_IN_MULTISIG
492            || weights.contains(&0)
493            || weights
494                .iter()
495                .map(|w| *w as ThresholdUnit)
496                .sum::<ThresholdUnit>()
497                < threshold
498            || pks
499                .iter()
500                .enumerate()
501                .any(|(i, pk)| pks.iter().skip(i + 1).any(|other_pk| *pk == *other_pk))
502        {
503            return Err(IotaError::InvalidSignature {
504                error: "Invalid multisig public key construction".to_string(),
505            });
506        }
507
508        Ok(MultiSigPublicKey {
509            pk_map: pks.into_iter().zip(weights).collect(),
510            threshold,
511        })
512    }
513
514    pub fn get_index(&self, pk: &PublicKey) -> Option<u8> {
515        self.pk_map.iter().position(|x| &x.0 == pk).map(|x| x as u8)
516    }
517
518    pub fn threshold(&self) -> &ThresholdUnit {
519        &self.threshold
520    }
521
522    pub fn pubkeys(&self) -> &Vec<(PublicKey, WeightUnit)> {
523        &self.pk_map
524    }
525
526    pub fn validate(&self) -> Result<MultiSigPublicKey, FastCryptoError> {
527        let pk_map = self.pubkeys();
528        if self.threshold == 0
529            || pk_map.is_empty()
530            || pk_map.len() > MAX_SIGNER_IN_MULTISIG
531            || pk_map.iter().any(|(_pk, weight)| *weight == 0)
532            || pk_map
533                .iter()
534                .map(|(_pk, weight)| *weight as ThresholdUnit)
535                .sum::<ThresholdUnit>()
536                < self.threshold
537            || pk_map.iter().enumerate().any(|(i, (pk, _weight))| {
538                pk_map
539                    .iter()
540                    .skip(i + 1)
541                    .any(|(other_pk, _weight)| *pk == *other_pk)
542            })
543        {
544            return Err(FastCryptoError::InvalidInput);
545        }
546        Ok(self.to_owned())
547    }
548}