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                    let pk =
151                        Ed25519PublicKey::from_bytes(subsig_pubkey.as_ref()).map_err(|_| {
152                            IotaError::InvalidSignature {
153                                error: "Invalid ed25519 pk bytes".to_string(),
154                            }
155                        })?;
156                    pk.verify(
157                        &digest,
158                        &s.try_into().map_err(|_| IotaError::InvalidSignature {
159                            error: "Invalid ed25519 signature bytes".to_string(),
160                        })?,
161                    )
162                }
163                CompressedSignature::Secp256k1(s) => {
164                    let pk =
165                        Secp256k1PublicKey::from_bytes(subsig_pubkey.as_ref()).map_err(|_| {
166                            IotaError::InvalidSignature {
167                                error: "Invalid k1 pk bytes".to_string(),
168                            }
169                        })?;
170                    pk.verify(
171                        &digest,
172                        &s.try_into().map_err(|_| IotaError::InvalidSignature {
173                            error: "Invalid k1 signature bytes".to_string(),
174                        })?,
175                    )
176                }
177                CompressedSignature::Secp256r1(s) => {
178                    let pk =
179                        Secp256r1PublicKey::from_bytes(subsig_pubkey.as_ref()).map_err(|_| {
180                            IotaError::InvalidSignature {
181                                error: "Invalid r1 pk bytes".to_string(),
182                            }
183                        })?;
184                    pk.verify(
185                        &digest,
186                        &s.try_into().map_err(|_| IotaError::InvalidSignature {
187                            error: "Invalid r1 signature bytes".to_string(),
188                        })?,
189                    )
190                }
191                CompressedSignature::ZkLogin(z) => {
192                    let authenticator = ZkLoginAuthenticator::from_bytes(&z.0).map_err(|_| {
193                        IotaError::InvalidSignature {
194                            error: "Invalid zklogin authenticator bytes".to_string(),
195                        }
196                    })?;
197                    authenticator
198                        .verify_claims(
199                            value,
200                            IotaAddress::from(subsig_pubkey),
201                            verify_params,
202                            zklogin_inputs_cache.clone(),
203                        )
204                        .map_err(|e| FastCryptoError::GeneralError(e.to_string()))
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(
215                            value,
216                            IotaAddress::from(subsig_pubkey),
217                            verify_params,
218                            zklogin_inputs_cache.clone(),
219                        )
220                        .map_err(|e| FastCryptoError::GeneralError(e.to_string()))
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
249/// Interpret a bitmap of 01s as a list of indices that is set to 1s.
250/// e.g. 22 = 0b10110, then the result is [1, 2, 4].
251pub 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    /// Create MultiSig from its fields without validation
268    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    /// This combines a list of [enum Signature] `flag || signature || pk` to a
281    /// MultiSig. The order of full_sigs must be the same as the order of
282    /// public keys in [enum MultiSigPublicKey]. e.g. for [pk1, pk2, pk3,
283    /// pk4, pk5], [sig1, sig2, sig5] is valid, but [sig2, sig1, sig5] is
284    /// invalid.
285    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_zklogin_sigs(&self) -> Result<Vec<ZkLoginAuthenticator>, IotaError> {
346        let authenticator_as_bytes: Vec<_> = self
347            .sigs
348            .iter()
349            .filter_map(|s| match s {
350                CompressedSignature::ZkLogin(z) => Some(z),
351                _ => None,
352            })
353            .collect();
354        authenticator_as_bytes
355            .iter()
356            .map(|z| {
357                ZkLoginAuthenticator::from_bytes(&z.0).map_err(|_| IotaError::InvalidSignature {
358                    error: "Invalid zklogin authenticator bytes".to_string(),
359                })
360            })
361            .collect()
362    }
363
364    pub fn get_indices(&self) -> Result<Vec<u8>, IotaError> {
365        as_indices(self.bitmap)
366    }
367
368    pub fn has_passkey_sigs(&self) -> bool {
369        self.sigs
370            .iter()
371            .any(|s| matches!(s, CompressedSignature::Passkey(_)))
372    }
373}
374
375impl ToFromBytes for MultiSig {
376    fn from_bytes(bytes: &[u8]) -> Result<MultiSig, FastCryptoError> {
377        // The first byte matches the flag of MultiSig.
378        if bytes.first().ok_or(FastCryptoError::InvalidInput)? != &SignatureScheme::MultiSig.flag()
379        {
380            return Err(FastCryptoError::InvalidInput);
381        }
382        let mut multisig: MultiSig =
383            bcs::from_bytes(&bytes[1..]).map_err(|_| FastCryptoError::InvalidSignature)?;
384        multisig.init_and_validate()
385    }
386}
387
388impl FromStr for MultiSig {
389    type Err = IotaError;
390
391    fn from_str(s: &str) -> Result<Self, Self::Err> {
392        let bytes = Base64::decode(s).map_err(|_| IotaError::InvalidSignature {
393            error: "Invalid base64 string".to_string(),
394        })?;
395        let sig = MultiSig::from_bytes(&bytes).map_err(|_| IotaError::InvalidSignature {
396            error: "Invalid multisig bytes".to_string(),
397        })?;
398        Ok(sig)
399    }
400}
401
402/// This initialize the underlying bytes representation of MultiSig. It encodes
403/// [struct MultiSig] as the MultiSig flag (0x03) concat with the bcs bytes
404/// of [struct MultiSig] i.e. `flag || bcs_bytes(MultiSig)`.
405impl AsRef<[u8]> for MultiSig {
406    fn as_ref(&self) -> &[u8] {
407        self.bytes
408            .get_or_try_init::<_, eyre::Report>(|| {
409                let as_bytes = bcs::to_bytes(self).expect("BCS serialization should not fail");
410                let mut bytes = Vec::with_capacity(1 + as_bytes.len());
411                bytes.push(SignatureScheme::MultiSig.flag());
412                bytes.extend_from_slice(as_bytes.as_slice());
413                Ok(bytes)
414            })
415            .expect("OnceCell invariant violated")
416    }
417}
418
419/// The struct that contains the public key used for authenticating a MultiSig.
420#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
421pub struct MultiSigPublicKey {
422    /// A list of public key and its corresponding weight.
423    pk_map: Vec<(PublicKey, WeightUnit)>,
424    /// If the total weight of the public keys corresponding to verified
425    /// signatures is larger than threshold, the MultiSig is verified.
426    threshold: ThresholdUnit,
427}
428
429impl MultiSigPublicKey {
430    /// Construct MultiSigPublicKey without validation.
431    pub fn insecure_new(pk_map: Vec<(PublicKey, WeightUnit)>, threshold: ThresholdUnit) -> Self {
432        Self { pk_map, threshold }
433    }
434
435    pub fn new(
436        pks: Vec<PublicKey>,
437        weights: Vec<WeightUnit>,
438        threshold: ThresholdUnit,
439    ) -> Result<Self, IotaError> {
440        if pks.is_empty()
441            || weights.is_empty()
442            || threshold == 0
443            || pks.len() != weights.len()
444            || pks.len() > MAX_SIGNER_IN_MULTISIG
445            || weights.contains(&0)
446            || weights
447                .iter()
448                .map(|w| *w as ThresholdUnit)
449                .sum::<ThresholdUnit>()
450                < threshold
451            || pks
452                .iter()
453                .enumerate()
454                .any(|(i, pk)| pks.iter().skip(i + 1).any(|other_pk| *pk == *other_pk))
455        {
456            return Err(IotaError::InvalidSignature {
457                error: "Invalid multisig public key construction".to_string(),
458            });
459        }
460
461        Ok(MultiSigPublicKey {
462            pk_map: pks.into_iter().zip(weights).collect(),
463            threshold,
464        })
465    }
466
467    pub fn get_index(&self, pk: &PublicKey) -> Option<u8> {
468        self.pk_map.iter().position(|x| &x.0 == pk).map(|x| x as u8)
469    }
470
471    pub fn threshold(&self) -> &ThresholdUnit {
472        &self.threshold
473    }
474
475    pub fn pubkeys(&self) -> &Vec<(PublicKey, WeightUnit)> {
476        &self.pk_map
477    }
478
479    pub fn validate(&self) -> Result<MultiSigPublicKey, FastCryptoError> {
480        let pk_map = self.pubkeys();
481        if self.threshold == 0
482            || pk_map.is_empty()
483            || pk_map.len() > MAX_SIGNER_IN_MULTISIG
484            || pk_map.iter().any(|(_pk, weight)| *weight == 0)
485            || pk_map
486                .iter()
487                .map(|(_pk, weight)| *weight as ThresholdUnit)
488                .sum::<ThresholdUnit>()
489                < self.threshold
490            || pk_map.iter().enumerate().any(|(i, (pk, _weight))| {
491                pk_map
492                    .iter()
493                    .skip(i + 1)
494                    .any(|(other_pk, _weight)| *pk == *other_pk)
495            })
496        {
497            return Err(FastCryptoError::InvalidInput);
498        }
499        Ok(self.to_owned())
500    }
501}