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};
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/// The struct that contains signatures and public keys necessary for
43/// authenticating a MultiSig.
44#[serde_as]
45#[derive(Debug, Serialize, Deserialize, Clone)]
46pub struct MultiSig {
47    /// The plain signature encoded with signature scheme.
48    sigs: Vec<CompressedSignature>,
49    /// A bitmap that indicates the position of which public key the signature
50    /// should be authenticated with.
51    bitmap: BitmapUnit,
52    /// The public key encoded with each public key with its signature scheme
53    /// used along with the corresponding weight.
54    multisig_pk: MultiSigPublicKey,
55    /// A bytes representation of [struct MultiSig]. This helps with
56    /// implementing [trait AsRef<[u8]>].
57    #[serde(skip)]
58    bytes: OnceCell<Vec<u8>>,
59}
60
61/// Necessary trait for [struct SenderSignedData].
62impl 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
70/// Necessary trait for [struct SenderSignedData].
71impl Eq for MultiSig {}
72
73/// Necessary trait for [struct SenderSignedData].
74impl 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        // Verify each signature against its corresponding signature scheme and public
114        // key. TODO: further optimization can be done because multiple Ed25519
115        // signatures can be batch verified.
116        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
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_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        // The first byte matches the flag of MultiSig.
359        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
383/// This initialize the underlying bytes representation of MultiSig. It encodes
384/// [struct MultiSig] as the MultiSig flag (0x03) concat with the bcs bytes
385/// of [struct MultiSig] i.e. `flag || bcs_bytes(MultiSig)`.
386impl 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/// The struct that contains the public key used for authenticating a MultiSig.
401#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
402pub struct MultiSigPublicKey {
403    /// A list of public key and its corresponding weight.
404    pk_map: Vec<(PublicKey, WeightUnit)>,
405    /// If the total weight of the public keys corresponding to verified
406    /// signatures is larger than threshold, the MultiSig is verified.
407    threshold: ThresholdUnit,
408}
409
410impl MultiSigPublicKey {
411    /// Construct MultiSigPublicKey without validation.
412    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}